Repository navigation
Dequantize fine-grained fp8 checkpoints on a 16bit load in FastLanguageModel, and save them as 16bit - #14516
Conversation
…icit load_in_16bit
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1a1bce7db3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if isinstance(quantization_config, dict) | ||
| else getattr(quantization_config, "to_dict", lambda: {})() | ||
| ) | ||
| if str(as_dict.get("quant_method", "")).lower() != "fp8": |
There was a problem hiding this comment.
Normalize enum quantization methods before comparing
When quantization_config is already a FineGrainedFP8Config, its to_dict() keeps quant_method as QuantizationMethod.FP8; in supported Transformers 5.5/5.10, str(QuantizationMethod.FP8).lower() is "quantizationmethod.fp8", not "fp8". Consequently this returns None, so object-backed checkpoint configs are not dequantized, and the newly added as_object=True unit-test case fails. Compare the enum's .value (or otherwise normalize it) before this check.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in cd97470: the helper now reads the enum's value before comparing (str(QuantizationMethod.FP8) is 'QuantizationMethod.FP8' even on 5.17). Added test_enum_quant_method_is_read_by_value, which fails on the previous commit and passes now.
|
@codex review |
for more information, see https://pre-commit.ci
… so save_pretrained writes 16bit weights [skip ci]
for more information, see https://pre-commit.ci

load_in_16bit = Trueon a fine-grained fp8 checkpoint is meant to load 16-bit weights. Since #11531,FastModelpassesdequantize = Trueto transformers' fp8 quantizer for this. TheFastLanguageModelfast text path (Llama, Qwen, Mistral, ... throughdispatch_model.from_pretrained) never does. On GPUs with native FP8 (sm89+: L4, H100, B200), transformers then keeps every projection as anFP8Linearwith fp8 weights, even though 16-bit was asked for.Reproduced on B200 with transformers 5.17.0, a tiny block-fp8 Llama (128 x 128 scales),
load_in_16bit = True:FastModelFastLanguageModelFP8Linear)Saving such a model was also broken, on both loaders. With
dequantize = True, transformers 5.x keeps theFp8Dequantizeload conversions on the model (model._weight_conversions), andsave_pretrainedreverses them intoFp8Quantize. On B200, transformers 5.17.0, after a 16-bit load followed bysave_pretrainedand a plain reload:FastModelquantization_config; reload changes all 14 projection weightsFastLanguageModelsave_pretrainedraisesValueError: Undefined Operation encountered!Change
_fp8_dequantize_quantization_config(model_config)inloader_utils.py. It returns the checkpoint's ownFineGrainedFP8Configwithdequantize = True, or None for anything other thanquant_method: fp8or when transformers has nodequantizeflag.FastLanguageModel.from_pretrainedsets it as thequantization_config, only when:load_in_16bitis explicit;fast_inferenceis off;quantization_config._drop_fp8_dequantize_conversions(model), called after a 16-bit load in both loaders:weight + weight_scale_inv -> weightconverter, whose only op isFp8Dequantize;Fp8Dequantizeprepended (expert merges) is replaced by its quantizer-free form fromget_model_conversion_mapping(model), so the experts still save per expert;Fp8Dequantizeconversion is present.FastModelloads (only what it saves);dequantizeflag.Validation
New
tests/test_fp8_16bit_text_loader_dequant.py:dequantize = Truefor a block-fp8 config (dict or object), and None for no config, bitsandbytes or ModelOpt;quant_methodis read by its value;FastLanguageModelon a tiny block-fp8 Llama: withload_in_16bit = Truethe model has 0 fp8 params and the quantizer gotdequantize = True; without the flag the config is not touched;_drop_fp8_dequantize_conversions,save_pretrainedand a reload: only bf16 tensors are written, noquantization_config, and every weight reloads bit-identical.With
tests/test_fp8_leftover_dequant_16bit.pyandtests/test_modelopt_fp8_loading.pyon B200:dequantizeflag); 47 passed, 4 failed, the same 4test_modelopt_fp8_loadingplan tests that fail on main.Not covered:
FineGrainedFP8Configthere has nodequantizeparameter, so both loaders still keepFP8Linearon an FP8-capable GPU, and there are noFp8Dequantizeconversions to drop. This PR does not change that.save_pretrained_mergedand GGUF export were not run on a dequantized fp8 model; onlysave_pretrained.