女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

OneFlow Softmax算子源碼解讀之BlockSoftmax

jf_pmFSk4VX ? 來源:后來遇見AI ? 2024-01-08 09:26 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

寫在前面:筆者這段時(shí)間工作太忙,身心俱疲,博客停更了一段時(shí)間,現(xiàn)在重新?lián)炱饋怼1疚闹饕庾x OneFlow 框架的第二種 Softmax 源碼實(shí)現(xiàn)細(xì)節(jié),即 block 級(jí)別的 Softmax。

1 整體邏輯

我們知道,對(duì)于形狀為 (num_rows, num_cols) 的矩陣來說,其 Softmax 計(jì)算結(jié)果只與當(dāng)前元素所在行的元素相關(guān),所以實(shí)現(xiàn) cuda kernel 的關(guān)鍵就是采用多大維度的線程組來處理一行元素。BlockSoftmax 的核心思想是使用一個(gè) block 處理一行元素的計(jì)算,借助共享內(nèi)存保存中間結(jié)果數(shù)據(jù)以及進(jìn)行線程間的通信。有興趣的讀者可以去如下地址閱讀源碼:https://github.com/Oneflow-Inc/oneflow/blob/master/oneflow/core/cuda/softmax.cuh
線程網(wǎng)絡(luò)結(jié)構(gòu)如下:

5dac33ac-ad3c-11ee-8b88-92fbcf53809c.png

2 源碼解析

2.1 數(shù)據(jù) Pack 提升訪問帶寬

數(shù)據(jù) pack 方面筆者在上一篇文章已經(jīng)詳細(xì)介紹,主要目的是提升內(nèi)存訪問帶寬,這里不再贅述。

2.2 調(diào)用鏈

針對(duì) BlockSMemSoftmax 這個(gè)分支,筆者對(duì)源碼中函數(shù)的調(diào)用關(guān)系梳理后整理如下:

TryDispatchSoftmaxBlockSMemImpl
  ->TryDispatchSoftmaxBlockSMemImplPackSize
    ->TryDispatchSoftmaxBlockSMemImplBlockSize
      ->LaunchSoftmaxBlockSMemImpl
        ->SoftmaxBlockSMemImpl(kernel)

接下來筆者將從上到下逐個(gè)解讀其實(shí)現(xiàn)細(xì)節(jié)。

2.3 TryDispatchSoftmaxBlockSMemImpl

該函數(shù)被 DispatchSoftmax 函數(shù)調(diào)用,其內(nèi)部邏輯非常簡(jiǎn)單,實(shí)例化了一個(gè) TryDispatchSoftmaxBlockSMemImplPackSize 類并調(diào)用了其重載的()運(yùn)算符函數(shù),所有的參數(shù)都是透?jìng)鳎瑳]有其他邏輯。

template
inline cudaError_t TryDispatchSoftmaxBlockSMemImpl(cudaStream_t stream, LOAD load, STORE store,
                                                   const int64_t rows, const int64_t cols,
                                                   bool* success) {
  return TryDispatchSoftmaxBlockSMemImplPackSize()(
      stream, load, store, rows, cols, success);
}

2.4 TryDispatchSoftmaxBlockSMemImplPackSize

顧名思義,pack_size 參數(shù)是在這個(gè)結(jié)構(gòu)體內(nèi)部確定的。該結(jié)構(gòu)體內(nèi)部重載了一個(gè)小括號(hào)運(yùn)算符,其函數(shù)內(nèi)部只做了一件事,對(duì)矩陣的列數(shù)進(jìn)行判斷從而確定 pack_size,如果矩陣列數(shù)是偶數(shù),pack_size 取 2,否則取 1。

template
struct TryDispatchSoftmaxBlockSMemImplPackSize {
  cudaError_t operator()(cudaStream_t stream, LOAD load, STORE store, const int64_t rows,
                         const int64_t cols, bool* success) {
    if (cols % 2 == 0) {
      return TryDispatchSoftmaxBlockSMemImplBlockSize(
          stream, load, store, rows, cols, success);
    } else {
      return TryDispatchSoftmaxBlockSMemImplBlockSize(
          stream, load, store, rows, cols, success);
    }
  }
};

