Skip to content

Commit 7a3f25a

Browse files
LessUpCopilot
andcommitted
feat: Add device capability seam for testable GPU queries
Extract GPU capability queries from singleton pattern to allow tests to inject fake device info without requiring specific GPU hardware. Design: - DeviceInfoProvider: lightweight struct-based interface - ProductionDeviceInfoProvider: adapter backed by DeviceInfoCache - Overloaded API: no-arg versions use production adapter by default - Tests can supply custom providers for capability scenarios Changes: - src/utils/device_info_provider.cuh: new seam interface - src/utils/cuda_utils.cuh: add production adapter implementation - src/kernels/tensor_core_sgemm.cuh: use seam for capability queries - src/utils/benchmark_metrics.cuh: use seam for peak calculations - tests/test_device_info_seam.cu: comprehensive test coverage - docs/device_capability_seam.md: design documentation The fallback policy was already well-structured with the template-based launch_tensor_core_sgemm_with_fallback interface and BankConflictFreeFallback concrete policy. No changes needed there. All existing production code continues to work unchanged, calling no-arg versions of functions that now delegate to the production adapter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 10fede2 commit 7a3f25a

7 files changed

Lines changed: 523 additions & 23 deletions

File tree

CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,23 @@ if(BUILD_TESTS)
121121
$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>
122122
)
123123

