权重格式转换与精度损失的定位

把一份 FP8 原始权重转成 BF16,供不支持 FP8 的 **** 平台加载(**** 的 ****,8 卡,单卡 64GB HBM,卡间互联 392 GB/s)。转换脚本跑完,目标目录里生成了新的权重文件,模型能加载,请求能返回。但输出与参考版本不一致:同一批问题下出现重复片段或提前终止,而同一模型在支持原生格式的另一条链路上结果正常。改动只落在权重这一项,问题要么在转换链路里,要么在加载后的推理路径上。定位要把两个来源分开:先固定比对条件,再逐层比对中间结果,最后用路径证据判断偏差是转换引入还是推理路径引入。

转换链路:格式、dtype、切分重排

原始权重到目标格式通常经过三步,每步都可能引入损伤:

步骤 做什么 常见损伤来源
格式转换 容器与数值格式互转,如 FP8 → BF16、safetensors 与 checkpoint 目录互转、导出 .onnx 一类中间格式 数值截断、旁路文件遗漏
dtype 转换 逐张量改变数值精度,如转换时把输入节点指定为 FP16、输出指定为 FP32 截断误差、逐层累积
切分与重排 按 tensor_parallel_size 切分权重、重排张量布局与头序 切分边界错位、头序错位

FP8 → BF16 属于格式与 dtype 同时变化的转换。源格式与目标格式的位分配不同,转换本身有精度损失,虽有损程度低于反向(高位转低位),误差仍会随层数累积。格式转换还有一处比数值更常见的遗漏:转换脚本只处理权重张量,不会自动复制 tokenizer.json、tokenizer_config.json 一类旁路文件。目标目录缺这些文件时模型仍能加载,分词结果却与源不一致,输出随之偏离,且过程不报错。部分模型还要求改写配置文件字段,例如把 config.json 里的 model_type 改成目标代码分支期望的取值,字段没对齐时模型按另一套代码路径加载,行为偏离同样不报错。

按参数 × 字节数估算转换后的体积(非实测):FP8 每参数 1 字节,BF16 每参数 2 字节,同一份权重转完体积约为原来的两倍。体积翻倍是转换链路的直接代价,与精度无关。

精度损伤的可能来源

触发条件

转换场景 高风险点
FP8 → BF16(位宽升位、格式变化) 数值截断、体积翻倍
低精度 → 高精度(如 INT8 → BF16) 截断误差已固化在源产物里,转高精度不能恢复
跨框架导出(导出 .onnx 一类中间格式) 算子映射差异、dtype 默认值不同
转换工具版本与目标运行时不一致 转换不报错,推理阶段才异常
切分重排改变张量布局 头序或边界错位,能加载但输出偏离

现象

定位步骤

  1. 固定采样配置与随机种子。把 temperature 设为 0(greedy),固定 seed 与 max_tokens,先排除采样随机性带来的差异。转换前后的比对必须在同一采样配置下做,greedy 两组输出相等是可比性的前提。
  2. 逐层比对中间结果。取若干层,用同一输入分别喂给源格式与目标格式,比对层输出与 logits,定位偏差从哪一层开始积累。
  3. 区分「转换引入」与「推理路径引入」。两个判据:其一,把目标格式在另一条已知正常的链路上加载,若仍偏差,损伤来自转换;其二,比对 target_backend 与 resolved_backend,若实际路径退回通用实现,损伤来自推理路径而非转换。
  4. 核对旁路文件与配置字段。列出源目录与目标目录的文件清单(tokenizer.json、tokenizer_config.json、config.json),核对 model_type 一类字段是否与目标框架的代码分支匹配。
  5. 固定工具版本。记录转换工具版本,用兼容窗口内的版本重跑一次,确认产物是否一致。版本不一致时,转换过程不报错而推理阶段异常,是这类问题的典型形态。
# 示意:一次格式转换(脚本名与参数名取自转换工具,取值按本机规格填)
python <convert-tool>.py \
  --input-fp8-hf-path /path/to/source \
  --output-bf16-hf-path /path/to/target

# 转换脚本通常不自动复制旁路文件,需手动同步
cp /path/to/source/tokenizer.json /path/to/target/
cp /path/to/source/tokenizer_config.json /path/to/target/

比对转换产物本身可用模型查看工具(如 netron)核对图结构,改动前后两张图不一致时,先确认差异来自转换参数而非工具版本。

处置

方向 动作 代价 / 前提
dtype 截断偏大 提高目标精度(如 INT8 → BF16) 体积上升
工具版本不在窗口内 退回被验证过的工具版本重转 需重新验证产物
旁路文件或字段遗漏 同步 tokenizer、config.json 字段 无
推理路径回退 修 guard、换后端,路径确认后再判转换 与转换无关
精度损失阈值可配 用精度阈值(如 accuracy_loss_threshold)触发失败,而非人工比对 阈值需按任务设定

记录字段

# 一条格式转换记录(字段名示意)
source_format:             # 源格式,如 FP8 / INT8 / safetensors
target_format:             # 目标格式,如 BF16
convert_tool:              # 转换工具名
convert_tool_version:      # 转换工具版本,写 <pinned-version>
tensor_parallel_size:      # 切分目标并行度
calibration_method:        # 若为量化转换,记校准方法(min_max / kl_divergence)
bypass_files:              # 是否同步 tokenizer / config 等旁路文件
compare_method:            # 比对方法:greedy + seed + 逐层中间结果
seed:                      # 比对用随机种子
temperature:               # 比对用采样参数
accuracy_before:           # 源格式参考输出
accuracy_after:            # 目标格式输出
diff_layer_first:          # 偏差首次出现的层
path:
  target_backend:          # 目标算子路径
  resolved_backend:        # 实际命中路径
status:                    # 转换无损 / 转换引入偏差 / 路径引入偏差

小结

权重格式转换的损伤来自三处:数值截断、旁路文件与配置字段遗漏、转换工具版本差异。三者都表现为转换过程不报错、推理阶段才出偏差,因此定位要先固定采样配置与种子,再逐层比对中间结果,最后用另一条正常链路与 resolved_backend 两项判据把转换引入与推理路径引入分开。记录时写清源格式、目标格式、转换工具版本与比对方法,才能在下一次复现同一份结论。