ZONOS3-Mini / README.md
fffiloni's picture
v0.3.5: align Speaking rate with Sampling controls
9f6ac5a verified
|
Raw History Blame Contribute Delete
16.4 kB

A newer version of the Gradio SDK is available: 6.31.0

Upgrade
metadata
title: ZONOS3 Mini
emoji: 〰️
colorFrom: gray
colorTo: gray
sdk: gradio
sdk_version: 6.30.0
python_version: '3.12'
app_file: app.py
pinned: false
license: mit
models:
  - Zyphra/ZONOS3-Mini
  - Qwen/Qwen3-ForcedAligner-0.6B-hf
short_description: A voice, a line, and your words.

ZONOS3 Mini · 0.3.5

A minimal Hugging Face Space for Zyphra's ZONOS3-Mini. Centered editable text, a fine audio-reactive line, and one Generate button. The interface follows the supplied Zyphra presentation references. Model: Zyphra. UI/UX: Sylvain Filoni / fffiloni.

Deployed on Hugging Face ZeroGPU. Short French/English generations, a synthetic reference clip, cancellation/retry and browser playback have passed live checks. See VALIDATION.md for the evidence and remaining limits; this is not a comprehensive audio-quality benchmark.

Use

  1. Write up to 600 characters. Choose a language and a preset voice.
  2. Select Generate speech. The native Gradio/ZeroGPU progress and quota notices remain available.
  3. The completed WAV plays automatically when Auto-play result is enabled (the default). Disable it in Voice settings if preferred. Play, seek and download remain available. A browser that blocks playback shows a prompt to tap Play.

The waveform starts flat. Voice settings use documented Gradio HTML props, watch callbacks, custom example events and a custom audio upload/record/trim child. The generation event snapshots the custom controls into the unchanged public API inputs. During playback the line follows a short window of actual PCM captured by Web Audio, retaining small vibrations and irregular crests. Adjacent-sample averaging, a rising zero-crossing trigger, a short temporal blend and monotone cubic interpolation soften jitter and sharp corners. This is a stylized signal display, not a calibrated oscilloscope. Its amplitude still follows actual audio energy and decays to flat on pause, end or silence. Reduced-motion mode keeps the playback line flat without disabling the audio clock or word synchronization. The separate animated request indicator is preserved from v0.3.3; it is not an audio preview or a completion estimate. Native Gradio progress retains the real preparation/allocation/inference stages. Filtering and gain affect only the drawing, never audio playback or export.

The player uses a compact circular Play/Pause/Replay control, a fine progress track with elapsed/total time beneath it, and a WAV download action. Interactive targets are at least 44 px high; the native range retains keyboard seeking and an accessible elapsed/total value. All icons have textual accessible names.

Voice options contains an optional reference clip, consent checkbox, seed, normalization, speaking rate and sampling. Temperature, top-p, top-k and repetition penalty each have a native range slider paired with an exact numeric input. Both stay synchronized, support keyboard input and reset together. Selecting a preset selects its default text language; the language can then be changed independently. Nine examples fill text, language and voice without starting inference: English, French, German, Spanish, Italian, Portuguese, Japanese, Mandarin and Korean. The Japanese example uses Savannah across languages because the exposed catalog has no native Japanese preset. The buttons use a fixed three-column grid with equal touch targets and underline positions. The English interface retains French text without requiring an interface language switch.

Loading and playback

The HTML entry response includes a compact loading shell before framework JavaScript executes. If initialization takes longer, it offers Reload and Open in a new tab. It disappears after the text, player and settings are initialized. The entry is served with Cache-Control: no-store; static hashed assets remain cacheable. ssr_mode=False is explicit so local and hosted startup use the same rendering path. This removes the empty SSR shell observed before client initialization; it does not claim to diagnose every network or browser-cache failure.

Auto-play is a browser-only preference, remembered locally when storage is available. The Generate gesture prepares one persistent audio element and AudioContext (including a short silent first-use primer). Only a new completed result may start automatically. Failed or cancelled requests never replay the preceding take, and a background tab waits for a manual Play click. Browser and iframe playback policies still apply; a rejected play() promise leaves Play and Download usable. No autoplay setting is added to the public synthesis API.

Deploy to Hugging Face

  1. Create a Gradio Space under an account with ZeroGPU access. Select ZeroGPU hardware in its settings. Consult the current HF ZeroGPU instructions.
  2. Upload this directory's contents at the repository root, including vendor, assets, voices.json, examples.json, requirements.txt and packages.txt. app.py must be at the root, not inside an additional archive folder.
  3. Keep sdk_version: 6.30.0 aligned with gradio==6.30.0. The Space installs the pinned runtime dependencies plus FFmpeg and libsndfile. Do not set ZONOS_UI_ONLY in production.
  4. Wait for startup loading to finish. The app imports spaces first, downloads the pinned model assets, and places the LM, decoder and speaker encoder on CUDA at startup, as required by the SDK's CUDA emulation.
  5. Run the live checks in VALIDATION.md before public release. A green Space badge alone does not validate an inference or GPU lease release.

