@@ -164,6 +164,7 @@ class ModelConfig:
164164 override_model_path : Path | None = None
165165 cpu_offloading : bool = False
166166 ltx_skip_upsampler : bool = False # Skip upsampler for LTX-Video (faster calibration)
167+ extra_params : dict [str , Any ] = field (default_factory = dict )
167168
168169 @property
169170 def model_path (self ) -> str :
@@ -232,6 +233,51 @@ def setup_logging(verbose: bool = False) -> logging.Logger:
232233 return logger
233234
234235
236+ def _coerce_extra_param_value (value : str ) -> Any :
237+ lowered = value .lower ()
238+ if lowered in {"true" , "false" }:
239+ return lowered == "true"
240+ try :
241+ return int (value )
242+ except ValueError :
243+ pass
244+ try :
245+ return float (value )
246+ except ValueError :
247+ return value
248+
249+
250+ def parse_extra_params (
251+ kv_args : list [str ], unknown_args : list [str ], logger : logging .Logger
252+ ) -> dict [str , Any ]:
253+ extra_params : dict [str , Any ] = {}
254+ for item in kv_args :
255+ if "=" not in item :
256+ raise ValueError (f"Invalid --extra-param value: '{ item } '. Expected KEY=VALUE." )
257+ key , value = item .split ("=" , 1 )
258+ extra_params [key ] = _coerce_extra_param_value (value )
259+
260+ i = 0
261+ while i < len (unknown_args ):
262+ token = unknown_args [i ]
263+ if token .startswith ("--extra_param." ):
264+ key = token [len ("--extra_param." ) :]
265+ value = "true"
266+ if i + 1 < len (unknown_args ) and not unknown_args [i + 1 ].startswith ("--" ):
267+ value = unknown_args [i + 1 ]
268+ i += 1
269+ extra_params [key ] = _coerce_extra_param_value (value )
270+ elif token .startswith ("--extra_param" ):
271+ raise ValueError (
272+ "Use --extra_param.KEY VALUE or --extra-param KEY=VALUE for extra parameters."
273+ )
274+ else :
275+ logger .warning ("Ignoring unknown argument: %s" , token )
276+ i += 1
277+
278+ return extra_params
279+
280+
235281class PipelineManager :
236282 """Manages diffusion pipeline creation and configuration."""
237283
@@ -245,8 +291,9 @@ def __init__(self, config: ModelConfig, logger: logging.Logger):
245291 """
246292 self .config = config
247293 self .logger = logger
248- self .pipe : DiffusionPipeline | None = None
294+ self .pipe : Any | None = None
249295 self .pipe_upsample : LTXLatentUpsamplePipeline | None = None # For LTX-Video upsampling
296+ self ._transformer : torch .nn .Module | None = None
250297
251298 @staticmethod
252299 def create_pipeline_from (
@@ -264,10 +311,13 @@ def create_pipeline_from(
264311 ValueError: If model type is unsupported
265312 """
266313 try :
314+ pipeline_cls = MODEL_PIPELINE [model_type ]
315+ if pipeline_cls is None :
316+ raise ValueError (f"Model type { model_type .value } does not use diffusers pipelines." )
267317 model_id = (
268318 MODEL_REGISTRY [model_type ] if override_model_path is None else override_model_path
269319 )
270- pipe = MODEL_PIPELINE [ model_type ] .from_pretrained (
320+ pipe = pipeline_cls .from_pretrained (
271321 model_id ,
272322 torch_dtype = torch_dtype ,
273323 use_safetensors = True ,
@@ -278,7 +328,7 @@ def create_pipeline_from(
278328 except Exception as e :
279329 raise e
280330
281- def create_pipeline (self ) -> DiffusionPipeline :
331+ def create_pipeline (self ) -> Any :
282332 """
283333 Create and return an appropriate pipeline based on configuration.
284334
@@ -293,6 +343,14 @@ def create_pipeline(self) -> DiffusionPipeline:
293343 self .logger .info (f"Data type: { self .config .model_dtype } " )
294344
295345 try :
346+ if self .config .model_type == ModelType .LTX2 :
347+ from modelopt .torch .quantization .plugins .diffusion import ltx2 as ltx2_plugin
348+
349+ ltx2_plugin .register_ltx2_quant_linear ()
350+ self .pipe = self ._create_ltx2_pipeline ()
351+ self .logger .info ("LTX-2 pipeline created successfully" )
352+ return self .pipe
353+
296354 self .pipe = MODEL_PIPELINE [self .config .model_type ].from_pretrained (
297355 self .config .model_path ,
298356 torch_dtype = self .config .model_dtype ,
@@ -325,6 +383,10 @@ def setup_device(self) -> None:
325383 if not self .pipe :
326384 raise RuntimeError ("Pipeline not created. Call create_pipeline() first." )
327385
386+ if self .config .model_type == ModelType .LTX2 :
387+ self .logger .info ("Skipping device setup for LTX-2 pipeline (handled internally)" )
388+ return
389+
328390 if self .config .cpu_offloading :
329391 self .logger .info ("Enabling CPU offloading for memory efficiency" )
330392 self .pipe .enable_model_cpu_offload ()
@@ -352,8 +414,58 @@ def get_backbone(self) -> torch.nn.Module:
352414 if not self .pipe :
353415 raise RuntimeError ("Pipeline not created. Call create_pipeline() first." )
354416
417+ if self .config .model_type == ModelType .LTX2 :
418+ self ._ensure_ltx2_transformer_cached ()
419+ return self ._transformer
355420 return getattr (self .pipe , self .config .backbone )
356421
422+ def _ensure_ltx2_transformer_cached (self ) -> None :
423+ if not self .pipe :
424+ raise RuntimeError ("Pipeline not created. Call create_pipeline() first." )
425+ if self ._transformer is None :
426+ transformer = self .pipe .stage_1_model_ledger .transformer ()
427+ self .pipe .stage_1_model_ledger .transformer = lambda : transformer
428+ self ._transformer = transformer
429+
430+ def _create_ltx2_pipeline (self ) -> Any :
431+ params = dict (self .config .extra_params )
432+ checkpoint_path = params .pop ("checkpoint_path" , None )
433+ distilled_lora_path = params .pop ("distilled_lora_path" , None )
434+ distilled_lora_strength = params .pop ("distilled_lora_strength" , 0.8 )
435+ spatial_upsampler_path = params .pop ("spatial_upsampler_path" , None )
436+ gemma_root = params .pop ("gemma_root" , None )
437+ fp8transformer = params .pop ("fp8transformer" , False )
438+
439+ if not checkpoint_path :
440+ raise ValueError ("Missing required extra_param: checkpoint_path." )
441+ if not distilled_lora_path :
442+ raise ValueError ("Missing required extra_param: distilled_lora_path." )
443+ if not spatial_upsampler_path :
444+ raise ValueError ("Missing required extra_param: spatial_upsampler_path." )
445+ if not gemma_root :
446+ raise ValueError ("Missing required extra_param: gemma_root." )
447+
448+ from ltx_core .loader import LTXV_LORA_COMFY_RENAMING_MAP , LoraPathStrengthAndSDOps
449+ from ltx_pipelines .ti2vid_two_stages import TI2VidTwoStagesPipeline
450+
451+ distilled_lora = [
452+ LoraPathStrengthAndSDOps (
453+ str (distilled_lora_path ),
454+ float (distilled_lora_strength ),
455+ LTXV_LORA_COMFY_RENAMING_MAP ,
456+ )
457+ ]
458+ pipeline_kwargs = {
459+ "checkpoint_path" : str (checkpoint_path ),
460+ "distilled_lora" : distilled_lora ,
461+ "spatial_upsampler_path" : str (spatial_upsampler_path ),
462+ "gemma_root" : str (gemma_root ),
463+ "loras" : [],
464+ "fp8transformer" : bool (fp8transformer ),
465+ }
466+ pipeline_kwargs .update (params )
467+ return TI2VidTwoStagesPipeline (** pipeline_kwargs )
468+
357469
358470class Calibrator :
359471 """Handles model calibration for quantization."""
@@ -417,7 +529,9 @@ def run_calibration(self, batched_prompts: list[list[str]]) -> None:
417529 if i >= self .config .num_batches :
418530 break
419531
420- if self .model_type == ModelType .LTX_VIDEO_DEV :
532+ if self .model_type == ModelType .LTX2 :
533+ self ._run_ltx2_calibration (prompt_batch , extra_args )
534+ elif self .model_type == ModelType .LTX_VIDEO_DEV :
421535 # Special handling for LTX-Video
422536 self ._run_ltx_video_calibration (prompt_batch , extra_args )
423537 elif self .model_type in [ModelType .WAN22_T2V_14b , ModelType .WAN22_T2V_5b ]:
@@ -448,6 +562,29 @@ def _run_wan_video_calibration(
448562
449563 self .pipe (prompt = prompt_batch , ** kwargs ).frames # type: ignore[misc]
450564
565+ def _run_ltx2_calibration (self , prompt_batch : list [str ], extra_args : dict [str , Any ]) -> None :
566+ from ltx_core .model .video_vae import TilingConfig
567+
568+ prompt = prompt_batch [0 ]
569+ extra_params = self .pipeline_manager .config .extra_params
570+ kwargs = {
571+ "negative_prompt" : extra_args .get (
572+ "negative_prompt" , "worst quality, inconsistent motion, blurry, jittery, distorted"
573+ ),
574+ "seed" : extra_params .get ("seed" , 0 ),
575+ "height" : extra_params .get ("height" , extra_args .get ("height" , 1024 )),
576+ "width" : extra_params .get ("width" , extra_args .get ("width" , 1536 )),
577+ "num_frames" : extra_params .get ("num_frames" , extra_args .get ("num_frames" , 121 )),
578+ "frame_rate" : extra_params .get ("frame_rate" , extra_args .get ("frame_rate" , 24.0 )),
579+ "num_inference_steps" : self .config .n_steps ,
580+ "cfg_guidance_scale" : extra_params .get (
581+ "cfg_guidance_scale" , extra_args .get ("cfg_guidance_scale" , 4.0 )
582+ ),
583+ "images" : extra_params .get ("images" , []),
584+ "tiling_config" : extra_params .get ("tiling_config" , TilingConfig .default ()),
585+ }
586+ self .pipe (prompt = prompt , ** kwargs ) # type: ignore[misc]
587+
451588 def _run_ltx_video_calibration (
452589 self , prompt_batch : list [str ], extra_args : dict [str , Any ]
453590 ) -> None :
@@ -568,7 +705,7 @@ def quantize_model(
568705 backbone : torch .nn .Module ,
569706 quant_config : Any ,
570707 forward_loop : callable , # type: ignore[valid-type]
571- ) -> None :
708+ ) -> torch . nn . Module :
572709 """
573710 Apply quantization to the model.
574711
@@ -590,6 +727,7 @@ def quantize_model(
590727 mtq .disable_quantizer (backbone , model_filter_func )
591728
592729 self .logger .info ("Quantization completed successfully" )
730+ return backbone
593731
594732
595733class ExportManager :
@@ -754,7 +892,7 @@ def create_argument_parser() -> argparse.ArgumentParser:
754892 model_group .add_argument (
755893 "--model-dtype" ,
756894 type = str ,
757- default = "Half " ,
895+ default = "BFloat16 " ,
758896 choices = [d .value for d in DataType ],
759897 help = "Precision for loading the pipeline. If you want different dtypes for separate components, "
760898 "please specify using --component-dtype" ,
@@ -778,6 +916,16 @@ def create_argument_parser() -> argparse.ArgumentParser:
778916 action = "store_true" ,
779917 help = "Skip upsampler pipeline for LTX-Video (faster calibration, only quantizes main transformer)" ,
780918 )
919+ model_group .add_argument (
920+ "--extra-param" ,
921+ action = "append" ,
922+ default = [],
923+ metavar = "KEY=VALUE" ,
924+ help = (
925+ "Extra model-specific parameters in KEY=VALUE form. Can be provided multiple times. "
926+ "These override model-specific CLI arguments when present."
927+ ),
928+ )
781929 quant_group = parser .add_argument_group ("Quantization Configuration" )
782930 quant_group .add_argument (
783931 "--format" ,
@@ -859,7 +1007,7 @@ def create_argument_parser() -> argparse.ArgumentParser:
8591007
8601008def main () -> None :
8611009 parser = create_argument_parser ()
862- args = parser .parse_args ()
1010+ args , unknown_args = parser .parse_known_args ()
8631011
8641012 model_type = ModelType (args .model )
8651013 if args .backbone is None :
@@ -875,6 +1023,7 @@ def main() -> None:
8751023 logger .info ("Starting Enhanced Diffusion Model Quantization" )
8761024
8771025 try :
1026+ extra_params = parse_extra_params (args .extra_param , unknown_args , logger )
8781027 model_config = ModelConfig (
8791028 model_type = model_type ,
8801029 model_dtype = model_dtype ,
@@ -885,6 +1034,7 @@ def main() -> None:
8851034 else None ,
8861035 cpu_offloading = args .cpu_offloading ,
8871036 ltx_skip_upsampler = args .ltx_skip_upsampler ,
1037+ extra_params = extra_params ,
8881038 )
8891039
8901040 quant_config = QuantizationConfig (
@@ -950,6 +1100,7 @@ def main() -> None:
9501100 quantizer = Quantizer (quant_config , model_config , logger )
9511101 backbone_quant_config = quantizer .get_quant_config (calib_config .n_steps , backbone )
9521102
1103+ # Pipe loads the ckpt just before the inference.
9531104 def forward_loop (mod ):
9541105 calibrator .run_calibration (batched_prompts )
9551106
0 commit comments