简介:面向大语言模型研究者、算法工程师与AI技术爱好者的Qwen首版技术报告资料,聚焦通义千问开源模型系列的架构设计与训练方法,可作为理解国产大模型技术路线的入门读物与案头查阅材料。压缩包内仅含1个PDF文件,整体约1.53MB,体量轻便,适合快速通读或按章节检索,无需搭建环境即可阅读。文档系统记录了Qwen基础预训练模型与Qwen-Chat对话模型的设计思路,内容涵盖预训练数据构成、分词方案、模型架构、上下文长度扩展、训练流程与实验结果,并对齐环节中的监督微调与人类反馈强化学习(RLHF)作了展开;同时介绍面向编程的Code-Qwen、Code-Qwen-Chat与面向数学的Math-Qwen-Chat等专用模型,以及对话模型在工具调用与智能体规划任务上的表现。目前已有1696人学习下载,适合希望梳理大模型从预训练到对齐全流程、并对照开源实践查漏补缺的读者参考。
1. 技术报告里最值钱的部分,是很少有人细看的那几节
翻开大模型技术资料,先跳进眼里的永远是参数规模、训练 token 数、榜单分数。真正决定一个模型能不能用、好不好用的,却常藏在最枯燥的那几节里:数据怎么筛、词表怎么定、位置编码怎么从 2048 外推到 8192、对齐阶段为什么非要在 SFT 之后单独训一个奖励模型。开源资料的价值正在这里——它把通常不会写进论文的工程决策摊开,让已经会用模型、但对它为什么能 work 说不清楚的人,能对着流程倒推一遍。后面不按报告章节复述,而是沿数据、结构、对齐、专题模型、落地踩坑这条线,把每个决策背后的动机和代价拆开看。
2. 数据管线与分词器:3 万亿 token 怎么筛成 15 万词表
2.1 语料构成:为什么多语言但不是平均分配
这套模型的预训练数据由公开网页文档、百科全书、书籍和代码构成,语料是多语言的,英文和中文占显著比例。这个构成单看像一句废话,拆开看其实每个来源都在补一种能力:网页文档量最大、覆盖面最广,但噪声与重复也最严重;百科全书提供事实密度高、组织规整的知识;书籍贡献长文本的连贯性和叙事结构;代码给的是形式化、结构化的样本,对逻辑推理和指令遵循有明显的迁移作用。多语言则是为了语言间的能力互相带动,而不是简单地凑数。
真正值得注意的是配比。同一种来源内部质量也参差不齐,所以原文提到会对某些来源的数据做“选择性上采样”——把高质量子集的重复次数提上去,让它在总 token 里占更大份额。这个动作听着不起眼,实际是在不动模型结构的前提下调整模型的“知识浓度”,也是后面对齐阶段能少花力气的前提。如果你自己做过继续预训练,就会知道这一步的收益往往比多加几层网络明显。
2.2 三道闸门:抽取清洗、两级去重、多模型质量打分
数据流水线的目标可以概括成一句话:把 3 万亿 token 里“不值得学”的部分尽量删掉。原文给出的顺序是,网页数据先做 HTML 正文抽取,再用语言识别工具判定语言;随后做规范化之后的精确匹配去重,以及基于 MinHash + LSH 的模糊去重;最后用规则加机器学习的方式过滤低质内容,评分模型包括语言模型、文本质量打分模型,以及识别冒犯性、不适当内容的模型,同时还保留人工抽样复核这道关。
这四步的先后顺序不是随意的。精确去重必须放在清洗之后,否则同一篇正文只要空白字符不同就躲过哈希;模糊去重放在质量打分之前,是因为重复的段落会污染打分模型的统计,让质量分数失去区分度。下面这段伪代码就是把这两级去重串起来的最小实现,能直接看出每个参数在防什么:
import hashlib, re, unicodedata from datasketch import MinHash, MinHashLSH def normalize(text: str) -> str: # 归一化:压平空白 + Unicode NFKC,避免格式差异绕过精确去重 text = re.sub(r"\s+", " ", text).strip() return unicodedata.normalize("NFKC", text) def exact_dedup(docs): seen, kept = set(), [] for d in docs: key = hashlib.sha1(normalize(d).encode("utf-8")).hexdigest() if key in seen: # 整篇重复,直接丢弃 continue seen.add(key) kept.append(d) return kept def fuzzy_dedup(docs, threshold=0.8, num_perm=128, shingle=5): lsh = MinHashLSH(threshold=threshold, num_perm=num_perm) kept = [] for i, d in enumerate(docs): m = MinHash(num_perm=num_perm) text = normalize(d) for j in range(0, max(len(text) - shingle, 1), 3): m.update(text[j:j + shingle].encode("utf-8")) if lsh.query(m): # 命中已有桶 = 与某篇高度相似,丢弃 continue lsh.insert(str(i), m) kept.append(d) return keptshingle=5控制切片粒度,太小会把套模板生成的段落误判为重复,太大则抓不到改写型抄袭;num_perm=128是 MinHash 的哈希函数个数,越大估计越准但内存和 CPU 越贵;threshold=0.8是 Jaccard 相似度门槛,实际项目里通常在 0.7 到 0.9 之间调。这套管线最终产出了最高 3 万亿 token 的语料,这个量级也解释了为什么后面模型能在同尺寸里跑出竞争力——数据侧的功课做在前面了。
还有一点容易被忽略:质量打分用的是“多个模型组合”而不是单一规则。语言模型负责判断文本的连贯性,质量模型负责判断信息密度,内容安全模型负责拦截不适当文本,三者的输出会做融合。单一模型打分最大的问题是会被长度、格式这些表面特征带偏,多模型加权能把这个偏差压下去一部分。手工抽样复核看起来是笨办法,但它其实是给自动打分器做校准——没有这一步,你永远不知道打分模型的阈值该定在哪。
2.3 151,851 个 token 的账:词表大小到底在优化什么
分词器采用 BPE,词表规模 151,851,实现上基于 tiktoken 这一套。词表大小直接决定两件事:一是同样一段文本会被切成多少个 token,也就是推理成本和上下文窗口的“实际容量”;二是 embedding 矩阵和输出投影矩阵的参数量,词表翻倍这两块参数也跟着翻倍。15 万这个量级,是在多语言覆盖和参数开销之间选的一个平衡点。
原文给出的对比方式是压缩率:随机抽取每种语言 100 万文档语料,测不同模型编码后的 token 数,以支持 100 种语言的 XLM-R 作为基准 1 归一化。结论是这套模型在保证中文、英文和代码解码效率的同时,对很多其他语言也维持了较高的压缩率。压缩率高意味着同样长度的窗口能塞进更多实际内容,长文档、代码仓库这类场景的收益最直接。
想自己验证这件事,用同一批业务语料横向跑一遍就行:
import tiktoken def compression_ratio(samples, enc): # 压缩率 = 原始字符数 / 编码后的 token 数,越大说明同样的窗口装得越多 chars = sum(len(s) for s in samples) tokens = sum(len(enc.encode(s)) for s in samples) return chars / max(tokens, 1) samples = ["一段中文技术文档……", "def train(model, data): ...", "A paragraph of English text."] enc = tiktoken.get_encoding("cl100k_base") # 示意用通用词表,实际应换成目标模型的词表文件 print(compression_ratio(samples, enc))这个脚本的意义在于:它把“词表好不好”从主观判断变成一个可复现的数字。中英混排的业务里,如果中文压缩率明显低于英文,说明该模型的词表对中文不够友好,长文档场景的显存和费用都会被放大。词表这事平时没人讨论,但它是最典型的“改一个数、影响全链路”的决策——上下文长度、推理成本、微调显存,全都跟着它走。
3. 模型结构:为什么和主流同构,却多了一个 QKV bias
3.1 RMSNorm、SwiGLU、RoPE:稳定组合的成本账
这套模型的基础架构采用主流的解码器结构:RMSNorm 做归一化、SwiGLU 做前馈激活、RoPE 做位置编码。这三个选择在同一时期多数开源模型里高度一致,原因并不是“大家都这么写”,而是各自解决了一个具体的数值或效率问题。RMSNorm 只按均方根缩放,不做去均值,少一次减法、少一组偏置参数,在深层网络里数值更稳;SwiGLU 用门控把前馈层的表达能力提上去,同等参数下比 ReLU 版本效果更好;RoPE 把位置信息编码成旋转,天然支持相对位置,是后来做长度外推的基础。
import torch, torch.nn as nn class RMSNorm(nn.Module): def __init__(self, dim, eps=1e-6): super().__init__() self.weight = nn.Parameter(torch.ones(dim)) self.eps = eps def forward(self, x): # 只除以均方根,不减均值:省一次减法,也不需要 bias 项 var = x.pow(2).mean(-1, keepdim=True) return self.weight * x * torch.rsqrt(var + self.eps)eps防止方差为 0 时除零,一般取 1e-6 到 1e-5;weight是可学习的缩放向量,让归一化之后仍保留逐维度的调节能力。这段代码只有几行,但它决定了训练时梯度会不会在深层堆叠后被放大或衰减——归一化层选错,训练早期就会出现 loss 抖动甚至发散,这类问题排查起来非常费劲。
RoPE 的实现更值得多说一句,因为它把“位置”变成了 query 和 key 的旋转角度,两个 token 的内积只和它们的相对距离有关,这就为后面的长度外推留了口子。SwiGLU 则把前馈层拆成两路,一路过激活函数,另一路做门控相乘,中间维度通常取隐藏维度的约 8/3 再对齐到整数,这个系数直接决定前馈层的参数量,也是不同模型之间参数差异的主要来源之一。
3.2 QKV bias:一个偏置项带来的收益与代价
和同构模型相比,这套结构有一处明确差异:在 query、key、value 的线性投影上保留偏置项,而多数线性层和归一化层不带偏置。这个改动很小,但它有具体的动机——注意力里的 QKV 投影是模型区分“该关注谁”的核心通道,加偏置相当于给每个通道一个初始的基准值,让模型在训练早期更容易学出有区分度的注意力分布,对长序列上的稳定性也有帮助。
代价也很清楚:三个投影矩阵各多出一组与隐藏维度等长的参数,在 7B、14B 这种规模下这部分增量可以忽略,但如果你在显存极紧张的环境里做推理,或者要做极致的权重量化,这些偏置项需要一并处理,不能只量化权重矩阵而漏掉 bias。常见的翻车是:量化脚本只处理了weight,bias 还是 fp16,结果某些层的数值范围对不上,输出出现系统性偏移。这种问题在短文本上看不出来,长上下文一跑就暴露。
“加不加 bias”在论文里通常一句话带过,但它是典型的工程取舍:收益是训练稳定性和收敛速度,代价是参数、显存和量化复杂度。我一般会这么理解——这类小改动之所以值得写进技术报告,不是因为它们多先进,而是因为它们是被真实训练跑出来的经验,而不是拍脑袋的设计。
3.3 2048 到 8192:NTK 插值为什么比直接拉长靠谱
训练阶段的上下文长度是 2048,推理阶段要支持到 8192,中间差了 4 倍。最朴素的做法是把位置索引直接按比例缩放,但这样会破坏 RoPE 的频率结构,模型在训练时没见过的位置分布上表现会明显掉。原文采用的方式是 NTK 感知插值:不去改位置索引,而是调整 RoPE 的基底频率,让高频维度保持原样、低频维度被拉伸,从而在更长序列上维持相对位置关系的分辨率。
import torch def ntk_inv_freq(dim, base=10000.0, train_len=2048, target_len=8192): # NTK 感知插值:按维度改写 base,而不是直接缩放位置索引 scale = target_len / train_len new_base = base * (scale ** (dim / (dim - 2))) idx = torch.arange(0, dim, 2).float() / dim return 1.0 / (new_base ** idx) # 与标准 RoPE 的 inv_freq 形状一致,可直接替换dim是注意力头的维度,scale是目标长度与训练长度之比,指数dim/(dim-2)是关键——它让不同维度被拉伸的程度不同,高频维度几乎不变,低频维度被显著拉长。这样做的效果是:短距离的位置关系仍然精确,长距离的位置关系被平滑外推,模型不需要重新训练就能处理更长的输入。而一旦scale设得过大,低频维度的周期会超出训练分布太多,模型对远距离的注意力就会变得模糊,表现出来就是“能读进去但抓不住重点”。
3.4 训练工程:FlashAttention、张量并行与显存账
训练侧的几个关键选择:注意力用 FlashAttention 实现,通过分块计算避免把完整的注意力矩阵写回显存;并行策略上用张量并行切分权重,配合零冗余优化器降低每张卡上的优化器状态占用。这几项加起来,才让 3 万亿 token 这个量级的训练在工程上可行。
这里值得说清一个常被误解的点:FlashAttention 优化的是显存访问和中间结果的存储,不改变注意力的数学结果;张量并行切的是矩阵乘法的维度,通信开销集中在层内的 all-reduce。两者组合的实际瓶颈往往不在算力,而在通信和显存带宽。所以真正决定训练能不能跑下去的,是 batch 大小、序列长度和并行度三者的匹配关系——序列拉长,激活值显存按平方增长,就得靠梯度检查点或更细的并行来换。这部分报告里通常只给结论,但它是你复现或继续预训练时最先撞上的墙。
4. 对齐链路:SFT、奖励模型、PPO 三段各自解决什么
4.1 监督微调:数据质量比数据量更决定上限
对齐的第一步是监督微调,用的是人工标注的指令数据,覆盖任务执行、对话、工具使用、Agent、安全等方向。这一步的目标不是教模型新知识,而是教它“怎么回答”——输出格式、语气、拒答边界、多轮对话的上下文组织,都在这一阶段定型。
微调数据里最值钱的其实是配比和难度分布。全是简单问答,模型会变得啰嗦且不敢下判断;全是复杂推理,模型在小问题上容易过度展开。常见的做法是按任务类型分层采样,再对高价值类型做上采样。另一个容易被忽略的细节是损失掩码:训练时只在回答部分计算 loss,prompt 部分要屏蔽掉,否则模型会学着去“复述问题”而不是“回答问题”。这个坑非常隐蔽,因为 loss 曲线看起来一切正常,只是生成时总带着问题的影子。
def build_labels(input_ids, response_start, ignore_index=-100): # 只对回答段计算损失:prompt 段全部置为 ignore_index labels = [ignore_index] * len(input_ids) for i in range(response_start, len(input_ids)): labels[i] = input_ids[i] return labelsresponse_start是回答第一个 token 的位置,ignore_index=-100是 PyTorch 交叉熵默认忽略的标记。这个函数只有几行,但它决定了模型学到的到底是“对话策略”还是“文本续写”。
4.2 奖励模型:为什么不能让 SFT 模型自己给自己打分
SFT 之后接的是强化学习,而强化学习需要一个可优化的目标函数。人类偏好没法直接求导,所以要先训一个奖励模型:用同一批 prompt 生成多个回答,让人工标注排序,再用排序数据训练一个打分器,让它学会模仿人类的偏好判断。这一步的价值在于把“人觉得哪个回答更好”这种模糊判断,压缩成一个可以对任意回答打分的标量函数。
奖励模型的质量直接决定 RLHF 的天花板。如果标注数据里存在系统性偏好,比如标注者普遍偏爱更长的回答,奖励模型就会把“长”当成“好”,后面的 PPO 会把这种偏差放大成模型行为——回答越来越长、越来越绕,但实际信息量没增加。这是 RLHF 最典型的翻车方式之一,业内常说的“奖励黑客”指的就是模型找到了讨好打分器但不符合真实意图的路径。
另一个现实约束是成本。奖励模型要单独训练、单独部署,PPO 阶段还要同时跑策略模型、参考模型、奖励模型、价值模型四套权重,显存开销远超 SFT。这也是为什么很多团队在小规模场景下会选择跳过 RLHF,直接用高质量的 SFT 数据逼近效果——取舍点在于:你有多少标注预算,以及你的业务对“回答风格”的敏感程度有多高。
4.3 PPO 阶段的取舍:KL 约束是后悔药
PPO 阶段的目标函数里有一个不能省的项:策略模型输出分布与参考模型(通常是 SFT 模型)之间的 KL 散度惩罚。它的作用是防止模型在追求高奖励的路上跑得太远,把语言能力本身弄坏。KL 系数调大,模型行为保守、变化小;调小,模型更敢冒险,但容易出现语言退化、答非所问、甚至开始输出乱码。
这个系数的调法没有通用解,只能按业务反馈调。我一般会这么理解:KL 惩罚本质上是给模型留的后悔药,它保证模型在优化偏好目标的同时,不会忘记“怎么说话”这件事。很多团队在 PPO 上翻车,不是算法写错了,而是 KL 系数和奖励归一化没配好,导致训练早期奖励涨得很快、后期生成质量断崖式下跌,而且这种退化在自动指标上未必看得出来。
4.4 代码与数学专精模型:继续预训练 + SFT 的两步走
除了通用模型,这套系列还派生出了代码和数学两个专精方向。代码方向的路径是在大量代码语料上做继续预训练,再做面向代码生成、调试、解释的对话微调;数学方向则是在基础模型上做面向数学推理的监督微调,重点在解题步骤的组织。两条路径的共同点是:都建立在基础模型之上,而不是从零训练。
这个选择背后的逻辑是,通用预训练已经把语言理解和基础推理能力打好了底,继续预训练只是在这个底子上做领域偏移,成本远低于重训。代价是领域偏移会带来通用能力的回退——代码模型在通用对话上往往不如同尺寸的通用模型,数学模型在开放式问题上也容易过度形式化。这一点在实际选型时非常关键:专精模型不是“更强的通用模型”,而是“在特定分布上更锋利的工具”。
从评测结果看,这两个方向在各自的基准上明显超过同尺寸开源模型,但和最强的闭源模型仍有差距。这个差距的来源,一部分在数据规模和配比,一部分在后续的对齐深度——专精模型的 SFT 数据更集中在领域内,通用对话的偏好对齐投入相对少。
5. 排查:这套模型落地时最容易翻车的五个地方
5.1 现象:中文输入 token 数远超预期,成本和延迟双高
原因通常不在模型,而在分词器版本对不上。词表不同,同一段中文切出来的 token 数可能差出 30% 以上,上下文窗口的“实际容量”也跟着缩水。
解决方式是先用第 2.3 节那段脚本,拿真实业务语料测一遍压缩率,确认加载的是正确的词表文件,再决定 prompt 的长度预算。如果中文压缩率明显偏低,长文档场景要考虑换更适配的词表,或者调整分块策略。
5.2 现象:把上下文从 2048 拉到 8192 后,模型开始答非所问
原因多半是只改了推理时的最大长度参数,没有同步调整 RoPE 的基底频率。位置分布超出了训练时的范围,注意力权重变得平坦,模型抓不住关键信息。
解决方式是按 3.3 节的公式重算inv_freq,并且用长文档问答、多跳检索这类任务做验证,而不是只看困惑度。困惑度在长度外推失效时可能仍然正常,但下游任务的表现会明显掉。
5.3 现象:微调后模型说话变得啰嗦,且总是复述问题
原因是 SFT 时没有对 prompt 段做损失掩码,模型把“复述问题”当成了回答的一部分学进去。
解决方式是在构造 labels 时把 prompt 段置为 ignore_index,并且在训练后专门抽一批样本检查输出结构。这个问题的隐蔽之处在于 loss 曲线完全正常,只有人工看生成结果才能发现。
5.4 现象:代码或数学专精模型在通用对话上明显变差
原因是继续预训练带来的领域偏移,以及领域 SFT 数据的分布过窄。这不是 bug,是设计上的必然取舍。
解决方式是按场景选模型:通用对话走通用版本,代码补全、调试、数学解题走专精版本;如果必须在同一个入口里覆盖两类需求,就要么做路由,要么在专精模型上补一轮通用对话的 SFT 来缓解回退。
5.5 现象:榜单分数很高,业务效果却不达预期
原因通常是评测集和业务分布不匹配,或者评测数据存在污染——训练语料里已经包含测试题,分数虚高。
解决方式是自建一套贴近业务的验证集,从真实请求里采样,并且定期更新;同时检查训练数据的去重流程是否覆盖了公开评测集的题目。榜单只能用来做粗筛,不能替代业务验证。
6. 设计取舍:为什么先开这两个尺寸,以及你该怎么用这份资料
这套系列开源的是 14B 和 7B 两个参数规模的基础模型和对齐后的对话模型。这个选择本身就是一个明确的设计判断:更大的模型能刷更高的榜,但开发者和应用侧真正用得起的,是能在单机多卡甚至单卡上跑起来的尺寸。7B 和 14B 覆盖了从实验验证到小规模上线的区间,同时保留了继续预训练和微调的空间。
把这份技术资料当文档读,收获有限;当成一份工程决策记录来读,价值就出来了。它的结构其实是一条完整的链路:数据怎么筛、词表怎么定、结构怎么调、对齐怎么做、专精模型怎么派生。每一环都有明确的动机和明确的代价,而且这些代价大多不会写在摘要里,只藏在小节和附录里。
下面这张表是我读完之后的取舍速查,按“改动点—收益—代价”整理:
| 决策点 | 收益 | 代价 |
|---|---|---|
| 15 万级 BPE 词表 | 中文与代码压缩率高,长文本更省窗口 | embedding 与输出层参数变大,小显存环境更吃紧 |
| QKV 保留 bias | 训练早期注意力更稳,收敛更顺 | 量化与显存优化时需额外处理 bias |
| NTK 感知插值 | 免重训即可外推到 4 倍长度 | 外推倍数过大时远距离注意力模糊 |
| SFT + 奖励模型 + PPO | 回答风格和安全性显著提升 | 四套权重同时驻留,显存与标注成本高 |
| 代码/数学继续预训练 | 领域基准上明显领先同尺寸开源模型 | 通用对话能力回退,需要路由或补训 |
想验证这些取舍是否成立,最直接的办法是做一次消融:在同样的小规模数据上,分别关掉词表优化、关掉 QKV bias、把 NTK 换回线性缩放,看下游任务的表现差多少。这类实验不需要大算力,但能让你对每个决策的实际权重有体感,而不是停留在“报告说它好”。
有一个教训我在几个项目里反复验证过:新模型上线前,先拿两三百条真实业务样本走一遍分词、模板、损失掩码、长度外推这套流程,再谈效果。这一步花不了半天,却能提前挡掉后面大部分的线上问题。从那以后我每次接入新模型,都会强制先跑一遍这条验证链路,再去看榜单分数。希望帮到你。
本文还有配套的精品资源,点击获取