Skip to content

Fix FFN down-projection HBM prefetch advance for multi-K-tile#49

Merged
GeorgeWu1204 merged 1 commit into
mainfrom
fix/ffn-down-multi-tile-prefetch
May 28, 2026
Merged

Fix FFN down-projection HBM prefetch advance for multi-K-tile#49
GeorgeWu1204 merged 1 commit into
mainfrom
fix/ffn-down-multi-tile-prefetch

Conversation

@booth-algo

@booth-algo booth-algo commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Summary

The down-projection weight prefetch in _ffn_asm_with_loops and _ffn_asm_fused_up_gate used _load_large_int(a_actual, mlen*hidden_size) to advance the HBM offset register between K-tile prefetches. This overwrote the register instead of adding to it, causing several silent miscomputations:

  • At K_tiles >= 3 the prefetch for K-tiles 2..N-1 all read from HBM[base + mlen*hidden_size] instead of advancing by that stride each iteration.
  • At K_tiles == 2 combined with multi-outer-iter (out_size > mlen), K-tile 1 of outer iters > 0 read from the wrong absolute address because the per-outer-iter base offset got dropped.

The up-projection in the same function already used _addi_large_int correctly; the down path now matches at all three call sites.

Also: _emit_ffn_projection_unrolled's K-split V_ADD_VV accumulator loop count is now math.ceil(output_elements / vlen) instead of integer division so the tail vector when output_elements is not a multiple of vlen is no longer dropped.

Test plan

Verified with the ATen FFN testbench in PLENA_Simulator (PR AICrossSim/PLENA_Simulator#60). Matrix spans single-tile, multi-K-tile, multi-outer-iter, and BLEN 4–64:

mlen blen hidden inter up K×out down K×out Before After
64 8 128 256 2×4 4×2 FAIL PASS
64 16 128 256 2×4 4×2 FAIL PASS
64 32 128 256 2×4 4×2 FAIL PASS
64 64 192 384 3×6 6×3 FAIL PASS
64 4 192 64 3×1 1×3 PASS PASS
64 4 64 192 1×3 3×1 FAIL PASS
64 4 256 256 4×4 4×4 FAIL PASS
64 4 64 128 1×2 2×1 PASS PASS
128 8 128 256 1×2 2×1 PASS PASS
128 32 256 256 2×2 2×2 FAIL PASS
128 64 256 512 2×4 4×2 FAIL PASS
128 16 384 384 3×3 3×3 FAIL PASS
128 16 128 512 1×4 4×1 FAIL PASS
128 16 512 128 4×1 1×4 FAIL PASS
256 64 256 512 1×2 2×1 FAIL PASS¹
256 64 512 512 2×2 2×2 FAIL PASS¹

¹ MLEN=256 additionally required the HBM tile-align fix on the simulator side (PR AICrossSim/PLENA_Simulator#60); with both, FFN passes at MLEN=256.

  • Up-projection codegen path unchanged (was already correct)
  • FFN multi-K-tile multi-outer-iter passes at MLEN=64 and MLEN=128
  • FFN passes at MLEN=256 (with the companion simulator HBM tile-align fix)
  • Full ATen sweep (linear, rms_norm, layer_norm, softmax, embedding_add, rope, flash_attn_gqa, flash_attn_mha, ffn) passes at MLEN=64 and MLEN=128

The down-projection weight prefetch in `_ffn_asm_with_loops` and
`_ffn_asm_fused_up_gate` used `_load_large_int(a_actual, mlen*hidden_size)`
to advance the HBM offset register between K-tile prefetches. This
overwrote the register instead of adding to it, so:

  * At K_tiles >= 3 the prefetch for tiles 2..N-1 all read from
    HBM[base + mlen*hidden_size] instead of advancing by that stride
    each iteration.
  * At K_tiles == 2 combined with multi-outer-iter (out_size > mlen),
    K-tile 1 of outer iters > 0 read from the wrong absolute address
    because the per-outer-iter base offset got dropped.

The up-projection in the same function already used `_addi_large_int`
correctly; the down path now matches. Fix applied at all three
down-projection prefetch sites (lines ~1333, 1699, 1777).

Also: `_emit_ffn_projection_unrolled` switched the K-split V_ADD_VV
accumulator loop count from integer division to `math.ceil` so the
tail vector when `output_elements` is not a multiple of `vlen` is no
longer truncated; `import math` added.

Verified with ATen FFN testbench at MLEN=64 and MLEN=128 across
hidden/inter combinations exercising single-tile, multi-K-tile, and
multi-outer-iter codegen paths (all pass).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
booth-algo added a commit to AICrossSim/PLENA_Simulator that referenced this pull request May 28, 2026
…+ BEHAVIOR.CONFIG

After PR #60's config-chain fix exposed the emulator's true MLEN, three
ATen tests still failed because their golden references and per-build
test plumbing weren't matching the emulator's actual precision and
HBM layout. This commit lands the remaining fixes plus the down-side
of the K-split bug (in the linked PLENA_Compiler PR).

- `golden.py`: `golden_rope`, `golden_rms_norm`, `golden_layer_norm` now
  pre-quantise their inputs through the HBM MXFP8 path via
  `_load_to_vector_fp(...)` before BF16 compute, matching the emulator's
  HBM (MXFP8) → VRAM (BF16) staging. Previously they only quantised at
  the vector precision and the deltas showed up as 0% match.

- `rope_test.py`: pass an explicit compact `tensor_layouts` and
  compiler-derived `hbm_addrs` (read from
  `prog._compiler.get_hbm_layout(name).hbm_base_addr`). Without this, a
  stale `tensor_layouts.json` (or default padding) shifted QROT/COS/SIN
  off the addresses the prefetch ISA reads.

- `norm_test.py`: install `fp_preload[eps_offset]=eps`,
  `fp_preload[reci_hid_offset]=1/hidden` at the offsets the chosen norm
  op actually reads (rms uses 0/1, layer uses 1/2). Without preload the
  emulator divided by zero and emitted `inf`. Default `hidden = mlen`
  to avoid mixing the norm test with the FFN K-split path.

- `flash_attention_gqa_test.py`: set `args.hlen = h_qkv` before
  `setup_hw` so the per-build HLEN matches the packed-attention
  constraint `broadcast_amount * hlen == mlen`.

- `flash_attention_mha_test.py`: default `seq_len` / `head_dim` to MLEN
  so the comparison-row layout matches at MLEN=128 (previously the
  64×64 defaults at MLEN=128 produced a 4096-vs-16384 length mismatch
  in `slice_rows`).

- `configurable.py`: `HardwareConfig.write_toml` now also writes the
  tile dimensions under a `BEHAVIOR.CONFIG` section. `PlenaCompiler`'s
  `_behavior_config_value` reads from `mode="BEHAVIOR"` (see
  `PLENA_Compiler/utils/load_config.py`); without the section, HLEN
  silently defaulted to MLEN and broke packed attention codegen.

- `PLENA_Compiler` submodule: bumped to the fix branch with the
  `_load_large_int` → `_addi_large_int` correction in the down-
  projection HBM prefetch (see AICrossSim/PLENA_Compiler#49).

Sweep result at MLEN=64 and MLEN=128 (linear, rms_norm, layer_norm,
fpvar_softmax, embedding_add, rope, flash_attn_gqa, flash_attn_mha,
ffn, norm rms, norm layer): all pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@booth-algo booth-algo requested a review from GeorgeWu1204 May 28, 2026 17:21
@GeorgeWu1204 GeorgeWu1204 merged commit acea1dd into main May 28, 2026
3 checks passed
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.

2 participants