最近拜讀了NVIDIA前陣子開源的fastertransformer,對CUDA編程不是很熟悉,但總算是啃下來一些,帶你們讀一下硬核源碼。node
英偉達公衆號推送的文章加上配圖其實已經把該要講的很清楚了,主要有如下幾方面:c++
不瞭解底層的同窗可能不是很懂,沒事我剛看到的時候也不懂,也不敢問,強擼一下源碼就通透(fang qi)了git
首先簡略說一下第一點優化。Kernel在tensorflow裏的概念是operation的計算實現,在cuda裏是執行一個線程的函數,也是一次計算,只不過tensorflow的更加宏觀些。每次tensorflow執行一個operation,都要調用對應的OpKernel,試想一個經過TF實現的transformer,有將近60個operation,計算一次要執行60次上述過程,進行頻繁的GPU調度和顯存讀寫。所以fastertransformer儘量多地對kernel進行了合併。github
Fastertransformer目錄下主要有(如下簡稱FTF):算法
接下來我主要想講一下1.1的細節,1.2能夠參考這位大佬的文章,剩下的代碼可讀性很強,基本讀一兩遍就知道了。編程
除去矩陣乘法,做者把剩下的op合併成了4個(圖中藍色框):異步
這四個op的cuda源碼分別在cuda_kernels.cu和open_attention.cu兩個文件中,接下來研究下每一個op。svg
在FP32時,每一個block負責處理一個word(num_head*head_size)的add bias運算,先找到要處理QKV中的一個,再進行運算,由於要transpose,因此把結果存入[bsz, num_head, seq_len, head_size]的矩陣裏。異步編程
在FP16是每一個block同時處理QKV上的同一個word(多是由於fp16計算的更快一些),在實際的計算中把half都轉成了half2計算。add的話直接用封裝好的__hadd2運算。使用half2計算的緣由原文說的比較清楚:函數
針對半精度FP16,咱們對各個kernel也進行了相應優化。首先,在kernel的實現中,將輸入的half指針轉成half2類型,並使用了half2相關的數學函數。這樣不只僅能夠達到2倍於half的訪存帶寬和計算吞吐,還能夠極大地減小指令的發射數量。其次,在SoftMax以及Layer Normalization的操做中,爲防止求和溢出,將數據以half2的形式讀入後,會轉成float2類型,來作求和計算。
-- NVIDIA BERT推理解決方案Faster Transformer開源啦
在計算softmax以前對block線程數進行了區間處理,由於block裏的線程數最好是wrap大小(32)的倍數,提升計算效率。
調用kernel以前,會根據batch_size * head_num選擇不一樣的softmax kernel,主要是爲了保證在大batch的狀況下的計算效率,這裏爲何使用120我也不是很清楚,但願懂的朋友助力一下
if(batch_size * head_num <= 120)
{
grid.x = batch_size * head_num * seq_len;
softmax_kernel_v2<DataType_><<<grid, block, 0, stream>>>(qk_buf_, attr_mask, batch_size, head_num, seq_len, scaler);
}
else
{
grid.x = batch_size * head_num;
softmax_kernel<DataType_><<<grid, block, 0, stream>>>(qk_buf_, attr_mask, batch_size, head_num, seq_len, scaler);
}複製代碼
在算softmax時,分母有個求和操做,用到了經典的parallel reduce算法,能夠仔細看看參考,講的比較清楚。
這裏注意,使用最第一版源碼的同窗們須要照着實現一個blockReduceMax,以防止數值溢出,softmax嚴謹的實現應該是:
def softmax(x):
"""Compute the softmax of vector x."""
exp_x = np.exp(x)
softmax_x = exp_x / np.sum(exp_x)
return softmax_x複製代碼
這裏要transpose回[bsz, seq_len, num_head, head_size]的矩陣。由於c++裏面矩陣是行優先存儲,只要按順序乘過來就行了(最開始看的有點暈)。
若是前面幾個函數啃下來了,這兩個就比較好懂,主要的優化點是:
矩陣乘法根據fp16和fp32的不一樣在不一樣的cublas算法中選擇,選擇後記錄到http://gemm_config.in文件中:
問了下做者,其實fp32也可使用CUBLAS_GEMM_ALGO0_TENSOR_OP到CUBLAS_GEMM_ALGO15_TENSOR_OP的算法,只不過存在一些風險(使用後速度提高2倍)。
做者額外封裝了一個tensorRT的層,tensorRT主要是經過engine,在給定的context和stream下進行異步計算,提供了multi stream inference的可能。關於TensorRT的異步編程推薦一個英偉達的PPT:
CUDA C/C++ Streams and Concurrency developer.download.nvidia.cn這篇文章寫做週期比較長,主要是源碼比較硬核,邊看邊學cuda和c++,到如今也就懂了80%左右吧。不過fastertransformer是真的香,並且直接用tensorflow也很方便,各位須要inference的朋友們必定要用呀