Skip to content

feat(v4/cuda): the CUDA tier wired into the DeepSeek V4 engine — generic kernels for any sm_80+, Windows runtime DLL loader, Linux CUDA=1 - #1055

Merged
JustVugg merged 7 commits into
JustVugg:devfrom
dcutugno:pr/v4-cuda-tier
Aug 16, 2026
Merged

Conversation

@dcutugno

Copy link
Copy Markdown
Contributor

Stacked on #772 (@ZacharyZcR's kernels-as-a-tier, rescoped head), on #988 and on #1053 — until those merge, the diff against dev includes them. The three commits that are this PR: dc54ce3 (kernels/loader), c259de0 (engine wiring), cee7149 (docs).

This is the engine side #772 deliberately left out, plus the kernels the wiring needed. It follows the direction set in #772's review: our own kernels, small consumer cards first, every stage CPU-canonical with a per-stage CPU fallback. The vLLM/FlashInfer files are gone (the same removal as #772's rescope), DeepGEMM stays an opt-in build (DEEPGEMM_HOME; a small follow-up PR offers to vendor the sm120 headers, separable).

What it is

  • Engine tier (COLI_V4_UNIT_GPU, #ifdef COLI_V4_GPU_TIER): all state stays in host memory; the tier accelerates stage by stage and falls back to the CPU reference for anything it declines. Dense/attention mirrors in VRAM (6.3 GB) with GPU dense matvecs in decode; a VRAM-sized fp4 expert mirror cache for decode (DSV4_CUDA_EXPERT_MIRRORS is an upper bound, growth stops when DSV4_CUDA_VRAM_RESERVE_MB is left; in-place refill); fused routed+shared MoE, sparse attention on the device KV, GPU indexer.
  • Prefill (COLI_CUDA_ATTN_BATCH=1): the whole-chunk attention block on the GPU — compressor/indexer projections, sparse attention on a persistent device KV ring (indexed kernel past index_topk), wo, mHC — plus batched indexer scoring (bitwise the CPU loop: __fadd_rn/__fmul_rn, MinGW GCC does not contract FMA) with a bitwise fp8 reference-matmul replica for the query projection.
  • Prefill MoE (COLI_CUDA_MOE_BATCH=1): route on device, refill a transient per-layer VRAM expert bank route-aware in pin-slot-sized groups (V4_MOE_REFILL_GROUP), lookups of group g+1 overlapped with the uploads of group g, page-locked host slabs; expert-grouped GEMM rows; a lookup that loses the pin-slot race is retried sequentially instead of failing the layer to the CPU union.
  • Windows: MinGW engine + MSVC/nvcc DLLs, two flavours picked at start-up by dsv4_cuda_backend_arch_ok: coli_cuda_dsv4.dll (generic kernels, CUDA_ARCH=portable = sm_80/86/89/90/120 + PTX, any RTX 30/40/50, A/H) and coli_cuda_dsv4_dg.dll (DeepGEMM sm_120a, opt-in). backend_loader_dsv4.c resolves every mandatory symbol up front; COLI_DSV4_DLL forces one; banner [DSV4 CUDA] backend=... (generic|deepgemm-sm120).
  • Linux: make -f Makefile.deepseek-v4 deepseek-v4 CUDA=1 [CUDA_ARCH=native|portable|sm_XX] [DEEPGEMM=1 DEEPGEMM_HOME=...] [NO_TC=1] links the tier directly (libcuda from the toolkit stubs + rpath); NO_TC=1 compiles out the opt-in cuBLASLt MXFP8 path for toolkits older than 12.8 (Jetson). Verified on Ubuntu 22.04/WSL2, RTX 5080, CUDA 13.3.1 + 12.6.85 — CPU-only, generic, DeepGEMM builds; the 12.6 compile fails on exactly the four cuBLASLt 12.8 symbols without the gate.
  • CLI: --gpu/--vram for the V4 engine, coli doctor sees the DLL, V4_MTP_GPU opt-in; kernel tests c/tests/test_dsv4_*_cuda.c (batched sparse attention: max diff 1e-3 = one bf16 ulp vs the CPU reference; decode kernels through the loader ABI).

Numbers (RTX 5080 16 GB, 2× NVMe mirror, 32 GB RAM)

before after
3324-token prefill 271 s 90 s (generic kernels: 225 s, text identical)
8.3k-token opencode first turn ~10 min ~4 min once, then 6–9 s per session/turn (with #1053's checkpoints)
decode at 3k context 0.6 tok/s 1.6–1.76 tok/s

Per-card settings for 6/8/10–12/16/24/32 GB are in docs/deepseek-v4.md (VRAM budget: 6.3 GB dense mirrors, 2.2 GB prefill bank, ~8 MB per decode mirror; free VRAM sizes the cache at run time). Every GPU stage was accepted only when its greedy text matched the CPU reference on the same prompt; what "identical" can and cannot mean across kernels/cache states is written up in the docs' Validation section.

Overlap

#1031 (@rafpigna) touches the same area with a different design (single DLL, per-call safetensors reads for dense-on-GPU, .coli_kv single-session persistence). Happy to reconcile with whichever lands first; the numbers above are the comparison point.

🤖 Generated with Claude Code

@JustVugg

Copy link
Copy Markdown
Owner

Two of the things your stack was carrying have landed in dev:

So #1055, #1053 and #1056 no longer need to carry either of them. A rebase on current dev should collapse #1055 to roughly its own three commits (dc54ce3, c259de0, cee7149), which is the point at which it becomes reviewable as the engine wiring rather than as a 10k-line diff.

One thing worth flagging before you rebase: c/Makefile moved twice today. Beyond the NVCCFLAGS resolution above, #1037 landed the fp8 warp-per-row kernels. If your CUDA build touches the same block, that's where the conflict will be — everything else in the merges was additive.

I have deliberately not merged the smaller PRs that touch c/deepseek_v4.c or c/colibri.c yet (#1017, #1035, #1038, #827), precisely so your rebase lands against a stable base instead of chasing a moving one. Tell me when you've pushed and I'll sequence the rest around you.

On the overlap with #1031: your numbers (3324-token prefill 271 s → 90 s, decode at 3k context 0.6 → 1.6–1.76 tok/s) are a different order of magnitude from what that PR measures (+4–5% on a 10.7 GB card), and you offered to reconcile with whichever lands first. That's the right spirit and it's noted — the decision on which carries the tier is the maintainer's, and it'll be made on the rebased diff rather than the stacked one.

Also: #1056 vendors third-party headers and adds THIRD_PARTY_NOTICES.md, which is the correct way to do it — thank you for splitting it out as separable rather than folding it into the tier PR.

dcutugno and others added 4 commits August 16, 2026 18:06
…efill segments, cancellable prefill, batched indexer selection

- Prefix checkpoints (V4_PREFIX_CKPT, on): snapshot the attention
  transaction (window KV + compressed slots + compressor/indexer state) at a
  shared boundary and restore it for any later request whose prompt starts
  with the same bytes. Capture rules: the gateway's prefix hint (prefix_bytes
  in the session options), the longest common prefix of two successive fresh
  prompts, and a prompt-end capture after every prefill (agent clients
  re-render the assistant reply). 4 LRU slots (V4_PREFIX_CKPT_SLOTS),
  prompt-end evicted first.
- Disk persistence (V4_PREFIX_CKPT_DISK, on): <model>/.coli_ckpt/, config
  fingerprinted, temp+rename, lazily loaded after a restart. New
  coli_v4_{attention,compressor,indexer}_snapshot_write/read; restore grows
  the live buffers and prepares the per-layer window attention state.
- Segmented prefill: atomic 4096-token segments (V4_PREFILL_SEGMENT), a
  should_abort poll between them (ColiV4SessionAbortFn in the session
  options), completed segments recorded so an identical retry resumes;
  progress lines per segment.
- 128-token chunks (V4_PREFILL_CHUNK clamp 64 -> 128; sums[128] in the fp8
  batch reference).
- Batched indexer selection in prefill (V4_IDX_BATCH, on): advance per
  token, score/select once per chunk past index_topk, per-token numerics
  unchanged; V4_IDX_IDENTITY (off) documents the identity short-circuit.
- MTP: clamp the noise token to the vocabulary.

CPU parity vs dev: 826-token prompt, 48 greedy tokens, COLI_V4_ROWS16=0
COLI_V4_AUTOPIN=0 COLI_V4_SAVE_USAGE=0 V4_PREFIX_CKPT=0 -> byte-identical
text (md5 9cf553633292 on both).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e acks a CANCEL

- Emit the byte offset of the first user/assistant turn marker as an
  optional 8th SUBMIT header field (older engines ignore it) so the very
  first request seeds the shared-prefix checkpoint.
- Do not release the scheduler admission before the engine confirms a
  client cancel: releasing early let the next request SUBMIT into a pipe
  the busy engine was not reading (every later request hung). Wait for the
  ERROR CANCELLED / DONE frame; raise ClientCancelled on a DONE that arrives
  after a CANCEL. Wire client_disconnected as the abort poll.
- Exit cleanly on Ctrl-C.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…U-path output identity means

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts.M)

coli plan counted zero routed experts for V4 checkpoints because the
expert regex only knew model.layers.N.mlp.experts.M.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dcutugno

Copy link
Copy Markdown
Contributor Author

Rebased all three on current dev (03e8677, i.e. with #1054, #988, #1023, #1037, #827…) and force-pushed:

Re-verified after the rebase: #1053 CPU-only build; #1055 Windows engine + both DLLs via the make targets, 826-token prompt / 48 greedy tokens → same text as before the rebase (md5 d4e3f23d3d68), generic DLL forced also OK; #1056 make cuda-dsv4-dg-dll with the vendored default. Ready for the rest to be sequenced around it.

dcutugno and others added 3 commits August 16, 2026 18:38
…eneric sm_80+ + opt-in DeepGEMM, Windows DLL export/loader

On top of JustVugg#772's rescoped backend_cuda_dsv4.cu (vLLM/FlashInfer paths gone,
DeepGEMM an external opt-in): the kernels the engine wiring in the next
commit calls, all validated against the CPU reference on greedy text.

- Batched prefill attention on a persistent device KV ring + compressed
  buffer (dsv4_cuda_sparse_attn_batch, _cached, _cached_idx for selections
  past index_topk), compressor/indexer bf16 projections, whole-chunk mHC
  (pre-norm / post), grouped wo tail, batched dense fp8 GEMM.
- Route-aware transient expert bank: dsv4_cuda_route_top6_batch on device,
  dsv4_cuda_expert_bank_upload (page-locked host slabs via
  cudaHostRegister/VirtualQuery on Windows, pageable elsewhere),
  dsv4_cuda_route_moe_ids_batch over host routes; expert-grouped GEMM rows.
- Decode: fused routed+shared MoE (dsv4_cuda_moe), fp4 expert mirrors with
  in-place refill (dsv4_cuda_tensor_refill_fp4), device-KV sparse attention,
  indexer scoring kernel (__fadd_rn/__fmul_rn: bitwise the CPU loop — MinGW
  GCC does not contract FMA), fp8 reference-matmul replica incl. the
  rows8-packed layout, dsv4_cuda_mem_free_mb.
- Backend identity: dsv4_cuda_backend_arch_ok / dsv4_cuda_backend_name so
  a host can pick a DLL flavour per device.
- Windows: dsv4.def exports and backend_loader_dsv4.c (MinGW engine,
  MSVC/nvcc DLL): tries coli_cuda_dsv4_dg.dll then coli_cuda_dsv4.dll,
  COLI_DSV4_DLL forces one, resolves every mandatory symbol up front.
- -DCOLI_DSV4_NO_TC compiles out the opt-in cuBLASLt MXFP8 path for
  toolkits older than 12.8 (verified: CUDA 12.6 fails on exactly the four
  block-scaling symbols without it).
- Makefile: cuda-dsv4-dll (generic; CUDA_ARCH=portable for a fat binary)
  and cuda-dsv4-dg-dll (DEEPGEMM_HOME external, sm_120a); DeepGEMM
  needs the community sm120 port + patches-deepgemm-sm120-msvc.patch (MSVC
  ignores alignas(64) on by-value TMA descriptors; the patch pads them).
- Kernel tests: tests/test_dsv4_sparse_attn_batch_cuda.c (max diff 1e-3 =
  one bf16 ulp vs the CPU reference), test_dsv4_decode_cuda.c through the
  loader ABI, test_dsv4_gpu_grouped.c.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ical with per-stage fallback

COLI_V4_UNIT_GPU + the #ifdef COLI_V4_GPU_TIER hooks: the engine keeps all
state in host memory and lets the tier accelerate stage by stage, falling
back to the CPU reference for anything it declines. Every stage was accepted
only when greedy text matched the CPU reference.

- Dense/attention mirrors in VRAM (6.3 GB) uploaded per resident layer;
  GPU dense matvecs (fp8, grouped wo) in decode; ColiTensorView.gpu /
  ColiExpertView .gpu handles (tensor.h, expert_store.h).
- Decode: fp4 expert mirror cache (VRAM-sized: DSV4_CUDA_EXPERT_MIRRORS is an
  upper bound, growth stops when DSV4_CUDA_VRAM_RESERVE_MB is left; probe
  throttled), in-place refill, fused routed+shared MoE, sparse attention on
  the device KV, indexer via advance + select_batch(1); DSV4_DECODE_PROF.
- Prefill (COLI_CUDA_ATTN_BATCH=1): whole-chunk attention block on the GPU
  — compressor/indexer projections, sparse attention on the persistent
  device KV ring (indexed kernel past index_topk), wo, mHC — with the ring
  appended after attention (chunk rows passed separately) and shared
  coli_v4_gpu_kv_cache_{sync,advance,poison,invalidate_all}; batched
  indexer scoring on the GPU (bitwise the CPU loop) with the fp8 reference
  matmul replica for the query projection; cached scratch arena.
- Prefill MoE (COLI_CUDA_MOE_BATCH=1): route on device, refill the transient
  per-layer VRAM expert bank route-aware in pin-slot-sized groups
  (V4_MOE_REFILL_GROUP), lookups of group g+1 overlapped with the uploads of
  group g, page-locked slabs; expert-grouped GEMM rows; a lookup that loses
  the pin-slot race is retried sequentially instead of failing the layer to
  the CPU union; V4_MOE_BANK_FULL opt-in whole-layer prefetch (measured
  worse, documented).
- Linux: Makefile.deepseek-v4 CUDA=1 [CUDA_ARCH=native|portable|sm_XX]
  [DEEPGEMM=1 DEEPGEMM_HOME=...] [NO_TC=1] links the tier directly (libcuda
  from the toolkit stubs + rpath); coli_v4_gpu_engine_open consults
  dsv4_cuda_backend_arch_ok and stays CPU-only on a device the binary
  cannot run on. Verified on Ubuntu 22.04/WSL2, RTX 5080, CUDA 13.3 + 12.6.
- CLI: --gpu/--vram for the V4 engine (coli), V4_MTP_GPU opt-in, doctor
  detects the DLL next to the engine, tiny fixture test covers the GPU MTP
  draft.

Numbers (RTX 5080 16 GB, 2x NVMe mirror, 32 GB RAM): 3324-token prefill
271 s -> 90 s (generic kernels 225 s, text identical), 8.3k-token agent first
turn ~4 min once, later sessions/turns 6-9 s (with the prefix checkpoints of
the previous PR), decode 0.6 -> 1.6-1.76 tok/s at 3k context. 826-token
prompt, 48 greedy tokens: text identical to the previous engine build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… VRAM budget + per-card settings, performance, GPU coverage, environment reference, validation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JustVugg
JustVugg merged commit dd39c4e into JustVugg:dev Aug 16, 2026
19 checks passed
@JustVugg

Copy link
Copy Markdown
Owner

Merged into dev. DeepSeek V4 now has a CUDA tier in the tree — the kernels from #1054 finally have something calling them.

What decided it, since I did not read all 7,161 lines and won't pretend otherwise:

  • It is compile-gated. 46 references to COLI_V4_GPU_TIER; the default build is untouched, so nobody who doesn't ask for it can be broken by it.
  • The blast radius is confined to V4. No other engine's .c and none of the shared headers (st.h, json.h, quant.h, tok.h) are modified. expert_store.h, native_quant.h and tensor.h take small additive changes only.
  • You extended the gate rather than routing around ittest_deepseek_v4_tiny.py +66. A GPU tier that adds to the token-exactness check instead of exempting itself from it is the version of this PR I was willing to take.
  • 19/19 green, and it shrank from 10,677 to 7,161 lines once feat(v4): dual-SSD mirror (COLI_MODEL_MIRROR) for the DeepSeek V4 expert store #988 and DeepSeek V4: the CUDA kernels, as a tier rather than an engine (rebase of #772) #1054 landed, which is exactly why the stack was worth unwinding in order.

Also worth recording: your design decision to keep every stage CPU-canonical with a per-stage fallback, and to accept a GPU stage only when its greedy text matched the CPU reference on the same prompt, is the reason this could be merged on a compile flag rather than after a month of review. The Validation section in the docs — being explicit about what "identical" can and cannot mean across kernels and cache states — is better than most projects manage.

Three follow-ups, none blocking:

  1. feat(v4): prefix checkpoints (memory + disk), resumable 4096-token prefill segments, cancellable prefill, gateway prefix hint #1053 still needs your call on V4_PREFIX_CKPT_DISK defaulting on. Everything else there is verified and ready — I checked the V4_PREFILL_CHUNK 64→128 raise myself, including that __m256 sums[] grew with it, and it's correct.
  2. build(v4/cuda): vendor the DeepGEMM sm120 headers — cuda-dsv4-dg-dll / DEEPGEMM=1 need no external checkout #1056 (vendoring CUTLASS + DeepGEMM) is a scope decision for @JustVugg rather than a technical one: ~35k lines of third-party headers in-tree, against not needing an external checkout for DEEPGEMM=1. For what it's worth you did the licensing correctly — cutlass/LICENSE.txt, deep_gemm/LICENSE and THIRD_PARTY_NOTICES.md are all present, which is the part people usually get wrong.
  3. feat(dsv4): DeepSeek V4 Flash support — CUDA/VRAM expert tiering, DSML tool use, cross-session KV persistence #1031 overlaps this area with a different design; it now needs reconciling against what just landed, and you'd already offered.

Thanks for splitting this the way you did. Landing the kernels with their tests first, then the wiring, made both reviewable — that sequencing was your suggestion and it was the right one.

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.

2 participants