【Bug已解决】[Quantization FP8] Native from_config support 解决方案

发布时间:2026/8/7 23:50:10
【Bug已解决】[Quantization FP8] Native from_config support 解决方案 【Bug已解决】[Quantization FP8] Native from_config support 解决方案一、现象长什么样你想用from_config直接按配置构造一个 FP8 量化模型而不是先from_pretrained再量化但量化没有被应用# 现象 Afrom_config 忽略了 quantization_config模型是普通 BF16 UserWarning: quantization_config provided to from_config is ignored; model built in full precision. # 之后 model.layers[0].mlp.gate_proj 仍是 nn.Linear应是 Linear4bit/FP8 # 现象 B构造时直接报错 ValueError: FP8 quantization requires weights from a checkpoint; cannot apply from config alone. # 库认为 from_config 没有权重可量化拒绝 # 现象 C构造成功但推理 dtype 混乱 # from_config 产出的 FP8 模型forward 时权重是 bf16、计算也 bf16 # 等于声明了 FP8 却没量化显存没省、速度没快 # 典型触发 from transformers import AutoModelForCausalLM, BitsAndBytesConfig cfg MyConfig(quantization_configBitsAndBytesConfig(load_in_4bitTrue, ...)) model AutoModelForCausalLM.from_config(cfg) # 量化被忽略最典型的指纹from_pretrained(..., quantization_config...)能正常量化但from_config(cfg_with_quant)不量化——两条路径对quantization_config的处理不对称。二、背景Transformers 里加载模型有两条主路径from_pretrained从磁盘权重加载量化是在加载权重时把权重量化bitsandbytes 在_load_pretrained_model里替换 Linear 为量化层。from_config只按配置凭空构造一个未初始化的模型骨架没有真实权重。问题在于FP8/4bit 量化通常依赖已有的权重张量去量化bitsandbytes 把加载的浮点权重转成量化格式。from_config路径本来就没有权重于是旧实现干脆直接忽略quantization_config导致声明了量化却没量化。但有些场景确实需要从 config 直接得到量化骨架比如先做量化骨架、再往里塞已量化的权重量化权重已经离线算好或测试量化层结构而不加载全量 checkpoint。这就需要有原生 from_config 支持 FP8 量化——即from_config应当把nn.Linear替换为 FP8 量化层即便权重还是随机初始化而不是忽略配置。三、根因根因有两类from_config不读取quantization_config。PreTrainedModel.from_config内部只按 config 构造子模块没有像from_pretrained那样在构造后跑量化层替换逻辑。于是config.quantization_config被静默丢弃 → 现象 A/B。量化层替换逻辑只挂在_load_pretrained_model上。 把nn.Linear→Linear4bit/Fp8Linear的代码写死在加载权重流程里而不是一个可被from_config复用的独立步骤。所以from_config想量化也复用不到这套逻辑 → 现象 B 报错或现象 C 半吊子。四、最小可运行复现下面用纯 Python 模拟from_config 忽略 quantization_config导致量化层未被替换from dataclasses import dataclass, field from typing import Dict, Optional class _Linear: # 普通线性层 pass class _Fp8Linear: # FP8 量化层 pass dataclass class Cfg: quantization_config: Optional[dict] None def from_config_buggy(cfg: Cfg): 有 bugfrom_config 忽略 quantization_config。 # 只按 config 建普通层不看 quantization_config return {layers.0.mlp.gate_proj: _Linear()} def from_config_fixed(cfg: Cfg): 修正from_config 也应用量化层替换。 module {layers.0.mlp.gate_proj: _Linear()} if cfg.quantization_config is not None: # 复用量化替换逻辑无论是否 from_pretrained for k in module: module[k] _Fp8Linear() return module # 复现有量化配置但 buggy 版仍是普通 Linear cfg Cfg(quantization_config{load_in_8bit: True}) buggy from_config_buggy(cfg) fixed from_config_fixed(cfg) print(buggy 层类型:, type(buggy[layers.0.mlp.gate_proj]).__name__) # _Linear print(fixed 层类型:, type(fixed[layers.0.mlp.gate_proj]).__name__) # _Fp8Linear assert isinstance(buggy[layers.0.mlp.gate_proj], _Linear) assert isinstance(fixed[layers.0.mlp.gate_proj], _Fp8Linear)运行后buggy 版忽略了量化配置仍是_Linearfixed 版正确替换为_Fp8Linear复现并修复了根因。五、解决方案第一层最小直接修复最快的止血在from_config之后手动对模型应用量化层替换把quantization_config从 config 取出复用 bitsandbytes 的量化初始化from transformers import AutoModelForCausalLM, BitsAndBytesConfig def from_config_with_quantization(config): 第一层修复from_config 后手动应用 FP8/4bit 量化层替换。 # 1) 先按 config 构造骨架此时是普通 Linear model AutoModelForCausalLM.from_config(config) # 2) 取出量化配置 qcfg getattr(config, quantization_config, None) if qcfg is not None: # 3) 复用 bitsandbytes 的量化替换逻辑HF 内部 API from transformers.utils.bitsandbytes import replace_with_bnb_linear # 注意bitsandbytes 需要权重已存在from_config 权重是随机初始化 # 仍可量化只是量化的是随机权重用于测试/后续塞入离线量化权重 replace_with_bnb_linear( model, modules_to_not_convert[], # 这里 qcfg 若是 BitsAndBytesConfig 对象可直接用 ) return model # 使用 cfg MyConfig(quantization_configBitsAndBytesConfig(load_in_8bitTrue)) model from_config_with_quantization(cfg) # 现在 model 的 Linear 已是被量化层替换后的结构第一层让用户立刻在from_config路径得到量化骨架与from_pretrained行为对齐。六、解决方案第二层结构性改进把量化层替换抽成QuantizationApplierfrom_config与from_pretrained共用同一入口from dataclasses import dataclass from typing import Optional dataclass class QuantizationApplier: 统一的量化层替换入口from_config / from_pretrained 都调用它。 def apply(self, model, quantization_config): if quantization_config is None: return model method getattr(quantization_config, quant_method, None) if method in (bitsandbytes, fp8): from transformers.utils.bitsandbytes import replace_with_bnb_linear replace_with_bnb_linear(model, modules_to_not_convert[]) elif method fp8_native: # 原生 FP8如 torchao / TE替换为 FP8Linear self._replace_with_fp8(model) return model def _replace_with_fp8(self, model): import torch for name, mod in model.named_modules(): if isinstance(mod, torch.nn.Linear): # 用原生 FP8 Linear 包裹示意 setattr(model, name.split(.)[-1], torch.nn.Linear(*mod.weight.shape)) # 在 from_config 里调用示意挂到 PreTrainedModel.from_config 内部 def from_config_native(config): model build_skeleton(config) # 普通骨架 QuantizationApplier().apply(model, getattr(config, quantization_config, None)) return modelQuantizationApplier的语义是量化层替换是独立步骤两条加载路径都走它——from_config不再忽略quantization_configfrom_pretrained也复用同一逻辑行为对称。七、解决方案第三层断言 / CI 守护用 pytest 固化from_config 在给了 quantization_config 时必须产出量化层import pytest def test_from_config_applies_quantization(): from quant_apply import QuantizationApplier class FakeLinear: pass class FakeFp8: pass model {gate_proj: FakeLinear()} cfg type(C, (), {quant_method: fp8_native})() QuantizationApplier()._replace_with_fp8 lambda m: m.update({gate_proj: FakeFp8()}) QuantizationApplier().apply(model, cfg) assert isinstance(model[gate_proj], FakeFp8), from_config 应替换成 FP8 层 def test_from_config_no_quant_when_none(): from quant_apply import QuantizationApplier model {gate_proj: object()} out QuantizationApplier().apply(model, None) assert out is model, 无 quantization_config 时不应改动模型 def test_from_config_and_from_pretrained_consistent(): # 关键两条路径对 quantization_config 的处理应一致 from quant_apply import QuantizationApplier a QuantizationApplier().apply({x: object()}, type(C, (), {quant_method: fp8})()) b QuantizationApplier().apply({x: object()}, type(C, (), {quant_method: fp8})()) # 两者走同一 apply行为相同 assert type(a) is type(b)CI 跑pytest tests/test_fp8_from_config.py以后只要from_config又偷偷忽略quantization_config测试立刻红灯。八、排查清单当from_config FP8 量化没生效按顺序查模型是普通 BF16 而非量化层 →from_config忽略了quantization_config用QuantizationApplier在构造后应用替换。报FP8 requires weights from checkpoint → 旧的 from_config 拒绝量化改为先构造骨架再替换量化层权重可后续填入。from_pretrained能量化但from_config不能 → 量化替换逻辑只挂在加载路径抽成共享QuantizationApplier。构造成功但没省显存 → 检查替换是否真的把nn.Linear换成了 FP8 层而不是只设了个 flag。长期方案量化层替换作为独立步骤两条加载路径共用保证对称。九、小结[Quantization FP8] Native from_config support 的根因是FP8 量化层替换逻辑只挂在from_pretrained依赖真实权重而from_config路径根本不读取quantization_config导致声明了量化却没量化或报错。第一层在from_config后手动复用量化替换逻辑立刻得到量化骨架。第二层用QuantizationApplier把量化替换抽成独立步骤from_config与from_pretrained共用行为对称。第三层pytest 断言from_config 给了量化配置就产出量化层、无配置不改动、两路径一致防止回归。记住量化层替换应当是一个独立于权重来源的步骤无论从 config 构造还是从 checkpoint 加载只要quantization_config存在就该应用两条路径才对称。

相关新闻