HuggingFace镜像/Qwen3.6-27B-int4-AutoRound
模型介绍文件和版本分析
下载使用量0

Qwen3.6-27B INT4 AutoRound

基于Intel's AutoRound工具对Qwen/Qwen3.6-27B模型进行的W4A16(INT4权重,FP16激活)量化版本。

核心要点

  • 基础模型:Qwen3.6-27B(270亿参数密集型大语言模型,2026年4月21日发布)
  • 量化配置:INT4 W4A16,分组大小128,对称量化
  • 量化工具:auto-round(默认方案,200轮迭代,启用torch.compile)
  • 模型大小:18 GB(相比约54 GB的BF16版本)——压缩3倍
  • 保留MTP功能:原生多token预测(Multi-Token Prediction)头保持BF16精度,支持vLLM中的原生投机解码(测试中草稿接受率≈90%,吞吐量提升约2倍)
  • 精度保障:默认AutoRound方案可良好保留模型质量;层归一化权重、路由层、RMSNorm、linear_attn.in_proj_a/b以及MTP的融合fc层均未进行量化(这些层规模较小,全精度更有利于性能)

使用vLLM快速推理(支持MTP投机解码)

需使用支持Qwen3_5 MTP的vLLM版本(最新 nightly 版本——已在eugr/spark-vllm-docker分支0.19.1rc1.dev39+g7055d32a7中测试通过):

vllm serve Lorbus/Qwen3.6-27B-int4-AutoRound \
  --dtype half \
  --max-model-len 262144 \
  --gpu-memory-utilization 0.85 \
  --kv-cache-dtype tq-t4nc \
  --max-num-seqs 3 \
  --reasoning-parser qwen3 \
  --enable-auto-tool-choice \
  --tool-call-parser qwen3_xml \
  --port 8888 --host 0.0.0.0 \
  --trust-remote-code \
  --compilation-config.cudagraph_mode none \
  --speculative-config '{"method": "mtp", "num_speculative_tokens": 1}'

注意事项:

  • --kv-cache-dtype tq-t4nc(TurboQuant 4 位)相比 fp8 可将 KV 内存减半。若使用未集成 TurboQuant 分支的主线 vLLM,请使用 --kv-cache-dtype fp8。
  • 在 Blackwell 消费级(SM120/SM121)GPU 上,目前需要设置 --compilation-config.cudagraph_mode none——在部分 vLLM nightly 版本中,MTP 模块上的 CUDA 图捕获会出现 cudaErrorStreamCaptureInvalidated 错误。
  • --speculative-config 可启用模型原生的 MTP head 作为内置的草稿生成器。

OpenAI 兼容请求

from openai import OpenAI
client = OpenAI(base_url="http://localhost:8888/v1", api_key="EMPTY")
r = client.chat.completions.create(
    model="Lorbus/Qwen3.6-27B-int4-AutoRound",
    messages=[{"role": "user", "content": "Write a quicksort in Python."}],
    max_tokens=512,
)
print(r.choices[0].message.content)

变形金刚(无规范解码)

from transformers import AutoModelForCausalLM, AutoTokenizer
m = AutoModelForCausalLM.from_pretrained(
    "Lorbus/Qwen3.6-27B-int4-AutoRound",
    trust_remote_code=True,
    device_map="auto",
)
tok = AutoTokenizer.from_pretrained("Lorbus/Qwen3.6-27B-int4-AutoRound")
msg = [{"role": "user", "content": "Explain quantum computing briefly."}]
ids = tok.apply_chat_template(msg, add_generation_prompt=True, return_tensors="pt").to(m.device)
print(tok.decode(m.generate(ids, max_new_tokens=256)[0]))

量化详情

字段值
基础模型Qwen/Qwen3.6-27B
方法AutoRound(intel/auto-round),默认方案
方案W4A16(4位权重,FP16激活值)
位数4
分组大小128
对称量化是
打包格式auto_round:auto_gptq
未量化层linear_attn.in_proj_a/b、mtp.fc、所有LayerNorm和RMSNorm、路由门控
校准样本数128(默认)
迭代次数200
torch.compile已启用
用于量化的GPU1× RTX 5090(32 GB,SM120),low_gpu_mem_usage=True
量化耗时~1小时40分钟

未量化层 — 原因

  • linear_attn.in_proj_a/b:这些是Qwen3.6的Gated DeltaNet中的低秩投影层。其形状不能被32(分组大小)整除,因此AutoRound会跳过它们。它们仅占参数的极小一部分。
  • mtp.fc:多令牌预测(Multi-Token Prediction)融合层。AutoRound最初将其量化为GPTQ打包的INT4格式,但vLLM的Qwen3_5MTP加载器期望未量化的fc.weight。我们将其反量化为BF16,以便MTP能原生工作。如果您使用此量化模型不启用MTP,fc权重仍然存在且无影响。
  • 归一化层(Norms)、路由层(routers):对精度敏感且规模很小。

MTP修复 — 与标准AutoRound运行的区别

在Qwen3.5/3.6模型上直接运行auto-round会将mtp.fc打包为INT4格式。在此形式下,vLLM会完全跳过加载该层(参数名fc.qweight与预期的fc.weight不匹配),导致MTP推测解码的接受率为0%。

此版本在AutoRound完成后将mtp.fc反量化回BF16。该层大小仅约100 MB(5120 × 10240 × 2字节),因此对文件大小的影响可忽略不计。结果:MTP可开箱即用,并在典型提示上达到~80-90%的草稿接受率。

性能

在1× RTX 5090(32 GB) 上使用vLLM + TurboQuant 4位KV缓存 + MTP进行基准测试:

提示类型最大生成长度(max_tokens)吞吐量
"Write a haiku"12858 tok/s
"Explain quantum computing in 3 paragraphs"25660 tok/s
"Write 8 paragraphs about deep learning history"102460 tok/s
"What is 127*83? Show reasoning"25661 tok/s

关闭MTP时(移除--speculative-config):~32 tok/s。2倍的速度提升来自MTP推测解码,其接受率约为85%。

已知限制

  • 该模型是一个视觉语言模型——您可以在 OpenAI 兼容的消息中通过 image_url 内容部分输入图像。图像量化并非此处的重点;MoonViT 编码器权重保持其原始精度(如基础模型中的 BF16/FP16)。
  • group_size: 128 下的 bits: 4 配置优先考虑吞吐量/内存,而非极致精度。对于精度要求严格的工作,可尝试 auto-round-best 方案(1000 次迭代,速度慢约 5-10 倍)或更高的位宽。
  • 在 128K 上下文之外未进行广泛测试。Qwen3.6 的 partial_rotary_factor RoPE 缩放机制得以保留,因此 262K 上下文应可正常工作。

复现

pip install auto-round-nightly

auto-round \
  --model Qwen/Qwen3.6-27B \
  --scheme W4A16 \
  --format auto_round \
  --output_dir Qwen3.6-27B-int4-AutoRound \
  --enable_torch_compile \
  --low_gpu_mem_usage \
  --device_map 0

然后对 mtp.fc 进行反量化以实现 MTP 兼容性——请参见下方的 dequant_mtp_fc.py 脚本:

mtp.fc 反量化脚本(点击展开)
#!/usr/bin/env python3
"""Dequantize mtp.fc from GPTQ INT4 back to bf16 so vLLM's MTP loader picks it up."""
import json, shutil
from pathlib import Path
import torch
from safetensors import safe_open
from safetensors.torch import save_file

BASE = Path("Qwen3.6-27B-int4-AutoRound")
EXTRA = BASE / "model_extra_tensors.safetensors"
INDEX = BASE / "model.safetensors.index.json"

tensors = {}
with safe_open(EXTRA, framework="pt") as f:
    meta = f.metadata() or {}
    for k in f.keys():
        tensors[k] = f.get_tensor(k)

qw = tensors["mtp.fc.qweight"]    # [1280, 5120] int32
qz = tensors["mtp.fc.qzeros"]     # [80, 640] int32
sc = tensors["mtp.fc.scales"]     # [80, 5120] fp16

in_features = qw.shape[0] * 8     # 10240
out_features = qw.shape[1]        # 5120
group_size = 128
num_groups = in_features // group_size   # 80

def unpack_int32_4bit(packed, axis, factor=8):
    dev = packed.device
    shifts = torch.arange(0, 32, 4, device=dev, dtype=torch.int32)
    expanded = (packed.unsqueeze(axis + 1) >> shifts.view([8 if i == axis + 1 else 1 for i in range(packed.ndim + 1)])) & 0xF
    new_shape = list(packed.shape); new_shape[axis] *= factor
    return expanded.reshape(new_shape).to(torch.int8)

w_int = unpack_int32_4bit(qw, axis=0)   # [10240, 5120]
z_int = unpack_int32_4bit(qz, axis=1)   # [80, 5120]

w_grouped = w_int.view(num_groups, group_size, out_features).to(torch.float32)
w_fp32 = (w_grouped - z_int.unsqueeze(1).to(torch.float32)) * sc.unsqueeze(1).to(torch.float32)
w_final = w_fp32.view(in_features, out_features).t().contiguous().to(torch.bfloat16)   # [5120, 10240]

# Replace
for k in ("mtp.fc.qweight", "mtp.fc.qzeros", "mtp.fc.scales"):
    del tensors[k]
tensors["mtp.fc.weight"] = w_final

save_file(tensors, str(EXTRA), metadata=meta)

# Update index
idx = json.loads(INDEX.read_text())
for k in ("mtp.fc.qweight", "mtp.fc.qzeros", "mtp.fc.scales"):
    idx["weight_map"].pop(k, None)
idx["weight_map"]["mtp.fc.weight"] = EXTRA.name
# Recompute total_size
from collections import defaultdict
shard_sizes = defaultdict(int)
for sf in set(idx["weight_map"].values()):
    with safe_open(BASE / sf, framework="pt") as f:
        for k in f.keys():
            t = f.get_tensor(k)
            shard_sizes[sf] += t.numel() * t.element_size()
idx["metadata"]["total_size"] = sum(shard_sizes.values())
INDEX.write_text(json.dumps(idx, indent=2))

致谢

  • 感谢 Alibaba / Qwen team 提供基础模型 Qwen3.6-27B
  • 感谢 Intel AutoRound 团队提供量化框架
  • 感谢 @eugr 贡献 spark-vllm-docker 分支及 TurboQuant KV 缓存相关工作
  • 感谢 vLLM project 提供推理引擎及 Qwen3_5 MTP 支持

许可证

Apache 2.0 — 与 Qwen3.6-27B base 相同。

引用

如果您使用此量化版本,请引用 Qwen3.6 的原始发布(参见基础模型卡片)以及 AutoRound 论文:

@article{cheng2023autoround,
  title   = {Optimize Weight Rounding via Signed Gradient Descent for the Quantization of LLMs},
  author  = {Cheng, Wenhua and Zhang, Weiwei and Shen, Haihao and Cai, Yiyang and He, Xin and Lv, Kaokao and Liu, Yi},
  journal = {arXiv preprint arXiv:2309.05516},
  year    = {2023}
}