Support code-declared inlined feedback in memory circuits#665
Draft
kvmto wants to merge 4 commits into
Draft
Conversation
Codes may declare record->detector feedback corrections as data via two new virtuals (get_inlined_feedback / get_observable_inlined_feedback, empty default = existing behavior); memory_circuit folds the declared records into its detector and observable definitions, so measurement schemes with readout byproducts get deterministic detectors through sample_memory_circuit without a physical correction gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: kvmto <kmato@nvidia.com>
…d_feedback The record->detector composition rule now lives in detector_error_model as a CSR layout builder; the memory_circuit kernel is a thin emitter walking the layout. Behavior is bit-identical; all suites pass unchanged. Signed-off-by: kvmto <kmato@nvidia.com>
…amed device helpers The kernel keeps its flat-matrix signature; all record-composition logic lives in reusable __qpu__ functions in device/inlined_feedback.h. The host CSR layout builder (build_inlined_feedback_layout) is retained for record-stream consumers and pinned to the kernel by an M2D conformance test Signed-off-by: kvmto <kmato@nvidia.com>
Signed-off-by: kvmto <kmato@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
memory_circuithardcodes the record→detector combination rule: every detector is "same record slot, two consecutive rounds" plus fixed boundary closures. This assumes each returned ancilla measurement is already a clean stabilizer value. Measurement schemes whose readout carries record-conditioned byproducts — e.g. superdense (paired-ancilla) extraction, where one ancilla's outcome heralds a Pauli byproduct on part of the plaquette — cannot express their true detector parities. Today such codes must apply the correction as a physical gate (an extra noise location — on hardware this correction is a classical frame update, not a gate) or abandon the framework path entirely.Stim solves this with record-controlled Paulis plus
with_inlined_feedback(); the CUDA-Q kernel language has no equivalent, anddem_from_kernelrejects measurement-dependent branching. This PR provides the cudaqx-level resolution.Design
Make the record→detector combination rule a declarative property of the code, with the identity default preserving today's behavior exactly:
The code declares data, not behavior (device kernels cannot call host callbacks mid-trace; the data rides the same rail as the parity matrices):
virtual cudaqx::tensor<uint8_t> code::get_inlined_feedback() const— shape[numCols × numCols],numCols = num_ancilla_qubits,[Z][X]per-round record order. Entry(j, k) = 1: the cross-round detector for recordjadditionally XORs recordkof the earlier round; the final boundary detector for recordjadditionally XORs recordkof the last round. Default: empty (identity).virtual cudaqx::tensor<uint8_t> code::get_observable_inlined_feedback() const— shape[num_observables × numCols]; entry(m, k) = 1: observablemadditionally XORs recordkof every round. Default: empty.experiments.cppvalidates and threads the flattened data into everymemory_circuitinvocation; inside the kernel, empty feedback keeps the existing code paths verbatim (the legacycudaq::detectors(prev, curr)line and observable emission are untouched under the default), while non-empty feedback emits per-recordcudaq::detectorparities, extended boundary detectors, and per-round observable record collection (onelogical_observablecall per observable, order unchanged). Python plugins simply define the two methods returning numpy arrays; the nanobind bridge forwards them.Wrong declarations fail loudly: stim's determinism analysis inside
dem_from_kernelvalidates every declared parity on each call.Tests
test_qec46,test_qec_stim7,test_decoders43,test_dem_sampling36) and Pythontest_code.pypass unchanged — with empty feedback the emitted circuit structure is bit-identical to main.[[0,1],[0,0]]/[[0,1]]— derivation documented in-code; verified unique by exhaustive search): noiselesssample_memory_circuityields all-zero syndromes and deterministic logical readout. Negative control: the identical circuit without the declaration throws stim's "non-deterministic detectors" — proving the feature does the work.@qec.codeplugin (positive + negative), exercising the trampoline and handle paths.Notes for reviewers
stabilizer_round— with this feature its byproduct correction moves from a physical CX into the detector definitions, making the framework-path DEM noise-faithful to the classical-feedforward convention.Update: rule extraction (second + third commits)
The initial commit computed the record-composition arithmetic inline in the kernel. Two follow-up commits restructure this into the intended data → rule → emission split, with no user-visible change (all suites bit-identical):
lib/device/inlined_feedback.h: named, documented__qpu__record builders (cross_round_detector_records,boundary_detector_records,observable_feedback_offsets,collect_observable_feedback_round,observable_support_records). Thememory_circuitkernel is now a thin emitter: state prep, rounds, andcudaq::detector/cudaq::logical_observablecalls over the vectors these helpers return. Its signature is unchanged (the two flattened matrices — the declared data, mirroring the getters).cudaq::qec::build_inlined_feedback_layoutindetector_error_model: the same composition rule computed host-side as a CSRinlined_feedback_layout, for record-stream consumers that cannot run a kernel (the planned realtimeD_sparseaugmentation and batch record folds). Unit-tested standalone.HostLayoutMatchesKernelM2D) derives the expected measurement→detector matrix from the host layout and asserts it equals the M2D thatdem_from_kernelextracts from the kernel's emissions on the toy. The device fold and the host rule cannot drift apart silently.Emission stays in-kernel deliberately: stim builds the probabilistic DEM and runs its determinism gate only over kernel-declared detectors.
Test tally after the refactor:
test_qec55 (49 + 5 layout unit tests + 1 conformance),test_qec_stim7,test_decoders43,test_dem_sampling36, Pythontest_code.py21.Update: basis-split observable feedback (fourth commit)
Working the motivating case (#658) through this API surfaced a defect in the original design: observable inlined feedback is basis-dependent, while the single
get_observable_inlined_feedback()was threaded identically into Z- and X-basis memory experiments. In the superdense color code, the heralding records must be folded into the Z-basis logical observable but not the X-basis one; with a single basis-blind matrix, every X-basis run fails stim's determinism gate (confirmed independently by GF(2) analysis of the record recursion and by noiseless sampling).The fix mirrors the existing basis-selected rail for the observable data matrix (
is_z_prep ? get_observables_z() : get_observables_x()): the getter is split intoget_observable_inlined_feedback_z()/_x()(empty defaults), and each driver queries only the selected getter — pinned by a selection test in both directions. The detector-feedback matrix remains a single basis-neutral getter, which #658's validation confirms is sufficient (its detector matrix serves both bases unchanged).Status of the motivating case: with this API, the color-code plugin (#658) declares its matrices and its framework-path DEM matches an independently constructed reference DEM bit-exactly in both bases (mechanism sets and probabilities), replacing the physical correction gate entirely.
Status
Draft for design feedback on the API shape and semantics.
🤖 Generated with Claude Code