11from functools import partial
22
33import torch
4+ from loguru import logger
45
56from lightx2v .common .transformer_infer .transformer_infer import BaseTransformerInfer
67from lightx2v .utils .envs import *
1011from .triton_ops import fuse_scale_shift_kernel
1112from .utils import apply_wan_rope_with_chunk , apply_wan_rope_with_flashinfer , apply_wan_rope_with_torch , apply_wan_rope_with_torch_naive
1213
14+ try :
15+ from lightx2v_kernel .gemm import (
16+ cutlass_scaled_mxfp8_mm ,
17+ cutlass_scaled_mxfp8_mm_residual_gate ,
18+ scaled_mxfp8_gelu_quant ,
19+ scaled_mxfp8_modulate_quant ,
20+ )
21+ _WAN_MXFP8_FFN_IMPORT_ERROR = None
22+ except Exception as exc :
23+ cutlass_scaled_mxfp8_mm , cutlass_scaled_mxfp8_mm_residual_gate = None , None
24+ scaled_mxfp8_gelu_quant = None
25+ scaled_mxfp8_modulate_quant = None
26+ _WAN_MXFP8_FFN_IMPORT_ERROR = exc
27+
1328torch_device_module = getattr (torch , AI_DEVICE )
1429
1530
@@ -75,6 +90,172 @@ def rope_wrapper(xq, xk, cos_sin_cache):
7590
7691 self .cos_sin = None
7792
93+ self ._mxfp8_fuse_available = self ._probe_mxfp8_fuse_availability ()
94+
95+ def _probe_mxfp8_fuse_availability (self ):
96+ """Probe once whether MXFP8 fused ops can run on this device.
97+
98+ Returns False (with a warning) if the kernel is unavailable or the GPU
99+ is not SM120/SM120a, so the inference falls back to the non-fused path.
100+ """
101+ if self .config .get ("dit_quant_scheme" , "Default" ) != "mxfp8" :
102+ return False
103+ if not torch .cuda .is_available ():
104+ logger .warning ("MXFP8 fused ops require a CUDA device, falling back to non-fused path" )
105+ return False
106+ if (
107+ cutlass_scaled_mxfp8_mm is None
108+ or cutlass_scaled_mxfp8_mm_residual_gate is None
109+ or scaled_mxfp8_gelu_quant is None
110+ or scaled_mxfp8_modulate_quant is None
111+ ):
112+ detail = (
113+ f": { type (_WAN_MXFP8_FFN_IMPORT_ERROR ).__name__ } : { _WAN_MXFP8_FFN_IMPORT_ERROR } "
114+ if _WAN_MXFP8_FFN_IMPORT_ERROR is not None
115+ else ""
116+ )
117+ logger .warning (f"MXFP8 fused ops unavailable, falling back to non-fused path{ detail } " )
118+ return False
119+ major , minor = torch .cuda .get_device_capability ()
120+ if major != 12 :
121+ logger .warning (f"MXFP8 fused ops require SM120/SM120a, got SM{ major } .{ minor } , falling back to non-fused path" )
122+ return False
123+ return True
124+
125+ def _use_mxfp8_quant_fuse (self ):
126+ return self ._mxfp8_fuse_available
127+
128+ def _ensure_mxfp8_quant_fuse_ready (self , phase , * tensors , module_names = (), required_module_attrs = ("weight" , "weight_scale" , "alpha" )):
129+ if not self ._use_mxfp8_quant_fuse ():
130+ return
131+ for tensor in tensors :
132+ if tensor is None :
133+ continue
134+ if not tensor .is_cuda :
135+ raise RuntimeError ("mxfp8_quant_fuse expects CUDA activations" )
136+ device_tensor = next ((tensor for tensor in tensors if tensor is not None ), None )
137+ if device_tensor is None :
138+ raise RuntimeError ("mxfp8_quant_fuse requires at least one CUDA tensor for device validation" )
139+ major , _minor = torch .cuda .get_device_capability (device_tensor .device )
140+ if major != 12 :
141+ raise RuntimeError ("mxfp8_quant_fuse is only enabled on SM120/SM120a GPUs" )
142+ if (
143+ cutlass_scaled_mxfp8_mm is None
144+ or cutlass_scaled_mxfp8_mm_residual_gate is None
145+ or scaled_mxfp8_gelu_quant is None
146+ or scaled_mxfp8_modulate_quant is None
147+ ):
148+ detail = f": { type (_WAN_MXFP8_FFN_IMPORT_ERROR ).__name__ } : { _WAN_MXFP8_FFN_IMPORT_ERROR } " if _WAN_MXFP8_FFN_IMPORT_ERROR is not None else ""
149+ raise RuntimeError (f"mxfp8_quant_fuse requires lightx2v_kernel with MXFP8 fused quant ops{ detail } " )
150+ for name in module_names :
151+ module = getattr (phase , name )
152+ if getattr (module , "has_lora_branch" , False ) or getattr (module , "has_diff" , False ):
153+ raise RuntimeError (f"mxfp8_quant_fuse does not support active LoRA/diff on { name } " )
154+ if not all (hasattr (module , attr ) for attr in required_module_attrs ):
155+ raise RuntimeError (f"mxfp8_quant_fuse expects { name } to be an MXFP8 quantized weight module" )
156+
157+ def _ensure_mxfp8_quant_ffn_ready (self , phase , norm2_out , residual , c_gate_msa = None , c_scale_msa = None , c_shift_msa = None ):
158+ if not self ._use_mxfp8_quant_fuse ():
159+ return
160+ if (c_scale_msa is None ) != (c_shift_msa is None ):
161+ raise RuntimeError ("MXFP8 FFN modulate-quant readiness requires both c_scale_msa and c_shift_msa" )
162+ extra_tensors = []
163+ self ._ensure_mxfp8_quant_fuse_ready (
164+ phase ,
165+ norm2_out ,
166+ residual ,
167+ c_scale_msa ,
168+ c_shift_msa ,
169+ module_names = ("ffn_0" , "ffn_2" ),
170+ required_module_attrs = ("act_quant_func" , "weight" , "weight_scale" , "alpha" ),
171+ )
172+ if c_gate_msa is None :
173+ raise RuntimeError ("mxfp8_quant_fuse requires c_gate_msa for residual-gate fusion" )
174+ extra_tensors .append (c_gate_msa )
175+ if extra_tensors :
176+ self ._ensure_mxfp8_quant_fuse_ready (phase , * extra_tensors )
177+
178+ def _can_use_mxfp8_modulate_quant (self , norm2_out , c_scale_msa , c_shift_msa ):
179+ if scaled_mxfp8_modulate_quant is None :
180+ return False
181+ if not self ._use_mxfp8_quant_fuse ():
182+ return False
183+ if self .sensitive_layer_dtype != self .infer_dtype :
184+ return False
185+ if norm2_out .dtype != torch .bfloat16 or c_scale_msa .dtype != torch .bfloat16 or c_shift_msa .dtype != torch .bfloat16 :
186+ return False
187+ if not (norm2_out .is_cuda and c_scale_msa .is_cuda and c_shift_msa .is_cuda ):
188+ return False
189+ if norm2_out .device != c_scale_msa .device or norm2_out .device != c_shift_msa .device :
190+ return False
191+ if norm2_out .dim () != 2 or not norm2_out .is_contiguous ():
192+ return False
193+ hidden = norm2_out .shape [1 ]
194+ tokens = norm2_out .shape [0 ]
195+ valid_numel = (hidden , tokens * hidden )
196+ return c_scale_msa .numel () in valid_numel and c_shift_msa .numel () in valid_numel
197+
198+ def _can_reuse_self_attn_mxfp8_quant (self , phase , norm1_out , scale_msa , shift_msa ):
199+ if cutlass_scaled_mxfp8_mm is None :
200+ return False
201+ if not self ._can_use_mxfp8_modulate_quant (norm1_out , scale_msa , shift_msa ):
202+ return False
203+ for name in ("self_attn_q" , "self_attn_k" , "self_attn_v" ):
204+ module = getattr (phase , name )
205+ if getattr (module , "has_lora_branch" , False ) or getattr (module , "has_diff" , False ):
206+ return False
207+ if not all (hasattr (module , attr ) for attr in ("weight" , "weight_scale" , "alpha" )):
208+ return False
209+ return True
210+
211+ def _mxfp8_quant_bias (self , module ):
212+ if hasattr (module , "_get_actual_bias" ):
213+ return module ._get_actual_bias ()
214+ return module .bias if hasattr (module , "bias" ) else None
215+
216+ def _mxfp8_apply (self , module , input_tensor ):
217+ input_tensor_quant , input_tensor_scale = module .act_quant_func (input_tensor )
218+ return self ._mxfp8_apply_quantized (module , input_tensor_quant , input_tensor_scale )
219+
220+ def _mxfp8_apply_quantized (self , module , input_tensor_quant , input_tensor_scale ):
221+ module .alpha = module .alpha .to (module .weight .device )
222+ return cutlass_scaled_mxfp8_mm (
223+ input_tensor_quant ,
224+ module .weight ,
225+ input_tensor_scale ,
226+ module .weight_scale ,
227+ alpha = module .alpha ,
228+ bias = self ._mxfp8_quant_bias (module ),
229+ )
230+
231+ def _mxfp8_apply_residual_gate (self , module , input_tensor , residual , gate ):
232+ input_tensor_quant , input_tensor_scale = module .act_quant_func (input_tensor )
233+ return self ._mxfp8_apply_residual_gate_quantized (module , input_tensor_quant , input_tensor_scale , residual , gate )
234+
235+ def _mxfp8_apply_residual_gate_quantized (self , module , input_tensor_quant , input_tensor_scale , residual , gate ):
236+ module .alpha = module .alpha .to (module .weight .device )
237+ return cutlass_scaled_mxfp8_mm_residual_gate (
238+ input_tensor_quant ,
239+ module .weight ,
240+ input_tensor_scale ,
241+ module .weight_scale ,
242+ alpha = module .alpha ,
243+ residual = residual ,
244+ gate = gate ,
245+ bias = self ._mxfp8_quant_bias (module ),
246+ )
247+
248+ def _infer_ffn_with_mxfp8_quant_fuse (self , phase , norm2_out , residual , c_gate_msa = None , c_scale_msa = None , c_shift_msa = None ):
249+ self ._ensure_mxfp8_quant_ffn_ready (phase , norm2_out , residual , c_gate_msa , c_scale_msa , c_shift_msa )
250+ if c_scale_msa is not None and c_shift_msa is not None and self ._can_use_mxfp8_modulate_quant (norm2_out , c_scale_msa , c_shift_msa ):
251+ norm2_quant , norm2_scale = scaled_mxfp8_modulate_quant (norm2_out , c_scale_msa , c_shift_msa )
252+ y = self ._mxfp8_apply_quantized (phase .ffn_0 , norm2_quant , norm2_scale )
253+ else :
254+ y = self ._mxfp8_apply (phase .ffn_0 , norm2_out )
255+ y_quant , y_scale = scaled_mxfp8_gelu_quant (y )
256+ self ._mxfp8_apply_residual_gate_quantized (phase .ffn_2 , y_quant , y_scale , residual , c_gate_msa .squeeze ())
257+ return None
258+
78259 @torch .no_grad ()
79260 def reset_post_adapter_states (self ):
80261 pass
@@ -149,7 +330,7 @@ def infer_block(self, block, x, pre_infer_out):
149330 y_out ,
150331 gate_msa ,
151332 )
152- y = self .infer_ffn (block .compute_phases [2 ], x , attn_out , c_shift_msa , c_scale_msa )
333+ y = self .infer_ffn (block .compute_phases [2 ], x , attn_out , c_shift_msa , c_scale_msa , c_gate_msa )
153334 x = self .post_process (x , y , c_gate_msa , pre_infer_out )
154335 if hasattr (block .compute_phases [2 ], "after_proj" ):
155336 pre_infer_out .adapter_args ["hints" ].append (block .compute_phases [2 ].after_proj .apply (x ))
@@ -175,6 +356,8 @@ def pre_process(self, modulation, embed0):
175356
176357 def infer_self_attn (self , phase , x , shift_msa , scale_msa ):
177358 cos_sin = self .cos_sin
359+ norm1_quant = None
360+ norm1_scale = None
178361 if hasattr (phase , "smooth_norm1_weight" ):
179362 norm1_weight = (1 + scale_msa .squeeze ()) * phase .smooth_norm1_weight .tensor
180363 norm1_bias = shift_msa .squeeze () * phase .smooth_norm1_bias .tensor
@@ -186,22 +369,40 @@ def infer_self_attn(self, phase, x, shift_msa, scale_msa):
186369 norm1_out = phase .norm1 .apply (x )
187370 if self .sensitive_layer_dtype != self .infer_dtype :
188371 norm1_out = norm1_out .to (self .sensitive_layer_dtype )
189- norm1_out = self .modulate_func (norm1_out , scale = scale_msa , shift = shift_msa ).squeeze ()
372+ if self ._use_mxfp8_quant_fuse ():
373+ self ._ensure_mxfp8_quant_fuse_ready (
374+ phase ,
375+ norm1_out ,
376+ scale_msa ,
377+ shift_msa ,
378+ module_names = ("self_attn_q" , "self_attn_k" , "self_attn_v" ),
379+ )
380+ if self ._can_reuse_self_attn_mxfp8_quant (phase , norm1_out , scale_msa , shift_msa ):
381+ norm1_quant , norm1_scale = scaled_mxfp8_modulate_quant (norm1_out , scale_msa , shift_msa )
382+ else :
383+ norm1_out = self .modulate_func (norm1_out , scale = scale_msa , shift = shift_msa ).squeeze ()
190384
191385 if self .sensitive_layer_dtype != self .infer_dtype :
192386 norm1_out = norm1_out .to (self .infer_dtype )
193387
194388 s , n , d = * norm1_out .shape [:1 ], self .num_heads , self .head_dim
195- q = phase .self_attn_norm_q .apply (phase .self_attn_q .apply (norm1_out )).view (s , n , d )
196- k = phase .self_attn_norm_k .apply (phase .self_attn_k .apply (norm1_out )).view (s , n , d )
197- v = phase .self_attn_v .apply (norm1_out ).view (s , n , d )
389+ if norm1_quant is not None :
390+ q = phase .self_attn_norm_q .apply (self ._mxfp8_apply_quantized (phase .self_attn_q , norm1_quant , norm1_scale )).view (s , n , d )
391+ k = phase .self_attn_norm_k .apply (self ._mxfp8_apply_quantized (phase .self_attn_k , norm1_quant , norm1_scale )).view (s , n , d )
392+ v = self ._mxfp8_apply_quantized (phase .self_attn_v , norm1_quant , norm1_scale ).view (s , n , d )
393+ else :
394+ q = phase .self_attn_norm_q .apply (phase .self_attn_q .apply (norm1_out )).view (s , n , d )
395+ k = phase .self_attn_norm_k .apply (phase .self_attn_k .apply (norm1_out )).view (s , n , d )
396+ v = phase .self_attn_v .apply (norm1_out ).view (s , n , d )
198397 q , k = self .apply_rope_func (q , k , cos_sin )
199398 img_qkv_len = q .shape [0 ]
200399 if self .self_attn_cu_seqlens_qkv is None :
201400 self .self_attn_cu_seqlens_qkv = torch .tensor ([0 , q .shape [0 ]]).cumsum (0 , dtype = torch .int32 )
202401
203402 if self .clean_cuda_cache :
204403 del norm1_out , shift_msa , scale_msa
404+ if norm1_quant is not None :
405+ del norm1_quant , norm1_scale
205406 torch_device_module .empty_cache ()
206407
207408 attn_running_args = {
@@ -310,13 +511,15 @@ def infer_cross_attn(self, phase, x, context, y_out, gate_msa):
310511 torch_device_module .empty_cache ()
311512 return x , attn_out
312513
313- def infer_ffn (self , phase , x , attn_out , c_shift_msa , c_scale_msa ):
514+ def infer_ffn (self , phase , x , attn_out , c_shift_msa , c_scale_msa , c_gate_msa = None ):
314515 x .add_ (attn_out )
315516
316517 if self .clean_cuda_cache :
317518 del attn_out
318519 torch_device_module .empty_cache ()
319520
521+ mxfp8_modulate_scale = None
522+ mxfp8_modulate_shift = None
320523 if hasattr (phase , "smooth_norm2_weight" ):
321524 norm2_weight = (1 + c_scale_msa .squeeze ()) * phase .smooth_norm2_weight .tensor
322525 norm2_bias = c_shift_msa .squeeze () * phase .smooth_norm2_bias .tensor
@@ -328,11 +531,27 @@ def infer_ffn(self, phase, x, attn_out, c_shift_msa, c_scale_msa):
328531 norm2_out = phase .norm2 .apply (x )
329532 if self .sensitive_layer_dtype != self .infer_dtype :
330533 norm2_out = norm2_out .to (self .sensitive_layer_dtype )
331- norm2_out = self .modulate_func (norm2_out , scale = c_scale_msa , shift = c_shift_msa ).squeeze ()
534+ if self ._use_mxfp8_quant_fuse ():
535+ self ._ensure_mxfp8_quant_ffn_ready (phase , norm2_out , x , c_gate_msa , c_scale_msa , c_shift_msa )
536+ if self ._can_use_mxfp8_modulate_quant (norm2_out , c_scale_msa , c_shift_msa ):
537+ mxfp8_modulate_scale = c_scale_msa
538+ mxfp8_modulate_shift = c_shift_msa
539+ else :
540+ norm2_out = self .modulate_func (norm2_out , scale = c_scale_msa , shift = c_shift_msa ).squeeze ()
332541
333542 if self .sensitive_layer_dtype != self .infer_dtype :
334543 norm2_out = norm2_out .to (self .infer_dtype )
335544
545+ if self ._use_mxfp8_quant_fuse ():
546+ return self ._infer_ffn_with_mxfp8_quant_fuse (
547+ phase ,
548+ norm2_out ,
549+ x ,
550+ c_gate_msa ,
551+ c_scale_msa = mxfp8_modulate_scale ,
552+ c_shift_msa = mxfp8_modulate_shift ,
553+ )
554+
336555 y = phase .ffn_0 .apply (norm2_out )
337556 if self .clean_cuda_cache :
338557 del norm2_out , x
@@ -345,6 +564,8 @@ def infer_ffn(self, phase, x, attn_out, c_shift_msa, c_scale_msa):
345564 return y
346565
347566 def post_process (self , x , y , c_gate_msa , pre_infer_out = None ):
567+ if y is None :
568+ return x
348569 if self .sensitive_layer_dtype != self .infer_dtype :
349570 x = x .to (self .sensitive_layer_dtype ) + y .to (self .sensitive_layer_dtype ) * c_gate_msa .squeeze ()
350571 else :
0 commit comments