Transformer模型在自然語言領域被提出后,目前已經擴展到了計算機視覺、語音等諸多領域。然而,雖然Transformer模型在語音識別領域有著更好的準確率,但還面臨著一個問題,計算復雜度和內存儲存開銷會隨著語音時長的增加而變大。
技術普及在于產品價格親民,而價格親民在于技術易落地易實現,離線語音識別應運而生,運用深度學習等技術且只需在本地進行運算就可實現人機語音交互,而且具備實時的響應速度、無需聯網的特點,能更好的應用在大小家電、照明、車載、健康儀器、教育設備等行業。
本次內容由社區優秀開發者、首百第四批新品體驗官——宋星辰將帶領大家DIY個人專屬離線語音識別,在X3派上玩轉一億參數量的超大Transformer。歡迎感興趣的旭友們點擊注冊地平線開發者社區交流討論,相關文檔詳見地平線開發者社區。
技術詳解
Step-1:模型轉換的環境準備
環境準備本身沒有什么奇技淫巧,這里想重點描述的是:pytorch版本的升級對精度瓶頸和速度瓶頸分析所帶來的跨越式的體驗提升。
在地平線開發者社區官方提供的安裝包中,為了兼容訓練算法包海圖(HAT),安裝的 pytorch版本為1.10.0。pytorch版本本身對模型轉換的精度不會有什么影響,但是不同版本的pytorch所導出的onnx,在節點(node, 或稱op)命名上有很大的區別。
就一般情況而言,當torch版本為1.10.0時,Node的命名采用了“optype+數字”的形式,這種形式的缺點是:當模型 Layer/SubLayer數量非常多(比如本文一億參數量的Transformer,包含的 OP 有上千個),我們很難一眼定位 Conv_xx 到底是第幾層的第幾個卷積。
torch 1.10.0版本結果
通常一個量化明顯掉點的模型,會從中間某一個OP開始有鮮明的Cosine Similarity損失,在當前的命名格式下,為了找到這個OP在原始模型中的位置(第x Layer的第y SubLayer),我們需要從頭開始一個一個數,這無疑是效率低下的。當然,隨著對模型細節的熟悉,定位的速度會越來越快,但這不能從根本上解決效率問題。
相反,當torch版本升級到1.13.0時Node的命名采用了“Layer+SubLayer+Attribute+OP”的形式,一眼定位,一眼丁真,大大節省了開發人員定位精度問題(哪層的OP相似度下降嚴重)or 速度問題(哪層的OP跑在CPU)的時間。
torch 1.13.0版本結果
Step-2:C++ Demo 的編譯
由于X3派板端內存有限,編譯C++ Demo時筆者采用了交叉編譯的形式,在開發機上sudo安裝aarch gcc即可。至于使用C++實現BPU模型的板上推理,實現推理的邏輯本身是一件很容易的事情,無論是使用python實現亦或是C++實現,其流程都是固定的,也即:
關于這四個步驟的API調用范例,官方 C++ 文檔中都給出了比較詳細的 know-how 示例,但是大多數都是單模型 + 單輸入的簡單case,在語音識別模型中,會涉及到 多模型(多個bin串聯)+ 多輸入(一個bin有多個輸入)的情況,這里給出本文的針對性示例:
// BPUAsrModel 類定義 using hobot::easy_dnn::Model; using hobot::easy_dnn::DNNTensor; using hobot::easy_dnn::TaskManager; using hobot::easy_dnn::ModelManager; class BPUAsrModel : public AsrModel { public: BPUAsrModel() = default; ~BPUAsrModel(); BPUAsrModel(const BPUAsrModel& other); void Read(const std::string& model_dir); void PrepareEncoderInput(const std::vector>& chunk_feats); // 其他成員函數... protected: void ForwardEncoderFunc(const std::vector>& chunk_feats, std::vector>* ctc_prob) override; private: // models std::shared_ptr encoder_model_ = nullptr; std::shared_ptr ctc_model_ = nullptr; // input/output tensors, 使用vector方便應對單模型多輸入的情況 std::vector> encoder_input_, encoder_output_; std::vector> ctc_input_, ctc_output_; // 其他成員變量... };
Step-3:正式開始模型轉換
(一)一行代碼 改寫Transformer模型
使用工具鏈去轉換NLP領域的原生Transformer模型,體驗可能會是非常糟糕的(甚至會在轉換過程中直接報錯)。這是因為NLP中的Transformer,輸入tensor的維度通常是二維或三維,類型既包含float也包含long 。而XJ3芯片在設計時只著重考慮了視覺任務,通常都是浮點的四維圖像輸入,工具鏈也只對這類視覺模型有比較極致的體驗優化。
那么,為了轉換NLP類的Transformer,我們是否需要重頭訓練一個四維數據流的模型呢?答案顯然是否定的,本文通過等價替換和抽象封裝,實現了一行代碼將原生Transformer等價改寫為BPU友好的Transformer:
# 一鍵完成 3D數據流 Transformer 等價轉換 4D數據流 Transformer Encoder4D = wenet.bin.export_onnx_bpu.BPUTransformerEncoder(Encoder3D)
這里的BPU TransformerEncoder就像是科幻電影中的“外骨骼機甲”一樣,其內核沒變(權重參數值沒變),但是功能上實現了針對性升級。具體而言,在 BPUTransformerEncoder 的構造過程中,會逐OP遍歷原生的 Encoder3D,并對其中的 BPU 不友好的 OP 實施等價改寫。
(二) 一句命令 走完轉換全流程
一個完整pytorch模型到bpu模型的轉換流程,一般要經過如下四步:
①pytorch 模型 轉 onnx 模型;
②構造 Calibration 數據;
③構造 config.yaml;
④調用 hb_mapper 執行 onnx 轉 bpu bin。
在WeNet開源的代碼中,我們用人民群眾喜聞樂見的python把這四個步驟 “粘” 到了一起,使用如下命令,就可走完全流程。
python3 $WENET_DIR/tools/onnx2horizonbin.py \ --config ./model_subsample8_parameter110M/train.yaml \ --checkpoint ./model_subsample8_parameter110M/final.pt \ --output_dir ./model_subsample8_parameter110M/sample50_chunk8_leftchunk16 \ --chunk_size 8 \ --num_decoding_left_chunks 16 \ --max_samples 50 \ --dict ./model_subsample8_parameter110M/units.txt \ --cali_datalist ./model_subsample8_parameter110M/calibration_data/data.list
其中:
config(描述了模型配置,幾層layer等);
checkpoint(pytorch 浮點模型);
output_dir(.bin 文件輸出目錄);
chunk_size(跟識別有關的解碼參數);
num_decoding_left_chunks(跟識別有關的解碼參數);
max_samples(使用多少句數據制作calibration data);
dict(字典);
cali_datalist(描述了標定數據的位置)。
綜上,我們對如下這四個步驟實現了完完全全的 python化封裝 和 一體化串聯 ,真正實現了一句命令(python3 $WENET_DIR/tools/onnx2horizonbin.py ...)走完全部轉換流程。
Demo展示
硬件配置:
模型配置:
解碼速度對比(單核單線程,量化后的模型):
本文轉自地平線開發者社區
原作者:xcsong
-
嵌入式
+關注
關注
5084文章
19133瀏覽量
305661 -
語音識別
+關注
關注
38文章
1742瀏覽量
112690 -
人工智能
+關注
關注
1791文章
47345瀏覽量
238730 -
Transformer
+關注
關注
0文章
143瀏覽量
6014
發布評論請先 登錄
相關推薦
評論