1. 从 Qwen 1 到 Qwen 2:架构演进到底改了什么
Qwen2 是阿里通义千问团队在 2024 年发布的开源大语言模型系列,包含 0.5B、1.5B、7B、72B 四个稠密模型,以及 57B-A14B 的 MoE 变体。它仍然是一个 Decoder-only Transformer,骨干结构和 Qwen 1 一脉相承:Pre-Norm、RMSNorm、RoPE、SwiGLU、QKV bias 这些设计全部保留。如果你已经读过 Qwen 1 / Qwen 1.5 的架构介绍,会发现 Qwen2 并不是推倒重来,而是在几个关键位置做了系统性升级。
适合谁来读这篇?如果你正在做模型选型、想搞清楚 Qwen2 为什么推理更快、长上下文为什么能到 128K、MoE 版本到底怎么省算力,那这篇就是写给你的。我会把 Qwen2 相对 Qwen 1 / Qwen 1.5 的演进拆成四条主线:GQA 全面替代 MHA、长上下文训练方案(RoPE 基频 + YARN + DCA)、Embedding 绑权策略按规模切换、MoE 细粒度专家 + 共享专家设计。每条线都给出配置对照和可复现的验证步骤。
先看一张全局对照表,把三个版本的核心差异钉住:
| 维度 | Qwen 1 | Qwen 1.5 | Qwen2 |
|---|---|---|---|
| 注意力 | 报告基线 MHA | 部分规格 GQA | 稠密全系 GQA |
| FFN 中间维 | 8/3·d 经验式 | 依配置 | 表 1 Intermediate Size |
| 词表 | ~152K | 生态一致 | 151,646 固定 |
| Embedding | untied | 多 untied | 小模型 tied,大模型 untied |
| 长上下文 | 推理技巧为主 | 32K 产品化 | 训练 32K + RoPE 1e6 + YARN + DCA → ~128K |
| MoE | 无 | Qwen1.5-MoE 线 | 57B-A14B,细粒度 + 共享专家 |
这张表里最值得关注的是 GQA 和长上下文方案。Qwen 1 时代 MHA 是默认配置,每个 Query 头都有独立的 Key/Value 头,推理时 KV Cache 随头数线性增长。Qwen2 把 GQA 变成稠密全系默认,KV 头数远小于 Query 头数,直接压低了推理显存和带宽。长上下文方面,Qwen 1 主要靠推理期的 NTK-aware RoPE、LogN 等技巧,Qwen2 则把 32K 训练写进预训练末段,再配合 RoPE 基频调整和 YARN、DCA 做外推。
下面逐个展开。每个部分我都会给出配置片段和验证代码,你可以直接在自己的环境里跑。
2. GQA 全面替代 MHA:Qwen2 推理加速的核心改动
2.1 MHA 和 GQA 的区别到底在哪
MHA(Multi-Head Attention)里 Query、Key、Value 的头数相同,记为 h。每个头有独立的投影矩阵 W_Q、W_K、W_V,得到 Q、K、V 三个张量,形状都是 L×d_k。推理时自回归生成,每步都要缓存历史位置的 K、V,缓存量正比于 KV 头数。头数越多,KV Cache 越大,显存和带宽压力越重。
GQA(Grouped-Query Attention)把 Query 头数 h_q 和 KV 头数 h_kv 拆开,满足 h_kv < h_q。g = h_q / h_kv 个 Query 头共用同一组 K、V。每个 Q 头仍然用自己的 Q 算注意力权重,但从同一组 K、V 上做点积和加权。直觉上就是:查询角度可以很多,但供检索的键值槽少开几份,在表达力和显存之间折中。
极端情况:h_kv = h_q 就是 MHA,h_kv = 1 就是 MQA(所有 Q 头共享一份 K、V)。GQA 介于两者之间。
| 项目 | MHA | GQA |
|---|---|---|
| Q 头数 / KV 头数 | h_q = h_kv = h | h_q > h_kv |
| 每头 d_k | 常取 d_model / h_q | 同左 |
| 单层 Attn 输出形状 | L × d_model | 同左 |
| 推理 KV Cache | 正比于 h_q 份 | 正比于 h_kv 份 |
配置里常写num_attention_heads= h_q,num_key_value_heads= h_kv。二者相等就是 MHA。
2.2 Qwen2 各尺寸的 GQA 配置
Qwen2 稠密模型全部采用 GQA。以 Qwen2-7B 为例:Hidden size 3584,层数 28,Query heads = 28,KV heads = 4,Head size 128。d_model = 28 × 128 = 3584,KV Cache 只随 4 个 KV 头增长,而不是 28 个。72B 则是 64 个 Query 头、8 个 KV 头。
分组规则:g = h_q / h_kv。第 t 个 Query 头使用第 u 组 KV,u = floor((t-1)/g) + 1。以 7B 为例,h_q=28,h_kv=4,g=7:头 1-7 共用 K(1)、V(1),头 8-14 共用 K(2)、V(2),依此类推。
2.3 可复现的 GQA 配置验证
你可以直接加载 Qwen2-7B 的 config.json 来验证这些参数。下面这段 Python 代码会打印出关键配置:
from transformers import AutoConfig config = AutoConfig.from_pretrained("Qwen/Qwen2-7B") print("hidden_size:", config.hidden_size) print("num_hidden_layers:", config.num_hidden_layers) print("num_attention_heads:", config.num_attention_heads) print("num_key_value_heads:", config.num_key_value_heads) print("head_dim:", config.hidden_size // config.num_attention_heads) print("intermediate_size:", config.intermediate_size) print("tie_word_embeddings:", config.tie_word_embeddings) print("rope_theta:", config.rope_theta) print("vocab_size:", config.vocab_size)预期输出(Qwen2-7B):
hidden_size: 3584 num_hidden_layers: 28 num_attention_heads: 28 num_key_value_heads: 4 head_dim: 128 intermediate_size: 18944 tie_word_embeddings: False rope_theta: 1000000.0 vocab_size: 151646注意rope_theta是 1000000.0,也就是 10^6,这是 Qwen2 相对 Qwen 1 的另一个关键改动,下一节会展开。tie_word_embeddings为 False,说明 7B 不绑权。
2.4 GQA 对推理的实际影响
我实测下来,Qwen2-7B 在单张 24GB 显卡上跑 32K 上下文推理,KV Cache 占用比同规格 MHA 模型低大约 7 倍(28/4)。这意味着同样的显存可以支撑更长的序列或更大的 batch。如果你用 vLLM 部署,GQA 的收益会更明显,因为 vLLM 的 PagedAttention 本身就是按 KV 头来管理显存块的。
3. 长上下文方案:RoPE 基频、YARN 与 DCA
3.1 训练期把上下文从 4K 拉到 32K
Qwen2 在预训练收尾阶段,把单条序列的训练上下文从 4096 token 提高到 32768 token,同时引入更多高质量长文本数据。这一步的意义在于:优化目标和梯度直接作用在最长 32K 的因果掩码自注意力上,模型在训练分布内就学会了跨万级 token 的依赖和检索,而不是只在短上下文上拟合、再靠推理时插值去猜长行为。
这和 Qwen 1 时代主要靠推理期技巧(NTK-aware RoPE、LogN、窗口注意力)有本质区别。Qwen2 是先把长上下文训进权重,再用扩展算子做外推。
3.2 RoPE 基频从 1e4 调到 1e6
RoPE 在每一维对上使用一组与位置 m 相关的旋转角,实现上通过底数 theta_0 生成逆频率,决定各维旋转随位置变化的快慢。Qwen2 把 RoPE 的 base 从常见的 10^4 改为 10^6。
直观理解:theta_0 更大,相邻 token 在多数频率分量上的相位差更小,极长距离上仍保留可分的相对位置信息,减轻训练长度之外位置编码退化的问题。这个改动不改变在注意力里旋 Q、K 的流程,只改 RoPE 超参。
你可以在 config.json 里看到rope_theta: 1000000.0,这就是 10^6。
3.3 YARN 和 DCA 的分工
YARN(Yet another RoPE extensioN)在超出训练所见上下文或需要平滑拉长有效窗口时,对注意力中的权重尺度做与目标长度相关的重缩放,使模型在更长序列上仍能保持合理的注意力分布。报告中的表述是 rescaling the attention weights for better length extrapolation。凡宣称支持超过 32K 上下文的 Instruct 型号,都集成了 YARN。
DCA(Dual Chunk Attention)把超长序列划成若干个长度可控的 chunk。如果整条输入能被单个 chunk 容纳,DCA 不产生与标准全序列自注意力不同的结果,即短于 chunk 阈值时行为与 vanilla 一致。如果序列长于单块容量,则在块内保持常规 token-token 相对位置建模,并额外引入跨 chunk 的相对位置信息,使模型在块边界处仍能建立连贯的长程依赖。
两者分工:DCA 偏结构上分块加位置关系,YARN 偏注意力尺度上的外推修正。报告 Table 12 有一条对部署很重要的注记:集成 YARN + DCA 后,长度不超过 32K 时,模型行为与不集成时一致。也就是说,长文扩展补丁不会在已训练的 32K 以内随意改变注意力形态。
3.4 长上下文配置验证
下面这段代码验证 Qwen2-7B-Instruct 的长上下文配置:
from transformers import AutoConfig, AutoTokenizer model_id = "Qwen/Qwen2-7B-Instruct" config = AutoConfig.from_pretrained(model_id) tokenizer = AutoTokenizer.from_pretrained(model_id) print("max_position_embeddings:", config.max_position_embeddings) print("rope_theta:", config.rope_theta) print("sliding_window:", getattr(config, "sliding_window", None)) print("use_yarn:", getattr(config, "use_yarn", "not in config")) print("model_type:", config.model_type)Qwen2-7B-Instruct 的max_position_embeddings通常为 32768,rope_theta为 1000000.0。YARN 和 DCA 的具体启用方式依推理框架而定,vLLM 和 transformers 都有对应的参数。
4. Embedding 绑权策略与 MoE 扩展
4.1 绑权是什么,Qwen2 怎么选
绑权(weight tying)指输出线性层与嵌入矩阵共用参数,即 W_out = E^T。此时第 w 个 logit 为 s_w = h^T · e_w + bias_w,也就是当前上下文表示 h 与该词嵌入向量 e_w 的内积。省掉一整块 d_model × V 的输出权重,并带来更强的归纳偏置。
Qwen2 按规模切换策略:
| 模型 | Embedding Tying |
|---|---|
| Qwen2-0.5B | True |
| Qwen2-1.5B | True |
| Qwen2-7B | False |
| Qwen2-72B | False |
| Qwen2-57B-A14B (MoE) | False |
小模型(0.5B/1.5B)偏部署与参数预算,用 tied;7B/72B/MoE 用 untied,与 Qwen 1 报告对大规模模型不绑权以换表现的取向一致。
4.2 MoE 变体:57B-A14B 的设计
Qwen2-57B-A14B 是 MoE 型号,总参约 57B,每 token 激活约 14B。MoE 仅替换每个 Decoder 层里的 FFN 子层,自注意力子层(GQA、RoPE、QKV bias)与稠密 Qwen2 同型。
关键配置:Hidden 3584,28 层,28 Q / 4 KV(与稠密 7B 同宽同深)。每个专家 Intermediate 2560(远小于稠密 7B 的 18944,体现细粒度)。64 个 routed experts,每 token 激活其中 8 个(k=8)。8 个 shared experts,每 token 必算。
路由公式:门控网络 G(x) 输出 n 个 logits,softmax 得到 p,取 top-k 个专家做加权求和:
y_route = sum_{i in topk(p)} p_i * E_i(x)加上共享专家后,一层 MoE FFN 的输出为:
y = sum_{j=1}^{n_s} S_j(x) + sum_{i in topk(p)} p_i * R_i(x)共享专家每个 token 都参与计算,提供稳定的表示基底;路由专家由门控动态挑选,承担专业化组合。
4.3 MoE 专家初始化
Qwen2 MoE 由稠密模型演化时,采用接近 upcycling 的流程。先将稠密 FFN 沿中间维复制若干份,对每份副本在中间维上做 shuffle,让切出的各专家即使来自同一副本也不完全相同。从副本中切出 n 个中间维为 h_E 的专家,丢弃多余维度。每个细粒度专家中 50% 参数随机重初始化,进一步增大专家间差异。Qwen2-57B-A14B 明确写为由 Qwen2-7B upscale 得到。
4.4 MoE 配置验证
from transformers import AutoConfig config = AutoConfig.from_pretrained("Qwen/Qwen2-57B-A14B") print("num_experts:", config.num_experts) print("num_experts_per_tok:", config.num_experts_per_tok) print("shared_expert_intermediate_size:", getattr(config, "shared_expert_intermediate_size", None)) print("moe_intermediate_size:", config.moe_intermediate_size) print("num_hidden_layers:", config.num_hidden_layers) print("hidden_size:", config.hidden_size)预期能看到num_experts: 64、num_experts_per_tok: 8、moe_intermediate_size: 2560等关键参数。
5. 常见报错排查
5.1 401 Unauthorized
加载 Qwen2 模型时如果报 401,通常是 Hugging Face token 没配置或过期。检查:
huggingface-cli whoami如果未登录,执行huggingface-cli login输入 token。Qwen2 系列是公开模型,一般不需要额外授权,但如果你用的是私有镜像或企业版,需要确认访问权限。
5.2 local proxy failed
这个报错通常出现在网络请求环节。如果你在调用 API 时遇到,检查你的 HTTP_PROXY / HTTPS_PROXY 环境变量是否指向了不可用的地址。在容器环境里,有时宿主机的代理配置会被继承进来,导致请求失败。清理环境变量:
unset HTTP_PROXY HTTPS_PROXY http_proxy https_proxy然后重新发起请求。
5.3 reading choices 报错
这个错误常见于用 OpenAI 兼容接口调用时,返回体格式不符合预期。检查你的请求是否带了正确的model参数,以及返回的 JSON 是否有choices字段。如果你用的是 TaoToken 这类兼容接口,确认 Base URL 拼写正确:
from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key="你的Key" ) resp = client.chat.completions.create( model="Qwen/Qwen2-7B-Instruct", messages=[{"role": "user", "content": "你好"}] ) print(resp.choices[0].message.content)如果choices为空,检查 model 名称是否在服务端已注册。
5.4 OAuth 相关报错
如果你用 Claude Code 或类似工具接入,遇到 OAuth 报错,通常是 token 刷新失败。检查你的 API Key 是否有效,以及 Base URL 是否指向正确的端点。对于 Claude Code 类工具,需要同时配置 Base URL、API Key 和 Model ID 三件套:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-xxxxxxxx", "model": "claude-sonnet-4-20250514" }5.5 模型加载 OOM
Qwen2-72B 全精度加载需要约 144GB 显存。如果显存不够,用 4bit 量化:
from transformers import AutoModelForCausalLM, BitsAndBytesConfig import torch bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_quant_type="nf4" ) model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen2-72B-Instruct", quantization_config=bnb_config, device_map="auto" )4bit 量化后 72B 约需 40GB 显存,单张 A100 80GB 可以跑。
6. 快速接入与验证
如果你不想在本地下载几十 GB 的权重,可以直接通过 API 验证 Qwen2 的推理效果。TaoToken 提供了兼容 OpenAI 接口的接入方式,你只需要一个 API Key 就能调用 Qwen2 系列模型。
先到 API Keys 页面 创建一个 Key,然后参考 接入文档 配置你的客户端。
验证 Qwen2-7B 的基本推理:
from openai import OpenAI client = OpenAI( base_url="https://taotoken.net/api", api_key="你的Key" ) resp = client.chat.completions.create( model="Qwen/Qwen2-7B-Instruct", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话解释 GQA 和 MHA 的区别。"} ], temperature=0.7, max_tokens=256 ) print(resp.choices[0].message.content)预期输出类似:GQA 让多个 Query 头共享一组 Key/Value 头,从而减少推理时的 KV Cache 体积,而 MHA 每个 Query 头都有独立的 Key/Value 头。
如果你想在浏览器里直接对比不同模型的输出,可以打开 模型对话 页面,切换 Qwen2-7B 和 Qwen2-72B 看效果差异。
对于需要长期跑编码任务或 Agent 的场景,Coding Plan 提供了更稳定的配额方案。如果你用 Claude Code 做开发,可以参考 Claude Code 接入指南 配置 Base URL 和 Model ID。
最后给一个实用技巧:验证 GQA 是否生效,最直接的方法是看推理时的 KV Cache 占用。用 transformers 加载 Qwen2-7B 和同规格 MHA 模型,在相同序列长度下对比past_key_values的显存占用,GQA 版本应该低大约 h_q/h_kv 倍。这个比值在 7B 上是 7,在 72B 上是 8。