mirror of
https://github.com/NVIDIA/Model-Optimizer.git
synced 2026-11-04 08:27:53 +08:00
## What does this PR do? **Type of change:** refactor / deprecation (examples) Follow-up to #1705 (which consolidated `examples/vlm_ptq` into `examples/llm_ptq`). Since that example now covers Hugging Face **LLM and VLM** PTQ, the `llm_ptq` name is a misnomer. This renames the directory to `examples/hf_ptq` and leaves a relative symlink `examples/llm_ptq → hf_ptq` so existing paths/commands keep working during a deprecation window. Requested by @kevalmorabia97 on #1705 (with the symlink-for-back-compat approach), targeted for the **same 0.46 release** as the consolidation. ### Changes - `git mv examples/llm_ptq → examples/hf_ptq` and `tests/examples/llm_ptq → tests/examples/hf_ptq` (the CI runner maps the matrix name to both `examples/<name>` and `tests/examples/<name>`). - Add a tracked back-compat symlink `examples/llm_ptq → hf_ptq`. - Update CI matrices and all repo **path references** (docs, READMEs, agent skills, launcher/debugger tools, tests) from `llm_ptq` to `hf_ptq`. - Keep Python identifiers / test-util module names (`run_llm_ptq_command`, `llm_ptq_utils`) — they name the LLM-PTQ task, not the directory. - Preserve the CODEOWNERS team slug (`modelopt-examples-llm_ptq-codeowners`) and historical CHANGELOG entries; add a CHANGELOG deprecation note. ### Back-compat caveats (inherent to git directory symlinks) - ✅ Linux/macOS CLI usage and Python `cwd`/pytest resolution work through the symlink. - ⚠️ Windows git checkouts don't materialize symlinks by default (low impact — this example is Linux-only in practice). - ⚠️ GitHub web doesn't follow directory symlinks, so legacy external deep-links to `examples/llm_ptq/...` won't navigate in. All **internal** references are repointed to `hf_ptq`, so the symlink is only for legacy external/CLI use. ### Usage (unchanged via symlink) ```bash # New canonical path cd examples/hf_ptq scripts/huggingface_example.sh --model <hf_model> --quant fp8 # Old path still works (forwards via symlink) cd examples/llm_ptq && scripts/huggingface_example.sh --model <hf_model> --quant fp8 ``` ### Testing - `bash -n` on moved/edited shell scripts (new path + via symlink). - `py_compile` on moved/edited Python; test re-export shim repointed to `examples/hf_ptq/example_utils`. - Verified git tracks `examples/llm_ptq` as a single symlink (mode 120000), not a duplicated tree (no pre-commit / pytest double-processing). - `pre-commit run` on all changed files passes. ### Before your PR is "*Ready for review*" - Is this change backward compatible?: ✅ (relative symlink keeps `examples/llm_ptq` paths valid; see caveats above) - Did you write any new necessary tests?: N/A (pure rename; existing tests moved with the dir) - Did you update Changelog?: ✅ ### Additional Information Follow-up (later release): remove the `examples/llm_ptq` symlink once external references have migrated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * PTQ guidance now directs to the unified Hugging Face PTQ flow, including VLM quantization via the shared `--vlm` entry point. * **Documentation** * Updated README and guide links, references, and command snippets to use `hf_ptq` (replacing `llm_ptq`). * Deprecated and consolidated `vlm_ptq` into `hf_ptq`; removed VILA/NVILA coverage from the Hugging Face PTQ examples. * **Bug Fixes** * Improved detection and routing so local/manual setup uses the correct PTQ source. * **Tests / Chores** * CI and example tests updated to run the `hf_ptq` variants. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
147 lines
7.2 KiB
ReStructuredText
147 lines
7.2 KiB
ReStructuredText
===================================================================================
|
|
Guides to quantize a customized model from Hugging Face for TensorRT-LLM deployment
|
|
===================================================================================
|
|
|
|
ModelOpt can usually quantize PyTorch models from the Hugging Face directly. By default, ModelOpt searches the PyTorch model and replaces the torch ``nn.Linear`` module with a quantized linear module.
|
|
|
|
If the model happens not using the ``nn.Linear`` for the linear layers, a customized Hugging Face plugin needs to be implemented to convert the model to use ``nn.Linear`` instead.
|
|
|
|
The following is an example about how a customized Hugging Face model can be supported using modelopt:
|
|
|
|
The `DBRX model <https://huggingface.co/databricks/dbrx-instruct>`_ is an MoE model with customized MoE linear implementation. The MoE layer in DBRX is implemented as a `DbrxExperts <https://github.com/databricks/dbrx/blob/main/model/modeling_dbrx.py>`_ module, where the three linear ops (w1, v1 and v2) are represented as ``nn.Parameter``. The linear op is forwarded as a pure ``matmul`` op.
|
|
|
|
As ModelOpt cannot detect these linear ops out-of-the-box, a HugggingFace plugin is implemented as the following:
|
|
|
|
#. Define a customized ``_QuantDbrxExpertGLU`` as a ``DynamicModule`` with the same ``forward`` signature.
|
|
#. Rewrite the linear ops (w1, v1 and v2) as a standard ``nn.Linear`` op, and re-implement the ``forward`` method.
|
|
#. Register the new dynamic ``_QuantDbrxExperts`` to replace the ``DbrxExperts`` from the modeling_dbrx.py in the ``transformers`` library
|
|
#. Try quantize the DBRX model after the plugin is implemented, feel free to follow the `hf_ptq example <https://github.com/NVIDIA/Model-Optimizer/tree/main/examples/hf_ptq>`_.
|
|
#. TensorRT-LLM is open-sourced. If this customized model is not supported by TensorRT-LLM yet, please modify :meth:`export_tensorrt_llm_checkpoint <modelopt.torch.export.export_tensorrt_llm_checkpoint>` or :meth:`export_hf_checkpoint <modelopt.torch.export.export_hf_checkpoint>` to export the quantized model for deployment with a customized TensorRT-LLM modeling implementation. Feel free to :doc:`contact us <../support/1_contact>` if further support is needed.
|
|
|
|
The following code snippet is excerpted from ``modelopt/torch/quantization/plugins/huggingface.py``
|
|
|
|
.. code-block:: python
|
|
|
|
from modelopt.torch.opt.dynamic import DynamicModule
|
|
from modelopt.torch.quantization.nn import QuantModuleRegistry
|
|
|
|
# For more information on DbrxExpert, see https://github.com/huggingface/transformers/blob/dcdda532/src/transformers/models/dbrx/modeling_dbrx.py#L756
|
|
class _QuantDbrxExperts(DynamicModule):
|
|
def _setup(self):
|
|
"""Modify the DbrxExpert."""
|
|
# No setup is needed for DbrxExpert, we only need to update DbrxExpertGLU
|
|
pass
|
|
|
|
# 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,
|
|
) -> 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(
|
|
2, 1, 0
|
|
)
|
|
for expert_idx in range(0, self.moe_num_experts):
|
|
topk_idx, token_idx = torch.where(expert_mask[expert_idx])
|
|
if token_idx.shape[0] == 0:
|
|
continue
|
|
|
|
token_list = token_idx.tolist()
|
|
topk_list = topk_idx.tolist()
|
|
|
|
expert_tokens = x[None, token_list].reshape(-1, hidden_size)
|
|
expert_out = (
|
|
self.mlp(expert_tokens, expert_idx) * top_weights[token_list, topk_list, None]
|
|
)
|
|
|
|
out.index_add_(0, token_idx, expert_out)
|
|
|
|
out = out.reshape(bsz, q_len, hidden_size)
|
|
return out
|
|
|
|
|
|
class _QuantDbrxExpertGLU(DynamicModule):
|
|
def _setup(self):
|
|
"""Modify the DbrxExpertGLU by using nn.Linear layers."""
|
|
dtype, device = self.w1.dtype, self.w1.device
|
|
|
|
def _copy_weights(modules, weights):
|
|
modules.to(dtype=dtype, device=device)
|
|
for expert_idx, module in enumerate(modules):
|
|
with torch.no_grad():
|
|
module.weight.copy_(weights[expert_idx].detach())
|
|
|
|
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(
|
|
1, 2
|
|
),
|
|
)
|
|
delattr(self, "w2")
|
|
|
|
def forward(self, x: torch.Tensor, expert_idx: int) -> torch.Tensor:
|
|
x1 = self.w1_linear[expert_idx](x)
|
|
x2 = self.v1_linear[expert_idx](x)
|
|
x1 = self.activation_fn(x1)
|
|
x1 = x1 * x2
|
|
return self.w2_linear[expert_idx](x1)
|
|
|
|
|
|
if transformers.models.dbrx.modeling_dbrx.DbrxExperts not in QuantModuleRegistry:
|
|
QuantModuleRegistry.register(
|
|
{transformers.models.dbrx.modeling_dbrx.DbrxExperts: "hf.DbrxExperts"}
|
|
)(_QuantDbrxExperts)
|
|
|
|
if transformers.models.dbrx.modeling_dbrx.DbrxExpertGLU not in QuantModuleRegistry:
|
|
QuantModuleRegistry.register(
|
|
{transformers.models.dbrx.modeling_dbrx.DbrxExpertGLU: "hf.DbrxExpertGLU"}
|
|
)(_QuantDbrxExpertGLU)
|
|
|
|
|
|
def register_dbrx_moe_on_the_fly(model):
|
|
"""Register DBRX MoE modules as QUANT_MODULE.
|
|
|
|
The MoE class in DBRX is `transformers_modules.modeling_dbrx.DbrxExpertGLU`, which loads dynamically.
|
|
"""
|
|
if type(model).__name__ in ["DbrxForCausalLM"]:
|
|
moe_type = type(model.transformer.blocks[0].ffn.experts.mlp)
|
|
# Create a QuantDbrxExpertGLU class on the fly
|
|
if QuantModuleRegistry.get(moe_type) is None:
|
|
QuantModuleRegistry.register({moe_type: moe_type.__name__})(_QuantDbrxExpertGLU)
|