Overview
- 2025.10.21 deepseek-ocr (multi modality and long-prompt compression): https://arxiv.org/pdf/2510.18234
- 2025.11.27 deepseek-math-v2 (RL training): https://arxiv.org/pdf/2511.22570
- 2025.12.2 deepseek-v32 (indexer+dsa attn): https://arxiv.org/pdf/2512.02556
- 2026.1.5 deepseek-mhc (residual connection): https://arxiv.org/pdf/2512.24880
- 2026.1.12 deepseek-engram (static memory for MOE): https://github.com/deepseek-ai/Engram/blob/main/Engram_paper.pdf
- 2026.1.27 deepseek-ocr2: https://github.com/deepseek-ai/DeepSeek-OCR-2/blob/main/DeepSeek_OCR2_paper.pdf
脉络:
- long-context高效处理:OCR前置的压缩,dsa next token时候的检索使推理稀疏,减少计算量和部分kv cache存储压力
- 模型的表达能力:mhc让让hc训练更加稳定,增强残差连接的表达能力,engram减少动态moe的计算消耗,通过静态memory检索,减少moe的开销。这些可以让模型层数不变,总参数量不变的前提下,模型的performance更好,同时静态memory可以进一步减少HBM压力,进一步提高kv cache存放量(long-context)
- RL 训练更优,通过生成器-验证器-监督器进行思维上的过程校正,在保证结果对的前提下还要保证过程正确
- 外挂组件端到端化 (NLP上的Faster-RCNN):
- attn稀疏端到端训练,以前出现了很多kv cache压缩方法,类似H2O, attion-sink,分层(前面4个token+中间窗口按block-size选取+临近所有token) 等方法重组kv cache再送给每一层,现在直接使用indexer端到端选择
- 静态知识库端到端训练,以前通过RAG方法在模型推理前把前置知识拼接到原始prompt上(比如To B的企业文档,to C的网络搜索),现在也直接通过训练静态知识库来解决
从这些论文来看,deepseek 近期发力的重点则在于长文本或者超长文本,因为26年 agent可能要继续长足发展,深入各种业务中,长文本,工具调用,自我推理能力必须要提高,另一方面,deepseek没有国外那么多的算子支持,因为只能通过算法,工程创新来提升模型能力,压榨算力利用率。
在V4即将发布之际,我们大概做下展望,猜错了不负责任:)
- v4 attn部分可能采用v32的dsa架构,但是不知道会不会改变indexer rope 和dsa rope不是一个style的结构。这部分现在大家应该都有技术储备了。

为什么这么说?个人猜想:v32中的indexer部分训练时是按照主attention单元的KL散度去训练的,而之前也有一些论文研究,在长文中,attention scores会出现attention sink现象,也就是开头的几个tokens的softmax分数更高,这个影响其他关键信息的捕捉,同时也会加剧训练不稳定性,所以indexer可能也是存在attention-sink的问题,进而影响长文本下的回答质量,deepseek可能会考虑引入一个额外的linear加几个element-wise算子来提升模型性能 indexer的weight充当了一个scale,乘在qk logits后面,可能有类似的门控作用 🧐


- v4 moe部分可能会减少专家数量,因为非激活部分交给了静态记忆存储,现在专家不需要存储静态知识,可以都用在动态推理上,这样对于大规模EP压力也会小点(可以省卡,毕竟外挂记忆可以放CPU DRAM),网络通信和ep负载不均衡可能会有所缓解。但是V4相比V3可能进一步提高总参数量,毕竟Engram和MOE都符合scaling law (可能出普通版和max版)
- 浅层的部分几层添加Engram,由于hash+embedding计算只和input_ids有关,因此可以分开做,所以可能Deepseek可能也选择分离静态记忆部署方案,再加上他们对存储的工程能力,可能做了多级缓存处理。然后本层进行静态记忆融合的时候,可能开启了另一个stream,准备加载下一层的静态记忆 (这个也可以和kv cache offload结合起来,因为dsa本质没有减少kv cache的容量,反而因为indexer的存在变多了,因此这里可能也会offload, vllm近期也对kv cache offload做了不少优化,结合最近cpu售空涨价,内存疯涨,以及1.6号CES 老黄展示的roubin 近存架构,存算分离可能是大势所趋)。
- attn部分和moe部分的残差连接采取mHC, 宽度可能是4,不过考虑到出初始训练稳定性和访存压力,可能前几层保持标准残差连接,后面隔几层回退到残差连接。deepseek可能也会开源他们的fused kernel方案。
- 训练上,可能RL训练比重继续提升,加上mathv2的训练方法和部分小作文,v4模型的coding能力可能会很强
- 多模态方面(这个也是agent 提高生产力很重要的方面),多模态输入,OCR提供了一种文档处理的新范式,用来减少视觉token,减轻基座模型处理负担,多模态输出方面,暂时没看到deepseek 做出MMDIT等diffusion架构的工作,毕竟这方面训练也是很吃数据和算力。猜测这次还是多模态输入发力,多模态输出就暂时交给Qwen团队吧。
DeepSeek-OCR

属于视觉语言模型(VLM),其核心目标是探索通过光学 2D 映射(Optical 2D Mapping)来实现长文本上下文的高效压缩。
1. 核心设计理念:光学压缩
该研究将视觉模态视为一种高效的文本压缩媒介。“一图胜千言”的思想在此被量化:一张包含文档文字的图像,其视觉 Token 数量远少于对应的文本 Token,因此通过视觉编码器可以实现极高的压缩比。
2. 模型架构:DeepEncoder + MoE 解码器
DeepSeek-OCR 由两个主要部分组成:
- DeepEncoder(编码器):负责图像特征提取、Token 化以及视觉表示的压缩。它的设计目标是在处理高分辨率输入时保持低激活量,并实现高压缩比,以确保视觉 Token 的数量在可控范围内。DeepEncoder 的参数量约为 380M,它将两个组件串联在一起:一个负责视觉感知的 SAM-base(80M 参数,以窗口注意力为主)和一个负责视觉知识提取的 CLIP-large(300M 参数,具有密集全局注意力)。在两个组件之间使用了一个 16倍卷积压缩器。该压缩器在视觉 Token 进入密集的全局注意力层之前对其进行下采样,从而显著降低了内存消耗和 Token 数量。
- DeepSeek-3B-MoE(解码器):负责根据 DeepEncoder 输出的压缩视觉 Token 和用户提示(Prompts)生成最终结果。它通过学习一种非线性映射,从视觉 Token 中重建出原始文本表示。采用混合专家(MoE)架构,拥有 3B 总参数。在推理阶段,它仅激活 570M 参数(包含 6 个路由专家和 2 个共享专家),在保持高性能的同时兼顾了推理效率。

3. 多模式与动态分辨率支持
为了适应不同复杂度的文档,DeepSeek-OCR 设计了多种分辨率模式:
- 原生分辨率模式:包括 Tiny (512px, 64 tokens)、Small (640px, 100 tokens)、Base (1024px) 和 Large (1280px)。
- 动态分辨率模式 (Gundam/Gundam-M):借鉴 InternVL2 的切片(Tiling)方法,支持处理超高分辨率输入(如报纸),通过切片和全局视图结合,Token 数量在 800-1800 左右。
4. 性能表现与压缩效率
实验证明,DeepSeek-OCR 在极低 Token 消耗下达到了领先水平:
- 高压缩比下的精准度:当文本 Token 数量是视觉 Token 的 10 倍以内时(10倍压缩),OCR 解码精度可达 97%。即使在 20 倍压缩比下,精度仍能保持在 60% 左右。
- 基准测试超越同行:在 OmniDocBench 测试中,DeepSeek-OCR 仅用 100 个视觉 Token 就超越了 GOT-OCR2.0 (256 tokens),并以不到 800 个 Token 击败了 MinerU2.0(平均每页 6000+ tokens)。
- 多语言与复杂解析:支持近 100 种语言,并具备“深度解析”能力,可处理图表(转为 HTML 格式)、化学分子式(转为 SMILES)、几何图形及自然图像描述。
5. 模拟人类“遗忘机制”的愿景
论文提出了一个有趣的观点:可以利用光学压缩来模拟人类记忆的衰减。对于最近发生的对话或上下文,使用高分辨率(清晰记忆);对于较久远的上下文,则逐步降低图像分辨率(模糊记忆),从而以更少的计算资源保留长期的“轮廓”信息。

.png?table=block&id=2ec8f42f-8313-805d-b39a-d1af5bf18989&t=2ec8f42f-8313-805d-b39a-d1af5bf18989)

Deepseek-OCR2


DeepSeek-OCR2核心创新是DeepEncoder V2——通过将传统CLIP组件替换为LLM风格架构(如Qwen2 500M) ,结合定制化注意力掩码实现视觉token的动态语义重排,打破传统VLMs的刚性光栅扫描顺序;其视觉tokenizer基于80M参数的SAM-base,支持256-1120个视觉token(匹配Gemini-3 Pro上限),搭配DeepSeek-MoE 3B解码器,在OmniDocBench v1.5基准上较DeepSeek-OCR实现3.73%的性能提升(Overall达91.09%),同时降低阅读顺序编辑距离(从0.085至0.057)和生产环境重复率(在线图像从6.25%至4.17%),兼具研究价值(探索2D推理新范式)与实用价值(高效OCR服务与LLM预训练数据生成)。
- 研究动机:传统视觉语言模型(VLMs)采用刚性光栅扫描顺序(top-left至bottom-right) 处理视觉token,与人类“语义驱动的灵活扫描”认知机制矛盾;文档OCR(含复杂布局、公式、表格)需因果视觉推理,成为验证新架构的理想场景。
- 研究目标:提出DeepSeek-OCR 2,通过新型编码器DeepEncoder V2实现更类人的视觉编码,打破传统VLMs的空间顺序偏见。
- 三大核心贡献:
- 设计DeepEncoder V2:用LLM风格架构(Qwen2 500M)替代CLIP,引入“因果流查询”实现视觉token动态重排,且查询与视觉token数量相等(支持重注视),仅输出查询token至解码器。
- DeepSeek-OCR2保持DeepSeek-OCR的图像压缩率与解码效率,视觉token范围256-1120(下限匹配1024×1024图像,上限匹配Gemini-3 Pro),同时显著提升性能。
- 验证“LLM架构作为VLM编码器”的可行性,为统一多模态编码铺垫(支持图像、音频、文本,仅需配置模态特定查询)。
整体架构沿用“编码器-解码器”结构:
- 编码器:DeepEncoder V2(负责视觉token离散化、压缩与因果重排)。
- 解码器:复用DeepSeek-OCR的DeepSeek-MoE 3B(约500M活跃参数),基于视觉token与文本提示生成输出。
组件 | 技术细节 | 核心作用 |
视觉Tokenizer(Vision Tokenizer) | 80M参数SAM-base + 2个卷积层,输出维度从1024降至896;16×token压缩 | 降低后续全局注意力的计算成本与激活内存,参数规模(80M)接近LLM文本嵌入层 |
LLM作为视觉编码器 | 采用Qwen2 500M架构,双流注意力:视觉token:双向注意力(保留全局视野), 因果流查询:因果注意力(仅关注前序查询与所有视觉token) | 替代CLIP实现视觉因果建模,支持token动态重排 |
因果流查询(Causal Flow Query) | 数量=视觉token数,计算式: (W×H)/(16^2×16);多裁剪策略:全局视图(1024×1024):256个查询, 局部视图(768×768):144个查询(0-6个局部视图) | 总token数范围256-1120,匹配Gemini-3 Pro,支持不同分辨率适配 |
注意力掩码(Attention Mask) | 公式:M = [[1_{m×m}, 0_{m×n}]; [1_{n×m}, LowerTri(n)]], (n=m) | 视觉token间全交互,查询token按因果顺序交互,实现“全局感知+因果重排” |


训练流程(三阶段)
训练阶段 | 目标 | 关键参数 |
1. DeepEncoder V2预训练 | 让Tokenizer与LLM编码器掌握特征提取、压缩与重排能力 | 优化器:AdamW;学习率:1e-4→1e-6;硬件:160 A100(20节点×8卡);batch size:640;迭代:40k(约100M图文对) |
2. 查询增强(Query Enhancement) | 强化编码器的token重排能力,优化查询表示 | 冻结Tokenizer,联合训练LLM编码器与解码器;学习率:5e-5→1e-6;迭代:15k;4阶段流水线并行 |
3. LLM续训 | 让解码器适配编码器输出的重排token | 冻结DeepEncoder V2,仅更新解码器;学习率:1e-6→5e-8;迭代:20k;训练速度提升2倍+ |

讨论与未来工作(Discussion and Future Works)
- 迈向真正的2D推理:将2D图像理解分解为“编码器的阅读逻辑推理(token重排)”与“解码器的任务推理”,需探索更长的因果流token以支持多轮重排与多跳推理。
- 迈向原生多模态:基于DeepEncoder V2的架构,构建“统一多模态编码器”——共享Wk、Wv投影、注意力机制、FFN,仅通过“模态特定可学习查询”适配图像、音频、文本,复用LLM社区的优化(如MoE、高效注意力)。
关键问题:
- DeepEncoder V2通过哪些具体设计实现“视觉token动态语义重排”,区别于传统CLIP编码器?
核心设计包括三点:①架构替换:用Qwen2 500M(LLM风格架构)替代CLIP ViT(300M),放弃刚性空间位置编码,采用因果注意力建模语义顺序;②因果流查询:引入与视觉token数量相等的可学习查询(全局256个、局部144个),查询仅关注“所有视觉token+前序查询”,实现动态重排;③定制化注意力掩码:视觉token采用双向注意力(保留全局视野),查询采用下三角因果掩码(确保顺序依赖),掩码公式为 ,两者拼接实现“全局感知+因果重排”的协同。
- 在OmniDocBench v1.5基准中,DeepSeek-OCR 2相比同参数规模/同token预算的模型,核心性能优势体现在哪些维度?
优势体现在三个核心维度:①综合性能领先:Overall准确率达91.09%,较基线DeepSeek-OCR(87.36%)提升3.73%,且视觉token上限(1120)低于基线(1156),实现“更高效+更优性能”;②关键子任务优化:文本编辑距离从0.073降至0.048(↓34.2%),阅读顺序编辑距离从0.085降至0.057(↓31.8%),公式CDM从84.14%升至90.31%(↑7.3%),适配文档OCR的核心需求;③同预算下更优:与Gemini-3 Pro(V-token max=1120)相比,DeepSeek-OCR 2的Overall Edit(0.100)低于前者(0.115),在相同视觉token成本下,逻辑理解能力更强。
- DeepSeek-OCR 2提出的“双1D因果推理”范式如何为“真正的2D推理”和“原生多模态”铺路?未来需突破哪些技术瓶颈?
(1)铺路逻辑:①2D推理:将2D图像理解分解为“编码器的阅读逻辑推理(语义重排视觉token)”与“解码器的任务推理(基于重排序列生成结果)”,通过双1D因果结构模拟人类“先理解顺序、再执行任务”的认知,避免直接 flatten 2D的空间偏见;②原生多模态:DeepEncoder V2的“共享架构(Wk/Wv/FFN)+模态特定查询”设计,可扩展至音频、文本等模态——仅需为不同模态训练专属查询嵌入,即可在同一编码器中完成特征提取与压缩,复用LLM的高效优化(如MoE)。(2)未来瓶颈:①2D推理需更长因果流token以支持多轮重注视与多跳重排(当前查询与视觉token数量相等,灵活性不足);②多模态需验证“跨模态注意力交互”的有效性,避免模态间干扰;③需扩展至通用视觉任务(如目标检测、分割),验证范式通用性。
Math-V2
使用verifer-meta_verifer- generator,“左脚踩右脚”来RL自监督训练推理过程,而非仅仅监督最后的推理结果,提升更多的梯度信号

1. 发现的问题:传统强化学习的局限
当前大语言模型(LLM)在数学推理训练中普遍采用以**最终答案(Final Answer)**为导向的强化学习(RL)模式。这种模式存在两大核心痛点:
- “对答案不代表对逻辑”:模型可能通过错误的逻辑或“幸运的巧合”得到正确答案,导致 RL 奖励了错误的推理路径。
- 定理证明无法应用:许多高阶数学任务(如定理证明)并没有数值化的最终答案,传统的答案匹配奖励机制在此类任务中完全失效。
- 缺乏验证能力:模型通常无法识别自己证明中的瑕疵,表现出极高的误报率,即坚持认为有明显逻辑错误的证明是正确的。

2. 解决思路:通往“自我验证”的数学推理
研究团队认为,要推动深度推理的极限,必须从“结果导向”转向“过程验证”。
核心思想是让模型显式地意识到其奖励函数(Reward Function):与其让模型在“盲目试错”中撞大运,不如教会它像人类专家一样识别证明的严谨性与完整性,从而通过深思熟虑的推理来最大化奖励。
3. 如何解决:三大核心技术组件
为了实现上述思路,DeepSeek 引入了三层架构方案:
- LLM 验证器 (Verifier):不仅对证明打分(0, 0.5, 1),还必须生成详细的分析报告,列出证明中的具体问题。
- 元验证器 (Meta-Verifier):这是最关键的监督层。它负责审核“验证器的分析报告”本身是否合理,防止验证器为了给低分而捏造不存在的错误(幻觉)。
- 自我验证生成器 (Self-Verifiable Generator):在训练生成器时,要求它在给出证明后紧接着进行“自评”,通过“奖励诚实”来引导它主动发现并修正错误。




4. 解决流程:协同进化的训练闭环
DeepSeekMath-V2 的构建遵循了一个协同进化(Synergistic Cycle)的流程:
- 冷启动与专家对齐:通过专家标注的少量证明数据训练初始验证器。
- 引入元验证反馈:利用元验证器对分析报告的质量进行打分,并将其作为额外的 RL 奖励($R_{meta}$),确保验证器既准确又忠实。
- 生成器强化训练:将验证器作为“奖励模型”来训练生成器,使其学会根据验证反馈进行序列化精炼(Sequential Refinement)——即分析错误、识别问题、修正证明,直到自我打分为满分。
- 自动化标注扩规模:通过扩展验证计算量(生成多个验证样本并由元验证器筛选),实现全自动标注新产生的难题,不再依赖人工标注。
5. 关键机制深度解析
- 元验证 (Meta-Verification) 的价值:它解决了 RL 训练中的“作弊”行为。在没有元验证时,验证器可能给正确分数但写胡乱的理由。引入元验证后,验证分析的质量分从 0.85 提升至 0.96,极大地增强了系统的可靠性。
- 训练有效信号的提升:
- 密集奖励:相比“对/错”这种稀疏信号,自评打分和分析提供了更密集的梯度。
- 诚实奖励策略:模型如果能“主动承认错误”会比“撒谎说自己正确”获得更高的奖励。这强迫模型学习真正的数学逻辑,而非寻找 RL 的漏洞。
6. 最终成果
DeepSeekMath-V2 在多项顶尖竞赛中展现了卓越的性能:
- IMO 2025 & CMO 2024:达到了金牌水平(IMO 解决 6 题中的 5 题)。
- Putnam 2024:获得了 118/120 的近乎满分,远超人类参赛者的最高分(90分)。

形象总结:
DeepSeekMath-V2 不再是一个只会机械刷题的孩子,它被训练成了一个具备“自省能力”的优等生。它通过元验证(督导员)的监督,学会了不仅要写出答案,还要写出理由;如果理由不对,它会自己批改草稿,直到整篇逻辑无懈可击为止。

DeepSeek-V32
官方微信公众号在发布v32时提到”相比kimi-k2-thinking, v32的输出长度大幅度降低,显著减少了计算开销与用户等待时间”, 这部分解释了为什么deepseek选择稀疏attn而不是linear attn
端到端实现attn稀疏化,而不是在模型之外自定义规则对kv cache进行选择和拼接
结构如下:

DeepSeek-V3.2 是一项旨在平衡高计算效率与卓越推理及智能体(Agent)性能的研究成果。该模型在长文本处理效率、强化学习(RL)规模化以及智能体任务泛化方面取得了重大技术突破。
- 核心架构设计:DeepSeek Sparse Attention (DSA)
DeepSeek-V3.2 在架构上最显著的改进是引入了 DeepSeek 稀疏注意力机制 (DSA)。
- 设计动机:传统的 Vanilla Attention 在处理长序列时效率极低(复杂度为 O(L^2)),这限制了模型的规模化部署和后期训练。
- DSA 原型:
- 闪电索引器 (Lightning Indexer):利用较少的头数和 FP8 低精度计算,快速计算当前 Token 与前序 Token 之间的索引分数。
- 细粒度 Token 选择:根据索引分数,仅检索 Top-k 个最相关的 Key-Value 条目进行注意力计算。
- MLA 实例化:DSA 在推理效率极高的 MLA (Multi-head Latent Attention) 基础上实现,并采用 MQA (Multi-Query Attention) 模式,确保潜在向量在所有查询头之间共享。
- 计算优势:核心注意力复杂度从 O(L^2) 降低到 O(Lk)(k≪L),在 H800 GPU 集群上显著降低了长文本的推理成本(Prefilling 和 Decoding 阶段均有大幅加速)。
- 训练与强化学习:规模化 GRPO
模型通过持续预训练 (Continued Pre-training) 和大规模 RL 训练 构建,其后训练计算预算超过了预训练成本的 10%。
- 稳定 RL 策略:为了稳定大规模强化学习,DeepSeek 改进了 GRPO (Group Relative Policy Optimization) 算法:
- 无偏 KL 估计:修正了 KL 散度估计器,消除系统误差,确保模型在采样概率较低时仍能稳定收敛。
- 离策略序列屏蔽 (Off-Policy Sequence Masking):屏蔽掉那些导致策略严重偏离的负样本序列,防止由于训练与推理框架不一致导致的优化不稳定。
- 保持专家路由 (Keep Routing):在 MoE 结构中,确保推理和训练时的专家路由路径完全一致。

- 工具使用中的思考机制 (Thinking in Tool-Use):
- 上下文管理:模型在进行多轮工具调用(Tool Call)时,会保留推理内容(Thinking traces),仅在收到新的用户消息时才丢弃历史推理,从而避免重复推理,提高 Token 效率。
- 冷启动与合成任务:通过合成 1,800 多个不同环境和 8.5 万个复杂 Prompt,构建大规模 Agent 任务流水线,显著增强了模型在复杂交互环境中的泛化能力

mHC
- paper:
- [ResNet] https://arxiv.org/abs/1512.03385
- [Identity mapping] https://arxiv.org/abs/1603.05027
- [Hyper-Connections] https://arxiv.org/abs/2409.19606
- [mHC] https://arxiv.org/abs/2512.24880

Overview
架构如下:

- residual connection


- hyper connection
H_res矩阵累乘存在梯度爆炸和消失的风险




- mHC maniflod 约束H_res
- 先exp把矩阵元素变成正数

- 迭代式先把每列scaling把列和变成1,然后继续scaling把行和变成1,然后进行下一次迭代,最后慢慢满足数值约束

- 流形约束是双随机矩阵,形成这个矩阵的算法是sinkhorn-knopp 算法


mHC 把tanh换成sigmoid是为了正数,H^post的2的系数是为了在训练初始阶段,alpha和b很小,让2sigmoid的值接近1,这样就是接近原始residual connection,帮助训练稳定
- mHC infra设计,优化训练开销

fusion kernel
论文的具体公式如下:

注意H_pre, H_post, H_res的尺寸。
- H_pre(输入映射):shape 1×n —— 作用是将n个并行残差流(总维度nC)“聚合” 为单流(维度C),因此需要n个权重系数(对应n个流的聚合比例)。
- H_post(输出映射):shape n×1 —— 作用是将单流(维度C)的计算结果 “分配” 回n个并行残差流,同样需要n个权重系数(对应n个流的分配比例)。
- H_res(残差映射):shape n×n —— 作用是在n个并行残差流之间进行 “跨流信息混合”(例如第i个流与第j个流的交互强度),因此需要n×n个权重系数(对应所有流对的交互关系)
fused_kernel解决IO问题:

fused的kernel主要是H_pre, H_post, H_res计算的三个和最后activation mapping的两个,基本都由tileLang实现

H_res, H_post, H_res的初始计算1把linea-projection和RMS_norm融合在一起(偏置和线性投影被合并为 和 , RMSNorm的权重也被吸收到 中),只要读一次activation,初始计算2把相关的标量计算,bias相加和sigmoid融合在一起,最后H_res的Sinkkhorn-Knopp算法过程fuse成一个kernel,内部做迭代算法计算双随机矩阵。
Sinkhorn-Knopp算法
mHC(Manifold-Constrained Hyper-Connections) 通过将超连接(HC)中的残差映射矩阵投影到特定的流形上,利用 Sinkhorn-Knopp 算法 确保了信号传播的稳定性和守恒性,从而恢复了恒等映射(Identity Mapping)特性。
具体实现流程与原理如下:
1. 将映射矩阵投影至 Birkhoff 流形
manifold约束让优化空间降低到了某个子集合上
mHC 的核心思路是约束残差映射矩阵 ,使其成为一个双随机矩阵(Doubly Stochastic Matrix), 这类矩阵构成的集合被称为 Birkhoff 多胞体(Birkhoff Polytope)。
双随机矩阵具有以下关键数学特性,能够有效恢复恒等映射的稳定性:
- 行和与列和均为 1:这确保了操作 本质上是输入特征的凸组合(Convex Combination)。
- 范数保持(Norm Preservation):其谱范数被限制在 1 以内,使映射具有非扩张性,从而有效抑制梯度爆炸问题。
- 复合闭包性(Compositional Closure):双随机矩阵在矩阵乘法下是封闭的。这意味着多层复合后的映射依然是双随机矩阵,确保了任意深度之间的恒等映射稳定性。
复合闭包性的证明:
一个矩阵 被称为双随机矩阵,需满足以下三个条件:
- 非负性:所有元素 。
- 行和为 1: ,其中 是全 1 向量
- 列和为 1:
假设有两个双随机矩阵 和 ,它们的乘积为 。我们需要证明 同样满足上述三个条件。
(1) 证明非负性
由于 和 的所有元素均大于或等于 0,根据矩阵乘法公式 , 的每个元素都是若干个非负数乘积的和。因此, ,满足非负性。
(2) 证明行和为 1
利用矩阵结合律:
因为 是双随机矩阵,其行和为 1,即 。
带入上式得:
又因为 也是双随机矩阵,所以
结论: 的行和为 1。
(3) 证明列和为 1
同理,利用转置和结合律:
因为 的列和为 1,即
带入上式得:
又因为 的列和为 1,所以
结论: 的列和为 1。
这一特性对 mHC 模型至关重要,原因如下:
- 信号能量守恒:由于复合后的矩阵依然是双随机的,这意味着在多层传播后,信号的全局均值(Global Mean)能够被精准保留,不会像普通超连接(HC)那样出现由于矩阵连乘导致的能量爆炸或消失。
- 防止梯度爆炸:双随机矩阵的谱范数(Spectral Norm)始终 。因为封闭性确保了多层复合矩阵的谱范数同样不会超过 1,这从数学上保证了即使网络极深,梯度流依然是非扩张的(Non-expansive),从而极大地提升了训练稳定性。
2. Sinkhorn-Knopp 算法的具体操作步骤
mHC 利用 Sinkhorn-Knopp 算法将原始的可学习映射矩阵 熵投影到上述流形中,具体步骤包括,:
- 正值化处理:首先通过指数运算符将矩阵的所有元素变为正数,得到初始矩阵
- 迭代归一化:交替进行行归一化(Row Normalization)和列归一化(Column Normalization),使每一行和每一列的和逐步趋近于 1。其迭代公式表示为:
- 实际应用:在实验中,通常通过 20 次迭代 来获得近似的双随机矩阵解。
渣总后续介绍了mHC-lite,可以不通过迭代算法计算出双随机矩阵
- 理论支撑:

- 计算过程:
直接通过预先生成的一系列的置换矩阵来直接计算,非迭代式近似生成

但是最后看评论得知可能deepseek也尝试这种做法,但是工程上存在问题可能没有采用

fusion kernel example
- mHC TileLang example: https://github.com/tile-ai/tilelang/pull/1684/files
- 我们先看下TileLang kernel example
TileLang (TL) 的几个核心概念,它比原生的 CUDA C++ 更抽象,比 Triton 更灵活:
- 分层结构 (Hierarchy):
T.Kernel: 定义一个 GPU Kernel,通常对应 CUDA Grid 的维度。T.Parallel: 并行循环,会被映射到 GPU 的 Block 或 Thread 上。T.serial: 串行循环,在一个线程内顺序执行。
- 内存空间 (Memory Hierarchy):
T.Global: 显存(HBM),最慢,容量最大(代码中的函数参数)。T.Shared(T.alloc_shared): 片上共享内存(SRAM),极快,线程块(Block)内共享。这是算子融合的核心,数据加载到这里后,尽量多做计算再写回。T.Fragment(T.alloc_fragment): 寄存器(Registers),最快,线程私有。
- 流水线 (Pipelining):
T.Pipelined: 自动实现软件流水线(Software Pipelining)。在计算当前数据块时,异步预取下一个数据块到 Shared Memory,掩盖内存延迟。
- Warp Specialization (Warp特化):
- 利用
if T.get_thread_binding() < 32将一个 Thread Block 内的线程分成两组,执行完全不同的任务(一组做密集计算,一组做数据搬运),这是高级优化技巧。
两个kernel,一个是`mhc_pre_gemm_sqrsum_tilelang`, 计算 RMSNorm 需要的平方和 (
sqrsum) 以及 线性投影的结果 (gemm_out), 另一个是`mhc_pre_big_fuse_tilelang`, 计算rms缩放,elememt-wise scale, Sinkhorn-Knopp迭代算法和最后的H_pres混合多个通道的input为layer_inputmhc_pre_gemm_sqrsum_tilelang:


- sqrt_sum 和 gemm在硬件上是并行执行的(如果是现代 GPU,ALU 和 Tensor Core 可以并发发射指令),或者是紧密交错的。最重要的是,它们共享了从 HBM 到 SRAM 的这一次数据加载开销。
- sqrt_sum同时维护了4个累加器,利用 ILP(指令级并行,没有数据依赖,填满指令流水线) 和 向量化(float4, 128-bit),防止流水线停顿,从而榨干GPU的计算性能。
- 重排输入activation数据在共享内存(Shared Memory)中的物理存储位置,以消除“Bank Conflict”(存储体冲突),从而极大提升访存带宽。
- 只有分row,没有split-k,如果Hidden Size 巨大(比如 128k),单个 Block 要跑很久循环,无法利用多个 Block 共同计算同一个 Token 的不同部分然后 Reduce。这在超大模型中可能是瓶颈。
为什么fn_smem(权重)不需要重排数据?
这是细节问题触及了 Tensor Core 的数据流架构 和 线性代数计算的内存访问模式。
简单来说:
x 需要为了“列存取”或“块存取”做特殊优化,而 fn 天然处于“最舒服”的连续读取状态。以下是 3 个层面的详细解释:
- 硬件指令的差异:
ldmatrixvs 直读
在 NVIDIA Ampere (A100) 及之后的架构(TileLang 通常针对这些架构优化)中,Tensor Core 矩阵乘法 C = A @ B 的两个输入操作数处理方式不同:
- Operand A (
x): 必须先加载到寄存器 - 代码中
T.copy(x_smem_16, x_frag_16)实际上会编译成特殊的硬件指令ldmatrix(Load Matrix)。 ldmatrix的怪癖: 这个指令是为了极速将 Shared Memory 中的数据搬运到线程私有的寄存器(Fragment)。它一次搬运一个 16 * 16 的矩阵块。- 为了让 32 个线程能在 1 个指令周期内把这个方块矩阵毫无冲突地“抠”出来,硬件强制要求 Shared Memory 中的数据必须是 Swizzled 布局。如果不 Swizzle,
ldmatrix就无法全速工作或直接报错。
- Operand B (
fn): 可以直接从 Shared Memory 流式读取 - 在 TF32 或 BF16 模式下,Operand B 通常不需要像 A 那样先加载到寄存器做复杂的预处理,或者其加载方式对 Bank Conflict 不敏感。
fn在这里是被当作“权重”使用的。
- 访问模式的几何差异:横着读 vs 竖着读
我们来看一下
T.gemm(..., transpose_B=True) 这个参数。数学上我们要算:
- X 的形状是 (M, K)。
- Fn 的形状是 (N, K)(在内存中是 N 行 K 列)。
在计算点积(Dot Product)的内部循环中:
- 对于 Fn (Operand B):
- 我们实际上是在取 Fn的一行(Row),和 X 的一行做点积。
- Fn在内存中是行优先(Row-Major)存储的。
- 这意味着,GPU 读取 Fn 的数据时,是沿着内存地址连续读取的(k, k+1, k+2...)。
- 连续读取是 Bank Conflict Free 的(就像一群人排队进门,每人进不同的门,顺畅无比)。所以它不需要重排。
- 对于 X (Operand A):
- 虽然 X 也是行优先存储,但 Tensor Core 为了计算效率,通常需要以 Tile(小方块) 的形式切分数据。
- 当
ldmatrix试图抓取一个 16 * 16 的方块时,它不仅仅是读一行,它涉及跨行的读取模式。 - 如果不做 Swizzle,多行数据的同一列可能会落在同一个 Bank 上,导致严重的 Bank Conflict。
- 数据重用的差异
x(Activation): 随着num_tokens变化,每个 Block 处理的 X 都是新的。我们需要极致的加载速度(也就是ldmatrix),所以必须配合 Swizzle。
fn(Weight): 它是投影矩阵。虽然代码中它也在循环里加载,但通常权重的复用率较高,且读取模式极其规整(连续流式读取)
mhc_pre_big_fuse_tilelang:
该 Kernel 将输入 (即
gemm_out 和 residual) 映射为四个输出: (双随机矩阵H_res), (Post-mix Gate, H_post), (Layer Input,先算出 (Pre-mix Gate, H_pre),再合并 )。
假设 M 为 hc_mult (残差流数量),D 为 hidden_size
A. 归一化与混合向量准备
首先,代码从 GEMM 的输出中恢复归一化系数,并应用到混合向量上。
1. RMS 计算:
2. 归一化与缩放 (Normalization):
这里 是一个长度为 的向量。
B. 向量切分 (Splitting)
向量 被切分为三部分,并应用线性变换(Scale s 和 Bias b):
• Part 1 ( ): 用于计算 Pre-mix 门控 。
• Part 2 ( ): 用于计算 Post-mix 门控 。
• Part 3 ( ): 用于计算双随机矩阵
C. 门控计算 (Gate Computation):
(注: 仅作为输出存回显存,Kernel 内部计算 时并不使用它,这是为了给下一层准备的)。
D. Sinkhorn-Knopp 流形投影 (核心)
对 的矩阵部分进行处理,使其变为双随机矩阵 。
令 为切分后的第三部分:
1. 初始化 (Log-Space to Prob-Space):
2. 首次行归一化:
3. 首次列归一化:
4. 迭代循环 (Repeat T times, usually 20):
这部分在代码中对应 for _ in T.serial(sinkhorn_repeat - 1)
E. 残差流聚合 (Weighted Sum)
利用计算出的 将 M 个残差流 R 聚合成进入当前层的输入 :



main point:
- 为什么是 "Big Fuse"?
通常,Sinkhorn 算法(矩阵归一化)和 Residual Sum(向量加法)是两个完全不同的 CUDA Kernel。
- Sinkhorn: 计算量大(Exp, Div),但数据量极小(n x n, 比如4 * 4 或 8 * 8 矩阵)。
- Residual Sum: 计算量小(乘加),但数据量极大(Hidden Size 维度,如 4096)。
如果分开写,Sinkhorn Kernel 会因为启动开销和极低的 Occupancy 而浪费 GPU;Residual Sum Kernel 会受限于显存带宽。
TileLang 通过
if T.get_thread_binding() < 32 将它们硬塞进同一个 Kernel:- 前 32 个线程在做复杂的 Sinkhorn 数学题。
- 后 64 个线程在搬运沉重的 Residual 数据。
- 收益: Sinkhorn 的计算时间被 Residual 的搬运时间完美掩盖(Hide Latency),几乎实现了“免费”的 Sinkhorn 计算。
- Sinkhorn 循环中的
T.ParallelvsT.serial
在
Warp 0 的 Sinkhorn 循环中:for _ in T.serial(sinkhorn_repeat - 1):
T.reduce_sum(cm, row_sum, dim=1)
# ...注意这里的操作都是在 SRAM (Shared Memory) 或 Register 中进行的。
T.reduce_sum会编译成 Warp Shuffle 指令(__shfl_down_sync),这是 GPU 内线程间通信最快的方式,不需要访问内存。
- 这就是为什么迭代 20 次也不会慢的原因:因为数据从未离开过计算核心。
- DMA流水线 (Pipelining)-double_buffer
在
Warp 1-2 中:for i0_h in T.Pipelined(hidden_size // hidden_block, num_stages=2):
T.copy(residual[...], xs) # 1. 搬运
# ...
ol += pre * xl # 2. 计算TileLang 编译器会将这段代码展开为异步指令。当 GPU 的计算单元(CUDA Cores)正在计算第 i 块数据的加权和时,GPU 的数据搬运单元(Copy Engine)正在异步地将第 i+1 块数据从 HBM 拉取到 SRAM。这确保了显存带宽始终被占满,没有浪费任何时钟周期。
最后还有一个完整计算最终activation的fuse算子mhc_post_tilelang:

Engram


deepseek继续发力transformers的稀疏性
- 条件计算: 针对动态计算过程, MoE 通过条件计算来扩展模型容量, 针对每个Token, 通过路由器选择稀疏性的激活少数几个专家. 本质上它是一个动态的计算过程, 并且很好的匹配了上下文的逻辑依赖和一些复杂的任务.
- 条件记忆: 针对静态模式模式匹配, 依赖稀疏的查找操作来检索静态知识的嵌入, 例如可以将这些静态的知识储存起来, 类似于外挂一个词典, 通过快速查表的方式直接检索知识向量.
deepseek将MOE中推理建模世界静态知识的部分单独拿出来换成外挂的记忆模块,从此MOE部分也抽出了“cache”, 只不过这个cache是训练好的,在inference时刻不会变的。(以后在持续学习里,会不会根据实际情况更新这块cache呢?🤔)
结构如下:


论文的idea是通过在某些层里(attn+moe之前)外挂一个静态记忆模块,让这个充当世界知识存储库,这个东西不需要模型去推理得到,直接通过对input进行检索匹配就得到相关的信息然后给模型。这样的好处是:1. 静态memory可以继续scaling;2.减轻模型训练优化空间,模型只需要专注动态推理,关注更复杂的任务(比如数学,coding,创作等),3. 让模型的在相同层数下表征更强,因为只需要专心学习动态推理即可,不需要同时针对世界知识的记忆来引入协同梯度下降的干扰。
Engram 的作用和相关实验
定性分析Engram 的功能贡献,不让把 Engram 的输出,发现知识类榜单下降明显,仅剩29%–44%,推理类榜单下降不明显,保留81%–93% ,说明Engram主要影响世界知识的存储。

我们先看下Engram模块的好处,比如如何选择参数量,参数量固定的情况下是否每层都分摊放还是找几层放(source: https://mp.weixin.qq.com/s/im9x5pFTYRQcrph_etGOkA),对模型推理负担影响如何
- 固定参数量情况下Engram的占比规律
分析Engram(条件记忆)与 MoE(条件计算)之间的结构互补性,在总参数量和训练计算量固定时,MoE 专家和 Engram 记忆之间存在U 型定律。将大约 20%–25% 的非激活参数,重新分配给 Engram时,效果最佳,如下图a所示。

同时, 因为Engram 的 O(1) 查找开销,不随规模变大而增长,所以,在不考虑显存资源的情况下,符合Power Law,更大显存会持续带来收益,并且没有额外的计算量,如上图b。
- Engram 符合scaling law
在4种模型上进行了实验,Dense-4B、MoE-27B、Engram-27B、Engram-40B,其中激活参数均为3.8B,训练数据262B Tokens,详细参数设置如下:

整体结果上,总参数一样下,Engram-27B优于MoE-27B,同时扩大N-Gram Embedding参数,收益持续增加。

- Engram放在什么位置,总参数不变是少层多参数,还是多层少参数
如果只增加一个 Engram 模块,第 2 层是最佳位置,如下所示,同时消融实验,发现多分支集成、上下文门控、Tokenizer 压缩很重要,卷积和变成4-gram作用没有很明显。

多层会优于单层,将同样的 1.6B Engram 拆分并放置在 第 2 层和第 6 层,效果优于单层。
- 模型学习效率
与 MoE 基线相比,Engram 模型在各层下有更低的KL 散度,曲线下降得更陡峭,说明模型能更快地完成特征组合,见下图a
同时,发现Engram 第5层的表征,与MoE第12层的表征最为对齐,见下图b,c,说明Engram让浅层网络获得了深层网络的表征能力,有效的增加了模型的深度。而且浅层加Engram效果更好

什么是CKA (Centered Kernel Alignment,中心化核对齐):


- Engram对推理延迟的影响
因为Engram的N-Gram Embedding的逻辑,只依赖于输入 Token,所以在计算之前就可以异步获取好,同时,自然语言 N-grams是长尾分布的,对于高频的Embedding可以缓存到GPU上,在推理阶段,基本上延迟很小。
下表展示的吞吐降低了5%不到,但是句子长度比较小,长文本影响待定。
但是因为这个查表可以提前做好,因为也可以做分离加异步的工程操作来减少device阻塞

Engram的执行流程
代码中提到了mHC,那么attn和moe模块可能都用mHC这种流形约束超连接的方式
大致的流程如下:input_ids—>词表压缩—>multi-head hash and embedding —> fusion query hidden-state —> gated value —>non-linearity conv1d + act —→ memory output
从input_ids检索到的记忆 e_t是静态的,缺乏上下文适应性,进一步引入了类似 Attention 的门控机制,上下文感知门控。
Query (查询),使用当前的隐藏状态h_t (包含了之前的全局上下文)作为动态查询。
Key&Value (键值),检索到的静态记忆 e_t作为 Key 和 Value 的源,通过映射k_t = W_k@e_t, v_t = W_v@e_t获得真实键值内容,对 Query 和 Key 应用 RMSNorm,计算门控 :alpha_t = sigmoid[(RN(h_t)^T * RN(k_t)).sum(dim=-1) / sqrt(d) ], 门控后的输出为 v_t = alpha_t * v_t, 注意q k v之间的计算是elenemt-wise乘积+sum(dim=-1),非矩阵运算。
为了扩大感受野并增强非线性,还引入了卷积层 Y = SiLU(Conv1D(RN(V))) + V
Engram后就是标准的Attention与MoE.
假设B=1, L=12, HC=4 (mHC宽度), D=1024, Engram整体的计算过程如下:
词表压缩
标准的Subword分词器的首要目标是无损地重构原始文本. 这个特性导致了一个问题: 语义上完全相同或高度相似的词, 仅仅因为表面形式的微小差异 (如大小写, 前导空格), 就可能被赋予完全不同的 token ID (transformers苦tokenizer久矣!)
这种"一义多词"的现象导致了:
- 参数效率低下: 模型需要为这些语义相近的 token 分别学习嵌入, 浪费了参数.
- 数据稀疏: 模型在文本中见到
Apple的次数和见到␣apple的次数是分开计数的, 这使得学习它们的共同语义变得更加困难.
- N-gram 空间爆炸: 对于 Engram 这种依赖 N-gram 的模块, 这种冗余会不必要地大大增加 N-gram 组合的数量.
Engram通过多对一的映射, 它将多个原始 token ID "折叠"到一个单一的规范 ID 上.
具体实现方式: 这个映射是基于文本等价性的. 具体来说, 它会对每个 token 对应的文本进行一系列规范化操作, 例如:
- NFKC 规范化: 一种 Unicode 规范化形式, 可以统一全角/半角字符、不同的符号变体等.
- 小写转换: 将
Apple和apple统一为apple.
- 去除特殊符号或前导空格.



最后将原始的 128k 词表压缩了 23%
基于多头hashing的N-gram
什么是N-gram?
N-gram 模型是自然语言处理中最基础、最经典的概率语言模型之一。在深度学习和 Transformer(如 GPT)出现之前,它是处理文本预测和语音识别的主流方法。 在现实中,一句话的第 100 个词可能与之前的任何一个词有关,但为了计算方便,N-gram 做了简化的假设(马尔可夫假设)。简单来说,N-gram 模型的核心思想是:假设一个词出现的概率,只与它前面 N-1 个词有关。
N 的选取是一个超参数,以 “Only Alexander the Great could tame the horse Bucephalus.” 为例:
- Unigram (1-gram):
- 假设每个词的出现都是独立的,完全不看之前的词。
- 对应:P(wt)。
- Bigram (2-gram):
- 假设当前词只与之前的一个词有关,模型会根据 “Alexander” 这个词去查找,发现后面接 “the” 的概率最高。
- 对应:P(wt | wt-1)。
- Trigram (3-gram):
- 假设当前词只与之前的两个词有关,模型会根据 “Alexander the” 这两个词去查找,发现后面接 “Great” 的概率最高。
- 对应:P(wt | wt-1, wt-2)。
N-gram 模型本质上就是一个巨大的统计计数表。比如训练一个 Bigram 模型,就是要统计每一个词对 (wi-1, wi) 出现的次数。如果 “Alexander the” 在数据里出现了 100 次,而 “Alexander” 总共出现 200 次,那么当看到 “Alexander” 时,下一个词是 “the” 的概率就是 50%。
N-gram 模型的优缺点也比较典型:
- 优点:完全基于统计,预测就是查表,速度极快 O(1);除此之外可解释性也很强。
- 缺点:如果 N 比较大,整个统计表可能非常稀疏,模型泛化能力较差;除此之外,无法解决长距离依赖的问题。
如果使用传统的查表法,N-gram 的表大小会呈指数级增长,假设词的个数为 V,则:
- Unigram (1-gram):表大小为 V。
- Bigram (2-gram):表大小为 V^2。
- Trigram (3-gram):表大小为 V^3。
然而,实际上很多组合永远不会出现,也就是 N-gram 组合是非常稀疏的。基于哈希的 N-gram 正是为了解决这个空间复杂度浪费的问题,并保持高效的查询效率,其不预先为所有组合分配索引和空间,而是使用 Hash 只为遇到过的组合分配。
对于一个序列 (t0, t1, t2, …, tn-1),可以使用如下的 Polynomial Rolling Hash (多项式滚动哈希)方式计算其哈希值:
H(t0, t1, t2, …, tn-1) = an-1 * t0 + an-2 * t1 + an-3 * t2 + … + a0 * tn-1
为了避免出现极大的哈希值,通常还会对上述结果取模(M),对应:
H(t0, t1, t2, …, tn-1) = (an-1 * t0 + an-2 * t1 + an-3 * t2 + … + a0 * tn-1) mod M
a 和 M 的选择至关重要,与哈希冲突密切相关。
在传统的数据结构中,可以使用“拉链法”或“开放寻址法”等解决哈希冲突的问题,以保证即使不同的 Key 有相同的哈希值,也能得到正确的 Value。然而,这种方式并不适合 GPU,GPU 更喜欢固定大小、连续内存的静态数组,在 GPU 上做大规模的“指针跳转”或“动态链表遍历”效率极低,会打破并行性;除此之外还可能频繁分配存储,导致碎片化问题。
因此在 GPU 中通常会避免哈希冲突的出现,并且如果 Hash 值是 Index,还会取模(Size)的方式限制大小

假设我们的句子是:"The cute cat"
假设分词器(Tokenizer)将其转换为 ID:
input_ids=[101, 202, 303](分别对应 "The", "cute", "cat")
pad_id(填充符) =0
- 序列长度 L=3
Engram 模型通过“向右移位”来获取历史信息。因为 LLM 是预测下一个词,所以当前的 N-gram 必须由“当前词”和“之前的词”组成(不能看后面,否则就剧透了)。
第一步:生成移位视图 (Shifted Views)
代码中的
shift_k 函数会生成不同时间步的序列:时间步 (Sequence) | ID 0 ("The") | ID 1 ("cute") | ID 2 ("cat") | 备注 |
原始输入 (Shift 0) | 101 | 202 | 303 | 当前词 |
右移一位 (Shift 1) | 0 (Pad) | 101 | 202 | 前 1 个词 |
右移两位 (Shift 2) | 0 (Pad) | 0 (Pad) | 101 | 前 2 个词 |
第二步:组合成 2-gram (Bigram)
2-gram 是由 (当前词, 前1个词) 组成的。
在代码中,它将 Shift 0 和 Shift 1 结合起来计算哈希。
- 位置 0 ("The"): 看到
(101, 0)-> 实际上只有 "The"
- 位置 1 ("cute"): 看到
(202, 101)-> 对应短语 "The cute"
- 位置 2 ("cat"): 看到
(303, 202)-> 对应短语 "cute cat"
第三步:组合成 3-gram (Trigram)
3-gram 是由 (当前词, 前1个词, 前2个词) 组成的。
它将 Shift 0, Shift 1, Shift 2 结合。
- 位置 0:
(101, 0, 0)
- 位置 1:
(202, 101, 0)
- 位置 2:
(303, 202, 101)-> 对应短语 "The cute cat"
输入数据流经
NgramHashMapping 时的形状变化过程:- 输入 (
input_ids): - 形状:
[Batch=1, Length=3] - 内容:
[[101, 202, 303]]
- 移位与哈希 (Internal Calculation):
- 模型并行计算 2-gram 和 3-gram。
- 假设 Bigram 分配了 8 个头,Trigram 分配了 8 个头。
对于 Bigram (N=2) 在位置 t:
即:
代码中通过循环
for k in range(1, n): mix = np.bitwise_xor(mix, tokens[k] * multipliers[k]) 实现- 输出 (Hash Indices):
- 形状:
[Batch=1, Length=3, Total_Heads=16] - 解释:
- 对于句子中的每一个位置 (比如 "cat" 这个位置),模型现在拥有了 16 个特征索引。
- 前 8 个索引代表 "cute cat" (2-gram) 的各种哈希特征。
- 后 8 个索引代表 "The cute cat" (3-gram) 的各种哈希特征。
论文中没有直接采用多项式滚动哈希,而是采用了一种近似方法 (多头滚动哈希):
H(t0, t1, t2, …, tn-1) = (m0 * t0 ⊕ m1 * t1 ⊕ m2 * t2 ⊕ … ⊕ mn-1 * tn-1) mod M
也就是用一个预先生成的随机乘数 mk 代替 an-1-k;并使用 ⊕(位异或 XOR)替代加法。这样做有几个好处:
- 确定性和随机性的结合:
- 确定性:固定层,固定输入,固定的 N-gram,其对应的哈希值永远是一样的。
- 随机性:不同层,不同的 base_seed, 会生成不同的随机乘数 mk(每一层都不同,每个 N-gram 长度不同);此外,每个 Head 会有不同的 M(互不相同的质数);这样可以确保不同层、不同 Head 具有不同的 Hash 值,并且具有位置敏感性:(m0*A)⊕(m1*B) ≠ (m0*B)⊕(m1*A) 。
- 整数乘法和 XOR 都非常高效。
每个layer的乘数不一样:

每个layer的每个gram的每个head找不同的素数(prime)



这个多头hash机制非常简洁高效,因此词表数量很大,而且带有N-gram,因此hash冲突概率很大,deepseek为此做了如下权衡:
- 多头hash,如果一个头hash碰撞了,还有其他头可以兜底
- hash 取模为素数,可以有限提高hash位置利用率,减少碰撞风险,且每个头的模不一样
- 即使hash冲突,后面的门控fusion也可以根据相关性调节检索出的记忆的采纳程度。
为什么素数可以减少hash碰撞?
选择素数主要有两个核心原因:最大限度减少哈希冲突(Collisions) 和 确保多头之间的差异性。
- 单头层面:利用素数的不可整除性,打破输入数据的规律,让哈希冲突均匀分布,最大化 Embedding 空间的利用率。
- 多头层面:利用不同素数互质的特性,确保各 Head 之间的哈希映射是正交(不相关)的,通过多视角的组合来消除歧义。
- 破坏数据中的规律性,均匀分布 (Uniform Distribution)
自然语言数据或计算出的哈希值通常不是完全随机的,它们往往包含某种模式或周期性(比如某些哈希算法生成的数值可能是 2 的倍数,或者输入的 ID 有固定的步长)。
如果 Embedding 表的大小 是一个合数(尤其是 2 的幂,如 1024),这些模式就会与 的因子发生“共振”,导致数据聚集在某几个特定的索引上,而其他索引完全闲置。
举个直观的例子:
假设你的哈希算法产生的数值碰巧都是 4 的倍数(如 4, 8, 12, 16, 20...)。
- 情况 A:表大小是合数 12
- (冲突!)
- 结果:所有的数只落在索引 0, 4, 8 上。这 12 个坑位中,只有 3 个被利用了,冲突率极高,空间浪费严重。
- 情况 B:表大小是素数 13
- 结果:数据被均匀地“散射”到了整个表中,没有明显的聚集。
结论:素数没有除 1 和自身以外的因子,这使得它能像“粉碎机”一样,把带有周期性规律的输入打散,均匀填满整个 Embedding 表。
- 多头互补 (Orthogonality across Heads)
Engram 使用了多头(Multi-Head)机制。代码中特意寻找不同的素数分配给不同的 Head:
# 代码片段:为每个 Head 寻找下一个素数
found_prime = find_next_prime(current_prime_search_start, seen_primes)
seen_primes.add(found_prime)这背后的数学原理与中国剩余定理 (Chinese Remainder Theorem) 有关。
- 目的:我们希望不同的 Head 犯“不同的错误”。
- 场景:假设 N-gram A ("apple pie") 和 N-gram B ("banana split") 在 Head 1 中发生了哈希冲突(撞车了)。
- 合数的问题:如果 Head 2 的大小 和 有公约数(比如 ),那么在 中撞车的两个数,很有可能在 中继续撞车。
- 素数的优势:如果 和 是不同的素数(互质),那么在 Head 1 中撞车的两个 N-gram,在 Head 2 中再次撞车的概率极低(约为 )。
这就好比用多层不同孔径的筛子:
- 第一层筛子(Head 1, 素数 P1)没分清 "apple" 和 "banana"。
- 第二层筛子(Head 2, 素数 P2)孔径完全不同,立刻就把它们分开了。
- 最终效果:模型综合所有 Head 的信息后,就能唯一确定这个 N-gram 的身份,即使没有一个巨大的完整词表。
多头hashing大致流程如下:创建移位视图 (Shifted Views)—>滚动哈希混合 (Rolling Hash Mix)—>多头取模 (Multi-Head Modulo)—>堆叠(stacking)
multi head embedding
每个hash head经过各自的embedding table,变成hidden vector. Engram为了工程效果将其将逻辑上独立的多个 Embedding 表,物理上合并为一个巨大的 Embedding 表进行管理。这种设计通常被称为 "Fused Embedding" (融合嵌入),在推荐系统和多模态模型中非常常见。
如果不使用这个类,我们需要为每一个 Hash Head(比如 16 个头)分别创建一个
nn.Embedding 对象。- 低效做法:
ModuleList([nn.Embedding(...) for _ in range(16)]) - 缺点:这会导致 CUDA kernel 启动次数过多(16 次小查询),显存碎片化,且并行效率低。
- 高效做法 (
MultiHeadEmbedding):nn.Embedding(Total_Size, ...) - 优点:一次 CUDA kernel 启动即可完成所有头的查表操作,显存连续。
如下图代码所示,把每个hash head合并成一个大的embedding table, 然后为每个head准备一个offset即可实现偏移查阅。其中offset是每个hash head embedding table的大小,也就是我们上一节做hash取的模M, 为那个素数prime。


上下文感知门控(Context-aware Gating)
门控过程是一个简化版本的点积注意力

q k的计算是token向量的点积,目的是根据动态的query hidden states,来判断当前每个token状态离检索出来的静态记忆向量的相近程度,
最后通过 将计算出的门控值 逐元素乘以 Value 向量 .
- 如果 表示上下文与内存高度匹配, 检索到的信息 将被几乎完整地保留.
- 如果 , 表示上下文与内存不匹配, 检索到的信息 将被完全抑制, 避免了噪声或歧义信息的污染.
针对mHC技术的融合,论文采取部分共享的策略,并不是每个参数都根据m分支单独创造一个,这个猜测是为了表达和效率的trade-off
完全共享的部分 (Shared Components):
- 稀疏嵌入表 (Sparse embedding table): 所有 N-gram 的嵌入表是全局共享的. 这部分是 Engram 参数量的大头, 共享它可以极大地节约内存.
- Value 投影矩阵 : 所有分支共享同一个 .这意味着从静态内存 中提取的 "信息内容" () 对所有分支来说是统一的.
分支特化的部分 (Branch-specific Components):
Key 投影矩阵 : 每个分支 m拥有一个独立的 Key 投影矩阵 .
- k和v的投影乘法可以fuse成一个矩阵乘法,加快效率
- 整体的forward操作开销都比较小,完全可以用server cpu来承担,比如使用intel avx512 smid指令实现点积或者amx-bf16实现linear乘法和conv1d,这样通过input_ids查询静态记忆就就可以由另一个额外的cpu节点承担,这个节点配备了大量的SSD和DRAM,查询时直接算好然后通过网络传输到目标节点,然后目标节点启动async host-device copy
update: 2026.1.23日,Intel中国官方公众号发了团队内对Engram linear/conv 上至强cpu加速的实验,原文在此

在value之后还增加了一个conv1d模块增强非线性

需要注意的是,这个卷积是Depthwise Causal Convolution,这样每个 Token 都会融合前面多个 Token 的信息,也就相当于提取了当前位置及之前少量 Token 的局部特征,不能看到后面的token特征(看173行的截断)


- 实际上对于每个 mHC 输入都是独立处理的,不过为了高效实现(减少 Kernel Launch,提升并行性),这里会将其 Concat 到一起处理。
- Depthwise Causal Convolution:
- groups=total_channels,表示为 Depthwise Convolution,每个 Channel 只和自己的卷积核计算,Channel 之间不混合。
- dilation 被设置为 max_ngram_size=3,实际上每个 Token 已经包含 N-gram 信息,因此可以采用空洞卷积,增大感受野。
- kernel_size:默认为 4,和 dilation 共同决定了感受野,也就是融合的 Token 个数。
- Padding:卷积核 Padding,实现 Causal 特性,每个 Token 看不到之后的 Token
5个token的conv1d图解展示:
通过配置padding, stride和截断来保证token因果性
- 输入序列 (L): 5个 Token,我们标记为
[T0, T1, T2, T3, T4]
- 卷积核大小 (K): 4 (要采样4个点)
- Dilation (D): 3 (采样点之间间隔 2 个空位,步长为 3)
- Padding 计算:
这意味着,我们在输入的左侧必须补充 9 个 0,才能保证因果对齐。
1. 准备数据:左侧填充 (Left Padding)
PyTorch 会在输入前面加上 9 个 0。实际输入给卷积层的序列 (索引从 -9 到 4):
[0, 0, 0, 0, 0, 0, 0, 0, 0, T0, T1, T2, T3, T4]
2. 卷积采样过程
卷积核 K=4, D=3 意味着它覆盖的总跨度(感受野)是: 个位置。
它会提取这 10 个位置中的第 1、4、7、10 个值。
让我们看看为了计算输出 Y_0 到 Y_4,卷积核分别“看”了哪些位置。
图例说明:
✅= 卷积核采样的点 (权重不为0)
_= 卷积核跳过的点 (Dilation 空洞)
|= 当前输出的时间步截止线 (因果边界)
时刻 t=0 (计算输出 Y_0)
我们希望 Y_0 只能看到 T_0 及以前的数据。
卷积核对齐到 T_0。
Plaintext
数据带: 0 0 0 0 0 0 0 0 0 T0 T1 T2 ...
采样点: ✅ _ _ ✅ _ _ ✅ _ _ ✅ | (截止线)
^ ^ ^ ^
索引: -9 -6 -3 0
值: 0 0 0 T0- 结果:
- 因果性: ✅ 只看到了 T_0 和填充的 0。
时刻 t=1 (计算输出 Y_1)
卷积核向右滑动 1 格,对齐到 T_1
数据带: 0 0 0 0 0 0 0 0 0 T0 T1 T2 ...
采样点: ✅ _ _ ✅ _ _ ✅ _ _ ✅ |
^ ^ ^ ^
索引: -8 -5 -2 1
值: 0 0 0 T1- 结果:
- 因果性: ✅ 只看到了 T_1 和填充的 0。
时刻 t=3 (计算输出 Y_3) —— 关键点来了
卷积核对齐到 T_3。让我们看看 Dilation 是如何抓取历史信息的。
数据带: 0 0 0 0 0 0 0 0 0 T0 T1 T2 T3 T4 ...
采样点: ✅ _ _ ✅ _ _ ✅ _ _ ✅ |
^ ^ ^ ^
索引: -6 -3 0 3
值: 0 0 T0 T3- 采样到的值:
[0, 0, T0, T3]
- 结果: Y_3 利用了当前时刻 T_3 和 3 个步长之前的 T_0 的信息(T1, T2属于T0的3-gram)。
- 因果性: ✅ 卷积核最右端停在 T_3,没有碰到 T_4。
时刻 t=4 (计算输出 Y_4)
卷积核对齐到 T_4。
数据带: 0 0 0 0 0 0 0 0 0 T0 T1 T2 T3 T4 ...
采样点: ✅ _ _ ✅ _ _ ✅ _ _ ✅ |
^ ^ ^ ^
索引: -5 -2 1 4
值: 0 0 T1 T4- 采样到的值:
[0, 0, T1, T4]
- 结果: Y_4 利用了 T_4 和 T_1 的信息。
- 因果性: ✅ 严格截止于 T_4。
3. 为什么后面的数据会被切掉?
PyTorch 的卷积操作还会继续向右滑动计算(因为右边也有 Padding),产生 Y_5, Y_6... 等等。
比如计算 Y_5 时,卷积核会覆盖 [..., T2, T5]。
但是,代码中紧接着做了一步操作:
y = y[..., :5] # 只取前 5 个结果这就强行把所有为了“对齐边缘”而产生的、看到了未来的 Y_5, Y_6... 全部丢弃了。


