cuda_debug

cuda 调试方法

程序串行

https://stackoverflow.com/questions/55020514/cudagetlasterror-which-kernel-execution-raised-it 环境变量CUDA_LAUNCH_BLOCKING 设为1 env CUDA_LAUNCH_BLOCKING=1 ./app

cuda-gdb 调试

https://developer.download.nvidia.com/GTC/PDF/1062_Satoor.pdf

发生error 时停止

https://stackoverflow.com/questions/35512837/how-to-find-where-does-program-crashed-when-cuda-api-error-detected-cudamemcpy

1
2
3
4
(cuda-gdb) set cuda api_failures stop
(cuda-gdb) set cuda api_failures stop_all
(cuda-gdb) set cuda api_failures ignore
(cuda-gdb) set cuda api_failures ignore_all

查看context events

1
(cuda-gdb) info cuda contexts

使用cuda-memcheck

1
(cuda-gdb) set cuda memcheck on

在每个 kernel 处停止

1
(cuda-gdb) set cuda break_on_launch application

问题记录

Cuda API error detected: cudaEventRecord returned (0x190)

错误码 400: invalid source handle 或者提示 Error Code 1: Cudnn (CUDNN_STATUS_MAPPING_ERROR) 或者提示 NPP、

cudaStream_t 是基于当前CUcontext 创建的,如果没有显式创建 context,则会使用默认的。当使用 stream 时的 context 与创建时的 context不一致,就会报该错误。

关于context 的介绍:https://developer.nvidia.com/zh-cn/blog/cuda-low-level-driver-api-cn/

  1. context 是绑定到线程的,在当前线程创建context 时不会影响其他线程
  2. 调用 cuCtxCreate 接口创建 context 时,其在当前线程下上下文就已经切换成了新创建的 context,如果需要使用之前的 context,可以把当前的 context pop 出去

几个 context 的接口示例如下:

1
2
3
4
5
CUdevice cuDevice = 0;
CUcontext cu_context_ = NULL;
int cuCtxCreateRet = cuCtxCreate(&cu_context_, 0, cuDevice);
// cuCtxPushCurrent(cu_context_);
cuCtxPopCurrent(NULL);

  1. https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html

一个gpu device 在同一个时刻只能有一个 context 在运行,因此多个context 之间无法并行,多个 context 之间的切换也有性能损耗,所以官方不推荐使用 cuCtxCreate 创建新的 context,而是通过 cuDevicePrimaryCtxRetain 获取默认的 context,这样就可以只是用默认的 context,

  1. 既然有默认的 context,cuda 底层也会自动管理 context,为什么还需要手动管理context 呢? https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__CTX.html

CUDA runtime API 能够自动管理 context,但是 CUDA driver API 需要手动管理。例如 NvCodec SDK 中的 video codec 是调用的 driver api,因此 video codec 需要手动维护 context。

throwing an instacen of NVECNEeception, returned error 21 at NvEncoder

消费级显卡默认是不支持多个硬解码器同时运行的(硬件限制),反映到软件程序中就是不支持多路解码并行,可能不同的显卡线程限制数量不一样,4090 默认可以同时跑两路;可以通过nvidia-patch 去规避这个问题

https://github.com/keylase/nvidia-patch

NVENC patch removes restriction on maximum number of simultaneous NVENC video encoding sessions imposed by Nvidia to consumer-grade GPUs.

H265编码的码流如何分段保存?除了第一段外,解码时都提示PPS is out of range错误 ?

首先要清楚h265编码后的 packet 的格式,每个packet应该是封装了一层的 NALU(Network Abstraction Layer Unit), H265 中的NALU的类型有很多种,ffmpeg 中enum NALUnitType 的定义如下。 https://www.ffmpeg.org/doxygen/2.2/libavcodec_2hevc_8h_source.html

在分段过程中需要关注的有两种NALU,

一是 NAL_VPS = 32,其只在编码的开头出现,也就是可以用来判断编码的开始,在分段过程中,VPS只出现在第一段。

二是 NAL_IDR_W_RADL = 19,IDR 表示 Instantaneous Decoding Refresh,是用在解码的过程中立即刷新缓存,即解码该帧时不会再利用之前帧的信息,全新的解码从该帧开始,这也就意味着在分段的过程中以该IDR 帧为开头才可以进行解码;但是解码时还需要 PPS 和 SPS 信息,在编码IDR 帧时是否携带 PPS 和 SPS 信息是可以配置的,不同的编码器实现中该配置不一样,比如同样是nvidia 的 multimedia 接口中,该配置应该就是默认打开的。但是在 nvcodec sdk 中该配置默认是关闭状态的,nvEncodeAPI.h 中定义了编码参数 NV_ENC_INITIALIZE_PARAMS, 用户使用时需要定义 NV_ENC_INITIALIZE_PARAMS initializeParams传递给编码器,然后需要设置 initializeParams.encodeConfig->encodeCodecConfig.hevcConfig.repeatSPSPPS = 1,从而IDR 才会携带 PPS 和 SPS 信息。

h265帧格式介绍: https://www.cnblogs.com/lcgbk/p/15005059.html

代码中如何判断 VPS 和 IDR?直接对编码后的 packet 进行判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
bool IsIFrame(const uint8_t *p) {
uint8_t type = 0;
if (p[0] == 0x00 && p[1] == 0x00) {
if (p[2] == 0x01) {
type = (p[3] & 0x7e) >> 1;
} else if (p[2] == 0x00 && p[3] == 0x01) {
type = (p[4] & 0x7e) >> 1;
} else {
type = 0xFF;
}

if (0x20 == type || 0x13 == type) {
// 0x20: 32 VPS; 0x13: 19 IDR (VPS:
// one sequence first nalu, IDR?)
std::cout << "assert IFrame, packet[4]=" << (uint32_t)type;
return true;
}
}
std::cout << "pub_id: " << pub_id_ << "IsIFrame type: " << (uint32_t)type;
return false;
}


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!