TransformerLM
1 概述¶
1.1 背景介绍¶
TransformerLM 通常作为ASR中声学模型的评分器, 对预测结果进行重打分, 该模型来源于FunASR开源仓库。
具体可参考:
https://github.com/modelscope/FunASR/blob/v0.3.0/funasr/bin/lm_inference.py
模型下载地址为:
https://www.modelscope.cn/models/iic/speech_transformer_lm_zh-cn-common-vocab8404-pytorch/files
1.2 使用说明¶
Linux SDK-alkaid中默认带了已经预先转换好的离线模型及板端示例, 相关文件路径如下:
-
板端示例程序路径
Linux_SDK/sdk/verify/opendla/source/llm/transformerlm
-
板端离线模型路径
Linux_SDK/project/board/${chip}/dla_file/ipu_open_models/llm/lm_100sim.img
-
板端测试字典路径
Linux_SDK/sdk/verify/opendla/source/resource/units_asr_punc_lm.txt
如果用户不需要转换模型可直接跳转至第3章节。
2 模型转换¶
2.1 onnx模型转换¶
-
python环境搭建
$conda create -n funasr python==3.10 $conda activate funasr $pip install "modelscope[audio_asr]" --upgrade -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html $git clone https://github.com/alibaba/FunASR.git && cd FunASR $pip install --editable ./ -i https://mirrors.aliyun.com/pypi/simple/
注意:这里提供的python环境搭建, 仅作为参考示例, 具体搭建过程请参考官方源码运行教程:
https://github.com/modelscope/FunASR/tree/v0.3.0?tab=readme-ov-file#installation
-
模型测试
-
编写模型测试脚本
funasr/bin/predict.py
from modelscope.pipelines import pipeline from modelscope.utils.constant import Tasks inference_pipline = pipeline( task=Tasks.language_score_prediction, model='./speech_transformer_lm_zh-cn', output_dir='./tmp/') rec_result = inference_pipline(text_in='hello 大 家 好 呀') print(rec_result)
-
运行模型测试脚本, 确保funasr环境配置正确
$将模型文件speech_transformer_lm_zh-cn-common-vocab8404-pytorch放在当前目录下 $mv speech_transformer_lm_zh-cn-common-vocab8404-pytorch speech_transformer_lm_zh-cn $python ./funasr/bin/predict.py
-
-
模型导出
-
编写模型转换脚本
funasr/bin/lm_export.py
:import os import sys sys.path.append(os.getcwd()) import logging import argparse import torch import onnx import onnxsim import onnxruntime as ort from funasr.torch_utils.initialize import initialize from funasr.train.class_choices import ClassChoices from funasr.lm.espnet_model import ESPnetLanguageModel from funasr.lm.seq_rnn_lm import SequentialRNNLM from funasr.lm.transformer_lm import TransformerLM from funasr.lm.abs_model import AbsLM from funasr.modules.mask import subsequent_mask from funasr.modules.nets_utils import make_pad_mask import torch.nn.functional as F import numpy as np from funasr.tasks.lm import LMTask from funasr.torch_utils.forward_adaptor import ForwardAdaptor def load_vocab(vocab_path, extra_word_list=[]): n = len(extra_word_list) with open(vocab_path, encoding='utf-8') as vf: vocab = {word.strip(): i + n for i, word in enumerate(vf)} for i, word in enumerate(extra_word_list): vocab[word] = i return vocab def softmaxcrossentropy_c( # type: ignore x, target, weight=None, reduction="None" ): input_shape = x.shape max_x = np.max(x, axis=1, keepdims=True).astype(np.float64) exp_x = np.exp(x - max_x) p = exp_x / np.sum(exp_x, axis=1, keepdims=True) inp = np.log(p) N = input_shape[0] neg_gather_element_input = np.zeros((N), dtype=x.dtype) inp = np.squeeze(inp) target = np.squeeze(target) for i in range(N): index = target[i] neg_gather_element_input[i] = -inp[i, index] return neg_gather_element_input if __name__ == '__main__': # 1. Build Model model, train_args = LMTask.build_model_from_file( './speech_transformer_lm_zh-cn/lm.yaml', './speech_transformer_lm_zh-cn/lm.pb', 'cpu') wrapped_model = ForwardAdaptor(model, "export") tmp_input = "梁家人出烟的电视剧有什么" token_dict = load_vocab('./speech_transformer_lm_zh-cn/tokens.txt') x_tmp = [] for char in tmp_input: if char in token_dict.keys(): x_tmp.append(token_dict[char]) else: x_tmp.append(token_dict["<unk>"]) x_tmp = torch.tensor([x_tmp], dtype=torch.int64) x_tmp = torch.cat([x_tmp,torch.zeros((1, 100 - x_tmp.shape[1]), dtype=int)], dim=1) wrapped_model.eval() onnx_path = './speech_transformer_lm_zh-cn/lm_1x100.onnx' torch.onnx.export( wrapped_model, (x_tmp), onnx_path, export_params=True, opset_version=14, input_names=["token"], output_names=["probs", 'x_lengths'], verbose=False, ) model_onnx = onnx.load(onnx_path) # load onnx model onnx.checker.check_model(model_onnx) # check onnx model model_onnx, check = onnxsim.simplify(model_onnx) onnx.save(model_onnx, onnx_path.replace('lm_1x100','lm_1x100_sim')) ort_sess = ort.InferenceSession(onnx_path) input_length = 100 input_list = [ "梁家人出烟的电视剧有什么" ] token_dict = load_vocab('./speech_transformer_lm_zh-cn/tokens.txt') count = 0 for sentence in input_list: x = [] for char in sentence: if char in token_dict.keys(): x.append(token_dict[char]) else: x.append(token_dict["<unk>"]) src_len = len(x) x = torch.tensor([x], dtype=torch.int64) x = torch.cat([x,torch.zeros((1, 100 - x.shape[1]), dtype=int)], dim=1) np.save("./speech_transformer_lm_zh-cn/lmx100/lm_input_%d.npy"%(count), x) count+=1 ort_inputs = { ort_sess.get_inputs()[0].name: x.numpy(), } ort_outs = ort_sess.run(None, ort_inputs) logits = ort_outs[0] print("logits: ", np.array(logits[0]).shape) print("logits: ", logits[0][0][:10]) onnx_nll = softmaxcrossentropy_c(logits[0], ort_outs[1].flatten()) print("onnx nll", onnx_nll) x_lens = torch.full((1,), fill_value=src_len, dtype=torch.int64) mask_ = np.zeros(input_length+1) mask_[np.arange(x_lens + 1)] = 1 onnx_nll = np.array(onnx_nll * mask_) onnx_nll = np.sum(onnx_nll) onnx_nll = onnx_nll / (x_lens + 1) print("onnx_nll ", onnx_nll)
-
运行模型转换脚本, 会在
./speech_transformer_lm_zh-cn
目录下生成lm_1x100.onnx模型python ./funasr/bin/lm_export.py
-
2.2 离线模型转换¶
2.2.1 预&后处理说明¶
-
预处理
language model 的输入为声学模型的输出, 即对应字典里的索引值。转换成功的lm_sim.onnx模型输入信息如下图所示:

-
后处理
language model模型没有后处理, 通常language model的输出的分数会再乘上一个系数, 再和声学模型的输出的分数进行加权, 然后再通过gready search的方式进行解码。输出信息如下所示:
2.2.2 offline模型转换流程¶
注意:1)OpenDLAModel对应的是压缩包image-dev_model_convert.tar解压之后的smodel文件。2)转换命令需要在docker环境下运行, 请先根据Docker开发环境教程, 加载SGS Docker环境。
-
拷贝onnx模型到转换代码目录
$cp speech_transformer_lm_zh-cn/lm_1x100.onnx OpenDLAModel/llm/transformerlm/onnx
-
转换命令
$cd IPU_SDK_Release/docker $bash run_docker.sh #进入到docker环境下的OpenDLAModel目录 $cd /work/SGS_XXX/OpenDLAModel $bash convert.sh -a llm/transformerlm -c config/llm_transformerlm.cfg -p SGS_IPU_Toolchain(绝对路径) -s false
-
最终生成的模型地址
output/${chip}_${时间}/lm_100sim.img output/${chip}_${时间}/lm_100sim_fixed.sim output/${chip}_${时间}/lm_100sim_float.sim
2.2.2 关键脚本参数解析¶
- input_config.ini
[INPUT_CONFIG]
inputs=token; #onnx 输入节点名称, 如果有多个需以“,”隔开;
input_formats=RAWDATA_S16_NHWC; #板端输入格式, 可以根据onnx的输入格式选择, 例如float:RAWDATA_F32_NHWC, int32:RAWDATA_S16_NHWC;
quantizations=TRUE; #打开输入量化, 不需要修改;
[OUTPUT_CONFIG]
outputs=probs; #onnx 输出节点名称, 如果有多个需以“,”隔开;
dequantizations=TRUE; #是否开启反量化, 根据实际需求填写, 建议为TRUE。设为False, 输出为int16; 设为True, 输出为float32
- llm_transformerlm.cfg
[TRANSFORMERLM]
CHIP_LIST=pcupid #平台名称, 必须和板端平台一致, 否则模型无法运行
Model_LIST=lm_1x100 #输入onnx模型名称
INPUT_SIZE_LIST=0 #模型输入分辨率,这里填0即可
INPUT_INI_LIST=input_config.ini #配置文件
CLASS_NUM_LIST=0 #填0即可
SAVE_NAME_LIST=lm_100sim.img #输出模型名称
QUANT_DATA_PATH=image_list.txt #量化数据路径
2.3 模型仿真¶
-
获取float/fixed/offline模型输出
$bash convert.sh -a llm/transformerlm -c config/llm_transformerlm.cfg -p SGS_IPU_Toolchain(绝对路径) -s true
执行上述命令后, 会默认将
float
模型的输出tensor保存到llm/transformerlm/log/output
路径下的txt文件中。此外, 在llm/transformerlm/convert.sh
脚本中也提供了fixed
和offline
的仿真示例, 用户在运行时可以通过打开注释代码块, 分别获取fixed
和offline
模型输出。 -
模型精度对比
在保证输入和上述模型相同的情况下, 进入2.1章节搭建好的环境, 在
FunASR/funasr/bin/lm_inference.py
脚本中第175行处添加打印:print(nll)
即可获取pytorch模型对应节点的输出tensor, 进而和float、fixed、offline模型进行对比。此外需要特别注意的是, 原始模型的输出格式是
NCHW
, 而float/fixed/offline模型输出的格式是NHWC
。
3 板端部署¶
3.1 程序编译¶
示例程序编译之前需要先根据板子(nand/nor/emmc, ddr型号等)选择deconfig进行sdk整包编译, 具体可以参考alkaid sdk sigdoc《开发环境搭建》文档。
-
编译板端transformerlm示例。
$cd sdk/verify/opendla $make clean && make source/llm/transformerlm -j8
-
最终生成的可执行文件地址
sdk/verify/opendla/out/${AARCH}/app/prog_llm_transformerlm
3.2 运行文件¶
运行程序时, 需要先将以下几个文件拷贝到板端
- prog_llm_transformerlm
- lm_100sim.img
3.3 运行说明¶
-
Usage:
./prog_llm_transformerlm model dict
(执行文件使用命令) -
Required Input:
- model: offline模型路径
- dict: 字典
-
Typical Output:
./prog_llm_transformerlm models/lm_100sim.img resource/units_asr_punc_lm.txt client [801] connected, module:ipu load dict... invoke start ... model invoke time: 60.463000 ms lm score: -3.692400 ------shutdown IPU0------ client [801] disconnected, module:ipu