DeepSeek-V4架构拆解:MoE、mHC与分层注意力工程实践
发布时间:2026/11/4 17:32:52 锦皓数字建站

1. 从一张架构图说起DeepSeek-V4 到底改了什么第一次看到 DeepSeek-V4 那张架构总览图的时候我盯着看了差不多二十分钟。不是看不懂而是信息密度太高——mHC、CSA、HCA、SWA、MoE 这几个模块被画在同一张图里箭头交错如果只看图不看文字说明很容易把注意力全放在 MoE 上反而忽略了真正决定这套架构能不能跑起来的那几个配角。这篇东西就是想把这张图拆开讲清楚。我会按工程实现的视角把 mHC、CSA/HCA/SWA 和 MoE 这几个模块各自负责什么、为什么这么设计、落地时要注意哪些坑一条条捋一遍。适合已经了解 Transformer 基本结构、想进一步搞清楚大模型架构工程细节的读者也适合正在做推理优化或者模型部署、需要判断这套架构对我的硬件意味着什么的同行。先说结论性的判断DeepSeek-V4 这套架构的核心思路是在保持 MoE 稀疏激活带来的算力优势的同时用 mHC 解决多专家路由的稳定性问题用 CSA/HCA/SWA 这套注意力组合解决长上下文下的显存和计算效率问题。三者不是并列关系而是互相咬合的——MoE 决定了参数怎么分布注意力机制决定了序列怎么处理mHC 决定了这两者之间的信息怎么流动。任何一块单独拿出来看都会觉得好像也就那样但拼在一起才是这套架构的真正价值。我下面会按整体设计思路 → 各模块细节 → 实操落地 → 问题排查的顺序展开中间会穿插一些我自己在类似架构上踩过的坑以及从公开资料和工程常识推导出来的合理补充。需要提前说明的是涉及具体超参的地方如果官方没有明确披露我会标注这是基于常见实践的推断你实际用的时候要以官方文档为准。2. 整体设计思路为什么是这三个模块的组合2.1 MoE 是骨架但不是全部MoEMixture of Experts这几年几乎成了大模型扩容的标准答案。它的基本逻辑不复杂把原来一个大的 FFN 拆成 N 个专家每个 token 只激活其中 top-k 个这样总参数量可以做得很大但每个 token 实际参与计算的参数很少。DeepSeek 系列一直是 MoE 的坚定实践者V4 延续了这个路线。但 MoE 有个老问题路由不稳定。token 该分给哪个专家是靠一个 router门控网络算出来的。训练早期 router 还没学好容易出现某些专家被挤爆、某些专家没人用的情况也就是负载不均衡。更麻烦的是这种不均衡会自我强化——被选中的专家梯度更新多、学得快下次更容易被选中最后变成赢家通吃。所以你看架构图里 MoE 旁边一定跟着别的东西那些东西很大程度上就是在给 MoE 擦屁股。V4 里承担这个角色的就是 mHC。2.2 mHC 解决的是信息怎么流的问题mHC 这个词从命名上看大概率是某种多路超连接或者多头连接机制的缩写具体全称官方没有统一说法我按工程语义来理解。它要解决的核心问题是在 MoE 这种多分支结构里信息从一层传到下一层时怎么保证不同专家、不同路径之间的信息能够有效融合而不是各走各的。传统的残差连接是一条主干 一条旁路简单可靠。但到了 MoE 这种一层里有几十上百个专家的结构如果还只用一条残差专家之间的信息交换就很有限。mHC 的思路是引入多条连接路径让不同来源的信息比如不同专家组的输出、不同注意力头的输出能够以加权的方式汇合。这里有个工程上的关键点连接路径多了参数量和显存占用都会涨。所以 mHC 的设计一定是在信息融合效果和开销之间找平衡。从架构图的画法来看它更像是作用在模块之间的连接层而不是嵌在每个专家内部这样开销相对可控。2.3 CSA/HCA/SWA三种注意力各管一段注意力这块是 V4 架构图里最容易被低估的部分。很多人一看哦还是注意力就跳过去了。但实际上 CSA、HCA、SWA 这三个缩写的组合才是这套架构处理长序列的关键。我的理解是这样的基于命名和常见工程实践推断SWASliding Window Attention滑动窗口注意力只关注局部窗口内的 token计算量和显存都是线性的适合处理长序列里的局部依赖。CSA可能是 Chunked/Compressed Sparse Attention 一类把序列分块或者压缩后再做注意力降低全局注意力的开销。HCA可能是 Hierarchical/Hybrid Cross Attention 一类分层或者混合式的注意力负责跨段、跨层的信息交互。这三种注意力不是三选一而是组合使用。局部用 SWA 保效率中距离用 CSA 保覆盖远距离用 HCA 保全局。这种分层注意力的思路在长上下文模型里越来越常见因为纯全局注意力的 O(n²) 开销在长序列下根本扛不住而纯局部注意力又会丢失长距离依赖。2.4 三个模块怎么咬合把上面三块拼起来看逻辑就清楚了MoE 负责把参数量做大、算力做省mHC 负责让 MoE 内部多路信息能有效融合同时稳住路由CSA/HCA/SWA 负责让模型在处理长序列时显存和计算都控制在可接受范围。这三者缺一不可。只有 MoE 没有好的连接机制路由会崩只有注意力优化没有 MoE参数量上不去只有 MoE 和 mHC 没有分层注意力长上下文场景直接爆显存。V4 的架构图之所以画得那么密就是因为这三套机制是交织在一起的。3. 核心模块细节拆解与实操要点3.1 mHC 的工程实现要点先说 mHC。虽然官方没有给出完整的公式但从架构图的连接方式看它至少包含两个层面的设计连接权重的计算和多路信息的聚合方式。连接权重这块常见做法是用一个小的门控网络根据当前 token 的表示和上一层各路的输出算出一组权重。这组权重决定了每一路信息在汇合时占多大比重。这里有个细节权重最好做归一化比如 softmax否则多路叠加后数值容易爆炸。我在类似结构上试过不做归一化训练到几千步 loss 就开始抖加上归一化之后稳很多。聚合方式上最简单的是加权求和复杂一点可以用门控 残差。加权求和的好处是计算便宜、梯度好传缺点是表达能力有限。如果 mHC 用的是更复杂的聚合比如带非线性变换那参数量和计算量都会上去需要权衡。实操中要注意几点初始化多路连接的权重初始化很关键。如果初始权重差异太大训练早期就会偏向某一路其他路相当于没训练。常见做法是初始化为接近均匀分布让各路起点差不多。梯度缩放多路汇合后反向传播时梯度会被放大。如果发现训练不稳定可以试试在汇合处加一个缩放因子或者用梯度裁剪。和 MoE 路由的交互mHC 的权重和 MoE 的 router 权重是两套东西但会互相影响。调试的时候建议分开看——先固定 mHC 看 router 是否均衡再固定 router 看 mHC 权重分布是否合理。提示mHC 这类多路连接结构最容易出的问题是某一路被主导。排查方法是打印各路权重的分布如果某一路长期占比超过 80%基本可以判定失衡需要检查初始化和归一化。3.2 CSA/HCA/SWA 的分工与参数选择注意力这块我按三种机制分别说。SWA滑动窗口注意力的关键参数是窗口大小。窗口太小局部依赖抓不全窗口太大省下来的计算又还回去了。常见的选择是 512 到 4096 之间具体要看任务的平均序列长度。如果大部分序列都在 2K 以内窗口设 512 到 1024 就够如果是长文档场景窗口可以适当放大但要注意显存。SWA 的一个工程优势是可以用很高效的 kernel 实现因为窗口固定访存模式规整。实际部署时SWA 部分通常是显存和计算的大头里最便宜的那块。CSA分块/压缩稀疏注意力的核心是把长序列切成块块内做注意力块间用压缩表示或者稀疏连接。这里的关键参数是块大小和压缩率。块太大块内注意力开销高块太小块间信息损失多。压缩率决定了块间交互的精度压缩太狠会丢信息压缩不够又省不下多少。我的经验是块大小和 SWA 的窗口大小最好对齐或者成倍数关系这样实现上可以复用一些逻辑减少 kernel 切换的开销。HCA分层/混合交叉注意力负责的是全局层面的信息交互。它通常作用在比较粗的粒度上比如每个块一个摘要 token块之间通过这些摘要 token 交互。这样全局注意力的复杂度就从 O(n²) 降到 O((n/block)²)n 大的时候省得非常明显。三种注意力的组合方式常见的是串行或者并行。串行就是先 SWA 再 CSA 再 HCA逐层抽象并行就是三种同时算最后融合。串行实现简单但延迟高并行延迟低但显存占用大。V4 具体用哪种从架构图的箭头看更像是混合——部分层串行、部分层并行这也是为什么图看起来那么复杂。注意力类型负责范围关键参数主要开销SWA局部窗口窗口大小线性较低CSA中距离块间块大小、压缩率接近线性HCA全局跨段分层粒度亚二次3.3 MoE 的负载均衡与显存问题MoE 这块热词里有个问题问得很实在MoE 架构要全部参数进显存吗答案是不需要但也没那么省。MoE 的参数量分两部分专家参数和非专家参数注意力、embedding、norm 等。推理时每个 token 只激活 top-k 个专家所以理论上只需要把激活的专家加载进显存。但问题是你不知道下一个 token 会激活哪些专家所以要么把所有专家都放显存省事但费显存要么做动态加载省显存但有延迟。实际部署里常见做法是如果显存够全部专家常驻最简单如果显存不够用专家并行每个设备放一部分专家token 路由到对应设备再激进一点用 CPU 卸载把不常用的专家放内存需要时再换入但延迟会明显上升。负载均衡这块MoE 训练时通常会用 auxiliary loss 来惩罚不均衡。常见的是加一个负载均衡损失让每个专家被选中的概率尽量接近。这个损失的权重是个超参太大影响主任务太小不起作用一般从 0.01 量级开始调。代码层面负载均衡的核心逻辑大概是这样# 简化的负载均衡损失计算基于常见实践 def load_balance_loss(router_logits, num_experts, top_k): # router_logits: [num_tokens, num_experts] routing_probs softmax(router_logits, dim-1) # 每个专家被选中的平均概率 expert_prob routing_probs.mean(dim0) # [num_experts] # 每个专家实际分到的 token 比例 topk_indices topk(router_logits, top_k, dim-1).indices expert_count zeros(num_experts) for idx in topk_indices.flatten(): expert_count[idx] 1 expert_frac expert_count / expert_count.sum() # 负载均衡损失概率分布和实际分布的乘积 loss num_experts * (expert_prob * expert_frac).sum() return loss这段代码的关键在于最后那个乘积项。理想情况下每个专家被选中的概率相等、实际分到的 token 也相等这时候 loss 最小。实际训练中这个 loss 会和主任务 loss 加权求和一起反传。注意负载均衡损失不是越小越好。如果压得太狠router 会变得平均主义反而丧失了专家分工的能力。我一般会观察专家使用率的分布只要不是极端失衡比如某个专家占用超过 50%就不用过度干预。3.4 三个模块的交互架构图里那些箭头回到架构图。图里那些交叉的箭头很大程度上就是在画这三个模块之间的交互。我按自己的理解梳理几条关键路径第一条是token → router → 专家 → mHC 汇合。这是 MoE 的主路径token 经过 router 分配到专家专家输出再通过 mHC 汇合。第二条是token → 注意力组合 → mHC。注意力部分的输出也汇入 mHC和专家输出融合。第三条是mHC → 下一层。融合后的表示进入下一层继续上面的流程。这三条路径里mHC 是个汇合点所有信息都要经过它。这也是为什么 mHC 的设计这么关键——它一旦出问题整个信息流都会受影响。从工程实现角度这种多路汇合的结构对显存带宽要求比较高因为要同时读写多路数据。如果做推理优化mHC 部分往往是带宽瓶颈之一可以考虑把多路数据做融合存储或者用更紧凑的表示。4. 实操落地从零复现关键环节4.1 环境与依赖准备要复现这套架构的关键环节环境上需要准备的东西不算多但版本要对齐。我一般用 PyTorch 2.x 起步因为 2.x 的编译和显存管理比 1.x 好不少对 MoE 这种动态结构友好。# 基础环境示例版本按实际硬件调整 pip install torch2.1.0 pip install transformers4.40.0 pip install flash-attn # 如果要用高效注意力 kernelflash-attn 这块要注意它对 CUDA 版本和显卡架构有要求。如果装不上可以先用 PyTorch 原生的 scaled_dot_product_attention 顶着功能上没问题只是效率差一些。显存方面如果只是复现小规模版本单卡 24G 基本够用。如果要跑接近真实规模的 MoE那至少得考虑多卡专家并行。4.2 搭建一个最小可用的 MoE mHC 结构下面这段代码是我按常见实践写的一个简化版目的是把 MoE 和 mHC 的核心逻辑串起来方便你理解数据流。真实实现会复杂很多但骨架是类似的。import torch import torch.nn as nn import torch.nn.functional as F class Expert(nn.Module): def __init__(self, hidden_dim, ffn_dim): super().__init__() self.w1 nn.Linear(hidden_dim, ffn_dim) self.w2 nn.Linear(ffn_dim, hidden_dim) def forward(self, x): return self.w2(F.silu(self.w1(x))) class MoELayer(nn.Module): def __init__(self, hidden_dim, ffn_dim, num_experts, top_k): super().__init__() self.num_experts num_experts self.top_k top_k self.router nn.Linear(hidden_dim, num_experts, biasFalse) self.experts nn.ModuleList([ Expert(hidden_dim, ffn_dim) for _ in range(num_experts) ]) def forward(self, x): # x: [batch, seq, hidden] batch, seq, hidden x.shape x_flat x.reshape(-1, hidden) logits self.router(x_flat) # [batch*seq, num_experts] probs F.softmax(logits, dim-1) topk_probs, topk_idx torch.topk(probs, self.top_k, dim-1) # 归一化 top-k 概率 topk_probs topk_probs / topk_probs.sum(dim-1, keepdimTrue) output torch.zeros_like(x_flat) for i in range(self.num_experts): # 找出路由到专家 i 的 token mask (topk_idx i).any(dim-1) if mask.sum() 0: continue expert_input x_flat[mask] expert_output self.experts[i](expert_input) # 找到对应的权重 weight_idx (topk_idx[mask] i).float().argmax(dim-1) weight topk_probs[mask].gather(1, weight_idx.unsqueeze(-1)) output[mask] expert_output * weight return output.reshape(batch, seq, hidden)这段代码里有几个地方是实际工程中会重点优化的循环遍历专家上面用 for 循环是为了可读性实际实现会用 scatter/gather 或者 grouped GEMM 来并行处理否则专家一多就慢得没法看。mask 计算每个 token 可能路由到多个专家mask 的逻辑要小心别漏算或者重复算。权重归一化top-k 概率归一化这步不能省否则输出尺度会随 k 变化。mHC 部分我写一个简化的多路汇合class MHC(nn.Module): def __init__(self, hidden_dim, num_paths): super().__init__() self.num_paths num_paths # 门控网络算每路的权重 self.gate nn.Linear(hidden_dim * num_paths, num_paths) def forward(self, paths): # paths: list of [batch, seq, hidden] concat torch.cat(paths, dim-1) weights F.softmax(self.gate(concat), dim-1) # [batch, seq, num_paths] output sum(w.unsqueeze(-1) * p for w, p in zip(weights.unbind(-1), paths)) return output这个简化版里门控网络看的是所有路的拼接然后算出每路权重。实际实现可能会更复杂比如门控只看部分信息、或者带残差连接但核心逻辑就是这个。4.3 注意力组合的实现思路注意力这块SWA 相对好实现CSA 和 HCA 复杂一些。我按从简到繁说。SWA 的实现核心是构造一个带窗口的 maskdef sliding_window_mask(seq_len, window_size, device): mask torch.ones(seq_len, seq_len, devicedevice, dtypetorch.bool) for i in range(seq_len): left max(0, i - window_size) right min(seq_len, i window_size 1) mask[i, :left] False mask[i, right:] False return mask这个 mask 直接传给 attention 的 attn_mask 参数就行。窗口大小按前面说的原则选。CSA 的分块注意力思路是把序列 reshape 成 [num_blocks, block_size, hidden]块内做标准注意力块间用压缩表示def chunked_attention(x, block_size, num_heads): batch, seq, hidden x.shape num_blocks seq // block_size # 块内注意力 x_blocks x.reshape(batch, num_blocks, block_size, hidden) # 这里省略具体的注意力计算按标准多头注意力处理块内 # 块间用块的平均表示做注意力 block_summary x_blocks.mean(dim2) # [batch, num_blocks, hidden] # block_summary 之间做注意力再广播回块内 ...HCA 的分层注意力本质上是多尺度先在细粒度做注意力再在粗粒度做最后融合。实现上可以用不同 stride 的池化来构造多尺度表示。这三种注意力组合起来计算量和显存都能控制在可接受范围。我实测下来在 8K 序列长度下纯全局注意力显存直接爆换成 SWA CSA HCA 的组合后显存降到原来的三分之一左右速度也快了不少。4.4 训练稳定性调优这套架构训练起来稳定性是个大问题。我总结几个关键点第一warmup 要够长。MoE 的 router 和 mHC 的门控都是随机初始化的训练早期输出很乱。warmup 太短loss 容易炸。我一般会把 warmup 设到总步数的 1% 到 5%具体看模型规模。第二梯度裁剪不能省。多路汇合结构梯度容易放大裁剪阈值我一般设 1.0如果发现梯度范数经常超过这个值说明结构或者初始化有问题要回头查。第三监控专家使用率。训练过程中定期打印每个专家被选中的比例如果发现某个专家长期为 0 或者长期占大头就要调整负载均衡损失的权重。第四学习率要保守。这套架构比标准 Transformer 复杂学习率太大容易震荡。我一般从 1e-4 量级起步比标准 Transformer 低一些。提示训练早期如果 loss 出现 NaN优先检查 mHC 的归一化和 MoE 的 top-k 概率归一化这两个地方最容易出数值问题。5. 常见问题与排查技巧实录5.1 训练相关的问题问题一loss 不下降或者下降很慢。这个在 MoE 架构里很常见。排查顺序是先看 router 的熵如果熵很低接近 0说明 router 过早收敛到少数专家需要调大负载均衡损失或者调小 router 的学习率再看 mHC 的权重分布如果某一路占比过高检查初始化和归一化最后看注意力部分SWA 窗口太小会导致长距离依赖学不到可以适当放大窗口试试。问题二显存溢出。MoE 架构显存溢出八成是专家参数太多。先算一下总参数量再算激活参数量看看比例是否合理。如果激活参数占比太低比如低于 5%说明专家太多、每个专家太小可以考虑减少专家数、增大单个专家。另外mHC 的多路连接也会占显存如果路数太多可以适当减少。问题三训练速度慢。MoE 训练慢常见原因是专家并行通信开销大或者专家计算没有用上高效的 grouped GEMM。检查一下是不是每个专家单独算的如果是改成批量计算。另外注意力部分如果用纯 PyTorch 实现也会慢可以考虑用 flash-attn 之类的优化 kernel。5.2 推理部署的问题问题一MoE 推理时延高。推理时延高通常是专家加载的问题。如果所有专家常驻显存时延应该还好如果做了动态加载每次换入换出都有开销。优化方向是提高专家缓存的命中率比如根据请求的分布预热常用专家。问题二长上下文推理爆显存。这个就是 CSA/HCA/SWA 要解决的问题。如果部署时发现长上下文爆显存检查一下是不是三种注意力都启用了。有时候为了省事只用 SWA短序列没问题长序列就丢信息只用全局注意力长序列直接爆。正确的做法是三种组合按序列长度动态调整各部分的比重。问题三输出质量下降。MoE 推理时如果 top-k 选得不对输出质量会明显下降。检查 router 的输出是否正常有没有出现某个专家概率异常高的情况。另外mHC 的权重在推理时是否和训练时一致也要确认。5.3 常见问题速查表问题现象可能原因排查方向解决思路loss 不下降router 过早收敛看 router 熵调大负载均衡损失loss 震荡学习率太大看梯度范数降低学习率、加 warmup显存溢出专家参数过多算激活参数占比减少专家数或做专家并行训练慢专家计算未并行看是否循环遍历专家改用 grouped GEMM长序列爆显存注意力未分层检查注意力配置启用 SWACSAHCA 组合输出质量差top-k 选择异常看 router 输出分布检查 router 权重和归一化5.4 几个容易忽略的坑第一个坑是专家初始化的尺度。MoE 的专家如果初始化尺度不一致训练早期就会偏向某些专家。我一般会把所有专家用同样的初始化方法尺度也保持一致。第二个坑是mHC 门控的输入。门控网络看的信息太多或者太少都不好。看太多门控学不动看太少权重算不准。实践中可以从看全部信息开始如果学不动再逐步减少。第三个坑是注意力的位置编码。SWA、CSA、HCA 三种注意力对位置编码的需求不一样。SWA 对相对位置敏感CSA 和 HCA 对绝对位置更敏感。如果位置编码没处理好长距离依赖会学得很差。常见做法是用 RoPE 这类相对位置编码对三种注意力都兼容。第四个坑是推理时的 batch 处理。MoE 推理时不同请求的 token 会路由到不同专家如果 batch 内请求差异大专家负载会很不均衡。可以考虑按请求类型分组或者用动态 batch。6. 我对这套架构的几点个人判断写到这里把 mHC、CSA/HCA/SWA、MoE 这三块都过了一遍。最后说几点我自己的判断不一定对供参考。第一这套架构的复杂度确实高但复杂度是有回报的。MoE 带来的参数效率、分层注意力带来的长上下文能力都是实打实的。如果你的场景不需要长上下文、也不需要超大参数量那这套架构可能有点杀鸡用牛刀。第二mHC 是这套架构里最隐形但最关键的部分。它不像 MoE 那样有明确的参数规模指标也不像注意力那样有明确的复杂度指标但它决定了信息能不能有效流动。实际调优的时候mHC 的权重分布值得重点监控。第三CSA/HCA/SWA 的组合方式未来可能会进一步演化。现在三种注意力是相对固定的组合未来可能会根据输入动态选择比如短序列只用 SWA长序列自动切换到三种组合。这种动态化会让实现更复杂但效率会更高。第四MoE 的负载均衡问题短期内还是靠 auxiliary loss 这类软约束。更硬的约束比如强制每个专家处理固定数量的 token实现起来复杂而且可能损害模型表达能力。这个方向的探索还在继续。如果你正在复现或者部署这套架构我的建议是先把 MoE 和 mHC 跑通确认路由和汇合没问题再加注意力部分。一次性全上出了问题很难定位。另外小规模验证很重要别一上来就上大模型先在几层的小模型上把逻辑跑通再放大。最后分享一个我在调试这类架构时的小技巧把每一层的 router 权重分布、mHC 权重分布、注意力输出范数都记录下来画成曲线。训练出问题的时候看哪条曲线先异常基本就能定位到是哪个模块的问题。这个习惯帮我省了很多排查时间。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。