DeepSeek V4.1-Flash 登陆昇腾:框架支持与 Engram 主机卸载

DeepSeek V4.1-Flash 已合入 main PR #16925 · commit 0deca3181 · 2026-09-21 合入 · 73 文件 +10554/-665 · 代码基线 a71b766ce

一、背景:DeepSeek 发了个新模型,昇腾为什么跑不动

2026-09-10,DeepSeek 发布 DeepSeek-V4.1-Flash: 552B 参数 MoE(每 token 激活 prefill 8B / decode 16B),训练 64K 稀疏注意力、外推 1M 上下文。 它的主线思路是把 KV Cache 压到极限,靠三件新架构武器:

武器一句话关键数字
CED
因果编码器-解码器
解码器的全局 KV 不由自己的 20 层逐层产生——输入先过 20 层因果编码器,再由末端隐状态一次性投影出解码器要用的全局 KV持久 KV ≈ V4-Flash 的 1/8
CSA2
压缩稀疏注意力 2
每层静态分派 Full / Reindex / Reuse 三种模式,跨层共享主 KV 与索引 K、复用 Top-K 索引;主 KV 用 FP4 存全局 KV 890 B/token
Engram
条件记忆
196B 参数的稀疏查表记忆:token 哈希命中才注入隐状态,不命中不花访存按需访问,不全驻显存

三者相乘,得到 890 字节/token 的全局 KV + 1M 上下文。上游 vLLM main 已带 V4.1 模型实现 (vllm commit 84030bbe)—— 但昇腾一处都跑不动。

读图指引:下面这张图回答一个问题——请求进来后,第一个断点在哪? 看那四条带 ✗ 的虚线:注意力后端、缓存分配、Engram 存储、DSpark 注册, 每一处都是"上游要、昇腾没有"。改动前四条全断(本图);改动后四条全通(图3)。
图1 · 改动前:V4.1-Flash 在昇腾上的四个断点
sequenceDiagram participant U as 用户 vllm serve V4.1-Flash participant V as 上游 vLLM(V4.1 模型代码) participant A as vllm-ascend 平台层 U->>V: 加载模型与配置(上游已支持) V->>A: 请求 V4.1 注意力后端 A--xV: ✗ 无 dsa_v41 后端 V->>A: 请求压缩/共享 KV 分配 A--xV: ✗ 无四槽层外置分配器 V->>A: 初始化 Engram 条件记忆 A--xV: ✗ 无 Engram 存储/主机卸载 V->>A: 注册 DSpark 草稿模型 A--xV: ✗ 无 V4.1 DSpark 适配 V--xU: 启动失败(前次合入已被回退)

二、前史:这个需求为什么是第二次提交

它不是一次成功的普通 PR。#16544 曾把完整框架集成首次合入,但与当周 main 产生集成冲突,被 #16905 回退—— 于是有了图1 里"改了却又没有"的空窗期。

读图指引:这张图回答"这个 PR 为什么看着眼熟"。看两条汇入线: 二次提交不是简单 revert 重放,而是踩着已合入的原生算子(#16422)与环形缓冲状态(#16890)这两块地基重新长出来。
图2 · 从回退到重新合入:两条地基线汇入二次提交
graph TD A["#16544 首次合入(完整框架集成)"] --> B["与当周 main 集成冲突"] B --> C["#16905 回退"] D["#16422 原生算子先行合入"] --> E["#16925 基于地基重新提交"] F["#16890 环形缓冲状态"] --> E C --> E E --> G["09-21 合入 main ✅ commit 0deca3181"]

能二次成功的关键:把大特性拆成"算子先行 → 框架跟上"两步走,二次提交只做框架集成、对齐当周 main、重建回归测试。 这也是这个 PR 体积高达 73 文件的原因——框架层一次补齐。

三、诉求与方案:在 A3 上完整跑起来,五件套补齐

诉求不是"能 import",而是教程里的完整部署形态:A3 双机 16 卡(DP4×TP8+EP)、 1M 上下文、block 128、W8A8 权重,能出 token、能开全量解码图、能跑 DSpark 投机 (教程)。 这决定了改动必然横跨模型、注意力、缓存、算子、Runner 五层——即"五件套":

件载体干什么
注意力执行边界attention/dsa_v41.py(1098 行)每层的三阶段执行:双流预处理 → 索引选择 → 双阶段稀疏注意力
模型与缓存models/deepseek_v41/(12 文件)拓扑层规划、MoE/视觉、四槽页分配、压缩环形缓冲
条件记忆models/deepseek_v41/engram/(7 文件)Engram 的 HBM 存储 + CPU 主机卸载 + 跨 DP 副本共享
投机解码models/deepseek_v41/dspark.py + spec_decodeDSpark 草稿模型,复用目标侧共享 SWA KV
平台接线patch/ + worker/model_runner_v1.py(+254 行)上游 config 注入、Engram 前向元数据、图输入定址

核心改动 3 项(★ 没有它会怎样)

★ 改动没有它会怎样
★ 新增 attention/dsa_v41.py注意力请求第一步就查无后端——图1 的第 1 个断点原样保留,服务起不来
★ 新增 models/deepseek_v41/(model.py 1647 行为主)上游 V4.1 权重在昇腾上没有可执行载体:拓扑规划、MoE、Engram 注入、视觉全无
★ 新增 models/deepseek_v41/cache_config.py(163 行)KV、索引 K、FP16 scale、滑窗四类缓存各自为战争抢显存——四槽"同页打包、一个块 ID 定位"的布局无从谈起

配套改动(7 项)

四、实现:先看执行链路,再读三段代码

读图指引:这张图回答"一个解码器层在昇腾上到底经过哪几步"。 loop 里的五步其实是三个阶段(图中色块):①预处理——Q 投影与 KV 写入双流并行; ②索引选择——QLI V3 给每个 token 挑 Top-K 位置(候选三模式); ③稀疏注意力——SparseFlashMla 滑窗一遍 + 压缩全局一遍。 层外的 Engram(命中注入)与 DSpark(草稿投机)是旁路,不占层内关键路径。
图3 · 改动后:一个解码器层的昇腾执行链路(三阶段)
sequenceDiagram participant R as ModelRunnerV1 participant M as DeepseekV41Model participant I as AscendDSAV41Impl participant K as AscendC 算子 R->>M: prepare_engram:设备端哈希+查表 M->>M: Engram 命中注入隐状态 loop 每个解码器层 rect rgb(238,242,255) note over I,K: 阶段① 预处理 I->>K: 双流预处理:Q 投影∥KV Cube end rect rgb(238,248,240) note over I,K: 阶段② 索引选择 I->>K: QLI V3(候选三模式) end rect rgb(255,242,238) note over I,K: 阶段③ 稀疏注意力 I->>K: SparseFlashMla:SWA+压缩双阶段 I->>I: 逆 RoPE → o_proj(图稳定缓冲) end end M->>M: 末层 hc_collapse(超连接收束) R->>R: DSpark 复用共享 SWA KV 投机
代码注释符号约定:⚡ = 昇腾特有的优化点 | ↳ = 承接上游/模型侧既有约定 | →NPU = 调用昇腾自研 AscendC 算子。
三段代码按 "先规划 → 再分配 → 再执行" 的顺序读。
代码 A(规划)· build_layer_plan:跨层共享拓扑(models/deepseek_v41/model.py:494,摘录自 a71b766ce)
看这段代码,只需抓一个点:每层的"KV/索引从哪来、要不要候选过滤",在启动时就静态算死了—— 原注释说得很直白:26 层复用 24 层 TopK,而 TopK 又是在 20 层候选块内算的。
    roles.append(
        DeepseekV41LayerRole(
            layer_idx=layer_idx,
            compress_ratio=ratio,
            kv_source=...,  # ↳ 该层全局 KV 来自哪个源层(CED:解码器 KV 由编码器源层投影)
            index_source=...,  # ↳ 该层稀疏索引来自哪个索引源层(跨层共享 TopK)
            is_candidate_source=layer_idx == candidate_source,  # ⚡分层索引器:候选池只由首个 Full 层构建
            # Consumer layers inherit the selection policy of their index
            # source.  For example, layer 26 reuses layer 24 TopK, and that
            # TopK was computed inside layer 20's candidate blocks.  ← 原注释
            uses_candidate_filter=index_source is not None and index_source > candidate_source,
            engram_slot=engram_slots.get(layer_idx),  # ↳ 该层是否挂 Engram 条件记忆注入
        )
    )
代码 B(分配)· get_layer_tuples:四槽层外置,KV/索引 K/scale 同页打包(models/deepseek_v41/cache_config.py:47,摘录)
看这段代码,只需抓一个点:物理页容量 = 槽内最大成员—— 全局 KV、索引 K、FP16 scale、滑窗/状态环同页打包,调度器的一个块 ID 同时定位四类缓存。
def get_layer_tuples(specs):
    """Return DSV4-style ordered layer tuples and their physical page sizes."""
    mla = {name for name, spec in specs.items() if isinstance(spec, AscendMLAAttentionSpec)}
    state = sorted((name for name, spec in specs.items() if isinstance(spec, CircularBufferSpec)), key=_layer_number)
    swa = {name for name, spec in specs.items() if isinstance(spec, AscendSlidingWindowMLASpec)}
    ...
    for slot_idx, kv_name in enumerate(full):
        prefix = kv_name.rsplit(".", 1)[0]
        index_name = prefix + ".indexer.k_cache"  # ⚡索引器 K 缓存与全局 KV 同槽配对
        index_spec = specs[index_name]
        kv_spec = specs[kv_name]
        aliases = ([state[slot_idx]] if slot_idx < len(state) else []) + target_swa[slot_idx :: len(full)]
        kv_bytes = kv_spec.unpadded_page_size_bytes
        index_bytes = index_spec.unpadded_page_size_bytes
        ...
        capacity = max(  # ⚡页容量=槽内最大成员:索引 K 与 FP16 scale 排在 KV 之后同页(压缩比2:64 行/131072B;压缩比1:128 行/147712B)
            kv_bytes + index_bytes,
            *(specs[name].unpadded_page_size_bytes for name in aliases),
        )
        layer_tuples.append((kv_name, index_name, *aliases))  # ⚡一个块 ID 同时定位四类缓存(层外置)
        page_sizes.append(capacity)
    return page_sizes, layer_tuples
代码 C(执行)· AscendDSAV41Impl.forward:三阶段串起来(attention/dsa_v41.py:538,摘录)
看这段代码,只需抓一个点:Q 投影与 KV 写入是双流并行的(⚡那一行,即图3 阶段①); 随后的两步依次落在阶段②(_select_sparse_indices)和阶段③(npu_sparse_flash_mla),其余都是收尾。
def forward(self, attn, positions, hidden_states, output: torch.Tensor | None = None):
    # ↳ 自定义算子调用方传入图稳定输出缓冲:O 投影结果直写,免去图边界的整张拷贝(⚡昇腾图优化)
    if output is None:
        output = torch.empty_like(hidden_states)
    forward_context = get_forward_context()
    if forward_context.attn_metadata is None:
        output.zero_()  # ↳ dummy 前向(剖析/捕获期)直接清零返回
        return output
    metadata = self._get_layer_metadata(forward_context.attn_metadata)
    self._prepare_inputs_and_caches(attn, hidden_states, metadata, forward_context.attn_metadata)
    num_tokens = metadata.swa.num_actual_tokens
    if num_tokens:
        positions = metadata.positions[:num_tokens]
        cos, sin = metadata.rope(attn.rotary_emb.layername, num_tokens)
        q, qr = self._prepare_queries(attn, hidden_states, positions, cos, sin, metadata)  # ⚡阶段①:双流 Q 投影∥KV Cube
        compressed_indices = self._select_sparse_indices(attn, hidden_states, qr, positions, cos, sin, metadata)  # ⚡阶段②:QLI V3 候选三模式
        attention_output = self._forward_attention(attn, q, metadata, compressed_indices)  # →NPU 阶段③:SparseFlashMla 双阶段
        torch.ops._C_ascend.inplace_partial_rotary_mul(  # →NPU:输出侧逆 RoPE 原地完成,不落临时张量
            attention_output.unsqueeze(1),
            cos,
            -sin,
            rotary_mode="interleave",
            partial_slice=[attn.nope_head_dim, attn.head_dim],
        )
    else:
        heads = attn.n_heads if getattr(attn, "enable_dsa_cp", False) else attn.n_local_heads
        attention_output = hidden_states.new_empty((0, heads, attn.head_dim))
    self._project_output(attn, attention_output, hidden_states, metadata, projected=output)
    return output

五、后续:三个跟进 PR 与部署形态

跟进 PR改了什么为什么
#17116只在 vLLM 0.29 以上的版本通道注册 V4.1 与其 DSparkv0.29 缺上游 V4.1 config/model 模块,import 即失败,连非 V4.1 模型都被拖垮
#17134Engram 主机头分片跨本地 DP 副本共享(SharedUvaBuffer)多副本各存一份主机侧权重,白白浪费内存
#17243Engram 平台补丁加模块可用性守卫无 vllm.config.engram 的上游构建在导入补丁时启动即崩(nightly 回归)

部署形态:A3 双机 16 卡 DP4 × TP8 + EP、--max-model-len 1048576、--block-size 128, tokenizer / reasoning-parser / tool-call-parser 三件套均为 deepseek_v41(教程)。

接力棒:到这里已经讲清"改了什么"。想知道"模型设计的红利还有哪些没兑现、下一步能优化什么", 请读续篇 专题:CED/CSA2 架构 × vllm-ascend 性能协助点(7 条)。

六、相关链接

首页