Repository navigation
Qwen3-Coder-30B-A3B (qwen3_moe) on the qwen36 engine - #1828
Merged
Merged
Conversation
added 4 commits
October 2, 2026 01:59
Qwen3-Coder-30B-A3B is Qwen3MoeForCausalLM: every layer attention, no shared expert and no attention output gate. The config check now accepts shared_inter 0 for a routed model and the loader and both shared-expert paths skip it. Attention multiplied a missing gate as sigmoid(0) = 0.5 on every layer; without a gate the output now passes as it is (Qwen3.5/3.6/3.8 always carry one, so their output is unchanged). The gate scratch is not allocated when there is none.
For model_type qwen3_moe the converter writes what the checkpoint is: all layers full attention, shared_inter 0, partial_rotary_factor 1.0, plain RMSNorm weights (zero_centered_norms false) and M-RoPE sections that cover the whole rotary half, which for one position per token is ordinary RoPE. It also reads num_local_experts, the name transformers 5 writes. make_qwen36_tiny.py gains the qwen3-coder-30b geometry, built with transformers' Qwen3MoeForCausalLM and RMSNorm weights drawn around 1 so a zero-centered reading cannot pass. CI converts it and holds the engine to transformers token for token at caches of 1 and 8.
The qwen36 family takes model_type qwen3_moe, names the 30B and the REAP 25B by geometry (qwen3-coder-30b-a3b-colibri), and plans it without the DeltaNet keys it does not have. The gateway recognises Qwen3-Coder's template and renders it byte for byte (its XML tool block, no thinking; tested against the release's chat_template.jinja), parses its calls with the Qwen parser, and keeps enable_thinking off so a request asking for it does not file the answer as reasoning. A declared boolean written as True/False, the way the template prints past values, is read as a boolean, as Qwen's own parser does. The tests that used qwen3_moe as an unknown model_type now use mixtral.
This was referenced Oct 2, 2026
JustVugg
added a commit
that referenced
this pull request
Oct 2, 2026
- qwen36-metal-test links qwen36.c, which now calls the Vulkan backend, so under VK=1 it needs $(VK_OBJ). The binary gets a rule named after it so the object can be its prerequisite, as test_makefile_vk_obj requires. - test_registry_engine_agreement no longer counts VK_OBJ as an accelerator: in olmoe, deepseek_v41, mimo and qwenimage, Vulkan only places resident matrices, opt-in, with no VRAM tier for the planner to size, which is what supports_accelerator promises. They stay CPU-only to the planner. - convert_fp8_to_int4's family guard gains qwen3_moe (#1828 added it to the registry only). The test is skipped in CI, so only a local run showed it.
pavolbauer
pushed a commit
to pavolbauer/colibri
that referenced
this pull request
Oct 2, 2026
The README the converter writes and its closing line said Qwen3.6-35B-A3B and Gated DeltaNet for every container, so a Qwen3-Coder container (JustVugg#1828) shipped a README about a different model. A qwen3_moe container now gets its own README (attention layers, no DeltaNet, no shared expert, the expert format), and the closing line counts the DeltaNet layers instead of naming them.
ggeorgovassilis
pushed a commit
to ggeorgovassilis/colibri
that referenced
this pull request
Oct 18, 2026
Qwen3-30B-A3B and Qwen3-Coder as llama.cpp writes them: every layer attention, no DeltaNet, no shared expert, no output gate on q, full rotary, the norm after attention named ffn_norm. The engine already runs the architecture from safetensors (JustVugg#1828); the GGUF config now takes its numbers, and the importer writes a Qwen3MoeForCausalLM config. Checked: the tiny Qwen3-Coder fixture (make_qwen36_tiny.py --geometry qwen3-coder-30b) converted to an F32 GGUF by llama.cpp's own converter gives the transformers reference's 16 of 16 greedy tokens, on the exact and on the integer path; unsloth's Qwen3-Coder-30B-A3B-Instruct Q4_K_M passes GGUF_CHECK=1 from its header (579 tensors, 18.55 GB).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Qwen/Qwen3-Coder-30B-A3B-Instruct (Apache-2.0,
qwen3_moe) runs on the qwen36 engine: text, tool calls and streaming, throughcoli chat,coli serveandcoli web. Its REAP prunes (same architecture, 103 experts) resolve to the same family under their own name.What it took
Qwen3 MoE differs from the Qwen3.5/3.6 layers this engine was written for in four places: no DeltaNet (every layer attention), no attention output gate, no shared expert, and plain RMSNorm weights with rotary over the whole head.
qwen36.c):shared_inter 0is accepted for a routed model, and the shared expert is skipped at load and in both compute paths. Attention multiplied a missing gate assigmoid(0) = 0.5on every layer; without a gate the output now passes through. Qwen3.5/3.6/3.8 always carry the gate, so their numerics are unchanged: the 35B, 2.4T and 27B-dense tiny oracles are 16/16 before and after.model_type: qwen3_moeit writes all-attentionlayer_types,shared_inter: 0,partial_rotary_factor: 1.0,zero_centered_norms: false, and M-RoPE sections that cover the rotary half (one position per token, so ordinary RoPE). It also readsnum_local_experts, the name transformers 5 writes.qwen3_moemaps to the qwen36 family, the variants are named by geometry (Qwen3-Coder-30B-A3B,qwen3-coder-30b-a3b-colibri;Qwen3-Coder-REAP-25B-A3B), and the planner no longer requires DeltaNet keys when every layer is attention.detect_chat_flavor) and rendered byrender_chat_qwen3_coder: its XML tool block, its default system line, tool responses sharing one user turn, no thinking.enable_thinkingis forced off for it (otherwise a request asking for it would file the whole answer as reasoning, as [Bug]: OLMoE streaming responses drop generated content #984 did on OLMoE). The Qwen parser reads its calls, and now reads a declared boolean written asTrue/Falseas a boolean, which is how the model writes it (seen on the real model; Qwen's own parser does the same).Correctness
make_qwen36_tiny.py --geometry qwen3-coder-30bbuilds the model with transformers'Qwen3MoeForCausalLM(RMSNorm weights drawn around 1, so a zero-centered reading cannot pass); converted, the engine matches it 16/16 at caches of 1 and 8. Before the gate fix it matched 1/16, with each layer's attention output exactly half of transformers'.tests/test_qwen3_coder_chat_template.pyrenders the release'schat_template.jinja(fixture, sha256 pinned) with jinja2 the way transformers does and compares byte for byte: single turns, system, history, tools with extra schema keys, an agent loop with parallel calls and tool results, a tool result last, and the open turn.Measured
Ryzen 7 PRO 8700GE, 64 GB, NVMe RAID, nothing else running; CLI from a cold cache, 128 tokens, dense trunk int8:
coli chatwith--cap 128and a warm cache streams a 577-token answer at 13 tok/s. Conversion takes under a minute per container.On the real model, every front end
enable_thinking: trueanswered incontent(not reasoning), a tool callrun_tests({"path": "/srv/app", "verbose": true, "workers": 4})with both types restored, and streaming.coli chat(attached) andcoli web:One thing to know, not changed here: like the rest of this engine,
coli serve/coli chatsize the expert cache from--cap, not from--ram(docs/qwen36.md); without--capthe engine starts at the routing floor (8 experts per layer) and runs at about 2.7 tok/s.coli planalready computes the right cap (128 here); passing it through for this family is the launcher change discussed in #1823.docs/qwen36.mddocuments the checkpoint, the commands, the quality and the speed tables.