Skip to content

Commit 8f1d97c

Browse files
blian6blian
andauthored
feat: add MindIE-SD as optional NPU attention and compilation backend (#1004)
Add MindIE-SD laser_attention and MindieSDBackend compile support for Ascend NPU, with CLI-explicit control and zero environment variables. Design: - --attn _mindiesd_laser: use MindIE-SD laser attention - --compile: auto-detect NPU + mindiesd -> torch.compile(backend=MindieSDBackend()) - Default attention is _native_npu; no env vars required (removed CACHE_DIT_ENABLE_MINDIESD_ATTN, CACHE_DIT_FORCE_DISABLE_MINDIESD_COMPILE_CONFIG) - enable_cache() decoupled from compile; compile follows CUDA --compile pattern - backend_selector.py moved from _utils/ to attention/ for semantic correctness - Optional dependency: import mindiesd wrapped in try/except, graceful fallback - NPU-only: all logic gated by torch.npu.is_available(), zero impact on CUDA/CPU Files modified: - attention/backends/npu.py: _mindiesd_laser_attention backend -> mindiesd.layers.attention_forward - attention/backends/register.py: _MINDIESD_LASER enum - attention/backend_selector.py: new BackendSelector.auto_select() -> _native_npu on NPU - kernels/backend.py: KernelBackend.MINDIESD enum - kernels/ops.py: MINDIESD kernel routing with PT fallback - caching/cache_interface.py: auto-select attention backend in enable_cache() - compile/utils.py: _maybe_apply_mindiesd_compile() -> torch.compile(backend=MindieSDBackend()) - _utils/utils.py: _mindiesd_laser in --attn CLI choices; _compile_transformer_module() detects NPU - envs.py: removed MindIE-SD env vars Verification (Ascend 910B, FLUX.1-dev 1024x1024, bf16, 28 steps): _native_npu (baseline): 489.7 ms/step _mindiesd_laser: 479.3 ms/step (-2.1%) _native_npu + compile: 447.3 ms/step (-8.7%) _mindiesd_laser + compile: 422.3 ms/step (-13.8%) Co-authored-by: blian <lianbin@huawei.com>
1 parent cdacd96 commit 8f1d97c

8 files changed

Lines changed: 173 additions & 21 deletions

File tree

src/cache_dit/_utils/utils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from ..logger import init_logger
99
from ..platforms import current_platform
10-
from ..compile.utils import set_compile_configs
10+
from ..compile.utils import set_compile_configs, _maybe_apply_mindiesd_compile
1111
from ..distributed import ParallelismBackend, ParallelismConfig
1212
from ..caching import enable_cache, steps_mask
1313
from ..attention import set_attn_backend
@@ -698,6 +698,7 @@ def get_args(parse: bool = True, ) -> argparse.ArgumentParser | argparse.Namespa
698698
"sage", # Need install sageattention: https://github.com/thu-ml/SageAttention
699699
"_native_npu", # native npu attention
700700
"_npu_fia", # npu fused infer attention
701+
"_mindiesd_laser", # MindIE-SD laser attention
701702
],
702703
)
703704
# Ulysses context parallelism settings
@@ -1255,6 +1256,12 @@ def _compile_transformer_module(args, pipe, transformer, name):
12551256
logger.warning(f"Cannot compile {name} module: {transformer_cls_name} Not a torch.nn.Module.")
12561257
return transformer
12571258

1259+
# Auto-enable MindieSDBackend on NPU
1260+
compiled = _maybe_apply_mindiesd_compile(transformer, name, transformer_cls_name)
1261+
if compiled is not None:
1262+
setattr(pipe, name, compiled)
1263+
return compiled
1264+
12581265
use_regional_compile = not args.disable_compile_repeated_blocks and hasattr(
12591266
transformer, "compile_repeated_blocks")
12601267

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import torch
2+
from cache_dit.logger import init_logger
3+
4+
logger = init_logger(__name__)
5+
6+
7+
class BackendSelector:
8+
_attn_backend: str | None = None
9+
_selected: bool = False
10+
11+
@classmethod
12+
def auto_select(cls, pipe_or_adapter) -> str | None:
13+
if cls._selected:
14+
return cls._attn_backend
15+
device = cls._detect_device(pipe_or_adapter)
16+
if device.type == "npu":
17+
cls._attn_backend = "_native_npu"
18+
cls._selected = True
19+
return cls._attn_backend
20+
21+
@classmethod
22+
def auto_select_kernel_backend(cls) -> str | None:
23+
return None
24+
25+
@staticmethod
26+
def _detect_device(pipe_or_adapter):
27+
try:
28+
if hasattr(pipe_or_adapter, "device"):
29+
return pipe_or_adapter.device
30+
param = next(pipe_or_adapter.parameters())
31+
return param.device
32+
except (StopIteration, AttributeError):
33+
return torch.device("cpu")

