aboutsummaryrefslogtreecommitdiffstats
path: root/libsmctrl.c
diff options
context:
space:
mode:
authorJoshua Bakita <jbakita@cs.unc.edu>2025-06-16 19:29:07 -0400
committerJoshua Bakita <jbakita@cs.unc.edu>2025-06-17 14:01:49 -0400
commit89177fce34edb5ad0059a41548888d05588cc1c5 (patch)
tree096dc302bb5e17e3987c45a59ef02c69ec73e9ed /libsmctrl.c
parent03ae77e35d35b2a82f5387d1903cfa954b696edd (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.c212
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.
51static 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}; 56static 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)
58static uint64_t *g_supreme_sm_mask = NULL; 69uint128_t *g_supreme_sm_mask = NULL;
59// Global mask (applies across all threads) 70// Global mask (applies across all threads)
60static uint64_t g_sm_mask = 0; 71uint64_t g_sm_mask = 0;
61// Next mask (applies per-thread) 72// Next mask (applies per-thread)
62static __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
64static bool sm_control_setup_called = false; 75bool 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).
80static void (*shared_set_global_mask)(uint64_t) = NULL;
81static 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
165void libsmctrl_set_global_mask(uint64_t mask) { 189void 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
171void libsmctrl_set_next_mask(uint64_t mask) { 202void 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__
251int detect_parker_soc() { 289static 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
278void libsmctrl_set_stream_mask(void* stream, uint64_t mask) { 316void 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`
423static int read_int_procfile(char* filename, uint64_t* out) { 462static 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.
638bool 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