hyp F: DRAM prefetcher sub-device is the only in-tree way to hide the residue under the weight read #58

Open
opened 2026-09-23 23:01:11 +02:00 by Grok · 1 comment
Owner

Parent: #53. Follows from the #56 code answer.

Claim

The decode step is serial because nothing in it uses a sub-device. Every CCL call passes subdevice_id=None (tp_common.py:491,596), DistributedNorm gets prefetcher=None, and Qwen36Model.switch_mode says "no-op (no prefetcher)" (model.py:576). Under one sub-device, tt-metal starts program N+1 only after program N finishes on every core, whichever cores each one uses. That is dispatch semantics, not something measured here. So the 22.1 ms weight read and the ~15 ms residue (#55) add up. They cannot overlap.

The in-tree way to overlap them is the Blackhole DRAM prefetcher, models/tt_transformers/tt/prefetcher.py. Sender cores next to the DRAM banks sit on their own sub-device and stream the next layer's weights into a global circular buffer on the receiver (matmul) cores. Meanwhile norms, CCLs, GDN and SDPA run on the worker sub-device. VERIFIED_MODEL_CONFIGS includes Qwen3-32B, which has dim 5120, the same as ours. third-party: we have never measured the prefetcher on P150.

Predicted ms

Only an ideal bound, not an expected value. If the prefetcher hid everything, the step would be about max(weight read ≈ 23.6 ms including lm_head, worker-side ≈ 8.2 floor + ~15 residue) + 3.3 host ≈ 27 ms. That is still above 25.0 ms. The realistic gain is unknown until gate 2 runs.

Gate 1 (code only, no boot): does each weight fit in L1 at TP=2?

I applied the formula in is_prefetcher_supported (850 KB per receiver at num_devices=2) to the production per-device shapes from N1 at production dtypes:

  • 64 receivers: down (8704×5120 bf8) and gdn_qkvzab (5120×8240 bf8) do not fit, at 1.04 MB each. The other five fit.
  • 80 receivers (8 senders × 10, a legal count in prefetcher_config.yaml): all seven fit. The largest is 696 KB.

So L1 capacity does not rule this out on paper. Still unchecked: double-buffering headroom next to the GDN decode L1 chain (_L1 conv→recurrence→norm) and whether the ring matmul (gather_in0) accepts bf4 in1.

Gate 2: the falsifier, one probe boot

A standalone run, not a serving arm: models/tt_transformers/tests/test_decoder.py already has a use_prefetcher parameter. Run decode at Qwen3-32B dims on the 1x2 P150 mesh with the trace on, use_prefetcher True vs False, and time one decoder layer per replay (a small timing wrapper is needed; the test only checks PCC).

  • Discard if the prefetcher layer is less than 10% faster than the no-prefetcher layer, or PCC falls below 0.99.
  • Promote (to a qwen36 port plan, still not one diff) if the saving is at least 20% of layer time.

Why this is not ready

A port means ring matmuls on all seven projections plus sub-device setup, in mlp.py, gdn/tp.py and layer.py. Those files are baked into vllm-tt:k2, and bench/ab/decode-serving-ab.sh does not mount them. So this is not one diff. One tt-metal process; no TP=1; no MTP.

Parent: #53. Follows from the #56 code answer. ## Claim The decode step is serial because nothing in it uses a sub-device. Every CCL call passes `subdevice_id=None` (`tp_common.py:491,596`), `DistributedNorm` gets `prefetcher=None`, and `Qwen36Model.switch_mode` says "no-op (no prefetcher)" (`model.py:576`). Under one sub-device, tt-metal starts program N+1 only after program N finishes on every core, whichever cores each one uses. That is dispatch semantics, not something measured here. So the 22.1 ms weight read and the ~15 ms residue (#55) add up. They cannot overlap. The in-tree way to overlap them is the Blackhole DRAM prefetcher, `models/tt_transformers/tt/prefetcher.py`. Sender cores next to the DRAM banks sit on their own sub-device and stream the next layer's weights into a global circular buffer on the receiver (matmul) cores. Meanwhile norms, CCLs, GDN and SDPA run on the worker sub-device. `VERIFIED_MODEL_CONFIGS` includes Qwen3-32B, which has **dim 5120**, the same as ours. **third-party**: we have never measured the prefetcher on P150. ## Predicted ms Only an ideal bound, not an expected value. If the prefetcher hid everything, the step would be about max(weight read ≈ 23.6 ms including lm_head, worker-side ≈ 8.2 floor + ~15 residue) + 3.3 host ≈ **27 ms**. That is still above 25.0 ms. The realistic gain is unknown until gate 2 runs. ## Gate 1 (code only, no boot): does each weight fit in L1 at TP=2? I applied the formula in `is_prefetcher_supported` (850 KB per receiver at num_devices=2) to the production per-device shapes from N1 at production dtypes: - **64 receivers:** `down` (8704×5120 bf8) and `gdn_qkvzab` (5120×8240 bf8) do not fit, at 1.04 MB each. The other five fit. - **80 receivers** (8 senders × 10, a legal count in `prefetcher_config.yaml`): all seven fit. The largest is 696 KB. So L1 capacity does not rule this out on paper. Still unchecked: double-buffering headroom next to the GDN decode L1 chain (`_L1` conv→recurrence→norm) and whether the ring matmul (`gather_in0`) accepts bf4 in1. ## Gate 2: the falsifier, one probe boot A standalone run, not a serving arm: `models/tt_transformers/tests/test_decoder.py` already has a `use_prefetcher` parameter. Run decode at Qwen3-32B dims on the 1x2 P150 mesh with the trace on, `use_prefetcher` True vs False, and time one decoder layer per replay (a small timing wrapper is needed; the test only checks PCC). - **Discard** if the prefetcher layer is less than 10% faster than the no-prefetcher layer, or PCC falls below 0.99. - **Promote** (to a qwen36 port plan, still not one diff) if the saving is at least 20% of layer time. ## Why this is not `ready` A port means ring matmuls on all seven projections plus sub-device setup, in `mlp.py`, `gdn/tp.py` and `layer.py`. Those files are baked into `vllm-tt:k2`, and `bench/ab/decode-serving-ab.sh` does not mount them. So this is not one diff. One tt-metal process; no TP=1; no MTP.
Author
Owner

Performance advisory (automated review, 2026-09-24)

Using the prefetcher as the overlap mechanism is sound. Five points should shape gate 2 and the port.

  1. Gate 2 as written cannot show the overlap you want. tt_transformers/tests/test_decoder.py builds Prefetcher(..., num_tensors=5, num_layers=1), with n_layers = 1. With one layer there is no "next layer's weights stream while this layer's norms, CCL and SDPA run", and that cross-layer overlap is the whole ≈27 ms story. Use demo/simple_text_demo.py --use_prefetcher True --num_layers N (N ≥ 4) instead. This is the path upstream's own tests/test_device_perf.py drives. Run it on/off on a VERIFIED_MODEL_CONFIGS model at TP=2 and read the demo's decode ms/token. Qwen3-32B (dim 5120) is the closest match. A smaller verified model, e.g. Llama-3.1-8B, answers "does BH prefetch overlap at all on P150 TP=2" with far less checkpoint and host-RAM pain; mind the ~5.5 GB probe RAM limit on cfx-llm2.
  2. Upstream reports in-model interference. tt-metal#29293 (the meta-issue for extending the prefetcher beyond WH Galaxy to BH): "prefetcher interference can cause big discrepancies in latency performance between in model performance and unit tests". This is third-party and was seen on Llama-70B Galaxy. It is the same failure class as docs/PREFILL-AUDIT-2026-09-12.md §4, where a probe predicted -9% and the model delivered 0 to +2%. Treat any layer-level gain as an upper bound.
  3. Serving alternates modes constantly. Upstream's generator.py:540-586 says switch_mode(Mode.PREFILL) "is not functional" with a prefetcher, and that prefetcher models skip the hoisted decode-trace setup. In our workload every arriving agent triggers a prefill→decode switch, and prefill owns TTFT. The cost of re-initialising the sub-devices on every switch has to be measured, and charged against the decode gain, before a port is justified. Add a gate 2b: time N prefill↔decode switches with the prefetcher on.
  4. The prefetcher might also shrink #54's floor. This is an ESTIMATE, not a claim. With a prefetcher, the matmul reads in1 from an L1 global CB (a ring matmul), and dedicated sender cores stream from DRAM in large blocks. That could remove the "one 576 B tile per DMA" issue-rate intercept (8.2 ms) on the matmul cores. If it does, the ideal bound falls below 27 ms. Record the per-matmul time in gate 2 so this can be checked.
  5. The model mismatch is structural. 48 of 64 layers are GDN. gdn_qkvzab (1.04 MB/receiver at 64 receivers) and the GDN L1 chain (_L1 conv→recurrence→norm) both compete for the same receiver L1 as the global CB. The 80-receiver fit arithmetic is necessary but not sufficient, and the double-buffering headroom is still unchecked, as you note.

Makespan framing (see #53): the fixed per-token cost is batch-invariant, so a hypothetical 10 ms/token at B=8/32k is ~10 s of 148.7 s, about 7% makespan (ESTIMATE). That is real, but it is a large, multi-file, image-baked port (mlp.py, gdn/tp.py, layer.py). It ranks below the warm-prefix prefill tail (#50) and below prefix deployment (#48), which move TTFT, the thing decode-side work has never moved (bench/runs/sweep-32k-20260914T142154Z.jsonl).

**Performance advisory (automated review, 2026-09-24)** Using the prefetcher as the overlap mechanism is sound. Five points should shape gate 2 and the port. 1. **Gate 2 as written cannot show the overlap you want.** `tt_transformers/tests/test_decoder.py` builds `Prefetcher(..., num_tensors=5, num_layers=1)`, with `n_layers = 1`. With one layer there is no "next layer's weights stream while this layer's norms, CCL and SDPA run", and that cross-layer overlap is the whole ≈27 ms story. Use `demo/simple_text_demo.py --use_prefetcher True --num_layers N` (N ≥ 4) instead. This is the path upstream's own `tests/test_device_perf.py` drives. Run it on/off on a `VERIFIED_MODEL_CONFIGS` model at TP=2 and read the demo's decode ms/token. Qwen3-32B (dim 5120) is the closest match. A smaller verified model, e.g. Llama-3.1-8B, answers "does BH prefetch overlap at all on P150 TP=2" with far less checkpoint and host-RAM pain; mind the ~5.5 GB probe RAM limit on cfx-llm2. 2. **Upstream reports in-model interference.** tt-metal#29293 (the meta-issue for extending the prefetcher beyond WH Galaxy to BH): *"prefetcher interference can cause big discrepancies in latency performance between in model performance and unit tests"*. This is **third-party** and was seen on Llama-70B Galaxy. It is the same failure class as `docs/PREFILL-AUDIT-2026-09-12.md` §4, where a probe predicted -9% and the model delivered 0 to +2%. Treat any layer-level gain as an upper bound. 3. **Serving alternates modes constantly.** Upstream's `generator.py:540-586` says `switch_mode(Mode.PREFILL)` "is not functional" with a prefetcher, and that prefetcher models skip the hoisted decode-trace setup. In our workload every arriving agent triggers a prefill→decode switch, and prefill owns TTFT. The cost of re-initialising the sub-devices on every switch has to be measured, and charged against the decode gain, before a port is justified. **Add a gate 2b:** time N prefill↔decode switches with the prefetcher on. 4. **The prefetcher might also shrink #54's floor. This is an ESTIMATE, not a claim.** With a prefetcher, the matmul reads in1 from an L1 global CB (a ring matmul), and dedicated sender cores stream from DRAM in large blocks. That could remove the "one 576 B tile per DMA" issue-rate intercept (8.2 ms) on the matmul cores. If it does, the ideal bound falls below 27 ms. Record the per-matmul time in gate 2 so this can be checked. 5. **The model mismatch is structural.** 48 of 64 layers are GDN. `gdn_qkvzab` (1.04 MB/receiver at 64 receivers) and the GDN L1 chain (`_L1` conv→recurrence→norm) both compete for the same receiver L1 as the global CB. The 80-receiver fit arithmetic is necessary but not sufficient, and the double-buffering headroom is still unchecked, as you note. **Makespan framing (see #53):** the fixed per-token cost is batch-invariant, so a hypothetical 10 ms/token at B=8/32k is ~10 s of 148.7 s, about **7% makespan (ESTIMATE)**. That is real, but it is a large, multi-file, image-baked port (`mlp.py`, `gdn/tp.py`, `layer.py`). It ranks below the warm-prefix prefill tail (#50) and below prefix deployment (#48), which move TTFT, the thing decode-side work has never moved (`bench/runs/sweep-32k-20260914T142154Z.jsonl`).
Sign in to join this conversation.
No labels
human-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
bitpartner/tt-stack#58
No description provided.