src/cache_dit/attention/backends/npu.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def _native_npu_attention(
4949
dropout_p: float = 0.0,
5050
scale: Optional[float] = None,
5151
return_lse: bool = False,
52+
is_causal: bool = False,
53+
enable_gqa: bool = False,
5254
_cp_config: Optional["_ContextParallelConfig"] = None,
5355
) -> torch.Tensor:
5456
if return_lse:
@@ -97,6 +99,8 @@ def _npu_fused_infer_attention(
9799
dropout_p: float = 0.0,
98100
scale: Optional[float] = None,
99101
return_lse: bool = False,
102+
is_causal: bool = False,
103+
enable_gqa: bool = False,
100104
_cp_config: Optional["_ContextParallelConfig"] = None,
101105
) -> torch.Tensor:
102106
if _cp_config is None:
@@ -128,3 +132,47 @@ def _npu_fused_infer_attention(
128132
_cp_config=_cp_config,
129133
)
130134
return out
135+
136+
137+
try:
138+
from mindiesd.layers import attention_forward
139+
140+
_mindiesd_available = True
141+
except Exception:
142+
_mindiesd_available = False
143+
attention_forward = None
144+
145+
if _mindiesd_available:
146+
147+
@_AttnBackendRegistry.register(
148+
_AttnBackend._MINDIESD_LASER,
149+
constraints=[],
150+
supports_context_parallel=True,
151+
)
152+
def _mindiesd_laser_attention(
153+
query: torch.Tensor,
154+
key: torch.Tensor,
155+
value: torch.Tensor,
156+
attn_mask: Optional[torch.Tensor] = None,
157+
dropout_p: float = 0.0,
158+
scale: Optional[float] = None,
159+
return_lse: bool = False,
160+
is_causal: bool = False,
161+
enable_gqa: bool = False,
162+
_cp_config: Optional["_ContextParallelConfig"] = None,
163+
) -> torch.Tensor:
164+
if return_lse:
165+
raise ValueError(
166+
"MindIE-SD laser attention backend does not support setting `return_lse=True`.")
167+
scale_val = scale if scale is not None else 1.0 / math.sqrt(query.shape[-1])
168+
return attention_forward(
169+
query,
170+
key,
171+
value,
172+
attn_mask=attn_mask,
173+
scale=scale_val,
174+
fused=True,
175+
head_first=False,
176+
opt_mode="manual",
177+
op_type="ascend_laser_attention",
178+
)

src/cache_dit/attention/backends/register.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class _AttnBackend(str, Enum):
3838
_SDPA_CUDNN = "_sdpa_cudnn"
3939
_NATIVE_NPU = "_native_npu"
4040
_NPU_FIA = "_npu_fia"
41+
_MINDIESD_LASER = "_mindiesd_laser"
4142

4243

4344
def _default_active_backend() -> _AttnBackend:

src/cache_dit/caching/cache_interface.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
logger = init_logger(__name__)
2323

2424

25+
def _auto_select_attention_backend(pipe_or_adapter) -> Optional[str]:
26+
"""Try to auto-select an optimal attention backend when none was specified."""
27+
try:
28+
from cache_dit.attention.backend_selector import BackendSelector
29+
30+
return BackendSelector.auto_select(pipe_or_adapter)
31+
except Exception:
32+
return None
33+
34+
2535
def enable_cache(
2636
pipe_or_adapter: Union[
2737
DiffusionPipeline,
@@ -352,6 +362,10 @@ def _enable_cache_impl(
352362
logger.warning("cache_config is None, skip cache acceleration for "
353363
f"{pipe_or_adapter.__class__.__name__}.")
354364

365+
# Auto-select attention backend when none specified
366+
if attention_backend is None and parallelism_config is None:
367+
attention_backend = _auto_select_attention_backend(pipe_or_adapter)
368+
355369
# Set custom attention backend for non-parallelism case
356370
if attention_backend is not None:
357371
if parallelism_config is not None:
@@ -457,6 +471,7 @@ def _enable_cache_impl(
457471
# Enable quantization for the specified component inplace
458472
quantized_component = quantize(component, quantize_config=config)
459473
setattr(pipe, name, quantized_component)
474+
460475
return pipe_or_adapter
461476

462477

src/cache_dit/compile/utils.py

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -117,27 +117,33 @@ def set_compile_configs(
117117
# them to your needs and test the performance
118118
inductor_config.max_fusion_size = 64
119119
inductor_config.max_pointwise_cat_inputs = 8
120-
inductor_config.triton.cudagraphs = cuda_graphs
121-
inductor_config.triton.use_block_ptr = False
122-
inductor_config.triton.codegen_upcast_to_fp32 = True
123-
124-
# Copy from https://pytorch.org/blog/accelerating-generative-ai-3/
125-
inductor_config.conv_1x1_as_mm = True
126-
inductor_config.coordinate_descent_tuning = True
127-
inductor_config.coordinate_descent_check_all_directions = True
128-
inductor_config.epilogue_fusion = False
129-
130-
# Enable epilogue and prologue fusion
131-
if ENV.CACHE_DIT_EPILOGUE_PROLOGUE_FUSION or kwargs.get(
132-
"epilogue_prologue_fusion",
133-
False,
134-
):
135-
inductor_config.epilogue_fusion = True
136-
inductor_config.prologue_fusion = True
137-
inductor_config.epilogue_fusion_first = True
138120

139-
# Dead code elimination
140-
inductor_config.dce = True # default is False
121+
if current_platform.device_type == "npu":
122+
# NPU: skip CUDA-specific inductor configs (triton, coordinate_descent, etc)
123+
inductor_config.dce = True # default is False
124+
inductor_config.epilogue_fusion = False
125+
else:
126+
inductor_config.triton.cudagraphs = cuda_graphs
127+
inductor_config.triton.use_block_ptr = False
128+
inductor_config.triton.codegen_upcast_to_fp32 = True
129+
130+
# Copy from https://pytorch.org/blog/accelerating-generative-ai-3/
131+
inductor_config.conv_1x1_as_mm = True
132+
inductor_config.coordinate_descent_tuning = True
133+
inductor_config.coordinate_descent_check_all_directions = True
134+
inductor_config.epilogue_fusion = False
135+
136+
# Enable epilogue and prologue fusion
137+
if ENV.CACHE_DIT_EPILOGUE_PROLOGUE_FUSION or kwargs.get(
138+
"epilogue_prologue_fusion",
139+
False,
140+
):
141+
inductor_config.epilogue_fusion = True
142+
inductor_config.prologue_fusion = True
143+
inductor_config.epilogue_fusion_first = True
144+
145+
# Dead code elimination
146+
inductor_config.dce = True # default is False
141147

142148
# May need to force disable all cache
143149
if force_disable_compile_caches:
@@ -153,3 +159,19 @@ def set_compile_configs(
153159
inductor_config.cuda.use_fast_math = use_fast_math
154160
except Exception:
155161
pass
162+
163+
164+
def _maybe_apply_mindiesd_compile(module, module_name, module_cls_name):
165+
# Auto-apply MindieSDBackend compile on NPU when mindiesd is available.
166+
# Returns the compiled module if compiled, None if MindIE-SD not applicable.
167+
try:
168+
import mindiesd # noqa F401
169+
170+
if not hasattr(torch, 'npu') or not torch.npu.is_available():
171+
return None
172+
from mindiesd.compilation import MindieSDBackend
173+
174+
logger.info(f"Compiling {module_name}: {module_cls_name} with MindieSDBackend ...")
175+
return torch.compile(module, backend=MindieSDBackend(), dynamic=True)
176+
except Exception:
177+
return None

src/cache_dit/kernels/backend.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class KernelBackend(Enum):
77
TRITON = "Triton"
88
CUDA = "CUDA"
99
CUTEDSL = "CuteDSL"
10+
MINDIESD = "MindIESD"
1011
NONE = "None"
1112

1213
@classmethod
@@ -41,4 +42,11 @@ def is_supported(cls, backend: "KernelBackend") -> bool:
4142
return True
4243
except ImportError:
4344
return False
45+
if backend == cls.MINDIESD:
46+
try:
47+
import mindiesd # noqa F401
48+
49+
return True
50+
except Exception:
51+
return False
4452
return False

src/cache_dit/kernels/ops.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ def _select_cuda_backend() -> KernelBackend:
1414
return KernelBackend.CUDA
1515

1616

17+
def _select_mindiesd_backend() -> KernelBackend:
18+
return KernelBackend.MINDIESD
19+
20+
1721
def _select_kernel_backend() -> KernelBackend:
1822
"""Select the default backend for kernel ops.
1923
@@ -149,6 +153,20 @@ def _fused_merge_attn_states_impl(
149153
suff_out,
150154
suff_lse,
151155
)
156+
if backend == KernelBackend.MINDIESD:
157+
max_lse = torch.max(prev_lse, suff_lse)
158+
prev_scale = torch.exp(prev_lse - max_lse)
159+
suff_scale = torch.exp(suff_lse - max_lse)
160+
denom = prev_scale + suff_scale
161+
denom = denom + 1e-12
162+
out = torch.where(
163+
denom > 1e-12,
164+
(prev_out * prev_scale.unsqueeze(-1) + suff_out * suff_scale.unsqueeze(-1)) /
165+
denom.unsqueeze(-1),
166+
prev_out,
167+
)
168+
lse = max_lse + torch.log(denom)
169+
return out, lse
152170
else:
153171
raise ValueError(_ERROR_TEMPLATE.format(backend))
154172

0 commit comments

Comments
 (0)