All posts
Product Update

Photon can now speak

Up to 70% lower speech startup latency. Meet text-to-speech in Photon.

Photon can now speak

Photon now generates speech with up to 70% lower p95 startup latency than vLLM-Omni. In our Qwen3-TTS tests on H100 and B200, the first audible output arrived in under 85 milliseconds for 95% of requests, at six requests per second. Less waiting for your voice assistant to answer. Faster feedback for the person on the other end.

Today we're adding Qwen3-TTS CustomVoice, in both 0.6B and 1.7B sizes, and Kokoro-82M. Generate a complete recording or stream audio as it's ready, through the same Moondream Python package you use for transcription and vision.

Photon versus vLLM-Omni p95 speech startup latency: 83.8 versus 279.5 ms for 0.6B on H100; 73.1 versus 246.9 ms for 0.6B on B200; 42.5 versus 109.1 ms for 1.7B on H100; 41.0 versus 93.5 ms for 1.7B on B200.

We measure the first audible audio, not a chunk of silence. Photon cut that wait by 56–70% across all four comparisons.

Give it a voice

Install the latest Moondream Python package:

pip install --upgrade "moondream>=2.5.0"

Here's a complete Qwen3-TTS example for an NVIDIA GPU:

import moondream as md

with md.photon("Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice", device="cuda") as voice:
    result = voice.synthesize(
        text="Good morning! What would you like to build today?",
        voice="Ryan",
        language="English",
    )
    pcm = result["audio"]
    sample_rate = result["sample_rate"]

The result is mono, 24 kHz floating-point PCM, ready for your audio output or file writer. Change 1.7B to 0.6B to use the smaller checkpoint. Both accept a voice and language; the 1.7B model also accepts instructions to guide delivery.

To start playback before the whole recording is ready, set stream=True. Each update contains the next piece of audio, not the recording so far. In this example, play is your application's audio output callback:

def speak(voice, text, play):
    stream = voice.synthesize(text=text, voice="Ryan", stream=True)
    for update in stream:
        play(update["audio"], update["sample_rate"])
    return stream.result()

stream.result() returns the complete recording. Async applications can use await voice.asynthesize(...) and consume updates with async for.

A smaller option with Kokoro

Kokoro has 82 million parameters and runs on either CPU or CUDA. It uses the same synthesis methods, with one difference: you pass phonemes instead of text.

Text-to-phoneme conversion decides how written words are pronounced. Keeping that step in your application lets you use your own pronunciation dictionary or language frontend, without installing a separate text-processing stack just to run the model. The input is a Unicode string in Kokoro's phoneme vocabulary, not arbitrary IPA or a list of token IDs.

import moondream as md

with md.photon("hexgrad/Kokoro-82M", device="cpu") as voice:
    result = voice.synthesize(phonemes="həlˈO", voice="af_heart")
    pcm = result["audio"]

Use device="cuda" to run on an NVIDIA GPU. On a Mac, use the CPU path. Kokoro also supports streamed output.

We're excited to see what you build with speech going in both directions. Give it a try, and let us know how it works in your application.

Product Update