DeepSeek V4.1 全链路主时序:一条请求从压测端到首字返回的 57 步(四阶段)
从 aisbench 打出第一条请求,到首字返回、decode 循环、后台横切——启动加载、prefill、decode、后台四阶段 57 步一张时序图走完——认模型入编为步骤 3;补充节展开它的源头:models/__init__ 的 24 条模型注册表,载入权重前就定下哪个模型用哪套实现
读前须知:八个名词 30 秒(更多见文末术语速查)
- EngineCore / NPUWorker / NPUModelRunner:vLLM 上游的执行三级——引擎核心进程→每卡执行进程→每卡运行器;插件里对应 worker/ 与 model_runner 两层目录;
- BalanceScheduler(DPLB):排班员——决定本轮算哪些请求(DP 侧的负载均衡器);
- DSA-CP:DeepSeek 稀疏注意力在多卡间的序列并行通信模式(attention 的输入按序列切片、算前聚算后散);
- MTP / dspark / dflash:模型架构里的投机解码草稿头(MTP);dspark、dflash 是两种草稿实现的名字;
- EPLB:MoE 专家并行的负载均衡——按专家热度重摆专家副本;
- prefix cache / chunkPrefill:算过的前缀直接复用 KV 块;长 prefill 切成多块分轮算;
- TTFT / TPOT:首字延迟 / 每字延迟——压测两端的两个核心读数;
- C8 / MLAPO:两个内部开关代号——C8=SFA 稀疏的 KV 存储块模式(enable_sparse_sfa_c8,随模型是否走 SFA 派生),MLAPO=MLA 相关融合优化开关(enable_mlapo,默认开)。
1. 全景:57 步四阶段主时序
这张图回答一个问题:一条请求从压测端打进来,到首字返回、再到流式吐完,路上每一步发生了什么、发生在哪个角色手里。横向是十条泳道——从最左的压测客户端,穿过 tokenizer、引擎(EngineCore)、排班(BalanceScheduler)、KV 块管理,到 Worker、运行器、模型实现,最后落在 parallel_state(HCCL 通信)与 torch_npu(CANN 算子)两件底座上;纵向按时间推进,分四个阶段。
图 1 · 全链路主时序:启动加载 → prefill → decode → 后台横切,57 步——认模型入编为步骤 3(宽图可左右滑动,点右上「全屏查看」整幅浏览)
全链路主时序 · 全屏视图——滚动查看全图;Esc 或点右侧按钮退出
阶段 0 · 启动加载(步 1 至 9):把"能算"准备好——torch_npu/HCCL 初始化、进程组拉起、认模型(步 3:24 条架构名→实现类注册进上游表)、权重载入(多线程/lazy·NZ·W8A8·细粒度切分)、16 卡通信组、预编译与绑核、专家初始摆放、显存 profiling 建 KV 块池,最后探活就绪。这一阶段的关键产物是 KV 池与块表——后面 prefill/decode 都在它上面分配。
阶段 1 · prefill(步 10 至 40):TTFT 的主体。HTTP 请求进来→文本转 token→引擎排队→排班→前缀缓存查命中(命中即复用 KV 块)→未命中则全量 prefill,长文切块分轮(图里 3.5K 长文切 4 块)→分配 KV 块写块表→下发执行→每卡只算 220 行的前向(步 21 至 33:SP 聚散、DSA-CP 三段流水、SFA 只算 topk 相关块、MoE 一笔融合、logits 分片归并)→采样挑首 token→一路回传→步 40 首字到手,TTFT 到此为止。
阶段 2 · decode 循环(步 41 至 52):每步先查 stop 条件,再为新 token 追加 KV 槽位;decode 批下发后,MTP 草稿连猜 3 字、验证器留正确前缀,一步产出 1 至 4 个 token;增量结果流式回传,EOS 或达上限时释放 KV 块收尾。
阶段 3 · 后台横切(步 53 至 57):与前台并行的几条侧线——每 600 步统计专家热度、热专家加副本重摆并搬权重(EPLB);PD 分离部署时 KV 跨实例搬运(本链路未启用即空转);指标上报与周期探活。
2. 阶段 0 的认模型一步:模型注册表
时序图把认模型画为步骤 3(浅蓝框),其后步骤全部顺延一位、全图 57 步——权重还没载之前,哪个模型用哪套实现就已经登记定死。翻 vllm-ascend 源码的 models/__init__.py,会看到 24 条"架构名→实现类"的登记——这正是"哪个模型、应该用哪个配置"的答案所在:
图 2 · 阶段 0 放大:认模型一步(图 1 的步骤 3)——入口点→24 条注册表→查表取实现类
# setup.py:519-526 —— 插件把自己挂进 vLLM 的两排钩子
entry_points={
"vllm.platform_plugins": ["ascend = vllm_ascend:register"],
"vllm.general_plugins": [
"ascend_kv_connector = vllm_ascend:register_connector",
"ascend_model_loader = vllm_ascend:register_model_loader",
"ascend_service_profiling = vllm_ascend:register_service_profiling",
"ascend_model = vllm_ascend:register_model", # ← 认模型挂在这
],
}
# vllm_ascend/__init__.py:100-105 —— 入口点进来后的包装
def register_model():
from .models import register_model
register_model()
# vllm_ascend/models/__init__.py —— 24 条登记(节选)
ModelRegistry.register_model(
"DeepseekV4ForCausalLM",
"vllm_ascend.models.deepseek_v4.model:AscendDeepseekV4ForCausalLM")
ModelRegistry.register_model(
"KimiLinearForCausalLM",
"vllm_ascend.models.kimi_k3:AscendKimiLinearForCausalLM")
ModelRegistry.register_model(
"GlmMoeDsaForCausalLM",
"vllm_ascend.models.deepseek_mtp:AscendGlmMoeDsaForCausalLM")
ModelRegistry.register_model(
"Qwen3DSparkModel",
"vllm_ascend.models.qwen3_dspark:AscendQwen3DSparkForCausalLM")
ModelRegistry.register_model(
"DeepSeekV4MTPModel", "vllm_ascend.models.deepseek_v4.mtp:DeepSeekV4MTP")
# 全表 24 条;KimiK3ForCausalLM 是 KimiLinear 的兼容别名,指同一实现
# 上游 vllm/model_executor/models/registry.py:1121 —— 注册语义
# model_cls 走 "<module>:<class>" 字符串懒导入;同名架构直接覆盖:
# "Model architecture %s is already registered, and will be
# overwritten by the new model class %s."
# 触发点(每进程各一遍,plugins_loaded 防重):
# vllm/engine/arg_utils.py:869 EngineArgs.__post_init__
# vllm/v1/engine/core.py:125 EngineCore.__init__
# vllm/v1/worker/worker_base.py:269 WorkerBase.__init__
机制链条四步(源码逐行核对过):①插件安装时 setup.py 把 ascend_model = vllm_ascend:register_model 登记进 vLLM 的 general_plugins 入口点;②触发点有三处、每个进程各执行一遍——前端解析引擎参数时(EngineArgs.__post_init__,arg_utils.py:869)、EngineCore 初始化时(v1/engine/core.py:125,源码注释原话"plugins need to be loaded at the engine/scheduler level too")、每卡 Worker 初始化时(v1/worker/worker_base.py:269),plugins_loaded 标志保证每进程只跑一次;③包装函数执行 models/__init__.py 里的 24 条 ModelRegistry.register_model——注册的是 <module>:<class> 字符串、用到时才真正 import(懒导入,防 fork 子进程重复初始化 CUDA),且同名架构直接被新类覆盖(registry.py 源码注释"overwritten by the new model class")——插件实现类就是这样顶掉上游默认的;④时序图步骤 4 载入权重时,上游按权重 config.json 的 architectures 字段查这张表——查到 AscendDeepseekV4ForCausalLM,后续步骤 21 至 33 的算子路径由此定型。登记之后还有第二层选择:attention 后端由 platform.py 按 hf_text_config 的字段(如 index_topk)决定走哪套注意力实现。
排查提示:模型没走上预期优化路径时,先查这两层
- 第一层查注册表:权重 config.json 的 architectures 名是否在 24 条里——不在表里,上游走默认实现,算子路径完全不同(DeepSeek V4.1 走的是
AscendDeepseekV4ForCausalLM 这条登记);
- 第二层查后端条件:platform.py 的选路按模型字段判定(如 index_topk 决定稀疏注意力),字段对不上就走通用后端;
- 两层都过了,才回到时序图对应步骤看执行路径——排查顺序与图的顺序一致:先认模型,再看执行。
3. 术语速查
| 术语 | 本文语境下的含义 |
| EngineCore / Worker / ModelRunner | vLLM 上游执行三级:引擎核心进程→每卡执行进程(NPUWorker)→每卡运行器(NPUModelRunner) |
| BalanceScheduler(DPLB) | 排班器——DP 侧负载均衡,决定本轮算哪些请求 |
| DSA-CP | DeepSeek 稀疏注意力 + 序列并行的通信模式(算前聚、算后散、head 归属互换) |
| MTP / dspark / dflash | 投机解码草稿头(MTP);dspark、dflash 为两种草稿实现名 |
| EPLB | 专家并行负载均衡——按热度给热专家加副本并重摆 |
| prefix cache / chunkPrefill | 前缀 KV 块复用 / 长 prefill 切块分轮执行 |
| C8 | SFA 稀疏的 KV 存储块模式(enable_sparse_sfa_c8 / li_c8,随模型是否走 SFA 派生,ascend_config.py) |
| MLAPO | MLA 相关融合优化开关(enable_mlapo,默认开)——图中步 21 归一化+cast 融合属它 |
| fused_mc2 / DFFC | MoE 大融合开关 / DispatchFFNCombine 算子(dispatch+专家 FFN+combine 一体) |
| TTFT / TPOT | 首字延迟 / 每字延迟——压测两端的两个核心读数 |
来源:时序主图为方案 B 稿件收录并经三项清理——口径、悬挂引用、行号/符号逐条对本地源码核验订正(13 处,泳道内 L348/L103/L363/L1041 与步 28/52 原本即精确命中);mm_preprocess.py 全路径 models/deepseek_v4/mm_preprocess.py、patch_balance_schedule.py 全路径 patch/platform/patch_balance_schedule.py(泳道小字限宽用短名);补充节与名词定义以本地 vllm-ascend 源码实测为准——setup.py:519-526(入口点)、vllm_ascend/__init__.py:100-105(包装)、vllm_ascend/models/__init__.py(24 条注册表)、ascend_config.py:481 与 753-758(MLAPO、C8);上游 vllm 侧 vllm/plugins/__init__.py(load_general_plugins)、v1/engine/core.py:125、v1/worker/worker_base.py:269、model_executor/models/registry.py:1121(register_model 覆盖语义)· 行号以当期源码为准