mirror of
https://github.com/NVIDIA/cuda-samples.git
synced 2026-10-13 00:08:26 +08:00
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:
67
cpp/9_CUDA_Tile/tileMatmulAutotuner/CMakeLists.txt
Normal file
67
cpp/9_CUDA_Tile/tileMatmulAutotuner/CMakeLists.txt
Normal 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()
|
||||
59
cpp/9_CUDA_Tile/tileMatmulAutotuner/README.md
Normal file
59
cpp/9_CUDA_Tile/tileMatmulAutotuner/README.md
Normal 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.
|
||||
@@ -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
|
||||
314
cpp/9_CUDA_Tile/tileMatmulAutotuner/backend_common.h
Normal file
314
cpp/9_CUDA_Tile/tileMatmulAutotuner/backend_common.h
Normal 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;
|
||||
}
|
||||
111
cpp/9_CUDA_Tile/tileMatmulAutotuner/backend_nvcc.h
Normal file
111
cpp/9_CUDA_Tile/tileMatmulAutotuner/backend_nvcc.h
Normal 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;
|
||||
}
|
||||
186
cpp/9_CUDA_Tile/tileMatmulAutotuner/backend_nvrtc.h
Normal file
186
cpp/9_CUDA_Tile/tileMatmulAutotuner/backend_nvrtc.h
Normal 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;
|
||||
}
|
||||
101
cpp/9_CUDA_Tile/tileMatmulAutotuner/matmul.cu
Normal file
101
cpp/9_CUDA_Tile/tileMatmulAutotuner/matmul.cu
Normal 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);
|
||||
}
|
||||
331
cpp/9_CUDA_Tile/tileMatmulAutotuner/matmul_autotuner.cpp
Normal file
331
cpp/9_CUDA_Tile/tileMatmulAutotuner/matmul_autotuner.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user