Skip to content

Commit 8b29228

Browse files
committed
Merge branch 'main' into jingyux/diffusion.export-fixed
2 parents e931fbc + 792806f commit 8b29228

12 files changed

Lines changed: 164 additions & 10 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ NVIDIA Model Optimizer Changelog (Linux)
1111
**New Features**
1212

1313
- Add standalone type inference option (``--use_standalone_type_inference``) in ONNX AutoCast as an alternative to ONNX's ``infer_shapes``. This experimental feature performs type-only inference without shape inference, useful as a workaround when shape inference fails or to avoid unnecessary shape inference overhead.
14+
- Add support for Kimi K2 Thinking model quantization from the original int4 checkpoint.
1415

1516
0.41 (2026-01-19)
1617
^^^^^^^^^^^^^^^^^
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
Security Considerations
2+
=======================
3+
4+
Overview
5+
--------
6+
7+
NVIDIA Model Optimizer (ModelOpt) is a library used to optimize ML models and
8+
may load and process user-provided artifacts (models, weights, configs,
9+
calibration data) and their dependencies. Secure deployment depends on how you
10+
source artifacts, validate inputs, and harden the environment where ModelOpt
11+
runs.
12+
13+
What to Be Aware Of
14+
-------------------
15+
16+
**Untrusted model and data inputs**
17+
18+
- Models, weights, configs and data may be malicious or corrupted.
19+
20+
**Deserialization and code-execution risks**
21+
22+
- Unsafe deserialization can lead to arbitrary code execution if fed untrusted
23+
inputs.
24+
- Avoid using serialization formats/settings that can deserialize arbitrary
25+
objects.
26+
27+
**Input validation and resource exhaustion**
28+
29+
- Large or malformed inputs can trigger crashes or excessive CPU/GPU/memory use.
30+
- Missing size/type checks can increase DoS risk.
31+
32+
**Data in transit and at rest**
33+
34+
- If fetching models or dependencies over the network, insecure transport can
35+
enable tampering.
36+
- Stored artifacts, logs, and caches may contain sensitive data.
37+
38+
**Logging and observability**
39+
40+
- Logs may inadvertently contain sensitive inputs, paths, tokens, or proprietary
41+
model details.
42+
- Overly verbose logs can leak operational and security-relevant information.
43+
44+
**Supply chain and third-party components**
45+
46+
- Dependencies may include known vulnerabilities or be compromised.
47+
- Third-party plugins/components loaded at runtime may not have the same
48+
security assurances.
49+
50+
Example Security Approaches
51+
---------------------------
52+
53+
**Artifact integrity**
54+
55+
- Only load artifacts from trusted sources.
56+
- Prefer signed artifacts; verify signatures before loading.
57+
58+
**Safe parsing and deserialization**
59+
60+
- Prefer safer storage formats (avoid object deserialization for untrusted
61+
inputs).
62+
- Avoid ``pickle``, ``torch.load()`` with untrusted weights, or YAML
63+
``unsafe_load``.
64+
- Treat any unverified artifact as untrusted and block/guard its loading.
65+
66+
**Hardening and least privilege**
67+
68+
- Run with least privilege and isolate workloads.
69+
70+
**Data protection**
71+
72+
- Encrypt sensitive data at rest; use TLS 1.3 for data in transit.
73+
- Never hardcode or log credentials.
74+
75+
**Resilience**
76+
77+
- Validate inputs and enforce limits (file size, timeouts, quotas,..).
78+
- Keep OS, containers, and dependencies patched; scan for known vulnerabilities.

examples/llm_ptq/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Please reference our [framework scripts](#framework-scripts) and our [docs](http
109109
| QWen3 MOE, Next <sup>6</sup> || - | - | - ||
110110
| QwQ || - | - | - ||
111111
| DeepSeek V3, R1, V3.1, V3.2<sup>7</sup> | - | - | - | - ||
112+
| Kimi K2 | - | - | - | - ||
112113
| T5 ||||| - |
113114
| Whisper ||||| - |
114115

examples/llm_ptq/example_utils.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ def build_quant_cfg(
187187
quant_cfg["quant_cfg"]["model*.*attn*k_proj*"] = {"enable": False}
188188
quant_cfg["quant_cfg"]["model*.*attn*v_proj*"] = {"enable": False}
189189

190+
if model_type == "deepseek":
191+
# Disable MLA quantization for accuracy.
192+
quant_cfg["quant_cfg"]["*self_attn.q*"] = {"enable": False}
193+
quant_cfg["quant_cfg"]["*self_attn.kv*"] = {"enable": False}
194+
190195
return quant_cfg
191196

192197

@@ -346,6 +351,17 @@ def get_model(
346351
device_map=device_map,
347352
**model_kwargs,
348353
)
354+
elif (
355+
hasattr(hf_config, "quantization_config")
356+
and hf_config.quantization_config.get("format", None) == "pack-quantized"
357+
):
358+
torch_dtype = getattr(hf_config, "torch_dtype", torch.bfloat16)
359+
model = AutoModelForCausalLM.from_pretrained(
360+
ckpt_path,
361+
device_map="auto",
362+
trust_remote_code=trust_remote_code,
363+
torch_dtype=torch_dtype,
364+
)
349365
else:
350366
architecture = hf_config.architectures[0]
351367

@@ -366,9 +382,9 @@ def get_model(
366382
from_config = auto_model_module._from_config
367383

368384
with init_empty_weights():
369-
# When computing the device_map, assuming half precision by default,
385+
# When computing the device_map, assuming bfloat16 precision by default,
370386
# unless specified by the hf_config.
371-
torch_dtype = getattr(hf_config, "torch_dtype", torch.float16)
387+
torch_dtype = getattr(hf_config, "torch_dtype", torch.bfloat16)
372388
model_kwargs2 = model_kwargs.copy()
373389
if auto_model_module != AutoModelForCausalLM:
374390
model_kwargs2.pop("trust_remote_code", None)

examples/llm_ptq/hf_ptq.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,10 @@ def pre_quantize(
575575
][0:1]
576576

577577
# Generate preview before quantization
578-
if is_nemotron_vl_model and tokenizer is not None:
578+
if model_type == "deepseek":
579+
# DeepSeek generation may go OOM, so we skip it
580+
generated_ids_before_ptq = None
581+
elif is_nemotron_vl_model and tokenizer is not None:
579582
generated_ids_before_ptq = run_nemotron_vl_preview(
580583
full_model,
581584
tokenizer,
@@ -618,7 +621,9 @@ def post_quantize(
618621
# Run some samples
619622
torch.cuda.empty_cache()
620623
generated_ids_after_ptq = None
621-
if model_type != "llama4" and not is_nemotron_vl_model:
624+
if generated_ids_before_ptq is None:
625+
pass
626+
elif model_type != "llama4" and not is_nemotron_vl_model:
622627
# Our fake quantizer may not be fully compatible with torch.compile.
623628
generated_ids_after_ptq = full_model.generate(preview_input_ids, max_new_tokens=100)
624629
elif is_nemotron_vl_model and tokenizer is not None:

examples/llm_ptq/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
compressed-tensors==0.12.0
12
fire
23
flash-attn>=2.6.0
34
rouge_score>=0.1.2

examples/llm_ptq/scripts/huggingface_example.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ esac
5353
IFS=","
5454
for qformat in $QFORMAT; do
5555
case $qformat in
56-
fp8 | fp8_pc_pt | fp8_pb_wo | int8_wo | int8_sq | int4_awq | w4a8_awq | fp16 | bf16 | nvfp4 | nvfp4_awq | w4a8_nvfp4_fp8 | w4a8_mxfp4_fp8) ;;
56+
fp8 | fp8_pc_pt | fp8_pb_wo | int8_wo | int8_sq | int4_awq | w4a8_awq | fp16 | bf16 | nvfp4 | nvfp4_awq | w4a8_nvfp4_fp8 | w4a8_mxfp4_fp8 | nvfp4_mlp_only) ;;
5757
*)
58-
echo "Unknown quant argument: Expected one of: [fp8, fp8_pc_pt, fp8_pb_wo, int8_wo, int8_sq, int4_awq, w4a8_awq, fp16, bf16, nvfp4, nvfp4_awq, w4a8_nvfp4_fp8, w4a8_mxfp4_fp8]" >&2
58+
echo "Unknown quant argument: Expected one of: [fp8, fp8_pc_pt, fp8_pb_wo, int8_wo, int8_sq, int4_awq, w4a8_awq, fp16, bf16, nvfp4, nvfp4_awq, w4a8_nvfp4_fp8, w4a8_mxfp4_fp8, nvfp4_mlp_only]" >&2
5959
exit 1
6060
;;
6161
esac

modelopt/torch/export/layer_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,8 @@ def is_moe(module: nn.Module) -> bool:
345345

346346
def is_quantlinear(module: nn.Module) -> bool:
347347
"""Returns whether the module is a quantized linear layer."""
348-
return "QuantLinear" in type(module).__name__ and "lora" not in type(module).__name__.lower()
348+
name = type(module).__name__
349+
return ("QuantLinear" in name or "QuantCompressedLinear" in name) and "lora" not in name.lower()
349350

350351

351352
def dup_kv_weight(v: torch.Tensor, head_size: int, num_head: int, tp_size: int) -> torch.Tensor:

modelopt/torch/export/quant_utils.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -881,7 +881,13 @@ def postprocess_state_dict(
881881
"v_bmm_quantizer._bias_value": "v_proj.v_bias",
882882
"input_quantizer._pre_quant_scale": "pre_quant_scale",
883883
}
884-
skip_keys = ["output_quantizer", "_amax", "_bias_value", "input_quantizer._pre_quant_scale"]
884+
skip_keys = [
885+
"output_quantizer",
886+
"_amax",
887+
"_bias_value",
888+
"input_quantizer._pre_quant_scale",
889+
"weight_shape",
890+
]
885891

886892
# For modelopt-trained LoRA models, we need to remove the base_layer prefix from the keys for deployment
887893
if is_modelopt_qlora:

modelopt/torch/export/unified_export_hf.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,8 @@ def _export_quantized_weight(
515515
if weight_scale is not None:
516516
sub_module.register_buffer(quantizer_attrs.weight_scale, weight_scale)
517517

518+
torch.cuda.empty_cache()
519+
518520

519521
def _process_quantized_modules(
520522
model: nn.Module,
@@ -550,6 +552,8 @@ def _process_quantized_modules(
550552
if is_modelopt_qlora and (hasattr(sub_module, "base_layer")):
551553
continue
552554

555+
if hasattr(sub_module, "weight_packed"):
556+
sub_module.unpack_weight()
553557
if get_quantization_format(sub_module) != QUANTIZATION_NONE:
554558
if is_quantlinear(sub_module):
555559
with fsdp2_aware_weight_update(model, sub_module, reshard=False):
@@ -948,6 +952,10 @@ def export_hf_checkpoint(
948952

949953
hf_quant_config = convert_hf_quant_config_format(hf_quant_config)
950954

955+
# Remove hf_quantizer from model so post_state_dict can be exported.
956+
if getattr(model, "hf_quantizer", None) is not None:
957+
model.hf_quantizer = None
958+
951959
# Save model
952960
model.save_pretrained(
953961
export_dir, state_dict=post_state_dict, save_modelopt_state=save_modelopt_state

0 commit comments

Comments
 (0)