import ctypes
import os
import re
import torch
import torch_npu
from ._version import get_version as _get_version
_vers = _get_version()
__version__ = _vers["full"]
__catlass_version__ = _vers["full"]
__catlass_tag__ = _vers["tag"]
__catlass_commit__ = _vers["commit"]
os.environ["CATLASS_JIT_VERSION"] = _vers["full"]
__all__ = [
"ops",
"basic_matmul",
"batched_matmul",
"grouped_matmul_slice_m",
"matmul_add",
"padding_matmul",
"grouped_matmul_slice_k",
"optimized_matmul",
"grouped_matmul_slice_m_per_token_dequant",
"grouped_matmul_slice_m_per_token_dequant_multistage",
"grouped_matmul_slice_k_per_token_dequant",
"splitk_matmul",
"quant_matmul",
"basic_matmul_tla",
"optimized_matmul_tla",
"basic_matmul_preload_zN",
"matmul_full_loadA",
"padding_splitk_matmul",
"a2_fp8_e4m3_matmul",
"w8a16_matmul",
"w4a8_matmul",
"small_matmul",
"single_core_splitk_matmul",
"streamk_matmul",
"sparse_matmul_tla",
"quant_optimized_matmul_tla",
"ascend950_basic_matmul",
"quant_matmul_full_loadA_tla",
"strided_batched_matmul_tla",
"quant_multi_core_splitk_matmul_tla",
"ascend950_fp8_mx_matmul_aswt",
"ascend950_fp4_mx_matmul_aswt",
"ascend950_matmul_fixpipe_opti",
"ascend950_basic_matmul_gemv",
"ascend950_quant_matmul_per_group_per_block_tla",
"ascend950_matmul_full_dequant",
"mla",
"ascend950_matmul_evg",
"EvgPostprocessMode",
"ascend950_matmul_full_loadA",
"ascend950_batched_matmul",
"ascend950_streamk_matmul",
"flash_attention_infer",
"conv_bias",
"flash_attention_infer_tla",
"ascend950_flash_attention_infer",
"ascend950_fp8_mx_flash_attention_infer",
"w4a4_matmul_per_token_per_channel_dequant",
"broadcast_matmul_perblock_quant",
"ascend950_fp8_mx_batch_matmul",
"ascend950_dual_level_quant_mx_batch_matmul",
"ascend950_multi_core_splitk_matmul",
"ascend950_tail_multi_core_splitk_matmul",
"ascend950_flash_attention_chunk_prefill",
"clear_jit_cache",
"__version__",
"__catlass_version__",
]
_catlass_loaded: bool = False
def enable_mssanitizer():
"""Enable Ascend memory sanitizer for subsequent JIT compilations."""
os.environ["MS_SANITIZE_MEMORY"] = "1"
def clear_jit_cache():
"""Remove all JIT-compiled kernel cache files on disk."""
import glob as _glob
import shutil
cache_dir = os.environ.get("CATLASS_JIT_CACHE_DIR", "")
if cache_dir and os.path.isdir(cache_dir):
shutil.rmtree(cache_dir, ignore_errors=True)
home_cache = os.path.expanduser("~/.cache/catlass/jit_cache")
if os.path.isdir(home_cache):
shutil.rmtree(home_cache, ignore_errors=True)
tmp_cache = "/tmp/catlass_jit"
if os.path.isdir(tmp_cache):
shutil.rmtree(tmp_cache, ignore_errors=True)
def get_npu_arch():
"""Return the CATLASS architecture id for the active torch-npu device."""
device_count = torch_npu.npu.device_count()
if device_count <= 0:
raise RuntimeError("No Ascend NPU device is available for torch-catlass")
device_name = torch_npu.npu.get_device_name()
if re.match(r"Ascend910B.+", device_name, re.I) or re.search(
r"Ascend910_93", device_name, re.I
):
return 2201
elif re.search("Ascend950(PR|DT)", device_name, re.I):
return 3510
else:
raise RuntimeError(f"Unsupported device name: {device_name}")
def _load_so_files(lib_dir):
"""Load every shared object in ``lib_dir`` with global symbol visibility."""
for lib_file in sorted(os.listdir(lib_dir)):
if lib_file.endswith(".so"):
lib_path = os.path.join(lib_dir, lib_file)
print(f"Loading library: {lib_path}")
_dl_mode = getattr(os, "RTLD_NOW", 0x2)
if lib_file.endswith("_ms.so"):
_dl_mode |= getattr(os, "RTLD_LOCAL", 0)
else:
_dl_mode |= getattr(os, "RTLD_GLOBAL", 0x100)
ctypes.CDLL(lib_path, mode=_dl_mode)
def _find_pkg_dir():
"""Return the directory that contains the compiled ``lib/`` tree.
In a normal install this is simply the package directory (``__file__``).
In an editable install the Python sources live in the source tree while
the compiled artefacts are installed inside site-packages, so we fall
back to the site-packages location.
"""
src_base = os.path.dirname(__file__)
if os.path.isdir(os.path.join(src_base, "lib")):
return src_base
from importlib.metadata import distribution
try:
dist = distribution("torch-catlass")
sp_dir = dist.locate_file("torch_catlass")
sp_dir_str = str(sp_dir)
if os.path.isdir(os.path.join(sp_dir_str, "lib")):
return sp_dir_str
except Exception:
pass
return src_base
def _load_kernel_libs():
"""Load JIT and architecture-specific kernel libraries once per process."""
global _catlass_loaded
if not _catlass_loaded:
print("Loading kernel libraries...")
base = _find_pkg_dir()
os.environ["CATLASS_JIT_PKG_DIR"] = base
jit_dir = os.path.join(base, "lib", "jit")
if os.path.exists(jit_dir):
mode = getattr(os, "RTLD_NOW", 0x2) | getattr(os, "RTLD_GLOBAL", 0x100)
compiler = os.path.join(jit_dir, "libcatlass_kernel_jit_compiler.so")
if os.path.exists(compiler):
print(f"Loading JIT compiler: {compiler}")
ctypes.CDLL(compiler, mode=mode)
jit_lib = os.path.join(jit_dir, "libcatlass_kernel_jit.so")
if os.path.exists(jit_lib):
print(f"Loading JIT library: {jit_lib}")
ctypes.CDLL(jit_lib, mode=mode)
arch = get_npu_arch()
arch_dir = os.path.join(base, "lib", str(arch))
if os.path.exists(arch_dir):
print(f"Loading architecture-specific kernels from: {arch_dir}")
_load_so_files(arch_dir)
if not os.path.exists(jit_dir) and not os.path.exists(arch_dir):
raise RuntimeError(f"No library directory found: checked {jit_dir} and {arch_dir}")
_catlass_loaded = True
def _load_main_lib():
"""Load the PyTorch extension that registers ``torch.ops.catlass`` ops."""
base = _find_pkg_dir()
lib_path = os.path.join(base, "lib", "libcatlass_torch.so")
print(f"Loading main library: {lib_path}")
torch.ops.load_library(lib_path)
_load_kernel_libs()
_load_main_lib()
print("Importing ops module")
from . import ops
from .ops import *