Skip to content

pulse/ops/scan: substitute stream symbol in Scan body during pulsification#2346

Open
JulienBalianSonos wants to merge 2 commits into
mainfrom
explore/scan-body-source-derived
Open

pulse/ops/scan: substitute stream symbol in Scan body during pulsification#2346
JulienBalianSonos wants to merge 2 commits into
mainfrom
explore/scan-body-source-derived

Conversation

@JulienBalianSonos
Copy link
Copy Markdown
Collaborator

@JulienBalianSonos JulienBalianSonos commented Jun 5, 2026

Alternative approach to #2337, addressing kali's feedback that the body source / outer fact discrepancy should not happen in the first place rather than being patched after the fact.

Where the drift comes from

Instrumenting PulsedModel::new and OptimizerSession::run_all_passes with a per-Scan checkpoint that compares outer input fact vs Scan body source fact shows on dpdfnet-2 (DPRNN x2):

  • Every pre-pulse declutter checkpoint is [OK] (108 Scans across the model). Both outer and body still carry symbolic STREAM in their shape expressions, e.g. 1 + -1*min(2, -1 + (STREAM)/160) + (STREAM)/160.
  • After Pulsifier::translate_model_with_mappings, on the very first before any pass checkpoint of the post-pulse declutter loop, the Scan slots show:
Scan #136 enc__dprnn_erb___0__gru2_l0_gru_h_n  iters=4
  slot 0 State  outer=1,2,64  body_src=1,2,64  [OK]
  slot 1 Scan   outer=8,1,64  body_src=1,2,64  [DRIFT]
  slot 2 Scan   outer=8,1,64  body_src=1,2,64  [DRIFT]
  slot 3 Scan   outer=8,1,64  body_src=1,2,64  [DRIFT]

The drift is born inside Pulsifier::translate_node for the Scan path (pulse/src/ops/scan.rs:43-51). That path clones the original Scan op, adjusts skip, and wires it as-is. The body is untouched, so its source facts still carry STREAM through their shape expressions. Meanwhile the outer wires the pulsifier just rewrote are concretized (STREAM substituted with the pulse value). Post-pulse declutter then folds the same symbolic expression to different literals in the outer scope and the body scope, and the disagreement is visible from that point onward.

PR #2337's declutter_resync_body_source_facts rule ran later and rebuilt the body to match outer. Functionally correct, but per kali, treating the symptom rather than the cause.

The fix

When the Scan pulsifier wraps the original op for the non-recursive path, apply the same symbol -> pulse substitution to the body and to the output_mapping that the outer pulsifier just applied to wire facts. After that, body and outer hold the same concrete expressions; post-pulse declutter folds both scopes to the same literal because there are no symbolic STREAM-dependent expressions left to fold differently.

The recursive-pulse path (pulse/src/ops/scan.rs:52-56) already bakes the substitution into the body via PulsedModel::new(&op.body, ...). Only the cloned output_mapping needed the same substitution for symmetry.

Verification on dpdfnet-2

Same checkpoint, with and without the fix:

BEFORE:  outer=8,1,64  body_src=1,2,64  [DRIFT]
AFTER:   outer=8,2,64  body_src=1,2,64  [OK]

The non-scan axis 2 is the correct evaluation at STREAM=320 of the symbolic expression that surrounds the chunking math. With the fix, outer and body agree on it; without the fix, outer was independently folded to 1.

grep -c DRIFT on the full trace: 0 after fix, 108 before fix.

Pulse pipeline runs to completion. 151 chunks of 320 samples (3.00s audio) processed in 0.566s, 5.30x real-time, 3.751 ms/chunk.

Test plan

  • harness/core-proptest-pulse: 72/72 pass (1 ignored), unchanged vs main.
  • Manual: dpdfnet-2 streaming pulse on a 3s 16kHz clip, output WAV correct length, no warnings.
  • New: harness/nnef-test-cases/scan-body-stream-drift/. Minimal NNEF graph that wires a streaming input into a Scan whose Full slot is an internally-constructed [S, 4] zero tensor. After pulsification with --pass pulse, the body source for the Full slot must be 4,4 (concrete) rather than S,4 (symbolic). Verified: passes with fix (exit 0), fails without fix (exit 1, grep on --pass pulse dump catches the residual stream symbol in the body source).

@JulienBalianSonos JulienBalianSonos force-pushed the explore/scan-body-source-derived branch 2 times, most recently from 78a9882 to 6d94196 Compare June 5, 2026 10:28
@JulienBalianSonos JulienBalianSonos force-pushed the explore/scan-body-source-derived branch from 1915403 to c5f93a5 Compare June 5, 2026 12:07
@JulienBalianSonos JulienBalianSonos marked this pull request as ready for review June 5, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant