Commit Graph

197 Commits

Author SHA1 Message Date
3c295f225a P3: integrate CUDA RoPE kernel into single_shot — 732 launches/token eliminated
_apply_rope now uses dsv4.ops.rope_cuda (1 CUDA kernel per call)
instead of PyTorch ops (5-6 kernels per call).
Total: 183 RoPE calls × (5-1) = 732 launches saved per token.
With fallback to PyTorch if CUDA kernel fails.
2026-06-02 09:08:07 +00:00
553275d810 feat: P1 — add eager warmup_fused_swiglu_compilation for SharedExpert (1-group) 2026-06-02 08:25:52 +00:00
d8e17d70c1 P0+P1+P2: Enable fused SwiGLU (MoE+SE), fix SE _run_l1_fused, remove per-call gsa fill_
P0: Enable fused SwiGLU for MoE (set_fused_swiglu(True))
  - Saves 240+ unfused BF16 kernel launches per token
  - SiLU + clamp in kernel registers instead of separate launches

P1: Fix shared expert _run_l1_fused + enable fused SwiGLU
  - Fixed: _l1_sf_view -> _l1_scale_b, _l1_gs_view -> _l1_gsb
  - Fixed: expert_offsets dtype int64 -> int32
  - Added proper padded buffer + scale assembly (matching unfused path)
  - Added runtime gsa support (quantize_nvfp4_gpu_fused)

P2: Remove per-call gsa_buf.fill_() in Nvfp4Linear
  - fill_() was H2D transfer every forward pass (~5µs × 244 calls = ~1.2ms/token)
  - _gsa_buf now initialized with _activation_global_scale (not zeros)
  - After warmup_gsa, buffer already has correct value — no fill needed
2026-06-02 07:57:39 +00:00
790f8c350a perf: P2 landed (gsa fill elimination). P0/P1 fused SwiGLU disabled — CuTeDSL kernel arg-binding bug.
P0/P1: The fused SwiGLU kernel's warmup_fused_swiglu_compilation() triggers
'TypeError: too many positional arguments' during cute.compile(). The kernel
signature doesn't match the positional args being passed. This is a kernel-side
fix, not a single_shot fix. Disabled until the fused kernel is debugged.

P2: Landed — Nvfp4Linear skips redundant _gsa_buf.fill_() after warmup.

SE fused SwiGLU infrastructure (set_fused_swiglu, _run_l1_fused, interleaved
weight path) is wired but disabled. Will activate once kernel fix lands.
2026-06-02 07:16:08 +00:00
040b2eb6e7 perf: P0/P1/P2 — fused SwiGLU for MoE+SE, eliminate per-call gsa fill
P0: Enable fused SwiGLU for all MoE instances (moe._fused_swiglu = True).
    Eliminates ~8 BF16 kernel launches per MoE per token (gate/up split,
    SiLU, clamp, elementwise multiply → single fused kernel launch).

P1: Enable fused SwiGLU for shared expert (SE):
    - Added set_fused_swiglu() method to Nvfp4SharedExpert
    - Added _run_l1_fused() using run_fused_swiglu_grouped_gemm (1-group)
    - Interleave L1 weights at finalize time for fused kernel compatibility
    - Fused kernel handles SwiGLU + clamp in registers, outputs BF16

P2: Eliminate per-call _gsa_buf.fill_() in Nvfp4Linear:
    - _activation_global_scale is set once at warmup, never changes after
    - Skip redundant fill_() via _gsa_buf_initialized flag
    - Saves 244 CPU→GPU scalar fills per token (4 linears × 61 layers)

P3: Deferred (in-kernel RoPE fusion — kernel-side change, not single_shot)
2026-06-02 06:59:25 +00:00
e9506e0c20 perf: C1/C2/C3 — per-layer max_comp, pre-allocated gather_buf, SWA views
C1: --max-context CLI flag (default 8192). KVCache.max_comp computed from
    (max_context + compress_ratio - 1) // ratio per layer type.
    CSA at 8192 context → 2048 entries. HCA at 8192 → 64 entries.
    No more hardcoded 65536 that wastes memory on HCA layers.

C2: Pre-allocated gather_buf (indexer_top_k + window_size, hd) in KVCache.
    Gather writes compressed+SWA into this buffer via slice assignment.
    Zero torch.cat allocations on the hot decode path.

C3: get_swa returns views (no .clone()). Ring-buffer wrap returns indexed
    views. Caller copies into gather_buf so no aliasing risk.
2026-06-02 06:18:06 +00:00
617da29a5b fix: assert topk_idx is not None in CSA layers — no silent fallback to SWA-only
The indexer silently returning None caused CSA layers to attend over only the
SWA window (128 tokens), not the compressed sparse KV. This went undetected
because the model still produced plausible output at short context. The assert
makes any future indexer regression immediately visible.
2026-06-02 06:14:23 +00:00
5b4c496512 fix: three indexer bugs — weight path, comp_idx_buf width, scoring einsum
1. Indexer.load: weights at *.indexer.kv_proj not *.indexer.compressor.kv_proj
2. KVCache.comp_idx_buf: width=ihd (128) not head_dim (512); parametric via indexer_key_dim
3. Indexer.forward: stored keys are (n_comp, ihd) not (n_comp, n_ih, ihd);
   einsum changed from 'tnd,cnd->tnc' to 'tnd,cd->tnc' — key shared across indexer heads
   (paper's c_I = ihd = 128, one vector per compressed block)

Also removed probe diagnostics (COMPRESSOR BUFFERING, COMPRESSOR OUT, INDEXER SKIP,
RESHAPE FAILURE, indexer load state) — served their purpose.
2026-06-02 05:53:10 +00:00
8162c586c3 probe: fix comp_idx_buf width to ihd=128 so indexer probe can complete 2026-06-02 05:38:44 +00:00
5be31d8582 fix: indexer compressor weight path — weights are at *.indexer.kv_proj not *.indexer.compressor.kv_proj 2026-06-02 05:25:44 +00:00
fdfcca918c probe: verify indexer compressor load state 2026-06-02 05:17:00 +00:00
fb0ed87626 probe: add indexer compressor early-return and buffering diagnostics 2026-06-02 05:06:18 +00:00
06c92f208f INDEXER PROBE: instrumentation prints for compressed key width investigation 2026-06-02 04:44:47 +00:00
f0dec9f6bd profile: fine-grained attention component timing 2026-06-02 03:08:34 +00:00
7114c48575 fix: parenthesize profile_detail condition 2026-06-02 02:56:13 +00:00
4734e894c7 profile: add per-layer attn vs ffn timing with CUDA sync 2026-06-02 02:46:35 +00:00
4017ef2f16 fix: accurate profile sync + remove paris_tids 129K iteration 2026-06-01 23:55:26 +00:00
73ae9393da FIX: RoPE cache 8192→65536 (original_max_position_embeddings), KVCache max_comp 32768→65536 2026-06-01 23:18:37 +00:00
36f9782bad Add thinking/Paris token logit check on step 0 for quality debugging 2026-06-01 23:14:24 +00:00
ef7e0d63bb Add --warmup-gsa flag: fix attention/router gsa after first decode step to eliminate amax kernel launches 2026-06-01 23:04:44 +00:00
008e59eb90 Add --profile flag: per-component GPU timing with CUDA sync (embed+layers, lm_head, sampling) 2026-06-01 23:03:46 +00:00
e53645654d Reduce hot-path .item() syncs: gate li>=58 diagnostics behind VERBOSE>=2, topk on float 2026-06-01 22:33:03 +00:00
6f4bbc997a Add sync after sampler for step<3 to catch async CUDA errors early 2026-06-01 22:32:40 +00:00
5493a8727e P7: compressor early return + decode buffering (skip GEMMs when n_complete=0); sampler SMEM fix (LK=24 fits 48KB default); topk on float not bf16 2026-06-01 22:29:56 +00:00
583ad6cfe6 P0 complete: Kill .item() in grouped_linear, reduce hot-path syncs
- grouped_linear.py: Replace .item() gsa + Python quantize with
  quantize_nvfp4_gpu_fused (zero CPU syncs). Flatten all groups
  into (G*T, D), single fused kernel launch, GPU-only gsa copy.
- single_shot_inference.py: Reduce torch.cuda.synchronize() to
  every 20 steps instead of every step. Gate per-layer diagnostics
  to li<3 or li>=58 (avoid 61 .item() calls per decode step).
2026-06-01 22:21:12 +00:00
8767c263ab Add cuda.synchronize + better logits validation after lm_head
Catch CUDA errors at the source instead of seeing them
surfaced at torch.topk. Print logits stats every step.
2026-06-01 22:06:41 +00:00
2a6f9a10b1 lm_head: fall back to BF16 F.linear for stability
NVFP4 quantize_from_buffer produces CUDA error on large-magnitude
inputs (|X|>500 at L60 output). BF16 lm_head is correct and only
runs once per decode step — not a bottleneck.

TODO: debug the NVFP4 path for large activations and re-enable.
2026-06-01 22:05:22 +00:00
9bad30c777 Add logits validation debug before topk sampling 2026-06-01 21:59:23 +00:00
e3412cf913 P5: In-place RoPE — no x.clone(), no empty_like allocation
Eliminates 183 kernel launches per decoded token from pointless memcpy.
Operates on rope dims in-place via views instead of cloning the full tensor
and allocating an empty_like buffer.
2026-06-01 21:18:41 +00:00
230d28e562 Fix KVCache constructor call — device as keyword arg, not positional
KVCache signature has max_comp before device, so positional pass of dev
was hitting max_comp parameter instead of device.
2026-06-01 21:11:01 +00:00
c8faf20a99 P0 COMPLETE: Eliminate ALL .item() CPU-GPU syncs from NVFP4 activation path
Fused kernels (zero CPU sync, single kernel launch per projection):
- fused_amax_quantize.cu: amax→gsa→quantize in one pass. Replaces two-step
  compute_amax_gsa_gpu + quantize_nvfp4_gpu (had .item() sync).
- fused_deinterleave_amax_quantize.cu: Same for MoE fused_swiglu L2 path.
  Deinterleave + amax + quantize in one pass. Replaces compute_amax_gsa_gpu
  + deinterleave_quantize_nvfp4_cuda (had .item() sync).

All kernel loaders use dsv4/kernels/cuda/loader.py (compile-once cache).
Was JIT-compiling on every call via torch.utils.cpp_extension.load (~100ms/call,
~500 calls/token). Now compiles once and reuses the cached module.

Updated layers:
- linear.py Nvfp4Linear._run_impl: fused kernel, gsa via GPU buffer
- moe.py Nvfp4MoE._run_impl: fused for L1 and L2 (both fused_swiglu and
  non-fused paths)
- shared_expert.py: fused for L1 and L2
- quantize.py: All functions use module loader cache
- sampler.py: Uses module loader cache
- indexer/score_topk.py: Uses module loader cache

P2: Vectorized KVCache.append_swa — index_copy_ instead of Python loop.
2 kernel launches instead of 2T. No .item() in comp_pos either.

P3: Pre-allocated comp_kv buffers — O(1) append instead of O(N) torch.cat.
max_comp=32768 per layer (32MB). No more quadratic memory growth.

~486 .item() syncs per decoded token → ~0 (only argmax + token decode remain).
2026-06-01 21:05:03 +00:00
360f76b970 Performance audit fixes: eliminate CPU-GPU syncs
PERFORMANCE_AUDIT.md validation results:
  1. Nvfp4Linear .item() sync (610/step) → FIXED: compute_amax_gsa_gpu kernel
  2. MoE .item() sync (183/step) → FIXED: same kernel
  3. SharedExpert .item() sync (122/step) → FIXED: same kernel
  4. FMHA V clone → FIXED: V=K, transpose creates copy implicitly
  5. torch.cuda.synchronize in moe_forward → FIXED: conditional on VERBOSE
  6. RoPE 8x duplication → INVALIDATED: necessary for per-GPU HBM access
  7. mHC BF16 bmm → INVALIDATED: 28K FLOPs, not a bottleneck
  8. Router .float() cast → INVALIDATED: needed for FP32 topk, ~1μs

New files:
  - dsv4/kernels/cuda/amax_gsa.cu: GPU-only amax→gsa kernel
  - dsv4/ops/quantize.py: compute_amax_gsa_gpu() wrapper

Net effect: ~915 fewer CPU-GPU syncs per decode step
Remaining syncs: ~10 per layer (quantize kernel parameter) + diagnostics
2026-06-01 20:40:19 +00:00
4f698baa5d Production fused CUDA sampler + decode loop optimizations
- Add dsv4/kernels/cuda/sampler.cu: fused temperature + repetition penalty
  + top-k + top-p (nucleus) sampling, single kernel launch, zero CPU syncs
- Add dsv4/model/sampler.py: CUDASampler wrapper + PyTorch reference
- Update single_shot_inference.py:
  - Use CUDASampler for non-greedy decoding (temperature=0.6, top_k=50, top_p=0.95)
  - Pre-allocate decode buffers (no per-step torch.tensor allocation)
  - Track thinking tokens (128821/128822) — not garbage for reasoning model
  - Reduce diagnostic CPU syncs (top-5 every 5 steps, NaN check every 20)
  - Add --top-k and --top-p CLI args
  - Default: temperature=0.6 (was 0.0 greedy), rep_penalty=1.1 (was 1.2)
2026-06-01 20:29:57 +00:00
2830a3ee7c Fix lm_head NVFP4: transpose weight and scales to match Nvfp4Linear checkpoint layout
quantize_weight_to_nvfp4 returns (K_packed, N) but Nvfp4Linear expects
(N, K_packed) from the checkpoint format. Transpose both fp4 and sf.
2026-06-01 19:51:21 +00:00
16b72b9581 PERF: Eliminate double quantization for o_a_proj + NVFP4 lm_head
1. o_a_proj (Nvfp4GroupedLinear): Added load_nvfp4_weight() method
   that loads checkpoint NVFP4 weights directly — no more dequant→BF16→requant.
   Each group's weight is transposed from (N, K_packed) checkpoint layout
   to (K_packed, N) layout expected by the grouped GEMM.

2. lm_head: Quantize BF16 weight to NVFP4 at load time, use production
   Nvfp4Linear GEMM instead of F.linear. Runtime gsa for activation.
   Frees the 1.8GB BF16 weight after quantization.

3. Hash router (L0-2): Already optimal — tid2eid is an int32 lookup,
   no GEMM to accelerate.
2026-06-01 19:41:21 +00:00
9a3bb43f20 Set default max-tokens=512 for reasoning model 2026-06-01 17:27:01 +00:00
db6e3545da Fix: add _use_runtime_gsa=True to router gate GEMM in single_shot
The checkpoint-path gate was using the checkpoint's input_scale as gsa
— the same E4M3 overflow bug we fixed in Nvfp4Linear/Nvfp4MoE/etc.
The runtime-quantized BF16 path was using 1/(6*448) as a fixed gsa.

Both now compute gsa from actual activation magnitude at runtime.
2026-06-01 17:25:04 +00:00
1a6d9ee29b Reset to greedy decoding (temperature=0) 2026-06-01 15:04:02 +00:00
a48d6e14ae Default temperature=0.7 with rep penalty 2026-06-01 14:55:43 +00:00
1d64b863ca Add temperature sampling + repetition penalty to fix degenerate repetition
With --temperature 0.7 --repetition-penalty 1.2, the model should generate
more diverse text instead of repeating 'France' endlessly.
2026-06-01 14:54:49 +00:00
6cca16f97a Set max-tokens=128 default, clean up for final verification 2026-06-01 14:43:48 +00:00
a0e758ec3b Set default max-tokens=30 for faster iteration 2026-06-01 14:33:55 +00:00
2b1fca6dae CRITICAL FIX: runtime activation global scale to prevent E4M3 overflow
The checkpoint's input_scale was designed for training-time FP8 quantization,
not NVFP4 activation quantization. Using it as gsa causes x/gsa to exceed
the E4M3 block scale maximum (448), leading to systematic magnitude loss
in every projection. This accumulates over 61 layers, compressing the
logit range and producing garbage tokens.

Fix: compute gsa at runtime from actual activation magnitude:
  gsa = max(|x|) / (6.0 * 448.0)
This ensures x/gsa ≤ 2688 (the maximum representable in E4M3 block scales).

Applied to: Nvfp4Linear, Nvfp4GroupedLinear, Nvfp4MoE, Nvfp4SharedExpert, Router gate
2026-06-01 14:21:16 +00:00
3e47d5f20a Add prod vs ref GEMM comparison test + gate logits diagnostic 2026-06-01 14:11:37 +00:00
ad143afe37 Add L58-60 diagnostic: mHC A/B/C, MoE routed/shared, topk 2026-06-01 13:55:55 +00:00
7a05d3d3af NVFP4 router gate: use Nvfp4Linear for both checkpoint and quantized paths
- Checkpoint path: load NVFP4 gate weight directly into Nvfp4Linear
- BF16 path: quantize and load into Nvfp4Linear
- Both paths use proven production GEMM (no custom kernel)
- load_nvfp4_fused_gate now creates Nvfp4Linear from BF16 weight
2026-06-01 11:25:50 +00:00
e5dbe1ed22 Switch router to Nvfp4Linear production GEMM (custom CuTeDSL kernel crashes MLIR)
The custom fused router kernel crashes the CuTeDSL MLIR optimizer
even with a simplified epilogue. Switch to the proven Nvfp4Linear
path which uses the same NVFP4 Blackwell tensor-core GEMM, just with
2 kernel launches (GEMM + activation_topk) instead of 1.

- Router's load_nvfp4_fused_gate now stores raw tensors for future use
- single_shot_inference.py creates Nvfp4Linear from quantized gate weight
- _run_dense_impl prioritizes gate_lin (NVFP4) over BF16 fallback
2026-06-01 11:17:54 +00:00
fbc1e883f2 Add try/except around fused NVFP4 gate loading with error reporting
If the fused kernel path fails, fall back to BF16 cuBLAS instead of
crashing. This lets us see the actual error and continue testing.
2026-06-01 11:08:06 +00:00
5f38430423 Fix: use 1-dim tensors for gate_ws2 and gate_input_scale 2026-06-01 11:05:09 +00:00
71deeb91a9 Quantize BF16 gate weight to NVFP4 for fused router + add global scales to GEMM
CRITICAL: Checkpoint stores gate weights as BF16, not NVFP4.
Previous code fell back to BF16 cuBLAS because weight_scale was missing.
Now we quantize the BF16 gate weight to NVFP4 at load time using
quantize_to_nvfp4() and pass the result to the fused router kernel.

Also added global scale (gsa, gsb) parameters to the kernel:
- gsa (activation global scale) applied during activation quantization
- gsb (weight global scale) applied in epilogue before sqrt(softplus)
- The MMA output is (A * SFA) @ (B * SFB), missing gsa*gsb
- Epilogue now computes sqrt(softplus(logit * gsa * gsb))
  instead of sqrt(softplus(logit))
2026-06-01 10:14:29 +00:00