Files
nvfp4-megamoe-kernel/README.md
biondizzle 0dc6fe4a7d Stage B progress: PV works for square (128,128), broken for (128,64)
- Bug 1 (V MN-major): Fix applied
- Bug 2 (softmax packing): Confirmed correct (V=I test: cosine 1.0)
- Bug 3 (ACCUMULATE): Fix applied (first PV must overwrite, not accumulate)
- Bug 4 (CURRENT): PV MMA broken for non-square output
  - (128,128) PV with random V: cosine 0.999999 
  - (128,64) PV with MN-major V: cosine ~0.01 
  - Softmax packing, layout aliasing, pipeline ordering all verified correct
  - Root cause unknown — likely epilogue/V layout/MMA tiler issue

Added test_pv_diag.py (V=I and random V, 128x128 output — PASS)
Added test_layout_compare.py (TMEM layout inspection)
Added test_inspect_types.py (TMEM pointer arithmetic verification)
Updated test_mma_si_pv.py with head_dim param, pv_mma_tiler_mn fix, ACCUMULATE fix
Updated READMEs with current state
2026-05-21 04:40:28 +00:00

219 lines
10 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DeepSeek-V4 NVFP4 Kernel Suite
CuTeDSL kernels for DeepSeek-V4 (Blackwell B200, SM100). All kernels use `cutlass.cute` (CuTeDSL) with Blackwell tensor cores.
## Status (May 21, 2026 — 04:35 UTC)
### ✅ Stage A: Bare Q@K^T via tcgen05.mma → TMEM → GMEM — COMPLETE
**File**: `tests/test_stage_a_v2.py`
**Result**: Q(128,128) @ K^T(128,128) → S(128,128), cosine 0.999999
### 🔨 Stage B: Two MMAs + Identity Softmax — IN PROGRESS
**Pipeline deadlock: FIXED. Kernel runs without deadlock.**
**Bug 1 (V MN-major): Fix applied.**
**Bug 2 (softmax packing): Confirmed correct (V=I test: cosine 1.0).**
**Bug 3 (ACCUMULATE): Fix applied.**
**Bug 4 (non-square PV): PV works for (128,128) output, broken for (128,64) output.**
#### Bug 1: V B-Operand Must Be MN-Major — ✅ FIX APPLIED
V must be shaped (head_dim, seq) = (64, 128) with strides (1, 64) — MN-major.
PV MMA uses `v_major` (OperandMajorMode.MN) instead of `b_major` (K).
V must use `as_strided` — default PyTorch (64,128) gives strides (128,1) which is K-major.
#### Bug 2 (Packing): C-Fragment Composition Store — ✅ CONFIRMED CORRECT
FP32→BF16 packing via C-fragment composition store (FMHA pattern) is correct.
Proven by V=I test (cosine 1.0) and random V 128x128 test (cosine 0.999999).
**FOOTGUN**: `St32x32bOp` MUST use Float32, NOT BFloat16.
⚠️ The recast view for P packing uses the LOAD layout (128 BF16 elements), not the store composition shape.
#### Bug 3 (ACCUMULATE): First PV Must Use ACCUMULATE=False — ✅ FIX APPLIED
If ACCUMULATE=True on the first PV, `O = P@V + old_O` adds uninitialized TMEM. Always ACCUMULATE=False for first PV, then True for subsequent tiles.
#### Bug 4 (CURRENT): PV MMA Broken for Non-Square Output — 🔨 ROOT CAUSE UNKNOWN
**What works:**
- PV with (128,128) output, V=I: cosine 1.0 ✅
- PV with (128,128) output, random V: cosine 0.999999 ✅
**What doesn't work:**
- PV with (128,64) output, V MN-major (64,128): cosine ~0.01 ❌
**Possible causes:**
1. `make_trivial_tiled_mma` with (128,64) produces different A-fragment layout — alias with softmax P may break
2. V TMA load wrong for (128,64) PV — SMEM layout, TMA descriptor, or partitioning incorrect
3. Epilogue/gC mismatch — output c is (128,64) but epilogue may write (128,128) tile
4. PV mma_tiler_mn doesn't affect the MMA atom (which is always (128,128,16))
**Diagnostic findings:**
- Pointer arithmetic correct: softmax P and PV A-fragment address same TMEM location
- Layout aliasing correct: C-fragment composition and A-fragment produce same physical addresses
- Pipeline ordering correct: softmax completes before PV starts
- Softmax packing correct: proven by V=I test
### 🔨 Stage C: Online Softmax — AFTER B
Per the pseudocode: epilogue warps compute per-row tile_max, rescale, exp, store P back to TMEM.
### 🔨 Stage D: FP8 Paged KV Gather — AFTER C
Replace BF16 TMA load with FP8 paged KV gather + per-position dequant.
---
## Pipeline Deadlock — ✅ FIXED (May 21)
v20-v25 all deadlocked on GPU. Three root causes found and fixed:
### Fix 1: PipelineUmmaAsync for mma_si Must NOT Pass cta_layout_vmnk
FMHA's mma_s0/mma_s1 PipelineUmmaAsync calls do NOT pass cta_layout_vmnk. Removing it fixes the deadlock.
### Fix 2: TMA Warp Must NOT Call tmem.wait_for_alloc()
The tmem allocation barrier has `num_threads = 32 * (mma_warp + epilogue_warps)`. The TMA warp is NOT part of this barrier. Calling `wait_for_alloc()` from the TMA warp corrupts the barrier.
### Fix 3: PipelineTmaStore (not TmaStorePipeline)
`pipeline.TmaStorePipeline` does not exist. The correct name is `pipeline.PipelineTmaStore`.
---
## ⛔ DEAD TEST: test_stage_b_v21.py — DELETED, DO NOT RECREATE
v21 attempted both Bug 1 and Bug 2 fixes in a hand-rolled pipeline kernel. It deadlocks on GPU. Root cause: pipeline synchronization mismatch. **Do not recreate.** Write from scratch using fmha.py as the reference.
---
## ⛔ FOOTGUNS — CUTLASS CuTeDSL Landmines
### 1. St32x32bOp with 16-bit dtype → ILLEGAL MEMORY ACCESS
`St32x32bOp(Repetition(N), BFloat16)` crashes at runtime. You MUST use `St32x32bOp(Repetition(N), Float32)` and pack 2×16-bit values into 1×Float32 backing words via `cute.recast_ptr`. The 16-bit type only appears in the recast view, never in the store atom itself.
### 2. V B-Operand Major Mode ≠ K Major Mode
FMHA requires `v_major_mode == OperandMajorMode.MN`. Passing K's K-major mode for V is WRONG. V must be shaped (head_dim, seq) with strides (1, head_dim) to produce MN-major. Standard PyTorch row-major (seq, head_dim) gives K-major.
### 3. CuTe Nested Layout Modes Flatten Sequentially
A layout like `((128,16),1,(4,2)):((65536,1),0,(16,64))` looks "non-sequential" but flattens to `addr = m*65536 + k` when k = k0 + 16*k1 + 64*k2 (CuTe row-major order). Do NOT assume nested modes imply non-sequential physical addressing. The C-fragment composition and A-fragment alias the same TMEM columns.
### 4. PipelineUmmaAsync Consumer Group = Thread Count, NOT Warp Count
```python
# WRONG: consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread, 4)
# CORRECT: consumer_group=pipeline.CooperativeGroup(pipeline.Agent.Thread, 32 * len(warp_ids))
```
### 5. PipelineUmmaAsync for mma_si Must NOT Pass cta_layout_vmnk
Passing `cta_layout_vmnk` to the mma_si PipelineUmmaAsync causes deadlock. FMHA does not pass it. Remove it.
### 6. TMA Warp Must NOT Call tmem.wait_for_alloc()
The tmem allocation barrier only includes MMA + epilogue warps. The TMA warp is excluded. Calling `wait_for_alloc()` from the TMA warp corrupts the barrier.
### 7. PV MMA ACCUMULATE Must Be False on First Tile
If ACCUMULATE=True on the first PV MMA, `O = P@V + old_O` adds uninitialized TMEM to the result. Always set ACCUMULATE=False for the first PV, then True for subsequent tiles. FMHA: `pv_tiled_mma.set(tcgen05.Field.ACCUMULATE, kphase_idx != 0)`.
### 8. TMEM Pointer Arithmetic: Offset Units Depend on Pointer Type
When computing PV A-fragment offset from the softmax P offset:
```python
# Softmax store: FP32 pointer + tmem_p0_offset (in FP32 elements)
tStS_P = cute.make_tensor(tStS.iterator + tmem_p0_offset, tStS_P_layout)
# PV A-fragment: BF16 pointer + scaled offset (in BF16 elements)
p_offset = acc_dtype.width // q_dtype.width * tmem_p0_offset # 2 * 32 = 64
tOrP0 = cute.make_tensor(tOrP.iterator + p_offset, tOrP.layout)
```
Both must address the same physical TMEM column. The 2× scaling accounts for FP32→BF16 element size difference.
---
## Architecture: Per-Tile Flow
```
For each KV tile:
1. Load warp writes sKV[stage] (paged FP8 gather via indexed cp.async)
2. MMA warp issues MMA1: sQ @ sKV[stage]^T → tmem_scores (accumulate=False)
Signals scores_full_mbar (via PipelineUmmaAsync commit)
3. Epilogue warps wait on mma_si consumer (scores ready), then:
a. tcgen05.ld scores from TMEM → register fragments
b. Compute tile_max, new_max, rescale = exp(old_max - new_max)
c. Apply rescale to tmem_output IN PLACE (tmem_output *= rescale)
d. tcgen05.st exp(scores - new_max) back to TMEM → P operand (via C-fragment composition)
e. Release mma_si (softmax_done — MMA warp can re-acquire and issue PV MMA)
4. MMA warp waits on mma_si acquire (softmax done), MMA2: P @ sV → tmem_output (accumulate=True)
5. Stage released, load warp can refill it
After all tiles: epilogue warps tcgen05.ld tmem_output, divide by row_sum, cast to BF16, store to GMEM
```
---
## Test Results
| File | Description | Cosine | Status |
|------|-------------|--------|--------|
| `test_stage_a_v2.py` | Q@K^T only | 0.999999 | ✅ PASS |
| `test_mma_si_only.py` | Q@K^T + mma_si pipeline (no PV) | 0.999999 | ✅ PASS |
| `test_softmax_only.py` | Q@K^T + softmax packing, output S | 0.52 | ❌ S overwritten by P (expected) |
| `test_mma_si_pv.py` | Q@K^T + softmax + P@V (V MN-major, 128x64) | 0.01 | ❌ PV output garbage |
| `test_pv_diag.py` | Q@K^T + softmax + P@V (V=I/random, 128x128) | 1.0 / 0.999999 | ✅ PASS |
| `test_layout_compare.py` | Print TMEM layouts for QK S and PV A-fragment | N/A | layout inspection |
| `test_stage_b_v7.py` | Q@K^T + C-fragment softmax (V=K, wrong major) | -0.02 | ❌ wrong major + P packing |
| `test_stage_b_v20.py` | Q@K^T + softmax (V=K, PipelineTmaStore bug) | N/A | ❌ compile error |
---
## Critical APIs & Lessons
### TMEM offset arithmetic
- `find_tmem_tensor_col_offset(fragment)` — returns physical TMEM column count
- QK accumulator: 128 TMEM columns
- A-fragment offset: `acc_dtype.width // q_dtype.width * tmem_p0_offset` (F32/BF16=2)
### pv_mma_tiler — FMHA Convention
```python
pv_mma_tiler = (qk_mma_tiler[0], qk_mma_tiler[2], qk_mma_tiler[1])
# = (M, head_dim, QK_N) = (128, 64, 128) for head_dim=64
```
FMHA passes `pv_mma_tiler[:2] = (128, head_dim)` to `make_trivial_tiled_mma`, NOT the QK tiler `(128, 128)`.
### make_trivial_tiled_mma — Use New Overload
```python
make_trivial_tiled_mma(a_dtype, b_dtype, a_leading_mode, b_leading_mode,
acc_dtype, cta_group, mma_tiler_mn, a_source=SMEM)
```
### 3D tensors required
Tensors must be 3D (M, K, L) for `cute.local_tile` — add L=1 dimension.
### Other APIs
1. `cutlass_torch.from_dlpack(t).mark_layout_dynamic(leading_dim=...)` — CuTe tensor from PyTorch
2. `PipelineTmaUmma.create(...).make_participants()` — returns (producer, consumer) pair
3. `utils.gemm.sm100.epilogue_tma_store` — handles transform + partition/dcopy. DO NOT hand-roll.
4. `smem.allocate_tensor()` — for SMEM tensors
5. `LayoutEnum.from_tensor(a).mma_major_mode()` — major mode from cute tensor
## Environment
- **Server**: root@45.76.247.107 (B200, 180 GiB HBM3e per GPU)
- **venv**: `source /root/dsv4-nvfp4-workspace/venv/bin/activate`
- **PYTHONPATH**: `/root/dsv4-nvfp4-workspace/kernel`
- **Model**: `/root/nvidia-meeting/DeepSeek-V4-Pro-NVFP4`
- **vLLM repo**: `/root/dsv4-nvfp4-workspace/vllm` (modified for Blackwell)
- **Pseudocode**: `/root/fragile-kernel-example/README.md`
- **fmha.py reference**: `/root/cutlass/examples/python/CuTeDSL/cute/blackwell/kernel/attention/fmha/fmha.py`
- **fmha_bwd.py reference**: `/root/cutlass/examples/python/CuTeDSL/cute/blackwell/kernel/attention/fmha/fmha_bwd.py`