2.5 TryDispatchSoftmaxBlockSMemImplBlockSize

顧名思義,block_size 參數(shù)是在該函數(shù)內(nèi)部確定的。關(guān)于 block_size 參數(shù)的確定方法筆者在另一篇文章(【CUDA編程】OneFlow Element-Wise 算子源碼解讀)中有詳細(xì)介紹,因?yàn)?blockSMemSoftmax 方案主要使用共享內(nèi)存而不是寄存器,所以這里我們?nèi)∠拇嫫飨拗疲梢缘玫?block_size 參數(shù)在理想情況下應(yīng)在 128 和 1024 之間。因此函數(shù) TryDispatchSoftmaxBlockSMemImplBlockSize 中分別定義了 4 個(gè)變量,對(duì)應(yīng) 4 種情況。

// 設(shè)置4個(gè)不同的block_size
constexpr int block_size_conf_1 = 128;
constexpr int block_size_conf_2 = 256;
constexpr int block_size_conf_3 = 512;
constexpr int block_size_conf_4 = 1024;

2.5.1 SM 占有率限制

我們知道,block 是運(yùn)行在 SM 上的,筆者在前面的文章說過,單個(gè) SM 所能承載的最大 block 數(shù)和最大 thread 數(shù)是有上限的,為了保證單個(gè) SM 被 thread 填滿(即 SM 占用率 100%),我們要求 block_size 最小取 128。

2.5.2 線程塊同步機(jī)制限制

我們知道單個(gè) SM 上的線程總量等于單個(gè) SM 上的 block 數(shù)量乘以 block_size,所以在確定 block_size 前我們不妨思考一個(gè)問題:?jiǎn)蝹€(gè) SM 上 block 的數(shù)量越多越好還是越少越好?
當(dāng)一個(gè) block 內(nèi)的線程共同完成一項(xiàng)計(jì)算任務(wù)時(shí),通常 block 內(nèi)線程要做同步防止出現(xiàn)讀寫競(jìng)爭(zhēng)問題。極端情況下,我們假設(shè)單個(gè) SM 上只有一個(gè) block,當(dāng) SM 中正在調(diào)度執(zhí)行的一個(gè) block 到達(dá)同步點(diǎn)時(shí),SM 內(nèi)可執(zhí)行 warp 將逐漸減少至 0,會(huì)導(dǎo)致計(jì)算資源空閑,相當(dāng)于整個(gè) SM 在等待剩余的 warp 逐步執(zhí)行,造成資源浪費(fèi)。若此時(shí) SM 上同時(shí)有其他 block 在執(zhí)行,則在一個(gè) block 到達(dá)同步點(diǎn)時(shí)仍然有其他 block 可以執(zhí)行。所以從這個(gè)層面上來說,單個(gè) SM 可同時(shí)調(diào)度的 block 越多越好,單個(gè) SM 上 block 數(shù)量越多的同時(shí),block_size 越小。

2.5.3 cudaOccupancyMaxActiveBlocksPerMultiprocessor 函數(shù)

前面說過單個(gè) SM 上同時(shí)調(diào)度的 block 數(shù)量越多越好,那么我們?nèi)绾稳〉竭@個(gè)最大的 block 數(shù)量?首先直接取官方給出的單個(gè) SM 可承載的 block 數(shù)上限值肯定是不行的,這只是一個(gè)理論上限,實(shí)際未必能保證這些 block 被同時(shí)調(diào)度,同時(shí)調(diào)度的影響因素還有:共享內(nèi)存、CUDA 函數(shù)、block_size 等等。這里 Nvidia 官方提供了一個(gè)預(yù)估函數(shù) cudaOccupancyMaxActiveBlocksPerMultiprocessor,該函數(shù)會(huì)根據(jù)設(shè)備的硬件資源限制,例如 SM 上的計(jì)算核心數(shù)量、共享內(nèi)存的大小等,來確定可調(diào)度塊的數(shù)量。隨后,它會(huì)計(jì)算出特定內(nèi)核函數(shù)中每個(gè)線程塊的負(fù)載平衡,并選出在給定硬件約束下可調(diào)度線程塊數(shù)量的最大值。最后,該函數(shù)將返回一個(gè)值,表示在當(dāng)前硬件資源限制下每個(gè) SM 可允許的最大線程塊數(shù)量,這個(gè)值是一個(gè)整數(shù)。該函數(shù)的返回結(jié)果可以用來估算程序并行效率,幫助開發(fā)人員優(yōu)化程序以使在 GPU 上運(yùn)行更有效率。