The model is public; no secret is required for these downloads. Weights are fetched into the HF cache and are not included in this repository. Only optional application variable: ZONOS_RUNTIME_DIR, a writable directory for temporary outputs/cancellation markers. The default is a subdirectory of the OS temporary directory. Do not change upstream model override variables without revisiting the release contract.

The two upstream NeMo .far language-rule files are real binary assets in this archive. For a Git push, install Git LFS and preserve the supplied .gitattributes; for a Hub upload, use the normal binary-aware upload workflow. Do not replace those assets with unresolved LFS pointer text.

Engine and functional choices

Item This integration
Model snapshot eaac314642440e8f1f497f1505ca376f8e618f2a
Official Python source b820f6da278862d7c2db3e628d21cbae85983720
Backend Official plain PyTorch reference runtime; FP32 LM
Decoder Official keep2_plain; upstream gain, limiter and sample rate preserved
Speaker conditioning embedding, with official presets or the ownmect_s2 192-dimensional encoder
Output Completed mono WAV, 44,100 Hz, PCM16; one export per successful take
Product limits 600 input characters; 1,350 generated frames; reference clip 0.5–60 seconds, upload at most 25 MiB
Sampling defaults Temperature 0.75, top-p 0.69, top-k 80, repetition penalty 2.4; other defaults remain upstream

Both presets and reference clips use embedding conditioning, also supported by the official Lite app. This is a deliberate choice: it avoids loading the separate dual-codec teacher needed by raw-reference conditioning. It is not a claim that the two conditioning modes produce equal quality. Raw-reference tokens, mood steering, accent adapters, voice blending, speech editing are not exposed in this first version.

There are 23 exposed presets. Four published Hindi/Telugu embeddings are not offered because the pinned Python language registry does not support those language codes. voices.json records the paths and exclusion reasons. Supported language selections: US/UK English, French, German, Spanish, Italian, Portuguese, Japanese, Mandarin and Korean. A preset's native language does not constrain the selected text language. No fallback to English is inserted by the app.

Speaking rate is the model's condition in UTF-8 bytes per second, not a playback multiplier; zero leaves the condition unset. Text normalization uses the upstream normalizer on CPU before requesting a GPU. English and French normalizer graphs load at startup; other supported graphs can initialize on their first request. The upstream normalization fallback remains intact.

Audio chunks are decoded internally through the upstream callback path to observe inference and check cancellation. Only the completed output is published. This release makes no streaming latency or real-time performance claim. Hitting the frame limit without an EOS is an error, not a successful truncated result.

ZeroGPU lifecycle

controller.py validates inputs → prepares text on CPU → waits for the shared inference slot → calls the regular @spaces.GPU function → exports PCM on CPU once → prepares alignment on CPU → requests a separate GPU lease for Qwen3 ForcedAligner → validates timestamps → publishes the result.

  • The engine is shared, with one active inference at a time. Request state, cancellation markers and output names are separate per session/request. Duplicate submissions in the same session are rejected.
  • The decorated function is synchronous, not a GPU generator. The app launches no inference thread. The pinned SDK owns the worker and lease release on return/error; the app synchronizes CUDA before returning. empty_cache() is not used as a substitute for release.
  • Cancellation is cooperative. A file marker crosses the SDK worker boundary; actual upstream callbacks check it. Cancellation during allocation/preparation may wait for the next callback. Controls remain busy until the decorated call has finished. Cancellation does not immediately imply the GPU is free.
  • CPU export happens only after the decorated call returns. The file is written atomically; “Ready to play” is emitted only after export and job cleanup. Error/cancellation leaves the previous valid player value unchanged.
  • demo.unload requests cancellation on browser disconnect. API clients should use /cancel on the same Client, then let the pending request finish. Calling Job.cancel() is not a substitute for the application's cooperative cancellation.
  • The duration callable reserves a conservative bounded GPU budget. It is not a timing estimate, progress percentage or measured benchmark. Native ZeroGPU errors remain visible.

Logs contain request_started, gpu_function_enter, inference_started, gpu_function_exit, gpu_call_returned and request_finished, associated with random request IDs. gpu_call_returned means the decorated SDK call returned; it is not independent telemetry of GPU allocation. Verify lease release with actual platform logs as described in VALIDATION.md.

