v4.4 tag release update. (#3032)

This commit is contained in:
Junkai-Wu
2026-02-14 12:27:58 +08:00
committed by GitHub
parent 01687cfba1
commit d4bbf728ca
140 changed files with 41624 additions and 3691 deletions

View File

@@ -29,7 +29,6 @@
import sys
import os
from typing import Tuple
import torch
import cutlass
import cutlass.cute as cute
@@ -125,6 +124,8 @@ def tensor_op_gemm_wrapper(
def run_tensor_op_gemm_wrapper(mnkl: Tuple[int, int, int, int]):
import torch
print("\nRunning TensorOpGemm test with:")
print(f"Tensor dimensions: {mnkl}")

View File

@@ -60,11 +60,8 @@ import os
import sys
from typing import Type, Tuple
import torch
import cutlass
import cutlass.cute as cute
from cutlass.torch import dtype as torch_dtype
from cutlass.cute.runtime import make_ptr
if __name__ == "__main__":
@@ -205,6 +202,9 @@ def tensor_op_gemm_wrapper(
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}")

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

@@ -28,7 +28,6 @@
import argparse
import torch
import time
from typing import Type
@@ -36,7 +35,6 @@ from typing import Type
import cutlass
import cutlass.cute as cute
import cutlass.cute.testing as testing
import cutlass.torch as cutlass_torch
from cutlass.cute.runtime import from_dlpack
"""
@@ -252,6 +250,8 @@ def elementwise_add(mA, mB, mC, copy_bits: cutlass.Constexpr = 128):
cC = cute.zipped_divide(idC, tiler=tiler_mn)
print(f"[DSL INFO] coord tensor = {cC.type}")
kernel_name = f"cutlass_dsl_elementwise_add_kernel"
elementwise_add_kernel.set_name_prefix(kernel_name)
elementwise_add_kernel(gA, gB, gC, cC, mC.shape, thr_layout, val_layout).launch(
grid=[cute.size(gC, mode=[1]), 1, 1],
block=[cute.size(tv_layout, mode=[0]), 1, 1],
@@ -270,6 +270,12 @@ def run_elementwise_add(
warmup_iterations=2,
iterations=200,
):
import torch
import cutlass.torch as cutlass_torch
if not torch.cuda.is_available():
raise RuntimeError("Ampere GPU is required to run this example!")
print("\nRunning Elementwise Add test with:")
print(f"Tensor dimensions: [{M}, {N}]")
print(f"Input and Output Data type: {dtype}")
@@ -304,6 +310,8 @@ def run_elementwise_add(
else:
c_tensor = c
elementwise_add.set_name_prefix("host_prefix")
print("Compiling kernel with cute.compile ...")
start_time = time.time()
compiled_func = cute.compile(
@@ -386,9 +394,6 @@ if __name__ == "__main__":
args = parser.parse_args()
if not torch.cuda.is_available():
raise RuntimeError("Ampere GPU is required to run this example!")
run_elementwise_add(
args.M,
args.N,

View File

@@ -0,0 +1,372 @@
# 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
from typing import Any, Callable, Type
import cutlass
import cutlass.cute as cute
import cutlass.cute.testing as testing
"""
In this example we revisit the elementwise add example and use the autotune_jit decorator to
autotune the kernel.
To run this example:
.. code-block:: bash
python examples/ampere/elementwise_add_autotune.py --M 3 --N 12
python examples/ampere/elementwise_add_autotune.py --M 1024 --N 512
python examples/ampere/elementwise_add_autotune.py --M 1024 --N 1024 --benchmark --warmup_iterations 2 --iterations 1000
"""
@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,
):
tidx, _, _ = cute.arch.thread_idx()
bidx, _, _ = cute.arch.block_idx()
# slice for CTAs
# logical id -> address
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)
# # declare the atoms which will be used later for memory copy
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)
# allocate fragments for gmem->rmem
frgA = cute.make_rmem_tensor_like(thrA)
frgB = cute.make_rmem_tensor_like(thrB)
frgC = cute.make_rmem_tensor_like(thrC)
thrCrd = thr_copy_C.partition_S(blkCrd)
frgPred = cute.make_rmem_tensor(thrCrd.shape, cutlass.Boolean)
for i in range(0, cute.size(frgPred), 1):
val = cute.elem_less(thrCrd[i], shape)
frgPred[i] = val
# Print per thread predicate mask
# if tidx == 0 and bidx == 0:
# cute.printf("block_dim = {}", cute.arch.grid_dim())
# cute.printf("shape = {}", shape)
# cute.print_tensor(thrA)
# cute.print_tensor(thrB)
# cute.print_tensor(frgPred)
##########################################################
# Move data to reg address space
##########################################################
cute.copy(copy_atom_load, thrA, frgA, pred=frgPred)
cute.copy(copy_atom_load, thrB, frgB, pred=frgPred)
# if tidx == 0 and bidx == 0:
# cute.print_tensor(frgA)
# cute.print_tensor(frgB)
# Load data before use. The compiler will optimize the copy and load
# operations to convert some memory ld/st into register uses.
result = frgA.load() + frgB.load()
# Save the results back to registers. Here we reuse b's registers.
frgC.store(result)
# Copy the results back to c
cute.copy(copy_atom_store, frgC, thrC, pred=frgPred)
@testing.autotune_jit(
params_dict={"copy_bits": [64, 128]},
update_on_change=["M", "N"],
warmup_iterations=100,
iterations=100,
)
@cute.jit
def elementwise_add_autotune(mA, mB, mC, M, N, copy_bits: cutlass.Constexpr = 128):
dtype = mA.element_type
vector_size = copy_bits // 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).launch(
grid=[cute.size(gC, mode=[1]), 1, 1],
block=[cute.size(tv_layout, mode=[0]), 1, 1],
)
class ElementwiseAddWrapper:
"""
This class mimics more advanced kernel development, where a class encapsulates
pieces of the kernel implementation.
The can_implement method can be used to check if the kernel can be implemented
for the given arguments.
The __call__ method is the actual cute.jit function.
"""
def __init__(self, copy_bits: cutlass.Constexpr = 128):
self.copy_bits = copy_bits
def can_implement(self, mA, mB, mC, M, N):
return self.copy_bits in [64, 128]
@cute.jit
def __call__(self, mA, mB, mC, M, N):
dtype = mA.element_type
vector_size = self.copy_bits // 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).launch(
grid=[cute.size(gC, mode=[1]), 1, 1],
block=[cute.size(tv_layout, mode=[0]), 1, 1],
)
def tune_class(mA, mB, mC, M, N):
"""
This function is used to autotune the elementwise add kernel which is wrapped in a class.
An internal function is defined to compile the class with the given arguments.
The internal function is then passed to the benchmarking.tune function to autotune.
The best parameters are then used to instantiate the class.
:param mA: Input tensor A
:type mA: cute.Tensor
:param mB: Input tensor B
:type mB: cute.Tensor
:param mC: Output tensor C
:type mC: cute.Tensor
:param M: Number of rows in the input tensors
:type M: int
:param N: Number of columns in the input tensors
:type N: int
:return: An instance of the ElementwiseAddWrapper class with the best parameters
:rtype: ElementwiseAddWrapper
"""
def compile_class(a, b, c, M, N, copy_bits=128) -> Callable[[], Any]:
kernel = ElementwiseAddWrapper(copy_bits)
if not kernel.can_implement(a, b, c, M, N):
raise ValueError(f"Cannot implement kernel for copy_bits={copy_bits}")
compiled_kernel = cute.compile(kernel, a, b, c, M, N)
return lambda: compiled_kernel(a, b, c, M, N)
params = testing.tune(
compile_class,
params_dict={"copy_bits": [1, 64, 128]},
kernel_arguments=testing.JitArguments(mA, mB, mC, M, N),
)
return ElementwiseAddWrapper(**params)
def run_elementwise_add(
M_start,
M_range,
M_step,
N_start,
N_range,
N_step,
dtype: Type[cutlass.Numeric],
skip_ref_check=False,
warmup_iterations=2,
iterations=200,
):
import torch
import cutlass.torch as cutlass_torch
if not torch.cuda.is_available():
raise RuntimeError("Ampere GPU is required to run this example!")
for M in range(M_start, M_start + M_range + 1, M_step):
for N in range(N_start, N_start + N_range + 1, N_step):
print("\nRunning Elementwise Add test with:")
print(f"Tensor dimensions: [{M}, {N}]")
print(f"Input and Output Data type: {dtype}")
torch_dtype = cutlass_torch.dtype(dtype)
if dtype.is_integer:
a = torch.randint(
0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype
)
b = torch.randint(
0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype
)
else:
a = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
b = torch.randn(M, N, device=torch.device("cuda"), dtype=torch_dtype)
c = torch.zeros_like(a)
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")
elementwise_class = tune_class(a, b, c, M, N)
if not skip_ref_check:
print("Verifying results for class ...")
torch.testing.assert_close(a + b, c)
print("Results verified successfully!")
c = torch.zeros_like(a)
elementwise_add_autotune(a, b, c, M, N)
if not skip_ref_check:
print("Verifying results for autotuned function ...")
torch.testing.assert_close(a + b, c)
print("Results verified successfully!")
def generate_kernel_arguments():
if dtype.is_integer:
a = torch.randint(
0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype
)
b = torch.randint(
0, 10, (M, N), device=torch.device("cuda"), dtype=torch_dtype
)
else:
a = torch.randn(
M, N, device=torch.device("cuda"), dtype=torch_dtype
)
b = torch.randn(
M, N, device=torch.device("cuda"), dtype=torch_dtype
)
c = torch.zeros_like(a)
return testing.JitArguments(a, b, c, M, N)
avg_time_us = testing.benchmark(
elementwise_add_autotune,
workspace_generator=generate_kernel_arguments,
workspace_count=10,
warmup_iterations=warmup_iterations,
iterations=iterations,
)
# Print execution results
print(
f"Kernel execution time for cute.jit kernel with M={M}, N={N}: {avg_time_us / 1e3:.4f} ms"
)
print(
f"Achieved memory throughput for M={M}, N={N}: {(3 * a.numel() * dtype.width // 8) / (avg_time_us / 1e6) / 1e9:.2f} GB/s"
)
compiled_class = cute.compile(elementwise_class, a, b, c, M, N)
avg_time_us = testing.benchmark(
compiled_class,
workspace_generator=generate_kernel_arguments,
workspace_count=10,
warmup_iterations=warmup_iterations,
iterations=iterations,
)
print(
f"Kernel execution time for Class Wrapper with M={M}, N={N}: {avg_time_us / 1e3:.4f} ms"
)
print(
f"Achieved memory throughput for M={M}, N={N}: {(3 * a.numel() * dtype.width // 8) / (avg_time_us / 1e6) / 1e9:.2f} GB/s"
)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="example of elementwise add to demonstrate the numpy/pytorch as input for kernels"
)
parser.add_argument("--M", default=1024, type=int)
parser.add_argument("--M_range", default=0, type=int)
parser.add_argument("--M_step", default=1024, type=int)
parser.add_argument("--N", default=1024, type=int)
parser.add_argument("--N_range", default=0, type=int)
parser.add_argument("--N_step", default=1024, type=int)
parser.add_argument("--warmup_iterations", default=2, type=int)
parser.add_argument("--iterations", default=100, type=int)
parser.add_argument("--skip_ref_check", action="store_true")
args = parser.parse_args()
run_elementwise_add(
args.M,
args.M_range,
args.M_step,
args.N,
args.N_range,
args.N_step,
dtype=cutlass.Float32,
skip_ref_check=args.skip_ref_check,
warmup_iterations=args.warmup_iterations,
iterations=args.iterations,
)
print("\nPASS")

View File

@@ -36,8 +36,6 @@ from typing import List, Type
import cuda.bindings.driver as cuda
import cutlass.cute as cute
import cutlass.cute.testing as testing
import cutlass.torch as cutlass_torch
import torch
from cutlass.cute.runtime import from_dlpack
import cutlass
@@ -274,6 +272,8 @@ def leaky_relu(x, alpha, *, loc=None, ip=None):
def leaky_relu_ref(x, alpha):
import torch
return torch.where(x > 0, x, alpha * x)
@@ -287,6 +287,9 @@ def run_and_verify(
warmup_iterations=2,
iterations=100,
):
import torch
import cutlass.torch as cutlass_torch
if not torch.cuda.is_available():
raise RuntimeError("NVIDIA GPU is required to run this example!")

View File

@@ -30,13 +30,11 @@ import argparse
from types import SimpleNamespace
from typing import Type, Callable
import torch
import cuda.bindings.driver as cuda
import cutlass.cute.testing as testing
import cutlass
import cutlass.cute as cute
from cutlass.cute.nvgpu import cpasync, warp
import cutlass.torch as cutlass_torch
from cutlass.cute.runtime import from_dlpack
import cutlass.pipeline as pipeline
import cutlass.utils as utils
@@ -1162,6 +1160,9 @@ def run(
use_cold_l2: bool = False,
**kwargs,
):
import torch
import cutlass.torch as cutlass_torch
# Skip unsupported testcase
if not FlashAttentionForwardAmpere.can_implement(
dtype,
@@ -1237,8 +1238,12 @@ def run(
torch_stream = torch.cuda.current_stream()
# Get the raw stream pointer as a CUstream
current_stream = cuda.CUstream(torch_stream.cuda_stream)
# Pass compile options if needed
compile_options = ""
# compile the fa2 forward pass
compiled_fa2_fwd = cute.compile(fa2_fwd, q, k, v, o, softmax_scale, current_stream)
compiled_fa2_fwd = cute.compile(
fa2_fwd, q, k, v, o, softmax_scale, current_stream, options=compile_options
)
if not skip_ref_check:
compiled_fa2_fwd(q, k, v, o, softmax_scale, current_stream)
@@ -1317,7 +1322,6 @@ if __name__ == "__main__":
default=False,
help="Use circular buffer tensor sets to ensure L2 cold cache",
)
args = parser.parse_args()
run(
args.dtype,

View File

@@ -29,10 +29,8 @@
from typing import Type
import argparse
import torch
import cuda.bindings.driver as cuda
import cutlass
import cutlass.torch as cutlass_torch
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
from cutlass._mlir.dialects import llvm
@@ -838,28 +836,25 @@ class HSTUAttentionForwardAmpere(object):
def run_pytorch_hstu_test(
dtype: torch.dtype,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
rab: torch.Tensor,
dtype,
q,
k,
v,
rab,
is_causal: bool,
):
"""Generate the reference output of the HSTU attention with Pytorch.
:param dtype: data type of the input tensors
:type dtype: torch.dtype
:param q: query tensor
:type q: torch.Tensor
:param k: key tensor
:type k: torch.Tensor
:param v: value tensor
:type v: torch.Tensor
:param rab: RAB tensor
:type rab: torch.Tensor
:param is_causal: whether to use causal masking
:type is_causal: bool
"""
import torch
q = q.to(dtype)
k = k.to(dtype)
v = v.to(dtype)
@@ -922,6 +917,9 @@ def run(
"""
assert dtype == cutlass.Float16 or dtype == cutlass.BFloat16
import torch
import cutlass.torch as cutlass_torch
torch_stream = torch.cuda.current_stream()
stream = cuda.CUstream(torch_stream.cuda_stream)

View File

@@ -29,8 +29,6 @@
from functools import partial
from typing import Union
import torch
import cutlass.cute as cute
from cutlass.cute.runtime import from_dlpack
from cutlass._mlir.dialects import llvm
@@ -49,7 +47,7 @@ Situations like:
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_wrappers.py for the test.
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>`_
@@ -61,8 +59,8 @@ To run this example:
python examples/ampere/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.
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.
"""
@@ -184,6 +182,8 @@ def vote(
def run():
import torch
ballot_ptx = torch.randint(
0, 100, (WARP_SIZE,), device=torch.device("cuda"), dtype=torch.int32
)
@@ -230,14 +230,11 @@ def run():
torch.testing.assert_close(ballot_ptx, ballot_nvvm)
print("Verifying any results...")
torch.testing.assert_close(any_ptx, any_nvvm)
print(torch.all(any_ptx == any(i < 10 for i in range(WARP_SIZE))))
assert torch.all(any_ptx == any(i < 10 for i in range(WARP_SIZE)))
print("Verifying all results...")
torch.testing.assert_close(all_ptx, all_nvvm)
assert torch.all(all_ptx == all(i < 10 for i in range(WARP_SIZE)))
print("Verifying uni results...")
torch.testing.assert_close(uni_ptx, uni_nvvm)
assert torch.all(uni_ptx == (len(set(i < 10 for i in range(WARP_SIZE))) == 1))
print("Results verified successfully!")

View File

@@ -31,7 +31,6 @@ import time
from typing import Tuple
import cuda.bindings.driver as cuda
import torch
import cutlass
import cutlass.cute as cute
@@ -643,6 +642,8 @@ def run(
use_cold_l2: bool = False,
**kwargs,
):
import torch
"""Execute SIMT GEMM operation and benchmark performance.
:param mnk: GEMM problem size (M, N, K, L)
@@ -666,6 +667,7 @@ def run(
:return: Execution time of the GEMM kernel in microseconds
:rtype: float
"""
torch.manual_seed(1024)
print("Running Ampere SIMT GEMM example:")
print(f"mnk: {mnk}")
print(f"A major: {a_major}, B major: {b_major}, C major: {c_major}")
@@ -851,8 +853,6 @@ if __name__ == "__main__":
args = parser.parse_args()
print("Running SIMT GEMM example:")
torch.manual_seed(1024)
run(
args.mnk,
args.a_major,

View File

@@ -28,7 +28,6 @@
import cutlass.cute as cute
import cutlass
import torch
import numpy as np
from cutlass.cute.runtime import from_dlpack
@@ -175,6 +174,8 @@ def host(
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")

View File

@@ -30,12 +30,9 @@ import argparse
import math
from typing import Tuple, Type
import torch
import cutlass
import cutlass.cute as cute
import cutlass.cute.testing as testing
import cutlass.torch as cutlass_torch
import cutlass.utils as utils
from cutlass.cute.runtime import from_dlpack
@@ -849,6 +846,9 @@ def run(
use_cold_l2: bool = False,
**kwargs,
):
import torch
import cutlass.torch as cutlass_torch
print("Running Ampere tensor core GEMM example:")
print(f"mnkl: {mnkl}")
print(