首页 > 其他分享 >ControlNet-trt优化总结3:使用multi-stream和cuda-graph构建并行流水线

ControlNet-trt优化总结3:使用multi-stream和cuda-graph构建并行流水线

时间:2023-10-08 16:23:02浏览次数:53  
标签:kernel multi stream graph self cudart trt

ControlNet-trt优化总结3:使用multi-stream和cuda-graph构建并行流水线

上节谈到使用TRT-API来构建网络,在这一节中总结一些trick来提升模型的运行效率,这些trick在所有的trt优化中均可使用,主要有以下几点:

  • 使用cuda_graph减少kernel间的启动间隙
  • 使用Mutil-stream增加异步

cuda_graph

cuda_graph的引入是为了解决kernel间launch的间隙时间问题的,尤其是有一堆小kernel,每个kernel启动也会带来一些开销,如果这些kernel足够多,那么就可能会影响系统的整体性能,cuda_graph的引入就是为了解决这个问题的,它会将stream内的kernel视为一整个graph,从而减少kernel的launch间隙时间。

cuda_graph基础

根据官方的源码示例,对cuda_graph的示例进行了补充,以下是完整代码示例:

#include <iostream>
#include <cuda_stream.h>

const int N = 5000;
const int NSTEP = 1000;
const int NKERNEL = 20;

const int blocks = 32;
const int threads = 512;

__global__ void shortKernel(float *out_d, float *in_d){
  int idx=blockIdx.x*blockDim.x+threadIdx.x;
  if(idx<N){
    out_d[idx]=1.5*in_d[idx];
  }
}

void run1(cudaStream_t &stream, float *out_d, float *in_d){
    // start CPU wallclock timer
    for(int istep=0; istep<NSTEP; istep++){
        for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
            shortKernel<<<blocks, threads, 0, stream>>>(out_d, in_d);
            cudaStreamSynchronize(stream);
        }
    }
}

void run2(cudaStream_t &stream, float *out_d, float *in_d){
    // start wallclock timer
    for(int istep=0; istep<NSTEP; istep++){
        for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
            shortKernel<<<blocks, threads, 0, stream>>>(out_d, in_d);
        }
        cudaStreamSynchronize(stream);
    }
    //end wallclock timer
}

void run3(cudaStream_t &stream, float *out_d, float *in_d){
    bool graphCreated=false;
    cudaGraph_t graph;
    cudaGraphExec_t instance;
    for(int istep=0; istep<NSTEP; istep++){
        if(!graphCreated){
            cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
            for(int ikrnl=0; ikrnl<NKERNEL; ikrnl++){
                shortKernel<<<blocks, threads, 0, stream>>>(out_d, in_d);
            }
            cudaStreamEndCapture(stream, &graph);
            cudaGraphInstantiate(&instance, graph, NULL, NULL, 0);
            graphCreated=true;
        }
    cudaGraphLaunch(instance, stream);
    cudaStreamSynchronize(stream);
    }
}

int main(int argc, char const *argv[])
{
    /* code */
    cudaStream_t stream;
    cudaStreamCreate(&stream);

    float* in_h = new float[N];
    float* out_h = new float[N];
    int nBytes = N * sizeof(float);
    // float *in_h, *out_h;
    // in_h = (float*)malloc(nBytes);
    // out_h = (float*)malloc(nBytes);

    for (int i=0;i<N;i++){
        in_h[i] = i+1;
    }
    
    float *in_d, *out_d;
    cudaMalloc((void **)&in_d, nBytes);
    cudaMalloc((void **)&out_d, nBytes);

    cudaMemcpy(in_d, in_h, nBytes, cudaMemcpyHostToDevice);

    // run1(stream, out_d, in_d);
    // run2(stream, out_d, in_d);
    run3(stream, out_d, in_d);

    cudaMemcpy(out_h, out_d, nBytes, cudaMemcpyDeviceToHost);

    for (int i=0;i<N;i++){
        printf("%f\n", out_h[i]);
    }

    cudaFree(in_d);
    cudaFree(out_d);
    delete[] in_h;
    delete[] out_h;

    return 0;
}

这里run1对应的是每个kernel都要进行同步,两个kernel之间会有大量的launch时间,对应的profile就像下图一样,可以看到每个kernel之间的gap时间是大于kernel本身的计算时间的,这就带来了极大的开销。


)

这里run2对应的是每20次kernel进行一次同步,也即视为每20个kernel相互间是独立的,这样20个kernel间便可以不等上一个kernel结束便开始下一轮启动,中间就可以overlap掉一部分开销,不过在同步之间的gap依然很大,profile图示如下:

