Model Card for VITS-CSMSC (IB-Robot, hybrid CPU + Ascend 310P)
PaddleSpeech VITS-CSMSC β single-speaker Mandarin text-to-speech, 22.05 kHz β
packaged for the IB-Robot framework
as a hybrid deployment: the text encoder and duration predictor run as ONNX
on CPU, and the flow decoder (which contains the vocoder and dominates compute)
runs as an .om on Ascend 310P1.
This is the speech-output stage of the robot pipeline. Accepts arbitrary Chinese text; the voice is the fixed CSMSC speaker.
Deployments
| deployment | backend | flow artifact | frames | notes |
|---|---|---|---|---|
ascend_310p |
ONNX CPU + Ascend ACL | vits_flow_decoder_dynamic_fp16.om |
variable | recommended, any utterance length |
torch_cpu |
ONNX Runtime CPU | artifacts/onnx/vits_flow_decoder.onnx |
variable | full CPU reference path |
Two fixed-shape flow decoders also ship in the repo without their own deployment entry, matching the convention across this organisation that one hardware target carries one deployment:
| file | frames | audio | notes |
|---|---|---|---|
vits_flow_decoder_t639_fp16.om |
639 | 7.42 s | ~20 % lower flow latency than dynamic, 44 % of its size |
vits_flow_decoder_t681_fp16.om |
681 | 7.91 s | second fixed-shape build |
Both are declared in bundle.files and load by pointing an ACL session at the
file directly. They accept exactly their frame count, so an utterance has
to be padded or trimmed to fit; the dynamic build takes any length, which is
why it is the one wired to ascend_310p. Accuracy for all three is in
Validation β they are within 1e-5 cosine of each other.
The ascend_310p row is a composite deployment in the manifest: there is no
single runtime_profile, because there is no single backend. role_identities
and role_runtime_profiles name the backend per role β onnx on CPU for
text_encoder and duration, ascend/acl for flow_decoder. torch_cpu
runs all three roles on ONNX Runtime and so keeps one shared profile.
The two host-side steps between duration and flow (duration_expand_path,
sample_z_p) are listed in each deployment's metadata.host_stages; they are
not model roles and have no artifact.
Shared contract: tensor_model/vits/synthesize
(tts.phone_ids [1,-1] -> tts.audio [1,-1]), preprocessing
paddlespeech-zh-frontend-jieba-pypinyin-phoneid-v1, output
mono-float32-pcm-22050hz-v1, logical revision
paddlespeech-vits-csmsc@onnx-export-v1.
ascend_310p uses the dynamic-shape flow decoder, so no padding or trimming
is needed. For latency-sensitive use with a known utterance length, load one of
the two fixed-shape builds above directly.
Pipeline
Chinese text
β PaddleSpeech frontend / pypinyin β phone ids
β text encoder (ONNX, CPU) β x, m_p, logs_p (192-d), x_mask
β duration predictor (ONNX, CPU) β logw
β host: alignment expansion + z_p (see below)
β flow decoder (.om, Ascend 310P1) β waveform
β 22.05 kHz mono wav
Host stage (between duration and flow)
This is not a thin glue layer β it is part of the model's semantics and must be
reproduced exactly. See assets/reference_impl/run_vits_hybrid_310p.py:
dur = ceil(exp(logw) * float32(x_mask)) # per-phone frame counts
path = generate_path(dur, ...) # [1, T_text, T_frames] alignment
m = (path.transpose @ m_p.transpose).transpose # expand to frames
logs = (path.transpose @ logs_p.transpose).transpose
y_mask = ones([1, 1, T_frames])
z_p = m + randn_like(m) * exp(logs) * 0.667 # noise_scale 0.667
The reference uses seed 1234 for reproducibility; the sampling of z_p is
genuinely stochastic, so runs with different seeds produce different (equally
valid) audio, and bit-exact comparison against a reference requires pinning it.
Tensor contract
text encoder text int64 [1, -1] phone ids
text_lengths int64 [1]
β x float32 [1, 192, -1] outputs are positional and
m_p float32 [1, 192, -1] come back in this order
logs_p float32 [1, 192, -1]
x_mask bool [1, 1, -1]
duration x float32 [1, 192, -1]
x_mask bool [1, 1, -1] bool, not float
β logw float32 [1, 1, -1]
flow decoder z_p float32 [1, 192, T] T = 639 / 681 / -1
y_mask float32 [1, 1, T]
β wav float32 [1, T*256] 256 samples per frame
Sample rate 22050 Hz, hop 256 β one frame β 11.61 ms.
Repository Structure
inference_manifest.jsonβ deployment routing (schema v3)assets/adapter.jsonβ adapter identity (vits/synthesize)assets/phone_id_map.txtβ 268-entry phoneβid map used by the frontendassets/snapshot_iter_150000.pdzβ the upstream Paddle training checkpoint (1.1 GB), byte-identical to the one invits_csmsc_ckpt_1.4.0.zipassets/default.yamlβ that checkpoint's own training configassets/reference_impl/run_vits_hybrid_310p.pyβ the hybrid runner, and the normative source for the host stage aboveassets/reference_impl/synthesize_vits_310p.pyβ text β wav CLIartifacts/onnx/*.onnxβ the three ONNX graphs (CPU stages + CPU reference)artifacts/ascend_310p/*.omβ three flow decoder builds
Source Model
Upstream is PaddleSpeech VITS-CSMSC, released checkpoint
vits_csmsc_ckpt_1.4.0. The manifest's logical revision is
paddlespeech-vits-csmsc@onnx-export-v1 β it names the ONNX export this bundle
runs, not the upstream release tag, which is 1.4.0.
wget https://paddlespeech.cdn.bcebos.com/Parakeet/released_models/vits/\
vits_csmsc_ckpt_1.4.0.zip
# 1,027,863,302 bytes
# sha256 994340036777b24f3bf02967bf37f570d40e5514b54ff0001c5b4732921ae064
The archive holds exactly three files, and all three are shipped here byte-for-byte (sha256 verified against a fresh download and extraction):
| shipped as | in archive | bytes | sha256 |
|---|---|---|---|
assets/snapshot_iter_150000.pdz |
snapshot_iter_150000.pdz |
1,116,898,380 | ef0faad8186cdd1aa278b61e0b2a3cb7dee9216d1b84f1bc760ec887bcd935c7 |
assets/default.yaml |
default.yaml |
7,835 | 7eebdd044a663f2485b6abd54f6179e90da64c0c7c109778295f9a23be6dcbfc |
assets/phone_id_map.txt |
phone_id_map.txt |
2,128 | ca00d60618a00a2dc1e42002faaa9024c9e5057e04b1e834325950111cee59c3 |
phone_id_map.txt pins the phone inventory, and therefore the text frontend
contract, to this exact release. default.yaml is the checkpoint's own training
config and independently confirms the numbers this bundle's tensor contract
depends on: fs: 22050, n_shift: 256 (the 256 samples per frame above) and
hidden_channels: 192 (the 192-d z_p).
Shipping the checkpoint follows the convention in the rest of this
organisation, where the upstream weights live under assets/. Note that it is
not the artifact any deployment loads β every deployment runs the ONNX
graphs below. It is there so the bundle carries its own provenance.
The three ONNX graphs were exported from that checkpoint in separate passes, which is why their opsets differ (12 / 13 / 11):
| file | bytes | opset | sha256 |
|---|---|---|---|
vits_text_encoder_clean.onnx |
55,329,745 | 12 | 15c5fb01538ce38c4748c9bc5892fc0af9bee7c65b228cd116651237a3f72536 |
vits_duration.onnx |
2,644,862 | 13 | f75fd325801a62976ecea0f52ba03a384197f574fedf0486598e5c0fc840c89c |
vits_flow_decoder.onnx |
86,596,889 | 11 | 3cb9dfe036b843abce71b867626f23d60745c0f0f35f29787ecec7fbfaf0f7fa |
vits_flow_decoder_t639_fp16.om |
91,708,818 | β | b7da515895536b7baf75314b908344164efb916ffc8cc2246c6dac0e4c4cf91b |
vits_flow_decoder_t681_fp16.om |
91,706,422 | β | df192baf48cefb732f52945cd411d3c88bbd310cac784dc51e11a8a7ed0d2b61 |
vits_flow_decoder_dynamic_fp16.om |
210,986,822 | β | c8b26160a0bc7e4de987424fab228bcc8dfd3fb053bd910737443999412c40d8 |
Shipping the checkpoint does not make the ONNX byte-reproducible from it:
the export script is not part of this bundle, and PaddleSpeech static-model
export behaviour changed with PaddlePaddle β₯ 2.5. What is established is the
accuracy of the export β cosine 0.9999999999998159 against the Paddle
reference, below β plus the exact checkpoint and config it came from.
Validation
All measured on a real Ascend 310P1, against ONNX Runtime CPU fp32.
ONNX export vs Paddle reference (text encoder + duration) β the export itself is lossless:
cosine 0.9999999999998159
max_abs 4.291534423828125e-06
Flow decoder OM vs ONNX, on the flow subgraph alone:
| build | cosine | max_abs | latency mean | p95 | subgraph RTF |
|---|---|---|---|---|---|
| t639 | 0.9999842557218173 | 0.01094835 | 96.17 ms | 96.38 ms | 0.01296 |
| t681 | 0.9999692545131995 | 0.01450025 | 102.72 ms | β | 0.01299 |
Those are the conversion-time numbers. They have since been independently
reproduced on the shipped artifacts β same board, same artifacts/onnx/ vits_flow_decoder.onnx reference, but with z_p built through the real
encoder β duration β host-alignment path rather than synthetic tensors
(_tools/board/verify_vits_flow_om_vs_onnx.py):
| build | frames | cosine | max_abs | rel_l2 | latency mean | p95 | subgraph RTF |
|---|---|---|---|---|---|---|---|
| t639 | 639 | 0.99998847 | 0.008860 | 0.00480 | 95.89 ms | 96.00 ms | 0.01292 |
| t681 | 681 | 0.99998923 | 0.008666 | 0.00464 | 102.47 ms | 102.71 ms | 0.01296 |
| dynamic | 399 | 0.99998542 | 0.006929 | 0.00540 | 77.32 ms | 77.90 ms | 0.01669 |
Latency matches the conversion report to within 0.3 ms on every build, and
accuracy comes out slightly better on realistic z_p than on the synthetic
input used at conversion time. The dynamic build's higher RTF at 399 frames is
the fixed per-call overhead spread over less audio, not a slower kernel β at
639 frames it lands within ~40 ms of the fixed build (see below).
End-to-end hybrid pipeline, 20 runs, 7.42 s of output audio. Measured with
the fixed t639 build, which is what the reference runner loads
(assets/reference_impl/run_vits_hybrid_310p.py:15); the same pipeline on the
ascend_310p dynamic build is the baseline row two tables down, 367.63 ms:
cosine_vs_onnx 0.9999853236698978
max_abs 0.008778512477874756
latency mean 327.38 ms p50 323.81 ms p95 355.89 ms
RTF 0.0441
Per-stage breakdown (mean, ms):
| stage | backend | ms | share |
|---|---|---|---|
| text encoder | ONNX CPU | 48.71 | 15% |
| duration | ONNX CPU | 25.38 | 8% |
| host alignment + z_p | numpy CPU | 156.64 | 48% |
| flow decoder | Ascend OM | 96.53 | 29% |
The host alignment stage is now the single largest cost β larger than the NPU
work it feeds. It is pure numpy (generate_path plus two matmuls) and is the
obvious next optimisation target; moving it onto the NPU or vectorising it
would cut end-to-end latency by roughly a third.
Dynamic build, variable text length:
| sample | phones | frames | audio | latency mean | RTF |
|---|---|---|---|---|---|
| baseline | 101 | 639 | 7.4188 s | 367.63 ms | 0.04955 |
| new_text | 144 | 915 | 10.6231 s | 414.97 ms | 0.03906 |
Latency grows sub-linearly with length, so RTF improves on longer utterances. The dynamic build costs ~40 ms over the fixed t639 build at the same length.
Usage
from inference_manifest import load_inference_manifest
validated = load_inference_manifest("models/vits-csmsc", "ascend_310p")
The hybrid path needs both an ONNX Runtime CPU session (encoder, duration) and
an ACL session (flow), plus the host stage between them β see
assets/reference_impl/run_vits_hybrid_310p.py, which is the normative
implementation.
Known Limitations
- Not a pure-NPU TTS. Text encoder and duration remain on CPU; only the flow decoder is compiled to OM.
- Chinese only. English and other languages are not a validation target.
- Fixed speaker. Single-speaker CSMSC voice, no speaker conditioning.
- The two extra fixed-shape OMs require exact frame counts (639 / 681).
The
ascend_310pdeployment uses the dynamic build and has no such limit; the fixed builds are for callers who know their length and want the ~40 ms. - Output is stochastic (
noise_scale = 0.667) unless the RNG seed is pinned.
Environment
Validated on: Ascend 310P1 with CANN and ais_bench, Python 3.11.6,
ONNX Runtime 1.23.2.
License
Code and packaging: Apache-2.0. Model weights follow the upstream PaddleSpeech license (Apache-2.0); the CSMSC corpus is subject to its own terms.
Citation
@inproceedings{vits2021,
title = {Conditional Variational Autoencoder with Adversarial Learning for End-to-End Text-to-Speech},
author = {Kim, Jaehyeon and Kong, Jungil and Son, Juhee},
booktitle = {arXiv preprint arXiv:2106.06103},
year = {2021}
}
@article{paddlespeech2022,
title = {PaddleSpeech: An Easy-to-Use All-in-One Speech Toolkit},
author = {Zhang, Hui and Yuan, Tian and Chen, Junkun and Li, Xintong and Zheng, Renjie and Huang, Yuxin and Chen, Xiaojie and Gong, Enlei and Chen, Zeyu and Hu, Xiaoguang and Yu, Dianhai and Ma, Yanjun and Huang, Liang},
journal = {arXiv preprint arXiv:2205.12007},
year = {2022}
}
@software{ib_robot,
title = {IB-Robot: Intelligence Boom Robot},
url = {https://atomgit.com/openeuler/IB_Robot},
license = {Apache-2.0}
}