在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

解析OneFlow BatchNorm相關算子實現

jf_pmFSk4VX ? 來源:GiantPandaCV ? 2022-12-23 15:08 ? 次閱讀

0x1. 前言

在ResNet中(https://github.com/pytorch/vision/blob/main/torchvision/models/resnet.py),關于BatchNorm的調用一共有兩種模式,第一種是ReLU接在BN之后:

out=self.bn1(out)
out=self.relu(out)

另外一種模式是殘差結構引入的 BNAddReLU 的模式:

out=self.bn2(out)

ifself.downsampleisnotNone:
identity=self.downsample(x)

out+=identity
out=self.relu(out)

我們知道在 CUDA 優化中常見的一個技巧是將一些ElementWise的算子融合到之前的計算密集型算子如卷積,矩陣乘等。在OneFlow中針對上述兩種情況并且cudnn無法fuse時分別進行了fuse和優化,本篇文章就來解析一下這里的代碼實現,體會其中的CUDA優化技巧。這里的源碼開源在OneFlow的github倉庫:「https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu」 。如果本文對你產生了啟發,不妨為OneFlow投個star。

0x2. 代碼解析

0x2.1 CUDNN BatchNorm算子的實現和局限

我們先來看一下OneFlow中是如何使用CUDNN庫實現BatchNorm算子的。代碼見:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu#L31-L244 。這段代碼中首先實現了一個getCudnnBatchNormMode工具函數:

cudnnBatchNormMode_tgetCudnnBatchNormMode(constint64_tdim){
if(dim==2){
returnCUDNN_BATCHNORM_PER_ACTIVATION;
}elseif(ParseBooleanFromEnv("ONEFLOW_ENABLE_NHWC",false)){
returnCUDNN_BATCHNORM_SPATIAL_PERSISTENT;
}else{
//NOTE(LiangDepeng):ThenewCUDNN_BATCHNORM_SPATIAL_PERSISTENTmodewas
//introducedinCuDNN7forperformanceoptimization,butitresultsin
//accuracylossesinconvolutionmodelssuchasResNeXt-101and
//videoR(2+1)D.WewillfallbacktothenormalCUDNN_BATCHNORM_SPATIAL
returnCUDNN_BATCHNORM_SPATIAL;
}
}

這里的dim表示輸入Tensor的維度,比如形狀為的輸入Tensor,這里的維度就是4。然后這里涉及到三種不同的cudnnBatchNormMode_t,我們看一下CUDNN的文檔(https://docs.nvidia.com/deeplearning/cudnn/api/index.html#cudnnBatchNormMode_t):

bb6519d8-828a-11ed-8abf-dac502259ad0.png

可以看到 CUDNN_BATCHNORM_PER_ACTIVATION 被用于非卷積層,在OneFlow中只有當輸入Tensor的維度為2時才選取這種模式。而CUDNN_BATCHNORM_SPATIAL_PERSISTENT這種模式只有當輸入Tensor的數據排布為NHWC方式時才會啟用。而對于其它的模式,在OneFlow中一律選取CUDNN_BATCHNORM_SPATIAL模式。

接下來閱讀一下 InferDimSizeAndDataFormat 函數:

voidInferDimSizeAndDataFormat(constShapeView&x_shape,constint32_taxis,int32_t*n,int32_t*c,
int32_t*h,int32_t*w,cudnnTensorFormat_t*format){
if(x_shape.Count(axis+1)==1){
if(axis==0){
*n=1;
*h=1;
}else{
*n=x_shape.At(0);
*h=x_shape.Count(1,axis);
}
*w=1;
*c=x_shape.At(axis);
*format=CUDNN_TENSOR_NHWC;
}else{
*n=x_shape.Count(0,axis);
*c=x_shape.At(axis);
*h=x_shape.Count(axis+1);
*w=1;
*format=CUDNN_TENSOR_NCHW;
}
}

這個函數會根據輸入Tensor的shape以及axis推斷這個Tensor的內存排布是NCHW還是NHWC模式,并設置對應的n, c, h, w變量。

//推斷和設置cudnn中的Tensor描述符
voidInferXYCudnnTensorDesc(constShapeView&xy_shape,constDataType&data_type,
constint32_taxis,cudnnTensorDescriptor_txy_desc){
int32_tn,c,h,w;
cudnnTensorFormat_tformat;
//根據輸入Tensor的shape推斷format和n,c,h,w
InferDimSizeAndDataFormat(xy_shape,axis,&n,&c,&h,&w,&format);
//根據上述的推斷結果,設置Tensor的描述符
OF_CUDNN_CHECK(
cudnnSetTensor4dDescriptor(xy_desc,format,GetCudnnDataType(data_type),n,c,h,w));
}
//根據輸入Tensor的描述符xy_desc和cudnnBatchNormMode_t模式設置參數的描述符param_desc
voidInferParamCudnnTensorDesc(constcudnnTensorDescriptor_txy_desc,cudnnBatchNormMode_tmode,
cudnnTensorDescriptor_tparam_desc){
OF_CUDNN_CHECK(cudnnDeriveBNTensorDescriptor(param_desc,xy_desc,mode));
}
//這個類就是完整使用上述的工具函數的工具類,負責推斷cudnnBatchNorm接口需要的各種描述信息比如這里的xy_desc_,param_desc_,param_data_type_和param_size_
classCudnnTensorDescHelperfinal{
public:
OF_DISALLOW_COPY_AND_MOVE(CudnnTensorDescHelper);
CudnnTensorDescHelper(constShapeView&xy_shape,constDataType&data_type,constint32_taxis,
cudnnBatchNormMode_tmode){
OF_CUDNN_CHECK(cudnnCreateTensorDescriptor(&xy_desc_));
InferXYCudnnTensorDesc(xy_shape,data_type,axis,xy_desc_);
OF_CUDNN_CHECK(cudnnCreateTensorDescriptor(¶m_desc_));
InferParamCudnnTensorDesc(xy_desc_,mode,param_desc_);
intn,c,h,w,n_stride,c_stride,h_stride,w_stride;
OF_CUDNN_CHECK(cudnnGetTensor4dDescriptor(param_desc_,¶m_data_type_,&n,&c,&h,&w,
&n_stride,&c_stride,&h_stride,&w_stride));
param_size_=c;
}
~CudnnTensorDescHelper(){
OF_CUDNN_CHECK(cudnnDestroyTensorDescriptor(param_desc_));
OF_CUDNN_CHECK(cudnnDestroyTensorDescriptor(xy_desc_));
}

cudnnTensorDescriptor_txy_desc()const{returnxy_desc_;}

cudnnTensorDescriptor_tparam_desc()const{returnparam_desc_;}

voidCheckParamTensor(constuser_op::Tensor*tensor)const{
CHECK_NOTNULL(tensor);
CHECK_EQ(tensor->shape_view().NumAxes(),1);
CHECK_EQ(tensor->shape_view().At(0),param_size_);
CHECK_EQ(GetCudnnDataType(tensor->data_type()),param_data_type_);
}

private:
cudnnTensorDescriptor_txy_desc_=nullptr;
cudnnTensorDescriptor_tparam_desc_=nullptr;
cudnnDataType_tparam_data_type_;
int32_tparam_size_=0;
};

除了這些描述信息之外,我們還可以在cudnn提供的文檔中查看BatchNorm相關的算子一般還需要什么特殊的輸入信息。我們來看 cudnnBatchNormalizationForwardTrainingEx() 這個API :https://docs.nvidia.com/deeplearning/cudnn/api/index.html#cudnnBatchNormalizationForwardTrainingEx 。

bb7f7b34-828a-11ed-8abf-dac502259ad0.png

可以看到這個算子是 cudnnBatchNormalizationForwardTraining() 這個算子的擴展,擴展的內容就是可以我們可以傳入額外的一個Activation的算子比如ReLU以及一個Add算子分別對應我們在前言中介紹的 ResNet 中的 BNReLU 和 BNAddReLU 模式。可以看到在這個算子接口中除了對輸入Tensor x,BN后需要add的輸入Tensor z以及輸出Tensor y的描述信息外,還需要指定workspace和reserveSpace,這個workspace是cudnn的BatchNorm以NHWC模式計算時需要的GPU內存buffer,而reserveSpace則表示當前這個配置的BN算子至少還需要多少可以申請的GPU顯存(從文檔猜測應該是和BNReLU/BNAddReLU這倆Pattern相關)。

在OneFlow中, https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu#L126-L175 以及 https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu#L637-L684 就是為了推斷BN算子以及BN擴展的算子需要的額外GPU內存大小,然后在OneFlow的內存池中開辟一塊顯存供調用cudnn的 cudnnBatchNormalizationForwardTrainingEx() 和 cudnnBatchNormalizationBackwardEx() 接口時使用。

關于調用cudnn的BatchNorm相關的算子api,我們還需要注意一點,那就是要使用cudnn提供的擴展接口cudnnBatchNormalizationForwardTrainingEx() 和 cudnnBatchNormalizationBackwardEx() 還存在一些限制:

bbbeb740-828a-11ed-8abf-dac502259ad0.png

首先是cudnn版本的限制,然后對于CUDNN_BATCHNORM_OPS_BN_ADD_ACTIVATION的Op模式,輸入Tensor的通道數必須是4的倍數,最后這個擴展Op必須在輸入Tensor的數據排布模式是NHWC時才能啟動。這些限制對應到OneFlow的代碼在:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/core/job_rewriter/cudnn_fused_normalization_add_relu_pass.cpp#L79-L86 。

0x2.2 善用CUDA優化打破cudnn的限制

上面提到要使用CUDNN的擴展算子有一系列限制,我們有沒有辦法打破這限制呢?有的。以ResNet為例,針對BNReLu和BNAddReLU這兩種Pattern,我們可以分別針對ReLU和AddReLU實現一個CUDA Kernel,相信入門CUDA的小伙伴寫這兩個算子都沒什么問題。但如何在考慮到Backward的時候把這兩個算子優化到位呢?OneFlow給出了一個解決方案。

前向的CUDA實現:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu#L246-L272bcbd3036-828a-11ed-8abf-dac502259ad0.png

反向的CUDA實現:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu#L246-L272

bdaaa654-828a-11ed-8abf-dac502259ad0.png

以 ReLU 算子為例,前向的輸入為x,輸出為y,后向的輸入為dy和y,輸出dx。后向計算中的y僅用來判斷對應元素是否大于0,因此可以將y替換為由前向生成的bitset(對應上述代碼中的mask),理論上可以省掉ReLU的后向算子對冗余的y的訪問操作,減少約y大小的讀取,也對應約1/3的global memory訪問。對于ReLU/ReLUAdd這種ElementWise算子來說,GPU的帶寬是極容易成為瓶頸的,通過這種優化可以大大提升ReLU和ReLUAdd算子的帶寬。

在 《OneFlow是如何做到世界上最快的深度學習框架》(https://zhuanlan.zhihu.com/p/271740706) 文章中已經介紹到了這種基于bitmask優化后向算子的方案。并且文章中給出了3種方案,但沒有給出對應的代碼實現,實際上我只讀懂了第一種和第三種方案,接下來我們描述一下這兩種方案。

Bitset mask生成方案一:順序遍歷法

這種方法是讓每個CUDA線程連續讀取內存中的8個元素,并根據每個元素是否大于0生成一個int8類型的mask,并寫入到最終的bitset mask中。這種訪問對于寫全局內存是連續訪問的,但對于讀(Read)全局內存,線程間內存訪問不連續,所以沒有充分合并內存事務。下圖展示了這種方案讀寫內存的示例:

bdc42034-828a-11ed-8abf-dac502259ad0.png

以ReLU為例子,這種方案的代碼實現如下:

template
__global__voidReluGpu(int64_tn,constT*x,T*y,int8_t*mask){
CUDA_1D_KERNEL_LOOP(i,n){
int8_tmask_val=0;
for(int32_tj=0;j0);
if(is_positive){
y[offset]=sum;
mask_val|=(1<

在這種方案中,每個thread需要連續讀的8個float32數據,則相鄰線程每次加載數據的間隔為32 bytes = 4 bytes * 8。所以每個線程一次加載指令就要執行一個32字節的內存事務。故warp內的線程間全局內存訪問完全沒有合并,實際有效訪存帶寬僅為 1/8,訪存效率十分低下,性能很差。

Bitset mask生成方案三:warp同步法

我們可以采用warp級別的同步原語:__ballot_sync(unsigned mask, predicate),這個函數接收兩個參數,第一個參數是warp中參與計算的線程掩碼,第二個參數是要參與判斷的bool值,返回一個32bit的mask,每個bit代表warp中各個線程傳入的元素是否大于0,最后由每個warp中的0號線程將生成的mask寫入global memory中。(idea可以參考NVIDIA的性能優化博客:https://developer.nvidia.com/blog/using-cuda-warp-level-primitives/)

這種方案的示意圖如下:

poYBAGOlVFuAWMhwAACXY46xsP8359.jpg

以ReLU為例,代碼實現如下:

template
__global__voidReluGpu(int64_tn,constT*x,T*y,int32_t*mask){
constint32_tlane_id=threadIdx.x%kCudaWarpSize;//如果lane_id=0,表示當前線程是一個warp的0號線程
CUDA_1D_KERNEL_LOOP(i,n){
constboolis_positive=(x[i]>0);
int32_twarp_mask=__ballot_sync(__activemask(),static_cast(is_positive));
if(lane_id==0){mask[i/kCudaWarpSize]=warp_mask;}//0號線程將生成的mask寫入globalmemory
y[i]=is_positive?sum:0;
}
}

0x3. 性能

我們這里對比一下BNReLU這個Pattern在優化前后的后向Kernel(也就是ReLU Grad Kernel)的性能和帶寬表現,本次測試的環境為A100 PCIE 40G,使用Nsight Compute工具進行Profile。Profile的腳本為:

importoneflowasflow
bn=flow.nn.BatchNorm2d(num_features=32,eps=1e-5,momentum=0.1).to("cuda")
fused_bn=flow.nn.FusedBatchNorm2d(32).to("cuda")
bn.train()
fused_bn.train()

x=flow.randn(16,32,112,112).to("cuda").requires_grad_()

y=flow.relu(bn(x))#這個是未優化的實現
#y=fused_bn(x)#打開這行代表啟用上述介紹的優化
res=y.sum()
res.backward()
res_scalar=res.detach().cpu().numpy()

經過多次測試,flow.relu(bn(x))中對應的ReLU的反向Kernel耗時大概為 「48.3us」,而fused_bn(x)中對應的ReLU的反向Kernel耗時大概為 「42.8us」 ,可以說明上述基于mask掩碼降低全局內存訪問的優化方法是有效的。而對于BNAddReLU的Pattern來說,則可以獲得更好的性能提升,因為ReluBackward相當于將這兩個ElementWise操作給fuse了。

0x4. 總結

這里暫時寫了一下個人看OneFlow Normalization 系列算子實現的理解。實際上我個人還是有一些疑問在,如果后續能搞清楚的話,會繼續補充和修改。

0x5. 相關鏈接

cudnn文檔:https://docs.nvidia.com/deeplearning/cudnn/api/index.html

oneflow代碼實現:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/user/kernels/normalization_kernel.cu

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 代碼
    +關注

    關注

    30

    文章

    4803

    瀏覽量

    68752
  • 算子
    +關注

    關注

    0

    文章

    16

    瀏覽量

    7267
  • CUDA
    +關注

    關注

    0

    文章

    121

    瀏覽量

    13643
  • OneFlow
    +關注

    關注

    0

    文章

    9

    瀏覽量

    8804

原文標題:【BBuf的CUDA筆記】二,解析 OneFlow BatchNorm 相關算子實現

文章出處:【微信號:GiantPandaCV,微信公眾號:GiantPandaCV】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    OneFlow Softmax算子源碼解讀之WarpSoftmax

    寫在前面:近來筆者偶然間接觸了一個深度學習框架 OneFlow,所以這段時間主要在閱讀 OneFlow 框架的 cuda 源碼。官方源碼基于不同場景分三種方式實現 Softmax,本文主要介紹其中一種的
    的頭像 發表于 01-08 09:24 ?897次閱讀
    <b class='flag-5'>OneFlow</b> Softmax<b class='flag-5'>算子</b>源碼解讀之WarpSoftmax

    OneFlow Softmax算子源碼解讀之BlockSoftmax

    寫在前面:筆者這段時間工作太忙,身心俱疲,博客停更了一段時間,現在重新撿起來。本文主要解讀 OneFlow 框架的第二種 Softmax 源碼實現細節,即 block 級別的 Softmax。
    的頭像 發表于 01-08 09:26 ?725次閱讀
    <b class='flag-5'>OneFlow</b> Softmax<b class='flag-5'>算子</b>源碼解讀之BlockSoftmax

    基于GFO算子的圖像增強算法如何去實現

    基于GFO算子(廣義模糊算子)的圖像增強算法如何去實現?怎樣對圖像增強算法進行分析?
    發表于 06-04 06:24

    TensorFlow、PyTorch,“后浪”OneFlow 有沒有機會

    TensorFlow、PyTorch,“后浪”OneFlow 有沒有機會 | 一流科技工程師成誠編者按:7月31日,一流科技在創業1300天后,他們宣布開源自研的深度學習框架OneFlow,此前,CSDN對CEO袁進輝進行了專訪。本文中,一流科技工程師成...
    發表于 07-27 08:24

    拉普拉斯算子的FPGA實現方法

    拉普拉斯算子的FPGA實現方法  引 言   在圖像處理系統中常需要對圖像進行預處理。由于圖像處理的數據量大,對于實時性要求高的系統,采用軟件實現通常
    發表于 02-11 11:01 ?1532次閱讀
    拉普拉斯<b class='flag-5'>算子</b>的FPGA<b class='flag-5'>實現</b>方法

    LOG算子在FPGA中的實現

    介紹了一種高斯拉普拉斯LOG算子在FPGA中的實現方案!并通過對一幅BMP圖像的處理!論證了在FPGA中實現的LOG算子的圖像增強效果
    發表于 05-16 17:12 ?50次下載
    LOG<b class='flag-5'>算子</b>在FPGA中的<b class='flag-5'>實現</b>

    BatchNorm是一種旨在通過固定層輸入的分布來改善神經網絡訓練的技術

    作者探討了BatchNorm,優化和Internal Covariate Shift三者之間的關系。作者在CIFAR-10數據集上分別使用和不使用BatchNorm來訓練標準的VGG網絡,如上圖顯示
    的頭像 發表于 07-03 14:37 ?7333次閱讀
    <b class='flag-5'>BatchNorm</b>是一種旨在通過固定層輸入的分布來改善神經網絡訓練的技術

    Laplacian算子的FPGA實現方法

    拉普拉斯算子是一種重要的圖像增強算子,它是一種各向同性濾波器,即濾波器的響應與濾波器作用圖像的突變方向無關,而且實現簡單,被廣泛用于圖像銳化和高頻增強等算法中。在此,提出一種使用QuartusⅡ開發環境的Megafunction
    的頭像 發表于 06-16 17:47 ?3294次閱讀
    Laplacian<b class='flag-5'>算子</b>的FPGA<b class='flag-5'>實現</b>方法

    鍵盤電子實現

    鍵盤電子實現
    發表于 05-26 15:32 ?1次下載

    開源軟件-OneFlow通用深度學習框架

    ./oschina_soft/oneflow.zip
    發表于 06-20 09:26 ?2次下載
    開源軟件-<b class='flag-5'>OneFlow</b>通用深度學習框架

    Laplacian算子的硬件實現及結果

    使用Laplacian算子濾波是將模板與圖像做卷積運算,然后將得到的結果取絕對值后,再進行防治溢出(灰度值大于255)處理。所以在用硬件實現Laplacian算子時可分成三個步驟:構造模板;使用模板對圖像進行卷積運算;對卷積后的
    發表于 07-21 09:27 ?1115次閱讀

    Sobel算子原理介紹與實現方法

    索貝爾算子(Sobel operator)主要用作邊緣檢測,在技術上,它是一離散性差分算子,用來運算圖像亮度函數的灰度之近似值。在圖像的任何一點使用此算子,將會產生對應的灰度矢量或是其法矢量Sobel 卷積因子為:
    的頭像 發表于 07-21 17:27 ?1.3w次閱讀

    flowflops:OneFlow模型的Flops計算

    用于計算 OneFlow 模型的 FLOPs 和 Parameters 的第三方庫。
    的頭像 發表于 11-16 10:04 ?1224次閱讀

    解析OneFlow Element-Wise算子實現方法

    雖然這種寫法非常簡單明了,但卻存在明顯的性能問題。所以這篇文章將基于OneFlow開源的Element-Wise CUDA算子方案來解釋如何寫一個高性能的Element-Wise CUDA算子
    的頭像 發表于 12-12 10:54 ?1588次閱讀

    自定義算子開發

    一個完整的自定義算子應用過程包括注冊算子算子實現、含自定義算子模型轉換和運行含自定義op模型四個階段。在大多數情況下,您的模型應該可以通過使用hb_mapper工具完成轉換并順利部署
    的頭像 發表于 04-07 16:11 ?2825次閱讀
    自定義<b class='flag-5'>算子</b>開發
    主站蜘蛛池模板: 天堂视频网| 午夜免费福利在线观看| 人人搞人人| 黄色网址有那些| 色婷婷一区二区三区四区成人网| 男女交性特一级| 国产伦精品一区二区三区高清| 伊人网在线观看| 国产福利网站| 狠狠要| 欧美亚洲天堂| 婷婷丁香综合网| 亚洲最大成人综合网| 国产在线精品观看| 欧美午夜寂寞影院安卓列表| 欧美乱妇高清无乱码| 成年全黄大色大黄| 人人操天天射| 国产三片高清在线观看| 六月丁香激情网| xxx日本69| 日韩激情淫片免费看| 福利看片| www.天天射| h在线网站| 一级一级18女人毛片| 永井玛丽亚中文在线观看视频| 性欧美暴力猛交69hd| 亚洲男同tv| 五月婷六月丁香| 欧美高清成人videosex| 巨乳色最新网址| 久久看免费视频| 精品欧美一区二区三区在线观看| 教官的好爽好深h片段| 97在线精品| 午夜无遮挡怕怕怕免费视频| 精品影视网站入口| 西西人体大胆高清啪啪欧洲| 黄色网址视频在线观看| 爱爱帝国亚洲一区二区三区|