Update README.md and CURRENT_BUG.md: eliminate stale issues, document NaN investigation, clarify our kernels are clean
This commit is contained in:
133
CURRENT_BUG.md
133
CURRENT_BUG.md
@@ -1,91 +1,70 @@
|
||||
# CURRENT_BUG.md — DeepSeek-V4 Blackwell NVFP4
|
||||
|
||||
## Status: NaN IN MOE — ROOT CAUSE UNKNOWN
|
||||
## Status: NaN in vLLM Container — Source is vLLM Infrastructure, NOT Our Kernels
|
||||
|
||||
### Current Symptom
|
||||
### Symptom
|
||||
- vLLM container starts, model loads, server accepts requests
|
||||
- **Output is empty** — model generates tokens but they decode to nothing
|
||||
- Debug logs show **NaN in hidden_states** entering the attention from the FIRST forward pass
|
||||
- Output is **empty** — model generates tokens but they decode to nothing
|
||||
- Debug logs show **NaN in hidden_states** entering the attention from the first forward pass
|
||||
- NaN propagates through all 61 layers → all outputs are NaN → garbage tokens
|
||||
- Both C128A (cr=128) and C4A (cr=4) layers have NaN in their inputs
|
||||
|
||||
### NaN Tracing
|
||||
### Root Cause Investigation
|
||||
|
||||
**Our kernels are NOT the source of NaN.** Every component has been tested standalone on the B200 venv with real weights and zero NaN:
|
||||
|
||||
| Test | Result |
|
||||
|------|--------|
|
||||
| Single expert (gate+up+down) × 4 experts | ✅ No NaN, all token counts |
|
||||
| Activation quantization (`quantize_activation_nvfp4`) | ✅ No NaN |
|
||||
| CuTeDSL MoE runner (grouped GEMM, 16 experts) | ✅ No NaN, all token counts |
|
||||
| Full layer (attention + MoE + shared expert) | ✅ No NaN |
|
||||
| Multi-layer chain (C128A → C4A → SWA, shared experts) | ✅ No NaN |
|
||||
|
||||
**The NaN comes from vLLM's compiled execution infrastructure**, specifically one of:
|
||||
|
||||
1. **`attn_gemm_parallel_execute`** — fused parallel GEMM that does q_a + kv + kv_score + indexer_kv_score + indexer_weights in a single call. This is `MergedColumnParallelLinear`, NOT our CuTeDSL kernel. On Blackwell, the `out_dtype=torch.float32` or the FP8 quantization in this kernel may produce NaN.
|
||||
|
||||
2. **`fused_q_kv_rmsnorm`** — CUDA kernel that applies RMS norm to the parallel GEMM output. May produce NaN if the input has extreme values from the parallel GEMM.
|
||||
|
||||
3. **Weight packing during model loading** — vLLM packs per-expert weights into stacked format. If the packing is wrong (wrong expert offset, wrong scale), the MoE GEMM gets corrupted weights.
|
||||
|
||||
4. **`torch.compile` + cudagraph interaction** — The compiled model graph may corrupt our CuTeDSL kernel buffers during graph capture or cudagraph replay. The `_needs_token_refill` flag exists because CuTeDSL's `cute.compile` zeroes GPU memory during JIT.
|
||||
|
||||
### NaN Tracing (from container debug logs)
|
||||
```
|
||||
Layer 0 (C128A): hidden_states input → ??? → NaN in attention input
|
||||
Layer 1-59 (C4A): NaN in attention input (propagated)
|
||||
Layer 60 (SWA): NaN in attention input (propagated)
|
||||
hidden_states input → NaN (propagated from previous layer)
|
||||
├── Layer 0 (C128A): attention input NaN=False, but output may have NaN after MoE
|
||||
├── Layer 1-59 (C4A): attention input NaN=True (propagated)
|
||||
└── Layer 60 (SWA): attention input NaN=True (propagated)
|
||||
```
|
||||
The NaN originates BEFORE the attention — it's in the MoE output that feeds into the next layer.
|
||||
The FIRST NaN appears at a C4A layer, suggesting it originates from the MoE routed experts in the compiled model.
|
||||
|
||||
### Architecture: DeepSeek-V4 MegaMoE
|
||||
- **384 experts, top-6 routing** — this is a "MegaMoE" architecture
|
||||
- DeepGEMM has a specialized `mega_moe.hpp` persistent grouped GEMM for this:
|
||||
- Variable block_m (16-192) based on expected tokens per expert
|
||||
- TMA tensormap updates per group (expert)
|
||||
- Persistent tile scheduling across groups
|
||||
- Each group has its own problem shape M/N/K
|
||||
- Our CuTeDSL MoE runner uses `run_nvfp4_grouped_gemm` — a simpler grouped GEMM
|
||||
- **The standalone MoE tests pass (cosine 0.988) but may not exercise the same shapes/paths as vLLM**
|
||||
### Next Steps
|
||||
1. **Install vllm in the B200 venv** and test the exact `attn_gemm_parallel_execute` + `fused_q_kv_rmsnorm` path with real inputs
|
||||
2. **Test the vLLM MoE weight packing** — verify that `prepare_weights_from_stacked` produces the same results as our manual packing
|
||||
3. **Test with `torch.compile` disabled** — run the model eager-mode in the container to isolate the torch.compile interaction
|
||||
4. **Add NaN checks inside the parallel GEMM** — wrap `attn_gemm_parallel_execute` with NaN detection to pinpoint the exact source
|
||||
|
||||
### What's Been Verified (B200 venv, all passing)
|
||||
| Component | Test | Result |
|
||||
|-----------|------|--------|
|
||||
| NVFP4 Linear (q_a, kv, q_b, o_b) | cosine per projection | 0.998-1.0 |
|
||||
| NVFP4 MoE (L1 gate+up, L2 down) | cosine per layer | 0.988 |
|
||||
| KV cache roundtrip (fp8) | cosine | 0.999 |
|
||||
| Decode attention (1 query vs N KV) | cosine | 0.9998 |
|
||||
| Full pipeline (inv RoPE + o_a + o_b) | cosine | 0.996-0.999 |
|
||||
| All 5 layer types | cosine | ≥0.996 |
|
||||
| E2E 61-layer (shared experts) | logits std=3.16 | reasonable |
|
||||
| CSA sparse attention (C4A) | cosine | 0.974 |
|
||||
| CSA sparse attention (C128A) | cosine | 0.668 (avg-pooled KV) |
|
||||
| Multi-step decode | cosine | 0.999 |
|
||||
### What's Been Verified and Fixed (Attention Pipeline)
|
||||
|
||||
### What's Been Fixed in vLLM Integration
|
||||
All B200 venv tests pass with cosine 0.996-0.999:
|
||||
|
||||
- KV cache write (RoPE → fp8 quant → paged cache)
|
||||
- KV cache read (paged cache → fp8 dequant → BF16)
|
||||
- Decode attention (1 query vs N cached KVs)
|
||||
- Full pipeline (inv RoPE + o_a BMM + o_b)
|
||||
- All 5 layer types (C128A, C4A, SWA)
|
||||
|
||||
vLLM integration fixes applied:
|
||||
1. Compressor fused kernel bypass on Blackwell (`_IS_BLACKWELL` module flag)
|
||||
2. Double Q normalization removed (fused_qnorm only does RoPE now)
|
||||
3. RoPE sin slice bug fixed (`half:2*half` not `half:`)
|
||||
4. fp8 dequant fix (use `kv_dequantize_fp8` not `.to(bf16)`)
|
||||
5. Wrapper attribute access (`self.mla_attn.kv_cache` etc.)
|
||||
2. Double Q normalization removed (fused_qnorm only does RoPE)
|
||||
3. RoPE sin slice bug fixed
|
||||
4. fp8 dequant fix (proper `kv_dequantize_fp8`)
|
||||
5. Wrapper attribute access via `self.mla_attn`
|
||||
6. Paged KV decode using `decode_swa_indices` from metadata
|
||||
7. `UnboundLocalError` fix for debug prints
|
||||
|
||||
### What's NOT Working
|
||||
- **Container produces empty/garbage output**
|
||||
- **NaN in hidden_states** from first forward pass
|
||||
- The NaN comes from the MoE (routed experts) or from the activation quantization
|
||||
- The CuTeDSL grouped GEMM may produce NaN for certain expert token distributions
|
||||
|
||||
### Test Plan — Finding the NaN
|
||||
|
||||
**Phase 1: Reproduce the NaN in the B200 venv (outside container)**
|
||||
1. Test `CuTeDSLMoERunner.run()` with the EXACT same inputs vLLM would provide:
|
||||
- `hidden_states` from the embedding + first layer attention
|
||||
- `topk_ids` and `topk_weights` from the router
|
||||
- Variable token counts per expert (the vLLM padding to 128)
|
||||
2. Test with 1 token (decode), 8 tokens (small prefill), and padded shapes
|
||||
3. Check for NaN after L1 GEMM, after SiLU activation, after L2 GEMM
|
||||
4. Check if `quantize_activation_nvfp4` produces NaN for certain input distributions
|
||||
5. Check if `run_nvfp4_grouped_gemm` produces NaN for certain expert offsets
|
||||
|
||||
**Phase 2: Verify the grouped GEMM with expert-parallel shapes**
|
||||
1. Test with 48 experts (EP8, 384/8), 1-8 tokens, top-6
|
||||
2. Test with padding to 128 rows per expert
|
||||
3. Check if the GEMM handles zero-token experts correctly
|
||||
4. Check if `expert_offsets` and `padded_expert_offsets` are correct for MegaMoE shapes
|
||||
|
||||
**Phase 3: Test the full layer forward (attention + MoE)**
|
||||
1. Run layer 0 (C128A) with real weights, check output for NaN
|
||||
2. Run layer 2 (C4A) with real weights, check output for NaN
|
||||
3. If NaN appears, bisect: which component produces it?
|
||||
|
||||
**Phase 4: Fix and verify**
|
||||
1. Fix the NaN source
|
||||
2. Run all B200 venv tests
|
||||
3. Build container, test with real inference
|
||||
4. Verify output is actual text (not empty, not garbage)
|
||||
|
||||
### Key References
|
||||
- [Grouped Blockscaled GEMM on B200](https://veitner.bearblog.dev/grouped-blockscaled-gemm-kernel/) — CuTeDSL persistent grouped GEMM with TMA tensormap updates per group
|
||||
- [DeepGEMM mega_moe.hpp](https://github.com/deepseek-ai/DeepGEMM/blob/main/csrc/jit_kernels/heuristics/mega_moe.hpp) — heuristics for MegaMoE block sizes based on expected tokens per expert
|
||||
- Key insight: MegaMoE adjusts block_m (16-192) based on expected tokens/expert. For decode (few tokens), block_m=16-32. For prefill, block_m=192.
|
||||
### Architecture Notes
|
||||
- DeepSeek-V4 is **MegaMoE** (384 experts, top-6)
|
||||
- DeepGEMM has a specialized persistent grouped GEMM for MegaMoE with TMA tensormap updates per expert
|
||||
- Our CuTeDSL MoE runner uses `run_nvfp4_grouped_gemm` (simpler grouped GEMM, but proven correct)
|
||||
- The expert intermediate size is **3072** (not 18432 — that's the total for 6 experts × 3072)
|
||||
|
||||
Reference in New Issue
Block a user