2.5.4 代碼邏輯

終上所述,如何選取最合適的 block_size 參數(shù)呢?首先 SM 能同時(shí)調(diào)度的 block 數(shù)量越大越好;當(dāng) SM 能同時(shí)調(diào)度的 Block 數(shù)不變的情況下,block_size 越大越好,越大就有越高的并行度。因此代碼中在選擇 block_size 時(shí),對(duì)不同 block_size 都計(jì)算了 cudaOccupancyMaxActiveBlocksPerMultiprocessor,若結(jié)果相同,使用較大的 block_size
簡(jiǎn)單來說,優(yōu)先讓 SM 同時(shí)調(diào)度的 block 數(shù)量達(dá)到最大,其次讓 block_size 達(dá)到最大。

template
inline cudaError_t TryDispatchSoftmaxBlockSMemImplBlockSize(cudaStream_t stream, LOAD load,
                                                            STORE store, const int64_t rows,
                                                            const int64_t cols, bool* success) {
  // 設(shè)置4個(gè)不同的block_size
  constexpr int block_size_conf_1 = 128;
  constexpr int block_size_conf_2 = 256;
  constexpr int block_size_conf_3 = 512;
  constexpr int block_size_conf_4 = 1024;
  // 計(jì)算blockSoftmax方案需要的共享內(nèi)存大小
  const size_t smem = cols * sizeof(ComputeType);
  int max_active_blocks_conf_1;
  {
    // 占用計(jì)算器API cudaOccupancyMaxActiveBlocksPerMultiprocessor可以根據(jù) kernel 的 block 大小和共享內(nèi)存使用情況提供占用率預(yù)測(cè)。
    cudaError_t err = cudaOccupancyMaxActiveBlocksPerMultiprocessor(
        &max_active_blocks_conf_1,
        SoftmaxBlockSMemImpl,
        block_size_conf_1, smem);
    if (err != cudaSuccess) { return err; }
  }
  if (max_active_blocks_conf_1 <= 0) {
    *success = false;
    return cudaSuccess;
  }
  int max_active_blocks_conf_4;
  {
    cudaError_t err = cudaOccupancyMaxActiveBlocksPerMultiprocessor(
        &max_active_blocks_conf_4,
        SoftmaxBlockSMemImpl,
        block_size_conf_4, smem);
    if (err != cudaSuccess) { return err; }
  }
  if (max_active_blocks_conf_4 == max_active_blocks_conf_1) {
    *success = true;
    return LaunchSoftmaxBlockSMemImpl(stream, load, store, smem, rows, cols);
  }
  int max_active_blocks_conf_3;
  {
    cudaError_t err = cudaOccupancyMaxActiveBlocksPerMultiprocessor(
        &max_active_blocks_conf_3,
        SoftmaxBlockSMemImpl,
        block_size_conf_3, smem);
    if (err != cudaSuccess) { return err; }
  }
  if (max_active_blocks_conf_3 == max_active_blocks_conf_1) {
    *success = true;
    return LaunchSoftmaxBlockSMemImpl(stream, load, store, smem, rows, cols);
  }
  int max_active_blocks_conf_2;
  {
    cudaError_t err = cudaOccupancyMaxActiveBlocksPerMultiprocessor(
        &max_active_blocks_conf_2,
        SoftmaxBlockSMemImpl,
        block_size_conf_2, smem);
    if (err != cudaSuccess) { return err; }
  }
  if (max_active_blocks_conf_2 == max_active_blocks_conf_1) {
    *success = true;
    return LaunchSoftmaxBlockSMemImpl(stream, load, store, smem, rows, cols);
  }
  *success = true;
  return LaunchSoftmaxBlockSMemImpl(stream, load, store, smem, rows, cols);
}