124+
# Device info seam test
125+
add_executable(test_device_info_seam tests/test_device_info_seam.cu)
126+
target_include_directories(test_device_info_seam PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
127+
target_link_libraries(test_device_info_seam PRIVATE
128+
GTest::gtest_main
129+
CUDA::cudart
130+
CUDA::cublas
131+
)
132+
target_compile_options(test_device_info_seam PRIVATE
133+
$<$<COMPILE_LANGUAGE:CUDA>:--expt-relaxed-constexpr>
134+
)
135+
124136
include(GoogleTest)
125137
gtest_discover_tests(test_sgemm)
126138
gtest_discover_tests(test_utils)
127139
gtest_discover_tests(test_performance)
128140
gtest_discover_tests(test_benchmark_settings)
129141
gtest_discover_tests(test_kernel_catalog)
142+
gtest_discover_tests(test_device_info_seam)
130143
endif()

docs/device_capability_seam.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Device Capability Seam
2+
3+
## Overview
4+
5+
The device capability seam extracts GPU capability queries from the singleton pattern (`DeviceInfoCache::instance()`), allowing tests to inject fake device info without requiring specific GPU hardware.
6+
7+
## Design
8+
9+
### Core Interface
10+
11+
`DeviceInfoProvider` (in `src/utils/device_info_provider.cuh`):
12+
- Lightweight struct-based interface (no virtual dispatch overhead)
13+
- Provides minimal surface for querying GPU capabilities
14+
- Methods: `hasTensorCores()`, `computeMajor()`, `computeMinor()`, `smCount()`, etc.
15+
16+
### Production Adapter
17+
18+
`ProductionDeviceInfoProvider`:
19+
- Backed by `DeviceInfoCache` singleton
20+
- Used by default in all production code paths
21+
- Zero runtime overhead compared to direct singleton access
22+
23+
### API Pattern
24+
25+
All capability-dependent functions provide two overloads:
26+
27+
```cpp
28+
// With explicit provider (for tests)
29+
bool tensorCoresAvailable(const DeviceInfoProvider &provider);
30+
float getTheoreticalPeakGflops(const DeviceInfoProvider &provider);
31+
32+
// Default (uses production provider)
33+
bool tensorCoresAvailable();
34+
float getTheoreticalPeakGflops();
35+
```
36+
37+
## Usage
38+
39+
### Production Code
40+
41+
Production code continues to use no-argument versions:
42+
43+
```cpp
44+
if (tensorCoresAvailable()) {
45+
// Use Tensor Cores
46+
}
47+
float peak = getTheoreticalPeakGflops();
48+
```
49+
50+
### Test Code
51+
52+
Tests can inject fake device info:
53+
54+
```cpp
55+
cudaDeviceProp fake_prop{};
56+
fake_prop.major = 7; // Volta
57+
fake_prop.minor = 0;
58+
fake_prop.multiProcessorCount = 80;
59+
60+
DeviceInfoProvider fake_provider{&fake_prop, 64, 1.53f};
61+
62+
EXPECT_TRUE(tensorCoresAvailable(fake_provider));
63+
EXPECT_NEAR(getTheoreticalPeakGflops(fake_provider), 15667.2f, 0.1f);
64+
```
65+
66+
## Affected Modules
67+
68+
### `src/utils/cuda_utils.cuh`
69+
- Added production adapter implementation
70+
- `DeviceInfoCache` remains unchanged (still singleton)
71+
- Only the production adapter calls the singleton
72+
73+
### `src/kernels/tensor_core_sgemm.cuh`
74+
- `tensorCoresAvailable()` - overloaded
75+
- `getTensorCoreArchName()` - overloaded
76+
- No longer calls `DeviceInfoCache::instance()` directly
77+
78+
### `src/utils/benchmark_metrics.cuh`
79+
- `getTheoreticalPeakGflops()` - overloaded
80+
- `getTheoreticalPeakBandwidth()` - overloaded
81+
- No longer calls `DeviceInfoCache::instance()` directly
82+
83+
## Testing
84+
85+
See `tests/test_device_info_seam.cu` for comprehensive examples:
86+
87+
- Fake device provider creation
88+
- Tensor Core capability testing without real hardware
89+
- Peak GFLOPS/bandwidth calculations with fake devices
90+
- Architectural classification (Volta, Ampere, Hopper, etc.)
91+
- Production adapter integration tests
92+
93+
## Design Rationale
94+
95+
### Why struct-based instead of virtual interfaces?
96+
97+
- Zero runtime overhead (no vtable lookups)
98+
- Header-only implementation remains simple
99+
- Sufficient for the single use case (production vs test)
100+
101+
### Why preserve the singleton?
102+
103+
- Minimal code changes to existing production paths
104+
- The singleton itself isn't problematic; the hard dependency was
105+
- Production adapter provides the indirection we need
106+
107+
### Why overloads instead of default parameters?
108+
109+
- Clearer call sites in tests
110+
- No ambiguity about which version is being called
111+
- Easier to grep for test-specific vs production usage
112+
113+
## Limitations
114+
115+
- Tests still require a CUDA-capable GPU to run (for initialization)
116+
- Only capability queries are abstracted, not actual kernel execution
117+
- The seam is focused on decision logic, not full GPU emulation
118+
119+
## Future Extensions
120+
121+
If needed, this seam could be extended to support:
122+
- Mock kernel execution for unit tests
123+
- Multiple device scenarios in a single test
124+
- Device capability fuzzing for edge case discovery

src/kernels/tensor_core_sgemm.cuh

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,19 @@ using tensor_core::WMMA_N;
4646

4747
/**
4848
* 检查当前设备是否支持 Tensor Core (sm_70+)
49+
*
50+
* 提供重载版本以支持可注入的 device info provider。
4951
*/
50-
inline bool tensorCoresAvailable() { return DeviceInfoCache::instance().hasTensorCores(); }
52+
inline bool tensorCoresAvailable(const DeviceInfoProvider &provider) {
53+
return provider.hasTensorCores();
54+
}
55+
56+
/**
57+
* 检查当前设备是否支持 Tensor Core (默认使用生产环境设备)
58+
*/
59+
inline bool tensorCoresAvailable() {
60+
return tensorCoresAvailable(getProductionDeviceInfo());
61+
}
5162

5263
/**
5364
* 检查给定维度是否适合 Tensor Core 加速
@@ -59,20 +70,30 @@ inline bool tensorCoreDimensionsSupported(int M, int K, int N) {
5970

6071
/**
6172
* 获取当前设备的 Tensor Core 信息字符串
73+
*
74+
* 提供重载版本以支持可注入的 device info provider。
6275
*/
63-
inline const char *getTensorCoreArchName() {
64-
const cudaDeviceProp &prop = DeviceInfoCache::instance().prop();
65-
66-
if (prop.major == 7) {
67-
return (prop.minor == 0) ? "Volta" : (prop.minor == 5) ? "Turing" : "Unknown sm_7x";
68-
} else if (prop.major == 8) {
69-
return (prop.minor == 0 || prop.minor == 6) ? "Ampere" : "Ampere/Ada";
70-
} else if (prop.major == 9) {
76+
inline const char *getTensorCoreArchName(const DeviceInfoProvider &provider) {
77+
int major = provider.computeMajor();
78+
int minor = provider.computeMinor();
79+
80+
if (major == 7) {
81+
return (minor == 0) ? "Volta" : (minor == 5) ? "Turing" : "Unknown sm_7x";
82+
} else if (major == 8) {
83+
return (minor == 0 || minor == 6) ? "Ampere" : "Ampere/Ada";
84+
} else if (major == 9) {
7185
return "Hopper";
7286
}
7387
return "Unknown";
7488
}
7589

90+
/**
91+
* 获取当前设备的 Tensor Core 信息字符串(默认使用生产环境设备)
92+
*/
93+
inline const char *getTensorCoreArchName() {
94+
return getTensorCoreArchName(getProductionDeviceInfo());
95+
}
96+
7697
// ============================================================================
7798
// Fallback 策略接口
7899
// ============================================================================

src/utils/benchmark_metrics.cuh

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,39 +55,46 @@ inline PerformanceMetrics calculateSgemmMetrics(int M, int K, int N, float time_
5555
* - SM 数量
5656
* - 每 SM 的 CUDA 核心数
5757
* - 时钟频率
58+
*
59+
* 提供重载版本以支持可注入的 device info provider。
5860
*/
59-
inline float getTheoreticalPeakGflops() {
60-
DeviceInfoCache &cache = DeviceInfoCache::instance();
61-
const cudaDeviceProp &prop = cache.prop();
62-
61+
inline float getTheoreticalPeakGflops(const DeviceInfoProvider &provider) {
6362
// 峰值 GFLOPS = SMs * cores/SM * 2 (FMA) * clock (GHz) * 1000 (MHz factor)
64-
float peakGflops = prop.multiProcessorCount * cache.coresPerSM() * 2 * cache.clockGHz() * 1000;
65-
63+
float peakGflops = provider.smCount() * provider.cores_per_sm * 2 * provider.clock_ghz * 1000;
6664
return peakGflops;
6765
}
6866

67+
/**
68+
* 获取 GPU 理论峰值 GFLOPS(默认使用生产环境设备)
69+
*/
70+
inline float getTheoreticalPeakGflops() {
71+
return getTheoreticalPeakGflops(getProductionDeviceInfo());
72+
}
73+
6974
/**
7075
* 获取 GPU 理论峰值带宽 (GB/s)
7176
*
7277
* 基于内存时钟频率和总线宽度计算:
7378
* - DDR 倍率 (2x)
7479
* - 内存时钟频率
7580
* - 内存总线宽度
81+
*
82+
* 提供重载版本以支持可注入的 device info provider。
7683
*/
77-
inline float getTheoreticalPeakBandwidth() {
78-
const cudaDeviceProp &prop = DeviceInfoCache::instance().prop();
79-
84+
inline float getTheoreticalPeakBandwidth(const DeviceInfoProvider &provider) {
8085
// 内存时钟频率 (Hz -> MHz)
81-
float memoryClockMHz = static_cast<float>(prop.memoryClockRate) / 1000.0f;
86+
float memoryClockMHz = static_cast<float>(provider.memoryClockRate()) / 1000.0f;
8287

8388
// 如果 memoryClockRate 不可用,使用架构默认值
8489
if (memoryClockMHz <= 0) {
85-
switch (prop.major) {
90+
int major = provider.computeMajor();
91+
int minor = provider.computeMinor();
92+
switch (major) {
8693
case 7:
87-
memoryClockMHz = (prop.minor == 5) ? 1750.0f : 877.0f; // Turing vs Volta
94+
memoryClockMHz = (minor == 5) ? 1750.0f : 877.0f; // Turing vs Volta
8895
break;
8996
case 8:
90-
memoryClockMHz = (prop.minor == 6) ? 1215.0f : 1593.0f; // A100 vs RTX 30
97+
memoryClockMHz = (minor == 6) ? 1215.0f : 1593.0f; // A100 vs RTX 30
9198
break;
9299
case 9:
93100
memoryClockMHz = 2619.0f; // H100 HBM3
@@ -98,11 +105,18 @@ inline float getTheoreticalPeakBandwidth() {
98105
}
99106

100107
// 峰值带宽 = 2 (DDR) * clock (MHz) * bus width (bits) / 8 (bytes)
101-
float peakBandwidth = 2 * memoryClockMHz * (prop.memoryBusWidth / 8) / 1000.0f;
108+
float peakBandwidth = 2 * memoryClockMHz * (provider.memoryBusWidth() / 8) / 1000.0f;
102109

103110
return peakBandwidth; // GB/s
104111
}
105112

113+
/**
114+
* 获取 GPU 理论峰值带宽(默认使用生产环境设备)
115+
*/
116+
inline float getTheoreticalPeakBandwidth() {
117+
return getTheoreticalPeakBandwidth(getProductionDeviceInfo());
118+
}
119+
106120
/**
107121
* 计算效率(相对于理论峰值的百分比)
108122
*/

src/utils/cuda_utils.cuh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#include <curand.h>
88
#include <random>
99

10+
#include "device_info_provider.cuh"
11+
1012
// ============================================================================
1113
// 命名常量
1214
// ============================================================================
@@ -247,3 +249,26 @@ class DeviceInfoCache {
247249
int coresPerSM_;
248250
float clockGHz_;
249251
};
252+
253+
// ============================================================================
254+
// Device Info Provider Implementation
255+
// ============================================================================
256+
257+
/**
258+
* Production adapter implementation: queries from DeviceInfoCache
259+
*/
260+
inline DeviceInfoProvider ProductionDeviceInfoProvider::get() const {
261+
DeviceInfoCache &cache = DeviceInfoCache::instance();
262+
return DeviceInfoProvider{
263+
&cache.prop(),
264+
cache.coresPerSM(),
265+
cache.clockGHz(),
266+
};
267+
}
268+
269+
/**
270+
* Convenience function: get production device info
271+
*/
272+
inline DeviceInfoProvider getProductionDeviceInfo() {
273+
return ProductionDeviceInfoProvider{}.get();
274+
}

0 commit comments

Comments
 (0)