4.9. 错误日志管理
错误日志管理允许用通俗英语报告 CUDA API 错误,与仅使用 API 返回值相比,能够提供有关错误原因的更多信息。
4.9.1. 背景
传统上,CUDA API 调用失败的唯一指示是非零返回码。许多 CUDA 错误码只描述一类故障,而不指出是哪个参数或哪项要求导致了故障。错误日志管理在这些返回码之外补充了描述性消息,旨在帮助开发者诊断 API 使用错误。
4.9.2. 启用
将环境变量CUDA_LOG_FILE设置为stdout、stderr或可写文件的路径。CUDA 会将生成的每条日志消息写入所选目标。
CUDA 还会将最近的消息存储在内部缓冲区中。即使在应用程序启动前未设置CUDA_LOG_FILE,应用程序也可以使用错误日志管理 API 注册回调或转储此缓冲区。无错误的执行可能不会生成任何消息。
4.9.3. 输出
写入CUDA_LOG_FILE或由转储 API 返回的消息使用以下格式:
[Time][TID][Source][Severity] Message
例如,调用cuLogsDumpToMemory时将缓冲区参数设为空指针,可能会生成类似以下内容的消息:
[22:21:32.099][25642][CUDA][E] buffer cannot be NULL
[22:21:32.099][25642][CUDA][E] Returning 1 (CUDA_ERROR_INVALID_VALUE) from cuLogsDumpToMemory
如果不使用错误日志管理,该调用会返回CUDA_ERROR_INVALID_VALUE。调用cuGetErrorString会提供通用描述invalid argument,但不会指出哪个参数无效。
4.9.4. API 说明
CUDA Driver API 和 Runtime API 均提供错误日志管理功能。Runtime 接口可处理 Runtime API 和 Driver API 生成的消息;这两个接口使用相同的底层日志缓冲区。
4.9.4.1. 回调注册
Driver API 的回调类型和注册函数如下:
typedef void (CUDA_CB *CUlogsCallback)(
void *data, CUlogLevel logLevel, char *message, size_t length);
CUresult CUDAAPI cuLogsRegisterCallback(
CUlogsCallback callbackFunc,
void *userData,
CUlogsCallbackHandle *callback_out);
CUresult CUDAAPI cuLogsUnregisterCallback(
CUlogsCallbackHandle callback);
对应的 Runtime API 回调类型和函数如下:
typedef void (CUDART_CB *cudaLogsCallback_t)(
void *data, cudaLogLevel logLevel, char *message, size_t length);
cudaError_t CUDARTAPI cudaLogsRegisterCallback(
cudaLogsCallback_t callbackFunc,
void *userData,
cudaLogsCallbackHandle *callback_out);
cudaError_t CUDARTAPI cudaLogsUnregisterCallback(
cudaLogsCallbackHandle callback);
Driver 回调将错误报告为CU_LOG_LEVEL_ERROR,将警告报告为CU_LOG_LEVEL_WARNING。对应的 Runtime 值为cudaLogLevelError和cudaLogLevelWarning。格式化输出分别将这些级别表示为E和W。
CUDA 将userData原样传递给回调。callback_out参数是可选的,但如果应用程序打算注销该回调,就必须保留返回的句柄。
回调接收消息文本及其明确的字节长度。消息不包含添加到格式化文件输出和转储输出中的时间戳、线程 ID、来源或严重性前缀。除非 API 参考明确保证该行为,否则应用程序不应依赖特定的回调线程或调用时机。
4.9.4.2. 迭代器与日志转储
日志迭代器标识内部缓冲区中的一个位置。调用cuLogsCurrent或cudaLogsCurrent会将迭代器设置为缓冲区的当前末尾,因此,之后使用该迭代器进行转储时,只会返回随后生成的消息。
Driver API 函数如下:
CUresult CUDAAPI cuLogsCurrent(
CUlogIterator *iterator_out, unsigned int flags);
CUresult CUDAAPI cuLogsDumpToFile(
CUlogIterator *iterator,
const char *pathToFile,
unsigned int flags);
CUresult CUDAAPI cuLogsDumpToMemory(
CUlogIterator *iterator,
char *buffer,
size_t *size,
unsigned int flags);
对应的 Runtime API 函数如下:
cudaError_t CUDARTAPI cudaLogsCurrent(
cudaLogIterator *iterator_out, unsigned int flags);
cudaError_t CUDARTAPI cudaLogsDumpToFile(
cudaLogIterator *iterator,
const char *pathToFile,
unsigned int flags);
cudaError_t CUDARTAPI cudaLogsDumpToMemory(
cudaLogIterator *iterator,
char *buffer,
size_t *size,
unsigned int flags);
所有获取当前位置的函数和转储函数中的flags参数均保留供将来使用,且必须为零。
以下 Runtime API 示例标记日志的当前末尾,并在之后仅转储该位置之后生成的消息:
#include <cstdio>
#include <cuda_runtime_api.h>
cudaError_t printNewCudaLogs()
{
cudaLogIterator iterator;
cudaError_t status = cudaLogsCurrent(&iterator, 0);
if (status != cudaSuccess) {
return status;
}
int deviceCount;
status = cudaGetDeviceCount(&deviceCount);
cudaError_t deviceCountStatus = status;
char buffer[25600];
size_t size = sizeof(buffer);
status = cudaLogsDumpToMemory(&iterator, buffer, &size, 0);
if (status != cudaSuccess) {
return status;
}
if (size != 0) {
std::fwrite(buffer, 1, size, stderr);
}
return deviceCountStatus;
}
在两者之间执行的 CUDA 调用仍然需要正常处理返回码。此示例演示了迭代器如何选择一段日志消息;它不要求故意执行无效的 CUDA 调用。该固定大小的数组使用文档规定的最大转储容量,即 25,600 字节。
4.9.4.3. 转储行为
如果iterator为空,转储会请求内部缓冲区当前保留的所有条目。如果提供了迭代器,转储就从该位置开始。成功转储后,CUDA 会将所提供的迭代器推进到日志的当前末尾。
内部缓冲区最多保留 100 个条目。如果请求的消息已被覆盖而不再保留,CUDA 会添加一条声明,指出输出已被截断,并返回仍然可用的条目。
cuLogsDumpToMemory和cudaLogsDumpToMemory还有以下要求:
size最初指定buffer的容量。CUDA 会以空字符终止该缓冲区。各条目之间以换行符而非空字符分隔。
25,600 字节的输出缓冲区足以容纳所有保留的日志数据。
成功时,
size包含已写入的字节数,不包括终止空字符。如果没有消息可供转储,函数会成功并将
size设置为零。如果缓冲区只能容纳部分而非全部请求的消息,CUDA 会在输出前添加一条截断声明,省略无法容纳的最旧消息,并在输出中保留最新的消息。
如果缓冲区无法容纳任何消息,函数会返回
CUDA_ERROR_INVALID_VALUE或cudaErrorInvalidValue,并将size设置为零。
4.9.5. 限制与已知问题
达到 100 个条目的上限时,CUDA 会替换最旧的条目。
目前并非所有 CUDA API 都会生成描述性消息。覆盖范围仍在持续扩大。
CUDA 直到生成消息时才会验证
CUDA_LOG_FILE目标。日志消息未进行本地化;CUDA 以美式英语提供这些消息。