Repository navigation
Conversation
Turing has no BF16 tensor cores, so cublasGemmEx on CUDA_R_16BF inputs falls back to a SIMT fp32 kernel (magma_sgemmEx). On an RTX 2080 Ti that was the largest kernel of the prompt path, 20 % of a 4K prompt: the hyper-connection down / up projections, the router and indexer rows, PLE key / value. Gemm::bf16 now converts the weight and the activations (in row slices, into the instance's own scratch) BF16 -> FP16, which is exact for every value inside FP16's range, and runs the existing FP16 GEMM (fp32 accumulate) on the tensor cores. Single-row products keep cuBLAS's GEMV. STRATA_BF16_TC=0/1 overrides; the default is on for sm_75 only, so other cards run exactly what they ran. New test gemm_bf16_parity: Gemm::bf16 against cuBLAS's BF16 product over the prompt path's shapes, within fp32 rounding (worst relative difference 1.7e-5 on sm_75; 0 with the path off). Qwen3.8-Flash-Next Q2_0, RTX 2080 Ti, i7-6700: prefill 877 / 1134 / 1153 / 1116 / 1009 t/s at 4K / 16K / 32K / 64K / 128K -> 1020 / 1337 / 1366 / 1312 / 1166. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
sm_70 can benefit from this too, Volta (V100) cards introduced Tensor core FP16 operations. |
…Volta too) Volta has FP16 tensor cores and no BF16 ones as well, so cuBLAS takes the same SIMT path for BF16 products there. Suggested in review. Not measured on sm_70; STRATA_BF16_TC=0 turns it off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@eelgaev Good point. Volta has FP16 tensor cores and no BF16 ones too, so cuBLAS takes the same SIMT path there. 7b2dcd3 widens the default to compute capability 7.x. I can't measure on a V100. If you can, |
…pt-in, Pascal through fp32 (#655 #540 #395) PR #655's FP16 tensor-core path for Gemm::bf16, adapted so the ready-made engine stays byte-identical: - default on for compute capability 7.0-7.4 only (Volta; the experimental STRATA_EXPERIMENTAL_SM60 build), off for 7.5 (RTX 20): STRATA_BF16_TC=1 opts in, =0 turns it off on any 7.x card - from #540: finite values beyond FP16's range clamp to +-65504; a beta = 1 product (the BF16X2 remainder, in FP16's subnormal band) keeps cuBLAS; the compute capability is cached per device (a layer split can mix cards) - from #395: Pascal (6.x; cuBLAS has no BF16 GEMM there) widens both operands to fp32 by an exact shift and runs cublasSgemm with the same fp32 accumulator, tiled over T in the instance's own buffers sm_80 and newer, and HIP, run the same cuBLAS / hipBLAS call as before. Co-Authored-By: Károly Sárdi <skver@tuta.io> Co-Authored-By: Rafal R@V Prasal <rafal.prasal@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ine and how to opt in docs/OLDER_GPUS.md: Pascal / Volta through the CUDA 12 engine (#395 #600 #540 #655), RTX 20's STRATA_BF16_TC opt-in, AMD gfx1031 (#524), gfx1012 (#442) and gfx906 (#638, #677) with the reporters' numbers; README, AI_SETUP, AMD_HIP and NVIDIA_V100 point to it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Merged for the next release, with your commits and gemm_bf16_parity, and two changes: on by default on Volta only, and opt-in on RTX 20 (STRATA_BF16_TC=1). We have no sm_75 card to gate it on, and the ready-made engine's output stays the same until then. #540's safeguards were added: finite values beyond FP16's range clamp to 65504, and a beta = 1 product (the BF16X2 remainder, in FP16's subnormal band) keeps cuBLAS. A run of the parity test and an A/B on your 2080 Ti with the merged code would help us turn it on for RTX 20. |
|
Thanks for taking it. Here are the parity test and an A/B on the RTX 2080 Ti with the merged code: the gemm_bf16_parityWith the default, the FP16 path is off on sm_75: 0 failures, every difference 0. With
Speed: same binary,
|
| prompt | prefill off | prefill on | decode off → on | |
|---|---|---|---|---|
| 4K | 904 | 1028 | +13.7 % | 49.0 → 49.0 |
| 32K | 1156 | 1371 | +18.6 % | 49.3 → 48.9 |
| 128K | 1010 | 1168 | +15.7 % | 45.6 → 46.3 |
Run to run, prefill spread is under 2 %.
Quality: teacher-forced, the two paths against each other
Free-running greedy text is not a usable gate here. In the one-shot engine, two runs with the same setting already part ways after 115-190 tokens. So I compared the two paths teacher-forced, the way docs/UNSLOTH_Q4.md describes:
- a
--serveengine with--adapt-every 100000; - the first P tokens of a document read by the batched prompt path (the path this changes), as one request;
- a second request with the next 2,048 tokens, which resumes at P (
RESUME P) and reads them through the verify windows with--short-read; STRATA_LOGPOSwithSTRATA_LOGPOS_TOPK=20writing every position.
The text is raw prose (Don Quixote, Spanish), without a chat header.
| prefix read batched | positions | argmax agreement | KL (off ‖ on, top 20 + rest) | perplexity off / on |
|---|---|---|---|---|
| 8,192 | 2,047 | 95.2 % | 0.011 | 3.995 / 4.038 |
| 30,720 | 2,047 | 96.8 % | 0.0085 | 2.146 / 2.159 |
- Noise floor: two runs with the path off write identical files (KL 0, every position the same), so every difference above is the FP16 path.
- Near-ties: the disagreements are almost all near-ties. Where the off run's two most likely tokens are at least 0.5 nats apart, the argmax agrees at 1745 of 1757 positions (8K) and 1858 of 1875 (30K).
- Perplexity: it moves by +1.1 % and +0.6 %. A chat-formatted run of the same 30K prompt went the other way (3.500 → 3.454, without
<|im_end|>). - For scale: both KLs are below the 0.02-0.05 that DETAILS.md lists for other prompt-path changes against the FP16 path.
The server here now runs 0.1.39 with STRATA_BF16_TC=1.
…iko1221#646/Niko1221#655/Niko1221#583/Niko1221#656-reimpl/YIELDED), ours kept where upstream lagged (Niko1221#584 carve+search, Niko1221#742, Niko1221#500, Niko1221#743, Niko1221#727/Niko1221#734, Niko1221#567+537 union)
…pt-in, Pascal through fp32 (Niko1221#655 Niko1221#540 Niko1221#395) PR Niko1221#655's FP16 tensor-core path for Gemm::bf16, adapted so the ready-made engine stays byte-identical: - default on for compute capability 7.0-7.4 only (Volta; the experimental STRATA_EXPERIMENTAL_SM60 build), off for 7.5 (RTX 20): STRATA_BF16_TC=1 opts in, =0 turns it off on any 7.x card - from Niko1221#540: finite values beyond FP16's range clamp to +-65504; a beta = 1 product (the BF16X2 remainder, in FP16's subnormal band) keeps cuBLAS; the compute capability is cached per device (a layer split can mix cards) - from Niko1221#395: Pascal (6.x; cuBLAS has no BF16 GEMM there) widens both operands to fp32 by an exact shift and runs cublasSgemm with the same fp32 accumulator, tiled over T in the instance's own buffers sm_80 and newer, and HIP, run the same cuBLAS / hipBLAS call as before. Co-Authored-By: Károly Sárdi <skver@tuta.io> Co-Authored-By: Rafal R@V Prasal <rafal.prasal@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pt-in, Pascal through fp32 (#655 #540 #395) PR #655's FP16 tensor-core path for Gemm::bf16, adapted so the ready-made engine stays byte-identical: - default on for compute capability 7.0-7.4 only (Volta; the experimental STRATA_EXPERIMENTAL_SM60 build), off for 7.5 (RTX 20): STRATA_BF16_TC=1 opts in, =0 turns it off on any 7.x card - from #540: finite values beyond FP16's range clamp to +-65504; a beta = 1 product (the BF16X2 remainder, in FP16's subnormal band) keeps cuBLAS; the compute capability is cached per device (a layer split can mix cards) - from #395: Pascal (6.x; cuBLAS has no BF16 GEMM there) widens both operands to fp32 by an exact shift and runs cublasSgemm with the same fp32 accumulator, tiled over T in the instance's own buffers sm_80 and newer, and HIP, run the same cuBLAS / hipBLAS call as before. Co-Authored-By: Károly Sárdi <skver@tuta.io> Co-Authored-By: Rafal R@V Prasal <rafal.prasal@gmail.com>
…pt-in, Pascal through fp32 (Niko1221#655 Niko1221#540 Niko1221#395) PR Niko1221#655's FP16 tensor-core path for Gemm::bf16, adapted so the ready-made engine stays byte-identical: - default on for compute capability 7.0-7.4 only (Volta; the experimental STRATA_EXPERIMENTAL_SM60 build), off for 7.5 (RTX 20): STRATA_BF16_TC=1 opts in, =0 turns it off on any 7.x card - from Niko1221#540: finite values beyond FP16's range clamp to +-65504; a beta = 1 product (the BF16X2 remainder, in FP16's subnormal band) keeps cuBLAS; the compute capability is cached per device (a layer split can mix cards) - from Niko1221#395: Pascal (6.x; cuBLAS has no BF16 GEMM there) widens both operands to fp32 by an exact shift and runs cublasSgemm with the same fp32 accumulator, tiled over T in the instance's own buffers sm_80 and newer, and HIP, run the same cuBLAS / hipBLAS call as before. Co-Authored-By: Károly Sárdi <skver@tuta.io> Co-Authored-By: Rafal R@V Prasal <rafal.prasal@gmail.com>
…ine and how to opt in docs/OLDER_GPUS.md: Pascal / Volta through the CUDA 12 engine (Niko1221#395 Niko1221#600 Niko1221#540 Niko1221#655), RTX 20's STRATA_BF16_TC opt-in, AMD gfx1031 (Niko1221#524), gfx1012 (Niko1221#442) and gfx906 (Niko1221#638, Niko1221#677) with the reporters' numbers; README, AI_SETUP, AMD_HIP and NVIDIA_V100 point to it.
…pt-in, Pascal through fp32 (Niko1221#655 Niko1221#540 Niko1221#395) PR Niko1221#655's FP16 tensor-core path for Gemm::bf16, adapted so the ready-made engine stays byte-identical: - default on for compute capability 7.0-7.4 only (Volta; the experimental STRATA_EXPERIMENTAL_SM60 build), off for 7.5 (RTX 20): STRATA_BF16_TC=1 opts in, =0 turns it off on any 7.x card - from Niko1221#540: finite values beyond FP16's range clamp to +-65504; a beta = 1 product (the BF16X2 remainder, in FP16's subnormal band) keeps cuBLAS; the compute capability is cached per device (a layer split can mix cards) - from Niko1221#395: Pascal (6.x; cuBLAS has no BF16 GEMM there) widens both operands to fp32 by an exact shift and runs cublasSgemm with the same fp32 accumulator, tiled over T in the instance's own buffers sm_80 and newer, and HIP, run the same cuBLAS / hipBLAS call as before. Co-Authored-By: Károly Sárdi <skver@tuta.io> Co-Authored-By: Rafal R@V Prasal <rafal.prasal@gmail.com> Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ine and how to opt in docs/OLDER_GPUS.md: Pascal / Volta through the CUDA 12 engine (Niko1221#395 Niko1221#600 Niko1221#540 Niko1221#655), RTX 20's STRATA_BF16_TC opt-in, AMD gfx1031 (Niko1221#524), gfx1012 (Niko1221#442) and gfx906 (Niko1221#638, Niko1221#677) with the reporters' numbers; README, AI_SETUP, AMD_HIP and NVIDIA_V100 point to it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
What
Turing (sm_75) and Volta (sm_70) have FP16 tensor cores but no BF16 ones, so
cublasGemmExonCUDA_R_16BFinputs falls back to a SIMT fp32 kernel (magma_sgemmEx). On an RTX 2080 Ti that was the largest kernel of the prompt path, 20 % of a 4K prompt (nsys). It runs everyGemm::bf16product: the hyper-connection down / up projections, the router and indexer rows, and PLE key / value.On compute capability 7.x,
Gemm::bf16now:Gemm::f16product (fp32 accumulate) on the tensor cores.BF16 → FP16 is exact for every value inside FP16's range, because the 7-bit mantissa fits in 10 bits.
#if !defined(__HIPCC__)).STRATA_BF16_TC=0/1forces the path off or on.Gemminstance (there is one, inPrefill::Impl) and is freed with it. With Qwen3.8-Flash-Next it peaks at about 45 MiB: the 2560×2560 PLE value weight plus one activation slice.Accuracy
New test
gemm_bf16_parity. It comparesGemm::bf16with cuBLAS's own BF16 product on the same inputs. GPU, synthetic, no model;--benchadds timings. It covers:beta = 1accumulation (the bf16x2 low parts);On the 2080 Ti the worst relative difference is 1.7e-5. With
STRATA_BF16_TC=0it is 0, so the test also passes unchanged on other cards.Real products. I also checked every
beta = 0product of a real 32K prompt (400 products) against the cuBLAS BF16 result, with a temporary diagnostic that is not in this PR:Greedy output. It matches the unpatched engine for the first 52 (4K) and 105 (32K) tokens, then diverges the usual way for a change in summation order.
gr_paritystill passes.Numbers
Setup:
--resident-experts,--spec 4.STRATA_BF16_TC=0vs1.Decode does not use
Gemm::bf16and is unchanged within noise.Per product (
gemm_bf16_parity --bench):After the change, the conversion kernel is 3.8 % of a 32K prefill. Writing FP16 directly from the producers would take that back, but it touches more code, so I left it out of this PR.
Measured on sm_75 only (Volta was added in review, unmeasured). Other cards keep the default off, so their path is the same code as before.
🤖 Generated with Claude Code