源碼中首先計(jì)算了 block_size = 128 時(shí)的 SM 同時(shí)調(diào)度的 block 數(shù)量 max_active_blocks_conf_1,并以此作為 SM 同時(shí)調(diào)度的最大 block 數(shù)量,然后分別計(jì)算其他三種 block_size 的 max_active_blocks_conf 如果等于最大的 block 數(shù)量,則取較大的 block_size。

2.6 核函數(shù) SoftmaxBlockSMemImpl

接下來就是 BlockSoftmax 的核函數(shù) SoftmaxBlockSMemImpl,先來看一下代碼,接下來筆者將逐步解讀源碼作者的實(shí)現(xiàn)意圖。

template
__global__ void SoftmaxBlockSMemImpl(LOAD load, STORE store, const int64_t rows,
                                     const int64_t cols) {
  extern __shared__ __align__(sizeof(double)) unsigned char shared_buf[];
  auto* buf = reinterpret_cast(shared_buf);
  const int tid = threadIdx.x;
  assert(cols % pack_size == 0);
  const int num_packs = cols / pack_size;
  // 一個(gè) Block 處理一行元素
  for (int64_t row = blockIdx.x; row < rows; row += gridDim.x) {
    // 當(dāng)前線程的最大值初始化為 -inf
    ComputeType thread_max = -Inf();
    // 以向量化的方式加載一行數(shù)據(jù),然后執(zhí)行pack reduce操作
    for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {
      ComputeType pack[pack_size];
      load.template load(pack, row, pack_id * pack_size);
#pragma unroll
      for (int i = 0; i < pack_size; ++i) {
        buf[i * num_packs + pack_id] = pack[i];
        thread_max = max(thread_max, pack[i]);
      }
    }
    // 執(zhí)行block reduce獲取當(dāng)前行(由一個(gè) Block 進(jìn)行處理)的最大值
    const ComputeType row_max = BlockAllReduce(thread_max);
    ComputeType thread_sum = 0;
    for (int col = tid; col < cols; col += block_size) {
      if (algorithm == Algorithm::kSoftmax) {
        const ComputeType exp_x = Exp(buf[col] - row_max);
        buf[col] = exp_x;
        thread_sum += exp_x;
      } else {
        const ComputeType x = buf[col] - row_max;
        buf[col] = x;
        thread_sum += Exp(x);
      }
    }
    // 同理,獲得當(dāng)前行的sum
    const ComputeType row_sum = BlockAllReduce(thread_sum);
    // 計(jì)算結(jié)果并寫回到全局內(nèi)存中
    for (int pack_id = tid; pack_id < num_packs; pack_id += block_size) {
      ComputeType pack[pack_size];
#pragma unroll
      for (int i = 0; i < pack_size; ++i) {
        if (algorithm == Algorithm::kSoftmax) {
          pack[i] = Div(buf[i * num_packs + pack_id], row_sum);
        } else if (algorithm == Algorithm::kLogSoftmax) {
          pack[i] = buf[i * num_packs + pack_id] - Log(row_sum);
        } else {
          __trap();
        }
      }
      store.template store(pack, row, pack_id * pack_size);
    }
  }
}

2.6.1 定義共享內(nèi)存變量

核函數(shù)內(nèi)部首先定義了一個(gè)共享內(nèi)存數(shù)組變量 shared_buf,內(nèi)存對(duì)齊大小為 sizeof(double),隨后在核函數(shù)內(nèi)部做了一個(gè)斷言,校驗(yàn) pack_size 是否能夠整除 cols。在 shared_buf 變量定義語句中,extern 是使用動(dòng)態(tài)共享內(nèi)存的一種聲明方式,表示內(nèi)存大小將在調(diào)用核函數(shù)時(shí)通過 <<<>>> 的第三個(gè)參數(shù)指定,這里為矩陣的一行元素對(duì)應(yīng)的內(nèi)存大小,即 cols * sizeof(ComputeType)。然后使用 reinterpret_cast 將 shared_buf 的首地址轉(zhuǎn)換為 ComputeType* 類型的指針并賦給指針 buf。這段代碼的意義是將共享內(nèi)存 shared_buf 的首地址強(qiáng)制轉(zhuǎn)換為ComputeType 類型的指針,使得在后續(xù)代碼中可以通過 buf 來訪問共享內(nèi)存,進(jìn)行后續(xù)的 GPU 并行計(jì)算操作。這里筆者沒想明白為什么要繞個(gè)圈子,而不是直接用如下代碼直接定義。

