Skip to content

Commit 7ea5e0e

Browse files
committed
add image-text data calibration support
Signed-off-by: Zhiyu Cheng <zhiyuc@nvidia.com>
1 parent 2f2203c commit 7ea5e0e

3 files changed

Lines changed: 128 additions & 53 deletions

File tree

examples/llm_ptq/example_utils.py

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,33 @@ def get_tokenizer(ckpt_path, trust_remote_code=False, **kwargs) -> PreTrainedTok
276276
if "vila" in ckpt_path.lower():
277277
ckpt_path += "/llm"
278278

279-
tokenizer = AutoTokenizer.from_pretrained(
280-
ckpt_path, trust_remote_code=trust_remote_code, **kwargs
281-
)
279+
# Suppress verbose tokenizer output (e.g., printing all special tokens)
280+
import contextlib
281+
import io
282+
import logging
283+
import os
284+
285+
# Save current settings
286+
old_verbosity = os.environ.get("TOKENIZERS_PARALLELISM", None)
287+
transformers_log_level = logging.getLogger("transformers").level
288+
289+
# Suppress output
290+
os.environ["TOKENIZERS_PARALLELISM"] = "false"
291+
logging.getLogger("transformers").setLevel(logging.ERROR)
292+
293+
# Also capture stdout to suppress verbose tokenizer printing
294+
with contextlib.redirect_stdout(io.StringIO()):
295+
try:
296+
tokenizer = AutoTokenizer.from_pretrained(
297+
ckpt_path, trust_remote_code=trust_remote_code, **kwargs
298+
)
299+
finally:
300+
# Restore original settings
301+
if old_verbosity is not None:
302+
os.environ["TOKENIZERS_PARALLELISM"] = old_verbosity
303+
else:
304+
os.environ.pop("TOKENIZERS_PARALLELISM", None)
305+
logging.getLogger("transformers").setLevel(transformers_log_level)
282306

283307
# can't set attribute 'pad_token' for "<unk>"
284308
# We skip this step for Nemo models
@@ -334,10 +358,23 @@ def get_processor(
334358
# Try to load AutoProcessor for other VL models (e.g., Nemotron-Parse)
335359
# This will only work if the model has a processor config
336360
try:
337-
processor = AutoProcessor.from_pretrained(
338-
ckpt_path,
339-
**model_kwargs,
340-
)
361+
import contextlib
362+
import io
363+
import logging
364+
365+
# Suppress verbose output from processor/tokenizer loading
366+
transformers_log_level = logging.getLogger("transformers").level
367+
logging.getLogger("transformers").setLevel(logging.ERROR)
368+
369+
with contextlib.redirect_stdout(io.StringIO()):
370+
processor = AutoProcessor.from_pretrained(
371+
ckpt_path,
372+
**model_kwargs,
373+
)
374+
375+
# Restore logging
376+
logging.getLogger("transformers").setLevel(transformers_log_level)
377+
341378
print(f"Loaded AutoProcessor for model type: {model_type}")
342379
return processor
343380
except Exception as e:
@@ -476,12 +513,26 @@ def get_model(
476513
# Load config once and handle VL model detection
477514
try:
478515
hf_config = AutoConfig.from_pretrained(ckpt_path, **config_kwargs)
516+
517+
# Check specifically for Nemotron-Parse
518+
architectures = getattr(hf_config, "architectures", [])
519+
is_nemotron_parse = any("nemotronparse" in arch.lower() for arch in architectures)
520+
479521
if is_nemotron_vl(hf_config):
480-
print(
481-
"Detected Nemotron VL model from config. "
482-
"Disabling automatic device mapping for compatibility."
483-
)
484-
device_map = None
522+
if is_nemotron_parse:
523+
# Nemotron-Parse works fine with device_map="auto"
524+
# Keep device_map="auto" to ensure proper device placement
525+
print(
526+
"Detected Nemotron-Parse model from config. "
527+
"Using automatic device mapping."
528+
)
529+
else:
530+
# For other Nemotron VL models, disable device_map for compatibility
531+
print(
532+
"Detected Nemotron VL model from config. "
533+
"Disabling automatic device mapping for compatibility."
534+
)
535+
device_map = None
485536
except Exception as e:
486537
print(f"Error: Could not load config from {ckpt_path}: {e}")
487538
raise RuntimeError(f"Failed to load model configuration from {ckpt_path}") from e
@@ -590,6 +641,21 @@ def get_model(
590641
print(f"Moving model to {device} device...")
591642
model = model.to(device)
592643

644+
# For Nemotron-Parse, ensure the encoder (including RADIO) is fully on device
645+
# The RADIO encoder has buffers that might not be properly moved even with device_map="auto"
646+
# This is because custom RADIO modules might not fully support accelerate's device_map
647+
if device != "cpu" and hasattr(model, "encoder"):
648+
# Check if encoder has any buffers on CPU
649+
cpu_buffers = []
650+
for name, buffer in model.encoder.named_buffers():
651+
if buffer.device.type == "cpu":
652+
cpu_buffers.append(name)
653+
654+
if cpu_buffers:
655+
print(f"Found {len(cpu_buffers)} encoder buffers on CPU. Moving encoder to {device}...")
656+
model.encoder = model.encoder.to(device)
657+
print(f"Encoder moved to {device}")
658+
593659
if device == "cuda" and not is_model_on_gpu(model):
594660
print("Warning: Some parameters are not on a GPU. Calibration can be slow or hit OOM")
595661

examples/llm_ptq/hf_ptq.py

Lines changed: 45 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
)
6767
from modelopt.torch.utils.image_processor import BaseImageProcessor, MllamaImageProcessor
6868
from modelopt.torch.utils.memory_monitor import launch_memory_monitor
69+
from modelopt.torch.utils.nemotron_vlm_dataset_utils import get_nemotron_vlm_dataset_dataloader
6970
from modelopt.torch.utils.speech_dataset_utils import get_speech_dataset_dataloader
7071
from modelopt.torch.utils.vlm_dataset_utils import get_vlm_dataset_dataloader
7172

@@ -141,6 +142,7 @@ def make_calib_dataloader(
141142
tokenizer: PreTrainedTokenizerBase | None,
142143
device: torch.device,
143144
model_type: str | None,
145+
full_model: torch.nn.Module | None = None,
144146
) -> tuple[DataLoader, str | None]:
145147
calib_dataloader = None
146148
first_text_speech_dataset = None
@@ -402,18 +404,35 @@ def load_model(args: argparse.Namespace):
402404
language_model = extracted_lm
403405
model_type = extracted_model_type
404406
else:
407+
# Check if this is a Nemotron VL model that needs a processor
408+
# Do this BEFORE setting default datasets so we can use image-text data for Nemotron-Parse
409+
is_nemotron_vl_model = is_nemotron_vl(full_model)
410+
411+
# Check specifically for Nemotron-Parse to set appropriate dataset defaults
412+
config = full_model.config
413+
architectures = getattr(config, "architectures", [])
414+
is_nemotron_parse = any("nemotronparse" in arch.lower() for arch in architectures)
415+
405416
if args.dataset is None:
406-
args.dataset = ["cnn_dailymail", "nemotron-post-training-dataset-v2"]
407-
warnings.warn(
408-
"No dataset specified. Defaulting to cnn_dailymail and nemotron-post-training-dataset-v2."
409-
)
417+
if is_nemotron_parse:
418+
# For Nemotron-Parse, default to Nemotron VLM Dataset v2
419+
args.dataset = ["nemotron_vlm_v2"]
420+
print(
421+
"No dataset specified. Defaulting to 'nemotron_vlm_v2' for Nemotron-Parse "
422+
"(NVIDIA's image-text dataset for better calibration)."
423+
)
424+
else:
425+
# For other models, use text-only datasets
426+
args.dataset = ["cnn_dailymail", "nemotron-post-training-dataset-v2"]
427+
warnings.warn(
428+
"No dataset specified. Defaulting to cnn_dailymail and nemotron-post-training-dataset-v2."
429+
)
430+
410431
# Adjust calib_size to match dataset length by extending or truncating as needed
411432
args.calib_size = (args.calib_size + [args.calib_size[-1]] * len(args.dataset))[
412433
: len(args.dataset)
413434
]
414435

415-
# Check if this is a Nemotron VL model that needs a processor
416-
is_nemotron_vl_model = is_nemotron_vl(full_model)
417436
if is_nemotron_vl_model:
418437
# Load processor for Nemotron VL models (like Nemotron-Parse)
419438
processor = get_processor(
@@ -506,14 +525,23 @@ def mono_quantize(
506525
"Consider reducing calib_size to reduce calibration time.\n####\n"
507526
)
508527

528+
# Check if this is Nemotron-Parse
529+
config = full_model.config
530+
architectures = getattr(config, "architectures", [])
531+
is_nemotron_parse = any("nemotronparse" in arch.lower() for arch in architectures)
532+
original_forward = None # Track original forward method if we wrap it
533+
509534
# For Nemotron VL models, disable quantization of vision components
510535
if is_nemotron_vl_model:
511536
print("Disabling quantization for vision components in Nemotron VL model")
512537
quant_cfg["quant_cfg"]["*vision*"] = {"enable": False}
513538
quant_cfg["quant_cfg"]["*image*"] = {"enable": False}
514-
# Also disable radio model components specifically
539+
# Also disable radio model components specifically (for Nemotron-Parse)
515540
quant_cfg["quant_cfg"]["*radio*"] = {"enable": False}
516541
quant_cfg["quant_cfg"]["*visual*"] = {"enable": False}
542+
quant_cfg["quant_cfg"]["*encoder*"] = {"enable": False} # Disable encoder
543+
quant_cfg["quant_cfg"]["*model_encoder*"] = {"enable": False} # Nemotron-Parse specific
544+
print("Quantization will only be applied to the decoder (text generation) component")
517545

518546
if not model_is_already_quantized or calibration_only:
519547
if model_type == "gptoss" and args.qformat == "nvfp4_mlp_only":
@@ -541,8 +569,15 @@ def mono_quantize(
541569
else:
542570
language_model = mtq.quantize(language_model, quant_cfg, forward_loop=calibrate_loop)
543571

544-
# For VL models, update full_model to use the quantized language model
545-
if is_nemotron_vl_model:
572+
# Restore original forward method if we wrapped it for Nemotron-Parse
573+
if is_nemotron_parse and original_forward is not None:
574+
print("Restoring original forward method after calibration")
575+
language_model.forward = original_forward
576+
original_forward = None
577+
578+
# For VL models (except Nemotron-Parse), update full_model to use the quantized language model
579+
# For Nemotron-Parse, language_model IS full_model, so no update needed
580+
if is_nemotron_vl_model and language_model is not full_model:
546581
language_model_lineage = get_language_model_from_vl(full_model)
547582
if language_model_lineage is not None:
548583
print("Updating full_model with quantized language_model...")
@@ -866,38 +901,12 @@ def quantize_main(
866901
print(f"Use calib batch_size {args.batch_size}")
867902

868903
calib_dataloader, first_text_speech_dataset = make_calib_dataloader(
869-
args, language_model, processor, tokenizer, device, model_type
904+
args, language_model, processor, tokenizer, device, model_type, full_model
870905
)
871906

872907
# Detect if this is a Nemotron VL model using architecture-based detection
873908
is_nemotron_vl_model = is_nemotron_vl(full_model)
874909

875-
# For Nemotron-Parse, wrap the text-only dataloader to add dummy images
876-
# Nemotron-Parse is an encoder-decoder model that requires pixel_values
877-
if is_nemotron_vl_model and processor is not None:
878-
config = full_model.config
879-
architectures = getattr(config, "architectures", [])
880-
is_nemotron_parse = any("nemotronparse" in arch.lower() for arch in architectures)
881-
882-
if is_nemotron_parse:
883-
# Check if we're quantizing just the decoder or the full model
884-
decoder_only = language_model is not full_model
885-
886-
if decoder_only:
887-
print(
888-
"Calibration will use text-only inputs for Nemotron-Parse decoder. "
889-
"Vision encoder is excluded from quantization."
890-
)
891-
else:
892-
print(
893-
"Wrapping calibration dataloader for Nemotron-Parse to add dummy images. "
894-
"Nemotron-Parse requires pixel_values for full model calibration."
895-
)
896-
897-
calib_dataloader = create_nemotron_parse_calib_wrapper(
898-
calib_dataloader, processor, device, decoder_only=decoder_only
899-
)
900-
901910
preview_input_ids, generated_ids_before_ptq = pre_quantize(
902911
args, full_model, model_type, tokenizer, calib_dataloader, is_nemotron_vl_model
903912
)

modelopt/torch/export/unified_export_hf.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,11 @@ def llm_dummy_forward():
330330
print(
331331
f"Running optimization on language model with fake_input shape: {fake_input.shape}"
332332
)
333-
# For Nemotron-Parse decoder, force use_cache=False to avoid tuple index errors
334-
if is_nemotron_parse:
335-
language_model(fake_input, use_cache=False)
336-
else:
337-
language_model(fake_input)
333+
# For Nemotron-Parse decoder, force use_cache=False to avoid tuple index errors
334+
if is_nemotron_parse:
335+
language_model(fake_input, use_cache=False)
336+
else:
337+
language_model(fake_input)
338338
else:
339339
raise ValueError(
340340
f"Cannot extract language_model from Nemotron VL model (type: {model_type}). "

0 commit comments

Comments
 (0)