Release v13.3 of the CUDA samples with CUDA 13.3 Toolkit (#435)

This is the release of the CUDA 13.3 samples, which include additions for CUDA Tile C++, and updated CCCL and Python samples.
This commit is contained in:
Dheemanth
2026-05-27 14:50:59 -07:00
committed by GitHub
parent aeab82ff30
commit b7c5481c55
150 changed files with 7977 additions and 246 deletions

View File

@@ -0,0 +1,67 @@
cmake_minimum_required(VERSION 3.20)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/Modules")
project(tileMatmulAutotuner LANGUAGES C CXX CUDA)
find_package(CUDAToolkit REQUIRED)
get_filename_component(CUDA_TOOLKIT_BIN_DIR "${CMAKE_CUDA_COMPILER}" DIRECTORY)
find_program(TILEIRAS_EXECUTABLE tileiras
HINTS "${CUDA_TOOLKIT_BIN_DIR}"
REQUIRED
)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_CUDA_ARCHITECTURES 80 86 87 89 90 100 110 120)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --enable-tile")
if(ENABLE_CUDA_DEBUG)
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -G")
else()
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -lineinfo") # add line information to all builds for debug tools (exclusive to -G option)
endif()
# Include directories and libraries
include_directories(../../../Common ../Benchmark_Common)
# Source files
add_executable(tileMatmulAutotuner
matmul_autotuner.cpp
autotuner_search_space.conf
)
target_compile_features(tileMatmulAutotuner PRIVATE cxx_std_20 cuda_std_20)
target_include_directories(tileMatmulAutotuner PRIVATE ${CUDAToolkit_INCLUDE_DIRS})
list(GET CUDAToolkit_INCLUDE_DIRS 0 CUDA_INCLUDE_DIR)
file(TO_CMAKE_PATH "${CUDA_INCLUDE_DIR}" CUDA_INCLUDE_DIR_FOR_DEFINE)
file(TO_CMAKE_PATH "${TILEIRAS_EXECUTABLE}" TILEIRAS_EXECUTABLE_FOR_DEFINE)
file(TO_CMAKE_PATH "${CMAKE_CUDA_COMPILER}" NVCC_EXECUTABLE_FOR_DEFINE)
target_compile_definitions(tileMatmulAutotuner PRIVATE
CUDA_INCLUDE_PATH="${CUDA_INCLUDE_DIR_FOR_DEFINE}"
NVCC_PATH="${NVCC_EXECUTABLE_FOR_DEFINE}"
TILEIRAS_PATH="${TILEIRAS_EXECUTABLE_FOR_DEFINE}"
)
target_link_libraries(tileMatmulAutotuner PRIVATE
CUDA::cuda_driver
CUDA::cudart
CUDA::nvrtc
)
add_custom_command(TARGET tileMatmulAutotuner POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/matmul.cu
${CMAKE_CURRENT_BINARY_DIR}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${CMAKE_CURRENT_SOURCE_DIR}/autotuner_search_space.conf
${CMAKE_CURRENT_BINARY_DIR}
)
# Include installation configuration
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/InstallSamples.cmake)
setup_samples_install()

View File

@@ -0,0 +1,59 @@
# tileMatmulAutotuner
## Description
A CUDA Tile C++ sample demonstrating how to autotune tile sizes and optimization hints for matrix multiplication with FP16 inputs and FP32 accumulation when compiling with nvrtc or nvcc.
The sample explores combinations of tile sizes and optimization hints, compiles the Tile C++ matrix multiplication kernel, executes it, and reports the best measured configuration. The launch grid is derived from the selected tile size and matrix dimensions.
The search space is read from `autotuner_search_space.conf` so one can edit tile sizes and hint values without rebuilding the sample.
## Running
```
# Run autotuner. Validation is disabled by default.
./tileMatmulAutotuner
# Select a backend
./tileMatmulAutotuner --backend=nvrtc
./tileMatmulAutotuner --backend=nvcc
# Enable CPU validation
./tileMatmulAutotuner --validate
# To run faster, skip warmups and set iteration to 1
./tileMatmulAutotuner --skip-warmup -i 1
# Show all options
./tileMatmulAutotuner --help
```
NOTE: When using the nvrtc backend, the libnvrtc library must be on the dynamic library search path. This may require adding the CUDA toolkit 'lib' directory to the LD_LIBRARY_PATH (for linux) or PATH (for windows) env var.
## Command-Line Options
| Option | Description |
|--------|-------------|
| `--validate` | Enable CPU cross-validation. Validation is disabled by default. |
| `--skip-warmup` | Disable warmup iterations. |
| `--warmup=N` | Set warmup iterations. The default is 5. |
| `-i N`, `--iters=N` | Set benchmark iterations. The default is 20. |
| `--backend=nvrtc\|nvcc` | Select the backend. The default is `nvrtc`. |
## Search Space Configuration
Each `tile` line adds one `TILE_BLOCK_M`, `TILE_BLOCK_N`, and `TILE_BLOCK_K` combination. The `load_latency` and `store_latency` lines list values that are combined with every `tile` entry.
```
tile 64 64 32
tile 128 64 32
load_latency 2 5 8
store_latency 2 5 8
```
Lines beginning with `#` are comments.
## Prerequisites
- [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) version 13.3 or later.
- [CUDA Driver](https://www.nvidia.com/en-us/drivers/) version 580 or later. The NVRTC backend invokes `tileiras` to compile Tile IR to cubin. JIT-compiling Tile IR to cubin with the CUDA Driver API instead requires version 590 or later.
- Host compiler with C++20 support.

View File

@@ -0,0 +1,15 @@
# tileMatmulAutotuner search space
#
# The autotuner tries every tile entry with every load_latency and
# store_latency value listed below. Edit this file to experiment without
# rebuilding the sample.
# tile block_m block_n block_k
tile 64 64 32
tile 128 64 32
tile 64 128 32
tile 128 128 32
# latency hint values
load_latency 2 5 8
store_latency 2 5 8

View File

@@ -0,0 +1,314 @@
/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of NVIDIA CORPORATION 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 ``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 OWNER 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.
*/
#pragma once
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <helper_string.h>
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
#include <process.h>
#else
#include <unistd.h>
#endif
enum class CompilerBackend {
NVRTC,
NVCC
};
struct CompiledKernel {
std::vector<char> image;
};
struct TileConfig {
int block_m;
int block_n;
int block_k;
};
struct SearchSpace {
std::vector<TileConfig> tile_options;
std::vector<int> load_latency_options;
std::vector<int> store_latency_options;
};
static constexpr const char *kSearchSpaceFileName = "autotuner_search_space.conf";
inline const char* compilerBackendName(CompilerBackend compiler_backend) {
return compiler_backend == CompilerBackend::NVCC ? "NVCC" : "NVRTC";
}
inline int ceilDiv(int a, int b) {
return (a + b - 1) / b;
}
inline unsigned long getProcessId() {
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
return static_cast<unsigned long>(_getpid());
#else
return static_cast<unsigned long>(getpid());
#endif
}
inline std::string makeTempPath(const char *prefix, const char *suffix) {
static unsigned int counter = 0;
std::string filename = std::string(prefix) + "_" + std::to_string(getProcessId()) + "_" +
std::to_string(static_cast<long>(time(NULL))) + "_" +
std::to_string(counter++) + suffix;
return (std::filesystem::temp_directory_path() / filename).string();
}
inline std::string shellQuote(const std::string& arg) {
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
std::string quoted = "\"";
for (char c : arg) {
if (c == '"') {
quoted += "\\\"";
} else {
quoted += c;
}
}
quoted += "\"";
return quoted;
#else
std::string quoted = "'";
for (char c : arg) {
if (c == '\'') {
quoted += "'\\''";
} else {
quoted += c;
}
}
quoted += "'";
return quoted;
#endif
}
inline std::string joinShellCommand(const std::vector<std::string>& args) {
std::string cmd;
for (const auto& arg : args) {
if (!cmd.empty()) {
cmd += " ";
}
cmd += shellQuote(arg);
}
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
return "\"" + cmd + "\"";
#else
return cmd;
#endif
}
inline std::vector<char> readBinaryFile(const std::string& path) {
std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
if (!file.is_open()) {
std::cerr << "\nerror: unable to open " << path << " for reading!\n";
exit(EXIT_FAILURE);
}
std::streamsize size = file.tellg();
if (size < 0) {
std::cerr << "\nerror: unable to determine size of " << path << "\n";
exit(EXIT_FAILURE);
}
std::vector<char> data(static_cast<size_t>(size));
file.seekg(0, std::ios::beg);
if (size > 0 && !file.read(data.data(), size)) {
std::cerr << "\nerror: unable to read " << path << "\n";
exit(EXIT_FAILURE);
}
return data;
}
inline std::string baseNameWithoutExtension(const std::string& path) {
size_t slash = path.find_last_of("/\\");
std::string base = (slash == std::string::npos) ? path : path.substr(slash + 1);
size_t dot = base.find_last_of('.');
if (dot != std::string::npos) {
base.resize(dot);
}
return base;
}
inline void appendTileBlockMacroOptions(std::vector<std::string>& options,
int block_m, int block_n, int block_k) {
options.push_back("-DTILE_BLOCK_M=" + std::to_string(block_m));
options.push_back("-DTILE_BLOCK_N=" + std::to_string(block_n));
options.push_back("-DTILE_BLOCK_K=" + std::to_string(block_k));
}
inline bool parsePositiveInt(const std::string& text, int *value) {
char *end = nullptr;
errno = 0;
long parsed = std::strtol(text.c_str(), &end, 10);
if (errno != 0 || end == text.c_str() || *end != '\0' ||
parsed <= 0 || parsed > INT_MAX) {
return false;
}
*value = static_cast<int>(parsed);
return true;
}
inline void searchSpaceError(const char *filename,
int line_number,
const std::string& message) {
fprintf(stderr, "Error: %s:%d: %s\n", filename, line_number, message.c_str());
exit(EXIT_FAILURE);
}
inline char *copyFilePath(const std::string& path) {
char *file_path = reinterpret_cast<char *>(malloc(path.length() + 1));
if (file_path == NULL) {
fprintf(stderr, "Error: failed to allocate memory for file path\n");
exit(EXIT_FAILURE);
}
std::memcpy(file_path, path.c_str(), path.length() + 1);
return file_path;
}
inline char *findSampleFile(const char *filename, const char *executable_path) {
if (executable_path != NULL) {
std::filesystem::path executable_dir =
std::filesystem::path(executable_path).parent_path();
if (!executable_dir.empty()) {
std::filesystem::path candidate = executable_dir / filename;
if (std::filesystem::exists(candidate)) {
return copyFilePath(candidate.string());
}
}
}
return sdkFindFilePath(filename, executable_path);
}
inline SearchSpace loadSearchSpace(const char *filename) {
std::ifstream input(filename);
if (!input.is_open()) {
fprintf(stderr, "Error: unable to open search space file %s\n", filename);
exit(EXIT_FAILURE);
}
SearchSpace search_space;
std::string line;
int line_number = 0;
while (std::getline(input, line)) {
line_number++;
size_t comment = line.find('#');
if (comment != std::string::npos) {
line.resize(comment);
}
std::istringstream tokens(line);
std::string directive;
if (!(tokens >> directive)) {
continue;
}
std::vector<std::string> values;
std::string value;
while (tokens >> value) {
values.push_back(value);
}
if (directive == "tile") {
if (values.size() != 3) {
searchSpaceError(filename, line_number,
"tile expects block_m block_n block_k");
}
TileConfig tile = {};
if (!parsePositiveInt(values[0], &tile.block_m) ||
!parsePositiveInt(values[1], &tile.block_n) ||
!parsePositiveInt(values[2], &tile.block_k)) {
searchSpaceError(filename, line_number,
"tile values must be positive integers");
}
search_space.tile_options.push_back(tile);
} else if (directive == "load_latency") {
if (values.empty()) {
searchSpaceError(filename, line_number,
"load_latency expects at least one value");
}
for (const auto& option : values) {
int latency = 0;
if (!parsePositiveInt(option, &latency)) {
searchSpaceError(filename, line_number,
"load_latency values must be positive integers");
}
search_space.load_latency_options.push_back(latency);
}
} else if (directive == "store_latency") {
if (values.empty()) {
searchSpaceError(filename, line_number,
"store_latency expects at least one value");
}
for (const auto& option : values) {
int latency = 0;
if (!parsePositiveInt(option, &latency)) {
searchSpaceError(filename, line_number,
"store_latency values must be positive integers");
}
search_space.store_latency_options.push_back(latency);
}
} else {
searchSpaceError(filename, line_number,
"unknown search space directive '" + directive + "'");
}
}
if (search_space.tile_options.empty()) {
fprintf(stderr, "Error: search space file %s does not list any tile entries\n", filename);
exit(EXIT_FAILURE);
}
if (search_space.load_latency_options.empty()) {
fprintf(stderr, "Error: search space file %s does not list any load_latency entries\n",
filename);
exit(EXIT_FAILURE);
}
if (search_space.store_latency_options.empty()) {
fprintf(stderr, "Error: search space file %s does not list any store_latency entries\n",
filename);
exit(EXIT_FAILURE);
}
return search_space;
}

View File

@@ -0,0 +1,111 @@
/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of NVIDIA CORPORATION 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 ``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 OWNER 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.
*/
#pragma once
#include "backend_common.h"
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <string>
#include <system_error>
#include <vector>
inline CompiledKernel compileFileWithNVCC(const char *filename,
int sm_value,
int block_m, int block_n, int block_k,
const std::vector<std::string>& extra_flags) {
// Check CUDA include path for cuda_fp16.h
const char *include_path = CUDA_INCLUDE_PATH;
if (include_path[0] == '\0') {
printf("\n ERROR: unable to locate CUDA include directory containing cuda_fp16.h\n");
exit(EXIT_FAILURE);
}
std::filesystem::path keep_dir = makeTempPath("matmul_nvcc_keep", "");
std::error_code ec;
if (!std::filesystem::create_directory(keep_dir, ec)) {
std::cerr << "\nerror: unable to create " << keep_dir.string()
<< " (" << ec.message() << ")\n";
exit(EXIT_FAILURE);
}
std::string base = baseNameWithoutExtension(filename);
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)
const char *object_suffix = ".obj";
#else
const char *object_suffix = ".o";
#endif
std::filesystem::path object_file = keep_dir / (base + object_suffix);
std::filesystem::path tile_cubin_file = keep_dir / (base + ".tile.cubin");
std::vector<std::string> args = {
NVCC_PATH,
"--enable-tile",
"-std=c++20",
"-arch=sm_" + std::to_string(sm_value),
"-lineinfo",
"-c",
filename,
"-o",
object_file.string(),
"--keep",
"--keep-dir",
keep_dir.string(),
"-I",
include_path
};
appendTileBlockMacroOptions(args, block_m, block_n, block_k);
for (const auto& flag : extra_flags) {
args.push_back(flag);
}
std::string cmd = joinShellCommand(args);
std::cerr << "\nCompiling file with NVCC\n";
int ret = system(cmd.c_str());
if (ret != 0) {
fprintf(stderr, "Error: nvcc compilation failed with code %d\n", ret);
fprintf(stderr, "Command: %s\n", cmd.c_str());
std::filesystem::remove_all(keep_dir);
exit(EXIT_FAILURE);
}
if (!std::filesystem::exists(tile_cubin_file)) {
fprintf(stderr, "Error: nvcc did not produce expected Tile cubin %s\n",
tile_cubin_file.string().c_str());
std::filesystem::remove_all(keep_dir);
exit(EXIT_FAILURE);
}
CompiledKernel kernel;
kernel.image = readBinaryFile(tile_cubin_file.string());
std::filesystem::remove_all(keep_dir);
return kernel;
}

View File

@@ -0,0 +1,186 @@
/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of NVIDIA CORPORATION 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 ``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 OWNER 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.
*/
#pragma once
#include "backend_common.h"
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <nvrtc.h>
#define NVRTC_SAFE_CALL(Name, x) \
do { \
nvrtcResult result = x; \
if (result != NVRTC_SUCCESS) { \
std::cerr << "\nerror: " << Name << " failed with error " << \
nvrtcGetErrorString(result); \
exit(EXIT_FAILURE); \
} \
} while(0)
inline std::vector<char> compileTileIRToCubin(const char *tileIR,
size_t tileIRSize,
int sm_value) {
// Generate unique temporary file names using PID, timestamp, and a local counter.
std::string tileir_file = makeTempPath("tileir", ".bc");
std::string cubin_file = makeTempPath("cubin", ".cubin");
// Write TileIR to a temporary file because tileiras consumes files rather than memory buffers.
FILE* fp = fopen(tileir_file.c_str(), "wb");
if (!fp) {
fprintf(stderr, "Error: failed to open %s for writing\n", tileir_file.c_str());
exit(EXIT_FAILURE);
}
fwrite(tileIR, 1, tileIRSize, fp);
fclose(fp);
/*
* Ideally we would use the Driver API to compile TileIR to cubin with the
* latest driver installed. However, to avoid the hassle of upgrading the
* driver, we use tileiras for now, which is handily available in CUDA
* Toolkit.
*
* The Driver API path would look like:
* (requires #include <helper_cuda_drvapi.h>)
*
* CUmodule module;
* CUjit_option options[] = {
* CU_JIT_GENERATE_LINE_INFO
* };
* void* optionValues[] = {
* (void*)(uintptr_t)1, // line info
* };
* unsigned int numOptions = sizeof(options) / sizeof(options[0]);
* checkCudaErrors(cuModuleLoadDataEx(&module, tileIR, numOptions,
* options, optionValues));
*/
// Compile TileIR to cubin using tileiras from the configured CUDA Toolkit.
// This happens before benchmarking so the timed path matches the NVCC backend.
std::string cmd = joinShellCommand({
TILEIRAS_PATH,
"-arch=sm_" + std::to_string(sm_value),
tileir_file,
"-o",
cubin_file
});
int ret = system(cmd.c_str());
if (ret != 0) {
fprintf(stderr, "Error: tileiras compilation failed with code %d\n", ret);
fprintf(stderr, "Command: %s\n", cmd.c_str());
remove(tileir_file.c_str());
remove(cubin_file.c_str());
exit(EXIT_FAILURE);
}
// Read the generated cubin into memory so later benchmark iterations do not depend on temp files.
std::vector<char> cubin = readBinaryFile(cubin_file);
// Remove temporary files after the cubin has been captured.
remove(tileir_file.c_str());
remove(cubin_file.c_str());
return cubin;
}
inline CompiledKernel compileFileWithNVRTC(const char *filename,
int sm_value,
int block_m, int block_n, int block_k,
const std::vector<std::string>& extra_flags) {
// Check include path for cuda_fp16.h
const char *ptr = CUDA_INCLUDE_PATH;
if (ptr[0] == '\0') {
printf("\n ERROR: unable to locate CUDA include directory containing cuda_fp16.h\n");
exit(EXIT_FAILURE);
}
std::vector<std::string> option_storage = {
"-I",
ptr,
"-std=c++20",
"-enable-tile",
"-lineinfo",
"-arch=compute_" + std::to_string(sm_value)
};
appendTileBlockMacroOptions(option_storage, block_m, block_n, block_k);
for (const auto& flag : extra_flags) {
option_storage.push_back(flag);
}
std::vector<const char*> argv_vec;
for (const auto& option : option_storage) {
argv_vec.push_back(option.c_str());
}
const char **argv = argv_vec.data();
int argc = static_cast<int>(argv_vec.size());
std::cerr << "\nCompiling file with NVRTC\n";
std::ifstream inputFile(filename, std::ios::in | std::ios::binary |
std::ios::ate);
if (!inputFile.is_open()) {
std::cerr << "\nerror: unable to open " << filename << " for reading!\n";
exit(EXIT_FAILURE);
}
std::streampos pos = inputFile.tellg();
size_t inputSize = pos;
char * memBlock = new char [inputSize + 1];
inputFile.seekg (0, std::ios::beg);
inputFile.read (memBlock, inputSize);
inputFile.close();
memBlock[inputSize] = '\x0';
// Compile the source string to PTX and Tile IR.
nvrtcProgram prog;
NVRTC_SAFE_CALL("nvrtcCreateProgram", nvrtcCreateProgram(&prog, memBlock,
"testprog", 0, NULL, NULL));
nvrtcResult res = nvrtcCompileProgram(prog, argc, argv);
// Dump the NVRTC compilation log
size_t logSize;
NVRTC_SAFE_CALL("nvrtcGetProgramLogSize", nvrtcGetProgramLogSize(prog, &logSize));
char* log = (char*)malloc(sizeof(char) * logSize + 1);
NVRTC_SAFE_CALL("nvrtcGetProgramLog", nvrtcGetProgramLog(prog, log));
log[logSize] = '\x0';
std::cerr << "\n compilation log ---\n";
std::cerr << log;
std::cerr << "\n end log ---\n\n";
free(log);
NVRTC_SAFE_CALL("nvrtcCompileProgram", res);
// Fetch Tile IR and compile it to cubin before benchmarking.
size_t tileIRSize;
NVRTC_SAFE_CALL("nvrtcGetTileIRSize", nvrtcGetTileIRSize(prog, &tileIRSize));
std::vector<char> tileIR(tileIRSize);
NVRTC_SAFE_CALL("nvrtcGetTileIR", nvrtcGetTileIR(prog, tileIR.data()));
CompiledKernel kernel;
kernel.image = compileTileIRToCubin(tileIR.data(), tileIR.size(), sm_value);
NVRTC_SAFE_CALL("nvrtcDestroyProgram", nvrtcDestroyProgram(&prog));
delete[] memBlock;
return kernel;
}

View File

@@ -0,0 +1,101 @@
/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of NVIDIA CORPORATION 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 ``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 OWNER 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.
*/
/*
* CUDA Tile C++ matrix multiplication kernel used by tileMatmulAutotuner.
*
* This sample implements a tiled FP16 -> FP32 matrix multiplication with
* ct::partition_view and ct::mma. The autotuner compiles this file repeatedly
* with TILE_BLOCK_M, TILE_BLOCK_N, TILE_BLOCK_K, LOAD_LATENCY, and
* STORE_LATENCY defined on the compiler command line.
*
* Approach:
* - Uses ct::tensor_span and ct::partition_view for blocked access.
* - Uses a K-dimension accumulation loop with ct::mma.
* - Loads FP16 inputs into tiles and accumulates into FP32.
*/
#include "cuda_tile.h"
#include <cuda_fp16.h>
namespace ct = cuda::tiles;
extern "C" __tile_global__ void matmul_tile(float* __restrict__ _C,
const __half* __restrict__ _A,
const __half* __restrict__ _B,
int _M, int _N, int _K) {
float* C = ct::assume_aligned<16>(_C);
const __half* A = ct::assume_aligned<16>(_A);
const __half* B = ct::assume_aligned<16>(_B);
auto M = ct::assume_divisible<16>(_M);
auto N = ct::assume_divisible<16>(_N);
auto K = ct::assume_divisible<16>(_K);
// Create tensor spans with runtime shapes (FP16 for A and B)
auto a_span = ct::tensor_span{A, ct::extents{M, K}};
auto b_span = ct::tensor_span{B, ct::extents{K, N}};
auto c_span = ct::tensor_span{C, ct::extents{M, N}};
// Create partition views with compile-time block sizes
auto a_view = ct::partition_view{a_span, ct::shape<TILE_BLOCK_M, TILE_BLOCK_K>{}};
auto b_view = ct::partition_view{b_span, ct::shape<TILE_BLOCK_K, TILE_BLOCK_N>{}};
auto c_view = ct::partition_view{c_span, ct::shape<TILE_BLOCK_M, TILE_BLOCK_N>{}};
// get block indices from the 2D grid
auto [pid_m, pid_n, dummy] = ct::bid();
// initialize FP32 accumulator
auto acc = ct::zeros<ct::tile<float, ct::shape<TILE_BLOCK_M, TILE_BLOCK_N>>>();
// loop over the K dimension in blocks
int num_k_blocks = (K + TILE_BLOCK_K - 1) / TILE_BLOCK_K;
for (auto k_block : ct::irange(0, num_k_blocks)) {
ct::tile<__half, ct::shape<TILE_BLOCK_M, TILE_BLOCK_K>> a_tile;
ct::tile<__half, ct::shape<TILE_BLOCK_K, TILE_BLOCK_N>> b_tile;
// load blocks of A and B (FP16)
[[
cutile::hint(0, latency=LOAD_LATENCY),
]]
a_tile = a_view.load(pid_m, k_block);
[[
cutile::hint(0, latency=LOAD_LATENCY),
]]
b_tile = b_view.load(k_block, pid_n);
// accumulate: acc += A_block @ B_block (FP16 inputs, FP32 accumulator)
// ct::mma handles mixed precision: FP16 operands with FP32 accumulator.
acc = ct::mma(a_tile, b_tile, acc);
}
// store result (FP32)
[[
cutile::hint(0, latency=STORE_LATENCY),
]]
c_view.store(acc, pid_m, pid_n);
}

View File

@@ -0,0 +1,331 @@
/* Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of NVIDIA CORPORATION 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 ``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 OWNER 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.
*/
/*
* CUDA Tile C++ matrix multiplication autotuner.
*
* This sample is the host-side driver for the tiled matrix multiplication
* kernel in matmul.cu. It compiles that kernel repeatedly with different
* TILE_BLOCK_M, TILE_BLOCK_N, TILE_BLOCK_K, LOAD_LATENCY, and STORE_LATENCY
* values configured in a search-space file, derives the launch grid from the
* selected tile size, and reports the fastest configuration for the requested
* problem size.
*
* Backend flow:
* - NVRTC compiles matmul.cu to TileIR, then invokes tileiras to produce a
* cubin image.
* - NVCC compiles matmul.cu as a standalone source file and reuses the
* generated Tile cubin artifact.
* - Both paths load the resulting cubin with the CUDA Driver API and launch
* the same matmul_tile entry point.
*/
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <random>
#include <string>
#include <vector>
#include <cuda.h>
#include <cuda_fp16.h>
#include "backend_common.h"
#include "backend_nvcc.h"
#include "backend_nvrtc.h"
#include "matmul_benchmark.h"
#include <helper_cuda_drvapi.h>
// global SM value (compute capability)
static int smValue = 0;
static constexpr const char *kMatmulKernelName = "matmul_tile";
CompilerBackend parseCompilerBackendValue(const char *value) {
if (std::strcmp(value, "nvrtc") == 0) {
return CompilerBackend::NVRTC;
}
if (std::strcmp(value, "nvcc") == 0) {
return CompilerBackend::NVCC;
}
fprintf(stderr, "Error: unsupported backend '%s'\n", value);
fprintf(stderr, "Expected 'nvrtc' or 'nvcc'.\n");
exit(EXIT_FAILURE);
}
void printCompilerOptions() {
printf("Backend options:\n");
printf(" --backend=nvrtc|nvcc Select backend (default: NVRTC)\n");
printf("\n");
}
CompilerBackend parseCompilerBackendArgs(int argc, char** argv, std::vector<char*>& benchmark_argv) {
CompilerBackend compiler_backend = CompilerBackend::NVRTC;
benchmark_argv.clear();
benchmark_argv.push_back(argv[0]);
for (int i = 1; i < argc; i++) {
if (std::strcmp(argv[i], "--help") == 0 || std::strcmp(argv[i], "-h") == 0) {
printCompilerOptions();
benchmark_argv.push_back(argv[i]);
} else if (std::strncmp(argv[i], "--backend=", 10) == 0) {
compiler_backend = parseCompilerBackendValue(argv[i] + 10);
} else if (std::strcmp(argv[i], "--backend") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "Error: %s requires an argument\n", argv[i]);
exit(EXIT_FAILURE);
}
compiler_backend = parseCompilerBackendValue(argv[++i]);
} else {
benchmark_argv.push_back(argv[i]);
}
}
return compiler_backend;
}
void setSMValue() {
CUdevice device;
int major = 0, minor = 0;
// initialize the CUDA Driver API
checkCudaErrors(cuInit(0));
// get the first device (device 0)
checkCudaErrors(cuDeviceGet(&device, 0));
checkCudaErrors(cuDeviceGetAttribute(&major, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR, device));
checkCudaErrors(cuDeviceGetAttribute(&minor, CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MINOR, device));
printf("GPU Compute Capability: %d.%d\n", major, minor);
smValue = major * 10 + minor;
}
CompiledKernel compileFile(const char *filename,
int block_m, int block_n, int block_k,
CompilerBackend compiler_backend,
const std::vector<std::string>& extra_flags = {}) {
if (compiler_backend == CompilerBackend::NVCC) {
return compileFileWithNVCC(filename, smValue, block_m, block_n, block_k, extra_flags);
}
return compileFileWithNVRTC(filename, smValue, block_m, block_n, block_k, extra_flags);
}
void loadAndExecuteKernel(const CompiledKernel& compiled_kernel,
CUdeviceptr d_A, CUdeviceptr d_B, CUdeviceptr d_C,
int M, int N, int K,
unsigned int gridDimX, unsigned int gridDimY, unsigned int sMem) {
CUmodule module;
CUfunction kernel_addr;
void* args[] = {
(void*)&d_C,
(void*)&d_A,
(void*)&d_B,
(void*)&M,
(void*)&N,
(void*)&K
};
checkCudaErrors(cuModuleLoadData(&module, compiled_kernel.image.data()));
checkCudaErrors(cuModuleGetFunction(&kernel_addr, module, kMatmulKernelName));
checkCudaErrors(cuLaunchKernel(kernel_addr,
gridDimX, gridDimY, 1, // grid dim
1, 1, 1, // block dim
sMem, 0, // shared mem, stream
args, // arguments
NULL));
checkCudaErrors(cuCtxSynchronize());
// cleanup
checkCudaErrors(cuModuleUnload(module));
}
void autotuner(int M, int N, int K,
const char *kernel_file,
const SearchSpace& search_space,
CompilerBackend compiler_backend) {
printf("\n=== Matrix: C[%dx%d] = A[%dx%d] x B[%dx%d] (FP16->FP32) ===\n",
M, N, M, K, K, N);
printf(" FLOPs: %.2f GFLOP\n", 2.0 * M * N * K / 1e9);
// allocate and initialize (FP16 for A and B)
std::vector<__half> h_A(M * K), h_B(K * N);
std::vector<float> h_C;
srand(42);
for (int i = 0; i < M * K; i++) h_A[i] = __float2half((float)rand() / RAND_MAX - 0.5f);
for (int i = 0; i < K * N; i++) h_B[i] = __float2half((float)rand() / RAND_MAX - 0.5f);
// compute the CPU reference unless validation is disabled
if (use_validation()) {
h_C.resize(M * N);
matmul_cpu(h_C.data(), h_A.data(), h_B.data(), M, N, K);
}
// allocate device memory
CUdeviceptr d_A, d_B, d_C;
checkCudaErrors(cuMemAlloc(&d_A, M * K * sizeof(__half)));
checkCudaErrors(cuMemAlloc(&d_B, K * N * sizeof(__half)));
checkCudaErrors(cuMemAlloc(&d_C, M * N * sizeof(float)));
checkCudaErrors(cuMemcpyHtoD(d_A, h_A.data(), M * K * sizeof(__half)));
checkCudaErrors(cuMemcpyHtoD(d_B, h_B.data(), K * N * sizeof(__half)));
// helper lambda to clear output
auto clear_output = [&]() {
std::vector<float> zeros(M * N, 0.0f);
checkCudaErrors(cuMemcpyHtoD(d_C, zeros.data(), M * N * sizeof(float)));
};
struct AutotuneResult {
int block_m;
int block_n;
int block_k;
int grid_x;
int grid_y;
int load_latency;
int store_latency;
BenchmarkResult result;
};
std::vector<AutotuneResult> autotune_results;
size_t config_count = 0;
size_t total_configs = search_space.tile_options.size() *
search_space.load_latency_options.size() *
search_space.store_latency_options.size();
for (const auto& tile : search_space.tile_options) {
int grid_x = ceilDiv(M, tile.block_m);
int grid_y = ceilDiv(N, tile.block_n);
for (int load_lat : search_space.load_latency_options) {
for (int store_lat : search_space.store_latency_options) {
config_count++;
printf(" [%zu/%zu] ", config_count, total_configs);
std::vector<std::string> compile_flags = {
"-DLOAD_LATENCY=" + std::to_string(load_lat),
"-DSTORE_LATENCY=" + std::to_string(store_lat)
};
CompiledKernel compiled_kernel = compileFile(kernel_file,
tile.block_m, tile.block_n, tile.block_k,
compiler_backend,
compile_flags);
clear_output();
std::string config_name = "bm=" + std::to_string(tile.block_m) +
",bn=" + std::to_string(tile.block_n) +
",bk=" + std::to_string(tile.block_k) +
",gx=" + std::to_string(grid_x) +
",gy=" + std::to_string(grid_y) +
",ld=" + std::to_string(load_lat) +
",st=" + std::to_string(store_lat);
auto result = run_benchmark(config_name.c_str(),
[&]() {
loadAndExecuteKernel(compiled_kernel, d_A, d_B, d_C, M, N, K,
grid_x, grid_y, 0);
},
[&]() {
std::vector<float> h_result(M * N);
checkCudaErrors(cuMemcpyDtoH(h_result.data(), d_C,
M * N * sizeof(float)));
return verify_matmul_result(config_name.c_str(),
h_result.data(), h_C.data(), M, N);
},
M, N, K);
print_result(result);
autotune_results.push_back({tile.block_m, tile.block_n, tile.block_k,
grid_x, grid_y, load_lat, store_lat, result});
}
}
}
// find the best configuration by GFLOPS
auto best = std::max_element(autotune_results.begin(), autotune_results.end(),
[](const AutotuneResult& a, const AutotuneResult& b) {
return a.result.gflops < b.result.gflops;
});
printf("\n *** BEST CONFIGURATION ***\n");
printf(" BLOCK_M=%d, BLOCK_N=%d, BLOCK_K=%d\n",
best->block_m, best->block_n, best->block_k);
printf(" LOAD_LATENCY=%d, STORE_LATENCY=%d, grid_x=%d, grid_y=%d\n",
best->load_latency, best->store_latency, best->grid_x, best->grid_y);
printf(" Performance: %.1f GFLOPS, %.3f ms, %.1f GB/s\n",
best->result.gflops, best->result.time_ms, best->result.bandwidth_gb_s);
checkCudaErrors(cuMemFree(d_A));
checkCudaErrors(cuMemFree(d_B));
checkCudaErrors(cuMemFree(d_C));
}
int main(int argc, char** argv) {
std::vector<char*> benchmark_argv;
CompilerBackend compiler_backend = parseCompilerBackendArgs(argc, argv, benchmark_argv);
parse_benchmark_args(static_cast<int>(benchmark_argv.size()), benchmark_argv.data());
print_device_info();
// initialize CUDA and get compute capability
setSMValue();
CUcontext context;
CUdevice cuDevice = 0;
checkCudaErrors(cuInit(0));
checkCudaErrors(cuDeviceGet(&cuDevice, 0));
checkCudaErrors(cuCtxCreate(&context, NULL, 0, cuDevice));
printf("\nMatrix Multiplication Autotuner (FP16 inputs, FP32 accumulate)\n");
printf("==============================================================\n");
printf("Backend: %s\n", compilerBackendName(compiler_backend));
char *kernel_file = findSampleFile("matmul.cu", argv[0]);
if (kernel_file == NULL) {
fprintf(stderr, "Error: unable to locate matmul.cu\n");
return 1;
}
char *search_space_file = findSampleFile(kSearchSpaceFileName, argv[0]);
if (search_space_file == NULL) {
fprintf(stderr, "Error: unable to locate %s\n", kSearchSpaceFileName);
free(kernel_file);
return 1;
}
SearchSpace search_space = loadSearchSpace(search_space_file);
printf("Search space: %s\n", search_space_file);
printf("Tuning for M=1024, N=4096, K=1024\n");
autotuner(1024, 4096, 1024, kernel_file, search_space, compiler_backend);
free(kernel_file);
free(search_space_file);
return 0;
}