extern __shared__ __align__(sizeof(double)) ComputeType buf[];

咨詢?cè)创a作者后收到反饋,“如果采用上述方式定義會(huì)編譯報(bào)錯(cuò)”,然后筆者親測(cè)沒有報(bào)錯(cuò),可能是兩邊開發(fā)環(huán)境不同導(dǎo)致,有興趣地讀者可以嘗試下,筆者的環(huán)境為 win10+cuda11.7+rtx2070super。

2.6.2 計(jì)算每個(gè)線程的 thread_max

主體部分是一個(gè) Grip-loop 的循環(huán),循環(huán)步長(zhǎng)設(shè)置為網(wǎng)格大小。Grip-loop 內(nèi)部定義了一個(gè)寄存器變量 thread_max,這個(gè)變量用來存儲(chǔ)當(dāng)前線程處理的元素中的最大值。
接下來是兩層循環(huán)求出 thread_max,這里和 WarpSoftmax 一樣,一個(gè)線程處理的 pack 是不相連的,這里主要是因?yàn)?GPU 在從內(nèi)存取數(shù)的時(shí)候,為了優(yōu)化內(nèi)存訪問性能,一次取數(shù)時(shí)會(huì)將臨近空間的數(shù)據(jù)也取出存入緩存,而 GPU 指令是按照線程束為基本單位進(jìn)行執(zhí)行的,這樣的話,一次取數(shù)可以滿足相鄰的多個(gè)線程的使用需求,直接去緩存取即可,無需再次訪問內(nèi)存。因此為了最大程度提高訪問效率,相鄰線程訪問的數(shù)據(jù)是緊鄰的。在循環(huán)體內(nèi)部定義了一個(gè)寄存器數(shù)組變量 pack[pack_size],將內(nèi)存中一個(gè) pack 的數(shù)據(jù)加載到數(shù)組 pack 中,然后在 pack 內(nèi)求 thread_max,順便將數(shù)據(jù)也加載到共享內(nèi)存變量 buf 中。

2.6.3 Bank Conflicts 優(yōu)化

從往 buf 里加載數(shù)據(jù)的代碼中可以發(fā)現(xiàn),在 buf 中內(nèi)存排列有些反常,一個(gè) pack 內(nèi)的元素不是相鄰排列的,而是隔了 num_packs 個(gè)元素,這里可以把 buf 想象成一個(gè) pack_size 行 num_packs 列的矩陣,一個(gè) pack 內(nèi)的元素是按列存儲(chǔ)的。為什么要按列往共享內(nèi)存里寫入數(shù)據(jù)?
這里涉及一個(gè)Bank Conflicts 概念,這也是在使用共享內(nèi)存時(shí)需要重點(diǎn)關(guān)注的問題。為了獲得更高的內(nèi)存帶寬,共享內(nèi)存在物理上被分為了 32 個(gè)寬度相等(開普勒架構(gòu)為8個(gè)字節(jié),其他架構(gòu)4個(gè)字節(jié))的 bank,這些 bank 可以被同時(shí)訪問。為什么恰好是 32 個(gè)?因?yàn)榍懊嬲f過 GPU 中執(zhí)行指令是以線程束(32個(gè)線程)為基本單位的,這樣可以保證每個(gè)線程同時(shí)訪問一個(gè) bank 的數(shù)據(jù),帶寬達(dá)到最大。那么 Bank Conflicts 的定義來了,在一個(gè) warp 內(nèi),有 2 個(gè)或以上的線程訪問了同一個(gè) bank 上的不同地址的內(nèi)存
現(xiàn)在我們假設(shè) buf 里使用如下方式加載數(shù)據(jù),即一個(gè) pack 里的元素相鄰排列,則代碼如下:

#pragma unroll
      for (int i = 0; i < pack_size; ++i) {
        buf[pack_id * pack_size + i] = pack[i];
        thread_max = max(thread_max, pack[i]);
      }

當(dāng) pack_size = 1 時(shí),每個(gè)線程連續(xù)寫 4 個(gè)字節(jié)時(shí),每個(gè) warp 剛好完整訪問 shared memory 的一行,這個(gè)時(shí)候并不會(huì)出現(xiàn) bank conflict。而當(dāng) pack_size = 2 時(shí),每個(gè)線程寫連續(xù) 2 個(gè) 4 字節(jié)時(shí)(可以看成8個(gè)字節(jié)),此時(shí) 0 號(hào)線程訪問的地址在第 0 和第 1 個(gè) bank,1 號(hào)線程訪問的地址在第 2 和第 3 個(gè) bank,以此類推,16 號(hào)線程訪問地址又在第 0 和第 1 個(gè) bank 內(nèi),此時(shí) 16 號(hào)線程和 0 號(hào)線程訪問了同一個(gè) bank 的不同地址,此時(shí)即產(chǎn)生了 Bank Conflicts。見圖a。
為了避免 Bank Conflicts,可以將一個(gè) pack 的數(shù)據(jù)按列存儲(chǔ),這樣當(dāng)矩陣元素的字節(jié)大于 4 或 pack_size >= 2 時(shí),可以保證每個(gè)線程訪問的數(shù)據(jù)都在自己的 bank 內(nèi),如圖b。

5dc7772a-ad3c-11ee-8b88-92fbcf53809c.png

2.6.4 block reduce 獲取 row_max

得到每一個(gè)線程處理的元素的最大值后,還需要計(jì)算每一行的最大值。在 WarpSoftmax 中,由于每一行是由一個(gè) warp 處理的,所以我們使用束內(nèi)洗牌指令即可得到矩陣單行最大值,而在 BlockSoftmax 中,每一行是由一個(gè) block 處理的,這時(shí)候不能再使用束內(nèi)指令來規(guī)約了,為了保證較高的性能,應(yīng)使用共享內(nèi)存進(jìn)行線程間的數(shù)據(jù)通信。這里源碼封裝了一個(gè) BlockAllReduce(thread_max) 函數(shù),在函數(shù)內(nèi)直接使用 Nvidia 官方 cub 庫進(jìn)行計(jì)算,官方文檔見:https://nvlabs.github.io/cub/classcub_1_1_block_reduce.html#a089953b3bdfe7c48208632d0cc2ac1fb

const ComputeType row_max = BlockAllReduce(thread_max);

函數(shù)體中聲明了兩個(gè)共享內(nèi)存變量 temp_storage 和 result_broadcast,可見該庫函數(shù)底層也是通過共享內(nèi)存實(shí)現(xiàn)的,這里如果不用官方庫,也可以自定義規(guī)約函數(shù),有興趣的讀者可以參考筆者的另一篇文章【CUDA編程】CUDA編程中的并行規(guī)約問題

2.6.5 thread_sum 和 row_sum

定義一個(gè)寄存器變量 thread_sum 存儲(chǔ)當(dāng)前線程處理的元素的指數(shù)和。由于矩陣元素已經(jīng)加載進(jìn)共享內(nèi)存 buf 中,所以這一次遍歷求和不需要再訪問全局內(nèi)存,直接在 buf 中以 block_size 為步長(zhǎng)求和即可,遍歷的同時(shí)也將 buf 的內(nèi)存替換成 exp_x 這是為了方便后續(xù)的計(jì)算。這里為什么要以 block_size 為步長(zhǎng)?也是因?yàn)榍懊鏋榱吮苊?bank conflicts 將 buf 的內(nèi)存排列設(shè)定為相鄰線程的元素相鄰存儲(chǔ),所以我們只要以 block_size 為步長(zhǎng)遍歷即可完成該線程處理的所有 pack 元素的遍歷。
獲取到 thread_sum 后,同樣使用前面 BlockAllReduce 函數(shù)進(jìn)行塊內(nèi)規(guī)約計(jì)算出 row_sum。

