orpheus ttslocal ttsdeveloperson-device aitext-to-speechdeep learningcanopy labsllamasnac codec

Orpheus TTS: A Deep Technical Dive into Canopy Labs' Llama-Based Speech Synthesis Architecture

A technical exploration of Orpheus TTS by Canopy Labs: a Llama-based speech-token generation approach, SNAC neural audio codec integration, hierarchical codebook layout, streaming-oriented inference, emotive tag control, and training notes from the public project materials.

Updated on May 23, 202614 min read

In March 2025, Canopy Labs released Orpheus TTS, an open-source text-to-speech system built around a Llama 3B backbone. What makes Orpheus technically interesting is not a novel neural architecture — it is the demonstration that an LLM-style model, when trained to emit audio tokens from the SNAC neural audio codec, can produce expressive speech with natural rhythm and intonation.

This post covers the architecture, the SNAC codec integration, the prompt conditioning mechanism, the streaming inference pipeline, and the training methodology in technical depth.


Model Family Overview

Orpheus TTS has been released in several variants, centered on the same Llama 3B-style backbone architecture:

Model Parameters Purpose
orpheus-tts-0.1-finetune-prod 3B Everyday TTS with 8 distinct voices
orpheus-tts-0.1-pretrained 3B Base model for conditioning experiments and custom fine-tuning
Multilingual Family 3B-class Research-release language variants

Additional smaller variants have been discussed in project materials, but availability can change. Check the official GitHub and Hugging Face pages before planning around a specific checkpoint or license.

The finetuned prod model provides 8 preset voices: tara, leah, jess, leo, dan, mia, zac, zoe. Multilingual variants may have their own language-specific voices, depending on the checkpoint.


Core Architecture: LLM as Speech Generator

Orpheus TTS makes a useful conceptual simplification: instead of building a traditional speech synthesis pipeline with separate acoustic models and vocoders, it treats speech generation as a language modeling problem on an extended token vocabulary.

Text prompt tokens → [Llama-3.2-3B] → Audio token sequence → [SNAC decoder] → 24kHz PCM waveform

The Key Insight

The Llama 3B-style backbone is used with an extended tokenizer vocabulary containing tokens that represent SNAC audio codec codes. This means:

  1. Same architecture — the model uses standard Llama attention, SwiGLU activations, RoPE position encoding, and KV caching
  2. Same inference pipeline — any framework that runs Llama can potentially run Orpheus (vLLM, llama.cpp, transformers)
  3. Same training approach — next-token prediction on interleaved text and audio token sequences

The model’s ability to produce natural prosody and emotion comes from training the LLM-style model on speech data rather than from a separate hand-built prosody module.

Vocabulary Extension

The base Llama tokenizer is extended with custom tokens corresponding to SNAC codec codes:

<custom_token_0> through <custom_token_N>

Where N covers the full range of SNAC codebook indices across all hierarchical levels. At inference time, the model outputs these custom tokens interleaved with regular text tokens. Post-processing extracts only the audio tokens and discards text tokens.


SNAC Audio Codec: The Tokenization Layer

Orpheus uses the SNAC (Multi-Scale Neural Audio Codec) operating at 24kHz, developed by Hubert Siuzdak. SNAC is the critical bridge between the LLM’s discrete token space and continuous audio.

Hierarchical Codebook Design

SNAC differs from traditional audio codecs like EnCodec or DAC in a key way: it uses a multi-scale hierarchical structure where coarse tokens are sampled less frequently than fine tokens.

Time:    t=0ms     t=40ms    t=80ms    t=120ms
         |         |         |         |
Layer 0: [c0_0]    [c0_1]    [c0_2]    [c0_3]     ← 1 token/frame (coarse)
Layer 1: [c1_0a]             [c1_1a]              ← 2 tokens/frame
         [c1_0b]             [c1_1b]
Layer 2: [c2_0a]   [c2_1a]   [c2_2a]   [c2_3a]   ← 4 tokens/frame (fine)
         [c2_0b]   [c2_1b]   [c2_2b]   [c2_3b]
         [c2_0c]   [c2_1c]   [c2_2c]   [c2_3c]
         [c2_0d]   [c2_1d]   [c2_2d]   [c2_3d]

Per-frame token count: 7 tokens per 40ms frame (1 from Layer 0, 2 from Layer 1, 4 from Layer 2).

