Repository navigation
[OMNIML-6429] [Reference, not for merge] Move examples/hf_ptq's named-model checks into modelopt/torch/models - #3217
Draft
shengliangxu wants to merge 9 commits into
Draft
shengliangxu wants to merge 9 commits into
shengliangxu wants to merge 9 commits into
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
|
Contributor
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3217 +/- ##
==========================================
+ Coverage 81.31% 81.34% +0.02%
==========================================
Files 684 704 +20
Lines 79292 79482 +190
==========================================
+ Hits 64479 64655 +176
- Misses 14813 14827 +14
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
examples/hf_ptq decided how to load and preview a model by testing its name:
model_type == "t5"/"bart", model_type.startswith(("deepseek", "kimi_k2")),
model_type == "nemotron_h", model_type not in ("llama4", "llama4_text"),
"DeciLM" in architecture, "Deepseek" in architecture.
Those facts now live with the model in modelopt/torch/models, as a PTQSpec
section on ModelSpec read through get_ptq_spec(model_type):
- no_device_map: T5 and BART cannot take a device map
- dtype_kwarg: the DeciLM types take their precision as torch_dtype
- preview_unsupported: the DeepSeek-V3 family and Nemotron-H skip the
generation preview
- post_ptq_generation_unsupported: Llama-4's quantized generation is skipped
Each field is a reason string (or the keyword) naming why the model is special,
so the example no longer carries a branch on any of them. The "Deepseek"
architecture check becomes the existing prefers_bundled_code hook, served by a
new deepseek.bundled_code plugin.
The registry is keyed by model_type, where the example matched by prefix and
architecture substring, so the types are now listed: deepseek, deepseek_v2,
deepseek_v3, deepseek_v32, deepseek_v4, kimi_k2 and kimi_k25 (new: deepseek_v2,
deepseek_v32, kimi_k2, kimi_k25, bart, t5, deci, nemotron-nas). A deepseek_vl*
type, which startswith("deepseek") used to catch, is no longer skipped.
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
hf_ptq recognized a Nemotron VL model by looking for "nemotron" in its architectures (is_nemotron_vl), and kept a calibration forward written for those wrappers in examples/hf_ptq/nemotron_vl_calib.py. Both now live with the model types, in modelopt/torch/models. - nemotron_vl_calib.py moves to nemotron_h_nano_vl_v2/calibration.py. Its routing, which used to sit in create_vlm_calibration_loop, becomes a new forward_calibration_batch hook that declines an encoder-decoder model and a wrapper the InternVL branch cannot drive. - PTQSpec gains vlm_wrapper (image-text calibration by default, a chat-path preview, and putting the quantized language model back into the wrapper) and no_auto_device_map, which replaces the device_map=None for Nemotron VL. Unlike no_device_map (T5/BART), an explicit --use_seq_device_map still wins, as it did. - The four types are registered: NemotronH_Nano_VL_V2, Llama_Nemotron_Nano_VL, nemotron_parse and nemotron_h_omni. internvl_chat, the InternVL family the calibration helper's InternVL branch was written against, is routed to the same hook, so a wrapper with img_context_token_id keeps the route it had. - hf.py lets a model type have several plugin modules (nemotron_h_omni needs its MTP hooks and the calibration hook) and refuses two that define one hook. - The example reads the flag instead of testing the name: is_nemotron_vl is gone, run_nemotron_vl_preview is run_vlm_preview, is_nemotron_vl_model is vlm_wrapper. The helper's tests move with it to tests/unit/torch/models; the two that go through create_vlm_calibration_loop stay with the example. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
…package hf_ptq branched on model_type == "whisper" in five places: to load a processor, to build the speech calibration data, to size the batch probe with a mel spectrogram, to pick the preview's input key, and to decode the preview through the processor (isinstance(processor, WhisperProcessor)). The model-specific parts become three optional hooks in the dispatcher, defined by a new whisper/calibration.py plugin: load_calibration_processor, make_calibration_dataloader and calibration_sample_input. get_processor, whose only caller was the Whisper branch, goes with them. The two decisions that were not Whisper's alone are structural now: - the preview feeds the batch's input_features when it has them, else input_ids; - a flow with a processor and no tokenizer decodes through the processor's tokenizer, and the transcript the data provider returns is the preview input. first_text_speech_dataset, which only ever held that transcript, is renamed preview_input_text. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The last checks in examples/hf_ptq that named a model or a family: - DiffusionGemma: the sequential multi-GPU device map and generate() including its prompt. Both are PTQSpec fields now (sequential_device_map, generate_includes_prompt), on the diffusion_gemma and diffusion_gemma_text types. is_diffusion_gemma, which matched the name in model_type, architectures and the nested text_config, is gone. - VILA: "vila" in the checkpoint path becomes PTQSpec.fsdp2_unsupported on llava_llama, the model type of VILA, NVILA and NVILA-Lite. It no longer depends on what the path is called. - Eagle/Medusa: the architecture-name list becomes modelopt.torch.speculative.utils.is_speculative_config. A draft keeps its base model's model_type, so no model-type registry can tell it apart. - The TensorRT-LLM encoder-decoder model types move to the deprecated exporter's own package (export/trtllm/enc_dec.py), which goes away with it in 0.49.0. A config that carries no model_type, or only a nested text_config naming DiffusionGemma, is no longer recognised. AutoConfig cannot load the first, and the real checkpoint's root type is diffusion_gemma. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
- The preview takes input_ids whenever the batch has them, and input_features only for a batch without: an audio-capable VLM's image-text batch can carry both, and its preview must stay on token ids. - Cover both device-map precedences in one test: a wrapper that cannot take "auto" still gets an explicitly requested sequential map, and T5 keeps no map whatever the run asks for. - Say only what is known of the DeciLM precision keyword, drop the stale DiffusionGemma comment the spec now carries, and fix the README's naming rule for model types that are not lowercase module names. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The ptq_spec row listed only the first fields written. It now covers loading, VLM wrappers, the preview and FSDP2 refusals the section grew to hold. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The torch-only partial-install CI job installs no transformers, and modelopt.torch.speculative.utils imports it at module level, so collecting test_is_speculative_config.py failed that job (and, through it, the required unit check) before any test ran. Skip the module the way its neighbours do. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
llava_llama is VILA's model type, but LLaVA-repo fine-tunes share it, so the FSDP2 refusal reaches them too. That is deliberate: the registry holds one spec per model type, not one per variant. Record it where the question gets asked. Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
The registry is keyed by exact model_type, so spelling out the types behind the
old prefix and substring checks (startswith("deepseek"), "DeciLM" in the
architecture, "nemotron" in the architectures, "diffusiongemma" in a name) meant
one package per type found on the hub. Keep the types the old code or its tests
name (t5, bart, whisper, diffusion_gemma, kimi_k2, deepseek_v32,
NemotronH_Nano_VL_V2) and llava_llama, and drop the rest:
- deci and nemotron-nas, and with them PTQSpec.dtype_kwarg: DeciLM checkpoints
load through the generic dtype keyword.
- deepseek_v2: no preview skip and no bundled-code preference.
- diffusion_gemma_text: a text-only DiffusionGemma checkpoint takes the generic
device map and preview.
- Llama_Nemotron_Nano_VL and nemotron_parse, and the internvl_chat calibration
route: they take the generic load, calibration and preview.
Signed-off-by: Shengliang Xu <shengliangx@nvidia.com>
shengliangxu
force-pushed
the
shengliangx/hf-ptq-model-agnostic
branch
from
November 4, 2026 20:38
6909616 to
1996aa0
Compare
This was referenced Nov 5, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Type of change: Refactor
Reference only, not for merge. This is the full change in one diff, for readers who want the whole picture. It is split for review into the three stacked PRs below; this PR will be closed as they land.
examples/hf_ptqdecided how to load, calibrate, preview and export a model by testing its name (model_type == "t5",startswith(("deepseek", "kimi_k2")),"nemotron" in architectures,"vila"in the checkpoint path, ...). That knowledge belongs with the model. This series moves it intomodelopt/torch/models, the per-model-type registry from #1828 and #2576-#2580, so the example dispatches through generic per-model data (PTQSpec) and optional hooks and contains no branch on a named model. Adding a model's PTQ facts becomes a registry entry, not an edit to the example. The example still dispatches through the registry; what leaves it is every condition that names a model.The series
In merge order; each part builds, carries its own tests and passes CI without the later ones.
PTQSpec: per-model facts for loading, preview and FSDP2 in the model registryBy the review guideline's count (lines added to Python source, without blank lines, comments and docstrings; tests excluded; a move git detects as a rename not counted as new), the whole series is 358 lines: 269 in
modelopt/and 89 inexamples/hf_ptq. That includes 8 new two-file model packages (the registry's convention is one package per model type); the 78 lines ofnemotron_vl_calib.pythat move verbatim into the library are not counted.Base. This branch is on main c785ca9 and has nine commits: the form of the change that was reviewed and that the GPU runs below ran on. The stack is on main 219b34a, which includes #3139 (expert-parallel PTQ from
hf_ptq.py). #3139 rewrote the example functions this change edits, so the stack carries the change merged with it (conflicts resolved by keeping #3139's structure and applying the registry and hooks on top), and this branch now conflicts with main in the example. It is not rebased, so the tables below describe the earlier form of the change; each PR of the stack has the results for its own tip.What moves where
model_type == "t5"/"bart"->device_map=Noneno_device_mapont5,bart"DeciLM" in architecture->torch_dtypekeyworddtypekeyword (see below)startswith(("deepseek", "kimi_k2")),== "nemotron_h"-> skip the generation previewpreview_unsupportedondeepseek,deepseek_v3,deepseek_v32,deepseek_v4,kimi_k2,kimi_k25andnemotron_hnot in ("llama4", "llama4_text")-> skip the post-quantization generationpost_ptq_generation_unsupportedonllama4,llama4_text"Deepseek" in architecture-> bundled checkpoint codeprefers_bundled_codehook, through a newdeepseek.bundled_codeplugin registered for the same DeepSeek types andkimi_k2is_nemotron_vl("nemotron"in the architectures) andexamples/hf_ptq/nemotron_vl_calib.pyvlm_wrapperandno_auto_device_maponNemotronH_Nano_VL_V2andnemotron_h_omni, and aforward_calibration_batchhook; the file moves tomodelopt/torch/models/nemotron_h_nano_vl_v2/calibration.py(git mv; the calibration forward is unchanged, the hook wrapper around it is new)model_type == "whisper"in five places (processor, speech calibration data, batch-probe sample shape, preview input key, preview decoding)load_calibration_processor,make_calibration_dataloader,calibration_sample_inputinmodelopt/torch/models/whisper/calibration.py; the preview takesinput_idswhen the batch has them, elseinput_featuresis_diffusion_gemma(sequential multi-GPU map;generate()includes the prompt)sequential_device_map,generate_includes_promptondiffusion_gemma"vila" in args.local_checkpoint_path(FSDP2 refusal)fsdp2_unsupportedonllava_llama(VILA, NVILA, NVILA-Lite; LLaVA-repo fine-tunes share the type and are covered too)SPECULATIVE_MODEL_LIST(Eagle,Medusain the architecture)modelopt.torch.speculative.utils.is_speculative_config(a draft keeps its base model'smodel_type, so no model-type registry can tell it apart)is_trtllm_enc_dec_export(t5, mt5, umt5, bart, mbart, whisper)modelopt/torch/export/trtllm/enc_dec.py, which goes away with that exporter in 0.49.0PTQSpecis a new section ofModelSpec, read throughget_ptq_spec(model_type), which returns an all-default section for a model without one. Its fields are reasons or keywords, so each special case carries its why.hf.pygains four optional hooks (forward_calibration_batch,load_calibration_processor,make_calibration_dataloader,calibration_sample_input), and a model type may now list several plugin modules (two that define the same hook are refused).Two device-map precedences are kept exactly, which is why there are two fields:
no_auto_device_map(Nemotron VL) still yields to an explicit--use_seq_device_map, since users shard these wrappers;no_device_map(T5, BART) yields to nothing.Model types the series adds to the registry
The registry is keyed by exact
model_type, where the example matched by prefix and by architecture substring. It does not enumerate every type those patterns reached: a type gets a spec only when the old code or its tests name it, or, for VILA, when no other key can hold the old check. Each new spec is its own package, the registry's convention.t5model_type == "t5"):device_map="auto"can shard its tied encoder/decoder embeddings and position-bias buffers across GPUs, which non-deterministically gives NaN activations in multi-GPU calibration (transformers#21093). Carried asno_device_map.bartmodel_type == "bart"):device_map="auto"and"cuda"raise a meta-tensor error from its safetensors. Carried asno_device_map.deepseek_v32deepseek*type ("DeepSeek generation may go OOM"), and its tests namedeepseek_v32. Carried aspreview_unsupported. Like the other DeepSeek types, it also maps to thedeepseek.bundled_codeplugin, which replaces the"Deepseek" in architecturetest.kimi_k2startswith(("deepseek", "kimi_k2"))), and its tests name it. Carried aspreview_unsupported. It maps to the same bundled-code plugin, since its architecture,DeepseekV3ForCausalLM, matched the old test.diffusion_gemmais_diffusion_gemma) to load it withdevice_map="sequential"on several GPUs, since the balanced map can split a tied encoder/decoder pair, and to know that itsgenerate()returns the prompt with the output. Carried assequential_device_mapandgenerate_includes_prompt.llava_llama--use_fsdp2when the checkpoint path contained "vila" ("custom builder + non-standard layer layout"). VILA has no model type of its own: its checkpoints declarellava_llama, the only key a per-model-type registry can hold that refusal under. Carried asfsdp2_unsupported.NemotronH_Nano_VL_V2(snake_case packagenemotron_h_nano_vl_v2)nemotron_vl_calib.pyruns a calibration forward in place of the wrapper's ownforward(), which callstorch.distributed.get_rank()unconditionally and assumespast_key_values, and the example's tests name the type. The example found it by "nemotron" in the architecture of any multimodal model; the registry needs the exact type. The spec carriesvlm_wrapperandno_auto_device_map, and the package holds the moved forward behind theforward_calibration_batchhook.whispermodel_type == "whisper"in five places. The package holds the three hooks that replace them, inwhisper/calibration.py. Its spec records only the transformers floor, since every package in the registry has aspecs.py(models README).Types already in the registry gain a
ptq_spec:deepseek,deepseek_v3,deepseek_v4,kimi_k25andnemotron_h(preview_unsupported),llama4andllama4_text(post_ptq_generation_unsupported), andnemotron_h_omni(the Nemotron VL fields and calibration hook: the old architecture match covered it too, and the moved forward already handles its layout).Behavior differences (deliberate)
deepseek*types (deepseek_v2,deepseek_vl*, ...) are not skipped by the generation preview and do not prefer the bundled code. DeciLM checkpoints (deci,nemotron-nas) load through thedtypekeyword instead oftorch_dtype. A text-only DiffusionGemma type (diffusion_gemma_text) gets the generic device map and preview. Llama-Nemotron-Nano-VL, Nemotron-Parse and plain InternVL wrappers get the generic load, calibration and preview, without the image-text calibration default or the wrapper preview.text_config(the real root type isdiffusion_gemma).llava_llamamodel type instead of the checkpoint path containing "vila". The type is shared with LLaVA-repo fine-tunes (15 of the 68llava_llamarepos that a hub search for llava or vila finds), which are now refused too, and a renamed VILA checkpoint no longer slips through. The other way round, two kinds of checkpoint that the path test refused are no longer refused: those that declare the model typevila(VILA's four-hf-previewports and one third-party port, 11 to 21 downloads each), and unrelated models whose path contains "vila" (for example Qwen2 fine-tunes namedvilaw-*). This is by design: the registry holds one spec per model type, not one per variant.Loading without a device map: ...,Skipping the generation preview: ...,Using device_map='sequential': .... Log wording: "VLM wrapper" replaces "Nemotron VL"; the FSDP2 MTP refusal says "MTP weights (...)".input_idswhen the batch has them, elseinput_features(it was Whisper by name).Left out on purpose:
dataset_utils.model_type_is_enc_dec,export.model_utils.is_multimodal_model(a Nemotron-Parse name test) and the/no_thinkprompt invlm_utilsare library code with the same smell, and are not touched here.Usage
Testing
The CPU parity, focused and torch-only rows are on this branch's tip, rebased onto main c785ca9. The other rows ran on earlier forms of the series, before two rebases onto main and before the last commit dropped six model-type specs. That commit only removes registry entries, a field and the plugin routes of the dropped types, and none of the model-loading runs below used those types (T5, Llama, Mixtral, Whisper, Qwen3-VL).
tests/examples/hf_ptqandtests/unit/torch/{models,export,speculative}, transformers 5.5.4 (the floor)tests/unit/torch/models, the enc-dec export and speculative-config tests)tests/unit/torchwith transformers and the ONNX packages hidden (the selection of the torch-only CI job)main..HEADget_model, processor, dataloader, batch-probe sample,pre_quantizeandpost_quantize, main vs an earlier form of this brancht5-small,whisper-tiny(synthetic audio in place of the audio dataset, which needs torchcodec and ffmpeg), a tiny Llama and a tiny Qwen3-VL: identical loaded class, devices, weights digest, preview input and generated text. The only difference is the deliberate newLoading without a device map: ...line for T5kimi_k2spec, the encoder-decoder decline)tests/examples/hf_ptq/test_llm_ptq.py, main vs this branch, 1 and 2 GPUs, transformers 5.18 (each arm shown to import its ownmodelopt)mpi4pyin the TRT-LLM deploy step of the test image, identical at main);test_ptq_t5passes at bothtest_ptq_t5alone, stdout shown)Loading without a device map: ...line appears once on this branch and not on main; both take the TRT-LLM enc-dec export; all 10 exported files are byte-identical, including both safetensorstests/gpu/torch/{models,export}(registration, unified HF export), 1 GPU, transformers 5.9tests/examples/hf_ptq/test_vlm_ptq.py, GPU, transformers 5.9Before your PR is "Ready for review"
This PR is never marked ready; the checklist is filled in on each sub-PR. Commits are signed (
git commit -s -S).CONTRIBUTING.md: N/AAdditional Information
Tracked by OMNIML-6429, for ModelOpt v0.49.0. The earlier reference draft of the same kind was #2566 (closed; superseded by #2576-#2580).