Skip to content

Add Qwen3.6-35B-A3B engine: Vulkan MoE backend, resident expert pinning, serve GPU support + fixes - #602

Open
minne100 wants to merge 2 commits into
JustVugg:mainfrom
minne100:feat/qwen36-gpu-residency-serve
Open

Add Qwen3.6-35B-A3B engine: Vulkan MoE backend, resident expert pinning, serve GPU support + fixes#602
minne100 wants to merge 2 commits into
JustVugg:mainfrom
minne100:feat/qwen36-gpu-residency-serve

Conversation

@minne100

Copy link
Copy Markdown

Summary

This PR adds a complete, self-contained engine for Qwen3.6-35B-A3B (35B params / 3B active, Apache-2.0) to colibri, plus a set of fixes and a benchmark. The goal is to run this large MoE model on a 16 GB machine by streaming experts from disk on demand.

What's included

  • Phase-2 engine (c/qwen36.c) — Gated Attention (GQA + partial RoPE, rope_dim=64) blended with a pure-C Gated DeltaNet recurrent linear-attention path (per-layer DN_rec/DN_conv state), followed by a streaming MoE (shared expert + group-limited top-k). Hybrid layers use Gated Attention at i%4==3 (10/40 layers), the rest are DeltaNet.
  • Vulkan MoE backend (c/vulkan_gemv.c + headers) — int4 weights are unpacked inside the shader and multiplied as float GEMV. This is a deliberate workaround for the AMD 780M driver OpSDotKHR/int8 compiler segfault (0x800184): we only use OpCapability Shader and never touch int8 types. The backend auto-probes a Vulkan compute device and silently falls back to CPU when none is present (dynamic vulkan-1 load, zero-dependency binary).
  • Bug fix: GPU produced all-zero output on the real 35B int4 model. Two root causes, both in vulkan_gemv.c:
    1. vg_expert_ensure → vg_expert_loaded had its layer/expert-id arguments swapped (loaded(layer, eid, li) was called as (layer, li, eid)), so weights were uploaded to the wrong slot and the shader read zeros. The self-test missed this because eid==li by coincidence.
    2. load_expert_merged's int4 slice was gated behind vg_use_int4(), so the int4 container (g4/u4/d4) was never filled when an expert loaded before vg_init. Now the int4 container is always populated.
      Verified: GPU vs CPU MoE cosine = 1.00000 at layer 0 / token 0, and byte-identical generated text vs CPU-only.
  • Resident expert pinning (COLIBRI_RESIDENT=1|2) — pin the experts a prompt actually routes to into RAM (and, via the GPU g_w buffer, into VRAM) so they are not repeatedly evicted and re-streamed from disk. =1 pins once after prefill; =2 keeps collecting and pinning incrementally across the decode phase. Per-layer pin budget = cap to avoid LRU deadlock.
  • Serve mode (c/qwen36_serve.c) — integrates the Vulkan backend (enabled automatically per g_gpu_backend) and fixes two HTTP POST parsing bugs:
    1. Expect: 100-continue was never answered, so clients that wait for 100 never sent the body.
    2. handle_conn NUL-truncated the request buffer in place at the first space (*sp1=0), which destroyed the body and made strstr(req,"\r\n\r\n") fail → 400. Now method/path are copied into local arrays and the full buffer is searched.
      Verified end-to-end: /v1/chat/completions returns valid OpenAI-format JSON ("The capital of France is Paris.").
  • Conversion toolkit (c/tools/convert_qwen36.py default true-int4, make_qwen36_oracle.py attention-only, make_qwen36_tiny.py weight-free smoke) and design docs (docs/qwen36-*.md).
  • Benchmark (c/int4_vs_int8_cold.md): cold-start comparison on an integrated-GPU 16 GB laptop (AMD 780M, no dedicated VRAM), long prompt, N_NEW=256.

Benchmark (cold start, long prompt, default GPU unless noted)

Metric int4 +GPU int8 +GPU int8 CPU-only
First-token latency (TTFT) 50.18 s 28.68 s 13.83 s
Throughput 0.29 tok/s 0.48 tok/s 1.08 tok/s
Peak RSS 11.30 GB 11.27 GB 10.25 GB
256-token wall time 873 s 532 s 238 s

Takeaway: on this iGPU machine, int8 CPU-only is the fastest and smallest — the integrated GPU shares system memory and gains no bandwidth, while int4 pays a CPU-side unpack-to-int8 penalty before upload. int4's only hard advantage is half the on-disk size (19.7 GB vs 34.6 GB). A dedicated GPU with the experts resident in VRAM is expected to show the opposite result; that path is implemented (COLIBRI_RESIDENT) but not yet measured on such hardware.

Test plan

  • int4 self-test (convert_qwen36.py --selftest) passes
  • GPU vs CPU MoE cosine = 1.00000; byte-identical generation
  • COLIBRI_RESIDENT=2 prefill pin hit-rate ~39%, text correct, <16 GB
  • serve POST returns valid OpenAI JSON (curl round-trip)
  • measure on a dedicated-GPU machine (not available here)

Notes / limitations

  • NFC normalization is not implemented (the tokenizer assumes NFC input).
  • The AMD integrated-GPU path uses float GEMV (not int8 dot) by design.
  • Model weights (modles/) and the Vulkan SDK headers (c/vulkan/) are intentionally git-ignored; the generated *.spv.h headers are committed.

minne100 added 2 commits July 25, 2026 10:07
…perts, and serve GPU support

- New Phase-2 engine (c/qwen36.c): Gated Attention (GQA + partial RoPE)
  + Gated DeltaNet recurrent linear attention + streaming MoE (shared
  expert + group-limited top-k). Targets Qwen3.6-35B-A3B (35B params,
  3B active, Apache-2.0); runs on 16GB RAM via on-demand expert streaming.

- Vulkan MoE backend (c/vulkan_gemv.c + headers): int4 weights unpacked
  in-shader + float GEMV, working around the AMD OpSDotKHR/int8 compiler
  segfault on integrated GPUs. Auto-probes a Vulkan compute device and
  silently falls back to CPU.

- Fix GPU zero-output regression:
  1. vg_expert_loaded() was called with layer/expert-index arguments
     swapped, uploading weights into the wrong slot (shader read zeros).
  2. the int4 container (g4/u4/d4) was gated behind vg_use_int4(), so it
     was never filled when experts loaded before vg_init.
  Verified: GPU vs CPU MoE cosine = 1.00000 and byte-identical output.

- Resident expert pinning (COLIBRI_RESIDENT=1/2): pin the experts a prompt
  actually uses into RAM (and, via the GPU g_w buffer, into VRAM) to remove
  repeated disk IO. mode 2 accumulates across the decode phase.

- Serve mode (c/qwen36_serve.c): wire up the Vulkan backend, and fix two
  HTTP POST parsing bugs (Expect: 100-continue was not answered; the request
  buffer was NUL-truncated in place, breaking the body search). Now returns
  valid OpenAI-format responses.

- Conversion toolkit (c/tools/convert_qwen36.py, make_qwen36_oracle.py,
  make_qwen36_tiny.py) and design docs under docs/qwen36-*.md.

- Benchmark (c/int4_vs_int8_cold.md): on an integrated-GPU 16GB machine, a
  cold-start comparison (N_NEW=256, long prompt) shows int8 CPU-only is
  fastest (1.08 tok/s, 10.25GB peak) because the iGPU shares system memory
  and gains no bandwidth, while int4 pays an unpack penalty. int4 stays
  half the on-disk size.
@JustVugg

Copy link
Copy Markdown
Owner

Thanks for this — it's a substantial, well-built contribution, and we'd like to bring the Qwen3.6-35B engine in. Two things before it can land, both about shape, not quality:

  1. Retarget to dev, not main. All contributions merge into dev (main is release-only and protected). Please change the base branch to dev and rebase onto it.

  2. Split it — this is +15,515 lines in one PR. It bundles at least three separable things: (a) the Qwen3.6 engine (qwen36.c / qwen36_serve.c / converter / oracle tooling), (b) a Vulkan MoE backend, and (c) resident-expert pinning + serve GPU changes. We can't responsibly review or merge that as a single unit, and if any one piece needs iteration it blocks the whole thing. Please break it into an incremental series we can land one at a time — ideally starting with the self-contained engine (new qwen36.c that doesn't touch the shared GLM/Inkling path), which is the lowest-risk piece and can go in first. The Vulkan backend is its own big review and also overlaps Vulkan backend: expert tier + dense + MLA attention on any Vulkan 1.2 GPU (successor to #84) #418 / WIP: MiniMax-M3 support — GQA + MSA block-sparse attention, o200k tokenizer, converter (follow-up to #418) #601 — worth coordinating so we don't end up with two Vulkan backends.

Land the engine piece on dev first, and we'll take the GPU/Vulkan parts as follow-ups. Happy to review each increment quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model-support Supporto a nuovi modelli needs-rebase Confligge, serve rebase dell'autore vulkan Backend Vulkan/AMD

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants