From 74146ff3c00bd1fa3943a8daa2aa86ea51adec0c Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 14:38:07 +0800 Subject: [PATCH 01/11] feat(npu): Add 4 Ascend NPU custom operators for sparse video inference --- lightx2v_platform/ops/__init__.py | 2 + .../attn/ascend_npu/rainfusion_blockwise.py | 270 ++++++++++++++++++ .../ops/norm/ascend_npu/__init__.py | 4 + .../ops/norm/ascend_npu/npu_layer_norm.py | 36 +++ .../ops/norm/ascend_npu/npu_rms_norm.py | 28 ++ .../ops/rope/ascend_npu/__init__.py | 3 + .../ops/rope/ascend_npu/npu_rope.py | 80 ++++++ 7 files changed, 423 insertions(+) create mode 100644 lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py create mode 100644 lightx2v_platform/ops/norm/ascend_npu/__init__.py create mode 100644 lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py create mode 100644 lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py create mode 100644 lightx2v_platform/ops/rope/ascend_npu/__init__.py create mode 100644 lightx2v_platform/ops/rope/ascend_npu/npu_rope.py diff --git a/lightx2v_platform/ops/__init__.py b/lightx2v_platform/ops/__init__.py index b56ad794a..163b91baa 100755 --- a/lightx2v_platform/ops/__init__.py +++ b/lightx2v_platform/ops/__init__.py @@ -15,6 +15,8 @@ elif PLATFORM == "ascend_npu": from .attn.ascend_npu import * from .mm.ascend_npu import * + from .norm.ascend_npu import * + from .rope.ascend_npu import * elif PLATFORM == "metax_cuda": from .attn.metax_cuda import * elif PLATFORM == "enflame_gcu": diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py new file mode 100644 index 000000000..382404300 --- /dev/null +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -0,0 +1,270 @@ +import os +import math +import torch +import torch_npu +import torch.nn as nn +import torch.nn.functional as F +from einops import rearrange +import time +import mindiesd +from mindiesd.layers.flash_attn.attention_forward import attention_forward + + +class Rainfusion_blockwise(nn.Module): + def __init__( + self, + grid_size: list, + pool_size: int = 128, + sparsity: float = 0.9, + skip_timesteps: int = 0, + txt_len: int = 0, + txt_first: bool = False, + ) -> None: + """ + 参数: + grid_size (list): latents的THW网格大小。 + sparsity (float, optional): 稀疏度, 取值范围[0, 1],默认为 0.50。 + """ + super().__init__() + + # Rainfusion_param + self.grid_size = grid_size + self.frame_num = self.grid_size[0] + self.num_tokens_per_frame = self.grid_size[1] * self.grid_size[2] + self.first_frame_len = self.num_tokens_per_frame + + self.pool_size = pool_size + self.sparsity = sparsity + self.skip_timesteps = skip_timesteps + self.text_len = txt_len + self.txt_first = txt_first + + @staticmethod + def get_grid_size(latent_size, patch_size): + t, h, w = latent_size[-3:] + return [t // patch_size[0], h // patch_size[1], w // patch_size[2]] + + def avgpool(self, input_tensor, pool_size=128): # BSND in, BSND out + batch, seqlen, headnum, dim = input_tensor.shape + + num_full_blocks = seqlen // pool_size + tail_size = seqlen % pool_size + + if num_full_blocks > 0: + full_blocks = input_tensor[:, :num_full_blocks * pool_size, :, :] + full_blocks_reshaped = full_blocks.view(batch, num_full_blocks, pool_size, headnum, dim) + full_pooled = full_blocks_reshaped.mean(dim=2) + else: + full_pooled = torch.empty(0, device=input_tensor.device) + if tail_size > 0: + tail_block = input_tensor[:, num_full_blocks * pool_size:, :, :] + tail_reshaped = tail_block.view(batch, 1, tail_size, headnum, dim) + tail_pooled = tail_reshaped.mean(dim=2) + else: + tail_pooled = torch.empty(0, device=input_tensor.device) + + if num_full_blocks > 0 and tail_size > 0: + output_tensor = torch.cat([full_pooled, tail_pooled], dim=1) + elif num_full_blocks > 0: + output_tensor = full_pooled + else: + output_tensor = tail_pooled + + return output_tensor + + def get_mask_index(self, mask): + B, N, S, _ = mask.shape + device = mask.device + + # 1. 重塑维度 → (B*N)×S×S + mask_reshaped = mask.reshape(-1, S, S) + batch_size = mask_reshaped.shape[0] + + # 2. 生成行索引 标记False位置为S(大于所有有效索引) + row_indices = torch.arange(S, device=device).expand(batch_size, S, -1) # (B*N, S, S) + sorted_vals = torch.where(mask_reshaped, row_indices, 1e9).to(torch.float32) + sorted_vals, _ = torch.sort(sorted_vals, dim=-1) + valid_count = mask_reshaped.sum(dim=-1, keepdim=True) # 每行True的个数 + keep_mask = row_indices < valid_count # 前valid_count个位置保留索引,其余填-1 + result = torch.where(keep_mask, sorted_vals, -1) + + pos_matrix = result.reshape(B, N, S, S).to(torch.int64) + return pos_matrix + + def get_blockwise_mask(self, score_matrix, sparsity): + batch_size, num_heads, rows, cols = score_matrix.shape + + keep_len = math.ceil(cols * (1 - sparsity)) + topk_values, _ = torch.topk(score_matrix, k=keep_len, dim=-1) + thresholds = topk_values[..., -1:] + mask = score_matrix >= thresholds + + protect_len = (self.first_frame_len + self.text_len + self.pool_size - 1) // self.pool_size + + if protect_len > 0: + mask[:, :, -protect_len:, :] = True + mask[:, :, :, -protect_len:] = True + + selectIdx = self.get_mask_index(mask) + selectIdx = selectIdx[0].transpose(0, 1) + selectNumIdx = mask[0].transpose(0, 1).sum(dim=-1) + return selectIdx, selectNumIdx + + def rearrange_with_remaining(self, tensor): # BSND in , BSND out + b, s, n, d = tensor.shape + h = self.grid_size[1] + w = self.grid_size[2] + h_res_len, w_res_len = 0, 0 + first_frame_num = self.first_frame_len // h // w + + tensor_hwt = rearrange(tensor, 'b (f h w) n d -> (b n) f h w d', f=self.frame_num - first_frame_num, h=h, w=w) + if h % 8 != 0: + tensor_hwt, tensor_h_r = torch.split(tensor_hwt, h - (h % 8), dim=2) + tensor_h_r = tensor_h_r.reshape(b * n, -1, d) + h_res_len = tensor_h_r.shape[1] + if w % 8 != 0: + tensor_hwt, tensor_w_r = torch.split(tensor_hwt, w - (w % 8), dim=3) + tensor_w_r = tensor_w_r.reshape(b * n, -1, d) + w_res_len = tensor_w_r.shape[1] + tensor_hwt = rearrange(tensor_hwt, 'b (fn fb) (hn hb) (wn wb) d -> b (fn hn wn fb hb wb) d', + fn=self.frame_num // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + if h % 8 != 0: + tensor_hwt = torch.cat((tensor_hwt, tensor_h_r), dim=1) + if w % 8 != 0: + tensor_hwt = torch.cat((tensor_hwt, tensor_w_r), dim=1) + tensor_hwt = rearrange(tensor_hwt, '(b n) s d -> b s n d', b=b, n=n) + return tensor_hwt, h_res_len, w_res_len + + def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in , BSND out + b, s, n, d = tensor.shape + h = self.grid_size[1] + w = self.grid_size[2] + h_sr, w_sr = h % 8, w % 8 + + tensor = rearrange(tensor, 'b s n d->(b n) s d', b=b, n=n) + tensor_hwt, tensor_h, tensor_w = torch.split(tensor, [s - h_res_len - w_res_len, h_res_len, w_res_len], dim=1) + tensor_hwt = rearrange(tensor_hwt, 'b (fn hn wn fb hb wb) d -> b (fn fb) (hn hb) (wn wb) d', + fn=self.frame_num // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + if w_res_len != 0: + tensor_w = tensor_w.reshape(b * n, self.frame_num - 1, h - h_sr, w_sr, d) + tensor_hwt = torch.cat((tensor_hwt, tensor_w), dim=3) + if h_sr != 0: + tensor_h = tensor_h.reshape(b * n, self.frame_num - 1, h_sr, w, d) + tensor_hwt = torch.cat((tensor_hwt, tensor_h), dim=2) + tensor_hwt = tensor_hwt.reshape(b * n, -1, d) + tensor_hwt = rearrange(tensor_hwt, '(b n) s d -> b s n d', b=b, n=n) + return tensor_hwt + + def do_tensor_rearrange_pooling(self, tensor): # BSND in , BSND out + b, s, n, d = tensor.shape + if self.txt_first: + tensor_t, tensor_f, tensor_i = torch.split(tensor, [self.text_len, self.first_frame_len, + s - self.text_len - self.first_frame_len], dim=1) + else: + tensor_f, tensor_i, tensor_t = torch.split(tensor, + [self.first_frame_len, s - self.text_len - self.first_frame_len, + self.text_len], dim=1) + tensor_i_2, h_res_len, w_res_len = self.rearrange_with_remaining(tensor_i) + tensor = torch.concat((tensor_i_2, tensor_f, tensor_t), dim=1) + tensor_pool = self.avgpool(tensor, pool_size=128) + return tensor, tensor_pool, h_res_len, w_res_len + + def do_tensor_inv_rearrange(self, tensor, h_res_len, w_res_len): + b, s, n, d = tensor.shape + tensor_i, tensor_f, tensor_t = torch.split(tensor, + [s - self.text_len - self.first_frame_len, self.first_frame_len, + self.text_len], dim=1) + tensor_i = self.inv_rearrange_with_remaining(tensor_i, h_res_len, w_res_len) + tensor = torch.concat((tensor_t, tensor_i), dim=1) + + if self.txt_first: + tensor = torch.concat((tensor_t, tensor_f, tensor_i), dim=1) + else: + tensor = torch.concat((tensor_f, tensor_i, tensor_t), dim=1) + return tensor + + def do_tensor_pooling(self, tensor): + tensor_t = tensor[:, :self.text_len, :, :] + tensor_i = tensor[:, self.text_len:, :, :] + + tensor_i_pool = self.avgpool(tensor_i, pool_size=128) + tensor_t_pool = self.avgpool(tensor_t, pool_size=128) + + tensor_pool = torch.concat((tensor_t_pool, tensor_i_pool), dim=1) + return tensor_pool + + def forward( + self, + q, # BSND + k, + v, + t_b_idx, + base_blockmask, + ): + t_idx = t_b_idx[0] + b_idx = t_b_idx[1] + device = q.device + + if t_idx < self.skip_timesteps: + base_blockmask = None + x = attention_forward(q, k, v, + opt_mode="manual", op_type="ascend_laser_attention", layout="BNSD") + else: + batch, qSeqlen, numHeads, headDim = q.shape + _, kvSeqlen, _, _ = k.shape + blockShapeX, blockShapeY = self.pool_size, self.pool_size + scale = headDim ** -0.5 + + totalQTokens = batch * qSeqlen + totalKvTokens = batch * kvSeqlen + qBlockNum = math.ceil(qSeqlen / blockShapeX) + kvBlockNum = math.ceil(kvSeqlen / blockShapeY) + totalQBlocks = qBlockNum + maxKvBlockNum = kvBlockNum + blockShape = [blockShapeX, blockShapeY] + actualSeqLengthsHost = [qSeqlen for _ in range(batch)] + actualSeqLengthsKvHost = [kvSeqlen for _ in range(batch)] + + sparsity = self.sparsity + + h_res_len, w_res_len = 0, 0 + if base_blockmask is None: + qkv = torch.cat((q, k, v), dim=0) + qkv, qkv_pool, h_res_len, w_res_len = self.do_tensor_rearrange_pooling(qkv) + q, k, v = torch.chunk(qkv, 3, dim=0) + # qkv_pool = self.do_tensor_pooling(qkv) + query_pool, key_pool, value_pool = torch.chunk(qkv_pool, 3, dim=0) + + attn_scores_head = torch.einsum("blnd,bsnd->bnls", query_pool, key_pool) * scale + attn_scores_fake = torch.nn.functional.softmax(attn_scores_head, dim=-1) + + selectIdx, selectNumIdx = self.get_blockwise_mask(attn_scores_fake, sparsity) + base_blockmask = [selectIdx, selectNumIdx] + else: + selectIdx = base_blockmask[0] + selectNumIdx = base_blockmask[1] + qkv = torch.cat((q, k, v), dim=0) + qkv, qkv_pool, h_res_len, w_res_len = self.do_tensor_rearrange_pooling(qkv) + q, k, v = torch.chunk(qkv, 3, dim=0) + + q_bnsd = q.transpose(1, 2) + k_bnsd = k.transpose(1, 2) + v_bnsd = v.transpose(1, 2) + x = mindiesd.layers.flash_attn.sparse_flash_attn_rf_v2.rain_fusion_attention( + q_bnsd, k_bnsd, v_bnsd, + scale=scale, + head_num=numHeads, + input_layout="BNSD", + select_idx=selectIdx, + select_num_idx=selectNumIdx, + blockshape=blockShape, + actual_seq_lengths=actualSeqLengthsHost, + actual_seq_lengths_kv=actualSeqLengthsKvHost + ) + + x = x.transpose(1, 2).view(batch, qSeqlen, numHeads, headDim) + + x = self.do_tensor_inv_rearrange(x, h_res_len, w_res_len) + base_blockmask = None + + return x, base_blockmask \ No newline at end of file diff --git a/lightx2v_platform/ops/norm/ascend_npu/__init__.py b/lightx2v_platform/ops/norm/ascend_npu/__init__.py new file mode 100644 index 000000000..e522b460b --- /dev/null +++ b/lightx2v_platform/ops/norm/ascend_npu/__init__.py @@ -0,0 +1,4 @@ +from .npu_rms_norm import NpuRmsNormWeight +from .npu_layer_norm import NpuLayerNormWeight + +__all__ = ["NpuRmsNormWeight", "NpuLayerNormWeight"] \ No newline at end of file diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py new file mode 100644 index 000000000..871c5d28f --- /dev/null +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py @@ -0,0 +1,36 @@ +import torch + +from lightx2v_platform.ops.norm.norm_template import LayerNormWeightTemplate +from lightx2v_platform.registry_factory import PLATFORM_LAYERNORM_WEIGHT_REGISTER + +try: + import torch_npu +except ImportError: + torch_npu = None + + +@PLATFORM_LAYERNORM_WEIGHT_REGISTER("npu_layer_norm") +class NpuLayerNormWeight(LayerNormWeightTemplate): + def __init__(self, weight_name=None, bias_name=None, create_cuda_buffer=False, create_cpu_buffer=False, lazy_load=False, lazy_load_file=None, is_post_adapter=False, eps=1e-6, **kwargs): + super().__init__(weight_name, bias_name, create_cuda_buffer, create_cpu_buffer, lazy_load, lazy_load_file, is_post_adapter, eps) + + def apply(self, input_tensor): + if torch_npu is not None and hasattr(torch_npu, 'npu_layer_norm'): + out = torch_npu.npu_layer_norm( + input_tensor, + (input_tensor.shape[-1],), + self.weight, + self.bias, + self.eps, + ) + if isinstance(out, tuple): + out = out[0] + return out + output = torch.nn.functional.layer_norm( + input_tensor, + (input_tensor.shape[-1],), + self.weight, + self.bias, + self.eps, + ) + return output \ No newline at end of file diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py new file mode 100644 index 000000000..bf01fefc7 --- /dev/null +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py @@ -0,0 +1,28 @@ +import torch + +from lightx2v_platform.ops.norm.norm_template import RMSWeightTemplate +from lightx2v_platform.registry_factory import PLATFORM_RMS_WEIGHT_REGISTER + +try: + import torch_npu +except ImportError: + torch_npu = None + + +@PLATFORM_RMS_WEIGHT_REGISTER("npu_rms_norm") +class NpuRmsNormWeight(RMSWeightTemplate): + def _norm(self, x): + return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps) + + def apply(self, input_tensor): + weight = self.weight + if torch_npu is not None and hasattr(torch_npu, "npu_rms_norm"): + if self.sensitive_layer_dtype != self.infer_dtype: + output_tensor, _ = torch_npu.npu_rms_norm( + input_tensor.float(), weight.float(), self.eps, + ) + return output_tensor.to(self.infer_dtype) + output_tensor, _ = torch_npu.npu_rms_norm(input_tensor, weight, self.eps) + return output_tensor + x = self._norm(input_tensor.float()) + return (x.float() * weight.float()).to(self.infer_dtype) \ No newline at end of file diff --git a/lightx2v_platform/ops/rope/ascend_npu/__init__.py b/lightx2v_platform/ops/rope/ascend_npu/__init__.py new file mode 100644 index 000000000..ba664c3de --- /dev/null +++ b/lightx2v_platform/ops/rope/ascend_npu/__init__.py @@ -0,0 +1,3 @@ +from .npu_rope import NpuRope + +__all__ = ["NpuRope"] diff --git a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py new file mode 100644 index 000000000..2d205eb83 --- /dev/null +++ b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py @@ -0,0 +1,80 @@ +import torch + +from lightx2v_platform.ops.rope.rope_template import GET_DTYPE, RopeTemplate +from lightx2v_platform.registry_factory import PLATFORM_ROPE_REGISTER + +try: + import torch_npu +except ImportError: + torch_npu = None + + +def GET_SENSITIVE_DTYPE(): + import os + DTYPE_MAP = { + "BF16": torch.bfloat16, + "FP16": torch.float16, + "FP32": torch.float32, + "bf16": torch.bfloat16, + "fp16": torch.float16, + "fp32": torch.float32, + } + flag = os.getenv("SENSITIVE_LAYER_DTYPE", "None") + if flag == "None": + return GET_DTYPE() + return DTYPE_MAP[flag] + + +@PLATFORM_ROPE_REGISTER("npu_rope") +class NpuRope(RopeTemplate): + def __init__(self): + super().__init__() + self.sensitive_layer_dtype = GET_SENSITIVE_DTYPE() + + def _apply_rope_fp32(self, xq, xk, cos_sin_cache): + n = xq.size(1) + seq_len = cos_sin_cache.size(0) + xq_fp32 = torch.view_as_complex( + xq[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2) + ) + xk_fp32 = torch.view_as_complex( + xk[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2) + ) + xq_rot = torch.view_as_real(xq_fp32 * cos_sin_cache).flatten(2) + xk_rot = torch.view_as_real(xk_fp32 * cos_sin_cache).flatten(2) + if xq.size(0) > seq_len: + xq_rot = torch.cat([xq_rot, xq[seq_len:]], dim=0) + xk_rot = torch.cat([xk_rot, xk[seq_len:]], dim=0) + return xq_rot.to(self.infer_dtype), xk_rot.to(self.infer_dtype) + + def apply(self, xq: torch.Tensor, xk: torch.Tensor, cos_sin_cache: torch.Tensor): + s, n, d = xq.shape + seq_len = cos_sin_cache.size(0) + cos = cos_sin_cache.real + sin = cos_sin_cache.imag + cos = cos.repeat_interleave(2, dim=-1) + sin = sin.repeat_interleave(2, dim=-1) + xq_part = xq[:seq_len] + xk_part = xk[:seq_len] + + if torch_npu is not None and hasattr(torch_npu, "npu_rotary_mul"): + if self.sensitive_layer_dtype != self.infer_dtype: + xq_part = xq_part.float() + xk_part = xk_part.float() + cos = cos.float() if cos.dtype != torch.float32 else cos + sin = sin.float() if sin.dtype != torch.float32 else sin + if not xq_part.is_contiguous(): + xq_part = xq_part.contiguous() + if not xk_part.is_contiguous(): + xk_part = xk_part.contiguous() + xq_rotated = torch_npu.npu_rotary_mul(xq_part, cos, sin, "interleave") + xk_rotated = torch_npu.npu_rotary_mul(xk_part, cos, sin, "interleave") + if s > seq_len: + xq = torch.cat([xq_rotated, xq[seq_len:]], dim=0) + xk = torch.cat([xk_rotated, xk[seq_len:]], dim=0) + else: + xq = xq_rotated + xk = xk_rotated + return xq.to(self.infer_dtype), xk.to(self.infer_dtype) + + return self._apply_rope_fp32(xq, xk, cos_sin_cache) From d49ef69d7dff96399ca3a1f9746f210faf3c195c Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 15:58:18 +0800 Subject: [PATCH 02/11] feat(npu): Add 4 Ascend NPU custom operators for sparse video inference --- .../attn/ascend_npu/rainfusion_blockwise.py | 22 ++++++------------- .../ops/norm/ascend_npu/npu_layer_norm.py | 2 +- .../ops/norm/ascend_npu/npu_rms_norm.py | 2 +- .../ops/rope/ascend_npu/npu_rope.py | 3 +++ 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py index 382404300..c8dd5d027 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -82,13 +82,13 @@ def get_mask_index(self, mask): # 2. 生成行索引 标记False位置为S(大于所有有效索引) row_indices = torch.arange(S, device=device).expand(batch_size, S, -1) # (B*N, S, S) - sorted_vals = torch.where(mask_reshaped, row_indices, 1e9).to(torch.float32) + sorted_vals = torch.where(mask_reshaped, row_indices, S) sorted_vals, _ = torch.sort(sorted_vals, dim=-1) - valid_count = mask_reshaped.sum(dim=-1, keepdim=True) # 每行True的个数 - keep_mask = row_indices < valid_count # 前valid_count个位置保留索引,其余填-1 + valid_count = mask_reshaped.sum(dim=-1, keepdim=True) + keep_mask = row_indices < valid_count result = torch.where(keep_mask, sorted_vals, -1) - pos_matrix = result.reshape(B, N, S, S).to(torch.int64) + pos_matrix = result.reshape(B, N, S, S) return pos_matrix def get_blockwise_mask(self, score_matrix, sparsity): @@ -127,7 +127,7 @@ def rearrange_with_remaining(self, tensor): # BSND in , BSND out tensor_w_r = tensor_w_r.reshape(b * n, -1, d) w_res_len = tensor_w_r.shape[1] tensor_hwt = rearrange(tensor_hwt, 'b (fn fb) (hn hb) (wn wb) d -> b (fn hn wn fb hb wb) d', - fn=self.frame_num // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + fn=(self.frame_num - first_frame_num) // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) if h % 8 != 0: tensor_hwt = torch.cat((tensor_hwt, tensor_h_r), dim=1) if w % 8 != 0: @@ -144,7 +144,7 @@ def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in tensor = rearrange(tensor, 'b s n d->(b n) s d', b=b, n=n) tensor_hwt, tensor_h, tensor_w = torch.split(tensor, [s - h_res_len - w_res_len, h_res_len, w_res_len], dim=1) tensor_hwt = rearrange(tensor_hwt, 'b (fn hn wn fb hb wb) d -> b (fn fb) (hn hb) (wn wb) d', - fn=self.frame_num // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + fn=(self.frame_num - 1) // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) if w_res_len != 0: tensor_w = tensor_w.reshape(b * n, self.frame_num - 1, h - h_sr, w_sr, d) tensor_hwt = torch.cat((tensor_hwt, tensor_w), dim=3) @@ -175,7 +175,6 @@ def do_tensor_inv_rearrange(self, tensor, h_res_len, w_res_len): [s - self.text_len - self.first_frame_len, self.first_frame_len, self.text_len], dim=1) tensor_i = self.inv_rearrange_with_remaining(tensor_i, h_res_len, w_res_len) - tensor = torch.concat((tensor_t, tensor_i), dim=1) if self.txt_first: tensor = torch.concat((tensor_t, tensor_f, tensor_i), dim=1) @@ -202,8 +201,6 @@ def forward( base_blockmask, ): t_idx = t_b_idx[0] - b_idx = t_b_idx[1] - device = q.device if t_idx < self.skip_timesteps: base_blockmask = None @@ -211,16 +208,11 @@ def forward( opt_mode="manual", op_type="ascend_laser_attention", layout="BNSD") else: batch, qSeqlen, numHeads, headDim = q.shape + assert batch == 1, "Rainfusion_blockwise currently only supports batch size 1." _, kvSeqlen, _, _ = k.shape blockShapeX, blockShapeY = self.pool_size, self.pool_size scale = headDim ** -0.5 - totalQTokens = batch * qSeqlen - totalKvTokens = batch * kvSeqlen - qBlockNum = math.ceil(qSeqlen / blockShapeX) - kvBlockNum = math.ceil(kvSeqlen / blockShapeY) - totalQBlocks = qBlockNum - maxKvBlockNum = kvBlockNum blockShape = [blockShapeX, blockShapeY] actualSeqLengthsHost = [qSeqlen for _ in range(batch)] actualSeqLengthsKvHost = [kvSeqlen for _ in range(batch)] diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py index 871c5d28f..465424d61 100644 --- a/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py @@ -15,7 +15,7 @@ def __init__(self, weight_name=None, bias_name=None, create_cuda_buffer=False, c super().__init__(weight_name, bias_name, create_cuda_buffer, create_cpu_buffer, lazy_load, lazy_load_file, is_post_adapter, eps) def apply(self, input_tensor): - if torch_npu is not None and hasattr(torch_npu, 'npu_layer_norm'): + if torch_npu is not None and hasattr(torch_npu, 'npu_layer_norm') and self.weight is not None and self.bias is not None: out = torch_npu.npu_layer_norm( input_tensor, (input_tensor.shape[-1],), diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py index bf01fefc7..2f94674f9 100644 --- a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py @@ -16,7 +16,7 @@ def _norm(self, x): def apply(self, input_tensor): weight = self.weight - if torch_npu is not None and hasattr(torch_npu, "npu_rms_norm"): + if torch_npu is not None and hasattr(torch_npu, "npu_rms_norm") and weight is not None: if self.sensitive_layer_dtype != self.infer_dtype: output_tensor, _ = torch_npu.npu_rms_norm( input_tensor.float(), weight.float(), self.eps, diff --git a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py index 2d205eb83..a4188d4b3 100644 --- a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py +++ b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py @@ -18,6 +18,9 @@ def GET_SENSITIVE_DTYPE(): "bf16": torch.bfloat16, "fp16": torch.float16, "fp32": torch.float32, + "torch.bfloat16": torch.bfloat16, + "torch.float16": torch.float16, + "torch.float32": torch.float32, } flag = os.getenv("SENSITIVE_LAYER_DTYPE", "None") if flag == "None": From 4fab6f61e47823d4c5ffd44f28928c1c88299dd4 Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 16:38:07 +0800 Subject: [PATCH 03/11] feat(npu): Add 4 Ascend NPU custom operators for sparse video inference --- .../ops/attn/ascend_npu/rainfusion_blockwise.py | 16 ++++++++-------- .../ops/norm/ascend_npu/npu_rms_norm.py | 4 +++- .../ops/rope/ascend_npu/npu_rope.py | 10 +++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py index c8dd5d027..7f10b156e 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -126,8 +126,11 @@ def rearrange_with_remaining(self, tensor): # BSND in , BSND out tensor_hwt, tensor_w_r = torch.split(tensor_hwt, w - (w % 8), dim=3) tensor_w_r = tensor_w_r.reshape(b * n, -1, d) w_res_len = tensor_w_r.shape[1] + remaining_frames = self.frame_num - first_frame_num + if remaining_frames % 2 != 0: + raise ValueError(f"The number of remaining frames ({remaining_frames}) must be even to be rearranged with block size 2.") tensor_hwt = rearrange(tensor_hwt, 'b (fn fb) (hn hb) (wn wb) d -> b (fn hn wn fb hb wb) d', - fn=(self.frame_num - first_frame_num) // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + fn=remaining_frames // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) if h % 8 != 0: tensor_hwt = torch.cat((tensor_hwt, tensor_h_r), dim=1) if w % 8 != 0: @@ -220,11 +223,11 @@ def forward( sparsity = self.sparsity h_res_len, w_res_len = 0, 0 + qkv = torch.cat((q, k, v), dim=0) + qkv, qkv_pool, h_res_len, w_res_len = self.do_tensor_rearrange_pooling(qkv) + q, k, v = torch.chunk(qkv, 3, dim=0) + if base_blockmask is None: - qkv = torch.cat((q, k, v), dim=0) - qkv, qkv_pool, h_res_len, w_res_len = self.do_tensor_rearrange_pooling(qkv) - q, k, v = torch.chunk(qkv, 3, dim=0) - # qkv_pool = self.do_tensor_pooling(qkv) query_pool, key_pool, value_pool = torch.chunk(qkv_pool, 3, dim=0) attn_scores_head = torch.einsum("blnd,bsnd->bnls", query_pool, key_pool) * scale @@ -235,9 +238,6 @@ def forward( else: selectIdx = base_blockmask[0] selectNumIdx = base_blockmask[1] - qkv = torch.cat((q, k, v), dim=0) - qkv, qkv_pool, h_res_len, w_res_len = self.do_tensor_rearrange_pooling(qkv) - q, k, v = torch.chunk(qkv, 3, dim=0) q_bnsd = q.transpose(1, 2) k_bnsd = k.transpose(1, 2) diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py index 2f94674f9..fcac7669e 100644 --- a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py @@ -25,4 +25,6 @@ def apply(self, input_tensor): output_tensor, _ = torch_npu.npu_rms_norm(input_tensor, weight, self.eps) return output_tensor x = self._norm(input_tensor.float()) - return (x.float() * weight.float()).to(self.infer_dtype) \ No newline at end of file + if weight is not None: + return (x.float() * weight.float()).to(self.infer_dtype) + return x.to(self.infer_dtype) \ No newline at end of file diff --git a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py index a4188d4b3..ec01dc50e 100644 --- a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py +++ b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py @@ -73,11 +73,11 @@ def apply(self, xq: torch.Tensor, xk: torch.Tensor, cos_sin_cache: torch.Tensor) xq_rotated = torch_npu.npu_rotary_mul(xq_part, cos, sin, "interleave") xk_rotated = torch_npu.npu_rotary_mul(xk_part, cos, sin, "interleave") if s > seq_len: - xq = torch.cat([xq_rotated, xq[seq_len:]], dim=0) - xk = torch.cat([xk_rotated, xk[seq_len:]], dim=0) + xq = torch.cat([xq_rotated.to(self.infer_dtype), xq[seq_len:]], dim=0) + xk = torch.cat([xk_rotated.to(self.infer_dtype), xk[seq_len:]], dim=0) else: - xq = xq_rotated - xk = xk_rotated - return xq.to(self.infer_dtype), xk.to(self.infer_dtype) + xq = xq_rotated.to(self.infer_dtype) + xk = xk_rotated.to(self.infer_dtype) + return xq, xk return self._apply_rope_fp32(xq, xk, cos_sin_cache) From c07b3203ac0e91ea874bdd7393651cd0035050de Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 17:15:16 +0800 Subject: [PATCH 04/11] feat(npu): Add 4 Ascend NPU custom operators for sparse video inference --- .../attn/ascend_npu/rainfusion_blockwise.py | 22 ++++++++----------- .../ops/rope/ascend_npu/npu_rope.py | 4 ++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py index 7f10b156e..d0267f829 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -55,22 +55,15 @@ def avgpool(self, input_tensor, pool_size=128): # BSND in, BSND out full_blocks_reshaped = full_blocks.view(batch, num_full_blocks, pool_size, headnum, dim) full_pooled = full_blocks_reshaped.mean(dim=2) else: - full_pooled = torch.empty(0, device=input_tensor.device) + full_pooled = torch.empty(batch, 0, headnum, dim, device=input_tensor.device) if tail_size > 0: tail_block = input_tensor[:, num_full_blocks * pool_size:, :, :] tail_reshaped = tail_block.view(batch, 1, tail_size, headnum, dim) tail_pooled = tail_reshaped.mean(dim=2) else: - tail_pooled = torch.empty(0, device=input_tensor.device) + tail_pooled = torch.empty(batch, 0, headnum, dim, device=input_tensor.device) - if num_full_blocks > 0 and tail_size > 0: - output_tensor = torch.cat([full_pooled, tail_pooled], dim=1) - elif num_full_blocks > 0: - output_tensor = full_pooled - else: - output_tensor = tail_pooled - - return output_tensor + return torch.cat([full_pooled, tail_pooled], dim=1) def get_mask_index(self, mask): B, N, S, _ = mask.shape @@ -95,6 +88,7 @@ def get_blockwise_mask(self, score_matrix, sparsity): batch_size, num_heads, rows, cols = score_matrix.shape keep_len = math.ceil(cols * (1 - sparsity)) + keep_len = max(1, min(keep_len, cols)) topk_values, _ = torch.topk(score_matrix, k=keep_len, dim=-1) thresholds = topk_values[..., -1:] mask = score_matrix >= thresholds @@ -143,16 +137,18 @@ def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in h = self.grid_size[1] w = self.grid_size[2] h_sr, w_sr = h % 8, w % 8 + first_frame_num = self.first_frame_len // (h * w) + remaining_frames = self.frame_num - first_frame_num tensor = rearrange(tensor, 'b s n d->(b n) s d', b=b, n=n) tensor_hwt, tensor_h, tensor_w = torch.split(tensor, [s - h_res_len - w_res_len, h_res_len, w_res_len], dim=1) tensor_hwt = rearrange(tensor_hwt, 'b (fn hn wn fb hb wb) d -> b (fn fb) (hn hb) (wn wb) d', - fn=(self.frame_num - 1) // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + fn=remaining_frames // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) if w_res_len != 0: - tensor_w = tensor_w.reshape(b * n, self.frame_num - 1, h - h_sr, w_sr, d) + tensor_w = tensor_w.reshape(b * n, remaining_frames, h - h_sr, w_sr, d) tensor_hwt = torch.cat((tensor_hwt, tensor_w), dim=3) if h_sr != 0: - tensor_h = tensor_h.reshape(b * n, self.frame_num - 1, h_sr, w, d) + tensor_h = tensor_h.reshape(b * n, remaining_frames, h_sr, w, d) tensor_hwt = torch.cat((tensor_hwt, tensor_h), dim=2) tensor_hwt = tensor_hwt.reshape(b * n, -1, d) tensor_hwt = rearrange(tensor_hwt, '(b n) s d -> b s n d', b=b, n=n) diff --git a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py index ec01dc50e..56daaf4fc 100644 --- a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py +++ b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py @@ -1,4 +1,5 @@ import torch +from functools import lru_cache from lightx2v_platform.ops.rope.rope_template import GET_DTYPE, RopeTemplate from lightx2v_platform.registry_factory import PLATFORM_ROPE_REGISTER @@ -9,6 +10,7 @@ torch_npu = None +@lru_cache(maxsize=None) def GET_SENSITIVE_DTYPE(): import os DTYPE_MAP = { @@ -25,6 +27,8 @@ def GET_SENSITIVE_DTYPE(): flag = os.getenv("SENSITIVE_LAYER_DTYPE", "None") if flag == "None": return GET_DTYPE() + if flag not in DTYPE_MAP: + raise ValueError(f"Unsupported SENSITIVE_LAYER_DTYPE: {flag}. Expected one of {list(DTYPE_MAP.keys())}") return DTYPE_MAP[flag] From 8f0d06a67693cf9195434fa54471982c164b27ad Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 17:53:23 +0800 Subject: [PATCH 05/11] feat(npu): Add 4 Ascend NPU custom operators for sparse video inference --- .../attn/ascend_npu/rainfusion_blockwise.py | 95 ++++++++----------- .../ops/norm/ascend_npu/__init__.py | 4 +- .../ops/norm/ascend_npu/npu_layer_norm.py | 15 ++- .../ops/norm/ascend_npu/npu_rms_norm.py | 6 +- .../ops/rope/ascend_npu/npu_rope.py | 20 ++-- 5 files changed, 70 insertions(+), 70 deletions(-) diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py index d0267f829..277132ffe 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -1,24 +1,21 @@ -import os import math + +import mindiesd import torch -import torch_npu import torch.nn as nn -import torch.nn.functional as F from einops import rearrange -import time -import mindiesd from mindiesd.layers.flash_attn.attention_forward import attention_forward class Rainfusion_blockwise(nn.Module): def __init__( - self, - grid_size: list, - pool_size: int = 128, - sparsity: float = 0.9, - skip_timesteps: int = 0, - txt_len: int = 0, - txt_first: bool = False, + self, + grid_size: list, + pool_size: int = 128, + sparsity: float = 0.9, + skip_timesteps: int = 0, + txt_len: int = 0, + txt_first: bool = False, ) -> None: """ 参数: @@ -50,20 +47,17 @@ def avgpool(self, input_tensor, pool_size=128): # BSND in, BSND out num_full_blocks = seqlen // pool_size tail_size = seqlen % pool_size + pooled_tensors = [] if num_full_blocks > 0: - full_blocks = input_tensor[:, :num_full_blocks * pool_size, :, :] + full_blocks = input_tensor[:, : num_full_blocks * pool_size, :, :] full_blocks_reshaped = full_blocks.view(batch, num_full_blocks, pool_size, headnum, dim) - full_pooled = full_blocks_reshaped.mean(dim=2) - else: - full_pooled = torch.empty(batch, 0, headnum, dim, device=input_tensor.device) + pooled_tensors.append(full_blocks_reshaped.mean(dim=2)) if tail_size > 0: - tail_block = input_tensor[:, num_full_blocks * pool_size:, :, :] + tail_block = input_tensor[:, num_full_blocks * pool_size :, :, :] tail_reshaped = tail_block.view(batch, 1, tail_size, headnum, dim) - tail_pooled = tail_reshaped.mean(dim=2) - else: - tail_pooled = torch.empty(batch, 0, headnum, dim, device=input_tensor.device) + pooled_tensors.append(tail_reshaped.mean(dim=2)) - return torch.cat([full_pooled, tail_pooled], dim=1) + return torch.cat(pooled_tensors, dim=1) def get_mask_index(self, mask): B, N, S, _ = mask.shape @@ -74,7 +68,7 @@ def get_mask_index(self, mask): batch_size = mask_reshaped.shape[0] # 2. 生成行索引 标记False位置为S(大于所有有效索引) - row_indices = torch.arange(S, device=device).expand(batch_size, S, -1) # (B*N, S, S) + row_indices = torch.arange(S, device=device).view(1, 1, S).expand(batch_size, S, S) # (B*N, S, S) sorted_vals = torch.where(mask_reshaped, row_indices, S) sorted_vals, _ = torch.sort(sorted_vals, dim=-1) valid_count = mask_reshaped.sum(dim=-1, keepdim=True) @@ -111,7 +105,7 @@ def rearrange_with_remaining(self, tensor): # BSND in , BSND out h_res_len, w_res_len = 0, 0 first_frame_num = self.first_frame_len // h // w - tensor_hwt = rearrange(tensor, 'b (f h w) n d -> (b n) f h w d', f=self.frame_num - first_frame_num, h=h, w=w) + tensor_hwt = rearrange(tensor, "b (f h w) n d -> (b n) f h w d", f=self.frame_num - first_frame_num, h=h, w=w) if h % 8 != 0: tensor_hwt, tensor_h_r = torch.split(tensor_hwt, h - (h % 8), dim=2) tensor_h_r = tensor_h_r.reshape(b * n, -1, d) @@ -123,13 +117,12 @@ def rearrange_with_remaining(self, tensor): # BSND in , BSND out remaining_frames = self.frame_num - first_frame_num if remaining_frames % 2 != 0: raise ValueError(f"The number of remaining frames ({remaining_frames}) must be even to be rearranged with block size 2.") - tensor_hwt = rearrange(tensor_hwt, 'b (fn fb) (hn hb) (wn wb) d -> b (fn hn wn fb hb wb) d', - fn=remaining_frames // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + tensor_hwt = rearrange(tensor_hwt, "bn (fn fb) (hn hb) (wn wb) d -> bn (fn hn wn fb hb wb) d", fn=remaining_frames // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) if h % 8 != 0: tensor_hwt = torch.cat((tensor_hwt, tensor_h_r), dim=1) if w % 8 != 0: tensor_hwt = torch.cat((tensor_hwt, tensor_w_r), dim=1) - tensor_hwt = rearrange(tensor_hwt, '(b n) s d -> b s n d', b=b, n=n) + tensor_hwt = rearrange(tensor_hwt, "(b n) s d -> b s n d", b=b, n=n) return tensor_hwt, h_res_len, w_res_len def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in , BSND out @@ -140,10 +133,9 @@ def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in first_frame_num = self.first_frame_len // (h * w) remaining_frames = self.frame_num - first_frame_num - tensor = rearrange(tensor, 'b s n d->(b n) s d', b=b, n=n) + tensor = rearrange(tensor, "b s n d->(b n) s d", b=b, n=n) tensor_hwt, tensor_h, tensor_w = torch.split(tensor, [s - h_res_len - w_res_len, h_res_len, w_res_len], dim=1) - tensor_hwt = rearrange(tensor_hwt, 'b (fn hn wn fb hb wb) d -> b (fn fb) (hn hb) (wn wb) d', - fn=remaining_frames // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) + tensor_hwt = rearrange(tensor_hwt, "bn (fn hn wn fb hb wb) d -> bn (fn fb) (hn hb) (wn wb) d", fn=remaining_frames // 2, fb=2, hb=8, wb=8, hn=h // 8, wn=w // 8) if w_res_len != 0: tensor_w = tensor_w.reshape(b * n, remaining_frames, h - h_sr, w_sr, d) tensor_hwt = torch.cat((tensor_hwt, tensor_w), dim=3) @@ -151,18 +143,17 @@ def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in tensor_h = tensor_h.reshape(b * n, remaining_frames, h_sr, w, d) tensor_hwt = torch.cat((tensor_hwt, tensor_h), dim=2) tensor_hwt = tensor_hwt.reshape(b * n, -1, d) - tensor_hwt = rearrange(tensor_hwt, '(b n) s d -> b s n d', b=b, n=n) + tensor_hwt = rearrange(tensor_hwt, "(b n) s d -> b s n d", b=b, n=n) return tensor_hwt def do_tensor_rearrange_pooling(self, tensor): # BSND in , BSND out b, s, n, d = tensor.shape + if s < self.text_len + self.first_frame_len: + raise ValueError(f"Sequence length {s} is too small for text_len {self.text_len} and first_frame_len {self.first_frame_len}") if self.txt_first: - tensor_t, tensor_f, tensor_i = torch.split(tensor, [self.text_len, self.first_frame_len, - s - self.text_len - self.first_frame_len], dim=1) + tensor_t, tensor_f, tensor_i = torch.split(tensor, [self.text_len, self.first_frame_len, s - self.text_len - self.first_frame_len], dim=1) else: - tensor_f, tensor_i, tensor_t = torch.split(tensor, - [self.first_frame_len, s - self.text_len - self.first_frame_len, - self.text_len], dim=1) + tensor_f, tensor_i, tensor_t = torch.split(tensor, [self.first_frame_len, s - self.text_len - self.first_frame_len, self.text_len], dim=1) tensor_i_2, h_res_len, w_res_len = self.rearrange_with_remaining(tensor_i) tensor = torch.concat((tensor_i_2, tensor_f, tensor_t), dim=1) tensor_pool = self.avgpool(tensor, pool_size=128) @@ -170,9 +161,7 @@ def do_tensor_rearrange_pooling(self, tensor): # BSND in , BSND out def do_tensor_inv_rearrange(self, tensor, h_res_len, w_res_len): b, s, n, d = tensor.shape - tensor_i, tensor_f, tensor_t = torch.split(tensor, - [s - self.text_len - self.first_frame_len, self.first_frame_len, - self.text_len], dim=1) + tensor_i, tensor_f, tensor_t = torch.split(tensor, [s - self.text_len - self.first_frame_len, self.first_frame_len, self.text_len], dim=1) tensor_i = self.inv_rearrange_with_remaining(tensor_i, h_res_len, w_res_len) if self.txt_first: @@ -182,8 +171,8 @@ def do_tensor_inv_rearrange(self, tensor, h_res_len, w_res_len): return tensor def do_tensor_pooling(self, tensor): - tensor_t = tensor[:, :self.text_len, :, :] - tensor_i = tensor[:, self.text_len:, :, :] + tensor_t = tensor[:, : self.text_len, :, :] + tensor_i = tensor[:, self.text_len :, :, :] tensor_i_pool = self.avgpool(tensor_i, pool_size=128) tensor_t_pool = self.avgpool(tensor_t, pool_size=128) @@ -192,25 +181,24 @@ def do_tensor_pooling(self, tensor): return tensor_pool def forward( - self, - q, # BSND - k, - v, - t_b_idx, - base_blockmask, + self, + q, # BSND + k, + v, + t_b_idx, + base_blockmask, ): t_idx = t_b_idx[0] if t_idx < self.skip_timesteps: base_blockmask = None - x = attention_forward(q, k, v, - opt_mode="manual", op_type="ascend_laser_attention", layout="BNSD") + x = attention_forward(q, k, v, opt_mode="manual", op_type="ascend_laser_attention", layout="BNSD") else: batch, qSeqlen, numHeads, headDim = q.shape assert batch == 1, "Rainfusion_blockwise currently only supports batch size 1." _, kvSeqlen, _, _ = k.shape blockShapeX, blockShapeY = self.pool_size, self.pool_size - scale = headDim ** -0.5 + scale = headDim**-0.5 blockShape = [blockShapeX, blockShapeY] actualSeqLengthsHost = [qSeqlen for _ in range(batch)] @@ -239,7 +227,9 @@ def forward( k_bnsd = k.transpose(1, 2) v_bnsd = v.transpose(1, 2) x = mindiesd.layers.flash_attn.sparse_flash_attn_rf_v2.rain_fusion_attention( - q_bnsd, k_bnsd, v_bnsd, + q_bnsd, + k_bnsd, + v_bnsd, scale=scale, head_num=numHeads, input_layout="BNSD", @@ -247,12 +237,11 @@ def forward( select_num_idx=selectNumIdx, blockshape=blockShape, actual_seq_lengths=actualSeqLengthsHost, - actual_seq_lengths_kv=actualSeqLengthsKvHost + actual_seq_lengths_kv=actualSeqLengthsKvHost, ) x = x.transpose(1, 2).view(batch, qSeqlen, numHeads, headDim) x = self.do_tensor_inv_rearrange(x, h_res_len, w_res_len) - base_blockmask = None - return x, base_blockmask \ No newline at end of file + return x, base_blockmask diff --git a/lightx2v_platform/ops/norm/ascend_npu/__init__.py b/lightx2v_platform/ops/norm/ascend_npu/__init__.py index e522b460b..91fa05c54 100644 --- a/lightx2v_platform/ops/norm/ascend_npu/__init__.py +++ b/lightx2v_platform/ops/norm/ascend_npu/__init__.py @@ -1,4 +1,4 @@ -from .npu_rms_norm import NpuRmsNormWeight from .npu_layer_norm import NpuLayerNormWeight +from .npu_rms_norm import NpuRmsNormWeight -__all__ = ["NpuRmsNormWeight", "NpuLayerNormWeight"] \ No newline at end of file +__all__ = ["NpuRmsNormWeight", "NpuLayerNormWeight"] diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py index 465424d61..136c9a19e 100644 --- a/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_layer_norm.py @@ -15,7 +15,18 @@ def __init__(self, weight_name=None, bias_name=None, create_cuda_buffer=False, c super().__init__(weight_name, bias_name, create_cuda_buffer, create_cpu_buffer, lazy_load, lazy_load_file, is_post_adapter, eps) def apply(self, input_tensor): - if torch_npu is not None and hasattr(torch_npu, 'npu_layer_norm') and self.weight is not None and self.bias is not None: + if torch_npu is not None and hasattr(torch_npu, "npu_layer_norm") and self.weight is not None and self.bias is not None: + if self.sensitive_layer_dtype != self.infer_dtype: + out = torch_npu.npu_layer_norm( + input_tensor.to(self.sensitive_layer_dtype), + (input_tensor.shape[-1],), + self.weight.to(self.sensitive_layer_dtype), + self.bias.to(self.sensitive_layer_dtype), + self.eps, + ) + if isinstance(out, tuple): + out = out[0] + return out.to(self.infer_dtype) out = torch_npu.npu_layer_norm( input_tensor, (input_tensor.shape[-1],), @@ -33,4 +44,4 @@ def apply(self, input_tensor): self.bias, self.eps, ) - return output \ No newline at end of file + return output diff --git a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py index fcac7669e..d2e78a2fd 100644 --- a/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py +++ b/lightx2v_platform/ops/norm/ascend_npu/npu_rms_norm.py @@ -19,7 +19,9 @@ def apply(self, input_tensor): if torch_npu is not None and hasattr(torch_npu, "npu_rms_norm") and weight is not None: if self.sensitive_layer_dtype != self.infer_dtype: output_tensor, _ = torch_npu.npu_rms_norm( - input_tensor.float(), weight.float(), self.eps, + input_tensor.to(self.sensitive_layer_dtype), + weight.to(self.sensitive_layer_dtype), + self.eps, ) return output_tensor.to(self.infer_dtype) output_tensor, _ = torch_npu.npu_rms_norm(input_tensor, weight, self.eps) @@ -27,4 +29,4 @@ def apply(self, input_tensor): x = self._norm(input_tensor.float()) if weight is not None: return (x.float() * weight.float()).to(self.infer_dtype) - return x.to(self.infer_dtype) \ No newline at end of file + return x.to(self.infer_dtype) diff --git a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py index 56daaf4fc..b08116811 100644 --- a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py +++ b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py @@ -1,6 +1,7 @@ -import torch from functools import lru_cache +import torch + from lightx2v_platform.ops.rope.rope_template import GET_DTYPE, RopeTemplate from lightx2v_platform.registry_factory import PLATFORM_ROPE_REGISTER @@ -13,6 +14,7 @@ @lru_cache(maxsize=None) def GET_SENSITIVE_DTYPE(): import os + DTYPE_MAP = { "BF16": torch.bfloat16, "FP16": torch.float16, @@ -41,12 +43,8 @@ def __init__(self): def _apply_rope_fp32(self, xq, xk, cos_sin_cache): n = xq.size(1) seq_len = cos_sin_cache.size(0) - xq_fp32 = torch.view_as_complex( - xq[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2) - ) - xk_fp32 = torch.view_as_complex( - xk[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2) - ) + xq_fp32 = torch.view_as_complex(xq[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2)) + xk_fp32 = torch.view_as_complex(xk[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2)) xq_rot = torch.view_as_real(xq_fp32 * cos_sin_cache).flatten(2) xk_rot = torch.view_as_real(xk_fp32 * cos_sin_cache).flatten(2) if xq.size(0) > seq_len: @@ -66,10 +64,10 @@ def apply(self, xq: torch.Tensor, xk: torch.Tensor, cos_sin_cache: torch.Tensor) if torch_npu is not None and hasattr(torch_npu, "npu_rotary_mul"): if self.sensitive_layer_dtype != self.infer_dtype: - xq_part = xq_part.float() - xk_part = xk_part.float() - cos = cos.float() if cos.dtype != torch.float32 else cos - sin = sin.float() if sin.dtype != torch.float32 else sin + xq_part = xq_part.to(self.sensitive_layer_dtype) + xk_part = xk_part.to(self.sensitive_layer_dtype) + cos = cos.to(self.sensitive_layer_dtype) + sin = sin.to(self.sensitive_layer_dtype) if not xq_part.is_contiguous(): xq_part = xq_part.contiguous() if not xk_part.is_contiguous(): From 0a65ead8626a8906288fc06f5497baaa13e753c2 Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 18:04:37 +0800 Subject: [PATCH 06/11] Update lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py index 277132ffe..b9dfd619f 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -240,7 +240,7 @@ def forward( actual_seq_lengths_kv=actualSeqLengthsKvHost, ) - x = x.transpose(1, 2).view(batch, qSeqlen, numHeads, headDim) + x = x.transpose(1, 2).reshape(batch, qSeqlen, numHeads, headDim) x = self.do_tensor_inv_rearrange(x, h_res_len, w_res_len) From 349c350d0e5333fcf2023fe4c0d8e33d37b1d89e Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Tue, 16 Jun 2026 18:05:29 +0800 Subject: [PATCH 07/11] feat(npu): Add 4 Ascend NPU custom operators for sparse video inference --- .../attn/ascend_npu/rainfusion_blockwise.py | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py index b9dfd619f..ec9077942 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py @@ -87,7 +87,10 @@ def get_blockwise_mask(self, score_matrix, sparsity): thresholds = topk_values[..., -1:] mask = score_matrix >= thresholds - protect_len = (self.first_frame_len + self.text_len + self.pool_size - 1) // self.pool_size + total_len = self.frame_num * self.num_tokens_per_frame + self.text_len + protected_start_idx = total_len - self.first_frame_len - self.text_len + protected_start_block = protected_start_idx // self.pool_size + protect_len = cols - protected_start_block if protect_len > 0: mask[:, :, -protect_len:, :] = True @@ -155,7 +158,7 @@ def do_tensor_rearrange_pooling(self, tensor): # BSND in , BSND out else: tensor_f, tensor_i, tensor_t = torch.split(tensor, [self.first_frame_len, s - self.text_len - self.first_frame_len, self.text_len], dim=1) tensor_i_2, h_res_len, w_res_len = self.rearrange_with_remaining(tensor_i) - tensor = torch.concat((tensor_i_2, tensor_f, tensor_t), dim=1) + tensor = torch.cat((tensor_i_2, tensor_f, tensor_t), dim=1) tensor_pool = self.avgpool(tensor, pool_size=128) return tensor, tensor_pool, h_res_len, w_res_len @@ -165,21 +168,11 @@ def do_tensor_inv_rearrange(self, tensor, h_res_len, w_res_len): tensor_i = self.inv_rearrange_with_remaining(tensor_i, h_res_len, w_res_len) if self.txt_first: - tensor = torch.concat((tensor_t, tensor_f, tensor_i), dim=1) + tensor = torch.cat((tensor_t, tensor_f, tensor_i), dim=1) else: - tensor = torch.concat((tensor_f, tensor_i, tensor_t), dim=1) + tensor = torch.cat((tensor_f, tensor_i, tensor_t), dim=1) return tensor - def do_tensor_pooling(self, tensor): - tensor_t = tensor[:, : self.text_len, :, :] - tensor_i = tensor[:, self.text_len :, :, :] - - tensor_i_pool = self.avgpool(tensor_i, pool_size=128) - tensor_t_pool = self.avgpool(tensor_t, pool_size=128) - - tensor_pool = torch.concat((tensor_t_pool, tensor_i_pool), dim=1) - return tensor_pool - def forward( self, q, # BSND From b3555b8342b4af88ea0410c31e99238c56d947f1 Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Wed, 17 Jun 2026 11:19:48 +0800 Subject: [PATCH 08/11] feat(npu): Perform inference with sparse FA and NPU affinity operators on Ascend NPU. --- ...oe_i2v_distill_int8_4step_ulysses_npu.json | 51 ++++++ .../wan/infer/offload/transformer_infer.py | 1 + .../networks/wan/infer/transformer_infer.py | 38 +++- .../worldmirror/models/layers/attention.py | 40 +++- lightx2v/models/vfi/rife/model/warplayer.py | 7 +- .../models/vfi/rife/rife_comfyui_wrapper.py | 23 ++- .../models/vfi/rife/train_log/RIFE_HDv3.py | 7 +- .../ops/attn/ascend_npu/__init__.py | 1 + ...on_blockwise.py => npu_rainfusion_attn.py} | 171 ++++++++++++++---- .../ops/rope/ascend_npu/npu_rope.py | 4 +- ..._moe_i2v_distill_int8_4step_ulysses_npu.sh | 21 +++ 11 files changed, 309 insertions(+), 55 deletions(-) create mode 100644 configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json rename lightx2v_platform/ops/attn/ascend_npu/{rainfusion_blockwise.py => npu_rainfusion_attn.py} (65%) create mode 100644 scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh diff --git a/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json b/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json new file mode 100644 index 000000000..581dcebe6 --- /dev/null +++ b/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json @@ -0,0 +1,51 @@ +{ + "infer_steps": 4, + "target_video_length": 81, + "text_len": 512, + "target_height": 720, + "target_width": 1280, + "use_rainfusion": true, + "rainfusion": { + "sparsity": 0.8, + "skip_timesteps": -1 + }, + "self_attn_1_type": "npu_flash_attn", + "cross_attn_1_type": "npu_flash_attn", + "cross_attn_2_type": "npu_flash_attn", + "sample_guide_scale": [ + 3.5, + 3.5 + ], + "sample_shift": 5.0, + "enable_cfg": false, + "cpu_offload": false, + "offload_granularity": "block", + "t5_cpu_offload": false, + "vae_cpu_offload": false, + "use_image_encoder": false, + "boundary": 0.900, + "boundary_step_index": 2, + "denoising_step_list": [ + 1000, + 750, + 500, + 250 + ], + "dit_quantized": true, + "dit_quant_scheme": "int8-npu", + "modulate_type": "torch", + "rope_type": "npu_rope", + "layer_norm_type": "npu_layer_norm", + "rms_norm_type": "npu_rms_norm", + "high_noise_quantized_ckpt": "models/Wan2.2-Distill-Models/wan2.2_i2v_A14b_high_noise_int8_lightx2v_4step.safetensors", + "low_noise_quantized_ckpt": "models/Wan2.2-Distill-Models/wan2.2_i2v_A14b_low_noise_int8_lightx2v_4step.safetensors", + "video_frame_interpolation": { + "algo": "rife", + "target_fps": 30, + "model_path": "models/rife/train_log/flownet.pkl" + }, + "parallel": { + "seq_p_size": 2, + "seq_p_attn_type": "ulysses" + } +} \ No newline at end of file diff --git a/lightx2v/models/networks/wan/infer/offload/transformer_infer.py b/lightx2v/models/networks/wan/infer/offload/transformer_infer.py index e7305a932..449714433 100755 --- a/lightx2v/models/networks/wan/infer/offload/transformer_infer.py +++ b/lightx2v/models/networks/wan/infer/offload/transformer_infer.py @@ -130,6 +130,7 @@ def infer_phase(self, cur_phase_idx, cur_phase, x, pre_infer_out): x, self.phase_params["shift_msa"], self.phase_params["scale_msa"], + grid_sizes=pre_infer_out.grid_sizes.tuple, ) elif cur_phase_idx == 1: x, self.phase_params["attn_out"] = self.infer_cross_attn( diff --git a/lightx2v/models/networks/wan/infer/transformer_infer.py b/lightx2v/models/networks/wan/infer/transformer_infer.py index 92030d16f..e3303d0f9 100755 --- a/lightx2v/models/networks/wan/infer/transformer_infer.py +++ b/lightx2v/models/networks/wan/infer/transformer_infer.py @@ -79,6 +79,11 @@ def rope_wrapper(xq, xk, cos_sin_cache): self._mxfp8_fuse_available = self._probe_mxfp8_fuse_availability() if self.mxfp8_fuse_enable else False + self._rf_mgr = None + if self.config.get("use_rainfusion", False) and "npu" in AI_DEVICE: + from lightx2v_platform.ops.attn.ascend_npu.npu_rainfusion_attn import RainfusionManager + self._rf_mgr = RainfusionManager(self.config) + @torch.no_grad() def reset_post_adapter_states(self): pass @@ -88,12 +93,16 @@ def reset_infer_states(self): self.cross_attn_cu_seqlens_q = None self.cross_attn_cu_seqlens_kv = None self.cross_attn_cu_seqlens_kv_img = None + if self._rf_mgr is not None: + self._rf_mgr.reset() if self.has_post_adapter: self.reset_post_adapter_states() @torch.no_grad() def infer(self, weights, pre_infer_out): self.cos_sin = pre_infer_out.cos_sin + if self._rf_mgr is not None: + self._rf_mgr.update_grid_sizes(pre_infer_out.grid_sizes.tuple) self.reset_infer_states() x = self.infer_main_blocks(weights.blocks, pre_infer_out) return self.infer_non_blocks(weights, x, pre_infer_out.embed) @@ -145,6 +154,7 @@ def infer_block(self, block, x, pre_infer_out): x, shift_msa, scale_msa, + grid_sizes=pre_infer_out.grid_sizes.tuple, ) x, attn_out = self.infer_cross_attn( block.compute_phases[1], @@ -177,7 +187,7 @@ def pre_process(self, modulation, embed0): return shift_msa, scale_msa, gate_msa, c_shift_msa, c_scale_msa, c_gate_msa - def infer_self_attn(self, phase, x, shift_msa, scale_msa): + def infer_self_attn(self, phase, x, shift_msa, scale_msa, grid_sizes=None): cos_sin = self.cos_sin norm1_quant = None norm1_scale = None @@ -233,7 +243,31 @@ def infer_self_attn(self, phase, x, shift_msa, scale_msa): "scheduler": self.scheduler, } - if self.config["seq_parallel"]: + if self._rf_mgr is not None and not self.config["seq_parallel"]: + self._rf_mgr.update_step_index(self.scheduler.step_index) + q_4d = q.unsqueeze(0) + k_4d = k.unsqueeze(0) + v_4d = v.unsqueeze(0) + attn_out = self._rf_mgr.apply_non_sp(q_4d, k_4d, v_4d, grid_sizes) + attn_out = attn_out.reshape(-1, self.num_heads * self.head_dim) + elif self._rf_mgr is not None and self.config["seq_parallel"]: + self._rf_mgr.update_step_index(self.scheduler.step_index) + attn_module = self._rf_mgr.get_sp_adapter() + attn_out = phase.self_attn_1_parallel.apply( + q=q, + k=k, + v=v, + slice_qkv_len=img_qkv_len, + cu_seqlens_qkv=self.self_attn_cu_seqlens_qkv, + attention_module=attn_module, + seq_p_group=self.seq_p_group, + use_fp8_comm=self.seq_p_fp8_comm, + use_fp4_comm=self.seq_p_fp4_comm, + use_tensor_fusion=self.seq_p_tensor_fusion, + enable_head_parallel=self.enable_head_parallel, + **attn_running_args, + ) + elif self.config["seq_parallel"]: attn_out = phase.self_attn_1_parallel.apply( q=q, k=k, diff --git a/lightx2v/models/networks/worldmirror/models/layers/attention.py b/lightx2v/models/networks/worldmirror/models/layers/attention.py index 869f08c5d..7c987f6a9 100755 --- a/lightx2v/models/networks/worldmirror/models/layers/attention.py +++ b/lightx2v/models/networks/worldmirror/models/layers/attention.py @@ -11,9 +11,22 @@ _USE_FLASH_ATTN_V3 = True except ImportError: + _USE_FLASH_ATTN_V3 = False + +try: from flash_attn.flash_attn_interface import flash_attn_func as flash_attn_func_v2 +except ImportError: + flash_attn_func_v2 = None + +_USE_FLASH_ATTN_V2 = flash_attn_func_v2 is not None + +try: + import torch_npu + _USE_NPU_FLASH_ATTN = True +except ImportError: + torch_npu = None + _USE_NPU_FLASH_ATTN = False - _USE_FLASH_ATTN_V3 = False from ...comm.communication import _All2All from ...comm.padding import depad_by_length, pad_by_length @@ -54,6 +67,25 @@ def _compute_qkv(self, x: Tensor): q, k = self.q_norm(q).to(v.dtype), self.k_norm(k).to(v.dtype) return q, k, v, B, N, C + def _npu_flash_attn(self, q: Tensor, k: Tensor, v: Tensor, dropout_p: float = 0.0) -> Tensor: + q_t = q.transpose(1, 2).contiguous() + k_t = k.transpose(1, 2).contiguous() + v_t = v.transpose(1, 2).contiguous() + keep_prob = 1.0 - dropout_p + x = torch_npu.npu_fusion_attention( + q_t, + k_t, + v_t, + self.num_heads, + pse=None, + atten_mask=None, + scale=self.scale, + keep_prob=keep_prob, + input_layout="BNSD", + ) + x = x[0].transpose(1, 2) + return x + def _apply_attention(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor: if q.dtype == torch.bfloat16 or q.dtype == torch.float16: if q.is_contiguous(): @@ -70,8 +102,12 @@ def _apply_attention(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor: v = v.transpose(1, 2).contiguous() if _USE_FLASH_ATTN_V3: x = flash_attn_func_v3(q, k, v) - else: + elif _USE_FLASH_ATTN_V2: x = flash_attn_func_v2(q, k, v, dropout_p=self.attn_drop.p if self.training else 0.0) + elif _USE_NPU_FLASH_ATTN: + x = self._npu_flash_attn(q, k, v, dropout_p=self.attn_drop.p if self.training else 0.0) + else: + x = F.scaled_dot_product_attention(q, k, v, dropout_p=self.attn_drop.p if self.training else 0.0) if x.is_contiguous(): x = x.transpose(1, 2) else: diff --git a/lightx2v/models/vfi/rife/model/warplayer.py b/lightx2v/models/vfi/rife/model/warplayer.py index 096d48fed..70565b0a7 100644 --- a/lightx2v/models/vfi/rife/model/warplayer.py +++ b/lightx2v/models/vfi/rife/model/warplayer.py @@ -1,6 +1,11 @@ import torch -device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +try: + import torch_npu + _HAS_NPU = torch.npu.is_available() +except ImportError: + _HAS_NPU = False +device = torch.device("cuda") if torch.cuda.is_available() else (torch.device("npu") if _HAS_NPU else torch.device("cpu")) backwarp_tenGrid = {} diff --git a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py index 0911c7259..cc82a9f29 100644 --- a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py +++ b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py @@ -13,7 +13,15 @@ class RIFEWrapper: BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def __init__(self, model_path, device: Optional[torch.device] = None): - self.device = device or torch.device("cuda" if torch.cuda.is_available() else "cpu") + if device is not None: + self.device = device + else: + try: + import torch_npu + _HAS_NPU = torch.npu.is_available() + except ImportError: + _HAS_NPU = False + self.device = torch.device("cuda") if torch.cuda.is_available() else (torch.device("npu") if _HAS_NPU else torch.device("cpu")) # Setup torch for optimal performance torch.set_grad_enabled(False) @@ -28,7 +36,7 @@ def __init__(self, model_path, device: Optional[torch.device] = None): with ProfilingContext4DebugL2("Load RIFE model"): self.model.load_model(model_path, -1) self.model.eval() - self.model.device() + self.model.flownet.to(self.device) @ProfilingContext4DebugL2("Interpolate frames") def interpolate_frames( @@ -74,7 +82,7 @@ def interpolate_frames( for source_idx1, source_idx2, interp_factor in frame_positions: if interp_factor == 0.0 or source_idx1 == source_idx2: # No interpolation needed, use the source frame directly - output_frames.append(images[source_idx1]) + output_frames.append(images[source_idx1].to(self.device).float()) else: # Get frames to interpolate frame1 = images[source_idx1] @@ -82,8 +90,8 @@ def interpolate_frames( # Convert ComfyUI format [H, W, C] to RIFE format [1, C, H, W] # Also convert from [0, 1] to [0, 1] (already in correct range) - I0 = frame1.permute(2, 0, 1).unsqueeze(0).to(self.device) - I1 = frame2.permute(2, 0, 1).unsqueeze(0).to(self.device) + I0 = frame1.permute(2, 0, 1).unsqueeze(0).to(self.device).float() + I1 = frame2.permute(2, 0, 1).unsqueeze(0).to(self.device).float() # Pad images I0 = F.pad(I0, padding) @@ -95,11 +103,10 @@ def interpolate_frames( # Convert back to ComfyUI format [H, W, C] # Crop to original size and permute dimensions - interpolated_frame = interpolated[0, :, :height, :width].permute(1, 2, 0).cpu() + interpolated_frame = interpolated[0, :, :height, :width].permute(1, 2, 0) output_frames.append(interpolated_frame) - # Stack all frames - return torch.stack(output_frames, dim=0) + return torch.stack(output_frames, dim=0).cpu() def _calculate_target_frame_positions(self, source_fps: float, target_fps: float, total_source_frames: int) -> List[Tuple[int, int, float]]: """ diff --git a/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py b/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py index 29d5caa80..47dda9656 100644 --- a/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py +++ b/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py @@ -5,7 +5,12 @@ from ..model.loss import * from .IFNet_HDv3 import * -device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +try: + import torch_npu + _HAS_NPU = torch.npu.is_available() +except ImportError: + _HAS_NPU = False +device = torch.device("cuda") if torch.cuda.is_available() else (torch.device("npu") if _HAS_NPU else torch.device("cpu")) class Model: diff --git a/lightx2v_platform/ops/attn/ascend_npu/__init__.py b/lightx2v_platform/ops/attn/ascend_npu/__init__.py index 46951c01f..b04781439 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/__init__.py +++ b/lightx2v_platform/ops/attn/ascend_npu/__init__.py @@ -1 +1,2 @@ from .npu_flash_attn import * +from .npu_rainfusion_attn import * diff --git a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py b/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py similarity index 65% rename from lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py rename to lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py index ec9077942..2fb91efc3 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/rainfusion_blockwise.py +++ b/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py @@ -2,33 +2,31 @@ import mindiesd import torch -import torch.nn as nn from einops import rearrange from mindiesd.layers.flash_attn.attention_forward import attention_forward - -class Rainfusion_blockwise(nn.Module): - def __init__( - self, - grid_size: list, - pool_size: int = 128, - sparsity: float = 0.9, - skip_timesteps: int = 0, - txt_len: int = 0, - txt_first: bool = False, - ) -> None: +from lightx2v_platform.ops.attn.template import AttnWeightTemplate +from lightx2v_platform.registry_factory import PLATFORM_ATTN_WEIGHT_REGISTER + +try: + import torch_npu +except ImportError: + torch_npu = None + +""" +RainFusion is a sparse attention acceleration algorithm. +RainFusion requires MindIE-SD to be installed. Installation guide: https://gitcode.com/Ascend/MindIE-SD/blob/master/docs/zh/installation.md +""" +@PLATFORM_ATTN_WEIGHT_REGISTER("npu_rainfusion_attn") +class NpuRainfusionAttnWeight(AttnWeightTemplate): + def __init__(self, grid_size=None, pool_size=128, sparsity=0.8, skip_timesteps=-1, txt_len=0, txt_first=False): """ 参数: grid_size (list): latents的THW网格大小。 sparsity (float, optional): 稀疏度, 取值范围[0, 1],默认为 0.50。 """ - super().__init__() - - # Rainfusion_param - self.grid_size = grid_size - self.frame_num = self.grid_size[0] - self.num_tokens_per_frame = self.grid_size[1] * self.grid_size[2] - self.first_frame_len = self.num_tokens_per_frame + self.config = {} + assert torch_npu is not None, "torch_npu is not installed." self.pool_size = pool_size self.sparsity = sparsity @@ -36,12 +34,21 @@ def __init__( self.text_len = txt_len self.txt_first = txt_first + if grid_size is not None: + self.update_grid_size(grid_size) + + def update_grid_size(self, grid_size): + self.grid_size = grid_size + self.frame_num = self.grid_size[0] + self.num_tokens_per_frame = self.grid_size[1] * self.grid_size[2] + self.first_frame_len = self.num_tokens_per_frame + @staticmethod def get_grid_size(latent_size, patch_size): t, h, w = latent_size[-3:] return [t // patch_size[0], h // patch_size[1], w // patch_size[2]] - def avgpool(self, input_tensor, pool_size=128): # BSND in, BSND out + def avgpool(self, input_tensor, pool_size=128): batch, seqlen, headnum, dim = input_tensor.shape num_full_blocks = seqlen // pool_size @@ -49,12 +56,12 @@ def avgpool(self, input_tensor, pool_size=128): # BSND in, BSND out pooled_tensors = [] if num_full_blocks > 0: - full_blocks = input_tensor[:, : num_full_blocks * pool_size, :, :] - full_blocks_reshaped = full_blocks.view(batch, num_full_blocks, pool_size, headnum, dim) + full_blocks = input_tensor[:, :num_full_blocks * pool_size, :, :] + full_blocks_reshaped = full_blocks.reshape(batch, num_full_blocks, pool_size, headnum, dim) pooled_tensors.append(full_blocks_reshaped.mean(dim=2)) if tail_size > 0: - tail_block = input_tensor[:, num_full_blocks * pool_size :, :, :] - tail_reshaped = tail_block.view(batch, 1, tail_size, headnum, dim) + tail_block = input_tensor[:, num_full_blocks * pool_size:, :, :] + tail_reshaped = tail_block.reshape(batch, 1, tail_size, headnum, dim) pooled_tensors.append(tail_reshaped.mean(dim=2)) return torch.cat(pooled_tensors, dim=1) @@ -63,12 +70,10 @@ def get_mask_index(self, mask): B, N, S, _ = mask.shape device = mask.device - # 1. 重塑维度 → (B*N)×S×S mask_reshaped = mask.reshape(-1, S, S) batch_size = mask_reshaped.shape[0] - # 2. 生成行索引 标记False位置为S(大于所有有效索引) - row_indices = torch.arange(S, device=device).view(1, 1, S).expand(batch_size, S, S) # (B*N, S, S) + row_indices = torch.arange(S, device=device).view(1, 1, S).expand(batch_size, S, S) sorted_vals = torch.where(mask_reshaped, row_indices, S) sorted_vals, _ = torch.sort(sorted_vals, dim=-1) valid_count = mask_reshaped.sum(dim=-1, keepdim=True) @@ -101,7 +106,7 @@ def get_blockwise_mask(self, score_matrix, sparsity): selectNumIdx = mask[0].transpose(0, 1).sum(dim=-1) return selectIdx, selectNumIdx - def rearrange_with_remaining(self, tensor): # BSND in , BSND out + def rearrange_with_remaining(self, tensor): b, s, n, d = tensor.shape h = self.grid_size[1] w = self.grid_size[2] @@ -128,7 +133,7 @@ def rearrange_with_remaining(self, tensor): # BSND in , BSND out tensor_hwt = rearrange(tensor_hwt, "(b n) s d -> b s n d", b=b, n=n) return tensor_hwt, h_res_len, w_res_len - def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in , BSND out + def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): b, s, n, d = tensor.shape h = self.grid_size[1] w = self.grid_size[2] @@ -149,7 +154,7 @@ def inv_rearrange_with_remaining(self, tensor, h_res_len, w_res_len): # BSND in tensor_hwt = rearrange(tensor_hwt, "(b n) s d -> b s n d", b=b, n=n) return tensor_hwt - def do_tensor_rearrange_pooling(self, tensor): # BSND in , BSND out + def do_tensor_rearrange_pooling(self, tensor): b, s, n, d = tensor.shape if s < self.text_len + self.first_frame_len: raise ValueError(f"Sequence length {s} is too small for text_len {self.text_len} and first_frame_len {self.first_frame_len}") @@ -173,14 +178,9 @@ def do_tensor_inv_rearrange(self, tensor, h_res_len, w_res_len): tensor = torch.cat((tensor_f, tensor_i, tensor_t), dim=1) return tensor - def forward( - self, - q, # BSND - k, - v, - t_b_idx, - base_blockmask, - ): + def apply(self, q, k, v, grid_size=None, t_b_idx=None, base_blockmask=None, **kwds): + if grid_size is not None: + self.update_grid_size(grid_size) t_idx = t_b_idx[0] if t_idx < self.skip_timesteps: @@ -188,7 +188,8 @@ def forward( x = attention_forward(q, k, v, opt_mode="manual", op_type="ascend_laser_attention", layout="BNSD") else: batch, qSeqlen, numHeads, headDim = q.shape - assert batch == 1, "Rainfusion_blockwise currently only supports batch size 1." + if batch != 1: + raise ValueError("NpuRainfusionAttnWeight currently only supports batch size 1.") _, kvSeqlen, _, _ = k.shape blockShapeX, blockShapeY = self.pool_size, self.pool_size scale = headDim**-0.5 @@ -238,3 +239,95 @@ def forward( x = self.do_tensor_inv_rearrange(x, h_res_len, w_res_len) return x, base_blockmask + + +class RainfusionAdapter: + def __init__(self, rf_attn, manager): + self.rf = rf_attn + self.manager = manager + + def _pad_to_expected(self, tensor, actual_seqlen, expected_seqlen): + if actual_seqlen > expected_seqlen: + return tensor[:expected_seqlen], True + elif actual_seqlen < expected_seqlen: + diff = expected_seqlen - actual_seqlen + pad = torch.zeros((diff, tensor.shape[1], tensor.shape[2]), dtype=tensor.dtype, device=tensor.device) + return torch.cat([tensor, pad], dim=0), False + else: + return tensor, None + + def apply(self, q, k, v, **kwargs): + grid_sizes = self.manager.grid_sizes + expected_seqlen = grid_sizes[0] * grid_sizes[1] * grid_sizes[2] + actual_seqlen = q.shape[0] + + q_valid, trunc_input = self._pad_to_expected(q, actual_seqlen, expected_seqlen) + k_valid, _ = self._pad_to_expected(k, actual_seqlen, expected_seqlen) + v_valid, _ = self._pad_to_expected(v, actual_seqlen, expected_seqlen) + + attn_out, self.manager._base_blockmask = self.rf.apply( + q=q_valid.unsqueeze(0), + k=k_valid.unsqueeze(0), + v=v_valid.unsqueeze(0), + grid_size=list(grid_sizes), + t_b_idx=(self.manager.step_index, 0), + base_blockmask=self.manager._base_blockmask, + ) + attn_out = attn_out.squeeze(0).reshape(attn_out.shape[1], -1) + + if trunc_input is True: + pad_out = torch.zeros((actual_seqlen - expected_seqlen, attn_out.shape[1]), dtype=attn_out.dtype, device=attn_out.device) + attn_out = torch.cat([attn_out, pad_out], dim=0) + elif trunc_input is False: + attn_out = attn_out[:actual_seqlen] + + return attn_out + + +class RainfusionManager: + def __init__(self, config): + self.config = config + self._rf_cfg = config.get("rainfusion", {}) + self._rf = None + self._base_blockmask = None + self.grid_sizes = None + self.step_index = 0 + + def update_grid_sizes(self, grid_sizes): + self.grid_sizes = grid_sizes + self._init_rf() + + def update_step_index(self, step_index): + self.step_index = step_index + + def reset(self): + self._base_blockmask = None + + def _init_rf(self): + if self._rf is not None and list(self._rf.grid_size) == list(self.grid_sizes): + return + self._base_blockmask = None + self._rf = NpuRainfusionAttnWeight( + grid_size=list(self.grid_sizes), + pool_size=128, + sparsity=self._rf_cfg.get("sparsity", 0.8), + skip_timesteps=self._rf_cfg.get("skip_timesteps", -1), + txt_len=0, + txt_first=False, + ) + + def apply_non_sp(self, q, k, v, grid_sizes): + attn_out, self._base_blockmask = self._rf.apply( + q=q, k=k, v=v, + grid_size=list(grid_sizes), + t_b_idx=(self.step_index, 0), + base_blockmask=self._base_blockmask, + ) + return attn_out + + def get_sp_adapter(self): + return RainfusionAdapter(self._rf, self) + + @property + def is_active(self): + return self._rf is not None diff --git a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py index b08116811..346209283 100644 --- a/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py +++ b/lightx2v_platform/ops/rope/ascend_npu/npu_rope.py @@ -43,8 +43,8 @@ def __init__(self): def _apply_rope_fp32(self, xq, xk, cos_sin_cache): n = xq.size(1) seq_len = cos_sin_cache.size(0) - xq_fp32 = torch.view_as_complex(xq[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2)) - xk_fp32 = torch.view_as_complex(xk[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2)) + xq_fp32 = torch.view_as_complex(xq[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2).contiguous()) + xk_fp32 = torch.view_as_complex(xk[:seq_len].to(torch.float32).reshape(seq_len, n, -1, 2).contiguous()) xq_rot = torch.view_as_real(xq_fp32 * cos_sin_cache).flatten(2) xk_rot = torch.view_as_real(xk_fp32 * cos_sin_cache).flatten(2) if xq.size(0) > seq_len: diff --git a/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh b/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh new file mode 100644 index 000000000..38dfbd5ba --- /dev/null +++ b/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +# set path firstly +lightx2v_path= +model_path= + +export PLATFORM=ascend_npu +export ASCEND_RT_VISIBLE_DEVICES=0,1 + +# set environment variables +source ${lightx2v_path}/scripts/base/base.sh + +torchrun --nproc_per_node=2 -m lightx2v.infer \ +--model_cls wan2.2_moe_distill \ +--task i2v \ +--model_path $model_path \ +--config_json ${lightx2v_path}/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json \ +--prompt "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside." \ +--negative_prompt "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" \ +--image_path ${lightx2v_path}/assets/inputs/imgs/img_0.jpg \ +--save_result_path ${lightx2v_path}/save_results/wan_moe_i2v_distill_int8_4step_ulysses_npu.mp4 \ No newline at end of file From 08465474b63a72807ef21b295edea49c5879633b Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Wed, 17 Jun 2026 11:43:03 +0800 Subject: [PATCH 09/11] feat(npu): Perform inference with sparse FA and NPU affinity operators on Ascend NPU. --- .../models/networks/wan/infer/offload/transformer_infer.py | 2 +- lightx2v/models/networks/wan/infer/transformer_infer.py | 2 +- .../models/networks/worldmirror/models/layers/attention.py | 2 +- lightx2v/models/vfi/rife/rife_comfyui_wrapper.py | 6 +++--- .../run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh | 5 +++++ 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lightx2v/models/networks/wan/infer/offload/transformer_infer.py b/lightx2v/models/networks/wan/infer/offload/transformer_infer.py index 449714433..233614c5d 100755 --- a/lightx2v/models/networks/wan/infer/offload/transformer_infer.py +++ b/lightx2v/models/networks/wan/infer/offload/transformer_infer.py @@ -130,7 +130,7 @@ def infer_phase(self, cur_phase_idx, cur_phase, x, pre_infer_out): x, self.phase_params["shift_msa"], self.phase_params["scale_msa"], - grid_sizes=pre_infer_out.grid_sizes.tuple, + grid_sizes=pre_infer_out.grid_sizes.tuple if getattr(pre_infer_out, "grid_sizes", None) is not None else None, ) elif cur_phase_idx == 1: x, self.phase_params["attn_out"] = self.infer_cross_attn( diff --git a/lightx2v/models/networks/wan/infer/transformer_infer.py b/lightx2v/models/networks/wan/infer/transformer_infer.py index e3303d0f9..da4a2e843 100755 --- a/lightx2v/models/networks/wan/infer/transformer_infer.py +++ b/lightx2v/models/networks/wan/infer/transformer_infer.py @@ -101,7 +101,7 @@ def reset_infer_states(self): @torch.no_grad() def infer(self, weights, pre_infer_out): self.cos_sin = pre_infer_out.cos_sin - if self._rf_mgr is not None: + if self._rf_mgr is not None and getattr(pre_infer_out, "grid_sizes", None) is not None: self._rf_mgr.update_grid_sizes(pre_infer_out.grid_sizes.tuple) self.reset_infer_states() x = self.infer_main_blocks(weights.blocks, pre_infer_out) diff --git a/lightx2v/models/networks/worldmirror/models/layers/attention.py b/lightx2v/models/networks/worldmirror/models/layers/attention.py index 7c987f6a9..93a438673 100755 --- a/lightx2v/models/networks/worldmirror/models/layers/attention.py +++ b/lightx2v/models/networks/worldmirror/models/layers/attention.py @@ -22,7 +22,7 @@ try: import torch_npu - _USE_NPU_FLASH_ATTN = True + _USE_NPU_FLASH_ATTN = torch.npu.is_available() except ImportError: torch_npu = None _USE_NPU_FLASH_ATTN = False diff --git a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py index cc82a9f29..e3bc6251a 100644 --- a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py +++ b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py @@ -82,7 +82,7 @@ def interpolate_frames( for source_idx1, source_idx2, interp_factor in frame_positions: if interp_factor == 0.0 or source_idx1 == source_idx2: # No interpolation needed, use the source frame directly - output_frames.append(images[source_idx1].to(self.device).float()) + output_frames.append(images[source_idx1].float()) else: # Get frames to interpolate frame1 = images[source_idx1] @@ -103,10 +103,10 @@ def interpolate_frames( # Convert back to ComfyUI format [H, W, C] # Crop to original size and permute dimensions - interpolated_frame = interpolated[0, :, :height, :width].permute(1, 2, 0) + interpolated_frame = interpolated[0, :, :height, :width].permute(1, 2, 0).cpu() output_frames.append(interpolated_frame) - return torch.stack(output_frames, dim=0).cpu() + return torch.stack(output_frames, dim=0) def _calculate_target_frame_positions(self, source_fps: float, target_fps: float, total_source_frames: int) -> List[Tuple[int, int, float]]: """ diff --git a/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh b/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh index 38dfbd5ba..3be4fbf21 100644 --- a/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh +++ b/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh @@ -4,6 +4,11 @@ lightx2v_path= model_path= +if [ -z "$lightx2v_path" ] || [ -z "$model_path" ]; then + echo "Error: Please set lightx2v_path and model_path in this script before running." + exit 1 +fi + export PLATFORM=ascend_npu export ASCEND_RT_VISIBLE_DEVICES=0,1 From 5ed56f4fce94760f5261e371677703fe6c181d90 Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Wed, 17 Jun 2026 15:04:36 +0800 Subject: [PATCH 10/11] feat(npu): Perform inference with sparse FA and NPU affinity operators on Ascend NPU. --- .../networks/wan/infer/transformer_infer.py | 2 +- .../worldmirror/models/layers/attention.py | 14 +++++-------- .../models/vfi/rife/rife_comfyui_wrapper.py | 2 +- .../attn/ascend_npu/npu_rainfusion_attn.py | 20 +++++++++++++------ 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/lightx2v/models/networks/wan/infer/transformer_infer.py b/lightx2v/models/networks/wan/infer/transformer_infer.py index da4a2e843..5fe35c418 100755 --- a/lightx2v/models/networks/wan/infer/transformer_infer.py +++ b/lightx2v/models/networks/wan/infer/transformer_infer.py @@ -154,7 +154,7 @@ def infer_block(self, block, x, pre_infer_out): x, shift_msa, scale_msa, - grid_sizes=pre_infer_out.grid_sizes.tuple, + grid_sizes=pre_infer_out.grid_sizes.tuple if getattr(pre_infer_out, "grid_sizes", None) is not None else None, ) x, attn_out = self.infer_cross_attn( block.compute_phases[1], diff --git a/lightx2v/models/networks/worldmirror/models/layers/attention.py b/lightx2v/models/networks/worldmirror/models/layers/attention.py index 93a438673..7c44e1690 100755 --- a/lightx2v/models/networks/worldmirror/models/layers/attention.py +++ b/lightx2v/models/networks/worldmirror/models/layers/attention.py @@ -68,23 +68,19 @@ def _compute_qkv(self, x: Tensor): return q, k, v, B, N, C def _npu_flash_attn(self, q: Tensor, k: Tensor, v: Tensor, dropout_p: float = 0.0) -> Tensor: - q_t = q.transpose(1, 2).contiguous() - k_t = k.transpose(1, 2).contiguous() - v_t = v.transpose(1, 2).contiguous() keep_prob = 1.0 - dropout_p x = torch_npu.npu_fusion_attention( - q_t, - k_t, - v_t, + q, + k, + v, self.num_heads, pse=None, atten_mask=None, scale=self.scale, keep_prob=keep_prob, - input_layout="BNSD", + input_layout="BSND", ) - x = x[0].transpose(1, 2) - return x + return x[0] def _apply_attention(self, q: Tensor, k: Tensor, v: Tensor) -> Tensor: if q.dtype == torch.bfloat16 or q.dtype == torch.float16: diff --git a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py index e3bc6251a..fb844c077 100644 --- a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py +++ b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py @@ -82,7 +82,7 @@ def interpolate_frames( for source_idx1, source_idx2, interp_factor in frame_positions: if interp_factor == 0.0 or source_idx1 == source_idx2: # No interpolation needed, use the source frame directly - output_frames.append(images[source_idx1].float()) + output_frames.append(images[source_idx1].float().cpu()) else: # Get frames to interpolate frame1 = images[source_idx1] diff --git a/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py b/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py index 2fb91efc3..6aebd54b5 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py +++ b/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py @@ -1,9 +1,16 @@ import math -import mindiesd import torch from einops import rearrange -from mindiesd.layers.flash_attn.attention_forward import attention_forward + +try: + import mindiesd + from mindiesd.layers.flash_attn.attention_forward import attention_forward + _HAS_MINDIESD = True +except ImportError: + mindiesd = None + attention_forward = None + _HAS_MINDIESD = False from lightx2v_platform.ops.attn.template import AttnWeightTemplate from lightx2v_platform.registry_factory import PLATFORM_ATTN_WEIGHT_REGISTER @@ -27,6 +34,7 @@ def __init__(self, grid_size=None, pool_size=128, sparsity=0.8, skip_timesteps=- """ self.config = {} assert torch_npu is not None, "torch_npu is not installed." + assert _HAS_MINDIESD, "mindiesd is not installed. RainFusion requires MindIE-SD." self.pool_size = pool_size self.sparsity = sparsity @@ -115,11 +123,11 @@ def rearrange_with_remaining(self, tensor): tensor_hwt = rearrange(tensor, "b (f h w) n d -> (b n) f h w d", f=self.frame_num - first_frame_num, h=h, w=w) if h % 8 != 0: - tensor_hwt, tensor_h_r = torch.split(tensor_hwt, h - (h % 8), dim=2) + tensor_hwt, tensor_h_r = torch.split(tensor_hwt, [h - (h % 8), h % 8], dim=2) tensor_h_r = tensor_h_r.reshape(b * n, -1, d) h_res_len = tensor_h_r.shape[1] if w % 8 != 0: - tensor_hwt, tensor_w_r = torch.split(tensor_hwt, w - (w % 8), dim=3) + tensor_hwt, tensor_w_r = torch.split(tensor_hwt, [w - (w % 8), w % 8], dim=3) tensor_w_r = tensor_w_r.reshape(b * n, -1, d) w_res_len = tensor_w_r.shape[1] remaining_frames = self.frame_num - first_frame_num @@ -251,7 +259,7 @@ def _pad_to_expected(self, tensor, actual_seqlen, expected_seqlen): return tensor[:expected_seqlen], True elif actual_seqlen < expected_seqlen: diff = expected_seqlen - actual_seqlen - pad = torch.zeros((diff, tensor.shape[1], tensor.shape[2]), dtype=tensor.dtype, device=tensor.device) + pad = tensor.new_zeros((diff,) + tensor.shape[1:]) return torch.cat([tensor, pad], dim=0), False else: return tensor, None @@ -276,7 +284,7 @@ def apply(self, q, k, v, **kwargs): attn_out = attn_out.squeeze(0).reshape(attn_out.shape[1], -1) if trunc_input is True: - pad_out = torch.zeros((actual_seqlen - expected_seqlen, attn_out.shape[1]), dtype=attn_out.dtype, device=attn_out.device) + pad_out = attn_out.new_zeros((actual_seqlen - expected_seqlen, attn_out.shape[1])) attn_out = torch.cat([attn_out, pad_out], dim=0) elif trunc_input is False: attn_out = attn_out[:actual_seqlen] From df8577a4923649a3d129c9c6295f546f04292f90 Mon Sep 17 00:00:00 2001 From: liaopingbo Date: Wed, 17 Jun 2026 17:16:33 +0800 Subject: [PATCH 11/11] feat(npu): pre-commit fix --- .../wan_moe_i2v_distill_int8_4step_ulysses_npu.json | 2 +- .../models/networks/wan/infer/transformer_infer.py | 1 + .../networks/worldmirror/models/layers/attention.py | 7 ++++--- lightx2v/models/vfi/rife/model/warplayer.py | 8 +++----- lightx2v/models/vfi/rife/rife_comfyui_wrapper.py | 7 ++----- lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py | 8 +++----- .../ops/attn/ascend_npu/npu_rainfusion_attn.py | 11 ++++++++--- ...un_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh | 2 +- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json b/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json index 581dcebe6..f32405ca1 100644 --- a/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json +++ b/configs/distill/wan22/wan_moe_i2v_distill_int8_4step_ulysses_npu.json @@ -48,4 +48,4 @@ "seq_p_size": 2, "seq_p_attn_type": "ulysses" } -} \ No newline at end of file +} diff --git a/lightx2v/models/networks/wan/infer/transformer_infer.py b/lightx2v/models/networks/wan/infer/transformer_infer.py index 5fe35c418..6e7064a23 100755 --- a/lightx2v/models/networks/wan/infer/transformer_infer.py +++ b/lightx2v/models/networks/wan/infer/transformer_infer.py @@ -82,6 +82,7 @@ def rope_wrapper(xq, xk, cos_sin_cache): self._rf_mgr = None if self.config.get("use_rainfusion", False) and "npu" in AI_DEVICE: from lightx2v_platform.ops.attn.ascend_npu.npu_rainfusion_attn import RainfusionManager + self._rf_mgr = RainfusionManager(self.config) @torch.no_grad() diff --git a/lightx2v/models/networks/worldmirror/models/layers/attention.py b/lightx2v/models/networks/worldmirror/models/layers/attention.py index 7c44e1690..ddfd991bf 100755 --- a/lightx2v/models/networks/worldmirror/models/layers/attention.py +++ b/lightx2v/models/networks/worldmirror/models/layers/attention.py @@ -6,6 +6,9 @@ import torch.nn.functional as F from torch import Tensor, nn +from ...comm.communication import _All2All +from ...comm.padding import depad_by_length, pad_by_length + try: from flash_attn_interface import flash_attn_func as flash_attn_func_v3 @@ -22,14 +25,12 @@ try: import torch_npu + _USE_NPU_FLASH_ATTN = torch.npu.is_available() except ImportError: torch_npu = None _USE_NPU_FLASH_ATTN = False -from ...comm.communication import _All2All -from ...comm.padding import depad_by_length, pad_by_length - class Attention(nn.Module): def __init__( diff --git a/lightx2v/models/vfi/rife/model/warplayer.py b/lightx2v/models/vfi/rife/model/warplayer.py index 70565b0a7..8ea6fb33a 100644 --- a/lightx2v/models/vfi/rife/model/warplayer.py +++ b/lightx2v/models/vfi/rife/model/warplayer.py @@ -1,10 +1,8 @@ +import importlib.util + import torch -try: - import torch_npu - _HAS_NPU = torch.npu.is_available() -except ImportError: - _HAS_NPU = False +_HAS_NPU = importlib.util.find_spec("torch_npu") is not None and torch.npu.is_available() device = torch.device("cuda") if torch.cuda.is_available() else (torch.device("npu") if _HAS_NPU else torch.device("cpu")) backwarp_tenGrid = {} diff --git a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py index fb844c077..d5afa7fe1 100644 --- a/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py +++ b/lightx2v/models/vfi/rife/rife_comfyui_wrapper.py @@ -1,3 +1,4 @@ +import importlib.util import os from typing import List, Optional, Tuple @@ -16,11 +17,7 @@ def __init__(self, model_path, device: Optional[torch.device] = None): if device is not None: self.device = device else: - try: - import torch_npu - _HAS_NPU = torch.npu.is_available() - except ImportError: - _HAS_NPU = False + _HAS_NPU = importlib.util.find_spec("torch_npu") is not None and torch.npu.is_available() self.device = torch.device("cuda") if torch.cuda.is_available() else (torch.device("npu") if _HAS_NPU else torch.device("cpu")) # Setup torch for optimal performance diff --git a/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py b/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py index 47dda9656..bcf6dc021 100644 --- a/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py +++ b/lightx2v/models/vfi/rife/train_log/RIFE_HDv3.py @@ -1,3 +1,5 @@ +import importlib.util + import torch from torch.nn.parallel import DistributedDataParallel as DDP from torch.optim import AdamW @@ -5,11 +7,7 @@ from ..model.loss import * from .IFNet_HDv3 import * -try: - import torch_npu - _HAS_NPU = torch.npu.is_available() -except ImportError: - _HAS_NPU = False +_HAS_NPU = importlib.util.find_spec("torch_npu") is not None and torch.npu.is_available() device = torch.device("cuda") if torch.cuda.is_available() else (torch.device("npu") if _HAS_NPU else torch.device("cpu")) diff --git a/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py b/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py index 6aebd54b5..70850f081 100644 --- a/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py +++ b/lightx2v_platform/ops/attn/ascend_npu/npu_rainfusion_attn.py @@ -6,6 +6,7 @@ try: import mindiesd from mindiesd.layers.flash_attn.attention_forward import attention_forward + _HAS_MINDIESD = True except ImportError: mindiesd = None @@ -24,6 +25,8 @@ RainFusion is a sparse attention acceleration algorithm. RainFusion requires MindIE-SD to be installed. Installation guide: https://gitcode.com/Ascend/MindIE-SD/blob/master/docs/zh/installation.md """ + + @PLATFORM_ATTN_WEIGHT_REGISTER("npu_rainfusion_attn") class NpuRainfusionAttnWeight(AttnWeightTemplate): def __init__(self, grid_size=None, pool_size=128, sparsity=0.8, skip_timesteps=-1, txt_len=0, txt_first=False): @@ -64,11 +67,11 @@ def avgpool(self, input_tensor, pool_size=128): pooled_tensors = [] if num_full_blocks > 0: - full_blocks = input_tensor[:, :num_full_blocks * pool_size, :, :] + full_blocks = input_tensor[:, : num_full_blocks * pool_size, :, :] full_blocks_reshaped = full_blocks.reshape(batch, num_full_blocks, pool_size, headnum, dim) pooled_tensors.append(full_blocks_reshaped.mean(dim=2)) if tail_size > 0: - tail_block = input_tensor[:, num_full_blocks * pool_size:, :, :] + tail_block = input_tensor[:, num_full_blocks * pool_size :, :, :] tail_reshaped = tail_block.reshape(batch, 1, tail_size, headnum, dim) pooled_tensors.append(tail_reshaped.mean(dim=2)) @@ -326,7 +329,9 @@ def _init_rf(self): def apply_non_sp(self, q, k, v, grid_sizes): attn_out, self._base_blockmask = self._rf.apply( - q=q, k=k, v=v, + q=q, + k=k, + v=v, grid_size=list(grid_sizes), t_b_idx=(self.step_index, 0), base_blockmask=self._base_blockmask, diff --git a/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh b/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh index 3be4fbf21..7d165d668 100644 --- a/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh +++ b/scripts/wan22/distill/run_wan22_moe_i2v_distill_int8_4step_ulysses_npu.sh @@ -23,4 +23,4 @@ torchrun --nproc_per_node=2 -m lightx2v.infer \ --prompt "Summer beach vacation style, a white cat wearing sunglasses sits on a surfboard. The fluffy-furred feline gazes directly at the camera with a relaxed expression. Blurred beach scenery forms the background featuring crystal-clear waters, distant green hills, and a blue sky dotted with white clouds. The cat assumes a naturally relaxed posture, as if savoring the sea breeze and warm sunlight. A close-up shot highlights the feline's intricate details and the refreshing atmosphere of the seaside." \ --negative_prompt "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" \ --image_path ${lightx2v_path}/assets/inputs/imgs/img_0.jpg \ ---save_result_path ${lightx2v_path}/save_results/wan_moe_i2v_distill_int8_4step_ulysses_npu.mp4 \ No newline at end of file +--save_result_path ${lightx2v_path}/save_results/wan_moe_i2v_distill_int8_4step_ulysses_npu.mp4