Skip to content

fix(train): NF4 QLoRA CUDA forward computed the WRONG MODEL — NeoX rope pairing + Q/K/V biases + full-warp softmax + causal CPU oracle - #2252

Merged
noahgift merged 1 commit into
mainfrom
fix/nf4-train-loss-parity-main
Jul 2, 2026
Merged

fix(train): NF4 QLoRA CUDA forward computed the WRONG MODEL — NeoX rope pairing + Q/K/V biases + full-warp softmax + causal CPU oracle#2252
noahgift merged 1 commit into
mainfrom
fix/nf4-train-loss-parity-main

Conversation

@noahgift

@noahgift noahgift commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Cascade defect #4 (after #2249 deadlock, #2250 loss window, #2251 stream race): training ran NaN-free but never learned — CE flat at 13-14 (> ln(V)=11.93, worse than uniform) on trivially predictable targets, then adapter poisoning after ~125 steps. Three-oracle bisection (GPU-fused vs GPU-forward+CPU-CE vs pure-CPU) localized it to the transformer forward and exonerated the fused CE kernel — then per-op layer-0 bisection found four stacked defects:

  1. Wrong RoPE pairing (dominant)batched_rope_neox_forward/_backward instantiated BatchedRopeKernel (GPT-J adjacent pairs, elem0=2*pair_idx) despite the NeoX name. Qwen2 weights need NeoX split-half (i, i+d/2). Fix: new BatchedRopeNeoxKernel/BatchedRopeNeoxBackwardKernel (precise trig per CORRECTNESS-013); wrappers + pre-warm keys rewired. BatchedRopeKernel untouched — realizar uses it for non-NeoX rope types.
  2. Dropped Q/K/V biasesCudaNf4TransformerBlock had zero bias support; Qwen2 sets use_bias=true and the model carries blk.N.attn_{q,k,v}.bias (CPU path applies them). Fix: replicated bias buffers + cuda_add_inplace after each projection GEMM, threaded from all 3 NF4 sites + the instruct FP32 site. Backward unchanged (activation-checkpoint re-runs the fixed forward; biases frozen).
  3. Partial-warp shfl.sync UB — batched softmax fwd/bwd launched block=(32.min(row_size)) while reducing with membermask 0xFFFFFFFF → undefined for seq<32 → data-dependent NaN rows. Fix: always full 32-lane warp.
  4. Non-causal CPU oracle (label leakage)autograd/ops/attention.rs had no causal mask; the CPU train/eval path attended bidirectionally (toy causal CE 2.13 misreported as 0.17). Fix: attention_causal dispatched for ModelArchitecture::Decoder (encoders stay bidirectional); shared backward exact since masked weights are 0.

Verification (RTX 4090, sm_89)

  • Falsifier FALSIFY-CUDA-NF4-TRAIN-LOSS-PARITY-001 (parity_probe.rs): GPU loss + full logits vs an NF4-quantization-matched causal CPU oracle. GREEN: CE 0.6820 vs 0.6557 (|Δ|=0.026 < 0.5), logits relL2 0.047 < 0.10. Mutation-verified RED per individually reverted fix: rope → relL2 0.183; biases → CE 5.17 / relL2 0.97; warp → NaN. Companion probes: per-layer bisect + per-op layer-0.
  • Contract contracts/cuda-nf4-train-loss-parity-v1.yamlpv validate + pv lint contracts/ PASS.
  • E2E (apr_code_sft_balanced.jsonl, 160 samples, --max-seq-len 2048): first-step CE 1.58 (pre-fix: 13-14); at lr 2e-5 loss descends 4.31→0.29 by step 50, epoch avg 1.55, 0 NaN — independently reproduced on this cherry-picked content: epoch 1 avg_loss=1.5842, 0 NaN, run still converging.
  • aprender-gpu --lib 440/440; aprender-train --lib failures (6) all reproduce on the base commit (pre-existing: release-mode debug_assert class, insta snapshot drift).

Known follow-ups (separate beats, not claimed here)

  • Auto-config lr 2e-4 @ rank 256 diverges after ~25 steps (finite) — training-dynamics defect; lr 2e-5 proven stable/decreasing.
  • evaluate() val_loss is byte-identical across runs with different lr (pre-existing) — likely no GPU→CPU LoRA sync before eval; needs its own bisection.
  • rms_norm_backward hardcodes eps=1e-5 (backward-only precision vs Qwen2 1e-6).
  • Single-row SoftmaxKernel broken by construction for all lengths (not on the training path).

With this, apr finetune -m qlora trains the correct model on the 4090 end-to-end — the apr-code tool_call flip is running on it now.

