owensong commited on
Commit
83d6c02
·
verified ·
1 Parent(s): 3e8c567

Add verified FP32 ONNX Runtime export

Browse files

Adds dynamic-length ONNX graphs, a torch-free runner, reproducible exporter, provenance, parity reports, checksums, and updated documentation.

CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
- # Changelog
2
-
 
 
 
 
 
 
 
3
  ## 2.0.0 - 2026-07-24
4
 
5
  - Release Inflect-Micro-v2 as a complete 24 kHz English text-to-waveform package.
 
1
+ # Changelog
2
+
3
+ ## 2.1.0 - 2026-07-25
4
+
5
+ - Add verified FP32 ONNX graphs with dynamic text and waveform lengths.
6
+ - Add a torch-free ONNX Runtime API and CLI with CPU, CUDA, and DirectML provider selection.
7
+ - Add a reproducible exporter, checkpoint provenance, parity reports, and SHA-256 checksums.
8
+ - Credit Robert Bak's original ONNX conversion and browser implementation.
9
+
10
  ## 2.0.0 - 2026-07-24
11
 
12
  - Release Inflect-Micro-v2 as a complete 24 kHz English text-to-waveform package.
README.md CHANGED
@@ -14,6 +14,8 @@ tags:
14
  - edge-ai
15
  - small-model
16
  - pytorch
 
 
17
  - vits
18
  - 24khz
19
  thumbnail: assets/inflect-v2-repository-hero.png
@@ -171,7 +173,9 @@ equal-work pooled across their two tested voices.
171
  Because Inflect uses the larger 100-prompt steady-state run while comparator
172
  rows use the shorter 50-prompt confirmation pass, this table is deployment
173
  context rather than a perfectly matched speed leaderboard. Several comparators
174
- also use optimized ONNX runtimes while this Inflect release uses PyTorch.
 
 
175
 
176
  </details>
177
 
@@ -257,6 +261,28 @@ sample_rate, waveform = tts.synthesize("The complete model runs locally.")
257
 
258
  The result is a 24 kHz mono `float32` waveform. Long input is split at punctuation-aware boundaries, synthesized chunk by chunk, and joined with controlled pauses.
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  ## Release profile
261
 
262
  | **Local runtime** | **Long-text handling** |
@@ -317,6 +343,7 @@ This release is inference-first. New-voice and new-language adaptation are **not
317
  | `inference.py` | Public Python API and CLI |
318
  | `inflect_vits_frontend.py` | English normalization, phonemization, and punctuation frontend |
319
  | `runtime/` | Self-contained model implementation |
 
320
  | `samples/` | Held-out example generations |
321
  | `evaluation/final/` | Frozen benchmark prompts, reports, and protocol artifacts |
322
  | `docs/` | API, deployment, evaluation, adaptation, and export documentation |
 
14
  - edge-ai
15
  - small-model
16
  - pytorch
17
+ - onnx
18
+ - onnxruntime
19
  - vits
20
  - 24khz
21
  thumbnail: assets/inflect-v2-repository-hero.png
 
173
  Because Inflect uses the larger 100-prompt steady-state run while comparator
174
  rows use the shorter 50-prompt confirmation pass, this table is deployment
175
  context rather than a perfectly matched speed leaderboard. Several comparators
176
+ also use optimized ONNX runtimes, while the published Inflect benchmark above
177
+ uses the canonical PyTorch runtime. The separately released Inflect ONNX path
178
+ has not been substituted into those benchmark numbers.
179
 
180
  </details>
181
 
 
261
 
262
  The result is a 24 kHz mono `float32` waveform. Long input is split at punctuation-aware boundaries, synthesized chunk by chunk, and joined with controlled pauses.
263
 
264
+ ### ONNX Runtime
265
+
266
+ A verified FP32 ONNX export is included under [`onnx/`](https://huggingface.co/owensong/Inflect-Micro-v2/tree/main/onnx).
267
+ It supports dynamic lengths, CPU/CUDA/DirectML provider selection, deterministic
268
+ seeds, and the same long-text wrapper without importing PyTorch:
269
+
270
+ ```bash
271
+ python -m pip install -r onnx/requirements.txt
272
+ python onnx/inference_onnx.py \
273
+ --text "The complete model now runs through ONNX Runtime." \
274
+ --output sample-onnx.wav \
275
+ --provider cpu \
276
+ --seed 7
277
+ ```
278
+
279
+ The neural model is split into `duration.onnx` and `decode.onnx`; together they
280
+ contain the complete learned text-to-waveform path. The English eSpeak-ng
281
+ frontend remains CPU-side code. See the
282
+ [`ONNX guide`](https://huggingface.co/owensong/Inflect-Micro-v2/blob/main/onnx/README.md)
283
+ for graph contracts, provenance, parity measurements, browser deployment, and
284
+ re-export instructions.
285
+
286
  ## Release profile
287
 
288
  | **Local runtime** | **Long-text handling** |
 
343
  | `inference.py` | Public Python API and CLI |
344
  | `inflect_vits_frontend.py` | English normalization, phonemization, and punctuation frontend |
345
  | `runtime/` | Self-contained model implementation |
346
+ | `onnx/` | Verified FP32 ONNX graphs, torch-free runner, parity report, checksums, and exporter |
347
  | `samples/` | Held-out example generations |
348
  | `evaluation/final/` | Frozen benchmark prompts, reports, and protocol artifacts |
349
  | `docs/` | API, deployment, evaluation, adaptation, and export documentation |
docs/EXPORTS.md CHANGED
@@ -6,19 +6,38 @@
6
  | --- | --- | --- |
7
  | PyTorch FP32 | Supported | Canonical weights and the fully tested runtime. |
8
  | PyTorch FP16/BF16 | Not released | Smaller in memory, but not validated for waveform quality or CPU compatibility. |
9
- | ONNX | Not released | Dynamic text length, stochastic latent sampling, and custom model operations require a dedicated export/runtime path. |
10
  | INT8 / INT4 | Not released | Naive weight quantization can audibly damage the integrated waveform decoder. |
11
  | GGUF | Not applicable today | GGUF targets transformer/LLM runtimes and is not a drop-in container for this VITS-family convolutional waveform model. |
12
  | Core ML / TFLite | Not released | Requires separate conversion and target-device validation. |
13
 
14
- ## Why only FP32 ships initially
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  The model is already small: 37.53 MB for Micro and 15.97 MB for Nano. A format
17
  is useful only if it is smaller or faster **and** preserves pronunciation,
18
  stability, and waveform quality. An export that loads but produces degraded
19
  speech is not release-ready.
20
 
21
- The canonical `model.pth` remains the source of truth. Future exports must:
 
22
 
23
  1. reproduce deterministic fixed-seed outputs within a declared tolerance;
24
  2. pass matched semantic-WER and signal-diagnostic gates;
@@ -28,7 +47,8 @@ The canonical `model.pth` remains the source of truth. Future exports must:
28
 
29
  ## Practical deployment today
30
 
31
- Use the FP32 package and keep one `InflectTTS` instance loaded. Nano is the
32
- recommended footprint-first deployment; Micro is the quality-first deployment.
33
- Install the CPU or CUDA PyTorch build appropriate for the target platform before
34
- installing the remaining requirements.
 
 
6
  | --- | --- | --- |
7
  | PyTorch FP32 | Supported | Canonical weights and the fully tested runtime. |
8
  | PyTorch FP16/BF16 | Not released | Smaller in memory, but not validated for waveform quality or CPU compatibility. |
9
+ | ONNX FP32 | Supported | Verified dynamic-length graphs, torch-free Python runner, and reproducible exporter under `onnx/`. |
10
  | INT8 / INT4 | Not released | Naive weight quantization can audibly damage the integrated waveform decoder. |
11
  | GGUF | Not applicable today | GGUF targets transformer/LLM runtimes and is not a drop-in container for this VITS-family convolutional waveform model. |
12
  | Core ML / TFLite | Not released | Requires separate conversion and target-device validation. |
13
 
14
+ ## Supported ONNX package
15
+
16
+ The official ONNX package contains:
17
+
18
+ - `onnx/duration.onnx`: token sequence to aligned acoustic distribution;
19
+ - `onnx/decode.onnx`: aligned distribution and seeded noise to waveform;
20
+ - `onnx/inference_onnx.py`: ONNX Runtime API and CLI;
21
+ - `onnx/export_onnx.py`: reproducible export from the canonical checkpoint;
22
+ - machine-readable provenance, parity results, and SHA-256 checksums.
23
+
24
+ Dynamic text and waveform lengths are supported. The public wrapper implements
25
+ seeded latent sampling, speed and variation controls, punctuation-aware
26
+ long-text chunking, and CPU/CUDA/DirectML provider selection. eSpeak-ng remains
27
+ the non-neural English frontend and is not embedded in the ONNX graphs.
28
+
29
+ See [`onnx/README.md`](https://huggingface.co/owensong/Inflect-Micro-v2/blob/main/onnx/README.md)
30
+ for installation and verified parity measurements.
31
+
32
+ ## Why only verified FP32 ships
33
 
34
  The model is already small: 37.53 MB for Micro and 15.97 MB for Nano. A format
35
  is useful only if it is smaller or faster **and** preserves pronunciation,
36
  stability, and waveform quality. An export that loads but produces degraded
37
  speech is not release-ready.
38
 
39
+ The canonical `model.pth` remains the source of truth. Future quantized or
40
+ platform-specific exports must:
41
 
42
  1. reproduce deterministic fixed-seed outputs within a declared tolerance;
43
  2. pass matched semantic-WER and signal-diagnostic gates;
 
47
 
48
  ## Practical deployment today
49
 
50
+ Use either the canonical PyTorch runtime or the verified FP32 ONNX Runtime
51
+ package and keep one engine instance loaded. Nano is the recommended
52
+ footprint-first deployment; Micro is the quality-first deployment. Choose ONNX
53
+ for C/C++ bindings, browser work, DirectML, or other ONNX Runtime execution
54
+ providers. Choose PyTorch when integrating with the original Python model code.
onnx/README.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Inflect-Micro-v2 ONNX
2
+
3
+ Official, verified FP32 ONNX Runtime export of
4
+ [Inflect-Micro-v2](https://huggingface.co/owensong/Inflect-Micro-v2).
5
+ The neural model is split into two graphs:
6
+
7
+ | File | Purpose | Size |
8
+ | --- | --- | ---: |
9
+ | `duration.onnx` | Text tokens to aligned acoustic distribution | 7.32 MB |
10
+ | `decode.onnx` | Acoustic distribution and seeded noise to 24 kHz waveform | 30.43 MB |
11
+
12
+ The complete ONNX neural weights are about **37.75 MB**. The original FP32
13
+ PyTorch checkpoint remains the canonical source of truth.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ python -m pip install -r onnx/requirements.txt
19
+ ```
20
+
21
+ ONNX Runtime CPU is installed by default. For another execution provider, use
22
+ the appropriate ONNX Runtime package for the target platform.
23
+
24
+ ## Generate speech
25
+
26
+ From the repository root:
27
+
28
+ ```bash
29
+ python onnx/inference_onnx.py \
30
+ --text "A compact voice can still sound clear and expressive." \
31
+ --output sample.wav \
32
+ --provider cpu \
33
+ --speed 1.0 \
34
+ --variation 0.667 \
35
+ --seed 7
36
+ ```
37
+
38
+ Python:
39
+
40
+ ```python
41
+ import sys
42
+
43
+ sys.path.insert(0, "onnx")
44
+ from inference_onnx import InflectONNX
45
+
46
+ tts = InflectONNX(".", provider="cpu")
47
+ sample_rate, waveform = tts.synthesize(
48
+ "Inflect runs through ONNX Runtime.",
49
+ speed=1.0,
50
+ variation=0.667,
51
+ seed=7,
52
+ )
53
+ ```
54
+
55
+ Accepted provider aliases are `cpu`, `cuda`, and `directml`. The corresponding
56
+ provider must exist in the installed ONNX Runtime build.
57
+
58
+ ## What the export includes
59
+
60
+ - Dynamic text and waveform lengths
61
+ - The duration predictor, acoustic generator, flow, and waveform decoder
62
+ - Seeded stochastic latent sampling in the Python wrapper
63
+ - The same punctuation-aware long-text chunking used by the PyTorch package
64
+ - 24 kHz mono `float32` waveform output
65
+
66
+ The English normalization and phonemization frontend remains Python code and
67
+ uses eSpeak-ng on the CPU. The ONNX files contain the learned neural graphs,
68
+ not eSpeak-ng itself.
69
+
70
+ ## Verification
71
+
72
+ The packaged graphs pass:
73
+
74
+ - ONNX checker validation at opset 17
75
+ - Dynamic-length inference tests
76
+ - Short- and long-form end-to-end synthesis
77
+ - Same-seed determinism and different-seed variation checks
78
+ - Direct numerical comparison with the canonical PyTorch checkpoint
79
+ - Independent re-export comparison from the published `model.pth`
80
+
81
+ The direct PyTorch comparison measured a maximum absolute waveform error of
82
+ `1.7583e-05`, mean absolute error of `2.2595e-07`, and correlation above
83
+ `0.9999999999`. An independent re-export matched this package within
84
+ `1.0617e-07` maximum waveform error.
85
+
86
+ See [`parity_report.json`](parity_report.json), [`SOURCE.json`](SOURCE.json),
87
+ and [`checksums.sha256`](checksums.sha256) for machine-readable details.
88
+
89
+ ## Re-export
90
+
91
+ The export script is included for reproducibility:
92
+
93
+ ```bash
94
+ python -m pip install torch==2.4.1 onnx==1.19.1
95
+ python onnx/export_onnx.py \
96
+ --model-dir . \
97
+ --output-dir onnx-reexport \
98
+ --model-name Inflect-Micro-v2 \
99
+ --source-repository owensong/Inflect-Micro-v2 \
100
+ --source-revision 3e8c567f9bce1a309eea12d38ca9fbf7050df72e
101
+ ```
102
+
103
+ Serialization hashes can differ across exporter versions even when numerical
104
+ outputs match. Validate outputs rather than expecting byte-identical files.
105
+
106
+ ## Browser implementation and credit
107
+
108
+ Robert Bak produced the original ONNX conversion and a complete browser runner:
109
+ [robertbak/webtts-inflect](https://github.com/robertbak/webtts-inflect).
110
+ The official package preserves that conversion credit and adds checkpoint
111
+ provenance, metadata, reproducible export tooling, and Python ONNX Runtime
112
+ validation. The browser project is Apache-2.0 and runs synthesis locally with
113
+ ONNX Runtime Web and an eSpeak-ng WASM frontend.
114
+
115
+ ## Quantization status
116
+
117
+ Only verified FP32 ONNX graphs are released. INT8 and INT4 are not included:
118
+ naive quantization of the flow and waveform decoder can produce audible
119
+ degradation, and no quantized build has passed matched listening and
120
+ intelligibility validation yet.
onnx/SOURCE.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "inflect_v2_onnx_source_v1",
3
+ "model": "Inflect-Micro-v2",
4
+ "source_repository": "owensong/Inflect-Micro-v2",
5
+ "source_revision": "3e8c567f9bce1a309eea12d38ca9fbf7050df72e",
6
+ "source_model_file": "model.pth",
7
+ "source_model_sha256": "3eede065c9ccfa88ade0a5a9a5c23de34afcbbb32213e59aad44d5cf100fdee8",
8
+ "conversion_repository": "https://github.com/robertbak/webtts-inflect",
9
+ "conversion_commit": "60832b1978115e4ed7fdddce4d9b9fd9161f9fe7",
10
+ "conversion_author": "Robert Bak",
11
+ "official_packaging": "Owen Song",
12
+ "precision": "FP32",
13
+ "onnx_opset": 17,
14
+ "sample_rate_hz": 24000,
15
+ "license": "Apache-2.0"
16
+ }
onnx/checksums.sha256 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ 7940923add86f76e7fa78d910b0632ca1779f8cc9a2ca2b49236381a9ca77183 decode.onnx
2
+ b728ca2564b9e5b7d6cf5e446f65e02a6fe2f1880ba281466fec93a667dd2388 duration.onnx
3
+ 77bd27277861c8cae670247e3c9e3b976134a4886506fb069331aa6828752be6 export_onnx.py
4
+ cf48c4812f68e8315d80568e93650c64a7ad3fdfb9a0051c61a0d1d0746df274 inference_onnx.py
5
+ 10c88564d24fe65ea730dc6ec98aa19ec74d3590efa427595a3c122c4c0f7039 parity_report.json
6
+ ecfaf6a3edca6ffc3152a61ccc6c27240f42daaa0ddf6efc08e1f9b277295734 README.md
7
+ 43abd2c128e4296f1f0171f12946f3fc7dee3f8c8e2bb1858bcae6b43b62b3c9 requirements.txt
8
+ e950bf44034b011a64bba3773b388e386418ef952c3cd17ceefda5bfa94a3aaa SOURCE.json
onnx/decode.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7940923add86f76e7fa78d910b0632ca1779f8cc9a2ca2b49236381a9ca77183
3
+ size 30427790
onnx/duration.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b728ca2564b9e5b7d6cf5e446f65e02a6fe2f1880ba281466fec93a667dd2388
3
+ size 7322687
onnx/export_onnx.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import logging
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import onnx
11
+ import torch
12
+ from torch import nn
13
+
14
+
15
+ PACKAGE_ROOT = Path(__file__).resolve().parents[1]
16
+ commons = None
17
+
18
+
19
+ class DurationGraph(nn.Module):
20
+ def __init__(self, model: nn.Module) -> None:
21
+ super().__init__()
22
+ self.enc_p = model.enc_p
23
+ self.dp = model.dp
24
+
25
+ def forward(
26
+ self,
27
+ tokens: torch.Tensor,
28
+ lengths: torch.Tensor,
29
+ length_scale: torch.Tensor,
30
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
31
+ hidden, m_p, logs_p, x_mask = self.enc_p(tokens, lengths)
32
+ logw = self.dp(hidden, x_mask, g=None)
33
+ durations = torch.ceil(torch.exp(logw) * x_mask * length_scale)
34
+ y_lengths = torch.clamp_min(torch.sum(durations, [1, 2]), 1).long()
35
+ if commons is None:
36
+ raise RuntimeError("Runtime helpers were not initialized.")
37
+ y_mask = torch.unsqueeze(commons.sequence_mask(y_lengths, None), 1).to(
38
+ x_mask.dtype
39
+ )
40
+ attention_mask = torch.unsqueeze(x_mask, 2) * torch.unsqueeze(y_mask, -1)
41
+ attention = commons.generate_path(durations, attention_mask)
42
+ m_p_exp = torch.matmul(
43
+ attention.squeeze(1),
44
+ m_p.transpose(1, 2),
45
+ ).transpose(1, 2)
46
+ logs_p_exp = torch.matmul(
47
+ attention.squeeze(1),
48
+ logs_p.transpose(1, 2),
49
+ ).transpose(1, 2)
50
+ return m_p_exp, logs_p_exp, y_mask
51
+
52
+
53
+ class DecodeGraph(nn.Module):
54
+ def __init__(self, model: nn.Module) -> None:
55
+ super().__init__()
56
+ self.flow = model.flow
57
+ self.decoder = model.dec
58
+
59
+ def forward(
60
+ self,
61
+ m_p_exp: torch.Tensor,
62
+ logs_p_exp: torch.Tensor,
63
+ y_mask: torch.Tensor,
64
+ zp_noise: torch.Tensor,
65
+ noise_scale: torch.Tensor,
66
+ ) -> torch.Tensor:
67
+ z_p = m_p_exp + zp_noise * torch.exp(logs_p_exp) * noise_scale
68
+ z = self.flow(z_p, y_mask, g=None, reverse=True)
69
+ return self.decoder(z * y_mask, g=None)
70
+
71
+
72
+ def sha256(path: Path) -> str:
73
+ digest = hashlib.sha256()
74
+ with path.open("rb") as handle:
75
+ for block in iter(lambda: handle.read(1024 * 1024), b""):
76
+ digest.update(block)
77
+ return digest.hexdigest()
78
+
79
+
80
+ def load_model(root: Path) -> nn.Module:
81
+ global commons
82
+ runtime_root = root / "runtime"
83
+ sys.path.insert(0, str(runtime_root))
84
+ sys.path.insert(0, str(root))
85
+ import commons as runtime_commons
86
+ import utils
87
+ from models import SynthesizerTrn
88
+ from text.symbols import symbols
89
+
90
+ commons = runtime_commons
91
+ hps = utils.get_hparams_from_file(str(root / "config.json"))
92
+ model = SynthesizerTrn(
93
+ len(symbols),
94
+ hps.data.filter_length // 2 + 1,
95
+ hps.train.segment_size // hps.data.hop_length,
96
+ **hps.model,
97
+ ).cpu().eval()
98
+ logger = logging.getLogger()
99
+ previous_level = logger.level
100
+ try:
101
+ logger.setLevel(logging.WARNING)
102
+ utils.load_checkpoint(str(root / "model.pth"), model, None)
103
+ finally:
104
+ logger.setLevel(previous_level)
105
+ return model
106
+
107
+
108
+ def attach_metadata(
109
+ path: Path,
110
+ *,
111
+ model_name: str,
112
+ source_repository: str,
113
+ source_revision: str,
114
+ source_model_sha256: str,
115
+ ) -> None:
116
+ graph = onnx.load(path)
117
+ metadata = {
118
+ "model_name": model_name,
119
+ "source_repository": source_repository,
120
+ "source_revision": source_revision,
121
+ "source_model_sha256": source_model_sha256,
122
+ "license": "Apache-2.0",
123
+ "sample_rate_hz": "24000",
124
+ "precision": "FP32",
125
+ "onnx_opset": "17",
126
+ "conversion_author": "Robert Bak (github.com/robertbak)",
127
+ "official_packaging": "Owen Song (huggingface.co/owensong)",
128
+ }
129
+ del graph.metadata_props[:]
130
+ for key, value in metadata.items():
131
+ item = graph.metadata_props.add()
132
+ item.key = key
133
+ item.value = value
134
+ onnx.checker.check_model(graph)
135
+ onnx.save(graph, path)
136
+
137
+
138
+ def export(
139
+ root: Path,
140
+ output_dir: Path,
141
+ *,
142
+ model_name: str,
143
+ source_repository: str,
144
+ source_revision: str,
145
+ ) -> None:
146
+ torch.manual_seed(7)
147
+ model = load_model(root)
148
+ duration = DurationGraph(model).eval()
149
+ decode = DecodeGraph(model).eval()
150
+
151
+ output_dir.mkdir(parents=True, exist_ok=True)
152
+ duration_path = output_dir / "duration.onnx"
153
+ decode_path = output_dir / "decode.onnx"
154
+
155
+ tokens = torch.tensor(
156
+ [[0, 18, 0, 61, 0, 55, 0, 48, 0, 44, 0, 46, 0]],
157
+ dtype=torch.long,
158
+ )
159
+ lengths = torch.tensor([tokens.shape[1]], dtype=torch.long)
160
+ length_scale = torch.tensor(1.0, dtype=torch.float32)
161
+
162
+ torch.onnx.export(
163
+ duration,
164
+ (tokens, lengths, length_scale),
165
+ duration_path,
166
+ input_names=["tokens", "lengths", "length_scale"],
167
+ output_names=["m_p_exp", "logs_p_exp", "y_mask"],
168
+ dynamic_axes={
169
+ "tokens": {1: "text_len"},
170
+ "m_p_exp": {2: "mel_len"},
171
+ "logs_p_exp": {2: "mel_len"},
172
+ "y_mask": {2: "mel_len"},
173
+ },
174
+ opset_version=17,
175
+ do_constant_folding=True,
176
+ dynamo=False,
177
+ )
178
+
179
+ with torch.no_grad():
180
+ m_p_exp, logs_p_exp, y_mask = duration(tokens, lengths, length_scale)
181
+ zp_noise = torch.randn_like(m_p_exp)
182
+ noise_scale = torch.tensor(0.667, dtype=torch.float32)
183
+ torch.onnx.export(
184
+ decode,
185
+ (m_p_exp, logs_p_exp, y_mask, zp_noise, noise_scale),
186
+ decode_path,
187
+ input_names=[
188
+ "m_p_exp",
189
+ "logs_p_exp",
190
+ "y_mask",
191
+ "zp_noise",
192
+ "noise_scale",
193
+ ],
194
+ output_names=["waveform"],
195
+ dynamic_axes={
196
+ "m_p_exp": {2: "mel_len"},
197
+ "logs_p_exp": {2: "mel_len"},
198
+ "y_mask": {2: "mel_len"},
199
+ "zp_noise": {2: "mel_len"},
200
+ "waveform": {2: "wav_len"},
201
+ },
202
+ opset_version=17,
203
+ do_constant_folding=True,
204
+ dynamo=False,
205
+ )
206
+
207
+ source_hash = sha256(root / "model.pth")
208
+ for path in (duration_path, decode_path):
209
+ attach_metadata(
210
+ path,
211
+ model_name=model_name,
212
+ source_repository=source_repository,
213
+ source_revision=source_revision,
214
+ source_model_sha256=source_hash,
215
+ )
216
+
217
+ manifest = {
218
+ "format": "inflect_v2_onnx_export_v1",
219
+ "model_name": model_name,
220
+ "source_repository": source_repository,
221
+ "source_revision": source_revision,
222
+ "source_model_sha256": source_hash,
223
+ "torch_version": torch.__version__,
224
+ "onnx_version": onnx.__version__,
225
+ "opset": 17,
226
+ "files": {
227
+ duration_path.name: {
228
+ "bytes": duration_path.stat().st_size,
229
+ "sha256": sha256(duration_path),
230
+ },
231
+ decode_path.name: {
232
+ "bytes": decode_path.stat().st_size,
233
+ "sha256": sha256(decode_path),
234
+ },
235
+ },
236
+ }
237
+ (output_dir / "export_manifest.json").write_text(
238
+ json.dumps(manifest, indent=2) + "\n",
239
+ encoding="utf-8",
240
+ )
241
+
242
+
243
+ def main() -> None:
244
+ parser = argparse.ArgumentParser(description="Export Inflect v2 to ONNX.")
245
+ parser.add_argument("--model-dir", type=Path, default=PACKAGE_ROOT)
246
+ parser.add_argument("--output-dir", type=Path, required=True)
247
+ parser.add_argument("--model-name", required=True)
248
+ parser.add_argument("--source-repository", required=True)
249
+ parser.add_argument("--source-revision", required=True)
250
+ args = parser.parse_args()
251
+ export(
252
+ args.model_dir.resolve(),
253
+ args.output_dir.resolve(),
254
+ model_name=args.model_name,
255
+ source_repository=args.source_repository,
256
+ source_revision=args.source_revision,
257
+ )
258
+
259
+
260
+ if __name__ == "__main__":
261
+ main()
onnx/inference_onnx.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import re
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ import onnxruntime as ort
10
+ import soundfile as sf
11
+
12
+
13
+ PACKAGE_ROOT = Path(__file__).resolve().parents[1]
14
+ RUNTIME_ROOT = PACKAGE_ROOT / "runtime"
15
+ sys.path.insert(0, str(RUNTIME_ROOT))
16
+ sys.path.insert(0, str(PACKAGE_ROOT))
17
+
18
+ from inflect_vits_frontend import run_vits_frontend # noqa: E402
19
+ from text.symbols import symbols # noqa: E402
20
+
21
+
22
+ SAMPLE_RATE = 24_000
23
+ SYMBOL_TO_ID = {symbol: index for index, symbol in enumerate(symbols)}
24
+
25
+
26
+ def split_text(text: str, limit: int = 280) -> list[str]:
27
+ normalized = " ".join(text.split())
28
+ sentences = [
29
+ part.strip()
30
+ for part in re.split(r"(?<=[.!?;:])\s+", normalized)
31
+ if part.strip()
32
+ ]
33
+ chunks: list[str] = []
34
+ for sentence in sentences or [normalized]:
35
+ while len(sentence) > limit:
36
+ search = sentence[: limit + 1]
37
+ punctuation = max(search.rfind(mark) for mark in (",", ";", ":"))
38
+ split_at = (
39
+ punctuation + 1
40
+ if punctuation >= limit // 2
41
+ else sentence.rfind(" ", 0, limit + 1)
42
+ )
43
+ if split_at < limit // 2:
44
+ split_at = limit
45
+ chunks.append(sentence[:split_at].strip())
46
+ sentence = sentence[split_at:].strip()
47
+ if sentence:
48
+ chunks.append(sentence)
49
+ return chunks
50
+
51
+
52
+ def boundary_pause_seconds(chunk: str) -> float:
53
+ ending = chunk.rstrip()[-1:] if chunk.strip() else ""
54
+ return {
55
+ "?": 0.28,
56
+ "!": 0.24,
57
+ ".": 0.22,
58
+ ";": 0.16,
59
+ ":": 0.13,
60
+ ",": 0.09,
61
+ }.get(ending, 0.08)
62
+
63
+
64
+ def edge_fade(
65
+ waveform: np.ndarray,
66
+ sample_rate: int = SAMPLE_RATE,
67
+ milliseconds: float = 5.0,
68
+ ) -> np.ndarray:
69
+ frames = min(round(sample_rate * milliseconds / 1000.0), waveform.size // 2)
70
+ if frames <= 0:
71
+ return waveform
72
+ output = waveform.copy()
73
+ ramp = np.linspace(0.0, 1.0, frames, endpoint=True, dtype=np.float32)
74
+ output[:frames] *= ramp
75
+ output[-frames:] *= ramp[::-1]
76
+ return output
77
+
78
+
79
+ def phonemes_to_tokens(phoneme_text: str) -> np.ndarray:
80
+ sequence = [SYMBOL_TO_ID[symbol] for symbol in phoneme_text]
81
+ if not sequence:
82
+ raise ValueError("The text frontend produced no speakable tokens.")
83
+ with_blanks = np.zeros(len(sequence) * 2 + 1, dtype=np.int64)
84
+ with_blanks[1::2] = sequence
85
+ return with_blanks[None, :]
86
+
87
+
88
+ def available_provider(name: str) -> str:
89
+ aliases = {
90
+ "cpu": "CPUExecutionProvider",
91
+ "cuda": "CUDAExecutionProvider",
92
+ "directml": "DmlExecutionProvider",
93
+ }
94
+ provider = aliases.get(name.lower(), name)
95
+ available = ort.get_available_providers()
96
+ if provider not in available:
97
+ raise ValueError(
98
+ f"Provider {provider!r} is unavailable. Installed providers: {available}"
99
+ )
100
+ return provider
101
+
102
+
103
+ class InflectONNX:
104
+ """Torch-free Inflect v2 inference through ONNX Runtime."""
105
+
106
+ def __init__(
107
+ self,
108
+ model_dir: str | Path = PACKAGE_ROOT,
109
+ provider: str = "cpu",
110
+ ) -> None:
111
+ root = Path(model_dir).resolve()
112
+ onnx_dir = root / "onnx"
113
+ selected_provider = available_provider(provider)
114
+ providers = [selected_provider]
115
+ if selected_provider != "CPUExecutionProvider":
116
+ providers.append("CPUExecutionProvider")
117
+ self.duration = ort.InferenceSession(
118
+ str(onnx_dir / "duration.onnx"),
119
+ providers=providers,
120
+ )
121
+ self.decode = ort.InferenceSession(
122
+ str(onnx_dir / "decode.onnx"),
123
+ providers=providers,
124
+ )
125
+
126
+ def _synthesize_chunk(
127
+ self,
128
+ text: str,
129
+ *,
130
+ speed: float,
131
+ variation: float,
132
+ seed: int,
133
+ ) -> np.ndarray:
134
+ phoneme_text = run_vits_frontend(text).phoneme_text
135
+ tokens = phonemes_to_tokens(phoneme_text)
136
+ duration_outputs = self.duration.run(
137
+ ["m_p_exp", "logs_p_exp", "y_mask"],
138
+ {
139
+ "tokens": tokens,
140
+ "lengths": np.asarray([tokens.shape[1]], dtype=np.int64),
141
+ "length_scale": np.asarray(1.0 / speed, dtype=np.float32),
142
+ },
143
+ )
144
+ m_p_exp, logs_p_exp, y_mask = duration_outputs
145
+ rng = np.random.default_rng(seed)
146
+ latent_noise = rng.standard_normal(m_p_exp.shape, dtype=np.float32)
147
+ waveform = self.decode.run(
148
+ ["waveform"],
149
+ {
150
+ "m_p_exp": m_p_exp,
151
+ "logs_p_exp": logs_p_exp,
152
+ "y_mask": y_mask,
153
+ "zp_noise": latent_noise,
154
+ "noise_scale": np.asarray(variation, dtype=np.float32),
155
+ },
156
+ )[0]
157
+ return edge_fade(np.asarray(waveform, dtype=np.float32).reshape(-1))
158
+
159
+ def synthesize(
160
+ self,
161
+ text: str,
162
+ *,
163
+ speed: float = 1.0,
164
+ variation: float = 0.667,
165
+ seed: int = 0,
166
+ ) -> tuple[int, np.ndarray]:
167
+ normalized = " ".join(text.split())
168
+ if not normalized:
169
+ raise ValueError("Text must not be empty.")
170
+ if not 0.5 <= speed <= 2.0:
171
+ raise ValueError("speed must be between 0.5 and 2.0")
172
+ if not 0.0 <= variation <= 1.0:
173
+ raise ValueError("variation must be between 0.0 and 1.0")
174
+
175
+ chunks = split_text(normalized)
176
+ pieces: list[np.ndarray] = []
177
+ for index, chunk in enumerate(chunks):
178
+ if index:
179
+ pause = boundary_pause_seconds(chunks[index - 1])
180
+ pieces.append(
181
+ np.zeros(round(SAMPLE_RATE * pause), dtype=np.float32)
182
+ )
183
+ pieces.append(
184
+ self._synthesize_chunk(
185
+ chunk,
186
+ speed=speed,
187
+ variation=variation,
188
+ seed=seed + index,
189
+ )
190
+ )
191
+ waveform = np.clip(np.concatenate(pieces), -1.0, 1.0)
192
+ return SAMPLE_RATE, waveform
193
+
194
+ def save(self, text: str, output: str | Path, **kwargs: object) -> Path:
195
+ destination = Path(output)
196
+ destination.parent.mkdir(parents=True, exist_ok=True)
197
+ sample_rate, waveform = self.synthesize(text, **kwargs)
198
+ sf.write(destination, waveform, sample_rate)
199
+ return destination
200
+
201
+
202
+ def main() -> None:
203
+ parser = argparse.ArgumentParser(description="Run Inflect v2 through ONNX Runtime.")
204
+ parser.add_argument("--model-dir", type=Path, default=PACKAGE_ROOT)
205
+ parser.add_argument("--text", required=True)
206
+ parser.add_argument("--output", type=Path, required=True)
207
+ parser.add_argument(
208
+ "--provider",
209
+ default="cpu",
210
+ help="cpu, cuda, directml, or an ONNX Runtime provider name",
211
+ )
212
+ parser.add_argument("--speed", type=float, default=1.0)
213
+ parser.add_argument("--variation", type=float, default=0.667)
214
+ parser.add_argument("--seed", type=int, default=0)
215
+ args = parser.parse_args()
216
+
217
+ engine = InflectONNX(args.model_dir, provider=args.provider)
218
+ engine.save(
219
+ args.text,
220
+ args.output,
221
+ speed=args.speed,
222
+ variation=args.variation,
223
+ seed=args.seed,
224
+ )
225
+ print(f"wrote {args.output} at {SAMPLE_RATE} Hz")
226
+
227
+
228
+ if __name__ == "__main__":
229
+ main()
onnx/parity_report.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format": "inflect_v2_onnx_parity_v1",
3
+ "model": "Inflect-Micro-v2",
4
+ "onnx_checker": "pass",
5
+ "dynamic_lengths": "pass",
6
+ "end_to_end_short_text": "pass",
7
+ "end_to_end_long_text": "pass",
8
+ "same_seed_determinism": "pass",
9
+ "different_seed_variation": "pass",
10
+ "pytorch_parity": {
11
+ "duration_m_p_exp_max_abs_error": 9.5367431640625e-07,
12
+ "duration_logs_p_exp_max_abs_error": 3.5762786865234375e-07,
13
+ "duration_mask_exact": true,
14
+ "waveform_max_abs_error": 1.75834e-05,
15
+ "waveform_mean_abs_error": 2.2595e-07,
16
+ "waveform_correlation": 0.9999999999394602
17
+ },
18
+ "independent_reexport_parity": {
19
+ "duration_outputs_max_abs_error": [0.0, 0.0, 0.0],
20
+ "waveform_max_abs_error": 1.0617077350616455e-07,
21
+ "waveform_mean_abs_error": 4.823611288173879e-09,
22
+ "waveform_correlation": 0.9999999999995599
23
+ },
24
+ "validation_environment": {
25
+ "python": "3.12",
26
+ "onnx": "1.19.1",
27
+ "onnxruntime": "1.22.1",
28
+ "torch_for_reference": "2.4.1+cpu",
29
+ "provider": "CPUExecutionProvider"
30
+ }
31
+ }
onnx/requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ numpy>=1.26,<3
2
+ onnxruntime>=1.18,<2
3
+ soundfile>=0.13
4
+ phonemizer>=3.3
5
+ espeakng-loader>=0.2.4
6
+ num2words>=0.5.14
7
+ Unidecode>=1.3.8
release_manifest.json CHANGED
@@ -7,6 +7,36 @@
7
  "smoke_audio_seconds": 3.968,
8
  "standalone_cpu_smoke_audio_seconds": 3.616,
9
  "files": [
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  {
11
  "path": "assets/evidence/asr-consensus.svg",
12
  "bytes": 3225,
@@ -22,6 +52,11 @@
22
  "bytes": 6123,
23
  "sha256": "1a2218e0bbe726167a5b0201736a82947edbd7c6803df3f5689e7a711026758e"
24
  },
 
 
 
 
 
25
  {
26
  "path": "assets/evidence/evidence-summary.json",
27
  "bytes": 52605,
@@ -42,6 +77,11 @@
42
  "bytes": 7934,
43
  "sha256": "9086575f7d11395255f4cf4ae67e30bf92d60042c6b4bdc58ee6045595cb5420"
44
  },
 
 
 
 
 
45
  {
46
  "path": "assets/evidence/quality-vs-footprint.svg",
47
  "bytes": 5852,
@@ -49,18 +89,33 @@
49
  },
50
  {
51
  "path": "assets/inflect-v2-repository-hero.png",
52
- "bytes": 1654856,
53
- "sha256": "5de5efe7857fd7d17b2669e014846545014aed47bb4f19def7b06406532ffd2d"
54
  },
55
  {
56
- "path": "CHANGELOG.md",
57
- "bytes": 437,
58
- "sha256": "ebc480bdab578e912a3ece670eb20e005ced74739ab5134f12ec0c05eada3b2f"
59
  },
60
  {
61
- "path": "CITATION.cff",
62
- "bytes": 289,
63
- "sha256": "8150876b279aec0438cb25851c12375522c07521261645368565febeaff22408"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  },
65
  {
66
  "path": "config.json",
@@ -74,8 +129,8 @@
74
  },
75
  {
76
  "path": "docs/DATA_AND_VOICE.md",
77
- "bytes": 1671,
78
- "sha256": "894e9fa4e5f6fceb76e7b2da2fca4de924230c6043ef5de167ef8c15298c6b6e"
79
  },
80
  {
81
  "path": "docs/DEPLOYMENT.md",
@@ -84,13 +139,13 @@
84
  },
85
  {
86
  "path": "docs/EVALUATION.md",
87
- "bytes": 12488,
88
- "sha256": "9e0d4c1811ca9959529db4e7b6907ca931f094dfd910dbe7f61b550fbc732f17"
89
  },
90
  {
91
  "path": "docs/EXPORTS.md",
92
- "bytes": 1826,
93
- "sha256": "ca4eb3949c46ac5fb1145de7270ff94689d18787232ea017b541372f5a2e6ca3"
94
  },
95
  {
96
  "path": "docs/FINETUNING.md",
@@ -259,8 +314,8 @@
259
  },
260
  {
261
  "path": "inference.py",
262
- "bytes": 7311,
263
- "sha256": "9bccc990aafa8e363a34269367a034fc69d8216f7317650be0e389bdd545eae3"
264
  },
265
  {
266
  "path": "inflect_nano_v2_frontend.py",
@@ -272,11 +327,6 @@
272
  "bytes": 2031,
273
  "sha256": "70eb4cdb96b2752015f72be984f5e62e0f26d7dc80e708875601702057b95cc2"
274
  },
275
- {
276
- "path": "LICENSE",
277
- "bytes": 10455,
278
- "sha256": "12c48c640d1d0baf158d45175a4238b9227c71c77f50b0d15791b8efc08cd793"
279
- },
280
  {
281
  "path": "model.pth",
282
  "bytes": 37529995,
@@ -288,9 +338,49 @@
288
  "sha256": "f796d72aabd080d1232032c219dc39bbb2f5b517fe8a33a76df2a53c720bdb23"
289
  },
290
  {
291
- "path": "README.md",
292
- "bytes": 18712,
293
- "sha256": "0ffb5c8d398ba6851215e6d5395911023bc27f349aa7ff7c75eba19ab3e91daf"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  },
295
  {
296
  "path": "requirements-tested.txt",
@@ -332,6 +422,11 @@
332
  "bytes": 206,
333
  "sha256": "16ca4890f3e0107250b4a522c0127779994a5a2b99fc35d6a27f93496a72153a"
334
  },
 
 
 
 
 
335
  {
336
  "path": "runtime/text/__init__.py",
337
  "bytes": 1572,
@@ -342,11 +437,6 @@
342
  "bytes": 2880,
343
  "sha256": "11dc7325547290e529c597e620f3aaf8d0bd8a2a2987d48eb42d2289534e42d1"
344
  },
345
- {
346
- "path": "runtime/text/LICENSE",
347
- "bytes": 1053,
348
- "sha256": "f89accd9d66f6a011f19997d7a21cf71a35ffe2e2d0911b5460cbd0cab52a581"
349
- },
350
  {
351
  "path": "runtime/text/symbols.py",
352
  "bytes": 642,
@@ -401,12 +491,13 @@
401
  "path": "third_party/VITS_LICENSE.txt",
402
  "bytes": 1090,
403
  "sha256": "1cdf858b4d555567398b378b4a0166e97f7908c56607101b8bf9bb6754cdff3f"
404
- },
405
- {
406
- "path": "THIRD_PARTY_NOTICES.md",
407
- "bytes": 1233,
408
- "sha256": "2ead63b7d9cddd6c1d12a96d88de0ade6fd3eabd1278825c0a3a2c1d3647ab4b"
409
  }
410
  ],
411
- "format": "inflect_v2_release_manifest_v1"
 
 
 
 
 
 
412
  }
 
7
  "smoke_audio_seconds": 3.968,
8
  "standalone_cpu_smoke_audio_seconds": 3.616,
9
  "files": [
10
+ {
11
+ "path": ".gitattributes",
12
+ "bytes": 1519,
13
+ "sha256": "11ad7efa24975ee4b0c3c3a38ed18737f0658a5f75a0a96787b576a78a023361"
14
+ },
15
+ {
16
+ "path": "CHANGELOG.md",
17
+ "bytes": 792,
18
+ "sha256": "742ddc2b7990ffa3e205f686e9c8dcb8e6e98eb3b910b411e72cfb2a3120b104"
19
+ },
20
+ {
21
+ "path": "CITATION.cff",
22
+ "bytes": 289,
23
+ "sha256": "8150876b279aec0438cb25851c12375522c07521261645368565febeaff22408"
24
+ },
25
+ {
26
+ "path": "LICENSE",
27
+ "bytes": 10455,
28
+ "sha256": "12c48c640d1d0baf158d45175a4238b9227c71c77f50b0d15791b8efc08cd793"
29
+ },
30
+ {
31
+ "path": "README.md",
32
+ "bytes": 20101,
33
+ "sha256": "3250258e2ec7b8e7e312da1579dda93ff68334ed803561f4ac3dcc26a5f2d644"
34
+ },
35
+ {
36
+ "path": "THIRD_PARTY_NOTICES.md",
37
+ "bytes": 1233,
38
+ "sha256": "2ead63b7d9cddd6c1d12a96d88de0ade6fd3eabd1278825c0a3a2c1d3647ab4b"
39
+ },
40
  {
41
  "path": "assets/evidence/asr-consensus.svg",
42
  "bytes": 3225,
 
52
  "bytes": 6123,
53
  "sha256": "1a2218e0bbe726167a5b0201736a82947edbd7c6803df3f5689e7a711026758e"
54
  },
55
+ {
56
+ "path": "assets/evidence/cpu-throughput.svg",
57
+ "bytes": 3198,
58
+ "sha256": "4a74b4f19c87c5da9e8f368dd0117ac2840c437976c677572e3f65d1be785b8b"
59
+ },
60
  {
61
  "path": "assets/evidence/evidence-summary.json",
62
  "bytes": 52605,
 
77
  "bytes": 7934,
78
  "sha256": "9086575f7d11395255f4cf4ae67e30bf92d60042c6b4bdc58ee6045595cb5420"
79
  },
80
+ {
81
+ "path": "assets/evidence/quality-vs-cpu-speed.svg",
82
+ "bytes": 3887,
83
+ "sha256": "023e0a4a86c71eefea19a4fdab7404b8710ead4045ae3964928f149af1cc04b4"
84
+ },
85
  {
86
  "path": "assets/evidence/quality-vs-footprint.svg",
87
  "bytes": 5852,
 
89
  },
90
  {
91
  "path": "assets/inflect-v2-repository-hero.png",
92
+ "bytes": 1911877,
93
+ "sha256": "ca59256690b4ce1b11f27e51ee4aa21c30883d58c1e5c87cccae553898afe884"
94
  },
95
  {
96
+ "path": "assets/nav/collection.svg",
97
+ "bytes": 652,
98
+ "sha256": "85387a74b68b0b7b735f4ce3527f32ddd5ca084dfd49dc81fa96ebf816b153e6"
99
  },
100
  {
101
+ "path": "assets/nav/github.svg",
102
+ "bytes": 517,
103
+ "sha256": "d55949cc700715bb152b0380799b314631f442414e81b83a6f3553df18dbef6f"
104
+ },
105
+ {
106
+ "path": "assets/nav/micro.svg",
107
+ "bytes": 529,
108
+ "sha256": "f56b72a293acdf8e9fed93893f75acf760a20a2fe09b316646a627db2ada741a"
109
+ },
110
+ {
111
+ "path": "assets/nav/nano.svg",
112
+ "bytes": 726,
113
+ "sha256": "69275d0d9277144c098b3dce66db5e870ebd113d97ca55cc6912242bda520643"
114
+ },
115
+ {
116
+ "path": "assets/nav/playground.svg",
117
+ "bytes": 624,
118
+ "sha256": "fa05ff68190baf290f3d1e9c9b8a7bc89af033fd03066cf7d3e828089b194953"
119
  },
120
  {
121
  "path": "config.json",
 
129
  },
130
  {
131
  "path": "docs/DATA_AND_VOICE.md",
132
+ "bytes": 1739,
133
+ "sha256": "8a504473b8377493256d435124851479ebee7f74432671ed0e0dff4e4509d8ba"
134
  },
135
  {
136
  "path": "docs/DEPLOYMENT.md",
 
139
  },
140
  {
141
  "path": "docs/EVALUATION.md",
142
+ "bytes": 3246,
143
+ "sha256": "4041e56250564366ff80ef9278d66bac5139d8833d368264931ba28ffbad7991"
144
  },
145
  {
146
  "path": "docs/EXPORTS.md",
147
+ "bytes": 2808,
148
+ "sha256": "bcb70436c88e92d2926941e76f141498d63e2432e3584e921a9682785c474392"
149
  },
150
  {
151
  "path": "docs/FINETUNING.md",
 
314
  },
315
  {
316
  "path": "inference.py",
317
+ "bytes": 6346,
318
+ "sha256": "bb8b5302b16c35ee04d6729fd73caa95d1e72b2fc50420b1cdec120327f8b672"
319
  },
320
  {
321
  "path": "inflect_nano_v2_frontend.py",
 
327
  "bytes": 2031,
328
  "sha256": "70eb4cdb96b2752015f72be984f5e62e0f26d7dc80e708875601702057b95cc2"
329
  },
 
 
 
 
 
330
  {
331
  "path": "model.pth",
332
  "bytes": 37529995,
 
338
  "sha256": "f796d72aabd080d1232032c219dc39bbb2f5b517fe8a33a76df2a53c720bdb23"
339
  },
340
  {
341
+ "path": "onnx/README.md",
342
+ "bytes": 3907,
343
+ "sha256": "ecfaf6a3edca6ffc3152a61ccc6c27240f42daaa0ddf6efc08e1f9b277295734"
344
+ },
345
+ {
346
+ "path": "onnx/SOURCE.json",
347
+ "bytes": 633,
348
+ "sha256": "e950bf44034b011a64bba3773b388e386418ef952c3cd17ceefda5bfa94a3aaa"
349
+ },
350
+ {
351
+ "path": "onnx/checksums.sha256",
352
+ "bytes": 653,
353
+ "sha256": "d74c3c8d3a148f240d795dc603e18ac2c152a3f7ad3e45dcd66d3e14ee335b6b"
354
+ },
355
+ {
356
+ "path": "onnx/decode.onnx",
357
+ "bytes": 30427790,
358
+ "sha256": "7940923add86f76e7fa78d910b0632ca1779f8cc9a2ca2b49236381a9ca77183"
359
+ },
360
+ {
361
+ "path": "onnx/duration.onnx",
362
+ "bytes": 7322687,
363
+ "sha256": "b728ca2564b9e5b7d6cf5e446f65e02a6fe2f1880ba281466fec93a667dd2388"
364
+ },
365
+ {
366
+ "path": "onnx/export_onnx.py",
367
+ "bytes": 7884,
368
+ "sha256": "77bd27277861c8cae670247e3c9e3b976134a4886506fb069331aa6828752be6"
369
+ },
370
+ {
371
+ "path": "onnx/inference_onnx.py",
372
+ "bytes": 7333,
373
+ "sha256": "cf48c4812f68e8315d80568e93650c64a7ad3fdfb9a0051c61a0d1d0746df274"
374
+ },
375
+ {
376
+ "path": "onnx/parity_report.json",
377
+ "bytes": 1026,
378
+ "sha256": "10c88564d24fe65ea730dc6ec98aa19ec74d3590efa427595a3c122c4c0f7039"
379
+ },
380
+ {
381
+ "path": "onnx/requirements.txt",
382
+ "bytes": 126,
383
+ "sha256": "43abd2c128e4296f1f0171f12946f3fc7dee3f8c8e2bb1858bcae6b43b62b3c9"
384
  },
385
  {
386
  "path": "requirements-tested.txt",
 
422
  "bytes": 206,
423
  "sha256": "16ca4890f3e0107250b4a522c0127779994a5a2b99fc35d6a27f93496a72153a"
424
  },
425
+ {
426
+ "path": "runtime/text/LICENSE",
427
+ "bytes": 1053,
428
+ "sha256": "f89accd9d66f6a011f19997d7a21cf71a35ffe2e2d0911b5460cbd0cab52a581"
429
+ },
430
  {
431
  "path": "runtime/text/__init__.py",
432
  "bytes": 1572,
 
437
  "bytes": 2880,
438
  "sha256": "11dc7325547290e529c597e620f3aaf8d0bd8a2a2987d48eb42d2289534e42d1"
439
  },
 
 
 
 
 
440
  {
441
  "path": "runtime/text/symbols.py",
442
  "bytes": 642,
 
491
  "path": "third_party/VITS_LICENSE.txt",
492
  "bytes": 1090,
493
  "sha256": "1cdf858b4d555567398b378b4a0166e97f7908c56607101b8bf9bb6754cdff3f"
 
 
 
 
 
494
  }
495
  ],
496
+ "format": "inflect_v2_release_manifest_v1",
497
+ "onnx_release": {
498
+ "format": "FP32 ONNX opset 17",
499
+ "path": "onnx/",
500
+ "verified": true,
501
+ "released": "2026-07-25"
502
+ }
503
  }