CUDA Stream优化经验

Multi-Process Service(MPS)原理:

    一个GPU卡上同时只能执行一个context;因此多进程同时往一个GPU卡上提交任务时,同时只能有一个任务跑起来,没法多任务并行;

    MPS服务:多进程提交的任务先提交至MPS服务进程,该进程会把所有任务使用同一个context但不同的stream, 提交给该块GPU卡,使得可以多任务并行;

    缺点:增大了任务提交的延迟,因为要多经过MPS服务进程这个“代理”;

CUDA Context里面有什么:

The cuda API exposes features of a stateful library: two consecutive calls relate one-another. In short, the context is its state.

The runtime API is a wrapper/helper of the driver API. You can see in the driver API that the context is explicitly made available, and you can have a stack of contexts for convenience. There is one specific context which is shared between driver and runtime API (See primary context)).

The context holds all the management data to control and use the device. For instance, it holds the list of allocated memory, the loaded modules that contain device code, the mapping between CPU and GPU memory for zero copy, etc.

Finally, note that this post is more from experience than documentation-proofed.

Stream: 任务队列,单个Stream内部FIFO,多个Stream之间及和host之间可overlap执行;

销毁Stream的API, host要blocking到该Stream所有任务执行完毕后,才会执行成功并继续;

尽量不要使用Stream0(defalut stream,有些API不指定stream则默认使用这个),它不能和其他stream并行执行;(其他stream设了cudaStreamNonBlocking者例外)

内存<-->显存Copy要想异步,必须同时满足以下条件:(另外,同方向同时只能有一个Copy在执行)

1. Copy任务必须不能在default stream里;

2. 必须使用Async版本API(cudaMemcpyAsync);

3. Host内存必须是pinned的;

同步:

按“狠”的程度从高到低:

    1. cudaDeviceSynchronize: Host等所有stream的所有任务都执行结束,才继续往下走;

    2. cudaStreamSynchronize: Host等这一个stream的所有任务都执行结束,才继续往下走; 

    3. 使用Event来同步;可以让Host等某个stream的某个event,也可以让某个stream等另一个stream的event;

Event有2种状态:发生,没发生;创建时默认是“发生”,cudaEventRecord会把它设为“没发生”,在stream里执行到它这里会把它设为“发生”;(多线程时,注意不要在创建event之前去调用cudaEventRecord,这里易出bug)

如果创建时使用"cudaEventDisableTiming"这个Flag,则该Event被执行到时不进行时间记录,可以节省开销;(我认为此时该event仅用于同步)

同步时对event的使用有3种方法:

    1. cudaEventQuery:Host主动查询event的状态;

    2. cudaEventSynchronize:Host blocking住,等待该event执行到才继续走;

    3. cudaStreamSynchronize:另一个stream blocking住(Host继续执行不blocking),等待该event执行到才继续走;

CUDA_LAUNCH_BLOCKING=1环境变量可以让所有stream变成对Host而言是同步执行(即Host发射一个任务,就等着该任务执行完,Host才能继续往下走);用于debug时;

Profiling工具:

    Windows上:Nsight Visual Studio版;NVIDIA Visual Profiler;

    Linux上:Nsight Eclipse版;NVIDIA Visual Profiler; 命令行nvprof;

优化原则:

    1. 让瓶颈设备的“空置率”尽可能低;

    2. Host线程发射任务尽可能早些,让发射和实际执行之间的“空闲”间隔尽可能小;

常见bad-case:

1-A: 启动kernel时忘记指定stream,导致默认stream和其他stream之间串行执行;

1-B: cudaEventRecord时忘记指定stream,导致该event被放进默认stream,导致cudaEventSynchronize时等待默认stream, 而默认stream又要等待其他steam的完成,造成大等待;

1:以上问题的解决:不要再默认stream上放任务;调用API要小心,不要忘记指定stream参数;或者让其他stream创建时指定为cudaStreamNonBlocking;

2-A:先cudaMemcpy,再启动另一个stream上的kernel,导致后者等待前者;因为前者放进了默认stream,要执行完Host才能继续;解决:换成cudaMemcpyAsync交给其他stream即可;

2-B:cudaMemcpyAsync时忘记在主存上使用pinned memory,导致退化为同步copy版本;(Visual Profiler上会显示"Memory Type"是"Pageable")

3:<显存开辟、kernel执行、显存释放>不断迭代,导致kernel要等显存开辟完成才执行,显存释放要等kernel执行完毕才执行(这两者会自动被CUDA检测到?示例代码不会崩溃?);显现出来痕迹是Host执行某些API的时间特别长(例如此例的cudaFree);解决:反复重用显存,减少开辟和释放的次数;

4:Host干活慢,耽误了GPU stream的发射;解决:把活儿交给GPU去做,Host采用多线程/SIMD等技术来加速;

5:kernel执行时间太短,显得Host端执行"cudaLaunch"的时间相对长,时间都浪费在"cudaLaunch"、"cudaLaunch"和真正执行之间的空隙上了;解决:"融合"成大任务、batch,总之让kernel耗时更长些;

6:过度同步:Host很多时候等待在同步API上;解决:合理重构尽量少同步、使用更”不狠“的同步API例如cudaEventSynchronize少用cudaDeviceSynchronize;

7:Profiler开销:减少同步次数和程度?

8:古老的CUDA GPU架构里,所有stream的任务被放到同一个队列的,所以并行程度和任务发射的顺序有关;

stream callback:

新一些的CUDA支持"cudaStreamAddCallback",在该stream执行到这里时,在host端调用这个指定的callback函数;可用于当stream的某任务完成时,通知Host端做些事;

注意:所有stream上注册的callback,都会被同一个driver线程执行,因此是串行的(神似ps-lite的用户callback!);所以callback里尽量只放很轻量级的操作,例如把指针交给线程池里的某个线程并signal它)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值