v4.5 tag update (#3202)

* Python DSL examples reorganization.

* v4.5 tag update.
This commit is contained in:
Junkai-Wu
2026-05-06 08:55:27 +08:00
committed by GitHub
parent f74fea9ce3
commit cb37157db5
351 changed files with 36688 additions and 8117 deletions

View File

@@ -0,0 +1,169 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
import os
from typing import Tuple
import cutlass
import cutlass.cute as cute
from cutlass.cute.runtime import make_ptr
"""
An Example demonstrating how to call off-the-shelf kernel by-passing dlpack protocol
The example shows how to directly pass pointers from PyTorch tensors to off-the-shelf kernels
written by CuTe DSL with a thin customized wrapper jit function. The jit function will be
compiled with inline without introducing overhead.
To run this example:
.. code-block:: bash
python examples/ampere/call_bypass_dlpack.py
It's worth to mention that by-passing dlpack protocol can resolve the issue that dlpack doesn't handle shape-1
mode correctly. For example, the following code will fail, because dlpack will convert the shape-1 mode
with stride-1 which propagate alignment incorrectly.
.. code-block:: python
@cute.kernel
def fails_kernel(gX: cute.Tensor):
bidx, _, _ = cute.arch.block_idx()
mX = gX[None, bidx, None] # We wish to retain alignment
# assert mX.iterator.alignment == 16
@cute.jit
def fails(gX_: cute.Tensor):
gX = gX_
fails_kernel(gX).launch(grid=(1, 1, 1), block=(128, 1, 1))
gX_torch = torch.rand((128, 1, 128), device="cuda", dtype=torch.bfloat16)
fails(from_dlpack(gX_torch, assumed_align=16))
"""
if __name__ == "__main__":
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(current_dir, ".."))
from cute.ampere.kernel.dense_gemm.tensorop_gemm import TensorOpGemm
@cute.jit
def tensor_op_gemm_wrapper(
a_ptr: cute.Pointer,
b_ptr: cute.Pointer,
c_ptr: cute.Pointer,
m: cutlass.Int32,
n: cutlass.Int32,
k: cutlass.Int32,
l: cutlass.Int32,
):
print("\n[DSL INFO] Input Parameters:")
print(f"[DSL INFO] mnkl: {(m, n, k, l)}")
# Assume alignment of shape to call tensorop_gemm example
m = cute.assume(m, divby=8)
n = cute.assume(n, divby=8)
# Torch is row major
a_layout = cute.make_ordered_layout((m, k, l), order=(0, 1, 2))
b_layout = cute.make_ordered_layout((n, k, l), order=(0, 1, 2))
c_layout = cute.make_ordered_layout((m, n, l), order=(1, 0, 2))
mA = cute.make_tensor(a_ptr, layout=a_layout)
mB = cute.make_tensor(b_ptr, layout=b_layout)
mC = cute.make_tensor(c_ptr, layout=c_layout)
print(f"[DSL INFO] mA: {mA}")
print(f"[DSL INFO] mB: {mB}")
print(f"[DSL INFO] mC: {mC}")
tensor_op_gemm = TensorOpGemm(
a_ptr.value_type, c_ptr.value_type, cutlass.Float32, (2, 2, 1)
)
print("\n[DSL INFO] Created TensorOpGemm instance")
print(f"[DSL INFO] Input dtype: {a_ptr.value_type}")
print(f"[DSL INFO] Output dtype: {c_ptr.value_type}")
print(f"[DSL INFO] Accumulation dtype: {cutlass.Float32}")
print(f"[DSL INFO] Atom layout: {(2, 2, 1)}")
# No need to compile inside jit function
tensor_op_gemm(mA, mB, mC)
print("\n[DSL INFO] Executed TensorOpGemm")
def run_tensor_op_gemm_wrapper(mnkl: Tuple[int, int, int, int]):
import torch
print("\nRunning TensorOpGemm test with:")
print(f"Tensor dimensions: {mnkl}")
# (M,K,L)
a = torch.randn(
mnkl[3], mnkl[2], mnkl[0], dtype=torch.float16, device="cuda"
).permute(2, 1, 0)
# (N,K,L)
b = torch.randn(
mnkl[3], mnkl[2], mnkl[1], dtype=torch.float16, device="cuda"
).permute(2, 1, 0)
# (N,M,L)
c = torch.randn(
mnkl[3], mnkl[0], mnkl[1], dtype=torch.float16, device="cuda"
).permute(1, 2, 0)
print("Input tensor shapes:")
print(f"a: {a.shape}, dtype: {a.dtype}")
print(f"b: {b.shape}, dtype: {b.dtype}")
print(f"c: {c.shape}, dtype: {c.dtype}\n")
a_ptr = make_ptr(
cutlass.Float16, a.data_ptr(), cute.AddressSpace.gmem, assumed_align=32
)
b_ptr = make_ptr(
cutlass.Float16, b.data_ptr(), cute.AddressSpace.gmem, assumed_align=32
)
c_ptr = make_ptr(
cutlass.Float16, c.data_ptr(), cute.AddressSpace.gmem, assumed_align=32
)
tensor_op_gemm_wrapper(a_ptr, b_ptr, c_ptr, *mnkl)
torch.cuda.synchronize()
ref = torch.einsum("mkl,nkl->mnl", a, b)
torch.testing.assert_close(c, ref, atol=1e-05, rtol=1e-05)
print("\n[DSL INFO] Results verified successfully!")
print(f"First few elements of result: \n{c[:3, :3, :3]}")
if __name__ == "__main__":
run_tensor_op_gemm_wrapper((512, 256, 128, 16))

View File

@@ -0,0 +1,260 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Demonstrating JIT GEMM Implementation with Static Shape Wrapper
This example illustrates how to invoke a JIT-compiled GEMM implementation through a wrapper function
with static shapes. It showcases the integration between PyTorch and CuTe tensors in a JIT context.
Key features demonstrated:
1. Seamless conversion between PyTorch and CuTe tensors using the JitArgument protocol
2. Integration of static shape GEMM operations within a JIT-compiled wrapper function
Core components:
- BufferWithLayout: Handles memory buffer management with configurable stride ordering
- tensor_op_gemm_wrapper: JIT-compiled entry point that orchestrates the GEMM operation
Usage:
.. code-block:: bash
python examples/ampere/call_from_jit.py
Default configuration:
- Batch dimension (L): 16
- Matrix dimensions: M=512, N=256, K=128
- Precision: Float16 inputs with Float32 accumulation
Requirements:
- CUDA-capable GPU
- PyTorch with CUDA support
"""
import os
import sys
from typing import Type, Tuple
import cutlass
import cutlass.cute as cute
from cutlass.cute.runtime import make_ptr
if __name__ == "__main__":
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(current_dir, ".."))
from cute.ampere.kernel.dense_gemm.tensorop_gemm import TensorOpGemm
class BufferWithLayout:
def __init__(self, ptr: cute.Pointer, stride_order: tuple[int, int, int]):
self.ptr = ptr
# static properties
self.stride_order = stride_order
def to_tensor(
self, shape: tuple[int, int, int], *, loc=None, ip=None
) -> cute.Tensor:
assert len(shape) == len(self.stride_order), (
f"Shape {shape} and stride_order {self.stride_order} must have the "
"same rank."
)
layout = cute.make_ordered_layout(shape, self.stride_order)
# permute (l, mn, k) -> (mn, k, l)
res = cute.make_tensor(self.ptr, cute.select(layout, mode=[1, 2, 0]))
return res
# Implement JitArgument Protocol and DynamicExpression Protocol
def __c_pointers__(self):
"""Get the C pointers for the underlying pointer.
This method is part of the JitArgument Protocol and returns the C pointers
from the underlying pointer object.
This is required for user to define a custom data type which can pass to JIT function.
When JIT compiled function is called, JIT executor will call this method to get raw pointers
to underlying data object.
Following condition must be satisfied:
len(__c_pointers__()) == len(__get_mlir_types__()) == len(__extract_mlir_values__())
:return: The C pointers from the underlying pointer object
:rtype: Any
"""
return self.ptr.__c_pointers__()
def __get_mlir_types__(self):
"""Get the MLIR types for the underlying pointer.
This method is part of the JitArgument Protocol and returns the MLIR types
used for compiler to generate code. It must match the type of the underlying pointers
returned by __c_pointers__().
:return: The MLIR types from the underlying pointer object
:rtype: Any
"""
return self.ptr.__get_mlir_types__()
def __extract_mlir_values__(self):
"""Extract MLIR values from the underlying pointer.
This method is part of the DynamicExpression Protocol and extracts MLIR values
from the underlying pointer object.
It is used by compiler to generate function call in MLIR to another JIT function.
It must match the types returned by __get_mlir_types__().
:return: The MLIR values extracted from the underlying pointer object
:rtype: Any
"""
return self.ptr.__extract_mlir_values__()
def __new_from_mlir_values__(self, values):
"""Create a new BufferWithLayout instance from MLIR values.
This method is part of the JitArgument & DynamicExpression Protocol and creates a new
BufferWithLayout instance with pointer initialized from the given MLIR values.
It is used by compiler to generate function body in MLIR called by JIT function.
It must match the types returned by __c_pointers__() and __get_mlir_types__().
code generator takes function arguments and reconstructs python object which is legal
inside function body.
:param values: MLIR values to initialize the underlying pointer
:type values: Any
:return: A new BufferWithLayout instance with pointer initialized from values
:rtype: BufferWithLayout
"""
return BufferWithLayout(
self.ptr.__new_from_mlir_values__(values), self.stride_order
)
@cute.jit
def tensor_op_gemm_wrapper(
buffer_a: BufferWithLayout,
buffer_b: BufferWithLayout,
buffer_c: BufferWithLayout,
mnkl: cutlass.Constexpr[tuple[int, int, int, int]],
acc_dtype: Type[cutlass.Numeric],
atom_layout_mnk: cutlass.Constexpr[tuple[int, int, int]],
):
print("\n[DSL INFO] Input Parameters:")
print(f"[DSL INFO] mnkl: {mnkl}")
print(f"[DSL INFO] buffer_a: {buffer_a}")
print(f"[DSL INFO] buffer_b: {buffer_b}")
print(f"[DSL INFO] buffer_c: {buffer_c}")
print(f"[DSL INFO] acc_dtype: {acc_dtype}")
print(f"[DSL INFO] atom_layout_mnk: {atom_layout_mnk}")
mA = buffer_a.to_tensor(cute.select(mnkl, mode=[3, 0, 2]))
mB = buffer_b.to_tensor(cute.select(mnkl, mode=[3, 1, 2]))
mC = buffer_c.to_tensor(cute.select(mnkl, mode=[3, 0, 1]))
print("\n[DSL INFO] Created Tensors:")
print(f"[DSL INFO] mA = {mA}")
print(f"[DSL INFO] mB = {mB}")
print(f"[DSL INFO] mC = {mC}")
tensor_op_gemm = TensorOpGemm(
buffer_a.ptr.value_type,
buffer_c.ptr.value_type,
acc_dtype,
atom_layout_mnk,
)
print("\n[DSL INFO] Created TensorOpGemm instance")
print(f"[DSL INFO] Input dtype: {buffer_a.ptr.value_type}")
print(f"[DSL INFO] Output dtype: {buffer_c.ptr.value_type}")
print(f"[DSL INFO] Accumulation dtype: {acc_dtype}")
print(f"[DSL INFO] Atom layout: {atom_layout_mnk}")
# No need to compile inside jit function
tensor_op_gemm(mA, mB, mC)
print("\n[DSL INFO] Executed TensorOpGemm")
def run_tensor_op_gemm_wrapper(mnkl: Tuple[int, int, int, int]):
import torch
from cutlass.torch import dtype as torch_dtype
print("\nRunning TensorOpGemm test with:")
print(f"Tensor dimensions: {mnkl}")
ab_dtype = cutlass.Float16
c_dtype = cutlass.Float16
a = torch.randn(
mnkl[3], mnkl[0], mnkl[2], dtype=torch_dtype(ab_dtype), device="cuda"
)
b = torch.randn(
mnkl[3], mnkl[1], mnkl[2], dtype=torch_dtype(ab_dtype), device="cuda"
)
c = torch.randn(
mnkl[3], mnkl[0], mnkl[1], dtype=torch_dtype(c_dtype), device="cuda"
)
print("Input tensor shapes:")
print(f"a: {a.shape}, dtype: {a.dtype}")
print(f"b: {b.shape}, dtype: {b.dtype}")
print(f"c: {c.shape}, dtype: {c.dtype}\n")
buffer_a = BufferWithLayout(
make_ptr(ab_dtype, a.data_ptr(), cute.AddressSpace.gmem, assumed_align=32),
(2, 1, 0),
)
buffer_b = BufferWithLayout(
make_ptr(ab_dtype, b.data_ptr(), cute.AddressSpace.gmem, assumed_align=32),
(2, 1, 0),
)
buffer_c = BufferWithLayout(
make_ptr(c_dtype, c.data_ptr(), cute.AddressSpace.gmem, assumed_align=32),
(2, 1, 0),
)
tensor_op_gemm_wrapper(
buffer_a,
buffer_b,
buffer_c,
mnkl, # pass shape as static value
# no stride passing
cutlass.Float32,
(2, 2, 1),
)
torch.cuda.synchronize()
ref = torch.einsum("lmk,lnk->lmn", a, b)
torch.testing.assert_close(c, ref, atol=1e-05, rtol=1e-05)
print("\n[DSL INFO] Results verified successfully!")
print(f"First few elements of result: \n{c[:3, :3, :3]}")
if __name__ == "__main__":
run_tensor_op_gemm_wrapper((512, 256, 128, 16))

View File

@@ -0,0 +1,627 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Cooperative Launch Example:
This module demonstrates CUDA Cooperative Launch functionality. It implements a
global barrier that synchronizes ALL threads across the entire GPU grid.
In traditional CUDA kernel launches, there is no guarantee that all thread blocks
will be resident on the GPU simultaneously. This means that thread blocks may
execute in waves (some finish before others start) and attempting to synchronize
across blocks can cause deadlock.
**Cooperative Launch** solves this by guaranteeing that all thread blocks launch
atomically and simultaneously.
For more details, see the CUDA Programming Guide official documentation:
https://docs.nvidia.com/cuda/cuda-programming-guide/04-special-topics/cooperative-groups.html#when-to-use-cudalaunchcooperativekernel
Cooperative Launch Limitations:
Cooperative launch has strict grid size constraints.
If you exceed this limit, cudaLaunchCooperativeKernel returns
cudaErrorCooperativeLaunchTooLarge.
This example demonstrates both a successful cooperative launch with a small grid
and an expected failure when exceeding the grid size limit.
Usage:
Run directly:
$ python cooperative_launch.py
This will:
1. Demonstrate expected failure with too many thread blocks
2. Successfully run a cooperative kernel with grid-wide barrier
3. Print confirmation that all threads synchronized successfully
"""
from typing import List, Optional
import cutlass
import cutlass.cute as cute
from cutlass._mlir import ir
from cutlass.cutlass_dsl import (
dsl_user_op, # Decorator for user-defined device operations
DSLCudaRuntimeError, # Exception type for CUDA runtime errors
extract_mlir_values, # Extract MLIR values from the object
new_from_mlir_values, # Create a new instance from MLIR values
)
# Function to check cuda errors
from cutlass.base_dsl.runtime.cuda import checkCudaErrors
# LLVM dialect for inline PTX assembly generation
from cutlass._mlir.dialects import llvm
# CUDA Python bindings for runtime API (memory allocation, synchronization, etc.)
import cuda.bindings.runtime as cuda_runtime
class GlobalBarrier:
"""
A grid-wide barrier for synchronizing ALL thread blocks on the GPU.
This class implements a cooperative barrier that enables grid-wide
synchronization. It requires cooperative launch to function correctly.
Design Overview:
The barrier uses a single 32-bit integer in global memory with the
following bit layout:
┌──────────────────────────────────────────────────────────────────┐
│ Bit 31 │ Bits 30-0 │
│ ────────── │ ───────────────────────────────────────────────────│
│ Phase Bit │ Arrival Counter (supports up to 2^31 - 1 blocks) │
└──────────────────────────────────────────────────────────────────┘
Capacity:
- Maximum thread blocks: 2^31 - 1 = 2,147,483,647 blocks
Memory Ordering:
The barrier uses specific memory ordering semantics:
- Release semantics on arrival (atom.add.release.gpu)
- Acquire semantics on wait (ld.global.acquire.gpu)
Usage Example:
Host-side setup:
>>> barrier_ptr = GlobalBarrier.allocate() # Allocate barrier memory
Device-side usage (inside a kernel):
>>> barrier = GlobalBarrier(barrier_ptr)
>>>
>>> # Do some work...
>>>
>>> barrier.arrive_and_wait() # Synchronize all blocks
>>>
>>> # All blocks proceed together after this point
Warning:
This barrier requires cooperative launch! Using it with a regular launch
can result in a deadlock because not all thread blocks may be resident
simultaneously.
"""
@staticmethod
def allocate() -> cute.runtime.Pointer:
"""
Allocate and initialize barrier memory on the GPU.
This function allocates device memory for the barrier.
It must be called before launching any kernel that uses the barrier.
"""
ptr = checkCudaErrors(cuda_runtime.cudaMalloc(4))
# This sets all 32 bits to 0:
# - Phase bit (bit 31) = 0
# - Counter (bits 30-0) = 0
checkCudaErrors(cuda_runtime.cudaMemset(ptr, 0, 4))
# Create a pointer with the following properties:
# - Type: Uint32 (32-bit unsigned integer)
# - Address: the allocated device pointer
# - Address Space: gmem (global memory)
barrier_ptr = cute.runtime.make_ptr(
cutlass.Uint32, # Element type
ptr, # Raw CUDA pointer
cute.AddressSpace.gmem, # Memory address space
)
return barrier_ptr
@staticmethod
def free(barrier_ptr: cute.Pointer):
"""
Free the barrier memory on the GPU.
This function frees the device memory for the barrier.
It must be called after the barrier is no longer needed.
"""
checkCudaErrors(cuda_runtime.cudaFree(barrier_ptr._pointer))
@dsl_user_op
def __init__(
self,
barrier_ptr: cute.Pointer,
*,
phase: Optional[cutlass.Uint32] = None,
is_leader: Optional[cutlass.Boolean] = None,
number_of_thread_blocks: Optional[cutlass.Uint32] = None,
loc=None,
ip=None,
):
"""
Initialize a GlobalBarrier instance on the device.
This constructor is called by each thread when the kernel
starts. It sets up the barrier state for this thread's participation
in grid-wide synchronization.
Each thread stores the following:
- A reference to the shared barrier memory
- Whether it's the leader thread of its block
- The current phase for barrier tracking
- The total number of thread blocks in the grid
"""
# The barrier is shared across ALL thread blocks, so it must be in
# global memory. Shared memory (smem) is block-local and wouldn't work.
if barrier_ptr.memspace != cute.AddressSpace.gmem:
raise ValueError(
"GlobalBarrier requires barrier_ptr to be in global memory (gmem)"
)
# Store barrier pointer reference
self.barrier_ptr = barrier_ptr
# Initialize phase tracking
# Phase starts at 0 for the first barrier, then alternates:
# First barrier: wait for phase 1
# Second barrier: wait for phase 0
# Third barrier: wait for phase 1
# ... and so on
if phase is not None:
self.phase = phase
else:
self.phase = cutlass.Uint32(0)
if is_leader is not None:
self.is_leader = is_leader
else:
# Determine if this thread is the block leader
# Get this thread's position within its block
tidx, tidy, tidz = cute.arch.thread_idx()
# Leader is the thread at position (0, 0, 0) in the block
# We use bitwise AND to combine the three conditions efficiently
self.is_leader = (
cutlass.Boolean(tidx == 0) # First in X dimension
& cutlass.Boolean(tidy == 0) # First in Y dimension
& cutlass.Boolean(tidz == 0) # First in Z dimension
)
if number_of_thread_blocks is not None:
self.number_of_thread_blocks = number_of_thread_blocks
else:
# Calculate total number of thread blocks in the grid
# Get grid dimensions (how many blocks in each dimension)
gidx, gidy, gidz = cute.arch.grid_dim()
# Total blocks = gridDim.x × gridDim.y × gridDim.z
# This is needed to know when ALL blocks have arrived
self.number_of_thread_blocks = cutlass.Uint32(gidx * gidy * gidz)
@dsl_user_op
@cute.jit
def arrive(self, *, loc=None, ip=None):
"""
Arrive at the barrier without waiting.
This signals that the calling thread block has reached the barrier
point, but does not wait for other blocks. Use this when you want
to overlap computation with barrier synchronization.
This method must be called by ALL threads in the block,
not just the leader. The internal block-level sync ensures all
threads in the block agree before the leader signals arrival.
"""
# Ensure ALL threads in this block have reached this point before
# the leader signals arrival. This is critical for correctness!
cute.arch.sync_threads(loc=loc, ip=ip)
# Only the leader thread performs atomic operations to minimize
# contention on the barrier memory location
if self.is_leader:
# Atomically increment the arrival counter by 1
# The atomic add returns the value before the add, so we add 1
# to get the current value after our arrival
barrier_value = (
self._increment_barrier(cutlass.Uint32(1), loc=loc, ip=ip) + 1
)
# Check if we're the last block to arrive
# Mask out the phase bit (bit 31) to get just the counter value
# Compare against total number of thread blocks
if (barrier_value & ~(1 << 31)) == self.number_of_thread_blocks:
# Flip phase and reset counter
# We add a value that simultaneously:
# 1. Flips bit 31 (adds 2^31)
# 2. Resets counter to 0 (subtracts N, where N was the count)
#
# Example with 8 blocks:
# Current: 0x00000008 (phase=0, counter=8)
# Add: 0x80000000 - 8 = 0x7FFFFFF8
# Result: 0x80000000 (phase=1, counter=0) ✓
#
# This works because we're doing modular arithmetic and the
# counter wraps correctly
self._increment_barrier(
cutlass.Uint32((1 << 31) - self.number_of_thread_blocks),
loc=loc,
ip=ip,
)
def _read_barrier(self, *, loc=None, ip=None) -> cutlass.Uint32:
"""
Read the barrier value with acquire memory semantics.
This is an internal method that reads the 32-bit barrier value from
global memory using GPU-scope acquire semantics.
Notes
-----
PTX Instruction:
Uses ld.global.acquire.gpu.b32 which is a:
- Global memory load (ld.global)
- With acquire semantics (.acquire)
- At GPU scope (.gpu) - visible across all thread blocks
- For 32-bit data (.b32)
Inline Assembly:
We use LLVM inline assembly because CuTe DSL may not have a direct
high-level API for acquire loads. The assembly string format:
- $0: Output operand (the loaded value)
- $1: Input operand (the address to load from)
"""
# Use inline PTX assembly for the acquire-semantics load
return cutlass.Uint32(
llvm.inline_asm(
# Return type: 32-bit unsigned integer
cutlass.Uint32.mlir_type,
# Input arguments: barrier pointer address
# We convert the pointer to an integer (64-bit address)
[self.barrier_ptr.toint().ir_value(loc=loc, ip=ip)],
# PTX instruction
"ld.global.acquire.gpu.b32 $0, [$1];",
# Constraint string
# "=r" : Output is a 32-bit register (write-only)
# "l" : Input is a 64-bit register (pointer address)
"=r,l",
# Assembly attributes
# Mark as having side effects
has_side_effects=True,
# No special stack alignment needed
is_align_stack=False,
# Use AT&T syntax (required for LLVM inline asm)
asm_dialect=llvm.AsmDialect.AD_ATT,
# MLIR location and insertion point
loc=loc,
ip=ip,
)
)
def _increment_barrier(
self, value: cutlass.Uint32, *, loc=None, ip=None
) -> cutlass.Uint32:
"""
Atomically increment the barrier with release memory semantics.
This is an internal method that performs an atomic add on the barrier
value using GPU-scope release semantics.
Notes
-----
PTX Instruction:
Uses atom.add.release.gpu.u32 which is a:
- Atomic operation (atom)
- Addition (.add)
- With release semantics (.release)
- At GPU scope (.gpu)
- For unsigned 32-bit integers (.u32)
Atomicity:
The atomic add is guaranteed to be indivisible - no other thread
can see a partial update or interleave with this operation.
Return Value:
Atomic operations return the OLD value, not the new value.
This is why the caller adds 1 to get the current count.
"""
# Atomic add using inline PTX assembly with release semantics
return cutlass.Uint32(
llvm.inline_asm(
# Return type: 32-bit unsigned integer (the old value)
cutlass.Uint32.mlir_type,
# Input arguments: (barrier address, value to add)
[
self.barrier_ptr.toint().ir_value(
loc=loc, ip=ip
), # Barrier address
value.ir_value(loc=loc, ip=ip), # Value to add
],
# PTX instruction
"atom.add.release.gpu.u32 $0, [$1], $2;",
# Constraint string
# "=r" : Output is a 32-bit register
# "l" : First input is 64-bit (pointer)
# "r" : Second input is 32-bit (value)
"=r,l,r",
# Assembly attributes
has_side_effects=True,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
# MLIR metadata
loc=loc,
ip=ip,
)
)
@dsl_user_op
@cute.jit
def wait(self, *, loc=None, ip=None):
"""
Wait for all thread blocks to arrive at the barrier.
This method blocks (spins) until all thread blocks have called
arrive() on the barrier. It does NOT signal arrival itself - use
arrive_and_wait() if you need to both arrive and wait.
IMPORTANT: This method MUST be called by ALL threads in the block.
The internal sync_threads ensures all threads proceed together.
Algorithm:
- Leader thread spins, reading barrier with acquire semantics
- Waits until phase bit matches expected value
- Block-level sync ensures all threads proceed together
- Update local phase tracking for next barrier
"""
# Leader thread: spin-wait for phase flip
if self.is_leader:
# Calculate expected phase (opposite of current phase)
# XOR with 1 flips: 0→1, 1→0
expected = self.phase ^ 1
# Initial read of barrier value
barrier_value = self._read_barrier(loc=loc, ip=ip)
# Spin loop: wait until phase matches expected
# Extract phase bit (bit 31)
# Compare against expected phase value
while (barrier_value >> 31) != expected:
# Keep reading barrier until phase flips
# The acquire semantics ensure memory ordering
barrier_value = self._read_barrier(loc=loc, ip=ip)
# Block-level synchronization
# Ensure all threads in the block wait for the leader to see the
# phase flip before any thread proceeds
cute.arch.sync_threads(loc=loc, ip=ip)
# Update phase for next barrier
# Flip local phase: 0→1 or 1→0
# This prepares for the next barrier synchronization
self.phase = self.phase ^ 1
@dsl_user_op
def arrive_and_wait(self, *, loc=None, ip=None):
"""
Arrive at the barrier AND wait for all other thread blocks.
This is the most common barrier operation - it combines arrive()
and wait() into a single call. All thread blocks will be synchronized
after this call returns.
IMPORTANT: This method MUST be called by ALL threads in the block.
Semantics
---------
Logically equivalent to:
>>> barrier.arrive() # Signal we've reached this point
>>> barrier.wait() # Wait for everyone else
"""
# Execute both phases: arrive then wait
self.arrive(loc=loc, ip=ip)
self.wait(loc=loc, ip=ip)
def __extract_mlir_values__(self) -> List[ir.Value]:
"""
Extract MLIR values from the GlobalBarrier instance.
"""
assert len(extract_mlir_values(self.barrier_ptr)) == 1
assert len(extract_mlir_values(self.is_leader)) == 1
assert len(extract_mlir_values(self.phase)) == 1
assert len(extract_mlir_values(self.number_of_thread_blocks)) == 1
return (
extract_mlir_values(self.barrier_ptr)
+ extract_mlir_values(self.is_leader)
+ extract_mlir_values(self.phase)
+ extract_mlir_values(self.number_of_thread_blocks)
)
def __new_from_mlir_values__(self, values: List[ir.Value]) -> "GlobalBarrier":
"""
Create a new GlobalBarrier instance from MLIR values.
"""
assert len(values) == 4, f"Expected 4 IR values, but got {len(values)}"
return GlobalBarrier(
barrier_ptr=new_from_mlir_values(self.barrier_ptr, [values[0]]),
is_leader=new_from_mlir_values(self.is_leader, [values[1]]),
phase=new_from_mlir_values(self.phase, [values[2]]),
number_of_thread_blocks=new_from_mlir_values(
self.number_of_thread_blocks, [values[3]]
),
)
@cute.kernel
def cooperative_kernel(barrier_ptr: cute.Pointer):
"""
Example kernel demonstrating cooperative launch with grid-wide barrier.
This kernel shows how to use the GlobalBarrier class to synchronize all
thread blocks in a grid. It performs 10 iterations, with a barrier
synchronization after each iteration.
Launch Requirements: This kernel MUST be launched with cooperative=True.
"""
# Initialize the barrier for this thread
# Each thread creates its own GlobalBarrier instance, all sharing the
# same underlying barrier_ptr in global memory
barrier = GlobalBarrier(barrier_ptr=barrier_ptr)
for i in range(10):
# Synchronize all thread blocks across the entire grid
# After this call, ALL blocks have completed iterations 0..i
barrier.arrive_and_wait()
# Get block and thread indices
bidx, bidy, bidz = cute.arch.block_idx()
tidx, tidy, tidz = cute.arch.thread_idx()
# Check if this is the leader block (first block in the grid)
leader_cluster = bidx == 0 and bidy == 0 and bidz == 0
# Check if this is the leader thread (first thread in the block)
leader_thread = tidx == 0 and tidy == 0 and tidz == 0
# Only the single leader thread of the leader block prints
if leader_cluster and leader_thread:
cute.printf("All threads arrived at barrier for the %dth iteration\n", i)
# =============================================================================
# KERNEL LAUNCH WRAPPERS
# =============================================================================
@cute.jit
def run_cooperative_kernel(barrier_ptr: cute.runtime.Pointer):
"""
Launch the cooperative kernel with a reasonable grid size.
This wrapper launches the cooperative_kernel with a grid of 8 thread blocks
(2×2×2), where each block contains 128 threads (32×2×2).
Notes: The cooperative=True flag is ESSENTIAL. It tells CUDA to:
- Verify the grid fits within hardware limits
- Launch all blocks atomically
"""
cooperative_kernel(barrier_ptr).launch(
grid=(2, 2, 2), # 8 thread blocks
block=(32, 2, 2), # 128 threads per block
cooperative=True, # Enable cooperative launch semantics
)
@cute.jit
def xfail_run_cooperative_kernel(barrier_ptr: cute.runtime.Pointer):
"""
Demonstrate cooperative launch failure with an oversized grid.
This wrapper intentionally launches with a grid that exceeds
the limits, demonstrating how cooperative launch fails.
This launch is expected to fail with cudaErrorCooperativeLaunchTooLarge.
This demonstrates proper error handling for cooperative launch.
See Also
--------
The main() function shows how to properly catch and handle this error.
"""
# Attempt to launch with way too many blocks
cooperative_kernel(barrier_ptr).launch(
grid=(10000, 1, 1), # 10,000 blocks
block=(1024, 1, 1), # 1,024 threads per block
cooperative=True, # Cooperative launch will reject this
)
if __name__ == "__main__":
# Initialize CUDA context
cutlass.cuda.initialize_cuda_context()
# Allocate barrier memory
# Allocate 4 bytes in device global memory for the barrier state
barrier_ptr = GlobalBarrier.allocate()
# Demonstrate expected failure (grid too large)
expectedly_failed = False
try:
# Attempt to launch with 10,000 blocks - this WILL fail
xfail_run_cooperative_kernel(barrier_ptr)
except DSLCudaRuntimeError as e:
# Verify we got the expected error code
assert (
e.error_code == cuda_runtime.cudaError_t.cudaErrorCooperativeLaunchTooLarge
)
expectedly_failed = True
finally:
# Ensure the failure actually happened (test validation)
assert expectedly_failed
# Run successful cooperative kernel
# Launch with a reasonable grid size that fits hardware constraints
run_cooperative_kernel(barrier_ptr)
# Synchronize and clean up
checkCudaErrors(cuda_runtime.cudaDeviceSynchronize())
# Free the barrier memory
GlobalBarrier.free(barrier_ptr)

