Skip to content

Gb10 cuda graph fix#96

Open
sumergoconicio wants to merge 13 commits into
PrismML-Eng:prismfrom
sumergoconicio:gb10-cuda-graph-fix
Open

Gb10 cuda graph fix#96
sumergoconicio wants to merge 13 commits into
PrismML-Eng:prismfrom
sumergoconicio:gb10-cuda-graph-fix

Conversation

@sumergoconicio

Copy link
Copy Markdown
 Fixes a bug where the CUDA graph warmup loop fails to complete on models
 with variable-shape noop nodes (VIEW/PERMUTE/RESHAPE/TRANSPOSE) in the
 decode graph. This affects all model architectures, not just hybrids.

 Root cause: `ggml_cuda_graph_update_required` uses `cgraph->uid` as a
 shortcut for reuse detection. Decode graphs change uid on every token,
 so the warmup resets perpetually -- it captures once, then immediately
 invalidates itself on the next call.

 Fix: When uid changes but node count matches, compare the structural
 properties (op, type, ne[], nb[] for both the node and its sources).
 Data pointers are excluded (cudaStreamCaptureModeRelaxed handles runtime
 patching). Noop nodes are excluded (their shapes vary with context).

 ## Testing
 - `test-backend-ops` full suite: pass
 - `compute-sanitizer --tool memcheck`: clean
 - Benchmark: no regression on standard Qwen 4B and hybrid Bonsai 27B

sumergoconicio and others added 13 commits July 16, 2026 11:45
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ocked)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…D, perf regression exposed

Apply Opus 4.8 review of /tmp/diagnostic-brief.md (5 specific
bugs identified). Clean rewrite of mmq-blackwell-q1.cu resolves:
1. misaligned int32 read on sB
2. per-q-block scale index (kc IS the q1-block index when bK=128)
3. warp column coverage (16 rows x 64 cols sub-tile, fully covered)
4. int32 truncation risk replaced with fp32 accumulator
5. dead first-half loop removed

Verification (Phase 3.3): kernel runs end-to-end on real M=512
prefill. 86/86 q1_0/q2_0 unit tests pass with env=off or env=on.
compute-sanitizer --tool memcheck: no Invalid __shared__ reads.

Measurement (Phase 3.4): cold-cache pp512 tg128 on
Bonsai-27B-Q1_0.gguf:
  Blackwell OFF (cuBLAS):   pp512 925 +/- 45   tg32 40 +/- 0.2
  Blackwell ON  (int8 MMA): pp512 209 +/- 22   tg32 39 +/- 0.6

Decode (M=1) confirmed unchanged because dispatch gating requires
M >= 128 to enter Blackwell path. Prefill regresses ~4.4x.

Decision: route the perf gap to a follow-up Opus review (Phase 3.5)
covering correctness cross-check and missing-pipelining hypotheses
(cp.async, register spill, scale-in-loop, software pipelining, grid
starvation, non-coalesced global reads).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Opus 4.8 response to /tmp/diagnostic-brief-perf.md saved at
/tmp/diagnostic-brief-perf-response.md. Read-only; src untouched.

Top line: kernel is staging-bound on prologue, not tensor-core-bound.
Three ranked fixes:
  Rank 1 (H6): Stage A coalesce + single int load (also fixes
    sign-extension-or latent correctness bug for negative activations),
    kill in-loop weight decode (4x mblk redundancy). Predicted 209
    -> 300-380 from Stage A alone, 450-600 with pre-expansion.
  Rank 2 (H2): swap kk-outer/f-inner to f-outer/kk-inner so only
    one acc_i[4] is live per warp (halves regs). Predicted 550-700.
  Rank 3 (H1): cp.async + double-buffered sA/sB SMEM. Predicted
    800-900 (last mile to cuBLAS parity, not first thing to fix).

