How we measure
What the speed numbers mean and how we measure them, with the raw data.
What the numbers mean
- fps (unpaced): frames rendered per second when all the speech audio is pushed at once, so the avatar renders as fast as the hardware allows.
- × real time: fps divided by the model’s playback rate (Essence 2 plays at 25 fps, Expression 2 at 20 fps). At 1.0× or more the avatar keeps up with a live conversation; higher means more headroom.
- Live fps: frames shown per second when the audio is pushed in real time, as in a conversation. It should equal the playback rate.
- First speech: seconds from the first audio pushed to the first frame shown with its audio.
Method
- One avatar session on one device, one model and one SDK build, from a cool start.
- Published builds only: the release a developer installs, with its exact version in the SDK column.
- fps is timed from the first audio push to the last frame, never through an idle wait after it. First-run warm-up is left out.
- Each cell names its own method: the median of three runs back to back (the minimum and maximum are in the raw data), or the slowest of the runs shown.
- Phones start cool: screen on, charger off, left idle first.
- Measured cells follow this method. A cell marked ≈ estimated was taken another way, for example on a development build; its raw record says how.
- A measurement older than 60 days is dropped from the pages until it is measured again.
The numbers measured before this method came from the bitHuman model performance record: the slowest of three runs. Each cell’s method in the raw data says which applies, and All measurements shows it.
Measured in September, October and November 2026 on CLI 2.8.1, bithuman 2.11.12, bithuman 2.11.18, bithuman 2.12.1, bithuman 2.12.9, Swift package 2.17.3, Swift package 2.18.0, Swift package 2.21.4, essence2-android 0.7.0, expression2-android 0.4.10, the cloud API and web viewer dcba335.
The macOS CLI Essence 2 figure was measured with BITHUMAN_THREADS=8; by default the CLI uses one thread per CPU it may use, up to 16. The Linux CLI uses default settings.
Memory
| Runs on | Hardware | Essence 2 | Expression 2 | Measured on |
|---|---|---|---|---|
| macOS · CLI | Apple M4 | 1.0 GB | 0.9 GB | CLI 2.7.8 |
| macOS · Python | Apple M4 | 1.2 GB | 0.8 GB | bithuman 2.11.12 |
| Linux · CLI | Intel Core i7-13700F (x86_64) | 2.2 GB | 1.6 GB | CLI 2.7.8 |
| Linux · Python | Intel Core i7-13700F (x86_64) | 2.8 GB (peak memory, whole render) | 1.5 GB | bithuman 2.12.6, bithuman 2.11.12 |
| iPhone · Swift package | iPhone 15 | 0.9 GB | 0.5 GB | Swift engine 1.10.0, Swift package 2.14.1 |
| Android | Samsung Galaxy S25+ | 1.0 GB | 2.3 GB | essence2-android 0.7.0, expression2-android 0.5.0 |
| Web browser (WebGPU) | Chrome on Apple M5 | 3.0 GB (Chrome tab memory footprint) | 1.8 GB (Chrome tab memory footprint) | — |
Peak memory (RAM) of one render, or of one session on a phone, on the release in the last column. Cloud API: nothing to provision.
Linux · Python, Essence 2 full-body avatars that play their own idle clip: 4.3 GB (peak memory, whole render, bithuman 2.12.6). Both Linux · Python Essence 2 numbers are one whole render (loading + 15 s of speech) with the engine’s default worker count (8 render workers on this CPU), the highest of 3 runs. The bithuman 2.12.6 changelog’s about 3.9 GB and 2.3 GB are 4 render workers on 8 s of speech.
Time to a finished video
| Runs on | Hardware | Essence 2, first run | Essence 2, after that |
|---|---|---|---|
| macOS · CLI | Apple M4 | 4.0 s | 4.8 s |
Seconds from starting the command to a finished MP4 of a 10-second clip, for the whole process: loading, rendering, encoding and exit, timed on CLI 2.7.8. Each number is the slowest of three runs. “First run” is a fresh install with the model files already downloaded. “After that” is every later render on the same machine. Lip sync is checked on every run: no run published here plays more than 40 ms off the audio.
Later renders are not faster: the model files are already downloaded on the first run, so both columns measure the same work, within run-to-run variation.
Pin a tier for a benchmark
To measure one tier, append ?model= with a tier slug to the viewer or embed URL:
https://www.bithuman.ai/embed/A23WJF0199?model=expression-2-apple
| Model | Tier slugs |
|---|---|
essence-2 | essence-2-gpu · essence-2-apple · essence-2-cpu |
expression-2 | expression-2-gpu · expression-2-apple · expression-2-cpu |
- A recognized slug pins the session to that tier: if the tier is unavailable, the session fails rather than playing elsewhere.
- An unrecognized slug is ignored and the session plays as usual. If a pin seems to have no effect, check the spelling.
- To be told about a typo, set the embed token’s
modelfield instead: an unknown value is refused with a400listing the accepted names when you mint the token.
In production, omit ?model= and let the service choose.
Raw data
performance.json holds every published cell: fps, × real time, live fps, first speech, the SDK version, date, status, method, the speech clip it was measured on, and memory. The pages under Performance are generated from it.