What each layer captures:

  • Layer 0 (coarse): Phonetic content, pitch contour, basic prosody — the semantic backbone
  • Layer 1 (medium): Speaker identity, timbre, voice characteristics
  • Layer 2 (fine): Breathiness, articulation details, background characteristics, micro-prosody

This hierarchical structure helps reduce the burden on the language model: coarse tokens carry much of the semantic and prosodic structure, while finer tokens add perceptual detail.

Comparison to Other Codecs

Feature SNAC (24kHz) EnCodec DAC S3 Tokenizer (Chatterbox)
Bitrate (kbps) ~7.3 1.5-24 8-32 ~6.4
Layers 3 (hierarchical) 4 (RVQ) 4 (RVQ) 8 (RVQ)
Frame rate 25 Hz (Layer 0) 50 Hz 50 Hz 25 Hz
Tokens/frame 7 4 4 8
Sample rate 24 kHz 32 kHz 44.1 kHz 16 kHz
Multi-scale Yes No No No

The multi-scale property is SNAC’s defining characteristic. Because coarse tokens are sampled at a lower rate, the LLM has fewer tokens to predict for the semantic content, reducing the autoregressive generation burden.


Token-to-Audio Decoding Pipeline

The decoding pipeline converts the LLM’s token outputs into audible waveforms. This is implemented in the decoder.py module of the Orpheus TTS repository.

Step 1: Token Extraction

The LLM outputs tokens as strings in the format <custom_token_N>. The turn_token_into_id function parses these strings and normalizes the token ID based on its position within a 7-token frame:

def turn_token_into_id(token_string, index):
    # Extract integer N from "<custom_token_N>"
    token_id = int(token_string.split("_")[2].split(">")[0])
    # Normalize based on codebook index offset
    # Each layer of the 7-token frame has a different offset
    offset = (index % 7) * 4096
    return token_id - 10 - offset

The subtraction of 10 accounts for special tokens in the Llama vocabulary. The - offset normalization ensures each codebook’s tokens are mapped to the correct 0-based range.

Step 2: Windowing and Buffering

The decoder accumulates tokens into frames. A window of 28 tokens (4 complete frames of 7 tokens each) is required before triggering the SNAC decoder. This buffering introduces approximately 160ms of latency (4 frames × 40ms/frame), which is the dominant component of the end-to-end streaming latency.

# Token windowing logic (conceptual)
buffer = []
for token in token_stream:
    buffer.append(token)
    if len(buffer) >= 28:  # 4 frames × 7 tokens
        frames = buffer[:28]
        buffer = buffer[28:]
        audio = snac_decode(frames)
        yield audio

Step 3: SNAC Decoding

The convert_to_audio function reshapes the 28-token window into the three hierarchical tensors required by the SNAC model:

def convert_to_audio(audio_ids):
    audio_ids = torch.tensor(audio_ids, dtype=torch.int32).reshape(-1, 7)
    
    # Layer 0: first token of each frame
    codes_0 = audio_ids[:, 0].unsqueeze(0)
    
    # Layer 1: tokens at positions 1 and 4 (interleaved)
    codes_1 = torch.stack((
        audio_ids[:, 1], audio_ids[:, 4]
    )).t().flatten().unsqueeze(0)
    
    # Layer 2: tokens at positions 2, 3, 5, 6 (interleaved)
    codes_2 = torch.stack((
        audio_ids[:, 2], audio_ids[:, 3],
        audio_ids[:, 5], audio_ids[:, 6]
    )).t().flatten().unsqueeze(0)
    
    with torch.inference_mode():
        audio_hat = snac_model.decode([codes_0, codes_1, codes_2])
    
    return audio_hat[0]  # 24kHz PCM mono

The SNAC decoder produces 24kHz PCM audio at int16 precision. Each 28-token window produces approximately 4 × 40ms = 160ms of audio.

Pipeline Summary

LLM output tokens
  ↓
<custom_token_28631> <custom_token_31142> ...
  ↓ turn_token_into_id()
Integer IDs (0-based per codebook)
  ↓ reshape(-1, 7)
[7 tokens × N frames] tensor
  ↓ deinterleave
codes_0: [1 × N]
codes_1: [1 × 2N]
codes_2: [1 × 4N]
  ↓ SNAC decoder
24kHz PCM waveform

Prompt Formatting and Conditioning

The prompt format is the mechanism through which voice selection, emotion control, and text content are communicated to the model.

Voice Selection

Each voice is activated by a simple name prefix in the prompt:

{voice_name}: {text_content}

For example:

tara: The quick brown fox jumps over the lazy dog.

The Python package handles this formatting automatically. The voice name functions as a conditioning signal — the model has learned during fine-tuning to associate each name with a specific speaker embedding and prosodic profile.

Emotive Tags

Orpheus supports inline emotion control through XML-style tags embedded directly in the text:

tara: <laugh> I can't believe you just said that! <chuckle> That's hilarious.

Available tags:

  • <laugh> — Laughter
  • <chuckle> — Light chuckle
  • <sigh> — Sigh
  • <cough> — Cough
  • <sniffle> — Sniffle
  • <groan> — Groan
  • <yawn> — Yawn
  • <gasp> — Gasp

These tags are not parsed by a separate classifier or rule engine. They are text tokens in the prompt that the model has learned to associate with specific acoustic patterns during training. The model internalizes the mapping from tag to prosodic realization through exposure to speech data annotated with these tags.

Special Tokens

The full prompt format includes control tokens:

<custom_token_3><|begin_of_text|>tara: Hello world<|eot_id|><custom_token_4><custom_token_5><custom_token_1>
  • <custom_token_3> — Start of generation marker
  • <|begin_of_text|> — Llama BOS token
  • <|eot_id|> — Llama end-of-turn token (separates text prompt from generation)
  • <custom_token_4> — Audio generation start
  • <custom_token_5> — Continuation marker
  • <custom_token_1> — Padding/boundary token
  • <custom_token_0> — Stop token (at position 128258 in the vocabulary, used as stop_token_id)

Sampling Parameters

Generation uses standard LLM sampling parameters with specific recommendations:

Parameter Recommended Range Effect
temperature 0.6-0.9 Higher = more varied prosody
top_p 0.9 Nucleus sampling threshold
repetition_penalty 1.1-1.3 Required for stability (<1.1 causes model collapse)
max_tokens 10240 Upper bound on generated audio tokens

Increasing both repetition_penalty and temperature can affect pacing and stability, so these settings should be tested with the target voice and prompt style.


Voice Conditioning and Fine-Tuning

Orpheus supports preset voices in the finetuned model and experimentation with voice conditioning in the pretrained model. Production-quality custom voices generally require data collection and fine-tuning.

For the Finetuned Model

The finetuned model’s 8 voices are fixed. Zero-shot cloning is not directly supported in the finetuned checkpoint — you select from the 8 preset voices. Cloning a novel voice requires fine-tuning the model on new speaker data.

For the Pretrained Model

The pretrained model supports a more flexible form of conditioning. Because this is an experimental workflow rather than a simple product-style cloning feature, the approach is different:

  1. Text-speech pairs as prompt context: Pass one or more text-speech example pairs in the prompt
  2. In-context conditioning: The model uses the acoustic patterns from the example speech tokens to influence the generated output
  3. More examples = more reliable cloning: The more text-speech pairs provided, the more consistently the model reproduces the target voice

This is similar in spirit to in-context learning applied to speech synthesis, but quality and reliability depend heavily on prompt construction and examples.

# Conceptual: few-shot conditioning with pretrained model
prompt = """
<audio_tokens_for_phrase_1>Hello, how are you?<|end_audio|>
<audio_tokens_for_phrase_2>My name is John.<|end_audio|>
Now generate in the same voice: This is a test of voice cloning.
"""

The pretrained model was designed to enable experimentation with different conditioning strategies. The Canopy Labs team explicitly notes that “the more text-speech pairs you pass in the prompt, the more reliably it will generate in the correct voice.”

Finetuning for Custom Voices

For production voice cloning, the recommended approach is fine-tuning:

  1. Collect ~50-300 audio examples per speaker
  2. Format as a Hugging Face dataset following the canopylabs/zac-sample-dataset schema
  3. Preprocess with the provided Colab notebook
  4. Train using the finetune/train.py script with standard HuggingFace Trainer + Transformers

Quality improvements may plateau as the dataset grows, but the exact point depends on recording quality, speaker consistency, and target use case. The training process is analogous to fine-tuning an LLM, and community workflows include LoRA-style approaches.


Streaming Inference Architecture

The streaming pipeline is the most architecturally interesting part of the inference system. It bridges vLLM’s asynchronous generation API with a synchronous generator interface.

Architecture Overview

OrpheusModel.generate_speech()
  ↓