2.6.6 計(jì)算 Softmax

首先對(duì)當(dāng)前線程以 block_size 為步長(zhǎng)做一次循環(huán),然后在 pack 內(nèi)利用上一步計(jì)算的 row_max 和 buf 計(jì)算出 Softmax 值,最后將 pack 內(nèi)的計(jì)算結(jié)果寫入全局內(nèi)存中。

3 小結(jié)

總結(jié)一下 BlockSoftmax 源碼中的一些值得學(xué)習(xí)的內(nèi)容:

在選取 block_size 時(shí),要結(jié)合 kernel 的計(jì)算邏輯,靈活選取。比如作者在實(shí)現(xiàn) BlockSoftmax 時(shí)考慮到這種實(shí)現(xiàn)方式不會(huì)大量使用寄存器,所以去除了寄存器限制;考慮到塊內(nèi)同步和共享內(nèi)存,所以使用了官方庫函數(shù)預(yù)估 SM 占用率。這些都是很值得讀者學(xué)習(xí)的,兵無常勢(shì),水無常形。

BlockSoftmax 的核心是塊內(nèi)規(guī)約,利用共享內(nèi)存讀寫速度遠(yuǎn)大于全局內(nèi)存的特性,提高 kernel 性能。

在使用共享內(nèi)存時(shí),一定要避免 Bank Conflicts,可以提升至少 20% 的訪存效率。

審核編輯:湯梓紅

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3122

    瀏覽量

    75251
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    671

    瀏覽量

    30321
  • OneFlow
    +關(guān)注

    關(guān)注

    0

    文章

    9

    瀏覽量

    8927

原文標(biāo)題:【CUDA編程】OneFlow Softmax算子源碼解讀之BlockSoftmax

文章出處:【微信號(hào):GiantPandaCV,微信公眾號(hào):GiantPandaCV】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    OneFlow Softmax算子源碼解讀WarpSoftmax

    寫在前面:近來筆者偶然間接觸了一個(gè)深度學(xué)習(xí)框架 OneFlow,所以這段時(shí)間主要在閱讀 OneFlow 框架的 cuda 源碼。官方源碼基于不同場(chǎng)景分三種方式實(shí)現(xiàn)
    的頭像 發(fā)表于 01-08 09:24 ?1257次閱讀
    <b class='flag-5'>OneFlow</b> <b class='flag-5'>Softmax</b><b class='flag-5'>算子</b><b class='flag-5'>源碼</b><b class='flag-5'>解讀</b><b class='flag-5'>之</b>WarpSoftmax

    caffe源碼解讀《九》softmax

    編程語言行業(yè)芯事經(jīng)驗(yàn)分享
    蒙特卡洛家的樹
    發(fā)布于 :2022年03月09日 15:34:58

    TensorFlow、PyTorch,“后浪”OneFlow 有沒有機(jī)會(huì)

    TensorFlow、PyTorch,“后浪”OneFlow 有沒有機(jī)會(huì) | 一流科技工程師成誠(chéng)編者按:7月31日,一流科技在創(chuàng)業(yè)1300天后,他們宣布開源自研的深度學(xué)習(xí)框架OneFlow,此前,CSDN對(duì)CEO袁進(jìn)輝進(jìn)行了專訪。本文中,一流科技工程師成...
    發(fā)表于 07-27 08:24

    機(jī)器學(xué)習(xí)的Softmax定義和優(yōu)點(diǎn)

    Softmax在機(jī)器學(xué)習(xí)中有非常廣泛的應(yīng)用,但是剛剛接觸機(jī)器學(xué)習(xí)的人可能對(duì)Softmax的特點(diǎn)以及好處并不理解,其實(shí)你了解了以后就會(huì)發(fā)現(xiàn),Softmax計(jì)算簡(jiǎn)單,效果顯著,非常好用。
    的頭像 發(fā)表于 03-15 17:18 ?5038次閱讀
    機(jī)器學(xué)習(xí)的<b class='flag-5'>Softmax</b>定義和優(yōu)點(diǎn)

    使用Softmax的信息來教學(xué) —— 知識(shí)蒸餾

    當(dāng)處理一個(gè)分類問題時(shí),使用softmax作為神經(jīng)網(wǎng)絡(luò)的最后一個(gè)激活單元是非常典型的用法。這是為什么呢?因?yàn)?b class='flag-5'>softmax函數(shù)接受一組logit為輸入并輸出離散類別上的概率分布。
    的頭像 發(fā)表于 10-10 10:23 ?2370次閱讀

    基于EAIDK的人臉?biāo)惴☉?yīng)用-源碼解讀(2)

    上一期介紹了基于EAIDK的人臉?biāo)惴☉?yīng)用,本期從應(yīng)用角度,解讀一下該案例源碼。本期案例源碼解讀,主要從源碼目錄結(jié)構(gòu)、配置文件、模型目...
    的頭像 發(fā)表于 12-10 21:14 ?1348次閱讀

    開源軟件-OneFlow通用深度學(xué)習(xí)框架

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

    Sobel算子原理介紹與實(shí)現(xiàn)方法

    索貝爾算子(Sobel operator)主要用作邊緣檢測(cè),在技術(shù)上,它是一離散性差分算子,用來運(yùn)算圖像亮度函數(shù)的灰度近似值。在圖像的任何一點(diǎn)使用此算子,將會(huì)產(chǎn)生對(duì)應(yīng)的灰度矢量或是其
    的頭像 發(fā)表于 07-21 17:27 ?1.4w次閱讀

    flowflops:OneFlow模型的Flops計(jì)算

    用于計(jì)算 OneFlow 模型的 FLOPs 和 Parameters 的第三方庫。
    的頭像 發(fā)表于 11-16 10:04 ?1597次閱讀

    解析OneFlow Element-Wise算子實(shí)現(xiàn)方法

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

    解析OneFlow BatchNorm相關(guān)算子實(shí)現(xiàn)

    可以看到 CUDNN_BATCHNORM_PER_ACTIVATION 被用于非卷積層,在OneFlow中只有當(dāng)輸入Tensor的維度為2時(shí)才選取這種模式。而
    的頭像 發(fā)表于 12-23 15:08 ?961次閱讀

    深度學(xué)習(xí)編譯器Layerout Transform優(yōu)化

    繼續(xù)深度學(xué)習(xí)編譯器的優(yōu)化工作解讀,本篇文章要介紹的是OneFlow系統(tǒng)中如何基于MLIR實(shí)現(xiàn)Layerout Transform。
    的頭像 發(fā)表于 05-18 17:32 ?1067次閱讀

    PyTorch教程4.1Softmax回歸

    電子發(fā)燒友網(wǎng)站提供《PyTorch教程4.1Softmax回歸.pdf》資料免費(fèi)下載
    發(fā)表于 06-05 15:46 ?0次下載
    PyTorch教程4.1<b class='flag-5'>之</b><b class='flag-5'>Softmax</b>回歸

    PyTorch教程4.4從頭開始實(shí)現(xiàn)Softmax回歸

    電子發(fā)燒友網(wǎng)站提供《PyTorch教程4.4從頭開始實(shí)現(xiàn)Softmax回歸.pdf》資料免費(fèi)下載
    發(fā)表于 06-05 15:37 ?0次下載
    PyTorch教程4.4<b class='flag-5'>之</b>從頭開始實(shí)現(xiàn)<b class='flag-5'>Softmax</b>回歸

    使用LabVIEW人工智能視覺工具包快速實(shí)現(xiàn)傳統(tǒng)Opencv算子的調(diào)用源碼

    電子發(fā)燒友網(wǎng)站提供《使用LabVIEW人工智能視覺工具包快速實(shí)現(xiàn)傳統(tǒng)Opencv算子的調(diào)用源碼.rar》資料免費(fèi)下載
    發(fā)表于 09-28 17:38 ?14次下載