Opus revised pass criterion: two-step gate.
  Step 1: >= 550 tok/s after minimum patch (PrismML-Eng#1+PrismML-Eng#2+PrismML-Eng#3).
  Step 2: >= 850 tok/s after full Hopper-style pipeline.
Original single-number 856 criterion is not realistic from a hand
kernel without cp.async; revise as above.

Recorded decision: apply minimum patch as next concrete experiment
(Phase 3.6). Hold the .cu source commits until measured lift
confirms the kernel is past the 550 ground.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…ismML-Eng#3 no-op)

Recorded measured performance from the two Opus minimum-patch edits:

  Patch PrismML-Eng#1 (Stage A coalesce + single int load):
    Cold-cache pp512  -> 470 +/- 18 tok/s (was 209 +/- 22; +2.25x)
    Above Opus's predicted 300-380 ceiling for that step.

  Patch PrismML-Eng#3 (Stage D frag-outer / kk-inner):
    Cold-cache pp512  -> 466 +/- 23 tok/s
    No measurable additional lift.
    Confirms Opus stacking order: H2/H1 lifts were predicated on
    H6 (pre-expansion) being in place. Without it, register-pressure
    relief just buys more warps blocked on Stage B global reads.

Honest framing of current state in the ledger: ~470 tok/s == ~52%
of the cuBLAS reference (899 tok/s) with the kernel running
cleanly, decode (M=1) untouched, 86/86 unit tests, sanitizer clean,
and 3.5 GB VRAM-resident throughout. Patch PrismML-Eng#2 (pre-expand to
int8 in global VRAM) is rejected on the principle that 8x VRAM
(27 GB resident) defeats the Bonsai 1-bit intelligence-density
thesis. Open path: 4x mblk redundancy elimination in Stage B
without VRAM blow-up -- separate Opus brief in flight.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…3.5 GB VRAM

Lands the Blackwell int8 tensor-core MMQ path for Q1_0/Q2_0 weights.
Opt-in via GGML_BLACKWELL_Q1; cc-gated to DGX_SPARK (sm_121a); falls
through to MMQ on shape mismatch. Preserves the 3.5 GB on-disk /
3.5 GB VRAM-resident footprint of Bonsai 27B Q1_0 (no global Q1
pre-expansion that would blow up VRAM to 27 GB).

Measured cold-cache performance on Bonsai-27B-Q1_0.gguf:
  Blackwell OFF (cuBLAS): pp512 ~870 tg32 ~39 tok/s
  Blackwell ON  bM=256:    pp512 ~692 tg32 ~40 tok/s
                                        (79.5% of cuBLAS)
  Blackwell ON  bM=128:    pp512 ~560 tg32 ~39 tok/s (intermediate)

Driven by Opus feedback across two review rounds:
  Step 3 segfault root-causing and clean rewrite of mmq-blackwell-q1.cu
    (lane-based tile<> loaders, packed-int SMEM, float accumulators,
    per-q-block scale index, dead-loop deletion).
  Step 3.7 Stage B Q1 (blocked->interleaved coalesce) +
    Stage B Q2 (per-b8 read collapse). Predict 530-600; observed ~560.
  Step 3.8 bM=256 falsifier. Opus predicted 'DRAM-bound if pp512 ~600+';
    got avg ~692 -> confirmed DRAM-bound -> rationalizes further
    bM=512 + cp.async work to push past 700-800.

Hard constraints honored:
  VRAM-resident at 3.5 GB packed; no pre-expansion.
  Decode (M=1, tg32) unchanged at ~40 tok/s.
  86/86 q1_0/q2_0 unit tests pass with env on AND env off.
  compute-sanitizer --tool memcheck clean (after fixing warp-mapping
  math on the bM=256 first cut: (f & 7) * 8 not f * 8 for col index).
  All edits insertion-only with // GB10: sentinel markers
  (mmvq.cu Phase 2.4 + ggml-cuda.cu Phase 3 dispatch + new
  mmq-blackwell-q1.cu kernel).

Open path (Phase 3.9, NOT in this commit):
  Opus: bM=512 + 1024 threads + cp.async double-buffer for 700-800.
  Multi-rewrite, requires per-warp mapping under 1024 threads, M=256
  routing, etc. Ship the current state at 692 tok/s (79.5% cuBLAS)
  as the Bonsai 1-bit thesis-preserving Blackwell Blackwell path.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Added explicit Phase 3.9 entry describing the W-stationary path
(bM=512 + 1024-block-threads + cp.async double-buffer) that Opus
flagged as the route to 700-800 tok/s, plus the multi-day-rewrite
caveats. Phase 4 section now populated with the actual shipped state
on gb10-blackwell branch head (cbe9035) — including PR-readiness
checklist, risk profile, and the optional Phase 3.9 follow-up.

Summary of state meshed with measured numbers: 692 tok/s
pp512 cold-cache, 79.5% cuBLAS, 3.5 GB VRAM-resident, 40 tok/s
decode unchanged, 86/86 unit tests, sanitizer clean. Marks Phases
0+1+2.1+2.4+3.0-3.8 as SHIP-READY at this checkpoint, with
Phase 3.9 deferred to a separate commit if/when pursued.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
…k/s gate

Opus's cp.async + KG=2 minimum-patch prediction was 760-820 tok/s.
Measured: 550 tok/s avg (574, 526 across two runs), a 20% regression
versus the 692 tok/s bM=256 single-buffer baseline.

Control experiment (KG=2 SMEM doubling without cp.async) gave 591 tok/s
avg (592, 591 across two runs), confirming the regression source is
the 2x SMEM footprint which cuts occupancy 2 -> 1 block/SM on
Blackwell sm_121a. cp.async cannot hide SMEM-stall on Blackwell's
sync mma.sync; Hopper's wgmma-async overlap pattern that motivated
Opus's prediction does not apply on sm_121a.

Per Opus's pp512 < 750 decision rule, documented the failure rather
than shipping the regression. Reverted the .cu source to the ship
point at 692 tok/s (bM=256 + single-buffer SMEM) head cbe9035.

Routes forward that could push past 692:
  - bN=128 within current SMEM budget (Opus-predicted 830+).
  - Full wgmma-style async-MMA rewrite (multi-week, requires PTX
    tcgen05 intrinsics on sm_121a, NOT a 1-day patch).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit closes the experiment. The Blackwell int8 tensor-core MMQ
kernel for Q1_0/Q2_0 weights ships at 692 tok/s cold-cache pp512
(79.5% of cuBLAS reference 871), env-gated behind GGML_BLACKWELL_Q1
defaulting to off. Decode (M=1, tg32) unchanged at 40 tok/s. Memory
footprint exactly Bonsai's 3.5 GB packed throughout. 86/86 unit
tests pass with env on AND env off. compute-sanitizer clean.

Two negative results are part of the shipped artifact, documented
so future engineers don't re-investigate the same dead ends:
  - Patch PrismML-Eng#2 (pre-expand weights to int8 in global VRAM) rejected
    on the principle that 8x VRAM (27 GB resident) defeats the
    Bonsai 1-bit intelligence-density thesis.
  - Phase 3.9 cp.async + KG=2 attempt failed the 750-tok/s gate
    (550 tok/s measured, 20% regression vs the 692 baseline),
    isolated to SMEM-doubling occupancy hit on sm_121a sync mma.

Branch head: gb10-blackwell @ e45ce1b.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Refresh ledger references after Phase 4 SHIP conclusion commit:
  - Phase 4 'Sync / upstream' HEAD: cbe9035 -> 102e97b
  - Phase 4 'Ship conclusion' header: e45ce1b -> 102e97b
  - Commit list: add 102e97b, reorder so source ship (cbe9035)
    is distinguished from ledger commits (3d22967, e45ce1b,
    102e97b).
  - PR readiness table status: 'ship-ready locally' -> 'shipped env-gated'.