_generate_tokens_sync()
  ├── async_producer() → runs in separate thread with asyncio event loop
  │     └── vLLM AsyncLLMEngine.generate() → token stream
  │
  └── queue.Queue → transfers tokens from async to sync thread
        ↓
generate_speech() → iterates token queue, decodes to audio

Component Breakdown

1. OrpheusModel Class (engine_class.py)

The OrpheusModel class is the top-level interface:

class OrpheusModel:
    def __init__(self, model_name, max_model_len=2048):
        self.model_name = model_name
        self.max_model_len = max_model_len
        # Initialize vLLM AsyncLLMEngine
        self.engine = AsyncLLMEngine.from_engine_args(...)
    
    def _format_prompt(self, prompt, voice):
        # Prepends voice identifier and control tokens
        return f"{voice}: {prompt}"
    
    def generate_speech(self, prompt, voice="tara"):
        # Returns a synchronous generator yielding audio chunks
        syn_tokens = self._generate_tokens_sync(prompt, voice)
        for audio_chunk in syn_tokens:
            yield audio_chunk

2. Async-to-Sync Bridge

The critical bridge uses asyncio.run() in a separate thread:

def _generate_tokens_sync(self, prompt, voice):
    queue = queue.Queue()
    
    def async_producer():
        async def _produce():
            async for token in self.engine.generate(prompt, sampling_params):
                queue.put(token)
            queue.put(None)  # Sentinel
        asyncio.run(_produce())
    
    thread = threading.Thread(target=async_producer)
    thread.start()
    
    while True:
        token = queue.get()
        if token is None:
            break
        yield token
    
    thread.join()

This pattern is necessary because vLLM’s engine exposes an async API, but the user-facing generate_speech method needs to be a synchronous generator for simplicity.

3. Audio Decoding Loop

The generate_speech method wraps the token generator with the SNAC decoder:

def generate_speech(self, prompt, voice="tara"):
    formatted = self._format_prompt(prompt, voice)
    token_stream = self._generate_tokens_sync(formatted)
    
    buffer = []
    for token in token_stream:
        token_id = turn_token_into_id(token)
        buffer.append(token_id)
        
        if len(buffer) >= 28:  # Window of 4 frames
            frames, buffer = buffer[:28], buffer[28:]
            audio = convert_to_audio(frames)
            yield audio.tobytes()

Latency Profile

Stage Example Latency Notes
Prompt processing Varies Depends on backend and hardware
First token generation Varies First autoregressive step
First 28-token buffer fill Often the largest component 7 tokens × 4 frames before decode
SNAC decode Usually small Depends on device and backend
End-to-end first audio Hardware-dependent From text input to first audio chunk
Subsequent tokens Hardware-dependent Throughput varies by backend

With input streaming, first-packet latency can improve, but the result depends on hardware, backend, prompt length, and batching.

Precision and Memory

  • Weights: bfloat16 by default
  • Output audio: int16 PCM at 24kHz
  • VRAM: roughly in the multi-GB range for the 3B model at bfloat16
  • Inference backend: vLLM with PagedAttention for efficient KV-cache management

Training Methodology

Pre-Training

The public project materials describe the base pretrained model (orpheus-tts-0.1-pretrained) as being trained on a large English speech corpus combined with text-only QA pairs.

The text QA data serves a specific purpose: it prevents the model from “forgetting” its semantic and reasoning capabilities during speech training. Standard LLMs are trained exclusively on text. When you continue training on speech tokens, the model risks catastrophic forgetting of linguistic knowledge. The interleaved text training maintains the model’s ability to understand linguistic nuance, which directly translates to better prosody and emphasis in generated speech.

Training format: Sequences of length 8192 tokens are chained together for efficient training. The dataset format interleaves text and speech tokens in the input_ids field.

Key finding from Canopy Labs: natural speech data is preferred for fine-tuning because synthetic speech can reduce acoustic diversity and hurt codebook utilization.

Fine-Tuning

Fine-tuning follows a straightforward recipe:

pip install transformers datasets wandb trl flash_attn torch
accelerate launch train.py

The fine-tuning process:

  1. Takes a Hugging Face dataset with text-speech pairs
  2. Preprocesses input text and tokenizes speech through SNAC
  3. Formats interleaved text-speech token sequences
  4. Trains with standard causal language modeling loss
  5. Supports LoRA, QLoRA, and full fine-tuning via HuggingFace Trainer

Data preprocessing speed depends on hardware, dataset format, and audio length.

Training Compute

While exact training compute is not disclosed, a 3B-class speech-token model with long context windows represents a significant training run. The open repository and permissive project materials make Orpheus practical for community experimentation, but downstream users should verify current license terms for the exact checkpoint they use.


Multilingual Architecture

In April 2025, Canopy Labs released multilingual models as a research preview.

Research-Release Languages

The public research release includes variants for languages such as:

Language Pretrained Finetuned
English Yes Yes
Chinese Yes Yes
Hindi Yes Yes
Korean Yes Yes
Spanish / Italian Yes Yes
French Yes Yes
German Yes Yes

(Available on Hugging Face)

Training Approach for Multilingual

The training guide published by Canopy Labs explains a key finding: to adapt Orpheus to a new language, the most effective approach is:

  1. Start from the English finetuned model (not from scratch or from the pretrained model)
  2. Fine-tune from that base rather than starting from scratch
  3. Use enough clean examples per voice to evaluate whether the result is production-ready

The prompt format is standardized across languages: {voice_name}: {text in target language}. The model learns to associate the voice name with language-appropriate acoustic characteristics through fine-tuning.


Deployment Options

Local GPU (Recommended)

pip install orpheus-speech

python -c "
from orpheus_tts import OrpheusModel
model = OrpheusModel('canopylabs/orpheus-tts-0.1-finetune-prod')
tokens = model.generate_speech('Hello world', voice='tara')
for chunk in tokens:
    # chunk is 16-bit PCM at 24kHz
    pass
"

The default workflow is oriented around CUDA GPUs and vLLM. Hardware requirements depend on precision, backend, and model variant.

Baseten Cloud Deployment

Canopy Labs partnered with Baseten as the preferred inference provider. Baseten offers:

  • fp8 inference: Lower precision, often faster
  • fp16 inference: Full fidelity
  • One-click deployment with real-time streaming on production infrastructure

CPU Inference via llama.cpp

The community has added support for Orpheus TTS in llama.cpp, enabling CPU-based inference:

# Orpheus GGUF inference
python gguf_orpheus.py --model orpheus-3b-ft-q8_0.gguf --prompt "Hello world"

Community performance reports vary by model file, quantization, backend, and hardware. Treat any llama.cpp numbers as setup-specific rather than general benchmarks.

API-Compatible Servers


Architecture Comparison

Feature Orpheus TTS Qwen3-TTS Chatterbox Kokoro
Architecture Llama-3.2-3B backbone Dual-track LM + multi-codebook Llama backbone + CFM decoder StyleTTS 2 + ISTFTNet
Codec SNAC (24kHz, 3-layer) Qwen-TTS-Tokenizer (12Hz/25Hz) S3 tokenizer (25Hz) Phoneme-based
Parameters 3B 0.6B / 1.7B 0.35B / 0.5B 82M
Streaming Streaming-oriented Streaming-oriented Depends on implementation Depends on implementation
Voice cloning Fine-tuning / conditioning workflows Prompt-based workflows Zero-shot workflows Not a core feature
Emotion control Tag-based Instruction-based Exaggeration-style control Limited
Languages Multilingual research release Multilingual Model-dependent Model-dependent
License Check current checkpoint terms Check current checkpoint terms Check current checkpoint terms Check current checkpoint terms
Hardware needs Heavier 3B-class model Depends on size Depends on variant Lightweight
Latency Backend-dependent Backend-dependent Backend-dependent Backend-dependent

Orpheus’s key differentiator is its architectural simplicity: a Llama-style model with an extended vocabulary for speech tokens. This means it can benefit from parts of the broader LLM tooling ecosystem, including serving, quantization, and fine-tuning workflows.


Practical Implications

1. The Llama ecosystem is part of your infrastructure. Many tools built for Llama-style models can be adapted to Orpheus, though audio-token decoding still adds TTS-specific requirements.

2. Quality and size trade off. Smaller variants may be easier to deploy, while larger models generally have more capacity for prosody and expression.

3. Emotion control is coarse but useful. The emotive tags provide a simple interface for expressive speech, though results vary by prompt and voice.

4. Custom voices require data collection. Compared with prompt-based cloning workflows, Orpheus custom voice work usually involves a higher setup cost because fine-tuning and evaluation data matter.

5. The multilingual research preview shows the path forward. The training guide provides a starting point for adding languages, and community variants show how the approach can be extended.

More from the blog