1. 程式人生 > 實用技巧 >在NVIDIA(CUDA,CUBLAS)和Intel MKL上快速實現BERT推理

在NVIDIA(CUDA,CUBLAS)和Intel MKL上快速實現BERT推理

在NVIDIA(CUDA,CUBLAS)和Intel MKL上快速實現BERT推理

直接在NVIDIA(CUDA,CUBLAS)或Intel MKL上進行高度定製和優化的BERT推理,而無需tensorflow及其框架開銷。

僅支援BERT(轉換器)。

基準測試

環境

  • Tesla P4
  • 28 * Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz
  • Debian GNU/Linux 8 (jessie)
  • gcc (Debian 4.9.2-10+deb8u1) 4.9.2
  • CUDA: release 9.0, V9.0.176
  • MKL: 2019.0.1.20181227
  • tensorflow: 1.12.0
  • BERT: seq_length = 32

注意:應該在下面執行MKLOMP_NUM_THREADS=?來控制其執行緒號。其他環境變數及其可能的值包括:

  • KMP_BLOCKTIME=0
  • KMP_AFFINITY=granularity=fine,verbose,compact,1,0

混合精度

NVIDIA Volta和Turing GPU上的Tensor CoreMixed Precision可以加速cuBERT。支援混合精度作為儲存在fp16中的變數,並在fp32中進行計算。與單精度推理相比,典型的精度誤差小於1%,而速度則達到了2倍以上的加速度。

API

API .h標頭

Pooler

支援以下2種pooling方法。

  • 標準BERT pooler,定義為:

with tf.variable_scope("pooler"):

# We "pool" the model by simply taking the hidden state corresponding

# to the first token. We assume that this has been pre-trained

first_token_tensor = tf.squeeze(self.sequence_output[:, 0:1, :], axis=1)

self.pooled_output = tf.layers.dense(

first_token_tensor,

config.hidden_size,

activation=tf.tanh,

kernel_initializer=create_initializer(config.initializer_range))

  • Simple average pooler:

self.pooled_output = tf.reduce_mean(self.sequence_output, axis=1)

輸出量

支援以下輸出:

從源構建Build from Source

mkdir build && cd build

# if build with CUDA

cmake -DCMAKE_BUILD_TYPE=Release -DcuBERT_ENABLE_GPU=ON -DCUDA_ARCH_NAME=Common ..

# or build with MKL

cmake -DCMAKE_BUILD_TYPE=Release -DcuBERT_ENABLE_MKL_SUPPORT=ON ..

make -j4

# install to /usr/local

# it will also install MKL if -DcuBERT_ENABLE_MKL_SUPPORT=ON

sudo make install

也將安裝MKL

如果想執行tfBERT_benchmark進行效能比較,首先從https://www.tensorflow.org/install/lang_c安裝tensorflow C API。

執行單元測試

Dropbox下載BERT測試模型bert_frozen_seq32.pb和vocab.txt,然後將它們放在dir下build,執行make test or ./cuBERT_test

thread

Cython提供的簡單Python包裝器,可以按如下所示在C ++構建之後構建和安裝:

cd python

python setup.py bdist_wheel

# install

pip install dist/cuBERT-xxx.whl

# test

python cuBERT_test.py

cuBERT_test.py中檢查Python API的用法和示例,獲取更多詳細資訊。

Java

Java包裝器是通過JNA實現的。安裝maven和C ++構建後,可以按以下步驟構建:

cd java

mvn clean package # -DskipTests

當使用Java JAR,需要指定jna.library.path的位置libcuBERT.so,如果沒有安裝到系統路徑。並且jna.encoding應像-Djna.encoding=UTF8JVM啟動指令碼中一樣設定為UTF8。

ModelTest.java中檢查Java API的用法和示例,獲取更多詳細資訊。

安裝

可以按以下方式安裝預構建的python二進位制軟體包(當前僅在Linux上與MKL一起安裝):

  • 下載MKL並將其安裝到系統路徑。
  • 下載wheel package包,pip install cuBERT-xxx-linux_x86_64.whl
  • 執行python -c 'import libcubert'以驗證安裝。

相依性Dependency

Protobuf

cuBERT是使用protobuf-c構建的,以避免版本和程式碼與tensorflow protobuf衝突。

CUDA

CUDA編譯具有不同版本的庫不相容。

MKL

MKL是動態連結的。使用sudo make install安裝cuBERT和MKL。

Threading

假設cuBERT的典型用法是線上服務,其中應儘快處理不同batch_size的併發請求。因此,吞吐量和延遲應保持平衡,尤其是在純CPU環境中。

vanilla class Bert類Bert由於其內部用於計算的緩衝區而不是執行緒安全的,因此編寫了wrapper class BertM來儲存不同Bert例項的鎖,以確保執行緒安全。BertM會以迴圈方式選擇一個基礎Bert例項,並且同一Bert例項的結果請求可能會被其相應的鎖排隊。

顯示卡

一個Bert放在一張GPU卡上。最大併發請求數是一臺計算機上可用的GPU卡數量,CUDA_VISIBLE_DEVICES如果指定,則可以控制該數量。

CPU

對於純CPU環境,它比GPU更復雜。有2個並行級別:

  1. 請求級別。如果線上伺服器本身是多執行緒的,併發請求將競爭CPU資源。如果伺服器是單執行緒的(例如Python中的某些伺服器實現),事情將會變得容易得多。
  2. 操作級別。矩陣運算由OpenMP和MKL並行化。最大並行被控制OMP_NUM_THREADS,MKL_NUM_THREADS和許多其他的環境變數。建議使用者首先閱讀在多執行緒應用程式中使用執行緒化英特爾®MKL建議的設定以從多執行緒應用程式中呼叫英特爾MKL例程

因此,引入CUBERT_NUM_CPU_MODELS,更好地控制請求級別並行性的方法。此變數指定Bert在CPU /記憶體上建立的例項數,其作用類似於CUDA_VISIBLE_DEVICESGPU。

  • 如果CPU核心數量有限(舊的或桌上型電腦CPU,或在Docker中),則無需使用CUBERT_NUM_CPU_MODELS。例如4個CPU核心,請求級並行度為1,操作級並行度為4,應該可以很好地工作。
  • 但是,如果有許多CPU核心(例如40),則最好嘗試使用5的請求級並行度和8的操作級並行度。

總而言之,OMP_NUM_THREADS或MKL_NUM_THREADS定義一個模型可以使用多少個執行緒,並CUBERT_NUM_CPU_MODELS定義總共有多少個模型。

同樣,每個請求的延遲和總體吞吐量應該保持平衡,並且與modelseq_length,batch_sizeCPU核心,伺服器QPS和許多其他事情有所不同。應該採用很多基準來實現最佳折衷。