mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-10-02 03:14:52 +08:00
Add experimental support for transformers>=5.0 + min torch 2.8 (#975)
### What does this PR do? - Add experimental support for transformers >=5.0 and remove deprecated usages: https://github.com/huggingface/transformers/blob/main/MIGRATION_GUIDE_V5.md - ⚠️ For accelerate examples that used `--warmup-ratio: float` (deprecated in 5.x), we now change it to `--warmup-steps: float | int` which works as ratio if float but only for 5.x. For 4.x, it will error out if float and prompt user to change back to `--warmup-ratio` or pass an int absolute step count. - ⚠️ Unified Hugging Face checkpoint export for quantized checkpoints may not work for some models with transformers>=5.0 yet as it requires a lot of fixes (e.g. change in how MoE experts are organized) - ~Add Workaround for TRT-LLM's import of deprecated transformers functions so trt-llm based gpu unit tests work fine. Still deployment for models needs proper fixes directly in TRT-LLM hence llm/vlm ptq example tests still run with transformers 4.57~ - Everything except PTQ and Export (mainly MoE) should work fine with transformers>=5.0 - Bump min torch to 2.8 and enable 2.11 cicd testing - NOTE: Upcoming Nemo:26.04 container comes with transformers 5.3 ### Testing <!-- Mention how have you tested your change if applicable. --> - [x] CI/CD tests passing - [x] Manually tested unit tests, gpu tests with transformers 4.56 and 5.4 - [x] Manually tested example tests (except trt-llm container tests) with transformers 4.56 and 5.4 - [x] 2-gpu nightly CICD tests manually triggered and passing: [gpu tests](https://github.com/NVIDIA/Model-Optimizer/actions/runs/23867257540), [example tests](https://github.com/NVIDIA/Model-Optimizer/actions/runs/23867260643) ### Before your PR is "*Ready for review*" Make sure you read and follow [Contributor guidelines](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md) and your commits are signed (`git commit -s -S`). Make sure you read and follow the [Security Best Practices](https://github.com/NVIDIA/Model-Optimizer/blob/main/SECURITY.md#security-coding-practices-for-contributors) (e.g. avoiding hardcoded `trust_remote_code=True`, using `torch.load(..., weights_only=True)`, avoiding `pickle`, etc.). - Is this change backward compatible?: ✅ <!--- If ❌, explain why. --> - If you copied code from any other source, did you follow IP policy in [CONTRIBUTING.md](https://github.com/NVIDIA/Model-Optimizer/blob/main/CONTRIBUTING.md#-copying-code-from-other-sources)?: N/A <!--- Mandatory --> - Did you write any new necessary tests?: ✅ <!--- Mandatory for new features or examples. --> - Did you update [Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?: ✅ <!--- Only for new features, API changes, critical bug fixes or backward incompatible changes. --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Make remote-code usage opt-in via a configurable --trust_remote_code flag across examples and tools. * **Bug Fixes** * Improve checkpoint/resume detection and related training guidance to avoid erroneous errors. * **Refactor** * Consolidate dtype/config naming, switch warmup settings from ratio → steps, and unify tokenizer invocation patterns. * **Documentation** * Simplify changelog title and add misc notes for release 0.44. * **Chores** * Remove scheduled PR-branch cleanup workflow and relax/remove several transformers version pins. * **Tests** * Adjust test gates, skips, and structures to align with updated deps and behaviors. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
This commit is contained in:
@@ -70,7 +70,7 @@ jobs:
|
||||
uses: ./.github/workflows/_example_tests_runner.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
docker_image: "nvcr.io/nvidia/pytorch:${{ matrix.docker_image || '26.01' }}-py3"
|
||||
docker_image: "nvcr.io/nvidia/pytorch:${{ matrix.docker_image || '26.03' }}-py3"
|
||||
example: ${{ matrix.example }}
|
||||
timeout_minutes: 30
|
||||
pip_install_extras: "[hf,dev-test]"
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
uses: ./.github/workflows/_example_tests_runner.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
docker_image: "nvcr.io/nvidia/pytorch:${{ matrix.docker_image || '26.01' }}-py3"
|
||||
docker_image: "nvcr.io/nvidia/pytorch:${{ matrix.docker_image || '26.03' }}-py3"
|
||||
example: ${{ matrix.example }}
|
||||
timeout_minutes: 30
|
||||
pip_install_extras: "[hf,dev-test]"
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
uses: ./.github/workflows/_example_tests_runner.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc5"
|
||||
docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc10"
|
||||
example: ${{ matrix.example }}
|
||||
pip_install_extras: "[hf,dev-test]"
|
||||
runner: linux-amd64-gpu-rtxpro6000-latest-1
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
uses: ./.github/workflows/_example_tests_runner.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc5"
|
||||
docker_image: "nvcr.io/nvidia/tensorrt-llm/release:1.3.0rc10"
|
||||
example: ${{ matrix.example }}
|
||||
pip_install_extras: "[hf,dev-test]"
|
||||
runner: linux-amd64-gpu-rtxpro6000-latest-2
|
||||
|
||||
@@ -65,18 +65,19 @@ jobs:
|
||||
- example: gpu
|
||||
timeout: 45
|
||||
container_image: pytorch:26.01-py3
|
||||
# tests/gpu/_extensions/test_onnx_extensions.py fails for newer containers until https://github.com/tbenthompson/cppimport/pull/98
|
||||
- example: gpu-megatron
|
||||
timeout: 45
|
||||
container_image: pytorch:26.01-py3
|
||||
- example: gpu-trtllm
|
||||
timeout: 30
|
||||
container_image: tensorrt-llm/release:1.3.0rc5
|
||||
container_image: tensorrt-llm/release:1.3.0rc10
|
||||
runs-on: linux-amd64-gpu-rtxpro6000-latest-1
|
||||
timeout-minutes: ${{ matrix.timeout }}
|
||||
container: &gpu_container
|
||||
image: nvcr.io/nvidia/${{ matrix.container_image }}
|
||||
env:
|
||||
GIT_DEPTH: 1000 # For correct version for tests/gpu/torch/quantization/plugins/test_megatron.py
|
||||
GIT_DEPTH: 1000 # For correct version
|
||||
PIP_CONSTRAINT: "" # Disable pip constraint for upgrading packages
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
steps: &gpu_steps
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/ubuntu-setup
|
||||
- name: Run unit tests
|
||||
run: pip install tox && COV_ARGS="--cov" tox -e py312-torch210-tf_latest-unit
|
||||
run: pip install tox && COV_ARGS="--cov" tox -e py312-torch211-tf_latest-unit
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
@@ -65,6 +65,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
py: [10, 11, 13]
|
||||
steps:
|
||||
@@ -73,15 +74,16 @@ jobs:
|
||||
with:
|
||||
python-version: "3.${{ matrix.py }}"
|
||||
- name: Run unit tests
|
||||
run: pip install tox && tox -e py3${{ matrix.py }}-torch210-tf_latest-unit
|
||||
run: pip install tox && tox -e py3${{ matrix.py }}-torch211-tf_latest-unit
|
||||
multi-torch:
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: [linux]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
torch: [26, 27, 28, 29]
|
||||
torch: [28, 29, 210]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/ubuntu-setup
|
||||
@@ -93,13 +95,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
tf: [min]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: ./.github/actions/ubuntu-setup
|
||||
- name: Run unit tests
|
||||
run: pip install tox && tox -e py312-torch210-tf_${{ matrix.tf }}-unit
|
||||
run: pip install tox && tox -e py312-torch211-tf_${{ matrix.tf }}-unit
|
||||
launcher:
|
||||
if: github.event_name == 'pull_request'
|
||||
needs: [linux]
|
||||
@@ -123,6 +126,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
test-env: [onnx, torch]
|
||||
steps:
|
||||
|
||||
+5
-2
@@ -1,5 +1,6 @@
|
||||
NVIDIA Model Optimizer Changelog
|
||||
================================
|
||||
Changelog
|
||||
=========
|
||||
|
||||
0.44 (2026-05-xx)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -25,6 +26,8 @@ NVIDIA Model Optimizer Changelog
|
||||
**Misc**
|
||||
|
||||
- [Security] Changed the default of ``weights_only`` to ``True`` in ``torch.load`` for secure checkpoint loading. If you need to load a checkpoint that requires unpickling arbitrary objects, first register the class in ``torch.serialization.add_safe_globals([cls])`` before loading. Added :meth:`safe_save <modelopt.torch.utils.serialization.safe_save>` and :meth:`safe_load <modelopt.torch.utils.serialization.safe_load>` API to save and load checkpoints securely.
|
||||
- Bump minimum required PyTorch version to 2.8.
|
||||
- [Experimental] Add support for transformers>=5.0. Unified Hugging Face checkpoint export for quantized checkpoints may not work for MoE models with transformers>=5.0 yet.
|
||||
|
||||
0.43 (2026-04-09)
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -16,7 +16,7 @@ Latest Model Optimizer (``nvidia-modelopt``) currently has the following system
|
||||
+-------------------------+-----------------------------+
|
||||
| CUDA | 12.x, 13.x |
|
||||
+-------------------------+-----------------------------+
|
||||
| PyTorch | >=2.6 |
|
||||
| PyTorch | >=2.8 |
|
||||
+-------------------------+-----------------------------+
|
||||
| TensorRT-LLM (Optional) | >=1.0 |
|
||||
+-------------------------+-----------------------------+
|
||||
|
||||
@@ -16,7 +16,7 @@ per_device_train_batch_size: 2
|
||||
per_device_eval_batch_size: 2
|
||||
gradient_accumulation_steps: 2
|
||||
max_length: 4096
|
||||
warmup_ratio: 0.03
|
||||
warmup_steps: 0.03 # use warmup_ratio if using transformers<5.0
|
||||
lr_scheduler_type: cosine_with_min_lr
|
||||
lr_scheduler_kwargs:
|
||||
min_lr_rate: 0.1
|
||||
@@ -30,6 +30,6 @@ eval_steps: 8
|
||||
dataset_test_split: test
|
||||
|
||||
# ModelOpt Quantization Parameters
|
||||
quant_cfg: # Examples: MXFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG
|
||||
# For the full list of supported configs, do: mtq.config.choices
|
||||
quant_cfg: # Examples: MXFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG
|
||||
# For the full list of supported configs, do: mtq.config.choices
|
||||
calib_size: 128
|
||||
|
||||
@@ -21,7 +21,7 @@ lora_alpha: 16
|
||||
lora_dropout: 0.0
|
||||
lora_target_modules: all-linear
|
||||
max_length: 4096
|
||||
warmup_ratio: 0.03
|
||||
warmup_steps: 0.03 # use warmup_ratio if using transformers<5.0
|
||||
lr_scheduler_type: cosine_with_min_lr
|
||||
lr_scheduler_kwargs:
|
||||
min_lr_rate: 0.1
|
||||
@@ -35,6 +35,6 @@ eval_steps: 8
|
||||
dataset_test_split: test
|
||||
|
||||
# ModelOpt Quantization Parameters
|
||||
quant_cfg: # Examples: MXFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG
|
||||
# For the full list of supported configs, do: mtq.config.choices
|
||||
quant_cfg: # Examples: MXFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_WEIGHT_ONLY_CFG, NVFP4_MLP_ONLY_CFG
|
||||
# For the full list of supported configs, do: mtq.config.choices
|
||||
calib_size: 128
|
||||
|
||||
@@ -122,11 +122,7 @@ if __name__ == "__main__":
|
||||
parser = create_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
kwargs = {
|
||||
"device_map": "auto",
|
||||
"torch_dtype": "auto",
|
||||
"trust_remote_code": args.trust_remote_code,
|
||||
}
|
||||
kwargs = {"device_map": "auto", "dtype": "auto", "trust_remote_code": args.trust_remote_code}
|
||||
if args.lora_path:
|
||||
assert args.model_path is None, "You can only specify lora_path or model_path, not both."
|
||||
model_path = args.base_path
|
||||
|
||||
@@ -207,7 +207,7 @@
|
||||
" per_device_eval_batch_size=1,\n",
|
||||
" gradient_accumulation_steps=2,\n",
|
||||
" max_length=4096,\n",
|
||||
" warmup_ratio=0.03,\n",
|
||||
" warmup_steps=0.03, # use warmup_ratio if using transformers<5.0\n",
|
||||
" eval_strategy=\"steps\",\n",
|
||||
" eval_on_start=True,\n",
|
||||
" logging_steps=10,\n",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
kernels>=0.9.0
|
||||
torch>2.7.1
|
||||
trackio
|
||||
transformers>=4.55.0
|
||||
trl>=0.21.0
|
||||
|
||||
@@ -72,7 +72,7 @@ def main(script_args, training_args, model_args, quant_args):
|
||||
"revision": model_args.model_revision,
|
||||
"trust_remote_code": model_args.trust_remote_code,
|
||||
"attn_implementation": model_args.attn_implementation,
|
||||
"torch_dtype": getattr(model_args, "dtype", "bfloat16"),
|
||||
"dtype": getattr(model_args, "dtype", "bfloat16"),
|
||||
"use_cache": not training_args.gradient_checkpointing,
|
||||
}
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ def modelopt_ptq(
|
||||
) -> torch.nn.Module:
|
||||
"""Quantize the model with modelopt."""
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
model_path, trust_remote_code=trust_remote_code, torch_dtype="auto", device_map="auto"
|
||||
model_path, trust_remote_code=trust_remote_code, dtype="auto", device_map="auto"
|
||||
)
|
||||
model.eval()
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pyarrow
|
||||
torchao>=0.14.1
|
||||
transformers<5.0
|
||||
trl>=0.23.0
|
||||
|
||||
@@ -187,7 +187,7 @@ class SeqToSeqModel(EvalModel):
|
||||
args.update(device_map="auto")
|
||||
if self.load_8bit:
|
||||
args.update(device_map="auto", load_in_8bit=True)
|
||||
args.update(torch_dtype=getattr(torch, self.dtype) if self.dtype != "auto" else "auto")
|
||||
args.update(dtype=getattr(torch, self.dtype) if self.dtype != "auto" else "auto")
|
||||
if self.attn_implementation:
|
||||
args["attn_implementation"] = self.attn_implementation
|
||||
self.model = AutoModelForSeq2SeqLM.from_pretrained(
|
||||
@@ -246,7 +246,7 @@ class CausalModel(SeqToSeqModel):
|
||||
args.update(device_map="auto")
|
||||
if self.load_8bit:
|
||||
args.update(device_map="auto", load_in_8bit=True)
|
||||
args.update(torch_dtype=getattr(torch, self.dtype) if self.dtype != "auto" else "auto")
|
||||
args.update(dtype=getattr(torch, self.dtype) if self.dtype != "auto" else "auto")
|
||||
if self.attn_implementation:
|
||||
args["attn_implementation"] = self.attn_implementation
|
||||
self.model = AutoModelForCausalLM.from_pretrained(
|
||||
@@ -327,7 +327,7 @@ class LlamaModel(SeqToSeqModel):
|
||||
args.update(device_map="auto")
|
||||
if self.load_8bit:
|
||||
args.update(device_map="auto", load_in_8bit=True)
|
||||
args.update(torch_dtype=getattr(torch, self.dtype) if self.dtype != "auto" else "auto")
|
||||
args.update(dtype=getattr(torch, self.dtype) if self.dtype != "auto" else "auto")
|
||||
self.model = LlamaForCausalLM.from_pretrained(self.model_path, **args)
|
||||
print_gpu_utilization()
|
||||
if self.lora_path:
|
||||
|
||||
@@ -115,7 +115,7 @@ Please reference our [framework scripts](#framework-scripts) and our [docs](http
|
||||
| Kimi K2 | - | - | - | - | ✅ |
|
||||
| MiniMax M2.1 | - | - | - | - | ✅ |
|
||||
| T5 | ✅ | ✅ | ✅ | ✅ | - |
|
||||
| Whisper | ✅ | ❌ | ❌ | ❌ | - |
|
||||
| Whisper<sup>9</sup> | ✅ | ❌ | ❌ | ❌ | - |
|
||||
| Nemotron-3 | ✅ | ❌ | ❌ | ❌ | ✅ |
|
||||
|
||||
> *This is a subset of the models supported. For the full list please check the [TensorRT-LLM support matrix](https://nvidia.github.io/TensorRT-LLM/reference/precision.html#support-matrix)*
|
||||
@@ -127,7 +127,8 @@ Please reference our [framework scripts](#framework-scripts) and our [docs](http
|
||||
> *<sup>5.</sup>A selective set of the popular models are internally tested. The actual model support list may be longer. NVFP4 inference requires Blackwell GPUs and TensorRT-LLM v0.17 or later* \
|
||||
> *<sup>6.</sup>Some models currently support export to HF format only.* \
|
||||
> *<sup>7.</sup>[PTQ for DeepSeek](../deepseek/README.md)* \
|
||||
> *<sup>8.</sup>GLM-4.7 has MTP (Multi-Token Prediction) layers that are automatically loaded and excluded from quantization.*
|
||||
> *<sup>8.</sup>GLM-4.7 has MTP (Multi-Token Prediction) layers that are automatically loaded and excluded from quantization.* \
|
||||
> *<sup>9.</sup>Running Whisper model with transformers>=5.0 requires [torchcodec](https://github.com/meta-pytorch/torchcodec?tab=readme-ov-file#installing-cuda-enabled-torchcodec) and other system packages (e.g. ffmpeg).*
|
||||
|
||||
> *The accuracy loss after PTQ may vary depending on the actual model and the quantization method. Different models may have different accuracy loss and usually the accuracy loss is more significant when the base model is small. If the accuracy after PTQ is not meeting the requirement, please try either modifying [hf_ptq.py](./hf_ptq.py) and disabling the KV cache quantization or using the [QAT](./../llm_qat/README.md) instead. For NVFP4 quantization specifically, we recommend `nvfp4_mlp_only`, `nvfp4_experts_only`, or `nvfp4_omlp_only` to achieve higher accuracy by restricting quantization to the MLP/expert layers (and optionally the `o_proj` layer) while keeping the attention QKV projections unquantized.*
|
||||
|
||||
|
||||
@@ -583,7 +583,7 @@ def get_model(
|
||||
model_kwargs = config_kwargs.copy()
|
||||
# Don't set torch_dtype for VILA models as they handle it explicitly in their builder
|
||||
if "vila" not in ckpt_path.lower():
|
||||
model_kwargs.setdefault("torch_dtype", "auto")
|
||||
model_kwargs.setdefault("dtype", "auto")
|
||||
|
||||
if "vila" in ckpt_path.lower():
|
||||
hf_vila = AutoModel.from_pretrained(
|
||||
@@ -634,7 +634,7 @@ def get_model(
|
||||
ckpt_path,
|
||||
device_map="auto",
|
||||
trust_remote_code=trust_remote_code,
|
||||
torch_dtype="auto",
|
||||
dtype="auto",
|
||||
)
|
||||
else:
|
||||
architecture = hf_config.architectures[0]
|
||||
@@ -666,7 +666,7 @@ def get_model(
|
||||
model_kwargs2 = model_kwargs.copy()
|
||||
if auto_model_module not in [AutoModelForCausalLM, AutoModel]:
|
||||
model_kwargs2.pop("trust_remote_code", None)
|
||||
model_kwargs2["torch_dtype"] = torch_dtype
|
||||
model_kwargs2["dtype"] = torch_dtype
|
||||
model_kwargs2.pop("max_memory", None)
|
||||
model = from_config(hf_config, **model_kwargs2)
|
||||
|
||||
|
||||
@@ -149,9 +149,7 @@ def load_and_prepare_model(
|
||||
Tuple of (prepared_model, model_type, original_architectures, calibration_dataloader)
|
||||
"""
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
model_path,
|
||||
torch_dtype="auto",
|
||||
trust_remote_code=trust_remote_code,
|
||||
model_path, dtype="auto", trust_remote_code=trust_remote_code
|
||||
)
|
||||
model.eval()
|
||||
model_type = get_model_type(model)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
transformers==4.48.0
|
||||
@@ -1,2 +0,0 @@
|
||||
librosa
|
||||
soundfile
|
||||
@@ -2,5 +2,6 @@ compressed-tensors==0.12.0
|
||||
fire
|
||||
flash-attn>=2.6.0
|
||||
rouge_score>=0.1.2
|
||||
transformers<5.0
|
||||
transformers_stream_generator
|
||||
zstandard
|
||||
|
||||
@@ -165,7 +165,7 @@ CMD="accelerate launch --config-file accelerate_config/$CONFIG_FILE $FSDP_ARGS \
|
||||
--save_total_limit 2 \
|
||||
--learning_rate $LR \
|
||||
--weight_decay 0.0 \
|
||||
--warmup_ratio 0.1 \
|
||||
--warmup_steps 0.1 \
|
||||
--lr_scheduler_type linear \
|
||||
--logging_steps 1 \
|
||||
--report_to tensorboard \
|
||||
|
||||
@@ -174,9 +174,7 @@ def train():
|
||||
print_rank_0(f"Last checkpoint detected: {last_checkpoint}")
|
||||
|
||||
model = transformers.AutoModelForCausalLM.from_pretrained(
|
||||
model_args.model_name_or_path,
|
||||
cache_dir=training_args.cache_dir,
|
||||
torch_dtype=torch.bfloat16,
|
||||
model_args.model_name_or_path, cache_dir=training_args.cache_dir, dtype=torch.bfloat16
|
||||
)
|
||||
model.generation_config.do_sample = True
|
||||
tokenizer = transformers.AutoTokenizer.from_pretrained(
|
||||
@@ -231,7 +229,7 @@ def train():
|
||||
teacher_model = transformers.AutoModelForCausalLM.from_pretrained(
|
||||
model_args.teacher_model,
|
||||
cache_dir=training_args.cache_dir,
|
||||
torch_dtype=torch.bfloat16,
|
||||
dtype=torch.bfloat16,
|
||||
)
|
||||
distill_config = {
|
||||
"teacher_model": teacher_model,
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": null,
|
||||
"id": "0bf60614-99a0-48b0-85a8-1d88cd7c72ba",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -290,7 +290,7 @@
|
||||
" per_device_eval_batch_size=1,\n",
|
||||
" gradient_accumulation_steps=2,\n",
|
||||
" max_length=4096,\n",
|
||||
" warmup_ratio=0.03,\n",
|
||||
" warmup_steps=0.03, # use warmup_ratio if using transformers<5.0\n",
|
||||
" eval_strategy=\"steps\",\n",
|
||||
" eval_on_start=True,\n",
|
||||
" logging_steps=50,\n",
|
||||
|
||||
@@ -143,10 +143,7 @@ def main(args):
|
||||
# No need to specify attn_implementation here — mtsa.sparsify() sets it
|
||||
# automatically ("eager" for pytorch backend, "modelopt_triton" for triton).
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
args.pyt_ckpt_path,
|
||||
attn_implementation="eager",
|
||||
torch_dtype="auto",
|
||||
device_map="auto",
|
||||
args.pyt_ckpt_path, attn_implementation="eager", dtype="auto", device_map="auto"
|
||||
)
|
||||
tokenizer = AutoTokenizer.from_pretrained(args.pyt_ckpt_path)
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class DataCollator:
|
||||
[instance[key] for instance in instances] for key in ("src_idx", "label_idx")
|
||||
)
|
||||
|
||||
batch_encoded = self.tokenizer.batch_encode_plus(
|
||||
batch_encoded = self.tokenizer(
|
||||
sources,
|
||||
return_tensors="pt",
|
||||
padding=True,
|
||||
@@ -254,7 +254,7 @@ def main():
|
||||
dataloader = get_dataloader(
|
||||
accelerator, dataset, tokenizer, args.model_max_length, args.batch_size, shuffle=False
|
||||
)
|
||||
model = AutoModelForCausalLM.from_pretrained(args.model_dir, torch_dtype=torch.float16).to(
|
||||
model = AutoModelForCausalLM.from_pretrained(args.model_dir, dtype=torch.float16).to(
|
||||
accelerator.device
|
||||
)
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ def get_model(ckpt_path, dtype="fp16", trust_remote_code=False):
|
||||
dtype = torch.float32
|
||||
else:
|
||||
raise NotImplementedError(f"Unknown dtype {dtype}")
|
||||
model_kwargs = {"torch_dtype": dtype}
|
||||
model_kwargs = {"dtype": dtype}
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
ckpt_path, device_map="auto", **model_kwargs, trust_remote_code=trust_remote_code
|
||||
|
||||
@@ -297,13 +297,12 @@ def train():
|
||||
)
|
||||
|
||||
last_checkpoint = None
|
||||
if os.path.isdir(args.output_dir) and args.do_train and not args.overwrite_output_dir:
|
||||
if os.path.isdir(args.output_dir) and args.do_train and args.resume_from_checkpoint is None:
|
||||
last_checkpoint = get_last_checkpoint(args.output_dir)
|
||||
if last_checkpoint is not None and args.resume_from_checkpoint is None:
|
||||
if last_checkpoint is not None:
|
||||
print_rank_0(
|
||||
f"Checkpoint detected, resuming training at {last_checkpoint}. To avoid this"
|
||||
" behavior, change the `--output_dir` or add `--overwrite_output_dir` to train"
|
||||
" from scratch."
|
||||
" behavior, change the `--output_dir` or pass `--resume_from_checkpoint`."
|
||||
)
|
||||
|
||||
model = transformers.AutoModelForCausalLM.from_pretrained(
|
||||
@@ -335,18 +334,12 @@ def train():
|
||||
|
||||
# Detecting last checkpoint.
|
||||
last_checkpoint = None
|
||||
if os.path.isdir(args.output_dir) and args.do_train and not args.overwrite_output_dir:
|
||||
if os.path.isdir(args.output_dir) and args.do_train and args.resume_from_checkpoint is None:
|
||||
last_checkpoint = get_last_checkpoint(args.output_dir)
|
||||
if last_checkpoint is None and len(os.listdir(args.output_dir)) > 0:
|
||||
raise ValueError(
|
||||
f"Output directory ({args.output_dir}) already exists and is not empty. "
|
||||
"Use --overwrite_output_dir to overcome."
|
||||
)
|
||||
elif last_checkpoint is not None and args.resume_from_checkpoint is None:
|
||||
if last_checkpoint is not None:
|
||||
print_rank_0(
|
||||
f"Checkpoint detected, resuming training at {last_checkpoint}. To avoid this"
|
||||
" behavior, change the `--output_dir` or add `--overwrite_output_dir` to train"
|
||||
" from scratch."
|
||||
" behavior, change the `--output_dir` or pass `--resume_from_checkpoint`."
|
||||
)
|
||||
|
||||
# Training
|
||||
|
||||
@@ -40,7 +40,7 @@ def get_calib_dataloader(
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
batch_encoded = tokenizer.batch_encode_plus(
|
||||
batch_encoded = tokenizer(
|
||||
dataset, return_tensors="pt", padding=True, truncation=True, max_length=block_size
|
||||
)
|
||||
if device:
|
||||
@@ -98,7 +98,7 @@ def get_model(ckpt_path, dtype="fp16", trust_remote_code=False):
|
||||
dtype = torch.float32
|
||||
else:
|
||||
raise NotImplementedError(f"Unknown dtype {dtype}")
|
||||
model_kwargs = {"torch_dtype": dtype}
|
||||
model_kwargs = {"dtype": dtype}
|
||||
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
ckpt_path, device_map="auto", **model_kwargs, trust_remote_code=trust_remote_code
|
||||
|
||||
@@ -88,11 +88,11 @@ CMD="accelerate launch --multi_gpu --mixed_precision bf16 finetune.py \
|
||||
--save_total_limit 10 \
|
||||
--learning_rate 2e-5 \
|
||||
--weight_decay 0.1 \
|
||||
--warmup_ratio 0.0 \
|
||||
--warmup_steps 0.0 \
|
||||
--lr_scheduler_type cosine \
|
||||
--logging_steps 1 \
|
||||
--fsdp 'full_shard auto_wrap' \
|
||||
--fsdp_transformer_layer_cls_to_wrap LlamaDecoderLayer \
|
||||
--fsdp_config '{\"transformer_layer_cls_to_wrap\": \"LlamaDecoderLayer\"}' \
|
||||
--tf32 True \
|
||||
--modelopt_restore_path $MODELOPT_RESTORE_PATH \
|
||||
--report_to tensorboard \
|
||||
|
||||
@@ -100,7 +100,7 @@ class SpecBenchMedusaModel(Model):
|
||||
self.draft_model_path,
|
||||
model_dir,
|
||||
medusa_num_heads=self.medusa_num_heads,
|
||||
torch_dtype=torch_dtype,
|
||||
dtype=torch_dtype,
|
||||
low_cpu_mem_usage=True,
|
||||
)
|
||||
self.model = self.model.to(self.device)
|
||||
|
||||
@@ -135,7 +135,7 @@ def main(args: argparse.Namespace) -> None:
|
||||
dataset = dataset.select(range(args.debug_max_num_conversations))
|
||||
|
||||
model = AutoModel.from_pretrained(
|
||||
args.model, torch_dtype="auto", device_map="auto", trust_remote_code=args.trust_remote_code
|
||||
args.model, dtype="auto", device_map="auto", trust_remote_code=args.trust_remote_code
|
||||
)
|
||||
num_hidden_layers = getattr(model.config, "num_hidden_layers", None)
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ def train():
|
||||
if checkpoint:
|
||||
with patch_transformers5_params_loading():
|
||||
model = load_vlm_or_llm(
|
||||
checkpoint, torch_dtype="auto", trust_remote_code=model_args.trust_remote_code
|
||||
checkpoint, dtype="auto", trust_remote_code=model_args.trust_remote_code
|
||||
)
|
||||
tokenizer = transformers.AutoTokenizer.from_pretrained(
|
||||
checkpoint, trust_remote_code=model_args.trust_remote_code
|
||||
@@ -238,7 +238,7 @@ def train():
|
||||
model_args.model_name_or_path,
|
||||
use_fake_base=model_args.use_fake_base_for_offline,
|
||||
use_offline_training=use_offline_training,
|
||||
torch_dtype="auto",
|
||||
dtype="auto",
|
||||
device_map="cpu",
|
||||
trust_remote_code=model_args.trust_remote_code,
|
||||
)
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
accelerate==1.12.0
|
||||
transformers<5.4
|
||||
|
||||
@@ -39,9 +39,7 @@ def parse_args():
|
||||
mto.enable_huggingface_checkpointing()
|
||||
|
||||
args = parse_args()
|
||||
model = load_vlm_or_llm(
|
||||
args.model_path, torch_dtype="auto", trust_remote_code=args.trust_remote_code
|
||||
)
|
||||
model = load_vlm_or_llm(args.model_path, dtype="auto", trust_remote_code=args.trust_remote_code)
|
||||
model.eval()
|
||||
with torch.inference_mode():
|
||||
export_speculative_decoding(model, export_dir=args.export_path)
|
||||
|
||||
@@ -49,8 +49,7 @@ quant_config: dict[str, Any] = {
|
||||
def _fakequant_run_prolog_worker(self) -> None:
|
||||
trust_remote_code = os.environ.get("TRUST_REMOTE_CODE", "false").lower() == "true"
|
||||
tokenizer = AutoTokenizer.from_pretrained(
|
||||
self.model_runner.model_config.tokenizer,
|
||||
trust_remote_code=trust_remote_code,
|
||||
self.model_runner.model_config.tokenizer, trust_remote_code=trust_remote_code
|
||||
)
|
||||
if tokenizer.pad_token != "<unk>" or tokenizer.pad_token is None:
|
||||
tokenizer.pad_token = tokenizer.eos_token
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
deepspeed>=0.16.0
|
||||
git+https://github.com/bfshi/scaling_on_scales.git
|
||||
transformers<=4.50.0
|
||||
@@ -0,0 +1 @@
|
||||
transformers<5.0
|
||||
@@ -3,6 +3,5 @@ accelerate
|
||||
datasets
|
||||
numpy
|
||||
safetensors>=0.4.0
|
||||
|
||||
torch>=2.0.0
|
||||
transformers>=4.30.0
|
||||
torch>=2.6.0
|
||||
transformers<5.0
|
||||
|
||||
@@ -8,4 +8,4 @@ pandas
|
||||
sentencepiece>=0.2.1
|
||||
tokenizers>=0.14.1
|
||||
torch>=2.6.0
|
||||
transformers>=4.53
|
||||
transformers<5.0
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
datasets>=2.14.5
|
||||
fire==0.6.0
|
||||
fire>=0.5.0
|
||||
numpy==1.26.4
|
||||
openai>=0.28.1
|
||||
pandas==2.2.2
|
||||
|
||||
@@ -6,7 +6,5 @@ ltx-trainer @ git+https://github.com/Lightricks/LTX-2.git#subdirectory=packages/
|
||||
|
||||
# NVIDIA ModelOpt (quantization & distillation)
|
||||
nvidia-modelopt
|
||||
pyyaml
|
||||
safetensors
|
||||
|
||||
torch>=2.0
|
||||
|
||||
@@ -180,7 +180,7 @@ def get_initial_inputs(
|
||||
"""
|
||||
# tokenizer.pad_token = "[PAD]"
|
||||
tokenizer.pad_token = tokenizer.eos_token
|
||||
encodings_dict = tokenizer.batch_encode_plus(prompt, padding=True)
|
||||
encodings_dict = tokenizer(prompt, padding=True)
|
||||
|
||||
# max_length = model.config.max_position_embeddings
|
||||
# input_ids = tokenizer.encode(text, truncation=True, padding='max_length', max_length=max_length)
|
||||
@@ -242,7 +242,7 @@ def get_calib_inputs(
|
||||
|
||||
# dataset2 = dataset2.shuffle(seed=42)
|
||||
dataset2 = dataset2[column][:calib_size]
|
||||
batch_encoded = tokenizer.batch_encode_plus(
|
||||
batch_encoded = tokenizer(
|
||||
dataset2, return_tensors="pt", padding=True, truncation=True, max_length=block_size
|
||||
) # return_tensors="pt",
|
||||
batch_encoded = batch_encoded.to(device)
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
"""Nvidia Model Optimizer (modelopt)."""
|
||||
|
||||
import warnings as _warnings
|
||||
from importlib.metadata import version as _version
|
||||
|
||||
__version__ = _version("nvidia-modelopt")
|
||||
|
||||
@@ -53,7 +53,7 @@ class ModelLoader:
|
||||
"""Load HuggingFace model based on model type."""
|
||||
print(f"Loading HF model from {self.hf_model_path} with model type {self.model_type}")
|
||||
self.hf_model = AutoModelForCausalLM.from_pretrained(
|
||||
self.hf_model_path, torch_dtype=torch.float16, trust_remote_code=trust_remote_code
|
||||
self.hf_model_path, dtype=torch.float16, trust_remote_code=trust_remote_code
|
||||
)
|
||||
|
||||
return self.hf_model.eval().cuda() # type: ignore[attr-defined]
|
||||
@@ -76,7 +76,7 @@ class WrapperModelForCausalLM(torch.nn.Module):
|
||||
self.lm_head = model.lm_head
|
||||
self.config = model.config
|
||||
|
||||
def forward(self, input_ids: torch.Tensor | None, past_key_values: tuple):
|
||||
def forward(self, input_ids: torch.Tensor, past_key_values: tuple):
|
||||
"""Forward pass."""
|
||||
# Convert tuple cache to DynamicCache for models that require it (e.g., Qwen3)
|
||||
cache = DynamicCache(config=self.config)
|
||||
@@ -84,9 +84,30 @@ class WrapperModelForCausalLM(torch.nn.Module):
|
||||
cache.value_cache = [kv[1] for kv in past_key_values]
|
||||
past_key_values = cache
|
||||
|
||||
outputs = self.model(input_ids=input_ids, past_key_values=past_key_values, use_cache=True)
|
||||
# Pre-compute a 4D causal mask so that transformers' internal mask creation
|
||||
# (which relies on Python-int shapes) is bypassed entirely. During ONNX/JIT tracing,
|
||||
# tensor.shape[N] can return a 0-dim scalar tensor instead of a Python int, which breaks
|
||||
# the masking code in transformers>=5.4
|
||||
seq_len = input_ids.shape[1]
|
||||
past_len = past_key_values.get_seq_length() # type: ignore[attr-defined]
|
||||
causal_mask = (
|
||||
torch.tril(
|
||||
torch.ones(seq_len, past_len + seq_len, dtype=torch.bool, device=input_ids.device),
|
||||
diagonal=past_len,
|
||||
)
|
||||
.unsqueeze(0)
|
||||
.unsqueeze(0)
|
||||
)
|
||||
|
||||
outputs = self.model(
|
||||
input_ids=input_ids,
|
||||
attention_mask=causal_mask,
|
||||
past_key_values=past_key_values,
|
||||
use_cache=True,
|
||||
)
|
||||
hidden_states = outputs[0]
|
||||
past_key_values = outputs.past_key_values.to_legacy_cache()
|
||||
cache = outputs.past_key_values
|
||||
past_key_values = tuple(zip(cache.key_cache, cache.value_cache))
|
||||
logits = self.lm_head(hidden_states)
|
||||
return logits, past_key_values
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# TODO: cppimport is no longer maintained, switch to a different library
|
||||
import cppimport
|
||||
|
||||
from modelopt.onnx.logging_config import logger
|
||||
@@ -30,6 +31,8 @@ try:
|
||||
sys.path.remove(path)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"{e}\nUnable to load `modelopt_round_and_pack_ext', falling back to python based optimized version"
|
||||
f"{e}\nUnable to load `modelopt_round_and_pack_ext', falling back to python based optimized version. "
|
||||
"If you see `copy_file() got an unexpected keyword argument 'dry_run'`, you will need "
|
||||
"https://github.com/tbenthompson/cppimport/pull/98 or downgrade setuptools until we have a workaround"
|
||||
)
|
||||
round_and_pack_ext = None
|
||||
|
||||
@@ -22,20 +22,24 @@ from torch import __version__ as _torch_version
|
||||
|
||||
from . import distill, nas, opt, peft, prune, quantization, sparsity, speculative, utils
|
||||
|
||||
if _Version(_torch_version) < _Version("2.7"):
|
||||
if _Version(_torch_version) < _Version("2.9"):
|
||||
_warnings.warn(
|
||||
"nvidia-modelopt will drop torch<2.7 support in a future release.", DeprecationWarning
|
||||
"nvidia-modelopt will drop torch<2.9 support in a future release.", DeprecationWarning
|
||||
)
|
||||
|
||||
# Since `hf` dependencies are optional and users have pre-installed transformers, we need to ensure
|
||||
# correct version is installed to avoid incompatibility issues.
|
||||
|
||||
try:
|
||||
from transformers import __version__ as _transformers_version
|
||||
|
||||
if not (_Version("4.56") <= _Version(_transformers_version) < _Version("5.0")):
|
||||
if _Version(_transformers_version) < _Version("4.56"):
|
||||
_warnings.warn(
|
||||
f"transformers version {_transformers_version} is not tested with nvidia-modelopt and may cause issues. "
|
||||
"Please install recommended version with `pip install nvidia-modelopt[hf]` if working with HF models.",
|
||||
f"transformers {_transformers_version} is not tested with current version of modelopt and may cause issues."
|
||||
" Please install recommended version with `pip install -U nvidia-modelopt[hf]` if working with HF models.",
|
||||
)
|
||||
elif _Version(_transformers_version) >= _Version("5.0"):
|
||||
_warnings.warn(
|
||||
"transformers>=5.0 support is experimental. Unified Hugging Face checkpoint export for quantized "
|
||||
"checkpoints may not work for some models yet.",
|
||||
)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -151,7 +151,8 @@ def torch_to_tensorrt_llm_checkpoint(
|
||||
model_metadata_config = model.config.__dict__
|
||||
vocab_size = model.config.vocab_size
|
||||
hf_config = model.config
|
||||
architecture = model.config.architectures[0]
|
||||
architectures = getattr(model.config, "architectures", None)
|
||||
architecture = architectures[0] if architectures else ""
|
||||
|
||||
# For Baichuan 13B, we check if alibi is used with the alibi_mask property.
|
||||
if hasattr(model, "model") and hasattr(model.model, "alibi_mask"):
|
||||
|
||||
@@ -48,6 +48,7 @@ MODEL_NAME_TO_HF_ARCH_MAP = {
|
||||
"gemma": "GemmaForCausalLM",
|
||||
"gemma3": "Gemma3ForCausalLM",
|
||||
"gpt": "GPTForCausalLM",
|
||||
"qwen": "QWenForCausalLM",
|
||||
"enc": "EncoderModel",
|
||||
"dec": "DecoderModel",
|
||||
"mllama": "MLLaMAModel",
|
||||
@@ -240,7 +241,7 @@ def convert_to_tensorrt_llm_config(
|
||||
layernorm_type_map = {i.name: i.value for i in LayerNormType}
|
||||
layernorm_position_map = {i.name: i.value for i in LayerNormPositionType}
|
||||
|
||||
if decoder_type in ["gpt", "gemma", "llama"]:
|
||||
if decoder_type in ["gpt", "gemma", "llama", "qwen"]:
|
||||
pass
|
||||
elif decoder_type == "mpt":
|
||||
config.update(
|
||||
|
||||
@@ -23,6 +23,8 @@ from contextlib import contextmanager, nullcontext
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
from huggingface_hub import try_to_load_from_cache
|
||||
from huggingface_hub.errors import HFValidationError
|
||||
|
||||
from modelopt.torch.utils import print_rank_0
|
||||
|
||||
@@ -57,7 +59,16 @@ def register_for_patching(name: str, cls: type, patch_methods: list[tuple[str, A
|
||||
|
||||
|
||||
def _get_modelopt_state_path(model_name_or_path: str) -> str:
|
||||
return os.path.join(model_name_or_path, _MODELOPT_STATE_SAVE_NAME)
|
||||
"""Get the path to the ModelOpt state file or empty string if not found.
|
||||
|
||||
Also handles HF model card as input path. However for hf hub models, we dont have modelopt_state at the moment.
|
||||
"""
|
||||
if os.path.isdir(model_name_or_path):
|
||||
return os.path.join(model_name_or_path, _MODELOPT_STATE_SAVE_NAME)
|
||||
try:
|
||||
return try_to_load_from_cache(model_name_or_path, _MODELOPT_STATE_SAVE_NAME) or ""
|
||||
except HFValidationError:
|
||||
return ""
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
"""ModelOpt plugin for enabling automatic save/restore of ModelOpt state for HuggingFace models."""
|
||||
|
||||
import os
|
||||
import types
|
||||
from contextlib import contextmanager
|
||||
|
||||
@@ -24,8 +25,9 @@ from transformers import modeling_utils as tf_modeling_utils
|
||||
|
||||
from modelopt.torch.utils import report_memory
|
||||
|
||||
from ..conversion import ModeloptStateManager
|
||||
from ..conversion import ModeloptStateManager, load_modelopt_state
|
||||
from .huggingface import (
|
||||
_get_modelopt_state_path,
|
||||
_new_save_pretrained,
|
||||
_patch_model_init_for_modelopt,
|
||||
enable_huggingface_checkpointing,
|
||||
@@ -60,6 +62,39 @@ def _undo_torch_init_override_by_transformers():
|
||||
setattr(torch.nn.init, name, init_func)
|
||||
|
||||
|
||||
def _restore_qtensor_wrappers(model, model_path):
|
||||
"""Re-wrap QTensorWrapper weights that were replaced during HF weight loading.
|
||||
|
||||
Transformers>=5.0 uses ``setattr`` to load weights, which replaces ``QTensorWrapper``
|
||||
objects with plain ``Parameter`` tensors. The compressed data is loaded correctly but
|
||||
the wrapper metadata (original shape, dtype, qtensor class) is lost. This function
|
||||
reads the saved ``q_tensor_state`` from ``modelopt_state.pth`` and re-wraps the affected
|
||||
weights.
|
||||
"""
|
||||
modelopt_state_path = _get_modelopt_state_path(model_path)
|
||||
if not os.path.isfile(modelopt_state_path):
|
||||
return
|
||||
|
||||
from modelopt.torch.quantization.nn.modules.quant_linear import RealQuantLinear
|
||||
from modelopt.torch.quantization.qtensor import QTensorWrapper
|
||||
|
||||
state = load_modelopt_state(modelopt_state_path)
|
||||
for _, mode_config in state["modelopt_state_dict"]:
|
||||
q_tensor_state = mode_config.get("metadata", {}).get("q_tensor_state", {})
|
||||
if not q_tensor_state:
|
||||
continue
|
||||
for name, module in model.named_modules():
|
||||
if (
|
||||
isinstance(module, RealQuantLinear)
|
||||
and name in q_tensor_state
|
||||
and not isinstance(module.weight, QTensorWrapper)
|
||||
):
|
||||
module._parameters["weight"] = QTensorWrapper(
|
||||
qtensor=module.weight.data,
|
||||
metadata=q_tensor_state[name]["metadata"],
|
||||
)
|
||||
|
||||
|
||||
def _new_from_pretrained(cls, /, pretrained_model_name_or_path, *args, **kwargs):
|
||||
"""Patch for `cls.from_pretrained` method to restore ModelOpt state."""
|
||||
with _patch_model_init_for_modelopt(
|
||||
@@ -69,6 +104,8 @@ def _new_from_pretrained(cls, /, pretrained_model_name_or_path, *args, **kwargs)
|
||||
pretrained_model_name_or_path, *args, **kwargs
|
||||
)
|
||||
|
||||
_restore_qtensor_wrappers(model, pretrained_model_name_or_path)
|
||||
|
||||
return model
|
||||
|
||||
|
||||
@@ -93,12 +130,12 @@ def _save_pretrained_with_checks(self, save_directory, *args, **kwargs):
|
||||
|
||||
# [Fix for huggingface bug] deepspeed zero3 training backend only loads params into the model from
|
||||
# state_dict, but not buffers. So lets explicitly load the buffers into the model from state_dict.
|
||||
def _load_params_and_buffers_into_zero3_model(model_to_load, state_dict):
|
||||
def _load_params_and_buffers_into_zero3_model(model_to_load, state_dict, load_config=None):
|
||||
buffer_names = [name for name, _ in model_to_load.named_buffers()]
|
||||
buffer_state_dict = {k: v for k, v in state_dict.items() if k in buffer_names}
|
||||
model_to_load.load_state_dict(buffer_state_dict, strict=False)
|
||||
return tf_modeling_utils._modelopt_cache["_load_state_dict_into_zero3_model"](
|
||||
model_to_load, state_dict
|
||||
model_to_load, state_dict, load_config
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ class Nvfp4Linear(Function):
|
||||
grad_weight = grad_outputs.reshape(-1, grad_outputs.shape[-1]).T @ input_tensor.reshape(
|
||||
-1, input_tensor.shape[-1]
|
||||
)
|
||||
if ctx.compute_bias_grad is not None:
|
||||
if ctx.compute_bias_grad:
|
||||
# Sum all dimensions except the last one
|
||||
grad_bias = grad_outputs.sum(dim=list(range(grad_outputs.dim() - 1)))
|
||||
|
||||
|
||||
@@ -246,26 +246,39 @@ class RealQuantLinear(QuantModule):
|
||||
self.weight_quantizer = weight_quantizer
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if (
|
||||
key == "weight"
|
||||
and self.weight_quantizer
|
||||
and self.weight_quantizer.is_enabled
|
||||
and not self.weight_quantizer._fake_quant
|
||||
and value.element_size() > 1
|
||||
):
|
||||
# reset the amax for later calibration
|
||||
if key == "weight" and not isinstance(value, QTensorWrapper):
|
||||
existing = self.get("weight")
|
||||
if (
|
||||
self.weight_quantizer.amax is not None
|
||||
and self.weight_quantizer.amax.is_meta
|
||||
isinstance(existing, QTensorWrapper)
|
||||
and not existing.is_meta
|
||||
and existing.shape == value.shape
|
||||
):
|
||||
delattr(self.weight_quantizer, "_amax")
|
||||
self.weight_quantizer.amax = self.weight_quantizer._get_amax(value)
|
||||
self.weight_quantizer._calibrator.reset()
|
||||
# compress the weight
|
||||
real_quant_tensor = self.weight_quantizer(value)
|
||||
real_quant_value = QTensorWrapper(real_quant_tensor)
|
||||
del value # delete the original weight to save memory
|
||||
value = real_quant_value
|
||||
# Loading a compressed weight (e.g. from safetensors in transformers>=5.0
|
||||
# which replaces parameters via setattr rather than copy_). Preserve the
|
||||
# QTensorWrapper type and metadata.
|
||||
super().__setitem__(
|
||||
key, QTensorWrapper(qtensor=value.data, metadata=existing.metadata)
|
||||
)
|
||||
return
|
||||
if (
|
||||
self.weight_quantizer
|
||||
and self.weight_quantizer.is_enabled
|
||||
and not self.weight_quantizer._fake_quant
|
||||
and value.element_size() > 1
|
||||
):
|
||||
# reset the amax for later calibration
|
||||
if (
|
||||
self.weight_quantizer.amax is not None
|
||||
and self.weight_quantizer.amax.is_meta
|
||||
):
|
||||
delattr(self.weight_quantizer, "_amax")
|
||||
self.weight_quantizer.amax = self.weight_quantizer._get_amax(value)
|
||||
self.weight_quantizer._calibrator.reset()
|
||||
# compress the weight
|
||||
real_quant_tensor = self.weight_quantizer(value)
|
||||
real_quant_value = QTensorWrapper(real_quant_tensor)
|
||||
del value # delete the original weight to save memory
|
||||
value = real_quant_value
|
||||
super().__setitem__(key, value)
|
||||
|
||||
# Monkey patch the _parameters.__setitem__ to real quant the weight when loading
|
||||
|
||||
@@ -190,8 +190,10 @@ def init_quantized_weights(
|
||||
|
||||
with init_empty_weights():
|
||||
# Fix torch_dtype to match original model
|
||||
torch_dtype = kwargs.get("torch_dtype", getattr(config, "torch_dtype", torch.float16))
|
||||
model = cls.from_config(config, torch_dtype=torch_dtype)
|
||||
torch_dtype = kwargs.get(
|
||||
"dtype", kwargs.get("torch_dtype", getattr(config, "torch_dtype", torch.float16))
|
||||
)
|
||||
model = cls.from_config(config, dtype=torch_dtype)
|
||||
|
||||
mtq.quantize(model, quant_cfg)
|
||||
mtq.compress(model, config=mtq.CompressConfig(quant_gemm=quant_gemm))
|
||||
|
||||
@@ -172,14 +172,20 @@ class _QuantAttention(QuantModule):
|
||||
The forward method is used to patch the attention interface with _quantized_attention.
|
||||
Once output tensors are generated, it restores the original attention interface.
|
||||
"""
|
||||
# In transformers>=5.0 some attention classes (e.g. BertAttention) no longer store
|
||||
# `self.config` directly; fall back to searching child modules for a config attribute.
|
||||
_config = getattr(self, "config", None)
|
||||
if _config is None:
|
||||
_config = next(
|
||||
(getattr(m, "config", None) for m in self.children() if hasattr(m, "config")),
|
||||
None,
|
||||
)
|
||||
_attn_impl = getattr(_config, "_attn_implementation", None) if _config is not None else None
|
||||
|
||||
def _is_eager_attention():
|
||||
if self.config._attn_implementation == "eager":
|
||||
if _attn_impl is None or _attn_impl == "eager":
|
||||
return True
|
||||
return bool(
|
||||
self.config._attn_implementation == "sdpa"
|
||||
and kwargs.get("output_attentions", False)
|
||||
)
|
||||
return bool(_attn_impl == "sdpa" and kwargs.get("output_attentions", False))
|
||||
|
||||
# Get the original transformers module before wrapped in any ModelOpt DynamicModule
|
||||
module: ModuleType = inspect.getmodule(self.get_attn_type(self))
|
||||
@@ -188,7 +194,7 @@ class _QuantAttention(QuantModule):
|
||||
original_attention_interface = (
|
||||
module.eager_attention_forward
|
||||
if _is_eager_attention()
|
||||
else module.ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
|
||||
else module.ALL_ATTENTION_FUNCTIONS[_attn_impl]
|
||||
)
|
||||
patch_fn = partial(self._quantized_attention, original_attention_interface)
|
||||
|
||||
@@ -201,7 +207,7 @@ class _QuantAttention(QuantModule):
|
||||
)
|
||||
module.eager_attention_forward = patch_fn # type: ignore[attr-defined]
|
||||
else:
|
||||
module.ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation] = patch_fn
|
||||
module.ALL_ATTENTION_FUNCTIONS[_attn_impl] = patch_fn
|
||||
|
||||
try:
|
||||
outputs = super().forward(*args, **kwargs)
|
||||
@@ -210,9 +216,7 @@ class _QuantAttention(QuantModule):
|
||||
if _is_eager_attention():
|
||||
module.eager_attention_forward = original_attention_interface # type: ignore[attr-defined]
|
||||
else:
|
||||
module.ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation] = (
|
||||
original_attention_interface
|
||||
)
|
||||
module.ALL_ATTENTION_FUNCTIONS[_attn_impl] = original_attention_interface
|
||||
|
||||
return outputs
|
||||
|
||||
@@ -333,10 +337,14 @@ class HFParallelLinear(torch.nn.Linear, DynamicModule):
|
||||
shard = None
|
||||
|
||||
def _setup(self):
|
||||
assert self.weight.placements == self.shard, (
|
||||
f"Received unexpected shard {self.weight.placements} for {self}"
|
||||
)
|
||||
tp_group = self.weight.device_mesh.get_group()
|
||||
if isinstance(self.weight, torch.distributed.tensor.DTensor): # transformers<5.0
|
||||
assert self.weight.placements == self.shard, (
|
||||
f"Received unexpected shard {self.weight.placements} for {self}"
|
||||
)
|
||||
device_mesh = self.weight.device_mesh
|
||||
else: # transformers>=5.0: weights are plain Parameters, mesh is on the module
|
||||
device_mesh = self._hf_device_mesh
|
||||
tp_group = device_mesh.get_group()
|
||||
self._parallel_state = ParallelState(data_parallel_group=-1, tensor_parallel_group=tp_group)
|
||||
|
||||
@classmethod
|
||||
@@ -371,14 +379,17 @@ class _QuantHFParallelLinear(_ParallelLinear):
|
||||
|
||||
@contextmanager
|
||||
def enable_weight_access_and_writeback(self):
|
||||
assert self.weight.placements == self.shard, (
|
||||
f"Received unexpected shard {self.weight.placements} for {self}"
|
||||
)
|
||||
weight = self.weight
|
||||
# TODO: To support TP + FSDP, we need to redistribute the tensor with replicate instead of shard
|
||||
self.weight = nn.Parameter(weight.to_local())
|
||||
yield
|
||||
self.weight = weight
|
||||
if isinstance(self.weight, torch.distributed.tensor.DTensor): # transformers<5.0
|
||||
assert self.weight.placements == self.shard, (
|
||||
f"Received unexpected shard {self.weight.placements} for {self}"
|
||||
)
|
||||
weight = self.weight
|
||||
# TODO: To support TP + FSDP, we need to redistribute the tensor with replicate instead of shard
|
||||
self.weight = nn.Parameter(weight.to_local())
|
||||
yield
|
||||
self.weight = weight
|
||||
else: # transformers>=5.0: weights are already plain Parameters
|
||||
yield
|
||||
|
||||
|
||||
@QuantModuleRegistry.register({HFColumnParallelLinear: "HFColumnParallelLinear"})
|
||||
@@ -441,9 +452,12 @@ class _TransposedQuantization(torch.autograd.Function):
|
||||
_transposed_quantize = _TransposedQuantization.apply
|
||||
|
||||
|
||||
class _QuantSparseMoe(QuantModule):
|
||||
class _QuantSparseSequentialMoe(QuantModule):
|
||||
"""Quantization wrapper for HuggingFace sparse MoE blocks.
|
||||
|
||||
This base class is for Sequential MoEs (i.e each experts are implemented as standalone modules).
|
||||
Transformers>=5.0 has batched experts, no per-expert quantizers.
|
||||
|
||||
Supports ``layer_sync_moe_local_experts_amax`` to sync input quantizer amax across experts.
|
||||
|
||||
Optionally supports config-driven features (disabled by default):
|
||||
@@ -523,7 +537,7 @@ class _QuantSparseMoe(QuantModule):
|
||||
super().forward(hidden_states)
|
||||
self.gate.top_k = original_top_k
|
||||
else:
|
||||
# Path for transformers < 5.0
|
||||
# Path for transformers<5.0
|
||||
if hasattr(self, "gate") and hasattr(self.gate, "top_k"):
|
||||
top_k_owner = self.gate
|
||||
else:
|
||||
@@ -565,10 +579,6 @@ class _QuantSparseMoe(QuantModule):
|
||||
"""
|
||||
if self._moe_calib_experts_ratio is not None:
|
||||
return
|
||||
try:
|
||||
iter(self.experts)
|
||||
except TypeError:
|
||||
return
|
||||
sync_moe_expert_amax(self.experts, sync_weight_amax=sync_weight_amax)
|
||||
|
||||
|
||||
@@ -600,22 +610,20 @@ class _QuantDbrxExperts(QuantModule):
|
||||
"""Modify the DbrxExpert."""
|
||||
# No setup is needed for DbrxExpert, we only need to update DbrxExpertGLU
|
||||
|
||||
# forward method copied from the original dbrx repo - https://github.com/databricks/dbrx/blob/a3200393/model/modeling_dbrx.py#L795
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
weights: torch.Tensor,
|
||||
top_weights: torch.Tensor,
|
||||
top_experts: torch.LongTensor,
|
||||
top_weights: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
bsz, q_len, hidden_size = x.shape
|
||||
x = x.view(-1, hidden_size)
|
||||
out = torch.zeros_like(x)
|
||||
|
||||
expert_mask = nn.functional.one_hot(top_experts, num_classes=self.moe_num_experts).permute(
|
||||
expert_mask = nn.functional.one_hot(top_experts, num_classes=self.num_experts).permute(
|
||||
2, 1, 0
|
||||
)
|
||||
for expert_idx in range(self.moe_num_experts):
|
||||
for expert_idx in range(self.num_experts):
|
||||
topk_idx, token_idx = torch.where(expert_mask[expert_idx])
|
||||
if token_idx.shape[0] == 0:
|
||||
continue
|
||||
@@ -645,42 +653,49 @@ class _QuantDbrxExpertGLU(QuantModule):
|
||||
with torch.no_grad():
|
||||
module.weight.copy_(weights[expert_idx].detach())
|
||||
|
||||
# In transformers 5.0, DbrxExpertGLU.forward uses raw matmul: x @ w1[i] where
|
||||
# w1[i] has shape (ffn_hidden_size, hidden_size). To match via F.linear (which
|
||||
# computes x @ W.T), we store weights transposed: W = w1[i].T.
|
||||
self.w1_linear = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(self.hidden_size, self.ffn_hidden_size, bias=False)
|
||||
for _ in range(self.moe_num_experts)
|
||||
]
|
||||
)
|
||||
_copy_weights(
|
||||
self.w1_linear,
|
||||
self.w1.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size),
|
||||
)
|
||||
delattr(self, "w1")
|
||||
|
||||
self.v1_linear = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(self.hidden_size, self.ffn_hidden_size, bias=False)
|
||||
for _ in range(self.moe_num_experts)
|
||||
]
|
||||
)
|
||||
_copy_weights(
|
||||
self.v1_linear,
|
||||
self.v1.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size),
|
||||
)
|
||||
delattr(self, "v1")
|
||||
|
||||
self.w2_linear = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(self.ffn_hidden_size, self.hidden_size, bias=False)
|
||||
for _ in range(self.moe_num_experts)
|
||||
]
|
||||
)
|
||||
_copy_weights(
|
||||
self.w2_linear,
|
||||
self.w2.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size).transpose(
|
||||
self.w1_linear,
|
||||
self.w1.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size).transpose(
|
||||
1, 2
|
||||
),
|
||||
)
|
||||
delattr(self, "w1")
|
||||
|
||||
self.v1_linear = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(self.ffn_hidden_size, self.hidden_size, bias=False)
|
||||
for _ in range(self.moe_num_experts)
|
||||
]
|
||||
)
|
||||
_copy_weights(
|
||||
self.v1_linear,
|
||||
self.v1.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size).transpose(
|
||||
1, 2
|
||||
),
|
||||
)
|
||||
delattr(self, "v1")
|
||||
|
||||
# w2: down_proj uses intermediate.matmul(w2[i].t()) = F.linear(intermediate, w2[i])
|
||||
# so W = w2[i] directly (no extra transpose needed).
|
||||
self.w2_linear = nn.ModuleList(
|
||||
[
|
||||
nn.Linear(self.hidden_size, self.ffn_hidden_size, bias=False)
|
||||
for _ in range(self.moe_num_experts)
|
||||
]
|
||||
)
|
||||
_copy_weights(
|
||||
self.w2_linear,
|
||||
self.w2.view(self.moe_num_experts, self.ffn_hidden_size, self.hidden_size),
|
||||
)
|
||||
delattr(self, "w2")
|
||||
|
||||
def forward(self, x: torch.Tensor, expert_idx: int) -> torch.Tensor:
|
||||
@@ -872,18 +887,25 @@ class _QuantQwen35MoeExperts(QuantModule):
|
||||
return final_hidden_states
|
||||
|
||||
|
||||
class _QuantDbrxFFN(_QuantSparseMoe):
|
||||
class _QuantDbrxFFN(_QuantSparseSequentialMoe):
|
||||
@property
|
||||
def num_experts(self):
|
||||
return self.router.moe_num_experts
|
||||
|
||||
@property
|
||||
def top_k(self):
|
||||
return self.router.moe_top_k
|
||||
# In older transformers, top_k was stored on DbrxRouter as moe_top_k.
|
||||
# In transformers 5.0, DbrxFFN stores it as a plain attribute (top_k).
|
||||
if hasattr(self.router, "moe_top_k"):
|
||||
return self.router.moe_top_k
|
||||
return self.__dict__.get("top_k", 1)
|
||||
|
||||
@top_k.setter
|
||||
def top_k(self, value):
|
||||
self.router.moe_top_k = value
|
||||
if hasattr(self.router, "moe_top_k"):
|
||||
self.router.moe_top_k = value
|
||||
else:
|
||||
self.__dict__["top_k"] = value
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -906,10 +928,7 @@ def patch_compressed_linear_loading():
|
||||
|
||||
with patch_compressed_linear_loading():
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
ckpt_path,
|
||||
device_map="auto",
|
||||
trust_remote_code=True,
|
||||
torch_dtype="auto",
|
||||
ckpt_path, device_map="auto", trust_remote_code=True, dtype="auto"
|
||||
)
|
||||
"""
|
||||
try:
|
||||
@@ -1309,8 +1328,8 @@ def _has_num_experts(obj):
|
||||
return hasattr(obj, "num_experts") or hasattr(obj, "n_routed_experts")
|
||||
|
||||
|
||||
def _is_sparse_moe_block(module):
|
||||
"""Check if a module is structurally a sparse MoE block compatible with _QuantSparseMoe.
|
||||
def _is_sparse_sequaential_moe_block(module):
|
||||
"""Check if a module is structurally a sparse sequential MoE block compatible with _QuantSparseSequentialMoe.
|
||||
|
||||
All HuggingFace MoE blocks (Mixtral, Qwen3Moe, Qwen2Moe, Qwen3Next, Llama4, MiniMax,
|
||||
NemotronH, etc.) share a common structural pattern: a ``gate`` (TopKRouter) sub-module with
|
||||
@@ -1323,6 +1342,10 @@ def _is_sparse_moe_block(module):
|
||||
if not hasattr(module, "experts"):
|
||||
return False
|
||||
|
||||
if not hasattr(module.experts, "__iter__"):
|
||||
# transformers>=5.0 has batched experts, no per-expert quantizers
|
||||
return False
|
||||
|
||||
# Primary: gate sub-module has topk/top_k + num_experts (standard TopKRouter pattern)
|
||||
if hasattr(module, "gate"):
|
||||
gate = module.gate
|
||||
@@ -1339,10 +1362,10 @@ def _is_sparse_moe_block(module):
|
||||
|
||||
|
||||
def register_sparse_moe_on_the_fly(model):
|
||||
"""Auto-detect and register MOE modules as _QuantSparseMoe.
|
||||
"""Auto-detect and register MOE modules as _QuantSparseSequentialMoe.
|
||||
|
||||
Walks the model tree, identifies MoE blocks by their structural attributes
|
||||
(``gate`` + ``experts``), and registers unregistered ones with ``_QuantSparseMoe``.
|
||||
(``gate`` + ``experts``), and registers unregistered ones with ``_QuantSparseSequentialMoe``.
|
||||
"""
|
||||
visited_types = set()
|
||||
for name, module in model.named_modules():
|
||||
@@ -1355,12 +1378,14 @@ def register_sparse_moe_on_the_fly(model):
|
||||
|
||||
visited_types.add(mod_type)
|
||||
|
||||
if _is_sparse_moe_block(module):
|
||||
if _is_sparse_sequaential_moe_block(module):
|
||||
print(
|
||||
f"\033[1mDetected MOE module '{name}' of type {mod_type.__name__}, "
|
||||
f"registering with _QuantSparseMoe.\033[0m"
|
||||
f"registering with _QuantSparseSequentialMoe.\033[0m"
|
||||
)
|
||||
QuantModuleRegistry.register({mod_type: f"hf.{mod_type.__name__}"})(
|
||||
_QuantSparseSequentialMoe
|
||||
)
|
||||
QuantModuleRegistry.register({mod_type: f"hf.{mod_type.__name__}"})(_QuantSparseMoe)
|
||||
|
||||
|
||||
def _is_supported_hf_model(model):
|
||||
|
||||
@@ -29,7 +29,7 @@ import modelopt.torch.opt as mto
|
||||
import modelopt.torch.quantization as mtq
|
||||
from modelopt.torch.distill.plugins.huggingface import KDTrainer
|
||||
from modelopt.torch.opt.plugins import ModelOptHFTrainer
|
||||
from modelopt.torch.utils import print_rank_0
|
||||
from modelopt.torch.utils import get_module_device, print_rank_0
|
||||
|
||||
from ..config import QuantizeConfig
|
||||
from ..nn import TensorQuantizer
|
||||
@@ -344,8 +344,10 @@ class QATTrainer(ModelOptHFTrainer):
|
||||
), "Some base_layer parameters are not frozen"
|
||||
|
||||
adapter_name = self.model.active_adapters()[0]
|
||||
device = get_module_device(self.model)
|
||||
self.model.delete_adapter(adapter_name)
|
||||
self.model.load_adapter(self.state.best_model_checkpoint, adapter_name)
|
||||
self.model.to(device)
|
||||
else:
|
||||
super()._load_best_model(*args, **kwargs)
|
||||
|
||||
|
||||
@@ -533,6 +533,7 @@ def sync_moe_expert_amax(experts, sync_weight_amax=False):
|
||||
received no tokens during calibration), runs a weight-only ``max_calibrate``
|
||||
to populate the missing amax.
|
||||
"""
|
||||
from ..model_calib import max_calibrate
|
||||
from ..nn import TensorQuantizer
|
||||
|
||||
amax_dict: dict[str, torch.Tensor] = {}
|
||||
@@ -552,8 +553,6 @@ def sync_moe_expert_amax(experts, sync_weight_amax=False):
|
||||
if isinstance(module, TensorQuantizer) and name in amax_dict:
|
||||
module.amax = amax_dict[name].detach().clone()
|
||||
|
||||
from ..model_calib import max_calibrate
|
||||
|
||||
for expert in experts:
|
||||
for name, module in expert.named_modules():
|
||||
if name.endswith("weight_quantizer") and module.is_enabled and module.amax is None:
|
||||
|
||||
@@ -139,7 +139,7 @@ def sparsify(
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
model_path,
|
||||
attn_implementation="eager", # Required for sparse attention
|
||||
torch_dtype=torch.bfloat16,
|
||||
dtype=torch.bfloat16,
|
||||
)
|
||||
|
||||
This is because sparse attention works by patching torch.nn.functional.softmax,
|
||||
|
||||
@@ -25,6 +25,7 @@ default_eagle_config = {
|
||||
"high_freq_factor": 4.0,
|
||||
"original_max_position_embeddings": 8192,
|
||||
"rope_type": "llama3",
|
||||
"rope_theta": 500000.0,
|
||||
},
|
||||
"rope_theta": 500000.0,
|
||||
"num_hidden_layers": 1,
|
||||
|
||||
@@ -38,7 +38,7 @@ import torch
|
||||
from torch import nn
|
||||
from torch.nn import CrossEntropyLoss
|
||||
from torch.nn.attention.flex_attention import BlockMask, create_block_mask
|
||||
from transformers import Cache, DynamicCache, PretrainedConfig, PreTrainedModel
|
||||
from transformers import Cache, DynamicCache, PreTrainedModel
|
||||
from transformers.models.llama.modeling_llama import (
|
||||
LlamaDecoderLayer,
|
||||
LlamaRMSNorm,
|
||||
@@ -601,7 +601,10 @@ class HFEagleModel(EagleModel):
|
||||
if rope_scaling and "rope_theta" not in rope_scaling and "rope_theta" in arch_config:
|
||||
rope_scaling["rope_theta"] = arch_config["rope_theta"]
|
||||
|
||||
self.eagle_config = PretrainedConfig.from_dict(arch_config)
|
||||
# Use the base model's config class so fields like max_position_embeddings are declared
|
||||
# before transformers>=5.5 rope standardization runs in __post_init__.
|
||||
base_config_cls = type(self._base_llm_config)
|
||||
self.eagle_config = base_config_cls.from_dict(arch_config)
|
||||
self.eagle_config.eagle_decoder_type = self.eagle_decoder_type
|
||||
self.eagle_config.draft_vocab_size = getattr(
|
||||
self.eagle_config, "draft_vocab_size", self.eagle_config.vocab_size
|
||||
|
||||
@@ -488,7 +488,7 @@ def load_vlm_or_llm(
|
||||
model_name_or_path: str,
|
||||
use_fake_base: bool = False,
|
||||
use_offline_training: bool = False,
|
||||
torch_dtype: str | torch.dtype | None = None,
|
||||
dtype: str | torch.dtype | None = None,
|
||||
device_map: str | None = None,
|
||||
trust_remote_code: bool = False,
|
||||
):
|
||||
@@ -502,7 +502,7 @@ def load_vlm_or_llm(
|
||||
Args:
|
||||
model_name_or_path: Local path or HuggingFace repo ID of the model.
|
||||
use_offline_training: Whether to load a memory-efficient model for offline training.
|
||||
torch_dtype: dtype to use when loading the model.
|
||||
dtype: dtype to use when loading the model.
|
||||
device_map: Device map passed to ``from_pretrained``.
|
||||
trust_remote_code: Whether to trust remote code.
|
||||
"""
|
||||
@@ -528,7 +528,7 @@ def load_vlm_or_llm(
|
||||
model = model_cls.from_pretrained(
|
||||
model_name_or_path,
|
||||
trust_remote_code=trust_remote_code,
|
||||
torch_dtype=torch_dtype,
|
||||
dtype=dtype,
|
||||
device_map=device_map,
|
||||
**extra,
|
||||
)
|
||||
|
||||
@@ -15,8 +15,11 @@
|
||||
|
||||
"""Utilities to describe symbols in the dynamic attention module."""
|
||||
|
||||
import torch
|
||||
import transformers
|
||||
from packaging.version import Version
|
||||
from torch import nn
|
||||
from transformers.models.bert.modeling_bert import BertAttention
|
||||
from transformers.models.bert.modeling_bert import BertAttention, BertLayer
|
||||
from transformers.models.gptj.modeling_gptj import GPTJAttention
|
||||
|
||||
from ..symbols import Symbol, SymInfo, SymMap
|
||||
@@ -56,3 +59,57 @@ def get_hf_attn_sym_info_sortable(mod: nn.Module) -> SymInfo:
|
||||
@SymMap.register([GPTJAttention])
|
||||
def get_hf_attn_sym_info_unsortable(mod: nn.Module) -> SymInfo:
|
||||
return get_hf_attn_sym_info(sortable_attn=True)
|
||||
|
||||
|
||||
# In transformers>=5.0, BertLayer.forward uses tuple unpacking on the BertAttention output
|
||||
# (e.g. `self_attn_out, _ = self.attention(...)`), which FX symbolic tracing cannot handle when
|
||||
# BertAttention is a registered leaf (the proxy is not iterable). Patch BertLayer.forward to use
|
||||
# indexing instead, and call feed_forward_chunk directly (equivalent to apply_chunking_to_forward
|
||||
# with chunk_size=0, which is the default for BERT).
|
||||
if Version(transformers.__version__) >= Version("5.0"):
|
||||
|
||||
def _fx_friendly_bert_layer_forward(
|
||||
self,
|
||||
hidden_states: torch.Tensor,
|
||||
attention_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_values=None,
|
||||
cache_position=None,
|
||||
**kwargs,
|
||||
):
|
||||
# Use indexing instead of tuple-unpacking so FX can trace through BertLayer
|
||||
# when BertAttention is a registered leaf (returns an opaque Proxy).
|
||||
# Accept **kwargs so that a parent trace (e.g. BertEncoder) passing extra kwargs
|
||||
# like position_ids does not mark BertLayer as failed. However, do NOT forward
|
||||
# **kwargs into self.attention: FX represents **kwargs as a Proxy(_kwargs), so
|
||||
# unpacking it with ** would trigger "Proxy cannot be iterated". Additionally,
|
||||
# BertSelfAttention ignores these kwargs (e.g. position_ids) in practice.
|
||||
_attn_outputs = self.attention(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
past_key_values=past_key_values,
|
||||
cache_position=cache_position,
|
||||
)
|
||||
attention_output = _attn_outputs[0]
|
||||
|
||||
if self.is_decoder and encoder_hidden_states is not None:
|
||||
if not hasattr(self, "crossattention"):
|
||||
raise ValueError(
|
||||
f"If `encoder_hidden_states` are passed, {self} has to be instantiated with"
|
||||
" cross-attention layers by setting `config.add_cross_attention=True`"
|
||||
)
|
||||
_cross_outputs = self.crossattention(
|
||||
attention_output,
|
||||
None,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
past_key_values=past_key_values,
|
||||
)
|
||||
attention_output = _cross_outputs[0]
|
||||
|
||||
# Call feed_forward_chunk directly (equivalent to apply_chunking_to_forward when
|
||||
# chunk_size_feed_forward=0, which is the BERT default).
|
||||
return self.feed_forward_chunk(attention_output)
|
||||
|
||||
BertLayer.forward = _fx_friendly_bert_layer_forward
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ dependencies = [
|
||||
"nvidia-ml-py>=12",
|
||||
"packaging",
|
||||
"setuptools>=80", # torch.utils.cpp_extension imports setuptools at load time
|
||||
"torch>=2.6",
|
||||
"torch>=2.8",
|
||||
"tqdm",
|
||||
# modelopt.torch
|
||||
"PyYAML>=6.0",
|
||||
@@ -81,8 +81,8 @@ hf = [
|
||||
"nltk",
|
||||
"peft>=0.17.0",
|
||||
"sentencepiece>=0.2.1", # Also implicitly used in test_unified_export_megatron, test_vllm_fakequant_megatron_export
|
||||
"transformers>=4.56,<5.0", # Should match modelopt/torch/__init__.py and tox.ini
|
||||
"tiktoken",
|
||||
"transformers>=4.56", # Should match modelopt/torch/__init__.py and tox.ini
|
||||
"wonderwords",
|
||||
]
|
||||
dev-lint = [
|
||||
|
||||
@@ -64,8 +64,8 @@ LLAVA_PATH = _select_path(
|
||||
)
|
||||
|
||||
QWEN_VL_PATH = _select_path(
|
||||
remote_id="Qwen/Qwen2-VL-2B-Instruct",
|
||||
local_id="Qwen2-VL-2B-Instruct",
|
||||
remote_id="Qwen/Qwen3-VL-2B-Instruct",
|
||||
local_id="Qwen3-VL-2B-Instruct",
|
||||
)
|
||||
|
||||
# Diffusers
|
||||
|
||||
@@ -144,10 +144,9 @@ class TensorQuantizerTester:
|
||||
rtol=0,
|
||||
)
|
||||
|
||||
@pytest.mark.manual(reason="slow test, run with --run-manual")
|
||||
def test_entropy_and_percentile_calib(self):
|
||||
"""Don't really have a good way to test it."""
|
||||
quant_attr_cfg1 = QuantizerAttributeConfig(calib_method="histogram")
|
||||
quant_attr_cfg1 = QuantizerAttributeConfig(calibrator="histogram")
|
||||
quantizer1 = TensorQuantizer(quant_attr_cfg1, if_calib=True, if_quant=False).to(self.device)
|
||||
|
||||
x_1 = torch.rand(3, 6, 7, 7).to(self.device)
|
||||
|
||||
@@ -132,10 +132,10 @@ MODELS = {
|
||||
],
|
||||
_create_torchvision_segmentation_fn,
|
||||
),
|
||||
"unet": (
|
||||
["unet_carvana"],
|
||||
_create_unet_fn,
|
||||
),
|
||||
# "unet": (
|
||||
# ["unet_carvana"],
|
||||
# _create_unet_fn,
|
||||
# ),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import pytest
|
||||
from _test_utils.examples.llm_ptq_utils import PTQCommand, WithRequirements
|
||||
import transformers
|
||||
from _test_utils.examples.llm_ptq_utils import PTQCommand
|
||||
from _test_utils.examples.models import (
|
||||
BART_PATH,
|
||||
MIXTRAL_PATH,
|
||||
@@ -23,6 +22,7 @@ from _test_utils.examples.models import (
|
||||
TINY_LLAMA_PATH,
|
||||
WHISPER_PATH,
|
||||
)
|
||||
from packaging.version import Version
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -36,18 +36,9 @@ def test_ptq_bart(command):
|
||||
command.run(BART_PATH)
|
||||
|
||||
|
||||
class TestT5(WithRequirements):
|
||||
requirements = [("transformers", "4.48.0")]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"command",
|
||||
[
|
||||
PTQCommand(quant="fp8", min_sm=89),
|
||||
],
|
||||
ids=PTQCommand.param_str,
|
||||
)
|
||||
def test_ptq_t5(self, command):
|
||||
command.run(T5_PATH)
|
||||
@pytest.mark.parametrize("command", [PTQCommand(quant="fp8", min_sm=89)], ids=PTQCommand.param_str)
|
||||
def test_ptq_t5(command):
|
||||
command.run(T5_PATH)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -61,22 +52,20 @@ def test_ptq_mixtral(command):
|
||||
command.run(MIXTRAL_PATH)
|
||||
|
||||
|
||||
class TestWhisper(WithRequirements):
|
||||
requirements = [
|
||||
("librosa", None),
|
||||
("soundfile", None),
|
||||
]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"command",
|
||||
[
|
||||
# Auto-batch-size computation seems to take >10mins for Whisper hence using a fixed batch size
|
||||
PTQCommand(quant="fp8", calib_batch_size=16, calib_dataset="peoples_speech", min_sm=89),
|
||||
],
|
||||
ids=PTQCommand.param_str,
|
||||
)
|
||||
def test_ptq_whisper(self, command):
|
||||
command.run(WHISPER_PATH)
|
||||
@pytest.mark.skipif(
|
||||
Version(transformers.__version__) >= Version("5.0"),
|
||||
reason="Whisper requires torchcodec and other system packages for transformers>=5.0",
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"command",
|
||||
[
|
||||
# Auto-batch-size computation seems to take >10mins for Whisper hence using a fixed batch size
|
||||
PTQCommand(quant="fp8", calib_batch_size=16, calib_dataset="peoples_speech", min_sm=89),
|
||||
],
|
||||
ids=PTQCommand.param_str,
|
||||
)
|
||||
def test_ptq_whisper(command):
|
||||
command.run(WHISPER_PATH)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import pytest
|
||||
import torch
|
||||
from _test_utils.examples.run_command import run_example_command
|
||||
from _test_utils.torch.misc import minimum_sm
|
||||
|
||||
|
||||
# fmt: off
|
||||
@@ -98,7 +99,7 @@ def test_llama_lora_qat_nvfp4(tiny_llama_path, tmp_path):
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@minimum_sm(90)
|
||||
def test_llama_qlora_nvfp4(tiny_llama_path, tmp_path):
|
||||
_run_command(
|
||||
[
|
||||
|
||||
@@ -36,7 +36,6 @@ MIN_OPSET = {
|
||||
|
||||
# onnxruntime version that supports opset 22+
|
||||
ORT_VERSION_FOR_OPSET_22 = version.parse("1.23.0")
|
||||
TORCH_VERSION_FOR_OPSET_22 = version.parse("2.8.0")
|
||||
|
||||
|
||||
# Test scenarios: (scenario_name, export_opset_offset, request_opset_offset, expected_opset_offset)
|
||||
@@ -87,11 +86,6 @@ def test_quantize_opset_handling(
|
||||
pytest.skip(
|
||||
f"Opset {max_opset} requires onnxruntime >= {ORT_VERSION_FOR_OPSET_22}, have {ort_version}"
|
||||
)
|
||||
torch_version = version.parse(torch.__version__)
|
||||
if torch_version < TORCH_VERSION_FOR_OPSET_22:
|
||||
pytest.skip(
|
||||
f"Opset {max_opset} requires torch >= {TORCH_VERSION_FOR_OPSET_22}, have {torch_version}"
|
||||
)
|
||||
|
||||
# Setup: create and export model
|
||||
model_torch = SimpleMLP()
|
||||
|
||||
@@ -17,6 +17,7 @@ import traceback
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
from _test_utils.torch.opt.utils import apply_mode_with_sampling
|
||||
from _test_utils.torch.transformers_models import (
|
||||
create_tiny_llama_dir,
|
||||
@@ -27,7 +28,7 @@ from transformers import AutoConfig, AutoModelForCausalLM, LlamaForCausalLM
|
||||
|
||||
@pytest.mark.parametrize("model_cls", [LlamaForCausalLM, AutoModelForCausalLM])
|
||||
def test_causal_lm_save_restore(tmp_path, model_cls):
|
||||
tiny_llama_dir = create_tiny_llama_dir(tmp_path, hidden_size=128)
|
||||
tiny_llama_dir = create_tiny_llama_dir(tmp_path, hidden_size=128, dtype=torch.float32)
|
||||
model_ref = model_cls.from_pretrained(tiny_llama_dir)
|
||||
# TODO: Add calibrate, compress mode to the test
|
||||
model_ref = apply_mode_with_sampling(
|
||||
@@ -41,7 +42,7 @@ def test_causal_lm_save_restore(tmp_path, model_cls):
|
||||
|
||||
def test_causal_lm_from_config(tmp_path):
|
||||
"""Test loading a model using from_config after applying optimizations"""
|
||||
tiny_llama_dir = create_tiny_llama_dir(tmp_path, hidden_size=128)
|
||||
tiny_llama_dir = create_tiny_llama_dir(tmp_path, hidden_size=128, dtype=torch.float32)
|
||||
|
||||
model_ref = AutoModelForCausalLM.from_pretrained(tiny_llama_dir)
|
||||
model_ref = apply_mode_with_sampling(
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import warnings
|
||||
from contextlib import nullcontext
|
||||
@@ -28,6 +27,7 @@ from _test_utils.torch.transformers_models import (
|
||||
get_tiny_qwen3_moe,
|
||||
tf_modelopt_state_and_output_tester,
|
||||
)
|
||||
from packaging.version import Version
|
||||
|
||||
import modelopt.torch.quantization as mtq
|
||||
from modelopt.torch.quantization.nn import QuantLinear, QuantModuleRegistry
|
||||
@@ -105,12 +105,17 @@ def test_convert_conv1d():
|
||||
assert torch.allclose(out_1, out_2)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
Version(transformers.__version__) < Version("5.0"),
|
||||
reason="test_dbrx is not supported for transformers<5.0",
|
||||
)
|
||||
def test_dbrx():
|
||||
assert DbrxExperts in QuantModuleRegistry
|
||||
assert DbrxExpertGLU in QuantModuleRegistry
|
||||
|
||||
config = DbrxConfig(
|
||||
ffn_config=DbrxFFNConfig(ffn_hidden_size=8, moe_num_experts=2), hidden_size=32
|
||||
ffn_config=DbrxFFNConfig(ffn_hidden_size=8, moe_num_experts=2, hidden_size=32),
|
||||
hidden_size=32,
|
||||
)
|
||||
|
||||
model_ref = DbrxFFN(config)
|
||||
@@ -131,14 +136,17 @@ def test_dbrx():
|
||||
assert hasattr(expertglu_test, "v1_linear") and not hasattr(expertglu_test, "v1")
|
||||
assert hasattr(expertglu_test, "w2_linear") and not hasattr(expertglu_test, "w2")
|
||||
|
||||
# Weights are stored transposed (W = w1[i].T) to match F.linear semantics with
|
||||
# transformers 5.0's raw matmul: x @ w1[i] = F.linear(x, w1[i].T)
|
||||
assert torch.allclose(
|
||||
torch.concat(list(expertglu_test.w1_linear.parameters()), dim=0),
|
||||
torch.concat([m.weight.T for m in expertglu_test.w1_linear], dim=0),
|
||||
expertglu_ref.w1,
|
||||
)
|
||||
|
||||
mtq.set_quantizer_attributes_partial(model_test, "*", {"enable": False})
|
||||
|
||||
x = torch.randn(1, 4, 32)
|
||||
# In transformers 5.0, the FFN input dimension is ffn_hidden_size (not hidden_size)
|
||||
x = torch.randn(1, 4, 8)
|
||||
out_1 = model_ref(x)
|
||||
out_2 = model_test(x)
|
||||
assert torch.allclose(out_1[0], out_2[0])
|
||||
@@ -147,6 +155,9 @@ def test_dbrx():
|
||||
@pytest.mark.parametrize("method", ["gradient", "kl_div"])
|
||||
@pytest.mark.parametrize("model_provider", [get_tiny_llama, get_tiny_qwen3_moe])
|
||||
def test_autoquantize_huggingface(model_provider, method):
|
||||
if model_provider == get_tiny_qwen3_moe and Version(torch.__version__) < Version("2.9"):
|
||||
pytest.skip("torch 2.8 grouped_mm is CUDA-only")
|
||||
|
||||
model = model_provider()
|
||||
input_ids = model.dummy_inputs["input_ids"]
|
||||
|
||||
@@ -190,7 +201,7 @@ def test_autoquantize_huggingface(model_provider, method):
|
||||
],
|
||||
)
|
||||
def test_quantized_transformers_save_restore(tmp_path, model_cls, quant_config):
|
||||
tiny_llama_dir = create_tiny_llama_dir(tmp_path)
|
||||
tiny_llama_dir = create_tiny_llama_dir(tmp_path, dtype=torch.float32)
|
||||
# update config to fit test cases
|
||||
if quant_config == mtq.INT4_AWQ_CFG:
|
||||
import copy
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import pytest
|
||||
import torch
|
||||
from _test_utils.torch.transformers_models import get_tiny_gpt_oss, get_tiny_llama, tf_output_tester
|
||||
from packaging.version import Version
|
||||
|
||||
pytest.importorskip("peft")
|
||||
transformers = pytest.importorskip("transformers")
|
||||
@@ -53,6 +54,9 @@ def test_convert_loralinear():
|
||||
tf_output_tester(model_ref, model_test)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
Version(torch.__version__) < Version("2.9"), reason="torch 2.8 grouped_mm is CUDA-only"
|
||||
)
|
||||
def test_peft_flow(tmp_path):
|
||||
model_original = get_tiny_gpt_oss(num_hidden_layers=1)
|
||||
|
||||
|
||||
+26
-20
@@ -13,29 +13,33 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Tests for _is_sparse_moe_block and _QuantSparseMoe."""
|
||||
"""Tests for _is_sparse_sequaential_moe_block and _QuantSparseSequentialMoe."""
|
||||
|
||||
import copy
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from packaging.version import Version
|
||||
|
||||
pytest.importorskip("transformers")
|
||||
|
||||
if Version(torch.__version__) < Version("2.9"):
|
||||
pytest.skip("torch 2.8 grouped_mm is CUDA-only", allow_module_level=True)
|
||||
|
||||
from _test_utils.torch.transformers_models import get_tiny_qwen3_moe
|
||||
|
||||
import modelopt.torch.quantization as mtq
|
||||
from modelopt.torch.quantization.nn import QuantModuleRegistry
|
||||
from modelopt.torch.quantization.plugins.huggingface import (
|
||||
TRANSFORMERS_VERSION_GE_5_0,
|
||||
_is_sparse_moe_block,
|
||||
_is_sparse_sequaential_moe_block,
|
||||
register_sparse_moe_on_the_fly,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers: lightweight mock modules for _is_sparse_moe_block
|
||||
# Helpers: lightweight mock modules for _is_sparse_sequaential_moe_block
|
||||
# ---------------------------------------------------------------------------
|
||||
class _FakeGateWithRouter(nn.Module):
|
||||
"""Mimics a v5.x TopKRouter gate with top_k and num_experts."""
|
||||
@@ -97,25 +101,25 @@ class _MoEBlockFallback(nn.Module):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests for _is_sparse_moe_block
|
||||
# Tests for _is_sparse_sequaential_moe_block
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestIsSparseBlock:
|
||||
def test_no_experts_returns_false(self):
|
||||
module = nn.Linear(8, 8)
|
||||
assert _is_sparse_moe_block(module) is False
|
||||
assert _is_sparse_sequaential_moe_block(module) is False
|
||||
|
||||
def test_experts_but_no_gate_or_topk_returns_false(self):
|
||||
module = nn.Module()
|
||||
module.experts = nn.ModuleList([nn.Linear(8, 8)])
|
||||
assert _is_sparse_moe_block(module) is False
|
||||
assert _is_sparse_sequaential_moe_block(module) is False
|
||||
|
||||
def test_gate_with_router_attrs_returns_true(self):
|
||||
block = _MoEBlockWithGateRouter(num_experts=4, top_k=2)
|
||||
assert _is_sparse_moe_block(block) is True
|
||||
assert _is_sparse_sequaential_moe_block(block) is True
|
||||
|
||||
def test_fallback_block_level_attrs_returns_true(self):
|
||||
block = _MoEBlockFallback(num_experts=4, top_k=2)
|
||||
assert _is_sparse_moe_block(block) is True
|
||||
assert _is_sparse_sequaential_moe_block(block) is True
|
||||
|
||||
def test_gate_missing_num_experts_returns_false(self):
|
||||
"""gate.top_k present but gate.num_experts absent -> primary path fails."""
|
||||
@@ -124,7 +128,7 @@ class TestIsSparseBlock:
|
||||
gate = nn.Module()
|
||||
gate.top_k = 2
|
||||
module.gate = gate
|
||||
assert _is_sparse_moe_block(module) is False
|
||||
assert _is_sparse_sequaential_moe_block(module) is False
|
||||
|
||||
def test_gate_missing_top_k_returns_false(self):
|
||||
"""gate.num_experts present but gate.top_k absent -> primary path fails."""
|
||||
@@ -133,14 +137,14 @@ class TestIsSparseBlock:
|
||||
gate = nn.Module()
|
||||
gate.num_experts = 4
|
||||
module.gate = gate
|
||||
assert _is_sparse_moe_block(module) is False
|
||||
assert _is_sparse_sequaential_moe_block(module) is False
|
||||
|
||||
def test_block_level_top_k_infers_num_experts(self):
|
||||
"""top_k on block + experts with __len__ -> num_experts is inferred, returns True."""
|
||||
module = nn.Module()
|
||||
module.experts = nn.ModuleList([nn.Linear(8, 8)])
|
||||
module.top_k = 2
|
||||
assert _is_sparse_moe_block(module) is True
|
||||
assert _is_sparse_sequaential_moe_block(module) is True
|
||||
assert module.num_experts == 1
|
||||
|
||||
def test_block_level_top_k_no_len_returns_false(self):
|
||||
@@ -148,14 +152,14 @@ class TestIsSparseBlock:
|
||||
module = nn.Module()
|
||||
module.experts = nn.Module()
|
||||
module.top_k = 2
|
||||
assert _is_sparse_moe_block(module) is False
|
||||
assert _is_sparse_sequaential_moe_block(module) is False
|
||||
|
||||
def test_block_level_only_num_experts_returns_false(self):
|
||||
"""Only num_experts on block (no top_k) -> fallback fails."""
|
||||
module = nn.Module()
|
||||
module.experts = nn.ModuleList([nn.Linear(8, 8)])
|
||||
module.num_experts = 4
|
||||
assert _is_sparse_moe_block(module) is False
|
||||
assert _is_sparse_sequaential_moe_block(module) is False
|
||||
|
||||
def test_n_routed_experts_accepted(self):
|
||||
"""A module with n_routed_experts (NemotronH-style) should be accepted."""
|
||||
@@ -165,20 +169,21 @@ class TestIsSparseBlock:
|
||||
gate.top_k = 2
|
||||
gate.n_routed_experts = 4
|
||||
module.gate = gate
|
||||
assert _is_sparse_moe_block(module) is True
|
||||
assert _is_sparse_sequaential_moe_block(module) is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests for _QuantSparseMoe
|
||||
# Tests for _QuantSparseSequentialMoe
|
||||
# ---------------------------------------------------------------------------
|
||||
class TestQuantSparseMoe:
|
||||
"""Tests for _QuantSparseMoe using a real tiny Qwen3Moe model."""
|
||||
@pytest.mark.skipif(TRANSFORMERS_VERSION_GE_5_0, reason="Transformers v5 has stacked MoE")
|
||||
class TestQuantSparseSequentialMoe:
|
||||
"""Tests for _QuantSparseSequentialMoe using a real tiny Qwen3Moe model."""
|
||||
|
||||
@staticmethod
|
||||
def _get_moe_block(model):
|
||||
"""Return the first MoE block from the model."""
|
||||
for module in model.modules():
|
||||
if _is_sparse_moe_block(module):
|
||||
if _is_sparse_sequaential_moe_block(module):
|
||||
return module
|
||||
raise RuntimeError("No MoE block found in model")
|
||||
|
||||
@@ -298,12 +303,13 @@ class TestQuantSparseMoe:
|
||||
assert converted.expert_token_count.sum().item() == 8 * top_k
|
||||
|
||||
|
||||
def test_qwen3_moe_quantize_with_token_forcing_and_counting():
|
||||
@pytest.mark.skipif(TRANSFORMERS_VERSION_GE_5_0, reason="Transformers v5 has stacked MoE")
|
||||
def test_qwen3_sequential_moe_quantize_with_token_forcing_and_counting():
|
||||
"""End-to-end: mtq.quantize a Qwen3MoE with INT8 + moe_calib_experts_ratio + token counting."""
|
||||
model = get_tiny_qwen3_moe()
|
||||
|
||||
# Verify detection
|
||||
moe_found = any(_is_sparse_moe_block(m) for m in model.modules())
|
||||
moe_found = any(_is_sparse_sequaential_moe_block(m) for m in model.modules())
|
||||
assert moe_found, "Qwen3MoE should be detected as a sparse MoE block"
|
||||
|
||||
quant_cfg = copy.deepcopy(mtq.INT8_DEFAULT_CFG)
|
||||
@@ -88,8 +88,8 @@ class TestMaxCalibrator:
|
||||
max_calibrator.collect(x_3)
|
||||
|
||||
|
||||
@pytest.mark.manual(reason="slow test, run with --run-manual")
|
||||
class TestHistogramCalibrator:
|
||||
@pytest.mark.skip(reason="TODO: Fix assertions in test_grow")
|
||||
def test_grow(self, verbose):
|
||||
x_1 = torch.tensor([0, 255, 255, 255, 255, 255])
|
||||
x_2 = torch.tensor([0, 255, 255, 255, 255, 256])
|
||||
@@ -181,7 +181,6 @@ class TestHistogramCalibrator:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.manual(reason="slow test, run with --run-manual")
|
||||
class TestEntropyCalibrator:
|
||||
def test_one_tensor(self, verbose):
|
||||
hist_calibrator = calib.HistogramCalibrator(
|
||||
@@ -244,7 +243,6 @@ class TestEntropyCalibrator:
|
||||
repr(hist_calibrator)
|
||||
|
||||
|
||||
@pytest.mark.manual(reason="slow test, run with --run-manual")
|
||||
class TestMSECalibrator:
|
||||
def test_one_tensor(self, verbose):
|
||||
calibrator = calib.HistogramCalibrator(8, None, False, num_bins=32)
|
||||
@@ -299,7 +297,6 @@ class TestMSECalibrator:
|
||||
repr(calibrator)
|
||||
|
||||
|
||||
@pytest.mark.manual(reason="slow test, run with --run-manual")
|
||||
class TestPercentileCalibrator:
|
||||
def test_one_tensor(self, verbose):
|
||||
calibrator = calib.HistogramCalibrator(8, None, False)
|
||||
@@ -359,7 +356,6 @@ class TestPercentileCalibrator:
|
||||
calibrator.compute_amax("percentile", percentile=200)
|
||||
|
||||
|
||||
@pytest.mark.manual(reason="slow test, run with --run-manual")
|
||||
class TestCalibrateWeights:
|
||||
def test_max(self):
|
||||
ref_lenet = QuantConvLinear()
|
||||
|
||||
@@ -12,14 +12,13 @@ passenv =
|
||||
############################
|
||||
# CPU Unit test environments
|
||||
############################
|
||||
[testenv:{py310,py311,py312,py313}-torch{26,27,28,29,210}-tf_{min,latest}-unit]
|
||||
[testenv:{py310,py311,py312,py313}-torch{28,29,210,211}-tf_{min,latest}-unit]
|
||||
deps =
|
||||
# torch version auto-selected based on torchvision version
|
||||
torch26: torchvision~=0.21.0
|
||||
torch27: torchvision~=0.22.0
|
||||
torch28: torchvision~=0.23.0
|
||||
torch29: torchvision~=0.24.0
|
||||
torch210: torchvision~=0.25.0
|
||||
torch211: torchvision~=0.26.0
|
||||
|
||||
-e .[all,dev-test]
|
||||
|
||||
@@ -37,7 +36,7 @@ allowlist_externals =
|
||||
bash, rm
|
||||
deps =
|
||||
# Make sure torch 2.10 is used
|
||||
torchvision~=0.25.0
|
||||
torchvision~=0.26.0
|
||||
|
||||
# ONNX unit tests heavily rely on torch / torchvision
|
||||
onnx: .[onnx,dev-test]
|
||||
|
||||
Reference in New Issue
Block a user