From 3f0d499cfb0767a7d6b49ee5c04fafc60ca69918 Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Wed, 10 Jun 2026 03:20:58 -0700 Subject: [PATCH 01/11] [tileir] Integrate TileIR backend --- .gitmodules | 3 + python/setup_tools/utils/tools.py | 6 + python/src/main.cc | 12 +- python/triton/compiler/compiler.py | 80 +- python/triton/language/__init__.py | 6 + python/triton/language/core.py | 44 + python/triton/language/semantic.py | 3 + python/triton/language/target_info.py | 18 +- setup.py | 2 +- third_party/tileir/CMakeLists.txt | 126 + third_party/tileir/backend/__init__.py | 0 third_party/tileir/backend/code_generator.py | 231 ++ third_party/tileir/backend/compiler.py | 357 ++ third_party/tileir/backend/conf.py | 85 + third_party/tileir/backend/driver.c | 114 + third_party/tileir/backend/driver.py | 588 ++++ third_party/tileir/backend/errors.py | 14 + third_party/tileir/backend/name.conf | 1 + third_party/tileir/include/CMakeLists.txt | 3 + .../tileir/include/Transform/CMakeLists.txt | 3 + third_party/tileir/include/Transform/Passes.h | 34 + .../tileir/include/Transform/Passes.td | 93 + .../include/TritonToTileIR/CMakeLists.txt | 3 + .../tileir/include/TritonToTileIR/Passes.h | 20 + .../tileir/include/TritonToTileIR/Passes.td | 44 + .../TritonToTileIR/TritonToTileIRPass.h | 32 + .../tileir/include/TritonToTileIR/Utils.h | 193 ++ .../tileir/include/Utils/CMakeLists.txt | 0 third_party/tileir/include/Utils/Utils.h | 33 + third_party/tileir/lib/CMakeLists.txt | 3 + .../lib/Transform/AutoGenMemoryToken.cpp | 644 ++++ .../tileir/lib/Transform/CMakeLists.txt | 22 + .../tileir/lib/Transform/LiftTTCFToSCF.cpp | 93 + .../Transform/RewriteAssumeWithCudaTile.cpp | 216 ++ .../tileir/lib/TritonToTileIR/CMakeLists.txt | 21 + .../MapElementwiseExpansion.cpp | 506 +++ .../TritonToTileIR/MapElementwiseExpansion.h | 32 + .../TritonToCudaTileExperimentalPass.cpp | 0 .../lib/TritonToTileIR/TritonToTileIRPass.cpp | 2958 +++++++++++++++++ .../tileir/lib/TritonToTileIR/Utils.cpp | 469 +++ third_party/tileir/lib/Utils/CMakeLists.txt | 10 + third_party/tileir/lib/Utils/Utils.cpp | 72 + third_party/tileir/scripts/build_cuda_tile.sh | 31 + .../scripts/build_helper/Dockerfile.release | 77 + .../tileir/scripts/check_wheel_deps.py | 62 + third_party/tileir/scripts/copy_wheel_deps.py | 41 + .../tileir/scripts/patch_bytecode_utils.sh | 69 + third_party/tileir/scripts/wheel_deps.json | 5 + third_party/tileir/third_party/cuda-tile | 1 + .../tools/triton-cuda-tile-opt/CMakeLists.txt | 23 + .../RegisterTritonCudaTileDialects.h | 43 + .../triton-cuda-tile-opt.cpp | 14 + third_party/tileir/triton_tileir.cc | 153 + 53 files changed, 7693 insertions(+), 20 deletions(-) create mode 100644 .gitmodules create mode 100644 third_party/tileir/CMakeLists.txt create mode 100644 third_party/tileir/backend/__init__.py create mode 100644 third_party/tileir/backend/code_generator.py create mode 100644 third_party/tileir/backend/compiler.py create mode 100644 third_party/tileir/backend/conf.py create mode 100644 third_party/tileir/backend/driver.c create mode 100644 third_party/tileir/backend/driver.py create mode 100644 third_party/tileir/backend/errors.py create mode 100644 third_party/tileir/backend/name.conf create mode 100644 third_party/tileir/include/CMakeLists.txt create mode 100644 third_party/tileir/include/Transform/CMakeLists.txt create mode 100644 third_party/tileir/include/Transform/Passes.h create mode 100644 third_party/tileir/include/Transform/Passes.td create mode 100644 third_party/tileir/include/TritonToTileIR/CMakeLists.txt create mode 100644 third_party/tileir/include/TritonToTileIR/Passes.h create mode 100644 third_party/tileir/include/TritonToTileIR/Passes.td create mode 100644 third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h create mode 100644 third_party/tileir/include/TritonToTileIR/Utils.h create mode 100644 third_party/tileir/include/Utils/CMakeLists.txt create mode 100644 third_party/tileir/include/Utils/Utils.h create mode 100644 third_party/tileir/lib/CMakeLists.txt create mode 100644 third_party/tileir/lib/Transform/AutoGenMemoryToken.cpp create mode 100644 third_party/tileir/lib/Transform/CMakeLists.txt create mode 100644 third_party/tileir/lib/Transform/LiftTTCFToSCF.cpp create mode 100644 third_party/tileir/lib/Transform/RewriteAssumeWithCudaTile.cpp create mode 100644 third_party/tileir/lib/TritonToTileIR/CMakeLists.txt create mode 100644 third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp create mode 100644 third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h create mode 100644 third_party/tileir/lib/TritonToTileIR/TritonToCudaTileExperimentalPass.cpp create mode 100644 third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp create mode 100644 third_party/tileir/lib/TritonToTileIR/Utils.cpp create mode 100644 third_party/tileir/lib/Utils/CMakeLists.txt create mode 100644 third_party/tileir/lib/Utils/Utils.cpp create mode 100644 third_party/tileir/scripts/build_cuda_tile.sh create mode 100644 third_party/tileir/scripts/build_helper/Dockerfile.release create mode 100644 third_party/tileir/scripts/check_wheel_deps.py create mode 100644 third_party/tileir/scripts/copy_wheel_deps.py create mode 100755 third_party/tileir/scripts/patch_bytecode_utils.sh create mode 100644 third_party/tileir/scripts/wheel_deps.json create mode 160000 third_party/tileir/third_party/cuda-tile create mode 100644 third_party/tileir/tools/triton-cuda-tile-opt/CMakeLists.txt create mode 100644 third_party/tileir/tools/triton-cuda-tile-opt/RegisterTritonCudaTileDialects.h create mode 100644 third_party/tileir/tools/triton-cuda-tile-opt/triton-cuda-tile-opt.cpp create mode 100644 third_party/tileir/triton_tileir.cc diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..fd32e978f5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/tileir/third_party/cuda-tile"] + path = third_party/tileir/third_party/cuda-tile + url = https://github.com/NVIDIA/cuda-tile diff --git a/python/setup_tools/utils/tools.py b/python/setup_tools/utils/tools.py index 8443cee7da..6e1beb220d 100644 --- a/python/setup_tools/utils/tools.py +++ b/python/setup_tools/utils/tools.py @@ -22,6 +22,12 @@ def _get_flagtree_root() -> str: @dataclass class FlagtreeConfigs: + # flagtree: keep this set narrow on purpose. tileir is only added in the + # explicit default build path (setup.py "no FLAGTREE_BACKEND" branch). Putting + # tileir here would also drag it into plugin-backend builds (thrive/aipu/ + # tsingmicro/enflame), which would force those builds to satisfy tileir's + # heavy dependencies (cuda-tile submodule, GCC>=13, LLVM build path) even + # though the selected backend never uses tileir. default_backends: tuple = ("nvidia", "amd") plugin_backends: tuple = ("cambricon", "ascend", "aipu", "tsingmicro", "enflame", "hcu", "thrive") use_cuda_toolkit_backends: tuple = ('aipu', ) diff --git a/python/src/main.cc b/python/src/main.cc index 40b19f77ea..b5713107a4 100644 --- a/python/src/main.cc +++ b/python/src/main.cc @@ -4,16 +4,24 @@ namespace py = pybind11; +// flagtree: extended from FOR_EACH_5 to FOR_EACH_8 to give headroom for the +// expanded backend set (nvidia + amd + tileir + proton + tle = 5 by default; +// can be 6+ with TRITON_PLUGIN_DIRS or FLAGTREE_BACKEND=thrive/aipu/...). +// Stopping at 5 caused the C preprocessor to emit an undefined FOR_EACH_N +// when TRITON_BACKENDS_TUPLE grew past 5 entries. #define FOR_EACH_1(MACRO, X) MACRO(X) #define FOR_EACH_2(MACRO, X, ...) MACRO(X) FOR_EACH_1(MACRO, __VA_ARGS__) #define FOR_EACH_3(MACRO, X, ...) MACRO(X) FOR_EACH_2(MACRO, __VA_ARGS__) #define FOR_EACH_4(MACRO, X, ...) MACRO(X) FOR_EACH_3(MACRO, __VA_ARGS__) #define FOR_EACH_5(MACRO, X, ...) MACRO(X) FOR_EACH_4(MACRO, __VA_ARGS__) +#define FOR_EACH_6(MACRO, X, ...) MACRO(X) FOR_EACH_5(MACRO, __VA_ARGS__) +#define FOR_EACH_7(MACRO, X, ...) MACRO(X) FOR_EACH_6(MACRO, __VA_ARGS__) +#define FOR_EACH_8(MACRO, X, ...) MACRO(X) FOR_EACH_7(MACRO, __VA_ARGS__) #define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N()) #define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__) -#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, N, ...) N -#define FOR_EACH_RSEQ_N() 5, 4, 3, 2, 1, 0 +#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N +#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0 #define CONCATENATE(x, y) CONCATENATE1(x, y) #define CONCATENATE1(x, y) x##y diff --git a/python/triton/compiler/compiler.py b/python/triton/compiler/compiler.py index a86d709532..f381e55c79 100644 --- a/python/triton/compiler/compiler.py +++ b/python/triton/compiler/compiler.py @@ -76,7 +76,20 @@ def hash(self): return hashlib.sha256(key.encode("utf-8")).hexdigest() def make_ir(self, target: GPUTarget, options, codegen_fns, module_map, context): - from .code_generator import ast_to_ttir + if target.backend == "tileir": + from ..backends.tileir.code_generator import ast_to_ttir + prev = os.environ.get("FLAGTREE_COMPILING_TILEIR") + os.environ["FLAGTREE_COMPILING_TILEIR"] = "1" + try: + return ast_to_ttir(self.fn, self, context=context, options=options, codegen_fns=codegen_fns, + module_map=module_map) + finally: + if prev is None: + os.environ.pop("FLAGTREE_COMPILING_TILEIR", None) + else: + os.environ["FLAGTREE_COMPILING_TILEIR"] = prev + else: + from .code_generator import ast_to_ttir return ast_to_ttir(self.fn, self, context=context, options=options, codegen_fns=codegen_fns, module_map=module_map) @@ -437,6 +450,24 @@ def __init__(self, src, metadata_group, hash): self.function = None self._run = None + def _get_kernel_driver(self): + """Return the driver instance matching this kernel's compile-time backend. + + flagtree: when per-kernel routing sends a kernel to the tileir backend, we + need to use TileIRDriver (different load_binary signature, different launcher) + even if driver.active is still CudaDriver — so non-tle kernels can go via + tileir while tle kernels stay on nvidia in the same process. + """ + backend = self.metadata.target.backend + if backend == "tileir": + cached = getattr(CompiledKernel, "_tileir_driver_instance", None) + if cached is None: + from ..backends import backends as _backends + cached = _backends["tileir"].driver() + CompiledKernel._tileir_driver_instance = cached + return cached + return driver.active + def _init_handles(self): if self.module is not None: return @@ -452,23 +483,44 @@ def raise_(err): raise err device = driver.active.get_current_device() + # flagtree: pick the driver matching the kernel's compile-time backend, so + # nvidia and tileir kernels can coexist in the same process. + kernel_driver = self._get_kernel_driver() + is_tileir = self.metadata.target.backend == "tileir" # create launcher - self._run = driver.active.launcher_cls(self.src, self.metadata) - # not enough shared memory to run the kernel + self._run = kernel_driver.launcher_cls(self.src, self.metadata) max_shared = max_shared_mem(device) - if self.metadata.shared > max_shared: - raise_(OutOfResources(self.metadata.shared, max_shared, "shared memory")) - if hasattr(self.metadata, "tmem_size") and self.metadata.tmem_size is not None: - # Use blackwell max tmem size for now, this should be moved in device properties - max_tmem_size = 512 # tmem size in number of columns - if self.metadata.tmem_size > max_tmem_size: - raise_(OutOfResources(self.metadata.tmem_size, max_tmem_size, "tensor memory")) if knobs.runtime.kernel_load_start_hook is not None: knobs.runtime.kernel_load_start_hook(self.module, self.function, self.name, self.metadata_group, self.hash) - # TODO: n_regs, n_spills should be metadata generated when calling `ptxas` - self.module, self.function, self.n_regs, self.n_spills, self.n_max_threads = driver.active.utils.load_binary( - self.name, self.kernel, self.metadata.shared, device) - warp_size = driver.active.get_current_target().warp_size + if is_tileir: + # TileIR load_binary takes (name, kernel, device) and returns a 6-tuple that + # includes static_smem_bytes (which the tileir backend uses as the .shared field). + ( + self.module, + self.function, + self.n_regs, + self.n_spills, + self.static_smem_bytes, + self.n_max_threads, + ) = kernel_driver.utils.load_binary(self.name, self.kernel, device) + if "shared" not in self.metadata._fields: + from collections import namedtuple + KernelMetadata = namedtuple("KernelMetadata", self.metadata._fields + ("shared",)) + self.metadata = KernelMetadata(**self.metadata._asdict(), shared=self.static_smem_bytes) + if self.metadata.shared > max_shared: + raise_(OutOfResources(self.metadata.shared, max_shared, "shared memory")) + else: + if self.metadata.shared > max_shared: + raise_(OutOfResources(self.metadata.shared, max_shared, "shared memory")) + if hasattr(self.metadata, "tmem_size") and self.metadata.tmem_size is not None: + # Use blackwell max tmem size for now, this should be moved in device properties + max_tmem_size = 512 # tmem size in number of columns + if self.metadata.tmem_size > max_tmem_size: + raise_(OutOfResources(self.metadata.tmem_size, max_tmem_size, "tensor memory")) + # TODO: n_regs, n_spills should be metadata generated when calling `ptxas` + self.module, self.function, self.n_regs, self.n_spills, self.n_max_threads = kernel_driver.utils.load_binary( + self.name, self.kernel, self.metadata.shared, device) + warp_size = self.metadata.target.warp_size if self.metadata.num_warps * warp_size > self.n_max_threads: raise_(OutOfResources(self.metadata.num_warps * warp_size, self.n_max_threads, "threads")) if knobs.runtime.kernel_load_end_hook is not None: diff --git a/python/triton/language/__init__.py b/python/triton/language/__init__.py index 04d548c9a5..04f80d4f72 100644 --- a/python/triton/language/__init__.py +++ b/python/triton/language/__init__.py @@ -34,6 +34,8 @@ make_tensor_descriptor, tensor_descriptor, tensor_descriptor_type, + tileir_tensor_descriptor, + tileir_tensor_descriptor_type, add, advance, arange, @@ -142,6 +144,8 @@ "store_tensor_descriptor", "make_tensor_descriptor", "tensor_descriptor", + "tileir_tensor_descriptor", + "tileir_tensor_descriptor_type", "abs", "add", "advance", @@ -320,6 +324,8 @@ def str_to_ty(name, c): return nvidia_tensor_descriptor_type(block, shape_type, stride_type, layout) else: return amd_tensor_descriptor_type(block, shape_type, stride_type, layout) + if target_info.is_tileir(): + return tileir_tensor_descriptor_type(block, shape_type, stride_type) return tensor_descriptor_type(block, shape_type, stride_type) if name.startswith("constexpr"): diff --git a/python/triton/language/core.py b/python/triton/language/core.py index 439671c3a1..5277bdddd6 100644 --- a/python/triton/language/core.py +++ b/python/triton/language/core.py @@ -1474,6 +1474,30 @@ def __eq__(self, other): == other.strides_type) +class tileir_tensor_descriptor_type(tensor_descriptor_type): + + def __init__(self, block_type: block_type, shape_type: tuple_type, strides_type: tuple_type): + super().__init__(block_type, shape_type, strides_type) + self.ptr_type = pointer_type(block_type.element_ty) + + def _unflatten_ir(self, handles: List[ir.value], cursor: int) -> Tuple[tensor_descriptor_base, int]: + handle = handles[cursor] + cursor += 1 + ptr, cursor = self.ptr_type._unflatten_ir(handles, cursor) + shape, cursor = self.shape_type._unflatten_ir(handles, cursor) + strides, cursor = self.strides_type._unflatten_ir(handles, cursor) + shape = shape.values + strides = strides.values + value = tileir_tensor_descriptor(handle, shape, strides, self.block_type, ptr) + return value, cursor + + def _flatten_ir_types(self, builder: ir.builder, out: List[ir.type]) -> None: + tensor_descriptor_base_type._flatten_ir_types(self, builder, out) + self.ptr_type._flatten_ir_types(builder, out) + self.shape_type._flatten_ir_types(builder, out) + self.strides_type._flatten_ir_types(builder, out) + + class tensor_descriptor(tensor_descriptor_base): """A descriptor representing a tensor in global memory. """ @@ -1497,6 +1521,26 @@ def _flatten_ir(self, handles: List[ir.value]) -> None: self.strides._flatten_ir(handles) +class tileir_tensor_descriptor(tensor_descriptor): + """A descriptor representing a tensor in global memory for TileIR.""" + + def __init__(self, handle, shape: List[tensor], strides: List[tensor], block_type: block_type, ptr): + """Not called by user code.""" + super().__init__(handle, shape, strides, block_type) + self.ptr = ptr + self.type = tileir_tensor_descriptor_type( + block_type, + shape_type=self.shape.type, + strides_type=self.strides.type, + ) + + def _flatten_ir(self, handles: List[ir.value]) -> None: + handles.append(self.handle) + handles.append(self.ptr.handle) + handles.extend(s.handle for s in self.shape) + handles.extend(s.handle for s in self.strides) + + # ----------------------- # aggregate # ----------------------- diff --git a/python/triton/language/semantic.py b/python/triton/language/semantic.py index c20ed04c2c..1d0d056559 100644 --- a/python/triton/language/semantic.py +++ b/python/triton/language/semantic.py @@ -7,6 +7,7 @@ from triton.runtime import driver from .._C.libtriton import ir +from . import target_info from . import core as tl T = TypeVar('T') @@ -1968,4 +1969,6 @@ def make_tensor_descriptor(self, base: TensorTy, shape: List[TensorTy], strides: handle = self.builder.create_make_tensor_descriptor(base_handle, [s.handle for s in shape], [s.handle for s in strides], block_shape, is_signed_int, padding) + if target_info.is_tileir(): + return tl.tileir_tensor_descriptor(handle, shape, strides, type, base) return tl.tensor_descriptor(handle, shape, strides, type) diff --git a/python/triton/language/target_info.py b/python/triton/language/target_info.py index 2c1a277f04..91bf69a25f 100644 --- a/python/triton/language/target_info.py +++ b/python/triton/language/target_info.py @@ -1,7 +1,9 @@ +import os + from triton.runtime import driver from triton.runtime.jit import constexpr_function -__all__ = ["current_target"] +__all__ = ["current_target", "is_tileir"] def current_target(): @@ -18,8 +20,18 @@ def current_target(): @constexpr_function def is_cuda(): + if os.environ.get("FLAGTREE_COMPILING_TILEIR", "0") == "1": + return True + target = current_target() + return target is not None and target.backend in ("cuda", "tileir") + + +@constexpr_function +def is_tileir(): + if os.environ.get("FLAGTREE_COMPILING_TILEIR", "0") == "1": + return True target = current_target() - return target is not None and target.backend == "cuda" + return target is not None and target.backend == "tileir" @constexpr_function @@ -30,7 +42,7 @@ def cuda_capability_geq(major, minor=0): inline asm implementations that require a certain compute capability. """ target = current_target() - if target is None or target.backend != "cuda": + if target is None or target.backend not in ("cuda", "tileir"): return False assert isinstance(target.arch, int) return target.arch >= major * 10 + minor diff --git a/setup.py b/setup.py index 3c3870e472..98ea3a7bf2 100644 --- a/setup.py +++ b/setup.py @@ -653,7 +653,7 @@ def download_and_copy_dependencies(): backends = [*BackendInstaller.copy(helper.configs.extend_backends), *BackendInstaller.copy_externals()] else: print(helper.configs.default_backends) - backends = [*BackendInstaller.copy(["nvidia", "amd"]), *BackendInstaller.copy_externals()] + backends = [*BackendInstaller.copy(["nvidia", "amd", "tileir"]), *BackendInstaller.copy_externals()] #backends = [*BackendInstaller.copy(["nvidia", "amd"]), *BackendInstaller.copy_externals()] diff --git a/third_party/tileir/CMakeLists.txt b/third_party/tileir/CMakeLists.txt new file mode 100644 index 0000000000..f928ce838b --- /dev/null +++ b/third_party/tileir/CMakeLists.txt @@ -0,0 +1,126 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) +include_directories(${CMAKE_CURRENT_BINARY_DIR}/include) + +# Require modern compilers: GCC/Clang version must be >= 13 (i.e., > 13) +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) + message(FATAL_ERROR "C++ compiler version must be >= 13 (found ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION})") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + if(CMAKE_C_COMPILER_VERSION VERSION_LESS 13) + message(FATAL_ERROR "C compiler version must be >= 13 (found ${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION})") + endif() +endif() + +set(HAVE_CUDA_TILE_INSTALL OFF) +if(DEFINED ENV{CUDA_TILE_INSTALL_DIR}) + set(CUDA_TILE_INSTALL_DIR $ENV{CUDA_TILE_INSTALL_DIR}) + set(CUDA_TILE_INCLUDE_DIRS ${CUDA_TILE_INSTALL_DIR}/include/include) + set(CUDA_TILE_LIBRARY_PATH ${CUDA_TILE_INSTALL_DIR}/lib) + set(CUDA_TILE_MLIR_INCLUDE_DIRS $ENV{CUDA_TILE_MLIR_INCLUDE_DIRS}) + set(HAVE_CUDA_TILE_INSTALL ON) +else() + # ===== flagtree-local rewrite of the upstream else() branch ===== + # Upstream does a build-time `git clone https://github.com/NVIDIA/cuda-tile` + # into ${CMAKE_CURRENT_BINARY_DIR}/tileir_src and applies an inline patch. + # + # flagtree instead: + # 1) pins cuda-tile as a git submodule under third_party/cuda-tile (v13.2.0), + # so the build doesn't need network access and the version is reviewable + # in flagtree's git history; + # 2) copies the submodule to a build-tree working dir so patches/build + # artifacts don't pollute the submodule checkout; + # 3) runs the flagtree-tuned patch script (see scripts/patch_bytecode_utils.sh) + # with non-fatal failure semantics so future cuda-tile bumps don't break + # the build for cosmetic patch mismatches. + # ================================================================ + # Use the cuda-tile git submodule (pinned to v13.2.0 for LLVM API compat with flagtree). + set(CUDA_TILE_SUBMODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/cuda-tile) + if(NOT EXISTS ${CUDA_TILE_SUBMODULE_DIR}/CMakeLists.txt) + message(FATAL_ERROR + "cuda-tile submodule not initialized at ${CUDA_TILE_SUBMODULE_DIR}. " + "Run: git submodule update --init --recursive") + endif() + + # Copy sources to the build tree so patches and build artifacts don't pollute the submodule. + set(CUDA_TILE_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/cuda-tile-work) + if(NOT EXISTS ${CUDA_TILE_SOURCE_DIR}/CMakeLists.txt) + message(STATUS "Copying cuda-tile sources from submodule to ${CUDA_TILE_SOURCE_DIR}") + execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CUDA_TILE_SUBMODULE_DIR} ${CUDA_TILE_SOURCE_DIR} + RESULT_VARIABLE CUDA_TILE_COPY_RESULT + ) + if(NOT CUDA_TILE_COPY_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to copy cuda-tile sources to ${CUDA_TILE_SOURCE_DIR}") + endif() + endif() + + # Patch cuda-tile sources to compile against flagtree's pinned LLVM (commit + # f6ded0be, older than cuda-tile v13.2.0's expected LLVM 13c00cbc). The patch + # script does narrow API renames; see comments in patch_bytecode_utils.sh. + # We treat a non-zero exit from the patch script as a warning (not a fatal error) + # so that future cuda-tile / LLVM bumps don't break the build for cosmetic patch + # failures — real compile errors will surface in the build step below. + execute_process( + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/scripts/patch_bytecode_utils.sh ${CUDA_TILE_SOURCE_DIR} + WORKING_DIRECTORY ${CUDA_TILE_SOURCE_DIR} + RESULT_VARIABLE CUDA_TILE_PATCH_RESULT + ) + if(NOT CUDA_TILE_PATCH_RESULT EQUAL 0) + message(WARNING "cuda_tile patch_bytecode_utils.sh exited ${CUDA_TILE_PATCH_RESULT}; continuing") + endif() + + execute_process( + COMMAND ${CMAKE_COMMAND} -E env LLVM_SYSPATH=${LLVM_SYSPATH} LLVM_EXTERNAL_LIT=${LLVM_SYSPATH}/bin/llvm-lit bash ${CMAKE_CURRENT_SOURCE_DIR}/scripts/build_cuda_tile.sh ${CUDA_TILE_SOURCE_DIR} + WORKING_DIRECTORY ${CUDA_TILE_SOURCE_DIR} + RESULT_VARIABLE CUDA_TILE_BUILD_RESULT + ) + if(NOT CUDA_TILE_BUILD_RESULT EQUAL 0) + message(FATAL_ERROR "cuda_tile build failed with code: ${CUDA_TILE_BUILD_RESULT}") + endif() + + # After successful build, set variables for downstream usage in this configure + set(CUDA_TILE_INSTALL_DIR ${CUDA_TILE_SOURCE_DIR}/build/install) + set(CUDA_TILE_LIBRARY_PATH ${CUDA_TILE_INSTALL_DIR}/lib) + set(CUDA_TILE_INCLUDE_DIRS ${CUDA_TILE_INSTALL_DIR}/include/include) + set(CUDA_TILE_MLIR_INCLUDE_DIRS ${CUDA_TILE_SOURCE_DIR}/build/include) + set(HAVE_CUDA_TILE_INSTALL ON) + # Export to CACHE and ENV for downstream visibility + set(CUDA_TILE_INSTALL_DIR ${CUDA_TILE_INSTALL_DIR} CACHE PATH "CUDA Tile install dir" FORCE) + set(CUDA_TILE_INCLUDE_DIRS ${CUDA_TILE_INCLUDE_DIRS} CACHE PATH "CUDA Tile include dirs" FORCE) + set(CUDA_TILE_LIBRARY_PATH ${CUDA_TILE_LIBRARY_PATH} CACHE PATH "CUDA Tile library path" FORCE) + set(HAVE_CUDA_TILE_INSTALL ${HAVE_CUDA_TILE_INSTALL} CACHE BOOL "Has cuda tile install" FORCE) + set(ENV{CUDA_TILE_INSTALL_DIR} ${CUDA_TILE_INSTALL_DIR}) + set(ENV{CUDA_TILE_INCLUDE_DIRS} ${CUDA_TILE_INCLUDE_DIRS}) + set(ENV{CUDA_TILE_LIBRARY_PATH} ${CUDA_TILE_LIBRARY_PATH}) +endif() + + + +# Set up tileir specific variables for lit tests +set(TRITON_BINARY_DIR ${CMAKE_BINARY_DIR}) +set(TRITON_CUDA_TILE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(TRITON_CUDA_TILE_TOOL_DIR ${CMAKE_BINARY_DIR}/third_party/tileir/tools/triton-cuda-tile-opt) +set(TRITON_CUDA_TILE_BINARY_DIR ${CMAKE_BINARY_DIR}/bin) +set(TRITON_CUDA_TILE_LIBRARY_DIR ${CMAKE_BINARY_DIR}/third_party/tileir/lib) +set(MLIR_BINARY_DIR ${CMAKE_BINARY_DIR}) + +if(HAVE_CUDA_TILE_INSTALL) + include_directories(${CUDA_TILE_INCLUDE_DIRS}) + include_directories(${CUDA_TILE_MLIR_INCLUDE_DIRS}) +endif() + +add_subdirectory(include) +add_subdirectory(lib) +add_subdirectory(tools/triton-cuda-tile-opt) + +if(TRITON_BUILD_PYTHON_MODULE AND HAVE_CUDA_TILE_INSTALL) + add_triton_plugin(TritonTileIR ${CMAKE_CURRENT_SOURCE_DIR}/triton_tileir.cc LINK_LIBS TritonToTileIR TritonTileIRTransforms) + target_link_libraries(TritonTileIR PRIVATE Python3::Module pybind11::headers) + target_link_libraries(TritonTileIR PRIVATE ${CUDA_TILE_LIBRARY_PATH}/libCudaTileTransforms.a) + target_link_libraries(TritonTileIR PRIVATE ${CUDA_TILE_LIBRARY_PATH}/libCudaTileBytecodeWriter.a) + target_link_libraries(TritonTileIR PRIVATE ${CUDA_TILE_LIBRARY_PATH}/libCudaTileBytecodeCommon.a) +elseif(TRITON_BUILD_PYTHON_MODULE) + message(STATUS "Skip building TritonTileIR Python plugin: CUDA_TILE_INSTALL_DIR not detected") +endif() diff --git a/third_party/tileir/backend/__init__.py b/third_party/tileir/backend/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/tileir/backend/code_generator.py b/third_party/tileir/backend/code_generator.py new file mode 100644 index 0000000000..e89dead9ae --- /dev/null +++ b/third_party/tileir/backend/code_generator.py @@ -0,0 +1,231 @@ +import ast +import inspect +import re +from typing import Dict, Optional +import warnings + +import triton +import triton.knobs as knobs +import triton.language as language +from triton.language import constexpr, str_to_ty +from triton._utils import ( + find_paths_if, + get_iterable_path, +) +from triton.language.core import _unwrap_if_constexpr, base_value, base_type + +from triton.compiler.code_generator import ( + _is_list_like, + _is_constexpr, + _is_triton_tensor, + _unwrap_if_constexpr, + ASTFunction, + CodeGenerator, + enter_sub_region, + flatten_values_to_ir, + unflatten_ir_values, +) +from triton.compiler.errors import CompilationError +from triton.runtime.jit import ( + get_jit_fn_file_line, + get_full_name, + JITFunction, + JITCallable, +) + +from triton.backends.tileir.conf import TileIREnvConf + +def mangle_fn(name, arg_tys, caller_context): + # doesn't mangle ret type, which must be a function of arg tys + mangled_args = '_'.join([tileir_mangle_ty(ty) for ty in arg_tys]) + mangled_args = mangled_args.replace("'", '_sq_') + # [ and ] are not allowed in LLVM identifiers + mangled_args = mangled_args.replace('[', '_').replace(']', '_') + ret = f'{name}__{mangled_args}' + if caller_context is not None: + ret += caller_context.mangle() + return ret + +def tileir_mangle_ty(ty): + return ty.mangle() + + +def tileir_mangle_fn(name, arg_tys, constants): + # doesn't mangle ret type, which must be a function of arg tys + mangled_arg_names = "_".join([tileir_mangle_ty(ty) for ty in arg_tys]) + mangled_constants = "_".join( + [f"{i}c{repr(constants[i])}" for i in sorted(constants)] + ) + mangled_constants = mangled_constants.replace(".", "_d_") + mangled_constants = mangled_constants.replace("'", "_sq_") + # [ and ] are not allowed in LLVM identifiers + mangled_constants = mangled_constants.replace('[', '_').replace(']', '_') + ret = f'{name}__{mangled_arg_names}__{mangled_constants}' + return ret + + +class TileIRCodeGenerator(CodeGenerator): + + def __init__( + self, + context, + prototype, + gscope, + function_name, + jit_fn: JITFunction, + options, + codegen_fns, + module_map, + is_gluon=False, + module=None, + is_kernel=False, + function_types: Optional[Dict] = None, + noinline=False, + file_name: Optional[str] = None, + begin_line=0, + ): + super().__init__( + context=context, + prototype=prototype, + gscope=gscope, + function_name=function_name, + jit_fn=jit_fn, + options=options, + codegen_fns=codegen_fns, + module_map=module_map, + is_gluon=is_gluon, + module=module, + is_kernel=is_kernel, + function_types=function_types, + noinline=noinline, + file_name=file_name, + begin_line=begin_line, + ) + def get_used_vars(self, stmt): + used_vars = dict() + for node in ast.walk(stmt): + if isinstance(node, ast.FunctionDef): + continue + if isinstance(node, ast.With): + continue + if isinstance(node, ast.AugAssign): + used_vars[node.target.id] = node.target + continue + if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Load): + used_vars[node.id] = node + return used_vars + + def call_JitFunction(self, fn: JITFunction, args, kwargs, caller_context=None): + bound_args = fn.signature.bind(*args, **kwargs) + bound_args.apply_defaults() + args = bound_args.arguments + args = [args[name] for name in fn.arg_names] + for i, arg in enumerate(args): + if not isinstance(arg, base_value) or isinstance(arg, JITCallable): + args[i] = language.core.constexpr(arg) + # mangle + caller_context = caller_context or self.caller_context + arg_types = [arg.type for arg in args] + fn_name = mangle_fn(get_full_name(fn), arg_types, caller_context) + # generate function def if necessary + if not self.module.has_function(fn_name): + # If the callee is not set, we use the same debug setting as the caller + file_name, begin_line = get_jit_fn_file_line(fn) + prototype = ASTFunction([], arg_types, dict(), dict()) + # TileIR backend does not support noinline mode currently + if fn.noinline: + import warnings + + warnings.warn( + "Current backend does not support noinline mode, noinline will be turn off.", + RuntimeWarning, + ) + fn.noinline = False + generator = TileIRCodeGenerator( + self.context, + prototype, + fn.get_capture_scope(), + module=self.module, + jit_fn=fn, + function_name=fn_name, + function_types=self.function_ret_types, + noinline=fn.noinline, + file_name=file_name, + begin_line=begin_line, + options=self.builder.options, + codegen_fns=self.builder.codegen_fns, + module_map=self.builder.module_map, + is_gluon=False, + ) + try: + generator.visit(fn.parse()) + except Exception as e: + # Wrap the error in the callee with the location of the call. + if knobs.compilation.front_end_debugging: + raise + raise CompilationError(self.jit_fn.src, self.cur_node, None) from e + + callee_ret_type = generator.ret_type + self.function_ret_types[fn_name] = callee_ret_type + else: + callee_ret_type = self.function_ret_types[fn_name] + symbol = self.module.get_function(fn_name) + args_val = flatten_values_to_ir(args) + call_op = self.builder.call(symbol, args_val) + if callee_ret_type == language.void: + return None + handles = [call_op.get_result(i) for i in range(call_op.get_num_results())] + return next(unflatten_ir_values(handles, [callee_ret_type])) + + +def ast_to_ttir(fn, src, context, options, codegen_fns, module_map, module=None): + arg_types = [None] * len(fn.arg_names) + + for k, v in src.signature.items(): + idx = fn.arg_names.index(k) + arg_types[idx] = str_to_ty(v, None) + + def apply_constexpr_types(argument, indices, value): + index = indices.pop() + if len(indices) == 0: + if isinstance(argument, list): + argument[index] = constexpr(value).type + else: + argument.types[index] = constexpr(value).type + else: + apply_constexpr_types(argument[index], indices, value) + + for path, value in src.constants.items(): + apply_constexpr_types(arg_types, list(path)[::-1], value) + + prototype = ASTFunction([], arg_types, src.constants, src.attrs) + file_name, begin_line = get_jit_fn_file_line(fn) + # query function representation + from collections import namedtuple + leaves = filter(lambda v: len(v) == 1, src.constants) + constants = {fn.arg_names[i[0]]: src.constants[i] for i in leaves} + signature = src.signature + + tileir_additonal_suffix = "" + proxy = namedtuple("SpecializationProxy", ["constants", "signature",])(constants, signature) + generator = TileIRCodeGenerator( + context, + prototype, + gscope=fn.get_capture_scope(), + function_name=fn.repr(proxy) + tileir_additonal_suffix, + jit_fn=fn, + is_kernel=True, + file_name=file_name, + begin_line=begin_line, + options=options, + codegen_fns=codegen_fns, + module_map=module_map, + is_gluon=False, + ) + generator.visit(fn.parse()) + + ret = generator.module + # module takes ownership of the context + ret.context = context + ret.name = generator.function_name + return ret diff --git a/third_party/tileir/backend/compiler.py b/third_party/tileir/backend/compiler.py new file mode 100644 index 0000000000..ad6f09951a --- /dev/null +++ b/third_party/tileir/backend/compiler.py @@ -0,0 +1,357 @@ +from triton.runtime.errors import OutOfResources +from triton.backends.tileir.errors import HitFallback +from triton.runtime.cache import get_cache_manager +from triton.backends.compiler import BaseBackend, GPUTarget, Language +from triton.backends.tileir.conf import TileIREnvConf +from triton._C.libtriton import ir, passes, tileir + +# flagtree: added `field` import for the lazy tileiras lookup default_factory below. +from dataclasses import dataclass, field +import functools +from typing import Any, Dict, Tuple, Optional +from types import ModuleType +import hashlib +import re +import logging +import tempfile +import os +import subprocess +import sys +from pathlib import Path + + +def format_compute_capability(capability: int) -> str: + """ + Format compute capability for GPU architecture. + + Args: + capability: Numeric compute capability (e.g., 80, 90, 100) + + Returns: + Formatted architecture string (e.g., "sm_80", "sm_90a", "sm_100a") + + Note: + - Hopper (sm_90) and newer architectures get 'a' suffix + - Ampere (sm_80) and older architectures have no suffix + """ + if capability >= 90: # Hopper and newer + return f"sm_{capability}a" + else: # Ampere and older + return f"sm_{capability}" + + +if sys.version_info >= (3, 12): + TemporaryDirectory = tempfile.TemporaryDirectory +else: + import shutil + from contextlib import contextmanager + + @contextmanager + def TemporaryDirectory(suffix=None, prefix=None, dir=None, delete=True): + temp_dir = tempfile.mkdtemp(suffix, prefix, dir) + try: + yield temp_dir + finally: + if delete: + shutil.rmtree(temp_dir) + + +@dataclass(frozen=True) +class TileIROptions: + ########################## tileIR core options ########################## + backend_name: str = "tileir" + arch: str = None + num_ctas: int = 1 + # tileir use num_stages to control the op cost, see + num_stages: int = 3 + # tileir use opt_level to control the optimization level, see + opt_level: int = 3 + # tileir use occupancy to control the register usage, see + occupancy: int = 1 + # tileir use enable_fp_fusion to control the fma fusion, see + enable_fp_fusion: bool = True + # flagtree: defer the tileiras lookup to instantiation time so that merely + # importing the tileir backend doesn't require the binary to be present + # (e.g. AMD-only builds, CUDA < 13, or systems where tileiras isn't on PATH). + # Upstream fork calls get_tileiras_path() at class-definition time and + # raises on import when tileiras is missing. + tileir_tileiras_path: str = field(default_factory=TileIREnvConf.get_tileiras_path) + + # type and precision control, compatibility with other backend + supported_fp8_dtypes: Tuple[str] = ("fp8e5", "fp8e4b15") + deprecated_fp8_dot_operand_dtypes: Tuple[str] = () + default_dot_input_precision: str = "tf32" + allowed_dot_input_precisions: Tuple[str] = ( + "tf32", + "tf32x3", + "bf16x3", + "bf16x6", + "ieee", + ) + + ########################## compatibility with other backend ########################## + # tileir doesn't need these flags, just for compatibility with other backend + num_warps: int = 4 + cluster_dims: tuple = (1, 1, 1) + instrumentation_mode: str = "" + debug: bool = False + sanitize_overflow: bool = True + extern_libs: dict = None + # maxnreg in tileir backend is just for compatibility with other backend + # tileir use occupancy to control the register usage. + maxnreg: Optional[int] = None + launch_pdl: bool = False + launch_cooperative_grid: bool = False + max_num_imprecise_acc_default: bool = None + # workaround for tileir memory model + # currently we only autogen alias mem token, non-alias is not supported + enable_autogen_alias_mem_token: bool = True + + # Dynamic environment-dependent properties + # These properties influence the behavior of the tile compiler + # and need to be updated automatically when accessed to reflect current environment settings + @property + def enable_ftz(self): + return TileIREnvConf.enable_ftz() + + @property + def enable_approx(self): + return TileIREnvConf.enable_approx() + + def __post_init__(self): + assert self.num_warps > 0 and (self.num_warps & (self.num_warps - 1)) == 0, ( + "num_warps must be a power of 2" + ) + + def hash(self): + hash_dict = dict(self.__dict__) + # Get all property values from class __dict__ + for name, value in type(self).__dict__.items(): + if isinstance(value, property): + hash_dict[name] = getattr(self, name) + key = "_".join([f"{name}-{val}" for name, val in sorted(hash_dict.items())]) + return hashlib.sha256(key.encode("utf-8")).hexdigest() + + +def get_tileir_version(): + return "13.1" + + +class TileIRBackend(BaseBackend): + def get_module_map(self): + from triton.language.extra.cuda import libdevice + + return {"triton.language.extra.libdevice": libdevice} + + @staticmethod + def supports_target(target: GPUTarget): + return target.backend == "tileir" + + def _parse_arch(self, arch): + pattern = r"^sm(\d+)$" + match = re.fullmatch(pattern, arch) + if not match: + raise ValueError(f"TRITON_OVERRIDE_ARCH must have the form {pattern}") + return int(match.group(1)) + + def __init__(self, target: GPUTarget) -> None: + super().__init__(target) + self.binary_ext = "cubin" + + def parse_options(self, opts) -> Any: + args = {"arch": os.getenv("TRITON_OVERRIDE_ARCH", f"sm{self.target.arch}")} + args.update( + { + k: opts[k] + for k in TileIROptions.__dataclass_fields__.keys() + if k in opts + if opts[k] is not None + } + ) + capability = int(self._parse_arch(args["arch"])) + if "supported_fp8_dtypes" not in args: + supported_fp8_dtypes = set(TileIROptions.supported_fp8_dtypes) + # todo: sm90 or 89? oait uses 89, we use 90 + if capability >= 90: + supported_fp8_dtypes.add("fp8e4nv") + supported_fp8_dtypes.add("fp8e5") + args["supported_fp8_dtypes"] = tuple(sorted(supported_fp8_dtypes)) + + if "deprecated_fp8_dot_operand_dtypes" not in args: + if capability >= 90: + args["deprecated_fp8_dot_operand_dtypes"] = ("fp8e4b15",) + + if "enable_fp_fusion" not in args: + args["enable_fp_fusion"] = os.getenv("TRITON_DEFAULT_FP_FUSION", "1") == "1" + + args["max_num_imprecise_acc_default"] = 2**30 if capability == 90 else 0 + return TileIROptions(**args) + + def pack_metadata(self, metadata): + return ( + metadata.num_warps, + metadata.num_ctas, + ) + + def get_codegen_implementation(self, options): + import triton.language.extra.cuda as cuda + + capability = int(self._parse_arch(options.arch)) + codegen_fns = { + "convert_custom_types": cuda.convert_custom_float8_sm80 + if capability >= 80 + else cuda.convert_custom_float8_sm70, + "min_dot_size": lambda lhs, rhs: (1, 1, 1), + } + return codegen_fns + + def load_dialects(self, ctx): + tileir.load_dialects(ctx) + + @staticmethod + def call_tileiras(mod, metadata, opt: TileIROptions, capability): + name = metadata["name"] + fn_cache_manager = get_cache_manager(metadata["hash"]) + + tileiras = opt.tileir_tileiras_path + tileiras_cmd = [ + tileiras, + f"--gpu-name=sm_{capability}", + f"--opt-level={opt.opt_level}", + ] + # Save bytecode to cache + bytecode = tileir.write_bytecode(mod) + bytecode_cache_name = f"{name}.bytecode" + bytecode_file = fn_cache_manager.put(bytecode, bytecode_cache_name) + + # Use temp file for cubin output to avoid race conditions. + with ( + tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".log") as flog, + tempfile.NamedTemporaryFile(delete=False, suffix=".cubin") as fbin, + ): + tileiras_cmd.append(bytecode_file) + tileiras_cmd.append(f"-o") + tileiras_cmd.append(fbin.name) + + try: + try: + subprocess.run(tileiras_cmd, check=True, close_fds=False, stderr=flog) + except subprocess.CalledProcessError as e: + with open(flog.name) as log_file: + log = log_file.read() + + if "uses too much shared data" in log: + pattern = r"0x([0-9a-fA-F]+) bytes, 0x([0-9a-fA-F]+) max" + match = re.search(pattern, log) + if match: + used_smem = int(match.group(1), 16) + max_smem = int(match.group(2), 16) + raise OutOfResources(used_smem, max_smem, "shared memory") + if "allocated tmem out of resource" in log: + # "allocated tmem out of resource: vs " + pattern = ( + r"allocated tmem out of resource:\s*([0-9]+)\s*vs\s*([0-9]+)" + ) + match = re.search(pattern, log) + if match: + used_tmem = int(match.group(1)) + max_tmem = int(match.group(2)) + raise OutOfResources(used_tmem, max_tmem, "tensor memory") + error = f"`tileiras` failed with error code {e.returncode}" + raise RuntimeError( + f"{error}\n" + f"`tileiras` stderr:\n{log}\n" + f"Repro command: {' '.join(str(item) for item in tileiras_cmd)}\n" + ) + with open(fbin.name, "rb") as f: + cubin = f.read() + finally: + # flagtree: clean up both temp files on every path (success or error). + # Previously the .log file leaked on success — long-running workloads + # that JIT many kernels would accumulate one .log per compile in /tmp. + if os.path.exists(flog.name): + os.remove(flog.name) + if os.path.exists(fbin.name): + os.remove(fbin.name) + return cubin + + @staticmethod + def make_ttir(mod, metadata, opt: TileIROptions, capability): + # TODO: check these transform passes + # flagtree: the upstream-newer fork uses mod.name; flagtree's MLIR API + # (older) exposes the entry function name via get_entry_func_name(). + metadata["name"] = mod.get_entry_func_name() + pm = ir.pass_manager(mod.context) + pm.enable_debug() + passes.common.add_inliner(pm) + passes.ttir.add_combine(pm) + passes.common.add_canonicalizer(pm) + passes.common.add_cse(pm) + passes.common.add_licm(pm) + passes.common.add_symbol_dce(pm) + # passes.ttir.add_loop_unroll(pm) + pm.run(mod, "make_ttir") + return mod + + @staticmethod + def make_tileir(mod, metadata, opt: TileIROptions, capability): + pm = ir.pass_manager(mod.context) + pm.enable_debug() + # Inherit LiftControlflowToSCF from upstream to adapt to `ControlFlow` within `triton.func` + tileir.passes.add_lift_tt_cf_to_scf(pm) + # The root IR for ttir is builtin moduleOp and all + # cuda-tile ir must under tileir_moduleOp. + # So, we will insert an tileir moduleOp directly at the beginning of TritonToCudaTile pass. + tileir.passes.add_assume_to_tileir(pm) + tileir.passes.add_triton_to_cudatile( + pm, + opt.enable_approx, + opt.enable_ftz, + capability, + metadata["num_ctas"], + metadata["num_warps"], + opt.occupancy, + metadata["num_stages"], + ) + tileir.passes.add_auto_gen_memtoken(pm, opt.enable_autogen_alias_mem_token) + passes.common.add_inliner(pm) + if opt.enable_fp_fusion: + tileir.passes.add_fma_fusion(pm) + tileir.passes.add_strip_debuginfo(pm) + pm.run(mod, "make_tileir") + if not tileir.only_contain_legal_dialects(mod): + if os.environ.get("FLAGTREE_TILEIR_DUMP_FAILED_IR", "0") == "1": + safe_name = re.sub(r"[^a-zA-Z0-9_]+", "_", metadata.get("name", "unknown")) + with open(f"/tmp/flagtree_tileir_failed_{safe_name}.mlir", "w") as f: + f.write(str(mod)) + raise RuntimeError( + "Triton ttir to tileir ir failed. Some ttir ops cannot be converted to tileir." + ) + + pattern = r"entry @([a-zA-Z0-9_]*)\(" + match = re.findall(pattern, mod.__str__()) + if len(match) != 1: + raise RuntimeError("Kernel Name matching fail") + return mod + + @staticmethod + def make_cubin(mod, metadata, opt: TileIROptions, capability): + return TileIRBackend.call_tileiras(mod, metadata, opt, capability) + + def add_stages(self, stages, options, language): + assert language == Language.TRITON, "Only TRITON language is supported for now" + capability = int(self._parse_arch(options.arch)) + stages["ttir"] = lambda src, metadata: self.make_ttir( + src, metadata, options, capability + ) + stages["tileir"] = lambda src, metadata: self.make_tileir( + src, metadata, options, capability + ) + stages["cubin"] = lambda src, metadata: self.make_cubin( + src, metadata, options, capability + ) + + @functools.lru_cache() + def hash(self): + version = get_tileir_version() + return f"{'tileir'}-{version}-{self.target.arch}" diff --git a/third_party/tileir/backend/conf.py b/third_party/tileir/backend/conf.py new file mode 100644 index 0000000000..b783b9d2b9 --- /dev/null +++ b/third_party/tileir/backend/conf.py @@ -0,0 +1,85 @@ +from contextlib import contextmanager +import os +import triton + + +class TileIREnvConf: + @staticmethod + def enable_approx(): + # Enable approximate calculation, trading off numerical precision for performance gains + return os.getenv("TILEIR_ENABLE_APPROX", "0") == "1" + + @staticmethod + def enable_ftz(): + # Enable flush denormal to zero, trading off numerical precision for performance gains + return os.getenv("TILEIR_ENABLE_FTZ", "0") == "1" + + @staticmethod + def enable_autogen_alias_mem_token(): + return os.getenv("TILEIR_ENABLE_AUTOGEN_ALIAS_MEM_TOKEN", "1") == "1" + + @staticmethod + def get_fmad_flag(): + # Default to True, but allow disabling via env var + return os.getenv("TILE_IR_DISABLE_FMAD", "0") != "1" + + @staticmethod + def get_tileiras_path(): + env_path = os.getenv("TRITON_TILEIRAS_PATH", None) + if env_path is None: + # Check if tileiras exists in system PATH + from shutil import which + + tileiras_path = which("tileiras") + if tileiras_path is None: + raise RuntimeError( + "tileiras not found in PATH and TRITON_TILEIRAS_PATH not set" + ) + return tileiras_path + return os.path.join(env_path, "tileiras") + + @staticmethod + def get_device(): + return "cpu" if os.environ.get("ENABLE_CPU_TORCH", False) else "cuda" + + @staticmethod + def in_nightly_pipeline(): + return os.getenv("RUN_FULL_TEST", "0") == "1" + + @staticmethod + def in_release_pipeline(): + """Check if running in release pipeline environment""" + return os.getenv("NVT_RUN_RELEASE_PIPELINE", "0") == "1" + + @staticmethod + def get_sm_arch(): + import torch + + device = "cuda" + cc = torch.cuda.get_device_capability(device) + sm_arch = f"sm{cc[0]}{cc[1]}" + return sm_arch + + @staticmethod + def enable_tma_offset_assert_check(): + return os.getenv("NVT_TMA_OFFSET_CHECK", "0") == "1" + + +@contextmanager +def set_env_var(var_name, new_value): + # Save the original value of the environment variable + original_value = os.getenv(var_name, None) + + # Set the new value + if new_value is None and var_name in os.environ: + del os.environ[var_name] + elif new_value is not None: + os.environ[var_name] = str(new_value) + try: + yield + finally: + # Reset to the original value or remove the variable + if original_value is not None: + os.environ[var_name] = original_value + elif var_name in os.environ: + del os.environ[var_name] diff --git a/third_party/tileir/backend/driver.c b/third_party/tileir/backend/driver.c new file mode 100644 index 0000000000..e52fdf0c7f --- /dev/null +++ b/third_party/tileir/backend/driver.c @@ -0,0 +1,114 @@ +#include "cuda.h" +#include +#include +#define PY_SSIZE_T_CLEAN +#include + +// Raises a Python exception and returns false if code is not CUDA_SUCCESS. +static bool gpuAssert(CUresult code, const char *file, int line) { + if (code == CUDA_SUCCESS) + return true; + + const char *prefix = "Triton Error [TileIR]: "; + const char *str; + cuGetErrorString(code, &str); + char err[1024] = {0}; + strcat(err, prefix); + strcat(err, str); + PyGILState_STATE gil_state; + gil_state = PyGILState_Ensure(); + PyErr_SetString(PyExc_RuntimeError, err); + PyGILState_Release(gil_state); + return false; +} + +// To be used only *outside* a Py_{BEGIN,END}_ALLOW_THREADS block. +#define CUDA_CHECK_AND_RETURN_NULL(ans) \ + do { \ + if (!gpuAssert((ans), __FILE__, __LINE__)) \ + return NULL; \ + } while (0) + +// To be used inside a Py_{BEGIN,END}_ALLOW_THREADS block. +#define CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS(ans) \ + do { \ + if (!gpuAssert((ans), __FILE__, __LINE__)) { \ + PyEval_RestoreThread(_save); \ + return NULL; \ + } \ + } while (0) + +// Using CUDA driver API to load the tile binary, default path +static PyObject *loadTileIRBinary(PyObject *self, PyObject *args) { + const char *name; + const char *data; + Py_ssize_t data_size; + int device; + if (!PyArg_ParseTuple(args, "ss#i", &name, &data, &data_size, &device)) { + return NULL; + } + + CUfunction fun; + CUmodule mod; + int32_t n_regs = 0; + int32_t n_spills = 0; + int32_t n_max_threads = 0; + int32_t static_smem_bytes = 0; + + // create driver handles + CUcontext pctx = 0; + Py_BEGIN_ALLOW_THREADS; + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS(cuCtxGetCurrent(&pctx)); + + if (!pctx) { + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS( + cuDevicePrimaryCtxRetain(&pctx, device)); + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS(cuCtxSetCurrent(pctx)); + } + + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS(cuModuleLoadData(&mod, data)); + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS( + cuModuleGetFunction(&fun, mod, name)); + + // Get number of allocated registers, spilled registers, and maximum size of + // staticlly allocated shared memory from the CU function. + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS( + cuFuncGetAttribute(&n_regs, CU_FUNC_ATTRIBUTE_NUM_REGS, fun)); + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS( + cuFuncGetAttribute(&n_spills, CU_FUNC_ATTRIBUTE_LOCAL_SIZE_BYTES, fun)); + n_spills /= 4; // Convert bytes to number of 32-bit registers. + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS(cuFuncGetAttribute( + &static_smem_bytes, CU_FUNC_ATTRIBUTE_SHARED_SIZE_BYTES, fun)); + CUDA_CHECK_AND_RETURN_NULL_ALLOW_THREADS(cuFuncGetAttribute( + &n_max_threads, CU_FUNC_ATTRIBUTE_MAX_THREADS_PER_BLOCK, fun)); + + Py_END_ALLOW_THREADS; + + if (PyErr_Occurred()) { + return NULL; + } + return Py_BuildValue("(KKiiii)", (uint64_t)mod, (uint64_t)fun, n_regs, + n_spills, static_smem_bytes, n_max_threads); +} + +static PyMethodDef ModuleMethods[] = { + {"load_tileir_binary", loadTileIRBinary, METH_VARARGS, + "Load provided tileir into CUDA driver"}, + {NULL, NULL, 0, NULL} // sentinel +}; + +static struct PyModuleDef ModuleDef = {PyModuleDef_HEAD_INIT, "tileir_utils", + NULL, // documentation + -1, // size + ModuleMethods}; + +PyMODINIT_FUNC PyInit_tileir_utils(void) { + PyObject *m = PyModule_Create(&ModuleDef); + if (m == NULL) { + return NULL; + } + + PyModule_AddFunctions(m, ModuleMethods); + + return m; +} diff --git a/third_party/tileir/backend/driver.py b/third_party/tileir/backend/driver.py new file mode 100644 index 0000000000..0af0a8f4f5 --- /dev/null +++ b/third_party/tileir/backend/driver.py @@ -0,0 +1,588 @@ +import functools +import sys +import os +import subprocess +from dataclasses import dataclass +from typing import Any +import shutil +from pathlib import Path +import tempfile +import threading +# flagtree: do NOT import torch at module level. Backend discovery imports this module +# even when tileir is inactive (e.g. AMD-only systems, CUDA-less environments). Top-level +# `import torch` would cause `import triton` to fail with ModuleNotFoundError on those +# systems. All functions that need torch already do their own lazy `import torch`. +from triton.backends.nvidia.driver import ( + library_dirs, + include_dirs, + libraries, + ty_to_cpp, +) + +from triton import knobs +from triton.runtime.build import compile_module_from_src +from triton.runtime.cache import get_cache_manager +from triton.backends.compiler import GPUTarget +from triton.backends.driver import GPUDriver +from triton.backends.tileir.conf import TileIREnvConf +from triton.tools.tensor_descriptor import TensorDescriptor + + +# ------------------------ +# Utils +# ------------------------ + + +class TileIRUtils(object): + def __new__(cls): + if not hasattr(cls, "instance"): + cls.instance = super(TileIRUtils, cls).__new__(cls) + return cls.instance + + def __init__(self): + tile_mod_path = dirname + nvidia_mod_path = os.path.join(os.path.dirname(dirname), "nvidia") + tile_mod = compile_module_from_src( + Path(os.path.join(tile_mod_path, "driver.c")).read_text(), + "tileir_utils", + library_dirs(), + include_dirs, + libraries, + ) + nvidia_mod = compile_module_from_src( + Path(os.path.join(nvidia_mod_path, "driver.c")).read_text(), + "cuda_utils", + library_dirs(), + include_dirs, + libraries, + ) + self.init_nvidia_function(nvidia_mod) + self.init_tileir_function(tile_mod) + + def init_tileir_function(self, mod): + self.load_binary = mod.load_tileir_binary + + def init_nvidia_function(self, mod): + self.get_device_properties = mod.get_device_properties + self.cuOccupancyMaxActiveClusters = mod.cuOccupancyMaxActiveClusters + self.set_printf_fifo_size = mod.set_printf_fifo_size + + +# ------------------------ +# Launcher +# ------------------------ + + +dirname = os.path.dirname(__file__) + +FLOAT_STORAGE_TYPE = { + "fp16": "uint16_t", + "bf16": "uint16_t", + "fp32": "uint32_t", + "f32": "uint32_t", + "fp64": "uint64_t", +} +FLOAT_PACK_FUNCTION = { + "fp16": "pack_fp16", + "bf16": "pack_bf16", + "fp32": "pack_fp32", + "f32": "pack_fp32", + "fp64": "pack_fp64", +} + + +_BASE_ARGS_FORMAT = "iiiKKpOOOO" +_BASE_ARGS_FORMAT_LEN = len(_BASE_ARGS_FORMAT) + + +def make_launcher(constants, signature): + def _flatten_signature(sig, output): + # Flatten tuples + if isinstance(sig, tuple): + for x in sig: + _flatten_signature(x, output) + else: + output.append(sig) + + def _extracted_type(ty): + if isinstance(ty, tuple): + val = ",".join(map(_extracted_type, ty)) + return f"[{val}]" + if ty[0] == "*": + return "PyObject*" + if ty in ("constexpr", "nvTmaDesc"): + return "PyObject*" + return ty_to_cpp(ty) + + def format_of(ty): + if isinstance(ty, tuple): + val = "".join(map(format_of, ty)) + return f"({val})" + if ty[0] == "*": + return "O" + if ty in ("constexpr", "nvTmaDesc"): + return "O" + return { + "double": "d", + "long": "l", + "int8_t": "b", + "int16_t": "h", + "int32_t": "i", + "int64_t": "L", + "uint8_t": "B", + "uint16_t": "H", + "uint32_t": "I", + "uint64_t": "K", + }[ty_to_cpp(ty)] + + args_format = "".join([format_of(ty) for ty in signature.values()]) + format = _BASE_ARGS_FORMAT + args_format + + flat_signature = [] + for sig in signature.values(): + _flatten_signature(sig, flat_signature) + signature = {i: s for i, s in enumerate(flat_signature)} + args_list = ( + ", " + ", ".join(f"&_arg{i}" for i, ty in signature.items()) + if len(signature) > 0 + else "" + ) + # Record the end of regular arguments; + # subsequent arguments are architecture-specific descriptors, such as tensor descriptors for CUDA. + arg_decl_list = [] + for i, ty in signature.items(): + if ty == "constexpr": + continue + if ty in FLOAT_STORAGE_TYPE: + arg_decl_list.append(f"{FLOAT_STORAGE_TYPE[ty]} arg{i}") + else: + arg_decl_list.append(f"{ty_to_cpp(ty)} arg{i}") + arg_decls = ", ".join(arg_decl_list) + internal_args_list = [] + for i, ty in signature.items(): + if ty[0] == "*": + internal_args_list.append(f"ptr_info{i}.dev_ptr") + elif ty in FLOAT_STORAGE_TYPE: + internal_args_list.append(f"_arg{i}_storage") + elif ty == "nvTmaDesc": + # Note: we have to dereference the pointer + internal_args_list.append(f"*tma_ptr{i}") + elif ty != "constexpr": + internal_args_list.append(f"_arg{i}") + + import torch + + device_id = torch.cuda.current_device() + # generate glue code + newline = "\n " + float_storage_decls = [ + f"{FLOAT_STORAGE_TYPE[ty]} _arg{i}_storage = {FLOAT_PACK_FUNCTION[ty]}(_arg{i});" + for i, ty in signature.items() + if ty in FLOAT_STORAGE_TYPE + ] + params = [f"&arg{i}" for i, ty in signature.items() if ty != "constexpr"] + src = f""" +#include \"cuda.h\" +#include +#include +#include + +static inline void gpuAssert(CUresult code, const char *file, int line) +{{ + if (code != CUDA_SUCCESS) + {{ + const char* prefix = "Triton Error [TileIR]: "; + const char* str; + cuGetErrorString(code, &str); + char err[1024] = {{0}}; + strcat(err, prefix); + strcat(err, str); + PyGILState_STATE gil_state; + gil_state = PyGILState_Ensure(); + PyErr_SetString(PyExc_RuntimeError, err); + PyGILState_Release(gil_state); + }} +}} + +#define CUDA_CHECK(ans) {{ gpuAssert((ans), __FILE__, __LINE__); }} + +typedef CUresult (*cuLaunchKernelEx_t)(const CUlaunchConfig* config, CUfunction f, void** kernelParams, void** extra); + +static cuLaunchKernelEx_t getLaunchKernelExHandle() {{ + // Open the shared library + void* handle = dlopen("libcuda.so.1", RTLD_LAZY); + if (!handle) {{ + PyErr_SetString(PyExc_RuntimeError, "Failed to open libcuda.so.1"); + return NULL; + }} + // Clear any existing error + dlerror(); + cuLaunchKernelEx_t cuLaunchKernelExHandle = (cuLaunchKernelEx_t)dlsym(handle, "cuLaunchKernelEx"); + // Check for errors + const char *dlsym_error = dlerror(); + if (dlsym_error) {{ + PyErr_SetString(PyExc_RuntimeError, "Failed to retrieve cuLaunchKernelEx from libcuda.so.1"); + return NULL; + }} + return cuLaunchKernelExHandle; +}} + +static void _launch(int numTilesX, int numTilesY, int numTilesZ, int launch_pdl, CUstream stream, CUfunction function{", " + arg_decls if len(arg_decls) > 0 else ""}) {{ + void *params[] = {{ {", ".join(f"{i}" for i in params)} }}; + if (numTilesX*numTilesY*numTilesZ > 0) {{ + int numAttrs = 1; + CUlaunchAttribute launchAttr[2]; + launchAttr[0].id = CU_LAUNCH_ATTRIBUTE_CLUSTER_SCHEDULING_POLICY_PREFERENCE; + launchAttr[0].value.clusterSchedulingPolicyPreference = CU_CLUSTER_SCHEDULING_POLICY_SPREAD; + if (launch_pdl != 0) {{ + CUlaunchAttribute pdlAttr = {{ .id = CU_LAUNCH_ATTRIBUTE_PROGRAMMATIC_STREAM_SERIALIZATION, .value = 1}}; + launchAttr[numAttrs++] = pdlAttr; + }} + CUlaunchConfig config; + config.gridDimX = numTilesX; + config.gridDimY = numTilesY; + config.gridDimZ = numTilesZ; + config.blockDimX = 1; + config.blockDimY = 1; + config.blockDimZ = 1; + config.sharedMemBytes = 0; + config.hStream = stream; + config.attrs = launchAttr; + config.numAttrs = numAttrs; + static cuLaunchKernelEx_t cuLaunchKernelExHandle = NULL; + if (cuLaunchKernelExHandle == NULL) {{ + cuLaunchKernelExHandle = getLaunchKernelExHandle(); + }} + CUDA_CHECK(cuLaunchKernelExHandle(&config, function, params, 0)); + }} +}} + +typedef struct _DevicePtrInfo {{ + CUdeviceptr dev_ptr; + bool valid; +}} DevicePtrInfo; + +static inline DevicePtrInfo getPointer(PyObject *obj, int idx) {{ + DevicePtrInfo ptr_info; + ptr_info.dev_ptr = 0; + ptr_info.valid = true; + if (PyLong_Check(obj)) {{ + ptr_info.dev_ptr = PyLong_AsUnsignedLongLong(obj); + return ptr_info; + }} + if (obj == Py_None) {{ + // valid nullptr + return ptr_info; + }} + PyObject *ptr = PyObject_GetAttrString(obj, "data_ptr"); + if(ptr){{ + PyObject *empty_tuple = PyTuple_New(0); + PyObject *ret = PyObject_Call(ptr, empty_tuple, NULL); + Py_DECREF(empty_tuple); + Py_DECREF(ptr); + if (!PyLong_Check(ret)) {{ + PyErr_SetString(PyExc_TypeError, "data_ptr method of Pointer object must return 64-bit int"); + ptr_info.valid = false; + return ptr_info; + }} + ptr_info.dev_ptr = PyLong_AsUnsignedLongLong(ret); + if(!ptr_info.dev_ptr) + return ptr_info; + uint64_t dev_ptr; + int status = cuPointerGetAttribute(&dev_ptr, CU_POINTER_ATTRIBUTE_DEVICE_POINTER, ptr_info.dev_ptr); + if (status == CUDA_ERROR_INVALID_VALUE) {{ + PyErr_Format(PyExc_ValueError, + "Pointer argument (at %d) cannot be accessed from Triton (cpu tensor?)", idx); + ptr_info.valid = false; + }} + ptr_info.dev_ptr = dev_ptr; + Py_DECREF(ret); // Thanks ChatGPT! + return ptr_info; + }} + PyErr_SetString(PyExc_TypeError, "Pointer argument must be either uint64 or have data_ptr method"); + ptr_info.valid = false; + return ptr_info; +}} + +static uint16_t pack_fp16(double f) {{ + uint16_t result; + // from https://github.com/python/pythoncapi-compat +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) + _PyFloat_Pack2(f, (void*)&result, 1); +#else + PyFloat_Pack2(f, (void*)&result, 1); +#endif + return result; +}} + +static uint16_t pack_bf16(double f) {{ + float f32 = (float)f; + uint32_t u32 = *(uint32_t*)&f32; + return (uint16_t)(u32 >> 16); +}} + +static uint32_t pack_fp32(double f) {{ + float f32 = (float)f; + return *(uint32_t*)&f32; +}} + +static uint64_t pack_fp64(double f) {{ + return *(uint64_t*)&f; +}} + +static PyObject* launch(PyObject* self, PyObject* args) {{ + int numTilesX, numTilesY, numTilesZ; + uint64_t _stream; + uint64_t _function; + int launch_pdl; + PyObject *launch_enter_hook = NULL; + PyObject *launch_exit_hook = NULL; + PyObject *kernel_metadata = NULL; + PyObject *launch_metadata = NULL; + {" ".join([f"{_extracted_type(ty)} _arg{i}; " for i, ty in signature.items()])} + if(!PyArg_ParseTuple(args, \"{format}\", &numTilesX, &numTilesY, &numTilesZ, + &_stream, &_function, &launch_pdl, + &kernel_metadata, &launch_metadata, + &launch_enter_hook, &launch_exit_hook {args_list})) {{ + return NULL; + }} + + // extract launch metadata + if (launch_enter_hook != Py_None){{ + PyObject* args = Py_BuildValue("(O)", launch_metadata); + PyObject* ret = PyObject_CallObject(launch_enter_hook, args); + Py_DECREF(args); + if (!ret) + return NULL; + }} + + // todo: triton doesn't need this fix, do we make something wrong? + CUcontext ctx = NULL; + cuCtxGetCurrent(&ctx); + if (!ctx) {{ + CUdevice device; + cuDeviceGet(&device, /*ordinal=*/{device_id}); + cuDevicePrimaryCtxRetain(&ctx, /*device=*/device); + cuCtxSetCurrent(ctx); + }} + // raise exception asap + {"; ".join([f"DevicePtrInfo ptr_info{i} = getPointer(_arg{i}, {i}); if (!ptr_info{i}.valid) return NULL;" if ty[0] == "*" else "" for i, ty in signature.items()])}; + {newline.join(float_storage_decls)} + Py_BEGIN_ALLOW_THREADS; + + _launch(numTilesX, numTilesY, numTilesZ, launch_pdl, (CUstream)_stream, (CUfunction)_function{", " + ", ".join(internal_args_list) if len(internal_args_list) > 0 else ""}); + Py_END_ALLOW_THREADS; + if (PyErr_Occurred()) {{ + return NULL; + }} + + if(launch_exit_hook != Py_None){{ + PyObject* args = Py_BuildValue("(O)", launch_metadata); + PyObject* ret = PyObject_CallObject(launch_exit_hook, args); + Py_DECREF(args); + if (!ret) + return NULL; + + }} + + // return None + Py_INCREF(Py_None); + return Py_None; +}} + +static PyMethodDef ModuleMethods[] = {{ + {{"launch", launch, METH_VARARGS, "Entry point for all kernels with this signature"}}, + {{NULL, NULL, 0, NULL}} // sentinel +}}; + +static struct PyModuleDef ModuleDef = {{ + PyModuleDef_HEAD_INIT, + "__triton_launcher", + NULL, //documentation + -1, //size + ModuleMethods +}}; + +PyMODINIT_FUNC PyInit___triton_launcher(void) {{ + PyObject *m = PyModule_Create(&ModuleDef); + if(m == NULL) {{ + return NULL; + }} + PyModule_AddFunctions(m, ModuleMethods); + return m; +}} +""" + return src + + +# This function unpacks a tensordesc object into its components: +# - data pointer +# - shape dimensions +# - stride values +def make_tensordesc_arg(arg): + assert isinstance(arg, TensorDescriptor) + data_ptr = arg.base.data_ptr() + shape = arg.shape + strides = arg.strides + # Currently only contiguous tensors are supported + assert strides[-1] == 1 + # The 0 is a placeholder that replaces the tensordesc type when passing to kernel. + # nvidia oss backend passes tensordesc directly, but tileir needs to decompose it. + result = [0, data_ptr, *shape, *strides] + return result + + +def wrap_handle_tensordesc(launcher): + def inner(*args): + # flagtree: upstream used `args[:9]`, but TileIRLauncher.__call__ unconditionally + # inserts `launch_pdl` at position 5 before invoking self.launch — making metadata + # 10 args, not 9. Slicing at 9 mis-routed launch_exit_hook as the first kernel + # argument and shifted every TensorDescriptor argument by one slot. + meta_args = args[:10] + raw_kernel_args = args[10:] + final_args = [] + for i, arg in enumerate(raw_kernel_args): + if isinstance(arg, TensorDescriptor): + final_args.extend(make_tensordesc_arg(arg)) + else: + final_args.append(arg) + return launcher(*meta_args, *final_args) + + return inner + + +class TileIRLauncher(object): + def __init__(self, src, metadata): + ids = { + "ids_of_const_exprs": src.fn.constexprs if hasattr(src, "fn") else tuple() + } + + constants = src.constants if hasattr(src, "constants") else dict() + arg_idx = lambda x: (src.fn.arg_names.index(x),) if isinstance(x, str) else x + constants = {arg_idx(idx): value for idx, value in constants.items()} + signature = {idx: value for idx, value in src.signature.items()} + has_tensordesc = any("tensordesc" in value for value in signature.values()) + self.ori_signature_len = len(signature) + if has_tensordesc: + # convert one tensordesc type to [placeholder, ptr, shape and stride] type + post_signature = {} + for key, value in src.signature.items(): + key = arg_idx(key) + if "tensordesc" in value: + shape_str = value.split("[")[1].split("]")[0] + shape = [int(s) for s in shape_str.split(",")] + dtype = value.split("<")[1].split("[")[0] + post_signature[key] = "i32" + post_signature[f"{key}_ptr"] = f"*{dtype}" + # add shape and stride to signature + for idx in range(len(shape)): + post_signature[f"{key}_shape_{idx}"] = "i32" + for idx in range(len(shape)): + post_signature[f"{key}_stride_{idx}"] = "i64" + else: + post_signature[key] = value + self.signature = post_signature + else: + self.signature = signature + if os.environ.get("FLAGTREE_TILEIR_DUMP_SIGNATURE", "0") == "1": + with open("/tmp/flagtree_tileir_signature.txt", "a") as f: + f.write(f"src_signature={signature}\n") + f.write(f"launcher_signature={self.signature}\n") + self.constants = constants + src = make_launcher(self.constants, self.signature) + mod = compile_module_from_src( + src, "__triton_launcher", library_dirs(), include_dirs, libraries + ) + if has_tensordesc: + self.launch = wrap_handle_tensordesc(mod.launch) + else: + self.launch = mod.launch + self.launch_pdl = metadata.launch_pdl + + def __call__(self, *args, **kwargs): + # TODO: below if branch is for torch 2.8.0a0+5228986c39.nvinternal commit + # where constexpr arguments are not passed to the launch function by inductor + # remove this after torch + # 9 is the number of metadata arguments in `src` defined in `make_launcher` + num_launch_args = 9 + num_params = len(args) - num_launch_args + if num_params < self.ori_signature_len: + extra_args = [ + self.constants[(i,)] for i in range(num_params, self.ori_signature_len) + ] + model_args = args + tuple(extra_args) + else: + model_args = args + model_args = model_args[:5] + (self.launch_pdl,) + model_args[5:] + + self.launch(*model_args, **kwargs) + + +class TileIRDriver(GPUDriver): + def __init__(self): + self.utils = TileIRUtils() # TODO: make static + self.launcher_cls = TileIRLauncher + super().__init__() + + def get_current_target(self): + device = self.get_current_device() + capability = self.get_device_capability(device) + capability = capability[0] * 10 + capability[1] + warp_size = 32 + return GPUTarget("tileir", capability, warp_size) + + def get_active_torch_device(self): + import torch + + return torch.device("cuda", self.get_current_device()) + + def get_device_interface(self): + import torch + + return torch.cuda + + @staticmethod + def is_active(): + # flagtree: TileIRDriver must NEVER be the global active driver in flagtree. + # flagtree integrates tileir as a per-kernel routing option (see + # CompiledKernel._get_kernel_driver in python/triton/compiler/compiler.py), + # which lazily instantiates TileIRDriver only for kernels whose target.backend + # is "tileir". The triton.runtime.driver._create_driver() mechanism still + # picks exactly one global active driver based on is_active() — that one + # remains CudaDriver, used for both nvidia-compiled and tileir-compiled + # kernels' device/stream queries. + # + # The upstream triton-to-tile-ir fork returned True when ENABLE_TILE=1 to + # make the driver replace CudaDriver wholesale. In flagtree that would + # produce a "2 active drivers" runtime error (since CudaDriver also reports + # active on CUDA hosts), and is unnecessary — our per-kernel routing already + # handles backend selection without changing the global active driver. + return False + + def map_python_to_cpp_type(self, ty: str) -> str: + return ty_to_cpp(ty) + + def get_benchmarker(self): + from triton.testing import do_bench + + return do_bench + + def get_empty_cache_for_benchmark(self): + import torch + + # We maintain a buffer of 256 MB that we clear + # before each kernel call to make sure that the L2 cache + # doesn't contain any input data before the run + cache_size = 256 * 1024 * 1024 + return torch.empty(int(cache_size // 4), dtype=torch.int, device="cuda") + + def clear_cache(self, cache): + cache.zero_() + + +# flagtree: the upstream fork eagerly constructs `GlobalTileIRDriver = TileIRDriver()` +# here, but that probes CUDA libraries and builds the launcher C extension at module +# import — which breaks plain `import triton` on AMD-only / CUDA-less systems. flagtree +# does per-kernel driver routing in CompiledKernel._get_kernel_driver (compiler.py), +# lazily instantiating TileIRDriver only when a kernel actually needs the tileir backend, +# so this global singleton is unnecessary and intentionally omitted. diff --git a/third_party/tileir/backend/errors.py b/third_party/tileir/backend/errors.py new file mode 100644 index 0000000000..bcb7159673 --- /dev/null +++ b/third_party/tileir/backend/errors.py @@ -0,0 +1,14 @@ +from triton.errors import TritonError + + +class HitFallback(TritonError): + def __init__(self, required, name): + self.required = required + self.name = name + + def __str__(self) -> str: + return f"HitFallback: {self.name}, Required: {self.required}." + + def __reduce__(self): + # this is necessary to make CompilationError picklable + return (type(self), (self.required, self.name)) diff --git a/third_party/tileir/backend/name.conf b/third_party/tileir/backend/name.conf new file mode 100644 index 0000000000..0e81969dad --- /dev/null +++ b/third_party/tileir/backend/name.conf @@ -0,0 +1 @@ +tileir diff --git a/third_party/tileir/include/CMakeLists.txt b/third_party/tileir/include/CMakeLists.txt new file mode 100644 index 0000000000..6730724381 --- /dev/null +++ b/third_party/tileir/include/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(TritonToTileIR) +add_subdirectory(Transform) +add_subdirectory(Utils) diff --git a/third_party/tileir/include/Transform/CMakeLists.txt b/third_party/tileir/include/Transform/CMakeLists.txt new file mode 100644 index 0000000000..f73c0759c8 --- /dev/null +++ b/third_party/tileir/include/Transform/CMakeLists.txt @@ -0,0 +1,3 @@ +set(LLVM_TARGET_DEFINITIONS Passes.td) +mlir_tablegen(Passes.h.inc -gen-pass-decls -name TritonTileIRTransforms) +add_public_tablegen_target(TritonTileIRTransformsIncGen) diff --git a/third_party/tileir/include/Transform/Passes.h b/third_party/tileir/include/Transform/Passes.h new file mode 100644 index 0000000000..252c9dbc6c --- /dev/null +++ b/third_party/tileir/include/Transform/Passes.h @@ -0,0 +1,34 @@ +#ifndef TRITON_TILEIR_TRANSFORMS_PASSES_H_ +#define TRITON_TILEIR_TRANSFORMS_PASSES_H_ + +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/Dialect/UB/IR/UBOps.h" +#include "mlir/Pass/Pass.h" +#include "triton/Dialect/Triton/IR/Dialect.h" +#include + +namespace mlir { +namespace triton { +std::unique_ptr +createRewriteTensorPointerToMemrefPass(int computeCapability, + std::optional numStages); +std::unique_ptr createRewriteAssumeWithCudaTilePass(); +std::unique_ptr createLiftTTCFToSCFPass(); +std::unique_ptr createAutoGenMemoryTokenPass(); +std::unique_ptr +createAutoGenMemoryTokenPass(bool enable_autogen_alias_mem_token); + +// Generate the pass class declarations (and options structs). +#define GEN_PASS_DECL +#include "Transform/Passes.h.inc" + +// Generate the pass registration. +#define GEN_PASS_REGISTRATION +#include "Transform/Passes.h.inc" + +} // namespace triton + +} // namespace mlir + +#endif // TRITON_TILEIR_TRANSFORMS_PASSES_H_ diff --git a/third_party/tileir/include/Transform/Passes.td b/third_party/tileir/include/Transform/Passes.td new file mode 100644 index 0000000000..5d398c6bee --- /dev/null +++ b/third_party/tileir/include/Transform/Passes.td @@ -0,0 +1,93 @@ +#ifndef TRITON_TILEIR_TRANSFORM_PASSES +#define TRITON_TILEIR_TRANSFORM_PASSES + +include "mlir/Pass/PassBase.td" + +def RewriteAssumeWithCudaTile : Pass { + let summary = "Rewrite llvm.intr.assume operations into cuda_tile.assume operations"; + let description = [{ + This pass rewrites patterns like: + ``` + %0 = constant dense<16> : tile + %1 = constant dense<0> : tile + %38 = bitcast %arg0 : tile> -> tile + %39 = remi %38, %0 : tile + %40 = cmpi eq, %39, %1 : tile + %41 = builtin.unrealized_conversion_cast %40 : tile to i1 + llvm.intr.assume %41 : i1 + ``` + into: + ``` + assume div_by<16 : i64>, %arg0: tile> + ``` + + It also supports integer types (i32 and i64) and rewrites patterns like: + ``` + %6 = constant dense<8> : tile loc(#loc1) + %10 = constant dense<0> : tile loc(#loc1) + %54 = remi %46, %6 : tile loc(#loc38) + %55 = cmpi eq, %54, %10 : tile loc(#loc39) + %56 = builtin.unrealized_conversion_cast %55 : tile to i1 loc(#loc39) + llvm.intr.assume %56 : i1 loc(#loc40) + ``` + into: + ``` + assume div_by<8 : i64>, %46 : tile + ``` + + There may be more patterns in the future. + If there are no patterns matched, the llvm.intr.assume will be removed without any new op. + + This transformation allows the compiler to better understand alignment assumptions + and potentially generate more efficient code. + }]; + + let constructor = "mlir::triton::createRewriteAssumeWithCudaTilePass()"; + + let dependentDialects = ["mlir::triton::TritonDialect", "::mlir::cuda_tile::CudaTileDialect", "mlir::LLVM::LLVMDialect"]; +} + +def LiftTTCFToSCF : Pass { + let summary = "Lift ControlFlow dialect (cf) to SCF dialect inside tt.func"; + let description = [{ + This pass applies MLIR's ControlFlowToSCF transformation to regions nested under + Triton `tt.func`. It structurizes `cf` control flow (e.g., `cf.cond_br`, `cf.switch`) + into `scf` constructs so downstream conversions (to cuda_tile) can rely on SCF. + }]; + let constructor = "mlir::triton::createLiftTTCFToSCFPass()"; + let dependentDialects = ["mlir::triton::TritonDialect", "mlir::cf::ControlFlowDialect", "mlir::scf::SCFDialect", "mlir::ub::UBDialect"]; +} + +def AutoGenMemoryToken : Pass { + let summary = "Automatically generate memory tokens for debug_barrier and cuda_tile memory operations"; + let description = [{ + This pass automatically generates memory tokens for debug_barrier in a serialized manner. + It also generates memory tokens for cuda_tile memory operations that have alias memory access patterns to ensure their access order, kernels + which already has user-added memory tokens will be ignored by this pass. + + A simple example looks like this: + ``` + %1, %token_1 = load_ptr_tko weak %ptr : tile> -> tile, token + %token2 = store_ptr_tko weak %ptr, %data : tile>, tile -> token + ``` + will be modified into: + ``` + %0 = make_token : token + %1, %token_1 = load_ptr_tko weak %ptr token=%0 : tile> -> tile, token + %token2 = store_ptr_tko weak %ptr, %data token=%token_1 : tile>, tile -> token + ``` + + For more examples, refer to the test cases in `test/FileCheck/op-conversion-auto-memtoken.mlir`. + }]; + + let constructor = "mlir::triton::createAutoGenMemoryTokenPass()"; + + let dependentDialects = ["::mlir::cuda_tile::CudaTileDialect"]; + let options = [ + Option<"enable_autogen_alias_mem_token", "autogen-alias-memtoken", "bool", + /*default=*/"true", + "Automatically generate memory token for memory ops with alias memory access."> + ]; +} + +#endif diff --git a/third_party/tileir/include/TritonToTileIR/CMakeLists.txt b/third_party/tileir/include/TritonToTileIR/CMakeLists.txt new file mode 100644 index 0000000000..38f742aba5 --- /dev/null +++ b/third_party/tileir/include/TritonToTileIR/CMakeLists.txt @@ -0,0 +1,3 @@ +set(LLVM_TARGET_DEFINITIONS Passes.td) +mlir_tablegen(Passes.h.inc -gen-pass-decls --name TritonToTileIR) +add_public_tablegen_target(TritonToTileIRConversionPassIncGen) diff --git a/third_party/tileir/include/TritonToTileIR/Passes.h b/third_party/tileir/include/TritonToTileIR/Passes.h new file mode 100644 index 0000000000..98325d0f1f --- /dev/null +++ b/third_party/tileir/include/TritonToTileIR/Passes.h @@ -0,0 +1,20 @@ +#ifndef TRITON_TO_TILEIR_CONVERSION_PASSES_H +#define TRITON_TO_TILEIR_CONVERSION_PASSES_H + +#include "TritonToTileIR/TritonToTileIRPass.h" + +namespace mlir { +namespace triton { + +// Generate the pass class declarations (and options structs). +#define GEN_PASS_DECL +#include "TritonToTileIR/Passes.h.inc" + +// Generate the pass registration. +#define GEN_PASS_REGISTRATION +#include "TritonToTileIR/Passes.h.inc" + +} // namespace triton +} // namespace mlir + +#endif // TRITON_TO_TILEIR_CONVERSION_PASSES_H diff --git a/third_party/tileir/include/TritonToTileIR/Passes.td b/third_party/tileir/include/TritonToTileIR/Passes.td new file mode 100644 index 0000000000..e8ed15c84d --- /dev/null +++ b/third_party/tileir/include/TritonToTileIR/Passes.td @@ -0,0 +1,44 @@ +#ifndef TRITON_TO_TILEIR_CONVERSION_PASSES +#define TRITON_TO_TILEIR_CONVERSION_PASSES + +include "mlir/Pass/PassBase.td" + +def ConvertTritonToCudaTile : Pass<"convert-triton-to-cuda-tile", "mlir::ModuleOp"> { + let summary = "Convert Triton to cuda_tile/triton dialect"; + let description = [{ + A convert pass for convert the triton dialect into cuda_tile/triton dialect. + }]; + let constructor = "mlir::triton::createConvertTritonToCudaTilePass()"; + let dependentDialects = ["mlir::arith::ArithDialect", + "mlir::math::MathDialect", + "mlir::triton::TritonDialect", + "mlir::cuda_tile::CudaTileDialect", + "mlir::gpu::GPUDialect", + "mlir::ub::UBDialect" + ]; + let options = [ + Option<"approxModifier", "approx-modifier", "bool", + /*default=*/"false", + "Set approx modifier on all the operations that support it in the module.">, + Option<"flushToZeroModifier", "flush-to-zero-modifier", "bool", + /*default=*/"false", + "Set the flush to zero modifier on all the operations that support it in the module.">, + Option<"computeCapability", "compute-capability", "int", + /*default=*/"100", + "Set the Compute Capability version that is supported in the module">, + Option<"numCTAInCGA", "num-cta-in-cga", "int", + /*default=*/"1", + "number of CTA in CGA">, + Option<"simtNumWarpsInCTA", "num-warps-in-cta", "int", + /*default=*/"4", + "number of SIMT warps in CTA">, + Option<"occupancy", "occupancy", "int", + /*default=*/"1", + "number of ctas in one SM">, + Option<"numStages", "num-stages", "int", + /*default=*/"", + "number of stages for the kernel"> + ]; +} + +#endif diff --git a/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h b/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h new file mode 100644 index 0000000000..6f4a22266d --- /dev/null +++ b/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h @@ -0,0 +1,32 @@ +#ifndef TRITON_CONVERSION_TRITONTOTILEIR_PASS_H +#define TRITON_CONVERSION_TRITONTOTILEIR_PASS_H + +#include "mlir/Dialect/GPU/IR/GPUDialect.h" +#include "mlir/Pass/Pass.h" + +#include "Utils.h" +#include "cuda_tile/Dialect/CudaTile/IR/Dialect.h" +#include "cuda_tile/Dialect/CudaTile/IR/Ops.h" + +namespace mlir { + +class ModuleOp; +template class OperationPass; + +namespace triton { + +std::unique_ptr> createConvertTritonToCudaTilePass(); +std::unique_ptr> +createConvertTritonToCudaTilePass(bool approx, bool ftz, int capability, + int num_ctas, int simt_num_warps, int occupancy, + std::optional num_stages); + +} // namespace triton + +namespace cuda_tile { +void legalize_agent_captures(Operation *rop); +} // namespace cuda_tile + +} // namespace mlir + +#endif // TRITON_CONVERSION_TRITONTOTILEIR_PASS_H diff --git a/third_party/tileir/include/TritonToTileIR/Utils.h b/third_party/tileir/include/TritonToTileIR/Utils.h new file mode 100644 index 0000000000..deaabda99f --- /dev/null +++ b/third_party/tileir/include/TritonToTileIR/Utils.h @@ -0,0 +1,193 @@ +#ifndef BRIDGE_UTILS_H +#define BRIDGE_UTILS_H + +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/Math/IR/Math.h" +#include "mlir/Dialect/UB/IR/UBOps.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" +#include "cuda_tile/Dialect/CudaTile/IR/Ops.h" +#include "cuda_tile/Dialect/CudaTile/IR/Types.h" +#include "triton/Analysis/AxisInfo.h" +#include "triton/Dialect/Triton/IR/Dialect.h" + +namespace mlir { +namespace bridge_utils { + +/// Return the identity (or initial value) attribute for the reduce operation. +/// The identity is computed by looking at the operation with the reduce region +/// `combineOp` and based on the reduce return type `retType`. +FailureOr +getIdentitiesFromCombineOp(Region &combineOp, ArrayRef retType, + ConversionPatternRewriter &rewriter); + +class CudaTileTypeConverter : public TypeConverter { +public: + CudaTileTypeConverter(); +}; + +bool canMapToCudaTile(triton::FuncOp op, CudaTileTypeConverter &typeConverter); + +enum class Signedness { None, Signed, Unsigned }; +enum class IntegerUpCast { None, To_I16 }; + +Value upCastOrSelf(OpBuilder &builder, Location loc, Value input, + Signedness signedness, IntegerUpCast integerUpCast); + +Value downCastOrSelf( + OpBuilder &builder, Location loc, ArrayRef operands, Type retType, + llvm::function_ref)> + createOp, + IntegerUpCast integerUpCast); + +LogicalResult matchAndRewriteGenericOpImpl( + Operation *op, ValueRange operands, const TypeConverter *converter, + ConversionPatternRewriter &rewriter, + llvm::function_ref)> + createOp, + Signedness signedness, IntegerUpCast integerUpCast); + +template +class ConvertGenericOp : public OpConversionPattern { +private: + bool approxModifier = false; + bool flushToZeroModifier = false; + +public: + ConvertGenericOp(TypeConverter &typeConverter, MLIRContext *context, + bool approx = false, bool flushToZero = false) + : OpConversionPattern(typeConverter, context), + approxModifier(approx), flushToZeroModifier(flushToZero) {} + + LogicalResult + matchAndRewrite(TritonOp op, typename TritonOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + return matchAndRewriteGenericOpImpl( + op, adaptor.getOperands(), this->getTypeConverter(), rewriter, + [&](OpBuilder &builder, Location loc, Type type, + ArrayRef operands) { + // For DivSIOp and RemSIOp, triton assume the LHS is positive in axis + // analysis pass, see + // https://github.com/triton-lang/triton/issues/7749. tileir backend + // also assume the LHS is positive here for simplicity of the axis + // analysis pass. + // TODO: write a more general pass to analyze the all positive value. + if constexpr (std::is_same_v) { + auto lhs = cuda_tile::AssumeOp::create( + rewriter, loc, operands[0], + cuda_tile::BoundedAttr::get(rewriter.getContext(), 0, + std::nullopt)) + .getResult(); + auto tileSignedness = signedness == Signedness::Unsigned + ? cuda_tile::Signedness::Unsigned + : cuda_tile::Signedness::Signed; + return CudaTileOp::create(builder, loc, type, lhs, operands[1], + tileSignedness); + } else if constexpr (std::is_same_v) { + auto tileSignedness = signedness == Signedness::Unsigned + ? cuda_tile::Signedness::Unsigned + : cuda_tile::Signedness::Signed; + auto lhs = cuda_tile::AssumeOp::create( + rewriter, loc, operands[0], + cuda_tile::BoundedAttr::get(rewriter.getContext(), 0, + std::nullopt)) + .getResult(); + return CudaTileOp::create(builder, loc, type, lhs, operands[1], + tileSignedness); + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v) { + assert(operands.size() == 2 && "expect two operands for divi"); + auto rounding = cuda_tile::RoundingMode::ZERO; + if (std::is_same_v || + std::is_same_v) + rounding = cuda_tile::RoundingMode::POSITIVE_INF; + else if (std::is_same_v) + rounding = cuda_tile::RoundingMode::NEGATIVE_INF; + auto tileSignedness = signedness == Signedness::Unsigned + ? cuda_tile::Signedness::Unsigned + : cuda_tile::Signedness::Signed; + return CudaTileOp::create(builder, loc, type, operands[0], + operands[1], tileSignedness, rounding); + } else if constexpr (std::is_same_v || + std::is_same_v || + std::is_same_v) { + assert(operands.size() == 2 && + "expect two operands for add/mul/sub"); + bool isF32 = getElementTypeOrSelf(op.getResult().getType()).isF32(); + bool ftzModifier = (this->flushToZeroModifier && isF32); + return CudaTileOp::create( + builder, loc, type, operands[0], operands[1], + cuda_tile::RoundingMode::NEAREST_EVEN, ftzModifier); + } else if constexpr (std::is_same_v) { + assert(operands.size() == 3 && "expect two operands for fma"); + bool isF32 = getElementTypeOrSelf(op.getResult().getType()).isF32(); + bool ftzModifier = (this->flushToZeroModifier && isF32); + return CudaTileOp::create( + builder, loc, type, operands[0], operands[1], operands[2], + cuda_tile::RoundingMode::NEAREST_EVEN, ftzModifier); + } else if constexpr (std::is_same_v) { + assert(operands.size() == 2 && "expect two operands for div"); + bool isF32 = getElementTypeOrSelf(op.getResult().getType()).isF32(); + auto rounding = cuda_tile::RoundingMode::NEAREST_EVEN; + if (isF32) { + rounding = cuda_tile::RoundingMode::FULL; + if (this->approxModifier) + rounding = cuda_tile::RoundingMode::APPROX; + } + bool ftzModifier = (this->flushToZeroModifier && isF32); + return CudaTileOp::create(builder, loc, operands[0], operands[1], + rounding, ftzModifier); + } else if constexpr (std::is_same_v) { + // Lower a precise div operation. The ftz flag will not + // have any effect. + return CudaTileOp::create(builder, loc, operands[0], operands[1], + cuda_tile::RoundingMode::NEAREST_EVEN); + } else if constexpr (std::is_same_v) { + assert(operands.size() == 1 && "expect single operand for exp"); + return CudaTileOp::create(builder, loc, operands[0]); + } else if constexpr (std::is_same_v) { + assert(operands.size() == 1 && "expect single operand for ex2"); + bool isF32 = getElementTypeOrSelf(op.getResult().getType()).isF32(); + bool ftzModifier = (this->flushToZeroModifier && isF32); + return CudaTileOp::create(builder, loc, operands[0], ftzModifier); + } else if constexpr (std::is_same_v) { + assert(operands.size() == 1 && "expect single operand for sqrt"); + bool isF32 = getElementTypeOrSelf(op.getResult().getType()).isF32(); + auto rounding = cuda_tile::RoundingMode::NEAREST_EVEN; + if (this->approxModifier && isF32) + rounding = cuda_tile::RoundingMode::APPROX; + bool ftzModifier = (this->flushToZeroModifier && isF32); + return CudaTileOp::create(builder, loc, operands[0], rounding, + ftzModifier); + } else if constexpr (std::is_same_v) { + // Lower a precise sqrt operation. The ftz flag will not + // have any effect. + return CudaTileOp::create(builder, loc, operands[0], + cuda_tile::RoundingMode::NEAREST_EVEN); + } else if constexpr (signedness != Signedness::None) { + auto tileSignedness = signedness == Signedness::Unsigned + ? cuda_tile::Signedness::Unsigned + : cuda_tile::Signedness::Signed; + return CudaTileOp::create(builder, loc, type, operands, + tileSignedness); + } else { + return CudaTileOp::create(builder, loc, type, operands); + } + }, + signedness, integerUpCast); + } +}; + +} // end namespace bridge_utils +} // namespace mlir + +#endif // BRIDGE_UTILS_H diff --git a/third_party/tileir/include/Utils/CMakeLists.txt b/third_party/tileir/include/Utils/CMakeLists.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/tileir/include/Utils/Utils.h b/third_party/tileir/include/Utils/Utils.h new file mode 100644 index 0000000000..fa461dd8e0 --- /dev/null +++ b/third_party/tileir/include/Utils/Utils.h @@ -0,0 +1,33 @@ +#ifndef UTILS_UTILS_H +#define UTILS_UTILS_H + +#include "mlir/IR/BuiltinOps.h" + +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" +#include + +namespace mlir { +namespace triton { +namespace utils { + +// Helper function to iterate through parent ForOp and find +// num_stages attribute +std::optional getNumStagesFromParentForOp(Operation *op); + +// Helper function to find the num_stages for the op and convert it to +// OptimizationHintsAttr. +std::optional +convertNumStagesToOptHint(Operation *op, MLIRContext *ctx, + const DenseMap &numStagesMap, + int computeCapability, std::optional numStages); + +// Helper function to convert a num_stages value to OptimizationHintsAttr. +std::optional +cvtNumStagesToOptHintAttr(MLIRContext *ctx, int computeCapability, + int numStages); + +} // namespace utils +} // namespace triton +} // namespace mlir + +#endif // UTILS_UTILS_H diff --git a/third_party/tileir/lib/CMakeLists.txt b/third_party/tileir/lib/CMakeLists.txt new file mode 100644 index 0000000000..6730724381 --- /dev/null +++ b/third_party/tileir/lib/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(TritonToTileIR) +add_subdirectory(Transform) +add_subdirectory(Utils) diff --git a/third_party/tileir/lib/Transform/AutoGenMemoryToken.cpp b/third_party/tileir/lib/Transform/AutoGenMemoryToken.cpp new file mode 100644 index 0000000000..21d034f6e1 --- /dev/null +++ b/third_party/tileir/lib/Transform/AutoGenMemoryToken.cpp @@ -0,0 +1,644 @@ +#include "Transform/Passes.h" +#include "TritonToTileIR/Utils.h" +#include "Utils/Utils.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/Support/Debug.h" + +using namespace mlir; + +// MLIR pass TableGen now uses per-pass macros (GEN_PASS_DEF_*). +namespace mlir { +namespace triton { +#define GEN_PASS_DEF_AUTOGENMEMORYTOKEN +#include "Transform/Passes.h.inc" +} // namespace triton +} // namespace mlir + +#define DEBUG_TYPE "add-memory-token" + +/* + * This Pass file aims to add memory tokens automatically to ensure tileIR's + * compatibility with Triton. We add memory tokens based on the following rules: + * - If a kernel contains memory ops with input token, which means user has + * already added some tokens in the kernel, we will keep the original token flow + * unchanged and do nothing. + * - If a kernel contains a triton debug_barrier op, we add memory tokens for + * all memory ops in a sequential way. + * - If a kernel contains sets of memory ops which acesses the same data, we + * will apply memory tokens to maintain their access order. + * + * Implementation: + * We organize memory ops into sequances, where each sequence access the same + * memory data and their access order need to be maintained by memory token. To + * distinguish different sequances, we assign SID for each sequence and add + * function getMemOpSeqId to map op to its sequence SID. There are 2 types of + * memory ops: + * - one is ptr memory ops, which uses tensor of pointers like LoadPtrTkoOp. + * - the other is view memory ops, which uses tensor of views like + * LoadViewTkoOp. These two kind of memory ops use different ways to represent + * their memory accessing pattern. So for ptr memory ops, we hash their ptr + * value as SID; for view ops, we hash their view value and index values as SID. + * The main transformation is done in Pass AutoGenMemoryTokenPass, which + * performs two walks for the entire input IR. One is to collect memory sequence + * info, and after processing collected data (to make sure there are memory + * tokens required to be added), another walk is performed to add memory tokens + * based on the sequence info. + * + * In this version of implementation, there are some scenarios we cannot handle: + * 1. if some ptr ops and view ops access the same data, we will not be able + * to detect that and put them into the same sequence. + * 2. if some memory ops' access memory overlap, we will not be able to find + * out. + * 3. if users pass 2 ptrs pointing to the same memory location, we will not + * be able to find out. + */ + +namespace { + +using namespace mlir::triton; + +using SeqId = std::size_t; +using SeqIdSet = llvm::SmallSet; +using SeqIdVec = SmallVector; +static const SeqId BARRIER_SEQ_ID = 1; + +using SeqTokensBase = DenseMap; +class SeqTokens : public SeqTokensBase { + SeqIdSet updatedSids; + +public: + SeqTokens() = default; + + void update(SeqId id, Value token) { + (*this)[id] = token; + updatedSids.insert(id); + } + + void update(const SeqTokens &newTokens) { + for (const auto &pair : newTokens) + update(pair.first, pair.second); + } + + SeqIdVec + aggregate(const SmallVector> &tokenSets) { + SeqIdSet allUpdatedSids; + for (auto &tokenRef : tokenSets) { + for (const auto &p : tokenRef.get()) + allUpdatedSids.insert(p.first); + } + + // Here we use a vector to make sure all results have the same sid order + SeqIdVec allUpdatedSidsVec(allUpdatedSids.begin(), allUpdatedSids.end()); + + auto genResult = [&](SeqTokens &tokens) { + for (const auto &sid : allUpdatedSidsVec) { + if (tokens.find(sid) == tokens.end()) + tokens[sid] = (*this)[sid]; + } + }; + + for (auto &tokenRef : tokenSets) + genResult(tokenRef.get()); + + return allUpdatedSidsVec; + } + + SeqTokens getUpdatedTokens() { + SeqTokens outTokens; + for (const auto &sid : updatedSids) + outTokens[sid] = (*this)[sid]; + return outTokens; + } + + void cleanUpdatedSids() { updatedSids.clear(); } +}; +using OpToTokens = SmallVector, 4>; + +struct MemSeqInfo { + size_t writeMemOpCounter = 0; + size_t memOpCounter = 0; // used for both preprocessing and transform + bool ignored = false; + + MemSeqInfo() = default; +}; + +struct BlockMemSeqs { + // collected data from preprocessing walk + bool hasBarrierOp = false; + bool hasMemToken = false; + DenseMap srcToMemSeqInfoMap; + + // runtime data for transform walk + int loop_level = 0; + int if_level = 0; + + BlockMemSeqs() = default; + SeqTokens getBlockInitTokens(Block *block, IRRewriter &rewriter) { + rewriter.setInsertionPointToStart(block); + SeqTokens tokens; + for (const auto &pair : srcToMemSeqInfoMap) { + if (pair.second.ignored) + continue; // only make new token for un-ignored sequences + auto makeTkOp = + cuda_tile::MakeTokenOp::create(rewriter, block->front().getLoc()); + tokens[pair.first] = makeTkOp.getResult(); + } + return tokens; + } + + void clear() { + hasBarrierOp = false; + hasMemToken = false; + loop_level = 0; + if_level = 0; + srcToMemSeqInfoMap.clear(); + } +}; + +bool isMemOp(Operation *op) { + return isa(op); +} + +bool isWriteMemOp(Operation *op) { + return isa(op); +} + +class AutoGenMemoryTokenPass + : public ::mlir::triton::impl::AutoGenMemoryTokenBase< + AutoGenMemoryTokenPass> { + // Data members + BlockMemSeqs currentBlockMemSeqs; + + /// Generate SeqId for a specific memory op + SeqId getMemOpSeqId(Operation *op) { + if (currentBlockMemSeqs.hasBarrierOp) + return BARRIER_SEQ_ID; + + Value val = Value(); + SmallVector indexes; + if (auto loadOp = dyn_cast(op)) { + val = loadOp.getSource(); + if (loadOp.getToken()) + currentBlockMemSeqs.hasMemToken = true; + } else if (auto storeOp = dyn_cast(op)) { + val = storeOp.getDestination(); + if (storeOp.getToken()) + currentBlockMemSeqs.hasMemToken = true; + } else if (auto atmRMWOp = dyn_cast(op)) { + val = atmRMWOp.getPointers(); + if (atmRMWOp.getToken()) + currentBlockMemSeqs.hasMemToken = true; + } else if (auto atmCASOp = dyn_cast(op)) { + val = atmCASOp.getPointers(); + if (atmCASOp.getToken()) + currentBlockMemSeqs.hasMemToken = true; + } else if (auto viewOp = dyn_cast(op)) { + val = viewOp.getView(); + indexes = viewOp.getIndex(); + if (viewOp.getToken()) + currentBlockMemSeqs.hasMemToken = true; + } else if (auto viewOp = dyn_cast(op)) { + val = viewOp.getView(); + indexes = viewOp.getIndex(); + if (viewOp.getToken()) + currentBlockMemSeqs.hasMemToken = true; + } + // TODO: does different order of index generate the same hash value? + if (!val) + return 0; + llvm::hash_code hash = mlir::hash_value(val); + for (const Value &val : indexes) + hash = llvm::hash_combine(hash, mlir::hash_value(val)); + return hash; + } + + /// Get function/entry block and name from operation + Block *getFuncBlock(Operation *op, std::string &funcName) { + if (auto entryOp = dyn_cast(op)) { + funcName = entryOp.getSymName().str(); + return &entryOp.getBody().front(); + } + return nullptr; + } + + /// Handle memory op + /// 1. add input token to op's operands(if token is not null) + /// 2. update operandSegmentSizes attribute(if exists) + /// 3. return the updated token value from op's result values. + template + Value updateMemOpWithToken(OpTy *op, Value token, IRRewriter &rewriter) { + SmallVector newOperands = llvm::to_vector(op->getOperands()); + + if (token) { + // append token operand + newOperands.push_back(token); + // update operand segment sizes attribute + if (auto segmentSizesAttr = op->getAttr("operandSegmentSizes")) { + auto arrayAttr = dyn_cast(segmentSizesAttr); + SmallVector newSegmentSizes = + llvm::to_vector(arrayAttr.asArrayRef()); + newSegmentSizes.back() = + 1; // the last segment indicates whether token operand exists + op->setAttr("operandSegmentSizes", + rewriter.getDenseI32ArrayAttr(newSegmentSizes)); + } + } + op->setOperands(newOperands); + + return op->getResults().back(); + } + + /// Handle terminator ops by adding token to its operands. + template + void updateTermOpWithToken(OpTy *op, SeqTokens &tokens, SeqIdVec &sids) { + SmallVector newOperands = llvm::to_vector(op->getOperands()); + // use sids to ensure the order of tokens + for (auto s : sids) + newOperands.push_back(tokens[s]); + op->setOperands(newOperands); + } + + SeqTokens handleIfOpTokens(cuda_tile::IfOp ifOp, SeqTokens tokens, + IRRewriter &rewriter, OpToTokens *termOps) { + tokens.cleanUpdatedSids(); + currentBlockMemSeqs.if_level++; + + // handle token in then and else block + SeqTokens thenTokens = + addMemTokenForBlock(ifOp.getThenBlock(), tokens, rewriter, termOps); + SeqTokens elseTokens = + addMemTokenForBlock(ifOp.getElseBlock(), tokens, rewriter, termOps); + SmallVector> tokenList{ + std::ref(thenTokens), std::ref(elseTokens)}; + SeqIdVec sids = tokens.aggregate(tokenList); + + // skip those sequences which will not be used in later memory ops + if (!currentBlockMemSeqs.loop_level) { + SeqIdVec newSids; + for (const auto &sid : sids) { + if (currentBlockMemSeqs.srcToMemSeqInfoMap[sid].memOpCounter == 0) { + thenTokens.erase(thenTokens.find(sid)); + elseTokens.erase(elseTokens.find(sid)); + } else + newSids.push_back(sid); + } + sids.swap(newSids); + } + + auto insertTermOp = [&]() { + rewriter.createBlock(&ifOp.getElseRegion()); + rewriter.setInsertionPointToEnd(ifOp.getElseBlock()); + cuda_tile::YieldOp::create(rewriter, ifOp.getLoc()); + }; + + // if either branch has memory token update, we need to update terminate ops + // of this ifOp + if (!sids.empty() && isa(ifOp.getThenTerminator())) { + updateTermOpWithToken(ifOp.getThenTerminator(), thenTokens, sids); + if (!ifOp.getElseTerminator()) + insertTermOp(); + updateTermOpWithToken(ifOp.getElseTerminator(), elseTokens, sids); + + // append token type to ifOp's return type + rewriter.setInsertionPointAfter(ifOp); + SmallVector resultTypes = llvm::to_vector(ifOp.getResultTypes()); + resultTypes.append(sids.size(), + cuda_tile::TokenType::get(rewriter.getContext())); + auto newIfOp = + cuda_tile::IfOp::create(rewriter, ifOp.getLoc(), resultTypes, + ifOp.getCondition(), ifOp->getAttrs()); + rewriter.inlineRegionBefore(ifOp.getThenRegion(), newIfOp.getThenRegion(), + newIfOp.getThenRegion().begin()); + rewriter.inlineRegionBefore(ifOp.getElseRegion(), newIfOp.getElseRegion(), + newIfOp.getElseRegion().begin()); + // update token + for (size_t i = 0; i < sids.size(); i++) + tokens.update(sids[i], newIfOp.getResult(ifOp.getNumResults() + i)); + + // replace old result values with new ones, except new token + SmallVector replacementResults; + for (size_t i = 0; i < ifOp.getNumResults(); i++) + replacementResults.push_back(newIfOp.getResult(i)); + rewriter.replaceOp(ifOp, replacementResults); + } else if (isa( + ifOp.getThenTerminator())) { + if (!ifOp.getElseTerminator()) + insertTermOp(); + if (termOps == nullptr) { + ifOp.emitWarning("unexpected terminator op in if op"); + } else { + termOps->push_back({ifOp.getThenTerminator(), thenTokens}); + termOps->push_back({ifOp.getElseTerminator(), elseTokens}); + } + } + + currentBlockMemSeqs.if_level--; + return tokens.getUpdatedTokens(); + } + + SeqTokens handleForOpTokens(cuda_tile::ForOp forOp, SeqTokens tokens, + IRRewriter &rewriter) { + tokens.cleanUpdatedSids(); + currentBlockMemSeqs.loop_level++; + + // handle token in body block + OpToTokens localTermOps; + SeqTokens forTokens = + addMemTokenForBlock(forOp.getBody(), tokens, rewriter, &localTermOps); + SmallVector> tokenList{ + std::ref(forTokens)}; + for (auto &opTokenPair : localTermOps) + tokenList.push_back(std::ref(opTokenPair.second)); + SeqIdVec sids = tokens.aggregate(tokenList); + + // add token to terminator + if (!sids.empty()) { + // add token to terminator recursively + updateTermOpWithToken(forOp.getBody()->getTerminator(), forTokens, sids); + for (auto &pair : localTermOps) + updateTermOpWithToken(pair.first, pair.second, sids); + + // append token type to forOp's init values + SmallVector newInitArgs = llvm::to_vector(forOp.getInitValues()); + for (const auto &sid : sids) + newInitArgs.push_back(tokens[sid]); + // create new loop op + rewriter.setInsertionPointAfter(forOp); + auto newForOp = cuda_tile::ForOp::create( + rewriter, forOp.getLoc(), forOp.getLowerBound(), + forOp.getUpperBound(), forOp.getStep(), newInitArgs); + + // copy block body + Block *newBlock = &newForOp.getRegion().front(); + SmallVector blockArgs; + size_t originNumArgs = forOp.getBody()->getNumArguments(); + for (auto arg : forOp.getBody()->getArguments()) + blockArgs.push_back(newBlock->getArgument(arg.getArgNumber())); + rewriter.mergeBlocks(forOp.getBody(), newBlock, blockArgs); + + // update token usage in loop body + for (size_t i = 0; i < sids.size(); i++) { + auto arg = newBlock->getArgument(originNumArgs + i); + tokens[sids[i]].replaceAllUsesExcept(arg, newForOp); + tokens.update(sids[i], newForOp.getResult(forOp.getNumResults() + i)); + } + + // replace old result values with new ones, except new token + SmallVector replacementResults; + for (size_t i = 0; i < forOp.getNumResults(); i++) + replacementResults.push_back(newForOp.getResult(i)); + rewriter.replaceOp(forOp, replacementResults); + } + + currentBlockMemSeqs.loop_level--; + return tokens.getUpdatedTokens(); + } + + SeqTokens handleLoopOpTokens(cuda_tile::LoopOp loopOp, SeqTokens tokens, + IRRewriter &rewriter) { + tokens.cleanUpdatedSids(); + currentBlockMemSeqs.loop_level++; + + // handle token in body block + OpToTokens localTermOps; + SeqTokens loopTokens = + addMemTokenForBlock(loopOp.getBody(), tokens, rewriter, &localTermOps); + SmallVector> tokenList{ + std::ref(loopTokens)}; + for (auto &opTokenPair : localTermOps) + tokenList.push_back(std::ref(opTokenPair.second)); + SeqIdVec sids = tokens.aggregate(tokenList); + + // add token to terminator + if (!sids.empty()) { + // add token to terminator recursively + updateTermOpWithToken(loopOp.getBody()->getTerminator(), loopTokens, + sids); + for (auto &pair : localTermOps) + updateTermOpWithToken(pair.first, pair.second, sids); + + // append token type to loopOp's operand + SmallVector newOperands = llvm::to_vector(loopOp->getOperands()); + for (const auto &sid : sids) + newOperands.push_back(tokens[sid]); + // append token type to loopOp's return type + auto tokenType = cuda_tile::TokenType::get(rewriter.getContext()); + SmallVector resultTypes = llvm::to_vector(loopOp.getResultTypes()); + resultTypes.append(sids.size(), tokenType); + // create new loop op + rewriter.setInsertionPointAfter(loopOp); + auto newLoopOp = + cuda_tile::LoopOp::create(rewriter, loopOp.getLoc(), resultTypes, + newOperands, loopOp->getAttrs()); + + // append token to loop block's argument list + Block *newBlock = rewriter.createBlock(&newLoopOp.getRegion()); + for (Type type : loopOp.getBody()->getArgumentTypes()) + newBlock->addArgument(type, loopOp.getLoc()); + for (const auto &sid : sids) + newBlock->addArgument(tokenType, loopOp.getLoc()); + + // copy block body + size_t originNumArgs = loopOp.getBody()->getNumArguments(); + SmallVector blockArgs; + for (auto arg : loopOp.getBody()->getArguments()) + blockArgs.push_back(newBlock->getArgument(arg.getArgNumber())); + rewriter.mergeBlocks(loopOp.getBody(), newBlock, blockArgs); + + // update token usage in loop body + for (size_t i = 0; i < sids.size(); i++) { + auto arg = newBlock->getArgument(originNumArgs + i); + tokens[sids[i]].replaceAllUsesExcept(arg, newLoopOp); + tokens.update(sids[i], newLoopOp.getResult(loopOp.getNumResults() + i)); + } + + // replace old result values with new ones, except new token + SmallVector replacementResults; + for (size_t i = 0; i < loopOp.getNumResults(); i++) + replacementResults.push_back(newLoopOp.getResult(i)); + rewriter.replaceOp(loopOp, replacementResults); + } + + currentBlockMemSeqs.loop_level--; + return tokens.getUpdatedTokens(); + } + + /// Propagates memory tokens through a block and its nested control flow. + /// + /// This function performs a pre-order walk of all operations in the block, + /// adding memory tokens to memory operations in sequential order. For control + /// flow operations (if/for/loop), it recursively processes nested blocks and + /// updates tokens appropriately. + /// + /// @param block: The block to process. + /// @param tokens: The initial tokens to propagate, the size of tokens should + /// be + /// the number of sequences which requires adding memory + /// tokens. + /// @param rewriter: IR rewriter for modifications. + /// @param termOps: Optional collector for terminator operations with their + /// tokens. + /// (e.g. loopOp -> ifOp -> breakOp) + /// @return The final token value after processing all operations in the + /// block. + /// The result will only contain the updated token value. + SeqTokens addMemTokenForBlock(Block *block, SeqTokens tokens, + IRRewriter &rewriter, + OpToTokens *termOps = nullptr) { + tokens.cleanUpdatedSids(); + if (!block || block->empty()) + return SeqTokens(); + + block->walk([&](Operation *childOp) { + if (auto ifOp = dyn_cast(childOp)) { + SeqTokens newTokens = handleIfOpTokens(ifOp, tokens, rewriter, termOps); + tokens.update(newTokens); + return WalkResult::skip(); + } else if (auto forOp = dyn_cast(childOp)) { + SeqTokens newTokens = handleForOpTokens(forOp, tokens, rewriter); + tokens.update(newTokens); + return WalkResult::skip(); + } else if (auto loopOp = dyn_cast(childOp)) { + SeqTokens newTokens = handleLoopOpTokens(loopOp, tokens, rewriter); + tokens.update(newTokens); + return WalkResult::skip(); + } else if (isMemOp(childOp)) { + SeqId sid = getMemOpSeqId(childOp); + auto &seq = currentBlockMemSeqs.srcToMemSeqInfoMap[sid]; + // only add memory token for memory op sequences with more than 1 memory + // op + if (!seq.ignored) { + tokens.update(sid, + updateMemOpWithToken(childOp, tokens[sid], rewriter)); + seq.memOpCounter--; + } + } + return WalkResult::advance(); + }); + + return tokens.getUpdatedTokens(); + }; + +public: + AutoGenMemoryTokenPass() = default; + AutoGenMemoryTokenPass(bool enable_autogen_alias_mem_token) { + this->enable_autogen_alias_mem_token = enable_autogen_alias_mem_token; + } + + void runOnOperation() override { + MLIRContext *context = &getContext(); + ModuleOp mod = getOperation(); + IRRewriter rewriter(context); + + mod->walk([&](Operation *outOp) { + std::string fname; + Block *body = getFuncBlock(outOp, fname); + if (!body) + return WalkResult::advance(); + + // 1. Preprocess walk: traverse the block to collect info + currentBlockMemSeqs.clear(); + body->walk([&](Operation *op) { + // 1.1 check if func/entry op contains debug_barrier op, if yes, all + // memory ops will map to the same SeqId + if (isa(op)) { + currentBlockMemSeqs.hasBarrierOp = true; + rewriter.eraseOp(op); + return WalkResult::advance(); + } + // 1.2 record all memory ops (possibly needs to be ordered) + if (!isMemOp(op)) + return WalkResult::advance(); + SeqId sid = getMemOpSeqId(op); + // 1.3 check if any memory op has input token, if yes, no need to + // proceed + if (currentBlockMemSeqs.hasMemToken) + return WalkResult::interrupt(); + currentBlockMemSeqs.srcToMemSeqInfoMap[sid].memOpCounter++; + if (isWriteMemOp(op)) + currentBlockMemSeqs.srcToMemSeqInfoMap[sid].writeMemOpCounter++; + LLVM_DEBUG(llvm::errs() + << "Add Operation to SeqId " << sid << ": " << *op << "\n"); + return WalkResult::advance(); + }); + + // 2. Check phase: walk through collected info to decide whether to run + // transform walk + // 2.1 if no barrier op and disable autogen alias mem token, skip + if (this->enable_autogen_alias_mem_token == false && + !currentBlockMemSeqs.hasBarrierOp) { + return WalkResult::skip(); + } + // 2.2 if contains user-defined mem token, skip + if (currentBlockMemSeqs.hasMemToken) { + if (currentBlockMemSeqs.hasBarrierOp) { + outOp->emitWarning( + "debug_barrier should not be added when memory tokens are added" + "manually, debug_barrier op will be ignored."); + } + return WalkResult::skip(); + } + // 2.3 map all mem op to a single sequence if there is debug_barrier + // op + if (currentBlockMemSeqs.hasBarrierOp) { + size_t writeMemOpCounter = 0, memOpCounter = 0; + for (auto &p : currentBlockMemSeqs.srcToMemSeqInfoMap) { + writeMemOpCounter += p.second.writeMemOpCounter; + memOpCounter += p.second.memOpCounter; + } + currentBlockMemSeqs.srcToMemSeqInfoMap.clear(); + currentBlockMemSeqs.srcToMemSeqInfoMap[BARRIER_SEQ_ID] = { + writeMemOpCounter, memOpCounter, false}; + } + // 2.4 ignore sequences with only 1 memory op, + // ignore sequences with no write ops + bool willModify = false; + for (auto &p : currentBlockMemSeqs.srcToMemSeqInfoMap) { + if (p.second.memOpCounter <= 1 || !p.second.writeMemOpCounter) + p.second.ignored = true; + else + willModify = true; + } + if (!willModify) { + LLVM_DEBUG(llvm::errs() + << "[AutoGenMemoryTokenPass] will not modify IR: " << fname + << ".\n"); + return WalkResult::skip(); + } else { + LLVM_DEBUG(llvm::errs() << "[AutoGenMemoryTokenPass] will modify IR: " + << fname << ".\n"); + LLVM_DEBUG(llvm::errs() + << "[AutoGenMemoryTokenPass] Memory Sequence Info Map:\n"); + for (const auto &pair : currentBlockMemSeqs.srcToMemSeqInfoMap) { + LLVM_DEBUG(llvm::errs() + << "\tSeqId: " << pair.first + << ", memOpCounter: " << pair.second.memOpCounter + << ", ignored: " << pair.second.ignored << "\n"); + } + } + + // 3. Transform walk: traverse all ops recursively in the mod again to add + // memory tokens + auto tokens = currentBlockMemSeqs.getBlockInitTokens(body, rewriter); + addMemTokenForBlock(body, tokens, rewriter); + return WalkResult::skip(); + }); + } +}; + +} // namespace + +std::unique_ptr mlir::triton::createAutoGenMemoryTokenPass() { + return std::make_unique(); +} + +std::unique_ptr mlir::triton::createAutoGenMemoryTokenPass( + bool enable_autogen_alias_mem_token) { + return std::make_unique( + enable_autogen_alias_mem_token); +} diff --git a/third_party/tileir/lib/Transform/CMakeLists.txt b/third_party/tileir/lib/Transform/CMakeLists.txt new file mode 100644 index 0000000000..45eb7c122e --- /dev/null +++ b/third_party/tileir/lib/Transform/CMakeLists.txt @@ -0,0 +1,22 @@ +set(CUDA_TILE_INSTALL_DIR $ENV{CUDA_TILE_INSTALL_DIR}) + +add_triton_library(TritonTileIRTransforms + RewriteAssumeWithCudaTile.cpp + LiftTTCFToSCF.cpp + AutoGenMemoryToken.cpp + + DEPENDS + TritonTileIRTransformsIncGen + + LINK_LIBS PUBLIC + MLIRPass + MLIRTransformUtils + MLIRControlFlowToSCF + MLIRControlFlowDialect + MLIRSCFDialect + MLIRFuncDialect + MLIRUBDialect + TritonIR + Utils + ${CUDA_TILE_INSTALL_DIR}/lib/libCudaTileDialect.a +) diff --git a/third_party/tileir/lib/Transform/LiftTTCFToSCF.cpp b/third_party/tileir/lib/Transform/LiftTTCFToSCF.cpp new file mode 100644 index 0000000000..28edd9941e --- /dev/null +++ b/third_party/tileir/lib/Transform/LiftTTCFToSCF.cpp @@ -0,0 +1,93 @@ +//===- LiftTTCFToSCF.cpp ---------------------------------------*- C++ -*-===// +// +// Mostly inherited from mlir/Conversion/ControlFlowToSCF/ControlFlowToSCF.cpp +// reason is cfToSCF only supports func.funcOp, we need to operate on tt.funcOp +// Apply MLIR ControlFlowToSCF transformation inside Triton tt.func. +// +//===----------------------------------------------------------------------===// + +#include "Transform/Passes.h" + +#include "mlir/Conversion/ControlFlowToSCF/ControlFlowToSCF.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/Dialect/UB/IR/UBOps.h" +#include "mlir/IR/Dominance.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/CFGToSCF.h" + +#include "triton/Dialect/Triton/IR/Dialect.h" + +namespace mlir::triton { +#define GEN_PASS_DEF_LIFTTTCFTOSCF +#include "Transform/Passes.h.inc" +} // namespace mlir::triton + +using namespace mlir; + +namespace { + +// A ControlFlowToSCF transformation that creates tt.return for unreachable. +struct TTControlFlowToSCFTransformation + : public ControlFlowToSCFTransformation { + FailureOr createUnreachableTerminator(Location loc, + OpBuilder &builder, + Region ®ion) override { + Operation *parentOp = region.getParentOp(); + if (auto funcOp = dyn_cast(parentOp)) { + SmallVector rets; + for (Type ty : funcOp.getResultTypes()) + rets.push_back(getUndefValue(loc, builder, ty)); + return triton::ReturnOp::create(builder, loc, rets).getOperation(); + } + return ControlFlowToSCFTransformation::createUnreachableTerminator( + loc, builder, region); + } +}; + +struct LiftTTCFToSCFPass + : public ::mlir::triton::impl::LiftTTCFToSCFBase { + + void runOnOperation() override { + ModuleOp module = getOperation(); + TTControlFlowToSCFTransformation transformation; + bool changed = false; + + WalkResult walkRes = module.walk([&](triton::FuncOp funcOp) { + if (funcOp.getBody().empty()) + return WalkResult::advance(); + + auto &domInfo = funcOp != module ? getChildAnalysis(funcOp) + : getAnalysis(); + + auto visitor = [&](Operation *innerOp) -> WalkResult { + for (Region ® : innerOp->getRegions()) { + FailureOr changedFunc = + transformCFGToSCF(reg, transformation, domInfo); + if (failed(changedFunc)) + return WalkResult::interrupt(); + changed |= *changedFunc; + } + return WalkResult::advance(); + }; + + if (funcOp->walk(visitor).wasInterrupted()) + return WalkResult::interrupt(); + return WalkResult::advance(); + }); + + if (walkRes.wasInterrupted()) + return signalPassFailure(); + if (!changed) + markAllAnalysesPreserved(); + } +}; + +} // namespace + +namespace mlir::triton { +std::unique_ptr createLiftTTCFToSCFPass() { + return std::make_unique(); +} +} // namespace mlir::triton diff --git a/third_party/tileir/lib/Transform/RewriteAssumeWithCudaTile.cpp b/third_party/tileir/lib/Transform/RewriteAssumeWithCudaTile.cpp new file mode 100644 index 0000000000..c6b8ccb03a --- /dev/null +++ b/third_party/tileir/lib/Transform/RewriteAssumeWithCudaTile.cpp @@ -0,0 +1,216 @@ +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/IR/Value.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +#include "Transform/Passes.h" +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" +#include "cuda_tile/Dialect/CudaTile/IR/Dialect.h" +#include "cuda_tile/Dialect/CudaTile/IR/Ops.h" +#include "triton/Dialect/Triton/IR/Dialect.h" + +using namespace mlir; + +// MLIR pass TableGen now uses per-pass macros (GEN_PASS_DEF_*). +namespace mlir { +namespace triton { +#define GEN_PASS_DEF_REWRITEASSUMEWITHCUDATILE +#include "Transform/Passes.h.inc" +} // namespace triton +} // namespace mlir + +namespace { + +// clang-format off +// Match pattern: +// %a = ... i32 +// %rem = arith.remsi %a, %c8_i32 : i32 +// %eq = arith.cmpi eq, %rem, %c0_i32 : i32 +// llvm.intr.assume %eq : i1 +// -> +// %tile_a = buildin.unrealized_conversion_cast %a : i32 -> tile +// %assume_a = assume div_by<8 : i64>, %tile_a : tile +// replace %a with %assume_a +// +// Or match pattern for ptr types: +// %ptr = ... tt.ptr +// %ptr_int = tt.ptr_to_int %ptr : !tt.ptr -> i64 +// %rem = arith.remsi %ptr_int, %c16_i64 : i64 +// %eq = arith.cmpi eq, %rem, %c0_i64 : i64 +// llvm.intr.assume %eq : i1 +// -> +// %cuda_ptr = buildin.unrealized_conversion_cast %ptr : !tt.ptr -> tile> +// %assume_cuda_ptr = assume div_by<16 : i64>, %cuda_ptr : tile> +// %tt_ptr = buildin.unrealized_conversion_cast %assume_cuda_ptr : tile> -> tt.ptr +// replace %ptr with %tt_ptr +// clang-format on +LogicalResult RewriteArithAssumeImpl(LLVM::AssumeOp assumeOp, + PatternRewriter &rewriter) { + auto loc = assumeOp.getLoc(); + Value condValue = assumeOp.getCond(); + + // Step 1: Check if the condition is from a arith.cmpi eq operation + auto cmpOp = condValue.getDefiningOp(); + if (!cmpOp || cmpOp.getPredicate() != arith::CmpIPredicate::eq) + return failure(); + + // Step 2: Get the operands of cmpi + Value remResult = cmpOp.getLhs(); + Value zeroConstant = cmpOp.getRhs(); + + // Step 3: Check if zeroConstant is a constant 0 + auto zeroDefOp = zeroConstant.getDefiningOp(); + if (!zeroDefOp) + return failure(); + + // Check if the constant value is 0 + auto zeroAttr = dyn_cast(zeroDefOp.getValue()); + if (!zeroAttr || !zeroAttr.getValue().isZero()) + return failure(); + + // Step 4: Check if remResult is from a arith.remsi operation + auto remOp = remResult.getDefiningOp(); + if (!remOp) + return failure(); + + // Step 5: Get the operands of remsi + Value intOrPtrToInt = remOp.getLhs(); + Value divisorConstant = remOp.getRhs(); + + // Step 6: Check if divisorConstant is a constant + auto divisorOp = divisorConstant.getDefiningOp(); + if (!divisorOp) + return failure(); + + // Get the divisor value + auto divisorAttr = dyn_cast(divisorOp.getValue()); + if (!divisorAttr) + return failure(); + int64_t divisor = divisorAttr.getValue().getSExtValue(); + + auto definingOp = intOrPtrToInt.getDefiningOp(); + if (definingOp) + definingOp->setAttr("tt.divisibility", divisorAttr); + // There are two cases: + // Case 1: intOrPtrToInt is a scalar integer value directly + // Case 2: intOrPtrToInt is a result of tt.ptr_to_int operation + auto ptrToIntOp = intOrPtrToInt.getDefiningOp(); + if (ptrToIntOp) { + Value ttPtr = ptrToIntOp.getOperand(); + auto ttPtrType = dyn_cast(ttPtr.getType()); + if (!ttPtrType) + return failure(); + + Type pointeeType = ttPtrType.getPointeeType(); + Type cudaTilePtrType = + cuda_tile::TileType::get({}, cuda_tile::PointerType::get(pointeeType)); + auto divByAttr = cuda_tile::DivByAttr::get(rewriter.getContext(), divisor, + std::nullopt, std::nullopt); + + auto ttptr2cudaPtrOp = UnrealizedConversionCastOp::create( + rewriter, loc, cudaTilePtrType, ttPtr); + auto cudaTilePtr = ttptr2cudaPtrOp.getResult(0); + auto assumeCudaTileOp = + cuda_tile::AssumeOp::create(rewriter, loc, cudaTilePtr, divByAttr); + Value newTtPtr = + UnrealizedConversionCastOp::create(rewriter, loc, ttPtr.getType(), + assumeCudaTileOp.getResult()) + .getResult(0); + + newTtPtr.getDefiningOp()->setAttr("tt.divisibility", divisorAttr); + // Don't replace uses in the cast tt.ptr to cuda_tile.ptr operation and + // those beyond dominance. + DominanceInfo domInfo(assumeOp); + ttPtr.replaceUsesWithIf(newTtPtr, [&](OpOperand &operand) { + Operation *user = operand.getOwner(); + if (user == ttptr2cudaPtrOp.getOperation()) + return false; + if (domInfo.dominates(assumeOp, user)) + return true; + return false; + }); + return success(); + } else { + // Handle integer case + auto intType = dyn_cast(intOrPtrToInt.getType()); + if (!isa(intOrPtrToInt.getType())) + return failure(); + + // Create cuda_tile.div_by attribute + auto divByAttr = cuda_tile::DivByAttr::get(rewriter.getContext(), divisor, + std::nullopt, std::nullopt); + + Type cudaTileIntType = + cuda_tile::TileType::get({}, intOrPtrToInt.getType()); + auto int2tileIntOp = UnrealizedConversionCastOp::create( + rewriter, loc, cudaTileIntType, intOrPtrToInt); + auto cudaTileInt = int2tileIntOp.getResult(0); + auto assumeCudaTileOp = + cuda_tile::AssumeOp::create(rewriter, loc, cudaTileInt, divByAttr); + Value assumedInt = UnrealizedConversionCastOp::create( + rewriter, loc, intOrPtrToInt.getType(), + assumeCudaTileOp.getResult()) + .getResult(0); + + assumedInt.getDefiningOp()->setAttr("tt.divisibility", divisorAttr); + // Don't replace uses in the cast tt.ptr to cuda_tile.ptr operation and + // those beyond dominance. + DominanceInfo domInfo(assumeOp); + intOrPtrToInt.replaceUsesWithIf(assumedInt, [&](OpOperand &operand) { + Operation *user = operand.getOwner(); + if (user == int2tileIntOp.getOperation()) + return false; + if (domInfo.dominates(assumeOp, user)) + return true; + return false; + }); + return success(); + } +} + +class CudaTileTensorAssumePattern : public OpRewritePattern { +public: + CudaTileTensorAssumePattern(MLIRContext *context) + : OpRewritePattern(context) {} + + LogicalResult matchAndRewrite(LLVM::AssumeOp assumeOp, + PatternRewriter &rewriter) const override { + if (succeeded(RewriteArithAssumeImpl(assumeOp, rewriter))) { + rewriter.eraseOp(assumeOp); + return success(); + } + + rewriter.eraseOp(assumeOp); + return failure(); + } +}; + +// Pass to rewrite llvm.intr.assume to cuda_tile.assume +class RewriteAssumeWithCudaTilePass + : public ::mlir::triton::impl::RewriteAssumeWithCudaTileBase< + RewriteAssumeWithCudaTilePass> { +public: + void runOnOperation() override { + MLIRContext *context = &getContext(); + ModuleOp module = getOperation(); + + // Create rewrite patterns + RewritePatternSet patterns(context); + patterns.add(context); + + // Apply rewrite patterns + if (failed(applyPatternsGreedily(module, std::move(patterns)))) + signalPassFailure(); + } +}; + +} // namespace + +std::unique_ptr mlir::triton::createRewriteAssumeWithCudaTilePass() { + return std::make_unique(); +} diff --git a/third_party/tileir/lib/TritonToTileIR/CMakeLists.txt b/third_party/tileir/lib/TritonToTileIR/CMakeLists.txt new file mode 100644 index 0000000000..46537b50bd --- /dev/null +++ b/third_party/tileir/lib/TritonToTileIR/CMakeLists.txt @@ -0,0 +1,21 @@ +set(CUDA_TILE_INSTALL_DIR $ENV{CUDA_TILE_INSTALL_DIR}) + +add_triton_library(TritonToTileIR + MapElementwiseExpansion.cpp + TritonToTileIRPass.cpp + Utils.cpp + + DEPENDS + TritonToTileIRConversionPassIncGen + + LINK_LIBS PUBLIC + # order is important + MLIRIR + MLIRPass + MLIRTransforms + TritonAnalysis + TritonIR + Utils + ${CUDA_TILE_INSTALL_DIR}/lib/libCudaTileDialect.a + ${CUDA_TILE_INSTALL_DIR}/lib/libCudaTileTransforms.a +) diff --git a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp new file mode 100644 index 0000000000..46ccea01e3 --- /dev/null +++ b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp @@ -0,0 +1,506 @@ +//===- MapElementwiseExpansion.cpp - Pre-processing for map_elementwise ---===// +// +// Implements pre-processing utilities for tt.map_elementwise: +// - Scalar-level if-conversion (scf.if → arith.select) +// - Tensor-level expansion (lift scalar ops to tensor ops) +// - K-way pack support via recursive binary split/join +// +// Expansion approach (tensor lifting): +// +// All map_elementwise ops are expanded before dialect conversion. The scalar +// region body is lifted to tensor-level ops: each scalar op becomes its +// tensor equivalent, constants are splatted via tt.splat, and scf.if is +// replaced with arith.select on tensors. The map_elementwise op is then +// erased, leaving only standard tensor ops that flow through the normal +// TritonToTileIR conversion pipeline. +// +// For regions with control flow (scf.if), the lifting handles branch +// flattening (scf.if → arith.select) as part of the tensor expansion. +// For pack > 1, inputs are split into sub-element tensors via recursive +// binary tt.split, processed independently, then reassembled via tt.join. +// +//===----------------------------------------------------------------------===// + +#include "MapElementwiseExpansion.h" + +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/IRMapping.h" +#include "triton/Dialect/Triton/IR/Dialect.h" + +namespace mlir { +namespace triton { +namespace { + +using namespace mlir; + +//===----------------------------------------------------------------------===// +// Tensor lifting utilities +//===----------------------------------------------------------------------===// + +// Forward declaration for mutual recursion. +static LogicalResult liftBodyOp( + Operation &bodyOp, OpBuilder &builder, Location loc, + IRMapping &mapping, ArrayRef shape, Attribute encoding); + +// Lift a scalar value to tensor: if already tensor, return as-is. +// If scalar (e.g., hoisted constant or block argument from outside), +// insert tt.splat to broadcast to the target tensor shape. +static Value liftToTensor( + Value val, OpBuilder &builder, Location loc, IRMapping &mapping, + ArrayRef shape, Attribute encoding) { + Value mapped = mapping.lookupOrDefault(val); + if (isa(mapped.getType())) + return mapped; + auto tensorType = RankedTensorType::get(shape, mapped.getType(), encoding); + auto splat = triton::SplatOp::create(builder, loc, tensorType, mapped); + mapped = splat.getResult(); + mapping.map(val, mapped); + return mapped; +} + +// Recursively lift all ops in a region body (single block) to tensor level. +// Returns the lifted values corresponding to the scf.yield operands. +static SmallVector liftRegionBody( + Region ®ion, OpBuilder &builder, Location loc, + IRMapping &mapping, ArrayRef shape, Attribute encoding) { + assert(region.hasOneBlock() && "expected single-block region"); + Block &body = region.front(); + for (auto &op : body.without_terminator()) { + if (failed(liftBodyOp(op, builder, loc, mapping, shape, encoding))) + return {}; + } + // Collect yield operands + auto yieldOp = cast(body.getTerminator()); + SmallVector results; + for (auto val : yieldOp.getOperands()) + results.push_back( + liftToTensor(val, builder, loc, mapping, shape, encoding)); + return results; +} + +// Lift a single scalar op to its tensor-level equivalent. +// Handles: +// - arith::ConstantOp → DenseElementsAttr splat +// - scf::IfOp → recursive expansion into arith.select +// - generic ops → remap operands to tensor, create new op with tensor types +static LogicalResult liftBodyOp( + Operation &bodyOp, OpBuilder &builder, Location loc, + IRMapping &mapping, ArrayRef shape, Attribute encoding) { + + // Skip terminators handled by callers + if (isa(&bodyOp)) + return success(); + + // (1) arith::ConstantOp → DenseElementsAttr splat + if (auto constOp = dyn_cast(&bodyOp)) { + auto scalarAttr = cast(constOp.getValue()); + auto tensorType = RankedTensorType::get( + shape, scalarAttr.getType(), encoding); + auto splatAttr = DenseElementsAttr::get(tensorType, scalarAttr); + auto newConst = arith::ConstantOp::create(builder, loc, splatAttr); + mapping.map(constOp.getResult(), newConst.getResult()); + return success(); + } + + // (2) scf::IfOp → lift both branches, emit arith.select + if (auto ifOp = dyn_cast(&bodyOp)) { + Value condTensor = + liftToTensor(ifOp.getCondition(), builder, loc, mapping, shape, + encoding); + + // Process then region + IRMapping thenMapping(mapping); + SmallVector thenVals = + liftRegionBody(ifOp.getThenRegion(), builder, loc, thenMapping, + shape, encoding); + if (thenVals.empty() && ifOp.getNumResults() > 0) + return failure(); + + // Process else region + IRMapping elseMapping(mapping); + SmallVector elseVals = + liftRegionBody(ifOp.getElseRegion(), builder, loc, elseMapping, + shape, encoding); + if (elseVals.empty() && ifOp.getNumResults() > 0) + return failure(); + + // Emit arith.select for each result + for (auto [i, oldRes] : llvm::enumerate(ifOp.getResults())) { + auto sel = arith::SelectOp::create( + builder, loc, condTensor, thenVals[i], elseVals[i]); + mapping.map(oldRes, sel.getResult()); + } + return success(); + } + + // (3) Generic op: remap operands to tensor, fix result types + SmallVector newOperands; + for (auto operand : bodyOp.getOperands()) { + newOperands.push_back( + liftToTensor(operand, builder, loc, mapping, shape, encoding)); + } + + SmallVector newResultTypes; + for (auto result : bodyOp.getResults()) { + newResultTypes.push_back( + RankedTensorType::get(shape, result.getType(), encoding)); + } + + OperationState state(loc, bodyOp.getName()); + state.addOperands(newOperands); + state.addTypes(newResultTypes); + state.addAttributes(bodyOp.getAttrs()); + auto *newOp = Operation::create(state); + builder.insert(newOp); + + for (auto [oldRes, newRes] : + llvm::zip(bodyOp.getResults(), newOp->getResults())) { + mapping.map(oldRes, newRes); + } + return success(); +} + +//===----------------------------------------------------------------------===// +// Scalar-level if-conversion (used by ifConvertMapElementwiseRegions) +//===----------------------------------------------------------------------===// + +// Convert all scf.if → arith.select within a region. +// Uses walk + reverse to process innermost scf.if first, ensuring outer +// scf.if bodies are already pure when we process them. +static LogicalResult scalarIfConvert(Region ®ion) { + if (!region.hasOneBlock()) + return failure(); + Block &body = region.front(); + + // Collect all scf.if ops (including nested ones in then/else regions) + SmallVector ifOps; + body.walk([&](scf::IfOp ifOp) { ifOps.push_back(ifOp); }); + + // Process innermost first (walk visits outer before inner, reverse fixes this) + for (auto ifOp : llvm::reverse(ifOps)) { + OpBuilder builder(ifOp); + Location loc = ifOp.getLoc(); + + // Get then yield values + auto &thenBlock = ifOp.getThenRegion().front(); + auto thenYield = cast(thenBlock.getTerminator()); + SmallVector thenVals(thenYield.getOperands()); + + // Get else yield values + auto &elseBlock = ifOp.getElseRegion().front(); + auto elseYield = cast(elseBlock.getTerminator()); + SmallVector elseVals(elseYield.getOperands()); + + // Move then/else ops before the scf.if (they're pure, safe to speculate) + for (auto &op : llvm::make_early_inc_range(thenBlock.without_terminator())) + op.moveBefore(ifOp); + for (auto &op : llvm::make_early_inc_range(elseBlock.without_terminator())) + op.moveBefore(ifOp); + + // Create arith.select for each result + SmallVector selectResults; + for (auto [tv, ev] : llvm::zip(thenVals, elseVals)) { + auto sel = arith::SelectOp::create( + builder, loc, ifOp.getCondition(), tv, ev); + selectResults.push_back(sel.getResult()); + } + + // Replace scf.if results with selects and erase + ifOp.replaceAllUsesWith(selectResults); + ifOp.erase(); + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// K-way pack support via recursive binary split/join +//===----------------------------------------------------------------------===// + +// Recursively split a tensor along its last dimension using binary tt.split. +// Input: tensor of shape [.., M, K] where K is a power of 2. +// Output: K tensors of shape [.., M]. +// For K=1: just return the input (no split needed). +// For K=2: one tt.split → 2 results. +// For K>2: split into halves, recurse on each half. +static SmallVector recursiveSplit( + OpBuilder &builder, Location loc, Value tensor, int K) { + if (K == 1) { + // Last dim is 1, drop it via reshape + auto srcType = cast(tensor.getType()); + auto srcShape = srcType.getShape(); + SmallVector newShape(srcShape.begin(), srcShape.end() - 1); + auto newType = RankedTensorType::get( + newShape, srcType.getElementType(), srcType.getEncoding()); + Value reshaped = triton::ReshapeOp::create( + builder, loc, newType, tensor).getResult(); + return {reshaped}; + } + + if (K == 2) { + // Base case: binary split + auto splitOp = triton::SplitOp::create(builder, loc, tensor); + return {splitOp.getOutLHS(), splitOp.getOutRHS()}; + } + + // Recursive case: reshape last dim K → [K/2, 2], split, recurse + auto srcType = cast(tensor.getType()); + auto srcShape = srcType.getShape(); + // [.., M, K] → [.., M, K/2, 2] + SmallVector reshapedShape(srcShape.begin(), srcShape.end()); + reshapedShape.back() = K / 2; + reshapedShape.push_back(2); + + auto reshapedType = RankedTensorType::get( + reshapedShape, srcType.getElementType(), srcType.getEncoding()); + Value reshaped = triton::ReshapeOp::create( + builder, loc, reshapedType, tensor).getResult(); + + // Split [.., M, K/2, 2] → lhs: [.., M, K/2], rhs: [.., M, K/2] + auto splitOp = triton::SplitOp::create(builder, loc, reshaped); + + // Recurse on each half + SmallVector lhsResults = recursiveSplit(builder, loc, splitOp.getOutLHS(), K / 2); + SmallVector rhsResults = recursiveSplit(builder, loc, splitOp.getOutRHS(), K / 2); + + // Interleave: [lhs0, rhs0, lhs1, rhs1, ...] to maintain element order + SmallVector results; + for (unsigned i = 0; i < lhsResults.size(); i++) { + results.push_back(lhsResults[i]); + results.push_back(rhsResults[i]); + } + return results; +} + +// Recursively join K tensors into one using binary tt.join. +// Input: K tensors of shape [.., M]. +// Output: tensor of shape [.., M, K]. +// Reverses the recursiveSplit operation. +static Value recursiveJoin( + OpBuilder &builder, Location loc, ArrayRef tensors, int K, + RankedTensorType finalType) { + if (K == 1) { + // Add back last dim via reshape: [.., M] → [.., M, 1] + auto srcType = cast(tensors[0].getType()); + auto srcShape = srcType.getShape(); + SmallVector newShape(srcShape.begin(), srcShape.end()); + newShape.push_back(1); + auto newType = RankedTensorType::get( + newShape, srcType.getElementType(), srcType.getEncoding()); + return triton::ReshapeOp::create(builder, loc, newType, tensors[0]).getResult(); + } + + if (K == 2) { + // Base case: binary join + return triton::JoinOp::create(builder, loc, tensors[0], tensors[1]).getResult(); + } + + // Recursive case: de-interleave into even/odd halves, recurse, join + SmallVector lhsTensors, rhsTensors; + for (unsigned i = 0; i < tensors.size(); i += 2) { + lhsTensors.push_back(tensors[i]); + rhsTensors.push_back(tensors[i + 1]); + } + + // Recurse on halves → each produces [.., M, K/2] + Value lhs = recursiveJoin(builder, loc, lhsTensors, K / 2, finalType); + Value rhs = recursiveJoin(builder, loc, rhsTensors, K / 2, finalType); + + // Reshape each [.., M, K/2] → [.., M * K/2] to prepare for join + // Then join [.., M*K/2] + [.., M*K/2] → [.., M*K/2, 2] + // Then reshape [.., M*K/2, 2] → [.., M, K] + + // Actually simpler: join adds a new last dim of 2. + // [.., M, K/2] join → [.., M, K/2, 2] + // Then reshape [.., M, K/2, 2] → [.., M, K] + auto joined = triton::JoinOp::create(builder, loc, lhs, rhs); + + // Reshape to collapse the last two dims + auto joinedType = cast(joined.getResult().getType()); + auto joinedShape = joinedType.getShape(); + SmallVector collapsedShape(joinedShape.begin(), joinedShape.end() - 2); + collapsedShape.push_back(joinedShape[joinedShape.size() - 2] * 2); + + auto collapsedType = RankedTensorType::get( + collapsedShape, joinedType.getElementType(), joinedType.getEncoding()); + return triton::ReshapeOp::create(builder, loc, collapsedType, joined.getResult()).getResult(); +} + +//===----------------------------------------------------------------------===// +// Main expansion logic +//===----------------------------------------------------------------------===// + +static bool regionHasScfIf(Region ®ion) { + bool found = false; + region.walk([&](scf::IfOp) { found = true; return WalkResult::interrupt(); }); + return found; +} + +static LogicalResult expandMapElementwiseOpsImpl(Operation *rootOp) { + SmallVector opsToExpand; + rootOp->walk([&](triton::MapElementwiseOp op) { + opsToExpand.push_back(op); + }); + + for (auto op : opsToExpand) { + auto ®ion = op.getScalarOp(); + + if (!region.hasOneBlock()) { + op.emitError("map_elementwise region has multiple blocks; " + "expected lift-tt-cf-to-scf to have run first"); + return failure(); + } + + int pack = op.getPack(); + if (!llvm::isPowerOf2_32(pack)) { + op.emitError("pack must be a power of 2"); + return failure(); + } + + Block &body = region.front(); + OpBuilder builder(op); + Location loc = op.getLoc(); + unsigned numSrcs = op.getSrcs().size(); + + auto inputType = cast(op.getSrcs()[0].getType()); + auto origShape = inputType.getShape(); + auto encoding = inputType.getEncoding(); + + if (pack == 1) { + // Direct lifting on original tensor shape. + IRMapping mapping; + for (auto [arg, src] : llvm::zip(body.getArguments(), op.getSrcs())) + mapping.map(arg, src); + + for (auto &bodyOp : body.without_terminator()) { + if (failed(liftBodyOp(bodyOp, builder, loc, mapping, origShape, + encoding))) + return failure(); + } + + auto terminator = + cast(body.getTerminator()); + SmallVector results; + for (auto val : terminator.getResult()) + results.push_back( + liftToTensor(val, builder, loc, mapping, origShape, encoding)); + + op.replaceAllUsesWith(results); + op.erase(); + } else { + // pack > 1: Decompose into sub-element tensors, lift, then reassemble. + // + // Block arg order: repeatInterleave([src0_type, src1_type, ...], pack) + // = [src0_e0, src0_e1, ..., src0_eK-1, src1_e0, ...] + // Return order: repeatInterleave([out0_type, out1_type, ...], pack) + // = [out0_e0, out0_e1, ..., out0_eK-1, out1_e0, ...] + + int64_t lastDim = origShape.back(); + if (lastDim % pack != 0) { + op.emitError("last dimension not divisible by pack"); + return failure(); + } + + // Step 1: Reshape each input [.., N] → [.., N/K, K] + // Step 2: Recursively split into K sub-tensors of shape [.., N/K] + SmallVector> splitResults(numSrcs); + for (unsigned i = 0; i < numSrcs; i++) { + Value src = op.getSrcs()[i]; + auto srcType = cast(src.getType()); + + // Build reshaped shape: [.., N/K, K] + SmallVector reshapedShape(srcType.getShape().begin(), + srcType.getShape().end()); + reshapedShape.back() = lastDim / pack; + reshapedShape.push_back(pack); + + auto reshapedType = RankedTensorType::get( + reshapedShape, srcType.getElementType(), srcType.getEncoding()); + Value reshaped = triton::ReshapeOp::create( + builder, loc, reshapedType, src).getResult(); + + // Recursively split into K sub-tensors + splitResults[i] = recursiveSplit(builder, loc, reshaped, pack); + } + + // Compute the sub-tensor shape (used for lifting) + SmallVector subShape(origShape.begin(), origShape.end()); + subShape.back() = lastDim / pack; + + // Infer sub-tensor encoding from the split result + auto subEncoding = + cast(splitResults[0][0].getType()).getEncoding(); + + // Step 3: Map block args to sub-tensors. + // Block args are interleaved: [src0_e0, src0_e1, ..., src1_e0, ...] + IRMapping mapping; + unsigned argIdx = 0; + for (unsigned srcIdx = 0; srcIdx < numSrcs; srcIdx++) { + for (int p = 0; p < pack; p++) { + mapping.map(body.getArgument(argIdx++), splitResults[srcIdx][p]); + } + } + + // Step 4: Lift all body ops on the sub-tensor shape [.., N/K] + for (auto &bodyOp : body.without_terminator()) { + if (failed(liftBodyOp(bodyOp, builder, loc, mapping, subShape, + subEncoding))) + return failure(); + } + + // Step 5+6: Collect results, join K sub-tensors, reshape back. + auto terminator = + cast(body.getTerminator()); + auto retVals = terminator.getResult(); + unsigned numOutputs = op.getNumResults(); + + SmallVector results; + for (unsigned outIdx = 0; outIdx < numOutputs; outIdx++) { + // Collect the K sub-element results for this output + SmallVector subResults; + for (int p = 0; p < pack; p++) { + subResults.push_back( + liftToTensor(retVals[outIdx * pack + p], builder, loc, mapping, + subShape, subEncoding)); + } + + // Join K sub-tensors → [.., N/K, K] + auto outputType = cast(op->getResult(outIdx).getType()); + Value joined = recursiveJoin(builder, loc, subResults, pack, outputType); + + // Reshape [.., N/K, K] → [.., N] + Value result = triton::ReshapeOp::create( + builder, loc, outputType, joined).getResult(); + results.push_back(result); + } + + op.replaceAllUsesWith(results); + op.erase(); + } + } + + return success(); +} + +} // anonymous namespace + +//===----------------------------------------------------------------------===// +// Public API +//===----------------------------------------------------------------------===// + +LogicalResult ifConvertMapElementwiseRegions(Operation *rootOp) { + WalkResult result = rootOp->walk([&](triton::MapElementwiseOp op) { + if (failed(scalarIfConvert(op.getScalarOp()))) + return WalkResult::interrupt(); + return WalkResult::advance(); + }); + return failure(result.wasInterrupted()); +} + +LogicalResult expandMapElementwiseOps(Operation *rootOp) { + return expandMapElementwiseOpsImpl(rootOp); +} + +} // namespace triton +} // namespace mlir diff --git a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h new file mode 100644 index 0000000000..b7f653a309 --- /dev/null +++ b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h @@ -0,0 +1,32 @@ +//===- MapElementwiseExpansion.h - Pre-processing for map_elementwise -----===// +// +// Declares pre-processing utilities for tt.map_elementwise: +// - ifConvertMapElementwiseRegions: scf.if → arith.select at scalar level +// - expandMapElementwiseOps: tensor-level expansion for all map_elementwise ops +// +// These are pure IR rewrites that run before dialect conversion. +// +//===----------------------------------------------------------------------===// + +#ifndef TRITON_TO_TILEIR_MAP_ELEMENTWISE_EXPANSION_H +#define TRITON_TO_TILEIR_MAP_ELEMENTWISE_EXPANSION_H + +#include "mlir/IR/Operation.h" +#include "mlir/Support/LogicalResult.h" + +namespace mlir { +namespace triton { + +/// Pre-process all map_elementwise regions: convert scf.if → arith.select +/// at scalar level within the region body. +LogicalResult ifConvertMapElementwiseRegions(Operation *rootOp); + +/// Expand all map_elementwise ops into tensor-level arith/math ops via +/// tensor lifting. Each scalar op in the region is lifted to its tensor +/// equivalent, and the map_elementwise op is erased. +LogicalResult expandMapElementwiseOps(Operation *rootOp); + +} // namespace triton +} // namespace mlir + +#endif // TRITON_TO_TILEIR_MAP_ELEMENTWISE_EXPANSION_H diff --git a/third_party/tileir/lib/TritonToTileIR/TritonToCudaTileExperimentalPass.cpp b/third_party/tileir/lib/TritonToTileIR/TritonToCudaTileExperimentalPass.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp b/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp new file mode 100644 index 0000000000..9f85f0a19c --- /dev/null +++ b/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp @@ -0,0 +1,2958 @@ +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" +#include "mlir/Dialect/GPU/IR/GPUDialect.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +#include "MapElementwiseExpansion.h" +#include "TritonToTileIR/Passes.h" +#include "TritonToTileIR/TritonToTileIRPass.h" +#include "TritonToTileIR/Utils.h" +#include "Utils/Utils.h" +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" +#include "cuda_tile/Dialect/CudaTile/IR/Types.h" +#include "triton/Analysis/AxisInfo.h" +#include +#include +#include +#include + +// MLIR pass TableGen uses per-pass macros (GEN_PASS_DEF_*). +namespace mlir { +namespace triton { +#define GEN_PASS_DEF_CONVERTTRITONTOCUDATILE +#include "TritonToTileIR/Passes.h.inc" +} // namespace triton +} // namespace mlir + +namespace { + +using namespace mlir; +using namespace mlir::triton; +using namespace mlir::bridge_utils; + +// We can safely assume that the pointer and strides in TMA descriptors are +// divisible by 16. (Sizes can do not have this divisibility requirement.) +static constexpr int64_t kTMAAlignment = 16; +// TMA hardware limit for stride. +constexpr unsigned long long kMaxStride = (1LL << 40) - 1; +// TMA hardware limit for shape. +constexpr unsigned long long kMaxShape = (1LL << 32) - 1; + +// +// CudaTileConversion +// +class CudaTileConversionTarget : public ConversionTarget { +public: + CudaTileConversionTarget(MLIRContext &context, + CudaTileTypeConverter &typeConverter) + : ConversionTarget(context) { + + addLegalDialect(); + addIllegalDialect(); + + addLegalOp(); + // barrierOp will be removed in AutoGenMemoryTokenPass + addLegalOp(); + + // TODO: support these arith/math ops in cuda_tile + addLegalOp(); + + // TODO: remove these ops + addLegalOp(); + } +}; + +template +static LogicalResult rewriteReshapeLike(const TypeConverter *typeConverter, + OpTy op, typename OpTy::Adaptor adaptor, + ConversionPatternRewriter &rewriter) { + RankedTensorType sourceTensorType = op.getSrc().getType(); + RankedTensorType resultTensorType = op.getResult().getType(); + + // If source and result types are matching, those are no-ops. + if (sourceTensorType == resultTensorType) { + rewriter.replaceOp(op, adaptor.getSrc()); + return success(); + } + + auto newResultType = typeConverter->convertType(resultTensorType); + assert(newResultType && "failed to convert tensor type"); + rewriter.replaceOpWithNewOp(op, newResultType, adaptor.getSrc()); + return success(); +} + +static DenseIntOrFPElementsAttr +convertArithAttrToCudaTileAttr(const TypedAttr &attr, + const ShapedType &shapeType) { + auto shape = shapeType.getShape(); + if (auto arithDense = dyn_cast(attr)) + return cast(DenseElementsAttr::getFromRawBuffer( + shapeType, arithDense.getRawData())); + return cast( + DenseElementsAttr::get(shapeType, {cast(attr)})); +} + +class ConvertAbsFOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(math::AbsFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = op.getLoc(); + cuda_tile::TileType newResultTensorType = cast( + this->getTypeConverter()->convertType(op.getResult().getType())); + auto eltype = newResultTensorType.getElementType(); + if (eltype.getIntOrFloatBitWidth() < 16) { + // f8 and f4 not directly supported, upcast to fp16 and downcast after + cuda_tile::TileType f16TensorType = cuda_tile::TileType::get( + newResultTensorType.getShape(), rewriter.getF16Type()); + auto upcast = cuda_tile::FToFOp::create(rewriter, loc, f16TensorType, + adaptor.getOperand()); + auto absop = cuda_tile::AbsFOp::create(rewriter, loc, upcast); + rewriter.replaceOpWithNewOp(op, newResultTensorType, + absop); + return success(); + } + rewriter.replaceOpWithNewOp(op, adaptor.getOperand()); + return success(); + } +}; + +class ConvertConstantOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::ConstantOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto converter = this->getTypeConverter(); + auto loc = op.getLoc(); + auto tensorType = converter->convertType(op.getValueAttr().getType()); + if (!isa(tensorType)) + return rewriter.notifyMatchFailure(loc, + "typeConversion of current op failed"); + + DenseIntOrFPElementsAttr tensorAttr = convertArithAttrToCudaTileAttr( + adaptor.getValueAttr(), cast(tensorType)); + + SmallVector retTypes; + if (failed(converter->convertTypes(op->getResultTypes(), retTypes))) + return rewriter.notifyMatchFailure(loc, + "typeConversion of current op failed"); + + rewriter.replaceOpWithNewOp(op, retTypes, + tensorAttr); + return success(); + } +}; + +class ConvertSelectOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::SelectOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + if (auto intTy = dyn_cast(op.getCondition().getType())) { + int rank = cast(adaptor.getTrueValue().getType()).getRank(); + SmallVector reshapedView(rank, 1); + auto newCondType1 = + cuda_tile::TileType::get(reshapedView, op.getCondition().getType()); + + auto reshapeOp = cuda_tile::ReshapeOp::create( + rewriter, op.getLoc(), newCondType1, adaptor.getCondition()); + + auto newShape = + cast(adaptor.getTrueValue().getType()).getShape(); + auto newCondType2 = + cuda_tile::TileType::get(newShape, op.getCondition().getType()); + + auto broadcastOp = cuda_tile::BroadcastOp::create( + rewriter, op.getLoc(), newCondType2, reshapeOp.getResult()); + + rewriter.replaceOpWithNewOp( + op, broadcastOp.getResult(), adaptor.getTrueValue(), + adaptor.getFalseValue()); + return success(); + } + rewriter.replaceOpWithNewOp(op, adaptor.getCondition(), + adaptor.getTrueValue(), + adaptor.getFalseValue()); + return success(); + } +}; + +class ConvertReturnOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + return success(); + } +}; + +class ConvertPrintOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::PrintOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto prefix = op.getPrefix().str(); + auto args = adaptor.getArgs(); + std::string newPrefix = prefix; + for (Value arg : args) { + auto tileType = cast(arg.getType()); + Type elType = tileType.getElementType(); + if (isa(elType)) { + newPrefix += "%i"; + } else if (isa(elType)) { + newPrefix += "%.5f"; + } else if (isa(elType)) { + newPrefix += "%p"; + } else { + llvm::report_fatal_error("unsupported type"); + } + } + newPrefix += "\n"; + + cuda_tile::PrintTkoOp::create(rewriter, op.getLoc(), newPrefix, args, + /*token=*/Value()); + rewriter.eraseOp(op); + return success(); + } +}; + +class ConvertLoadOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + const DenseMap &numStagesMap; + int computeCapability; + std::optional numStages; + ConvertLoadOp(TypeConverter &typeConverter, MLIRContext *context, + DenseMap &numStagesMap, int computeCapability, + std::optional numStages) + : OpConversionPattern(typeConverter, context), + numStagesMap(numStagesMap), computeCapability(computeCapability), + numStages(numStages) {} + + LogicalResult + matchAndRewrite(triton::LoadOp op, typename triton::LoadOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type retType = getTypeConverter()->convertType(op.getType()); + auto ctx = rewriter.getContext(); + + auto sem = cuda_tile::MemoryOrderingSemanticsAttr::get( + ctx, cuda_tile::MemoryOrderingSemantics::WEAK); + // There is no per op num_stages support in triton, and usually the + // global-scope num_stages is meant to tune for the large load op which are + // critical to performance. For small load op, we skip the num_stages hint. + auto tileType = cast(retType); + int64_t tileSizeInBytes = + tileType.getNumElements() * + (tileType.getElementType().getIntOrFloatBitWidth() / 8); + std::optional optHint; + if (tileSizeInBytes <= 256) { + optHint = std::nullopt; + } else { + optHint = mlir::triton::utils::convertNumStagesToOptHint( + op, ctx, numStagesMap, computeCapability, numStages); + } + + auto newLoadOp = cuda_tile::LoadPtrTkoOp::create( + rewriter, op.getLoc(), retType, cuda_tile::TokenType::get(ctx), sem, + /*memoryScope=*/nullptr, adaptor.getPtr(), adaptor.getMask(), + adaptor.getOther(), /*token=*/nullptr, optHint.value_or(nullptr)); + rewriter.replaceOp(op, newLoadOp.getResult()); + return success(); + } +}; + +class ConvertStoreOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + const DenseMap &numStagesMap; + int computeCapability; + std::optional numStages; + ConvertStoreOp(TypeConverter &typeConverter, MLIRContext *context, + DenseMap &numStagesMap, + int computeCapability, std::optional numStages) + : OpConversionPattern(typeConverter, context), + numStagesMap(numStagesMap), computeCapability(computeCapability), + numStages(numStages) {} + + LogicalResult + matchAndRewrite(triton::StoreOp op, typename triton::StoreOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto ctx = rewriter.getContext(); + auto sem = cuda_tile::MemoryOrderingSemanticsAttr::get( + ctx, cuda_tile::MemoryOrderingSemantics::WEAK); + + // There is no per op num_stages support in triton, and usually the + // global-scope num_stages is meant to tune for the large store op which are + // critical to performance. For small store op, we skip the num_stages hint. + auto tileType = cast(adaptor.getValue().getType()); + int64_t tileSizeInBytes = + tileType.getNumElements() * + (tileType.getElementType().getIntOrFloatBitWidth() / 8); + std::optional optHint; + if (tileSizeInBytes <= 256) { + optHint = std::nullopt; + } else { + optHint = mlir::triton::utils::convertNumStagesToOptHint( + op, ctx, numStagesMap, computeCapability, numStages); + } + + cuda_tile::StorePtrTkoOp::create( + rewriter, op.getLoc(), cuda_tile::TokenType::get(ctx), sem, + /*memoryScope=*/nullptr, adaptor.getPtr(), adaptor.getValue(), + adaptor.getMask(), /*token=*/nullptr, optHint.value_or(nullptr)); + rewriter.eraseOp(op); + return success(); + } +}; + +// Helper function to create target operations (FuncOp or EntryOp) +template +void createTargetOp(ConversionPatternRewriter &rewriter, triton::FuncOp op, + FunctionType newFunctionType, + TypeConverter::SignatureConversion &result, + int computeCapability, int numCTAInCGA, + int simtNumWarpsInCTA, int occupancy) { + MLIRContext *ctx = rewriter.getContext(); + + auto newKernelName = op.getSymName(); + + TargetOp newOp; + if constexpr (std::is_same_v) { + llvm::StringRef archPrefix = "sm_"; + auto numCTAAttr = + rewriter.getNamedAttr("num_cta_in_cga", + rewriter.getI32IntegerAttr(numCTAInCGA)); + auto occupancyAttr = + rewriter.getNamedAttr("occupancy", + rewriter.getI32IntegerAttr(occupancy)); + auto hintEntry = rewriter.getNamedAttr( + (archPrefix + llvm::Twine(computeCapability)).str(), + DictionaryAttr::get(ctx, + {numCTAAttr, occupancyAttr})); + cuda_tile::OptimizationHintsAttr optHint = + cuda_tile::OptimizationHintsAttr::get( + ctx, DictionaryAttr::get(ctx, {hintEntry})); + newOp = cuda_tile::EntryOp::create( + rewriter, op.getLoc(), StringAttr::get(ctx, newKernelName), + newFunctionType, op.getArgAttrsAttr(), op.getResAttrsAttr(), optHint); + } else { + static_assert(std::is_same_v, "unsupported TargetOp type"); + } + + for (unsigned i = 0, e = newOp.getNumArguments(); i < e; ++i) { + if (newOp.getArgAttr(i, "tt.divisibility")) { + newOp.removeArgAttr(i, "tt.divisibility"); + } + } + + Block *oldBody = &op.getFunctionBody().front(); + (void)rewriter.applySignatureConversion(oldBody, result); + rewriter.inlineRegionBefore(op.getBody(), newOp.getBody(), + newOp.getBody().end()); + rewriter.replaceOp(op, newOp); +} + +class ConvertFuncOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + int computeCapability; + int numCTAInCGA; + int simtNumWarpsInCTA; + int occupancy; + ConvertFuncOp(TypeConverter &typeConverter, MLIRContext *context, + int computeCapability, int numCTAInCGA, int simtNumWarpsInCTA, + int occupancy) + : OpConversionPattern(typeConverter, context), + computeCapability(computeCapability), numCTAInCGA(numCTAInCGA), + simtNumWarpsInCTA(simtNumWarpsInCTA), occupancy(occupancy) {}; + + LogicalResult + matchAndRewrite(triton::FuncOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto converter = getTypeConverter(); + FunctionType type = dyn_cast(op.getFunctionType()); + + TypeConverter::SignatureConversion result(type.getNumInputs()); + + // Special treat for host tma descriptor: + // We need to convert triton::TensorDescType to TileType instead of + // PartitionViewType, because cuda_tile does not allow view type in + // signatures. Here we convert triton::TensorDescType to integer type, later + // type converter will convert it to TileType in the + // convertSignatureArgs API. + SmallVector modifiedInputs = llvm::to_vector(type.getInputs()); + for (auto &inType : modifiedInputs) { + if (isa(inType)) + inType = rewriter.getI32Type(); + } + + SmallVector newResults; + if (failed(typeConverter->convertSignatureArgs(modifiedInputs, result)) || + failed(typeConverter->convertTypes(type.getResults(), newResults)) || + failed(rewriter.convertRegionTypes(&op.getFunctionBody(), + *typeConverter, &result))) + return rewriter.notifyMatchFailure(op.getLoc(), + "typeConversion of FuncOp failed"); + + auto context = rewriter.getContext(); + auto newFunctionType = + FunctionType::get(context, result.getConvertedTypes(), newResults); + + createTargetOp(rewriter, op, newFunctionType, result, + computeCapability, numCTAInCGA, + simtNumWarpsInCTA, occupancy); + return success(); + return success(); + } +}; + +class ConvertBitcastOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::BitcastOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto tyCvt = getTypeConverter(); + auto ty = tyCvt->convertType(op.getResult().getType()); + auto resTyScalar = getElementTypeOrSelf(ty); + auto srcTyScalar = getElementTypeOrSelf(adaptor.getSrc().getType()); + if (isa(resTyScalar) && + isa(srcTyScalar)) { + rewriter.replaceOpWithNewOp(op, ty, + adaptor.getSrc()); + return success(); + } + if (!isa(resTyScalar)) + return rewriter.notifyMatchFailure( + op, "bitcast supports only integer or float types"); + rewriter.replaceOpWithNewOp(op, ty, adaptor.getSrc()); + return success(); + } +}; + +class ConvertBroadCastOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::BroadcastOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + return rewriteReshapeLike( + getTypeConverter(), op, adaptor, rewriter); + } +}; + +class ConvertReshapeOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ReshapeOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: Investigate allow_reorder and efficient_layout since we + // do not map these flags to cuda_tile. + return rewriteReshapeLike( + getTypeConverter(), op, adaptor, rewriter); + } +}; + +class ConvertDescriptorLoadOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + const DenseMap &numStagesMap; + int computeCapability; + std::optional numStages; + ConvertDescriptorLoadOp(TypeConverter &typeConverter, MLIRContext *context, + DenseMap &numStagesMap, + int computeCapability, std::optional numStages) + : OpConversionPattern(typeConverter, context), + numStagesMap(numStagesMap), computeCapability(computeCapability), + numStages(numStages) {} + + LogicalResult + matchAndRewrite(triton::DescriptorLoadOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + + auto ctx = rewriter.getContext(); + auto view = adaptor.getDesc(); + + auto optHint = mlir::triton::utils::convertNumStagesToOptHint( + op, ctx, numStagesMap, computeCapability, numStages); + + auto originalIndices = adaptor.getIndices(); + + SmallVector indices; + SmallVector viewShapeVec; + Type viewTy = view.getType(); + + // Handle PartitionViewType + if (auto partTy = dyn_cast(viewTy)) { + // For PartitionViewType, we need to divide indices by tile size + auto tileSizes = partTy.getTileShape(); + for (size_t i = 0; i < originalIndices.size(); i++) { + Value indicesWithBlockSize = originalIndices[i]; + cuda_tile::TileType constType = + cuda_tile::TileType::get({}, rewriter.getI32Type()); + auto tileSizeAttr = + DenseIntElementsAttr::get(constType, {tileSizes[i]}); + Value tileSizeOp = cuda_tile::ConstantOp::create( + rewriter, op.getLoc(), constType, tileSizeAttr); + indices.push_back(cuda_tile::DivIOp::create( + rewriter, op.getLoc(), indicesWithBlockSize, tileSizeOp, + cuda_tile::Signedness::Signed)); + } + for (size_t i = 0; i < tileSizes.size(); i++) + viewShapeVec.push_back(tileSizes[i]); + } else { + return rewriter.notifyMatchFailure( + op.getLoc(), "expect a partition view type"); + } + + auto tileShape = op.getResult().getType().getShape(); + auto elemTy = op.getResult().getType().getElementType(); + auto viewTileTy = cuda_tile::TileType::get(ctx, viewShapeVec, elemTy); + + auto memOrder = cuda_tile::MemoryOrderingSemanticsAttr::get( + ctx, cuda_tile::MemoryOrderingSemantics::WEAK); + auto LoadViewOp = cuda_tile::LoadViewTkoOp::create( + rewriter, op.getLoc(), viewTileTy, cuda_tile::TokenType::get(ctx), + /*memory_ordering_semantics=*/memOrder, + /*scope=*/nullptr, view, indices, /*token=*/nullptr, + optHint.value_or(nullptr)); + + if (viewShapeVec.size() != tileShape.size()) { + auto tileTy = cuda_tile::TileType::get(ctx, tileShape, elemTy); + auto reshapeOp = cuda_tile::ReshapeOp::create( + rewriter, op.getLoc(), tileTy, LoadViewOp.getTile()); + rewriter.replaceOp(op, reshapeOp.getResult()); + return success(); + } + + rewriter.replaceOp(op, LoadViewOp.getTile()); + return success(); + } +}; + +class ConvertDescriptorStoreOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + const DenseMap &numStagesMap; + int computeCapability; + std::optional numStages; + ConvertDescriptorStoreOp(TypeConverter &typeConverter, MLIRContext *context, + DenseMap &numStagesMap, + int computeCapability, std::optional numStages) + : OpConversionPattern(typeConverter, context), + numStagesMap(numStagesMap), computeCapability(computeCapability), + numStages(numStages) {} + + LogicalResult + matchAndRewrite(triton::DescriptorStoreOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto ctx = rewriter.getContext(); + auto view = adaptor.getDesc(); + + auto originalIndices = adaptor.getIndices(); + SmallVector indices; + Type viewTy = view.getType(); + + // Handle PartitionViewType + if (auto partTy = dyn_cast(viewTy)) { + // For PartitionViewType, we need to divide indices by tile size + auto tileSizes = partTy.getTileShape(); + for (size_t i = 0; i < originalIndices.size(); i++) { + Value idxWithBlockSize = originalIndices[i]; + auto tileSize = tileSizes[i]; + cuda_tile::TileType constType = + cuda_tile::TileType::get({}, rewriter.getI32Type()); + auto tileSizeAttr = DenseIntElementsAttr::get(constType, {tileSize}); + Value tileSizeOp = cuda_tile::ConstantOp::create( + rewriter, op.getLoc(), constType, tileSizeAttr); + indices.push_back(cuda_tile::DivIOp::create( + rewriter, op.getLoc(), idxWithBlockSize, tileSizeOp, + cuda_tile::Signedness::Signed)); + } + } else { + return rewriter.notifyMatchFailure( + op.getLoc(), "expect a partition view type"); + } + + auto optHint = mlir::triton::utils::convertNumStagesToOptHint( + op, ctx, numStagesMap, computeCapability, numStages); + + auto src = adaptor.getSrc(); + auto StoreViewOp = cuda_tile::StoreViewTkoOp::create( + rewriter, op.getLoc(), cuda_tile::TokenType::get(ctx), + cuda_tile::MemoryOrderingSemantics::WEAK, /*scope=*/nullptr, src, view, + indices, /*token=*/nullptr, optHint.value_or(nullptr)); + + rewriter.eraseOp(op); + return success(); + } +}; + + +/// Convert an expand dims to a reshape by adding a new dimension (1) at a given +/// position. +class ConvertExpandDimsOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ExpandDimsOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + return rewriteReshapeLike( + getTypeConverter(), op, adaptor, rewriter); + } +}; + +class ConvertExternElementwiseOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ExternElementwiseOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); + auto symbol = op.getSymbol(); + + auto getTileType = [](Value v) { + return cast(v.getType()); + }; + auto rmNearestEven = cuda_tile::RoundingModeAttr::get( + rewriter.getContext(), cuda_tile::RoundingMode::NEAREST_EVEN); + + auto splatFloat = [&](cuda_tile::TileType tileType, double value) -> Value { + auto elementType = cast(tileType.getElementType()); + auto scalarAttr = rewriter.getFloatAttr(elementType, value); + auto denseAttr = cast( + DenseElementsAttr::get(tileType, scalarAttr)); + return cuda_tile::ConstantOp::create(rewriter, loc, tileType, denseAttr); + }; + auto splatInt = [&](cuda_tile::TileType tileType, int64_t value) -> Value { + auto elementType = cast(tileType.getElementType()); + auto scalarAttr = rewriter.getIntegerAttr(elementType, value); + auto denseAttr = cast( + DenseElementsAttr::get(tileType, scalarAttr)); + return cuda_tile::ConstantOp::create(rewriter, loc, tileType, denseAttr); + }; + + auto addf = [&](Value a, Value b) -> Value { + return cuda_tile::AddFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto subf = [&](Value a, Value b) -> Value { + return cuda_tile::SubFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto mulf = [&](Value a, Value b) -> Value { + return cuda_tile::MulFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto divf = [&](Value a, Value b) -> Value { + return cuda_tile::DivFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto floor = [&](Value a) -> Value { + return cuda_tile::FloorOp::create(rewriter, loc, a); + }; + auto sqrt = [&](Value a) -> Value { + return cuda_tile::SqrtOp::create(rewriter, loc, a, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto exp = [&](Value a) -> Value { + return cuda_tile::ExpOp::create(rewriter, loc, a); + }; + auto cmpf = [&](cuda_tile::ComparisonPredicate pred, + cuda_tile::ComparisonOrdering ord, Value a, + Value b) -> Value { + return cuda_tile::CmpFOp::create(rewriter, loc, pred, ord, a, b); + }; + // TODO: other math func support(use extern_eltwise or impl math func) + if (symbol == "__nv_acosf" || symbol == "__nv_acos") { + // acos(x) = atan2(sqrt(1 - x^2), x) + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Value one = splatFloat(xType, 1.0); + Value xx = mulf(x, x); + Value y = sqrt(subf(one, xx)); + // cuda_tile.atan2 uses the conventional atan2(y, x) argument order. + rewriter.replaceOpWithNewOp(op, y, x); + return success(); + } else if (symbol == "__nv_atanf" || symbol == "__nv_atan") { + // atan(x) = atan2(x, 1) + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Value one = splatFloat(xType, 1.0); + // cuda_tile.atan2 uses the conventional atan2(y, x) argument order. + rewriter.replaceOpWithNewOp(op, x, one); + return success(); + } else if (symbol == "__nv_asinf" || symbol == "__nv_asin") { + // asin(x) = atan2(x, sqrt(1 - x^2)) + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Value one = splatFloat(xType, 1.0); + Value xx = mulf(x, x); + Value denom = sqrt(subf(one, xx)); + // cuda_tile.atan2 uses the conventional atan2(y, x) argument order. + rewriter.replaceOpWithNewOp(op, x, denom); + return success(); + } else if (symbol == "__nv_rintf" || symbol == "__nv_rint" || + symbol == "__nv_nearbyintf" || symbol == "__nv_nearbyint") { + // Round-to-nearest-even (ties to even). + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Value f = floor(x); + Value d = subf(x, f); // d in [0, 1) for finite x + + Value half = splatFloat(xType, 0.5); + Value one = splatFloat(xType, 1.0); + Value fPlusOne = addf(f, one); + + Value gtHalf = cmpf(cuda_tile::ComparisonPredicate::GREATER_THAN, + cuda_tile::ComparisonOrdering::ORDERED, d, half); + Value ltHalf = cmpf(cuda_tile::ComparisonPredicate::LESS_THAN, + cuda_tile::ComparisonOrdering::ORDERED, d, half); + + // Even check without int conversion: f is even iff (f * 0.5) is integer. + Value halfF = mulf(f, half); + Value halfFFloor = floor(halfF); + Value fIsEven = + cmpf(cuda_tile::ComparisonPredicate::EQUAL, + cuda_tile::ComparisonOrdering::ORDERED, halfF, halfFFloor); + + Value tie = + cuda_tile::SelectOp::create(rewriter, loc, fIsEven, f, fPlusOne); + Value resLt = cuda_tile::SelectOp::create(rewriter, loc, ltHalf, f, tie); + Value res = + cuda_tile::SelectOp::create(rewriter, loc, gtHalf, fPlusOne, resLt); + rewriter.replaceOp(op, res); + return success(); + } else if (symbol == "__nv_isnanf" || symbol == "__nv_isnand") { + // isnan(x) = unordered (x != x) + Value x = adaptor.getSrcs()[0]; + auto resType = cast( + getTypeConverter()->convertType(op.getResult().getType())); + Value cond = cmpf(cuda_tile::ComparisonPredicate::NOT_EQUAL, + cuda_tile::ComparisonOrdering::UNORDERED, x, x); + Value one = splatInt(resType, 1); + Value zero = splatInt(resType, 0); + Value res = + cuda_tile::SelectOp::create(rewriter, loc, resType, cond, one, zero); + rewriter.replaceOp(op, res); + return success(); + } else if (symbol == "__nv_isinff" || symbol == "__nv_isinfd") { + // IEEE isinf classification via bitcast. + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Type floatElemType = xType.getElementType(); + auto resType = cast( + getTypeConverter()->convertType(op.getResult().getType())); + + cuda_tile::TileType bitsType; + int64_t signMask = 0; + int64_t infBits = 0; + if (floatElemType.isF32()) { + auto shape = llvm::to_vector(xType.getShape()); + bitsType = cuda_tile::TileType::get(shape, rewriter.getI32Type()); + signMask = 0x7fffffffLL; + infBits = 0x7f800000LL; + } else if (floatElemType.isF64()) { + auto shape = llvm::to_vector(xType.getShape()); + bitsType = cuda_tile::TileType::get(shape, rewriter.getI64Type()); + signMask = 0x7fffffffffffffffLL; + infBits = 0x7ff0000000000000LL; + } else { + return rewriter.notifyMatchFailure( + op, llvm::Twine("unsupported type for ") + symbol + + ": expected f32/f64 input"); + } + + Value bits = cuda_tile::BitcastOp::create(rewriter, loc, bitsType, x); + Value mask = splatInt(bitsType, signMask); + Value absBits = cuda_tile::AndIOp::create(rewriter, loc, bits, mask); + Value inf = splatInt(bitsType, infBits); + Value cond = cuda_tile::CmpIOp::create( + rewriter, loc, cuda_tile::ComparisonPredicate::EQUAL, absBits, inf, + cuda_tile::Signedness::Unsigned); + + Value one = splatInt(resType, 1); + Value zero = splatInt(resType, 0); + Value res = + cuda_tile::SelectOp::create(rewriter, loc, resType, cond, one, zero); + rewriter.replaceOp(op, res); + return success(); + } else if (symbol == "__nv_finitef" || symbol == "__nv_isfinited") { + // IEEE isfinite classification via bitcast. + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Type floatElemType = xType.getElementType(); + auto resType = cast( + getTypeConverter()->convertType(op.getResult().getType())); + + cuda_tile::TileType bitsType; + int64_t signMask = 0; + int64_t infBits = 0; + if (symbol == "__nv_finitef") { + // finitef is only defined for fp32 in libdevice. + if (!floatElemType.isF32()) + return rewriter.notifyMatchFailure(op, + "__nv_finitef expects f32 input"); + auto shape = llvm::to_vector(xType.getShape()); + bitsType = cuda_tile::TileType::get(shape, rewriter.getI32Type()); + signMask = 0x7fffffffLL; + infBits = 0x7f800000LL; + } else { + // __nv_isfinited is fp64. + if (!floatElemType.isF64()) + return rewriter.notifyMatchFailure( + op, "__nv_isfinited expects f64 input"); + auto shape = llvm::to_vector(xType.getShape()); + bitsType = cuda_tile::TileType::get(shape, rewriter.getI64Type()); + signMask = 0x7fffffffffffffffLL; + infBits = 0x7ff0000000000000LL; + } + + Value bits = cuda_tile::BitcastOp::create(rewriter, loc, bitsType, x); + Value mask = splatInt(bitsType, signMask); + Value absBits = cuda_tile::AndIOp::create(rewriter, loc, bits, mask); + Value inf = splatInt(bitsType, infBits); + Value cond = cuda_tile::CmpIOp::create( + rewriter, loc, cuda_tile::ComparisonPredicate::LESS_THAN, absBits, + inf, cuda_tile::Signedness::Unsigned); + + Value one = splatInt(resType, 1); + Value zero = splatInt(resType, 0); + Value res = + cuda_tile::SelectOp::create(rewriter, loc, resType, cond, one, zero); + rewriter.replaceOp(op, res); + return success(); + } else if (symbol == "__nv_erff" || symbol == "__nv_erf") { + // High-accuracy approximation (max error ~1.5e-7): + // https://stackoverflow.com/a/4578056 + // + // t = 1 / (1 + 0.5 * |x|) + // tau = t * exp(-x^2 - 1.26551223 + t*(1.00002368 + t*(0.37409196 + ... + // ))) erf(x) = sign(x) * (1 - tau) + Value x = adaptor.getSrcs()[0]; + auto xType = getTileType(x); + Type floatElemType = xType.getElementType(); + if (!floatElemType.isF32() && !floatElemType.isF64()) { + return rewriter.notifyMatchFailure( + op, llvm::Twine("unsupported type for ") + symbol + + ": expected f32/f64 input"); + } + + Value zero = splatFloat(xType, 0.0); + Value one = splatFloat(xType, 1.0); + Value half = splatFloat(xType, 0.5); + + Value ax = cuda_tile::AbsFOp::create(rewriter, loc, x); + Value t = divf(one, addf(one, mulf(half, ax))); + + // Horner form for the nested polynomial. + Value p = splatFloat(xType, 0.17087277); + p = addf(splatFloat(xType, -0.82215223), mulf(t, p)); + p = addf(splatFloat(xType, 1.48851587), mulf(t, p)); + p = addf(splatFloat(xType, -1.13520398), mulf(t, p)); + p = addf(splatFloat(xType, 0.27886807), mulf(t, p)); + p = addf(splatFloat(xType, -0.18628806), mulf(t, p)); + p = addf(splatFloat(xType, 0.09678418), mulf(t, p)); + p = addf(splatFloat(xType, 0.37409196), mulf(t, p)); + p = addf(splatFloat(xType, 1.00002368), mulf(t, p)); + + Value negAx2 = subf(zero, mulf(ax, ax)); + Value expArg = + addf(addf(negAx2, splatFloat(xType, -1.26551223)), mulf(t, p)); + Value tau = mulf(t, exp(expArg)); + Value erfAbs = subf(one, tau); + + Value isNeg = cmpf(cuda_tile::ComparisonPredicate::LESS_THAN, + cuda_tile::ComparisonOrdering::ORDERED, x, zero); + Value negErfAbs = subf(zero, erfAbs); + Value res = + cuda_tile::SelectOp::create(rewriter, loc, isNeg, negErfAbs, erfAbs); + rewriter.replaceOp(op, res); + return success(); + } else if (symbol == "__nv_ceil" || symbol == "__nv_ceilf") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_pow" || symbol == "__nv_powf") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0], + adaptor.getSrcs()[1]); + return success(); + } else if (symbol == "__nv_cos" || symbol == "__nv_cosf") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_sin" || symbol == "__nv_sinf") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_tan" || symbol == "__nv_tanf") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_exp" || symbol == "__nv_expf") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_fast_expf") { + rewriter.replaceOpWithNewOp( + op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_exp2" || symbol == "__nv_exp2f") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_log2f" || symbol == "__nv_log2") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_rsqrtf" || symbol == "__nv_rsqrt") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_sqrtf") { + rewriter.replaceOpWithNewOp( + op, adaptor.getSrcs()[0], cuda_tile::RoundingMode::APPROX); + return success(); + } else if (symbol == "__nv_sqrt") { + // Prefer IEEE-style rounding for fp64 sqrt. + Value x = adaptor.getSrcs()[0]; + rewriter.replaceOp(op, sqrt(x)); + return success(); + } else if (symbol == "__nv_floorf" || symbol == "__nv_floor") { + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_tanhf" || symbol == "__nv_tanh") { + rewriter.replaceOpWithNewOp( + op, adaptor.getSrcs()[0]); + return success(); + } else if (symbol == "__nv_fast_tanhf") { + rewriter.replaceOpWithNewOp( + op, adaptor.getSrcs()[0]); + return success(); + } + return rewriter.notifyMatchFailure( + op, llvm::Twine("unsupported extern_elementwise symbol for stable " + "cuda_tile lowering: ") + + symbol + + ". Please add a rewrite in ConvertExternElementwiseOp."); + } +}; + +class ConvertErfOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(math::ErfOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); + Value x = adaptor.getOperand(); + auto xType = cast(x.getType()); + Type floatElemType = xType.getElementType(); + if (!floatElemType.isF32() && !floatElemType.isF64()) { + return rewriter.notifyMatchFailure( + op, "math.erf expects f32/f64 input for TileIR lowering"); + } + + auto rmNearestEven = cuda_tile::RoundingModeAttr::get( + rewriter.getContext(), cuda_tile::RoundingMode::NEAREST_EVEN); + auto splatFloat = [&](double value) -> Value { + auto elementType = cast(xType.getElementType()); + auto scalarAttr = rewriter.getFloatAttr(elementType, value); + auto denseAttr = cast( + DenseElementsAttr::get(xType, scalarAttr)); + return cuda_tile::ConstantOp::create(rewriter, loc, xType, denseAttr); + }; + auto addf = [&](Value a, Value b) -> Value { + return cuda_tile::AddFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto subf = [&](Value a, Value b) -> Value { + return cuda_tile::SubFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto mulf = [&](Value a, Value b) -> Value { + return cuda_tile::MulFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + auto divf = [&](Value a, Value b) -> Value { + return cuda_tile::DivFOp::create(rewriter, loc, a, b, rmNearestEven, + /*flush_to_zero=*/nullptr); + }; + + Value zero = splatFloat(0.0); + Value one = splatFloat(1.0); + Value half = splatFloat(0.5); + + Value ax = cuda_tile::AbsFOp::create(rewriter, loc, x); + Value t = divf(one, addf(one, mulf(half, ax))); + + Value p = splatFloat(0.17087277); + p = addf(splatFloat(-0.82215223), mulf(t, p)); + p = addf(splatFloat(1.48851587), mulf(t, p)); + p = addf(splatFloat(-1.13520398), mulf(t, p)); + p = addf(splatFloat(0.27886807), mulf(t, p)); + p = addf(splatFloat(-0.18628806), mulf(t, p)); + p = addf(splatFloat(0.09678418), mulf(t, p)); + p = addf(splatFloat(0.37409196), mulf(t, p)); + p = addf(splatFloat(1.00002368), mulf(t, p)); + + Value negAx2 = subf(zero, mulf(ax, ax)); + Value expArg = + addf(addf(negAx2, splatFloat(-1.26551223)), mulf(t, p)); + Value tau = mulf(t, cuda_tile::ExpOp::create(rewriter, loc, expArg)); + Value erfAbs = subf(one, tau); + + Value isNeg = cuda_tile::CmpFOp::create( + rewriter, loc, cuda_tile::ComparisonPredicate::LESS_THAN, + cuda_tile::ComparisonOrdering::ORDERED, x, zero); + Value negErfAbs = subf(zero, erfAbs); + Value res = + cuda_tile::SelectOp::create(rewriter, loc, isNeg, negErfAbs, erfAbs); + rewriter.replaceOp(op, res); + return success(); + } +}; + +class ConvertCatOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::CatOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = cast( + getTypeConverter()->convertType(op.getResult().getType())); + + // This should always be true since SameTypeOperands trait is enforced for + // triton::CatOp + auto lhsTy = adaptor.getLhs().getType(); + auto rhsTy = adaptor.getRhs().getType(); + assert(lhsTy == rhsTy && "Operands must have identical types"); + + // Add singleton dimension to operand type to match result rank + auto reshapeToMatchResultRank = [&](Value operand) -> Value { + auto operandTy = cast(operand.getType()); + if (operandTy.getRank() == resTy.getRank()) + return operand; + auto operandShape = llvm::to_vector(operandTy.getShape()); + operandShape.resize(resTy.getRank(), 1); + auto newTy = + operandTy.cloneWith(operandShape, operandTy.getElementType()); + return cuda_tile::ReshapeOp::create(rewriter, op.getLoc(), newTy, + operand); + }; + + Value lhs = reshapeToMatchResultRank(adaptor.getLhs()); + Value rhs = reshapeToMatchResultRank(adaptor.getRhs()); + + // Determine concatenation axis (last dimension by default) + int64_t concatDim = resTy.getRank() - 1; + auto lhsShape = cast(lhs.getType()).getShape(); + for (int64_t i = 0; i < resTy.getRank(); i++) + if (lhsShape[i] != resTy.getShape()[i]) { + concatDim = i; + break; + } + + rewriter.replaceOpWithNewOp( + op, resTy, lhs, rhs, rewriter.getI64IntegerAttr(concatDim)); + + return success(); + } +}; + +// Join the tensors in a new minor dimension. +class ConvertJoinOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::JoinOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); + + // Step1. Create a new minor dimenion using reshape. + cuda_tile::TileType lhsType = + cast(adaptor.getLhs().getType()); + cuda_tile::TileType rhsType = + cast(adaptor.getRhs().getType()); + assert(lhsType == rhsType && + "expect triton cat to have same operand type for lhs and rhs"); + + SmallVector reshapedView = llvm::to_vector(lhsType.getShape()); + int64_t concatDim = reshapedView.size(); + reshapedView.push_back(1); + cuda_tile::TileType newLhsType = + cuda_tile::TileType::get(reshapedView, lhsType.getElementType()); + auto viewOnLhs = cuda_tile::ReshapeOp::create(rewriter, loc, newLhsType, + adaptor.getLhs()); + auto viewOnRhs = cuda_tile::ReshapeOp::create(rewriter, loc, newLhsType, + adaptor.getRhs()); + reshapedView[concatDim] += reshapedView[concatDim]; + cuda_tile::TileType newResType = + cuda_tile::TileType::get(reshapedView, lhsType.getElementType()); + + // Step2. Concat along the new minor dimension. + rewriter.replaceOpWithNewOp( + op, newResType, viewOnLhs, viewOnRhs, + rewriter.getI64IntegerAttr(concatDim)); + return success(); + } +}; + +class ConvertGetProgramIdOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::GetProgramIdOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + int32_t axis = op.getAxisAsInt(); + Operation *newOp = + cuda_tile::GetTileBlockIdOp::create(rewriter, op.getLoc()); + rewriter.replaceOp(op, newOp->getResult(axis)); + return success(); + } +}; + +// Helper function for common functionality between ReduceOp and ScanOp +LogicalResult convertAggregationOp(Operation *op, + ConversionPatternRewriter &rewriter, + const TypeConverter *typeConverter, + Operation *newOp) { + SmallVector newResultTypes; + auto resConversion = + typeConverter->convertTypes(op->getResults().getType(), newResultTypes); + assert(succeeded(resConversion) && "failed to convert tensor type"); + + Block *oldBody = &op->getRegion(0).front(); + + Block *body = new Block(); + SmallVector currentOperandsTy; + SmallVector locs; + // We use pair for better readability: + // [current_operand[i], prev_operand[i], current_operand[i + 1], + // prev_operand[i + 1]] while triton is: current_operand[i], + // current_operand[i + 1], prev_operand[i], prev_operand[i + 1]] + size_t numberOfArgs = oldBody->getNumArguments(); + size_t half = numberOfArgs / 2; + + for (int i = 0, e = numberOfArgs / 2; i < e; i++) { + BlockArgument currentBlkOperand = oldBody->getArgument(i); + locs.push_back(currentBlkOperand.getLoc()); + BlockArgument preBlkOperand = oldBody->getArgument(i + half); + locs.push_back(preBlkOperand.getLoc()); + currentOperandsTy.push_back( + cuda_tile::TileType::get({}, currentBlkOperand.getType())); + currentOperandsTy.push_back( + cuda_tile::TileType::get({}, preBlkOperand.getType())); + } + body->addArguments(currentOperandsTy, locs); + + SmallVector blockOperands; + for (int i = 0; i < numberOfArgs / 2; i++) + blockOperands.push_back(body->getArgument(i * 2)); + + for (int i = 0; i < numberOfArgs / 2; i++) + blockOperands.push_back(body->getArgument(i * 2 + 1)); + + rewriter.inlineBlockBefore(&op->getRegion(0).front(), body, body->end(), + blockOperands); + newOp->getRegion(0).push_back(body); + rewriter.replaceOp(op, newOp->getResults()); + return success(); +} + +class ConvertReduceOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ReduceOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector newResultTypes; + auto resConversion = getTypeConverter()->convertTypes( + op.getResults().getType(), newResultTypes); + assert(succeeded(resConversion) && "failed to convert tensor type"); + Block *oldBody = &op.getRegion().front(); + if (llvm::hasSingleElement(*oldBody)) { + // Fast path: This reduction is a no-op. It contains just the terminator. + auto terminator = cast(oldBody->getTerminator()); + SmallVector repls; + for (auto it : llvm::enumerate(terminator.getResult())) { + // The returned value must be one of the bbargs. Find out which one, + // then replace the reduction op result with the respective operand. + Value v = it.value(); + auto bbArg = cast(v); + unsigned operandIdx = bbArg.getArgNumber() / 2; + repls.push_back(cuda_tile::ReshapeOp::create( + rewriter, op.getLoc(), newResultTypes[it.index()], + adaptor.getSrcs()[operandIdx])); + } + rewriter.replaceOp(op, repls); + return success(); + } + + auto identitiesOrFailure = + getIdentitiesFromCombineOp(op.getCombineOp(), newResultTypes, rewriter); + if (failed(identitiesOrFailure)) + return rewriter.notifyMatchFailure( + op, "failed to setup valid identities for combineOp"); + ArrayAttr identities = *identitiesOrFailure; + + auto newReduceOp = cuda_tile::ReduceOp::create( + rewriter, op.getLoc(), newResultTypes, adaptor.getOperands(), + adaptor.getAxis(), identities); + + return convertAggregationOp(op, rewriter, getTypeConverter(), newReduceOp); + } +}; + +class ConvertScanOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ScanOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector newResultTypes; + auto resConversion = getTypeConverter()->convertTypes( + op.getResults().getType(), newResultTypes); + assert(succeeded(resConversion) && "failed to convert tensor type"); + + auto identitiesOrFailure = + getIdentitiesFromCombineOp(op.getCombineOp(), newResultTypes, rewriter); + if (failed(identitiesOrFailure)) + return rewriter.notifyMatchFailure( + op, "failed to setup valid identities for combineOp"); + ArrayAttr identities = *identitiesOrFailure; + + auto newScanOp = cuda_tile::ScanOp::create( + rewriter, op.getLoc(), newResultTypes, adaptor.getOperands(), + adaptor.getAxis(), adaptor.getReverse(), identities); + + return convertAggregationOp(op, rewriter, getTypeConverter(), newScanOp); + } +}; + +class ConvertScanReturnOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ScanReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto parentOp = op->getParentOp(); + if (parentOp && isa(parentOp)) { + rewriter.replaceOpWithNewOp(op, + adaptor.getOperands()); + return success(); + } + return failure(); + } +}; + +class ConvertGetNumProgramsOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::GetNumProgramsOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + int32_t axis = op.getAxisAsInt(); + Operation *newOp = + cuda_tile::GetNumTileBlocksOp::create(rewriter, op.getLoc()); + rewriter.replaceOp(op, newOp->getResult(axis)); + return success(); + } +}; + +class ConvertReduceReturnOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ReduceReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto parentOp = op->getParentOp(); + if (parentOp && isa(parentOp)) { + rewriter.replaceOpWithNewOp(op, + adaptor.getOperands()); + return success(); + } + return failure(); + } +}; + +class ConvertIfOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(scf::IfOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector resultTypes; + if (failed(typeConverter->convertTypes(op.getResultTypes(), resultTypes))) + return failure(); + auto newIfOp = cuda_tile::IfOp::create(rewriter, op.getLoc(), resultTypes, + adaptor.getCondition()); + rewriter.inlineRegionBefore(op.getThenRegion(), newIfOp.getThenRegion(), + newIfOp.getThenRegion().begin()); + rewriter.inlineRegionBefore(op.getElseRegion(), newIfOp.getElseRegion(), + newIfOp.getElseRegion().begin()); + rewriter.replaceOp(op, newIfOp.getResults()); + return success(); + } +}; + +// clang-format off +// We will rewrite scf.while op into cuda_tile.loop op + +// for example: +// --------------------------------------------------------- +// scf.while +// %results = scf.while () : type() -> type(results) { // type() != type(results) +// ... // `before` region code +// %cond = ... +// = ... +// scf.condition (%cond) : type(condition_args) // type(condition_args) == type(results) +// } do { +// ^bb0(after_args): // `after_args` come from `condition_args` and type(condition_args) == type(after_args) +// ... // `after` region code +// scf.yield : type(yield_vals) // type(yield_vals) == type(while_args) +// } +// --------------------------------------------------------- + +// will be rewritten into: + +// --------------------------------------------------------- +// %results = cuda_tile.loop iter_values() -> type(results) { // type() != type(results) +// ... // `before` region code +// %cond = ... +// = ... +// cuda_tile.if %cond { +// ... // `after` region code +// cuda_tile.continue +// } +// cuda_tile.break +// } +// --------------------------------------------------------- + +// clang-format on +class ConvertWhileOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(scf::WhileOp whileOp, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Block *beforeBlock = &adaptor.getBefore().front(); + Block *afterBlock = &adaptor.getAfter().front(); + + SmallVector resultTypes; + if (failed(typeConverter->convertTypes(whileOp->getResultTypes(), + resultTypes))) + return failure(); + + SmallVector inputTypes; + for (auto type : whileOp.getOperandTypes()) { + Type convertedType = typeConverter->convertType(type); + inputTypes.push_back(convertedType); + } + + rewriter.setInsertionPoint(whileOp); + + SmallVector locs(inputTypes.size(), whileOp.getLoc()); + auto newLoopOp = cuda_tile::LoopOp::create( + rewriter, whileOp.getLoc(), resultTypes, adaptor.getOperands()); + + Block *newLoopBlock = rewriter.createBlock( + &newLoopOp.getRegion(), /*insertPt=*/{}, inputTypes, locs); + + rewriter.inlineBlockBefore(beforeBlock, newLoopBlock, newLoopBlock->end(), + newLoopBlock->getArguments()); + auto conditionOp = &newLoopBlock->back(); + auto castI1Op = rewriter.getRemappedValue(conditionOp->getOperand(0)); + + auto ifOp = cuda_tile::IfOp::create(rewriter, whileOp.getLoc(), TypeRange(), + castI1Op); + rewriter.createBlock(&ifOp.getThenRegion()); + rewriter.createBlock(&ifOp.getElseRegion()); + + rewriter.setInsertionPointToStart(ifOp.getThenBlock()); + + SmallVector afterBlockArgs; + for (size_t i = 1; i < conditionOp->getOperands().size(); i++) { + afterBlockArgs.push_back(conditionOp->getOperands()[i]); + } + + rewriter.inlineBlockBefore(afterBlock, ifOp.getThenBlock(), + ifOp.getThenBlock()->end(), afterBlockArgs); + auto yieldOp = &ifOp.getThenBlock()->back(); + SmallVector yieldOpCastArgs; + if (failed(rewriter.getRemappedValues(yieldOp->getOperands(), + yieldOpCastArgs))) + return failure(); + + cuda_tile::ContinueOp::create(rewriter, whileOp.getLoc(), yieldOpCastArgs); + + SmallVector afterBlockCastArgs; + if (failed(rewriter.getRemappedValues(afterBlockArgs, afterBlockCastArgs))) + return failure(); + + rewriter.setInsertionPointToEnd(ifOp.getElseBlock()); + cuda_tile::BreakOp::create(rewriter, whileOp.getLoc(), afterBlockCastArgs); + + rewriter.setInsertionPointToEnd(newLoopBlock); + cuda_tile::BreakOp::create(rewriter, whileOp.getLoc(), afterBlockCastArgs); + + rewriter.eraseOp(conditionOp); + rewriter.eraseOp(yieldOp); + rewriter.replaceOp(whileOp, newLoopOp.getResults()); + return success(); + } +}; + +class ConvertForOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(scf::ForOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); + BlockArgument indVar = op.getBody()->getArgument(0); + if (isa(indVar.getType())) { + return rewriter.notifyMatchFailure( + loc, "index type is not supported in cuda tile"); + } + Block *origBody = op.getBody(); + + auto newForOp = cuda_tile::ForOp::create( + rewriter, loc, adaptor.getLowerBound(), adaptor.getUpperBound(), + adaptor.getStep(), adaptor.getInitArgs(), + [&](OpBuilder builder, Location loc, Value indVar, + ValueRange initArgs) { + // Don't build the body here, we'll inline it right after. + }); + + // Apply a signature conversion on the for loop body. + cuda_tile::TileType indVarTy = + cuda_tile::TileType::get({}, indVar.getType()); + TypeConverter::SignatureConversion sigConversion( + origBody->getNumArguments()); + sigConversion.addInputs(0, indVarTy); + if (failed(typeConverter->convertSignatureArgs( + llvm::drop_begin(TypeRange(origBody->getArgumentTypes())), + sigConversion, /*origInputOffset=*/1))) + return failure(); + Block *body = rewriter.applySignatureConversion(origBody, sigConversion); + + rewriter.inlineRegionBefore(op.getRegion(), newForOp.getRegion(), + newForOp.getRegion().begin()); + rewriter.replaceOp(op, newForOp.getResults()); + return success(); + } +}; + +struct ConvertCmpIOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::CmpIOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Get the arith comparison predicate + auto arithPredicate = op.getPredicate(); + + // Infer signedness and comparison predicate from arith predicate + cuda_tile::Signedness signedness = cuda_tile::Signedness::Signed; + cuda_tile::ComparisonPredicate comparisonPredicate; + + switch (arithPredicate) { + case arith::CmpIPredicate::eq: + comparisonPredicate = cuda_tile::ComparisonPredicate::EQUAL; + break; + case arith::CmpIPredicate::ne: + comparisonPredicate = cuda_tile::ComparisonPredicate::NOT_EQUAL; + break; + case arith::CmpIPredicate::slt: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN; + break; + case arith::CmpIPredicate::sle: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN_OR_EQUAL; + break; + case arith::CmpIPredicate::sgt: + comparisonPredicate = cuda_tile::ComparisonPredicate::GREATER_THAN; + break; + case arith::CmpIPredicate::sge: + comparisonPredicate = + cuda_tile::ComparisonPredicate::GREATER_THAN_OR_EQUAL; + break; + case arith::CmpIPredicate::ult: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN; + signedness = cuda_tile::Signedness::Unsigned; + break; + case arith::CmpIPredicate::ule: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN_OR_EQUAL; + signedness = cuda_tile::Signedness::Unsigned; + break; + case arith::CmpIPredicate::ugt: + comparisonPredicate = cuda_tile::ComparisonPredicate::GREATER_THAN; + signedness = cuda_tile::Signedness::Unsigned; + break; + case arith::CmpIPredicate::uge: + comparisonPredicate = + cuda_tile::ComparisonPredicate::GREATER_THAN_OR_EQUAL; + signedness = cuda_tile::Signedness::Unsigned; + break; + default: + return rewriter.notifyMatchFailure( + op, "unsupported arith::CmpIOp predicate" + + stringifyCmpIPredicate(arithPredicate)); + } + + // Upcast to i16 if necessary. + Location loc = op.getLoc(); + Value lhs = upCastOrSelf(rewriter, loc, adaptor.getLhs(), + signedness == cuda_tile::Signedness::Unsigned + ? Signedness::Unsigned + : Signedness::Signed, + IntegerUpCast::To_I16); + Value rhs = upCastOrSelf(rewriter, loc, adaptor.getRhs(), + signedness == cuda_tile::Signedness::Unsigned + ? Signedness::Unsigned + : Signedness::Signed, + IntegerUpCast::To_I16); + + // Replace the op with cuda_tile.cmpi. + rewriter.replaceOpWithNewOp(op, comparisonPredicate, lhs, + rhs, signedness); + return success(); + } +}; + +struct ConvertCmpFOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::CmpFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Get the arith comparison predicate + auto arithPredicate = op.getPredicate(); + + // Infer comparison predicate and ordering from arith predicate + cuda_tile::ComparisonPredicate comparisonPredicate; + cuda_tile::ComparisonOrdering comparisonOrdering = + cuda_tile::ComparisonOrdering::ORDERED; + + switch (arithPredicate) { + case arith::CmpFPredicate::OEQ: + comparisonPredicate = cuda_tile::ComparisonPredicate::EQUAL; + break; + case arith::CmpFPredicate::ONE: + comparisonPredicate = cuda_tile::ComparisonPredicate::NOT_EQUAL; + break; + case arith::CmpFPredicate::OLT: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN; + break; + case arith::CmpFPredicate::OLE: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN_OR_EQUAL; + break; + case arith::CmpFPredicate::OGT: + comparisonPredicate = cuda_tile::ComparisonPredicate::GREATER_THAN; + break; + case arith::CmpFPredicate::OGE: + comparisonPredicate = + cuda_tile::ComparisonPredicate::GREATER_THAN_OR_EQUAL; + break; + case arith::CmpFPredicate::UEQ: + comparisonPredicate = cuda_tile::ComparisonPredicate::EQUAL; + comparisonOrdering = cuda_tile::ComparisonOrdering::UNORDERED; + break; + case arith::CmpFPredicate::UNE: + comparisonPredicate = cuda_tile::ComparisonPredicate::NOT_EQUAL; + comparisonOrdering = cuda_tile::ComparisonOrdering::UNORDERED; + break; + case arith::CmpFPredicate::ULT: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN; + comparisonOrdering = cuda_tile::ComparisonOrdering::UNORDERED; + break; + case arith::CmpFPredicate::ULE: + comparisonPredicate = cuda_tile::ComparisonPredicate::LESS_THAN_OR_EQUAL; + comparisonOrdering = cuda_tile::ComparisonOrdering::UNORDERED; + break; + case arith::CmpFPredicate::UGT: + comparisonPredicate = cuda_tile::ComparisonPredicate::GREATER_THAN; + comparisonOrdering = cuda_tile::ComparisonOrdering::UNORDERED; + break; + case arith::CmpFPredicate::UGE: + comparisonPredicate = + cuda_tile::ComparisonPredicate::GREATER_THAN_OR_EQUAL; + comparisonOrdering = cuda_tile::ComparisonOrdering::UNORDERED; + break; + default: + return rewriter.notifyMatchFailure( + op, "unsupported arith::CmpFOp predicate" + + stringifyCmpFPredicate(arithPredicate)); + } + + // Replace the op with cuda_tile.cmpf. + rewriter.replaceOpWithNewOp( + op, comparisonPredicate, comparisonOrdering, adaptor.getLhs(), + adaptor.getRhs()); + return success(); + } +}; + +class ConvertYieldOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(scf::YieldOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto parentOp = op->getParentOp(); + // Only ForOp is currently supported as parent operation. + if (parentOp && isa(parentOp)) { + rewriter.replaceOpWithNewOp(op, + adaptor.getOperands()); + return success(); + } + if (parentOp && isa(parentOp)) { + rewriter.replaceOpWithNewOp(op, + adaptor.getOperands()); + return success(); + } + rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + return success(); + } +}; + +/// Simple pattern to convert a tt.splat ty -> tensor by first +/// reshaping and then broadcasting. +class ConvertSplatOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::SplatOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type newResultType = + getTypeConverter()->convertType(op.getResult().getType()); + assert(newResultType && "unable to convert type"); + cuda_tile::TileType newResultTensorType = + cast(newResultType); + + SmallVector rank1Shape(newResultTensorType.getRank(), 1); + cuda_tile::TileType rank1Ty = cuda_tile::TileType::get( + rank1Shape, newResultTensorType.getElementType()); + auto replaceOp = cuda_tile::ReshapeOp::create(rewriter, op.getLoc(), + rank1Ty, adaptor.getSrc()); + rewriter.replaceOpWithNewOp(op, newResultTensorType, + replaceOp); + return success(); + } +}; + +class ConvertUnsplatOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::UnsplatOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type reTileTy = getTypeConverter()->convertType(op.getResult().getType()); + auto reshapeOp = cuda_tile::ReshapeOp::create(rewriter, op.getLoc(), + reTileTy, adaptor.getSrc()); + rewriter.replaceOp(op, reshapeOp.getResult()); + return success(); + } +}; + +class ConvertMaximumFOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::MaximumFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = getTypeConverter()->convertType(op.getResult().getType()); + assert(resTy && "expect valid type"); + rewriter.replaceOpWithNewOp( + op, resTy, adaptor.getLhs(), adaptor.getRhs(), + /*nan=*/rewriter.getUnitAttr(), + /*flush_to_zero=*/nullptr); + return success(); + } +}; + +class ConvertMinimumFOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::MinimumFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = getTypeConverter()->convertType(op.getResult().getType()); + assert(resTy && "expect valid type"); + rewriter.replaceOpWithNewOp( + op, resTy, adaptor.getLhs(), adaptor.getRhs(), + /*nan_modifier=*/rewriter.getUnitAttr(), + /*flush_to_zero_modifier=*/nullptr); + return success(); + } +}; + +class ConvertMakeRangeOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::MakeRangeOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + + int64_t start = op.getStart(); + auto converter = this->getTypeConverter(); + Type retTy = converter->convertType(op.getResult().getType()); + if (!retTy) + return rewriter.notifyMatchFailure( + op.getLoc(), "typeConversion of make_range op failed"); + + Location loc = op.getLoc(); + auto iotaOp = cuda_tile::IotaOp::create(rewriter, loc, retTy); + if (start == 0) { + rewriter.replaceOp(op, iotaOp); + return success(); + } + ShapedType retTyAsShape = cast(retTy); + IntegerAttr attr = + rewriter.getIntegerAttr(retTyAsShape.getElementType(), start); + DenseIntOrFPElementsAttr denseAttr = cast( + DenseElementsAttr::get(retTyAsShape, attr)); + cuda_tile::ConstantOp cstOp = + cuda_tile::ConstantOp::create(rewriter, loc, retTy, denseAttr); + cuda_tile::AddIOp offsetOp = + cuda_tile::AddIOp::create(rewriter, loc, iotaOp, cstOp); + rewriter.replaceOp(op, offsetOp); + return success(); + } +}; + +Value wrapIntoScalarTile(OpBuilder &rewriter, Value v, unsigned attachAlignment, + int64_t attachRange) { + auto ctx = v.getType().getContext(); + auto loc = v.getLoc(); + if (v.getType().isInteger(32)) + v = arith::ExtSIOp::create(rewriter, loc, rewriter.getI64Type(), v) + .getResult(); + auto elemType = v.getType(); + auto scalarTileTy = cuda_tile::TileType::get(ctx, /*shape=*/{}, elemType); + auto scalarTile = + UnrealizedConversionCastOp::create(rewriter, loc, scalarTileTy, v) + .getResult(0); + scalarTile = cuda_tile::AssumeOp::create( + rewriter, loc, scalarTile, + cuda_tile::BoundedAttr::get(ctx, 0, attachRange)) + .getResult(); + + // we can always assume the stride are divisible by 16 + // because openai has already make it into host tma api. + if (!attachAlignment) + return scalarTile; + return cuda_tile::AssumeOp::create( + rewriter, loc, scalarTile, + cuda_tile::DivByAttr::get(ctx, attachAlignment, std::nullopt, + std::nullopt)) + .getResult(); +}; + +/// Lowering of tt.make_tensor_desc to cuda_tile.make_tensor_view. +/// +/// Triton currently assumes that the pointer, sizes and strides are +/// compatible with the TMA requirements of the target architecture. +/// See commit message: https://github.com/triton-lang/triton/pull/6753 +/// "This does not implement: Interop for unsupported tensor descriptors on +/// devices which support tensor descriptors." +/// +/// This means that we can safely assume that the pointer and strides are +/// divisible by 16. (Sizes do not have this divisibility requirement.) +/// Using a pointer or strides that are not divisible by 16 will result in +/// undefined behavior. +/// This lowering attaches the divisibility hints to the pointer and strides. +/// +/// It is safe to assume that shapes are in the range [0, 2^32 - 1] and +/// strides are in the range [0, 2^40 - 1] +/// This lowering attaches the range hints to the shapes and strides. +class ConvertMakeTensorDescOp + : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + ConvertMakeTensorDescOp(MLIRContext *context) + : OpConversionPattern(context) {} + + LogicalResult + matchAndRewrite(triton::MakeTensorDescOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = op.getLoc(); + auto ctx = op->getContext(); + auto ptrTy = op.getBase().getType(); + + size_t rank = op.getShape().size(); + if (rank == 0) { + return rewriter.notifyMatchFailure( + loc, "Device TMA descriptor with rank 0 is not supported."); + } + + SmallVector globalShape(rank, cuda_tile::TensorViewType::kDynamic); + SmallVector globalStride(rank, + cuda_tile::TensorViewType::kDynamic); + // we can always assume the stride is 1 + // because openai has assume this. + globalStride[rank - 1] = 1; + + auto elemTy = ptrTy.getPointeeType(); + unsigned elem_bytes = elemTy.getIntOrFloatBitWidth() / 8; + unsigned align_byte = kTMAAlignment / elem_bytes; + + auto getConstInt = [&](Value stride) -> std::optional { + if (auto constOp = + dyn_cast_or_null(stride.getDefiningOp())) { + if (auto intAttr = dyn_cast(constOp.getValue())) + return intAttr.getInt(); + } + return std::nullopt; + }; + + SmallVector wrappedDynShapes; + // It is safe to assume that shapes are in the range [0, 2^32 - 1] which is + // the TMA hardware limit for shape. This ensures that if users explicitly + // want to use TMA for this operation, the shape parameters will satisfy TMA + // descriptor encoding requirements. + for (auto v : op.getShape()) + wrappedDynShapes.push_back( + wrapIntoScalarTile(rewriter, v, /*attachAlignment=*/0, kMaxShape)); + // Strides are required to be divisible by 16. + SmallVector wrappedDynStrides; + auto strides = op.getStrides(); + for (size_t i = 0; i < rank; ++i) { + auto stride = strides[i]; + auto constValOpt = getConstInt(stride); + + if (i == rank - 1) { + // Last stride must be 1 + if (!constValOpt.has_value() || constValOpt.value() != 1) + return rewriter.notifyMatchFailure( + loc, "the last stride is expected to be constexpr 1"); + } else { + // Other strides should be divisible by 16-bytes + if (constValOpt.has_value() && + (constValOpt.value() % align_byte != 0)) { + op.emitWarning("the stride is expected to be divisible by 16-bytes, " + "may result in error"); + // It is safe to assume that shapes are in the range [0, 2^40 - 1] + // which is the TMA hardware limit for stride. This ensures that if + // users explicitly want to use TMA for this operation, the stride + // parameters will satisfy TMA descriptor encoding requirements. + wrappedDynStrides.push_back(wrapIntoScalarTile( + rewriter, stride, /*attachAlignment=*/0, kMaxStride)); + } else + wrappedDynStrides.push_back(wrapIntoScalarTile( + rewriter, stride, /*attachAlignment=*/align_byte, kMaxStride)); + } + } + + auto tensorViewTy = + cuda_tile::TensorViewType::get(ctx, elemTy, globalShape, globalStride); + + auto tileIRPtrType = cuda_tile::PointerType::get(elemTy); + auto ptrTypeWrapper = cuda_tile::TileType::get(ctx, {}, tileIRPtrType); + auto ptrOp = UnrealizedConversionCastOp::create( + rewriter, loc, ptrTypeWrapper, op.getBase()) + .getResult(0); + // Pointer is required to be divisible by 16. + auto ptrWithDivBy = cuda_tile::AssumeOp::create( + rewriter, loc, ptrOp, + cuda_tile::DivByAttr::get( + ctx, kTMAAlignment, std::nullopt, std::nullopt)) + .getResult(); + + auto makeTensorViewOp = cuda_tile::MakeTensorViewOp::create( + rewriter, loc, tensorViewTy, ptrWithDivBy, wrappedDynShapes, + wrappedDynStrides); + + SmallVector dimMap(rank); + std::iota(dimMap.begin(), dimMap.end(), 0); + + auto tileShape = op.getTensorShape(); + SmallVector arrayOfi32Shape; + for (auto i64Shape : tileShape) { + arrayOfi32Shape.push_back(i64Shape); + } + + auto tilePartViewTy = cuda_tile::PartitionViewType::get( + ctx, rewriter.getDenseI32ArrayAttr(arrayOfi32Shape), tensorViewTy, + dimMap, + cuda_tile::PaddingValueAttr::get(ctx, cuda_tile::PaddingValue::zero)); + + auto partViewOp = cuda_tile::MakePartitionViewOp::create( + rewriter, loc, tilePartViewTy, makeTensorViewOp); + + rewriter.replaceOp(op, partViewOp); + return success(); + } +}; + +class ConvertMaxNumFOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::MaxNumFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = getTypeConverter()->convertType(op.getResult().getType()); + assert(resTy && "expect valid type"); + rewriter.replaceOpWithNewOp( + op, resTy, adaptor.getLhs(), adaptor.getRhs(), + /*nan_modifier=*/nullptr, + /*flush_to_zero_modifier=*/nullptr); + return success(); + } +}; + +class ConvertMinNumFOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(arith::MinNumFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = getTypeConverter()->convertType(op.getResult().getType()); + assert(resTy && "expect valid type"); + rewriter.replaceOpWithNewOp( + op, resTy, adaptor.getLhs(), adaptor.getRhs(), + /*nan_modifier=*/nullptr, + /*flush_to_zero_modifier=*/nullptr); + return success(); + } +}; + +class ConvertDotOp : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(triton::DotOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto converter = this->getTypeConverter(); + SmallVector retTypes; + if (failed(converter->convertTypes(op->getResultTypes(), retTypes))) + return failure(); + + // Aux functions + auto isF32 = [](Value operand) { + return cast(operand.getType()).getElementType().isF32(); + }; + auto zeroLike = [&](Value value) -> Value { + auto shapedType = cast(value.getType()); + Type elemType = shapedType.getElementType(); + + TypedAttr zeroAttr; + if (auto floatTy = dyn_cast(elemType)) { + zeroAttr = rewriter.getFloatAttr(floatTy, 0.0); + } else { + llvm_unreachable("unsupported element type for zeroLike"); + } + + auto c0Attr = DenseElementsAttr::get(shapedType, zeroAttr); + return cuda_tile::ConstantOp::create( + rewriter, op.getLoc(), value.getType(), + cast(c0Attr)); + }; + auto convertFloat = [&](Value value, FloatType dstFloatTy) -> Value { + auto srcTy = dyn_cast(value.getType()); + auto dstTy = cuda_tile::TileType::get(srcTy.getShape(), dstFloatTy); + return cuda_tile::FToFOp::create(rewriter, op.getLoc(), dstTy, value); + }; + auto sub = [&](Value a, Value b) -> Value { + return cuda_tile::SubFOp::create( + rewriter, op.getLoc(), a, b, + cuda_tile::RoundingModeAttr::get( + rewriter.getContext(), cuda_tile::RoundingMode::NEAREST_EVEN), + /*FlushToZeroModifier=*/nullptr); + }; + auto add = [&](Value a, Value b) -> Value { + return cuda_tile::AddFOp::create( + rewriter, op.getLoc(), a, b, + cuda_tile::RoundingModeAttr::get( + rewriter.getContext(), cuda_tile::RoundingMode::NEAREST_EVEN), + /*FlushToZeroModifier=*/nullptr); + }; + auto splitF32 = [&](Value input, unsigned N, + FloatType dstFloatTy) -> llvm::SmallVector { + llvm::SmallVector splitInputs; + + FloatType f32Ty = rewriter.getF32Type(); + for (unsigned i = 0; i < N; ++i) { + Value dstValue = convertFloat(input, dstFloatTy); + if (i != N - 1) { + Value dstF32Value = convertFloat(dstValue, f32Ty); + input = sub(input, dstF32Value); + } + splitInputs.push_back(dstValue); + } + return splitInputs; + }; + auto mma = [&](Value a, Value b, Value c) -> Value { + return cuda_tile::MmaFOp::create(rewriter, op->getLoc(), a, b, c); + }; + auto replaceNansWithZeros = [&](Value value) -> Value { + // triton use arith::CmpFPredicate::UNO here, we use an ordered equal to + // replace it + auto nans = cuda_tile::CmpFOp::create( + rewriter, op.getLoc(), cuda_tile::ComparisonPredicate::EQUAL, + cuda_tile::ComparisonOrdering::ORDERED, value, value); + auto zero = zeroLike(value); + return cuda_tile::SelectOp::create(rewriter, op.getLoc(), nans, value, + zero); + }; + + // Non-IEEE mode, mixed precision + if (isF32(op.getA()) && isF32(op.getB()) && + op.getInputPrecision() != InputPrecision::IEEE) { + FloatType computeTy; // mma compute type + unsigned nSplits = 0; // number of splits for lhs and rhs + switch (op.getInputPrecision()) { + case InputPrecision::TF32: { + computeTy = rewriter.getTF32Type(); + nSplits = 1; + break; + } + case InputPrecision::TF32x3: { + computeTy = rewriter.getTF32Type(); + nSplits = 2; + break; + } + case InputPrecision::BF16x3: { + computeTy = rewriter.getBF16Type(); + nSplits = 2; + break; + } + case InputPrecision::BF16x6: { + computeTy = rewriter.getBF16Type(); + nSplits = 3; + break; + } + default: + return rewriter.notifyMatchFailure(op, "unsupported input precision"); + } + + const auto lhs_parts = splitF32(adaptor.getA(), nSplits, computeTy); + const auto rhs_parts = splitF32(adaptor.getB(), nSplits, computeTy); + const unsigned hi = 0, mid = 1, lo = 2; + + if (nSplits == 1) { + // for TF32 mode, only one mma is needed + auto result = mma(lhs_parts[hi], rhs_parts[hi], adaptor.getC()); + rewriter.replaceOp(op, result); + return success(); + } else { + // for other mixed precision modes, multiple mmas are needed + auto result = zeroLike(adaptor.getC()); + if (nSplits > 2) { + result = mma(lhs_parts[mid], rhs_parts[mid], result); + result = mma(lhs_parts[lo], rhs_parts[hi], result); + result = mma(lhs_parts[hi], rhs_parts[lo], result); + } + if (nSplits > 1) { + result = mma(lhs_parts[mid], rhs_parts[hi], result); + result = mma(lhs_parts[hi], rhs_parts[mid], result); + result = replaceNansWithZeros(result); + } + result = mma(lhs_parts[hi], rhs_parts[hi], result); + result = add(result, adaptor.getC()); + rewriter.replaceOp(op, result); + return success(); + } + } + + // IEEE mode, directly lower to mma + auto opElType = + cast(adaptor.getA().getType()).getElementType(); + if (opElType.isInteger(8)) { + // To lower IMMA, we must distinguish between signed and unsigned at the + // operation level. Triton IR is signless, and there are no attributes for + // us to recover this information. Hence, here, for integer type, we + // default to signed. + rewriter.replaceOpWithNewOp( + op, adaptor.getA(), adaptor.getB(), adaptor.getC(), + cuda_tile::Signedness::Signed, cuda_tile::Signedness::Signed); + } else if (opElType.isFloat()) { + rewriter.replaceOpWithNewOp( + op, adaptor.getA(), adaptor.getB(), adaptor.getC()); + } else { + return rewriter.notifyMatchFailure(op, + "unsupported operand types of mma op"); + } + return success(); + } +}; + + +class ConvertTransOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::TransOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = getTypeConverter()->convertType(op.getResult().getType()); + assert(resTy && "expect valid type"); + // We need to replace the attribute, so we cannot use ConvertGenericOp. + rewriter.replaceOpWithNewOp( + op, resTy, adaptor.getSrc(), op.getOrder()); + return success(); + } +}; + +class ConvertAssertOp : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(triton::AssertOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getCondition(), + op.getMessage()); + return success(); + } +}; + +class ConvertRsqrtOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(math::RsqrtOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto resTy = getTypeConverter()->convertType(op.getResult().getType()); + if (!resTy) + return rewriter.notifyMatchFailure(op.getLoc(), + "typeConversion of rsqrt op failed"); + rewriter.replaceOpWithNewOp(op, resTy, + adaptor.getOperand()); + return success(); + } +}; + +static cuda_tile::AtomicRMWMode +convertAtomicModeToCudaTile(triton::RMWOp rmwOp) { + switch (rmwOp) { + case triton::RMWOp::AND: + return cuda_tile::AtomicRMWMode::AND; + case triton::RMWOp::OR: + return cuda_tile::AtomicRMWMode::OR; + case triton::RMWOp::XOR: + return cuda_tile::AtomicRMWMode::XOR; + case triton::RMWOp::ADD: + return cuda_tile::AtomicRMWMode::ADD; + case triton::RMWOp::FADD: + return cuda_tile::AtomicRMWMode::ADDF; + case triton::RMWOp::MAX: + return cuda_tile::AtomicRMWMode::MAX; + case triton::RMWOp::MIN: + return cuda_tile::AtomicRMWMode::MIN; + case triton::RMWOp::UMAX: + return cuda_tile::AtomicRMWMode::UMAX; + case triton::RMWOp::UMIN: + return cuda_tile::AtomicRMWMode::UMIN; + case triton::RMWOp::XCHG: + return cuda_tile::AtomicRMWMode::XCHG; + default: + llvm_unreachable("unknown RMW mode"); + } +} + +static cuda_tile::MemoryOrderingSemantics +convertMemorySemToCudaTile(triton::MemSemantic sem) { + switch (sem) { + case triton::MemSemantic::RELAXED: + return cuda_tile::MemoryOrderingSemantics::RELAXED; + case triton::MemSemantic::ACQUIRE: + return cuda_tile::MemoryOrderingSemantics::ACQUIRE; + case triton::MemSemantic::RELEASE: + return cuda_tile::MemoryOrderingSemantics::RELEASE; + case triton::MemSemantic::ACQUIRE_RELEASE: + return cuda_tile::MemoryOrderingSemantics::ACQ_REL; + default: + llvm_unreachable("unknown memory sem mode"); + } +} + +static cuda_tile::MemoryScope +convertMemoryScopeToCudaTile(triton::MemSyncScope scope) { + switch (scope) { + case triton::MemSyncScope::GPU: + return cuda_tile::MemoryScope::DEVICE; + // We do not expose CTA use TL_BLK instead. + case triton::MemSyncScope::CTA: + return cuda_tile::MemoryScope::TL_BLK; + case triton::MemSyncScope::SYSTEM: + return cuda_tile::MemoryScope::SYS; + default: + llvm_unreachable("unknown memory scope mode"); + } +} + +static cuda_tile::RoundingMode +convertRoundingModeToCudaTile(triton::RoundingMode rounding) { + switch (rounding) { + case triton::RoundingMode::RTZ: + return cuda_tile::RoundingMode::ZERO; + case triton::RoundingMode::RTNE: + return cuda_tile::RoundingMode::NEAREST_EVEN; + default: + llvm_unreachable("unknown rounding mode"); + } +} + +class ConvertAtomicRMWOp : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::AtomicRMWOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type retType = getTypeConverter()->convertType(op.getType()); + auto scalarTy = getElementTypeOrSelf(retType); + auto mode = convertAtomicModeToCudaTile(op.getAtomicRmwOp()); + auto newRMWOp = cuda_tile::AtomicRMWTkoOp::create( + rewriter, op.getLoc(), retType, + cuda_tile::TokenType::get(rewriter.getContext()), + convertMemorySemToCudaTile(op.getSem()), + convertMemoryScopeToCudaTile(op.getScope()), adaptor.getPtr(), mode, + adaptor.getVal(), adaptor.getMask(), /*token=*/nullptr); + rewriter.replaceOp(op, newRMWOp.getResult()); + return success(); + } +}; + +class ConvertAtomicCASOp : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::AtomicCASOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type retType = getTypeConverter()->convertType(op.getType()); + auto newCASOp = cuda_tile::AtomicCASTkoOp::create( + rewriter, op.getLoc(), retType, + cuda_tile::TokenType::get(rewriter.getContext()), + convertMemorySemToCudaTile(op.getSem()), + convertMemoryScopeToCudaTile(op.getScope()), adaptor.getPtr(), + adaptor.getCmp(), adaptor.getVal(), /*mask=*/Value(), + /*token=*/nullptr); + rewriter.replaceOp(op, newCASOp.getResult()); + return success(); + } +}; + +// Clamp operation clamps a value x between min and max bounds: +// clamp(x, min, max) = min(max(x, min), max) +// +// Examples: +// For x = -3 with bounds [0,2]: +// max(-3, 0) = 0 // First clamp to lower bound +// min(0, 2) = 0 // Then clamp to upper bound +// +// For x = 5 with bounds [0,2]: +// max(5, 0) = 5 // First clamp to lower bound +// min(5, 2) = 2 // Then clamp to upper bound +// +// The operation can either propagate NaN values (ALL) or not (NONE) +class ConvertClampFOp : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::ClampFOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = op.getLoc(); + + auto converter = getTypeConverter(); + Type xTy = converter->convertType(op.getX().getType()); + if (!xTy) + return rewriter.notifyMatchFailure( + op, "failed to convert operand types of clampf"); + + auto nanModifier = op.getPropagateNan() == PropagateNan::ALL + ? rewriter.getUnitAttr() + : nullptr; + + auto v = cuda_tile::MaxFOp::create(rewriter, loc, xTy, adaptor.getX(), + adaptor.getMin(), nanModifier, + /*flush_to_zero_modifier=*/nullptr); + rewriter.replaceOpWithNewOp( + op, v, adaptor.getMax(), nanModifier, + /*flush_to_zero_modifier=*/nullptr); + return success(); + } +}; + +class ConvertSplitOp : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::SplitOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Convert result types. + assert(op.getResult(0).getType() == op.getResult(1).getType() && + "result type mismatch"); + Type resultType = + getTypeConverter()->convertType(op.getResult(0).getType()); + if (!resultType) + return rewriter.notifyMatchFailure(op, "failed to convert result type"); + + // Split the last dimension with two cuda_tile.extract. + auto loc = op.getLoc(); + Value src = adaptor.getSrc(); + cuda_tile::TileType srcType = cast(src.getType()); + cuda_tile::TileType constType = + cuda_tile::TileType::get({}, rewriter.getI32Type()); + auto c0Attr = DenseIntElementsAttr::get(constType, {0}); + Value c0 = cuda_tile::ConstantOp::create(rewriter, loc, constType, c0Attr); + auto c1Attr = DenseIntElementsAttr::get(constType, {1}); + Value c1 = cuda_tile::ConstantOp::create(rewriter, loc, constType, c1Attr); + SmallVector indices0(srcType.getRank() - 1, c0); + SmallVector indices1(srcType.getRank() - 1, c0); + indices0.push_back(c0); + indices1.push_back(c1); + SmallVector extractedShape = + llvm::to_vector(llvm::drop_end(srcType.getShape())); + extractedShape.push_back(1); + auto extractedType = + cuda_tile::TileType::get(extractedShape, srcType.getElementType()); + Value extract0 = cuda_tile::ExtractOp::create(rewriter, loc, extractedType, + src, indices0); + Value extract1 = cuda_tile::ExtractOp::create(rewriter, loc, extractedType, + src, indices1); + + // Drop the last dimension. + SmallVector repls; + repls.push_back( + cuda_tile::ReshapeOp::create(rewriter, loc, resultType, extract0)); + repls.push_back( + cuda_tile::ReshapeOp::create(rewriter, loc, resultType, extract1)); + rewriter.replaceOp(op, repls); + return success(); + } +}; + +class ConvertFpToFpOp : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(triton::FpToFpOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto converter = this->getTypeConverter(); + SmallVector retTypes; + if (failed(converter->convertTypes(op->getResultTypes(), retTypes))) + return rewriter.notifyMatchFailure(op.getLoc(), + "typeConversion for FpToFpOp failed"); + + auto roundingMode = op.getRounding(); + auto cudaTileRoundingMode = cuda_tile::RoundingMode::NEAREST_EVEN; + if (roundingMode.has_value()) + cudaTileRoundingMode = + convertRoundingModeToCudaTile(roundingMode.value()); + auto cudaTileRoundingModeAttr = cuda_tile::RoundingModeAttr::get( + rewriter.getContext(), cudaTileRoundingMode); + rewriter.replaceOpWithNewOp( + op, retTypes, adaptor.getSrc(), cudaTileRoundingModeAttr); + return success(); + } +}; + +void populateTTirToCudaTileConversionPatternsAndLegality( + TypeConverter &typeConverter, RewritePatternSet &patterns, + ConversionTarget &target, bool approx, bool flushToZero, + DenseMap &numStagesMap, int computeCapability, + int numCTAInCGA, int simtNumWarpsInCTA, int occupancy, + std::optional numStages) { + MLIRContext *context = patterns.getContext(); + // clang-format off + patterns.add< + // Arith operations + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + + // Math operations + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + + // Triton operations + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp, + ConvertGenericOp +>(typeConverter, context, approx, flushToZero); + + patterns.add(typeConverter, context, computeCapability, numCTAInCGA, simtNumWarpsInCTA, occupancy); + patterns.add< + ConvertGenericOp + >(typeConverter, context); + + patterns.add< + ConvertAbsFOp, + ConvertAssertOp, + ConvertAtomicCASOp, + ConvertAtomicRMWOp, + ConvertBitcastOp, + ConvertBroadCastOp, + ConvertCatOp, + ConvertClampFOp, + ConvertCmpFOp, + ConvertCmpIOp, + ConvertConstantOp, + ConvertDotOp, + ConvertErfOp, + ConvertExpandDimsOp, + ConvertExternElementwiseOp, + ConvertForOp, + ConvertFpToFpOp, + ConvertGetNumProgramsOp, + ConvertGetProgramIdOp, + ConvertJoinOp, + ConvertMakeRangeOp, + ConvertMaxNumFOp, + ConvertMaximumFOp, + ConvertMinNumFOp, + ConvertMinimumFOp, + ConvertPrintOp, + ConvertReduceOp, + ConvertReduceReturnOp, + ConvertReshapeOp, + ConvertReturnOp, + ConvertRsqrtOp, + ConvertScanOp, + ConvertScanReturnOp, + ConvertSelectOp, + ConvertSplatOp, + ConvertSplitOp, + ConvertTransOp, + ConvertIfOp, + ConvertUnsplatOp, + ConvertWhileOp, + ConvertYieldOp +>(typeConverter, context); + + patterns.add(typeConverter, context, numStagesMap, computeCapability, numStages); + + patterns.add( + typeConverter, context, numStagesMap, computeCapability, numStages); + patterns.add(context); + // clang-format on +} + +/// Convert the given tt.constancy attribute. +static Value convertConstAttr(OpBuilder &b, Value v, Location loc, + const AxisInfo &info) { + auto type = cast(v.getType()); + if (type.getRank() == 0) + return v; + assert(type.getRank() == info.getRank() && "rank mismatch"); + auto pred = cuda_tile::SameElementsAttr::get( + b.getContext(), b.getDenseI64ArrayAttr(info.getConstancy())); + return cuda_tile::AssumeOp::create(b, loc, v, pred); +} + +/// Insert a cuda_tile.assume op based on the divisibility / contiguity of the +/// given Triton axis attributes. +static Value convertDivByAndContAttr(OpBuilder &b, Value v, Location loc, + const AxisInfo &info) { + auto type = cast(v.getType()); + + // Find the dimension with the largest divisibility. + int64_t divisor = 1; + std::optional every = std::nullopt; + std::optional along = std::nullopt; + for (int64_t i = 0, e = info.getRank(); i < e; ++i) { + if (info.getDivisibility(i) > divisor || + (info.getDivisibility(i) == divisor && + info.getContiguity(i) > every.value_or(1))) { + divisor = info.getDivisibility(i); + every = info.getContiguity(i); + along = i; + } + } + + if (type.getRank() == 0) { + // Rank 0 (scalar): drop contiguity. + every = std::nullopt; + along = std::nullopt; + } + + cuda_tile::DivByAttr pred = + cuda_tile::DivByAttr::get(b.getContext(), divisor, every, along); + return cuda_tile::AssumeOp::create(b, loc, v, pred); +} + +/// Helper struct that stores the Triton axis information for a given SSA +/// value, which was injected by the user. The AxisInfo object stores not only +/// the injected information. That's because divisibility and contiguity in +/// Triton can be set independently, whereas they always come as a pair in +/// cuda_tile. (And must be set together in cuda_tile.) +struct Assumption { + Assumption(Value value, const AxisInfo &info, bool hasDivByAttr, + bool hasContAttr, bool hasConstrAttr) + : value(value), info(info), hasDivByAttr(hasDivByAttr), + hasContAttr(hasContAttr), hasConstAttr(hasConstrAttr) {} + Value value; + AxisInfo info; + bool hasDivByAttr; + bool hasContAttr; + bool hasConstAttr; +}; + +/// Create a cuda_tile.assume op for the given assumption. +static void assumeAxisAttributes(RewriterBase &rewriter, + TypeConverter &converter, + const Assumption &assumption) { + assert((assumption.hasDivByAttr || assumption.hasContAttr || + assumption.hasConstAttr) && + "no attributes found to forward"); + OpBuilder::InsertionGuard g(rewriter); + Value v = assumption.value; + Location loc = v.getLoc(); + if (auto bbArg = dyn_cast(v)) { + rewriter.setInsertionPointToStart(bbArg.getOwner()); + } else { + rewriter.setInsertionPointAfter(v.getDefiningOp()); + } + + // Insert an unrealized_conversion_cast to the respective cuda_tile type. + Type convertedType = converter.convertType(v.getType()); + assert(convertedType && "could not convert type"); + auto tileType = dyn_cast(convertedType); + assert(tileType && "axis attribute not supported on non-tensor type"); + if (!isa(tileType.getElementType())) + return; + Value val = + UnrealizedConversionCastOp::create(rewriter, loc, convertedType, v) + .getResult(0); + Operation *firstCast = val.getDefiningOp(); + + // Create cuda_tile.assume op. + if (assumption.hasConstAttr) + val = convertConstAttr(rewriter, val, loc, assumption.info); + if (assumption.hasDivByAttr || assumption.hasContAttr) + val = convertDivByAndContAttr(rewriter, val, loc, assumption.info); + + // Insert an unrealized_conversion_cast back to the original type. + val = UnrealizedConversionCastOp::create(rewriter, loc, v.getType(), val) + .getResult(0); + rewriter.replaceAllUsesExcept(v, val, firstCast); +} + +static void getNumStages(Operation *op, + DenseMap &numStagesMap) { + op->walk([&](Operation *op) { + if (isa(op)) { + auto numStages = mlir::triton::utils::getNumStagesFromParentForOp(op); + if (numStages.has_value()) { + numStagesMap.insert({op, numStages.value()}); + } + } + return WalkResult::advance(); + }); +} + +static void +checkDivisibilityForDescriptorOps(mlir::ModuleOp op, + ModuleAxisInfoAnalysis &axisInfo) { + auto checkDivisibility = [&](Operation *op, mlir::ValueRange indices, + Value desc) -> void { + auto tensorDescType = dyn_cast(desc.getType()); + if (!tensorDescType) + return; + auto tileSizes = tensorDescType.getBlockType().getShape(); + for (size_t i = 0; i < indices.size(); i++) { + auto *info = axisInfo.getAxisInfo(indices[i]); + if (!info) { + op->emitWarning() + << "divisibility info not found for offset in dimension " << i + << " (offset: " << indices[i] << "), " + << "but tile size requires divisibility by " << tileSizes[i] + << " in tileir 13.1. Please add tl.assume(offset % block_shape == " + "0) if the offset is always divisible by the block shape."; + } + auto divisibility = info->getDivisibility(0); + if (divisibility % tileSizes[i] != 0) { + op->emitWarning() + << "divisibility at dimension " << i << ": offset divisibility is " + << divisibility << " but tile size requires divisibility by " + << tileSizes[i] + << " in tileir 13.1. Please add tl.assume(offset % block_shape == " + "0) if the offset is always divisible by the block shape."; + } + } + }; + + op->walk([&](Operation *op) { + if (auto descriptorStoreOp = dyn_cast(op)) { + checkDivisibility(op, descriptorStoreOp.getIndices(), + descriptorStoreOp.getDesc()); + } else if (auto descriptorLoadOp = dyn_cast(op)) { + checkDivisibility(op, descriptorLoadOp.getIndices(), + descriptorLoadOp.getDesc()); + } + }); +} + +static void convertTmaDescriptorOps(Operation *op, TypeConverter &converter) { + IRRewriter rewriter(op->getContext()); + auto ctx = op->getContext(); + auto loc = op->getLoc(); + op->walk([&](Operation *op) { + if (auto funcOp = dyn_cast(op)) { + for (size_t i = 0; i < funcOp.getNumArguments(); i++) { + Value tensorDesc = funcOp.getArgument(i); + if (isa(tensorDesc.getType())) { + // [tensordesc, ptr, shape, stride] + // 'i' is the tensordesc type + int argIdx = i; + rewriter.setInsertionPointToStart(&funcOp.getBody().front()); + auto tensorDescType = + cast(tensorDesc.getType()); + auto descBlock = tensorDescType.getBlockType(); + auto rank = descBlock.getRank(); + if (rank == 0) { + op->emitError("Host TMA descriptor with rank 0 is not supported."); + return WalkResult::interrupt(); + } + + auto pointeeType = descBlock.getElementType(); + if (auto intTy = dyn_cast(pointeeType)) { + pointeeType = mlir::IntegerType::get(ctx, intTy.getWidth(), + mlir::IntegerType::Signless); + } + unsigned elem_bytes = pointeeType.getIntOrFloatBitWidth() / 8; + unsigned align_byte = kTMAAlignment / elem_bytes; + + // 'i + 1' is the pointer of the global tensor + auto ptrArg = funcOp.getArgument(i + 1); + i = i + 1; + SmallVector shape; + for (size_t j = 0; j < rank; j++) + shape.push_back(funcOp.getArgument(i + j + 1)); + i = i + rank; + SmallVector stride; + for (size_t j = 0; j < rank; j++) + stride.push_back(funcOp.getArgument(i + j + 1)); + i = i + rank - 1; + + SmallVector wrappedDynShapes; + // It is safe to assume that shapes are in the range [0, 2^32 - 1] + // which is the TMA hardware limit for shape. This ensures that if + // users explicitly want to use TMA for this operation, the shape + // parameters will satisfy TMA descriptor encoding requirements. + for (auto v : shape) + wrappedDynShapes.push_back(wrapIntoScalarTile( + rewriter, v, /*attachAlignment=*/0, kMaxShape)); + + SmallVector wrappedDynStrides; + // It is safe to assume that strides are in the range [0, 2^40 - 1] + // which is the TMA hardware limit for stride. This ensures that if + // users explicitly want to use TMA for this operation, the stride + // parameters will satisfy TMA descriptor encoding requirements. + for (int i = 0; i < stride.size() - 1; i++) + wrappedDynStrides.push_back( + wrapIntoScalarTile(rewriter, stride[i], + /*attachAlignment=*/align_byte, kMaxStride)); + + auto tileIRPtrType = cuda_tile::PointerType::get(pointeeType); + auto ptrTypeWrapper = + cuda_tile::TileType::get(ctx, {}, tileIRPtrType); + auto ptrOp = UnrealizedConversionCastOp::create( + rewriter, loc, ptrTypeWrapper, ptrArg) + .getResult(0); + + // we can always assume the pointer is divisible by 16 + // because openai has already make it into host tma api. + auto ptrWithDivBy = + cuda_tile::AssumeOp::create( + rewriter, loc, ptrOp, + cuda_tile::DivByAttr::get(ctx, kTMAAlignment, std::nullopt, + std::nullopt)) + .getResult(); + + SmallVector globalShape(rank, + cuda_tile::TensorViewType::kDynamic); + SmallVector globalStride( + rank, cuda_tile::TensorViewType::kDynamic); + // we can always assume the stride is 1 + // because openai has assume this. + globalStride[globalShape.size() - 1] = 1; + + auto tensorViewTy = cuda_tile::TensorViewType::get( + ctx, tileIRPtrType.getPointeeType(), globalShape, globalStride); + auto makeTensorViewOp = cuda_tile::MakeTensorViewOp::create( + rewriter, loc, tensorViewTy, ptrWithDivBy, wrappedDynShapes, + wrappedDynStrides); + + auto tileShape = descBlock.getShape(); + SmallVector arrayOfi32Shape; + for (auto i64Shape : tileShape) { + arrayOfi32Shape.push_back(i64Shape); + } + + SmallVector dimMap(rank); + std::iota(dimMap.begin(), dimMap.end(), 0); + + auto tilePartViewTy = cuda_tile::PartitionViewType::get( + ctx, rewriter.getDenseI32ArrayAttr(arrayOfi32Shape), + tensorViewTy, dimMap, + cuda_tile::PaddingValueAttr::get(ctx, + cuda_tile::PaddingValue::zero)); + + auto partViewOp = cuda_tile::MakePartitionViewOp::create( + rewriter, loc, tilePartViewTy, makeTensorViewOp); + + auto castBackToTensorDescriptorOp = + UnrealizedConversionCastOp::create(rewriter, loc, tensorDescType, + partViewOp.getResult()); + + rewriter.replaceAllUsesWith( + tensorDesc, castBackToTensorDescriptorOp.getResult(0)); + } + } + } + return WalkResult::advance(); + }); +} + +/// Convert attributes that are related to the axis analysis. +static void convertAxisAttributes(mlir::ModuleOp op, + ModuleAxisInfoAnalysis &axisInfo, + TypeConverter &converter) { + SmallVector assumptions; + + // Find all tt.divisibility, tt.contiguity, tt.constancy attributes. For each + // such value, do not read the value directly, but query the Triton AxisInfo. + op->walk([&](Operation *op) { + if (auto funcOp = dyn_cast(op)) { + // Convert attributes that are attached to function block arguments. + for (size_t i = 0; i < funcOp.getNumArguments(); i++) { + auto divByAttr = funcOp.getArgAttr(i, "tt.divisibility"); + auto contAttr = funcOp.getArgAttr(i, "tt.contiguity"); + auto constAttr = funcOp.getArgAttr(i, "tt.constancy"); + if (divByAttr || contAttr || constAttr) { + Value v = funcOp.getArgument(i); + assumptions.emplace_back( + v, *axisInfo.getAxisInfo(v), static_cast(divByAttr), + static_cast(contAttr), static_cast(constAttr)); + } + } + return WalkResult::advance(); + } + + // Convert attributes that are attached to operations. + auto divByAttr = op->getDiscardableAttr("tt.divisibility"); + auto contAttr = op->getDiscardableAttr("tt.contiguity"); + auto constAttr = op->getDiscardableAttr("tt.constancy"); + if (divByAttr || contAttr || constAttr) { + assert(op->getNumResults() == 1 && "expected op with single result"); + Value v = op->getResult(0); + assumptions.emplace_back( + v, *axisInfo.getAxisInfo(v), static_cast(divByAttr), + static_cast(contAttr), static_cast(constAttr)); + } + return WalkResult::advance(); + }); + + // Now materialize all assumptions as cuda_tile.assume ops. This is not done + // during the above loop because modifying IR invalidates the axis analysis. + IRRewriter rewriter(op->getContext()); + for (const auto &assumption : assumptions) + assumeAxisAttributes(rewriter, converter, assumption); +} + +// map_elementwise pre-processing functions are in MapElementwiseExpansion.cpp. +// expandMapElementwiseOps() is called from runOnOperation() below. + +struct ConvertTritonToCudaTile + : public ::mlir::triton::impl::ConvertTritonToCudaTileBase< + ConvertTritonToCudaTile> { +public: + // Map from load/store operations to num_stages from its parent ForOp. + DenseMap numStagesMap; + // Value of per-kernel num_stages. + std::optional numStages; + + ConvertTritonToCudaTile() = default; + ConvertTritonToCudaTile(bool approxModifier, bool flushToZeroModifier, + int computeCapability, int numCTAInCGA, + int simtNumWarpsInCTA, int occupancy, + std::optional numStages) { + this->approxModifier = approxModifier; + this->flushToZeroModifier = flushToZeroModifier; + this->computeCapability = computeCapability; + this->numCTAInCGA = numCTAInCGA; + this->simtNumWarpsInCTA = simtNumWarpsInCTA; + this->occupancy = occupancy; + this->numStages = numStages; + } + void runOnOperation() override { + MLIRContext *context = &getContext(); + ModuleOp mod_buildin = getOperation(); + CudaTileTypeConverter typeConverter; + + // Insert cuda tile module directly. + OpBuilder builder(context); + auto mod = cuda_tile::ModuleOp::create(builder, mod_buildin.getLoc(), + "cuda_tile_module"); + auto ®ion = mod.getBodyRegion(); + region.getBlocks().clear(); + IRMapping mapping; + mod_buildin.getBodyRegion().cloneInto(®ion, mapping); + auto &block = mod_buildin.getBodyRegion().front(); + block.clear(); + block.push_front(mod); + + // Insert Host TMA descriptor ops. + convertTmaDescriptorOps(mod.getOperation(), typeConverter); + + ModuleAxisInfoAnalysis axisInfo(mod_buildin); + + // Check divisibility for all indices in descriptor load and store ops. + checkDivisibilityForDescriptorOps(mod_buildin, axisInfo); + + // Convert all axis attributes. + convertAxisAttributes(mod_buildin, axisInfo, typeConverter); + + // Get num_stages for load/store ops. + getNumStages(mod.getOperation(), this->numStagesMap); + + // Pre-processing: expand tt.map_elementwise into tensor-level ops. + // This must run before dialect conversion so the expanded arith/math ops + // get picked up by ConvertGenericOp patterns. + if (failed(mlir::triton::expandMapElementwiseOps(mod.getOperation()))) + return signalPassFailure(); + + // Dialect conversion: Convert all operations. + CudaTileConversionTarget target(*context, typeConverter); + RewritePatternSet patterns(context); + populateTTirToCudaTileConversionPatternsAndLegality( + typeConverter, patterns, target, this->approxModifier, + this->flushToZeroModifier, this->numStagesMap, this->computeCapability, + this->numCTAInCGA, this->simtNumWarpsInCTA, this->occupancy, + this->numStages); + + ConversionConfig config = ConversionConfig(); + config.buildMaterializations = false; + // use full conversion here to allow only know operations since cuda_tile + // doesn't allow other dialect's ops + if (failed(applyFullConversion(mod, target, std::move(patterns), config))) + return signalPassFailure(); + + // Try to reconcile as many unrealized_conversion_cast ops as possible. + SmallVector castOps, remainingCastOps; + mod->walk([&](UnrealizedConversionCastOp op) { castOps.push_back(op); }); + reconcileUnrealizedCasts(castOps, &remainingCastOps); + + // Required to clean up any remaining unrealized casts and ensure IR + // validity after dialect conversion. Without this, subsequent passes may + // fail due to invalid IR structure or unreconciled casts. + { + RewritePatternSet patterns(context); + if (failed(applyPatternsGreedily(mod, std::move(patterns)))) + return signalPassFailure(); + } + } +}; + +} // namespace + +std::unique_ptr> +mlir::triton::createConvertTritonToCudaTilePass() { + return std::make_unique(); +} + +std::unique_ptr> +mlir::triton::createConvertTritonToCudaTilePass(bool approx, bool ftz, + int capability, int num_ctas, + int simt_num_warps, + int occupancy, + std::optional num_stages) { + return std::make_unique( + approx, ftz, capability, num_ctas, simt_num_warps, occupancy, num_stages); +} diff --git a/third_party/tileir/lib/TritonToTileIR/Utils.cpp b/third_party/tileir/lib/TritonToTileIR/Utils.cpp new file mode 100644 index 0000000000..4dc95bd837 --- /dev/null +++ b/third_party/tileir/lib/TritonToTileIR/Utils.cpp @@ -0,0 +1,469 @@ +#include "TritonToTileIR/Utils.h" + +#include "mlir/Dialect/Arith/IR/Arith.h" + +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/APInt.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "triton-to-tileir-utils" + +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" +#include "cuda_tile/Dialect/CudaTile/IR/Dialect.h" +#include "cuda_tile/Dialect/CudaTile/IR/Ops.h" +#include "cuda_tile/Dialect/CudaTile/IR/Types.h" +#include "triton/Analysis/Utility.h" +#include "triton/Dialect/Triton/IR/Dialect.h" + +namespace mlir { +namespace bridge_utils { +namespace { + +enum class IdentityValue { + NONE, + ZERO, + ONE, + POS_INF, + NEG_INF, + MAX_SIGNED, + MIN_SIGNED, + MAX_UNSIGNED, + MIN_UNSIGNED +}; + +// Helper function to convert IdentityValue to string for debugging +static const char *identityValueToString(IdentityValue value) { + switch (value) { + case IdentityValue::NONE: + return "NONE"; + case IdentityValue::ZERO: + return "ZERO"; + case IdentityValue::ONE: + return "ONE"; + case IdentityValue::POS_INF: + return "POS_INF"; + case IdentityValue::NEG_INF: + return "NEG_INF"; + case IdentityValue::MAX_SIGNED: + return "MAX_SIGNED"; + case IdentityValue::MIN_SIGNED: + return "MIN_SIGNED"; + case IdentityValue::MAX_UNSIGNED: + return "MAX_UNSIGNED"; + case IdentityValue::MIN_UNSIGNED: + return "MIN_UNSIGNED"; + } + return "UNKNOWN"; +} + +Attribute getIdentitiesAttr(MLIRContext *context, + ConversionPatternRewriter &rewriter, Type eltType, + IdentityValue value) { + if (auto floatTy = dyn_cast(eltType)) { + const auto &semantics = floatTy.getFloatSemantics(); + switch (value) { + case IdentityValue::ZERO: + return FloatAttr::get(floatTy, APFloat::getZero(semantics)); + case IdentityValue::ONE: + return FloatAttr::get(floatTy, APFloat::getOne(semantics)); + case IdentityValue::NEG_INF: + return FloatAttr::get(floatTy, + APFloat::getInf(semantics, /*negative=*/true)); + case IdentityValue::POS_INF: + return FloatAttr::get(floatTy, + APFloat::getInf(semantics, /*negative=*/false)); + default: + llvm_unreachable("unexpected identity value for float type"); + } + } + + if (auto intTy = dyn_cast(eltType)) { + auto width = intTy.getWidth(); + switch (value) { + case IdentityValue::ZERO: + return IntegerAttr::get(intTy, APInt::getZero(width)); + case IdentityValue::ONE: + return IntegerAttr::get(intTy, APInt(width, 1)); + case IdentityValue::MAX_SIGNED: + return IntegerAttr::get(intTy, APInt::getSignedMaxValue(width)); + case IdentityValue::MIN_SIGNED: + return IntegerAttr::get(intTy, APInt::getSignedMinValue(width)); + case IdentityValue::MAX_UNSIGNED: + return IntegerAttr::get(intTy, APInt::getMaxValue(width)); + case IdentityValue::MIN_UNSIGNED: + return IntegerAttr::get(intTy, APInt::getMinValue(width)); + default: + llvm_unreachable("unexpected identity value for integer type"); + } + } + + assert(false && "unexpected data type for reduceOp."); +} + +bool isI8OrI1ElementTensor(Type type) { + auto tensorTy = dyn_cast(type); + if (!tensorTy) + return false; + auto elmTy = tensorTy.getElementType(); + if (auto intTy = dyn_cast(elmTy)) { + auto bitWidth = intTy.getIntOrFloatBitWidth(); + return bitWidth == 8 || bitWidth == 1; + } + return false; +} + +} // namespace + +// Helper function to find operations that consume both block arguments +static SmallVector findConsumingOperations(Value inputOperand, + Value identityOperand) { + SmallVector consumingOps; + + // Collect all operations that use the input operand + SmallPtrSet inputUsers; + for (auto &use : inputOperand.getUses()) { + inputUsers.insert(use.getOwner()); + } + + // Check which of these also use the identity operand + for (auto &use : identityOperand.getUses()) { + Operation *op = use.getOwner(); + if (inputUsers.contains(op)) { + // Verify the operation actually uses both values as operands + bool usesInput = llvm::any_of(op->getOperands(), [&](Value operand) { + return operand == inputOperand; + }); + bool usesIdentity = llvm::any_of(op->getOperands(), [&](Value operand) { + return operand == identityOperand; + }); + + if (usesInput && usesIdentity) { + consumingOps.push_back(op); + } + } + } + + return consumingOps; +} + +// Helper function to analyze operations and get consistent identity +static FailureOr +analyzeConsistentIdentity(ArrayRef consumingOps, + size_t pairIndex) { + if (consumingOps.empty()) { + LLVM_DEBUG(llvm::dbgs() + << "Warning: No consumer found for Arguments in pair " + << pairIndex << ".\n"); + return IdentityValue::NONE; + } + + // Helper to analyze operation and determine reduction type + auto analyzeOp = [&](Operation *op) -> IdentityValue { + // Integer comparison operations + if (auto cmp = dyn_cast(op)) { + switch (cmp.getPredicate()) { + case arith::CmpIPredicate::sgt: + return IdentityValue::MIN_SIGNED; + case arith::CmpIPredicate::slt: + return IdentityValue::MAX_SIGNED; + case arith::CmpIPredicate::ugt: + return IdentityValue::MIN_UNSIGNED; + case arith::CmpIPredicate::ult: + return IdentityValue::MAX_UNSIGNED; + default: + break; + } + } + + // Float comparison operations + if (auto cmp = dyn_cast(op)) { + switch (cmp.getPredicate()) { + case arith::CmpFPredicate::OGT: + return IdentityValue::NEG_INF; + case arith::CmpFPredicate::OLT: + return IdentityValue::POS_INF; + default: + break; + } + } + + // Arithmetic operations + if (isa(op)) + return IdentityValue::ZERO; + if (isa(op)) + return IdentityValue::ONE; + if (isa(op)) + return IdentityValue::ZERO; + if (isa(op)) + // Bitwise AND identity requires all bits set to 1, not just value 1 + return IdentityValue::MAX_UNSIGNED; + if (isa(op)) + return IdentityValue::ZERO; + + // Min/Max operations + if (isa(op)) + return IdentityValue::POS_INF; + if (isa(op)) + return IdentityValue::NEG_INF; + if (isa(op)) + return IdentityValue::MAX_SIGNED; + if (isa(op)) + return IdentityValue::MAX_UNSIGNED; + if (isa(op)) + return IdentityValue::MIN_SIGNED; + if (isa(op)) + return IdentityValue::MIN_UNSIGNED; + + assert(!isa(op) && "CallOp need to be inlined"); + return IdentityValue::NONE; + }; + + // Analyze all consuming operations to get their identities + SmallVector identityValues; + for (Operation *op : consumingOps) { + IdentityValue identity = analyzeOp(op); + if (identity != IdentityValue::NONE) { + identityValues.push_back(identity); + } + } + + if (identityValues.empty()) { + LLVM_DEBUG( + llvm::dbgs() + << "Warning: No valid identity values found for Arguments in pair " + << pairIndex << ".\n"); + return IdentityValue::NONE; + } + + // Check if all identities are the same (with early exit optimization) + IdentityValue firstIdentity = identityValues[0]; + for (size_t j = 1; j < identityValues.size(); ++j) { + if (identityValues[j] != firstIdentity) { + llvm::errs() << "Error: Arguments pair in combineOp are consumed by " + "operations with different identities: " + << "first=" << identityValueToString(firstIdentity) + << ", found=" << identityValueToString(identityValues[j]) + << ", please check if combineOp satisfies associative and " + "commutative laws.\n"; + return failure(); + } + } + + return firstIdentity; +} + +FailureOr +getIdentitiesFromCombineOp(Region &combineOp, ArrayRef retType, + ConversionPatternRewriter &rewriter) { + SmallVector attributes; + MLIRContext *context = combineOp.getContext(); + // Here, it tries to deduce the correct identity, but even if it fails, + // the backend can still calculate the correct result. + // It's hard to code a general logic to cover all complicate region + // calculations. Hence, backend should ensure ReduceOp to be identity + // insensitive in power of 2 cases. Details refer to: + Block &block = combineOp.front(); + auto blockArgs = block.getArguments(); + size_t numReturns = blockArgs.size() / 2; + +#ifndef NDEBUG + // Validate that we have an even number of arguments + assert(blockArgs.size() % 2 == 0 && + "Combine operation must have an even number of block arguments"); + // Number of returns should be half of all operands + assert(retType.size() == numReturns && + "Number of return types must be half of block arguments"); + // Validate the block arguments types with the retType + // First half of blockArgs are input operands, second half are identities + for (size_t i = 0; i < numReturns; i++) { + // Check input operand type + assert(blockArgs[i].getType() == + dyn_cast(retType[i]).getElementType() && + "block argument type mismatch for input operand"); + // Check identity type + assert(blockArgs[i + numReturns].getType() == + dyn_cast(retType[i]).getElementType() && + "block argument type mismatch for identity"); + } +#endif // NDEBUG + + // Process each pair of arguments + for (size_t i = 0; i < numReturns; i++) { + Value inputOperand = blockArgs[i]; + Value identityOperand = blockArgs[i + numReturns]; + IdentityValue identityValue = IdentityValue::ZERO; + + // Find operations and analyze their identities + SmallVector consumingOps = + findConsumingOperations(inputOperand, identityOperand); + FailureOr identityValueOr = + analyzeConsistentIdentity(consumingOps, i); + + if (failed(identityValueOr)) { + // Identity consistency error - propagate failure + return failure(); + } + + IdentityValue result = *identityValueOr; + if (result == IdentityValue::NONE) { + // Use dummy identity for cases with no valid identity value + LLVM_DEBUG(llvm::dbgs() + << "Using dummy identity (ZERO) for pair " << i << ".\n"); + identityValue = IdentityValue::ZERO; + } else { + identityValue = result; + } + attributes.push_back(getIdentitiesAttr( + context, rewriter, dyn_cast(retType[i]).getElementType(), + identityValue)); + } + + return ArrayAttr::get(context, attributes); +} + +bool canMapToCudaTile(triton::FuncOp op, CudaTileTypeConverter &typeConverter) { + // kernel in cuda tile do not return any result. + if (op.getNumResults() > 0 && op.getSymVisibility() == "public") + return false; + // The operation is legal if we cannot convert a type to cuda tile. + SmallVector newTypes; + if (failed(typeConverter.convertTypes(op.getFunctionType().getInputs(), + newTypes))) + return false; + if (failed(typeConverter.convertTypes(op.getFunctionType().getResults(), + newTypes))) + return false; + return true; +} + +/// Upcast input (expected to be a cuda tile tensor) to i16 from i1 or i8, +/// otherwise just return the input. +Value upCastOrSelf(OpBuilder &builder, Location loc, Value input, + Signedness signedness, IntegerUpCast integerUpCast) { + auto type = dyn_cast(input.getType()); + // Cast not needed. + if (integerUpCast == IntegerUpCast::None || !isI8OrI1ElementTensor(type)) + return input; + assert(integerUpCast == IntegerUpCast::To_I16 && + "only upcast to i16 is supported"); + auto tensorTy = + cuda_tile::TileType::get(type.getShape(), builder.getIntegerType(16)); + auto signednessAttr = signedness == Signedness::Unsigned + ? cuda_tile::Signedness::Unsigned + : cuda_tile::Signedness::Signed; + return cuda_tile::ExtIOp::create(builder, loc, tensorTy, input, + signednessAttr); +} + +/// Downcast the result of `createOp` back to i1 or i8. +Value downCastOrSelf( + OpBuilder &builder, Location loc, ArrayRef operands, Type retType, + llvm::function_ref)> + createOp, + IntegerUpCast integerUpCast) { + if (integerUpCast == IntegerUpCast::None || !isI8OrI1ElementTensor(retType)) + return createOp(builder, loc, retType, operands); + + auto tensorTy = + cuda_tile::TileType::get(cast(retType).getShape(), + builder.getIntegerType(16)); + auto newOp = createOp(builder, loc, tensorTy, operands); + return cuda_tile::TruncIOp::create(builder, loc, retType, newOp); +} + +LogicalResult matchAndRewriteGenericOpImpl( + Operation *op, ValueRange operands, const TypeConverter *converter, + ConversionPatternRewriter &rewriter, + llvm::function_ref)> + createOp, + Signedness signedness, IntegerUpCast integerUpCast) { + if (!op->hasTrait()) + return rewriter.notifyMatchFailure(op, "expect single result operation"); + + auto loc = op->getLoc(); + SmallVector newOperands; + for (auto input : operands) + newOperands.push_back( + upCastOrSelf(rewriter, loc, input, signedness, integerUpCast)); + + Type retType = converter->convertType(op->getResult(0).getType()); + auto newOp = downCastOrSelf(rewriter, loc, newOperands, retType, createOp, + integerUpCast); + rewriter.replaceOp(op, newOp); + return success(); +} + +CudaTileTypeConverter::CudaTileTypeConverter() { + // in python api level, we use 0 as a placeholder for tensordesc type + // so we need to convert it to i32 type + addConversion([](triton::TensorDescType type) { + MLIRContext *ctx = type.getContext(); + auto descBlock = type.getBlockType(); + auto tileShape = descBlock.getShape(); + auto rank = tileShape.size(); + + SmallVector globalShape(rank, cuda_tile::TensorViewType::kDynamic); + SmallVector globalStride(rank, + cuda_tile::TensorViewType::kDynamic); + globalStride[rank - 1] = 1; + + auto pointeeType = descBlock.getElementType(); + if (auto intTy = dyn_cast(pointeeType)) { + pointeeType = mlir::IntegerType::get(ctx, intTy.getWidth(), + mlir::IntegerType::Signless); + } + + auto tensorViewTy = cuda_tile::TensorViewType::get( + ctx, pointeeType, globalShape, globalStride); + + SmallVector dimMap(rank); + std::iota(dimMap.begin(), dimMap.end(), 0); + + SmallVector arrayOfi32Shape; + for (auto i64Shape : tileShape) { + arrayOfi32Shape.push_back(i64Shape); + } + auto shapeAttr = DenseI32ArrayAttr::get(ctx, arrayOfi32Shape); + + return cuda_tile::PartitionViewType::get( + ctx, shapeAttr, tensorViewTy, dimMap, + cuda_tile::PaddingValueAttr::get(ctx, cuda_tile::PaddingValue::zero)); + }); + addConversion([](cuda_tile::TensorViewType type) { return type; }); + addConversion([](cuda_tile::PartitionViewType type) { return type; }); + addConversion([](cuda_tile::TokenType type) { return type; }); + addConversion( + [](FloatType type) { return cuda_tile::TileType::get({}, type); }); + addConversion( + [](IntegerType type) { return cuda_tile::TileType::get({}, type); }); + // Convert a pointer type into a zero-ranked tensor type, where the element + // type is a CUDA pointer type. + addConversion([this](triton::PointerType ptrType) -> Type { + auto pointeeType = ptrType.getPointeeType(); + // Do not crash on cuda tile verifier if we get a ptr. + if (isa(pointeeType)) + return Type(); + return cuda_tile::TileType::get({}, + cuda_tile::PointerType::get(pointeeType)); + }); + // Convert a ranked tensor type to a CUDA tensor type. There are two + // possible conversions: 1. When the element type is a pointer type: Extract + // the pointer type element from the zero-ranked tensor type produced by the + // type converter, then repack it into a new tensor while adjusting the + // shape accordingly. + // 2. When the element type is an integer or a floating point scalar type, + // pack it into a CUDA tensor type adjusting the shape accordingly. + addConversion([this](RankedTensorType tensorType) { + auto elemType = tensorType.getElementType(); + if (isa(elemType)) { + cuda_tile::TileType tensorTy = + cast(convertType(elemType)); + return cuda_tile::TileType::get(tensorType.getShape(), + tensorTy.getElementType()); + } + return cuda_tile::TileType::get(tensorType.getShape(), elemType); + }); +} + +} // namespace bridge_utils +} // namespace mlir diff --git a/third_party/tileir/lib/Utils/CMakeLists.txt b/third_party/tileir/lib/Utils/CMakeLists.txt new file mode 100644 index 0000000000..1003485620 --- /dev/null +++ b/third_party/tileir/lib/Utils/CMakeLists.txt @@ -0,0 +1,10 @@ +set(CUDA_TILE_INSTALL_DIR $ENV{CUDA_TILE_INSTALL_DIR}) + +add_triton_library(Utils + Utils.cpp + + LINK_LIBS PUBLIC + MLIRIR + TritonIR + ${CUDA_TILE_INSTALL_DIR}/lib/libCudaTileDialect.a +) diff --git a/third_party/tileir/lib/Utils/Utils.cpp b/third_party/tileir/lib/Utils/Utils.cpp new file mode 100644 index 0000000000..456c0df903 --- /dev/null +++ b/third_party/tileir/lib/Utils/Utils.cpp @@ -0,0 +1,72 @@ +#include "Utils/Utils.h" + +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Types.h" +#include "mlir/Support/LLVM.h" + +#include "triton/Dialect/Triton/IR/Dialect.h" + +namespace mlir { +namespace triton { +namespace utils { + +std::optional getNumStagesFromParentForOp(Operation *op) { + auto parentOp = op->getParentOfType(); + while (parentOp) { + // Check for tt.num_stages attribute on ForOp + if (auto numStagesAttr = parentOp->getAttr("tt.num_stages")) { + if (auto intAttr = dyn_cast(numStagesAttr)) { + return intAttr.getInt(); + } + } + parentOp = parentOp->getParentOfType(); + } + return std::nullopt; +} + +std::optional +convertNumStagesToOptHint(Operation *op, MLIRContext *ctx, + const DenseMap &numStagesMap, + int computeCapability, std::optional numStages) { + int numStagesValue = -1; + if (numStagesMap.find(op) != numStagesMap.end()) { + numStagesValue = numStagesMap.at(op); + } else if (numStages.has_value()) { + numStagesValue = numStages.value(); + } + // The cost is valid between 1 and 10. + // Will clip to 10 if numStages is greater than 10. + // For 0 or negative values, we will use the default cost indicated by a null + // OptHintAttr. + int clippedNumStages = std::min(numStagesValue, 10); + if (clippedNumStages > 0) + return mlir::triton::utils::cvtNumStagesToOptHintAttr( + ctx, computeCapability, clippedNumStages); + + return std::nullopt; +} + +std::optional +cvtNumStagesToOptHintAttr(MLIRContext *ctx, int computeCapability, + int numStages) { + + std::string arch = ("sm_" + llvm::Twine(computeCapability)).str(); + if (numStages == -1) + return std::nullopt; + return cuda_tile::OptimizationHintsAttr::get( + ctx, + mlir::DictionaryAttr::get( + ctx, + mlir::NamedAttribute( + arch, mlir::DictionaryAttr::get( + ctx, mlir::NamedAttribute( + mlir::StringAttr::get(ctx, "latency"), + mlir::IntegerAttr::get( + IntegerType::get(ctx, 32), numStages)))))); +} + +} // namespace utils +} // namespace triton +} // namespace mlir diff --git a/third_party/tileir/scripts/build_cuda_tile.sh b/third_party/tileir/scripts/build_cuda_tile.sh new file mode 100644 index 0000000000..a425798248 --- /dev/null +++ b/third_party/tileir/scripts/build_cuda_tile.sh @@ -0,0 +1,31 @@ + +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="${1:-}" +if [[ -z "${REPO_ROOT}" ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +if [[ ! -d "${REPO_ROOT}" ]]; then + echo "Repo root does not exist: ${REPO_ROOT}" >&2 + exit 2 +fi + +: "${LLVM_SYSPATH:?LLVM_SYSPATH is required}" +LLVM_EXTERNAL_LIT="${LLVM_EXTERNAL_LIT:-${LLVM_SYSPATH}/bin/llvm-lit}" + +BUILD_DIR="${REPO_ROOT}/build" +INSTALL_DIR="${REPO_ROOT}/build/install" +JOBS="${NINJA_JOBS:-32}" + +# Clean previous build and install results +rm -rf "${BUILD_DIR}" "${INSTALL_DIR}" +mkdir -p "${BUILD_DIR}" "${INSTALL_DIR}" + +cmake -S "${REPO_ROOT}" -B "${BUILD_DIR}" \ + -DCUDA_TILE_USE_LLVM_INSTALL_DIR="${LLVM_SYSPATH}" \ + -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} + +cmake --build "${BUILD_DIR}" --target install -- -j"${JOBS}" diff --git a/third_party/tileir/scripts/build_helper/Dockerfile.release b/third_party/tileir/scripts/build_helper/Dockerfile.release new file mode 100644 index 0000000000..b65a962db5 --- /dev/null +++ b/third_party/tileir/scripts/build_helper/Dockerfile.release @@ -0,0 +1,77 @@ +# syntax=docker/dockerfile:1 +# Multi-stage build: wheel (CI) or source (default/development) +# +# Usage: +# CI (wheel): docker build --target wheel ... +# Dev (source): docker build --target source ... +# Default: docker build ... (uses source) + +ARG BASE_IMAGE=nvcr.io/nvidia/pytorch:25.08-py3 +ARG TRITON_SRC_DIR=/workspace/triton-src + +#################### +# Stage 1: Base (shared) +#################### +FROM ${BASE_IMAGE} AS base + +ARG TRITON_SRC_DIR +# Uninstall preinstalled triton variants to avoid conflicts +RUN pip uninstall -y triton triton-nightly pytorch-triton || true + +# Install gcc-13/g++-13 (used by CC/CXX during build) and ccache, then clean cache +# Also install git + git-lfs for repos that rely on LFS objects. +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + software-properties-common ca-certificates gnupg curl git && \ + curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash && \ + add-apt-repository -y ppa:ubuntu-toolchain-r/test && \ + apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + gcc-13 g++-13 ccache git-lfs && \ + git lfs install --system && \ + rm -rf /var/lib/apt/lists/* + +# Configure ccache for faster rebuilds +ENV CCACHE_DIR=/ccache +ENV PATH=/usr/lib/ccache:$PATH + +# Bring in partial CUDA 13.1 deps directory (includes tileiras) and expose its bin +COPY cuda_dep /opt/cuda_dep +ENV CUDA_HOME=/opt/cuda_dep/ +ENV TRITON_TILEIRAS_PATH=/opt/cuda_dep/bin +# Install build dependencies BEFORE copying source for better cache efficiency +# These match pyproject.toml [build-system] requires +RUN pip install "setuptools>=40.8.0" "cmake>=3.20,<4.0" "ninja>=1.11.1" "pybind11>=2.13.1" + +# Copy source (this layer changes frequently, so keep it late) +COPY src ${TRITON_SRC_DIR} + +WORKDIR ${TRITON_SRC_DIR} +# Remove host build cache to prevent CMake source/build dir mismatch +RUN rm -rf ${TRITON_SRC_DIR}/build || true + +#################### +# Stage 2: Wheel install (CI – Python 3.12 x86_64) +#################### +FROM base AS wheel + +ARG TRITON_SRC_DIR +# Copy and install pre-built wheel (e.g. nvtriton-*-cp312-cp312-linux_x86_64.whl) +COPY wheel/*.whl /tmp/ +RUN pip install --no-cache-dir /tmp/*.whl && rm -f /tmp/*.whl + +# Copy source so /workspace/triton-src exists for make test-* and test discovery +COPY src ${TRITON_SRC_DIR} + +#################### +# Stage 3: Source install (default for docker build) – just the install step +#################### +FROM base AS source + +# Use --no-build-isolation so cmake path in build.ninja points to installed cmake +# (not temporary pip-build-env path that gets cleaned up) +# Use editable install so: +# 1. Python uses source directory directly (tests can import each other) +# 2. Compiled .so files are placed in source directory +# 3. No version mismatch between installed and source triton +RUN CC="gcc-13" CXX="g++-13" pip install -e . --no-build-isolation diff --git a/third_party/tileir/scripts/check_wheel_deps.py b/third_party/tileir/scripts/check_wheel_deps.py new file mode 100644 index 0000000000..5b710c0a3d --- /dev/null +++ b/third_party/tileir/scripts/check_wheel_deps.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Sanity check that wheel contains tileir backend deps (tileiras, ptxas, nvvm). +Usage: python check_wheel_deps.py [path/to/wheel.whl] + If no path given, uses the first .whl in dist/. +Exits 0 if all required names are found, 1 otherwise. +""" +import sys +import zipfile +from pathlib import Path + + +REQUIRED = [ + "tileiras", # backend/bin/tileiras + "ptxas", # backend/bin/ptxas + "libnvvm.so", # backend/lib/nvvm/.../libnvvm.so +] + + +def main(): + if len(sys.argv) > 1: + wheel_path = Path(sys.argv[1]) + else: + dist = Path(__file__).resolve().parents[2] / ".." / ".." / "dist" + dist = dist.resolve() + whls = list(dist.glob("*.whl")) + if not whls: + print("No wheel found in dist/ and no path given.", file=sys.stderr) + return 1 + wheel_path = whls[0] + + if not wheel_path.is_file(): + print(f"Not a file: {wheel_path}", file=sys.stderr) + return 1 + + with zipfile.ZipFile(wheel_path) as z: + names = z.namelist() + + # Normalize: we only care about the last path component for binaries/libs + found = {r: False for r in REQUIRED} + for name in names: + base = name.rstrip("/").split("/")[-1] + if base == "tileiras": + found["tileiras"] = True + elif base == "ptxas": + found["ptxas"] = True + elif "libnvvm.so" in name: + found["libnvvm.so"] = True + + all_ok = all(found.values()) + for r in REQUIRED: + status = "ok" if found[r] else "MISSING" + print(f" {r}: {status}") + if not all_ok: + print("Wheel is missing one or more tileir deps.", file=sys.stderr) + return 1 + print("All tileir deps present in wheel.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/third_party/tileir/scripts/copy_wheel_deps.py b/third_party/tileir/scripts/copy_wheel_deps.py new file mode 100644 index 0000000000..c1258fe4df --- /dev/null +++ b/third_party/tileir/scripts/copy_wheel_deps.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +""" +Copy cuda_dep tree into third_party/tileir/backend so the wheel packs it. +Preserves cuda_dep layout (bin/, nvvm/lib64/, etc.) so CUDA_HOME can point at +backend/cuda_dep. Reads wheel_deps.json; ${arch} -> x86 or arm64. +Run before: python -m build --wheel +""" +import json +import os +import platform +import shutil +import sys + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +REPO_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "..", "..")) +BACKEND_DIR = os.path.join(REPO_ROOT, "third_party", "tileir", "backend") + +ARCH = platform.machine() +DEP_ARCH = "x86" if ARCH in ("x86_64", "amd64") else "arm64" if ARCH in ("aarch64", "arm64") else None +if DEP_ARCH is None: + sys.exit(f"Unsupported arch: {ARCH}") + +with open(os.path.join(SCRIPT_DIR, "wheel_deps.json")) as f: + deps = json.load(f) + +for dest_name, path_tpl in deps.get("copy", {}).items(): + src_rel = path_tpl.replace("${arch}", DEP_ARCH) + src = os.path.join(SCRIPT_DIR, src_rel) + dst = os.path.join(BACKEND_DIR, dest_name) + if not os.path.isdir(src): + sys.exit(f"Missing dir: {src}") + if os.path.exists(dst): + shutil.rmtree(dst) + shutil.copytree(src, dst) + # Ensure binaries are executable + bin_dir = os.path.join(dst, "bin") + if os.path.isdir(bin_dir): + for name in os.listdir(bin_dir): + p = os.path.join(bin_dir, name) + if os.path.isfile(p): + os.chmod(p, 0o755) diff --git a/third_party/tileir/scripts/patch_bytecode_utils.sh b/third_party/tileir/scripts/patch_bytecode_utils.sh new file mode 100755 index 0000000000..f669ee8d9c --- /dev/null +++ b/third_party/tileir/scripts/patch_bytecode_utils.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# flagtree: this entire file is a flagtree-local rewrite of the upstream version. +# Upstream's script renames OLDER LLVM API names to NEWER ones (because their LLVM +# is newer than cuda-tile v13.2.0's expected LLVM). flagtree's LLVM is OLDER than +# cuda-tile's expected LLVM, so we need the OPPOSITE direction renames, plus a +# couple extra patches that upstream doesn't have. +# +# Patches cuda-tile sources for compatibility with flagtree's pinned LLVM. +# +# flagtree currently pins LLVM commit f6ded0be (v3.6.0 era, ~Jan 2026), which is +# OLDER than the LLVM commit (13c00cbc) that cuda-tile v13.2.0 was developed against. +# This script renames newer LLVM API references in the cuda-tile sources back to +# their older equivalents so the code compiles against flagtree's LLVM. +# +# All patches are idempotent (sed replace; no-op if pattern absent). Safe to re-run. +set -euo pipefail + +patch_in_place() { + local file="$1"; shift + if [[ ! -f "${file}" ]]; then + echo "[patch] Target file not found: ${file}" >&2 + return 0 + fi + if [[ ! -f "${file}.bak" ]]; then + cp "${file}" "${file}.bak" + fi + local tmpfile="${file}.tmp" + rm -f "${tmpfile}" + sed "$@" "${file}" > "${tmpfile}" && mv "${tmpfile}" "${file}" +} + +ARG_PATH="${1:-${CUDA_TILE_SOURCE_DIR:-}}" +if [[ -z "${ARG_PATH}" ]]; then + echo "[patch] Base directory not provided and CUDA_TILE_SOURCE_DIR unset" >&2 + exit 1 +fi +REPO_ROOT="${ARG_PATH}" +echo "[patch] repo_root=${REPO_ROOT}" + +# 1) Global rename: DenseTypedElementsAttr → DenseIntOrFPElementsAttr +# cuda-tile uses the newer LLVM class name DenseTypedElementsAttr (post-rename). +# flagtree's pinned LLVM still uses the older name DenseIntOrFPElementsAttr. +echo "[patch] Global rename: DenseTypedElementsAttr → DenseIntOrFPElementsAttr" +find "${REPO_ROOT}" -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.td" \) \ + -exec sed -i 's/DenseTypedElementsAttr/DenseIntOrFPElementsAttr/g' {} + + +# 2) Rename llvm::scope_exit → llvm::make_scope_exit +# cuda-tile uses the factory-call style llvm::scope_exit([&]{...}) which in newer +# LLVM is the class template constructor. flagtree's older LLVM only has the +# factory function llvm::make_scope_exit; rename safely because cuda-tile's usage +# is the factory-call form, not the type-declaration form. +BYTECODE_READER_PATH="${REPO_ROOT}/lib/Bytecode/Reader/BytecodeReader.cpp" +if [[ -f "${BYTECODE_READER_PATH}" ]] && grep -q 'llvm::scope_exit' "${BYTECODE_READER_PATH}"; then + echo "[patch] Renaming llvm::scope_exit → llvm::make_scope_exit in BytecodeReader.cpp" + patch_in_place "${BYTECODE_READER_PATH}" -e 's/llvm::scope_exit/llvm::make_scope_exit/g' +fi + +# 3) Convert isValidRawBuffer 2-arg → 3-arg form +# cuda-tile v13.2.0 calls DenseElementsAttr::isValidRawBuffer(tileType, rawData) — 2-arg. +# flagtree's LLVM only has the 3-arg form: isValidRawBuffer(ShapedType, ArrayRef, bool& detectedSplat). +# Inject a local "bool detectedSplat = false" before the call and pass it as the 3rd argument. +if [[ -f "${BYTECODE_READER_PATH}" ]] && grep -q 'isValidRawBuffer(tileType, rawData))' "${BYTECODE_READER_PATH}"; then + echo "[patch] Converting isValidRawBuffer 2-arg → 3-arg in BytecodeReader.cpp" + patch_in_place "${BYTECODE_READER_PATH}" \ + -e 's|// Validate the buffer size and format\.|&\n bool detectedSplat = false;|' \ + -e 's/isValidRawBuffer(tileType, rawData))/isValidRawBuffer(tileType, rawData, detectedSplat))/g' +fi + +echo "[patch] DONE" diff --git a/third_party/tileir/scripts/wheel_deps.json b/third_party/tileir/scripts/wheel_deps.json new file mode 100644 index 0000000000..2e08c66241 --- /dev/null +++ b/third_party/tileir/scripts/wheel_deps.json @@ -0,0 +1,5 @@ +{ + "copy": { + "cuda_dep": "cuda_dep_${arch}" + } +} diff --git a/third_party/tileir/third_party/cuda-tile b/third_party/tileir/third_party/cuda-tile new file mode 160000 index 0000000000..0859212ad1 --- /dev/null +++ b/third_party/tileir/third_party/cuda-tile @@ -0,0 +1 @@ +Subproject commit 0859212ad19f71133a9b940c05323286cbf28a05 diff --git a/third_party/tileir/tools/triton-cuda-tile-opt/CMakeLists.txt b/third_party/tileir/tools/triton-cuda-tile-opt/CMakeLists.txt new file mode 100644 index 0000000000..a3aa447b00 --- /dev/null +++ b/third_party/tileir/tools/triton-cuda-tile-opt/CMakeLists.txt @@ -0,0 +1,23 @@ +get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) +get_property(conversion_libs GLOBAL PROPERTY MLIR_CONVERSION_LIBS) + +add_llvm_executable(triton-cuda-tile-opt triton-cuda-tile-opt.cpp PARTIAL_SOURCES_INTENDED) + +# TODO: what's this? +llvm_update_compile_flags(triton-cuda-tile-opt) +target_link_libraries(triton-cuda-tile-opt PRIVATE + TritonTransforms + TritonTileIRTransforms + TritonToTileIR + TritonNvidiaGPUTransforms + # tests + TritonTestAnalysis + # MLIR core + MLIROptLib + MLIRPass + MLIRRegisterAllDialects + MLIRRegisterAllPasses + MLIRTransforms +) + +mlir_check_all_link_libraries(triton-cuda-tile-opt) diff --git a/third_party/tileir/tools/triton-cuda-tile-opt/RegisterTritonCudaTileDialects.h b/third_party/tileir/tools/triton-cuda-tile-opt/RegisterTritonCudaTileDialects.h new file mode 100644 index 0000000000..13e18f13ec --- /dev/null +++ b/third_party/tileir/tools/triton-cuda-tile-opt/RegisterTritonCudaTileDialects.h @@ -0,0 +1,43 @@ +#pragma once +#include "mlir/Dialect/Bufferization/IR/Bufferization.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/GPU/IR/GPUDialect.h" +#include "mlir/Dialect/LLVMIR/NVVMDialect.h" +#include "mlir/Dialect/Linalg/IR/Linalg.h" +#include "mlir/Dialect/Linalg/Passes.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/Dialect/Ptr/IR/PtrDialect.h" +#include "mlir/Dialect/Tensor/IR/Tensor.h" +#include "mlir/Dialect/UB/IR/UBOps.h" + +#include "Transform/Passes.h" +#include "TritonToTileIR/Passes.h" +#include "cuda_tile/Dialect/CudaTile/IR/Dialect.h" +#include "triton/Dialect/Triton/IR/Dialect.h" + +// clang-format off +#include "mlir/InitAllPasses.h" +// clang-format on + +namespace mlir { +namespace test { +void registerTestAliasPass(); +void registerTestAlignmentPass(); +void registerTestAllocationPass(); +void registerTestMembarPass(); +} // namespace test +} // namespace mlir + +inline void registerTritonCudaTileDialects(mlir::DialectRegistry ®istry) { + mlir::registerAllPasses(); + + mlir::triton::registerConvertTritonToCudaTilePass(); + mlir::triton::registerRewriteAssumeWithCudaTilePass(); + mlir::triton::registerAutoGenMemoryTokenPass(); + registry.insert(); + registry.insert(); +} diff --git a/third_party/tileir/tools/triton-cuda-tile-opt/triton-cuda-tile-opt.cpp b/third_party/tileir/tools/triton-cuda-tile-opt/triton-cuda-tile-opt.cpp new file mode 100644 index 0000000000..0291196a89 --- /dev/null +++ b/third_party/tileir/tools/triton-cuda-tile-opt/triton-cuda-tile-opt.cpp @@ -0,0 +1,14 @@ +#include "mlir/Tools/mlir-opt/MlirOptMain.h" + +#include "RegisterTritonCudaTileDialects.h" +#include "cuda_tile/Dialect/CudaTile/Transforms/Passes.h" + +int main(int argc, char **argv) { + mlir::DialectRegistry registry; + registerTritonCudaTileDialects(registry); + mlir::cuda_tile::registerFuseFMAPass(); + mlir::cuda_tile::registerLoopSplitPass(); + + return mlir::asMainReturnCode(mlir::MlirOptMain( + argc, argv, "Triton-Cuda-Tile test driver\n", registry)); +} diff --git a/third_party/tileir/triton_tileir.cc b/third_party/tileir/triton_tileir.cc new file mode 100644 index 0000000000..442308fa2b --- /dev/null +++ b/third_party/tileir/triton_tileir.cc @@ -0,0 +1,153 @@ +#include "mlir/Bytecode/BytecodeWriter.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" +#include "mlir/Dialect/GPU/IR/GPUDialect.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/Dialect/Index/IR/IndexDialect.h" +#include "mlir/Dialect/Index/IR/IndexOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.h" +#include "mlir/IR/Types.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Verifier.h" +#include "mlir/Parser/Parser.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Support/FileUtilities.h" +#include "mlir/Support/LLVM.h" +#include "mlir/Target/LLVMIR/Dialect/Builtin/BuiltinToLLVMIRTranslation.h" +#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h" +#include "mlir/Target/LLVMIR/Dialect/NVVM/NVVMToLLVMIRTranslation.h" +#include "mlir/Transforms/LocationSnapshot.h" +#include "mlir/Transforms/Passes.h" +#include "mlir/Conversion/Passes.h" + +#include "llvm/ADT/SmallVector.h" +#include "llvm/IR/Constants.h" +#include "llvm/Support/TargetSelect.h" + +#include "Transform/Passes.h" +#include "TritonToTileIR/Passes.h" +#include "Utils/Utils.h" +#include "cuda_tile/Bytecode/Writer/BytecodeWriter.h" +#include "cuda_tile/Dialect/CudaTile/IR/Dialect.h" +#include "cuda_tile/Dialect/CudaTile/IR/Ops.h" +#include "cuda_tile/Dialect/CudaTile/IR/Types.h" +#include "cuda_tile/Dialect/CudaTile/Transforms/Passes.h" +#include "passes.h" +#include "ir.h" +#include "triton/Analysis/Allocation.h" +#include "triton/Dialect/Triton/IR/Dialect.h" +#include "triton/Dialect/Triton/IR/Types.h" +#include "triton/Dialect/Triton/IR/Utility.h" +#include "triton/Dialect/Triton/Transforms/Passes.h" +#include "triton/Tools/Sys/GetEnv.hpp" +#include +#include +#include +#include "triton/Dialect/Triton/IR/Types.h" + +namespace py = pybind11; +using namespace mlir; +using namespace triton; + +void init_triton_to_cudatile_passes(py::module &&m) { + using namespace mlir::triton; + // TODO: it is weird to pass mlir::triton::NVVM here since the conversion is + // nvidia-specificontext + m.def("add_triton_to_cudatile", [](mlir::PassManager &pm, bool approx, + bool ftz, int capability, int num_ctas, + int simt_num_warps, int occupancy, std::optional num_stages) { + pm.addPass(mlir::triton::createConvertTritonToCudaTilePass( + approx, ftz, capability, num_ctas, simt_num_warps, occupancy, num_stages)); + }); + m.def("add_fma_fusion", [](mlir::PassManager &pm) { + // Add FMA fusion pass to cuda tile entry operations + auto &mpm = pm.nest(); + auto &epm = mpm.nest(); + epm.addPass(cuda_tile::createFuseFMAPass()); + }); + m.def("add_loop_split", [](mlir::PassManager &pm, int threshold = 1) { + // Add Loop Split pass to cuda tile entry operations + auto &mpm = pm.nest(); + auto &epm = mpm.nest(); + epm.addPass(cuda_tile::createLoopSplitPass({threshold})); + }); + m.def("add_lift_tt_cf_to_scf", [](mlir::PassManager &pm) { + pm.addPass(mlir::triton::createLiftTTCFToSCFPass()); + }); + m.def("add_strip_debuginfo", [](mlir::PassManager &pm) { + // Strip debug info + auto &mpm = pm.nest(); + mpm.addPass(mlir::createStripDebugInfoPass()); + }); + m.def("add_synthesize_debug_info_scopes", [](mlir::PassManager &pm) { + // Synthesize scoped debug info + auto &mpm = pm.nest(); + mpm.addPass(cuda_tile::createSynthesizeDebugInfoScopesPass()); + }); + m.def("add_rewrite_tensor_pointers_to_ldst", [](mlir::PassManager &pm) { + pm.addPass(mlir::triton::createTritonRewriteTensorPointer()); + }); + m.def("add_assume_to_tileir", [](mlir::PassManager &pm) { + pm.addPass(mlir::triton::createRewriteAssumeWithCudaTilePass()); + }); + m.def("add_auto_gen_memtoken", [](mlir::PassManager &pm, + bool enable_autogen_alias_mem_token + ) { + pm.addPass(mlir::triton::createAutoGenMemoryTokenPass(enable_autogen_alias_mem_token)); + }); +} + +void init_triton_tileir(py::module &&m) { + init_triton_to_cudatile_passes(m.def_submodule("passes")); + // load dialects + m.def("load_dialects", [](mlir::MLIRContext &context) { + mlir::DialectRegistry registry; + registry.insert(); + registry.insert(); + registry.insert(); + context.appendDialectRegistry(registry); + context.loadAllAvailableDialects(); + + // Register cuda_tile passes to enable nested pass manager parsing + cuda_tile::registerCudaTilePasses(); + }); + m.def("only_contain_legal_dialects", [](mlir::ModuleOp mod) { + bool only_contain_legal_dialects = true; + mod->walk([&](mlir::Operation *op) { + if (!llvm::isa(op) && + (op->getName().getDialectNamespace() != + mlir::cuda_tile::CudaTileDialect::getDialectNamespace() + )) { + only_contain_legal_dialects = false; + } + }); + return only_contain_legal_dialects; + }); + m.def("write_bytecode", [](mlir::ModuleOp mod) { + // Find the cuda_tile::ModuleOp within the mlir::ModuleOp. + cuda_tile::ModuleOp cudaTileModule; + if (!mod.getBody()->empty()) + if (auto nestedCudaTileModule = + dyn_cast(&mod.getBody()->front())) + cudaTileModule = nestedCudaTileModule; + + if (!cudaTileModule) + throw std::runtime_error( + "No cuda_tile::ModuleOp found in the input module"); + + std::string buffer; + llvm::raw_string_ostream ostream(buffer); + if (failed(cuda_tile::writeBytecode( + ostream, cudaTileModule, + cuda_tile::BytecodeVersion::kCurrentVersion))) + throw std::runtime_error("Failed to write cuda_tile bytecode"); + py::bytes bytes(buffer.data(), buffer.size()); + return bytes; + }); +} From 6ce1c8777c36c15a32097427bef19b7517d8f329 Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Wed, 10 Jun 2026 03:22:31 -0700 Subject: [PATCH 02/11] [tileir] Add per-kernel TileIR routing --- python/triton/_flagtree/__init__.py | 2 + python/triton/_flagtree/tileir_router.py | 143 +++++++++++++++++++++++ python/triton/runtime/jit.py | 4 + 3 files changed, 149 insertions(+) create mode 100644 python/triton/_flagtree/__init__.py create mode 100644 python/triton/_flagtree/tileir_router.py diff --git a/python/triton/_flagtree/__init__.py b/python/triton/_flagtree/__init__.py new file mode 100644 index 0000000000..b86cbf5dc2 --- /dev/null +++ b/python/triton/_flagtree/__init__.py @@ -0,0 +1,2 @@ +# flagtree-specific runtime extensions to triton. +# Currently contains the TileIR routing layer. diff --git a/python/triton/_flagtree/tileir_router.py b/python/triton/_flagtree/tileir_router.py new file mode 100644 index 0000000000..98517bbfcc --- /dev/null +++ b/python/triton/_flagtree/tileir_router.py @@ -0,0 +1,143 @@ +"""Per-kernel routing between the nvidia backend and the tileir backend. + +Activation: + FLAGTREE_USE_TILEIR=1 — opt non-TLE kernels into the TileIR compile path. + Kernels that use TLE features automatically fall back + to the nvidia backend. + FLAGTREE_TILEIR_VERBOSE=1 — print routing decisions to stderr for debugging. + +When FLAGTREE_USE_TILEIR is unset (the default), the current target is returned +unchanged and flagtree behaves exactly as before. +""" +from __future__ import annotations + +import ast +import inspect +import os +import sys +from enum import Enum + +from triton.backends.compiler import GPUTarget + + +_NVIDIA_BACKENDS = ("cuda", "nvidia") + + +class TLEUsage(Enum): + NONE = "none" + OTHER = "other" + UNKNOWN = "unknown" + + +def _env_use_tileir() -> bool: + return os.environ.get("FLAGTREE_USE_TILEIR", "0") == "1" + + +def _env_verbose() -> bool: + return os.environ.get("FLAGTREE_TILEIR_VERBOSE", "0") == "1" + + +def _log(msg: str) -> None: + if _env_verbose(): + print(f"[flagtree] {msg}", file=sys.stderr, flush=True) + + +def _kernel_name(jit_fn) -> str: + fn = getattr(jit_fn, "fn", jit_fn) + return getattr(fn, "__name__", repr(fn)) + + +def kernel_tle_usage(jit_fn) -> TLEUsage: + """Heuristic detection of TLE usage in a JIT'd function's source. + + Looks for any of: + - ``tle.`` attribute access (covers ``tle.extract_tile``, ``tle.gpu.alloc``, etc.) + - ``from triton.experimental.tle import ...`` + - ``from triton.experimental import tle`` + - ``import triton.experimental.tle`` (with or without ``as``) + + Result is cached on the JITFunction instance via ``_flagtree_tle_usage``. + """ + cached = getattr(jit_fn, "_flagtree_tle_usage", None) + if cached is not None: + return cached + + fn = getattr(jit_fn, "fn", jit_fn) + try: + src = inspect.getsource(fn) + except (OSError, TypeError): + # Can't prove this kernel is TLE-free. + result = TLEUsage.UNKNOWN + else: + result = _scan_source_for_tle(src) + + jit_fn._flagtree_tle_usage = result + return result + + +def kernel_uses_tle(jit_fn) -> bool: + """Backward-compatible boolean predicate for callers/tests.""" + return kernel_tle_usage(jit_fn) is TLEUsage.OTHER + + +def _scan_source_for_tle(src: str) -> TLEUsage: + if "tle" not in src: + return TLEUsage.NONE + try: + tree = ast.parse(src) + except SyntaxError: + # `inspect.getsource` of a decorated function may include the decorator; + # if ast can't parse, fall back to a substring check. + return TLEUsage.OTHER if any(pat in src for pat in ( + "tle.", "from triton.experimental import tle", + "from triton.experimental.tle", "import triton.experimental.tle", + )) else TLEUsage.NONE + + for node in ast.walk(tree): + if isinstance(node, ast.Attribute): + base = node + while isinstance(base, ast.Attribute): + base = base.value + if isinstance(base, ast.Name) and base.id == "tle": + return TLEUsage.OTHER + elif isinstance(node, ast.ImportFrom): + mod = node.module or "" + if mod.startswith("triton.experimental.tle"): + return TLEUsage.OTHER + if mod == "triton.experimental" and any(n.name == "tle" for n in node.names): + return TLEUsage.OTHER + elif isinstance(node, ast.Import): + for n in node.names: + if n.name and n.name.startswith("triton.experimental.tle"): + return TLEUsage.OTHER + return TLEUsage.NONE + + +def pick_target_for_kernel(target: GPUTarget, jit_fn) -> GPUTarget: + """Return either ``target`` unchanged or a TileIR-flavored variant. + + Routing matrix: + backend not in {cuda, nvidia} → unchanged (e.g. AMD) + FLAGTREE_USE_TILEIR not set → unchanged (no behavior change vs today) + env set + kernel uses TLE → unchanged + warning + env set + unknown source → unchanged + warning + env set + no TLE → swap backend to "tileir" + """ + if target.backend not in _NVIDIA_BACKENDS: + return target + if not _env_use_tileir(): + return target + + name = _kernel_name(jit_fn) + tle_usage = kernel_tle_usage(jit_fn) + if tle_usage is TLEUsage.OTHER: + _log(f"kernel {name}: TLE detected, falling back to nvidia " + f"(FLAGTREE_USE_TILEIR=1 is set but this TLE subset is incompatible with TileIR)") + return target + if tle_usage is TLEUsage.UNKNOWN: + _log(f"kernel {name}: source unavailable, falling back to nvidia " + f"(cannot prove TLE usage is TileIR-compatible)") + return target + + _log(f"kernel {name}: routed via tileir (no TLE, FLAGTREE_USE_TILEIR=1)") + return GPUTarget(backend="tileir", arch=target.arch, warp_size=target.warp_size) diff --git a/python/triton/runtime/jit.py b/python/triton/runtime/jit.py index 33b98fd12c..b742a063b3 100644 --- a/python/triton/runtime/jit.py +++ b/python/triton/runtime/jit.py @@ -669,7 +669,11 @@ def create_binder(self): Precompute as much as possible. """ from ..compiler import CompiledKernel, compile, ASTSource, make_backend + from .._flagtree.tileir_router import pick_target_for_kernel target = driver.active.get_current_target() + # flagtree: opt compatible kernels into the tileir backend when + # FLAGTREE_USE_TILEIR=1 is set. No-op otherwise. + target = pick_target_for_kernel(target, self) backend = make_backend(target) self.CompiledKernel = CompiledKernel self.compile = compile From 09b9f8f3227cbe3c6cb8f151ed4d43efe699fa7d Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Wed, 10 Jun 2026 03:25:36 -0700 Subject: [PATCH 03/11] [tileir] Support TLE load-view token ops --- python/triton/_flagtree/tileir_router.py | 170 ++++++++-- .../experimental/tle/language/gpu/__init__.py | 2 + .../experimental/tle/language/gpu/tile.py | 22 ++ python/triton/language/__init__.py | 2 + third_party/tileir/backend/code_generator.py | 3 + third_party/tileir/backend/extend_core.py | 311 ++++++++++++++++++ third_party/tileir/backend/extend_semantic.py | 154 +++++++++ .../scripts/build_helper/Dockerfile.release | 2 +- third_party/tileir/triton_tileir.cc | 222 +++++++++++++ 9 files changed, 857 insertions(+), 31 deletions(-) create mode 100644 python/triton/experimental/tle/language/gpu/tile.py create mode 100644 third_party/tileir/backend/extend_core.py create mode 100644 third_party/tileir/backend/extend_semantic.py diff --git a/python/triton/_flagtree/tileir_router.py b/python/triton/_flagtree/tileir_router.py index 98517bbfcc..16f8215959 100644 --- a/python/triton/_flagtree/tileir_router.py +++ b/python/triton/_flagtree/tileir_router.py @@ -2,7 +2,8 @@ Activation: FLAGTREE_USE_TILEIR=1 — opt non-TLE kernels into the TileIR compile path. - Kernels that use TLE features automatically fall back + Kernels that only use the supported tle.gpu.tile + subset also use TileIR; other TLE kernels fall back to the nvidia backend. FLAGTREE_TILEIR_VERBOSE=1 — print routing decisions to stderr for debugging. @@ -16,15 +17,29 @@ import os import sys from enum import Enum +from types import ModuleType from triton.backends.compiler import GPUTarget _NVIDIA_BACKENDS = ("cuda", "nvidia") +_TILEIR_EXT_MODULE = "triton.backends.tileir.extend_core" +_TLE_TILE_MODULE = "triton.experimental.tle.language.gpu.tile" +_TILEIR_SUPPORTED_TLE_TILE_ATTRS = frozenset({ + "make_tensor_view", + "make_partition_view", + "make_view", + "dim", + "load_view_tko", + "store_view_tko", + "create_mem_token", + "join_mem_tokens", +}) class TLEUsage(Enum): NONE = "none" + TILEIR_SUBSET = "tileir_subset" OTHER = "other" UNKNOWN = "unknown" @@ -50,11 +65,9 @@ def _kernel_name(jit_fn) -> str: def kernel_tle_usage(jit_fn) -> TLEUsage: """Heuristic detection of TLE usage in a JIT'd function's source. - Looks for any of: - - ``tle.`` attribute access (covers ``tle.extract_tile``, ``tle.gpu.alloc``, etc.) - - ``from triton.experimental.tle import ...`` - - ``from triton.experimental import tle`` - - ``import triton.experimental.tle`` (with or without ``as``) + ``TLEUsage.TILEIR_SUBSET`` means every detected TLE operation is under + ``tle.gpu.tile`` and belongs to the TileIR-supported TKO view/token subset. + Any other TLE usage keeps the kernel on the native NVIDIA path. Result is cached on the JITFunction instance via ``_flagtree_tle_usage``. """ @@ -69,7 +82,7 @@ def kernel_tle_usage(jit_fn) -> TLEUsage: # Can't prove this kernel is TLE-free. result = TLEUsage.UNKNOWN else: - result = _scan_source_for_tle(src) + result = _scan_source_for_tle(src, getattr(fn, "__globals__", None)) jit_fn._flagtree_tle_usage = result return result @@ -77,39 +90,132 @@ def kernel_tle_usage(jit_fn) -> TLEUsage: def kernel_uses_tle(jit_fn) -> bool: """Backward-compatible boolean predicate for callers/tests.""" - return kernel_tle_usage(jit_fn) is TLEUsage.OTHER - - -def _scan_source_for_tle(src: str) -> TLEUsage: - if "tle" not in src: + return kernel_tle_usage(jit_fn) in (TLEUsage.TILEIR_SUBSET, TLEUsage.OTHER) + + +def _global_obj_kind(obj) -> str | None: + if isinstance(obj, ModuleType): + module_name = getattr(obj, "__name__", "") + if module_name == _TLE_TILE_MODULE: + return "tle_tile_module" + if module_name == "triton.experimental.tle.language": + return "tle_language_module" + if module_name.startswith("triton.experimental.tle."): + return "tle_other_module" + return None + + obj_module = getattr(obj, "__module__", "") + obj_name = getattr(obj, "__name__", "") + if obj_module == _TILEIR_EXT_MODULE and obj_name in _TILEIR_SUPPORTED_TLE_TILE_ATTRS: + return "supported_function" + if obj_module.startswith(_TLE_TILE_MODULE): + if obj_name in _TILEIR_SUPPORTED_TLE_TILE_ATTRS: + return "supported_function" + return "unsupported_function" + if obj_module.startswith("triton.experimental.tle."): + return "unsupported_function" + return None + + +def _scan_source_for_tle(src: str, global_ns=None) -> TLEUsage: + if "tle" not in src and not global_ns: return TLEUsage.NONE try: tree = ast.parse(src) except SyntaxError: # `inspect.getsource` of a decorated function may include the decorator; # if ast can't parse, fall back to a substring check. - return TLEUsage.OTHER if any(pat in src for pat in ( - "tle.", "from triton.experimental import tle", - "from triton.experimental.tle", "import triton.experimental.tle", - )) else TLEUsage.NONE + if any(f"tile.{name}" in src or f"tle.gpu.tile.{name}" in src for name in _TILEIR_SUPPORTED_TLE_TILE_ATTRS): + return TLEUsage.TILEIR_SUBSET + return TLEUsage.OTHER if "tle." in src or "triton.experimental.tle" in src else TLEUsage.NONE + + tle_aliases = {"tle"} + tile_module_aliases = set() + imported_supported = False + imported_other = False for node in ast.walk(tree): - if isinstance(node, ast.Attribute): - base = node - while isinstance(base, ast.Attribute): - base = base.value - if isinstance(base, ast.Name) and base.id == "tle": - return TLEUsage.OTHER - elif isinstance(node, ast.ImportFrom): + if isinstance(node, ast.ImportFrom): mod = node.module or "" - if mod.startswith("triton.experimental.tle"): - return TLEUsage.OTHER if mod == "triton.experimental" and any(n.name == "tle" for n in node.names): - return TLEUsage.OTHER + for n in node.names: + if n.name == "tle": + tle_aliases.add(n.asname or n.name) + elif mod == "triton.experimental.tle.language.gpu" and any(n.name == "tile" for n in node.names): + for n in node.names: + if n.name == "tile": + tile_module_aliases.add(n.asname or n.name) + elif n.name == "*": + imported_other = True + else: + imported_other = True + elif mod == _TLE_TILE_MODULE: + for n in node.names: + if n.name == "*": + imported_other = True + elif n.name in _TILEIR_SUPPORTED_TLE_TILE_ATTRS: + imported_supported = True + else: + imported_other = True + elif mod.startswith("triton.experimental.tle"): + imported_other = True elif isinstance(node, ast.Import): for n in node.names: - if n.name and n.name.startswith("triton.experimental.tle"): - return TLEUsage.OTHER + if n.name == "triton.experimental.tle.language.gpu.tile": + if n.asname: + tile_module_aliases.add(n.asname) + else: + imported_other = True + elif n.name == "triton.experimental.tle.language": + if n.asname: + tle_aliases.add(n.asname) + else: + imported_other = True + elif n.name and n.name.startswith("triton.experimental.tle"): + imported_other = True + + saw_supported = imported_supported + saw_other = imported_other + + def classify_attribute(func): + attrs = [] + base = func + while isinstance(base, ast.Attribute): + attrs.append(base.attr) + base = base.value + if not isinstance(base, ast.Name): + return None + attrs.reverse() + kind = _global_obj_kind(global_ns.get(base.id)) if global_ns else None + is_tle_language = base.id in tle_aliases or kind == "tle_language_module" + is_tile_module = base.id in tile_module_aliases or kind == "tle_tile_module" + is_tle_other_module = kind == "tle_other_module" + if is_tle_language and len(attrs) == 3 and attrs[:2] == ["gpu", "tile"]: + return "supported" if attrs[2] in _TILEIR_SUPPORTED_TLE_TILE_ATTRS else "other" + if is_tile_module and len(attrs) == 1: + return "supported" if attrs[0] in _TILEIR_SUPPORTED_TLE_TILE_ATTRS else "other" + if is_tle_language or is_tile_module or is_tle_other_module: + return "other" + return None + + for node in ast.walk(tree): + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name): + kind = _global_obj_kind(global_ns.get(node.func.id)) if global_ns else None + if kind == "supported_function": + saw_supported = True + elif kind in ("unsupported_function", "tle_other_module"): + saw_other = True + elif isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute): + kind = classify_attribute(node.func) + if kind == "supported": + saw_supported = True + elif kind == "other": + saw_other = True + + if saw_other: + return TLEUsage.OTHER + if saw_supported: + return TLEUsage.TILEIR_SUBSET return TLEUsage.NONE @@ -119,7 +225,8 @@ def pick_target_for_kernel(target: GPUTarget, jit_fn) -> GPUTarget: Routing matrix: backend not in {cuda, nvidia} → unchanged (e.g. AMD) FLAGTREE_USE_TILEIR not set → unchanged (no behavior change vs today) - env set + kernel uses TLE → unchanged + warning + env set + unsupported TLE → unchanged + warning + env set + TileIR TLE subset → swap backend to "tileir" env set + unknown source → unchanged + warning env set + no TLE → swap backend to "tileir" """ @@ -139,5 +246,8 @@ def pick_target_for_kernel(target: GPUTarget, jit_fn) -> GPUTarget: f"(cannot prove TLE usage is TileIR-compatible)") return target - _log(f"kernel {name}: routed via tileir (no TLE, FLAGTREE_USE_TILEIR=1)") + if tle_usage is TLEUsage.TILEIR_SUBSET: + _log(f"kernel {name}: routed via tileir (TileIR-supported TLE subset, FLAGTREE_USE_TILEIR=1)") + else: + _log(f"kernel {name}: routed via tileir (no TLE, FLAGTREE_USE_TILEIR=1)") return GPUTarget(backend="tileir", arch=target.arch, warp_size=target.warp_size) diff --git a/python/triton/experimental/tle/language/gpu/__init__.py b/python/triton/experimental/tle/language/gpu/__init__.py index 939adb04f2..a3ef45df6e 100644 --- a/python/triton/experimental/tle/language/gpu/__init__.py +++ b/python/triton/experimental/tle/language/gpu/__init__.py @@ -9,6 +9,7 @@ ) from .types import (layout, shared_layout, swizzled_shared_layout, tensor_memory_layout, nv_mma_shared_layout, scope, buffered_tensor, buffered_tensor_type, smem, tmem) +from . import tile # Backward-compat alias expected by existing tests/tutorials. storage_kind = memory_space @@ -31,4 +32,5 @@ "buffered_tensor_type", "smem", "tmem", + "tile", ] diff --git a/python/triton/experimental/tle/language/gpu/tile.py b/python/triton/experimental/tle/language/gpu/tile.py new file mode 100644 index 0000000000..c128496cce --- /dev/null +++ b/python/triton/experimental/tle/language/gpu/tile.py @@ -0,0 +1,22 @@ +# flagtree tle +from triton.language import ext as _tileir_ext + +create_mem_token = _tileir_ext.create_mem_token +dim = _tileir_ext.dim +join_mem_tokens = _tileir_ext.join_mem_tokens +load_view_tko = _tileir_ext.load_view_tko +make_partition_view = _tileir_ext.make_partition_view +make_tensor_view = _tileir_ext.make_tensor_view +make_view = _tileir_ext.make_view +store_view_tko = _tileir_ext.store_view_tko + +__all__ = [ + "make_tensor_view", + "make_partition_view", + "make_view", + "dim", + "load_view_tko", + "store_view_tko", + "create_mem_token", + "join_mem_tokens", +] diff --git a/python/triton/language/__init__.py b/python/triton/language/__init__.py index 04f80d4f72..1addf84208 100644 --- a/python/triton/language/__init__.py +++ b/python/triton/language/__init__.py @@ -3,6 +3,7 @@ from . import math from . import extra +from ..backends.tileir import extend_core as ext from .standard import ( argmax, argmin, @@ -191,6 +192,7 @@ "exp2", "expand_dims", "extra", + "ext", "fdiv", "flip", "float16", diff --git a/third_party/tileir/backend/code_generator.py b/third_party/tileir/backend/code_generator.py index e89dead9ae..c5f4647f42 100644 --- a/third_party/tileir/backend/code_generator.py +++ b/third_party/tileir/backend/code_generator.py @@ -34,6 +34,7 @@ ) from triton.backends.tileir.conf import TileIREnvConf +from triton.backends.tileir.extend_semantic import CudaTileSemantic def mangle_fn(name, arg_tys, caller_context): # doesn't mangle ret type, which must be a function of arg tys @@ -101,6 +102,8 @@ def __init__( file_name=file_name, begin_line=begin_line, ) + self.semantic = CudaTileSemantic(self.builder) + def get_used_vars(self, stmt): used_vars = dict() for node in ast.walk(stmt): diff --git a/third_party/tileir/backend/extend_core.py b/third_party/tileir/backend/extend_core.py new file mode 100644 index 0000000000..949af2e622 --- /dev/null +++ b/third_party/tileir/backend/extend_core.py @@ -0,0 +1,311 @@ +import os +import re + +from triton.language import core as tl + + +MLIR_DYNAMIC = -9223372036854775808 + + +def _safe_mangle(prefix, text): + return prefix + re.sub(r"[^0-9A-Za-z_]+", "_", str(text)) + + +class mem_token_type(tl.dtype): + + def __init__(self): + self.name = "mem_token" + + def to_ir(self, builder): + return builder.get_tileir_mem_token_ty() + + def __str__(self): + return self.name + + __repr__ = __str__ + + def mangle(self): + return "MTmem_token" + + def is_ptr(self): + return False + + def __eq__(self, other): + return isinstance(other, mem_token_type) + + +class tensor_view_type(tl.dtype): + + def __init__(self, element_ty, shape, stride): + if not isinstance(element_ty, tl.dtype): + raise TypeError(f"element_ty is a {type(element_ty).__name__}.") + if len(shape) != len(stride): + raise ValueError("tensor_view_type expects shape and stride to have the same rank") + self.element_ty = element_ty + self.shape = shape + self.stride = stride + shape_text = [x if x != MLIR_DYNAMIC else "?" for x in shape] + stride_text = [x if x != MLIR_DYNAMIC else "?" for x in stride] + if shape_text: + self.name = f"tensor_view<{'x'.join(map(str, shape_text))}x{element_ty}, {stride_text}>" + else: + self.name = f"tensor_view<{element_ty}>" + + def to_ir(self, builder): + return builder.get_tensor_view_ty( + self.element_ty.to_ir(builder), + self.shape, + self.stride, + ) + + def __str__(self): + return self.name + + __repr__ = __str__ + + def mangle(self): + return _safe_mangle("TV", self.name) + + def is_ptr(self): + return True + + def __eq__(self, other): + return ( + isinstance(other, tensor_view_type) + and self.element_ty == other.element_ty + and self.shape == other.shape + and self.stride == other.stride + ) + + +class partition_view_type(tl.dtype): + + def __init__(self, tensor_view, element_ty, tile_shape, tile_dim_map): + self.ir_type = None + self.tensor_view = tensor_view + self.element_ty = element_ty + self.tile_shape = tile_shape + self.tile_dim_map = tile_dim_map + self.name = "partition_view" + + def to_ir(self, builder): + if self.ir_type is None: + raise RuntimeError("partition_view_type IR type is only available after make_partition_view") + return self.ir_type + + def __str__(self): + return self.name + + __repr__ = __str__ + + def mangle(self): + return _safe_mangle("PV", f"{self.tile_shape}_{self.tile_dim_map}_{self.element_ty}") + + def is_ptr(self): + return False + + def __eq__(self, other): + return ( + isinstance(other, partition_view_type) + and self.tensor_view == other.tensor_view + and self.element_ty == other.element_ty + and self.tile_shape == other.tile_shape + and self.tile_dim_map == other.tile_dim_map + ) + + +def _require_tileir_builder(_semantic, op_name): + if os.environ.get("FLAGTREE_COMPILING_TILEIR", "0") != "1": + raise NotImplementedError( + f"tl.ext.{op_name} is only supported by FlagTree's TileIR path; " + "set FLAGTREE_USE_TILEIR=1 for kernels that only use TileIR TKO view ops" + ) + semantic_op_name = "create_mem_token" if op_name == "mem_token" else op_name + if _semantic is None or not hasattr(_semantic, semantic_op_name): + raise NotImplementedError( + f"tl.ext.{op_name} requires TileIR view/token semantic bindings; rebuild flagtree" + ) + + +def _unwrap_list(values): + return [tl._unwrap_if_constexpr(x) for x in values] + + +@tl.builtin +def extract_slice(input, offsets, tiles, _semantic=None): + """Compatibility shim for nv-Triton tl.ext.extract_slice. + + This currently supports the Ocean FFT pattern that slices one element from + the last dimension of a tensor whose last dimension is size 2. It maps to + Triton's existing split operation, which removes that last dimension. + """ + offsets = [tl._unwrap_if_constexpr(x) for x in offsets] + tiles = [tl._unwrap_if_constexpr(x) for x in tiles] + input_shape = [tl._unwrap_if_constexpr(x) for x in input.shape] + if len(offsets) != len(input_shape) or len(tiles) != len(input_shape): + raise ValueError("tl.ext.extract_slice expects rank-matched offsets and tiles") + if offsets[:-1] != [0] * (len(offsets) - 1): + raise NotImplementedError("tl.ext.extract_slice only supports leading zero offsets") + if tiles[:-1] != input_shape[:-1] or tiles[-1] != 1: + raise NotImplementedError("tl.ext.extract_slice only supports full leading tiles and last tile 1") + if input_shape[-1] != 2: + raise NotImplementedError("tl.ext.extract_slice only supports last dimension size 2") + if offsets[-1] == 0: + return tl.split(input, _semantic=_semantic)[0] + if offsets[-1] == 1: + return tl.split(input, _semantic=_semantic)[1] + raise NotImplementedError("tl.ext.extract_slice only supports last offset 0 or 1") + + +@tl.builtin +def cat(input, other, can_reorder=False, dim=-1, _semantic=None): + """Compatibility shim for nv-Triton tl.ext.cat on the last dimension.""" + dim = tl._unwrap_if_constexpr(dim) + if dim not in (-1, len(input.shape)): + raise NotImplementedError("tl.ext.cat currently only supports appending a last dimension") + return tl.join(input, other, _semantic=_semantic) + + +def _unsupported(name): + def fn(*args, **kwargs): + raise NotImplementedError(f"tl.ext.{name} is not supported by FlagTree's TileIR frontend yet") + + return fn + + +@tl.builtin +def make_tensor_view(base, shapes, strides, _semantic=None): + _require_tileir_builder(_semantic, "make_tensor_view") + if not base.dtype.is_ptr(): + raise TypeError("tl.ext.make_tensor_view base must be a pointer") + + shapes_ty = [x.value if isinstance(x, tl.constexpr) else MLIR_DYNAMIC for x in shapes] + strides_ty = [x.value if isinstance(x, tl.constexpr) else MLIR_DYNAMIC for x in strides] + shapes_val = [x for x in shapes if not isinstance(x, tl.constexpr)] + strides_val = [x for x in strides if not isinstance(x, tl.constexpr)] + res_type = tensor_view_type(base.dtype.element_ty, shapes_ty, strides_ty) + return _semantic.make_tensor_view(res_type, base, shapes_val, strides_val) + + +@tl.builtin +def make_partition_view(src_tensor_view, tile_shape, tile_dim_map, pad_val="zero", _semantic=None): + _require_tileir_builder(_semantic, "make_partition_view") + pad_val = tl._unwrap_if_constexpr(pad_val) + tile_shape = _unwrap_list(tile_shape) + tile_dim_map = _unwrap_list(tile_dim_map) + res_type = partition_view_type( + src_tensor_view.handle, + src_tensor_view.dtype.element_ty, + tile_shape, + tile_dim_map, + ) + return _semantic.make_partition_view(src_tensor_view, tile_shape, tile_dim_map, res_type, pad_val) + + +@tl.builtin +def make_view(base, shapes, strides, tile_shape, tile_dim_map, traversal_strides=None, pad_val="zero", _semantic=None): + if traversal_strides is not None: + raise NotImplementedError("tl.ext.make_view traversal_strides is outside FlagTree's TKO-load MVP") + if len(tile_shape) != len(tile_dim_map): + raise ValueError("tl.ext.make_view expects tile_shape and tile_dim_map to have the same rank") + if len(tile_dim_map) != len(shapes): + raise ValueError("tl.ext.make_view expects every source dimension to appear in tile_dim_map") + tile_dim_map = _unwrap_list(tile_dim_map) + if len(set(tile_dim_map)) != len(shapes): + raise ValueError("tl.ext.make_view expects every source dimension to appear exactly once") + tensor_view = make_tensor_view(base, shapes, strides, _semantic=_semantic) + return make_partition_view(tensor_view, tile_shape, tile_dim_map, pad_val, _semantic=_semantic) + + +@tl.builtin +def load_view_tko( + src, + coords, + memToken=None, + semantics="weak", + scope="", + has_result_token=False, + cost=-1, + _semantic=None, +): + _require_tileir_builder(_semantic, "load_view_tko") + has_result_token = bool(tl._unwrap_if_constexpr(has_result_token)) + return _semantic.load_view_tko( + src, + coords, + memToken, + semantics, + scope, + mem_token_type(), + has_result_token, + int(tl._unwrap_if_constexpr(cost)), + ) + + +@tl.builtin +def store_view_tko( + dst, + value, + coords, + memToken=None, + semantics="weak", + scope="", + has_result_token=False, + cost=-1, + allow_tma=None, + _semantic=None, +): + _require_tileir_builder(_semantic, "store_view_tko") + has_result_token = bool(tl._unwrap_if_constexpr(has_result_token)) + if allow_tma is not None: + raise NotImplementedError("tl.ext.store_view_tko allow_tma is outside FlagTree's TKO-load MVP") + value = _semantic.to_tensor(value) + return _semantic.store_view_tko( + dst, + value, + coords, + memToken, + semantics, + scope, + mem_token_type(), + has_result_token, + int(tl._unwrap_if_constexpr(cost)), + ) + + +@tl.builtin +def dim(src, index, _semantic=None): + _require_tileir_builder(_semantic, "dim") + index = tl._unwrap_if_constexpr(index) + return _semantic.dim(src, index) + + +@tl.builtin +def create_mem_token(_semantic=None): + _require_tileir_builder(_semantic, "mem_token") + return tl.tensor(_semantic.create_mem_token(), mem_token_type()) + + +@tl.builtin +def join_mem_tokens(*tokens, _semantic=None): + _require_tileir_builder(_semantic, "join_mem_tokens") + if len(tokens) == 0: + return create_mem_token(_semantic=_semantic) + result = tokens[0] + if not isinstance(result.type, mem_token_type): + raise TypeError(f"{result} is not a mem_token_type") + for token in tokens[1:]: + if not isinstance(token.type, mem_token_type): + raise TypeError(f"{token} is not a mem_token_type") + result = tl.tensor( + _semantic.join_mem_tokens(result, token), + mem_token_type(), + ) + return result + + +load = _unsupported("load") +store = _unsupported("store") +atomic_cas = _unsupported("atomic_cas") +atomic_xchg = _unsupported("atomic_xchg") +tileir_tiled_atomic_rmw = _unsupported("tileir_tiled_atomic_rmw") diff --git a/third_party/tileir/backend/extend_semantic.py b/third_party/tileir/backend/extend_semantic.py new file mode 100644 index 0000000000..0376096903 --- /dev/null +++ b/third_party/tileir/backend/extend_semantic.py @@ -0,0 +1,154 @@ +from triton.language import core as tl +from triton.language.semantic import TritonSemantic + + +def _str_to_tileir_semantic(semantic_option): + semantic_option = tl._unwrap_if_constexpr(semantic_option) + semantic_list = ["weak", "relaxed", "acquire", "release", "acq_rel"] + if semantic_option not in semantic_list: + raise ValueError(f"semantic must be in {semantic_list}, but got {semantic_option}") + return semantic_list.index(semantic_option) + + +def _str_to_tileir_scope(scope_option): + scope_option = tl._unwrap_if_constexpr(scope_option) + scope_list = ["tl_blk", "device", "sys"] + if scope_option in scope_list: + return scope_list.index(scope_option) + return -1 + + +def _check_memory_ordering(semantics, scope): + if semantics > 0 and scope < 0: + raise ValueError("scope must be tl_blk, device, or sys when semantics is not weak") + if semantics == 0 and scope > 0: + raise ValueError("scope should not be set when semantics is weak") + + +def _check_load_view_ordering(semantics): + if semantics not in (0, 1, 2): + raise ValueError("tl.ext.load_view_tko semantics must be one of ['weak', 'relaxed', 'acquire']") + + +def _check_store_view_ordering(semantics): + if semantics not in (0, 1, 3): + raise ValueError("tl.ext.store_view_tko semantics must be one of ['weak', 'relaxed', 'release']") + + +def _str_to_ele_ty(dtype): + return { + "f16": tl.float16, + "f32": tl.float32, + "f64": tl.float64, + "i1": tl.int1, + "i8": tl.int8, + "i16": tl.int16, + "i32": tl.int32, + "i64": tl.int64, + "ui1": tl.int1, + "ui8": tl.uint8, + "ui16": tl.uint16, + "ui32": tl.uint32, + "ui64": tl.uint64, + "bf16": tl.bfloat16, + "f8E5M2": tl.float8e5, + "f8E4M3FN": tl.float8e4nv, + }[str(dtype)] + + +def _convert_view_coords(coords, semantic): + def convert_elem(elem): + elem = tl._unwrap_if_constexpr(elem) + if isinstance(elem, int): + assert -(2**31) <= elem < 2**31, f"view ops support 32 bit indices, got {elem}" + return semantic._convert_to_ir_values([elem], require_i64=False)[0] + if isinstance(elem, tl.tensor): + assert elem.dtype.is_int(), "expected an integer tile type for view indices" + return elem.handle + raise TypeError(f"unsupported element type for view indices: {type(elem)}") + + if hasattr(coords, "__iter__"): + return [convert_elem(elem) for elem in coords] + return [convert_elem(coords)] + + +def _tileir_capability(semantic): + arch = getattr(getattr(semantic.builder, "options", None), "arch", None) + if isinstance(arch, str): + arch = arch.removeprefix("sm").removeprefix("_") + return int(arch or 0) + + +class CudaTileSemantic(TritonSemantic): + + def make_tensor_view(self, res_type, base, shapes, strides): + return tl.tensor( + self.builder.create_make_tensor_view( + res_type.to_ir(self.builder), + base.handle, + self._convert_to_ir_values(shapes, require_i64=False), + self._convert_to_ir_values(strides, require_i64=False), + ), + res_type, + ) + + def make_partition_view(self, src_tensor_view, tile_shape, tile_dim_map, res_type, pad_val): + ret_op, ir_type = self.builder.create_make_partition_view( + src_tensor_view.handle, + tile_shape, + tile_dim_map, + pad_val, + ) + res_type.ir_type = ir_type + return tl.tensor(ret_op, res_type) + + def dim(self, src, index): + return tl.tensor(self.builder.create_dim(src.handle, index), tl.int32) + + def load_view_tko(self, src, coords, mem_token, semantics, scope, token_ty, has_result_token, cost): + coords = _convert_view_coords(coords, self) + semantics = _str_to_tileir_semantic(semantics) + scope = _str_to_tileir_scope(scope) + _check_load_view_ordering(semantics) + _check_memory_ordering(semantics, scope) + data, res_token, shape, dtype = self.builder.create_load_view_tko( + src.handle, + coords, + mem_token.handle if mem_token else None, + semantics, + scope, + has_result_token, + cost, + _tileir_capability(self), + ) + result = tl.tensor(data, tl.block_type(_str_to_ele_ty(dtype), shape)) + if has_result_token: + return result, tl.tensor(res_token, token_ty) + return result + + def store_view_tko(self, dst, value, coords, mem_token, semantics, scope, token_ty, has_result_token, cost): + coords = _convert_view_coords(coords, self) + semantics = _str_to_tileir_semantic(semantics) + scope = _str_to_tileir_scope(scope) + _check_store_view_ordering(semantics) + _check_memory_ordering(semantics, scope) + res_token = self.builder.create_store_view_tko( + dst.handle, + value.handle, + coords, + mem_token.handle if mem_token else None, + semantics, + scope, + has_result_token, + cost, + _tileir_capability(self), + ) + if has_result_token: + return tl.tensor(res_token, token_ty) + return tl.tensor(res_token, tl.void) + + def create_mem_token(self): + return self.builder.create_mem_token() + + def join_mem_tokens(self, token_a, token_b): + return self.builder.create_join_mem_tokens(token_a.handle, token_b.handle) diff --git a/third_party/tileir/scripts/build_helper/Dockerfile.release b/third_party/tileir/scripts/build_helper/Dockerfile.release index b65a962db5..56525c61fc 100644 --- a/third_party/tileir/scripts/build_helper/Dockerfile.release +++ b/third_party/tileir/scripts/build_helper/Dockerfile.release @@ -56,7 +56,7 @@ RUN rm -rf ${TRITON_SRC_DIR}/build || true FROM base AS wheel ARG TRITON_SRC_DIR -# Copy and install pre-built wheel (e.g. nvtriton-*-cp312-cp312-linux_x86_64.whl) +# Copy and install pre-built wheel. COPY wheel/*.whl /tmp/ RUN pip install --no-cache-dir /tmp/*.whl && rm -f /tmp/*.whl diff --git a/third_party/tileir/triton_tileir.cc b/third_party/tileir/triton_tileir.cc index 442308fa2b..54ade94d92 100644 --- a/third_party/tileir/triton_tileir.cc +++ b/third_party/tileir/triton_tileir.cc @@ -34,6 +34,7 @@ #include "TritonToTileIR/Passes.h" #include "Utils/Utils.h" #include "cuda_tile/Bytecode/Writer/BytecodeWriter.h" +#include "cuda_tile/Dialect/CudaTile/IR/Attributes.h" #include "cuda_tile/Dialect/CudaTile/IR/Dialect.h" #include "cuda_tile/Dialect/CudaTile/IR/Ops.h" #include "cuda_tile/Dialect/CudaTile/IR/Types.h" @@ -55,6 +56,226 @@ namespace py = pybind11; using namespace mlir; using namespace triton; +namespace { + +Value castToCudaTileType(TritonOpBuilder &self, Value value) { + auto *ctx = self.getBuilder().getContext(); + Type elemTy = getElementTypeOrSelf(value); + SmallVector shape; + if (auto rankedTy = dyn_cast(value.getType())) { + shape = llvm::to_vector(rankedTy.getShape()); + } + return self + .create( + cuda_tile::TileType::get(ctx, shape, elemTy), value) + .getResult(0); +} + +SmallVector castToTileVec(TritonOpBuilder &self, + std::vector &values) { + SmallVector dst; + for (auto v : values) + dst.push_back(castToCudaTileType(self, v)); + return dst; +} + +Value castFromCudaTileAndExtract(TritonOpBuilder &self, Type resultType, + Value value) { + auto cTileTy = cast(resultType); + auto resTy = RankedTensorType::get(cTileTy.getShape(), + cTileTy.getElementType()); + Value castedValue = + self.create(resTy, value).getResult(0); + if (resTy.getRank() == 0) { + castedValue = + self.create(resTy.getElementType(), + castedValue) + .getResult(0); + } + return castedValue; +} + +cuda_tile::MemoryOrderingSemanticsAttr +getMemoryOrderingAttr(MLIRContext *ctx, uint32_t semantic) { + return cuda_tile::MemoryOrderingSemanticsAttr::get( + ctx, cuda_tile::symbolizeMemoryOrderingSemantics(semantic).value()); +} + +cuda_tile::MemoryScopeAttr getMemoryScopeAttr(MLIRContext *ctx, + int32_t scope) { + if (scope < 0) + return cuda_tile::MemoryScopeAttr(); + return cuda_tile::MemoryScopeAttr::get( + ctx, cuda_tile::symbolizeMemoryScope(scope).value()); +} + +void init_tileir_tko_ir(py::module &&) { + auto &builder_cls = *ir::getBuilderClass(); + builder_cls + .def("get_tileir_mem_token_ty", + [](TritonOpBuilder &self) -> Type { + return cuda_tile::TokenType::get(self.getBuilder().getContext()); + }) + .def("get_tensor_view_ty", + [](TritonOpBuilder &self, Type &elementType, + std::vector &shape, + std::vector &stride) -> Type { + auto ctx = self.getBuilder().getContext(); + return cuda_tile::TensorViewType::get(ctx, elementType, shape, + stride); + }) + .def("create_make_tensor_view", + [](TritonOpBuilder &self, Type &type, Value &ptr, + std::vector &shapes, + std::vector &strides) -> Value { + auto tvTy = dyn_cast(type); + if (!tvTy) + throw std::runtime_error( + "create_make_tensor_view expects tensor_view result type"); + + Value cudaTilePtrOperand; + if (auto tritonPtrTy = dyn_cast(ptr.getType())) { + Type cudaTilePtrTy = + cuda_tile::PointerType::get(tritonPtrTy.getPointeeType()); + cudaTilePtrOperand = + self.create(cudaTilePtrTy, ptr) + .getResult(0); + } else if (isa(ptr.getType())) { + cudaTilePtrOperand = ptr; + } else { + throw std::runtime_error("expect a pointer type"); + } + + return self + .create( + type, castToCudaTileType(self, cudaTilePtrOperand), + castToTileVec(self, shapes), castToTileVec(self, strides)) + .getResult(); + }) + .def("create_make_partition_view", + [](TritonOpBuilder &self, Value &src, + std::vector &tileShape, + std::vector &tileDimMap, + std::string paddingValue) -> std::tuple { + auto tensorViewTy = cast(src.getType()); + auto ctx = self.getBuilder().getContext(); + auto builder = self.getBuilder(); + auto partitionViewTy = cuda_tile::PartitionViewType::get( + ctx, builder.getDenseI32ArrayAttr(tileShape), tensorViewTy, + tileDimMap, + cuda_tile::PaddingValueAttr::get( + ctx, cuda_tile::symbolizePaddingValue(paddingValue) + .value())); + auto op = + self.create(partitionViewTy, + src); + return std::make_tuple(op.getResult(), op.getResult().getType()); + }) + .def("create_dim", + [](TritonOpBuilder &self, Value &src, int64_t dim) -> Value { + auto ctx = self.getBuilder().getContext(); + auto builder = self.getBuilder(); + Type intTy = builder.getIntegerType(32); + auto scalarTileTy = cuda_tile::TileType::get(ctx, {}, intTy); + + if (auto partTy = + dyn_cast(src.getType())) { + SmallVector resTys(partTy.getViewIndexRank(), + scalarTileTy); + auto shapeOp = + self.create(resTys, src); + return castFromCudaTileAndExtract(self, scalarTileTy, + shapeOp.getResults()[dim]); + } + if (auto tensorViewTy = + dyn_cast(src.getType())) { + SmallVector resTys(tensorViewTy.getShape().size(), + scalarTileTy); + auto shapeOp = + self.create(resTys, src); + return castFromCudaTileAndExtract(self, scalarTileTy, + shapeOp.getResults()[dim]); + } + throw std::runtime_error( + "src expected to be a partition view or tensor view"); + }) + .def("create_load_view_tko", + [](TritonOpBuilder &self, Value &view, std::vector &coords, + std::optional &token, uint32_t semantic, int32_t scope, + [[maybe_unused]] bool hasResultToken, int32_t cost, + int32_t capability) + -> std::tuple, Type> { + auto *ctx = self.getBuilder().getContext(); + auto optHint = mlir::triton::utils::cvtNumStagesToOptHintAttr( + ctx, capability, cost); + auto semanticAttr = getMemoryOrderingAttr(ctx, semantic); + auto scopeAttr = getMemoryScopeAttr(ctx, scope); + + cuda_tile::TileType resultTileTy; + if (auto partTy = + dyn_cast(view.getType())) { + resultTileTy = + cast(partTy.getViewTileType()); + } else { + throw std::runtime_error( + "expect a cuda_tile.partition_view type"); + } + + auto retOp = self.create( + resultTileTy, cuda_tile::TokenType::get(ctx), semanticAttr, + scopeAttr, view, castToTileVec(self, coords), + token.value_or(Value()), optHint.value_or(nullptr)); + + std::vector shape(resultTileTy.getShape().begin(), + resultTileTy.getShape().end()); + auto rankedTy = + RankedTensorType::get(shape, resultTileTy.getElementType()); + auto tensor = + self.create(rankedTy, + retOp.getTile()) + .getResult(0); + return std::make_tuple(tensor, retOp.getResultToken(), shape, + resultTileTy.getElementType()); + }) + .def("create_store_view_tko", + [](TritonOpBuilder &self, Value &view, Value &tile, + std::vector &coords, std::optional &token, + uint32_t semantic, int32_t scope, + [[maybe_unused]] bool hasResultToken, + int32_t cost, int32_t capability) -> Value { + auto *ctx = self.getBuilder().getContext(); + auto optHint = mlir::triton::utils::cvtNumStagesToOptHintAttr( + ctx, capability, cost); + Value castedTile = tile; + if (!isa(getElementTypeOrSelf(tile))) + castedTile = castToCudaTileType(self, tile); + + auto retOp = self.create( + cuda_tile::TokenType::get(ctx), + getMemoryOrderingAttr(ctx, semantic), + getMemoryScopeAttr(ctx, scope), castedTile, view, + castToTileVec(self, coords), token.value_or(Value()), + optHint.value_or(nullptr)); + return retOp.getResultToken(); + }) + .def("create_mem_token", + [](TritonOpBuilder &self) -> Value { + return self.create().getResult(); + }) + .def("create_join_mem_tokens", + [](TritonOpBuilder &self, Value &tokenA, Value &tokenB) -> Value { + assert(isa(tokenA.getType()) && + "tokenA must be a cuda_tile::TokenType"); + assert(isa(tokenB.getType()) && + "tokenB must be a cuda_tile::TokenType"); + return self + .create(ValueRange{tokenA, tokenB}) + .getResult(); + }); +} + +} // namespace + void init_triton_to_cudatile_passes(py::module &&m) { using namespace mlir::triton; // TODO: it is weird to pass mlir::triton::NVVM here since the conversion is @@ -150,4 +371,5 @@ void init_triton_tileir(py::module &&m) { py::bytes bytes(buffer.data(), buffer.size()); return bytes; }); + init_tileir_tko_ir(m.def_submodule("ir")); } From 38e843a33865b2014a70f502a698d743e76f4210 Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Mon, 15 Jun 2026 02:14:40 -0700 Subject: [PATCH 04/11] [tileir] Add tutorial examples --- .../tileir/01-load-view-token-ordering.py | 137 ++ .../tileir/02-mixed-kernel-routing.py | 84 ++ .../tileir/03-triton-tileir-benchmarks.py | 949 +++++++++++++ python/tutorials/tileir/tilegym/__init__.py | 5 + python/tutorials/tileir/tilegym/autotune.py | 5 + python/tutorials/tileir/tilegym/backend.py | 71 + python/tutorials/tileir/tilegym/logger.py | 16 + .../tutorials/tileir/tilegym/ops/__init__.py | 1 + .../tileir/tilegym/ops/triton/__init__.py | 1 + .../tilegym/ops/triton/activation/__init__.py | 1 + .../tilegym/ops/triton/activation/gelu.py | 181 +++ .../tilegym/ops/triton/activation/relu.py | 1222 +++++++++++++++++ .../tilegym/ops/triton/activation/silu.py | 114 ++ .../tilegym/ops/triton/activation/tanh.py | 110 ++ .../tilegym/ops/triton/activation/utils.py | 24 + .../tileir/tilegym/ops/triton/attention.py | 1130 +++++++++++++++ .../tileir/tilegym/ops/triton/bmm.py | 656 +++++++++ .../ops/triton/linear_bias_activation.py | 798 +++++++++++ .../tileir/tilegym/ops/triton/matmul.py | 1120 +++++++++++++++ .../tilegym/ops/triton/matmul_perf_model.py | 223 +++ .../tileir/tilegym/ops/triton/mla.py | 644 +++++++++ .../tileir/tilegym/ops/triton/mla_decoding.py | 458 ++++++ .../tileir/tilegym/ops/triton/rope.py | 667 +++++++++ 23 files changed, 8617 insertions(+) create mode 100644 python/tutorials/tileir/01-load-view-token-ordering.py create mode 100644 python/tutorials/tileir/02-mixed-kernel-routing.py create mode 100644 python/tutorials/tileir/03-triton-tileir-benchmarks.py create mode 100644 python/tutorials/tileir/tilegym/__init__.py create mode 100644 python/tutorials/tileir/tilegym/autotune.py create mode 100644 python/tutorials/tileir/tilegym/backend.py create mode 100644 python/tutorials/tileir/tilegym/logger.py create mode 100644 python/tutorials/tileir/tilegym/ops/__init__.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/__init__.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/activation/relu.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/activation/silu.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/activation/utils.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/attention.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/bmm.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/matmul.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/mla.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py create mode 100644 python/tutorials/tileir/tilegym/ops/triton/rope.py diff --git a/python/tutorials/tileir/01-load-view-token-ordering.py b/python/tutorials/tileir/01-load-view-token-ordering.py new file mode 100644 index 0000000000..25ae69206d --- /dev/null +++ b/python/tutorials/tileir/01-load-view-token-ordering.py @@ -0,0 +1,137 @@ +""" +Load View Token Ordering +======================== + +This tutorial runs a minimal TileIR-only TLE view-token kernel. The kernel +creates tensor views, chains memory tokens through ``load_view_tko`` and +``store_view_tko``, and checks the result against PyTorch. +""" + +import argparse +import os +import subprocess +import sys + +os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") + +import torch +import triton +import triton.language as tl +import triton.experimental.tle.language as tle +from triton.compiler.errors import CompilationError + + +@triton.jit +def _copy_with_token_kernel(x, y, n_elements, BLOCK_SIZE: tl.constexpr): + pid = tl.program_id(0) + x_view = tle.gpu.tile.make_view( + base=x, + shapes=[n_elements], + strides=[1], + tile_shape=[BLOCK_SIZE], + tile_dim_map=[0], + ) + y_view = tle.gpu.tile.make_view( + base=y, + shapes=[n_elements], + strides=[1], + tile_shape=[BLOCK_SIZE], + tile_dim_map=[0], + ) + + start_token = tle.gpu.tile.create_mem_token() + values, load_token = tle.gpu.tile.load_view_tko( + x_view, + [pid], + memToken=start_token, + semantics="acquire", + scope="device", + has_result_token=True, + ) + joined = tle.gpu.tile.join_mem_tokens(start_token, load_token) + store_token = tle.gpu.tile.store_view_tko( + y_view, + values + 1.0, + [pid], + memToken=joined, + semantics="release", + scope="device", + has_result_token=True, + ) + # The second store writes the same tile after the first store token to + # demonstrate token-ordered memory side effects. + tle.gpu.tile.store_view_tko( + y_view, + values + 2.0, + [pid], + memToken=store_token, + semantics="weak", + has_result_token=False, + ) + + +def _require_cuda(): + if not torch.cuda.is_available(): + raise RuntimeError("this tutorial requires a CUDA GPU") + + +def run_positive(): + os.environ["FLAGTREE_USE_TILEIR"] = "1" + _require_cuda() + n_elements = 1024 + block = 256 + x = torch.arange(n_elements, device="cuda", dtype=torch.float32) + y = torch.empty_like(x) + _copy_with_token_kernel[(triton.cdiv(n_elements, block),)]( + x, y, n_elements, BLOCK_SIZE=block, num_warps=4 + ) + torch.testing.assert_close(y, x + 2) + print("[OK] TileIR TKO view-token kernel") + + +def run_expected_native_failure(): + os.environ.pop("FLAGTREE_USE_TILEIR", None) + _require_cuda() + n_elements = 1024 + block = 256 + x = torch.arange(n_elements, device="cuda", dtype=torch.float32) + y = torch.empty_like(x) + try: + _copy_with_token_kernel[(triton.cdiv(n_elements, block),)]( + x, y, n_elements, BLOCK_SIZE=block, num_warps=4 + ) + except CompilationError as exc: + text = str(exc) + expected_message = "TileIR path" in text or "FLAGTREE_USE_TILEIR" in text + if not expected_message: + raise + print("[OK] native NVIDIA path reports the expected TileIR-only API error") + return + raise AssertionError("expected native NVIDIA path to reject TKO view-token APIs") + + +def run_negative_subprocess(): + env = os.environ.copy() + env.pop("FLAGTREE_USE_TILEIR", None) + env.setdefault("TRITON_BACKENDS_IN_TREE", "1") + cmd = [sys.executable, __file__, "--expect-native-failure"] + subprocess.run(cmd, env=env, check=True) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--expect-native-failure", action="store_true") + parser.add_argument("--skip-native-failure", action="store_true") + args = parser.parse_args() + + if args.expect_native_failure: + run_expected_native_failure() + return + + run_positive() + if not args.skip_native_failure: + run_negative_subprocess() + + +if __name__ == "__main__": + main() diff --git a/python/tutorials/tileir/02-mixed-kernel-routing.py b/python/tutorials/tileir/02-mixed-kernel-routing.py new file mode 100644 index 0000000000..b81c41888a --- /dev/null +++ b/python/tutorials/tileir/02-mixed-kernel-routing.py @@ -0,0 +1,84 @@ +""" +Mixed Kernel Routing +==================== + +This tutorial demonstrates per-kernel routing in one Python process. With +``FLAGTREE_USE_TILEIR=1`` enabled, a plain Triton kernel routes to TileIR, while +a kernel using non-TileIR TLE shared-memory APIs falls back to the native NVIDIA +path. +""" + +import os +import pathlib +import tempfile + +os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") + +import torch +import triton +import triton.language as tl +import triton.experimental.tle.language as tle + + +@triton.jit +def _plain_add_kernel(x, y, n_elements, BLOCK_SIZE: tl.constexpr): + pid = tl.program_id(0) + offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + mask = offsets < n_elements + values = tl.load(x + offsets, mask=mask, other=0.0) + tl.store(y + offsets, values + 1.0, mask=mask) + + +@triton.jit +def _tle_shared_memory_kernel(x, y, BLOCK_SIZE: tl.constexpr): + offsets = tl.arange(0, BLOCK_SIZE) + smem = tle.gpu.alloc([BLOCK_SIZE], dtype=tl.float32, scope=tle.gpu.smem) + smem_ptrs = tle.gpu.local_ptr(smem, (offsets,)) + values = tl.load(x + offsets) + tl.store(smem_ptrs, values) + copied = tl.load(smem_ptrs) + tl.store(y + offsets, copied + 2.0) + + +def _cache_files(cache_dir: pathlib.Path): + return sorted(path.name for path in cache_dir.glob("**/*") if path.is_file()) + + +def main(): + if not torch.cuda.is_available(): + raise RuntimeError("this tutorial requires a CUDA GPU") + + with tempfile.TemporaryDirectory(prefix="flagtree_tileir_routing_") as tmp: + cache_dir = pathlib.Path(tmp) + os.environ["TRITON_CACHE_DIR"] = str(cache_dir) + os.environ["FLAGTREE_USE_TILEIR"] = "1" + + n_elements = 1024 + block = 256 + x = torch.arange(n_elements, device="cuda", dtype=torch.float32) + y_tileir = torch.empty_like(x) + y_native = torch.empty(block, device="cuda", dtype=torch.float32) + + _plain_add_kernel[(triton.cdiv(n_elements, block),)]( + x, y_tileir, n_elements, BLOCK_SIZE=block, num_warps=4 + ) + _tle_shared_memory_kernel[(1,)](x, y_native, BLOCK_SIZE=block, num_warps=4) + + torch.testing.assert_close(y_tileir, x + 1) + torch.testing.assert_close(y_native, x[:block] + 2) + + files = _cache_files(cache_dir) + plain_used_tileir = any(name == "_plain_add_kernel.tileir" for name in files) + tle_used_native = any(name == "_tle_shared_memory_kernel.ptx" for name in files) + tle_used_tileir = any(name == "_tle_shared_memory_kernel.tileir" for name in files) + if not plain_used_tileir: + raise AssertionError("plain Triton kernel did not produce TileIR cache output") + if not tle_used_native or tle_used_tileir: + raise AssertionError("TLE shared-memory kernel did not fall back to native NVIDIA") + + print("[OK] plain Triton kernel routed to TileIR") + print("[OK] non-TileIR TLE kernel routed to native NVIDIA in the same process") + + +if __name__ == "__main__": + main() diff --git a/python/tutorials/tileir/03-triton-tileir-benchmarks.py b/python/tutorials/tileir/03-triton-tileir-benchmarks.py new file mode 100644 index 0000000000..a7fd932c5e --- /dev/null +++ b/python/tutorials/tileir/03-triton-tileir-benchmarks.py @@ -0,0 +1,949 @@ +#!/usr/bin/env python3 +""" +Triton TileIR Benchmarks +======================== + +Run the selected ported benchmark kernels on both native NVIDIA and TileIR +paths. The benchmark is self-contained: the original Triton operator +implementations and autotune configs used by these cases are bundled as the +local ``tilegym`` package next to this script. Timings use CUPTI CUDA kernel +self time through ``torch.profiler``. +""" + +import argparse +import gc +import importlib +import json +import math +import os +import pathlib +import random +import shutil +import subprocess +import sys +import tempfile +import traceback +import zlib +from dataclasses import dataclass +from typing import Callable, Dict, List, Optional, Sequence, Tuple + + +SCRIPT_DIR = pathlib.Path(__file__).resolve().parent +if str(SCRIPT_DIR) not in sys.path: + sys.path.insert(0, str(SCRIPT_DIR)) + +os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") + +import torch + + +CASES = ("bmm", "fmha", "linear_bias_act", "mla", "mla_decoding", "matmul", "rope") + + +@dataclass(frozen=True) +class PairSpec: + case: str + group_id: str + test_class: str + dtype: str + pair_id: str + function_name: str + + @property + def signature(self) -> str: + return f"{self.test_class} / {self.function_name}" + + +@dataclass +class BuiltCase: + run: Callable[[], object] + ref: Optional[Callable[[], object]] + meta: dict + check: bool = True + atol: float = 1e-2 + rtol: float = 1e-2 + + +def _add_pair(out: List[PairSpec], case: str, group_id: str, test_class: str, dtype: str, function_name: str): + idx = sum(1 for item in out if item.group_id == group_id) + out.append(PairSpec(case, group_id, test_class, dtype, f"{group_id}-{idx:02d}", function_name)) + + +def _build_pair_specs() -> Tuple[PairSpec, ...]: + specs: List[PairSpec] = [] + + for batch in (2, 8): + for dim in (2048, 4096, 8192): + for trans_a in (False, True): + for trans_b in (False, True): + _add_pair( + specs, + "bmm", + "001", + "Test_BMM_FWD", + "float16", + f"test_perf[FRAMEWORK-{batch}-{dim}-{dim}-{dim}-{trans_a}-{trans_b}-torch.float16]", + ) + + for is_causal in (False, True): + for seq_len in (1024, 1025, 2048, 2049, 4096, 4097, 512, 8192): + _add_pair( + specs, + "fmha", + "004", + "Test_FMHA", + "float8_e5m2", + f"test_perf[FRAMEWORK-{is_causal}-4-32-{seq_len}-128-torch.float8_e5m2]", + ) + for seq_len in (31072, 9): + _add_pair( + specs, + "fmha", + "004", + "Test_FMHA", + "float8_e5m2", + f"test_perf_llm[FRAMEWORK-torch.float8_e5m2-llama-1-32-{seq_len}-128]", + ) + + for dim in (1024, 2048, 4096, 8192): + for act in ("None", "gelu", "relu"): + _add_pair( + specs, + "linear_bias_act", + "015", + "Test_LinearBiasAct", + "float16", + f"test_perf[FRAMEWORK-{dim}-{dim}-{dim}-{act}-torch.float16-True]", + ) + + for seq_len in (1024, 2048, 4096, 512): + _add_pair( + specs, + "mla", + "016", + "Test_MLA", + "float8_e5m2", + f"test_perf[FRAMEWORK-True-4-32-{seq_len}-128-64-torch.float8_e5m2]", + ) + + for kv_len in (1024, 2048, 8192): + for batch in (1, 1024, 128): + for kpe_dim, transpose in ((128, False), (16, True), (32, True), (64, False)): + _add_pair( + specs, + "mla_decoding", + "017", + "Test_MLADecoding", + "float16", + f"test_perf[FRAMEWORK-{kv_len}-{batch}-512-64-float16-{kpe_dim}-{transpose}]", + ) + + matmul_sizes = (16384, 2048, 256, 4096, 64, 8192) + for trans_a in (False, True): + for trans_b in (False, True): + for dim in matmul_sizes: + _add_pair( + specs, + "matmul", + "021", + "Test_Matmul", + "float32", + f"test_perf[FRAMEWORK-True-False-{trans_a}-{trans_b}-{dim}-{dim}-{dim}-0-0-torch.float32]", + ) + for trans_a in (False, True): + for dim in matmul_sizes: + _add_pair( + specs, + "matmul", + "021", + "Test_Matmul", + "float32", + f"test_perf[FRAMEWORK-True-True-{trans_a}-False-{dim}-{dim}-{dim}-0-0-torch.float32]", + ) + + rope_configs = ((32, 32, 128, 1.0), (32, 8, 128, 0.5), (32, 8, 128, 1.0), (32, 8, 256, 0.25)) + for seq_len in (1, 128, 65536): + for num_q_heads, num_kv_heads, head_dim, partial in rope_configs: + _add_pair( + specs, + "rope", + "024", + "Test_RoPE", + "float16", + f"test_perf[FRAMEWORK-torch.float16-8-{seq_len}-{num_q_heads}-{num_kv_heads}-{head_dim}-{partial}]", + ) + + return tuple(specs) + + +PAIR_SPECS = _build_pair_specs() +PAIR_BY_ID: Dict[str, PairSpec] = {spec.pair_id: spec for spec in PAIR_SPECS} +PAIRS_BY_CASE: Dict[str, Tuple[PairSpec, ...]] = { + case: tuple(spec for spec in PAIR_SPECS if spec.case == case) for case in CASES +} + + +def _set_path(path: str) -> None: + if path == "tileir": + os.environ["FLAGTREE_USE_TILEIR"] = "1" + else: + os.environ.pop("FLAGTREE_USE_TILEIR", None) + os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") + + +def _path_name() -> str: + return "tileir" if os.environ.get("FLAGTREE_USE_TILEIR", "0") == "1" else "native" + + +def _require_cuda() -> None: + if not torch.cuda.is_available(): + raise RuntimeError("this tutorial requires a CUDA GPU") + + +def _dtype(token: str) -> torch.dtype: + name = token.replace("torch.", "") + mapping = { + "float16": torch.float16, + "float32": torch.float32, + "bfloat16": torch.bfloat16, + } + float8_e5m2 = getattr(torch, "float8_e5m2", None) + float8_e4m3fn = getattr(torch, "float8_e4m3fn", None) + if float8_e5m2 is not None: + mapping["float8_e5m2"] = float8_e5m2 + if float8_e4m3fn is not None: + mapping["float8_e4m3fn"] = float8_e4m3fn + if name not in mapping: + raise RuntimeError(f"unsupported dtype token: {token}") + return mapping[name] + + +def _bool(token: str) -> bool: + if token == "True": + return True + if token == "False": + return False + raise ValueError(f"not a bool token: {token}") + + +def _maybe_none(token: str): + return None if token == "None" else token + + +def _params(function_name: str) -> List[str]: + """Parse bracketed pytest-style parameters. + + Tokens are separated by top-level ``-`` characters. The selected benchmark + names only use non-negative numeric values, booleans, dtypes, and simple + strings, so parameter values themselves must not contain ``-``. + """ + start = function_name.index("[") + 1 + end = function_name.rindex("]") + body = function_name[start:end] + if body.startswith("FRAMEWORK-"): + body = body[len("FRAMEWORK-"):] + out: List[str] = [] + token: List[str] = [] + depth = 0 + for ch in body: + if ch == "(": + depth += 1 + elif ch == ")": + depth -= 1 + if ch == "-" and depth == 0: + out.append("".join(token)) + token = [] + else: + token.append(ch) + out.append("".join(token)) + return out + + +def _meta(x): + if isinstance(x, torch.Tensor): + return {"shape": list(x.shape), "dtype": str(x.dtype).replace("torch.", ""), "device": str(x.device)} + if isinstance(x, (list, tuple)): + return [_meta(v) for v in x] + if isinstance(x, dict): + return {k: _meta(v) for k, v in x.items()} + return repr(x) + + +def _seed_from_name(name: str) -> None: + seed = zlib.crc32(name.encode("utf-8")) & 0x7FFFFFFF + torch.manual_seed(seed) + random.seed(seed) + + +def _randn(shape, dtype, scale=1.0): + if dtype in (getattr(torch, "float8_e4m3fn", None), getattr(torch, "float8_e5m2", None)): + return (torch.randn(shape, device="cuda", dtype=torch.float16) * scale).to(dtype) + return torch.randn(shape, device="cuda", dtype=dtype) * scale + + +def _rand(shape, dtype): + if dtype in (getattr(torch, "float8_e4m3fn", None), getattr(torch, "float8_e5m2", None)): + return torch.rand(shape, device="cuda", dtype=torch.float16).to(dtype) + return torch.rand(shape, device="cuda", dtype=dtype) + + +def _make_tensor_descriptor_hashable() -> None: + try: + from triton.tools.tensor_descriptor import TensorDescriptor + except Exception: + return + if getattr(TensorDescriptor, "__hash__", None) is None: + TensorDescriptor.__hash__ = object.__hash__ + + +def _import_triton_op(module: str): + mod = importlib.import_module(f"tilegym.ops.triton.{module}") + _make_tensor_descriptor_hashable() + return mod + + +def _patch_empty_prune_fallback(autotuner) -> None: + if getattr(autotuner, "_flagtree_empty_prune_fallback", False): + return + original = getattr(autotuner, "early_config_prune", None) + if original is None: + return + + def prune(configs, named_args, **kwargs): + pruned = original(configs, named_args, **kwargs) + if pruned: + return pruned + return configs[:1] + + autotuner.early_config_prune = prune + autotuner._flagtree_empty_prune_fallback = True + + +def _patch_matmul_empty_prune_fallback(mod) -> None: + if _path_name() != "tileir": + return + _patch_empty_prune_fallback(mod._matmul_kernel) + _patch_empty_prune_fallback(mod._static_persistent_matmul_kernel) + + +def _assert_close(name: str, actual, expected, atol: float, rtol: float) -> float: + actual_items = [actual] if isinstance(actual, torch.Tensor) else list(actual) + expected_items = [expected] if isinstance(expected, torch.Tensor) else list(expected) + if len(actual_items) != len(expected_items): + raise AssertionError(f"{name}: output arity mismatch {len(actual_items)} != {len(expected_items)}") + max_abs = 0.0 + for idx, (a, e) in enumerate(zip(actual_items, expected_items)): + diff = (a.float() - e.float()).abs() + item_max = float(diff.max().item()) + max_abs = max(max_abs, item_max) + if not torch.allclose(a.float(), e.float(), atol=atol, rtol=rtol, equal_nan=True): + raise AssertionError(f"{name}: output {idx} mismatch max_abs={item_max:.6g}") + return max_abs + + +def _estimate_ms(fn: Callable[[], object], initial_rep: int) -> float: + fn() + torch.cuda.synchronize() + start_event = torch.cuda.Event(enable_timing=True) + end_event = torch.cuda.Event(enable_timing=True) + start_event.record() + for _ in range(initial_rep): + fn() + end_event.record() + torch.cuda.synchronize() + return max(start_event.elapsed_time(end_event) / initial_rep, 1.0e-6) + + +def _bench_ms_cupti(fn: Callable[[], object], warmup: float, rep: float, min_rep: int, initial_rep: int) -> float: + from torch.profiler import ProfilerActivity + from torch.profiler import profile + + estimate_ms = _estimate_ms(fn, initial_rep) + n_warmup = max(1, int(warmup / estimate_ms)) + n_repeat = max(min_rep, int(rep / estimate_ms)) + + for _ in range(n_warmup): + fn() + torch.cuda.synchronize() + torch.cuda.empty_cache() + gc.collect() + torch._C._cuda_clearCublasWorkspaces() + torch.cuda.empty_cache() + torch.cuda.reset_peak_memory_stats() + + run_times_us = [] + for _ in range(n_repeat): + with profile(activities=[ProfilerActivity.CUDA]) as prof: + fn() + torch.cuda.synchronize() + total_us = sum(evt.self_device_time_total for evt in prof.key_averages() if evt.self_device_time_total > 0) + if total_us == 0: + raise RuntimeError("CUPTI returned 0 device time") + run_times_us.append(total_us) + + times = torch.tensor(run_times_us, dtype=torch.float64) / 1000.0 + return times.mean().item() + + +def _bench_ms( + fn: Callable[[], object], + warmup: float, + rep: float, + min_rep: int, + initial_rep: int, +) -> Tuple[float, str]: + return _bench_ms_cupti(fn, warmup, rep, min_rep, initial_rep), "cupti" + + +def _reference_bmm(a, b, transpose_a=False, transpose_b=False): + aa = a.transpose(-1, -2) if transpose_a else a + bb = b.transpose(-1, -2) if transpose_b else b + return torch.bmm(aa, bb) + + +def _build_bmm(fn: str) -> BuiltCase: + q, m, n, k, ta, tb, dtype_s = _params(fn) + q, m, n, k = int(q), int(m), int(n), int(k) + ta, tb = _bool(ta), _bool(tb) + dtype = _dtype(dtype_s) + mod = _import_triton_op("bmm") + a_shape = (q, k, m) if ta else (q, m, k) + b_shape = (q, n, k) if tb else (q, k, n) + a = _rand(a_shape, dtype) + b = _rand(b_shape, dtype) + return BuiltCase( + run=lambda: mod.bmm_memref(a, b, transpose_a=ta, transpose_b=tb, static_persistent=True), + ref=lambda: _reference_bmm(a, b, ta, tb), + meta={"a": _meta(a), "b": _meta(b), "transpose_a": ta, "transpose_b": tb}, + atol=5e-2, + rtol=5e-2, + ) + + +def _build_fmha(fn: str) -> BuiltCase: + params = _params(fn) + if fn.startswith("test_perf_llm["): + dtype_s, _model, batch, heads, seq_len, head_dim = params + is_causal = True + else: + is_causal_s, batch, heads, seq_len, head_dim, dtype_s = params + is_causal = _bool(is_causal_s) + batch, heads, seq_len, head_dim = int(batch), int(heads), int(seq_len), int(head_dim) + dtype = _dtype(dtype_s) + mod = _import_triton_op("attention") + q = _randn((batch, heads, seq_len, head_dim), dtype) + k = _randn((batch, heads, seq_len, head_dim), dtype) + v = _randn((batch, heads, seq_len, head_dim), dtype) + scale = 1.0 / math.sqrt(head_dim) + check = dtype not in (getattr(torch, "float8_e4m3fn", None), getattr(torch, "float8_e5m2", None)) + ref = None if not check else lambda: torch.nn.functional.scaled_dot_product_attention(q, k, v, is_causal=is_causal, scale=scale) + return BuiltCase( + run=lambda: mod.triton_fmha(q, k, v, scaling=scale, is_causal=is_causal), + ref=ref, + meta={"q": _meta(q), "is_causal": is_causal}, + check=check, + atol=8e-2, + rtol=8e-2, + ) + + +def _build_linear_bias_act(fn: str) -> BuiltCase: + m, n, k, act_type, dtype_s, is_bias = _params(fn) + m, n, k = int(m), int(n), int(k) + act_type = _maybe_none(act_type) + dtype = _dtype(dtype_s) + is_bias = _bool(is_bias) + mod = _import_triton_op("linear_bias_activation") + x = _rand((m, k), dtype) + weight = _rand((n, k), dtype) + bias = _rand((n,), dtype) if is_bias else None + + def ref(): + out = torch.nn.functional.linear(x, weight, bias) + if act_type == "relu": + return torch.relu(out) + if act_type == "gelu": + return torch.nn.functional.gelu(out) + return out + + return BuiltCase( + run=lambda: mod.linear_bias_act_dropout(x, weight, bias, act_type, is_grad_enabled=False), + ref=ref, + meta={"x": _meta(x), "weight": _meta(weight), "act_type": act_type, "is_bias": is_bias}, + atol=8e-2, + rtol=8e-2, + ) + + +def _build_mla(fn: str) -> BuiltCase: + is_causal, batch, heads, seq_len, d_model, d_pe, dtype_s = _params(fn) + is_causal = _bool(is_causal) + batch, heads, seq_len, d_model, d_pe = map(int, (batch, heads, seq_len, d_model, d_pe)) + dtype = _dtype(dtype_s) + mod = _import_triton_op("mla") + q = _randn((batch, heads, seq_len, d_model), dtype, scale=0.3) + k = _randn((batch, heads, seq_len, d_model), dtype, scale=0.3) + v = _randn((batch, heads, seq_len, d_model), dtype, scale=0.3) + qpe = _randn((batch, heads, seq_len, d_pe), dtype, scale=0.3) + kpe = _randn((batch, heads, seq_len, d_pe), dtype, scale=0.3) + scale = 1.0 / math.sqrt(d_model + d_pe) + return BuiltCase( + run=lambda: mod.triton_mla(q, k, v, qpe, kpe, is_causal, scale), + ref=None, + meta={"q": _meta(q), "qpe": _meta(qpe), "is_causal": is_causal}, + check=False, + ) + + +def _build_mla_decoding(fn: str) -> BuiltCase: + s_kv, num_batch, num_heads, head_dim, dtype_s, kpe_dim, transpose = _params(fn) + s_kv, num_batch, num_heads, head_dim, kpe_dim = map(int, (s_kv, num_batch, num_heads, head_dim, kpe_dim)) + dtype = _dtype(dtype_s) + transpose = _bool(transpose) + mod = _import_triton_op("mla_decoding") + q = _randn((num_batch, num_heads, head_dim), dtype, scale=0.3) + qpe = _randn((num_batch, num_heads, kpe_dim), dtype, scale=0.3) + kv = _randn((num_batch, s_kv, head_dim), dtype, scale=0.3) + kpe = _randn((num_batch, s_kv, kpe_dim), dtype, scale=0.3) + scale = 1.0 / math.sqrt(head_dim + kpe_dim) + return BuiltCase( + run=lambda: mod.mla_decoding(q, qpe, kv, kpe, scale, transpose=transpose), + ref=None, + meta={"q": _meta(q), "kv": _meta(kv), "transpose": transpose}, + check=False, + ) + + +def _build_matmul(fn: str) -> BuiltCase: + use_tma, static_persistent, ta, tb, m, n, k, _offset_a, _offset_b, dtype_s = _params(fn) + use_tma, static_persistent, ta, tb = map(_bool, (use_tma, static_persistent, ta, tb)) + m, n, k = int(m), int(n), int(k) + dtype = _dtype(dtype_s) + mod = _import_triton_op("matmul") + _patch_matmul_empty_prune_fallback(mod) + a = _randn((k, m) if ta else (m, k), dtype, scale=0.2) + b = _randn((n, k) if tb else (k, n), dtype, scale=0.2) + check = dtype not in (getattr(torch, "float8_e4m3fn", None), getattr(torch, "float8_e5m2", None)) + return BuiltCase( + run=lambda: mod.matmul(a, b, trans_a=ta, trans_b=tb, static_persistent=static_persistent, use_tma=use_tma), + ref=None if not check else lambda: (a.t() if ta else a) @ (b.t() if tb else b), + meta={"a": _meta(a), "b": _meta(b), "use_tma": use_tma, "static_persistent": static_persistent}, + check=check, + atol=8e-2, + rtol=8e-2, + ) + + +def _build_rope(fn: str) -> BuiltCase: + dtype_s, bsz, seq_len, num_q_heads, num_kv_heads, head_dim, partial = _params(fn) + dtype = _dtype(dtype_s) + bsz, seq_len, num_q_heads, num_kv_heads, head_dim = map(int, (bsz, seq_len, num_q_heads, num_kv_heads, head_dim)) + partial = float(partial) + mod = _import_triton_op("rope") + q = _randn((bsz, num_q_heads, seq_len, head_dim), dtype) + k = _randn((bsz, num_kv_heads, seq_len, head_dim), dtype) + rope_dim = int(head_dim * partial) + pos = torch.arange(seq_len, device="cuda", dtype=torch.float32)[None, :, None] + freqs = torch.arange(rope_dim, device="cuda", dtype=torch.float32)[None, None, :] / max(rope_dim, 1) + angle = pos * freqs + cos = torch.cos(angle).to(dtype) + sin = torch.sin(angle).to(dtype) + return BuiltCase( + run=lambda: mod.apply_rope_base(q, k, cos, sin, partial_rotary_factor=partial), + ref=None, + meta={"q": _meta(q), "k": _meta(k), "partial": partial}, + check=False, + ) + + +BUILDERS = { + "Test_BMM_FWD": _build_bmm, + "Test_FMHA": _build_fmha, + "Test_LinearBiasAct": _build_linear_bias_act, + "Test_MLA": _build_mla, + "Test_MLADecoding": _build_mla_decoding, + "Test_Matmul": _build_matmul, + "Test_RoPE": _build_rope, +} + + +def _build_case(spec: PairSpec) -> BuiltCase: + return BUILDERS[spec.test_class](spec.function_name) + + +def _cache_route(cache_dir: pathlib.Path) -> str: + files = [path.name for path in cache_dir.glob("**/*") if path.is_file()] + if any(name.endswith(".tileir") for name in files): + return "tileir" + if any(name.endswith((".ttgir", ".ptx", ".cubin")) for name in files): + return "native" + return "unknown" + + +def run_child( + pair_id: str, + path: str, + warmup: float, + rep: float, + min_rep: int, + initial_rep: int, + check: bool, +) -> int: + _set_path(path) + os.environ.setdefault("TILEGYM_DISABLE_AUTOTUNE", "1") + _require_cuda() + spec = PAIR_BY_ID[pair_id] + row = { + "pair_id": spec.pair_id, + "case": spec.case, + "path": _path_name(), + "signature": spec.signature, + "status": "FAIL", + } + try: + _seed_from_name(spec.function_name) + built = _build_case(spec) + out = built.run() + torch.cuda.synchronize() + validation = "runtime" + max_abs = None + if check and built.check and built.ref is not None: + expected = built.ref() + torch.cuda.synchronize() + max_abs = _assert_close(spec.function_name, out, expected, built.atol, built.rtol) + validation = "torch" + bench_ms, timing = _bench_ms( + built.run, + warmup, + rep, + min_rep, + initial_rep, + ) + row.update( + { + "status": "PASS", + "ms": bench_ms, + "timing": timing, + "validation": validation, + "max_abs": max_abs, + "meta": built.meta, + } + ) + except Exception as exc: + row["message"] = f"{type(exc).__name__}: {exc}" + row["traceback"] = traceback.format_exc(limit=8) + finally: + try: + torch.cuda.empty_cache() + except Exception: + pass + print(json.dumps(row, sort_keys=True)) + return 0 if row["status"] == "PASS" else 1 + + +def _run_subprocess( + script: pathlib.Path, + spec: PairSpec, + path: str, + warmup: float, + rep: float, + min_rep: int, + initial_rep: int, + check: bool, + cache_root: pathlib.Path, +) -> dict: + env = os.environ.copy() + env.setdefault("TRITON_BACKENDS_IN_TREE", "1") + env.setdefault("TILEGYM_DISABLE_AUTOTUNE", "1") + env["PYTHONUNBUFFERED"] = "1" + cache_dir = cache_root / spec.case / spec.pair_id / path + env["TRITON_CACHE_DIR"] = str(cache_dir) + if path == "tileir": + env["FLAGTREE_USE_TILEIR"] = "1" + else: + env.pop("FLAGTREE_USE_TILEIR", None) + cmd = [ + sys.executable, + str(script), + "--child", + "--pair", + spec.pair_id, + "--path", + path, + "--warmup", + str(warmup), + "--rep", + str(rep), + "--min-rep", + str(min_rep), + "--initial-rep", + str(initial_rep), + ] + if not check: + cmd.append("--no-check") + proc = subprocess.run(cmd, env=env, text=True, capture_output=True, check=False) + row = None + for line in reversed(proc.stdout.splitlines()): + line = line.strip() + if line.startswith("{") and line.endswith("}"): + row = json.loads(line) + break + if row is None: + row = { + "pair_id": spec.pair_id, + "case": spec.case, + "path": path, + "status": "FAIL", + "message": "child did not print JSON", + "stdout": proc.stdout, + "stderr": proc.stderr, + } + if proc.returncode != 0 and row.get("status") == "PASS": + row["status"] = "FAIL" + row["message"] = f"child exited with {proc.returncode}" + row["route"] = _cache_route(cache_dir) + row["cache_dir"] = str(cache_dir) + if proc.stderr: + row.setdefault("stderr", proc.stderr) + return row + + +def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str], cache_root: pathlib.Path) -> dict: + passed = sum(1 for row in rows if row.get("status") == "PASS") + failed = len(rows) - passed + summary = { + "pairs": len(specs), + "paths": list(paths), + "runs": len(rows), + "passed": passed, + "failed": failed, + "cache_root": str(cache_root), + } + if set(paths) == {"native", "tileir"}: + ratios = [] + by_pair = {spec.pair_id: {} for spec in specs} + for row in rows: + by_pair[row["pair_id"]][row["path"]] = row + for pair_rows in by_pair.values(): + native = pair_rows.get("native") + tileir = pair_rows.get("tileir") + if native and tileir and native.get("status") == "PASS" and tileir.get("status") == "PASS": + ratios.append(native["ms"] / tileir["ms"]) + if ratios: + summary.update( + { + "mean_native_over_tileir": sum(ratios) / len(ratios), + "min_native_over_tileir": min(ratios), + "max_native_over_tileir": max(ratios), + "tileir_faster": sum(1 for value in ratios if value > 1.0), + "speedup_pairs": len(ratios), + } + ) + return summary + + +def _print_table(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str]) -> None: + by_pair = {spec.pair_id: {} for spec in specs} + for row in rows: + by_pair[row["pair_id"]][row["path"]] = row + + if set(paths) == {"native", "tileir"}: + print( + f"{'pair':<8} {'case':<16} {'native ms':>10} {'TileIR ms':>10} " + f"{'native/TileIR':>13} {'check':>8} {'route':>13} {'timing':>14} signature" + ) + print("-" * 148) + for spec in specs: + native = by_pair[spec.pair_id].get("native") + tileir = by_pair[spec.pair_id].get("tileir") + if native and tileir and native.get("status") == "PASS" and tileir.get("status") == "PASS": + ratio = native["ms"] / tileir["ms"] if tileir["ms"] else float("nan") + validation = native.get("validation") if native.get("validation") == tileir.get("validation") else "mixed" + route_s = f"{native.get('route')}/{tileir.get('route')}" + timing_s = native.get("timing") if native.get("timing") == tileir.get("timing") else "mixed" + print( + f"{spec.pair_id:<8} {spec.case:<16} {native['ms']:10.3f} {tileir['ms']:10.3f} " + f"{ratio:13.3f} {validation:>8} {route_s:>13} {timing_s:>14} {spec.function_name}" + ) + else: + for row in (native, tileir): + if row: + print( + f"{spec.pair_id:<8} {spec.case:<16} {row.get('path', ''):<10} " + f"{row.get('status', 'FAIL'):<8} route={row.get('route')} " + f"message={row.get('message', '')}" + ) + return + + print(f"{'pair':<8} {'case':<16} {'path':<8} {'ms':>10} {'check':>8} {'route':>8} {'timing':>14} signature") + print("-" * 132) + for spec in specs: + for path in paths: + row = by_pair[spec.pair_id].get(path) + if row and row.get("status") == "PASS": + print( + f"{spec.pair_id:<8} {spec.case:<16} {path:<8} {row['ms']:10.3f} " + f"{row.get('validation'):>8} {row.get('route'):>8} {row.get('timing'):>14} {spec.function_name}" + ) + elif row: + print( + f"{spec.pair_id:<8} {spec.case:<16} {path:<8} {row.get('status', 'FAIL'):<8} " + f"route={row.get('route')} message={row.get('message', '')}" + ) + + +def run_parent( + specs: Sequence[PairSpec], + paths: Sequence[str], + warmup: float, + rep: float, + min_rep: int, + initial_rep: int, + check: bool, + cache_dir: Optional[pathlib.Path], + clear_cache: bool, + json_output: bool, + require_speedup_fraction: Optional[float], +) -> int: + script = pathlib.Path(__file__).resolve() + if cache_dir is None: + cache_root = pathlib.Path(tempfile.mkdtemp(prefix="flagtree_tileir_kernel_benchmarks_")) + else: + cache_root = cache_dir + if clear_cache and cache_root.exists(): + shutil.rmtree(cache_root) + cache_root.mkdir(parents=True, exist_ok=True) + + rows = [] + for spec in specs: + for path in paths: + rows.append(_run_subprocess(script, spec, path, warmup, rep, min_rep, initial_rep, check, cache_root)) + + summary = _summarize(rows, specs, paths, cache_root) + if json_output: + print(json.dumps({"summary": summary, "rows": rows}, sort_keys=True, indent=2)) + else: + _print_table(rows, specs, paths) + print("") + print( + f"passed runs: {summary['passed']}/{summary['runs']} " + f"passed pairs: {len(specs) - sum(1 for spec in specs if any(row.get('pair_id') == spec.pair_id and row.get('status') != 'PASS' for row in rows))}/{len(specs)}" + ) + if "mean_native_over_tileir" in summary: + print( + "native/TileIR ratio: " + f"mean={summary['mean_native_over_tileir']:.3f}, " + f"min={summary['min_native_over_tileir']:.3f}, " + f"max={summary['max_native_over_tileir']:.3f}, " + f"TileIR faster={summary['tileir_faster']}/{len(specs)}" + ) + print(f"cache root: {summary['cache_root']}") + + if summary["failed"]: + return 1 + if require_speedup_fraction is not None: + speedup_pairs = summary.get("speedup_pairs", 0) + if speedup_pairs == 0: + return 2 + faster_fraction = summary.get("tileir_faster", 0) / speedup_pairs + if faster_fraction < require_speedup_fraction: + return 2 + return 0 + + +def _parse_cases(value: str) -> List[str]: + if value == "all": + return list(CASES) + out = [item.strip() for item in value.split(",") if item.strip()] + bad = [item for item in out if item not in CASES] + if bad: + raise SystemExit(f"unknown case(s): {', '.join(bad)}") + return out + + +def _parse_pair_filter(value: str, cases: Sequence[str]) -> List[PairSpec]: + if value == "all": + return [spec for spec in PAIR_SPECS if spec.case in cases] + requested = [item.strip() for item in value.split(",") if item.strip()] + out: List[PairSpec] = [] + for item in requested: + if item in PAIR_BY_ID: + spec = PAIR_BY_ID[item] + if spec.case not in cases: + raise SystemExit(f"pair {item} belongs to case {spec.case}, not selected cases {','.join(cases)}") + out.append(spec) + continue + if item.isdigit(): + suffix = f"-{int(item):02d}" + matches = [spec for spec in PAIR_SPECS if spec.case in cases and spec.pair_id.endswith(suffix)] + if len(matches) == 1: + out.append(matches[0]) + continue + if len(matches) > 1: + raise SystemExit(f"pair index {item} matches multiple cases; use full pair id") + raise SystemExit(f"unknown pair id: {item}") + return out + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--case", default="all", help="comma-separated case list or 'all'") + parser.add_argument("--pair", default="all", help="pair id, comma-separated pair ids, numeric case-local index, or 'all'") + parser.add_argument("--path", choices=("both", "native", "tileir"), default="both") + parser.add_argument("--warmup", type=float, default=100.0, help="benchmark warmup duration in ms") + parser.add_argument("--rep", type=float, default=50.0, help="benchmark measured duration in ms") + parser.add_argument("--min-rep", type=int, default=2, help="minimum measured benchmark iterations") + parser.add_argument("--initial-rep", type=int, default=5, help="initial iterations for benchmark runtime estimate") + parser.add_argument("--no-check", dest="check", action="store_false", help="skip torch-reference checks where available") + parser.add_argument("--cache-dir", type=pathlib.Path, default=None) + parser.add_argument("--clear-cache", action="store_true") + parser.add_argument("--json", action="store_true") + parser.add_argument("--require-speedup", action="store_true", help="require every paired TileIR run to be faster") + parser.add_argument( + "--require-speedup-fraction", + type=float, + default=None, + help="require at least this fraction of paired TileIR runs to be faster", + ) + parser.add_argument("--child", action="store_true", help=argparse.SUPPRESS) + args = parser.parse_args() + + if args.require_speedup_fraction is not None and not 0.0 <= args.require_speedup_fraction <= 1.0: + raise SystemExit("--require-speedup-fraction must be between 0.0 and 1.0") + require_speedup_fraction = args.require_speedup_fraction + if args.require_speedup: + require_speedup_fraction = 1.0 + + cases = _parse_cases(args.case) + specs = _parse_pair_filter(args.pair, cases) + if args.child: + if len(specs) != 1 or args.path == "both": + raise SystemExit("--child requires exactly one pair and one path") + return run_child( + specs[0].pair_id, + args.path, + args.warmup, + args.rep, + args.min_rep, + args.initial_rep, + args.check, + ) + + paths = ["native", "tileir"] if args.path == "both" else [args.path] + return run_parent( + specs=specs, + paths=paths, + warmup=args.warmup, + rep=args.rep, + min_rep=args.min_rep, + initial_rep=args.initial_rep, + check=args.check, + cache_dir=args.cache_dir, + clear_cache=args.clear_cache, + json_output=args.json, + require_speedup_fraction=require_speedup_fraction, + ) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/python/tutorials/tileir/tilegym/__init__.py b/python/tutorials/tileir/tilegym/__init__.py new file mode 100644 index 0000000000..b4e2af9ac8 --- /dev/null +++ b/python/tutorials/tileir/tilegym/__init__.py @@ -0,0 +1,5 @@ +from .backend import get_available_triton_backend +from .backend import get_current_backend +from .backend import is_backend_available +from .backend import mark_perf_ready +from .backend import register_impl diff --git a/python/tutorials/tileir/tilegym/autotune.py b/python/tutorials/tileir/tilegym/autotune.py new file mode 100644 index 0000000000..7dec32c662 --- /dev/null +++ b/python/tutorials/tileir/tilegym/autotune.py @@ -0,0 +1,5 @@ +import os + + +def is_autotune_disabled(): + return os.environ.get("TILEGYM_DISABLE_AUTOTUNE", "0") == "1" diff --git a/python/tutorials/tileir/tilegym/backend.py b/python/tutorials/tileir/tilegym/backend.py new file mode 100644 index 0000000000..62257f0506 --- /dev/null +++ b/python/tutorials/tileir/tilegym/backend.py @@ -0,0 +1,71 @@ +import functools +import os + + +_IMPL_REGISTRY = {} + + +def get_available_triton_backend(): + return "nvt" if os.environ.get("FLAGTREE_USE_TILEIR", "0") == "1" else "oait" + + +def get_current_backend(): + return get_available_triton_backend() + + +def is_backend_available(name): + return name in {"triton", "nvt", "oait", "cutile", "tilecpp"} + + +def register_impl(name=None, backend=None): + def deco(fn): + impl_name = name or fn.__name__ + impl_backend = backend or "triton" + _IMPL_REGISTRY[(impl_name, impl_backend)] = fn + return fn + + if callable(name) and backend is None: + fn = name + name = None + return deco(fn) + return deco + + +def mark_perf_ready(*args, **_kwargs): + if args and callable(args[0]): + return args[0] + + def deco(fn): + return fn + + return deco + + +def dispatch(name, fallback_backend=None): + """Dispatch to a registered implementation. + + Lookup prefers the explicit dispatch name over the decorated function name, + and the current backend over the fallback backend over generic ``triton``. + """ + + def deco(fn): + @functools.wraps(fn) + def wrapper(*args, **kwargs): + backend = get_current_backend() + candidates = [ + (name, backend), + (name, fallback_backend or "triton"), + (name, "triton"), + (fn.__name__, backend), + (fn.__name__, fallback_backend or "triton"), + (fn.__name__, "triton"), + ] + for key in candidates: + impl = _IMPL_REGISTRY.get(key) + if impl is not None: + return impl(*args, **kwargs) + return fn(*args, **kwargs) + + return wrapper + + return deco diff --git a/python/tutorials/tileir/tilegym/logger.py b/python/tutorials/tileir/tilegym/logger.py new file mode 100644 index 0000000000..b56fe3476e --- /dev/null +++ b/python/tutorials/tileir/tilegym/logger.py @@ -0,0 +1,16 @@ +class _Logger: + def debug(self, *_args, **_kwargs): + pass + + def info(self, *_args, **_kwargs): + pass + + def warning(self, *_args, **_kwargs): + pass + + def error(self, *_args, **_kwargs): + pass + + +def get_logger(*_args, **_kwargs): + return _Logger() diff --git a/python/tutorials/tileir/tilegym/ops/__init__.py b/python/tutorials/tileir/tilegym/ops/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/__init__.py @@ -0,0 +1 @@ + diff --git a/python/tutorials/tileir/tilegym/ops/triton/__init__.py b/python/tutorials/tileir/tilegym/ops/triton/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/__init__.py @@ -0,0 +1 @@ + diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py b/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py @@ -0,0 +1 @@ + diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py new file mode 100644 index 0000000000..48ae766850 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py @@ -0,0 +1,181 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import torch +import triton +import triton.language as tl + +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl + +from .tanh import tanh_forward + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def _standard_normal_cdf(x): + # inverse_sqrt_2 = 1.0 / tl.sqrt(2.0) + inverse_sqrt_2 = 0.7071067811865475 + cdf = 0.5 * (1 + tl.math.erf(x * inverse_sqrt_2)) + return cdf + + +@triton.jit +def _standard_normal_pdf(x): + # inverse_sqrt_2_pi = 1.0 / (tl.sqrt(2 * PI)) + inverse_sqrt_2_pi = 0.3989422804014327 + pdf = inverse_sqrt_2_pi * tl.exp((-0.5 * x * x).to(tl.float32)) + return pdf + + +@triton.jit +def gelu_tanh_forward(x): + """ + Compute the approximate GELU activation function using tanh approximation. + + This is the fast approximation version of GELU that uses the hyperbolic tangent + function to approximate the cumulative distribution function. The mathematical formula is: + f(x) = 0.5 * x * (1 + tanh(√(2/π) * (x + 0.044715 * x³))) + + This approximation is computationally faster than the exact version while maintaining + good accuracy for most practical applications. + + Args: + x: Input tensor values + + Returns: + Approximate GELU activation output using tanh approximation + """ + # sqrt_2_div_pi = tl.sqrt(2 / PI) + sqrt_2_div_pi = 0.7978845608028654 + val = 0.5 * x * (1 + tanh_forward(sqrt_2_div_pi * (x + 0.044715 * x * x * x))) + return val + + +@triton.jit +def gelu_forward(x): + """ + Compute the exact GELU (Gaussian Error Linear Unit) activation function. + + This is the precise version of GELU using the cumulative distribution function (CDF) + of the standard normal distribution. The mathematical formula is: + f(x) = x * Φ(x) + where Φ(x) is the CDF of the standard normal distribution. + + Args: + x: Input tensor values + + Returns: + GELU activation output: x * Φ(x) + """ + return x * _standard_normal_cdf(x) + + +@triton.jit +def gelu_backward(x, dy): + return dy * (_standard_normal_cdf(x) + x * _standard_normal_pdf(x)) + + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _gelu_kernel(y_ptr, x_ptr, n_elements, BLOCK_SIZE: tl.constexpr, approximate): + pid = tl.program_id(axis=0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + + # Compute gelu + if approximate: + gelu_output = gelu_tanh_forward(x) + else: + gelu_output = gelu_forward(x) + + # Write back output to DRAM + tl.store(y_ptr + offsets, gelu_output, mask=mask) + + +@triton.jit +def _gelu_kernel_backward( + dx_ptr, + dy_ptr, + x_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(axis=0) # use a 1D launch grid so axis is 0. + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + gelu_grad_output = gelu_backward(x, dy) + + tl.store(dx_ptr + offsets, gelu_grad_output, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _GeLU(torch.autograd.Function): + @staticmethod + def forward(ctx, x, approximate): + assert approximate == "none" or approximate == "tanh", "Only `none` or `tanh` activations are supported" + + # Allocate output + y = torch.empty_like(x) + n_elements = y.numel() + + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + BLOCK_SIZE = 1024 + _gelu_kernel[grid](y, x, n_elements, BLOCK_SIZE, approximate == "tanh") + + ctx.x = x + return y + + @staticmethod + def backward(ctx, dy): + x = ctx.x + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + + dx = torch.empty_like(dy) + + BLOCK_SIZE = 1024 + _gelu_kernel_backward[grid](dx, dy, x, n_elements, BLOCK_SIZE) + + return dx, None + + +@register_impl("gelu", backend="triton") +def gelu(input: torch.Tensor, approximate="none"): + r""" + Returns GeLU activation of input. + If approximate is ``'tanh'`` then + $f(x) = 0.5 * x * (1 + \text{Tanh}(\sqrt(2 / \pi) * (x + 0.044715 * x^3)))$ + Else if approximate is ``'none'`` then + $f(x) = x * \Phi(x)$ + Where $Phi(x)$ is the Cumulative Distribution Function for Gaussian Distribution. + + Args: + input: Tensor + approximate: ``'none'`` or ``'tanh'`` + """ + return _GeLU.apply(input.view(-1), approximate).view(input.shape) + + +# {$nv-internal-release begin} + +# Backend Registration & Perf Markers # {$nv-internal-release} + +mark_perf_ready("gelu", "nvt") + +# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py new file mode 100644 index 0000000000..21dd352f79 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py @@ -0,0 +1,1222 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT +""" +TODO: in place ops not implemented +The ReLU family (ReLU, ELU, LeaklyReLU, PReLU, RReLU, SELU, CELU). +ReLU: x | 0 +ELU: x | alpha_constant * (exp(x) - 1) +LeakyReLU: x | alpha_constant * x +PReLU: x | alpha_learnable * x +RReLU: x | alpha_random * x +SELU: 1.67 * x | negative: 1.67*1.05 * (exp(x) - 1) +CELU: x | alpha_constant * (exp(x / alpha_constant) - 1) +""" + +import torch +import triton +import triton.language as tl + +from tilegym.backend import get_available_triton_backend +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl + +from .utils import exp_forward + +""" +======================================================= + ReLU +======================================================= +""" + +# Feature: relu # {$nv-internal-release} + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def relu_forward(x): + return tl.where(x > 0.0, x, 0.0) + + +@triton.jit +def relu_backward(x, dy): + dydx = tl.where(x > 0.0, 1.0, 0.0) + return dydx * dy + + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _relu_fwd_kernel( + x_ptr, + y_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + # write-back + y = relu_forward(x) + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _relu_bwd_kernel( + dy_ptr, + dx_ptr, + x_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + dx = relu_backward(x, dy) + tl.store(dx_ptr + offsets, dx, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _ReLU(torch.autograd.Function): + @staticmethod + def forward(ctx, x): + ctx.x = x + y = torch.empty_like(x) + + assert x.is_contiguous() + + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _relu_fwd_kernel[grid](x, y, n_elements, BLOCK_SIZE=1024) + + return y + + @staticmethod + def backward(ctx, dy): + dx = torch.empty_like(dy) + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _relu_bwd_kernel[grid](dy, dx, ctx.x, n_elements, BLOCK_SIZE=1024) + return dx + + +@register_impl("relu", backend="triton") +def relu(x): + r""" + Returns ReLU activation of x. + + Args: + x: Tensor + """ + return _ReLU.apply(x) + + +""" +======================================================= + ELU +======================================================= +""" + +# Feature: elu # {$nv-internal-release} + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _elu_fwd_kernel( + x_ptr, + y_ptr, + alpha, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + # write-back + y = tl.where(x > 0.0, x, alpha * (exp_forward(x) - 1)) + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _elu_bwd_kernel( + dy_ptr, + dx_ptr, + x_ptr, + alpha, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + dydx = tl.where(x > 0.0, 1.0, alpha * exp_forward(x)) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _ELU(torch.autograd.Function): + @staticmethod + def forward(ctx, x, alpha): + ctx.x = x + ctx.ALPHA = alpha + + y = torch.empty_like(x) + + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _elu_fwd_kernel[grid](x, y, alpha, n_elements, BLOCK_SIZE=1024) + + return y + + @staticmethod + def backward(ctx, dy): + dx = torch.empty_like(dy) + + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _elu_bwd_kernel[grid](dy, dx, ctx.x, ctx.ALPHA, n_elements, BLOCK_SIZE=1024) + + return dx, None + + +@register_impl("elu", backend="triton") +def elu(x, alpha=1.0): + r""" + Returns ELU activation of x. + + .. math:: + \text{ELU}(x) = \begin{cases} + x, & \text{ if } x > 0\\ + \alpha * (\exp(x) - 1), & \text{ if } x \leq 0 + \end{cases} + + Args: + x: Tensor + alpha: Float, default = 1.0 + """ + return _ELU.apply(x, alpha) + + +""" +======================================================= + LeakyReLU +======================================================= +""" + +# Feature: leaky_relu # {$nv-internal-release} + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _leaky_relu_fwd_kernel( + x_ptr, + y_ptr, + alpha, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + # write-back + y = tl.where(x > 0.0, x, alpha * x) + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _leaky_relu_bwd_kernel( + dy_ptr, + dx_ptr, + x_ptr, + alpha, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + dydx = tl.where(x > 0.0, 1.0, alpha) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _LeakyReLU(torch.autograd.Function): + @staticmethod + def forward(ctx, x, alpha): + ctx.x = x + ctx.ALPHA = alpha + + y = torch.empty_like(x) + + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _leaky_relu_fwd_kernel[grid](x, y, alpha, n_elements, BLOCK_SIZE=1024) + + return y + + @staticmethod + def backward(ctx, dy): + dx = torch.empty_like(dy) + + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _leaky_relu_bwd_kernel[grid](dy, dx, ctx.x, ctx.ALPHA, n_elements, BLOCK_SIZE=1024) + + return dx, None + + +@register_impl("leaky_relu", backend="triton") +def leaky_relu(x, negative_slope=0.01): + r""" + Returns Leaky ReLU activation of x. + + .. math:: + \text{LeakyReLU}(x) = + \begin{cases} + x, & \text{ if } x \geq 0 \\ + \text{negative\_slope} \times x, & \text{ otherwise } + \end{cases} + + Args: + x: Tensor + negative_slope: Float, default = 0.01 + """ + return _LeakyReLU.apply(x, negative_slope) + + +""" +======================================================= + PReLU +======================================================= +""" + +# Feature: prelu # {$nv-internal-release} + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _scalar_prelu_fwd_kernel( + x_ptr, + y_ptr, + w_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + """ + This is called when using scalar weight regardless of input shape. + Grids (BLOCK_SIZE). Treats input as vector. + Each block gets (BLOCK_SIZE,) input data, matching (1,) scalar weight. + """ + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + w = tl.load(w_ptr) + # write-back + y = tl.where(x > 0.0, x, w * x) + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _vector_prelu_nc_fwd_kernel( + x_ptr, + y_ptr, + w_ptr, + stride, + BLOCK_SIZE: tl.constexpr, +): + """ + This is called when using vector weights and input is 2 dimensional. (N, C). + Grids (N, C // BLOCK_SIZE). + Each block gets (1, BLOCK_SIZE) input data, matching (BLOCK_SIZE,) weights + """ + row = tl.program_id(0) + x_ptr += row * stride # move x to assigned row + y_ptr += row * stride # move y to assigned row + + col_start = tl.program_id(1) * BLOCK_SIZE + offsets = col_start + tl.arange(0, BLOCK_SIZE) # column offsets for x and w + mask = offsets < stride # mask out offsets going over a row + + # load in x and w + x = tl.load(x_ptr + offsets, mask=mask) # (BLOCK_SIZE,) + w = tl.load(w_ptr + offsets, mask=mask) # (BLOCK_SIZE,) + + # compute PReLU + y = tl.where(x > 0.0, x, w * x) # (BLOCK_SIZE,) + + # store in y + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _vector_prelu_nchw_fwd_kernel( + x_ptr, + y_ptr, + w_ptr, + stride_n, + stride_c, + stride_w, + C, + W, + BLOCK_SIZE_C: tl.constexpr, + BLOCK_SIZE_W: tl.constexpr, +): + """ + This is called when using vector weights and input is >2 dimensional. e.g. (N, C, H, W) + Grids(N, C // BLOCK_SIZE_C, W // BLOCK_SIZE_W) + Each block gets (1, BLOCK_SIZE_C, BLOCK_SIZE_W) input data, matchine (BLOCK_SIZE,) weights + """ + row = tl.program_id(0) + x_ptr += row * stride_n + y_ptr += row * stride_n + + col_start = tl.program_id(1) * BLOCK_SIZE_C + col_offsets = col_start + tl.arange(0, BLOCK_SIZE_C) + mask_C = col_offsets < C + + tub_start = tl.program_id(2) * BLOCK_SIZE_W + tub_offsets = tub_start + tl.arange(0, BLOCK_SIZE_W) + mask_W = tub_offsets < W + + offsets = col_offsets[:, None] * stride_c + tub_offsets[None, :] * stride_w + mask = mask_C[:, None] & mask_W[None, :] + + x = tl.load(x_ptr + offsets, mask=mask).to(tl.float32) # (BLOCK_SIZE_C, BLOCK_SIZE_W) + w = tl.load(w_ptr + col_offsets, mask=mask_C)[:, None] # (BLOCK_SIZE_C, 1) + + y = tl.where(x > 0.0, x, w * x) + + tl.store(y_ptr + offsets, y, mask=mask) + + +# {$nv-internal-release begin} +if get_available_triton_backend() == "nvt": + + @triton.jit + def _scalar_prelu_bwd_stage1_kernel( + dy_ptr, + dx_ptr, + dw_ptr, + x_ptr, + w_ptr, + lock_ptr, + n_elements, + n_weights, + BLOCK_SIZE: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, # this should be the number of groups + ): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + lock_id = pid % GROUP_SIZE_M + group_offset = lock_id * n_weights + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + w = tl.load(w_ptr) + + dydx = tl.where(x > 0.0, 1.0, w) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + dydw = tl.where(x > 0.0, 0.0, x) + dw = tl.sum(dydw * dy) + + r, token = tl.ext.atomic_cas( + lock_ptr + lock_id, + 0, + 1, + semantics="acquire", + scope="device", + has_result_token=True, + ) + while r == 1: + r, token = tl.ext.atomic_cas( + lock_ptr + lock_id, + 0, + 1, + semantics="acquire", + scope="device", + has_result_token=True, + ) + + rw, token = tl.ext.load(dw_ptr + group_offset, memToken=token, has_result_token=True) + dw += rw + token = tl.ext.store(dw_ptr + group_offset, dw, memToken=token, has_result_token=True) + tl.ext.atomic_xchg( + lock_ptr + lock_id, + 0, + memToken=token, + semantics="release", + scope="device", + ) + + +@triton.jit +def _vector_prelu_nc_bwd_stage1_kernel( + dy_ptr, + dx_ptr, + dw_ptr, + x_ptr, + w_ptr, + lock_ptr, + stride, + lock_stride, + BLOCK_SIZE: tl.constexpr, + GROUP_SIZE: tl.constexpr, +): + """ + This is called when using vector weights and input is 2 dimensional. (N, C). + Grids (N, C // BLOCK_SIZE). + Each block gets (1, BLOCK_SIZE) input data, matching (BLOCK_SIZE,) weights. + dw is a (GROUP_SIZE, C) matrix for parallel reduction. + Reduces (N, C) to (GROUP_SIZE, C) + The reduction requires grabbing one of (GROUP_SIZE, C // BLOCK_SIZE) locks. + """ + row = tl.program_id(0) + # move to assigned row + dx_ptr += row * stride + dy_ptr += row * stride + x_ptr += row * stride + + col_start = tl.program_id(1) * BLOCK_SIZE + offsets = col_start + tl.arange(0, BLOCK_SIZE) # column offsets for dy and w + mask = offsets < stride # mask out offsets going over a row + + lock_row = row % GROUP_SIZE # stage 1 reduction over rows + lock_col = tl.program_id(1) # every blocks in the same row gets a different lock. + lock_ptr += lock_row * lock_stride + lock_col + dw_ptr += lock_row * stride + + # load in dy, x, w + dy = tl.load(dy_ptr + offsets, mask=mask) # (BLOCK_SIZE,) + x = tl.load(x_ptr + offsets, mask=mask) # (BLOCK_SIZE,) + w = tl.load(w_ptr + offsets, mask=mask) # (BLOCK_SIZE,) + + # store in dx + dydx = tl.where(x > 0.0, 1.0, w) # (BLOCK_SIZE,) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + dydw = tl.where(x > 0.0, 0.0, x) # (BLOCK_SIZE,) + dw = dydw * dy # (BLOCK_SIZE,) we don't sum over the C axis as in scalar case + + # spin for lock -> load -> sum -> store -> release lock + r, token = tl.ext.atomic_cas( + lock_ptr, + 0, + 1, + semantics="acquire", + scope="device", + has_result_token=True, + ) + while r == 1: + r, token = tl.ext.atomic_cas( + lock_ptr, + 0, + 1, + semantics="acquire", + scope="device", + has_result_token=True, + ) + + rw, token = tl.ext.load(dw_ptr + offsets, mask=mask, memToken=token, has_result_token=True) + dw += rw + token = tl.ext.store(dw_ptr + offsets, dw, mask=mask, memToken=token, has_result_token=True) + tl.ext.atomic_xchg(lock_ptr, 0, memToken=token, semantics="release", scope="device") + + +@triton.jit +def _vector_prelu_nchw_bwd_stage1_kernel( + dy_ptr, + dx_ptr, + dw_ptr, + x_ptr, + w_ptr, + lock_ptr, + stride_n, + stride_c, + stride_w, + C, + W, + lock_stride, + BLOCK_SIZE_C: tl.constexpr, + BLOCK_SIZE_W: tl.constexpr, + GROUP_SIZE: tl.constexpr, +): + """ + This is called when using vector weights and input is >2 dimensional. (N, C, H, W). + Grids (N, C // BLOCK_SIZE_C, W // BLOCK_SIZE_W). + Each block gets (1, BLOCK_SIZE_C, BLOCK_SIZE_W) input data, matching (BLOCK_SIZE,) weights. + dw is a (GROUP_SIZE, C) matrix for parallel reduction. + Reduces (N, C) to (GROUP_SIZE, C) + The reduction requires grabbing one of (GROUP_SIZE, C // BLOCK_SIZE) locks. + """ + row = tl.program_id(0) + # move to assigned row + dx_ptr += row * stride_n + dy_ptr += row * stride_n + x_ptr += row * stride_n + + col_start = tl.program_id(1) * BLOCK_SIZE_C + col_offsets = col_start + tl.arange(0, BLOCK_SIZE_C) # column offsets for dy and w + mask_C = col_offsets < C # mask out offsets going over a row + + tub_start = tl.program_id(2) * BLOCK_SIZE_W + tub_offsets = tub_start + tl.arange(0, BLOCK_SIZE_W) + mask_W = tub_offsets < W + + offsets = col_offsets[:, None] * stride_c + tub_offsets[None, :] * stride_w + mask = mask_C[:, None] & mask_W[None, :] + + lock_row = row % GROUP_SIZE # stage 1 reduction over rows + lock_col = tl.program_id(1) # every blocks in the same row gets a different lock. + lock_ptr += lock_row * lock_stride + lock_col + dw_ptr += lock_row * C + + # load in dy, x, w + dy = tl.load(dy_ptr + offsets, mask=mask, other=0.0).to(tl.float32) # (BLOCK_SIZE_C, BLOCK_SIZE_W) + x = tl.load(x_ptr + offsets, mask=mask, other=0.0).to(tl.float32) # (BLOCK_SIZE_C, BLOCK_SIZE_W) + w = tl.load(w_ptr + col_offsets, mask=mask_C).to(tl.float32)[:, None] # (BLOCK_SIZE_C, 1) + + # store in dx + dydx = tl.where(x > 0.0, 1.0, w) # (BLOCK_SIZE_C, BLOCK_SIZE_W) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + dydw = tl.where(x > 0.0, 0.0, x) # (BLOCK_SIZE_C, BLOCK_SIZE_W) + dw = tl.sum(dydw * dy, axis=1).to(w.dtype) # (BLOCK_SIZE,) here we sum out the spatial axis. + + # spin for lock -> load -> sum -> store -> release lock + r, token = tl.ext.atomic_cas( + lock_ptr, + 0, + 1, + semantics="acquire", + scope="device", + has_result_token=True, + ) + while r == 1: + r, token = tl.ext.atomic_cas( + lock_ptr, + 0, + 1, + semantics="acquire", + scope="device", + has_result_token=True, + ) + + rw, token = tl.ext.load( + dw_ptr + col_offsets, + mask=mask_C, + memToken=token, + has_result_token=True, + ) + dw += rw + token = tl.ext.store( + dw_ptr + col_offsets, + dw, + mask=mask_C, + memToken=token, + has_result_token=True, + ) + tl.ext.atomic_xchg(lock_ptr, 0, memToken=token, semantics="release", scope="device") + + +@triton.jit +def _vector_prelu_nc_bwd_stage2_kernel( + _dw_ptr, + dw_ptr, + N, + C, + BLOCK_SIZE_N: tl.constexpr, + BLOCK_SIZE_C: tl.constexpr, +): + """ + This is stage 2 of the previous kernel. + Grids (C // BLOCK_SIZE_C) + Each block gets (N, BLOCK_SIZE_C) _dw data from previous stage, sums out the first axis. + Reduces (N, C) to (C,) + Note that N here is equal to GROUP_SIZE from prvious stage. + """ + col_start = tl.program_id(0) * BLOCK_SIZE_C + col_offsets = col_start + tl.arange(0, BLOCK_SIZE_C) + + dw = tl.zeros((BLOCK_SIZE_N, BLOCK_SIZE_C), dtype=tl.float32) # TODO: generalize dtype here? + + for row_start in range(0, N, BLOCK_SIZE_N): + row_offsets = row_start + tl.arange(0, BLOCK_SIZE_N) + mask = (row_offsets[:, None] < N) & (col_offsets[None, :] < C) + offsets = row_offsets[:, None] * C + col_offsets[None, :] + dw += tl.load(_dw_ptr + offsets, mask=mask, other=0.0) + sum_dw = tl.sum(dw, axis=0) + tl.store(dw_ptr + col_offsets, sum_dw, mask=col_offsets < C) + + +# {$nv-internal-release end} + +# Host Launchers & Public API # {$nv-internal-release} + + +class _PReLU(torch.autograd.Function): + @staticmethod + def forward(ctx, x, w): + # weight should either be a scalar or match the number of channels of input. + n_weights = w.numel() + assert n_weights == 1 or (x.dim() > 1 and n_weights == x.shape[1]) + + ctx.x = x + ctx.w = w + + y = torch.empty_like(x) + + if n_weights == 1: + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _scalar_prelu_fwd_kernel[grid](x, y, w, n_elements, BLOCK_SIZE=1024) + elif x.dim() == 2: + N, C = x.shape + grid = lambda meta: (N, triton.cdiv(C, meta["BLOCK_SIZE"])) + _vector_prelu_nc_fwd_kernel[grid](x, y, w, x.stride(0), BLOCK_SIZE=1024) + else: + x_squashed = x.view(x.shape[0], x.shape[1], -1) + N, C, W = x_squashed.shape + stride_n, stride_c, stride_w = x_squashed.stride() + grid = lambda meta: ( + N, + triton.cdiv(C, meta["BLOCK_SIZE_C"]), + triton.cdiv(W, meta["BLOCK_SIZE_W"]), + ) + _vector_prelu_nchw_fwd_kernel[grid]( + x, + y, + w, + stride_n, + stride_c, + stride_w, + C, + W, + BLOCK_SIZE_C=32, + BLOCK_SIZE_W=256, + ) + + return y + + @staticmethod + def backward(ctx, dy): + # {$nv-internal-release begin} + if get_available_triton_backend() == "nvt": + GROUP_SIZE = 256 + BLOCK_SIZE = 1024 + n_weights = ctx.w.numel() + + dx = torch.empty_like(dy) + dw = torch.empty_like(ctx.w) + _dw = torch.zeros( + (GROUP_SIZE, ctx.w.numel()), + dtype=torch.float32, + device=ctx.w.device, + ) + + if n_weights == 1: + n_elements = dy.numel() + locks = torch.zeros(GROUP_SIZE, dtype=torch.int32, device=ctx.w.device) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _scalar_prelu_bwd_stage1_kernel[grid]( + dy, + dx, + _dw, + ctx.x, + ctx.w, + locks, + n_elements, + n_weights, + BLOCK_SIZE=BLOCK_SIZE, + GROUP_SIZE_M=GROUP_SIZE, + ) + dw = _dw.sum( + dim=0, keepdim=True + ) # TODO: am I allowed to do this? it's just summing a 64d vector into a scalar. + elif dy.dim() == 2: + N, C = dy.shape + num_blocks_per_row = triton.cdiv(C, BLOCK_SIZE) + locks = torch.zeros( + (GROUP_SIZE, num_blocks_per_row), + dtype=torch.int32, + device=ctx.w.device, + ) + grid = lambda meta: (N, num_blocks_per_row) + _vector_prelu_nc_bwd_stage1_kernel[grid]( + dy, + dx, + _dw, + ctx.x, + ctx.w, + locks, + dy.stride(0), + locks.stride(0), + BLOCK_SIZE=BLOCK_SIZE, + GROUP_SIZE=GROUP_SIZE, + ) + grid = lambda meta: (triton.cdiv(C, meta["BLOCK_SIZE_C"]),) + _vector_prelu_nc_bwd_stage2_kernel[grid](_dw, dw, GROUP_SIZE, C, BLOCK_SIZE_N=32, BLOCK_SIZE_C=128) + else: + dy_squashed = dy.view(dy.shape[0], dy.shape[1], -1) + N, C, W = dy_squashed.shape + stride_n, stride_c, stride_w = dy_squashed.stride() + BLOCK_SIZE_W = 32 + BLOCK_SIZE_C = BLOCK_SIZE // BLOCK_SIZE_W + num_blocks_per_row = triton.cdiv(C, BLOCK_SIZE_C) + locks = torch.zeros( + (GROUP_SIZE, num_blocks_per_row), + dtype=torch.int32, + device=ctx.w.device, + ) + grid = lambda meta: ( + N, + num_blocks_per_row, + triton.cdiv(W, BLOCK_SIZE_W), + ) + _vector_prelu_nchw_bwd_stage1_kernel[grid]( + dy, + dx, + _dw, + ctx.x, + ctx.w, + locks, + stride_n, + stride_c, + stride_w, + C, + W, + locks.stride(0), + BLOCK_SIZE_C=BLOCK_SIZE_C, + BLOCK_SIZE_W=BLOCK_SIZE_W, + GROUP_SIZE=GROUP_SIZE, + ) + dw = _dw.sum(0) + # grid = lambda meta: (triton.cdiv(C, meta['BLOCK_SIZE_C']),) + # _vector_prelu_nc_bwd_stage2_kernel[grid]( + # _dw, dw, GROUP_SIZE, C, BLOCK_SIZE_N=32, BLOCK_SIZE_C=128 + # ) + + return dx, dw + # {$nv-internal-release end} + raise NotImplementedError("Backward is not supported") + + +@register_impl("prelu", backend="triton") +def prelu(x, weight): + r""" + Returns PReLU activation of x. + + .. math:: + \text{PReLU}(x) = + \begin{cases} + x, & \text{ if } x \geq 0 \\ + ax, & \text{ otherwise } + \end{cases} + + Here weight is a learnable parameter. It could be a vector or a scalar. + When weight is a vector, its number of elements should match the number + of channels of x. When weight is a scalar, the same weight is used across + all input channels. + + Args: + x: Tensor (N, C, \*) + weight: Tensor (C,) or (1,) + """ + return _PReLU.apply(x, weight) + + +""" +======================================================= + RReLU +======================================================= +""" + +# Feature: rrelu # {$nv-internal-release} + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _rrelu_fwd_kernel( + x_ptr, + y_ptr, + seed, + lower, + upper, + training, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + # randomly generate alpha ~ U(lower, upper) + if training: + alpha = tl.rand(seed, offsets) * (upper - lower) + lower + y = tl.where(x > 0.0, x, alpha * x) + else: + y = tl.where(x > 0.0, x, x * (upper + lower) / 2.0) + # write-back + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _rrelu_bwd_kernel( + dy_ptr, + dx_ptr, + x_ptr, + seed, + lower, + upper, + training, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + if training: + alpha = tl.rand(seed, offsets) * (upper - lower) + lower + dydx = tl.where(x > 0.0, 1.0, alpha) + else: + dydx = tl.where(x > 0.0, 1.0, (upper + lower) / 2.0) + + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _RReLU(torch.autograd.Function): + @staticmethod + def forward(ctx, x, seed, lower, upper, training): + ctx.x = x + ctx.seed = seed + ctx.lower = lower + ctx.upper = upper + ctx.training = training + + y = torch.empty_like(x) + + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _rrelu_fwd_kernel[grid](x, y, seed, lower, upper, training, n_elements, BLOCK_SIZE=1024) + + return y + + @staticmethod + def backward(ctx, dy): + dx = torch.empty_like(dy) + + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _rrelu_bwd_kernel[grid]( + dy, + dx, + ctx.x, + ctx.seed, + ctx.lower, + ctx.upper, + ctx.training, + n_elements, + BLOCK_SIZE=1024, + ) + + return dx, None, None, None, None + + +@register_impl("rrelu", backend="triton") +def rrelu(x, seed, lower=1.0 / 8, upper=1.0 / 3, training=False): + r""" + Returns RReLU activation of x. + + .. math:: + \text{RReLU}(x) = + \begin{cases} + x & \text{if } x \geq 0 \\ + ax & \text{ otherwise } + \end{cases} + + If training is true, then $a \sim U(\text{lower}, \text{upper})$ for each input element. + Otherwise, $a = \frac{\text{lower} + \text{upper}}2$ + + Args: + x: Tensor + seed: Float + lower: Float, default = 0.125 + upper: Float, default = 1 / 3 + training: Boolean, default = False + """ + return _RReLU.apply(x, seed, lower, upper, training) + + +""" +======================================================= + SELU +======================================================= +""" + +# Feature: selu # {$nv-internal-release} + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _selu_fwd_kernel( + x_ptr, + y_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + # params + scale = 1.0507009873554804934193349852946 + alpha = 1.6732632423543772848170429916717 + # write-back + y = scale * tl.where(x > 0.0, x, alpha * (exp_forward(x) - 1)) + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _selu_bwd_kernel( + dy_ptr, + dx_ptr, + x_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + scale = 1.0507009873554804934193349852946 + alpha = 1.6732632423543772848170429916717 + + dydx = scale * tl.where(x > 0.0, 1.0, alpha * exp_forward(x)) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _SELU(torch.autograd.Function): + @staticmethod + def forward(ctx, x): + ctx.x = x + + y = torch.empty_like(x) + + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _selu_fwd_kernel[grid](x, y, n_elements, BLOCK_SIZE=1024) + + return y + + @staticmethod + def backward(ctx, dy): + dx = torch.empty_like(dy) + + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _selu_bwd_kernel[grid](dy, dx, ctx.x, n_elements, BLOCK_SIZE=1024) + + return dx + + +@register_impl("selu", backend="triton") +def selu(x): + r""" + Returns SeLU activation of x. + + .. math:: + \text{SELU}(x) = \text{scale} * (\max(0,x) + \min(0, \alpha * (\exp(x) - 1))) + + + with :math:`\alpha = 1.6732632423543772848170429916717` and + :math:`\text{scale} = 1.0507009873554804934193349852946`. + + Args: + x: Tensor + """ + return _SELU.apply(x) + + +""" +======================================================= + CELU +======================================================= +""" + +# Feature: celu # {$nv-internal-release} + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _celu_fwd_kernel( + x_ptr, + y_ptr, + alpha, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + # compute memory offsets of elements handled by this instance + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + # load data from x + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + # write-back + y = tl.where(x > 0.0, x, alpha * (tl.exp(x / alpha) - 1)) + tl.store(y_ptr + offsets, y, mask=mask) + + +@triton.jit +def _celu_bwd_kernel( + dy_ptr, + dx_ptr, + x_ptr, + alpha, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + dydx = tl.where(x > 0.0, 1.0, tl.exp(x / alpha)) + dx = dydx * dy + tl.store(dx_ptr + offsets, dx, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _CELU(torch.autograd.Function): + @staticmethod + def forward(ctx, x, alpha): + ctx.x = x + ctx.alpha = alpha + + y = torch.empty_like(x) + + n_elements = x.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _celu_fwd_kernel[grid](x, y, alpha, n_elements, BLOCK_SIZE=1024) + + return y + + @staticmethod + def backward(ctx, dy): + dx = torch.empty_like(dy) + + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + _celu_bwd_kernel[grid](dy, dx, ctx.x, ctx.alpha, n_elements, BLOCK_SIZE=1024) + + return dx, None + + +@register_impl("celu", backend="triton") +def celu(x, alpha=1.0): + r""" + Returns CeLU actication of input. + + .. math:: + \text{CELU}(x) = \max(0,x) + \min(0, \alpha * (\exp(x/\alpha) - 1)) + + Args: + input: Tensor + alpha: Float, default = 1.0 + """ + return _CELU.apply(x, alpha) + + +# {$nv-internal-release begin} + + +mark_perf_ready("celu", "nvt") +mark_perf_ready("elu", "nvt") +mark_perf_ready("leaky_relu", "nvt") +mark_perf_ready("prelu", "nvt") +mark_perf_ready("relu", "nvt") +mark_perf_ready("rrelu", "nvt") +mark_perf_ready("selu", "nvt") + +# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py new file mode 100644 index 0000000000..b09c65bdc3 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py @@ -0,0 +1,114 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import torch +import triton +import triton.language as tl + +from tilegym.backend import register_impl + +from .utils import sigmoid_forward + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def silu_forward(x): + return x * sigmoid_forward(x) + + +@triton.jit +def silu_backward(x, dy): + return dy * (sigmoid_forward(x) * (1.0 + x * (1.0 - sigmoid_forward(x)))) + + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _silu_kernel( + y_ptr, + x_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(axis=0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + + # Compute silu + silu_output = silu_forward(x) + + # Write back output to DRAM + tl.store(y_ptr + offsets, silu_output, mask=mask) + + +@triton.jit +def _silu_kernel_backward( + dx_ptr, + dy_ptr, + x_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(axis=0) # use a 1D launch grid so axis is 0. + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + x = tl.load(x_ptr + offsets, mask=mask) + + silu_grad_output = silu_backward(x, dy) + + tl.store(dx_ptr + offsets, silu_grad_output, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _SiLU(torch.autograd.Function): + @staticmethod + def forward(ctx, x): + # Allocate output + y = torch.empty_like(x) + n_elements = y.numel() + + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + + _silu_kernel[grid](y, x, n_elements, BLOCK_SIZE=1024) + ctx.x = x + return y + + @staticmethod + def backward(ctx, dy): + x = ctx.x + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + + dx = torch.empty_like(dy) + + _silu_kernel_backward[grid](dx, dy, x, n_elements=n_elements, BLOCK_SIZE=1024) + + return dx + + +@register_impl("silu", backend="triton") +def silu( + input: torch.Tensor, +): + r""" + Returns SiLU activation of input. + + .. math:: + f(x) = x * \sigma (x) + + Args: + input: Tensor + """ + return _SiLU.apply(input.view(-1)).view(input.shape) diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py b/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py new file mode 100644 index 0000000000..f99b2ec99f --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py @@ -0,0 +1,110 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import torch +import triton +import triton.language as tl + +from tilegym.backend import register_impl + +from .utils import sigmoid_forward + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def tanh_forward(x): + return 2 * sigmoid_forward(2 * x) - 1 + + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _tanh_kernel( + y_ptr, + x_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(axis=0) + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + x = tl.load(x_ptr + offsets, mask=mask) + + # Compute tanh + tanh_output = tanh_forward(x) + + # Write back output to DRAM + tl.store(y_ptr + offsets, tanh_output, mask=mask) + + +@triton.jit +def _tanh_kernel_backward( + dx_ptr, + dy_ptr, + y_ptr, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(axis=0) # use a 1D launch grid so axis is 0. + block_start = pid * BLOCK_SIZE + offsets = block_start + tl.arange(0, BLOCK_SIZE) + + mask = offsets < n_elements + dy = tl.load(dy_ptr + offsets, mask=mask) + y = tl.load(y_ptr + offsets, mask=mask) + + tanh_grad_output = dy * (1 - y * y) + + tl.store(dx_ptr + offsets, tanh_grad_output, mask=mask) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _Tanh(torch.autograd.Function): + @staticmethod + def forward(ctx, x): + # Allocate output + y = torch.empty_like(x) + n_elements = y.numel() + + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + + _tanh_kernel[grid](y, x, n_elements, BLOCK_SIZE=1024) + + ctx.y = y + return y + + @staticmethod + def backward(ctx, dy): + y = ctx.y + n_elements = dy.numel() + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + + dx = torch.empty_like(dy) + + _tanh_kernel_backward[grid](dx, dy, y, n_elements=n_elements, BLOCK_SIZE=1024) + + return dx + + +@register_impl("tanh", backend="triton") +def tanh( + input: torch.Tensor, +): + r""" + Returns Tanh actication of input. + + .. math:: + f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} + + Args: + input: Tensor + """ + return _Tanh.apply(input.view(-1)).view(input.shape) diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/utils.py b/python/tutorials/tileir/tilegym/ops/triton/activation/utils.py new file mode 100644 index 0000000000..f6dab33a62 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/utils.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +import triton +import triton.language as tl + + +@triton.jit +def sigmoid_forward(x): + out_dtype = x.dtype + return tl.sigmoid(x.to(tl.float32)).to(out_dtype) + + +@triton.jit +def exp_forward(x): + out_dtype = x.dtype + return tl.exp(x.to(tl.float32)).to(out_dtype) + + +@triton.jit +def log_forward(x): + out_dtype = x.dtype + return tl.log(x.to(tl.float32)).to(out_dtype) diff --git a/python/tutorials/tileir/tilegym/ops/triton/attention.py b/python/tutorials/tileir/tilegym/ops/triton/attention.py new file mode 100644 index 0000000000..d0f72a3e85 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/attention.py @@ -0,0 +1,1130 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import inspect +import math +from typing import Optional + +import torch +import triton +import triton.language as tl +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.autotune import is_autotune_disabled +from tilegym.backend import get_available_triton_backend +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl +from tilegym.logger import get_logger + +# Constants & Type Aliases # {$nv-internal-release} + +logger = get_logger(__name__) + +INV_LOG_2 = tl.constexpr(1.0 / math.log(2)) +# {$nv-internal-release begin} +# Check if triton.Config supports v2_opt_level parameter + +# Capability Probe # {$nv-internal-release} + + +def _supports_v2_opt_level(): + try: + sig = inspect.signature(triton.Config.__init__) + return "v2_opt_level" in sig.parameters + except Exception: + return False + + +_TRITON_SUPPORTS_V2_OPT_LEVEL = _supports_v2_opt_level() + +# {$nv-internal-release end} + + +def _supports_host_descriptor(): + return torch.cuda.get_device_capability()[0] >= 9 + + +def _supports_tma(tensor: torch.Tensor): + # Check if the tensor stride is divisible by 16 bytes + # Mainly for the non-even sequence length case + return torch.finfo(tensor.dtype).bits * tensor.stride(-2) // 8 % 16 == 0 + + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def _maybe_make_tensor_desc(desc_or_ptr, shape, strides, block_shape): + if isinstance(desc_or_ptr, tl.tensor_descriptor): + return desc_or_ptr + else: + return tl.make_tensor_descriptor(desc_or_ptr, shape, strides, block_shape) + + +@triton.jit +def _attn_fwd_inner( + K_desc, + V_desc, + acc, + l_i, + m_i, + q, + batch_idx, + off_kv_h, + start_m, + qk_scale, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_D: tl.constexpr, + STAGE: tl.constexpr, + offs_m: tl.constexpr, + offs_n: tl.constexpr, + N_CTX: tl.constexpr, + EVEN_K: tl.constexpr, + warp_specialize: tl.constexpr, + prefix_kvlen, +): + # Range of values handled by this stage + if STAGE == 1: + lo, hi = 0, start_m * BLOCK_M # start_m=0,1,2,3 + elif STAGE == 2: + lo, hi = start_m * BLOCK_M, (start_m + 1) * BLOCK_M + lo = tl.multiple_of(lo, BLOCK_M) + # causal = False + else: + lo, hi = 0, N_CTX + cnt = lo // BLOCK_N + # Loop over k, v and update accumulator + # Here use warp_specialize=True only for best performance in oait, for nvt, it will ignore this argument + for curr_n in range(lo, hi, BLOCK_N, warp_specialize=warp_specialize): + curr_n = tl.multiple_of(curr_n, BLOCK_N) + # -- Compute qk ---- + k = K_desc.load([batch_idx, off_kv_h, cnt * BLOCK_N, 0]) + k = tl.reshape(k, (BLOCK_N, BLOCK_D)) + # Transpose K to get (BLOCK_D, BLOCK_N) for matrix multiplication + k = tl.trans(k) + qk = tl.dot(q, k) + # Process boundary case(here we only need to process non-causal case) + if STAGE == 3 and not EVEN_K: + mask = curr_n + offs_n[None, :] < N_CTX + qk = tl.where(mask, qk, -1.0e6) + # Apply causal mask: query at global position (prefix_kvlen + offs_m) attends to KV[0..that pos] + if STAGE == 2: + mask = (prefix_kvlen + offs_m[:, None]) >= (curr_n + offs_n[None, :]) + qk = tl.where(mask, qk, -1.0e6) + m_ij = tl.maximum(m_i, tl.max(qk, 1) * qk_scale) + qk = qk * qk_scale - m_ij[:, None] + + # Attention weights + p = tl.math.exp2(qk) + l_ij = tl.sum(p, 1) + # Update m_i and l_i + alpha = tl.math.exp2(m_i - m_ij) + l_i = l_i * alpha + l_ij + # Update output accumulator + acc = acc * alpha[:, None] + # Update acc + v = V_desc.load([batch_idx, off_kv_h, cnt * BLOCK_N, 0]) + v = tl.reshape(v, (BLOCK_N, BLOCK_D)) + p = p.to(q.dtype) + acc = tl.dot(p, v, acc) + m_i = m_ij + cnt += 1 + + return acc, l_i, m_i + + +# The `_impl` suffix denotes the core kernel logic, separated from the +# @triton.autotune'd entry point so it can be reused by different kernels +# (e.g. standalone prefill attention and fused POD attention) without +# duplicating the attention computation code. +@triton.jit +def prefill_fmha_impl( + Q, + K, + V, + Out, + L, + sm_scale, + stride_qb, + stride_qh, + stride_qm, + stride_kb, + stride_kh, + stride_kn, + stride_vb, + stride_vh, + stride_vk, + stride_ob, + stride_oh, + stride_om, + stride_lb, + stride_lh, + B, + H, + S_qo, + S_kv, + BHS_qo, + BHS_kv, + pid_x, + pid_y, + HAS_BACKWARD: tl.constexpr, + USE_DESC_FOR_CORR: tl.constexpr, + BLOCK_D: tl.constexpr, # BLOCK_D = hidden_size + STAGE: tl.constexpr, + QUERY_GROUP_SIZE: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + EVEN_K: tl.constexpr, + dtype: tl.constexpr, + warp_specialize: tl.constexpr, +): + if isinstance(Q, tl.tensor_descriptor): + dtype = Q.type.block_type.element_ty + else: + dtype = Q.dtype.element_ty + batch_idx = pid_y // H + head_idx = pid_y % H + if QUERY_GROUP_SIZE: + off_kv_h = head_idx // QUERY_GROUP_SIZE + else: + off_kv_h = head_idx + qk_scale = sm_scale * INV_LOG_2 + + # Initialize offsets + offs_m = pid_x * BLOCK_M + tl.arange(0, BLOCK_M) + offs_n = tl.arange(0, BLOCK_N) + + # Create tensor descriptors + Q_desc = _maybe_make_tensor_desc( + Q, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_qb, stride_qh, stride_qm, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + + # For K, we need to load BLOCK_N x BLOCK_D but then transpose to BLOCK_D x BLOCK_N + K_desc = _maybe_make_tensor_desc( + K, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_kb, stride_kh, stride_kn, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + + V_desc = _maybe_make_tensor_desc( + V, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_vb, stride_vh, stride_vk, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + + O_desc = _maybe_make_tensor_desc( + Out, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_ob, stride_oh, stride_om, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + + if USE_DESC_FOR_CORR: + L_desc = _maybe_make_tensor_desc( + L, + shape=[B, H, S_qo], + strides=[stride_lb, stride_lh, 1], + block_shape=[1, 1, BLOCK_M], + ) + + # Initialize m, l, acc + m_i = tl.full([BLOCK_M], -float("inf"), dtype=tl.float32) + l_i = tl.full([BLOCK_M], 1.0, dtype=tl.float32) + acc = tl.zeros([BLOCK_M, BLOCK_D], dtype=tl.float32) + + # Load q - calculate explicit offsets + q_offset_b = batch_idx * 1 + q_offset_h = head_idx * 1 + q_offset_m = pid_x * BLOCK_M + q_offset_d = 0 * BLOCK_D + q = Q_desc.load([q_offset_b, q_offset_h, q_offset_m, q_offset_d]) + q = tl.reshape(q, (BLOCK_M, BLOCK_D)) + + # chunked prefill: query token i attends to KV[0..prefix_kvlen+i] + prefix_kvlen = S_kv - S_qo + start_m = prefix_kvlen // BLOCK_M + pid_x + + # For causal = False, STAGE = 1, and _attn_fwd_inner gets 3 as its STAGE + if STAGE & 1: + acc, l_i, m_i = _attn_fwd_inner( + K_desc, + V_desc, + acc, + l_i, + m_i, + q, + batch_idx, + off_kv_h, + start_m, + qk_scale, + BLOCK_M, + BLOCK_N, + BLOCK_D, + 4 - STAGE, + offs_m, + offs_n, + S_kv, + EVEN_K, + warp_specialize, + prefix_kvlen, + ) + # Stage 2: on-band + if STAGE & 2: + # Barrier makes it easier for compielr to schedule the + # Two loops independently + acc, l_i, m_i = _attn_fwd_inner( + K_desc, + V_desc, + acc, + l_i, + m_i, + q, + batch_idx, + off_kv_h, + start_m, + qk_scale, + BLOCK_M, + BLOCK_N, + BLOCK_D, + 2, + offs_m, + offs_n, + S_kv, + EVEN_K, + warp_specialize, + prefix_kvlen, + ) + # Epilogue + acc = acc / (l_i[:, None]) + l_i = m_i + tl.math.log2(l_i) + + # Write back l and o - calculate explicit offsets + if HAS_BACKWARD: + l_offset_b = batch_idx * 1 + l_offset_h = head_idx * 1 + l_offset_m = pid_x * BLOCK_M + if USE_DESC_FOR_CORR: + L_desc.store([l_offset_b, l_offset_h, l_offset_m], l_i.reshape(1, 1, BLOCK_M)) + else: + offset_s_ptrs = l_offset_m + tl.reshape(tl.arange(0, BLOCK_M), (1, 1, BLOCK_M)) + offset_ptrs = l_offset_b * stride_lb + l_offset_h * stride_lh + offset_s_ptrs + tl.store(L + offset_ptrs, l_i.reshape(1, 1, BLOCK_M), mask=offset_s_ptrs < S_qo) + + O_desc.store( + [batch_idx, head_idx, pid_x * BLOCK_M, 0], + acc.to(dtype).reshape(1, 1, BLOCK_M, BLOCK_D), + ) + + +# Autotune Config # {$nv-internal-release} + + +def _host_descriptor_pre_hook_fwd(nargs): + BLOCK_M = nargs["BLOCK_M"] + BLOCK_N = nargs["BLOCK_N"] + BLOCK_D = nargs["BLOCK_D"] + if not isinstance(nargs["Q"], TensorDescriptor): + return + nargs["Q"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + nargs["K"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["V"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["Out"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + if isinstance(nargs["L"], TensorDescriptor): + nargs["L"].block_shape = [1, 1, BLOCK_M] + + +def _host_descriptor_pre_hook_bwd(nargs): + BLOCK_M = nargs["BLOCK_M"] + BLOCK_N = nargs["BLOCK_N"] + BLOCK_D = nargs["BLOCK_D"] + if not isinstance(nargs["dO"], TensorDescriptor): + return + nargs["dO"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + # bwd preprocess kernel + if "minus_L" in nargs: + nargs["Out"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + if isinstance(nargs["minus_L"], TensorDescriptor): + nargs["minus_L"].block_shape = [1, 1, BLOCK_M] + nargs["minus_Delta"].block_shape = [1, 1, BLOCK_M] + nargs["L"].block_shape = [1, 1, BLOCK_M] + # bwd kernel + else: + nargs["Q"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + nargs["K"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["V"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["dQ"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + nargs["dK"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["dV"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + if isinstance(nargs["L"], TensorDescriptor): + nargs["L"].block_shape = [1, 1, BLOCK_M] + nargs["Delta"].block_shape = [1, 1, BLOCK_M] + + +def _get_configs(is_backward=False): + if _supports_host_descriptor(): + _hook = _host_descriptor_pre_hook_bwd if is_backward else _host_descriptor_pre_hook_fwd + else: + _hook = None + + if get_available_triton_backend() == "nvt": + default_config = {"warp_specialize": False} + if torch.cuda.get_device_capability() in [(12, 0), (12, 1)]: + configs = [ + triton.Config(dict(BLOCK_M=64, BLOCK_N=64, occupancy=2, **default_config), pre_hook=_hook), + ] + elif torch.cuda.get_device_capability() == (9, 0): + configs = [ + # occupancy = 2. We currently do not expose num_warps in CudaTile. + # Once we do (and if we do), we can experiment with occupancy = 1 and num_warps = 8. + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=2, **default_config), num_stages=s, pre_hook=_hook) + for BM in [64, 128] + for BN in [64, 128] + for s in [2, 3] + ] + elif torch.cuda.get_device_capability() == (8, 0): + configs = [ + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=occ, **default_config), num_stages=s, pre_hook=_hook + ) + for BM in [64, 128] + for BN in [32, 64] + for s in [2, 3, 4] + for occ in [1, 2] + ] + else: + if is_backward: + configs = [ + triton.Config(dict(BLOCK_M=128, BLOCK_N=128, **default_config), num_stages=7, pre_hook=_hook) + ] + else: + configs = [ + triton.Config(dict(BLOCK_M=256, BLOCK_N=128, **default_config), pre_hook=_hook), + triton.Config(dict(BLOCK_M=128, BLOCK_N=128, occupancy=2, **default_config), pre_hook=_hook), + triton.Config(dict(BLOCK_M=256, BLOCK_N=128, occupancy=2, **default_config), pre_hook=_hook), + ] + # GB300 (sm103): avoid num_ctas>1 which causes PTX codegen error + # (mixed cta_group::1 and cta_group::2). See CFK-33703. + if torch.cuda.get_device_capability() != (10, 3): + configs.append( + triton.Config( + dict(BLOCK_M=256, BLOCK_N=128, occupancy=2, **default_config), num_ctas=2, pre_hook=_hook + ) + ) + # {$nv-internal-release-oait begin} + elif get_available_triton_backend() == "oait": + # Full tuning space for oait + if is_backward: + # Force num_stages=1 to work around oait bug for bwd, see https://github.com/triton-lang/triton/issues/7386 + configs = [ + triton.Config( + dict(BLOCK_M=128, BLOCK_N=64, warp_specialize=False), num_stages=1, num_warps=4, pre_hook=_hook + ) + ] + configs = list(configs) + else: + configs = [ + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, warp_specialize=ws), num_stages=s, num_warps=w, pre_hook=_hook + ) + for BM in [64, 128, 256] + for BN in [64, 128] + for s in [2, 3, 4] + for w in [4, 8] + for ws in [True, False] + ] + if torch.cuda.get_device_capability() == (8, 0): + configs = [ + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, warp_specialize=False), num_stages=s, num_warps=w) + for BM in [64, 128] + for BN in [32, 64] + for s in [2, 4] + for w in [4, 8] + ] + # {$nv-internal-release-oait end} + return configs + + +def _prune_invalid_configs(configs, named_args, **kwargs): + # Filter out configs where BLOCK_M % BLOCK_N != 0 when causal is True + if kwargs["STAGE"] == 3: + return [conf for conf in configs if conf.kwargs["BLOCK_M"] % conf.kwargs["BLOCK_N"] == 0] + else: + return configs + + +# Device Kernels # {$nv-internal-release} + + +@triton.autotune( + configs=_get_configs(), + key=["S_qo", "S_kv", "BLOCK_D", "STAGE", "QUERY_GROUP_SIZE", "dtype"], + prune_configs_by={"early_config_prune": _prune_invalid_configs}, +) +@triton.heuristics( + { + "EVEN_K": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, + } +) +@triton.jit +def _prefill_fmha( + Q, + K, + V, + Out, + L, + sm_scale, + stride_qb, + stride_qh, + stride_qm, + stride_kb, + stride_kh, + stride_kn, + stride_vb, + stride_vh, + stride_vk, + stride_ob, + stride_oh, + stride_om, + stride_lb, + stride_lh, + B, + H, + S_qo, + S_kv, + BHS_qo, + BHS_kv, + HAS_BACKWARD: tl.constexpr, + USE_DESC_FOR_CORR: tl.constexpr, + BLOCK_D: tl.constexpr, # BLOCK_D = hidden_size + STAGE: tl.constexpr, + QUERY_GROUP_SIZE: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + EVEN_K: tl.constexpr, + dtype: tl.constexpr, + warp_specialize: tl.constexpr, +): + pid_x = tl.program_id(0) + pid_y = tl.program_id(1) + prefill_fmha_impl( + Q, + K, + V, + Out, + L, + sm_scale, + stride_qb, + stride_qh, + stride_qm, + stride_kb, + stride_kh, + stride_kn, + stride_vb, + stride_vh, + stride_vk, + stride_ob, + stride_oh, + stride_om, + stride_lb, + stride_lh, + B, + H, + S_qo, + S_kv, + BHS_qo, + BHS_kv, + pid_x, + pid_y, + HAS_BACKWARD, + USE_DESC_FOR_CORR, + BLOCK_D, + STAGE, + QUERY_GROUP_SIZE, + BLOCK_M, + BLOCK_N, + EVEN_K, + dtype, + warp_specialize, + ) + + +@triton.autotune( + configs=_get_configs(is_backward=True), + key=["S_qo", "USE_DESC_FOR_CORR"], +) +@triton.jit +def _fmha_bwd_preprocess_kernel( + Out, + dO, + L, + minus_Delta, + minus_L, + stride_ob, + stride_oh, + stride_om, + stride_lb, + stride_lh, + B, + H, + S_qo, + softmax_scale, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_D: tl.constexpr, + USE_DESC_FOR_CORR: tl.constexpr, + warp_specialize: tl.constexpr, +): + pid_x = tl.program_id(0) + pid_y = tl.program_id(1) + O_desc = _maybe_make_tensor_desc( + Out, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_ob, stride_oh, stride_om, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + dO_desc = _maybe_make_tensor_desc( + dO, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_ob, stride_oh, stride_om, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + if USE_DESC_FOR_CORR: + L_desc = _maybe_make_tensor_desc( + L, + shape=[B, H, S_qo], + strides=[stride_lb, stride_lh, 1], + block_shape=[1, 1, BLOCK_M], + ) + minus_Delta_desc = _maybe_make_tensor_desc( + minus_Delta, + shape=[B, H, S_qo], + strides=[stride_lb, stride_lh, 1], + block_shape=[1, 1, BLOCK_M], + ) + minus_L_desc = _maybe_make_tensor_desc( + minus_L, + shape=[B, H, S_qo], + strides=[stride_lb, stride_lh, 1], + block_shape=[1, 1, BLOCK_M], + ) + offset_b = pid_y // H + offset_h = pid_y % H + offset_s = (pid_x * BLOCK_M).to(tl.int32) + o = O_desc.load([offset_b, offset_h, offset_s, 0]).to(tl.float32) + do = dO_desc.load([offset_b, offset_h, offset_s, 0]).to(tl.float32) + offset_s_ptrs = offset_s + tl.reshape(tl.arange(0, BLOCK_M), (1, 1, BLOCK_M)) + offset_ptrs = offset_b * stride_lb + offset_h * stride_lh + offset_s_ptrs + if USE_DESC_FOR_CORR: + l = L_desc.load([offset_b, offset_h, offset_s]) + else: + l = tl.load(L + offset_ptrs, mask=offset_s_ptrs < S_qo, other=0.0) + + delta = -tl.sum(o * do, axis=3) * softmax_scale + ml = -l + if USE_DESC_FOR_CORR: + minus_Delta_desc.store([offset_b, offset_h, offset_s], delta) + minus_L_desc.store([offset_b, offset_h, offset_s], ml) + else: + tl.store(minus_Delta + offset_ptrs, delta, mask=offset_s_ptrs < S_qo) + tl.store(minus_L + offset_ptrs, ml, mask=offset_s_ptrs < S_qo) + + +# Note: only ensure the functional correctness, not performance for now +@triton.autotune( + configs=_get_configs(is_backward=True), + key=["S_qo", "S_kv", "USE_DESC_FOR_CORR", "IS_CAUSAL"], + reset_to_zero=["dq_ptr"], +) +@triton.jit +def _fmha_bwd_kernel( + Q, + K, + V, + dO, + L, + Delta, + dQ, + dq_ptr, # used to reset dQ to zero during autotuning + dK, + dV, + softmax_scale, + stride_qb, + stride_qh, + stride_qm, + stride_kb, + stride_kh, + stride_kn, + stride_vb, + stride_vh, + stride_vk, + stride_ob, + stride_oh, + stride_om, + stride_lb, + stride_lh, + B, + H, + S_qo, + S_kv, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_D: tl.constexpr, + IS_CAUSAL: tl.constexpr, + USE_DESC_FOR_CORR: tl.constexpr, + warp_specialize: tl.constexpr, +): + if isinstance(Q, tl.tensor_descriptor): + dtype = Q.type.block_type.element_ty + else: + dtype = Q.dtype.element_ty + + pid_x = tl.program_id(0) + pid_y = tl.program_id(1) + + if not IS_CAUSAL: + start_m = 0 + else: + start_m = ((pid_x * BLOCK_N) // BLOCK_M) * BLOCK_M + + Q_desc = _maybe_make_tensor_desc( + Q, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_qb, stride_qh, stride_qm, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + K_desc = _maybe_make_tensor_desc( + K, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_kb, stride_kh, stride_kn, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + V_desc = _maybe_make_tensor_desc( + V, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_vb, stride_vh, stride_vk, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + dO_desc = _maybe_make_tensor_desc( + dO, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_ob, stride_oh, stride_om, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + dQ_desc = _maybe_make_tensor_desc( + dQ, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_qb, stride_qh, stride_qm, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + + dK_desc = _maybe_make_tensor_desc( + dK, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_kb, stride_kh, stride_kn, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + dV_desc = _maybe_make_tensor_desc( + dV, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_vb, stride_vh, stride_vk, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + if USE_DESC_FOR_CORR: + L_desc = _maybe_make_tensor_desc( + L, + shape=[B, H, S_qo], + strides=[stride_lb, stride_lh, 1], + block_shape=[1, 1, BLOCK_M], + ) + Delta_desc = _maybe_make_tensor_desc( + Delta, + shape=[B, H, S_qo], + strides=[stride_lb, stride_lh, 1], + block_shape=[1, 1, BLOCK_M], + ) + + offset_b = pid_y // H + offset_h = pid_y % H + offset_skv = pid_x * BLOCK_N + + offset_s_ptrs = start_m + tl.reshape(tl.arange(0, BLOCK_M), (1, 1, BLOCK_M)) + offset_ptrs = offset_b * stride_lb + offset_h * stride_lh + offset_s_ptrs + + k = K_desc.load([offset_b, offset_h, offset_skv, 0]).reshape(BLOCK_N, BLOCK_D) + v = V_desc.load([offset_b, offset_h, offset_skv, 0]).reshape(BLOCK_N, BLOCK_D) + + dk = tl.zeros([BLOCK_N, BLOCK_D], dtype=tl.float32) + dv = tl.zeros([BLOCK_N, BLOCK_D], dtype=tl.float32) + + softmax_scale_inv_ln2 = softmax_scale * tl.constexpr(1.0 / math.log(2)) # INV_LOG_2 + + for curr_m in range(start_m, S_qo, BLOCK_M, warp_specialize): + curr_m = tl.multiple_of(curr_m, BLOCK_M) + q = Q_desc.load([offset_b, offset_h, curr_m, 0]).reshape(BLOCK_M, BLOCK_D) + s_t = tl.dot(k, tl.trans(q)) + do = dO_desc.load([offset_b, offset_h, curr_m, 0]).reshape(BLOCK_M, BLOCK_D) + dp_t = tl.dot(v, tl.trans(do)) + if USE_DESC_FOR_CORR: + l = L_desc.load([offset_b, offset_h, curr_m]) + else: + l = tl.load(L + offset_ptrs, mask=offset_s_ptrs < S_qo, other=0.0) + if IS_CAUSAL: + offs_n = offset_skv + tl.arange(0, BLOCK_N) + offs_m_curr = curr_m + tl.arange(0, BLOCK_M) + s_t = tl.where(offs_m_curr[None, :] >= offs_n[:, None], s_t, float("-inf")) + if USE_DESC_FOR_CORR: + delta = Delta_desc.load([offset_b, offset_h, curr_m]) + else: + delta = tl.load(Delta + offset_ptrs, mask=offset_s_ptrs < S_qo, other=0.0) + l_t = tl.view(l, (1, BLOCK_M)) + # replace sub l_t to add l_t to use fma2 instruction + s_t = softmax_scale_inv_ln2 * s_t + l_t + p_t = tl.exp2(s_t) + p_t_f16 = p_t.to(dtype) + delta_t = tl.view(delta, (1, BLOCK_M)) + dp_t_new = softmax_scale * dp_t + delta_t + ds_t = (p_t * dp_t_new).to(dtype) + dk = tl.dot(ds_t, q, dk) + dv = tl.dot(p_t_f16, do, dv) + ds = tl.trans(ds_t) + dq = tl.dot(ds, k) + dQ_desc.atomic_add( + [offset_b, offset_h, curr_m, 0], + dq.reshape(1, 1, BLOCK_M, BLOCK_D), + ) + if not USE_DESC_FOR_CORR: + offset_s_ptrs += BLOCK_M + offset_ptrs += BLOCK_M + dK_desc.store([offset_b, offset_h, offset_skv, 0], dk.to(dtype).reshape(1, 1, BLOCK_N, BLOCK_D)) + dV_desc.store([offset_b, offset_h, offset_skv, 0], dv.to(dtype).reshape(1, 1, BLOCK_N, BLOCK_D)) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _attention(torch.autograd.Function): + @staticmethod + def forward( + ctx, + q, + k, + v, + sm_scale, + is_causal, + has_backward=False, + ): + B, H, S_qo, BLOCK_D = q.shape + assert k.shape == v.shape + num_head_kv = k.shape[1] + S_kv = k.shape[2] + BHS_qo = B * H * S_qo + BHS_kv = B * H * S_kv + o = torch.empty_like(q) + l = torch.empty((B, H, S_qo), device=q.device, dtype=torch.float32) + stage = 3 if is_causal else 1 + if H == num_head_kv: + query_group_size = 0 + else: + assert H % num_head_kv == 0 + query_group_size = int(H / num_head_kv) + + # Launch fmha fwd kernel + grid = lambda args: (triton.cdiv(S_qo, args["BLOCK_M"]), B * H, 1) + USE_DESC_FOR_CORR = False + + # TMA descriptors require a global memory allocation + def alloc_fn(size: int, alignment: int, stream: Optional[int]): + return torch.empty(size, device="cuda", dtype=torch.int8) + + triton.set_allocator(alloc_fn) + if _supports_host_descriptor(): + dummy_block_qo = [1, 1, 1, 1] + dummy_block_kv = [1, 1, 1, 1] + dummy_block_l = [1, 1, 1] + desc_q = TensorDescriptor( + q, + shape=[B, H, S_qo, BLOCK_D], + strides=[q.stride(0), q.stride(1), q.stride(2), 1], + block_shape=dummy_block_qo, + ) + desc_v = TensorDescriptor( + v, + shape=[B, num_head_kv, S_kv, BLOCK_D], + strides=[v.stride(0), v.stride(1), v.stride(2), 1], + block_shape=dummy_block_kv, + ) + desc_k = TensorDescriptor( + k, + shape=[B, num_head_kv, S_kv, BLOCK_D], + strides=[k.stride(0), k.stride(1), k.stride(2), 1], + block_shape=dummy_block_kv, + ) + desc_o = TensorDescriptor( + o, + shape=[B, H, S_qo, BLOCK_D], + strides=[o.stride(0), o.stride(1), o.stride(2), 1], + block_shape=dummy_block_qo, + ) + if has_backward and _supports_tma(l): + desc_l = TensorDescriptor( + l, + shape=[B, H, S_qo], + strides=[l.stride(0), l.stride(1), 1], + block_shape=dummy_block_l, + ) + USE_DESC_FOR_CORR = True + else: + desc_l = l + else: + desc_q = q + desc_v = v + desc_k = k + desc_o = o + desc_l = l + + # disable autotune when run test_op in test_attention.py + if is_autotune_disabled(): + _prefill_fmha.configs = [_prefill_fmha.configs[0]] + + _prefill_fmha[grid]( + desc_q, + desc_k, + desc_v, + desc_o, + desc_l, + sm_scale, + q.stride(0), + q.stride(1), + q.stride(2), + k.stride(0), + k.stride(1), + k.stride(2), + v.stride(0), + v.stride(1), + v.stride(2), + o.stride(0), + o.stride(1), + o.stride(2), + l.stride(0), + l.stride(1), + B, + H, + S_qo, + S_kv, + BHS_qo, + BHS_kv, + HAS_BACKWARD=has_backward, + USE_DESC_FOR_CORR=USE_DESC_FOR_CORR, + BLOCK_D=BLOCK_D, + STAGE=stage, + QUERY_GROUP_SIZE=query_group_size, + dtype=q.dtype, + ) + + ctx.save_for_backward(q, k, v, o, l) + ctx.sm_scale = sm_scale + ctx.shapes = (B, H, S_qo, S_kv) + ctx.launch_configs = ( + is_causal, + BLOCK_D, + ) + return o + + @staticmethod + def backward(ctx, do): + q, k, v, o, l = ctx.saved_tensors + B, H, S_qo, S_kv = ctx.shapes + is_causal, BLOCK_D = ctx.launch_configs + USE_DESC_FOR_CORR = False + do = do.contiguous() + dq = torch.zeros_like(q, dtype=torch.float32) + dk = torch.empty_like(k) + dv = torch.empty_like(v) + minus_delta = torch.empty_like(l) + minus_l = torch.empty_like(l) + assert q.shape[1] == k.shape[1], "bwd for FMHAGQA is not supported for now." + assert dq.stride() == q.stride() + assert dk.stride() == k.stride() + assert dv.stride() == v.stride() + assert do.stride() == o.stride() + + if _supports_host_descriptor(): + dummy_block_qo = [1, 1, 1, 1] + dummy_block_kv = [1, 1, 1, 1] + dummy_block_ld = [1, 1, 1] + Q_desc = TensorDescriptor( + q, + shape=[B, H, S_qo, BLOCK_D], + strides=[q.stride(0), q.stride(1), q.stride(2), 1], + block_shape=dummy_block_qo, + ) + K_desc = TensorDescriptor( + k, + shape=[B, H, S_kv, BLOCK_D], + strides=[k.stride(0), k.stride(1), k.stride(2), 1], + block_shape=dummy_block_kv, + ) + V_desc = TensorDescriptor( + v, + shape=[B, H, S_kv, BLOCK_D], + strides=[v.stride(0), v.stride(1), v.stride(2), 1], + block_shape=dummy_block_kv, + ) + dO_desc = TensorDescriptor( + do, + shape=[B, H, S_qo, BLOCK_D], + strides=[o.stride(0), o.stride(1), o.stride(2), 1], + block_shape=dummy_block_qo, + ) + O_desc = TensorDescriptor( + o, + shape=[B, H, S_qo, BLOCK_D], + strides=[o.stride(0), o.stride(1), o.stride(2), 1], + block_shape=dummy_block_qo, + ) + dQ_desc = TensorDescriptor( + dq, + shape=[B, H, S_qo, BLOCK_D], + strides=[dq.stride(0), dq.stride(1), dq.stride(2), 1], + block_shape=dummy_block_qo, + ) + dK_desc = TensorDescriptor( + dk, + shape=[B, H, S_kv, BLOCK_D], + strides=[dk.stride(0), dk.stride(1), dk.stride(2), 1], + block_shape=dummy_block_kv, + ) + dV_desc = TensorDescriptor( + dv, + shape=[B, H, S_kv, BLOCK_D], + strides=[dv.stride(0), dv.stride(1), dv.stride(2), 1], + block_shape=dummy_block_kv, + ) + if _supports_tma(l): + L_desc = TensorDescriptor( + l, + shape=[B, H, S_qo], + strides=[l.stride(0), l.stride(1), 1], + block_shape=dummy_block_ld, + ) + minus_L_desc = TensorDescriptor( + minus_l, + shape=[B, H, S_qo], + strides=[minus_l.stride(0), minus_l.stride(1), 1], + block_shape=dummy_block_ld, + ) + Delta_desc = TensorDescriptor( + minus_delta, + shape=[B, H, S_qo], + strides=[minus_delta.stride(0), minus_delta.stride(1), 1], + block_shape=dummy_block_ld, + ) + USE_DESC_FOR_CORR = True + else: + L_desc = l + minus_L_desc = minus_l + Delta_desc = minus_delta + else: + Q_desc = q + K_desc = k + V_desc = v + dO_desc = do + O_desc = o + dQ_desc = dq + dK_desc = dk + dV_desc = dv + L_desc = l + minus_L_desc = minus_l + Delta_desc = minus_delta + + grid = lambda args: (triton.cdiv(S_qo, args["BLOCK_M"]), B * H, 1) + _fmha_bwd_preprocess_kernel[grid]( + O_desc, + dO_desc, + L_desc, + Delta_desc, + minus_L_desc, + o.stride(0), + o.stride(1), + o.stride(2), + l.stride(0), + l.stride(1), + B, + H, + S_qo, + ctx.sm_scale, + BLOCK_D=BLOCK_D, + USE_DESC_FOR_CORR=USE_DESC_FOR_CORR, + ) + grid = lambda args: (triton.cdiv(S_kv, args["BLOCK_N"]), B * H, 1) + _fmha_bwd_kernel[grid]( + Q_desc, + K_desc, + V_desc, + dO_desc, + minus_L_desc, + Delta_desc, + dQ_desc, + dq, + dK_desc, + dV_desc, + ctx.sm_scale, + q.stride(0), + q.stride(1), + q.stride(2), + k.stride(0), + k.stride(1), + k.stride(2), + v.stride(0), + v.stride(1), + v.stride(2), + o.stride(0), + o.stride(1), + o.stride(2), + l.stride(0), + l.stride(1), + B, + H, + S_qo, + S_kv, + BLOCK_D=BLOCK_D, + IS_CAUSAL=is_causal, + USE_DESC_FOR_CORR=USE_DESC_FOR_CORR, + ) + return dq, dk, dv, None, None, None + + +triton_prefill_fmha = _attention.apply + + +@register_impl("fmha", backend="triton") +def triton_fmha( + q, + k, + v, + scaling=None, + is_causal=True, + **kwargs, +): + if scaling is None: + scaling = 1.0 / math.sqrt(q.size(-1)) + has_backward = kwargs.get("has_backward", False) + o = triton_prefill_fmha(q, k, v, scaling, is_causal, has_backward) + return o + + +# {$nv-internal-release begin} + +# Backend Registration & Perf Markers # {$nv-internal-release} + +mark_perf_ready("fmha", "nvt") + +# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/bmm.py b/python/tutorials/tileir/tilegym/ops/triton/bmm.py new file mode 100644 index 0000000000..c5de02de8c --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/bmm.py @@ -0,0 +1,656 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# {$nv-internal-release file} + +# Imports # {$nv-internal-release} +from typing import Optional + +import torch +import triton +import triton.language as tl +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.backend import get_available_triton_backend +from tilegym.backend import register_impl +from tilegym.logger import get_logger + +# Constants & Type Aliases # {$nv-internal-release} + +logger = get_logger(__name__) + + +# Capability Probe # {$nv-internal-release} + + +def _is_cuda(): + return triton.runtime.driver.active.get_current_target().backend in [ + "cuda", + "tileir", + ] + + +def _supports_host_descriptor(): + return _is_cuda() and torch.cuda.get_device_capability()[0] >= 9 + + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def _maybe_make_tensor_desc(desc_or_ptr, shape, strides, block_shape): + if isinstance(desc_or_ptr, tl.tensor_descriptor): + return desc_or_ptr + else: + return tl.make_tensor_descriptor(desc_or_ptr, shape, strides, block_shape) + + +def _bmm_memref_set_block_size_hook(nargs): + EPILOGUE_SUBTILE = nargs.get("EPILOGUE_SUBTILE", False) + BLOCK_M = nargs["BLOCK_M"] + BLOCK_N = nargs["BLOCK_N"] + BLOCK_K = nargs["BLOCK_K"] + if not isinstance(nargs.get("a_ptr"), TensorDescriptor): + return + if nargs.get("transpose_a", False): + nargs["a_ptr"].block_shape = [1, BLOCK_K, BLOCK_M] + else: + nargs["a_ptr"].block_shape = [1, BLOCK_M, BLOCK_K] + if nargs.get("transpose_b", False): + nargs["b_ptr"].block_shape = [1, BLOCK_N, BLOCK_K] + else: + nargs["b_ptr"].block_shape = [1, BLOCK_K, BLOCK_N] + nargs["c_ptr"].block_shape = [1, BLOCK_M, BLOCK_N] + if EPILOGUE_SUBTILE: + nargs["c_ptr"].block_shape = [1, BLOCK_M, BLOCK_N // 2] + else: + nargs["c_ptr"].block_shape = [1, BLOCK_M, BLOCK_N] + + +@triton.jit +def _bmm_calculate_pid(pid, M, N, BLOCK_M, BLOCK_N, GROUP_SIZE_M): + num_pid_m = tl.cdiv(M, BLOCK_M) + num_pid_n = tl.cdiv(N, BLOCK_N) + pid_q = pid // (num_pid_m * num_pid_n) + num_pid_in_group = GROUP_SIZE_M * num_pid_n + + pid = pid % (num_pid_m * num_pid_n) + group_id = pid // num_pid_in_group + first_pid_m = group_id * GROUP_SIZE_M + group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M) + pid_m = first_pid_m + (pid % group_size_m) + pid_n = (pid % num_pid_in_group) // group_size_m + return pid_q, pid_m, pid_n + + +# Autotune Config # {$nv-internal-release} + + +def _bmm_memref_get_configs(pre_hook=None): + capability = torch.cuda.get_device_capability() + if get_available_triton_backend() == "oait": + if capability in [(12, 0), (12, 1)]: + return [ + triton.Config( + { + "BLOCK_M": BM, + "BLOCK_N": BN, + "BLOCK_K": BK, + "GROUP_SIZE_M": 8, + "EPILOGUE_SUBTILE": SUBTILE, + "occupancy": 1, + }, + num_stages=s, + num_warps=w, + pre_hook=pre_hook, + ) # + # To reduce the CI time, we pre-tune the whole config sets in local Geforce RTX 5080 360w machine and select the best config to here. + for BM in [64] # + for BN in [64] # + for BK in [64, 128] # + for s in ([2, 3]) # + for w in [4] # + for SUBTILE in [True, False] # + ] + elif capability == (9, 0): + return [ + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK, GROUP_SIZE_M=8, EPILOGUE_SUBTILE=SUBTILE, occupancy=occ), + pre_hook=pre_hook, + num_stages=s, + ) + for BM in [64, 128, 256] + for BN in [64, 128, 256] + for BK in [64] + for SUBTILE in [False] + for occ in [1, 2] + for s in [2, 3] + ] + elif capability[0] == 8: + # A100/A10 (sm_80/sm_86): no TMA, no clusters, num_ctas=1 + # Tuned: 128x128 BK=64 occ=2 stages=2 warps=4 is dominant winner + return [ + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK, GROUP_SIZE_M=8, EPILOGUE_SUBTILE=False, occupancy=occ), + pre_hook=pre_hook, + num_stages=s, + num_warps=4, + ) + for BM in [128] + for BN in [128] + for BK in [32, 64] + for occ in [2] + for s in [2, 4] + ] + else: + return [ + triton.Config( + { + "BLOCK_M": BM, + "BLOCK_N": BN, + "BLOCK_K": BK, + "GROUP_SIZE_M": 8, + "EPILOGUE_SUBTILE": SUBTILE, + "occupancy": 1, + }, + num_stages=s, + num_warps=w, + pre_hook=pre_hook, + ) # + # To reduce the CI time, we pre-tune the whole config sets in local gb100 850w machine and select the best config to here. + for BM in [128, 256] # + for BN in [256] # + for BK in [64] # + for s in ([2, 3]) # + for w in [4] # + for SUBTILE in [True, False] # + ] + else: + if capability in [(12, 0), (12, 1)]: + return [ + triton.Config( + { + "BLOCK_M": BM, + "BLOCK_N": BN, + "BLOCK_K": BK, + "GROUP_SIZE_M": 8, + "EPILOGUE_SUBTILE": SUBTILE, + "occupancy": occ, + }, + pre_hook=pre_hook, + num_stages=s, + ) # + for BM in [64, 128] # + for BN in [64, 128] # + for BK in [32, 64] # + for SUBTILE in [False] # + for occ in [1, 2, 4] + for s in [10] + ] + elif capability == (9, 0): + return [ + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK, GROUP_SIZE_M=8, EPILOGUE_SUBTILE=SUBTILE, occupancy=occ), + pre_hook=pre_hook, + num_stages=s, + num_warps=w, + num_ctas=c, + ) + for BM in [256] + for BN in [128] + for BK in [64] + for SUBTILE in [False] + for occ in [2] + for s in [2, 3] + for w in [4] + for c in [2] + ] + elif capability[0] == 8: + # A100/A10 (sm_80/sm_86): no TMA, no clusters, num_ctas=1 + # Tuned: 128x128 BK=64 occ=2 stages=2 warps=4 is dominant winner + return [ + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK, GROUP_SIZE_M=8, EPILOGUE_SUBTILE=False, occupancy=occ), + pre_hook=pre_hook, + num_stages=s, + num_warps=4, + ) + for BM in [128] + for BN in [128] + for BK in [32, 64] + for occ in [2] + for s in [2, 4] + ] + else: + return [ + triton.Config( + dict(BLOCK_M=256, BLOCK_N=256, BLOCK_K=64, GROUP_SIZE_M=8, EPILOGUE_SUBTILE=False, occupancy=1), + pre_hook=pre_hook, + num_ctas=2, + num_stages=s, + ) + for s in [3, 4, 5] + ] + + +# Device Kernels # {$nv-internal-release} + +# Adapted from https://github.com/openai/triton + +# @triton.autotune( +# configs=[ +# triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8), +# ], +# key=['M', 'N', 'K'], +# ) + + +@triton.autotune( + configs=[ + # triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8), + # triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 + ), + # triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), + # triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), + # triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), + # triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2), + # triton.Config({'BLOCK_SIZE_M': 32, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=5, num_warps=2), + ], + key=["M", "N", "K"], +) +@triton.heuristics( + { + "EVEN_K": lambda args: args["K"] % args["BLOCK_SIZE_K"] == 0, + } +) +@triton.jit +def _bmm_kernel_naive( + a_ptr, + b_ptr, + c_ptr, + M, + N, + K, + stride_aq, + stride_am, + stride_ak, + stride_bq, + stride_bk, + stride_bn, + stride_cq, + stride_cm, + stride_cn, + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, + # Meta-parameters + BLOCK_SIZE_M: tl.constexpr, + BLOCK_SIZE_N: tl.constexpr, + BLOCK_SIZE_K: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, + EVEN_K: tl.constexpr, + ACTIVATION: tl.constexpr, +): + """Kernel for computing the bmm C = A x B. + A has shape (Q, M, K), B has shape (Q, K, N) and C has shape (Q, M, N) + """ + # ----------------------------------------------------------- + # Map program ids `pid` to the block of C it should compute. + # This is done in a grouped ordering to promote L2 data reuse + # See above `L2 Cache Optimizations` section for details + pid = tl.program_id(axis=0) + num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) + num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) + num_pid_in_group = GROUP_SIZE_M * num_pid_n + group_id = pid // num_pid_in_group + first_pid_m = group_id * GROUP_SIZE_M + group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M) + pid_m = first_pid_m + (pid % group_size_m) + pid_n = (pid % num_pid_in_group) // group_size_m + + # ---------------------------------------------------------- + # Create pointers for the first blocks of A and B. + # We will advance this pointer as we move in the K direction + # and accumulate + # a_ptrs is a block of [BLOCK_SIZE_M, BLOCK_SIZE_K] pointers + # b_ptrs is a block of [BLOCK_SIZE_K, BLOCK_SIZE_n] pointers + # see above `Pointer Arithmetics` section for details + offs_am = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M) + offs_bn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N) + offs_k = tl.arange(0, BLOCK_SIZE_K) + pid_q = tl.program_id(axis=1) + if transpose_a: + a_ptrs = a_ptr + pid_q * stride_aq + offs_am[:, None] * stride_ak + offs_k[None, :] * stride_am + + else: + a_ptrs = a_ptr + pid_q * stride_aq + offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak + + if transpose_b: + b_ptrs = b_ptr + pid_q * stride_bq + offs_k[:, None] * stride_bn + offs_bn[None, :] * stride_bk + else: + b_ptrs = b_ptr + pid_q * stride_bq + offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn + + # ----------------------------------------------------------- + # Iterate to compute a block of the C matrix + # We accumulate into a `[BLOCK_SIZE_M, BLOCK_SIZE_N]` block + # of fp32 values for higher accuracy. + # `accumulator` will be converted back to fp16 after the loop + accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)): + if EVEN_K: + a = tl.load(a_ptrs) + b = tl.load(b_ptrs) + else: + k_remaining = K - k * BLOCK_SIZE_K + a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0) + b = tl.load(b_ptrs, mask=offs_k[:, None] < k_remaining, other=0.0) + # We accumulate along the K dimension + accumulator += tl.dot(a, b) + # Advance the ptrs to the next K block + if transpose_a: + a_ptrs += BLOCK_SIZE_K * stride_am + else: + a_ptrs += BLOCK_SIZE_K * stride_ak + if transpose_b: + b_ptrs += BLOCK_SIZE_K * stride_bn + else: + b_ptrs += BLOCK_SIZE_K * stride_bk + # you can fuse arbitrary activation functions here + # while the accumulator is still in FP32! + if ACTIVATION: + accumulator = ACTIVATION(accumulator) + c = accumulator.to(tl.float16) + + # ----------------------------------------------------------- + # Write back the block of the output matrix C + offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M) + offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N) + pid_q = tl.program_id(axis=1) + c_ptrs = c_ptr + pid_q * stride_cq + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :] + c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N) + tl.store(c_ptrs, c, mask=c_mask) + + +@triton.autotune( + configs=_bmm_memref_get_configs(pre_hook=_bmm_memref_set_block_size_hook), + key=["Q", "M", "N", "K", "transpose_a", "transpose_b"], +) +@triton.jit +def _bmm_kernel_memref( + a_ptr, + b_ptr, + c_ptr, # + Q, + M, + N, + K, # + stride_aq, + stride_am, + stride_ak, # + stride_bq, + stride_bk, + stride_bn, # + stride_cq, + stride_cm, + stride_cn, # + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, # + BLOCK_K: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, + EPILOGUE_SUBTILE: tl.constexpr, + occupancy: tl.constexpr, +): + if isinstance(a_ptr, tl.tensor_descriptor): + dtype = a_ptr.type.block_type.element_ty + else: + dtype = c_ptr.dtype.element_ty + pid = tl.program_id(axis=0) + + # Create tensor descriptors for a, b, c + if transpose_a: + # For transpose_a, we need to swap M and K dimensions + a_desc = _maybe_make_tensor_desc( + a_ptr, + shape=[Q, K, M], + strides=[stride_aq, stride_am, stride_ak], + block_shape=[1, BLOCK_K, BLOCK_M], + ) + else: + a_desc = _maybe_make_tensor_desc( + a_ptr, + shape=[Q, M, K], + strides=[stride_aq, stride_am, stride_ak], + block_shape=[1, BLOCK_M, BLOCK_K], + ) + + if transpose_b: + # For transpose_b, we need to swap K and N dimensions + b_desc = _maybe_make_tensor_desc( + b_ptr, + shape=[Q, N, K], + strides=[stride_bq, stride_bk, stride_bn], + block_shape=[1, BLOCK_N, BLOCK_K], + ) + else: + b_desc = _maybe_make_tensor_desc( + b_ptr, + shape=[Q, K, N], + strides=[stride_bq, stride_bk, stride_bn], + block_shape=[1, BLOCK_K, BLOCK_N], + ) + + if EPILOGUE_SUBTILE: + c_desc = _maybe_make_tensor_desc( + c_ptr, + shape=[Q, M, N], + strides=[stride_cq, stride_cm, stride_cn], + block_shape=[1, BLOCK_M, BLOCK_N // 2], + ) + else: + c_desc = _maybe_make_tensor_desc( + c_ptr, + shape=[Q, M, N], + strides=[stride_cq, stride_cm, stride_cn], + block_shape=[1, BLOCK_M, BLOCK_N], + ) + + total_tiles = tl.cdiv(M, BLOCK_M) * tl.cdiv(N, BLOCK_N) * Q + num_programs = tl.num_programs(0) + # for loop for static scheduling + for current_pid in tl.range(pid, total_tiles, num_programs, flatten=True): + pid_q, pid_m, pid_n = _bmm_calculate_pid(current_pid, M, N, BLOCK_M, BLOCK_N, GROUP_SIZE_M) + acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_K)): + if transpose_a: + # Load and transpose: from [1, BLOCK_K, BLOCK_M] to [1, BLOCK_M, BLOCK_K] + a = a_desc.load([pid_q * 1, k * BLOCK_K, pid_m * BLOCK_M]) + a = tl.trans(a, (0, 2, 1)) + else: + a = a_desc.load([pid_q * 1, pid_m * BLOCK_M, k * BLOCK_K]) + + if transpose_b: + # Load and transpose: from [1, BLOCK_N, BLOCK_K] to [1, BLOCK_K, BLOCK_N] + b = b_desc.load([pid_q * 1, pid_n * BLOCK_N, k * BLOCK_K]) + b = tl.trans(b, (0, 2, 1)) + else: + b = b_desc.load([pid_q * 1, k * BLOCK_K, pid_n * BLOCK_N]) + + # Reshape 3D tensors to 2D for tl.dot (mmaV5 expects 2D inputs) + a_2d = tl.reshape(a, (BLOCK_M, BLOCK_K)) + b_2d = tl.reshape(b, (BLOCK_K, BLOCK_N)) + acc = tl.dot(a_2d, b_2d, acc) + + pid_q, pid_m, pid_n = _bmm_calculate_pid(current_pid, M, N, BLOCK_M, BLOCK_N, GROUP_SIZE_M) + + # Reshape 2D accumulator back to 3D for epilogue processing + acc = tl.reshape(acc, (1, BLOCK_M, BLOCK_N)) + if EPILOGUE_SUBTILE: + acc = tl.reshape(acc, (1, BLOCK_M, 2, BLOCK_N // 2)) + acc = tl.permute(acc, (0, 1, 3, 2)) + acc0, acc1 = tl.split(acc) + c0 = acc0.to(dtype) + c_desc.store([pid_q * 1, pid_m * BLOCK_M, pid_n * BLOCK_N], c0) + c1 = acc1.to(dtype) + c_desc.store([pid_q * 1, pid_m * BLOCK_M, pid_n * BLOCK_N + BLOCK_N // 2], c1) + else: + c = acc.to(dtype) + c_desc.store([pid_q * 1, pid_m * BLOCK_M, pid_n * BLOCK_N], c) + + +# Host Launchers & Public API # {$nv-internal-release} + + +def bmm_fn(a, b, transpose_a=False, transpose_b=False): + if transpose_a: + Q_A, K_A, M = a.shape + else: + Q_A, M, K_A = a.shape + + if transpose_b: + Q_B, N, K_B = b.shape + else: + Q_B, K_B, N = b.shape + + assert K_A == K_B, "incompatible dimensions" + assert Q_A == Q_B, "incompatible dimensions" + K = K_A + Q = Q_A + + assert a.is_contiguous(), "matrix A must be contiguous" + assert b.is_contiguous(), "matrix B must be contiguous" + # allocates output + c = torch.empty((Q, M, N), device=a.device, dtype=a.dtype) + # 1D launch kernel where each block gets its own program. + grid = lambda META: ( + triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]), + Q, + ) + _bmm_kernel_naive[grid]( + a, + b, + c, + M, + N, + K, + a.stride(0), + a.stride(1), + a.stride(2), + b.stride(0), + b.stride(1), + b.stride(2), + c.stride(0), + c.stride(1), + c.stride(2), + transpose_a, + transpose_b, + ACTIVATION=None, + ) + return c + + +@register_impl("bmm", backend="triton") +def bmm_memref(a, b, transpose_a=False, transpose_b=False, static_persistent=None, **kwargs): + if static_persistent is None: + static_persistent = True + if transpose_a: + Q_A, K_A, M = a.shape + else: + Q_A, M, K_A = a.shape + + if transpose_b: + Q_B, N, K_B = b.shape + else: + Q_B, K_B, N = b.shape + + assert K_A == K_B, "incompatible dimensions" + assert Q_A == Q_B, "incompatible dimensions" + K = K_A + Q = Q_A + + assert a.is_contiguous(), "matrix A must be contiguous" + assert b.is_contiguous(), "matrix B must be contiguous" + + c = torch.empty((Q, M, N), device=a.device, dtype=a.dtype) + assert static_persistent == True, "only support static persistent mode" + + # TMA descriptors require a global memory allocation + def alloc_fn(size: int, alignment: int, stream: Optional[int]): + return torch.empty(size, device="cuda", dtype=torch.int8) + + triton.set_allocator(alloc_fn) + + # Add host descriptor support + if _supports_host_descriptor(): + dummy_block = [1, 1, 1] + a_desc = TensorDescriptor(a, a.shape, a.stride(), dummy_block) + b_desc = TensorDescriptor(b, b.shape, b.stride(), dummy_block) + c_desc = TensorDescriptor(c, c.shape, c.stride(), dummy_block) + else: + a_desc = a + b_desc = b + c_desc = c + + # get grid size for static persistence + NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count + + if get_available_triton_backend() == "oait": + num_ctas = 1 + else: + cap = torch.cuda.get_device_capability() + if cap in [(12, 0), (12, 1)] or cap[0] < 9: + num_ctas = 1 # Clusters require sm_90+ + else: + num_ctas = 2 + + grid = lambda META: ( + min( + NUM_SMS // num_ctas, + triton.cdiv(M, META["BLOCK_M"]) * triton.cdiv(N, META["BLOCK_N"]) * Q, + ) + * META["occupancy"], + ) + _bmm_kernel_memref[grid]( + a_desc, + b_desc, + c_desc, # + Q, + M, + N, + K, # + a.stride(0), + a.stride(1), + a.stride(2), + b.stride(0), + b.stride(1), + b.stride(2), + c.stride(0), + c.stride(1), + c.stride(2), + transpose_a=transpose_a, + transpose_b=transpose_b, + ) + + return c + + +class _BMM(torch.autograd.Function): + @staticmethod + def forward(ctx, a, b): + c = bmm_memref(a, b) + ctx.save_for_backward(a, b) + return c + + @staticmethod + def backward(ctx, dy): + a, b = ctx.saved_tensors + da = bmm_memref(dy, b, transpose_b=True) + db = bmm_memref(a, dy, transpose_a=True) + return da, db + + +def bmm_memref_fwd_bwd(a, b): + return _BMM.apply(a, b) diff --git a/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py b/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py new file mode 100644 index 0000000000..8c59640928 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py @@ -0,0 +1,798 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# {$nv-internal-release file} + +# Imports # {$nv-internal-release} + +import itertools +import math +import os +from typing import Optional +from typing import Union + +import torch +import triton +import triton.language as tl +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.backend import register_impl +from tilegym.ops.triton.activation.gelu import gelu_backward +from tilegym.ops.triton.activation.gelu import gelu_tanh_forward +from tilegym.ops.triton.activation.relu import relu_backward +from tilegym.ops.triton.activation.relu import relu_forward +from tilegym.ops.triton.activation.silu import silu_backward +from tilegym.ops.triton.activation.silu import silu_forward + +from .matmul_perf_model import early_config_prune +from .matmul_perf_model import estimate_matmul_time + +# Constants & Type Aliases # {$nv-internal-release} + + +supported_act_types = { + "relu": {"fwd": relu_forward, "bwd": relu_backward}, + "gelu": {"fwd": gelu_tanh_forward, "bwd": gelu_backward}, + "silu": {"fwd": silu_forward, "bwd": silu_backward}, +} + + +# Autotune Config # {$nv-internal-release} + + +def _init_to_zero(name): + return lambda nargs: nargs[name].zero_() if torch.is_tensor(nargs[name]) else None + + +def _host_descriptor_pre_hook(nargs): + BLOCK_M = nargs["BLOCK_M"] + BLOCK_N = nargs["BLOCK_N"] + BLOCK_K = nargs["BLOCK_K"] + + # Set block shapes for input tensors + if not isinstance(nargs.get("A"), TensorDescriptor): + return + + # A tensor (input): [M, K] for normal, [K, M] for transpose + if nargs.get("transpose_a", False): + nargs["A"].block_shape = [BLOCK_K, BLOCK_M] + else: + nargs["A"].block_shape = [BLOCK_M, BLOCK_K] + + # B tensor (weight): [K, N] for normal, [N, K] for transpose + if nargs.get("transpose_b", False): + nargs["B"].block_shape = [BLOCK_N, BLOCK_K] + else: + nargs["B"].block_shape = [BLOCK_K, BLOCK_N] + + # C tensor (output): [M, N] + nargs["C"].block_shape = [BLOCK_M, BLOCK_N] + + # Act_in tensor: [M, N] + if "Act_in" in nargs and isinstance(nargs["Act_in"], TensorDescriptor): + nargs["Act_in"].block_shape = [BLOCK_M, BLOCK_N] + + # Dropout_mask tensor: [M, N] + if "Dropout_mask" in nargs and isinstance(nargs["Dropout_mask"], TensorDescriptor): + nargs["Dropout_mask"].block_shape = [BLOCK_M, BLOCK_N] + + +def _host_descriptor_bias_bwd_pre_hook(nargs): + # Initialize bias to zero for backward pass + _init_to_zero("Bias")(nargs) + + # Set up TMA descriptors + BLOCK_M = nargs["BLOCK_M"] + BLOCK_N = nargs["BLOCK_N"] + BLOCK_K = nargs["BLOCK_K"] + + # Set block shapes for input tensors + if not isinstance(nargs.get("A"), TensorDescriptor): + return + + # A tensor (input): [M, K] for normal, [K, M] for transpose + if nargs.get("transpose_a", False): + nargs["A"].block_shape = [BLOCK_K, BLOCK_M] + else: + nargs["A"].block_shape = [BLOCK_M, BLOCK_K] + + # B tensor (weight): [K, N] for normal, [N, K] for transpose + if nargs.get("transpose_b", False): + nargs["B"].block_shape = [BLOCK_N, BLOCK_K] + else: + nargs["B"].block_shape = [BLOCK_K, BLOCK_N] + + # C tensor (output): [M, N] + nargs["C"].block_shape = [BLOCK_M, BLOCK_N] + + # Bias tensor: [N] + if "Bias" in nargs and isinstance(nargs["Bias"], TensorDescriptor): + nargs["Bias"].block_shape = [BLOCK_N] + + # Act_in tensor: [M, N] + if "Act_in" in nargs and isinstance(nargs["Act_in"], TensorDescriptor): + nargs["Act_in"].block_shape = [BLOCK_M, BLOCK_N] + + # Dropout_mask tensor: [M, N] + if "Dropout_mask" in nargs and isinstance(nargs["Dropout_mask"], TensorDescriptor): + nargs["Dropout_mask"].block_shape = [BLOCK_M, BLOCK_N] + + +def _matmul_act_bias_bwd_get_configs_io_bound(): + configs = [] + for num_stages in [2, 6]: + for block_m in [ + 16, + ]: + for block_k in [ + 32, + ]: + for block_n in [ + 32, + ]: + num_warps = 2 if block_n <= 64 else 4 + configs.append( + triton.Config( + dict(BLOCK_M=block_m, BLOCK_N=block_n, BLOCK_K=block_k), + num_stages=num_stages, + num_warps=num_warps, + pre_hook=_host_descriptor_bias_bwd_pre_hook, + ) + ) + return configs + + +def _get_configs_io_bound(): + configs = [] + for num_stages in [2, 3]: + for block_m in [ + 32, + ]: + for block_k in [32, 64]: + for block_n in [ + 128, + ]: + num_warps = 2 if block_n <= 64 else 4 + configs.append( + triton.Config( + dict(BLOCK_M=block_m, BLOCK_N=block_n, BLOCK_K=block_k), + num_stages=num_stages, + num_warps=num_warps, + pre_hook=_host_descriptor_pre_hook, + ) + ) + return configs + + +matmul_act_bias_bwd_autotune = triton.autotune( + configs=[ + # basic configs for compute-bound matmuls + triton.Config( + dict(BLOCK_M=128, BLOCK_N=256, BLOCK_K=32), + num_stages=3, + num_warps=8, + pre_hook=_host_descriptor_bias_bwd_pre_hook, + ), + # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 64, 'BLOCK_K': 32}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 32}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 32}, num_stages=5, num_warps=2, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # good for int8 + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 128}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 128, 'BLOCK_K': 128}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 64, 'BLOCK_K': 128}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 128}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 128}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=5, num_warps=2, pre_hook=_host_descriptor_bias_bwd_pre_hook), + ] + + _matmul_act_bias_bwd_get_configs_io_bound(), + key=["M", "N", "K", "align_a", "align_b"], + prune_configs_by={ + "early_config_prune": early_config_prune, + "perf_model": estimate_matmul_time, + "top_k": 10, + }, +) + +matmul_autotune = triton.autotune( + configs=[ + # basic configs for compute-bound matmuls + triton.Config( + dict(BLOCK_M=128, BLOCK_N=256, BLOCK_K=32), num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=256, BLOCK_N=128, BLOCK_K=32), num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=256, BLOCK_N=64, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=64, BLOCK_N=256, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=128, BLOCK_N=128, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=128, BLOCK_N=64, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=64, BLOCK_N=128, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=128, BLOCK_N=32, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook + ), + triton.Config( + dict(BLOCK_M=64, BLOCK_N=32, BLOCK_K=32), num_stages=5, num_warps=2, pre_hook=_host_descriptor_pre_hook + ), + # good for int8 + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 128}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 128, 'BLOCK_K': 128}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 64, 'BLOCK_K': 128}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 128}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 128}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), + # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=5, num_warps=2, pre_hook=_host_descriptor_pre_hook), + ] + + _get_configs_io_bound(), + key=["M", "N", "K", "align_a", "align_b"], + # prune_configs_by={ + # 'early_config_prune': early_config_prune, + # 'perf_model': estimate_matmul_time, + # 'top_k': 10, + # }, +) + +matmul_act_bias_bwd_heuristics = triton.heuristics( + { + "EVEN_K": lambda args: args["K"] % args["BLOCK_K"] == 0, + } +) + + +matmul_heuristics = triton.heuristics( + { + "EVEN_K": lambda args: args["K"] % args["BLOCK_K"] == 0, + } +) + + +# Device Kernels # {$nv-internal-release} + + +# TODO: add split_k support +@matmul_autotune +@matmul_heuristics +@triton.jit +def matmul_bias_activation_dropout_fwd( + A, + B, + C, + Bias, + Act_in, + Dropout_mask, + M, + N, + K, + stride_am, + stride_ak, + stride_bk, + stride_bn, + stride_cm, + stride_cn, + align_a, + align_b, + dropout_seed, + dropout_prob, + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, + dot_out_dtype: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, + GROUP_M: tl.constexpr, + EVEN_K: tl.constexpr, + IS_BIAS: tl.constexpr, + ACT_TYPE: tl.constexpr, + IS_GRAD: tl.constexpr, + IS_DROPOUT: tl.constexpr, +): + # Determine dtype from tensor descriptors or fallback to C dtype + if isinstance(A, tl.tensor_descriptor): + dtype = A.type.block_type.element_ty + else: + dtype = C.dtype.element_ty + + # matrix multiplication + pid = tl.program_id(0) + grid_m = tl.cdiv(M, BLOCK_M) + grid_n = tl.cdiv(N, BLOCK_N) + # re-order program ID for better L2 performance + width = GROUP_M * grid_n + group_id = pid // width + group_size = min(grid_m - group_id * GROUP_M, GROUP_M) + pid_m = group_id * GROUP_M + (pid % group_size) + pid_n = (pid % width) // (group_size) + + # Calculate explicit offsets for tensor descriptors + offset_am = pid_m * BLOCK_M + offset_bn = pid_n * BLOCK_N + + # do matrix multiplication + acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=dot_out_dtype) + for k in range(0, tl.cdiv(K, BLOCK_K)): + offset_k = k * BLOCK_K + + # Load using tensor descriptors with explicit offsets + if transpose_a: + # For transposed A: load (BLOCK_K, BLOCK_M) then transpose + a = A.load([offset_k, offset_am]) + a = tl.trans(a) # Transpose from (BLOCK_K, BLOCK_M) to (BLOCK_M, BLOCK_K) + else: + a = A.load([offset_am, offset_k]) + + if transpose_b: + # For transposed B: load (BLOCK_N, BLOCK_K) then transpose + b = B.load([offset_bn, offset_k]) + b = tl.trans(b) # Transpose from (BLOCK_N, BLOCK_K) to (BLOCK_K, BLOCK_N) + else: + b = B.load([offset_k, offset_bn]) + + acc += tl.dot(a, b, out_dtype=dot_out_dtype) + + # Calculate output offsets + offset_cm = pid_m * BLOCK_M + offset_cn = pid_n * BLOCK_N + + # handles write-back + store_acc = acc.to(dtype) + if IS_BIAS: + cols = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) + bias_mask = cols < N + # Load bias using standard pointer access for now (bias is 1D) + bias = tl.load(Bias + cols, mask=bias_mask) + bias = bias[None, :] + store_acc += bias + if ACT_TYPE is not None: + if IS_GRAD: + Act_in.store([offset_cm, offset_cn], store_acc) + if ACT_TYPE == "gelu": + store_acc = gelu_tanh_forward(store_acc).to(dtype) + elif ACT_TYPE == "relu": + store_acc = relu_forward(store_acc).to(dtype) + elif ACT_TYPE == "silu": + store_acc = silu_forward(store_acc).to(dtype) + if IS_DROPOUT: + rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) + rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) + random = tl.rand( + dropout_seed, + rm[:, None] * stride_cm + rn[None, :] * stride_cn, + n_rounds=7, + ) + x_keep = random > dropout_prob + dropout_0 = 0.0 + dropout_0 = dropout_0.to(dtype) + + Dropout_mask.store([offset_cm, offset_cn], x_keep.to(tl.int8)) + store_acc = tl.where( + x_keep, + (store_acc / (1 - dropout_prob)).to(dtype), + dropout_0, + ) + C.store([offset_cm, offset_cn], store_acc) + + +@triton.autotune( + configs=[ + # fmt: off + triton.Config( + dict(BLOCK_M=32, BLOCK_N=32), num_stages=1, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook + ), + # triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32}, num_stages=1, num_warps=16, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64}, num_stages=1, num_warps=1, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32}, num_stages=1, num_warps=1, pre_hook=_host_descriptor_bias_bwd_pre_hook), + # fmt: on + ] + if not os.getenv("EXHAUSTIVE_BWD", False) + else [ + triton.Config( + dict(BLOCK_M=block_m, BLOCK_N=block_n), + num_stages=num_stages, + num_warps=num_warps, + pre_hook=_host_descriptor_bias_bwd_pre_hook, + ) + for block_m, block_n, num_warps, num_stages in itertools.product([64, 128], [32, 128], [1, 4, 16], [1, 4]) + ], + key=["M", "N"], +) +@triton.jit +def _kernel_bias_act_bwd( + # fmt: off + Dy, # pointer to output gradient + Bias, # pointer to the biases gradient + Act_in, # pointer to store activation input for bprop + M, # number of rows in DY + N, # number of columns in DY + input_row_stride, # row stride + input_col_stride, # col stride + IS_BIAS: tl.constexpr, + ACT_TYPE: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + # fmt: on +): + # matrix multiplication + pid = tl.program_id(0) + grid_n = tl.cdiv(N, BLOCK_N) + # TODO re-order program ID for better L2 performance + pid_m = pid // grid_n + pid_n = pid % grid_n + + # Calculate explicit offsets for tensor descriptors + offset_m = pid_m * BLOCK_M + offset_n = pid_n * BLOCK_N + + dy = Dy.load([offset_m, offset_n]) + + if ACT_TYPE is not None: + act_inp = Act_in.load([offset_m, offset_n]) + dy = _act_bwd_func(act_inp, dy) + # dy = act_bwd_func(act_inp, dy, ACT_TYPE) + Dy.store([offset_m, offset_n], dy) + + if IS_BIAS: + rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) + acc = tl.sum(dy, axis=0) + bias_mask = rn < N + # Use standard pointer access for bias gradient accumulation (atomic add) + tl.atomic_add(Bias + (rn * input_col_stride), acc, mask=bias_mask) + + +# TODO: add split_k support +@matmul_act_bias_bwd_autotune +@matmul_act_bias_bwd_heuristics +@triton.jit +def matmul_dropout_activation_bias_bwd( + # fmt: off + A, + B, + C, + Bias, + Act_in, + Dropout_mask, + M, + N, + K, + stride_am, + stride_ak, + stride_bk, + stride_bn, + stride_cm, + stride_cn, + align_a, + align_b, + dropout_prob, + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, + dot_out_dtype: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, + GROUP_M: tl.constexpr, + EVEN_K: tl.constexpr, + IS_BIAS: tl.constexpr, + ACT_TYPE: tl.constexpr, + IS_DROPOUT: tl.constexpr, + # fmt: on +): + # Determine dtype from tensor descriptors or fallback to C dtype + if isinstance(A, tl.tensor_descriptor): + dtype = A.type.block_type.element_ty + else: + dtype = C.dtype.element_ty + + pid = tl.program_id(0) + grid_m = tl.cdiv(M, BLOCK_M) + grid_n = tl.cdiv(N, BLOCK_N) + # re-order program ID for better L2 performance + width = GROUP_M * grid_n + group_id = pid // width + group_size = min(grid_m - group_id * GROUP_M, GROUP_M) + pid_m = group_id * GROUP_M + (pid % group_size) + pid_n = (pid % width) // (group_size) + + # Calculate explicit offsets for tensor descriptors + offset_am = pid_m * BLOCK_M + offset_bn = pid_n * BLOCK_N + + # do matrix multiplication + acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=dot_out_dtype) + for k in range(0, tl.cdiv(K, BLOCK_K)): + offset_k = k * BLOCK_K + + # Load using tensor descriptors with explicit offsets + if transpose_a: + # For transposed A: load (BLOCK_K, BLOCK_M) then transpose + a = A.load([offset_k, offset_am]) + a = tl.trans(a) # Transpose from (BLOCK_K, BLOCK_M) to (BLOCK_M, BLOCK_K) + else: + a = A.load([offset_am, offset_k]) + + if transpose_b: + # For transposed B: load (BLOCK_N, BLOCK_K) then transpose + b = B.load([offset_bn, offset_k]) + b = tl.trans(b) # Transpose from (BLOCK_N, BLOCK_K) to (BLOCK_K, BLOCK_N) + else: + b = B.load([offset_k, offset_bn]) + + acc += tl.dot(a, b, out_dtype=dot_out_dtype) + + # Calculate output offsets + offset_cm = pid_m * BLOCK_M + offset_cn = pid_n * BLOCK_N + + # handles write-back + store_acc = acc.to(dtype) + if IS_DROPOUT: + x_keep = Dropout_mask.load([offset_cm, offset_cn]) + dropout_0 = 0.0 + dropout_0 = dropout_0.to(dtype) + store_acc = tl.where( + x_keep, + (store_acc / (1.0 - dropout_prob)).to(dtype), + dropout_0, + ) + if ACT_TYPE is not None: + act_in = Act_in.load([offset_cm, offset_cn]) + if ACT_TYPE == "gelu": + store_acc = gelu_backward(act_in, store_acc).to(dtype) + elif ACT_TYPE == "relu": + store_acc = relu_backward(act_in, store_acc).to(dtype) + elif ACT_TYPE == "silu": + store_acc = silu_backward(act_in, store_acc).to(dtype) + if IS_BIAS: + cols = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) + bias_mask = cols < N + dacc_sum = tl.sum(store_acc, axis=0) + # Use standard pointer access for bias gradient accumulation (atomic add) + tl.atomic_add(Bias + cols, dacc_sum.to(tl.float32), mask=bias_mask) + C.store([offset_cm, offset_cn], store_acc) + + +# Host Helpers # {$nv-internal-release} + + +def get_tensor_alignment(tensor): + address = tensor.data_ptr() + alignment = (((address - 1) ^ address) + 1) >> 1 + return alignment + + +def allocate_tma_aligned_2d(shape, dtype, device): + """Allocate a 2D tensor whose row stride times element size is 16-byte aligned. + + ``TensorDescriptor`` (TMA) asserts ``(stride * elem_bytes) % 16 == 0`` for + every non-innermost dimension. For 2D contiguous tensors that means the + inner dimension ``N`` must satisfy ``N * itemsize % 16 == 0``. When ``N`` + does not already meet this constraint (e.g. fp32 with ``N=258``), allocate + padded storage on the inner dimension and return a sliced view with the + requested logical ``shape`` but a padded ``stride(0)``. The returned tensor + is non-contiguous when padding is applied; call ``.contiguous()`` before + passing it to kernels that require contiguity. + """ + assert len(shape) == 2, "allocate_tma_aligned_2d expects a 2D shape" + m, n = shape + itemsize = torch.empty(0, dtype=dtype).element_size() + if (n * itemsize) % 16 == 0: + return torch.empty((m, n), dtype=dtype, device=device) + # Pad inner dim so padded_n * itemsize is a multiple of 16 bytes. + align_elems = 16 // math.gcd(16, itemsize) + padded_n = ((n + align_elems - 1) // align_elems) * align_elems + return torch.empty((m, padded_n), dtype=dtype, device=device)[:, :n] + + +def pad_for_tma_2d(t): + """Return ``t`` (or a padded copy of it) with a 16-byte aligned row stride. + + Used to wrap a caller-provided 2D tensor before constructing a + ``TensorDescriptor``. When ``t`` is already row-major with a 16-byte + aligned ``stride(0)`` it is returned unchanged; otherwise a padded buffer + is allocated, ``t``'s contents are copied into it, and a non-contiguous + view of the padded buffer with the original logical shape is returned. + ``None`` is propagated. + """ + if t is None: + return t + assert t.dim() == 2, "pad_for_tma_2d expects a 2D tensor" + if t.stride(-1) != 1: + # Non row-major; compact first so we own the inner stride. + t = t.contiguous() + itemsize = t.element_size() + if (t.stride(0) * itemsize) % 16 == 0: + return t + m, n = t.shape + align_elems = 16 // math.gcd(16, itemsize) + padded_n = ((n + align_elems - 1) // align_elems) * align_elems + padded = torch.empty((m, padded_n), dtype=t.dtype, device=t.device) + padded[:, :n] = t + return padded[:, :n] + + +# Host Launchers & Public API # {$nv-internal-release} + + +@register_impl("linear_bias_activation", backend="triton") +def linear_bias_act_dropout( + input: torch.Tensor, + weight: torch.Tensor, + bias: Union[torch.Tensor, None], + act_type: Union[str, None], + is_grad_enabled: bool = True, + dropout_seed: int = 1234, + dropout_prob: float = 0.0, + kernel_configs: dict = None, +): + r""" + Applies Linear (+Bias) + Activation + Dropout (Optional) on the input. + Only supports limited activation types + + Args: + input: Tensor + weight: Tensor + bias: Tensor or None + act_type: String. Supported Activation types: ``relu``, ``gelu``. Default is None + is_grad_enabled: Bool + dropout_seed: Int + dropout_prob: Float + + Shape: + input: (*, in_features) + weight: (out_features, in_features) + bias: (out_features) or None + """ + assert act_type is None or act_type in supported_act_types.keys(), ( + f"Activation Type: {act_type} is not supported by the fused linear layer" + ) + return _LinearBiasActDropout.apply( + input, + weight, + bias, + act_type, + is_grad_enabled, + dropout_seed, + dropout_prob, + ) + + +def _linear_bias_act_dropout_fwd_fn( + input: torch.Tensor, + weight: torch.Tensor, + bias: Union[torch.Tensor, None], + act_type: Union[str, None], + is_grad_enabled: bool, + dropout_seed: int, + dropout_prob: float, +): + M, K_A = input.shape + N, K_B = weight.shape + # K_B, N = w.shape + assert act_type is None or act_type in supported_act_types.keys(), ( + f"Activation Type: {act_type} is not supported by the fused linear layer" + ) + assert K_A == K_B, "incompatible dimensions" + K = K_A + assert input.is_contiguous(), "matrix input must be contiguous" + assert weight.is_contiguous(), "matrix weight must be contiguous" + # allocates output + c = torch.empty((M, N), device=input.device, dtype=input.dtype) + + act_inp = None + is_dropout = False + dropout_mask = None + if is_grad_enabled: + if act_type is not None: + act_inp = torch.empty((M, N), device=input.device, dtype=input.dtype) + if dropout_prob > 0.0: + is_dropout = True + # here use int8 because tma descriptor does not support bool, also torch bool/int8 both occupy 1 byte (8 bits). + # see https://discuss.pytorch.org/t/why-are-torch-bool-s-elements-1-byte-and-not-1-bit/204299 + dropout_mask = torch.empty((M, N), device=input.device, dtype=torch.int8) + + # TMA descriptors require a global memory allocation + def alloc_fn(size: int, alignment: int, stream: Optional[int]): + return torch.empty(size, device="cuda", dtype=torch.int8) + + triton.set_allocator(alloc_fn) + + # Add host descriptor support + dummy_block = [1, 1] + desc_input = TensorDescriptor(input, input.shape, input.stride(), dummy_block) + desc_weight = TensorDescriptor(weight, weight.shape, weight.stride(), dummy_block) + desc_c = TensorDescriptor(c, c.shape, c.stride(), dummy_block) + + # Create descriptors for optional tensors + desc_act_inp = ( + TensorDescriptor(act_inp, act_inp.shape, act_inp.stride(), dummy_block) if act_inp is not None else act_inp + ) + desc_dropout_mask = ( + TensorDescriptor( + dropout_mask, + dropout_mask.shape, + dropout_mask.stride(), + dummy_block, + ) + if dropout_mask is not None + else dropout_mask + ) + + # 2D launch kernel where each block gets its own program. + grid = lambda META: (triton.cdiv(M, META["BLOCK_M"]) * triton.cdiv(N, META["BLOCK_N"]),) + matmul_bias_activation_dropout_fwd[grid]( + desc_input, + desc_weight, + desc_c, + bias, + desc_act_inp, + desc_dropout_mask, + M, + N, + K, + input.stride(0), + input.stride(1), + weight.stride(0), + weight.stride(1), + c.stride(0), + c.stride(1), + get_tensor_alignment(input) % 32, + get_tensor_alignment(weight) % 32, + dropout_seed, + dropout_prob, + transpose_a=False, + transpose_b=True, + dot_out_dtype=tl.float32, + GROUP_M=8, + IS_BIAS=torch.is_tensor(bias), + ACT_TYPE=act_type, + IS_GRAD=is_grad_enabled, + IS_DROPOUT=is_dropout, + ) + return c, act_inp, dropout_mask + + +class _LinearBiasActDropout(torch.autograd.Function): + @staticmethod + def forward(ctx, a, w, bias, act_type, is_grad_enabled, dropout_seed, dropout_prob): + if a.dim() > 2: + inp_shape = a.shape + inp = a.view(-1, inp_shape[-1]) + else: + inp = a + out, act_inp, dropout_mask = _linear_bias_act_dropout_fwd_fn( + inp, w, bias, act_type, is_grad_enabled, dropout_seed, dropout_prob + ) + if a.dim() > 2: + out = out.view(*inp_shape[:-1], out.shape[-1]) + if is_grad_enabled: + if torch.is_tensor(bias): + ctx.is_bias = True + else: + ctx.is_bias = False + ctx.is_dropout = dropout_prob > 0.0 + ctx.dropout_prob = dropout_prob + ctx.act_type = act_type + ctx.save_for_backward(inp, w, bias, act_inp, dropout_mask) + return out + + @staticmethod + def backward(ctx, dy): + pass diff --git a/python/tutorials/tileir/tilegym/ops/triton/matmul.py b/python/tutorials/tileir/tilegym/ops/triton/matmul.py new file mode 100644 index 0000000000..0d29a250ac --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/matmul.py @@ -0,0 +1,1120 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import os +from typing import Optional + +import torch +import triton +import triton.language as tl +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.backend import get_available_triton_backend +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl +from tilegym.logger import get_logger + +# Constants & Type Aliases # {$nv-internal-release} + +logger = get_logger(__name__) + + +def _disable_autotune(): + return os.getenv("TILEGYM_DISABLE_AUTOTUNE", "0") == "1" + +# Capability Probe # {$nv-internal-release} + + +def _is_cuda(): + return triton.runtime.driver.active.get_current_target().backend in [ + "cuda", + "tileir", + ] + + +def _supports_host_descriptor(): + return _is_cuda() and torch.cuda.get_device_capability()[0] >= 8 + + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def _swizzle_2d(M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M): + pid = tl.program_id(axis=0) + num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) + num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) + num_pid_in_group = GROUP_SIZE_M * num_pid_n + + group_id = pid // num_pid_in_group + first_pid_m = group_id * GROUP_SIZE_M + group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M) + pid_m = first_pid_m + (pid % group_size_m) + pid_n = (pid % num_pid_in_group) // group_size_m + return pid_m, pid_n + + +@triton.jit +def _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M, NUM_SMS): + group_id = tile_id // num_pid_in_group + first_pid_m = group_id * GROUP_SIZE_M + group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M) + pid_m = first_pid_m + (tile_id % group_size_m) + pid_n = (tile_id % num_pid_in_group) // group_size_m + return pid_m, pid_n + + +# Autotune Config # {$nv-internal-release} + + +def _get_cuda_autotune_config(pre_hook=None): + if get_available_triton_backend() == "nvt": + config = [ + triton.Config( + dict(BLOCK_SIZE_M=32, BLOCK_SIZE_N=32, BLOCK_SIZE_K=64, GROUP_SIZE_M=8, occupancy=2), num_stages=5 + ), + triton.Config( + dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 + ), + triton.Config( + dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 + ), + triton.Config( + dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=256, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=4 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=64, GROUP_SIZE_M=8, occupancy=2), num_stages=5 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 + ), + ] + else: + config = [ + triton.Config( + dict(BLOCK_SIZE_M=32, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=5, num_warps=2 + ), + triton.Config( + dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=5, num_warps=2 + ), + triton.Config( + dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 + ), + triton.Config( + dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=256, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 + ), + triton.Config( + dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=256, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), num_stages=3, num_warps=8 + ), + ] + return config + + +def _get_pure_ptr_autotune_config(): + if torch.cuda.get_device_capability() == (8, 0): + return _get_cuda_autotune_config(pre_hook=None) + else: + config = [ + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), num_stages=3) + ] + return config + + +def _host_descriptor_pre_hook(nargs): + # {$nv-internal-release-oait begin} + EPILOGUE_SUBTILE = nargs.get("EPILOGUE_SUBTILE", False) + # {$nv-internal-release-oait end} + BLOCK_SIZE_M = nargs["BLOCK_SIZE_M"] + BLOCK_SIZE_N = nargs["BLOCK_SIZE_N"] + BLOCK_SIZE_K = nargs["BLOCK_SIZE_K"] + if not isinstance(nargs.get("A"), TensorDescriptor): + return + if nargs.get("transpose_a", False): + nargs["A"].block_shape = [BLOCK_SIZE_K, BLOCK_SIZE_M] + else: + nargs["A"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_K] + if nargs.get("transpose_b", False): + nargs["B"].block_shape = [BLOCK_SIZE_N, BLOCK_SIZE_K] + else: + nargs["B"].block_shape = [BLOCK_SIZE_K, BLOCK_SIZE_N] + nargs["C"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_N] + # {$nv-internal-release-oait begin} + if EPILOGUE_SUBTILE: + nargs["C"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_N // 2] + else: + nargs["C"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_N] + # {$nv-internal-release-oait end} + + +# from https://github.com/triton-lang/triton/blob/main/python/tutorials/09-persistent-matmul.py +def _matmul_get_configs(pre_hook=None): + # {$nv-internal-release-oait begin} + if get_available_triton_backend() == "oait": + # fmt: off + # default config from triton + config = [ + triton.Config(dict(BLOCK_SIZE_M=BM, BLOCK_SIZE_N=BN, BLOCK_SIZE_K=BK, GROUP_SIZE_M=8), num_stages=s, num_warps=w, pre_hook=pre_hook) + for BM in [128] + for BN in [128, 256] + for BK in [64, 128] + for s in [3, 4] + for w in [4, 8] + ] + # some configs for fp32 + config.extend( + [ + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=128, GROUP_SIZE_M=8), num_stages=2, num_warps=4, pre_hook=pre_hook), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), num_stages=2, num_warps=4, pre_hook=pre_hook), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), num_stages=2, num_warps=4, pre_hook=pre_hook), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=2, num_warps=4, pre_hook=pre_hook), + ] + ) + return config + # {$nv-internal-release-oait end} + if torch.cuda.get_device_capability() in [(12, 0), (12, 1)]: + # Default config from triton + config = [ + triton.Config( + dict(BLOCK_SIZE_M=BM, BLOCK_SIZE_N=BN, BLOCK_SIZE_K=BK, GROUP_SIZE_M=8, occupancy=occ), + pre_hook=pre_hook, + ) + for BM in [64, 128] + for BN in [64] + for BK in [64, 32] + for occ in [1, 2, 4] + ] + config.extend( + [ + triton.Config( + dict(BLOCK_SIZE_M=256, BLOCK_SIZE_N=256, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), pre_hook=pre_hook + ) + ] + ) + return config + elif torch.cuda.get_device_capability()[0] == 8: + # SM80 (A100): acc_regs = BM*BN/(warps*32) must be <= 255. + # Restrict to safe tiles; use fewer stages to fit SMEM. + return [ + triton.Config( + {"BLOCK_SIZE_M": BM, "BLOCK_SIZE_N": BN, "BLOCK_SIZE_K": BK, "GROUP_SIZE_M": 8, "occupancy": 1}, + pre_hook=pre_hook, + num_ctas=1, + num_warps=8, + num_stages=s, + ) + for BM, BN in [(128, 256), (256, 128), (128, 128)] + for BK in [64, 128] + for s in [2, 3, 4] + ] + # fmt: on + else: + return [ + triton.Config( + { + "BLOCK_SIZE_M": BM, + "BLOCK_SIZE_N": BN, + "BLOCK_SIZE_K": BK, + "GROUP_SIZE_M": 8, + "occupancy": 1, + }, + pre_hook=pre_hook, + num_ctas=NUM_CTAS, + num_warps=NUM_WARPS, + # For the 256x256x64 tile, we use num_stages=5. + num_stages=5, + ) + for BM in [128, 256, 512] + for BN in [128, 256, 512] + for BK in [32, 64, 128] + for NUM_CTAS in ([1, 2, 4] if torch.cuda.get_device_capability()[0] > 8 else [1]) + for NUM_WARPS in [4, 8] + ] + # fmt: on + + +def _prune_configs_by_shape(configs, named_args): + """Drop autotune configs whose block shape vastly exceeds the problem shape. + + Background + ---------- + The TMA matmul autotune grid on sm_90+ is + ``3 BM * 3 BN * 3 BK * 3 NUM_CTAS * 2 NUM_WARPS = 162`` configs. The dtype + whitelist below (``early_config_prune`` / ``early_config_prune_for_persistent``) + reduces this sharply for fp16/fp32/fp8, but dtypes it doesn't cover (e.g. + ``bf16``) keep the full grid. On small shapes such as M=N=K=64 every cold + miss spawns a ``tileiras`` compile; at 162 variants this easily blows the + 300s pytest-timeout. + + Policy + ------ + A tile larger than ``2 * dim`` contributes no extra parallelism + (``cdiv(dim, block)`` is already 1), so we drop BLOCK_SIZE_{M,N,K} beyond + that cap. To guarantee a non-empty result when the problem dim is smaller + than the smallest legal block, the cap is floored to the minimum block + size present in the surviving config list. If pruning would empty the list + we fall back to the input configs unchanged. + """ + if not configs: + return configs + M = named_args.get("M") + N = named_args.get("N") + K = named_args.get("K") + # Only apply when we actually know the problem dims. If the kernel is ever + # launched without these positional args (shouldn't happen, but defensive) + # we leave the set untouched. + if M is None or N is None or K is None: + return configs + + def _kw(c, name, default): + return c.kwargs.get(name, default) + + min_bm = min(_kw(c, "BLOCK_SIZE_M", 1) for c in configs) + min_bn = min(_kw(c, "BLOCK_SIZE_N", 1) for c in configs) + min_bk = min(_kw(c, "BLOCK_SIZE_K", 1) for c in configs) + + bm_cap = max(min_bm, 2 * M) + bn_cap = max(min_bn, 2 * N) + bk_cap = max(min_bk, 2 * K) + + pruned = [ + c + for c in configs + if _kw(c, "BLOCK_SIZE_M", 0) <= bm_cap + and _kw(c, "BLOCK_SIZE_N", 0) <= bn_cap + and _kw(c, "BLOCK_SIZE_K", 0) <= bk_cap + ] + return pruned or configs + + +def _early_config_prune(configs, named_args, **kwargs): + if get_available_triton_backend() == "nvt" and torch.cuda.get_device_capability() not in [ + (12, 0), + (12, 1), + ]: + dtype = kwargs.get("DTYPE") + gpu_capability = torch.cuda.get_device_capability() + supports_multi_cta = gpu_capability[0] > 8 + + tune_space = [] + if dtype == torch.float32: + # TODO: Remove (256, 256, 64, 2) after resolving other perf issues. # {$nv-TODO} + if supports_multi_cta: + tune_space = [ + (512, 256, 32, 4), + (256, 256, 32, 2), + (256, 256, 64, 2), + (128, 512, 32, 4), + (512, 512, 64, 4), + (256, 512, 32, 2), + (512, 256, 64, 2), + ] + if gpu_capability == (9, 0): + block_m_sizes = [128, 256] + block_n_sizes = [128, 256] + block_k_sizes = [32] + num_ctas = [1] + num_warps = [8] + tune_space = [ + (bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps + ] + else: + # For GPU capability < 9.0, only NUM_CTAS=1 is supported + tune_space = [ + (512, 256, 32, 1), + (256, 256, 32, 1), + (256, 256, 64, 1), + (128, 512, 32, 1), + (512, 512, 64, 1), + (256, 512, 32, 1), + (512, 256, 64, 1), + ] + elif dtype == torch.float16: + if supports_multi_cta: + tune_space = [ + (128, 512, 64, 4), + (256, 256, 64, 1), + (256, 256, 64, 2), + (256, 256, 64, 4), + (256, 256, 32, 2), + (256, 512, 64, 2), + (512, 512, 128, 4), + (512, 256, 64, 2), + ] + if gpu_capability == (9, 0): + block_m_sizes = [128, 256] + block_n_sizes = [128, 256] + block_k_sizes = [64] + num_ctas = [1] + num_warps = [8] + tune_space = [ + (bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps + ] + else: + # SM80 (A100): restrict to safe tiles (acc_regs = BM*BN/(8*32) <= 255). + tune_space = [ + (128, 256, 64, 1), + (256, 128, 64, 1), + (128, 128, 64, 1), + (128, 256, 128, 1), + (256, 128, 128, 1), + (128, 128, 128, 1), + ] + elif dtype in [torch.float8_e4m3fn, torch.float8_e5m2]: + if supports_multi_cta: + tune_space = [ + (256, 256, 64, 2), + (256, 256, 128, 4), + (128, 256, 128, 2), + (256, 256, 128, 1), + (256, 256, 128, 2), + (512, 128, 128, 4), + (256, 128, 128, 2), + (128, 128, 128, 1), + (512, 512, 128, 4), + (512, 256, 128, 2), + ] + if gpu_capability == (9, 0): + block_m_sizes = [128, 256] + block_n_sizes = [128, 256] + block_k_sizes = [128] + num_ctas = [1] + num_warps = [8] + tune_space = [ + (bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps + ] + else: + # For GPU capability < 9.0, only NUM_CTAS=1 is supported + tune_space = [ + (256, 256, 64, 1), + (256, 256, 128, 1), + (128, 256, 128, 1), + (512, 128, 128, 1), + (256, 128, 128, 1), + (128, 128, 128, 1), + (512, 512, 128, 1), + (512, 256, 128, 1), + ] + else: + # Unknown dtype (e.g. bf16): skip the dtype whitelist, but still + # apply shape-based pruning below to keep the autotune search + # space reasonable on small problems. + return _prune_configs_by_shape(configs, named_args) + pruned_configs = [] + for config in configs: + BLOCK_SIZE_M = config.kwargs.get("BLOCK_SIZE_M") + BLOCK_SIZE_N = config.kwargs.get("BLOCK_SIZE_N") + BLOCK_SIZE_K = config.kwargs.get("BLOCK_SIZE_K") + NUM_CTAS = config.num_ctas + NUM_WARPS = config.num_warps + # Support both 4-tuples and 5-tuples (with num_warps) + if (BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, NUM_CTAS) in tune_space or ( + BLOCK_SIZE_M, + BLOCK_SIZE_N, + BLOCK_SIZE_K, + NUM_CTAS, + NUM_WARPS, + ) in tune_space: + pruned_configs.append(config) + return _prune_configs_by_shape(pruned_configs, named_args) + else: + return _prune_configs_by_shape(configs, named_args) + + +def _matmul_tma_persistent_get_configs(pre_hook=None): + # fmt: off + # {$nv-internal-release-oait begin} + if get_available_triton_backend() == "oait": + return [ + triton.Config( + { + 'BLOCK_SIZE_M': BM, + 'BLOCK_SIZE_N': BN, + "BLOCK_SIZE_K": BK, + "GROUP_SIZE_M": 8, + "EPILOGUE_SUBTILE": SUBTILE, + "occupancy": 1, + }, + num_stages=s, + num_warps=w, + pre_hook=pre_hook, + ) # + for BM in [128] + for BN in [128, 256] + for BK in [64, 128] + for s in ([2, 3, 4]) + for w in [4, 8] + for SUBTILE in [True, False] + ] + # {$nv-internal-release-oait end} + if torch.cuda.get_device_capability() in [(12, 0), (12, 1)]: + configs = [ + triton.Config( + { + 'BLOCK_SIZE_M': BM, + 'BLOCK_SIZE_N': BN, + "BLOCK_SIZE_K": BK, + "GROUP_SIZE_M": 8, + # {$nv-internal-release-oait begin} + "EPILOGUE_SUBTILE": SUBTILE, + # {$nv-internal-release-oait end} + "occupancy": occ, + }, + pre_hook=pre_hook, + ) # + for BM in [64, 128] + for BN in [64] + for BK in [64] + for SUBTILE in [True, False] + for occ in [1, 2, 4] + ] + + configs.extend( + [ + triton.Config( + { + 'BLOCK_SIZE_M': 256, + 'BLOCK_SIZE_N': 256, + "BLOCK_SIZE_K": 64, + "GROUP_SIZE_M": 8, + # {$nv-internal-release-oait begin} + "EPILOGUE_SUBTILE": False, + # {$nv-internal-release-oait end} + "occupancy": 1, + }, + pre_hook=pre_hook, + ) + ] + ) + return configs + elif torch.cuda.get_device_capability()[0] == 8: + # SM80 (A100): same register-safety constraint as _matmul_get_configs. + # Also explore EPILOGUE_SUBTILE which OAI selects for persistent kernels. + return [ + triton.Config( + {'BLOCK_SIZE_M': BM, 'BLOCK_SIZE_N': BN, 'BLOCK_SIZE_K': BK, + 'GROUP_SIZE_M': 8, 'EPILOGUE_SUBTILE': subtile, 'occupancy': 1}, + pre_hook=pre_hook, num_ctas=1, num_warps=8, num_stages=s, + ) + for BM, BN in [(128, 256), (256, 128), (128, 128)] + for BK in [64, 128] + for s in [2, 3, 4] + for subtile in [True, False] + ] + else: + # For dev and debug + if _disable_autotune(): + return [ + triton.Config( + { + 'BLOCK_SIZE_M': 256, + 'BLOCK_SIZE_N': 256, + "BLOCK_SIZE_K": 64, + "GROUP_SIZE_M": 8, + # {$nv-internal-release-oait begin} + "EPILOGUE_SUBTILE": False, + # {$nv-internal-release-oait end} + "occupancy": 1 + }, + num_stages=5, + pre_hook=pre_hook, + num_ctas=2, + ) + ] + else: + return [ + triton.Config( + { + 'BLOCK_SIZE_M': BM, + 'BLOCK_SIZE_N': BN, + "BLOCK_SIZE_K": BK, + "GROUP_SIZE_M": 8, + # {$nv-internal-release-oait begin} + "EPILOGUE_SUBTILE": False, + # {$nv-internal-release-oait end} + "occupancy": 1}, + pre_hook=pre_hook, + num_ctas=NUM_CTAS, + num_warps=NUM_WARPS, + # For the 256x256x64 tile, we use num_stages=5. + num_stages=5, + ) + for BM in [128, 256, 512] + for BN in [128, 256, 512] + for BK in [32, 64, 128] + for NUM_CTAS in ([1, 2, 4] if torch.cuda.get_device_capability()[0] > 8 else [1]) + for NUM_WARPS in [4, 8] + ] + # fmt: on + + +def _early_config_prune_for_persistent(configs, named_args, **kwargs): + if get_available_triton_backend() == "nvt" and torch.cuda.get_device_capability() not in [ + (12, 0), + (12, 1), + ]: + dtype = kwargs.get("DTYPE") + gpu_capability = torch.cuda.get_device_capability() + supports_multi_cta = gpu_capability[0] > 8 + + tune_space = [] + if dtype == torch.float32: + # TODO: Remove (256, 256, 64, 2) after resolving other perf issues. # {$nv-TODO} + if supports_multi_cta: + tune_space = [ + (512, 256, 32, 4), + (256, 256, 32, 2), + (256, 256, 64, 2), + (128, 512, 32, 4), + (256, 256, 128, 2), + (256, 256, 64, 1), + ] + if gpu_capability == (9, 0): + block_m_sizes = [128, 256] + block_n_sizes = [128, 256] + block_k_sizes = [32] + num_ctas = [1] + num_warps = [8] + tune_space = [ + (bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps + ] + else: + # For GPU capability < 9.0, only NUM_CTAS=1 is supported + tune_space = [ + (512, 256, 32, 1), + (256, 256, 32, 1), + (256, 256, 64, 1), + (128, 512, 32, 1), + (256, 256, 128, 1), + ] + elif dtype == torch.float16: + if supports_multi_cta: + tune_space = [ + (128, 512, 64, 4), + (256, 256, 64, 2), + (256, 256, 64, 1), + (256, 256, 64, 4), + (256, 256, 128, 2), + (512, 256, 32, 4), + ] + if gpu_capability == (9, 0): + block_m_sizes = [128, 256] + block_n_sizes = [128, 256] + block_k_sizes = [64] + num_ctas = [1] + num_warps = [8] + tune_space = [ + (bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps + ] + else: + # SM80 (A100): restrict to safe tiles (acc_regs = BM*BN/(8*32) <= 255). + tune_space = [ + (128, 256, 64, 1), + (256, 128, 64, 1), + (128, 128, 64, 1), + (128, 256, 128, 1), + (256, 128, 128, 1), + (128, 128, 128, 1), + ] + elif dtype in [torch.float8_e4m3fn, torch.float8_e5m2]: + if supports_multi_cta: + tune_space = [ + (256, 256, 128, 4), + (128, 256, 128, 2), + (256, 256, 128, 2), + (512, 128, 128, 4), + (256, 128, 128, 2), + (256, 256, 64, 2), + (256, 256, 32, 2), + ] + if gpu_capability == (9, 0): + block_m_sizes = [128, 256] + block_n_sizes = [128, 256] + block_k_sizes = [128] + num_ctas = [1] + num_warps = [8] + tune_space = [ + (bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps + ] + else: + # For GPU capability < 9.0, only NUM_CTAS=1 is supported + tune_space = [ + (256, 256, 128, 1), + (128, 256, 128, 1), + (512, 128, 128, 1), + (256, 128, 128, 1), + (256, 256, 64, 1), + (256, 256, 32, 1), + ] + else: + # Unknown dtype (e.g. bf16): skip the dtype whitelist, but still + # apply shape-based pruning below to keep the autotune search + # space reasonable on small problems. + return _prune_configs_by_shape(configs, named_args) + pruned_configs = [] + for config in configs: + BLOCK_SIZE_M = config.kwargs.get("BLOCK_SIZE_M") + BLOCK_SIZE_N = config.kwargs.get("BLOCK_SIZE_N") + BLOCK_SIZE_K = config.kwargs.get("BLOCK_SIZE_K") + NUM_CTAS = config.num_ctas + NUM_WARPS = config.num_warps + # Support both 4-tuples and 5-tuples (with num_warps) + if (BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, NUM_CTAS) in tune_space or ( + BLOCK_SIZE_M, + BLOCK_SIZE_N, + BLOCK_SIZE_K, + NUM_CTAS, + NUM_WARPS, + ) in tune_space: + pruned_configs.append(config) + return _prune_configs_by_shape(pruned_configs, named_args) + else: + return _prune_configs_by_shape(configs, named_args) + + +# Device Kernels # {$nv-internal-release} + + +@triton.autotune( + configs=_get_pure_ptr_autotune_config(), + key=["M", "N", "K", "transpose_a", "transpose_b"], +) +@triton.jit +def _matmul_kernel_pure_ptr( + a_ptr, b_ptr, c_ptr, + M, N, K, + stride_am, stride_ak, + stride_bk, stride_bn, + stride_cm, stride_cn, + BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, + BLOCK_SIZE_K: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, ACTIVATION: tl.constexpr, + transpose_a: tl.constexpr, transpose_b: tl.constexpr, +): # fmt: skip + # Initialize offsets + pid_m, pid_n = _swizzle_2d(M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M) + + offs_am = (pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)) % M + offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)) % N + offs_k = tl.arange(0, BLOCK_SIZE_K) + + if transpose_a: + a_ptrs = a_ptr + (offs_am[:, None] * stride_ak + offs_k[None, :] * stride_am) + else: + a_ptrs = a_ptr + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak) + if transpose_b: + b_ptrs = b_ptr + (offs_k[:, None] * stride_bn + offs_bn[None, :] * stride_bk) + else: + b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn) + + # KV loop + accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_SIZE_K)): + a = tl.load(a_ptrs, mask=offs_k[None, :] < K - k * BLOCK_SIZE_K, other=0.0) + b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k * BLOCK_SIZE_K, other=0.0) + accumulator = tl.dot(a, b, accumulator) + if transpose_a: + a_ptrs += BLOCK_SIZE_K * stride_am + else: + a_ptrs += BLOCK_SIZE_K * stride_ak + if transpose_b: + b_ptrs += BLOCK_SIZE_K * stride_bn + else: + b_ptrs += BLOCK_SIZE_K * stride_bk + + # store output c + if ACTIVATION == "relu": + accumulator = tl.maximum(accumulator, 0.0) + c = accumulator.to(c_ptr.dtype.element_ty) + offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M) + offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N) + c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :] + c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N) + tl.store(c_ptrs, c, mask=c_mask) + + +@triton.autotune( + configs=_matmul_get_configs(pre_hook=_host_descriptor_pre_hook), + key=["M", "N", "K", "transpose_a", "transpose_b", "DTYPE"], + prune_configs_by={"early_config_prune": _early_config_prune}, +) +@triton.jit +def _matmul_kernel( + A, + B, + C, + M, + N, + K, + BLOCK_SIZE_M: tl.constexpr, + BLOCK_SIZE_N: tl.constexpr, + BLOCK_SIZE_K: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, + DTYPE: tl.constexpr, +): + pid_m, pid_n = _swizzle_2d(M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M) + + num_tiles = tl.cdiv(K, BLOCK_SIZE_K) + sum = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32) + + offs_am = pid_m * BLOCK_SIZE_M + offs_bn = pid_n * BLOCK_SIZE_N + + dtype = C.type.block_type.element_ty + + for k in range(num_tiles): + offs_k = k * BLOCK_SIZE_K + if transpose_a: + a = A.load([offs_k, offs_am]) + a = tl.trans(a) + else: + a = A.load([offs_am, offs_k]) + + if transpose_b: + b = B.load([offs_bn, offs_k]) + b = tl.trans(b) + else: + b = B.load([offs_k, offs_bn]) + sum = tl.dot(a, b, sum) + + sum = sum.to(dtype) + + offs_cm = pid_m * BLOCK_SIZE_M + offs_cn = pid_n * BLOCK_SIZE_N + C.store([offs_cm, offs_cn], sum) + + +@triton.autotune( + configs=_matmul_tma_persistent_get_configs(pre_hook=_host_descriptor_pre_hook), + key=["M", "N", "K", "transpose_a", "transpose_b", "DTYPE"], + prune_configs_by={"early_config_prune": _early_config_prune_for_persistent}, +) +@triton.jit +def _static_persistent_matmul_kernel( + A, + B, + C, + M, + N, + K, + BLOCK_SIZE_M: tl.constexpr, + BLOCK_SIZE_N: tl.constexpr, + BLOCK_SIZE_K: tl.constexpr, + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, + # {$nv-internal-release-oait begin} + EPILOGUE_SUBTILE: tl.constexpr, + # {$nv-internal-release-oait end} + DTYPE: tl.constexpr, + occupancy: tl.constexpr, + num_ctas: tl.constexpr, +): + """Static persistent matmul kernel: C = A @ B with static scheduling.""" + # {$nv-internal-release-oait begin} + """ + This kernel is adapted from triton tutorial, for oait get the best performance on that. + For NVTriton, we don't need to introdce `tile_id_c`, `flatten`, `warp_specialize`, `EPILOGUE_SUBTILE`. + """ + # {$nv-internal-release-oait end} + + dtype = C.type.block_type.element_ty + # Get program ID + start_pid = tl.program_id(axis=0) + + # Calculate total number of tiles + num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) + num_pid_n = tl.cdiv(N, BLOCK_SIZE_N) + k_tiles = tl.cdiv(K, BLOCK_SIZE_K) + num_tiles = num_pid_m * num_pid_n + num_programs = tl.num_programs(0) + + # {$nv-internal-release-oait begin} + # tile_id_c is used in the epilogue to break the dependency between + # the prologue and the epilogue in oait. In NVTiton, we don't need to introdce this, + # as well as `flatten` and `warp_specialize`. We don't need to recalculate the pid_m and pid_n using tile_id_c + tile_id_c = start_pid - num_programs + # {$nv-internal-release-oait end} + num_pid_in_group = GROUP_SIZE_M * num_pid_n + + # Static persistent scheduling loop + for tile_id in tl.range(start_pid, num_tiles, num_programs, flatten=True): + # Calculate tile coordinates using GROUP_SIZE_M grouping + pid_m, pid_n = _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M, num_programs) + offs_am = pid_m * BLOCK_SIZE_M + offs_bn = pid_n * BLOCK_SIZE_N + + # Initialize accumulator + accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32) + + # K-dimension loop + for k_tile in range(k_tiles): + offs_k = k_tile * BLOCK_SIZE_K + # Load A tile + if transpose_a: + # A is transposed: load from (K, M) layout + a = A.load([offs_k, offs_am]) + a = tl.trans(a) # Convert to (BLOCK_SIZE_M, BLOCK_SIZE_K) + else: + # A is normal: load from (M, K) layout + a = A.load([offs_am, offs_k]) + + # Load B tile + if transpose_b: + # B is transposed: load from (N, K) layout + b = B.load([offs_bn, offs_k]) + b = tl.trans(b) # Convert to (BLOCK_SIZE_K, BLOCK_SIZE_N) + else: + # B is normal: load from (K, N) layout + b = B.load([offs_k, offs_bn]) + + # Matrix multiplication and accumulation + accumulator = tl.dot(a, b, accumulator) + # {$nv-internal-release-oait begin} + tile_id_c += num_programs + pid_m, pid_n = _compute_pid(tile_id_c, num_pid_in_group, num_pid_m, GROUP_SIZE_M, num_programs) + # {$nv-internal-release-oait indent-end} + offs_am_c = pid_m * BLOCK_SIZE_M + offs_bn_c = pid_n * BLOCK_SIZE_N + + # {$nv-internal-release-oait begin} + # Epilogue subtiling is a technique to break our computation and stores into multiple pieces + # By subtiling we can reduce shared memory consumption by the epilogue and instead use that + # memory to increase our stage count. + # In this case we partition the accumulator into 2 BLOCK_SIZE_M x BLOCK_SIZE_N // 2 tensors + if EPILOGUE_SUBTILE: + acc = tl.reshape(accumulator, (BLOCK_SIZE_M, 2, BLOCK_SIZE_N // 2)) + acc = tl.permute(acc, (0, 2, 1)) + acc0, acc1 = tl.split(acc) + c0 = acc0.to(dtype) + C.store([offs_am_c, offs_bn_c], c0) + c1 = acc1.to(dtype) + C.store([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2], c1) + else: + # {$nv-internal-release-oait end} + # {$nv-internal-release-oait indent-begin -4} + # Convert to output dtype and store + result = accumulator.to(dtype) + C.store([offs_am_c, offs_bn_c], result) + # {$nv-internal-release-oait indent-end} + + +# Host Launchers & Public API # {$nv-internal-release} + + +def matmul_fn( + a: torch.Tensor, + b: torch.Tensor, + transpose_a: bool = False, + transpose_b: bool = False, + kernel_configs: dict = None, +): + if transpose_a: + K_A, M = a.shape + else: + M, K_A = a.shape + + if transpose_b: + N, K_B = b.shape + else: + K_B, N = b.shape + + assert K_A == K_B, "incompatible dimensions" + K = K_A + + assert a.is_contiguous(), "matrix A must be contiguous" + assert b.is_contiguous(), "matrix B must be contiguous" + # allocates output + c = torch.zeros((M, N), device=a.device, dtype=a.dtype) + # 1D launch kernel where each block gets its own program. + grid = lambda META: (triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),) + + _matmul_kernel_pure_ptr[grid]( + a, + b, + c, + M, + N, + K, + a.stride(0), + a.stride(1), + b.stride(0), + b.stride(1), + c.stride(0), + c.stride(1), + ACTIVATION="", + transpose_a=transpose_a, + transpose_b=transpose_b, + ) + return c + + +class MatmulFunction(torch.autograd.Function): + @staticmethod + def forward(ctx, a, b, transpose_a=False, transpose_b=False): + c = matmul_fn(a, b, transpose_a=transpose_a, transpose_b=transpose_b) + ctx.save_for_backward(a, b) + ctx.transpose_a = transpose_a + ctx.transpose_b = transpose_b + return c + + @staticmethod + def backward(ctx, dy): + a, b = ctx.saved_tensors + transpose_a = ctx.transpose_a + transpose_b = ctx.transpose_b + + # Compute gradients with appropriate transposes + # da = dy @ b^T (or handle transposes based on forward transposes) + # db = a^T @ dy (or handle transposes based on forward transposes) + if transpose_a: + # Forward was: c = a^T @ b + da = matmul_fn(b, dy, transpose_a=transpose_b, transpose_b=True) + else: + # Forward was: c = a @ b + da = matmul_fn(dy, b, transpose_b=not transpose_b) + + if transpose_b: + # Forward was: c = a @ b^T + db = matmul_fn(dy, a, transpose_a=True, transpose_b=transpose_a) + else: + # Forward was: c = a @ b + db = matmul_fn(a, dy, transpose_a=not transpose_a) + + return da, db, None, None + + +@register_impl("matmul", backend="triton") +def matmul( + a: torch.Tensor, + b: torch.Tensor, + trans_a=False, + trans_b=False, + static_persistent=None, + use_tma=False, + **kwargs, +): + if use_tma: + if static_persistent is None: + static_persistent = True + + # Get matrix dimensions + if trans_a: + K, M = a.shape + else: + M, K = a.shape + if trans_b: + N, KB = b.shape + else: + KB, N = b.shape + assert K == KB, f"Incompatible matrices: K dimension of A is {K}, K dimension of B is {KB}" + + # Create output tensor + c = torch.empty((M, N), device=a.device, dtype=a.dtype) + + # TMA descriptors require a global memory allocation + def alloc_fn(size: int, alignment: int, stream: Optional[int]): + return torch.empty(size, device="cuda", dtype=torch.int8) + + triton.set_allocator(alloc_fn) + + # Add host descriptor support + if _supports_host_descriptor(): + dummy_block = [1, 1] + desc_a = TensorDescriptor(a, a.shape, a.stride(), dummy_block) + desc_b = TensorDescriptor(b, b.shape, b.stride(), dummy_block) + desc_c = TensorDescriptor(c, c.shape, c.stride(), dummy_block) + else: + desc_a = a + desc_b = b + desc_c = c + raise NotImplementedError( + "Only support host descriptor for now, need to modify kernel code to support non-host descriptor" + ) + + # Grid calculation + if static_persistent: + NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count + grid = lambda META: ( + min( + NUM_SMS // META["num_ctas"], + triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]), + ) + * META["occupancy"], + ) + kernel = _static_persistent_matmul_kernel + else: + grid = lambda META: (triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),) + kernel = _matmul_kernel + + logger.debug( + f"[triton] calling matmul_kernel: use_tma: {use_tma}, static_persistent: {static_persistent}, trans_a: {trans_a}, trans_b: {trans_b}" + ) + kernel[grid]( + desc_a, + desc_b, + desc_c, + M, + N, + K, + transpose_a=trans_a, + transpose_b=trans_b, + DTYPE=a.dtype, + ) + return c + else: + return MatmulFunction.apply(a, b, trans_a, trans_b) + + +# {$nv-internal-release begin} + +# Backend Registration & Perf Markers # {$nv-internal-release} + +mark_perf_ready("matmul", "nvt") + +# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py b/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py new file mode 100644 index 0000000000..ade1cd7e2a --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py @@ -0,0 +1,223 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# {$nv-internal-release file} +# Adapted from https://github.com/openai/triton +import heapq + +import torch +import triton +from triton.runtime import driver +from triton.testing import get_dram_gbps +from triton.testing import get_max_simd_tflops +from triton.testing import get_max_tensorcore_tflops +from triton.testing import nvsmi +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.logger import get_logger + +logger = get_logger(__name__) + + +def _get_clock_rate_in_khz(): + return nvsmi(["clocks.max.sm"])[0] * 1e3 + + +def _get_tensorcore_tflops(device, num_ctas, num_warps, dtype): + """return compute throughput in TOPS""" + total_warps = num_ctas * min(num_warps, 4) + num_subcores = driver.active.utils.get_device_properties(device)["multiprocessor_count"] * 4 # on recent GPUs + tflops = ( + min(num_subcores, total_warps) + / num_subcores + * get_max_tensorcore_tflops(dtype, _get_clock_rate_in_khz(), device) + ) + return tflops + + +def _get_simd_tflops(device, num_ctas, num_warps, dtype): + """return compute throughput in TOPS""" + total_warps = num_ctas * min(num_warps, 4) + num_subcores = driver.active.utils.get_device_properties(device)["multiprocessor_count"] * 4 # on recent GPUs + tflops = ( + min(num_subcores, total_warps) / num_subcores * get_max_simd_tflops(dtype, _get_clock_rate_in_khz(), device) + ) + return tflops + + +def _get_tflops(device, num_ctas, num_warps, dtype): + capability = torch.cuda.get_device_capability(device) + if capability[0] < 8 and dtype == torch.float32: + return _get_simd_tflops(device, num_ctas, num_warps, dtype) + return _get_tensorcore_tflops(device, num_ctas, num_warps, dtype) + + +def estimate_matmul_time( + num_warps, + num_stages, + A, + B, + C, + M, + N, + K, + BLOCK_M, + BLOCK_N, + BLOCK_K, + debug=False, + **kwargs, +): + """return estimated running time in ms + = max(compute, loading) + store""" + if "SPLIT_K" not in kwargs: + SPLIT_K = 1 + else: + SPLIT_K = kwargs["SPLIT_K"] + + device = torch.cuda.current_device() + if isinstance(A, TensorDescriptor): + dtype = A.base.dtype + dtsize = A.base.element_size() + else: + dtype = A.dtype + dtsize = A.element_size() + + num_cta_m = triton.cdiv(M, BLOCK_M) + num_cta_n = triton.cdiv(N, BLOCK_N) + num_cta_k = SPLIT_K + num_ctas = num_cta_m * num_cta_n * num_cta_k + + # If the input is smaller than the block size + M, N = max(M, BLOCK_M), max(N, BLOCK_N) + + # time to compute + total_ops = 2 * M * N * K / (1024 * 1024 * 1024) # GOPS + tput = _get_tflops(device, num_ctas, num_warps, dtype) + compute_ms = total_ops / tput + + # time to load data + num_sm = driver.active.utils.get_device_properties(device)["multiprocessor_count"] + active_cta_ratio = min(1, num_ctas / num_sm) + active_cta_ratio_bw1 = min(1, num_ctas / 32) # 32 active ctas are enough to saturate + active_cta_ratio_bw2 = max(min(1, (num_ctas - 32) / (108 - 32)), 0) # 32-108, remaining 5% + dram_bw = get_dram_gbps(device) * (active_cta_ratio_bw1 * 0.95 + active_cta_ratio_bw2 * 0.05) # in GB/s + l2_bw = dram_bw * 4 # rough estimation (should be 4.7 for A100?) + # assume 80% of (following) loads are in L2 cache + load_a_dram = M * K * dtsize * (1 + 0.2 * (num_cta_n - 1)) + load_a_l2 = M * K * dtsize * 0.8 * (num_cta_n - 1) + load_b_dram = N * K * dtsize * (1 + 0.2 * (num_cta_m - 1)) + load_b_l2 = N * K * dtsize * 0.8 * (num_cta_m - 1) + # total + total_dram = (load_a_dram + load_b_dram) / (1024 * 1024) # MB + total_l2 = (load_a_l2 + load_b_l2) / (1024 * 1024) + # loading time in ms + load_ms = total_dram / dram_bw + total_l2 / l2_bw + + # estimate storing time + store_bw = dram_bw * 0.6 # :o + store_c_dram = M * N * dtsize * SPLIT_K / (1024 * 1024) # MB + if SPLIT_K == 1: + store_ms = store_c_dram / store_bw + else: + reduce_bw = store_bw + store_ms = store_c_dram / reduce_bw + # c.zero_() + zero_ms = M * N * 2 / (1024 * 1024) / store_bw + store_ms += zero_ms + + total_time_ms = max(compute_ms, load_ms) + store_ms + logger.debug( + f"Total time: {total_time_ms}ms, compute time: {compute_ms}ms, " + f"loading time: {load_ms}ms, store time: {store_ms}ms, " + f"Activate CTAs: {active_cta_ratio * 100}%" + ) + return total_time_ms + + +def early_config_prune(configs, named_args, **kwargs): + device = torch.cuda.current_device() + capability = torch.cuda.get_device_capability() + # BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps, num_stages + if isinstance(named_args["A"], TensorDescriptor): + dtsize = named_args["A"].base.element_size() + dtype = named_args["A"].base.dtype + else: + dtsize = named_args["A"].element_size() + dtype = named_args["A"].dtype + + # 1. make sure we have enough smem + pruned_configs = [] + for config in configs: + kw = config.kwargs + if "BLOCK_K" not in kw: + kw["BLOCK_K"] = 1 + BLOCK_M, BLOCK_N, BLOCK_K, num_stages = ( + kw["BLOCK_M"], + kw["BLOCK_N"], + kw["BLOCK_K"], + config.num_stages, + ) + + max_shared_memory = driver.active.utils.get_device_properties(device)["max_shared_mem"] + required_shared_memory = (BLOCK_M + BLOCK_N) * BLOCK_K * num_stages * dtsize + if required_shared_memory <= max_shared_memory: + pruned_configs.append(config) + configs = pruned_configs + + # Some dtypes do not allow atomic_add + if dtype not in [torch.float16, torch.float32]: + # A missing "SPLIT_K" key means SPLIT_K == 1 (no split), so keep the config. + configs = [config for config in configs if config.kwargs.get("SPLIT_K", 1) == 1] + + # group configs by (BLOCK_M,_N,_K, SPLIT_K, num_warps) + configs_map = {} + for config in configs: + kw = config.kwargs + BLOCK_M, BLOCK_N, BLOCK_K, num_warps, num_stages = ( + kw["BLOCK_M"], + kw["BLOCK_N"], + kw["BLOCK_K"], + config.num_warps, + config.num_stages, + ) + if "SPLIT_K" not in kw: + SPLIT_K = 1 + else: + SPLIT_K = kw["SPLIT_K"] + + key = (BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps) + if key in configs_map: + configs_map[key].append((config, num_stages)) + else: + configs_map[key] = [(config, num_stages)] + + pruned_configs = [] + for k, v in configs_map.items(): + BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps = k + if capability[0] >= 8: + # compute cycles (only works for ampere GPUs) + mmas = BLOCK_M * BLOCK_N * BLOCK_K / (16 * 8 * 16) + mma_cycles = mmas / min(4, num_warps) * 8 + + ldgsts_latency = 300 # Does this matter? + optimal_num_stages = ldgsts_latency / mma_cycles + + # nearest stages, prefer large #stages + nearest = heapq.nsmallest( + 2, + v, + key=lambda x: ( + 10 + abs(x[1] - optimal_num_stages) + if (x[1] - optimal_num_stages) < 0 + else x[1] - optimal_num_stages + ), + ) + + for n in nearest: + pruned_configs.append(n[0]) + else: # Volta & Turing only supports num_stages <= 2 + random_config = v[0][0] + random_config.num_stages = 2 + pruned_configs.append(random_config) + return pruned_configs diff --git a/python/tutorials/tileir/tilegym/ops/triton/mla.py b/python/tutorials/tileir/tilegym/ops/triton/mla.py new file mode 100644 index 0000000000..ecd25d036a --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/mla.py @@ -0,0 +1,644 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import math +from typing import Optional + +import torch +import triton +import triton.language as tl +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.autotune import is_autotune_disabled +from tilegym.backend import get_available_triton_backend +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl +from tilegym.logger import get_logger + +# Constants & Type Aliases # {$nv-internal-release} + +logger = get_logger(__name__) + +INV_LOG_2 = tl.constexpr(1.0 / math.log(2)) + +# Capability Probe # {$nv-internal-release} + + +def _supports_host_descriptor(): + return torch.cuda.get_device_capability()[0] >= 9 + + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def _maybe_make_tensor_desc(desc_or_ptr, shape, strides, block_shape): + if isinstance(desc_or_ptr, tl.tensor_descriptor): + return desc_or_ptr + else: + return tl.make_tensor_descriptor(desc_or_ptr, shape, strides, block_shape) + + +@triton.jit +def _attn_fwd_inner( + K_desc, + KPE_desc, + V_desc, + acc, + l_i, + m_i, + q, + qpe, + batch_idx, + off_kv_h, + start_m, + qk_scale, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_D: tl.constexpr, + BLOCK_KPE: tl.constexpr, + STAGE: tl.constexpr, + offs_m: tl.constexpr, + offs_n: tl.constexpr, + EVEN_K: tl.constexpr, + N_CTX: tl.constexpr, + warp_specialize: tl.constexpr, +): + # Range of values handled by this stage + if STAGE == 1: + lo, hi = 0, start_m * BLOCK_M # start_m=0,1,2,3 + elif STAGE == 2: + lo, hi = start_m * BLOCK_M, (start_m + 1) * BLOCK_M + lo = tl.multiple_of(lo, BLOCK_M) + # causal = False + else: + lo, hi = 0, N_CTX + cnt = lo // BLOCK_N + # Loop over k, v and update accumulator + for curr_n in range(lo, hi, BLOCK_N, warp_specialize=warp_specialize): + curr_n = tl.multiple_of(curr_n, BLOCK_N) + # Compute qk + # Calculate K offsets - we load BLOCK_N x BLOCK_D and then transpose + k_offset_b = batch_idx * 1 + k_offset_h = off_kv_h * 1 + k_offset_n = cnt * BLOCK_N + k_offset_d = 0 * BLOCK_D + k = K_desc.load([k_offset_b, k_offset_h, k_offset_n, k_offset_d]) + k = tl.reshape(k, (BLOCK_N, BLOCK_D)) + # Transpose K to get (BLOCK_D, BLOCK_N) for matrix multiplication + k = tl.trans(k) + qk = tl.dot(q, k) + + # Calculate KPE offsets + kpe_offset_b = batch_idx * 1 + kpe_offset_h = 0 * 1 + kpe_offset_n = cnt * BLOCK_N + kpe_offset_d = 0 * BLOCK_KPE + # Add position embedding contribution + kpe = KPE_desc.load([kpe_offset_b, kpe_offset_h, kpe_offset_n, kpe_offset_d]) + kpe = tl.reshape(kpe, (BLOCK_N, BLOCK_KPE)) + # Transpose KPE to get (BLOCK_KPE, BLOCK_N) for matrix multiplication + kpe = tl.trans(kpe) + qk = tl.dot(qpe, kpe, qk) + + # Process boundary case(here we only need to process non-causal case) + # Apply mask + if STAGE == 3 and not EVEN_K: + mask = curr_n + offs_n[None, :] < N_CTX + qk = tl.where(mask, qk, -1.0e6) + if STAGE == 2: + mask = offs_m[:, None] >= (curr_n + offs_n[None, :]) + qk = tl.where(mask, qk, -1.0e6) + + m_ij = tl.maximum(m_i, tl.max(qk, 1) * qk_scale) + qk = qk * qk_scale - m_ij[:, None] + + p = tl.math.exp2(qk) + l_ij = tl.sum(p, 1) + alpha = tl.math.exp2(m_i - m_ij) + + # Update m_i and l_i + l_i = l_i * alpha + l_ij + # Scale acc + acc = acc * alpha[:, None] + + # Compute pv + v_offset_b = batch_idx * 1 + v_offset_h = off_kv_h * 1 + v_offset_n = cnt * BLOCK_N + v_offset_d = 0 * BLOCK_D + v = V_desc.load([v_offset_b, v_offset_h, v_offset_n, v_offset_d]) + v = tl.reshape(v, (BLOCK_N, BLOCK_D)) + p = p.to(q.dtype) + acc = tl.dot(p, v, acc) + m_i = m_ij + cnt += 1 + + return acc, l_i, m_i + + +# Autotune Config # {$nv-internal-release} + + +def _host_descriptor_pre_hook(nargs): + BLOCK_M = nargs["BLOCK_M"] + BLOCK_N = nargs["BLOCK_N"] + BLOCK_D = nargs["BLOCK_D"] + BLOCK_KPE = nargs["BLOCK_KPE"] + if not isinstance(nargs["Q"], TensorDescriptor): + return + nargs["Q"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + nargs["QPE"].block_shape = [1, 1, BLOCK_M, BLOCK_KPE] + nargs["K"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["KPE"].block_shape = [1, 1, BLOCK_N, BLOCK_KPE] + nargs["V"].block_shape = [1, 1, BLOCK_N, BLOCK_D] + nargs["Out"].block_shape = [1, 1, BLOCK_M, BLOCK_D] + + +def _get_configs(kernel_type="prefill"): + if _supports_host_descriptor(): + _hook = _host_descriptor_pre_hook + else: + _hook = None + + def keep(conf): + """Filter out invalid configurations for the given kernel type. + + For prefill kernels with causal attention, BLOCK_M must be >= BLOCK_N. + This is because the diagonal block loop (STAGE 2) iterates with step BLOCK_N + over a range of size BLOCK_M. If BLOCK_M < BLOCK_N, the loop executes + 0 iterations, causing incorrect results for all blocks after the first. + + The constraint BLOCK_M % BLOCK_N == 0 ensures BLOCK_M is a multiple of + BLOCK_N (and thus BLOCK_M >= BLOCK_N). + """ + BLOCK_M = conf.kwargs["BLOCK_M"] + BLOCK_N = conf.kwargs["BLOCK_N"] + if kernel_type == "prefill": + return BLOCK_M % BLOCK_N == 0 + else: + return True + + if get_available_triton_backend() == "nvt": + default_config = {"warp_specialize": False} + if torch.cuda.get_device_capability() in [(12, 0), (12, 1)]: + return [triton.Config(dict(BLOCK_M=64, BLOCK_N=64, occupancy=2, **default_config), pre_hook=_hook)] + elif torch.cuda.get_device_capability() == (9, 0): + sm90_configs = [ + # occupancy = 2. We currently do not expose num_warps in CudaTile. + # Once we do (and if we do), we can experiment with occupancy = 1 and num_warps = 8. + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=2, **default_config), pre_hook=_hook) + for BM in [64, 128] + for BN in [64, 128] + ] + return list(filter(keep, sm90_configs)) + elif torch.cuda.get_device_capability()[0] == 8: + # SM80 (A100): smaller tiles reduce register pressure and improve occupancy. + sm80_configs = [ + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=occ, **default_config), pre_hook=_hook) + for BM in [64, 128] + for BN in [64, 128] + for occ in [1, 2] + ] + return list(filter(keep, sm80_configs)) + else: + # SM100 (Blackwell): expand search over BLOCK sizes, occupancy, num_ctas, num_stages. + # Dot-related kernel → 2CTA available; per-Config pre_hook required for TMA. + sm100_configs = [] + # 1CTA configs across block sizes and occupancy + for BM, BN in [(128, 128), (256, 128), (128, 64), (64, 64)]: + for occ in [1, 2]: + for ns in [2, 3, 4]: + sm100_configs.append( + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=occ, **default_config), + num_stages=ns, + pre_hook=_hook, + ) + ) + # 2CTA configs (SM100+ only) - dot-related kernel benefits from 2CTA MMA + for BM, BN in [(256, 128), (128, 128), (256, 64), (256, 256), (128, 64)]: + for ns in [3, 4, 5, 6]: + sm100_configs.append( + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=2, **default_config), + num_stages=ns, + num_ctas=2, + pre_hook=_hook, + ) + ) + # 2CTA configs with warp_specialize=True around the winning shape + for BM, BN in [(256, 128), (128, 128), (256, 256)]: + for ns in [4, 5, 6]: + sm100_configs.append( + triton.Config( + dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=2, warp_specialize=True), + num_stages=ns, + num_ctas=2, + pre_hook=_hook, + ) + ) + return list(filter(keep, sm100_configs)) + + # Full tuning space for oait + configs = [ + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, warp_specialize=ws), num_stages=s, num_warps=w, pre_hook=_hook) + for BM in [64, 128, 256] + for BN in [64, 128] + for s in [2, 3, 4] + for w in [4, 8] + for ws in [True, False] + ] + configs = list(filter(keep, configs)) + return configs + + +# Device Kernels # {$nv-internal-release} + + +@triton.autotune( + configs=_get_configs(), + key=["S_qo", "S_kv", "STAGE", "QUERY_GROUP_SIZE", "dtype"], +) +@triton.heuristics( + { + "EVEN_K": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, + } +) +@triton.jit +def _prefill_mla( + Q, + QPE, + K, + KPE, + V, + Out, + L, + sm_scale, + stride_qb, + stride_qh, + stride_qm, + stride_qpeb, + stride_qpeh, + stride_qpem, + stride_kb, + stride_kh, + stride_kn, + stride_kpeb, + stride_kpeh, + stride_kpen, + stride_vb, + stride_vh, + stride_vk, + stride_ob, + stride_oh, + stride_om, + B, + H, + S_qo, + S_kv, + BLOCK_D: tl.constexpr, # BLOCK_D = hidden_size + BLOCK_KPE: tl.constexpr, # BLOCK_KPE = position embedding size + STAGE: tl.constexpr, + QUERY_GROUP_SIZE: tl.constexpr, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + EVEN_K: tl.constexpr, + dtype: tl.constexpr, + warp_specialize: tl.constexpr, +): + pid_x = tl.program_id(0) + pid_y = tl.program_id(1) + batch_idx = pid_y // H + head_idx = pid_y % H + if QUERY_GROUP_SIZE: + off_kv_h = head_idx // QUERY_GROUP_SIZE + else: + off_kv_h = head_idx + qk_scale = sm_scale * INV_LOG_2 + + # Initialize offsets + offs_m = pid_x * BLOCK_M + tl.arange(0, BLOCK_M) + offs_n = tl.arange(0, BLOCK_N) + + # Create tensor descriptors + Q_desc = _maybe_make_tensor_desc( + Q, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_qb, stride_qh, stride_qm, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + + # For K, we need to load BLOCK_N x BLOCK_D but then transpose to BLOCK_D x BLOCK_N + K_desc = _maybe_make_tensor_desc( + K, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_kb, stride_kh, stride_kn, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + + QPE_desc = _maybe_make_tensor_desc( + QPE, + shape=[B, H, S_qo, BLOCK_KPE], + strides=[stride_qpeb, stride_qpeh, stride_qpem, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_KPE], + ) + + # For KPE, we need to load BLOCK_N x BLOCK_KPE but then transpose to BLOCK_KPE x BLOCK_N + KPE_desc = _maybe_make_tensor_desc( + KPE, + shape=[B, 1, S_qo, BLOCK_KPE], + strides=[stride_kpeb, stride_kpeh, stride_kpen, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_KPE], + ) + + V_desc = _maybe_make_tensor_desc( + V, + shape=[B, H, S_kv, BLOCK_D], + strides=[stride_vb, stride_vh, stride_vk, 1], + block_shape=[1, 1, BLOCK_N, BLOCK_D], + ) + + O_desc = _maybe_make_tensor_desc( + Out, + shape=[B, H, S_qo, BLOCK_D], + strides=[stride_ob, stride_oh, stride_om, 1], + block_shape=[1, 1, BLOCK_M, BLOCK_D], + ) + + L_desc = _maybe_make_tensor_desc( + L, + shape=[B, H, S_qo], + strides=[1, 1, 1], + block_shape=[1, 1, BLOCK_M], + ) + + # Initialize m, l, acc + m_i = tl.full([BLOCK_M], -float("inf"), dtype=tl.float32) + l_i = tl.full([BLOCK_M], 1.0, dtype=tl.float32) + acc = tl.zeros([BLOCK_M, BLOCK_D], dtype=tl.float32) + + # Load q + q_offset_b = batch_idx * 1 + q_offset_h = head_idx * 1 + q_offset_m = pid_x * BLOCK_M + q_offset_d = 0 * BLOCK_D + q = Q_desc.load([q_offset_b, q_offset_h, q_offset_m, q_offset_d]) + q = tl.reshape(q, (BLOCK_M, BLOCK_D)) + + # Load qpe + qpe_offset_b = batch_idx * 1 + qpe_offset_h = head_idx * 1 + qpe_offset_m = pid_x * BLOCK_M + qpe_offset_d = 0 * BLOCK_KPE + qpe = QPE_desc.load([qpe_offset_b, qpe_offset_h, qpe_offset_m, qpe_offset_d]) + qpe = tl.reshape(qpe, (BLOCK_M, BLOCK_KPE)) + + # For causal = False, STAGE = 1, and _attn_fwd_inner gets 3 as its STAGE + if STAGE & 1: + acc, l_i, m_i = _attn_fwd_inner( + K_desc, + KPE_desc, + V_desc, + acc, + l_i, + m_i, + q, # + qpe, + batch_idx, + off_kv_h, + pid_x, + qk_scale, # + BLOCK_M, + BLOCK_N, + BLOCK_D, # + BLOCK_KPE, + 4 - STAGE, + offs_m, + offs_n, + EVEN_K, + S_kv, + warp_specialize, + ) + # Stage 2: on-band + if STAGE & 2: + # Barrier makes it easier for compielr to schedule the + # Two loops independently + acc, l_i, m_i = _attn_fwd_inner( + K_desc, + KPE_desc, + V_desc, + acc, + l_i, + m_i, + q, + qpe, + batch_idx, + off_kv_h, + pid_x, + qk_scale, + BLOCK_M, + BLOCK_N, + BLOCK_D, + BLOCK_KPE, + 2, + offs_m, + offs_n, + EVEN_K, + S_kv, + warp_specialize, + ) + # Epilogue + acc = acc / (l_i[:, None]) + l_i = m_i + tl.math.log2(l_i) + + # Write back l and o - calculate explicit offsets + l_offset_b = batch_idx * 1 + l_offset_h = head_idx * 1 + l_offset_m = pid_x * BLOCK_M + L_desc.store([l_offset_b, l_offset_h, l_offset_m], l_i.reshape(1, 1, BLOCK_M)) + + o_offset_b = batch_idx * 1 + o_offset_h = head_idx * 1 + o_offset_m = pid_x * BLOCK_M + o_offset_d = 0 * BLOCK_D + O_desc.store( + [o_offset_b, o_offset_h, o_offset_m, o_offset_d], + acc.to(dtype).reshape(1, 1, BLOCK_M, BLOCK_D), + ) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _attention(torch.autograd.Function): + @staticmethod + def forward(ctx, q, qpe, k, kpe, v, sm_scale, IS_CAUSAL): + # Setup stride and shape + B, H, S_qo, BLOCK_D = q.shape + BLOCK_KPE = kpe.shape[3] + assert k.shape == v.shape + num_head_kv = k.shape[1] + S_kv = k.shape[2] + o = torch.empty_like(q) + l = torch.empty((B, H, S_qo), device=q.device, dtype=torch.float32) + + if H == num_head_kv: + query_group_size = 0 + else: + assert H % num_head_kv == 0 + query_group_size = int(H / num_head_kv) + # Launch fmha fwd kernel + grid = lambda args: (triton.cdiv(S_qo, args["BLOCK_M"]), B * H, 1) + + # TMA descriptors require a global memory allocation + def alloc_fn(size: int, alignment: int, stream: Optional[int]): + return torch.empty(size, device="cuda", dtype=torch.int8) + + triton.set_allocator(alloc_fn) + if _supports_host_descriptor(): + dummy_block_qo = [1, 1, 1, BLOCK_D] + dummy_block_kv = [1, 1, 1, BLOCK_D] + dummy_block_qpe = [1, 1, 1, BLOCK_KPE] + dummy_block_kpe = [1, 1, 1, BLOCK_KPE] + desc_q = TensorDescriptor( + q, + shape=[B, H, S_qo, BLOCK_D], + strides=[q.stride(0), q.stride(1), q.stride(2), 1], + block_shape=dummy_block_qo, + ) + desc_qpe = TensorDescriptor( + qpe, + shape=[B, H, S_qo, BLOCK_KPE], + strides=[qpe.stride(0), qpe.stride(1), qpe.stride(2), 1], + block_shape=dummy_block_qpe, + ) + desc_k = TensorDescriptor( + k, + shape=[B, H, S_kv, BLOCK_D], + strides=[k.stride(0), k.stride(1), k.stride(2), 1], + block_shape=dummy_block_kv, + ) + desc_kpe = TensorDescriptor( + kpe, + shape=[B, 1, S_qo, BLOCK_KPE], + strides=[kpe.stride(0), kpe.stride(1), kpe.stride(2), 1], + block_shape=dummy_block_kpe, + ) + desc_v = TensorDescriptor( + v, + shape=[B, H, S_kv, BLOCK_D], + strides=[v.stride(0), v.stride(1), v.stride(2), 1], + block_shape=dummy_block_kv, + ) + desc_o = TensorDescriptor( + o, + shape=[B, H, S_qo, BLOCK_D], + strides=[o.stride(0), o.stride(1), o.stride(2), 1], + block_shape=dummy_block_qo, + ) + else: + desc_q = q + desc_qpe = qpe + desc_k = k + desc_kpe = kpe + desc_v = v + desc_o = o + + def torch_to_triton_type(dtype): + type_map = { + torch.float16: tl.float16, + torch.float8_e5m2: tl.float8e5, + torch.float32: tl.float32, + torch.int32: tl.int32, + torch.bfloat16: tl.bfloat16, + } + return type_map.get(dtype, tl.float16) + + dtype = torch_to_triton_type(q.dtype) + # disable autotune when run test_op in test_mla.py + if is_autotune_disabled(): + _prefill_mla.configs = [_prefill_mla.configs[0]] + _prefill_mla[grid]( + desc_q, + desc_qpe, + desc_k, + desc_kpe, + desc_v, + desc_o, + l, + sm_scale, + q.stride(0), + q.stride(1), + q.stride(2), + qpe.stride(0), + qpe.stride(1), + qpe.stride(2), + k.stride(0), + k.stride(1), + k.stride(2), + kpe.stride(0), + kpe.stride(1), + kpe.stride(2), + v.stride(0), + v.stride(1), + v.stride(2), + o.stride(0), + o.stride(1), + o.stride(2), + B, + H, + S_qo, + S_kv, + BLOCK_D=BLOCK_D, + BLOCK_KPE=BLOCK_KPE, + STAGE=3 if IS_CAUSAL else 1, + QUERY_GROUP_SIZE=query_group_size, + dtype=dtype, + ) + ctx.save_for_backward(q, k, v, o, l) + ctx.sm_scale = sm_scale + ctx.shapes = (B, H, S_qo, S_kv) + return o + + @staticmethod + def backward(ctx, do): + raise NotImplementedError() + + +class _Attention: + def __init__(self, IS_CAUSAL): + self.IS_CAUSAL = IS_CAUSAL + + def __call__(self, q, k, v, sm_scale, qpe=None, kpe=None): + c = _attention.apply( + q, + qpe, + k, + kpe, + v, + sm_scale, + self.IS_CAUSAL, + ) + return c + + +@register_impl("mla", backend="triton") +def triton_mla(q, k, v, qpe, kpe, is_causal, scaling, **kwargs): + if scaling is None: + scaling = 1.0 / math.sqrt(q.size(-1) + qpe.size(-1)) + + attention = _Attention(is_causal) + o = attention(q, k, v, scaling, qpe, kpe) + return o + + +# {$nv-internal-release begin} + +# Backend Registration & Perf Markers # {$nv-internal-release} + +mark_perf_ready("mla", "nvt") + +# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py b/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py new file mode 100644 index 0000000000..ed582bc652 --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py @@ -0,0 +1,458 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import math +from typing import Optional + +import torch +import triton +import triton.language as tl + +from tilegym.backend import get_available_triton_backend +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl + +# Constants & Type Aliases # {$nv-internal-release} + +INV_LOG_2 = tl.constexpr(1.0 / math.log(2)) + +# Autotune Config # {$nv-internal-release} + + +def _get_mla_decoding_configs(): + """Get autotune configs based on GPU architecture and backend""" + base_configs = [ + triton.Config(dict(BLOCK_H=64, BLOCK_N=64), num_ctas=1, num_stages=1), + triton.Config(dict(BLOCK_H=64, BLOCK_N=128), num_ctas=1, num_stages=1), + ] + + # Add Hopper+ specific config (SM90 and later) + if torch.cuda.is_available() and torch.cuda.get_device_capability()[0] >= 9: + base_configs.append(triton.Config(dict(BLOCK_H=128, BLOCK_N=128), num_ctas=2, num_stages=1)) + + return base_configs + + +# Device Kernels # {$nv-internal-release} + + +@triton.autotune( + configs=_get_mla_decoding_configs() + if get_available_triton_backend() == "nvt" + else [ + # OAIT meet CUDA error for BLOCK_H = 64, BLOCK_N = 64 + # use BLOCK_H = 32, BLOCK_N = 32 instead + triton.Config(dict(BLOCK_H=32, BLOCK_N=32), num_ctas=1), + ], + key=["BLOCK_D", "BLOCK_KPE", "S_kv", "EVEN_N"], +) +@triton.heuristics( + { + "EVEN_N": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, + } +) +@triton.jit +def _naive_absorb_mla( + Q, + QPE, + KV, + KPE, + Out, + L, + sm_scale, + stride_qb, + stride_qm, + stride_qpeb, + stride_qpem, + stride_kvb, + stride_kvn, + stride_kpeb, + stride_kpem, + stride_ob, + stride_om, + B, + num_head, + S_kv, + BLOCK_D: tl.constexpr, + BLOCK_H: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_KPE: tl.constexpr, + EVEN_N: tl.constexpr, +): + pid_x = tl.program_id(0) + pid_y = tl.program_id(1) + batch_idx = pid_y + qk_scale = sm_scale * INV_LOG_2 + + # Create tensor descriptors + Q_desc = tl.make_tensor_descriptor( + base=Q, + shape=[B, num_head, BLOCK_D], + strides=[stride_qb, stride_qm, 1], + block_shape=[1, BLOCK_H, BLOCK_D], + ) + QPE_desc = tl.make_tensor_descriptor( + base=QPE, + shape=[B, num_head, BLOCK_KPE], + strides=[stride_qpeb, stride_qpem, 1], + block_shape=[1, BLOCK_H, BLOCK_KPE], + ) + K_desc = tl.make_tensor_descriptor( + base=KV, + shape=[B, S_kv, BLOCK_D], + strides=[stride_kvb, stride_kvn, 1], + block_shape=[1, BLOCK_N, BLOCK_D], + ) + KPE_desc = tl.make_tensor_descriptor( + base=KPE, + shape=[B, S_kv, BLOCK_KPE], + strides=[stride_kpeb, stride_kpem, 1], + block_shape=[1, BLOCK_N, BLOCK_KPE], + ) + V_desc = tl.make_tensor_descriptor( + base=KV, + shape=[B, S_kv, BLOCK_D], + strides=[stride_kvb, stride_kvn, 1], + block_shape=[1, BLOCK_N, BLOCK_D], + ) + O_desc = tl.make_tensor_descriptor( + base=Out, + shape=[B, num_head, BLOCK_D], + strides=[stride_ob, stride_om, 1], + block_shape=[1, BLOCK_H, BLOCK_D], + ) + L_desc = tl.make_tensor_descriptor( + base=L, + shape=[B, num_head], + strides=[num_head, 1], + block_shape=[1, BLOCK_H], + ) + + # Initialize accumulation variables + m_prev = tl.full([BLOCK_H], -float("inf"), dtype=tl.float32) + l_prev = tl.full([BLOCK_H], 1.0, dtype=tl.float32) + acc = tl.zeros([BLOCK_H, BLOCK_D], dtype=tl.float32) + + # Load q + q = Q_desc.load([batch_idx, pid_x * BLOCK_H, 0]) + q = tl.reshape(q, (BLOCK_H, BLOCK_D)) + qpe = QPE_desc.load([batch_idx, pid_x * BLOCK_H, 0]) + qpe = tl.reshape(qpe, (BLOCK_H, BLOCK_KPE)) + + # Loop over key-value pairs and update accumulator + end_n = S_kv + cnt = 0 + offs_n = tl.arange(0, BLOCK_N) + for curr_n in range(0, end_n, BLOCK_N): + # Compute qk + k = K_desc.load([batch_idx, cnt * BLOCK_N, 0]) + k = tl.reshape(k, (BLOCK_N, BLOCK_D)) + k = tl.trans(k, (1, 0)) + qk = tl.dot(q, k) + kpe = KPE_desc.load([batch_idx, cnt * BLOCK_N, 0]) + kpe = tl.reshape(kpe, (BLOCK_N, BLOCK_KPE)) + kpe = tl.trans(kpe, (1, 0)) + qk = tl.dot(qpe, kpe, qk) + # Apply mask to avoid out of bounds + if not EVEN_N: + mask = (curr_n + offs_n[None, :]) < S_kv + qk = tl.where(mask, qk, -1.0e6) + qk = qk.to(tl.float32) + m_ij = tl.maximum(m_prev, tl.max(qk, 1) * qk_scale) + qk = qk * qk_scale - m_ij[:, None] + # Attention weights + p = tl.math.exp2(qk) + l_curr = tl.sum(p, 1) + alpha = tl.math.exp2(m_prev - m_ij) + # Update m_prev and l_prev + l_prev = l_prev * alpha + l_curr + # Scale acc + acc = acc * alpha[:, None] + # Compute pv + v = V_desc.load([batch_idx, cnt * BLOCK_N, 0]) + v = tl.reshape(v, (BLOCK_N, BLOCK_D)) + p = p.to(Q.dtype.element_ty) + acc = tl.dot(p, v, acc) + m_prev = m_ij + cnt += 1 + + acc = acc / (l_prev[:, None]) + l_prev = m_prev + tl.math.log2(l_prev) + + # Store results + L_desc.store([batch_idx, pid_x * BLOCK_H], l_prev.reshape(1, BLOCK_H)) + + acc = acc.to(Out.dtype.element_ty) + O_desc.store([batch_idx, pid_x * BLOCK_H, 0], acc.reshape(1, BLOCK_H, BLOCK_D)) + + +@triton.autotune( + configs=[ + triton.Config(dict(BLOCK_H=16, BLOCK_N=128), num_ctas=1), + triton.Config(dict(BLOCK_H=32, BLOCK_N=128), num_ctas=1), + ] + if get_available_triton_backend() == "nvt" + else [ + # OAIT meet CUDA error when num_stages = 3 + # use num_stages = 1 instead + triton.Config(dict(BLOCK_H=16, BLOCK_N=128), num_ctas=1, num_stages=1), + triton.Config(dict(BLOCK_H=32, BLOCK_N=32), num_ctas=1), + ], + key=["BLOCK_D", "BLOCK_KPE", "S_kv", "EVEN_N"], +) +@triton.heuristics( + { + "EVEN_N": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, + } +) +@triton.jit +def _naive_absorb_mla_transpose( + Q, + QPE, + KV, + KPE, + Out, + L, + sm_scale, + stride_qb, + stride_qm, + stride_qpeb, + stride_qpem, + stride_kvb, + stride_kvn, + stride_kpeb, + stride_kpem, + stride_ob, + stride_om, + B, + num_head, + S_kv, + BLOCK_D: tl.constexpr, # BLOCK_D = hidden_size + BLOCK_H: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_KPE: tl.constexpr, # BLOCK_KPE = position embedding size + EVEN_N: tl.constexpr, +): + pid_x = tl.program_id(0) + pid_y = tl.program_id(1) + batch_idx = pid_y + qk_scale = sm_scale * INV_LOG_2 + + # Create tensor descriptors + Q_desc = tl.make_tensor_descriptor( + base=Q, + shape=[B, num_head, BLOCK_D], + strides=[stride_qb, stride_qm, 1], + block_shape=[1, BLOCK_H, BLOCK_D], + ) + QPE_desc = tl.make_tensor_descriptor( + base=QPE, + shape=[B, num_head, BLOCK_KPE], + strides=[stride_qpeb, stride_qpem, 1], + block_shape=[1, BLOCK_H, BLOCK_KPE], + ) + K_desc = tl.make_tensor_descriptor( + base=KV, + shape=[B, S_kv, BLOCK_D], + strides=[stride_kvb, stride_kvn, 1], + block_shape=[1, BLOCK_N, BLOCK_D], + ) + KPE_desc = tl.make_tensor_descriptor( + base=KPE, + shape=[B, S_kv, BLOCK_KPE], + strides=[stride_kpeb, stride_kpem, 1], + block_shape=[1, BLOCK_N, BLOCK_KPE], + ) + + # TMA descriptor for V must be different from K, + # to avoid its load being optimized out. + V_desc = tl.make_tensor_descriptor( + base=KV, + shape=[S_kv, B, BLOCK_D], + strides=[stride_kvn, stride_kvb, 1], + block_shape=[BLOCK_N, 1, BLOCK_D], + ) + O_desc = tl.make_tensor_descriptor( + base=Out, + shape=[B, num_head, BLOCK_D], + strides=[stride_ob, stride_om, 1], + block_shape=[1, BLOCK_H, BLOCK_D], + ) + L_desc = tl.make_tensor_descriptor( + base=L, + shape=[B, num_head], + strides=[num_head, 1], + block_shape=[1, BLOCK_H], + ) + + # Initialize accumulation variables + m_prev = tl.full([BLOCK_H], -float("inf"), dtype=tl.float32) + l_prev = tl.full([BLOCK_N, BLOCK_H], 1.0, dtype=tl.float32) + acc = tl.zeros([BLOCK_D, BLOCK_H], dtype=tl.float32) + + # Load query and query position encoding + q = Q_desc.load([batch_idx, pid_x * BLOCK_H, 0]) + q = tl.reshape(q, (BLOCK_H, BLOCK_D)) + q = tl.trans(q, (1, 0)) + qpe = QPE_desc.load([batch_idx, pid_x * BLOCK_H, 0]) + qpe = tl.reshape(qpe, (BLOCK_H, BLOCK_KPE)) + qpe = tl.trans(qpe, (1, 0)) + + # Loop over key-value pairs and update accumulator + end_n = S_kv + cnt = 0 + mask_start = S_kv // BLOCK_N * BLOCK_N + offs_n = tl.arange(0, BLOCK_N) + for curr_n in range(0, end_n, BLOCK_N): + # Load key and compute Q@K^T + k = K_desc.load([batch_idx, cnt * BLOCK_N, 0]) + k = tl.reshape(k, (BLOCK_N, BLOCK_D)) + qk = tl.dot(k, q) + + # Load key position encoding and compute QPE@KPE^T + kpe = KPE_desc.load([batch_idx, cnt * BLOCK_N, 0]) + kpe = tl.reshape(kpe, (BLOCK_N, BLOCK_KPE)) + qk = tl.dot(kpe, qpe, qk) + + if curr_n >= mask_start: + mask = (curr_n + offs_n) < S_kv + qk = tl.where(mask[:, None], qk, -1.0e6) + + # Apply scaling and compute attention scores + qk = qk.to(tl.float32) + m_ij = tl.maximum(m_prev, tl.max(qk, 0) * qk_scale) + qk = qk * qk_scale - m_ij[None, :] + + # Compute attention weights and update running statistics + p = tl.math.exp2(qk) + alpha = tl.math.exp2(m_prev - m_ij) + l_prev = l_prev * alpha[None, :] + p + acc = acc * alpha[None, :] + + # Load value and compute attention @ value + v = V_desc.load([cnt * BLOCK_N, batch_idx, 0]) + v = tl.reshape(v, (BLOCK_N, BLOCK_D)) + v = tl.trans(v, (1, 0)) + p = p.to(Q.dtype.element_ty) + acc = tl.dot(v, p, acc) + m_prev = m_ij + cnt += 1 + + # Finalize attention computation + l_prev = tl.sum(l_prev, 0) + acc = acc / (l_prev[None, :]) + l_prev = m_prev + tl.math.log2(l_prev) + + # Store results + L_desc.store([batch_idx, pid_x * BLOCK_H], l_prev.reshape(1, BLOCK_H)) + + acc = acc.to(Out.dtype.element_ty) + acc = tl.trans(acc, (1, 0)) + O_desc.store([batch_idx, pid_x * BLOCK_H, 0], acc.reshape(1, BLOCK_H, BLOCK_D)) + + +# Host Launchers & Public API # {$nv-internal-release} + + +class _mla_decoding(torch.autograd.Function): + @staticmethod + def forward( + ctx, + q, + qpe, + kv, + kpe, + sm_scale, + transpose, + ): + # TMA descriptors require a global memory allocation + def alloc_fn(size: int, alignment: int, stream: Optional[int]): + return torch.empty(size, device="cuda", dtype=torch.int8) + + triton.set_allocator(alloc_fn) + + # Setup stride and shape + B, num_head, BLOCK_D = q.shape + BLOCK_KPE = kpe.shape[2] + S_kv = kv.shape[1] + o = torch.empty_like(q) + l = torch.empty((B, num_head), device=q.device, dtype=torch.float32).contiguous() + + # Launch fmha fwd kernel + grid = lambda META: (triton.cdiv(num_head, META["BLOCK_H"]), B, 1) + if not transpose: + kernel = _naive_absorb_mla + else: + kernel = _naive_absorb_mla_transpose + + kernel[grid]( + q, + qpe, + kv, + kpe, + o, + l, + sm_scale, + q.stride(0), + q.stride(1), + qpe.stride(0), + qpe.stride(1), + kv.stride(0), + kv.stride(1), + kpe.stride(0), + kpe.stride(1), + o.stride(0), + o.stride(1), + B, + num_head, + S_kv, + BLOCK_D=BLOCK_D, + BLOCK_KPE=BLOCK_KPE, + ) + return o, l + + @staticmethod + def backward(ctx, do): + raise NotImplementedError() + + +class MLADecoding: + def __init__(self, transpose): + self.transpose = transpose + + def __call__(self, q, qpe, kv, kpe, sm_scale): + if sm_scale is None: + sm_scale = 1.0 / (math.sqrt(q.size(-1) + qpe.size(-1))) + o, l = _mla_decoding.apply( + q, + qpe, + kv, + kpe, + sm_scale, + self.transpose, + ) + return o, l + + +@register_impl("mla_decoding", backend="triton") +def mla_decoding( + q: torch.Tensor, + qpe: torch.Tensor, + kv: torch.Tensor, + kpe: torch.Tensor, + sm_scale: float, + transpose: bool = True, + **kwargs, +) -> torch.Tensor: + return _mla_decoding.apply(q, qpe, kv, kpe, sm_scale, transpose) + + +# {$nv-internal-release begin} + +# Backend Registration & Perf Markers # {$nv-internal-release} + +mark_perf_ready("mla_decoding", "nvt") + +# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/rope.py b/python/tutorials/tileir/tilegym/ops/triton/rope.py new file mode 100644 index 0000000000..cd998aad8d --- /dev/null +++ b/python/tutorials/tileir/tilegym/ops/triton/rope.py @@ -0,0 +1,667 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: MIT + +# Imports # {$nv-internal-release} +import torch +import triton +import triton.language as tl +from triton.tools.tensor_descriptor import TensorDescriptor + +from tilegym.backend import get_current_backend +from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import register_impl + +# Capability Probe # {$nv-internal-release} + + +def _supports_host_descriptor(): + return torch.cuda.get_device_capability()[0] >= 9 + + +# Kernel Helpers # {$nv-internal-release} + + +@triton.jit +def _maybe_make_tensor_desc(desc_or_ptr, shape, strides, block_shape): + if isinstance(desc_or_ptr, tl.tensor_descriptor): + return desc_or_ptr + else: + return tl.make_tensor_descriptor(desc_or_ptr, shape, strides, block_shape) + + +# Device Kernels # {$nv-internal-release} + + +@triton.jit +def _triton_rope_kernel_tma( + Q, + K, + cos, + sin, + # Q tensor parameters + Q_shape_0, + Q_shape_1, + Q_shape_2, + Q_shape_3, + Q_shape_4, + Q_stride_0, + Q_stride_1, + Q_stride_2, + Q_stride_3, + Q_stride_4, + # K tensor parameters + K_shape_0, + K_shape_1, + K_shape_2, + K_shape_3, + K_shape_4, + K_stride_0, + K_stride_1, + K_stride_2, + K_stride_3, + K_stride_4, + # cos tensor parameters + cos_shape_0, + cos_shape_1, + cos_shape_2, + cos_shape_3, + cos_stride_0, + cos_stride_1, + cos_stride_2, + cos_stride_3, + # sin tensor parameters + sin_shape_0, + sin_shape_1, + sin_shape_2, + sin_shape_3, + sin_stride_0, + sin_stride_1, + sin_stride_2, + sin_stride_3, + cos_bs: tl.constexpr, + seq_len: tl.constexpr, + BLOCK_QH: tl.constexpr, + BLOCK_KH: tl.constexpr, + BLOCK_HD: tl.constexpr, +): + # Create tensor descriptors using _maybe_make_tensor_desc + Q_desc = _maybe_make_tensor_desc( + Q, + shape=[Q_shape_0, Q_shape_1, Q_shape_2, Q_shape_3, Q_shape_4], + strides=[Q_stride_0, Q_stride_1, Q_stride_2, Q_stride_3, Q_stride_4], + block_shape=[1, BLOCK_QH, 1, 1, BLOCK_HD], + ) + + K_desc = _maybe_make_tensor_desc( + K, + shape=[K_shape_0, K_shape_1, K_shape_2, K_shape_3, K_shape_4], + strides=[K_stride_0, K_stride_1, K_stride_2, K_stride_3, K_stride_4], + block_shape=[1, BLOCK_KH, 1, 1, BLOCK_HD], + ) + + cos_desc = _maybe_make_tensor_desc( + cos, + shape=[cos_shape_0, cos_shape_1, cos_shape_2, cos_shape_3], + strides=[cos_stride_0, cos_stride_1, cos_stride_2, cos_stride_3], + block_shape=[1, 1, 1, BLOCK_HD], + ) + + sin_desc = _maybe_make_tensor_desc( + sin, + shape=[sin_shape_0, sin_shape_1, sin_shape_2, sin_shape_3], + strides=[sin_stride_0, sin_stride_1, sin_stride_2, sin_stride_3], + block_shape=[1, 1, 1, BLOCK_HD], + ) + + dtype = Q_desc.type.block_type.element_ty + pid = tl.program_id(0) + batch_idx = pid // seq_len + row_idx = pid % seq_len + cos_batch_idx = 0 if cos_bs == 1 else batch_idx + cos_row = cos_desc.load([cos_batch_idx, row_idx, 0, 0]).reshape((1, BLOCK_HD)) + sin_row = sin_desc.load([cos_batch_idx, row_idx, 0, 0]).reshape((1, BLOCK_HD)) + q_tile_1 = Q_desc.load([batch_idx, 0, row_idx, 0, 0]).reshape((BLOCK_QH, BLOCK_HD)) + q_tile_2 = Q_desc.load([batch_idx, 0, row_idx, 1, 0]).reshape((BLOCK_QH, BLOCK_HD)) + new_q_tile_1 = q_tile_1 * cos_row - q_tile_2 * sin_row + new_q_tile_2 = q_tile_2 * cos_row + q_tile_1 * sin_row + Q_desc.store( + [batch_idx, 0, row_idx, 0, 0], + new_q_tile_1.reshape((1, BLOCK_QH, 1, 1, BLOCK_HD)).to(dtype), + ) + Q_desc.store( + [batch_idx, 0, row_idx, 1, 0], + new_q_tile_2.reshape((1, BLOCK_QH, 1, 1, BLOCK_HD)).to(dtype), + ) + k_tile_1 = K_desc.load([batch_idx, 0, row_idx, 0, 0]).reshape((BLOCK_KH, BLOCK_HD)) + k_tile_2 = K_desc.load([batch_idx, 0, row_idx, 1, 0]).reshape((BLOCK_KH, BLOCK_HD)) + new_k_tile_1 = k_tile_1 * cos_row - k_tile_2 * sin_row + new_k_tile_2 = k_tile_2 * cos_row + k_tile_1 * sin_row + K_desc.store( + [batch_idx, 0, row_idx, 0, 0], + new_k_tile_1.reshape((1, BLOCK_KH, 1, 1, BLOCK_HD)).to(dtype), + ) + K_desc.store( + [batch_idx, 0, row_idx, 1, 0], + new_k_tile_2.reshape((1, BLOCK_KH, 1, 1, BLOCK_HD)).to(dtype), + ) + + +# TODO: support different layout # {$nv-TODO} +# Adapted from Liger-Kernel: https://github.com/linkedin/Liger-Kernel/blob/main/src/liger_kernel/ops/rope.py + + +@triton.jit +def _rope_kernel( + q, + q_row_stride, + k, + k_row_stride, + cos, + cos_row_stride, + sin, + sin_row_stride, + sl, + bs: tl.constexpr, + cos_bs: tl.constexpr, + n_qh: tl.constexpr, + n_kh: tl.constexpr, + hd: tl.constexpr, + pad_n_qh: tl.constexpr, + pad_n_kh: tl.constexpr, + pad_hd: tl.constexpr, + BACKWARD_PASS: tl.constexpr = False, + rope_hd: tl.constexpr = 0, + pad_rope_hd: tl.constexpr = 0, +): + # q size: (bsz, seq_len, num_q_heads, head_dim) + # q stride: (seq_len * num_q_heads * head_dim, num_q_heads * head_dim, head_dim, 1) + # k size: (bsz, seq_len, num_kv_heads, head_dim) + # k stride: (seq_len * num_kv_heads * head_dim, num_kv_heads * head_dim, head_dim, 1) + # cos size: (1, seq_len, rope_hd) or (bsz, seq_len, rope_hd) — rope_hd == hd for full RoPE + # stride: (seq_len * rope_hd, rope_hd, 1) + + # When rope_hd == 0, fall back to full-dim RoPE (rope_hd == hd) + effective_rope_hd: tl.constexpr = hd if rope_hd == 0 else rope_hd + effective_pad_rope_hd: tl.constexpr = pad_hd if pad_rope_hd == 0 else pad_rope_hd + + # Cast to int64 to prevent int32 overflow in pointer offset arithmetic. + # For large configs (e.g. bsz=8, seq_len=65536, n_qh=32, hd=256), + # pid * q_row_stride exceeds INT32_MAX and wraps to a negative offset. + pid = tl.program_id(0).to(tl.int64) + + # Locate start address + q = q + pid * q_row_stride + k = k + pid * k_row_stride + + # #################################################################### + # Get the cos(mθ_{i...d/2}) and sin(mθ_{i...d/2}) for token position + # m of this program instance + # #################################################################### + + # Program instances are laid out in a 1D vector of size bsz * seq_len, which + # effectively represents a 2D grid of size [bsz, seq_len] with seq_len dimension + # being the fastest changing dimension. Thus we can simply do pid // sl to get the batch index + # and pid % sl to get the sequence index. + batch_idx = pid // sl + cos_row_idx = pid % sl + cos = cos + tl.where( + cos_bs == 1, + cos_row_idx * cos_row_stride, + batch_idx * (sl * cos_row_stride) + cos_row_idx * cos_row_stride, + ) + sin = sin + tl.where( + cos_bs == 1, + cos_row_idx * sin_row_stride, + batch_idx * (sl * sin_row_stride) + cos_row_idx * sin_row_stride, + ) + + cos_offsets = tl.arange(0, effective_pad_rope_hd // 2) + cos_mask = cos_offsets < effective_rope_hd // 2 + cos_row = tl.load(cos + cos_offsets, mask=cos_mask, other=0).to(tl.float32) + sin_row = tl.load(sin + cos_offsets, mask=cos_mask, other=0).to(tl.float32) + + # #################################################################### + # Process Q and K tensors: load the left and right half of the rotary + # portion for the current token. When rope_hd < hd (partial RoPE), + # only the first rope_hd dims are touched; the rest pass through. + # #################################################################### + # Left half of the rotary portion [0 : rope_hd//2] + first_half_q_offsets = tl.arange(0, pad_n_qh)[:, None] * hd + tl.arange(0, effective_pad_rope_hd // 2)[None, :] + first_half_k_offsets = tl.arange(0, pad_n_kh)[:, None] * hd + tl.arange(0, effective_pad_rope_hd // 2)[None, :] + first_q_mask = (tl.arange(0, pad_n_qh)[:, None] < n_qh) & ( + tl.arange(0, effective_pad_rope_hd // 2)[None, :] < effective_rope_hd // 2 + ) + first_k_mask = (tl.arange(0, pad_n_kh)[:, None] < n_kh) & ( + tl.arange(0, effective_pad_rope_hd // 2)[None, :] < effective_rope_hd // 2 + ) + q_tile_1 = tl.load(q + first_half_q_offsets, mask=first_q_mask, other=0).to(tl.float32) + k_tile_1 = tl.load(k + first_half_k_offsets, mask=first_k_mask, other=0).to(tl.float32) + + # Right half of the rotary portion [rope_hd//2 : rope_hd] + second_half_q_offsets = first_half_q_offsets + (effective_rope_hd // 2) + second_half_k_offsets = first_half_k_offsets + (effective_rope_hd // 2) + second_q_mask = first_q_mask + second_k_mask = first_k_mask + q_tile_2 = tl.load(q + second_half_q_offsets, mask=second_q_mask, other=0).to(tl.float32) + k_tile_2 = tl.load(k + second_half_k_offsets, mask=second_k_mask, other=0).to(tl.float32) + + if not BACKWARD_PASS: + # y = [x1, x2] * [cos, cos] + [-x2, x1] * [sin, sin] + # Process Q tensor + new_q_tile_1 = q_tile_1 * cos_row - q_tile_2 * sin_row + new_q_tile_2 = q_tile_2 * cos_row + q_tile_1 * sin_row + tl.store( + q + first_half_q_offsets, + new_q_tile_1.to(sin_row.dtype), + mask=first_q_mask, + ) + tl.store( + q + second_half_q_offsets, + new_q_tile_2.to(sin_row.dtype), + mask=second_q_mask, + ) + + # Process K tensor + new_k_tile_1 = k_tile_1 * cos_row - k_tile_2 * sin_row + new_k_tile_2 = k_tile_2 * cos_row + k_tile_1 * sin_row + tl.store( + k + first_half_k_offsets, + new_k_tile_1.to(sin_row.dtype), + mask=first_k_mask, + ) + tl.store( + k + second_half_k_offsets, + new_k_tile_2.to(sin_row.dtype), + mask=second_k_mask, + ) + else: + # with some math, we can get: + # dy = [dx1, dx2] * [cos, cos] + [-dx2, dx1] * [-sin, -sin] + # Process Q tensor + new_q_tile_1 = q_tile_1 * cos_row + q_tile_2 * sin_row + new_q_tile_2 = q_tile_2 * cos_row - q_tile_1 * sin_row + tl.store( + q + first_half_q_offsets, + new_q_tile_1.to(sin_row.dtype), + mask=first_q_mask, + ) + tl.store( + q + second_half_q_offsets, + new_q_tile_2.to(sin_row.dtype), + mask=second_q_mask, + ) + + # Process K tensor + new_k_tile_1 = k_tile_1 * cos_row + k_tile_2 * sin_row + new_k_tile_2 = k_tile_2 * cos_row - k_tile_1 * sin_row + tl.store( + k + first_half_k_offsets, + new_k_tile_1.to(sin_row.dtype), + mask=first_k_mask, + ) + tl.store( + k + second_half_k_offsets, + new_k_tile_2.to(sin_row.dtype), + mask=second_k_mask, + ) + + +# Host Launchers & Public API # {$nv-internal-release} + + +def _rope_forward_tma(q, k, cos, sin): + batch_size, n_q_head, seq_len, head_dim = q.shape + n_kv_head = k.shape[1] + q = q.reshape(batch_size, n_q_head, seq_len, 2, head_dim // 2) + k = k.reshape(batch_size, n_kv_head, seq_len, 2, head_dim // 2) + assert cos.shape[-1] == head_dim // 2 or cos.shape[-1] == head_dim, ( + f"cos.shape[-1]: {cos.shape[-1]}, head_dim: {head_dim}" + ) + original_cos_shape = cos.shape + original_sin_shape = sin.shape + # Here use contiguous to avoid the TensorDescriptor error: "strides must be 16-byte aligned" + if cos.shape[-1] == head_dim: + cos = cos.reshape(cos.shape[0], seq_len, 2, head_dim // 2).contiguous() + sin = sin.reshape(sin.shape[0], seq_len, 2, head_dim // 2).contiguous() + else: + cos = cos.reshape(cos.shape[0], seq_len, 1, head_dim // 2).contiguous() + sin = sin.reshape(sin.shape[0], seq_len, 1, head_dim // 2).contiguous() + + half_head_dim = q.shape[-1] + BLOCK_HD = triton.next_power_of_2(half_head_dim) + BLOCK_QH = triton.next_power_of_2(n_q_head) + BLOCK_KH = triton.next_power_of_2(n_kv_head) + # Calculate grid size + n_row = batch_size * seq_len + + # Launch kernel + + # {$nv-internal-release-oait begin} + # Create tensor descriptors if supported + if _supports_host_descriptor() and get_current_backend() == "oait": + desc_q = TensorDescriptor( + q, + shape=q.shape, + strides=q.stride(), + block_shape=[1, BLOCK_QH, 1, 1, BLOCK_HD], + ) + desc_k = TensorDescriptor( + k, + shape=k.shape, + strides=k.stride(), + block_shape=[1, BLOCK_KH, 1, 1, BLOCK_HD], + ) + desc_cos = TensorDescriptor( + cos, + shape=cos.shape, + strides=cos.stride(), + block_shape=[1, 1, 1, BLOCK_HD], + ) + desc_sin = TensorDescriptor( + sin, + shape=sin.shape, + strides=sin.stride(), + block_shape=[1, 1, 1, BLOCK_HD], + ) + else: + # {$nv-internal-release-oait end} + # {$nv-internal-release-oait indent-begin -4} + # Because nvt does not support divisibility for shape, we use device descriptor here + desc_q = q + desc_k = k + desc_cos = cos + desc_sin = sin + # {$nv-internal-release-oait indent-end} + + _triton_rope_kernel_tma[(n_row,)]( + desc_q, + desc_k, + desc_cos, + desc_sin, + # Q tensor parameters + q.shape[0], + q.shape[1], + q.shape[2], + q.shape[3], + q.shape[4], + q.stride(0), + q.stride(1), + q.stride(2), + q.stride(3), + q.stride(4), + # K tensor parameters + k.shape[0], + k.shape[1], + k.shape[2], + k.shape[3], + k.shape[4], + k.stride(0), + k.stride(1), + k.stride(2), + k.stride(3), + k.stride(4), + # cos tensor parameters + cos.shape[0], + cos.shape[1], + cos.shape[2], + cos.shape[3], + cos.stride(0), + cos.stride(1), + cos.stride(2), + cos.stride(3), + # sin tensor parameters + sin.shape[0], + sin.shape[1], + sin.shape[2], + sin.shape[3], + sin.stride(0), + sin.stride(1), + sin.stride(2), + sin.stride(3), + cos.shape[0], + seq_len, + BLOCK_QH, + BLOCK_KH, + BLOCK_HD, + ) + return ( + q.reshape(batch_size, n_q_head, seq_len, head_dim), + k.reshape(batch_size, n_kv_head, seq_len, head_dim), + cos.reshape(original_cos_shape), + sin.reshape(original_sin_shape), + ) + + +def _rope_forward(q, k, cos, sin, rope_dim=None): + """ + Apply rotary position encoding in forward pass + + Args: + q: [bsz, n_q_head, seq_len, head_dim] - Query tensor + k: [bsz, n_kv_head, seq_len, head_dim] - Key tensor + cos: [1, seq_len, rope_dim] or [bsz, seq_len, rope_dim] - Cosine values + (rope_dim == head_dim for full RoPE) + sin: [1, seq_len, rope_dim] or [bsz, seq_len, rope_dim] - Sine values + rope_dim: Number of head dimensions to rotate (0 = full head_dim) + + Returns: + Query and key tensors with RoPE applied + """ + # Transpose it back to the physical shape because Triton looks at the physical storage + # Note: q and k are incontiguous before the transformation and will become contiguous after transpose + q = q.transpose(1, 2) + k = k.transpose(1, 2) + + batch_size, seq_len, n_q_head, head_dim = q.shape + n_kv_head = k.shape[2] + pad_hd = triton.next_power_of_2(head_dim) + pad_n_q_head = triton.next_power_of_2(n_q_head) + pad_n_kv_head = triton.next_power_of_2(n_kv_head) + + # Partial RoPE support + if rope_dim is None: + rope_dim = head_dim + pad_rope_hd = triton.next_power_of_2(rope_dim) + + n_row = batch_size * seq_len + + # Ensure tensors passed into the kernel are contiguous. It will be no-op if they are already contiguous + q = q.contiguous() + k = k.contiguous() + cos = cos.contiguous() + sin = sin.contiguous() + cos_batch_size = cos.shape[0] + + _rope_kernel[(n_row,)]( + q, + q.stride(1), + k, + k.stride(1), + cos, + cos.stride(-2), + sin, + sin.stride(-2), + seq_len, + batch_size, + cos_batch_size, + n_q_head, + n_kv_head, + head_dim, + pad_n_q_head, + pad_n_kv_head, + pad_hd, + BACKWARD_PASS=False, + rope_hd=rope_dim, + pad_rope_hd=pad_rope_hd, + ) + return ( + q.transpose(1, 2), + k.transpose(1, 2), + cos, + sin, + ) + + +def _rope_backward(dq, dk, cos, sin, rope_dim=None): + dq = dq.transpose(1, 2) + dk = dk.transpose(1, 2) + + batch_size, seq_len, n_q_head, head_dim = dq.shape + cos_batch_size = cos.shape[0] + n_kv_head = dk.shape[2] + pad_hd = triton.next_power_of_2(head_dim) + pad_n_q_head = triton.next_power_of_2(n_q_head) + pad_n_kv_head = triton.next_power_of_2(n_kv_head) + + # Partial RoPE support + if rope_dim is None: + rope_dim = head_dim + pad_rope_hd = triton.next_power_of_2(rope_dim) + + n_row = batch_size * seq_len + + # Ensure dq and dk are contiguous + dq = dq.contiguous() + dk = dk.contiguous() + + # Backward is similar to forward except swapping few ops + _rope_kernel[(n_row,)]( + dq, + dq.stride(1), + dk, + dk.stride(1), + cos, + cos.stride(-2), + sin, + sin.stride(-2), + seq_len, + batch_size, + cos_batch_size, + n_q_head, + n_kv_head, + head_dim, + pad_n_q_head, + pad_n_kv_head, + pad_hd, + BACKWARD_PASS=True, + rope_hd=rope_dim, + pad_rope_hd=pad_rope_hd, + ) + return dq.transpose(1, 2), dk.transpose(1, 2) + + +class _TritonRopeFunction(torch.autograd.Function): + """ + Triton implementation of the Rotary Positional Embedding (RoPE) operation. Please note that + this implements the HuggingFace Llama & Mistral version, whose rotation matrix is slightly different + than the original RoPE paper. + + Please find the corresponding HuggingFace implementation here: + https://github.com/huggingface/transformers/blob/v4.40.2/src/transformers/models/llama/modeling_llama.py#L184 + + For more details about the rotation matrix used here, please refer to: + https://discuss.huggingface.co/t/is-llama-rotary-embedding-implementation-correct/44509/2 + """ + + @staticmethod + def forward(ctx, q, k, cos, sin, position_ids=None, unsqueeze_dim=1, use_tma=True, rope_dim=None): + """ + q size: (bsz, n_q_head, seq_len, head_dim) + k size: (bsz, n_kv_head, seq_len, head_dim) + cos size: (1, seq_len, rope_dim) or (bsz, seq_len, rope_dim) — rope_dim == head_dim for full RoPE + sin size: same as cos + """ + if use_tma and rope_dim is None: + # TMA path only supports full RoPE; fall back to standard kernel for partial + q, k, cos, sin = _rope_forward_tma(q, k, cos, sin) + else: + q, k, cos, sin = _rope_forward(q, k, cos, sin, rope_dim=rope_dim) + ctx.save_for_backward(cos, sin) + ctx.rope_dim = rope_dim + return q, k + + def backward(ctx, dq, dk): + cos, sin = ctx.saved_tensors + dq, dk = _rope_backward(dq, dk, cos, sin, rope_dim=ctx.rope_dim) + return dq, dk, None, None, None, None, None, None + + +@register_impl("apply_rope_base", backend="triton") +def apply_rope_base(q, k, cos, sin, position_ids=None, unsqueeze_dim=1, use_tma=True, partial_rotary_factor=1.0): + """ + Applies Rotary Positional Embedding (RoPE) operation to query and key states. + + Args: + q: [bsz, n_q_head, seq_len, head_dim] - Query tensor + k: [bsz, n_kv_head, seq_len, head_dim] - Key tensor + cos: [1, seq_len, rope_dim] or [bsz, seq_len, rope_dim] - Cosine tensor + sin: [1, seq_len, rope_dim] or [bsz, seq_len, rope_dim] - Sine tensor + position_ids: Optional - Position IDs tensor, default None + unsqueeze_dim: Optional - Dimension to unsqueeze, default 1 + partial_rotary_factor: Fraction of head dims to rotate (default 1.0 = full RoPE) + + Returns: + Query and key tensor pair with RoPE applied + """ + rope_dim = None + if partial_rotary_factor < 1.0: + head_dim = q.shape[-1] + rope_dim = int(head_dim * partial_rotary_factor) + assert cos.shape[-1] == rope_dim, ( + f"cos last dim ({cos.shape[-1]}) must equal int(head_dim * partial_rotary_factor) " + f"= int({head_dim} * {partial_rotary_factor}) = {rope_dim}" + ) + return _TritonRopeFunction.apply(q, k, cos, sin, position_ids, unsqueeze_dim, use_tma, rope_dim) + + +@register_impl("get_apply_rope_func", backend="triton") +def get_apply_rope_func(model="llama"): + def is_use_tma(s): + return s > 128 + + if model == "llama" or model == "qwen2" or model == "gemma3" or model == "gpt-oss": + + def wrapper(q, k, cos, sin, position_ids=None, unsqueeze_dim=1, use_tma=True): + return apply_rope_base(q, k, cos, sin, use_tma=is_use_tma(q.shape[2])) + + return wrapper + elif model == "qwen3_5": + + def wrapper(q, k, cos, sin, position_ids=None, unsqueeze_dim=1): + return apply_rope_base( + q, + k, + cos, + sin, + use_tma=False, + partial_rotary_factor=0.25, + ) + + return wrapper + elif model == "deepseek": + + def wrapper(q, k, freqs_cis): + cos, sin = freqs_cis.real, freqs_cis.imag + + b, h, s, d = q.shape + q = q.view(b, h, s, d // 2, 2).transpose(4, 3).reshape(b, h, s, d) + + b, h, s, d = k.shape + k = k.view(b, h, s, d // 2, 2).transpose(4, 3).reshape(b, h, s, d) + + return apply_rope_base(q, k, cos, sin, use_tma=is_use_tma(s)) + + return wrapper + + else: + raise ValueError(f"Unsupported model: {model}") + + +# {$nv-internal-release begin} + +# Backend Registration & Perf Markers # {$nv-internal-release} + +mark_perf_ready("apply_rope_base", "nvt") + +# {$nv-internal-release end} From c096f19ac12dcb9b5fd8433e5bb70d05af6bdb3e Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Mon, 15 Jun 2026 19:04:17 -0700 Subject: [PATCH 05/11] [tileir] Sync TileIR vendor files Add upstream TileIR docs, tests, and assets. Record the FlagTree vendor provenance and drop the unused backend name.conf file. --- third_party/tileir/PerformanceTuningTips.md | 99 ++++++++ third_party/tileir/README.flagtree.md | 42 ++++ third_party/tileir/README.md | 21 ++ third_party/tileir/backend/name.conf | 1 - third_party/tileir/fused-attention-bwd.png | Bin 0 -> 844132 bytes third_party/tileir/fused-attention-fwd.png | Bin 0 -> 626058 bytes .../tileir/include/Transform/Passes.td | 4 +- .../MapElementwiseExpansion.cpp | 1 + .../scripts/build_helper/Dockerfile.release | 2 +- third_party/tileir/test/CMakeLists.txt | 36 +++ third_party/tileir/test/FileCheck/fma.mlir | 2 + .../tileir/test/FileCheck/inliner.mlir | 16 ++ .../test/FileCheck/op-conversion-assume.mlir | 87 +++++++ .../op-conversion-auto-memtoken.mlir | 228 ++++++++++++++++++ .../test/FileCheck/op-conversion-barrier.mlir | 165 +++++++++++++ .../op-conversion-host-tma-desc.mlir | 31 +++ .../FileCheck/op-conversion-modifiers.mlir | 6 + .../FileCheck/op-conversion-xfailure.mlir | 1 + .../tileir/test/FileCheck/op-conversion.mlir | 2 + .../test/FileCheck/op-rewrite-assume.mlir | 57 +++++ third_party/tileir/test/lit.cfg.py | 0 third_party/tileir/test/lit.site.cfg.py.in | 0 22 files changed, 797 insertions(+), 4 deletions(-) create mode 100644 third_party/tileir/PerformanceTuningTips.md create mode 100644 third_party/tileir/README.flagtree.md create mode 100644 third_party/tileir/README.md delete mode 100644 third_party/tileir/backend/name.conf create mode 100644 third_party/tileir/fused-attention-bwd.png create mode 100644 third_party/tileir/fused-attention-fwd.png create mode 100644 third_party/tileir/test/CMakeLists.txt create mode 100644 third_party/tileir/test/FileCheck/fma.mlir create mode 100644 third_party/tileir/test/FileCheck/inliner.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion-assume.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion-auto-memtoken.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion-barrier.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion-host-tma-desc.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion-xfailure.mlir create mode 100644 third_party/tileir/test/FileCheck/op-conversion.mlir create mode 100644 third_party/tileir/test/FileCheck/op-rewrite-assume.mlir create mode 100644 third_party/tileir/test/lit.cfg.py create mode 100644 third_party/tileir/test/lit.site.cfg.py.in diff --git a/third_party/tileir/PerformanceTuningTips.md b/third_party/tileir/PerformanceTuningTips.md new file mode 100644 index 0000000000..7b735e125e --- /dev/null +++ b/third_party/tileir/PerformanceTuningTips.md @@ -0,0 +1,99 @@ +# Performance Tuning Tips for CUDA Tile IR Backend + +This document provides a practical tutorial for optimizing Triton scripts to achieve better performance when running with the CUDA Tile IR backend. + +## Autotune Configurations + +### New Hints & Configs for CUDA Tile IR Backend + +#### **occupancy** (Critical) + +The **occupancy** hint accepts an integer N from 1 to 32, indicating that the programmer expects N active thread blocks to run simultaneously per SM. This hint is 1 by default and is worth tuning for many SIMT compute-intensive kernels. + +#### Numerical Precision Options (approx & ftz) + +Unlike the Triton PTX backend, the CUDA Tile IR Backend disables approx and ftz by default. Setting `TILEIR_ENABLE_APPROX=1` and `TILEIR_ENABLE_FTZ=1` can provide performance improvements in certain workloads (with precision degradation within acceptable ranges), such as **`attention`** and its variant kernels. + +Note that the TileIR compiler (`tileiras`) shipping in CUDA 13.1 does not automatically optimize `exp.approx -> ex2 + mulf`. For performance and precision parity with the Triton PTX backend, please explicitly rewrite `expOp` to use `ex2 + mulf` instead. + +#### opt-level + +The default optimization level is currently `opt-level=3`. At this stage, adjusting this parameter is unnecessary. + +### Existing Triton Hints + +#### **num_ctas** (Critical) + +Setting **num_ctas=2** is critical for dense dot-related workloads on specific hardware, for example, it enables 2CTA mode MMA on Blackwell architecture. + +#### num_warps + +The CUDA Tile IR Backend currently ignores the `num_warps` hint, leaving tileiras to determine the optimal number of warps automatically. Therefore, autotuning `num_warps` is unnecessary. While the default is 4, the tileiras compiler will analyze and decide the specific num_warps after optimization. + +#### num_stages + +Unlike the PTX backend, the CUDA Tile IR Backend treats the `num_stages` hint (whether per-kernel or per-loop) as a cost hint rather than a strict directive. This means a matmul kernel with `num_stages=3` won't necessarily have 3 stage buffers for pipelining. Instead, tileiras analyzes the impact of the `num_stages=3` operation from a whole program perspective and determines the optimal pipeline configuration. + +Since `num_stages` is a cost semantic hint, it is strongly recommended to expand the tuning range of `num_stages` during autotune, especially for dot-related kernels, where larger values can be tried. + +The compiler should generally avoid producing SMEM or TMEM out-of-memory errors solely due to varying `num_stages` (or other hints). If you encounter systematic failures on reasonable configs, please capture a minimal repro and report it. + +#### warp_specialize + +The CUDA Tile IR Backend does not consider this loop hint. + +#### Manual Slicing + +Manual slicing approaches (such as `EPILOGUE_SUBTILE` in `python/tutorials/09-persistent-matmul.py`) may not provide positive benefits for CUDA Tile IR Backend. + +## Optimization Tips + +- **CGA-Level Tile Representation**: The CUDA Tile IR Backend treats tiles as CGA-level representations. When autotuning `BLOCK_SIZE`, consider increasing the block size appropriately to avoid missing high-performance program solutions. + +- **2CTA Mode**: When using 2CTA mode, experiment with relatively larger `BLOCK_SIZE` values. + +- **TMA API Preference**: The TileIR compiler shipping in CUDA 13.1 has a known performance issue with the `tl.load` API (for example, running `03-matrix-multiplication.py` is 20%+ slower than when using the Triton PTX backend). It is recommended to use TMA APIs for all data loading scenarios. The tileiras compiler will automatically fall back to alternative instructions when TMA requirements are not met. + +## Performance Benchmarks on B200(1000W) + +```bash +sudo nvidia-smi -i 0 -pm 1; sudo nvidia-smi -i 0 -pl 1000; sudo nvidia-smi -i 0 -lgc 1800 +``` + +### Fused Attention (06-fused-attention.py) + +> For Triton PTX backend, choose the best one in warp_specialize={true, false}. For CUDA Tile IR Backend, enable approx & ftz + +![Fused Attention Forward Benchmark](./fused-attention-fwd.png) + +![Fused Attention Backward Benchmark](./fused-attention-bwd.png) + +### Persistent Matmul (09-persistent-matmul.py) + +> TFLOPS by Proton + +#### NVIDIA PTX backend + +| Kernel Name | K=512 | K=1024 | K=1536 | K=2048 | K=2560 | K=3072 | K=3584 | K=4096 | K=4608 | K=5120 | K=5632 | K=6144 | K=6656 | K=7168 | K=7680 | K=8192 | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| matmul_kernel | 410.535 | 485.939 | 508.868 | 523.959 | 523.860 | 517.353 | 509.405 | 503.433 | 457.957 | 462.662 | 466.334 | 467.583 | 465.737 | 468.807 | 467.914 | 474.498 | +| matmul_kernel_descriptor_persistent | 439.707 | 500.525 | 531.170 | 553.606 | 564.037 | 556.934 | 559.873 | 524.308 | 515.534 | 519.169 | 520.699 | 520.417 | 552.134 | 521.023 | 518.283 | 516.987 | +| matmul_kernel_descriptor_persistent_ws | 424.881 | 492.736 | 536.487 | 554.557 | 566.113 | 566.654 | 560.431 | 525.796 | 523.949 | 523.864 | 525.539 | 524.556 | 519.728 | 524.902 | 521.294 | 520.290 | +| matmul_kernel_persistent | 437.177 | 490.192 | 505.463 | 526.356 | 495.549 | 502.120 | 492.795 | 509.629 | 464.547 | 492.138 | 461.204 | 473.903 | 456.420 | 459.663 | 482.381 | 476.654 | +| matmul_kernel_tma | 453.171 | 510.479 | 540.693 | 554.571 | 550.412 | 547.197 | 537.709 | 504.863 | 495.738 | 495.422 | 501.529 | 500.631 | 502.919 | 504.600 | 503.772 | 505.822 | +| matmul_kernel_tma_persistent | 457.762 | 526.818 | 541.512 | 562.336 | 569.793 | 552.891 | 560.229 | 509.174 | 516.811 | 549.679 | 522.550 | 519.533 | 515.688 | 539.053 | 512.148 | 509.444 | +| matmul_kernel_tma_persistent_ws | 443.856 | 519.320 | 553.608 | 574.412 | 578.525 | 579.166 | 569.080 | 534.047 | 532.451 | 532.137 | 533.668 | 530.485 | 554.178 | 524.998 | 522.821 | 550.687 | +| matmul_kernel_tma_ws | 421.550 | 502.304 | 537.107 | 551.843 | 551.784 | 541.865 | 532.079 | 495.340 | 495.921 | 494.918 | 492.878 | 496.289 | 502.044 | 503.006 | 501.350 | 504.051 | + +#### CUDA Tile IR Backend + +| Kernel Name | K=512 | K=1024 | K=1536 | K=2048 | K=2560 | K=3072 | K=3584 | K=4096 | K=4608 | K=5120 | K=5632 | K=6144 | K=6656 | K=7168 | K=7680 | K=8192 | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| matmul_kernel | 372.083 | 478.821 | 515.220 | 523.229 | 536.626 | 538.881 | 540.379 | 540.189 | 536.922 | 496.812 | 527.281 | 527.333 | 545.069 | 551.638 | 556.737 | 546.898 | +| matmul_kernel_descriptor_persistent | 449.608 | 566.495 | 592.396 | 615.399 | 621.022 | 625.198 | 633.241 | 632.614 | 633.009 | 629.261 | 632.138 | 637.709 | 641.277 | 644.160 | 648.690 | 648.044 | +| matmul_kernel_descriptor_persistent_ws | 448.865 | 566.048 | 592.297 | 616.102 | 620.858 | 628.390 | 637.610 | 640.445 | 634.553 | 631.684 | 647.245 | 639.895 | 641.622 | 645.320 | 650.257 | 646.576 | +| matmul_kernel_persistent | 386.227 | 472.954 | 502.894 | 512.529 | 523.132 | 530.562 | 535.570 | 538.549 | 538.180 | 538.355 | 541.091 | 541.664 | 547.022 | 549.228 | 548.273 | 552.914 | +| matmul_kernel_tma | 447.497 | 557.842 | 579.246 | 584.937 | 579.374 | 562.360 | 590.016 | 596.886 | 605.709 | 574.770 | 578.394 | 608.760 | 612.595 | 615.713 | 616.805 | 618.996 | +| matmul_kernel_tma_persistent | 450.121 | 566.328 | 594.972 | 614.759 | 620.405 | 628.140 | 635.045 | 635.619 | 630.554 | 629.911 | 646.355 | 636.326 | 639.891 | 645.985 | 644.748 | 644.186 | +| matmul_kernel_tma_persistent_ws | 442.042 | 566.433 | 591.798 | 616.341 | 621.496 | 628.013 | 636.439 | 633.790 | 633.202 | 629.759 | 631.215 | 630.826 | 641.347 | 643.391 | 649.245 | 646.864 | +| matmul_kernel_tma_ws | 446.199 | 557.764 | 581.963 | 588.196 | 580.131 | 558.987 | 590.458 | 599.535 | 607.182 | 608.649 | 611.659 | 611.689 | 614.381 | 617.276 | 619.827 | 620.500 | diff --git a/third_party/tileir/README.flagtree.md b/third_party/tileir/README.flagtree.md new file mode 100644 index 0000000000..638c6c1237 --- /dev/null +++ b/third_party/tileir/README.flagtree.md @@ -0,0 +1,42 @@ +# FlagTree TileIR Vendor Notes + +## Imported Source + +- Upstream repo: `https://github.com/triton-lang/Triton-to-Tile-IR` +- Upstream commit: `0d9283bf495f9712877f27eb3b1234081a12cfe9` +- Synced path: `third_party/tileir` + +## Pinned Dependencies + +- cuda-tile submodule path: `third_party/tileir/third_party/cuda-tile` +- cuda-tile commit: `0859212ad19f71133a9b940c05323286cbf28a05` +- cuda-tile reference: `v13.2.0` +- tileiras version used for validation: CUDA 13.3, `V13.3.36` + +## FlagTree Compatibility + +- Target FlagTree/Triton base: `triton_v3.6.x` / Triton 3.6.0 +- FlagTree's pinned LLVM is older than the LLVM expected by cuda-tile v13.2.0. +- `scripts/patch_bytecode_utils.sh` is a FlagTree-local compatibility patch for + that LLVM skew. It rewrites cuda-tile source before build, including: + - `DenseTypedElementsAttr` to `DenseIntOrFPElementsAttr` + - `llvm::scope_exit` to `llvm::make_scope_exit` + - `DenseElementsAttr::isValidRawBuffer` 2-argument calls to the 3-argument API + +## FlagTree-local Changes + +- Build integration: use the pinned cuda-tile submodule instead of the upstream + build-time clone. +- Backend integration: adapt TileIR compiler and driver hooks to FlagTree's + per-kernel routing and runtime expectations. +- TLE/TKO support: expose TileIR tensor-view and load/store-view token helpers + needed by FlagTree TLE kernels. +- Lowering compatibility: add `math.erf` lowering for current FlagTree coverage. + +## Update Checklist + +1. Sync `third_party/tileir` from the chosen Triton-to-Tile-IR commit. +2. Keep `third_party/tileir/third_party/cuda-tile` as an explicit submodule pin. +3. Reapply or verify the FlagTree-local changes above. +4. Recheck the LLVM compatibility patch in `scripts/patch_bytecode_utils.sh`. +5. Run TileIR routing, TLE/TKO, and tutorial validation before updating this file. diff --git a/third_party/tileir/README.md b/third_party/tileir/README.md new file mode 100644 index 0000000000..0818755eb3 --- /dev/null +++ b/third_party/tileir/README.md @@ -0,0 +1,21 @@ +# Triton-TileIR Backend User Guide + +## Build Instructions + +To build and install the Triton-TileIR backend, simply run: + +```bash +pip install . +``` + +## Running + +Before using the backend, ensure you have CTK 13.1 installed and set the following environment variable: + +```bash +export ENABLE_TILE=1 +``` + +## Known Limitations + +- Some tests that are not supported by CudaTile are not yet automatically skipped; as a result, you may see failures in certain unit tests. diff --git a/third_party/tileir/backend/name.conf b/third_party/tileir/backend/name.conf deleted file mode 100644 index 0e81969dad..0000000000 --- a/third_party/tileir/backend/name.conf +++ /dev/null @@ -1 +0,0 @@ -tileir diff --git a/third_party/tileir/fused-attention-bwd.png b/third_party/tileir/fused-attention-bwd.png new file mode 100644 index 0000000000000000000000000000000000000000..769086713f375784c6007653c57f9e1bf4caa2f5 GIT binary patch literal 844132 zcmeFZc{G=6+c&Pc&_HM)GG>;NDMOhuRAwPVk;puTOnVn4Ln5LgnKFfxxljseG7lkh z5eb>+_jB(1dG@{Uwcg)a_n*H%UTf{OcWu6|>pIW#I6l*H-PAaJVk_ldN(u^!tt!e2 zS`-wti4+vu#5Qll-|#F&zr?>JT^03RwH+?Hx}SHppg48j)zQ|$)%KFv{>v85E|(na z4+{z(6gQ%|i z$d;CNE+=pwRp4f}eKU*f(Y{JNE*C8?AOF?u=2Ys?>GE*$=dYh1zpNYF6RMZld^PaD z_Ln)(`2ape;4?F zeV+gC3I2a=pZ~W9|F0VcfBFBHQ*7|@`c}qA^;R6K9-f|iBrVw9|FKuZYX7{HR*~a* z`Bb&Y1pNILjZNCRIYYOk^}129Gpm$TXI(n&|8ARlv5#-- zw(R)dKmO0ZHNU$q%{lQK744|LwNGA4D%ymRgNR&!^Q{B`Pj zc_jsfNX?V?4sE!xZJ+A%Q~LV5AD_H;RWX?12M^z0KlIlRTI&Y}{av)DE{?Y6d(8a2 z_oOWDsJqgaXBPCI@jn~-M@P3hI5;Sp?b);EbyE}F#ful;-4$T``Sa)P1M19lygHk2 z-MV$~&>^i0O!!bO(+d|kltT9gxQ(>zk)+4HKRuIU(%X=tR^&Z@h*`?xk5u*ezWK>P zkJS}vGc&UtyLN5jGS9E^@)m_i3(eq(zQ2jWfWETbL|G|`|i%pht{rL+w<+)20uSPDUWfA+K1Be zIy!W?{d1QtZDC|&q~q40s0`k9B8peH?&*1Xoh-v6AF`GYaB%drWSt$Kovof8X`L9T z*(v3>!jxlDDu+wO58u9hn*z_St*z}G^9K@xIFo368xLlA$jgd*u=)ZBtaE>avZG){Gh(8P%1z*)WMa?*DZ8 zL$px!cG;yruyAU(PW^psIq3h{99GwkE-G9CP zbKNG#$?uGTfAYP1LUa(WwWb+$JxJcqMky(CpDt zP*~4(IyT1sg_P&-bGU(~ri~iP>&H^VOB*z~ErDd}I7>9F%b^XRM6RUcNKg>nWPD&hF~=nca6~dU(Um zoy&IXJSX}#+!Z)`9ar6p=bB!yINrNX#(VCM)*RC}-Q6r5g$|o2#3dyYv43}q+U{oB zw{JSCpex|U4fEMwU&URAcASnC|1&Ku&2`aR;>5;3%&TuL50+Cg@NX6qD^!;)Vc_su zniCG9<4M#GiHx~-?{!7R+RK+Ozk47iM7c{a1l#{rW#tB(Vcop(pZ(Qg0j&oZMXVkv zv+{;d+IE-u1W;>=YFKd)9o`I5X`^>Rx0;hrrQ@kr8Z?>in1h+ zO{(+Z#=AOf0mu2@7Di>>dp_J1xR#Q_Dd{@&hp>6oBj<1Lub&PJ4AhB?jSY#3+5PtI z+n&*m!Xo!E=9e#D%1GNgIE2lNb|m?&uB4qU%;SG_WeR8W}u6m!^eo1DCSqn()9+s*oh zhQqAvo+hPN0!XQg_gi_Lta3*qO@o!1S!y@R@t>uorJpBi@nOr0v;E_}Z*Wl$*X~(8 zeGGf%yrpI8oQHE|bv3(;40CvRxW~d2 zkD$rRxgWyp?CecBrYzE4GYVnM(ubxbKG$XFq+hKHW$d4u=)Y8O^vV?X>4#EV+}+)! zEzo)X=gVNjf7A*O509ZHSvIz{4<9}pYDfu|+v8)0^~ZA#NiH~j`g8_eV#JkQ%?r~b z&v)o@9k^21ghUuAXvNptbH!X`ccwZtt= zGil@ZkN2+O2m3hm~m!8_wxu4@@!zKr7-_+D> z3Em~7^gz^3hviOSpz^8n=g;FwIhBl}W(j+YpOe0j9820&VYly_m!6Xcy=H8^XTQ2j zR*rNO<_vU4{dnKnx=S}xud-HlHALkO*XxfTPfiGvtGK!I0Go`ANp8$`W~pQ7*Y%la zg!oO%m{6p(`Dac<3z;h6QKE&U>3?i%)Bkwy@TSF?(Ko9rv$8swdZ%*Rt~;)-_$cW1)=W}LIU7hx!KME2<3HF+viN_nuy9^6#)1%pR z;=O(kY{8CBX%)U17}#{s@-x#*zg3y9mwgtdj^j{G-wyxXRpt{sR=&zi${z5`*0@_2 zwrtrlH(4uNpLa=HK}m_1K@6p3d13kuHW|Hu+;s7$2VzObuWj^LUdT^$l9rb49UjiG zqRFV;uyJD|y4v{Eln!TEdqhOUL7NtSoivR#FD{QZ*x(x~tE$XUE`pc~7skJTVhmvv z+eXi)_ZG#SI_~uKrY8o6BL$5mzg{jYD-*x`Glu!l-w-MBiGM)a-w;XJO_lz(Bz8N_ zKK)Iw$$M&m5uKf#J7{UmhI9f0xz~}ULCbkFw&@DPzI~xP51igDxs{Er;m$~F4uLde zBU_Sh4Lp=pSEs>mT%x1Zf!di78@hmAeFeury_M2zO}3A1 zXOZTg9~&5U?D%8j*1e>*1-(7$*aOjE=+TvlF_b*lp}IAjwlQhAF{K1tzn*yh&Gndr z=dZF=tLj}I{Jakhn7wh|x&@%y@kVu773-s(zc0Am`4@)74JY#7_>WtCAm*UQaoWs` zt-!A1u`)k!ruRb`pCGJRSKXx@G@2WozP+axv8toM+Fw7-`C)uw;<%jL)q$D4p&QZp z*-<-imzy~b9FWg!_MM*m_4Vj<1Q#2d!OHv~3u$;b%12yzQ7rDY17%F~zPaJr(Tu-G zIc$+v8a`X>%!!_AJ|yDr>E&hqvn^M$`rg6wYYQB@vyUk?koIfWQ9!>?_K+5^%5|h= ztBQ&WrN!NXuuvU{llE$kCrZf$WkMrV; zo$FZF*Sbp=_K4Yc?y#1<_;EMZln&7TT2__-lgfqWv#cbIq&C(b;e~q6#fQ`|-)}ud8ov zY#-_>J&NidrKWmyXvpeX2;cC^@}gDSQ&wi_D;I`z$|TUehFY>10k+Q#=^To*{qigU z{Xe2z&@fN=o9(}El<)k1+-O5UlAv*kzt`+9p1pUtGk>(^u%Pe^#ACPZmb6G*)i1EU zm!Q3$Es=TvB}Tv8w=|*g9W7e9fBW{`2alzkL(aOsr z>uWpYdXuB0#Enl*POibu(#|dvzifz=eR&HdWd-V*Ztz%7&mWIZJ?KMMdy57-{z0hx zT+gdN(+u-@f$S}+o2~QbmS?*yrbB~*D*jlr?oqRKz{0%;50WfC+}$l~ww@Gt*U@(R zZQHh0^w-`$LWOIoSf=QSHuXJz?%X5C^7m1E=CA%(GdEh;J^t0bI{^T{LU`@EbrT~w z<&?9H(LFuqM68=O=a`m@y7G2EnH}ryUmPnJbKSe=R@2%5v@QOKe|NVaVPV&`|J&*m zrbdIp!cgar(uIxm_Wmimx}-e;gqs|aC)!Ss$hQyC;_kng^q%Pd=`#3vOSLQ-QeS`n z`HL4HtKA2p*gB6ptAB2(Zo;TDGc$TNVWu*OUYlnp2-TYx?qhZ7+6O>1e$b^F=386U zCvL!{Z?Lqq6c-nN_5MAjlao^*p2>5|+8uOs>v0-9yu9Aj)^100pp>S<(GD@6=&Ouq z&v)o%78MocwVX8dS)8c^)z$Jonc2*C=n(CnfBq@%Jh1sLzrj_MrCwn`r5gaC!!bWr zGc^*{mRueU0(MIPrA#mIT3$Hvy*55<0~N!5R@UPlv8ZxAIKJG5d1)HuU!Gg`gUiVI zEXm6*q()!;1BHU{$F2TMDz=wbpk8M<1PYzYn(+j)pJ9pAw5#7^q@35ugP#vpc z?Vfwa*EVh?m`w`Z^wVWLIsFn>p6{O?q^5rS`@{^Ly?9}0{lEX%;WplL-2cW6K4y2& zj|%slTzJ&48yaXqv=zbAdiwjX0Tyhj4!m`%3PeWd-7U03>|5>SoA?`G5GQ=V(p-Po zGqElia67={GvY1_TDG=NG`tu#va`K6!s+t|>-g}iJ4J0*XOYvoT|y>rBYCvz&?V^v z4C%0*Am3Mkgq;UJhw|!XJi-Qxk#OPq9C!4_?b|!}4W6y1=he~pRRcj`Pg`^58P1&Z zz=E3JY~Nam>?KTDP*5;N41T^@#nwSU|{VpD++#Yu@~I?T$t=FS22yPYPc zfG*Ugrlwz(spz=>05f0{5ut;qQ9&RV5a=6-ED#P33Jd?4p3Y-yXQ!EGrAe3@VC^eF z8mHch4Ok(vN6~7(|`Q^$1-DPX6B5yKEOJkA4*f5Idew0PkVulo!x1yOIpfn zhMJX?6$`IqWW)qK3DSEF{XfBDqOZa|m@Dy#Vp9D%AO-UKQ8&1n#2`!@v2M~3iAO76 zziAUY06q3l#f|MOJ9h58hQH+C;CO}I&cMJxbP^^eCXHle8gU7U*Y5&neG@+NNl5HL z?OJjDS&kas=)}UvNVRk4&X@-e-n6tZpehnMTTTk=z10by6z-mHY zsI2B(jPIrsGKt;L`YiCt z(`~=8i044OpDjqDkiB`c?$mve@Mc*EEQHgdJY8o$aqAp*T#@UKh$s3A_m1o9x22Aq zLq(adbwAkC-Nz1&^DgeF7zCqDsE24wZ(bSrz33|Sy7B1Iew<}GVEVw25Cv~;picV4_{g}e4HE|U*T>wAfmjju2%asJKie~1oOdA z_Q;__a?ovr0h;Le^iCGZQt0HE>;rICfbQo{#kV*&cMcFMq9e!P`9{b|w>!%qPs9kD zE0v7i7jwA9Du4alkA{>8b0NdL%|M=an`TfY+wyIq2iky`Y+9ZihkDa!8-3XF^b_M3 z3=q2%oSmN~w-JE@WF}n5RHkM=?FZ^1*ypV$W%wa<^-7ciSHlzexw*%2D(VvCH=$3Q zA8E-V3->X4HUZvIHJgNlIx+Y-z~D$ zAs>RF)>(->&CUHYG;}Mp7)`K9>~kiZ=VN#>x23%m>sHTpPQfWMO%r#wTOdF z>9D!MN`zbQ$!;Q3ahZgH{y^|ejVkoN!;%Fy4*%ggi6&p(1yxEbjT6!@jfVPb}Koswy_GnV;M`X*-M{T0TC-C}Pt>&BbNrca;wHXW!Awub|W7 zXXLG{xUmt#`OXLusDxhe>HZP<=NCWSmi6;hjE~mKtSt4Kg_>8X-uedRz7OCfqLoAD zkf^8<5l>T3@5Ak{#SdL8E-p@-=Ve9r0mYh|G9m36QsWIQIBC3)2Z)Aa)%8-sx;0y1 z$#-}dk79g!x)OcgarT!nDtbRuT+$Fuo}?x#Wo|y1K4+S1R;iJp!$YJ_C<(gXycY#> zt|fCoQkGqwy7QzAMWVXq&H+t86Gi7s_;~cm>*!mA5Cy$m>MSf_COz)wm*<-RZeD?4 z&@eM^L-R&0Tn8$pg#A~Sbyf^@LWw&YH;kptwX9_}dg(EtZR_IVLNr%&^5c*>$tfdB z4|YGVw7e717QANoalk`R+5O&2i;6M;Zg6WnqJT~nhVGkW{Ni#3-4#ggZ}72Bkd@S1 zW6^wAxww?X^|0$SGW7&RY}}1r!3!+WPu>f?}>+ zTStH;Y8<0TEy&H!?s67v&nhenk-rmGxVX3+FJD%I9H;?#RVPDd3$TfvTbvL+6^G;o zGzv)V<4_FLUFOj;t?~^a&sCuR!KC0eDmut}COZVDI?4WP$)4XYwv3NSR6)ZbfCA6T z5smw$&msc|)RlX?GI-EX&lcK8H5CBF-An8#M9XW>yTq=O$Bj0KX1PDU4BID7 zKR40pzA1qCYjxRWf8a{W=@(wJ*&2&je#-oKX|I9-&)K|7jZXn^6;7N`v-TT!M}z*P z)39WV?WKJ3WF*}#3Q6~0Awc*l)@R`WfXnFqwgiY+W7C@5>=}BDCrK$3?CGkbEV>nz ze;t-qO9XA6k(x<-$GT11-a<$?XxAi0atyT9AP}Y>_*iGB|ndc(ew^ryfcf zaEqn*$_zse>#Dx&>>M#|7!VL}$!~E~EbH2}Yo8_BIy!8~pGHAqE`0l)0(Ea>Y6*g3 zJ~%@Ylrn8+CS3n0o=hJ0fL!t3K92`dp3WKbPfW{AxLZ)zD!XEJ(2H%{e|4tYwdbX` zKxCV7`4BH#ei>W59qo}jB3kc3@vQ?scU(Tx(bLkhNmb$V{HyQD`jxdbH|yG5x|H5- znWG%e)(&7^Q>JutGZj@MVZ&!juefDF>--rT8WNJHo1s&G`hn=FA(xbqK`v=Lt`SaN%WL~GBnYh@5fNpBKle~bk(AYSp2Ya8$;YzmOSlJPsioK!q6Tq zD$L7k+QuZV!MJ-j_mq)#|G)r;=TAHUzvk7iFWlQrediTz%+2rEufhUQ{>&#OWpotk zfn9;^7yr*&aNt_8*;fvbM-X7rpO-zd@HlMbDuNbW#V#jofTpGHmY0|R9E3)4cod>? zK47q8cE_Z<8SLVn`hqn2&bfT;?$$H|U*jm^&5$ z_PLzlr=fAX03wiFcX@1VY#vUUF2`!#`i&c{51fp7B%t>s=5$Umwo?J>N_+Y0%Gt$* zX`8so_wPpbTelaxq&^vpL0unhOjXzYFoKHM4zM`HbbvkQM4(Z>_JWDq)R1NtWR`*5 zp?i-sY+S!y`Mn)_Li;m|5309d;QgW-EK77XF2sE_WEa{ypa|RLTQ}#Cn>Q(ORnhb1nV6n#p856ltc|7RfumyB`Q(c7v1hdS zh3ePTefp*nBqgAS3NRA_v2M+`~GL z3(sK#BnT527JKH3Ntt(gvz3UPC&;m}vv>XU%cc2AwS|?bViZ-XP+`9nZxyExQG5sJ z07jDJJXwxjKFRGHKuPc4ms#1}ZQ3Hyiglbj(ol&FpATtI&Ihs*$I?qJ!xy(nhpRB( z$|586wJ6a>=#N9?BTdzwqMXuKrf!QoOi4+Z0hkC1QTbaIF^H!5n=S?F^z`Yx-^nBN`_wL&!0cv`ODnSPN35g?Q48y2JJb-(KH=*?T>Pr2=xreBbnD6 z9eyLeLmuesp!0w-`X>z<^_}z}LXanQWuiXlee=k$tEja@C#a6&!^E2G2Nfa^o*PpxPyP4tv65{&&hiQ6l`V zr&p$}BL&Vf5Q1iAmS}CBQjLy9^lMYU<$$y_?#ez~D;>P6x41-tjBA$3#Y0I>L@yy; zliB=*4QQEHGcz+C%h7Q+&)$Su_4ek@-k(21@PlA$TH;mFCNDkygiV|T-jZRwJy~nS z#j2Pz;ZrEoSya(x_}oIEjEyR-Cy)3s9*;aovW z3Ow%I_RNBc*!jRc>=25y;!#LQt0ExG%E~HZltIWOgp!{39K`vEK|wrDSk&vVbh%!$ z_9{MFFa!P~5OOY;HL(?Aj=Bqyy#Pp*DSG+}Nj>;1xB}R0goQ)9urdvZ05x_WjyrA1 zcNI4x&JH(iffN1;1`8>I+Aj&$ko9GfT^e7f8pX?b^mAd^wupyNvfd3BS~v58HE8tO zu9(84sK+V)vRwXcIuJl)YP4e)uKEqs3C`K0PJQb$&g9$zH+kI71w;rPS`la1aim3{ zCRbiwUf6X=qo}ATZDE;RfEYlypo6eY5c&+_V&9)taboVnLU+i=>PUD0Ft>m2xEp(H^el@9}S!z zj>Annag&XeD1sUQ8pP9* zi{FXW123%rNXB;65|+0hRouA)9V;{D=Ci5XTOELhy}}K5#2ifEt{hJbdnegj=CkN; zCT7=8r=(=oA+BH=4VN&vg9}!a@NC;%?Q-pXS0Wpq_ZwWV61$x&g&1s9K#W?7%l% z)8<*I1{$n8ze#z|?Eq=1OjHb}3w>enfgzGtmlE7YH|x<|LB*^{A_oFSQQFPCM}P(k zz0k}RU*a}W(b;LF74r720+J1PrJyjYoVPbeBUxAz-o6TrHtxutH;HWzm7u8Hidzra9;%H^I~rfRi_VnYtet>5m0G4=H9N z-_R^naguU)7Mu%I!GzPjI_!NfCMIyrx{c?6$({`g0m}`w=ktl$cXDTzmz5%xf#$3L z5pH_L7aIB_0O}ZVCpOpw7pf&d>^i@Goq+7E_Mj=&*}jryTJCGrT~_jn$IzxbqYa`L2;Wzh=jAVLvLu<$K5LaR2d{>!y^)?)Ps0qg|hoES{g*p{} z)zlh}VOsKKfWm8NHt)Z1pK#sR3LOvi^b8N9FsyI6(UfeSwL1jI^*}fiW+(`2N|^EW z_5I5THc~9ZQ#Ie>g3(`uh@S zb&l~Ut~~L96{t%Up7++zW1oh4M$Old3 z2JVGV%Lv_!mYisGZ@PEwV$ z0j$thA31cF?LKrNu+EF0%|xe2gRBH_yFOkuF%1>G?Zv*(7bw~c?Z3H;rfFXB{0<`7W*;* zj~58%%5}cCQ=t9IoR)V=b904})l2j~L^*%Al^t?&Jx+XFP?5I?up2)D8I69jp1;@Q z)Kvp*x!u|ay$qR>I;j6{BXD9wn4(c`!)LsU={_7+(wz{%N zeN@OBF~Na8M_j`h2}X;X+5LBgOihM1ida9(&84wMT2Lq*X)>mz<>dggGo)2WQ$RGO zhH@OyfmB=aqVLTo4r_n^zT2oCD(B6%jaiC&q=ZPTQ2qYe$}{aIdYX_$@W@VRoz0g1 zJ6IH+eW=Re^539x=mDEn;ConLKwJw`-DqfcJH^15I|}S}z7N%!1vD-yF7Cy~xWd@j z*tin`g!&KHmr&WxlR!X~_sWt))vbM*Rv#S#;7y z>=c5Is0gejK*az0b=6yZ`8%3HR7j;$u{*MNE4CH4#3rAkh#Db{mKL2$Bt@73ye4zj z;9I-J>|+y!`}_LpfLNQUSbX>Zd31w)riXQ*cb&`$usZ`k5r}QXfdfn+D- zD{CWTMsyOv(YgQ0Yos?1RG_Bk=f`V~w>4T&1V|x)bIB3^i z!>8x|Jf2gVNO}(t?0)c|>0DHhIvC;9rtqGwZ`*gDCrE=}dAH#v&E$8nI)(OM<#4E{ zf6G7{q(N=go?3_w9}Hj%*paE5`PhVwl4{$w>#3=!8u9R!Ga^I9!}FymxQ$Elk}ng- zUQomkT7SaKt8$w^6PE=|%VNLxCvM0a7}!byU`%HnZ_;Ib=ddjPhtys%`zLau_a5B0 z@iGVnuS~vt{P=N?m8IhMUtq2M>#M#nR4?2;KTL@x^=g?s?-eM0OL9Izr{rPbiYTI9exu|EHlqW3+ zw5}U9s_lJY^Nr;AG^L_9LWAbDNPY>cm6}oXq@F1d9n|B#wp?>c%cxwV64xzgm5LWG zBoD|y4kKI#3VEaL;IChT5|88KnQp*hlP$CJOD`0yk&!_fmI?sror>B4 zz@X+2?z@j*n;vrPIo4Zzl@?!h(3g8dixWu_7eTu;Nu?`rpc6el0$Q74OQANF^yNBy zElFwjHWDab`W-y1=mbWbYUOml7B%g(-g%(Wu2)~b?gLTLWaaz%*OsiTtgMl&-MZ(H zLV&I7Y*x+*QUYN!pAor;s^mNWZCA){VY#U3z)Ovfk-avWD2iu1e32U3bB^jyWTtpb zdel#0>E57F>#|s`EX^a_qUpnA42-%JntWJ7qw*O9cyYuYw^jtQY}m9Z35X`cPwf67 ziy+?aoWsK}#{tkII!IltJQ=x@f#Ev1^A=-s2rjziqN-5{DMI$yJRNSx@e|Bk&dJWs zE*v&Nf(#2iI7ph0VKFBsC+oq38r&ucasmGBejNARkzzPbwHrn5bWtt(mhL-_No^ZD zA-)WagsTGuY^{H1dDs$#{yD|Mqw1>d7Ads;UP9}=ewy_Sk*d+x1A=O}(N66UPYifn)!JO>D}`O#NAko-a}2lUgmwEa zo=WqBnoDi+FIMy{UKG56+9ZvK@}Zmei|T~eCTTdd>$z0p~*#X zP7n-tE*X7UTFP0uD3u-giG|JSYiFmw^FemqhD}*q|M$!q}wjIQ3>*UW_`v_8>M<*kx=hs!M$j<_5KQ?rtqKJ|9 z5=EBwk)gLdZ1}$<(GIDXps~7r83^XD1qByz-TMq>MsDt91Xc>gXfr)u>w)ZROCtcB z5tO;UpPQO=tVU0p7BRCT4!fwsXF|uw7>wecNxPVY1K4a11IHpY}8mRn&fnlz=JTkdGqF1gyzFG z>xG*wCyWwJ0e>gz5S`2Sk9$D)mD1F)IGp)A1u6tBzOo&`0@GW^>)D5~$TB?K^!}5v z$QU^9HTvg7%6gJ}_>Ex}Yg&FmH2qB@A%$%Dzd4nH>7oA&v;_Vu(85tzSWuvZW?8;4 zi^NV3m~=!xvYh7t1|vEe{Wh#$--G7D@M~~rD3E9l0O7>YvbD9{v3D;etotW0M3G>K zX5xcLk7QWVxY9=BC@>9TaiXRWzXIQ8^y2a<@mr8?E`E;PqOGk>|LmjDF4}M}a&@(Z$iqI9W6ZG7tz`9@DL)2A> z50PUcO@snsh#*`Jm+S_F9ah+h#85+=9yn_P1fjFUjS9MN6_#`dBO{*(^xe!Jl*i&_ zU>%aT4U|b)<%U=T3UM3iBZ*HyrGlRP1`3rE6rgKe2F!ms8z|WQSlft2lEz0#C;mtB z42#%%771qstwn@&62T~c6wrJd9gVX$3YXJit8R#fG4z*RiS#15R>)Q1C~)f4rBP7? zkI*1fNX$U+Yj|~SFWiMtI{8IfQ0AX{Oqc;efA}mWfI}5jJvA}$8kNOj`qM+{qv&1K z2M!!S(wh}(0vJRF!@dQ*uVp@GTP(8>Nw_3a=00XZ2*0>J62%j5w#&Zy^l2;jE=iW4 z4{ZSC=096-QY*JO49`l~zEd1IaAWc9JWX>(yXBOXH~TL9IAGwDm6cVGpdp>0(Qag2 z-v9yhjE#knQddpfKHt@4cYgh+$Mgh$)C7)2hlkUU0)pMm3Ri(7j)7nNq3kL4?ZQX| zbd#$|N$fxl7@pvia@dB`0KbP=KXJ~v*p}#$ny5SKt;8YsE8#LUG-LxK0i7TwB%)nT zs5n2{^QoW$$eCwBXhDkZPmB|smI#L5B!@~!<_w5cCS>yR6<{K~9I9S@7%qxvKb(cI z=K|_VUPZ%K)`V9;mJ;(IR|5hzVB=ncRSeIQs#ZlNI;!<}nJaK!uM7-lJhK$aFGF0J z*(ch*d`UoDhFDJs9TDyUA(s+NA?R(6cIr0dZPvrA{?d94_hmLW-WxQ)9(C>7n^c=n zJR9^^CrC90gXM?{nWJnV*7xA2H-ZfKIe%H}@OlKKN%CWO)Sg6w$tM(K-NHL2nH5*AHIm>y6G_BRgj;;QMe{G z0petOVytWtG7i2YT}JeL#rob)`%ts0V+GXuv8uHQEXcM zLi@M@VFZD1TQ5Zxm6WL3XGRhbiI!2Z3_Cg$G%mHsfEz~xC-H4g`AQJvi5g*~5dT0@ zowBsb`NyKi`;3#PPUT)|J5~1%K4~;V~%}qw>;6$T??_4mifdP zpbL3;nmd@81&h^DYaBsf;nQyp{r3GkSMiRh!a^~GItO)Fz-)7$oQgws0S=s8Mp!p` zCak8rbR*_Y0yLnNB=1UG1xU}YtE#v@%|Li3DgrPpnZqH;Xduu^;5iJ+DHF8XP=qo- zB9B+5hFBwWSeK_;O)GqmiJE9N_3ORC;yY*Uo7`;e1QTg#{LOZLWll|CN8C7v)QS9O z&*X^5=VO%F+=qB6P0*za7yQAh&B1k0R~Bl`q3R5E6bca*1kRN@zh|`pj)(C13V&*^ z(lJD9aVgP#<-xW(E!O$ZTu88$X3rj;rfLYLkbWz}WLIMP+&@9HHOF|8T)-;DIn+}U zi-JSK)g*M-+0BgvILq3{8TahDiOf=8&+BW?%+H=ZYmNer-#vCFJ{2AIT-~GNa@8N9 z%U_^pdRka`0-3V9XBKKmZuewP&P0=uHzZ~-wMa4}D5!5B%;rK;Jb7Pa3ykoHDxdk^ zoZz+%0w>cWQJ&9XN0@%vgk%}{uBC;MoCudq6lf0+Q&nt)AfQwtz3cC?aYaB!)TvK_ zDDto-5gSE$my(vwwf%Cav()R1RmhW~Z4vjePdCq|8$5qZd2fh|FZ%=(0CUKU;J!@! z{QN7EZP>d+ijSk*te38}0qP`3_(#7LX#h2pZ`u-O0Eeykt;Em1FvzZpe()5L6}acq znH7HU6kx!-1zMi?{Y@>Q2>Bg4L4}lhE5b#|wje2)GVl2YTL&|k1_fMa3;RQt_8MmRjgo?pAf@Kf_$=LM? zpL~F1ae=tJkF+4vQI7~m@7ib1g8|lrat@4KzjNo#`!Bl*LLfP-{eLB+Hc&Vsn~u7# zpH_=g&Rd+-H2pHnwYcW0Rd=5A&QE7Q&y`Hwxf4%2K=(2W3eWGG-s>=d#A~Fn^Ic50 z*rB!IHdXj#x*z`?hc9#cXW}bpVtjmuHKM4|bgR;!T!`I5H;GOb2i&|K78W*el{z~+ zn}^|tgj1jYfb2m8BacGb;7%WnCdZSAX47NHx`i7BzOSiKnVE}!;WolRBB4!|4aq9d z*Q`rMacI^dB7oj>*7+wK`}DFkjbM0Wl-2FI6I?tJS0WitNa7Lw3yX`XhRXX>X5S;n zZ>3FWqc;D>+u{7k*&&Z1=Yy>OE%YwG{7b{^m<N0fsYAUI;?*Q=uHg3Sjwmr8DOO`>-?-gW14*HuObMuB6EVdVW%QcQ4|0+5a&_zKX|w|N$1q#}MD zk|Y2myv?VySi~M}TEG4gFlp!J$Xi65#6Jiy(f>MI?(2i7*yDvcv>_61O^Cs@S0RGm zV4H+#64-Ra-gEAy$8JX3FbC_Bn}3(`VX$Hc@+wcyzuA}|gWwRlKOI2JUF!#Az7P%~ zR1ErsmHW4E|AB{ShO#9mQS%K>BVcP1sJWxFvmE3P8Wxr?*f#yBm}JxmC4`=(5OiA{ zeI}@K6EeTK@LG_C)=7Ied;;UinBgILx;uUSq_^VXbLuD)63Tr8(MEF=Zc>?1_xu-p zYoNpLIx?eoTSb0 zY|@!+5V_VzKoeV2kgot!R$#x{{KFuomw~GHk=b2G$EO1^2qjuIrG-E7PIh*6eT%gw zzk-fM4A2#B8Lo;s*MHmubv9aU+n-f~WF{Hph>&hhgHf*7x zuP?-4+VuSRaT8Kf%6!~_3;g7_kR<%8F!v&Y^BxSK^LTEqdbSTaNz{i*oITF!ZPe6n z3Ao8MKamitkUT3PpqaRyOvPYBQ2Hn-qOQ^MP<$U6N;BhHu(Y)eL3+--<%vN=YgQ8j z6BECy_O708M)j%TW;JK8`mqxw-n(c<8R_W*fP8a;^w#!ye%NCg?VFI0@T{sKDwc6a z^5xTNwaA_0+-)`RDZc>5hk+;_TD3D^*5J-GyhAe}*x_}CzItXhHdL?;k(^EGm_d;3 z(u&}}-)6#n>K}2a4KOY)efsz@kAhfoYR2FsaH0CUA^h zna9)s10t${RLa=QZ|ym(G~{;K&8?a1zyYezdOZ9DVNBO?RMLBv%dF7xXelt#(6I8X zxOe*;pj!k2*>S}f#-{+CA30AGjn90SuO#H68O7Ougu;*&7Z<0|iDzun-`~$USPK5u zJG+Ed5J`9RIg7gWeJRgp<2FeHwFtEM-`eT(!e_CdZjxl8l8XRh>3y?sMv^c~)9_>d zXIJUjMTApT?7o?x9KKBd6nC_+&cy{6tE8m#-q!?#rJYH_S($kZ`bT;d?J_3O`^U!O ztvi7{RBs(d2IEUkfEMIGL5Xgdq?;=l?(OuMZ!Gii8b!I`2%YSCy$%@LVRh1hv|wfz zAdQIl4m6v9?;lzGph@%prp-|O?8ZviYdbiu4LrECeeh{p+S?6h7Ryl(M5+O{lQdp} zk8UXyCD(*ft$fSj(j}hxmv--JD+d>5Ml;*I;_rfTZIP|TZK~XY9+l(fK$Y}^jHFyK4xg0xgqnMQZ21HPnY+`SsJHj*e?kwStmH>H ztVFo^ZcI$tCm2ctu^sTj=uAJ`e8rAd$wf2HN9EfUR0=J^wV#WVb4w`~iyz=9=Ty5l z4@N;>093ekSbTTeaRjjA_*^ONy~QE6_>T|!w7o;z0Jgk{VJ#@l(z&fY%T2T-_V zBK6b*F1j=C-Fn#T-S{XB+1Zms${l=upp6;=e$>KrY9nJl_*=meaN(u!nexCd42kJ2VOl+3o0< zDz(Qyw+BadyFETJC56pp8dtp(`_6{XP2l#w9U?V;C-uKot{{-LP0&#|3mxzxf*|*v zr>{ApOd2biXkXPb(Uf4Rk+JBNLMM2do+Xr)FmK$M@l~Y4C=VgXGkmt zP(sDt4|v73gUpBlzsq@&kUj}&;cEf}F2x&w{}fs0dV{tg1I*u#z&9 z1|4#`9*3r8^wgiDYO1UC ziGj9w0b;_}x3fI7e}O5EM2w_G^k4Lscn;$fsvJjQ^r2l%0Z^?N`1SXvXQ9>M2?zA4 zd4BTo_Qs$CNC3CCNPcE3XS#7ZcXd;gU8he)1l=u}jpPKfi?=h1Qg-bbymdVd`Sa(`C~ z3kwJ4G5jwL%J$epye5)m{y8cT=QeNO zzH6Ah85=^g*x5qGt~n-YC|Fq#eOgm^=W(Cq0Rb9TPD)HRjVi`H(GR>rCE+}9yVE4=(;OL-gA~9~D0C(}R$~d% z!u645Ja`;CtxX~LCL|tYQTg&}MFJ@cqI7%2PT?@+0G6JIQkJHdwHM9j;*e_@oAWKW ze5VUTK}8%{?*bdXuCLcj3o9eqIs!bKw`>XTOvRA|74(NT_qjjVmPtLUtqGwYCp-e3 zMF4%FPjU~^=^sE22p=%-XlT%CnbyC=R_%kpwZ??71PPje_E7E$nlnO*i%jS?Swi2h zFy98BDNS1CFtRk5wMtzmf|FKPoDY!Ct^f3qBQsWHmn2scze37e0OE=_Z=8_UN{~r( zQRwMzO(pfxM{&!p0c`q+ekDaki$J*) zw$`_pI!TM!QU^+y;1a#JTN|LBgKj4UvPE3!6c4hWwMz!lIg8GXXzkbAYG5awNG?Su z?RIj3^XD&GIh)u9ZH_IwZIf_WQ@=JBf$$29+WA*&2EFs#aJyjT5dm;!P=4?}8*Mix zV`YJsgXUG#Q1O*=CWF9ew$T46jDo4#$elIAJTEjgl z*VDbWKuaZ#-I9wXxG|JP)$fye6v-yyRVAF9FmV3`Xd*alP)PxO-IsVOAdVIyifq=NdYQtYIk-m3QwWm8# zAN;D6H#db$^*|5j%qP&u(K|deIdSqkV#M{XW!N0+h;^XNIg5N{WlO>i7f>?Q?SB4Q`px69FsLL7X2FO8hG*$1Il;b3W9`>0Ez0)De zn36?>MJ6t@D_Bz+?|?$Fu7V31ut0{ganPfVJWfwfhqi69Q^H64!Pw330`A_$%L5;^ zqxM^V)t!1wJCzT>aQ-iImi^7nV*5ZNyzb_IzhqKZvIhS54}n7u{uT7XyhQlajh95w z1`Frtu33PE3bRTc*Ano%CgWFEDPL0=@@3De0o0-g3P4qqFf)aTCIPRPk(dQCO)D)f zmiYMT6KD6bcxJc33{-r?)$G?VM!*EYG)=5^cuXYblI1TuU2PioI24OCbWoE293}u+SuI7pW+oSJQ(Rh=N&$LO66D*sA8JwXAqoQwWSdZ$g zvxOI?Q&xH5@gW8iVSm)8)(0{V6fAszMc5sN6hs*IOXfV$NssuswKLI0tG+XNuZ(vQ zuTu)GMAzu(sBWkO;^cIT;5N^kgF-_qk?45}g#rVMDi2fnSS2O*lGngUWfS+SC5x}= z0K6|e)4%rpWpIPEQo+AUICSj_AM^{|cM4@s3YZhVOT0j=PEL^@(mLlZUrm=d`Cnd? z6c{pm1%QWB>$51@o^NwNqzahvDNfbTjzaY`@#oo1OWW=u!!h;qi+28;>ZOL{Eu>Wx z`(ULJQo9by;G7LS;B1EMM0=Tya>aTTDy zEv6oRSV-r-^6~o)NHv{+9P!skwuH;~A&ZwF4T$yORduF7*|~NdqEK}|>^+lrE~2U1 z^D&38ux63Eb$M8n_7D$3J}pU$3i&x`A^Pmzfa~Oyk5dE+G4jvC*6|LGZ6`#|2Snq z-`V*PTu%%)Qv-`&c!09H951v1?;_BC%WNpzGC;Xp=WnW*O{AP{jfyny0n?HbR;1c? z!4(ScTR>IU_PTp}iO@HBmq>8=^PKr>wJ9`8TrUyS58d=q)%48FO2!UdL#Ofq(hf!k z$@-9dg|ib=O1eQ$62MQ;TTdv!8jHy4y+`FJn_7j^N8a$I(r(Ky$x>&LR7fK0xxv5PSU!j0TbHVuwq4m zuU1u7BKWcvsPzp1+J#SR$T&ZWK@z0QlH~i@KsSj{8Y=>}gS-aeziN2jPla0@&&3ei z7xS7Q4-9>sDHAH+zI}yEhn}qFb>61CUk0o1g`)70w}Jt;?vac-`MRzZFY@`HulIZZ ziGp37kv>ldC5he>*zlZ%W1Yp%2jp}pF!6%-pwX6kPFB_NB%@rN3934bzyS=03)4wc zrO3jiA@G^|WVSyEjBIoMu1`YJQV$!%;G5Lgq2dJr%k~mtXilaCu#f24MXmro5(%NG zEhHk@HfX2xMYInAF}&js&Q&F}KHXp;!$50flH*s7;=v&1rkuZAy;cV;=Q`eqhnB2u zJ2*NT3XiL|(GLeK0N85*`Cs#>xXj0;=zo5$+ytW`=Mk{#t=NRF2Koy6q&c}Wn-*?gung!bY z^#MlnKr)?9Li+&z#Lzp2=Sf~;k%+e+Ml6iDe6|^K*>zj^JI?}})_RTTs|uK2Txn#6fOJMIA`yuEDmdPgQ4uolP}u(@`KmUPk0{Ub@~a5sCL_)wH9S z=vzlc6->u+M|=GdRCO*0t1jyWHX3M>4{KlAD0ThxaBm?m#DJNI~xC zf4&ypz5B<@KQ+I9Ck(3$KOpDM@T)DC9e9PaaCUMMFJ^e8aoQkC(C=q<0p4iIy;uD+ zOVzr2?$3kEZkCw>6-kCh3!D{An2~yTr~r+l7m#%~rT{g{F<=eNTGe?G`b82{(R*gv z$~roc8b~5|MIEY_4Yw&{0&R-uxZ>rtI#>?g+|c=MF?UB4hPlDUBdlKt1)F+HoHoR) zM1V;WiVdoLXOCi9(*;C-J(C=+4A_3ZqtfJz0^ z?e)b8*~?MB^`M%Duc#(UsZ8!Xjm|%8$n2 zhvELH*oytu3=un`np@ZvCTpJz+G>`3+@N5p%`+H|irbHOwH>yi;mx=3pAXhSTdWc) zUrHR_4N;N2nhTF100}9T;iOuLoP=8AV^uM!Luwa!6*wzCexxQC4~InK@yR_2Wk^mG zIe!b{^~7w}dQ7(E=D*KCk9q>p>1f2ggNLM8O3UNL86JP+F2U> zf0%pofS%L#{W~*bXE4exTT$7|GFd_?%9d6oYotXQWlhP9J$nlhlA@I;QK7_GN=hjS zG00N3qC}~Fud{KF`F+3l^Y8OK^T*ugM)i5Wuj^cn<2a8q#(r(_ld{mA0GZ8P7NBQ} z3LW#cRm+z8&}S{qCeHMGH8{$t!Oraq&~NP@>a^Ff?9jo3#sEQv&XhS@Xe~Xe-j_Es zAXW{$T32@rB=#A!Pf%?&Rh7?&1`4?Rr8zg4O9zf9vQ3F^fm)*alx2604#?Mfv}g8q zj6NW08dLL&-c)9PcJA?ik-X`CbcA2@uyYwr-tRjZ6C6B$lGvIt3Gndz%B1EY?0J%qN?@8A4ElU^mC%|$>ps`pRwg;YTYQVZ|jXq7^y; z`Ud+`ws7GaiDurt>@~o+Ko~?}-&~6gI=en%bD;0N++#qm4AXM`&`Y!ItfIc!Q2v&s z)YVM;Ttg+c?P;PI?O`Fc;kPF#;8t@%gg~FBtL&rKmjMp>M2Pl|X%gtXFqTpQ#61cY5}$+dGr{ezngm zRktqbGI7_#DSZtLYz+44mbXe?USsRBXUv&KMK$BxtyhhT(8FH_7){p`Auh9TeoM|% zN3YI6aLvJMnwU0N7 zxs>;Eyz3Y|tN@?;BzoHU!&P0K9}p1GG(P{idvEvt)ps3+bb@TW^yrbY5LhITGE@z= zj7y2fjp(H)Cu9yR_WBJYw>hd~P^{;@*xT_*jftDDmw{UMQ_EjlJXyXmV`8S$iSm_x z+jVx`GRprUf+nNisK)qfO>n~p$X!YUY-X%d zRvVH0aP5m1FVK0Niax!}A1xKm$P*KSK%_pTEzo3g8Z==X&*BLI5rjczPd0~z^}#3u zZQDjzU3HTqbEf`opz2+>^_p?g?Wh~?6I+;PP+5NaM>8ek%ghn~$r3%&TaOwws>LB) zZyzA+=qNLYf(LJt>nP!d ziM#-%Yj@@ssX!tg1w4e1|8=eP_^d2IJ@1`CZGE=ro+Tq`g)GpL&v0}gG#jHxi60;ppgbD1B!j0vJ(Mp=RB z?`m(39#U!U?i-lAHM4M8{{^SeTww_7Wl4=Wpp^f(< z{*+9<0n*421CO66i~Ub4UQlQ0p3d{LdN~kxt#!Of@wS%fSGuN1C5#U8=iKS18J?v* zFl$zoYV6bwUAjci?_KiCtm{);T3SX@drA$rs_-^>hY^UY3@9q5J##~D|BGWC06Rou zTBp|p58bzS(r?|n*P)(Xszmr2{Hfd47jNIZS$uo`GO|^>cON-RQM(PVo#qE!mR||I zAylqt-jSJIru!^KxO#-~9zpbm1fd*jTA5u@sm5#HvSWu*iSNjjOAcN8>XZ9g%ENz~ zwl4lRycUa()-&*vO;dZBcZ{ECVQ_iOSk+@iw*oE0J+UL7y0V5FL|=j=J5708`J~J> zcrp0Acm`)PUW_4E(a+vXbnD~c=_$f6F@zQ^Wn$v$NYC+4C-tZrF|q%G1ru>}((P;> zZ{1%+W_05khquY#^p4}O8x0k(w$Qm%`Q_%VmH2x*wA)cNV@t`iI-_h5wY1gvUj2+Z zcyE`^3gn>8{Bl0LwmtnWl;lrNPOr_bn^h%bHNlB$!+iwsx}Rnwm8-7)wer@e@h|rt zb-R+aal?itL0P|^%dip62H~N3#TVY>QVd+gO`UW$7K-Oizfm9h&u;tN=i`^#FKzbb z|KS(Eq|iD+=;n>hn}z@Wy3J3ohP7|q{|C~-VY6-z$T7zK+P822FvY7IUvmPOw{h$O zLomDJ)*JluJ3kjAH;UNHXq`mhF2YT(+An?J?l*Bn)lB+5qayf>xvPn%O%N&Ye0h`< zu@CHC=$ak9p2i1l-L>n9JNrRx_4qo_^Xj=-n4Ulr=_9gR-f7HGzVv3IwQ&%$pih=^ z&o)*kCM1M@Sw+de@o8FGX9cSLKDjyHx?&<m-%_E28FY?)c;sWJZ~u!U3puj3*PB`9U;kIU$k?E<;!zRe066ZEcdiGLOL zl0hS;9WdJibT(t=%;pt|t3MupQ#QW39dYy1jLF*8pV%wnt>HF1GS;qbew$*m%L^+mkC^4&}YrUgfMByw#6QX#m{tl;dZmm zxSAVEt+y{(W!J033Ld3GXn|BK zzqE_l>iQgoF|B~EXSw#i=OZSjk67_W?O`7jyXcXp>vSAvv#a~qbCqVxW*@%4audiY zNZG`lzL<82B>Po|Clv-=(UXe1&|2J778fQdVQvXcbe*z9=!C_T9^wuIgSXK(h{@W# z>zW@9?t3|)kREE#tv^1ACGXbsg767DmOng$7f}fD`?A}0QL%w{N{#vE^c`CmVw>9_ z;!c;JiA3*JknRNAQt?(Z<#yo<<#Yvj2k8-bWr$;HeO?ap88;B zFEze4{#xBJXXC+InJ#k3$l7Y71!||%dEb9h*_K%4e`7gTe`m4-yzI>o@0~{MXmNt( zNcU|`O--KRhUBI^@b-?Jz13FFAdH`oWpq8kJi0se%Si!lY*sl~)2KRsR?k2q;5Tuu z3ofI&*+sAq)+uf?+75BrPK*-t-Hi1h&Q77D!1*xElSWu%bzMfgn`PS|R920@;}}m- zYZy1zjB>^F;l1gUOG9kwibU$?^Fbl>Ws5B?$CarRZ&&}IQ1AE})DiOu>+l*7ozjxL zzE-xgps6j!`0R3foS|1fVim)Bkh?cJOf$-+pS*37T zX6Z#v7utnJBM}Hk3He(Ju{xF6<&E3_{B86f^=7a!_0$BqsnE~wNw?#$hAaA&ex?0@ zC26mBti^Z!3SQna&CDxZ$~VPltf|sP^jlLZ24{a<|IlsU!Q@dZi?4CWKRA6R$XLA& z?)ZDB*9j4c3nt&vzmDqE)P zTN`wvx@EXqhPwL2kxpI?#JAXMP>NfAOFcyHP3yF+JPRdY!ZhI99FI}I{yM|8AGAR5 z$DRzTDT`rGxF=(cj7&tWQg#&AY9Weq`(WvW+T;=lq0&vg1-^OY@|=hF4qB(GTUzE1 zdFJl!ZuDrB2{v58=fk#jsT^Ap7-0(B_+d&r6OEZ?qi<`E1>!myX+Zym+Eih{cV}6(@Jxbdt*zp;QrGFj!+(lad=r}n`C3t z*szdlU_nLaUw=DN`f~Dkis~eNheYLZIMVJj4;2f*1h>7f3%EoiZnG?hp9@x<&P%qxKW{7 z9{+t{t*Xnv>yA@k?VU$7G!*MG@p*ZRBd9yj&sbKXFAb(Ft$3Zk4nw!nm4b+MU~T`PuGTY+J+> z_i_!2eP%v^Z+vXGK598~z<{X<;XaCFdPIT##zAAS^37}cmr*BeR~1pR=JsJg0$yeM zn-OL(@M#SjpiK;VvDD8xHA05b5*-{NuzgRX~ z9$-Pp)Icg7tOM6^&UlT054F}o#m3;5asRb;0^2`n={9L^zc$M3 z0ho~RdB>}R`&vwU>-3bdFx6b0zPJHz{4EF&r+DF#)aAQ#m%q8!MkZI^F?Id}y*ffI zn`{pTatX2k8v)CjPhUFv4~5lkzY|rP#}Bkg_$|_@wc{KY?ysN2wmc6fi3N$y)+vGb zX0+AVx!=!E#UtZ~y0vwRiK)L+AQCAzUJtITy4#$&@m14T{VeQPeG2CmHj&?5o7MR1 zOvMt0`JtOO8;_awjdAmNz?*BL)S*Swrm2jIshrEVAMy%CSLj)Qn!R1xuPbfYM%v5V zWN2pHw|OQA5^!Xmm@o$q5E;KcH~+kq6$Q?QYR;M7U76ES8lYRqfV`FMu}%MuCUgFc zsccxdSJw=Z`ChW;`JzuGQlAI#3{QccHDgWezA^_7#iaXkuzua83yjf{SMygt%o6vH zl$uYS=~+(k&h%{;E$I1vcUFteFD}qY7$PGFx}M)gruCbhlQ}bx0RP-!7j2J{k0W&UTJOn3U9!LC2EtrMHTFK#VO)=z;+IZWu3a;@+uKaD-_V$?6!U*FY&PDs>P2Ew zQcvnCUEo0vVg3(Yn}Um-*}c85yQ^yvz-KL}B%>qKI8a(ykxQ0U6gy1JgjAHCzo6Nw zYnPbdUJ+UJiE2q{Kx^D(KALqi{pik=Qs;UCr>uC6&`P|fjd$I1KB5~)bG}nGK*nUB zystvTnM!@Fa*o148#+&QhyjqpJOIet&B?t@K7U{Uw?y^mA9x;!YBXVvyHmoP52dAJ zVF8%#X&k(k)f_S*heK?S+-XgzJlZ+dVLCqX@UE{PZVqJrabK77fQ87AeR9PFY(3); zMfZ5gS9tfT@g&=R5Q_t~&vGd+Mp%>fHt#GW5oyDQju0+ndDP zyrS&Wr*?;BlZ)}^Yn>D}cwP5x2f6;m9n#qET}zvPDx@trm;be}L8XY{&n7KeFqZ1G zRk2a)8rwW@swR)OT1Av0J(PCpQ1hMGHS2|U&jIdWGlDm#`p~r<*i3VyvJtRLkD+Ck zVeDbM$e@q=g&I`bFm2n63SMO*9N^KlEpfy5ru=oj_=fx3r{l0xOU6{0k<;$Eo6ECU zI|Hxlf5zeKBz(+jL*2+-K@4N{slXmtu%Is|lD#0*PPcm@A zdsJZA@lKY)>?xqZHAmuU2sDL=k-Ys$+G+)sxkp_8Io`!~t9(cig7 z=5^18R@RVc7?W6k`*=;paq4kF#XQB^(4p6Ds^DyRB8tPbDZ7>pVRc7v%MJ94`kbYW z!ve!p5WtJWEzxE_5TgHbJOY}DN zai8+_*{(4v&ZRm;H$2S;4nN^?0DMor^+OjBomX@+(XT^sT@q6$k(?KvC5O+F@7=t? zS`J@dv6UB-w4s)OiB}-*WQ0)|(r~rL`MsKaK6l%NQ>mh~?dPUV$5EngNXh4A_R70H zK4!SwMY9Fkz%0n`e$mhwU-zEb>kgzNjAms^2_q^^lr~OH z0~iqJBxeZF&F%VIV^nKo-y-{<+CqM3BUUt_XFJw3QtZaGd@4Rs)NXziBX##YwP8|9 zpF(EO{{3o0#7XfK`sNNDJKC-J6QH3L8>iCVLU66;rwDtn1i(;M0T>Tp1={Y1XUG2j z!(}&!hQ`OwTZqyJyDww4C7g>5UMr6umoa7$(&r7Q8WKH-__3f*HT;;Fb$Xq76{EYQ zqq=^sTse@3=eulY;%`rWz$9Y}gOET1|2Y=*UKchVGxjzgKjMGd1fzj2ReAZSs z`v)KExkYKk59@f;GA-x5<30TyV}$4}>1<^O7jVk7b0Q_(=&B8>Nswny>PJ7d=|9k? zsXrQe@5YTLDYaEmPoKRnJl~!PHu2IGoE*VxXu@e7YB@}#?7as^|2^mSRSa>j!}RsW z+-V!xZ9W8?8u)#ZC(N-pN--)b0t7%UqmMgFt;SKG?jATtM+`Ac)-X9iBQPI1ku0*| zsP8K=_zVJagkIF>c;7Sp5HB zFv=MbCiXT20%nD@@vhj8NdJa%7`LYQth!0)mfIaz1+~r~B$dg!2Aw+x#wY`w3_y(` z{a3T+8tHj?O7tKbndmVd19vNPue{$&pg&A={%-aFE!q?MMxj(B1YYQ9V2SjI!O*to z6=WW1;=4-1xSV^2_I!p{9Q%`TVidK0h20crB?Di)7`8`yk|SK0y2pUN{7A;tfEB6y zA%r@U%aW7<>trZ|2lCjX>>%M!CPH9_!;5sN)$DH{X&}=uFvnptM!iI<_!){X;rEA^ zI3*hh>b4vppK2r*!Q;37(qXjw5WY~a;*UxgF(6Km{ZUkD>tkYKN*mpe9>DX&ct<7n=w*i*wiUxBY7u?r=v9=);;5r8WBMSGM#|@?Mjdn> zVaO~eMs%cIK9WadkDn~Mf=Ty`?;ZU2RAmcflb@>>ajI`KB@nFRm&8S#q;anti7p*f z$(d`a`49$o4z^_{l}ys(J)+DU;xdid0~dC!3;|hA>triKV?!K#qLU-EJ*&@j7+W<8%3Weewrz zcs4^!&m|+OpY*C?{ttHv-Pa!=mVI1)37$p{U58Z=-bM>6Qse|M*GAzVOl}MZR+rn4 z)xC!=&=OloJoVB#$j_w74!JnW}KSapjo?qTVPXD)6$Af2cIl0{Y}tet^ifap9GOrm5&q!;g!m` z#EmrcFxhkDtzkTEt%*G%d+K`&H&s93J%%ztjGPI16PR|ErIMnCXFN@&CyAXK-A_fAxnaQHtiQ2{7K+-3A`y=?C%k>2V z*aRmm`+wvC@!?Xw{9qPT{d^z3My%3n7t%>K*f4N91a-gIE(8R06hBV5^Ed_bMw)If zCJUq)eq=nyi8k&J4(t&uS>$R_2aHjg_{HPh8qQeq<7NTqQbjie)#Ar~t^-^7v6CO`jt4HRZWb7RjT z3{DT}CX@3BJ9B2mRokFlgTek0WJhXDdI0e&0fZB5;=R(%0AN(Y@_d13rLyLNiJL}E#X;m0MuZ}?$Mqui8TG|4GW8E?s+6&l zI%kkrk=j;!(qaXMA$I}X&E@iOFKZRA=H&7;6 z$Ef{Lwimp5n5ZQPT4EL-R8{FHW<)<=3b6|%!7HDmi{Tq{6T5Hf0fxQP{MWd?(=8A6DvF_wRQ?^dQemW^xmmdRWri@Lv!L4SsUH2qbJixfs3 z%lL6Cj=yh2vl!4Eok}Y0dK)(t3qC_O&b-SWFo9wejxv+YPJ|pk-%je?0aWATeYwqH z502wHM%C1qBD80hI*uP!7jXx3lXIBN5n^)BjPij90#G+k)Ua_QNl0hpe#5Aa<28u& zdU|2O!+{m9z!hYp*9=}uDeCa3)F6_sAcUeQwu(T|mLfS7SQio5^xM&ZgrAXLE%OdO zPOKDHGO6M?SL_$`oX0Std?>u%9A23zC@`M-2l(6kGC+EW$-4D`kaH)abq?<~Nx$=P~WwkWMmEzQlS&9OD5X3oSHPfF&){ zSf={{BTB36F$S4Uuq_g$GDUueew)RLZ|%8x=`&=m@ykJTT1RXrt>bMRbHhb|miH7p z#P?VQ`a70sc}slfLjf23QX+d}a9ZreMZTMm^swtgEDEWarkp$EH{U!0mPD5@xUiyz zreWc(KxI?YJ1(^eO!Ew8t+LrR7dyM-){BiE({Am;QvXEi#NmuH5V=kJCnL6wKDx*1cUn_k>K`1|&LjL4mv&Wgmf0YXn3h$Avla7u#SXw+ zDQ`ZUsx9e{F1q+^S8@-|7v?uN>i?m+k7*^NTAez+c?f)f#v;EY8Jp%@HR@b9b0NF8 zz8LhE#Uy}8=9xDJaf3FJN;;2ddC`&0WY@NL45?holRt6i>s{6oQ}3ErSnvG>eG+f4 zAz{G1tECy4Ld-tU%wE48al1XstA8g@T;j3cju5|v%U;)aE2AO0u%>dV$xOYI!T-GT z>~rh?jq~}4bX)|Io}smSE{5Zl(!^zfpY?br`sW*mN1xvw?Kc=PB~^Qe1#d)5N&BgJ zq$~09{%xTh2GoCYl~kR7ZNHEaIEq*`<$Zr5(rb2T#B$ALom5ic zf^UQk(m0*cP@*}BQ}d0}pUZcjiD!ce zMT=B`!KQn*ziwl7ht|!!@9e=0QUxofM9~xdR9w0&qWl z_T9Ya?v;N7V`$yvU#*atE`U6NY?}|F2uOpd}+4H*SpGj4oH64A_NyYj>)w zeZLv&paddKmi=z>Nt}%9b(nffsXZ?fyeg8mM!;ubE-(oHk@8`^i^Zk}x8H4|o2Zbl zBXTMG0WhQYa155&KKqA$a5R6BKm`jbvF&&OOdq$8X1K8gujQ}nQTK`_BX}(|jTqk< z2ki=Nw_gJr%F@Brdj@QbRhWy5!{v<^bMBz4WkKoG?EM|hIVgiD{Pb-P1O&|Efrvi! z=u*RjydWaLwU#vj>!@L5xB=eamGfl29kqNDaH5Ri*3;UezGBO)rDFSm^0`N|VQ# zQ$H{PT2~ww^lg2TWLpa-vsr~YOJNXpsJ7QJ)n#l$9D-${-Nn{!C{acap@&L*A)XoO z1+R9olY}h08HH7c5lZU`hL`zI)%Lya=<}7IpoU`3M&p+lex7@*$13^Q3D*uW@d7Mj zAnh26s}3S;+0v$$X2!}Uu>R{6OEc|7EJ7NuWw`|aCw!pv$fz>r)J$9=>aw3zni&203mXD`T&Kz&%=9$VADu@WiusLP z#N%Qub7wiT;PbDSFUFP5>Z(C5 z$*U4IL#yK^{y&S8Hd{HHH%i{K!;ThGJV~EqB;SSohx(Qc_mR8&^L9YBo1Et(Hb3F8 za6B!pupdjnbEq;qkLbD{G5%G;LMe4{#HZ?H>cmvmH-L|r4q3v5<1ik(KcxfRkT;E3 z2wJkpKP>}gwislEIF8PTge zIl|N#sBIIVhFnWDuWHIE{H(HBjR}`ly?WK&e>T&HzPv?;K?;l~Ba3bZ9u@4CA$>Gu zdai@srR!5PHzk>_0XG(hn6Kq`rbQO|>-6&2D+Yh4*PG7y5vX_@ajPd3t7|vy&JURX zo=zT2dwZ3q#K@4O!HVyEC>oR{0AR>-rr0-V;P3^R3WN8t4(pSaW%FYkO z$rFIf6*IQ`<=~NwW~DuIFy`@FHZZKn6v^*M_eFpku;R+GU^E|MMkbEI+-oKE)|H8) zR)3hw%NBse$WJZUesOw-`)8e-V;OPC+YVo+xr7|4Srs$-Il)?UYrJ{Ehjv z2C*+Y31G!t$;J+@U^R9!G007N!K;n9nwBoK5#hDu`I^O`&*G#Ag;A_{oz4dKcsM0? zh|%fGd{NO@3i{goV6h=LUFAfd3wZ8RAcOu#wc1jY(g+)@gK;5+Gqn4#-E zdH*`PR#~9!eU*VywH5CkZUuOZ{A6_!;DRXTjjX=!*`INx*vVMx6Ur1_r>KtR6ebeo zWknaji+7A^=%4h}Vjw2wnXF;sxrbnS5Vxe%A42*C&v*#!!rq}})eFDWYm@%V+ z>x&Ha2v0yshHieRp85;OoV}61$UZP2MPk38FT&~tu0>>FBa@tv<6U?pSHYeHp$8Qa z^LSigCQhDg=IjC0A@(=IwQz1lb8D&R;qmlfXFe}AMUdzJ)Z0bpEHRkI&MrsUE-(QptG&xM#^(pj4NQjP$#s+>|fl?piJM{oCn!WBit_|c-t=* z_~>wEabA+iURW{Fxiw>VB_rdqOHX=J8sD(=Ydds2tBwFLi{=cPUnS^{6x6)vdHB&W zlU$o+i#FM3TRV;E4+o)t5d01;13Y&t=^EF|9;uJ(DWFKx;?P4kq&L@D2|*ShTCt+#Yj)uB4|MrAAjs%^(u&ac*iRp-rR>!f*CRzU*b1 z-ZB6OtJC7-d2Y$9^7oIs@rTV%+~~Ei3vNtIYIFj*-vTuzj?gA!Xp(3leq@yTy5+`b zolK`=IoBC=isr%JJLl3(eqyrP)3FvCGA6y9&>cE$BZpicVG5qk!8pwMS&m}3o_|C-# zW#1q}M--Rt;=s$7-S?2W_Q?ICR%?Pp*93WC4)sgYqXk!cUY_GfE)Yj1*{Mgo{Nc2uYFLzb10+`!oRRx@XY@MKs) zv-?rAW4G(#h=nv=sx<>-SAgtCQI7S=;?oaqshF2(CzN~_FU&3i1n2jde6Bz!k3Rp7bY7Wm^M2mj-y4(uVy_5g9F^S zMzA$gZ|!JL>>USj`deMCr}$>!(K6yhzE>uaYM!7>h5q&8#U={FOZd_J9-ZPW-dGrY zW1Hg*dw&VmlXRv#{M1B4unf6-u;0_ddghzp%^?;DKKE-Sk-gCUr*gE~W)RqMW%Tzr zwP&0}7N{YyY9^|SH8Es4xJ^VMQl+O;$zpb}N!z8rnA6tfZ zKXSwQVw=``jyGur)|<2KkBM#ldQPz(+q%ic4V~PZsy2-Xz3BD%!1c7rJ^^W=d)_bV zpYMLs^N`!8sY^6JUs}tcHVonekga_xHcdw=pNV10W#u|=%rtdm%`v&))R(L~k?#h@838O2 zPL1vUF7f5(R&TU8X)hI9w?0C3qQU4T0Ja+g@S9K9zTqaJOb3EnO|*yklBkrZ+_xLdUTk(j@6 z0V4okBPnsMKCR9Ono#5c#+Jj~zco9Bf**5%9JRw(E%B2$(yoe|Y-Z4y7`}zZuwfP; z-x|}8Zjd)W%=vye1ks0ziufm%^X%(tYwj~;M%fTSbBy=z8rB8x23|rIWX90vP*P;p z=*ojc9?cU!f9;PK7IJ_bZuWABPK-zUayPy_t^n><;t7CuqNtW5fWdW|*I^S(?$8ZQ zqz&4)e!bq_+%E0ghYe%T;m6Y`hRv?4%*V}?_d7&?}AK=_2CNL`*hTH4JCQ+ zeV0^MTbXh|+s&?>*-r{LB9L{;J8syp;j!~8=z&U52(&Y6`1jxM9&;+|-M26P&sS7X zSoMo^Wz>wtCug3q-@|yCR!Vn;u{+~&EQs4=Jk%j)Lk|$(`hOPh9QU7Je*X#!exTpG zSaEk|@yTu5x95Tu#2)FXFm~!tY--}}dX{HT2$-8KHlmrzj9%*IX4j8c7BzGqc&7)K!U4{rqp z?W;%*&eTuzfND$zJiaCWcl|#_`Yn$BujksmEh6yOQ8CFzD+>wC6@@DwFC3?@u&pTsM5uv6D>UtmVxIV;+K`}Oz#x#atCCM_xo?}o-p`-9nJ5*tI&N{>?Ce} z`n#V0`al2YFLzh`|N0ZmZ>{t{f8y`|Jy!YZe>pk-{QkO8-v8y4|MS}ls};G#|Ihz? zQ2YP#)&0Nn!N;Cu@+=G}tVL}BW!{Q?4ZE*Ll(A#&+4lXd{R$WU`{d5psMALw0K&Pc zK~Vp{pIU+XKlf#KM2mzrXX{@l-R*Ss6m8 zF6PH(Ww$RsHY$#A$hb&g1@if=@)0}~*_#WEVTtlYN*HP@`BPHD!0RO*?;~!Or~m*g z_AR58&^ItB0XI*ZgIH`A>GK2#;vzni>pJ}0ASxz<)#i|}6|pymXIQ;z6<~yk*a-0B zmjBtRck`xAr{iTnVR8)B05Zvmlr;Ok6%dRZP|8mQpUQk^B5$etv?nRc{(fE4wlnSB ze@?QE6f!i_itXF)dr?JlFP1PyO_#^Fo!qe*$UsL~g^2js;JwJR;MDGo3X6wYjAuv4 z37bR{%&4*{dvgk9I*c-ss=;vJ#d05Nb^nI`J!7z!115hSt6Iq{s`Ij_;tfsP4uMw+ zeDQ^zv?6ol`_QUnDm*I3edFFxBgja|l-Cm{d3aoqeM9n7QXX1D6C8NmwQJWr@LkH= zNpw%XRFRuL3}hQMg2*9jF7m-)PFM9-nW|-4XHVCnx<06P&1D zs-OkRnGU71H#I^e;WmwfnTM0+#j27hDfp(8><*>YaHJcxPWDKodJ24DjWPsh%5g|X zvbC~C`95nDi=uL@tgIHZ1yE9{t_nxXCI)JPJRsT8l@HxqSFFhV!)Qol6TtSFf}IT-8YLyntIxu{knZHw-@o#=XugMI71Y3Rf@RA91@ z)r6iyIv$edqVS*PqKqy1PYycVgo-kM!Pz9Owp&?|V?c5iXjH5og09kp6{YU6I~(z9TC;oxCbq)d)%H+BWd zDu-Jq2WVTTEBGnF~!MrW6c&0t09Ewy5@!K+~~f1gfMGh*Ej{DiI)XokqK# zK|AeO7C^MfhXvVw;Ey}tDIxqgPE_MnF9KbPFjAhT^JP4u%~J`DzK4OanehzSv&?+t z>e5_pN361aHSRFZv-Yh}4f6rZ`gzDzNC^e#_4FH7Q0qLWhnlcsvUlv1`E`Gpv zZ@1zdj9xJwq=4?h1VMf_mGr{pEzi2r$W1x;>vUA6gPcC+ricLYixV#?7kzy+K$$TL z^0ZLL1`Ezn2Rkt-D!bqGJwrh%@)5cqrO_0+Jjmn2R`Acgt2fq8A|6T@m41Z6m+{yz zm9(o@_37htsld!|QQDU2LDxooTIT@&-!7lG-}MBW8Rcbyk3MH(Cn%FCHT9X#{p#O% zRfn49^$*829-jy)DWyg0*Gbo3NvHaa7A@AG^EW5nK_2n^{rebH&DNtfXdFYVQG=Yy z5y*1H%6E%D?J!qx-38Ab!H?|z+}~1j*~ocV;ff;#{gMNa_^{~PuDC-RgFWYRx#9v{ zaNEj558qOG)V-%Y%#Nd6FGhabQK#|uceOiCwv?ez$l-|}UZ@uynljL$3dKNwW!(xF z>N#09CRIErqxEtF+H_X+MdIfdFq$8dHZBqdD(#AK>`j=Kid@ra4v-M-S_K=a(<>px zl^iq_cBZp=lkRmQQ1{Jsg4x;zr?eD=I zmH8HF`B`XZBJC4^kk*N_5^bgA2Js zGtLD2h>%oEfe?s{6gc)vVbvB`xkov;>I9Y?kV=962qnW&UW5u=7}DI=_$(y- z_T-P+s@DSrli-^*s^*DH40--pN2_Qy$YFd!RtcfPpnaUxo#qSQxlejMDJ?a%hhDYT z6$BZPt#m$j&uid;0jP)<1Wr|1`x#)*}7bvB_9_JM#``E!sKdH9jSl1mOzgX+<-j=9VG54t$Zeg(fZ>s-e-)+7N=mfnS#%C3UOF zXla(=cAaYTWpRh?CDt>3=^CkqNb5dUp$k4c|UYyt86s&CLnjJj=(o zoG&XMzUi zcPh1L(ctd~^RF~>o}?66;EA`neJ1j}DP%fbirjlAb)-?lXc=*W0Xlht62a^Bf$r z4^sx(lPDUU&Umvi_DrjOVS!lYlZR~;&&9>vp_7gz+eRS!hJngd;pa(7b_pFv1{3vMX=k!XF>>TvQ?_sYo0vebuCW+1EWNX7m5QMOe3lt z?_v}u2ZlJ9BuQ>?DRyU@I)$sczuNT6cJcV&SfTw(%ObJgiObS)iuisMOg z6caEoUs7I6Whv7zgm7nzbL#LH?(Dk~`+46Q6M{b-fLUZUO4`M@C-gOhoVkxmlU)gm z@3z(`e!tE9Iz=P*!4ay$z3giH=k%Bb?SGU zN+ypSIdZ1oaX47~Kn>HfNXdf>%=~8ohLu0vHc&%TGp9Qk-AUpW?hx~efA%#L2wc)c z|JfI2@LOlq+K>IY6n+!{_g;6qMT^8U*#Xk5cv!@7Db<||lg!~)8kGS}MlouKae4aO zt1leL<7_r_R@}99>)@okGO)RaO0^9HDnH)U&$^`;yca{ARM0N*J=I)z1-E_3A_`3M~&^KF2c4-A_3 z$L!V5l|ojEUV<+)fhn+Vc&3>bvBDA#wCA^ zTTEDVFNp|?jX0VDRTAMI+D-bnfzLu^(J}v0*lQ@M1 zDfnhv0(3MQ%(E4mg$#45>ftV(svAYE!;vJ~Kqtgj*-Y~Mkk*ImtLYtB5(kPZJ_{}9 z1BY|%f3x$iFRV|sxp^}3?-F~WKAuHL*`oz=fokL_(utpw3oaxTMM3Y?WRIb1J}q9q zAA+pW&t2?&KWaTC&)g*w`2?*ge-%vcWM`|tNjQap$)7K)Q3J(9(QOaAMRC|a>XVQ% z$bihTk;LdrkrG-+nFkMMf}L1EPcHuCq#h>A)rm68Kq=_Yjj6zV)~{dB2%0&*-u~*Q z27F*+bPbvZ`}XXq%a_Z8K3WFl`3)+uWFp5Lm`EC5DYa59H}<)T-=)1`6v8y}neUUh zAPR4J&sBuMmSP<25T}2BD?@$<*A#gyb0>JCBKR8ZC6E zKhegP)6m~2EG#^pD504RVvhbCTw7nGIq%>9`Gw6u6$&39C4(`H`ln@#n?**`jTVY*+@ zBI2$m^7(=pdrtG}>O<=bs3cyxovFTHW978P=p&q|*DaHF%%^c7bew)favY7)x_DT8 zzWF!%Korf*H&Ha#|782OWR7hP}opbJy`y}Kmc3z`beISHfFB*H?CHad1 z0%w8PNX8z^VYe!%-c1PILlJRDHG0Cfw;SOD-+x2it}a>_I;StC!DG+L zLui>JQSXJFe*`^7`^ws^6VfMssjlw)e7Ne!s(YnHpSid@sy+xs4K;MH$OG-UurZwm z&0RO{E7fNQp0ZE0fb#WJ8I?&$S;uJdre|L{^4R*bPrC1+=AVMOaG zuU=doqFfCoOrXxc)ZKzQ@=H~f_430fCIfEa$T@NCJLK-OL3kHO-?)e5a(UTP{gBU2 zF&Fo?%=?P6!L!ko^Zf*NXIOCt7d&pBd4Lot)VOyTG}RiNFMkOPB&t$O8+QzUbQ`(h zk}HwBXX1zLK$~t*2E1D^DxZ>d$9#d((XKB(eA5r8+;=IbG-L*maL4?<7SX4c8-9Nk z3Z2t;>V)-4rQQKUbZE^gFZ22cfR4^Bq(Q%wy4v$shc14Ykgax?aoufMr)4dyqat-$ zvM}{ST^t1raUHZb5ssIU-%*Sa=wb{xEV`c?fpQvA%VOfhiN{~2Z{wLCBB)$>6{pG5 zmghF*6YSvTq#nLL-C zDTX`I`A_T(b8&S+<}sZT+5{$5s0N8`fL#_qfhzUSisrF@<{E(QkiY^4h-^zekQCt8 zJ6@z1;3!I+I%%NNzPw_)O zf>KejO`Bi=fr)}r|4)y*fov)UF^4EZEM6MCbE_(MfU(I}dlanKIPj%l8;qju_#kB| zUn+ievft#wiN#PGAon2&$o-`EZatIx1qcxey0y?^>%Y!U--VW3V;TE4IE{L0e>~lr z<-iN2e-IA#b{XmM`4sj03G4_TMQfpI4MoD*;m^bFCF-KSv3q-NZ-h=1IK(2ysgs!3 zGiId5n6G8d^-)qY9xG~5N*)dnc7EzqbXV<5?~87Rd;W?qnt43^R(tS^;g447fipK+ z%IvQAE6SJQ`7mI65@$Dg0elY;9HG?@|Ld>0QEo5&b%x!`B)aUFFHjO)#NxxRNn3dU z>$a^&qG$n^;_ol{=%G#s5b$g|^C9zvtjW%L3J-q?o>1gH)gbX(1C ziR}!#rLL|foE~;_jFG28&sh8pYRVfA+!K^}>e?$P4KGrOTDiyV8wv%ny;Gj3h&WH- zps2;~ygru(qu~kVe%z#(iatpA%wJRt1KV=R*slW45W^$Uu-HA< zf45R@+kV8K!r-ZY0n}qj-LQ^-PWD`~a1&pt!&Q^&i@_jW0&$HCvE{BJ#11BfTCuP6 zWcIr8-w)c@Pd75D3j`pMa>9G%+xvNMw9q;VTvrUPn0A_`?j|U|@Q7~jiIsWCr1kan zRj*TY4n-0QeG@oq{V_O{$)CcJ8V$PRdLRT~RHBfePXJb7)uSguxCnd$Pxp#jn^xZ% zQNfk)4D9wD>;bl;m2@hWfK^5BJNf5}hQ%n)i4*?W-Q#kL;yG4wrZY-zMH3&V_hXQf zPe`45OH=jb7`QPR4d{5)^mQCxF^tY-No&D!Bzf_ECBg5aV|{|FzW9`tMDaHuK6@GF7uLaYHpTqP|%#?L){KkjY{_pDyCoO@3|OYZ~7q6 zW)2m;@2&mZZtK_uTRHbph=wJa$$xV8Y(Kr9^!=D9JV=*gvV=q@$jP`24up`i1&jRD zrt4De?AyF~f@?obO%pKl9Jj2|VRXJ~WLb~kd1FxiqN2m$1a^K3172A5pF#C}uW>|O&;j3u)$by2pq)pJ@BgVO;2c6zFH*B>75Y6|lZ#xoKO&8JrR2QA>^ zDThYnSv)VWNgwCPWVY~1Cq{^Mc;V2FUuwm15d{8jXO|0mx&%U)2!2%&u=*BzpQL!( zR;e^_whf>8jOVy^Nq7fNk0#Uv?9Iw8IIeSUaWRTA0mb|Qaa!XZnyo!7ox`i#0&Q42iAx4zmL>hmGjjHUE@Toh(>CtHx4H@ ze3`Sa`gwVI(E&ShPVTca#?=FK$(o|jYEdq02cb8obKm->JMYMKSBj0xN5Q+;jDp4*``yQ(wgEoIOa48)5PGKT!08 z`TZ1W%qUtqU)4IFrSW``x!or#RaMnu-)TD8yo~LS;$0p;Dw^BR5asU{nLp&x9k_5~ zAW{q&@*qC@*h-;9*UrzXH56+pEFSIZy41&+#p~+^wy6K``<)3a23`)e*E+j|^JCft zO{dt|(RHK+XYkywb^LqBe88N+3CmB(iN*znipl=Bz#N{?nN?-;J$V%d=M>kLrZy(J z9Y!PPWYHDjWy#}|6xco=TcK>N&V?;(Z~A!Tz=1K*1OGGtH7-Xb(Pwz+L|E~$!z}!g ziJl!tRe%1xz*O~xH~~NM_;{8U%a%}O*ek)~tf_Y0F?PIGeDCG@KZy!>4 z?0WsN(A1!t7u7CQgYYK4=gKdZUF%FDUmUpzLo9oK&;7~9_T|(Q#{*g0tjh#oHV;kA z^@R1JE*6?5FJwY1(clzK`j?(=|L#K1sfn^?!JV-|3hni)n3t-6x|p%tPB2iX`~grg zrXoARmA zi^xX6WU;H}@Byd4d8~Jg8?{gptWJ?+Zev6}6M@ zOO1cNB%(-me|_Ryzb_`SHtA1hn!Fe`a>2&1!|v&SX;Z7bscRXhm%b@n+I4#Vz(~U& zghbi>_X5IZ9e3QFH`&u}!Hd!nri%h2dyK|3?V{B0AA;Z&MYIeEdI!$Y$+d|Pg7xj| zM=XU*=uLS8IGgDXF7%Sa%#Eq64W@LDLQICwrck5{jr1@O5NuMyblD8HU$9EwmFG1K2&V zFP$;9+Z(?JRrltOjHnp?e3MQTV;%?IoQ7~e4wZY1gBZ!Fj;Q_W$qqAk=M_n-rSJiA zlKkIp&OHn{kJ4jVbA0ZK41$n1<4iaCS?4DH! z_f`~+6|<;$@BmF025hrPa!7A7liGVzvXO0w>mfn)Uex&UXrApzz1W` zWfgo(p5pmFw&eI&H`sc6XkSUD60Ue9eK~MUEcAA1xoyjqL92G|*pY{TV0)t-&lN!~ zWY@WG5StI8r9{UhhLwy?$6vB4A%`MuIThidUzh5Nj2$vUQCa!0=kP}oQb#Ch{hKD=$KY6#JiOTj5sjyRjr?KW%G%93l6 z7Sm&4*0ibb8lYjq#43In6;aVWNxbmunuk0djWt>nd0Ar5xx zyuG%-$zlCoinO!@Vgl%@(~3LOxGMsJQ7xu**Fl&!r15krWCsi*8r@Ri_*p`bWTXck z;@EHRG#gAiE^NP|Am7pr0Q<(5*q!uh-m>Mb(=A8Ql-=cV&n+D~0704*BI_Fbm`jiZ zA-(isy*()Q5S+DYC}hwz8g0rKl4&~7zCpqVs0z>Ow`R)Yg^ECvZTuV9>3`cr&)pi}v^PhaKzzMEH$~FpiJM)%u zKmPt_U7c^+s)Me9@Ed_&N4}<8ab%j+;!wvd-1V-B1~U@ zIEIo($UiCUmqAVyeU>f)5QNzwL$Y>Uq_W%ol6^PSYc#R<6hVi8)|_6uSJ4MQ3_WJY zb98@sXr)h(Egc}RQWVBxlI@WG98@1YfAz6l{liY%Z84cTuS84FU9&p+;Gshm{5dC> z9ce7h7l}bQ7vnAwhm8nFr4$AxM%b2%$=CJ)nUlrQ+3|7mMR;A`;K%v(T|$KIH$m{A zNho`==X?fiFDUsK-SH7DsGa5`#`OFL&Uth+Znjs=a=6}tx#787M!@GDoIT$)63YM{ z`;4jn^2GtKt#;3E4S4kEk@k|-vM=b7XXPwD{LP{%OYh#uXtZ-X$mf0vvuMbc{O)VP z@~P>}xq$vqs>}w7UQr+{T<0Td-G^M~qL$xxis@L`&f&ztuQeN;1zl6y8xYj-c;+y3 zJp(+M(!b4~5Fe#UoF;`KwtY@$1G6DP8k8B9GhHWk4qVi%?V}uoIt>d9!%*SU{pY z1ZmsX)-dQgM;JuT*-5$;5n4sk3=+_TVV^@&|A&pu=M0wcU3bA~4-W~Bo; z%Onrp8w?ot07?@1A<03W>#Yy6BZhl1JYiHR8ZDVS5qp1UP`RS93BZ^jgR>xfZpCi< zSq??_f&WVBb4%FrK6>;CovZ^F$*{ej$;`slMN&*tZw%UkG)L5cz4PfRXFm4$IOT&I zjfn(Z>YtG>JOUjb#peCiQ%qAMXoaij&YbUF_?VTbT_DOwg2S<@*S!HNBnt2t^I&v& z%f^6z0%Ztjxy_Ywe=@+`g(-C_Gm9V3xwkVSYde|P&N>-AT!6WrI|Jk~z-7L5Ow^S? zvx$5M8Dt;ne};9{^*;b17S2OHz$mYb^6;}kznJvS-`P>Cx8>sx-{v?Z0%(t$`pPZ6 zzhJV`gBp|(yJpStDBGC#asWRDb%N#=kxI)iqPoeZL$q_6k%+_A2#=h%{8*p(`_%0s zrWDH6dDsS{^e>~wiU>obV~qHk<%`vu)OB<#f{meDu%xw$9k%45{q!-`v)|gTcV-*x zG-1oVUs43nkq5pxjp!#|&^%Zw3d?CCuH(@=kJz9gdpD%egfr0KwNDY3R6=)l5;}-t z&hX80clm7H2mVI}Kj)Zl}*;QSv6L|;Wg zFVlM>C#N1+;BdlExe%wf8egPtgNh8 zH7JyjNBzDy{O{&O9)!Z-F7+3k5#`@`DRzq8=$$|n9<(?lL9_E!Iwy^K@!ppr-)Y-7 zUBEPE)SX6pGU~;P9PYiyGDrfPl!YvF>2ML--vA`6N5G+9!w-tei`m&w5X2DWBP9Dp zdKDd?p$z3D3-~B1ot#;9NG579|D-1M1(-QbjTPwMXJF;&)M2r)!#{GOMAdIKv}gr4 z?*J$r=R*yVY)B<3{N|=e`5=F#?HhRhZ*{3c;(CzW=@|!q|25*YoePcwQ?4Orj*d*t zO(vuS+620e%_ETfANJlnn(MuNAN4$YZ_R@yQ>CaBO)?ZRm7%CmBq2>QB}I~1X|Sb~ zp`tRA*_J338kCvLnMU-xx) zA++**)C^N88v`$+kJ>F`3;@=~02lEsgPNvz4MoHe0*>I>eXO_0qOml4J-57HaThwW zKsfdqziPkMQODor5&^b9f55BtG(Voc2;J^DC=?JPj0^-#Z(CR{_86ngK^M;!`mu;X zR%fqN2GukPY?L|22|9qR+wCUFtp`jxds$dd80^ZY@D9iJ_jTwHvu6rlto#x|qXw0wjlT2>w2T8K!4;GxQv0&cJcq9AF{7AwsWr_5Jy{Z%e^g z`Y=|Zq38;my>;+mMcE%fF&a!i0!Y1srt_8V84(q86b7iou@rdkeRBX7Lk~L%i?v9}X9llx{;oI?8pm!l?r~ z^gVx^oVdj|VBeJ?Ae04{UHYKx$HD{8K6<_bt6=Stg$qvH$^k<(RX1qPw3tkezXb@ zU7uL-u3ZovBlAP4E&!0R;;tAt(8`c=}fXw8vx3g9NZ>Ng-6OAvRudXDdIt5cvy!P;8gS#8K z17v(;3Z#aD?j6ZTR)mp|K7O4T!E$D-xCACZgh({b2ctvCNctUC2T<1rBlgerr&fe5 ztJN&YJ2>b=PLmk1q2zs!0f1_nqCHkINpXo4cIya2R|lqC59k zBlDt^l{kDIS=>P`b4tVU2^9LxK2B&6d@V609@LN3Q6j)LTQhC5_-+`QV7w6;TdG#r zLi^xn?2T$>iz4G#Fed4(j08L2!J92x*3-P4C2gUgTc*DeEf%uE=xMh1X#a^2;e8x_ zB=H5XcH>1p(5Sy#@&sIahX$-c6L8X}eGftXQumOk+iO-VBpPHE?j1*W2MNeGkot-c z4ijq=POUP8sSW)Y6pN_+dV`FMmL;WD)RdrLs7 zyen0H$UhJkN>mP-SBzrK=EIP5tW~k%d-Bp{j^Z^SBxyxOMJtw|e-{ueih_u{DEkSx zh%;*B#m!Ak900MLYr53w&F#PQ{c8HV{~pW%lwJ57mx)_{8oN;vcD9H|KdrUx@6ZW1 zfKsTClTP5bb&su3h;crSiuS>iCr{ojK8NLUXn>@s`u0`3U6lho5>UT8`eP|L?M1?n z!TQI*XEoK7QJUBX7?02L8Q7w3R{L%3R)=Cumo&up4Bp^QMH1wEoVu0~s2kn=3QV7H z1oZ?TX_Q}NW8B|${9NU+AMd8qQs;pg2}X_tDncwNR*>oer7Ew{U%(LbVfObh&ynJr zs7`*@r18cEDqt&$b-ODMQAmI#hdfGw-qHJlVv-=h#26^;&sffz{z5!FqA|t<#7Zm} z06}rk#NT|@f?@{Y0B(b0>&%D}A2Pzswc>!q0&>fpArKyvcXTU4((bnE>QlldTLvQN zJy%RHo9}O^n;!zHj>BeOs}aS-p#~OifQ}$HWr!-+Eb(zeS2)-X?C$|S7@A;ecnC_u z5hJ<~7m?M)bfS-Xl>ms>j~vbtJef8hG<((*$`etU*psO*pP>-~PEVu=y#BI-b-f)O zTQ0^2=QY7Vy#Q>h+8>z*`vtac>WT}1$r*At+Oj2kS?r2b%x zwli`)fh-QejG!avCX@jR2}?YF3CU082uv6&Rhlum72DNKcK96F_XLb(7|0U6wel#O z5xw;R>KA*o|J3^~s>TlEHO*(+`SCQeBx^f)jU>gT@5fm!MwT8K_63y%_r%069Qvf5v}B%4Dc~(K_VYnvc!j<` zLshK=DZE}cTov2<+~Sakg0b|pzGuR6H#9i3%NoR9^d-iG#p4jNZ|4v> zpVrv(x(iWzYFGv9osQrfVh=RaDbGw!CDa4?-1;&H?gDhuI3Fi$qV%b5C=yl55!a4T zK!}VznYFiGpfGsem>>)0_0M zCGfuyBF2ExWn%!EshizRKsewJtHhoH^q5d$UJy@%6ZW&KoVc;v7yW%Uo!MIt7I{2@ zogt5ZBL}2|k67u*4C6?5v4gJtHV4%!Q?GH$5%dk9BA^egc@VHknC&x4Y;EKmjC`OY zQd>Byxad|g6~fau)inr=U4pOUJzrS>0=EXRUxBK8DENu7xzlINNdKs9dk-2J2&wrU z!R34B|3(t9;~K+8RQZa`>XvX=|G1>=rUV!V(36hI3MWBmO-B$#8Fj}s)y%vHvp!qU z9TL<-X^Lm(&a+5`b$?hRZbQyn*)oj8s|Xg~$jsId56S#29QR<^0c?x9fF2bYqhED6 z<;}UD_Q2b!RX8A5b2k8a>uYR(;TRLIi*3#&q|FaM+*)SXo#1JNbGgR2B=!Ts)4f## zr69mRcsM16wos@1X$C@7n&`G}m*2gGIe)*5FN*`T7&7z6x;6bnv$=MXe-&qH5hiOK^@?^VzP zLVoDue8zMZ@ING10$_m#yxCk&MSI!`v=~W(?GYMfhz_1Nra&A)JJ&mS3oHbPTF9W$ z^kC6i&!qMaL{eJ~p7YlMnW4%sz_$%K;7^==ByLkyW8+Fn4rpLwN!c&dGR#%itLD09 zuU8-j6`t>0h0dF}k|V?tiVRz*7>r7FIOJHzHfIi`^HH*neSVBcgP^%bj+qH_aMuAN z?5%3_JODo2E)*>a2qr;*scw@4-cTV1BLWa-O@aKoAM%Y;PxnmT;j~T@d7lFpNXvt8 z>oFRRDA*w&kmzL053_@IZuwfw+*pt98O2jR4 zmBz{uPJoyuQXG6z4?@Yo!}DtL0_BtRGZ&e{h?MS4%tWct}*(Hy~;S% z^Uz5=Jq)rbQZ0zYsEpXtR_)W2M6E=KC}&Qee!Htza+M*%Q3Y}vQdE70B+R~8;RYm? zW8AU6nppwn0O5Cc*T-`2#r5t+eKix^N-IWiF6Uqr13-a%TC?kY5<=#zkCg{o8VN8ng*v>KPutCipR0E&@RD|`0Z4;|p zNs?~|@R{gs5)BAGf&e7jM?#xu@@D-)%-}*p05k;huXKKCN9uJ`lMzfeAsB|p&P^hG z1@r>Ax*3@{<~+xas{M9e?k-`SdvL{}IM=f8B!D6NB6nf$o>N+;VVoEQ#Dp~k0SXah z5~0rwgP~w%3=>V9C75OQ;mQ{PA^>(iTqpQo3mB$uVj_s`_G5=AV2ho$^Xk>B`j~VY zh8Ul41PBcYR*H>P$4mh&c-!=-BkkZX5(ABgdTs#e`PLqpYd9Ezp$?}PvBQ>}%Jnun z-(M_2tRa9hq$ulj^Jd)h)F#|#f|E&{g^mCUmVC{Z-=I|PvbzI8X?}*MV}=jOdLB`y~2i?v+L;OlJf^Y(1NQM;$nRnHy6#oiTI`-qlQTL8RA7U9uigI?hlperEa|JU1R?-)~ zeHb7zV$b6p)W~iF(99C;VUYs^5W9-N_OPd*Zy_2WatNSTkO3}HmIit{$@&1>EQZr? zEurB<;CAme{h@9E-$)%_`aluTa{Ge#2PmzlV_YA)UjUkYL^Xlf{nQVGj}#OMKx(nZ z_|{M=3PMF-Upjn(%^-3hvO86TT$l$VvltECKwk9_a}zEs<(5i(3DmI!uB)xo{RYc< zja^1;`}U_uf&(t1st-GMVeBG1mf;P7W@>=mFO4eRK{$ByvhqPz=^LP>7w5iS1IH%< zrpznr8~VhNNk10R-0&Fv|AqK{`W)aCD8b7Uw+PwMD)gsXSXuERAVEnCibt+Qal|LH zRSFEc;9+!B)CXlIeaY*E>v2mfF+-K=%H4^f$veeS>LEC-$s6r|M9SB%M~eARxMe{4TDj))2$i^AQ;k7W0)P~pvx(3}OdM&3#>o@_p5q||d+>d~* z&V1zVG6pl>523>&zkv;NMA7%+YY0A7%2(#0f^ z4SpmRA~q|yjQ_S5wVe=rtmU@utDSjFbtA!A#0A7mFRg%ksV#lr{)!0jF zz6(*yT#5ih1u+aLdM|1ZfdogbmlXKmtg2x{>S}8PQJF$MdkDKM(73^q@5D7~OFY+# zqn7P0rh$a)E>J^|afuO%mcV^DO>}zz;u7qHInLn2me$(GqGs|~WY0$Ku9%h0LA|Vo zQTL8m?gr4j*~tZ@Fd%G1Op@A;?ZZOkL&KD05{NrU0u6N3z~09~uY-*zuhYH%75u6` z4j#{uR~N4}=gPy*d5M}wp)ke^)QM0?;$ot8vJL~70pPDiKLB+u<3*^PgHSi4Rvl%J z!>=x$3!Vp|C?xZ*#wBWAABPLbLmW&+GIoJf;yI&4Q3i^(*I;sgJv%g11a}HjqzN2@ z`j&}U>0nNhBZy(LL`eIXfNdRJ-FtqPCIO^G2d)BiW_{)^-FCD~9Fux1^jw1wk_RE2 z4H_6h$)*@vt0Qpo8ytY_v-}{^N5GO|&Q(#(ixHXvJO_iy#<11v*RG*2+rn$B@sThq zRAs}oZTn>am3tTU2PLyBVCt#gLhgNso<`%$k|sl*k@aXXhR>0&q3+o^HNd)xk$A-c z5fv~cML{~1XfQNZHcC5EZ`&I5zMz1=$Q-e&2&lA2tlQt$YG{vs2T^OusMk7ZxDW&; z2o>p?!=|o?#euP7Z_>hs5lmnJliO*{DN;czKxSGlVH<(;TKy^amMvSd#YUIQyL%yZ zayxf`*dgrbDd?W)KAg4?Tg|ZmZhkTssi%5#L1-6}W8vE6F@GE@ABn1$4b0Fnj`zg?Lh44Up~Ki7=rM!KUFkqtQN;Tg@=l z4fnrb#hHE81<easSzz#`i36fJw3Xg(kx(Gn8KKjkZhyHnCXZ!b z7&M|C{uB8M^h20KBoUlvou1`5yNE!lPd&4PH8aGg(dWN+2t;9gOz;9m6qLMYyTuZy z?|t)02z7z~Geb8wvX~vXIlIgv;j78^!x?J0(+pg&p)|a<%6J(fMWWA^ zu_7yjG@7~w9!4XDUeeaT6?8%%G1`FPPgUv(p`F$Sv&)tYpWe1@Tcg7~@sF^Nowa9$ z0;8cbg7tkyW&XSdJ9+S?sg4E4#v{lsP)BDI=s?+Q(=m%X01vsQG>P{~uE_uhWuXdZ z*oFKu6ln56AHpyJrO1=!*&NR2pBLJ`%kJ!)X@SPc(PjF zGS;rK1$gn#IIswd9ggtG5R+i_g$$sd5MnnvLha1dX8EA_GZYX(0fA56No0WhWpnqV zU2G1xe$+$(_sJR`kUVWEU3->f>_;flJq7fOYxKC^&<{3<%vBtOEerz^qoKem(x=J529#8itRuNKoXvHW=#yd3uJjBu)^@^QawGPqsqSF;o;Pz zg?zgiyujV28;-)oq4?H}6GOTnXusbId8NRYVCq;SU?TzwHO^6plW=DKQ3Sb90foT> zDN_Fz+MH=f@(s2X5X1;7IbyXJ?5h1oj~Fc|s-Mmw@r%T5aJAfK#< zutNmW9!P!f{8d!858;0|MERT0VNY2p@}9?#3upwPf6GhV5;Sn2#8g7k6(|53qOydm zlU|!Kb^$Md*1Wq^>(8-snl_A7g!nB8m-c~|n~$WFw4D%EVf|%Ulm5!lB!7Wzq+Sr( z8_aG4TO7?sSD^}MbmPpa%x3cUICPD;i@(aaAem5mC>7)>=tY2gTIeZwdAlG6L5i&? z+!EgxwJEA3Q6{_BBoJ7%G|UdbzPlu)gLIH;?G#tzgLONt5oaO2C`Qyom27la@WQbp zJX8ZJgFzvDlTyjr#y9$Bz*S6PDcW@40SZuQf>P^hG{VvM zCDxU@LOPb4jv6W_yD@LDnChe$N&+GdjA0luQe^peOPPm)xL*!_`f(Dd5VdcrE*X2bngr)yeM_~* zT6JrM$Gh2nuF-$|m=^GVBoO~&Nq+tB{|_V(|6lpy1S{A-DU7q?Vx{_Pui+*7;|!)<;YT_5OJ+K(C|bi}RJAd%9^f9%gk zfC!DqGP`w)z2@KI>n4AT!MiC&rl;>ly#!)GH%U|UK|K{psrW^~D1wE9f)P*r?*GFrI^-gyV(}W8EI@)y2xc~Ao8b!`MBvj!8YD=^ zJRk!nV}T1#nhP7WUI=8Hp^`$OqU}h(E~O`Ur+?N?sapX&sMv(ahMX^=E}#{wk;xIb zMUfB8h6k=wcZ37jX91X5WH3(o;sp13ZDpQ z2e?V-xc(Ha3tT4t&5lhon4!@QvK)_yNZE>3s1uR2C@MJ+U-Y70mul)X#><2L>46@X ztPCoYBRr*29^*OG96dn%1wnuUP87sltKsa4>qx@V)bDvIdcoA4lRWbSOmeDs0(o{|Y%why;u|5B9W?9E200qkUlclS zhXNNOKC>5SEgdhwR%k>6Lm??w#hQS3maR9v2zizu^ciX`p>73iNhGl|N0cJxRnP*J zC+r+R?K%Wau_nLSurwP2TPcabq4^eN0|prp!VycwPE#gw5ApHvO8J`9c#4L*y`%lP zcGU6%IMdT?MB@M6NxH3y`|t-HcJKV?*kZK{@%t7SZ1vxYjud`q_^Rs7osbi$W&B% z=`;M}x0iu`!Cu58OKXyNKI~BM7rYLS-p@TeNEKA#bC5%ozW_Y0fNeT%RW23&PhCJm= z#dANBQy~)Z!-o&^#YU-A3W>5e^TQygrH8{wjT>m4G6T~G(*l}N6y^W)5_zmmf8D)P zb{nX7mu-h2ref1T2ukjONOyt28L1vC3&3~Zk%hoFK>~bpRB{S##=vm<0NwF8>wXjT z!>p3P?D8Gd6JY9sNt!5T7|+)L2Q_cQl~IUBgrFe8mI6C^yLHYb{3&H1)owl_a(FDx*6#`vjuhWk34w!3fsx`a6)1Ox$> zehjAin$&0{i%=uttla*abvbxqe$hgnx=1VN4s9{i#?wi%rnUiuL*j{tMpfgop zEc&nH!Q*)GwP@jk^n;M27$}&hzQ;l652f!p#|f}D8%F9s+q*o@Q1|SOqlXcj>Hc^H z5)+iEz2bg5l#)4UxfcNmma9L3+P^VSb+0KH#G0_;>A7$`O2j3sI00M*`5Ai(2(S=) z2vq4sHQoTs8^#z?Pb}x9q78a1xA*rG6)fhsjF;8aSGJG%up*3`q*+LkdPp-$Qx*yyc7#8*D zO(#0_v&u@sRC!?*Hum>p+aW?>7uMX1{fBj^CADH=J)WwQS1-U?WS9N+fhQNk?#~B9 zK?DuzWNLwEQe_Ld=D|m#RN~NpN{9MWiMj#z1|7BEydp!wI}muDVzX)+fV6}ldKJpX z()q#Dv!{4l)U(F+l%aG4W5XDOBmVe>E#M-?TwG!|Tq^a1|6yy=X3qA{_}9o_^(dsE zNuurmu2>N?K*S(jOUyu&j09m%9m>1%7cN?K@`ol$kvE!lCyW5Q0NIJ}FqM>{%UUL& zmQnVbkEs<;hS~{vY#$5}*6CbI)n0(U9j4GtP=O3;Z0}Z}?VtNWu?&sc+n+-dYPJIf zQA2k)ZaL6(_{j%}zg>g0)AYUoV~tEc9sqG3r3YUeju0ZwQ@O?TpaEpT41Z|i7T;)E zhqhhp%e|q8uqB{;vq$1EdjlGB^+Ep|aZ+!XQ03^eu+I0XCyBwt2lXhn?Fh?6`$xsm zmQrQeyaCLA4T#9Q0}uo)EF1tMT`nr@sS8v0{0~i(P;MCPNf58IDE0U)TuU?P&)2^E za0M_2(s%}OAfBO7H>vgSf~jfj+I!e7LdCHlD!BrSAs*v_Uy(`JUwn$kMG)UB z9C*Zy9yCk5`78_~(dQ4+Q{VdJJj-n+KJE@7UkRqY|X{Tm-l(W_TALY>v~;@YQKon!OaPv(M5SM2*a9EFRvh!5Wj zVvc99#i@h!zuofNR@kt0q{z60g=Ot-s%4NU0+3twS8WI=NWqgN@ZgWtO{`I6C%W=T zr_U%$-EQHmbgwEdLq=uw2i%eTku*Dn=xeMZT#(AfPN60=^|k*w0v-SOx>NI2H-1I{ zN6IR&nP>vK*VViC0w^7rFjwhZjJ%0`dumF`p@;k;A|l!Rv0GdO38V)5PW@>E&`!o` zL~4rteSMz-r;KV~U^tYE0^v&q`@iLfrhfqf78OKEs04V`-cN7{0d%<`gVYs@ zR#2w_@xuhIgI4Pp#j{D1VIn!4ow^urpMePVKew;zZ0$xod(Br^seNYCizKLG1CAIO zZSV=)sdh=FKGNCnvOt~k^tnj*9(`|54~wZQYTxp9MVMwF{3F@|U-n#jG|Nb2k?uJiu5GXruPK`%b1IlY)uKU;L^{ehO?w@cR~`Jz#8MvrsvKngKa=q zxat^0j&yO%CoX0-pe3m$Rrk=W9n(z~on6#8ea z#uco6;r{L+IWA>y)D!|Oj$HW30UG~;w?zY&-oi8mnbPtH)lqjlw$MEee(;rcLuGAc zf~g!hu<5z5uoxL4c(?(3viUp@KKP`Vegb^Z8&lcqa=%^8_!KNwEg}`hT&VVKNr*z= zz_>Gnm>ZOlEwo5lA2c9hdRQPwc{p-F|w zp2wP_=zIwfTz6d+DL|3`(sd;yi->b6+YB#8qX8b&*Sr-U|AO@2+EsAhGKhbEx*U^7 zu-8<)Hs87`q^XZK#0%($wDn2sxV;O|i zGG={4PP(Ct1ARU}TAG>+#`X^ZgogW9FtGK#?`i!8*o*~>ngo#|coA}pgHl6LH??3~ z^hN`Sx&?_IA|mIOV1@@FTBBJal&1j1pd%mJTc|6I1?oUP|1(g3n2j#G&;7(J_@0cu zw>Qh)&N#w=o!Kodr4oy>d7c*zY-U~_Uq3W35M5So5lsbtF~=JfWw)j>_cm;q?gek+ zhM+jzUkU@Dr}SGueftseX@ys4=0sg;uk8hBQ-LnKU#l9tH8cXOmZ0WSJ2ZKC(UMX= z?C=SL+jNY51~x4OBM?N|*DHkZ%P>1Zo`z`U1=&flBNn>>=G|3+-rp8CZ5g7@lON5` zBNYVm=XJ|4Q3Ea_jd_yCHgDcMNYuSu5sxG~HixtcP@A899HZwn!9CYPI^u4mb%)mixB4L`O%D^~O=R z5~*=Hyir3@k*c$3B4508>BFTYOA2`g?=!!7Jcfi+h$f!;u^p-KdwBPk#-LEV028gl z6tzbZlXf#4?E1#Fb?dX*21HEF04H~K4iQoP;kLXLLTK11Eq`3KkBZ2s)3T%xAN%8V zoeZ2{w267wQbSqdkGHe*TYs}mPuLtno}>ddBx$e^xI_>@h@J((KQRK{(CBa*qO??Y zM@^5dg%`rHXk-E-&gZ=8MJuaj(7*1+#+sV6AgZOhI9gpmU3g467)WBk%i`Zuf%({k zerNu{W>!!`iQPH>0aAA$HIZ26G;|xHiQNs!LD`7zVU*QrltJsPzuM*W>o>qd%fB4423X6enI- z4iiA*m#AmNk$csuMgR)bdk>swP8H%JJtTd^F~DH#ssGf@h4Y~fAm! zqv5-^Z@bV%6M~CQguGj7km^H^hLo3u?5(AtpjNDr05w7#kT3N=BIt2n3KGl<;^slu zhNXpr|CF<`oyR)dPuVzUh*lsX;AgR>0yV%lOfJ<1xgZ^(_YIl=@$L5jbnGmBf)ua- zuqYfLEwmVXi$V<;NJHXNXa-?wf)sG1_-(r2)=@P&5uiTGxqHpDkdM%u0^s2^6X4l4 z7wBZrg(N(jNU>&*F!zIENB9v+?w2(DwlTLwF|bvZbekLr=tJtQhUye4Z-5oP7tbul z^31uWL`#a)8UrGC@n6U85abRnEq5wSiNZ0Z%n7tKo}c|j;Y6)(LGm7sMjEl$WD+~1 z)POMtY}`;ZBT#)E{KQ(Y0Ex;0XOeCRhfBONbOd-l`aSdHxv+aQsbh{hW<$wCsqP>| zu}PT?`5|ifl=LH|B}zPIUG0r!Jm^Vc&u|C@V3RC@jweD}WXL<<0r%sA8r_w1rMftP z*+yiN)Ok!5aY%j;anAP}@8fMyh9suwkTrVT={~dbPPMr9(%PwRpE2hm4@JjbAvYHiiqyf*@YSF?)9{H z{$zXnJfu{r)Rs$HpZ;$*Vmn}P2l2`?O0vw;rQUZO={ zfaFKOd;6E|LSQLOSo)|-qSd_Eu%x#4qn`izz`FFxDi*1Q(w8G0@7xcWZ^FxB68+4V zDfdL)0o#QSKA0}Z4i#A9fAqlVg$2bzf(I4|igU9B{av45@aOvq`4#n?2UAoGWZC{; zG@ZG{lU35QnqzSN(fg&bZC24@gWZ~CECZC|b|tm%%nvl(h_8#%Mv@Zs6FGulgYZH2^#zbU5TnjFUkivcFDr?3CF z+dh$XpyO-ByEG?af{P2-Zo@ISC73CFU^`T`Vo(#izxmh>_%N=juHdaQhK7df#p6fC zUVg#k+)v4(rREjq+M~=9d8@jk%vDd{jE|4s*7mBi=2d4-_$Ri%L4dutp<*JwH}Fd+ zW}j-fe*WB7=+FORRHne%|7wQg9QpsEs4M9(<)tP*|D*5!Lmw+!VmTgsZ(h*V+bf5p zHS*b=X8^BdF(y6onUq7P`S_n;a6V}n7{YvceBh+-Y)eZ^e-KNH%hNPq43}E9#KcuG z&dI5sD0^>q{dULyh`{7iCcY8_L*a+U!^Y$PFY6^SVv=&d_;B0tfBvvxvKzg0xb5Fh z3^}UVctx4tKV^06ux%I--THpnvnY=9qhW>(saMre42{L@`WM?LoP1yBqeU6LtUoKk;N>-vZDZQpLxalGO_lhEw6(kj%_swb6O zqC?SPN63f0dYJ<`XX$B7Om9OR>n9h5;oyahJkMwF#GFm9Xgu`d=*Qt4)#l^ho8~yQ zMr$P=Q#z}9Wj@dN<9#j5KE$Uu<;d_E<`n*`x3{(RW;1j3vhw&Amb1yvbVq$ZhdI{l z3|YW@MEGS$FN^xb3NR>{7M$9{^KIDrUC5P5qN)BJV;0q*9U&|DUQC>H`#UW`e=t30 zz0p@xoYnm|Kj$fJHoh?Xg@a-dz{9saF-9j+l5Jl8WA0nE zN@~B{w7ttLHvHsz(mU^RTjku;=o`Aq+2DP{#v7s%o&U^-`BDrpMSI9p$?JU&?e zuGf1+Ii*LAYh(XO+hZy(UvL&{GUt`p^i}01GxLo$ImT|vFy@dLF-RFGpH(Vc7;XJF zh89W z%rVW(J0+SWSYkRuD@?cds+?Lc;H7y?WykpKVX%L?`)$a+2WI<=`F4ba{P}-jB2?1; z=6ABX%TmC0ZfM<32}6Th_s<)&3a@$O#X9TeDV&MC1Ou(&hDYJW@5_qV!=7`Ep9}jh z-90TXb;}HE(zA0i8nN^QF84wi{h+wCR0rx)RPzq?ib@}(NfVjb?ubvRjg-%&xF zwY~m5Q$bGl&?}2`^;IEx{n_V+3LW43+}ze#$;9om_*_{z=fSa^&|8n?X7Im1} zP|bL){v6Bd^tB%xKzMn-VwV$3m|FFR57FO>INsq(kU zb>&E{#Feej&1|IFHgB0R;d56ko@sQAN9YRAO6Lg&77~9PPpz97kWp+Wb7IozNE+0Z z&sH_~ZJ(7k1YJ{FEk7qs#XDKGnkPKMYu1K6f2VSk+BS;lUlV!pCaeN)URxQ^ko2{1CKW9&D_r`K-WXW5qQvc*Ezbq;~zZro1QR z8`JxBlH{){tzNRxlYN;2v-|e(?RvedsGwjW07$dW1B!m?&enBv{p432Q+eN`t!4Io z$hBDaB+KOMzusl)yU9ODFga#$kh8dQNlf>bJuF3h--)A;Cm4$gF1P#~Bh}`?dxhstR$aK=_%)Rba>p;_9o#Wm zWVJ~`lr~YlM{0O-V|045H8?l+cRB{we@X4;aPBM1W>S8na=b+k(m^k2t$ zocQAn=W}UX-Q;TW!|1iOS7oluBbdVIbzUPysmGehNu)0Y`C(jjb*Bvs1}_3<)oh(0sO-afu?=h11z$skb6wDJ48+`pE|djXDi*O1Sl<=Zp7&TMwAEqL}c zTC~$`Z|Jbv?Ss7H=IZZvge=himh7K25wP0NWUpD z+xjn!-%GXI&GJ9m$=EPW1fe>`BTkNLKAu(;?&?h67hbo$F!1UNd*lNC+{}@duKtAT z>jH;gTJA(Va<99;sxt8$OgV4a>^P&$ThDFVEAtW`M;|wwc&;>_E9l~MpU=2a_v@Ow zRd#>r$61_ETiDEF{e&Qn*(*hEljHBaemk1I;wD=Rg662|YItkbjMWBN7w$n&(gG533!hzFj?_#OpCTKz`@w;%@!(kz2=Klq-8iK_`dkw?aqW(Umn4 z95)&*Rvv#l1KvUKA1U5@v74OkTOLfwc|12)_?&^1#x<3+;y=kXyz1?UJzn!(;|Gs@n2ExEIJId{?>ZS*fg-$EBE5h+; zjAIp^*mQ>5B63@?k4&;28?G1>KMaj&iC38EJ#H8oKI_I&@mG)(D$w1xQG-Plj%mg*mDw^AaUDbGo$wj> zx|_o^SE?9IZk7EOUO5)kn6W>;bnlcue%lz&ao^0dQf1=9Bi$Fsl8@s2mMrr1;ktg~ zhfE5Se?!o`D6Vss!DabH3uRYNjRKFq4WY%LjmbRoGVpURi^b$>D49}Z+-N>)2Hf0) zXAP8Dc}#`u-qR*e&WS%P`ChDgaeLp3tlUnmtI5@NoBf-)nOsIpQ*x6z@&9-|NIb^|DJsLX(qRG*#4C&w|{dqJzt|` zw^vv^zZ4cKeEf)9N-1+)@5o4!V|3-x_AajLZE={9-uJu??2B^7Yf3UF*jamDF_>lH|*^9wc{n%pptJCVlUzH!KG?=$bu9y{sWyIG2!&oB~Bo$2Ra zF}bc+Pg|@VjO8wFupNaL*Wg$_P+HZ%HPRVVVS2iHJwDt`k;P*o3b)T-jWYiB_jUe6 zMg4#o`9~@pSo<04LG$2vu+CyY3Ck3YMSCib-f#8#g+$ zsR$8!jo-B8nFH1^XyhB-FUy-gpJ4_t#Y4L5!2~GCJG(ug#qP7Uxq~ zXL05vuk>H%a$q#L-m!CZedj*$uYOfJv;0Jiig%wYi*RGCSU3@;DzVD-z$Il=hz5%l z%<3*Puiq*sJP_^ceG-4Z?K1y~iYxK|>b-(^K43DOV@Y4L@8+gyJe&CFhYv4QIdDL_`T5(OA-7aAUMlI^thV*sgiHxgz*HaL zjC2p@LOFk;Ji-!Tp(1T)^<|}s91qSx{(Af+>69){x3A>O&Mhosq`zaS(31S`$?UMO zBEzti_XR7D&-}0}lbO+q4IS`ADm>0j$Wd-sy4;y!aRqz(xXN!BTq8d-H3f*=Z8rrybW<32}dUKABH#djxoQ= zx-knrVXEUi79bB0gqIp!d~=GC4s{J@4O@!XA33q2ai#i?k?}2IxVHZm9uh;jGKM)%xx<`q86 zi>n&{+s*zetQvDft|}Le>_F>XWjLO`D`}r9IWqzmr#wj7+*tKujmSv_RQ3I+RSCo zo`&-t2ZP_H@_O6wkQGyB^sQ}!{zE;R5isQ@->ZJ9q(J+CeBdu8rtGONT0OiYrBf>* z6}iyG=cYe~k)T9JL(^+-FJUXEtCz-ns~W1e7fA2XNeWQq#IlchjJOul@zC6PksRWU zncp-9K7?JuDqa^=XihHcEgveremGPbet*ueS|RtuolrV6%p~fQA=Z-cm~&v>+~LzD zn>goo)p>s%W1Ed_N|$hd+p&<5@Yqea`reMJ{fPcUwFtnM zwDfw-EzeGyE-AO`ZcnzgEA5dS&NmqcI|PMn_8rSUAjdui4b_#;D|}m^VLsR3yi=Nh z)M}{%&nnE``^tuzTvMLmH*V_Ev`tyDBR=!nqg>pDQuBt@1_$LI%nJ+p8S&ONB_FoP zwpksmIBYltAUdj^>UHdaFDGYk?!%?{SQ+OBABB!;E|8_1xv@L;s#2Vb@rJ39x@t)d z$N6Z}q8Epd+U@SFU$|rOoy5_mcDeX_PsO<$CJr0SpH=Q_Mdjs{e|m&S!%WQ&gM&D( z)8K!(Vf@-KY%2pQe(P4yEXK-BtDSj(Q%T2<@XnveFeSrI8eW!n=XW-EK0geRJ!f$C zeU+(}_X+ZrCs&n!!b|U!CPEN~+iVG5z6*Y46M{g=*(3W^6#zxhLzH~$QZp8(<*)bI90u${>iE?3wKOWq(Ky2CQ}>Om?)JyhuZ;Cm+7ER4e2p=d z0gN0IE$VNflHzQ^gq#@BcH8i<4O8=(8yAn;_`SS7fLM81$ywWh!fl2$Nu>a`M*51K+r#_sq z*qq$1)CO#uqK^tYlg`-53@xlSEh)%hlTPdR=A5n_!{yI99t12MR#Q^^1**+{G5YZj zml=NkH52DX@+!&#(%QYB2W=`5x_0%-ZLbjSiha!SHxluL2K?X|^KlnW+_K%1`AOaE zl4nno_zT!Q6abhgx%~R=sVMu&Ul`9wQfro?(96!gFm3S@M^|~#2|>!P?h}n4C*Pds z@#Iead2+_NBRs9sf7x%}b9m>%?IQj`8+w-p^=L~a65Kn~ z_)<~FW+Z#l@N=h^e#%oYB~ObZJ-Ux`bBAp^LmXK2Q8?U^L>%(@_Rnw zJD*Qn<9sw>M+n(&CMH<#s}te1J&(TV#v1b~&9brGEvuX9XTV-=vi7t*%VLlHlCmqt z{kX~p06;tgm4NjB!wH=J6XcPuMJ*q3CMuzD>Wrq+nrnH3xV}aaFQa&^Tn@yLT^Dh_k9^GZd*%l%KEd(WSB4 z>3gz&%#?bC*%QH?9{d33G+Bx$+p+Ha`FTn^@mYCQRY#hQY9n916ul+Z^Xag$17p20 zqrHKJWpmD^BEpyUp!(@?8yv?(5|;?tJ76mKek|6NY$n0X#e_ZE+c)J zOwcx8+?UPIlxvhZ)UYsE?p00g+u|k(mUf>9To*<~{3FF`ilT4oiJfVTnKr;VGV;gO z?0H3JOWuugjWj8?a&+bS!>z_VF;$j6xkLIzZ`YiQw(zOPkxxh)®Q_hK|>FQq$~ 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

k-(B3rONSg>OW`+DqOC|}s- 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

`<5G(Ok- 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

tr?9)}cwtmyaXzog9BAKQ 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�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

D9 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 z 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, %46 : tile ``` - There may be more patterns in the future. + There may be more patterns in the future. If there are no patterns matched, the llvm.intr.assume will be removed without any new op. - + This transformation allows the compiler to better understand alignment assumptions and potentially generate more efficient code. }]; diff --git a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp index 46ccea01e3..0cc089fa79 100644 --- a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp +++ b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp @@ -504,3 +504,4 @@ LogicalResult expandMapElementwiseOps(Operation *rootOp) { } // namespace triton } // namespace mlir + diff --git a/third_party/tileir/scripts/build_helper/Dockerfile.release b/third_party/tileir/scripts/build_helper/Dockerfile.release index 56525c61fc..b65a962db5 100644 --- a/third_party/tileir/scripts/build_helper/Dockerfile.release +++ b/third_party/tileir/scripts/build_helper/Dockerfile.release @@ -56,7 +56,7 @@ RUN rm -rf ${TRITON_SRC_DIR}/build || true FROM base AS wheel ARG TRITON_SRC_DIR -# Copy and install pre-built wheel. +# Copy and install pre-built wheel (e.g. nvtriton-*-cp312-cp312-linux_x86_64.whl) COPY wheel/*.whl /tmp/ RUN pip install --no-cache-dir /tmp/*.whl && rm -f /tmp/*.whl diff --git a/third_party/tileir/test/CMakeLists.txt b/third_party/tileir/test/CMakeLists.txt new file mode 100644 index 0000000000..232d976eea --- /dev/null +++ b/third_party/tileir/test/CMakeLists.txt @@ -0,0 +1,36 @@ +# Passed to lit.site.cfg.py.in to set up the path where to find libraries. +set(MLIR_LIB_DIR ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) + +configure_lit_site_cfg( + ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in + ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py + MAIN_CONFIG + ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py +) + +set(TRITON_CUDA_TILE_TEST_DEPENDS + triton-cuda-tile-opt + triton-opt + triton-llvm-opt +) + +set(FILECHECK_PATH "${LLVM_LIBRARY_DIR}/../bin/FileCheck") +set(LIT_ARGS "-Dfilecheck=${FILECHECK_PATH}") + +# This target can be used to just build the dependencies +# for the check-triton-cuda-tile target without executing the tests. +# This is useful for bots when splitting the build step from +# the testing step. +add_custom_target(check-triton-cuda-tile-build-only + DEPENDS ${TRITON_CUDA_TILE_TEST_DEPENDS} +) + +add_lit_testsuite(check-triton-cuda-tile "Running the triton-cuda-tile tests" + ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${TRITON_CUDA_TILE_TEST_DEPENDS} + ) +set_target_properties(check-triton-cuda-tile PROPERTIES FOLDER "Tests") + +add_lit_testsuites(triton-cuda-tile ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${TRITON_CUDA_TILE_TEST_DEPENDS} +) diff --git a/third_party/tileir/test/FileCheck/fma.mlir b/third_party/tileir/test/FileCheck/fma.mlir new file mode 100644 index 0000000000..eb6d77aa82 --- /dev/null +++ b/third_party/tileir/test/FileCheck/fma.mlir @@ -0,0 +1,2 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts)" | FileCheck %s + diff --git a/third_party/tileir/test/FileCheck/inliner.mlir b/third_party/tileir/test/FileCheck/inliner.mlir new file mode 100644 index 0000000000..93154aab20 --- /dev/null +++ b/third_party/tileir/test/FileCheck/inliner.mlir @@ -0,0 +1,16 @@ +// RUN: triton-cuda-tile-opt %s --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts, inline)" | FileCheck %s + +// CHECK-LABEL: kernel_12 +module @kernel_12 { + // CHECK-NOT: @kernel + tt.func private @kernel(%arg0: i32) { + tt.return + } + + // CHECK-LABEL: @main + tt.func @main(%arg0: i32) { + // CHECK-NOT: call + tt.call @kernel(%arg0) : (i32) -> () + tt.return + } +} diff --git a/third_party/tileir/test/FileCheck/op-conversion-assume.mlir b/third_party/tileir/test/FileCheck/op-conversion-assume.mlir new file mode 100644 index 0000000000..5f98c739b3 --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion-assume.mlir @@ -0,0 +1,87 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(rewrite-assume-with-cuda-tile)" 2>&1 | FileCheck %s + +// CHECK-NOT: error +module @kernel{ + tt.func public @assume_rewrite( + %arg0: !tt.ptr {tt.divisibility = 16 : i32}, + %arg1: !tt.ptr {tt.divisibility = 16 : i32}, + %arg2: !tt.ptr {tt.divisibility = 16 : i32}, + %arg3: i32 {tt.divisibility = 16 : i32}, + %arg4: i32 {tt.divisibility = 16 : i32}, + %arg5: i32 {tt.divisibility = 16 : i32}, + %arg6: i32 {tt.divisibility = 16 : i32}, + %arg7: i32 {tt.divisibility = 16 : i32}, + %arg8: i32 {tt.divisibility = 16 : i32}, + %arg9: i32 {tt.divisibility = 16 : i32}, + %arg10: i32 {tt.divisibility = 16 : i32}, + %arg11: !tt.ptr {tt.divisibility = 16 : i32} + ) attributes {noinline = false} { + %cst = arith.constant dense<0.000000e+00> : tensor<64x64xf32> + %c63_i32 = arith.constant 63 : i32 + %c0_i64 = arith.constant 0 : i64 + %c1_i32 = arith.constant 1 : i32 + %c0_i32 = arith.constant 0 : i32 + %true = arith.constant true + %c16_i64 = arith.constant 16 : i64 + %c64_i32 = arith.constant 64 : i32 + %c8_i32 = arith.constant 8 : i32 + %c1_i64 = arith.constant 1 : i64 + %0 = arith.extsi %arg6 : i32 to i64 + %1 = tt.make_tensor_descriptor %arg0, [%arg3, %arg5], [%0, %c1_i64] : , > + %2 = arith.extsi %arg7 : i32 to i64 + %3 = tt.make_tensor_descriptor %arg1, [%arg4, %arg5], [%2, %c1_i64] : , > + %4 = tt.get_program_id x : i32 + %5 = arith.addi %arg3, %c63_i32 : i32 + %6 = arith.divsi %5, %c64_i32 : i32 + %7 = arith.addi %arg4, %c63_i32 : i32 + %8 = arith.divsi %7, %c64_i32 : i32 + %9 = arith.addi %arg5, %c63_i32 : i32 + %10 = arith.divsi %9, %c64_i32 : i32 + %11 = arith.muli %6, %8 : i32 + %12 = tt.get_num_programs x : i32 + %13 = arith.muli %8, %c8_i32 : i32 + %14 = tt.ptr_to_int %arg2 : !tt.ptr -> i64 + %15 = arith.remsi %14, %c16_i64 : i64 + %16 = arith.cmpi eq, %15, %c0_i64 : i64 + %17 = builtin.unrealized_conversion_cast %arg2 : !tt.ptr to !cuda_tile.ptr + %18 = builtin.unrealized_conversion_cast %arg8 : i32 to !cuda_tile.tile + %19 = builtin.unrealized_conversion_cast %arg3 : i32 to !cuda_tile.tile + %20 = builtin.unrealized_conversion_cast %arg4 : i32 to !cuda_tile.tile + %21 = builtin.unrealized_conversion_cast %17 : !cuda_tile.ptr to !cuda_tile.tile> + %22 = cuda_tile.make_token : token + %23 = builtin.unrealized_conversion_cast %c0_i32 : i32 to !cuda_tile.tile + %24 = builtin.unrealized_conversion_cast %true : i1 to !cuda_tile.tile + scf.for %arg12 = %4 to %11 step %12 : i32 { + %25 = arith.divsi %arg12, %13 : i32 + %26 = arith.muli %25, %c8_i32 : i32 + %27 = arith.subi %6, %26 : i32 + %28 = arith.minsi %27, %c8_i32 : i32 + %29 = arith.remsi %arg12, %28 : i32 + %30 = arith.addi %26, %29 : i32 + %31 = arith.remsi %arg12, %13 : i32 + %32 = arith.divsi %31, %28 : i32 + %33 = arith.muli %30, %c64_i32 : i32 + %34 = arith.muli %32, %c64_i32 : i32 + %35 = scf.for %arg13 = %c0_i32 to %10 step %c1_i32 iter_args(%arg14 = %cst) -> (tensor<64x64xf32>) : i32 { + %43 = arith.muli %arg13, %c64_i32 : i32 + %44 = tt.descriptor_load %1[%33, %43] : !tt.tensordesc> -> tensor<64x64xf16> + %45 = tt.descriptor_load %3[%34, %43] : !tt.tensordesc> -> tensor<64x64xf16> + %46 = tt.trans %45 {order = array} : tensor<64x64xf16> -> tensor<64x64xf16> + %47 = tt.dot %44, %46, %arg14, inputPrecision = tf32 : tensor<64x64xf16> * tensor<64x64xf16> -> tensor<64x64xf32> + scf.yield %47 : tensor<64x64xf32> + } + llvm.intr.assume %16 : i1 + %tview = cuda_tile.make_tensor_view %21, shape = [%19, %20], strides = [%18, 1] : tile -> tensor_view + %pview = cuda_tile.make_partition_view %tview : partition_view> + %36 = tt.addptr %arg11, %arg12 : !tt.ptr, i32 + %37 = arith.truncf %35 : tensor<64x64xf32> to tensor<64x64xf16> + %38 = builtin.unrealized_conversion_cast %37 : tensor<64x64xf16> to !cuda_tile.tile<64x64xf16> + %39 = builtin.unrealized_conversion_cast %30 : i32 to !cuda_tile.tile + %40 = builtin.unrealized_conversion_cast %32 : i32 to !cuda_tile.tile + %41 = cuda_tile.store_view_tko release device %38, %pview[%39, %40] token = %22 : tile<64x64xf16>, partition_view>, tile -> token + %42 = builtin.unrealized_conversion_cast %36 : !tt.ptr to !cuda_tile.tile> + %result, %result_token = cuda_tile.atomic_rmw_tko release device %42, xchg, %23, %24 token=%41 : tile>, tile, tile -> tile, token + } {tt.flatten} + tt.return + } +} diff --git a/third_party/tileir/test/FileCheck/op-conversion-auto-memtoken.mlir b/third_party/tileir/test/FileCheck/op-conversion-auto-memtoken.mlir new file mode 100644 index 0000000000..fae6dae8a7 --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion-auto-memtoken.mlir @@ -0,0 +1,228 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts,auto-gen-memory-token{autogen-alias-memtoken=true})" | FileCheck %s + +module { + tt.func public @test_auto_memtoken_if_normal(%Cond: !tt.ptr {tt.divisibility = 16 : i32}, %XTrue: !tt.ptr {tt.divisibility = 16 : i32}, %XFalse: !tt.ptr {tt.divisibility = 16 : i32} , %Ret: !tt.ptr {tt.divisibility = 16 : i32}) attributes {noinline = false} { + %c1_i32 = arith.constant 1 : i32 + %c0_i32 = arith.constant 0 : i32 + %c2_i32 = arith.constant 2 : i32 + %pid = tt.get_program_id x : i32 + %0 = arith.remsi %pid, %c2_i32 : i32 + %1 = arith.cmpi eq, %0, %c0_i32 : i32 + scf.if %1 { + %2 = tt.load %XTrue : !tt.ptr + tt.store %Ret, %2 : !tt.ptr + } else { + %2 = arith.cmpi eq, %0, %c1_i32 : i32 + scf.if %2 { + %3 = tt.load %XFalse : !tt.ptr + tt.store %Ret, %3 : !tt.ptr + } + } + %4 = tt.load %Ret : !tt.ptr + %5 = arith.addf %4, %4 : f32 + tt.store %Ret, %5 : !tt.ptr + tt.return + } +} + +// CHECK-LABEL: @test_auto_memtoken_if_normal +// CHECK: %[[TOKEN0:.*]] = make_token : token +// CHECK: %[[TOKEN4:.*]] = if {{.*}} { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token=%{{.*}} +// CHECK: %[[TOKEN1:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN0]] {{.*}} +// CHECK: yield %[[TOKEN1]] : token +// CHECK: } else { +// CHECK: %[[TOKEN3:.*]] = if {{.*}} { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token=%{{.*}} +// CHECK: %[[TOKEN2:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN0]] {{.*}} +// CHECK: yield %[[TOKEN2]] : token +// CHECK: } else { +// CHECK: yield %[[TOKEN0]] : token +// CHECK: } +// CHECK: yield %[[TOKEN3]] : token +// CHECK: } +// CHECK: {{.*}}, %[[TOKEN5:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN4]] +// CHECK: %[[TOKEN6:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN5]] {{.*}} + +// ----- + + +module { + tt.func public @test_auto_memtoken_if_condensed1(%Cond: !tt.ptr {tt.divisibility = 16 : i32}, %XTrue: !tt.ptr {tt.divisibility = 16 : i32}, %XFalse: !tt.ptr {tt.divisibility = 16 : i32} , %Ret: !tt.ptr {tt.divisibility = 16 : i32}) attributes {noinline = false} { + %c1_i32 = arith.constant 1 : i32 + %c0_i32 = arith.constant 0 : i32 + %c2_i32 = arith.constant 2 : i32 + %pid = tt.get_program_id x : i32 + %0 = arith.remsi %pid, %c2_i32 : i32 + %1 = arith.cmpi eq, %0, %c0_i32 : i32 + scf.if %1 { + %2 = tt.load %XTrue : !tt.ptr + tt.store %Ret, %2 : !tt.ptr + } else { + %2 = arith.cmpi eq, %0, %c1_i32 : i32 + scf.if %2 { + %3 = tt.load %XFalse : !tt.ptr + tt.store %Ret, %3 : !tt.ptr + } + } + tt.return + } +} + +// CHECK-LABEL: @test_auto_memtoken_if_condensed1 +// CHECK: %[[TOKEN:.*]] = make_token : token +// CHECK: if {{.*}} { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token=%{{.*}} +// CHECK: {{.*}} = store_ptr_tko {{.*}}token=%[[TOKEN]] {{.*}} +// CHECK-NOT: yield {{.*}} +// CHECK: } else { +// CHECK if {{.*}} { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token=%{{.*}} +// CHECK: {{.*}} = store_ptr_tko {{.*}}token=%[[TOKEN]] {{.*}} +// CHECK-NOT: yield {{.*}} +// CHECK-NOT: } else { +// CHECK-NOT: yield {{.*}} +// CHECK-NOT: } +// CHECK-NOT: yield {{.*}} +// CHECK: } + +// ----- + + +module { + tt.func public @test_auto_memtoken_if_condensed2(%Cond: !tt.ptr {tt.divisibility = 16 : i32}, %XTrue: !tt.ptr {tt.divisibility = 16 : i32}, %XFalse: !tt.ptr {tt.divisibility = 16 : i32} , %Ret: !tt.ptr {tt.divisibility = 16 : i32}) attributes {noinline = false} { + %c1_i32 = arith.constant 1 : i32 + %c0_i32 = arith.constant 0 : i32 + %c2_i32 = arith.constant 2 : i32 + %pid = tt.get_program_id x : i32 + %0 = arith.remsi %pid, %c2_i32 : i32 + %1 = arith.cmpi eq, %0, %c0_i32 : i32 + scf.if %1 { + %2 = arith.cmpi eq, %0, %c1_i32 : i32 + scf.if %2 { + %3 = tt.load %XFalse : !tt.ptr + tt.store %Ret, %3 : !tt.ptr + } + } else { + %2 = tt.load %XTrue : !tt.ptr + tt.store %Ret, %2 : !tt.ptr + } + tt.return + } +} + +// CHECK-LABEL: @test_auto_memtoken_if_condensed2 +// CHECK: %[[TOKEN:.*]] = make_token : token +// CHECK: if {{.*}} { +// CHECK: if {{.*}} { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token=%{{.*}} +// CHECK: %[[TOKEN1:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN]] {{.*}} +// CHECK: yield %[[TOKEN1]] +// CHECK: } else { +// CHECK: yield %[[TOKEN]] +// CHECK: } +// CHECK-NOT: yield {{.*}} +// CHECK: } else { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token=%{{.*}} +// CHECK: {{.*}} = store_ptr_tko {{.*}}token=%[[TOKEN]] {{.*}} +// CHECK-NOT: yield {{.*}} +// CHECK: } + + +// ----- + + +module { + tt.func public @test_auto_memtoken_for(%Out1: !tt.ptr, %Out2: !tt.ptr) attributes {noinline = false} { + %c10000_i32 = arith.constant 10000 : i32 + %c0_i32 = arith.constant 0 : i32 + %cst = arith.constant dense<1> : tensor<128xi64> + %c1_i32 = arith.constant 1 : i32 + %start = tt.elementwise_inline_asm "mov.u64 $0, %globaltimer;" {constraints = "=l", packed_element = 1 : i32, pure = false} -> i64 + %off = tt.make_range {end = 128 : i32, start = 0 : i32} : tensor<128xi32> + %0 = tt.splat %Out1 : !tt.ptr -> tensor<128x!tt.ptr> + %1 = tt.addptr %0, %off : tensor<128x!tt.ptr>, tensor<128xi32> + scf.for %i = %c0_i32 to %c10000_i32 step %c1_i32 : i32 { + %3 = tt.load %1 : tensor<128x!tt.ptr> + %4 = arith.addi %3, %cst : tensor<128xi64> + tt.store %1, %4 : tensor<128x!tt.ptr> + } + %end = tt.elementwise_inline_asm "mov.u64 $0, %globaltimer;" {constraints = "=l", packed_element = 1 : i32, pure = false} -> i64 + tt.store %Out2, %start : !tt.ptr + %2 = tt.addptr %Out2, %c1_i32 : !tt.ptr, i32 + tt.store %2, %end : !tt.ptr + tt.return + } +} + +// CHECK-LABEL: @test_auto_memtoken_for +// CHECK: %[[TOKEN0:.*]] = make_token : token +// CHECK: %[[TOKEN:.*]] = for {{.*}} iter_values(%[[TOKEN1:.*]] = %[[TOKEN0]]) -> (token) { +// CHECK: {{.*}}, %[[TOKEN2:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN1]] +// CHECK: %[[TOKEN3:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN2]] +// CHECK: continue %[[TOKEN3]] : token +// CHECK: } + +// ----- + +module { + tt.func public @test_auto_memtoken_nested_while(%data: !tt.ptr, %countPtr: !tt.ptr) attributes {noinline = false} { + %c2_i32 = arith.constant 2 : i32 + %cst = arith.constant 1.000000e+00 : f32 + %c0_i32 = arith.constant 0 : i32 + %c10_i32 = arith.constant 10 : i32 + %c1_i32 = arith.constant 1 : i32 + scf.for %i = %c0_i32 to %c10_i32 step %c1_i32 : i32 { + %count = tt.load %countPtr : !tt.ptr + %count_0 = scf.while (%count_1 = %count) : (i32) -> i32 { + %0 = arith.cmpi sgt, %count_1, %c0_i32 : i32 + scf.condition(%0) %count_1 : i32 + } do { + ^bb0(%count_1: i32): + %0 = tt.load %data : !tt.ptr + %1 = arith.addf %0, %cst : f32 + tt.store %data, %1 : !tt.ptr + %count_2 = arith.subi %count_1, %c2_i32 : i32 + scf.yield %count_2 : i32 + } + } + tt.return + } +} + +// CHECK-LABEL: @test_auto_memtoken_nested_while +// CHECK: %[[TOKEN0:.*]] = make_token : token +// CHECK: %[[TOKEN:.*]] = for {{.*}} iter_values(%[[TOKEN1:.*]] = %[[TOKEN0]]) -> (token) { +// CHECK-NOT: {{.*}} = load_ptr_tko {{.*}}token={{.*}} +// CHECK: %[[LOOP_RESULT:.*]]:2 = loop iter_values({{.*}}, %[[TOKEN2:.*]] = %[[TOKEN1]]) : {{.*}} { +// CHECK: if {{.*}} { +// CHECK: {{.*}}, %[[TOKEN3:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN2]] +// CHECK: %[[TOKEN4:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN3]] +// CHECK: continue {{.*}}, %[[TOKEN4]] +// CHECK: } else { +// CHECK: break {{.*}}, %[[TOKEN2]] +// CHECK: } +// CHECK: break {{.*}}, %[[TOKEN2]] +// CHECK: } +// CHECK: continue %[[LOOP_RESULT]]#1 : token +// CHECK: } + + +// ----- + +module { + tt.func public @test_auto_memtoken_read_only(%X: !tt.ptr {tt.divisibility = 16 : i32}, %Y: !tt.ptr {tt.divisibility = 16 : i32}) attributes {noinline = false} { + %1 = tt.load %X : !tt.ptr + %2 = tt.load %X : !tt.ptr + %3 = tt.load %Y : !tt.ptr + tt.store %Y, %3 : !tt.ptr + tt.return + } +} + +// CHECK-LABEL: @test_auto_memtoken_read_only +// CHECK: %[[TOKEN0:.*]] = make_token : token +// CHECK: {{.*}}, %[[TOKEN1:.*]] = load_ptr_tko weak %{{.*}} : {{.*}} +// CHECK: {{.*}}, %[[TOKEN2:.*]] = load_ptr_tko weak %{{.*}} : {{.*}} +// CHECK: {{.*}}, %[[TOKEN3:.*]] = load_ptr_tko weak %{{.*}} token=%[[TOKEN0]] : {{.*}} +// CHECK: %[[TOKEN4:.*]] = store_ptr_tko {{.*}} token=%[[TOKEN3]] {{.*}} diff --git a/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir b/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir new file mode 100644 index 0000000000..11efd3937e --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir @@ -0,0 +1,165 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts,auto-gen-memory-token)" | FileCheck %s + +module @test_barrier_add_kernel { + tt.func public @test_barrier_add_kernel(%arg0: !tt.ptr {tt.divisibility = 16 : i32}, %arg1: !tt.ptr {tt.divisibility = 16 : i32}, %arg2: !tt.ptr {tt.divisibility = 16 : i32}, %arg3: i32 {tt.divisibility = 16 : i32}) attributes {noinline = false} { + %c1024_i32 = arith.constant 1024 : i32 + %0 = tt.get_program_id x : i32 + %1 = arith.muli %0, %c1024_i32 : i32 + %2 = tt.make_range {end = 1024 : i32, start = 0 : i32} : tensor<1024xi32> + %3 = tt.splat %1 : i32 -> tensor<1024xi32> + %4 = arith.addi %3, %2 : tensor<1024xi32> + %5 = tt.splat %arg3 : i32 -> tensor<1024xi32> + %6 = arith.cmpi slt, %4, %5 : tensor<1024xi32> + %7 = tt.splat %arg0 : !tt.ptr -> tensor<1024x!tt.ptr> + %8 = tt.addptr %7, %4 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %9 = tt.load %8, %6 : tensor<1024x!tt.ptr> + %10 = tt.splat %arg1 : !tt.ptr -> tensor<1024x!tt.ptr> + %11 = tt.addptr %10, %4 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %12 = tt.load %11, %6 : tensor<1024x!tt.ptr> + %13 = arith.addf %9, %12 : tensor<1024xf32> + gpu.barrier + %14 = tt.splat %arg2 : !tt.ptr -> tensor<1024x!tt.ptr> + %15 = tt.addptr %14, %4 : tensor<1024x!tt.ptr>, tensor<1024xi32> + tt.store %15, %13, %6 : tensor<1024x!tt.ptr> + tt.return + } +} + +// CHECK-LABEL: @test_barrier_add_kernel +// CHECK-NOT: gpu.barrier +// CHECK: %[[RESULT1:.*]], %[[TOKEN1:.*]] = load_ptr_tko +// CHECK: %[[RESULT2:.*]], %[[TOKEN2:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN1]] +// CHECK: %[[TOKEN3:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN2]] + +// ----- + +module @test_barrier_layer_norm_bwd{ + tt.func public @test_barrier_layer_norm_bwd(%arg0: !tt.ptr {tt.divisibility = 16 : i32}, %arg1: !tt.ptr {tt.divisibility = 16 : i32}, %arg2: !tt.ptr {tt.divisibility = 16 : i32}, %arg3: !tt.ptr {tt.divisibility = 16 : i32}, %arg4: !tt.ptr {tt.divisibility = 16 : i32}, %arg5: !tt.ptr {tt.divisibility = 16 : i32}, %arg6: !tt.ptr {tt.divisibility = 16 : i32}, %arg7: !tt.ptr {tt.divisibility = 16 : i32}, %arg8: !tt.ptr {tt.divisibility = 16 : i32}, %arg9: i32 {tt.divisibility = 16 : i32}, %arg10: i32 {tt.divisibility = 16 : i32}) attributes {noinline = false} { + %cst = arith.constant dense<0.000000e+00> : tensor<1024xf16> + %true = arith.constant true + %c1_i32 = arith.constant 1 : i32 + %cst_0 = arith.constant dense<0.000000e+00> : tensor<1024xf32> + %c0_i32 = arith.constant 0 : i32 + %c256_i32 = arith.constant 256 : i32 + %0 = tt.get_program_id x : i32 + %1 = tt.make_range {end = 1024 : i32, start = 0 : i32} : tensor<1024xi32> + %2 = tt.splat %arg10 : i32 -> tensor<1024xi32> + %3 = arith.cmpi slt, %1, %2 : tensor<1024xi32> + %4 = arith.muli %0, %arg9 : i32 + %5 = tt.addptr %arg4, %4 : !tt.ptr, i32 + %6 = tt.addptr %arg1, %4 : !tt.ptr, i32 + %7 = tt.addptr %arg0, %4 : !tt.ptr, i32 + %8 = arith.remsi %0, %c256_i32 : i32 + %9 = tt.addptr %arg8, %8 : !tt.ptr, i32 + %10 = tt.addptr %9, %c256_i32 : !tt.ptr, i32 + %11 = arith.muli %8, %arg10 : i32 + %12 = tt.addptr %arg2, %11 : !tt.ptr, i32 + %13 = tt.splat %12 : !tt.ptr -> tensor<1024x!tt.ptr> + %14 = tt.addptr %13, %1 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %15 = tt.addptr %arg3, %11 : !tt.ptr, i32 + %16 = tt.splat %15 : !tt.ptr -> tensor<1024x!tt.ptr> + %17 = tt.addptr %16, %1 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %18 = tt.splat %5 : !tt.ptr -> tensor<1024x!tt.ptr> + %19 = tt.addptr %18, %1 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %20 = tt.load %19, %3, %cst : tensor<1024x!tt.ptr> + %21 = arith.extf %20 : tensor<1024xf16> to tensor<1024xf32> + %22 = tt.splat %6 : !tt.ptr -> tensor<1024x!tt.ptr> + %23 = tt.addptr %22, %1 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %24 = tt.load %23, %3, %cst : tensor<1024x!tt.ptr> + %25 = arith.extf %24 : tensor<1024xf16> to tensor<1024xf32> + %26 = tt.splat %arg5 : !tt.ptr -> tensor<1024x!tt.ptr> + %27 = tt.addptr %26, %1 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %28 = tt.load %27, %3 : tensor<1024x!tt.ptr> + %29 = arith.extf %28 : tensor<1024xf16> to tensor<1024xf32> + %30 = tt.addptr %arg6, %0 : !tt.ptr, i32 + %31 = tt.load %30 : !tt.ptr + %32 = tt.addptr %arg7, %0 : !tt.ptr, i32 + %33 = tt.load %32 : !tt.ptr + %34 = tt.splat %31 : f32 -> tensor<1024xf32> + %35 = arith.subf %21, %34 : tensor<1024xf32> + %36 = tt.splat %33 : f32 -> tensor<1024xf32> + %37 = arith.mulf %35, %36 : tensor<1024xf32> + %38 = arith.mulf %29, %25 : tensor<1024xf32> + %39 = arith.select %3, %37, %cst_0 : tensor<1024xi1>, tensor<1024xf32> + %40 = arith.select %3, %38, %cst_0 : tensor<1024xi1>, tensor<1024xf32> + %41 = arith.mulf %39, %40 : tensor<1024xf32> + %42 = "tt.reduce"(%41) <{axis = 0 : i32}> ({ + ^bb0(%arg11: f32, %arg12: f32): + %63 = arith.addf %arg11, %arg12 : f32 + tt.reduce.return %63 : f32 + }) : (tensor<1024xf32>) -> f32 + %43 = arith.sitofp %arg10 : i32 to f32 + %44 = arith.divf %42, %43 : f32 + %45 = "tt.reduce"(%40) <{axis = 0 : i32}> ({ + ^bb0(%arg11: f32, %arg12: f32): + %63 = arith.addf %arg11, %arg12 : f32 + tt.reduce.return %63 : f32 + }) : (tensor<1024xf32>) -> f32 + %46 = arith.divf %45, %43 : f32 + %47 = tt.splat %44 : f32 -> tensor<1024xf32> + %48 = arith.mulf %39, %47 : tensor<1024xf32> + %49 = tt.splat %46 : f32 -> tensor<1024xf32> + %50 = arith.addf %48, %49 : tensor<1024xf32> + %51 = arith.subf %40, %50 : tensor<1024xf32> + %52 = arith.mulf %51, %36 : tensor<1024xf32> + %53 = tt.splat %7 : !tt.ptr -> tensor<1024x!tt.ptr> + %54 = tt.addptr %53, %1 : tensor<1024x!tt.ptr>, tensor<1024xi32> + %55 = arith.truncf %52 : tensor<1024xf32> to tensor<1024xf16> + tt.store %54, %55, %3 : tensor<1024x!tt.ptr> + %56 = arith.mulf %25, %39 : tensor<1024xf32> + scf.while : () -> () { + %63 = tt.atomic_cas acq_rel, gpu, %9, %c0_i32, %c1_i32 : (!tt.ptr, i32, i32) -> i32 + %64 = arith.cmpi eq, %63, %c1_i32 : i32 + scf.condition(%64) + } do { + scf.yield + } + %57 = tt.load %10 : !tt.ptr + %58 = arith.cmpi eq, %57, %c0_i32 : i32 + %59:2 = scf.if %58 -> (tensor<1024xf32>, tensor<1024xf32>) { + %63 = tt.atomic_rmw exch, acq_rel, gpu, %10, %c1_i32, %true : (!tt.ptr, i32, i1) -> i32 + scf.yield %56, %25 : tensor<1024xf32>, tensor<1024xf32> + } else { + %63 = tt.load %14, %3 : tensor<1024x!tt.ptr> + %64 = arith.extf %63 : tensor<1024xf16> to tensor<1024xf32> + %65 = arith.addf %56, %64 : tensor<1024xf32> + %66 = tt.load %17, %3 : tensor<1024x!tt.ptr> + %67 = arith.extf %66 : tensor<1024xf16> to tensor<1024xf32> + %68 = arith.addf %25, %67 : tensor<1024xf32> + scf.yield %65, %68 : tensor<1024xf32>, tensor<1024xf32> + } + %60 = arith.truncf %59#0 : tensor<1024xf32> to tensor<1024xf16> + tt.store %14, %60, %3 : tensor<1024x!tt.ptr> + %61 = arith.truncf %59#1 : tensor<1024xf32> to tensor<1024xf16> + tt.store %17, %61, %3 : tensor<1024x!tt.ptr> + gpu.barrier + %62 = tt.atomic_rmw exch, acq_rel, gpu, %9, %c0_i32, %true : (!tt.ptr, i32, i1) -> i32 + tt.return + } +} + +// CHECK-LABEL: @test_barrier_layer_norm_bwd +// CHECK-NOT: gpu.barrier +// CHECK: %[[RESULT1:.*]], %[[TOKEN1:.*]] = load_ptr_tko +// CHECK: %[[RESULT2:.*]], %[[TOKEN2:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN1]] +// CHECK: %[[RESULT3:.*]], %[[TOKEN3:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN2]] +// CHECK: %[[RESULT4:.*]], %[[TOKEN4:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN3]] +// CHECK: %[[RESULT5:.*]], %[[TOKEN5:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN4]] +// CHECK: %[[TOKEN6:.*]] = store_ptr_tko {{.*}}token=%[[TOKEN5]] +// CHECK: %[[TOKEN8:.*]] = loop iter_values(%[[TOKEN_LOOP:.*]] = %[[TOKEN6]]) : token -> token { +// CHECK: %[[RESULT7:.*]], %[[TOKEN7:.*]] = atomic_cas_tko {{.*}} token=%[[TOKEN_LOOP]] +// CHECK: break %[[TOKEN7]] : token +// CHECK: } +// CHECK: %[[RESULT9:.*]], %[[TOKEN9:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN8]] +// CHECK: %[[IF:.*]]:3 = if %{{.*}} { +// CHECK: %[[RMW_RES:.*]], %[[RMW_TOKEN:.*]] = atomic_rmw_tko {{.*}} token=%[[TOKEN9]] +// CHECK: yield %{{.*}}, %{{.*}}, %[[RMW_TOKEN]] +// CHECK: } else { +// CHECK: %[[LOAD1_RES:.*]], %[[LOAD1_TOKEN:.*]] = load_ptr_tko {{.*}} token=%[[TOKEN9]] +// CHECK: %[[LOAD2_RES:.*]], %[[LOAD2_TOKEN:.*]] = load_ptr_tko {{.*}} token=%[[LOAD1_TOKEN]] +// CHECK: yield %{{.*}}, %{{.*}}, %[[LOAD2_TOKEN]] +// CHECK: } +// CHECK: %[[TOKEN10:.*]] = store_ptr_tko {{.*}} token=%[[IF]]#2 +// CHECK: %[[TOKEN11:.*]] = store_ptr_tko {{.*}} token=%[[TOKEN10]] +// CHECK: %[[RESULT12:.*]], %[[TOKEN12:.*]] = atomic_rmw_tko {{.*}} token=%[[TOKEN11]] + diff --git a/third_party/tileir/test/FileCheck/op-conversion-host-tma-desc.mlir b/third_party/tileir/test/FileCheck/op-conversion-host-tma-desc.mlir new file mode 100644 index 0000000000..0cc42a6cae --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion-host-tma-desc.mlir @@ -0,0 +1,31 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts)" | FileCheck %s + +module { + tt.func public @host_tma_load_store(%in_desc: !tt.tensordesc>, %in_desc_0: !tt.ptr, %in_desc_1: i32, %in_desc_2: i32 {tt.divisibility = 16 : i32}, %in_desc_3: i64, %in_desc_4: i64, %out_desc: !tt.tensordesc>, %out_desc_5: !tt.ptr, %out_desc_6: i32, %out_desc_7: i32 {tt.divisibility = 16 : i32}, %out_desc_8: i64, %out_desc_9: i64) attributes {noinline = false} { + %cst = arith.constant dense<1.000000e+00> : tensor<2x128xf16> + %c0_i32 = arith.constant 0 : i32 + %0 = tt.descriptor_load %in_desc[%c0_i32, %c0_i32] : !tt.tensordesc> -> tensor<2x128xf16> + %1 = arith.addf %0, %cst : tensor<2x128xf16> + tt.descriptor_store %out_desc[%c0_i32, %c0_i32], %1 : !tt.tensordesc>, tensor<2x128xf16> + tt.return + } +} + +// CHECK-LABEL: entry @host_tma_load_store +// CHECK-SAME: {{.*}}, {{.*}}, %[[ARG2:.*]]: tile, %[[ARG3:.*]]: tile, %[[ARG4:.+]]: tile, {{.*}}: tile, {{.*}}, {{.*}}, %[[ARG8:.*]]: tile, %[[ARG9:.*]]: tile, {{.*}} +// CHECK: %[[ASSUME2:.*]] = assume div_by<16>, %[[ARG9]] : tile +// CHECK: %[[ASSUME1:.*]] = assume div_by<16>, %[[ARG3]] : tile +// CHECK: %[[EXT8:.*]] = exti %[[ARG8]] signed : tile -> tile +// Verify shape bounds use TMA hardware limit (2^32 - 1 = 4294967295) +// CHECK: assume bounded<0, 4294967295>, %[[EXT8]] : tile +// CHECK: %[[EXT9:.*]] = exti %[[ASSUME2]] signed : tile -> tile +// CHECK: assume bounded<0, 4294967295>, %[[EXT9]] : tile +// CHECK: %[[VIEW2:.*]] = make_tensor_view {{.*}}, shape = [{{.*}}, {{.*}}], {{.*}} + +// Verify stride bounds use TMA hardware limit (2^40 - 1 = 1099511627775) +// CHECK: %[[EXT2:.*]] = exti %[[ARG2]] signed : tile -> tile +// CHECK: assume bounded<0, 4294967295>, %[[EXT2]] : tile +// CHECK: %[[EXT3:.*]] = exti %[[ASSUME1]] signed : tile -> tile +// CHECK: assume bounded<0, 4294967295>, %[[EXT3]] : tile +// CHECK: %{{.+}} = assume bounded<0, 1099511627775>, %[[ARG4]] : tile +// CHECK: %[[VIEW1:.*]] = make_tensor_view {{.*}}, shape = [{{.*}}, {{.*}}], {{.*}} diff --git a/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir b/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir new file mode 100644 index 0000000000..dc3acc197f --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir @@ -0,0 +1,6 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{approx-modifier=true flush-to-zero-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=APPROX_FTZ %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{approx-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=APPROX %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{flush-to-zero-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=FTZ %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{compute-capability=100 num-cta-in-cga=2},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=HINT-100 %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{compute-capability=120 num-cta-in-cga=4},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=HINT-120 %s + diff --git a/third_party/tileir/test/FileCheck/op-conversion-xfailure.mlir b/third_party/tileir/test/FileCheck/op-conversion-xfailure.mlir new file mode 100644 index 0000000000..4e87d7de46 --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion-xfailure.mlir @@ -0,0 +1 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts)" -verify-diagnostics diff --git a/third_party/tileir/test/FileCheck/op-conversion.mlir b/third_party/tileir/test/FileCheck/op-conversion.mlir new file mode 100644 index 0000000000..d10a603725 --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-conversion.mlir @@ -0,0 +1,2 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts)" -verify-diagnostics | FileCheck %s + diff --git a/third_party/tileir/test/FileCheck/op-rewrite-assume.mlir b/third_party/tileir/test/FileCheck/op-rewrite-assume.mlir new file mode 100644 index 0000000000..69bb42286d --- /dev/null +++ b/third_party/tileir/test/FileCheck/op-rewrite-assume.mlir @@ -0,0 +1,57 @@ +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(rewrite-assume-with-cuda-tile)" | FileCheck %s + +// CHECK-LABEL: kernel_assume +// CHECK: [[V0:%.*]] = builtin.unrealized_conversion_cast {{.*}} : i32 to !cuda_tile.tile +// CHECK-NEXT: [[V1:%.*]] = cuda_tile.assume div_by<32>, [[V0]] : tile +// CHECK-NEXT: builtin.unrealized_conversion_cast [[V1]] : !cuda_tile.tile to i32 {tt.divisibility = 32 : i32} +module @kernel_assume { + tt.func private @assume(%arg0: i32 {tt.divisibility = 16 : i32}) -> i32 { + %c32_i32 = arith.constant 32 : i32 + %c0_i32 = arith.constant 0 : i32 + %0 = arith.remsi %arg0, %c32_i32 : i32 + %1 = arith.cmpi eq, %0, %c0_i32 : i32 + llvm.intr.assume %1 : i1 + %2 = arith.addi %arg0, %arg0 : i32 + tt.return %2 : i32 + } +} + +// ----- + +// CHECK-LABEL: kernel_assume_2 +// CHECK: tt.ptr_to_int [[V0:%.*]] {tt.divisibility = 32 : i64} : !tt.ptr -> i64 +// CHECK-NEXT: [[V1:%.*]] = builtin.unrealized_conversion_cast [[V0]] : !tt.ptr to !cuda_tile.tile> +// CHECK-NEXT: cuda_tile.assume div_by<32>, [[V1]] : tile> +module @kernel_assume_2 { + tt.func private @assume_2(%arg0: !tt.ptr {tt.divisibility = 16 : i32}) -> i64 { + %0 = tt.ptr_to_int %arg0 : !tt.ptr -> i64 + %c32_i64 = arith.constant 32 : i64 + %c0_i64 = arith.constant 0 : i64 + %1 = arith.remsi %0, %c32_i64 : i64 + %2 = arith.cmpi eq, %1, %c0_i64 : i64 + llvm.intr.assume %2 : i1 + %3 = arith.addi %0, %0 : i64 + tt.return %3 : i64 + } +} + +// ----- + +// CHECK-LABEL: kernel_assume_3 +// CHECK: [[V0:%.*]] = arith.addi {{.*}}, {{.*}} {tt.divisibility = 32 : i64} : i64 +// CHECK-NEXT: [[V1:%.*]] = builtin.unrealized_conversion_cast [[V0]] : i64 to !cuda_tile.tile +// CHECK-NEXT: [[V2:%.*]] = cuda_tile.assume div_by<32>, [[V1]] : tile +// CHECK-NEXT: builtin.unrealized_conversion_cast [[V2]] : !cuda_tile.tile to i64 {tt.divisibility = 32 : i64} +module @kernel_assume_3 { + tt.func private @assume_3(%arg0: !tt.ptr {tt.divisibility = 16 : i32}) -> i64 { + %0 = tt.ptr_to_int %arg0 : !tt.ptr -> i64 + %c32_i64 = arith.constant 32 : i64 + %1 = arith.addi %0, %c32_i64 : i64 + %c0_i64 = arith.constant 0 : i64 + %2 = arith.remsi %1, %c32_i64 : i64 + %3 = arith.cmpi eq, %2, %c0_i64 : i64 + llvm.intr.assume %3 : i1 + %4 = arith.addi %1, %1 : i64 + tt.return %4 : i64 + } +} diff --git a/third_party/tileir/test/lit.cfg.py b/third_party/tileir/test/lit.cfg.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/third_party/tileir/test/lit.site.cfg.py.in b/third_party/tileir/test/lit.site.cfg.py.in new file mode 100644 index 0000000000..e69de29bb2 From d5a5c878ad8ab455b5186e2c8342d87c32b139a2 Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Mon, 15 Jun 2026 19:20:23 -0700 Subject: [PATCH 06/11] [tileir] Clean tutorial benchmark comments Remove internal release markers from the ported TileIR tutorial benchmark sources while preserving SPDX headers and ordinary section comments. --- .../tilegym/ops/triton/activation/gelu.py | 14 +++-- .../tilegym/ops/triton/activation/relu.py | 52 ++++++++----------- .../tilegym/ops/triton/activation/silu.py | 8 +-- .../tilegym/ops/triton/activation/tanh.py | 8 +-- .../tileir/tilegym/ops/triton/attention.py | 24 ++++----- .../tileir/tilegym/ops/triton/bmm.py | 15 +++--- .../ops/triton/linear_bias_activation.py | 13 +++-- .../tileir/tilegym/ops/triton/matmul.py | 48 ++++------------- .../tilegym/ops/triton/matmul_perf_model.py | 1 - .../tileir/tilegym/ops/triton/mla.py | 20 ++++--- .../tileir/tilegym/ops/triton/mla_decoding.py | 16 +++--- .../tileir/tilegym/ops/triton/rope.py | 20 +++---- 12 files changed, 91 insertions(+), 148 deletions(-) diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py index 48ae766850..37b0d1970d 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py @@ -2,17 +2,17 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import torch import triton import triton.language as tl -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl from .tanh import tanh_forward -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -79,7 +79,7 @@ def gelu_backward(x, dy): return dy * (_standard_normal_cdf(x) + x * _standard_normal_pdf(x)) -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -122,7 +122,7 @@ def _gelu_kernel_backward( tl.store(dx_ptr + offsets, gelu_grad_output, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _GeLU(torch.autograd.Function): @@ -172,10 +172,8 @@ def gelu(input: torch.Tensor, approximate="none"): return _GeLU.apply(input.view(-1), approximate).view(input.shape) -# {$nv-internal-release begin} -# Backend Registration & Perf Markers # {$nv-internal-release} +# Backend Registration & Perf Markers mark_perf_ready("gelu", "nvt") -# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py index 21dd352f79..ffbb8dafc9 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py @@ -18,7 +18,7 @@ import triton.language as tl from tilegym.backend import get_available_triton_backend -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl from .utils import exp_forward @@ -29,9 +29,9 @@ ======================================================= """ -# Feature: relu # {$nv-internal-release} +# Feature: relu -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -45,7 +45,7 @@ def relu_backward(x, dy): return dydx * dy -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -87,7 +87,7 @@ def _relu_bwd_kernel( tl.store(dx_ptr + offsets, dx, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _ReLU(torch.autograd.Function): @@ -130,9 +130,9 @@ def relu(x): ======================================================= """ -# Feature: elu # {$nv-internal-release} +# Feature: elu -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -177,7 +177,7 @@ def _elu_bwd_kernel( tl.store(dx_ptr + offsets, dx, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _ELU(torch.autograd.Function): @@ -229,9 +229,9 @@ def elu(x, alpha=1.0): ======================================================= """ -# Feature: leaky_relu # {$nv-internal-release} +# Feature: leaky_relu -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -276,7 +276,7 @@ def _leaky_relu_bwd_kernel( tl.store(dx_ptr + offsets, dx, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _LeakyReLU(torch.autograd.Function): @@ -329,9 +329,9 @@ def leaky_relu(x, negative_slope=0.01): ======================================================= """ -# Feature: prelu # {$nv-internal-release} +# Feature: prelu -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -433,7 +433,6 @@ def _vector_prelu_nchw_fwd_kernel( tl.store(y_ptr + offsets, y, mask=mask) -# {$nv-internal-release begin} if get_available_triton_backend() == "nvt": @triton.jit @@ -699,9 +698,8 @@ def _vector_prelu_nc_bwd_stage2_kernel( tl.store(dw_ptr + col_offsets, sum_dw, mask=col_offsets < C) -# {$nv-internal-release end} -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _PReLU(torch.autograd.Function): @@ -750,7 +748,6 @@ def forward(ctx, x, w): @staticmethod def backward(ctx, dy): - # {$nv-internal-release begin} if get_available_triton_backend() == "nvt": GROUP_SIZE = 256 BLOCK_SIZE = 1024 @@ -847,7 +844,6 @@ def backward(ctx, dy): # ) return dx, dw - # {$nv-internal-release end} raise NotImplementedError("Backward is not supported") @@ -881,9 +877,9 @@ def prelu(x, weight): ======================================================= """ -# Feature: rrelu # {$nv-internal-release} +# Feature: rrelu -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -944,7 +940,7 @@ def _rrelu_bwd_kernel( tl.store(dx_ptr + offsets, dx, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _RReLU(torch.autograd.Function): @@ -1016,9 +1012,9 @@ def rrelu(x, seed, lower=1.0 / 8, upper=1.0 / 3, training=False): ======================================================= """ -# Feature: selu # {$nv-internal-release} +# Feature: selu -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -1067,7 +1063,7 @@ def _selu_bwd_kernel( tl.store(dx_ptr + offsets, dx, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _SELU(torch.autograd.Function): @@ -1118,9 +1114,9 @@ def selu(x): ======================================================= """ -# Feature: celu # {$nv-internal-release} +# Feature: celu -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -1165,7 +1161,7 @@ def _celu_bwd_kernel( tl.store(dx_ptr + offsets, dx, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _CELU(torch.autograd.Function): @@ -1208,7 +1204,6 @@ def celu(x, alpha=1.0): return _CELU.apply(x, alpha) -# {$nv-internal-release begin} mark_perf_ready("celu", "nvt") @@ -1219,4 +1214,3 @@ def celu(x, alpha=1.0): mark_perf_ready("rrelu", "nvt") mark_perf_ready("selu", "nvt") -# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py index b09c65bdc3..21dbdaa758 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import torch import triton import triton.language as tl @@ -11,7 +11,7 @@ from .utils import sigmoid_forward -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -24,7 +24,7 @@ def silu_backward(x, dy): return dy * (sigmoid_forward(x) * (1.0 + x * (1.0 - sigmoid_forward(x)))) -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -69,7 +69,7 @@ def _silu_kernel_backward( tl.store(dx_ptr + offsets, silu_grad_output, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _SiLU(torch.autograd.Function): diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py b/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py index f99b2ec99f..e27f32dc00 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import torch import triton import triton.language as tl @@ -11,7 +11,7 @@ from .utils import sigmoid_forward -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -19,7 +19,7 @@ def tanh_forward(x): return 2 * sigmoid_forward(2 * x) - 1 -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -64,7 +64,7 @@ def _tanh_kernel_backward( tl.store(dx_ptr + offsets, tanh_grad_output, mask=mask) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _Tanh(torch.autograd.Function): diff --git a/python/tutorials/tileir/tilegym/ops/triton/attention.py b/python/tutorials/tileir/tilegym/ops/triton/attention.py index d0f72a3e85..b867d07a0c 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/attention.py +++ b/python/tutorials/tileir/tilegym/ops/triton/attention.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import inspect import math from typing import Optional @@ -14,19 +14,18 @@ from tilegym.autotune import is_autotune_disabled from tilegym.backend import get_available_triton_backend -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl from tilegym.logger import get_logger -# Constants & Type Aliases # {$nv-internal-release} +# Constants & Type Aliases logger = get_logger(__name__) INV_LOG_2 = tl.constexpr(1.0 / math.log(2)) -# {$nv-internal-release begin} # Check if triton.Config supports v2_opt_level parameter -# Capability Probe # {$nv-internal-release} +# Capability Probe def _supports_v2_opt_level(): @@ -39,7 +38,6 @@ def _supports_v2_opt_level(): _TRITON_SUPPORTS_V2_OPT_LEVEL = _supports_v2_opt_level() -# {$nv-internal-release end} def _supports_host_descriptor(): @@ -52,7 +50,7 @@ def _supports_tma(tensor: torch.Tensor): return torch.finfo(tensor.dtype).bits * tensor.stride(-2) // 8 % 16 == 0 -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -324,7 +322,7 @@ def prefill_fmha_impl( ) -# Autotune Config # {$nv-internal-release} +# Autotune Config def _host_descriptor_pre_hook_fwd(nargs): @@ -418,7 +416,6 @@ def _get_configs(is_backward=False): dict(BLOCK_M=256, BLOCK_N=128, occupancy=2, **default_config), num_ctas=2, pre_hook=_hook ) ) - # {$nv-internal-release-oait begin} elif get_available_triton_backend() == "oait": # Full tuning space for oait if is_backward: @@ -448,7 +445,6 @@ def _get_configs(is_backward=False): for s in [2, 4] for w in [4, 8] ] - # {$nv-internal-release-oait end} return configs @@ -460,7 +456,7 @@ def _prune_invalid_configs(configs, named_args, **kwargs): return configs -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.autotune( @@ -807,7 +803,7 @@ def _fmha_bwd_kernel( dV_desc.store([offset_b, offset_h, offset_skv, 0], dv.to(dtype).reshape(1, 1, BLOCK_N, BLOCK_D)) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _attention(torch.autograd.Function): @@ -1121,10 +1117,8 @@ def triton_fmha( return o -# {$nv-internal-release begin} -# Backend Registration & Perf Markers # {$nv-internal-release} +# Backend Registration & Perf Markers mark_perf_ready("fmha", "nvt") -# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/bmm.py b/python/tutorials/tileir/tilegym/ops/triton/bmm.py index c5de02de8c..784698eac5 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/bmm.py +++ b/python/tutorials/tileir/tilegym/ops/triton/bmm.py @@ -2,9 +2,8 @@ # # SPDX-License-Identifier: MIT -# {$nv-internal-release file} -# Imports # {$nv-internal-release} +# Imports from typing import Optional import torch @@ -16,12 +15,12 @@ from tilegym.backend import register_impl from tilegym.logger import get_logger -# Constants & Type Aliases # {$nv-internal-release} +# Constants & Type Aliases logger = get_logger(__name__) -# Capability Probe # {$nv-internal-release} +# Capability Probe def _is_cuda(): @@ -35,7 +34,7 @@ def _supports_host_descriptor(): return _is_cuda() and torch.cuda.get_device_capability()[0] >= 9 -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -84,7 +83,7 @@ def _bmm_calculate_pid(pid, M, N, BLOCK_M, BLOCK_N, GROUP_SIZE_M): return pid_q, pid_m, pid_n -# Autotune Config # {$nv-internal-release} +# Autotune Config def _bmm_memref_get_configs(pre_hook=None): @@ -234,7 +233,7 @@ def _bmm_memref_get_configs(pre_hook=None): ] -# Device Kernels # {$nv-internal-release} +# Device Kernels # Adapted from https://github.com/openai/triton @@ -501,7 +500,7 @@ def _bmm_kernel_memref( c_desc.store([pid_q * 1, pid_m * BLOCK_M, pid_n * BLOCK_N], c) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API def bmm_fn(a, b, transpose_a=False, transpose_b=False): diff --git a/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py b/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py index 8c59640928..277f372a3c 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py +++ b/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py @@ -2,9 +2,8 @@ # # SPDX-License-Identifier: MIT -# {$nv-internal-release file} -# Imports # {$nv-internal-release} +# Imports import itertools import math @@ -28,7 +27,7 @@ from .matmul_perf_model import early_config_prune from .matmul_perf_model import estimate_matmul_time -# Constants & Type Aliases # {$nv-internal-release} +# Constants & Type Aliases supported_act_types = { @@ -38,7 +37,7 @@ } -# Autotune Config # {$nv-internal-release} +# Autotune Config def _init_to_zero(name): @@ -266,7 +265,7 @@ def _get_configs_io_bound(): ) -# Device Kernels # {$nv-internal-release} +# Device Kernels # TODO: add split_k support @@ -569,7 +568,7 @@ def matmul_dropout_activation_bias_bwd( C.store([offset_cm, offset_cn], store_acc) -# Host Helpers # {$nv-internal-release} +# Host Helpers def get_tensor_alignment(tensor): @@ -628,7 +627,7 @@ def pad_for_tma_2d(t): return padded[:, :n] -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API @register_impl("linear_bias_activation", backend="triton") diff --git a/python/tutorials/tileir/tilegym/ops/triton/matmul.py b/python/tutorials/tileir/tilegym/ops/triton/matmul.py index 0d29a250ac..e304a8d595 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/matmul.py +++ b/python/tutorials/tileir/tilegym/ops/triton/matmul.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import os from typing import Optional @@ -12,11 +12,11 @@ from triton.tools.tensor_descriptor import TensorDescriptor from tilegym.backend import get_available_triton_backend -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl from tilegym.logger import get_logger -# Constants & Type Aliases # {$nv-internal-release} +# Constants & Type Aliases logger = get_logger(__name__) @@ -24,7 +24,7 @@ def _disable_autotune(): return os.getenv("TILEGYM_DISABLE_AUTOTUNE", "0") == "1" -# Capability Probe # {$nv-internal-release} +# Capability Probe def _is_cuda(): @@ -38,7 +38,7 @@ def _supports_host_descriptor(): return _is_cuda() and torch.cuda.get_device_capability()[0] >= 8 -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -66,7 +66,7 @@ def _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M, NUM_SMS): return pid_m, pid_n -# Autotune Config # {$nv-internal-release} +# Autotune Config def _get_cuda_autotune_config(pre_hook=None): @@ -135,9 +135,7 @@ def _get_pure_ptr_autotune_config(): def _host_descriptor_pre_hook(nargs): - # {$nv-internal-release-oait begin} EPILOGUE_SUBTILE = nargs.get("EPILOGUE_SUBTILE", False) - # {$nv-internal-release-oait end} BLOCK_SIZE_M = nargs["BLOCK_SIZE_M"] BLOCK_SIZE_N = nargs["BLOCK_SIZE_N"] BLOCK_SIZE_K = nargs["BLOCK_SIZE_K"] @@ -152,17 +150,14 @@ def _host_descriptor_pre_hook(nargs): else: nargs["B"].block_shape = [BLOCK_SIZE_K, BLOCK_SIZE_N] nargs["C"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_N] - # {$nv-internal-release-oait begin} if EPILOGUE_SUBTILE: nargs["C"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_N // 2] else: nargs["C"].block_shape = [BLOCK_SIZE_M, BLOCK_SIZE_N] - # {$nv-internal-release-oait end} # from https://github.com/triton-lang/triton/blob/main/python/tutorials/09-persistent-matmul.py def _matmul_get_configs(pre_hook=None): - # {$nv-internal-release-oait begin} if get_available_triton_backend() == "oait": # fmt: off # default config from triton @@ -184,7 +179,6 @@ def _matmul_get_configs(pre_hook=None): ] ) return config - # {$nv-internal-release-oait end} if torch.cuda.get_device_capability() in [(12, 0), (12, 1)]: # Default config from triton config = [ @@ -451,7 +445,6 @@ def _early_config_prune(configs, named_args, **kwargs): def _matmul_tma_persistent_get_configs(pre_hook=None): # fmt: off - # {$nv-internal-release-oait begin} if get_available_triton_backend() == "oait": return [ triton.Config( @@ -474,7 +467,6 @@ def _matmul_tma_persistent_get_configs(pre_hook=None): for w in [4, 8] for SUBTILE in [True, False] ] - # {$nv-internal-release-oait end} if torch.cuda.get_device_capability() in [(12, 0), (12, 1)]: configs = [ triton.Config( @@ -483,9 +475,7 @@ def _matmul_tma_persistent_get_configs(pre_hook=None): 'BLOCK_SIZE_N': BN, "BLOCK_SIZE_K": BK, "GROUP_SIZE_M": 8, - # {$nv-internal-release-oait begin} "EPILOGUE_SUBTILE": SUBTILE, - # {$nv-internal-release-oait end} "occupancy": occ, }, pre_hook=pre_hook, @@ -505,9 +495,7 @@ def _matmul_tma_persistent_get_configs(pre_hook=None): 'BLOCK_SIZE_N': 256, "BLOCK_SIZE_K": 64, "GROUP_SIZE_M": 8, - # {$nv-internal-release-oait begin} "EPILOGUE_SUBTILE": False, - # {$nv-internal-release-oait end} "occupancy": 1, }, pre_hook=pre_hook, @@ -539,9 +527,7 @@ def _matmul_tma_persistent_get_configs(pre_hook=None): 'BLOCK_SIZE_N': 256, "BLOCK_SIZE_K": 64, "GROUP_SIZE_M": 8, - # {$nv-internal-release-oait begin} "EPILOGUE_SUBTILE": False, - # {$nv-internal-release-oait end} "occupancy": 1 }, num_stages=5, @@ -557,9 +543,7 @@ def _matmul_tma_persistent_get_configs(pre_hook=None): 'BLOCK_SIZE_N': BN, "BLOCK_SIZE_K": BK, "GROUP_SIZE_M": 8, - # {$nv-internal-release-oait begin} "EPILOGUE_SUBTILE": False, - # {$nv-internal-release-oait end} "occupancy": 1}, pre_hook=pre_hook, num_ctas=NUM_CTAS, @@ -715,7 +699,7 @@ def _early_config_prune_for_persistent(configs, named_args, **kwargs): return _prune_configs_by_shape(configs, named_args) -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.autotune( @@ -848,20 +832,16 @@ def _static_persistent_matmul_kernel( transpose_a: tl.constexpr, transpose_b: tl.constexpr, GROUP_SIZE_M: tl.constexpr, - # {$nv-internal-release-oait begin} EPILOGUE_SUBTILE: tl.constexpr, - # {$nv-internal-release-oait end} DTYPE: tl.constexpr, occupancy: tl.constexpr, num_ctas: tl.constexpr, ): """Static persistent matmul kernel: C = A @ B with static scheduling.""" - # {$nv-internal-release-oait begin} """ This kernel is adapted from triton tutorial, for oait get the best performance on that. For NVTriton, we don't need to introdce `tile_id_c`, `flatten`, `warp_specialize`, `EPILOGUE_SUBTILE`. """ - # {$nv-internal-release-oait end} dtype = C.type.block_type.element_ty # Get program ID @@ -874,12 +854,10 @@ def _static_persistent_matmul_kernel( num_tiles = num_pid_m * num_pid_n num_programs = tl.num_programs(0) - # {$nv-internal-release-oait begin} # tile_id_c is used in the epilogue to break the dependency between # the prologue and the epilogue in oait. In NVTiton, we don't need to introdce this, # as well as `flatten` and `warp_specialize`. We don't need to recalculate the pid_m and pid_n using tile_id_c tile_id_c = start_pid - num_programs - # {$nv-internal-release-oait end} num_pid_in_group = GROUP_SIZE_M * num_pid_n # Static persistent scheduling loop @@ -915,14 +893,11 @@ def _static_persistent_matmul_kernel( # Matrix multiplication and accumulation accumulator = tl.dot(a, b, accumulator) - # {$nv-internal-release-oait begin} tile_id_c += num_programs pid_m, pid_n = _compute_pid(tile_id_c, num_pid_in_group, num_pid_m, GROUP_SIZE_M, num_programs) - # {$nv-internal-release-oait indent-end} offs_am_c = pid_m * BLOCK_SIZE_M offs_bn_c = pid_n * BLOCK_SIZE_N - # {$nv-internal-release-oait begin} # Epilogue subtiling is a technique to break our computation and stores into multiple pieces # By subtiling we can reduce shared memory consumption by the epilogue and instead use that # memory to increase our stage count. @@ -936,15 +911,12 @@ def _static_persistent_matmul_kernel( c1 = acc1.to(dtype) C.store([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2], c1) else: - # {$nv-internal-release-oait end} - # {$nv-internal-release-oait indent-begin -4} # Convert to output dtype and store result = accumulator.to(dtype) C.store([offs_am_c, offs_bn_c], result) - # {$nv-internal-release-oait indent-end} -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API def matmul_fn( @@ -1111,10 +1083,8 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): return MatmulFunction.apply(a, b, trans_a, trans_b) -# {$nv-internal-release begin} -# Backend Registration & Perf Markers # {$nv-internal-release} +# Backend Registration & Perf Markers mark_perf_ready("matmul", "nvt") -# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py b/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py index ade1cd7e2a..f28a416125 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py +++ b/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT -# {$nv-internal-release file} # Adapted from https://github.com/openai/triton import heapq diff --git a/python/tutorials/tileir/tilegym/ops/triton/mla.py b/python/tutorials/tileir/tilegym/ops/triton/mla.py index ecd25d036a..0dd7936e78 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/mla.py +++ b/python/tutorials/tileir/tilegym/ops/triton/mla.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import math from typing import Optional @@ -13,24 +13,24 @@ from tilegym.autotune import is_autotune_disabled from tilegym.backend import get_available_triton_backend -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl from tilegym.logger import get_logger -# Constants & Type Aliases # {$nv-internal-release} +# Constants & Type Aliases logger = get_logger(__name__) INV_LOG_2 = tl.constexpr(1.0 / math.log(2)) -# Capability Probe # {$nv-internal-release} +# Capability Probe def _supports_host_descriptor(): return torch.cuda.get_device_capability()[0] >= 9 -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -139,7 +139,7 @@ def _attn_fwd_inner( return acc, l_i, m_i -# Autotune Config # {$nv-internal-release} +# Autotune Config def _host_descriptor_pre_hook(nargs): @@ -255,7 +255,7 @@ def keep(conf): return configs -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.autotune( @@ -470,7 +470,7 @@ def _prefill_mla( ) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _attention(torch.autograd.Function): @@ -635,10 +635,8 @@ def triton_mla(q, k, v, qpe, kpe, is_causal, scaling, **kwargs): return o -# {$nv-internal-release begin} -# Backend Registration & Perf Markers # {$nv-internal-release} +# Backend Registration & Perf Markers mark_perf_ready("mla", "nvt") -# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py b/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py index ed582bc652..828ab19ae9 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py +++ b/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import math from typing import Optional @@ -11,14 +11,14 @@ import triton.language as tl from tilegym.backend import get_available_triton_backend -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl -# Constants & Type Aliases # {$nv-internal-release} +# Constants & Type Aliases INV_LOG_2 = tl.constexpr(1.0 / math.log(2)) -# Autotune Config # {$nv-internal-release} +# Autotune Config def _get_mla_decoding_configs(): @@ -35,7 +35,7 @@ def _get_mla_decoding_configs(): return base_configs -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.autotune( @@ -353,7 +353,7 @@ def _naive_absorb_mla_transpose( O_desc.store([batch_idx, pid_x * BLOCK_H, 0], acc.reshape(1, BLOCK_H, BLOCK_D)) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API class _mla_decoding(torch.autograd.Function): @@ -449,10 +449,8 @@ def mla_decoding( return _mla_decoding.apply(q, qpe, kv, kpe, sm_scale, transpose) -# {$nv-internal-release begin} -# Backend Registration & Perf Markers # {$nv-internal-release} +# Backend Registration & Perf Markers mark_perf_ready("mla_decoding", "nvt") -# {$nv-internal-release end} diff --git a/python/tutorials/tileir/tilegym/ops/triton/rope.py b/python/tutorials/tileir/tilegym/ops/triton/rope.py index cd998aad8d..69d040a502 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/rope.py +++ b/python/tutorials/tileir/tilegym/ops/triton/rope.py @@ -2,24 +2,24 @@ # # SPDX-License-Identifier: MIT -# Imports # {$nv-internal-release} +# Imports import torch import triton import triton.language as tl from triton.tools.tensor_descriptor import TensorDescriptor from tilegym.backend import get_current_backend -from tilegym.backend import mark_perf_ready # {$nv-internal-release} +from tilegym.backend import mark_perf_ready from tilegym.backend import register_impl -# Capability Probe # {$nv-internal-release} +# Capability Probe def _supports_host_descriptor(): return torch.cuda.get_device_capability()[0] >= 9 -# Kernel Helpers # {$nv-internal-release} +# Kernel Helpers @triton.jit @@ -30,7 +30,7 @@ def _maybe_make_tensor_desc(desc_or_ptr, shape, strides, block_shape): return tl.make_tensor_descriptor(desc_or_ptr, shape, strides, block_shape) -# Device Kernels # {$nv-internal-release} +# Device Kernels @triton.jit @@ -307,7 +307,7 @@ def _rope_kernel( ) -# Host Launchers & Public API # {$nv-internal-release} +# Host Launchers & Public API def _rope_forward_tma(q, k, cos, sin): @@ -337,7 +337,6 @@ def _rope_forward_tma(q, k, cos, sin): # Launch kernel - # {$nv-internal-release-oait begin} # Create tensor descriptors if supported if _supports_host_descriptor() and get_current_backend() == "oait": desc_q = TensorDescriptor( @@ -365,14 +364,11 @@ def _rope_forward_tma(q, k, cos, sin): block_shape=[1, 1, 1, BLOCK_HD], ) else: - # {$nv-internal-release-oait end} - # {$nv-internal-release-oait indent-begin -4} # Because nvt does not support divisibility for shape, we use device descriptor here desc_q = q desc_k = k desc_cos = cos desc_sin = sin - # {$nv-internal-release-oait indent-end} _triton_rope_kernel_tma[(n_row,)]( desc_q, @@ -658,10 +654,8 @@ def wrapper(q, k, freqs_cis): raise ValueError(f"Unsupported model: {model}") -# {$nv-internal-release begin} -# Backend Registration & Perf Markers # {$nv-internal-release} +# Backend Registration & Perf Markers mark_perf_ready("apply_rope_base", "nvt") -# {$nv-internal-release end} From ba8dc54d4d814d19f51fe2f741fbeab879c2df9d Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Tue, 16 Jun 2026 00:06:46 -0700 Subject: [PATCH 07/11] [tileir] Polish tutorials and vendor notes --- .../tileir/01-load-view-token-ordering.py | 20 +-- .../tileir/02-mixed-kernel-routing.py | 62 +++---- .../tileir/03-triton-tileir-benchmarks.py | 160 +++++++++++++----- python/tutorials/tileir/README.md | 78 +++++++++ ...ference_speedup_heatmap_h100_sxm5_80gb.png | Bin 0 -> 51204 bytes third_party/tileir/README.flagtree.md | 42 ----- third_party/tileir/README.md | 82 ++++++++- 7 files changed, 308 insertions(+), 136 deletions(-) create mode 100644 python/tutorials/tileir/README.md create mode 100644 python/tutorials/tileir/assets/reference_speedup_heatmap_h100_sxm5_80gb.png delete mode 100644 third_party/tileir/README.flagtree.md diff --git a/python/tutorials/tileir/01-load-view-token-ordering.py b/python/tutorials/tileir/01-load-view-token-ordering.py index 25ae69206d..2498090061 100644 --- a/python/tutorials/tileir/01-load-view-token-ordering.py +++ b/python/tutorials/tileir/01-load-view-token-ordering.py @@ -7,7 +7,6 @@ ``store_view_tko``, and checks the result against PyTorch. """ -import argparse import os import subprocess import sys @@ -86,7 +85,7 @@ def run_positive(): x, y, n_elements, BLOCK_SIZE=block, num_warps=4 ) torch.testing.assert_close(y, x + 2) - print("[OK] TileIR TKO view-token kernel") + print("[OK] FLAGTREE_USE_TILEIR=1: TileIR TKO view-token kernel") def run_expected_native_failure(): @@ -105,7 +104,7 @@ def run_expected_native_failure(): expected_message = "TileIR path" in text or "FLAGTREE_USE_TILEIR" in text if not expected_message: raise - print("[OK] native NVIDIA path reports the expected TileIR-only API error") + print("[OK] FLAGTREE_USE_TILEIR=0: native NVIDIA path reports the expected TileIR-only API error") return raise AssertionError("expected native NVIDIA path to reject TKO view-token APIs") @@ -113,24 +112,17 @@ def run_expected_native_failure(): def run_negative_subprocess(): env = os.environ.copy() env.pop("FLAGTREE_USE_TILEIR", None) - env.setdefault("TRITON_BACKENDS_IN_TREE", "1") - cmd = [sys.executable, __file__, "--expect-native-failure"] - subprocess.run(cmd, env=env, check=True) + env["FLAGTREE_TILEIR_TUTORIAL_EXPECT_NATIVE_FAILURE"] = "1" + subprocess.run([sys.executable, __file__], env=env, check=True) def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--expect-native-failure", action="store_true") - parser.add_argument("--skip-native-failure", action="store_true") - args = parser.parse_args() - - if args.expect_native_failure: + if os.environ.get("FLAGTREE_TILEIR_TUTORIAL_EXPECT_NATIVE_FAILURE") == "1": run_expected_native_failure() return run_positive() - if not args.skip_native_failure: - run_negative_subprocess() + run_negative_subprocess() if __name__ == "__main__": diff --git a/python/tutorials/tileir/02-mixed-kernel-routing.py b/python/tutorials/tileir/02-mixed-kernel-routing.py index b81c41888a..fda2a3f2aa 100644 --- a/python/tutorials/tileir/02-mixed-kernel-routing.py +++ b/python/tutorials/tileir/02-mixed-kernel-routing.py @@ -10,7 +10,6 @@ import os import pathlib -import tempfile os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") @@ -48,36 +47,37 @@ def main(): if not torch.cuda.is_available(): raise RuntimeError("this tutorial requires a CUDA GPU") - with tempfile.TemporaryDirectory(prefix="flagtree_tileir_routing_") as tmp: - cache_dir = pathlib.Path(tmp) - os.environ["TRITON_CACHE_DIR"] = str(cache_dir) - os.environ["FLAGTREE_USE_TILEIR"] = "1" - - n_elements = 1024 - block = 256 - x = torch.arange(n_elements, device="cuda", dtype=torch.float32) - y_tileir = torch.empty_like(x) - y_native = torch.empty(block, device="cuda", dtype=torch.float32) - - _plain_add_kernel[(triton.cdiv(n_elements, block),)]( - x, y_tileir, n_elements, BLOCK_SIZE=block, num_warps=4 - ) - _tle_shared_memory_kernel[(1,)](x, y_native, BLOCK_SIZE=block, num_warps=4) - - torch.testing.assert_close(y_tileir, x + 1) - torch.testing.assert_close(y_native, x[:block] + 2) - - files = _cache_files(cache_dir) - plain_used_tileir = any(name == "_plain_add_kernel.tileir" for name in files) - tle_used_native = any(name == "_tle_shared_memory_kernel.ptx" for name in files) - tle_used_tileir = any(name == "_tle_shared_memory_kernel.tileir" for name in files) - if not plain_used_tileir: - raise AssertionError("plain Triton kernel did not produce TileIR cache output") - if not tle_used_native or tle_used_tileir: - raise AssertionError("TLE shared-memory kernel did not fall back to native NVIDIA") - - print("[OK] plain Triton kernel routed to TileIR") - print("[OK] non-TileIR TLE kernel routed to native NVIDIA in the same process") + cache_dir = pathlib.Path(os.environ.get("TRITON_CACHE_DIR", "/tmp/flagtree_tileir_tutorial_02_cache")) + cache_dir.mkdir(parents=True, exist_ok=True) + os.environ["TRITON_CACHE_DIR"] = str(cache_dir) + os.environ["FLAGTREE_USE_TILEIR"] = "1" + + n_elements = 1024 + block = 256 + x = torch.arange(n_elements, device="cuda", dtype=torch.float32) + y_tileir = torch.empty_like(x) + y_native = torch.empty(block, device="cuda", dtype=torch.float32) + + _plain_add_kernel[(triton.cdiv(n_elements, block),)]( + x, y_tileir, n_elements, BLOCK_SIZE=block, num_warps=4 + ) + _tle_shared_memory_kernel[(1,)](x, y_native, BLOCK_SIZE=block, num_warps=4) + + torch.testing.assert_close(y_tileir, x + 1) + torch.testing.assert_close(y_native, x[:block] + 2) + + files = _cache_files(cache_dir) + plain_used_tileir = any(name == "_plain_add_kernel.tileir" for name in files) + tle_used_native = any(name == "_tle_shared_memory_kernel.ptx" for name in files) + tle_used_tileir = any(name == "_tle_shared_memory_kernel.tileir" for name in files) + if not plain_used_tileir: + raise AssertionError("plain Triton kernel did not produce TileIR cache output") + if not tle_used_native or tle_used_tileir: + raise AssertionError("TLE shared-memory kernel did not fall back to native NVIDIA") + + print("[OK] FLAGTREE_USE_TILEIR=1: plain Triton kernel routed to TileIR") + print("[OK] FLAGTREE_USE_TILEIR=1: non-TileIR TLE kernel routed to native NVIDIA in the same process") + print(f"[OK] TRITON_CACHE_DIR={cache_dir}") if __name__ == "__main__": diff --git a/python/tutorials/tileir/03-triton-tileir-benchmarks.py b/python/tutorials/tileir/03-triton-tileir-benchmarks.py index a7fd932c5e..c5ebd7f2aa 100644 --- a/python/tutorials/tileir/03-triton-tileir-benchmarks.py +++ b/python/tutorials/tileir/03-triton-tileir-benchmarks.py @@ -43,7 +43,6 @@ @dataclass(frozen=True) class PairSpec: case: str - group_id: str test_class: str dtype: str pair_id: str @@ -64,9 +63,9 @@ class BuiltCase: rtol: float = 1e-2 -def _add_pair(out: List[PairSpec], case: str, group_id: str, test_class: str, dtype: str, function_name: str): - idx = sum(1 for item in out if item.group_id == group_id) - out.append(PairSpec(case, group_id, test_class, dtype, f"{group_id}-{idx:02d}", function_name)) +def _add_pair(out: List[PairSpec], case: str, test_class: str, dtype: str, function_name: str): + idx = sum(1 for item in out if item.case == case) + out.append(PairSpec(case, test_class, dtype, f"{case}-{idx:02d}", function_name)) def _build_pair_specs() -> Tuple[PairSpec, ...]: @@ -79,7 +78,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "bmm", - "001", "Test_BMM_FWD", "float16", f"test_perf[FRAMEWORK-{batch}-{dim}-{dim}-{dim}-{trans_a}-{trans_b}-torch.float16]", @@ -90,7 +88,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "fmha", - "004", "Test_FMHA", "float8_e5m2", f"test_perf[FRAMEWORK-{is_causal}-4-32-{seq_len}-128-torch.float8_e5m2]", @@ -99,7 +96,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "fmha", - "004", "Test_FMHA", "float8_e5m2", f"test_perf_llm[FRAMEWORK-torch.float8_e5m2-llama-1-32-{seq_len}-128]", @@ -110,7 +106,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "linear_bias_act", - "015", "Test_LinearBiasAct", "float16", f"test_perf[FRAMEWORK-{dim}-{dim}-{dim}-{act}-torch.float16-True]", @@ -120,7 +115,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "mla", - "016", "Test_MLA", "float8_e5m2", f"test_perf[FRAMEWORK-True-4-32-{seq_len}-128-64-torch.float8_e5m2]", @@ -132,7 +126,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "mla_decoding", - "017", "Test_MLADecoding", "float16", f"test_perf[FRAMEWORK-{kv_len}-{batch}-512-64-float16-{kpe_dim}-{transpose}]", @@ -145,7 +138,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "matmul", - "021", "Test_Matmul", "float32", f"test_perf[FRAMEWORK-True-False-{trans_a}-{trans_b}-{dim}-{dim}-{dim}-0-0-torch.float32]", @@ -155,7 +147,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "matmul", - "021", "Test_Matmul", "float32", f"test_perf[FRAMEWORK-True-True-{trans_a}-False-{dim}-{dim}-{dim}-0-0-torch.float32]", @@ -167,7 +158,6 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: _add_pair( specs, "rope", - "024", "Test_RoPE", "float16", f"test_perf[FRAMEWORK-torch.float16-8-{seq_len}-{num_q_heads}-{num_kv_heads}-{head_dim}-{partial}]", @@ -580,6 +570,13 @@ def _cache_route(cache_dir: pathlib.Path) -> str: return "unknown" +def _group_specs_by_case(specs: Sequence[PairSpec]) -> List[Tuple[str, List[PairSpec]]]: + grouped: Dict[str, List[PairSpec]] = {} + for spec in specs: + grouped.setdefault(spec.case, []).append(spec) + return list(grouped.items()) + + def run_child( pair_id: str, path: str, @@ -711,6 +708,9 @@ def _run_subprocess( def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str], cache_root: pathlib.Path) -> dict: passed = sum(1 for row in rows if row.get("status") == "PASS") failed = len(rows) - passed + by_pair = {spec.pair_id: {} for spec in specs} + for row in rows: + by_pair[row["pair_id"]][row["path"]] = row summary = { "pairs": len(specs), "paths": list(paths), @@ -718,12 +718,42 @@ def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[ "passed": passed, "failed": failed, "cache_root": str(cache_root), + "cases": {}, } + for case, case_specs in _group_specs_by_case(specs): + case_rows = [row for row in rows if row["pair_id"] in {spec.pair_id for spec in case_specs}] + case_summary = { + "pairs": len(case_specs), + "runs": len(case_rows), + "passed": sum(1 for row in case_rows if row.get("status") == "PASS"), + "failed": sum(1 for row in case_rows if row.get("status") != "PASS"), + "passed_pairs": sum( + 1 + for spec in case_specs + if by_pair[spec.pair_id] + and all(row.get("status") == "PASS" for row in by_pair[spec.pair_id].values()) + ), + } + if set(paths) == {"native", "tileir"}: + case_ratios = [] + for spec in case_specs: + native = by_pair[spec.pair_id].get("native") + tileir = by_pair[spec.pair_id].get("tileir") + if native and tileir and native.get("status") == "PASS" and tileir.get("status") == "PASS": + case_ratios.append(native["ms"] / tileir["ms"]) + if case_ratios: + case_summary.update( + { + "mean_native_over_tileir": sum(case_ratios) / len(case_ratios), + "min_native_over_tileir": min(case_ratios), + "max_native_over_tileir": max(case_ratios), + "tileir_faster": sum(1 for value in case_ratios if value > 1.0), + "speedup_pairs": len(case_ratios), + } + ) + summary["cases"][case] = case_summary if set(paths) == {"native", "tileir"}: ratios = [] - by_pair = {spec.pair_id: {} for spec in specs} - for row in rows: - by_pair[row["pair_id"]][row["path"]] = row for pair_rows in by_pair.values(): native = pair_rows.get("native") tileir = pair_rows.get("tileir") @@ -742,17 +772,19 @@ def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[ return summary -def _print_table(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str]) -> None: +def _print_case_table(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str]) -> None: by_pair = {spec.pair_id: {} for spec in specs} for row in rows: - by_pair[row["pair_id"]][row["path"]] = row + if row["pair_id"] in by_pair: + by_pair[row["pair_id"]][row["path"]] = row + pair_col_width = 20 if set(paths) == {"native", "tileir"}: print( - f"{'pair':<8} {'case':<16} {'native ms':>10} {'TileIR ms':>10} " + f"{'pair':<{pair_col_width}} {'native ms':>10} {'TileIR ms':>10} " f"{'native/TileIR':>13} {'check':>8} {'route':>13} {'timing':>14} signature" ) - print("-" * 148) + print("-" * 144) for spec in specs: native = by_pair[spec.pair_id].get("native") tileir = by_pair[spec.pair_id].get("tileir") @@ -762,36 +794,91 @@ def _print_table(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequenc route_s = f"{native.get('route')}/{tileir.get('route')}" timing_s = native.get("timing") if native.get("timing") == tileir.get("timing") else "mixed" print( - f"{spec.pair_id:<8} {spec.case:<16} {native['ms']:10.3f} {tileir['ms']:10.3f} " + f"{spec.pair_id:<{pair_col_width}} {native['ms']:10.3f} {tileir['ms']:10.3f} " f"{ratio:13.3f} {validation:>8} {route_s:>13} {timing_s:>14} {spec.function_name}" ) else: for row in (native, tileir): if row: print( - f"{spec.pair_id:<8} {spec.case:<16} {row.get('path', ''):<10} " + f"{spec.pair_id:<{pair_col_width}} {row.get('path', ''):<10} " f"{row.get('status', 'FAIL'):<8} route={row.get('route')} " f"message={row.get('message', '')}" ) return - print(f"{'pair':<8} {'case':<16} {'path':<8} {'ms':>10} {'check':>8} {'route':>8} {'timing':>14} signature") - print("-" * 132) + print( + f"{'pair':<{pair_col_width}} {'path':<8} {'ms':>10} " + f"{'check':>8} {'route':>8} {'timing':>14} signature" + ) + print("-" * 128) for spec in specs: for path in paths: row = by_pair[spec.pair_id].get(path) if row and row.get("status") == "PASS": print( - f"{spec.pair_id:<8} {spec.case:<16} {path:<8} {row['ms']:10.3f} " + f"{spec.pair_id:<{pair_col_width}} {path:<8} {row['ms']:10.3f} " f"{row.get('validation'):>8} {row.get('route'):>8} {row.get('timing'):>14} {spec.function_name}" ) elif row: print( - f"{spec.pair_id:<8} {spec.case:<16} {path:<8} {row.get('status', 'FAIL'):<8} " + f"{spec.pair_id:<{pair_col_width}} {path:<8} {row.get('status', 'FAIL'):<8} " f"route={row.get('route')} message={row.get('message', '')}" ) +def _print_case_summary(summary: dict) -> None: + print( + f"passed runs: {summary['passed']}/{summary['runs']} " + f"passed pairs: {summary['passed_pairs']}/{summary['pairs']}" + ) + if "mean_native_over_tileir" in summary: + print( + "native/TileIR ratio: " + f"mean={summary['mean_native_over_tileir']:.3f}, " + f"min={summary['min_native_over_tileir']:.3f}, " + f"max={summary['max_native_over_tileir']:.3f}, " + f"TileIR faster={summary['tileir_faster']}/{summary['speedup_pairs']}" + ) + + +def _print_tables(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str], summary: dict) -> None: + for idx, (case, case_specs) in enumerate(_group_specs_by_case(specs)): + if idx: + print("") + print(f"case: {case} ({len(case_specs)} pairs)") + _print_case_table(rows, case_specs, paths) + _print_case_summary(summary["cases"][case]) + + +def _print_pair_progress(spec: PairSpec, pair_rows: Sequence[dict], stream) -> None: + by_path = {row.get("path"): row for row in pair_rows} + failures = [row for row in pair_rows if row.get("status") != "PASS"] + if failures: + details = "; ".join( + f"{row.get('path', '?')}: {row.get('message', 'failed')}" for row in failures + ) + print(f"[FAIL] {spec.pair_id} {spec.case}: {details}", file=stream, flush=True) + return + + native = by_path.get("native") + tileir = by_path.get("tileir") + if native and tileir: + ratio = native["ms"] / tileir["ms"] if tileir["ms"] else float("nan") + print( + f"[OK] {spec.pair_id} {spec.case}: " + f"native={native['ms']:.3f} ms, TileIR={tileir['ms']:.3f} ms, speedup={ratio:.3f}x", + file=stream, + flush=True, + ) + return + + details = ", ".join( + f"{row.get('path', '?')}={row['ms']:.3f} ms route={row.get('route')}" for row in pair_rows + ) + print(f"[OK] {spec.pair_id} {spec.case}: {details}", file=stream, flush=True) + + def run_parent( specs: Sequence[PairSpec], paths: Sequence[str], @@ -815,28 +902,21 @@ def run_parent( cache_root.mkdir(parents=True, exist_ok=True) rows = [] + progress_stream = sys.stderr if json_output else sys.stdout for spec in specs: + pair_rows = [] for path in paths: - rows.append(_run_subprocess(script, spec, path, warmup, rep, min_rep, initial_rep, check, cache_root)) + row = _run_subprocess(script, spec, path, warmup, rep, min_rep, initial_rep, check, cache_root) + rows.append(row) + pair_rows.append(row) + _print_pair_progress(spec, pair_rows, progress_stream) summary = _summarize(rows, specs, paths, cache_root) if json_output: print(json.dumps({"summary": summary, "rows": rows}, sort_keys=True, indent=2)) else: - _print_table(rows, specs, paths) + _print_tables(rows, specs, paths, summary) print("") - print( - f"passed runs: {summary['passed']}/{summary['runs']} " - f"passed pairs: {len(specs) - sum(1 for spec in specs if any(row.get('pair_id') == spec.pair_id and row.get('status') != 'PASS' for row in rows))}/{len(specs)}" - ) - if "mean_native_over_tileir" in summary: - print( - "native/TileIR ratio: " - f"mean={summary['mean_native_over_tileir']:.3f}, " - f"min={summary['min_native_over_tileir']:.3f}, " - f"max={summary['max_native_over_tileir']:.3f}, " - f"TileIR faster={summary['tileir_faster']}/{len(specs)}" - ) print(f"cache root: {summary['cache_root']}") if summary["failed"]: diff --git a/python/tutorials/tileir/README.md b/python/tutorials/tileir/README.md new file mode 100644 index 0000000000..252e482da1 --- /dev/null +++ b/python/tutorials/tileir/README.md @@ -0,0 +1,78 @@ +# TileIR Tutorials + +These tutorials assume FlagTree has been built with the default in-tree backend +set including `tileir`. For build environment requirements, see +[`third_party/tileir/README.md`](../../../third_party/tileir/README.md). + +## 01 Load View Token Ordering + +Tests: + +- TileIR TLE view-token APIs: `make_view`, `load_view_tko`, `store_view_tko` +- memory token chaining +- expected native NVIDIA failure when TileIR routing is disabled + +Run: + +```bash +python3 python/tutorials/tileir/01-load-view-token-ordering.py +``` + +## 02 Mixed Kernel Routing + +Tests: + +- a plain Triton kernel routes to TileIR +- a non-TileIR TLE shared-memory kernel falls back to native NVIDIA +- both routes work in one process + +Run: + +```bash +python3 python/tutorials/tileir/02-mixed-kernel-routing.py +``` + +## 03 Triton TileIR Benchmarks + +Tests: + +- selected self-contained Triton benchmark kernels +- native NVIDIA path vs TileIR path +- correctness checks and CUPTI kernel-time measurements + +`pair` selects benchmark configurations. Use `all` for every selected case, a +case-local numeric index such as `0` when only one case is selected, or full pair +ids such as `bmm-00,matmul-03`. + +`path` selects which backend path to run: `native`, `tileir`, or `both`. + +Run one pair: + +```bash +python3 python/tutorials/tileir/03-triton-tileir-benchmarks.py \ + --case bmm --pair 0 --path both +``` + +Run one case: + +```bash +python3 python/tutorials/tileir/03-triton-tileir-benchmarks.py \ + --case bmm --pair all --path both +``` + +Run all cases: + +```bash +python3 python/tutorials/tileir/03-triton-tileir-benchmarks.py \ + --case all --pair all --path both +``` + +Available cases: + +```text +bmm, fmha, linear_bias_act, mla, mla_decoding, matmul, rope +``` + +Reference speedup from a full run on H100 SXM5 80GB: + +![TileIR reference speedup heatmap on H100 SXM5 80GB](assets/reference_speedup_heatmap_h100_sxm5_80gb.png) diff --git a/python/tutorials/tileir/assets/reference_speedup_heatmap_h100_sxm5_80gb.png b/python/tutorials/tileir/assets/reference_speedup_heatmap_h100_sxm5_80gb.png new file mode 100644 index 0000000000000000000000000000000000000000..fa6a33f9b538661e95f53bfa4684c04749d843e8 GIT binary patch literal 51204 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/GNSNP0(jfI*Jg#_ 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 literal 0 HcmV?d00001 diff --git a/third_party/tileir/README.flagtree.md b/third_party/tileir/README.flagtree.md deleted file mode 100644 index 638c6c1237..0000000000 --- a/third_party/tileir/README.flagtree.md +++ /dev/null @@ -1,42 +0,0 @@ -# FlagTree TileIR Vendor Notes - -## Imported Source - -- Upstream repo: `https://github.com/triton-lang/Triton-to-Tile-IR` -- Upstream commit: `0d9283bf495f9712877f27eb3b1234081a12cfe9` -- Synced path: `third_party/tileir` - -## Pinned Dependencies - -- cuda-tile submodule path: `third_party/tileir/third_party/cuda-tile` -- cuda-tile commit: `0859212ad19f71133a9b940c05323286cbf28a05` -- cuda-tile reference: `v13.2.0` -- tileiras version used for validation: CUDA 13.3, `V13.3.36` - -## FlagTree Compatibility - -- Target FlagTree/Triton base: `triton_v3.6.x` / Triton 3.6.0 -- FlagTree's pinned LLVM is older than the LLVM expected by cuda-tile v13.2.0. -- `scripts/patch_bytecode_utils.sh` is a FlagTree-local compatibility patch for - that LLVM skew. It rewrites cuda-tile source before build, including: - - `DenseTypedElementsAttr` to `DenseIntOrFPElementsAttr` - - `llvm::scope_exit` to `llvm::make_scope_exit` - - `DenseElementsAttr::isValidRawBuffer` 2-argument calls to the 3-argument API - -## FlagTree-local Changes - -- Build integration: use the pinned cuda-tile submodule instead of the upstream - build-time clone. -- Backend integration: adapt TileIR compiler and driver hooks to FlagTree's - per-kernel routing and runtime expectations. -- TLE/TKO support: expose TileIR tensor-view and load/store-view token helpers - needed by FlagTree TLE kernels. -- Lowering compatibility: add `math.erf` lowering for current FlagTree coverage. - -## Update Checklist - -1. Sync `third_party/tileir` from the chosen Triton-to-Tile-IR commit. -2. Keep `third_party/tileir/third_party/cuda-tile` as an explicit submodule pin. -3. Reapply or verify the FlagTree-local changes above. -4. Recheck the LLVM compatibility patch in `scripts/patch_bytecode_utils.sh`. -5. Run TileIR routing, TLE/TKO, and tutorial validation before updating this file. diff --git a/third_party/tileir/README.md b/third_party/tileir/README.md index 0818755eb3..6ce78aaf29 100644 --- a/third_party/tileir/README.md +++ b/third_party/tileir/README.md @@ -1,21 +1,85 @@ -# Triton-TileIR Backend User Guide +# FlagTree TileIR Backend -## Build Instructions +## Imported Source -To build and install the Triton-TileIR backend, simply run: +- Upstream repo: `https://github.com/triton-lang/Triton-to-Tile-IR` +- Upstream commit: `0d9283bf495f9712877f27eb3b1234081a12cfe9` +- Synced path: `third_party/tileir` + +## Pinned Dependencies + +- cuda-tile submodule path: `third_party/tileir/third_party/cuda-tile` +- cuda-tile commit: `0859212ad19f71133a9b940c05323286cbf28a05` +- cuda-tile reference: `v13.2.0` +- tileiras version used for validation: CUDA 13.3, `V13.3.36` + +## Build Environment + +When `FLAGTREE_BACKEND` is not set, FlagTree builds the default in-tree backend +set, including `nvidia`, `amd`, and `tileir`. + +TileIR requires CUDA TileIR tooling from CTK 13.3. The validated toolchain is: + +- `tileiras` from CUDA 13.3, version `V13.3.36` +- repo-provided NVIDIA assemblers: + - `third_party/nvidia/backend/bin/ptxas` + - `third_party/nvidia/backend/bin/ptxas-blackwell` +- cuda-tile submodule: + - `third_party/tileir/third_party/cuda-tile` + - commit `0859212ad19f71133a9b940c05323286cbf28a05` + +Default FlagTree source install: ```bash -pip install . +MAX_JOBS=32 python3 -m pip install . --no-build-isolation ``` -## Running +For local editable development, the usual editable install also works when the +build dependencies are already present: + +```bash +python3 -m pip install -e . --no-build-isolation --no-deps +``` -Before using the backend, ensure you have CTK 13.1 installed and set the following environment variable: +## Runtime Routing + +TileIR routing is opt-in: + +```bash +FLAGTREE_USE_TILEIR=1 +TRITON_BACKENDS_IN_TREE=1 +``` + +For debugging routing decisions: ```bash -export ENABLE_TILE=1 +FLAGTREE_TILEIR_VERBOSE=1 ``` -## Known Limitations +## FlagTree Compatibility + +- Target FlagTree/Triton base: `triton_v3.6.x` / Triton 3.6.0 +- FlagTree's pinned LLVM is older than the LLVM expected by cuda-tile v13.2.0. +- `scripts/patch_bytecode_utils.sh` is a FlagTree-local compatibility patch for + that LLVM skew. It rewrites cuda-tile source before build, including: + - `DenseTypedElementsAttr` to `DenseIntOrFPElementsAttr` + - `llvm::scope_exit` to `llvm::make_scope_exit` + - `DenseElementsAttr::isValidRawBuffer` 2-argument calls to the 3-argument API + +## FlagTree-local Changes + +- Build integration: use the pinned cuda-tile submodule instead of the upstream + build-time clone. +- Backend integration: adapt TileIR compiler and driver hooks to FlagTree's + per-kernel routing and runtime expectations. +- TLE/TKO support: expose TileIR tensor-view and load/store-view token helpers + needed by FlagTree TLE kernels. +- Lowering compatibility: add `math.erf` lowering for current FlagTree coverage. + +## Update Checklist -- Some tests that are not supported by CudaTile are not yet automatically skipped; as a result, you may see failures in certain unit tests. +1. Sync `third_party/tileir` from the chosen Triton-to-Tile-IR commit. +2. Keep `third_party/tileir/third_party/cuda-tile` as an explicit submodule pin. +3. Reapply or verify the FlagTree-local changes above. +4. Recheck the LLVM compatibility patch in `scripts/patch_bytecode_utils.sh`. +5. Run TileIR routing, TLE/TKO, and tutorial validation before updating this file. From babe605e1068f7e793971ce2c41b38c3421b809d Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Tue, 16 Jun 2026 03:35:06 -0700 Subject: [PATCH 08/11] [tileir] Sync vendor and fix TensorDescriptor ABI --- third_party/tileir/CMakeLists.txt | 36 ++++++++++++++++--- third_party/tileir/README.md | 10 +++--- third_party/tileir/backend/conf.py | 36 +++++++++++++------ third_party/tileir/backend/driver.py | 7 ++-- .../lib/TritonToTileIR/TritonToTileIRPass.cpp | 20 ++++++++--- .../tileir/scripts/patch_bytecode_utils.sh | 28 +++++++++------ third_party/tileir/third_party/cuda-tile | 2 +- 7 files changed, 99 insertions(+), 40 deletions(-) diff --git a/third_party/tileir/CMakeLists.txt b/third_party/tileir/CMakeLists.txt index f928ce838b..96ce81423f 100644 --- a/third_party/tileir/CMakeLists.txt +++ b/third_party/tileir/CMakeLists.txt @@ -26,7 +26,7 @@ else() # into ${CMAKE_CURRENT_BINARY_DIR}/tileir_src and applies an inline patch. # # flagtree instead: - # 1) pins cuda-tile as a git submodule under third_party/cuda-tile (v13.2.0), + # 1) pins cuda-tile as a git submodule under third_party/cuda-tile (v13.3.0), # so the build doesn't need network access and the version is reviewable # in flagtree's git history; # 2) copies the submodule to a build-tree working dir so patches/build @@ -35,7 +35,8 @@ else() # with non-fatal failure semantics so future cuda-tile bumps don't break # the build for cosmetic patch mismatches. # ================================================================ - # Use the cuda-tile git submodule (pinned to v13.2.0 for LLVM API compat with flagtree). + # Use the cuda-tile git submodule (pinned to v13.3.0 for LLVM API compat with flagtree). + set(CUDA_TILE_COMMIT 2e5ccba66fb3afdba34b26cf358418283027c248) set(CUDA_TILE_SUBMODULE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/cuda-tile) if(NOT EXISTS ${CUDA_TILE_SUBMODULE_DIR}/CMakeLists.txt) message(FATAL_ERROR @@ -43,10 +44,36 @@ else() "Run: git submodule update --init --recursive") endif() + execute_process( + COMMAND git rev-parse HEAD + WORKING_DIRECTORY ${CUDA_TILE_SUBMODULE_DIR} + OUTPUT_VARIABLE CUDA_TILE_SUBMODULE_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE CUDA_TILE_REV_PARSE_RESULT + ) + if(NOT CUDA_TILE_REV_PARSE_RESULT EQUAL 0) + message(FATAL_ERROR "Failed to read cuda-tile submodule commit at ${CUDA_TILE_SUBMODULE_DIR}") + endif() + if(NOT CUDA_TILE_SUBMODULE_COMMIT STREQUAL CUDA_TILE_COMMIT) + message(FATAL_ERROR + "cuda-tile submodule is at ${CUDA_TILE_SUBMODULE_COMMIT}, expected ${CUDA_TILE_COMMIT}. " + "Run: git submodule update --init --recursive third_party/tileir/third_party/cuda-tile") + endif() + # Copy sources to the build tree so patches and build artifacts don't pollute the submodule. set(CUDA_TILE_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/cuda-tile-work) - if(NOT EXISTS ${CUDA_TILE_SOURCE_DIR}/CMakeLists.txt) + set(CUDA_TILE_COPY_MARKER ${CUDA_TILE_SOURCE_DIR}/.flagtree_cuda_tile_commit) + set(CUDA_TILE_NEEDS_COPY ON) + if(EXISTS ${CUDA_TILE_SOURCE_DIR}/CMakeLists.txt AND EXISTS ${CUDA_TILE_COPY_MARKER}) + file(READ ${CUDA_TILE_COPY_MARKER} CUDA_TILE_COPIED_COMMIT) + string(STRIP "${CUDA_TILE_COPIED_COMMIT}" CUDA_TILE_COPIED_COMMIT) + if(CUDA_TILE_COPIED_COMMIT STREQUAL CUDA_TILE_COMMIT) + set(CUDA_TILE_NEEDS_COPY OFF) + endif() + endif() + if(CUDA_TILE_NEEDS_COPY) message(STATUS "Copying cuda-tile sources from submodule to ${CUDA_TILE_SOURCE_DIR}") + file(REMOVE_RECURSE ${CUDA_TILE_SOURCE_DIR}) execute_process( COMMAND ${CMAKE_COMMAND} -E copy_directory ${CUDA_TILE_SUBMODULE_DIR} ${CUDA_TILE_SOURCE_DIR} RESULT_VARIABLE CUDA_TILE_COPY_RESULT @@ -54,10 +81,11 @@ else() if(NOT CUDA_TILE_COPY_RESULT EQUAL 0) message(FATAL_ERROR "Failed to copy cuda-tile sources to ${CUDA_TILE_SOURCE_DIR}") endif() + file(WRITE ${CUDA_TILE_COPY_MARKER} "${CUDA_TILE_COMMIT}\n") endif() # Patch cuda-tile sources to compile against flagtree's pinned LLVM (commit - # f6ded0be, older than cuda-tile v13.2.0's expected LLVM 13c00cbc). The patch + # f6ded0be, older than cuda-tile v13.3.0's expected LLVM 13c00cbc). The patch # script does narrow API renames; see comments in patch_bytecode_utils.sh. # We treat a non-zero exit from the patch script as a warning (not a fatal error) # so that future cuda-tile / LLVM bumps don't break the build for cosmetic patch diff --git a/third_party/tileir/README.md b/third_party/tileir/README.md index 6ce78aaf29..3773f598fa 100644 --- a/third_party/tileir/README.md +++ b/third_party/tileir/README.md @@ -3,14 +3,14 @@ ## Imported Source - Upstream repo: `https://github.com/triton-lang/Triton-to-Tile-IR` -- Upstream commit: `0d9283bf495f9712877f27eb3b1234081a12cfe9` +- Upstream commit: `a3befd959b02410cfbdac08d91d817b0ec0b3e33` - Synced path: `third_party/tileir` ## Pinned Dependencies - cuda-tile submodule path: `third_party/tileir/third_party/cuda-tile` -- cuda-tile commit: `0859212ad19f71133a9b940c05323286cbf28a05` -- cuda-tile reference: `v13.2.0` +- cuda-tile commit: `2e5ccba66fb3afdba34b26cf358418283027c248` +- cuda-tile reference: `v13.3.0` - tileiras version used for validation: CUDA 13.3, `V13.3.36` ## Build Environment @@ -26,7 +26,7 @@ TileIR requires CUDA TileIR tooling from CTK 13.3. The validated toolchain is: - `third_party/nvidia/backend/bin/ptxas-blackwell` - cuda-tile submodule: - `third_party/tileir/third_party/cuda-tile` - - commit `0859212ad19f71133a9b940c05323286cbf28a05` + - commit `2e5ccba66fb3afdba34b26cf358418283027c248` Default FlagTree source install: @@ -59,7 +59,7 @@ FLAGTREE_TILEIR_VERBOSE=1 ## FlagTree Compatibility - Target FlagTree/Triton base: `triton_v3.6.x` / Triton 3.6.0 -- FlagTree's pinned LLVM is older than the LLVM expected by cuda-tile v13.2.0. +- FlagTree's pinned LLVM is older than the LLVM expected by cuda-tile v13.3.0. - `scripts/patch_bytecode_utils.sh` is a FlagTree-local compatibility patch for that LLVM skew. It rewrites cuda-tile source before build, including: - `DenseTypedElementsAttr` to `DenseIntOrFPElementsAttr` diff --git a/third_party/tileir/backend/conf.py b/third_party/tileir/backend/conf.py index b783b9d2b9..9a172e11e6 100644 --- a/third_party/tileir/backend/conf.py +++ b/third_party/tileir/backend/conf.py @@ -25,18 +25,32 @@ def get_fmad_flag(): @staticmethod def get_tileiras_path(): + # Resolution order: explicit env override > bundled binary > system PATH. env_path = os.getenv("TRITON_TILEIRAS_PATH", None) - if env_path is None: - # Check if tileiras exists in system PATH - from shutil import which - - tileiras_path = which("tileiras") - if tileiras_path is None: - raise RuntimeError( - "tileiras not found in PATH and TRITON_TILEIRAS_PATH not set" - ) - return tileiras_path - return os.path.join(env_path, "tileiras") + if env_path is not None: + return os.path.join(env_path, "tileiras") + + # Bundled binary downloaded at build time into the nvidia backend. + bundled_path = os.path.join( + os.path.dirname(triton.__file__), + "backends", + "nvidia", + "bin", + "tileiras", + ) + if os.path.isfile(bundled_path) and os.access(bundled_path, os.X_OK): + return bundled_path + + # Fall back to system PATH. + from shutil import which + + tileiras_path = which("tileiras") + if tileiras_path is None: + raise RuntimeError( + "tileiras not found: TRITON_TILEIRAS_PATH unset, no bundled " + "binary, and not in PATH" + ) + return tileiras_path @staticmethod def get_device(): diff --git a/third_party/tileir/backend/driver.py b/third_party/tileir/backend/driver.py index 0af0a8f4f5..0ab6e9b24f 100644 --- a/third_party/tileir/backend/driver.py +++ b/third_party/tileir/backend/driver.py @@ -428,7 +428,7 @@ def make_tensordesc_arg(arg): assert strides[-1] == 1 # The 0 is a placeholder that replaces the tensordesc type when passing to kernel. # nvidia oss backend passes tensordesc directly, but tileir needs to decompose it. - result = [0, data_ptr, *shape, *strides] + result = [0, data_ptr, *shape, *strides[:-1]] return result @@ -474,10 +474,11 @@ def __init__(self, src, metadata): dtype = value.split("<")[1].split("[")[0] post_signature[key] = "i32" post_signature[f"{key}_ptr"] = f"*{dtype}" - # add shape and stride to signature + # add shape and dynamic stride args to signature; the last + # stride is statically known to be 1. for idx in range(len(shape)): post_signature[f"{key}_shape_{idx}"] = "i32" - for idx in range(len(shape)): + for idx in range(len(shape) - 1): post_signature[f"{key}_stride_{idx}"] = "i64" else: post_signature[key] = value diff --git a/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp b/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp index 9f85f0a19c..e14fa95429 100644 --- a/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp +++ b/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp @@ -398,9 +398,19 @@ class ConvertFuncOp : public OpConversionPattern { // type converter will convert it to TileType in the // convertSignatureArgs API. SmallVector modifiedInputs = llvm::to_vector(type.getInputs()); - for (auto &inType : modifiedInputs) { - if (isa(inType)) - inType = rewriter.getI32Type(); + for (size_t i = 0; i < modifiedInputs.size(); ++i) { + if (auto tensorDescType = + dyn_cast(modifiedInputs[i])) { + auto pointeeType = tensorDescType.getBlockType().getElementType(); + if (auto intTy = dyn_cast(pointeeType)) { + pointeeType = + mlir::IntegerType::get(rewriter.getContext(), intTy.getWidth(), + mlir::IntegerType::Signless); + } + modifiedInputs[i] = rewriter.getI32Type(); + if (i + 1 < modifiedInputs.size()) + modifiedInputs[i + 1] = triton::PointerType::get(pointeeType, 1); + } } SmallVector newResults; @@ -2716,7 +2726,7 @@ static void convertTmaDescriptorOps(Operation *op, TypeConverter &converter) { shape.push_back(funcOp.getArgument(i + j + 1)); i = i + rank; SmallVector stride; - for (size_t j = 0; j < rank; j++) + for (size_t j = 0; j + 1 < rank; j++) stride.push_back(funcOp.getArgument(i + j + 1)); i = i + rank - 1; @@ -2734,7 +2744,7 @@ static void convertTmaDescriptorOps(Operation *op, TypeConverter &converter) { // which is the TMA hardware limit for stride. This ensures that if // users explicitly want to use TMA for this operation, the stride // parameters will satisfy TMA descriptor encoding requirements. - for (int i = 0; i < stride.size() - 1; i++) + for (int i = 0; i < stride.size(); i++) wrappedDynStrides.push_back( wrapIntoScalarTile(rewriter, stride[i], /*attachAlignment=*/align_byte, kMaxStride)); diff --git a/third_party/tileir/scripts/patch_bytecode_utils.sh b/third_party/tileir/scripts/patch_bytecode_utils.sh index f669ee8d9c..62f44ce507 100755 --- a/third_party/tileir/scripts/patch_bytecode_utils.sh +++ b/third_party/tileir/scripts/patch_bytecode_utils.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash # flagtree: this entire file is a flagtree-local rewrite of the upstream version. # Upstream's script renames OLDER LLVM API names to NEWER ones (because their LLVM -# is newer than cuda-tile v13.2.0's expected LLVM). flagtree's LLVM is OLDER than +# is newer than cuda-tile v13.3.0's expected LLVM). flagtree's LLVM is OLDER than # cuda-tile's expected LLVM, so we need the OPPOSITE direction renames, plus a # couple extra patches that upstream doesn't have. # # Patches cuda-tile sources for compatibility with flagtree's pinned LLVM. # # flagtree currently pins LLVM commit f6ded0be (v3.6.0 era, ~Jan 2026), which is -# OLDER than the LLVM commit (13c00cbc) that cuda-tile v13.2.0 was developed against. +# OLDER than the LLVM commit (13c00cbc) that cuda-tile v13.3.0 was developed against. # This script renames newer LLVM API references in the cuda-tile sources back to # their older equivalents so the code compiles against flagtree's LLVM. # @@ -44,19 +44,25 @@ echo "[patch] Global rename: DenseTypedElementsAttr → DenseIntOrFPElementsAttr find "${REPO_ROOT}" -type f \( -name "*.cpp" -o -name "*.h" -o -name "*.td" \) \ -exec sed -i 's/DenseTypedElementsAttr/DenseIntOrFPElementsAttr/g' {} + -# 2) Rename llvm::scope_exit → llvm::make_scope_exit -# cuda-tile uses the factory-call style llvm::scope_exit([&]{...}) which in newer -# LLVM is the class template constructor. flagtree's older LLVM only has the -# factory function llvm::make_scope_exit; rename safely because cuda-tile's usage -# is the factory-call form, not the type-declaration form. +# 2) Rewrite llvm::scope_exit usages for older LLVM +# cuda-tile v13.3.0 uses class-template declaration style: +# [[maybe_unused]] llvm::scope_exit name(...) +# flagtree's older LLVM only has the factory function llvm::make_scope_exit, so +# rewrite declarations to: +# [[maybe_unused]] auto name = llvm::make_scope_exit(...) +# Also handle already-partially-patched build-tree copies where the type was +# rewritten to llvm::make_scope_exit but the declaration shape was left intact. BYTECODE_READER_PATH="${REPO_ROOT}/lib/Bytecode/Reader/BytecodeReader.cpp" -if [[ -f "${BYTECODE_READER_PATH}" ]] && grep -q 'llvm::scope_exit' "${BYTECODE_READER_PATH}"; then - echo "[patch] Renaming llvm::scope_exit → llvm::make_scope_exit in BytecodeReader.cpp" - patch_in_place "${BYTECODE_READER_PATH}" -e 's/llvm::scope_exit/llvm::make_scope_exit/g' +if [[ -f "${BYTECODE_READER_PATH}" ]] && grep -q 'llvm::\(make_\)\?scope_exit' "${BYTECODE_READER_PATH}"; then + echo "[patch] Rewriting llvm::scope_exit usages in BytecodeReader.cpp" + patch_in_place "${BYTECODE_READER_PATH}" \ + -e 's/\(\[\[maybe_unused\]\]\) llvm::scope_exit \([A-Za-z_][A-Za-z0-9_]*\)(/\1 auto \2 = llvm::make_scope_exit(/g' \ + -e 's/\(\[\[maybe_unused\]\]\) llvm::make_scope_exit \([A-Za-z_][A-Za-z0-9_]*\)(/\1 auto \2 = llvm::make_scope_exit(/g' \ + -e 's/llvm::scope_exit(/llvm::make_scope_exit(/g' fi # 3) Convert isValidRawBuffer 2-arg → 3-arg form -# cuda-tile v13.2.0 calls DenseElementsAttr::isValidRawBuffer(tileType, rawData) — 2-arg. +# cuda-tile v13.3.0 calls DenseElementsAttr::isValidRawBuffer(tileType, rawData) — 2-arg. # flagtree's LLVM only has the 3-arg form: isValidRawBuffer(ShapedType, ArrayRef, bool& detectedSplat). # Inject a local "bool detectedSplat = false" before the call and pass it as the 3rd argument. if [[ -f "${BYTECODE_READER_PATH}" ]] && grep -q 'isValidRawBuffer(tileType, rawData))' "${BYTECODE_READER_PATH}"; then diff --git a/third_party/tileir/third_party/cuda-tile b/third_party/tileir/third_party/cuda-tile index 0859212ad1..2e5ccba66f 160000 --- a/third_party/tileir/third_party/cuda-tile +++ b/third_party/tileir/third_party/cuda-tile @@ -1 +1 @@ -Subproject commit 0859212ad19f71133a9b940c05323286cbf28a05 +Subproject commit 2e5ccba66fb3afdba34b26cf358418283027c248 From 123f5654033ff4f005a10d8a21ad424572a4824f Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Tue, 16 Jun 2026 03:35:20 -0700 Subject: [PATCH 09/11] [tileir] Remove compile env and polish benchmarks --- python/triton/compiler/compiler.py | 10 ---------- python/triton/language/target_info.py | 6 ------ python/tutorials/tileir/03-triton-tileir-benchmarks.py | 9 +++++++-- python/tutorials/tileir/README.md | 3 ++- third_party/tileir/backend/extend_core.py | 9 ++------- 5 files changed, 11 insertions(+), 26 deletions(-) diff --git a/python/triton/compiler/compiler.py b/python/triton/compiler/compiler.py index f381e55c79..225d6cd48e 100644 --- a/python/triton/compiler/compiler.py +++ b/python/triton/compiler/compiler.py @@ -78,16 +78,6 @@ def hash(self): def make_ir(self, target: GPUTarget, options, codegen_fns, module_map, context): if target.backend == "tileir": from ..backends.tileir.code_generator import ast_to_ttir - prev = os.environ.get("FLAGTREE_COMPILING_TILEIR") - os.environ["FLAGTREE_COMPILING_TILEIR"] = "1" - try: - return ast_to_ttir(self.fn, self, context=context, options=options, codegen_fns=codegen_fns, - module_map=module_map) - finally: - if prev is None: - os.environ.pop("FLAGTREE_COMPILING_TILEIR", None) - else: - os.environ["FLAGTREE_COMPILING_TILEIR"] = prev else: from .code_generator import ast_to_ttir return ast_to_ttir(self.fn, self, context=context, options=options, codegen_fns=codegen_fns, diff --git a/python/triton/language/target_info.py b/python/triton/language/target_info.py index 91bf69a25f..0a1b5bbb54 100644 --- a/python/triton/language/target_info.py +++ b/python/triton/language/target_info.py @@ -1,5 +1,3 @@ -import os - from triton.runtime import driver from triton.runtime.jit import constexpr_function @@ -20,16 +18,12 @@ def current_target(): @constexpr_function def is_cuda(): - if os.environ.get("FLAGTREE_COMPILING_TILEIR", "0") == "1": - return True target = current_target() return target is not None and target.backend in ("cuda", "tileir") @constexpr_function def is_tileir(): - if os.environ.get("FLAGTREE_COMPILING_TILEIR", "0") == "1": - return True target = current_target() return target is not None and target.backend == "tileir" diff --git a/python/tutorials/tileir/03-triton-tileir-benchmarks.py b/python/tutorials/tileir/03-triton-tileir-benchmarks.py index c5ebd7f2aa..78c6c424ee 100644 --- a/python/tutorials/tileir/03-triton-tileir-benchmarks.py +++ b/python/tutorials/tileir/03-triton-tileir-benchmarks.py @@ -942,8 +942,13 @@ def _parse_cases(value: str) -> List[str]: def _parse_pair_filter(value: str, cases: Sequence[str]) -> List[PairSpec]: + def sort_specs(specs: Sequence[PairSpec]) -> List[PairSpec]: + case_index = {case: idx for idx, case in enumerate(cases)} + pair_index = {spec.pair_id: idx for idx, spec in enumerate(PAIR_SPECS)} + return sorted(specs, key=lambda spec: (case_index[spec.case], pair_index[spec.pair_id])) + if value == "all": - return [spec for spec in PAIR_SPECS if spec.case in cases] + return sort_specs([spec for spec in PAIR_SPECS if spec.case in cases]) requested = [item.strip() for item in value.split(",") if item.strip()] out: List[PairSpec] = [] for item in requested: @@ -962,7 +967,7 @@ def _parse_pair_filter(value: str, cases: Sequence[str]) -> List[PairSpec]: if len(matches) > 1: raise SystemExit(f"pair index {item} matches multiple cases; use full pair id") raise SystemExit(f"unknown pair id: {item}") - return out + return sort_specs(out) def main() -> int: diff --git a/python/tutorials/tileir/README.md b/python/tutorials/tileir/README.md index 252e482da1..0ae258be5a 100644 --- a/python/tutorials/tileir/README.md +++ b/python/tutorials/tileir/README.md @@ -42,7 +42,8 @@ Tests: `pair` selects benchmark configurations. Use `all` for every selected case, a case-local numeric index such as `0` when only one case is selected, or full pair -ids such as `bmm-00,matmul-03`. +ids such as `bmm-00,matmul-03`. Full pair ids may be listed in any order; output +is grouped by the selected case order. `path` selects which backend path to run: `native`, `tileir`, or `both`. diff --git a/third_party/tileir/backend/extend_core.py b/third_party/tileir/backend/extend_core.py index 949af2e622..092e562959 100644 --- a/third_party/tileir/backend/extend_core.py +++ b/third_party/tileir/backend/extend_core.py @@ -1,4 +1,3 @@ -import os import re from triton.language import core as tl @@ -115,15 +114,11 @@ def __eq__(self, other): def _require_tileir_builder(_semantic, op_name): - if os.environ.get("FLAGTREE_COMPILING_TILEIR", "0") != "1": - raise NotImplementedError( - f"tl.ext.{op_name} is only supported by FlagTree's TileIR path; " - "set FLAGTREE_USE_TILEIR=1 for kernels that only use TileIR TKO view ops" - ) semantic_op_name = "create_mem_token" if op_name == "mem_token" else op_name if _semantic is None or not hasattr(_semantic, semantic_op_name): raise NotImplementedError( - f"tl.ext.{op_name} requires TileIR view/token semantic bindings; rebuild flagtree" + f"tl.ext.{op_name} is only supported by FlagTree's TileIR path; " + "set FLAGTREE_USE_TILEIR=1 for kernels that only use TileIR TKO view ops" ) From cbf1dc54ed5085e19c2efbdbffbe9dc765611ee0 Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Sun, 28 Jun 2026 16:09:21 -0700 Subject: [PATCH 10/11] [tileir] Address backend CI review feedback --- .../workflows/tileir3.6-build-and-test.yml | 70 ++++++ python/setup_tools/utils/tools.py | 11 +- python/triton/_filecheck.py | 2 +- python/triton/_flagtree/__init__.py | 2 - python/triton/backends/__init__.py | 65 +++++ python/triton/backends/compiler.py | 14 ++ python/triton/compiler/compiler.py | 75 ++---- .../experimental/tle/language/gpu/tile.py | 32 ++- python/triton/language/__init__.py | 9 +- python/triton/language/core.py | 44 ---- python/triton/language/semantic.py | 3 - python/triton/language/target_info.py | 12 +- python/triton/runtime/jit.py | 6 +- .../tileir/01-load-view-token-ordering.py | 22 +- .../tileir/02-mixed-kernel-routing.py | 19 +- .../tileir/03-triton-tileir-benchmarks.py | 193 +++++++-------- python/tutorials/tileir/README.md | 11 + python/tutorials/tileir/tilegym/backend.py | 3 +- python/tutorials/tileir/tilegym/logger.py | 1 + .../tutorials/tileir/tilegym/ops/__init__.py | 1 - .../tileir/tilegym/ops/triton/__init__.py | 1 - .../tilegym/ops/triton/activation/__init__.py | 1 - .../tilegym/ops/triton/activation/gelu.py | 7 +- .../tilegym/ops/triton/activation/relu.py | 61 ++--- .../tilegym/ops/triton/activation/silu.py | 9 +- .../tilegym/ops/triton/activation/tanh.py | 9 +- .../tileir/tilegym/ops/triton/attention.py | 33 +-- .../tileir/tilegym/ops/triton/bmm.py | 46 ++-- .../ops/triton/linear_bias_activation.py | 148 +++++------ .../tileir/tilegym/ops/triton/matmul.py | 234 ++++++++---------- .../tilegym/ops/triton/matmul_perf_model.py | 27 +- .../tileir/tilegym/ops/triton/mla.py | 21 +- .../tileir/tilegym/ops/triton/mla_decoding.py | 28 +-- .../tileir/tilegym/ops/triton/rope.py | 25 +- setup.py | 6 +- third_party/tileir/PerformanceTuningTips.md | 6 +- third_party/tileir/README.md | 59 ++++- third_party/tileir/backend/code_generator.py | 11 +- third_party/tileir/backend/compiler.py | 77 +++--- third_party/tileir/backend/conf.py | 7 +- third_party/tileir/backend/driver.py | 57 ++--- third_party/tileir/backend/errors.py | 1 + third_party/tileir/backend/extend_core.py | 26 +- third_party/tileir/backend/extend_semantic.py | 1 + .../tileir/backend/router.py | 11 +- .../tileir/include/Transform/Passes.td | 4 +- .../TritonToTileIR/TritonToTileIRPass.h | 4 +- .../tileir/include/TritonToTileIR/Utils.h | 2 +- .../MapElementwiseExpansion.cpp | 136 +++++----- .../TritonToTileIR/MapElementwiseExpansion.h | 3 +- .../lib/TritonToTileIR/TritonToTileIRPass.cpp | 75 +++--- .../tileir/lib/TritonToTileIR/Utils.cpp | 6 +- third_party/tileir/lib/Utils/Utils.cpp | 2 +- .../tileir/scripts/check_wheel_deps.py | 7 +- third_party/tileir/scripts/copy_wheel_deps.py | 0 third_party/tileir/test/CMakeLists.txt | 2 +- third_party/tileir/test/FileCheck/fma.mlir | 1 - .../test/FileCheck/op-conversion-barrier.mlir | 11 +- .../FileCheck/op-conversion-modifiers.mlir | 9 +- .../tileir/test/FileCheck/op-conversion.mlir | 1 - third_party/tileir/triton_tileir.cc | 75 +++--- 61 files changed, 914 insertions(+), 931 deletions(-) create mode 100644 .github/workflows/tileir3.6-build-and-test.yml delete mode 100644 python/triton/_flagtree/__init__.py rename python/triton/_flagtree/tileir_router.py => third_party/tileir/backend/router.py (96%) mode change 100644 => 100755 third_party/tileir/scripts/check_wheel_deps.py mode change 100644 => 100755 third_party/tileir/scripts/copy_wheel_deps.py diff --git a/.github/workflows/tileir3.6-build-and-test.yml b/.github/workflows/tileir3.6-build-and-test.yml new file mode 100644 index 0000000000..3850d11efe --- /dev/null +++ b/.github/workflows/tileir3.6-build-and-test.yml @@ -0,0 +1,70 @@ +name: TileIR3.6-Build-And-Test + +on: + push: + branches: [ "triton_v3.6.x" ] + pull_request: + branches: [ "triton_v3.6.x" ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + tileir-build-and-test: + runs-on: hopper + if: ${{ github.repository == 'FlagTree/flagtree' || github.repository == 'flagos-ai/flagtree' }} + steps: + - name: Setup environment + shell: bash + run: | + source ~/env.sh + env | grep -E '^(http_proxy|https_proxy|all_proxy|no_proxy)=' >> $GITHUB_ENV || true + + - name: Smart Checkout + uses: flagos-ai/FlagTree/.github/actions/smart-checkout@main + with: + checkout_version: 'v6' + + - name: Check if backend-relevant files changed + id: check_backend + uses: flagos-ai/FlagTree/.github/actions/check-backend-changed@main + with: + backend: tileir + + - name: FlagTree Build on TileIR + if: steps.check_backend.outputs.should_skip != 'true' + shell: bash + run: | + set -x + pip uninstall -y triton || true + source ~/env-3.6.sh + env | grep -E '^(LLVM_SYSPATH)=' >> $GITHUB_ENV || true + /usr/local/cuda/bin/tileiras --version + rm -rf ~/.triton/cache + MAX_JOBS=32 CMAKE_BUILD_PARALLEL_LEVEL=32 \ + TRITON_PTXAS_PATH="$PWD/third_party/nvidia/backend/bin/ptxas" \ + TRITON_PTXAS_BLACKWELL_PATH="$PWD/third_party/nvidia/backend/bin/ptxas-blackwell" \ + python3 -m pip install . --no-build-isolation + + - name: FlagTree Test on TileIR + if: steps.check_backend.outputs.should_skip != 'true' + shell: bash + run: | + set -x + source ~/env-3.6.sh + rm -rf /tmp/flagtree_tileir_ci_01_cache /tmp/flagtree_tileir_ci_02_cache + TRITON_CACHE_DIR=/tmp/flagtree_tileir_ci_01_cache \ + python3 python/tutorials/tileir/01-load-view-token-ordering.py + TRITON_CACHE_DIR=/tmp/flagtree_tileir_ci_02_cache \ + python3 python/tutorials/tileir/02-mixed-kernel-routing.py + python3 python/tutorials/tileir/03-triton-tileir-benchmarks.py \ + --case all \ + --ci-subset \ + --path both \ + --warmup 1 \ + --rep 1 \ + --min-rep 1 \ + --initial-rep 1 \ + --cache-dir /tmp/flagtree_tileir_ci_03_cache \ + --clear-cache diff --git a/python/setup_tools/utils/tools.py b/python/setup_tools/utils/tools.py index 6e1beb220d..23bd7b88b2 100644 --- a/python/setup_tools/utils/tools.py +++ b/python/setup_tools/utils/tools.py @@ -22,13 +22,7 @@ def _get_flagtree_root() -> str: @dataclass class FlagtreeConfigs: - # flagtree: keep this set narrow on purpose. tileir is only added in the - # explicit default build path (setup.py "no FLAGTREE_BACKEND" branch). Putting - # tileir here would also drag it into plugin-backend builds (thrive/aipu/ - # tsingmicro/enflame), which would force those builds to satisfy tileir's - # heavy dependencies (cuda-tile submodule, GCC>=13, LLVM build path) even - # though the selected backend never uses tileir. - default_backends: tuple = ("nvidia", "amd") + default_backends: tuple = ("nvidia", "amd", "tileir") plugin_backends: tuple = ("cambricon", "ascend", "aipu", "tsingmicro", "enflame", "hcu", "thrive") use_cuda_toolkit_backends: tuple = ('aipu', ) language_extra_backends: tuple = ('xpu', 'mthreads', "cambricon") @@ -52,6 +46,9 @@ def __post_init__(self): self.flagtree_submodule_dir = os.path.join(self.flagtree_root_dir, "third_party") self.activated_module = self._activate_device_module() + def non_tileir_default_backends(self): + return tuple(backend for backend in self.default_backends if backend != "tileir") + def _activate_device_module(self, suffix=".py"): backend = self.flagtree_backend or "default" module_path = Path(os.path.dirname(__file__)) / backend diff --git a/python/triton/_filecheck.py b/python/triton/_filecheck.py index d96eb3cca7..850bc76a79 100644 --- a/python/triton/_filecheck.py +++ b/python/triton/_filecheck.py @@ -73,7 +73,7 @@ def run_parser(kernel_fn, args=(), kwargs={}, target=stub_target): codegen_fns = backend.get_codegen_implementation(options) module_map = backend.get_module_map() - module = src.make_ir(target, options, codegen_fns, module_map, context) + module = backend.make_ir(src, options, codegen_fns, module_map, context) return module diff --git a/python/triton/_flagtree/__init__.py b/python/triton/_flagtree/__init__.py deleted file mode 100644 index b86cbf5dc2..0000000000 --- a/python/triton/_flagtree/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# flagtree-specific runtime extensions to triton. -# Currently contains the TileIR routing layer. diff --git a/python/triton/backends/__init__.py b/python/triton/backends/__init__.py index 97092379e0..31b9ce9e0a 100644 --- a/python/triton/backends/__init__.py +++ b/python/triton/backends/__init__.py @@ -64,3 +64,68 @@ def _discover_backends() -> dict[str, Backend]: backends: dict[str, Backend] = _discover_backends() + + +def get_backend(target) -> Backend: + compatible = [backend for backend in backends.values() if backend.compiler.supports_target(target)] + if len(compatible) != 1: + raise RuntimeError(f"{len(compatible)} compatible backends for target ({target.backend}) ({compatible}). " + "There should only be one.") + return compatible[0] + + +def route_target(target, jit_fn): + routes = [] + for name, backend in backends.items(): + candidate = backend.compiler.route_target(target, jit_fn) + if candidate is not None and candidate != target: + routes.append((name, candidate)) + if len(routes) > 1: + names = ", ".join(name for name, _ in routes) + raise RuntimeError(f"Multiple backends requested this kernel: {names}") + return routes[0][1] if routes else target + + +_target_drivers = {} + + +def get_driver(target, active_driver): + driver_cls = get_backend(target).driver + if isinstance(active_driver, driver_cls): + return active_driver + if driver_cls not in _target_drivers: + _target_drivers[driver_cls] = driver_cls() + return _target_drivers[driver_cls] + + +class _LanguageExtensions: + + def __init__(self): + self._modules = None + self._symbols = {} + + def _get_modules(self): + if self._modules is None: + self._modules = [] + for name, backend in backends.items(): + module = backend.compiler.get_language_extension() + if module is not None: + self._modules.append((name, module)) + return self._modules + + def __getattr__(self, name): + if name in self._symbols: + return self._symbols[name] + providers = [(backend_name, getattr(module, name)) + for backend_name, module in self._get_modules() + if hasattr(module, name)] + if not providers: + raise RuntimeError(f"tl.ext.{name} is not provided by an installed Triton backend") + if len(providers) > 1: + names = ", ".join(backend_name for backend_name, _ in providers) + raise RuntimeError(f"tl.ext.{name} is provided by multiple backends: {names}") + self._symbols[name] = providers[0][1] + return self._symbols[name] + + +language_extensions = _LanguageExtensions() diff --git a/python/triton/backends/compiler.py b/python/triton/backends/compiler.py index 10754e7157..1ffa50dbf5 100644 --- a/python/triton/backends/compiler.py +++ b/python/triton/backends/compiler.py @@ -27,6 +27,20 @@ def __init__(self, target: GPUTarget) -> None: self.target = target assert self.supports_target(target) + @classmethod + def route_target(cls, target: GPUTarget, jit_fn): + """Return an alternate per-kernel target, or ``None`` to keep ``target``.""" + return None + + @classmethod + def get_language_extension(cls): + """Return an optional module contributing symbols to ``triton.language.ext``.""" + return None + + def make_ir(self, src, options, codegen_fns, module_map, context): + """Create the initial IR module for this backend.""" + return src.make_ir(self.target, options, codegen_fns, module_map, context) + @staticmethod @abstractmethod def supports_target(target: GPUTarget): diff --git a/python/triton/compiler/compiler.py b/python/triton/compiler/compiler.py index 225d6cd48e..d94507ebe3 100644 --- a/python/triton/compiler/compiler.py +++ b/python/triton/compiler/compiler.py @@ -2,7 +2,7 @@ import hashlib import json from .._C.libtriton import get_cache_invalidating_env_vars, ir -from ..backends import backends +from ..backends import get_backend, get_driver from ..backends.compiler import Language from ..backends.compiler import BaseBackend, GPUTarget from .. import __version__, knobs @@ -76,10 +76,7 @@ def hash(self): return hashlib.sha256(key.encode("utf-8")).hexdigest() def make_ir(self, target: GPUTarget, options, codegen_fns, module_map, context): - if target.backend == "tileir": - from ..backends.tileir.code_generator import ast_to_ttir - else: - from .code_generator import ast_to_ttir + from .code_generator import ast_to_ttir return ast_to_ttir(self.fn, self, context=context, options=options, codegen_fns=codegen_fns, module_map=module_map) @@ -304,7 +301,7 @@ def compile(src, target=None, options=None, _env_vars=None): codegen_fns = backend.get_codegen_implementation(options) module_map = backend.get_module_map() try: - module = src.make_ir(target, options, codegen_fns, module_map, context) + module = backend.make_ir(src, options, codegen_fns, module_map, context) except Exception as e: filter_traceback(e) raise @@ -364,11 +361,7 @@ def compile(src, target=None, options=None, _env_vars=None): def make_backend(target: GPUTarget) -> BaseBackend: - actives = [x.compiler for x in backends.values() if x.compiler.supports_target(target)] - if len(actives) != 1: - raise RuntimeError( - f"{len(actives)} compatible backends for target ({target.backend}) ({actives}). There should only be one.") - return actives[0](target) + return get_backend(target).compiler(target) class LazyDict: @@ -441,22 +434,8 @@ def __init__(self, src, metadata_group, hash): self._run = None def _get_kernel_driver(self): - """Return the driver instance matching this kernel's compile-time backend. - - flagtree: when per-kernel routing sends a kernel to the tileir backend, we - need to use TileIRDriver (different load_binary signature, different launcher) - even if driver.active is still CudaDriver — so non-tle kernels can go via - tileir while tle kernels stay on nvidia in the same process. - """ - backend = self.metadata.target.backend - if backend == "tileir": - cached = getattr(CompiledKernel, "_tileir_driver_instance", None) - if cached is None: - from ..backends import backends as _backends - cached = _backends["tileir"].driver() - CompiledKernel._tileir_driver_instance = cached - return cached - return driver.active + """Return the runtime driver paired with this kernel's compile target.""" + return get_driver(self.metadata.target, driver.active) def _init_handles(self): if self.module is not None: @@ -473,43 +452,23 @@ def raise_(err): raise err device = driver.active.get_current_device() - # flagtree: pick the driver matching the kernel's compile-time backend, so - # nvidia and tileir kernels can coexist in the same process. kernel_driver = self._get_kernel_driver() - is_tileir = self.metadata.target.backend == "tileir" # create launcher self._run = kernel_driver.launcher_cls(self.src, self.metadata) max_shared = max_shared_mem(device) + # not enough shared memory to run the kernel + if self.metadata.shared > max_shared: + raise_(OutOfResources(self.metadata.shared, max_shared, "shared memory")) + if hasattr(self.metadata, "tmem_size") and self.metadata.tmem_size is not None: + # Use blackwell max tmem size for now, this should be moved in device properties + max_tmem_size = 512 # tmem size in number of columns + if self.metadata.tmem_size > max_tmem_size: + raise_(OutOfResources(self.metadata.tmem_size, max_tmem_size, "tensor memory")) if knobs.runtime.kernel_load_start_hook is not None: knobs.runtime.kernel_load_start_hook(self.module, self.function, self.name, self.metadata_group, self.hash) - if is_tileir: - # TileIR load_binary takes (name, kernel, device) and returns a 6-tuple that - # includes static_smem_bytes (which the tileir backend uses as the .shared field). - ( - self.module, - self.function, - self.n_regs, - self.n_spills, - self.static_smem_bytes, - self.n_max_threads, - ) = kernel_driver.utils.load_binary(self.name, self.kernel, device) - if "shared" not in self.metadata._fields: - from collections import namedtuple - KernelMetadata = namedtuple("KernelMetadata", self.metadata._fields + ("shared",)) - self.metadata = KernelMetadata(**self.metadata._asdict(), shared=self.static_smem_bytes) - if self.metadata.shared > max_shared: - raise_(OutOfResources(self.metadata.shared, max_shared, "shared memory")) - else: - if self.metadata.shared > max_shared: - raise_(OutOfResources(self.metadata.shared, max_shared, "shared memory")) - if hasattr(self.metadata, "tmem_size") and self.metadata.tmem_size is not None: - # Use blackwell max tmem size for now, this should be moved in device properties - max_tmem_size = 512 # tmem size in number of columns - if self.metadata.tmem_size > max_tmem_size: - raise_(OutOfResources(self.metadata.tmem_size, max_tmem_size, "tensor memory")) - # TODO: n_regs, n_spills should be metadata generated when calling `ptxas` - self.module, self.function, self.n_regs, self.n_spills, self.n_max_threads = kernel_driver.utils.load_binary( - self.name, self.kernel, self.metadata.shared, device) + # TODO: n_regs, n_spills should be metadata generated when calling `ptxas` + self.module, self.function, self.n_regs, self.n_spills, self.n_max_threads = kernel_driver.utils.load_binary( + self.name, self.kernel, self.metadata.shared, device) warp_size = self.metadata.target.warp_size if self.metadata.num_warps * warp_size > self.n_max_threads: raise_(OutOfResources(self.metadata.num_warps * warp_size, self.n_max_threads, "threads")) diff --git a/python/triton/experimental/tle/language/gpu/tile.py b/python/triton/experimental/tle/language/gpu/tile.py index c128496cce..714bf395db 100644 --- a/python/triton/experimental/tle/language/gpu/tile.py +++ b/python/triton/experimental/tle/language/gpu/tile.py @@ -1,14 +1,30 @@ # flagtree tle from triton.language import ext as _tileir_ext -create_mem_token = _tileir_ext.create_mem_token -dim = _tileir_ext.dim -join_mem_tokens = _tileir_ext.join_mem_tokens -load_view_tko = _tileir_ext.load_view_tko -make_partition_view = _tileir_ext.make_partition_view -make_tensor_view = _tileir_ext.make_tensor_view -make_view = _tileir_ext.make_view -store_view_tko = _tileir_ext.store_view_tko + +def _bind_tileir_ext(name): + try: + return getattr(_tileir_ext, name) + except RuntimeError: + pass + + def unavailable(*args, **kwargs): + return getattr(_tileir_ext, name)(*args, **kwargs) + + unavailable.__name__ = name + unavailable.__qualname__ = name + unavailable.__module__ = __name__ + return unavailable + + +create_mem_token = _bind_tileir_ext("create_mem_token") +dim = _bind_tileir_ext("dim") +join_mem_tokens = _bind_tileir_ext("join_mem_tokens") +load_view_tko = _bind_tileir_ext("load_view_tko") +make_partition_view = _bind_tileir_ext("make_partition_view") +make_tensor_view = _bind_tileir_ext("make_tensor_view") +make_view = _bind_tileir_ext("make_view") +store_view_tko = _bind_tileir_ext("store_view_tko") __all__ = [ "make_tensor_view", diff --git a/python/triton/language/__init__.py b/python/triton/language/__init__.py index 1addf84208..7b8607de64 100644 --- a/python/triton/language/__init__.py +++ b/python/triton/language/__init__.py @@ -3,7 +3,8 @@ from . import math from . import extra -from ..backends.tileir import extend_core as ext +from ..backends import language_extensions as ext + from .standard import ( argmax, argmin, @@ -35,8 +36,6 @@ make_tensor_descriptor, tensor_descriptor, tensor_descriptor_type, - tileir_tensor_descriptor, - tileir_tensor_descriptor_type, add, advance, arange, @@ -145,8 +144,6 @@ "store_tensor_descriptor", "make_tensor_descriptor", "tensor_descriptor", - "tileir_tensor_descriptor", - "tileir_tensor_descriptor_type", "abs", "add", "advance", @@ -326,8 +323,6 @@ def str_to_ty(name, c): return nvidia_tensor_descriptor_type(block, shape_type, stride_type, layout) else: return amd_tensor_descriptor_type(block, shape_type, stride_type, layout) - if target_info.is_tileir(): - return tileir_tensor_descriptor_type(block, shape_type, stride_type) return tensor_descriptor_type(block, shape_type, stride_type) if name.startswith("constexpr"): diff --git a/python/triton/language/core.py b/python/triton/language/core.py index 5277bdddd6..439671c3a1 100644 --- a/python/triton/language/core.py +++ b/python/triton/language/core.py @@ -1474,30 +1474,6 @@ def __eq__(self, other): == other.strides_type) -class tileir_tensor_descriptor_type(tensor_descriptor_type): - - def __init__(self, block_type: block_type, shape_type: tuple_type, strides_type: tuple_type): - super().__init__(block_type, shape_type, strides_type) - self.ptr_type = pointer_type(block_type.element_ty) - - def _unflatten_ir(self, handles: List[ir.value], cursor: int) -> Tuple[tensor_descriptor_base, int]: - handle = handles[cursor] - cursor += 1 - ptr, cursor = self.ptr_type._unflatten_ir(handles, cursor) - shape, cursor = self.shape_type._unflatten_ir(handles, cursor) - strides, cursor = self.strides_type._unflatten_ir(handles, cursor) - shape = shape.values - strides = strides.values - value = tileir_tensor_descriptor(handle, shape, strides, self.block_type, ptr) - return value, cursor - - def _flatten_ir_types(self, builder: ir.builder, out: List[ir.type]) -> None: - tensor_descriptor_base_type._flatten_ir_types(self, builder, out) - self.ptr_type._flatten_ir_types(builder, out) - self.shape_type._flatten_ir_types(builder, out) - self.strides_type._flatten_ir_types(builder, out) - - class tensor_descriptor(tensor_descriptor_base): """A descriptor representing a tensor in global memory. """ @@ -1521,26 +1497,6 @@ def _flatten_ir(self, handles: List[ir.value]) -> None: self.strides._flatten_ir(handles) -class tileir_tensor_descriptor(tensor_descriptor): - """A descriptor representing a tensor in global memory for TileIR.""" - - def __init__(self, handle, shape: List[tensor], strides: List[tensor], block_type: block_type, ptr): - """Not called by user code.""" - super().__init__(handle, shape, strides, block_type) - self.ptr = ptr - self.type = tileir_tensor_descriptor_type( - block_type, - shape_type=self.shape.type, - strides_type=self.strides.type, - ) - - def _flatten_ir(self, handles: List[ir.value]) -> None: - handles.append(self.handle) - handles.append(self.ptr.handle) - handles.extend(s.handle for s in self.shape) - handles.extend(s.handle for s in self.strides) - - # ----------------------- # aggregate # ----------------------- diff --git a/python/triton/language/semantic.py b/python/triton/language/semantic.py index 1d0d056559..c20ed04c2c 100644 --- a/python/triton/language/semantic.py +++ b/python/triton/language/semantic.py @@ -7,7 +7,6 @@ from triton.runtime import driver from .._C.libtriton import ir -from . import target_info from . import core as tl T = TypeVar('T') @@ -1969,6 +1968,4 @@ def make_tensor_descriptor(self, base: TensorTy, shape: List[TensorTy], strides: handle = self.builder.create_make_tensor_descriptor(base_handle, [s.handle for s in shape], [s.handle for s in strides], block_shape, is_signed_int, padding) - if target_info.is_tileir(): - return tl.tileir_tensor_descriptor(handle, shape, strides, type, base) return tl.tensor_descriptor(handle, shape, strides, type) diff --git a/python/triton/language/target_info.py b/python/triton/language/target_info.py index 0a1b5bbb54..2c1a277f04 100644 --- a/python/triton/language/target_info.py +++ b/python/triton/language/target_info.py @@ -1,7 +1,7 @@ from triton.runtime import driver from triton.runtime.jit import constexpr_function -__all__ = ["current_target", "is_tileir"] +__all__ = ["current_target"] def current_target(): @@ -19,13 +19,7 @@ def current_target(): @constexpr_function def is_cuda(): target = current_target() - return target is not None and target.backend in ("cuda", "tileir") - - -@constexpr_function -def is_tileir(): - target = current_target() - return target is not None and target.backend == "tileir" + return target is not None and target.backend == "cuda" @constexpr_function @@ -36,7 +30,7 @@ def cuda_capability_geq(major, minor=0): inline asm implementations that require a certain compute capability. """ target = current_target() - if target is None or target.backend not in ("cuda", "tileir"): + if target is None or target.backend != "cuda": return False assert isinstance(target.arch, int) return target.arch >= major * 10 + minor diff --git a/python/triton/runtime/jit.py b/python/triton/runtime/jit.py index b742a063b3..ecd8dc8d73 100644 --- a/python/triton/runtime/jit.py +++ b/python/triton/runtime/jit.py @@ -669,11 +669,9 @@ def create_binder(self): Precompute as much as possible. """ from ..compiler import CompiledKernel, compile, ASTSource, make_backend - from .._flagtree.tileir_router import pick_target_for_kernel + from ..backends import route_target target = driver.active.get_current_target() - # flagtree: opt compatible kernels into the tileir backend when - # FLAGTREE_USE_TILEIR=1 is set. No-op otherwise. - target = pick_target_for_kernel(target, self) + target = route_target(target, self) backend = make_backend(target) self.CompiledKernel = CompiledKernel self.compile = compile diff --git a/python/tutorials/tileir/01-load-view-token-ordering.py b/python/tutorials/tileir/01-load-view-token-ordering.py index 2498090061..bd9f454bb1 100644 --- a/python/tutorials/tileir/01-load-view-token-ordering.py +++ b/python/tutorials/tileir/01-load-view-token-ordering.py @@ -12,12 +12,13 @@ import sys os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") +os.environ.setdefault("TRITON_CACHE_DIR", "/tmp/flagtree_tileir_tutorial_cache/01") -import torch -import triton -import triton.language as tl -import triton.experimental.tle.language as tle -from triton.compiler.errors import CompilationError +import torch # noqa: E402 +import triton # noqa: E402 +import triton.language as tl # noqa: E402 +import triton.experimental.tle.language as tle # noqa: E402 +from triton.compiler.errors import CompilationError # noqa: E402 @triton.jit @@ -81,9 +82,8 @@ def run_positive(): block = 256 x = torch.arange(n_elements, device="cuda", dtype=torch.float32) y = torch.empty_like(x) - _copy_with_token_kernel[(triton.cdiv(n_elements, block),)]( - x, y, n_elements, BLOCK_SIZE=block, num_warps=4 - ) + grid = (triton.cdiv(n_elements, block), ) + _copy_with_token_kernel[grid](x, y, n_elements, BLOCK_SIZE=block, num_warps=4) torch.testing.assert_close(y, x + 2) print("[OK] FLAGTREE_USE_TILEIR=1: TileIR TKO view-token kernel") @@ -96,9 +96,8 @@ def run_expected_native_failure(): x = torch.arange(n_elements, device="cuda", dtype=torch.float32) y = torch.empty_like(x) try: - _copy_with_token_kernel[(triton.cdiv(n_elements, block),)]( - x, y, n_elements, BLOCK_SIZE=block, num_warps=4 - ) + grid = (triton.cdiv(n_elements, block), ) + _copy_with_token_kernel[grid](x, y, n_elements, BLOCK_SIZE=block, num_warps=4) except CompilationError as exc: text = str(exc) expected_message = "TileIR path" in text or "FLAGTREE_USE_TILEIR" in text @@ -123,6 +122,7 @@ def main(): run_positive() run_negative_subprocess() + print(f"[OK] TRITON_CACHE_DIR={os.environ['TRITON_CACHE_DIR']}") if __name__ == "__main__": diff --git a/python/tutorials/tileir/02-mixed-kernel-routing.py b/python/tutorials/tileir/02-mixed-kernel-routing.py index fda2a3f2aa..793f3a06fb 100644 --- a/python/tutorials/tileir/02-mixed-kernel-routing.py +++ b/python/tutorials/tileir/02-mixed-kernel-routing.py @@ -13,10 +13,10 @@ os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") -import torch -import triton -import triton.language as tl -import triton.experimental.tle.language as tle +import torch # noqa: E402 +import triton # noqa: E402 +import triton.language as tl # noqa: E402 +import triton.experimental.tle.language as tle # noqa: E402 @triton.jit @@ -32,7 +32,7 @@ def _plain_add_kernel(x, y, n_elements, BLOCK_SIZE: tl.constexpr): def _tle_shared_memory_kernel(x, y, BLOCK_SIZE: tl.constexpr): offsets = tl.arange(0, BLOCK_SIZE) smem = tle.gpu.alloc([BLOCK_SIZE], dtype=tl.float32, scope=tle.gpu.smem) - smem_ptrs = tle.gpu.local_ptr(smem, (offsets,)) + smem_ptrs = tle.gpu.local_ptr(smem, (offsets, )) values = tl.load(x + offsets) tl.store(smem_ptrs, values) copied = tl.load(smem_ptrs) @@ -47,7 +47,7 @@ def main(): if not torch.cuda.is_available(): raise RuntimeError("this tutorial requires a CUDA GPU") - cache_dir = pathlib.Path(os.environ.get("TRITON_CACHE_DIR", "/tmp/flagtree_tileir_tutorial_02_cache")) + cache_dir = pathlib.Path(os.environ.get("TRITON_CACHE_DIR", "/tmp/flagtree_tileir_tutorial_cache/02")) cache_dir.mkdir(parents=True, exist_ok=True) os.environ["TRITON_CACHE_DIR"] = str(cache_dir) os.environ["FLAGTREE_USE_TILEIR"] = "1" @@ -58,10 +58,9 @@ def main(): y_tileir = torch.empty_like(x) y_native = torch.empty(block, device="cuda", dtype=torch.float32) - _plain_add_kernel[(triton.cdiv(n_elements, block),)]( - x, y_tileir, n_elements, BLOCK_SIZE=block, num_warps=4 - ) - _tle_shared_memory_kernel[(1,)](x, y_native, BLOCK_SIZE=block, num_warps=4) + plain_grid = (triton.cdiv(n_elements, block), ) + _plain_add_kernel[plain_grid](x, y_tileir, n_elements, BLOCK_SIZE=block, num_warps=4) + _tle_shared_memory_kernel[(1, )](x, y_native, BLOCK_SIZE=block, num_warps=4) torch.testing.assert_close(y_tileir, x + 1) torch.testing.assert_close(y_native, x[:block] + 2) diff --git a/python/tutorials/tileir/03-triton-tileir-benchmarks.py b/python/tutorials/tileir/03-triton-tileir-benchmarks.py index 78c6c424ee..0f6f24a9a1 100644 --- a/python/tutorials/tileir/03-triton-tileir-benchmarks.py +++ b/python/tutorials/tileir/03-triton-tileir-benchmarks.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Triton TileIR Benchmarks ======================== @@ -21,23 +20,21 @@ import shutil import subprocess import sys -import tempfile import traceback import zlib from dataclasses import dataclass from typing import Callable, Dict, List, Optional, Sequence, Tuple - SCRIPT_DIR = pathlib.Path(__file__).resolve().parent if str(SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(SCRIPT_DIR)) os.environ.setdefault("TRITON_BACKENDS_IN_TREE", "1") -import torch - +import torch # noqa: E402 CASES = ("bmm", "fmha", "linear_bias_act", "mla", "mla_decoding", "matmul", "rope") +DEFAULT_CACHE_DIR = pathlib.Path("/tmp/flagtree_tileir_tutorial_cache/03") @dataclass(frozen=True) @@ -168,11 +165,22 @@ def _build_pair_specs() -> Tuple[PairSpec, ...]: PAIR_SPECS = _build_pair_specs() PAIR_BY_ID: Dict[str, PairSpec] = {spec.pair_id: spec for spec in PAIR_SPECS} -PAIRS_BY_CASE: Dict[str, Tuple[PairSpec, ...]] = { - case: tuple(spec for spec in PAIR_SPECS if spec.case == case) for case in CASES +PAIRS_BY_CASE = {case: tuple(spec for spec in PAIR_SPECS if spec.case == case) for case in CASES} +CI_PAIR_IDS = { + "bmm": ("bmm-00", "bmm-04", "bmm-08"), + "fmha": ("fmha-06", "fmha-00", "fmha-08"), + "linear_bias_act": ("linear_bias_act-00", "linear_bias_act-04", "linear_bias_act-08"), + "mla": ("mla-03", "mla-00", "mla-01"), + "mla_decoding": ("mla_decoding-00", "mla_decoding-10", "mla_decoding-20"), + "matmul": ("matmul-04", "matmul-20", "matmul-32"), + "rope": ("rope-01", "rope-05", "rope-09"), } +def _representative_specs(cases: Sequence[str]) -> List[PairSpec]: + return [PAIR_BY_ID[pair_id] for case in cases for pair_id in CI_PAIR_IDS[case]] + + def _set_path(path: str) -> None: if path == "tileir": os.environ["FLAGTREE_USE_TILEIR"] = "1" @@ -426,7 +434,8 @@ def _build_fmha(fn: str) -> BuiltCase: v = _randn((batch, heads, seq_len, head_dim), dtype) scale = 1.0 / math.sqrt(head_dim) check = dtype not in (getattr(torch, "float8_e4m3fn", None), getattr(torch, "float8_e5m2", None)) - ref = None if not check else lambda: torch.nn.functional.scaled_dot_product_attention(q, k, v, is_causal=is_causal, scale=scale) + ref = None if not check else lambda: torch.nn.functional.scaled_dot_product_attention( + q, k, v, is_causal=is_causal, scale=scale) return BuiltCase( run=lambda: mod.triton_fmha(q, k, v, scaling=scale, is_causal=is_causal), ref=ref, @@ -446,7 +455,7 @@ def _build_linear_bias_act(fn: str) -> BuiltCase: mod = _import_triton_op("linear_bias_activation") x = _rand((m, k), dtype) weight = _rand((n, k), dtype) - bias = _rand((n,), dtype) if is_bias else None + bias = _rand((n, ), dtype) if is_bias else None def ref(): out = torch.nn.functional.linear(x, weight, bias) @@ -616,16 +625,14 @@ def run_child( min_rep, initial_rep, ) - row.update( - { - "status": "PASS", - "ms": bench_ms, - "timing": timing, - "validation": validation, - "max_abs": max_abs, - "meta": built.meta, - } - ) + row.update({ + "status": "PASS", + "ms": bench_ms, + "timing": timing, + "validation": validation, + "max_abs": max_abs, + "meta": built.meta, + }) except Exception as exc: row["message"] = f"{type(exc).__name__}: {exc}" row["traceback"] = traceback.format_exc(limit=8) @@ -723,16 +730,18 @@ def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[ for case, case_specs in _group_specs_by_case(specs): case_rows = [row for row in rows if row["pair_id"] in {spec.pair_id for spec in case_specs}] case_summary = { - "pairs": len(case_specs), - "runs": len(case_rows), - "passed": sum(1 for row in case_rows if row.get("status") == "PASS"), - "failed": sum(1 for row in case_rows if row.get("status") != "PASS"), - "passed_pairs": sum( - 1 - for spec in case_specs - if by_pair[spec.pair_id] - and all(row.get("status") == "PASS" for row in by_pair[spec.pair_id].values()) - ), + "pairs": + len(case_specs), + "runs": + len(case_rows), + "passed": + sum(1 for row in case_rows if row.get("status") == "PASS"), + "failed": + sum(1 for row in case_rows if row.get("status") != "PASS"), + "passed_pairs": + sum(1 for spec in case_specs + if by_pair[spec.pair_id] and all(row.get("status") == "PASS" + for row in by_pair[spec.pair_id].values())), } if set(paths) == {"native", "tileir"}: case_ratios = [] @@ -742,15 +751,13 @@ def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[ if native and tileir and native.get("status") == "PASS" and tileir.get("status") == "PASS": case_ratios.append(native["ms"] / tileir["ms"]) if case_ratios: - case_summary.update( - { - "mean_native_over_tileir": sum(case_ratios) / len(case_ratios), - "min_native_over_tileir": min(case_ratios), - "max_native_over_tileir": max(case_ratios), - "tileir_faster": sum(1 for value in case_ratios if value > 1.0), - "speedup_pairs": len(case_ratios), - } - ) + case_summary.update({ + "mean_native_over_tileir": sum(case_ratios) / len(case_ratios), + "min_native_over_tileir": min(case_ratios), + "max_native_over_tileir": max(case_ratios), + "tileir_faster": sum(1 for value in case_ratios if value > 1.0), + "speedup_pairs": len(case_ratios), + }) summary["cases"][case] = case_summary if set(paths) == {"native", "tileir"}: ratios = [] @@ -760,15 +767,13 @@ def _summarize(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[ if native and tileir and native.get("status") == "PASS" and tileir.get("status") == "PASS": ratios.append(native["ms"] / tileir["ms"]) if ratios: - summary.update( - { - "mean_native_over_tileir": sum(ratios) / len(ratios), - "min_native_over_tileir": min(ratios), - "max_native_over_tileir": max(ratios), - "tileir_faster": sum(1 for value in ratios if value > 1.0), - "speedup_pairs": len(ratios), - } - ) + summary.update({ + "mean_native_over_tileir": sum(ratios) / len(ratios), + "min_native_over_tileir": min(ratios), + "max_native_over_tileir": max(ratios), + "tileir_faster": sum(1 for value in ratios if value > 1.0), + "speedup_pairs": len(ratios), + }) return summary @@ -780,66 +785,51 @@ def _print_case_table(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Se pair_col_width = 20 if set(paths) == {"native", "tileir"}: - print( - f"{'pair':<{pair_col_width}} {'native ms':>10} {'TileIR ms':>10} " - f"{'native/TileIR':>13} {'check':>8} {'route':>13} {'timing':>14} signature" - ) + print(f"{'pair':<{pair_col_width}} {'native ms':>10} {'TileIR ms':>10} " + f"{'native/TileIR':>13} {'check':>8} {'route':>13} {'timing':>14} signature") print("-" * 144) for spec in specs: native = by_pair[spec.pair_id].get("native") tileir = by_pair[spec.pair_id].get("tileir") if native and tileir and native.get("status") == "PASS" and tileir.get("status") == "PASS": ratio = native["ms"] / tileir["ms"] if tileir["ms"] else float("nan") - validation = native.get("validation") if native.get("validation") == tileir.get("validation") else "mixed" + validation = native.get("validation") if native.get("validation") == tileir.get( + "validation") else "mixed" route_s = f"{native.get('route')}/{tileir.get('route')}" timing_s = native.get("timing") if native.get("timing") == tileir.get("timing") else "mixed" - print( - f"{spec.pair_id:<{pair_col_width}} {native['ms']:10.3f} {tileir['ms']:10.3f} " - f"{ratio:13.3f} {validation:>8} {route_s:>13} {timing_s:>14} {spec.function_name}" - ) + print(f"{spec.pair_id:<{pair_col_width}} {native['ms']:10.3f} {tileir['ms']:10.3f} " + f"{ratio:13.3f} {validation:>8} {route_s:>13} {timing_s:>14} {spec.function_name}") else: for row in (native, tileir): if row: - print( - f"{spec.pair_id:<{pair_col_width}} {row.get('path', ''):<10} " - f"{row.get('status', 'FAIL'):<8} route={row.get('route')} " - f"message={row.get('message', '')}" - ) + print(f"{spec.pair_id:<{pair_col_width}} {row.get('path', ''):<10} " + f"{row.get('status', 'FAIL'):<8} route={row.get('route')} " + f"message={row.get('message', '')}") return - print( - f"{'pair':<{pair_col_width}} {'path':<8} {'ms':>10} " - f"{'check':>8} {'route':>8} {'timing':>14} signature" - ) + print(f"{'pair':<{pair_col_width}} {'path':<8} {'ms':>10} " + f"{'check':>8} {'route':>8} {'timing':>14} signature") print("-" * 128) for spec in specs: for path in paths: row = by_pair[spec.pair_id].get(path) if row and row.get("status") == "PASS": - print( - f"{spec.pair_id:<{pair_col_width}} {path:<8} {row['ms']:10.3f} " - f"{row.get('validation'):>8} {row.get('route'):>8} {row.get('timing'):>14} {spec.function_name}" - ) + print(f"{spec.pair_id:<{pair_col_width}} {path:<8} {row['ms']:10.3f} " + f"{row.get('validation'):>8} {row.get('route'):>8} {row.get('timing'):>14} {spec.function_name}") elif row: - print( - f"{spec.pair_id:<{pair_col_width}} {path:<8} {row.get('status', 'FAIL'):<8} " - f"route={row.get('route')} message={row.get('message', '')}" - ) + print(f"{spec.pair_id:<{pair_col_width}} {path:<8} {row.get('status', 'FAIL'):<8} " + f"route={row.get('route')} message={row.get('message', '')}") def _print_case_summary(summary: dict) -> None: - print( - f"passed runs: {summary['passed']}/{summary['runs']} " - f"passed pairs: {summary['passed_pairs']}/{summary['pairs']}" - ) + print(f"passed runs: {summary['passed']}/{summary['runs']} " + f"passed pairs: {summary['passed_pairs']}/{summary['pairs']}") if "mean_native_over_tileir" in summary: - print( - "native/TileIR ratio: " - f"mean={summary['mean_native_over_tileir']:.3f}, " - f"min={summary['min_native_over_tileir']:.3f}, " - f"max={summary['max_native_over_tileir']:.3f}, " - f"TileIR faster={summary['tileir_faster']}/{summary['speedup_pairs']}" - ) + print("native/TileIR ratio: " + f"mean={summary['mean_native_over_tileir']:.3f}, " + f"min={summary['min_native_over_tileir']:.3f}, " + f"max={summary['max_native_over_tileir']:.3f}, " + f"TileIR faster={summary['tileir_faster']}/{summary['speedup_pairs']}") def _print_tables(rows: Sequence[dict], specs: Sequence[PairSpec], paths: Sequence[str], summary: dict) -> None: @@ -855,9 +845,7 @@ def _print_pair_progress(spec: PairSpec, pair_rows: Sequence[dict], stream) -> N by_path = {row.get("path"): row for row in pair_rows} failures = [row for row in pair_rows if row.get("status") != "PASS"] if failures: - details = "; ".join( - f"{row.get('path', '?')}: {row.get('message', 'failed')}" for row in failures - ) + details = "; ".join(f"{row.get('path', '?')}: {row.get('message', 'failed')}" for row in failures) print(f"[FAIL] {spec.pair_id} {spec.case}: {details}", file=stream, flush=True) return @@ -873,9 +861,7 @@ def _print_pair_progress(spec: PairSpec, pair_rows: Sequence[dict], stream) -> N ) return - details = ", ".join( - f"{row.get('path', '?')}={row['ms']:.3f} ms route={row.get('route')}" for row in pair_rows - ) + details = ", ".join(f"{row.get('path', '?')}={row['ms']:.3f} ms route={row.get('route')}" for row in pair_rows) print(f"[OK] {spec.pair_id} {spec.case}: {details}", file=stream, flush=True) @@ -894,12 +880,12 @@ def run_parent( ) -> int: script = pathlib.Path(__file__).resolve() if cache_dir is None: - cache_root = pathlib.Path(tempfile.mkdtemp(prefix="flagtree_tileir_kernel_benchmarks_")) + cache_root = pathlib.Path(os.environ.get("TRITON_CACHE_DIR", str(DEFAULT_CACHE_DIR))) else: cache_root = cache_dir - if clear_cache and cache_root.exists(): - shutil.rmtree(cache_root) - cache_root.mkdir(parents=True, exist_ok=True) + if clear_cache and cache_root.exists(): + shutil.rmtree(cache_root) + cache_root.mkdir(parents=True, exist_ok=True) rows = [] progress_stream = sys.stderr if json_output else sys.stdout @@ -942,6 +928,7 @@ def _parse_cases(value: str) -> List[str]: def _parse_pair_filter(value: str, cases: Sequence[str]) -> List[PairSpec]: + def sort_specs(specs: Sequence[PairSpec]) -> List[PairSpec]: case_index = {case: idx for idx, case in enumerate(cases)} pair_index = {spec.pair_id: idx for idx, spec in enumerate(PAIR_SPECS)} @@ -973,16 +960,23 @@ def sort_specs(specs: Sequence[PairSpec]) -> List[PairSpec]: def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--case", default="all", help="comma-separated case list or 'all'") - parser.add_argument("--pair", default="all", help="pair id, comma-separated pair ids, numeric case-local index, or 'all'") + parser.add_argument("--pair", default="all", + help="pair id, comma-separated pair ids, numeric case-local index, or 'all'") parser.add_argument("--path", choices=("both", "native", "tileir"), default="both") parser.add_argument("--warmup", type=float, default=100.0, help="benchmark warmup duration in ms") parser.add_argument("--rep", type=float, default=50.0, help="benchmark measured duration in ms") parser.add_argument("--min-rep", type=int, default=2, help="minimum measured benchmark iterations") parser.add_argument("--initial-rep", type=int, default=5, help="initial iterations for benchmark runtime estimate") - parser.add_argument("--no-check", dest="check", action="store_false", help="skip torch-reference checks where available") + parser.add_argument("--no-check", dest="check", action="store_false", + help="skip torch-reference checks where available") parser.add_argument("--cache-dir", type=pathlib.Path, default=None) parser.add_argument("--clear-cache", action="store_true") parser.add_argument("--json", action="store_true") + parser.add_argument( + "--ci-subset", + action="store_true", + help="run three curated representative pairs per selected case for CI", + ) parser.add_argument("--require-speedup", action="store_true", help="require every paired TileIR run to be faster") parser.add_argument( "--require-speedup-fraction", @@ -1000,7 +994,12 @@ def main() -> int: require_speedup_fraction = 1.0 cases = _parse_cases(args.case) - specs = _parse_pair_filter(args.pair, cases) + if args.ci_subset: + if args.pair != "all": + raise SystemExit("--ci-subset cannot be combined with --pair") + specs = _representative_specs(cases) + else: + specs = _parse_pair_filter(args.pair, cases) if args.child: if len(specs) != 1 or args.path == "both": raise SystemExit("--child requires exactly one pair and one path") diff --git a/python/tutorials/tileir/README.md b/python/tutorials/tileir/README.md index 0ae258be5a..f446e9dd4f 100644 --- a/python/tutorials/tileir/README.md +++ b/python/tutorials/tileir/README.md @@ -4,6 +4,10 @@ These tutorials assume FlagTree has been built with the default in-tree backend set including `tileir`. For build environment requirements, see [`third_party/tileir/README.md`](../../../third_party/tileir/README.md). +By default, their compilation caches are stored under +`/tmp/flagtree_tileir_tutorial_cache/{01,02,03}`. `TRITON_CACHE_DIR` overrides +the default, and tutorial 03 also accepts `--cache-dir`. + ## 01 Load View Token Ordering Tests: @@ -68,6 +72,13 @@ python3 python/tutorials/tileir/03-triton-tileir-benchmarks.py \ --case all --pair all --path both ``` +Run the CI subset, with three curated representative pairs per selected case: + +```bash +python3 python/tutorials/tileir/03-triton-tileir-benchmarks.py \ + --case all --ci-subset --path both +``` + Available cases: ```text diff --git a/python/tutorials/tileir/tilegym/backend.py b/python/tutorials/tileir/tilegym/backend.py index 62257f0506..ab273fa4ea 100644 --- a/python/tutorials/tileir/tilegym/backend.py +++ b/python/tutorials/tileir/tilegym/backend.py @@ -1,7 +1,6 @@ import functools import os - _IMPL_REGISTRY = {} @@ -18,6 +17,7 @@ def is_backend_available(name): def register_impl(name=None, backend=None): + def deco(fn): impl_name = name or fn.__name__ impl_backend = backend or "triton" @@ -49,6 +49,7 @@ def dispatch(name, fallback_backend=None): """ def deco(fn): + @functools.wraps(fn) def wrapper(*args, **kwargs): backend = get_current_backend() diff --git a/python/tutorials/tileir/tilegym/logger.py b/python/tutorials/tileir/tilegym/logger.py index b56fe3476e..ee6d3b4f2a 100644 --- a/python/tutorials/tileir/tilegym/logger.py +++ b/python/tutorials/tileir/tilegym/logger.py @@ -1,4 +1,5 @@ class _Logger: + def debug(self, *_args, **_kwargs): pass diff --git a/python/tutorials/tileir/tilegym/ops/__init__.py b/python/tutorials/tileir/tilegym/ops/__init__.py index 8b13789179..e69de29bb2 100644 --- a/python/tutorials/tileir/tilegym/ops/__init__.py +++ b/python/tutorials/tileir/tilegym/ops/__init__.py @@ -1 +0,0 @@ - diff --git a/python/tutorials/tileir/tilegym/ops/triton/__init__.py b/python/tutorials/tileir/tilegym/ops/triton/__init__.py index 8b13789179..e69de29bb2 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/__init__.py +++ b/python/tutorials/tileir/tilegym/ops/triton/__init__.py @@ -1 +0,0 @@ - diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py b/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py index 8b13789179..e69de29bb2 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/__init__.py @@ -1 +0,0 @@ - diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py index 37b0d1970d..5b08234505 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/gelu.py @@ -126,6 +126,7 @@ def _gelu_kernel_backward( class _GeLU(torch.autograd.Function): + @staticmethod def forward(ctx, x, approximate): assert approximate == "none" or approximate == "tanh", "Only `none` or `tanh` activations are supported" @@ -134,7 +135,7 @@ def forward(ctx, x, approximate): y = torch.empty_like(x) n_elements = y.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) BLOCK_SIZE = 1024 _gelu_kernel[grid](y, x, n_elements, BLOCK_SIZE, approximate == "tanh") @@ -145,7 +146,7 @@ def forward(ctx, x, approximate): def backward(ctx, dy): x = ctx.x n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) dx = torch.empty_like(dy) @@ -172,8 +173,6 @@ def gelu(input: torch.Tensor, approximate="none"): return _GeLU.apply(input.view(-1), approximate).view(input.shape) - # Backend Registration & Perf Markers mark_perf_ready("gelu", "nvt") - diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py index ffbb8dafc9..8d2a4415c4 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/relu.py @@ -22,7 +22,6 @@ from tilegym.backend import register_impl from .utils import exp_forward - """ ======================================================= ReLU @@ -91,6 +90,7 @@ def _relu_bwd_kernel( class _ReLU(torch.autograd.Function): + @staticmethod def forward(ctx, x): ctx.x = x @@ -99,7 +99,7 @@ def forward(ctx, x): assert x.is_contiguous() n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _relu_fwd_kernel[grid](x, y, n_elements, BLOCK_SIZE=1024) return y @@ -108,7 +108,7 @@ def forward(ctx, x): def backward(ctx, dy): dx = torch.empty_like(dy) n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _relu_bwd_kernel[grid](dy, dx, ctx.x, n_elements, BLOCK_SIZE=1024) return dx @@ -181,6 +181,7 @@ def _elu_bwd_kernel( class _ELU(torch.autograd.Function): + @staticmethod def forward(ctx, x, alpha): ctx.x = x @@ -189,7 +190,7 @@ def forward(ctx, x, alpha): y = torch.empty_like(x) n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _elu_fwd_kernel[grid](x, y, alpha, n_elements, BLOCK_SIZE=1024) return y @@ -199,7 +200,7 @@ def backward(ctx, dy): dx = torch.empty_like(dy) n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _elu_bwd_kernel[grid](dy, dx, ctx.x, ctx.ALPHA, n_elements, BLOCK_SIZE=1024) return dx, None @@ -280,6 +281,7 @@ def _leaky_relu_bwd_kernel( class _LeakyReLU(torch.autograd.Function): + @staticmethod def forward(ctx, x, alpha): ctx.x = x @@ -288,7 +290,7 @@ def forward(ctx, x, alpha): y = torch.empty_like(x) n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _leaky_relu_fwd_kernel[grid](x, y, alpha, n_elements, BLOCK_SIZE=1024) return y @@ -298,7 +300,7 @@ def backward(ctx, dy): dx = torch.empty_like(dy) n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _leaky_relu_bwd_kernel[grid](dy, dx, ctx.x, ctx.ALPHA, n_elements, BLOCK_SIZE=1024) return dx, None @@ -436,18 +438,10 @@ def _vector_prelu_nchw_fwd_kernel( if get_available_triton_backend() == "nvt": @triton.jit - def _scalar_prelu_bwd_stage1_kernel( - dy_ptr, - dx_ptr, - dw_ptr, - x_ptr, - w_ptr, - lock_ptr, - n_elements, - n_weights, - BLOCK_SIZE: tl.constexpr, - GROUP_SIZE_M: tl.constexpr, # this should be the number of groups - ): + def _scalar_prelu_bwd_stage1_kernel(dy_ptr, dx_ptr, dw_ptr, x_ptr, w_ptr, lock_ptr, n_elements, n_weights, + BLOCK_SIZE: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, # this should be the number of groups + ): pid = tl.program_id(0) block_start = pid * BLOCK_SIZE offsets = block_start + tl.arange(0, BLOCK_SIZE) @@ -698,11 +692,11 @@ def _vector_prelu_nc_bwd_stage2_kernel( tl.store(dw_ptr + col_offsets, sum_dw, mask=col_offsets < C) - # Host Launchers & Public API class _PReLU(torch.autograd.Function): + @staticmethod def forward(ctx, x, w): # weight should either be a scalar or match the number of channels of input. @@ -716,7 +710,7 @@ def forward(ctx, x, w): if n_weights == 1: n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _scalar_prelu_fwd_kernel[grid](x, y, w, n_elements, BLOCK_SIZE=1024) elif x.dim() == 2: N, C = x.shape @@ -764,7 +758,7 @@ def backward(ctx, dy): if n_weights == 1: n_elements = dy.numel() locks = torch.zeros(GROUP_SIZE, dtype=torch.int32, device=ctx.w.device) - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _scalar_prelu_bwd_stage1_kernel[grid]( dy, dx, @@ -778,8 +772,7 @@ def backward(ctx, dy): GROUP_SIZE_M=GROUP_SIZE, ) dw = _dw.sum( - dim=0, keepdim=True - ) # TODO: am I allowed to do this? it's just summing a 64d vector into a scalar. + dim=0, keepdim=True) # TODO: am I allowed to do this? it's just summing a 64d vector into a scalar. elif dy.dim() == 2: N, C = dy.shape num_blocks_per_row = triton.cdiv(C, BLOCK_SIZE) @@ -801,7 +794,7 @@ def backward(ctx, dy): BLOCK_SIZE=BLOCK_SIZE, GROUP_SIZE=GROUP_SIZE, ) - grid = lambda meta: (triton.cdiv(C, meta["BLOCK_SIZE_C"]),) + grid = lambda meta: (triton.cdiv(C, meta["BLOCK_SIZE_C"]), ) _vector_prelu_nc_bwd_stage2_kernel[grid](_dw, dw, GROUP_SIZE, C, BLOCK_SIZE_N=32, BLOCK_SIZE_C=128) else: dy_squashed = dy.view(dy.shape[0], dy.shape[1], -1) @@ -944,6 +937,7 @@ def _rrelu_bwd_kernel( class _RReLU(torch.autograd.Function): + @staticmethod def forward(ctx, x, seed, lower, upper, training): ctx.x = x @@ -955,7 +949,7 @@ def forward(ctx, x, seed, lower, upper, training): y = torch.empty_like(x) n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _rrelu_fwd_kernel[grid](x, y, seed, lower, upper, training, n_elements, BLOCK_SIZE=1024) return y @@ -965,7 +959,7 @@ def backward(ctx, dy): dx = torch.empty_like(dy) n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _rrelu_bwd_kernel[grid]( dy, dx, @@ -1067,6 +1061,7 @@ def _selu_bwd_kernel( class _SELU(torch.autograd.Function): + @staticmethod def forward(ctx, x): ctx.x = x @@ -1074,7 +1069,7 @@ def forward(ctx, x): y = torch.empty_like(x) n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _selu_fwd_kernel[grid](x, y, n_elements, BLOCK_SIZE=1024) return y @@ -1084,7 +1079,7 @@ def backward(ctx, dy): dx = torch.empty_like(dy) n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _selu_bwd_kernel[grid](dy, dx, ctx.x, n_elements, BLOCK_SIZE=1024) return dx @@ -1165,6 +1160,7 @@ def _celu_bwd_kernel( class _CELU(torch.autograd.Function): + @staticmethod def forward(ctx, x, alpha): ctx.x = x @@ -1173,7 +1169,7 @@ def forward(ctx, x, alpha): y = torch.empty_like(x) n_elements = x.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _celu_fwd_kernel[grid](x, y, alpha, n_elements, BLOCK_SIZE=1024) return y @@ -1183,7 +1179,7 @@ def backward(ctx, dy): dx = torch.empty_like(dy) n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _celu_bwd_kernel[grid](dy, dx, ctx.x, ctx.alpha, n_elements, BLOCK_SIZE=1024) return dx, None @@ -1204,8 +1200,6 @@ def celu(x, alpha=1.0): return _CELU.apply(x, alpha) - - mark_perf_ready("celu", "nvt") mark_perf_ready("elu", "nvt") mark_perf_ready("leaky_relu", "nvt") @@ -1213,4 +1207,3 @@ def celu(x, alpha=1.0): mark_perf_ready("relu", "nvt") mark_perf_ready("rrelu", "nvt") mark_perf_ready("selu", "nvt") - diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py b/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py index 21dbdaa758..2b7ea4e2d6 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/silu.py @@ -73,13 +73,14 @@ def _silu_kernel_backward( class _SiLU(torch.autograd.Function): + @staticmethod def forward(ctx, x): # Allocate output y = torch.empty_like(x) n_elements = y.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _silu_kernel[grid](y, x, n_elements, BLOCK_SIZE=1024) ctx.x = x @@ -89,7 +90,7 @@ def forward(ctx, x): def backward(ctx, dy): x = ctx.x n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) dx = torch.empty_like(dy) @@ -99,9 +100,7 @@ def backward(ctx, dy): @register_impl("silu", backend="triton") -def silu( - input: torch.Tensor, -): +def silu(input: torch.Tensor, ): r""" Returns SiLU activation of input. diff --git a/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py b/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py index e27f32dc00..00b9becf0a 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py +++ b/python/tutorials/tileir/tilegym/ops/triton/activation/tanh.py @@ -68,13 +68,14 @@ def _tanh_kernel_backward( class _Tanh(torch.autograd.Function): + @staticmethod def forward(ctx, x): # Allocate output y = torch.empty_like(x) n_elements = y.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) _tanh_kernel[grid](y, x, n_elements, BLOCK_SIZE=1024) @@ -85,7 +86,7 @@ def forward(ctx, x): def backward(ctx, dy): y = ctx.y n_elements = dy.numel() - grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]),) + grid = lambda meta: (triton.cdiv(n_elements, meta["BLOCK_SIZE"]), ) dx = torch.empty_like(dy) @@ -95,9 +96,7 @@ def backward(ctx, dy): @register_impl("tanh", backend="triton") -def tanh( - input: torch.Tensor, -): +def tanh(input: torch.Tensor, ): r""" Returns Tanh actication of input. diff --git a/python/tutorials/tileir/tilegym/ops/triton/attention.py b/python/tutorials/tileir/tilegym/ops/triton/attention.py index b867d07a0c..e34f6cff08 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/attention.py +++ b/python/tutorials/tileir/tilegym/ops/triton/attention.py @@ -39,7 +39,6 @@ def _supports_v2_opt_level(): _TRITON_SUPPORTS_V2_OPT_LEVEL = _supports_v2_opt_level() - def _supports_host_descriptor(): return torch.cuda.get_device_capability()[0] >= 9 @@ -389,9 +388,8 @@ def _get_configs(is_backward=False): ] elif torch.cuda.get_device_capability() == (8, 0): configs = [ - triton.Config( - dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=occ, **default_config), num_stages=s, pre_hook=_hook - ) + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=occ, **default_config), num_stages=s, + pre_hook=_hook) for BM in [64, 128] for BN in [32, 64] for s in [2, 3, 4] @@ -412,25 +410,21 @@ def _get_configs(is_backward=False): # (mixed cta_group::1 and cta_group::2). See CFK-33703. if torch.cuda.get_device_capability() != (10, 3): configs.append( - triton.Config( - dict(BLOCK_M=256, BLOCK_N=128, occupancy=2, **default_config), num_ctas=2, pre_hook=_hook - ) - ) + triton.Config(dict(BLOCK_M=256, BLOCK_N=128, occupancy=2, **default_config), num_ctas=2, + pre_hook=_hook)) elif get_available_triton_backend() == "oait": # Full tuning space for oait if is_backward: # Force num_stages=1 to work around oait bug for bwd, see https://github.com/triton-lang/triton/issues/7386 configs = [ - triton.Config( - dict(BLOCK_M=128, BLOCK_N=64, warp_specialize=False), num_stages=1, num_warps=4, pre_hook=_hook - ) + triton.Config(dict(BLOCK_M=128, BLOCK_N=64, warp_specialize=False), num_stages=1, num_warps=4, + pre_hook=_hook) ] configs = list(configs) else: configs = [ - triton.Config( - dict(BLOCK_M=BM, BLOCK_N=BN, warp_specialize=ws), num_stages=s, num_warps=w, pre_hook=_hook - ) + triton.Config(dict(BLOCK_M=BM, BLOCK_N=BN, warp_specialize=ws), num_stages=s, num_warps=w, + pre_hook=_hook) for BM in [64, 128, 256] for BN in [64, 128] for s in [2, 3, 4] @@ -464,11 +458,9 @@ def _prune_invalid_configs(configs, named_args, **kwargs): key=["S_qo", "S_kv", "BLOCK_D", "STAGE", "QUERY_GROUP_SIZE", "dtype"], prune_configs_by={"early_config_prune": _prune_invalid_configs}, ) -@triton.heuristics( - { - "EVEN_K": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, - } -) +@triton.heuristics({ + "EVEN_K": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, +}) @triton.jit def _prefill_fmha( Q, @@ -807,6 +799,7 @@ def _fmha_bwd_kernel( class _attention(torch.autograd.Function): + @staticmethod def forward( ctx, @@ -1117,8 +1110,6 @@ def triton_fmha( return o - # Backend Registration & Perf Markers mark_perf_ready("fmha", "nvt") - diff --git a/python/tutorials/tileir/tilegym/ops/triton/bmm.py b/python/tutorials/tileir/tilegym/ops/triton/bmm.py index 784698eac5..3c881b3ace 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/bmm.py +++ b/python/tutorials/tileir/tilegym/ops/triton/bmm.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT - # Imports from typing import Optional @@ -19,7 +18,6 @@ logger = get_logger(__name__) - # Capability Probe @@ -135,12 +133,7 @@ def _bmm_memref_get_configs(pre_hook=None): pre_hook=pre_hook, num_stages=s, num_warps=4, - ) - for BM in [128] - for BN in [128] - for BK in [32, 64] - for occ in [2] - for s in [2, 4] + ) for BM in [128] for BN in [128] for BK in [32, 64] for occ in [2] for s in [2, 4] ] else: return [ @@ -214,12 +207,7 @@ def _bmm_memref_get_configs(pre_hook=None): pre_hook=pre_hook, num_stages=s, num_warps=4, - ) - for BM in [128] - for BN in [128] - for BK in [32, 64] - for occ in [2] - for s in [2, 4] + ) for BM in [128] for BN in [128] for BK in [32, 64] for occ in [2] for s in [2, 4] ] else: return [ @@ -228,8 +216,7 @@ def _bmm_memref_get_configs(pre_hook=None): pre_hook=pre_hook, num_ctas=2, num_stages=s, - ) - for s in [3, 4, 5] + ) for s in [3, 4, 5] ] @@ -249,9 +236,8 @@ def _bmm_memref_get_configs(pre_hook=None): configs=[ # triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 64, 'GROUP_SIZE_M': 8}, num_stages=3, num_warps=8), # triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 256, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 - ), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, + num_warps=4), # triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 64, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), # triton.Config({'BLOCK_SIZE_M': 64, 'BLOCK_SIZE_N': 128, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), # triton.Config({'BLOCK_SIZE_M': 128, 'BLOCK_SIZE_N': 32, 'BLOCK_SIZE_K': 32, 'GROUP_SIZE_M': 8}, num_stages=4, num_warps=4), @@ -260,11 +246,9 @@ def _bmm_memref_get_configs(pre_hook=None): ], key=["M", "N", "K"], ) -@triton.heuristics( - { - "EVEN_K": lambda args: args["K"] % args["BLOCK_SIZE_K"] == 0, - } -) +@triton.heuristics({ + "EVEN_K": lambda args: args["K"] % args["BLOCK_SIZE_K"] == 0, +}) @triton.jit def _bmm_kernel_naive( a_ptr, @@ -574,7 +558,7 @@ def bmm_memref(a, b, transpose_a=False, transpose_b=False, static_persistent=Non assert b.is_contiguous(), "matrix B must be contiguous" c = torch.empty((Q, M, N), device=a.device, dtype=a.dtype) - assert static_persistent == True, "only support static persistent mode" + assert static_persistent, "only support static persistent mode" # TMA descriptors require a global memory allocation def alloc_fn(size: int, alignment: int, stream: Optional[int]): @@ -605,13 +589,10 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): else: num_ctas = 2 - grid = lambda META: ( - min( - NUM_SMS // num_ctas, - triton.cdiv(M, META["BLOCK_M"]) * triton.cdiv(N, META["BLOCK_N"]) * Q, - ) - * META["occupancy"], - ) + grid = lambda META: (min( + NUM_SMS // num_ctas, + triton.cdiv(M, META["BLOCK_M"]) * triton.cdiv(N, META["BLOCK_N"]) * Q, + ) * META["occupancy"], ) _bmm_kernel_memref[grid]( a_desc, b_desc, @@ -637,6 +618,7 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): class _BMM(torch.autograd.Function): + @staticmethod def forward(ctx, a, b): c = bmm_memref(a, b) diff --git a/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py b/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py index 277f372a3c..eeeae8e864 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py +++ b/python/tutorials/tileir/tilegym/ops/triton/linear_bias_activation.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: MIT - # Imports import itertools @@ -29,7 +28,6 @@ # Constants & Type Aliases - supported_act_types = { "relu": {"fwd": relu_forward, "bwd": relu_backward}, "gelu": {"fwd": gelu_tanh_forward, "bwd": gelu_backward}, @@ -37,6 +35,17 @@ } +@triton.jit +def _act_bwd_func(act_inp, dy, ACT_TYPE: tl.constexpr): + if ACT_TYPE == "gelu": + return gelu_backward(act_inp, dy) + if ACT_TYPE == "relu": + return relu_backward(act_inp, dy) + if ACT_TYPE == "silu": + return silu_backward(act_inp, dy) + return dy + + # Autotune Config @@ -122,13 +131,13 @@ def _matmul_act_bias_bwd_get_configs_io_bound(): configs = [] for num_stages in [2, 6]: for block_m in [ - 16, + 16, ]: for block_k in [ - 32, + 32, ]: for block_n in [ - 32, + 32, ]: num_warps = 2 if block_n <= 64 else 4 configs.append( @@ -137,8 +146,7 @@ def _matmul_act_bias_bwd_get_configs_io_bound(): num_stages=num_stages, num_warps=num_warps, pre_hook=_host_descriptor_bias_bwd_pre_hook, - ) - ) + )) return configs @@ -146,11 +154,11 @@ def _get_configs_io_bound(): configs = [] for num_stages in [2, 3]: for block_m in [ - 32, + 32, ]: for block_k in [32, 64]: for block_n in [ - 128, + 128, ]: num_warps = 2 if block_n <= 64 else 4 configs.append( @@ -159,8 +167,7 @@ def _get_configs_io_bound(): num_stages=num_stages, num_warps=num_warps, pre_hook=_host_descriptor_pre_hook, - ) - ) + )) return configs @@ -191,8 +198,7 @@ def _get_configs_io_bound(): # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook), # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=5, num_warps=2, pre_hook=_host_descriptor_bias_bwd_pre_hook), - ] - + _matmul_act_bias_bwd_get_configs_io_bound(), + ] + _matmul_act_bias_bwd_get_configs_io_bound(), key=["M", "N", "K", "align_a", "align_b"], prune_configs_by={ "early_config_prune": early_config_prune, @@ -204,33 +210,24 @@ def _get_configs_io_bound(): matmul_autotune = triton.autotune( configs=[ # basic configs for compute-bound matmuls - triton.Config( - dict(BLOCK_M=128, BLOCK_N=256, BLOCK_K=32), num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=256, BLOCK_N=128, BLOCK_K=32), num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=256, BLOCK_N=64, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=64, BLOCK_N=256, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=128, BLOCK_N=128, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=128, BLOCK_N=64, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=64, BLOCK_N=128, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=128, BLOCK_N=32, BLOCK_K=32), num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook - ), - triton.Config( - dict(BLOCK_M=64, BLOCK_N=32, BLOCK_K=32), num_stages=5, num_warps=2, pre_hook=_host_descriptor_pre_hook - ), + triton.Config(dict(BLOCK_M=128, BLOCK_N=256, BLOCK_K=32), num_stages=3, num_warps=8, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=256, BLOCK_N=128, BLOCK_K=32), num_stages=3, num_warps=8, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=256, BLOCK_N=64, BLOCK_K=32), num_stages=4, num_warps=4, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=64, BLOCK_N=256, BLOCK_K=32), num_stages=4, num_warps=4, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=128, BLOCK_N=128, BLOCK_K=32), num_stages=4, num_warps=4, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=128, BLOCK_N=64, BLOCK_K=32), num_stages=4, num_warps=4, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=64, BLOCK_N=128, BLOCK_K=32), num_stages=4, num_warps=4, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=128, BLOCK_N=32, BLOCK_K=32), num_stages=4, num_warps=4, + pre_hook=_host_descriptor_pre_hook), + triton.Config(dict(BLOCK_M=64, BLOCK_N=32, BLOCK_K=32), num_stages=5, num_warps=2, + pre_hook=_host_descriptor_pre_hook), # good for int8 # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 128}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook), # triton.Config({'BLOCK_M': 256, 'BLOCK_N': 128, 'BLOCK_K': 128}, num_stages=3, num_warps=8, pre_hook=_host_descriptor_pre_hook), @@ -241,8 +238,7 @@ def _get_configs_io_bound(): # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), # triton.Config({'BLOCK_M': 128, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=4, num_warps=4, pre_hook=_host_descriptor_pre_hook), # triton.Config({'BLOCK_M': 64, 'BLOCK_N': 32, 'BLOCK_K': 64}, num_stages=5, num_warps=2, pre_hook=_host_descriptor_pre_hook), - ] - + _get_configs_io_bound(), + ] + _get_configs_io_bound(), key=["M", "N", "K", "align_a", "align_b"], # prune_configs_by={ # 'early_config_prune': early_config_prune, @@ -251,19 +247,13 @@ def _get_configs_io_bound(): # }, ) -matmul_act_bias_bwd_heuristics = triton.heuristics( - { - "EVEN_K": lambda args: args["K"] % args["BLOCK_K"] == 0, - } -) - - -matmul_heuristics = triton.heuristics( - { - "EVEN_K": lambda args: args["K"] % args["BLOCK_K"] == 0, - } -) +matmul_act_bias_bwd_heuristics = triton.heuristics({ + "EVEN_K": lambda args: args["K"] % args["BLOCK_K"] == 0, +}) +matmul_heuristics = triton.heuristics({ + "EVEN_K": lambda args: args["K"] % args["BLOCK_K"] == 0, +}) # Device Kernels @@ -394,23 +384,19 @@ def matmul_bias_activation_dropout_fwd( @triton.autotune( configs=[ # fmt: off - triton.Config( - dict(BLOCK_M=32, BLOCK_N=32), num_stages=1, num_warps=4, pre_hook=_host_descriptor_bias_bwd_pre_hook - ), + triton.Config(dict(BLOCK_M=32, BLOCK_N=32), num_stages=1, num_warps=4, + pre_hook=_host_descriptor_bias_bwd_pre_hook), # triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32}, num_stages=1, num_warps=16, pre_hook=_host_descriptor_bias_bwd_pre_hook), # triton.Config({'BLOCK_M': 32, 'BLOCK_N': 64}, num_stages=1, num_warps=1, pre_hook=_host_descriptor_bias_bwd_pre_hook), # triton.Config({'BLOCK_M': 32, 'BLOCK_N': 32}, num_stages=1, num_warps=1, pre_hook=_host_descriptor_bias_bwd_pre_hook), # fmt: on - ] - if not os.getenv("EXHAUSTIVE_BWD", False) - else [ + ] if not os.getenv("EXHAUSTIVE_BWD", False) else [ triton.Config( dict(BLOCK_M=block_m, BLOCK_N=block_n), num_stages=num_stages, num_warps=num_warps, pre_hook=_host_descriptor_bias_bwd_pre_hook, - ) - for block_m, block_n, num_warps, num_stages in itertools.product([64, 128], [32, 128], [1, 4, 16], [1, 4]) + ) for block_m, block_n, num_warps, num_stages in itertools.product([64, 128], [32, 128], [1, 4, 16], [1, 4]) ], key=["M", "N"], ) @@ -445,8 +431,7 @@ def _kernel_bias_act_bwd( if ACT_TYPE is not None: act_inp = Act_in.load([offset_m, offset_n]) - dy = _act_bwd_func(act_inp, dy) - # dy = act_bwd_func(act_inp, dy, ACT_TYPE) + dy = _act_bwd_func(act_inp, dy, ACT_TYPE) Dy.store([offset_m, offset_n], dy) if IS_BIAS: @@ -660,8 +645,7 @@ def linear_bias_act_dropout( bias: (out_features) or None """ assert act_type is None or act_type in supported_act_types.keys(), ( - f"Activation Type: {act_type} is not supported by the fused linear layer" - ) + f"Activation Type: {act_type} is not supported by the fused linear layer") return _LinearBiasActDropout.apply( input, weight, @@ -686,8 +670,7 @@ def _linear_bias_act_dropout_fwd_fn( N, K_B = weight.shape # K_B, N = w.shape assert act_type is None or act_type in supported_act_types.keys(), ( - f"Activation Type: {act_type} is not supported by the fused linear layer" - ) + f"Activation Type: {act_type} is not supported by the fused linear layer") assert K_A == K_B, "incompatible dimensions" K = K_A assert input.is_contiguous(), "matrix input must be contiguous" @@ -720,22 +703,17 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): desc_c = TensorDescriptor(c, c.shape, c.stride(), dummy_block) # Create descriptors for optional tensors - desc_act_inp = ( - TensorDescriptor(act_inp, act_inp.shape, act_inp.stride(), dummy_block) if act_inp is not None else act_inp - ) - desc_dropout_mask = ( - TensorDescriptor( - dropout_mask, - dropout_mask.shape, - dropout_mask.stride(), - dummy_block, - ) - if dropout_mask is not None - else dropout_mask - ) + desc_act_inp = (TensorDescriptor(act_inp, act_inp.shape, act_inp.stride(), dummy_block) + if act_inp is not None else act_inp) + desc_dropout_mask = (TensorDescriptor( + dropout_mask, + dropout_mask.shape, + dropout_mask.stride(), + dummy_block, + ) if dropout_mask is not None else dropout_mask) # 2D launch kernel where each block gets its own program. - grid = lambda META: (triton.cdiv(M, META["BLOCK_M"]) * triton.cdiv(N, META["BLOCK_N"]),) + grid = lambda META: (triton.cdiv(M, META["BLOCK_M"]) * triton.cdiv(N, META["BLOCK_N"]), ) matmul_bias_activation_dropout_fwd[grid]( desc_input, desc_weight, @@ -769,6 +747,7 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): class _LinearBiasActDropout(torch.autograd.Function): + @staticmethod def forward(ctx, a, w, bias, act_type, is_grad_enabled, dropout_seed, dropout_prob): if a.dim() > 2: @@ -776,9 +755,8 @@ def forward(ctx, a, w, bias, act_type, is_grad_enabled, dropout_seed, dropout_pr inp = a.view(-1, inp_shape[-1]) else: inp = a - out, act_inp, dropout_mask = _linear_bias_act_dropout_fwd_fn( - inp, w, bias, act_type, is_grad_enabled, dropout_seed, dropout_prob - ) + out, act_inp, dropout_mask = _linear_bias_act_dropout_fwd_fn(inp, w, bias, act_type, is_grad_enabled, + dropout_seed, dropout_prob) if a.dim() > 2: out = out.view(*inp_shape[:-1], out.shape[-1]) if is_grad_enabled: diff --git a/python/tutorials/tileir/tilegym/ops/triton/matmul.py b/python/tutorials/tileir/tilegym/ops/triton/matmul.py index e304a8d595..3e05f8d0e3 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/matmul.py +++ b/python/tutorials/tileir/tilegym/ops/triton/matmul.py @@ -24,6 +24,7 @@ def _disable_autotune(): return os.getenv("TILEGYM_DISABLE_AUTOTUNE", "0") == "1" + # Capability Probe @@ -72,54 +73,39 @@ def _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M, NUM_SMS): def _get_cuda_autotune_config(pre_hook=None): if get_available_triton_backend() == "nvt": config = [ - triton.Config( - dict(BLOCK_SIZE_M=32, BLOCK_SIZE_N=32, BLOCK_SIZE_K=64, GROUP_SIZE_M=8, occupancy=2), num_stages=5 - ), - triton.Config( - dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 - ), - triton.Config( - dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 - ), - triton.Config( - dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=256, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=4 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=64, GROUP_SIZE_M=8, occupancy=2), num_stages=5 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), num_stages=5 - ), + triton.Config(dict(BLOCK_SIZE_M=32, BLOCK_SIZE_N=32, BLOCK_SIZE_K=64, GROUP_SIZE_M=8, occupancy=2), + num_stages=5), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), + num_stages=5), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), + num_stages=5), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=256, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), + num_stages=4), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), + num_stages=5), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=64, GROUP_SIZE_M=8, occupancy=2), + num_stages=5), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8, occupancy=2), + num_stages=5), ] else: config = [ - triton.Config( - dict(BLOCK_SIZE_M=32, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=5, num_warps=2 - ), - triton.Config( - dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=5, num_warps=2 - ), - triton.Config( - dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 - ), - triton.Config( - dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=256, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, num_warps=4 - ), - triton.Config( - dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=256, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), num_stages=3, num_warps=8 - ), + triton.Config(dict(BLOCK_SIZE_M=32, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=5, + num_warps=2), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=5, + num_warps=2), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, + num_warps=4), + triton.Config(dict(BLOCK_SIZE_M=64, BLOCK_SIZE_N=256, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, + num_warps=4), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=32, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, + num_warps=4), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=64, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, + num_warps=4), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=128, BLOCK_SIZE_K=32, GROUP_SIZE_M=8), num_stages=4, + num_warps=4), + triton.Config(dict(BLOCK_SIZE_M=128, BLOCK_SIZE_N=256, BLOCK_SIZE_K=64, GROUP_SIZE_M=8), num_stages=3, + num_warps=8), ] return config @@ -285,10 +271,7 @@ def _kw(c, name, default): bk_cap = max(min_bk, 2 * K) pruned = [ - c - for c in configs - if _kw(c, "BLOCK_SIZE_M", 0) <= bm_cap - and _kw(c, "BLOCK_SIZE_N", 0) <= bn_cap + c for c in configs if _kw(c, "BLOCK_SIZE_M", 0) <= bm_cap and _kw(c, "BLOCK_SIZE_N", 0) <= bn_cap and _kw(c, "BLOCK_SIZE_K", 0) <= bk_cap ] return pruned or configs @@ -322,14 +305,12 @@ def _early_config_prune(configs, named_args, **kwargs): block_k_sizes = [32] num_ctas = [1] num_warps = [8] - tune_space = [ - (bm, bn, bk, nc, nw) - for bm in block_m_sizes - for bn in block_n_sizes - for bk in block_k_sizes - for nc in num_ctas - for nw in num_warps - ] + tune_space = [(bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps] else: # For GPU capability < 9.0, only NUM_CTAS=1 is supported tune_space = [ @@ -359,14 +340,12 @@ def _early_config_prune(configs, named_args, **kwargs): block_k_sizes = [64] num_ctas = [1] num_warps = [8] - tune_space = [ - (bm, bn, bk, nc, nw) - for bm in block_m_sizes - for bn in block_n_sizes - for bk in block_k_sizes - for nc in num_ctas - for nw in num_warps - ] + tune_space = [(bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps] else: # SM80 (A100): restrict to safe tiles (acc_regs = BM*BN/(8*32) <= 255). tune_space = [ @@ -397,14 +376,12 @@ def _early_config_prune(configs, named_args, **kwargs): block_k_sizes = [128] num_ctas = [1] num_warps = [8] - tune_space = [ - (bm, bn, bk, nc, nw) - for bm in block_m_sizes - for bn in block_n_sizes - for bk in block_k_sizes - for nc in num_ctas - for nw in num_warps - ] + tune_space = [(bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps] else: # For GPU capability < 9.0, only NUM_CTAS=1 is supported tune_space = [ @@ -431,11 +408,11 @@ def _early_config_prune(configs, named_args, **kwargs): NUM_WARPS = config.num_warps # Support both 4-tuples and 5-tuples (with num_warps) if (BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, NUM_CTAS) in tune_space or ( - BLOCK_SIZE_M, - BLOCK_SIZE_N, - BLOCK_SIZE_K, - NUM_CTAS, - NUM_WARPS, + BLOCK_SIZE_M, + BLOCK_SIZE_N, + BLOCK_SIZE_K, + NUM_CTAS, + NUM_WARPS, ) in tune_space: pruned_configs.append(config) return _prune_configs_by_shape(pruned_configs, named_args) @@ -518,7 +495,7 @@ def _matmul_tma_persistent_get_configs(pre_hook=None): for subtile in [True, False] ] else: - # For dev and debug + # For dev and debug if _disable_autotune(): return [ triton.Config( @@ -587,14 +564,12 @@ def _early_config_prune_for_persistent(configs, named_args, **kwargs): block_k_sizes = [32] num_ctas = [1] num_warps = [8] - tune_space = [ - (bm, bn, bk, nc, nw) - for bm in block_m_sizes - for bn in block_n_sizes - for bk in block_k_sizes - for nc in num_ctas - for nw in num_warps - ] + tune_space = [(bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps] else: # For GPU capability < 9.0, only NUM_CTAS=1 is supported tune_space = [ @@ -620,14 +595,12 @@ def _early_config_prune_for_persistent(configs, named_args, **kwargs): block_k_sizes = [64] num_ctas = [1] num_warps = [8] - tune_space = [ - (bm, bn, bk, nc, nw) - for bm in block_m_sizes - for bn in block_n_sizes - for bk in block_k_sizes - for nc in num_ctas - for nw in num_warps - ] + tune_space = [(bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps] else: # SM80 (A100): restrict to safe tiles (acc_regs = BM*BN/(8*32) <= 255). tune_space = [ @@ -655,14 +628,12 @@ def _early_config_prune_for_persistent(configs, named_args, **kwargs): block_k_sizes = [128] num_ctas = [1] num_warps = [8] - tune_space = [ - (bm, bn, bk, nc, nw) - for bm in block_m_sizes - for bn in block_n_sizes - for bk in block_k_sizes - for nc in num_ctas - for nw in num_warps - ] + tune_space = [(bm, bn, bk, nc, nw) + for bm in block_m_sizes + for bn in block_n_sizes + for bk in block_k_sizes + for nc in num_ctas + for nw in num_warps] else: # For GPU capability < 9.0, only NUM_CTAS=1 is supported tune_space = [ @@ -687,11 +658,11 @@ def _early_config_prune_for_persistent(configs, named_args, **kwargs): NUM_WARPS = config.num_warps # Support both 4-tuples and 5-tuples (with num_warps) if (BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K, NUM_CTAS) in tune_space or ( - BLOCK_SIZE_M, - BLOCK_SIZE_N, - BLOCK_SIZE_K, - NUM_CTAS, - NUM_WARPS, + BLOCK_SIZE_M, + BLOCK_SIZE_N, + BLOCK_SIZE_K, + NUM_CTAS, + NUM_WARPS, ) in tune_space: pruned_configs.append(config) return _prune_configs_by_shape(pruned_configs, named_args) @@ -708,15 +679,25 @@ def _early_config_prune_for_persistent(configs, named_args, **kwargs): ) @triton.jit def _matmul_kernel_pure_ptr( - a_ptr, b_ptr, c_ptr, - M, N, K, - stride_am, stride_ak, - stride_bk, stride_bn, - stride_cm, stride_cn, - BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_N: tl.constexpr, + a_ptr, + b_ptr, + c_ptr, + M, + N, + K, + stride_am, + stride_ak, + stride_bk, + stride_bn, + stride_cm, + stride_cn, + BLOCK_SIZE_M: tl.constexpr, + BLOCK_SIZE_N: tl.constexpr, BLOCK_SIZE_K: tl.constexpr, - GROUP_SIZE_M: tl.constexpr, ACTIVATION: tl.constexpr, - transpose_a: tl.constexpr, transpose_b: tl.constexpr, + GROUP_SIZE_M: tl.constexpr, + ACTIVATION: tl.constexpr, + transpose_a: tl.constexpr, + transpose_b: tl.constexpr, ): # fmt: skip # Initialize offsets pid_m, pid_n = _swizzle_2d(M, N, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M) @@ -944,7 +925,7 @@ def matmul_fn( # allocates output c = torch.zeros((M, N), device=a.device, dtype=a.dtype) # 1D launch kernel where each block gets its own program. - grid = lambda META: (triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),) + grid = lambda META: (triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]), ) _matmul_kernel_pure_ptr[grid]( a, @@ -967,6 +948,7 @@ def matmul_fn( class MatmulFunction(torch.autograd.Function): + @staticmethod def forward(ctx, a, b, transpose_a=False, transpose_b=False): c = matmul_fn(a, b, transpose_a=transpose_a, transpose_b=transpose_b) @@ -1046,22 +1028,18 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): desc_b = b desc_c = c raise NotImplementedError( - "Only support host descriptor for now, need to modify kernel code to support non-host descriptor" - ) + "Only support host descriptor for now, need to modify kernel code to support non-host descriptor") # Grid calculation if static_persistent: NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count - grid = lambda META: ( - min( - NUM_SMS // META["num_ctas"], - triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]), - ) - * META["occupancy"], - ) + grid = lambda META: (min( + NUM_SMS // META["num_ctas"], + triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]), + ) * META["occupancy"], ) kernel = _static_persistent_matmul_kernel else: - grid = lambda META: (triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]),) + grid = lambda META: (triton.cdiv(M, META["BLOCK_SIZE_M"]) * triton.cdiv(N, META["BLOCK_SIZE_N"]), ) kernel = _matmul_kernel logger.debug( @@ -1083,8 +1061,6 @@ def alloc_fn(size: int, alignment: int, stream: Optional[int]): return MatmulFunction.apply(a, b, trans_a, trans_b) - # Backend Registration & Perf Markers mark_perf_ready("matmul", "nvt") - diff --git a/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py b/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py index f28a416125..5521df010d 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py +++ b/python/tutorials/tileir/tilegym/ops/triton/matmul_perf_model.py @@ -27,11 +27,8 @@ def _get_tensorcore_tflops(device, num_ctas, num_warps, dtype): """return compute throughput in TOPS""" total_warps = num_ctas * min(num_warps, 4) num_subcores = driver.active.utils.get_device_properties(device)["multiprocessor_count"] * 4 # on recent GPUs - tflops = ( - min(num_subcores, total_warps) - / num_subcores - * get_max_tensorcore_tflops(dtype, _get_clock_rate_in_khz(), device) - ) + tflops = (min(num_subcores, total_warps) / num_subcores * + get_max_tensorcore_tflops(dtype, _get_clock_rate_in_khz(), device)) return tflops @@ -39,9 +36,8 @@ def _get_simd_tflops(device, num_ctas, num_warps, dtype): """return compute throughput in TOPS""" total_warps = num_ctas * min(num_warps, 4) num_subcores = driver.active.utils.get_device_properties(device)["multiprocessor_count"] * 4 # on recent GPUs - tflops = ( - min(num_subcores, total_warps) / num_subcores * get_max_simd_tflops(dtype, _get_clock_rate_in_khz(), device) - ) + tflops = (min(num_subcores, total_warps) / num_subcores * + get_max_simd_tflops(dtype, _get_clock_rate_in_khz(), device)) return tflops @@ -126,11 +122,9 @@ def estimate_matmul_time( store_ms += zero_ms total_time_ms = max(compute_ms, load_ms) + store_ms - logger.debug( - f"Total time: {total_time_ms}ms, compute time: {compute_ms}ms, " - f"loading time: {load_ms}ms, store time: {store_ms}ms, " - f"Activate CTAs: {active_cta_ratio * 100}%" - ) + logger.debug(f"Total time: {total_time_ms}ms, compute time: {compute_ms}ms, " + f"loading time: {load_ms}ms, store time: {store_ms}ms, " + f"Activate CTAs: {active_cta_ratio * 100}%") return total_time_ms @@ -206,11 +200,8 @@ def early_config_prune(configs, named_args, **kwargs): nearest = heapq.nsmallest( 2, v, - key=lambda x: ( - 10 + abs(x[1] - optimal_num_stages) - if (x[1] - optimal_num_stages) < 0 - else x[1] - optimal_num_stages - ), + key=lambda x: (10 + abs(x[1] - optimal_num_stages) + if (x[1] - optimal_num_stages) < 0 else x[1] - optimal_num_stages), ) for n in nearest: diff --git a/python/tutorials/tileir/tilegym/ops/triton/mla.py b/python/tutorials/tileir/tilegym/ops/triton/mla.py index 0dd7936e78..9a7162b9bd 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/mla.py +++ b/python/tutorials/tileir/tilegym/ops/triton/mla.py @@ -216,8 +216,7 @@ def keep(conf): dict(BLOCK_M=BM, BLOCK_N=BN, occupancy=occ, **default_config), num_stages=ns, pre_hook=_hook, - ) - ) + )) # 2CTA configs (SM100+ only) - dot-related kernel benefits from 2CTA MMA for BM, BN in [(256, 128), (128, 128), (256, 64), (256, 256), (128, 64)]: for ns in [3, 4, 5, 6]: @@ -227,8 +226,7 @@ def keep(conf): num_stages=ns, num_ctas=2, pre_hook=_hook, - ) - ) + )) # 2CTA configs with warp_specialize=True around the winning shape for BM, BN in [(256, 128), (128, 128), (256, 256)]: for ns in [4, 5, 6]: @@ -238,8 +236,7 @@ def keep(conf): num_stages=ns, num_ctas=2, pre_hook=_hook, - ) - ) + )) return list(filter(keep, sm100_configs)) # Full tuning space for oait @@ -262,11 +259,9 @@ def keep(conf): configs=_get_configs(), key=["S_qo", "S_kv", "STAGE", "QUERY_GROUP_SIZE", "dtype"], ) -@triton.heuristics( - { - "EVEN_K": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, - } -) +@triton.heuristics({ + "EVEN_K": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, +}) @triton.jit def _prefill_mla( Q, @@ -474,6 +469,7 @@ def _prefill_mla( class _attention(torch.autograd.Function): + @staticmethod def forward(ctx, q, qpe, k, kpe, v, sm_scale, IS_CAUSAL): # Setup stride and shape @@ -609,6 +605,7 @@ def backward(ctx, do): class _Attention: + def __init__(self, IS_CAUSAL): self.IS_CAUSAL = IS_CAUSAL @@ -635,8 +632,6 @@ def triton_mla(q, k, v, qpe, kpe, is_causal, scaling, **kwargs): return o - # Backend Registration & Perf Markers mark_perf_ready("mla", "nvt") - diff --git a/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py b/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py index 828ab19ae9..58014da21a 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py +++ b/python/tutorials/tileir/tilegym/ops/triton/mla_decoding.py @@ -39,20 +39,16 @@ def _get_mla_decoding_configs(): @triton.autotune( - configs=_get_mla_decoding_configs() - if get_available_triton_backend() == "nvt" - else [ + configs=_get_mla_decoding_configs() if get_available_triton_backend() == "nvt" else [ # OAIT meet CUDA error for BLOCK_H = 64, BLOCK_N = 64 # use BLOCK_H = 32, BLOCK_N = 32 instead triton.Config(dict(BLOCK_H=32, BLOCK_N=32), num_ctas=1), ], key=["BLOCK_D", "BLOCK_KPE", "S_kv", "EVEN_N"], ) -@triton.heuristics( - { - "EVEN_N": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, - } -) +@triton.heuristics({ + "EVEN_N": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, +}) @triton.jit def _naive_absorb_mla( Q, @@ -192,9 +188,7 @@ def _naive_absorb_mla( configs=[ triton.Config(dict(BLOCK_H=16, BLOCK_N=128), num_ctas=1), triton.Config(dict(BLOCK_H=32, BLOCK_N=128), num_ctas=1), - ] - if get_available_triton_backend() == "nvt" - else [ + ] if get_available_triton_backend() == "nvt" else [ # OAIT meet CUDA error when num_stages = 3 # use num_stages = 1 instead triton.Config(dict(BLOCK_H=16, BLOCK_N=128), num_ctas=1, num_stages=1), @@ -202,11 +196,9 @@ def _naive_absorb_mla( ], key=["BLOCK_D", "BLOCK_KPE", "S_kv", "EVEN_N"], ) -@triton.heuristics( - { - "EVEN_N": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, - } -) +@triton.heuristics({ + "EVEN_N": lambda args: args["S_kv"] % args["BLOCK_N"] == 0, +}) @triton.jit def _naive_absorb_mla_transpose( Q, @@ -357,6 +349,7 @@ def _naive_absorb_mla_transpose( class _mla_decoding(torch.autograd.Function): + @staticmethod def forward( ctx, @@ -419,6 +412,7 @@ def backward(ctx, do): class MLADecoding: + def __init__(self, transpose): self.transpose = transpose @@ -449,8 +443,6 @@ def mla_decoding( return _mla_decoding.apply(q, qpe, kv, kpe, sm_scale, transpose) - # Backend Registration & Perf Markers mark_perf_ready("mla_decoding", "nvt") - diff --git a/python/tutorials/tileir/tilegym/ops/triton/rope.py b/python/tutorials/tileir/tilegym/ops/triton/rope.py index 69d040a502..438806d62d 100644 --- a/python/tutorials/tileir/tilegym/ops/triton/rope.py +++ b/python/tutorials/tileir/tilegym/ops/triton/rope.py @@ -229,12 +229,10 @@ def _rope_kernel( # Left half of the rotary portion [0 : rope_hd//2] first_half_q_offsets = tl.arange(0, pad_n_qh)[:, None] * hd + tl.arange(0, effective_pad_rope_hd // 2)[None, :] first_half_k_offsets = tl.arange(0, pad_n_kh)[:, None] * hd + tl.arange(0, effective_pad_rope_hd // 2)[None, :] - first_q_mask = (tl.arange(0, pad_n_qh)[:, None] < n_qh) & ( - tl.arange(0, effective_pad_rope_hd // 2)[None, :] < effective_rope_hd // 2 - ) - first_k_mask = (tl.arange(0, pad_n_kh)[:, None] < n_kh) & ( - tl.arange(0, effective_pad_rope_hd // 2)[None, :] < effective_rope_hd // 2 - ) + first_q_mask = (tl.arange(0, pad_n_qh)[:, None] < n_qh) & (tl.arange(0, effective_pad_rope_hd // 2)[None, :] + < effective_rope_hd // 2) + first_k_mask = (tl.arange(0, pad_n_kh)[:, None] < n_kh) & (tl.arange(0, effective_pad_rope_hd // 2)[None, :] + < effective_rope_hd // 2) q_tile_1 = tl.load(q + first_half_q_offsets, mask=first_q_mask, other=0).to(tl.float32) k_tile_1 = tl.load(k + first_half_k_offsets, mask=first_k_mask, other=0).to(tl.float32) @@ -316,8 +314,7 @@ def _rope_forward_tma(q, k, cos, sin): q = q.reshape(batch_size, n_q_head, seq_len, 2, head_dim // 2) k = k.reshape(batch_size, n_kv_head, seq_len, 2, head_dim // 2) assert cos.shape[-1] == head_dim // 2 or cos.shape[-1] == head_dim, ( - f"cos.shape[-1]: {cos.shape[-1]}, head_dim: {head_dim}" - ) + f"cos.shape[-1]: {cos.shape[-1]}, head_dim: {head_dim}") original_cos_shape = cos.shape original_sin_shape = sin.shape # Here use contiguous to avoid the TensorDescriptor error: "strides must be 16-byte aligned" @@ -370,7 +367,7 @@ def _rope_forward_tma(q, k, cos, sin): desc_cos = cos desc_sin = sin - _triton_rope_kernel_tma[(n_row,)]( + _triton_rope_kernel_tma[(n_row, )]( desc_q, desc_k, desc_cos, @@ -469,7 +466,7 @@ def _rope_forward(q, k, cos, sin, rope_dim=None): sin = sin.contiguous() cos_batch_size = cos.shape[0] - _rope_kernel[(n_row,)]( + _rope_kernel[(n_row, )]( q, q.stride(1), k, @@ -522,7 +519,7 @@ def _rope_backward(dq, dk, cos, sin, rope_dim=None): dk = dk.contiguous() # Backward is similar to forward except swapping few ops - _rope_kernel[(n_row,)]( + _rope_kernel[(n_row, )]( dq, dq.stride(1), dk, @@ -606,13 +603,13 @@ def apply_rope_base(q, k, cos, sin, position_ids=None, unsqueeze_dim=1, use_tma= rope_dim = int(head_dim * partial_rotary_factor) assert cos.shape[-1] == rope_dim, ( f"cos last dim ({cos.shape[-1]}) must equal int(head_dim * partial_rotary_factor) " - f"= int({head_dim} * {partial_rotary_factor}) = {rope_dim}" - ) + f"= int({head_dim} * {partial_rotary_factor}) = {rope_dim}") return _TritonRopeFunction.apply(q, k, cos, sin, position_ids, unsqueeze_dim, use_tma, rope_dim) @register_impl("get_apply_rope_func", backend="triton") def get_apply_rope_func(model="llama"): + def is_use_tma(s): return s > 128 @@ -654,8 +651,6 @@ def wrapper(q, k, freqs_cis): raise ValueError(f"Unsupported model: {model}") - # Backend Registration & Perf Markers mark_perf_ready("apply_rope_base", "nvt") - diff --git a/setup.py b/setup.py index 98ea3a7bf2..c1af0830dd 100644 --- a/setup.py +++ b/setup.py @@ -645,15 +645,15 @@ def download_and_copy_dependencies(): if helper.flagtree_backend: if helper.flagtree_backend in ("aipu", "tsingmicro", "enflame", "rpu", "thrive"): + default_backends = helper.configs.non_tileir_default_backends() backends = [ - *BackendInstaller.copy(helper.configs.default_backends + tuple(helper.configs.extend_backends)), + *BackendInstaller.copy(default_backends + tuple(helper.configs.extend_backends)), *BackendInstaller.copy_externals(), ] else: backends = [*BackendInstaller.copy(helper.configs.extend_backends), *BackendInstaller.copy_externals()] else: - print(helper.configs.default_backends) - backends = [*BackendInstaller.copy(["nvidia", "amd", "tileir"]), *BackendInstaller.copy_externals()] + backends = [*BackendInstaller.copy(helper.configs.default_backends), *BackendInstaller.copy_externals()] #backends = [*BackendInstaller.copy(["nvidia", "amd"]), *BackendInstaller.copy_externals()] diff --git a/third_party/tileir/PerformanceTuningTips.md b/third_party/tileir/PerformanceTuningTips.md index 7b735e125e..591998757a 100644 --- a/third_party/tileir/PerformanceTuningTips.md +++ b/third_party/tileir/PerformanceTuningTips.md @@ -14,7 +14,7 @@ The **occupancy** hint accepts an integer N from 1 to 32, indicating that the pr Unlike the Triton PTX backend, the CUDA Tile IR Backend disables approx and ftz by default. Setting `TILEIR_ENABLE_APPROX=1` and `TILEIR_ENABLE_FTZ=1` can provide performance improvements in certain workloads (with precision degradation within acceptable ranges), such as **`attention`** and its variant kernels. -Note that the TileIR compiler (`tileiras`) shipping in CUDA 13.1 does not automatically optimize `exp.approx -> ex2 + mulf`. For performance and precision parity with the Triton PTX backend, please explicitly rewrite `expOp` to use `ex2 + mulf` instead. +Note that the TileIR compiler (`tileiras`) shipping in CUDA 13.1 does not automatically optimize `exp.approx -> ex2 + mulf`. For performance and precision parity with the Triton PTX backend, please explicitly rewrite `expOp` to use `ex2 + mulf` instead. #### opt-level @@ -68,11 +68,11 @@ sudo nvidia-smi -i 0 -pm 1; sudo nvidia-smi -i 0 -pl 1000; sudo nvidia-smi -i 0 ![Fused Attention Backward Benchmark](./fused-attention-bwd.png) -### Persistent Matmul (09-persistent-matmul.py) +### Persistent Matmul (09-persistent-matmul.py) > TFLOPS by Proton -#### NVIDIA PTX backend +#### NVIDIA PTX backend | Kernel Name | K=512 | K=1024 | K=1536 | K=2048 | K=2560 | K=3072 | K=3584 | K=4096 | K=4608 | K=5120 | K=5632 | K=6144 | K=6656 | K=7168 | K=7680 | K=8192 | |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| diff --git a/third_party/tileir/README.md b/third_party/tileir/README.md index 3773f598fa..12ee5cb25d 100644 --- a/third_party/tileir/README.md +++ b/third_party/tileir/README.md @@ -41,21 +41,53 @@ build dependencies are already present: python3 -m pip install -e . --no-build-isolation --no-deps ``` +## Backend Discovery + +A normal installation registers the selected backends through the +`triton.backends` entry-point group. No discovery environment variable is +required. + +The existing FlagTree option below bypasses entry points and scans the installed +`triton/backends/` directory instead: + +```bash +TRITON_BACKENDS_IN_TREE=1 +``` + +This option only changes where Triton discovers already-present backends. It +does not build, install, enable, or route TileIR and is normally unnecessary. + ## Runtime Routing -TileIR routing is opt-in: +Opt in to per-kernel TileIR routing with: ```bash FLAGTREE_USE_TILEIR=1 -TRITON_BACKENDS_IN_TREE=1 ``` -For debugging routing decisions: +The routing result is: + +```text +non-CUDA target -> unchanged +CUDA kernel with no TLE -> TileIR +CUDA kernel using only supported TLE -> TileIR +CUDA kernel using other/unknown TLE -> native NVIDIA +``` + +For routing diagnostics: ```bash FLAGTREE_TILEIR_VERBOSE=1 ``` +On NVIDIA hardware, `CudaDriver` remains the global active driver and produces +an initial `cuda` target. Routing may replace it with a `tileir` target; that +target selects `TileIRBackend` for JIT compilation and a lazily created +`TileIRDriver` for binary loading and launch. + +The installed backend is named `nvidia`, while its `GPUTarget.backend` label is +`cuda`. Routing operates on target labels and must not treat `nvidia` as one. + ## FlagTree Compatibility - Target FlagTree/Triton base: `triton_v3.6.x` / Triton 3.6.0 @@ -70,12 +102,27 @@ FLAGTREE_TILEIR_VERBOSE=1 - Build integration: use the pinned cuda-tile submodule instead of the upstream build-time clone. -- Backend integration: adapt TileIR compiler and driver hooks to FlagTree's - per-kernel routing and runtime expectations. +- Backend integration: + - `backend/router.py` is FlagTree-only and owns the AST-based per-kernel + selection between TileIR and native NVIDIA. + - `backend/compiler.py` implements FlagTree's generic backend hooks for target + routing, initial AST lowering, and `tl.ext` registration. It also records + zero dynamic shared memory in TileIR kernel metadata. + - `backend/driver.py` keeps `TileIRDriver` out of global active-driver + discovery, constructs it lazily for TileIR targets, and normalizes + `load_binary` to FlagTree's standard driver ABI. - TLE/TKO support: expose TileIR tensor-view and load/store-view token helpers - needed by FlagTree TLE kernels. + in `backend/extend_core.py`, `backend/extend_semantic.py`, and + `triton_tileir.cc` for FlagTree TLE kernels. +- Frontend compatibility: `backend/code_generator.py` and + `backend/compiler.py` adapt the upstream TileIR frontend to FlagTree's Triton + 3.6 AST and MLIR APIs. - Lowering compatibility: add `math.erf` lowering for current FlagTree coverage. +TileIR-specific routing and frontend behavior must remain in this vendor +directory. Changes under `python/triton` are limited to backend-neutral hooks so +that non-TileIR backends do not import or depend on TileIR implementation code. + ## Update Checklist 1. Sync `third_party/tileir` from the chosen Triton-to-Tile-IR commit. diff --git a/third_party/tileir/backend/code_generator.py b/third_party/tileir/backend/code_generator.py index c5f4647f42..4f662fff55 100644 --- a/third_party/tileir/backend/code_generator.py +++ b/third_party/tileir/backend/code_generator.py @@ -36,6 +36,7 @@ from triton.backends.tileir.conf import TileIREnvConf from triton.backends.tileir.extend_semantic import CudaTileSemantic + def mangle_fn(name, arg_tys, caller_context): # doesn't mangle ret type, which must be a function of arg tys mangled_args = '_'.join([tileir_mangle_ty(ty) for ty in arg_tys]) @@ -47,6 +48,7 @@ def mangle_fn(name, arg_tys, caller_context): ret += caller_context.mangle() return ret + def tileir_mangle_ty(ty): return ty.mangle() @@ -54,9 +56,7 @@ def tileir_mangle_ty(ty): def tileir_mangle_fn(name, arg_tys, constants): # doesn't mangle ret type, which must be a function of arg tys mangled_arg_names = "_".join([tileir_mangle_ty(ty) for ty in arg_tys]) - mangled_constants = "_".join( - [f"{i}c{repr(constants[i])}" for i in sorted(constants)] - ) + mangled_constants = "_".join([f"{i}c{repr(constants[i])}" for i in sorted(constants)]) mangled_constants = mangled_constants.replace(".", "_d_") mangled_constants = mangled_constants.replace("'", "_sq_") # [ and ] are not allowed in LLVM identifiers @@ -210,7 +210,10 @@ def apply_constexpr_types(argument, indices, value): signature = src.signature tileir_additonal_suffix = "" - proxy = namedtuple("SpecializationProxy", ["constants", "signature",])(constants, signature) + proxy = namedtuple("SpecializationProxy", [ + "constants", + "signature", + ])(constants, signature) generator = TileIRCodeGenerator( context, prototype, diff --git a/third_party/tileir/backend/compiler.py b/third_party/tileir/backend/compiler.py index ad6f09951a..e6bb7427e2 100644 --- a/third_party/tileir/backend/compiler.py +++ b/third_party/tileir/backend/compiler.py @@ -119,9 +119,7 @@ def enable_approx(self): return TileIREnvConf.enable_approx() def __post_init__(self): - assert self.num_warps > 0 and (self.num_warps & (self.num_warps - 1)) == 0, ( - "num_warps must be a power of 2" - ) + assert self.num_warps > 0 and (self.num_warps & (self.num_warps - 1)) == 0, ("num_warps must be a power of 2") def hash(self): hash_dict = dict(self.__dict__) @@ -138,6 +136,28 @@ def get_tileir_version(): class TileIRBackend(BaseBackend): + + @classmethod + def route_target(cls, target, jit_fn): + from triton.backends.tileir.router import pick_target_for_kernel + + candidate = pick_target_for_kernel(target, jit_fn) + return candidate if candidate != target else None + + @classmethod + def get_language_extension(cls): + from triton.backends.tileir import extend_core + + return extend_core + + def make_ir(self, src, options, codegen_fns, module_map, context): + if hasattr(src, "fn"): + from triton.backends.tileir.code_generator import ast_to_ttir + + return ast_to_ttir(src.fn, src, context=context, options=options, codegen_fns=codegen_fns, + module_map=module_map) + return super().make_ir(src, options, codegen_fns, module_map, context) + def get_module_map(self): from triton.language.extra.cuda import libdevice @@ -160,14 +180,7 @@ def __init__(self, target: GPUTarget) -> None: def parse_options(self, opts) -> Any: args = {"arch": os.getenv("TRITON_OVERRIDE_ARCH", f"sm{self.target.arch}")} - args.update( - { - k: opts[k] - for k in TileIROptions.__dataclass_fields__.keys() - if k in opts - if opts[k] is not None - } - ) + args.update({k: opts[k] for k in TileIROptions.__dataclass_fields__.keys() if k in opts if opts[k] is not None}) capability = int(self._parse_arch(args["arch"])) if "supported_fp8_dtypes" not in args: supported_fp8_dtypes = set(TileIROptions.supported_fp8_dtypes) @@ -179,7 +192,7 @@ def parse_options(self, opts) -> Any: if "deprecated_fp8_dot_operand_dtypes" not in args: if capability >= 90: - args["deprecated_fp8_dot_operand_dtypes"] = ("fp8e4b15",) + args["deprecated_fp8_dot_operand_dtypes"] = ("fp8e4b15", ) if "enable_fp_fusion" not in args: args["enable_fp_fusion"] = os.getenv("TRITON_DEFAULT_FP_FUSION", "1") == "1" @@ -198,9 +211,8 @@ def get_codegen_implementation(self, options): capability = int(self._parse_arch(options.arch)) codegen_fns = { - "convert_custom_types": cuda.convert_custom_float8_sm80 - if capability >= 80 - else cuda.convert_custom_float8_sm70, + "convert_custom_types": + cuda.convert_custom_float8_sm80 if capability >= 80 else cuda.convert_custom_float8_sm70, "min_dot_size": lambda lhs, rhs: (1, 1, 1), } return codegen_fns @@ -226,8 +238,8 @@ def call_tileiras(mod, metadata, opt: TileIROptions, capability): # Use temp file for cubin output to avoid race conditions. with ( - tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".log") as flog, - tempfile.NamedTemporaryFile(delete=False, suffix=".cubin") as fbin, + tempfile.NamedTemporaryFile(delete=False, mode="w", suffix=".log") as flog, + tempfile.NamedTemporaryFile(delete=False, suffix=".cubin") as fbin, ): tileiras_cmd.append(bytecode_file) tileiras_cmd.append(f"-o") @@ -249,20 +261,16 @@ def call_tileiras(mod, metadata, opt: TileIROptions, capability): raise OutOfResources(used_smem, max_smem, "shared memory") if "allocated tmem out of resource" in log: # "allocated tmem out of resource: vs " - pattern = ( - r"allocated tmem out of resource:\s*([0-9]+)\s*vs\s*([0-9]+)" - ) + pattern = (r"allocated tmem out of resource:\s*([0-9]+)\s*vs\s*([0-9]+)") match = re.search(pattern, log) if match: used_tmem = int(match.group(1)) max_tmem = int(match.group(2)) raise OutOfResources(used_tmem, max_tmem, "tensor memory") error = f"`tileiras` failed with error code {e.returncode}" - raise RuntimeError( - f"{error}\n" - f"`tileiras` stderr:\n{log}\n" - f"Repro command: {' '.join(str(item) for item in tileiras_cmd)}\n" - ) + raise RuntimeError(f"{error}\n" + f"`tileiras` stderr:\n{log}\n" + f"Repro command: {' '.join(str(item) for item in tileiras_cmd)}\n") with open(fbin.name, "rb") as f: cubin = f.read() finally: @@ -324,9 +332,7 @@ def make_tileir(mod, metadata, opt: TileIROptions, capability): safe_name = re.sub(r"[^a-zA-Z0-9_]+", "_", metadata.get("name", "unknown")) with open(f"/tmp/flagtree_tileir_failed_{safe_name}.mlir", "w") as f: f.write(str(mod)) - raise RuntimeError( - "Triton ttir to tileir ir failed. Some ttir ops cannot be converted to tileir." - ) + raise RuntimeError("Triton ttir to tileir ir failed. Some ttir ops cannot be converted to tileir.") pattern = r"entry @([a-zA-Z0-9_]*)\(" match = re.findall(pattern, mod.__str__()) @@ -336,20 +342,17 @@ def make_tileir(mod, metadata, opt: TileIROptions, capability): @staticmethod def make_cubin(mod, metadata, opt: TileIROptions, capability): + # TileIR kernels use statically allocated shared memory and request no + # dynamic shared memory from the CUDA launcher. + metadata["shared"] = 0 return TileIRBackend.call_tileiras(mod, metadata, opt, capability) def add_stages(self, stages, options, language): assert language == Language.TRITON, "Only TRITON language is supported for now" capability = int(self._parse_arch(options.arch)) - stages["ttir"] = lambda src, metadata: self.make_ttir( - src, metadata, options, capability - ) - stages["tileir"] = lambda src, metadata: self.make_tileir( - src, metadata, options, capability - ) - stages["cubin"] = lambda src, metadata: self.make_cubin( - src, metadata, options, capability - ) + stages["ttir"] = lambda src, metadata: self.make_ttir(src, metadata, options, capability) + stages["tileir"] = lambda src, metadata: self.make_tileir(src, metadata, options, capability) + stages["cubin"] = lambda src, metadata: self.make_cubin(src, metadata, options, capability) @functools.lru_cache() def hash(self): diff --git a/third_party/tileir/backend/conf.py b/third_party/tileir/backend/conf.py index 9a172e11e6..8ba745af49 100644 --- a/third_party/tileir/backend/conf.py +++ b/third_party/tileir/backend/conf.py @@ -4,6 +4,7 @@ class TileIREnvConf: + @staticmethod def enable_approx(): # Enable approximate calculation, trading off numerical precision for performance gains @@ -46,10 +47,8 @@ def get_tileiras_path(): tileiras_path = which("tileiras") if tileiras_path is None: - raise RuntimeError( - "tileiras not found: TRITON_TILEIRAS_PATH unset, no bundled " - "binary, and not in PATH" - ) + raise RuntimeError("tileiras not found: TRITON_TILEIRAS_PATH unset, no bundled " + "binary, and not in PATH") return tileiras_path @staticmethod diff --git a/third_party/tileir/backend/driver.py b/third_party/tileir/backend/driver.py index 0ab6e9b24f..5261399463 100644 --- a/third_party/tileir/backend/driver.py +++ b/third_party/tileir/backend/driver.py @@ -27,13 +27,13 @@ from triton.backends.tileir.conf import TileIREnvConf from triton.tools.tensor_descriptor import TensorDescriptor - # ------------------------ # Utils # ------------------------ class TileIRUtils(object): + def __new__(cls): if not hasattr(cls, "instance"): cls.instance = super(TileIRUtils, cls).__new__(cls) @@ -60,7 +60,11 @@ def __init__(self): self.init_tileir_function(tile_mod) def init_tileir_function(self, mod): - self.load_binary = mod.load_tileir_binary + self._load_binary = mod.load_tileir_binary + + def load_binary(self, name, kernel, shared, device): + module, function, n_regs, n_spills, _static_smem, n_max_threads = self._load_binary(name, kernel, device) + return module, function, n_regs, n_spills, n_max_threads def init_nvidia_function(self, mod): self.get_device_properties = mod.get_device_properties @@ -72,7 +76,6 @@ def init_nvidia_function(self, mod): # Launcher # ------------------------ - dirname = os.path.dirname(__file__) FLOAT_STORAGE_TYPE = { @@ -90,12 +93,12 @@ def init_nvidia_function(self, mod): "fp64": "pack_fp64", } - _BASE_ARGS_FORMAT = "iiiKKpOOOO" _BASE_ARGS_FORMAT_LEN = len(_BASE_ARGS_FORMAT) def make_launcher(constants, signature): + def _flatten_signature(sig, output): # Flatten tuples if isinstance(sig, tuple): @@ -142,11 +145,7 @@ def format_of(ty): for sig in signature.values(): _flatten_signature(sig, flat_signature) signature = {i: s for i, s in enumerate(flat_signature)} - args_list = ( - ", " + ", ".join(f"&_arg{i}" for i, ty in signature.items()) - if len(signature) > 0 - else "" - ) + args_list = (", " + ", ".join(f"&_arg{i}" for i, ty in signature.items()) if len(signature) > 0 else "") # Record the end of regular arguments; # subsequent arguments are architecture-specific descriptors, such as tensor descriptors for CUDA. arg_decl_list = [] @@ -433,6 +432,7 @@ def make_tensordesc_arg(arg): def wrap_handle_tensordesc(launcher): + def inner(*args): # flagtree: upstream used `args[:9]`, but TileIRLauncher.__call__ unconditionally # inserts `launch_pdl` at position 5 before invoking self.launch — making metadata @@ -452,13 +452,12 @@ def inner(*args): class TileIRLauncher(object): + def __init__(self, src, metadata): - ids = { - "ids_of_const_exprs": src.fn.constexprs if hasattr(src, "fn") else tuple() - } + ids = {"ids_of_const_exprs": src.fn.constexprs if hasattr(src, "fn") else tuple()} constants = src.constants if hasattr(src, "constants") else dict() - arg_idx = lambda x: (src.fn.arg_names.index(x),) if isinstance(x, str) else x + arg_idx = lambda x: (src.fn.arg_names.index(x), ) if isinstance(x, str) else x constants = {arg_idx(idx): value for idx, value in constants.items()} signature = {idx: value for idx, value in src.signature.items()} has_tensordesc = any("tensordesc" in value for value in signature.values()) @@ -491,9 +490,7 @@ def __init__(self, src, metadata): f.write(f"launcher_signature={self.signature}\n") self.constants = constants src = make_launcher(self.constants, self.signature) - mod = compile_module_from_src( - src, "__triton_launcher", library_dirs(), include_dirs, libraries - ) + mod = compile_module_from_src(src, "__triton_launcher", library_dirs(), include_dirs, libraries) if has_tensordesc: self.launch = wrap_handle_tensordesc(mod.launch) else: @@ -508,18 +505,17 @@ def __call__(self, *args, **kwargs): num_launch_args = 9 num_params = len(args) - num_launch_args if num_params < self.ori_signature_len: - extra_args = [ - self.constants[(i,)] for i in range(num_params, self.ori_signature_len) - ] + extra_args = [self.constants[(i, )] for i in range(num_params, self.ori_signature_len)] model_args = args + tuple(extra_args) else: model_args = args - model_args = model_args[:5] + (self.launch_pdl,) + model_args[5:] + model_args = model_args[:5] + (self.launch_pdl, ) + model_args[5:] self.launch(*model_args, **kwargs) class TileIRDriver(GPUDriver): + def __init__(self): self.utils = TileIRUtils() # TODO: make static self.launcher_cls = TileIRLauncher @@ -544,14 +540,10 @@ def get_device_interface(self): @staticmethod def is_active(): - # flagtree: TileIRDriver must NEVER be the global active driver in flagtree. - # flagtree integrates tileir as a per-kernel routing option (see - # CompiledKernel._get_kernel_driver in python/triton/compiler/compiler.py), - # which lazily instantiates TileIRDriver only for kernels whose target.backend - # is "tileir". The triton.runtime.driver._create_driver() mechanism still - # picks exactly one global active driver based on is_active() — that one - # remains CudaDriver, used for both nvidia-compiled and tileir-compiled - # kernels' device/stream queries. + # TileIR is selected per kernel through the backend routing hook. The + # global active driver remains CudaDriver for CUDA device/stream queries; + # Triton's target-driver lookup instantiates TileIRDriver only for a + # TileIR-compiled kernel. # # The upstream triton-to-tile-ir fork returned True when ENABLE_TILE=1 to # make the driver replace CudaDriver wholesale. In flagtree that would @@ -581,9 +573,6 @@ def clear_cache(self, cache): cache.zero_() -# flagtree: the upstream fork eagerly constructs `GlobalTileIRDriver = TileIRDriver()` -# here, but that probes CUDA libraries and builds the launcher C extension at module -# import — which breaks plain `import triton` on AMD-only / CUDA-less systems. flagtree -# does per-kernel driver routing in CompiledKernel._get_kernel_driver (compiler.py), -# lazily instantiating TileIRDriver only when a kernel actually needs the tileir backend, -# so this global singleton is unnecessary and intentionally omitted. +# The upstream fork eagerly constructs `GlobalTileIRDriver = TileIRDriver()` here. +# FlagTree resolves target drivers lazily, avoiding CUDA probing and extension builds +# during a plain `import triton` on AMD-only or CUDA-less systems. diff --git a/third_party/tileir/backend/errors.py b/third_party/tileir/backend/errors.py index bcb7159673..4edac1f567 100644 --- a/third_party/tileir/backend/errors.py +++ b/third_party/tileir/backend/errors.py @@ -2,6 +2,7 @@ class HitFallback(TritonError): + def __init__(self, required, name): self.required = required self.name = name diff --git a/third_party/tileir/backend/extend_core.py b/third_party/tileir/backend/extend_core.py index 092e562959..b4d403dd36 100644 --- a/third_party/tileir/backend/extend_core.py +++ b/third_party/tileir/backend/extend_core.py @@ -2,7 +2,6 @@ from triton.language import core as tl - MLIR_DYNAMIC = -9223372036854775808 @@ -69,12 +68,8 @@ def is_ptr(self): return True def __eq__(self, other): - return ( - isinstance(other, tensor_view_type) - and self.element_ty == other.element_ty - and self.shape == other.shape - and self.stride == other.stride - ) + return (isinstance(other, tensor_view_type) and self.element_ty == other.element_ty + and self.shape == other.shape and self.stride == other.stride) class partition_view_type(tl.dtype): @@ -104,22 +99,16 @@ def is_ptr(self): return False def __eq__(self, other): - return ( - isinstance(other, partition_view_type) - and self.tensor_view == other.tensor_view - and self.element_ty == other.element_ty - and self.tile_shape == other.tile_shape - and self.tile_dim_map == other.tile_dim_map - ) + return (isinstance(other, partition_view_type) and self.tensor_view == other.tensor_view + and self.element_ty == other.element_ty and self.tile_shape == other.tile_shape + and self.tile_dim_map == other.tile_dim_map) def _require_tileir_builder(_semantic, op_name): semantic_op_name = "create_mem_token" if op_name == "mem_token" else op_name if _semantic is None or not hasattr(_semantic, semantic_op_name): - raise NotImplementedError( - f"tl.ext.{op_name} is only supported by FlagTree's TileIR path; " - "set FLAGTREE_USE_TILEIR=1 for kernels that only use TileIR TKO view ops" - ) + raise NotImplementedError(f"tl.ext.{op_name} is only supported by FlagTree's TileIR path; " + "set FLAGTREE_USE_TILEIR=1 for kernels that only use TileIR TKO view ops") def _unwrap_list(values): @@ -162,6 +151,7 @@ def cat(input, other, can_reorder=False, dim=-1, _semantic=None): def _unsupported(name): + def fn(*args, **kwargs): raise NotImplementedError(f"tl.ext.{name} is not supported by FlagTree's TileIR frontend yet") diff --git a/third_party/tileir/backend/extend_semantic.py b/third_party/tileir/backend/extend_semantic.py index 0376096903..b4a976d70d 100644 --- a/third_party/tileir/backend/extend_semantic.py +++ b/third_party/tileir/backend/extend_semantic.py @@ -57,6 +57,7 @@ def _str_to_ele_ty(dtype): def _convert_view_coords(coords, semantic): + def convert_elem(elem): elem = tl._unwrap_if_constexpr(elem) if isinstance(elem, int): diff --git a/python/triton/_flagtree/tileir_router.py b/third_party/tileir/backend/router.py similarity index 96% rename from python/triton/_flagtree/tileir_router.py rename to third_party/tileir/backend/router.py index 16f8215959..0918f3c194 100644 --- a/python/triton/_flagtree/tileir_router.py +++ b/third_party/tileir/backend/router.py @@ -1,4 +1,4 @@ -"""Per-kernel routing between the nvidia backend and the tileir backend. +"""Per-kernel routing between the NVIDIA and TileIR backends. Activation: FLAGTREE_USE_TILEIR=1 — opt non-TLE kernels into the TileIR compile path. @@ -21,8 +21,9 @@ from triton.backends.compiler import GPUTarget - -_NVIDIA_BACKENDS = ("cuda", "nvidia") +# ``nvidia`` is the installed backend name; its GPUTarget label is ``cuda``. +# Routing operates on target labels, so only ``cuda`` is valid here. +_CUDA_TARGET = "cuda" _TILEIR_EXT_MODULE = "triton.backends.tileir.extend_core" _TLE_TILE_MODULE = "triton.experimental.tle.language.gpu.tile" _TILEIR_SUPPORTED_TLE_TILE_ATTRS = frozenset({ @@ -223,14 +224,14 @@ def pick_target_for_kernel(target: GPUTarget, jit_fn) -> GPUTarget: """Return either ``target`` unchanged or a TileIR-flavored variant. Routing matrix: - backend not in {cuda, nvidia} → unchanged (e.g. AMD) + backend is not cuda → unchanged (e.g. AMD) FLAGTREE_USE_TILEIR not set → unchanged (no behavior change vs today) env set + unsupported TLE → unchanged + warning env set + TileIR TLE subset → swap backend to "tileir" env set + unknown source → unchanged + warning env set + no TLE → swap backend to "tileir" """ - if target.backend not in _NVIDIA_BACKENDS: + if target.backend != _CUDA_TARGET: return target if not _env_use_tileir(): return target diff --git a/third_party/tileir/include/Transform/Passes.td b/third_party/tileir/include/Transform/Passes.td index 0886303bc6..5d398c6bee 100644 --- a/third_party/tileir/include/Transform/Passes.td +++ b/third_party/tileir/include/Transform/Passes.td @@ -35,9 +35,9 @@ def RewriteAssumeWithCudaTile : Pass, %46 : tile ``` - There may be more patterns in the future. + There may be more patterns in the future. If there are no patterns matched, the llvm.intr.assume will be removed without any new op. - + This transformation allows the compiler to better understand alignment assumptions and potentially generate more efficient code. }]; diff --git a/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h b/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h index 6f4a22266d..06be2c340e 100644 --- a/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h +++ b/third_party/tileir/include/TritonToTileIR/TritonToTileIRPass.h @@ -18,8 +18,8 @@ namespace triton { std::unique_ptr> createConvertTritonToCudaTilePass(); std::unique_ptr> createConvertTritonToCudaTilePass(bool approx, bool ftz, int capability, - int num_ctas, int simt_num_warps, int occupancy, - std::optional num_stages); + int num_ctas, int simt_num_warps, + int occupancy, std::optional num_stages); } // namespace triton diff --git a/third_party/tileir/include/TritonToTileIR/Utils.h b/third_party/tileir/include/TritonToTileIR/Utils.h index deaabda99f..e3e69e4d04 100644 --- a/third_party/tileir/include/TritonToTileIR/Utils.h +++ b/third_party/tileir/include/TritonToTileIR/Utils.h @@ -152,7 +152,7 @@ class ConvertGenericOp : public OpConversionPattern { cuda_tile::RoundingMode::NEAREST_EVEN); } else if constexpr (std::is_same_v) { assert(operands.size() == 1 && "expect single operand for exp"); - return CudaTileOp::create(builder, loc, operands[0]); + return CudaTileOp::create(builder, loc, operands[0]); } else if constexpr (std::is_same_v) { assert(operands.size() == 1 && "expect single operand for ex2"); bool isF32 = getElementTypeOrSelf(op.getResult().getType()).isF32(); diff --git a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp index 0cc089fa79..3008f62ad1 100644 --- a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp +++ b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.cpp @@ -39,16 +39,16 @@ using namespace mlir; //===----------------------------------------------------------------------===// // Forward declaration for mutual recursion. -static LogicalResult liftBodyOp( - Operation &bodyOp, OpBuilder &builder, Location loc, - IRMapping &mapping, ArrayRef shape, Attribute encoding); +static LogicalResult liftBodyOp(Operation &bodyOp, OpBuilder &builder, + Location loc, IRMapping &mapping, + ArrayRef shape, Attribute encoding); // Lift a scalar value to tensor: if already tensor, return as-is. // If scalar (e.g., hoisted constant or block argument from outside), // insert tt.splat to broadcast to the target tensor shape. -static Value liftToTensor( - Value val, OpBuilder &builder, Location loc, IRMapping &mapping, - ArrayRef shape, Attribute encoding) { +static Value liftToTensor(Value val, OpBuilder &builder, Location loc, + IRMapping &mapping, ArrayRef shape, + Attribute encoding) { Value mapped = mapping.lookupOrDefault(val); if (isa(mapped.getType())) return mapped; @@ -61,9 +61,10 @@ static Value liftToTensor( // Recursively lift all ops in a region body (single block) to tensor level. // Returns the lifted values corresponding to the scf.yield operands. -static SmallVector liftRegionBody( - Region ®ion, OpBuilder &builder, Location loc, - IRMapping &mapping, ArrayRef shape, Attribute encoding) { +static SmallVector liftRegionBody(Region ®ion, OpBuilder &builder, + Location loc, IRMapping &mapping, + ArrayRef shape, + Attribute encoding) { assert(region.hasOneBlock() && "expected single-block region"); Block &body = region.front(); for (auto &op : body.without_terminator()) { @@ -84,9 +85,9 @@ static SmallVector liftRegionBody( // - arith::ConstantOp → DenseElementsAttr splat // - scf::IfOp → recursive expansion into arith.select // - generic ops → remap operands to tensor, create new op with tensor types -static LogicalResult liftBodyOp( - Operation &bodyOp, OpBuilder &builder, Location loc, - IRMapping &mapping, ArrayRef shape, Attribute encoding) { +static LogicalResult liftBodyOp(Operation &bodyOp, OpBuilder &builder, + Location loc, IRMapping &mapping, + ArrayRef shape, Attribute encoding) { // Skip terminators handled by callers if (isa(&bodyOp)) @@ -95,8 +96,8 @@ static LogicalResult liftBodyOp( // (1) arith::ConstantOp → DenseElementsAttr splat if (auto constOp = dyn_cast(&bodyOp)) { auto scalarAttr = cast(constOp.getValue()); - auto tensorType = RankedTensorType::get( - shape, scalarAttr.getType(), encoding); + auto tensorType = + RankedTensorType::get(shape, scalarAttr.getType(), encoding); auto splatAttr = DenseElementsAttr::get(tensorType, scalarAttr); auto newConst = arith::ConstantOp::create(builder, loc, splatAttr); mapping.map(constOp.getResult(), newConst.getResult()); @@ -105,30 +106,27 @@ static LogicalResult liftBodyOp( // (2) scf::IfOp → lift both branches, emit arith.select if (auto ifOp = dyn_cast(&bodyOp)) { - Value condTensor = - liftToTensor(ifOp.getCondition(), builder, loc, mapping, shape, - encoding); + Value condTensor = liftToTensor(ifOp.getCondition(), builder, loc, mapping, + shape, encoding); // Process then region IRMapping thenMapping(mapping); - SmallVector thenVals = - liftRegionBody(ifOp.getThenRegion(), builder, loc, thenMapping, - shape, encoding); + SmallVector thenVals = liftRegionBody( + ifOp.getThenRegion(), builder, loc, thenMapping, shape, encoding); if (thenVals.empty() && ifOp.getNumResults() > 0) return failure(); // Process else region IRMapping elseMapping(mapping); - SmallVector elseVals = - liftRegionBody(ifOp.getElseRegion(), builder, loc, elseMapping, - shape, encoding); + SmallVector elseVals = liftRegionBody( + ifOp.getElseRegion(), builder, loc, elseMapping, shape, encoding); if (elseVals.empty() && ifOp.getNumResults() > 0) return failure(); // Emit arith.select for each result for (auto [i, oldRes] : llvm::enumerate(ifOp.getResults())) { - auto sel = arith::SelectOp::create( - builder, loc, condTensor, thenVals[i], elseVals[i]); + auto sel = arith::SelectOp::create(builder, loc, condTensor, thenVals[i], + elseVals[i]); mapping.map(oldRes, sel.getResult()); } return success(); @@ -177,7 +175,8 @@ static LogicalResult scalarIfConvert(Region ®ion) { SmallVector ifOps; body.walk([&](scf::IfOp ifOp) { ifOps.push_back(ifOp); }); - // Process innermost first (walk visits outer before inner, reverse fixes this) + // Process innermost first (walk visits outer before inner, reverse fixes + // this) for (auto ifOp : llvm::reverse(ifOps)) { OpBuilder builder(ifOp); Location loc = ifOp.getLoc(); @@ -201,8 +200,8 @@ static LogicalResult scalarIfConvert(Region ®ion) { // Create arith.select for each result SmallVector selectResults; for (auto [tv, ev] : llvm::zip(thenVals, elseVals)) { - auto sel = arith::SelectOp::create( - builder, loc, ifOp.getCondition(), tv, ev); + auto sel = + arith::SelectOp::create(builder, loc, ifOp.getCondition(), tv, ev); selectResults.push_back(sel.getResult()); } @@ -224,17 +223,17 @@ static LogicalResult scalarIfConvert(Region ®ion) { // For K=1: just return the input (no split needed). // For K=2: one tt.split → 2 results. // For K>2: split into halves, recurse on each half. -static SmallVector recursiveSplit( - OpBuilder &builder, Location loc, Value tensor, int K) { +static SmallVector recursiveSplit(OpBuilder &builder, Location loc, + Value tensor, int K) { if (K == 1) { // Last dim is 1, drop it via reshape auto srcType = cast(tensor.getType()); auto srcShape = srcType.getShape(); SmallVector newShape(srcShape.begin(), srcShape.end() - 1); - auto newType = RankedTensorType::get( - newShape, srcType.getElementType(), srcType.getEncoding()); - Value reshaped = triton::ReshapeOp::create( - builder, loc, newType, tensor).getResult(); + auto newType = RankedTensorType::get(newShape, srcType.getElementType(), + srcType.getEncoding()); + Value reshaped = + triton::ReshapeOp::create(builder, loc, newType, tensor).getResult(); return {reshaped}; } @@ -254,15 +253,17 @@ static SmallVector recursiveSplit( auto reshapedType = RankedTensorType::get( reshapedShape, srcType.getElementType(), srcType.getEncoding()); - Value reshaped = triton::ReshapeOp::create( - builder, loc, reshapedType, tensor).getResult(); + Value reshaped = + triton::ReshapeOp::create(builder, loc, reshapedType, tensor).getResult(); // Split [.., M, K/2, 2] → lhs: [.., M, K/2], rhs: [.., M, K/2] auto splitOp = triton::SplitOp::create(builder, loc, reshaped); // Recurse on each half - SmallVector lhsResults = recursiveSplit(builder, loc, splitOp.getOutLHS(), K / 2); - SmallVector rhsResults = recursiveSplit(builder, loc, splitOp.getOutRHS(), K / 2); + SmallVector lhsResults = + recursiveSplit(builder, loc, splitOp.getOutLHS(), K / 2); + SmallVector rhsResults = + recursiveSplit(builder, loc, splitOp.getOutRHS(), K / 2); // Interleave: [lhs0, rhs0, lhs1, rhs1, ...] to maintain element order SmallVector results; @@ -277,23 +278,25 @@ static SmallVector recursiveSplit( // Input: K tensors of shape [.., M]. // Output: tensor of shape [.., M, K]. // Reverses the recursiveSplit operation. -static Value recursiveJoin( - OpBuilder &builder, Location loc, ArrayRef tensors, int K, - RankedTensorType finalType) { +static Value recursiveJoin(OpBuilder &builder, Location loc, + ArrayRef tensors, int K, + RankedTensorType finalType) { if (K == 1) { // Add back last dim via reshape: [.., M] → [.., M, 1] auto srcType = cast(tensors[0].getType()); auto srcShape = srcType.getShape(); SmallVector newShape(srcShape.begin(), srcShape.end()); newShape.push_back(1); - auto newType = RankedTensorType::get( - newShape, srcType.getElementType(), srcType.getEncoding()); - return triton::ReshapeOp::create(builder, loc, newType, tensors[0]).getResult(); + auto newType = RankedTensorType::get(newShape, srcType.getElementType(), + srcType.getEncoding()); + return triton::ReshapeOp::create(builder, loc, newType, tensors[0]) + .getResult(); } if (K == 2) { // Base case: binary join - return triton::JoinOp::create(builder, loc, tensors[0], tensors[1]).getResult(); + return triton::JoinOp::create(builder, loc, tensors[0], tensors[1]) + .getResult(); } // Recursive case: de-interleave into even/odd halves, recurse, join @@ -319,12 +322,15 @@ static Value recursiveJoin( // Reshape to collapse the last two dims auto joinedType = cast(joined.getResult().getType()); auto joinedShape = joinedType.getShape(); - SmallVector collapsedShape(joinedShape.begin(), joinedShape.end() - 2); + SmallVector collapsedShape(joinedShape.begin(), + joinedShape.end() - 2); collapsedShape.push_back(joinedShape[joinedShape.size() - 2] * 2); auto collapsedType = RankedTensorType::get( collapsedShape, joinedType.getElementType(), joinedType.getEncoding()); - return triton::ReshapeOp::create(builder, loc, collapsedType, joined.getResult()).getResult(); + return triton::ReshapeOp::create(builder, loc, collapsedType, + joined.getResult()) + .getResult(); } //===----------------------------------------------------------------------===// @@ -333,15 +339,16 @@ static Value recursiveJoin( static bool regionHasScfIf(Region ®ion) { bool found = false; - region.walk([&](scf::IfOp) { found = true; return WalkResult::interrupt(); }); + region.walk([&](scf::IfOp) { + found = true; + return WalkResult::interrupt(); + }); return found; } static LogicalResult expandMapElementwiseOpsImpl(Operation *rootOp) { SmallVector opsToExpand; - rootOp->walk([&](triton::MapElementwiseOp op) { - opsToExpand.push_back(op); - }); + rootOp->walk([&](triton::MapElementwiseOp op) { opsToExpand.push_back(op); }); for (auto op : opsToExpand) { auto ®ion = op.getScalarOp(); @@ -374,8 +381,8 @@ static LogicalResult expandMapElementwiseOpsImpl(Operation *rootOp) { mapping.map(arg, src); for (auto &bodyOp : body.without_terminator()) { - if (failed(liftBodyOp(bodyOp, builder, loc, mapping, origShape, - encoding))) + if (failed( + liftBodyOp(bodyOp, builder, loc, mapping, origShape, encoding))) return failure(); } @@ -417,8 +424,9 @@ static LogicalResult expandMapElementwiseOpsImpl(Operation *rootOp) { auto reshapedType = RankedTensorType::get( reshapedShape, srcType.getElementType(), srcType.getEncoding()); - Value reshaped = triton::ReshapeOp::create( - builder, loc, reshapedType, src).getResult(); + Value reshaped = + triton::ReshapeOp::create(builder, loc, reshapedType, src) + .getResult(); // Recursively split into K sub-tensors splitResults[i] = recursiveSplit(builder, loc, reshaped, pack); @@ -460,18 +468,21 @@ static LogicalResult expandMapElementwiseOpsImpl(Operation *rootOp) { // Collect the K sub-element results for this output SmallVector subResults; for (int p = 0; p < pack; p++) { - subResults.push_back( - liftToTensor(retVals[outIdx * pack + p], builder, loc, mapping, - subShape, subEncoding)); + subResults.push_back(liftToTensor(retVals[outIdx * pack + p], builder, + loc, mapping, subShape, + subEncoding)); } // Join K sub-tensors → [.., N/K, K] - auto outputType = cast(op->getResult(outIdx).getType()); - Value joined = recursiveJoin(builder, loc, subResults, pack, outputType); + auto outputType = + cast(op->getResult(outIdx).getType()); + Value joined = + recursiveJoin(builder, loc, subResults, pack, outputType); // Reshape [.., N/K, K] → [.., N] - Value result = triton::ReshapeOp::create( - builder, loc, outputType, joined).getResult(); + Value result = + triton::ReshapeOp::create(builder, loc, outputType, joined) + .getResult(); results.push_back(result); } @@ -504,4 +515,3 @@ LogicalResult expandMapElementwiseOps(Operation *rootOp) { } // namespace triton } // namespace mlir - diff --git a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h index b7f653a309..0d35670029 100644 --- a/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h +++ b/third_party/tileir/lib/TritonToTileIR/MapElementwiseExpansion.h @@ -2,7 +2,8 @@ // // Declares pre-processing utilities for tt.map_elementwise: // - ifConvertMapElementwiseRegions: scf.if → arith.select at scalar level -// - expandMapElementwiseOps: tensor-level expansion for all map_elementwise ops +// - expandMapElementwiseOps: tensor-level expansion for all map_elementwise +// ops // // These are pure IR rewrites that run before dialect conversion. // diff --git a/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp b/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp index e14fa95429..ec97af44bc 100644 --- a/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp +++ b/third_party/tileir/lib/TritonToTileIR/TritonToTileIRPass.cpp @@ -225,7 +225,7 @@ class ConvertPrintOp : public OpConversionPattern { newPrefix += "\n"; cuda_tile::PrintTkoOp::create(rewriter, op.getLoc(), newPrefix, args, - /*token=*/Value()); + /*token=*/Value()); rewriter.eraseOp(op); return success(); } @@ -336,16 +336,13 @@ void createTargetOp(ConversionPatternRewriter &rewriter, triton::FuncOp op, TargetOp newOp; if constexpr (std::is_same_v) { llvm::StringRef archPrefix = "sm_"; - auto numCTAAttr = - rewriter.getNamedAttr("num_cta_in_cga", - rewriter.getI32IntegerAttr(numCTAInCGA)); - auto occupancyAttr = - rewriter.getNamedAttr("occupancy", - rewriter.getI32IntegerAttr(occupancy)); - auto hintEntry = rewriter.getNamedAttr( - (archPrefix + llvm::Twine(computeCapability)).str(), - DictionaryAttr::get(ctx, - {numCTAAttr, occupancyAttr})); + auto numCTAAttr = rewriter.getNamedAttr( + "num_cta_in_cga", rewriter.getI32IntegerAttr(numCTAInCGA)); + auto occupancyAttr = rewriter.getNamedAttr( + "occupancy", rewriter.getI32IntegerAttr(occupancy)); + auto hintEntry = rewriter.getNamedAttr( + (archPrefix + llvm::Twine(computeCapability)).str(), + DictionaryAttr::get(ctx, {numCTAAttr, occupancyAttr})); cuda_tile::OptimizationHintsAttr optHint = cuda_tile::OptimizationHintsAttr::get( ctx, DictionaryAttr::get(ctx, {hintEntry})); @@ -534,8 +531,8 @@ class ConvertDescriptorLoadOp for (size_t i = 0; i < tileSizes.size(); i++) viewShapeVec.push_back(tileSizes[i]); } else { - return rewriter.notifyMatchFailure( - op.getLoc(), "expect a partition view type"); + return rewriter.notifyMatchFailure(op.getLoc(), + "expect a partition view type"); } auto tileShape = op.getResult().getType().getShape(); @@ -605,8 +602,8 @@ class ConvertDescriptorStoreOp cuda_tile::Signedness::Signed)); } } else { - return rewriter.notifyMatchFailure( - op.getLoc(), "expect a partition view type"); + return rewriter.notifyMatchFailure(op.getLoc(), + "expect a partition view type"); } auto optHint = mlir::triton::utils::convertNumStagesToOptHint( @@ -623,7 +620,6 @@ class ConvertDescriptorStoreOp } }; - /// Convert an expand dims to a reshape by adding a new dimension (1) at a given /// position. class ConvertExpandDimsOp : public OpConversionPattern { @@ -927,8 +923,7 @@ class ConvertExternElementwiseOp rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); return success(); } else if (symbol == "__nv_fast_expf") { - rewriter.replaceOpWithNewOp( - op, adaptor.getSrcs()[0]); + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); return success(); } else if (symbol == "__nv_exp2" || symbol == "__nv_exp2f") { rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); @@ -952,12 +947,10 @@ class ConvertExternElementwiseOp rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); return success(); } else if (symbol == "__nv_tanhf" || symbol == "__nv_tanh") { - rewriter.replaceOpWithNewOp( - op, adaptor.getSrcs()[0]); + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); return success(); } else if (symbol == "__nv_fast_tanhf") { - rewriter.replaceOpWithNewOp( - op, adaptor.getSrcs()[0]); + rewriter.replaceOpWithNewOp(op, adaptor.getSrcs()[0]); return success(); } return rewriter.notifyMatchFailure( @@ -1028,8 +1021,7 @@ class ConvertErfOp : public OpConversionPattern { p = addf(splatFloat(1.00002368), mulf(t, p)); Value negAx2 = subf(zero, mulf(ax, ax)); - Value expArg = - addf(addf(negAx2, splatFloat(-1.26551223)), mulf(t, p)); + Value expArg = addf(addf(negAx2, splatFloat(-1.26551223)), mulf(t, p)); Value tau = mulf(t, cuda_tile::ExpOp::create(rewriter, loc, expArg)); Value erfAbs = subf(one, tau); @@ -1926,15 +1918,15 @@ class ConvertMakeTensorDescOp arrayOfi32Shape.push_back(i64Shape); } - auto tilePartViewTy = cuda_tile::PartitionViewType::get( - ctx, rewriter.getDenseI32ArrayAttr(arrayOfi32Shape), tensorViewTy, - dimMap, - cuda_tile::PaddingValueAttr::get(ctx, cuda_tile::PaddingValue::zero)); + auto tilePartViewTy = cuda_tile::PartitionViewType::get( + ctx, rewriter.getDenseI32ArrayAttr(arrayOfi32Shape), tensorViewTy, + dimMap, + cuda_tile::PaddingValueAttr::get(ctx, cuda_tile::PaddingValue::zero)); - auto partViewOp = cuda_tile::MakePartitionViewOp::create( - rewriter, loc, tilePartViewTy, makeTensorViewOp); + auto partViewOp = cuda_tile::MakePartitionViewOp::create( + rewriter, loc, tilePartViewTy, makeTensorViewOp); - rewriter.replaceOp(op, partViewOp); + rewriter.replaceOp(op, partViewOp); return success(); } }; @@ -2132,7 +2124,6 @@ class ConvertDotOp : public OpConversionPattern { } }; - class ConvertTransOp : public OpConversionPattern { public: using OpConversionPattern::OpConversionPattern; @@ -2788,18 +2779,18 @@ static void convertTmaDescriptorOps(Operation *op, TypeConverter &converter) { SmallVector dimMap(rank); std::iota(dimMap.begin(), dimMap.end(), 0); - auto tilePartViewTy = cuda_tile::PartitionViewType::get( - ctx, rewriter.getDenseI32ArrayAttr(arrayOfi32Shape), - tensorViewTy, dimMap, - cuda_tile::PaddingValueAttr::get(ctx, - cuda_tile::PaddingValue::zero)); + auto tilePartViewTy = cuda_tile::PartitionViewType::get( + ctx, rewriter.getDenseI32ArrayAttr(arrayOfi32Shape), tensorViewTy, + dimMap, + cuda_tile::PaddingValueAttr::get(ctx, + cuda_tile::PaddingValue::zero)); - auto partViewOp = cuda_tile::MakePartitionViewOp::create( - rewriter, loc, tilePartViewTy, makeTensorViewOp); + auto partViewOp = cuda_tile::MakePartitionViewOp::create( + rewriter, loc, tilePartViewTy, makeTensorViewOp); - auto castBackToTensorDescriptorOp = - UnrealizedConversionCastOp::create(rewriter, loc, tensorDescType, - partViewOp.getResult()); + auto castBackToTensorDescriptorOp = + UnrealizedConversionCastOp::create(rewriter, loc, tensorDescType, + partViewOp.getResult()); rewriter.replaceAllUsesWith( tensorDesc, castBackToTensorDescriptorOp.getResult(0)); diff --git a/third_party/tileir/lib/TritonToTileIR/Utils.cpp b/third_party/tileir/lib/TritonToTileIR/Utils.cpp index 4dc95bd837..f1a34a9d1b 100644 --- a/third_party/tileir/lib/TritonToTileIR/Utils.cpp +++ b/third_party/tileir/lib/TritonToTileIR/Utils.cpp @@ -425,9 +425,9 @@ CudaTileTypeConverter::CudaTileTypeConverter() { } auto shapeAttr = DenseI32ArrayAttr::get(ctx, arrayOfi32Shape); - return cuda_tile::PartitionViewType::get( - ctx, shapeAttr, tensorViewTy, dimMap, - cuda_tile::PaddingValueAttr::get(ctx, cuda_tile::PaddingValue::zero)); + return cuda_tile::PartitionViewType::get( + ctx, shapeAttr, tensorViewTy, dimMap, + cuda_tile::PaddingValueAttr::get(ctx, cuda_tile::PaddingValue::zero)); }); addConversion([](cuda_tile::TensorViewType type) { return type; }); addConversion([](cuda_tile::PartitionViewType type) { return type; }); diff --git a/third_party/tileir/lib/Utils/Utils.cpp b/third_party/tileir/lib/Utils/Utils.cpp index 456c0df903..0efcf2a93d 100644 --- a/third_party/tileir/lib/Utils/Utils.cpp +++ b/third_party/tileir/lib/Utils/Utils.cpp @@ -62,7 +62,7 @@ cvtNumStagesToOptHintAttr(MLIRContext *ctx, int computeCapability, mlir::NamedAttribute( arch, mlir::DictionaryAttr::get( ctx, mlir::NamedAttribute( - mlir::StringAttr::get(ctx, "latency"), + mlir::StringAttr::get(ctx, "latency"), mlir::IntegerAttr::get( IntegerType::get(ctx, 32), numStages)))))); } diff --git a/third_party/tileir/scripts/check_wheel_deps.py b/third_party/tileir/scripts/check_wheel_deps.py old mode 100644 new mode 100755 index 5b710c0a3d..33328d81fe --- a/third_party/tileir/scripts/check_wheel_deps.py +++ b/third_party/tileir/scripts/check_wheel_deps.py @@ -9,11 +9,10 @@ import zipfile from pathlib import Path - REQUIRED = [ - "tileiras", # backend/bin/tileiras - "ptxas", # backend/bin/ptxas - "libnvvm.so", # backend/lib/nvvm/.../libnvvm.so + "tileiras", # backend/bin/tileiras + "ptxas", # backend/bin/ptxas + "libnvvm.so", # backend/lib/nvvm/.../libnvvm.so ] diff --git a/third_party/tileir/scripts/copy_wheel_deps.py b/third_party/tileir/scripts/copy_wheel_deps.py old mode 100644 new mode 100755 diff --git a/third_party/tileir/test/CMakeLists.txt b/third_party/tileir/test/CMakeLists.txt index 232d976eea..7c4b49eda1 100644 --- a/third_party/tileir/test/CMakeLists.txt +++ b/third_party/tileir/test/CMakeLists.txt @@ -33,4 +33,4 @@ set_target_properties(check-triton-cuda-tile PROPERTIES FOLDER "Tests") add_lit_testsuites(triton-cuda-tile ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${TRITON_CUDA_TILE_TEST_DEPENDS} -) +) diff --git a/third_party/tileir/test/FileCheck/fma.mlir b/third_party/tileir/test/FileCheck/fma.mlir index eb6d77aa82..f460dcb6ed 100644 --- a/third_party/tileir/test/FileCheck/fma.mlir +++ b/third_party/tileir/test/FileCheck/fma.mlir @@ -1,2 +1 @@ // RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts)" | FileCheck %s - diff --git a/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir b/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir index 11efd3937e..669fe26fe4 100644 --- a/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir +++ b/third_party/tileir/test/FileCheck/op-conversion-barrier.mlir @@ -152,14 +152,13 @@ module @test_barrier_layer_norm_bwd{ // CHECK: } // CHECK: %[[RESULT9:.*]], %[[TOKEN9:.*]] = load_ptr_tko {{.*}}token=%[[TOKEN8]] // CHECK: %[[IF:.*]]:3 = if %{{.*}} { -// CHECK: %[[RMW_RES:.*]], %[[RMW_TOKEN:.*]] = atomic_rmw_tko {{.*}} token=%[[TOKEN9]] -// CHECK: yield %{{.*}}, %{{.*}}, %[[RMW_TOKEN]] +// CHECK: %[[RMW_RES:.*]], %[[RMW_TOKEN:.*]] = atomic_rmw_tko {{.*}} token=%[[TOKEN9]] +// CHECK: yield %{{.*}}, %{{.*}}, %[[RMW_TOKEN]] // CHECK: } else { -// CHECK: %[[LOAD1_RES:.*]], %[[LOAD1_TOKEN:.*]] = load_ptr_tko {{.*}} token=%[[TOKEN9]] -// CHECK: %[[LOAD2_RES:.*]], %[[LOAD2_TOKEN:.*]] = load_ptr_tko {{.*}} token=%[[LOAD1_TOKEN]] -// CHECK: yield %{{.*}}, %{{.*}}, %[[LOAD2_TOKEN]] +// CHECK: %[[LOAD1_RES:.*]], %[[LOAD1_TOKEN:.*]] = load_ptr_tko {{.*}} token=%[[TOKEN9]] +// CHECK: %[[LOAD2_RES:.*]], %[[LOAD2_TOKEN:.*]] = load_ptr_tko {{.*}} token=%[[LOAD1_TOKEN]] +// CHECK: yield %{{.*}}, %{{.*}}, %[[LOAD2_TOKEN]] // CHECK: } // CHECK: %[[TOKEN10:.*]] = store_ptr_tko {{.*}} token=%[[IF]]#2 // CHECK: %[[TOKEN11:.*]] = store_ptr_tko {{.*}} token=%[[TOKEN10]] // CHECK: %[[RESULT12:.*]], %[[TOKEN12:.*]] = atomic_rmw_tko {{.*}} token=%[[TOKEN11]] - diff --git a/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir b/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir index dc3acc197f..d6913bf51f 100644 --- a/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir +++ b/third_party/tileir/test/FileCheck/op-conversion-modifiers.mlir @@ -1,6 +1,5 @@ -// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{approx-modifier=true flush-to-zero-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=APPROX_FTZ %s -// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{approx-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=APPROX %s -// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{flush-to-zero-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=FTZ %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{approx-modifier=true flush-to-zero-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=APPROX_FTZ %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{approx-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=APPROX %s +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{flush-to-zero-modifier=true},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=FTZ %s // RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{compute-capability=100 num-cta-in-cga=2},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=HINT-100 %s -// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{compute-capability=120 num-cta-in-cga=4},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=HINT-120 %s - +// RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile{compute-capability=120 num-cta-in-cga=4},cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)))" | FileCheck --check-prefix=HINT-120 %s diff --git a/third_party/tileir/test/FileCheck/op-conversion.mlir b/third_party/tileir/test/FileCheck/op-conversion.mlir index d10a603725..f2f600cbfb 100644 --- a/third_party/tileir/test/FileCheck/op-conversion.mlir +++ b/third_party/tileir/test/FileCheck/op-conversion.mlir @@ -1,2 +1 @@ // RUN: triton-cuda-tile-opt %s -split-input-file --pass-pipeline="builtin.module(convert-triton-to-cuda-tile,cuda_tile.module(cuda_tile.experimental\$func(fuse-fma)),reconcile-unrealized-casts)" -verify-diagnostics | FileCheck %s - diff --git a/third_party/tileir/triton_tileir.cc b/third_party/tileir/triton_tileir.cc index 54ade94d92..f56d6e2909 100644 --- a/third_party/tileir/triton_tileir.cc +++ b/third_party/tileir/triton_tileir.cc @@ -1,18 +1,19 @@ #include "mlir/Bytecode/BytecodeWriter.h" +#include "mlir/Conversion/Passes.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/GPU/IR/GPUDialect.h" -#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Dialect/Index/IR/IndexDialect.h" #include "mlir/Dialect/Index/IR/IndexOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/LLVMIR/Transforms/InlinerInterfaceImpl.h" -#include "mlir/IR/Types.h" -#include "mlir/IR/BuiltinTypes.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Types.h" #include "mlir/IR/Verifier.h" #include "mlir/Parser/Parser.h" #include "mlir/Pass/Pass.h" @@ -24,7 +25,6 @@ #include "mlir/Target/LLVMIR/Dialect/NVVM/NVVMToLLVMIRTranslation.h" #include "mlir/Transforms/LocationSnapshot.h" #include "mlir/Transforms/Passes.h" -#include "mlir/Conversion/Passes.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/Constants.h" @@ -39,8 +39,8 @@ #include "cuda_tile/Dialect/CudaTile/IR/Ops.h" #include "cuda_tile/Dialect/CudaTile/IR/Types.h" #include "cuda_tile/Dialect/CudaTile/Transforms/Passes.h" -#include "passes.h" #include "ir.h" +#include "passes.h" #include "triton/Analysis/Allocation.h" #include "triton/Dialect/Triton/IR/Dialect.h" #include "triton/Dialect/Triton/IR/Types.h" @@ -50,7 +50,6 @@ #include #include #include -#include "triton/Dialect/Triton/IR/Types.h" namespace py = pybind11; using namespace mlir; @@ -82,15 +81,14 @@ SmallVector castToTileVec(TritonOpBuilder &self, Value castFromCudaTileAndExtract(TritonOpBuilder &self, Type resultType, Value value) { auto cTileTy = cast(resultType); - auto resTy = RankedTensorType::get(cTileTy.getShape(), - cTileTy.getElementType()); + auto resTy = + RankedTensorType::get(cTileTy.getShape(), cTileTy.getElementType()); Value castedValue = self.create(resTy, value).getResult(0); if (resTy.getRank() == 0) { - castedValue = - self.create(resTy.getElementType(), - castedValue) - .getResult(0); + castedValue = self.create( + resTy.getElementType(), castedValue) + .getResult(0); } return castedValue; } @@ -101,8 +99,7 @@ getMemoryOrderingAttr(MLIRContext *ctx, uint32_t semantic) { ctx, cuda_tile::symbolizeMemoryOrderingSemantics(semantic).value()); } -cuda_tile::MemoryScopeAttr getMemoryScopeAttr(MLIRContext *ctx, - int32_t scope) { +cuda_tile::MemoryScopeAttr getMemoryScopeAttr(MLIRContext *ctx, int32_t scope) { if (scope < 0) return cuda_tile::MemoryScopeAttr(); return cuda_tile::MemoryScopeAttr::get( @@ -154,8 +151,7 @@ void init_tileir_tko_ir(py::module &&) { }) .def("create_make_partition_view", [](TritonOpBuilder &self, Value &src, - std::vector &tileShape, - std::vector &tileDimMap, + std::vector &tileShape, std::vector &tileDimMap, std::string paddingValue) -> std::tuple { auto tensorViewTy = cast(src.getType()); auto ctx = self.getBuilder().getContext(); @@ -164,11 +160,10 @@ void init_tileir_tko_ir(py::module &&) { ctx, builder.getDenseI32ArrayAttr(tileShape), tensorViewTy, tileDimMap, cuda_tile::PaddingValueAttr::get( - ctx, cuda_tile::symbolizePaddingValue(paddingValue) - .value())); - auto op = - self.create(partitionViewTy, - src); + ctx, + cuda_tile::symbolizePaddingValue(paddingValue).value())); + auto op = self.create( + partitionViewTy, src); return std::make_tuple(op.getResult(), op.getResult().getType()); }) .def("create_dim", @@ -230,10 +225,9 @@ void init_tileir_tko_ir(py::module &&) { resultTileTy.getShape().end()); auto rankedTy = RankedTensorType::get(shape, resultTileTy.getElementType()); - auto tensor = - self.create(rankedTy, - retOp.getTile()) - .getResult(0); + auto tensor = self.create( + rankedTy, retOp.getTile()) + .getResult(0); return std::make_tuple(tensor, retOp.getResultToken(), shape, resultTileTy.getElementType()); }) @@ -241,8 +235,8 @@ void init_tileir_tko_ir(py::module &&) { [](TritonOpBuilder &self, Value &view, Value &tile, std::vector &coords, std::optional &token, uint32_t semantic, int32_t scope, - [[maybe_unused]] bool hasResultToken, - int32_t cost, int32_t capability) -> Value { + [[maybe_unused]] bool hasResultToken, int32_t cost, + int32_t capability) -> Value { auto *ctx = self.getBuilder().getContext(); auto optHint = mlir::triton::utils::cvtNumStagesToOptHintAttr( ctx, capability, cost); @@ -280,12 +274,14 @@ void init_triton_to_cudatile_passes(py::module &&m) { using namespace mlir::triton; // TODO: it is weird to pass mlir::triton::NVVM here since the conversion is // nvidia-specificontext - m.def("add_triton_to_cudatile", [](mlir::PassManager &pm, bool approx, - bool ftz, int capability, int num_ctas, - int simt_num_warps, int occupancy, std::optional num_stages) { - pm.addPass(mlir::triton::createConvertTritonToCudaTilePass( - approx, ftz, capability, num_ctas, simt_num_warps, occupancy, num_stages)); - }); + m.def("add_triton_to_cudatile", + [](mlir::PassManager &pm, bool approx, bool ftz, int capability, + int num_ctas, int simt_num_warps, int occupancy, + std::optional num_stages) { + pm.addPass(mlir::triton::createConvertTritonToCudaTilePass( + approx, ftz, capability, num_ctas, simt_num_warps, occupancy, + num_stages)); + }); m.def("add_fma_fusion", [](mlir::PassManager &pm) { // Add FMA fusion pass to cuda tile entry operations auto &mpm = pm.nest(); @@ -317,11 +313,11 @@ void init_triton_to_cudatile_passes(py::module &&m) { m.def("add_assume_to_tileir", [](mlir::PassManager &pm) { pm.addPass(mlir::triton::createRewriteAssumeWithCudaTilePass()); }); - m.def("add_auto_gen_memtoken", [](mlir::PassManager &pm, - bool enable_autogen_alias_mem_token - ) { - pm.addPass(mlir::triton::createAutoGenMemoryTokenPass(enable_autogen_alias_mem_token)); - }); + m.def("add_auto_gen_memtoken", + [](mlir::PassManager &pm, bool enable_autogen_alias_mem_token) { + pm.addPass(mlir::triton::createAutoGenMemoryTokenPass( + enable_autogen_alias_mem_token)); + }); } void init_triton_tileir(py::module &&m) { @@ -343,8 +339,7 @@ void init_triton_tileir(py::module &&m) { mod->walk([&](mlir::Operation *op) { if (!llvm::isa(op) && (op->getName().getDialectNamespace() != - mlir::cuda_tile::CudaTileDialect::getDialectNamespace() - )) { + mlir::cuda_tile::CudaTileDialect::getDialectNamespace())) { only_contain_legal_dialects = false; } }); From a473babdce4f4af6e5febed5186e0deca381aadc Mon Sep 17 00:00:00 2001 From: KingsleyLiu-NV Date: Mon, 29 Jun 2026 01:56:51 -0700 Subject: [PATCH 11/11] [tle] Resolve backend language extensions lazily --- .../experimental/tle/language/gpu/tile.py | 41 +++---------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/python/triton/experimental/tle/language/gpu/tile.py b/python/triton/experimental/tle/language/gpu/tile.py index 714bf395db..fc4df79312 100644 --- a/python/triton/experimental/tle/language/gpu/tile.py +++ b/python/triton/experimental/tle/language/gpu/tile.py @@ -1,38 +1,9 @@ # flagtree tle -from triton.language import ext as _tileir_ext +import triton.language as _language -def _bind_tileir_ext(name): - try: - return getattr(_tileir_ext, name) - except RuntimeError: - pass - - def unavailable(*args, **kwargs): - return getattr(_tileir_ext, name)(*args, **kwargs) - - unavailable.__name__ = name - unavailable.__qualname__ = name - unavailable.__module__ = __name__ - return unavailable - - -create_mem_token = _bind_tileir_ext("create_mem_token") -dim = _bind_tileir_ext("dim") -join_mem_tokens = _bind_tileir_ext("join_mem_tokens") -load_view_tko = _bind_tileir_ext("load_view_tko") -make_partition_view = _bind_tileir_ext("make_partition_view") -make_tensor_view = _bind_tileir_ext("make_tensor_view") -make_view = _bind_tileir_ext("make_view") -store_view_tko = _bind_tileir_ext("store_view_tko") - -__all__ = [ - "make_tensor_view", - "make_partition_view", - "make_view", - "dim", - "load_view_tko", - "store_view_tko", - "create_mem_token", - "join_mem_tokens", -] +def __getattr__(name): + language_extensions = getattr(_language, "ext", None) + if language_extensions is None: + raise RuntimeError(f"tle.gpu.tile.{name} requires a backend providing tl.ext.{name}") + return getattr(language_extensions, name)