简介一份面向算法工程师、AI研究者与深度学习初学者的DeepSeek全流程实操指南系统讲解分层预训练、参数高效融合微调与蒸馏模型低比特量化帮助读者从模型架构理解到训练推理落地建立完整认知。资源为单个PDF文件共231页大小约11.62MB文档支持目录跳转和书签大纲快速定位目前已有335人学习。全书共50个大章节内容覆盖DeepSeek技术生态、数据筛选清洗与预处理、算力规划、超参数调优、掩码策略、梯度累积与混合精度训练、检查点管理与断点续训、损失函数设计、监控体系以及数据标注规范、多模态标注、分布式训练、过拟合抑制、动态采样、学习率调度与硬件性能优化等均有实操级讲解。适合需要系统掌握DeepSeek预训练与微调量化落地细节的读者作为案头手册按章节查阅。1. DeepSeek全流程实操一份231页PDF背后的三层工程链路如果你手头只有一张 24GB 显存的卡却想让 DeepSeek 在你们团队的业务数据上真正能用起来光“把权重跑起来”这一步根本不够。开源权重下载下来能用和模型输出能解决业务问题是两回事。把 DeepSeek 从入门练到精通中间横着的其实是三段链路分层预训练做领域适配、Parameter-Efficient 融合微调把行为对齐到业务、蒸馏模型加低比特量化把推理成本压回单卡能扛的范围。这份标题里所谓 231 页的全流程指南说穿了就是把这三段链路从头到尾梳理了一遍。它适合已经具备深度学习基础、想自己从零拉起一套 DeepSeek 服务的算法工程师或平台工程师而不是只想知道怎么调 API 的业务同学。你可以照着这条链路做下来先解决模型“懂不懂你的数据”再解决“听话不听话”最后解决“跑不跑得起”。这三关每关都有明确的参数、命令和坑下面按顺序展开。2. 分层预训练先想清楚“冻结哪几层”再动手2.1 分层预训练的基本盘不是所有层都值得被更新拿到 DeepSeek 这样的开源基座目标一般不是从 tokenizer 开始重新练而是让模型把行业语料里的表达方式、专有名词和长尾知识“补进”自己的参数。最忌讳的就是“一上来就全量训”。全量微调一开推理能力和多轮对话习惯会被灾难性遗忘搅得七零八落训练成本还特别高。分层预训练的思路是把 Transformer 的几十层按阶段分组低层负责词法、句法等通用能力冻结或给极低学习率高层贴近输出分布与任务语义重点更新。这样领域知识的注入集中发生在上层模型底子不会退步。从模型结构角度DeepSeek 跟 Llama/Qwen 这类稠密模型不一样的地方在于它是 MoE注意力和专家网络被拆开。如果你的冻结策略只按“层”来切会漏掉一类关键参数路由 gate。MoE 的每个专家处理什么数据路由层说了算。做分层预训练时通常不冻结 gate 层或者给 gate 单独一个低学习率让领域语料能重新分配专家。这块没有公开的通用标准很多团队就是靠实验标定相当玄学但“低层冻结、高层更新、gate 单独给学习率”这个框架是稳定可行的。我一般会把模型层切成三段前 1/3 冻结中间 1/3 用较小学习率微调最后 1/3 放开训练。如果数据量少中段也可以只解冻注意力层专家层留着后面微调再动。这样做的另一个好处是显存占用冻结层不需要保存梯度在 DeepSpeed ZeRO-3 下能明显降低通信量对单机多卡训练更友好。2.2 基于 DeepSeek 的继续预训练最小脚本下面这段代码是继续预训练的第一步加载模型、按层号冻结、打印实际可训练参数占比。以公开的deepseek-ai/DeepSeek-V2-Lite这类小 MoE 版本做演示思路完全一致。from transformers import AutoModelForCausalLM, AutoTokenizer model_id deepseek-ai/DeepSeek-V2-Lite model AutoModelForCausalLM.from_pretrained(model_id, torch_dtypeauto) tokenizer AutoTokenizer.from_pretrained(model_id) # 按层号做三段分组先冻结前 66% 的层 total_layers model.config.num_hidden_layers freeze_until int(total_layers * 0.66) for name, param in model.named_parameters(): # 参数名形如 model.layers.5.self_attn.q_proj.weight try: layer_idx int(name.split(.)[2]) except (ValueError, IndexError): # 不属于 transformer 层的参数embedding、norm 等默认参与训练 continue if layer_idx freeze_until: param.requires_grad False trainable sum(p.numel() for p in model.parameters() if p.requires_grad) total sum(p.numel() for p in model.parameters()) print(ftrainable params: {trainable / total:.1%})这段代码的核心是“按层号冻结”先通过config.num_hidden_layers拿到总层数再把前 66% 层的所有参数关掉梯度。name.split(.)[2]依赖 transformers 那一层的命名格式所以外面套了try/except命名换了也不会直接把训练跑崩。一个容易忽略的事实embedding 和 lm_head 不在任何model.layers下面上面的代码不会误冻结它们。但注意lm_head是最后输出层继续预训练时一般保持可训练否则领域词汇永远映射不到输出空间。冻结比例从 0.5 起步数据多可以放到 0.75数据少就老实一点冻结多一些。如果想把 gate 单独放进高学习率组需要再加一个分组操作。下面这段是通用的按层位置拆分参数组的写法可以直接放进 Trainer 的optimizer参数构造里from transformers import Trainer from torch.optim import AdamW group_low, group_mid, group_high [], [], [] for name, param in model.named_parameters(): if not param.requires_grad: continue layer_idx int(name.split(.)[2]) if name.split(.)[2].isdigit() else -1 if layer_idx int(total_layers * 0.33): group_low.append(param) elif layer_idx int(total_layers * 0.66): group_mid.append(param) else: group_high.append(param) optimizer AdamW([ {params: group_low, lr: 1e-5}, {params: group_mid, lr: 3e-5}, {params: group_high, lr: 5e-5}, ], weight_decay0.01) trainer Trainer(modelmodel, optimizers(optimizer, None), ...)分组的意义在于低层不是“完全不学”而是让它学得非常慢避免把通用语法冲掉高层学习率拉高让领域语义快速成型。MoE 里的 gate 层位置分散在各层如果你用的是统一分组还需要单独把含gate的参数学出来放到group_low防止路由分布被推得过于极端这是很多翻车现场的直接原因。2.3 三个必调参数冻结比例、数据配比、学习率分段下表是继续预训练阶段我每次都会先定死的三个参数组合。它们之间是耦合的单看某一个意义不大。参数建议取值范围说明冻结比例0.5 ~ 0.75冻结太多学不进去领域知识太少会显著加剧遗忘领域数据 : 通用数据3 : 1 ~ 1 : 1纯领域数据会把模型带偏必须掺通用语料防遗忘三段学习率低层 1e-5 / 中层 3e-5 / 高层 5e-5以 1e-5 为基线MoE 模型整体比稠密模型更敏感数据配比是这三者里最容易被忽视的。很多人以为继续预训练就是“拿领域数据一直喂”结果训练完模型领域术语说得溜数学题和常识问答全面崩盘。原因是通用语料被稀释。我一般会保持至少 30% 的通用数据并且在每个 epoch 里打散混合而不是先领域后通用。学习率分段也不能拍脑袋。继续预训练的常态是 loss 曲线前期快速下降、后期平台期很长。如果你发现领域 loss 降了但验证集的通用任务掉点超过 5%优先怀疑学习率太高而不是数据配比这时把高段学习率从 5e-5 降到 3e-5通常比重新调数据更快见效。warmup 建议拉长到总步数的 10%——预训练阶段的 promise 是“数据量越大warmup 越要长”这和微调阶段的习惯完全相反。3. Parameter-Efficient 融合微调LoRA 为主选型与融合策略3.1 各家 PEFT 横向选型QLoRA、IA3、P-Tuning 在这条链路里的位置分层预训练解决的是“模型懂不懂你的数据”接下来对齐业务行为标准做法落到 PEFTParameter-Efficient Fine-Tuning上。这里把常用方案按“是否适合 DeepSeek 这种 MoE 模型”排一下方案原理显存占用对 MoE 的适配度适用场景LoRA在注意力和 FFN 上注入低秩旁路中高默认首选可离线合并QLoRA基座量化到 4bit 再插 LoRA低中单卡显存吃紧时IA3只学缩放向量最低中数据量极小、快速试探P-Tuning v2在输入侧加可学习前缀中低少样本分类任务不适合生成我一般直接选 LoRA原因有两条一是它训练完可以 merge 回基座部署链路干净二是 MoE 模型里专家网络本身是稀疏激活的QLoRA 把基座压到 4bit 后量化噪声和路由选择叠加在一起结果很不稳定。QLoRA 不是不能用而是你需要花费额外的精力去排查“到底是量化伤了模型还是 LoRA 没学对”这排查成本在工程上一文不值。IA3 适合做 baseline把多个任务各自训一个 IA3 adapter用最少显存验证数据质量。但它容量太小真正上线时撑不住复杂指令我通常只在第一天探索阶段拿来跑通流程后面直接切 LoRA。3.2 用 LoRA 对 DeepSeek 做领域微调代码级实操LoRA 微调 DeepSeek 有一个跟 Llama 系完全不同的点DeepSeek 用的是 MLAMulti-head Latent AttentionQ/K/V 会被压缩到一个低维隐含向量里再展开所以权重名不是标准的q_proj、k_proj、v_proj。如果照抄 Llama 的 target_modules大概率训练时 loss 能降、验证集怎么都不涨。正确做法是先打印模型结构看清你这版权重里的投影层到底叫什么model AutoModelForCausalLM.from_pretrained(model_id, torch_dtypeauto) for name, _ in model.named_modules(): if any(key in name for key in (proj, gate, experts)): print(name)输出里你会看到q_a_proj、q_b_proj、kv_a_proj_with_mqa、kv_b_proj、o_proj这类名字——它们是 MLA 的投影投影层。把 target_modules 对准这些名字LoRA 才真正作用在注意力路径上。下面是完整的最小微调脚本from peft import LoraConfig, get_peft_model, TaskType lora_config LoraConfig( task_typeTaskType.CAUSAL_LM, r64, lora_alpha128, # 以你上一步打印出的实际命名为准以下只是 MLA 常见形态 target_modules[q_a_proj, q_b_proj, kv_a_proj_with_mqa, kv_b_proj, o_proj], lora_dropout0.05, ) lora_model get_peft_model(model, lora_config) lora_model.print_trainable_parameters()几个参数说明。r64是 LoRA 秩。DeepSeek 这类 MoE 模型的路由层对秩不敏感但 MLA 的隐含维度本来就不高秩太大会让旁路矩阵学到冗余噪声。经验和 r32 ~ 64 之间收敛稳定数据量少于 10 万条时不要上 128。lora_alpha128与 r 的比例保持在 2:1 左右这是 PEFT 社区用下来的稳定区间等于直接初始化旁路时就把缩放因子放大两倍收敛更快。target_modules里没有包含gate和experts。微调阶段我不建议对 MoE 的专家网络注入 LoRA。原因是专家层本身是稀疏的如果给每个专家都加一个 LoRA 旁路训练时路由选择一变某些专家长期不被激活对应的 LoRA 矩阵就白训了。gate 层同理保持冻结即可。训练时数据格式用对话模板。DeepSeek 系模型一般都有自己官方对话格式指令和回答之间不能随便用bos分隔。最稳的方式是用transformers自带 tokenizer 的apply_chat_template把消息列表转成训练文本别手拼模板。这步错了模型不会崩只是指令跟随能力一直很奇怪。训练完成后LoRA adapter 单独保存lora_model.save_pretrained(./lora_adapter_domain) tokenizer.save_pretrained(./lora_adapter_domain)adapter 权重很小一般几十 MB 到两百 MB 左右方便后面融合。3.3 多 LoRA 融合并行 adapter 与离线合并两种路线“融合”这个词在这份链路里有两层含义。如果你只是想让多个技能共存比如一个能写代码、一个懂业务术语可以用 vLLM 的动态 LoRA 加载部署时不合并权重按请求路由到不同 adapter如果你希望所有能力都固化进同一份权重方便后续做量化和分发就需要离线合并。vLLM 路线适合在线服务vllm serve ./base_model \ --enable-lora \ --lora-modules code./lora_adapter_code domain./lora_adapter_domain请求里带lora_name参数就能切换。它的优点是 adapter 可以热更新训练完一个新 adapter 直接挂上去不用重启服务。缺点是每个请求都要动态计算 LoRA 分支吞吐量会比合并后低一些。离线合并路线我一般用两步走先把每个 LoRA merge 回基座得到全量权重再用 mergekit 做模型融合# 先把 adapter 合并回基座 python scripts/merge_adapter.py \ --base deepseek-ai/DeepSeek-V2-Lite \ --adapter ./lora_adapter_domain \ --output ./model_after_lora_domain第二步用 mergekit 合并两份同基座权重。配置文件长这样# merge.yaml slices: - model: ./model_after_lora_code - model: ./model_after_lora_domain merge_method: dare_linear base_model: deepseek-ai/DeepSeek-V2-Lite dense_method: magnitude_prune parameters: theta: 100.0mergekit-yaml merge.yaml ./model_fused --copy-tokenizerdare_linear的含义是把两份权重里贡献小的参数直接置零再对剩余参数做加权平均。它跟朴素平均的关键区别就在“置零”这一步能避免两份权重在同一位点上互相拉扯导致模型输出退化。theta100.0是裁剪阈值设得越小裁剪越激进模型能力保留越少但冲突越小。先 100 起调如果合并后模型出现复读或答非所问把 theta 调到 150 到 200 区间重试。需要留个心眼多 LoRA 融合之后一定要回到业务评测集上重新跑一遍别只看合并后 loss。融合操作的目的是减少部署开销能力上限不会比分别加载高能用就行。4. 蒸馏模型与低比特量化把跑得慢的大模型变成可上线的服务4.1 知识蒸馏的设计把 DeepSeek 当 teacher 的落地方案PEFT 微调做完模型的行为已经对齐业务但如果你用的是 DeepSeek 的满血版单卡推理依然吃力。知识蒸馏的目的不是“提升效果”而是把大模型学到的能力迁移到一个更小、更快的骨干上。在这个场景里teacher 是微调好的 DeepSeekstudent 可以选择同架构小模型也可以选 Qwen/Llama 系的小稠密模型后者部署更省心。蒸馏方案我优先推荐 response-based也就是让 teacher 生成高质量回答student 直接拟合这些回答。它比 logits 蒸馏省显存、好实现。原因在于DeepSeek 的输出层词表很大student 要对齐整个词表分布batch 稍微大一点显存就爆而直接拟合回答文本只需要用标准交叉熵。如果你确实要上 logits 蒸馏核心是温度系数和软标签的 KL 散度。一个可以照抄的蒸馏损失函数写法如下import torch import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, temp4.0, hard_weight0.8): # 软标签teacher 的输出分布 soft_targets F.softmax(teacher_logits.detach() / temp, dim-1) soft_loss F.kl_div( F.log_softmax(student_logits / temp, dim-1), soft_targets, reductionbatchmean, ) * (temp ** 2) # 硬标签真实回答的交叉熵 hard_loss F.cross_entropy(student_logits, labels, ignore_index-100) return hard_weight * hard_loss (1 - hard_weight) * soft_loss温度temp4.0是把 teacher 输出“摊软”让 student 学到 token 之间的相似性而不是死记最优解。temp太低软标签退化成 one-hot蒸馏失去意义太高会把噪声也放进去student 学得含糊。hard_weight 控制在 0.7 到 0.9 之间蒸馏是辅助信号硬标签才是主线。蒸馏数据集的生成是关键也是坑最多的地方。常见做法是把业务侧的 prompt 清洗后灌给 teacher温度设 0.7每个 prompt 回复一遍或两遍。生成完必须做质量过滤太短的删掉、包含重复片段的删掉、明显答非所问的删掉。蒸馏训练里最怕 teacher 的幻觉被 student 当标准答案学进去这一步没有捷径只能抽样本人工抽检。4.2 蒸馏后的 4bit 量化GPTQ/AWQ 选型与代码蒸馏出来的 student 模型依然有几十亿参数fp16 推理在消费级显卡上显存吃紧。低比特量化这一步目标是把权重压到 4bit。主流的两个方案是 GPTQ 和 AWQ二者选谁取决于你的部署推理框架和校准数据。GPTQ 基于二阶误差补偿对校准集的依赖比较强AWQ 基于激活值统计选择保留重要通道通常更稳。如果蒸馏后的 student 是标准稠密模型我倾向于 AWQ如果你要配合 vLLM 的生态GPTQ 的兼容性更无脑。两个都跑一遍取效果好的也是常见姿势。用 AutoGPTQ 库做 INT4 量化的最小流程如下from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig from transformers import AutoTokenizer model_path ./student_model_after_distill quantize_config BaseQuantizeConfig( bits4, group_size128, desc_actTrue, ) tokenizer AutoTokenizer.from_pretrained(model_path) model AutoGPTQForCausalLM.from_pretrained(model_path, quantize_configquantize_config) # 校准数据用业务侧真实样本200~500 条即可 calib_samples [ 你们产品的退款流程是什么, 如何配置内网 DNS 服务器, # ... 尽量覆盖线上真实请求 ] encodings tokenizer(calib_samples, return_tensorspt, paddingTrue) model.quantize(encodings) model.save_quantized(./student_model_int4)group_size128表示每个 128 个权重共享一套缩放因子和零点。bits4是权重位宽desc_actTrue开启按激活顺序重排通道精度更好但推理稍微慢一点。这两个参数是精度和速度的平衡点不建议动。校准集怎么选比量化方法本身更影响结果。如果你拿通用语料做校准量完的模型在业务场景会非常飘因为校准集覆盖不到线上长尾分布。我一般从蒸馏数据里直接随机抽 300 条作为校准集不要额外构造。量化完成后放到 vLLM 里拉起服务vllm serve ./student_model_int4 \ --quantization gptq \ --max-model-len 4096 \ --gpu-memory-utilization 0.9vLLM 对 GPTQ 的支持很成熟--quantization gptq指定量化方式即可。需要注意group_size必须和量化时一致vLLM 对某些 group size 的 kernel 支持有限128 是目前兼容面最广的选择。4.3 量化不止权重KV Cache 量化与混合部署很多人做完 weight-only 量化发现长上下文场景下显存还是涨得飞快。问题出在 KV Cache。DeepSeek 系模型的 MLA 已经把 KV 压过一轮但蒸馏后的 student 如果不用 MLA长上下文的 KV Cache 依然是显存大头。vLLM 支持 KV Cache 量化可以在推理时把缓存压到 fp8基本不影响生成质量vllm serve ./student_model_int4 \ --quantization gptq \ --kv-cache-dtype fp8 \ --max-model-len 8192--kv-cache-dtype fp8是显存吃紧时的后悔药。如果你只跑到 4096 上下文可以先不开先把权重量化带来的收益吃透。部署阶段的另一个实际选择是“混合部署”把蒸馏模型作为日常服务遇到超难样本再转发到未量化的 teacher。这个方案在工程上很常见我也一直推荐。它等于用 10% 的流量换来 90% 的精度兜底等于给量化留了后悔药。转发逻辑可以在应用层做模型侧不用改任何东西。低比特量化最容易被忽略的验证项是“量化前后的输出差异”。很多团队只盯 benchmark 分数忽略真实用户在意的回答风格。量化后如果模型回答变短、应变少不是显存问题是量化噪声把生成分布的尾端压平了这时优先调整校准集其次调 group_size 到 64而不是换更复杂的量化方法。5. 避坑排查全流程最容易翻车的 5 个环节5.1 分层预训练后领域指标涨了但通用能力全面下滑现象领域数据上的 loss 降得漂亮但常规问答、数学推理掉点超过 5%甚至出现答非所问。原因最常见的是领域数据 : 通用数据配比失衡或者高层学习率太高把通用语义冲掉了。另一个隐藏场景是 gate 层跟着高学习率走路由分布被单一领域语料带偏专家选择失去多样性。解决先把领域数据占比降到 50% 以下掺回通用语料再把高层学习率从 5e-5 降到 3e-5。如果掉点依然明显检查 gate 参数是否被单独设成了低学习率把包含gate的权重拆出来单独放一组。配比和学习率两个参数必须同时调不能只动一个。5.2 LoRA 训练正常但验证集不涨问题出在 target_modules现象loss 曲线正常下降打印可训练参数也没问题但验证集指标一直原地踏步。原因把 Llama 系的[q_proj, k_proj, v_proj, o_proj]直接搬过来用而 DeepSeek 的 MLA 投影层命名不同。LoRA 挂在了不存在的权重名上或者挂在了一部分非关键投影上训练时根本没注入到实际推理路径。解决训练前先打印model.named_modules()确认你手上这版权重的投影层真实名称比如q_a_proj、kv_b_proj这类再填 target_modules。如果已经训了一半也不用重来——adapter 权重和基座是分开存的改完配置重新训即可基座不动。5.3 多个 LoRA 合并后模型像“哑巴”输出空洞重复现象合并后模型的输出变短、复读严重或者不同任务的能力互相干扰。原因直接把两个 adapter 权重做算术平均同一位点上两个任务的方向互斥参数抵消。这个问题在模型容量越小的时候越明显蒸馏 student 上几乎必现。解决采用 mergekit 的dare_linear或dare_ties方法先按幅度裁剪再平均避免参数冲突。theta从 100 起调模型越小 theta 越大比如 150 到 200。合并完必须过一轮业务评测集不要只看困惑度。5.4 蒸馏 student 量化后困惑度飙升而不是小涨现象量化前困惑度 9.5量化后直接跳到 15 甚至更高生成质量肉眼可见地下降。原因蒸馏阶段温度设得太高或 hard_weight 太低student 的输出概率分布过于平缓量化时按激活统计保留重要通道的依据失真。换句话说student 学到的分布“太软”4bit 量化一压噪声被放大。解决先看训练日志里的蒸馏损失构成把 hard_weight 提到 0.9温度降到 3 以下重新蒸馏。如果时间不允许重训就用 AWQ 替代 GPTQAWQ 对激活分布更鲁棒通常能救回大半损失。同步检查校准集是否用了业务侧数据通用语料校准在蒸馏模型上更容易翻车。5.5 量化模型部署后显存依然超预期服务被 OOM 打死现象4bit 权重加载后用 vLLM 一跑长上下文显存还是爆多个并发直接 OOM。原因只量化了权重没算 KV Cache。长上下文下缓存占用甚至超过权重本身另外 vLLM 默认给每个请求预留最大上下文的 KV并发高时显存成倍增长。解决先加--kv-cache-dtype fp8压缩缓存再把--max-model-len从 8192 降到业务真实需要的长度比如 4096最后调低--gpu-memory-utilization到 0.85给调度器留出换入换出的缓冲。如果还超就要回到模型侧换更小的 student 或更激进的量化组配置。6. 收尾验证上线前用一份体检清单确认整条链路真的成立全流程跑完最怕的是每个环节单独看都没问题串起来却守不住业务。我的习惯是每次做完一版都按下面这张表逐项过一遍再决定要不要上线。检查项方法通过标准基座语言能力困惑度 通用基准走 100 条抽测与量化前相比差距小于 5%领域知识注入抽取 50 条领域问答人工打分关键实体与术语准确率不低于 90%指令跟随跑 30 条多轮指令检查格式与拒答无复读、无跑题、无空回答量化损益同一批 100 条 prompt 对比 int4 与 fp16 输出语义一致率大于 95%显存与吞吐vLLM 压测 20 并发观察显存峰值峰值低于显卡显存的 90%评估这一步不要只看单项指标。常见误区是量化后用困惑度做唯一判断但困惑度对量化噪声不敏感可能显示“没变化”实际生成风格已经变了。我一般会在业务侧准备一批真实用户请求量化前后各跑一遍用字符串编辑距离加人工抽样来评估输出一致性。还有一个我吃了亏才养成的习惯每完成一步就把config.json、tokenizer 文件和 adapter/量化权重各存一份文件名带步骤编号绝不覆盖。分层预训练、PEFT 融合、蒸馏、量化这四个状态分别对应一份可回溯的权重。你永远不知道下一步会不会把一个环节推翻重来这些就是你的后悔药。最后提醒一句蒸馏和量化能解决成本问题不能解决数据问题。如果业务效果本身不行回到第 2 章和第 3 章检查数据配比和 LoRA 参数别在最后两章里找原因。这条链路跑通一次之后后面每换一个领域真正要重做的只有数据整理和校准集希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?