Files and privacy

Inputs are sent to this Space for inference. Reference uploads use Gradio's temporary file handling. Successful output files have random 192-bit names; older outputs are pruned after 24 hours when another request succeeds. Gradio's own temporary cache cleanup runs hourly with a 24-hour age limit. A restart or storage reset can remove files earlier; download results to retain them.

The file endpoint is a normal Gradio download URL, not authenticated per-file storage. Anyone holding a result URL can access it while retained. Session separation prevents accidental result mixing; it is not an access-control guarantee for sensitive audio. Neither full input text nor raw audio is intentionally included in application lifecycle logs. Upstream normalizer caches are process-local and can hold normalized text until eviction/restart.

Local development

Use Python 3.12 with requirements.txt; normal execution requires CUDA or the HF ZeroGPU environment. A UI-only mode imports no model weights and makes attempted generation fail explicitly:

python -m venv .venv
. .venv/bin/activate
pip install -r requirements-dev.txt
ZONOS_UI_ONLY=1 python app.py

On CPU-only development machines, install the official matching CPU wheels for torch/torchaudio 2.9.1 before the other requirements. That mode does not test ZeroGPU. Keep ZONOS_UI_ONLY unset on the Space.

python -m pytest tests
python -m pip check
node --check assets/player.js
node --check assets/picker.js

Tests use a synthetic CPU engine for lifecycle and HTTP transport. They do not synthesize speech. The HTTP test starts and stops a local Gradio server.

Public API

Use one gradio_client.Client instance per logical session. The /generate endpoint has the same thirteen inputs as the UI; its first output is a JSON string describing the completed audio. UI updates occupy the other output slots. /cancel has no public inputs and acts on the calling client's session.

import json
from urllib.parse import urljoin
from gradio_client import Client

client = Client("OWNER/SPACE")
job = client.submit(
    "Un seul modèle. Une voix pour chaque idée.",
    json.dumps({"voice": "Gaspard", "language": "fr_fr"}),
    "Preset", None, False,  # source, reference clip, permission for that clip
    True, 42, False,        # normalization, seed, randomize
    0.75, 0.69, 80, 2.4, 0, # temperature, top-p, top-k, repetition, natural rate
    api_name="/generate",
)
# To stop cooperatively while it runs:
# client.predict(api_name="/cancel")
result = job.result()
take = json.loads(result[0])
if take.get("url"):
    print(urljoin(client.src, take["url"]))
client.close()

On cancellation, the audio output is a Gradio skip update, not a new take. Validation, duplicate-submission and inference errors produce API failures. Inspect client.view_api() for the pinned Gradio transport schema. This is a new Space API, not an implementation of Zyphra's /v2 HTTP service.

Credits and licenses

Model authors: Gabriel Clark, Sofian Mejjoute, George Close and Beren Millidge, Zyphra. Model card: weights under Apache 2.0. Official runtime: MIT. UI/UX: Sylvain Filoni / fffiloni. Independent adaptation; no partnership implied.

Application code: MIT, see LICENSE. Original runtime notices and third-party licenses are retained under vendor. See PROVENANCE.md for the exact source, documented modification, dependency choice and integrity manifest.

Word synchronization · v0.3.0

The generated WAV and original text are passed to Qwen/Qwen3-ForcedAligner-0.6B-hf, pinned at c07281df297b9905d24a508279258cccf987a064 (Apache 2.0). It uses the documented Transformers 5.22.2 processor and token-classification model. The aligner loads on CUDA at startup, then runs in a separate synchronous spaces.GPU call after synthesis has returned. One shared semaphore protects both models; session ownership spans both calls. Cancellation is checked before and after the alignment forward pass. Logs identify alignment_gpu_enter, alignment_gpu_exit and alignment_gpu_returned; SDK release logs must also be checked.

No second transcription rewrites the input. The aligner's default segmentation uses whitespace words and individual Chinese/Japanese characters. Predicted timestamps have 80 ms resolution. Invalid or incomplete alignment leaves the new WAV playable with an explicit unavailable status and manual text pagination; no evenly spaced or estimated timings are substituted. A failed/cancelled synthesis still preserves the previous result.

The custom HTML text viewport shows at most two lines and follows the actual audio clock, including seeking and replay. Editing preserves the draft separately from the last take. The custom reference component uses Gradio's documented upload(file) API. Recording starts only on a user click; media tracks stop on stop, error, removal or page disposal. Trimming exports one mono PCM16 clip only when Apply is pressed; Reset restores the original uploaded clip. The hidden native audio wire preserves the existing public API's FileData preprocessing. Generated output format and all 13 public inputs / 5 outputs are unchanged; the player JSON adds text, words and alignment.