aboutsummaryrefslogtreecommitdiffstats
path: root/nvtaskset.c
diff options
context:
space:
mode:
Diffstat (limited to 'nvtaskset.c')
-rw-r--r--nvtaskset.c210
1 files changed, 210 insertions, 0 deletions
diff --git a/nvtaskset.c b/nvtaskset.c
new file mode 100644
index 0000000..b8873ab
--- /dev/null
+++ b/nvtaskset.c
@@ -0,0 +1,210 @@
1// Copyright 2025 Joshua Bakita
2// taskset-like utility for the GPU
3#define _GNU_SOURCE // For program_invocation_name
4#include <argp.h>
5#include <errno.h>
6#include <error.h>
7#include <stdint.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <sys/ipc.h>
12#include <sys/shm.h>
13#include <sys/types.h>
14#include <unistd.h>
15
16#include <cuda.h> // To help with getting GPC info
17
18#include "libsmctrl.h"
19
20const char* maintainer = "<jbakita@cs.unc.edu>";
21const char* version = "nvtaskset 2025.03";
22const char* desc = "taskset-like utility for NVIDIA GPUs.";
23
24const struct argp_option opts[] = {
25 {0}
26};
27
28unsigned __int128 strtou128(const char *nptr, char **endptr, int base) {
29 unsigned __int128 result = 0;
30 if (base != 16)
31 error(1, EINVAL, "Internal error");
32 // Skip a "0x" prefix. Safe due to early evaluation
33 if (*nptr == '0' && (*(nptr + 1) == 'x' || *(nptr + 1) == 'X'))
34 nptr += 2;
35 // Until hitting an invalid character
36 while (1) {
37 if (*nptr >= 'a' && *nptr <= 'f')
38 result = result << 4 | (*nptr - 'a' + 10);
39 else if (*nptr >= 'A' && *nptr <= 'F')
40 result = result << 4 | (*nptr - 'A' + 10);
41 else if (*nptr >= '0' && *nptr <= '9')
42 result = result << 4 | (*nptr - '0');
43 else
44 break;
45 nptr++;
46 }
47 if (endptr)
48 *endptr = (char*)nptr;
49 return result;
50}
51
52void libsmctrl_get_gpc_info_ext_easy(uint32_t* num_gpcs, uint128_t** masks, int gpu_id) {
53 int res;
54 CUcontext ctx;
55 // XXX: Copied from libsmctrl_test_gpc_info
56 // Tell CUDA to use PCI device id ordering (to match nvdebug)
57 putenv((char*)"CUDA_DEVICE_ORDER=PCI_BUS_ID");
58 // A CUDA context is required before reading the topology information
59 if ((res = cuInit(0))) {
60 const char* name;
61 cuGetErrorName(res, &name);
62 fprintf(stderr, "%s: Unable to initialize CUDA, error %s\n", program_invocation_name, name);
63 exit(1);
64 }
65 if ((res = cuCtxCreate(&ctx, 0, 0))) {
66 const char* name;
67 cuGetErrorName(res, &name);
68 fprintf(stderr, "%s: Unable to create a CUDA context, error %s\n", program_invocation_name, name);
69 exit(1);
70 }
71 // Pull topology information from libsmctrl
72 if ((res = libsmctrl_get_gpc_info_ext(num_gpcs, masks, gpu_id)) != 0) {
73 error(0, res, "libsmctrl_get_gpc_info() failed");
74 if (res == ENOENT)
75 fprintf(stderr, "%s: Is the nvdebug kernel module loaded?\n", program_invocation_name);
76 if (res == EIO)
77 fprintf(stderr, "%s: Is the GPU powered on, i.e., is there an active context?\n", program_invocation_name);
78 exit(1);
79 }
80 // Not copied
81 unsetenv("CUDA_DEVICE_ORDER");
82}
83
84int main(int argc, char **argv) {
85 if (argc < 3) {
86 fprintf(stderr, "Usage: %s -p <hex mask> <pid>\n", argv[0]);
87 fprintf(stderr, " %s <hex mask> <command> <argument...>\n", argv[0]);
88 fprintf(stderr, " %s --gpc-list <gpc list> <command> <argument...>\n", argv[0]);
89 fprintf(stderr, " <hex mask> has a bit set for each TPC to be enabled\n");
90 return 1;
91 }
92 // TODO: Use a proper argument parser
93 if (strcmp("-p", argv[1]) == 0) { // Setting mask on running task
94 char *end;
95 pid_t target_pid = strtoul(argv[2], &end, 10);
96 // strtoul stores a pointer to the first invalid character in `end`
97 if (*end != '\0') {
98 fprintf(stderr, "Invalid character \"%c\" in PID argument.\n", *end);
99 return 1;
100 }
101 unsigned __int128 mask = strtou128(argv[3], &end, 16);
102 if (*end != '\0') {
103 fprintf(stderr, "Invalid character \"%c\" in mask argument.\n", *end);
104 return 1;
105 }
106 // The shared memory lookup key is the lower 16-bits of the PID | "sm"
107 key_t shm_key = target_pid << 16 | (int)'s' << 8 | (int) 'm';
108 // Get a handle to the 128-bit shared memory region
109 int shmid = shmget(shm_key, 16, 0);
110 if (shmid == -1)
111 error(1, errno, "Unable to find control region for PID %d", target_pid);
112 // Open the shared memory region
113 unsigned __int128 *supreme_mask = shmat(shmid, NULL, 0);
114 if (supreme_mask == (void*)-1)
115 error(1, errno, "Unable to open control region for PID %d", target_pid);
116 // Write the requested mask into the shared memory region
117 *supreme_mask = mask;
118 } else { // Starting a new task with a mask
119 // TODO: Check other locations for nvidia-cuda-mps-control if its not on the path
120 // TODO: Use dup2() to redirect MPS startup messages
121 int ret = system("echo -n | nvidia-cuda-mps-control");
122 if (ret == -1)
123 error(1, errno, "Unable to run subshell to check MPS status");
124 if (ret != 0) { // Control deamon not yet started
125 fprintf(stderr, "nvtaskset: MPS control deamon does not appear to be running. Automatically starting...\n");
126 ret = system("nvidia-cuda-mps-control -d");
127 if (ret == -1)
128 error(1, errno, "Unable to run subshell to start MPS");
129 if (ret == 1) {
130 fprintf(stderr, "nvtaskset: Error starting MPS control deamon. Terminating...\n");
131 return 1;
132 }
133 fprintf(stderr, "nvtaskset: Done. Use \"echo quit | nvidia-cuda-mps-control\" to terminate it later as desired.\n");
134 }
135 // Tell loader to initialize libsmctrl.so first
136 // TODO: Append, rather than overwrite LD_PRELOAD
137 setenv("LD_PRELOAD", "./libsmctrl.so", 1);
138 // Explictly set the number of channels, otherwise CUDA will only use two
139 // (see paper for why that causes problems)
140 setenv("CUDA_DEVICE_MAX_CONNECTIONS", "8", 1);
141 // Check if a mask, or a list of GPCs is being provided
142 if (strcmp(argv[1], "--gpc-list") == 0) {
143 // TODO: Support the full syntax that taskset supports
144 // We just support X,Y,Z for now
145 uint32_t num_gpcs = 0;
146 uint128_t* masks = NULL;
147 // TODO: Allow specifying GPU ID, rather than assuming 0!
148 libsmctrl_get_gpc_info_ext_easy(&num_gpcs, &masks, 0);
149 uint128_t mask = 0;
150 int range_start_gpc = -1;
151 char* start = argv[2];
152 int len = strlen(argv[2]);
153 // TODO: Handle invalid input cleanly.
154 // Convert comma-seperated GPC list into a mask
155 for (int i = 0; i < len + 1; i++) {
156 if (argv[2][i] == ',' || argv[2][i] == '\0') {
157 argv[2][i] = '\0';
158 int gpc = atoi(start);
159 if (gpc > num_gpcs - 1) {
160 fprintf(stderr, "Invalid GPC ID '%s'!\n", start);
161 return 1;
162 }
163 // Handle ranges
164 if (range_start_gpc != -1) {
165 if (range_start_gpc >= gpc) {
166 fprintf(stderr, "Invalid GPC range!\n");
167 return 1;
168 }
169 while (range_start_gpc <= gpc) {
170 //printf("gpc %i\n", range_start_gpc);
171 mask |= masks[range_start_gpc];
172 range_start_gpc++;
173 }
174 range_start_gpc = -1;
175 } else {
176 //printf("gpc %i\n", gpc);
177 mask |= masks[gpc];
178 }
179 start = argv[2] + i + 1;
180 }
181 // Range start
182 if (argv[2][i] == '-') {
183 argv[2][i] = '\0';
184 range_start_gpc = atoi(start);
185 start = argv[2] + i + 1;
186 }
187 }
188 // Convert to string, prefix with ~, and set env var
189 char mask_str[32+3+1]; // 32 hexits, "~0x", and '\0'
190 snprintf(mask_str, 36, "~0x%lx%016lx", (uint64_t)(mask >> 64), (uint64_t)mask);
191 //printf("nvtaskset: Using mask string %s\n", mask_str);
192 setenv("LIBSMCTRL_MASK", mask_str, 1);
193 // Start task
194 execvp(argv[3], argv+3);
195 error(1, errno, "Unable to launch task '%s'", argv[3]);
196 } else {
197 // Tell libsmctrl what mask to use
198 char* mask = malloc(strlen(argv[1]) + 2);
199 mask[0] = '~'; // Make an enable mask
200 strcpy(mask+1, argv[1]);
201 setenv("LIBSMCTRL_MASK", mask, 1);
202 free(mask); // setenv() made a copy
203 // Start task
204 execvp(argv[2], argv+2);
205 error(1, errno, "Unable to launch task '%s'", argv[2]);
206 }
207 }
208 fprintf(stderr, "Invalid arguments\n");
209 return 1;
210}