Skip to content

feat(moonshine,transformer-core): fixed-max cross-memory padding mask for streaming decode - #267

Merged
michalharakal merged 1 commit into
feat/true-dynamic-decode-exportfrom
feat/streaming-cross-memory-mask
Jul 30, 2026
Merged

feat(moonshine,transformer-core): fixed-max cross-memory padding mask for streaming decode#267
michalharakal merged 1 commit into
feat/true-dynamic-decode-exportfrom
feat/streaming-cross-memory-mask

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Stacked on #266 (feat/true-dynamic-decode-export). Review/merge that first; this branch is based on it, so its diff is scoped to the cross-mask changes once the base merges.

Streaming ASR finalizes a variable-length encoder memory, but the decoder prefill is fixed-shape (IREE rejects dynamic_reshape for the memory head-split). This zero-pads the cross (encoder) memory to a fixed MAX and masks the padding out of cross-attention — so one prefill + one with_past vmfb pair serve any encoder length ≤ MAX, while the self-cache stays dynamic (growing).

What

  • transformer-core MultiHeadAttention — optional trailing crossMask on attentionImpl + forwardWithKV, applied as SDPA mask = slidingMask ?: crossMask. Default nullbyte-identical for all existing callers (verified: llm-core 101 + gemma 77 tests green, incl. MHA/SDPA/sliding-window coverage).
  • MoonshineDecoder — threads crossMask through the layer/model forwardPrefill (via MHA.forwardWithKV) and the hand-wired forwardWithPast (sdpaMerge). Backward compatible (trailing default; v1 positional callers untouched).
  • MoonshineV2DecoderBakeTestMOONSHINE_V2_MAX_MEM=N pads both prefill memory and the with_past cross cache to N and adds a crossMask input [1,1,1,N] to both graphs.

Verification

End-to-end: masked decode over memory padded 64→96 (with a garbage tail) == onnxruntime unpadded-64 token-for-token on real audio; the control (no mask) corrupts. Since verified on the SL2610 board (full v2 pipeline decodes the reference clip identically to ONNX).

… for streaming decode

Streaming ASR finalizes a variable-length encoder memory, but the decoder prefill
is fixed-shape (iree rejects dynamic_reshape for the memory head-split). Fix by
zero-padding the cross (encoder) memory to a fixed MAX and masking the padding out
of cross-attention, so ONE prefill + ONE with_past vmfb pair serve any encoder
length ≤ MAX while the self-cache stays dynamic (growing).

- transformer-core MultiHeadAttention: optional trailing `crossMask` on
  attentionImpl + forwardWithKV, applied as SDPA `mask = slidingMask ?: crossMask`.
  Default null → byte-identical for all existing callers (verified: llm-core 101 +
  gemma 77 tests green, incl. MHA/SDPA/sliding-window coverage).
- MoonshineDecoder: thread `crossMask` through the layer/model forwardPrefill
  (via MHA.forwardWithKV) and the hand-wired forwardWithPast (sdpaMerge). Backward
  compatible (trailing default; v1 positional callers untouched).
- MoonshineV2DecoderBakeTest: MOONSHINE_V2_MAX_MEM=N pads both prefill memory and
  with_past cross cache to N and adds a crossMask input [1,1,1,N] to both graphs.

Verified end-to-end: masked decode over memory padded 64→96 (with a garbage tail)
== onnxruntime unpadded-64 token-for-token on real audio; control (no mask) corrupts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@michalharakal
michalharakal force-pushed the feat/streaming-cross-memory-mask branch from dd0495f to 96d925f Compare July 30, 2026 11:11
@michalharakal
michalharakal merged commit 72fa86f into feat/true-dynamic-decode-export Jul 30, 2026
2 checks passed
@michalharakal
michalharakal deleted the feat/streaming-cross-memory-mask branch July 30, 2026 11:49
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