PennyLaneAI / PennyLaneAI/catalyst

Parameter-dependent decomposition rules cannot specialize on constant parameters under capture

Open
#3,172 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
234
Forks
84
Avg merge
2d 15h
Merged PRs (30d)
66

Description

Summary

Under qml.qjit(capture=True), Catalyst's graph-based decomposition compiles a single, generic decomposition rule per operator type, traced with an abstract parameter, and applies it uniformly via the MLIR decompose-lowering pass. As a result, decomposition rules whose structure depends on the concrete value of a parameter cannot specialize, i.e. the parameter is always abstract at rule-compile time.

The concrete motivating case is the adaptive-precision RZ phase-gradient decomposition here: https://github.com/PennyLaneAI/pennylane/pull/10098. It truncates the SemiAdder width (and drops angles that round to zero) based on the concrete angle bits, guarded by:

if adaptive_precision and not math.is_abstract(phi):
    ...  # truncate angle_wires / phase_grad_wires to the significant width

Under capture=True, phi is always abstract when the rule is compiled, so this branch is dead code: the adder is always full width and the Toffoli/T counts are identical for adaptive_precision=True and False. The large (~70%) Toffoli/T reduction that this rule produces under capture=False silently vanishes under capture.

Why "the angles are constants" does not help

Even when the circuit has no arguments and the angles are compile-time constants, the concreteness is not exploited. The angles are concrete literals in the captured plxpr:

import numpy as np, pennylane as qml, jax
qml.capture.enable()

angles = [f * 2 * np.pi for f in (1/2, 1/4, 3/4, 1/64, 7/8, 2.0**-7)]

def circuit():
    for a in angles:
        qml.RZ(a, 0)
    return qml.expval(qml.Z(0))

print(jax.make_jaxpr(circuit)())
# ... operator[op_cls=RZ ...] 3.141592653589793:f32[] 0:i32[]
# ... operator[op_cls=RZ ...] 1.5707963267948966:f32[] 0:i32[]
# ... operator[op_cls=RZ ...] 0.09817477042468103:f32[] 0:i32[]  (etc.)

But the decomposition rule is compiled once, generically, keyed by op-name + wire-count rather than by the concrete angle. The concrete value flows into the compiled rule as a runtime SSA operand (the emitted MLIR computes binary_decimals(phi) at "rule runtime"), so the per-angle structural truncation has nowhere to happen.

The rule still produces correct results, just without the optimization, so this is a silent performance regression relative to capture=False.

Environment
  • catalyst 0.16.0-dev86
  • pennylane 0.46.0-dev88
  • jax 0.7.1
  • macOS arm64

written with the help of genAI

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the graph-based decomposition path and the MLIR decompose-lowering pass, then compare how the adaptive-precision RZ decomposition behaves under capture=True and capture=False. Done means concrete constant parameters can enable the intended structural specialization under capture while preserving correct results.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
compilers, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.