v4.3.3 update. (#2868)

This commit is contained in:
Junkai-Wu
2025-12-11 00:26:58 -05:00
committed by GitHub
parent 49bd6bf1ba
commit d3a5492381
24 changed files with 789 additions and 211 deletions
+11
View File
@@ -2,6 +2,17 @@
# CUTLASS 4.x # CUTLASS 4.x
## [4.3.3](https://github.com/NVIDIA/cutlass/releases/tag/v4.3.3) (2025-12-12)
### CuTe DSL
* New features
- Supported namedtuple and kwargs for JIT function arguments in tvm-ffi
- Supported variadic tuples for JIT function argument in tvm-ffi
* Bug fixing and improvements
- Fixed an issue when JIT function argument with union type annotation for tvm-ffi
- Clearer error message for the case of runtime error cudaErrorInsufficientDriver
## [4.3.2](https://github.com/NVIDIA/cutlass/releases/tag/v4.3.2) (2025-12-05) ## [4.3.2](https://github.com/NVIDIA/cutlass/releases/tag/v4.3.2) (2025-12-05)
### CuTe DSL ### CuTe DSL
+6 -2
View File
@@ -1,9 +1,9 @@
![ALT](./media/images/gemm-hierarchy-with-epilogue-no-labels.png "Complete CUDA GEMM decomposition") ![ALT](./media/images/gemm-hierarchy-with-epilogue-no-labels.png "Complete CUDA GEMM decomposition")
# Overview # Overview
# CUTLASS 4.3.2 # CUTLASS 4.3.3
_CUTLASS 4.3.2 - Dec 2025_ _CUTLASS 4.3.3 - Dec 2025_
CUTLASS is a collection of abstractions for implementing high-performance matrix-matrix multiplication (GEMM) CUTLASS is a collection of abstractions for implementing high-performance matrix-matrix multiplication (GEMM)
and related computations at all levels and scales within CUDA. It incorporates strategies for and related computations at all levels and scales within CUDA. It incorporates strategies for
@@ -54,6 +54,8 @@ To get started quickly - please refer :
- Added Blackwell SM103 support. - Added Blackwell SM103 support.
- Multiple dependent DSOs in the wheel have been merged into one single DSO. - Multiple dependent DSOs in the wheel have been merged into one single DSO.
- New env var `CUTE_DSL_CACHE_DIR` to specify the path for dumping caches. - New env var `CUTE_DSL_CACHE_DIR` to specify the path for dumping caches.
- Supported namedtuple and kwargs for JIT function arguments in tvm-ffi.
- Supported variadic tuples for JIT function argument in tvm-ffi.
* Debuggability improvements: * Debuggability improvements:
- Supported source location tracking for DSL APIs (Allow tools like ``nsight`` profiling to correlate perf metrics with Python source code) - Supported source location tracking for DSL APIs (Allow tools like ``nsight`` profiling to correlate perf metrics with Python source code)
- Supported dumping PTX and CUBIN code: [Hello World Example](https://github.com/NVIDIA/cutlass/blob/main/examples/python/CuTeDSL/notebooks/hello_world.ipynb) - Supported dumping PTX and CUBIN code: [Hello World Example](https://github.com/NVIDIA/cutlass/blob/main/examples/python/CuTeDSL/notebooks/hello_world.ipynb)
@@ -102,6 +104,8 @@ To get started quickly - please refer :
- Fixed tvm-ffi export compiled function - Fixed tvm-ffi export compiled function
- Fixed an issue of CUDA JitExecutor when unloading kernels - Fixed an issue of CUDA JitExecutor when unloading kernels
- Fixed an issue of allocating max smem when there's statically allocated smem - Fixed an issue of allocating max smem when there's statically allocated smem
- Fixed an issue when JIT function argument with union type annotation for tvm-ffi
- Clearer error message for the case of runtime error cudaErrorInsufficientDriver
## CUTLASS C++ ## CUTLASS C++
* Further enhance Blackwell SM100 Attention kernels in [example 77](https://github.com/NVIDIA/cutlass/tree/main/examples/77_blackwell_fmha/). * Further enhance Blackwell SM100 Attention kernels in [example 77](https://github.com/NVIDIA/cutlass/tree/main/examples/77_blackwell_fmha/).
+1 -1
View File
@@ -36,7 +36,7 @@
#define CUTLASS_MAJOR 4 #define CUTLASS_MAJOR 4
#define CUTLASS_MINOR 3 #define CUTLASS_MINOR 3
#define CUTLASS_PATCH 2 #define CUTLASS_PATCH 3
#ifdef CUTLASS_VERSIONS_GENERATED #ifdef CUTLASS_VERSIONS_GENERATED
#include "cutlass/version_extended.h" #include "cutlass/version_extended.h"
@@ -288,6 +288,131 @@ composed of the types that are supported by TVM FFI. The example below shows how
example_add_one_with_tuple() example_add_one_with_tuple()
Working with Variadic Tuples
----------------------------
Sometimes it is helpful to annotate a tuple with no explicit element types.
This can be useful to build up a generic template for a function that accepts
a variable number of elements. The compiled function's signature will be
determined by the tuple argument passed to the ``cute.compile`` function.
The following example shows how to use a variadic tuple to build such a
generic template.
.. code-block:: python
import cutlass
import torch
from cutlass import cute
@cute.kernel
def device_add_one(a: cute.Tensor, b: cute.Tensor, extra_value: tuple):
threads_per_block = 128
cta_x_, _, _ = cute.arch.block_idx()
tid_x, _, _ = cute.arch.thread_idx()
tid = cta_x_ * threads_per_block + tid_x
if tid < a.shape[0]:
if cutlass.const_expr(len(extra_value) != 0):
b[tid] = a[tid] + 1 + extra_value[0]
else:
b[tid] = a[tid] + 1
@cute.jit
def add_one_with_extra_value(a: cute.Tensor, b: cute.Tensor, extra_value: tuple):
n = a.shape[0]
threads_per_block = 128
blocks = (n + threads_per_block - 1) // threads_per_block
device_add_one(a, b, extra_value).launch(grid=(blocks, 1, 1), block=(threads_per_block, 1, 1))
def example_add_one_with_variadic_tuple():
n = cute.sym_int()
a_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
b_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
compiled_add_one_no_extra = cute.compile(
add_one_with_extra_value, a_cute, b_cute, (),
options="--enable-tvm-ffi"
)
compiled_add_one_with_extra = cute.compile(
add_one_with_extra_value, a_cute, b_cute, (cute.Float32(4),),
options="--enable-tvm-ffi"
)
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.empty(10, dtype=torch.float32, device="cuda")
compiled_add_one_no_extra(a_torch, b_torch, ())
print("result of b_torch after compiled_add_one_no_extra(a_torch, b_torch, ())")
print(b_torch)
compiled_add_one_with_extra(a_torch, b_torch, (4,))
print("result of b_torch after compiled_add_one_with_extra(a_torch, b_torch, (4,))")
print(b_torch)
example_add_one_with_variadic_tuple()
Working with Named Tuples
~~~~~~~~~~~~~~~~~~~~~~~~~
Named tuples are also supported and help logically group related arguments together.
The example below shows how to use named tuples as arguments. Under the hood, named tuples
are passed as unnamed tuples at the ABI level. When errors occur, the function signature in
error messages will display unnamed tuple arguments.
Ensure that the compile-time CuTe named tuple type definition has the same fields
as the runtime PyTorch named tuple.
Currently, users need to explicitly unpack the named tuple outside of conditionals and then
use the unpacked variables inside the conditionals.
.. code-block:: python
from typing import NamedTuple
from cutlass import cute
import torch
class CuteNamedTuple(NamedTuple):
a: cute.Tensor
b: cute.Tensor
c: cute.Float32 = cute.Float32(1)
def __new_from_mlir_values__(self, values):
return CuteNamedTuple(*values)
class TorchNamedTuple(NamedTuple):
a: torch.Tensor
b: torch.Tensor
c: float = 1
@cute.kernel
def device_add_one_named_tuple(value: CuteNamedTuple):
tid = cute.arch.block_idx()[0] * 128 + cute.arch.thread_idx()[0]
# need to unpack namedtuple outside conditionals
a = value.a
b = value.b
c = value.c
if tid < a.shape[0]:
b[tid] = a[tid] + c
@cute.jit
def add_one_with_named_tuple(value: CuteNamedTuple):
n = value.a.shape[0]
threads_per_block = 128
blocks = (n + threads_per_block - 1) // threads_per_block
device_add_one_named_tuple(value).launch(grid=(blocks, 1, 1), block=(threads_per_block, 1, 1))
def example_add_one_with_named_tuple():
n = cute.sym_int()
a_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
b_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
compiled_add_one = cute.compile(
add_one_with_named_tuple, CuteNamedTuple(a=a_cute, b=b_cute),
options="--enable-tvm-ffi"
)
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.empty(10, dtype=torch.float32, device="cuda")
compiled_add_one(TorchNamedTuple(a=a_torch, b=b_torch))
print("result of b_torch")
print(b_torch)
example_add_one_with_named_tuple()
Supported types Supported types
--------------- ---------------
@@ -464,3 +589,97 @@ When you build your own libraries, make sure you link against the necessary runt
You can use ``cute.runtime.find_runtime_libraries(enable_tvm_ffi=True)`` to get the path to these libraries. You can use ``cute.runtime.find_runtime_libraries(enable_tvm_ffi=True)`` to get the path to these libraries.
``cute.runtime.load_module`` will load these libraries automatically before loading ``cute.runtime.load_module`` will load these libraries automatically before loading
an exported module. You can also manually load these libraries in advanced use cases. an exported module. You can also manually load these libraries in advanced use cases.
Keyword Arguments and Defaults
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The function returned by ``cute.compile`` supports keyword arguments and defaults.
The example below shows how to use keyword arguments and defaults:
.. code-block:: python
import torch
from cutlass import cute
@cute.kernel
def device_add_scalar(a: cute.Tensor, b: cute.Tensor, offset: cutlass.Float32):
threads_per_block = 128
cta_x_, _, _ = cute.arch.block_idx()
tid_x, _, _ = cute.arch.thread_idx()
tid = cta_x_ * threads_per_block + tid_x
if tid < a.shape[0]:
b[tid] = a[tid] + offset
@cute.jit
def add_constant(a: cute.Tensor, b: cute.Tensor, offset: cutlass.Float32=cutlass.Float32(1)):
n = a.shape[0]
threads_per_block = 128
blocks = (n + threads_per_block - 1) // threads_per_block
device_add_scalar(a, b, offset).launch(grid=(blocks, 1, 1), block=(threads_per_block, 1, 1))
def example_kwargs_and_defaults():
n = cute.sym_int()
a_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
b_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
compiled_add_constant = cute.compile(add_constant, a_cute, b_cute, options="--enable-tvm-ffi")
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.empty(10, dtype=torch.float32, device="cuda")
compiled_add_constant(a_torch, b_torch)
print("result of b_torch after compiled_add_constant(a_torch, b_torch)")
print(b_torch)
compiled_add_constant(a_torch, b_torch, offset=4)
print("result of b_torch after compiled_add_constant(a_torch, b_torch, offset=4)")
print(b_torch)
For efficiency and portability reasons, TVM FFI ABI supports functions with positional-only arguments.
If you export the compiled module to an object file and then load it back, the function
will only accept positional arguments in the order of the arguments in the function signature.
You can rewrap the function or use the TVM FFI wrapper generator to generate a kwargs wrapper.
The code block below shows how to do this:
.. code-block:: python
def example_kwargs_and_defaults():
n = cute.sym_int()
a_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
b_cute = cute.runtime.make_fake_compact_tensor(cute.Float32, (n,))
compiled_add_constant = cute.compile(add_constant, a_cute, b_cute, options="--enable-tvm-ffi")
# export the compiled module to object file
compiled_add_constant.export_to_c("./add_constant.o", function_name="add_constant")
# obtain necessary runtime libs for loading the shared library
runtime_libs = cute.runtime.find_runtime_libraries(enable_tvm_ffi=True)
# compile the object file to a shared library
cmd = ["gcc", "-shared", "-o", "./add_constant.so", "./add_constant.o", *runtime_libs]
subprocess.run(cmd, check=True)
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.empty(10, dtype=torch.float32, device="cuda")
mod = cute.runtime.load_module("./add_constant.so")
try:
mod.add_constant(a_torch, b_torch)
except Exception as e:
# Raises a missing arguments error because kwargs and default information are lost
print(e)
# We rewrap the function to regain argument and kwargs support.
# Alternatively, use the TVM FFI wrapper generator to generate a kwargs wrapper function.
from tvm_ffi.utils import kwargs_wrapper
# arg_defaults are aligned to the end of the argument list
wrapped_func = kwargs_wrapper.make_kwargs_wrapper(
mod.add_constant, arg_names=["a", "b", "offset"], arg_defaults=(1,)
)
wrapped_func(a_torch, b_torch)
print("result of b_torch after wrapped_func(a_torch, b_torch)")
print(b_torch)
# You can also use the signature of the original function
# to generate a kwargs wrapper function. Make sure to exclude
# arguments that are not included in the runtime,
# such as 'self', constexpr, and env stream arguments.
wrapped_func = kwargs_wrapper.make_kwargs_wrapper_from_signature(
mod.add_constant, signature=inspect.signature(add_constant),
exclude_arg_names=["self"]
)
wrapped_func(a_torch, b_torch, offset=4)
print("result of b_torch after wrapped_func(a_torch, b_torch, offset=4)")
print(b_torch)
@@ -128,7 +128,7 @@ Here is an example demonstrating automatic caching of the ``add`` kernel:
The cache can be serialized to files for subsequent runs. The cache can be serialized to files for subsequent runs.
After serialization, compiled MLIR bytecode is stored in file. After serialization, compiled MLIR bytecode is stored in file.
The cache directory is ``/tmp/{current_user}/cutlass_python_cache``. The cache directory is ``/tmp/{current_user}/cutlass_python_cache``.
The cache loads from files into memory during |DSL| initialization and saves back to files when the process exits. During compilation, the cache loads the corresponding kernel from file (if it exists) into memory as needed, and after compilation, it saves any newly compiled executables back to file.
Note that for efficiency, the default cache directory is located in a temporary folder. However, this location is not persistent, it may be cleared by the system (for example, during a reboot or disk space cleanup). Note that for efficiency, the default cache directory is located in a temporary folder. However, this location is not persistent, it may be cleared by the system (for example, during a reboot or disk space cleanup).
If you wish to preserve the cache across sessions, set the ``CUTE_DSL_CACHE_DIR`` environment variable to point to a persistent directory. If you wish to preserve the cache across sessions, set the ``CUTE_DSL_CACHE_DIR`` environment variable to point to a persistent directory.
@@ -140,9 +140,6 @@ The following environment variables control file caching:
# Disable file caching while keeping in-memory cache available, defaults to False. # Disable file caching while keeping in-memory cache available, defaults to False.
export CUTE_DSL_DISABLE_FILE_CACHING=True export CUTE_DSL_DISABLE_FILE_CACHING=True
# Maximum number of cache files allowed, defaults to 1000.
export CUTE_DSL_FILE_CACHING_CAPACITY=1000
# Cache directory, defaults to /tmp/{current_user}/cutlass_python_cache. # Cache directory, defaults to /tmp/{current_user}/cutlass_python_cache.
export CUTE_DSL_CACHE_DIR=/home/user/local_cutlass_python_cache/dense_gemm_cache/ export CUTE_DSL_CACHE_DIR=/home/user/local_cutlass_python_cache/dense_gemm_cache/
@@ -192,6 +192,11 @@ For example:
- For a tensor with layout ``(2,2):(8,2)``, since no dimension has stride 1, - For a tensor with layout ``(2,2):(8,2)``, since no dimension has stride 1,
all dimensions are marked as dynamic: ``(?,?):(?,?)``. all dimensions are marked as dynamic: ``(?,?):(?,?)``.
The leading dimension accepts negative index which means the dimension is counted from the last dimension. For example,
- For a tensor with layout ``(2,2,3,4):(2,1,4,12)``, if ``leading_dim`` is specified to be -1,
the layout will be marked as ``(?,?,?,?):(?,?,?,1)``.
Code Example Code Example
~~~~~~~~~~~~ ~~~~~~~~~~~~
+100 -62
View File
@@ -14,6 +14,8 @@ This module provides jit cache load/dump helper functions
""" """
import os import os
import io
import sys
import uuid import uuid
import random import random
import tempfile import tempfile
@@ -22,7 +24,7 @@ import time
from pathlib import Path from pathlib import Path
import hashlib import hashlib
from functools import lru_cache from functools import lru_cache
import tempfile import zlib
from .utils.logger import log from .utils.logger import log
from .jit_executor import JitCompiledFunction from .jit_executor import JitCompiledFunction
@@ -74,13 +76,58 @@ def get_default_file_dump_root():
return dump_root return dump_root
def load_ir(file, asBytecode=False): def write_bytecode_with_crc32(f, module):
"""Write the bytecode to the file and calculate the crc32 checksum.
:param f: The file to write the bytecode to.
:type f: file
:param module: The IR module to write the bytecode to.
:type module: object
"""
s = io.BytesIO()
module.operation.write_bytecode(s)
content = s.getvalue()
crc = zlib.crc32(content)
s.write(crc.to_bytes(4, sys.byteorder))
f.write(s.getvalue())
return
def read_bytecode_and_check_crc32(f):
"""
Read the bytecode from the file and check the crc32 checksum.
:param f: The file to read the bytecode with appended CRC32 from.
:type f: file
:return: The bytecode content if checksum matches.
:rtype: bytes
:raises DSLRuntimeError: If checksum does not match.
"""
content = f.read()
if len(content) < 4:
raise DSLRuntimeError(
f"File {f.name} does not contain enough data for CRC32 checksum."
)
bytecode = content[:-4]
crc_appended = content[-4:]
crc_appended_int = int.from_bytes(crc_appended, sys.byteorder)
crc_computed = zlib.crc32(bytecode)
if crc_appended_int != crc_computed:
raise DSLRuntimeError(
f"CRC32 checksum mismatch! Expected {crc_computed}, got {crc_appended_int}"
)
return ir.Module.parse(bytecode)
def load_ir(file, asBytecode=False, bytecode_reader=None):
"""Load generated IR from a file. """Load generated IR from a file.
:param file: The path to the file to load. :param file: The path to the file to load.
:type file: str :type file: str
:param asBytecode: Whether to load the IR as bytecode, defaults to False :param asBytecode: Whether to load the IR as bytecode, defaults to False
:type asBytecode: bool, optional :type asBytecode: bool, optional
:param bytecode_reader: The bytecode reader to use, defaults to None
:type bytecode_reader: callable, optional
:return: The function name and the IR module :return: The function name and the IR module
:rtype: tuple[str, ir.Module] :rtype: tuple[str, ir.Module]
""" """
@@ -88,8 +135,10 @@ def load_ir(file, asBytecode=False):
func_name = file.split(".mlir")[0].split("dsl_")[-1] func_name = file.split(".mlir")[0].split("dsl_")[-1]
with ir.Context() as ctx: with ir.Context() as ctx:
with open(file, "rb" if asBytecode else "r") as f: with open(file, "rb" if asBytecode else "r") as f:
module = ir.Module.parse(f.read()) if bytecode_reader:
module = bytecode_reader(f)
else:
module = ir.Module.parse(f.read())
return func_name, module return func_name, module
@@ -128,6 +177,14 @@ def save_ir(
:type module: object :type module: object
:param fname: The name of the file to save. :param fname: The name of the file to save.
:type fname: str :type fname: str
:param output_dir: The path to the output directory, defaults to None
:type output_dir: str, optional
:param as_bytecode: Whether to save the IR as bytecode, defaults to False
:type as_bytecode: bool, optional
:param bytecode_writer: The bytecode writer to use, defaults to None
:type bytecode_writer: callable, optional
:return: The path to the saved file
:rtype: str
""" """
initial_name = f"{dsl_name.lower()}_{fname}.mlir" initial_name = f"{dsl_name.lower()}_{fname}.mlir"
save_path = Path(output_dir if output_dir else tempfile.gettempdir()) save_path = Path(output_dir if output_dir else tempfile.gettempdir())
@@ -158,63 +215,45 @@ def save_ir(
return save_fname return save_fname
def check_func_name(jit_cache, func_name): def load_cache_from_path(
"""Check if the function name is in the cache. dsl_name, file, path=default_generated_ir_path, bytecode_reader=None
If not, create a new JitCompiledFunction object and add it to the cache. ):
:param jit_cache: The cache to check.
:type jit_cache: dict
:param func_name: The name of the function to check.
:type func_name: str
:return: The cache
:rtype: dict
"""
if not func_name in jit_cache:
jit_cache[func_name] = JitCompiledFunction(
None, None, None, None, None, [], False, None
)
return jit_cache
def load_cache_from_path(dsl_name, cache_limit, path=default_generated_ir_path):
"""Load cache from a directory path. """Load cache from a directory path.
:param dsl_name: The name of the DSL. :param dsl_name: The name of the DSL.
:type dsl_name: str :type dsl_name: str
:param cache_limit: The limit of the cache. :param file: The name of the file to load.
:type cache_limit: int :type file: str
:param path: The path to the cache directory, defaults to default_generated_ir_path :param path: The path to the cache directory, defaults to default_generated_ir_path
:type path: str, optional :type path: str, optional
:param bytecode_reader: The bytecode reader to use, defaults to None
:type bytecode_reader: callable, optional
:return: The cache :return: The cache
:rtype: dict :rtype: dict
""" """
if not os.path.exists(path): if not os.path.exists(path):
return dict() return None
files = os.listdir(path) ret = None
jit_cache = dict()
try: try:
for idx, file in enumerate(files): file = f"{dsl_name.lower()}_{file}.mlir"
if idx >= int(cache_limit): if os.path.exists(os.path.join(path, file)):
break _, module = load_ir(
# identify dsl prefix os.path.join(path, file),
if not file.startswith(f"{dsl_name.lower()}"): asBytecode=True,
continue bytecode_reader=bytecode_reader,
if ".mlir" in file: )
func_name, ir_module = load_ir( ret = JitCompiledFunction(module, None, None, None, None, [], False, None)
os.path.join(path, file), asBytecode=True
)
jit_cache = check_func_name(jit_cache, func_name)
jit_cache[func_name].ir_module = ir_module
except Exception as e: except Exception as e:
print(f"{dsl_name} failed with loading generated IR cache.", e) log().warning(
jit_cache = dict() f"{dsl_name} failed with loading generated IR cache for {file}.", e
return jit_cache )
return ret
def dump_cache_to_path( def dump_cache_to_path(
dsl_name, dsl_name,
jit_cache, jit_function,
cache_limit, file,
path=default_generated_ir_path, path=default_generated_ir_path,
bytecode_writer=None, bytecode_writer=None,
): ):
@@ -222,30 +261,29 @@ def dump_cache_to_path(
:param dsl_name: The name of the DSL. :param dsl_name: The name of the DSL.
:type dsl_name: str :type dsl_name: str
:param jit_cache: The cache to dump. :param jit_function: The JitCompiledFunction to dump.
:type jit_cache: dict :type jit_function: JitCompiledFunction
:param cache_limit: The limit of the cache. :param file: The name of the file to dump.
:type cache_limit: int :type file: str
:param path: The path to the cache directory, defaults to default_generated_ir_path :param path: The path to the cache directory, defaults to default_generated_ir_path
:type path: str, optional :type path: str, optional
:param bytecode_writer: The bytecode writer to use, defaults to None :param bytecode_writer: The bytecode writer to use, defaults to None
:type bytecode_writer: callable, optional :type bytecode_writer: callable, optional
""" """
log().info("JIT cache : dumping [%s] items=[%s]", dsl_name, len(jit_cache)) log().info("JIT cache : dumping [%s] file=[%s]", dsl_name, file)
if not path: if not path:
path = default_generated_ir_path path = default_generated_ir_path
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
try: try:
for idx, [key, value] in enumerate(jit_cache.items()): save_ir(
if idx >= int(cache_limit): dsl_name,
break jit_function.ir_module,
save_ir( file,
dsl_name, output_dir=path,
value.ir_module, as_bytecode=True,
key, bytecode_writer=bytecode_writer,
output_dir=path, )
as_bytecode=True,
bytecode_writer=bytecode_writer,
)
except Exception as e: except Exception as e:
print(f"{dsl_name} failed with caching generated IR", e) log().warning(
f"{dsl_name} failed with dumping generated IR cache for {file}: {e}"
)
@@ -195,6 +195,10 @@ def _get_friendly_cuda_error_message(error_code, error_name):
f"2. SM ARCH setting", f"2. SM ARCH setting",
f"3. Steps to reproduce", f"3. Steps to reproduce",
), ),
"cudaErrorInsufficientDriver": (
f"1. Run nvidia-smi to confirm CUDA driver version",
f"2. Ensure the CUDA driver version meets the requirement of the installed cuda-python package",
),
} }
message = f"{error_name} (error code: {error_code}) \n" \ message = f"{error_name} (error code: {error_code}) \n" \
+22 -14
View File
@@ -318,11 +318,8 @@ class BaseDSL:
self.envar = self._env_class(self.name) self.envar = self._env_class(self.name)
self.enable_preprocessor = preprocess self.enable_preprocessor = preprocess
# This cache uses hash of original ir and env as key, allows dump/load to/from file. Enabled by default # This cache uses hash of original ir and env as key, allows dump/load to/from file. Enabled by default
self.jit_cache = ( self.jit_cache = dict()
dict()
if self.envar.disable_file_caching
else load_cache_from_path(self.name, self.envar.file_caching_capacity)
)
self.host_jit_decorator_name = f"@{BaseDSL.jit.__name__}" self.host_jit_decorator_name = f"@{BaseDSL.jit.__name__}"
self.device_jit_decorator_name = f"@{BaseDSL.kernel.__name__}" self.device_jit_decorator_name = f"@{BaseDSL.kernel.__name__}"
@@ -372,12 +369,6 @@ class BaseDSL:
atexit.register(restore_excepthook, origin_excepthook) atexit.register(restore_excepthook, origin_excepthook)
def dump_cache(self, path=None):
if not self.envar.disable_file_caching:
dump_cache_to_path(
self.name, self.jit_cache, self.envar.file_caching_capacity, path=path
)
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def print_warning_once(self, message): def print_warning_once(self, message):
log().warning(f"Warning: {message}") log().warning(f"Warning: {message}")
@@ -392,9 +383,6 @@ class BaseDSL:
def _get_dsl(cls): def _get_dsl(cls):
# Instantiate the DSL Class once # Instantiate the DSL Class once
main_dsl = cls() main_dsl = cls()
if not main_dsl.no_cache:
# register atexit callback
atexit.register(main_dsl.dump_cache)
return main_dsl return main_dsl
@staticmethod @staticmethod
@@ -1235,6 +1223,16 @@ class BaseDSL:
log().debug(f"Using pipeline = {pipeline}") log().debug(f"Using pipeline = {pipeline}")
shared_libs = self.get_shared_libs() shared_libs = self.get_shared_libs()
profiler = timer(enable=self.envar.jit_time_profiling) profiler = timer(enable=self.envar.jit_time_profiling)
# try load the file cache
load_from_file_cache = False
if not no_cache:
fn = load_cache_from_path(
self.name, module_hash, bytecode_reader=read_bytecode_and_check_crc32
)
if fn is not None:
load_from_file_cache = True
self.jit_cache[module_hash] = fn
if ( if (
no_cache no_cache
or module_hash not in self.jit_cache or module_hash not in self.jit_cache
@@ -1288,6 +1286,16 @@ class BaseDSL:
if not no_cache: if not no_cache:
# module stored in cache is compiled. # module stored in cache is compiled.
self.jit_cache[module_hash] = fn self.jit_cache[module_hash] = fn
# write through the file cache if enabled.
if not self.envar.disable_file_caching and not load_from_file_cache:
dump_cache_to_path(
self.name,
fn,
module_hash,
bytecode_writer=lambda f: write_bytecode_with_crc32(
f, fn.ir_module
),
)
return fn return fn
@@ -311,7 +311,6 @@ class EnvironmentVarManager(LogEnvironmentManager):
- [DSL_NAME]_ENABLE_OPTIMIZATION_WARNINGS: Enable warnings of optimization warnings (default: False) - [DSL_NAME]_ENABLE_OPTIMIZATION_WARNINGS: Enable warnings of optimization warnings (default: False)
- [DSL_NAME]_JIT_TIME_PROFILING: Whether or not to profile the IR generation/compilation/execution time (default: False) - [DSL_NAME]_JIT_TIME_PROFILING: Whether or not to profile the IR generation/compilation/execution time (default: False)
- [DSL_NAME]_DISABLE_FILE_CACHING: Disable file caching (default: False) - [DSL_NAME]_DISABLE_FILE_CACHING: Disable file caching (default: False)
- [DSL_NAME]_FILE_CACHING_CAPACITY: Limits the number of the cache save/load files (default: 1000)
- [DSL_NAME]_LIBS: Path to dependent shared libraries (default: None) - [DSL_NAME]_LIBS: Path to dependent shared libraries (default: None)
- [DSL_NAME]_ENABLE_TVM_FFI: Enable TVM-FFI or not (default: False) - [DSL_NAME]_ENABLE_TVM_FFI: Enable TVM-FFI or not (default: False)
""" """
@@ -350,9 +349,6 @@ class EnvironmentVarManager(LogEnvironmentManager):
self.disable_file_caching = get_bool_env_var( self.disable_file_caching = get_bool_env_var(
f"{prefix}_DISABLE_FILE_CACHING", False f"{prefix}_DISABLE_FILE_CACHING", False
) )
self.file_caching_capacity = get_int_env_var(
f"{prefix}_FILE_CACHING_CAPACITY", 1000
)
# set cuda # set cuda
self.cuda_toolkit = get_cuda_toolkit_path() self.cuda_toolkit = get_cuda_toolkit_path()
@@ -27,17 +27,21 @@ from typing import Union
cubin_suffix = "cubin" cubin_suffix = "cubin"
def get_export_module(ir_module: ir.Module, symbol_prefix: str): def get_export_module(ir_module: ir.Module, symbol_prefix: str, *, preserve_symbols = None):
"""Get the export module which is cloned from the original compiled ir module, and add the prefix """Get the export module which is cloned from the original compiled ir module, and add the prefix
to avoid the symbol conflict. to avoid the symbol conflict.
@param ir_module: The original compiled ir module. Comes from the JitCompiledFunction.ir_module. @param ir_module: The original compiled ir module. Comes from the JitCompiledFunction.ir_module.
@param symbol_prefix: The prefix name of the function. This is the unique identifier name of the function to avoid symbol conflict in the generated object file. @param symbol_prefix: The prefix name of the function. This is the unique identifier name of the function to avoid symbol conflict in the generated object file.
@param preserve_symbols: Optional symbols to preserve in the export module.
@return: The export module of the function. @return: The export module of the function.
""" """
# Add prefix for symbol names to avoid conflict with other functions # Add prefix for symbol names to avoid conflict with other functions
defined_symbols = set() defined_symbols = set()
if preserve_symbols is None:
preserve_symbols = set()
def walk_llvm_func_op(op): def walk_llvm_func_op(op):
# not a declaration # not a declaration
if ( if (
@@ -45,23 +49,61 @@ def get_export_module(ir_module: ir.Module, symbol_prefix: str):
and len(op.opview.operation.regions) > 0 and len(op.opview.operation.regions) > 0
and len(op.opview.operation.regions[0].blocks) > 0 and len(op.opview.operation.regions[0].blocks) > 0
): ):
defined_symbols.add(op.attributes["sym_name"].value) func_name = op.attributes["sym_name"].value
# skip preserving symbols
if func_name in preserve_symbols:
return ir.WalkResult.ADVANCE
defined_symbols.add(func_name)
op.attributes["sym_name"] = ir.StringAttr.get( op.attributes["sym_name"] = ir.StringAttr.get(
symbol_prefix + "_" + op.attributes["sym_name"].value symbol_prefix + "_" + func_name
) )
return ir.WalkResult.ADVANCE return ir.WalkResult.ADVANCE
def walk_llvm_call_op(op): def walk_llvm_references(op):
# Rename function calls
if op.name == "llvm.call" and op.attributes["callee"].value in defined_symbols: if op.name == "llvm.call" and op.attributes["callee"].value in defined_symbols:
op.attributes["callee"] = ir.FlatSymbolRefAttr.get( op.attributes["callee"] = ir.FlatSymbolRefAttr.get(
symbol_prefix + "_" + op.attributes["callee"].value symbol_prefix + "_" + op.attributes["callee"].value
) )
# Rename addressof references
elif op.name == "llvm.mlir.addressof" and op.attributes["global_name"].value in defined_symbols:
op.attributes["global_name"] = ir.FlatSymbolRefAttr.get(
symbol_prefix + "_" + op.attributes["global_name"].value
)
# Rename global_ctors references
elif op.name == "llvm.mlir.global_ctors" and "ctors" in op.attributes:
ctors = list(op.attributes["ctors"])
renamed_ctors = []
for ctor in ctors:
if ctor.value in defined_symbols:
renamed_ctors.append(ir.FlatSymbolRefAttr.get(
symbol_prefix + "_" + ctor.value
))
else:
renamed_ctors.append(ctor)
if renamed_ctors:
op.attributes["ctors"] = ir.ArrayAttr.get(renamed_ctors)
# Rename global_dtors references
elif op.name == "llvm.mlir.global_dtors" and "dtors" in op.attributes:
dtors = list(op.attributes["dtors"])
renamed_dtors = []
for dtor in dtors:
if dtor.value in defined_symbols:
renamed_dtors.append(ir.FlatSymbolRefAttr.get(
symbol_prefix + "_" + dtor.value
))
else:
renamed_dtors.append(dtor)
if renamed_dtors:
op.attributes["dtors"] = ir.ArrayAttr.get(renamed_dtors)
return ir.WalkResult.ADVANCE return ir.WalkResult.ADVANCE
with ir.Context(): with ir.Context():
export_module = ir.Module.parse(str(ir_module)) export_module = ir.Module.parse(str(ir_module))
# First pass: collect and rename function definitions
export_module.operation.walk(walk_llvm_func_op) export_module.operation.walk(walk_llvm_func_op)
export_module.operation.walk(walk_llvm_call_op) # Second pass: rename call and addressof references
export_module.operation.walk(walk_llvm_references)
return export_module return export_module
@@ -16,7 +16,7 @@ This module provides jit executor related classes
import ctypes import ctypes
import inspect import inspect
import io import io
from typing import Union, Optional from typing import Union, Optional, NamedTuple, Any, Sequence
import weakref import weakref
import threading import threading
import collections import collections
@@ -132,6 +132,15 @@ def load_kernels_from_ir_module(module, kernel_info) -> list[CudaModuleAndKernel
return list(kernel_modules.values()) return list(kernel_modules.values())
class KwargsWrapperSpec(NamedTuple):
"""A specification for keyword arguments wrapper."""
arg_names: list[str]
arg_defaults: tuple[Any, ...]
kwonly_names: list[str]
kwonly_defaults: dict[str, Any]
class ExecutionArgs: class ExecutionArgs:
"""Helper that wraps the function signature spec to filter exeuction and compile time arguments.""" """Helper that wraps the function signature spec to filter exeuction and compile time arguments."""
@@ -216,6 +225,59 @@ class ExecutionArgs:
return exe_args, adapted_args return exe_args, adapted_args
def get_kwargs_wrapper_spec(self, exclude_arg_names: Sequence[str] = ()) -> KwargsWrapperSpec:
"""
This function is used to get the kwargs wrapper spec from the original args_spec.
"""
excluded_arg_names = set(exclude_arg_names)
arg_spec = self.original_args_spec
if arg_spec.defaults:
defaults_start_idx = len(arg_spec.args) - len(arg_spec.defaults)
else:
defaults_start_idx = len(arg_spec.args)
arg_names = []
arg_defaults = []
kwonly_names = []
kwonly_defaults = {}
# Filter arguments and maintain their properties
for i, arg_name in enumerate(arg_spec.args):
arg_type = arg_spec.annotations.get(arg_name, None)
# Skip compile-time arguments
if is_arg_spec_constexpr(arg_type, arg_name, i, self.function_name):
continue
if arg_name in excluded_arg_names:
continue
arg_names.append(arg_name)
if i >= defaults_start_idx:
arg_defaults.append(arg_spec.defaults[i - defaults_start_idx])
if arg_spec.kwonlyargs:
for i, kwarg in enumerate(arg_spec.kwonlyargs):
arg_type = arg_spec.annotations.get(kwarg, None)
# Skip compile-time arguments
if is_arg_spec_constexpr(arg_type, kwarg, i, self.function_name):
continue
if kwarg in excluded_arg_names:
continue
kwonly_names.append(kwarg)
if arg_spec.kwonlydefaults and kwarg in arg_spec.kwonlydefaults:
kwonly_defaults[kwarg] = arg_spec.kwonlydefaults[kwarg]
return KwargsWrapperSpec(
arg_names=arg_names,
arg_defaults=tuple(arg_defaults),
kwonly_names=kwonly_names,
kwonly_defaults=kwonly_defaults,
)
def get_rectified_args_from_original_args(self, full_args, full_kwargs): def get_rectified_args_from_original_args(self, full_args, full_kwargs):
""" """
This function is used to rectify the original arguments to the runtime This function is used to rectify the original arguments to the runtime
@@ -233,6 +295,7 @@ class ExecutionArgs:
defaults_start_idx = len(arg_spec.args) defaults_start_idx = len(arg_spec.args)
runtime_args = [] runtime_args = []
# Filter arguments and maintain their properties # Filter arguments and maintain their properties
for i, arg_name in enumerate(arg_spec.args): for i, arg_name in enumerate(arg_spec.args):
arg_type = arg_spec.annotations.get(arg_name, None) arg_type = arg_spec.annotations.get(arg_name, None)
@@ -241,12 +304,24 @@ class ExecutionArgs:
if is_arg_spec_constexpr(arg_type, arg_name, i, self.function_name): if is_arg_spec_constexpr(arg_type, arg_name, i, self.function_name):
continue continue
# Keep corresponding default if it exists # Check if argument was provided by user, otherwise use default
if i >= defaults_start_idx: if i < len(full_args):
# User provided this argument - use it
runtime_args.append(full_args[i])
elif i >= defaults_start_idx:
# Argument not provided, but has default - use default
default_idx = i - defaults_start_idx default_idx = i - defaults_start_idx
runtime_args.append(arg_spec.defaults[default_idx]) runtime_args.append(arg_spec.defaults[default_idx])
else: else:
runtime_args.append(full_args[i]) # Required argument missing
raise DSLRuntimeError(
f"Missing required argument '{arg_name}' at position {i}",
context={
"function_name": self.function_name,
"expected_args": len(arg_spec.args),
"provided_args": len(full_args),
}
)
# Filter keyword-only arguments # Filter keyword-only arguments
runtime_kwargs = {} runtime_kwargs = {}
@@ -21,6 +21,7 @@ import os
import ctypes import ctypes
import cuda.bindings.driver as cuda import cuda.bindings.driver as cuda
import cuda.bindings.runtime as cudart
import cuda.bindings.nvrtc as nvrtc import cuda.bindings.nvrtc as nvrtc
# Local module imports # Local module imports
@@ -44,6 +45,8 @@ def _cudaGetErrorEnum(error):
if isinstance(error, cuda.CUresult): if isinstance(error, cuda.CUresult):
err, name = cuda.cuGetErrorName(error) err, name = cuda.cuGetErrorName(error)
return name if err == cuda.CUresult.CUDA_SUCCESS else "<unknown>" return name if err == cuda.CUresult.CUDA_SUCCESS else "<unknown>"
elif isinstance(error, cudart.cudaError_t):
return cudart.cudaGetErrorName(error)[1]
elif isinstance(error, nvrtc.nvrtcResult): elif isinstance(error, nvrtc.nvrtcResult):
return nvrtc.nvrtcGetErrorString(error)[1] return nvrtc.nvrtcGetErrorString(error)[1]
else: else:
@@ -38,6 +38,7 @@ class MLIRTypeBuilder:
self.gpu_ptr_type = llvm.PointerType.get(address_space=1) self.gpu_ptr_type = llvm.PointerType.get(address_space=1)
# did not find a programmatic way to get the void type # did not find a programmatic way to get the void type
self.void_type = ir.Type.parse("!llvm.void") self.void_type = ir.Type.parse("!llvm.void")
self.llvm_internal_linkage = ir.Attribute.parse("#llvm.linkage<internal>")
def ptr_type_with_address_space( def ptr_type_with_address_space(
self, address_space: Optional[int] = None self, address_space: Optional[int] = None
@@ -374,8 +375,10 @@ class MLIRBuilder(MLIRTypeBuilder):
params_type: Sequence[ir.Type], params_type: Sequence[ir.Type],
ret_type: ir.Type, ret_type: ir.Type,
internal: bool = False, internal: bool = False,
llvm_func_attrs: Sequence[str] = (),
) -> tuple[list[ir.Value], ir.Block]: ) -> tuple[list[ir.Value], ir.Block]:
"""Create a function with the given signature.""" """Create a function with the given signature.
"""
func_op = llvm.func( func_op = llvm.func(
name, name,
function_type=self.as_attr( function_type=self.as_attr(
@@ -383,10 +386,16 @@ class MLIRBuilder(MLIRTypeBuilder):
), ),
) )
if internal: if internal:
func_op.attributes["llvm.linkage"] = ir.StringAttr.get("private") func_op.attributes["linkage"] = self.llvm_internal_linkage
else: else:
func_op.attributes["llvm.emit_c_interface"] = ir.UnitAttr.get() func_op.attributes["llvm.emit_c_interface"] = ir.UnitAttr.get()
# Add LLVM function attributes via passthrough
if llvm_func_attrs:
func_op.attributes["passthrough"] = ir.ArrayAttr.get(
[ir.StringAttr.get(attr) for attr in llvm_func_attrs]
)
params = [] params = []
func_body: Any = func_op.body func_body: Any = func_op.body
if func_body is not None: if func_body is not None:
@@ -668,12 +668,14 @@ class TVMFFIBuilder(MLIRBuilder):
param_types.append(self.ptr_type) # p0, p1, ..., pN-1 param_types.append(self.ptr_type) # p0, p1, ..., pN-1
# Create the helper function # Create the helper function
# Mark as noinline since error handling is a slow path and benefits from not inlining
with ir.InsertionPoint(self.module.body): # type: ignore[union-attr] with ir.InsertionPoint(self.module.body): # type: ignore[union-attr]
params, entry_block = self.function( params, entry_block = self.function(
name=helper_name, name=helper_name,
params_type=param_types, params_type=param_types,
ret_type=self.void_type, ret_type=self.void_type,
internal=True, internal=True,
llvm_func_attrs=["noinline"],
) )
kind_param = params[0] kind_param = params[0]
@@ -1244,12 +1246,7 @@ class TVMFFIFunctionBuilder(TVMFFIBuilder):
else: else:
assert isinstance(var, int) assert isinstance(var, int)
with ir.InsertionPoint(current_block): with ir.InsertionPoint(current_block):
if not skip_cast_and_check: expected_value = self.i64(var)
expected_value = self.i64(var)
else:
expected_value = self.downcast_i64_to_lower_bits(
self.i64(var), var.dtype
)
error_msg_mismatch = [ error_msg_mismatch = [
error_prefix_mismatch, error_prefix_mismatch,
@@ -1983,13 +1980,21 @@ class TVMFFIFunctionBuilder(TVMFFIBuilder):
) )
# decode parameters to populate the matched var binding # decode parameters to populate the matched var binding
for arg_index, param in enumerate(params_list): # Track the actual FFI argument index separately from parameter index
# since some parameters (like EnvStream) are not passed as FFI arguments
ffi_arg_index = 0
for param in params_list:
# Skip EnvStream parameters as they are not in the FFI args array
if isinstance(param, spec.EnvStream):
continue
arg_context = ArgContext( arg_context = ArgContext(
param_name=param.name, param_name=param.name,
arg_index=arg_index, arg_index=ffi_arg_index,
tuple_indices=[], tuple_indices=[],
) )
current_block = self.decode_param(current_block, param, args, arg_index, arg_context) current_block = self.decode_param(current_block, param, args, ffi_arg_index, arg_context)
ffi_arg_index += 1
with ir.InsertionPoint(current_block): with ir.InsertionPoint(current_block):
env_stream = self.find_env_stream(params_list) env_stream = self.find_env_stream(params_list)
@@ -2047,7 +2052,9 @@ def attach_ffi_func(
builder.attach_ffi_func(symbol_name, params, call_provider, fn_display_name) builder.attach_ffi_func(symbol_name, params, call_provider, fn_display_name)
def rename_tvm_ffi_function(module: ir.Module, old_name: str, new_name: str) -> None: def rename_tvm_ffi_function(
module: ir.Module, old_name: str, new_name: str,
) -> None:
"""Rename the TVM FFI function in the module. """Rename the TVM FFI function in the module.
Parameters Parameters
@@ -19,6 +19,7 @@ from .typing import Tensor, Pointer, SymInt
from .typing import ( from .typing import (
Numeric, Numeric,
Boolean, Boolean,
Integer,
Int4, Int4,
Int8, Int8,
Uint8, Uint8,
@@ -42,7 +43,17 @@ from .typing import (
) )
import cuda.bindings.driver as cuda import cuda.bindings.driver as cuda
from typing import List, Dict, Any, Optional, Tuple, get_origin, get_args from typing import (
List,
Dict,
Any,
Optional,
Tuple,
get_origin,
get_args,
get_type_hints,
)
from types import UnionType
import inspect import inspect
NumericToTVMFFIDtype = { NumericToTVMFFIDtype = {
@@ -91,6 +102,7 @@ def _get_llvm_address_space_from_memspace(
return 1 return 1
return None return None
def _is_gpu_memspace( def _is_gpu_memspace(
memspace: _cute_ir.AddressSpace, memspace: _cute_ir.AddressSpace,
) -> bool: ) -> bool:
@@ -108,7 +120,6 @@ class SymIntId:
return self.sym_int is other.sym_int return self.sym_int is other.sym_int
class ConverterContext: class ConverterContext:
"""Context for managing variable allocation during TVM FFI args conversion.""" """Context for managing variable allocation during TVM FFI args conversion."""
@@ -145,7 +156,9 @@ class ConverterContext:
self.sym_int_id_mapping[sym_int_id] = var self.sym_int_id_mapping[sym_int_id] = var
return var return var
def alloc_or_reuse_device_id(self, device_type: str, vdevice_id: int) -> Optional[spec.Var]: def alloc_or_reuse_device_id(
self, device_type: str, vdevice_id: int
) -> Optional[spec.Var]:
"""Allocate or reuse a device_id variable for a given virtual device. """Allocate or reuse a device_id variable for a given virtual device.
This function returns None for CPU tensors. This function returns None for CPU tensors.
@@ -166,10 +179,7 @@ class ConverterContext:
def _convert_single_arg( def _convert_single_arg(
arg, arg, arg_name: str, arg_type, ctx: ConverterContext
arg_name: str,
arg_type,
ctx: ConverterContext
) -> spec.Param: ) -> spec.Param:
"""Convert a single argument to a spec.Param. """Convert a single argument to a spec.Param.
@@ -191,7 +201,7 @@ def _convert_single_arg(
""" """
if arg is None: if arg is None:
return spec.ConstNone(arg_name) return spec.ConstNone(arg_name)
elif (isinstance(arg, Numeric) and arg.dtype in AcceptableNumericTypesForScalar): elif isinstance(arg, Numeric) and arg.dtype in AcceptableNumericTypesForScalar:
return spec.Var(arg_name, NumericToTVMFFIDtype[arg.dtype]) return spec.Var(arg_name, NumericToTVMFFIDtype[arg.dtype])
elif arg_type in AcceptableNumericTypesForScalar: elif arg_type in AcceptableNumericTypesForScalar:
return spec.Var(arg_name, NumericToTVMFFIDtype[arg_type]) return spec.Var(arg_name, NumericToTVMFFIDtype[arg_type])
@@ -201,9 +211,13 @@ def _convert_single_arg(
if isinstance(arg[i], int): if isinstance(arg[i], int):
shape.append(arg[i]) shape.append(arg[i])
elif isinstance(arg[i], SymInt): elif isinstance(arg[i], SymInt):
shape.append(ctx.alloc_or_reuse_symint_var(arg[i], ctx.alloc_shape_name)) shape.append(
ctx.alloc_or_reuse_symint_var(arg[i], ctx.alloc_shape_name)
)
else: else:
shape.append(spec.Var(ctx.alloc_shape_name(), NumericToTVMFFIDtype[arg[i].dtype])) shape.append(
spec.Var(ctx.alloc_shape_name(), NumericToTVMFFIDtype[arg[i].dtype])
)
return spec.Shape(arg_name, shape) return spec.Shape(arg_name, shape)
elif isinstance(arg, Tensor): elif isinstance(arg, Tensor):
shapes = [] shapes = []
@@ -211,16 +225,22 @@ def _convert_single_arg(
if not dyn_mask: if not dyn_mask:
shapes.append(arg.shape[i]) shapes.append(arg.shape[i])
elif isinstance(arg.shape[i], SymInt): elif isinstance(arg.shape[i], SymInt):
shapes.append(ctx.alloc_or_reuse_symint_var(arg.shape[i], ctx.alloc_shape_name)) shapes.append(
ctx.alloc_or_reuse_symint_var(arg.shape[i], ctx.alloc_shape_name)
)
else: else:
shapes.append(spec.Var(ctx.alloc_shape_name(), NumericToTVMFFIDtype[Int32])) shapes.append(
spec.Var(ctx.alloc_shape_name(), NumericToTVMFFIDtype[Int32])
)
strides = [] strides = []
for i, dyn_mask in enumerate(arg.dynamic_strides_mask): for i, dyn_mask in enumerate(arg.dynamic_strides_mask):
if not dyn_mask: if not dyn_mask:
strides.append(arg.stride[i]) strides.append(arg.stride[i])
elif isinstance(arg.stride[i], SymInt): elif isinstance(arg.stride[i], SymInt):
strides.append(ctx.alloc_or_reuse_symint_var(arg.stride[i], ctx.alloc_stride_name)) strides.append(
ctx.alloc_or_reuse_symint_var(arg.stride[i], ctx.alloc_stride_name)
)
else: else:
if hasattr(arg, "_use_32bit_stride") and arg._use_32bit_stride: if hasattr(arg, "_use_32bit_stride") and arg._use_32bit_stride:
dtype = NumericToTVMFFIDtype[Int32] dtype = NumericToTVMFFIDtype[Int32]
@@ -243,7 +263,7 @@ def _convert_single_arg(
strides=strides, strides=strides,
data_alignment=arg._assumed_align, data_alignment=arg._assumed_align,
device_type=device_type, device_type=device_type,
device_id=device_id device_id=device_id,
) )
else: else:
# for FakeTensor, strictly follow the shape and stride from the cute tensor # for FakeTensor, strictly follow the shape and stride from the cute tensor
@@ -259,7 +279,7 @@ def _convert_single_arg(
strides=strides, strides=strides,
data_alignment=arg._assumed_align, data_alignment=arg._assumed_align,
device_type=device_type, device_type=device_type,
device_id=device_id device_id=device_id,
) )
if arg.element_type == Float4E2M1FN: if arg.element_type == Float4E2M1FN:
tvm_ffi_cute_tensor = spec.create_map_tensor_dtype_f4x2_to_f4_spec( tvm_ffi_cute_tensor = spec.create_map_tensor_dtype_f4x2_to_f4_spec(
@@ -278,11 +298,38 @@ def _convert_single_arg(
return spec.Stream(arg_name) return spec.Stream(arg_name)
elif isinstance(arg, cuda.CUstream): elif isinstance(arg, cuda.CUstream):
return spec.Stream(arg_name) return spec.Stream(arg_name)
elif arg_type is not None and hasattr(arg_type, "_fields"):
# Handle NamedTuple - normalize to Tuple by order of fields, ignoring defaults
# Get field types from annotations
type_hints = get_type_hints(arg_type)
tuple_element_types = [type_hints[field] for field in arg_type._fields]
# NamedTuples inherit from tuple, so we can check with isinstance(arg, tuple)
if not isinstance(arg, tuple):
raise DSLRuntimeError(
f"Expected namedtuple for argument {arg_name}, got {type(arg)}"
)
if len(arg) != len(tuple_element_types):
raise DSLRuntimeError(
f"NamedTuple length mismatch for argument {arg_name}: "
f"expected {len(tuple_element_types)}, got {len(arg)}"
)
# Recursively convert each tuple element
tuple_params = []
for i, (elem, elem_type) in enumerate(zip(arg, tuple_element_types)):
elem_name = f"{arg_name}[{i}]"
elem_param = _convert_single_arg(elem, elem_name, elem_type, ctx)
tuple_params.append(elem_param)
return spec.TupleParam(arg_name, tuple_params)
elif arg_type is not None and get_origin(arg_type) is tuple: elif arg_type is not None and get_origin(arg_type) is tuple:
# Handle Tuple[X, Y, ...] type annotations # Handle Tuple[X, Y, ...] type annotations
tuple_element_types = get_args(arg_type) tuple_element_types = get_args(arg_type)
if not isinstance(arg, (tuple, list)): if not isinstance(arg, (tuple, list)):
raise DSLRuntimeError(f"Expected tuple for argument {arg_name}, got {type(arg)}") raise DSLRuntimeError(
f"Expected tuple for argument {arg_name}, got {type(arg)}"
)
if len(arg) != len(tuple_element_types): if len(arg) != len(tuple_element_types):
raise DSLRuntimeError( raise DSLRuntimeError(
f"Tuple length mismatch for argument {arg_name}: " f"Tuple length mismatch for argument {arg_name}: "
@@ -297,8 +344,24 @@ def _convert_single_arg(
tuple_params.append(elem_param) tuple_params.append(elem_param)
return spec.TupleParam(arg_name, tuple_params) return spec.TupleParam(arg_name, tuple_params)
elif isinstance(arg, (tuple, list)):
# Handle plain tuple type annotation without explicit element types
# Recursively convert each tuple element with None as elem_type (un-annotated)
tuple_params = []
for i, elem in enumerate(arg):
elem_name = f"{arg_name}[{i}]"
elem_param = _convert_single_arg(elem, elem_name, None, ctx)
tuple_params.append(elem_param)
return spec.TupleParam(arg_name, tuple_params)
elif isinstance(arg, int):
# in cute.compile, unannotated const int is converted to int32
return spec.Var(arg_name, NumericToTVMFFIDtype[Int32])
elif isinstance(arg, float):
return spec.Var(arg_name, NumericToTVMFFIDtype[Float32])
else: else:
raise DSLRuntimeError(f"Unsupported argument type: {type(arg)}") raise DSLRuntimeError(
f"Unsupported argument type: {type(arg)} for annotated type: {get_origin(arg_type)}"
)
def _tvm_ffi_args_spec_converter( def _tvm_ffi_args_spec_converter(
@@ -312,17 +375,24 @@ def _tvm_ffi_args_spec_converter(
This function converts the cute arguments specs to tvm ffi spec params. This function converts the cute arguments specs to tvm ffi spec params.
""" """
exec_args = ExecutionArgs(args_spec, function_name) exec_args = ExecutionArgs(args_spec, function_name)
rectified_args = exec_args.get_rectified_args_from_original_args(full_args, full_kwargs) rectified_args = exec_args.get_rectified_args_from_original_args(
full_args, full_kwargs
)
arg_names = exec_args.args_spec.args + exec_args.args_spec.kwonlyargs arg_names = exec_args.args_spec.args + exec_args.args_spec.kwonlyargs
params = [] params = []
ctx = ConverterContext() ctx = ConverterContext()
wrapper_extra_exclude_arg_names = []
for arg, arg_name in zip(rectified_args, arg_names): for arg, arg_name in zip(rectified_args, arg_names):
arg_type = args_spec.annotations.get(arg_name, None) arg_type = args_spec.annotations.get(arg_name, None)
param = _convert_single_arg(arg, arg_name, arg_type, ctx) param = _convert_single_arg(arg, arg_name, arg_type, ctx)
params.append(param) params.append(param)
if isinstance(param, spec.EnvStream):
return params wrapper_extra_exclude_arg_names.append(arg_name)
kwargs_wrapper_spec = exec_args.get_kwargs_wrapper_spec(
wrapper_extra_exclude_arg_names
)
return params, kwargs_wrapper_spec
def attach_args_spec_converter(): def attach_args_spec_converter():
@@ -28,8 +28,9 @@ from ..base_dsl.jit_executor import (
JitFunctionArtifacts, JitFunctionArtifacts,
) )
from ..base_dsl.utils.logger import log from ..base_dsl.utils.logger import log
from ..base_dsl.common import DSLCudaRuntimeError, DSLRuntimeError from ..base_dsl.common import DSLRuntimeError
from ..base_dsl.typing import Int32 from ..base_dsl.typing import Int32
from ..base_dsl.runtime.cuda import checkCudaErrors
class CudaDialectJitModule: class CudaDialectJitModule:
"""Holds the execution engine and cuda libraries.""" """Holds the execution engine and cuda libraries."""
@@ -113,10 +114,7 @@ class CudaDialectJitCompiledFunction(JitCompiledFunction):
@functools.cached_property @functools.cached_property
def num_devices(self): def num_devices(self):
"""Returns the number of CUDA devices available.""" """Returns the number of CUDA devices available."""
dev_err, devs = cuda_runtime.cudaGetDeviceCount() return checkCudaErrors(cuda_runtime.cudaGetDeviceCount())
if dev_err != cuda_runtime.cudaError_t.cudaSuccess:
raise DSLCudaRuntimeError(dev_err, cuda_runtime.cudaGetErrorName(dev_err))
return devs
def _deserializer(self): def _deserializer(self):
"""Load the cuda library from the binary execution engine. """Load the cuda library from the binary execution engine.
@@ -148,12 +146,7 @@ class CudaDialectJitCompiledFunction(JitCompiledFunction):
packed_args[i] = ctypes.cast(cuda_init_args[i], ctypes.c_void_p) packed_args[i] = ctypes.cast(cuda_init_args[i], ctypes.c_void_p)
cuda_init(packed_args) cuda_init(packed_args)
if err.value != 0: checkCudaErrors((cuda_runtime.cudaError_t(err.value),))
error_code = err.value
error_name = cuda_runtime.cudaGetErrorName(
cuda_runtime.cudaError_t(error_code)
)
raise DSLCudaRuntimeError(error_code, error_name)
cuda_load_args = [pointer_to_library, pointer_to_err] cuda_load_args = [pointer_to_library, pointer_to_err]
packed_args = (ctypes.c_void_p * len(cuda_load_args))() packed_args = (ctypes.c_void_p * len(cuda_load_args))()
@@ -161,12 +154,7 @@ class CudaDialectJitCompiledFunction(JitCompiledFunction):
packed_args[i] = ctypes.cast(cuda_load_args[i], ctypes.c_void_p) packed_args[i] = ctypes.cast(cuda_load_args[i], ctypes.c_void_p)
cuda_load(packed_args) cuda_load(packed_args)
if err.value != 0: checkCudaErrors((cuda_runtime.cudaError_t(err.value),))
error_code = err.value
error_name = cuda_runtime.cudaGetErrorName(
cuda_runtime.cudaError_t(error_code)
)
raise DSLCudaRuntimeError(error_code, error_name)
return [cuda_runtime.cudaLibrary_t(library.value)] return [cuda_runtime.cudaLibrary_t(library.value)]
@@ -229,12 +217,7 @@ class CudaDialectJitCompiledFunction(JitCompiledFunction):
packed_args[i] = ctypes.cast(cuda_init_args[i], ctypes.c_void_p) packed_args[i] = ctypes.cast(cuda_init_args[i], ctypes.c_void_p)
cuda_init(packed_args) cuda_init(packed_args)
if err.value != 0: checkCudaErrors((cuda_runtime.cudaError_t(err.value),))
error_code = err.value
error_name = cuda_runtime.cudaGetErrorName(
cuda_runtime.cudaError_t(error_code)
)
raise DSLCudaRuntimeError(error_code, error_name)
device_id = ctypes.c_int32(0) device_id = ctypes.c_int32(0)
pointer_to_device_id = ctypes.pointer(device_id) pointer_to_device_id = ctypes.pointer(device_id)
@@ -247,18 +230,9 @@ class CudaDialectJitCompiledFunction(JitCompiledFunction):
for dev in range(self.num_devices): for dev in range(self.num_devices):
device_id.value = dev device_id.value = dev
cuda_load_to_device(packed_args) cuda_load_to_device(packed_args)
if err.value != 0: checkCudaErrors((cuda_runtime.cudaError_t(err.value),))
raise DSLCudaRuntimeError(
err.value,
cuda_runtime.cudaGetErrorName(cuda_runtime.cudaError_t(err.value)),
)
if err.value != 0: checkCudaErrors((cuda_runtime.cudaError_t(err.value),))
error_code = err.value
error_name = cuda_runtime.cudaGetErrorName(
cuda_runtime.cudaError_t(error_code)
)
raise DSLCudaRuntimeError(error_code, error_name)
return [cuda_runtime.cudaLibrary_t(library.value)] return [cuda_runtime.cudaLibrary_t(library.value)]
+13 -3
View File
@@ -43,9 +43,9 @@ from ..base_dsl.dsl import is_dynamic_expression, extract_mlir_values
from ..base_dsl.typing import * from ..base_dsl.typing import *
from ..base_dsl.typing import DynamicExpression, get_mlir_types from ..base_dsl.typing import DynamicExpression, get_mlir_types
from ..base_dsl.runtime.jit_arg_adapters import is_arg_spec_constexpr from ..base_dsl.runtime.jit_arg_adapters import is_arg_spec_constexpr
from ..base_dsl.jit_executor import ExecutionArgs
from ..base_dsl.runtime import cuda as cuda_helpers from ..base_dsl.runtime import cuda as cuda_helpers
from .cuda_stream_adapter import CudaDialectStreamAdapter from .cuda_stream_adapter import CudaDialectStreamAdapter
from .cuda_jit_executor import CudaDialectJitCompiledFunction from .cuda_jit_executor import CudaDialectJitCompiledFunction
# MLIR Imports # MLIR Imports
@@ -421,12 +421,13 @@ class CutlassBaseDSL(BaseDSL):
# attach extra ABI function to the MLIR module # attach extra ABI function to the MLIR module
from .tvm_ffi_provider import ( from .tvm_ffi_provider import (
TVMFFIJitCompiledFunction, TVMFFIJitCompiledFunction,
TVMFFIJitCompiledFunctionWithKwargs,
TVMFFICuteCallProvider, TVMFFICuteCallProvider,
) )
from cutlass.base_dsl.tvm_ffi_builder import attach_ffi_func from cutlass.base_dsl.tvm_ffi_builder import attach_ffi_func
assert self._tvm_ffi_args_spec_converter is not None assert self._tvm_ffi_args_spec_converter is not None
tvm_ffi_spec_params = self._tvm_ffi_args_spec_converter( tvm_ffi_spec_params, kwargs_wrapper_spec = self._tvm_ffi_args_spec_converter(
function_name, args_spec, full_args, full_kwargs function_name, args_spec, full_args, full_kwargs
) )
tvm_ffi_provider = TVMFFICuteCallProvider(function_name) tvm_ffi_provider = TVMFFICuteCallProvider(function_name)
@@ -444,6 +445,15 @@ class CutlassBaseDSL(BaseDSL):
) )
module.operation.verify() module.operation.verify()
def _make_compiled_func(*args, **kwargs):
if kwargs_wrapper_spec.kwonly_names or kwargs_wrapper_spec.arg_defaults:
return TVMFFIJitCompiledFunctionWithKwargs(
*args, **kwargs,
kwargs_wrapper_spec=kwargs_wrapper_spec
)
else:
return TVMFFIJitCompiledFunction(*args, **kwargs)
# ensure the compiler can run post-compile hook after its passes # ensure the compiler can run post-compile hook after its passes
# the context will restore the previous post-compile hook after it exits # the context will restore the previous post-compile hook after it exits
with compiler.PostCompileHookContext( with compiler.PostCompileHookContext(
@@ -456,7 +466,7 @@ class CutlassBaseDSL(BaseDSL):
pipeline, pipeline,
args_spec, args_spec,
no_cache, no_cache,
TVMFFIJitCompiledFunction, _make_compiled_func,
full_args=full_args, full_args=full_args,
full_kwargs=full_kwargs, full_kwargs=full_kwargs,
dynamic_args=dynamic_args, dynamic_args=dynamic_args,
@@ -15,12 +15,14 @@ from cutlass.base_dsl.tvm_ffi_builder import (
rename_tvm_ffi_function, rename_tvm_ffi_function,
spec, spec,
) )
from cutlass.base_dsl.export import get_export_module
from cutlass._mlir import ir from cutlass._mlir import ir
from cutlass._mlir.dialects import llvm from cutlass._mlir.dialects import llvm
from cutlass._mlir._mlir_libs._cutlass_ir import _execution_engine_extra from cutlass._mlir._mlir_libs._cutlass_ir import _aot_support
from cutlass.cutlass_dsl.cuda_jit_executor import CudaDialectJitCompiledFunction from cutlass.cutlass_dsl.cuda_jit_executor import CudaDialectJitCompiledFunction
from cutlass.base_dsl.common import DSLRuntimeError from cutlass.base_dsl.common import DSLRuntimeError
from typing import Optional from cutlass.base_dsl.jit_executor import ExecutionArgs
from typing import Optional, Callable
import tvm_ffi import tvm_ffi
@@ -400,41 +402,52 @@ class TVMFFICuteCallProvider(DynamicParamPackCallProvider):
return current_block return current_block
class TVMFFIJitCompiledFunction(tvm_ffi.Function, CudaDialectJitCompiledFunction): def _inplace_hide_symbols(ir_module: ir.Module, hide_check: Callable[[str], bool]):
"""TVM FFI Function that contains metadata of the compiled function and interface to the FFI layer. """Walk through the IRModule, hide functions that do not yet have linkage set.
This function should not be directly used after @param ir_module: The ir module to hide the symbols.
@param hide_check: The callback to check if the symbol should be hidden.
@return: The ir module with the symbols hidden.
""" """
defined_symbols = set()
def walk_llvm_func_op(op):
# not a declaration
if (
op.name == "llvm.func"
and len(op.opview.operation.regions) > 0
and len(op.opview.operation.regions[0].blocks) > 0
):
func_name = op.attributes["sym_name"].value
defined_symbols.add(func_name)
return ir.WalkResult.ADVANCE
def walk_and_hide_symbols(op):
# Handle llvm.func operations
if op.name == "llvm.func":
func_name = op.attributes["sym_name"].value
# Only set linkage if it doesn't already have one
if func_name in defined_symbols and hide_check(func_name):
# Set to internal linkage to hide the symbol
op.attributes["linkage"] = ir.Attribute.parse("#llvm.linkage<internal>")
return ir.WalkResult.ADVANCE
with ir_module.context:
ir_module.operation.walk(walk_llvm_func_op)
ir_module.operation.walk(walk_and_hide_symbols)
def _get_format_from_object_file_path(object_file_path: str) -> str:
format = object_file_path.split(".")[-1]
if format not in ("o", "ll", "bc"):
return "o"
return format
class TVMFFIJitCompiledFunctionBase(CudaDialectJitCompiledFunction):
"""Base class for TVM FFI compiled function."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# initialize the tvm_ffi.Function from the current execution engine
self._init_ffi_function()
# use direct call to the tvm_ffi.Function.__call__
# to avoid most of python overhead
__call__ = tvm_ffi.Function.__call__
def _init_ffi_function(self):
"""Initialize the tvm_ffi.Function from the current execution engine.
This function must be called at once during compilation time.
The reason why it is not called during init is because the original
flow may already created an execution engine and the function is not
guaranteed to be initialized at that time.
"""
if self.__chandle__() != 0:
raise DSLRuntimeError("TVM FFI function is already initialized")
# get the MLIR function pointer from the execution engine
if self.engine is not None:
tvm_ffi_function_ptr = self.engine.raw_lookup(
"__tvm_ffi_" + self.function_name
)
tvm_ffi_function = tvm_ffi.Function.__from_mlir_packed_safe_call__(
tvm_ffi_function_ptr
)
# move the handle from the tvm_ffi.Function to the current instance
self.__move_handle_from__(tvm_ffi_function)
def to(self, device=None): def to(self, device=None):
"""TVM FFI function itself is already support all devices.""" """TVM FFI function itself is already support all devices."""
@@ -444,18 +457,111 @@ class TVMFFIJitCompiledFunction(tvm_ffi.Function, CudaDialectJitCompiledFunction
"""Run the compiled program. This override is needed for implicit compile and execution.""" """Run the compiled program. This override is needed for implicit compile and execution."""
return self.__call__(*exe_args) return self.__call__(*exe_args)
def export_to_c(self, object_file_path: str, function_name: str = None): def export_to_c(
self, object_file_path: str, function_name: str = None,
*,
enable_pic: bool = True,
export_only_tvm_ffi_symbols: bool = False
):
"""Export the TVM FFI function to an object file. """Export the TVM FFI function to an object file.
:param object_file_path: The path to the object file. :param object_file_path: The path to the object file.
:param function_name: The name of the function to export. :param function_name: The name of the function to export.
:param enable_pic: Whether to enable PIC relocation needed for shared library loading.
:param export_only_tvm_ffi_symbols: Only export TVM FFI symbols (hide all others).
:param host_target_triple: If not provided, the current host target is used.
""" """
if function_name is not None and function_name != self.function_name: # prefix internal function by function name
mod = self.ir_module internal_symbol_prefix = "__cute_internal_" + function_name
rename_tvm_ffi_function(mod, self.function_name, function_name) mod = self.ir_module
else: mod = get_export_module(
mod = self.ir_module self.ir_module, internal_symbol_prefix,
preserve_symbols=[f"__tvm_ffi_{self.function_name}"]
_execution_engine_extra.dump_object_file_pic(
mod, object_file_path, "__tvm_ffi_" + function_name, 2
) )
rename_tvm_ffi_function(mod, self.function_name, function_name)
if export_only_tvm_ffi_symbols:
_inplace_hide_symbols(mod, lambda x: not x.startswith("__tvm_ffi"))
format = _get_format_from_object_file_path(object_file_path)
out_bytes = _aot_support.export_module_to_bytes(
mod, format=format, opt_level=3, enable_pic=enable_pic
)
with open(object_file_path, "wb") as f:
f.write(out_bytes)
def _create_tvm_ffi_function(self):
"""Create the tvm_ffi.Function from the current execution engine.
"""
if self.engine is not None:
tvm_ffi_function_ptr = self.engine.raw_lookup(
"__tvm_ffi_" + self.function_name
)
tvm_ffi_function = tvm_ffi.Function.__from_mlir_packed_safe_call__(
tvm_ffi_function_ptr, keep_alive_object=self.engine)
return tvm_ffi_function
return None
class TVMFFIJitCompiledFunction(tvm_ffi.Function, TVMFFIJitCompiledFunctionBase):
"""TVM FFI Function that directly subclasses the tvm_ffi.Function for pos only arguments.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# initialize the tvm_ffi.Function from the current execution engine
if self.__chandle__() != 0:
raise DSLRuntimeError("TVM FFI function is already initialized")
tvm_ffi_function = self._create_tvm_ffi_function()
if tvm_ffi_function is not None:
# move the handle from the tvm_ffi.Function to the current instance
self.__move_handle_from__(tvm_ffi_function)
# use direct call to the tvm_ffi.Function.__call__
# to avoid most of python overhead
__call__ = tvm_ffi.Function.__call__
class TVMFFIJitCompiledFunctionWithKwargs(TVMFFIJitCompiledFunctionBase):
"""TVM FFI Function with kwargs wrapper support
"""
def __init__(self, *args, **kwargs):
assert "kwargs_wrapper_spec" in kwargs, "kwargs_wrapper_spec is required"
kwargs_wrapper_spec = kwargs.pop("kwargs_wrapper_spec")
super().__init__(*args, **kwargs)
# initialize the tvm_ffi.Function from the current execution engine
self._tvm_ffi_function = self._create_tvm_ffi_function()
if kwargs_wrapper_spec.kwonly_names or kwargs_wrapper_spec.arg_defaults:
try:
from tvm_ffi.utils import kwargs_wrapper # type: ignore
self._kwargs_wrapper = kwargs_wrapper.make_kwargs_wrapper(
self._tvm_ffi_function,
arg_names=kwargs_wrapper_spec.arg_names,
arg_defaults=kwargs_wrapper_spec.arg_defaults,
kwonly_names=kwargs_wrapper_spec.kwonly_names,
kwonly_defaults=kwargs_wrapper_spec.kwonly_defaults,
)
except ImportError:
raise DSLRuntimeError("install apache-tvm-ffi>=0.1.5 to enable kwargs/defaults")
else:
# positional only is probably fine
self._kwargs_wrapper = self._tvm_ffi_function
def __call__(self, *args, **kwargs):
"""Call the TVM FFI function with kwargs wrapper.
"""
return self._kwargs_wrapper(*args, **kwargs)
def __tvm_ffi_object__(self):
return self._tvm_ffi_function
def supports_kwargs_wrapper() -> bool:
"""Check if the kwargs wrapper is supported."""
try:
from tvm_ffi.utils import kwargs_wrapper # type: ignore
return True
except ImportError:
return False
+1 -1
View File
@@ -1,3 +1,3 @@
# Use `pip install -r requirements.txt` with the present file to install a # Use `pip install -r requirements.txt` with the present file to install a
# wheel consistent with the present state of the github repository # wheel consistent with the present state of the github repository
nvidia-cutlass-dsl==4.3.0 nvidia-cutlass-dsl==4.3.3
+1 -1
View File
@@ -133,7 +133,7 @@ def get_option_registry():
this._option_registry = OptionRegistry(device_cc()) this._option_registry = OptionRegistry(device_cc())
return this._option_registry return this._option_registry
this.__version__ = '4.3.2' this.__version__ = '4.3.3'
from cutlass_cppgen.backend import create_memory_pool from cutlass_cppgen.backend import create_memory_pool
from cutlass_cppgen.emit.pytorch import pytorch from cutlass_cppgen.emit.pytorch import pytorch
+1 -1
View File
@@ -51,7 +51,7 @@ setup_pycute.perform_setup()
setup( setup(
name='cutlass_cppgen', name='cutlass_cppgen',
version='4.3.2', version='4.3.3',
description='CUTLASS Pythonic Interface', description='CUTLASS Pythonic Interface',
package_dir={'': '.'}, package_dir={'': '.'},
packages=[ packages=[
+1 -1
View File
@@ -36,7 +36,7 @@ from setuptools import setup
def perform_setup(): def perform_setup():
setup( setup(
name='cutlass_library', name='cutlass_library',
version='4.3.2', version='4.2.1',
description='CUTLASS library generation scripts', description='CUTLASS library generation scripts',
packages=['cutlass_library'] packages=['cutlass_library']
) )
+1 -1
View File
@@ -36,7 +36,7 @@ from setuptools import setup
def perform_setup(): def perform_setup():
setup( setup(
name='pycute', name='pycute',
version='4.3.2', version='4.3.3',
description='Python implementation of CuTe', description='Python implementation of CuTe',
packages=['pycute'], packages=['pycute'],
) )