@@ -74,6 +74,45 @@ def _init_all_weights(model: torch.nn.Module, std: float = 0.02):
7474 torch .nn .init .normal_ (p , mean = 0.0 , std = std )
7575
7676
77+ # float32 quant-scale params (filled to 1.0, not normal_, so dequant is well-behaved).
78+ _FP4_FLOAT_SCALE_SUFFIXES = (
79+ ".input_scale" ,
80+ ".inv_input_scale" ,
81+ ".weight_scale_2" ,
82+ ".alpha" ,
83+ ".kv_scales" ,
84+ ".inv_kv_scales" ,
85+ )
86+
87+
88+ def _init_weights_quant_safe (model : torch .nn .Module , std : float = 0.02 ):
89+ """Like ``_init_all_weights`` but safe for NVFP4 modules.
90+
91+ NVFP4 Linears hold a packed-fp4 weight (``float4_e2m1x2``) and an FP8 scale
92+ factor (``weight_scale``) whose dtypes ``torch.nn.init.normal_`` cannot fill.
93+ Fill standard float params (norm weights and quant scales to 1.0, the rest
94+ random); for the sub-byte/FP8 quant tensors write a benign finite byte.
95+ """
96+ with torch .no_grad ():
97+ for name , p in model .named_parameters ():
98+ if p .numel () == 0 :
99+ continue
100+ if p .dtype in (torch .float32 , torch .float16 , torch .bfloat16 ):
101+ if any (name .endswith (s ) for s in _FP4_FLOAT_SCALE_SUFFIXES ):
102+ p .fill_ (1.0 )
103+ elif "norm" in name and "weight" in name :
104+ p .fill_ (1.0 )
105+ else :
106+ torch .nn .init .normal_ (p , mean = 0.0 , std = std )
107+ else :
108+ # Packed-fp4 weight / FP8 scale factor: normal_ is undefined on
109+ # these dtypes, so write a benign finite byte via a uint8 view.
110+ try :
111+ p .view (torch .uint8 ).fill_ (1 )
112+ except Exception :
113+ pass
114+
115+
77116def _make_video_positions (
78117 batch : int , n_patches : int , n_frames : int , grid_h : int , grid_w : int , device : torch .device
79118) -> torch .Tensor :
@@ -390,6 +429,153 @@ def test_video_only_input_to_audio_video_model(self):
390429 )
391430
392431
432+ # AudioVideo config with real fused-AdaLN hidden dims (video/audio both 32*64=2048,
433+ # in _FUSED_ADALN_SUPPORTED_DIMS). Smaller dims (e.g. 128) take the eager bf16 fallback,
434+ # so no rank-3 Fp4QuantizedTensor is ever produced and the smoke would be a trivial pass.
435+ FP4_SMOKE_AV_CONFIG = dict (
436+ num_attention_heads = 32 ,
437+ attention_head_dim = 64 , # video inner dim = 2048
438+ in_channels = 32 , # NVFP4 GEMM needs K (= in_channels patchify) divisible by 32
439+ out_channels = 32 ,
440+ num_layers = 1 ,
441+ cross_attention_dim = 2048 , # text cross-attn context_dim must equal inner_dim (32*64)
442+ caption_channels = 64 ,
443+ norm_eps = 1e-6 ,
444+ positional_embedding_max_pos = [4 , 32 , 32 ],
445+ timestep_scale_multiplier = 1000 ,
446+ use_middle_indices_grid = True ,
447+ audio_num_attention_heads = 32 ,
448+ audio_attention_head_dim = 64 , # audio inner dim = 2048
449+ audio_in_channels = 32 ,
450+ audio_out_channels = 32 ,
451+ audio_cross_attention_dim = 2048 , # must equal audio inner_dim (32*64)
452+ audio_positional_embedding_max_pos = [4 ],
453+ av_ca_timestep_scale_multiplier = 1 ,
454+ )
455+
456+
457+ class TestLTX2NVFP4ForwardSmoke (unittest .TestCase ):
458+ """NVFP4 forward smoke: a static-NVFP4 AudioVideo model must run end-to-end with
459+ fabricated weights/inputs without raising.
460+
461+ Guards the rank-3 ``Fp4QuantizedTensor`` bug class (PR #15718): the fused
462+ AdaLN+NVFP4 norm emits a rank-3 ``[B, S, D/2]`` fp4 activation that flows into the
463+ fused MLP epilogue and the attention projections; consumers that ``reshape``/assert
464+ 2D crash on it. The forward-pre-hook below asserts the fp4 path is actually
465+ exercised, so the test fails (rather than silently passing) if the model falls back
466+ to bf16.
467+
468+ Requires Blackwell (NVFP4 fused kernels are SM100+) and a hidden dim in {2048, 4096}.
469+ """
470+
471+ @pytest .mark .skipif (
472+ not torch .cuda .is_available () or torch .cuda .get_device_capability () < (10 , 0 ),
473+ reason = "NVFP4 fused kernels require Blackwell (SM100+)" ,
474+ )
475+ def test_audio_video_nvfp4_forward_smoke (self ):
476+ from types import SimpleNamespace
477+
478+ from tensorrt_llm ._torch .utils import Fp4QuantizedTensor
479+ from tensorrt_llm ._torch .visual_gen .models .ltx2 .ltx2_core .modality import Modality
480+ from tensorrt_llm ._torch .visual_gen .models .ltx2 .transformer_ltx2 import (
481+ LTXModel ,
482+ LTXModelType ,
483+ )
484+ from tensorrt_llm .quantization .mode import QuantAlgo
485+
486+ torch .manual_seed (42 )
487+ device = "cuda"
488+ dtype = torch .bfloat16
489+
490+ quant_config = QuantConfig (quant_algo = QuantAlgo .NVFP4 , group_size = 16 )
491+ model_config = DiffusionModelConfig (
492+ pretrained_config = SimpleNamespace (),
493+ quant_config = quant_config ,
494+ mapping = Mapping (),
495+ attention = AttentionConfig (backend = "VANILLA" ),
496+ skip_create_weights_in_init = False ,
497+ )
498+ # NVFP4 model: device-only .to() (a dtype cast would corrupt the packed fp4 weights).
499+ model = (
500+ LTXModel (
501+ model_type = LTXModelType .AudioVideo ,
502+ model_config = model_config ,
503+ ** FP4_SMOKE_AV_CONFIG ,
504+ )
505+ .to (device )
506+ .eval ()
507+ )
508+ _init_weights_quant_safe (model )
509+
510+ batch = 1
511+ v_frames , v_h , v_w = 1 , 4 , 4
512+ v_patches = v_frames * v_h * v_w
513+ a_patches = 8
514+ in_channels = FP4_SMOKE_AV_CONFIG ["in_channels" ]
515+ audio_in_channels = FP4_SMOKE_AV_CONFIG ["audio_in_channels" ]
516+ caption_channels = FP4_SMOKE_AV_CONFIG ["caption_channels" ]
517+ text_len = 8
518+
519+ v_context = (
520+ torch .randn (batch , text_len , caption_channels , device = device , dtype = dtype ) * 0.02
521+ )
522+ a_context = (
523+ torch .randn (batch , text_len , caption_channels , device = device , dtype = dtype ) * 0.02
524+ )
525+ v_positions = _make_video_positions (batch , v_patches , v_frames , v_h , v_w , device )
526+ a_positions = _make_audio_positions (batch , a_patches , device )
527+
528+ video_modality = Modality (
529+ latent = torch .randn (batch , v_patches , in_channels , device = device , dtype = dtype ) * 0.02 ,
530+ timesteps = torch .tensor ([0.5 ], device = device ),
531+ positions = v_positions ,
532+ context = v_context ,
533+ )
534+ audio_modality = Modality (
535+ latent = torch .randn (batch , a_patches , audio_in_channels , device = device , dtype = dtype )
536+ * 0.02 ,
537+ timesteps = torch .tensor ([0.5 ], device = device ),
538+ positions = a_positions ,
539+ context = a_context ,
540+ )
541+ text_cache = model .prepare_text_cache (
542+ video_context = v_context ,
543+ video_positions = v_positions ,
544+ audio_context = a_context ,
545+ audio_positions = a_positions ,
546+ dtype = dtype ,
547+ )
548+
549+ # Self-validation: record whether a rank-3 Fp4QuantizedTensor reaches any module,
550+ # so a silent bf16 fallback (e.g. unsupported dim) fails the test instead of passing.
551+ saw_rank3_fp4 = []
552+
553+ def _record (mod , args ):
554+ for a in args :
555+ if isinstance (a , Fp4QuantizedTensor ) and a .fp4_tensor .dim () > 2 :
556+ saw_rank3_fp4 .append (type (mod ).__name__ )
557+
558+ handles = [m .register_forward_pre_hook (_record ) for m in model .modules ()]
559+ try :
560+ with torch .no_grad ():
561+ video_out , audio_out = model (
562+ video = video_modality , audio = audio_modality , text_cache = text_cache
563+ )
564+ finally :
565+ for h in handles :
566+ h .remove ()
567+
568+ self .assertEqual (video_out .shape , (batch , v_patches , FP4_SMOKE_AV_CONFIG ["out_channels" ]))
569+ self .assertEqual (
570+ audio_out .shape , (batch , a_patches , FP4_SMOKE_AV_CONFIG ["audio_out_channels" ])
571+ )
572+ self .assertTrue (
573+ saw_rank3_fp4 ,
574+ "no rank-3 Fp4QuantizedTensor reached any module -- the fused AdaLN+NVFP4 "
575+ "path was not exercised (check hidden dim in {2048, 4096} and the NVFP4 build)." ,
576+ )
577+
578+
393579class TestLTX2QuantExcludeModuleRemapping (unittest .TestCase ):
394580 """Tests for _remap_exclude_modules and _apply_quant_config_exclude_modules.
395581
0 commit comments