一个cuda profile的原理例子
概要
下面给出一个可编译的最小示例,演示如何用 启动器 + 注入代理库 + collector 的方式在不改动目标可执行的情况下采集 CUPTI activity buffer 并把数据发回采集进程。示例尽量精简以便能在真实环境中编译运行并验证数据流通路。示例仅用于学习与验证,生产环境需做更多健壮性、权限与性能处理。
提供的文件
-
launcher.sh 启动脚本,设置环境并启动 collector,然后
exec目标程序(通过LD_PRELOAD注入代理库)。 -
collector.c 简单的 Unix domain socket 服务端,接收代理发来的 activity buffer 并把长度与前几个字节打印出来(示例用途)。
-
libcupti_agent.c 注入代理库(shared object),在库加载时初始化 CUPTI、注册缓冲回调并在
bufferCompleted中把 buffer 发回 collector。
代码 文件 1 launcher.sh
#!/bin/bash
# launcher.sh
# Usage: ./launcher.sh /path/to/your_app [args...]
set -e
OUT_DIR=/tmp/cupts_capture
mkdir -p "$OUT_DIR"
# Socket path for IPC between agent and collector
SOCK="$OUT_DIR/cuptsock.sock"
rm -f "$SOCK"
# Path to the agent shared library (adjust as needed)
AGENT_LIB="$(pwd)/libcupti_agent.so"
if [ ! -f "$AGENT_LIB" ]; then
echo "Agent library not found: $AGENT_LIB"
exit 1
fi
# Start collector in background
./collector "$SOCK" "$OUT_DIR" &
COLLECTOR_PID=$!
# Export socket path for agent to connect
export CUPTI_AGENT_SOCK="$SOCK"
# Inject agent via LD_PRELOAD and exec target program
export LD_PRELOAD="$AGENT_LIB:$LD_PRELOAD"
# Optionally set CUPTI activity kinds via env (agent reads CUPTI_ACTIVITY_KIND if needed)
export CUPTI_ACTIVITY_KIND="KERNEL,MEMCPY"
# Exec the target program so it inherits LD_PRELOAD
exec "$@"
# Note: collector will exit when launcher/target process ends; you can also kill it manually.
代码 文件 2 collector.c
// collector.c
// Simple unix domain socket collector that receives buffers from agent
// Build: gcc collector.c -o collector
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: %s <socket_path> <out_dir>\n", argv[0]);
return 1;
}
const char *sockpath = argv[1];
const char *outdir = argv[2];
int listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (listen_fd < 0) { perror("socket"); return 1; }
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path)-1);
unlink(sockpath);
if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; }
if (listen(listen_fd, 1) < 0) { perror("listen"); return 1; }
printf("Collector listening on %s\n", sockpath);
int conn = accept(listen_fd, NULL, NULL);
if (conn < 0) { perror("accept"); return 1; }
printf("Agent connected\n");
// Receive loop: first 8 bytes length, then data
while (1) {
uint64_t len = 0;
ssize_t r = read(conn, &len, sizeof(len));
if (r == 0) { printf("Agent closed connection\n"); break; }
if (r < 0) { perror("read len"); break; }
if (r != sizeof(len)) { fprintf(stderr, "Partial read len\n"); break; }
len = (uint64_t)len;
if (len == 0) continue;
uint8_t *buf = malloc(len);
uint64_t got = 0;
while (got < len) {
ssize_t n = read(conn, buf + got, len - got);
if (n <= 0) { perror("read data"); goto cleanup; }
got += n;
}
// For demo, print first few bytes and length
printf("Received buffer len=%llu first4=0x%02x%02x%02x%02x\n",
(unsigned long long)len,
buf[0], buf[1], buf[2], buf[3]);
// Optionally write raw buffer to file for offline parsing
static int idx = 0;
char fname[512];
snprintf(fname, sizeof(fname), "%s/activity_%04d.bin", outdir, idx++);
FILE *f = fopen(fname, "wb");
if (f) {
fwrite(buf, 1, len, f);
fclose(f);
}
free(buf);
}
cleanup:
close(conn);
close(listen_fd);
unlink(sockpath);
return 0;
}
代码 文件 3 libcupti_agent.c
// libcupti_agent.c
// Minimal CUPTI agent library that registers buffer callbacks and sends buffers to collector
// Build: gcc -shared -fPIC libcupti_agent.c -o libcupti_agent.so -I${CUDA_HOME}/extras/CUPTI/include -L${CUDA_HOME}/extras/CUPTI/lib64 -lcupti -ldl -lpthread
// Note: This example omits robust error handling for brevity.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include <cupti.h>
static int sockfd = -1;
static pthread_mutex_t sock_lock = PTHREAD_MUTEX_INITIALIZER;
static void open_ipc() {
const char *sockpath = getenv("CUPTI_AGENT_SOCK");
if (!sockpath) sockpath = "/tmp/cuptsock.default";
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("agent socket");
return;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path)-1);
// Try connect; collector should be listening
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("agent connect");
close(sockfd);
sockfd = -1;
}
}
// bufferRequested callback signature
static void bufferRequested(uint8_t **buffer, size_t *size, size_t *maxNumRecords) {
// allocate a reasonably large buffer
*size = 8 * 1024 * 1024; // 8MB
*buffer = (uint8_t*)malloc(*size);
*maxNumRecords = 0;
}
// bufferCompleted callback signature
static void bufferCompleted(CUcontext ctx, uint32_t streamId, uint8_t *buffer, size_t size, size_t validSize) {
(void)ctx; (void)streamId; (void)size;
if (validSize == 0) {
free(buffer);
return;
}
// Send length then data to collector (synchronously for simplicity)
pthread_mutex_lock(&sock_lock);
if (sockfd >= 0) {
uint64_t len = (uint64_t)validSize;
ssize_t w = write(sockfd, &len, sizeof(len));
if (w == sizeof(len)) {
ssize_t sent = 0;
while (sent < (ssize_t)len) {
ssize_t n = write(sockfd, buffer + sent, len - sent);
if (n <= 0) break;
sent += n;
}
}
}
pthread_mutex_unlock(&sock_lock);
free(buffer);
}
__attribute__((constructor))
static void agent_init(void) {
// Open IPC to collector
open_ipc();
// Initialize CUPTI activity callbacks
CUptiResult res;
res = cuptiActivityRegisterCallbacks(bufferRequested, bufferCompleted);
if (res != CUPTI_SUCCESS) {
const char *s; cuptiGetResultString(res, &s);
fprintf(stderr, "cuptiActivityRegisterCallbacks failed: %s\n", s);
return;
}
// Enable a couple of activity kinds
cuptiActivityEnable(CUPTI_ACTIVITY_KIND_KERNEL);
cuptiActivityEnable(CUPTI_ACTIVITY_KIND_MEMCPY);
// Optionally enable other kinds based on env
// e.g., read CUPTI_ACTIVITY_KIND env and enable accordingly (omitted)
fprintf(stderr, "CUPTI agent initialized\n");
}
__attribute__((destructor))
static void agent_fini(void) {
// Flush activity and cleanup
cuptiActivityFlushAll(0);
pthread_mutex_lock(&sock_lock);
if (sockfd >= 0) close(sockfd);
sockfd = -1;
pthread_mutex_unlock(&sock_lock);
fprintf(stderr, "CUPTI agent finalized\n");
}
构建命令
在有 CUDA 的机器上,假设 CUDA_HOME 指向 CUDA 安装目录(例如 /usr/local/cuda):
export CUDA_HOME=/usr/local/cuda
# 编译 collector
gcc collector.c -o collector
# 编译 agent shared lib
gcc -shared -fPIC libcupti_agent.c -o libcupti_agent.so \
-I${CUDA_HOME}/extras/CUPTI/include \
-L${CUDA_HOME}/extras/CUPTI/lib64 -lcupti -ldl -lpthread
# 使 launcher 可执行
chmod +x launcher.sh
运行步骤
-
启动目标程序通过 launcher
bash./launcher.sh /path/to/your_app [args...]-
launcher.sh会启动collector并以LD_PRELOAD注入libcupti_agent.so,然后exec目标程序。
-
-
查看 collector 输出
-
collector 会打印收到的 buffer 长度与前几个字节,并把原始 buffer 写入
/tmp/cupts_capture/activity_*.bin。
-
-
验证
-
在目标程序中执行一些 CUDA 操作(kernel launch、memcpy),collector 应收到 buffer。
-
若没有数据,检查
stderr中 agent 初始化输出与 collector 是否成功建立连接,检查 CUPTI 与驱动版本兼容性。
-
重要注意事项与限制
-
仅作学习示例:示例省略了大量错误处理、重连逻辑、异步上报与高效缓冲管理;生产工具需把
bufferCompleted的发送改为异步队列并避免阻塞应用线程。 -
权限与兼容性:
LD_PRELOAD注入对动态链接程序有效;对静态链接或 setuid 程序无效。attach 场景需不同实现且可能需要 root。CUPTI、CUDA Toolkit 与驱动版本必须兼容。 -
性能影响:同步写 socket 会对应用造成延迟,实际应把数据放入本地环形缓冲并由后台线程批量发送。
-
时间对齐:示例未做 GPU ticks 到主机时间的映射;实际需要用 CUPTI/驱动提供的映射或多点拟合来对齐时间戳。
-
安全:不要在不受信任环境下注入库或对生产服务做长时间采集。
后续建议
-
把
bufferCompleted的发送改为后台线程异步上报并做批量写入以降低对应用的影响。 -
在 collector 端实现对 CUPTI activity record 的解析(或把原始 buffer 交给离线解析工具),并实现 GPU→主机时间映射与 correlation 关联。
-
在受控测试上验证
cuptiActivityGetNumDroppedRecords并根据需要调整 buffer 大小或采集范围。

浙公网安备 33010602011771号