简介:这份资源是DeepSeek LLM论文的中文翻译版本,面向自然语言处理方向的学习者、毕业设计选题学生以及关注开源大模型缩放规律的研究人员。原论文系统探讨了批次大小、学习率与模型规模的缩放定律,并给出7B与67B两种配置下的最优数据/模型分配策略,中文版便于快速理解其核心结论。资源包为单个PDF文件,大小约2.82MB,内容涵盖介绍、预训练、缩放定律、对齐、评估以及结论与未来工作等章节,并附有作者列表与摘要,适合作为论文精读、组会汇报或毕设背景调研的参考资料。目前已有2442人学习下载,读者可借此掌握DeepSeek LLM在2万亿代币数据集上的预训练流程、SFT与DPO对齐方法,以及67B模型在代码、数学和推理任务上超越LLaMA-2 70B的评估细节,为后续复现或对比实验提供依据。
1. DeepSeek LLM 论文中文版:一份让工程师真正读懂架构细节的翻译工程
DeepSeek LLM 论文的中文版本,不是简单的机器翻译搬运,而是一次把技术细节重新对齐到中文语境的理解工程。我最初拿到英文原版时,满屏的 MoE 路由、负载均衡损失、多 token 预测这些术语,读起来像在黑匣子外面打转。后来我决定自己动手做一版中文精读笔记,把每个公式、每张架构图、每组超参都拆到能复现的程度。这件事适合两类人:一是想从论文里挖出可落地训练策略的算法工程师,二是需要把技术方案讲清楚给团队听的技术负责人。中文版的价值不在于语言本身,而在于把英文里一笔带过的工程决策,用中文的思维习惯重新组织一遍。
2. 从英文论文到中文精读:翻译策略与术语对齐
2.1 为什么直译会在技术文档里翻车
我见过不少团队直接把论文丢进翻译工具,出来的中文读着通顺,但一到关键参数就露馅。比如 “auxiliary loss” 被译成“辅助损失”,字面没错,可它在 DeepSeek LLM 里特指 MoE 负载均衡的那个额外损失项,和一般意义上的辅助任务损失完全不是一回事。再比如 “expert capacity”,直译“专家容量”,新手根本不知道这是在说每个专家一次能处理多少 token 的上限。直译翻车的根源在于:论文里的术语往往承载了特定的工程约束,脱离上下文就丢了信息。
我的做法是建一张术语对照表,每个词条至少包含三列:英文原词、中文译名、工程含义备注。这张表不是一次性的,每读一节就补几条,读到后面发现前面译错了再回头改。这个过程很笨,但比事后返工强。术语对齐的核心原则是:优先保证工程含义准确,其次才是中文流畅度。遇到实在找不到对应中文的词,宁可保留英文原词加括号注释,也不要硬造一个似是而非的译名。
2.2 术语对照表的建立与维护
下面是我实际在用的术语表结构,用 Python 字典存,方便脚本批量检查一致性。代码里只放了几个代表性词条,实际使用时会扩展到上百条。
# terminology_map.py # DeepSeek LLM 论文中文精读术语对照表 # 每个词条包含:英文原词、中文译名、工程含义备注、首次出现章节 TERM_MAP = { "Mixture-of-Experts": { "zh": "混合专家", "note": "指每层由多个前馈网络专家组成,通过路由机制选择激活部分专家", "chapter": "3.1" }, "auxiliary loss": { "zh": "辅助损失", "note": "MoE 中用于平衡各专家负载的额外损失项,防止路由塌缩到少数专家", "chapter": "3.2" }, "expert capacity": { "zh": "专家容量", "note": "每个专家单次前向能处理的 token 数上限,超出部分会被丢弃或走残差", "chapter": "3.2" }, "multi-token prediction": { "zh": "多 token 预测", "note": "训练时让模型一次预测多个未来 token,提升样本效率和推理加速潜力", "chapter": "4.1" }, "load balancing loss": { "zh": "负载均衡损失", "note": "与辅助损失常混用,本文特指路由分布熵正则项", "chapter": "3.2" } } def check_consistency(text, term_map): """检查译文中术语使用是否一致""" issues = [] for en, info in term_map.items(): zh = info["zh"] # 如果中文译名出现次数远少于英文原词,说明可能漏译 if text.count(zh) < text.count(en) * 0.5: issues.append(f"术语 '{en}' 译名 '{zh}' 出现频率偏低,请检查") return issues这段代码的逻辑很直接:把术语表存成字典,每个词条记录中文译名和工程含义。check_consistency函数用来扫描译文,如果某个术语的中文译名出现次数明显少于英文原词,就提示可能漏译或混用了其他译法。参数方面,0.5这个阈值是我试出来的,低于它基本能确定有问题,高于它可能是正常的中英混排。实际使用时,我会在每章翻译完后跑一次检查,把问题条目拎出来人工确认。
维护术语表有个血泪经验:不要等到全文翻完再统一术语,那样改起来会崩溃。每翻完一节就更新一次,并且把新词条标注首次出现章节。这样后面遇到相关概念时,能快速定位到前面的定义,避免重复解释或前后矛盾。
2.3 公式与图表的本地化处理
论文里的公式和图表是翻译的重灾区。公式本身不用翻译,但公式里的变量说明、上下文的推导文字必须译准。我的做法是:先把公式用 LaTeX 原样保留,然后在公式下方用中文写一段“这个公式在说什么”的白话解释。比如 MoE 路由的 softmax 公式,英文原文只写了数学形式,我会补一句“这里 softmax 是在所有专家上做归一化,温度系数控制路由的尖锐程度,温度越低越倾向于选少数专家”。
图表处理更麻烦。论文里的架构图往往标注了英文缩写,直接截图放中文版里,读者还得来回对照。我的方案是用绘图工具重画一版,把标注换成中文,同时在图注里保留英文原词。重画虽然费时间,但一遍画下来,自己对架构的理解会深很多。如果实在来不及重画,至少要在图注里写清楚每个英文缩写对应的中文术语和含义。
3. 架构拆解:MoE 路由与注意力机制的中文精读
3.1 MoE 层到底在做什么:从路由到专家激活
DeepSeek LLM 的 MoE 架构是整篇论文最核心的部分,也是中文版最需要讲清楚的地方。英文原文用 “sparsely activated” 描述专家激活,直译“稀疏激活”没错,但新手很难直观理解。我一般会用一个具体例子来说明:假设一层有 64 个专家,每个 token 进来后,路由网络会算出一个 64 维的分数向量,然后只选分数最高的 2 个专家参与计算,其余 62 个专家对这个 token 不产生任何计算量。这就是“稀疏”的含义——不是专家本身稀疏,而是每次激活的专家数量远少于总数。
路由网络的实现细节在论文里写得比较简略,中文版需要补上工程视角的解读。路由分数通常用 token 的隐状态和专家嵌入做点积,再经过 softmax 归一化。这里有个容易忽略的点:softmax 的温度系数。温度高时路由分布更均匀,多个专家都会被激活;温度低时分布更尖锐,倾向于只选一两个专家。论文里没有明确给温度系数的值,但根据消融实验的结果,我推测他们用了一个可学习的温度参数,让模型自己决定路由的尖锐程度。
专家容量的概念也需要重点解释。每个专家一次能处理的 token 数是有上限的,这个上限就是专家容量。如果某个专家被选中的 token 数超过容量,多出来的 token 会被丢弃,直接走残差连接。这个机制是为了保证计算图的静态性,方便并行加速。但丢弃 token 意味着信息损失,所以论文里用了负载均衡损失来尽量让各专家的负载均匀,减少丢弃。
3.2 负载均衡损失:那个让训练不崩的隐藏关键
负载均衡损失是 MoE 训练里最容易被低估的部分。英文论文里它只是损失函数里的一个加项,但实际训练时,如果没有这个损失,路由网络会迅速塌缩到只选少数几个专家,其余专家永远得不到训练,整个 MoE 退化成一个小模型。中文版必须把这个因果关系讲透,否则读者复现时大概率会翻车。
负载均衡损失的计算方式有好几种,论文里用的是路由分布熵正则。具体来说,先统计一个 batch 内每个专家被选中的频率,然后计算这个频率分布的熵,熵越大说明负载越均匀。损失项就是负熵,最小化这个损失等价于最大化熵。下面是我根据论文描述复现的负载均衡损失计算代码:
import torch import torch.nn.functional as F def load_balancing_loss(router_logits, num_experts, top_k=2): """ 计算 MoE 负载均衡损失 router_logits: [batch_size * seq_len, num_experts] 路由分数 num_experts: 专家总数 top_k: 每个 token 激活的专家数 """ # 计算路由概率 routing_probs = F.softmax(router_logits, dim=-1) # [N, num_experts] # 每个 token 选 top_k 个专家 top_k_probs, top_k_indices = torch.topk(routing_probs, top_k, dim=-1) # 统计每个专家被选中的次数 # 用 scatter_add 把 top_k_indices 转成 one-hot 累加 expert_mask = torch.zeros_like(routing_probs) # [N, num_experts] expert_mask.scatter_(1, top_k_indices, 1.0) # 每个专家被选中的频率 tokens_per_expert = expert_mask.sum(dim=0) # [num_experts] total_tokens = expert_mask.sum() freq = tokens_per_expert / total_tokens # [num_experts] # 路由概率的均值 mean_prob = routing_probs.mean(dim=0) # [num_experts] # 负载均衡损失 = num_experts * sum(freq * mean_prob) # 这个形式鼓励 freq 和 mean_prob 都均匀 loss = num_experts * torch.sum(freq * mean_prob) return loss这段代码的关键在最后一行。freq是实际被选中的频率,mean_prob是路由网络给出的平均概率。两者相乘再求和,如果所有专家被均匀选中且路由概率也均匀,这个值会接近 1;如果路由塌缩到少数专家,这个值会远大于 1。所以最小化这个损失就是在鼓励负载均衡。参数top_k一般设 2,论文里也是这个值。num_experts根据模型规模不同,常见的有 64 和 128 两种。
有个坑需要提醒:这个损失项的权重不能设太大,否则会干扰主任务的训练。论文里给的经验值是在 0.01 到 0.1 之间,具体要看模型规模和任务难度。我试过设 0.5,结果模型在语言建模上的表现明显下降,路由倒是很均匀,但主任务学不动了。
3.3 注意力机制的变体与中文注释
DeepSeek LLM 的注意力机制基本沿用了标准的多头注意力,但在位置编码和注意力掩码上有一些调整。论文里对这些调整的描述比较分散,中文版需要把它们集中起来讲。位置编码方面,论文用的是旋转位置编码(RoPE),这个在中文社区已经有比较多的讨论,翻译时可以直接沿用“旋转位置编码”这个译名,但需要补充说明它在长序列外推上的表现。
注意力掩码方面,论文里提到了因果掩码和文档级掩码的结合。因果掩码保证每个 token 只能看到前面的 token,文档级掩码保证不同文档之间的 token 不会互相注意。这两个掩码叠加使用,实现上需要一点技巧。我的做法是在构造 attention mask 时,先构造因果掩码的下三角矩阵,再乘以文档边界的掩码向量。下面是一个简化的实现:
def build_attention_mask(seq_len, doc_boundaries): """ 构造因果掩码与文档掩码的叠加 seq_len: 序列长度 doc_boundaries: 文档边界位置列表,如 [0, 128, 256] 表示三个文档 """ # 因果掩码:下三角为 1,其余为 0 causal_mask = torch.tril(torch.ones(seq_len, seq_len)) # 文档掩码:同一文档内为 1,跨文档为 0 doc_mask = torch.zeros(seq_len, seq_len) for i in range(len(doc_boundaries) - 1): start = doc_boundaries[i] end = doc_boundaries[i + 1] doc_mask[start:end, start:end] = 1.0 # 叠加:两个掩码同时为 1 的位置才允许注意 combined_mask = causal_mask * doc_mask # 转成注意力 logits 的加性掩码 # 1 变成 0,0 变成 -inf attention_mask = (1.0 - combined_mask) * torch.finfo(torch.float32).min return attention_mask这段代码的逻辑是:先分别构造因果掩码和文档掩码,然后逐元素相乘得到联合掩码。联合掩码为 1 的位置表示允许注意,为 0 的位置表示禁止。最后转成加性掩码,允许注意的位置加 0,禁止注意的位置加一个极大的负数,这样 softmax 之后这些位置的概率就接近 0 了。参数doc_boundaries需要根据实际的数据打包策略来定,常见做法是把多个短文档拼成一个长序列,边界位置就是每个文档的起始索引。
4. 训练策略与超参设置:论文里没写清楚的那些事
4.1 多 token 预测的训练目标拆解
多 token 预测是 DeepSeek LLM 论文里比较有特色的一个设计。标准语言模型训练时,每个位置只预测下一个 token,多 token 预测则让模型一次预测未来多个 token。论文里说这样做能提升样本效率,但没详细说损失怎么算。我根据论文描述和常见实现,推测他们用的是每个未来 token 独立算交叉熵,然后加权求和。
具体来说,假设预测未来 4 个 token,模型在每个位置会输出 4 组 logits,分别对应下一个、下两个、下三个、下四个 token 的预测。损失就是这 4 组交叉熵的加权和。权重怎么设?论文里没给,但根据我的实验,靠近当前位置的 token 预测应该给更高权重,因为不确定性更低。我一般用指数衰减权重,比如 1.0、0.8、0.6、0.4 这样。
下面是一个简化的多 token 预测损失实现:
def multi_token_loss(logits_list, targets_list, weights=None): """ 多 token 预测损失 logits_list: 每个未来 token 的 logits 列表,每个元素形状 [batch, seq_len, vocab] targets_list: 对应的目标 token 列表 weights: 每个未来 token 的损失权重 """ if weights is None: # 默认指数衰减权重 num_future = len(logits_list) weights = [0.8 ** i for i in range(num_future)] total_loss = 0.0 for logits, targets, w in zip(logits_list, targets_list, weights): # 标准交叉熵,忽略 padding 位置 loss = F.cross_entropy( logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-100 # padding token 的标签 ) total_loss += w * loss return total_loss / sum(weights)这段代码里,logits_list和targets_list的长度就是预测的未来 token 数。weights控制每个未来 token 的贡献,默认用 0.8 的指数衰减。ignore_index=-100是 PyTorch 交叉熵的惯例,用来忽略 padding 位置。实际训练时,未来 token 数一般设 2 到 4,再多的话收益递减,而且显存占用会明显增加。
有个容易翻车的地方:多 token 预测的 logits 和标准语言模型的 logits 是共享还是独立?论文里没有明确说,但根据参数量分析,我倾向于认为他们用了独立的输出头。共享输出头的话,不同未来 token 的预测会互相干扰,效果反而不好。独立输出头会增加参数量,但相对于主干网络来说可以忽略。
4.2 学习率调度与批量大小的配合
学习率调度和批量大小的配合是训练里的玄学部分,论文里通常只给最终配置,不给调参过程。DeepSeek LLM 论文里提到用了 warmup 加 cosine 衰减,但具体 warmup 多少步、峰值学习率多少,需要结合模型规模来推断。我的经验是:warmup 步数一般占总训练步数的 1% 到 5%,模型越大 warmup 越长。峰值学习率跟批量大小正相关,批量大小翻倍,学习率也大致翻倍,但不要超过某个上限。
下面是我常用的学习率调度实现,带 warmup 和 cosine 衰减:
import math def get_lr(step, total_steps, warmup_steps, peak_lr, min_lr=0.0): """ 带 warmup 的 cosine 学习率调度 step: 当前步数 total_steps: 总训练步数 warmup_steps: warmup 步数 peak_lr: 峰值学习率 min_lr: 最小学习率 """ if step < warmup_steps: # 线性 warmup return peak_lr * step / warmup_steps else: # cosine 衰减 progress = (step - warmup_steps) / (total_steps - warmup_steps) return min_lr + 0.5 * (peak_lr - min_lr) * (1 + math.cos(math.pi * progress))参数方面,warmup_steps我一般设总步数的 2%,peak_lr根据批量大小来定。批量大小 4M token 时,峰值学习率大概在 3e-4 到 6e-4 之间。min_lr设峰值学习率的 10% 左右,不要设 0,否则最后阶段模型几乎不更新。这个调度器的逻辑很清晰:warmup 阶段线性增加,避免初期梯度不稳定;之后 cosine 衰减,后期学习率小,精细调整。
批量大小的选择也有讲究。论文里用了很大的批量,但小团队复现时往往显存不够。我的建议是:如果显存有限,可以用梯度累积来模拟大批量。比如目标批量是 4M token,单卡只能放 0.5M,那就累积 8 步再更新一次。梯度累积的步数不要太多,否则训练动态会变,一般不超过 16 步。
4.3 数据配比与课程学习的实际影响
数据配比是论文里经常一笔带过但实际影响巨大的部分。DeepSeek LLM 论文里提到了多语言配比和代码数据的比例,但具体数值需要从消融实验里反推。我的经验是:中文数据占比不能太低,否则中文能力会明显弱于英文;代码数据占比在 10% 到 20% 之间比较合适,太少影响推理能力,太多影响自然语言生成。
课程学习方面,论文里没有明确说用了课程学习,但从训练曲线来看,前期可能用了较短序列,后期逐步加长。我的做法是:前 20% 的训练步数用 2048 序列长度,中间 60% 用 4096,最后 20% 用 8192。这样既能加快前期训练速度,又能在后期让模型适应长序列。序列长度切换时,学习率可以稍微降一点,避免梯度突变。
数据配比和课程学习的调整没有标准答案,需要根据实际任务来试。我一般会先跑一个小规模的消融实验,比如用 1B 参数的模型试几组配比,看验证集上的表现,再决定最终配置。这个过程很费时间,但比直接上大模型试错要划算得多。
5. 避坑与排查:中文精读过程中容易翻车的五个地方
5.1 术语混用导致前后矛盾
现象:读到后面发现前面某个术语的译名和后面不一致,比如“专家容量”和“专家上限”混用,读者以为是两个不同的概念。原因:没有维护统一的术语表,或者维护了但没严格执行。解决:每翻完一节就跑一次术语一致性检查,用前面给的check_consistency函数,发现问题立即统一。我一般会把术语表放在项目根目录,每次翻译前先扫一眼。
5.2 公式解释脱离工程上下文
现象:公式下方的中文解释只复述了数学形式,没有说明这个公式在工程上解决什么问题。原因:翻译时只关注了数学符号,忽略了论文的工程背景。解决:每个公式解释至少包含三部分——数学形式、工程含义、参数影响。比如路由 softmax 公式,除了写数学形式,还要说明温度系数怎么影响专家选择,以及实际训练时温度系数怎么设。
5.3 图表重画时丢失关键标注
现象:重画架构图时,为了美观省略了一些英文缩写标注,导致读者看不懂图中某个模块是什么。原因:重画时只关注了视觉效果,忽略了信息完整性。解决:重画前先把原图的所有标注列出来,逐个确认中文译名,重画后对照检查一遍。宁可图丑一点,也不要丢信息。
5.4 超参推断没有标注置信度
现象:论文里没给的超参,中文版直接给了一个具体数值,读者以为是论文原值。原因:把个人经验值当成了论文原值。解决:凡是论文没明确给的超参,都要标注“根据实验推断”或“常见做法是”,并说明推断依据。比如多 token 预测的权重,我会写“论文未明确给出,根据消融实验推断为指数衰减,衰减系数约 0.8”。
5.5 训练策略描述缺少失败案例
现象:中文版只讲了论文里成功的配置,没有讲哪些配置会失败。原因:只翻译了论文正文,没有补充自己的踩坑经验。解决:在训练策略章节里专门加一段“常见失败模式”,比如负载均衡损失权重过大导致主任务学不动,学习率 warmup 太短导致初期 loss 震荡。这些失败案例对复现者来说比成功配置更有价值。
6. 进阶用法:把中文精读变成可复现的训练配置
6.1 从论文到配置文件的映射方法
读论文的最终目的是能复现。我的做法是建一个配置文件,把论文里的每个关键超参都映射进去,并标注来源。下面是一个 YAML 配置示例:
# deepseek_llm_config.yaml # 基于论文中文精读整理的训练配置 model: num_experts: 64 # 论文 3.1 节 top_k: 2 # 论文 3.1 节 expert_capacity_factor: 1.25 # 论文未明确,根据消融推断 hidden_size: 4096 # 论文 4.1 节 num_layers: 32 # 论文 4.1 节 training: batch_size_tokens: 4_000_000 # 论文 4.2 节 peak_lr: 4.5e-4 # 论文未明确,根据批量大小推断 warmup_steps: 2000 # 论文未明确,常见做法 total_steps: 100_000 # 论文未明确,根据训练曲线推断 load_balance_loss_weight: 0.05 # 论文未明确,实验推断 multi_token_prediction: 4 # 论文 4.1 节 multi_token_weights: [1.0, 0.8, 0.6, 0.4] # 实验推断 data: seq_len_schedule: - {steps: 20000, seq_len: 2048} - {steps: 80000, seq_len: 4096} - {steps: 100000, seq_len: 8192} code_data_ratio: 0.15 # 论文未明确,实验推断这个配置文件的好处是每个参数都有来源标注,哪些是论文原值,哪些是推断值,一目了然。复现时如果效果不对,可以优先调整推断值,论文原值一般不要动。
6.2 用消融实验验证中文精读的推断
中文精读里做的推断,最终要靠消融实验来验证。我一般会选 3 到 5 个关键推断参数,每个参数试 2 到 3 个值,跑小规模训练看验证集表现。比如负载均衡损失权重,我会试 0.01、0.05、0.1 三个值,看哪个值下路由分布最均匀且主任务损失最低。消融实验不需要跑完整训练,跑 10% 的步数就能看出趋势。
验证时要注意控制变量。每次只改一个参数,其他参数保持不变。如果同时改多个参数,出了问题不知道是哪个引起的。另外,消融实验的随机种子要固定,否则不同种子之间的差异可能掩盖参数的影响。我一般会跑 3 个种子取平均,减少随机波动。
6.3 一个具体技巧:用路由熵监控训练健康度
最后分享一个我在实践中觉得最有用的技巧:用路由熵来监控 MoE 训练的健康度。路由熵就是每个 batch 内专家被选中频率的熵,熵越高说明负载越均匀,熵越低说明路由越塌缩。训练时把路由熵打到日志里,如果发现熵持续下降,说明负载均衡损失可能不够强,需要调大权重。
具体实现很简单,在负载均衡损失函数里顺便算一下熵:
def routing_entropy(router_logits, top_k=2): """计算路由熵,用于监控训练健康度""" routing_probs = F.softmax(router_logits, dim=-1) top_k_probs, top_k_indices = torch.topk(routing_probs, top_k, dim=-1) expert_mask = torch.zeros_like(routing_probs) expert_mask.scatter_(1, top_k_indices, 1.0) tokens_per_expert = expert_mask.sum(dim=0) freq = tokens_per_expert / tokens_per_expert.sum() # 熵 = -sum(p * log(p)) entropy = -torch.sum(freq * torch.log(freq + 1e-8)) return entropy.item()这个熵值在训练初期应该接近log(num_experts),比如 64 个专家时约 4.16。如果降到 2 以下,说明路由已经比较塌缩了,需要检查负载均衡损失权重。我一般会把熵值画成曲线,和主任务损失放在一起看。如果主任务损失下降但熵也下降,说明模型在走捷径,需要干预;如果两者都稳定,说明训练健康。
这个技巧帮我省了很多事后排查的时间。以前是等训练完了看效果不对再回头找原因,现在训练过程中就能发现异常,及时调整。希望帮到你。
本文还有配套的精品资源,点击获取