Pull requests / #2178

#2178 sm_61 (Tesla P40): a wide dequant kernel, expert mode 8 and its matvec table by default (bitwise), the dense GEMMs as cublasSgemm opt-in (7K prompt 360-432 -> 464-474 tok/s)

open · @Techannel · 0 comentários · No GitHub

BenchmarksSetup & installAMD / HIPNVIDIA / CUDAModels & quantsDocumentationWindows

Descrição

## Title
Issue: none open for this. The P40 path is #395's; #1639's Pascal Q6_K kernel is the P100's decode GEMV, a different kernel from the prompt-path dequantization here.

## Summary
One Tesla P40 (sm_61) with IQ3_S on `a0116b2` (0.1.43.1). On this card the GPU is the bottleneck of the prompt and of the decode (the prompt's GPU timeline equals its wall time, a decode window is 49 ms of GPU stages with 1-3 ms of CPU experts), so I read the profile first and changed four things where it pointed. Each gives exactly the bits of what it replaces; three become sm_61's defaults, the fourth is opt-in.

Through the server, medians of 3 requests, two runs per build alternated, every run started with the card at 65 C:

| | `a0116b2` | this PR (defaults) | this PR + `STRATA_DENSE_F32=1` |
| --- | ---: | ---: | ---: |
| 565-token prompt, tok/s | 203 / 205 | 256 / 255 | 254 / 252 |
| 7,047-token prompt, tok/s | 360-365 or 430-432, by the token count | 369-374 or 437-443 | 464-474 at every count |
| decode after the short prompt, tok/s | 35.0 / 34.2 | 38.2 / 37.2 | 37.7 / 37.9 |

The decode gain is small and inside the spread of single requests; the GPU stage profile shows the same size (49.1 -> 45.6 ms per window). Everything, with every request, is in `bench/results/2026-10-12-p40-sm61-kernels/`.

## What changed
Four commits, one per change:

1. **`dequant_bf16.cu`: a thread per 8 values for Q4_K / Q5_K / Q6_K / IQ4_XS** (16-bit loads, one 16-byte store, the same expression per value). The per-group kernel runs at 10 GB/s for Q6_K on a P40 and costs every prompt chunk of the IQ3_S pack ~0.5 s, whatever its length. Q6_K 10240 x 2560: 7.25 -> 0.33 ms; Q4_K 5.2x, Q5_K 3.8x, IQ4_XS 3.6x. New `dequant_wide_parity`: 360 cases bitwise (FP16 / BF16 / FP32 out, a padded stride, row slices, sane and raw random blocks). Default on compute capability 6.1 only; `STRATA_DEQUANT_WIDE=0` / `=1` for any card.
2. **`gemm.cu`: opt-in `STRATA_DENSE_F32=1` on Pascal - the qkv / query / gate / output projections' FP16-operand GEMMs as `cublasSgemm` on FP32 copies.** cuBLAS 12.4 picks its slow FP16-operand kernel for most chunk sizes on this card: qkv runs at 7.6 TFLOPS for T 7,030-7,040 and at 4.3 from T 7,041 (49 -> 85 ms), so the same 7K prompt reads in 16.3 or 19.5 s depending on its token count. `cublasSgemm` runs at 8.4-9.8 TFLOPS from T 512. For the five shapes and the sizes the route takes the result is bitwise the FP16 call's: new `gemm_f32_parity`, 5,201 chunk sizes through `Gemm::f16`. **That equality is a property of the cuBLAS build (12.4.5.8 measured), not of the code, which is why it is opt-in** and why the tool is not registered as a ctest.
3. **`iq_kernels.cu`, `native_mmvq.cu`: expert mode 8 and `kIlRows_61` / `kIl1Rows_61` for sm_61**, as sm_60 has them (`STRATA_SM61_TABLE=0`). `native_expert_bench` on the pack's layers, bitwise: IQ3_S 390 -> 255 us for an 18-expert call at 2 tokens. The tables are from three runs of `mmvq_il_parity --bench --emit-table`, identical but for two Q3_K cells.
4. **docs + bench**: the sm_61 section of `docs/OLDER_GPUS.md`, the results folder, one line in `COMMUNITY_BENCHMARKS.md`.

What it leaves alone: every other card runs what it ran (the three defaults are picked only on compute capability 6.1, sm_60 keeps its own; the GEMM route needs the variable and a 6.x card). No change to the server, setup or any HIP / SYCL path.

## Extra Notes
- **How I checked the bits.** Kernel level: the parity tools above plus `mmvq_il_parity` (the table) and `native_expert_parity` on the model's layers. End to end: seven fixed greedy prompts, two runs of each build with `--prompt-cache 0 --adapt-swaps 0 --pcie-frac 0 STRATA_PREFILL_CPU_SHARE=0 STRATA_ROUTE_TAIL_SKIP=0 STRATA_IQ_MT_MIN=1`: byte-identical answers in 4 of 4 runs on 7 of 7 prompts. Without `STRATA_IQ_MT_MIN=1` the comparison cannot be made on this AVX2 CPU - the *same* build gave two answers to one prompt in two runs (`native_gu_mt_min`'s lone-token rounding and timing-dependent draft windows); the docs section says so.
- **One PR, three code changes.** AGENTS.md asks for one change per pull request. The three are independent (they share only `CMakeLists.txt` and the docs section) but come from one measurement session, so I kept them as separate commits here. If you would rather have three PRs, say so and I will split them.
- **Built:** the sm_61 CUDA 12.4 engine and tests (`-DSTRATA_EXPERIMENTAL_SM60=ON`), and `strata_kernels` / `strata_prefill` / the two new tools for sm_75 without that flag. **Not built:** the CUDA 13 engine, HIP, SYCL - I have no toolkit for them here.
- **Not tested:** any other sm_61 card (P4, GTX 10), Windows, cuBLAS other than 12.4.5.8, two cards or a layer split, `--batch`, prompts above 7K tokens, a quality suite.
- **Two things that distort a P40 measurement**, both in the README: the card's thermal slowdown at 89 C (clock 1,531 -> ~1,300 MHz within minutes; I cooled to 65 C before every run), and the token-count dependence above, which looks like 20% of run-to-run noise on the old build.
- Measured and not taken: `STRATA_GR_FAST=1` is slower on the P40 below 5 tokens (82 -> 88 us at one).
- The engine A/B was run on two builds of this code that differ from these commits only in the GEMM route's default; on the commits as pushed I ran the parity tools and one engine pass each with and without `STRATA_DENSE_F32=1` (default: 261 tok/s short, 439-446 long at 7,045-7,047 tokens; with the variable: 250 short, 465-473 long).

The server here runs with all four.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

No site

Links install, modelos, releases.