tensorRT-HPC经验

前言

这里总结一些经常使用到的HPC的经验。便于后期对照查询使用

TensorRT辅助流

参考 https://developer.nvidia.com/docs/drive/drive-os/6.0.8/public/drive-os-tensorrt/pdf/NVIDIA-TensorRT-8.6.11-API-Reference-for-DRIVE-OS.pdf

如果希望stream是可控的,最好将辅助流设置为0.

如是是使用代码构建engine:

  • 函数为 setMaxAuxStreams()。如果不设置,那么在分析nsys时会发现TensorRT构建的engine有可能会有多个stream。

如果是使用trtexec构建engine

  • 在构建engine的时候使用trtexec的选项—maxAuxStreams=N 设置为—maxAuxStreams=0 即可。
  • 模型小kernel比较多的时候,aux stream用default(7)会比0快
  • optlevel=4和aux stream对最终测出的性能有影响,optlevel=4一般是性能更好,aux stream只在少数情况下性能更好

在我们多线程,多stream的程序中,最好是可以手动控制stream。因此这里设置为0最好。

trtexec的IO format

在使用trtexec构建engine是可以指定engine的输出输出格式,这在一个onnx拆分为两个联接在一起的engine时非常有用,因为在拆分的部分可能存在reformat节点,在两个engine都存在比必要的reformat节点会增加延迟,这时只需要指定第一个engine的输出的format和第二个engine的输入formart就可以了

trtexec对应的选项是—inputIOFormats 和 —outputIOFormats

这里根据官方的文档,如果engine的第一层是卷积层,那么最合适的format是Tensor Core 实现的卷积需要 NHWC 布局,并且在 NHWC 中布局输入张量时速度最快。参考参考Tensor Layouts In Memory: NCHW vs NHWC

但是如果engine是在DLA上运行,那么参考https://zmurder.github.io/TensorRT/DLA/?highlight=hwc

  • kDLA_HWC 专为图像处理优化,维度顺序NHWC(Batch×Height×Width×Channels),适合相机输入(上面提到了The first layer must be convolution.)
  • kDLA_LINEAR是DLA支持的行主序(Row-Major)内存布局,维度顺序NCHW(Batch×Channels×Height×Width),适合非结构化数据,例如激光雷达点云数据

参考https://docs.nvidia.com/deeplearning/tensorrt/latest/inference-library/advanced.html

Format kINT32 kFLOAT kHALF kINT8 kBOOL kUINT8 kINT64 BF16 FP8 FP4/INT4
kLINEAR Only for GPU Yes Yes Yes Yes Yes Yes Yes Yes Yes
kCHW2 No No Only for GPU No No No No Yes No No
kCHW4 No No No Yes No No No No No No
kHWC8 No No Only for GPU No No No No Only for GPU No No
kCHW16 No No Only for DLA No No No No No No No
kCHW32 No Only for GPU Only for GPU Yes No No No No No No
kDHWC8 No No Only for GPU No No No No Only for GPU No No
kCDHW32 No No Only for GPU Only for GPU No No No No No No
kHWC No Only for GPU No No No Yes No No No No
kDLA_LINEAR No No Only for DLA Only for DLA No No No No No No
kDLA_HWC4 No No Only for DLA Only for DLA No No No No No No
kHWC16 No No Only for NVIDIA Ampere GPUs and later Only for GPU No No No No Only for GPU No
kDHWC No Only for GPU No No No No No No No No

CPU内存类型

在CPU生申请的的内存主要和GPU交互,例如H2D和D2H的拷贝,这里只说明一种情况,就是当我们的进程收到另一个进程发来的一个内存指针,需要将这段内存放在GPU中运算。

我们都知道CPU与GPU交互的内存最好都是pinne mem,这样拷贝速度最快。但是上面的情况是CPU的内存空间是我们的进程收到的,不是我们自己申请的,如何保证这段内存是pinned mem呢?我们可以使用cudaHostRegister函数来处理这段内存,从pageable mem变为pinned mem。(只处理一次就可以了)。

内存相关的细节参考

cuda context

一般情况下我们的一个进程就是共用一个cuda context,在多个进程使用到GPU时会涉及到cuda context的切换,切换时间大约也就是1ms以内。但是有时候会因为切换context造成一些GPU延迟的不稳定。这里举一个例子说明:

假如我们有两个进程使用GPU,A和B进程。

context A->context B 切换,那么A的kernel这时候可能还没有运算完成,但是context切换到B了,GPU同一时刻只有一个cuda context可以运行,因此A的这个kernel就会暂停运行,直到重新切换回context A才能继续运行。

这时我们分析nsys时会发现这个contextA的运行时间是不稳定的(或者kernel的时间是不稳定的)。可能就是这个原因。

kernel或者enqueue时间不稳定

除了上面说到的cuda context的切换会导致kernel或者模型的推理enqueue时间不稳定,stream的并行数量设置也是导致时间不稳定的原因。这个有一个突出的特点就是GPU空闲,但是kernel偶发时间变长。

使用环境变量设置stream的并发数 export CUDA_DEVICE_MAX_CONNECTIONS=32等 默认应该是8.

如图cudagraph或者一个kernel都运行完成了,但是从nsys上分析sync等了一会儿才返回,这时就要考虑是不是stream不够了。可以根据自己的线程和需要的stream设置需要的并发stream数量。