diff options
| author | Joshua Bakita <jbakita@cs.unc.edu> | 2025-06-16 19:29:07 -0400 |
|---|---|---|
| committer | Joshua Bakita <jbakita@cs.unc.edu> | 2025-06-17 14:01:49 -0400 |
| commit | 89177fce34edb5ad0059a41548888d05588cc1c5 (patch) | |
| tree | 096dc302bb5e17e3987c45a59ef02c69ec73e9ed /libsmctrl.c | |
| parent | 03ae77e35d35b2a82f5387d1903cfa954b696edd (diff) | |
Rewrite nvtaskset and implementation of partitioning for unmodified tasks
Rather than requiring libsmctrl.so to be preloaded, we now wrap
libcuda.so.1. All CUDA-using applications will load libcuda.so.1,
ensuring that our wrapper will always be dynamically loaded, no
matter if LD_PRELOAD is enabled, or if a program has been staticly
linked. All that needs to be done is that the location of our
"fake" libcuda.so.1 need to be put within the loader search path.
This can be done by setting LD_LIBRARY_PATH, or by installing
our wrapper into /lib/x86_64-linux-gnu.
The mask can still be set via the LIBSMCTRL_MASK environment
variable, but the easier-to-use nvtaskset tool is now the
recommended way to view or change the supreme TPC mask for any
CUDA-using application. This allows launching a program on the
first two GPCs via a command as simple as:
./nvtaskset -g 0-1 ./a_program a_program_args
(Note that use of the -g option requires the nvdebug kernel module
to first be loaded.)
These changes support the final version of the ECRTS'25 paper.
Note that nvtaskset does not yet fully support multi-GPU systems.
Bugfixes:
- Fix crash that would occur if both libsmctrl.so and libsmctrl.a
were built into an application.
- Correctly use GPU ID when initializing a context in
`libsmctrl_test_gpc_info`.
- Include `nvtaskset` as a prerequisite for
`libsmctrl_test_supreme_mask`.
- Fix malfunction of `libsmctrl_test_gpc_info` if
CUDA_VISIBLE_DEVICES is set.
Other minor changes:
- Adds make target to run all the tests.
- Fixes typos in comments.
- Enables -Wall build option.
- Upgrades supreme mask from 64 to 128 bits.
- Removes `detect_parker_soc()` from the global namespace.
- Adjusts test messages to be more succinct.
- Updates README with overview of how to partition unmodified
applications, more details on the tests, and information on the
new ECRTS'25 paper.
Diffstat (limited to 'libsmctrl.c')
| -rw-r--r-- | libsmctrl.c | 212 |
1 files changed, 173 insertions, 39 deletions
diff --git a/libsmctrl.c b/libsmctrl.c index 6aa471b..79d2b33 100644 --- a/libsmctrl.c +++ b/libsmctrl.c | |||
| @@ -17,22 +17,27 @@ | |||
| 17 | * Please contact the authors if support is needed for a particular feature on | 17 | * Please contact the authors if support is needed for a particular feature on |
| 18 | * an older CUDA version. Support for those is unimplemented, not impossible. | 18 | * an older CUDA version. Support for those is unimplemented, not impossible. |
| 19 | * | 19 | * |
| 20 | * An old implementation of this file effected the global mask on CUDA 10.2 by | 20 | * An old implementation of this file affected the global mask on CUDA 10.2 by |
| 21 | * changing a field in CUDA's global struct that CUDA applies to the QMD/TMD. | 21 | * changing a field in CUDA's global struct that CUDA applies to the QMD/TMD. |
| 22 | * That implementation was extraordinarily complicated, and was replaced in | 22 | * That implementation was extraordinarily complicated, and was replaced in |
| 23 | * 2024 with a more-backward-compatible way of hooking the TMD/QMD. | 23 | * 2024 with a more-backward-compatible way of hooking the TMD/QMD. |
| 24 | * View the old implementation via Git: `git show aa63a02e:libsmctrl.c`. | 24 | * View the old implementation via Git: `git show aa63a02e:libsmctrl.c`. |
| 25 | */ | 25 | */ |
| 26 | #define _GNU_SOURCE // To enable use of memfd_create() | ||
| 26 | #include <cuda.h> | 27 | #include <cuda.h> |
| 27 | 28 | ||
| 28 | #include <errno.h> | 29 | #include <errno.h> |
| 29 | #include <error.h> | 30 | #include <error.h> |
| 31 | #include <dlfcn.h> | ||
| 30 | #include <fcntl.h> | 32 | #include <fcntl.h> |
| 31 | #include <stdbool.h> | 33 | #include <stdbool.h> |
| 32 | #include <stdint.h> | 34 | #include <stdint.h> |
| 33 | #include <stdio.h> | 35 | #include <stdio.h> |
| 34 | #include <sys/ipc.h> | 36 | #include <string.h> |
| 35 | #include <sys/shm.h> | 37 | #include <sys/mman.h> |
| 38 | #include <sys/socket.h> | ||
| 39 | #include <sys/types.h> | ||
| 40 | #include <sys/un.h> | ||
| 36 | #include <unistd.h> | 41 | #include <unistd.h> |
| 37 | 42 | ||
| 38 | #include "libsmctrl.h" | 43 | #include "libsmctrl.h" |
| @@ -48,20 +53,33 @@ | |||
| 48 | // (No testing attempted on pre-CUDA-6.5 versions) | 53 | // (No testing attempted on pre-CUDA-6.5 versions) |
| 49 | // Values for the following three lines can be extracted by tracing CUPTI as | 54 | // Values for the following three lines can be extracted by tracing CUPTI as |
| 50 | // it interects with libcuda.so to set callbacks. | 55 | // it interects with libcuda.so to set callbacks. |
| 51 | static const CUuuid callback_funcs_id = {0x2c, (char)0x8e, 0x0a, (char)0xd8, 0x07, 0x10, (char)0xab, 0x4e, (char)0x90, (char)0xdd, 0x54, 0x71, (char)0x9f, (char)0xe5, (char)0xf7, 0x4b}; | 56 | static const CUuuid callback_funcs_id = {{0x2c, (char)0x8e, 0x0a, (char)0xd8, 0x07, 0x10, (char)0xab, 0x4e, (char)0x90, (char)0xdd, 0x54, 0x71, (char)0x9f, (char)0xe5, (char)0xf7, 0x4b}}; |
| 52 | // These callback descriptors appear to intercept the TMD/QMD late enough that | 57 | // These callback descriptors appear to intercept the TMD/QMD late enough that |
| 53 | // CUDA has already applied the per-stream mask from its internal data | 58 | // CUDA has already applied the per-stream mask from its internal data |
| 54 | // structures, allowing us to override it with the next mask. | 59 | // structures, allowing us to override it with the next mask. |
| 55 | #define QMD_DOMAIN 0xb | 60 | #define QMD_DOMAIN 0xb |
| 56 | #define QMD_PRE_UPLOAD 0x1 | 61 | #define QMD_PRE_UPLOAD 0x1 |
| 62 | /** | ||
| 63 | * These globals must be non-static (i.e., have global linkage) to ensure that | ||
| 64 | * if multiple copies of the library are loaded (e.g., dynamically linked to | ||
| 65 | * both this program and a dependency), secondary copies do not attempt to | ||
| 66 | * repeat initialization or make changes to unused copies of mask values. | ||
| 67 | */ | ||
| 57 | // Supreme mask (cannot be overridden) | 68 | // Supreme mask (cannot be overridden) |
| 58 | static uint64_t *g_supreme_sm_mask = NULL; | 69 | uint128_t *g_supreme_sm_mask = NULL; |
| 59 | // Global mask (applies across all threads) | 70 | // Global mask (applies across all threads) |
| 60 | static uint64_t g_sm_mask = 0; | 71 | uint64_t g_sm_mask = 0; |
| 61 | // Next mask (applies per-thread) | 72 | // Next mask (applies per-thread) |
| 62 | static __thread uint64_t g_next_sm_mask = 0; | 73 | __thread uint64_t g_next_sm_mask = 0; |
| 63 | // Flag value to indicate if setup has been completed | 74 | // Flag value to indicate if setup has been completed |
| 64 | static bool sm_control_setup_called = false; | 75 | bool sm_control_setup_called = false; |
| 76 | |||
| 77 | #ifdef LIBSMCTRL_STATIC | ||
| 78 | // Special handling for if built as a static library, and the libcuda.so.1 | ||
| 79 | // libsmctrl wrapper is in use (see comment on setup() constructor for detail). | ||
| 80 | static void (*shared_set_global_mask)(uint64_t) = NULL; | ||
| 81 | static void (*shared_set_next_mask)(uint64_t) = NULL; | ||
| 82 | #endif | ||
| 65 | 83 | ||
| 66 | // v1 has been removed---it intercepted the TMD/QMD too early, making it | 84 | // v1 has been removed---it intercepted the TMD/QMD too early, making it |
| 67 | // impossible to override the CUDA-injected stream mask with the next mask. | 85 | // impossible to override the CUDA-injected stream mask with the next mask. |
| @@ -78,7 +96,7 @@ static void control_callback_v2(void *ukwn, int domain, int cbid, const void *in | |||
| 78 | if (!tmd) | 96 | if (!tmd) |
| 79 | abort(1, 0, "TMD allocation appears NULL; likely forward-compatibilty issue.\n"); | 97 | abort(1, 0, "TMD allocation appears NULL; likely forward-compatibilty issue.\n"); |
| 80 | 98 | ||
| 81 | uint32_t *lower_ptr, *upper_ptr; | 99 | uint32_t *lower_ptr, *upper_ptr, *ext_lower_ptr, *ext_upper_ptr; |
| 82 | 100 | ||
| 83 | // The location of the TMD version field seems consistent across versions | 101 | // The location of the TMD version field seems consistent across versions |
| 84 | uint8_t tmd_ver = *(uint8_t*)(tmd + 72); | 102 | uint8_t tmd_ver = *(uint8_t*)(tmd + 72); |
| @@ -87,10 +105,12 @@ static void control_callback_v2(void *ukwn, int domain, int cbid, const void *in | |||
| 87 | // TMD V04_00 is used starting with Hopper to support masking >64 TPCs | 105 | // TMD V04_00 is used starting with Hopper to support masking >64 TPCs |
| 88 | lower_ptr = tmd + 304; | 106 | lower_ptr = tmd + 304; |
| 89 | upper_ptr = tmd + 308; | 107 | upper_ptr = tmd + 308; |
| 108 | ext_lower_ptr = tmd + 312; | ||
| 109 | ext_upper_ptr = tmd + 316; | ||
| 90 | // XXX: Disable upper 64 TPCs until we have ...next_mask_ext and | 110 | // XXX: Disable upper 64 TPCs until we have ...next_mask_ext and |
| 91 | // ...global_mask_ext | 111 | // ...global_mask_ext |
| 92 | *(uint32_t*)(tmd + 312) = -1; | 112 | *ext_lower_ptr = -1; |
| 93 | *(uint32_t*)(tmd + 316) = -1; | 113 | *ext_upper_ptr = -1; |
| 94 | // An enable bit is also required | 114 | // An enable bit is also required |
| 95 | *(uint32_t*)tmd |= 0x80000000; | 115 | *(uint32_t*)tmd |= 0x80000000; |
| 96 | } else if (tmd_ver >= 0x16) { | 116 | } else if (tmd_ver >= 0x16) { |
| @@ -119,6 +139,10 @@ static void control_callback_v2(void *ukwn, int domain, int cbid, const void *in | |||
| 119 | if (g_supreme_sm_mask) { | 139 | if (g_supreme_sm_mask) { |
| 120 | *lower_ptr |= (uint32_t)*g_supreme_sm_mask; | 140 | *lower_ptr |= (uint32_t)*g_supreme_sm_mask; |
| 121 | *upper_ptr |= (uint32_t)(*g_supreme_sm_mask >> 32); | 141 | *upper_ptr |= (uint32_t)(*g_supreme_sm_mask >> 32); |
| 142 | if (tmd_ver >= 0x40) { | ||
| 143 | *ext_lower_ptr |= (uint32_t)(*g_supreme_sm_mask >> 64); | ||
| 144 | *ext_upper_ptr |= (uint32_t)(*g_supreme_sm_mask >> 96); | ||
| 145 | } | ||
| 122 | } | 146 | } |
| 123 | 147 | ||
| 124 | //fprintf(stderr, "Final SM Mask (lower): %x\n", *lower_ptr); | 148 | //fprintf(stderr, "Final SM Mask (lower): %x\n", *lower_ptr); |
| @@ -163,12 +187,26 @@ static void setup_sm_control_callback() { | |||
| 163 | 187 | ||
| 164 | // Set default mask for all launches | 188 | // Set default mask for all launches |
| 165 | void libsmctrl_set_global_mask(uint64_t mask) { | 189 | void libsmctrl_set_global_mask(uint64_t mask) { |
| 190 | #ifdef LIBSMCTRL_STATIC | ||
| 191 | // Special handling for if built as a static library, and the libcuda.so.1 | ||
| 192 | // libsmctrl wrapper is in use (see comment on setup() constructor for | ||
| 193 | // detail). | ||
| 194 | if (shared_set_global_mask) | ||
| 195 | return (*shared_set_global_mask)(mask); | ||
| 196 | #endif | ||
| 166 | setup_sm_control_callback(); | 197 | setup_sm_control_callback(); |
| 167 | g_sm_mask = mask; | 198 | g_sm_mask = mask; |
| 168 | } | 199 | } |
| 169 | 200 | ||
| 170 | // Set mask for next launch from this thread | 201 | // Set mask for next launch from this thread |
| 171 | void libsmctrl_set_next_mask(uint64_t mask) { | 202 | void libsmctrl_set_next_mask(uint64_t mask) { |
| 203 | #ifdef LIBSMCTRL_STATIC | ||
| 204 | // Special handling for if built as a static library, and the libcuda.so.1 | ||
| 205 | // libsmctrl wrapper is in use (see comment on setup() constructor for | ||
| 206 | // detail). | ||
| 207 | if (shared_set_next_mask) | ||
| 208 | return (*shared_set_next_mask)(mask); | ||
| 209 | #endif | ||
| 172 | setup_sm_control_callback(); | 210 | setup_sm_control_callback(); |
| 173 | g_next_sm_mask = mask; | 211 | g_next_sm_mask = mask; |
| 174 | } | 212 | } |
| @@ -248,7 +286,7 @@ struct stream_sm_mask_v2 { | |||
| 248 | // (CUDA 9.0 behaves slightly different on this platform.) | 286 | // (CUDA 9.0 behaves slightly different on this platform.) |
| 249 | // @return 1 if detected, 0 if not, -cuda_err on error | 287 | // @return 1 if detected, 0 if not, -cuda_err on error |
| 250 | #if __aarch64__ | 288 | #if __aarch64__ |
| 251 | int detect_parker_soc() { | 289 | static int detect_parker_soc() { |
| 252 | int cap_major, cap_minor, err, dev_count; | 290 | int cap_major, cap_minor, err, dev_count; |
| 253 | if (err = cuDeviceGetCount(&dev_count)) | 291 | if (err = cuDeviceGetCount(&dev_count)) |
| 254 | return -err; | 292 | return -err; |
| @@ -272,7 +310,7 @@ int detect_parker_soc() { | |||
| 272 | } | 310 | } |
| 273 | #endif // __aarch64__ | 311 | #endif // __aarch64__ |
| 274 | 312 | ||
| 275 | // Should work for CUDA 8.0 through 12.6 | 313 | // Should work for CUDA 8.0 through 12.8 |
| 276 | // A cudaStream_t is a CUstream*. We use void* to avoid a cuda.h dependency in | 314 | // A cudaStream_t is a CUstream*. We use void* to avoid a cuda.h dependency in |
| 277 | // our header | 315 | // our header |
| 278 | void libsmctrl_set_stream_mask(void* stream, uint64_t mask) { | 316 | void libsmctrl_set_stream_mask(void* stream, uint64_t mask) { |
| @@ -417,7 +455,8 @@ void libsmctrl_set_stream_mask_ext(void* stream, uint128_t mask) { | |||
| 417 | } | 455 | } |
| 418 | } | 456 | } |
| 419 | 457 | ||
| 420 | /* INFORMATIONAL FUNCTIONS */ | 458 | |
| 459 | /*** TPC and GPU Informational Functions ***/ | ||
| 421 | 460 | ||
| 422 | // Read an integer from a file in `/proc` | 461 | // Read an integer from a file in `/proc` |
| 423 | static int read_int_procfile(char* filename, uint64_t* out) { | 462 | static int read_int_procfile(char* filename, uint64_t* out) { |
| @@ -590,32 +629,98 @@ abort_cuda: | |||
| 590 | return EIO; | 629 | return EIO; |
| 591 | } | 630 | } |
| 592 | 631 | ||
| 632 | |||
| 633 | /*** Private functions for nvtaskset and building as a libcuda.so.1 wrapper ***/ | ||
| 634 | |||
| 635 | // Check if NVIDIA MPS is running, following the process that `strace` shows | ||
| 636 | // `nvidia-cuda-mps-control` to use. MPS is a prerequisite to co-running | ||
| 637 | // multiple GPU-using tasks without timeslicing. | ||
| 638 | bool libsmctrl_is_mps_running() { | ||
| 639 | char *mps_pipe_dir; | ||
| 640 | int mps_ctrl; | ||
| 641 | struct sockaddr_un mps_ctrl_addr; | ||
| 642 | mps_ctrl_addr.sun_family = AF_UNIX; | ||
| 643 | const int yes = 1; | ||
| 644 | |||
| 645 | if (!(mps_pipe_dir = getenv("CUDA_MPS_PIPE_DIRECTORY"))) | ||
| 646 | mps_pipe_dir = "/tmp/nvidia-mps"; | ||
| 647 | // Pipe names | ||
