Dequantize fine-grained fp8 checkpoints on a 16bit load in FastLanguageModel, and save them as 16bit by danielhanchen · Pull Request #14516 · unslothai/unsloth · GitHub
Skip to content

Dequantize fine-grained fp8 checkpoints on a 16bit load in FastLanguageModel, and save them as 16bit - #14516

Merged
danielhanchen merged 5 commits into
mainfrom
fp8-16bit-text-dequant
Oct 21, 2026
Merged

danielhanchen merged 5 commits into
mainfrom
fp8-16bit-text-dequant

Conversation

@danielhanchen

@danielhanchen danielhanchen commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

load_in_16bit = True on a fine-grained fp8 checkpoint is meant to load 16-bit weights. Since #11531, FastModel passes dequantize = True to transformers' fp8 quantizer for this. The FastLanguageModel fast text path (Llama, Qwen, Mistral, ... through dispatch_model.from_pretrained) never does. On GPUs with native FP8 (sm89+: L4, H100, B200), transformers then keeps every projection as an FP8Linear with 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:

loader main this PR
FastModel 0 fp8 params 0 fp8 params
FastLanguageModel 14 fp8 params (FP8Linear) 0 fp8 params

Saving such a model was also broken, on both loaders. With dequantize = True, transformers 5.x keeps the Fp8Dequantize load conversions on the model (model._weight_conversions), and save_pretrained reverses them into Fp8Quantize. On B200, transformers 5.17.0, after a 16-bit load followed by save_pretrained and a plain reload:

checkpoint loader main this PR
block-fp8 Llama FastModel saves 16 fp8 + 16 scale tensors with no quantization_config; reload changes all 14 projection weights 21 bf16 tensors; reload bit-identical
block-fp8 Llama FastLanguageModel stays fp8 (above), saved as fp8 with its config 21 bf16 tensors; reload bit-identical
block-fp8 Qwen3-MoE both save_pretrained raises ValueError: Undefined Operation encountered! 45 bf16 tensors; reload bit-identical

Change

  • New _fp8_dequantize_quantization_config(model_config) in loader_utils.py. It returns the checkpoint's own FineGrainedFP8Config with dequantize = True, or None for anything other than quant_method: fp8 or when transformers has no dequantize flag.
  • FastLanguageModel.from_pretrained sets it as the quantization_config, only when:
    • load_in_16bit is explicit;
    • no 4-bit / 8-bit is requested;
    • fast_inference is off;
    • the user passed no quantization_config.
  • New _drop_fp8_dequantize_conversions(model), called after a 16-bit load in both loaders:
    • drops the generic weight + weight_scale_inv -> weight converter, whose only op is Fp8Dequantize;
    • a model converter that had Fp8Dequantize prepended (expert merges) is replaced by its quantizer-free form from get_model_conversion_mapping(model), so the experts still save per expert;
    • if no such form exists, it keeps the converter and says so;
    • it does nothing when no Fp8Dequantize conversion is present.
  • Unchanged:
    • loads without the flag;
    • ModelOpt / compressed-tensors checkpoints;
    • what FastModel loads (only what it saves);
    • transformers versions without the dequantize flag.

Validation

New tests/test_fp8_16bit_text_loader_dequant.py:

  • the helper returns dequantize = True for a block-fp8 config (dict or object), and None for no config, bitsandbytes or ModelOpt;
  • an enum quant_method is read by its value;
  • in a subprocess, FastLanguageModel on a tiny block-fp8 Llama: with load_in_16bit = True the model has 0 fp8 params and the quantizer got dequantize = True; without the flag the config is not touched;
  • a dequantizing load of a tiny block-fp8 Llama and Qwen3-MoE, then _drop_fp8_dequantize_conversions, save_pretrained and a reload: only bf16 tensors are written, no quantization_config, and every weight reloads bit-identical.

With tests/test_fp8_leftover_dequant_16bit.py and tests/test_modelopt_fp8_loading.py on B200:

  • transformers 5.17.0: 77 passed, 1 skipped.
  • transformers 4.57.6: the new tests skip (no dequantize flag); 47 passed, 4 failed, the same 4 test_modelopt_fp8_loading plan tests that fail on main.

Not covered:

  • transformers 4.57.x: FineGrainedFP8Config there has no dequantize parameter, so both loaders still keep FP8Linear on an FP8-capable GPU, and there are no Fp8Dequantize conversions to drop. This PR does not change that.
  • Merged saves: save_pretrained_merged and GGUF export were not run on a dequantized fp8 model; only save_pretrained.
  • GPUs without native FP8 (T4, A100): transformers already dequantizes there, so they were never affected.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-21T15:48:05.258476Z 8595e3d Manual request
🔒 Security Review ⚠️ Failed 2026-10-21T13:34:47.456264Z
We couldn’t complete this review. Comment @codex security review to try again.
1a1bce7 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

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.

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread unsloth/models/loader_utils.py Outdated
if isinstance(quantization_config, dict)
else getattr(quantization_config, "to_dict", lambda: {})()
)
if str(as_dict.get("quant_method", "")).lower() != "fp8":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@danielhanchen

Copy link
Copy Markdown
Member Author

@codex review

@danielhanchen danielhanchen changed the title FastLanguageModel: dequantize fine-grained fp8 checkpoints on an explicit load_in_16bit Dequantize fine-grained fp8 checkpoints on a 16bit load in FastLanguageModel, and save them as 16bit Oct 21, 2026
@danielhanchen

Copy link
Copy Markdown
Member Author

@danielhanchen
danielhanchen merged commit 795f8d0 into main Oct 21, 2026
20 of 38 checks passed
@danielhanchen
danielhanchen deleted the fp8-16bit-text-dequant branch October 21, 2026 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant