AI AI infra · 推理侧
第 1 章
AI infra · 推理侧

第 3 章 模型:结构参数如何改变瓶颈

上一章补齐了四个基础概念,最后留下三个问题:同样标着 7B,为什么有的模型一张 24GB 卡能并六条、有的只能并一条;为什么 MoE 的参数量很大、单条请求却很便宜,到了批处理又变贵;为什么上下文从 8K 涨到 32K,算力和显存会同时吃紧。这三个问题的答案都藏在一个文件里——模型自带的 config.json。这一章就从这个文件读起,把结构参数逐个对应到显存和时间上。

先读 config.json

Hugging Face 上的每个模型都带一份配置文件,里面写着这个模型的形状。推理工程师需要关注的只有几个数:层数、隐藏维度、注意力头数、KV 头数、每个头的维度、前馈网络的中间维度,以及词表大小。

这几个数决定了三件事。参数量由层数、隐藏维度和中间维度共同决定;缓存大小由层数、KV 头数、头维度和上下文长度决定;而每生成一个 token 需要的串行步数等于层数,因为数据必须一层一层往上走。

以 Llama-2-7B 为例:32 层,隐藏维度 4096,32 个注意力头,32 个 KV 头,每头 128 维,前馈中间维度 11008,词表约 32000。这些数字本身没有意义,但把它们代进公式,就能得到显存和时间的账。

对应的配置大概长这样,字段名各家略有不同,含义一致:

{
  "num_hidden_layers": 32,
  "hidden_size": 4096,
  "num_attention_heads": 32,
  "num_key_value_heads": 32,
  "intermediate_size": 11008,
  "vocab_size": 32000,
  "torch_dtype": "float16"
}

num_key_value_heads 这一项最容易被忽略,却是缓存账里最关键的参数。它等于头数时是标准多头注意力,小于头数时是分组查询注意力。很多模型卡片不会在标题里说明这一点,只有配置里写着。

参数花在哪:一次手算

很多人以为参数主要花在注意力上,实际不是。

每一层里,注意力的四个投影矩阵(Q、K、V、输出)各是隐藏维度乘隐藏维度,合计约四倍隐藏维度的平方。Llama-2-7B 的隐藏维度是 4096,四乘 4096 的平方约为 6700 万参数一层。

前馈网络有三个矩阵,通常是隐藏维度乘中间维度再乘回去,合计约三倍隐藏维度乘中间维度。代入 4096 和 11008,约为 1.35 亿参数一层,正好是注意力的两倍。

三十二层加起来,注意力约 21 亿,前馈约 43 亿,加上词表嵌入的 1.3 亿,总数接近 67 亿。前馈占了将近三分之二,注意力只占三分之一。Inference Engineering 的说法是:前馈子层构成大部分可训练权重,注意力子层是第二大部分。这个比例直接决定了显存账的写法——权重的大头不在注意力上。

组成每层参数32 层合计占比
注意力(Q、K、V、输出四个投影)约 6700 万约 21 亿约 32%
前馈(三个矩阵)约 1.35 亿约 43 亿约 65%
词表嵌入—约 1.3 亿约 2%

这张表解释了两件事。第一,想靠"减少注意力头"来压缩权重,收益有限;第二,量化整模型时,前馈网络是体积最大的一块,收益也最大。

权重:参数量乘字节

知道了参数量,权重体积就是参数量乘以每个参数的字节数。FP16 每参数两字节,7B 模型约 14 GB,换成二进制单位是 13 GiB;BF16 一样;FP8 和 INT8 减半;INT4 再减半。

这笔账只跟模型有关,与并发无关。它的作用是决定"还剩多少显存能给缓存",而不是决定能接多少请求。第 1 章已经强调过这个区别,这里补一句它的来源:权重是所有人共享的一份,缓存是每个请求各占一份,所以前者固定、后者线性增长。

缓存:头数决定的那一项

KV cache 的公式在上一章出现过一次,这里把它写全:

单条请求的缓存字节数等于二(K 和 V 各一份)乘以层数、KV 头数、每头维度、序列长度和每元素字节数。

Llama-2-7B 代入:2 × 32 层 × 32 头 × 128 维 × 2 字节,每 token 是 524288 字节,也就是 512 KiB。一条 8K 输入的请求,八千一百九十二个 token,约 4 GiB。二十条并发就是 80 GiB,整张 80GB 卡都不够。

公式里每一项都可以动,但动的方式不同。层数和头维度是模型设计时定死的;序列长度由用户决定;字节数由量化决定;只有 KV 头数是"模型之间差异最大"的那一项。下一节专门说它。

GQA 与 MQA:省在哪儿,代价是什么

标准的多头注意力里,每个注意力头都有一份自己的 K 和 V,所以 KV 头数等于注意力头数。

分组查询注意力(GQA)改变了这一点:多个查询头共享同一组 K 和 V。Llama-3-8B 有 32 个查询头,但只有 8 个 KV 头,四组查询共用一组缓存。

缓存的大小只跟 KV 头数有关,所以头数从 32 降到 8,每个 token 的缓存就从 512 KiB 降到 128 KiB,正好四分之一。一条 8K 请求从 4 GiB 降到 1 GiB,同样一张 24GB 卡,并发从一条变成六条。

更激进的做法是多查询注意力(MQA),所有查询头共用一组 KV,缓存再降一个量级。代价是模型表达能力下降:K 和 V 的多样性减少,注意力能区分的关系变少。这个取舍在训练阶段就定下来了,推理工程师改不了,只能根据手上模型的 config.json 判断它属于哪一类。

实际影响很大。同样叫"8B",GQA 和 MHA 在显存账上差四倍,而这个差别不会写在模型名里。

MoE:省算力,不省显存

混合专家(MoE)在结构上做了另一件事:把前馈网络拆成很多个小矩阵,每个叫一个专家,再用一个小型路由网络决定每个 token 走哪几个专家。

以 Qwen3-235B-A22B 为例,总参数 2350 亿,但每次请求只激活 220 亿。单看计算,它像一个小模型;单看权重,它是一个大模型。

这个组合在单条请求上很划算:激活的参数少,每步读取的权重也少,出字速度快。但到了批处理场景,结论会反过来。Inference Engineering 提醒过这一点:不同请求会激活不同的专家,在生产环境的批处理里,应当预期几乎所有参数都处于激活状态。因为一批里有几十条请求,它们的路由结果合起来覆盖了大部分专家,每个专家都要被读到。

所以 MoE 省的是单条请求的算力,不省显存。权重必须全部加载,显存占用按总参数算;批处理时读取量也接近总参数。它带来的收益是"单条便宜",代价是"显存贵、并行复杂",因为专家分布在不同卡上会引入额外的通信。

上下文长度:两个方向同时涨

上下文从 8K 涨到 32K,看起来只是长度翻了四倍,实际有两笔账同时变坏。

第一笔是缓存,严格线性:长度四倍,缓存四倍。一条 8K 请求占 4 GiB,32K 就是 16 GiB。80GB 卡剩下 56 GiB 给缓存,只能放三条。

第二笔是注意力的计算量,严格平方:当前 token 要和此前每一个 token 算相关性,8K 时是六千四百万次比较,32K 时是十亿次以上,正好十六倍。预填充的运算量里,线性部分(逐层矩阵乘)涨四倍,平方部分涨十六倍,整体涨幅介于两者之间,长度越长越靠近平方项。

两笔账同时涨,先撞哪一个取决于具体数字。在 32K 这个尺度上,缓存先到:三条并发已经很紧张,而预填充的计算还能靠批处理摊薄一部分。但如果输入再长、输出再短,算力就会先到。

名字不可信,配置才可信

模型名里的数字只能当近似值用。

"7B"和"8B"经常指同一个量级:Llama-2-7B 实际约 6.7B,Llama-3-8B 实际约 8.0B,两者权重体积相差不到两成,但缓存差了四倍——差别不在参数量,在 KV 头数。

同一系列里还有 base 和 instruct 两个变体,架构完全一样,只是一个经过指令微调。它们的显存账完全相同,行为不同,选型时不要把它们当成两个不同规模的模型。

MoE 的命名更容易误导。Qwen3-235B-A22B 里的两个数字分别是总参数和激活参数,权重按 2350 亿算,单条请求的计算按 220 亿算。看到这类名字,第一反应应该是问"权重按哪个数"。

所以判断一个模型的资源需求,不能看名字,要看配置:层数、隐藏维度、KV 头数、词表大小。这四项决定了权重和缓存,其余都可以推出来。

结构决定的下限

有些模型不是"慢一点",而是换了一套系统才能跑。

70B 级模型,FP16 权重约 130 GiB。一张 80GB 卡装不下,无论怎么优化都装不下——这不是调参能解决的,是结构决定的。它必须跨卡,而一旦跨卡,每生成一个 token 都要做一次跨卡同步,通信时间进入每一步的固定开销。第 10 章会专门算这笔账,这里只需要记住结论:跨卡不是免费的容量扩展,它改变了时间结构。

MoE 也有类似的下限。2350 亿参数的模型,权重约 440 GiB,至少要六张 80GB 卡才放得下。它的单条请求很便宜,但前提是这些卡都在,而且专家之间的通信足够快。

反过来看小模型。7B 在 24GB 卡上能跑,但并发只有一条;换成 8 个 KV 头的版本,同样的卡能并六条。这个差别不是优化出来的,是结构给的。

一句话:模型的形状决定了资源的下限,工程手段只能在下限之上做优化。

一个陌生模型,五分钟读明白

拿到一个新模型,不需要跑起来就能判断它能不能上你的机器。顺序是这样。

第一步看 num_key_value_heads。它和 num_attention_heads 的比值,决定了缓存能比标准结构小多少倍。这个数经常决定成败。

第二步算权重:参数量乘以每参数字节。参数量不必精确,用隐藏维度和层数估个量级就够。

第三步算一条请求的缓存:二乘以层数、KV 头数、头维度、目标上下文长度和字节数。目标上下文要按业务上限取,不能按平均值。

第四步把两者相加,除以一张卡的可用显存,得到并发上限。余量按两到四 GiB 留。

第五步看结果。如果权重一项就超过单卡容量,讨论并发没有意义,必须先决定怎么跨卡。

这五步做完通常只要几分钟,却能挡掉大部分"上线才发现跑不动"的情况。

结构参数与并行方式

结构参数不只决定显存,还决定跨卡时怎么切。

层数决定流水线并行能切几段:层与层之间是串行的,切段之后每张卡负责一段,卡与卡之间传激活值。

注意力头数决定张量并行能切几份:每个头独立计算,切头最自然,切完只需要在注意力输出处做一次同步。

专家数决定专家并行怎么分:每个专家独立,可以放到不同卡上,代价是路由时需要通信。

上下文长度决定序列并行是否值得上:把一条长序列切成几段分给不同卡,只在注意力处交换边界信息。

这四种切法可以组合,但每加一种,通信就多一层。第 10 章会算这笔代价的账,这里只要记住:结构参数是并行策略的输入,不是事后补救的选项。

四个模型放一张表

把前面的公式用到四个典型结构上,差别会非常直观。表里都是 FP16 权重、8K 上下文、单条请求。

模型层数KV 头权重每 token 缓存一条 8K
7B,MHA323213 GiB512 KiB4 GiB
8B,GQA32815 GiB128 KiB1 GiB
70B,GQA808130 GiB320 KiB2.5 GiB
235B,MoE,激活 22B——440 GiB按结构算按结构算

这张表要你看三件事。第一,权重和缓存的变化方向不同:8B 的权重比 7B 大,缓存却只有四分之一。第二,70B 的瓶颈从缓存转到了权重:130 GiB 装不进一张 80GB 卡,必须跨卡,而跨卡会引入新的开销。第三,MoE 的权重按总参数算,不能按激活参数算,这是最常见的误判。

下一章转向硬件。前面所有的字节数和运算量,最终都要落在具体的算力、带宽和互联上;同一份账,在 A100、H100 和一张消费级显卡上算出来,结论可能完全不同。