Skip to content

Benchmarks

Use this page to answer two questions:

  1. How quickly does one synthesis finish?
  2. How much speech can a server produce when work is batched?

Those are different measurements. Single-request results are end to end. Batch results measure aggregate token-generator throughput and exclude the renderer.

  • RTF is seconds of audio produced per second of wall time. 1.0x is real time. Higher is faster.
  • TTFA is time to first audio for the measured streaming request.
  • Aggregate throughput adds the audio-equivalent output of every item in a batch. It is a capacity number, not the latency of one request.
  • Cold includes the first run after loading. Warm is the median of three later runs.

Unless a row says otherwise, measurements use voice joe, seed 1234, and the third passage in the shipped benchmark set.

deploymentmeasured pathresult
NVIDIA desktop GPUPyTorch, RTX 3090, CUDA graphs7.47x end to end
Apple Siliconsplit PyTorch engine, M3 Pro3.43x end to end
Embedded NVIDIAPyTorch, Jetson Orin Nano, CUDA graphs1.83x end to end
CPU without PyTorchONNX Runtime CPU provider, M3 Pro1.21x end to end
Swift on Apple Siliconnative generator plus CoreML renderer, M3 Pro2.28x warm
Batched NVIDIA workloadtoken generator, A100, batch 64170.8x aggregate

For one request on an Ampere-or-newer NVIDIA GPU, use CUDA graphs. For portable CPU deployment without PyTorch, use ONNX Runtime. On Apple Silicon, the split PyTorch path is the fastest measured Python path; Swift uses its native generator with the CoreML renderer.

These rows include token generation and rendering.

Current workstation and laptop measurements

Section titled “Current workstation and laptop measurements”
runtimehardwareconfigurationRTFTTFA warm
PyTorchRTX 3090CUDA graphs7.47x0.86s
PyTorchRTX 3090eager CUDA3.39x1.84s
PyTorchApple M3 Prosplit CPU/MPS3.43x2.02s
ONNX RuntimeApple M3 ProCPU provider1.21x4.58s
PyTorchApple M3 ProCPU reference0.33x17.2s

The RTX 3090 figures use PyTorch 2.13 with CUDA 12.6. CUDA graphs capture the decode step over a static KV cache. The flag is opt in because padded attention can change sampled tokens on long windows. Use it when speed matters more than byte identity with eager execution.

The Apple M3 Pro machine has a 10-core CPU, 18-core GPU and 36 GB of unified memory. The split path generates on CPU while the previous window renders on the GPU.

hardwareeagerCUDA graphsnote
A1002.16x8.12xolder, shorter benchmark passage
L42.20x7.56xolder, shorter benchmark passage
T42.10x5.46xolder, shorter benchmark passage
GTX 1080 Ti1.95x1.89xPascal cannot capture the graph; the flag falls back
Jetson Orin Nano Super0.73x1.83xJetPack 6, CUDA 12.6, 25 W mode

The older A100, L4 and T4 runs used the previous 255-token version of the third benchmark passage. They are useful device measurements but should not be compared directly with the current RTX 3090 row.

On an M3 Pro with an 11-core CPU, 14-core GPU and 36 GB of memory, Engine.synthesizeLong measured 2.11x cold and 2.28x warm. The native generator runs on CPU and the renderer runs through CoreML. This uses a Swift harness rather than loudkit bench, so read it as a deployment result, not a strict cross-runtime comparison.

These figures measure a fixed 255-token generator window decoded in lockstep. Mel generation and the vocoder are excluded. They show server capacity, not single-request latency.

hardwarebatch 1batch 8batch 16batch 32batch 64
RTX 3090, CUDA graphs20.1x80.8x110.8x137.3x153.1x
GTX 1080 Ti, eager2.16x16.7x33.5x43.5x47.3x
Jetson Orin Nano, CUDA3.82x9.82x11.08x11.77xnot measured
T48.43x25.21x29.74x31.86x31.39x
A10015.68x69.41x103.50x140.90x170.80x
L413.99x49.03x50.67x53.89x57.77x
Apple Silicon CPU2.71x4.43x4.81x4.97x4.97x
i7-6850K CPU0.87x0.98x1.01x1.02x1.17x

The RTX 3090 rises from 20.1x at batch 1 to 153.1x at batch 64. The largest measured aggregate result is 170.8x on the A100 at batch 64. CPU throughput barely changes because CPU decode is compute bound rather than launch bound.

Reproduce the table with:

Terminal window
python tools/bench_batch.py <checkpoint> <voice> <device> <outdir> 1,2,4,8,16,32,64

ONNX Runtime runs without PyTorch at inference. The release ships fp32 graphs; fp16 did not justify a second artefact and int8 did not pass the quality gate.

onnx_provider="auto" selects CUDA when the installed runtime offers it and CPU otherwise. CoreML and DirectML must be requested explicitly.

This table times synthesize_long with load excluded. It is a separate harness from the end-to-end table above.

portproviderRTFloadresult
PythonCPU1.22x to 1.31x2.9sreference
PythonCoreML2.06x to 2.17x113s cold, 25s warmsame tokens as CPU
RustCoreMLnot timedsupported, same token stream
GoCoreMLnot timedsupported, same token stream
JavaScriptCoreMLunavailableruntime cannot persist the compile cache

CoreML places the three renderer graphs on CoreML and keeps the token generator on CPU. The first compile took 113 seconds and created about 1.6 GB in ~/Library/Caches/loudkit/coreml. Later loads took about 25 seconds. This cost is why auto does not select CoreML.

These rows compare ONNX providers on the same Linux machine. They are not the PyTorch CUDA measurements above.

portCUDA providerCPU providerCUDA speedupsame tokens as CPU
Python4.21x0.77x5.5xyes
Rust3.60x0.70x5.1xyes
Go2.68x0.67x4.0xyes
JavaScript2.54x0.65x3.9xyes

The JavaScript row was measured with onnxruntime-node 1.26.0. The package declares 1.27 or newer, whose CUDA build needs a newer NVIDIA driver than the measurement machine had. Treat that row as evidence for the port, not a result from the default npm installation.

DirectML has not been measured. Swift uses CoreML directly and does not expose ONNX providers.

Download only the runtime you want:

Terminal window
loudkit download loudreader/loudr-1 --for torch --local-dir loudr-1
loudkit download loudreader/loudr-1 --for onnx --local-dir loudr-1
loudkit download loudreader/loudr-1 --for coreml --local-dir loudr-1

Run the end-to-end benchmark:

Terminal window
loudkit bench \
--checkpoint loudr-1/loudr-1.safetensors \
--voice loudr-1/voices/joe.safetensors \
--device cuda \
--cuda-graphs \
--json row.json

Use --device mps, --device cpu or --device onnx for the other Python paths. The JSON contains RTF, TTFA, stage timings, peak memory, the exact command and a determinism check.

For a stage-by-stage profile:

Terminal window
loudkit profile \
--checkpoint loudr-1/loudr-1.safetensors \
--voice loudr-1/voices/joe.safetensors \
-- "A passage to profile."

See Benchmarking for the command reference and Identity contract for which execution changes may alter tokens or waveforms.