如何根据 config.json 核对 MoE 模型的激活参数:以 gpt-oss-120b 为例(GPT-5.4-high 生成)

很多开发者看到模型卡里的 117B parameters with 5.1B active parameters,第一反应是"这个数到底怎么算出来的"。本文就用 gpt-oss-120b 做一个完整示范:如何仅凭 config.json 里的关键字段,推导出 MoE 模型的总参数量、每 token 激活参数量,以及为什么你自己算出来的数字有时会和官方只差一点但不完全一样。

关键词: MoE、active parameters、config.json、gpt-oss-120b、模型卡、Hugging Face、参数量估算、推理成本

很多人看 MoE 模型时,最容易被一句话吸引:

117B parameters with 5.1B active parameters

这句话很有信息量,但也很容易让人误解。

常见误区有三个:

  • 以为 117B 就意味着每个 token 都会走完 117B 参数
  • 以为 5.1B active 就等价于"这个模型推理成本和一个 5B dense 模型差不多"
  • 以为只要看模型卡,不需要自己核对

如果你是做模型选型、推理优化、服务部署,或者只是想更扎实地理解一个 MoE 模型,那么我非常建议你掌握一项能力:

自己根据 config.json 去核对模型卡里的 active parameters。

这篇文章我会以 openai/gpt-oss-120b 为例,把整个过程走一遍,并顺手补充一些对开发者非常有价值、但模型卡里通常不会直说的信息。

一、先说结论:gpt-oss-120b 的 active params 怎么看

gpt-oss-120b 的模型卡写的是:

  • 117B parameters
  • 5.1B active parameters

它的 config.json 里关键字段是:

json 复制代码
{
  "hidden_size": 2880,
  "intermediate_size": 2880,
  "num_hidden_layers": 36,
  "num_local_experts": 128,
  "num_experts_per_tok": 4,
  "num_attention_heads": 64,
  "num_key_value_heads": 8,
  "head_dim": 64,
  "vocab_size": 201088
}

这几个字段已经足够让我们抓住本质:

  • 一共有 36
  • 每层有 128 个 expert
  • 每个 token 在每层只会路由到 4 个 expert
  • 所以每层只会激活 4 / 128 = 3.125% 的 expert 参数

最终核算下来:

  • 总参数量约为 116.83B ,四舍五入后就是模型卡里的 117B
  • 按官方口径统计的 active parameters 约为 5.13B ,四舍五入后就是 5.1B

换句话说,模型卡这两个数字并不是"营销数字",而是可以从配置和结构中推出来的。

二、为什么开发者要学会自己核对 active parameters

因为 active parameters 这个指标虽然常见,但并不是一个全行业绝对统一的口径。

不同团队在统计时,可能会有以下差异:

  • 是否把 embedding 算进 active params
  • 是否把 lm_head 算进去
  • 是否把 router、norm、bias 这种"小而常开"的模块算进去
  • 是按"单步前向参与计算"的口径,还是按"主干 + 被选中的专家"口径

这就导致一个现象:

你自己算出来的结果,往往不是"错",而是"统计口径不同"。

所以,学会核对 active params 的价值,不只是为了验证模型卡,更是为了:

  • 判断一个模型真实的每 token 计算规模
  • 理解它为什么"总参数很大,但单步推理没那么大"
  • 评估它和 dense 模型相比的部署意义
  • 分清"显存占用""吞吐成本""理论激活量"到底在说什么

三、核对 active parameters,第一步先找哪些字段

对于大多数 Transformer MoE 模型,你先看 config.json 里下面这些字段:

1. 与 MoE 路由直接相关

  • num_local_experts
  • num_experts_per_tok

这两个字段决定:

  • 每层一共有多少个专家
  • 每个 token 每层会命中多少个专家

gpt-oss-120b 来说:

  • num_local_experts = 128
  • num_experts_per_tok = 4

意思就是:

每层 128 个 expert,但每个 token 每层只激活其中 4 个。

2. 与 expert 大小相关

  • hidden_size
  • intermediate_size

这两个字段决定 expert 的 MLP 有多大。

gpt-oss-120b 来说:

  • hidden_size = 2880
  • intermediate_size = 2880

3. 与层数相关

  • num_hidden_layers

gpt-oss-120b 来说:

  • num_hidden_layers = 36

4. 与 dense attention 主干相关

  • num_attention_heads
  • num_key_value_heads
  • head_dim

这些字段决定 attention 的 Q/K/V/O 投影规模,也会影响"总参数量"和"active params 中始终开启的 dense 部分"。

5. 与 embedding / 输出头相关

  • vocab_size
  • hidden_size
  • tie_word_embeddings

这几个字段决定 embedding 和 lm_head 的规模。

这里有一个重要提醒:

很多人只算了 expert 部分,然后说"这就是 active params"。这通常是不完整的。

因为 attention、router、norm、embedding 等模块也会参与每一步前向。

四、第二步:先算单个 expert 有多少参数

MoE 模型最关键的地方,是先弄清楚:

一个 expert 到底有多大?

gpt-oss 这类结构里,expert 本质上是一个带门控的 MLP。

如果从结构上理解,它大致相当于:

  • 一个 gate_proj
  • 一个 up_proj
  • 一个 down_proj

对应的参数量可以近似写成:

text 复制代码
expert_params ≈ hidden_size * intermediate_size
              + hidden_size * intermediate_size
              + intermediate_size * hidden_size
              = 3 * hidden_size * intermediate_size

如果再把 bias 算进去,会多一点点,但主量级还是由上面这个 3 * h * i 决定。

对于 gpt-oss-120b

text 复制代码
hidden_size = 2880
intermediate_size = 2880

expert_params ≈ 3 * 2880 * 2880
             = 24,883,200

再加上 bias 后,精确值约为:

text 复制代码
24,891,840

也就是:

单个 expert 大约 2489 万参数。

五、第三步:算每层会激活多少 expert 参数

现在我们已经知道:

  • 每层有 128 个 expert
  • 每个 token 每层只激活 4 个 expert
  • 每个 expert 约 24.89M 参数

那么每层被激活的 expert 参数量大约就是:

text 复制代码
active_expert_params_per_layer
= 4 * 24,891,840
= 99,567,360

也就是:

每层大约激活 9957 万 expert 参数。

再乘以 36 层:

text 复制代码
active_expert_params_total
= 36 * 99,567,360
= 3,584,424,960

约等于:

3.58B

这一步很关键,因为很多人看到 MoE 会先问:

"那是不是每个 token 只走 3.58B 参数?"

答案是:

还不止。因为除了被选中的 experts,还有 dense 的 attention 主干、router、norm、embedding 等模块也一直在工作。

六、第四步:把始终开启的 dense 部分也加进去

MoE 模型并不是"除了 expert 之外什么都没有"。

gpt-oss-120b 每一层还有这些固定参与前向的部分:

  • self-attention 的 q_proj
  • k_proj
  • v_proj
  • o_proj
  • router
  • 两个 RMSNorm
  • attention 中一些小参数

这些 dense 主干参数虽然远小于全部 experts,但它们是每层都一定会参与计算的。

gpt-oss-120b 进行核算后,可以得到:

  • 36 层 dense transformer 主干约 0.97B
  • embedding 约 0.58B

于是:

text 复制代码
active_params
≈ 激活的 experts
+ dense transformer 主干
+ embedding

≈ 3.58B + 0.97B + 0.58B
≈ 5.13B

这就是模型卡里:

5.1B active parameters

的来源。

七、那为什么有时候你会算出 5.7B,而不是 5.1B

这正是 active params 最值得开发者注意的地方。

如果你把 lm_head 也算进"每步前向实际参与"的参数中,那么 gpt-oss-120b 还要再加上:

text 复制代码
lm_head = hidden_size * vocab_size
        = 2880 * 201088
        ≈ 0.58B

这时你会得到一个更大的数字:

text 复制代码
5.13B + 0.58B ≈ 5.71B

也就是说:

  • 按官方常见口径gpt-oss-120b5.1B active
  • 按"连 lm_head 也算进去"的严格前向口径 ,它更接近 5.7B

这两者并不矛盾,只是口径不同。

所以以后你再看任何模型卡里的 active params,都建议先问自己一句:

这个 active params,究竟有没有把 embedding、lm_head、router、norm 一起算进去?

八、总参数量为什么是 117B

现在再看总参数量,就很容易理解了。

总参数量不是"每次激活多少",而是:

模型一共持有多少参数。

gpt-oss-120b 来说,总参数量由以下部分组成:

  • embedding
  • 36 层 attention 主干
  • 36 层 router / norm
  • 36 层全部 128 个 experts
  • 最终 lm_head

核算下来大约是:

text 复制代码
116.83B

四舍五入就是:

text 复制代码
117B

所以这两个数字可以这样理解:

  • 117B 说的是"这个模型总共装了多少参数"
  • 5.1B 说的是"每个 token 实际只会动用其中一小部分参数"

九、只看 config.json,什么时候够用,什么时候不够用

这里有一个很重要的经验判断。

只看 config.json 就够用的场景

如果一个 MoE 模型满足下面这些条件,那么只看配置通常就足以做高可信度估算:

  • MoE 结构比较标准
  • expert 是典型的 gate/up/down 三段 MLP
  • hidden_sizeintermediate_sizenum_experts_per_tok 等字段清晰
  • 没有额外共享 expert、残差 expert、特殊并行分支

这时你完全可以通过:

text 复制代码
单 expert 参数
-> 每层激活 expert 参数
-> 全层累计
-> 再加 dense 主干

把 active params 估出来。

只看 config.json 不够用的场景

如果你遇到以下情况,就最好顺手打开一次实现代码:

  • 配置里写了 intermediate_size,但 expert 实际不是标准 3 段 MLP
  • shared_expertshared_mlp
  • num_experts_per_tok,但还有额外 dense FFN 分支并行存在
  • 一个 block 里不只是一个 MoE MLP
  • 有 fused projection,单从名字看不清矩阵形状

简单说:

config.json 能告诉你"结构有多大",但不一定能 100% 告诉你"矩阵具体怎么摆"。

对于 gpt-oss-120b,如果进一步看实现,会发现它的 expert 是:

text 复制代码
gate_up_proj: [hidden_size, 2 * intermediate_size]
down_proj:    [intermediate_size, hidden_size]

这和我们上面使用的 3 * hidden_size * intermediate_size 推导是一致的。

十、给开发者最有帮助的一点:active params 不等于真实推理成本

这是很多工程讨论里最容易混淆的一点。

active parameters 很重要,但它并不是部署成本的唯一指标。

1. active params 更接近"每 token 参与计算的参数量"

它能帮助你理解:

  • 每个 token 大概有多少参数参与乘加
  • 为什么 MoE 可以做到"总参数很大,但单步计算没那么大"
  • 为什么同等 active params 下,MoE 可能比 dense 模型更有能力

2. 但它不等于真实延迟

真实延迟还会受到这些因素影响:

  • expert 路由带来的 gather / scatter 开销
  • 多卡 expert parallel 的通信成本
  • attention 计算和上下文长度
  • KV cache 读写
  • batch size
  • sequence length
  • kernel 实现质量
  • 量化方式

所以你不能简单说:

"5.1B active,所以它就等于一个 5B dense 模型的推理成本。"

这在工程上通常是不成立的。

3. total params 更接近"模型存储规模和权重驻留规模"

117B total params 更影响:

  • 权重总占用
  • 模型加载体积
  • 是否能装进单卡 / 多卡
  • 分片和并行策略

5.1B active 更影响:

  • 单步 token 计算规模
  • 理论 FLOPs 量级
  • 对吞吐与时延的直觉判断

一个非常实用的理解方式是:

  • total params 决定"你要把多大的模型搬上车"
  • active params 决定"每次踩油门时,真正有多少部件在发力"

十一、为什么量化不会改变参数量口径

gpt-oss-120b 的配置里还写了:

json 复制代码
"quantization_config": {
  "quant_method": "mxfp4"
}

这说明它的部分权重采用了量化表示。

但要注意:

量化改变的是参数存储精度,不改变参数个数。

所以:

  • 117B total params 还是 117B
  • 5.1B active params 还是 5.1B

量化会显著影响:

  • 显存占用
  • 带宽压力
  • 实际吞吐

但不会改变模型卡里的参数"个数"统计。

十二、一个可复用的通用核对模板

以后你看到任何 MoE 模型卡,只要它公布了 config.json,都可以按下面这个顺序核对。

第一步:确认是不是标准 MoE

重点看这些字段:

  • num_local_experts
  • num_experts_per_tok
  • hidden_size
  • intermediate_size
  • num_hidden_layers

第二步:估算单 expert 参数量

如果是标准门控 MLP,可先用近似公式:

text 复制代码
expert_params ≈ 3 * hidden_size * intermediate_size

如果要更精确,就把 bias 也加上,或者去看实现里矩阵的精确 shape。

第三步:估算每层激活参数

text 复制代码
active_expert_params_per_layer
= num_experts_per_tok * expert_params

第四步:乘总层数

text 复制代码
active_expert_params_total
= num_hidden_layers * active_expert_params_per_layer

第五步:加上始终开启的 dense 部分

包括但不限于:

  • attention Q/K/V/O
  • router
  • norm
  • embedding
  • 有时还要考虑 lm_head

第六步:明确你的统计口径

最后一定要说明:

  • 你有没有算 embedding
  • 你有没有算 lm_head
  • 你有没有算 router / norm / bias

这一步非常关键,否则同一个模型,不同人算出来的数字会看起来"互相矛盾"。

十三、附一个简单 Python 脚本:快速估算 gpt-oss-120b

如果你只是想快速验证一个数量级,可以直接用下面这个脚本。

python 复制代码
vocab_size = 201088
hidden_size = 2880
intermediate_size = 2880
num_hidden_layers = 36
num_local_experts = 128
num_experts_per_tok = 4
num_attention_heads = 64
num_key_value_heads = 8
head_dim = 64

# embedding / lm_head
embed = vocab_size * hidden_size
lm_head = hidden_size * vocab_size
final_norm = hidden_size

# 单层 attention
q_proj = hidden_size * (num_attention_heads * head_dim) + (num_attention_heads * head_dim)
k_proj = hidden_size * (num_key_value_heads * head_dim) + (num_key_value_heads * head_dim)
v_proj = hidden_size * (num_key_value_heads * head_dim) + (num_key_value_heads * head_dim)
o_proj = (num_attention_heads * head_dim) * hidden_size + hidden_size

# norm / router
norms = hidden_size + hidden_size
router = num_local_experts * hidden_size + num_local_experts

# 单个 expert
per_expert = (
    hidden_size * (2 * intermediate_size) + (2 * intermediate_size)
    + intermediate_size * hidden_size + hidden_size
)

# 每层
all_experts_per_layer = num_local_experts * per_expert
active_experts_per_layer = num_experts_per_tok * per_expert
per_layer_dense = q_proj + k_proj + v_proj + o_proj + norms + router

total_params = (
    embed
    + num_hidden_layers * (per_layer_dense + all_experts_per_layer)
    + final_norm
    + lm_head
)

active_params_official_style = (
    embed
    + num_hidden_layers * (per_layer_dense + active_experts_per_layer)
    + final_norm
)

active_params_with_lm_head = active_params_official_style + lm_head

print(f"total params: {total_params / 1e9:.2f}B")
print(f"active params (official-style): {active_params_official_style / 1e9:.2f}B")
print(f"active params (with lm_head): {active_params_with_lm_head / 1e9:.2f}B")

运行后你会得到接近下面的结果:

text 复制代码
total params: 116.83B
active params (official-style): 5.13B
active params (with lm_head): 5.71B

十四、顺手对比一下 gpt-oss-20b:你会更容易看懂 120b

gpt-oss-20b 的配置很有意思:

  • hidden_size = 2880
  • intermediate_size = 2880
  • num_hidden_layers = 24
  • num_local_experts = 32
  • num_experts_per_tok = 4

也就是说,它和 120b 的单个 expert 大小其实是一样的,区别主要在于:

  • 层数更少:24 层 vs 36
  • 每层总专家数更少:32 个 vs 128

这会导致一个很有意思的结果:

  • 20b 的总参数量远小于 120b
  • 但它每层激活的 expert 数量同样是 4
  • 所以它的 active / total 比例会明显更高

粗略核算:

  • gpt-oss-20b 总参数约 20.91B
  • 如果采用"包含 embedding,但不包含 lm_head "这套和 gpt-oss-120b 一致的官方口径,那么 active params 约为 3.61B
  • 四舍五入后,正好就是模型卡里的 3.6B active

这个对照其实很有价值,因为它说明 gpt-oss 系列的模型卡口径大概率是统一的:

  • 统计 active params 时包含 embedding
  • 但不包含 lm_head

这说明一个很重要的事实:

MoE 模型的"总参数变大"未必意味着"每 token 激活参数同比例变大",因为它可能主要是在加 experts 的总池子,而不是增加 top-k。

这也是 MoE 架构非常有吸引力的原因之一。

十五、最后总结:以后怎么最快判断模型卡的 active params 靠不靠谱

以后你看到一个 MoE 模型卡,不妨直接按下面这套方法走:

  1. 先看 num_local_expertsnum_experts_per_tok,确认每层到底激活多少 expert。
  2. 再看 hidden_sizeintermediate_size,估单个 expert 大小。
  3. 乘上层数,得到全部激活 expert 参数。
  4. 再把 attention、router、norm、embedding 加回来。
  5. 最后确认口径里是否包含 lm_head

如果这样算出来和模型卡差得不多,比如只差几个亿以内,通常不是你算错了,而是:

  • 四舍五入差异
  • 是否统计 bias
  • 是否统计 embedding
  • 是否统计 lm_head
  • 官方采用了更偏工程化的 active 口径

真正重要的不是把数字抠到一模一样,而是:

你是否已经理解这个模型为什么是这个数量级。

一旦你掌握了这套方法,以后无论是看 Mixtral、DeepSeek、Qwen MoE,还是别的开源专家模型,都会比只看模型卡踏实得多。

十六、给工程实践者的最终建议

如果你的目标是选型或部署,而不是单纯看热闹,我建议把下面三件事分开看:

  • total params:决定模型总体规模、装载成本、权重驻留压力
  • active params:决定每 token 理论参与计算的参数规模
  • real latency / throughput:决定真实服务体验,受实现、并行、通信、KV cache、量化等多因素共同影响

这三者经常相关,但绝不等价。

所以,最稳妥的工程心智模型是:

先用 config.json 算结构规模,再用真实 benchmark 验证部署表现。

前者帮你建立判断,后者帮你避免踩坑。

如果你愿意,我下一篇还可以继续整理成一篇姊妹篇:

  • 如何根据 config.json 判断一个模型是不是"强推理模型"
  • 如何快速看懂 dense 模型与 MoE 模型的真实部署差异
  • 如何用同一套方法核对 Mixtral、DeepSeek、Qwen MoE 的 active params
相关推荐
带刺的坐椅5 小时前
Snack4 Json 流式解析与自动结构修复深度指南
java·llm·json·jsonpath
Pyeako7 小时前
大模型--模型部署
人工智能·python·大模型·客户端·模型部署·服务端·路由-端口
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ7 小时前
DB-GPT 和 Dify 区别
数据库·gpt
圣殿骑士-Khtangc8 小时前
【论文精读】《A Survey of Vibe Coding with Large Language Models》| 通俗解读+核心提炼
人工智能·大模型·vibe coding
m0_584624508 小时前
调用接口返回的json数据被截断
java·json
CoderJia程序员甲8 小时前
GitHub 热榜项目 - 日榜(2026-03-17)
人工智能·ai·大模型·github·ai教程
Shining05968 小时前
推理引擎系列(四)《大模型计算优化与分布式推理》
人工智能·分布式·深度学习·机器学习·大模型·注意力机制·推理引擎
假如梵高是飞行员9 小时前
一种可信Agent架构设计思路,采用异步和分布式来提高效率
分布式·大模型·agent
Pyeako9 小时前
大模型--OpenAI&创建阿里云百炼API Key
python·阿里云·大模型·云计算·openai·qwen·api key