View File

@@ -0,0 +1,98 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
"""
Dataclass Example: Passing Tensors via Frozen Dataclass
=======================================================
Demonstrates passing tensors from `@cute.jit` to `@cute.kernel`
using `@dataclass(frozen=True)`.
Uses fake tensors for compilation and TVM-FFI for efficient runtime dispatch.
To run:
.. code-block:: bash
python examples/python/CuTeDSL/cute/dataclass_immutable.py
"""
from dataclasses import dataclass
import cutlass
import cutlass.cute as cute
from cutlass import Int32
@dataclass(frozen=True)
class CopyParams:
"""
Dataclass with one static attribute and two tensors.
Attributes:
num_threads: Threads per block (static - used for loop unrolling)
src: Source tensor (dynamic)
dst: Destination tensor (dynamic)
"""
num_threads: int # static
src: cute.Tensor # dynamic
dst: cute.Tensor # dynamic
@cute.jit
def memcpy_jit(mSrc: cute.Tensor, mDst: cute.Tensor, num_threads: cutlass.Constexpr[int]):
"""Host function - passes tensors via dataclass."""
params = CopyParams(
num_threads=num_threads,
src=mSrc,
dst=mDst,
)
N = mSrc.shape[0]
num_blocks = cute.ceil_div(N, num_threads)
memcpy_kernel(params, N).launch(
grid=[num_blocks, 1, 1],
block=[num_threads, 1, 1],
)
@cute.kernel
def memcpy_kernel(params: CopyParams, N: Int32):
"""Device kernel - receives tensors via CopyParams dataclass."""
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
idx = bidx * params.num_threads + tidx
if idx < N:
params.dst[idx] = params.src[idx]
if __name__ == "__main__":
import torch
from cutlass.cute.runtime import make_fake_compact_tensor
n = cute.sym_int64()
# 1. Create fake tensors for compilation (no GPU memory allocated)
fake_src = make_fake_compact_tensor(cutlass.Float32, (n,), stride_order=(0,))
fake_dst = make_fake_compact_tensor(cutlass.Float32, (n,), stride_order=(0,))
# 2. Compile with --enable-tvm-ffi
compiled_fn = cute.compile(
memcpy_jit, fake_src, fake_dst, num_threads=128, options="--enable-tvm-ffi"
)
# 3. Create real torch tensors
src = torch.randn(1024, device="cuda", dtype=torch.float32)
dst = torch.zeros_like(src)
# 4. Run compiled kernel
compiled_fn(src, dst)
# 5. Verify dst == src
torch.cuda.synchronize()
torch.testing.assert_close(dst, src)
print("PASS")

View File

@@ -0,0 +1,145 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass.cute as cute
import cutlass
"""
Example of automatic shared memory size computation for configuring kernel launch
This example demonstrates how to let the DSL automatically set shared memory
size for a kernel launch rather explicitly configuring it at launch time,
provided that developers are using `SmemAllocator` for all allocations.
Usage:
python dynamic_smem_size.py # Show auto inference
"""
@cute.struct
class SharedData:
"""A struct to demonstrate shared memory allocation."""
values: cute.struct.MemRange[cutlass.Float32, 64] # 256 bytes
counter: cutlass.Int32 # 4 bytes
flag: cutlass.Int8 # 1 byte
@cute.kernel
def kernel_static():
"""
Example kernel that allocates shared memory.
The total allocation will be automatically calculated when smem=None.
"""
tidx, _, _ = cute.arch.block_idx()
if tidx == 0:
cute.printf("Running kernel_static")
allocator = cutlass.utils.SmemAllocator()
# Allocate various types of shared memory
shared_data = allocator.allocate(SharedData)
raw_buffer = allocator.allocate(512, byte_alignment=64)
int_array = allocator.allocate_array(element_type=cutlass.Int32, num_elems=128)
tensor_smem = allocator.allocate_tensor(
element_type=cutlass.Float16,
layout=cute.make_layout((32, 16)),
byte_alignment=16,
swizzle=None,
)
cute.printf("Kernel launch smem size: {}", cute.arch.dynamic_smem_size())
return
@cute.kernel
def kernel_no_smem():
"""
Example kernel that does not allocates shared memory.
The total allocation will be automatically calculated as 0 when smem=None.
"""
tidx, _, _ = cute.arch.block_idx()
if tidx == 0:
cute.printf("Running kernel_no_smem")
return
if __name__ == "__main__":
# Initialize CUDA context
cutlass.cuda.initialize_cuda_context()
print("Launching kernel with auto smem size. (launch config `smem=None`)")
# Compile the static example
@cute.jit
def launch_kernelno_smem():
kernel_no_smem().launch(
grid=(1, 1, 1),
block=(1, 1, 1),
)
# --------
print(f" > Run {kernel_no_smem.__name__}")
func = cute.compile(launch_kernelno_smem)
func()
cutlass.cuda.stream_sync(cutlass.cuda.default_stream())
@cute.jit
def launch_kernel_static():
kernel_static().launch(
grid=(1, 1, 1),
block=(1, 1, 1),
# smem=None
# auto infer launch kernel static smem usage
)
# --------
print(f" > Run {kernel_static.__name__} with sufficient smem")
func = cute.compile(launch_kernel_static)
func()
cutlass.cuda.stream_sync(cutlass.cuda.default_stream())
@cute.jit
def launch_kernel_static_insufficient():
kernel_static().launch(
grid=(1, 1, 1),
block=(1, 1, 1),
# launch kernel with static smem usage exceeds cfg
# show warning
smem=16,
)
# --------
print(f" > Run {kernel_static.__name__} with insufficient smem, show warning:")
func = cute.compile(launch_kernel_static_insufficient)
func()
cutlass.cuda.stream_sync(cutlass.cuda.default_stream())
print("PASS")

View File

@@ -0,0 +1,107 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass
import cutlass.cute as cute
import os
import subprocess
import cuda.bindings.driver as cuda
"""Example demonstrating how to export a CuTe function.
This example shows how to:
1. Compile a CuTe function
2. Export the compiled function to a object file and a C header file
3. Compile the object file to a shared library
To run this example:
.. code-block:: bash
# export the compiled function to a object file and a C header file
python cutlass_ir/compiler/python/examples/cute/export/export_to_c.py
"""
@cute.kernel
def print_tensor_kernel(a: cute.Tensor):
cute.printf("a: {}", a)
@cute.jit
def print_tensor(a: cute.Tensor, stream: cuda.CUstream):
print_tensor_kernel(a).launch(grid=(1, 1, 1), block=(1, 1, 1), stream=stream)
@cute.kernel
def add_one_kernel(a: cute.Tensor, b: cute.Tensor):
a[0] = b[0] + 1
@cute.jit
def add_one(a: cute.Tensor, b: cute.Tensor, stream: cuda.CUstream):
add_one_kernel(a, b).launch(grid=(1, 1, 1), block=(1, 1, 1), stream=stream)
def run():
from cutlass.cute.runtime import make_fake_compact_tensor
shape = (cute.SymInt(divisibility=16), cute.SymInt())
a = make_fake_compact_tensor(cutlass.Float32, shape, stride_order=(1, 0))
b = make_fake_compact_tensor(cutlass.Float32, shape, stride_order=(1, 0))
stream = cuda.CUstream(0)
compiled_print_tensor = cute.compile(print_tensor, a, stream=stream)
compiled_add_one = cute.compile(add_one, a, b, stream=stream)
os.makedirs("./build", exist_ok=True)
compiled_print_tensor.export_to_c(
file_path="./build",
file_name="print_tensor_example",
function_prefix="print_tensor",
)
compiled_add_one.export_to_c(
file_path="./build", file_name="add_one_example", function_prefix="add_one"
)
cc = os.environ.get("CC", "gcc")
# compile the object file to a shared library
cmd = [
cc,
"-shared",
"-o",
"./build/libexport_example.so",
"./build/print_tensor_example.o",
"./build/add_one_example.o",
]
subprocess.run(cmd, check=True)
if __name__ == "__main__":
run()

