Skip to content

perf(seedless): fold the cross-layer resid_add + input rmsnorm into the existing gdn_resid_postnorm_rows kernel (−39 dispatch/token) #144

Description

@penta2himajin

Observation

encodeLayer ends with the MoE residual fold, and the next layer's encodePreMoE starts with the input norm:

// layer i, tail
SeedlessFusedVerify.encodeResidAdd(enc, h: hBuf, r: moeOut, total: M * H)
// layer i+1, head
SeedlessFusedVerify.encodeRmsNormRows(enc, x: hBuf, w: L.inputLN, out: normed, rows: M, D: H, eps: eps)

That pair is exactly what gdn_resid_postnorm_rows already computes — h += r; out = rmsnorm(h, w). The kernel exists, ships, and is already used one step earlier in the same function for the mixer residual + post-norm (F5, notes/07 §3 Wave 2). It is being applied to one of the two structurally identical sites and not the other.

Change

In encodeLayer / encodeLayerBolt, hand the next layer's inputLN to encodeGdnResidPostNormRows instead of emitting resid_add then rmsnorm:

  • layer 0: unchanged (no preceding MoE residual).
  • layers 1..n-1: one fused dispatch replaces two.
  • final layer: keep the plain encodeResidAdd (its consumer is the final norm, not a layer input).

Requires threading layers[i+1].inputLN into the layer-i encode, or restructuring the loop to encode the fold at the head of layer i+1 with the previous layer's moeOut — the latter mirrors the pendingResid idiom already used in SeedlessMetalForward.encodeMixerHalf, so it is the closer precedent.

Effect

Small in isolation. Worth doing anyway because it is nearly free, it is independent of #143's outcome, and it gives P1 a known-magnitude delta to calibrate the dispatch counter against.

Why this is not a rewrite (Prohibition 3)

No new kernel, no kernel edit, no change to the op sequence's arithmetic — only which encode helper emits it. Same class of change as the F5 fusion that introduced the kernel.

Bit-exactness

Constructive, and verified by reading the kernel (2026-07-25) rather than assumed:

gdn_resid_postnorm_rows does not carry an unrounded float register into the norm. It rounds the residual sum
into half hn, stores it, and the normalization reads back from that stored half
(SeedlessFusedVerify.swift:907, :936). The separate residual kernel also writes half
(SeedlessMetalForward.swift:1019). So "store-then-reload" vs "register reuse" is a distinction without a
difference here — there is no f16 rounding divergence to worry about, which was the one real risk in this change.

The equivalence is moreover already locked for the mixer-side use by the F5 test at M ∈ {1, 8}
(SeedlessVerifyTests.swift:2177). This issue still gets its own locked test (G-A) because the cross-layer
wiring is new, but the arithmetic question is settled.

Gates

Refs

#143 (Step 0 / dispatch inventory — this issue does not depend on its outcome) · notes/10:12 (the canonical "
"2026-07-05 recon already classified this exact change "(d) MoE-resid + next-input-norm fold = GO, stage 2, "
"別 devloop" — this issue is picking up a scheduled item, not proposing a new one)
· notes/07 §3 Wave 2, §6 "
"(F5 and the fusion doctrine; §35 explicitly deferred this fold as cross-layer) · SeedlessFusedVerify.encodeGdnResidPostNormRows, encodePreMoE, encodeLayer · SeedlessMetalForward.encodeMixerHalf (pendingResid idiom)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions