6969_H3_MASKED_CONTEXT_PARALLEL_BACKENDS = frozenset ({AttentionBackendName .NATIVE , AttentionBackendName ._NATIVE_CUDNN })
7070
7171
72+ def _linear_compute_dtype (linear : nn .Module ) -> torch .dtype :
73+ compute_dtype = getattr (linear , "compute_dtype" , None )
74+ if isinstance (compute_dtype , torch .dtype ):
75+ return compute_dtype
76+ weight = linear .weight
77+ dequantizer = getattr (weight , "sdnq_dequantizer" , None )
78+ result_dtype = getattr (dequantizer , "result_dtype" , None )
79+ return result_dtype if isinstance (result_dtype , torch .dtype ) else weight .dtype
80+
81+
7282class _MiniMaxH3AllGather (torch .autograd .Function ):
7383 """Gather sequence shards without PyTorch's unsupported NCCL coalesced path."""
7484
@@ -440,7 +450,13 @@ def _infer_minimax_h3_config_from_checkpoint(checkpoint) -> dict[str, Any]:
440450 audio_weight = _get_checkpoint_tensor (checkpoint , "audio_proj_in.weight" )
441451 context_weight = _get_checkpoint_tensor (checkpoint , "context_embedder.weight" )
442452 q_norm_weight = _get_checkpoint_tensor (checkpoint , "transformer_blocks.0.attn.norm_q.weight" )
443- q_weight = _get_checkpoint_tensor (checkpoint , "transformer_blocks.0.attn.to_q.weight" )
453+ if "transformer_blocks.0.attn.to_q.weight" in raw_keys :
454+ q_output_dim = _get_checkpoint_tensor (checkpoint , "transformer_blocks.0.attn.to_q.weight" ).shape [0 ]
455+ else :
456+ qkv_weight = _get_checkpoint_tensor (checkpoint , "blocks.0.attn.qkv_proj.weight" )
457+ if qkv_weight .shape [0 ] % 3 != 0 :
458+ raise RuntimeError ("MiniMax-H3 fused QKV tensor blocks.0.attn.qkv_proj.weight cannot be split into q/k/v" )
459+ q_output_dim = qkv_weight .shape [0 ] // 3
444460 ffn_weight = _get_checkpoint_tensor (checkpoint , "transformer_blocks.0.ff.net.0.proj.weight" )
445461 has_adaln_curve = "adaln_t_table" in raw_keys
446462 adaln_curve_table = _get_checkpoint_tensor (checkpoint , "adaln_t_table" ) if has_adaln_curve else None
@@ -456,7 +472,7 @@ def _infer_minimax_h3_config_from_checkpoint(checkpoint) -> dict[str, Any]:
456472 "audio_in_channels" : audio_weight .shape [1 ],
457473 "text_dim" : context_weight .shape [1 ],
458474 "attention_head_dim" : q_norm_weight .shape [0 ],
459- "num_attention_heads" : q_weight . shape [ 0 ] // q_norm_weight .shape [0 ],
475+ "num_attention_heads" : q_output_dim // q_norm_weight .shape [0 ],
460476 "freq_dim" : time_in .shape [1 ] if time_in is not None else 256 ,
461477 "time_embed_hidden_dim" : time_in .shape [0 ] if time_in is not None else 5376 ,
462478 "time_embed_dim" : adaln_curve_table .shape [1 ] if has_adaln_curve else time_out .shape [0 ],
@@ -631,7 +647,7 @@ def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
631647 # The activation runs at `temb`'s own precision and only the projection input is aligned to the projection
632648 # weight. Every block reads the same `temb`, so early rounding biases every block's modulation coherently.
633649 temb = nn .functional .silu (temb ) if self .apply_silu else temb
634- temb = self .linear (temb .to (self .linear . weight . dtype ))
650+ temb = self .linear (temb .to (_linear_compute_dtype ( self .linear ) ))
635651 temb = temb .view (- 1 , 6 * self .hidden_size )
636652 return temb .chunk (6 , dim = - 1 )
637653
@@ -661,7 +677,7 @@ def forward(
661677 ) -> torch .Tensor :
662678 # As in `MiniMaxH3AdaLayerNormModulation`: activate at `temb`'s precision, cast to the projection's dtype after.
663679 temb = nn .functional .silu (temb ) if self .apply_silu else temb
664- shift , scale = self .linear (temb .to (self .linear . weight . dtype )).chunk (2 , dim = - 1 )
680+ shift , scale = self .linear (temb .to (_linear_compute_dtype ( self .linear ) )).chunk (2 , dim = - 1 )
665681 activation_dtype = hidden_states .dtype
666682 hidden_states = self .norm (hidden_states )
667683 shift = _select_modulation (shift , timestep_indices ).to (dtype = activation_dtype )
@@ -1522,7 +1538,7 @@ def _time_embedding(
15221538 temb = blend_flowmap_embeddings (temb , delta_temb , self .flowmap_delta_emb_gate )
15231539 return temb
15241540
1525- dtype = self .time_embedder .linear_1 . weight . dtype
1541+ dtype = _linear_compute_dtype ( self .time_embedder .linear_1 )
15261542 temb = flowmap_timestep_embedding (
15271543 time_proj = self .time_proj ,
15281544 timestep_embedder = self .time_embedder ,
@@ -1857,21 +1873,20 @@ def from_single_file(
18571873 result_dtype = torch_dtype or torch .bfloat16 ,
18581874 hadamard_group_size = hadamard_group_size ,
18591875 )
1860- if len (hadamard_group_sizes ) != 1 :
1861- raise RuntimeError (
1862- f"MiniMax-H3 ConvRot checkpoint uses multiple Hadamard group sizes: { sorted (hadamard_group_sizes )} "
1863- )
1864- group_size = hadamard_group_sizes .pop ()
18651876 model .quantization_method = "minimax_h3_comfy_convrot_sdnq"
18661877 model .quantization_config = {
18671878 "quant_method" : "sdnq_training" ,
18681879 "weights_dtype" : "int8" ,
18691880 "quantized_matmul_dtype" : "int8" ,
18701881 "use_hadamard" : True ,
1871- "hadamard_group_size" : group_size ,
18721882 "group_size" : - 1 ,
18731883 "source_format" : "comfy_minimax_h3_convrot" ,
18741884 }
1885+ sorted_group_sizes = sorted (hadamard_group_sizes )
1886+ if len (sorted_group_sizes ) == 1 :
1887+ model .quantization_config ["hadamard_group_size" ] = sorted_group_sizes [0 ]
1888+ else :
1889+ model .quantization_config ["hadamard_group_sizes" ] = sorted_group_sizes
18751890 elif fp8_state_dict :
18761891 model .quantization_method = "minimax_h3_comfy_fp8"
18771892 model .quantization_config = {
@@ -2022,9 +2037,9 @@ def forward(
20222037 # mixed-precision (the two patch projections are float32 while `context_embedder` and the block stack are
20232038 # bfloat16 — see `_keep_in_fp32_modules`), so every input is aligned with its projection's parameter dtype,
20242039 # mirroring the reference's explicit casts. The text stream sets the dtype of the packed sequence.
2025- video_embeds = self .proj_in (hidden_states .to (self .proj_in . weight . dtype ))
2026- audio_embeds = self .audio_proj_in (audio_hidden_states .to (self .audio_proj_in . weight . dtype ))
2027- text_embeds = self .context_embedder (encoder_hidden_states .to (self .context_embedder . weight . dtype ))
2040+ video_embeds = self .proj_in (hidden_states .to (_linear_compute_dtype ( self .proj_in ) ))
2041+ audio_embeds = self .audio_proj_in (audio_hidden_states .to (_linear_compute_dtype ( self .audio_proj_in ) ))
2042+ text_embeds = self .context_embedder (encoder_hidden_states .to (_linear_compute_dtype ( self .context_embedder ) ))
20282043 self .token_refiner .gradient_checkpointing = self .gradient_checkpointing
20292044 text_attention_mask = None
20302045 if packed_valid_mask is not None :
@@ -2375,7 +2390,7 @@ def run_checkpointed_block(
23752390 # 5. Both heads run over every row, then the rows of each modality are selected. The heads are listed in
23762391 # `_keep_in_fp32_modules`, so they stay float32 while the block stack runs in the requested `torch_dtype`;
23772392 # align the activation with their parameter dtype.
2378- hidden_states = self .norm_out (hidden_states , temb , timestep_indices ).to (self .proj_out . weight . dtype )
2393+ hidden_states = self .norm_out (hidden_states , temb , timestep_indices ).to (_linear_compute_dtype ( self .proj_out ) )
23792394 video_output = _gather_h3_context_parallel_output (self .proj_out (hidden_states ), cp_config , dim = 1 ).index_select (
23802395 1 , video_indices .to (hidden_states .device )
23812396 )
0 commit comments