Skip to content

feat(qwen36): name a checkpoint by its geometry, and a converter that refuses what it cannot place - #1326

Open
kreuzzelg wants to merge 1 commit into
JustVugg:devfrom
kreuzzelg:qwen36-size-aware-descriptor
Open

kreuzzelg wants to merge 1 commit into
JustVugg:devfrom
kreuzzelg:qwen36-size-aware-descriptor

Conversation

@kreuzzelg

@kreuzzelg kreuzzelg commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes the banner defect confirmed in #1045 (comment 5458211750), and lands the pieces that make the Qwen3.8-2.4T-A95B shape a verified one rather than a guessed one — without a single weight of it.

The defect

Qwen/Qwen3.8-2.4T-A95B declares the same model_type as Qwen3.6-35B-A3B (qwen3_5_moe_text) and resolves to the qwen36 engine today, where the banner announces it as "Qwen3.6-35B-A3B · 35B". A loader that misreports what it loaded costs someone a day.

Registry: display by geometry

A model_type resolves to exactly one family (_BY_TYPE), so a second descriptor is not available. Instead the descriptor carries display_variants: a DisplayVariant pairs a geometry (config keys that must match exactly) with a name, and display_for(resolved) picks the first match.

geometry banner
40 layers / 256 experts / hidden 2048 Qwen3.6-35B-A3B · 35B MoE · 21 GB on disk
92 / 512 / 8192 Qwen3.8-2.4T-A95B · 2.4T MoE
anything else (a tiny fixture, a third sibling) qwen3_5_moe_text · 8L x 8E MoE — its own model_type and measured numbers, never a sibling's parameter count

The registry refuses a variant that matches everything, and a family whose static display_name is not among its variants (the READMEs are held to that name by the existing contract test). coli's banner reads the family config, so the VL-wrapped 35B repo (text_config) and the flat 2.4T config both display right. public_metadata carries the variants.

Converter: every tensor name is placed, or it stops

While checking the 2.4T index I found the class of defect you described for GLM-5.3, live in this converter. It selected <prefix>layers.<i>. plus four globals and let the rest fall through unmentioned. That skipped the mtp.* head and the visual.* tower by accident, and would have copied an unfamiliar tensor inside a layer as f16 without a word.

tools/qwen36_tensor_kinds.py now classifies every name before the first shard is read: layer tensors by an exact suffix list, mtp.* / visual.* skipped on purpose with a count and a reason (skipping 19 mtp.* tensor(s): multi-token-prediction head …, config mtp_num_hidden_layers=1), anything else refused with the names. Torch-free, so tests/test_qwen36_tensor_kinds.py runs wherever the registry tests run; it pins the contract to the safetensors indexes of both real checkpoints (1609 and 1045 tensors) and to the per-expert layout transformers saves.

One correction on the way: a comment claimed the real 35B ships per-expert tensors. Both real checkpoints ship the fused layout (experts.gate_up_proj / down_proj, one tensor per layer) and both carry the mtp head; only the tiny fixture is per-expert. So the "five-minute index check" I proposed in #1045 was aimed at the wrong regex — expert_inventory describes the container, not the checkpoint — and the real thing to verify was the fused split and the deliberate skip. Both are now under test.

Fixture: the 2.4T shape at toy widths, in the real layout

make_qwen36_tiny.py --geometry qwen38-2p4t: 92 layers / interval 4 / 512 experts top-10 / 16:1 attention heads / 8:1 DeltaNet heads, and the saved shard rewritten into what the checkpoints ship — fused experts plus a one-layer mtp.* head with mtp_num_hidden_layers: 1. The converter has to split the one and skip the other; the engine has to match the transformers reference token for token. The default preset reproduces the shipped fixture's reference exactly, so the existing gate is unchanged.

CI (qwen36-tiny-check) gains a step: build the 2.4T fixture, assert the converter said it skipped 19 mtp tensors and placed 1587 in 92 layers, assert no mtp.* survives in the container, token-exact at cap 1 / 2 / 512; the sanitizer step now runs both fixtures.

Verified

  • old fixture: reference identical, converted shard bit-identical (cmp), 16/16 at cap 1/2/8
  • 2.4T geometry: 19 mtp tensors skipped and said so, 1587 placed in 92 layers, 16/16 at cap 1/2/512, 0.35 s per run; generator 17 s, converter 9 s
  • ASan/UBSan (nm confirms 31 __asan symbols — the lesson of fix(pilot): the prefetch worker outlives the Model it dereferences #1277): no diagnostic on either fixture under PILOT=1 WIDE=2
  • registry planner on the real 2.4T config: KV 1.44 GiB @ 8k, 46.0 GiB @ 256k, DeltaNet state 0.55 GiB context-free — the numbers quoted in [Feature]: Can you please consider adding qwen 3.8 #1045. The engine's KV cache is F32 (ensure_kvfalloc), so they stand; the F16 caveat I raised there is closed
  • python suite: 743 tests, OK (skipped=35); the new CI step run locally word for word
  • --seed 3 for the 2.4T rung: the default seed collapses this geometry's reference to one repeated token, which a shape error could still reproduce; seed 3 gives twelve distinct tokens over sixteen

Not in this PR, and said in docs/qwen36.md

Running the 2.4T. The warmstart keeps every expert in RAM by design, ~1.4 TB of int4 for this checkpoint; that needs the disk-streaming design, not this one. What this PR guarantees is that the shape is right before anyone spends the bandwidth.

Thanks to claude code!

JustVugg added a commit to donovan-yohan/colibri that referenced this pull request Sep 6, 2026
A GLM-5.3 container was announced as "GLM-5.2 744B". The engine loaded
the right weights and ran them correctly; the banner named a different
model.

This is not fixable by detection, and that is the point of the change
rather than an excuse for it. Z.ai state on GLM-5.3's own card that it
"uses the same base model as GLM-5.2 -- every gain comes from
post-training", and the checkpoints agree: diffing the two real
config.json leaves one extra key (moe_router_dtype) and the
transformers_version that wrote the file. Same 78 layers, 256 experts,
hidden 6144, moe_intermediate 2048. JustVugg#1326's rule for Qwen -- name a
checkpoint by its geometry -- cannot apply, because the geometry is
identical. No rule over the configuration can name one and not the
other, now or later.

So the family says what it actually loads: GLM-5.2/5.3, at the 744B
scale both share. Verified by resolving both real configs through the
registry.

The docs move with it wherever the family is ENUMERATED: the "families
run today" line in all four READMEs, the model table and the RAM table
in README.md, the model card and the nav entry on the site. Mentions of
GLM-5.2 as a fact -- DSA came from its lightning indexer, its native MTP
head, benchmarks measured on it, the pre-converted 5.2 container on
Hugging Face -- are still true and are left alone.

Three tests pin the reason, because the obvious later "improvement" is
to add detection: both configs resolve to one family, neither differing
key may be read by the registry, and the name must state both models.
Reverting the name fails the third and nothing else.

751 Python tests green.
@kreuzzelg
kreuzzelg force-pushed the qwen36-size-aware-descriptor branch from af89d53 to b10cf6b Compare September 10, 2026 19:01
… refuses what it cannot place

Qwen/Qwen3.8-2.4T-A95B declares the same model_type as Qwen3.6-35B-A3B
(qwen3_5_moe_text) and resolves to this engine today, where the banner
announces it as "Qwen3.6-35B-A3B · 35B" (JustVugg#1045). Fixed on its own merits,
with the pieces that make the 2.4T shape a verified one rather than a
guessed one -- without a single weight of it.

A model_type resolves to exactly one family, so a second descriptor is not
an option. Instead `DisplayVariant` pairs a geometry (config keys that must
match exactly) with a name, and `display_for(resolved)` picks the first
match: 40/256/2048 -> "Qwen3.6-35B-A3B · 35B", 92/512/8192 ->
"Qwen3.8-2.4T-A95B · 2.4T". A config matching neither -- a tiny fixture, a
third sibling -- is named by its own model_type and measured geometry, never
by a sibling's parameter count. The registry refuses a variant that matches
everything, and a family whose static display_name is not among its
variants (the READMEs are held to that name). `coli`'s banner reads the
family config (text_config-aware), so the VL-wrapped 35B repo and the flat
2.4T config both display right.

The converter selected `<prefix>layers.<i>.` and four globals and let the
rest fall through unmentioned. That skipped the `mtp.*` head and the
`visual.*` tower by accident, and would have copied an unfamiliar tensor
inside a layer as f16 without a word. `tools/qwen36_tensor_kinds.py` now
classifies every name before the first shard is read: layer tensors by an
exact suffix list, `mtp.*` / `visual.*` skipped ON PURPOSE with a count and
a reason, anything else refused with the names. `test_qwen36_tensor_kinds`
pins the contract to the safetensors indexes of both real checkpoints
(1609 and 1045 tensors) and to the per-expert layout transformers saves.

One correction found on the way: the comment claimed the real 35B ships
per-expert tensors. Both real checkpoints ship the FUSED layout
(`experts.gate_up_proj` / `down_proj`); only the tiny fixture is per-expert.

`make_qwen36_tiny.py --geometry qwen38-2p4t`: 92 layers / interval 4 / 512
experts top-10 / 16:1 attention heads / 8:1 DeltaNet heads, and the saved
shard rewritten into what the checkpoints ship -- fused experts plus a
one-layer `mtp.*` head with `mtp_num_hidden_layers: 1`. The converter has to
split the one and skip the other; the engine has to match the transformers
reference. The default preset reproduces the shipped fixture's reference
exactly.

- old fixture: reference identical, converted shard bit-identical (cmp),
  16/16 at cap 1/2/8
- 2.4T geometry: 19 mtp tensors skipped and said so, 1587 placed in 92
  layers, 16/16 at cap 1/2/512, 0.35 s per run
- ASan/UBSan (nm confirms 31 __asan symbols): no diagnostic on either
  fixture under PILOT=1 WIDE=2
- registry planner on the real 2.4T config: KV 1.44 GiB @8k, 46.0 GiB
  @256k, DeltaNet state 0.55 GiB context-free -- the numbers quoted in JustVugg#1045;
  the engine's KV cache is F32 (ensure_kv: falloc), so they stand
- python suite: 743 tests, OK (skipped=35)
- the new CI step run locally word for word

`--seed 3` for the 2.4T rung: the default seed collapses this geometry's
reference to one repeated token, which a shape error could still reproduce;
seed 3 gives twelve distinct tokens over sixteen.

Not done here, and said in docs/qwen36.md: running the 2.4T. The warmstart
keeps every expert in RAM by design, ~1.4 TB of int4 for this checkpoint;
that needs the disk-streaming design, not this one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DLj9ctDNPGTsYxgBuDmy5a
@kreuzzelg
kreuzzelg force-pushed the qwen36-size-aware-descriptor branch from b10cf6b to 6c6354e Compare September 13, 2026 21:32
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.

1 participant