这里run2对应的是将每20个小kernel进行capture得到一个cuda_graph,然后在后续的每次运行时只需launch这个cuda_graph即可,对于这20个小kernel,便是连续的没有中间启动时间开销的,这样就可以加快运行速度,其profile图示如下所示:

controlnet-cuda_graph优化

理解了上面cuda_graph运行原理,那么便不难写出优化代码了,具体如下:

with open('trt/{}.plan'.format(model), 'rb') as f, trt.Runtime(trt_logger) as runtime:
            trt_engine = runtime.deserialize_cuda_engine(f.read())
            trt_ctx = trt_engine.create_execution_context()

            for index in range(trt_engine.num_io_tensors):
                name = trt_engine.get_binding_name(index)
                if 'vae' in model:
                    if name == 'z':
                        trt_ctx.set_tensor_address(name, self.tensors['x'].data_ptr())
                    if name == 'out':
                        trt_ctx.set_tensor_address(name, self.tensors['img_out'].data_ptr())
                else:
                    trt_ctx.set_tensor_address(name, self.tensors[name].data_ptr())

            trt_ctx.execute_async_v3(stream)
            
            if useGraph:
                cudart.cudaStreamBeginCapture(stream, cudart.cudaStreamCaptureMode.cudaStreamCaptureModeGlobal)
                trt_ctx.execute_async_v3(stream)
                graph = cudart.cudaStreamEndCapture(stream)[1]
                graph_instance = cudart.cudaGraphInstantiate(graph, 0)[1]
            else:
                graph_instance = None

            self.engine_context_map[model] = trt_ctx

关键在于在每次运行前都capture对应的stream,形成graph_instance,而在具体使用时,只需要将对应的graph_instance启动起来即可:

 if useGraph:
    cudart.cudaGraphLaunch(self.control_fp16_graph_instance, self.stream1)
    cudart.cudaEventRecord(self.event1, self.stream1)
    cudart.cudaGraphLaunch(self.unet_input_fp16_graph_instance, self.stream)
    cudart.cudaStreamWaitEvent(self.stream, self.event1, cudart.cudaEventWaitDefault)
    cudart.cudaGraphLaunch(self.unet_output_fp16_graph_instance, self.stream)
else:
    self.engine_context_map['sd_control_fp16'].execute_async_v3(self.stream1)
    cudart.cudaEventRecord(self.event1, self.stream1)
    self.engine_context_map['sd_unet_input_fp16'].execute_async_v3(self.stream)
    cudart.cudaStreamWaitEvent(self.stream, self.event1, cudart.cudaEventWaitDefault)
    self.engine_context_map['sd_unet_output_fp16'].execute_async_v3(self.stream)

Multi-stream

cudaStream分为隐式流(默认流)和显式流。

对于隐式流:

  • 所有的CUDA操作默认运行在隐式流里;
  • 隐式流里的GPU 操作和CPU 操作两者是同步的;

对于显式流:

  • CPU计算和kernel计算并行;
  • CPU计算和数据传输并行;
  • 数据传输和kernel计算并行
  • 不同显式流的kernel计算并行

对于controlnet,可以将clip编码部分和vae的解码部分分割维两个不同的流,这样就可以利用输入输出的pipeline,上一个数据的解码便可以与下一个数据的编码同时进行。对于Unet和control部分,也可以通过分割为两个不同的stream进行处理。

self.stream = cudart.cudaStreamCreateWithPriority(cudart.cudaStreamNonBlocking, 0)[1]  
self.stream1 = cudart.cudaStreamCreateWithPriority(cudart.cudaStreamNonBlocking, 0)[1]
self.event = cudart.cudaEventCreateWithFlags(cudart.cudaEventDisableTiming)[1]
self.event1 = cudart.cudaEventCreateWithFlags(cudart.cudaEventDisableTiming)[1]

self.clip_instance = self.load_engine('sd_clip', self.stream1)
self.vae_instance = self.load_engine('sd_vae_fp16', self.stream)

self.control_fp16_graph_instance = self.load_engine('sd_control_fp16', self.stream1)
self.unet_input_fp16_graph_instance = self.load_engine('sd_unet_input_fp16', self.stream)
self.unet_output_fp16_graph_instance = self.load_engine('sd_unet_output_fp16', self.stream)

在数据同步上,通过cudaEventRecord标记同步点,通过cudaStreamWaitEvent同步时间,其实是同步不同stream之间的数据,保持数据一致性,下面的例子中clip模型要等待前面tokenizer操作完成,才能进行下操作,保持stream1开始前所有stream流中的数据都处理好了,重叠的是第二次的tokenizer和第一次的clip操作,这样整个运行是个流水线。最后通过cudaStreamSynchronize来保持所有同步。

cudart.cudaEventRecord(self.event, self.stream)
cudart.cudaStreamWaitEvent(self.stream1, self.event, cudart.cudaEventWaitDefault)
if useGraph:
    cudart.cudaGraphLaunch(self.clip_instance, self.stream1)
else:
    self.engine_context_map['sd_clip'].execute_async_v3(self.stream1)

cudart.cudaEventRecord(self.event1, self.stream1)

参考

  1. nvidia cuda-graphs: https://developer.nvidia.com/blog/cuda-graphs/
  2. nvidia developer blogs:https://developer.nvidia.com/zh-cn/blog/how-overlap-data-transfers-cuda-cc/

标签:kernel,multi,stream,graph,self,cudart,trt
From: https://www.cnblogs.com/wildkid1024/p/17749492.html

相关文章

  • 论文阅读:A Lightweight Knowledge Graph Embedding Framework for Efficient Inferenc
    ABSTRACT现存的KGE方法无法适用于大规模的图(由于存储和推理效率的限制)作者提出了一种LightKG框架:自动的推断出码本codebooks和码字codewords,为每个实体生成合适的embedding。同时,框架中包含残差模块来实现码本的多样性,并且包含连续函数来近似的实现码字的选择。为更好的提升K......
  • Langchain-Chatchat项目:2.1-通过GPT2模型来检索NebulaGraph
      在官方例子中给出了通过chain=NebulaGraphQAChain.from_llm(ChatOpenAI(temperature=0),graph=graph,verbose=True)来检索NebulaGraph图数据库。本文介绍了通过GPT2替换ChatOpenAI的思路和实现,暂时不考虑效果。之所以没用ChatGLM2是因为加载模型太慢,调试不方便,不过将GPT2......
  • CF506D Mr. Kitayuta's Colorful Graph
    好久没更新这个单题系列了,主要是最近没啥CF比赛空闲时间又少,今天忙里偷闲写了两个题这个题就比较典了,两点是否连通一般都是想到并查集维护,现在的问题是要对每种颜色的边把贡献算清楚很容易想到枚举所有颜色的边,每次求出所有连通分量后遍历一遍询问统计答案,这样正确性显然但复杂......
  • 初识stream流
     下面是stream的filter和forEach的使用filter来过滤出满足条件的元素  forEach 逐一处理流中的元素importjava.util.ArrayList;importjava.util.List;publicclassDemo2Steam{publicstaticvoidmain(String[]args){List<String>list=n......
  • Multisim 12.0-虚拟MOS管简单设置
    在软件中NMOS-FET使用需要设置参数,否则没作用简单的方法:其他用默认值,只要修改参数:M多重性导通关系M:10000Vgs:1VIds:100mAVgs:2VIds:400mAM:1000Vgs:1VIds:10mAVgs:2VIds:40mA关系大致如下:I=vvM*k......
  • 构造Vulkan图形管线:VkGraphicsPipeline
     创建Pipeline构造信息:它包括:基本构造信息VkStructureType构建Pipeline额外需要的结构:constvoid*pNext构建Pipeline时指定的Flags:VkPipelineCreateFlags多个ShaderStage信息:VkPipelineShaderStageCreateInfo*(数组)......
  • multipart/form-data数据格式
    multipart/form-data数据格式(*********************************************multipart/form-data数据格式********************************************#请求头,这个是必须的,需要指定Content-Type为multipart/form-data,指定唯一边界值Content-Ty......
  • Graph-less Collaborative Filtering
    目录概符号说明SimRecPrediction-LevelDistillationEmbedding-levelDistillationAdaptiveContrastiveRegularization总的损失代码XiaL.,HuangC.,ShiJ.andXuY.Graph-lesscollaborativefiltering.WWW,2023.概从GNN的教师模型中蒸馏结构信息到一般的不带图结......
  • Streamlit项目:乐高风格马赛克设计工坊~打造个性化马赛克图案的平台
    文章目录1前言2项目概述2.1项目背景和目标2.2功能和特性2.3技术实现2.4开发计划2.5预期成果2.6应用场景3使用指南3.1源代码使用指南3.2普通网页用户使用指南3.3文件保存3.3.1导出图像文件3.3.2获取像素RGB数据3.4注意事项4实现细节4.1准备工作4.2编写代码4.3运......
  • 深入探讨Java Stream流:数据处理的新思维
    文章目录1.流式思想1.1输入流与输出流1.2Stream流2.使用Stream流的步骤3.获取Stream流3.1容器3.2数组4.Stream流中间操作方法4.1`filter(Predicate<?superT>predicate)`4.2`limit(longmaxSize)`4.3`skip(longn)`4.4`distinct()`4.5`sorted()`和`sorted(Compar......