在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 Core和Mixed Precision可以加速cuBERT。支援混合精度作為儲存在fp16中的變數,並在fp32中進行計算。與單精度推理相比,典型的精度誤差小於1%,而速度則達到了2倍以上的加速度。
API
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個並行級別:
- 請求級別。如果線上伺服器本身是多執行緒的,併發請求將競爭CPU資源。如果伺服器是單執行緒的(例如Python中的某些伺服器實現),事情將會變得容易得多。
- 操作級別。矩陣運算由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和許多其他事情有所不同。應該採用很多基準來實現最佳折衷。