🤖 Generated with Claude Code

…pe pairing, Q/K/V biases, full-warp softmax, causal CPU oracle (FALSIFY-CUDA-NF4-TRAIN-LOSS-PARITY-001)

Cascade defect #4: after the stream-ordering fix (#2249/#2250/stream-bind)
apr finetune -m qlora trained end-to-end but the loss sat FLAT at CE 13-14
(> ln(151936)=11.93 — worse than uniform) on data the base model emits
correctly in inference, then adapters blew into permanent NaN at ~step 125.
The forward was finite but WRONG. Oracle bisection (pure-CPU CE vs
GPU-forward+CPU-CE vs GPU-forward+fused-GPU-CE, then per-op layer-0
bisection vs a manual CPU replay) found FOUR stacked defects:

1. WRONG ROPE PAIRING (dominant): entrenar's batched_rope_neox_forward/
   _backward wrappers instantiated BatchedRopeKernel — ADJACENT-pair
   (GPT-J) rotation, the convention realizar reserves for non-NeoX rope
   types. Qwen2/LLaMA need NEOX split-half pairs (i, i+d/2). Every
   layer's Q/K rotated in the wrong basis (post-rope relL2 0.42/0.65 vs
   oracle while un-roped V matched at quant noise 0.09). Fix: new
   BatchedRopeNeoxKernel + BatchedRopeNeoxBackwardKernel (precise trig,
   CORRECTNESS-013) in aprender-gpu; wrappers + pre-warm keys rewired.
   BatchedRopeKernel semantics preserved for realizar's non-NeoX users.

2. DROPPED Q/K/V BIASES: CudaNf4TransformerBlock had no bias support at
   all (Qwen2 use_bias=true; CPU path applies them). Bias drop alone
   shifts toy causal CE 2.13 -> 4.49. Fix: replicated bias buffers +
   cuda_add_inplace after each projection GEMM (before QK-norm/RoPE),
   threaded from all three NF4 construction sites + the instruct FP32
   site (which passed None despite FALSIFY-CUDA-FORWARD-PARITY-002).

3. PARTIAL-WARP SHFL UB: batched softmax forward/backward launched
   block=(32.min(row_size)) while the kernels' reductions use shfl.sync
   membermask 0xFFFFFFFF — undefined with inactive named lanes (PTX
   ISA). For seq<32 row max/sum picked up garbage data-dependently ->
   0/0=NaN rows. Fix: always launch a FULL 32-lane warp (guarded loops
   carry reduction identities on idle lanes).

4. NON-CAUSAL CPU ORACLE (label leakage): autograd::ops::attention had
   NO causal mask — the CPU train/eval path for decoder models attended
   bidirectionally, leaking label tokens backwards (toy causal CE 2.13
   reported as 0.17) and corrupting CPU training/eval. Fix:
   attention_causal (masked scores; shared softmax backward is exact
   since masked weights are 0) dispatched for ModelArchitecture::Decoder;
   encoders stay bidirectional.

Falsifier FALSIFY-CUDA-NF4-TRAIN-LOSS-PARITY-001 (parity_probe.rs):
GPU fused loss vs an NF4-quantization-MATCHED causal CPU oracle
(|dCE|<0.5, full-logits relL2<0.10, toy CE<6, fused-vs-CPU CE on
identical logits <0.05). Mutation-verified RED per reverted fix:
rope -> relL2 0.183; biases -> CE 5.17/relL2 0.97; warp -> NaN.
GREEN: CE 0.6820 vs 0.6557 (|d|=0.026), logits relL2 0.047.

E2E (RTX 4090, apr_code_sft_balanced @ --max-seq-len 2048): first-step
CE now 1.58 (was 13-14), 0 NaN across the epoch; at lr 2e-5 the loss
DECREASES 4.31 -> 0.29 by step 50. (The auto-selected lr 2e-4 @ rank 256
still diverges after ~25 steps — separate training-dynamics defect,
gradients verified descending.)

Contract: contracts/cuda-nf4-train-loss-parity-v1.yaml (pv lint PASS).
Probes: instruct_pipeline/parity_probe.rs (loss/logits falsifier +
layer bisect), transformer/cuda_block_parity_probe.rs (per-op layer-0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@noahgift
noahgift enabled auto-merge July 2, 2026 00:10
@noahgift
noahgift added this pull request to the merge queue Jul 2, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 2, 2026
@noahgift
noahgift added this pull request to the merge queue Jul 2, 2026
Merged via the queue into main with commit fcc0fbc Jul 2, 2026
11 checks passed
@noahgift
noahgift deleted the fix/nf4-train-loss-parity-main branch July 2, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant