fuse_for_vllm.py 源码精读:gpt-oss-20b-BF16-w8a8-llmcompressor 如何重写 MoE 专家权重布局以无缝适配 vLLM 加载器
【免费下载链接】gpt-oss-20b-BF16-w8a8-llmcompressor项目地址: https://ai.gitcode.com/hf_mirrors/amd/gpt-oss-20b-BF16-w8a8-llmcompressor
gpt-oss-20b-BF16-w8a8-llmcompressor 是 AMD 基于 LLM Compressor 产出的 W8A8 量化版本,磁盘体积从 39.0 GiB 压到 20.6 GiB。它的 MoE 专家权重布局无法被 vLLM 直接加载,仓库中的 fuse_for_vllm.py 就是来解决这件事的:把逐专家存储的专家张量重写(拼接、转置、重新分片)成 vLLM 期望的融合布局,无需修改任何模型文件。
模型速览:为什么专家权重是大头
| 项目 | 数值 |
|---|---|
| 基座模型 | gpt-oss-20b(BF16,39.0 GiB) |
| 量化方案 | W8A8:INT8 权重 + per-token 动态 INT8 激活,对称 per-channel 缩放 |
| 量化后体积 | 约 20.6 GiB(-47%) |
| MoE 结构 | 24 层 × 32 专家,每 token 选 top-4 专家(experts_per_token: 4) |
| 推理目标 | AMD EPYC CPU,vLLM 0.26.0 |
从 config.json 可以看到num_hidden_layers: 24、num_local_experts: 32。量化配置见 config.json 与 recipe.yaml:只量化Linear层,MoE 路由器、lm_head、embed_tokens保持 BF16。
问题出在布局上。gpt-oss 原生的专家是一个融合的 3D 张量,量化时必须先用 shim 模块拆成逐专家的独立nn.Linear(见 README.md 的说明),量化后检查点里每个专家存成gate_proj/up_proj/down_proj三组独立键。但 vLLM 的 gpt-oss 加载路径要求:gate 与 up 合并为一个投影(内部叫 w13),且读取后要做一次固定的"截取 + 转置"——所以存储侧必须提前把转置后的结果存好,两边组合起来才恰好是加载器期望的方向(源码注释 fuse_for_vllm.py)。
💡 仓库里同时保留了两组 safetensors:model-*-of-00005是量化产出的原始布局,model-*-of-00006是融合后的产物,可以直接对照键名差异。
转换流程总览:一条命令四步走
运行方式(见文件头注释 fuse_for_vllm.py):
python fuse_for_vllm.py SRC_DIR DST_DIR| 步骤 | 动作 | 代码位置 |
|---|---|---|
| 1️⃣ 键分类 | 读 model.safetensors.index.json,把 5019 个张量分成"专家键"与"普通键" | fuse_for_vllm.py |
| 2️⃣ 逐专家融合 | gate+up 按行拼接后转置,每专家产出 6 个融合张量 | fuse_for_vllm.py |
| 3️⃣ 增量分片 | 缓冲到约 4 GiB 即落盘,最后统一改名并重建索引 | fuse_for_vllm.py |
| 4️⃣ 文件搬运 | 非权重文件(config、tokenizer 等)原样复制到目标目录 | fuse_for_vllm.py |
第 1 步:用一条正则识别 MoE 专家权重键
整个脚本的"路由"靠一条正则完成(fuse_for_vllm.py):
_EXPERT_KEY = re.compile( r"^(?P<prefix>.*\.mlp\.experts)\.(?P<expert>\d+)\." r"(?P<proj>gate_proj|up_proj|down_proj)\." r"(?P<kind>weight|weight_scale|bias)$")四个命名组把键拆解为:层前缀(prefix)、专家编号(expert)、投影名(proj)、张量类型(kind)。匹配不上的键(注意力投影、LayerNorm、24 个 attention sinks 等 411 个张量)走"直通"通道原样复制。若某个键含mlp.experts.却又匹配不上,脚本会直接报错,避免静默丢权重(fuse_for_vllm.py)。
第 2 步:gate/up 融合与转置——每个专家的 6 个键
以本仓库的 W8A8 格式为例,每个专家从 9 个键变成 6 个:
| 原始键(逐专家) | 融合后键 | 变换 |
|---|---|---|
gate_proj.weight+up_proj.weight | gate_up_proj.weight | 沿第 0 维拼接 → 转置 |
gate_proj.weight_scale+up_proj.weight_scale | gate_up_proj.weight_scale | 拼接 → 转置 |
down_proj.weight | down_proj.weight | 转置 |
down_proj.weight_scale | down_proj.weight_scale | 转置 |
gate_proj.bias+up_proj.bias | gate_up_proj.bias | 拼接(不转置) |
down_proj.bias | down_proj.bias | 原样 |
核心代码就一行(fuse_for_vllm.py):
torch.cat([gate_w, up_w], 0).t().contiguous()为什么要转置?vLLM 加载器读到的张量会被再转置一次,预存转置结果才能让最终方向正确——这是"无缝适配"的关键一步。
⚠️W4A16 例外:两种量化方案的参数轴向顺序相反——INT8 的 w13 逻辑上是[2I, K],packed INT4 的 w13 是[K/8, 2I]。因此 W4A16 分支直接"按拼接原样存储",不转置(fuse_for_vllm.py、fuse_for_vllm.py)。此外 W8A8 的gate_up_proj.weight靠 vLLM 的hf_to_vllm_mapper映射到w13_weight,而 W4A16 因无对应映射规则,直接以w13_weight_packed命名,未匹配的键会原样穿透映射器(fuse_for_vllm.py)。
第 3 步:4 GiB 增量分片与索引重建
- 懒加载读取:_Source 按需打开 safetensors 分片句柄并缓存,不会把整个检查点读进内存。
- 缓冲写出:_ShardWriter 累计到约 4 GiB(可用
--shard-size-gib调整)就先落盘为part-00001.safetensors;finalize时统一改名为model-00001-of-00006.safetensors并生成weight_map。 - 一个贴心的细节:
save_file新建文件权限是 0600,放在 NFS 上其他用户会读不了,所以脚本显式chmod 0o644(fuse_for_vllm.py)。 - 重建索引:重写 model.safetensors.index.json,写入
total_size(本仓库约 20.6 GiB)与排序后的weight_map(fuse_for_vllm.py)。
使用前的三个限制,先知道再动手
- 仅支持 TP=1:更高的张量并行度下,down 投影的 per-channel scale 无法正确传到非零 rank(fuse_for_vllm.py)。
- 融合布局不属于 compressed-tensors 惯例:所以源检查点仍是对外发布的正式产物,融合版只是 vLLM 专用的派生形态(fuse_for_vllm.py)。
- W4A16 还需 vLLM 侧配合:必须把
w13_weight_packed/w2_weight_packed加入 vLLMgpt_oss.py的_GPT_OSS_STREAMED_EXPERT_SUFFIX_TO_SHARD(精确匹配,检查点侧无绕法);W8A8 则不需要(fuse_for_vllm.py)。
总结与参考文件
一句话总结:fuse_for_vllm.py用"正则分类 → 拼接转置 → 增量分片 → 重建索引"四步,把 W8A8/W4A16 量化检查点的 4608 个 MoE 专家张量重排成 vLLM 可直读的融合布局,全程流式处理、不改模型文件。
- 核心脚本:fuse_for_vllm.py
- 融合后检查点索引:model.safetensors.index.json
- 模型架构与量化配置:config.json
- 量化配方:recipe.yaml
- 模型卡与量化流程说明:README.md
- 许可证:LICENSE
如需完整运行,先获取包含全部权重文件的仓库副本:
git clone https://gitcode.com/hf_mirrors/amd/gpt-oss-20b-BF16-w8a8-llmcompressor【免费下载链接】gpt-oss-20b-BF16-w8a8-llmcompressor项目地址: https://ai.gitcode.com/hf_mirrors/amd/gpt-oss-20b-BF16-w8a8-llmcompressor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考