View File

@@ -0,0 +1,79 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass
import cutlass.cute as cute
import cuda.bindings.driver as cuda
"""Example demonstrating how to load a CuTe module/function in Python.
This example shows how to:
1. Load a CuTe module from a object file or a shared library
2. Extract the function from the module and call it in Python
To run this example:
.. code-block:: bash
# prerequesites: export the compiled functions to object files and compile them into a shared library
python examples/cute/export/export_to_c.py
# load the module from a object file or a shared library
python examples/cute/export/load_in_python.py
"""
def run():
import torch
from cutlass.cute.runtime import from_dlpack
a = (
torch.arange(16 * 10, dtype=torch.float32, device="cuda")
.reshape(16, 10)
.permute(1, 0)
)
b = torch.ones(16, 10, dtype=torch.float32, device="cuda").permute(1, 0)
a_cute = from_dlpack(a).mark_layout_dynamic()
b_cute = from_dlpack(b).mark_layout_dynamic()
stream = cuda.CUstream(0)
print_tensor_mod = cute.runtime.load_module("./build/print_tensor_example.o")
print_tensor_mod.print_tensor(a_cute, stream=stream)
add_one_mod = cute.runtime.load_module("./build/add_one_example.o")
add_one_mod.add_one(a_cute, b_cute, stream=stream)
assert a[0, 0] == b[0, 0] + 1
shared_mod = cute.runtime.load_module("./build/libexport_example.so")
shared_mod.print_tensor(a_cute, stream=stream)
shared_mod.add_one(a_cute, b_cute, stream=stream)
assert a[0, 0] == b[0, 0] + 1
if __name__ == "__main__":
run()

View File

@@ -0,0 +1,254 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES.
* All rights reserved. SPDX-License-Identifier: LicenseRef-NvidiaProprietary
*
* NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
* property and proprietary rights in and to this material, related
* documentation and any modifications thereto. Any use, reproduction,
* disclosure or distribution of this material and related documentation
* without an express license agreement from NVIDIA CORPORATION or
* its affiliates is strictly prohibited.
*/
/**
* This example demonstrates how to load a CuTe module/function from a object
* file or a shared library and run it in a CUDA kernel.
*
* To run this example:
*
* .. code-block:: bash
*
* # prerequesites: export the compiled functions to object files and compile
* them into a shared library python examples/cute/export/export_to_c.py # run
* the example bash ./examples/cute/export/run_with_dynamic_loading.sh
*/
#include "CuteDSLRuntime.h"
#include <cuda_runtime.h>
#include <fstream>
#include <vector>
std::vector<unsigned char> read_file(const std::string &filename) {
std::ifstream file(filename, std::ios::binary);
std::vector<unsigned char> content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
return content;
}
void initialize_cuda_context() {
// Initialize cuda context
cudaSetDevice(0);
}
void check_error(CuteDSLRT_Error_t error) {
if (error != CuteDSLRT_Error_Success) {
printf("Got runtime error: %d\n", error);
exit(1);
}
}
// Copy the definition of the tensor from `print_tensor_example.h` to here
typedef struct {
void *data;
int32_t dynamic_shapes[2];
int64_t dynamic_strides[1];
} print_tensor_Tensor_a_t;
// Copy the definition of the tensor from `add_one_example.h` to here
typedef struct {
void *data;
int32_t dynamic_shapes[2];
int64_t dynamic_strides[1];
} add_one_Tensor_a_t;
typedef struct {
void *data;
int32_t dynamic_shapes[2];
int64_t dynamic_strides[1];
} add_one_Tensor_b_t;
print_tensor_Tensor_a_t prepare_print_tensor_tensor() {
print_tensor_Tensor_a_t tensor;
tensor.data = NULL;
tensor.dynamic_shapes[0] = 32;
tensor.dynamic_shapes[1] = 16;
tensor.dynamic_strides[0] = 16;
return tensor;
}
add_one_Tensor_a_t prepare_add_one_tensor_a() {
float a_host_ptr[32 * 16];
for (int i = 0; i < 32 * 16; i++) {
a_host_ptr[i] = i;
}
void *a_device_ptr;
cudaMalloc(&a_device_ptr, sizeof(float) * 32 * 16);
cudaMemcpy(a_device_ptr, a_host_ptr, sizeof(float) * 32 * 16,
cudaMemcpyHostToDevice);
add_one_Tensor_a_t tensor;
tensor.data = (void *)a_device_ptr;
tensor.dynamic_shapes[0] = 32;
tensor.dynamic_shapes[1] = 16;
tensor.dynamic_strides[0] = 16;
return tensor;
}
add_one_Tensor_b_t prepare_add_one_tensor_b() {
float b_host_ptr[32 * 16];
for (int i = 0; i < 32 * 16; i++) {
b_host_ptr[i] = 32 * 16 - i;
}
void *b_device_ptr;
cudaMalloc(&b_device_ptr, sizeof(float) * 32 * 16);
cudaMemcpy(b_device_ptr, b_host_ptr, sizeof(float) * 32 * 16,
cudaMemcpyHostToDevice);
add_one_Tensor_b_t tensor;
tensor.data = (void *)b_device_ptr;
tensor.dynamic_shapes[0] = 32;
tensor.dynamic_shapes[1] = 16;
tensor.dynamic_strides[0] = 16;
return tensor;
}
void run_print_tensor_with_object_file() {
// prepare tensor
print_tensor_Tensor_a_t tensor = prepare_print_tensor_tensor();
// prepare stream
cudaStream_t stream;
cudaStreamCreate(&stream);
// load module and function
CuteDSLRT_Module_t *module = nullptr;
std::vector<unsigned char> print_tensor_example_o_bytes =
read_file("build/print_tensor_example.o");
size_t print_tensor_example_o_size = print_tensor_example_o_bytes.size();
CuteDSLRT_Error_t error = CuteDSLRT_Module_Create_From_Bytes(
&module, print_tensor_example_o_bytes.data(), print_tensor_example_o_size,
nullptr, 0);
check_error(error);
CuteDSLRT_Function_t *function = nullptr;
error = CuteDSLRT_Module_Get_Function(&function, module, "print_tensor");
check_error(error);
// run kernel, refer to the wrapper function in `print_tensor_example.h`
int32_t cuda_result;
void *args[3] = {&tensor, &stream, &cuda_result};
error = CuteDSLRT_Function_Run(function, args, 3);
check_error(error);
// synchronize stream
cudaStreamSynchronize(stream);
// unload module
error = CuteDSLRT_Module_Destroy(module);
check_error(error);
cudaStreamDestroy(stream);
}
void run_add_one_with_object_file() {
// prepare a tensor
add_one_Tensor_a_t tensor_a = prepare_add_one_tensor_a();
// prepare b tensor
add_one_Tensor_b_t tensor_b = prepare_add_one_tensor_b();
// prepare stream
cudaStream_t stream;
cudaStreamCreate(&stream);
// load module
CuteDSLRT_Module_t *module = nullptr;
std::vector<unsigned char> add_one_example_o_bytes =
read_file("build/add_one_example.o");
size_t add_one_example_o_size = add_one_example_o_bytes.size();
CuteDSLRT_Error_t error = CuteDSLRT_Module_Create_From_Bytes(
&module, add_one_example_o_bytes.data(), add_one_example_o_size, nullptr,
0);
check_error(error);
CuteDSLRT_Function_t *function = nullptr;
error = CuteDSLRT_Module_Get_Function(&function, module, "add_one");
check_error(error);
// run kernel, refer to the wrapper function in `add_one_example.h`
int32_t cuda_result;
void *args[4] = {&tensor_a, &tensor_b, &stream, &cuda_result};
error = CuteDSLRT_Function_Run(function, args, 4);
check_error(error);
cudaStreamSynchronize(stream);
// unload module
error = CuteDSLRT_Module_Destroy(module);
check_error(error);
cudaStreamDestroy(stream);
// check result
float a_host_ptr[32 * 16];
cudaMemcpy(a_host_ptr, tensor_a.data, sizeof(float) * 32 * 16,
cudaMemcpyDeviceToHost);
if (a_host_ptr[0] != 32 * 16 + 1) {
printf("Error: a_host_ptr[0] = %f, expected %d\n", a_host_ptr[0], 32 * 16);
exit(1);
}
}
void run_example_with_shared_library() {
// prepare tensor
print_tensor_Tensor_a_t tensor = prepare_print_tensor_tensor();
// prepare a tensor
add_one_Tensor_a_t tensor_a = prepare_add_one_tensor_a();
// prepare b tensor
add_one_Tensor_b_t tensor_b = prepare_add_one_tensor_b();
// prepare stream
cudaStream_t stream;
cudaStreamCreate(&stream);
// load module
CuteDSLRT_Module_t *module = nullptr;
const char *shared_libs[] = {"build/libexport_example.so"};
CuteDSLRT_Error_t error =
CuteDSLRT_Module_Create_From_Bytes(&module, nullptr, 0, shared_libs, 1);
check_error(error);
// get print tensor function
CuteDSLRT_Function_t *print_tensor_function = nullptr;
error = CuteDSLRT_Module_Get_Function(&print_tensor_function, module,
"print_tensor");
check_error(error);
// get add one function
CuteDSLRT_Function_t *add_one_function = nullptr;
error = CuteDSLRT_Module_Get_Function(&add_one_function, module, "add_one");
check_error(error);
// run print tensor kernel
int32_t cuda_result;
void *print_tensor_args[3] = {&tensor, &stream, &cuda_result};
error = CuteDSLRT_Function_Run(print_tensor_function, print_tensor_args, 3);
check_error(error);
cudaStreamSynchronize(stream);
// run add one kernel
void *add_one_args[4] = {&tensor_a, &tensor_b, &stream, &cuda_result};
error = CuteDSLRT_Function_Run(add_one_function, add_one_args, 4);
check_error(error);
cudaStreamSynchronize(stream);
// unload module
error = CuteDSLRT_Module_Destroy(module);
check_error(error);
cudaStreamDestroy(stream);
}
int main() {
initialize_cuda_context();
run_print_tensor_with_object_file();
run_add_one_with_object_file();
run_example_with_shared_library();
return 0;
}

View File

@@ -0,0 +1,80 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#!/bin/bash
set -eu
# Try to find the wheel path of nvidia-cutlass-dsl
WHEEL_PATH=$(python3 -c "import cutlass, os; print(os.path.dirname(cutlass.__file__))" 2>/dev/null)/../..
if [[ -z "$WHEEL_PATH" ]]; then
echo "nvidia-cutlass-dsl wheel not found in the current Python environment."
exit 1
else
echo "nvidia-cutlass-dsl wheel path found at: $WHEEL_PATH"
fi
CUTE_DSL_LIB_PATH="${WHEEL_PATH}/lib/"
export LD_LIBRARY_PATH=${CUTE_DSL_LIB_PATH}:${CUDA_HOME}/lib64:./build
if [ -z "$CUDA_HOME" ]; then
CUDA_HOME=/usr/local/cuda
echo "CUDA_HOME not set, using default: $CUDA_HOME"
else
echo "CUDA_HOME found: $CUDA_HOME"
fi
SOURCE_FILE="$(dirname "$0")/run_with_dynamic_loading.cpp"
echo "Compiling the executable..."
# Search for a common C++ compiler: g++, clang++, or c++
if [ -n "${CXX-}" ] && command -v "$CXX" &> /dev/null; then
CXX="$CXX"
elif command -v g++ &> /dev/null; then
CXX="g++"
elif command -v clang++ &> /dev/null; then
CXX="clang++"
elif command -v c++ &> /dev/null; then
CXX="c++"
else
echo "Error: No common C++ compiler found (g++, clang++, or c++). Please install a C++ compiler to continue."
exit 1
fi
$CXX -o build/run_with_dynamic_loading \
-I${CUDA_HOME}/include \
-I${WHEEL_PATH}/include \
${SOURCE_FILE} \
-L${CUTE_DSL_LIB_PATH} \
-L${CUDA_HOME}/lib64 \
-L./build \
-lcudart \
-lcute_dsl_runtime \
-lexport_example
echo "Running the executable..."
./build/run_with_dynamic_loading

View File

@@ -0,0 +1,124 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES.
* All rights reserved. SPDX-License-Identifier: LicenseRef-NvidiaProprietary
*
* NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
* property and proprietary rights in and to this material, related
* documentation and any modifications thereto. Any use, reproduction,
* disclosure or distribution of this material and related documentation
* without an express license agreement from NVIDIA CORPORATION or
* its affiliates is strictly prohibited.
*/
/**
* This example demonstrates how to load a CuTe module/function from compilation
* and static linking and run it in a CUDA kernel.
*
* To run this example:
*
* .. code-block:: bash
*
* # prerequesites: export the compiled functions to object files and compile
* them into a shared library python examples/cute/export/export_to_c.py # run
* the example bash ./examples/cute/export/run_with_static_linking.sh
*/
#include "add_one_example.h"
#include "print_tensor_example.h"
#include <cuda_runtime.h>
void initialize_cuda_context() {
// Initialize cuda context
int device_id = 0;
cudaSetDevice(device_id);
}
void run_print_tensor() {
// prepare tensor
print_tensor_Tensor_a_t tensor;
tensor.data = NULL;
tensor.dynamic_shapes[0] = 32;
tensor.dynamic_shapes[1] = 16;
tensor.dynamic_strides[0] = 16;
// prepare stream
cudaStream_t stream;
cudaStreamCreate(&stream);
// load module
print_tensor_Kernel_Module_t module;
print_tensor_Kernel_Module_Load(&module);
// run kernel
cute_dsl_print_tensor_wrapper(&module, &tensor, stream);
// synchronize stream
cudaStreamSynchronize(stream);
// unload module
print_tensor_Kernel_Module_Unload(&module);
cudaStreamDestroy(stream);
}
void run_add_one() {
// prepare a tensor
add_one_Tensor_a_t a_tensor;
float a_host_ptr[32 * 16];
for (int i = 0; i < 32 * 16; i++) {
a_host_ptr[i] = i;
}
void *a_device_ptr;
cudaMalloc(&a_device_ptr, sizeof(float) * 32 * 16);
cudaMemcpy(a_device_ptr, a_host_ptr, sizeof(float) * 32 * 16,
cudaMemcpyHostToDevice);
a_tensor.data = (void *)a_device_ptr;
a_tensor.dynamic_shapes[0] = 32;
a_tensor.dynamic_shapes[1] = 16;
a_tensor.dynamic_strides[0] = 16;
// prepare b tensor
add_one_Tensor_b_t b_tensor;
float b_host_ptr[32 * 16];
for (int i = 0; i < 32 * 16; i++) {
b_host_ptr[i] = 32 * 16 - i;
}
void *b_device_ptr;
cudaMalloc(&b_device_ptr, sizeof(float) * 32 * 16);
cudaMemcpy(b_device_ptr, b_host_ptr, sizeof(float) * 32 * 16,
cudaMemcpyHostToDevice);
b_tensor.data = (void *)b_device_ptr;
b_tensor.dynamic_shapes[0] = 32;
b_tensor.dynamic_shapes[1] = 16;
b_tensor.dynamic_strides[0] = 16;
// prepare stream
cudaStream_t stream;
cudaStreamCreate(&stream);
// load module
add_one_Kernel_Module_t module;
add_one_Kernel_Module_Load(&module);
// run kernel
cute_dsl_add_one_wrapper(&module, &a_tensor, &b_tensor, stream);
cudaStreamSynchronize(stream);
// unload module
add_one_Kernel_Module_Unload(&module);
cudaStreamDestroy(stream);
// check result
cudaMemcpy(a_host_ptr, a_device_ptr, sizeof(float) * 32 * 16,
cudaMemcpyDeviceToHost);
if (a_host_ptr[0] != 32 * 16 + 1) {
printf("Error: a_host_ptr[0] = %f, expected %d\n", a_host_ptr[0], 32 * 16);
}
}
int main() {
initialize_cuda_context();
run_print_tensor();
run_add_one();
return 0;
}

View File

@@ -0,0 +1,76 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#!/bin/bash
set -eu
# Try to find the wheel path of nvidia-cutlass-dsl
WHEEL_PATH=$(python3 -c "import cutlass, os; print(os.path.dirname(cutlass.__file__))" 2>/dev/null)/../..
if [[ -z "$WHEEL_PATH" ]]; then
echo "nvidia-cutlass-dsl wheel not found in the current Python environment."
exit 1
else
echo "nvidia-cutlass-dsl wheel path found at: $WHEEL_PATH"
fi
CUTE_DSL_LIB_PATH="${WHEEL_PATH}/lib/"
export LD_LIBRARY_PATH=${CUTE_DSL_LIB_PATH}
if [ -z "$CUDA_HOME" ]; then
CUDA_HOME=/usr/local/cuda
echo "CUDA_HOME not set, using default: $CUDA_HOME"
else
echo "CUDA_HOME found: $CUDA_HOME"
fi
SOURCE_FILE="$(dirname "$0")/run_with_static_linking.cpp"
echo "Compiling the executable..."
# Search for a common C++ compiler: g++, clang++, or c++
if [ -n "${CXX-}" ] && command -v "$CXX" &> /dev/null; then
CXX="$CXX"
elif command -v g++ &> /dev/null; then
CXX="g++"
elif command -v clang++ &> /dev/null; then
CXX="clang++"
elif command -v c++ &> /dev/null; then
CXX="c++"
else
echo "Error: No common C++ compiler found (g++, clang++, or c++). Please install a C++ compiler to continue."
exit 1
fi
# Note: The options -ldl, -lrt, and -lpthread are optional to satisfy symbol dependencies required by `libcudart_static.a`.
$CXX -o build/run_with_static_linking \
-I${CUDA_HOME}/include \
-I./build \
${SOURCE_FILE} build/add_one_example.o build/print_tensor_example.o \
${CUTE_DSL_LIB_PATH}/libcuda_dialect_runtime_static.a \
${CUDA_HOME}/lib64/libcudart_static.a -ldl -lrt -lpthread
echo "Running the executable..."
./build/run_with_static_linking

View File

@@ -0,0 +1,64 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
cmake_minimum_required(VERSION 3.15)
project(tensor)
# Find Python
find_package(Python COMPONENTS Interpreter Development REQUIRED)
find_package(Python3 COMPONENTS Interpreter Development REQUIRED)
# Get Python site-packages directory using Python
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import sys, sysconfig; print(';'.join([sysconfig.get_paths()['purelib'],sysconfig.get_paths(vars={'base': sys.base_prefix, 'platbase': sys.base_prefix})['purelib']]))"
OUTPUT_VARIABLE Python_SITE_PACKAGES_PATHS
OUTPUT_STRIP_TRAILING_WHITESPACE
)
message(STATUS "Python site-packages directories: ${Python_SITE_PACKAGES_PATHS}")
# Add nanobind path to CMAKE_PREFIX_PATH
foreach(path IN LISTS Python_SITE_PACKAGES_PATHS)
if(EXISTS "${path}/nanobind/cmake")
message(STATUS "Adding nanobind cmake path: ${path}/nanobind/cmake")
list(APPEND CMAKE_PREFIX_PATH "${path}/nanobind/cmake")
break()
endif()
endforeach()
# Find nanobind
find_package(nanobind)
if(NOT nanobind_FOUND)
message(FATAL_ERROR
"nanobind not found!\n"
"Please install nanobind with: pip install nanobind\n"
)
endif()
# Add the module
nanobind_add_module(tensor tensor.cpp)

View File

@@ -0,0 +1,320 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Example of accessing POD (Plain Old Data) from C or other languages via LLVM operations.
This example demonstrates a basic approach to building customized interfaces as C-structures between user code
and JIT compiled functions. It provides a minimal-cost solution for calling JIT functions
and can be used to build AOT (Ahead-of-Time) launchers for JIT compiled functions.
The C-structure is defined as:
.. code-block:: c
struct Tensor {
void *ptr; // Pointer to tensor data
int32_t shape[3]; // Tensor dimensions
int32_t strides[3]; // Memory strides for each dimension
};
The example defines Tensor and TensorValue classes that wrap C structs for view of a tensor with its data pointer,
shape, and strides, enabling efficient data passing between different language boundaries.
.. note::
Future development may include automated code generation flows.
"""
import cutlass
import cutlass.cute as cute
from cutlass._mlir import ir
from cutlass._mlir.dialects import llvm
class ExampleTensorValue(ir.Value):
"""A wrapper class for tensor values in MLIR.
This class extends ir.Value to provide convenient access to tensor data pointer,
shape, and strides through MLIR operations.
:type: ir.Value
"""
def __init__(self, v):
"""Initialize a new TensorValue.
:param v: The underlying MLIR value to wrap
:type v: ir.Value
"""
super().__init__(v)
@property
def data_ptr(self, *, loc=None, ip=None):
"""Get the data pointer from the tensor value.
Extracts the data pointer (first field) from the LLVM struct value.
:param loc: Optional location information for MLIR operations
:type loc: Optional[ir.Location]
:param ip: Optional insertion point for MLIR operations
:type ip: Optional[ir.InsertionPoint]
:return: An integer value representing the data pointer
:rtype: ir.Value
"""
# Extract the data pointer from the LLVM struct value
# The data pointer is the first field (index 0) in the struct
# Use llvm.extractvalue to get the pointer field from the struct
ptr_val = llvm.extractvalue(
llvm.PointerType.get(),
self,
[0], # Extract the first field (index 0)
loc=loc,
ip=ip,
)
return cute.make_ptr(cutlass.Float32, ptr_val)
@property
def shape(self):
"""Get the shape of the tensor.
Extracts the shape (second field) from the LLVM struct value.
:return: A tuple of integers representing the tensor dimensions
:rtype: tuple[ir.Value, ...]
"""
i32_type = ir.IntegerType.get_signless(32)
# Extract the shape field from the LLVM struct value
# The shape is the second field (index 1) in the struct
shape_val = llvm.extractvalue(
llvm.StructType.get_literal([i32_type] * 3),
self,
[1], # Extract the second field (index 1)
)
# Extract each dimension from the shape struct
return tuple(llvm.extractvalue(i32_type, shape_val, [i]) for i in range(3))
@property
def stride(self):
"""Get the strides of the tensor.
Extracts the strides (third field) from the LLVM struct value.
:return: A tuple of integers representing the tensor strides
:rtype: tuple[ir.Value, ...]
"""
i32_type = ir.IntegerType.get_signless(32)
# Extract the strides field from the LLVM struct value
# The strides are the third field (index 2) in the struct
strides_val = llvm.extractvalue(
llvm.StructType.get_literal([i32_type] * 3),
self,
[2], # Extract the third field (index 2)
)
# Extract each dimension from the strides struct
return tuple(llvm.extractvalue(i32_type, strides_val, [i]) for i in range(3))
class ExampleTensor:
"""A class representing a tensor with its data pointer, shape, and strides.
This class provides a Python interface to create and manipulate tensor structures
that can be passed to CUTE JIT compiled functions.
:ivar _c_struct_p: The C struct pointer for the tensor
:ivar _rank: The number of dimensions in the tensor
"""
def __init__(self, c_struct_p, rank):
"""Initialize a new Tensor.
:param c_struct_p: The C struct pointer for the tensor
:type c_struct_p: int
:param rank: The number of dimensions in the tensor
:type rank: int
"""
self._c_struct_p = c_struct_p
self._rank = rank
def __get_mlir_types__(self):
"""Get the MLIR types for this tensor.
Creates an LLVM structure type representing a C-structure with:
.. code-block:: c
struct Tensor {
void *ptr;
int32_t shape[3];
int32_t strides[3];
};
:return: A list containing the MLIR struct type
:rtype: list[llvm.StructType]
Create an LLVM structure type that represents a C-structure like:
"""
# Get the number of dimensions from the shape
ndim = self._rank
# Create the pointer type (void*)
ptr_type = llvm.PointerType.get()
# Create array types for shape and strides (int32_t[ndim])
int32_type = ir.IntegerType.get_signless(32)
shape_type = llvm.StructType.get_literal([int32_type] * ndim)
strides_type = llvm.StructType.get_literal([int32_type] * ndim)
# Create the structure type
struct_type = llvm.StructType.get_literal([ptr_type, shape_type, strides_type])
return [struct_type]
def __new_from_mlir_values__(self, values):
"""Create a new TensorValue from MLIR values.
:param values: A list of MLIR values
:type values: list[ir.Value]
:return: A new TensorValue instance
:rtype: TensorValue
"""
return ExampleTensorValue(values[0])
def __c_pointers__(self):
"""Get the C pointers for this tensor.
:return: A list containing the C struct pointer
:rtype: list[int]
"""
return [self._c_struct_p]
@cute.jit
def foo(tensor):
"""Example JIT function that prints tensor information.
:param tensor: A Tensor instance to print information about
:type tensor: Tensor
"""
cute.printf("data_ptr: {}", tensor.data_ptr)
cute.printf("shape: {}", tensor.shape)
cute.printf("stride: {}", tensor.stride)
mA = cute.make_tensor(
tensor.data_ptr, cute.make_layout(tensor.shape, stride=tensor.stride)
)
cute.print_tensor(mA)
import sys
import os
import subprocess
import shutil
import tempfile
def run_test(tmpdir=None, cmake_args="", cleanup=True):
import torch
try:
current_dir = os.path.dirname(os.path.abspath(__file__))
cmake_args = cmake_args.split()
subprocess.run(["cmake", "-B", tmpdir, current_dir] + cmake_args, check=True)
subprocess.run(["cmake", "--build", tmpdir], check=True)
from tensor import make_tensor, pycapsule_get_pointer
# Mock test tensor and corresponding C structure for this example
# In production, this may come from external library
x = torch.arange(2 * 8 * 4).to(torch.float32).reshape(2, 8, 4)
c_struct = make_tensor(x.data_ptr(), x.shape, x.stride())
c_struct_p = pycapsule_get_pointer(c_struct)
# Initialize tensor wrapper and compile test function
tensor = ExampleTensor(c_struct_p, len(x.shape))
compiled_func = cute.compile(foo, tensor)
# Benchmark pointer access performance
from time import time
start = time()
# Measure performance of critical path pointer access
# get C pointers is on critical path to call JIT compiled function
for _ in range(1000):
tensor.__c_pointers__()
end = time()
print(f"__c_pointers__: {(end - start) * 1000} us")
# Execute compiled function
compiled_func(tensor)
except Exception as e:
import traceback
traceback.print_exception(type(e), e, e.__traceback__)
raise e
finally:
if cleanup:
# Clean up the temporary directory
shutil.rmtree(tmpdir)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Set temporary directory for building C modules"
)
parser.add_argument(
"--tmp-dir",
type=str,
default=None,
help="Temporary directory path for building C modules",
)
parser.add_argument(
"--cmake-args",
type=str,
default="",
help="Extra CMake arguments for building C modules",
)
args = parser.parse_args()
if args.tmp_dir:
tmp_dir = args.tmp_dir
cleanup = False
else:
tmp_dir = tempfile.mkdtemp()
cleanup = True
sys.path.append(tmp_dir)
run_test(tmp_dir, args.cmake_args, cleanup)

View File

@@ -0,0 +1,82 @@
// Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
#include <cstdint>
#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/vector.h>
namespace nb = nanobind;
// Forward declaration of the MockTensor struct for testing only
struct MockTensor {
void *ptr;
struct {
int32_t shape[3];
} shape;
struct {
int32_t strides[3];
} strides;
};
NB_MODULE(tensor, m) {
// create a tensor for testing
m.def("make_tensor", [](int64_t ptr, std::vector<int32_t> shape,
std::vector<int32_t> strides) {
auto *tensor = new MockTensor();
tensor->ptr = reinterpret_cast<void *>(ptr);
assert(shape.size() == 3 && "shape must have 3 elements");
assert(strides.size() == 3 && "strides must have 3 elements");
for (size_t i = 0; i < shape.size(); i++) {
tensor->shape.shape[i] = shape[i];
tensor->strides.strides[i] = strides[i];
}
return nb::steal(PyCapsule_New(tensor, "tensor", [](PyObject *capsule) {
auto n = PyCapsule_GetName(capsule);
if (void *p = PyCapsule_GetPointer(capsule, n)) {
delete reinterpret_cast<MockTensor *>(p);
}
}));
});
m.def(
"pycapsule_get_pointer",
[](nb::object &capsule) {
void *ptr = PyCapsule_GetPointer(capsule.ptr(), "tensor");
if (!ptr) {
throw std::runtime_error("Invalid tensor capsule");
}
return reinterpret_cast<uintptr_t>(ptr);
},
"Get pointer from PyCapsule");
}

View File

@@ -0,0 +1,243 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from functools import partial
from typing import Union
import cutlass.cute as cute
from cutlass import Constexpr
from cutlass.cute.runtime import from_dlpack
from cutlass._mlir.dialects import llvm
from cutlass.cute.typing import Boolean, Int32, Int
from cutlass.cutlass_dsl import T, dsl_user_op
from cutlass.cute.arch.nvvm_wrappers import FULL_MASK, WARP_SIZE
"""
A simple example to show how to wrap PTX instructions by using inline_asm op in llvm dialect.
Situations like:
1. Instructions that are not already exposed by CuTe DSL via `nvvm` module
2. Sequences of instructions that the compiler otherwise does not generate optimally
motivate developers to inline PTX themselves.
In this example, we inline the vote.sync.ballot.b32, vote.sync.any.pred, vote.sync.all.pred,
vote.sync.uni.pred, and use the corresponding ops in nvvm dialect for the test.
You can refer to the documentation of `inline_asm op in llvm dialect <https://mlir.llvm.org/docs/Dialects/LLVM/#llvminline_asm-llvminlineasmop>`_
and `vote.sync <https://docs.nvidia.com/cuda/parallel-thread-execution/#parallel-synchronization-and-communication-instructions-vote-sync>`_
for more details.
To run this example:
.. code-block:: bash
python examples/dsl/inline_ptx.py
The example will run the vote kernel with inline ptx and nvvm dialect separately.
The results from inline ptx and nvvm dialect will be verified correspondingly.
"""
@dsl_user_op
def ptx_vote_sync_op(
pred: Boolean, kind: str, mask: Int = FULL_MASK, *, loc=None, ip=None
) -> Union[Int32, Boolean]:
return_type = Boolean
return_type_str = "pred"
return return_type(
llvm.inline_asm(
T.bool(),
[
Boolean(pred).ir_value(loc=loc, ip=ip),
Int32(mask).ir_value(loc=loc, ip=ip),
],
f"""{{\n\t
.reg .pred ps;\n\t
.reg .pred pd;\n\t
setp.ne.b32 ps, $1, 0;\n\t
vote.sync.{kind}.{return_type_str} pd, ps, $2;\n\t
selp.b32 $0, 1, 0, pd;\n\t
}}""",
"=r,r,i",
has_side_effects=True,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
)
ptx_vote_any_sync = partial(ptx_vote_sync_op, kind="any")
ptx_vote_all_sync = partial(ptx_vote_sync_op, kind="all")
ptx_vote_uni_sync = partial(ptx_vote_sync_op, kind="uni")
@dsl_user_op
def ptx_vote_ballot_sync(
pred: Boolean, mask: Int = FULL_MASK, *, loc=None, ip=None
) -> Union[Int32, Boolean]:
return_type = Int32
return_type_str = "b32"
return return_type(
llvm.inline_asm(
T.i32(),
[
Boolean(pred).ir_value(loc=loc, ip=ip),
Int32(mask).ir_value(loc=loc, ip=ip),
],
f"""{{\n\t
.reg .pred p;\n\t
setp.ne.b32 p, $1, 0;\n\t
vote.sync.ballot.{return_type_str} $0, p, $2;\n\t
}}""",
"=r,r,i",
has_side_effects=True,
is_align_stack=False,
asm_dialect=llvm.AsmDialect.AD_ATT,
)
)
@cute.kernel
def vote_kernel(
mBallot: cute.Tensor,
mAny: cute.Tensor,
mAll: cute.Tensor,
mUni: cute.Tensor,
use_inline_ptx: Constexpr[bool],
):
tidx, _, _ = cute.arch.thread_idx()
vote_ballot = (
ptx_vote_ballot_sync(tidx < 10)
if use_inline_ptx
else cute.arch.vote_ballot_sync(tidx < 10)
)
vote_any = (
ptx_vote_any_sync(tidx < 10)
if use_inline_ptx
else cute.arch.vote_any_sync(tidx < 10)
)
vote_all = (
ptx_vote_all_sync(tidx < 10)
if use_inline_ptx
else cute.arch.vote_all_sync(tidx < 10)
)
vote_uni = (
ptx_vote_uni_sync(tidx < 10)
if use_inline_ptx
else cute.arch.vote_uni_sync(tidx < 10)
)
mBallot[tidx] = vote_ballot
mAny[tidx] = vote_any
mAll[tidx] = vote_all
mUni[tidx] = vote_uni
@cute.jit
def vote(
mBallot: cute.Tensor,
mAny: cute.Tensor,
mAll: cute.Tensor,
mUni: cute.Tensor,
use_inline_ptx: Constexpr[bool],
):
vote_kernel(
mBallot,
mAny,
mAll,
mUni,
use_inline_ptx,
).launch(
grid=[1, 1, 1],
block=[cute.size(WARP_SIZE, mode=[0]), 1, 1],
)
def run():
import torch
ballot_ptx = torch.randint(
0, 100, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.int32
)
any_ptx = torch.randint(
0, 2, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.bool
)
all_ptx = torch.randint(
0, 2, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.bool
)
uni_ptx = torch.randint(
0, 2, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.bool
)
mBallotPTX = from_dlpack(ballot_ptx).mark_layout_dynamic()
mAnyPTX = from_dlpack(any_ptx).mark_layout_dynamic()
mAllPTX = from_dlpack(all_ptx).mark_layout_dynamic()
mUniPTX = from_dlpack(uni_ptx).mark_layout_dynamic()
# get the results from ptx
vote(mBallotPTX, mAnyPTX, mAllPTX, mUniPTX, use_inline_ptx=True)
ballot_nvvm = torch.randint(
0, 100, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.int32
)
any_nvvm = torch.randint(
0, 2, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.bool
)
all_nvvm = torch.randint(
0, 2, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.bool
)
uni_nvvm = torch.randint(
0, 2, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.bool
)
mBallotNVVM = from_dlpack(ballot_nvvm).mark_layout_dynamic()
mAnyNVVM = from_dlpack(any_nvvm).mark_layout_dynamic()
mAllNVVM = from_dlpack(all_nvvm).mark_layout_dynamic()
mUniNVVM = from_dlpack(uni_nvvm).mark_layout_dynamic()
# get the results from nvvm
vote(mBallotNVVM, mAnyNVVM, mAllNVVM, mUniNVVM, use_inline_ptx=False)
print("Verifying ballot results...")
torch.testing.assert_close(ballot_ptx, ballot_nvvm)
print("Verifying any results...")
torch.testing.assert_close(any_ptx, any_nvvm)
print("Verifying all results...")
torch.testing.assert_close(all_ptx, all_nvvm)
print("Verifying uni results...")
torch.testing.assert_close(uni_ptx, uni_nvvm)
print("Results verified successfully!")
if __name__ == "__main__":
run()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,366 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass
import cutlass.cute as cute
import cutlass.jax as cjax
import cuda.bindings.driver as cuda
"""
CuTe DSL kernels used by the ``cute_dsl_jax.ipynb`` notebook.
This module defines GPU kernels written in CuTe DSL (CUTLASS 4.x Python DSL)
that are called from JAX via ``cutlass.jax.cutlass_call``. ``cutlass_call`` is a
JAX primitive that triggers compilation of the kernel during lowering and embeds
it into the HLO computation, so XLA can launch it efficiently without callback
to Python.
Kernels provided:
- ``vector_add`` — element-wise c = a + b (3-D CuTe layout)
- ``saxpy`` — y = alpha * x + y
- ``relu`` — element-wise ReLU with flat indexing
- ``fused_bias_relu`` — fused bias addition + ReLU
- ``gemm`` — tiled matrix multiplication
- ``elementwise_add`` — 2-D element-wise add (flat indexing, ``jax.export``-compatible)
The notebook imports these kernels and wraps each one with ``cutlass_call``
inside ``@jax.jit`` functions. See ``cute_dsl_jax.ipynb`` for usage, validation,
and step-by-step explanations.
This module is imported by the notebook and by ``cute_dsl_jax.py``. It can also
be run directly to validate every kernel:
.. code-block:: bash
# Interactive notebook (recommended for learning)
jupyter lab cute_dsl_jax.ipynb
# Full demo as a standalone script
python cute_dsl_jax_kernels.py
"""
# ------------------------------------------------------------------ #
# Vector Add: c = a + b #
# ------------------------------------------------------------------ #
@cute.kernel
def vector_add_kernel(a: cute.Tensor, b: cute.Tensor, c: cute.Tensor):
"""Per-thread kernel: each thread adds one element."""
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
frgA = cute.make_rmem_tensor(cute.size(a, mode=[0]), a.element_type)
frgB = cute.make_rmem_tensor(cute.size(b, mode=[0]), b.element_type)
frgC = cute.make_rmem_tensor(cute.size(c, mode=[0]), c.element_type)
cute.autovec_copy(a[None, tidx, bidx], frgA)
cute.autovec_copy(b[None, tidx, bidx], frgB)
frgC.store(frgA.load() + frgB.load())
cute.autovec_copy(frgC, c[None, tidx, bidx])
@cute.jit
def launch_vector_add(
stream: cuda.CUstream,
a: cute.Tensor, b: cute.Tensor, c: cute.Tensor,
):
vector_add_kernel(a, b, c).launch(
grid=[a.shape[-1], 1, 1],
block=[a.shape[-2], 1, 1],
stream=stream,
)
# ------------------------------------------------------------------ #
# SAXPY: y = alpha * x + y #
# ------------------------------------------------------------------ #
@cute.kernel
def saxpy_kernel(x: cute.Tensor, y: cute.Tensor, out: cute.Tensor, alpha: float):
"""SAXPY: out[i] = alpha * x[i] + y[i]."""
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
frgX = cute.make_rmem_tensor(cute.size(x, mode=[0]), x.element_type)
frgY = cute.make_rmem_tensor(cute.size(y, mode=[0]), y.element_type)
frgO = cute.make_rmem_tensor(cute.size(out, mode=[0]), out.element_type)
cute.autovec_copy(x[None, tidx, bidx], frgX)
cute.autovec_copy(y[None, tidx, bidx], frgY)
frgO.store(alpha * frgX.load() + frgY.load())
cute.autovec_copy(frgO, out[None, tidx, bidx])
@cute.jit
def launch_saxpy(
stream: cuda.CUstream,
x: cute.Tensor, y: cute.Tensor, out: cute.Tensor,
*, alpha: float,
):
saxpy_kernel(x, y, out, alpha).launch(
grid=[x.shape[-1], 1, 1],
block=[x.shape[-2], 1, 1],
stream=stream,
)
# ------------------------------------------------------------------ #
# ReLU: out = max(0, x) #
# ------------------------------------------------------------------ #
@cute.kernel
def relu_kernel(x: cute.Tensor, out: cute.Tensor, N: int):
"""Per-thread kernel: each thread computes ReLU of one element."""
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
bdx, _, _ = cute.arch.block_dim()
idx = bidx * bdx + tidx
if idx < N:
val = x[idx]
out[idx] = cutlass.max(val, cutlass.Float32(0.0))
@cute.jit
def launch_relu(
stream: cuda.CUstream,
x: cute.Tensor, out: cute.Tensor,
*, N: int,
):
BLOCK_SIZE = 256
grid_size = (N + BLOCK_SIZE - 1) // BLOCK_SIZE
relu_kernel(x, out, N).launch(
grid=[grid_size, 1, 1],
block=[BLOCK_SIZE, 1, 1],
stream=stream,
)
# ------------------------------------------------------------------ #
# Fused Bias + ReLU: out = max(0, x + bias[col]) #
# ------------------------------------------------------------------ #
@cute.kernel
def fused_bias_relu_kernel(
x: cute.Tensor, bias: cute.Tensor, out: cute.Tensor, N: int, width: int,
):
"""Per-thread: out[i] = max(0, x[i] + bias[i % width])."""
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
bdx, _, _ = cute.arch.block_dim()
idx = bidx * bdx + tidx
if idx < N:
col = idx % width
val = x[idx] + bias[col]
out[idx] = cutlass.max(val, cutlass.Float32(0.0))
@cute.jit
def launch_fused_bias_relu(
stream: cuda.CUstream,
x: cute.Tensor, bias: cute.Tensor, out: cute.Tensor,
*, N: int, width: int,
):
BLOCK_SIZE = 256
grid_size = (N + BLOCK_SIZE - 1) // BLOCK_SIZE
fused_bias_relu_kernel(x, bias, out, N, width).launch(
grid=[grid_size, 1, 1],
block=[BLOCK_SIZE, 1, 1],
stream=stream,
)
# ------------------------------------------------------------------ #
# GEMM: D = A @ B #
# ------------------------------------------------------------------ #
@cute.kernel
def gemm_kernel(
A: cute.Tensor, B: cute.Tensor, D: cute.Tensor,
M: int, N: int, K: int, BLOCK_M: int, BLOCK_N: int,
):
"""Tiled GEMM: each thread accumulates output elements."""
tidx, _, _ = cute.arch.thread_idx()
bm, bn, _ = cute.arch.block_idx()
bdx, _, _ = cute.arch.block_dim()
for i in cutlass.range(tidx, BLOCK_M * BLOCK_N, bdx):
row = i // BLOCK_N
col = i % BLOCK_N
m_idx = bm * BLOCK_M + row
n_idx = bn * BLOCK_N + col
if m_idx < M and n_idx < N:
acc = cutlass.Float32(0.0)
for k in cutlass.range(K):
acc += A[m_idx * K + k] * B[k * N + n_idx]
D[m_idx * N + n_idx] = acc
@cute.jit
def launch_gemm(
stream: cuda.CUstream,
A: cute.Tensor, B: cute.Tensor, D: cute.Tensor,
*, M: int, N: int, K: int,
):
BLOCK_M, BLOCK_N = 64, 64
grid_m = (M + BLOCK_M - 1) // BLOCK_M
grid_n = (N + BLOCK_N - 1) // BLOCK_N
gemm_kernel(A, B, D, M, N, K, BLOCK_M, BLOCK_N).launch(
grid=[grid_m, grid_n, 1],
block=[256, 1, 1],
stream=stream,
)
# ------------------------------------------------------------------ #
# Element-wise Add (2-D, flat indexing) #
# ------------------------------------------------------------------ #
@cute.kernel
def elementwise_add_kernel(gA: cute.Tensor, gB: cute.Tensor, gC: cute.Tensor):
"""Per-thread kernel: 2-D element-wise add using flat indexing."""
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
bdim, _, _ = cute.arch.block_dim()
thread_idx = bidx * bdim + tidx
m, n = gA.shape
ni = thread_idx % n
mi = thread_idx // n
a_val = gA[mi, ni]
b_val = gB[mi, ni]
gC[mi, ni] = a_val + b_val
@cute.jit
def launch_elementwise_add(
stream: cuda.CUstream,
mA: cute.Tensor, mB: cute.Tensor, mC: cute.Tensor,
):
num_threads_per_block = 256
m, n = mA.shape
elementwise_add_kernel(mA, mB, mC).launch(
grid=((m * n) // num_threads_per_block, 1, 1),
block=(num_threads_per_block, 1, 1),
stream=stream,
)
# ------------------------------------------------------------------ #
# Self-tests #
# ------------------------------------------------------------------ #
if __name__ == '__main__':
import os
os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "2")
import jax
import jax.numpy as jnp
import numpy as np
BLOCK = 256
N_BLOCKS = 4
# ── Vector Add ────────────────────────────────────────────────────
# 3-D CuTe layout: (elems_per_thread, threads_per_block, num_blocks)
a = jax.random.normal(jax.random.PRNGKey(0), (1, BLOCK, N_BLOCKS), dtype=jnp.float32)
b = jax.random.normal(jax.random.PRNGKey(1), (1, BLOCK, N_BLOCKS), dtype=jnp.float32)
call = cjax.cutlass_call(
launch_vector_add,
output_shape_dtype=jax.ShapeDtypeStruct(a.shape, a.dtype),
use_static_tensors=True,
)
c = jax.jit(call)(a, b)
np.testing.assert_allclose(np.array(c), np.array(a + b), rtol=1e-5, atol=1e-5)
print('vector_add: PASSED')
# ── SAXPY ─────────────────────────────────────────────────────────
x = jax.random.normal(jax.random.PRNGKey(2), (1, BLOCK, N_BLOCKS), dtype=jnp.float32)
y = jax.random.normal(jax.random.PRNGKey(3), (1, BLOCK, N_BLOCKS), dtype=jnp.float32)
alpha = 2.5
call = cjax.cutlass_call(
launch_saxpy,
output_shape_dtype=jax.ShapeDtypeStruct(x.shape, x.dtype),
use_static_tensors=True,
alpha=alpha,
)
out = jax.jit(call)(x, y)
np.testing.assert_allclose(np.array(out), np.array(alpha * x + y), rtol=1e-5, atol=1e-5)
print('saxpy: PASSED')
# ── ReLU ──────────────────────────────────────────────────────────
N_ELEM = BLOCK * N_BLOCKS
x = jax.random.normal(jax.random.PRNGKey(4), (N_ELEM,), dtype=jnp.float32)
call = cjax.cutlass_call(
launch_relu,
output_shape_dtype=jax.ShapeDtypeStruct(x.shape, x.dtype),
N=N_ELEM,
)
out = jax.jit(call)(x)
np.testing.assert_allclose(np.array(out), np.array(jnp.maximum(x, 0)), rtol=1e-5, atol=1e-5)
print('relu: PASSED')
# ── Fused Bias + ReLU ─────────────────────────────────────────────
ROWS, COLS = 16, 64
x = jax.random.normal(jax.random.PRNGKey(5), (ROWS * COLS,), dtype=jnp.float32)
bias = jax.random.normal(jax.random.PRNGKey(6), (COLS,), dtype=jnp.float32)
call = cjax.cutlass_call(
launch_fused_bias_relu,
output_shape_dtype=jax.ShapeDtypeStruct(x.shape, x.dtype),
N=ROWS * COLS, width=COLS,
)
out = jax.jit(call)(x, bias)
ref = jnp.maximum(x.reshape(ROWS, COLS) + bias, 0).reshape(-1)
np.testing.assert_allclose(np.array(out), np.array(ref), rtol=1e-5, atol=1e-5)
print('fused_bias_relu: PASSED')
# ── GEMM ──────────────────────────────────────────────────────────
M, N, K = 128, 128, 64
A = jax.random.normal(jax.random.PRNGKey(7), (M * K,), dtype=jnp.float32)
B = jax.random.normal(jax.random.PRNGKey(8), (K * N,), dtype=jnp.float32)
call = cjax.cutlass_call(
launch_gemm,
output_shape_dtype=jax.ShapeDtypeStruct((M * N,), A.dtype),
M=M, N=N, K=K,
)
D = jax.jit(call)(A, B)
ref = A.reshape(M, K) @ B.reshape(K, N)
np.testing.assert_allclose(np.array(D.reshape(M, N)), np.array(ref), rtol=1e-2, atol=1e-2)
print('gemm: PASSED')
# ── Elementwise Add (2-D) ─────────────────────────────────────────
M, N = 16, 256
a = jax.random.normal(jax.random.PRNGKey(9), (M, N), dtype=jnp.float32)
b = jax.random.normal(jax.random.PRNGKey(10), (M, N), dtype=jnp.float32)
call = cjax.cutlass_call(
launch_elementwise_add,
output_shape_dtype=jax.ShapeDtypeStruct(a.shape, a.dtype),
)
c = jax.jit(call)(a, b)
np.testing.assert_allclose(np.array(c), np.array(a + b), rtol=1e-5, atol=1e-5)
print('elementwise_add: PASSED')
print('\nAll kernels passed.')

View File

@@ -0,0 +1,254 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from functools import partial
import jax
import jax.numpy as jnp
import cutlass.cute as cute
import cutlass.jax as cjax
import cuda.bindings.driver as cuda
"""
Examples of calling CuTe DSL from jax.jit function using cutlass_call.
cutlass_call is a Jax primitive the enables calling of CuTe DSL kernels within a
a jit-compiled Jax function. During the lowering process cutlass_call will
trigger compilation of the kernel and embed it into the HLO computation. It can
then be efficiently launched by XLA without callback to Python.
This example assumes familiarity with CuTe DSL concepts such as layouts and
dynamic shapes.
To run this example:
.. code-block:: bash
# Run with addition operation
python examples/jax/cutlass_call_basic.py
"""
# This is a typical CuTe DSL kernel function that accepts both tensor and scalar values.
@cute.jit
def launch(
A: cute.Tensor,
B: cute.Tensor,
x: cute.Int32,
y: cute.Int32,
C: cute.Tensor,
D: cute.Tensor,
stream: cuda.CUstream,
):
# Print layouts
print("A layout: ", A.layout)
print("B layout: ", B.layout)
print("C layout: ", C.layout)
print("D layout: ", D.layout)
cute.printf("A layout: {}", A.layout)
cute.printf("B layout: {}", B.layout)
cute.printf("C layout: {}", C.layout)
cute.printf("D layout: {}", D.layout)
cute.printf("")
# Print non-tensor values
print("X is: ", x)
print("Y is: ", y)
cute.printf("X is: {}", x)
cute.printf("Y is: {}", y)
print()
# cutlass_call uses a fixed function signature to pass arguments between Jax and CuTeDSL kernel.
#
# Function Signature Requirement:
# stream, inputs, outputs, *, kwargs...
#
# The first argument must be the CUstream that the kernel is run. This stream is managed by the XLA runtime
# and is necessary to schedule and synchronize launches with the rest of your computation.
#
# The second set of arguments are the Jax arrays for inputs and outputs. Inputs must be passed before
# outputs.
#
# Lastly static arguments (i.e. static_argnums or static_argnames) values are passed as keyword only arguments
# by name.
#
# The the kernel does not match this signature a wrapper functions like the one shown below can be written
# or an inline lambda function can be used to rebind the arguments into the appropriate order.
@cute.jit
def launch_jax_wrapper(
stream: cuda.CUstream,
A: cute.Tensor,
B: cute.Tensor,
C: cute.Tensor,
D: cute.Tensor,
*,
x: cute.Int32,
y: cute.Int32,
):
launch(A, B, x, y, C, D, stream)
@cute.jit
def launch_aliased(
A: cute.Tensor, B: cute.Tensor, x: cute.Int32, y: cute.Int32, stream: cuda.CUstream
):
# Print layouts
print("A layout: ", A.layout)
print("B layout: ", B.layout)
cute.printf("A layout: {}", A.layout)
cute.printf("B layout: {}", B.layout)
cute.printf("")
# Print non-tensor values
print("X is: ", x)
print("Y is: ", y)
cute.printf("X is: {}", x)
cute.printf("Y is: {}", y)
print()
if __name__ == "__main__":
@partial(jax.jit, static_argnums=[2, 3])
def run_cutlass_kernel(a, b, x, y):
call = cjax.cutlass_call(
launch_jax_wrapper,
# Describe the shape and dtype of each output buffer.
output_shape_dtype=(
jax.ShapeDtypeStruct(a.shape, a.dtype),
jax.ShapeDtypeStruct(b.shape, a.dtype),
),
# Static jit arguments are passed via additional keyword arguments.
x=x,
y=y,
)
# Returned value is a callable to invoke the kernel passing only jax arrays.
return call(a, b)
print("\nExample: example_basic_call_from_jit")
A = jnp.zeros((512, 32, 64))
B = jnp.zeros((1, 256, 64, 128))
C, D = run_cutlass_kernel(A, B, 0, 1)
@partial(jax.jit, static_argnums=[2, 3])
def run_cutlass_kernel_lambda(a, b, x, y):
call = cjax.cutlass_call(
# A lambda function may be used to wrap and bind arguments passed by jax
# to the kernel. Alternatively you can wrap using another separate cute.jit
# function.
lambda stream, a, b, c, d, *, x, y: launch(a, b, x, y, c, d, stream),
output_shape_dtype=(
jax.ShapeDtypeStruct(a.shape, a.dtype),
jax.ShapeDtypeStruct(b.shape, a.dtype),
),
# Static jit arguments are passed via additional keyword arguments.
x=x,
y=y,
)
# Returned value is a callable to invoke the kernel passing only jax arrays.
return call(a, b)
print("\nExample: run_cutlass_kernel_lambda")
A = jnp.zeros((512, 32, 64))
B = jnp.zeros((1, 256, 64, 128))
C, D = run_cutlass_kernel_lambda(A, B, 1, 2)
@partial(jax.jit, static_argnums=[2, 3])
def run_cutlass_kernel_static_shapes(a, b, x, y):
call = cjax.cutlass_call(
lambda stream, a, b, c, d, *, x, y: launch(a, b, x, y, c, d, stream),
output_shape_dtype=(
jax.ShapeDtypeStruct(a.shape, a.dtype),
jax.ShapeDtypeStruct(b.shape, a.dtype),
),
# By default cutlass_call treats all tensors as dynamic shape.
# Dynamic shapes are often expected for kernels so this default ensures
# the broadest support. If you know that a kernel can accept fully static
# tensors then you can enable this flag to compile all tensor shapes and
# layouts as constexpr values known at compile time.
# Individual tensors may opt out via .mark_layout_dynamic().
use_static_tensors=True,
x=x,
y=y,
)
return call(a, b)
print("\nExample: run_cutlass_kernel_static_shapes")
A = jnp.zeros((512, 32, 64))
B = jnp.zeros((1, 256, 64, 128))
C, D = run_cutlass_kernel_static_shapes(A, B, 3, 4)
@partial(jax.jit, static_argnums=[2, 3])
def run_cutlass_kernel_with_modes(a, b, x, y):
# input_spec and output_spec accept TensorSpec values to attach layout
# metadata to tensors. mode remaps the logical dimension order seen by
# the kernel. static=True compiles that tensor's layout as constexpr.
call = cjax.cutlass_call(
lambda stream, a, b, c, d, *, x, y: launch(a, b, x, y, c, d, stream),
output_shape_dtype=(
jax.ShapeDtypeStruct(a.shape, a.dtype),
jax.ShapeDtypeStruct(b.shape, a.dtype),
),
input_spec=(
cjax.TensorSpec(mode=(1, 0, 2), static=True),
cjax.TensorSpec(mode=(3, 1, 2, 0)),
),
output_spec=(None, cjax.TensorSpec(mode=(0, 1, 3, 2))),
x=x,
y=y,
)
return call(a, b)
print("\nExample: run_cutlass_kernel_with_modes")
A = jnp.zeros((512, 32, 64))
B = jnp.zeros((1, 256, 64, 128))
C, D = run_cutlass_kernel_with_modes(A, B, 5, 6)
@partial(jax.jit, static_argnums=[2, 3], donate_argnums=[0, 1])
def run_cutlass_kernel_aliased_outputs(a, b, x, y):
call = cjax.cutlass_call(
lambda stream, a, b, *, x, y: launch_aliased(a, b, x, y, stream),
output_shape_dtype=(
jax.ShapeDtypeStruct(a.shape, a.dtype),
jax.ShapeDtypeStruct(b.shape, b.dtype),
),
# Map input indices to output indices so XLA can reuse the input
# buffers for the outputs, avoiding extra allocations.
input_output_aliases={0: 0, 1: 1},
x=x,
y=y,
)
return call(a, b)
print("\nExample: run_cutlass_kernel_aliased_outputs")
A = jnp.zeros((512, 32, 64))
B = jnp.zeros((1, 256, 64, 128))
A, B = run_cutlass_kernel_aliased_outputs(A, B, 7, 8)

View File

@@ -0,0 +1,201 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Examples of using jax.export APIs with functions using cutlass_call.
This example demonstrates three export modes:
1. Concrete shapes -- shapes are fixed constants baked into the export.
2. Unconstrained symbolic shapes ("a, b")
3. Constrained symbolic shapes ("32*M, 16*N")
The JAX function being exported is the same in all three cases; only the
shape specification passed to jax.export differs.
It assumes familiarity with CuTe DSL concepts such as layouts and dynamic shapes
as well as JAX's exporting and serialization features:
https://docs.jax.dev/en/latest/export/index.html#export
To run this example:
.. code-block:: bash
python examples/jax/cutlass_call_export.py --M 512 --N 256
"""
import argparse
import cuda.bindings.driver as cuda
import cutlass.cute as cute
import jax
import jax.numpy as jnp
from jax import export
from cutlass.jax import cutlass_call, get_export_disabled_safety_checks, TensorSpec
from cutlass.jax.testing import create_tensor
# Simple element-wise addition kernel: gC[i,j] = gA[i,j] + gB[i,j]
@cute.kernel
def kernel(gA: cute.Tensor, gB: cute.Tensor, gC: cute.Tensor):
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
bdim, _, _ = cute.arch.block_dim()
thread_idx = bidx * bdim + tidx
m, n = gA.shape
ni = thread_idx % n
mi = thread_idx // n
a_val = gA[mi, ni]
b_val = gB[mi, ni]
gC[mi, ni] = a_val + b_val
@cute.jit
def launch(stream: cuda.CUstream, mA: cute.Tensor, mB: cute.Tensor, mC: cute.Tensor):
num_threads_per_block = 256
m, n = mA.shape
kernel(mA, mB, mC).launch(
grid=((m * n) // num_threads_per_block, 1, 1),
block=(num_threads_per_block, 1, 1),
stream=stream,
)
def _export_and_run(f, ref_f, input_shape_dtype, run_shapes):
"""Export f, serialize/deserialize, then run on each shape in run_shapes.
Both inputs (a, b) are assumed to share the same input_shape_dtype.
"""
print(f"Exporting with input signature: ({input_shape_dtype}, {input_shape_dtype})")
# jax.export can be used to export a jit function containing cutlass_call.
# CUTLASS custom call targets are not on JAX's built-in stable custom-call
# allowlist, so we pass them via disabled_checks to suppress that safety check.
exported = jax.export.export(f, disabled_checks=get_export_disabled_safety_checks())
traced = exported(input_shape_dtype, input_shape_dtype)
blob = traced.serialize()
print(f"Serialized computation is {len(blob)} bytes.")
rehydrated = export.deserialize(blob)
key = jax.random.key(1123)
a_key, b_key = jax.random.split(key, 2)
for shape in run_shapes:
a = create_tensor(shape, dtype=jnp.float32, key=a_key)
b = create_tensor(shape, dtype=jnp.float32, key=b_key)
c = rehydrated.call(a, b)
assert jnp.allclose(c, ref_f(a, b)), f"Mismatch at shape {shape}"
print(f" shape {shape}: OK")
def run_example(M, N):
@jax.jit
def ref_f(a, b):
return jax.nn.sigmoid(a + b)
# The same JAX function is used in all three examples below. The export
# mode is determined entirely by the shape spec passed to jax.export.
@jax.jit
def f(a, b):
call = cutlass_call(launch, output_shape_dtype=a)
return jax.nn.sigmoid(call(a, b))
# ── 1. Concrete shapes ────────────────────────────────────────────────────
# Shapes are fixed constants baked into the export. The deserialized
# computation only accepts exactly these dimensions at runtime.
print("\nConcrete shapes:")
input_shape_dtype = jax.ShapeDtypeStruct((M, N), jnp.float32)
_export_and_run(
f,
ref_f,
input_shape_dtype,
run_shapes=[(M, N)], # concrete exports reject any other shape
)
# ── 2. Unconstrained symbolic shapes ─────────────────────────────────────
# Both dimensions are fully dynamic. The exported computation accepts any
# (M, N) at runtime without recompilation.
print("\nUnconstrained symbolic shapes:")
a_sym, b_sym = export.symbolic_shape("a, b")
input_shape_dtype = jax.ShapeDtypeStruct((a_sym, b_sym), jnp.float32)
_export_and_run(
f,
ref_f,
input_shape_dtype,
run_shapes=[(M, N), (M * 2, N * 4), (M * 4, N * 4)],
)
# ── 3. Constrained symbolic shapes (divisibility) ─────────────────────────
# Shapes are declared as multiples of a tile size via TensorSpec.divisibility.
# The symbolic expression "32*M, 16*N" tells jax.export that dim 0 is always
# a multiple of 32 and dim 1 is always a multiple of 16. This lets the
# compiler generate more efficient code (e.g. no remainder handling).
# Runtime shapes must satisfy these divisibility constraints.
print("\nConstrained symbolic shapes:")
@jax.jit
def f_divisible(a, b):
spec = TensorSpec(divisibility=(32, 16))
call = cutlass_call(
launch,
output_shape_dtype=a,
input_spec=(spec, spec),
output_spec=spec,
)
return jax.nn.sigmoid(call(a, b))
m_sym, n_sym = export.symbolic_shape("32*M, 16*N")
input_shape_dtype = jax.ShapeDtypeStruct((m_sym, n_sym), jnp.float32)
_export_and_run(
f_divisible,
ref_f,
input_shape_dtype,
run_shapes=[(M, N), (M * 2, N * 2), (M * 4, N * 4)],
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Demonstration of using jax.export with functions with cutlass_call"
)
parser.add_argument("--M", default=512, type=int)
parser.add_argument("--N", default=256, type=int)
args = parser.parse_args()
run_example(args.M, args.N)
print("PASS")

View File

@@ -0,0 +1,175 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from functools import partial
import jax
import jax.numpy as jnp
from jax.sharding import NamedSharding, PartitionSpec as P, AxisType
from jax.experimental.custom_partitioning import custom_partitioning
import cutlass.cute as cute
import cutlass.jax as cjax
from cutlass.jax.testing import create_tensor
import cuda.bindings.driver as cuda
"""
Examples of combining jax.jit, jax.shard_map and custom_partitioning for sharding
and executing kernels across multiple GPU devices.
To run this example:
.. code-block:: bash
# Run with addition operation
python examples/jax/cutlass_call_sharding.py
"""
@cute.kernel
def kernel(a: cute.Tensor, b: cute.Tensor, c: cute.Tensor):
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
frgA = cute.make_rmem_tensor(cute.size(a, mode=[0]), a.element_type)
frgB = cute.make_rmem_tensor(cute.size(b, mode=[0]), b.element_type)
frgC = cute.make_rmem_tensor(cute.size(c, mode=[0]), c.element_type)
cute.autovec_copy(a[None, tidx, bidx], frgA)
cute.autovec_copy(b[None, tidx, bidx], frgB)
frgC.store(frgA.load() + frgB.load())
cute.autovec_copy(frgC, c[None, tidx, bidx])
@cute.jit
def launch(
stream: cuda.CUstream,
a: cute.Tensor,
b: cute.Tensor,
c: cute.Tensor,
):
cute.printf("a: {}", a.layout)
cute.printf("b: {}", b.layout)
cute.printf("c: {}", c.layout)
kernel(a, b, c).launch(
grid=[a.shape[-1], 1, 1], block=[a.shape[-2], 1, 1], stream=stream
)
def sharded_cutlass_call_impl(a_block, b_block):
"""The sharded implementation that operates on a single device."""
call = cjax.cutlass_call(
launch,
use_static_tensors=True,
output_shape_dtype=jax.ShapeDtypeStruct(a_block.shape, a_block.dtype),
)
ref_result = a_block + b_block
return call(a_block, b_block), ref_result
@custom_partitioning
def custom_shared_call(a, b):
return sharded_cutlass_call_impl(a, b)
def custom_shared_call_partitioner(mesh, arg_shapes, result_shape):
arg_shardings = jax.tree.map(lambda x: x.sharding, arg_shapes)
result_shardings = tuple([arg_shardings[0]] * len(result_shape))
def lower_fn(*args):
return sharded_cutlass_call_impl(*args)
return mesh, lower_fn, result_shardings, arg_shardings
custom_shared_call.def_partition(custom_shared_call_partitioner)
def run_example():
# Create a device mesh with one axis b
ngpu = jax.device_count()
mesh = jax.make_mesh((ngpu,), "b", axis_types=(AxisType.Explicit,))
if ngpu == 1:
print("Note: only 1 GPU was detected.")
# We will shard our 3D tensors over b
sharding = P("b", None, None)
named_sharding = NamedSharding(mesh, sharding)
print("Testing shard_map...")
@partial(
jax.jit, static_argnums=[0, 1], out_shardings=(named_sharding, named_sharding)
)
def allocate_sharded_tensors(shape, dtype):
key = jax.random.key(1123)
a_key, b_key = jax.random.split(key, 2)
a = create_tensor(shape, dtype, a_key)
b = create_tensor(shape, dtype, b_key)
return a, b
@jax.jit
def compute(a, b):
# This jax.shard_map partitions the cutlass_call over the mesh.
@partial(
jax.shard_map,
mesh=mesh,
in_specs=(sharding, sharding),
out_specs=(sharding, sharding),
)
def sharded_call(a_block, b_block):
return sharded_cutlass_call_impl(a_block, b_block)
return sharded_call(a, b)
# Allocate (32, 16, 64) on each GPU
shape = (32 * ngpu, 16, 64)
dtype = jnp.float32
a, b = allocate_sharded_tensors(shape, dtype)
c, c_ref = compute(a, b)
assert jnp.allclose(c, c_ref)
print("Testing custom_partitioning...")
# Test custom_partitioning implementation which should produce identical results
@jax.jit
def compute_cp(a, b):
return custom_shared_call(a, b)
c, c_ref = compute_cp(a, b)
assert jnp.allclose(c, c_ref)
if __name__ == "__main__":
run_example()
print("PASS")

View File

@@ -0,0 +1,329 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import operator
from functools import partial
from typing import List
import cuda.bindings.driver as cuda
import cutlass
import cutlass.cute as cute
"""
An Elementwise Apply Example using CuTe DSL with cutlass.jax.cutlass_call
This example is similar to examples/ampere/elementwise_apply.py but demonstrates
how to run the code in a jax specific way using the cutlass_call primitive. It assumes
familiarity with basic CuTe DSL concepts as well as the cutlass_call primitive.
To run this example:
.. code-block:: bash
# Run with addition operation
python examples/jax/elementwise_apply_example.py --M 1024 --N 512 --op add
# Run with multiplication operation
python examples/ampere/elementwise_apply_example.py --M 1024 --N 512 --op mul
# Run with subtraction operation
python examples/ampere/elementwise_apply_example.py --M 1024 --N 512 --op sub
"""
@cute.kernel
def elementwise_apply_kernel(
op: cutlass.Constexpr,
mInputs: List[cute.Tensor],
mC: cute.Tensor,
cC: cute.Tensor, # coordinate tensor
shape: cute.Shape,
tv_layout: cute.Layout, # (tid, vid) -> logic coord
):
tidx, _, _ = cute.arch.thread_idx()
bidx, bidy, _ = cute.arch.block_idx()
###############################################################################
# Slice to local tile of thread block
###############################################################################
blk_crd = ((None, None), (bidx, bidy))
# Leverage the meta-programming capability of the DSL to slice the tensors for each input
# All for loops below on input tensors would be fully unrolled automatically at compile time
# logical coord -> memory address
gInputs = [t[blk_crd] for t in mInputs] # (TileM, TileN)
gC = mC[blk_crd] # (TileM, TileN)
gCrd = cC[blk_crd] # (TileM, TileN)
print("[DSL INFO] Sliced Tensors per thread block:")
for i in cutlass.range_constexpr(len(gInputs)):
print(f"[DSL INFO] ctaInputs{i} = {gInputs[i].type}")
print(f"[DSL INFO] gC = {gC.type}")
print(f"[DSL INFO] gCrd = {gCrd.type}")
###############################################################################
# Compose with thread block TV layout to map thread & value indices to memory address
###############################################################################
# (tid, vid) -> memory address
tidfrgInputs = [cute.composition(t, tv_layout) for t in gInputs]
tidfrgC = cute.composition(gC, tv_layout)
tidfrgCrd = cute.composition(gCrd, tv_layout)
# repeat None like vid to remove hierarchy of layout
thr_crd = (tidx, cute.repeat_like(None, tidfrgInputs[0][1]))
###############################################################################
# Slice to local tile of thread
###############################################################################
# vid -> address
thrInputs = [t[thr_crd] for t in tidfrgInputs] # (V)
thrC = tidfrgC[thr_crd] # (V)
thrCrd = tidfrgCrd[thr_crd]
print("[DSL INFO] Sliced Tensors per thread:")
for i in cutlass.range_constexpr(len(thrInputs)):
print(f"[DSL INFO] thrInputs{i} = {thrInputs[i].type}")
print(f"[DSL INFO] thrC = {thrC.type}")
print(f"[DSL INFO] thrCrd = {thrCrd.type}")
###############################################################################
# Compute predicate for out of boundary checks
###############################################################################
frgPred = cute.make_fragment(thrCrd.shape, cutlass.Boolean)
print(f"[DSL INFO] frgPred = {frgPred.type}")
for i in cutlass.range_constexpr(cute.size(frgPred)):
frgPred[i] = cute.elem_less(thrCrd[i], shape)
# if tidx == 0 and bidx == 0:
# cute.print_tensor(frgPred)
##########################################################
# Load data and compute result
##########################################################
# Load data before use. The compiler will optimize the copy and load
# operations to convert some memory ld/st into register uses.
result = op(*[thrInput.load() for thrInput in thrInputs])
thrC.store(result)
@cute.jit
def elementwise_apply(
op: cutlass.Constexpr, inputs, result: cute.Tensor, stream: cuda.CUstream
):
"""CUDA kernel applying binary operator on each element of two n-D input tensors in
CuTe Python and store to result tensor.
:param op: Binary operator or lambda function to apply element-wise
:type op: cutlass.Constexpr
:param a: First input tensor
:type a: cute.Tensor
:param b: Second input tensor
:type b: cute.Tensor
:param result: Output tensor to store the results of op(a, b)
:type result: cute.Tensor
:return: None
:rtype: None
"""
# Baseline: naive TV layout
# * mA layout: (4096, 4096):(4096, 1)
# * TV layout map to (512, 4) tile
# * tidx maps to mode-0 but input layout is contiguous on mode-1, performance will be bad
# tv_layout = cute.make_layout((128, (4, 4)), stride=(4, (512, 1)))
# cta_tiler = (512, 4)
# Opt-1: better TV layout with better 1D thread layout (SOL with 1D thread layout)
# * mA layout: (4096, 4096):(4096, 1)
# * TV layout map to (4, 512) tile
# * tidx maps to mode-1 which is leading mode of input tensor for coalesced load
# tv_layout = cute.make_layout((128, (4, 4)), stride=(16, (4, 1)))
# cta_tiler = (4, 512)
# Opt-2: 2D tile but worse
# * mA layout: (4096, 4096):(4096, 1)
# * TV layout map to (128, 16) logical tile
# * V layout is bad as contiguous mode is not on right-most
# * `cute.copy` only supports vectorize when stride-1 of v-layout on right-most )
# tv_layout = cute.make_layout(((32, 4), (4, 4)), stride=((4, 512), (1, 128)))
# cta_tiler = (128, 16)
# Opt-3: SOL with 2D thread tile
# * mA layout: (4096, 4096):(4096, 1)
# * TV layout map to (64, 256) logical tile
# * tidx maps to mode-1 and input layout is contiguous on mode-1 for coalesced load-store
# Use 128bit(16B) load as canonicalized form of val_layout then recast to target element-type
coalesced_ldst_bytes = 16
# Compile time validation: expect same element type for all input tensors
assert all(t.element_type == inputs[0].element_type for t in inputs)
dtype = inputs[0].element_type
thr_layout = cute.make_ordered_layout((4, 64), order=(1, 0))
val_layout = cute.make_ordered_layout((16, coalesced_ldst_bytes), order=(1, 0))
val_layout = cute.recast_layout(dtype.width, 8, val_layout)
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
print("[DSL INFO] Input Tensors:")
for i, t in enumerate(inputs):
print(f"[DSL INFO] inputs{i} = {t}")
print(f"[DSL INFO] result = {result}")
print("[DSL INFO] Tiling Parameters:")
print(f"[DSL INFO] tiler_mn = {tiler_mn} per thread block")
print(f"[DSL INFO] tv_layout = {tv_layout}")
print("[DSL INFO] Tiled Tensors:")
mInputs = [cute.zipped_divide(input, tiler_mn) for input in inputs]
# ((TileM, TileN), (RestM, RestN))
mC = cute.zipped_divide(result, tiler_mn)
# (RestM, RestN) -> (RestN, RestM)
remap_block = cute.make_ordered_layout(
cute.select(mInputs[0].shape[1], mode=[1, 0]), order=(1, 0)
)
for i, t in enumerate(mInputs):
print(f"[DSL INFO] gInputs{i} = {mInputs[i]}")
mInputs[i] = cute.composition(t, (None, remap_block))
print(f"[DSL INFO] gInputs{i} (remapped) = {mInputs[i]}")
mC = cute.composition(mC, (None, remap_block))
print(f"[DSL INFO] gC = {mC}")
idC = cute.make_identity_tensor(result.shape)
cC = cute.zipped_divide(idC, tiler=tiler_mn)
print(f"[DSL INFO] coord tensor = {cC}")
# Launch the kernel asynchronously
# Group input tensors into a list as a single argument
elementwise_apply_kernel(op, mInputs, mC, cC, result.shape, tv_layout).launch(
# Compute production at each mode of mC.shape[1] to get multi-dimensional grid size
grid=cute.product_each(mC.shape[1]),
block=[cute.size(tv_layout, mode=[0]), 1, 1],
stream=stream,
)
@cutlass.dsl_user_op
def leaky_relu(x, alpha, *, loc=None, ip=None):
return cute.where(x > 0, x, alpha * x, loc=loc, ip=ip)
def leaky_relu_ref(x, alpha):
import jax.numpy as jnp
return jnp.where(x > 0, x, alpha * x)
def run_and_verify(op, M, N, dtype, skip_ref_check=False):
import jax
import jax.numpy as jnp
import cutlass.jax as cjax
import cutlass.jax.testing as testing
if op == "leaky_relu":
op = partial(leaky_relu, alpha=0.01)
ref_op = partial(leaky_relu_ref, alpha=0.01)
num_inputs = 1
else:
op = getattr(operator, op)
ref_op = op
num_inputs = 2
# This jax function is transformed using jax.jit to compile its contents
# into an efficient HLO executable.
@partial(jax.jit, static_argnums=[1])
def jax_function(inputs, op):
call = cjax.cutlass_call(
# Bind jax arguments to kernel signature
lambda stream, inputs, output, *, op: elementwise_apply(
op, inputs, output, stream
),
# Specify output shape/dtype of result
output_shape_dtype=jax.ShapeDtypeStruct(inputs[0].shape, inputs[0].dtype),
# Pass static/constexpr values as kwargs
op=op,
)
# Call the kernel!
return call(inputs)
@partial(jax.jit, static_argnums=[1])
def jax_ref_function(inputs, op):
return op(*inputs)
print("\nRunning Elementwise Apply test with:")
print(f"Tensor dimensions: [{M}, {N}]")
print(f"Input and Output Data type: {dtype}")
jax_dtype = cjax.cutlass_to_jax_dtype(dtype)
keys = jax.random.split(jax.random.key(1435), num_inputs)
inputs = [testing.create_tensor((M, N), jax_dtype, key) for key in keys]
print("Input tensor shapes:")
for i in range(num_inputs):
print(f"inputs[{i}]: {inputs[i].shape}, dtype: {inputs[i].dtype}")
epsilon = 1.2
if op in (operator.truediv, operator.floordiv):
inputs[1] = jnp.where(inputs[1] == 0, epsilon, inputs[1])
# Call the jax.jit function which will compile the kernel
c = jax_function(inputs, op)
if not skip_ref_check:
print("Executing elementwise apply kernel...")
c = jax_function(inputs, op)
print("Verifying results...")
assert jnp.allclose(ref_op(*inputs), c)
print("Results verified successfully!")
print(f"First few elements of result: \n{c[:3, :3]}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Demonstration of calling a kernel with cutlass_call"
)
parser.add_argument("--M", default=4096, type=int)
parser.add_argument("--N", default=4096, type=int)
parser.add_argument("--op", default="add", type=str)
parser.add_argument("--skip_ref_check", action="store_true")
args = parser.parse_args()
run_and_verify(
args.op,
args.M,
args.N,
dtype=cutlass.Float32,
skip_ref_check=args.skip_ref_check,
)
print("\nPASS")

View File

@@ -0,0 +1,72 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import cutlass
import cutlass.cute as cute
from cutlass.utils import print_latex, print_latex_tv
from cutlass import for_generate, yield_out
"""
A Latex Printing Example using CuTe DSL.
This example prints latex for a given layout or thread value layout.
The primary goal for this example is to demonstrate how to dump latex, which can then be
turned into an image in your favorite latex compiler.
To run this example:
.. code-block:: bash
python examples/python/CuteDSL/cute/print_latex.py
python examples/python/CuteDSL/cute/print_latex.py --tv_layout
To compile, pipe the output to a file and use a tool like pdflatex:
.. code-block:: bash
python examples/python/CuTeDSL/cute/print_latex.py > latex.tex
pdflatex latex.tex
"""
@cute.jit
def main(print_tv_layout: cutlass.Constexpr[bool]):
# Note: only support compile time printing layouts
if cutlass.const_expr(print_tv_layout):
thr_layout = cute.make_ordered_layout((4, 32), order=(1, 0))
val_layout = cute.make_ordered_layout((4, 1), order=(1, 0))
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
print_latex_tv(tv_layout, tiler_mn)
else:
layout = cute.make_layout((10, 10))
print_latex(layout)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="example of print latex and print latex tv"
)
parser.add_argument("--tv_layout", action="store_true")
args = parser.parse_args()
main(args.tv_layout)

