大模型结构与推理工程:从配置到 SGLang 热路径
为什么做大模型推理一定要懂模型结构?
本文围绕两个问题,文中的源码都基于sglang:
- 为什么推理工程师必须吃透模型结构与关键模块?
- 在 SGLang 代码里,这些结构分别落在哪、改错了会怎样?
1. 心智模型:config 是 serving 的类型系统
一句话:
| 你若只懂… | 却不懂架构,会踩的坑 |
|---|---|
| 「显存 = 参数 + KV」 | MLA/GQA/SWA 的 KV 字节公式完全不同,OOM 估错 |
| 「开 TP=8」 | num_heads / num_kv_heads / MoE intermediate 不整除直接起不来 |
| 「换 FlashAttention backend」 | MLA-only backend 喂 MHA 模型直接 ValueError |
| 「开投机加速」 | embed/vocab/hidden 对不齐,draft 共享失败或结果错 |
| 「量化省显存」 | QKV/gate_up packing、MoE block size 与 EP 不兼容 |
| 结论: 推理优化的合法搜索空间,由模型结构划定;SGLang 只是把这些约束写成了代码。 |
2. 从 HF 名字到 Python 类
2.1 加载链
--model-path
→ ModelConfig / get_config # configs/model_config.py
→ get_model_architecture # model_loader/utils.py
→ ModelRegistry.resolve_model_cls # models/registry.py
→ _initialize_model(model_class) # model_loader/loader.py
→ model.load_weights(...)
对应源码:
model_config.pymodel_loader/utils.pymodels/registry.pymodel_loader/loader.pyconfig.json里的"architectures": ["LlamaForCausalLM"]不是装饰,而是注册表主键。python/sglang/srt/models/下每个文件末尾声明:
EntryClass = [LlamaForCausalLM, Phi3ForCausalLM, ...] # llama.py
ModelRegistry 扫描 sglang.srt.models.*,把 EntryClass 名字映射到 nn.Module。没有原生实现时 fallback 到 Transformers 兼容封装。
2.2 为什么这关推理工程师的事?
- 起服务失败常见根因是:架构字符串对上了类,但
_derive_model_shapes没认出 MLA/GQA/hybrid,后续 KV/backend 全错。 - 加新模型时,不只是「抄一份 Llama forward」——必须声明正确的
EntryClass,并保证RadixAttention/ MoE / embed hooks 与真实结构一致。 - 官方「如何接入新模型」说明:support_new_models.md。
2.3 ModelConfig:架构归一化中枢
ModelConfig._derive_model_shapes(同上 model_config.py)根据 architectures 判定:
| 结果 | 影响 |
|---|---|
AttentionArch.MLA | 走 MLA backend + MLATokenToKVPool;读 kv_lora_rank / qk_rope_head_dim 等 |
AttentionArch.MHA(含 GQA) | 走 MHA backend + MHATokenToKVPool;num_key_value_heads 决定 KV 宽度 |
| Hybrid / SWA / DSA 等 | 额外 pool(SWAKVPool、DSATokenToKVPool…) |
DeepSeek V2/V3 等在代码里被 显式名单 标成 MLA(见 _derive_model_shapes 中对 DeepseekV3ForCausalLM 等的分支)。换模型族却照搬 Llama 的「按 head 估 KV」会错一个数量级。 |
3. ModelRunner 契约:引擎对模型类的期望
Scheduler 侧:TpModelWorker → ModelRunner(model_runner.py)。
3.1 标准 forward 签名(以 Llama 为模板)
模型类需要能被 Runner 这样调用(见 llama.py):
forward(
input_ids,
positions,
forward_batch: ForwardBatch, # 含 mode、KV loc、spec_info…
input_embeds=None,
get_embedding=False,
pp_proxy_tensors=None,
) -> LogitsProcessorOutput
ForwardBatch.forward_mode 区分 EXTEND / DECODE / TARGET_VERIFY / DRAFT_EXTEND_V2 等——同一次「跑模型」,因模式不同,attention 与 KV 写入完全不同。不懂结构就无法理解「为什么 verify 一次能验一整棵树」。
3.2 可选但关键的 hook
| Hook | 谁用 | 不懂结构会怎样 |
|---|---|---|
get_embed_and_head / set_embed_and_head | EAGLE 等投机 | draft/target 词表或权重对不齐 |
set_embed | EAGLE3 hidden 不一致时 | 错误共享导致维度炸 |
load_weights + packed_modules_mapping | 量化加载 | QKV 合包错位 |
get_model_config_for_expert_location | MoE EPLB | 专家位置元数据缺失 |
forward_split_prefill | chunked prefill | 长 prompt 路径不对 |
capture_aux_hidden_states | EAGLE3 | aux 层抓不到 |
3.3 Runner 初始化顺序(结构信息何时被消费)
dist init (TP/PP/EP 尺寸)
→ load_model(架构类 + 权重)
→ init_memory_pool(按 AttentionArch 选 pool)
→ init_attention_backend(按 MLA/MHA/hybrid 选 backend)
→ cuda graph capture(形状钉死:bs × tokens_per_req)
任一步读错架构字段,后面 capture 的 graph 都是「合法但错误形状」的定时炸弹。
4. Attention:MHA / GQA / MLA 如何改写整条热路径
4.1 层内统一入口:RadixAttention
radix_attention.py:各模型 Attention 子模块把
num_heads/num_kv_heads/head_dim/v_head_dim/layer_id
喂给RadixAttention,真正算子由 runtime 的 attention backend 执行(attention_registry.py)。
4.2 GQA 不是「小优化」,是 KV 布局参数
Llama 类:num_key_value_heads < num_attention_heads → GQA。
影响:
QKVParallelLinear的 partition 尺寸(Q 与 KV 宽度不同)MHATokenToKVPool每 token 字节:2 × (kv_heads/tp) × head_dim × dtype- TP 必须整除 heads / kv_heads(否则起服失败)
只背「总参数 70B」估不出 decode 显存;KV 宽度看的是 kv_heads,不是 query heads。
4.3 MLA:另一条宇宙
MLA(Multi-head Latent Attention)在 ModelConfig 里切到 AttentionArch.MLA 后:
| 维度 | MHA/GQA | MLA |
|---|---|---|
| KV pool | MHATokenToKVPool | MLATokenToKVPool |
| 每 token 内容 | 分离 K/V,按 kv_heads | 压缩向量:kv_lora_rank + qk_rope_head_dim 量级 |
| Backend | FlashInfer MHA 等 | FlashInfer MLA / TRTLLM MLA 等 |
| 错配后果 | — | 选错 backend 直接报错或 silent 错结果 |
DeepSeek 系还可能叠加 DSA 等稀疏 indexer(额外 index_head_dim、专用 pool)。这是「结构 → 系统」耦合的典型:算法论文里的投影维,变成了 serving 的 pool 类型枚举。 |
Backend 选型说明(官方文档):attention_backend.md。
4.4 Hybrid / SWA
部分模型交替 full attention 与 sliding window / linear attention:Runner 走 HybridLinearAttnBackend 或 SWAKVPool。此时「一层一个统一 KV 公式」不成立——必须按层类型读 config。
5. KV Cache:显存公式几乎全是架构字段
生产上 decode 显存大头常是 KV,不是权重。SGLang 在 pool_configurator.py / memory_pool.py 里按架构算 cell size。
5.1 直觉公式
# MHA / GQA(每层)
cell ≈ 2 * (num_kv_heads / tp) * head_dim * dtype_size
# MLA(每层,示意)
cell ≈ (kv_lora_rank + qk_rope_head_dim) * dtype_size # 布局见 MLATokenToKVPool
总 KV ≈ cell * num_layers * max_total_num_tokens
(再乘 draft 层比例、SWA 混合等)
5.2 和「懂结构」的关系
| 结构变化 | Serving 后果 |
|---|---|
| GQA kv_heads 减半 | 同 context 下 KV 显存近乎减半,可撑更大 batch |
| 换成 MLA | 不能用旧 GQA 表格估显存;backend 与 page 布局全换 |
| 层数 80 → 60 | 线性降低 KV;cuda graph / 延迟也变 |
| 开投机 | 再加一套 draft KV(层数常更少,见 eagle_draft_num_layers) |
| page_size / radix | 复用前缀,但 每 token cell 仍由结构决定 |
调 --mem-fraction-static / max_running_requests 之前,先问:当前是 MHA、GQA 还是 MLA?有没有 draft pool? |
6. MoE:参数容量与激活解耦后的系统代价
6.1 模型侧
MoE 把稠密 FFN 换成多个 expert + router。SGLang 中常见形态:
- Router:
Linear+ top-k(layers/moe/topk.py) - Experts:
FusedMoE(fused_moe_triton/layer.py) - 权重映射:
make_expert_params_mapping(HFexperts.{i}.gate/up/down→ 内部w13_/w2_)
6.2 系统侧(不懂结构就会「只会开 EP」)
| 架构字段 | 代码消费 | 影响 |
|---|---|---|
num_experts / num_experts_per_tok | FusedMoE、dispatcher | 每 token 激活量、A2A 体积 |
moe_intermediate_size | EP/TP 切分、量化 block 校验 | 不整除则起服失败 |
| 层内是否 MoE | EPLB get_model_config_for_expert_location | 专家放置与负载均衡 |
| Dense 的「加 TP 就近似线性」在 MoE 上不成立:要同时想 token 路由、All-to-All、expert 倾斜、EPLB。用户问「为什么 MoE 吞吐抖」,答案往往在结构(Top-K、expert 数、中间层宽)而不在「再加一张卡」。 |
Expert Parallelism 说明:expert_parallelism.md。
7. 投机解码:架构硬耦合
投机不是 Scheduler 外挂一个黑盒加速器,而是 两套(或更多)ModelRunner 在结构上对齐:
每个 decode 步:
draft ModelRunner ——猜树/链——► target ModelRunner 验整块
│ │
│ ▼
│ eagle_sample(accept + bonus)
▼
draft_extend(按接受结果把 draft KV 追上)
入口与共享逻辑见 eagle_worker_v2.py(init_lm_head、forward_batch_generation)。
7.1 必须对齐的结构点
| 点 | 代码落点 | 说明 |
|---|---|---|
| embed / lm_head | init_lm_head ← target get_embed_and_head | 默认共享;否则 draft 词表空间不一致 |
| hidden_size | EAGLE3 可与 target 不同 | 不同则往往只能 set_embed,不能整头共享 |
| Attention 类型 | draft/target 各自 KV pool | MLA target + 错误 draft 布局会挂 |
| 层数 | eagle_draft_num_layers | draft KV 预算按层数缩放 |
| vocab / hot_token_map | speculative token map | 小词表 draft 的结构扩展 |
7.2 EAGLE3 例:结构改写 forward
llama_eagle3.py:decoder 输入可变成 [embed; target_hidden],QKV 输入维到 2 * hidden_size,并依赖 target 捕获 aux hidden states。
这不是「调个步数」能概括的——draft 网络拓扑就是为目标投机设计的。
7.3 三套 CUDA Graph 也因结构而分
一步里三套固定形状 graph(不能共用一张):
| Runner | 模型 | 作用 | 形状直觉 |
|---|---|---|---|
| Draft decode | draft | 多步猜候选 | bs × topk(× steps) |
| Target verify | target | 一次验整棵树/链 | bs × draft_token_num |
| Draft extend | draft | 按 accept+bonus 补 KV | bs × num_draft_tokens |
形状来自 speculative_num_steps / num_draft_tokens / topk,必须与模型能表达的链/树一致。用法概述:speculative_decoding.md。 |
8. 量化:packing 形状跟着模块走
量化不是「把 Linear 统一 int8」:
| 模块类型 | 路径 | 结构敏感点 |
|---|---|---|
QKVParallelLinear / MergedColumnParallelLinear | packed_modules_mapping | QKV、gate_up 是否融合打包 |
FusedMoE | FusedMoEMethodBase | expert 的 w13/w2 与 block size |
| MLA 融合投影 | 部分 quant config 特殊 pack | fused_qkv_a_proj_with_mqa 等 |
| MoE + EP | check_quantized_moe_compatibility | moe_intermediate_size / moe_tp 要整除 weight block |
| 不懂「这一层是 fused QKV 还是分置、是 dense MLP 还是 MoE」,就无法判断某张量化 checkpoint 能否在当前 TP/EP 下加载。总览:quantization.md。 |
9. 架构旋钮 → 代码消费点速查
| 旋钮 | 常见 HF 字段 | SGLang 主要消费 | Serving 影响 |
|---|---|---|---|
| Hidden | hidden_size | Linear / embed / MoE | 激活与权重内存 |
| Layers | num_hidden_layers | KV layer_num、延迟 | KV 总容量 |
| Q heads | num_attention_heads | RadixAttention、TP | TP 整除约束 |
| KV heads | num_key_value_heads | GQA pool 宽度 | decode 显存 |
| Head dim | head_dim 或推导 | Attention / KV cell | 每 token KV 字节 |
| FFN 宽 | intermediate_size | MLP / MoE | 算力与 EP 约束 |
| MLA | kv_lora_rank, qk_*_head_dim | MLA pool + backend | 整条注意力宇宙切换 |
| MoE | num_experts, num_experts_per_tok | FusedMoE、EPLB | 通信与负载 |
| RoPE | rope_theta, rope_scaling | rotary_embedding | 长上下文 |
| Vocab | vocab_size, tie embed | ParallelLMHead、投机共享 | 输出层与 draft |
| SWA | sliding_window / layer types | SWAKVPool | 混合 KV |
| Draft | num_nextn_predict_layers 等 | 投机 worker、draft KV | 多一套结构预算 |
10. 案例对照
10.1 Llama 3 系(GQA 基线)
- 结构:MHA 接口 + GQA kv_heads
- 代码模板:llama.py(
LlamaAttention→RadixAttention) - 推理含义:KV 按 kv_heads 估;投机可共享 embed/head;最适合建立「标准契约」心智。
10.2 DeepSeek V3(MLA + MoE + 可选 DSA)
- 结构:MLA 压缩 KV + 超大 expert 池
- 代码:
ModelConfigMLA 分支 + deepseek_v2.py(及后续 V3 实现)+FusedMoE - 推理含义:换 backend、换 pool、EP/通信、量化合法性全部与结构绑定;不能用 Llama 运维手册硬套。
10.3 Qwen3-MoE
- 结构:主干 +
FusedMoE - 代码:qwen3_moe.py、
get_model_config_for_expert_location - 推理含义:吞吐对 batch / A2A / EPLB 敏感;hidden 可能相对 Dense 偏小(参数预算给了专家)。
10.4 EAGLE3 draft
- 结构:浅层 draft + 拼接 target hidden
- 代码:llama_eagle3.py + eagle_worker_v2.py
- 推理含义:多一套 ModelRunner 与 KV;accept 长度受 draft 结构与 topk/steps 共同限制。
11. 阅读路线
11.1 建议阅读顺序(GitHub)
| 源码 | 目的 |
|---|---|
| model_config.py | 架构如何被「类型化」 |
| registry.py · utils.py | HF → 类 |
| llama.py | 标准 LLM 模板与 hooks |
| model_runner.py | 加载 / backend / forward |
| memory_pool.py · pool_configurator.py | KV 形状与预算 |
| radix_attention.py · attention_registry.py | Attention 分发 |
| fused_moe layer.py · eplb/ | MoE/EP |
| deepseek_v2.py 或 qwen3_moe.py | 复杂实例 |
| llama_eagle3.py · eagle_worker_v2.py | 投机耦合 |
| support_new_models.md | 官方接入契约 |
| 本篇聚焦:模型结构如何穿透到 GPU 热路径(内存池、backend、EP、graph、投机、量化)。 |
11.2 线上问题 → 先问结构
| 现象 | 先查的结构问题 |
|---|---|
| 起服 TP 报错 | heads / kv_heads / moe_intermediate 能否整除 |
| KV OOM 与估算不符 | 是否 MLA?是否 GQA?是否双 KV(投机)? |
| 换 attention backend 崩溃 | 模型是 MLA 还是 MHA?backend 是否白名单? |
| MoE 吞吐极差或挂起 | Top-K、expert 数、EP size、负载是否倾斜 |
| 投机正确性差 / 不加速 | embed/head 是否共享成功?draft 层数与 steps? |
| 量化权重 load 失败 | packing 是否匹配 fused QKV/MoE?block 与 EP? |
11.3 一句话收束
大模型推理 = 在模型结构给出的约束下,做系统调度与算子选择。
SGLang 里每一档性能旋钮(KV pool、backend、EP、graph、投机、量化)背后,都有对应的 Transformer / MoE / MLA 结构字段。先读结构,再调系统,路径最短。
阅读导航




