Qwen3.6-40B-Deckard — AWQ (GEMM) 4-bit for vLLM, with a working MTP head
⚡ Built for 2× RTX 2080 Ti (Turing / SM75) + NVLink
A 40B reasoning model at ~42–44 tok/s with working MTP speculative decoding on a pair of 2019 gaming cards — faster than the same model's MTP GGUF in llama.cpp (~35 tok/s) on the same hardware. If you have old Turing cards gathering dust, this is for you.
The first vLLM-servable AWQ quant of DavidAU's Qwen3.6-40B Opus-Deckard that includes a working Multi-Token Prediction (MTP) speculative-decoding head. Complements PiehSoft's GGUF release (llama.cpp-only) by bringing MTP to the vLLM / AWQ path.
Reference hardware (what all numbers below were measured on)
| Component | Detail |
|---|---|
| GPUs | 2× NVIDIA RTX 2080 Ti, 22 GB VRAM-modded each (44 GB total), TU102, compute capability 7.5 |
| Interconnect | NVLink bridge (NV2) — used by NCCL for the tensor-parallel all-reduce |
| PCIe | Gen3, x8/x8 (bifurcated riser setup) — NVLink carries the TP traffic, so PCIe lanes are not the bottleneck |
| Host | Proxmox LXC container with GPU passthrough, 97 GB RAM |
| Engine | SM75 community build of vLLM (weicj/vLLM-2080ti-definitive, v0.1.dev1) |
| Runtime | CUDA 12.8 · PyTorch 2.11.0+cu128 · Python 3.11 |
| Parallelism | --tensor-parallel-size 2, single-stream benchmarks (--max-num-seqs 1) |
Stock 11 GB 2080 Tis will not fit this model in TP2 (~17.6 GiB weights per GPU). 2× 22 GB modded cards, or any pair with ≥20 GB per card and SM ≥ 7.5, is the realistic floor.
What this is
- Base: DavidAU/Qwen3.6-40B-Claude-4.6-Opus-Deckard-Heretic-Uncensored-Thinking — a 27B→40B width-preserving layer expansion (hidden 5120, 96 decoder blocks: 72 GatedDeltaNet linear-attention + 24 full-attention, interval 4). Reasoning ("Thinking") model; uncensored ("Heretic") finetune — use responsibly.
- Quantization: 4-bit AWQ,
version: gemm(AutoAWQ export layout), served with vLLM'sawq_marlinkernel. Produced with Intel AutoRound 0.14.2 in RTN mode (iters=0, no gradient rounding optimization — see Honesty notes).bits=4, group_size=128, sym=False(asymmetric, runtime zero-points). - MTP head: transplanted from a Qwen3.6-27B donor (QuantTrio/Qwen3.6-27B-AWQ-6Bit), kept in BF16 (15 tensors, 0.85 GB,
model-mtphead.safetensors). The 40B expansion preserved the 27B's width — hidden 5120, intermediate 17408, 24 attention heads / 4 KV heads, head_dim 256, vocab 248320 are identical — so the donor head drops in unchanged. Same transplant idea as PiehSoft'sinject_mtp_40b.py, done in the safetensors/HF path. The head was not fine-tuned on the 40B.
Precision layout (what is 4-bit vs what stays BF16)
| Group | Precision | Size (disk) |
|---|---|---|
MLP gate/up/down (96 blocks), attn o_proj, GDN out_proj — 380 Linear layers |
INT4 AWQ g128 | 14.8 GB |
GDN linear_attn.in_proj_{qkv,z,a,b} |
BF16 | 12.2 GB |
self_attn.{q,k,v}_proj (24 attention blocks) |
BF16 | 3.5 GB |
lm_head / embed_tokens |
BF16 | 2.5 / 2.5 GB |
| MTP head (transplanted) | BF16 | 0.85 GB |
| Layer 0 (entire block) + norms | BF16 | 0.6 GB |
GDN in-projections and attention q/k/v are deliberately kept in BF16: quality of the hybrid linear-attention state and draft acceptance of the MTP head both degrade when they are quantized. (Quantizing GDN in_proj also turns out to save disk only — vLLM's GatedDeltaNet kernel dequantizes them back to fp16 in VRAM, so there is no context-length benefit. Measured, not guessed.)
Measured performance (single stream, greedy unless noted)
| Mode | Context | Decode | Notes |
|---|---|---|---|
| MTP, safe sync (recommended) | 12k | ~42–44 tok/s | lossless: greedy output is token-identical to no-MTP |
| No MTP | 24k | ~24 tok/s | more KV headroom |
| Long context (weights → RAM) | up to 131k | ~3.2 tok/s | --cpu-offload-gb 8; PCIe-bound |
- Methodology: 520-token generations with
ignore_eos, decode rate computed as a delta between a long and a short run (excludes prompt/TTFT). TTFT on short prompts ≈ 0.4 s. - MTP draft acceptance (transplanted, untuned head): ≈78% average on greedy fresh-context; per-position ≈ 0.88 / 0.60 / 0.42 at depth 3; mean acceptance length ≈ 2.7–2.9 tokens per step.
- Comparison point: the same model's MTP GGUF in llama.cpp (NCCL build,
-sm tensor, NVLink) does ~35 tok/s on the same cards. This vLLM path is ~20% faster single-stream, and unlike llama.cpp it batches concurrent requests. - Why contexts differ: weights take ~17.6 GiB/GPU, leaving ~1.9 GiB for KV at
gpu_memory_utilization 0.94→ ~27k max without MTP; the safe-sync MTP path reserves ~1 GiB more → ~12k. KV-cache fp8 does not engage on SM75; vLLM's--kv-offloading-sizeis a prefix-cache offload and does not extend a single request's context.
⚠️ Two SM75 pitfalls (read before serving)
- Never set
VLLM_ALLOW_MAMBA_SPEC_FULL_CUDAGRAPH=1with MTP on this model. Full-CUDA-graphing the GatedDeltaNet (linear-attention) state update during speculation replays stale recurrent state and silently corrupts generation — repetition loops, hallucinated prompts, empty outputs — while short trivial prompts can still look fine, which makes it easy to miss. Keep the default0, and useVLLM_SM75_SPEC_SYNC_MODE=safe(notnosync, which corrupts the same way). Symptom → cause was verified by A/B on identical prompts. --disable-custom-all-reduceis required on this 2× 2080 Ti setup: the custom all-reduce kernel fails with CUDA graphs (custom_all_reduce.cuh: invalid argument). NCCL over NVLink takes over the TP all-reduce; cost is ~2 tok/s.
Serving — recommended (MTP, ~42–44 tok/s, 12k ctx)
VLLM_SM75_SPEC_SYNC_MODE=safe VLLM_ALLOW_MAMBA_SPEC_FULL_CUDAGRAPH=0 \
python -m vllm.entrypoints.openai.api_server \
--model <this-repo> --served-model-name qwen40b-deckard \
--dtype half --tensor-parallel-size 2 --disable-custom-all-reduce \
--quantization awq_marlin --max-model-len 12288 \
--gpu-memory-utilization 0.94 --max-num-seqs 1 \
--language-model-only --skip-mm-profiling \
--mamba-cache-mode align --enable-prefix-caching \
--reasoning-parser qwen3 \
--enable-auto-tool-choice --tool-call-parser qwen3_xml \
--speculative-config '{"method":"mtp","num_speculative_tokens":3}' \
--compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[4],"max_cudagraph_capture_size":4}'
Notes:
- CUDA-graph capture size must be a multiple of
num_speculative_tokens+1 (here 4), or the engine aborts with "No valid cudagraph sizes". - Plain mode (24k ctx, ~24 tok/s): drop
--speculative-config, use capture size 1 and--max-model-len 24576. - Long context (131k, ~3 tok/s): add
--cpu-offload-gb 8 --max-model-len 131072. - This is a vision-language checkpoint architecture served text-only (
--language-model-only); no vision weights are shipped. - The base model is a thinking model: responses arrive as
reasoning_content+content(hence--reasoning-parser qwen3). Give it generousmax_tokens.
Reproducing the quant (outline)
- Quantize the BF16 base with AutoRound (
bits=4, group_size=128, sym=False, iters=0,format="auto_awq"), keeping the BF16 list above vialayer_config={name:{"bits":16}}. Two patches are needed for this VL-architecture checkpoint: force the LLM (not MLLM) calibration path, and fix the checkpoint-name reverse mapping so tensors export asmodel.language_model.*(matching how vLLM loads the composite arch). - Transplant the MTP head: copy the 15
mtp.*tensors from the 27B donor into a separate safetensors shard and register them inmodel.safetensors.index.json. No renaming needed —mtp.*lives top-level. - Config: base model's
config.json+quantization_config {quant_method: awq, bits: 4, group_size: 128, version: gemm, zero_point: true, modules_to_not_convert: [...]}(see this repo's config for the exact list).mtp_num_hidden_layers: 1is already present in the base config.
Honesty notes / limitations
- The quant is RTN (round-to-nearest, no per-block gradient optimization). It measures coherent across RU/EN chat, code, and reasoning prompts, but a calibrated AutoRound run (
iters=200) would likely be marginally more accurate. May follow later. - The MTP head is a dimensional transplant, not a trained one — acceptance (~78% greedy) is good but below a natively trained head; creative/high-temperature text will accept less and gain less speed.
- The base model is a community franken-expansion of Qwen3.6-27B and an uncensored finetune. Quality characteristics of the base are inherited as-is; the base model's license and usage terms apply.
- Context ceilings above are specific to 2× 22 GB cards; more VRAM → linearly more KV/context.
Credits
- DavidAU — the base Opus-Deckard 40B model.
- QuantTrio — the Qwen3.6-27B AWQ whose MTP head was transplanted.
- PiehSoft — the MTP-transplant method (
inject_mtp_40b.py) and acceptance write-up for the GGUF path. - Intel AutoRound — the quantization toolkit.
- weicj / vLLM-2080ti-definitive — the SM75 vLLM build that makes any of this possible on Turing.
Support the project · Поддержать проект
Everything here — the quants, the engine, the measurements — is made and published for free. If it helped you run a model on your own hardware, you can say thanks with a donation. Если это помогло вам запустить модель на своём железе, можно поблагодарить донатом.
| address | QR | |
|---|---|---|
| YooMoney / ЮMoney (roubles: a YooMoney wallet or any bank card) | 4100119356331418 · send / перевести |
|
| USDT (TRC-20, Tron network) | TBvoJHi7uyonSpvdH9Y6RAAXGWYVR2jeqw |
|
| Ethereum (ETH and Ethereum-network tokens, ERC-20) | 0x66CA7c683fbaF030b2300c918A3751209eA30dEa |
⚠️ Send only Tron-network assets (USDT TRC-20, TRX) to the Tron address and only Ethereum-network assets to the Ethereum address; anything sent over the wrong network is lost. Сеть важна: отправленное не в той сети пропадёт.
Thank you! Спасибо! 🙏