View File

@@ -0,0 +1,368 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import argparse
import cuda.bindings.driver as cuda
import cutlass
import cutlass.cute as cute
import cutlass.cute.testing as testing
from cutlass.cute.runtime import from_dlpack
def supports_pdl():
import torch
return torch.cuda.get_device_capability()[0] >= 9
"""
This example demonstrates the use of Programmatic Dependent Launch (PDL) using
CuTe DSL.
PDL is a mechanism which allows for overlapping execution of back-to-back kernels
within the same stream.
For example, consider the following two elementwise add operations, where the second
operation's first operand is the result of the first operation. While performing
``w = u + v`` we will load u and v, add them, and then store the result. Once we
have finished loading data, we are no longer utilizing the read bandwidth.
To effectively utilize the read bandwidth, we can start loading ``x``
immediately upon finishing reading. This is what PDL enables us to do.
.. code-block:: bash
w = u + v
y = w + x
To enable PDL, we need to do two things:
1. Insert the ``griddepcontrol.launch_dependents`` and ``griddepcontrol.wait`` instructions in the kernel.
2. Set the PDL launch attribute when launching the kernel.
The ``griddepcontrol.launch_dependents`` and ``griddepcontrol.wait``
instructions enable fine-grained control over kernel execution in PDL.
Once all thread blocks execute the ``griddepcontrol.launch_dependents``
instruction, the dependent kernels can opportunistically be early-launched.
``griddepcontrol.wait`` functions as a synchronization barrier - any warp
executing this instruction will block until the previous kernel finishes
execution. This allows precise control over data dependencies between kernels.
The following diagram shows the overlapping execution of two dependent kernels.
We call the instructions before ``griddepcontrol.wait`` as prologue (``P0``),
which may include barrier initialization and loading of independent data, etc.
We call the instructions after ``griddepcontrol.launch_dependents`` as epilogue
(``P2``), which may include math operations, data stores, etc. PDL enables
these prologue and epilogue phases to execute concurrently across dependent
kernels, improving GPU resource utilization. This is particularly beneficial
when prologue and epilogue are bound by different resources (e.g., memory
bandwidth vs compute throughput).
# P0: Prologue, P1: Main compute, P2: Epilogue
P0 P1 P2
K1: |=====|+++++|-----|
<-----> K2 can start early
(K1's P2 overlaps with K2's P0)
P0 P1 P2
K2: |=====| |+++++|-----|
^
|
wait for K1 to complete
Time ------------------------------------------------------>
We could run this example with and without PDL:
.. code-block:: bash
python examples/blackwell/programmatic_dependent_launch.py --benchmark
python examples/blackwell/programmatic_dependent_launch.py --benchmark --use_pdl
From the benchmark results, you can see some speedups for the PDL version in most cases, benefiting from
the overlapping execution of consecutive kernels. Moreover, you can use nsys to observe the overlapping execution.
.. code-block:: bash
nsys profile python examples/blackwell/programmatic_dependent_launch.py --benchmark --use_pdl
Note, PDL feature is supported on Hopper and later GPUs.
See [the programming guide](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#programmatic-dependent-launch-and-synchronization)
and the [PTX documentation](https://docs.nvidia.com/cuda/parallel-thread-execution/index.html#parallel-synchronization-and-communication-instructions-griddepcontrol)
for more details.
"""
@cute.kernel
def elementwise_add_kernel(
gA: cute.Tensor,
gB: cute.Tensor,
gC: cute.Tensor,
cC: cute.Tensor, # coordinate tensor
shape: cute.Shape,
thr_layout: cute.Layout,
val_layout: cute.Layout,
is_first_kernel: cutlass.Constexpr = True,
):
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
blk_coord = ((None, None), bidx)
blkA = gA[blk_coord] # (TileM,TileN)
blkB = gB[blk_coord] # (TileM,TileN)
blkC = gC[blk_coord] # (TileM,TileN)
blkCrd = cC[blk_coord] # (TileM, TileN)
copy_atom_load = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), gA.element_type)
copy_atom_store = cute.make_copy_atom(cute.nvgpu.CopyUniversalOp(), gC.element_type)
tiled_copy_A = cute.make_tiled_copy_tv(copy_atom_load, thr_layout, val_layout)
tiled_copy_B = cute.make_tiled_copy_tv(copy_atom_load, thr_layout, val_layout)
tiled_copy_C = cute.make_tiled_copy_tv(copy_atom_store, thr_layout, val_layout)
thr_copy_A = tiled_copy_A.get_slice(tidx)
thr_copy_B = tiled_copy_B.get_slice(tidx)
thr_copy_C = tiled_copy_C.get_slice(tidx)
thrA = thr_copy_A.partition_S(blkA)
thrB = thr_copy_B.partition_S(blkB)
thrC = thr_copy_C.partition_S(blkC)
frgA = cute.make_fragment_like(thrA)
frgB = cute.make_fragment_like(thrB)
frgC = cute.make_fragment_like(thrC)
thrCrd = thr_copy_C.partition_S(blkCrd)
frgPred = cute.make_rmem_tensor(thrCrd.shape, cutlass.Boolean)
for i in range(cute.size(frgPred)):
val = cute.elem_less(thrCrd[i], shape)
frgPred[i] = val
# Note: when not using cuda-graph, the kernel execution may be blocked by the host overhead.
# In this case we won't see overlapping even when pdl is enabled.
# In this example, we add a loop (10 times) for all the copy and compute operations in the following code
# to make kernel running longer and make pdl benefits observable for both cuda-graph enabled and disabled cases.
if is_first_kernel:
for _ in range(10):
cute.copy(copy_atom_load, thrA, frgA, pred=frgPred)
cute.copy(copy_atom_load, thrB, frgB, pred=frgPred)
# Here we add the launch dependents instruction for the first kernel as a hint to the runtime to early-launch
# the next kernel. If the next kernel becomes concurrent, we will have overlap where the second kernel
# can start reading x to ensure an E2E speedup. Note the placement of launch dependents has no implication
# on correctness, only performance.
cute.arch.griddepcontrol_launch_dependents()
else:
# In this example, the second kernel's second operand ``gB`` has no dependencies, its loading can overlap
# with the computation of ``gC`` from the first kernel.
for _ in range(10):
cute.copy(copy_atom_load, thrB, frgB, pred=frgPred)
# For the second kernel, its first operand ``gA`` is dependent on the previous kernel, we must call
# griddepcontrol.wait to assure correctness. This instruction will block until the prior kernels finishes
# and its memory operations are visible. Since gA is written by the prior kernel, this will block until gA
# is visible to our kernel. Without it, we would have undefined behavior due to a race condition.
cute.arch.griddepcontrol_wait()
for _ in range(10):
cute.copy(copy_atom_load, thrA, frgA, pred=frgPred)
for _ in range(10):
result = frgA.load() + frgB.load()
frgC.store(result)
cute.copy(copy_atom_store, frgC, thrC, pred=frgPred)
@cute.jit
def elementwise_add(
mA,
mB,
mC,
stream: cuda.CUstream,
use_pdl: cutlass.Constexpr = True,
is_first_kernel: cutlass.Constexpr = True,
):
dtype = mA.element_type
# copy_bits for a thread is 128 bits, and we use 128 // dtype.width to get the vector size
vector_size = 128 // dtype.width
thr_layout = cute.make_ordered_layout((4, 32), order=(1, 0))
val_layout = cute.make_ordered_layout((4, vector_size), order=(1, 0))
tiler_mn, tv_layout = cute.make_layout_tv(thr_layout, val_layout)
gA = cute.zipped_divide(mA, tiler_mn) # ((TileM,TileN),(RestM,RestN))
gB = cute.zipped_divide(mB, tiler_mn) # ((TileM,TileN),(RestM,RestN))
gC = cute.zipped_divide(mC, tiler_mn) # ((TileM,TileN),(RestM,RestN))
idC = cute.make_identity_tensor(mC.shape)
cC = cute.zipped_divide(idC, tiler=tiler_mn)
elementwise_add_kernel(
gA, gB, gC, cC, mC.shape, thr_layout, val_layout, is_first_kernel
).launch(
grid=[cute.size(gC, mode=[1]), 1, 1],
block=[cute.size(tv_layout, mode=[0]), 1, 1],
stream=stream,
use_pdl=use_pdl,
)
def run_pdl_example(
M,
N,
skip_ref_check=False,
benchmark=False,
warmup_iterations=5,
iterations=100,
use_pdl=True,
):
import torch
if not torch.cuda.is_available():
raise RuntimeError("Blackwell/Hopper GPU is required to run this example!")
print("\nRunning Elementwise Add test with:")
print(f"Tensor dimensions: [{M}, {N}]")
print(f"Use PDL: {use_pdl}")
u = torch.randn(M, N, dtype=torch.float32, device="cuda")
v = torch.randn(M, N, dtype=torch.float32, device="cuda")
w = torch.randn(M, N, dtype=torch.float32, device="cuda")
x = torch.randn(M, N, dtype=torch.float32, device="cuda")
y = torch.empty(M, N, dtype=torch.float32, device="cuda")
u_tensor = from_dlpack(u).mark_layout_dynamic()
v_tensor = from_dlpack(v).mark_layout_dynamic()
w_tensor = from_dlpack(w).mark_layout_dynamic()
x_tensor = from_dlpack(x).mark_layout_dynamic()
y_tensor = from_dlpack(y).mark_layout_dynamic()
stream = torch.cuda.Stream()
current_stream = cuda.CUstream(stream.cuda_stream)
# Since is_first_kernel is cutlass.Constexpr, we need to compile for
# the first and second kernel separately.
compiled_func_first_kernel = cute.compile(
elementwise_add,
u_tensor,
v_tensor,
w_tensor,
current_stream,
use_pdl,
is_first_kernel=True,
options="--enable-tvm-ffi",
)
compiled_func_second_kernel = cute.compile(
elementwise_add,
w_tensor,
x_tensor,
y_tensor,
current_stream,
use_pdl,
is_first_kernel=False,
options="--enable-tvm-ffi",
)
# launch and run the two consecutive kernels in a same stream.
def run_func(current_stream, u, v, w, x, y):
# Run first operation: w_tensor = u_tensor + v_tensor
compiled_func_first_kernel(
u,
v,
w,
current_stream,
)
# Run second operation: y_tensor = w_tensor + x_tensor
# its first operand ``w_tensor`` is the result of the first operation,
# they use the same memory space.
compiled_func_second_kernel(
w,
x,
y,
current_stream,
)
if not skip_ref_check:
run_func(current_stream, u, v, w, x, y)
print("Verifying results...")
torch.testing.assert_close(u.cpu() + v.cpu() + x.cpu(), y.cpu())
print("Results verified successfully!")
if not benchmark:
return
def generate_kernel_arguments():
u = torch.randn(M, N, dtype=torch.float32, device="cuda")
v = torch.randn(M, N, dtype=torch.float32, device="cuda")
w = torch.randn(M, N, dtype=torch.float32, device="cuda")
x = torch.randn(M, N, dtype=torch.float32, device="cuda")
y = torch.empty(M, N, dtype=torch.float32, device="cuda")
return testing.JitArguments(current_stream, u, v, w, x, y)
avg_time_us = testing.benchmark(
run_func,
workspace_generator=generate_kernel_arguments,
workspace_count=10,
warmup_iterations=warmup_iterations,
iterations=iterations,
stream=current_stream,
use_cuda_graphs=True,
)
print(f"Execution time: {avg_time_us:.4f} us")
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="example of Programmatic Dependent Launch (PDL) using CuTe DSL"
)
parser.add_argument("--M", default=256, type=int)
parser.add_argument("--N", default=256, type=int)
parser.add_argument("--warmup_iterations", default=5, type=int)
parser.add_argument("--iterations", default=10, type=int)
parser.add_argument("--skip_ref_check", action="store_true")
parser.add_argument("--benchmark", action="store_true")
parser.add_argument("--use_pdl", action="store_true")
args = parser.parse_args()
if supports_pdl():
run_pdl_example(
args.M,
args.N,
skip_ref_check=args.skip_ref_check,
benchmark=args.benchmark,
warmup_iterations=args.warmup_iterations,
iterations=args.iterations,
use_pdl=args.use_pdl,
)
print("\nPASS")
else:
print(
"PDL is not supported on this device, it requires Hopper or newer generations"
)

View File

@@ -0,0 +1,223 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass.cute as cute
import cutlass
import numpy as np
from cutlass.cute.runtime import from_dlpack
"""
A Shared Memory Allocator Example on NVIDIA Ampere architecture using CuTe DSL.
This example demonstrates how to allocate and manage shared memory in JIT kernels by using the SmemAllocator in CuTe DSL.
It shows various ways to allocate different data structures in shared memory:
1. Struct allocation with natural and strict alignment
2. Raw memory block allocation with custom alignment
3. Array allocation with automatic alignment
4. Tensor allocation with layout specification
The example includes:
- Shared storage struct with mixed alignment requirements
- Memory allocation patterns for different data types
- Tensor operations on allocated memory
To run this example:
.. code-block:: bash
python examples/ampere/smem_allocator.py
The example will allocate shared memory, perform tensor operations, and verify the results.
"""
@cute.struct
class complex:
real: cutlass.Float32
imag: cutlass.Float32
# SharedStorage size is 512, alignment is 128
@cute.struct
class SharedStorage:
# struct elements with natural alignment
a: cute.struct.MemRange[cutlass.Float32, 32] # array
b: cutlass.Int64 # scalar
c: complex # nested struct
# struct elements with strict alignment
x: cute.struct.Align[
cute.struct.MemRange[cutlass.Float32, 32],
128,
]
y: cute.struct.Align[cutlass.Int32, 8]
z: cute.struct.Align[complex, 16]
@cute.kernel
def kernel(
const_a: cutlass.Constexpr,
dst_a: cute.Tensor,
const_b: cutlass.Constexpr,
dst_b: cute.Tensor,
const_c: cutlass.Constexpr,
dst_c: cute.Tensor,
):
# Note: SMEM_SIZE bytes (specified in kernel().launch(smem=...)) can be reserved for developer to utilize
# Note: alignment of initial allocator base ptr is 1024
allocator = cutlass.utils.SmemAllocator()
# base ptr of allocator points at: SMEM_ADDR_START (the starting address of available shared memory)
# -- Allocate a scalar
int_ptr = allocator.allocate(cutlass.Int32)
# base ptr of allocator now points at: SMEM_ADDR_AFTER_INT = SMEM_ADDR_START + aligned_size(int)
assert int_ptr.dtype == cutlass.Int32, "Expected Int32, but got {}".format(
int_ptr.dtype
)
# -- Allocate a struct --
# Note: when specified alignment, max(alignment, alignof(struct)) will be applied
# reserves the section of struct in smem, elements in the struct can be accessed by ptr
struct_in_smem = allocator.allocate(SharedStorage)
# base ptr of allocator now points at: SMEM_ADDR_AFTER_STRUCT = SMEM_ADDR_START + aligned_size(struct)
# -- Allocate a block of memory --
# reserves a section of 64 bytes in smem, align to 128 bytes, returns the section base ptr
section_in_smem = allocator.allocate(64, byte_alignment=128)
# base ptr of allocator now points at: SMEM_ADDR_AFTER_SECTION = SMEM_ADDR_AFTER_STRUCT + aligned_size(section)
# -- Allocate an array --
# reserves an int64 array of size 14 in smem, returns the array base ptr
array_in_smem = allocator.allocate_array(element_type=cutlass.Int64, num_elems=14)
# base ptr of allocator now points at: SMEM_ADDR_AFTER_ARRAY = SMEM_ADDR_AFTER_SECTION + aligned_size(array)
# -- Allocate a tensor --
# Note: use cute.ComposedLayout or cute.Layout to specify layout of tensor
# Note: iterator swizzle with swizzle layout is currently not supported
layout = cute.make_layout((16, 2))
tensor_in_smem = allocator.allocate_tensor(
element_type=cutlass.Float32, layout=layout, byte_alignment=32, swizzle=None
)
# base ptr of allocator now points at: SMEM_ADDR_AFTER_TENSOR = SMEM_ADDR_AFTER_ARRAY + aligned_size(tensor)
# ptr<f16, smem, align<1024>>
# ptr<i64, smem, align<128>>
# ptr<f32, smem, align<8>>
print(struct_in_smem.a.data_ptr())
print(struct_in_smem.b.ptr)
print(struct_in_smem.c.real.ptr)
# ptr<i8, smem, align<512>>
print(section_in_smem)
# ptr<i64, smem, align<64>>
print(array_in_smem)
# tensor<ptr<f16, smem, align<32>> o (16,4):(1,16)>
print(tensor_in_smem)
# assign struct member array element
cute.printf("struct_in_smem.a[0] = {}", struct_in_smem.a[0])
struct_in_smem.a[0] = 2
cute.printf("struct_in_smem.a[0] = {}", struct_in_smem.a[0])
# assign struct member scalar
cute.printf("struct_in_smem.b.ptr = {}", struct_in_smem.b.ptr)
cute.printf("struct_in_smem.b: value = {}", struct_in_smem.b.ptr.load())
struct_in_smem.b = 16
cute.printf("struct_in_smem.b: value = {}", struct_in_smem.b.ptr.load())
# fill MemRange tensor in struct and copy to dst
a_tensor = struct_in_smem.a.get_tensor(cute.make_layout((8, 4)))
a_tensor.fill(const_a)
cute.printf("cute.struct.MemRange: {}", a_tensor)
dst_a.store(a_tensor.load())
# convert block of smem to fill tensor and copy to dst
layout = cute.make_layout((8, 2))
sec_ptr = cute.recast_ptr(section_in_smem, dtype=cutlass.Float32)
sec_tensor = cute.make_tensor(sec_ptr, layout)
sec_tensor.fill(const_b)
cute.printf("block of memory: {}", sec_tensor)
dst_b.store(sec_tensor.load())
# fill allocated tensor in smem and copy to dst
tensor_in_smem.fill(const_c)
cute.printf("tensor in smem: {}", tensor_in_smem)
dst_c.store(tensor_in_smem.load())
@cute.jit
def host(
const_a: cutlass.Constexpr,
dst_a: cute.Tensor,
const_b: cutlass.Constexpr,
dst_b: cute.Tensor,
const_c: cutlass.Constexpr,
dst_c: cute.Tensor,
):
# Note: Shared Memory size is automatically calculated now
kernel(const_a, dst_a, const_b, dst_b, const_c, dst_c).launch(
grid=(1, 1, 1),
block=(1, 1, 1),
# Automatically calculate the launch kernel shared memory usage when `smem=None`
)
def run_and_verify(const_a, const_b, const_c):
import torch
dst_a = torch.zeros((8, 4), dtype=torch.float32, device="cuda")
dst_b = torch.zeros((8, 2), dtype=torch.float32, device="cuda")
dst_c = torch.zeros((16, 2), dtype=torch.float32, device="cuda")
host(
const_a,
from_dlpack(dst_a),
const_b,
from_dlpack(dst_b),
const_c,
from_dlpack(dst_c),
)
assert const_a == dst_a.cpu()[0, 0], (
f"Expected {const_a}, but got {dst_a.cpu()[0, 0]}"
)
assert const_b == dst_b.cpu()[0, 0], (
f"Expected {const_b}, but got {dst_b.cpu()[0, 0]}"
)
assert const_c == dst_c.cpu()[0, 0], (
f"Expected {const_c}, but got {dst_c.cpu()[0, 0]}"
)
if __name__ == "__main__":
# prepare cuda context
cutlass.cuda.initialize_cuda_context()
# An example for shared memory allocation
const_a = 0.5
const_b = 1.0
const_c = 2.0
run_and_verify(const_a, const_b, const_c)

