Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5d43386
feat: model GLM-5.2 (glm_moe_dsa) — MLA + DSA indexer + IndexShare
nicholaslawrence-hub Aug 27, 2026
937e5b4
wip: GLM-5.2 per-op precision, prefill phase, launch bound, MTP chain
nicholaslawrence-hub Sep 2, 2026
9c11927
GLM-5.2: FP8 catalogue entry, fp32 SKU peaks, launch-aware plan table…
nicholaslawrence-hub Sep 2, 2026
e81fe43
GLM-5.2: design note, prefill/MTP graph, per-op precision
nicholaslawrence-hub Sep 2, 2026
776663b
GLM-5.2: restore the indexer's 32 head scores; no draft head on prefill
nicholaslawrence-hub Sep 2, 2026
01a0857
GLM-5.2: lower a layer to its real kernel sequence, not just its GEMMs
nicholaslawrence-hub Sep 2, 2026
d39a66a
GLM-5.2: op names a capture can pair against; scan math dtype
nicholaslawrence-hub Sep 2, 2026
62a4758
GLM-5.2: fix a truncated schedule, dedupe the catalogue, prune prose
nicholaslawrence-hub Sep 2, 2026
6dcc7e8
GLM-5.2: restore the vendor launch args; correct two overstated claims
nicholaslawrence-hub Sep 2, 2026
0e3db39
GLM-5.2: prune the note and delete dead helpers
nicholaslawrence-hub Sep 2, 2026
99512c7
Fix lint, and the MTP break-even the review caught
nicholaslawrence-hub Sep 2, 2026
2a010fb
Warn in gitm plan that a speculative token rate is a ceiling
nicholaslawrence-hub Sep 2, 2026
7af8184
Condense the attn_kv_b needle comment
nicholaslawrence-hub Sep 2, 2026
4d4b394
Bound Q12, name the chunking assumption, mark the unusable break-even…
nicholaslawrence-hub Sep 2, 2026
e11ee7b
Price embed_tokens at its own declared precision
nicholaslawrence-hub Sep 3, 2026
e1ad9af
Stop counting an indexer the MTP block does not carry
nicholaslawrence-hub Sep 3, 2026
093621d
Make the two most-misread tables derive themselves
nicholaslawrence-hub Sep 3, 2026
c7130a7
Say that act_dtype is inherited, and scope the draft-gap row
nicholaslawrence-hub Sep 3, 2026
1de831c
Fix a fragment I left behind, and make the byte column add up
nicholaslawrence-hub Sep 3, 2026
88bd970
Print the KV formula so it reproduces its own number
nicholaslawrence-hub Sep 3, 2026
d9b2351
Show the per-stage node arithmetic and the baseline row
nicholaslawrence-hub Sep 3, 2026
db81150
Don't run the draft head when nothing is being drafted
nicholaslawrence-hub Sep 3, 2026
00d60a6
Prefill is comm-bound under EP8, not memory-bound
nicholaslawrence-hub Sep 3, 2026
860ef62
A skipped norm does not imply a skipped projection
nicholaslawrence-hub Sep 3, 2026
a669102
Count accepted tokens as a prefix chain, in BatchConfig
nicholaslawrence-hub Sep 3, 2026
2db5f21
Show where a draft stage's 0.297 ms goes
nicholaslawrence-hub Sep 3, 2026
1e73a95
Name the crossover rate, and say how the embedding shards
nicholaslawrence-hub Sep 3, 2026
3a1e191
Name the fp8 constant the KV formula borrows
nicholaslawrence-hub Sep 3, 2026
8db324e
CI: stop failing PRs on review-bot outages, and show the bot the code
nicholaslawrence-hub Sep 3, 2026
c263749
Merge remote-tracking branch 'origin/main' into feat/glm-5.2-design-note
nicholaslawrence-hub Sep 3, 2026
22364f8
Delete tests/fixtures/importers/mixed_dump/nsys_2024_min.sqlite
aditchawdhary Sep 4, 2026
4dce893
Delete tests/fixtures/importers/torch_trace_min.json.gz
aditchawdhary Sep 4, 2026
2cb6a48
Delete tests/fixtures/importers/nsys_2025_min.sqlite
aditchawdhary Sep 4, 2026
c38363c
Delete tests/fixtures/importers/mixed_dump/torch_trace_min.json.gz
aditchawdhary Sep 4, 2026
6757b9c
Delete tests/fixtures/importers/parity_nsys.sqlite
aditchawdhary Sep 4, 2026
a872584
Delete tests/fixtures/importers/nsys_2023_min.sqlite
aditchawdhary Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ jobs:
with:
fetch-depth: 0

# Code first, prose second. The diff is truncated below when it is large,
# and `git diff` orders by path — so a PR that touches `docs/` and `gitm/`
# used to spend its whole budget on documentation and never show the
# reviewer a line of Python. Every review of PR #104 opened by calling it
# "a documentation-only diff" while 2k lines of planner changes sat past
# the cut. Emitting code first means truncation drops prose instead.
- name: Get PR diff
run: |
git diff origin/${{ github.base_ref }}...HEAD \
-- '*.py' '*.sh' '*.md' '*.yaml' '*.yml' \
> pr_diff.txt
BASE="origin/${{ github.base_ref }}"
git diff "$BASE"...HEAD -- '*.py' '*.sh' '*.yaml' '*.yml' > pr_diff.txt
git diff "$BASE"...HEAD -- '*.md' >> pr_diff.txt
echo "Diff size: $(wc -c < pr_diff.txt) bytes"

- name: Run Claude Review
Expand All @@ -44,7 +50,7 @@ jobs:
fi

python3 << 'EOF'
import json, os, subprocess, sys, urllib.request
import json, os, subprocess, sys, time, urllib.error, urllib.request

with open("pr_diff.txt") as f:
diff = f.read()
Expand All @@ -64,6 +70,8 @@ jobs:
"**⚡ Performance** — unnecessary CPU↔GPU transfers, missed parallelism\n"
"**📊 Reproducibility** — seed handling, non-determinism risks\n"
"**💡 Suggestions** — missing error handling, untested edge cases\n\n"
"The diff may be truncated; review what is present and do not "
"infer anything from what is missing.\n\n"
f"```diff\n{diff}\n```"
)
}]
Expand All @@ -80,13 +88,34 @@ jobs:
}
)

try:
with urllib.request.urlopen(req) as resp:
data = json.load(resp)
comment = data["content"][0]["text"]
except urllib.error.HTTPError as e:
print(f"API error {e.code}: {e.read().decode()}", file=sys.stderr)
sys.exit(1)
# Retry the transient classes only. 429 and 5xx are load; 4xx otherwise
# (auth, billing, malformed) will fail identically on every attempt, so
# retrying them just burns runner minutes.
comment = None
reason = None
for attempt in range(3):
try:
with urllib.request.urlopen(req, timeout=180) as resp:
comment = json.load(resp)["content"][0]["text"]
break
except urllib.error.HTTPError as e:
reason = f"HTTP {e.code}: {e.read().decode()[:300]}"
if e.code not in (429, 500, 502, 503, 504) or attempt == 2:
break
time.sleep(5 * (attempt + 1))
except (urllib.error.URLError, TimeoutError, KeyError, IndexError) as e:
reason = f"{type(e).__name__}: {e}"
if attempt == 2:
break
time.sleep(5 * (attempt + 1))

# An advisory reviewer that cannot reach its API has found nothing; it
# has not found a problem. Failing the check here blocks a PR on someone
# else's outage or on an expired key, which is not a signal about the
# code. Say so in the log and pass.
if comment is None:
print(f"::warning::Claude review skipped — {reason}", file=sys.stderr)
sys.exit(0)

body = f"## 🤖 Claude Code Review\n\n{comment}"
subprocess.run([
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/gemini-pr-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ jobs:
with:
fetch-depth: 0 # Fetches full history so git diff can calculate cleanly

# Advisory, so an upstream outage must not block a PR. This action exits
# non-zero when the Gemini API answers 503 "experiencing high demand",
# which it did five times on PR #104 alone — a red check that says nothing
# about the branch. The review still posts when the API answers.
- name: Run Gemini Code Review
continue-on-error: true
uses: sshnaidm/gemini-code-review-action@v2
with:
gemini-key: ${{ secrets.GEMINI_API_KEY }}
Expand Down
1,141 changes: 1,141 additions & 0 deletions docs/glm-5.2/DESIGN-NOTE.md

Large diffs are not rendered by default.

48 changes: 45 additions & 3 deletions gitm/optimizer/deviation.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@
# completely different cost curve; and the indexer must never fall through to
# a bare "index" rule, which is how it gets misfiled as elementwise in the
# coarse taxonomy.
# The indexer's own projections, before the score entry below — whose
# "indexer" needle would otherwise claim ``indexers_proj`` and file a GEMM as
# a scan. They are separate kernels with opposite bounds: on GLM-5.2 the
# projection is bf16 and flat in context, the scan grows with it.
"attn_index_proj": ("indexer_proj", "indexers_proj", "index_proj", "wq_b",
"weights_proj"),
"attn_index_score": ("indexer", "lightning_index", "index_topk", "topk_indices"),
"moe_shared": ("shared_expert", "moe_shared"),
# `topkGating` is vLLM's fused routing kernel. Without it the generic "moe"
Expand All @@ -96,6 +102,17 @@
# traffic dominates the step.
"moe_router": ("moe_align", "topk_softmax", "topkgating", "gating", "router",
"routing", "sinkhorn", "expert_bias"),
# Dispatch/gather into expert-major order and the weighted scatter back.
# Before the generic "moe" needle below, which would claim both as expert
# GEMMs — they move real bytes and do no arithmetic, so folding them into the
# dominant weight-traffic row hides a term that chunk size and expert
# imbalance both move.
# Combine first: "unpermute" contains "permute", so the reverse direction has
# to be tested before the forward one or every combine kernel files as a
# dispatch.
"moe_combine": ("moe_sum", "finalize_moe", "unpermute", "scatter_add",
"index_add", "moe_combine"),
"moe_permute": ("permute", "expert_sort", "shuffle_rows", "gather_rows"),
"moe_routed": ("moe", "expert", "grouped_gemm", "group_gemm", "groupedgemm"),
"dspark": ("dspark",),

Expand All @@ -120,17 +137,42 @@
# checkpoints use (`_triton_mrope_forward`).
"attn_qnorm_rope_insert": ("qnorm", "q_norm", "qk_norm", "mrope", "rope", "rotary"),

# ── pointwise work that is its own kernel ────────────────────────────────
# These used to classify as ``None`` — "a norm/activation/copy" — and land as
# unmodeled. That was right while no graph emitted them. The GLM-5.2 graph
# does, because on a sparse model at low batch the pointwise kernels are the
# majority of the launches and a step bounded by its launches cannot be
# explained by a graph that only has GEMMs in it.
#
# ``rms_norm`` covers every norm site in a block. They are one kernel name in
# the trace, so they are one op here; which site a given launch belongs to is
# recoverable only from an NVTX range (``docs/kernel_identity.md``), never
# from the name.
"rms_norm": ("rms_norm", "rmsnorm", "layernorm", "layer_norm", "fused_add_rms"),
# Dynamic FP8 activation scaling ahead of a quantised GEMM. "dequant" is
# excluded on purpose: it is the epilogue of the GEMM, not this kernel.
"act_quant": ("scaled_fp8_quant", "per_token_quant", "act_quant",
"quant_fp8", "dynamic_scaled"),
# Before ``lm_head``, whose "embed" needle would otherwise claim the input
# gather and attribute it to the vocabulary projection.
"embed_tokens": ("embedding", "embed_tokens", "index_select"),

# ── projections ──────────────────────────────────────────────────────────
"attn_q_a": ("q_a_proj", "q_lora", "q_down"),
"attn_q_b": ("q_b_proj", "q_up"),
# `kv_b_proj` is absent on purpose: in the absorbed decode form it is folded
# into the query and output projections, so there is no node to map it to and
# a guess would attribute real work to the wrong op.
"attn_kv_a": ("kv_a_proj", "kv_lora", "kv_down", "compress_kv"),
# Unabsorbed MLA only: the absorbed decode form folds W^UK into the query and
# W^UV into the output and launches no such kernel, so these needles match
# nothing there and the op stays absent rather than mis-attributing.
"attn_kv_b": ("kv_b_proj", "kv_up", "w_uk", "w_uv"),
"qkv_proj": ("qkv",),
"attn_out_proj": ("o_proj", "out_proj", "attn_out"),
"mlp_gate_up": ("gate_up", "gate_proj", "up_proj", "swiglu", "silu_and_mul"),
"mlp_down": ("down_proj", "mlp_down"),
# The MTP block's [2h, h] fusion of the carried hidden state with the
# embedding of the token just drafted. Before ``lm_head``, which is the other
# bf16 GEMM in a draft stage.
"mtp_eh_proj": ("eh_proj", "mtp_proj"),
"lm_head": ("lm_head", "logits", "vocab_proj", "embed"),
}

Expand Down
Loading
Loading