Skip to content

glm: GLM-5.3-Flash on CUDA, prompts 267.6 → 447.9 tok/s and decode 19.9 → 25.1 tok/s, every reply unchanged (#593) - #839

Closed
justincasey wants to merge 22 commits into
ashhart:mainfrom
justincasey:up/glm-cuda-perf
Closed

justincasey wants to merge 22 commits into
ashhart:mainfrom
justincasey:up/glm-cuda-perf

Conversation

@justincasey

Copy link
Copy Markdown

What this changes

Depends on #838: its two commits come first in this diff, and this PR's own are the last 20.

Speed work on PR 2's GLM-5.3-Flash layers, one technique a commit, with every reply unchanged.

Two GB10s, TP=2, eight prompts (24,099 tokens), same session #838 This PR
Prompts (tok/s) 267.6 447.9
Serial greedy decode (tok/s) 19.87 25.10
Replies equal to the Python engine's "draft": false 8/8 8/8

Decode:

  • CUDA graphs: one per KDA parity in the dense window, one per pool bucket past it.
  • Weights in mma fragment order: bf16 matmul, router and DSA gate.
  • One-row projections written in place.
  • Latent attention:
    • stops at the last visible tile;
    • stages its query heads once;
    • reads fp8 keys 16 bytes at a time.
  • hc_partial a block per four mixing dots.
  • KDA's f_a, g_a and b as one matmul.
  • Pool scores one 64-pool block a program.
  • EXL3:
    • split-K counters zeroed once;
    • staged 16-byte trellis loads for even widths (the linear; the experts in decode windows).

Prompts:

  • Strided row copies as one cuMemcpy2DAsync, through a shared DeviceBuffer.copyRows.
  • Grouped experts that walk only their members' tiles.
  • Expert grouping as a stable counting sort.
  • The fp32 combine with 16-byte loads.
  • An EXL3 prompt linear that decodes each weight tile once per 64 rows, keeping the decode kernel's sums in its order.

Every commit builds and passes test-cuda-host on its own.

Receipt

  • Environment: this head on c126fe16; two DGX Sparks (GB10, sm_121), CUDA 13.0, RoCE; checkpoint vcruz305/RED-SNOW-5.3-FLASH-EXL3-SAGE-4.91bpw.
  • Exactness: glm-model 12/12; all eight replies equal the Python engine's greedy "draft": false ids.
  • Decode and prompt speed: the table above, measured by tf-cuda-test glm-model on this head and on glm: GLM-5.3-Flash's layers on CUDA at TP=2 from EXL3, checked end to end by tf-cuda-test glm-model (#593) #838's head in one session.
  • Tests run:
    • exl3: 28/28.
    • glm: 25/25.
    • glm-model: 12/12.
    • test-cuda-host: pass at every commit.
    • zig fmt: clean.
    • Lean: 538, the same as c126fe16.
  • Not run: bench_openai.py, prefill_cold.py and vLLM (not served); Metal (no change).

Checklist

  • Every token still goes through the lane rounds. (No serving path is added.)
  • Drafted output equals "draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (The runner is serial and greedy.)
  • No precision traded for speed. Replies and the float64 and Python-engine byte checks are unchanged.
  • Prompt processing is no slower than the last release. (267.6 → 447.9 tok/s on the runner; no existing path changes.)
  • The description says which platforms I ran, Metal M1 to M5 and CUDA, and which I could not.
  • New tests fail before the change, pass after it, and skip cleanly without their dependency. (Each change keeps the existing checks green; no new tests.)
  • Comments and docstrings are one line. No measurements or history in the source.
  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.

…h, a width per expert

Hand-written ports of the Python engine's EXL3 kernels:
- the trellis decode into mma.m16n8k16 B fragments;
- a row-invariant linear for 1 to 128 rows (mul1 half steps included), with prompts in 128-row blocks and qlinear's .exl3 arm;
- grouped routed experts with each expert's own width;
- the checkpoint's trellis groups from its safetensors headers, and their upload.
tf-cuda-test exl3 checks them against float64 references, row and chunk invariance, and, with fixtures, the Python engine's bytes.
tf-cuda-test exl3-tp2 checks the GLM split rules on two ranks over NCCL.
…run by tf-cuda-test glm-model

Hand-written kernels ported from the Python engine's Triton and CUDA in their order of operations:
- the bf16 matmul and glue;
- KDA's decode chain and chunked prompts;
- the DSA indexer and top-pool selection;
- the latent cache, absorb/expand and latent attention;
- hyper-connection mixing, the router and EXL3 experts, the dense MLP of layers 0-2, and the head's greedy pick across ranks.
cuda_load reads each rank's shard; cuda_model runs prompt chunks of 2,048 rows and serial greedy steps.
tf-cuda-test glm checks the kernels and whole layers against float64 and, with fixtures, the Python engine's bytes.
tf-cuda-test glm-model checks greedy replies against recorded ones.
…ible key (that tile and later ones change nothing)
…ead trellis words as staged 16-byte no-allocate loads, two steps in flight
… in order to the first empty one, not a block a tile
@ashhart

ashhart commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Thank you, @justincasey. Prompts from 267.6 to 447.9 tok/s and decode from 19.87 to 25.10 tok/s on two GB10s, with all eight replies unchanged, one technique a commit and every commit building and passing on its own: this is how speed work should arrive.

Those wins should land wherever GLM-5.3-Flash on CUDA ends up. As the reply on #838 says, we would like them in #804's family and the shared EXL3 module, in particular the decode graphs, the weights in fragment order, the latent attention changes and the 64-row EXL3 prompt linear. Each carries your name.

Thank you for the care in the receipts too: both heads in one session, token ids against the Python engine.

@ashhart

ashhart commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Thank you, @justincasey, for this speed work: prompts from 267.6 to 447.9 tok/s and decode from 19.9 to 25.1 tok/s with every reply unchanged, in separately checked commits measured in the same session.

We are closing this branch because #804 is the one GLM CUDA family. The decode graphs, fragment order weights, latent attention changes and the 64 row EXL3 prompt linear are the pieces to bring into #804 and the shared module chosen in #832, under your name.

Please send them as focused picks on that base, with the same unchanged token comparisons.

Posted by Ash's AI Assistant Synthena

@ashhart ashhart closed this Oct 15, 2026
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