View File

@@ -0,0 +1,80 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
"""Example demonstrating how to use CuTe with PyTorch's FakeTensor mode.
This example shows how to:
1. Use PyTorch's FakeTensor mode to compile a CuTe function without real data
2. Execute the compiled function on real data later
FakeTensor mode allows compiling code without allocating real memory, which is useful
for ahead-of-time compilation scenarios. The compiled function can then be executed
on real tensors that match the expected shapes and dtypes.
Primary goals of this example are to demonstrate: How to use PyTorch's FakeTensor mode with CuTe
to enable ahead-of-time compilation without real data allocation.
The example:
1. Creates a fake tensor in PyTorch using FakeTensor mode
2. Compiles a CuTe function using the fake tensor without allocating real memory
3. Creates a real tensor with matching shape and dtype
4. Executes the compiled function on the real tensor
To run this example:
.. code-block:: bash
python examples/cute/torch_fake_tensor.py
"""
@cute.jit
def print_tensor(t: cute.Tensor):
cute.print_tensor(t)
def run():
import torch
from torch._subclasses.fake_tensor import FakeTensorMode
shape = (3, 4)
with FakeTensorMode():
fake_tensor = torch.zeros(shape, dtype=torch.float32)
compiled_fn = cute.compile(print_tensor, from_dlpack(fake_tensor))
real_tensor = torch.randn(shape, dtype=torch.float32)
compiled_fn(from_dlpack(real_tensor))
if __name__ == "__main__":
run()

View File

