Repository navigation
metal: OUT_PROD multiplies in float staging; a training gradient overflowed half - #43
Conversation
…flowed half kernel_mul_mm_f32_f32 stages both inputs through half in threadgroup memory. An activation fits; a training GRADIENT does not. On Qwen3.5 0.8B's LoRA backward (M5, 2026-10-06) one OUT_PROD saw |grad| up to 131273, 95 values past half's 65504. They staged as Inf, and 32,256 outputs came out NaN/Inf. Its inputs, read back right after the op, were finite and correct; the finite outputs carried half's rounding too (3053.17 against 3052.11 computed in double), so every Metal OUT_PROD, the whole LoRA backward, lost ~1e-3 relative precision. - kernel_mul_mm places sb at 64*32*sizeof(S0) (4096 for every half instantiation, as before) - a float-staged instantiation, kernel_mul_mm_f32_f32_fp32 (simdgroup kernel only, not built with the tensor API), with its pipeline getter: 12288 bytes of threadgroup memory - OUT_PROD's internal product uses it whenever the simdgroup kernel runs (the opt-in tensor API keeps its own path) This covers the whole matmul backward: ggml's MUL_MAT backward computes BOTH gradients through OUT_PROD (src0's as out_prod(src1, grad), src1's as out_prod(src0, grad^T)), so no plain MUL_MAT takes a gradient as src1. Test: test_out_prod gains b_range, and cases with |b| up to 1.5e5 (F32 and q8_0 weights, both layouts) run against CPU. Known-positive: with half staging they FAIL (NaN against CPU ~3e5, 33/37); with float staging 37/37 pass. Measured on the 0.8B, window 1024, seed 7: recompute OFF eval 1.9292085 -> 1.9291925 (the precision); layer-3-only recompute NaN -> finite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
|
Reviewed. Approve (as a comment: shared account). The fix is the right one:
One non-blocking trap worth a line or an assert: float staging is used only when |
|
Review of 285ff85; the verdict comes once CI is in (the macOS/Metal jobs compile the new instantiation). The fix is right:
One open question, the tensor-API path: Note: float staging halves the threadgroup tile throughput of OUT_PROD. That's right for training; worth a sentence with an epoch-time receipt next to the 0.8B numbers, so the cost is known. |
|
Answers to both. (1) Tensor API ON: the gate passes, 37/37 with |
|
APPROVED at 285ff85. Both open points are answered with receipts: the gate passes 37/37 with the tensor API ON (no half staging on that path), and float staging is FASTER on 27B-sized OUT_PRODs (27 vs 41-59 ms; 88-94 vs 168-197), so it is free where it matters. The macOS builds that compile the change are green (arm64, x64, iOS, tvOS, visionOS). The runtime proof is the 37/37 on the M5, where half staging fails 33/37. The red Windows check was a CPU server test this change does not build into (BigMama traced it). The MUL_MAT backward is all OUT_PROD in this ggml, so this covers the matmul backward. |
kernel_mul_mm_f32_f32 stages both inputs through half in threadgroup memory. An activation
fits; a training GRADIENT does not. On Qwen3.5 0.8B's LoRA backward (M5, 2026-10-06) one
OUT_PROD saw |grad| up to 131273, 95 values past half's 65504. They staged as Inf, and 32,256
outputs came out NaN/Inf. Its inputs, read back right after the op, were finite and correct;
the finite outputs carried half's rounding too (3053.17 against 3052.11 computed in double),
so every Metal OUT_PROD, the whole LoRA backward, lost ~1e-3 relative precision.
with the tensor API), with its pipeline getter: 12288 bytes of threadgroup memory
API keeps its own path)
This covers the whole matmul backward: ggml's MUL_MAT backward computes BOTH gradients
through OUT_PROD (src0's as out_prod(src1, grad), src1's as out_prod(src0, grad^T)), so no
plain MUL_MAT takes a gradient as src1.
Test: test_out_prod gains b_range, and cases with |b| up to 1.5e5 (F32 and q8_0 weights,
both layouts) run against CPU. Known-positive: with half staging they FAIL (NaN against CPU
~3e5, 33/37); with float staging 37/37 pass.
Measured on the 0.8B, window 1024, seed 7: recompute OFF eval 1.9292085 -> 1.9291925 (the
precision); layer-3-only recompute NaN -> finite.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo