第26章 Qwen 系列训推实战

第26章 Qwen 系列训推实战

Qwen 是阿里云开源的大语言模型系列。在中文 NLP 的世界里,Qwen 已经成为事实上的首选开源基座——不是因为它的英文能力有多强,而是因为它在中文理解、多语言支持和代码能力上的综合表现,让它在最需要中文能力的场景中无人能及。

26.1 章节导入

2023 年 8 月,阿里云发布了 Qwen 的第一批模型(1.8B、7B、14B、72B)。与很多”换个数据再训一遍”的开源模型不同,Qwen 从第一天就在架构层面做了针对中文的深度优化——最显著的标志是一个包含 151,851 个 token 的超大词表。

到 2024 年,Qwen 已经发展为一个庞大的模型家族:

graph TD
    QWEN["Qwen 系列家族 (2024)"]
    QWEN --> BASE["Qwen 2.5 Base<br/>(0.5B / 1.5B / 3B / 7B / 14B / 32B / 72B)"]
    QWEN --> INSTRUCT["Qwen 2.5 Instruct<br/>(对话模型)"]
    QWEN --> CODE["Qwen 2.5-Coder<br/>(7B / 32B)"]
    QWEN --> MATH["Qwen 2.5-Math<br/>(数学专用)"]
    QWEN --> VL["Qwen 2-VL<br/>(视觉语言 2B / 7B / 72B)"]
    QWEN --> AUDIO["Qwen 2-Audio<br/>(语音语言)"]
    QWEN --> QWQ["QwQ / Qwen3<br/>(推理增强)"]
    QWEN --> MOE["Qwen-MoE<br/>(A14B MoE)"]

本章将深入 Qwen 的架构设计、QwQ 推理模型、Qwen-VL 多模态架构,以及”训推一体”的工程实践。

26.2 架构设计

26.2.1 大词表设计

Qwen 最鲜明的架构特征是它的大词表——151,851 个 token,远超 LLaMA 2(32K)和 LLaMA 3(128K)。

为什么要这么大的词表?因为中文编码效率

句子: "人工智能是计算机科学的一个分支"

LLaMA 2 tokenizer (32K vocab):
  → 人工(3) 智能(3) 是(1) 计算机(4) 科学(3) 的(1) 一个(2) 分支(3)
  → 总计 ~20 tokens

LLaMA 3 tokenizer (128K vocab):
  → 人工(1) 智能(1) 是(1) 计算机(1) 科学(1) 的(1) 一个(1) 分支(1)
  → 总计 ~10 tokens

Qwen tokenizer (151K vocab):
  → 人工智能(1) 是(1) 计算机(1) 科学(1) 的(1) 一个(1) 分支(1)
  → 总计 ~8 tokens("人工智能"是一个 token!)

更少的 token 数意味着: 1. 推理更快:序列更短,Attention 计算量更小 2. 上下文更长:同样的 token 限制下能放更多内容 3. API 更便宜:按 token 计费时成本更低 4. 编码效率更高:特别是中文密集场景

但大词表也有代价——Embedding 层和 Output Head 的参数量很大

# Qwen 7B 的 Embedding 参数计算
vocab_size = 151851
hidden_dim = 4096

embedding_params = vocab_size * hidden_dim  # = 622,616,576 ≈ 623M
# 占 7B 总参数的 ~9%

# 对比 LLaMA 2 7B
llama_vocab = 32000
llama_embedding = llama_vocab * 4096  # = 131M (仅 1.9%)

26.2.2 整体架构

Qwen 2.5 的架构与 LLaMA 非常相似,但有几个关键差异:

graph TD
    A["Input Tokens"] --> B["Token Embedding<br/>(151K vocab)"]
    B --> C["RoPE (无绝对位置编码)"]
    C --> D["Transformer Blocks × N"]
    D --> E["RMSNorm"]
    E --> F["LM Head (tied weights)"]
    
    subgraph "Qwen Transformer Block"
        G["RMSNorm"] --> H["Grouped Query Attention<br/>+ RoPE + QKV Bias"]
        H --> I["Residual + Dropout"]
        I --> J["RMSNorm"]
        J --> K["SwiGLU FFN"]
        K --> L["Residual + Dropout"]
    end
    
    D --> G

Qwen 的几个独有设计:

  1. QKV Bias:在 Attention 的 QKV 投影中加了 bias 项——LLaMA 没有。这对某些需要位置敏感的任务有帮助。
  2. Tied Embeddings(小模型):Qwen 0.5B/1.5B 将 Input Embedding 和 Output Head 共享权重以节省参数。大模型(7B+)则不共享。
  3. Tokenizer 设计:基于 BBPE(Byte-level BPE),对中文、日文、韩文做了专门的词表优化。

26.2.3 主要模型规格

配置 Qwen 2.5 0.5B Qwen 2.5 7B Qwen 2.5 14B Qwen 2.5 72B
Layers 24 28 48 80
Dim 896 3584 5120 8192
Heads 14 28 40 64
KV Heads 2 4 8 8
FFN Dim 4864 18944 13824 29568
Vocab 151,936 151,936 151,936 151,936
Context 32K 128K 128K 128K
Params 0.49B 7.62B 14.7B 72.7B
Tip

注意 KV Heads 的激进压缩:Qwen 2.5 7B 只有 4 个 KV Heads(Query 有 28 个),GQA ratio 是 7:1。这比 LLaMA 3 8B 的 4 个 KV Heads(32 个 Query,8:1)更加激进。这是为什么 Qwen 在长上下文推理时显存效率特别好。

26.3 QwQ 推理增强模型

26.3.1 什么是 QwQ

QwQ(Qwen with Questions)是阿里在 2024 年底发布的推理增强模型,对标 OpenAI 的 o1。与标准 Instruct 模型不同,QwQ 会先进行长链式思维(Chain-of-Thought)再给出答案。

用户问题: "一个水池有两个进水管,A管单独注满需要6小时,B管需要8小时,
          一个出水管C单独排完需要12小时。三管同时开,几小时注满?"

标准模型直接给答案: 4.8小时(可能没有过程)

QwQ 的推理过程:
[Thinking] 让我一步步分析...
首先,计算每个管道的流量(以水池容量为单位):
- A管进水速度:1/6 池/小时
- B管进水速度:1/8 池/小时
- C管排水速度:1/12 池/小时

三管同时开启时的净流量:
1/6 + 1/8 - 1/12 = 4/24 + 3/24 - 2/24 = 5/24 池/小时

注满时间 = 1 / (5/24) = 24/5 = 4.8 小时

[Answer] 三管同时开启,需要 4.8 小时注满水池。

26.3.2 推理增强模型的训练

QwQ 的训练方法(基于社区的逆向推测)大概率遵循以下范式:

graph TD
    A["Qwen Base Model"] --> B["SFT<br/>(大量 CoT 数据)"]
    B --> C["RL on Math/Code<br/>(有明确答案的验证)"]
    C --> D["RL on General<br/>(开放问题)"]
    D --> E["QwQ"]
    
    F["数学题 + 解答"] --> B
    G["代码题 + 推理过程"] --> B
    H["验证器 / 执行器"] --> C

关键创新在于强化学习阶段——不像 RLHF 那样依赖人类标注偏好,而是使用可验证的奖励(数学题有标准答案,代码可以执行测试)。

# 推理增强模型的奖励函数示例
def math_reward(response, ground_truth):
    """从模型回复中提取答案,与标准答案比较"""
    answer = extract_final_answer(response)  # 提取 \\boxed{} 中的内容
    try:
        if float(answer) == float(ground_truth):
            return 1.0
        elif abs(float(answer) - float(ground_truth)) < 1e-6:
            return 0.8  # 数值近似
        else:
            return 0.0
    except:
        return 0.0

def code_reward(response, test_cases):
    """执行模型生成的代码,跑测试用例"""
    code = extract_code(response)
    passed = 0
    total = len(test_cases)
    for test in test_cases:
        try:
            result = execute(code, test["input"])
            if result == test["expected"]:
                passed += 1
        except:
            pass
    return passed / total

26.3.3 QwQ 的推理服务挑战

推理增强模型在服务部署上有一个新的挑战:推理过程很长。一个数学题可能需要 1000-10000 token 的思维链,但用户只想看最终答案。

# QwQ 部署需要特别配置
vllm serve Qwen/QwQ-32B-Preview \
  --tensor-parallel-size 2 \
  --max-model-len 32768 \      # 需要支持长思维链
  --max-num-seqs 32 \           # 但单请求 token 很多,batch 不能太大
  --enable-prefix-caching \     # 推理过程可缓存
  --gpu-memory-utilization 0.92
Warning

推理模型的成本问题:QwQ-32B 处理一个复杂数学题可能需要 5000+ token 的思维链。如果按 token 计费,用户需要为”思考过程”付费——但这个过程对用户不一定有直接价值。OpenAI 的 o1 系列也面临同样的问题。在生产环境中,需要让用户明确知道推理成本,或提供”快速模式”(跳过思维链)选项。

26.4 Qwen-VL 多模态架构

26.4.1 架构概览

Qwen-VL(Vision-Language)是 Qwen 的多模态扩展。它不是一个从头训练的多模态模型,而是使用模块化拼接的方式——预训练的 Vision Encoder + LLM,通过 Adapter 连接。

graph TD
    A["图像输入"] --> B["ViT Vision Encoder<br/>(OpenCLIP ViT-bigG)"]
    B --> C["图像 Patches<br/>(256x256 → patch embeddings)"]
    C --> D["Vision-Language Adapter<br/>(MLP + Cross Attention)"]
    D --> E["视觉 Token 序列"]
    
    F["文本输入"] --> G["Text Tokenizer<br/>(Qwen BBPE)"]
    G --> H["文本 Token 序列"]
    
    E --> I["拼接: [视觉 tokens] + [文本 tokens]"]
    H --> I
    I --> J["Qwen LLM<br/>(标准 Transformer)"]
    J --> K["输出"]

26.4.2 Vision-Language Adapter

Qwen-VL 的关键组件是 VL Adapter,它负责将视觉特征对齐到语言空间:

# Qwen-VL Adapter 的简化架构
class VLAdapter(nn.Module):
    def __init__(self, vision_dim=1664, llm_dim=4096, num_query=256):
        super().__init__()
        # 方式1:Cross Attention(Qwen-VL v1)
        self.cross_attn = nn.MultiheadAttention(
            embed_dim=llm_dim,
            num_heads=16,
            kdim=vision_dim,
            vdim=vision_dim
        )
        # 可学习的 Query tokens
        self.query_embed = nn.Parameter(torch.randn(num_query, llm_dim))
        
        # 方式2:简单 MLP(Qwen-VL v2)
        self.mlp = nn.Sequential(
            nn.Linear(vision_dim, llm_dim * 2),
            nn.GELU(),
            nn.Linear(llm_dim * 2, llm_dim)
        )
    
    def forward(self, vision_features):
        # vision_features: [num_patches, vision_dim]
        
        # Cross Attention 路径
        query = self.query_embed.unsqueeze(1)  # [num_query, 1, llm_dim]
        # 固定数量的 query tokens 从视觉特征中提取信息
        visual_tokens, _ = self.cross_attn(
            query, vision_features, vision_features
        )  # [num_query, 1, llm_dim]
        
        return visual_tokens.squeeze(1)  # [num_query, llm_dim]

Qwen 2-VL 引入了 Naive Dynamic Resolution——根据图像的实际大小动态调整 patch 数量,而不是将所有图像 resize 到固定大小。这对文档理解、长截图等场景非常重要。

26.4.3 Qwen-VL 的训练

# Qwen-VL 训练阶段(简化)
stages:
  - name: "Stage 1: Vision Encoder 预训练"
    description: "在大量图像-文本对上训练 ViT"
    data: "LAION-400M / COYO-700M"
    
  - name: "Stage 2: VL Adapter 训练"
    description: "冻结 ViT 和 LLM,只训练 Adapter"
    data: "图像-文本对(详细描述、QA)"
    frozen: ["ViT", "LLM"]
    trainable: ["VL Adapter"]
    
  - name: "Stage 3: 端到端微调"
    description: "解冻 LLM,整体微调"
    data: "多模态指令数据"
    frozen: ["ViT"]
    trainable: ["VL Adapter", "LLM"]

26.5 训推一体的工程实践

26.5.1 Qwen 的全栈工具链

Qwen 与其他开源模型最大的差异化不在于模型质量,而在于全栈工具链的完整性。阿里为 Qwen 提供了从训练到部署的完整支持:

graph LR
    subgraph "训练工具"
        A["Qwen-Agent<br/>(框架)"]
        B["EasyNLP<br/>(训练框架)"]
        C["ModelScope<br/>(模型中心)"]
    end
    
    subgraph "部署工具"
        D["DashScope API<br/>(云服务)"]
        E["vLLM 支持<br/>(开源部署)"]
        F["Ollama 支持<br/>(本地部署)"]
    end
    
    subgraph "应用工具"
        G["Qwen-Agent<br/>(Agent 应用)"]
        H["通义千问 App<br/>(消费端)"]
        I["阿里云百炼<br/>(企业平台)"]
    end
    
    A --> G
    B --> E
    C --> F

26.5.2 使用 vLLM 部署 Qwen

# Qwen 2.5 7B 部署(单卡)
vllm serve Qwen/Qwen2.5-7B-Instruct \
  --port 8000 \
  --max-model-len 32768 \
  --enable-prefix-caching \
  --gpu-memory-utilization 0.9

# Qwen 2.5 72B 部署(4卡)
vllm serve Qwen/Qwen2.5-72B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 65536 \
  --enable-prefix-caching \
  --quantization fp8

# Qwen 2-VL 部署(视觉语言模型)
vllm serve Qwen/Qwen2-VL-7B-Instruct \
  --port 8000 \
  --max-model-len 32768 \
  --trust-remote-code

26.5.3 LoRA 微调 Qwen

# 使用 LLaMA-Factory 微调 Qwen(社区最流行方案)
from llama_factory import create_args, run_sft

args = create_args(
    model_name_or_path="Qwen/Qwen2.5-7B",
    dataset="your_dataset",  # 在 data/dataset_info.json 中注册
    finetuning_type="lora",
    lora_target=["q_proj", "k_proj", "v_proj", "o_proj"],
    lora_rank=32,
    lora_alpha=64,
    learning_rate=5e-5,
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    cutoff_len=4096,
    output_dir="./qwen-lora-output",
    fp16=False,
    bf16=True,
)

run_sft(args)
# 或使用 YAML 配置文件
# qwen_finetune.yaml
### model
model_name_or_path: Qwen/Qwen2.5-7B
trust_remote_code: true

### method
stage: sft
do_train: true
finetuning_type: lora
lora_target: q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj
lora_rank: 32
lora_alpha: 64

### dataset
dataset: my_data
template: qwen
cutoff_len: 4096
overwrite_cache: true

### output
output_dir: ./output/qwen-7b-lora
logging_steps: 10
save_steps: 500

### train
per_device_train_batch_size: 4
gradient_accumulation_steps: 4
learning_rate: 5e-5
num_train_epochs: 3
warmup_ratio: 0.03
lr_scheduler_type: cosine
bf16: true
flash_attn: fa2  # 使用 FlashAttention 2
# 启动微调
llamafactory-cli train qwen_finetune.yaml

# 合并 LoRA 权重
llamafactory-cli export \
  --model_name_or_path Qwen/Qwen2.5-7B \
  --adapter_name_or_path ./output/qwen-7b-lora \
  --export_dir ./output/qwen-7b-merged

26.5.4 Qwen-Agent:内置 Agent 框架

# Qwen-Agent 示例
from qwen_agent.agents import Assistant
from qwen_agent.tools import code_interpreter

# 定义工具
tools = [
    {"type": "code_interpreter"},  # 代码执行
    # 可以添加自定义工具
]

# 创建 Agent
agent = Assistant(
    llm={"model": "Qwen/Qwen2.5-7B-Instruct"},
    function_list=tools,
    system_message="你是一个数据分析助手,擅长用 Python 解决数据分析问题。"
)

# 运行
messages = [{"role": "user", "content": "帮我分析一下这个 CSV 文件的统计数据"}]
for response in agent.run(messages):
    print(response)

26.6 Qwen vs LLaMA 全面对比

维度 Qwen 2.5 72B LLaMA 3.1 70B
英文能力 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
中文能力 ⭐⭐⭐⭐⭐ ⭐⭐⭐
代码能力 ⭐⭐⭐⭐ ⭐⭐⭐⭐
数学能力 ⭐⭐⭐⭐ ⭐⭐⭐⭐
Vocab 大小 151,936 128,256
KV Heads 8 8
上下文 128K 128K
推理速度(中文) ⭐⭐⭐⭐⭐ ⭐⭐⭐
推理速度(英文) ⭐⭐⭐⭐ ⭐⭐⭐⭐
微调生态 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
部署便捷度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
Tip

Qwen 的中文优势在哪些场景最明显? 1. 法律/合同文本:中文专业术语的编码效率和准确性 2. 古文/文学:Qwen 对文言文的理解远超 LLaMA 3. 客服/对话:更自然的中文表达,不像翻译腔 4. 代码注释/文档生成:中文注释的质量 5. 表格/结构化数据:中文表头和内容的理解

26.7 实践建议

Tip

选型建议: 1. 面向中文用户 → Qwen:毫无疑问,中文场景 Qwen 全面领先 2. 多语言服务 → Qwen:Qwen 的词表覆盖 29+ 种语言 3. 英文为主 → LLaMA 3:英文质量略优,生态更成熟 4. 需要推理能力 → QwQ:开源推理模型中最强的选择之一 5. 需要多模态 → Qwen-VL:模块化设计,部署灵活 6. 资源受限 → Qwen 2.5 3B:3B 参数量但能力出色

Warning

Qwen 部署的注意事项: 1. FlashAttention 版本:Qwen 需要 FlashAttention 2 或以上,旧版本可能有兼容性问题 2. Tokenizer 版本:Qwen 2.5 的 tokenizer 与 Qwen 1 不兼容,升级时必须同时更新数据处理管线 3. Chat Template:Qwen 使用 <|im_start|><|im_end|> 作为对话标记,与 LLaMA 的格式不同 4. 大词表的显存开销:151K vocab 的 Embedding 层在 FP16 下占 ~1.2GB(72B 模型),推理时需要确保有足够余量

26.8 小结

Qwen 系列代表了中国 AI 团队在大模型领域的工程实力。它没有革命性的架构创新——大部分技术与 LLaMA 一脉相承——但它的价值在于针对中文场景的系统化优化

  • 大词表解决了中文编码效率问题
  • QwQ 用可验证奖励的 RL 方法实现了推理增强
  • Qwen-VL 用模块化方式实现了多模态
  • 完整的工具链让从训练到部署的每个环节都有官方支持

在中文 NLP 场景,Qwen 已经是开源模型的首选。

26.9 延伸阅读

  • Qwen Team (2024). Qwen2.5 Technical Report. arXiv:2412.15115
  • Bai et al. (2023). Qwen Technical Report. arXiv:2309.16609
  • Wang et al. (2024). Qwen2-VL: Enhancing Vision-Language Model’s Perception of the World at Any Resolution. arXiv:2409.12191
  • Qwen-Agent 项目:https://github.com/QwenLM/Qwen-Agent
  • LLaMA-Factory(微调工具):https://github.com/hiyouga/LLaMA-Factory
  • ModelScope(模型中心):https://modelscope.cn