精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

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

3天內不再提示

束內規約與塊內規約問題

jf_pmFSk4VX ? 來源:GiantPandaCV ? 2023-11-27 17:29 ? 次閱讀

寫在前面:規約問題在 CUDA 編程中應用非常廣泛,筆者最近在研究 Faster Transformer 源碼,趁此機會結合 Nivida 官方的代碼對規約手段進行總結。

1 應用背景

關于規約的定義,相信能讀到這篇文章的讀者都不陌生,筆者在早期的文章中也介紹過一些規約方法,基本思想都是折半規約,主要應用于較大元素規模的向量規約,有興趣的讀者可以移步【CUDA編程】CUDA編程中的并行規約問題。
本文要介紹的規約場景與之前有所不同,主要應用于矩陣規約,也就是說本文假設的輸入變量的維度是 2 維的,形狀為 [batch_size, hidden_units],規約之后的輸出變量形狀為 [batch_size, ]
接下來,本文將以規約求和為例介紹兩種規約方式:束內規約塊內規約

2 束內規約

束內規約,也就是在一個線程束內對某個變量進行規約。我們知道 CUDA 架構下指令是以線程束(相鄰的 32 個線程)為基本單元執行的,線程束內也可以通過束內洗牌指令進行通信,所以這提供了一個很好的束內規約思路。下面是 Nvidia 提供的基礎的一個規約設備函數。

template <typename T>
__inline__ __device__
T warpReduceSum(T val)
{
  for(int mask = 16; mask > 0; mask >>= 1)
    val += __shfl_xor_sync(FINAL_MASK, val, mask, 32);
  return val;
}

這個設備函數可以求出當前線程所在線程束的指定變量的規約和,原理涉及洗牌指令的計算邏輯,不再贅述。
當矩陣寬度 hidden_units 較小時,通常可以使用一個 warp 處理一行數據,一個 block 內可以處理多行數據,筆者給出具體的核函數如下:

// 一個 warp 處理一行數據
template<typename T>
__global__ void matrix2DWarpReduceSum(const T* inp, T*out, const uint32_t hidden_units) {
    uint32_t tid = threadIdx.x;
    uint32_t lane_id = tid % 32;
    uint32_t warp_id = tid / 32;
    uint32_t warp_num = blockDim.x / 32;
    uint32_t offset = blockIdx.x * warp_num * hidden_units + warp_id * hidden_units;
    T val = 0.0f;
    for (uint32_t i=lane_id; i32) {
        val += inp[offset + i];
    }
    __syncwarp();
    T warpSum;
    warpSum = warpReduceSum(val);
    if (lane_id == 0) {
      out[blockIdx.x * warp_num + warp_id] = warpSum;
    }
}

template<typename T>
void launchMatrix2DWarpReduceSum(const T* d_x, T* d_y, const uint32_t batch_size, const uint32_t hidden_units) {
  constexpr uint32_t warp_num = BLOCK_SIZE / 32;
  uint32_t gird_size = (batch_size - 1) / (warp_num) + 1;
  matrix2DWarpReduceSum<<>>(d_x, d_y, hidden_units);
}

先確定 block_size,這里筆者直接取 128,由于是一個 warp 處理一行數據,所以一個 block 可以處理 warp_num 行數據,總共需要 grid_size 個 block。
核函數內部首先計算當前線程所在的 warp 編號 warp_id 用來定位當前處理元素在哪一行,然后確定線程在 warp 內的編號 lane_id 用來定位該線程具體處理那些元素。由于矩陣寬度 hidden_units 實際肯定還是比 32 大的,所以不可能說一個線程只處理一個元素,因此每個線程會處理多個元素,步長為 32,例如當 hidden_units128 時,lane_id = 0 的線程將處理位置為 0、32、64、96 的四個元素,lane_id = 1 的線程將處理位置為 1、33、65、97 的四個元素,以此類推,這個計算過程是沒有并行的。循環計算一輪后,對線程束內每個線程的 val 進行束內規約就可以得到一行元素的規約和。

3 塊內規約

塊內規約,就是在一個線程塊內求規約值,通常塊內規約會通過束內規約來實現,以下是 Nvidia 提供的一個塊內規約設備函數。

template <typename T>
__inline__ __device__
T blockReduceSum(T val)
{
  static __shared__ T shared[32]; 
  int lane = threadIdx.x & 0x1f; 
  int wid = threadIdx.x >> 5;  

  val = warpReduceSum(val);

  if(lane == 0)
    shared[wid] = val;
  __syncthreads();
  
  val = (threadIdx.x < (blockDim.x >> 5 )) ? shared[lane] : (T)0.0f;
  val = warpReduceSum(val);
  return val;
}

規約思路分為兩步,首先通過束內規約求出當前線程所在 warp 的規約值,存入 shared 中,然后把 warpSum 賦值給 threadIdx.x 小于 32 的線程內的變量 val,這 32 個線程正好也在一個線程束內,然后再執行一次束內規約就得到塊內規約值,計算思路非常巧妙。
另外針對塊內規約的問題,官方 cub 庫其實提供了 API開發者可以導入頭文件 cub/cub.cuh 后直接使用,注意低版本的 cuda 不支持此 API。我們來看下 API 的調用方式。

#include 

template<typename T>
struct SumOp {
  __device__ __forceinline__ T operator()(const T& a, const T& b) const { return a + b; }
};

template<template<typename> class ReductionOp, typename T, int block_size>
__inline__ __device__ T BlockAllReduce(T val) {
  typedef cub::BlockReduce BlockReduce;
  __shared__ typename BlockReduce::TempStorage temp_storage;
  __shared__ T result_broadcast;
  T result = BlockReduce(temp_storage).Reduce(val, ReductionOp());
  if (threadIdx.x == 0) { result_broadcast = result; }
  __syncthreads();
  return result_broadcast;
}

除了必要的待規約變量、block_size 以外,還需要傳入一個計算函數,筆者給出了示例 SumOp
當矩陣寬度 hidden_units 較大時,通常可以使用一個 block 處理一行數據,筆者給出具體的核函數如下:

template<typename T>
__global__ void matrix2DBlockReduceSum(const T* inp, T*out, const uint32_t hidden_units) {
  T val = 0.0f;
  uint32_t offset = blockIdx.x * hidden_units;
  for (uint32_t i=threadIdx.x; i(val);
  if (threadIdx.x == 0) {
    out[blockIdx.x] = blockSum;
  }
}

template<typename T>
void launchMatrix2DBlockReduceSum(const T* d_x, T* d_y, const uint32_t batch_size, const uint32_t hidden_units) {
  uint32_t gird_size = batch_size;
  matrix2DBlockReduceSum<<>>(d_x, d_y, hidden_units);
}

同樣,block_size 這里筆者直接取 128,由于是一個 block 處理一行數據,總共需要 batch_size 個 block。
由于矩陣寬度 hidden_units 實際肯定還是比 block_size 大的,所以不可能說一個線程只處理一個元素,因此每個線程會處理多個元素,步長為 block_size,例如當 hidden_units512 時,lane_id = 0 的線程將處理位置為 0、128、256、384 的四個元素,lane_id = 1 的線程將處理位置為 1、129、257、385 的四個元素,以此類推,這個計算過程是沒有并行的。循環計算一輪后,對 block 內每個線程的 val 進行塊內規約就可以得到一行元素的規約和。

4 向量化數據提升訪存帶寬

使用向量化操作能夠提升內存讀寫的帶寬,而 CUDA 里也提供了一系列數據類型來支持向量化操作,如 float2、float4,就是將 2 個或 4 個 float 數據作為一個整體。為了增加代碼的復用性,筆者這里封裝了一個 Packed 數據結構,用于對不同的數據類型進行打包。

template <typename T, int pack_size>
struct alignas(sizeof(T) * pack_size) Packed
{
    __device__ Packed()
    {
        // do nothing
    }
    union
    {
        T elem[pack_size]; // 這里聯合體只有一個成員,為了方便后期擴展
    };
};

結構體內有一個 elem 數組變量,整個結構的內存對齊設置為 sizeof(T) * pack_size,說白了其實就是把 pack_sizeT 類型的數據“捆綁”在一起組成一個新的數據結構,讀寫內存的時候只需要一次讀寫就可以讀 pack_size 個數據,目的是減小內存讀寫次數。
那么這個 pack_size 能不能無限大呢?顯然不能,CUDA 里最大支持 128 bit 的訪問粒度,也就是說對于 float 類型(占 4 個字節,32 bit),一次最多讀寫 4 個,也就是說 float 的 pack_size 最多取到 4,本文筆者的示例代碼中數據類型都以 float 為例,pack_size4

4.1 pack 后的束內規約示例代碼

matrix2DWarpReduceSum 改寫為 pack 版的核函數也很簡單,計算思路都是一致的,只不過原來一次訪問一個元素,現在一次訪問一個 pack 的元素,在執行核函數之前筆者加了一個斷言,保證 hidden_units 能夠被 pack_size 整除,具體代碼如下。

template <int pack_size, typename T>
__global__ void matrix2DWarpReduceSumPack(const T* d_x, T* d_y, const uint32_t hidden_units, const uint32_t num_packs) {
  const uint32_t warp_id = threadIdx.x / 32;
  const uint32_t lane_id = threadIdx.x & 0x1f;
  const uint32_t warp_num = blockDim.x / 32;
  const uint32_t offset = blockIdx.x * warp_num * hidden_units + warp_id * hidden_units;
  const Packed* buf = reinterpret_cast<const Packed*>(d_x + offset);
  Packed pack;
  T val = 0.0f;
  for (uint32_t pack_id=lane_id; pack_id32) {
    pack = buf[pack_id];
    for (uint32_t i=0; i(val);
  if (lane_id == 0) {
    d_y[blockIdx.x * warp_num + warp_id] = warpSum;
  }
}

template<typename T>
void launchMatrix2DWarpReduceSumPack(const T* d_x, T* d_y, const uint32_t batch_size, const uint32_t hidden_units) {
  constexpr uint32_t warp_num = BLOCK_SIZE / 32;
  uint32_t gird_size = (batch_size - 1) / (warp_num) + 1;
  constexpr uint32_t pack_size = 4;
  // 一行元素的 pack 數量
  uint32_t num_packs = hidden_units / pack_size;
  assert(hidden_units % pack_size == 0);
  matrix2DWarpReduceSumPack<<>>(d_x, d_y, hidden_units, num_packs);
}

核函數內部就一句核心代碼,將 const T* 指針轉換成 const Packed*

const Packed* buf = reinterpret_cast<const Packed*>(d_x + offset);

然后用 pack_id 索引一次取一個 pack 的數據,注意這里對 pack 索引的時候不要寫錯了。跟前面一樣,相鄰的線程處理相鄰的 pack 數據,這是為了全局內存的合并訪問。加法計算次數還是那么多次,因為 Packed 結構體并不能直接參與計算,還是要用 elem 里面的元素計算,這個核函數也就節省了訪存次數而已。

4.2 pack 后的塊內規約示例代碼

matrix2DBlockReduceSumPack 核函數的實現就更簡單了,直接上代碼。

template <int pack_size, typename T>
__global__ void matrix2DBlockReduceSumPack(const T* d_x, T* d_y, const uint32_t hidden_units, const uint32_t num_packs) {
  T val = 0.0f;
  uint32_t offset = blockIdx.x * hidden_units;
  const Packed* buf = reinterpret_cast<const Packed*>(d_x + offset);
  Packed pack;
  for (uint32_t pack_id=threadIdx.x; pack_idfor (uint32_t i=0; i(val);
  if (threadIdx.x == 0) {
    d_y[blockIdx.x] = blockSum;
  }
}

template<typename T>
void launchMatrix2DBlockReduceSumPack(const T* d_x, T* d_y, const uint32_t batch_size, const uint32_t hidden_units) {
  uint32_t gird_size = batch_size;
  constexpr uint32_t pack_size = 4;
  assert(hidden_units % pack_size == 0);
  uint32_t num_packs = hidden_units / pack_size;
  matrix2DBlockReduceSumPack<<>>(d_x, d_y, hidden_units, num_packs);
}

5 小結

深度學習算子的開發過程中,規約是一個非常常見的場景,以 Softmax 為例就有 reduceMax 和 reduceSum 的應用,本文給出了兩種規約實現方式,可供讀者參考使用。實際開發過程中,規約計算一般是隱藏在其他 kernel 中的,并不會奢侈到單獨寫個規約 kernel,所以要求開發人員領會思路活學活用。


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

    關注

    88

    文章

    3592

    瀏覽量

    93596
  • 矩陣
    +關注

    關注

    0

    文章

    422

    瀏覽量

    34502
  • 變量
    +關注

    關注

    0

    文章

    613

    瀏覽量

    28329

原文標題:【CUDA編程】束內規約與塊內規約問題

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

收藏 人收藏

    評論

    相關推薦

    配電自動化系統數據采集和遠動規約的研究

    配電自動化系統數據采集和遠動規約的研究摘要:介紹了配電自動化通信結構和通信規約,分析討論配電自動化系統中數據的傳輸特點,論述了遠動規約在配電自動化系統的數據傳輸過程中的應用,并對規約
    發表于 08-08 09:50

    IEC61850-9-2通信規約

    現在網上只有許繼IEC61850-9-2LE版通信規約,大神們誰有IEC62850-9-2的通信規約的資料嗎?
    發表于 04-27 10:36

    嵌入式系統的通信規約管理平臺該怎么設計?

    眾所周知,通信的雙方必須遵守相同的協議,報文才能互相識別。目前,不同行業間的通信協議千差萬別。為解決不同通信協議間的計算機系統通信問題,人們普遍采用的措施是一個具體規約對應一段程序。如果出現新規約
    發表于 09-18 06:55

    水利rtu通信規約

    `  水利rtu通信規約,為保證數據通信系統中通信雙方能有效和可靠地通信而規定的雙方應共同遵守的一系列約定,包括:數據的格式、順序和速率、鏈路管理、流量調節和差錯控制等。  水利rtu通信規約
    發表于 09-07 11:47

    有哪位大佬做過基于單片機的104規約解析嗎

    有大佬做過基于單片機的104規約解析嗎?單片機作為從站,通過104規約與主機通訊。
    發表于 09-05 14:12

    有大佬做過基于單片機的104規約解析嗎?

    有大佬做過基于單片機的104規約解析嗎?單片機作為從站,通過104規約與主機通訊。
    發表于 05-12 15:54

    [電能表規約調試助手]1.0版.

    關于[電能表規約調試助手]1.0版(軟件功能和特色:     1、作為一般的串口調試軟件(不要選中[規約調試])     2、規約調試 完
    發表于 03-28 00:15 ?0次下載

    嵌入式系統的通信規約管理平臺設計

    論述設計通信規約管理平臺的必要性與可行性;借鑒操作系統的PCB 思想,結合面向對象的方法學提出通信規約管理平臺設計的核心思想——用戶填寫靜態規約說明書。規約管理平臺
    發表于 05-15 15:46 ?12次下載

    基于CAN總線的綜自通訊規約設計

    介紹一種基于CAN總線的牽引變電站自動化系統通訊規約的設計,CAN通訊規約采用標準幀,報文采用主動發送和發送查詢兩種處理形式。該設計在城市輕軌與地鐵牽引變電站中的應
    發表于 12-25 16:26 ?39次下載

    101規約通信流程及各功能碼作用

    101通訊規約,電力行業101通信規約,主要介紹了101規約通信流程及各功能碼作用,使用方法等
    發表于 10-28 11:21 ?54次下載

    電力101規約(2002版)報文解析

    電力101規約(2002版)報文解析~~~~
    發表于 01-08 14:39 ?0次下載

    104規約調試和學習小記下載資料

    04規約是廠站與配網主站進行通訊的規約,以以太網為載體,服務模式是平衡傳輸。
    發表于 03-22 16:23 ?7次下載

    軟件的順序語句自動化規約與驗證研究

    算法以及對所規約的語義自動生成證明腳本的算法。利用C++和 Python并通過交互式定理證明器abelle2017在基準數據中隨機選擇10個程序進行測試,結果表明,與完全人工操作相比,該算法具有較高的驗證效率,可實現順序語句的自動化
    發表于 06-03 14:31 ?5次下載

    Modbus RTU通訊規約

    艾德堡HP系列數顯推拉力計_Modbus_RTU規約_HP_通信協議
    發表于 10-20 17:03 ?4次下載

    電力104規約工業智能網關

    計訊物聯電力104規約工業智能網關在實現電力系統全面監控、智能管理方面發揮著不可替代的作用。電力104規約工業智能網關的基本原理電力104規約工業智能網關作為數據傳輸的核心設備,通過將電力系統中
    的頭像 發表于 09-21 14:31 ?952次閱讀
    電力104<b class='flag-5'>規約</b>工業智能網關