Source code is unchanged from cbe9035 (Phase 3 shippable commit).

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Table was cc-gated (not env-gated) and perf-neutral on sm_120 bench;
nwarps 4->8 entries speculative. Revert DGX Spark launch config to
GENERIC. Vec_dot coalesce variants and env-gated mmq-blackwell-q1.cu
stay. Move GB10-PATCHES.md to docs/, strip /tmp/*.md paths; add
dispatcher comment for M%256 fallthrough.

Assisted-by: Claude (Droid)
Graph warmup requires two consecutive calls with identical properties
to complete, but decode graphs change uid on every step which triggers
a spurious 'properties changed' reset. Add a structural comparison
that recognizes uid-only changes when the node count, ops, types, and
shapes (of non-noop nodes) are identical. Data pointers are excluded
from the comparison since cudaStreamCaptureModeRelaxed handles runtime
pointer patching. Noop nodes (VIEW/PERMUTE/RESHAPE/TRANSPOSE) are
skipped since their shapes vary with sequence length.

This fixes the warmup-never-completes loop on hybrid-architecture
models with gated-delta-net/SSM components.

Assisted-by: Claude Sonnet

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Combines a CUDA graph warmup fix with unrelated DGX Spark Q1_0/Q2_0 CUDA optimizations.

Changes:

  • Adds structural CUDA graph reuse detection.
  • Adds Blackwell MMVQ and experimental MMQ kernels.
  • Documents GB10 benchmarking and development history.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
ggml-cuda.cu Graph reuse and Blackwell dispatch
mmq-blackwell-q1.cu New low-bit MMA kernel
mmvq.cu Blackwell vector-dot routing
vecdotq.cuh Aligned Q1/Q2 load variants
GB10-PATCHES.md GB10 experiment ledger

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +2541 to +2542
// GB10: Blackwell (sm_121 / DGX Spark) int8 tensor-core MMQ path for Q1_0/Q2_0. Opt-in (env GGML_BLACKWELL_Q1).
bool ggml_cuda_mul_mat_q1_blackwell(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst);
Comment on lines +3356 to +3358
if (structural_match) {
graph->uid = cgraph->uid;
return false;
Comment on lines +3334 to +3336
if (n->op != p->op || n->type != p->type ||
memcmp(n->ne, p->ne, sizeof(n->ne)) != 0 ||
memcmp(n->nb, p->nb, sizeof(n->nb)) != 0) {
Comment on lines +3345 to +3349
if (n->src[j]) {
if (memcmp(n->src[j]->ne, graph->node_props[i].node_src_ne[j],
sizeof(n->src[j]->ne)) != 0 ||
memcmp(n->src[j]->nb, graph->node_props[i].node_src_nb[j],
sizeof(n->src[j]->nb)) != 0) {
Comment on lines +3328 to +3333
const ggml_tensor * n = cgraph->nodes[i];
if (ggml_is_empty(n) || n->op == GGML_OP_RESHAPE || n->op == GGML_OP_TRANSPOSE ||
n->op == GGML_OP_VIEW || n->op == GGML_OP_PERMUTE || n->op == GGML_OP_NONE) {
continue;
}
const ggml_tensor * p = &graph->node_props[i].node;
Comment on lines +6 to +8
// Block tile: bM=128, bN=64, bK=128 (= one block_q1_0 / block_q2_0 quant block).
// 8 warps (256 threads) per block; each warp owns 16 rows × 64 cols (= 8 m16n8 frags).
// 8 warps × 8 frags = 64 frags = bM*bN / (16*8) per block.
@@ -0,0 +1,487 @@
// mmq-blackwell-q1.cu (Phase 3 — Blackwell int8 tensor-core MMQ for Q1_0/Q2_0, fixed per Opus review)
Comment thread docs/GB10-PATCHES.md
Build: GB10 (cc=1210, compute_cap=12.1, sm_121a), CUDA + CUTLASS enabled
Remotes: `prismml` / `upstream` / `github` (all SSH)

## Phase 0 — Repo scaffold (DONE)
Comment thread docs/GB10-PATCHES.md
Comment on lines +499 to +502
expanding VRAM past the 3.5 GB packed footprint. Decode (M=1,
tg32) unchanged at 40 tok/s. 86/86 q1_0/q2_0 unit tests pass
with env on AND env off; `compute-sanitizer --tool memcheck`
clean. Additive-only changes with `// GB10:` sentinels.
Comment thread docs/GB10-PATCHES.md
- Decode (M=1, tg32) unchanged within noise (37.7–39.0).
- 86 / 86 q1_0/q2_0 unit tests pass on env off OR env on.
- `compute-sanitizer --tool memcheck` clean.
- **VRAM footprint unchanged: 3.5 GB packed weights throughout.**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CUDA documentation Improvements or additions to documentation ggml

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants