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)
Observation
encodeLayerends with the MoE residual fold, and the next layer'sencodePreMoEstarts with the input norm:That pair is exactly what
gdn_resid_postnorm_rowsalready 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'sinputLNtoencodeGdnResidPostNormRowsinstead of emittingresid_addthenrmsnorm:encodeResidAdd(its consumer is the final norm, not a layer input).Requires threading
layers[i+1].inputLNinto the layer-i encode, or restructuring the loop to encode the fold at the head of layer i+1 with the previous layer'smoeOut— the latter mirrors thependingResididiom already used inSeedlessMetalForward.encodeMixerHalf, so it is the closer precedent.Effect
QWISP_FUSE_MOE2=1shape, which is not the default).hBufwrite→read round trip per layer (H×2 bytes each way at M=1; M×H at verify widths).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_rowsdoes not carry an unrounded float register into the norm. It rounds the residual suminto
half hn, stores it, and the normalization reads back from that storedhalf(
SeedlessFusedVerify.swift:907,:936). The separate residual kernel also writeshalf(
SeedlessMetalForward.swift:1019). So "store-then-reload" vs "register reuse" is a distinction without adifference 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-layerwiring is new, but the arithmetic question is settled.
Gates
hBufandnormedacross a multi-layer synthetic stack, M ∈ {1, 2, 8}, both layer types. Bumps the RAWTESTStotal = Ncounter.QWISP_FUSE_XLAYER=0).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(pendingResididiom)