First commit
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- LICENSE +21 -0
- README.md +151 -3
- ax_common.py +625 -0
- config_decoder.json +88 -0
- config_encoder_offline.json +48 -0
- config_encoder_online.json +88 -0
- demo.wav +3 -0
- export_onnx.py +1723 -0
- generate_data.py +122 -0
- ort_common.py +567 -0
- requirements_ax.txt +3 -0
- requirements_x86.txt +12 -0
- run_ax.py +65 -0
- run_ort.py +55 -0
- wenet/LLM/__init__.py +0 -0
- wenet/LLM/causallm_model.py +207 -0
- wenet/LLM/decoder.py +161 -0
- wenet/LLM/sampler.py +43 -0
- wenet/README.md +29 -0
- wenet/__init__.py +1 -0
- wenet/bin/__init__.py +0 -0
- wenet/bin/alignment.py +268 -0
- wenet/bin/average_model.py +125 -0
- wenet/bin/export_ipex.py +95 -0
- wenet/bin/export_jit.py +71 -0
- wenet/bin/export_onnx_bpu.py +1065 -0
- wenet/bin/export_onnx_cpu.py +470 -0
- wenet/bin/export_onnx_gpu.py +1266 -0
- wenet/bin/recognize.py +317 -0
- wenet/bin/recognize_onnx_gpu.py +297 -0
- wenet/bin/train.py +190 -0
- wenet/branchformer/__init__.py +0 -0
- wenet/branchformer/cgmlp.py +194 -0
- wenet/branchformer/encoder.py +177 -0
- wenet/branchformer/encoder_layer.py +245 -0
- wenet/cli/__init__.py +0 -0
- wenet/cli/hub.py +118 -0
- wenet/cli/model.py +209 -0
- wenet/cli/paraformer_model.py +110 -0
- wenet/cli/punc_model.py +115 -0
- wenet/cli/transcribe.py +102 -0
- wenet/ctl_model/__init__.py +0 -0
- wenet/ctl_model/asr_model_ctl.py +277 -0
- wenet/ctl_model/encoder.py +172 -0
- wenet/dataset/__init__.py +0 -0
- wenet/dataset/datapipes.py +470 -0
- wenet/dataset/dataset.py +155 -0
- wenet/dataset/deprecated/__init__.py +0 -0
- wenet/dataset/deprecated/dataset.py +202 -0
.gitattributes
CHANGED
|
@@ -29,6 +29,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
*.wasm filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 32 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 29 |
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.wav filter=lfs diff=lfs merge=lfs -text
|
| 33 |
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 祈Inory
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# wenet.axera
|
| 2 |
+
|
| 3 |
+
WeNet on Axera.
|
| 4 |
+
|
| 5 |
+
预转换好的模型在[Release](https://github.com/ml-inory/wenet.axera/releases/download/v1.0/axmodel.tar.gz) 基于AIShell数据集的100条语音量化
|
| 6 |
+
|
| 7 |
+
## 安装依赖
|
| 8 |
+
|
| 9 |
+
x86:
|
| 10 |
+
|
| 11 |
+
```bash
|
| 12 |
+
pip install -r requirements_x86.txt
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
开发板:
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
pip install -r requirements_ax.txt
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
## 导出 ONNX
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
python export_onnx.py \
|
| 25 |
+
--pretrained_model_dir pretrained/aishell_u2pp_conformer_exp \
|
| 26 |
+
--output_onnx_dir onnx_model
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
如果不指定参数,默认会下载并使用 `pretrained/aishell_u2pp_conformer_exp`。
|
| 30 |
+
|
| 31 |
+
输出:
|
| 32 |
+
|
| 33 |
+
```text
|
| 34 |
+
onnx_model/encoder_offline.onnx
|
| 35 |
+
onnx_model/encoder_online.onnx
|
| 36 |
+
onnx_model/decoder.onnx
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
## 生成量化数据集
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
wget https://github.com/ml-inory/whisper.axera/releases/download/v1.0/datasets.zip
|
| 43 |
+
unzip datasets.zip -d datasets
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
```bash
|
| 47 |
+
python generate_data.py \
|
| 48 |
+
-i datasets \
|
| 49 |
+
--config pretrained/aishell_u2pp_conformer_exp/train.yaml \
|
| 50 |
+
--vocab pretrained/aishell_u2pp_conformer_exp/units.txt \
|
| 51 |
+
--onnx_dir onnx_model \
|
| 52 |
+
--calib_data_path calibration_dataset \
|
| 53 |
+
--max_num 100
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## 转 axmodel
|
| 57 |
+
|
| 58 |
+
转换:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
pulsar2 build \
|
| 62 |
+
--input onnx_model/encoder_offline.onnx \
|
| 63 |
+
--config config_encoder_offline.json \
|
| 64 |
+
--output_dir axmodel/encoder_offline \
|
| 65 |
+
--output_name encoder_offline.axmodel \
|
| 66 |
+
--target_hardware AX650 \
|
| 67 |
+
--npu_mode NPU3
|
| 68 |
+
|
| 69 |
+
pulsar2 build \
|
| 70 |
+
--input onnx_model/encoder_online.onnx \
|
| 71 |
+
--config config_encoder_online.json \
|
| 72 |
+
--output_dir axmodel/encoder_online \
|
| 73 |
+
--output_name encoder_online.axmodel \
|
| 74 |
+
--target_hardware AX650 \
|
| 75 |
+
--npu_mode NPU3
|
| 76 |
+
|
| 77 |
+
pulsar2 build \
|
| 78 |
+
--input onnx_model/decoder.onnx \
|
| 79 |
+
--config config_decoder.json \
|
| 80 |
+
--output_dir axmodel/decoder \
|
| 81 |
+
--output_name decoder.axmodel \
|
| 82 |
+
--target_hardware AX650 \
|
| 83 |
+
--npu_mode NPU3
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
## 跑 ONNX
|
| 87 |
+
|
| 88 |
+
Offline CTC:
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
python run_ort.py \
|
| 92 |
+
-i demo.wav \
|
| 93 |
+
--config pretrained/aishell_u2pp_conformer_exp/train.yaml \
|
| 94 |
+
--vocab pretrained/aishell_u2pp_conformer_exp/units.txt \
|
| 95 |
+
--onnx_dir onnx_model \
|
| 96 |
+
--mode ctc_prefix_beam_search \
|
| 97 |
+
--calib_data_path ""
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
Online CTC:
|
| 101 |
+
|
| 102 |
+
```bash
|
| 103 |
+
python run_ort.py \
|
| 104 |
+
-i demo.wav \
|
| 105 |
+
--online \
|
| 106 |
+
--config pretrained/aishell_u2pp_conformer_exp/train.yaml \
|
| 107 |
+
--vocab pretrained/aishell_u2pp_conformer_exp/units.txt \
|
| 108 |
+
--onnx_dir onnx_model \
|
| 109 |
+
--mode ctc_prefix_beam_search \
|
| 110 |
+
--calib_data_path ""
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
Attention rescoring:
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
python run_ort.py \
|
| 117 |
+
-i demo.wav \
|
| 118 |
+
--config pretrained/aishell_u2pp_conformer_exp/train.yaml \
|
| 119 |
+
--vocab pretrained/aishell_u2pp_conformer_exp/units.txt \
|
| 120 |
+
--onnx_dir onnx_model \
|
| 121 |
+
--mode attention_rescoring \
|
| 122 |
+
--calib_data_path ""
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
## 跑 axmodel
|
| 126 |
+
|
| 127 |
+
在开发板仓库目录运行。
|
| 128 |
+
|
| 129 |
+
Offline CTC:
|
| 130 |
+
|
| 131 |
+
```bash
|
| 132 |
+
python3 run_ax.py -i demo.wav --mode ctc_prefix_beam_search
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
Online CTC:
|
| 136 |
+
|
| 137 |
+
```bash
|
| 138 |
+
python3 run_ax.py -i demo.wav --online --mode ctc_prefix_beam_search
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
Offline attention rescoring:
|
| 142 |
+
|
| 143 |
+
```bash
|
| 144 |
+
python3 run_ax.py -i demo.wav --mode attention_rescoring
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
Online attention rescoring:
|
| 148 |
+
|
| 149 |
+
```bash
|
| 150 |
+
python3 run_ax.py -i demo.wav --online --mode attention_rescoring
|
| 151 |
+
```
|
ax_common.py
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import multiprocessing
|
| 2 |
+
import wave
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import yaml
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _log_add(*values):
|
| 9 |
+
values = [value for value in values if value != -float("inf")]
|
| 10 |
+
if not values:
|
| 11 |
+
return -float("inf")
|
| 12 |
+
max_value = max(values)
|
| 13 |
+
return max_value + np.log(sum(np.exp(value - max_value)
|
| 14 |
+
for value in values))
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _map_sentence(sent, vocabulary, greedy=False, blank_id=0):
|
| 18 |
+
mapped = []
|
| 19 |
+
prev = None
|
| 20 |
+
for token in sent:
|
| 21 |
+
token = int(token)
|
| 22 |
+
if greedy and token == prev:
|
| 23 |
+
prev = token
|
| 24 |
+
continue
|
| 25 |
+
prev = token
|
| 26 |
+
if token == blank_id or token < 0 or token >= len(vocabulary):
|
| 27 |
+
continue
|
| 28 |
+
piece = vocabulary[token]
|
| 29 |
+
if piece.startswith("<") and piece.endswith(">"):
|
| 30 |
+
continue
|
| 31 |
+
mapped.append(piece)
|
| 32 |
+
return "".join(mapped)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def map_batch(batch_sents, vocabulary, num_processes, greedy=False,
|
| 36 |
+
blank_id=0):
|
| 37 |
+
del num_processes
|
| 38 |
+
return [_map_sentence(sent, vocabulary, greedy, blank_id)
|
| 39 |
+
for sent in batch_sents]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _ctc_prefix_beam_search(log_probs_seq, log_probs_idx, beam_size,
|
| 43 |
+
blank_id):
|
| 44 |
+
beam = {(): (0.0, -float("inf"))}
|
| 45 |
+
for frame_probs, frame_ids in zip(log_probs_seq, log_probs_idx):
|
| 46 |
+
frame_probs = np.asarray(frame_probs, dtype=np.float32)
|
| 47 |
+
frame_probs = frame_probs - _log_add(*frame_probs.tolist())
|
| 48 |
+
next_beam = {}
|
| 49 |
+
for prefix, (prob_blank, prob_non_blank) in beam.items():
|
| 50 |
+
for prob, token in zip(frame_probs, frame_ids):
|
| 51 |
+
token = int(token)
|
| 52 |
+
prob = float(prob)
|
| 53 |
+
next_prob_blank, next_prob_non_blank = next_beam.get(
|
| 54 |
+
prefix, (-float("inf"), -float("inf")))
|
| 55 |
+
if token == blank_id:
|
| 56 |
+
next_beam[prefix] = (
|
| 57 |
+
_log_add(next_prob_blank, prob_blank + prob,
|
| 58 |
+
prob_non_blank + prob),
|
| 59 |
+
next_prob_non_blank,
|
| 60 |
+
)
|
| 61 |
+
continue
|
| 62 |
+
|
| 63 |
+
last = prefix[-1] if prefix else None
|
| 64 |
+
if token == last:
|
| 65 |
+
next_beam[prefix] = (
|
| 66 |
+
next_prob_blank,
|
| 67 |
+
_log_add(next_prob_non_blank, prob_non_blank + prob),
|
| 68 |
+
)
|
| 69 |
+
new_prefix = prefix + (token, )
|
| 70 |
+
nb_blank, nb_non_blank = next_beam.get(
|
| 71 |
+
new_prefix, (-float("inf"), -float("inf")))
|
| 72 |
+
next_beam[new_prefix] = (
|
| 73 |
+
nb_blank,
|
| 74 |
+
_log_add(nb_non_blank, prob_blank + prob),
|
| 75 |
+
)
|
| 76 |
+
else:
|
| 77 |
+
new_prefix = prefix + (token, )
|
| 78 |
+
nb_blank, nb_non_blank = next_beam.get(
|
| 79 |
+
new_prefix, (-float("inf"), -float("inf")))
|
| 80 |
+
next_beam[new_prefix] = (
|
| 81 |
+
nb_blank,
|
| 82 |
+
_log_add(nb_non_blank, prob_blank + prob,
|
| 83 |
+
prob_non_blank + prob),
|
| 84 |
+
)
|
| 85 |
+
beam = dict(sorted(
|
| 86 |
+
next_beam.items(),
|
| 87 |
+
key=lambda item: _log_add(item[1][0], item[1][1]),
|
| 88 |
+
reverse=True)[:beam_size])
|
| 89 |
+
return [(_log_add(prob_blank, prob_non_blank), prefix)
|
| 90 |
+
for prefix, (prob_blank, prob_non_blank) in sorted(
|
| 91 |
+
beam.items(),
|
| 92 |
+
key=lambda item: _log_add(item[1][0], item[1][1]),
|
| 93 |
+
reverse=True)]
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def ctc_beam_search_decoder_batch(batch_log_probs_seq,
|
| 97 |
+
batch_log_probs_idx,
|
| 98 |
+
batch_root_trie,
|
| 99 |
+
batch_start,
|
| 100 |
+
beam_size,
|
| 101 |
+
num_processes,
|
| 102 |
+
blank_id=0,
|
| 103 |
+
space_id=-1,
|
| 104 |
+
cutoff_prob=0.999,
|
| 105 |
+
ext_scorer=None):
|
| 106 |
+
del batch_root_trie, batch_start, num_processes, space_id
|
| 107 |
+
del cutoff_prob, ext_scorer
|
| 108 |
+
return [
|
| 109 |
+
_ctc_prefix_beam_search(log_probs_seq, log_probs_idx, beam_size,
|
| 110 |
+
blank_id)
|
| 111 |
+
for log_probs_seq, log_probs_idx in zip(batch_log_probs_seq,
|
| 112 |
+
batch_log_probs_idx)
|
| 113 |
+
]
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def load_config(config_path):
|
| 117 |
+
with open(config_path, "r") as fin:
|
| 118 |
+
return yaml.load(fin, Loader=yaml.FullLoader)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def load_vocab(vocab_path):
|
| 122 |
+
vocabulary = []
|
| 123 |
+
char_dict = {}
|
| 124 |
+
with open(vocab_path, "r") as fin:
|
| 125 |
+
for line in fin:
|
| 126 |
+
arr = line.strip().split()
|
| 127 |
+
assert len(arr) == 2
|
| 128 |
+
char_dict[int(arr[1])] = arr[0]
|
| 129 |
+
vocabulary.append(arr[0])
|
| 130 |
+
return vocabulary, char_dict
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def load_wav(audio_file):
|
| 134 |
+
with wave.open(audio_file, "rb") as wav_file:
|
| 135 |
+
sample_rate = wav_file.getframerate()
|
| 136 |
+
num_channels = wav_file.getnchannels()
|
| 137 |
+
sample_width = wav_file.getsampwidth()
|
| 138 |
+
frames = wav_file.readframes(wav_file.getnframes())
|
| 139 |
+
|
| 140 |
+
if sample_width == 1:
|
| 141 |
+
waveform = np.frombuffer(frames, dtype=np.uint8).astype(np.float32)
|
| 142 |
+
waveform -= 128.0
|
| 143 |
+
elif sample_width == 2:
|
| 144 |
+
waveform = np.frombuffer(frames, dtype="<i2").astype(np.float32)
|
| 145 |
+
elif sample_width == 4:
|
| 146 |
+
waveform = np.frombuffer(frames, dtype="<i4").astype(np.float32)
|
| 147 |
+
else:
|
| 148 |
+
raise ValueError(f"Unsupported wav sample width: {sample_width}")
|
| 149 |
+
|
| 150 |
+
if num_channels > 1:
|
| 151 |
+
waveform = waveform.reshape(-1, num_channels).mean(axis=1)
|
| 152 |
+
return waveform, sample_rate
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def resample_linear(waveform, orig_sr, target_sr):
|
| 156 |
+
if orig_sr == target_sr:
|
| 157 |
+
return waveform
|
| 158 |
+
duration = waveform.shape[0] / float(orig_sr)
|
| 159 |
+
target_len = int(round(duration * target_sr))
|
| 160 |
+
if target_len <= 1:
|
| 161 |
+
return waveform
|
| 162 |
+
src_pos = np.linspace(0, waveform.shape[0] - 1, target_len)
|
| 163 |
+
return np.interp(src_pos, np.arange(waveform.shape[0]),
|
| 164 |
+
waveform).astype(np.float32)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def hz_to_mel(freq):
|
| 168 |
+
return 1127.0 * np.log1p(freq / 700.0)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def mel_to_hz(mel):
|
| 172 |
+
return 700.0 * np.expm1(mel / 1127.0)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def mel_filterbank(num_mel_bins, n_fft, sample_rate):
|
| 176 |
+
low_mel = hz_to_mel(20.0)
|
| 177 |
+
high_mel = hz_to_mel(sample_rate / 2.0)
|
| 178 |
+
mel_points = np.linspace(low_mel, high_mel, num_mel_bins + 2)
|
| 179 |
+
hz_points = mel_to_hz(mel_points)
|
| 180 |
+
bins = np.floor((n_fft + 1) * hz_points / sample_rate).astype(np.int32)
|
| 181 |
+
fbanks = np.zeros((num_mel_bins, n_fft // 2 + 1), dtype=np.float32)
|
| 182 |
+
for i in range(num_mel_bins):
|
| 183 |
+
left, center, right = bins[i], bins[i + 1], bins[i + 2]
|
| 184 |
+
if center > left:
|
| 185 |
+
fbanks[i, left:center] = (
|
| 186 |
+
np.arange(left, center) - left) / float(center - left)
|
| 187 |
+
if right > center:
|
| 188 |
+
fbanks[i, center:right] = (
|
| 189 |
+
right - np.arange(center, right)) / float(right - center)
|
| 190 |
+
return fbanks
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def numpy_fbank(waveform,
|
| 194 |
+
sample_rate=16000,
|
| 195 |
+
num_mel_bins=80,
|
| 196 |
+
frame_length=25,
|
| 197 |
+
frame_shift=10):
|
| 198 |
+
frame_size = int(round(sample_rate * frame_length / 1000.0))
|
| 199 |
+
frame_step = int(round(sample_rate * frame_shift / 1000.0))
|
| 200 |
+
if waveform.shape[0] < frame_size:
|
| 201 |
+
waveform = np.pad(waveform, (0, frame_size - waveform.shape[0]))
|
| 202 |
+
num_frames = 1 + (waveform.shape[0] - frame_size) // frame_step
|
| 203 |
+
frames = np.lib.stride_tricks.as_strided(
|
| 204 |
+
waveform,
|
| 205 |
+
shape=(num_frames, frame_size),
|
| 206 |
+
strides=(waveform.strides[0] * frame_step, waveform.strides[0]),
|
| 207 |
+
).copy()
|
| 208 |
+
frames *= np.hamming(frame_size).astype(np.float32)
|
| 209 |
+
n_fft = 1
|
| 210 |
+
while n_fft < frame_size:
|
| 211 |
+
n_fft <<= 1
|
| 212 |
+
power = np.abs(np.fft.rfft(frames, n=n_fft))**2
|
| 213 |
+
fbanks = mel_filterbank(num_mel_bins, n_fft, sample_rate)
|
| 214 |
+
mel_energies = np.maximum(np.dot(power, fbanks.T), np.finfo(np.float32).eps)
|
| 215 |
+
return np.log(mel_energies).astype(np.float32)
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def compute_feats(audio_file, sr=16000):
|
| 219 |
+
waveform, sample_rate = load_wav(audio_file)
|
| 220 |
+
waveform = resample_linear(waveform.astype(np.float32), sample_rate, sr)
|
| 221 |
+
return numpy_fbank(waveform, sample_rate=sr).reshape(1, -1, 80)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def pad_array_along_axis(array, pad_width, axis, mode="constant", **kwargs):
|
| 225 |
+
if array.shape[axis] >= pad_width:
|
| 226 |
+
return array
|
| 227 |
+
full_pad_width = [(0, 0)] * array.ndim
|
| 228 |
+
full_pad_width[axis] = (0, pad_width - array.shape[axis])
|
| 229 |
+
return np.pad(array, pad_width=full_pad_width, mode=mode, **kwargs)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def numpy_topk(array, k, axis=-1, largest=True):
|
| 233 |
+
if largest:
|
| 234 |
+
partitioned_indices = np.argpartition(array, -k, axis=axis)
|
| 235 |
+
topk_indices = np.take(partitioned_indices, range(-k, 0), axis=axis)
|
| 236 |
+
else:
|
| 237 |
+
partitioned_indices = np.argpartition(array, k, axis=axis)
|
| 238 |
+
topk_indices = np.take(partitioned_indices, range(0, k), axis=axis)
|
| 239 |
+
|
| 240 |
+
topk_values = np.take_along_axis(array, topk_indices, axis=axis)
|
| 241 |
+
sorted_indices_in_topk = np.argsort(topk_values, axis=axis)
|
| 242 |
+
if largest:
|
| 243 |
+
sorted_indices_in_topk = np.flip(sorted_indices_in_topk, axis=axis)
|
| 244 |
+
sorted_topk_values = np.take_along_axis(topk_values,
|
| 245 |
+
sorted_indices_in_topk,
|
| 246 |
+
axis=axis)
|
| 247 |
+
sorted_topk_indices = np.take_along_axis(topk_indices,
|
| 248 |
+
sorted_indices_in_topk,
|
| 249 |
+
axis=axis)
|
| 250 |
+
return sorted_topk_values, sorted_topk_indices
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def ctc_decoding(beam_log_probs,
|
| 254 |
+
beam_log_probs_idx,
|
| 255 |
+
encoder_out_lens,
|
| 256 |
+
vocabulary,
|
| 257 |
+
mode="ctc_prefix_beam_search"):
|
| 258 |
+
beam_size = beam_log_probs.shape[-1]
|
| 259 |
+
batch_size = beam_log_probs.shape[0]
|
| 260 |
+
num_processes = min(multiprocessing.cpu_count(), batch_size)
|
| 261 |
+
hyps = []
|
| 262 |
+
score_hyps = []
|
| 263 |
+
|
| 264 |
+
if mode == "ctc_greedy_search":
|
| 265 |
+
if beam_size == 1:
|
| 266 |
+
log_probs_idx = beam_log_probs_idx.squeeze(-1)
|
| 267 |
+
else:
|
| 268 |
+
log_probs_idx = beam_log_probs_idx[:, :, 0]
|
| 269 |
+
batch_sents = []
|
| 270 |
+
for idx, seq in enumerate(log_probs_idx):
|
| 271 |
+
batch_sents.append(seq[0:encoder_out_lens[idx]].tolist())
|
| 272 |
+
hyps = map_batch(batch_sents, vocabulary, num_processes, True, 0)
|
| 273 |
+
elif mode in ("ctc_prefix_beam_search", "attention_rescoring"):
|
| 274 |
+
batch_log_probs_seq_list = beam_log_probs.tolist()
|
| 275 |
+
batch_log_probs_idx_list = beam_log_probs_idx.tolist()
|
| 276 |
+
batch_len_list = encoder_out_lens.tolist()
|
| 277 |
+
batch_log_probs_seq = []
|
| 278 |
+
batch_log_probs_ids = []
|
| 279 |
+
batch_start = []
|
| 280 |
+
batch_root = []
|
| 281 |
+
for i in range(len(batch_len_list)):
|
| 282 |
+
num_sent = batch_len_list[i]
|
| 283 |
+
batch_log_probs_seq.append(batch_log_probs_seq_list[i][0:num_sent])
|
| 284 |
+
batch_log_probs_ids.append(batch_log_probs_idx_list[i][0:num_sent])
|
| 285 |
+
batch_root.append(None)
|
| 286 |
+
batch_start.append(True)
|
| 287 |
+
score_hyps = ctc_beam_search_decoder_batch(batch_log_probs_seq,
|
| 288 |
+
batch_log_probs_ids,
|
| 289 |
+
batch_root,
|
| 290 |
+
batch_start,
|
| 291 |
+
beam_size,
|
| 292 |
+
num_processes,
|
| 293 |
+
0, -2, 0.99999)
|
| 294 |
+
if mode == "ctc_prefix_beam_search":
|
| 295 |
+
for cand_hyps in score_hyps:
|
| 296 |
+
hyps.append(cand_hyps[0][1])
|
| 297 |
+
hyps = map_batch(hyps, vocabulary, num_processes, False, 0)
|
| 298 |
+
return hyps, score_hyps
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def has_higher_scored_collapsed_repeat(hyp, kept_hyps):
|
| 302 |
+
for i in range(1, len(hyp)):
|
| 303 |
+
if hyp[i] != hyp[i - 1]:
|
| 304 |
+
continue
|
| 305 |
+
collapsed = hyp[:i] + hyp[i + 1:]
|
| 306 |
+
if collapsed in kept_hyps:
|
| 307 |
+
return True
|
| 308 |
+
return False
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def make_decoder_inputs(encoder_out,
|
| 312 |
+
encoder_out_lens,
|
| 313 |
+
beam_log_probs,
|
| 314 |
+
beam_log_probs_idx,
|
| 315 |
+
vocabulary,
|
| 316 |
+
sos,
|
| 317 |
+
eos,
|
| 318 |
+
decoder_len):
|
| 319 |
+
_, score_hyps = ctc_decoding(beam_log_probs, beam_log_probs_idx,
|
| 320 |
+
encoder_out_lens, vocabulary,
|
| 321 |
+
"attention_rescoring")
|
| 322 |
+
ignore_id = -1
|
| 323 |
+
beam_size = beam_log_probs.shape[-1]
|
| 324 |
+
batch_size = beam_log_probs.shape[0]
|
| 325 |
+
ctc_score, all_hyps = [], []
|
| 326 |
+
for hyps in score_hyps:
|
| 327 |
+
filtered_hyps = []
|
| 328 |
+
kept_hyps = set()
|
| 329 |
+
for score, hyp in hyps:
|
| 330 |
+
hyp = tuple(hyp)
|
| 331 |
+
if has_higher_scored_collapsed_repeat(hyp, kept_hyps):
|
| 332 |
+
continue
|
| 333 |
+
filtered_hyps.append((score, hyp))
|
| 334 |
+
kept_hyps.add(hyp)
|
| 335 |
+
if len(filtered_hyps) == beam_size:
|
| 336 |
+
break
|
| 337 |
+
hyps = filtered_hyps
|
| 338 |
+
cur_len = len(hyps)
|
| 339 |
+
if len(hyps) < beam_size:
|
| 340 |
+
hyps += (beam_size - cur_len) * [(-float("inf"), (0,))]
|
| 341 |
+
cur_ctc_score = []
|
| 342 |
+
for hyp in hyps:
|
| 343 |
+
cur_ctc_score.append(hyp[0])
|
| 344 |
+
all_hyps.append(list(hyp[1]))
|
| 345 |
+
ctc_score.append(cur_ctc_score)
|
| 346 |
+
ctc_score = np.array(ctc_score, dtype=np.float32)
|
| 347 |
+
|
| 348 |
+
max_len = decoder_len - 2
|
| 349 |
+
hyps_pad_sos_eos = np.ones((batch_size, beam_size, max_len + 2),
|
| 350 |
+
dtype=np.int64) * ignore_id
|
| 351 |
+
r_hyps_pad_sos_eos = np.ones((batch_size, beam_size, max_len + 2),
|
| 352 |
+
dtype=np.int64) * ignore_id
|
| 353 |
+
hyps_lens_sos = np.ones((batch_size, beam_size), dtype=np.int32)
|
| 354 |
+
k = 0
|
| 355 |
+
for i in range(batch_size):
|
| 356 |
+
for j in range(beam_size):
|
| 357 |
+
cand = all_hyps[k][:max_len]
|
| 358 |
+
length = len(cand) + 2
|
| 359 |
+
hyps_pad_sos_eos[i][j][0:length] = [sos] + cand + [eos]
|
| 360 |
+
r_hyps_pad_sos_eos[i][j][0:length] = [sos] + cand[::-1] + [eos]
|
| 361 |
+
hyps_lens_sos[i][j] = len(cand) + 1
|
| 362 |
+
k += 1
|
| 363 |
+
|
| 364 |
+
if decoder_len > encoder_out.shape[1]:
|
| 365 |
+
encoder_out = np.pad(encoder_out,
|
| 366 |
+
[(0, 0),
|
| 367 |
+
(0, decoder_len - encoder_out.shape[1]),
|
| 368 |
+
(0, 0)],
|
| 369 |
+
mode="constant",
|
| 370 |
+
constant_values=0)
|
| 371 |
+
elif decoder_len < encoder_out.shape[1]:
|
| 372 |
+
encoder_out = encoder_out[:, :decoder_len, :]
|
| 373 |
+
|
| 374 |
+
return {
|
| 375 |
+
"encoder_out": encoder_out,
|
| 376 |
+
"encoder_out_lens": np.full(batch_size,
|
| 377 |
+
fill_value=decoder_len,
|
| 378 |
+
dtype=np.int32),
|
| 379 |
+
"hyps_pad_sos_eos": hyps_pad_sos_eos.astype(np.int32),
|
| 380 |
+
"hyps_lens_sos": hyps_lens_sos,
|
| 381 |
+
"r_hyps_pad_sos_eos": r_hyps_pad_sos_eos.astype(np.int32),
|
| 382 |
+
"ctc_score": ctc_score,
|
| 383 |
+
}, all_hyps
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def make_offline_inputs(feats, seq_len):
|
| 387 |
+
feats = feats[:, :seq_len, :]
|
| 388 |
+
speech_lengths = np.array([feats.shape[1]], dtype=np.int32)
|
| 389 |
+
if feats.shape[1] < seq_len:
|
| 390 |
+
feats = pad_array_along_axis(feats, pad_width=seq_len, axis=1)
|
| 391 |
+
return {"speech": feats, "speech_lengths": speech_lengths}
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def make_online_initial_state(configs,
|
| 395 |
+
batch_size=1,
|
| 396 |
+
decoding_chunk_size=16,
|
| 397 |
+
num_decoding_left_chunks=5):
|
| 398 |
+
subsampling = 4
|
| 399 |
+
context = 7
|
| 400 |
+
stride = subsampling * decoding_chunk_size
|
| 401 |
+
decoding_window = (decoding_chunk_size - 1) * subsampling + context
|
| 402 |
+
required_cache_size = decoding_chunk_size * num_decoding_left_chunks
|
| 403 |
+
|
| 404 |
+
output_size = configs["encoder_conf"]["output_size"]
|
| 405 |
+
num_layers = configs["encoder_conf"]["num_blocks"]
|
| 406 |
+
cnn_module_kernel = configs["encoder_conf"].get("cnn_module_kernel", 1) - 1
|
| 407 |
+
head = configs["encoder_conf"]["attention_heads"]
|
| 408 |
+
d_k = configs["encoder_conf"]["output_size"] // head
|
| 409 |
+
|
| 410 |
+
state = {
|
| 411 |
+
"att_cache": np.zeros((batch_size, num_layers, head,
|
| 412 |
+
required_cache_size, d_k * 2),
|
| 413 |
+
dtype=np.float32),
|
| 414 |
+
"cnn_cache": np.zeros((batch_size, num_layers, output_size,
|
| 415 |
+
cnn_module_kernel),
|
| 416 |
+
dtype=np.float32),
|
| 417 |
+
"cache_mask": np.zeros((batch_size, 1, required_cache_size),
|
| 418 |
+
dtype=np.float32),
|
| 419 |
+
"offset": np.zeros((batch_size, 1), dtype=np.int32),
|
| 420 |
+
}
|
| 421 |
+
params = {
|
| 422 |
+
"batch_size": batch_size,
|
| 423 |
+
"context": context,
|
| 424 |
+
"stride": stride,
|
| 425 |
+
"decoding_window": decoding_window,
|
| 426 |
+
}
|
| 427 |
+
return state, params
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def make_online_encoder_input(feats, cur, params, state):
|
| 431 |
+
batch_size = params["batch_size"]
|
| 432 |
+
decoding_window = params["decoding_window"]
|
| 433 |
+
end = min(cur + decoding_window, feats.shape[1])
|
| 434 |
+
chunk_xs = feats[:, cur:end, :]
|
| 435 |
+
if chunk_xs.shape[1] < decoding_window:
|
| 436 |
+
chunk_xs = pad_array_along_axis(chunk_xs,
|
| 437 |
+
pad_width=decoding_window,
|
| 438 |
+
axis=1)
|
| 439 |
+
chunk_xs = chunk_xs.astype(np.float32)
|
| 440 |
+
chunk_lens = np.full(batch_size,
|
| 441 |
+
fill_value=chunk_xs.shape[1],
|
| 442 |
+
dtype=np.int32)
|
| 443 |
+
return {
|
| 444 |
+
"chunk_xs": chunk_xs,
|
| 445 |
+
"chunk_lens": chunk_lens,
|
| 446 |
+
"offset": state["offset"],
|
| 447 |
+
"att_cache": state["att_cache"],
|
| 448 |
+
"cnn_cache": state["cnn_cache"],
|
| 449 |
+
"cache_mask": state["cache_mask"],
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
def output_value(outputs, name):
|
| 454 |
+
if name in outputs:
|
| 455 |
+
return outputs[name]
|
| 456 |
+
r_name = "r_" + name
|
| 457 |
+
if r_name in outputs:
|
| 458 |
+
return outputs[r_name]
|
| 459 |
+
raise KeyError(name)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def update_online_state(state, outputs):
|
| 463 |
+
state["offset"] = output_value(outputs, "offset")
|
| 464 |
+
state["att_cache"] = output_value(outputs, "att_cache")
|
| 465 |
+
state["cnn_cache"] = output_value(outputs, "cnn_cache")
|
| 466 |
+
state["cache_mask"] = output_value(outputs, "cache_mask")
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
class AxModel:
|
| 470 |
+
|
| 471 |
+
def __init__(self, path):
|
| 472 |
+
from axengine import InferenceSession
|
| 473 |
+
|
| 474 |
+
self.session = InferenceSession(path)
|
| 475 |
+
self.output_names = [item.name for item in self.session.get_outputs()]
|
| 476 |
+
|
| 477 |
+
def run(self, input_feed):
|
| 478 |
+
output_values = self.session.run(self.output_names, input_feed)
|
| 479 |
+
return dict(zip(self.output_names, output_values))
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
class WenetAXRunner:
|
| 483 |
+
|
| 484 |
+
def __init__(self,
|
| 485 |
+
config_path,
|
| 486 |
+
vocab_path,
|
| 487 |
+
encoder_offline_path="axmodel/encoder_offline/encoder_offline.axmodel",
|
| 488 |
+
encoder_online_path="axmodel/encoder_online/encoder_online.axmodel",
|
| 489 |
+
decoder_path="axmodel/decoder/decoder.axmodel",
|
| 490 |
+
offline_seq_len=1024,
|
| 491 |
+
decoder_len=32,
|
| 492 |
+
decoding_chunk_size=16,
|
| 493 |
+
num_decoding_left_chunks=5,
|
| 494 |
+
batch_size=1):
|
| 495 |
+
self.config_path = config_path
|
| 496 |
+
self.vocab_path = vocab_path
|
| 497 |
+
self.encoder_offline_path = encoder_offline_path
|
| 498 |
+
self.encoder_online_path = encoder_online_path
|
| 499 |
+
self.decoder_path = decoder_path
|
| 500 |
+
self.offline_seq_len = offline_seq_len
|
| 501 |
+
self.decoder_len = decoder_len
|
| 502 |
+
self.decoding_chunk_size = decoding_chunk_size
|
| 503 |
+
self.num_decoding_left_chunks = num_decoding_left_chunks
|
| 504 |
+
self.batch_size = batch_size
|
| 505 |
+
|
| 506 |
+
self.configs = load_config(config_path)
|
| 507 |
+
self.vocabulary, self.char_dict = load_vocab(vocab_path)
|
| 508 |
+
self.eos = self.sos = len(self.char_dict) - 1
|
| 509 |
+
|
| 510 |
+
self._offline_encoder = None
|
| 511 |
+
self._online_encoder = None
|
| 512 |
+
self._decoder = None
|
| 513 |
+
|
| 514 |
+
@property
|
| 515 |
+
def offline_encoder(self):
|
| 516 |
+
if self._offline_encoder is None:
|
| 517 |
+
self._offline_encoder = AxModel(self.encoder_offline_path)
|
| 518 |
+
return self._offline_encoder
|
| 519 |
+
|
| 520 |
+
@property
|
| 521 |
+
def online_encoder(self):
|
| 522 |
+
if self._online_encoder is None:
|
| 523 |
+
self._online_encoder = AxModel(self.encoder_online_path)
|
| 524 |
+
return self._online_encoder
|
| 525 |
+
|
| 526 |
+
@property
|
| 527 |
+
def decoder(self):
|
| 528 |
+
if self._decoder is None:
|
| 529 |
+
self._decoder = AxModel(self.decoder_path)
|
| 530 |
+
return self._decoder
|
| 531 |
+
|
| 532 |
+
def compute_feats(self, audio_file):
|
| 533 |
+
return compute_feats(audio_file)
|
| 534 |
+
|
| 535 |
+
def run_offline_encoder(self, feats):
|
| 536 |
+
encoder_input = make_offline_inputs(feats, self.offline_seq_len)
|
| 537 |
+
speech_lengths = encoder_input["speech_lengths"]
|
| 538 |
+
outputs = self.offline_encoder.run(encoder_input)
|
| 539 |
+
encoder_out_lens = outputs["encoder_out_lens"].astype(np.int32)
|
| 540 |
+
encoder_out_lens[0] = np.ones([speech_lengths[0]],
|
| 541 |
+
dtype=np.int32)[2::2][2::2].sum()
|
| 542 |
+
beam_log_probs, beam_log_probs_idx = numpy_topk(
|
| 543 |
+
outputs["ctc_log_probs"], k=10)
|
| 544 |
+
return {
|
| 545 |
+
"encoder_out": outputs["encoder_out"],
|
| 546 |
+
"encoder_out_lens": encoder_out_lens,
|
| 547 |
+
"ctc_log_probs": outputs["ctc_log_probs"],
|
| 548 |
+
"beam_log_probs": beam_log_probs,
|
| 549 |
+
"beam_log_probs_idx": beam_log_probs_idx,
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
def run_online_encoder(self, feats):
|
| 553 |
+
state, online_params = make_online_initial_state(
|
| 554 |
+
self.configs, self.batch_size, self.decoding_chunk_size,
|
| 555 |
+
self.num_decoding_left_chunks)
|
| 556 |
+
encoder_out = []
|
| 557 |
+
beam_log_probs = []
|
| 558 |
+
beam_log_probs_idx = []
|
| 559 |
+
num_frames = feats.shape[1]
|
| 560 |
+
|
| 561 |
+
for cur in range(0, num_frames - online_params["context"] + 1,
|
| 562 |
+
online_params["stride"]):
|
| 563 |
+
encoder_input = make_online_encoder_input(feats, cur,
|
| 564 |
+
online_params, state)
|
| 565 |
+
outputs = self.online_encoder.run(encoder_input)
|
| 566 |
+
update_online_state(state, outputs)
|
| 567 |
+
encoder_out.append(outputs["chunk_out"])
|
| 568 |
+
beam_log_probs.append(outputs["log_probs"])
|
| 569 |
+
beam_log_probs_idx.append(outputs["log_probs_idx"].astype(np.int32))
|
| 570 |
+
|
| 571 |
+
return {
|
| 572 |
+
"encoder_out": np.concatenate(encoder_out, axis=1),
|
| 573 |
+
"encoder_out_lens": np.full(self.batch_size,
|
| 574 |
+
fill_value=sum(
|
| 575 |
+
out.shape[1]
|
| 576 |
+
for out in encoder_out),
|
| 577 |
+
dtype=np.int32),
|
| 578 |
+
"beam_log_probs": np.concatenate(beam_log_probs, axis=1),
|
| 579 |
+
"beam_log_probs_idx": np.concatenate(beam_log_probs_idx, axis=1),
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
def ctc_decode(self, encoder_outputs, mode):
|
| 583 |
+
return ctc_decoding(encoder_outputs["beam_log_probs"],
|
| 584 |
+
encoder_outputs["beam_log_probs_idx"],
|
| 585 |
+
encoder_outputs["encoder_out_lens"],
|
| 586 |
+
self.vocabulary, mode)
|
| 587 |
+
|
| 588 |
+
def run_decoder(self, encoder_outputs):
|
| 589 |
+
decoder_input, all_hyps = make_decoder_inputs(
|
| 590 |
+
encoder_outputs["encoder_out"],
|
| 591 |
+
encoder_outputs["encoder_out_lens"],
|
| 592 |
+
encoder_outputs["beam_log_probs"],
|
| 593 |
+
encoder_outputs["beam_log_probs_idx"],
|
| 594 |
+
self.vocabulary,
|
| 595 |
+
self.sos,
|
| 596 |
+
self.eos,
|
| 597 |
+
self.decoder_len,
|
| 598 |
+
)
|
| 599 |
+
best_index = self.decoder.run(decoder_input)["best_index"].astype(
|
| 600 |
+
np.int32)
|
| 601 |
+
beam_size = encoder_outputs["beam_log_probs"].shape[-1]
|
| 602 |
+
num_processes = min(multiprocessing.cpu_count(), best_index.shape[0])
|
| 603 |
+
best_sents = []
|
| 604 |
+
k = 0
|
| 605 |
+
for idx in best_index:
|
| 606 |
+
best_sents.append(all_hyps[k:k + beam_size][idx])
|
| 607 |
+
k += beam_size
|
| 608 |
+
hyps = map_batch(best_sents, self.vocabulary, num_processes)
|
| 609 |
+
return "".join(hyps)
|
| 610 |
+
|
| 611 |
+
def transcribe(self,
|
| 612 |
+
audio_file,
|
| 613 |
+
online=False,
|
| 614 |
+
mode="ctc_prefix_beam_search"):
|
| 615 |
+
feats = self.compute_feats(audio_file)
|
| 616 |
+
if online:
|
| 617 |
+
encoder_outputs = self.run_online_encoder(feats)
|
| 618 |
+
else:
|
| 619 |
+
encoder_outputs = self.run_offline_encoder(feats)
|
| 620 |
+
|
| 621 |
+
if mode == "attention_rescoring":
|
| 622 |
+
return self.run_decoder(encoder_outputs)
|
| 623 |
+
|
| 624 |
+
hyps, _ = self.ctc_decode(encoder_outputs, mode)
|
| 625 |
+
return "".join(hyps) if hyps else ""
|
config_decoder.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "ONNX",
|
| 3 |
+
"npu_mode": "NPU1",
|
| 4 |
+
"quant": {
|
| 5 |
+
"input_configs": [
|
| 6 |
+
{
|
| 7 |
+
"tensor_name": "encoder_out",
|
| 8 |
+
"calibration_dataset": "./calibration_dataset/encoder_out.tar.gz",
|
| 9 |
+
"calibration_size": -1,
|
| 10 |
+
"calibration_format": "Numpy"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"tensor_name": "encoder_out_lens",
|
| 14 |
+
"calibration_dataset": "./calibration_dataset/encoder_out_lens.tar.gz",
|
| 15 |
+
"calibration_size": -1,
|
| 16 |
+
"calibration_format": "Numpy"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"tensor_name": "hyps_pad_sos_eos",
|
| 20 |
+
"calibration_dataset": "./calibration_dataset/hyps_pad_sos_eos.tar.gz",
|
| 21 |
+
"calibration_size": -1,
|
| 22 |
+
"calibration_format": "Numpy"
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"tensor_name": "hyps_lens_sos",
|
| 26 |
+
"calibration_dataset": "./calibration_dataset/hyps_lens_sos.tar.gz",
|
| 27 |
+
"calibration_size": -1,
|
| 28 |
+
"calibration_format": "Numpy"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"tensor_name": "r_hyps_pad_sos_eos",
|
| 32 |
+
"calibration_dataset": "./calibration_dataset/r_hyps_pad_sos_eos.tar.gz",
|
| 33 |
+
"calibration_size": -1,
|
| 34 |
+
"calibration_format": "Numpy"
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"tensor_name": "ctc_score",
|
| 38 |
+
"calibration_dataset": "./calibration_dataset/ctc_score.tar.gz",
|
| 39 |
+
"calibration_size": -1,
|
| 40 |
+
"calibration_format": "Numpy"
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"layer_configs": [
|
| 44 |
+
{
|
| 45 |
+
"start_tensor_names": ["DEFAULT"],
|
| 46 |
+
"end_tensor_names": ["DEFAULT"],
|
| 47 |
+
"data_type": "U8"
|
| 48 |
+
}
|
| 49 |
+
],
|
| 50 |
+
"calibration_method": "MinMax",
|
| 51 |
+
"conv_bias_data_type": "FP32",
|
| 52 |
+
"disable_auto_refine_scale": true,
|
| 53 |
+
"enable_smooth_quant": true,
|
| 54 |
+
"precision_analysis": true,
|
| 55 |
+
"precision_analysis_method": "EndToEnd",
|
| 56 |
+
"transformer_opt_level": 1
|
| 57 |
+
},
|
| 58 |
+
"input_processors": [
|
| 59 |
+
{
|
| 60 |
+
"tensor_name": "encoder_out",
|
| 61 |
+
"src_dtype": "FP32"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"tensor_name": "encoder_out_lens",
|
| 65 |
+
"src_dtype": "S32"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"tensor_name": "hyps_pad_sos_eos",
|
| 69 |
+
"src_dtype": "S32"
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"tensor_name": "hyps_lens_sos",
|
| 73 |
+
"src_dtype": "S32"
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"tensor_name": "r_hyps_pad_sos_eos",
|
| 77 |
+
"src_dtype": "S32"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"tensor_name": "ctc_score",
|
| 81 |
+
"src_dtype": "FP32"
|
| 82 |
+
}
|
| 83 |
+
],
|
| 84 |
+
"compiler": {
|
| 85 |
+
"check": 0
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
config_encoder_offline.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "ONNX",
|
| 3 |
+
"npu_mode": "NPU3",
|
| 4 |
+
"quant": {
|
| 5 |
+
"input_configs": [
|
| 6 |
+
{
|
| 7 |
+
"tensor_name": "speech",
|
| 8 |
+
"calibration_dataset": "./calibration_dataset/speech.tar.gz",
|
| 9 |
+
"calibration_size": -1,
|
| 10 |
+
"calibration_format": "Numpy"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"tensor_name": "speech_lengths",
|
| 14 |
+
"calibration_dataset": "./calibration_dataset/speech_lengths.tar.gz",
|
| 15 |
+
"calibration_size": -1,
|
| 16 |
+
"calibration_format": "Numpy"
|
| 17 |
+
}
|
| 18 |
+
],
|
| 19 |
+
"layer_configs": [
|
| 20 |
+
{
|
| 21 |
+
"start_tensor_names": ["DEFAULT"],
|
| 22 |
+
"end_tensor_names": ["DEFAULT"],
|
| 23 |
+
"data_type": "U16"
|
| 24 |
+
}
|
| 25 |
+
],
|
| 26 |
+
"calibration_method": "MinMax",
|
| 27 |
+
"conv_bias_data_type": "FP32",
|
| 28 |
+
"disable_auto_refine_scale": true,
|
| 29 |
+
"enable_smooth_quant": true,
|
| 30 |
+
"precision_analysis": true,
|
| 31 |
+
"precision_analysis_method": "EndToEnd",
|
| 32 |
+
"transformer_opt_level": 1
|
| 33 |
+
},
|
| 34 |
+
"input_processors": [
|
| 35 |
+
{
|
| 36 |
+
"tensor_name": "speech",
|
| 37 |
+
"src_dtype": "FP32"
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"tensor_name": "speech_lengths",
|
| 41 |
+
"src_dtype": "S32"
|
| 42 |
+
}
|
| 43 |
+
],
|
| 44 |
+
"compiler": {
|
| 45 |
+
"check": 0
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
config_encoder_online.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "ONNX",
|
| 3 |
+
"npu_mode": "NPU3",
|
| 4 |
+
"quant": {
|
| 5 |
+
"input_configs": [
|
| 6 |
+
{
|
| 7 |
+
"tensor_name": "chunk_xs",
|
| 8 |
+
"calibration_dataset": "./calibration_dataset/chunk_xs.tar.gz",
|
| 9 |
+
"calibration_size": -1,
|
| 10 |
+
"calibration_format": "Numpy"
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"tensor_name": "chunk_lens",
|
| 14 |
+
"calibration_dataset": "./calibration_dataset/chunk_lens.tar.gz",
|
| 15 |
+
"calibration_size": -1,
|
| 16 |
+
"calibration_format": "Numpy"
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"tensor_name": "offset",
|
| 20 |
+
"calibration_dataset": "./calibration_dataset/offset.tar.gz",
|
| 21 |
+
"calibration_size": -1,
|
| 22 |
+
"calibration_format": "Numpy"
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"tensor_name": "att_cache",
|
| 26 |
+
"calibration_dataset": "./calibration_dataset/att_cache.tar.gz",
|
| 27 |
+
"calibration_size": -1,
|
| 28 |
+
"calibration_format": "Numpy"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"tensor_name": "cnn_cache",
|
| 32 |
+
"calibration_dataset": "./calibration_dataset/cnn_cache.tar.gz",
|
| 33 |
+
"calibration_size": -1,
|
| 34 |
+
"calibration_format": "Numpy"
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"tensor_name": "cache_mask",
|
| 38 |
+
"calibration_dataset": "./calibration_dataset/cache_mask.tar.gz",
|
| 39 |
+
"calibration_size": -1,
|
| 40 |
+
"calibration_format": "Numpy"
|
| 41 |
+
}
|
| 42 |
+
],
|
| 43 |
+
"layer_configs": [
|
| 44 |
+
{
|
| 45 |
+
"start_tensor_names": ["DEFAULT"],
|
| 46 |
+
"end_tensor_names": ["DEFAULT"],
|
| 47 |
+
"data_type": "U16"
|
| 48 |
+
}
|
| 49 |
+
],
|
| 50 |
+
"calibration_method": "MinMax",
|
| 51 |
+
"conv_bias_data_type": "FP32",
|
| 52 |
+
"disable_auto_refine_scale": true,
|
| 53 |
+
"enable_smooth_quant": true,
|
| 54 |
+
"precision_analysis": true,
|
| 55 |
+
"precision_analysis_method": "EndToEnd",
|
| 56 |
+
"transformer_opt_level": 1
|
| 57 |
+
},
|
| 58 |
+
"input_processors": [
|
| 59 |
+
{
|
| 60 |
+
"tensor_name": "chunk_xs",
|
| 61 |
+
"src_dtype": "FP32"
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"tensor_name": "chunk_lens",
|
| 65 |
+
"src_dtype": "S32"
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"tensor_name": "offset",
|
| 69 |
+
"src_dtype": "S32"
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"tensor_name": "att_cache",
|
| 73 |
+
"src_dtype": "FP32"
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"tensor_name": "cnn_cache",
|
| 77 |
+
"src_dtype": "FP32"
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"tensor_name": "cache_mask",
|
| 81 |
+
"src_dtype": "FP32"
|
| 82 |
+
}
|
| 83 |
+
],
|
| 84 |
+
"compiler": {
|
| 85 |
+
"check": 0
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
demo.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:46dbc998c9d1d48111267c40741dd3200f2e5bcf4075f8c4c97f4451160dce50
|
| 3 |
+
size 134570
|
export_onnx.py
ADDED
|
@@ -0,0 +1,1723 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import copy
|
| 19 |
+
import logging
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
import tarfile
|
| 23 |
+
import urllib.request
|
| 24 |
+
|
| 25 |
+
import numpy as np
|
| 26 |
+
import torch
|
| 27 |
+
import torch.nn.functional as F
|
| 28 |
+
import yaml
|
| 29 |
+
from wenet.transformer.ctc import CTC
|
| 30 |
+
from wenet.transformer.decoder import TransformerDecoder
|
| 31 |
+
from wenet.transformer.encoder import BaseEncoder
|
| 32 |
+
from wenet.utils.init_model import init_model
|
| 33 |
+
from wenet.utils.mask import make_pad_mask
|
| 34 |
+
from typing import List, Tuple
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
import onnx
|
| 38 |
+
import onnxruntime
|
| 39 |
+
from onnx import helper, numpy_helper
|
| 40 |
+
from onnxsim import simplify
|
| 41 |
+
except ImportError:
|
| 42 |
+
print("Please install onnxruntime!")
|
| 43 |
+
sys.exit(1)
|
| 44 |
+
|
| 45 |
+
logger = logging.getLogger(__file__)
|
| 46 |
+
logger.setLevel(logging.INFO)
|
| 47 |
+
|
| 48 |
+
DEFAULT_PRETRAINED_MODEL_URL = (
|
| 49 |
+
"https://huggingface.co/openspeech/wenet-models/resolve/main/"
|
| 50 |
+
"aishell_u2pp_conformer_exp.tar.gz")
|
| 51 |
+
DEFAULT_PRETRAINED_MODEL_DIR = "pretrained/aishell_u2pp_conformer_exp"
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def safe_extract_tar(tar, output_dir):
|
| 55 |
+
output_dir = os.path.abspath(output_dir)
|
| 56 |
+
for member in tar.getmembers():
|
| 57 |
+
member_path = os.path.abspath(os.path.join(output_dir, member.name))
|
| 58 |
+
if not member_path.startswith(output_dir + os.sep):
|
| 59 |
+
raise RuntimeError(f"Unsafe tar member path: {member.name}")
|
| 60 |
+
tar.extractall(output_dir)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def download_file(url, output_path):
|
| 64 |
+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 65 |
+
print(f"Downloading pretrained model from {url}")
|
| 66 |
+
print(f"Saving to {output_path}")
|
| 67 |
+
urllib.request.urlretrieve(url, output_path)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def prepare_pretrained_model(args):
|
| 71 |
+
model_dir = args.pretrained_model_dir
|
| 72 |
+
archive_dir = os.path.dirname(model_dir.rstrip(os.sep)) or "."
|
| 73 |
+
archive_path = os.path.join(
|
| 74 |
+
archive_dir, os.path.basename(model_dir.rstrip(os.sep)) + ".tar.gz")
|
| 75 |
+
|
| 76 |
+
if not os.path.exists(model_dir):
|
| 77 |
+
if not os.path.exists(archive_path):
|
| 78 |
+
download_file(args.pretrained_model_url, archive_path)
|
| 79 |
+
print(f"Extracting pretrained model to {archive_dir}")
|
| 80 |
+
with tarfile.open(archive_path, "r:gz") as tar:
|
| 81 |
+
safe_extract_tar(tar, archive_dir)
|
| 82 |
+
|
| 83 |
+
args.config = os.path.join(model_dir, "train.yaml")
|
| 84 |
+
args.checkpoint = os.path.join(model_dir, "final.pt")
|
| 85 |
+
args.cmvn_file = os.path.join(model_dir, "global_cmvn")
|
| 86 |
+
|
| 87 |
+
missing = [path for path in (args.config, args.checkpoint)
|
| 88 |
+
if not os.path.exists(path)]
|
| 89 |
+
if missing:
|
| 90 |
+
raise FileNotFoundError(
|
| 91 |
+
"Missing pretrained model files: " + ", ".join(missing))
|
| 92 |
+
|
| 93 |
+
print(f"Using config: {args.config}")
|
| 94 |
+
print(f"Using checkpoint: {args.checkpoint}")
|
| 95 |
+
if os.path.exists(args.cmvn_file):
|
| 96 |
+
print(f"Using CMVN: {args.cmvn_file}")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _constant_node_value(node):
|
| 100 |
+
if node is None or node.op_type != "Constant":
|
| 101 |
+
return None
|
| 102 |
+
for attr in node.attribute:
|
| 103 |
+
if attr.name == "value":
|
| 104 |
+
return numpy_helper.to_array(attr.t)
|
| 105 |
+
return None
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def _attribute_value(attr):
|
| 109 |
+
return helper.get_attribute_value(attr)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def _get_attr(node, name, default=None):
|
| 113 |
+
for attr in node.attribute:
|
| 114 |
+
if attr.name == name:
|
| 115 |
+
return _attribute_value(attr)
|
| 116 |
+
return default
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _cast_to_onnx_dtype(value, to_dtype):
|
| 120 |
+
tensor_type = onnx.TensorProto.DataType.Name(to_dtype).lower()
|
| 121 |
+
dtype_map = {
|
| 122 |
+
"float": np.float32,
|
| 123 |
+
"double": np.float64,
|
| 124 |
+
"float16": np.float16,
|
| 125 |
+
"int64": np.int64,
|
| 126 |
+
"int32": np.int32,
|
| 127 |
+
"int16": np.int16,
|
| 128 |
+
"int8": np.int8,
|
| 129 |
+
"uint64": np.uint64,
|
| 130 |
+
"uint32": np.uint32,
|
| 131 |
+
"uint16": np.uint16,
|
| 132 |
+
"uint8": np.uint8,
|
| 133 |
+
"bool": np.bool_,
|
| 134 |
+
}
|
| 135 |
+
if tensor_type not in dtype_map:
|
| 136 |
+
return None
|
| 137 |
+
return value.astype(dtype_map[tensor_type])
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _shape_from_value_info(value_info):
|
| 141 |
+
if not value_info.type.HasField("tensor_type"):
|
| 142 |
+
return None
|
| 143 |
+
if not value_info.type.tensor_type.shape.dim:
|
| 144 |
+
return None
|
| 145 |
+
shape = []
|
| 146 |
+
for dim in value_info.type.tensor_type.shape.dim:
|
| 147 |
+
if dim.HasField("dim_value") and dim.dim_value > 0:
|
| 148 |
+
shape.append(dim.dim_value)
|
| 149 |
+
else:
|
| 150 |
+
return None
|
| 151 |
+
return tuple(shape)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _collect_static_shapes(model):
|
| 155 |
+
inferred = onnx.shape_inference.infer_shapes(model)
|
| 156 |
+
shapes = {}
|
| 157 |
+
for value_info in list(inferred.graph.input) + list(
|
| 158 |
+
inferred.graph.value_info) + list(inferred.graph.output):
|
| 159 |
+
shape = _shape_from_value_info(value_info)
|
| 160 |
+
if shape is not None:
|
| 161 |
+
shapes[value_info.name] = shape
|
| 162 |
+
for initializer in inferred.graph.initializer:
|
| 163 |
+
shapes[initializer.name] = tuple(initializer.dims)
|
| 164 |
+
return shapes
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _eval_static_node(node, inputs, static_shapes):
|
| 168 |
+
if node.op_type == "Constant":
|
| 169 |
+
return _constant_node_value(node)
|
| 170 |
+
if node.op_type != "Shape" and any(value is None for value in inputs):
|
| 171 |
+
return None
|
| 172 |
+
|
| 173 |
+
try:
|
| 174 |
+
if node.op_type == "Add":
|
| 175 |
+
return np.add(inputs[0], inputs[1])
|
| 176 |
+
if node.op_type == "Sub":
|
| 177 |
+
return np.subtract(inputs[0], inputs[1])
|
| 178 |
+
if node.op_type == "Mul":
|
| 179 |
+
return np.multiply(inputs[0], inputs[1])
|
| 180 |
+
if node.op_type == "Div":
|
| 181 |
+
return np.divide(inputs[0], inputs[1])
|
| 182 |
+
if node.op_type == "Equal":
|
| 183 |
+
return np.equal(inputs[0], inputs[1])
|
| 184 |
+
if node.op_type == "Greater":
|
| 185 |
+
return np.greater(inputs[0], inputs[1])
|
| 186 |
+
if node.op_type == "GreaterOrEqual":
|
| 187 |
+
return np.greater_equal(inputs[0], inputs[1])
|
| 188 |
+
if node.op_type == "Less":
|
| 189 |
+
return np.less(inputs[0], inputs[1])
|
| 190 |
+
if node.op_type == "LessOrEqual":
|
| 191 |
+
return np.less_equal(inputs[0], inputs[1])
|
| 192 |
+
if node.op_type == "Where":
|
| 193 |
+
return np.where(inputs[0], inputs[1], inputs[2])
|
| 194 |
+
if node.op_type == "Concat":
|
| 195 |
+
axis = _get_attr(node, "axis", 0)
|
| 196 |
+
return np.concatenate(inputs, axis=axis)
|
| 197 |
+
if node.op_type == "Unsqueeze":
|
| 198 |
+
axes = _get_attr(node, "axes")
|
| 199 |
+
if axes is None and len(inputs) > 1:
|
| 200 |
+
axes = inputs[1]
|
| 201 |
+
axes = tuple(int(axis) for axis in np.asarray(axes).reshape(-1))
|
| 202 |
+
return np.expand_dims(inputs[0], axis=axes)
|
| 203 |
+
if node.op_type == "Squeeze":
|
| 204 |
+
axes = _get_attr(node, "axes")
|
| 205 |
+
if axes is None and len(inputs) > 1:
|
| 206 |
+
axes = inputs[1]
|
| 207 |
+
if axes is None:
|
| 208 |
+
return np.squeeze(inputs[0])
|
| 209 |
+
axes = tuple(int(axis) for axis in np.asarray(axes).reshape(-1))
|
| 210 |
+
return np.squeeze(inputs[0], axis=axes)
|
| 211 |
+
if node.op_type == "Cast":
|
| 212 |
+
return _cast_to_onnx_dtype(inputs[0], _get_attr(node, "to"))
|
| 213 |
+
if node.op_type == "Reshape":
|
| 214 |
+
return np.reshape(inputs[0], tuple(int(i) for i in inputs[1]))
|
| 215 |
+
if node.op_type == "Shape":
|
| 216 |
+
if inputs[0] is not None:
|
| 217 |
+
shape = inputs[0].shape
|
| 218 |
+
else:
|
| 219 |
+
shape = static_shapes.get(node.input[0])
|
| 220 |
+
if shape is None:
|
| 221 |
+
return None
|
| 222 |
+
return np.asarray(shape, dtype=np.int64)
|
| 223 |
+
if node.op_type == "Slice":
|
| 224 |
+
data = inputs[0]
|
| 225 |
+
starts = np.asarray(inputs[1]).reshape(-1)
|
| 226 |
+
ends = np.asarray(inputs[2]).reshape(-1)
|
| 227 |
+
axes = (np.asarray(inputs[3]).reshape(-1)
|
| 228 |
+
if len(inputs) > 3 and inputs[3] is not None else
|
| 229 |
+
np.arange(len(starts)))
|
| 230 |
+
steps = (np.asarray(inputs[4]).reshape(-1)
|
| 231 |
+
if len(inputs) > 4 and inputs[4] is not None else
|
| 232 |
+
np.ones(len(starts), dtype=np.int64))
|
| 233 |
+
slices = [slice(None)] * data.ndim
|
| 234 |
+
for start, end, axis, step in zip(starts, ends, axes, steps):
|
| 235 |
+
axis = int(axis)
|
| 236 |
+
start = int(start)
|
| 237 |
+
end = int(end)
|
| 238 |
+
step = int(step)
|
| 239 |
+
if end >= np.iinfo(np.int32).max:
|
| 240 |
+
end = None
|
| 241 |
+
if end <= np.iinfo(np.int32).min:
|
| 242 |
+
end = None
|
| 243 |
+
slices[axis] = slice(start, end, step)
|
| 244 |
+
return data[tuple(slices)]
|
| 245 |
+
if node.op_type == "Gather":
|
| 246 |
+
axis = _get_attr(node, "axis", 0)
|
| 247 |
+
return np.take(inputs[0], inputs[1], axis=axis)
|
| 248 |
+
except Exception:
|
| 249 |
+
return None
|
| 250 |
+
return None
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def _constant_node(output_name, value, name):
|
| 254 |
+
const_tensor = numpy_helper.from_array(np.asarray(value),
|
| 255 |
+
name=output_name + "_value")
|
| 256 |
+
return helper.make_node("Constant",
|
| 257 |
+
inputs=[],
|
| 258 |
+
outputs=[output_name],
|
| 259 |
+
name=name,
|
| 260 |
+
value=const_tensor)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
def _node_attributes(node):
|
| 264 |
+
return {attr.name: helper.get_attribute_value(attr) for attr in node.attribute}
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
def _copy_node(node, inputs=None, outputs=None, name=None):
|
| 268 |
+
copied = copy.deepcopy(node)
|
| 269 |
+
if inputs is not None:
|
| 270 |
+
del copied.input[:]
|
| 271 |
+
copied.input.extend(inputs)
|
| 272 |
+
if outputs is not None:
|
| 273 |
+
del copied.output[:]
|
| 274 |
+
copied.output.extend(outputs)
|
| 275 |
+
if name is not None:
|
| 276 |
+
copied.name = name
|
| 277 |
+
return copied
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def _producer_map(model):
|
| 281 |
+
return {output: node for node in model.graph.node for output in node.output}
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
def _unsqueeze_greater_equal_pattern(producer, value_name):
|
| 285 |
+
unsqueeze = producer.get(value_name)
|
| 286 |
+
if unsqueeze is None or unsqueeze.op_type != "Unsqueeze":
|
| 287 |
+
return None, None
|
| 288 |
+
compare = producer.get(unsqueeze.input[0])
|
| 289 |
+
if compare is None or compare.op_type != "GreaterOrEqual":
|
| 290 |
+
return None, None
|
| 291 |
+
return unsqueeze, compare
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def rewrite_pulsar2_bool_not(onnx_path):
|
| 295 |
+
"""Remove simple Not nodes that Pulsar2 quantization can cast to float.
|
| 296 |
+
|
| 297 |
+
The encoder mask contains Not(Unsqueeze(GreaterOrEqual(...))) and another
|
| 298 |
+
Not over a sliced version of that mask. Pulsar2 can quantize the Not input
|
| 299 |
+
to FP32 and then fail because bitwise Not only accepts bool/integer tensors.
|
| 300 |
+
Rewriting those patterns keeps the graph boolean-equivalent without Not.
|
| 301 |
+
"""
|
| 302 |
+
model = onnx.load(onnx_path)
|
| 303 |
+
producer = _producer_map(model)
|
| 304 |
+
rewritten = 0
|
| 305 |
+
new_nodes = []
|
| 306 |
+
|
| 307 |
+
for node in model.graph.node:
|
| 308 |
+
if node.op_type != "Not":
|
| 309 |
+
new_nodes.append(node)
|
| 310 |
+
continue
|
| 311 |
+
|
| 312 |
+
compare = producer.get(node.input[0])
|
| 313 |
+
if compare is not None and compare.op_type == "GreaterOrEqual":
|
| 314 |
+
less = helper.make_node("Less",
|
| 315 |
+
inputs=list(compare.input),
|
| 316 |
+
outputs=list(node.output),
|
| 317 |
+
name=node.name + "_less",
|
| 318 |
+
**_node_attributes(compare))
|
| 319 |
+
new_nodes.append(less)
|
| 320 |
+
rewritten += 1
|
| 321 |
+
continue
|
| 322 |
+
|
| 323 |
+
unsqueeze, compare = _unsqueeze_greater_equal_pattern(
|
| 324 |
+
producer, node.input[0])
|
| 325 |
+
if unsqueeze is not None:
|
| 326 |
+
less_output = node.output[0] + "_less"
|
| 327 |
+
less = helper.make_node("Less",
|
| 328 |
+
inputs=list(compare.input),
|
| 329 |
+
outputs=[less_output],
|
| 330 |
+
name=node.name + "_less",
|
| 331 |
+
**_node_attributes(compare))
|
| 332 |
+
rewritten_unsqueeze = _copy_node(
|
| 333 |
+
unsqueeze,
|
| 334 |
+
inputs=[less_output] + list(unsqueeze.input[1:]),
|
| 335 |
+
outputs=list(node.output),
|
| 336 |
+
name=node.name + "_unsqueeze")
|
| 337 |
+
new_nodes.extend([less, rewritten_unsqueeze])
|
| 338 |
+
rewritten += 1
|
| 339 |
+
continue
|
| 340 |
+
|
| 341 |
+
slice_1 = producer.get(node.input[0])
|
| 342 |
+
slice_0 = producer.get(slice_1.input[0]) if slice_1 else None
|
| 343 |
+
inner_not = producer.get(slice_0.input[0]) if slice_0 else None
|
| 344 |
+
if (slice_1 is not None and slice_1.op_type == "Slice"
|
| 345 |
+
and slice_0 is not None and slice_0.op_type == "Slice"
|
| 346 |
+
and inner_not is not None and inner_not.op_type == "Not"):
|
| 347 |
+
unsqueeze, _ = _unsqueeze_greater_equal_pattern(
|
| 348 |
+
producer, inner_not.input[0])
|
| 349 |
+
if unsqueeze is not None:
|
| 350 |
+
slice_0_output = node.output[0] + "_slice0"
|
| 351 |
+
rewritten_slice_0 = _copy_node(
|
| 352 |
+
slice_0,
|
| 353 |
+
inputs=[unsqueeze.output[0]] + list(slice_0.input[1:]),
|
| 354 |
+
outputs=[slice_0_output],
|
| 355 |
+
name=node.name + "_slice0")
|
| 356 |
+
rewritten_slice_1 = _copy_node(
|
| 357 |
+
slice_1,
|
| 358 |
+
inputs=[slice_0_output] + list(slice_1.input[1:]),
|
| 359 |
+
outputs=list(node.output),
|
| 360 |
+
name=node.name + "_slice1")
|
| 361 |
+
new_nodes.extend([rewritten_slice_0, rewritten_slice_1])
|
| 362 |
+
rewritten += 1
|
| 363 |
+
continue
|
| 364 |
+
|
| 365 |
+
new_nodes.append(node)
|
| 366 |
+
|
| 367 |
+
if rewritten:
|
| 368 |
+
del model.graph.node[:]
|
| 369 |
+
model.graph.node.extend(new_nodes)
|
| 370 |
+
onnx.checker.check_model(model)
|
| 371 |
+
onnx.save(model, onnx_path)
|
| 372 |
+
print(f"Rewrote {rewritten} bool Not node(s) in {onnx_path}")
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def rewrite_pulsar2_bool_and(onnx_path):
|
| 376 |
+
"""Replace boolean And with arithmetic comparison for Pulsar2 quantization."""
|
| 377 |
+
model = onnx.load(onnx_path)
|
| 378 |
+
rewritten = 0
|
| 379 |
+
new_nodes = []
|
| 380 |
+
|
| 381 |
+
for node in model.graph.node:
|
| 382 |
+
if node.op_type != "And" or len(node.input) != 2 or len(
|
| 383 |
+
node.output) != 1:
|
| 384 |
+
new_nodes.append(node)
|
| 385 |
+
continue
|
| 386 |
+
|
| 387 |
+
left = node.output[0] + "_left_i32"
|
| 388 |
+
right = node.output[0] + "_right_i32"
|
| 389 |
+
added = node.output[0] + "_sum"
|
| 390 |
+
threshold = node.output[0] + "_threshold"
|
| 391 |
+
new_nodes.append(
|
| 392 |
+
helper.make_node("Cast",
|
| 393 |
+
inputs=[node.input[0]],
|
| 394 |
+
outputs=[left],
|
| 395 |
+
name=node.name + "_cast_left",
|
| 396 |
+
to=onnx.TensorProto.INT32))
|
| 397 |
+
new_nodes.append(
|
| 398 |
+
helper.make_node("Cast",
|
| 399 |
+
inputs=[node.input[1]],
|
| 400 |
+
outputs=[right],
|
| 401 |
+
name=node.name + "_cast_right",
|
| 402 |
+
to=onnx.TensorProto.INT32))
|
| 403 |
+
new_nodes.append(
|
| 404 |
+
helper.make_node("Add",
|
| 405 |
+
inputs=[left, right],
|
| 406 |
+
outputs=[added],
|
| 407 |
+
name=node.name + "_add"))
|
| 408 |
+
new_nodes.append(
|
| 409 |
+
_constant_node(threshold, np.asarray(1, dtype=np.int32),
|
| 410 |
+
node.name + "_threshold"))
|
| 411 |
+
new_nodes.append(
|
| 412 |
+
helper.make_node("Greater",
|
| 413 |
+
inputs=[added, threshold],
|
| 414 |
+
outputs=list(node.output),
|
| 415 |
+
name=node.name + "_greater"))
|
| 416 |
+
rewritten += 1
|
| 417 |
+
|
| 418 |
+
if rewritten:
|
| 419 |
+
del model.graph.node[:]
|
| 420 |
+
model.graph.node.extend(new_nodes)
|
| 421 |
+
onnx.checker.check_model(model)
|
| 422 |
+
onnx.save(model, onnx_path)
|
| 423 |
+
print(f"Rewrote {rewritten} bool And node(s) in {onnx_path}")
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
def simplify_pulsar2_onnx(onnx_path):
|
| 427 |
+
model = onnx.load(onnx_path)
|
| 428 |
+
sim_model, ok = simplify(model)
|
| 429 |
+
if not ok:
|
| 430 |
+
raise RuntimeError(f"onnxsim failed to validate {onnx_path}")
|
| 431 |
+
onnx.checker.check_model(sim_model)
|
| 432 |
+
onnx.save(sim_model, onnx_path)
|
| 433 |
+
print(f"Simplified {onnx_path} for Pulsar2")
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def fold_static_pulsar2_subgraphs(onnx_path):
|
| 437 |
+
"""Fold static ONNX patterns that Pulsar2 5.0 cannot infer reliably.
|
| 438 |
+
|
| 439 |
+
Pulsar2 5.0 can fail shape inference on ConstantOfShape when its input is
|
| 440 |
+
a constant tensor value instead of an initializer. The legacy exporter emits
|
| 441 |
+
this pattern for masks/padding in the encoder graphs. It can also fail when
|
| 442 |
+
an Expand shape is produced by a constant-only subgraph such as
|
| 443 |
+
Mul/Equal/Where. Fold those static pieces before handing the model to
|
| 444 |
+
Pulsar2.
|
| 445 |
+
"""
|
| 446 |
+
model = onnx.load(onnx_path)
|
| 447 |
+
static_shapes = _collect_static_shapes(model)
|
| 448 |
+
constants = {
|
| 449 |
+
initializer.name: numpy_helper.to_array(initializer)
|
| 450 |
+
for initializer in model.graph.initializer
|
| 451 |
+
}
|
| 452 |
+
folded = 0
|
| 453 |
+
new_nodes = []
|
| 454 |
+
for node in model.graph.node:
|
| 455 |
+
inputs = [constants.get(name) for name in node.input]
|
| 456 |
+
if node.op_type == "ConstantOfShape" and node.input:
|
| 457 |
+
shape_value = inputs[0]
|
| 458 |
+
if shape_value is not None:
|
| 459 |
+
fill_value = np.array(0, dtype=np.float32)
|
| 460 |
+
for attr in node.attribute:
|
| 461 |
+
if attr.name == "value":
|
| 462 |
+
fill_value = numpy_helper.to_array(attr.t)
|
| 463 |
+
break
|
| 464 |
+
shape = tuple(int(dim)
|
| 465 |
+
for dim in np.asarray(shape_value).reshape(-1))
|
| 466 |
+
value = np.full(shape,
|
| 467 |
+
fill_value.reshape(-1)[0],
|
| 468 |
+
dtype=fill_value.dtype)
|
| 469 |
+
else:
|
| 470 |
+
value = None
|
| 471 |
+
else:
|
| 472 |
+
value = _eval_static_node(node, inputs, static_shapes)
|
| 473 |
+
|
| 474 |
+
if value is None or len(node.output) != 1:
|
| 475 |
+
new_nodes.append(node)
|
| 476 |
+
continue
|
| 477 |
+
|
| 478 |
+
constants[node.output[0]] = value
|
| 479 |
+
new_nodes.append(_constant_node(node.output[0], value, node.name))
|
| 480 |
+
folded += 1
|
| 481 |
+
|
| 482 |
+
if folded:
|
| 483 |
+
del model.graph.node[:]
|
| 484 |
+
model.graph.node.extend(new_nodes)
|
| 485 |
+
onnx.checker.check_model(model)
|
| 486 |
+
onnx.save(model, onnx_path)
|
| 487 |
+
print(f"Folded {folded} static node(s) in {onnx_path}")
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
class Encoder(torch.nn.Module):
|
| 491 |
+
|
| 492 |
+
def __init__(self, encoder: BaseEncoder, ctc: CTC, beam_size: int = 10):
|
| 493 |
+
super().__init__()
|
| 494 |
+
self.encoder = encoder
|
| 495 |
+
self.ctc = ctc
|
| 496 |
+
self.beam_size = beam_size
|
| 497 |
+
|
| 498 |
+
def forward(
|
| 499 |
+
self,
|
| 500 |
+
speech: torch.Tensor,
|
| 501 |
+
speech_lengths: torch.Tensor,
|
| 502 |
+
):
|
| 503 |
+
"""Encoder
|
| 504 |
+
Args:
|
| 505 |
+
speech: (Batch, Length, ...)
|
| 506 |
+
speech_lengths: (Batch, )
|
| 507 |
+
Returns:
|
| 508 |
+
encoder_out: B x T x F
|
| 509 |
+
encoder_out_lens: B
|
| 510 |
+
ctc_log_probs: B x T x V
|
| 511 |
+
beam_log_probs: B x T x beam_size
|
| 512 |
+
beam_log_probs_idx: B x T x beam_size
|
| 513 |
+
"""
|
| 514 |
+
encoder_out, encoder_mask = self.encoder(speech, speech_lengths, -1,
|
| 515 |
+
-1)
|
| 516 |
+
encoder_out_lens = encoder_mask.squeeze(1).sum(1)
|
| 517 |
+
# ctc_log_probs = self.ctc.log_softmax(encoder_out)
|
| 518 |
+
ctc_log_probs = self.ctc.linear(encoder_out)
|
| 519 |
+
encoder_out_lens = encoder_out_lens.int()
|
| 520 |
+
beam_log_probs, beam_log_probs_idx = torch.topk(ctc_log_probs,
|
| 521 |
+
self.beam_size,
|
| 522 |
+
dim=2)
|
| 523 |
+
return (
|
| 524 |
+
encoder_out,
|
| 525 |
+
encoder_out_lens,
|
| 526 |
+
ctc_log_probs,
|
| 527 |
+
beam_log_probs,
|
| 528 |
+
beam_log_probs_idx,
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
class StreamingEncoder(torch.nn.Module):
|
| 533 |
+
|
| 534 |
+
def __init__(
|
| 535 |
+
self,
|
| 536 |
+
model,
|
| 537 |
+
required_cache_size,
|
| 538 |
+
beam_size,
|
| 539 |
+
transformer=False,
|
| 540 |
+
return_ctc_logprobs=False,
|
| 541 |
+
):
|
| 542 |
+
super().__init__()
|
| 543 |
+
self.ctc = model.ctc
|
| 544 |
+
self.subsampling_rate = model.encoder.embed.subsampling_rate
|
| 545 |
+
self.embed = model.encoder.embed
|
| 546 |
+
self.global_cmvn = model.encoder.global_cmvn
|
| 547 |
+
self.required_cache_size = required_cache_size
|
| 548 |
+
self.beam_size = beam_size
|
| 549 |
+
self.encoder = model.encoder
|
| 550 |
+
self.transformer = transformer
|
| 551 |
+
self.return_ctc_logprobs = return_ctc_logprobs
|
| 552 |
+
|
| 553 |
+
def forward(self, chunk_xs, chunk_lens, offset, att_cache, cnn_cache,
|
| 554 |
+
cache_mask):
|
| 555 |
+
"""Streaming Encoder
|
| 556 |
+
Args:
|
| 557 |
+
xs (torch.Tensor): chunk input, with shape (b, time, mel-dim),
|
| 558 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 559 |
+
subsample.right_context + 1`
|
| 560 |
+
offset (torch.Tensor): offset with shape (b, 1)
|
| 561 |
+
1 is retained for triton deployment
|
| 562 |
+
required_cache_size (int): cache size required for next chunk
|
| 563 |
+
compuation
|
| 564 |
+
> 0: actual cache size
|
| 565 |
+
<= 0: not allowed in streaming gpu encoder `
|
| 566 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 567 |
+
transformer/conformer attention, with shape
|
| 568 |
+
(b, elayers, head, cache_t1, d_k * 2), where
|
| 569 |
+
`head * d_k == hidden-dim` and
|
| 570 |
+
`cache_t1 == chunk_size * num_decoding_left_chunks`.
|
| 571 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 572 |
+
(b, elayers, b, hidden-dim, cache_t2), where
|
| 573 |
+
`cache_t2 == cnn.lorder - 1`
|
| 574 |
+
cache_mask: (torch.Tensor): cache mask with shape (b, required_cache_size)
|
| 575 |
+
in a batch of request, each request may have different
|
| 576 |
+
history cache. Cache mask is used to indidate the effective
|
| 577 |
+
cache for each request
|
| 578 |
+
Returns:
|
| 579 |
+
torch.Tensor: log probabilities of ctc output and cutoff by beam size
|
| 580 |
+
with shape (b, chunk_size, beam)
|
| 581 |
+
torch.Tensor: index of top beam size probabilities for each timestep
|
| 582 |
+
with shape (b, chunk_size, beam)
|
| 583 |
+
torch.Tensor: output of current input xs,
|
| 584 |
+
with shape (b, chunk_size, hidden-dim).
|
| 585 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 586 |
+
same shape (b, elayers, head, cache_t1, d_k * 2)
|
| 587 |
+
as the original att_cache
|
| 588 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 589 |
+
same shape as the original cnn_cache.
|
| 590 |
+
torch.Tensor: new cache mask, with same shape as the original
|
| 591 |
+
cache mask
|
| 592 |
+
"""
|
| 593 |
+
offset = offset.squeeze(1)
|
| 594 |
+
T = chunk_xs.size(1)
|
| 595 |
+
chunk_mask = ~make_pad_mask(chunk_lens, T).unsqueeze(1)
|
| 596 |
+
# B X 1 X T
|
| 597 |
+
chunk_mask = chunk_mask.to(chunk_xs.dtype)
|
| 598 |
+
# transpose batch & num_layers dim
|
| 599 |
+
att_cache = torch.transpose(att_cache, 0, 1)
|
| 600 |
+
cnn_cache = torch.transpose(cnn_cache, 0, 1)
|
| 601 |
+
|
| 602 |
+
# rewrite encoder.forward_chunk
|
| 603 |
+
# <---------forward_chunk START--------->
|
| 604 |
+
xs = self.global_cmvn(chunk_xs)
|
| 605 |
+
# chunk mask is important for batch inferencing since
|
| 606 |
+
# different sequence in a batch has different length
|
| 607 |
+
xs, pos_emb, chunk_mask = self.embed(xs, chunk_mask, offset)
|
| 608 |
+
cache_size = att_cache.size(3) # required cache size
|
| 609 |
+
masks = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 610 |
+
index = offset - cache_size
|
| 611 |
+
|
| 612 |
+
pos_emb = self.embed.position_encoding(index, cache_size + xs.size(1))
|
| 613 |
+
pos_emb = pos_emb.to(dtype=xs.dtype)
|
| 614 |
+
|
| 615 |
+
next_cache_start = -self.required_cache_size
|
| 616 |
+
r_cache_mask = masks[:, :, next_cache_start:]
|
| 617 |
+
|
| 618 |
+
r_att_cache = []
|
| 619 |
+
r_cnn_cache = []
|
| 620 |
+
for i, layer in enumerate(self.encoder.encoders):
|
| 621 |
+
i_kv_cache = att_cache[i]
|
| 622 |
+
size = att_cache.size(-1) // 2
|
| 623 |
+
kv_cache = (i_kv_cache[:, :, :, :size], i_kv_cache[:, :, :, size:])
|
| 624 |
+
xs, _, new_kv_cache, new_cnn_cache = layer(
|
| 625 |
+
xs,
|
| 626 |
+
masks,
|
| 627 |
+
pos_emb,
|
| 628 |
+
att_cache=kv_cache,
|
| 629 |
+
cnn_cache=cnn_cache[i],
|
| 630 |
+
)
|
| 631 |
+
# shape(new_att_cache) is (B, head, attention_key_size, d_k * 2),
|
| 632 |
+
# shape(new_cnn_cache) is (B, hidden-dim, cache_t2)
|
| 633 |
+
new_att_cache = torch.cat(new_kv_cache, dim=-1)
|
| 634 |
+
r_att_cache.append(
|
| 635 |
+
new_att_cache[:, :, next_cache_start:, :].unsqueeze(1))
|
| 636 |
+
if not self.transformer:
|
| 637 |
+
r_cnn_cache.append(new_cnn_cache.unsqueeze(1))
|
| 638 |
+
if self.encoder.normalize_before:
|
| 639 |
+
chunk_out = self.encoder.after_norm(xs)
|
| 640 |
+
else:
|
| 641 |
+
chunk_out = xs
|
| 642 |
+
|
| 643 |
+
r_att_cache = torch.cat(r_att_cache, dim=1) # concat on layers idx
|
| 644 |
+
if not self.transformer:
|
| 645 |
+
r_cnn_cache = torch.cat(r_cnn_cache, dim=1) # concat on layers
|
| 646 |
+
|
| 647 |
+
# <---------forward_chunk END--------->
|
| 648 |
+
|
| 649 |
+
# log_ctc_probs = self.ctc.log_softmax(chunk_out)
|
| 650 |
+
log_ctc_probs = self.ctc.linear(chunk_out)
|
| 651 |
+
log_probs, log_probs_idx = torch.topk(log_ctc_probs,
|
| 652 |
+
self.beam_size,
|
| 653 |
+
dim=2)
|
| 654 |
+
log_probs = log_probs.to(chunk_xs.dtype)
|
| 655 |
+
|
| 656 |
+
r_offset = offset + chunk_out.shape[1]
|
| 657 |
+
# the below ops not supported in Tensorrt
|
| 658 |
+
# chunk_out_lens = torch.div(chunk_lens, subsampling_rate,
|
| 659 |
+
# rounding_mode='floor')
|
| 660 |
+
chunk_out_lens = chunk_lens // self.subsampling_rate
|
| 661 |
+
r_offset = r_offset.unsqueeze(1)
|
| 662 |
+
if self.return_ctc_logprobs:
|
| 663 |
+
return (
|
| 664 |
+
log_ctc_probs,
|
| 665 |
+
chunk_out,
|
| 666 |
+
chunk_out_lens,
|
| 667 |
+
r_offset,
|
| 668 |
+
r_att_cache,
|
| 669 |
+
r_cnn_cache,
|
| 670 |
+
r_cache_mask,
|
| 671 |
+
)
|
| 672 |
+
else:
|
| 673 |
+
return (
|
| 674 |
+
log_probs,
|
| 675 |
+
log_probs_idx,
|
| 676 |
+
chunk_out,
|
| 677 |
+
chunk_out_lens,
|
| 678 |
+
r_offset,
|
| 679 |
+
r_att_cache,
|
| 680 |
+
r_cnn_cache,
|
| 681 |
+
r_cache_mask,
|
| 682 |
+
)
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
class StreamingSqueezeformerEncoder(torch.nn.Module):
|
| 686 |
+
|
| 687 |
+
def __init__(self, model, required_cache_size, beam_size):
|
| 688 |
+
super().__init__()
|
| 689 |
+
self.ctc = model.ctc
|
| 690 |
+
self.subsampling_rate = model.encoder.embed.subsampling_rate
|
| 691 |
+
self.embed = model.encoder.embed
|
| 692 |
+
self.global_cmvn = model.encoder.global_cmvn
|
| 693 |
+
self.required_cache_size = required_cache_size
|
| 694 |
+
self.beam_size = beam_size
|
| 695 |
+
self.encoder = model.encoder
|
| 696 |
+
self.reduce_idx = model.encoder.reduce_idx
|
| 697 |
+
self.recover_idx = model.encoder.recover_idx
|
| 698 |
+
if self.reduce_idx is None:
|
| 699 |
+
self.time_reduce = None
|
| 700 |
+
else:
|
| 701 |
+
if self.recover_idx is None:
|
| 702 |
+
self.time_reduce = "normal" # no recovery at the end
|
| 703 |
+
else:
|
| 704 |
+
self.time_reduce = "recover" # recovery at the end
|
| 705 |
+
assert len(self.reduce_idx) == len(self.recover_idx)
|
| 706 |
+
|
| 707 |
+
def calculate_downsampling_factor(self, i: int) -> int:
|
| 708 |
+
if self.reduce_idx is None:
|
| 709 |
+
return 1
|
| 710 |
+
else:
|
| 711 |
+
reduce_exp, recover_exp = 0, 0
|
| 712 |
+
for exp, rd_idx in enumerate(self.reduce_idx):
|
| 713 |
+
if i >= rd_idx:
|
| 714 |
+
reduce_exp = exp + 1
|
| 715 |
+
if self.recover_idx is not None:
|
| 716 |
+
for exp, rc_idx in enumerate(self.recover_idx):
|
| 717 |
+
if i >= rc_idx:
|
| 718 |
+
recover_exp = exp + 1
|
| 719 |
+
return int(2**(reduce_exp - recover_exp))
|
| 720 |
+
|
| 721 |
+
def forward(self, chunk_xs, chunk_lens, offset, att_cache, cnn_cache,
|
| 722 |
+
cache_mask):
|
| 723 |
+
"""Streaming Encoder
|
| 724 |
+
Args:
|
| 725 |
+
xs (torch.Tensor): chunk input, with shape (b, time, mel-dim),
|
| 726 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 727 |
+
subsample.right_context + 1`
|
| 728 |
+
offset (torch.Tensor): offset with shape (b, 1)
|
| 729 |
+
1 is retained for triton deployment
|
| 730 |
+
required_cache_size (int): cache size required for next chunk
|
| 731 |
+
compuation
|
| 732 |
+
> 0: actual cache size
|
| 733 |
+
<= 0: not allowed in streaming gpu encoder `
|
| 734 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 735 |
+
transformer/conformer attention, with shape
|
| 736 |
+
(b, elayers, head, cache_t1, d_k * 2), where
|
| 737 |
+
`head * d_k == hidden-dim` and
|
| 738 |
+
`cache_t1 == chunk_size * num_decoding_left_chunks`.
|
| 739 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 740 |
+
(b, elayers, b, hidden-dim, cache_t2), where
|
| 741 |
+
`cache_t2 == cnn.lorder - 1`
|
| 742 |
+
cache_mask: (torch.Tensor): cache mask with shape (b, required_cache_size)
|
| 743 |
+
in a batch of request, each request may have different
|
| 744 |
+
history cache. Cache mask is used to indidate the effective
|
| 745 |
+
cache for each request
|
| 746 |
+
Returns:
|
| 747 |
+
torch.Tensor: log probabilities of ctc output and cutoff by beam size
|
| 748 |
+
with shape (b, chunk_size, beam)
|
| 749 |
+
torch.Tensor: index of top beam size probabilities for each timestep
|
| 750 |
+
with shape (b, chunk_size, beam)
|
| 751 |
+
torch.Tensor: output of current input xs,
|
| 752 |
+
with shape (b, chunk_size, hidden-dim).
|
| 753 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 754 |
+
same shape (b, elayers, head, cache_t1, d_k * 2)
|
| 755 |
+
as the original att_cache
|
| 756 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 757 |
+
same shape as the original cnn_cache.
|
| 758 |
+
torch.Tensor: new cache mask, with same shape as the original
|
| 759 |
+
cache mask
|
| 760 |
+
"""
|
| 761 |
+
offset = offset.squeeze(1)
|
| 762 |
+
T = chunk_xs.size(1)
|
| 763 |
+
chunk_mask = ~make_pad_mask(chunk_lens, T).unsqueeze(1)
|
| 764 |
+
# B X 1 X T
|
| 765 |
+
chunk_mask = chunk_mask.to(chunk_xs.dtype)
|
| 766 |
+
# transpose batch & num_layers dim
|
| 767 |
+
att_cache = torch.transpose(att_cache, 0, 1)
|
| 768 |
+
cnn_cache = torch.transpose(cnn_cache, 0, 1)
|
| 769 |
+
|
| 770 |
+
# rewrite encoder.forward_chunk
|
| 771 |
+
# <---------forward_chunk START--------->
|
| 772 |
+
xs = self.global_cmvn(chunk_xs)
|
| 773 |
+
# chunk mask is important for batch inferencing since
|
| 774 |
+
# different sequence in a batch has different length
|
| 775 |
+
xs, pos_emb, chunk_mask = self.embed(xs, chunk_mask, offset)
|
| 776 |
+
elayers, cache_size = att_cache.size(0), att_cache.size(3)
|
| 777 |
+
att_mask = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 778 |
+
index = offset - cache_size
|
| 779 |
+
|
| 780 |
+
pos_emb = self.embed.position_encoding(index, cache_size + xs.size(1))
|
| 781 |
+
pos_emb = pos_emb.to(dtype=xs.dtype)
|
| 782 |
+
|
| 783 |
+
next_cache_start = -self.required_cache_size
|
| 784 |
+
r_cache_mask = att_mask[:, :, next_cache_start:]
|
| 785 |
+
|
| 786 |
+
r_att_cache = []
|
| 787 |
+
r_cnn_cache = []
|
| 788 |
+
mask_pad = torch.ones(1,
|
| 789 |
+
xs.size(1),
|
| 790 |
+
device=xs.device,
|
| 791 |
+
dtype=torch.bool)
|
| 792 |
+
mask_pad = mask_pad.unsqueeze(1)
|
| 793 |
+
max_att_len: int = 0
|
| 794 |
+
recover_activations: List[Tuple[torch.Tensor, torch.Tensor,
|
| 795 |
+
torch.Tensor, torch.Tensor]] = []
|
| 796 |
+
index = 0
|
| 797 |
+
xs_lens = torch.tensor([xs.size(1)], device=xs.device, dtype=torch.int)
|
| 798 |
+
xs = self.encoder.preln(xs)
|
| 799 |
+
for i, layer in enumerate(self.encoder.encoders):
|
| 800 |
+
if self.reduce_idx is not None:
|
| 801 |
+
if self.time_reduce is not None and i in self.reduce_idx:
|
| 802 |
+
recover_activations.append(
|
| 803 |
+
(xs, att_mask, pos_emb, mask_pad))
|
| 804 |
+
(
|
| 805 |
+
xs,
|
| 806 |
+
xs_lens,
|
| 807 |
+
att_mask,
|
| 808 |
+
mask_pad,
|
| 809 |
+
) = self.encoder.time_reduction_layer(
|
| 810 |
+
xs, xs_lens, att_mask, mask_pad)
|
| 811 |
+
pos_emb = pos_emb[:, ::2, :]
|
| 812 |
+
if self.encoder.pos_enc_layer_type == "rel_pos_repaired":
|
| 813 |
+
pos_emb = pos_emb[:, :xs.size(1) * 2 - 1, :]
|
| 814 |
+
index += 1
|
| 815 |
+
|
| 816 |
+
if self.recover_idx is not None:
|
| 817 |
+
if self.time_reduce == "recover" and i in self.recover_idx:
|
| 818 |
+
index -= 1
|
| 819 |
+
(
|
| 820 |
+
recover_tensor,
|
| 821 |
+
recover_att_mask,
|
| 822 |
+
recover_pos_emb,
|
| 823 |
+
recover_mask_pad,
|
| 824 |
+
) = recover_activations[index]
|
| 825 |
+
# recover output length for ctc decode
|
| 826 |
+
xs = xs.unsqueeze(2).repeat(1, 1, 2, 1).flatten(1, 2)
|
| 827 |
+
xs = self.encoder.time_recover_layer(xs)
|
| 828 |
+
recoverd_t = recover_tensor.size(1)
|
| 829 |
+
xs = recover_tensor + xs[:, :recoverd_t, :].contiguous()
|
| 830 |
+
att_mask = recover_att_mask
|
| 831 |
+
pos_emb = recover_pos_emb
|
| 832 |
+
mask_pad = recover_mask_pad
|
| 833 |
+
|
| 834 |
+
factor = self.calculate_downsampling_factor(i)
|
| 835 |
+
|
| 836 |
+
xs, _, new_att_cache, new_cnn_cache = layer(
|
| 837 |
+
xs,
|
| 838 |
+
att_mask,
|
| 839 |
+
pos_emb,
|
| 840 |
+
att_cache=att_cache[i][:, :, ::factor, :]
|
| 841 |
+
[:, :, :pos_emb.size(1) - xs.size(1), :]
|
| 842 |
+
if elayers > 0 else att_cache[:, :, ::factor, :],
|
| 843 |
+
cnn_cache=cnn_cache[i] if cnn_cache.size(0) > 0 else cnn_cache,
|
| 844 |
+
)
|
| 845 |
+
cached_att = new_att_cache[:, :, next_cache_start // factor:, :]
|
| 846 |
+
cached_cnn = new_cnn_cache.unsqueeze(1)
|
| 847 |
+
cached_att = (cached_att.unsqueeze(3).repeat(1, 1, 1, factor,
|
| 848 |
+
1).flatten(2, 3))
|
| 849 |
+
if i == 0:
|
| 850 |
+
# record length for the first block as max length
|
| 851 |
+
max_att_len = cached_att.size(2)
|
| 852 |
+
r_att_cache.append(cached_att[:, :, :max_att_len, :].unsqueeze(1))
|
| 853 |
+
r_cnn_cache.append(cached_cnn)
|
| 854 |
+
|
| 855 |
+
chunk_out = xs
|
| 856 |
+
r_att_cache = torch.cat(r_att_cache, dim=1) # concat on layers idx
|
| 857 |
+
r_cnn_cache = torch.cat(r_cnn_cache, dim=1) # concat on layers
|
| 858 |
+
|
| 859 |
+
# <---------forward_chunk END--------->
|
| 860 |
+
|
| 861 |
+
# log_ctc_probs = self.ctc.log_softmax(chunk_out)
|
| 862 |
+
log_ctc_probs = self.ctc.linear(chunk_out)
|
| 863 |
+
log_probs, log_probs_idx = torch.topk(log_ctc_probs,
|
| 864 |
+
self.beam_size,
|
| 865 |
+
dim=2)
|
| 866 |
+
log_probs = log_probs.to(chunk_xs.dtype)
|
| 867 |
+
|
| 868 |
+
r_offset = offset + chunk_out.shape[1]
|
| 869 |
+
# the below ops not supported in Tensorrt
|
| 870 |
+
# chunk_out_lens = torch.div(chunk_lens, subsampling_rate,
|
| 871 |
+
# rounding_mode='floor')
|
| 872 |
+
chunk_out_lens = chunk_lens // self.subsampling_rate
|
| 873 |
+
r_offset = r_offset.unsqueeze(1)
|
| 874 |
+
|
| 875 |
+
return (
|
| 876 |
+
log_probs,
|
| 877 |
+
log_probs_idx,
|
| 878 |
+
chunk_out,
|
| 879 |
+
chunk_out_lens,
|
| 880 |
+
r_offset,
|
| 881 |
+
r_att_cache,
|
| 882 |
+
r_cnn_cache,
|
| 883 |
+
r_cache_mask,
|
| 884 |
+
)
|
| 885 |
+
|
| 886 |
+
|
| 887 |
+
class StreamingEfficientConformerEncoder(torch.nn.Module):
|
| 888 |
+
|
| 889 |
+
def __init__(self, model, required_cache_size, beam_size):
|
| 890 |
+
super().__init__()
|
| 891 |
+
self.ctc = model.ctc
|
| 892 |
+
self.subsampling_rate = model.encoder.embed.subsampling_rate
|
| 893 |
+
self.embed = model.encoder.embed
|
| 894 |
+
self.global_cmvn = model.encoder.global_cmvn
|
| 895 |
+
self.required_cache_size = required_cache_size
|
| 896 |
+
self.beam_size = beam_size
|
| 897 |
+
self.encoder = model.encoder
|
| 898 |
+
|
| 899 |
+
# Efficient Conformer
|
| 900 |
+
self.stride_layer_idx = model.encoder.stride_layer_idx
|
| 901 |
+
self.stride = model.encoder.stride
|
| 902 |
+
self.num_blocks = model.encoder.num_blocks
|
| 903 |
+
self.cnn_module_kernel = model.encoder.cnn_module_kernel
|
| 904 |
+
|
| 905 |
+
def calculate_downsampling_factor(self, i: int) -> int:
|
| 906 |
+
factor = 1
|
| 907 |
+
for idx, stride_idx in enumerate(self.stride_layer_idx):
|
| 908 |
+
if i > stride_idx:
|
| 909 |
+
factor *= self.stride[idx]
|
| 910 |
+
return factor
|
| 911 |
+
|
| 912 |
+
def forward(self, chunk_xs, chunk_lens, offset, att_cache, cnn_cache,
|
| 913 |
+
cache_mask):
|
| 914 |
+
"""Streaming Encoder
|
| 915 |
+
Args:
|
| 916 |
+
chunk_xs (torch.Tensor): chunk input, with shape (b, time, mel-dim),
|
| 917 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 918 |
+
subsample.right_context + 1`
|
| 919 |
+
chunk_lens (torch.Tensor):
|
| 920 |
+
offset (torch.Tensor): offset with shape (b, 1)
|
| 921 |
+
1 is retained for triton deployment
|
| 922 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 923 |
+
transformer/conformer attention, with shape
|
| 924 |
+
(b, elayers, head, cache_t1, d_k * 2), where
|
| 925 |
+
`head * d_k == hidden-dim` and
|
| 926 |
+
`cache_t1 == chunk_size * num_decoding_left_chunks`.
|
| 927 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 928 |
+
(b, elayers, hidden-dim, cache_t2), where
|
| 929 |
+
`cache_t2 == cnn.lorder - 1`
|
| 930 |
+
cache_mask: (torch.Tensor): cache mask with shape (b, required_cache_size)
|
| 931 |
+
in a batch of request, each request may have different
|
| 932 |
+
history cache. Cache mask is used to indidate the effective
|
| 933 |
+
cache for each request
|
| 934 |
+
Returns:
|
| 935 |
+
torch.Tensor: log probabilities of ctc output and cutoff by beam size
|
| 936 |
+
with shape (b, chunk_size, beam)
|
| 937 |
+
torch.Tensor: index of top beam size probabilities for each timestep
|
| 938 |
+
with shape (b, chunk_size, beam)
|
| 939 |
+
torch.Tensor: output of current input xs,
|
| 940 |
+
with shape (b, chunk_size, hidden-dim).
|
| 941 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 942 |
+
same shape (b, elayers, head, cache_t1, d_k * 2)
|
| 943 |
+
as the original att_cache
|
| 944 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 945 |
+
same shape as the original cnn_cache.
|
| 946 |
+
torch.Tensor: new cache mask, with same shape as the original
|
| 947 |
+
cache mask
|
| 948 |
+
"""
|
| 949 |
+
offset = offset.squeeze(1) # (b, )
|
| 950 |
+
offset *= self.calculate_downsampling_factor(self.num_blocks + 1)
|
| 951 |
+
|
| 952 |
+
T = chunk_xs.size(1)
|
| 953 |
+
chunk_mask = ~make_pad_mask(chunk_lens, T).unsqueeze(1) # (b, 1, T)
|
| 954 |
+
# B X 1 X T
|
| 955 |
+
chunk_mask = chunk_mask.to(chunk_xs.dtype)
|
| 956 |
+
# transpose batch & num_layers dim
|
| 957 |
+
# Shape(att_cache): (elayers, b, head, cache_t1, d_k * 2)
|
| 958 |
+
# Shape(cnn_cache): (elayers, b, outsize, cnn_kernel)
|
| 959 |
+
att_cache = torch.transpose(att_cache, 0, 1)
|
| 960 |
+
cnn_cache = torch.transpose(cnn_cache, 0, 1)
|
| 961 |
+
|
| 962 |
+
# rewrite encoder.forward_chunk
|
| 963 |
+
# <---------forward_chunk START--------->
|
| 964 |
+
xs = self.global_cmvn(chunk_xs)
|
| 965 |
+
# chunk mask is important for batch inferencing since
|
| 966 |
+
# different sequence in a batch has different length
|
| 967 |
+
xs, pos_emb, chunk_mask = self.embed(xs, chunk_mask, offset)
|
| 968 |
+
cache_size = att_cache.size(3) # required cache size
|
| 969 |
+
masks = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 970 |
+
att_mask = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 971 |
+
index = offset - cache_size
|
| 972 |
+
|
| 973 |
+
pos_emb = self.embed.position_encoding(index, cache_size + xs.size(1))
|
| 974 |
+
pos_emb = pos_emb.to(dtype=xs.dtype)
|
| 975 |
+
|
| 976 |
+
next_cache_start = -self.required_cache_size
|
| 977 |
+
r_cache_mask = masks[:, :, next_cache_start:]
|
| 978 |
+
|
| 979 |
+
r_att_cache = []
|
| 980 |
+
r_cnn_cache = []
|
| 981 |
+
mask_pad = chunk_mask.to(torch.bool)
|
| 982 |
+
max_att_len, max_cnn_len = (
|
| 983 |
+
0,
|
| 984 |
+
0,
|
| 985 |
+
) # for repeat_interleave of new_att_cache
|
| 986 |
+
for i, layer in enumerate(self.encoder.encoders):
|
| 987 |
+
factor = self.calculate_downsampling_factor(i)
|
| 988 |
+
# NOTE(xcsong): Before layer.forward
|
| 989 |
+
# shape(att_cache[i:i + 1]) is (b, head, cache_t1, d_k * 2),
|
| 990 |
+
# shape(cnn_cache[i]) is (b=1, hidden-dim, cache_t2)
|
| 991 |
+
# shape(new_att_cache) = [ batch, head, time2, outdim//head * 2 ]
|
| 992 |
+
att_cache_trunc = 0
|
| 993 |
+
if xs.size(1) + att_cache.size(3) / factor > pos_emb.size(1):
|
| 994 |
+
# The time step is not divisible by the downsampling multiple
|
| 995 |
+
# We propose to double the chunk_size.
|
| 996 |
+
att_cache_trunc = (xs.size(1) + att_cache.size(3) // factor -
|
| 997 |
+
pos_emb.size(1) + 1)
|
| 998 |
+
xs, _, new_att_cache, new_cnn_cache = layer(
|
| 999 |
+
xs,
|
| 1000 |
+
att_mask,
|
| 1001 |
+
pos_emb,
|
| 1002 |
+
mask_pad=mask_pad,
|
| 1003 |
+
att_cache=att_cache[i][:, :, ::factor, :][:, :,
|
| 1004 |
+
att_cache_trunc:, :],
|
| 1005 |
+
cnn_cache=cnn_cache[i, :, :, :]
|
| 1006 |
+
if cnn_cache.size(0) > 0 else cnn_cache,
|
| 1007 |
+
)
|
| 1008 |
+
|
| 1009 |
+
if i in self.stride_layer_idx:
|
| 1010 |
+
# compute time dimension for next block
|
| 1011 |
+
efficient_index = self.stride_layer_idx.index(i)
|
| 1012 |
+
att_mask = att_mask[:, ::self.stride[efficient_index], ::self.
|
| 1013 |
+
stride[efficient_index], ]
|
| 1014 |
+
mask_pad = mask_pad[:, ::self.stride[efficient_index], ::self.
|
| 1015 |
+
stride[efficient_index], ]
|
| 1016 |
+
pos_emb = pos_emb[:, ::self.stride[efficient_index], :]
|
| 1017 |
+
|
| 1018 |
+
# shape(new_att_cache) = [batch, head, time2, outdim]
|
| 1019 |
+
new_att_cache = new_att_cache[:, :, next_cache_start // factor:, :]
|
| 1020 |
+
# shape(new_cnn_cache) = [batch, 1, outdim, cache_t2]
|
| 1021 |
+
new_cnn_cache = new_cnn_cache.unsqueeze(1) # shape(1):layerID
|
| 1022 |
+
|
| 1023 |
+
# use repeat_interleave to new_att_cache
|
| 1024 |
+
# new_att_cache = new_att_cache.repeat_interleave(repeats=factor, dim=2)
|
| 1025 |
+
new_att_cache = (new_att_cache.unsqueeze(3).repeat(
|
| 1026 |
+
1, 1, 1, factor, 1).flatten(2, 3))
|
| 1027 |
+
# padding new_cnn_cache to cnn.lorder for casual convolution
|
| 1028 |
+
new_cnn_cache = F.pad(
|
| 1029 |
+
new_cnn_cache,
|
| 1030 |
+
(self.cnn_module_kernel - 1 - new_cnn_cache.size(3), 0),
|
| 1031 |
+
)
|
| 1032 |
+
|
| 1033 |
+
if i == 0:
|
| 1034 |
+
# record length for the first block as max length
|
| 1035 |
+
max_att_len = new_att_cache.size(2)
|
| 1036 |
+
max_cnn_len = new_cnn_cache.size(3)
|
| 1037 |
+
|
| 1038 |
+
# update real shape of att_cache and cnn_cache
|
| 1039 |
+
r_att_cache.append(new_att_cache[:, :,
|
| 1040 |
+
-max_att_len:, :].unsqueeze(1))
|
| 1041 |
+
r_cnn_cache.append(new_cnn_cache[:, :, :, -max_cnn_len:])
|
| 1042 |
+
|
| 1043 |
+
if self.encoder.normalize_before:
|
| 1044 |
+
chunk_out = self.encoder.after_norm(xs)
|
| 1045 |
+
else:
|
| 1046 |
+
chunk_out = xs
|
| 1047 |
+
|
| 1048 |
+
# shape of r_att_cache: (b, elayers, head, time2, outdim)
|
| 1049 |
+
r_att_cache = torch.cat(r_att_cache, dim=1) # concat on layers idx
|
| 1050 |
+
# shape of r_cnn_cache: (b, elayers, outdim, cache_t2)
|
| 1051 |
+
r_cnn_cache = torch.cat(r_cnn_cache, dim=1) # concat on layers
|
| 1052 |
+
|
| 1053 |
+
# <---------forward_chunk END--------->
|
| 1054 |
+
|
| 1055 |
+
# log_ctc_probs = self.ctc.log_softmax(chunk_out)
|
| 1056 |
+
log_ctc_probs = self.ctc.linear(chunk_out)
|
| 1057 |
+
log_probs, log_probs_idx = torch.topk(log_ctc_probs,
|
| 1058 |
+
self.beam_size,
|
| 1059 |
+
dim=2)
|
| 1060 |
+
log_probs = log_probs.to(chunk_xs.dtype)
|
| 1061 |
+
|
| 1062 |
+
r_offset = offset + chunk_out.shape[1]
|
| 1063 |
+
# the below ops not supported in Tensorrt
|
| 1064 |
+
# chunk_out_lens = torch.div(chunk_lens, subsampling_rate,
|
| 1065 |
+
# rounding_mode='floor')
|
| 1066 |
+
chunk_out_lens = (
|
| 1067 |
+
chunk_lens // self.subsampling_rate //
|
| 1068 |
+
self.calculate_downsampling_factor(self.num_blocks + 1))
|
| 1069 |
+
chunk_out_lens += 1
|
| 1070 |
+
r_offset = r_offset.unsqueeze(1)
|
| 1071 |
+
|
| 1072 |
+
return (
|
| 1073 |
+
log_probs,
|
| 1074 |
+
log_probs_idx,
|
| 1075 |
+
chunk_out,
|
| 1076 |
+
chunk_out_lens,
|
| 1077 |
+
r_offset,
|
| 1078 |
+
r_att_cache,
|
| 1079 |
+
r_cnn_cache,
|
| 1080 |
+
r_cache_mask,
|
| 1081 |
+
)
|
| 1082 |
+
|
| 1083 |
+
|
| 1084 |
+
class Decoder(torch.nn.Module):
|
| 1085 |
+
|
| 1086 |
+
def __init__(
|
| 1087 |
+
self,
|
| 1088 |
+
decoder: TransformerDecoder,
|
| 1089 |
+
ctc_weight: float = 0.5,
|
| 1090 |
+
reverse_weight: float = 0.0,
|
| 1091 |
+
beam_size: int = 10,
|
| 1092 |
+
decoder_fastertransformer: bool = False,
|
| 1093 |
+
):
|
| 1094 |
+
super().__init__()
|
| 1095 |
+
self.decoder = decoder
|
| 1096 |
+
self.ctc_weight = ctc_weight
|
| 1097 |
+
self.reverse_weight = reverse_weight
|
| 1098 |
+
self.beam_size = beam_size
|
| 1099 |
+
self.decoder_fastertransformer = decoder_fastertransformer
|
| 1100 |
+
|
| 1101 |
+
def forward(
|
| 1102 |
+
self,
|
| 1103 |
+
encoder_out: torch.Tensor,
|
| 1104 |
+
encoder_lens: torch.Tensor,
|
| 1105 |
+
hyps_pad_sos_eos: torch.Tensor,
|
| 1106 |
+
hyps_lens_sos: torch.Tensor,
|
| 1107 |
+
r_hyps_pad_sos_eos: torch.Tensor,
|
| 1108 |
+
ctc_score: torch.Tensor,
|
| 1109 |
+
):
|
| 1110 |
+
"""Encoder
|
| 1111 |
+
Args:
|
| 1112 |
+
encoder_out: B x T x F
|
| 1113 |
+
encoder_lens: B
|
| 1114 |
+
hyps_pad_sos_eos: B x beam x (T2+1),
|
| 1115 |
+
hyps with sos & eos and padded by ignore id
|
| 1116 |
+
hyps_lens_sos: B x beam, length for each hyp with sos
|
| 1117 |
+
r_hyps_pad_sos_eos: B x beam x (T2+1),
|
| 1118 |
+
reversed hyps with sos & eos and padded by ignore id
|
| 1119 |
+
ctc_score: B x beam, ctc score for each hyp
|
| 1120 |
+
Returns:
|
| 1121 |
+
decoder_out: B x beam x T2 x V
|
| 1122 |
+
r_decoder_out: B x beam x T2 x V
|
| 1123 |
+
best_index: B
|
| 1124 |
+
"""
|
| 1125 |
+
B, T, F = encoder_out.shape
|
| 1126 |
+
bz = self.beam_size
|
| 1127 |
+
B2 = B * bz
|
| 1128 |
+
encoder_out = encoder_out.repeat(1, bz, 1).view(B2, T, F)
|
| 1129 |
+
encoder_mask = ~make_pad_mask(encoder_lens, T).unsqueeze(1)
|
| 1130 |
+
encoder_mask = encoder_mask.repeat(1, bz, 1).view(B2, 1, T)
|
| 1131 |
+
T2 = hyps_pad_sos_eos.shape[2] - 1
|
| 1132 |
+
hyps_pad = hyps_pad_sos_eos.view(B2, T2 + 1)
|
| 1133 |
+
hyps_lens = hyps_lens_sos.view(B2, )
|
| 1134 |
+
hyps_pad_sos = hyps_pad[:, :-1].contiguous()
|
| 1135 |
+
hyps_pad_eos = hyps_pad[:, 1:].contiguous()
|
| 1136 |
+
|
| 1137 |
+
r_hyps_pad = r_hyps_pad_sos_eos.view(B2, T2 + 1)
|
| 1138 |
+
r_hyps_pad_sos = r_hyps_pad[:, :-1].contiguous()
|
| 1139 |
+
r_hyps_pad_eos = r_hyps_pad[:, 1:].contiguous()
|
| 1140 |
+
|
| 1141 |
+
decoder_out, r_decoder_out, _ = self.decoder(
|
| 1142 |
+
encoder_out,
|
| 1143 |
+
encoder_mask,
|
| 1144 |
+
hyps_pad_sos,
|
| 1145 |
+
hyps_lens,
|
| 1146 |
+
r_hyps_pad_sos,
|
| 1147 |
+
self.reverse_weight,
|
| 1148 |
+
)
|
| 1149 |
+
# decoder_out = torch.nn.functional.log_softmax(decoder_out, dim=-1)
|
| 1150 |
+
V = decoder_out.shape[-1]
|
| 1151 |
+
decoder_out = decoder_out.view(B2, T2, V)
|
| 1152 |
+
mask = ~make_pad_mask(hyps_lens, T2) # B2 x T2
|
| 1153 |
+
# mask index, remove ignore id
|
| 1154 |
+
index = torch.unsqueeze(hyps_pad_eos * mask, 2).to(torch.long)
|
| 1155 |
+
score = decoder_out.gather(2, index).squeeze(2) # B2 X T2
|
| 1156 |
+
# mask padded part
|
| 1157 |
+
score = score * mask
|
| 1158 |
+
decoder_out = decoder_out.view(B, bz, T2, V)
|
| 1159 |
+
if self.reverse_weight > 0:
|
| 1160 |
+
# r_decoder_out = torch.nn.functional.log_softmax(r_decoder_out,
|
| 1161 |
+
# dim=-1)
|
| 1162 |
+
r_decoder_out = r_decoder_out.view(B2, T2, V)
|
| 1163 |
+
index = torch.unsqueeze(r_hyps_pad_eos * mask, 2).to(torch.long)
|
| 1164 |
+
r_score = r_decoder_out.gather(2, index).squeeze(2)
|
| 1165 |
+
r_score = r_score * mask
|
| 1166 |
+
score = (score * (1 - self.reverse_weight) +
|
| 1167 |
+
self.reverse_weight * r_score)
|
| 1168 |
+
r_decoder_out = r_decoder_out.view(B, bz, T2, V)
|
| 1169 |
+
score = torch.sum(score, axis=1) # B2
|
| 1170 |
+
score = torch.reshape(score, (B, bz)) + self.ctc_weight * ctc_score
|
| 1171 |
+
best_index = torch.argmax(score, dim=1)
|
| 1172 |
+
if self.decoder_fastertransformer:
|
| 1173 |
+
return decoder_out, best_index
|
| 1174 |
+
else:
|
| 1175 |
+
return best_index
|
| 1176 |
+
|
| 1177 |
+
|
| 1178 |
+
def to_numpy(tensors):
|
| 1179 |
+
out = []
|
| 1180 |
+
if type(tensors) == torch.tensor:
|
| 1181 |
+
tensors = [tensors]
|
| 1182 |
+
for tensor in tensors:
|
| 1183 |
+
if tensor.requires_grad:
|
| 1184 |
+
tensor = tensor.detach().cpu().numpy()
|
| 1185 |
+
else:
|
| 1186 |
+
tensor = tensor.cpu().numpy()
|
| 1187 |
+
out.append(tensor)
|
| 1188 |
+
return out
|
| 1189 |
+
|
| 1190 |
+
|
| 1191 |
+
def test(xlist, blist, rtol=1e-3, atol=1e-5, tolerate_small_mismatch=True):
|
| 1192 |
+
for a, b in zip(xlist, blist):
|
| 1193 |
+
try:
|
| 1194 |
+
torch.testing.assert_allclose(a, b, rtol=rtol, atol=atol)
|
| 1195 |
+
except AssertionError as error:
|
| 1196 |
+
if tolerate_small_mismatch:
|
| 1197 |
+
print(error)
|
| 1198 |
+
else:
|
| 1199 |
+
raise
|
| 1200 |
+
|
| 1201 |
+
|
| 1202 |
+
def export_offline_encoder(model, configs, args, logger, encoder_onnx_path):
|
| 1203 |
+
bz = 1
|
| 1204 |
+
seq_len = 1024
|
| 1205 |
+
beam_size = args.beam_size
|
| 1206 |
+
feature_size = configs["input_dim"]
|
| 1207 |
+
|
| 1208 |
+
speech = torch.randn(bz, seq_len, feature_size, dtype=torch.float32)
|
| 1209 |
+
speech_lens = torch.randint(low=10,
|
| 1210 |
+
high=seq_len,
|
| 1211 |
+
size=(bz, ),
|
| 1212 |
+
dtype=torch.int32)
|
| 1213 |
+
encoder = Encoder(model.encoder, model.ctc, beam_size)
|
| 1214 |
+
encoder.eval()
|
| 1215 |
+
|
| 1216 |
+
torch.onnx.export(
|
| 1217 |
+
encoder,
|
| 1218 |
+
(speech, speech_lens),
|
| 1219 |
+
encoder_onnx_path,
|
| 1220 |
+
export_params=True,
|
| 1221 |
+
opset_version=13,
|
| 1222 |
+
do_constant_folding=True,
|
| 1223 |
+
input_names=["speech", "speech_lengths"],
|
| 1224 |
+
output_names=[
|
| 1225 |
+
"encoder_out",
|
| 1226 |
+
"encoder_out_lens",
|
| 1227 |
+
"ctc_log_probs",
|
| 1228 |
+
"beam_log_probs",
|
| 1229 |
+
"beam_log_probs_idx",
|
| 1230 |
+
],
|
| 1231 |
+
dynamic_axes=None,
|
| 1232 |
+
# dynamic_axes={
|
| 1233 |
+
# "speech": {
|
| 1234 |
+
# 0: "B",
|
| 1235 |
+
# 1: "T"
|
| 1236 |
+
# },
|
| 1237 |
+
# "speech_lengths": {
|
| 1238 |
+
# 0: "B"
|
| 1239 |
+
# },
|
| 1240 |
+
# "encoder_out": {
|
| 1241 |
+
# 0: "B",
|
| 1242 |
+
# 1: "T_OUT"
|
| 1243 |
+
# },
|
| 1244 |
+
# "encoder_out_lens": {
|
| 1245 |
+
# 0: "B"
|
| 1246 |
+
# },
|
| 1247 |
+
# "ctc_log_probs": {
|
| 1248 |
+
# 0: "B",
|
| 1249 |
+
# 1: "T_OUT"
|
| 1250 |
+
# },
|
| 1251 |
+
# "beam_log_probs": {
|
| 1252 |
+
# 0: "B",
|
| 1253 |
+
# 1: "T_OUT"
|
| 1254 |
+
# },
|
| 1255 |
+
# "beam_log_probs_idx": {
|
| 1256 |
+
# 0: "B",
|
| 1257 |
+
# 1: "T_OUT"
|
| 1258 |
+
# },
|
| 1259 |
+
# },
|
| 1260 |
+
verbose=False,
|
| 1261 |
+
dynamo=False,
|
| 1262 |
+
)
|
| 1263 |
+
fold_static_pulsar2_subgraphs(encoder_onnx_path)
|
| 1264 |
+
simplify_pulsar2_onnx(encoder_onnx_path)
|
| 1265 |
+
rewrite_pulsar2_bool_not(encoder_onnx_path)
|
| 1266 |
+
|
| 1267 |
+
with torch.no_grad():
|
| 1268 |
+
o0, o1, o2, o3, o4 = encoder(speech, speech_lens)
|
| 1269 |
+
|
| 1270 |
+
providers = ["CPUExecutionProvider"]
|
| 1271 |
+
ort_session = onnxruntime.InferenceSession(encoder_onnx_path,
|
| 1272 |
+
providers=providers)
|
| 1273 |
+
ort_inputs = {
|
| 1274 |
+
"speech": to_numpy(speech),
|
| 1275 |
+
"speech_lengths": to_numpy(speech_lens),
|
| 1276 |
+
}
|
| 1277 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 1278 |
+
|
| 1279 |
+
# check encoder output
|
| 1280 |
+
test(to_numpy([o0, o1, o2, o3, o4]), ort_outs)
|
| 1281 |
+
logger.info("export offline onnx encoder succeed!")
|
| 1282 |
+
onnx_config = {
|
| 1283 |
+
"beam_size": args.beam_size,
|
| 1284 |
+
"reverse_weight": configs["model_conf"]["reverse_weight"],
|
| 1285 |
+
"ctc_weight": configs["model_conf"]["ctc_weight"],
|
| 1286 |
+
}
|
| 1287 |
+
return onnx_config
|
| 1288 |
+
|
| 1289 |
+
|
| 1290 |
+
def export_online_encoder(model, configs, args, logger, encoder_onnx_path):
|
| 1291 |
+
decoding_chunk_size = args.decoding_chunk_size
|
| 1292 |
+
subsampling = model.encoder.embed.subsampling_rate
|
| 1293 |
+
context = model.encoder.embed.right_context + 1
|
| 1294 |
+
decoding_window = (decoding_chunk_size - 1) * subsampling + context
|
| 1295 |
+
batch_size = 1
|
| 1296 |
+
audio_len = decoding_window
|
| 1297 |
+
feature_size = configs["input_dim"]
|
| 1298 |
+
output_size = configs["encoder_conf"]["output_size"]
|
| 1299 |
+
num_layers = configs["encoder_conf"]["num_blocks"]
|
| 1300 |
+
# in transformer the cnn module will not be available
|
| 1301 |
+
transformer = False
|
| 1302 |
+
cnn_module_kernel = configs["encoder_conf"].get("cnn_module_kernel", 1) - 1
|
| 1303 |
+
if not cnn_module_kernel:
|
| 1304 |
+
transformer = True
|
| 1305 |
+
num_decoding_left_chunks = args.num_decoding_left_chunks
|
| 1306 |
+
required_cache_size = decoding_chunk_size * num_decoding_left_chunks
|
| 1307 |
+
if configs["encoder"] == "squeezeformer":
|
| 1308 |
+
encoder = StreamingSqueezeformerEncoder(model, required_cache_size,
|
| 1309 |
+
args.beam_size)
|
| 1310 |
+
elif configs["encoder"] == "efficientConformer":
|
| 1311 |
+
encoder = StreamingEfficientConformerEncoder(model,
|
| 1312 |
+
required_cache_size,
|
| 1313 |
+
args.beam_size)
|
| 1314 |
+
else:
|
| 1315 |
+
encoder = StreamingEncoder(
|
| 1316 |
+
model,
|
| 1317 |
+
required_cache_size,
|
| 1318 |
+
args.beam_size,
|
| 1319 |
+
transformer,
|
| 1320 |
+
args.return_ctc_logprobs,
|
| 1321 |
+
)
|
| 1322 |
+
encoder.eval()
|
| 1323 |
+
|
| 1324 |
+
# begin to export encoder
|
| 1325 |
+
chunk_xs = torch.randn(batch_size,
|
| 1326 |
+
audio_len,
|
| 1327 |
+
feature_size,
|
| 1328 |
+
dtype=torch.float32)
|
| 1329 |
+
chunk_lens = torch.ones(batch_size, dtype=torch.int32) * audio_len
|
| 1330 |
+
|
| 1331 |
+
offset = torch.arange(0, batch_size, dtype=torch.int32).unsqueeze(1)
|
| 1332 |
+
# (elayers, b, head, cache_t1, d_k * 2)
|
| 1333 |
+
head = configs["encoder_conf"]["attention_heads"]
|
| 1334 |
+
d_k = configs["encoder_conf"]["output_size"] // head
|
| 1335 |
+
att_cache = torch.randn(
|
| 1336 |
+
batch_size,
|
| 1337 |
+
num_layers,
|
| 1338 |
+
head,
|
| 1339 |
+
required_cache_size,
|
| 1340 |
+
d_k * 2,
|
| 1341 |
+
dtype=torch.float32,
|
| 1342 |
+
)
|
| 1343 |
+
cnn_cache = torch.randn(
|
| 1344 |
+
batch_size,
|
| 1345 |
+
num_layers,
|
| 1346 |
+
output_size,
|
| 1347 |
+
cnn_module_kernel,
|
| 1348 |
+
dtype=torch.float32,
|
| 1349 |
+
)
|
| 1350 |
+
|
| 1351 |
+
cache_mask = torch.ones(batch_size,
|
| 1352 |
+
1,
|
| 1353 |
+
required_cache_size,
|
| 1354 |
+
dtype=torch.float32)
|
| 1355 |
+
input_names = [
|
| 1356 |
+
"chunk_xs",
|
| 1357 |
+
"chunk_lens",
|
| 1358 |
+
"offset",
|
| 1359 |
+
"att_cache",
|
| 1360 |
+
"cnn_cache",
|
| 1361 |
+
"cache_mask",
|
| 1362 |
+
]
|
| 1363 |
+
output_names = [
|
| 1364 |
+
"log_probs",
|
| 1365 |
+
"log_probs_idx",
|
| 1366 |
+
"chunk_out",
|
| 1367 |
+
"chunk_out_lens",
|
| 1368 |
+
"r_offset",
|
| 1369 |
+
"r_att_cache",
|
| 1370 |
+
"r_cnn_cache",
|
| 1371 |
+
"r_cache_mask",
|
| 1372 |
+
]
|
| 1373 |
+
if args.return_ctc_logprobs:
|
| 1374 |
+
output_names = [
|
| 1375 |
+
"ctc_log_probs",
|
| 1376 |
+
"chunk_out",
|
| 1377 |
+
"chunk_out_lens",
|
| 1378 |
+
"r_offset",
|
| 1379 |
+
"r_att_cache",
|
| 1380 |
+
"r_cnn_cache",
|
| 1381 |
+
"r_cache_mask",
|
| 1382 |
+
]
|
| 1383 |
+
input_tensors = (
|
| 1384 |
+
chunk_xs,
|
| 1385 |
+
chunk_lens,
|
| 1386 |
+
offset,
|
| 1387 |
+
att_cache,
|
| 1388 |
+
cnn_cache,
|
| 1389 |
+
cache_mask,
|
| 1390 |
+
)
|
| 1391 |
+
if transformer:
|
| 1392 |
+
assert (args.return_ctc_logprobs is
|
| 1393 |
+
False), "return_ctc_logprobs is not supported in transformer"
|
| 1394 |
+
output_names.pop(6)
|
| 1395 |
+
|
| 1396 |
+
all_names = input_names + output_names
|
| 1397 |
+
dynamic_axes = {}
|
| 1398 |
+
for name in all_names:
|
| 1399 |
+
# only the first dimension is dynamic
|
| 1400 |
+
# all other dimension is fixed
|
| 1401 |
+
dynamic_axes[name] = {0: "B"}
|
| 1402 |
+
|
| 1403 |
+
torch.onnx.export(
|
| 1404 |
+
encoder,
|
| 1405 |
+
input_tensors,
|
| 1406 |
+
encoder_onnx_path,
|
| 1407 |
+
export_params=True,
|
| 1408 |
+
opset_version=14,
|
| 1409 |
+
do_constant_folding=True,
|
| 1410 |
+
input_names=input_names,
|
| 1411 |
+
output_names=output_names,
|
| 1412 |
+
# dynamic_axes=dynamic_axes,
|
| 1413 |
+
dynamic_axes=None,
|
| 1414 |
+
verbose=False,
|
| 1415 |
+
dynamo=False,
|
| 1416 |
+
)
|
| 1417 |
+
fold_static_pulsar2_subgraphs(encoder_onnx_path)
|
| 1418 |
+
simplify_pulsar2_onnx(encoder_onnx_path)
|
| 1419 |
+
rewrite_pulsar2_bool_not(encoder_onnx_path)
|
| 1420 |
+
|
| 1421 |
+
with torch.no_grad():
|
| 1422 |
+
torch_outs = encoder(chunk_xs, chunk_lens, offset, att_cache,
|
| 1423 |
+
cnn_cache, cache_mask)
|
| 1424 |
+
if transformer:
|
| 1425 |
+
torch_outs = list(torch_outs).pop(6)
|
| 1426 |
+
ort_session = onnxruntime.InferenceSession(
|
| 1427 |
+
encoder_onnx_path, providers=["CPUExecutionProvider"])
|
| 1428 |
+
ort_inputs = {}
|
| 1429 |
+
|
| 1430 |
+
input_tensors = to_numpy(input_tensors)
|
| 1431 |
+
for idx, name in enumerate(input_names):
|
| 1432 |
+
ort_inputs[name] = input_tensors[idx]
|
| 1433 |
+
if transformer:
|
| 1434 |
+
del ort_inputs["cnn_cache"]
|
| 1435 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 1436 |
+
test(to_numpy(torch_outs), ort_outs, rtol=1e-03, atol=1e-05)
|
| 1437 |
+
logger.info("export to onnx streaming encoder succeed!")
|
| 1438 |
+
onnx_config = {
|
| 1439 |
+
"subsampling_rate": subsampling,
|
| 1440 |
+
"context": context,
|
| 1441 |
+
"decoding_chunk_size": decoding_chunk_size,
|
| 1442 |
+
"num_decoding_left_chunks": num_decoding_left_chunks,
|
| 1443 |
+
"beam_size": args.beam_size,
|
| 1444 |
+
"feat_size": feature_size,
|
| 1445 |
+
"decoding_window": decoding_window,
|
| 1446 |
+
"cnn_module_kernel_cache": cnn_module_kernel,
|
| 1447 |
+
"return_ctc_logprobs": args.return_ctc_logprobs,
|
| 1448 |
+
}
|
| 1449 |
+
return onnx_config
|
| 1450 |
+
|
| 1451 |
+
|
| 1452 |
+
def export_rescoring_decoder(model, configs, args, logger, decoder_onnx_path,
|
| 1453 |
+
decoder_fastertransformer):
|
| 1454 |
+
bz, seq_len = 1, 32
|
| 1455 |
+
beam_size = args.beam_size
|
| 1456 |
+
decoder = Decoder(
|
| 1457 |
+
model.decoder,
|
| 1458 |
+
model.ctc_weight,
|
| 1459 |
+
model.reverse_weight,
|
| 1460 |
+
beam_size,
|
| 1461 |
+
decoder_fastertransformer,
|
| 1462 |
+
)
|
| 1463 |
+
decoder.eval()
|
| 1464 |
+
|
| 1465 |
+
hyps_pad_sos_eos = torch.randint(low=3,
|
| 1466 |
+
high=1000,
|
| 1467 |
+
size=(bz, beam_size, seq_len),
|
| 1468 |
+
dtype=torch.int32)
|
| 1469 |
+
hyps_lens_sos = torch.randint(low=3,
|
| 1470 |
+
high=seq_len,
|
| 1471 |
+
size=(bz, beam_size),
|
| 1472 |
+
dtype=torch.int32)
|
| 1473 |
+
r_hyps_pad_sos_eos = torch.randint(low=3,
|
| 1474 |
+
high=1000,
|
| 1475 |
+
size=(bz, beam_size, seq_len),
|
| 1476 |
+
dtype=torch.int32)
|
| 1477 |
+
|
| 1478 |
+
output_size = configs["encoder_conf"]["output_size"]
|
| 1479 |
+
encoder_out = torch.randn(bz, seq_len, output_size, dtype=torch.float32)
|
| 1480 |
+
encoder_out_lens = torch.randint(low=3,
|
| 1481 |
+
high=seq_len,
|
| 1482 |
+
size=(bz, ),
|
| 1483 |
+
dtype=torch.int32)
|
| 1484 |
+
ctc_score = torch.randn(bz, beam_size, dtype=torch.float32)
|
| 1485 |
+
|
| 1486 |
+
input_names = [
|
| 1487 |
+
"encoder_out",
|
| 1488 |
+
"encoder_out_lens",
|
| 1489 |
+
"hyps_pad_sos_eos",
|
| 1490 |
+
"hyps_lens_sos",
|
| 1491 |
+
"r_hyps_pad_sos_eos",
|
| 1492 |
+
"ctc_score",
|
| 1493 |
+
]
|
| 1494 |
+
output_names = ["best_index"]
|
| 1495 |
+
if decoder_fastertransformer:
|
| 1496 |
+
output_names.insert(0, "decoder_out")
|
| 1497 |
+
|
| 1498 |
+
torch.onnx.export(
|
| 1499 |
+
decoder,
|
| 1500 |
+
(
|
| 1501 |
+
encoder_out,
|
| 1502 |
+
encoder_out_lens,
|
| 1503 |
+
hyps_pad_sos_eos,
|
| 1504 |
+
hyps_lens_sos,
|
| 1505 |
+
r_hyps_pad_sos_eos,
|
| 1506 |
+
ctc_score,
|
| 1507 |
+
),
|
| 1508 |
+
decoder_onnx_path,
|
| 1509 |
+
export_params=True,
|
| 1510 |
+
opset_version=13,
|
| 1511 |
+
do_constant_folding=True,
|
| 1512 |
+
input_names=input_names,
|
| 1513 |
+
output_names=output_names,
|
| 1514 |
+
dynamic_axes=None,
|
| 1515 |
+
# dynamic_axes={
|
| 1516 |
+
# "encoder_out": {
|
| 1517 |
+
# 0: "B",
|
| 1518 |
+
# 1: "T"
|
| 1519 |
+
# },
|
| 1520 |
+
# "encoder_out_lens": {
|
| 1521 |
+
# 0: "B"
|
| 1522 |
+
# },
|
| 1523 |
+
# "hyps_pad_sos_eos": {
|
| 1524 |
+
# 0: "B",
|
| 1525 |
+
# 2: "T2"
|
| 1526 |
+
# },
|
| 1527 |
+
# "hyps_lens_sos": {
|
| 1528 |
+
# 0: "B"
|
| 1529 |
+
# },
|
| 1530 |
+
# "r_hyps_pad_sos_eos": {
|
| 1531 |
+
# 0: "B",
|
| 1532 |
+
# 2: "T2"
|
| 1533 |
+
# },
|
| 1534 |
+
# "ctc_score": {
|
| 1535 |
+
# 0: "B"
|
| 1536 |
+
# },
|
| 1537 |
+
# "best_index": {
|
| 1538 |
+
# 0: "B"
|
| 1539 |
+
# },
|
| 1540 |
+
# },
|
| 1541 |
+
verbose=False,
|
| 1542 |
+
dynamo=False,
|
| 1543 |
+
)
|
| 1544 |
+
fold_static_pulsar2_subgraphs(decoder_onnx_path)
|
| 1545 |
+
simplify_pulsar2_onnx(decoder_onnx_path)
|
| 1546 |
+
rewrite_pulsar2_bool_not(decoder_onnx_path)
|
| 1547 |
+
rewrite_pulsar2_bool_and(decoder_onnx_path)
|
| 1548 |
+
with torch.no_grad():
|
| 1549 |
+
o0 = decoder(
|
| 1550 |
+
encoder_out,
|
| 1551 |
+
encoder_out_lens,
|
| 1552 |
+
hyps_pad_sos_eos,
|
| 1553 |
+
hyps_lens_sos,
|
| 1554 |
+
r_hyps_pad_sos_eos,
|
| 1555 |
+
ctc_score,
|
| 1556 |
+
)
|
| 1557 |
+
providers = ["CPUExecutionProvider"]
|
| 1558 |
+
ort_session = onnxruntime.InferenceSession(decoder_onnx_path,
|
| 1559 |
+
providers=providers)
|
| 1560 |
+
|
| 1561 |
+
input_tensors = [
|
| 1562 |
+
encoder_out,
|
| 1563 |
+
encoder_out_lens,
|
| 1564 |
+
hyps_pad_sos_eos,
|
| 1565 |
+
hyps_lens_sos,
|
| 1566 |
+
r_hyps_pad_sos_eos,
|
| 1567 |
+
ctc_score,
|
| 1568 |
+
]
|
| 1569 |
+
ort_inputs = {}
|
| 1570 |
+
input_tensors = to_numpy(input_tensors)
|
| 1571 |
+
for idx, name in enumerate(input_names):
|
| 1572 |
+
ort_inputs[name] = input_tensors[idx]
|
| 1573 |
+
|
| 1574 |
+
# if model.reverse weight == 0,
|
| 1575 |
+
# the r_hyps_pad will be removed
|
| 1576 |
+
# from the onnx decoder since it doen't play any role
|
| 1577 |
+
if model.reverse_weight == 0:
|
| 1578 |
+
del ort_inputs["r_hyps_pad_sos_eos"]
|
| 1579 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 1580 |
+
|
| 1581 |
+
# check decoder output
|
| 1582 |
+
if decoder_fastertransformer:
|
| 1583 |
+
test(to_numpy(o0), ort_outs, rtol=1e-03, atol=1e-05)
|
| 1584 |
+
else:
|
| 1585 |
+
test(to_numpy([o0]), ort_outs, rtol=1e-03, atol=1e-05)
|
| 1586 |
+
logger.info("export to onnx decoder succeed!")
|
| 1587 |
+
|
| 1588 |
+
|
| 1589 |
+
if __name__ == "__main__":
|
| 1590 |
+
parser = argparse.ArgumentParser(description="export x86_gpu model")
|
| 1591 |
+
parser.add_argument(
|
| 1592 |
+
"--pretrained_model_dir",
|
| 1593 |
+
default=DEFAULT_PRETRAINED_MODEL_DIR,
|
| 1594 |
+
help=("pretrained model directory containing train.yaml, final.pt, "
|
| 1595 |
+
"and global_cmvn"),
|
| 1596 |
+
)
|
| 1597 |
+
parser.add_argument(
|
| 1598 |
+
"--pretrained_model_url",
|
| 1599 |
+
default=DEFAULT_PRETRAINED_MODEL_URL,
|
| 1600 |
+
help="pretrained model tar.gz URL used when pretrained_model_dir is missing",
|
| 1601 |
+
)
|
| 1602 |
+
parser.add_argument(
|
| 1603 |
+
"--reverse_weight",
|
| 1604 |
+
default=-1.0,
|
| 1605 |
+
type=float,
|
| 1606 |
+
required=False,
|
| 1607 |
+
help="reverse weight for bitransformer," +
|
| 1608 |
+
"default value is in config file",
|
| 1609 |
+
)
|
| 1610 |
+
parser.add_argument(
|
| 1611 |
+
"--ctc_weight",
|
| 1612 |
+
default=-1.0,
|
| 1613 |
+
type=float,
|
| 1614 |
+
required=False,
|
| 1615 |
+
help="ctc weight, default value is in config file",
|
| 1616 |
+
)
|
| 1617 |
+
parser.add_argument(
|
| 1618 |
+
"--beam_size",
|
| 1619 |
+
default=10,
|
| 1620 |
+
type=int,
|
| 1621 |
+
required=False,
|
| 1622 |
+
help="beam size would be ctc output size",
|
| 1623 |
+
)
|
| 1624 |
+
parser.add_argument(
|
| 1625 |
+
"--output_onnx_dir",
|
| 1626 |
+
default="onnx_model",
|
| 1627 |
+
help="output onnx encoder and decoder directory",
|
| 1628 |
+
)
|
| 1629 |
+
# arguments for streaming encoder
|
| 1630 |
+
# parser.add_argument(
|
| 1631 |
+
# "--streaming",
|
| 1632 |
+
# action="store_true",
|
| 1633 |
+
# help="whether to export streaming encoder, default false",
|
| 1634 |
+
# )
|
| 1635 |
+
parser.add_argument(
|
| 1636 |
+
"--decoding_chunk_size",
|
| 1637 |
+
default=16,
|
| 1638 |
+
type=int,
|
| 1639 |
+
required=False,
|
| 1640 |
+
help="the decoding chunk size, <=0 is not supported",
|
| 1641 |
+
)
|
| 1642 |
+
parser.add_argument(
|
| 1643 |
+
"--num_decoding_left_chunks",
|
| 1644 |
+
default=5,
|
| 1645 |
+
type=int,
|
| 1646 |
+
required=False,
|
| 1647 |
+
help="number of left chunks, <= 0 is not supported",
|
| 1648 |
+
)
|
| 1649 |
+
parser.add_argument(
|
| 1650 |
+
"--decoder_fastertransformer",
|
| 1651 |
+
action="store_true",
|
| 1652 |
+
help="return decoder_out and best_index for ft",
|
| 1653 |
+
)
|
| 1654 |
+
parser.add_argument(
|
| 1655 |
+
"--return_ctc_logprobs",
|
| 1656 |
+
action="store_true",
|
| 1657 |
+
help="return full ctc_log_probs for TLG streaming encoder",
|
| 1658 |
+
)
|
| 1659 |
+
args = parser.parse_args()
|
| 1660 |
+
prepare_pretrained_model(args)
|
| 1661 |
+
|
| 1662 |
+
torch.manual_seed(0)
|
| 1663 |
+
torch.set_printoptions(precision=10)
|
| 1664 |
+
|
| 1665 |
+
with open(args.config, "r") as fin:
|
| 1666 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 1667 |
+
if os.path.exists(args.cmvn_file):
|
| 1668 |
+
if 'cmvn' not in configs:
|
| 1669 |
+
configs['cmvn'] = "global_cmvn"
|
| 1670 |
+
configs['cmvn_conf'] = {}
|
| 1671 |
+
else:
|
| 1672 |
+
assert configs['cmvn'] == "global_cmvn"
|
| 1673 |
+
assert configs['cmvn_conf'] is not None
|
| 1674 |
+
configs['cmvn_conf']["cmvn_file"] = args.cmvn_file
|
| 1675 |
+
configs['cmvn_conf'].setdefault(
|
| 1676 |
+
"is_json_cmvn", configs.get("is_json_cmvn", True))
|
| 1677 |
+
elif configs.get('cmvn', None) == 'global_cmvn':
|
| 1678 |
+
raise FileNotFoundError(
|
| 1679 |
+
f"Expected global_cmvn in pretrained model dir: {args.cmvn_file}")
|
| 1680 |
+
if (args.reverse_weight != -1.0
|
| 1681 |
+
and "reverse_weight" in configs["model_conf"]):
|
| 1682 |
+
configs["model_conf"]["reverse_weight"] = args.reverse_weight
|
| 1683 |
+
print("Update reverse weight to", args.reverse_weight)
|
| 1684 |
+
if args.ctc_weight != -1:
|
| 1685 |
+
print("Update ctc weight to ", args.ctc_weight)
|
| 1686 |
+
configs["model_conf"]["ctc_weight"] = args.ctc_weight
|
| 1687 |
+
configs["encoder_conf"]["use_dynamic_chunk"] = False
|
| 1688 |
+
|
| 1689 |
+
model, configs = init_model(args, configs)
|
| 1690 |
+
model.eval()
|
| 1691 |
+
|
| 1692 |
+
if not os.path.exists(args.output_onnx_dir):
|
| 1693 |
+
os.mkdir(args.output_onnx_dir)
|
| 1694 |
+
|
| 1695 |
+
export_enc_func = None
|
| 1696 |
+
# if args.streaming:
|
| 1697 |
+
assert args.decoding_chunk_size > 0
|
| 1698 |
+
assert args.num_decoding_left_chunks > 0
|
| 1699 |
+
export_enc_func = export_online_encoder
|
| 1700 |
+
encoder_onnx_path = os.path.join(args.output_onnx_dir, "encoder_online.onnx")
|
| 1701 |
+
onnx_config = export_enc_func(model, configs, args, logger,
|
| 1702 |
+
encoder_onnx_path)
|
| 1703 |
+
|
| 1704 |
+
# else
|
| 1705 |
+
export_enc_func = export_offline_encoder
|
| 1706 |
+
encoder_onnx_path = os.path.join(args.output_onnx_dir, "encoder_offline.onnx")
|
| 1707 |
+
onnx_config = export_enc_func(model, configs, args, logger,
|
| 1708 |
+
encoder_onnx_path)
|
| 1709 |
+
|
| 1710 |
+
|
| 1711 |
+
decoder_onnx_path = os.path.join(args.output_onnx_dir, "decoder.onnx")
|
| 1712 |
+
export_rescoring_decoder(
|
| 1713 |
+
model,
|
| 1714 |
+
configs,
|
| 1715 |
+
args,
|
| 1716 |
+
logger,
|
| 1717 |
+
decoder_onnx_path,
|
| 1718 |
+
args.decoder_fastertransformer,
|
| 1719 |
+
)
|
| 1720 |
+
|
| 1721 |
+
config_dir = os.path.join(args.output_onnx_dir, "config.yaml")
|
| 1722 |
+
with open(config_dir, "w") as out:
|
| 1723 |
+
yaml.dump(onnx_config, out)
|
generate_data.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import shutil
|
| 4 |
+
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
|
| 7 |
+
from ort_common import WenetONNXRunner, pack_calibration_dataset
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def get_args():
|
| 11 |
+
parser = argparse.ArgumentParser(
|
| 12 |
+
description="Generate calibration_dataset for exported ONNX models")
|
| 13 |
+
parser.add_argument("--input",
|
| 14 |
+
"-i",
|
| 15 |
+
nargs="+",
|
| 16 |
+
required=True,
|
| 17 |
+
help="Input wav file(s) or directory/directories")
|
| 18 |
+
parser.add_argument("--config",
|
| 19 |
+
required=True,
|
| 20 |
+
help="yaml file in checkpoint path")
|
| 21 |
+
parser.add_argument(
|
| 22 |
+
"--vocab",
|
| 23 |
+
required=True,
|
| 24 |
+
help="pretrained units.txt, for example pretrained/<model>/units.txt",
|
| 25 |
+
)
|
| 26 |
+
parser.add_argument("--onnx_dir",
|
| 27 |
+
default="onnx_model",
|
| 28 |
+
help="directory containing exported ONNX models")
|
| 29 |
+
parser.add_argument("--calib_data_path",
|
| 30 |
+
default="calibration_dataset",
|
| 31 |
+
help="output calibration dataset directory")
|
| 32 |
+
parser.add_argument("--parts",
|
| 33 |
+
nargs="+",
|
| 34 |
+
choices=["all", "offline", "online", "decoder"],
|
| 35 |
+
default=["all"],
|
| 36 |
+
help="which model inputs to generate")
|
| 37 |
+
parser.add_argument("--offline_seq_len", type=int, default=1024)
|
| 38 |
+
parser.add_argument("--decoder_len", type=int, default=32)
|
| 39 |
+
parser.add_argument("--decoding_chunk_size", type=int, default=16)
|
| 40 |
+
parser.add_argument("--num_decoding_left_chunks", type=int, default=5)
|
| 41 |
+
parser.add_argument("--max_num",
|
| 42 |
+
type=int,
|
| 43 |
+
default=100,
|
| 44 |
+
help="maximum number of audio files used for calibration; set <= 0 to use all")
|
| 45 |
+
parser.add_argument("--keep_existing",
|
| 46 |
+
action="store_true",
|
| 47 |
+
help="append to an existing calibration directory")
|
| 48 |
+
return parser.parse_args()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def expand_audio_inputs(inputs):
|
| 52 |
+
audio_exts = {".wav", ".flac", ".mp3", ".m4a", ".ogg"}
|
| 53 |
+
audio_files = []
|
| 54 |
+
for path in inputs:
|
| 55 |
+
if os.path.isdir(path):
|
| 56 |
+
for root, _, files in os.walk(path):
|
| 57 |
+
for filename in files:
|
| 58 |
+
if os.path.splitext(filename)[1].lower() in audio_exts:
|
| 59 |
+
audio_files.append(os.path.join(root, filename))
|
| 60 |
+
else:
|
| 61 |
+
audio_files.append(path)
|
| 62 |
+
audio_files = sorted(audio_files)
|
| 63 |
+
if not audio_files:
|
| 64 |
+
raise FileNotFoundError("No audio files found")
|
| 65 |
+
return audio_files
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def normalize_parts(parts):
|
| 69 |
+
if "all" in parts:
|
| 70 |
+
return {"offline", "online", "decoder"}
|
| 71 |
+
return set(parts)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def limit_audio_files(audio_files, max_num):
|
| 75 |
+
if max_num is None or max_num <= 0:
|
| 76 |
+
return audio_files
|
| 77 |
+
return audio_files[:max_num]
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def main():
|
| 81 |
+
args = get_args()
|
| 82 |
+
parts = normalize_parts(args.parts)
|
| 83 |
+
audio_files = limit_audio_files(expand_audio_inputs(args.input),
|
| 84 |
+
args.max_num)
|
| 85 |
+
|
| 86 |
+
if os.path.exists(args.calib_data_path) and not args.keep_existing:
|
| 87 |
+
shutil.rmtree(args.calib_data_path)
|
| 88 |
+
os.makedirs(args.calib_data_path, exist_ok=True)
|
| 89 |
+
|
| 90 |
+
runner = WenetONNXRunner(
|
| 91 |
+
args.config,
|
| 92 |
+
args.vocab,
|
| 93 |
+
onnx_dir=args.onnx_dir,
|
| 94 |
+
offline_seq_len=args.offline_seq_len,
|
| 95 |
+
decoder_len=args.decoder_len,
|
| 96 |
+
decoding_chunk_size=args.decoding_chunk_size,
|
| 97 |
+
num_decoding_left_chunks=args.num_decoding_left_chunks,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
counts = {"offline": 0, "online": 0, "decoder": 0}
|
| 101 |
+
progress = tqdm(audio_files,
|
| 102 |
+
desc="Generating calibration data",
|
| 103 |
+
unit="wav")
|
| 104 |
+
for audio_idx, audio_file in enumerate(progress):
|
| 105 |
+
sample_counts = runner.save_calibration_for_audio(
|
| 106 |
+
audio_file, parts, args.calib_data_path, audio_idx)
|
| 107 |
+
for key, value in sample_counts.items():
|
| 108 |
+
counts[key] += value
|
| 109 |
+
progress.set_postfix(offline=counts["offline"],
|
| 110 |
+
online=counts["online"],
|
| 111 |
+
decoder=counts["decoder"])
|
| 112 |
+
|
| 113 |
+
print("Packing calibration dataset...")
|
| 114 |
+
pack_calibration_dataset(args.calib_data_path)
|
| 115 |
+
print(f"Generated calibration data in {args.calib_data_path}")
|
| 116 |
+
print(f"offline samples: {counts['offline']}")
|
| 117 |
+
print(f"online samples: {counts['online']}")
|
| 118 |
+
print(f"decoder samples: {counts['decoder']}")
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
if __name__ == "__main__":
|
| 122 |
+
main()
|
ort_common.py
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import multiprocessing
|
| 2 |
+
import os
|
| 3 |
+
import tarfile as tf
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
import torchaudio
|
| 8 |
+
import torchaudio.compliance.kaldi as kaldi
|
| 9 |
+
import yaml
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
from swig_decoders import (map_batch, ctc_beam_search_decoder_batch,
|
| 13 |
+
TrieVector, PathTrie)
|
| 14 |
+
except ModuleNotFoundError:
|
| 15 |
+
class PathTrie:
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
class TrieVector(list):
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
def _log_add(*values):
|
| 22 |
+
values = [value for value in values if value != -float("inf")]
|
| 23 |
+
if not values:
|
| 24 |
+
return -float("inf")
|
| 25 |
+
max_value = max(values)
|
| 26 |
+
return max_value + np.log(sum(np.exp(value - max_value)
|
| 27 |
+
for value in values))
|
| 28 |
+
|
| 29 |
+
def _map_sentence(sent, vocabulary, greedy=False, blank_id=0):
|
| 30 |
+
mapped = []
|
| 31 |
+
prev = None
|
| 32 |
+
for token in sent:
|
| 33 |
+
token = int(token)
|
| 34 |
+
if greedy and token == prev:
|
| 35 |
+
prev = token
|
| 36 |
+
continue
|
| 37 |
+
prev = token
|
| 38 |
+
if token == blank_id or token < 0 or token >= len(vocabulary):
|
| 39 |
+
continue
|
| 40 |
+
piece = vocabulary[token]
|
| 41 |
+
if piece.startswith("<") and piece.endswith(">"):
|
| 42 |
+
continue
|
| 43 |
+
mapped.append(piece)
|
| 44 |
+
return "".join(mapped)
|
| 45 |
+
|
| 46 |
+
def map_batch(batch_sents, vocabulary, num_processes, greedy=False,
|
| 47 |
+
blank_id=0):
|
| 48 |
+
del num_processes
|
| 49 |
+
return [_map_sentence(sent, vocabulary, greedy, blank_id)
|
| 50 |
+
for sent in batch_sents]
|
| 51 |
+
|
| 52 |
+
def _ctc_prefix_beam_search(log_probs_seq, log_probs_idx, beam_size,
|
| 53 |
+
blank_id):
|
| 54 |
+
beam = {(): (0.0, -float("inf"))}
|
| 55 |
+
for frame_probs, frame_ids in zip(log_probs_seq, log_probs_idx):
|
| 56 |
+
next_beam = {}
|
| 57 |
+
for prefix, (prob_blank, prob_non_blank) in beam.items():
|
| 58 |
+
for prob, token in zip(frame_probs, frame_ids):
|
| 59 |
+
token = int(token)
|
| 60 |
+
prob = float(prob)
|
| 61 |
+
next_prob_blank, next_prob_non_blank = next_beam.get(
|
| 62 |
+
prefix, (-float("inf"), -float("inf")))
|
| 63 |
+
if token == blank_id:
|
| 64 |
+
next_beam[prefix] = (
|
| 65 |
+
_log_add(next_prob_blank, prob_blank + prob,
|
| 66 |
+
prob_non_blank + prob),
|
| 67 |
+
next_prob_non_blank,
|
| 68 |
+
)
|
| 69 |
+
continue
|
| 70 |
+
|
| 71 |
+
last = prefix[-1] if prefix else None
|
| 72 |
+
if token == last:
|
| 73 |
+
next_beam[prefix] = (
|
| 74 |
+
next_prob_blank,
|
| 75 |
+
_log_add(next_prob_non_blank,
|
| 76 |
+
prob_non_blank + prob),
|
| 77 |
+
)
|
| 78 |
+
new_prefix = prefix + (token, )
|
| 79 |
+
nb_blank, nb_non_blank = next_beam.get(
|
| 80 |
+
new_prefix, (-float("inf"), -float("inf")))
|
| 81 |
+
next_beam[new_prefix] = (
|
| 82 |
+
nb_blank,
|
| 83 |
+
_log_add(nb_non_blank, prob_blank + prob),
|
| 84 |
+
)
|
| 85 |
+
else:
|
| 86 |
+
new_prefix = prefix + (token, )
|
| 87 |
+
nb_blank, nb_non_blank = next_beam.get(
|
| 88 |
+
new_prefix, (-float("inf"), -float("inf")))
|
| 89 |
+
next_beam[new_prefix] = (
|
| 90 |
+
nb_blank,
|
| 91 |
+
_log_add(nb_non_blank, prob_blank + prob,
|
| 92 |
+
prob_non_blank + prob),
|
| 93 |
+
)
|
| 94 |
+
beam = dict(sorted(
|
| 95 |
+
next_beam.items(),
|
| 96 |
+
key=lambda item: _log_add(item[1][0], item[1][1]),
|
| 97 |
+
reverse=True)[:beam_size])
|
| 98 |
+
return [(_log_add(prob_blank, prob_non_blank), prefix)
|
| 99 |
+
for prefix, (prob_blank, prob_non_blank) in sorted(
|
| 100 |
+
beam.items(),
|
| 101 |
+
key=lambda item: _log_add(item[1][0], item[1][1]),
|
| 102 |
+
reverse=True)]
|
| 103 |
+
|
| 104 |
+
def ctc_beam_search_decoder_batch(batch_log_probs_seq,
|
| 105 |
+
batch_log_probs_idx,
|
| 106 |
+
batch_root_trie,
|
| 107 |
+
batch_start,
|
| 108 |
+
beam_size,
|
| 109 |
+
num_processes,
|
| 110 |
+
blank_id=0,
|
| 111 |
+
space_id=-1,
|
| 112 |
+
cutoff_prob=0.999,
|
| 113 |
+
ext_scorer=None):
|
| 114 |
+
del batch_root_trie, batch_start, num_processes, space_id
|
| 115 |
+
del cutoff_prob, ext_scorer
|
| 116 |
+
return [
|
| 117 |
+
_ctc_prefix_beam_search(log_probs_seq, log_probs_idx, beam_size,
|
| 118 |
+
blank_id)
|
| 119 |
+
for log_probs_seq, log_probs_idx in zip(batch_log_probs_seq,
|
| 120 |
+
batch_log_probs_idx)
|
| 121 |
+
]
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def load_config(config_path):
|
| 125 |
+
with open(config_path, "r") as fin:
|
| 126 |
+
return yaml.load(fin, Loader=yaml.FullLoader)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def load_vocab(vocab_path):
|
| 130 |
+
vocabulary = []
|
| 131 |
+
char_dict = {}
|
| 132 |
+
with open(vocab_path, "r") as fin:
|
| 133 |
+
for line in fin:
|
| 134 |
+
arr = line.strip().split()
|
| 135 |
+
assert len(arr) == 2
|
| 136 |
+
char_dict[int(arr[1])] = arr[0]
|
| 137 |
+
vocabulary.append(arr[0])
|
| 138 |
+
return vocabulary, char_dict
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def compute_feats(audio_file: str, sr=16000) -> np.ndarray:
|
| 142 |
+
try:
|
| 143 |
+
import soundfile as sf
|
| 144 |
+
waveform, sample_rate = sf.read(audio_file, dtype="int16",
|
| 145 |
+
always_2d=True)
|
| 146 |
+
waveform = torch.from_numpy(waveform.T).to(torch.float)
|
| 147 |
+
except ModuleNotFoundError:
|
| 148 |
+
waveform, sample_rate = torchaudio.load(audio_file, normalize=True)
|
| 149 |
+
waveform = waveform.to(torch.float)
|
| 150 |
+
if sample_rate != sr:
|
| 151 |
+
waveform = torchaudio.transforms.Resample(
|
| 152 |
+
orig_freq=sample_rate, new_freq=sr)(waveform)
|
| 153 |
+
feats = kaldi.fbank(waveform,
|
| 154 |
+
num_mel_bins=80,
|
| 155 |
+
frame_length=25,
|
| 156 |
+
frame_shift=10,
|
| 157 |
+
energy_floor=0.0,
|
| 158 |
+
sample_frequency=sr)
|
| 159 |
+
return feats.unsqueeze(0).numpy()
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def pad_array_along_axis(array, pad_width, axis, mode="constant", **kwargs):
|
| 163 |
+
if array.shape[axis] >= pad_width:
|
| 164 |
+
return array
|
| 165 |
+
full_pad_width = [(0, 0)] * array.ndim
|
| 166 |
+
full_pad_width[axis] = (0, pad_width - array.shape[axis])
|
| 167 |
+
return np.pad(array, pad_width=full_pad_width, mode=mode, **kwargs)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def ctc_decoding(beam_log_probs,
|
| 171 |
+
beam_log_probs_idx,
|
| 172 |
+
encoder_out_lens,
|
| 173 |
+
vocabulary,
|
| 174 |
+
mode="ctc_prefix_beam_search"):
|
| 175 |
+
beam_size = beam_log_probs.shape[-1]
|
| 176 |
+
batch_size = beam_log_probs.shape[0]
|
| 177 |
+
num_processes = min(multiprocessing.cpu_count(), batch_size)
|
| 178 |
+
hyps = []
|
| 179 |
+
score_hyps = []
|
| 180 |
+
|
| 181 |
+
if mode == "ctc_greedy_search":
|
| 182 |
+
log_probs_idx = beam_log_probs_idx[:, :, 0]
|
| 183 |
+
batch_sents = []
|
| 184 |
+
for idx, seq in enumerate(log_probs_idx):
|
| 185 |
+
batch_sents.append(seq[0:encoder_out_lens[idx]].tolist())
|
| 186 |
+
hyps = map_batch(batch_sents, vocabulary, num_processes, True, 0)
|
| 187 |
+
elif mode in ("ctc_prefix_beam_search", "attention_rescoring"):
|
| 188 |
+
batch_log_probs_seq_list = beam_log_probs.tolist()
|
| 189 |
+
batch_log_probs_idx_list = beam_log_probs_idx.tolist()
|
| 190 |
+
batch_len_list = encoder_out_lens.tolist()
|
| 191 |
+
batch_log_probs_seq = []
|
| 192 |
+
batch_log_probs_ids = []
|
| 193 |
+
batch_start = []
|
| 194 |
+
batch_root = TrieVector()
|
| 195 |
+
root_dict = {}
|
| 196 |
+
for i in range(len(batch_len_list)):
|
| 197 |
+
num_sent = batch_len_list[i]
|
| 198 |
+
batch_log_probs_seq.append(batch_log_probs_seq_list[i][0:num_sent])
|
| 199 |
+
batch_log_probs_ids.append(batch_log_probs_idx_list[i][0:num_sent])
|
| 200 |
+
root_dict[i] = PathTrie()
|
| 201 |
+
batch_root.append(root_dict[i])
|
| 202 |
+
batch_start.append(True)
|
| 203 |
+
score_hyps = ctc_beam_search_decoder_batch(batch_log_probs_seq,
|
| 204 |
+
batch_log_probs_ids,
|
| 205 |
+
batch_root,
|
| 206 |
+
batch_start,
|
| 207 |
+
beam_size,
|
| 208 |
+
num_processes,
|
| 209 |
+
0, -2, 0.99999)
|
| 210 |
+
if mode == "ctc_prefix_beam_search":
|
| 211 |
+
for cand_hyps in score_hyps:
|
| 212 |
+
hyps.append(cand_hyps[0][1])
|
| 213 |
+
hyps = map_batch(hyps, vocabulary, num_processes, False, 0)
|
| 214 |
+
return hyps, score_hyps
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def make_decoder_inputs(encoder_out,
|
| 218 |
+
encoder_out_lens,
|
| 219 |
+
beam_log_probs,
|
| 220 |
+
beam_log_probs_idx,
|
| 221 |
+
vocabulary,
|
| 222 |
+
sos,
|
| 223 |
+
eos,
|
| 224 |
+
decoder_len):
|
| 225 |
+
_, score_hyps = ctc_decoding(beam_log_probs, beam_log_probs_idx,
|
| 226 |
+
encoder_out_lens, vocabulary,
|
| 227 |
+
"attention_rescoring")
|
| 228 |
+
ignore_id = -1
|
| 229 |
+
beam_size = beam_log_probs.shape[-1]
|
| 230 |
+
batch_size = beam_log_probs.shape[0]
|
| 231 |
+
ctc_score, all_hyps = [], []
|
| 232 |
+
for hyps in score_hyps:
|
| 233 |
+
cur_len = len(hyps)
|
| 234 |
+
if len(hyps) < beam_size:
|
| 235 |
+
hyps += (beam_size - cur_len) * [(-float("inf"), (0,))]
|
| 236 |
+
cur_ctc_score = []
|
| 237 |
+
for hyp in hyps:
|
| 238 |
+
cur_ctc_score.append(hyp[0])
|
| 239 |
+
all_hyps.append(list(hyp[1]))
|
| 240 |
+
ctc_score.append(cur_ctc_score)
|
| 241 |
+
ctc_score = np.array(ctc_score, dtype=np.float32)
|
| 242 |
+
|
| 243 |
+
max_len = decoder_len - 2
|
| 244 |
+
hyps_pad_sos_eos = np.ones((batch_size, beam_size, max_len + 2),
|
| 245 |
+
dtype=np.int64) * ignore_id
|
| 246 |
+
r_hyps_pad_sos_eos = np.ones((batch_size, beam_size, max_len + 2),
|
| 247 |
+
dtype=np.int64) * ignore_id
|
| 248 |
+
hyps_lens_sos = np.ones((batch_size, beam_size), dtype=np.int32)
|
| 249 |
+
k = 0
|
| 250 |
+
for i in range(batch_size):
|
| 251 |
+
for j in range(beam_size):
|
| 252 |
+
cand = all_hyps[k][:max_len]
|
| 253 |
+
length = len(cand) + 2
|
| 254 |
+
hyps_pad_sos_eos[i][j][0:length] = [sos] + cand + [eos]
|
| 255 |
+
r_hyps_pad_sos_eos[i][j][0:length] = [sos] + cand[::-1] + [eos]
|
| 256 |
+
hyps_lens_sos[i][j] = len(cand) + 1
|
| 257 |
+
k += 1
|
| 258 |
+
|
| 259 |
+
if decoder_len > encoder_out.shape[1]:
|
| 260 |
+
encoder_out = np.pad(encoder_out,
|
| 261 |
+
[(0, 0),
|
| 262 |
+
(0, decoder_len - encoder_out.shape[1]),
|
| 263 |
+
(0, 0)],
|
| 264 |
+
mode="constant",
|
| 265 |
+
constant_values=0)
|
| 266 |
+
elif decoder_len < encoder_out.shape[1]:
|
| 267 |
+
encoder_out = encoder_out[:, :decoder_len, :]
|
| 268 |
+
|
| 269 |
+
return {
|
| 270 |
+
"encoder_out": encoder_out,
|
| 271 |
+
"encoder_out_lens": np.full(batch_size,
|
| 272 |
+
fill_value=decoder_len,
|
| 273 |
+
dtype=np.int32),
|
| 274 |
+
"hyps_pad_sos_eos": hyps_pad_sos_eos.astype(np.int32),
|
| 275 |
+
"hyps_lens_sos": hyps_lens_sos,
|
| 276 |
+
"r_hyps_pad_sos_eos": r_hyps_pad_sos_eos.astype(np.int32),
|
| 277 |
+
"ctc_score": ctc_score,
|
| 278 |
+
}, all_hyps
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def make_offline_inputs(feats, seq_len):
|
| 282 |
+
feats = feats[:, :seq_len, :]
|
| 283 |
+
speech_lengths = np.array([feats.shape[1]], dtype=np.int32)
|
| 284 |
+
if feats.shape[1] < seq_len:
|
| 285 |
+
feats = pad_array_along_axis(feats, pad_width=seq_len, axis=1)
|
| 286 |
+
return {"speech": feats, "speech_lengths": speech_lengths}
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def make_online_initial_state(configs,
|
| 290 |
+
batch_size=1,
|
| 291 |
+
decoding_chunk_size=16,
|
| 292 |
+
num_decoding_left_chunks=5):
|
| 293 |
+
subsampling = 4
|
| 294 |
+
context = 7
|
| 295 |
+
stride = subsampling * decoding_chunk_size
|
| 296 |
+
decoding_window = (decoding_chunk_size - 1) * subsampling + context
|
| 297 |
+
required_cache_size = decoding_chunk_size * num_decoding_left_chunks
|
| 298 |
+
|
| 299 |
+
output_size = configs["encoder_conf"]["output_size"]
|
| 300 |
+
num_layers = configs["encoder_conf"]["num_blocks"]
|
| 301 |
+
cnn_module_kernel = configs["encoder_conf"].get("cnn_module_kernel", 1) - 1
|
| 302 |
+
head = configs["encoder_conf"]["attention_heads"]
|
| 303 |
+
d_k = configs["encoder_conf"]["output_size"] // head
|
| 304 |
+
|
| 305 |
+
state = {
|
| 306 |
+
"att_cache": np.zeros((batch_size, num_layers, head,
|
| 307 |
+
required_cache_size, d_k * 2),
|
| 308 |
+
dtype=np.float32),
|
| 309 |
+
"cnn_cache": np.zeros((batch_size, num_layers, output_size,
|
| 310 |
+
cnn_module_kernel),
|
| 311 |
+
dtype=np.float32),
|
| 312 |
+
"cache_mask": np.zeros((batch_size, 1, required_cache_size),
|
| 313 |
+
dtype=np.float32),
|
| 314 |
+
"offset": np.zeros((batch_size, 1), dtype=np.int32),
|
| 315 |
+
}
|
| 316 |
+
params = {
|
| 317 |
+
"batch_size": batch_size,
|
| 318 |
+
"context": context,
|
| 319 |
+
"stride": stride,
|
| 320 |
+
"decoding_window": decoding_window,
|
| 321 |
+
}
|
| 322 |
+
return state, params
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def make_online_encoder_input(feats, cur, params, state):
|
| 326 |
+
batch_size = params["batch_size"]
|
| 327 |
+
decoding_window = params["decoding_window"]
|
| 328 |
+
end = min(cur + decoding_window, feats.shape[1])
|
| 329 |
+
chunk_xs = feats[:, cur:end, :]
|
| 330 |
+
if chunk_xs.shape[1] < decoding_window:
|
| 331 |
+
chunk_xs = pad_array_along_axis(chunk_xs,
|
| 332 |
+
pad_width=decoding_window,
|
| 333 |
+
axis=1)
|
| 334 |
+
chunk_xs = chunk_xs.astype(np.float32)
|
| 335 |
+
chunk_lens = np.full(batch_size,
|
| 336 |
+
fill_value=chunk_xs.shape[1],
|
| 337 |
+
dtype=np.int32)
|
| 338 |
+
return {
|
| 339 |
+
"chunk_xs": chunk_xs,
|
| 340 |
+
"chunk_lens": chunk_lens,
|
| 341 |
+
"offset": state["offset"],
|
| 342 |
+
"att_cache": state["att_cache"],
|
| 343 |
+
"cnn_cache": state["cnn_cache"],
|
| 344 |
+
"cache_mask": state["cache_mask"],
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
def update_online_state(state, outputs):
|
| 349 |
+
state["offset"] = outputs[4]
|
| 350 |
+
state["att_cache"] = outputs[5]
|
| 351 |
+
state["cnn_cache"] = outputs[6]
|
| 352 |
+
state["cache_mask"] = outputs[7]
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def save_calibration_inputs(calib_data_path, inputs, sample_id):
|
| 356 |
+
for input_name, data in inputs.items():
|
| 357 |
+
data_path = os.path.join(calib_data_path, input_name)
|
| 358 |
+
os.makedirs(data_path, exist_ok=True)
|
| 359 |
+
np.save(os.path.join(data_path, f"{sample_id}.npy"), data)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def pack_calibration_dataset(calib_data_path):
|
| 363 |
+
for input_name in sorted(os.listdir(calib_data_path)):
|
| 364 |
+
data_path = os.path.join(calib_data_path, input_name)
|
| 365 |
+
if not os.path.isdir(data_path):
|
| 366 |
+
continue
|
| 367 |
+
tar_path = os.path.join(calib_data_path, input_name + ".tar.gz")
|
| 368 |
+
with tf.open(tar_path, "w:gz") as tf_file:
|
| 369 |
+
tf_file.add(data_path, arcname=input_name)
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
class WenetONNXRunner:
|
| 373 |
+
|
| 374 |
+
def __init__(self,
|
| 375 |
+
config_path,
|
| 376 |
+
vocab_path,
|
| 377 |
+
onnx_dir="onnx_model",
|
| 378 |
+
offline_seq_len=1024,
|
| 379 |
+
decoder_len=32,
|
| 380 |
+
decoding_chunk_size=16,
|
| 381 |
+
num_decoding_left_chunks=5,
|
| 382 |
+
batch_size=1,
|
| 383 |
+
providers=None):
|
| 384 |
+
self.config_path = config_path
|
| 385 |
+
self.vocab_path = vocab_path
|
| 386 |
+
self.onnx_dir = onnx_dir
|
| 387 |
+
self.offline_seq_len = offline_seq_len
|
| 388 |
+
self.decoder_len = decoder_len
|
| 389 |
+
self.decoding_chunk_size = decoding_chunk_size
|
| 390 |
+
self.num_decoding_left_chunks = num_decoding_left_chunks
|
| 391 |
+
self.batch_size = batch_size
|
| 392 |
+
self.providers = providers or ["CPUExecutionProvider"]
|
| 393 |
+
|
| 394 |
+
self.configs = load_config(config_path)
|
| 395 |
+
self.vocabulary, self.char_dict = load_vocab(vocab_path)
|
| 396 |
+
self.eos = self.sos = len(self.char_dict) - 1
|
| 397 |
+
|
| 398 |
+
self._offline_encoder = None
|
| 399 |
+
self._online_encoder = None
|
| 400 |
+
self._decoder = None
|
| 401 |
+
|
| 402 |
+
@property
|
| 403 |
+
def offline_encoder(self):
|
| 404 |
+
if self._offline_encoder is None:
|
| 405 |
+
self._offline_encoder = self._new_session("encoder_offline.onnx")
|
| 406 |
+
return self._offline_encoder
|
| 407 |
+
|
| 408 |
+
@property
|
| 409 |
+
def online_encoder(self):
|
| 410 |
+
if self._online_encoder is None:
|
| 411 |
+
self._online_encoder = self._new_session("encoder_online.onnx")
|
| 412 |
+
return self._online_encoder
|
| 413 |
+
|
| 414 |
+
@property
|
| 415 |
+
def decoder(self):
|
| 416 |
+
if self._decoder is None:
|
| 417 |
+
self._decoder = self._new_session("decoder.onnx")
|
| 418 |
+
return self._decoder
|
| 419 |
+
|
| 420 |
+
def _new_session(self, filename):
|
| 421 |
+
import onnxruntime as ort
|
| 422 |
+
return ort.InferenceSession(os.path.join(self.onnx_dir, filename),
|
| 423 |
+
providers=self.providers)
|
| 424 |
+
|
| 425 |
+
def compute_feats(self, audio_file):
|
| 426 |
+
return compute_feats(audio_file)
|
| 427 |
+
|
| 428 |
+
def run_offline_encoder(self,
|
| 429 |
+
feats,
|
| 430 |
+
calib_data_path=None,
|
| 431 |
+
sample_id=0):
|
| 432 |
+
encoder_input = make_offline_inputs(feats, self.offline_seq_len)
|
| 433 |
+
if calib_data_path:
|
| 434 |
+
save_calibration_inputs(calib_data_path, encoder_input, sample_id)
|
| 435 |
+
encoder_out, encoder_out_lens, ctc_log_probs, beam_log_probs, beam_log_probs_idx = (
|
| 436 |
+
self.offline_encoder.run(None, encoder_input))
|
| 437 |
+
return {
|
| 438 |
+
"encoder_out": encoder_out,
|
| 439 |
+
"encoder_out_lens": encoder_out_lens,
|
| 440 |
+
"ctc_log_probs": ctc_log_probs,
|
| 441 |
+
"beam_log_probs": beam_log_probs,
|
| 442 |
+
"beam_log_probs_idx": beam_log_probs_idx,
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
def run_online_encoder(self,
|
| 446 |
+
feats,
|
| 447 |
+
calib_data_path=None,
|
| 448 |
+
sample_prefix=0):
|
| 449 |
+
state, online_params = make_online_initial_state(
|
| 450 |
+
self.configs, self.batch_size, self.decoding_chunk_size,
|
| 451 |
+
self.num_decoding_left_chunks)
|
| 452 |
+
encoder_out = []
|
| 453 |
+
beam_log_probs = []
|
| 454 |
+
beam_log_probs_idx = []
|
| 455 |
+
num_frames = feats.shape[1]
|
| 456 |
+
|
| 457 |
+
for cur in range(0, num_frames - online_params["context"] + 1,
|
| 458 |
+
online_params["stride"]):
|
| 459 |
+
encoder_input = make_online_encoder_input(feats, cur,
|
| 460 |
+
online_params, state)
|
| 461 |
+
if calib_data_path:
|
| 462 |
+
save_calibration_inputs(calib_data_path, encoder_input,
|
| 463 |
+
f"{sample_prefix}_{cur}")
|
| 464 |
+
outputs = self.online_encoder.run(None, encoder_input)
|
| 465 |
+
chunk_log_probs, chunk_log_probs_idx, chunk_out, chunk_out_lens = outputs[:4]
|
| 466 |
+
update_online_state(state, outputs)
|
| 467 |
+
del chunk_out_lens
|
| 468 |
+
encoder_out.append(chunk_out)
|
| 469 |
+
beam_log_probs.append(chunk_log_probs)
|
| 470 |
+
beam_log_probs_idx.append(chunk_log_probs_idx.astype(np.int32))
|
| 471 |
+
|
| 472 |
+
return {
|
| 473 |
+
"encoder_out": np.concatenate(encoder_out, axis=1),
|
| 474 |
+
"encoder_out_lens": np.full(self.batch_size,
|
| 475 |
+
fill_value=sum(
|
| 476 |
+
out.shape[1]
|
| 477 |
+
for out in encoder_out),
|
| 478 |
+
dtype=np.int32),
|
| 479 |
+
"beam_log_probs": np.concatenate(beam_log_probs, axis=1),
|
| 480 |
+
"beam_log_probs_idx": np.concatenate(beam_log_probs_idx, axis=1),
|
| 481 |
+
"num_chunks": len(encoder_out),
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
def ctc_decode(self, encoder_outputs, mode):
|
| 485 |
+
return ctc_decoding(encoder_outputs["beam_log_probs"],
|
| 486 |
+
encoder_outputs["beam_log_probs_idx"],
|
| 487 |
+
encoder_outputs["encoder_out_lens"],
|
| 488 |
+
self.vocabulary, mode)
|
| 489 |
+
|
| 490 |
+
def run_decoder(self,
|
| 491 |
+
encoder_outputs,
|
| 492 |
+
calib_data_path=None,
|
| 493 |
+
sample_id=0):
|
| 494 |
+
decoder_input, all_hyps = make_decoder_inputs(
|
| 495 |
+
encoder_outputs["encoder_out"],
|
| 496 |
+
encoder_outputs["encoder_out_lens"],
|
| 497 |
+
encoder_outputs["beam_log_probs"],
|
| 498 |
+
encoder_outputs["beam_log_probs_idx"],
|
| 499 |
+
self.vocabulary,
|
| 500 |
+
self.sos,
|
| 501 |
+
self.eos,
|
| 502 |
+
self.decoder_len,
|
| 503 |
+
)
|
| 504 |
+
if calib_data_path:
|
| 505 |
+
save_calibration_inputs(calib_data_path, decoder_input, sample_id)
|
| 506 |
+
|
| 507 |
+
best_index = self.decoder.run(None, decoder_input)[0].astype(np.int32)
|
| 508 |
+
beam_size = encoder_outputs["beam_log_probs"].shape[-1]
|
| 509 |
+
num_processes = min(multiprocessing.cpu_count(), best_index.shape[0])
|
| 510 |
+
best_sents = []
|
| 511 |
+
k = 0
|
| 512 |
+
for idx in best_index:
|
| 513 |
+
best_sents.append(all_hyps[k:k + beam_size][idx])
|
| 514 |
+
k += beam_size
|
| 515 |
+
hyps = map_batch(best_sents, self.vocabulary, num_processes)
|
| 516 |
+
return "".join(hyps)
|
| 517 |
+
|
| 518 |
+
def transcribe(self,
|
| 519 |
+
audio_file,
|
| 520 |
+
online=False,
|
| 521 |
+
mode="ctc_prefix_beam_search",
|
| 522 |
+
calib_data_path=None):
|
| 523 |
+
feats = self.compute_feats(audio_file)
|
| 524 |
+
if online:
|
| 525 |
+
encoder_outputs = self.run_online_encoder(
|
| 526 |
+
feats, calib_data_path=calib_data_path, sample_prefix=0)
|
| 527 |
+
else:
|
| 528 |
+
encoder_outputs = self.run_offline_encoder(
|
| 529 |
+
feats, calib_data_path=calib_data_path, sample_id=0)
|
| 530 |
+
|
| 531 |
+
if mode == "attention_rescoring":
|
| 532 |
+
result = self.run_decoder(encoder_outputs, calib_data_path, 0)
|
| 533 |
+
else:
|
| 534 |
+
hyps, _ = self.ctc_decode(encoder_outputs, mode)
|
| 535 |
+
result = "".join(hyps) if hyps else ""
|
| 536 |
+
|
| 537 |
+
if calib_data_path:
|
| 538 |
+
pack_calibration_dataset(calib_data_path)
|
| 539 |
+
return result
|
| 540 |
+
|
| 541 |
+
def save_calibration_for_audio(self,
|
| 542 |
+
audio_file,
|
| 543 |
+
parts,
|
| 544 |
+
calib_data_path,
|
| 545 |
+
sample_id):
|
| 546 |
+
counts = {"offline": 0, "online": 0, "decoder": 0}
|
| 547 |
+
feats = self.compute_feats(audio_file)
|
| 548 |
+
offline_outputs = None
|
| 549 |
+
if "offline" in parts or "decoder" in parts:
|
| 550 |
+
offline_outputs = self.run_offline_encoder(
|
| 551 |
+
feats,
|
| 552 |
+
calib_data_path=calib_data_path if "offline" in parts else None,
|
| 553 |
+
sample_id=sample_id)
|
| 554 |
+
if "offline" in parts:
|
| 555 |
+
counts["offline"] += 1
|
| 556 |
+
if "online" in parts:
|
| 557 |
+
online_outputs = self.run_online_encoder(
|
| 558 |
+
feats,
|
| 559 |
+
calib_data_path=calib_data_path,
|
| 560 |
+
sample_prefix=sample_id)
|
| 561 |
+
counts["online"] += online_outputs["num_chunks"]
|
| 562 |
+
if "decoder" in parts:
|
| 563 |
+
self.run_decoder(offline_outputs,
|
| 564 |
+
calib_data_path=calib_data_path,
|
| 565 |
+
sample_id=sample_id)
|
| 566 |
+
counts["decoder"] += 1
|
| 567 |
+
return counts
|
requirements_ax.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
https://github.com/AXERA-TECH/pyaxengine/releases/download/0.1.3.rc3/axengine-0.1.3-py3-none-any.whl
|
| 2 |
+
pyyaml
|
| 3 |
+
numpy
|
requirements_x86.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
--extra-index-url https://download.pytorch.org/whl/cpu
|
| 2 |
+
|
| 3 |
+
pyyaml>=5.1
|
| 4 |
+
torch==2.11.0+cpu
|
| 5 |
+
torchaudio==2.11.0+cpu
|
| 6 |
+
tqdm
|
| 7 |
+
soundfile
|
| 8 |
+
openai-whisper==20250625
|
| 9 |
+
onnx
|
| 10 |
+
onnxruntime
|
| 11 |
+
onnxscript
|
| 12 |
+
onnxsim
|
run_ax.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from ax_common import WenetAXRunner
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def get_args():
|
| 7 |
+
parser = argparse.ArgumentParser()
|
| 8 |
+
parser.add_argument("--input", "-i", type=str, required=True, help="Input audio file")
|
| 9 |
+
parser.add_argument("--online", action="store_true")
|
| 10 |
+
parser.add_argument("--config",
|
| 11 |
+
type=str,
|
| 12 |
+
default="pretrained/aishell_u2pp_conformer_exp/train.yaml",
|
| 13 |
+
help="yaml file in checkpoint path")
|
| 14 |
+
parser.add_argument("--vocab",
|
| 15 |
+
type=str,
|
| 16 |
+
default="pretrained/aishell_u2pp_conformer_exp/units.txt",
|
| 17 |
+
help="pretrained units.txt")
|
| 18 |
+
parser.add_argument("--encoder_online",
|
| 19 |
+
type=str,
|
| 20 |
+
default="axmodel/encoder_online/encoder_online.axmodel")
|
| 21 |
+
parser.add_argument("--encoder_offline",
|
| 22 |
+
type=str,
|
| 23 |
+
default="axmodel/encoder_offline/encoder_offline.axmodel")
|
| 24 |
+
parser.add_argument("--decoder",
|
| 25 |
+
type=str,
|
| 26 |
+
default="axmodel/decoder/decoder.axmodel")
|
| 27 |
+
parser.add_argument("--offline_seq_len", type=int, default=1024)
|
| 28 |
+
parser.add_argument("--online_seq_len", type=int, default=67)
|
| 29 |
+
parser.add_argument("--decoder_len", type=int, default=32)
|
| 30 |
+
parser.add_argument("--decoding_chunk_size", type=int, default=16)
|
| 31 |
+
parser.add_argument("--num_decoding_left_chunks", type=int, default=5)
|
| 32 |
+
parser.add_argument("--mode",
|
| 33 |
+
choices=[
|
| 34 |
+
"ctc_greedy_search", "ctc_prefix_beam_search",
|
| 35 |
+
"attention_rescoring"
|
| 36 |
+
],
|
| 37 |
+
default="ctc_prefix_beam_search",
|
| 38 |
+
help="decoding mode")
|
| 39 |
+
return parser.parse_args()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def main():
|
| 43 |
+
args = get_args()
|
| 44 |
+
print(f"online: {args.online}")
|
| 45 |
+
print(f"mode: {args.mode}")
|
| 46 |
+
|
| 47 |
+
runner = WenetAXRunner(
|
| 48 |
+
args.config,
|
| 49 |
+
args.vocab,
|
| 50 |
+
encoder_offline_path=args.encoder_offline,
|
| 51 |
+
encoder_online_path=args.encoder_online,
|
| 52 |
+
decoder_path=args.decoder,
|
| 53 |
+
offline_seq_len=args.offline_seq_len,
|
| 54 |
+
decoder_len=args.decoder_len,
|
| 55 |
+
decoding_chunk_size=args.decoding_chunk_size,
|
| 56 |
+
num_decoding_left_chunks=args.num_decoding_left_chunks,
|
| 57 |
+
)
|
| 58 |
+
result = runner.transcribe(args.input,
|
| 59 |
+
online=args.online,
|
| 60 |
+
mode=args.mode)
|
| 61 |
+
print(f"ASR Result: {result}")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
if __name__ == "__main__":
|
| 65 |
+
main()
|
run_ort.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
from ort_common import WenetONNXRunner
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def get_args():
|
| 8 |
+
parser = argparse.ArgumentParser()
|
| 9 |
+
parser.add_argument("--input", "-i", type=str, required=True, help="Input audio file")
|
| 10 |
+
parser.add_argument("--config", type=str, required=True, help="yaml file in checkpoint path")
|
| 11 |
+
parser.add_argument(
|
| 12 |
+
"--vocab",
|
| 13 |
+
type=str,
|
| 14 |
+
required=True,
|
| 15 |
+
help="pretrained units.txt, for example pretrained/<model>/units.txt",
|
| 16 |
+
)
|
| 17 |
+
parser.add_argument("--online", action="store_true")
|
| 18 |
+
parser.add_argument("--offline_seq_len", type=int, default=1024)
|
| 19 |
+
parser.add_argument("--online_seq_len", type=int, default=67)
|
| 20 |
+
parser.add_argument("--decoder_len", type=int, default=32)
|
| 21 |
+
parser.add_argument("--onnx_dir", type=str, default="onnx_model")
|
| 22 |
+
parser.add_argument('--mode',
|
| 23 |
+
choices=[
|
| 24 |
+
'ctc_greedy_search', 'ctc_prefix_beam_search', 'attention_rescoring'],
|
| 25 |
+
default='ctc_prefix_beam_search',
|
| 26 |
+
help='decoding mode')
|
| 27 |
+
parser.add_argument("--calib_data_path", type=str, default="calibration_dataset", help="Generated calibration data path")
|
| 28 |
+
return parser.parse_args()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def run_ort():
|
| 32 |
+
args = get_args()
|
| 33 |
+
print(f"online: {args.online}")
|
| 34 |
+
print(f"mode: {args.mode}")
|
| 35 |
+
print(f"calib_data_path: {args.calib_data_path}")
|
| 36 |
+
calib_data_path = args.calib_data_path or None
|
| 37 |
+
if calib_data_path:
|
| 38 |
+
os.makedirs(calib_data_path, exist_ok=True)
|
| 39 |
+
|
| 40 |
+
runner = WenetONNXRunner(
|
| 41 |
+
args.config,
|
| 42 |
+
args.vocab,
|
| 43 |
+
onnx_dir=args.onnx_dir,
|
| 44 |
+
offline_seq_len=args.offline_seq_len,
|
| 45 |
+
decoder_len=args.decoder_len,
|
| 46 |
+
)
|
| 47 |
+
result = runner.transcribe(args.input,
|
| 48 |
+
online=args.online,
|
| 49 |
+
mode=args.mode,
|
| 50 |
+
calib_data_path=calib_data_path)
|
| 51 |
+
|
| 52 |
+
print(f"ASR Result: {result}")
|
| 53 |
+
|
| 54 |
+
if __name__ == "__main__":
|
| 55 |
+
run_ort()
|
wenet/LLM/__init__.py
ADDED
|
File without changes
|
wenet/LLM/causallm_model.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, List, Optional, Union
|
| 2 |
+
import torch
|
| 3 |
+
from wenet.LLM.decoder import DecoderOnly
|
| 4 |
+
from wenet.LLM.sampler import sampler
|
| 5 |
+
from wenet.utils.common import IGNORE_ID, th_accuracy
|
| 6 |
+
from wenet.utils.mask import make_pad_mask, subsequent_mask
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CausalLM(torch.nn.Module):
|
| 10 |
+
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
vocab_size: int,
|
| 14 |
+
decoder: DecoderOnly,
|
| 15 |
+
special_tokens: dict,
|
| 16 |
+
tie_word_embedding: bool = False,
|
| 17 |
+
linear_bias: bool = False,
|
| 18 |
+
ignore_id: int = IGNORE_ID,
|
| 19 |
+
lsm_weight: float = 0.0,
|
| 20 |
+
reduction: str = 'mean',
|
| 21 |
+
) -> None:
|
| 22 |
+
super().__init__()
|
| 23 |
+
del special_tokens
|
| 24 |
+
|
| 25 |
+
self.embed = torch.nn.Embedding(vocab_size, decoder.hidden_size)
|
| 26 |
+
self.out = torch.nn.Linear(decoder.hidden_size,
|
| 27 |
+
vocab_size,
|
| 28 |
+
bias=linear_bias)
|
| 29 |
+
|
| 30 |
+
self.decoder = decoder
|
| 31 |
+
self.vocab_size = vocab_size
|
| 32 |
+
self.criterion_att = torch.nn.CrossEntropyLoss(
|
| 33 |
+
ignore_index=ignore_id,
|
| 34 |
+
label_smoothing=lsm_weight,
|
| 35 |
+
reduction=reduction,
|
| 36 |
+
)
|
| 37 |
+
self.tie_word_embedding = tie_word_embedding
|
| 38 |
+
self.ignore_id = ignore_id
|
| 39 |
+
|
| 40 |
+
@torch.jit.unused
|
| 41 |
+
def forward(
|
| 42 |
+
self,
|
| 43 |
+
batch: dict,
|
| 44 |
+
device: torch.device,
|
| 45 |
+
) -> Dict[str, Optional[torch.Tensor]]:
|
| 46 |
+
""" Forward for training
|
| 47 |
+
"""
|
| 48 |
+
text = batch['feats'].to(device)
|
| 49 |
+
target = batch['target'].to(device)
|
| 50 |
+
text_length = batch['feats_lengths'].to(device)
|
| 51 |
+
|
| 52 |
+
mask = ~make_pad_mask(text_length, max_len=text.size(1)).unsqueeze(
|
| 53 |
+
1) # (B,1,L)
|
| 54 |
+
causal_mask = subsequent_mask(
|
| 55 |
+
mask.size(-1), device=mask.device).unsqueeze(0) # (1,L,L)
|
| 56 |
+
att_mask = causal_mask & mask # (B, L, L)
|
| 57 |
+
|
| 58 |
+
embeding = self.embed(text)
|
| 59 |
+
decoder_out = self.out(self.decoder(embeding,
|
| 60 |
+
att_mask)[0]) # (B, L, vocab_size)
|
| 61 |
+
loss = self.criterion_att(decoder_out.view(-1, self.vocab_size),
|
| 62 |
+
target.view(-1))
|
| 63 |
+
acc = th_accuracy(decoder_out.view(-1, self.vocab_size),
|
| 64 |
+
target,
|
| 65 |
+
ignore_label=self.ignore_id)
|
| 66 |
+
|
| 67 |
+
return {
|
| 68 |
+
"loss": loss,
|
| 69 |
+
"ppl": torch.exp(loss.detach()),
|
| 70 |
+
"th_accuracy": acc
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
def tie_or_clone_weights(self, jit_mode: bool):
|
| 74 |
+
if not self.tie_word_embedding:
|
| 75 |
+
return
|
| 76 |
+
if jit_mode:
|
| 77 |
+
self.out.weight = torch.nn.Parameter(self.embed.weight.clone())
|
| 78 |
+
else:
|
| 79 |
+
self.out.weight = self.embed.weight
|
| 80 |
+
# TODO(Mddct): whether to deal bias for other llm model
|
| 81 |
+
|
| 82 |
+
@torch.jit.unused
|
| 83 |
+
@torch.inference_mode()
|
| 84 |
+
def generate(
|
| 85 |
+
self,
|
| 86 |
+
prompts_tokens: List[List[int]],
|
| 87 |
+
device: torch.device,
|
| 88 |
+
stop_tokens: List[int],
|
| 89 |
+
dtype: torch.dtype = torch.float32,
|
| 90 |
+
output_len: int = 100,
|
| 91 |
+
temperature: Union[float, None] = 0.95,
|
| 92 |
+
top_p: float = 1.0,
|
| 93 |
+
top_k: int = 100,
|
| 94 |
+
) -> List[List[int]]:
|
| 95 |
+
"""Generates responses for given prompts using Gemma model."""
|
| 96 |
+
# If a single prompt is provided, treat it as a batch of 1.
|
| 97 |
+
batch_size = len(prompts_tokens)
|
| 98 |
+
min_prompt_len = min(len(p) for p in prompts_tokens)
|
| 99 |
+
max_prompt_len = max(len(p) for p in prompts_tokens)
|
| 100 |
+
max_seq_len = max_prompt_len + output_len
|
| 101 |
+
assert max_seq_len <= self.decoder.pos_enc.max_len
|
| 102 |
+
|
| 103 |
+
# build KV caches
|
| 104 |
+
kv_caches = []
|
| 105 |
+
for _ in range(len(self.decoder.decoders)):
|
| 106 |
+
size = (batch_size, 0, self.decoder.n_kv_head,
|
| 107 |
+
self.decoder.head_dim)
|
| 108 |
+
k_cache = torch.zeros(size=size, dtype=dtype, device=device)
|
| 109 |
+
v_cache = torch.zeros(size=size, dtype=dtype, device=device)
|
| 110 |
+
kv_caches.append((k_cache, v_cache))
|
| 111 |
+
|
| 112 |
+
# prepare inputs
|
| 113 |
+
token_ids_tensor = torch.full((batch_size, max_seq_len),
|
| 114 |
+
IGNORE_ID,
|
| 115 |
+
dtype=torch.int64,
|
| 116 |
+
device=device)
|
| 117 |
+
input_token_ids_tensor = torch.full((batch_size, min_prompt_len),
|
| 118 |
+
IGNORE_ID,
|
| 119 |
+
dtype=torch.int64,
|
| 120 |
+
device=device)
|
| 121 |
+
# right padding
|
| 122 |
+
for i, p in enumerate(prompts_tokens):
|
| 123 |
+
token_ids_tensor[i, :len(p)] = torch.tensor(p)
|
| 124 |
+
input_token_ids_tensor[i, :min_prompt_len] = torch.tensor(
|
| 125 |
+
p[:min_prompt_len])
|
| 126 |
+
|
| 127 |
+
prompt_mask_tensor = token_ids_tensor != IGNORE_ID
|
| 128 |
+
input_positions_tensor = torch.arange(0,
|
| 129 |
+
min_prompt_len,
|
| 130 |
+
dtype=torch.int64).to(device)
|
| 131 |
+
mask_tensor = torch.ones((1, 1, max_seq_len, max_seq_len),
|
| 132 |
+
dtype=torch.bool)
|
| 133 |
+
mask_tensor = torch.tril(mask_tensor).to(device)
|
| 134 |
+
curr_mask_tensor = mask_tensor.index_select(2, input_positions_tensor)
|
| 135 |
+
att_mask = curr_mask_tensor.squeeze(
|
| 136 |
+
1)[:, :min_prompt_len, :min_prompt_len]
|
| 137 |
+
output_positions_tensor = torch.LongTensor([min_prompt_len - 1
|
| 138 |
+
]).to(device)
|
| 139 |
+
temperatures_tensor = None if not temperature else torch.FloatTensor(
|
| 140 |
+
[temperature] * batch_size).to(device)
|
| 141 |
+
top_ps_tensor = torch.FloatTensor([top_p] * batch_size).to(device)
|
| 142 |
+
top_ks_tensor = torch.LongTensor([top_k] * batch_size).to(device)
|
| 143 |
+
output_index = torch.tensor(min_prompt_len,
|
| 144 |
+
dtype=torch.int64).to(device)
|
| 145 |
+
|
| 146 |
+
input_token_embeding = self.embed(input_token_ids_tensor)
|
| 147 |
+
offset = torch.tensor([0] * len(prompts_tokens)).to(device)
|
| 148 |
+
input_offset = offset
|
| 149 |
+
|
| 150 |
+
stop_tokens_tensor = torch.tensor(stop_tokens, device=device)
|
| 151 |
+
# Prefill up to min_prompt_len tokens, then treat other prefill as
|
| 152 |
+
# decode and ignore output.
|
| 153 |
+
for i in range(max_seq_len - min_prompt_len):
|
| 154 |
+
decoder_out, kv_caches, = self.decoder(
|
| 155 |
+
input_token_embeding,
|
| 156 |
+
att_mask,
|
| 157 |
+
input_offset,
|
| 158 |
+
kv_caches,
|
| 159 |
+
)
|
| 160 |
+
decoder_out = self.out(decoder_out)
|
| 161 |
+
decoder_out = decoder_out.index_select(1, output_positions_tensor)
|
| 162 |
+
next_token_ids = sampler(
|
| 163 |
+
decoder_out,
|
| 164 |
+
temperatures_tensor,
|
| 165 |
+
top_ps_tensor,
|
| 166 |
+
top_ks_tensor,
|
| 167 |
+
)
|
| 168 |
+
curr_prompt_mask = prompt_mask_tensor.index_select(
|
| 169 |
+
1, output_index).squeeze(dim=1)
|
| 170 |
+
curr_token_ids = token_ids_tensor.index_select(
|
| 171 |
+
1, output_index).squeeze(dim=1)
|
| 172 |
+
output_token_ids = torch.where(curr_prompt_mask, curr_token_ids,
|
| 173 |
+
next_token_ids).unsqueeze(dim=1)
|
| 174 |
+
token_ids_tensor.index_copy_(1, output_index, output_token_ids)
|
| 175 |
+
|
| 176 |
+
input_token_ids_tensor = output_token_ids
|
| 177 |
+
input_token_embeding = self.embed(input_token_ids_tensor)
|
| 178 |
+
|
| 179 |
+
input_positions_tensor = output_index.unsqueeze(dim=-1)
|
| 180 |
+
curr_mask_tensor = mask_tensor.index_select(
|
| 181 |
+
2, input_positions_tensor)
|
| 182 |
+
att_mask = curr_mask_tensor.squeeze(1)[:, :output_index +
|
| 183 |
+
1, :output_index + 1]
|
| 184 |
+
|
| 185 |
+
output_positions_tensor = torch.tensor(
|
| 186 |
+
0, dtype=torch.int64).to(device)
|
| 187 |
+
input_offset = offset + output_index.unsqueeze(-1)
|
| 188 |
+
output_index = output_index + 1
|
| 189 |
+
|
| 190 |
+
if all(torch.isin(next_token_ids, stop_tokens_tensor)):
|
| 191 |
+
break
|
| 192 |
+
|
| 193 |
+
token_ids = token_ids_tensor.tolist()
|
| 194 |
+
results = []
|
| 195 |
+
for i, tokens in enumerate(token_ids):
|
| 196 |
+
trimmed_output = tokens[len(prompts_tokens[i]
|
| 197 |
+
):len(prompts_tokens[i]) + output_len]
|
| 198 |
+
for stop_token in stop_tokens:
|
| 199 |
+
try:
|
| 200 |
+
eos_index = trimmed_output.index(stop_token)
|
| 201 |
+
trimmed_output = trimmed_output[:eos_index]
|
| 202 |
+
break
|
| 203 |
+
except Exception:
|
| 204 |
+
continue
|
| 205 |
+
results.append(trimmed_output)
|
| 206 |
+
|
| 207 |
+
return results
|
wenet/LLM/decoder.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import partial
|
| 2 |
+
from typing import List, Optional, Tuple, Union
|
| 3 |
+
import torch
|
| 4 |
+
import torch.utils.checkpoint as ckpt
|
| 5 |
+
from wenet.transformer.attention import T_CACHE
|
| 6 |
+
|
| 7 |
+
from wenet.transformer.encoder_layer import TransformerEncoderLayer
|
| 8 |
+
from wenet.utils.class_utils import (WENET_ACTIVATION_CLASSES,
|
| 9 |
+
WENET_ATTENTION_CLASSES,
|
| 10 |
+
WENET_EMB_CLASSES, WENET_MLP_CLASSES,
|
| 11 |
+
WENET_NORM_CLASSES)
|
| 12 |
+
from wenet.utils.common import mask_to_bias
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class DecoderOnly(torch.nn.Module):
|
| 16 |
+
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
n_kv_head: int,
|
| 20 |
+
head_dim: int,
|
| 21 |
+
hidden_size: int,
|
| 22 |
+
attention_heads: int = 4,
|
| 23 |
+
linear_units: int = 2048,
|
| 24 |
+
num_blocks: int = 6,
|
| 25 |
+
dropout_rate: float = 0.1,
|
| 26 |
+
positional_dropout_rate: float = 0.1,
|
| 27 |
+
attention_dropout_rate: float = 0.0,
|
| 28 |
+
normalize_before: bool = True,
|
| 29 |
+
query_bias: bool = False,
|
| 30 |
+
key_bias: bool = False,
|
| 31 |
+
value_bias: bool = False,
|
| 32 |
+
mlp_bias: bool = False,
|
| 33 |
+
activation_type: str = "gelu",
|
| 34 |
+
gelu_approximate: Union[str, None] = None,
|
| 35 |
+
max_position_embeding: int = 8192,
|
| 36 |
+
mlp_type: str = 'gated',
|
| 37 |
+
layer_norm_type: str = 'rms_norm',
|
| 38 |
+
norm_eps: float = 1e-5,
|
| 39 |
+
rms_norm_offset: bool = True,
|
| 40 |
+
selfattention_layer_type: str = "rope_abs_selfattn",
|
| 41 |
+
use_sdpa: bool = False,
|
| 42 |
+
gradient_checkpointing: bool = False,
|
| 43 |
+
rope_theta: float = 10000.0,
|
| 44 |
+
rope_style: str = 'google',
|
| 45 |
+
scale_embed: bool = True,
|
| 46 |
+
) -> None:
|
| 47 |
+
super().__init__()
|
| 48 |
+
|
| 49 |
+
assert selfattention_layer_type in ['rope_abs_selfattn']
|
| 50 |
+
self.pos_enc = WENET_EMB_CLASSES["rope_pos"](
|
| 51 |
+
hidden_size,
|
| 52 |
+
head_dim,
|
| 53 |
+
max_len=max_position_embeding,
|
| 54 |
+
dropout_rate=positional_dropout_rate,
|
| 55 |
+
rope_theta=rope_theta,
|
| 56 |
+
scale=scale_embed)
|
| 57 |
+
if activation_type == "gelu" and gelu_approximate is not None:
|
| 58 |
+
activation = WENET_ACTIVATION_CLASSES['gelu'](
|
| 59 |
+
approximate=gelu_approximate)
|
| 60 |
+
else:
|
| 61 |
+
activation = WENET_ACTIVATION_CLASSES[activation_type]()
|
| 62 |
+
|
| 63 |
+
mlp_class = WENET_MLP_CLASSES[mlp_type]
|
| 64 |
+
self.num_blocks = num_blocks
|
| 65 |
+
# TODO: support lora & refactor lora
|
| 66 |
+
self.decoders = torch.nn.ModuleList([
|
| 67 |
+
TransformerEncoderLayer(
|
| 68 |
+
hidden_size,
|
| 69 |
+
WENET_ATTENTION_CLASSES[selfattention_layer_type](
|
| 70 |
+
attention_heads,
|
| 71 |
+
hidden_size,
|
| 72 |
+
attention_dropout_rate,
|
| 73 |
+
query_bias,
|
| 74 |
+
key_bias,
|
| 75 |
+
value_bias,
|
| 76 |
+
use_sdpa,
|
| 77 |
+
n_kv_head,
|
| 78 |
+
head_dim,
|
| 79 |
+
style=rope_style),
|
| 80 |
+
mlp_class(hidden_size, linear_units, dropout_rate, activation,
|
| 81 |
+
mlp_bias),
|
| 82 |
+
dropout_rate,
|
| 83 |
+
normalize_before,
|
| 84 |
+
layer_norm_type=layer_norm_type,
|
| 85 |
+
norm_eps=norm_eps,
|
| 86 |
+
rms_norm_offset=rms_norm_offset,
|
| 87 |
+
) for _ in range(self.num_blocks)
|
| 88 |
+
])
|
| 89 |
+
self.pre_norm = normalize_before
|
| 90 |
+
self.final_norm: Optional[torch.nn.Module] = None
|
| 91 |
+
if self.pre_norm:
|
| 92 |
+
norm_class = WENET_NORM_CLASSES[layer_norm_type]
|
| 93 |
+
if layer_norm_type == "rms_norm":
|
| 94 |
+
norm_class = partial(
|
| 95 |
+
norm_class,
|
| 96 |
+
add_unit_offset=rms_norm_offset,
|
| 97 |
+
)
|
| 98 |
+
self.final_norm = norm_class(hidden_size, eps=norm_eps)
|
| 99 |
+
|
| 100 |
+
self.n_kv_head = n_kv_head
|
| 101 |
+
self.head_dim = head_dim
|
| 102 |
+
self._hidden_size = hidden_size
|
| 103 |
+
self.use_sdpa = use_sdpa
|
| 104 |
+
self.gradient_checkpointing = gradient_checkpointing
|
| 105 |
+
|
| 106 |
+
def forward(
|
| 107 |
+
self,
|
| 108 |
+
input: torch.Tensor,
|
| 109 |
+
att_mask: torch.Tensor,
|
| 110 |
+
input_position: Union[int, torch.Tensor] = 0,
|
| 111 |
+
kv_caches: Optional[List[T_CACHE]] = None,
|
| 112 |
+
) -> Tuple[torch.Tensor, Union[List[T_CACHE], None]]:
|
| 113 |
+
xs, pos_emb = self.pos_enc(input, offset=input_position)
|
| 114 |
+
if self.use_sdpa:
|
| 115 |
+
att_mask = mask_to_bias(att_mask, xs.dtype)
|
| 116 |
+
|
| 117 |
+
if self.gradient_checkpointing and self.training:
|
| 118 |
+
xs = self.forward_layers_checkpointed(xs, att_mask, pos_emb)
|
| 119 |
+
else:
|
| 120 |
+
xs, kv_caches = self.forward_layers(xs, att_mask, pos_emb,
|
| 121 |
+
kv_caches)
|
| 122 |
+
if self.pre_norm and self.final_norm is not None:
|
| 123 |
+
xs = self.final_norm(xs)
|
| 124 |
+
return xs, kv_caches
|
| 125 |
+
|
| 126 |
+
def forward_layers(
|
| 127 |
+
self,
|
| 128 |
+
xs: torch.Tensor,
|
| 129 |
+
att_mask: torch.Tensor,
|
| 130 |
+
pos_emb: torch.Tensor,
|
| 131 |
+
kv_caches: Optional[List[T_CACHE]] = None,
|
| 132 |
+
) -> Tuple[torch.Tensor, Union[List[T_CACHE], None]]:
|
| 133 |
+
if self.training:
|
| 134 |
+
for (i, layer) in enumerate(self.decoders):
|
| 135 |
+
xs, _, _, _ = layer(xs, att_mask, pos_emb)
|
| 136 |
+
new_kv_caches = kv_caches
|
| 137 |
+
else:
|
| 138 |
+
assert kv_caches is not None
|
| 139 |
+
new_kv_caches = []
|
| 140 |
+
for (i, layer) in enumerate(self.decoders):
|
| 141 |
+
xs, _, new_kv_cache, _ = layer(xs,
|
| 142 |
+
att_mask,
|
| 143 |
+
pos_emb,
|
| 144 |
+
att_cache=(kv_caches[i][0],
|
| 145 |
+
kv_caches[i][1]))
|
| 146 |
+
new_kv_caches.append(new_kv_cache)
|
| 147 |
+
|
| 148 |
+
return xs, new_kv_caches
|
| 149 |
+
|
| 150 |
+
@torch.jit.ignore(drop=True)
|
| 151 |
+
def forward_layers_checkpointed(self, xs: torch.Tensor,
|
| 152 |
+
att_mask: torch.Tensor,
|
| 153 |
+
pos_emb: torch.Tensor) -> torch.Tensor:
|
| 154 |
+
for layer in self.decoders:
|
| 155 |
+
xs, _, _, _ = ckpt.checkpoint(layer.__call__, xs, att_mask,
|
| 156 |
+
pos_emb)
|
| 157 |
+
return xs
|
| 158 |
+
|
| 159 |
+
@property
|
| 160 |
+
def hidden_size(self):
|
| 161 |
+
return self._hidden_size
|
wenet/LLM/sampler.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Union
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# modified from https://github.com/google/gemma_pytorch/blob/main/gemma/model.py#L26
|
| 6 |
+
@torch.no_grad()
|
| 7 |
+
def sampler(
|
| 8 |
+
logits: torch.Tensor,
|
| 9 |
+
temperatures: Union[torch.Tensor, None],
|
| 10 |
+
top_ps: torch.Tensor,
|
| 11 |
+
top_ks: torch.Tensor,
|
| 12 |
+
) -> torch.Tensor:
|
| 13 |
+
assert logits.size(1) == 1
|
| 14 |
+
logits = logits.squeeze(1) # (batch_size, vocab_size)
|
| 15 |
+
if temperatures is None:
|
| 16 |
+
return torch.argmax(logits, dim=-1).squeeze(dim=-1)
|
| 17 |
+
|
| 18 |
+
# Apply temperature scaling.
|
| 19 |
+
logits.div_(temperatures.unsqueeze(dim=1))
|
| 20 |
+
|
| 21 |
+
# Calculate probabilities with softmax.
|
| 22 |
+
probs = torch.softmax(logits, dim=-1, dtype=torch.float)
|
| 23 |
+
probs_sort, probs_idx = torch.sort(probs, dim=-1, descending=True)
|
| 24 |
+
|
| 25 |
+
# Apply top-p, top-k.
|
| 26 |
+
probs_sum = torch.cumsum(probs_sort, dim=-1)
|
| 27 |
+
top_ps_mask = (probs_sum - probs_sort) > top_ps.unsqueeze(dim=1)
|
| 28 |
+
probs_sort = torch.where(top_ps_mask, 0, probs_sort)
|
| 29 |
+
|
| 30 |
+
top_ks_mask = torch.arange(probs_idx.shape[-1], device=probs_idx.device)
|
| 31 |
+
top_ks_mask = top_ks_mask.expand(probs_idx.shape[0], -1)
|
| 32 |
+
top_ks_mask = top_ks_mask >= top_ks.unsqueeze(dim=1)
|
| 33 |
+
probs_sort = torch.where(top_ks_mask, 0, probs_sort)
|
| 34 |
+
|
| 35 |
+
# Re-normalization.
|
| 36 |
+
probs_sort.div_(probs_sort.sum(dim=-1, keepdim=True))
|
| 37 |
+
probs = torch.gather(probs_sort,
|
| 38 |
+
dim=-1,
|
| 39 |
+
index=torch.argsort(probs_idx, dim=-1))
|
| 40 |
+
|
| 41 |
+
next_token_ids = torch.multinomial(probs, num_samples=1,
|
| 42 |
+
replacement=True).squeeze(dim=-1)
|
| 43 |
+
return next_token_ids
|
wenet/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Module Introduction
|
| 2 |
+
|
| 3 |
+
Here is a brief introduction of each module(directory).
|
| 4 |
+
|
| 5 |
+
* `bin`: training and recognition binaries
|
| 6 |
+
* `dataset`: IO design
|
| 7 |
+
* `utils`: common utils
|
| 8 |
+
* `transformer`: the core of `WeNet`, in which the standard transformer/conformer is implemented. It contains the common blocks(backbone) of speech transformers.
|
| 9 |
+
* transformer/attention.py: Standard multi head attention
|
| 10 |
+
* transformer/embedding.py: Standard position encoding
|
| 11 |
+
* transformer/positionwise_feed_forward.py: Standard feed forward in transformer
|
| 12 |
+
* transformer/convolution.py: ConvolutionModule in Conformer model
|
| 13 |
+
* transformer/subsampling.py: Subsampling implementation for speech task
|
| 14 |
+
* `transducer`: transducer implementation
|
| 15 |
+
* `squeezeformer`: squeezeformer implementation, please refer [paper](https://arxiv.org/pdf/2206.00888.pdf)
|
| 16 |
+
* `efficient_conformer`: efficient conformer implementation, please refer [paper](https://arxiv.org/pdf/2109.01163.pdf)
|
| 17 |
+
* `paraformer`: paraformer implementation, please refer [paper](https://arxiv.org/pdf/1905.11235.pdf)
|
| 18 |
+
* `paraformer/cif.py`: Continuous Integrate-and-Fire implemented, please refer [paper](https://arxiv.org/pdf/1905.11235.pdf)
|
| 19 |
+
* `branchformer`: branchformer implementation, please refer [paper](https://arxiv.org/abs/2207.02971)
|
| 20 |
+
* `whisper`: whisper implementation, please refer [paper](https://arxiv.org/abs/2212.04356)
|
| 21 |
+
* `ssl`: Self-supervised speech model implementation. e.g. wav2vec2, bestrq, w2vbert.
|
| 22 |
+
* `ctl_model`: Enhancing the Unified Streaming and Non-streaming Model with with Contrastive Learning implementation [paper](https://arxiv.org/abs/2306.00755)
|
| 23 |
+
|
| 24 |
+
`transducer`, `squeezeformer`, `efficient_conformer`, `branchformer` and `cif` are all based on `transformer`,
|
| 25 |
+
they resue a lot of the common blocks of `tranformer`.
|
| 26 |
+
|
| 27 |
+
**If you want to contribute your own x-former, please reuse the current code as much as possible**.
|
| 28 |
+
|
| 29 |
+
|
wenet/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from wenet.cli.model import load_model, load_model_pt # noqa
|
wenet/bin/__init__.py
ADDED
|
File without changes
|
wenet/bin/alignment.py
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 Mobvoi Inc. (authors: Di Wu)
|
| 2 |
+
# 2022 Tinnove Inc (authors: Wei Ren)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from __future__ import print_function
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import copy
|
| 20 |
+
import logging
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import yaml
|
| 26 |
+
from torch.utils.data import DataLoader
|
| 27 |
+
from textgrid import TextGrid, IntervalTier
|
| 28 |
+
import math
|
| 29 |
+
|
| 30 |
+
from wenet.dataset.dataset import Dataset
|
| 31 |
+
from wenet.utils.ctc_utils import force_align
|
| 32 |
+
from wenet.utils.common import get_subsample
|
| 33 |
+
from wenet.utils.init_model import init_model
|
| 34 |
+
from wenet.utils.init_tokenizer import init_tokenizer
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def generator_textgrid(maxtime, lines, output):
|
| 38 |
+
# Download Praat: https://www.fon.hum.uva.nl/praat/
|
| 39 |
+
interval = maxtime / (len(lines) + 1)
|
| 40 |
+
margin = 0.0001
|
| 41 |
+
|
| 42 |
+
tg = TextGrid(maxTime=maxtime)
|
| 43 |
+
linetier = IntervalTier(name="line", maxTime=maxtime)
|
| 44 |
+
|
| 45 |
+
i = 0
|
| 46 |
+
for l in lines:
|
| 47 |
+
s, e, w = l.split()
|
| 48 |
+
linetier.add(minTime=float(s) + margin, maxTime=float(e), mark=w)
|
| 49 |
+
|
| 50 |
+
tg.append(linetier)
|
| 51 |
+
print("successfully generator {}".format(output))
|
| 52 |
+
tg.write(output)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def get_frames_timestamp(alignment,
|
| 56 |
+
prob,
|
| 57 |
+
blank_thres=0.999,
|
| 58 |
+
thres=0.0000000001):
|
| 59 |
+
# convert alignment to a praat format, which is a doing phonetics
|
| 60 |
+
# by computer and helps analyzing alignment
|
| 61 |
+
timestamp = []
|
| 62 |
+
# get frames level duration for each token
|
| 63 |
+
start = 0
|
| 64 |
+
end = 0
|
| 65 |
+
local_start = 0
|
| 66 |
+
while end < len(alignment):
|
| 67 |
+
while end < len(alignment) and alignment[end] == 0:
|
| 68 |
+
end += 1
|
| 69 |
+
if end == len(alignment):
|
| 70 |
+
timestamp[-1] += alignment[start:]
|
| 71 |
+
break
|
| 72 |
+
end += 1
|
| 73 |
+
while end < len(alignment) and alignment[end - 1] == alignment[end]:
|
| 74 |
+
end += 1
|
| 75 |
+
local_start = end - 1
|
| 76 |
+
# find the possible front border for current token
|
| 77 |
+
while local_start >= start and (
|
| 78 |
+
prob[local_start][0] < math.log(blank_thres)
|
| 79 |
+
or prob[local_start][alignment[end - 1]] > math.log(thres)):
|
| 80 |
+
alignment[local_start] = alignment[end - 1]
|
| 81 |
+
local_start -= 1
|
| 82 |
+
cur_alignment = alignment[start:end]
|
| 83 |
+
timestamp.append(cur_alignment)
|
| 84 |
+
start = end
|
| 85 |
+
return timestamp
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def get_labformat(timestamp, subsample):
|
| 89 |
+
begin = 0
|
| 90 |
+
begin_time = 0
|
| 91 |
+
duration = 0
|
| 92 |
+
labformat = []
|
| 93 |
+
for idx, t in enumerate(timestamp):
|
| 94 |
+
# 25ms frame_length,10ms hop_length, 1/subsample
|
| 95 |
+
subsample = get_subsample(configs)
|
| 96 |
+
# time duration
|
| 97 |
+
i = 0
|
| 98 |
+
while t[i] == 0:
|
| 99 |
+
i += 1
|
| 100 |
+
begin = i
|
| 101 |
+
dur = 0
|
| 102 |
+
while i < len(t) and t[i] != 0:
|
| 103 |
+
i += 1
|
| 104 |
+
dur += 1
|
| 105 |
+
begin = begin_time + begin * 0.01 * subsample
|
| 106 |
+
duration = dur * 0.01 * subsample
|
| 107 |
+
if idx < len(timestamp) - 1:
|
| 108 |
+
print("{:.2f} {:.2f} {}".format(begin, begin + duration,
|
| 109 |
+
char_dict[t[-1]]))
|
| 110 |
+
labformat.append("{:.2f} {:.2f} {}\n".format(
|
| 111 |
+
begin, begin + duration, char_dict[t[-1]]))
|
| 112 |
+
else: # last token
|
| 113 |
+
non_blank = 0
|
| 114 |
+
for i in t:
|
| 115 |
+
if i != 0:
|
| 116 |
+
token = i
|
| 117 |
+
break
|
| 118 |
+
print("{:.2f} {:.2f} {}".format(begin, begin + duration,
|
| 119 |
+
char_dict[token]))
|
| 120 |
+
labformat.append("{:.2f} {:.2f} {}\n".format(
|
| 121 |
+
begin, begin + duration, char_dict[token]))
|
| 122 |
+
begin_time += len(t) * 0.01 * subsample
|
| 123 |
+
return labformat
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == '__main__':
|
| 127 |
+
parser = argparse.ArgumentParser(
|
| 128 |
+
description='use ctc to generate alignment')
|
| 129 |
+
parser.add_argument('--config', required=True, help='config file')
|
| 130 |
+
parser.add_argument('--input_file', required=True, help='format data file')
|
| 131 |
+
parser.add_argument('--data_type',
|
| 132 |
+
default='raw',
|
| 133 |
+
choices=['raw', 'shard'],
|
| 134 |
+
help='train and cv data type')
|
| 135 |
+
parser.add_argument('--gpu',
|
| 136 |
+
type=int,
|
| 137 |
+
default=-1,
|
| 138 |
+
help='gpu id for this rank, -1 for cpu')
|
| 139 |
+
parser.add_argument('--device',
|
| 140 |
+
type=str,
|
| 141 |
+
default="cpu",
|
| 142 |
+
choices=["cpu", "npu", "cuda"],
|
| 143 |
+
help='accelerator to use')
|
| 144 |
+
parser.add_argument('--blank_thres',
|
| 145 |
+
default=0.999999,
|
| 146 |
+
type=float,
|
| 147 |
+
help='ctc blank thes')
|
| 148 |
+
parser.add_argument('--thres',
|
| 149 |
+
default=0.000001,
|
| 150 |
+
type=float,
|
| 151 |
+
help='ctc non blank thes')
|
| 152 |
+
parser.add_argument('--checkpoint', required=True, help='checkpoint model')
|
| 153 |
+
parser.add_argument('--dict', required=True, help='dict file')
|
| 154 |
+
parser.add_argument(
|
| 155 |
+
'--non_lang_syms',
|
| 156 |
+
help="non-linguistic symbol file. One symbol per line.")
|
| 157 |
+
parser.add_argument('--result_file',
|
| 158 |
+
required=True,
|
| 159 |
+
help='alignment result file')
|
| 160 |
+
parser.add_argument('--batch_size', type=int, default=1, help='batch size')
|
| 161 |
+
parser.add_argument('--gen_praat',
|
| 162 |
+
action='store_true',
|
| 163 |
+
help='convert alignment to a praat format')
|
| 164 |
+
parser.add_argument('--bpe_model',
|
| 165 |
+
default=None,
|
| 166 |
+
type=str,
|
| 167 |
+
help='bpe model for english part')
|
| 168 |
+
|
| 169 |
+
args = parser.parse_args()
|
| 170 |
+
print(args)
|
| 171 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 172 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 173 |
+
if args.gpu != -1:
|
| 174 |
+
# remain the original usage of gpu
|
| 175 |
+
args.device = "cuda"
|
| 176 |
+
if "cuda" in args.device:
|
| 177 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
|
| 178 |
+
|
| 179 |
+
if args.batch_size > 1:
|
| 180 |
+
logging.fatal('alignment mode must be running with batch_size == 1')
|
| 181 |
+
sys.exit(1)
|
| 182 |
+
|
| 183 |
+
with open(args.config, 'r') as fin:
|
| 184 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 185 |
+
|
| 186 |
+
# Load dict
|
| 187 |
+
char_dict = {}
|
| 188 |
+
with open(args.dict, 'r') as fin:
|
| 189 |
+
for line in fin:
|
| 190 |
+
arr = line.strip().split()
|
| 191 |
+
assert len(arr) == 2
|
| 192 |
+
char_dict[int(arr[1])] = arr[0]
|
| 193 |
+
eos = len(char_dict) - 1
|
| 194 |
+
|
| 195 |
+
# Init dataset and data loader
|
| 196 |
+
ali_conf = copy.deepcopy(configs['dataset_conf'])
|
| 197 |
+
|
| 198 |
+
ali_conf['filter_conf']['max_length'] = 102400
|
| 199 |
+
ali_conf['filter_conf']['min_length'] = 0
|
| 200 |
+
ali_conf['filter_conf']['token_max_length'] = 102400
|
| 201 |
+
ali_conf['filter_conf']['token_min_length'] = 0
|
| 202 |
+
ali_conf['filter_conf']['max_output_input_ratio'] = 102400
|
| 203 |
+
ali_conf['filter_conf']['min_output_input_ratio'] = 0
|
| 204 |
+
ali_conf['speed_perturb'] = False
|
| 205 |
+
ali_conf['spec_aug'] = False
|
| 206 |
+
ali_conf['spec_trim'] = False
|
| 207 |
+
ali_conf['shuffle'] = False
|
| 208 |
+
ali_conf['sort'] = False
|
| 209 |
+
ali_conf['fbank_conf']['dither'] = 0.0
|
| 210 |
+
ali_conf['batch_conf']['batch_type'] = "static"
|
| 211 |
+
ali_conf['batch_conf']['batch_size'] = args.batch_size
|
| 212 |
+
|
| 213 |
+
tokenizer = init_tokenizer(configs)
|
| 214 |
+
ali_dataset = Dataset(args.data_type,
|
| 215 |
+
args.input_file,
|
| 216 |
+
tokenizer,
|
| 217 |
+
ali_conf,
|
| 218 |
+
partition=False)
|
| 219 |
+
|
| 220 |
+
ali_data_loader = DataLoader(ali_dataset, batch_size=None, num_workers=0)
|
| 221 |
+
|
| 222 |
+
# Init asr model from configs
|
| 223 |
+
model, configs = init_model(args, configs)
|
| 224 |
+
|
| 225 |
+
device = torch.device(args.device)
|
| 226 |
+
model = model.to(device)
|
| 227 |
+
|
| 228 |
+
model.eval()
|
| 229 |
+
with torch.no_grad(), open(args.result_file, 'w',
|
| 230 |
+
encoding='utf-8') as fout:
|
| 231 |
+
for batch_idx, batch in enumerate(ali_data_loader):
|
| 232 |
+
print("#" * 80)
|
| 233 |
+
key, feat, target, feats_length, target_length = batch
|
| 234 |
+
|
| 235 |
+
feat = feat.to(device)
|
| 236 |
+
target = target.to(device)
|
| 237 |
+
feats_length = feats_length.to(device)
|
| 238 |
+
target_length = target_length.to(device)
|
| 239 |
+
# Let's assume B = batch_size and N = beam_size
|
| 240 |
+
# 1. Encoder
|
| 241 |
+
encoder_out, encoder_mask = model._forward_encoder(
|
| 242 |
+
feat, feats_length) # (B, maxlen, encoder_dim)
|
| 243 |
+
maxlen = encoder_out.size(1)
|
| 244 |
+
ctc_probs = model.ctc.log_softmax(
|
| 245 |
+
encoder_out) # (1, maxlen, vocab_size)
|
| 246 |
+
# print(ctc_probs.size(1))
|
| 247 |
+
ctc_probs = ctc_probs.squeeze(0)
|
| 248 |
+
target = target.squeeze(0)
|
| 249 |
+
alignment = force_align(ctc_probs, target)
|
| 250 |
+
fout.write('{} {}\n'.format(key[0], alignment))
|
| 251 |
+
|
| 252 |
+
if args.gen_praat:
|
| 253 |
+
timestamp = get_frames_timestamp(alignment, ctc_probs,
|
| 254 |
+
args.blank_thres, args.thres)
|
| 255 |
+
subsample = get_subsample(configs)
|
| 256 |
+
labformat = get_labformat(timestamp, subsample)
|
| 257 |
+
|
| 258 |
+
lab_path = os.path.join(os.path.dirname(args.result_file),
|
| 259 |
+
key[0] + ".lab")
|
| 260 |
+
with open(lab_path, 'w', encoding='utf-8') as f:
|
| 261 |
+
f.writelines(labformat)
|
| 262 |
+
|
| 263 |
+
textgrid_path = os.path.join(os.path.dirname(args.result_file),
|
| 264 |
+
key[0] + ".TextGrid")
|
| 265 |
+
generator_textgrid(maxtime=(len(alignment) + 1) * 0.01 *
|
| 266 |
+
subsample,
|
| 267 |
+
lines=labformat,
|
| 268 |
+
output=textgrid_path)
|
wenet/bin/average_model.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 Mobvoi Inc (Di Wu)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import argparse
|
| 17 |
+
import glob
|
| 18 |
+
import sys
|
| 19 |
+
|
| 20 |
+
import yaml
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_args():
|
| 25 |
+
parser = argparse.ArgumentParser(description='average model')
|
| 26 |
+
parser.add_argument('--dst_model', required=True, help='averaged model')
|
| 27 |
+
parser.add_argument('--src_path',
|
| 28 |
+
required=True,
|
| 29 |
+
help='src model path for average')
|
| 30 |
+
parser.add_argument('--val_best',
|
| 31 |
+
action="store_true",
|
| 32 |
+
help='averaged model')
|
| 33 |
+
parser.add_argument('--num',
|
| 34 |
+
default=5,
|
| 35 |
+
type=int,
|
| 36 |
+
help='nums for averaged model')
|
| 37 |
+
parser.add_argument('--min_epoch',
|
| 38 |
+
default=0,
|
| 39 |
+
type=int,
|
| 40 |
+
help='min epoch used for averaging model')
|
| 41 |
+
parser.add_argument('--max_epoch',
|
| 42 |
+
default=sys.maxsize,
|
| 43 |
+
type=int,
|
| 44 |
+
help='max epoch used for averaging model')
|
| 45 |
+
parser.add_argument('--min_step',
|
| 46 |
+
default=0,
|
| 47 |
+
type=int,
|
| 48 |
+
help='min step used for averaging model')
|
| 49 |
+
parser.add_argument('--max_step',
|
| 50 |
+
default=sys.maxsize,
|
| 51 |
+
type=int,
|
| 52 |
+
help='max step used for averaging model')
|
| 53 |
+
parser.add_argument('--mode',
|
| 54 |
+
default="hybrid",
|
| 55 |
+
choices=["hybrid", "epoch", "step"],
|
| 56 |
+
type=str,
|
| 57 |
+
help='average mode')
|
| 58 |
+
|
| 59 |
+
args = parser.parse_args()
|
| 60 |
+
print(args)
|
| 61 |
+
return args
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def main():
|
| 65 |
+
args = get_args()
|
| 66 |
+
checkpoints = []
|
| 67 |
+
val_scores = []
|
| 68 |
+
if args.val_best:
|
| 69 |
+
if args.mode == "hybrid":
|
| 70 |
+
yamls = glob.glob('{}/*.yaml'.format(args.src_path))
|
| 71 |
+
yamls = [
|
| 72 |
+
f for f in yamls
|
| 73 |
+
if not (os.path.basename(f).startswith('train')
|
| 74 |
+
or os.path.basename(f).startswith('init'))
|
| 75 |
+
]
|
| 76 |
+
elif args.mode == "step":
|
| 77 |
+
yamls = glob.glob('{}/step_*.yaml'.format(args.src_path))
|
| 78 |
+
else:
|
| 79 |
+
yamls = glob.glob('{}/epoch_*.yaml'.format(args.src_path))
|
| 80 |
+
for y in yamls:
|
| 81 |
+
with open(y, 'r') as f:
|
| 82 |
+
dic_yaml = yaml.load(f, Loader=yaml.FullLoader)
|
| 83 |
+
loss = dic_yaml['loss_dict']['loss']
|
| 84 |
+
epoch = dic_yaml['epoch']
|
| 85 |
+
step = dic_yaml['step']
|
| 86 |
+
tag = dic_yaml['tag']
|
| 87 |
+
if epoch >= args.min_epoch and epoch <= args.max_epoch \
|
| 88 |
+
and step >= args.min_step and step <= args.max_step:
|
| 89 |
+
val_scores += [[epoch, step, loss, tag]]
|
| 90 |
+
sorted_val_scores = sorted(val_scores,
|
| 91 |
+
key=lambda x: x[2],
|
| 92 |
+
reverse=False)
|
| 93 |
+
print("best val (epoch, step, loss, tag) = " +
|
| 94 |
+
str(sorted_val_scores[:args.num]))
|
| 95 |
+
path_list = [
|
| 96 |
+
args.src_path + '/{}.pt'.format(score[-1])
|
| 97 |
+
for score in sorted_val_scores[:args.num]
|
| 98 |
+
]
|
| 99 |
+
else:
|
| 100 |
+
path_list = glob.glob('{}/[!init]*.pt'.format(args.src_path))
|
| 101 |
+
path_list = sorted(path_list, key=os.path.getmtime)
|
| 102 |
+
path_list = path_list[-args.num:]
|
| 103 |
+
print(path_list)
|
| 104 |
+
avg = {}
|
| 105 |
+
num = args.num
|
| 106 |
+
assert num == len(path_list)
|
| 107 |
+
for path in path_list:
|
| 108 |
+
print('Processing {}'.format(path))
|
| 109 |
+
states = torch.load(path, map_location=torch.device('cpu'))
|
| 110 |
+
for k in states.keys():
|
| 111 |
+
if k not in avg.keys():
|
| 112 |
+
avg[k] = states[k].clone()
|
| 113 |
+
else:
|
| 114 |
+
avg[k] += states[k]
|
| 115 |
+
# average
|
| 116 |
+
for k in avg.keys():
|
| 117 |
+
if avg[k] is not None:
|
| 118 |
+
# pytorch 1.6 use true_divide instead of /=
|
| 119 |
+
avg[k] = torch.true_divide(avg[k], num)
|
| 120 |
+
print('Saving to {}'.format(args.dst_model))
|
| 121 |
+
torch.save(avg, args.dst_model)
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
if __name__ == '__main__':
|
| 125 |
+
main()
|
wenet/bin/export_ipex.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (C) 2021-2023 Intel Corporation
|
| 2 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 3 |
+
|
| 4 |
+
from __future__ import print_function
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import logging
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import yaml
|
| 12 |
+
|
| 13 |
+
from wenet.utils.init_model import init_model
|
| 14 |
+
import intel_extension_for_pytorch as ipex
|
| 15 |
+
from intel_extension_for_pytorch.quantization import prepare, convert
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def get_args():
|
| 19 |
+
parser = argparse.ArgumentParser(description='export your script model')
|
| 20 |
+
parser.add_argument('--config', required=True, help='config file')
|
| 21 |
+
parser.add_argument('--checkpoint', required=True, help='checkpoint model')
|
| 22 |
+
parser.add_argument('--output_file', default=None, help='output file')
|
| 23 |
+
parser.add_argument('--dtype',
|
| 24 |
+
default="fp32",
|
| 25 |
+
help='choose the dtype to run:[fp32,bf16]')
|
| 26 |
+
parser.add_argument('--output_quant_file',
|
| 27 |
+
default=None,
|
| 28 |
+
help='output quantized model file')
|
| 29 |
+
args = parser.parse_args()
|
| 30 |
+
return args
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def scripting(model):
|
| 34 |
+
with torch.inference_mode():
|
| 35 |
+
script_model = torch.jit.script(model)
|
| 36 |
+
script_model = torch.jit.freeze(
|
| 37 |
+
script_model,
|
| 38 |
+
preserved_attrs=[
|
| 39 |
+
"forward_encoder_chunk", "ctc_activation",
|
| 40 |
+
"forward_attention_decoder", "subsampling_rate",
|
| 41 |
+
"right_context", "sos_symbol", "eos_symbol",
|
| 42 |
+
"is_bidirectional_decoder"
|
| 43 |
+
])
|
| 44 |
+
return script_model
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def main():
|
| 48 |
+
args = get_args()
|
| 49 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 50 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 51 |
+
# No need gpu for model export
|
| 52 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
| 53 |
+
|
| 54 |
+
with open(args.config, 'r') as fin:
|
| 55 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 56 |
+
model, configs = init_model(args, configs)
|
| 57 |
+
print(model)
|
| 58 |
+
|
| 59 |
+
# Apply IPEX optimization
|
| 60 |
+
model.eval()
|
| 61 |
+
torch._C._jit_set_texpr_fuser_enabled(False)
|
| 62 |
+
model.to(memory_format=torch.channels_last)
|
| 63 |
+
if args.dtype == "fp32":
|
| 64 |
+
ipex_model = ipex.optimize(model)
|
| 65 |
+
elif args.dtype == "bf16": # For Intel 4th generation Xeon (SPR)
|
| 66 |
+
ipex_model = ipex.optimize(model,
|
| 67 |
+
dtype=torch.bfloat16,
|
| 68 |
+
weights_prepack=False)
|
| 69 |
+
|
| 70 |
+
# Export jit torch script model
|
| 71 |
+
if args.output_file:
|
| 72 |
+
if args.dtype == "fp32":
|
| 73 |
+
script_model = scripting(ipex_model)
|
| 74 |
+
elif args.dtype == "bf16":
|
| 75 |
+
torch._C._jit_set_autocast_mode(True)
|
| 76 |
+
with torch.cpu.amp.autocast():
|
| 77 |
+
script_model = scripting(ipex_model)
|
| 78 |
+
script_model.save(args.output_file)
|
| 79 |
+
print('Export model successfully, see {}'.format(args.output_file))
|
| 80 |
+
|
| 81 |
+
# Export quantized jit torch script model
|
| 82 |
+
if args.output_quant_file:
|
| 83 |
+
dynamic_qconfig = ipex.quantization.default_dynamic_qconfig
|
| 84 |
+
dummy_data = (torch.zeros(1, 67, 80), 16, -16,
|
| 85 |
+
torch.zeros(12, 4, 32, 128), torch.zeros(12, 1, 256, 7))
|
| 86 |
+
model = prepare(model, dynamic_qconfig, dummy_data)
|
| 87 |
+
model = convert(model)
|
| 88 |
+
script_quant_model = scripting(model)
|
| 89 |
+
script_quant_model.save(args.output_quant_file)
|
| 90 |
+
print('Export quantized model successfully, '
|
| 91 |
+
'see {}'.format(args.output_quant_file))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
if __name__ == '__main__':
|
| 95 |
+
main()
|
wenet/bin/export_jit.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 Mobvoi Inc. (authors: Binbin Zhang, Di Wu)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import logging
|
| 19 |
+
import os
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
import yaml
|
| 23 |
+
|
| 24 |
+
from wenet.utils.init_model import init_model
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def get_args():
|
| 28 |
+
parser = argparse.ArgumentParser(description='export your script model')
|
| 29 |
+
parser.add_argument('--config', required=True, help='config file')
|
| 30 |
+
parser.add_argument('--checkpoint', required=True, help='checkpoint model')
|
| 31 |
+
parser.add_argument('--output_file', default=None, help='output file')
|
| 32 |
+
parser.add_argument('--output_quant_file',
|
| 33 |
+
default=None,
|
| 34 |
+
help='output quantized model file')
|
| 35 |
+
args = parser.parse_args()
|
| 36 |
+
return args
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def main():
|
| 40 |
+
args = get_args()
|
| 41 |
+
args.jit = True
|
| 42 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 43 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 44 |
+
# No need gpu for model export
|
| 45 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
| 46 |
+
|
| 47 |
+
with open(args.config, 'r') as fin:
|
| 48 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 49 |
+
model, configs = init_model(args, configs)
|
| 50 |
+
model.eval()
|
| 51 |
+
print(model)
|
| 52 |
+
# Export jit torch script model
|
| 53 |
+
|
| 54 |
+
if args.output_file:
|
| 55 |
+
script_model = torch.jit.script(model)
|
| 56 |
+
script_model.save(args.output_file)
|
| 57 |
+
print('Export model successfully, see {}'.format(args.output_file))
|
| 58 |
+
|
| 59 |
+
# Export quantized jit torch script model
|
| 60 |
+
if args.output_quant_file:
|
| 61 |
+
quantized_model = torch.quantization.quantize_dynamic(
|
| 62 |
+
model, {torch.nn.Linear}, dtype=torch.qint8)
|
| 63 |
+
print(quantized_model)
|
| 64 |
+
script_quant_model = torch.jit.script(quantized_model)
|
| 65 |
+
script_quant_model.save(args.output_quant_file)
|
| 66 |
+
print('Export quantized model successfully, '
|
| 67 |
+
'see {}'.format(args.output_quant_file))
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
if __name__ == '__main__':
|
| 71 |
+
main()
|
wenet/bin/export_onnx_bpu.py
ADDED
|
@@ -0,0 +1,1065 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022, Horizon Inc. Xingchen Song (sxc19@tsinghua.org.cn)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""NOTE(xcsong): Currently, we only support
|
| 15 |
+
1. specific conformer encoder architecture, see:
|
| 16 |
+
encoder: conformer
|
| 17 |
+
encoder_conf:
|
| 18 |
+
activation_type: **must be** relu
|
| 19 |
+
attention_heads: 2 or 4 or 8 or any number divisible by output_size
|
| 20 |
+
causal: **must be** true
|
| 21 |
+
cnn_module_kernel: 1 ~ 7
|
| 22 |
+
cnn_module_norm: **must be** batch_norm
|
| 23 |
+
input_layer: **must be** conv2d8
|
| 24 |
+
linear_units: 1 ~ 2048
|
| 25 |
+
normalize_before: **must be** true
|
| 26 |
+
num_blocks: 1 ~ 12
|
| 27 |
+
output_size: 1 ~ 512
|
| 28 |
+
pos_enc_layer_type: **must be** no_pos
|
| 29 |
+
selfattention_layer_type: **must be** selfattn
|
| 30 |
+
use_cnn_module: **must be** true
|
| 31 |
+
use_dynamic_chunk: **must be** true
|
| 32 |
+
use_dynamic_left_chunk: **must be** true
|
| 33 |
+
|
| 34 |
+
2. specific decoding method: ctc_greedy_search
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
from __future__ import print_function
|
| 38 |
+
|
| 39 |
+
import os
|
| 40 |
+
import sys
|
| 41 |
+
import copy
|
| 42 |
+
import math
|
| 43 |
+
import yaml
|
| 44 |
+
import logging
|
| 45 |
+
from typing import Tuple
|
| 46 |
+
|
| 47 |
+
import torch
|
| 48 |
+
import numpy as np
|
| 49 |
+
|
| 50 |
+
from wenet.transformer.embedding import NoPositionalEncoding
|
| 51 |
+
from wenet.utils.init_model import init_model
|
| 52 |
+
from wenet.bin.export_onnx_cpu import (get_args, to_numpy,
|
| 53 |
+
print_input_output_info)
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
import onnx
|
| 57 |
+
import onnxruntime
|
| 58 |
+
except ImportError:
|
| 59 |
+
print('Please install onnx and onnxruntime!')
|
| 60 |
+
sys.exit(1)
|
| 61 |
+
|
| 62 |
+
logger = logging.getLogger(__file__)
|
| 63 |
+
logger.setLevel(logging.INFO)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class BPULayerNorm(torch.nn.Module):
|
| 67 |
+
"""Refactor torch.nn.LayerNorm to meet 4-D dataflow."""
|
| 68 |
+
|
| 69 |
+
def __init__(self, module, chunk_size=8, run_on_bpu=False):
|
| 70 |
+
super().__init__()
|
| 71 |
+
original = copy.deepcopy(module)
|
| 72 |
+
self.hidden = module.weight.size(0)
|
| 73 |
+
self.chunk_size = chunk_size
|
| 74 |
+
self.run_on_bpu = run_on_bpu
|
| 75 |
+
|
| 76 |
+
if self.run_on_bpu:
|
| 77 |
+
self.weight = torch.nn.Parameter(
|
| 78 |
+
module.weight.reshape(1, self.hidden, 1,
|
| 79 |
+
1).repeat(1, 1, 1, chunk_size))
|
| 80 |
+
self.bias = torch.nn.Parameter(
|
| 81 |
+
module.bias.reshape(1, self.hidden, 1,
|
| 82 |
+
1).repeat(1, 1, 1, chunk_size))
|
| 83 |
+
self.negtive = torch.nn.Parameter(
|
| 84 |
+
torch.ones((1, self.hidden, 1, chunk_size)) * -1.0)
|
| 85 |
+
self.eps = torch.nn.Parameter(
|
| 86 |
+
torch.zeros((1, self.hidden, 1, chunk_size)) + module.eps)
|
| 87 |
+
self.mean_conv_1 = torch.nn.Conv2d(self.hidden, 1, 1, bias=False)
|
| 88 |
+
self.mean_conv_1.weight = torch.nn.Parameter(
|
| 89 |
+
torch.ones(self.hidden, self.hidden, 1, 1) /
|
| 90 |
+
(1.0 * self.hidden))
|
| 91 |
+
self.mean_conv_2 = torch.nn.Conv2d(self.hidden, 1, 1, bias=False)
|
| 92 |
+
self.mean_conv_2.weight = torch.nn.Parameter(
|
| 93 |
+
torch.ones(self.hidden, self.hidden, 1, 1) /
|
| 94 |
+
(1.0 * self.hidden))
|
| 95 |
+
else:
|
| 96 |
+
self.norm = module
|
| 97 |
+
|
| 98 |
+
self.check_equal(original)
|
| 99 |
+
|
| 100 |
+
def check_equal(self, module):
|
| 101 |
+
random_data = torch.randn(1, self.chunk_size, self.hidden)
|
| 102 |
+
orig_out = module(random_data)
|
| 103 |
+
new_out = self.forward(random_data.transpose(1, 2).unsqueeze(2))
|
| 104 |
+
np.testing.assert_allclose(to_numpy(orig_out),
|
| 105 |
+
to_numpy(
|
| 106 |
+
new_out.squeeze(2).transpose(1, 2)),
|
| 107 |
+
rtol=1e-02,
|
| 108 |
+
atol=1e-03)
|
| 109 |
+
|
| 110 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 111 |
+
if self.run_on_bpu:
|
| 112 |
+
u = self.mean_conv_1(x) # (1, h, 1, c)
|
| 113 |
+
numerator = x + u * self.negtive # (1, h, 1, c)
|
| 114 |
+
s = torch.pow(numerator, 2) # (1, h, 1, c)
|
| 115 |
+
s = self.mean_conv_2(s) # (1, h, 1, c)
|
| 116 |
+
denominator = torch.sqrt(s + self.eps) # (1, h, 1, c)
|
| 117 |
+
x = torch.div(numerator, denominator) # (1, h, 1, c)
|
| 118 |
+
x = x * self.weight + self.bias
|
| 119 |
+
else:
|
| 120 |
+
x = x.squeeze(2).transpose(1, 2).contiguous()
|
| 121 |
+
x = self.norm(x)
|
| 122 |
+
x = x.transpose(1, 2).contiguous().unsqueeze(2)
|
| 123 |
+
return x
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class BPUIdentity(torch.nn.Module):
|
| 127 |
+
"""Refactor torch.nn.Identity().
|
| 128 |
+
For inserting BPU node whose input == output.
|
| 129 |
+
"""
|
| 130 |
+
|
| 131 |
+
def __init__(self, channels):
|
| 132 |
+
super().__init__()
|
| 133 |
+
self.channels = channels
|
| 134 |
+
self.identity_conv = torch.nn.Conv2d(channels,
|
| 135 |
+
channels,
|
| 136 |
+
1,
|
| 137 |
+
groups=channels,
|
| 138 |
+
bias=False)
|
| 139 |
+
torch.nn.init.dirac_(self.identity_conv.weight.data, groups=channels)
|
| 140 |
+
|
| 141 |
+
self.check_equal()
|
| 142 |
+
|
| 143 |
+
def check_equal(self):
|
| 144 |
+
random_data = torch.randn(1, self.channels, 1, 10)
|
| 145 |
+
result = self.forward(random_data)
|
| 146 |
+
np.testing.assert_allclose(to_numpy(random_data),
|
| 147 |
+
to_numpy(result),
|
| 148 |
+
rtol=1e-02,
|
| 149 |
+
atol=1e-03)
|
| 150 |
+
|
| 151 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 152 |
+
"""Identity with 4-D dataflow, input == output.
|
| 153 |
+
Args:
|
| 154 |
+
x (torch.Tensor): (batch, in_channel, 1, time)
|
| 155 |
+
|
| 156 |
+
Returns:
|
| 157 |
+
(torch.Tensor): (batch, in_channel, 1, time).
|
| 158 |
+
"""
|
| 159 |
+
return self.identity_conv(x)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
class BPULinear(torch.nn.Module):
|
| 163 |
+
"""Refactor torch.nn.Linear or pointwise_conv"""
|
| 164 |
+
|
| 165 |
+
def __init__(self, module, is_pointwise_conv=False):
|
| 166 |
+
super().__init__()
|
| 167 |
+
# Unchanged submodules and attributes
|
| 168 |
+
original = copy.deepcopy(module)
|
| 169 |
+
self.idim = module.weight.size(1)
|
| 170 |
+
self.odim = module.weight.size(0)
|
| 171 |
+
self.is_pointwise_conv = is_pointwise_conv
|
| 172 |
+
|
| 173 |
+
# Modify weight & bias
|
| 174 |
+
self.linear = torch.nn.Conv2d(self.idim, self.odim, 1, 1)
|
| 175 |
+
if is_pointwise_conv:
|
| 176 |
+
# (odim, idim, kernel=1) -> (odim, idim, 1, 1)
|
| 177 |
+
self.linear.weight = torch.nn.Parameter(
|
| 178 |
+
module.weight.unsqueeze(-1))
|
| 179 |
+
else:
|
| 180 |
+
# (odim, idim) -> (odim, idim, 1, 1)
|
| 181 |
+
self.linear.weight = torch.nn.Parameter(
|
| 182 |
+
module.weight.unsqueeze(2).unsqueeze(3))
|
| 183 |
+
self.linear.bias = module.bias
|
| 184 |
+
|
| 185 |
+
self.check_equal(original)
|
| 186 |
+
|
| 187 |
+
def check_equal(self, module):
|
| 188 |
+
random_data = torch.randn(1, 8, self.idim)
|
| 189 |
+
if self.is_pointwise_conv:
|
| 190 |
+
random_data = random_data.transpose(1, 2)
|
| 191 |
+
original_result = module(random_data)
|
| 192 |
+
if self.is_pointwise_conv:
|
| 193 |
+
random_data = random_data.transpose(1, 2)
|
| 194 |
+
original_result = original_result.transpose(1, 2)
|
| 195 |
+
random_data = random_data.transpose(1, 2).unsqueeze(2)
|
| 196 |
+
new_result = self.forward(random_data)
|
| 197 |
+
np.testing.assert_allclose(to_numpy(original_result),
|
| 198 |
+
to_numpy(
|
| 199 |
+
new_result.squeeze(2).transpose(1, 2)),
|
| 200 |
+
rtol=1e-02,
|
| 201 |
+
atol=1e-03)
|
| 202 |
+
|
| 203 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 204 |
+
"""Linear with 4-D dataflow.
|
| 205 |
+
Args:
|
| 206 |
+
x (torch.Tensor): (batch, in_channel, 1, time)
|
| 207 |
+
Returns:
|
| 208 |
+
(torch.Tensor): (batch, out_channel, 1, time).
|
| 209 |
+
"""
|
| 210 |
+
return self.linear(x)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
class BPUGlobalCMVN(torch.nn.Module):
|
| 214 |
+
"""Refactor wenet/transformer/cmvn.py::GlobalCMVN"""
|
| 215 |
+
|
| 216 |
+
def __init__(self, module):
|
| 217 |
+
super().__init__()
|
| 218 |
+
# Unchanged submodules and attributes
|
| 219 |
+
self.norm_var = module.norm_var
|
| 220 |
+
|
| 221 |
+
# NOTE(xcsong): Expand to 4-D tensor, (mel_dim) -> (1, 1, mel_dim, 1)
|
| 222 |
+
self.mean = module.mean.unsqueeze(-1).unsqueeze(0).unsqueeze(0)
|
| 223 |
+
self.istd = module.istd.unsqueeze(-1).unsqueeze(0).unsqueeze(0)
|
| 224 |
+
|
| 225 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 226 |
+
"""CMVN with 4-D dataflow.
|
| 227 |
+
Args:
|
| 228 |
+
x (torch.Tensor): (batch, 1, mel_dim, time)
|
| 229 |
+
Returns:
|
| 230 |
+
(torch.Tensor): normalized feature with same shape.
|
| 231 |
+
"""
|
| 232 |
+
x = x - self.mean
|
| 233 |
+
if self.norm_var:
|
| 234 |
+
x = x * self.istd
|
| 235 |
+
return x
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
class BPUConv2dSubsampling8(torch.nn.Module):
|
| 239 |
+
"""Refactor wenet/transformer/subsampling.py::Conv2dSubsampling8
|
| 240 |
+
|
| 241 |
+
NOTE(xcsong): Only support pos_enc_class == NoPositionalEncoding
|
| 242 |
+
"""
|
| 243 |
+
|
| 244 |
+
def __init__(self, module):
|
| 245 |
+
super().__init__()
|
| 246 |
+
# Unchanged submodules and attributes
|
| 247 |
+
original = copy.deepcopy(module)
|
| 248 |
+
self.right_context = module.right_context
|
| 249 |
+
self.subsampling_rate = module.subsampling_rate
|
| 250 |
+
assert isinstance(module.pos_enc, NoPositionalEncoding)
|
| 251 |
+
|
| 252 |
+
# 1. Modify self.conv
|
| 253 |
+
# NOTE(xcsong): We change input shape from (1, 1, frames, mel_dim)
|
| 254 |
+
# to (1, 1, mel_dim, frames) for more efficient computation.
|
| 255 |
+
self.conv = module.conv
|
| 256 |
+
for idx in [0, 2, 4]:
|
| 257 |
+
self.conv[idx].weight = torch.nn.Parameter(
|
| 258 |
+
module.conv[idx].weight.transpose(2, 3))
|
| 259 |
+
|
| 260 |
+
# 2. Modify self.linear
|
| 261 |
+
# NOTE(xcsong): Split final projection to meet the requirment of
|
| 262 |
+
# maximum kernel_size (7 for XJ3)
|
| 263 |
+
self.linear = torch.nn.ModuleList()
|
| 264 |
+
odim = module.linear.weight.size(0) # 512, in this case
|
| 265 |
+
freq = module.linear.weight.size(1) // odim # 4608 // 512 == 9
|
| 266 |
+
self.odim, self.freq = odim, freq
|
| 267 |
+
weight = module.linear.weight.reshape(
|
| 268 |
+
odim, odim, freq,
|
| 269 |
+
1) # (odim, odim * freq) -> (odim, odim, freq, 1)
|
| 270 |
+
self.split_size = []
|
| 271 |
+
num_split = (freq - 1) // 7 + 1 # XJ3 requires kernel_size <= 7
|
| 272 |
+
slice_begin = 0
|
| 273 |
+
for idx in range(num_split):
|
| 274 |
+
kernel_size = min(freq, (idx + 1) * 7) - idx * 7
|
| 275 |
+
conv_ele = torch.nn.Conv2d(odim, odim, (kernel_size, 1),
|
| 276 |
+
(kernel_size, 1))
|
| 277 |
+
conv_ele.weight = torch.nn.Parameter(
|
| 278 |
+
weight[:, :, slice_begin:slice_begin + kernel_size, :])
|
| 279 |
+
conv_ele.bias = torch.nn.Parameter(torch.zeros_like(conv_ele.bias))
|
| 280 |
+
self.linear.append(conv_ele)
|
| 281 |
+
self.split_size.append(kernel_size)
|
| 282 |
+
slice_begin += kernel_size
|
| 283 |
+
self.linear[0].bias = torch.nn.Parameter(module.linear.bias)
|
| 284 |
+
|
| 285 |
+
self.check_equal(original)
|
| 286 |
+
|
| 287 |
+
def check_equal(self, module):
|
| 288 |
+
random_data = torch.randn(1, 67, 80)
|
| 289 |
+
mask = torch.zeros(1, 1, 67)
|
| 290 |
+
original_result, _, _ = module(random_data, mask) # (1, 8, 512)
|
| 291 |
+
random_data = random_data.transpose(1,
|
| 292 |
+
2).unsqueeze(0) # (1, 1, 80, 67)
|
| 293 |
+
new_result = self.forward(random_data) # (1, 512, 1, 8)
|
| 294 |
+
np.testing.assert_allclose(to_numpy(original_result),
|
| 295 |
+
to_numpy(
|
| 296 |
+
new_result.squeeze(2).transpose(1, 2)),
|
| 297 |
+
rtol=1e-02,
|
| 298 |
+
atol=1e-03)
|
| 299 |
+
|
| 300 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 301 |
+
"""Subsample x with 4-D dataflow.
|
| 302 |
+
Args:
|
| 303 |
+
x (torch.Tensor): Input tensor (#batch, 1, mel_dim, time).
|
| 304 |
+
|
| 305 |
+
Returns:
|
| 306 |
+
torch.Tensor: Subsampled tensor (#batch, odim, 1, time'),
|
| 307 |
+
where time' = time // 8.
|
| 308 |
+
"""
|
| 309 |
+
x = self.conv(x) # (1, odim, freq, time')
|
| 310 |
+
x_out = torch.zeros(x.size(0), self.odim, 1, x.size(3))
|
| 311 |
+
x = torch.split(x, self.split_size, dim=2)
|
| 312 |
+
for idx, (x_part, layer) in enumerate(zip(x, self.linear)):
|
| 313 |
+
x_out += layer(x_part)
|
| 314 |
+
return x_out
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
class BPUMultiHeadedAttention(torch.nn.Module):
|
| 318 |
+
"""Refactor wenet/transformer/attention.py::MultiHeadedAttention
|
| 319 |
+
|
| 320 |
+
NOTE(xcsong): Only support attention_class == MultiHeadedAttention,
|
| 321 |
+
we do not consider RelPositionMultiHeadedAttention currently.
|
| 322 |
+
"""
|
| 323 |
+
|
| 324 |
+
def __init__(self, module, chunk_size, left_chunks):
|
| 325 |
+
super().__init__()
|
| 326 |
+
# Unchanged submodules and attributes
|
| 327 |
+
original = copy.deepcopy(module)
|
| 328 |
+
self.d_k = module.d_k
|
| 329 |
+
self.h = module.h
|
| 330 |
+
n_feat = self.d_k * self.h
|
| 331 |
+
self.chunk_size = chunk_size
|
| 332 |
+
self.left_chunks = left_chunks
|
| 333 |
+
self.time = chunk_size * (left_chunks + 1)
|
| 334 |
+
self.activation = torch.nn.Softmax(dim=-1)
|
| 335 |
+
|
| 336 |
+
# 1. Modify self.linear_x
|
| 337 |
+
self.linear_q = BPULinear(module.linear_q)
|
| 338 |
+
self.linear_k = BPULinear(module.linear_k)
|
| 339 |
+
self.linear_v = BPULinear(module.linear_v)
|
| 340 |
+
self.linear_out = BPULinear(module.linear_out)
|
| 341 |
+
# 2. denom
|
| 342 |
+
self.register_buffer(
|
| 343 |
+
"denom", torch.full((1, self.h, 1, 1), 1.0 / math.sqrt(self.d_k)))
|
| 344 |
+
|
| 345 |
+
self.check_equal(original)
|
| 346 |
+
|
| 347 |
+
def check_equal(self, module):
|
| 348 |
+
random_data = torch.randn(1, self.chunk_size, self.d_k * self.h)
|
| 349 |
+
mask = torch.ones((1, self.h, self.chunk_size, self.time),
|
| 350 |
+
dtype=torch.bool)
|
| 351 |
+
cache = torch.zeros(1, self.h, self.chunk_size * self.left_chunks,
|
| 352 |
+
self.d_k * 2)
|
| 353 |
+
original_out, original_cache = module(random_data, random_data,
|
| 354 |
+
random_data, mask[:, 0, :, :],
|
| 355 |
+
torch.empty(0), cache)
|
| 356 |
+
random_data = random_data.transpose(1, 2).unsqueeze(2)
|
| 357 |
+
cache = cache.reshape(1, self.h, self.d_k * 2,
|
| 358 |
+
self.chunk_size * self.left_chunks)
|
| 359 |
+
new_out, new_cache = self.forward(random_data, random_data,
|
| 360 |
+
random_data, mask, cache)
|
| 361 |
+
np.testing.assert_allclose(to_numpy(original_out),
|
| 362 |
+
to_numpy(
|
| 363 |
+
new_out.squeeze(2).transpose(1, 2)),
|
| 364 |
+
rtol=1e-02,
|
| 365 |
+
atol=1e-03)
|
| 366 |
+
np.testing.assert_allclose(to_numpy(original_cache),
|
| 367 |
+
to_numpy(new_cache.transpose(2, 3)),
|
| 368 |
+
rtol=1e-02,
|
| 369 |
+
atol=1e-03)
|
| 370 |
+
|
| 371 |
+
def forward(
|
| 372 |
+
self,
|
| 373 |
+
q: torch.Tensor,
|
| 374 |
+
k: torch.Tensor,
|
| 375 |
+
v: torch.Tensor,
|
| 376 |
+
mask: torch.Tensor,
|
| 377 |
+
cache: torch.Tensor,
|
| 378 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 379 |
+
"""Compute scaled dot product attention.
|
| 380 |
+
|
| 381 |
+
Args:
|
| 382 |
+
q (torch.Tensor): Query tensor (#batch, size, 1, chunk_size).
|
| 383 |
+
k (torch.Tensor): Key tensor (#batch, size, 1, chunk_size).
|
| 384 |
+
v (torch.Tensor): Value tensor (#batch, size, 1, chunk_size).
|
| 385 |
+
mask (torch.Tensor): Mask tensor,
|
| 386 |
+
(#batch, head, chunk_size, cache_t + chunk_size).
|
| 387 |
+
cache (torch.Tensor): Cache tensor
|
| 388 |
+
(1, head, d_k * 2, cache_t),
|
| 389 |
+
where `cache_t == chunk_size * left_chunks`.
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
Returns:
|
| 393 |
+
torch.Tensor: Output tensor (#batch, size, 1, chunk_size).
|
| 394 |
+
torch.Tensor: Cache tensor
|
| 395 |
+
(1, head, d_k * 2, cache_t + chunk_size)
|
| 396 |
+
where `cache_t == chunk_size * left_chunks`
|
| 397 |
+
"""
|
| 398 |
+
# 1. Forward QKV
|
| 399 |
+
q = self.linear_q(q) # (1, d, 1, c) d == size, c == chunk_size
|
| 400 |
+
k = self.linear_k(k) # (1, d, 1, c)
|
| 401 |
+
v = self.linear_v(v) # (1, d, 1, c)
|
| 402 |
+
q = q.view(1, self.h, self.d_k, self.chunk_size)
|
| 403 |
+
k = k.view(1, self.h, self.d_k, self.chunk_size)
|
| 404 |
+
v = v.view(1, self.h, self.d_k, self.chunk_size)
|
| 405 |
+
q = q.transpose(2, 3) # (batch, head, time1, d_k)
|
| 406 |
+
k_cache, v_cache = torch.split(cache, cache.size(2) // 2, dim=2)
|
| 407 |
+
k = torch.cat((k_cache, k), dim=3)
|
| 408 |
+
v = torch.cat((v_cache, v), dim=3)
|
| 409 |
+
new_cache = torch.cat((k, v), dim=2)
|
| 410 |
+
# 2. (Q^T)K
|
| 411 |
+
scores = torch.matmul(q, k) * self.denom # (#b, n_head, time1, time2)
|
| 412 |
+
# 3. Forward attention
|
| 413 |
+
mask = mask.eq(0)
|
| 414 |
+
scores = scores.masked_fill(mask, -float('inf'))
|
| 415 |
+
attn = self.activation(scores).masked_fill(mask, 0.0)
|
| 416 |
+
attn = attn.transpose(2, 3)
|
| 417 |
+
x = torch.matmul(v, attn)
|
| 418 |
+
x = x.view(1, self.d_k * self.h, 1, self.chunk_size)
|
| 419 |
+
x_out = self.linear_out(x)
|
| 420 |
+
return x_out, new_cache
|
| 421 |
+
|
| 422 |
+
|
| 423 |
+
class BPUConvolution(torch.nn.Module):
|
| 424 |
+
"""Refactor wenet/transformer/convolution.py::ConvolutionModule
|
| 425 |
+
|
| 426 |
+
NOTE(xcsong): Only suport use_layer_norm == False
|
| 427 |
+
"""
|
| 428 |
+
|
| 429 |
+
def __init__(self, module):
|
| 430 |
+
super().__init__()
|
| 431 |
+
# Unchanged submodules and attributes
|
| 432 |
+
original = copy.deepcopy(module)
|
| 433 |
+
self.lorder = module.lorder
|
| 434 |
+
self.use_layer_norm = False
|
| 435 |
+
self.activation = module.activation
|
| 436 |
+
channels = module.pointwise_conv1.weight.size(1)
|
| 437 |
+
self.channels = channels
|
| 438 |
+
kernel_size = module.depthwise_conv.weight.size(2)
|
| 439 |
+
assert module.use_layer_norm is False
|
| 440 |
+
|
| 441 |
+
# 1. Modify self.pointwise_conv1
|
| 442 |
+
self.pointwise_conv1 = BPULinear(module.pointwise_conv1, True)
|
| 443 |
+
|
| 444 |
+
# 2. Modify self.depthwise_conv
|
| 445 |
+
self.depthwise_conv = torch.nn.Conv2d(channels,
|
| 446 |
+
channels, (1, kernel_size),
|
| 447 |
+
stride=1,
|
| 448 |
+
groups=channels)
|
| 449 |
+
self.depthwise_conv.weight = torch.nn.Parameter(
|
| 450 |
+
module.depthwise_conv.weight.unsqueeze(-2))
|
| 451 |
+
self.depthwise_conv.bias = torch.nn.Parameter(
|
| 452 |
+
module.depthwise_conv.bias)
|
| 453 |
+
|
| 454 |
+
# 3. Modify self.norm, Only support batchnorm2d
|
| 455 |
+
self.norm = torch.nn.BatchNorm2d(channels)
|
| 456 |
+
self.norm.training = False
|
| 457 |
+
self.norm.num_features = module.norm.num_features
|
| 458 |
+
self.norm.eps = module.norm.eps
|
| 459 |
+
self.norm.momentum = module.norm.momentum
|
| 460 |
+
self.norm.weight = torch.nn.Parameter(module.norm.weight)
|
| 461 |
+
self.norm.bias = torch.nn.Parameter(module.norm.bias)
|
| 462 |
+
self.norm.running_mean = module.norm.running_mean
|
| 463 |
+
self.norm.running_var = module.norm.running_var
|
| 464 |
+
|
| 465 |
+
# 4. Modify self.pointwise_conv2
|
| 466 |
+
self.pointwise_conv2 = BPULinear(module.pointwise_conv2, True)
|
| 467 |
+
|
| 468 |
+
# 5. Identity conv, for running `concat` on BPU
|
| 469 |
+
self.identity = BPUIdentity(channels)
|
| 470 |
+
|
| 471 |
+
self.check_equal(original)
|
| 472 |
+
|
| 473 |
+
def check_equal(self, module):
|
| 474 |
+
random_data = torch.randn(1, 8, self.channels)
|
| 475 |
+
cache = torch.zeros((1, self.channels, self.lorder))
|
| 476 |
+
original_out, original_cache = module(random_data, cache=cache)
|
| 477 |
+
random_data = random_data.transpose(1, 2).unsqueeze(2)
|
| 478 |
+
cache = cache.unsqueeze(2)
|
| 479 |
+
new_out, new_cache = self.forward(random_data, cache)
|
| 480 |
+
np.testing.assert_allclose(to_numpy(original_out),
|
| 481 |
+
to_numpy(
|
| 482 |
+
new_out.squeeze(2).transpose(1, 2)),
|
| 483 |
+
rtol=1e-02,
|
| 484 |
+
atol=1e-03)
|
| 485 |
+
np.testing.assert_allclose(to_numpy(original_cache),
|
| 486 |
+
to_numpy(new_cache.squeeze(2)),
|
| 487 |
+
rtol=1e-02,
|
| 488 |
+
atol=1e-03)
|
| 489 |
+
|
| 490 |
+
def forward(self, x: torch.Tensor,
|
| 491 |
+
cache: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 492 |
+
"""Compute convolution module.
|
| 493 |
+
Args:
|
| 494 |
+
x (torch.Tensor): Input tensor (#batch, channels, 1, chunk_size).
|
| 495 |
+
cache (torch.Tensor): left context cache, it is only
|
| 496 |
+
used in causal convolution (#batch, channels, 1, cache_t).
|
| 497 |
+
Returns:
|
| 498 |
+
torch.Tensor: Output tensor (#batch, channels, 1, chunk_size).
|
| 499 |
+
torch.Tensor: Cache tensor (#batch, channels, 1, cache_t).
|
| 500 |
+
"""
|
| 501 |
+
# Concat cache
|
| 502 |
+
x = torch.cat((self.identity(cache), self.identity(x)), dim=3)
|
| 503 |
+
new_cache = x[:, :, :, -self.lorder:]
|
| 504 |
+
|
| 505 |
+
# GLU mechanism
|
| 506 |
+
x = self.pointwise_conv1(x) # (batch, 2*channel, 1, dim)
|
| 507 |
+
x = torch.nn.functional.glu(x, dim=1) # (b, channel, 1, dim)
|
| 508 |
+
|
| 509 |
+
# Depthwise Conv
|
| 510 |
+
x = self.depthwise_conv(x)
|
| 511 |
+
x = self.activation(self.norm(x))
|
| 512 |
+
x = self.pointwise_conv2(x)
|
| 513 |
+
return x, new_cache
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
class BPUFFN(torch.nn.Module):
|
| 517 |
+
"""Refactor wenet/transformer/positionwise_feed_forward.py::PositionwiseFeedForward
|
| 518 |
+
"""
|
| 519 |
+
|
| 520 |
+
def __init__(self, module):
|
| 521 |
+
super().__init__()
|
| 522 |
+
# Unchanged submodules and attributes
|
| 523 |
+
original = copy.deepcopy(module)
|
| 524 |
+
self.activation = module.activation
|
| 525 |
+
|
| 526 |
+
# 1. Modify self.w_x
|
| 527 |
+
self.w_1 = BPULinear(module.w_1)
|
| 528 |
+
self.w_2 = BPULinear(module.w_2)
|
| 529 |
+
|
| 530 |
+
self.check_equal(original)
|
| 531 |
+
|
| 532 |
+
def check_equal(self, module):
|
| 533 |
+
random_data = torch.randn(1, 8, self.w_1.idim)
|
| 534 |
+
original_out = module(random_data)
|
| 535 |
+
random_data = random_data.transpose(1, 2).unsqueeze(2)
|
| 536 |
+
new_out = self.forward(random_data)
|
| 537 |
+
np.testing.assert_allclose(to_numpy(original_out),
|
| 538 |
+
to_numpy(
|
| 539 |
+
new_out.squeeze(2).transpose(1, 2)),
|
| 540 |
+
rtol=1e-02,
|
| 541 |
+
atol=1e-03)
|
| 542 |
+
|
| 543 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 544 |
+
"""Forward function.
|
| 545 |
+
|
| 546 |
+
Args:
|
| 547 |
+
xs: input tensor (B, D, 1, L)
|
| 548 |
+
Returns:
|
| 549 |
+
output tensor, (B, D, 1, L)
|
| 550 |
+
"""
|
| 551 |
+
return self.w_2(self.activation(self.w_1(x)))
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
class BPUConformerEncoderLayer(torch.nn.Module):
|
| 555 |
+
"""Refactor wenet/transformer/encoder_layer.py::ConformerEncoderLayer
|
| 556 |
+
"""
|
| 557 |
+
|
| 558 |
+
def __init__(self, module, chunk_size, left_chunks, ln_run_on_bpu=False):
|
| 559 |
+
super().__init__()
|
| 560 |
+
# Unchanged submodules and attributes
|
| 561 |
+
original = copy.deepcopy(module)
|
| 562 |
+
self.size = module.size
|
| 563 |
+
assert module.normalize_before is True
|
| 564 |
+
assert module.concat_after is False
|
| 565 |
+
|
| 566 |
+
# 1. Modify submodules
|
| 567 |
+
self.feed_forward_macaron = BPUFFN(module.feed_forward_macaron)
|
| 568 |
+
self.self_attn = BPUMultiHeadedAttention(module.self_attn, chunk_size,
|
| 569 |
+
left_chunks)
|
| 570 |
+
self.conv_module = BPUConvolution(module.conv_module)
|
| 571 |
+
self.feed_forward = BPUFFN(module.feed_forward)
|
| 572 |
+
|
| 573 |
+
# 2. Modify norms
|
| 574 |
+
self.norm_ff = BPULayerNorm(module.norm_ff, chunk_size, ln_run_on_bpu)
|
| 575 |
+
self.norm_mha = BPULayerNorm(module.norm_mha, chunk_size,
|
| 576 |
+
ln_run_on_bpu)
|
| 577 |
+
self.norm_ff_macron = BPULayerNorm(module.norm_ff_macaron, chunk_size,
|
| 578 |
+
ln_run_on_bpu)
|
| 579 |
+
self.norm_conv = BPULayerNorm(module.norm_conv, chunk_size,
|
| 580 |
+
ln_run_on_bpu)
|
| 581 |
+
self.norm_final = BPULayerNorm(module.norm_final, chunk_size,
|
| 582 |
+
ln_run_on_bpu)
|
| 583 |
+
|
| 584 |
+
# 3. 4-D ff_scale
|
| 585 |
+
self.register_buffer("ff_scale",
|
| 586 |
+
torch.full((1, self.size, 1, 1), module.ff_scale))
|
| 587 |
+
|
| 588 |
+
self.check_equal(original)
|
| 589 |
+
|
| 590 |
+
def check_equal(self, module):
|
| 591 |
+
time1 = self.self_attn.chunk_size
|
| 592 |
+
time2 = self.self_attn.time
|
| 593 |
+
h, d_k = self.self_attn.h, self.self_attn.d_k
|
| 594 |
+
random_x = torch.randn(1, time1, self.size)
|
| 595 |
+
att_mask = torch.ones(1, h, time1, time2)
|
| 596 |
+
att_cache = torch.zeros(1, h, time2 - time1, d_k * 2)
|
| 597 |
+
cnn_cache = torch.zeros(1, self.size, self.conv_module.lorder)
|
| 598 |
+
original_x, _, original_att_cache, original_cnn_cache = module(
|
| 599 |
+
random_x,
|
| 600 |
+
att_mask[:, 0, :, :],
|
| 601 |
+
torch.empty(0),
|
| 602 |
+
att_cache=att_cache,
|
| 603 |
+
cnn_cache=cnn_cache)
|
| 604 |
+
random_x = random_x.transpose(1, 2).unsqueeze(2)
|
| 605 |
+
att_cache = att_cache.reshape(1, h, d_k * 2, time2 - time1)
|
| 606 |
+
cnn_cache = cnn_cache.unsqueeze(2)
|
| 607 |
+
new_x, new_att_cache, new_cnn_cache = self.forward(
|
| 608 |
+
random_x, att_mask, att_cache, cnn_cache)
|
| 609 |
+
np.testing.assert_allclose(to_numpy(original_att_cache),
|
| 610 |
+
to_numpy(new_att_cache.transpose(2, 3)),
|
| 611 |
+
rtol=1e-02,
|
| 612 |
+
atol=1e-03)
|
| 613 |
+
np.testing.assert_allclose(to_numpy(original_x),
|
| 614 |
+
to_numpy(new_x.squeeze(2).transpose(1, 2)),
|
| 615 |
+
rtol=1e-02,
|
| 616 |
+
atol=1e-03)
|
| 617 |
+
np.testing.assert_allclose(to_numpy(original_cnn_cache),
|
| 618 |
+
to_numpy(new_cnn_cache.squeeze(2)),
|
| 619 |
+
rtol=1e-02,
|
| 620 |
+
atol=1e-03)
|
| 621 |
+
|
| 622 |
+
def forward(
|
| 623 |
+
self, x: torch.Tensor, att_mask: torch.Tensor, att_cache: torch.Tensor,
|
| 624 |
+
cnn_cache: torch.Tensor
|
| 625 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 626 |
+
"""Compute encoded features.
|
| 627 |
+
|
| 628 |
+
Args:
|
| 629 |
+
x (torch.Tensor): (#batch, size, 1, chunk_size)
|
| 630 |
+
att_mask (torch.Tensor): Mask tensor for the input
|
| 631 |
+
(#batch, head, chunk_size, cache_t1 + chunk_size),
|
| 632 |
+
att_cache (torch.Tensor): Cache tensor of the KEY & VALUE
|
| 633 |
+
(#batch=1, head, d_k * 2, cache_t1), head * d_k == size.
|
| 634 |
+
cnn_cache (torch.Tensor): Convolution cache in conformer layer
|
| 635 |
+
(#batch=1, size, 1, cache_t2)
|
| 636 |
+
Returns:
|
| 637 |
+
torch.Tensor: Output tensor (#batch, size, 1, chunk_size).
|
| 638 |
+
torch.Tensor: att_cache tensor,
|
| 639 |
+
(1, head, d_k * 2, cache_t1 + chunk_size).
|
| 640 |
+
torch.Tensor: cnn_cahce tensor (#batch, size, 1, cache_t2).
|
| 641 |
+
"""
|
| 642 |
+
# 1. ffn_macaron
|
| 643 |
+
residual = x
|
| 644 |
+
x = self.norm_ff_macron(x)
|
| 645 |
+
x = residual + self.ff_scale * self.feed_forward_macaron(x)
|
| 646 |
+
|
| 647 |
+
# 2. attention
|
| 648 |
+
residual = x
|
| 649 |
+
x = self.norm_mha(x)
|
| 650 |
+
x_att, new_att_cache = self.self_attn(x, x, x, att_mask, att_cache)
|
| 651 |
+
x = residual + x_att
|
| 652 |
+
|
| 653 |
+
# 3. convolution
|
| 654 |
+
residual = x
|
| 655 |
+
x = self.norm_conv(x)
|
| 656 |
+
x, new_cnn_cache = self.conv_module(x, cnn_cache)
|
| 657 |
+
x = residual + x
|
| 658 |
+
|
| 659 |
+
# 4. ffn
|
| 660 |
+
residual = x
|
| 661 |
+
x = self.norm_ff(x)
|
| 662 |
+
x = residual + self.ff_scale * self.feed_forward(x)
|
| 663 |
+
|
| 664 |
+
# 5. final post-norm
|
| 665 |
+
x = self.norm_final(x)
|
| 666 |
+
|
| 667 |
+
return x, new_att_cache, new_cnn_cache
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
class BPUConformerEncoder(torch.nn.Module):
|
| 671 |
+
"""Refactor wenet/transformer/encoder.py::ConformerEncoder
|
| 672 |
+
"""
|
| 673 |
+
|
| 674 |
+
def __init__(self, module, chunk_size, left_chunks, ln_run_on_bpu=False):
|
| 675 |
+
super().__init__()
|
| 676 |
+
# Unchanged submodules and attributes
|
| 677 |
+
original = copy.deepcopy(module)
|
| 678 |
+
output_size = module.output_size()
|
| 679 |
+
self._output_size = module.output_size()
|
| 680 |
+
self.after_norm = module.after_norm
|
| 681 |
+
self.chunk_size = chunk_size
|
| 682 |
+
self.left_chunks = left_chunks
|
| 683 |
+
self.head = module.encoders[0].self_attn.h
|
| 684 |
+
self.layers = len(module.encoders)
|
| 685 |
+
|
| 686 |
+
# 1. Modify submodules
|
| 687 |
+
self.global_cmvn = BPUGlobalCMVN(module.global_cmvn)
|
| 688 |
+
self.embed = BPUConv2dSubsampling8(module.embed)
|
| 689 |
+
self.encoders = torch.nn.ModuleList()
|
| 690 |
+
for layer in module.encoders:
|
| 691 |
+
self.encoders.append(
|
| 692 |
+
BPUConformerEncoderLayer(layer, chunk_size, left_chunks,
|
| 693 |
+
ln_run_on_bpu))
|
| 694 |
+
|
| 695 |
+
# 2. Auxiliary conv
|
| 696 |
+
self.identity_cnncache = BPUIdentity(output_size)
|
| 697 |
+
|
| 698 |
+
self.check_equal(original)
|
| 699 |
+
|
| 700 |
+
def check_equal(self, module):
|
| 701 |
+
time1 = self.encoders[0].self_attn.chunk_size
|
| 702 |
+
time2 = self.encoders[0].self_attn.time
|
| 703 |
+
layers = self.layers
|
| 704 |
+
h, d_k = self.head, self.encoders[0].self_attn.d_k
|
| 705 |
+
decoding_window = (self.chunk_size - 1) * \
|
| 706 |
+
module.embed.subsampling_rate + \
|
| 707 |
+
module.embed.right_context + 1
|
| 708 |
+
lorder = self.encoders[0].conv_module.lorder
|
| 709 |
+
random_x = torch.randn(1, decoding_window, 80)
|
| 710 |
+
att_mask = torch.ones(1, h, time1, time2)
|
| 711 |
+
att_cache = torch.zeros(layers, h, time2 - time1, d_k * 2)
|
| 712 |
+
cnn_cache = torch.zeros(layers, 1, self._output_size, lorder)
|
| 713 |
+
orig_x, orig_att_cache, orig_cnn_cache = module.forward_chunk(
|
| 714 |
+
random_x,
|
| 715 |
+
0,
|
| 716 |
+
time2 - time1,
|
| 717 |
+
att_mask=att_mask[:, 0, :, :],
|
| 718 |
+
att_cache=att_cache,
|
| 719 |
+
cnn_cache=cnn_cache)
|
| 720 |
+
random_x = random_x.unsqueeze(0)
|
| 721 |
+
att_cache = att_cache.reshape(1, h * layers, d_k * 2, time2 - time1)
|
| 722 |
+
cnn_cache = cnn_cache.reshape(1, self._output_size, layers, lorder)
|
| 723 |
+
new_x, new_att_cache, new_cnn_cache = self.forward(
|
| 724 |
+
random_x, att_cache, cnn_cache, att_mask)
|
| 725 |
+
caches = torch.split(new_att_cache, h, dim=1)
|
| 726 |
+
caches = [c.transpose(2, 3) for c in caches]
|
| 727 |
+
np.testing.assert_allclose(to_numpy(orig_att_cache),
|
| 728 |
+
to_numpy(torch.cat(caches, dim=0)),
|
| 729 |
+
rtol=1e-02,
|
| 730 |
+
atol=1e-03)
|
| 731 |
+
np.testing.assert_allclose(to_numpy(orig_x),
|
| 732 |
+
to_numpy(new_x.squeeze(2).transpose(1, 2)),
|
| 733 |
+
rtol=1e-02,
|
| 734 |
+
atol=1e-03)
|
| 735 |
+
np.testing.assert_allclose(
|
| 736 |
+
to_numpy(orig_cnn_cache),
|
| 737 |
+
to_numpy(new_cnn_cache.transpose(0, 2).transpose(1, 2)),
|
| 738 |
+
rtol=1e-02,
|
| 739 |
+
atol=1e-03)
|
| 740 |
+
|
| 741 |
+
def forward(
|
| 742 |
+
self, xs: torch.Tensor, att_cache: torch.Tensor,
|
| 743 |
+
cnn_cache: torch.Tensor, att_mask: torch.Tensor
|
| 744 |
+
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 745 |
+
""" Forward just one chunk
|
| 746 |
+
|
| 747 |
+
Args:
|
| 748 |
+
xs (torch.Tensor): chunk input, with shape (b=1, 1, time, mel-dim),
|
| 749 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 750 |
+
subsample.right_context + 1`
|
| 751 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 752 |
+
transformer/conformer attention, with shape
|
| 753 |
+
(1, head * elayers, d_k * 2, cache_t1), where
|
| 754 |
+
`head * d_k == hidden-dim` and
|
| 755 |
+
`cache_t1 == chunk_size * left_chunks`.
|
| 756 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 757 |
+
(1, hidden-dim, elayers, cache_t2), where
|
| 758 |
+
`cache_t2 == cnn.lorder - 1`
|
| 759 |
+
att_mask (torch.Tensor): Mask tensor for the input
|
| 760 |
+
(#batch, head, chunk_size, cache_t1 + chunk_size),
|
| 761 |
+
|
| 762 |
+
Returns:
|
| 763 |
+
torch.Tensor: output of current input xs,
|
| 764 |
+
with shape (b=1, hidden-dim, 1, chunk_size).
|
| 765 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 766 |
+
same shape as the original att_cache.
|
| 767 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 768 |
+
same shape as the original cnn_cache.
|
| 769 |
+
"""
|
| 770 |
+
# xs: (B, 1, time, mel_dim) -> (B, 1, mel_dim, time)
|
| 771 |
+
xs = xs.transpose(2, 3)
|
| 772 |
+
xs = self.global_cmvn(xs)
|
| 773 |
+
# xs: (B, 1, mel_dim, time) -> (B, hidden_dim, 1, chunk_size)
|
| 774 |
+
xs = self.embed(xs)
|
| 775 |
+
|
| 776 |
+
att_cache = torch.split(att_cache, self.head, dim=1)
|
| 777 |
+
cnn_cache = self.identity_cnncache(cnn_cache)
|
| 778 |
+
cnn_cache = torch.split(cnn_cache, 1, dim=2)
|
| 779 |
+
r_att_cache = []
|
| 780 |
+
r_cnn_cache = []
|
| 781 |
+
for i, layer in enumerate(self.encoders):
|
| 782 |
+
xs, new_att_cache, new_cnn_cache = layer(xs,
|
| 783 |
+
att_mask,
|
| 784 |
+
att_cache=att_cache[i],
|
| 785 |
+
cnn_cache=cnn_cache[i])
|
| 786 |
+
r_att_cache.append(new_att_cache[:, :, :, self.chunk_size:])
|
| 787 |
+
r_cnn_cache.append(new_cnn_cache)
|
| 788 |
+
r_att_cache = torch.cat(r_att_cache, dim=1)
|
| 789 |
+
r_cnn_cache = self.identity_cnncache(torch.cat(r_cnn_cache, dim=2))
|
| 790 |
+
|
| 791 |
+
xs = xs.squeeze(2).transpose(1, 2).contiguous()
|
| 792 |
+
xs = self.after_norm(xs)
|
| 793 |
+
# NOTE(xcsong): 4D in, 4D out to meet the requirment of CTC input.
|
| 794 |
+
xs = xs.transpose(1, 2).contiguous().unsqueeze(2) # (B, C, 1, T)
|
| 795 |
+
|
| 796 |
+
return (xs, r_att_cache, r_cnn_cache)
|
| 797 |
+
|
| 798 |
+
|
| 799 |
+
class BPUCTC(torch.nn.Module):
|
| 800 |
+
"""Refactor wenet/transformer/ctc.py::CTC
|
| 801 |
+
"""
|
| 802 |
+
|
| 803 |
+
def __init__(self, module):
|
| 804 |
+
super().__init__()
|
| 805 |
+
# Unchanged submodules and attributes
|
| 806 |
+
original = copy.deepcopy(module)
|
| 807 |
+
self.idim = module.ctc_lo.weight.size(1)
|
| 808 |
+
num_class = module.ctc_lo.weight.size(0)
|
| 809 |
+
|
| 810 |
+
# 1. Modify self.ctc_lo, Split final projection to meet the
|
| 811 |
+
# requirment of maximum in/out channels (2048 for XJ3)
|
| 812 |
+
self.ctc_lo = torch.nn.ModuleList()
|
| 813 |
+
self.split_size = []
|
| 814 |
+
num_split = (num_class - 1) // 2048 + 1
|
| 815 |
+
for idx in range(num_split):
|
| 816 |
+
out_channel = min(num_class, (idx + 1) * 2048) - idx * 2048
|
| 817 |
+
conv_ele = torch.nn.Conv2d(self.idim, out_channel, 1, 1)
|
| 818 |
+
self.ctc_lo.append(conv_ele)
|
| 819 |
+
self.split_size.append(out_channel)
|
| 820 |
+
orig_weight = torch.split(module.ctc_lo.weight, self.split_size, dim=0)
|
| 821 |
+
orig_bias = torch.split(module.ctc_lo.bias, self.split_size, dim=0)
|
| 822 |
+
for i, (w, b) in enumerate(zip(orig_weight, orig_bias)):
|
| 823 |
+
w = w.unsqueeze(2).unsqueeze(3)
|
| 824 |
+
self.ctc_lo[i].weight = torch.nn.Parameter(w)
|
| 825 |
+
self.ctc_lo[i].bias = torch.nn.Parameter(b)
|
| 826 |
+
|
| 827 |
+
self.check_equal(original)
|
| 828 |
+
|
| 829 |
+
def check_equal(self, module):
|
| 830 |
+
random_data = torch.randn(1, 100, self.idim)
|
| 831 |
+
original_result = module.ctc_lo(random_data)
|
| 832 |
+
random_data = random_data.transpose(1, 2).unsqueeze(2)
|
| 833 |
+
new_result = self.forward(random_data)
|
| 834 |
+
np.testing.assert_allclose(to_numpy(original_result),
|
| 835 |
+
to_numpy(
|
| 836 |
+
new_result.squeeze(2).transpose(1, 2)),
|
| 837 |
+
rtol=1e-02,
|
| 838 |
+
atol=1e-03)
|
| 839 |
+
|
| 840 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 841 |
+
"""frame activations, without softmax.
|
| 842 |
+
|
| 843 |
+
Args:
|
| 844 |
+
Tensor x: 4d tensor (B, hidden_dim, 1, chunk_size)
|
| 845 |
+
Returns:
|
| 846 |
+
torch.Tensor: (B, num_class, 1, chunk_size)
|
| 847 |
+
"""
|
| 848 |
+
out = []
|
| 849 |
+
for i, layer in enumerate(self.ctc_lo):
|
| 850 |
+
out.append(layer(x))
|
| 851 |
+
out = torch.cat(out, dim=1)
|
| 852 |
+
return out
|
| 853 |
+
|
| 854 |
+
|
| 855 |
+
def export_encoder(asr_model, args):
|
| 856 |
+
logger.info("Stage-1: export encoder")
|
| 857 |
+
decode_window, mel_dim = args.decoding_window, args.feature_size
|
| 858 |
+
encoder = BPUConformerEncoder(asr_model.encoder, args.chunk_size,
|
| 859 |
+
args.num_decoding_left_chunks,
|
| 860 |
+
args.ln_run_on_bpu)
|
| 861 |
+
encoder.eval()
|
| 862 |
+
encoder_outpath = os.path.join(args.output_dir, 'encoder.onnx')
|
| 863 |
+
|
| 864 |
+
logger.info("Stage-1.1: prepare inputs for encoder")
|
| 865 |
+
chunk = torch.randn((1, 1, decode_window, mel_dim))
|
| 866 |
+
required_cache_size = encoder.chunk_size * encoder.left_chunks
|
| 867 |
+
kv_time = required_cache_size + encoder.chunk_size
|
| 868 |
+
hidden, layers = encoder._output_size, len(encoder.encoders)
|
| 869 |
+
head = encoder.encoders[0].self_attn.h
|
| 870 |
+
d_k = hidden // head
|
| 871 |
+
lorder = encoder.encoders[0].conv_module.lorder
|
| 872 |
+
att_cache = torch.zeros(1, layers * head, d_k * 2, required_cache_size)
|
| 873 |
+
att_mask = torch.ones((1, head, encoder.chunk_size, kv_time))
|
| 874 |
+
att_mask[:, :, :, :required_cache_size] = 0
|
| 875 |
+
cnn_cache = torch.zeros((1, hidden, layers, lorder))
|
| 876 |
+
inputs = (chunk, att_cache, cnn_cache, att_mask)
|
| 877 |
+
logger.info("chunk.size(): {} att_cache.size(): {} "
|
| 878 |
+
"cnn_cache.size(): {} att_mask.size(): {}".format(
|
| 879 |
+
list(chunk.size()), list(att_cache.size()),
|
| 880 |
+
list(cnn_cache.size()), list(att_mask.size())))
|
| 881 |
+
|
| 882 |
+
logger.info("Stage-1.2: torch.onnx.export")
|
| 883 |
+
# NOTE(xcsong): Below attributes will be used in
|
| 884 |
+
# onnx2horizonbin.py::generate_config()
|
| 885 |
+
attributes = {}
|
| 886 |
+
attributes['input_name'] = "chunk;att_cache;cnn_cache;att_mask"
|
| 887 |
+
attributes['output_name'] = "output;r_att_cache;r_cnn_cache"
|
| 888 |
+
attributes['input_type'] = "featuremap;featuremap;featuremap;featuremap"
|
| 889 |
+
attributes['norm_type'] = \
|
| 890 |
+
"no_preprocess;no_preprocess;no_preprocess;no_preprocess"
|
| 891 |
+
attributes['input_layout_train'] = "NCHW;NCHW;NCHW;NCHW"
|
| 892 |
+
attributes['input_layout_rt'] = "NCHW;NCHW;NCHW;NCHW"
|
| 893 |
+
attributes['input_shape'] = \
|
| 894 |
+
"{}x{}x{}x{};{}x{}x{}x{};{}x{}x{}x{};{}x{}x{}x{}".format(
|
| 895 |
+
chunk.size(0), chunk.size(1), chunk.size(2), chunk.size(3),
|
| 896 |
+
att_cache.size(0), att_cache.size(1), att_cache.size(2),
|
| 897 |
+
att_cache.size(3), cnn_cache.size(0), cnn_cache.size(1),
|
| 898 |
+
cnn_cache.size(2), cnn_cache.size(3), att_mask.size(0),
|
| 899 |
+
att_mask.size(1), att_mask.size(2), att_mask.size(3)
|
| 900 |
+
)
|
| 901 |
+
torch.onnx.export( # NOTE(xcsong): only support opset==11
|
| 902 |
+
encoder,
|
| 903 |
+
inputs,
|
| 904 |
+
encoder_outpath,
|
| 905 |
+
opset_version=11,
|
| 906 |
+
export_params=True,
|
| 907 |
+
do_constant_folding=True,
|
| 908 |
+
input_names=attributes['input_name'].split(';'),
|
| 909 |
+
output_names=attributes['output_name'].split(';'),
|
| 910 |
+
dynamic_axes=None,
|
| 911 |
+
verbose=False)
|
| 912 |
+
onnx_encoder = onnx.load(encoder_outpath)
|
| 913 |
+
for k in vars(args):
|
| 914 |
+
meta = onnx_encoder.metadata_props.add()
|
| 915 |
+
meta.key, meta.value = str(k), str(getattr(args, k))
|
| 916 |
+
for k in attributes:
|
| 917 |
+
meta = onnx_encoder.metadata_props.add()
|
| 918 |
+
meta.key, meta.value = str(k), str(attributes[k])
|
| 919 |
+
onnx.checker.check_model(onnx_encoder)
|
| 920 |
+
onnx.helper.printable_graph(onnx_encoder.graph)
|
| 921 |
+
onnx.save(onnx_encoder, encoder_outpath)
|
| 922 |
+
print_input_output_info(onnx_encoder, "onnx_encoder")
|
| 923 |
+
logger.info('Export onnx_encoder, done! see {}'.format(encoder_outpath))
|
| 924 |
+
|
| 925 |
+
logger.info("Stage-1.3: check onnx_encoder and torch_encoder")
|
| 926 |
+
torch_output = []
|
| 927 |
+
torch_chunk, torch_att_mask = copy.deepcopy(chunk), copy.deepcopy(att_mask)
|
| 928 |
+
torch_att_cache = copy.deepcopy(att_cache)
|
| 929 |
+
torch_cnn_cache = copy.deepcopy(cnn_cache)
|
| 930 |
+
for i in range(10):
|
| 931 |
+
logger.info("torch chunk-{}: {}, att_cache: {}, cnn_cache: {}"
|
| 932 |
+
", att_mask: {}".format(i, list(torch_chunk.size()),
|
| 933 |
+
list(torch_att_cache.size()),
|
| 934 |
+
list(torch_cnn_cache.size()),
|
| 935 |
+
list(torch_att_mask.size())))
|
| 936 |
+
torch_att_mask[:, :, :, -(encoder.chunk_size * (i + 1)):] = 1
|
| 937 |
+
out, torch_att_cache, torch_cnn_cache = encoder(
|
| 938 |
+
torch_chunk, torch_att_cache, torch_cnn_cache, torch_att_mask)
|
| 939 |
+
torch_output.append(out)
|
| 940 |
+
torch_output = torch.cat(torch_output, dim=-1)
|
| 941 |
+
|
| 942 |
+
onnx_output = []
|
| 943 |
+
onnx_chunk, onnx_att_mask = to_numpy(chunk), to_numpy(att_mask)
|
| 944 |
+
onnx_att_cache = to_numpy(att_cache)
|
| 945 |
+
onnx_cnn_cache = to_numpy(cnn_cache)
|
| 946 |
+
ort_session = onnxruntime.InferenceSession(encoder_outpath)
|
| 947 |
+
input_names = [node.name for node in onnx_encoder.graph.input]
|
| 948 |
+
for i in range(10):
|
| 949 |
+
logger.info("onnx chunk-{}: {}, att_cache: {}, cnn_cache: {},"
|
| 950 |
+
" att_mask: {}".format(i, onnx_chunk.shape,
|
| 951 |
+
onnx_att_cache.shape,
|
| 952 |
+
onnx_cnn_cache.shape,
|
| 953 |
+
onnx_att_mask.shape))
|
| 954 |
+
onnx_att_mask[:, :, :, -(encoder.chunk_size * (i + 1)):] = 1
|
| 955 |
+
ort_inputs = {
|
| 956 |
+
'chunk': onnx_chunk,
|
| 957 |
+
'att_cache': onnx_att_cache,
|
| 958 |
+
'cnn_cache': onnx_cnn_cache,
|
| 959 |
+
'att_mask': onnx_att_mask,
|
| 960 |
+
}
|
| 961 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 962 |
+
onnx_att_cache, onnx_cnn_cache = ort_outs[1], ort_outs[2]
|
| 963 |
+
onnx_output.append(ort_outs[0])
|
| 964 |
+
onnx_output = np.concatenate(onnx_output, axis=-1)
|
| 965 |
+
|
| 966 |
+
np.testing.assert_allclose(to_numpy(torch_output),
|
| 967 |
+
onnx_output,
|
| 968 |
+
rtol=1e-03,
|
| 969 |
+
atol=1e-04)
|
| 970 |
+
meta = ort_session.get_modelmeta()
|
| 971 |
+
logger.info("custom_metadata_map={}".format(meta.custom_metadata_map))
|
| 972 |
+
logger.info("Check onnx_encoder, pass!")
|
| 973 |
+
return encoder, ort_session
|
| 974 |
+
|
| 975 |
+
|
| 976 |
+
def export_ctc(asr_model, args):
|
| 977 |
+
logger.info("Stage-2: export ctc")
|
| 978 |
+
ctc = BPUCTC(asr_model.ctc).eval()
|
| 979 |
+
ctc_outpath = os.path.join(args.output_dir, 'ctc.onnx')
|
| 980 |
+
|
| 981 |
+
logger.info("Stage-2.1: prepare inputs for ctc")
|
| 982 |
+
hidden = torch.randn((1, args.output_size, 1, args.chunk_size))
|
| 983 |
+
|
| 984 |
+
logger.info("Stage-2.2: torch.onnx.export")
|
| 985 |
+
# NOTE(xcsong): Below attributes will be used in
|
| 986 |
+
# onnx2horizonbin.py::generate_config()
|
| 987 |
+
attributes = {}
|
| 988 |
+
attributes['input_name'], attributes['input_type'] = "hidden", "featuremap"
|
| 989 |
+
attributes['norm_type'] = "no_preprocess"
|
| 990 |
+
attributes['input_layout_train'] = "NCHW"
|
| 991 |
+
attributes['input_layout_rt'] = "NCHW"
|
| 992 |
+
attributes['input_shape'] = "{}x{}x{}x{}".format(
|
| 993 |
+
hidden.size(0),
|
| 994 |
+
hidden.size(1),
|
| 995 |
+
hidden.size(2),
|
| 996 |
+
hidden.size(3),
|
| 997 |
+
)
|
| 998 |
+
torch.onnx.export(ctc,
|
| 999 |
+
hidden,
|
| 1000 |
+
ctc_outpath,
|
| 1001 |
+
opset_version=11,
|
| 1002 |
+
export_params=True,
|
| 1003 |
+
do_constant_folding=True,
|
| 1004 |
+
input_names=['hidden'],
|
| 1005 |
+
output_names=['probs'],
|
| 1006 |
+
dynamic_axes=None,
|
| 1007 |
+
verbose=False)
|
| 1008 |
+
onnx_ctc = onnx.load(ctc_outpath)
|
| 1009 |
+
for k in vars(args):
|
| 1010 |
+
meta = onnx_ctc.metadata_props.add()
|
| 1011 |
+
meta.key, meta.value = str(k), str(getattr(args, k))
|
| 1012 |
+
for k in attributes:
|
| 1013 |
+
meta = onnx_ctc.metadata_props.add()
|
| 1014 |
+
meta.key, meta.value = str(k), str(attributes[k])
|
| 1015 |
+
onnx.checker.check_model(onnx_ctc)
|
| 1016 |
+
onnx.helper.printable_graph(onnx_ctc.graph)
|
| 1017 |
+
onnx.save(onnx_ctc, ctc_outpath)
|
| 1018 |
+
print_input_output_info(onnx_ctc, "onnx_ctc")
|
| 1019 |
+
logger.info('Export onnx_ctc, done! see {}'.format(ctc_outpath))
|
| 1020 |
+
|
| 1021 |
+
logger.info("Stage-2.3: check onnx_ctc and torch_ctc")
|
| 1022 |
+
torch_output = ctc(hidden)
|
| 1023 |
+
ort_session = onnxruntime.InferenceSession(ctc_outpath)
|
| 1024 |
+
onnx_output = ort_session.run(None, {'hidden': to_numpy(hidden)})
|
| 1025 |
+
|
| 1026 |
+
np.testing.assert_allclose(to_numpy(torch_output),
|
| 1027 |
+
onnx_output[0],
|
| 1028 |
+
rtol=1e-03,
|
| 1029 |
+
atol=1e-04)
|
| 1030 |
+
meta = ort_session.get_modelmeta()
|
| 1031 |
+
logger.info("custom_metadata_map={}".format(meta.custom_metadata_map))
|
| 1032 |
+
logger.info("Check onnx_ctc, pass!")
|
| 1033 |
+
return ctc, ort_session
|
| 1034 |
+
|
| 1035 |
+
|
| 1036 |
+
def export_decoder(asr_model, args):
|
| 1037 |
+
logger.info("Currently, Decoder is not supported.")
|
| 1038 |
+
|
| 1039 |
+
|
| 1040 |
+
if __name__ == '__main__':
|
| 1041 |
+
torch.manual_seed(777)
|
| 1042 |
+
args = get_args()
|
| 1043 |
+
args.ln_run_on_bpu = False
|
| 1044 |
+
# NOTE(xcsong): XJ3 BPU only support static shapes
|
| 1045 |
+
assert args.chunk_size > 0
|
| 1046 |
+
assert args.num_decoding_left_chunks > 0
|
| 1047 |
+
os.system("mkdir -p " + args.output_dir)
|
| 1048 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
| 1049 |
+
|
| 1050 |
+
with open(args.config, 'r') as fin:
|
| 1051 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 1052 |
+
|
| 1053 |
+
model, configs = init_model(args, configs)
|
| 1054 |
+
model.eval()
|
| 1055 |
+
print(model)
|
| 1056 |
+
|
| 1057 |
+
args.feature_size = configs['input_dim']
|
| 1058 |
+
args.output_size = model.encoder.output_size()
|
| 1059 |
+
args.decoding_window = (args.chunk_size - 1) * \
|
| 1060 |
+
model.encoder.embed.subsampling_rate + \
|
| 1061 |
+
model.encoder.embed.right_context + 1
|
| 1062 |
+
|
| 1063 |
+
export_encoder(model, args)
|
| 1064 |
+
export_ctc(model, args)
|
| 1065 |
+
export_decoder(model, args)
|
wenet/bin/export_onnx_cpu.py
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022, Xingchen Song (sxc19@mails.tsinghua.edu.cn)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import logging
|
| 19 |
+
import os
|
| 20 |
+
import copy
|
| 21 |
+
import sys
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
import yaml
|
| 25 |
+
import numpy as np
|
| 26 |
+
|
| 27 |
+
from wenet.utils.init_model import init_model
|
| 28 |
+
|
| 29 |
+
try:
|
| 30 |
+
import onnx
|
| 31 |
+
import onnxruntime
|
| 32 |
+
from onnxruntime.quantization import quantize_dynamic, QuantType
|
| 33 |
+
except ImportError:
|
| 34 |
+
print('Please install onnx and onnxruntime!')
|
| 35 |
+
sys.exit(1)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def get_args():
|
| 39 |
+
parser = argparse.ArgumentParser(description='export your script model')
|
| 40 |
+
parser.add_argument('--config', required=True, help='config file')
|
| 41 |
+
parser.add_argument('--checkpoint', required=True, help='checkpoint model')
|
| 42 |
+
parser.add_argument('--output_dir', required=True, help='output directory')
|
| 43 |
+
parser.add_argument('--chunk_size',
|
| 44 |
+
required=True,
|
| 45 |
+
type=int,
|
| 46 |
+
help='decoding chunk size')
|
| 47 |
+
parser.add_argument('--num_decoding_left_chunks',
|
| 48 |
+
required=True,
|
| 49 |
+
type=int,
|
| 50 |
+
help='cache chunks')
|
| 51 |
+
parser.add_argument('--reverse_weight',
|
| 52 |
+
default=0.5,
|
| 53 |
+
type=float,
|
| 54 |
+
help='reverse_weight in attention_rescoing')
|
| 55 |
+
args = parser.parse_args()
|
| 56 |
+
return args
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def to_numpy(tensor):
|
| 60 |
+
if tensor.requires_grad:
|
| 61 |
+
return tensor.detach().cpu().numpy()
|
| 62 |
+
else:
|
| 63 |
+
return tensor.cpu().numpy()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def print_input_output_info(onnx_model, name, prefix="\t\t"):
|
| 67 |
+
input_names = [node.name for node in onnx_model.graph.input]
|
| 68 |
+
input_shapes = [[d.dim_value for d in node.type.tensor_type.shape.dim]
|
| 69 |
+
for node in onnx_model.graph.input]
|
| 70 |
+
output_names = [node.name for node in onnx_model.graph.output]
|
| 71 |
+
output_shapes = [[d.dim_value for d in node.type.tensor_type.shape.dim]
|
| 72 |
+
for node in onnx_model.graph.output]
|
| 73 |
+
print("{}{} inputs : {}".format(prefix, name, input_names))
|
| 74 |
+
print("{}{} input shapes : {}".format(prefix, name, input_shapes))
|
| 75 |
+
print("{}{} outputs: {}".format(prefix, name, output_names))
|
| 76 |
+
print("{}{} output shapes : {}".format(prefix, name, output_shapes))
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def export_encoder(asr_model, args):
|
| 80 |
+
print("Stage-1: export encoder")
|
| 81 |
+
encoder = asr_model.encoder
|
| 82 |
+
encoder.forward = encoder.forward_chunk
|
| 83 |
+
encoder_outpath = os.path.join(args['output_dir'], 'encoder.onnx')
|
| 84 |
+
|
| 85 |
+
print("\tStage-1.1: prepare inputs for encoder")
|
| 86 |
+
chunk = torch.randn(
|
| 87 |
+
(args['batch'], args['decoding_window'], args['feature_size']))
|
| 88 |
+
offset = 0
|
| 89 |
+
# NOTE(xcsong): The uncertainty of `next_cache_start` only appears
|
| 90 |
+
# in the first few chunks, this is caused by dynamic att_cache shape, i,e
|
| 91 |
+
# (0, 0, 0, 0) for 1st chunk and (elayers, head, ?, d_k*2) for subsequent
|
| 92 |
+
# chunks. One way to ease the ONNX export is to keep `next_cache_start`
|
| 93 |
+
# as a fixed value. To do this, for the **first** chunk, if
|
| 94 |
+
# left_chunks > 0, we feed real cache & real mask to the model, otherwise
|
| 95 |
+
# fake cache & fake mask. In this way, we get:
|
| 96 |
+
# 1. 16/-1 mode: next_cache_start == 0 for all chunks
|
| 97 |
+
# 2. 16/4 mode: next_cache_start == chunk_size for all chunks
|
| 98 |
+
# 3. 16/0 mode: next_cache_start == chunk_size for all chunks
|
| 99 |
+
# 4. -1/-1 mode: next_cache_start == 0 for all chunks
|
| 100 |
+
# NO MORE DYNAMIC CHANGES!!
|
| 101 |
+
#
|
| 102 |
+
# NOTE(Mddct): We retain the current design for the convenience of supporting some
|
| 103 |
+
# inference frameworks without dynamic shapes. If you're interested in all-in-one
|
| 104 |
+
# model that supports different chunks please see:
|
| 105 |
+
# https://github.com/wenet-e2e/wenet/pull/1174
|
| 106 |
+
|
| 107 |
+
if args['left_chunks'] > 0: # 16/4
|
| 108 |
+
required_cache_size = args['chunk_size'] * args['left_chunks']
|
| 109 |
+
offset = required_cache_size
|
| 110 |
+
# Real cache
|
| 111 |
+
att_cache = torch.zeros(
|
| 112 |
+
(args['num_blocks'], args['head'], required_cache_size,
|
| 113 |
+
args['output_size'] // args['head'] * 2))
|
| 114 |
+
# Real mask
|
| 115 |
+
att_mask = torch.ones(
|
| 116 |
+
(args['batch'], 1, required_cache_size + args['chunk_size']),
|
| 117 |
+
dtype=torch.bool)
|
| 118 |
+
att_mask[:, :, :required_cache_size] = 0
|
| 119 |
+
elif args['left_chunks'] <= 0: # 16/-1, -1/-1, 16/0
|
| 120 |
+
required_cache_size = -1 if args['left_chunks'] < 0 else 0
|
| 121 |
+
# Fake cache
|
| 122 |
+
att_cache = torch.zeros((args['num_blocks'], args['head'], 0,
|
| 123 |
+
args['output_size'] // args['head'] * 2))
|
| 124 |
+
# Fake mask
|
| 125 |
+
att_mask = torch.ones((0, 0, 0), dtype=torch.bool)
|
| 126 |
+
cnn_cache = torch.zeros(
|
| 127 |
+
(args['num_blocks'], args['batch'], args['output_size'],
|
| 128 |
+
args['cnn_module_kernel'] - 1))
|
| 129 |
+
inputs = (chunk, offset, required_cache_size, att_cache, cnn_cache,
|
| 130 |
+
att_mask)
|
| 131 |
+
print("\t\tchunk.size(): {}\n".format(chunk.size()),
|
| 132 |
+
"\t\toffset: {}\n".format(offset),
|
| 133 |
+
"\t\trequired_cache: {}\n".format(required_cache_size),
|
| 134 |
+
"\t\tatt_cache.size(): {}\n".format(att_cache.size()),
|
| 135 |
+
"\t\tcnn_cache.size(): {}\n".format(cnn_cache.size()),
|
| 136 |
+
"\t\tatt_mask.size(): {}\n".format(att_mask.size()))
|
| 137 |
+
|
| 138 |
+
print("\tStage-1.2: torch.onnx.export")
|
| 139 |
+
dynamic_axes = {
|
| 140 |
+
'chunk': {
|
| 141 |
+
1: 'T'
|
| 142 |
+
},
|
| 143 |
+
'att_cache': {
|
| 144 |
+
2: 'T_CACHE'
|
| 145 |
+
},
|
| 146 |
+
'att_mask': {
|
| 147 |
+
2: 'T_ADD_T_CACHE'
|
| 148 |
+
},
|
| 149 |
+
'output': {
|
| 150 |
+
1: 'T'
|
| 151 |
+
},
|
| 152 |
+
'r_att_cache': {
|
| 153 |
+
2: 'T_CACHE'
|
| 154 |
+
},
|
| 155 |
+
}
|
| 156 |
+
# NOTE(xcsong): We keep dynamic axes even if in 16/4 mode, this is
|
| 157 |
+
# to avoid padding the last chunk (which usually contains less
|
| 158 |
+
# frames than required). For users who want static axes, just pop
|
| 159 |
+
# out specific axis.
|
| 160 |
+
# if args['chunk_size'] > 0: # 16/4, 16/-1, 16/0
|
| 161 |
+
# dynamic_axes.pop('chunk')
|
| 162 |
+
# dynamic_axes.pop('output')
|
| 163 |
+
# if args['left_chunks'] >= 0: # 16/4, 16/0
|
| 164 |
+
# # NOTE(xsong): since we feed real cache & real mask into the
|
| 165 |
+
# # model when left_chunks > 0, the shape of cache will never
|
| 166 |
+
# # be changed.
|
| 167 |
+
# dynamic_axes.pop('att_cache')
|
| 168 |
+
# dynamic_axes.pop('r_att_cache')
|
| 169 |
+
torch.onnx.export(encoder,
|
| 170 |
+
inputs,
|
| 171 |
+
encoder_outpath,
|
| 172 |
+
opset_version=13,
|
| 173 |
+
export_params=True,
|
| 174 |
+
do_constant_folding=True,
|
| 175 |
+
input_names=[
|
| 176 |
+
'chunk', 'offset', 'required_cache_size',
|
| 177 |
+
'att_cache', 'cnn_cache', 'att_mask'
|
| 178 |
+
],
|
| 179 |
+
output_names=['output', 'r_att_cache', 'r_cnn_cache'],
|
| 180 |
+
dynamic_axes=dynamic_axes,
|
| 181 |
+
verbose=False)
|
| 182 |
+
onnx_encoder = onnx.load(encoder_outpath)
|
| 183 |
+
for (k, v) in args.items():
|
| 184 |
+
meta = onnx_encoder.metadata_props.add()
|
| 185 |
+
meta.key, meta.value = str(k), str(v)
|
| 186 |
+
onnx.checker.check_model(onnx_encoder)
|
| 187 |
+
onnx.helper.printable_graph(onnx_encoder.graph)
|
| 188 |
+
# NOTE(xcsong): to add those metadatas we need to reopen
|
| 189 |
+
# the file and resave it.
|
| 190 |
+
onnx.save(onnx_encoder, encoder_outpath)
|
| 191 |
+
print_input_output_info(onnx_encoder, "onnx_encoder")
|
| 192 |
+
# Dynamic quantization
|
| 193 |
+
model_fp32 = encoder_outpath
|
| 194 |
+
model_quant = os.path.join(args['output_dir'], 'encoder.quant.onnx')
|
| 195 |
+
quantize_dynamic(model_fp32, model_quant, weight_type=QuantType.QUInt8)
|
| 196 |
+
print('\t\tExport onnx_encoder, done! see {}'.format(encoder_outpath))
|
| 197 |
+
|
| 198 |
+
print("\tStage-1.3: check onnx_encoder and torch_encoder")
|
| 199 |
+
torch_output = []
|
| 200 |
+
torch_chunk = copy.deepcopy(chunk)
|
| 201 |
+
torch_offset = copy.deepcopy(offset)
|
| 202 |
+
torch_required_cache_size = copy.deepcopy(required_cache_size)
|
| 203 |
+
torch_att_cache = copy.deepcopy(att_cache)
|
| 204 |
+
torch_cnn_cache = copy.deepcopy(cnn_cache)
|
| 205 |
+
torch_att_mask = copy.deepcopy(att_mask)
|
| 206 |
+
for i in range(10):
|
| 207 |
+
print("\t\ttorch chunk-{}: {}, offset: {}, att_cache: {},"
|
| 208 |
+
" cnn_cache: {}, att_mask: {}".format(
|
| 209 |
+
i, list(torch_chunk.size()), torch_offset,
|
| 210 |
+
list(torch_att_cache.size()), list(torch_cnn_cache.size()),
|
| 211 |
+
list(torch_att_mask.size())))
|
| 212 |
+
# NOTE(xsong): att_mask of the first few batches need changes if
|
| 213 |
+
# we use 16/4 mode.
|
| 214 |
+
if args['left_chunks'] > 0: # 16/4
|
| 215 |
+
torch_att_mask[:, :, -(args['chunk_size'] * (i + 1)):] = 1
|
| 216 |
+
out, torch_att_cache, torch_cnn_cache = encoder(
|
| 217 |
+
torch_chunk, torch_offset, torch_required_cache_size,
|
| 218 |
+
torch_att_cache, torch_cnn_cache, torch_att_mask)
|
| 219 |
+
torch_output.append(out)
|
| 220 |
+
torch_offset += out.size(1)
|
| 221 |
+
torch_output = torch.cat(torch_output, dim=1)
|
| 222 |
+
|
| 223 |
+
onnx_output = []
|
| 224 |
+
onnx_chunk = to_numpy(chunk)
|
| 225 |
+
onnx_offset = np.array((offset)).astype(np.int64)
|
| 226 |
+
onnx_required_cache_size = np.array((required_cache_size)).astype(np.int64)
|
| 227 |
+
onnx_att_cache = to_numpy(att_cache)
|
| 228 |
+
onnx_cnn_cache = to_numpy(cnn_cache)
|
| 229 |
+
onnx_att_mask = to_numpy(att_mask)
|
| 230 |
+
ort_session = onnxruntime.InferenceSession(
|
| 231 |
+
encoder_outpath, providers=['CPUExecutionProvider'])
|
| 232 |
+
input_names = [node.name for node in onnx_encoder.graph.input]
|
| 233 |
+
for i in range(10):
|
| 234 |
+
print("\t\tonnx chunk-{}: {}, offset: {}, att_cache: {},"
|
| 235 |
+
" cnn_cache: {}, att_mask: {}".format(i, onnx_chunk.shape,
|
| 236 |
+
onnx_offset,
|
| 237 |
+
onnx_att_cache.shape,
|
| 238 |
+
onnx_cnn_cache.shape,
|
| 239 |
+
onnx_att_mask.shape))
|
| 240 |
+
# NOTE(xsong): att_mask of the first few batches need changes if
|
| 241 |
+
# we use 16/4 mode.
|
| 242 |
+
if args['left_chunks'] > 0: # 16/4
|
| 243 |
+
onnx_att_mask[:, :, -(args['chunk_size'] * (i + 1)):] = 1
|
| 244 |
+
ort_inputs = {
|
| 245 |
+
'chunk': onnx_chunk,
|
| 246 |
+
'offset': onnx_offset,
|
| 247 |
+
'required_cache_size': onnx_required_cache_size,
|
| 248 |
+
'att_cache': onnx_att_cache,
|
| 249 |
+
'cnn_cache': onnx_cnn_cache,
|
| 250 |
+
'att_mask': onnx_att_mask
|
| 251 |
+
}
|
| 252 |
+
# NOTE(xcsong): If we use 16/-1, -1/-1 or 16/0 mode, `next_cache_start`
|
| 253 |
+
# will be hardcoded to 0 or chunk_size by ONNX, thus
|
| 254 |
+
# required_cache_size and att_mask are no more needed and they will
|
| 255 |
+
# be removed by ONNX automatically.
|
| 256 |
+
for k in list(ort_inputs):
|
| 257 |
+
if k not in input_names:
|
| 258 |
+
ort_inputs.pop(k)
|
| 259 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 260 |
+
onnx_att_cache, onnx_cnn_cache = ort_outs[1], ort_outs[2]
|
| 261 |
+
onnx_output.append(ort_outs[0])
|
| 262 |
+
onnx_offset += ort_outs[0].shape[1]
|
| 263 |
+
onnx_output = np.concatenate(onnx_output, axis=1)
|
| 264 |
+
|
| 265 |
+
np.testing.assert_allclose(to_numpy(torch_output),
|
| 266 |
+
onnx_output,
|
| 267 |
+
rtol=1e-03,
|
| 268 |
+
atol=1e-05)
|
| 269 |
+
meta = ort_session.get_modelmeta()
|
| 270 |
+
print("\t\tcustom_metadata_map={}".format(meta.custom_metadata_map))
|
| 271 |
+
print("\t\tCheck onnx_encoder, pass!")
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def export_ctc(asr_model, args):
|
| 275 |
+
print("Stage-2: export ctc")
|
| 276 |
+
ctc = asr_model.ctc
|
| 277 |
+
ctc.forward = ctc.log_softmax
|
| 278 |
+
ctc_outpath = os.path.join(args['output_dir'], 'ctc.onnx')
|
| 279 |
+
|
| 280 |
+
print("\tStage-2.1: prepare inputs for ctc")
|
| 281 |
+
hidden = torch.randn(
|
| 282 |
+
(args['batch'], args['chunk_size'] if args['chunk_size'] > 0 else 16,
|
| 283 |
+
args['output_size']))
|
| 284 |
+
|
| 285 |
+
print("\tStage-2.2: torch.onnx.export")
|
| 286 |
+
dynamic_axes = {'hidden': {1: 'T'}, 'probs': {1: 'T'}}
|
| 287 |
+
torch.onnx.export(ctc,
|
| 288 |
+
hidden,
|
| 289 |
+
ctc_outpath,
|
| 290 |
+
opset_version=13,
|
| 291 |
+
export_params=True,
|
| 292 |
+
do_constant_folding=True,
|
| 293 |
+
input_names=['hidden'],
|
| 294 |
+
output_names=['probs'],
|
| 295 |
+
dynamic_axes=dynamic_axes,
|
| 296 |
+
verbose=False)
|
| 297 |
+
onnx_ctc = onnx.load(ctc_outpath)
|
| 298 |
+
for (k, v) in args.items():
|
| 299 |
+
meta = onnx_ctc.metadata_props.add()
|
| 300 |
+
meta.key, meta.value = str(k), str(v)
|
| 301 |
+
onnx.checker.check_model(onnx_ctc)
|
| 302 |
+
onnx.helper.printable_graph(onnx_ctc.graph)
|
| 303 |
+
onnx.save(onnx_ctc, ctc_outpath)
|
| 304 |
+
print_input_output_info(onnx_ctc, "onnx_ctc")
|
| 305 |
+
# Dynamic quantization
|
| 306 |
+
model_fp32 = ctc_outpath
|
| 307 |
+
model_quant = os.path.join(args['output_dir'], 'ctc.quant.onnx')
|
| 308 |
+
quantize_dynamic(model_fp32, model_quant, weight_type=QuantType.QUInt8)
|
| 309 |
+
print('\t\tExport onnx_ctc, done! see {}'.format(ctc_outpath))
|
| 310 |
+
|
| 311 |
+
print("\tStage-2.3: check onnx_ctc and torch_ctc")
|
| 312 |
+
torch_output = ctc(hidden)
|
| 313 |
+
ort_session = onnxruntime.InferenceSession(
|
| 314 |
+
ctc_outpath, providers=['CPUExecutionProvider'])
|
| 315 |
+
onnx_output = ort_session.run(None, {'hidden': to_numpy(hidden)})
|
| 316 |
+
|
| 317 |
+
np.testing.assert_allclose(to_numpy(torch_output),
|
| 318 |
+
onnx_output[0],
|
| 319 |
+
rtol=1e-03,
|
| 320 |
+
atol=1e-05)
|
| 321 |
+
print("\t\tCheck onnx_ctc, pass!")
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def export_decoder(asr_model, args):
|
| 325 |
+
print("Stage-3: export decoder")
|
| 326 |
+
decoder = asr_model
|
| 327 |
+
# NOTE(lzhin): parameters of encoder will be automatically removed
|
| 328 |
+
# since they are not used during rescoring.
|
| 329 |
+
decoder.forward = decoder.forward_attention_decoder
|
| 330 |
+
decoder_outpath = os.path.join(args['output_dir'], 'decoder.onnx')
|
| 331 |
+
|
| 332 |
+
print("\tStage-3.1: prepare inputs for decoder")
|
| 333 |
+
# hardcode time->200 nbest->10 len->20, they are dynamic axes.
|
| 334 |
+
encoder_out = torch.randn((1, 200, args['output_size']))
|
| 335 |
+
hyps = torch.randint(low=0, high=args['vocab_size'], size=[10, 20])
|
| 336 |
+
hyps[:, 0] = args['vocab_size'] - 1 # <sos>
|
| 337 |
+
hyps_lens = torch.randint(low=15, high=21, size=[10])
|
| 338 |
+
|
| 339 |
+
print("\tStage-3.2: torch.onnx.export")
|
| 340 |
+
dynamic_axes = {
|
| 341 |
+
'hyps': {
|
| 342 |
+
0: 'NBEST',
|
| 343 |
+
1: 'L'
|
| 344 |
+
},
|
| 345 |
+
'hyps_lens': {
|
| 346 |
+
0: 'NBEST'
|
| 347 |
+
},
|
| 348 |
+
'encoder_out': {
|
| 349 |
+
1: 'T'
|
| 350 |
+
},
|
| 351 |
+
'score': {
|
| 352 |
+
0: 'NBEST',
|
| 353 |
+
1: 'L'
|
| 354 |
+
},
|
| 355 |
+
'r_score': {
|
| 356 |
+
0: 'NBEST',
|
| 357 |
+
1: 'L'
|
| 358 |
+
}
|
| 359 |
+
}
|
| 360 |
+
inputs = (hyps, hyps_lens, encoder_out, args['reverse_weight'])
|
| 361 |
+
torch.onnx.export(
|
| 362 |
+
decoder,
|
| 363 |
+
inputs,
|
| 364 |
+
decoder_outpath,
|
| 365 |
+
opset_version=13,
|
| 366 |
+
export_params=True,
|
| 367 |
+
do_constant_folding=True,
|
| 368 |
+
input_names=['hyps', 'hyps_lens', 'encoder_out', 'reverse_weight'],
|
| 369 |
+
output_names=['score', 'r_score'],
|
| 370 |
+
dynamic_axes=dynamic_axes,
|
| 371 |
+
verbose=False)
|
| 372 |
+
onnx_decoder = onnx.load(decoder_outpath)
|
| 373 |
+
for (k, v) in args.items():
|
| 374 |
+
meta = onnx_decoder.metadata_props.add()
|
| 375 |
+
meta.key, meta.value = str(k), str(v)
|
| 376 |
+
onnx.checker.check_model(onnx_decoder)
|
| 377 |
+
onnx.helper.printable_graph(onnx_decoder.graph)
|
| 378 |
+
onnx.save(onnx_decoder, decoder_outpath)
|
| 379 |
+
print_input_output_info(onnx_decoder, "onnx_decoder")
|
| 380 |
+
model_fp32 = decoder_outpath
|
| 381 |
+
model_quant = os.path.join(args['output_dir'], 'decoder.quant.onnx')
|
| 382 |
+
quantize_dynamic(model_fp32, model_quant, weight_type=QuantType.QUInt8)
|
| 383 |
+
print('\t\tExport onnx_decoder, done! see {}'.format(decoder_outpath))
|
| 384 |
+
|
| 385 |
+
print("\tStage-3.3: check onnx_decoder and torch_decoder")
|
| 386 |
+
torch_score, torch_r_score = decoder(hyps, hyps_lens, encoder_out,
|
| 387 |
+
args['reverse_weight'])
|
| 388 |
+
ort_session = onnxruntime.InferenceSession(
|
| 389 |
+
decoder_outpath, providers=['CPUExecutionProvider'])
|
| 390 |
+
input_names = [node.name for node in onnx_decoder.graph.input]
|
| 391 |
+
ort_inputs = {
|
| 392 |
+
'hyps': to_numpy(hyps),
|
| 393 |
+
'hyps_lens': to_numpy(hyps_lens),
|
| 394 |
+
'encoder_out': to_numpy(encoder_out),
|
| 395 |
+
'reverse_weight': np.array((args['reverse_weight'])),
|
| 396 |
+
}
|
| 397 |
+
for k in list(ort_inputs):
|
| 398 |
+
if k not in input_names:
|
| 399 |
+
ort_inputs.pop(k)
|
| 400 |
+
onnx_output = ort_session.run(None, ort_inputs)
|
| 401 |
+
|
| 402 |
+
np.testing.assert_allclose(to_numpy(torch_score),
|
| 403 |
+
onnx_output[0],
|
| 404 |
+
rtol=1e-03,
|
| 405 |
+
atol=1e-05)
|
| 406 |
+
if args['is_bidirectional_decoder'] and args['reverse_weight'] > 0.0:
|
| 407 |
+
np.testing.assert_allclose(to_numpy(torch_r_score),
|
| 408 |
+
onnx_output[1],
|
| 409 |
+
rtol=1e-03,
|
| 410 |
+
atol=1e-05)
|
| 411 |
+
print("\t\tCheck onnx_decoder, pass!")
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
def main():
|
| 415 |
+
torch.manual_seed(777)
|
| 416 |
+
args = get_args()
|
| 417 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 418 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 419 |
+
output_dir = args.output_dir
|
| 420 |
+
os.system("mkdir -p " + output_dir)
|
| 421 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'
|
| 422 |
+
|
| 423 |
+
with open(args.config, 'r') as fin:
|
| 424 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 425 |
+
|
| 426 |
+
model, configs = init_model(args, configs)
|
| 427 |
+
model.eval()
|
| 428 |
+
print(model)
|
| 429 |
+
|
| 430 |
+
arguments = {}
|
| 431 |
+
arguments['output_dir'] = output_dir
|
| 432 |
+
arguments['batch'] = 1
|
| 433 |
+
arguments['chunk_size'] = args.chunk_size
|
| 434 |
+
arguments['left_chunks'] = args.num_decoding_left_chunks
|
| 435 |
+
arguments['reverse_weight'] = args.reverse_weight
|
| 436 |
+
arguments['output_size'] = configs['encoder_conf']['output_size']
|
| 437 |
+
arguments['num_blocks'] = configs['encoder_conf']['num_blocks']
|
| 438 |
+
arguments['cnn_module_kernel'] = configs['encoder_conf'].get(
|
| 439 |
+
'cnn_module_kernel', 1)
|
| 440 |
+
arguments['head'] = configs['encoder_conf']['attention_heads']
|
| 441 |
+
arguments['feature_size'] = configs['input_dim']
|
| 442 |
+
arguments['vocab_size'] = configs['output_dim']
|
| 443 |
+
# NOTE(xcsong): if chunk_size == -1, hardcode to 67
|
| 444 |
+
arguments['decoding_window'] = (args.chunk_size - 1) * \
|
| 445 |
+
model.encoder.embed.subsampling_rate + \
|
| 446 |
+
model.encoder.embed.right_context + 1 if args.chunk_size > 0 else 67
|
| 447 |
+
arguments['encoder'] = configs['encoder']
|
| 448 |
+
arguments['decoder'] = configs['decoder']
|
| 449 |
+
arguments['subsampling_rate'] = model.subsampling_rate()
|
| 450 |
+
arguments['right_context'] = model.right_context()
|
| 451 |
+
arguments['sos_symbol'] = model.sos_symbol()
|
| 452 |
+
arguments['eos_symbol'] = model.eos_symbol()
|
| 453 |
+
arguments['is_bidirectional_decoder'] = 1 \
|
| 454 |
+
if model.is_bidirectional_decoder() else 0
|
| 455 |
+
|
| 456 |
+
# NOTE(xcsong): Please note that -1/-1 means non-streaming model! It is
|
| 457 |
+
# not a [16/4 16/-1 16/0] all-in-one model and it should not be used in
|
| 458 |
+
# streaming mode (i.e., setting chunk_size=16 in `decoder_main`). If you
|
| 459 |
+
# want to use 16/-1 or any other streaming mode in `decoder_main`,
|
| 460 |
+
# please export onnx in the same config.
|
| 461 |
+
if arguments['left_chunks'] > 0:
|
| 462 |
+
assert arguments['chunk_size'] > 0 # -1/4 not supported
|
| 463 |
+
|
| 464 |
+
export_encoder(model, arguments)
|
| 465 |
+
export_ctc(model, arguments)
|
| 466 |
+
export_decoder(model, arguments)
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
if __name__ == '__main__':
|
| 470 |
+
main()
|
wenet/bin/export_onnx_gpu.py
ADDED
|
@@ -0,0 +1,1266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import logging
|
| 19 |
+
import os
|
| 20 |
+
import sys
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
import torch.nn.functional as F
|
| 24 |
+
import yaml
|
| 25 |
+
from wenet.transformer.ctc import CTC
|
| 26 |
+
from wenet.transformer.decoder import TransformerDecoder
|
| 27 |
+
from wenet.transformer.encoder import BaseEncoder
|
| 28 |
+
from wenet.utils.init_model import init_model
|
| 29 |
+
from wenet.utils.mask import make_pad_mask
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
import onnxruntime
|
| 33 |
+
except ImportError:
|
| 34 |
+
print("Please install onnxruntime-gpu!")
|
| 35 |
+
sys.exit(1)
|
| 36 |
+
|
| 37 |
+
logger = logging.getLogger(__file__)
|
| 38 |
+
logger.setLevel(logging.INFO)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Encoder(torch.nn.Module):
|
| 42 |
+
|
| 43 |
+
def __init__(self, encoder: BaseEncoder, ctc: CTC, beam_size: int = 10):
|
| 44 |
+
super().__init__()
|
| 45 |
+
self.encoder = encoder
|
| 46 |
+
self.ctc = ctc
|
| 47 |
+
self.beam_size = beam_size
|
| 48 |
+
|
| 49 |
+
def forward(
|
| 50 |
+
self,
|
| 51 |
+
speech: torch.Tensor,
|
| 52 |
+
speech_lengths: torch.Tensor,
|
| 53 |
+
):
|
| 54 |
+
"""Encoder
|
| 55 |
+
Args:
|
| 56 |
+
speech: (Batch, Length, ...)
|
| 57 |
+
speech_lengths: (Batch, )
|
| 58 |
+
Returns:
|
| 59 |
+
encoder_out: B x T x F
|
| 60 |
+
encoder_out_lens: B
|
| 61 |
+
ctc_log_probs: B x T x V
|
| 62 |
+
beam_log_probs: B x T x beam_size
|
| 63 |
+
beam_log_probs_idx: B x T x beam_size
|
| 64 |
+
"""
|
| 65 |
+
encoder_out, encoder_mask = self.encoder(speech, speech_lengths, -1,
|
| 66 |
+
-1)
|
| 67 |
+
encoder_out_lens = encoder_mask.squeeze(1).sum(1)
|
| 68 |
+
ctc_log_probs = self.ctc.log_softmax(encoder_out)
|
| 69 |
+
encoder_out_lens = encoder_out_lens.int()
|
| 70 |
+
beam_log_probs, beam_log_probs_idx = torch.topk(ctc_log_probs,
|
| 71 |
+
self.beam_size,
|
| 72 |
+
dim=2)
|
| 73 |
+
return (
|
| 74 |
+
encoder_out,
|
| 75 |
+
encoder_out_lens,
|
| 76 |
+
ctc_log_probs,
|
| 77 |
+
beam_log_probs,
|
| 78 |
+
beam_log_probs_idx,
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class StreamingEncoder(torch.nn.Module):
|
| 83 |
+
|
| 84 |
+
def __init__(
|
| 85 |
+
self,
|
| 86 |
+
model,
|
| 87 |
+
required_cache_size,
|
| 88 |
+
beam_size,
|
| 89 |
+
transformer=False,
|
| 90 |
+
return_ctc_logprobs=False,
|
| 91 |
+
):
|
| 92 |
+
super().__init__()
|
| 93 |
+
self.ctc = model.ctc
|
| 94 |
+
self.subsampling_rate = model.encoder.embed.subsampling_rate
|
| 95 |
+
self.embed = model.encoder.embed
|
| 96 |
+
self.global_cmvn = model.encoder.global_cmvn
|
| 97 |
+
self.required_cache_size = required_cache_size
|
| 98 |
+
self.beam_size = beam_size
|
| 99 |
+
self.encoder = model.encoder
|
| 100 |
+
self.transformer = transformer
|
| 101 |
+
self.return_ctc_logprobs = return_ctc_logprobs
|
| 102 |
+
|
| 103 |
+
def forward(self, chunk_xs, chunk_lens, offset, att_cache, cnn_cache,
|
| 104 |
+
cache_mask):
|
| 105 |
+
"""Streaming Encoder
|
| 106 |
+
Args:
|
| 107 |
+
xs (torch.Tensor): chunk input, with shape (b, time, mel-dim),
|
| 108 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 109 |
+
subsample.right_context + 1`
|
| 110 |
+
offset (torch.Tensor): offset with shape (b, 1)
|
| 111 |
+
1 is retained for triton deployment
|
| 112 |
+
required_cache_size (int): cache size required for next chunk
|
| 113 |
+
compuation
|
| 114 |
+
> 0: actual cache size
|
| 115 |
+
<= 0: not allowed in streaming gpu encoder `
|
| 116 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 117 |
+
transformer/conformer attention, with shape
|
| 118 |
+
(b, elayers, head, cache_t1, d_k * 2), where
|
| 119 |
+
`head * d_k == hidden-dim` and
|
| 120 |
+
`cache_t1 == chunk_size * num_decoding_left_chunks`.
|
| 121 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 122 |
+
(b, elayers, b, hidden-dim, cache_t2), where
|
| 123 |
+
`cache_t2 == cnn.lorder - 1`
|
| 124 |
+
cache_mask: (torch.Tensor): cache mask with shape (b, required_cache_size)
|
| 125 |
+
in a batch of request, each request may have different
|
| 126 |
+
history cache. Cache mask is used to indidate the effective
|
| 127 |
+
cache for each request
|
| 128 |
+
Returns:
|
| 129 |
+
torch.Tensor: log probabilities of ctc output and cutoff by beam size
|
| 130 |
+
with shape (b, chunk_size, beam)
|
| 131 |
+
torch.Tensor: index of top beam size probabilities for each timestep
|
| 132 |
+
with shape (b, chunk_size, beam)
|
| 133 |
+
torch.Tensor: output of current input xs,
|
| 134 |
+
with shape (b, chunk_size, hidden-dim).
|
| 135 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 136 |
+
same shape (b, elayers, head, cache_t1, d_k * 2)
|
| 137 |
+
as the original att_cache
|
| 138 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 139 |
+
same shape as the original cnn_cache.
|
| 140 |
+
torch.Tensor: new cache mask, with same shape as the original
|
| 141 |
+
cache mask
|
| 142 |
+
"""
|
| 143 |
+
offset = offset.squeeze(1)
|
| 144 |
+
T = chunk_xs.size(1)
|
| 145 |
+
chunk_mask = ~make_pad_mask(chunk_lens, T).unsqueeze(1)
|
| 146 |
+
# B X 1 X T
|
| 147 |
+
chunk_mask = chunk_mask.to(chunk_xs.dtype)
|
| 148 |
+
# transpose batch & num_layers dim
|
| 149 |
+
att_cache = torch.transpose(att_cache, 0, 1)
|
| 150 |
+
cnn_cache = torch.transpose(cnn_cache, 0, 1)
|
| 151 |
+
|
| 152 |
+
# rewrite encoder.forward_chunk
|
| 153 |
+
# <---------forward_chunk START--------->
|
| 154 |
+
xs = self.global_cmvn(chunk_xs)
|
| 155 |
+
# chunk mask is important for batch inferencing since
|
| 156 |
+
# different sequence in a batch has different length
|
| 157 |
+
xs, pos_emb, chunk_mask = self.embed(xs, chunk_mask, offset)
|
| 158 |
+
cache_size = att_cache.size(3) # required cache size
|
| 159 |
+
masks = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 160 |
+
index = offset - cache_size
|
| 161 |
+
|
| 162 |
+
pos_emb = self.embed.position_encoding(index, cache_size + xs.size(1))
|
| 163 |
+
pos_emb = pos_emb.to(dtype=xs.dtype)
|
| 164 |
+
|
| 165 |
+
next_cache_start = -self.required_cache_size
|
| 166 |
+
r_cache_mask = masks[:, :, next_cache_start:]
|
| 167 |
+
|
| 168 |
+
r_att_cache = []
|
| 169 |
+
r_cnn_cache = []
|
| 170 |
+
for i, layer in enumerate(self.encoder.encoders):
|
| 171 |
+
i_kv_cache = att_cache[i]
|
| 172 |
+
size = att_cache.size(-1) // 2
|
| 173 |
+
kv_cache = (i_kv_cache[:, :, :, :size], i_kv_cache[:, :, :, size:])
|
| 174 |
+
xs, _, new_kv_cache, new_cnn_cache = layer(
|
| 175 |
+
xs,
|
| 176 |
+
masks,
|
| 177 |
+
pos_emb,
|
| 178 |
+
att_cache=kv_cache,
|
| 179 |
+
cnn_cache=cnn_cache[i],
|
| 180 |
+
)
|
| 181 |
+
# shape(new_att_cache) is (B, head, attention_key_size, d_k * 2),
|
| 182 |
+
# shape(new_cnn_cache) is (B, hidden-dim, cache_t2)
|
| 183 |
+
new_att_cache = torch.cat(new_kv_cache, dim=-1)
|
| 184 |
+
r_att_cache.append(
|
| 185 |
+
new_att_cache[:, :, next_cache_start:, :].unsqueeze(1))
|
| 186 |
+
if not self.transformer:
|
| 187 |
+
r_cnn_cache.append(new_cnn_cache.unsqueeze(1))
|
| 188 |
+
if self.encoder.normalize_before:
|
| 189 |
+
chunk_out = self.encoder.after_norm(xs)
|
| 190 |
+
else:
|
| 191 |
+
chunk_out = xs
|
| 192 |
+
|
| 193 |
+
r_att_cache = torch.cat(r_att_cache, dim=1) # concat on layers idx
|
| 194 |
+
if not self.transformer:
|
| 195 |
+
r_cnn_cache = torch.cat(r_cnn_cache, dim=1) # concat on layers
|
| 196 |
+
|
| 197 |
+
# <---------forward_chunk END--------->
|
| 198 |
+
|
| 199 |
+
log_ctc_probs = self.ctc.log_softmax(chunk_out)
|
| 200 |
+
log_probs, log_probs_idx = torch.topk(log_ctc_probs,
|
| 201 |
+
self.beam_size,
|
| 202 |
+
dim=2)
|
| 203 |
+
log_probs = log_probs.to(chunk_xs.dtype)
|
| 204 |
+
|
| 205 |
+
r_offset = offset + chunk_out.shape[1]
|
| 206 |
+
# the below ops not supported in Tensorrt
|
| 207 |
+
# chunk_out_lens = torch.div(chunk_lens, subsampling_rate,
|
| 208 |
+
# rounding_mode='floor')
|
| 209 |
+
chunk_out_lens = chunk_lens // self.subsampling_rate
|
| 210 |
+
r_offset = r_offset.unsqueeze(1)
|
| 211 |
+
if self.return_ctc_logprobs:
|
| 212 |
+
return (
|
| 213 |
+
log_ctc_probs,
|
| 214 |
+
chunk_out,
|
| 215 |
+
chunk_out_lens,
|
| 216 |
+
r_offset,
|
| 217 |
+
r_att_cache,
|
| 218 |
+
r_cnn_cache,
|
| 219 |
+
r_cache_mask,
|
| 220 |
+
)
|
| 221 |
+
else:
|
| 222 |
+
return (
|
| 223 |
+
log_probs,
|
| 224 |
+
log_probs_idx,
|
| 225 |
+
chunk_out,
|
| 226 |
+
chunk_out_lens,
|
| 227 |
+
r_offset,
|
| 228 |
+
r_att_cache,
|
| 229 |
+
r_cnn_cache,
|
| 230 |
+
r_cache_mask,
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
class StreamingSqueezeformerEncoder(torch.nn.Module):
|
| 235 |
+
|
| 236 |
+
def __init__(self, model, required_cache_size, beam_size):
|
| 237 |
+
super().__init__()
|
| 238 |
+
self.ctc = model.ctc
|
| 239 |
+
self.subsampling_rate = model.encoder.embed.subsampling_rate
|
| 240 |
+
self.embed = model.encoder.embed
|
| 241 |
+
self.global_cmvn = model.encoder.global_cmvn
|
| 242 |
+
self.required_cache_size = required_cache_size
|
| 243 |
+
self.beam_size = beam_size
|
| 244 |
+
self.encoder = model.encoder
|
| 245 |
+
self.reduce_idx = model.encoder.reduce_idx
|
| 246 |
+
self.recover_idx = model.encoder.recover_idx
|
| 247 |
+
if self.reduce_idx is None:
|
| 248 |
+
self.time_reduce = None
|
| 249 |
+
else:
|
| 250 |
+
if self.recover_idx is None:
|
| 251 |
+
self.time_reduce = "normal" # no recovery at the end
|
| 252 |
+
else:
|
| 253 |
+
self.time_reduce = "recover" # recovery at the end
|
| 254 |
+
assert len(self.reduce_idx) == len(self.recover_idx)
|
| 255 |
+
|
| 256 |
+
def calculate_downsampling_factor(self, i: int) -> int:
|
| 257 |
+
if self.reduce_idx is None:
|
| 258 |
+
return 1
|
| 259 |
+
else:
|
| 260 |
+
reduce_exp, recover_exp = 0, 0
|
| 261 |
+
for exp, rd_idx in enumerate(self.reduce_idx):
|
| 262 |
+
if i >= rd_idx:
|
| 263 |
+
reduce_exp = exp + 1
|
| 264 |
+
if self.recover_idx is not None:
|
| 265 |
+
for exp, rc_idx in enumerate(self.recover_idx):
|
| 266 |
+
if i >= rc_idx:
|
| 267 |
+
recover_exp = exp + 1
|
| 268 |
+
return int(2**(reduce_exp - recover_exp))
|
| 269 |
+
|
| 270 |
+
def forward(self, chunk_xs, chunk_lens, offset, att_cache, cnn_cache,
|
| 271 |
+
cache_mask):
|
| 272 |
+
"""Streaming Encoder
|
| 273 |
+
Args:
|
| 274 |
+
xs (torch.Tensor): chunk input, with shape (b, time, mel-dim),
|
| 275 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 276 |
+
subsample.right_context + 1`
|
| 277 |
+
offset (torch.Tensor): offset with shape (b, 1)
|
| 278 |
+
1 is retained for triton deployment
|
| 279 |
+
required_cache_size (int): cache size required for next chunk
|
| 280 |
+
compuation
|
| 281 |
+
> 0: actual cache size
|
| 282 |
+
<= 0: not allowed in streaming gpu encoder `
|
| 283 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 284 |
+
transformer/conformer attention, with shape
|
| 285 |
+
(b, elayers, head, cache_t1, d_k * 2), where
|
| 286 |
+
`head * d_k == hidden-dim` and
|
| 287 |
+
`cache_t1 == chunk_size * num_decoding_left_chunks`.
|
| 288 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 289 |
+
(b, elayers, b, hidden-dim, cache_t2), where
|
| 290 |
+
`cache_t2 == cnn.lorder - 1`
|
| 291 |
+
cache_mask: (torch.Tensor): cache mask with shape (b, required_cache_size)
|
| 292 |
+
in a batch of request, each request may have different
|
| 293 |
+
history cache. Cache mask is used to indidate the effective
|
| 294 |
+
cache for each request
|
| 295 |
+
Returns:
|
| 296 |
+
torch.Tensor: log probabilities of ctc output and cutoff by beam size
|
| 297 |
+
with shape (b, chunk_size, beam)
|
| 298 |
+
torch.Tensor: index of top beam size probabilities for each timestep
|
| 299 |
+
with shape (b, chunk_size, beam)
|
| 300 |
+
torch.Tensor: output of current input xs,
|
| 301 |
+
with shape (b, chunk_size, hidden-dim).
|
| 302 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 303 |
+
same shape (b, elayers, head, cache_t1, d_k * 2)
|
| 304 |
+
as the original att_cache
|
| 305 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 306 |
+
same shape as the original cnn_cache.
|
| 307 |
+
torch.Tensor: new cache mask, with same shape as the original
|
| 308 |
+
cache mask
|
| 309 |
+
"""
|
| 310 |
+
offset = offset.squeeze(1)
|
| 311 |
+
T = chunk_xs.size(1)
|
| 312 |
+
chunk_mask = ~make_pad_mask(chunk_lens, T).unsqueeze(1)
|
| 313 |
+
# B X 1 X T
|
| 314 |
+
chunk_mask = chunk_mask.to(chunk_xs.dtype)
|
| 315 |
+
# transpose batch & num_layers dim
|
| 316 |
+
att_cache = torch.transpose(att_cache, 0, 1)
|
| 317 |
+
cnn_cache = torch.transpose(cnn_cache, 0, 1)
|
| 318 |
+
|
| 319 |
+
# rewrite encoder.forward_chunk
|
| 320 |
+
# <---------forward_chunk START--------->
|
| 321 |
+
xs = self.global_cmvn(chunk_xs)
|
| 322 |
+
# chunk mask is important for batch inferencing since
|
| 323 |
+
# different sequence in a batch has different length
|
| 324 |
+
xs, pos_emb, chunk_mask = self.embed(xs, chunk_mask, offset)
|
| 325 |
+
elayers, cache_size = att_cache.size(0), att_cache.size(3)
|
| 326 |
+
att_mask = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 327 |
+
index = offset - cache_size
|
| 328 |
+
|
| 329 |
+
pos_emb = self.embed.position_encoding(index, cache_size + xs.size(1))
|
| 330 |
+
pos_emb = pos_emb.to(dtype=xs.dtype)
|
| 331 |
+
|
| 332 |
+
next_cache_start = -self.required_cache_size
|
| 333 |
+
r_cache_mask = att_mask[:, :, next_cache_start:]
|
| 334 |
+
|
| 335 |
+
r_att_cache = []
|
| 336 |
+
r_cnn_cache = []
|
| 337 |
+
mask_pad = torch.ones(1,
|
| 338 |
+
xs.size(1),
|
| 339 |
+
device=xs.device,
|
| 340 |
+
dtype=torch.bool)
|
| 341 |
+
mask_pad = mask_pad.unsqueeze(1)
|
| 342 |
+
max_att_len: int = 0
|
| 343 |
+
recover_activations: List[Tuple[torch.Tensor, torch.Tensor,
|
| 344 |
+
torch.Tensor, torch.Tensor]] = []
|
| 345 |
+
index = 0
|
| 346 |
+
xs_lens = torch.tensor([xs.size(1)], device=xs.device, dtype=torch.int)
|
| 347 |
+
xs = self.encoder.preln(xs)
|
| 348 |
+
for i, layer in enumerate(self.encoder.encoders):
|
| 349 |
+
if self.reduce_idx is not None:
|
| 350 |
+
if self.time_reduce is not None and i in self.reduce_idx:
|
| 351 |
+
recover_activations.append(
|
| 352 |
+
(xs, att_mask, pos_emb, mask_pad))
|
| 353 |
+
(
|
| 354 |
+
xs,
|
| 355 |
+
xs_lens,
|
| 356 |
+
att_mask,
|
| 357 |
+
mask_pad,
|
| 358 |
+
) = self.encoder.time_reduction_layer(
|
| 359 |
+
xs, xs_lens, att_mask, mask_pad)
|
| 360 |
+
pos_emb = pos_emb[:, ::2, :]
|
| 361 |
+
if self.encoder.pos_enc_layer_type == "rel_pos_repaired":
|
| 362 |
+
pos_emb = pos_emb[:, :xs.size(1) * 2 - 1, :]
|
| 363 |
+
index += 1
|
| 364 |
+
|
| 365 |
+
if self.recover_idx is not None:
|
| 366 |
+
if self.time_reduce == "recover" and i in self.recover_idx:
|
| 367 |
+
index -= 1
|
| 368 |
+
(
|
| 369 |
+
recover_tensor,
|
| 370 |
+
recover_att_mask,
|
| 371 |
+
recover_pos_emb,
|
| 372 |
+
recover_mask_pad,
|
| 373 |
+
) = recover_activations[index]
|
| 374 |
+
# recover output length for ctc decode
|
| 375 |
+
xs = xs.unsqueeze(2).repeat(1, 1, 2, 1).flatten(1, 2)
|
| 376 |
+
xs = self.encoder.time_recover_layer(xs)
|
| 377 |
+
recoverd_t = recover_tensor.size(1)
|
| 378 |
+
xs = recover_tensor + xs[:, :recoverd_t, :].contiguous()
|
| 379 |
+
att_mask = recover_att_mask
|
| 380 |
+
pos_emb = recover_pos_emb
|
| 381 |
+
mask_pad = recover_mask_pad
|
| 382 |
+
|
| 383 |
+
factor = self.calculate_downsampling_factor(i)
|
| 384 |
+
|
| 385 |
+
xs, _, new_att_cache, new_cnn_cache = layer(
|
| 386 |
+
xs,
|
| 387 |
+
att_mask,
|
| 388 |
+
pos_emb,
|
| 389 |
+
att_cache=att_cache[i][:, :, ::factor, :]
|
| 390 |
+
[:, :, :pos_emb.size(1) - xs.size(1), :]
|
| 391 |
+
if elayers > 0 else att_cache[:, :, ::factor, :],
|
| 392 |
+
cnn_cache=cnn_cache[i] if cnn_cache.size(0) > 0 else cnn_cache,
|
| 393 |
+
)
|
| 394 |
+
cached_att = new_att_cache[:, :, next_cache_start // factor:, :]
|
| 395 |
+
cached_cnn = new_cnn_cache.unsqueeze(1)
|
| 396 |
+
cached_att = (cached_att.unsqueeze(3).repeat(1, 1, 1, factor,
|
| 397 |
+
1).flatten(2, 3))
|
| 398 |
+
if i == 0:
|
| 399 |
+
# record length for the first block as max length
|
| 400 |
+
max_att_len = cached_att.size(2)
|
| 401 |
+
r_att_cache.append(cached_att[:, :, :max_att_len, :].unsqueeze(1))
|
| 402 |
+
r_cnn_cache.append(cached_cnn)
|
| 403 |
+
|
| 404 |
+
chunk_out = xs
|
| 405 |
+
r_att_cache = torch.cat(r_att_cache, dim=1) # concat on layers idx
|
| 406 |
+
r_cnn_cache = torch.cat(r_cnn_cache, dim=1) # concat on layers
|
| 407 |
+
|
| 408 |
+
# <---------forward_chunk END--------->
|
| 409 |
+
|
| 410 |
+
log_ctc_probs = self.ctc.log_softmax(chunk_out)
|
| 411 |
+
log_probs, log_probs_idx = torch.topk(log_ctc_probs,
|
| 412 |
+
self.beam_size,
|
| 413 |
+
dim=2)
|
| 414 |
+
log_probs = log_probs.to(chunk_xs.dtype)
|
| 415 |
+
|
| 416 |
+
r_offset = offset + chunk_out.shape[1]
|
| 417 |
+
# the below ops not supported in Tensorrt
|
| 418 |
+
# chunk_out_lens = torch.div(chunk_lens, subsampling_rate,
|
| 419 |
+
# rounding_mode='floor')
|
| 420 |
+
chunk_out_lens = chunk_lens // self.subsampling_rate
|
| 421 |
+
r_offset = r_offset.unsqueeze(1)
|
| 422 |
+
|
| 423 |
+
return (
|
| 424 |
+
log_probs,
|
| 425 |
+
log_probs_idx,
|
| 426 |
+
chunk_out,
|
| 427 |
+
chunk_out_lens,
|
| 428 |
+
r_offset,
|
| 429 |
+
r_att_cache,
|
| 430 |
+
r_cnn_cache,
|
| 431 |
+
r_cache_mask,
|
| 432 |
+
)
|
| 433 |
+
|
| 434 |
+
|
| 435 |
+
class StreamingEfficientConformerEncoder(torch.nn.Module):
|
| 436 |
+
|
| 437 |
+
def __init__(self, model, required_cache_size, beam_size):
|
| 438 |
+
super().__init__()
|
| 439 |
+
self.ctc = model.ctc
|
| 440 |
+
self.subsampling_rate = model.encoder.embed.subsampling_rate
|
| 441 |
+
self.embed = model.encoder.embed
|
| 442 |
+
self.global_cmvn = model.encoder.global_cmvn
|
| 443 |
+
self.required_cache_size = required_cache_size
|
| 444 |
+
self.beam_size = beam_size
|
| 445 |
+
self.encoder = model.encoder
|
| 446 |
+
|
| 447 |
+
# Efficient Conformer
|
| 448 |
+
self.stride_layer_idx = model.encoder.stride_layer_idx
|
| 449 |
+
self.stride = model.encoder.stride
|
| 450 |
+
self.num_blocks = model.encoder.num_blocks
|
| 451 |
+
self.cnn_module_kernel = model.encoder.cnn_module_kernel
|
| 452 |
+
|
| 453 |
+
def calculate_downsampling_factor(self, i: int) -> int:
|
| 454 |
+
factor = 1
|
| 455 |
+
for idx, stride_idx in enumerate(self.stride_layer_idx):
|
| 456 |
+
if i > stride_idx:
|
| 457 |
+
factor *= self.stride[idx]
|
| 458 |
+
return factor
|
| 459 |
+
|
| 460 |
+
def forward(self, chunk_xs, chunk_lens, offset, att_cache, cnn_cache,
|
| 461 |
+
cache_mask):
|
| 462 |
+
"""Streaming Encoder
|
| 463 |
+
Args:
|
| 464 |
+
chunk_xs (torch.Tensor): chunk input, with shape (b, time, mel-dim),
|
| 465 |
+
where `time == (chunk_size - 1) * subsample_rate + \
|
| 466 |
+
subsample.right_context + 1`
|
| 467 |
+
chunk_lens (torch.Tensor):
|
| 468 |
+
offset (torch.Tensor): offset with shape (b, 1)
|
| 469 |
+
1 is retained for triton deployment
|
| 470 |
+
att_cache (torch.Tensor): cache tensor for KEY & VALUE in
|
| 471 |
+
transformer/conformer attention, with shape
|
| 472 |
+
(b, elayers, head, cache_t1, d_k * 2), where
|
| 473 |
+
`head * d_k == hidden-dim` and
|
| 474 |
+
`cache_t1 == chunk_size * num_decoding_left_chunks`.
|
| 475 |
+
cnn_cache (torch.Tensor): cache tensor for cnn_module in conformer,
|
| 476 |
+
(b, elayers, hidden-dim, cache_t2), where
|
| 477 |
+
`cache_t2 == cnn.lorder - 1`
|
| 478 |
+
cache_mask: (torch.Tensor): cache mask with shape (b, required_cache_size)
|
| 479 |
+
in a batch of request, each request may have different
|
| 480 |
+
history cache. Cache mask is used to indidate the effective
|
| 481 |
+
cache for each request
|
| 482 |
+
Returns:
|
| 483 |
+
torch.Tensor: log probabilities of ctc output and cutoff by beam size
|
| 484 |
+
with shape (b, chunk_size, beam)
|
| 485 |
+
torch.Tensor: index of top beam size probabilities for each timestep
|
| 486 |
+
with shape (b, chunk_size, beam)
|
| 487 |
+
torch.Tensor: output of current input xs,
|
| 488 |
+
with shape (b, chunk_size, hidden-dim).
|
| 489 |
+
torch.Tensor: new attention cache required for next chunk, with
|
| 490 |
+
same shape (b, elayers, head, cache_t1, d_k * 2)
|
| 491 |
+
as the original att_cache
|
| 492 |
+
torch.Tensor: new conformer cnn cache required for next chunk, with
|
| 493 |
+
same shape as the original cnn_cache.
|
| 494 |
+
torch.Tensor: new cache mask, with same shape as the original
|
| 495 |
+
cache mask
|
| 496 |
+
"""
|
| 497 |
+
offset = offset.squeeze(1) # (b, )
|
| 498 |
+
offset *= self.calculate_downsampling_factor(self.num_blocks + 1)
|
| 499 |
+
|
| 500 |
+
T = chunk_xs.size(1)
|
| 501 |
+
chunk_mask = ~make_pad_mask(chunk_lens, T).unsqueeze(1) # (b, 1, T)
|
| 502 |
+
# B X 1 X T
|
| 503 |
+
chunk_mask = chunk_mask.to(chunk_xs.dtype)
|
| 504 |
+
# transpose batch & num_layers dim
|
| 505 |
+
# Shape(att_cache): (elayers, b, head, cache_t1, d_k * 2)
|
| 506 |
+
# Shape(cnn_cache): (elayers, b, outsize, cnn_kernel)
|
| 507 |
+
att_cache = torch.transpose(att_cache, 0, 1)
|
| 508 |
+
cnn_cache = torch.transpose(cnn_cache, 0, 1)
|
| 509 |
+
|
| 510 |
+
# rewrite encoder.forward_chunk
|
| 511 |
+
# <---------forward_chunk START--------->
|
| 512 |
+
xs = self.global_cmvn(chunk_xs)
|
| 513 |
+
# chunk mask is important for batch inferencing since
|
| 514 |
+
# different sequence in a batch has different length
|
| 515 |
+
xs, pos_emb, chunk_mask = self.embed(xs, chunk_mask, offset)
|
| 516 |
+
cache_size = att_cache.size(3) # required cache size
|
| 517 |
+
masks = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 518 |
+
att_mask = torch.cat((cache_mask, chunk_mask), dim=2)
|
| 519 |
+
index = offset - cache_size
|
| 520 |
+
|
| 521 |
+
pos_emb = self.embed.position_encoding(index, cache_size + xs.size(1))
|
| 522 |
+
pos_emb = pos_emb.to(dtype=xs.dtype)
|
| 523 |
+
|
| 524 |
+
next_cache_start = -self.required_cache_size
|
| 525 |
+
r_cache_mask = masks[:, :, next_cache_start:]
|
| 526 |
+
|
| 527 |
+
r_att_cache = []
|
| 528 |
+
r_cnn_cache = []
|
| 529 |
+
mask_pad = chunk_mask.to(torch.bool)
|
| 530 |
+
max_att_len, max_cnn_len = (
|
| 531 |
+
0,
|
| 532 |
+
0,
|
| 533 |
+
) # for repeat_interleave of new_att_cache
|
| 534 |
+
for i, layer in enumerate(self.encoder.encoders):
|
| 535 |
+
factor = self.calculate_downsampling_factor(i)
|
| 536 |
+
# NOTE(xcsong): Before layer.forward
|
| 537 |
+
# shape(att_cache[i:i + 1]) is (b, head, cache_t1, d_k * 2),
|
| 538 |
+
# shape(cnn_cache[i]) is (b=1, hidden-dim, cache_t2)
|
| 539 |
+
# shape(new_att_cache) = [ batch, head, time2, outdim//head * 2 ]
|
| 540 |
+
att_cache_trunc = 0
|
| 541 |
+
if xs.size(1) + att_cache.size(3) / factor > pos_emb.size(1):
|
| 542 |
+
# The time step is not divisible by the downsampling multiple
|
| 543 |
+
# We propose to double the chunk_size.
|
| 544 |
+
att_cache_trunc = (xs.size(1) + att_cache.size(3) // factor -
|
| 545 |
+
pos_emb.size(1) + 1)
|
| 546 |
+
xs, _, new_att_cache, new_cnn_cache = layer(
|
| 547 |
+
xs,
|
| 548 |
+
att_mask,
|
| 549 |
+
pos_emb,
|
| 550 |
+
mask_pad=mask_pad,
|
| 551 |
+
att_cache=att_cache[i][:, :, ::factor, :][:, :,
|
| 552 |
+
att_cache_trunc:, :],
|
| 553 |
+
cnn_cache=cnn_cache[i, :, :, :]
|
| 554 |
+
if cnn_cache.size(0) > 0 else cnn_cache,
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
if i in self.stride_layer_idx:
|
| 558 |
+
# compute time dimension for next block
|
| 559 |
+
efficient_index = self.stride_layer_idx.index(i)
|
| 560 |
+
att_mask = att_mask[:, ::self.stride[efficient_index], ::self.
|
| 561 |
+
stride[efficient_index], ]
|
| 562 |
+
mask_pad = mask_pad[:, ::self.stride[efficient_index], ::self.
|
| 563 |
+
stride[efficient_index], ]
|
| 564 |
+
pos_emb = pos_emb[:, ::self.stride[efficient_index], :]
|
| 565 |
+
|
| 566 |
+
# shape(new_att_cache) = [batch, head, time2, outdim]
|
| 567 |
+
new_att_cache = new_att_cache[:, :, next_cache_start // factor:, :]
|
| 568 |
+
# shape(new_cnn_cache) = [batch, 1, outdim, cache_t2]
|
| 569 |
+
new_cnn_cache = new_cnn_cache.unsqueeze(1) # shape(1):layerID
|
| 570 |
+
|
| 571 |
+
# use repeat_interleave to new_att_cache
|
| 572 |
+
# new_att_cache = new_att_cache.repeat_interleave(repeats=factor, dim=2)
|
| 573 |
+
new_att_cache = (new_att_cache.unsqueeze(3).repeat(
|
| 574 |
+
1, 1, 1, factor, 1).flatten(2, 3))
|
| 575 |
+
# padding new_cnn_cache to cnn.lorder for casual convolution
|
| 576 |
+
new_cnn_cache = F.pad(
|
| 577 |
+
new_cnn_cache,
|
| 578 |
+
(self.cnn_module_kernel - 1 - new_cnn_cache.size(3), 0),
|
| 579 |
+
)
|
| 580 |
+
|
| 581 |
+
if i == 0:
|
| 582 |
+
# record length for the first block as max length
|
| 583 |
+
max_att_len = new_att_cache.size(2)
|
| 584 |
+
max_cnn_len = new_cnn_cache.size(3)
|
| 585 |
+
|
| 586 |
+
# update real shape of att_cache and cnn_cache
|
| 587 |
+
r_att_cache.append(new_att_cache[:, :,
|
| 588 |
+
-max_att_len:, :].unsqueeze(1))
|
| 589 |
+
r_cnn_cache.append(new_cnn_cache[:, :, :, -max_cnn_len:])
|
| 590 |
+
|
| 591 |
+
if self.encoder.normalize_before:
|
| 592 |
+
chunk_out = self.encoder.after_norm(xs)
|
| 593 |
+
else:
|
| 594 |
+
chunk_out = xs
|
| 595 |
+
|
| 596 |
+
# shape of r_att_cache: (b, elayers, head, time2, outdim)
|
| 597 |
+
r_att_cache = torch.cat(r_att_cache, dim=1) # concat on layers idx
|
| 598 |
+
# shape of r_cnn_cache: (b, elayers, outdim, cache_t2)
|
| 599 |
+
r_cnn_cache = torch.cat(r_cnn_cache, dim=1) # concat on layers
|
| 600 |
+
|
| 601 |
+
# <---------forward_chunk END--------->
|
| 602 |
+
|
| 603 |
+
log_ctc_probs = self.ctc.log_softmax(chunk_out)
|
| 604 |
+
log_probs, log_probs_idx = torch.topk(log_ctc_probs,
|
| 605 |
+
self.beam_size,
|
| 606 |
+
dim=2)
|
| 607 |
+
log_probs = log_probs.to(chunk_xs.dtype)
|
| 608 |
+
|
| 609 |
+
r_offset = offset + chunk_out.shape[1]
|
| 610 |
+
# the below ops not supported in Tensorrt
|
| 611 |
+
# chunk_out_lens = torch.div(chunk_lens, subsampling_rate,
|
| 612 |
+
# rounding_mode='floor')
|
| 613 |
+
chunk_out_lens = (
|
| 614 |
+
chunk_lens // self.subsampling_rate //
|
| 615 |
+
self.calculate_downsampling_factor(self.num_blocks + 1))
|
| 616 |
+
chunk_out_lens += 1
|
| 617 |
+
r_offset = r_offset.unsqueeze(1)
|
| 618 |
+
|
| 619 |
+
return (
|
| 620 |
+
log_probs,
|
| 621 |
+
log_probs_idx,
|
| 622 |
+
chunk_out,
|
| 623 |
+
chunk_out_lens,
|
| 624 |
+
r_offset,
|
| 625 |
+
r_att_cache,
|
| 626 |
+
r_cnn_cache,
|
| 627 |
+
r_cache_mask,
|
| 628 |
+
)
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
class Decoder(torch.nn.Module):
|
| 632 |
+
|
| 633 |
+
def __init__(
|
| 634 |
+
self,
|
| 635 |
+
decoder: TransformerDecoder,
|
| 636 |
+
ctc_weight: float = 0.5,
|
| 637 |
+
reverse_weight: float = 0.0,
|
| 638 |
+
beam_size: int = 10,
|
| 639 |
+
decoder_fastertransformer: bool = False,
|
| 640 |
+
):
|
| 641 |
+
super().__init__()
|
| 642 |
+
self.decoder = decoder
|
| 643 |
+
self.ctc_weight = ctc_weight
|
| 644 |
+
self.reverse_weight = reverse_weight
|
| 645 |
+
self.beam_size = beam_size
|
| 646 |
+
self.decoder_fastertransformer = decoder_fastertransformer
|
| 647 |
+
|
| 648 |
+
def forward(
|
| 649 |
+
self,
|
| 650 |
+
encoder_out: torch.Tensor,
|
| 651 |
+
encoder_lens: torch.Tensor,
|
| 652 |
+
hyps_pad_sos_eos: torch.Tensor,
|
| 653 |
+
hyps_lens_sos: torch.Tensor,
|
| 654 |
+
r_hyps_pad_sos_eos: torch.Tensor,
|
| 655 |
+
ctc_score: torch.Tensor,
|
| 656 |
+
):
|
| 657 |
+
"""Encoder
|
| 658 |
+
Args:
|
| 659 |
+
encoder_out: B x T x F
|
| 660 |
+
encoder_lens: B
|
| 661 |
+
hyps_pad_sos_eos: B x beam x (T2+1),
|
| 662 |
+
hyps with sos & eos and padded by ignore id
|
| 663 |
+
hyps_lens_sos: B x beam, length for each hyp with sos
|
| 664 |
+
r_hyps_pad_sos_eos: B x beam x (T2+1),
|
| 665 |
+
reversed hyps with sos & eos and padded by ignore id
|
| 666 |
+
ctc_score: B x beam, ctc score for each hyp
|
| 667 |
+
Returns:
|
| 668 |
+
decoder_out: B x beam x T2 x V
|
| 669 |
+
r_decoder_out: B x beam x T2 x V
|
| 670 |
+
best_index: B
|
| 671 |
+
"""
|
| 672 |
+
B, T, F = encoder_out.shape
|
| 673 |
+
bz = self.beam_size
|
| 674 |
+
B2 = B * bz
|
| 675 |
+
encoder_out = encoder_out.repeat(1, bz, 1).view(B2, T, F)
|
| 676 |
+
encoder_mask = ~make_pad_mask(encoder_lens, T).unsqueeze(1)
|
| 677 |
+
encoder_mask = encoder_mask.repeat(1, bz, 1).view(B2, 1, T)
|
| 678 |
+
T2 = hyps_pad_sos_eos.shape[2] - 1
|
| 679 |
+
hyps_pad = hyps_pad_sos_eos.view(B2, T2 + 1)
|
| 680 |
+
hyps_lens = hyps_lens_sos.view(B2, )
|
| 681 |
+
hyps_pad_sos = hyps_pad[:, :-1].contiguous()
|
| 682 |
+
hyps_pad_eos = hyps_pad[:, 1:].contiguous()
|
| 683 |
+
|
| 684 |
+
r_hyps_pad = r_hyps_pad_sos_eos.view(B2, T2 + 1)
|
| 685 |
+
r_hyps_pad_sos = r_hyps_pad[:, :-1].contiguous()
|
| 686 |
+
r_hyps_pad_eos = r_hyps_pad[:, 1:].contiguous()
|
| 687 |
+
|
| 688 |
+
decoder_out, r_decoder_out, _ = self.decoder(
|
| 689 |
+
encoder_out,
|
| 690 |
+
encoder_mask,
|
| 691 |
+
hyps_pad_sos,
|
| 692 |
+
hyps_lens,
|
| 693 |
+
r_hyps_pad_sos,
|
| 694 |
+
self.reverse_weight,
|
| 695 |
+
)
|
| 696 |
+
decoder_out = torch.nn.functional.log_softmax(decoder_out, dim=-1)
|
| 697 |
+
V = decoder_out.shape[-1]
|
| 698 |
+
decoder_out = decoder_out.view(B2, T2, V)
|
| 699 |
+
mask = ~make_pad_mask(hyps_lens, T2) # B2 x T2
|
| 700 |
+
# mask index, remove ignore id
|
| 701 |
+
index = torch.unsqueeze(hyps_pad_eos * mask, 2)
|
| 702 |
+
score = decoder_out.gather(2, index).squeeze(2) # B2 X T2
|
| 703 |
+
# mask padded part
|
| 704 |
+
score = score * mask
|
| 705 |
+
decoder_out = decoder_out.view(B, bz, T2, V)
|
| 706 |
+
if self.reverse_weight > 0:
|
| 707 |
+
r_decoder_out = torch.nn.functional.log_softmax(r_decoder_out,
|
| 708 |
+
dim=-1)
|
| 709 |
+
r_decoder_out = r_decoder_out.view(B2, T2, V)
|
| 710 |
+
index = torch.unsqueeze(r_hyps_pad_eos * mask, 2)
|
| 711 |
+
r_score = r_decoder_out.gather(2, index).squeeze(2)
|
| 712 |
+
r_score = r_score * mask
|
| 713 |
+
score = (score * (1 - self.reverse_weight) +
|
| 714 |
+
self.reverse_weight * r_score)
|
| 715 |
+
r_decoder_out = r_decoder_out.view(B, bz, T2, V)
|
| 716 |
+
score = torch.sum(score, axis=1) # B2
|
| 717 |
+
score = torch.reshape(score, (B, bz)) + self.ctc_weight * ctc_score
|
| 718 |
+
best_index = torch.argmax(score, dim=1)
|
| 719 |
+
if self.decoder_fastertransformer:
|
| 720 |
+
return decoder_out, best_index
|
| 721 |
+
else:
|
| 722 |
+
return best_index
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
def to_numpy(tensors):
|
| 726 |
+
out = []
|
| 727 |
+
if type(tensors) == torch.tensor:
|
| 728 |
+
tensors = [tensors]
|
| 729 |
+
for tensor in tensors:
|
| 730 |
+
if tensor.requires_grad:
|
| 731 |
+
tensor = tensor.detach().cpu().numpy()
|
| 732 |
+
else:
|
| 733 |
+
tensor = tensor.cpu().numpy()
|
| 734 |
+
out.append(tensor)
|
| 735 |
+
return out
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
def test(xlist, blist, rtol=1e-3, atol=1e-5, tolerate_small_mismatch=True):
|
| 739 |
+
for a, b in zip(xlist, blist):
|
| 740 |
+
try:
|
| 741 |
+
torch.testing.assert_allclose(a, b, rtol=rtol, atol=atol)
|
| 742 |
+
except AssertionError as error:
|
| 743 |
+
if tolerate_small_mismatch:
|
| 744 |
+
print(error)
|
| 745 |
+
else:
|
| 746 |
+
raise
|
| 747 |
+
|
| 748 |
+
|
| 749 |
+
def export_offline_encoder(model, configs, args, logger, encoder_onnx_path):
|
| 750 |
+
bz = 32
|
| 751 |
+
seq_len = 100
|
| 752 |
+
beam_size = args.beam_size
|
| 753 |
+
feature_size = configs["input_dim"]
|
| 754 |
+
|
| 755 |
+
speech = torch.randn(bz, seq_len, feature_size, dtype=torch.float32)
|
| 756 |
+
speech_lens = torch.randint(low=10,
|
| 757 |
+
high=seq_len,
|
| 758 |
+
size=(bz, ),
|
| 759 |
+
dtype=torch.int32)
|
| 760 |
+
encoder = Encoder(model.encoder, model.ctc, beam_size)
|
| 761 |
+
encoder.eval()
|
| 762 |
+
|
| 763 |
+
torch.onnx.export(
|
| 764 |
+
encoder,
|
| 765 |
+
(speech, speech_lens),
|
| 766 |
+
encoder_onnx_path,
|
| 767 |
+
export_params=True,
|
| 768 |
+
opset_version=13,
|
| 769 |
+
do_constant_folding=True,
|
| 770 |
+
input_names=["speech", "speech_lengths"],
|
| 771 |
+
output_names=[
|
| 772 |
+
"encoder_out",
|
| 773 |
+
"encoder_out_lens",
|
| 774 |
+
"ctc_log_probs",
|
| 775 |
+
"beam_log_probs",
|
| 776 |
+
"beam_log_probs_idx",
|
| 777 |
+
],
|
| 778 |
+
dynamic_axes={
|
| 779 |
+
"speech": {
|
| 780 |
+
0: "B",
|
| 781 |
+
1: "T"
|
| 782 |
+
},
|
| 783 |
+
"speech_lengths": {
|
| 784 |
+
0: "B"
|
| 785 |
+
},
|
| 786 |
+
"encoder_out": {
|
| 787 |
+
0: "B",
|
| 788 |
+
1: "T_OUT"
|
| 789 |
+
},
|
| 790 |
+
"encoder_out_lens": {
|
| 791 |
+
0: "B"
|
| 792 |
+
},
|
| 793 |
+
"ctc_log_probs": {
|
| 794 |
+
0: "B",
|
| 795 |
+
1: "T_OUT"
|
| 796 |
+
},
|
| 797 |
+
"beam_log_probs": {
|
| 798 |
+
0: "B",
|
| 799 |
+
1: "T_OUT"
|
| 800 |
+
},
|
| 801 |
+
"beam_log_probs_idx": {
|
| 802 |
+
0: "B",
|
| 803 |
+
1: "T_OUT"
|
| 804 |
+
},
|
| 805 |
+
},
|
| 806 |
+
verbose=False,
|
| 807 |
+
)
|
| 808 |
+
|
| 809 |
+
with torch.no_grad():
|
| 810 |
+
o0, o1, o2, o3, o4 = encoder(speech, speech_lens)
|
| 811 |
+
|
| 812 |
+
providers = ["CUDAExecutionProvider"]
|
| 813 |
+
ort_session = onnxruntime.InferenceSession(encoder_onnx_path,
|
| 814 |
+
providers=providers)
|
| 815 |
+
ort_inputs = {
|
| 816 |
+
"speech": to_numpy(speech),
|
| 817 |
+
"speech_lengths": to_numpy(speech_lens),
|
| 818 |
+
}
|
| 819 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 820 |
+
|
| 821 |
+
# check encoder output
|
| 822 |
+
test(to_numpy([o0, o1, o2, o3, o4]), ort_outs)
|
| 823 |
+
logger.info("export offline onnx encoder succeed!")
|
| 824 |
+
onnx_config = {
|
| 825 |
+
"beam_size": args.beam_size,
|
| 826 |
+
"reverse_weight": args.reverse_weight,
|
| 827 |
+
"ctc_weight": args.ctc_weight,
|
| 828 |
+
"fp16": args.fp16,
|
| 829 |
+
}
|
| 830 |
+
return onnx_config
|
| 831 |
+
|
| 832 |
+
|
| 833 |
+
def export_online_encoder(model, configs, args, logger, encoder_onnx_path):
|
| 834 |
+
decoding_chunk_size = args.decoding_chunk_size
|
| 835 |
+
subsampling = model.encoder.embed.subsampling_rate
|
| 836 |
+
context = model.encoder.embed.right_context + 1
|
| 837 |
+
decoding_window = (decoding_chunk_size - 1) * subsampling + context
|
| 838 |
+
batch_size = 32
|
| 839 |
+
audio_len = decoding_window
|
| 840 |
+
feature_size = configs["input_dim"]
|
| 841 |
+
output_size = configs["encoder_conf"]["output_size"]
|
| 842 |
+
num_layers = configs["encoder_conf"]["num_blocks"]
|
| 843 |
+
# in transformer the cnn module will not be available
|
| 844 |
+
transformer = False
|
| 845 |
+
cnn_module_kernel = configs["encoder_conf"].get("cnn_module_kernel", 1) - 1
|
| 846 |
+
if not cnn_module_kernel:
|
| 847 |
+
transformer = True
|
| 848 |
+
num_decoding_left_chunks = args.num_decoding_left_chunks
|
| 849 |
+
required_cache_size = decoding_chunk_size * num_decoding_left_chunks
|
| 850 |
+
if configs["encoder"] == "squeezeformer":
|
| 851 |
+
encoder = StreamingSqueezeformerEncoder(model, required_cache_size,
|
| 852 |
+
args.beam_size)
|
| 853 |
+
elif configs["encoder"] == "efficientConformer":
|
| 854 |
+
encoder = StreamingEfficientConformerEncoder(model,
|
| 855 |
+
required_cache_size,
|
| 856 |
+
args.beam_size)
|
| 857 |
+
else:
|
| 858 |
+
encoder = StreamingEncoder(
|
| 859 |
+
model,
|
| 860 |
+
required_cache_size,
|
| 861 |
+
args.beam_size,
|
| 862 |
+
transformer,
|
| 863 |
+
args.return_ctc_logprobs,
|
| 864 |
+
)
|
| 865 |
+
encoder.eval()
|
| 866 |
+
|
| 867 |
+
# begin to export encoder
|
| 868 |
+
chunk_xs = torch.randn(batch_size,
|
| 869 |
+
audio_len,
|
| 870 |
+
feature_size,
|
| 871 |
+
dtype=torch.float32)
|
| 872 |
+
chunk_lens = torch.ones(batch_size, dtype=torch.int32) * audio_len
|
| 873 |
+
|
| 874 |
+
offset = torch.arange(0, batch_size).unsqueeze(1)
|
| 875 |
+
# (elayers, b, head, cache_t1, d_k * 2)
|
| 876 |
+
head = configs["encoder_conf"]["attention_heads"]
|
| 877 |
+
d_k = configs["encoder_conf"]["output_size"] // head
|
| 878 |
+
att_cache = torch.randn(
|
| 879 |
+
batch_size,
|
| 880 |
+
num_layers,
|
| 881 |
+
head,
|
| 882 |
+
required_cache_size,
|
| 883 |
+
d_k * 2,
|
| 884 |
+
dtype=torch.float32,
|
| 885 |
+
)
|
| 886 |
+
cnn_cache = torch.randn(
|
| 887 |
+
batch_size,
|
| 888 |
+
num_layers,
|
| 889 |
+
output_size,
|
| 890 |
+
cnn_module_kernel,
|
| 891 |
+
dtype=torch.float32,
|
| 892 |
+
)
|
| 893 |
+
|
| 894 |
+
cache_mask = torch.ones(batch_size,
|
| 895 |
+
1,
|
| 896 |
+
required_cache_size,
|
| 897 |
+
dtype=torch.float32)
|
| 898 |
+
input_names = [
|
| 899 |
+
"chunk_xs",
|
| 900 |
+
"chunk_lens",
|
| 901 |
+
"offset",
|
| 902 |
+
"att_cache",
|
| 903 |
+
"cnn_cache",
|
| 904 |
+
"cache_mask",
|
| 905 |
+
]
|
| 906 |
+
output_names = [
|
| 907 |
+
"log_probs",
|
| 908 |
+
"log_probs_idx",
|
| 909 |
+
"chunk_out",
|
| 910 |
+
"chunk_out_lens",
|
| 911 |
+
"r_offset",
|
| 912 |
+
"r_att_cache",
|
| 913 |
+
"r_cnn_cache",
|
| 914 |
+
"r_cache_mask",
|
| 915 |
+
]
|
| 916 |
+
if args.return_ctc_logprobs:
|
| 917 |
+
output_names = [
|
| 918 |
+
"ctc_log_probs",
|
| 919 |
+
"chunk_out",
|
| 920 |
+
"chunk_out_lens",
|
| 921 |
+
"r_offset",
|
| 922 |
+
"r_att_cache",
|
| 923 |
+
"r_cnn_cache",
|
| 924 |
+
"r_cache_mask",
|
| 925 |
+
]
|
| 926 |
+
input_tensors = (
|
| 927 |
+
chunk_xs,
|
| 928 |
+
chunk_lens,
|
| 929 |
+
offset,
|
| 930 |
+
att_cache,
|
| 931 |
+
cnn_cache,
|
| 932 |
+
cache_mask,
|
| 933 |
+
)
|
| 934 |
+
if transformer:
|
| 935 |
+
assert (args.return_ctc_logprobs is
|
| 936 |
+
False), "return_ctc_logprobs is not supported in transformer"
|
| 937 |
+
output_names.pop(6)
|
| 938 |
+
|
| 939 |
+
all_names = input_names + output_names
|
| 940 |
+
dynamic_axes = {}
|
| 941 |
+
for name in all_names:
|
| 942 |
+
# only the first dimension is dynamic
|
| 943 |
+
# all other dimension is fixed
|
| 944 |
+
dynamic_axes[name] = {0: "B"}
|
| 945 |
+
|
| 946 |
+
torch.onnx.export(
|
| 947 |
+
encoder,
|
| 948 |
+
input_tensors,
|
| 949 |
+
encoder_onnx_path,
|
| 950 |
+
export_params=True,
|
| 951 |
+
opset_version=14,
|
| 952 |
+
do_constant_folding=True,
|
| 953 |
+
input_names=input_names,
|
| 954 |
+
output_names=output_names,
|
| 955 |
+
dynamic_axes=dynamic_axes,
|
| 956 |
+
verbose=False,
|
| 957 |
+
)
|
| 958 |
+
|
| 959 |
+
with torch.no_grad():
|
| 960 |
+
torch_outs = encoder(chunk_xs, chunk_lens, offset, att_cache,
|
| 961 |
+
cnn_cache, cache_mask)
|
| 962 |
+
if transformer:
|
| 963 |
+
torch_outs = list(torch_outs).pop(6)
|
| 964 |
+
ort_session = onnxruntime.InferenceSession(
|
| 965 |
+
encoder_onnx_path, providers=["CUDAExecutionProvider"])
|
| 966 |
+
ort_inputs = {}
|
| 967 |
+
|
| 968 |
+
input_tensors = to_numpy(input_tensors)
|
| 969 |
+
for idx, name in enumerate(input_names):
|
| 970 |
+
ort_inputs[name] = input_tensors[idx]
|
| 971 |
+
if transformer:
|
| 972 |
+
del ort_inputs["cnn_cache"]
|
| 973 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 974 |
+
test(to_numpy(torch_outs), ort_outs, rtol=1e-03, atol=1e-05)
|
| 975 |
+
logger.info("export to onnx streaming encoder succeed!")
|
| 976 |
+
onnx_config = {
|
| 977 |
+
"subsampling_rate": subsampling,
|
| 978 |
+
"context": context,
|
| 979 |
+
"decoding_chunk_size": decoding_chunk_size,
|
| 980 |
+
"num_decoding_left_chunks": num_decoding_left_chunks,
|
| 981 |
+
"beam_size": args.beam_size,
|
| 982 |
+
"fp16": args.fp16,
|
| 983 |
+
"feat_size": feature_size,
|
| 984 |
+
"decoding_window": decoding_window,
|
| 985 |
+
"cnn_module_kernel_cache": cnn_module_kernel,
|
| 986 |
+
"return_ctc_logprobs": args.return_ctc_logprobs,
|
| 987 |
+
}
|
| 988 |
+
return onnx_config
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
def export_rescoring_decoder(model, configs, args, logger, decoder_onnx_path,
|
| 992 |
+
decoder_fastertransformer):
|
| 993 |
+
bz, seq_len = 32, 100
|
| 994 |
+
beam_size = args.beam_size
|
| 995 |
+
decoder = Decoder(
|
| 996 |
+
model.decoder,
|
| 997 |
+
model.ctc_weight,
|
| 998 |
+
model.reverse_weight,
|
| 999 |
+
beam_size,
|
| 1000 |
+
decoder_fastertransformer,
|
| 1001 |
+
)
|
| 1002 |
+
decoder.eval()
|
| 1003 |
+
|
| 1004 |
+
hyps_pad_sos_eos = torch.randint(low=3,
|
| 1005 |
+
high=1000,
|
| 1006 |
+
size=(bz, beam_size, seq_len))
|
| 1007 |
+
hyps_lens_sos = torch.randint(low=3,
|
| 1008 |
+
high=seq_len,
|
| 1009 |
+
size=(bz, beam_size),
|
| 1010 |
+
dtype=torch.int32)
|
| 1011 |
+
r_hyps_pad_sos_eos = torch.randint(low=3,
|
| 1012 |
+
high=1000,
|
| 1013 |
+
size=(bz, beam_size, seq_len))
|
| 1014 |
+
|
| 1015 |
+
output_size = configs["encoder_conf"]["output_size"]
|
| 1016 |
+
encoder_out = torch.randn(bz, seq_len, output_size, dtype=torch.float32)
|
| 1017 |
+
encoder_out_lens = torch.randint(low=3,
|
| 1018 |
+
high=seq_len,
|
| 1019 |
+
size=(bz, ),
|
| 1020 |
+
dtype=torch.int32)
|
| 1021 |
+
ctc_score = torch.randn(bz, beam_size, dtype=torch.float32)
|
| 1022 |
+
|
| 1023 |
+
input_names = [
|
| 1024 |
+
"encoder_out",
|
| 1025 |
+
"encoder_out_lens",
|
| 1026 |
+
"hyps_pad_sos_eos",
|
| 1027 |
+
"hyps_lens_sos",
|
| 1028 |
+
"r_hyps_pad_sos_eos",
|
| 1029 |
+
"ctc_score",
|
| 1030 |
+
]
|
| 1031 |
+
output_names = ["best_index"]
|
| 1032 |
+
if decoder_fastertransformer:
|
| 1033 |
+
output_names.insert(0, "decoder_out")
|
| 1034 |
+
|
| 1035 |
+
torch.onnx.export(
|
| 1036 |
+
decoder,
|
| 1037 |
+
(
|
| 1038 |
+
encoder_out,
|
| 1039 |
+
encoder_out_lens,
|
| 1040 |
+
hyps_pad_sos_eos,
|
| 1041 |
+
hyps_lens_sos,
|
| 1042 |
+
r_hyps_pad_sos_eos,
|
| 1043 |
+
ctc_score,
|
| 1044 |
+
),
|
| 1045 |
+
decoder_onnx_path,
|
| 1046 |
+
export_params=True,
|
| 1047 |
+
opset_version=13,
|
| 1048 |
+
do_constant_folding=True,
|
| 1049 |
+
input_names=input_names,
|
| 1050 |
+
output_names=output_names,
|
| 1051 |
+
dynamic_axes={
|
| 1052 |
+
"encoder_out": {
|
| 1053 |
+
0: "B",
|
| 1054 |
+
1: "T"
|
| 1055 |
+
},
|
| 1056 |
+
"encoder_out_lens": {
|
| 1057 |
+
0: "B"
|
| 1058 |
+
},
|
| 1059 |
+
"hyps_pad_sos_eos": {
|
| 1060 |
+
0: "B",
|
| 1061 |
+
2: "T2"
|
| 1062 |
+
},
|
| 1063 |
+
"hyps_lens_sos": {
|
| 1064 |
+
0: "B"
|
| 1065 |
+
},
|
| 1066 |
+
"r_hyps_pad_sos_eos": {
|
| 1067 |
+
0: "B",
|
| 1068 |
+
2: "T2"
|
| 1069 |
+
},
|
| 1070 |
+
"ctc_score": {
|
| 1071 |
+
0: "B"
|
| 1072 |
+
},
|
| 1073 |
+
"best_index": {
|
| 1074 |
+
0: "B"
|
| 1075 |
+
},
|
| 1076 |
+
},
|
| 1077 |
+
verbose=False,
|
| 1078 |
+
)
|
| 1079 |
+
with torch.no_grad():
|
| 1080 |
+
o0 = decoder(
|
| 1081 |
+
encoder_out,
|
| 1082 |
+
encoder_out_lens,
|
| 1083 |
+
hyps_pad_sos_eos,
|
| 1084 |
+
hyps_lens_sos,
|
| 1085 |
+
r_hyps_pad_sos_eos,
|
| 1086 |
+
ctc_score,
|
| 1087 |
+
)
|
| 1088 |
+
providers = ["CUDAExecutionProvider"]
|
| 1089 |
+
ort_session = onnxruntime.InferenceSession(decoder_onnx_path,
|
| 1090 |
+
providers=providers)
|
| 1091 |
+
|
| 1092 |
+
input_tensors = [
|
| 1093 |
+
encoder_out,
|
| 1094 |
+
encoder_out_lens,
|
| 1095 |
+
hyps_pad_sos_eos,
|
| 1096 |
+
hyps_lens_sos,
|
| 1097 |
+
r_hyps_pad_sos_eos,
|
| 1098 |
+
ctc_score,
|
| 1099 |
+
]
|
| 1100 |
+
ort_inputs = {}
|
| 1101 |
+
input_tensors = to_numpy(input_tensors)
|
| 1102 |
+
for idx, name in enumerate(input_names):
|
| 1103 |
+
ort_inputs[name] = input_tensors[idx]
|
| 1104 |
+
|
| 1105 |
+
# if model.reverse weight == 0,
|
| 1106 |
+
# the r_hyps_pad will be removed
|
| 1107 |
+
# from the onnx decoder since it doen't play any role
|
| 1108 |
+
if model.reverse_weight == 0:
|
| 1109 |
+
del ort_inputs["r_hyps_pad_sos_eos"]
|
| 1110 |
+
ort_outs = ort_session.run(None, ort_inputs)
|
| 1111 |
+
|
| 1112 |
+
# check decoder output
|
| 1113 |
+
if decoder_fastertransformer:
|
| 1114 |
+
test(to_numpy(o0), ort_outs, rtol=1e-03, atol=1e-05)
|
| 1115 |
+
else:
|
| 1116 |
+
test(to_numpy([o0]), ort_outs, rtol=1e-03, atol=1e-05)
|
| 1117 |
+
logger.info("export to onnx decoder succeed!")
|
| 1118 |
+
|
| 1119 |
+
|
| 1120 |
+
if __name__ == "__main__":
|
| 1121 |
+
parser = argparse.ArgumentParser(description="export x86_gpu model")
|
| 1122 |
+
parser.add_argument("--config", required=True, help="config file")
|
| 1123 |
+
parser.add_argument("--checkpoint", required=True, help="checkpoint model")
|
| 1124 |
+
parser.add_argument(
|
| 1125 |
+
"--cmvn_file",
|
| 1126 |
+
required=False,
|
| 1127 |
+
default="",
|
| 1128 |
+
type=str,
|
| 1129 |
+
help="global_cmvn file, default path is in config file",
|
| 1130 |
+
)
|
| 1131 |
+
parser.add_argument(
|
| 1132 |
+
"--reverse_weight",
|
| 1133 |
+
default=-1.0,
|
| 1134 |
+
type=float,
|
| 1135 |
+
required=False,
|
| 1136 |
+
help="reverse weight for bitransformer," +
|
| 1137 |
+
"default value is in config file",
|
| 1138 |
+
)
|
| 1139 |
+
parser.add_argument(
|
| 1140 |
+
"--ctc_weight",
|
| 1141 |
+
default=-1.0,
|
| 1142 |
+
type=float,
|
| 1143 |
+
required=False,
|
| 1144 |
+
help="ctc weight, default value is in config file",
|
| 1145 |
+
)
|
| 1146 |
+
parser.add_argument(
|
| 1147 |
+
"--beam_size",
|
| 1148 |
+
default=10,
|
| 1149 |
+
type=int,
|
| 1150 |
+
required=False,
|
| 1151 |
+
help="beam size would be ctc output size",
|
| 1152 |
+
)
|
| 1153 |
+
parser.add_argument(
|
| 1154 |
+
"--output_onnx_dir",
|
| 1155 |
+
default="onnx_model",
|
| 1156 |
+
help="output onnx encoder and decoder directory",
|
| 1157 |
+
)
|
| 1158 |
+
parser.add_argument(
|
| 1159 |
+
"--fp16",
|
| 1160 |
+
action="store_true",
|
| 1161 |
+
help="whether to export fp16 model, default false",
|
| 1162 |
+
)
|
| 1163 |
+
# arguments for streaming encoder
|
| 1164 |
+
parser.add_argument(
|
| 1165 |
+
"--streaming",
|
| 1166 |
+
action="store_true",
|
| 1167 |
+
help="whether to export streaming encoder, default false",
|
| 1168 |
+
)
|
| 1169 |
+
parser.add_argument(
|
| 1170 |
+
"--decoding_chunk_size",
|
| 1171 |
+
default=16,
|
| 1172 |
+
type=int,
|
| 1173 |
+
required=False,
|
| 1174 |
+
help="the decoding chunk size, <=0 is not supported",
|
| 1175 |
+
)
|
| 1176 |
+
parser.add_argument(
|
| 1177 |
+
"--num_decoding_left_chunks",
|
| 1178 |
+
default=5,
|
| 1179 |
+
type=int,
|
| 1180 |
+
required=False,
|
| 1181 |
+
help="number of left chunks, <= 0 is not supported",
|
| 1182 |
+
)
|
| 1183 |
+
parser.add_argument(
|
| 1184 |
+
"--decoder_fastertransformer",
|
| 1185 |
+
action="store_true",
|
| 1186 |
+
help="return decoder_out and best_index for ft",
|
| 1187 |
+
)
|
| 1188 |
+
parser.add_argument(
|
| 1189 |
+
"--return_ctc_logprobs",
|
| 1190 |
+
action="store_true",
|
| 1191 |
+
help="return full ctc_log_probs for TLG streaming encoder",
|
| 1192 |
+
)
|
| 1193 |
+
args = parser.parse_args()
|
| 1194 |
+
|
| 1195 |
+
torch.manual_seed(0)
|
| 1196 |
+
torch.set_printoptions(precision=10)
|
| 1197 |
+
|
| 1198 |
+
with open(args.config, "r") as fin:
|
| 1199 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 1200 |
+
if args.cmvn_file and os.path.exists(args.cmvn_file):
|
| 1201 |
+
if 'cmvn' not in configs:
|
| 1202 |
+
configs['cmvn'] = "global_cmvn"
|
| 1203 |
+
configs['cmvn_conf'] = {}
|
| 1204 |
+
else:
|
| 1205 |
+
assert configs['cmvn'] == "global_cmvn"
|
| 1206 |
+
assert configs['cmvn_conf'] is not None
|
| 1207 |
+
configs['cmvn_conf']["cmvn_file"] = args.cmvn_file
|
| 1208 |
+
if (args.reverse_weight != -1.0
|
| 1209 |
+
and "reverse_weight" in configs["model_conf"]):
|
| 1210 |
+
configs["model_conf"]["reverse_weight"] = args.reverse_weight
|
| 1211 |
+
print("Update reverse weight to", args.reverse_weight)
|
| 1212 |
+
if args.ctc_weight != -1:
|
| 1213 |
+
print("Update ctc weight to ", args.ctc_weight)
|
| 1214 |
+
configs["model_conf"]["ctc_weight"] = args.ctc_weight
|
| 1215 |
+
configs["encoder_conf"]["use_dynamic_chunk"] = False
|
| 1216 |
+
|
| 1217 |
+
model, configs = init_model(args, configs)
|
| 1218 |
+
model.eval()
|
| 1219 |
+
|
| 1220 |
+
if not os.path.exists(args.output_onnx_dir):
|
| 1221 |
+
os.mkdir(args.output_onnx_dir)
|
| 1222 |
+
encoder_onnx_path = os.path.join(args.output_onnx_dir, "encoder.onnx")
|
| 1223 |
+
export_enc_func = None
|
| 1224 |
+
if args.streaming:
|
| 1225 |
+
assert args.decoding_chunk_size > 0
|
| 1226 |
+
assert args.num_decoding_left_chunks > 0
|
| 1227 |
+
export_enc_func = export_online_encoder
|
| 1228 |
+
else:
|
| 1229 |
+
export_enc_func = export_offline_encoder
|
| 1230 |
+
|
| 1231 |
+
onnx_config = export_enc_func(model, configs, args, logger,
|
| 1232 |
+
encoder_onnx_path)
|
| 1233 |
+
|
| 1234 |
+
decoder_onnx_path = os.path.join(args.output_onnx_dir, "decoder.onnx")
|
| 1235 |
+
export_rescoring_decoder(
|
| 1236 |
+
model,
|
| 1237 |
+
configs,
|
| 1238 |
+
args,
|
| 1239 |
+
logger,
|
| 1240 |
+
decoder_onnx_path,
|
| 1241 |
+
args.decoder_fastertransformer,
|
| 1242 |
+
)
|
| 1243 |
+
|
| 1244 |
+
if args.fp16:
|
| 1245 |
+
try:
|
| 1246 |
+
import onnxmltools
|
| 1247 |
+
from onnxmltools.utils.float16_converter import \
|
| 1248 |
+
convert_float_to_float16
|
| 1249 |
+
except ImportError:
|
| 1250 |
+
print("Please install onnxmltools!")
|
| 1251 |
+
sys.exit(1)
|
| 1252 |
+
encoder_onnx_model = onnxmltools.utils.load_model(encoder_onnx_path)
|
| 1253 |
+
encoder_onnx_model = convert_float_to_float16(encoder_onnx_model)
|
| 1254 |
+
encoder_onnx_path = os.path.join(args.output_onnx_dir,
|
| 1255 |
+
"encoder_fp16.onnx")
|
| 1256 |
+
onnxmltools.utils.save_model(encoder_onnx_model, encoder_onnx_path)
|
| 1257 |
+
decoder_onnx_model = onnxmltools.utils.load_model(decoder_onnx_path)
|
| 1258 |
+
decoder_onnx_model = convert_float_to_float16(decoder_onnx_model)
|
| 1259 |
+
decoder_onnx_path = os.path.join(args.output_onnx_dir,
|
| 1260 |
+
"decoder_fp16.onnx")
|
| 1261 |
+
onnxmltools.utils.save_model(decoder_onnx_model, decoder_onnx_path)
|
| 1262 |
+
# dump configurations
|
| 1263 |
+
|
| 1264 |
+
config_dir = os.path.join(args.output_onnx_dir, "config.yaml")
|
| 1265 |
+
with open(config_dir, "w") as out:
|
| 1266 |
+
yaml.dump(onnx_config, out)
|
wenet/bin/recognize.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 Mobvoi Inc. (authors: Binbin Zhang, Xiaoyu Chen, Di Wu)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import copy
|
| 19 |
+
import logging
|
| 20 |
+
import os
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
import yaml
|
| 24 |
+
from torch.utils.data import DataLoader
|
| 25 |
+
|
| 26 |
+
from wenet.dataset.dataset import Dataset
|
| 27 |
+
from wenet.utils.config import override_config
|
| 28 |
+
from wenet.utils.init_model import init_model
|
| 29 |
+
from wenet.utils.init_tokenizer import init_tokenizer
|
| 30 |
+
from wenet.utils.context_graph import ContextGraph
|
| 31 |
+
from wenet.utils.ctc_utils import get_blank_id
|
| 32 |
+
from wenet.utils.common import TORCH_NPU_AVAILABLE # noqa just ensure to check torch-npu
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def get_args():
|
| 36 |
+
parser = argparse.ArgumentParser(description='recognize with your model')
|
| 37 |
+
parser.add_argument('--config', required=True, help='config file')
|
| 38 |
+
parser.add_argument('--test_data', required=True, help='test data file')
|
| 39 |
+
parser.add_argument('--data_type',
|
| 40 |
+
default='raw',
|
| 41 |
+
choices=['raw', 'shard'],
|
| 42 |
+
help='train and cv data type')
|
| 43 |
+
parser.add_argument('--gpu',
|
| 44 |
+
type=int,
|
| 45 |
+
default=-1,
|
| 46 |
+
help='gpu id for this rank, -1 for cpu')
|
| 47 |
+
parser.add_argument('--device',
|
| 48 |
+
type=str,
|
| 49 |
+
default="cpu",
|
| 50 |
+
choices=["cpu", "npu", "cuda"],
|
| 51 |
+
help='accelerator to use')
|
| 52 |
+
parser.add_argument('--dtype',
|
| 53 |
+
type=str,
|
| 54 |
+
default='fp32',
|
| 55 |
+
choices=['fp16', 'fp32', 'bf16'],
|
| 56 |
+
help='model\'s dtype')
|
| 57 |
+
parser.add_argument('--num_workers',
|
| 58 |
+
default=0,
|
| 59 |
+
type=int,
|
| 60 |
+
help='num of subprocess workers for reading')
|
| 61 |
+
parser.add_argument('--checkpoint', required=True, help='checkpoint model')
|
| 62 |
+
parser.add_argument('--beam_size',
|
| 63 |
+
type=int,
|
| 64 |
+
default=10,
|
| 65 |
+
help='beam size for search')
|
| 66 |
+
parser.add_argument('--length_penalty',
|
| 67 |
+
type=float,
|
| 68 |
+
default=0.0,
|
| 69 |
+
help='length penalty')
|
| 70 |
+
parser.add_argument('--blank_penalty',
|
| 71 |
+
type=float,
|
| 72 |
+
default=0.0,
|
| 73 |
+
help='blank penalty')
|
| 74 |
+
parser.add_argument('--result_dir', required=True, help='asr result file')
|
| 75 |
+
parser.add_argument('--batch_size',
|
| 76 |
+
type=int,
|
| 77 |
+
default=16,
|
| 78 |
+
help='asr result file')
|
| 79 |
+
parser.add_argument('--modes',
|
| 80 |
+
nargs='+',
|
| 81 |
+
help="""decoding mode, support the following:
|
| 82 |
+
attention
|
| 83 |
+
ctc_greedy_search
|
| 84 |
+
ctc_prefix_beam_search
|
| 85 |
+
attention_rescoring
|
| 86 |
+
rnnt_greedy_search
|
| 87 |
+
rnnt_beam_search
|
| 88 |
+
rnnt_beam_attn_rescoring
|
| 89 |
+
ctc_beam_td_attn_rescoring
|
| 90 |
+
hlg_onebest
|
| 91 |
+
hlg_rescore
|
| 92 |
+
paraformer_greedy_search
|
| 93 |
+
paraformer_beam_search""")
|
| 94 |
+
parser.add_argument('--search_ctc_weight',
|
| 95 |
+
type=float,
|
| 96 |
+
default=1.0,
|
| 97 |
+
help='ctc weight for nbest generation')
|
| 98 |
+
parser.add_argument('--search_transducer_weight',
|
| 99 |
+
type=float,
|
| 100 |
+
default=0.0,
|
| 101 |
+
help='transducer weight for nbest generation')
|
| 102 |
+
parser.add_argument('--ctc_weight',
|
| 103 |
+
type=float,
|
| 104 |
+
default=0.0,
|
| 105 |
+
help='ctc weight for rescoring weight in \
|
| 106 |
+
attention rescoring decode mode \
|
| 107 |
+
ctc weight for rescoring weight in \
|
| 108 |
+
transducer attention rescore decode mode')
|
| 109 |
+
|
| 110 |
+
parser.add_argument('--transducer_weight',
|
| 111 |
+
type=float,
|
| 112 |
+
default=0.0,
|
| 113 |
+
help='transducer weight for rescoring weight in '
|
| 114 |
+
'transducer attention rescore mode')
|
| 115 |
+
parser.add_argument('--attn_weight',
|
| 116 |
+
type=float,
|
| 117 |
+
default=0.0,
|
| 118 |
+
help='attention weight for rescoring weight in '
|
| 119 |
+
'transducer attention rescore mode')
|
| 120 |
+
parser.add_argument('--decoding_chunk_size',
|
| 121 |
+
type=int,
|
| 122 |
+
default=-1,
|
| 123 |
+
help='''decoding chunk size,
|
| 124 |
+
<0: for decoding, use full chunk.
|
| 125 |
+
>0: for decoding, use fixed chunk size as set.
|
| 126 |
+
0: used for training, it's prohibited here''')
|
| 127 |
+
parser.add_argument('--num_decoding_left_chunks',
|
| 128 |
+
type=int,
|
| 129 |
+
default=-1,
|
| 130 |
+
help='number of left chunks for decoding')
|
| 131 |
+
parser.add_argument('--simulate_streaming',
|
| 132 |
+
action='store_true',
|
| 133 |
+
help='simulate streaming inference')
|
| 134 |
+
parser.add_argument('--reverse_weight',
|
| 135 |
+
type=float,
|
| 136 |
+
default=0.0,
|
| 137 |
+
help='''right to left weight for attention rescoring
|
| 138 |
+
decode mode''')
|
| 139 |
+
parser.add_argument('--override_config',
|
| 140 |
+
action='append',
|
| 141 |
+
default=[],
|
| 142 |
+
help="override yaml config")
|
| 143 |
+
|
| 144 |
+
parser.add_argument('--word',
|
| 145 |
+
default='',
|
| 146 |
+
type=str,
|
| 147 |
+
help='word file, only used for hlg decode')
|
| 148 |
+
parser.add_argument('--hlg',
|
| 149 |
+
default='',
|
| 150 |
+
type=str,
|
| 151 |
+
help='hlg file, only used for hlg decode')
|
| 152 |
+
parser.add_argument('--lm_scale',
|
| 153 |
+
type=float,
|
| 154 |
+
default=0.0,
|
| 155 |
+
help='lm scale for hlg attention rescore decode')
|
| 156 |
+
parser.add_argument('--decoder_scale',
|
| 157 |
+
type=float,
|
| 158 |
+
default=0.0,
|
| 159 |
+
help='lm scale for hlg attention rescore decode')
|
| 160 |
+
parser.add_argument('--r_decoder_scale',
|
| 161 |
+
type=float,
|
| 162 |
+
default=0.0,
|
| 163 |
+
help='lm scale for hlg attention rescore decode')
|
| 164 |
+
|
| 165 |
+
parser.add_argument(
|
| 166 |
+
'--context_bias_mode',
|
| 167 |
+
type=str,
|
| 168 |
+
default='',
|
| 169 |
+
help='''Context bias mode, selectable from the following
|
| 170 |
+
option: decoding-graph, deep-biasing''')
|
| 171 |
+
parser.add_argument('--context_list_path',
|
| 172 |
+
type=str,
|
| 173 |
+
default='',
|
| 174 |
+
help='Context list path')
|
| 175 |
+
parser.add_argument('--context_graph_score',
|
| 176 |
+
type=float,
|
| 177 |
+
default=0.0,
|
| 178 |
+
help='''The higher the score, the greater the degree of
|
| 179 |
+
bias using decoding-graph for biasing''')
|
| 180 |
+
|
| 181 |
+
parser.add_argument('--use_lora',
|
| 182 |
+
type=bool,
|
| 183 |
+
default=False,
|
| 184 |
+
help='''Whether to use lora for biasing''')
|
| 185 |
+
parser.add_argument("--lora_ckpt_path",
|
| 186 |
+
default=None,
|
| 187 |
+
type=str,
|
| 188 |
+
help="lora checkpoint path.")
|
| 189 |
+
args = parser.parse_args()
|
| 190 |
+
print(args)
|
| 191 |
+
return args
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def main():
|
| 195 |
+
args = get_args()
|
| 196 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 197 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 198 |
+
if args.gpu != -1:
|
| 199 |
+
# remain the original usage of gpu
|
| 200 |
+
args.device = "cuda"
|
| 201 |
+
if "cuda" in args.device:
|
| 202 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
|
| 203 |
+
|
| 204 |
+
with open(args.config, 'r') as fin:
|
| 205 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 206 |
+
if len(args.override_config) > 0:
|
| 207 |
+
configs = override_config(configs, args.override_config)
|
| 208 |
+
|
| 209 |
+
test_conf = copy.deepcopy(configs['dataset_conf'])
|
| 210 |
+
|
| 211 |
+
test_conf['filter_conf']['max_length'] = 102400
|
| 212 |
+
test_conf['filter_conf']['min_length'] = 0
|
| 213 |
+
test_conf['filter_conf']['token_max_length'] = 102400
|
| 214 |
+
test_conf['filter_conf']['token_min_length'] = 0
|
| 215 |
+
test_conf['filter_conf']['max_output_input_ratio'] = 102400
|
| 216 |
+
test_conf['filter_conf']['min_output_input_ratio'] = 0
|
| 217 |
+
test_conf['speed_perturb'] = False
|
| 218 |
+
test_conf['spec_aug'] = False
|
| 219 |
+
test_conf['spec_sub'] = False
|
| 220 |
+
test_conf['spec_trim'] = False
|
| 221 |
+
test_conf['shuffle'] = False
|
| 222 |
+
test_conf['sort'] = False
|
| 223 |
+
test_conf['cycle'] = 1
|
| 224 |
+
test_conf['list_shuffle'] = False
|
| 225 |
+
if 'fbank_conf' in test_conf:
|
| 226 |
+
test_conf['fbank_conf']['dither'] = 0.0
|
| 227 |
+
elif 'mfcc_conf' in test_conf:
|
| 228 |
+
test_conf['mfcc_conf']['dither'] = 0.0
|
| 229 |
+
test_conf['batch_conf']['batch_type'] = "static"
|
| 230 |
+
test_conf['batch_conf']['batch_size'] = args.batch_size
|
| 231 |
+
|
| 232 |
+
tokenizer = init_tokenizer(configs)
|
| 233 |
+
test_dataset = Dataset(args.data_type,
|
| 234 |
+
args.test_data,
|
| 235 |
+
tokenizer,
|
| 236 |
+
test_conf,
|
| 237 |
+
partition=False)
|
| 238 |
+
|
| 239 |
+
test_data_loader = DataLoader(test_dataset,
|
| 240 |
+
batch_size=None,
|
| 241 |
+
num_workers=args.num_workers)
|
| 242 |
+
|
| 243 |
+
# Init asr model from configs
|
| 244 |
+
args.jit = False
|
| 245 |
+
model, configs = init_model(args, configs)
|
| 246 |
+
|
| 247 |
+
device = torch.device(args.device)
|
| 248 |
+
model = model.to(device)
|
| 249 |
+
model.eval()
|
| 250 |
+
dtype = torch.float32
|
| 251 |
+
if args.dtype == 'fp16':
|
| 252 |
+
dtype = torch.float16
|
| 253 |
+
elif args.dtype == 'bf16':
|
| 254 |
+
dtype = torch.bfloat16
|
| 255 |
+
logging.info("compute dtype is {}".format(dtype))
|
| 256 |
+
|
| 257 |
+
context_graph = None
|
| 258 |
+
if 'decoding-graph' in args.context_bias_mode:
|
| 259 |
+
context_graph = ContextGraph(args.context_list_path,
|
| 260 |
+
tokenizer.symbol_table,
|
| 261 |
+
configs['tokenizer_conf']['bpe_path'],
|
| 262 |
+
args.context_graph_score)
|
| 263 |
+
|
| 264 |
+
_, blank_id = get_blank_id(configs, tokenizer.symbol_table)
|
| 265 |
+
logging.info("blank_id is {}".format(blank_id))
|
| 266 |
+
|
| 267 |
+
# TODO(Dinghao Zhou): Support RNN-T related decoding
|
| 268 |
+
# TODO(Lv Xiang): Support k2 related decoding
|
| 269 |
+
# TODO(Kaixun Huang): Support context graph
|
| 270 |
+
files = {}
|
| 271 |
+
for mode in args.modes:
|
| 272 |
+
dir_name = os.path.join(args.result_dir, mode)
|
| 273 |
+
os.makedirs(dir_name, exist_ok=True)
|
| 274 |
+
file_name = os.path.join(dir_name, 'text')
|
| 275 |
+
files[mode] = open(file_name, 'w')
|
| 276 |
+
max_format_len = max([len(mode) for mode in args.modes])
|
| 277 |
+
|
| 278 |
+
with torch.cuda.amp.autocast(enabled=True,
|
| 279 |
+
dtype=dtype,
|
| 280 |
+
cache_enabled=False):
|
| 281 |
+
with torch.no_grad():
|
| 282 |
+
for batch_idx, batch in enumerate(test_data_loader):
|
| 283 |
+
keys = batch["keys"]
|
| 284 |
+
feats = batch["feats"].to(device)
|
| 285 |
+
target = batch["target"].to(device)
|
| 286 |
+
feats_lengths = batch["feats_lengths"].to(device)
|
| 287 |
+
target_lengths = batch["target_lengths"].to(device)
|
| 288 |
+
infos = {"tasks": batch["tasks"], "langs": batch["langs"]}
|
| 289 |
+
results = model.decode(
|
| 290 |
+
args.modes,
|
| 291 |
+
feats,
|
| 292 |
+
feats_lengths,
|
| 293 |
+
args.beam_size,
|
| 294 |
+
decoding_chunk_size=args.decoding_chunk_size,
|
| 295 |
+
num_decoding_left_chunks=args.num_decoding_left_chunks,
|
| 296 |
+
ctc_weight=args.ctc_weight,
|
| 297 |
+
simulate_streaming=args.simulate_streaming,
|
| 298 |
+
reverse_weight=args.reverse_weight,
|
| 299 |
+
context_graph=context_graph,
|
| 300 |
+
blank_id=blank_id,
|
| 301 |
+
blank_penalty=args.blank_penalty,
|
| 302 |
+
length_penalty=args.length_penalty,
|
| 303 |
+
infos=infos)
|
| 304 |
+
for i, key in enumerate(keys):
|
| 305 |
+
for mode, hyps in results.items():
|
| 306 |
+
tokens = hyps[i].tokens
|
| 307 |
+
line = '{} {}'.format(key,
|
| 308 |
+
tokenizer.detokenize(tokens)[0])
|
| 309 |
+
logging.info('{} {}'.format(mode.ljust(max_format_len),
|
| 310 |
+
line))
|
| 311 |
+
files[mode].write(line + '\n')
|
| 312 |
+
for mode, f in files.items():
|
| 313 |
+
f.close()
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
if __name__ == '__main__':
|
| 317 |
+
main()
|
wenet/bin/recognize_onnx_gpu.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 Mobvoi Inc. (authors: Binbin Zhang, Xiaoyu Chen, Di Wu)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
# Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
|
| 16 |
+
#
|
| 17 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 18 |
+
# you may not use this file except in compliance with the License.
|
| 19 |
+
# You may obtain a copy of the License at
|
| 20 |
+
#
|
| 21 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 22 |
+
#
|
| 23 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 24 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 25 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 26 |
+
# See the License for the specific language governing permissions and
|
| 27 |
+
# limitations under the License.
|
| 28 |
+
"""
|
| 29 |
+
This script is for testing exported onnx encoder and decoder from
|
| 30 |
+
export_onnx_gpu.py. The exported onnx models only support batch offline ASR inference.
|
| 31 |
+
It requires a python wrapped c++ ctc decoder.
|
| 32 |
+
Please install it by following:
|
| 33 |
+
https://github.com/Slyne/ctc_decoder.git
|
| 34 |
+
"""
|
| 35 |
+
from __future__ import print_function
|
| 36 |
+
|
| 37 |
+
import argparse
|
| 38 |
+
import copy
|
| 39 |
+
import logging
|
| 40 |
+
import os
|
| 41 |
+
import sys
|
| 42 |
+
|
| 43 |
+
import torch
|
| 44 |
+
import yaml
|
| 45 |
+
from torch.utils.data import DataLoader
|
| 46 |
+
|
| 47 |
+
from wenet.dataset.dataset import Dataset
|
| 48 |
+
from wenet.utils.common import IGNORE_ID
|
| 49 |
+
from wenet.utils.config import override_config
|
| 50 |
+
from wenet.utils.init_tokenizer import init_tokenizer
|
| 51 |
+
|
| 52 |
+
import onnxruntime as rt
|
| 53 |
+
import multiprocessing
|
| 54 |
+
import numpy as np
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
from swig_decoders import map_batch, \
|
| 58 |
+
ctc_beam_search_decoder_batch, \
|
| 59 |
+
TrieVector, PathTrie
|
| 60 |
+
except ImportError:
|
| 61 |
+
print('Please install ctc decoders first by refering to\n' +
|
| 62 |
+
'https://github.com/Slyne/ctc_decoder.git')
|
| 63 |
+
sys.exit(1)
|
| 64 |
+
|
| 65 |
+
def get_args():
|
| 66 |
+
parser = argparse.ArgumentParser(description='recognize with your model')
|
| 67 |
+
parser.add_argument('--config', required=True, help='config file')
|
| 68 |
+
parser.add_argument('--test_data', required=True, help='test data file')
|
| 69 |
+
parser.add_argument('--data_type',
|
| 70 |
+
default='raw',
|
| 71 |
+
choices=['raw', 'shard'],
|
| 72 |
+
help='train and cv data type')
|
| 73 |
+
parser.add_argument('--gpu',
|
| 74 |
+
type=int,
|
| 75 |
+
default=-1,
|
| 76 |
+
help='gpu id for this rank, -1 for cpu')
|
| 77 |
+
parser.add_argument('--dict', required=True, help='dict file')
|
| 78 |
+
parser.add_argument('--encoder_onnx',
|
| 79 |
+
required=True,
|
| 80 |
+
help='encoder onnx file')
|
| 81 |
+
parser.add_argument('--decoder_onnx',
|
| 82 |
+
required=True,
|
| 83 |
+
help='decoder onnx file')
|
| 84 |
+
parser.add_argument('--result_file', required=True, help='asr result file')
|
| 85 |
+
parser.add_argument('--batch_size',
|
| 86 |
+
type=int,
|
| 87 |
+
default=32,
|
| 88 |
+
help='asr result file')
|
| 89 |
+
parser.add_argument('--mode',
|
| 90 |
+
choices=[
|
| 91 |
+
'ctc_greedy_search', 'ctc_prefix_beam_search',
|
| 92 |
+
'attention_rescoring'
|
| 93 |
+
],
|
| 94 |
+
default='attention_rescoring',
|
| 95 |
+
help='decoding mode')
|
| 96 |
+
parser.add_argument('--bpe_model',
|
| 97 |
+
default=None,
|
| 98 |
+
type=str,
|
| 99 |
+
help='bpe model for english part')
|
| 100 |
+
parser.add_argument('--override_config',
|
| 101 |
+
action='append',
|
| 102 |
+
default=[],
|
| 103 |
+
help="override yaml config")
|
| 104 |
+
parser.add_argument('--fp16',
|
| 105 |
+
action='store_true',
|
| 106 |
+
help='whether to export fp16 model, default false')
|
| 107 |
+
args = parser.parse_args()
|
| 108 |
+
return args
|
| 109 |
+
|
| 110 |
+
def main():
|
| 111 |
+
args = get_args()
|
| 112 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 113 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 114 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
|
| 115 |
+
|
| 116 |
+
with open(args.config, 'r') as fin:
|
| 117 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 118 |
+
if len(args.override_config) > 0:
|
| 119 |
+
configs = override_config(configs, args.override_config)
|
| 120 |
+
|
| 121 |
+
reverse_weight = configs["model_conf"].get("reverse_weight", 0.0)
|
| 122 |
+
special_tokens = configs.get('tokenizer_conf', {}).get('special_tokens', None)
|
| 123 |
+
test_conf = copy.deepcopy(configs['dataset_conf'])
|
| 124 |
+
test_conf['filter_conf']['max_length'] = 102400
|
| 125 |
+
test_conf['filter_conf']['min_length'] = 0
|
| 126 |
+
test_conf['filter_conf']['token_max_length'] = 102400
|
| 127 |
+
test_conf['filter_conf']['token_min_length'] = 0
|
| 128 |
+
test_conf['filter_conf']['max_output_input_ratio'] = 102400
|
| 129 |
+
test_conf['filter_conf']['min_output_input_ratio'] = 0
|
| 130 |
+
test_conf['speed_perturb'] = False
|
| 131 |
+
test_conf['spec_aug'] = False
|
| 132 |
+
test_conf['spec_sub'] = False
|
| 133 |
+
test_conf['spec_trim'] = False
|
| 134 |
+
test_conf['shuffle'] = False
|
| 135 |
+
test_conf['sort'] = False
|
| 136 |
+
test_conf['fbank_conf']['dither'] = 0.0
|
| 137 |
+
test_conf['batch_conf']['batch_type'] = "static"
|
| 138 |
+
test_conf['batch_conf']['batch_size'] = args.batch_size
|
| 139 |
+
|
| 140 |
+
tokenizer = init_tokenizer(configs)
|
| 141 |
+
test_dataset = Dataset(args.data_type,
|
| 142 |
+
args.test_data,
|
| 143 |
+
tokenizer,
|
| 144 |
+
test_conf,
|
| 145 |
+
partition=False)
|
| 146 |
+
test_data_loader = DataLoader(test_dataset, batch_size=None, num_workers=0)
|
| 147 |
+
|
| 148 |
+
# Init asr model from configs
|
| 149 |
+
use_cuda = args.gpu >= 0 and torch.cuda.is_available()
|
| 150 |
+
if use_cuda:
|
| 151 |
+
EP_list = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
| 152 |
+
else:
|
| 153 |
+
EP_list = ['CPUExecutionProvider']
|
| 154 |
+
|
| 155 |
+
encoder_ort_session = rt.InferenceSession(args.encoder_onnx,
|
| 156 |
+
providers=EP_list)
|
| 157 |
+
decoder_ort_session = None
|
| 158 |
+
if args.mode == "attention_rescoring":
|
| 159 |
+
decoder_ort_session = rt.InferenceSession(args.decoder_onnx,
|
| 160 |
+
providers=EP_list)
|
| 161 |
+
|
| 162 |
+
# Load dict
|
| 163 |
+
vocabulary = []
|
| 164 |
+
char_dict = {}
|
| 165 |
+
with open(args.dict, 'r') as fin:
|
| 166 |
+
for line in fin:
|
| 167 |
+
arr = line.strip().split()
|
| 168 |
+
assert len(arr) == 2
|
| 169 |
+
char_dict[int(arr[1])] = arr[0]
|
| 170 |
+
vocabulary.append(arr[0])
|
| 171 |
+
|
| 172 |
+
vocab_size = len(char_dict)
|
| 173 |
+
sos = (vocab_size - 1 if special_tokens is None else
|
| 174 |
+
special_tokens.get("<sos>", vocab_size - 1))
|
| 175 |
+
eos = (vocab_size - 1 if special_tokens is None else
|
| 176 |
+
special_tokens.get("<eos>", vocab_size - 1))
|
| 177 |
+
|
| 178 |
+
with torch.no_grad(), open(args.result_file, 'w') as fout:
|
| 179 |
+
for _, batch in enumerate(test_data_loader):
|
| 180 |
+
keys = batch['keys']
|
| 181 |
+
feats = batch['feats']
|
| 182 |
+
feats_lengths = batch['feats_lengths']
|
| 183 |
+
feats, feats_lengths = feats.numpy(), feats_lengths.numpy()
|
| 184 |
+
if args.fp16:
|
| 185 |
+
feats = feats.astype(np.float16)
|
| 186 |
+
ort_inputs = {
|
| 187 |
+
encoder_ort_session.get_inputs()[0].name: feats,
|
| 188 |
+
encoder_ort_session.get_inputs()[1].name: feats_lengths
|
| 189 |
+
}
|
| 190 |
+
ort_outs = encoder_ort_session.run(None, ort_inputs)
|
| 191 |
+
encoder_out, encoder_out_lens, ctc_log_probs, \
|
| 192 |
+
beam_log_probs, beam_log_probs_idx = ort_outs
|
| 193 |
+
beam_size = beam_log_probs.shape[-1]
|
| 194 |
+
batch_size = beam_log_probs.shape[0]
|
| 195 |
+
num_processes = min(multiprocessing.cpu_count(), batch_size)
|
| 196 |
+
if args.mode == 'ctc_greedy_search':
|
| 197 |
+
if beam_size != 1:
|
| 198 |
+
log_probs_idx = beam_log_probs_idx[:, :, 0]
|
| 199 |
+
batch_sents = []
|
| 200 |
+
for idx, seq in enumerate(log_probs_idx):
|
| 201 |
+
batch_sents.append(seq[0:encoder_out_lens[idx]].tolist())
|
| 202 |
+
hyps = map_batch(batch_sents, vocabulary, num_processes, True,
|
| 203 |
+
0)
|
| 204 |
+
elif args.mode in ('ctc_prefix_beam_search',
|
| 205 |
+
"attention_rescoring"):
|
| 206 |
+
batch_log_probs_seq_list = beam_log_probs.tolist()
|
| 207 |
+
batch_log_probs_idx_list = beam_log_probs_idx.tolist()
|
| 208 |
+
batch_len_list = encoder_out_lens.tolist()
|
| 209 |
+
batch_log_probs_seq = []
|
| 210 |
+
batch_log_probs_ids = []
|
| 211 |
+
batch_start = [] # only effective in streaming deployment
|
| 212 |
+
batch_root = TrieVector()
|
| 213 |
+
root_dict = {}
|
| 214 |
+
for i in range(len(batch_len_list)):
|
| 215 |
+
num_sent = batch_len_list[i]
|
| 216 |
+
batch_log_probs_seq.append(
|
| 217 |
+
batch_log_probs_seq_list[i][0:num_sent])
|
| 218 |
+
batch_log_probs_ids.append(
|
| 219 |
+
batch_log_probs_idx_list[i][0:num_sent])
|
| 220 |
+
root_dict[i] = PathTrie()
|
| 221 |
+
batch_root.append(root_dict[i])
|
| 222 |
+
batch_start.append(True)
|
| 223 |
+
score_hyps = ctc_beam_search_decoder_batch(
|
| 224 |
+
batch_log_probs_seq, batch_log_probs_ids, batch_root,
|
| 225 |
+
batch_start, beam_size, num_processes, 0, -2, 0.99999)
|
| 226 |
+
if args.mode == 'ctc_prefix_beam_search':
|
| 227 |
+
hyps = []
|
| 228 |
+
for cand_hyps in score_hyps:
|
| 229 |
+
hyps.append(cand_hyps[0][1])
|
| 230 |
+
hyps = map_batch(hyps, vocabulary, num_processes, False, 0)
|
| 231 |
+
if args.mode == 'attention_rescoring':
|
| 232 |
+
ctc_score, all_hyps = [], []
|
| 233 |
+
max_len = 0
|
| 234 |
+
for hyps in score_hyps:
|
| 235 |
+
cur_len = len(hyps)
|
| 236 |
+
if len(hyps) < beam_size:
|
| 237 |
+
hyps += (beam_size - cur_len) * [(-float("INF"),
|
| 238 |
+
(0, ))]
|
| 239 |
+
cur_ctc_score = []
|
| 240 |
+
for hyp in hyps:
|
| 241 |
+
cur_ctc_score.append(hyp[0])
|
| 242 |
+
all_hyps.append(list(hyp[1]))
|
| 243 |
+
if len(hyp[1]) > max_len:
|
| 244 |
+
max_len = len(hyp[1])
|
| 245 |
+
ctc_score.append(cur_ctc_score)
|
| 246 |
+
if args.fp16:
|
| 247 |
+
ctc_score = np.array(ctc_score, dtype=np.float16)
|
| 248 |
+
else:
|
| 249 |
+
ctc_score = np.array(ctc_score, dtype=np.float32)
|
| 250 |
+
hyps_pad_sos_eos = np.ones(
|
| 251 |
+
(batch_size, beam_size, max_len + 2),
|
| 252 |
+
dtype=np.int64) * IGNORE_ID
|
| 253 |
+
r_hyps_pad_sos_eos = np.ones(
|
| 254 |
+
(batch_size, beam_size, max_len + 2),
|
| 255 |
+
dtype=np.int64) * IGNORE_ID
|
| 256 |
+
hyps_lens_sos = np.ones((batch_size, beam_size),
|
| 257 |
+
dtype=np.int32)
|
| 258 |
+
k = 0
|
| 259 |
+
for i in range(batch_size):
|
| 260 |
+
for j in range(beam_size):
|
| 261 |
+
cand = all_hyps[k]
|
| 262 |
+
l = len(cand) + 2
|
| 263 |
+
hyps_pad_sos_eos[i][j][0:l] = [sos] + cand + [eos]
|
| 264 |
+
r_hyps_pad_sos_eos[i][j][0:l] = [sos] + cand[::-1] + [
|
| 265 |
+
eos
|
| 266 |
+
]
|
| 267 |
+
hyps_lens_sos[i][j] = len(cand) + 1
|
| 268 |
+
k += 1
|
| 269 |
+
decoder_ort_inputs = {
|
| 270 |
+
decoder_ort_session.get_inputs()[0].name: encoder_out,
|
| 271 |
+
decoder_ort_session.get_inputs()[1].name: encoder_out_lens,
|
| 272 |
+
decoder_ort_session.get_inputs()[2].name: hyps_pad_sos_eos,
|
| 273 |
+
decoder_ort_session.get_inputs()[3].name: hyps_lens_sos,
|
| 274 |
+
decoder_ort_session.get_inputs()[-1].name: ctc_score
|
| 275 |
+
}
|
| 276 |
+
if reverse_weight > 0:
|
| 277 |
+
r_hyps_pad_sos_eos_name = decoder_ort_session.get_inputs(
|
| 278 |
+
)[4].name
|
| 279 |
+
decoder_ort_inputs[
|
| 280 |
+
r_hyps_pad_sos_eos_name] = r_hyps_pad_sos_eos
|
| 281 |
+
best_index = decoder_ort_session.run(None,
|
| 282 |
+
decoder_ort_inputs)[0]
|
| 283 |
+
best_sents = []
|
| 284 |
+
k = 0
|
| 285 |
+
for idx in best_index:
|
| 286 |
+
cur_best_sent = all_hyps[k:k + beam_size][idx]
|
| 287 |
+
best_sents.append(cur_best_sent)
|
| 288 |
+
k += beam_size
|
| 289 |
+
hyps = map_batch(best_sents, vocabulary, num_processes)
|
| 290 |
+
|
| 291 |
+
for i, key in enumerate(keys):
|
| 292 |
+
content = hyps[i]
|
| 293 |
+
logging.info('{} {}'.format(key, content))
|
| 294 |
+
fout.write('{} {}\n'.format(key, content))
|
| 295 |
+
|
| 296 |
+
if __name__ == '__main__':
|
| 297 |
+
main()
|
wenet/bin/train.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 Mobvoi Inc. (authors: Binbin Zhang)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
from __future__ import print_function
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import datetime
|
| 19 |
+
import logging
|
| 20 |
+
import os
|
| 21 |
+
import torch
|
| 22 |
+
import yaml
|
| 23 |
+
|
| 24 |
+
import torch.distributed as dist
|
| 25 |
+
|
| 26 |
+
from torch.distributed.elastic.multiprocessing.errors import record
|
| 27 |
+
from wenet.utils.common import lrs_to_str, TORCH_NPU_AVAILABLE # noqa just ensure to check torch-npu
|
| 28 |
+
|
| 29 |
+
from wenet.utils.executor import Executor
|
| 30 |
+
from wenet.utils.config import override_config
|
| 31 |
+
from wenet.utils.init_model import init_model
|
| 32 |
+
from wenet.utils.init_tokenizer import init_tokenizer
|
| 33 |
+
from wenet.utils.train_utils import (
|
| 34 |
+
add_fsdp_args, add_model_args, add_dataset_args, add_ddp_args,
|
| 35 |
+
add_deepspeed_args, add_trace_args, init_distributed,
|
| 36 |
+
init_dataset_and_dataloader, check_modify_and_save_config,
|
| 37 |
+
init_optimizer_and_scheduler, init_scaler, trace_and_print_model,
|
| 38 |
+
wrap_cuda_model, init_summarywriter, save_model, log_per_epoch,
|
| 39 |
+
add_lora_args, reinit_lora)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def get_args():
|
| 43 |
+
parser = argparse.ArgumentParser(description='training your network')
|
| 44 |
+
parser.add_argument('--train_engine',
|
| 45 |
+
default='torch_ddp',
|
| 46 |
+
choices=['torch_ddp', 'torch_fsdp', 'deepspeed'],
|
| 47 |
+
help='Engine for paralleled training')
|
| 48 |
+
# set default value of device to "cuda", avoiding the modify of original scripts
|
| 49 |
+
parser.add_argument('--device',
|
| 50 |
+
type=str,
|
| 51 |
+
default='cuda',
|
| 52 |
+
choices=["cpu", "npu", "cuda"],
|
| 53 |
+
help='accelerator for training')
|
| 54 |
+
parser = add_model_args(parser)
|
| 55 |
+
parser = add_dataset_args(parser)
|
| 56 |
+
parser = add_ddp_args(parser)
|
| 57 |
+
parser = add_lora_args(parser)
|
| 58 |
+
parser = add_deepspeed_args(parser)
|
| 59 |
+
parser = add_fsdp_args(parser)
|
| 60 |
+
parser = add_trace_args(parser)
|
| 61 |
+
args = parser.parse_args()
|
| 62 |
+
if args.train_engine == "deepspeed":
|
| 63 |
+
args.deepspeed = True
|
| 64 |
+
assert args.deepspeed_config is not None
|
| 65 |
+
return args
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# NOTE(xcsong): On worker errors, this recod tool will summarize the
|
| 69 |
+
# details of the error (e.g. time, rank, host, pid, traceback, etc).
|
| 70 |
+
@record
|
| 71 |
+
def main():
|
| 72 |
+
args = get_args()
|
| 73 |
+
logging.basicConfig(level=logging.DEBUG,
|
| 74 |
+
format='%(asctime)s %(levelname)s %(message)s')
|
| 75 |
+
|
| 76 |
+
# Set random seed
|
| 77 |
+
torch.manual_seed(777)
|
| 78 |
+
|
| 79 |
+
# Read config
|
| 80 |
+
with open(args.config, 'r') as fin:
|
| 81 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 82 |
+
if len(args.override_config) > 0:
|
| 83 |
+
configs = override_config(configs, args.override_config)
|
| 84 |
+
|
| 85 |
+
# init tokenizer
|
| 86 |
+
tokenizer = init_tokenizer(configs)
|
| 87 |
+
|
| 88 |
+
# Init env for ddp OR deepspeed
|
| 89 |
+
_, _, rank = init_distributed(args)
|
| 90 |
+
|
| 91 |
+
# Get dataset & dataloader
|
| 92 |
+
train_dataset, cv_dataset, train_data_loader, cv_data_loader = \
|
| 93 |
+
init_dataset_and_dataloader(args, configs, tokenizer)
|
| 94 |
+
|
| 95 |
+
# Do some sanity checks and save config to arsg.model_dir
|
| 96 |
+
configs = check_modify_and_save_config(args, configs,
|
| 97 |
+
tokenizer.symbol_table)
|
| 98 |
+
|
| 99 |
+
# Init asr model from configs
|
| 100 |
+
model, configs = init_model(args, configs)
|
| 101 |
+
|
| 102 |
+
if hasattr(args, 'lora_reinit') and args.lora_reinit:
|
| 103 |
+
reinit_lora(model, args, configs, tokenizer)
|
| 104 |
+
|
| 105 |
+
# Check model is jitable & print model archtectures
|
| 106 |
+
trace_and_print_model(args, model)
|
| 107 |
+
|
| 108 |
+
# Tensorboard summary
|
| 109 |
+
writer = init_summarywriter(args)
|
| 110 |
+
|
| 111 |
+
# Dispatch model from cpu to gpu
|
| 112 |
+
model, device = wrap_cuda_model(args, model, configs)
|
| 113 |
+
|
| 114 |
+
# Get optimizer & scheduler
|
| 115 |
+
model, optimizer, scheduler = init_optimizer_and_scheduler(
|
| 116 |
+
args, configs, model)
|
| 117 |
+
|
| 118 |
+
# Save checkpoints
|
| 119 |
+
save_model(model,
|
| 120 |
+
info_dict={
|
| 121 |
+
"save_time":
|
| 122 |
+
datetime.datetime.now().strftime('%d/%m/%Y %H:%M:%S'),
|
| 123 |
+
"tag":
|
| 124 |
+
"init",
|
| 125 |
+
**configs
|
| 126 |
+
})
|
| 127 |
+
|
| 128 |
+
# Get executor
|
| 129 |
+
tag = configs["init_infos"].get("tag", "init")
|
| 130 |
+
executor = Executor(global_step=configs["init_infos"].get('step', -1),
|
| 131 |
+
device=device)
|
| 132 |
+
|
| 133 |
+
# Init scaler, used for pytorch amp mixed precision training
|
| 134 |
+
scaler = init_scaler(args)
|
| 135 |
+
|
| 136 |
+
# Start training loop
|
| 137 |
+
start_epoch = configs["init_infos"].get('epoch', 0) + int("epoch_" in tag)
|
| 138 |
+
# if save_interval in configs, steps mode else epoch mode
|
| 139 |
+
end_epoch = configs.get('max_epoch',
|
| 140 |
+
100) if "save_interval" not in configs else 1
|
| 141 |
+
assert start_epoch <= end_epoch
|
| 142 |
+
configs.pop("init_infos", None)
|
| 143 |
+
final_epoch = None
|
| 144 |
+
for epoch in range(start_epoch, end_epoch):
|
| 145 |
+
configs['epoch'] = epoch
|
| 146 |
+
|
| 147 |
+
lrs = [group['lr'] for group in optimizer.param_groups]
|
| 148 |
+
logging.info('Epoch {} Step {} TRAIN info lr {} rank {}'.format(
|
| 149 |
+
epoch, executor.step, lrs_to_str(lrs), rank))
|
| 150 |
+
|
| 151 |
+
dist.barrier(
|
| 152 |
+
) # NOTE(xcsong): Ensure all ranks start Train at the same time.
|
| 153 |
+
# NOTE(xcsong): Why we need a new group? see `train_utils.py::wenet_join`
|
| 154 |
+
group_join = dist.new_group(
|
| 155 |
+
backend="gloo", timeout=datetime.timedelta(seconds=args.timeout))
|
| 156 |
+
executor.train(model, optimizer, scheduler, train_data_loader,
|
| 157 |
+
cv_data_loader, writer, configs, scaler, group_join)
|
| 158 |
+
dist.destroy_process_group(group_join)
|
| 159 |
+
|
| 160 |
+
dist.barrier(
|
| 161 |
+
) # NOTE(xcsong): Ensure all ranks start CV at the same time.
|
| 162 |
+
loss_dict = executor.cv(model, cv_data_loader, configs)
|
| 163 |
+
info_dict = {
|
| 164 |
+
'epoch': epoch,
|
| 165 |
+
'lrs': [group['lr'] for group in optimizer.param_groups],
|
| 166 |
+
'step': executor.step,
|
| 167 |
+
'save_time': datetime.datetime.now().strftime('%d/%m/%Y %H:%M:%S'),
|
| 168 |
+
'tag': "epoch_{}".format(epoch),
|
| 169 |
+
'loss_dict': loss_dict,
|
| 170 |
+
**configs
|
| 171 |
+
}
|
| 172 |
+
# epoch cv: tensorboard && log
|
| 173 |
+
log_per_epoch(writer, info_dict=info_dict)
|
| 174 |
+
save_model(model, info_dict=info_dict)
|
| 175 |
+
|
| 176 |
+
final_epoch = epoch
|
| 177 |
+
|
| 178 |
+
if final_epoch is not None and rank == 0:
|
| 179 |
+
final_model_path = os.path.join(args.model_dir, 'final.pt')
|
| 180 |
+
os.remove(final_model_path) if os.path.exists(
|
| 181 |
+
final_model_path) else None
|
| 182 |
+
os.symlink('{}.pt'.format(final_epoch), final_model_path)
|
| 183 |
+
writer.close()
|
| 184 |
+
dist.barrier(
|
| 185 |
+
) # NOTE(yktian): Ensure all ranks end Train before destroy process group.
|
| 186 |
+
dist.destroy_process_group()
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
if __name__ == '__main__':
|
| 190 |
+
main()
|
wenet/branchformer/__init__.py
ADDED
|
File without changes
|
wenet/branchformer/cgmlp.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022 Yifan Peng (Carnegie Mellon University)
|
| 2 |
+
# 2023 Voicecomm Inc (Kai Li)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
# Modified from ESPnet(https://github.com/espnet/espnet)
|
| 16 |
+
"""MLP with convolutional gating (cgMLP) definition.
|
| 17 |
+
|
| 18 |
+
References:
|
| 19 |
+
https://openreview.net/forum?id=RA-zVvZLYIy
|
| 20 |
+
https://arxiv.org/abs/2105.08050
|
| 21 |
+
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
from typing import Tuple
|
| 25 |
+
import torch
|
| 26 |
+
import torch.nn as nn
|
| 27 |
+
from wenet.utils.class_utils import WENET_ACTIVATION_CLASSES
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class ConvolutionalSpatialGatingUnit(torch.nn.Module):
|
| 31 |
+
"""Convolutional Spatial Gating Unit (CSGU)."""
|
| 32 |
+
|
| 33 |
+
def __init__(
|
| 34 |
+
self,
|
| 35 |
+
size: int,
|
| 36 |
+
kernel_size: int,
|
| 37 |
+
dropout_rate: float,
|
| 38 |
+
use_linear_after_conv: bool,
|
| 39 |
+
gate_activation: str,
|
| 40 |
+
causal: bool = True,
|
| 41 |
+
):
|
| 42 |
+
super().__init__()
|
| 43 |
+
|
| 44 |
+
# split input channels
|
| 45 |
+
n_channels = size // 2
|
| 46 |
+
self.norm = nn.LayerNorm(n_channels)
|
| 47 |
+
# self.lorder is used to distinguish if it's a causal convolution,
|
| 48 |
+
# if self.lorder > 0: it's a causal convolution, the input will be
|
| 49 |
+
# padded with self.lorder frames on the left in forward.
|
| 50 |
+
# else: it's a symmetrical convolution
|
| 51 |
+
if causal:
|
| 52 |
+
padding = 0
|
| 53 |
+
self.lorder = kernel_size - 1
|
| 54 |
+
else:
|
| 55 |
+
# kernel_size should be an odd number for none causal convolution
|
| 56 |
+
assert (kernel_size - 1) % 2 == 0
|
| 57 |
+
padding = (kernel_size - 1) // 2
|
| 58 |
+
self.lorder = 0
|
| 59 |
+
self.conv = torch.nn.Conv1d(
|
| 60 |
+
n_channels,
|
| 61 |
+
n_channels,
|
| 62 |
+
kernel_size,
|
| 63 |
+
1,
|
| 64 |
+
padding,
|
| 65 |
+
groups=n_channels,
|
| 66 |
+
)
|
| 67 |
+
if use_linear_after_conv:
|
| 68 |
+
self.linear = torch.nn.Linear(n_channels, n_channels)
|
| 69 |
+
else:
|
| 70 |
+
self.linear = None
|
| 71 |
+
|
| 72 |
+
if gate_activation == "identity":
|
| 73 |
+
self.act = torch.nn.Identity()
|
| 74 |
+
else:
|
| 75 |
+
self.act = WENET_ACTIVATION_CLASSES[gate_activation]()
|
| 76 |
+
|
| 77 |
+
self.dropout = torch.nn.Dropout(dropout_rate)
|
| 78 |
+
|
| 79 |
+
def espnet_initialization_fn(self):
|
| 80 |
+
torch.nn.init.normal_(self.conv.weight, std=1e-6)
|
| 81 |
+
torch.nn.init.ones_(self.conv.bias)
|
| 82 |
+
if self.linear is not None:
|
| 83 |
+
torch.nn.init.normal_(self.linear.weight, std=1e-6)
|
| 84 |
+
torch.nn.init.ones_(self.linear.bias)
|
| 85 |
+
|
| 86 |
+
def forward(
|
| 87 |
+
self, x: torch.Tensor, cache: torch.Tensor = torch.zeros((0, 0, 0))
|
| 88 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 89 |
+
"""Forward method
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
x (torch.Tensor): (batch, time, channels)
|
| 93 |
+
cache (torch.Tensor): left context cache, it is only
|
| 94 |
+
used in causal convolution (#batch, channels, cache_t),
|
| 95 |
+
(0, 0, 0) meas fake cache.
|
| 96 |
+
|
| 97 |
+
Returns:
|
| 98 |
+
out (torch.Tensor): (batch, time, channels/2)
|
| 99 |
+
"""
|
| 100 |
+
|
| 101 |
+
x_r, x_g = x.chunk(2, dim=-1)
|
| 102 |
+
# exchange the temporal dimension and the feature dimension
|
| 103 |
+
x_g = x_g.transpose(1, 2) # (#batch, channels, time)
|
| 104 |
+
|
| 105 |
+
if self.lorder > 0:
|
| 106 |
+
if cache.size(2) == 0: # cache_t == 0
|
| 107 |
+
x_g = nn.functional.pad(x_g, (self.lorder, 0), 'constant', 0.0)
|
| 108 |
+
else:
|
| 109 |
+
assert cache.size(0) == x_g.size(0) # equal batch
|
| 110 |
+
assert cache.size(1) == x_g.size(1) # equal channel
|
| 111 |
+
x_g = torch.cat((cache, x_g), dim=2)
|
| 112 |
+
assert (x_g.size(2) > self.lorder)
|
| 113 |
+
new_cache = x_g[:, :, -self.lorder:]
|
| 114 |
+
else:
|
| 115 |
+
# It's better we just return None if no cache is required,
|
| 116 |
+
# However, for JIT export, here we just fake one tensor instead of
|
| 117 |
+
# None.
|
| 118 |
+
new_cache = torch.zeros((0, 0, 0),
|
| 119 |
+
dtype=x_g.dtype,
|
| 120 |
+
device=x_g.device)
|
| 121 |
+
|
| 122 |
+
x_g = x_g.transpose(1, 2)
|
| 123 |
+
x_g = self.norm(x_g) # (N, T, D/2)
|
| 124 |
+
x_g = self.conv(x_g.transpose(1, 2)).transpose(1, 2) # (N, T, D/2)
|
| 125 |
+
if self.linear is not None:
|
| 126 |
+
x_g = self.linear(x_g)
|
| 127 |
+
|
| 128 |
+
x_g = self.act(x_g)
|
| 129 |
+
out = x_r * x_g # (N, T, D/2)
|
| 130 |
+
out = self.dropout(out)
|
| 131 |
+
return out, new_cache
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class ConvolutionalGatingMLP(torch.nn.Module):
|
| 135 |
+
"""Convolutional Gating MLP (cgMLP)."""
|
| 136 |
+
|
| 137 |
+
def __init__(
|
| 138 |
+
self,
|
| 139 |
+
size: int,
|
| 140 |
+
linear_units: int,
|
| 141 |
+
kernel_size: int,
|
| 142 |
+
dropout_rate: float,
|
| 143 |
+
use_linear_after_conv: bool,
|
| 144 |
+
gate_activation: str,
|
| 145 |
+
causal: bool = True,
|
| 146 |
+
):
|
| 147 |
+
super().__init__()
|
| 148 |
+
|
| 149 |
+
self.channel_proj1 = torch.nn.Sequential(
|
| 150 |
+
torch.nn.Linear(size, linear_units), torch.nn.GELU())
|
| 151 |
+
self.csgu = ConvolutionalSpatialGatingUnit(
|
| 152 |
+
size=linear_units,
|
| 153 |
+
kernel_size=kernel_size,
|
| 154 |
+
dropout_rate=dropout_rate,
|
| 155 |
+
use_linear_after_conv=use_linear_after_conv,
|
| 156 |
+
gate_activation=gate_activation,
|
| 157 |
+
causal=causal,
|
| 158 |
+
)
|
| 159 |
+
self.channel_proj2 = torch.nn.Linear(linear_units // 2, size)
|
| 160 |
+
|
| 161 |
+
def forward(
|
| 162 |
+
self,
|
| 163 |
+
x: torch.Tensor,
|
| 164 |
+
mask: torch.Tensor,
|
| 165 |
+
cache: torch.Tensor = torch.zeros((0, 0, 0))
|
| 166 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 167 |
+
"""Forward method
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
x (torch.Tensor): (batch, time, channels)
|
| 171 |
+
mask_pad (torch.Tensor): used for batch padding (#batch, 1, time),
|
| 172 |
+
(0, 0, 0) means fake mask. Not used yet
|
| 173 |
+
cache (torch.Tensor): left context cache, it is only
|
| 174 |
+
used in causal convolution (#batch, channels, cache_t),
|
| 175 |
+
(0, 0, 0) meas fake cache.
|
| 176 |
+
|
| 177 |
+
Returns:
|
| 178 |
+
out (torch.Tensor): (batch, time, channels/2)
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
xs_pad = x
|
| 182 |
+
|
| 183 |
+
# size -> linear_units
|
| 184 |
+
xs_pad = self.channel_proj1(xs_pad)
|
| 185 |
+
|
| 186 |
+
# linear_units -> linear_units/2
|
| 187 |
+
xs_pad, new_cnn_cache = self.csgu(xs_pad, cache)
|
| 188 |
+
|
| 189 |
+
# linear_units/2 -> size
|
| 190 |
+
xs_pad = self.channel_proj2(xs_pad)
|
| 191 |
+
|
| 192 |
+
out = xs_pad
|
| 193 |
+
|
| 194 |
+
return out, new_cnn_cache
|
wenet/branchformer/encoder.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022 Yifan Peng (Carnegie Mellon University)
|
| 2 |
+
# 2023 Voicecomm Inc (Kai Li)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
# Modified from ESPnet(https://github.com/espnet/espnet)
|
| 16 |
+
"""Encoder definition."""
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from typing import List, Optional, Union
|
| 21 |
+
|
| 22 |
+
from wenet.branchformer.encoder_layer import BranchformerEncoderLayer
|
| 23 |
+
from wenet.branchformer.cgmlp import ConvolutionalGatingMLP
|
| 24 |
+
from wenet.transformer.encoder import BaseEncoder
|
| 25 |
+
from wenet.utils.class_utils import (
|
| 26 |
+
WENET_ATTENTION_CLASSES, )
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class BranchformerEncoder(BaseEncoder):
|
| 30 |
+
"""Branchformer encoder module."""
|
| 31 |
+
|
| 32 |
+
def __init__(
|
| 33 |
+
self,
|
| 34 |
+
input_size: int,
|
| 35 |
+
output_size: int = 256,
|
| 36 |
+
use_attn: bool = True,
|
| 37 |
+
attention_heads: int = 4,
|
| 38 |
+
selfattention_layer_type: str = "rel_selfattn",
|
| 39 |
+
pos_enc_layer_type: str = "rel_pos",
|
| 40 |
+
use_cgmlp: bool = True,
|
| 41 |
+
cgmlp_linear_units: int = 2048,
|
| 42 |
+
cgmlp_conv_kernel: int = 31,
|
| 43 |
+
use_linear_after_conv: bool = False,
|
| 44 |
+
gate_activation: str = "identity",
|
| 45 |
+
merge_method: str = "concat",
|
| 46 |
+
cgmlp_weight: Union[float, List[float]] = 0.5,
|
| 47 |
+
attn_branch_drop_rate: Union[float, List[float]] = 0.0,
|
| 48 |
+
num_blocks: int = 12,
|
| 49 |
+
dropout_rate: float = 0.1,
|
| 50 |
+
positional_dropout_rate: float = 0.1,
|
| 51 |
+
attention_dropout_rate: float = 0.0,
|
| 52 |
+
input_layer: str = "conv2d",
|
| 53 |
+
stochastic_depth_rate: Union[float, List[float]] = 0.0,
|
| 54 |
+
static_chunk_size: int = 0,
|
| 55 |
+
use_dynamic_chunk: bool = False,
|
| 56 |
+
global_cmvn: torch.nn.Module = None,
|
| 57 |
+
use_dynamic_left_chunk: bool = False,
|
| 58 |
+
causal: bool = False,
|
| 59 |
+
query_bias: bool = True,
|
| 60 |
+
key_bias: bool = True,
|
| 61 |
+
value_bias: bool = True,
|
| 62 |
+
gradient_checkpointing: bool = False,
|
| 63 |
+
use_sdpa: bool = False,
|
| 64 |
+
layer_norm_type: str = 'layer_norm',
|
| 65 |
+
norm_eps: float = 1e-5,
|
| 66 |
+
n_kv_head: Optional[int] = None,
|
| 67 |
+
head_dim: Optional[int] = None,
|
| 68 |
+
):
|
| 69 |
+
super().__init__(input_size, output_size, attention_heads,
|
| 70 |
+
cgmlp_linear_units, num_blocks, dropout_rate,
|
| 71 |
+
positional_dropout_rate, attention_dropout_rate,
|
| 72 |
+
input_layer, pos_enc_layer_type, True,
|
| 73 |
+
static_chunk_size, use_dynamic_chunk, global_cmvn,
|
| 74 |
+
use_dynamic_left_chunk, gradient_checkpointing,
|
| 75 |
+
use_sdpa, layer_norm_type, norm_eps)
|
| 76 |
+
|
| 77 |
+
encoder_selfattn_layer_args = (
|
| 78 |
+
attention_heads,
|
| 79 |
+
output_size,
|
| 80 |
+
attention_dropout_rate,
|
| 81 |
+
query_bias,
|
| 82 |
+
key_bias,
|
| 83 |
+
value_bias,
|
| 84 |
+
use_sdpa,
|
| 85 |
+
n_kv_head,
|
| 86 |
+
head_dim,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
cgmlp_layer = ConvolutionalGatingMLP
|
| 90 |
+
cgmlp_layer_args = (
|
| 91 |
+
output_size,
|
| 92 |
+
cgmlp_linear_units,
|
| 93 |
+
cgmlp_conv_kernel,
|
| 94 |
+
dropout_rate,
|
| 95 |
+
use_linear_after_conv,
|
| 96 |
+
gate_activation,
|
| 97 |
+
causal,
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
if isinstance(stochastic_depth_rate, float):
|
| 101 |
+
stochastic_depth_rate = [stochastic_depth_rate] * num_blocks
|
| 102 |
+
if len(stochastic_depth_rate) != num_blocks:
|
| 103 |
+
raise ValueError(
|
| 104 |
+
f"Length of stochastic_depth_rate ({len(stochastic_depth_rate)}) "
|
| 105 |
+
f"should be equal to num_blocks ({num_blocks})")
|
| 106 |
+
|
| 107 |
+
if isinstance(cgmlp_weight, float):
|
| 108 |
+
cgmlp_weight = [cgmlp_weight] * num_blocks
|
| 109 |
+
if len(cgmlp_weight) != num_blocks:
|
| 110 |
+
raise ValueError(
|
| 111 |
+
f"Length of cgmlp_weight ({len(cgmlp_weight)}) should be equal to "
|
| 112 |
+
f"num_blocks ({num_blocks})")
|
| 113 |
+
|
| 114 |
+
if isinstance(attn_branch_drop_rate, float):
|
| 115 |
+
attn_branch_drop_rate = [attn_branch_drop_rate] * num_blocks
|
| 116 |
+
if len(attn_branch_drop_rate) != num_blocks:
|
| 117 |
+
raise ValueError(
|
| 118 |
+
f"Length of attn_branch_drop_rate ({len(attn_branch_drop_rate)}) "
|
| 119 |
+
f"should be equal to num_blocks ({num_blocks})")
|
| 120 |
+
|
| 121 |
+
self.encoders = LayerDropModuleList(
|
| 122 |
+
p=stochastic_depth_rate,
|
| 123 |
+
modules=[
|
| 124 |
+
BranchformerEncoderLayer(
|
| 125 |
+
output_size,
|
| 126 |
+
WENET_ATTENTION_CLASSES[selfattention_layer_type](
|
| 127 |
+
*encoder_selfattn_layer_args) if use_attn else None,
|
| 128 |
+
cgmlp_layer(*cgmlp_layer_args) if use_cgmlp else None,
|
| 129 |
+
dropout_rate,
|
| 130 |
+
merge_method,
|
| 131 |
+
cgmlp_weight[lnum],
|
| 132 |
+
attn_branch_drop_rate[lnum],
|
| 133 |
+
stochastic_depth_rate[lnum],
|
| 134 |
+
) for lnum in range(num_blocks)
|
| 135 |
+
])
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
# modify from : https://github.com/facebookresearch/fairseq/blob/main/fairseq/modules/layer_drop.py # noqa
|
| 139 |
+
class LayerDropModuleList(torch.nn.ModuleList):
|
| 140 |
+
"""
|
| 141 |
+
A LayerDrop implementation based on :class:`torch.nn.ModuleList`.
|
| 142 |
+
|
| 143 |
+
We refresh the choice of which layers to drop every time we iterate
|
| 144 |
+
over the LayerDropModuleList instance. During evaluation we always
|
| 145 |
+
iterate over all layers.
|
| 146 |
+
|
| 147 |
+
Usage::
|
| 148 |
+
|
| 149 |
+
layers = LayerDropList(p=0.5, modules=[layer1, layer2, layer3])
|
| 150 |
+
for layer in layers: # this might iterate over layers 1 and 3
|
| 151 |
+
x = layer(x)
|
| 152 |
+
for layer in layers: # this might iterate over all layers
|
| 153 |
+
x = layer(x)
|
| 154 |
+
for layer in layers: # this might not iterate over any layers
|
| 155 |
+
x = layer(x)
|
| 156 |
+
|
| 157 |
+
Args:
|
| 158 |
+
p (float): probability of dropping out each layer
|
| 159 |
+
modules (iterable, optional): an iterable of modules to add
|
| 160 |
+
|
| 161 |
+
Limitations:
|
| 162 |
+
1 can work with ddp when layer's gradient checkpoint disabled
|
| 163 |
+
2 can't work with ddp when layer's gradient checkpoint enables
|
| 164 |
+
3 can work with fsdp
|
| 165 |
+
4 can work with deepspeed
|
| 166 |
+
"""
|
| 167 |
+
|
| 168 |
+
def __init__(self, p: List[float], modules=None):
|
| 169 |
+
super().__init__(modules)
|
| 170 |
+
assert len(p) == len(self)
|
| 171 |
+
self.p = p
|
| 172 |
+
|
| 173 |
+
def __iter__(self):
|
| 174 |
+
dropout_probs = torch.empty(len(self)).uniform_()
|
| 175 |
+
for i, m in enumerate(super().__iter__()):
|
| 176 |
+
if not self.training or (dropout_probs[i] > self.p[i]):
|
| 177 |
+
yield m
|
wenet/branchformer/encoder_layer.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022 Yifan Peng (Carnegie Mellon University)
|
| 2 |
+
# 2023 Voicecomm Inc (Kai Li)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
# Modified from ESPnet(https://github.com/espnet/espnet)
|
| 16 |
+
"""BranchformerEncoderLayer definition."""
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
import torch.nn as nn
|
| 20 |
+
from typing import Optional, Tuple
|
| 21 |
+
|
| 22 |
+
from wenet.transformer.attention import T_CACHE
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class BranchformerEncoderLayer(torch.nn.Module):
|
| 26 |
+
"""Branchformer encoder layer module.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
size (int): model dimension
|
| 30 |
+
attn: standard self-attention or efficient attention, optional
|
| 31 |
+
cgmlp: ConvolutionalGatingMLP, optional
|
| 32 |
+
dropout_rate (float): dropout probability
|
| 33 |
+
merge_method (str): concat, learned_ave, fixed_ave
|
| 34 |
+
cgmlp_weight (float): weight of the cgmlp branch, between 0 and 1,
|
| 35 |
+
used if merge_method is fixed_ave
|
| 36 |
+
attn_branch_drop_rate (float): probability of dropping the attn branch,
|
| 37 |
+
used if merge_method is learned_ave
|
| 38 |
+
stochastic_depth_rate (float): stochastic depth probability
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
def __init__(
|
| 42 |
+
self,
|
| 43 |
+
size: int,
|
| 44 |
+
attn: Optional[torch.nn.Module],
|
| 45 |
+
cgmlp: Optional[torch.nn.Module],
|
| 46 |
+
dropout_rate: float,
|
| 47 |
+
merge_method: str,
|
| 48 |
+
cgmlp_weight: float = 0.5,
|
| 49 |
+
attn_branch_drop_rate: float = 0.0,
|
| 50 |
+
stochastic_depth_rate: float = 0.0,
|
| 51 |
+
):
|
| 52 |
+
super().__init__()
|
| 53 |
+
assert (attn is not None) or (
|
| 54 |
+
cgmlp is not None), "At least one branch should be valid"
|
| 55 |
+
|
| 56 |
+
self.size = size
|
| 57 |
+
self.attn = attn
|
| 58 |
+
self.cgmlp = cgmlp
|
| 59 |
+
self.merge_method = merge_method
|
| 60 |
+
self.cgmlp_weight = cgmlp_weight
|
| 61 |
+
self.attn_branch_drop_rate = attn_branch_drop_rate
|
| 62 |
+
self.stochastic_depth_rate = stochastic_depth_rate
|
| 63 |
+
self.use_two_branches = (attn is not None) and (cgmlp is not None)
|
| 64 |
+
|
| 65 |
+
if attn is not None:
|
| 66 |
+
self.norm_mha = nn.LayerNorm(size) # for the MHA module
|
| 67 |
+
if cgmlp is not None:
|
| 68 |
+
self.norm_mlp = nn.LayerNorm(size) # for the MLP module
|
| 69 |
+
self.norm_final = nn.LayerNorm(
|
| 70 |
+
size) # for the final output of the block
|
| 71 |
+
|
| 72 |
+
self.dropout = torch.nn.Dropout(dropout_rate)
|
| 73 |
+
|
| 74 |
+
# # attention-based pooling for two branches
|
| 75 |
+
self.pooling_proj1 = torch.nn.Linear(size, 1)
|
| 76 |
+
self.pooling_proj2 = torch.nn.Linear(size, 1)
|
| 77 |
+
|
| 78 |
+
# # linear projections for calculating merging weights
|
| 79 |
+
self.weight_proj1 = torch.nn.Linear(size, 1)
|
| 80 |
+
self.weight_proj2 = torch.nn.Linear(size, 1)
|
| 81 |
+
|
| 82 |
+
if self.use_two_branches:
|
| 83 |
+
if self.merge_method == "concat":
|
| 84 |
+
self.merge_proj = torch.nn.Linear(size + size, size)
|
| 85 |
+
|
| 86 |
+
elif self.merge_method == "learned_ave":
|
| 87 |
+
# linear projection after weighted average
|
| 88 |
+
self.merge_proj = torch.nn.Linear(size, size)
|
| 89 |
+
|
| 90 |
+
elif self.merge_method == "fixed_ave":
|
| 91 |
+
assert (0.0 <= cgmlp_weight <=
|
| 92 |
+
1.0), "cgmlp weight should be between 0.0 and 1.0"
|
| 93 |
+
|
| 94 |
+
# remove the other branch if only one branch is used
|
| 95 |
+
if cgmlp_weight == 0.0:
|
| 96 |
+
self.use_two_branches = False
|
| 97 |
+
self.cgmlp = None
|
| 98 |
+
self.norm_mlp = None
|
| 99 |
+
elif cgmlp_weight == 1.0:
|
| 100 |
+
self.use_two_branches = False
|
| 101 |
+
self.attn = None
|
| 102 |
+
self.norm_mha = None
|
| 103 |
+
|
| 104 |
+
# linear projection after weighted average
|
| 105 |
+
self.merge_proj = torch.nn.Linear(size, size)
|
| 106 |
+
else:
|
| 107 |
+
raise ValueError(f"unknown merge method: {merge_method}")
|
| 108 |
+
else:
|
| 109 |
+
self.merge_proj = torch.nn.Identity()
|
| 110 |
+
|
| 111 |
+
def _forward(
|
| 112 |
+
self,
|
| 113 |
+
x: torch.Tensor,
|
| 114 |
+
mask: torch.Tensor,
|
| 115 |
+
pos_emb: torch.Tensor,
|
| 116 |
+
mask_pad: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
|
| 117 |
+
att_cache: T_CACHE = (torch.zeros(
|
| 118 |
+
(0, 0, 0, 0)), torch.zeros(0, 0, 0, 0)),
|
| 119 |
+
cnn_cache: torch.Tensor = torch.zeros((0, 0, 0, 0)),
|
| 120 |
+
stoch_layer_coeff: float = 1.0
|
| 121 |
+
) -> Tuple[torch.Tensor, torch.Tensor, T_CACHE, torch.Tensor]:
|
| 122 |
+
# Two branches
|
| 123 |
+
x1 = x
|
| 124 |
+
x2 = x
|
| 125 |
+
|
| 126 |
+
# Branch 1: multi-headed attention module
|
| 127 |
+
if self.attn is not None:
|
| 128 |
+
x1 = self.norm_mha(x1)
|
| 129 |
+
x_att, new_att_cache = self.attn(x1, x1, x1, mask, pos_emb,
|
| 130 |
+
att_cache)
|
| 131 |
+
x1 = self.dropout(x_att)
|
| 132 |
+
|
| 133 |
+
# Branch 2: convolutional gating mlp
|
| 134 |
+
# Fake new cnn cache here, and then change it in conv_module
|
| 135 |
+
new_cnn_cache = torch.zeros((0, 0, 0), dtype=x.dtype, device=x.device)
|
| 136 |
+
if self.cgmlp is not None:
|
| 137 |
+
x2 = self.norm_mlp(x2)
|
| 138 |
+
x2, new_cnn_cache = self.cgmlp(x2, mask_pad, cnn_cache)
|
| 139 |
+
x2 = self.dropout(x2)
|
| 140 |
+
|
| 141 |
+
# Merge two branches
|
| 142 |
+
if self.use_two_branches:
|
| 143 |
+
if self.merge_method == "concat":
|
| 144 |
+
x = x + stoch_layer_coeff * self.dropout(
|
| 145 |
+
self.merge_proj(torch.cat([x1, x2], dim=-1)))
|
| 146 |
+
elif self.merge_method == "learned_ave":
|
| 147 |
+
if (self.training and self.attn_branch_drop_rate > 0
|
| 148 |
+
and torch.rand(1).item() < self.attn_branch_drop_rate):
|
| 149 |
+
# Drop the attn branch
|
| 150 |
+
w1, w2 = torch.tensor(0.0), torch.tensor(1.0)
|
| 151 |
+
else:
|
| 152 |
+
# branch1
|
| 153 |
+
score1 = (self.pooling_proj1(x1).transpose(1, 2) /
|
| 154 |
+
self.size**0.5)
|
| 155 |
+
score1 = score1.masked_fill(mask_pad.eq(0), -float('inf'))
|
| 156 |
+
score1 = torch.softmax(score1, dim=-1).masked_fill(
|
| 157 |
+
mask_pad.eq(0), 0.0)
|
| 158 |
+
|
| 159 |
+
pooled1 = torch.matmul(score1,
|
| 160 |
+
x1).squeeze(1) # (batch, size)
|
| 161 |
+
weight1 = self.weight_proj1(pooled1) # (batch, 1)
|
| 162 |
+
|
| 163 |
+
# branch2
|
| 164 |
+
score2 = (self.pooling_proj2(x2).transpose(1, 2) /
|
| 165 |
+
self.size**0.5)
|
| 166 |
+
score2 = score2.masked_fill(mask_pad.eq(0), -float('inf'))
|
| 167 |
+
score2 = torch.softmax(score2, dim=-1).masked_fill(
|
| 168 |
+
mask_pad.eq(0), 0.0)
|
| 169 |
+
|
| 170 |
+
pooled2 = torch.matmul(score2,
|
| 171 |
+
x2).squeeze(1) # (batch, size)
|
| 172 |
+
weight2 = self.weight_proj2(pooled2) # (batch, 1)
|
| 173 |
+
|
| 174 |
+
# normalize weights of two branches
|
| 175 |
+
merge_weights = torch.softmax(torch.cat([weight1, weight2],
|
| 176 |
+
dim=-1),
|
| 177 |
+
dim=-1) # (batch, 2)
|
| 178 |
+
merge_weights = merge_weights.unsqueeze(-1).unsqueeze(
|
| 179 |
+
-1) # (batch, 2, 1, 1)
|
| 180 |
+
w1, w2 = merge_weights[:,
|
| 181 |
+
0], merge_weights[:,
|
| 182 |
+
1] # (batch, 1, 1)
|
| 183 |
+
|
| 184 |
+
x = x + stoch_layer_coeff * self.dropout(
|
| 185 |
+
self.merge_proj(w1 * x1 + w2 * x2))
|
| 186 |
+
elif self.merge_method == "fixed_ave":
|
| 187 |
+
x = x + stoch_layer_coeff * self.dropout(
|
| 188 |
+
self.merge_proj((1.0 - self.cgmlp_weight) * x1 +
|
| 189 |
+
self.cgmlp_weight * x2))
|
| 190 |
+
else:
|
| 191 |
+
raise RuntimeError(
|
| 192 |
+
f"unknown merge method: {self.merge_method}")
|
| 193 |
+
else:
|
| 194 |
+
if self.attn is None:
|
| 195 |
+
x = x + stoch_layer_coeff * self.dropout(self.merge_proj(x2))
|
| 196 |
+
elif self.cgmlp is None:
|
| 197 |
+
x = x + stoch_layer_coeff * self.dropout(self.merge_proj(x1))
|
| 198 |
+
else:
|
| 199 |
+
# This should not happen
|
| 200 |
+
raise RuntimeError(
|
| 201 |
+
"Both branches are not None, which is unexpected.")
|
| 202 |
+
|
| 203 |
+
x = self.norm_final(x)
|
| 204 |
+
|
| 205 |
+
return x, mask, new_att_cache, new_cnn_cache
|
| 206 |
+
|
| 207 |
+
def forward(
|
| 208 |
+
self,
|
| 209 |
+
x: torch.Tensor,
|
| 210 |
+
mask: torch.Tensor,
|
| 211 |
+
pos_emb: torch.Tensor,
|
| 212 |
+
mask_pad: torch.Tensor = torch.ones((0, 0, 0), dtype=torch.bool),
|
| 213 |
+
att_cache: T_CACHE = (torch.zeros(
|
| 214 |
+
(0, 0, 0, 0)), torch.zeros(0, 0, 0, 0)),
|
| 215 |
+
cnn_cache: torch.Tensor = torch.zeros((0, 0, 0, 0)),
|
| 216 |
+
) -> Tuple[torch.Tensor, torch.Tensor, T_CACHE, torch.Tensor]:
|
| 217 |
+
"""Compute encoded features.
|
| 218 |
+
|
| 219 |
+
Args:
|
| 220 |
+
x (Union[Tuple, torch.Tensor]): Input tensor (#batch, time, size).
|
| 221 |
+
mask (torch.Tensor): Mask tensor for the input (#batch, time, time).
|
| 222 |
+
pos_emb (torch.Tensor): positional encoding, must not be None
|
| 223 |
+
for BranchformerEncoderLayer.
|
| 224 |
+
mask_pad (torch.Tensor): batch padding mask used for conv module.
|
| 225 |
+
(#batch, 1,time), (0, 0, 0) means fake mask.
|
| 226 |
+
att_cache (torch.Tensor): Cache tensor of the KEY & VALUE
|
| 227 |
+
(#batch=1, head, cache_t1, d_k * 2), head * d_k == size.
|
| 228 |
+
cnn_cache (torch.Tensor): Convolution cache in cgmlp layer
|
| 229 |
+
(#batch=1, size, cache_t2)
|
| 230 |
+
|
| 231 |
+
Returns:
|
| 232 |
+
torch.Tensor: Output tensor (#batch, time, size).
|
| 233 |
+
torch.Tensor: Mask tensor (#batch, time, time.
|
| 234 |
+
torch.Tensor: att_cache tensor,
|
| 235 |
+
(#batch=1, head, cache_t1 + time, d_k * 2).
|
| 236 |
+
torch.Tensor: cnn_cahce tensor (#batch, size, cache_t2).
|
| 237 |
+
"""
|
| 238 |
+
|
| 239 |
+
stoch_layer_coeff = 1.0
|
| 240 |
+
# with stochastic depth, residual connection `x + f(x)` becomes
|
| 241 |
+
# `x <- x + 1 / (1 - p) * f(x)` at training time.
|
| 242 |
+
if self.training:
|
| 243 |
+
stoch_layer_coeff = 1.0 / (1 - self.stochastic_depth_rate)
|
| 244 |
+
return self._forward(x, mask, pos_emb, mask_pad, att_cache, cnn_cache,
|
| 245 |
+
stoch_layer_coeff)
|
wenet/cli/__init__.py
ADDED
|
File without changes
|
wenet/cli/hub.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2022 Mddct(hamddct@gmail.com)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
import tarfile
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
from urllib.request import urlretrieve
|
| 20 |
+
|
| 21 |
+
import requests
|
| 22 |
+
import tqdm
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def download(url: str, dest: str, only_child=True):
|
| 26 |
+
""" download from url to dest
|
| 27 |
+
"""
|
| 28 |
+
assert os.path.exists(dest)
|
| 29 |
+
print('Downloading {} to {}'.format(url, dest))
|
| 30 |
+
|
| 31 |
+
def progress_hook(t):
|
| 32 |
+
last_b = [0]
|
| 33 |
+
|
| 34 |
+
def update_to(b=1, bsize=1, tsize=None):
|
| 35 |
+
if tsize not in (None, -1):
|
| 36 |
+
t.total = tsize
|
| 37 |
+
displayed = t.update((b - last_b[0]) * bsize)
|
| 38 |
+
last_b[0] = b
|
| 39 |
+
return displayed
|
| 40 |
+
|
| 41 |
+
return update_to
|
| 42 |
+
|
| 43 |
+
# *.tar.gz
|
| 44 |
+
name = url.split('?')[0].split('/')[-1]
|
| 45 |
+
tar_path = os.path.join(dest, name)
|
| 46 |
+
with tqdm.tqdm(unit='B',
|
| 47 |
+
unit_scale=True,
|
| 48 |
+
unit_divisor=1024,
|
| 49 |
+
miniters=1,
|
| 50 |
+
desc=(name)) as t:
|
| 51 |
+
urlretrieve(url,
|
| 52 |
+
filename=tar_path,
|
| 53 |
+
reporthook=progress_hook(t),
|
| 54 |
+
data=None)
|
| 55 |
+
t.total = t.n
|
| 56 |
+
|
| 57 |
+
with tarfile.open(tar_path) as f:
|
| 58 |
+
if not only_child:
|
| 59 |
+
f.extractall(dest)
|
| 60 |
+
else:
|
| 61 |
+
for tarinfo in f:
|
| 62 |
+
if "/" not in tarinfo.name:
|
| 63 |
+
continue
|
| 64 |
+
name = os.path.basename(tarinfo.name)
|
| 65 |
+
fileobj = f.extractfile(tarinfo)
|
| 66 |
+
with open(os.path.join(dest, name), "wb") as writer:
|
| 67 |
+
writer.write(fileobj.read())
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class Hub(object):
|
| 71 |
+
"""Hub for wenet pretrain runtime model
|
| 72 |
+
"""
|
| 73 |
+
# TODO(Mddct): make assets class to support other language
|
| 74 |
+
Assets = {
|
| 75 |
+
# wenetspeech
|
| 76 |
+
"chinese": "wenetspeech_u2pp_conformer_libtorch.tar.gz",
|
| 77 |
+
# gigaspeech
|
| 78 |
+
"english": "gigaspeech_u2pp_conformer_libtorch.tar.gz",
|
| 79 |
+
# paraformer
|
| 80 |
+
"paraformer": "paraformer.tar.gz",
|
| 81 |
+
# punc
|
| 82 |
+
"punc": "punc.tar.gz"
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
def __init__(self) -> None:
|
| 86 |
+
pass
|
| 87 |
+
|
| 88 |
+
@staticmethod
|
| 89 |
+
def get_model_by_lang(lang: str) -> str:
|
| 90 |
+
if lang not in Hub.Assets.keys():
|
| 91 |
+
print('ERROR: Unsupported language {} !!!'.format(lang))
|
| 92 |
+
sys.exit(1)
|
| 93 |
+
|
| 94 |
+
# NOTE(Mddct): model_dir structure
|
| 95 |
+
# Path.Home()/.wenet
|
| 96 |
+
# - chs
|
| 97 |
+
# - units.txt
|
| 98 |
+
# - final.zip
|
| 99 |
+
# - en
|
| 100 |
+
# - units.txt
|
| 101 |
+
# - final.zip
|
| 102 |
+
model = Hub.Assets[lang]
|
| 103 |
+
model_dir = os.path.join(Path.home(), ".wenet", lang)
|
| 104 |
+
if not os.path.exists(model_dir):
|
| 105 |
+
os.makedirs(model_dir)
|
| 106 |
+
# TODO(Mddct): model metadata
|
| 107 |
+
if set(["final.zip",
|
| 108 |
+
"units.txt"]).issubset(set(os.listdir(model_dir))):
|
| 109 |
+
return model_dir
|
| 110 |
+
# If not exist, download
|
| 111 |
+
response = requests.get(
|
| 112 |
+
"https://modelscope.cn/api/v1/datasets/wenet/wenet_pretrained_models/oss/tree" # noqa
|
| 113 |
+
)
|
| 114 |
+
model_info = next(data for data in response.json()["Data"]
|
| 115 |
+
if data["Key"] == model)
|
| 116 |
+
model_url = model_info['Url']
|
| 117 |
+
download(model_url, model_dir, only_child=True)
|
| 118 |
+
return model_dir
|
wenet/cli/model.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2023 Binbin Zhang (binbzha@qq.com)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torchaudio
|
| 19 |
+
import torchaudio.compliance.kaldi as kaldi
|
| 20 |
+
import yaml
|
| 21 |
+
|
| 22 |
+
from wenet.cli.hub import Hub
|
| 23 |
+
from wenet.utils.ctc_utils import (force_align, gen_ctc_peak_time,
|
| 24 |
+
gen_timestamps_from_peak)
|
| 25 |
+
from wenet.utils.file_utils import read_symbol_table
|
| 26 |
+
from wenet.utils.init_model import init_model
|
| 27 |
+
from wenet.transformer.search import (attention_rescoring,
|
| 28 |
+
ctc_prefix_beam_search, DecodeResult)
|
| 29 |
+
from wenet.utils.context_graph import ContextGraph
|
| 30 |
+
from wenet.utils.common import TORCH_NPU_AVAILABLE # noqa just ensure to check torch-npu
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class Model:
|
| 34 |
+
|
| 35 |
+
def __init__(self,
|
| 36 |
+
model_dir: str,
|
| 37 |
+
gpu: int = -1,
|
| 38 |
+
beam: int = 5,
|
| 39 |
+
context_path: str = None,
|
| 40 |
+
context_score: float = 6.0,
|
| 41 |
+
resample_rate: int = 16000):
|
| 42 |
+
model_path = os.path.join(model_dir, 'final.zip')
|
| 43 |
+
units_path = os.path.join(model_dir, 'units.txt')
|
| 44 |
+
self.model = torch.jit.load(model_path)
|
| 45 |
+
self.resample_rate = resample_rate
|
| 46 |
+
self.model.eval()
|
| 47 |
+
if gpu >= 0:
|
| 48 |
+
device = 'cuda:{}'.format(gpu)
|
| 49 |
+
else:
|
| 50 |
+
device = 'cpu'
|
| 51 |
+
self.device = torch.device(device)
|
| 52 |
+
self.model.to(device)
|
| 53 |
+
self.symbol_table = read_symbol_table(units_path)
|
| 54 |
+
self.char_dict = {v: k for k, v in self.symbol_table.items()}
|
| 55 |
+
self.beam = beam
|
| 56 |
+
if context_path is not None:
|
| 57 |
+
self.context_graph = ContextGraph(context_path,
|
| 58 |
+
self.symbol_table,
|
| 59 |
+
context_score=context_score)
|
| 60 |
+
else:
|
| 61 |
+
self.context_graph = None
|
| 62 |
+
|
| 63 |
+
def compute_feats(self, audio_file: str) -> torch.Tensor:
|
| 64 |
+
waveform, sample_rate = torchaudio.load(audio_file, normalize=False)
|
| 65 |
+
waveform = waveform.to(torch.float)
|
| 66 |
+
if sample_rate != self.resample_rate:
|
| 67 |
+
waveform = torchaudio.transforms.Resample(
|
| 68 |
+
orig_freq=sample_rate, new_freq=self.resample_rate)(waveform)
|
| 69 |
+
# NOTE (MengqingCao): complex dtype not supported in torch_npu.abs() now,
|
| 70 |
+
# thus, delay placing data on NPU after the calculation of fbank.
|
| 71 |
+
# revert me after complex dtype is supported.
|
| 72 |
+
if "npu" not in self.device.__str__():
|
| 73 |
+
waveform = waveform.to(self.device)
|
| 74 |
+
feats = kaldi.fbank(waveform,
|
| 75 |
+
num_mel_bins=80,
|
| 76 |
+
frame_length=25,
|
| 77 |
+
frame_shift=10,
|
| 78 |
+
energy_floor=0.0,
|
| 79 |
+
sample_frequency=self.resample_rate)
|
| 80 |
+
if "npu" in self.device.__str__():
|
| 81 |
+
feats = feats.to(self.device)
|
| 82 |
+
feats = feats.unsqueeze(0)
|
| 83 |
+
return feats
|
| 84 |
+
|
| 85 |
+
@torch.no_grad()
|
| 86 |
+
def _decode(self,
|
| 87 |
+
audio_file: str,
|
| 88 |
+
tokens_info: bool = False,
|
| 89 |
+
label: str = None) -> dict:
|
| 90 |
+
feats = self.compute_feats(audio_file)
|
| 91 |
+
encoder_out, _, _ = self.model.forward_encoder_chunk(feats, 0, -1)
|
| 92 |
+
encoder_lens = torch.tensor([encoder_out.size(1)],
|
| 93 |
+
dtype=torch.long,
|
| 94 |
+
device=encoder_out.device)
|
| 95 |
+
ctc_probs = self.model.ctc_activation(encoder_out)
|
| 96 |
+
if label is None:
|
| 97 |
+
ctc_prefix_results = ctc_prefix_beam_search(
|
| 98 |
+
ctc_probs,
|
| 99 |
+
encoder_lens,
|
| 100 |
+
self.beam,
|
| 101 |
+
context_graph=self.context_graph)
|
| 102 |
+
else: # force align mode, construct ctc prefix result from alignment
|
| 103 |
+
label_t = self.tokenize(label)
|
| 104 |
+
alignment = force_align(ctc_probs.squeeze(0),
|
| 105 |
+
torch.tensor(label_t, dtype=torch.long))
|
| 106 |
+
peaks = gen_ctc_peak_time(alignment)
|
| 107 |
+
ctc_prefix_results = [
|
| 108 |
+
DecodeResult(tokens=label_t,
|
| 109 |
+
score=0.0,
|
| 110 |
+
times=peaks,
|
| 111 |
+
nbest=[label_t],
|
| 112 |
+
nbest_scores=[0.0],
|
| 113 |
+
nbest_times=[peaks])
|
| 114 |
+
]
|
| 115 |
+
rescoring_results = attention_rescoring(self.model, ctc_prefix_results,
|
| 116 |
+
encoder_out, encoder_lens, 0.3,
|
| 117 |
+
0.5)
|
| 118 |
+
res = rescoring_results[0]
|
| 119 |
+
result = {}
|
| 120 |
+
result['text'] = ''.join([self.char_dict[x] for x in res.tokens])
|
| 121 |
+
result['confidence'] = res.confidence
|
| 122 |
+
|
| 123 |
+
if tokens_info:
|
| 124 |
+
frame_rate = self.model.subsampling_rate(
|
| 125 |
+
) * 0.01 # 0.01 seconds per frame
|
| 126 |
+
max_duration = encoder_out.size(1) * frame_rate
|
| 127 |
+
times = gen_timestamps_from_peak(res.times, max_duration,
|
| 128 |
+
frame_rate, 1.0)
|
| 129 |
+
tokens_info = []
|
| 130 |
+
for i, x in enumerate(res.tokens):
|
| 131 |
+
tokens_info.append({
|
| 132 |
+
'token': self.char_dict[x],
|
| 133 |
+
'start': round(times[i][0], 3),
|
| 134 |
+
'end': round(times[i][1], 3),
|
| 135 |
+
'confidence': round(res.tokens_confidence[i], 2)
|
| 136 |
+
})
|
| 137 |
+
result['tokens'] = tokens_info
|
| 138 |
+
return result
|
| 139 |
+
|
| 140 |
+
def transcribe(self, audio_file: str, tokens_info: bool = False) -> dict:
|
| 141 |
+
return self._decode(audio_file, tokens_info)
|
| 142 |
+
|
| 143 |
+
def tokenize(self, label: str):
|
| 144 |
+
# TODO(Binbin Zhang): Support BPE
|
| 145 |
+
tokens = []
|
| 146 |
+
for c in label:
|
| 147 |
+
if c == ' ':
|
| 148 |
+
c = "▁"
|
| 149 |
+
tokens.append(c)
|
| 150 |
+
token_list = []
|
| 151 |
+
for c in tokens:
|
| 152 |
+
if c in self.symbol_table:
|
| 153 |
+
token_list.append(self.symbol_table[c])
|
| 154 |
+
elif '<unk>' in self.symbol_table:
|
| 155 |
+
token_list.append(self.symbol_table['<unk>'])
|
| 156 |
+
return token_list
|
| 157 |
+
|
| 158 |
+
def align(self, audio_file: str, label: str) -> dict:
|
| 159 |
+
return self._decode(audio_file, True, label)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def load_model(language: str = None,
|
| 163 |
+
model_dir: str = None,
|
| 164 |
+
gpu: int = -1,
|
| 165 |
+
beam: int = 5,
|
| 166 |
+
context_path: str = None,
|
| 167 |
+
context_score: float = 6.0,
|
| 168 |
+
device: str = "cpu") -> Model:
|
| 169 |
+
if model_dir is None:
|
| 170 |
+
model_dir = Hub.get_model_by_lang(language)
|
| 171 |
+
|
| 172 |
+
if gpu != -1:
|
| 173 |
+
# remain the original usage of gpu
|
| 174 |
+
device = "cuda"
|
| 175 |
+
model = Model(model_dir, gpu, beam, context_path, context_score)
|
| 176 |
+
model.device = torch.device(device)
|
| 177 |
+
model.model.to(device)
|
| 178 |
+
return model
|
| 179 |
+
|
| 180 |
+
# Load the pytorch pt model which contains all the details compared with jit.
|
| 181 |
+
# And we can use the pt model as a third party pytorch nn.Module for training
|
| 182 |
+
def load_model_pt(model_dir):
|
| 183 |
+
""" There are the followi files in in `model_dir`
|
| 184 |
+
* final.pt, required
|
| 185 |
+
* train.yaml, required
|
| 186 |
+
* units.txt, required
|
| 187 |
+
* global_cmvn, optional
|
| 188 |
+
"""
|
| 189 |
+
# Check required files
|
| 190 |
+
required_files = ['train.yaml', 'final.pt', 'units.txt']
|
| 191 |
+
for file in required_files:
|
| 192 |
+
file_path = os.path.join(model_dir, file)
|
| 193 |
+
if not os.path.exists(file_path):
|
| 194 |
+
raise FileNotFoundError(
|
| 195 |
+
f"Required file {file} not found in {model_dir}")
|
| 196 |
+
# Read config and override some config
|
| 197 |
+
config_file = os.path.join(model_dir, 'train.yaml')
|
| 198 |
+
with open(config_file, 'r') as fin:
|
| 199 |
+
configs = yaml.load(fin, Loader=yaml.FullLoader)
|
| 200 |
+
token_file = os.path.join(model_dir, 'units.txt')
|
| 201 |
+
configs['tokenizer_conf']['symbol_table_path'] = token_file
|
| 202 |
+
cmvn_file = os.path.join(model_dir, 'global_cmvn')
|
| 203 |
+
if os.path.exists(cmvn_file):
|
| 204 |
+
configs['cmvn_conf']['cmvn_file'] = cmvn_file
|
| 205 |
+
# Read model
|
| 206 |
+
pt_file = os.path.join(model_dir, 'final.pt')
|
| 207 |
+
args = {'checkpoint': pt_file}
|
| 208 |
+
model, configs = init_model(args, configs)
|
| 209 |
+
return model
|
wenet/cli/paraformer_model.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import io
|
| 2 |
+
import os
|
| 3 |
+
from typing import Dict, List, Union
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torchaudio
|
| 7 |
+
import torchaudio.compliance.kaldi as kaldi
|
| 8 |
+
from wenet.cli.hub import Hub
|
| 9 |
+
from wenet.paraformer.search import (gen_timestamps_from_peak,
|
| 10 |
+
paraformer_greedy_search)
|
| 11 |
+
from wenet.text.paraformer_tokenizer import ParaformerTokenizer
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Paraformer:
|
| 15 |
+
|
| 16 |
+
def __init__(self, model_dir: str, resample_rate: int = 16000) -> None:
|
| 17 |
+
|
| 18 |
+
model_path = os.path.join(model_dir, 'final.zip')
|
| 19 |
+
units_path = os.path.join(model_dir, 'units.txt')
|
| 20 |
+
self.model = torch.jit.load(model_path)
|
| 21 |
+
self.resample_rate = resample_rate
|
| 22 |
+
self.device = torch.device("cpu")
|
| 23 |
+
self.tokenizer = ParaformerTokenizer(symbol_table=units_path)
|
| 24 |
+
|
| 25 |
+
@torch.inference_mode()
|
| 26 |
+
def transcribe_batch(self,
|
| 27 |
+
audio_files: List[Union[str, bytes]],
|
| 28 |
+
tokens_info: bool = False) -> List[Dict]:
|
| 29 |
+
feats_lst = []
|
| 30 |
+
feats_lens_lst = []
|
| 31 |
+
for audio in audio_files:
|
| 32 |
+
if isinstance(audio, bytes):
|
| 33 |
+
with io.BytesIO(audio) as fobj:
|
| 34 |
+
waveform, sample_rate = torchaudio.load(fobj,
|
| 35 |
+
normalize=False)
|
| 36 |
+
else:
|
| 37 |
+
waveform, sample_rate = torchaudio.load(audio, normalize=False)
|
| 38 |
+
if sample_rate != self.resample_rate:
|
| 39 |
+
waveform = torchaudio.transforms.Resample(
|
| 40 |
+
orig_freq=sample_rate,
|
| 41 |
+
new_freq=self.resample_rate)(waveform)
|
| 42 |
+
|
| 43 |
+
waveform = waveform.to(torch.float)
|
| 44 |
+
feats = kaldi.fbank(waveform,
|
| 45 |
+
num_mel_bins=80,
|
| 46 |
+
frame_length=25,
|
| 47 |
+
frame_shift=10,
|
| 48 |
+
energy_floor=0.0,
|
| 49 |
+
sample_frequency=self.resample_rate,
|
| 50 |
+
window_type="hamming")
|
| 51 |
+
feats_lst.append(feats)
|
| 52 |
+
feats_lens_lst.append(
|
| 53 |
+
torch.tensor(feats.shape[0], dtype=torch.int64))
|
| 54 |
+
feats_tensor = torch.nn.utils.rnn.pad_sequence(
|
| 55 |
+
feats_lst, batch_first=True).to(device=self.device)
|
| 56 |
+
feats_lens_tensor = torch.tensor(feats_lens_lst, device=self.device)
|
| 57 |
+
|
| 58 |
+
decoder_out, token_num, tp_alphas, frames = self.model.forward_paraformer(
|
| 59 |
+
feats_tensor, feats_lens_tensor)
|
| 60 |
+
frames = frames.cpu().numpy()
|
| 61 |
+
cif_peaks = self.model.forward_cif_peaks(tp_alphas, token_num)
|
| 62 |
+
|
| 63 |
+
results = paraformer_greedy_search(decoder_out, token_num, cif_peaks)
|
| 64 |
+
|
| 65 |
+
r = []
|
| 66 |
+
for (i, res) in enumerate(results):
|
| 67 |
+
result = {}
|
| 68 |
+
result['confidence'] = res.confidence
|
| 69 |
+
result['text'] = self.tokenizer.detokenize(res.tokens)[0]
|
| 70 |
+
if tokens_info:
|
| 71 |
+
tokens_info_l = []
|
| 72 |
+
times = gen_timestamps_from_peak(res.times,
|
| 73 |
+
num_frames=frames[i],
|
| 74 |
+
frame_rate=0.02)
|
| 75 |
+
|
| 76 |
+
for i, x in enumerate(res.tokens[:len(times)]):
|
| 77 |
+
tokens_info_l.append({
|
| 78 |
+
'token':
|
| 79 |
+
self.tokenizer.char_dict[x],
|
| 80 |
+
'start':
|
| 81 |
+
round(times[i][0], 3),
|
| 82 |
+
'end':
|
| 83 |
+
round(times[i][1], 3),
|
| 84 |
+
'confidence':
|
| 85 |
+
round(res.tokens_confidence[i], 2)
|
| 86 |
+
})
|
| 87 |
+
result['tokens'] = tokens_info_l
|
| 88 |
+
r.append(result)
|
| 89 |
+
return r
|
| 90 |
+
|
| 91 |
+
def transcribe(self, audio_file: str, tokens_info: bool = False) -> dict:
|
| 92 |
+
result = self.transcribe_batch([audio_file], tokens_info)[0]
|
| 93 |
+
return result
|
| 94 |
+
|
| 95 |
+
def align(self, audio_file: str, label: str) -> dict:
|
| 96 |
+
raise NotImplementedError("Align is currently not supported")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def load_model(model_dir: str = None,
|
| 100 |
+
gpu: int = -1,
|
| 101 |
+
device: str = "cpu") -> Paraformer:
|
| 102 |
+
if model_dir is None:
|
| 103 |
+
model_dir = Hub.get_model_by_lang('paraformer')
|
| 104 |
+
if gpu != -1:
|
| 105 |
+
# remain the original usage of gpu
|
| 106 |
+
device = "cuda"
|
| 107 |
+
paraformer = Paraformer(model_dir)
|
| 108 |
+
paraformer.device = torch.device(device)
|
| 109 |
+
paraformer.model.to(device)
|
| 110 |
+
return paraformer
|
wenet/cli/punc_model.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import List
|
| 3 |
+
|
| 4 |
+
import jieba
|
| 5 |
+
import torch
|
| 6 |
+
from wenet.cli.hub import Hub
|
| 7 |
+
from wenet.paraformer.search import _isAllAlpha
|
| 8 |
+
from wenet.text.char_tokenizer import CharTokenizer
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class PuncModel:
|
| 12 |
+
|
| 13 |
+
def __init__(self, model_dir: str) -> None:
|
| 14 |
+
self.model_dir = model_dir
|
| 15 |
+
model_path = os.path.join(model_dir, 'final.zip')
|
| 16 |
+
units_path = os.path.join(model_dir, 'units.txt')
|
| 17 |
+
|
| 18 |
+
self.model = torch.jit.load(model_path)
|
| 19 |
+
self.tokenizer = CharTokenizer(units_path)
|
| 20 |
+
self.device = torch.device("cpu")
|
| 21 |
+
self.use_jieba = False
|
| 22 |
+
|
| 23 |
+
self.punc_table = ['<unk>', '', ',', '。', '?', '、']
|
| 24 |
+
|
| 25 |
+
def split_words(self, text: str):
|
| 26 |
+
if not self.use_jieba:
|
| 27 |
+
self.use_jieba = True
|
| 28 |
+
import logging
|
| 29 |
+
|
| 30 |
+
# Disable jieba's logger
|
| 31 |
+
logging.getLogger('jieba').disabled = True
|
| 32 |
+
jieba.load_userdict(os.path.join(self.model_dir, 'jieba_usr_dict'))
|
| 33 |
+
|
| 34 |
+
result_list = []
|
| 35 |
+
tokens = text.split()
|
| 36 |
+
current_language = None
|
| 37 |
+
buffer = []
|
| 38 |
+
|
| 39 |
+
for token in tokens:
|
| 40 |
+
is_english = token.isascii()
|
| 41 |
+
if is_english:
|
| 42 |
+
language = "English"
|
| 43 |
+
else:
|
| 44 |
+
language = "Chinese"
|
| 45 |
+
|
| 46 |
+
if current_language and language != current_language:
|
| 47 |
+
if current_language == "Chinese":
|
| 48 |
+
result_list.extend(jieba.cut(''.join(buffer), HMM=False))
|
| 49 |
+
else:
|
| 50 |
+
result_list.extend(buffer)
|
| 51 |
+
buffer = []
|
| 52 |
+
|
| 53 |
+
buffer.append(token)
|
| 54 |
+
current_language = language
|
| 55 |
+
|
| 56 |
+
if buffer:
|
| 57 |
+
if current_language == "Chinese":
|
| 58 |
+
result_list.extend(jieba.cut(''.join(buffer), HMM=False))
|
| 59 |
+
else:
|
| 60 |
+
result_list.extend(buffer)
|
| 61 |
+
|
| 62 |
+
return result_list
|
| 63 |
+
|
| 64 |
+
def add_punc_batch(self, texts: List[str]):
|
| 65 |
+
batch_text_words = []
|
| 66 |
+
batch_text_ids = []
|
| 67 |
+
batch_text_lens = []
|
| 68 |
+
|
| 69 |
+
for text in texts:
|
| 70 |
+
words = self.split_words(text)
|
| 71 |
+
ids = self.tokenizer.tokens2ids(words)
|
| 72 |
+
batch_text_words.append(words)
|
| 73 |
+
batch_text_ids.append(ids)
|
| 74 |
+
batch_text_lens.append(len(ids))
|
| 75 |
+
|
| 76 |
+
texts_tensor = torch.tensor(batch_text_ids,
|
| 77 |
+
device=self.device,
|
| 78 |
+
dtype=torch.int64)
|
| 79 |
+
texts_lens_tensor = torch.tensor(batch_text_lens,
|
| 80 |
+
device=self.device,
|
| 81 |
+
dtype=torch.int64)
|
| 82 |
+
|
| 83 |
+
log_probs, _ = self.model(texts_tensor, texts_lens_tensor)
|
| 84 |
+
result = []
|
| 85 |
+
outs = log_probs.argmax(-1).cpu().numpy()
|
| 86 |
+
for i, out in enumerate(outs):
|
| 87 |
+
punc_id = out[:batch_text_lens[i]]
|
| 88 |
+
sentence = ''
|
| 89 |
+
for j, word in enumerate(batch_text_words[i]):
|
| 90 |
+
if _isAllAlpha(word):
|
| 91 |
+
word = '▁' + word
|
| 92 |
+
word += self.punc_table[punc_id[j]]
|
| 93 |
+
sentence += word
|
| 94 |
+
result.append(sentence.replace('▁', ' '))
|
| 95 |
+
return result
|
| 96 |
+
|
| 97 |
+
def __call__(self, text: str):
|
| 98 |
+
if text != '':
|
| 99 |
+
r = self.add_punc_batch([text])[0]
|
| 100 |
+
return r
|
| 101 |
+
return ''
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def load_model(model_dir: str = None,
|
| 105 |
+
gpu: int = -1,
|
| 106 |
+
device: str = "cpu") -> PuncModel:
|
| 107 |
+
if model_dir is None:
|
| 108 |
+
model_dir = Hub.get_model_by_lang('punc')
|
| 109 |
+
if gpu != -1:
|
| 110 |
+
# remain the original usage of gpu
|
| 111 |
+
device = "cuda"
|
| 112 |
+
punc = PuncModel(model_dir)
|
| 113 |
+
punc.device = torch.device(device)
|
| 114 |
+
punc.model.to(device)
|
| 115 |
+
return punc
|
wenet/cli/transcribe.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2023 Binbin Zhang (binbzha@qq.com)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
|
| 17 |
+
from wenet.cli.model import load_model
|
| 18 |
+
from wenet.cli.paraformer_model import load_model as load_paraformer
|
| 19 |
+
from wenet.cli.punc_model import load_model as load_punc_model
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def get_args():
|
| 23 |
+
parser = argparse.ArgumentParser(description='')
|
| 24 |
+
parser.add_argument('audio_file', help='audio file to transcribe')
|
| 25 |
+
parser.add_argument('-l',
|
| 26 |
+
'--language',
|
| 27 |
+
choices=[
|
| 28 |
+
'chinese',
|
| 29 |
+
'english',
|
| 30 |
+
],
|
| 31 |
+
default='chinese',
|
| 32 |
+
help='language type')
|
| 33 |
+
parser.add_argument('-m',
|
| 34 |
+
'--model_dir',
|
| 35 |
+
default=None,
|
| 36 |
+
help='specify your own model dir')
|
| 37 |
+
parser.add_argument('-g',
|
| 38 |
+
'--gpu',
|
| 39 |
+
type=int,
|
| 40 |
+
default='-1',
|
| 41 |
+
help='gpu id to decode, default is cpu.')
|
| 42 |
+
parser.add_argument('--device',
|
| 43 |
+
type=str,
|
| 44 |
+
default='cpu',
|
| 45 |
+
choices=["cpu", "npu", "cuda"],
|
| 46 |
+
help='accelerator to use')
|
| 47 |
+
parser.add_argument('-t',
|
| 48 |
+
'--show_tokens_info',
|
| 49 |
+
action='store_true',
|
| 50 |
+
help='whether to output token(word) level information'
|
| 51 |
+
', such times/confidence')
|
| 52 |
+
parser.add_argument('--align',
|
| 53 |
+
action='store_true',
|
| 54 |
+
help='force align the input audio and transcript')
|
| 55 |
+
parser.add_argument('--label', type=str, help='the input label to align')
|
| 56 |
+
parser.add_argument('--paraformer',
|
| 57 |
+
action='store_true',
|
| 58 |
+
help='whether to use the best chinese model')
|
| 59 |
+
parser.add_argument('--beam', type=int, default=5, help="beam size")
|
| 60 |
+
parser.add_argument('--context_path',
|
| 61 |
+
type=str,
|
| 62 |
+
default=None,
|
| 63 |
+
help='context list file')
|
| 64 |
+
parser.add_argument('--context_score',
|
| 65 |
+
type=float,
|
| 66 |
+
default=6.0,
|
| 67 |
+
help='context score')
|
| 68 |
+
parser.add_argument('--punc', action='store_true', help='context score')
|
| 69 |
+
|
| 70 |
+
parser.add_argument('-pm',
|
| 71 |
+
'--punc_model_dir',
|
| 72 |
+
default=None,
|
| 73 |
+
help='specify your own punc model dir')
|
| 74 |
+
|
| 75 |
+
args = parser.parse_args()
|
| 76 |
+
return args
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def main():
|
| 80 |
+
args = get_args()
|
| 81 |
+
|
| 82 |
+
if args.paraformer:
|
| 83 |
+
model = load_paraformer(args.model_dir, args.gpu, args.device)
|
| 84 |
+
else:
|
| 85 |
+
model = load_model(args.language, args.model_dir, args.gpu, args.beam,
|
| 86 |
+
args.context_path, args.context_score, args.device)
|
| 87 |
+
punc_model = None
|
| 88 |
+
if args.punc:
|
| 89 |
+
punc_model = load_punc_model(args.punc_model_dir, args.gpu,
|
| 90 |
+
args.device)
|
| 91 |
+
if args.align:
|
| 92 |
+
result = model.align(args.audio_file, args.label)
|
| 93 |
+
else:
|
| 94 |
+
result = model.transcribe(args.audio_file, args.show_tokens_info)
|
| 95 |
+
if args.punc:
|
| 96 |
+
assert punc_model is not None
|
| 97 |
+
result['text_with_punc'] = punc_model(result['text'])
|
| 98 |
+
print(result)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if __name__ == "__main__":
|
| 102 |
+
main()
|
wenet/ctl_model/__init__.py
ADDED
|
File without changes
|
wenet/ctl_model/asr_model_ctl.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2020 Mobvoi Inc. (authors: Binbin Zhang, Di Wu)
|
| 2 |
+
# 2023 NetEase Inc. (authors: Yuting Yang)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
# Modified from ESPnet(https://github.com/espnet/espnet) and
|
| 16 |
+
# fairseq(https://github.com/facebookresearch/fairseq)
|
| 17 |
+
|
| 18 |
+
from typing import Dict, Optional
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
from wenet.transformer.ctc import CTC
|
| 23 |
+
from wenet.transformer.decoder import TransformerDecoder
|
| 24 |
+
from wenet.ctl_model.encoder import TransformerEncoder
|
| 25 |
+
from wenet.transformer.asr_model import ASRModel
|
| 26 |
+
from wenet.utils.common import IGNORE_ID
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class CTLModel(ASRModel):
|
| 30 |
+
"""
|
| 31 |
+
Implementation of Interspeecch 2023 paper:
|
| 32 |
+
'Enhancing the Unified Streaming and Non-streaming Model
|
| 33 |
+
with Contrastive Learning'
|
| 34 |
+
https://arxiv.org/abs/2306.00755
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
vocab_size: int,
|
| 40 |
+
encoder: TransformerEncoder,
|
| 41 |
+
decoder: TransformerDecoder,
|
| 42 |
+
ctc: CTC,
|
| 43 |
+
ctc_weight: float = 0.5,
|
| 44 |
+
ignore_id: int = IGNORE_ID,
|
| 45 |
+
reverse_weight: float = 0.0,
|
| 46 |
+
lsm_weight: float = 0.0,
|
| 47 |
+
length_normalized_loss: bool = False,
|
| 48 |
+
logit_temp: float = 0.1,
|
| 49 |
+
n_negatives: int = 0,
|
| 50 |
+
ctl_weight: float = 1,
|
| 51 |
+
special_tokens: dict = None,
|
| 52 |
+
):
|
| 53 |
+
assert 0.0 <= ctc_weight <= 1.0, ctc_weight
|
| 54 |
+
super().__init__(vocab_size,
|
| 55 |
+
encoder,
|
| 56 |
+
decoder,
|
| 57 |
+
ctc,
|
| 58 |
+
ctc_weight,
|
| 59 |
+
ignore_id,
|
| 60 |
+
reverse_weight,
|
| 61 |
+
lsm_weight,
|
| 62 |
+
length_normalized_loss,
|
| 63 |
+
special_tokens=special_tokens)
|
| 64 |
+
|
| 65 |
+
# For CTL Loss
|
| 66 |
+
self.n_negatives = n_negatives
|
| 67 |
+
self.ctl_weight = ctl_weight
|
| 68 |
+
self.logit_temp = logit_temp
|
| 69 |
+
|
| 70 |
+
@torch.jit.unused
|
| 71 |
+
def forward(
|
| 72 |
+
self,
|
| 73 |
+
batch: dict,
|
| 74 |
+
device: torch.device,
|
| 75 |
+
) -> Dict[str, Optional[torch.Tensor]]:
|
| 76 |
+
|
| 77 |
+
speech = batch['feats'].to(device)
|
| 78 |
+
speech_lengths = batch['feats_lengths'].to(device)
|
| 79 |
+
text = batch['target'].to(device)
|
| 80 |
+
text_lengths = batch['target_lengths'].to(device)
|
| 81 |
+
loss_full, encoder_out_full, _, _ = self.forward_full(
|
| 82 |
+
speech, speech_lengths, text, text_lengths)
|
| 83 |
+
loss_chunk, encoder_out, lens_chunk, encoder_mask = self.forward_chunk(
|
| 84 |
+
speech, speech_lengths, text, text_lengths)
|
| 85 |
+
|
| 86 |
+
ctl_loss = 0.0
|
| 87 |
+
if self.ctl_weight > 0 and self.n_negatives > 0:
|
| 88 |
+
num = encoder_out_full.size(1)
|
| 89 |
+
targets = encoder_out_full
|
| 90 |
+
src = encoder_out
|
| 91 |
+
negs, negs_idxs = self.sample_negatives(targets,
|
| 92 |
+
targets.size(1),
|
| 93 |
+
speech_lengths=lens_chunk)
|
| 94 |
+
ctl_loss = self.CTL(src, targets, negs, encoder_mask)
|
| 95 |
+
|
| 96 |
+
loss = loss_full + loss_chunk + self.ctl_weight * ctl_loss
|
| 97 |
+
return {
|
| 98 |
+
"loss": loss,
|
| 99 |
+
"loss_full": loss_full,
|
| 100 |
+
"loss_chunk": loss_chunk,
|
| 101 |
+
"loss_ctl": ctl_loss
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
def forward_full(
|
| 105 |
+
self,
|
| 106 |
+
speech: torch.Tensor,
|
| 107 |
+
speech_lengths: torch.Tensor,
|
| 108 |
+
text: torch.Tensor,
|
| 109 |
+
text_lengths: torch.Tensor,
|
| 110 |
+
):
|
| 111 |
+
"""Full context mode
|
| 112 |
+
Frontend + Encoder + Decoder + Calc loss
|
| 113 |
+
|
| 114 |
+
Args:
|
| 115 |
+
speech: (Batch, Length, ...)
|
| 116 |
+
speech_lengths: (Batch, )
|
| 117 |
+
text: (Batch, Length)
|
| 118 |
+
text_lengths: (Batch,)
|
| 119 |
+
"""
|
| 120 |
+
|
| 121 |
+
assert text_lengths.dim() == 1, text_lengths.shape
|
| 122 |
+
# Check that batch_size is unified
|
| 123 |
+
assert (speech.shape[0] == speech_lengths.shape[0] == text.shape[0] ==
|
| 124 |
+
text_lengths.shape[0]), (speech.shape, speech_lengths.shape,
|
| 125 |
+
text.shape, text_lengths.shape)
|
| 126 |
+
# 1. Encoder
|
| 127 |
+
encoder_out, encoder_mask = self.encoder.forward_full(
|
| 128 |
+
speech, speech_lengths)
|
| 129 |
+
encoder_out_lens = encoder_mask.squeeze(1).sum(1)
|
| 130 |
+
|
| 131 |
+
# 2a. Attention-decoder branch
|
| 132 |
+
if self.ctc_weight != 1.0:
|
| 133 |
+
loss_att, acc_att = self._calc_att_loss(encoder_out, encoder_mask,
|
| 134 |
+
text, text_lengths)
|
| 135 |
+
else:
|
| 136 |
+
loss_att = None
|
| 137 |
+
|
| 138 |
+
# 2b. CTC branch
|
| 139 |
+
if self.ctc_weight != 0.0:
|
| 140 |
+
loss_ctc = self.ctc(encoder_out, encoder_out_lens, text,
|
| 141 |
+
text_lengths)
|
| 142 |
+
else:
|
| 143 |
+
loss_ctc = None
|
| 144 |
+
|
| 145 |
+
if loss_ctc is None:
|
| 146 |
+
loss = loss_att
|
| 147 |
+
elif loss_att is None:
|
| 148 |
+
loss = loss_ctc
|
| 149 |
+
else:
|
| 150 |
+
loss = self.ctc_weight * loss_ctc[0] + (1 -
|
| 151 |
+
self.ctc_weight) * loss_att
|
| 152 |
+
return loss, encoder_out, encoder_out_lens, encoder_mask
|
| 153 |
+
|
| 154 |
+
def forward_chunk(
|
| 155 |
+
self,
|
| 156 |
+
speech: torch.Tensor,
|
| 157 |
+
speech_lengths: torch.Tensor,
|
| 158 |
+
text: torch.Tensor,
|
| 159 |
+
text_lengths: torch.Tensor,
|
| 160 |
+
):
|
| 161 |
+
"""Chunk-based context mode
|
| 162 |
+
Frontend + Encoder + Decoder + Calc loss
|
| 163 |
+
|
| 164 |
+
Args:
|
| 165 |
+
speech: (Batch, Length, ...)
|
| 166 |
+
speech_lengths: (Batch, )
|
| 167 |
+
text: (Batch, Length)
|
| 168 |
+
text_lengths: (Batch,)
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
assert text_lengths.dim() == 1, text_lengths.shape
|
| 172 |
+
# Check that batch_size is unified
|
| 173 |
+
assert (speech.shape[0] == speech_lengths.shape[0] == text.shape[0] ==
|
| 174 |
+
text_lengths.shape[0]), (speech.shape, speech_lengths.shape,
|
| 175 |
+
text.shape, text_lengths.shape)
|
| 176 |
+
# 1. Encoder
|
| 177 |
+
encoder_out, encoder_mask = self.encoder(speech, speech_lengths)
|
| 178 |
+
encoder_out_lens = encoder_mask.squeeze(1).sum(1)
|
| 179 |
+
|
| 180 |
+
# 2a. Attention-decoder branch
|
| 181 |
+
if self.ctc_weight != 1.0:
|
| 182 |
+
loss_att, acc_att = self._calc_att_loss(encoder_out, encoder_mask,
|
| 183 |
+
text, text_lengths)
|
| 184 |
+
else:
|
| 185 |
+
loss_att = None
|
| 186 |
+
|
| 187 |
+
# 2b. CTC branch
|
| 188 |
+
if self.ctc_weight != 0.0:
|
| 189 |
+
loss_ctc = self.ctc(encoder_out, encoder_out_lens, text,
|
| 190 |
+
text_lengths)
|
| 191 |
+
else:
|
| 192 |
+
loss_ctc = None
|
| 193 |
+
|
| 194 |
+
if loss_ctc is None:
|
| 195 |
+
loss = loss_att
|
| 196 |
+
elif loss_att is None:
|
| 197 |
+
loss = loss_ctc
|
| 198 |
+
else:
|
| 199 |
+
loss = self.ctc_weight * loss_ctc[0] + (1 -
|
| 200 |
+
self.ctc_weight) * loss_att
|
| 201 |
+
return loss, encoder_out, encoder_out_lens, encoder_mask
|
| 202 |
+
|
| 203 |
+
def sample_negatives(self, y, num, padding_count=0, speech_lengths=None):
|
| 204 |
+
if self.n_negatives == 0:
|
| 205 |
+
return y.new(0)
|
| 206 |
+
bsz, tsz, fsz = y.shape
|
| 207 |
+
y = y.reshape(-1, fsz) # BTC => (BxT)C
|
| 208 |
+
|
| 209 |
+
# FIXME: what happens if padding_count is specified?
|
| 210 |
+
high = tsz - (padding_count or 0)
|
| 211 |
+
with torch.no_grad():
|
| 212 |
+
assert high > 1, f"{bsz,tsz,fsz}"
|
| 213 |
+
|
| 214 |
+
if self.n_negatives > 0:
|
| 215 |
+
tszs = (torch.arange(num).unsqueeze(-1).expand(
|
| 216 |
+
-1, self.n_negatives).flatten())
|
| 217 |
+
if speech_lengths is not None:
|
| 218 |
+
neg_idxs = [
|
| 219 |
+
torch.randint(low=0,
|
| 220 |
+
high=speech_lengths[i].item() - 1,
|
| 221 |
+
size=(1, self.n_negatives * tsz))
|
| 222 |
+
for i in range(len(speech_lengths))
|
| 223 |
+
]
|
| 224 |
+
neg_idxs = torch.cat(neg_idxs).reshape(
|
| 225 |
+
bsz, self.n_negatives * tsz)
|
| 226 |
+
else:
|
| 227 |
+
neg_idxs = torch.randint(low=0,
|
| 228 |
+
high=num - 1,
|
| 229 |
+
size=(bsz,
|
| 230 |
+
self.n_negatives * tsz))
|
| 231 |
+
neg_idxs[neg_idxs >= tszs] += 1
|
| 232 |
+
|
| 233 |
+
if self.n_negatives > 0:
|
| 234 |
+
neg_idxs = neg_idxs + (torch.arange(bsz).unsqueeze(1) * high)
|
| 235 |
+
|
| 236 |
+
negs = y[neg_idxs.view(-1)]
|
| 237 |
+
negs = negs.contiguous().view(bsz, num, self.n_negatives,
|
| 238 |
+
fsz).permute(2, 0, 1, 3) # to NxBxTxC
|
| 239 |
+
return negs, neg_idxs
|
| 240 |
+
|
| 241 |
+
def compute_preds(self, x, y, negatives):
|
| 242 |
+
neg_is_pos = (y == negatives).all(-1)
|
| 243 |
+
y = y.unsqueeze(0)
|
| 244 |
+
targets = torch.cat([y, negatives], dim=0)
|
| 245 |
+
|
| 246 |
+
logits = torch.cosine_similarity(x.float(), targets.float(), dim=-1)
|
| 247 |
+
logits = logits / self.logit_temp
|
| 248 |
+
logits = logits.type_as(x)
|
| 249 |
+
|
| 250 |
+
if neg_is_pos.any():
|
| 251 |
+
if not hasattr(self, "_inftensor"):
|
| 252 |
+
self._inftensor = float("-inf")
|
| 253 |
+
# logits[1:] = index_put(logits[1:], neg_is_pos, self._inftensor)
|
| 254 |
+
logits[1:][neg_is_pos] = self._inftensor
|
| 255 |
+
logits = logits.transpose(0, 2)
|
| 256 |
+
logits = logits.transpose(0, 1)
|
| 257 |
+
logits = logits.reshape(-1, logits.size(-1))
|
| 258 |
+
return logits
|
| 259 |
+
|
| 260 |
+
def CTL(self, x, y, negs, mask=None):
|
| 261 |
+
# Step1: compute cosine similarity, shape [B*T, n_negatives+1]
|
| 262 |
+
logits = self.compute_preds(x, y, negs)
|
| 263 |
+
|
| 264 |
+
# Step2: target shape [B*T]
|
| 265 |
+
target = x.new_zeros(x.size(0) * x.size(1), dtype=torch.long)
|
| 266 |
+
|
| 267 |
+
# Step3: compute CTL loss
|
| 268 |
+
if mask is not None:
|
| 269 |
+
normalize_length = mask.sum()
|
| 270 |
+
bz, sz = mask.size(0), mask.size(-1)
|
| 271 |
+
mask = mask.squeeze(1).reshape(bz * sz).eq(0)
|
| 272 |
+
ce = F.cross_entropy(logits, target, reduction='none')
|
| 273 |
+
loss = ce.masked_fill(mask, 0).sum() / normalize_length
|
| 274 |
+
else:
|
| 275 |
+
loss = F.cross_entropy(logits, target)
|
| 276 |
+
|
| 277 |
+
return loss
|
wenet/ctl_model/encoder.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 Mobvoi Inc (Binbin Zhang, Di Wu)
|
| 2 |
+
# 2022 Xingchen Song (sxc19@mails.tsinghua.edu.cn)
|
| 3 |
+
# 2023 NetEase Inc
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
#
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
#
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
# Modified from ESPnet(https://github.com/espnet/espnet)
|
| 17 |
+
"""Encoder definition."""
|
| 18 |
+
from typing import Optional, Tuple
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
from wenet.utils.mask import make_pad_mask
|
| 23 |
+
from wenet.transformer.encoder import TransformerEncoder, ConformerEncoder
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class DualTransformerEncoder(TransformerEncoder):
|
| 27 |
+
"""Transformer encoder module."""
|
| 28 |
+
|
| 29 |
+
def __init__(
|
| 30 |
+
self,
|
| 31 |
+
input_size: int,
|
| 32 |
+
output_size: int = 256,
|
| 33 |
+
attention_heads: int = 4,
|
| 34 |
+
linear_units: int = 2048,
|
| 35 |
+
num_blocks: int = 6,
|
| 36 |
+
dropout_rate: float = 0.1,
|
| 37 |
+
positional_dropout_rate: float = 0.1,
|
| 38 |
+
attention_dropout_rate: float = 0.0,
|
| 39 |
+
input_layer: str = "conv2d",
|
| 40 |
+
pos_enc_layer_type: str = "abs_pos",
|
| 41 |
+
normalize_before: bool = True,
|
| 42 |
+
static_chunk_size: int = 0,
|
| 43 |
+
use_dynamic_chunk: bool = False,
|
| 44 |
+
global_cmvn: torch.nn.Module = None,
|
| 45 |
+
use_dynamic_left_chunk: bool = False,
|
| 46 |
+
query_bias: bool = True,
|
| 47 |
+
key_bias: bool = True,
|
| 48 |
+
value_bias: bool = True,
|
| 49 |
+
activation_type: str = "relu",
|
| 50 |
+
gradient_checkpointing: bool = False,
|
| 51 |
+
use_sdpa: bool = False,
|
| 52 |
+
layer_norm_type: str = 'layer_norm',
|
| 53 |
+
norm_eps: float = 1e-5,
|
| 54 |
+
n_kv_head: Optional[int] = None,
|
| 55 |
+
head_dim: Optional[int] = None,
|
| 56 |
+
selfattention_layer_type: str = "selfattn",
|
| 57 |
+
mlp_type: str = 'position_wise_feed_forward',
|
| 58 |
+
mlp_bias: bool = True,
|
| 59 |
+
n_expert: int = 8,
|
| 60 |
+
n_expert_activated: int = 2,
|
| 61 |
+
):
|
| 62 |
+
""" Construct DualTransformerEncoder
|
| 63 |
+
Support both the full context mode and the streaming mode separately
|
| 64 |
+
"""
|
| 65 |
+
super().__init__(input_size, output_size, attention_heads,
|
| 66 |
+
linear_units, num_blocks, dropout_rate,
|
| 67 |
+
positional_dropout_rate, attention_dropout_rate,
|
| 68 |
+
input_layer, pos_enc_layer_type, normalize_before,
|
| 69 |
+
static_chunk_size, use_dynamic_chunk, global_cmvn,
|
| 70 |
+
use_dynamic_left_chunk, query_bias, key_bias,
|
| 71 |
+
value_bias, activation_type, gradient_checkpointing,
|
| 72 |
+
use_sdpa, layer_norm_type, norm_eps, n_kv_head,
|
| 73 |
+
head_dim, selfattention_layer_type, mlp_type,
|
| 74 |
+
mlp_bias, n_expert, n_expert_activated)
|
| 75 |
+
|
| 76 |
+
def forward_full(
|
| 77 |
+
self,
|
| 78 |
+
xs: torch.Tensor,
|
| 79 |
+
xs_lens: torch.Tensor,
|
| 80 |
+
decoding_chunk_size: int = 0,
|
| 81 |
+
num_decoding_left_chunks: int = -1,
|
| 82 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 83 |
+
T = xs.size(1)
|
| 84 |
+
masks = ~make_pad_mask(xs_lens, T).unsqueeze(1) # (B, 1, T)
|
| 85 |
+
if self.global_cmvn is not None:
|
| 86 |
+
xs = self.global_cmvn(xs)
|
| 87 |
+
xs, pos_emb, masks = self.embed(xs, masks)
|
| 88 |
+
mask_pad = masks # (B, 1, T/subsample_rate)
|
| 89 |
+
for layer in self.encoders:
|
| 90 |
+
xs, masks, _, _ = layer(xs, masks, pos_emb, mask_pad)
|
| 91 |
+
if self.normalize_before:
|
| 92 |
+
xs = self.after_norm(xs)
|
| 93 |
+
return xs, masks
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class DualConformerEncoder(ConformerEncoder):
|
| 97 |
+
"""Conformer encoder module."""
|
| 98 |
+
|
| 99 |
+
def __init__(
|
| 100 |
+
self,
|
| 101 |
+
input_size: int,
|
| 102 |
+
output_size: int = 256,
|
| 103 |
+
attention_heads: int = 4,
|
| 104 |
+
linear_units: int = 2048,
|
| 105 |
+
num_blocks: int = 6,
|
| 106 |
+
dropout_rate: float = 0.1,
|
| 107 |
+
positional_dropout_rate: float = 0.1,
|
| 108 |
+
attention_dropout_rate: float = 0.0,
|
| 109 |
+
input_layer: str = "conv2d",
|
| 110 |
+
pos_enc_layer_type: str = "rel_pos",
|
| 111 |
+
normalize_before: bool = True,
|
| 112 |
+
static_chunk_size: int = 0,
|
| 113 |
+
use_dynamic_chunk: bool = False,
|
| 114 |
+
global_cmvn: torch.nn.Module = None,
|
| 115 |
+
use_dynamic_left_chunk: bool = False,
|
| 116 |
+
positionwise_conv_kernel_size: int = 1,
|
| 117 |
+
macaron_style: bool = True,
|
| 118 |
+
selfattention_layer_type: str = "rel_selfattn",
|
| 119 |
+
activation_type: str = "swish",
|
| 120 |
+
use_cnn_module: bool = True,
|
| 121 |
+
cnn_module_kernel: int = 15,
|
| 122 |
+
causal: bool = False,
|
| 123 |
+
cnn_module_norm: str = "batch_norm",
|
| 124 |
+
query_bias: bool = True,
|
| 125 |
+
key_bias: bool = True,
|
| 126 |
+
value_bias: bool = True,
|
| 127 |
+
conv_bias: bool = True,
|
| 128 |
+
gradient_checkpointing: bool = False,
|
| 129 |
+
use_sdpa: bool = False,
|
| 130 |
+
layer_norm_type: str = 'layer_norm',
|
| 131 |
+
norm_eps: float = 1e-5,
|
| 132 |
+
n_kv_head: Optional[int] = None,
|
| 133 |
+
head_dim: Optional[int] = None,
|
| 134 |
+
mlp_type: str = 'position_wise_feed_forward',
|
| 135 |
+
mlp_bias: bool = True,
|
| 136 |
+
n_expert: int = 8,
|
| 137 |
+
n_expert_activated: int = 2,
|
| 138 |
+
):
|
| 139 |
+
""" Construct DualConformerEncoder
|
| 140 |
+
Support both the full context mode and the streaming mode separately
|
| 141 |
+
"""
|
| 142 |
+
super().__init__(
|
| 143 |
+
input_size, output_size, attention_heads, linear_units, num_blocks,
|
| 144 |
+
dropout_rate, positional_dropout_rate, attention_dropout_rate,
|
| 145 |
+
input_layer, pos_enc_layer_type, normalize_before,
|
| 146 |
+
static_chunk_size, use_dynamic_chunk, global_cmvn,
|
| 147 |
+
use_dynamic_left_chunk, positionwise_conv_kernel_size,
|
| 148 |
+
macaron_style, selfattention_layer_type, activation_type,
|
| 149 |
+
use_cnn_module, cnn_module_kernel, causal, cnn_module_norm,
|
| 150 |
+
query_bias, key_bias, value_bias, conv_bias,
|
| 151 |
+
gradient_checkpointing, use_sdpa, layer_norm_type, norm_eps,
|
| 152 |
+
n_kv_head, head_dim, mlp_type, mlp_bias, n_expert,
|
| 153 |
+
n_expert_activated)
|
| 154 |
+
|
| 155 |
+
def forward_full(
|
| 156 |
+
self,
|
| 157 |
+
xs: torch.Tensor,
|
| 158 |
+
xs_lens: torch.Tensor,
|
| 159 |
+
decoding_chunk_size: int = 0,
|
| 160 |
+
num_decoding_left_chunks: int = -1,
|
| 161 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 162 |
+
T = xs.size(1)
|
| 163 |
+
masks = ~make_pad_mask(xs_lens, T).unsqueeze(1) # (B, 1, T)
|
| 164 |
+
if self.global_cmvn is not None:
|
| 165 |
+
xs = self.global_cmvn(xs)
|
| 166 |
+
xs, pos_emb, masks = self.embed(xs, masks)
|
| 167 |
+
mask_pad = masks # (B, 1, T/subsample_rate)
|
| 168 |
+
for layer in self.encoders:
|
| 169 |
+
xs, masks, _, _ = layer(xs, masks, pos_emb, mask_pad)
|
| 170 |
+
if self.normalize_before:
|
| 171 |
+
xs = self.after_norm(xs)
|
| 172 |
+
return xs, masks
|
wenet/dataset/__init__.py
ADDED
|
File without changes
|
wenet/dataset/datapipes.py
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2023 Wenet Community. (authors: Dinghao Zhou)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import collections
|
| 16 |
+
from collections.abc import Callable
|
| 17 |
+
import copy
|
| 18 |
+
import sys
|
| 19 |
+
import tarfile
|
| 20 |
+
import logging
|
| 21 |
+
from typing import List, Optional
|
| 22 |
+
import numpy as np
|
| 23 |
+
import torch
|
| 24 |
+
from torch.utils.data import IterDataPipe, functional_datapipe
|
| 25 |
+
from torch.utils.data import datapipes
|
| 26 |
+
from torch.utils.data.datapipes.iter import Mapper
|
| 27 |
+
from torch.utils.data.datapipes.iter.sharding import (
|
| 28 |
+
SHARDING_PRIORITIES, ShardingFilterIterDataPipe)
|
| 29 |
+
from torch.utils.data.datapipes.utils.common import _check_unpickable_fn
|
| 30 |
+
|
| 31 |
+
from wenet.dataset.processor import parse_url
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@functional_datapipe("map_ignore_error")
|
| 35 |
+
class MapperIgnoreErrorDataPipe(Mapper):
|
| 36 |
+
|
| 37 |
+
def __init__(self,
|
| 38 |
+
dataset: IterDataPipe,
|
| 39 |
+
fn: Callable,
|
| 40 |
+
input_col=None,
|
| 41 |
+
output_col=None,
|
| 42 |
+
log_error: bool = True) -> None:
|
| 43 |
+
super().__init__(dataset, fn, input_col, output_col)
|
| 44 |
+
self._iter = None
|
| 45 |
+
self.log_error = log_error
|
| 46 |
+
|
| 47 |
+
def __iter__(self):
|
| 48 |
+
if self._iter is None:
|
| 49 |
+
self._iter = iter(self.datapipe)
|
| 50 |
+
|
| 51 |
+
while True:
|
| 52 |
+
try:
|
| 53 |
+
elem = next(self._iter)
|
| 54 |
+
yield self._apply_fn(elem)
|
| 55 |
+
except StopIteration:
|
| 56 |
+
self._iter = None
|
| 57 |
+
return
|
| 58 |
+
except Exception as ex:
|
| 59 |
+
if self.log_error:
|
| 60 |
+
logging.warning(str(ex))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@functional_datapipe('bucket_by_sequence_length')
|
| 64 |
+
class BucketBySequenceLengthDataPipe(IterDataPipe):
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
dataset: IterDataPipe,
|
| 69 |
+
elem_length_func,
|
| 70 |
+
bucket_boundaries: List[int],
|
| 71 |
+
bucket_batch_sizes: List[int],
|
| 72 |
+
wrapper_class=None,
|
| 73 |
+
) -> None:
|
| 74 |
+
super().__init__()
|
| 75 |
+
_check_unpickable_fn(elem_length_func)
|
| 76 |
+
assert len(bucket_batch_sizes) == len(bucket_boundaries) + 1
|
| 77 |
+
self.bucket_batch_sizes = bucket_batch_sizes
|
| 78 |
+
self.bucket_boundaries = bucket_boundaries + [sys.maxsize]
|
| 79 |
+
self.elem_length_func = elem_length_func
|
| 80 |
+
|
| 81 |
+
self._group_dp = GroupByWindowDataPipe(dataset,
|
| 82 |
+
self._element_to_bucket_id,
|
| 83 |
+
self._window_size_func,
|
| 84 |
+
wrapper_class=wrapper_class)
|
| 85 |
+
|
| 86 |
+
def __iter__(self):
|
| 87 |
+
yield from self._group_dp
|
| 88 |
+
|
| 89 |
+
def _element_to_bucket_id(self, elem):
|
| 90 |
+
seq_len = self.elem_length_func(elem)
|
| 91 |
+
bucket_id = 0
|
| 92 |
+
for (i, b) in enumerate(self.bucket_boundaries):
|
| 93 |
+
if seq_len < b:
|
| 94 |
+
bucket_id = i
|
| 95 |
+
break
|
| 96 |
+
return bucket_id
|
| 97 |
+
|
| 98 |
+
def _window_size_func(self, bucket_id):
|
| 99 |
+
return self.bucket_batch_sizes[bucket_id]
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
@functional_datapipe("group_by_window")
|
| 103 |
+
class GroupByWindowDataPipe(datapipes.iter.Grouper):
|
| 104 |
+
|
| 105 |
+
def __init__(
|
| 106 |
+
self,
|
| 107 |
+
dataset: IterDataPipe,
|
| 108 |
+
key_func,
|
| 109 |
+
window_size_func,
|
| 110 |
+
wrapper_class=None,
|
| 111 |
+
):
|
| 112 |
+
super().__init__(dataset,
|
| 113 |
+
key_func,
|
| 114 |
+
keep_key=False,
|
| 115 |
+
group_size=None,
|
| 116 |
+
drop_remaining=False)
|
| 117 |
+
_check_unpickable_fn(window_size_func)
|
| 118 |
+
self.dp = dataset
|
| 119 |
+
self.window_size_func = window_size_func
|
| 120 |
+
if wrapper_class is not None:
|
| 121 |
+
_check_unpickable_fn(wrapper_class)
|
| 122 |
+
del self.wrapper_class
|
| 123 |
+
self.wrapper_class = wrapper_class
|
| 124 |
+
|
| 125 |
+
def __iter__(self):
|
| 126 |
+
for x in self.datapipe:
|
| 127 |
+
key = self.group_key_fn(x)
|
| 128 |
+
|
| 129 |
+
self.buffer_elements[key].append(x)
|
| 130 |
+
self.curr_buffer_size += 1
|
| 131 |
+
|
| 132 |
+
group_size = self.window_size_func(key)
|
| 133 |
+
if group_size == len(self.buffer_elements[key]):
|
| 134 |
+
result = self.wrapper_class(self.buffer_elements[key])
|
| 135 |
+
yield result
|
| 136 |
+
self.curr_buffer_size -= len(self.buffer_elements[key])
|
| 137 |
+
del self.buffer_elements[key]
|
| 138 |
+
|
| 139 |
+
if self.curr_buffer_size == self.max_buffer_size:
|
| 140 |
+
result_to_yield = self._remove_biggest_key()
|
| 141 |
+
if result_to_yield is not None:
|
| 142 |
+
result = self.wrapper_class(result_to_yield)
|
| 143 |
+
yield result
|
| 144 |
+
|
| 145 |
+
for key in tuple(self.buffer_elements.keys()):
|
| 146 |
+
result = self.wrapper_class(self.buffer_elements.pop(key))
|
| 147 |
+
self.curr_buffer_size -= len(result)
|
| 148 |
+
yield result
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@functional_datapipe("sort")
|
| 152 |
+
class SortDataPipe(IterDataPipe):
|
| 153 |
+
|
| 154 |
+
def __init__(self,
|
| 155 |
+
dataset: IterDataPipe,
|
| 156 |
+
buffer_size: int = 500,
|
| 157 |
+
key_func=None,
|
| 158 |
+
reverse=False) -> None:
|
| 159 |
+
if key_func is not None:
|
| 160 |
+
_check_unpickable_fn(key_func)
|
| 161 |
+
self.buffer_size = buffer_size
|
| 162 |
+
super().__init__()
|
| 163 |
+
self.dp = dataset
|
| 164 |
+
self._buffer = []
|
| 165 |
+
self.key_func = key_func
|
| 166 |
+
self.reverse = reverse
|
| 167 |
+
|
| 168 |
+
def __iter__(self):
|
| 169 |
+
for elem in self.dp:
|
| 170 |
+
self._buffer.append(elem)
|
| 171 |
+
if len(self._buffer) >= self.buffer_size:
|
| 172 |
+
self._buffer.sort(key=self.key_func, reverse=self.reverse)
|
| 173 |
+
for x in self._buffer:
|
| 174 |
+
yield x
|
| 175 |
+
del self._buffer
|
| 176 |
+
self._buffer = []
|
| 177 |
+
# The sample left over
|
| 178 |
+
self._buffer.sort(key=self.key_func, reverse=self.reverse)
|
| 179 |
+
for x in self._buffer:
|
| 180 |
+
yield x
|
| 181 |
+
del self._buffer
|
| 182 |
+
self._buffer = []
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
@functional_datapipe("dynamic_batch")
|
| 186 |
+
class DynamicBatchDataPipe(IterDataPipe):
|
| 187 |
+
|
| 188 |
+
def __init__(self, dataset: IterDataPipe, window_class,
|
| 189 |
+
wrapper_class) -> None:
|
| 190 |
+
_check_unpickable_fn(window_class)
|
| 191 |
+
_check_unpickable_fn(wrapper_class)
|
| 192 |
+
super().__init__()
|
| 193 |
+
self.dp = dataset
|
| 194 |
+
assert window_class is not None
|
| 195 |
+
assert wrapper_class is not None
|
| 196 |
+
self.window_class = window_class
|
| 197 |
+
self._buffer = []
|
| 198 |
+
self._wrappr_class = wrapper_class
|
| 199 |
+
|
| 200 |
+
def __iter__(self):
|
| 201 |
+
for elem in self.dp:
|
| 202 |
+
if not self.window_class(elem, len(self._buffer)):
|
| 203 |
+
self._buffer.append(elem)
|
| 204 |
+
else:
|
| 205 |
+
if len(self._buffer) > 0:
|
| 206 |
+
yield self._wrappr_class(self._buffer)
|
| 207 |
+
del self._buffer
|
| 208 |
+
self._buffer = [elem]
|
| 209 |
+
if len(self._buffer) > 0:
|
| 210 |
+
yield self._wrappr_class(self._buffer)
|
| 211 |
+
del self._buffer
|
| 212 |
+
self._buffer = []
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
@functional_datapipe("prefetch")
|
| 216 |
+
class PrefetchDataPipe(IterDataPipe):
|
| 217 |
+
"""Performs prefetching"""
|
| 218 |
+
|
| 219 |
+
def __init__(
|
| 220 |
+
self,
|
| 221 |
+
dataset: IterDataPipe,
|
| 222 |
+
buffer_size: int = 500,
|
| 223 |
+
):
|
| 224 |
+
# TODO(Mddct): support multiprocessing pool with shared-memory to
|
| 225 |
+
# prefetch
|
| 226 |
+
super().__init__()
|
| 227 |
+
self.dp = dataset
|
| 228 |
+
self._iter = None
|
| 229 |
+
self._prefetch_buffer_size = buffer_size
|
| 230 |
+
self._buffer = None
|
| 231 |
+
if self._prefetch_buffer_size > 0:
|
| 232 |
+
self._buffer = collections.deque(maxlen=self._prefetch_buffer_size)
|
| 233 |
+
|
| 234 |
+
def __iter__(self):
|
| 235 |
+
if self._prefetch_buffer_size > 0:
|
| 236 |
+
if self._iter is None:
|
| 237 |
+
self._iter = iter(self.dp)
|
| 238 |
+
assert self._buffer is not None
|
| 239 |
+
|
| 240 |
+
while True:
|
| 241 |
+
if len(self._buffer) <= self._prefetch_buffer_size // 2:
|
| 242 |
+
while len(self._buffer) < self._prefetch_buffer_size:
|
| 243 |
+
try:
|
| 244 |
+
self._buffer.append(next(self._iter))
|
| 245 |
+
except StopIteration:
|
| 246 |
+
if len(self._buffer) != 0:
|
| 247 |
+
while len(self._buffer) > 0:
|
| 248 |
+
yield self._buffer.popleft()
|
| 249 |
+
self._iter = None
|
| 250 |
+
return
|
| 251 |
+
while len(self._buffer) > self._prefetch_buffer_size // 2:
|
| 252 |
+
elem = self._buffer.popleft()
|
| 253 |
+
yield elem
|
| 254 |
+
|
| 255 |
+
else:
|
| 256 |
+
yield from self.dp
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
@functional_datapipe("repeat")
|
| 260 |
+
class RepeatDatapipe(IterDataPipe):
|
| 261 |
+
|
| 262 |
+
def __init__(self, dataset: IterDataPipe, count: int = -1):
|
| 263 |
+
super().__init__()
|
| 264 |
+
self.dp = dataset
|
| 265 |
+
self.count = count
|
| 266 |
+
|
| 267 |
+
def __iter__(self):
|
| 268 |
+
if self.count == 1:
|
| 269 |
+
yield from self.dp
|
| 270 |
+
return
|
| 271 |
+
i = 0
|
| 272 |
+
while self.count < 0 or i < self.count:
|
| 273 |
+
for elem in self.dp:
|
| 274 |
+
new_elem = copy.copy(elem)
|
| 275 |
+
yield new_elem
|
| 276 |
+
i += 1
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
@functional_datapipe("shard")
|
| 280 |
+
class ShardDataPipe(ShardingFilterIterDataPipe):
|
| 281 |
+
|
| 282 |
+
def __init__(self, dataset: IterDataPipe, partition: bool = False):
|
| 283 |
+
super().__init__(dataset, None)
|
| 284 |
+
self.partition = partition
|
| 285 |
+
self.dp = dataset
|
| 286 |
+
|
| 287 |
+
def apply_sharding(self, num_of_instances: int, instance_id: int,
|
| 288 |
+
sharding_group: SHARDING_PRIORITIES):
|
| 289 |
+
if self.partition:
|
| 290 |
+
return super().apply_sharding(num_of_instances, instance_id,
|
| 291 |
+
sharding_group)
|
| 292 |
+
else:
|
| 293 |
+
# We can not handle uneven data for CV on DDP, so we don't
|
| 294 |
+
# sample data by rank, that means every GPU gets the same
|
| 295 |
+
# and all the CV data
|
| 296 |
+
info = torch.utils.data.get_worker_info()
|
| 297 |
+
if info is None:
|
| 298 |
+
self.num_of_instances = 1
|
| 299 |
+
self.instance_id = 0
|
| 300 |
+
else:
|
| 301 |
+
n_workers_per_device = info.num_workers
|
| 302 |
+
self.num_of_instances = n_workers_per_device
|
| 303 |
+
self.instance_id = info.id
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
@functional_datapipe("interleave")
|
| 307 |
+
class InterlaveDataPipe(IterDataPipe):
|
| 308 |
+
|
| 309 |
+
def __init__(
|
| 310 |
+
self,
|
| 311 |
+
source_datapipes: List[IterDataPipe],
|
| 312 |
+
weights: Optional[List[float]] = None,
|
| 313 |
+
seed=2027,
|
| 314 |
+
):
|
| 315 |
+
super().__init__()
|
| 316 |
+
self.rng = np.random.default_rng(seed)
|
| 317 |
+
self.source_datapipes = source_datapipes
|
| 318 |
+
self.weights = weights
|
| 319 |
+
if weights is None:
|
| 320 |
+
self.weights = [1 / len(self.source_datapipes)] * len(
|
| 321 |
+
self.source_datapipes)
|
| 322 |
+
else:
|
| 323 |
+
self.weights = [weight / sum(weights) for weight in weights]
|
| 324 |
+
self.iters = None
|
| 325 |
+
|
| 326 |
+
def __iter__(self):
|
| 327 |
+
weights = copy.deepcopy(self.weights)
|
| 328 |
+
exhausted = len(self.source_datapipes) * [False]
|
| 329 |
+
if self.iters is None:
|
| 330 |
+
self.iters = [(i, iter(d))
|
| 331 |
+
for i, d in enumerate(self.source_datapipes)]
|
| 332 |
+
while True:
|
| 333 |
+
# TODO(Mddct): rng
|
| 334 |
+
index_iter = self.rng.choice(self.iters, p=weights)
|
| 335 |
+
i, ite = index_iter
|
| 336 |
+
try:
|
| 337 |
+
elem = next(ite)
|
| 338 |
+
yield elem
|
| 339 |
+
except StopIteration:
|
| 340 |
+
weights[i] = 0.
|
| 341 |
+
exhausted[i] = True
|
| 342 |
+
if all(exhausted):
|
| 343 |
+
return
|
| 344 |
+
weights = [weight / sum(weights) for weight in weights]
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
class TextLineDataPipe(IterDataPipe):
|
| 348 |
+
""" Streamming Text line
|
| 349 |
+
"""
|
| 350 |
+
|
| 351 |
+
def __init__(self, filenames, mode='r'):
|
| 352 |
+
super().__init__()
|
| 353 |
+
_dp = datapipes.iter.FileLister(filenames)
|
| 354 |
+
_dp = datapipes.iter.FileOpener(_dp, mode=mode)
|
| 355 |
+
self.dp = _dp
|
| 356 |
+
|
| 357 |
+
def __iter__(self):
|
| 358 |
+
for fname, stream in self.dp:
|
| 359 |
+
for line in stream:
|
| 360 |
+
line = line.strip('\n')
|
| 361 |
+
yield {"file_name": fname, "line": line}
|
| 362 |
+
stream.close()
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
@functional_datapipe("tar_file_and_group")
|
| 366 |
+
class TarsDataPipe(IterDataPipe):
|
| 367 |
+
""" Decode wenet's tar , yield {'txt': "...", "raw": "..."}
|
| 368 |
+
"""
|
| 369 |
+
|
| 370 |
+
def __init__(self, dataset: IterDataPipe) -> None:
|
| 371 |
+
super().__init__()
|
| 372 |
+
self.dp = dataset
|
| 373 |
+
|
| 374 |
+
def __iter__(self):
|
| 375 |
+
from wenet.dataset.processor import AUDIO_FORMAT_SETS
|
| 376 |
+
for sample in self.dp:
|
| 377 |
+
assert 'file_name' in sample
|
| 378 |
+
assert 'line' in sample
|
| 379 |
+
assert 'stream' in sample
|
| 380 |
+
try:
|
| 381 |
+
with tarfile.open(fileobj=sample['stream'],
|
| 382 |
+
mode="r:*") as stream:
|
| 383 |
+
prev_prefix = None
|
| 384 |
+
example = {
|
| 385 |
+
'file_name': sample['file_name'],
|
| 386 |
+
'tar_file_name': sample['line']
|
| 387 |
+
}
|
| 388 |
+
valid = True
|
| 389 |
+
for tarinfo in stream:
|
| 390 |
+
name = tarinfo.name
|
| 391 |
+
pos = name.rfind('.')
|
| 392 |
+
assert pos > 0
|
| 393 |
+
prefix, postfix = name[:pos], name[pos + 1:]
|
| 394 |
+
if prev_prefix is not None and prefix != prev_prefix:
|
| 395 |
+
example['key'] = prev_prefix
|
| 396 |
+
if valid:
|
| 397 |
+
yield example
|
| 398 |
+
example = {
|
| 399 |
+
'file_name': sample['file_name'],
|
| 400 |
+
'tar_file_name': sample['line']
|
| 401 |
+
}
|
| 402 |
+
valid = True
|
| 403 |
+
with stream.extractfile(tarinfo) as file_obj:
|
| 404 |
+
try:
|
| 405 |
+
if postfix == 'txt':
|
| 406 |
+
example['txt'] = file_obj.read().decode(
|
| 407 |
+
'utf8').strip()
|
| 408 |
+
elif postfix in AUDIO_FORMAT_SETS:
|
| 409 |
+
example['wav'] = file_obj.read()
|
| 410 |
+
else:
|
| 411 |
+
example[postfix] = file_obj.read()
|
| 412 |
+
except Exception as ex:
|
| 413 |
+
valid = False
|
| 414 |
+
logging.warning(
|
| 415 |
+
'error to parse {}'.format(name))
|
| 416 |
+
prev_prefix = prefix
|
| 417 |
+
if prev_prefix is not None:
|
| 418 |
+
example['key'] = prev_prefix
|
| 419 |
+
yield example
|
| 420 |
+
except Exception as ex:
|
| 421 |
+
msg = 'In tar_file_and_group: {} when processing {}'.format(
|
| 422 |
+
ex, sample['line'])
|
| 423 |
+
logging.warning(msg)
|
| 424 |
+
finally:
|
| 425 |
+
if 'process' in sample:
|
| 426 |
+
sample['process'].communicate()
|
| 427 |
+
sample['stream'].close()
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
class WenetRawDatasetSource(IterDataPipe):
|
| 431 |
+
|
| 432 |
+
def __init__(self,
|
| 433 |
+
filenames: str,
|
| 434 |
+
prefetch: int = 500,
|
| 435 |
+
partition: bool = True,
|
| 436 |
+
shuffle: bool = False,
|
| 437 |
+
shuffle_size: int = 10000,
|
| 438 |
+
cycle: int = 1) -> None:
|
| 439 |
+
super().__init__()
|
| 440 |
+
self.dp = TextLineDataPipe(filenames)
|
| 441 |
+
if shuffle:
|
| 442 |
+
self.dp = self.dp.shuffle(buffer_size=shuffle_size)
|
| 443 |
+
self.dp = self.dp.repeat(cycle).prefetch(prefetch)
|
| 444 |
+
self.dp = self.dp.shard(partition)
|
| 445 |
+
|
| 446 |
+
def __iter__(self):
|
| 447 |
+
for d in self.dp:
|
| 448 |
+
yield d
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
class WenetTarShardDatasetSource(IterDataPipe):
|
| 452 |
+
|
| 453 |
+
def __init__(self,
|
| 454 |
+
filenames: str,
|
| 455 |
+
prefetch: int = 500,
|
| 456 |
+
partition: bool = True,
|
| 457 |
+
shuffle: bool = False,
|
| 458 |
+
shuffle_size: int = 10000,
|
| 459 |
+
cycle: int = 1) -> None:
|
| 460 |
+
super().__init__()
|
| 461 |
+
self.dp = TextLineDataPipe(filenames)
|
| 462 |
+
if shuffle:
|
| 463 |
+
self.dp = self.dp.shuffle(buffer_size=shuffle_size)
|
| 464 |
+
self.dp = self.dp.repeat(cycle)
|
| 465 |
+
self.dp = self.dp.shard(partition).map_ignore_error(
|
| 466 |
+
parse_url).tar_file_and_group().prefetch(prefetch)
|
| 467 |
+
|
| 468 |
+
def __iter__(self):
|
| 469 |
+
for d in self.dp:
|
| 470 |
+
yield d
|
wenet/dataset/dataset.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 Wenet Community. (authors: Binbin Zhang)
|
| 2 |
+
# 2023 Wenet Community. (authors: Dinghao Zhou)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
from functools import partial
|
| 17 |
+
import sys
|
| 18 |
+
from typing import Optional
|
| 19 |
+
from wenet.dataset import processor
|
| 20 |
+
from wenet.dataset.datapipes import (WenetRawDatasetSource,
|
| 21 |
+
WenetTarShardDatasetSource)
|
| 22 |
+
from wenet.text.base_tokenizer import BaseTokenizer
|
| 23 |
+
from wenet.utils.file_utils import read_symbol_table
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def Dataset(data_type,
|
| 27 |
+
data_list_file,
|
| 28 |
+
tokenizer: Optional[BaseTokenizer] = None,
|
| 29 |
+
conf=None,
|
| 30 |
+
partition=True):
|
| 31 |
+
""" Construct dataset from arguments
|
| 32 |
+
|
| 33 |
+
We have two shuffle stage in the Dataset. The first is global
|
| 34 |
+
shuffle at shards tar/raw file level. The second is global shuffle
|
| 35 |
+
at training samples level.
|
| 36 |
+
|
| 37 |
+
Args:
|
| 38 |
+
data_type(str): raw/shard
|
| 39 |
+
tokenizer (BaseTokenizer or None): tokenizer to tokenize
|
| 40 |
+
partition(bool): whether to do data partition in terms of rank
|
| 41 |
+
"""
|
| 42 |
+
assert conf is not None
|
| 43 |
+
assert data_type in ['raw', 'shard']
|
| 44 |
+
# cycle dataset
|
| 45 |
+
cycle = conf.get('cycle', 1)
|
| 46 |
+
# stage1 shuffle: source
|
| 47 |
+
list_shuffle = conf.get('list_shuffle', True)
|
| 48 |
+
list_shuffle_size = sys.maxsize
|
| 49 |
+
if list_shuffle:
|
| 50 |
+
list_shuffle_conf = conf.get('list_shuffle_conf', {})
|
| 51 |
+
list_shuffle_size = list_shuffle_conf.get('shuffle_size',
|
| 52 |
+
list_shuffle_size)
|
| 53 |
+
if data_type == 'raw':
|
| 54 |
+
dataset = WenetRawDatasetSource(data_list_file,
|
| 55 |
+
partition=partition,
|
| 56 |
+
shuffle=list_shuffle,
|
| 57 |
+
shuffle_size=list_shuffle_size,
|
| 58 |
+
cycle=cycle)
|
| 59 |
+
dataset = dataset.map(processor.parse_json)
|
| 60 |
+
else:
|
| 61 |
+
dataset = WenetTarShardDatasetSource(data_list_file,
|
| 62 |
+
partition=partition,
|
| 63 |
+
shuffle=list_shuffle,
|
| 64 |
+
shuffle_size=list_shuffle_size,
|
| 65 |
+
cycle=cycle)
|
| 66 |
+
dataset = dataset.map_ignore_error(processor.decode_wav)
|
| 67 |
+
|
| 68 |
+
singal_channel_conf = conf.get('singal_channel_conf', {})
|
| 69 |
+
dataset = dataset.map(
|
| 70 |
+
partial(processor.singal_channel, **singal_channel_conf))
|
| 71 |
+
|
| 72 |
+
speaker_conf = conf.get('speaker_conf', None)
|
| 73 |
+
if speaker_conf is not None:
|
| 74 |
+
assert 'speaker_table_path' in speaker_conf
|
| 75 |
+
speaker_table = read_symbol_table(speaker_conf['speaker_table_path'])
|
| 76 |
+
dataset = dataset.map(
|
| 77 |
+
partial(processor.parse_speaker, speaker_dict=speaker_table))
|
| 78 |
+
|
| 79 |
+
if tokenizer is not None:
|
| 80 |
+
dataset = dataset.map(partial(processor.tokenize, tokenizer=tokenizer))
|
| 81 |
+
|
| 82 |
+
filter_conf = conf.get('filter_conf', {})
|
| 83 |
+
dataset = dataset.filter(partial(processor.filter, **filter_conf))
|
| 84 |
+
|
| 85 |
+
resample_conf = conf.get('resample_conf', {})
|
| 86 |
+
dataset = dataset.map(partial(processor.resample, **resample_conf))
|
| 87 |
+
|
| 88 |
+
speed_perturb = conf.get('speed_perturb', False)
|
| 89 |
+
if speed_perturb:
|
| 90 |
+
dataset = dataset.map(partial(processor.speed_perturb))
|
| 91 |
+
|
| 92 |
+
feats_type = conf.get('feats_type', 'fbank')
|
| 93 |
+
assert feats_type in ['fbank', 'mfcc', 'log_mel_spectrogram']
|
| 94 |
+
if feats_type == 'fbank':
|
| 95 |
+
fbank_conf = conf.get('fbank_conf', {})
|
| 96 |
+
dataset = dataset.map(partial(processor.compute_fbank, **fbank_conf))
|
| 97 |
+
elif feats_type == 'mfcc':
|
| 98 |
+
mfcc_conf = conf.get('mfcc_conf', {})
|
| 99 |
+
dataset = dataset.map(partial(processor.compute_mfcc, **mfcc_conf))
|
| 100 |
+
elif feats_type == 'log_mel_spectrogram':
|
| 101 |
+
log_mel_spectrogram_conf = conf.get('log_mel_spectrogram_conf', {})
|
| 102 |
+
dataset = dataset.map(
|
| 103 |
+
partial(processor.compute_log_mel_spectrogram,
|
| 104 |
+
**log_mel_spectrogram_conf))
|
| 105 |
+
spec_aug = conf.get('spec_aug', True)
|
| 106 |
+
spec_sub = conf.get('spec_sub', False)
|
| 107 |
+
spec_trim = conf.get('spec_trim', False)
|
| 108 |
+
if spec_aug:
|
| 109 |
+
spec_aug_conf = conf.get('spec_aug_conf', {})
|
| 110 |
+
dataset = dataset.map(partial(processor.spec_aug, **spec_aug_conf))
|
| 111 |
+
if spec_sub:
|
| 112 |
+
spec_sub_conf = conf.get('spec_sub_conf', {})
|
| 113 |
+
dataset = dataset.map(partial(processor.spec_sub, **spec_sub_conf))
|
| 114 |
+
if spec_trim:
|
| 115 |
+
spec_trim_conf = conf.get('spec_trim_conf', {})
|
| 116 |
+
dataset = dataset.map(partial(processor.spec_trim, **spec_trim_conf))
|
| 117 |
+
|
| 118 |
+
language_conf = conf.get('language_conf', {"limited_langs": ['zh', 'en']})
|
| 119 |
+
dataset = dataset.map(partial(processor.detect_language, **language_conf))
|
| 120 |
+
dataset = dataset.map(processor.detect_task)
|
| 121 |
+
|
| 122 |
+
shuffle = conf.get('shuffle', True)
|
| 123 |
+
if shuffle:
|
| 124 |
+
shuffle_conf = conf.get('shuffle_conf', {})
|
| 125 |
+
dataset = dataset.shuffle(buffer_size=shuffle_conf['shuffle_size'])
|
| 126 |
+
|
| 127 |
+
sort = conf.get('sort', True)
|
| 128 |
+
if sort:
|
| 129 |
+
sort_conf = conf.get('sort_conf', {})
|
| 130 |
+
dataset = dataset.sort(buffer_size=sort_conf['sort_size'],
|
| 131 |
+
key_func=processor.sort_by_feats)
|
| 132 |
+
|
| 133 |
+
batch_conf = conf.get('batch_conf', {})
|
| 134 |
+
batch_type = batch_conf.get('batch_type', 'static')
|
| 135 |
+
assert batch_type in ['static', 'bucket', 'dynamic']
|
| 136 |
+
if batch_type == 'static':
|
| 137 |
+
assert 'batch_size' in batch_conf
|
| 138 |
+
batch_size = batch_conf.get('batch_size', 16)
|
| 139 |
+
dataset = dataset.batch(batch_size, wrapper_class=processor.padding)
|
| 140 |
+
elif batch_type == 'bucket':
|
| 141 |
+
assert 'bucket_boundaries' in batch_conf
|
| 142 |
+
assert 'bucket_batch_sizes' in batch_conf
|
| 143 |
+
dataset = dataset.bucket_by_sequence_length(
|
| 144 |
+
processor.feats_length_fn,
|
| 145 |
+
batch_conf['bucket_boundaries'],
|
| 146 |
+
batch_conf['bucket_batch_sizes'],
|
| 147 |
+
wrapper_class=processor.padding)
|
| 148 |
+
else:
|
| 149 |
+
max_frames_in_batch = batch_conf.get('max_frames_in_batch', 12000)
|
| 150 |
+
dataset = dataset.dynamic_batch(
|
| 151 |
+
processor.DynamicBatchWindow(max_frames_in_batch),
|
| 152 |
+
wrapper_class=processor.padding,
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
return dataset
|
wenet/dataset/deprecated/__init__.py
ADDED
|
File without changes
|
wenet/dataset/deprecated/dataset.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2021 Mobvoi Inc. (authors: Binbin Zhang)
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
|
| 15 |
+
import random
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.distributed as dist
|
| 19 |
+
from torch.utils.data import IterableDataset
|
| 20 |
+
|
| 21 |
+
import wenet.dataset.deprecated.processor as processor
|
| 22 |
+
from wenet.text.base_tokenizer import BaseTokenizer
|
| 23 |
+
from wenet.utils.file_utils import read_lists
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Processor(IterableDataset):
|
| 27 |
+
|
| 28 |
+
def __init__(self, source, f, *args, **kw):
|
| 29 |
+
assert callable(f)
|
| 30 |
+
self.source = source
|
| 31 |
+
self.f = f
|
| 32 |
+
self.args = args
|
| 33 |
+
self.kw = kw
|
| 34 |
+
|
| 35 |
+
def set_epoch(self, epoch):
|
| 36 |
+
self.source.set_epoch(epoch)
|
| 37 |
+
|
| 38 |
+
def __iter__(self):
|
| 39 |
+
""" Return an iterator over the source dataset processed by the
|
| 40 |
+
given processor.
|
| 41 |
+
"""
|
| 42 |
+
assert self.source is not None
|
| 43 |
+
assert callable(self.f)
|
| 44 |
+
return self.f(iter(self.source), *self.args, **self.kw)
|
| 45 |
+
|
| 46 |
+
def apply(self, f):
|
| 47 |
+
assert callable(f)
|
| 48 |
+
return Processor(self, f, *self.args, **self.kw)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class DistributedSampler:
|
| 52 |
+
|
| 53 |
+
def __init__(self, shuffle=True, partition=True):
|
| 54 |
+
self.epoch = -1
|
| 55 |
+
self.update()
|
| 56 |
+
self.shuffle = shuffle
|
| 57 |
+
self.partition = partition
|
| 58 |
+
|
| 59 |
+
def update(self):
|
| 60 |
+
assert dist.is_available()
|
| 61 |
+
if dist.is_initialized():
|
| 62 |
+
self.rank = dist.get_rank()
|
| 63 |
+
self.world_size = dist.get_world_size()
|
| 64 |
+
else:
|
| 65 |
+
self.rank = 0
|
| 66 |
+
self.world_size = 1
|
| 67 |
+
worker_info = torch.utils.data.get_worker_info()
|
| 68 |
+
if worker_info is None:
|
| 69 |
+
self.worker_id = 0
|
| 70 |
+
self.num_workers = 1
|
| 71 |
+
else:
|
| 72 |
+
self.worker_id = worker_info.id
|
| 73 |
+
self.num_workers = worker_info.num_workers
|
| 74 |
+
return dict(rank=self.rank,
|
| 75 |
+
world_size=self.world_size,
|
| 76 |
+
worker_id=self.worker_id,
|
| 77 |
+
num_workers=self.num_workers)
|
| 78 |
+
|
| 79 |
+
def set_epoch(self, epoch):
|
| 80 |
+
self.epoch = epoch
|
| 81 |
+
|
| 82 |
+
def sample(self, data):
|
| 83 |
+
""" Sample data according to rank/world_size/num_workers
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
data(List): input data list
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
List: data list after sample
|
| 90 |
+
"""
|
| 91 |
+
data = list(range(len(data)))
|
| 92 |
+
# TODO(Binbin Zhang): fix this
|
| 93 |
+
# We can not handle uneven data for CV on DDP, so we don't
|
| 94 |
+
# sample data by rank, that means every GPU gets the same
|
| 95 |
+
# and all the CV data
|
| 96 |
+
if self.partition:
|
| 97 |
+
if self.shuffle:
|
| 98 |
+
random.Random(self.epoch).shuffle(data)
|
| 99 |
+
data = data[self.rank::self.world_size]
|
| 100 |
+
data = data[self.worker_id::self.num_workers]
|
| 101 |
+
return data
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class DataList(IterableDataset):
|
| 105 |
+
|
| 106 |
+
def __init__(self, lists, shuffle=True, partition=True):
|
| 107 |
+
self.lists = lists
|
| 108 |
+
self.sampler = DistributedSampler(shuffle, partition)
|
| 109 |
+
|
| 110 |
+
def set_epoch(self, epoch):
|
| 111 |
+
self.sampler.set_epoch(epoch)
|
| 112 |
+
|
| 113 |
+
def __iter__(self):
|
| 114 |
+
sampler_info = self.sampler.update()
|
| 115 |
+
indexes = self.sampler.sample(self.lists)
|
| 116 |
+
for index in indexes:
|
| 117 |
+
# yield dict(src=src)
|
| 118 |
+
data = dict(src=self.lists[index])
|
| 119 |
+
data.update(sampler_info)
|
| 120 |
+
yield data
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def Dataset(data_type,
|
| 124 |
+
data_list_file,
|
| 125 |
+
tokenizer: BaseTokenizer,
|
| 126 |
+
conf,
|
| 127 |
+
partition=True):
|
| 128 |
+
""" Construct dataset from arguments
|
| 129 |
+
|
| 130 |
+
We have two shuffle stage in the Dataset. The first is global
|
| 131 |
+
shuffle at shards tar/raw file level. The second is global shuffle
|
| 132 |
+
at training samples level.
|
| 133 |
+
|
| 134 |
+
Args:
|
| 135 |
+
data_type(str): raw/shard
|
| 136 |
+
bpe_model(str): model for english bpe part
|
| 137 |
+
partition(bool): whether to do data partition in terms of rank
|
| 138 |
+
"""
|
| 139 |
+
assert data_type in ['raw', 'shard']
|
| 140 |
+
lists = read_lists(data_list_file)
|
| 141 |
+
shuffle = conf.get('shuffle', True)
|
| 142 |
+
dataset = DataList(lists, shuffle=shuffle, partition=partition)
|
| 143 |
+
if data_type == 'shard':
|
| 144 |
+
dataset = Processor(dataset, processor.url_opener)
|
| 145 |
+
dataset = Processor(dataset, processor.tar_file_and_group)
|
| 146 |
+
else:
|
| 147 |
+
dataset = Processor(dataset, processor.parse_raw)
|
| 148 |
+
|
| 149 |
+
speaker_conf = conf.get('speaker_conf', None)
|
| 150 |
+
if speaker_conf is not None:
|
| 151 |
+
dataset = Processor(dataset, processor.parse_speaker, **speaker_conf)
|
| 152 |
+
|
| 153 |
+
dataset = Processor(dataset, processor.tokenize, tokenizer)
|
| 154 |
+
filter_conf = conf.get('filter_conf', {})
|
| 155 |
+
dataset = Processor(dataset, processor.filter, **filter_conf)
|
| 156 |
+
|
| 157 |
+
resample_conf = conf.get('resample_conf', {})
|
| 158 |
+
dataset = Processor(dataset, processor.resample, **resample_conf)
|
| 159 |
+
|
| 160 |
+
speed_perturb = conf.get('speed_perturb', False)
|
| 161 |
+
if speed_perturb:
|
| 162 |
+
dataset = Processor(dataset, processor.speed_perturb)
|
| 163 |
+
|
| 164 |
+
feats_type = conf.get('feats_type', 'fbank')
|
| 165 |
+
assert feats_type in ['fbank', 'mfcc', 'log_mel_spectrogram']
|
| 166 |
+
if feats_type == 'fbank':
|
| 167 |
+
fbank_conf = conf.get('fbank_conf', {})
|
| 168 |
+
dataset = Processor(dataset, processor.compute_fbank, **fbank_conf)
|
| 169 |
+
elif feats_type == 'mfcc':
|
| 170 |
+
mfcc_conf = conf.get('mfcc_conf', {})
|
| 171 |
+
dataset = Processor(dataset, processor.compute_mfcc, **mfcc_conf)
|
| 172 |
+
elif feats_type == 'log_mel_spectrogram':
|
| 173 |
+
log_mel_spectrogram_conf = conf.get('log_mel_spectrogram_conf', {})
|
| 174 |
+
dataset = Processor(dataset, processor.compute_log_mel_spectrogram,
|
| 175 |
+
**log_mel_spectrogram_conf)
|
| 176 |
+
|
| 177 |
+
spec_aug = conf.get('spec_aug', True)
|
| 178 |
+
spec_sub = conf.get('spec_sub', False)
|
| 179 |
+
spec_trim = conf.get('spec_trim', False)
|
| 180 |
+
if spec_aug:
|
| 181 |
+
spec_aug_conf = conf.get('spec_aug_conf', {})
|
| 182 |
+
dataset = Processor(dataset, processor.spec_aug, **spec_aug_conf)
|
| 183 |
+
if spec_sub:
|
| 184 |
+
spec_sub_conf = conf.get('spec_sub_conf', {})
|
| 185 |
+
dataset = Processor(dataset, processor.spec_sub, **spec_sub_conf)
|
| 186 |
+
if spec_trim:
|
| 187 |
+
spec_trim_conf = conf.get('spec_trim_conf', {})
|
| 188 |
+
dataset = Processor(dataset, processor.spec_trim, **spec_trim_conf)
|
| 189 |
+
|
| 190 |
+
if shuffle:
|
| 191 |
+
shuffle_conf = conf.get('shuffle_conf', {})
|
| 192 |
+
dataset = Processor(dataset, processor.shuffle, **shuffle_conf)
|
| 193 |
+
|
| 194 |
+
sort = conf.get('sort', True)
|
| 195 |
+
if sort:
|
| 196 |
+
sort_conf = conf.get('sort_conf', {})
|
| 197 |
+
dataset = Processor(dataset, processor.sort, **sort_conf)
|
| 198 |
+
|
| 199 |
+
batch_conf = conf.get('batch_conf', {})
|
| 200 |
+
dataset = Processor(dataset, processor.batch, **batch_conf)
|
| 201 |
+
dataset = Processor(dataset, processor.padding)
|
| 202 |
+
return dataset
|