Skip to content

Commit 8e77671

Browse files
LessUpCopilot
andauthored
fix: stabilize CUDA build and validation (#1)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 89ce670 commit 8e77671

8 files changed

Lines changed: 218 additions & 72 deletions

File tree

CMakeLists.txt

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
11
cmake_minimum_required(VERSION 3.18)
2+
3+
if(UNIX)
4+
if(EXISTS "/usr/bin/gcc-12")
5+
set(SGEMM_DEFAULT_C_COMPILER "/usr/bin/gcc-12")
6+
elseif(EXISTS "/usr/bin/cc")
7+
set(SGEMM_DEFAULT_C_COMPILER "/usr/bin/cc")
8+
endif()
9+
10+
if(EXISTS "/usr/bin/g++-12")
11+
set(SGEMM_DEFAULT_CXX_COMPILER "/usr/bin/g++-12")
12+
elseif(EXISTS "/usr/bin/c++")
13+
set(SGEMM_DEFAULT_CXX_COMPILER "/usr/bin/c++")
14+
endif()
15+
16+
if(NOT DEFINED CMAKE_C_COMPILER AND DEFINED SGEMM_DEFAULT_C_COMPILER)
17+
set(CMAKE_C_COMPILER "${SGEMM_DEFAULT_C_COMPILER}")
18+
endif()
19+
if(NOT DEFINED CMAKE_CXX_COMPILER AND DEFINED SGEMM_DEFAULT_CXX_COMPILER)
20+
set(CMAKE_CXX_COMPILER "${SGEMM_DEFAULT_CXX_COMPILER}")
21+
endif()
22+
if(NOT DEFINED CMAKE_CUDA_HOST_COMPILER AND DEFINED SGEMM_DEFAULT_CXX_COMPILER)
23+
set(CMAKE_CUDA_HOST_COMPILER "${SGEMM_DEFAULT_CXX_COMPILER}")
24+
endif()
25+
endif()
26+
227
project(sgemm_optimization
328
VERSION 2.1.0
429
DESCRIPTION "SGEMM optimization from naive to Tensor Core"
@@ -15,15 +40,26 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
1540

1641
# ── CUDA 配置 ────────────────────────────────────────────────────
1742
find_package(CUDAToolkit REQUIRED)
43+
get_target_property(SGEMM_CUDART_LIBRARY CUDA::cudart IMPORTED_LOCATION)
44+
get_filename_component(SGEMM_CUDA_LIBRARY_DIR "${SGEMM_CUDART_LIBRARY}" DIRECTORY)
1845

1946
if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
20-
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
21-
set(CMAKE_CUDA_ARCHITECTURES native)
22-
else()
23-
set(CMAKE_CUDA_ARCHITECTURES 70 75 80 86 89 90)
24-
endif()
47+
# `native` has proven unreliable with some toolkit/driver combinations and
48+
# can silently drop WMMA-capable codegen. Default to a portable set that
49+
# keeps pre-Volta fallback builds working while still emitting Tensor Core
50+
# code for modern GPUs.
51+
set(CMAKE_CUDA_ARCHITECTURES 52 60 61 70 75 80 86 89 90)
2552
endif()
2653

54+
set(SGEMM_HAS_WMMA_TARGET 0)
55+
foreach(cuda_arch IN LISTS CMAKE_CUDA_ARCHITECTURES)
56+
string(REGEX MATCH "^[0-9]+" SGEMM_CUDA_ARCH_NUMBER "${cuda_arch}")
57+
if(SGEMM_CUDA_ARCH_NUMBER AND SGEMM_CUDA_ARCH_NUMBER GREATER_EQUAL 70)
58+
set(SGEMM_HAS_WMMA_TARGET 1)
59+
break()
60+
endif()
61+
endforeach()
62+
2763
# ── 输出目录 ─────────────────────────────────────────────────────
2864
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
2965

@@ -33,6 +69,7 @@ add_executable(sgemm_benchmark src/main.cu)
3369
target_include_directories(sgemm_benchmark PRIVATE
3470
${CMAKE_CURRENT_SOURCE_DIR}/src
3571
)
72+
target_compile_definitions(sgemm_benchmark PRIVATE SGEMM_HAS_WMMA_TARGET=${SGEMM_HAS_WMMA_TARGET})
3673

3774
target_link_libraries(sgemm_benchmark PRIVATE
3875
CUDA::cudart
@@ -61,6 +98,8 @@ if(BUILD_TESTS)
6198

6299
add_executable(test_sgemm tests/test_sgemm.cu)
63100
target_include_directories(test_sgemm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
101+
target_compile_definitions(test_sgemm PRIVATE SGEMM_HAS_WMMA_TARGET=${SGEMM_HAS_WMMA_TARGET})
102+
target_link_options(test_sgemm PRIVATE -L${SGEMM_CUDA_LIBRARY_DIR})
64103
target_link_libraries(test_sgemm PRIVATE
65104
GTest::gtest_main
66105
CUDA::cudart
@@ -74,6 +113,8 @@ if(BUILD_TESTS)
74113
# 工具层测试
75114
add_executable(test_utils tests/test_utils.cu)
76115
target_include_directories(test_utils PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
116+
target_compile_definitions(test_utils PRIVATE SGEMM_HAS_WMMA_TARGET=${SGEMM_HAS_WMMA_TARGET})
117+
target_link_options(test_utils PRIVATE -L${SGEMM_CUDA_LIBRARY_DIR})
77118
target_link_libraries(test_utils PRIVATE
78119
GTest::gtest_main
79120
CUDA::cudart
@@ -87,6 +128,8 @@ if(BUILD_TESTS)
87128
# 性能回归测试
88129
add_executable(test_performance tests/test_performance.cu)
89130
target_include_directories(test_performance PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
131+
target_compile_definitions(test_performance PRIVATE SGEMM_HAS_WMMA_TARGET=${SGEMM_HAS_WMMA_TARGET})
132+
target_link_options(test_performance PRIVATE -L${SGEMM_CUDA_LIBRARY_DIR})
90133
target_link_libraries(test_performance PRIVATE
91134
GTest::gtest_main
92135
CUDA::cudart

src/kernels/tensor_core_sgemm.cuh

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
#include <cuda_runtime.h>
2727
#include <functional>
2828

29+
#ifndef SGEMM_HAS_WMMA_TARGET
30+
#define SGEMM_HAS_WMMA_TARGET 0
31+
#endif
32+
2933
// ============================================================================
3034
// WMMA Tile Dimensions
3135
// ============================================================================
@@ -48,6 +52,7 @@ using tensor_core::WMMA_N;
4852
* 检查当前设备是否支持 Tensor Core (sm_70+)
4953
*/
5054
inline bool tensorCoresAvailable() { return DeviceInfoCache::instance().hasTensorCores(); }
55+
inline constexpr bool tensorCoreFastPathCompiled() { return SGEMM_HAS_WMMA_TARGET != 0; }
5156

5257
/**
5358
* 检查给定维度是否适合 Tensor Core 加速
@@ -100,24 +105,9 @@ nullFallback(const float *, const float *, float *, int, int, int, cudaStream_t
100105
// Tensor Core Compute - 纯 WMMA 计算路径
101106
// ============================================================================
102107

103-
// WMMA is only available on sm_70+
104-
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 700
108+
// WMMA is only emitted when the configured target architectures include sm_70+.
109+
#if SGEMM_HAS_WMMA_TARGET
105110
#include <mma.h>
106-
#endif
107-
108-
/**
109-
* FP32 → FP16 转换内核
110-
*/
111-
__global__ void float_to_half_kernel(const float *__restrict__ input, half *__restrict__ output,
112-
int size) {
113-
int idx = blockIdx.x * blockDim.x + threadIdx.x;
114-
if (idx < size) {
115-
output[idx] = __float2half(input[idx]);
116-
}
117-
}
118-
119-
// WMMA kernel is only available on sm_70+
120-
#if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 700
121111

122112
/**
123113
* 纯 Tensor Core WMMA 计算内核
@@ -132,6 +122,7 @@ __global__ void float_to_half_kernel(const float *__restrict__ input, half *__re
132122
__global__ void tensor_core_sgemm_kernel_fp16(const half *__restrict__ A,
133123
const half *__restrict__ B, float *__restrict__ C,
134124
int M, int K, int N) {
125+
#if defined(__CUDA_ARCH__) && __CUDA_ARCH__ >= 700
135126
int warpM = blockIdx.y;
136127
int warpN = blockIdx.x;
137128

@@ -159,6 +150,14 @@ __global__ void tensor_core_sgemm_kernel_fp16(const half *__restrict__ A,
159150
}
160151

161152
nvcuda::wmma::store_matrix_sync(C + aRow * N + bCol, c_frag, N, nvcuda::wmma::mem_row_major);
153+
#else
154+
(void)A;
155+
(void)B;
156+
(void)C;
157+
(void)M;
158+
(void)K;
159+
(void)N;
160+
#endif
162161
}
163162

164163
/**
@@ -179,13 +178,24 @@ inline void launch_tensor_core_sgemm_fp16_fast_path(const half *A, const half *B
179178
}
180179

181180
#else
182-
// Stub implementations for older architectures (will not be called)
181+
// Stub implementations when no WMMA-capable target was configured.
183182
inline void launch_tensor_core_sgemm_fp16_fast_path(const half *, const half *, float *, int, int,
184183
int, cudaStream_t) {
185-
// This function should never be called on pre-sm_70 GPUs
184+
throw CudaError("Tensor Core fast path was not compiled for the configured CUDA architectures");
186185
}
187186
#endif
188187

188+
/**
189+
* FP32 → FP16 转换内核
190+
*/
191+
__global__ void float_to_half_kernel(const float *__restrict__ input, half *__restrict__ output,
192+
int size) {
193+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
194+
if (idx < size) {
195+
output[idx] = __float2half(input[idx]);
196+
}
197+
}
198+
189199
/**
190200
* 纯 WMMA 计算路径入口(FP16 输入)
191201
*
@@ -198,9 +208,13 @@ inline void launch_tensor_core_sgemm_fp16(const half *A, const half *B, float *C
198208
return;
199209
}
200210

211+
if (!tensorCoreFastPathCompiled()) {
212+
throw CudaError("launch_tensor_core_sgemm_fp16 requires a build that targets sm_70+");
213+
}
214+
201215
if (!tensorCoresAvailable() || !tensorCoreDimensionsSupported(M, K, N)) {
202-
throw CudaError("launch_tensor_core_sgemm_fp16 requires sm_70+ and dimensions aligned "
203-
"to 16");
216+
throw CudaError(
217+
"launch_tensor_core_sgemm_fp16 requires runtime sm_70+ support and dimensions aligned to 16");
204218
}
205219

206220
launch_tensor_core_sgemm_fp16_fast_path(A, B, C, M, K, N, stream);
@@ -235,7 +249,8 @@ inline void launch_tensor_core_sgemm_with_fallback(const float *A, const float *
235249
}
236250

237251
// Fallback 路径:设备或维度不支持 Tensor Core
238-
if (!tensorCoresAvailable() || !tensorCoreDimensionsSupported(M, K, N)) {
252+
if (!tensorCoreFastPathCompiled() || !tensorCoresAvailable() ||
253+
!tensorCoreDimensionsSupported(M, K, N)) {
239254
fallback(A, B, C, M, K, N, stream);
240255
return;
241256
}
@@ -283,7 +298,8 @@ inline void launch_tensor_core_sgemm_with_fallback(const float *A, const float *
283298
inline void launch_tensor_core_sgemm_with_fallback(const float *A, const float *B, float *C, int M,
284299
int K, int N, const FallbackKernel &fallback,
285300
cudaStream_t stream = 0) {
286-
launch_tensor_core_sgemm_with_fallback(A, B, C, M, K, N, fallback, stream);
301+
launch_tensor_core_sgemm_with_fallback<const FallbackKernel &>(A, B, C, M, K, N, fallback,
302+
stream);
287303
}
288304

289305
// ============================================================================

src/utils/benchmark_metrics.cuh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ inline float getTheoreticalPeakGflops() {
6060
DeviceInfoCache &cache = DeviceInfoCache::instance();
6161
const cudaDeviceProp &prop = cache.prop();
6262

63-
// 峰值 GFLOPS = SMs * cores/SM * 2 (FMA) * clock (GHz) * 1000 (MHz factor)
64-
float peakGflops = prop.multiProcessorCount * cache.coresPerSM() * 2 * cache.clockGHz() * 1000;
63+
// 峰值 GFLOPS = SMs * cores/SM * 2 (FMA) * clock (GHz)
64+
float peakGflops = prop.multiProcessorCount * cache.coresPerSM() * 2 * cache.clockGHz();
6565

6666
return peakGflops;
6767
}

src/utils/cuda_utils.cuh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,30 @@ inline void initRandomMatrix(float *data, int rows, int cols, float min_val = -1
168168
// Utility Functions
169169
// ============================================================================
170170

171+
inline bool cudaDeviceAvailable() {
172+
int device_count = 0;
173+
cudaError_t status = cudaGetDeviceCount(&device_count);
174+
if (status == cudaSuccess) {
175+
return device_count > 0;
176+
}
177+
178+
if (status == cudaErrorNoDevice || status == cudaErrorInsufficientDriver ||
179+
status == cudaErrorInitializationError || status == cudaErrorSystemDriverMismatch) {
180+
cudaGetLastError();
181+
return false;
182+
}
183+
184+
cudaGetLastError();
185+
return false;
186+
}
187+
171188
// Get GPU device properties
172189
inline void printGPUInfo() {
190+
if (!cudaDeviceAvailable()) {
191+
printf("GPU Device: unavailable (no CUDA-capable device detected)\n\n");
192+
return;
193+
}
194+
173195
int device;
174196
CUDA_CHECK(cudaGetDevice(&device));
175197

tests/gtest_cuda_environment.cuh

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#pragma once
2+
3+
#include <cstring>
4+
#include <gtest/gtest.h>
5+
6+
#include "utils/cuda_utils.cuh"
7+
8+
inline bool isGtestListMode(int argc, char **argv) {
9+
for (int i = 1; i < argc; ++i) {
10+
if (std::strcmp(argv[i], "--gtest_list_tests") == 0) {
11+
return true;
12+
}
13+
}
14+
return false;
15+
}
16+
17+
class CudaTestEnvironment : public ::testing::Environment {
18+
public:
19+
void SetUp() override {
20+
if (!cudaDeviceAvailable()) {
21+
GTEST_SKIP() << "No CUDA-capable device is detected";
22+
}
23+
24+
printGPUInfo();
25+
}
26+
};
27+
28+
inline int runCudaAwareTests(int argc, char **argv) {
29+
::testing::InitGoogleTest(&argc, argv);
30+
31+
if (!isGtestListMode(argc, argv)) {
32+
::testing::AddGlobalTestEnvironment(new CudaTestEnvironment());
33+
}
34+
35+
return RUN_ALL_TESTS();
36+
}

0 commit comments

Comments
 (0)