@@ -0,0 +1,170 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import sys
import os
import torch
import cutlass
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
"""Demonstrates calling off-the-shelf kernels with TVM FFI without DLPack.
This example shows how to compile CuTe JIT function with fake tensors then run it with TVM FFI.
"""
if __name__ == "__main__":
# Add the current directory to sys.path
current_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(current_dir, "..", ".."))
from cute.ampere.kernel.dense_gemm.tensorop_gemm import TensorOpGemm
@cute.jit
def bmm(
a: cute.Tensor, # (l, m, k)
b: cute.Tensor, # (l, k, n)
c: cute.Tensor, # (l, m, n)
):
gemm_op = TensorOpGemm(cutlass.Float16, cutlass.Float16, cutlass.Float32, (2, 2, 1))
# Permute to follow convention of CuTe
# (l, m, k) -> (m, k, l)
a = cute.make_tensor(a.iterator, cute.select(a.layout, mode=[1, 2, 0]))
# (l, k, n) -> (n, k, l)
b = cute.make_tensor(b.iterator, cute.select(b.layout, mode=[2, 1, 0]))
# (l, m, n) -> (m, n, l)
c = cute.make_tensor(c.iterator, cute.select(c.layout, mode=[1, 2, 0]))
gemm_op(a, b, c)
def compile_bmm_dynamic_layout():
from cutlass.cute.runtime import make_fake_compact_tensor
m = cute.sym_int()
n = cute.sym_int(divisibility=16)
k = cute.sym_int(divisibility=16)
l = cute.sym_int()
# Contiguous on K
fake_a = make_fake_compact_tensor(
cutlass.Float16, (l, m, k), stride_order=(2, 1, 0), assumed_align=16
)
# Contiguous on N
fake_b = make_fake_compact_tensor(
cutlass.Float16, (l, k, n), stride_order=(2, 1, 0), assumed_align=16
)
# Contiguous on N
fake_c = make_fake_compact_tensor(
cutlass.Float16, (l, m, n), stride_order=(2, 1, 0), assumed_align=16
)
compiled_fn = cute.compile(bmm, fake_a, fake_b, fake_c, options="--enable-tvm-ffi")
return compiled_fn
def compile_bmm_static_layout(m, n, k, l):
from cutlass.cute.runtime import make_fake_compact_tensor
fake_a = make_fake_compact_tensor(
cutlass.Float16, (l, m, k), stride_order=(2, 1, 0), assumed_align=16
)
fake_b = make_fake_compact_tensor(
cutlass.Float16, (l, k, n), stride_order=(2, 1, 0), assumed_align=16
)
fake_c = make_fake_compact_tensor(
cutlass.Float16, (l, m, n), stride_order=(2, 1, 0), assumed_align=16
)
compiled_fn = cute.compile(bmm, fake_a, fake_b, fake_c, options="--enable-tvm-ffi")
return compiled_fn
def run_bmm_and_verify(compiled_fn, m, n, k, l):
torch.manual_seed(1112)
a = torch.randn(l, m, k, dtype=torch.float16, device="cuda")
b = torch.randn(l, k, n, dtype=torch.float16, device="cuda")
c = torch.randn(l, m, n, dtype=torch.float16, device="cuda")
print("[Runtime INFO] Input tensor shapes:")
print(f"a: {a.shape=}, {a.stride()=}, {a.dtype=}")
print(f"b: {b.shape=}, {b.stride()=}, {b.dtype=}")
print(f"c: {c.shape=}, {c.stride()=}, {c.dtype=}\n")
# pass in torch tensor as input
compiled_fn(a, b, c)
torch.cuda.synchronize()
ref = torch.bmm(a, b)
torch.testing.assert_close(c, ref, atol=1e-05, rtol=1e-05)
print("[Runtime INFO] Verification successful!")
print(f" First few elements of result: \n{c[:3, :3, :3]}")
if __name__ == "__main__":
m, n, k, l = (512, 512, 256, 2)
compiled_fn_dynamic = compile_bmm_dynamic_layout()
run_bmm_and_verify(compiled_fn_dynamic, m, n, k, l)
compiled_fn_static = compile_bmm_static_layout(m, n, k, l)
run_bmm_and_verify(compiled_fn_static, m, n, k, l)
# Error Check:
# 1. mis-matched tensor dim raise error
a = torch.randn(l, m, k, dtype=torch.float16, device="cuda")
b = torch.randn(l, 2 * k, n, dtype=torch.float16, device="cuda")
c = torch.randn(l, m, n, dtype=torch.float16, device="cuda")
try:
compiled_fn_dynamic(a, b, c)
except Exception as e:
print(f"\n[Runtime Error]: {e}")
# 2. mis-matched divisibility
a = torch.randn(l, m, k + 1, dtype=torch.float16, device="cuda")
b = torch.randn(l, k + 1, n, dtype=torch.float16, device="cuda")
c = torch.randn(l, m, n, dtype=torch.float16, device="cuda")
try:
compiled_fn_dynamic(a, b, c)
except Exception as e:
print(f"\n[Runtime Error]: {e}")
# 3. mis-matched static shape constraint
a = torch.randn(l * 2, m, k, dtype=torch.float16, device="cuda")
b = torch.randn(l * 2, k, n, dtype=torch.float16, device="cuda")
c = torch.randn(l * 2, m, n, dtype=torch.float16, device="cuda")
try:
compiled_fn_static(a, b, c)
except Exception as e:
print(f"\n[Runtime Error]: {e}")

View File

@@ -0,0 +1,93 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Example demonstrating how to use TVM-FFI ABI with CuTe.
This example shows how to:
1. Compile a CuTe function with "--enable-tvm-ffi" option
2. Export the compiled function to a shared library
3. Load the shared library and use the compiled function to work with torch.Tensor
To run this example:
.. code-block:: bash
python cutlass_ir/compiler/python/examples/cute/tvm_ffi/aot_export.py
# run example to use in torch
python cutlass_ir/compiler/python/examples/cute/tvm_ffi/aot_use_in_torch.py
# run example to use in jax
python cutlass_ir/compiler/python/examples/cute/tvm_ffi/aot_use_in_jax.py
# run example to use in c++ bundle
bash cutlass_ir/compiler/python/examples/cute/tvm_ffi/aot_use_in_cpp_bundle.sh
"""
from pathlib import Path
import os
import subprocess
import tvm_ffi
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
@cute.kernel
def device_add_one(a: cute.Tensor, b: cute.Tensor):
for i in range(a.shape[0]):
b[i] = a[i] + 1
@cute.jit
def add_one(a: cute.Tensor, b: cute.Tensor):
"""b = a + 1"""
device_add_one(a, b).launch(grid=(1, 1, 1), block=(1, 1, 1))
def main():
import torch
# compile the kernel with "--enable-tvm-ffi" option
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.zeros(10, dtype=torch.float32, device="cuda")
a_cute = from_dlpack(a_torch, enable_tvm_ffi=True).mark_layout_dynamic()
b_cute = from_dlpack(b_torch, enable_tvm_ffi=True).mark_layout_dynamic()
# compile the kernel with "--enable-tvm-ffi" option
compiled_add_one = cute.compile(add_one, a_cute, b_cute, options="--enable-tvm-ffi")
os.makedirs("./build", exist_ok=True)
object_file_path = "./build/add_one.o"
lib_path = "./build/add_one.so"
compiled_add_one.export_to_c(object_file_path, function_name="add_one")
shared_libs = cute.runtime.find_runtime_libraries(enable_tvm_ffi=True)
# compile the object file to a shared library
cmd = ["gcc", "-shared", "-o", lib_path, object_file_path, *shared_libs]
print(cmd)
subprocess.run(cmd, check=True)
print(f"Successfully created shared library: {lib_path}")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,94 @@
// clang-format off
/*
* SPDX-FileCopyrightText: Copyright (c) 2023 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: LicenseRef-NvidiaProprietary
*
* NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
* property and proprietary rights in and to this material, related
* documentation and any modifications thereto. Any use, reproduction,
* disclosure or distribution of this material and related documentation
* without an express license agreement from NVIDIA CORPORATION or
* its affiliates is strictly prohibited.
*/
// clang-format on
// This example shows how to interface with an AOT compiled function in a C++
// bundle. to build and run the example, run the following command in project
// root bash
// cutlass_ir/compiler/python/examples/cute/tvm_ffi/aot_use_in_cpp_bundle.sh
#include <cuda_runtime.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/extra/module.h>
#include <iostream>
#include <vector>
namespace ffi = tvm::ffi;
struct CUDANDAlloc {
void AllocData(DLTensor* tensor) {
size_t data_size = ffi::GetDataSize(*tensor);
void* ptr = nullptr;
cudaError_t err = cudaMalloc(&ptr, data_size);
TVM_FFI_ICHECK_EQ(err, cudaSuccess) << "cudaMalloc failed: " << cudaGetErrorString(err);
tensor->data = ptr;
}
void FreeData(DLTensor* tensor) {
if (tensor->data != nullptr) {
cudaError_t err = cudaFree(tensor->data);
TVM_FFI_ICHECK_EQ(err, cudaSuccess) << "cudaFree failed: " << cudaGetErrorString(err);
tensor->data = nullptr;
}
}
};
inline ffi::Tensor Empty(ffi::Shape shape, DLDataType dtype, DLDevice device) {
return ffi::Tensor::FromNDAlloc(CUDANDAlloc(), shape, dtype, device);
}
// symbol from the shared library
extern "C" int __tvm_ffi_add_one(void*, const TVMFFIAny*, int32_t, TVMFFIAny*);
// Redirects into the exported function in object
void CallAddOne(ffi::TensorView x, ffi::TensorView y) {
tvm::ffi::Function::InvokeExternC(nullptr, __tvm_ffi_add_one, x, y);
}
int main() {
DLDataType f32_dtype{kDLFloat, 32, 1};
DLDevice cuda_device{kDLCUDA, 0};
constexpr int ARRAY_SIZE = 10;
ffi::Tensor x = Empty({ARRAY_SIZE}, f32_dtype, cuda_device);
ffi::Tensor y = Empty({ARRAY_SIZE}, f32_dtype, cuda_device);
std::vector<float> host_x(ARRAY_SIZE);
for (int i = 0; i < ARRAY_SIZE; ++i) {
host_x[i] = static_cast<float>(i);
}
size_t nbytes = host_x.size() * sizeof(float);
cudaError_t err = cudaMemcpy(x.data_ptr(), host_x.data(), nbytes, cudaMemcpyHostToDevice);
TVM_FFI_ICHECK_EQ(err, cudaSuccess)
<< "cudaMemcpy host to device failed: " << cudaGetErrorString(err);
// Call into the FFI function; tensors remain on device because they carry a
// kDLCUDA device tag.
CallAddOne(x, y);
std::vector<float> host_y(host_x.size());
err = cudaMemcpy(host_y.data(), y.data_ptr(), nbytes, cudaMemcpyDeviceToHost);
TVM_FFI_ICHECK_EQ(err, cudaSuccess)
<< "cudaMemcpy device to host failed: " << cudaGetErrorString(err);
std::cout << "y after add_one_cuda(x, y)" << std::endl;
for (float value : host_y) {
std::cout << value << " ";
}
std::cout << std::endl;
return 0;
}

View File

@@ -0,0 +1,48 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#!/bin/bash
# Set up library paths for runtime
export LD_LIBRARY_PATH=$(python3 -m cutlass.cute.export.aot_config --libdir):$(tvm-ffi-config --libdir):$LD_LIBRARY_PATH
CUDA_HOME=/usr/local/cuda
SOURCE_FILE="$(dirname "$0")/aot_use_in_cpp_bundle.cpp"
echo "Compiling the executable..."
g++ -o build/aot_use_in_cpp_bundle \
-I${CUDA_HOME}/include \
`tvm-ffi-config --cxxflags` \
${SOURCE_FILE} build/add_one.o \
$(python3 -m cutlass.cute.export.aot_config --ldflags) \
-L${CUDA_HOME}/lib64 \
$(python3 -m cutlass.cute.export.aot_config --libs) -lcuda -lcudart \
$(tvm-ffi-config --ldflags) \
$(tvm-ffi-config --libs)
echo "Running the executable..."
./build/aot_use_in_cpp_bundle

View File

@@ -0,0 +1,52 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import jax
import jax.numpy as jnp
import jax_tvm_ffi
import cutlass.cute as cute
# now load it back
def main():
a_jax = jnp.arange(10, dtype=jnp.float32)
b_jax = jnp.zeros(10, dtype=jnp.float32)
lib_path = "./build/add_one.so"
aot_mod = cute.runtime.load_module(lib_path, enable_tvm_ffi=True)
jax_tvm_ffi.register_ffi_target("add_one_cute", aot_mod.add_one, platform="gpu")
b_jax = jax.ffi.ffi_call(
"add_one_cute",
jax.ShapeDtypeStruct(a_jax.shape, a_jax.dtype),
vmap_method="broadcast_all",
)(a_jax)
print("result of b after aot_mod.add_one(a, b)")
print(b_jax)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,46 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import cutlass.cute as cute
# now load it back
def main():
import torch
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.zeros(10, dtype=torch.float32, device="cuda")
lib_path = "./build/add_one.so"
aot_mod = cute.runtime.load_module(lib_path, enable_tvm_ffi=True)
aot_mod.add_one(a_torch, b_torch)
print("result of b after aot_mod.add_one(a, b)")
print(b_torch)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,48 @@
import cutlass
import cutlass.cute as cute
"""
Example of using fake tensors in CuTe.
This script demonstrates how to use fake tensors in CuTe to drive compilation without creating actual tensors
from frameworks like PyTorch or TensorFlow.
Run this file directly to see the output type information.
"""
@cute.jit
def print_tensor_type(t: cute.Tensor):
print(t)
def run():
from cutlass.cute.runtime import make_fake_compact_tensor, make_fake_tensor
shape = (3, 4)
a = make_fake_compact_tensor(cutlass.Float16, (3, 4), stride_order=(1, 0))
cute.compile(print_tensor_type, a, options="--enable-tvm-ffi")
# 32-bit symbolic integer with divisibility 8
shape = (3, cute.sym_int32(divisibility=8))
a = make_fake_compact_tensor(cutlass.Float16, shape, stride_order=(1, 0))
cute.compile(print_tensor_type, a, options="--enable-tvm-ffi")
# with static stride
a = make_fake_tensor(cutlass.Float16, shape, stride=(4, 1))
cute.compile(print_tensor_type, a, options="--enable-tvm-ffi")
# with dynamic stride using 32bit integer
stride = (cute.sym_int32(divisibility=8), 1)
a = make_fake_tensor(cutlass.Float16, shape, stride=stride)
cute.compile(print_tensor_type, a, options="--enable-tvm-ffi")
# with dynamic stride using 64bit integer
stride = (cute.sym_int64(divisibility=8), 1)
a = make_fake_tensor(cutlass.Float16, shape, stride=stride)
cute.compile(print_tensor_type, a, options="--enable-tvm-ffi")
if __name__ == "__main__":
run()

View File

@@ -0,0 +1,72 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Example demonstrating how to use TVM-FFI ABI with CuTe.
This example shows how to:
1. Compile a CuTe function with "--enable-tvm-ffi" option
2. Directly use the compiled function to work with torch.Tensor
To run this example:
.. code-block:: bash
python cutlass_ir/compiler/python/examples/cute/tvm_ffi/error_reporting.py
"""
import torch
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
@cute.kernel
def device_add_one(a: cute.Tensor, b: cute.Tensor):
for i in range(a.shape[0]):
b[i] = a[i] + 1
@cute.jit
def add_one(a: cute.Tensor, b: cute.Tensor):
"""b = a + 1"""
device_add_one(a, b).launch(grid=(1, 1, 1), block=(1, 1, 1))
def main():
# compile the kernel with "--enable-tvm-ffi" option
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.zeros(10, dtype=torch.float32, device="cuda")
a_cute = from_dlpack(a_torch, enable_tvm_ffi=True)
b_cute = from_dlpack(b_torch, enable_tvm_ffi=True)
# compile the kernel with "--enable-tvm-ffi" option
compiled_add_one = cute.compile(add_one, a_cute, b_cute, options="--enable-tvm-ffi")
# should raise an error because of shape mismatch
compiled_add_one(torch.arange(5, dtype=torch.float32, device="cuda"), b_cute)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,93 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Example demonstrating how to use TVM-FFI ABI with CuTe.
This example shows how to:
1. Compile a CuTe function with "--enable-tvm-ffi" option
2. Directly use the compiled function to work with JAX
To run this example:
.. code-block:: bash
pip install jax-tvm-ffi
pip install jax[cuda13]
python cutlass_ir/compiler/python/examples/cute/tvm_ffi/jit_and_use_in_jax.py
"""
import jax
from jax import numpy as jnp
import jax_tvm_ffi
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
@cute.kernel
def device_add_one(a: cute.Tensor, b: cute.Tensor):
for i in range(a.shape[0]):
b[i] = a[i] + 1
@cute.jit
def add_one(a: cute.Tensor, b: cute.Tensor):
"""b = a + 1"""
device_add_one(a, b).launch(grid=(1, 1, 1), block=(1, 1, 1))
def main():
# compile the kernel with "--enable-tvm-ffi" option
a_jax = jnp.arange(
10,
dtype=jnp.float32,
)
b_jax = jnp.zeros(
10,
dtype=jnp.float32,
)
a_cute = from_dlpack(a_jax, enable_tvm_ffi=True).mark_layout_dynamic()
b_cute = from_dlpack(b_jax, enable_tvm_ffi=True).mark_layout_dynamic()
# compile the kernel with "--enable-tvm-ffi" option
compiled_add_one = cute.compile(add_one, a_cute, b_cute, options="--enable-tvm-ffi")
# register the compiled function to JAX as a FFI target
jax_tvm_ffi.register_ffi_target("add_one_cute", compiled_add_one, platform="gpu")
a_jax = jnp.arange(10, dtype=jnp.float32)
# call the compiled function using JAX FFI
b_jax = jax.ffi.ffi_call(
"add_one_cute",
jax.ShapeDtypeStruct(a_jax.shape, a_jax.dtype),
vmap_method="broadcast_all",
)(a_jax)
print("result of b_jax after add_one_cute")
print(b_jax)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,85 @@
# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Example demonstrating how to use TVM-FFI ABI with CuTe.
This example shows how to:
1. Compile a CuTe function with "--enable-tvm-ffi" option
2. Directly use the compiled function to work with torch.Tensor
To run this example:
.. code-block:: bash
python cutlass_ir/compiler/python/examples/cute/tvm_ffi/jit_and_use_in_torch.py
"""
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
@cute.kernel
def device_add_one(a: cute.Tensor, b: cute.Tensor):
for i in range(a.shape[0]):
b[i] = a[i] + 1
@cute.jit
def add_one(a: cute.Tensor, b: cute.Tensor):
"""b = a + 1"""
device_add_one(a, b).launch(grid=(1, 1, 1), block=(1, 1, 1))
def main():
import torch
# compile the kernel with "--enable-tvm-ffi" option
a_torch = torch.arange(10, dtype=torch.float32, device="cuda")
b_torch = torch.zeros(10, dtype=torch.float32, device="cuda")
a_cute = from_dlpack(a_torch, enable_tvm_ffi=True).mark_layout_dynamic()
b_cute = from_dlpack(b_torch, enable_tvm_ffi=True).mark_layout_dynamic()
# compile the kernel with "--enable-tvm-ffi" option
compiled_add_one = cute.compile(add_one, a_cute, b_cute, options="--enable-tvm-ffi")
# run the compiled function by passing in cute.Tensor as input
# you need to set enable_tvm_ffi=True for now
compiled_add_one(a_cute, b_cute)
# print the result
print("result of b after compiled_add_one(a, b)")
print(b_torch)
a_torch = a_torch + 1
# We can directly pass in torch.Tensor as input
# the call overhead is optimized so it is very fast to pass in torch.Tensor as input
# takes about less than 0.5us per call likely in terms of API overhead
compiled_add_one(a_torch, b_torch)
# print the result
print("result of b after compiled_add_one(a, b)")
print(b_torch)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,2 @@
apache-tvm-ffi
torch-c-dlpack-ext