401 lines
20 KiB
Markdown
401 lines
20 KiB
Markdown
|
|
# ROADMAP — Stage E and the path to a runnable model
|
|||
|
|
|
|||
|
|
**Method.** Every state claim below was verified against the source in this
|
|||
|
|
revision. Agent status doc and agent assessment were both read, then ignored
|
|||
|
|
where they contradict the code. The doctrine rules from prior issue docs apply:
|
|||
|
|
DSL wall → raw CUDA C++ (not Python); raw CUDA ≠ scalar math; print, don't
|
|||
|
|
guess; integration over exploration; falsifiable gates only.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## TL;DR — where we actually are
|
|||
|
|
|
|||
|
|
**The FMHA kernel itself is in genuinely good shape.** `fmha_6warp_tma_multirow_multitile.cuh`
|
|||
|
|
is one file, properly Blackwell-native (`tcgen05.mma` SS, UMMA descriptors,
|
|||
|
|
TMEM, `cp.async.bulk.tensor.2d` + `mbarrier.arrive.expect_tx`, in-kernel
|
|||
|
|
multi-tile rescale via SMEM accumulator, multi-row softmax). The TMA hang from
|
|||
|
|
P4 was correctly diagnosed (missing `expect_tx`) and resolved. The C-API +
|
|||
|
|
ctypes shim works. **P3, P4, P5, P6, P7, P8 numerics are done.**
|
|||
|
|
|
|||
|
|
**The agent's "Stage E" assessment is correct in spirit but wrong on specifics.**
|
|||
|
|
Where it says "AttentionSubBlock has `NotImplementedError` stubs" — it doesn't.
|
|||
|
|
The layer is structurally complete and *imports the right things*. The real
|
|||
|
|
problem is one layer down: it calls cache methods like `gather_compressed_kv()`,
|
|||
|
|
`gather_swa_kv()`, and properties like `cache.num_query_heads` / `cache.head_dim`
|
|||
|
|
**that don't exist on `LayerCacheHandle`**. The handle exposes `read_*_view()`
|
|||
|
|
returning paged FP8 buffers; nothing materializes them into the BF16 dense
|
|||
|
|
tensors `dsv4_attention` consumes. That's the integration gap, not stubs.
|
|||
|
|
|
|||
|
|
**Three structural shortcuts still live in production code.** They are not
|
|||
|
|
performance-fatal yet but they will be at scale:
|
|||
|
|
1. Two `torch.cuda.synchronize()` calls remain on the kernel-launch path
|
|||
|
|
(`fmha_multitile_op.py:130`, `production.py:279`). The second is in the still-resident
|
|||
|
|
"slow path." The first is unconditional, even on the fast path.
|
|||
|
|
2. The slow CuTeDSL+Python-merge path is **not deleted**, only relabeled
|
|||
|
|
"slow path." 471 lines of code, 48 unit tests bound to its `FmhaKernel` symbol,
|
|||
|
|
still imported at `production.py:53`.
|
|||
|
|
3. The batched call shape (4D q) does a **Python for-loop over batch items**
|
|||
|
|
at `production.py:380` instead of folding batch into the grid.
|
|||
|
|
|
|||
|
|
**Cleanup is non-trivial.** ~22 dead/stale code paths live alongside the
|
|||
|
|
working ones. Some are old CuTeDSL drafts (`ops/decode_sparse.py`,
|
|||
|
|
`ops/decode_swa.py`), some are pre-consolidation Stage D probes
|
|||
|
|
(`fmha_sm100.cuh`, `fmha_sm100_tc.cuh`, `fmha_sm100_launch.cu`,
|
|||
|
|
`fmha_qk_verify.cuh`, `fmha_epilogue_sm100.cuh`), some are misplaced root files
|
|||
|
|
(`debug_linear.py`, `test_mapping.py`, `tests/working_softmax_maybe.py`), and 46
|
|||
|
|
of 177 unit tests are pinned to the deprecated `FmhaKernel`. Left in place,
|
|||
|
|
this is the primary path by which an agent will drift back into the wrong
|
|||
|
|
codebase the next time it context-switches.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# PART 1 — CLEANUP (do this first, in this order)
|
|||
|
|
|
|||
|
|
Cleanup is gated *before* Stage E work because the next stage's failure mode is
|
|||
|
|
"agent picks the wrong file to extend." Right now `dsv4/kernels/attention/` has
|
|||
|
|
14 files; only 6 are live. That ratio is the diagnostic.
|
|||
|
|
|
|||
|
|
## C1. Confirm what's live with `grep`, not by reading status docs
|
|||
|
|
|
|||
|
|
Before deleting anything: produce `audit_attention_live.md` listing each file
|
|||
|
|
under `dsv4/kernels/attention/` and `dsv4/ops/` with its **outbound reference
|
|||
|
|
count from non-archive code** (production, layers, model, integration tests).
|
|||
|
|
Anything with 0 refs is a deletion candidate; anything with >0 refs gets
|
|||
|
|
inspected to confirm the reference is live, not a stale import.
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** the audit table goes into `archived_plans/audit_attention_live.md`,
|
|||
|
|
file count column adds up to the actual directory listing. Mention deletion
|
|||
|
|
candidates by name with the ref count next to them.
|
|||
|
|
|
|||
|
|
## C2. Delete the dead CuTeDSL FMHA scaffolding
|
|||
|
|
|
|||
|
|
These have **zero external refs** outside their own `#include` chain and
|
|||
|
|
self-references:
|
|||
|
|
|
|||
|
|
| File | Status | Action |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `dsv4/kernels/attention/fmha_sm100.cuh` | Phase-1 scalar reference, header says "WORKING cos 0.999999" | Delete. The 6-warp kernel supersedes it. |
|
|||
|
|
| `dsv4/kernels/attention/fmha_sm100_tc.cuh` | Earlier tensor-core fork before consolidation. | Delete. The 6-warp kernel is its successor. |
|
|||
|
|
| `dsv4/kernels/attention/fmha_sm100_launch.cu` | Header says "COMPILES but doesn't run via torch.utils.cpp_extension" | Delete. Replaced by `fmha_multitile_capi.cu` + ctypes. |
|
|||
|
|
| `dsv4/kernels/attention/fmha_qk_verify.cuh` | Header says "QK GEMM verification" — one test references it. | Move to `tests/unit/qk_verify_kernel.cuh` (it's a test fixture, not a library kernel). |
|
|||
|
|
| `dsv4/kernels/attention/fmha_epilogue_sm100.cuh` | Phase-2 epilogue prototype; the multi-tile kernel already does in-kernel rescale via SMEM and doesn't use this. | Delete after confirming it's not `#include`'d by anything live. Audit table from C1 answers this. |
|
|||
|
|
| `dsv4/ops/decode_sparse.py` | Old CuTeDSL CSA decode draft, not called from anywhere. | Delete. Path is now `layers/attention.py:_forward_csa` → `kernels/attention.sparse_fmha_with_swa` → `kernels/attention/production.py`. |
|
|||
|
|
| `dsv4/ops/decode_swa.py` | Old CuTeDSL SWA decode draft, same story. | Delete. |
|
|||
|
|
| `dsv4/kernels/decode/` | Two files: `__init__.py` (empty) and `_NOTES_fp8_bf16.md` (scratch). | Delete the whole directory. |
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** after C2, `find dsv4/kernels/attention -name '*.cuh' -o -name '*.cu' | wc -l`
|
|||
|
|
returns the value in `audit_attention_live.md`'s live count. No reference
|
|||
|
|
warnings on `python -c "import dsv4"`.
|
|||
|
|
|
|||
|
|
## C3. Decide the fate of the CuTeDSL slow path (`fmha.py`)
|
|||
|
|
|
|||
|
|
`fmha.py` is 645 lines and is the entire pre-P3-P8 CuTeDSL path. It is currently
|
|||
|
|
imported by `production.py:53` (`from dsv4.kernels.attention.fmha import FmhaKernel`)
|
|||
|
|
and used by the `else` branch starting at `production.py:410` ("SLOW PATH"). It
|
|||
|
|
is also imported by **48 unit tests**.
|
|||
|
|
|
|||
|
|
**Two options. Pick one, document it, do not maintain both.**
|
|||
|
|
|
|||
|
|
- **Option A (preferred, matches doctrine).** Delete `fmha.py`, delete the
|
|||
|
|
SLOW PATH branch from `production.py`, delete the 48 `test_d*.py` unit tests
|
|||
|
|
bound to `FmhaKernel`. The 6-warp multi-tile kernel covers
|
|||
|
|
`hd∈{64,128,256,512}` × `T=1` decode × any `n_segments`. That's the production
|
|||
|
|
surface. Everything else is exploration that already paid off — it does not
|
|||
|
|
need to ship.
|
|||
|
|
- **Option B (fallback, only if Option A is blocked).** Move `fmha.py` and the
|
|||
|
|
48 tests to `archived_plans/cutedsl_legacy/`. Delete the SLOW PATH from
|
|||
|
|
`production.py` regardless. The kernel and tests stay buildable for reference
|
|||
|
|
but are no longer in the active import graph.
|
|||
|
|
|
|||
|
|
**Failure mode to refuse:** "keep it as fallback in case the 6-warp kernel has
|
|||
|
|
a regression." If there's a regression, you fix the 6-warp kernel — that's the
|
|||
|
|
production kernel. Carrying a parallel implementation guarantees both rot.
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** `grep -r "FmhaKernel" dsv4/ tests/ scripts/ | wc -l` returns
|
|||
|
|
0 (Option A) or only matches inside `archived_plans/` (Option B). The
|
|||
|
|
SLOW PATH branch is gone from `production.py` either way.
|
|||
|
|
|
|||
|
|
## C4. Empty the `archive/` subdirectory the same way
|
|||
|
|
|
|||
|
|
`dsv4/kernels/attention/archive/` currently holds 5 backups
|
|||
|
|
(`fmha_backup_pre_epilog.py`, `fmha_backup_v2.py`, `fmha_smem_acc.py`,
|
|||
|
|
`fmha_6warp_tma_driver_api.cuh`, `fmha_tma_driver_api.cuh`). These were correct
|
|||
|
|
to *not* delete during P8 consolidation. Now: move them out of `dsv4/`
|
|||
|
|
entirely into `archived_plans/code_archive/` and delete `dsv4/kernels/attention/archive/`.
|
|||
|
|
Active code directories should contain only active code. `archived_plans/` is
|
|||
|
|
the right place for everything that isn't.
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** `find dsv4 -name 'archive' -type d | wc -l` returns 0.
|
|||
|
|
|
|||
|
|
## C5. Move the root-level scratch files
|
|||
|
|
|
|||
|
|
Three files live at the repo root and don't belong there:
|
|||
|
|
|
|||
|
|
- `debug_linear.py` (58 lines, computes expected O for a debug pattern — Stage D
|
|||
|
|
probe). → `tests/unit/archive/` or delete if no longer useful.
|
|||
|
|
- `test_mapping.py` (49 lines, SMEM-P coordinate mapping check — Stage D probe). → same.
|
|||
|
|
- `run_router_tests.py` (hardcoded path `/root/dsv4-nvfp4-workspace/kernel`). →
|
|||
|
|
Either generalize the path and move to `scripts/`, or delete. Hardcoded
|
|||
|
|
absolute paths in tracked files are bug magnets.
|
|||
|
|
- `tests/working_softmax_maybe.py` (496 lines, name speaks for itself). → If
|
|||
|
|
it's a draft, archive it. If it's superseded, delete it. The doctrine is
|
|||
|
|
"code or archive, never `maybe`."
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** repo root has no `.py` files except generated/build
|
|||
|
|
artifacts. `tests/` contains no `*_maybe.py` or `working_*.py`.
|
|||
|
|
|
|||
|
|
## C6. Prune the unit test D-probe explosion
|
|||
|
|
|
|||
|
|
46 `test_d*.py` files were Stage D debugging probes. Many are bound to
|
|||
|
|
`FmhaKernel` and will be deleted in C3 if you pick Option A. The remainder that
|
|||
|
|
test components still in use (`FmhaTmaMultiRowMultiTile`, the indexer, the
|
|||
|
|
compressor, mHC, MoE) should either be promoted to named integration tests
|
|||
|
|
(`test_fmha_decode.py`, `test_csa_indexer.py`, etc.) or archived.
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** `ls tests/unit/test_d*.py | wc -l` returns ≤ 5
|
|||
|
|
(only the ones genuinely testing currently-live behavior, with descriptive names).
|
|||
|
|
|
|||
|
|
## C7. Status doc consolidation
|
|||
|
|
|
|||
|
|
After C1–C6, write **one** new top-level `STATUS.md` describing the **post-cleanup**
|
|||
|
|
state of the production path, and delete `NEXT_PRIORITIES.md` (this doc replaces it).
|
|||
|
|
`archived_plans/` already holds the historical paper trail; do not add to it
|
|||
|
|
during cleanup.
|
|||
|
|
|
|||
|
|
**Falsifiable gate:** repo root has exactly two living `.md` files: `README.md`
|
|||
|
|
and `STATUS.md` (plus this `ROADMAP.md`). `archived_plans/` holds the history,
|
|||
|
|
unchanged.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# PART 2 — STAGE E PRIORITY ORDER (post-cleanup)
|
|||
|
|
|
|||
|
|
The agent is right that Stage E = "make the model run end-to-end." The order
|
|||
|
|
below is sequenced so each priority unblocks the next, and so the
|
|||
|
|
performance-decisive moves come *after* end-to-end correctness, not before.
|
|||
|
|
|
|||
|
|
## E1. Wire `LayerCacheHandle` to expose what `AttentionSubBlock` calls
|
|||
|
|
|
|||
|
|
This is the load-bearing missing piece. `dsv4/kernels/attention/__init__.py`
|
|||
|
|
calls four things that don't exist on `dsv4/cache/handle.py`:
|
|||
|
|
|
|||
|
|
- `cache.gather_compressed_kv(selected_indices) → (k_compressed, v_compressed)` BF16 dense
|
|||
|
|
- `cache.gather_all_compressed_kv() → (k_compressed, v_compressed)` BF16 dense (HCA, no top-k)
|
|||
|
|
- `cache.gather_swa_kv() → (k_swa, v_swa)` BF16 dense
|
|||
|
|
- `cache.num_query_heads`, `cache.head_dim` (properties)
|
|||
|
|
|
|||
|
|
The handle already exposes the right *raw* state via
|
|||
|
|
`read_classical_view()` (FP8 entries + RoPE BF16 + paged block_table) and
|
|||
|
|
`read_swa_view()` (FP8 entries + RoPE BF16 + positions). The work is the
|
|||
|
|
gather+dequantize+concat path.
|
|||
|
|
|
|||
|
|
**Constraints (doctrine):**
|
|||
|
|
|
|||
|
|
- Each gather is a **single fused kernel**, raw CUDA C++ if CuTeDSL can't do
|
|||
|
|
block-table-strided FP8→BF16 dequant+gather in one pass. Not a Python loop
|
|||
|
|
over selected indices. Not five eager torch ops.
|
|||
|
|
- The kernel uses TMA + warp-level dequant, not scalar loads. The MoE GEMM
|
|||
|
|
already proves out the FP8 dequant pattern on Blackwell — reuse it.
|
|||
|
|
- The dequant decodes E4M3 with the inverse-scale stored alongside. **Print
|
|||
|
|
the actual layout of `paged.entries_fp8` + `paged.inv_scale` before writing
|
|||
|
|
the kernel** — do not assume the layout matches an MoE convention.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. The four methods exist on `LayerCacheHandle`, implemented in
|
|||
|
|
`dsv4/kernels/cache/gather.{cu,py}`.
|
|||
|
|
2. `python -c "from dsv4.layers.attention import AttentionSubBlock; ..."` runs
|
|||
|
|
a forward pass on one layer without AttributeError, NotImplementedError,
|
|||
|
|
or shape mismatch.
|
|||
|
|
3. Numerical parity vs a pure-Python reference (FP32 dequant + dense FMHA over
|
|||
|
|
the gathered tensors): `cos ≥ 0.9995`. FP8 dequant loses a little, that's
|
|||
|
|
expected.
|
|||
|
|
4. **Launch count for one CSA layer** measured with Nsight: gather is ≤ 3 launches
|
|||
|
|
(compressed gather, SWA gather, concat). Not 30, not 5+5+cat.
|
|||
|
|
|
|||
|
|
**Failure modes to refuse:**
|
|||
|
|
- A Python gather loop over the block table. The block table is on GPU; the
|
|||
|
|
gather is on GPU. No host trips.
|
|||
|
|
- BF16 materialization "for now" without FP8 dequant. The paged cache stores
|
|||
|
|
FP8 because that's the design — bypassing it loses 2x KV memory and the
|
|||
|
|
whole point of the cache structure.
|
|||
|
|
|
|||
|
|
## E2. End-to-end smoke test through one full layer
|
|||
|
|
|
|||
|
|
After E1, both halves of `dsv4/model/layer.py` (`mhc_attn` → `attn.forward` →
|
|||
|
|
`mhc_ffn` → `ffn.forward`) should execute without error. The test is the
|
|||
|
|
forcing function.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `tests/e2e/test_one_layer.py` runs `DSV4Layer.forward(X, token_ids, cache)`
|
|||
|
|
on synthetic inputs with cached weights loaded from `loader/`, returns a
|
|||
|
|
tensor of the right shape and dtype, no errors.
|
|||
|
|
2. The same test with `cache` containing actual paged FP8 entries (not zeros)
|
|||
|
|
produces output with `cos ≥ 0.99` against a Python reference of the same
|
|||
|
|
layer (compressor + indexer + FMHA + mHC, all in eager PyTorch).
|
|||
|
|
3. Test exists for all three attention types (CSA, HCA, SWA) by toggling the
|
|||
|
|
layer's `attn_type`.
|
|||
|
|
|
|||
|
|
**Failure mode to refuse:** wrapping the test in a try/except to "make it
|
|||
|
|
pass." The cos threshold is the gate.
|
|||
|
|
|
|||
|
|
## E3. Top-level `model/dsv4.py` (currently a 2-line TODO)
|
|||
|
|
|
|||
|
|
`dsv4/model/dsv4.py` is literally:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
"""Full DSV4 model."""
|
|||
|
|
# TODO: Phase 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Build the actual model class: embedding → N×DSV4Layer → final norm → prediction
|
|||
|
|
head. Use `layer_schedule.py` to alternate CSA/HCA per the V4 spec
|
|||
|
|
(Pro: layers 0–1 HCA, then interleaved; Flash: layers 0–1 SWA, then
|
|||
|
|
interleaved).
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `DSV4Model.forward(token_ids, cache_manager) → logits` works on a single
|
|||
|
|
token decode.
|
|||
|
|
2. Layer schedule matches the V4 paper (verify against `config.py`).
|
|||
|
|
3. MTP module is wired but optional (off by default; doctrine note in code
|
|||
|
|
that MTP fine-tuning is a separate workstream).
|
|||
|
|
4. End-to-end test: `tests/e2e/test_decode.py` generates one token from a
|
|||
|
|
3-layer toy config, succeeds, cos ≥ 0.99 vs Python reference.
|
|||
|
|
|
|||
|
|
## E4. Delete the two remaining `torch.cuda.synchronize()` calls on the fast path
|
|||
|
|
|
|||
|
|
After C3 the slow-path sync at `production.py:279` is gone with the slow path.
|
|||
|
|
The other sync is at `fmha_multitile_op.py:130`:
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# Synchronize to catch async errors
|
|||
|
|
torch.cuda.synchronize()
|
|||
|
|
return o, lse
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This is on the *fast path*. It's there to catch async kernel errors, but the
|
|||
|
|
correct way to do that is to check the return code from the C-API launch
|
|||
|
|
function (which `fmha_multitile_decode_launch` already returns) plus
|
|||
|
|
`cudaPeekAtLastError`, not a full device sync.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. The sync is removed from `fmha_multitile_op.py`.
|
|||
|
|
2. Error checking moves to a `cudaPeekAtLastError` call + the existing return
|
|||
|
|
code check.
|
|||
|
|
3. Nsight measurement on a 4-layer decode: zero `cudaDeviceSynchronize` calls
|
|||
|
|
between layer 0 entry and final logits.
|
|||
|
|
|
|||
|
|
## E5. Fold the batch loop into the kernel grid
|
|||
|
|
|
|||
|
|
`production.py:380–388` is a Python for-loop over batch items. Each iteration
|
|||
|
|
calls back into `dsv4_attention` on a 3D slice. For batch=8 and 61 layers,
|
|||
|
|
that's 488 launches per decoded step instead of 61.
|
|||
|
|
|
|||
|
|
The multi-tile kernel's grid is currently `(1, n_h, 1)`. It needs to become
|
|||
|
|
`(1, n_h, batch)` with `blockIdx.z` used to index batch. The kernel already
|
|||
|
|
has `_batch_stride` params (read off `FmhaTmaMultiRowMultiTileParams`); they
|
|||
|
|
just aren't wired to a grid dim.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `production.py:380–388` for-loop deleted; the batched path calls the
|
|||
|
|
multi-tile kernel once with `gridDim.z = batch`.
|
|||
|
|
2. Numerical parity: `cos ≥ 0.999998` vs the deleted Python loop.
|
|||
|
|
3. Launch count for batch=8 decode: 1 per layer, not 8.
|
|||
|
|
|
|||
|
|
## E6. NVFP4-1.2: FP4 output fusion for FMHA → `wo_a`
|
|||
|
|
|
|||
|
|
Right now the FMHA writes BF16 to GMEM, then `wo_a` re-quantizes it to FP4.
|
|||
|
|
That's a redundant memory pass on the attention output. The epilogue already
|
|||
|
|
has the one-way TMEM→regs→SMEM→GMEM shape — adding an FP4 pack with amax in
|
|||
|
|
registers, gated by a template param, is the same pattern the MoE epilogue
|
|||
|
|
already uses for NVFP4-1.1.
|
|||
|
|
|
|||
|
|
**Constraints (doctrine):**
|
|||
|
|
- The FP4 pack is **part of the same single launch**. No second kernel for
|
|||
|
|
quantize. Per-128-element block amax reduces via warp shuffle in registers.
|
|||
|
|
- The amax → scale → E2M1 LUT path matches `dsv4/ops/quantize.py` byte for byte.
|
|||
|
|
Print the LUT from the Python side; assert equality in a unit test. The
|
|||
|
|
indexer LUT bug we already fixed is the cautionary tale.
|
|||
|
|
- This is gated on E4 (`epilogue_op` slot exists in the multi-tile kernel —
|
|||
|
|
let's verify it does before starting E6).
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `fmha_6warp_tma_multirow_multitile_kernel` accepts a template arg `Epilogue`
|
|||
|
|
(default = identity); a `FP4Epilogue` specialization packs E2M1 + emits SF
|
|||
|
|
blocks in the layout `wo_a` expects.
|
|||
|
|
2. `tests/unit/test_fp4_epilogue_parity.py`: BF16 reference vs FP4 fused,
|
|||
|
|
`cos ≥ 0.999` after dequant.
|
|||
|
|
3. End-to-end CSA layer: BF16 output → FP4 output replaces one full BF16 R/W
|
|||
|
|
pass through (T, n_h * hd) memory. Measured bandwidth drop with Nsight.
|
|||
|
|
|
|||
|
|
## E7. Stage F: Lightning indexer FP4 tensor-core scoring
|
|||
|
|
|
|||
|
|
Currently `indexer_score_topk.cu` uses scalar FP32 cores after the LUT dequant.
|
|||
|
|
The paper §5.2.1 is explicit that this path runs in FP4 on tensor cores
|
|||
|
|
("multiplied entirely in FP4 ... 99.7% recall"). The LUT bug fix made the
|
|||
|
|
scalar path *correct*; this priority makes it *fast*, the way V4 actually
|
|||
|
|
specifies it.
|
|||
|
|
|
|||
|
|
**Constraints (doctrine):**
|
|||
|
|
- `tcgen05.mma` with `mxf4nvf4` kind. Keys and queries both FP4. Scales E4M3
|
|||
|
|
in shared mem, hardware decodes. No scalar fallback in the live path.
|
|||
|
|
- Recall vs the FP32 oracle ≥ 99.7% per paper. The recall test from the LUT
|
|||
|
|
fix is the same test; the bar is the same.
|
|||
|
|
- The top-k selector goes warp-level (`__shfl_xor` ballot) at the same time —
|
|||
|
|
the current serial single-thread heap merge at top_k=1024 is its own latency
|
|||
|
|
source on Pro decode.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `indexer_score_topk.cu` has a `tcgen05`-MMA FP4 path, gated by a config
|
|||
|
|
flag, with the scalar path archived (not deleted, since the FP4 path needs
|
|||
|
|
the oracle).
|
|||
|
|
2. Recall@k ≥ 99.7% for k ∈ {512, 1024} at compressed_blocks ∈ {2k, 8k, 32k}.
|
|||
|
|
3. End-to-end CSA layer latency drops measurably (Nsight).
|
|||
|
|
|
|||
|
|
## E8. Multi-CTA grid for prefill (Priority 4 from the original ROADMAP)
|
|||
|
|
|
|||
|
|
Decode is single-CTA per head and that's correct. Prefill (T > 1) wants
|
|||
|
|
multi-CTA to parallelize over M. The one-way epilogue from E6 is the
|
|||
|
|
prerequisite — once it lands, the grid expansion is wiring.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `gridDim.x = ceil_div(M, M_TILE)` for `T > 1` paths.
|
|||
|
|
2. Prefill latency at T=128 with M-tile=64: 2-CTA grid, measured speedup ≥ 1.5×
|
|||
|
|
over single-CTA.
|
|||
|
|
3. Decode path unchanged (single CTA, no regression).
|
|||
|
|
|
|||
|
|
## E9. CUDA graph capture (Stage E item from the agent's notes)
|
|||
|
|
|
|||
|
|
Once E5 lands and the decode hot path is sync-free, graph capture is
|
|||
|
|
straightforward. This is the biggest single perf win on autoregressive decode
|
|||
|
|
because it eliminates host launch overhead entirely.
|
|||
|
|
|
|||
|
|
**Definition of done:**
|
|||
|
|
1. `DSV4Model.decode_step(token, cache)` is `cudaGraph`-capturable.
|
|||
|
|
2. Replay latency vs uncaptured: ≥ 2× faster on a 3-layer toy at batch=1.
|
|||
|
|
3. Memory: graph capture doesn't `torch.zeros` on the hot path (E1 already
|
|||
|
|
forced this for gather; verify it holds for compressor + indexer + FMHA).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# DOCTRINE — applies to every item above
|
|||
|
|
|
|||
|
|
1. **DSL wall → raw CUDA C++, not Python.** The C-API + ctypes pattern in
|
|||
|
|
`fmha_multitile_op.py` is the correct shape for new kernels.
|
|||
|
|
2. **Raw CUDA ≠ scalar math.** Every kernel above is `tcgen05` / UMMA / TMA /
|
|||
|
|
warp reductions. No "temporary scalar dot product" without a labeled
|
|||
|
|
replacement target.
|
|||
|
|
3. **Print, don't guess.** Two places this round specifically:
|
|||
|
|
- E1 gather kernels: print the FP8 cache layout + inv-scale layout before
|
|||
|
|
writing the dequant.
|
|||
|
|
- E6 FP4 pack: print the LUT from `quantize.py` and assert in a test that
|
|||
|
|
the kernel produces byte-identical packed nibbles.
|
|||
|
|
4. **Integration over exploration.** Do not create
|
|||
|
|
`fmha_6warp_tma_multirow_multitile_v2.cuh`. Extend the chosen file or stop
|
|||
|
|
and replan. Variant proliferation is the agent's primary failure mode in
|
|||
|
|
this repo.
|
|||
|
|
5. **Falsifiable gates only.** Every "done" above has a number or a binary
|
|||
|
|
check. "Looks fine," "milestone complete," and "Stage X done" without a
|
|||
|
|
number are read as "not done."
|
|||
|
|
6. **The slow path is not a fallback.** Once C3 deletes the CuTeDSL+Python-merge
|
|||
|
|
path, it stays deleted. If the 6-warp kernel breaks, you fix the 6-warp
|
|||
|
|
kernel — not resurrect 471 lines of dead code.
|