khariha/dia2-easy-tts

An easy way to deploy Dia2 TTS without a GPU

9

stars

8

commits

Python

primary language

Mar 2, 2026

updated

README

Dia2 Easy TTS

Deploy a production-ready Text-to-Speech API using Dia2 in minutes—no powerful GPU hardware required. Clone voices from short audio samples and generate natural-sounding speech with just a few API calls, including real-time streaming audio.

This project bundles a custom Dia2 TTS build (with streaming support) in a simple REST API deployed on Modal's serverless infrastructure.

Why use Modal?

  • Zero hardware setup - Runs entirely on Modal's cloud GPUs
  • OpenAI-compatible API - Standard /v1/audio/speech endpoint
  • Voice cloning - Clone any voice from a short WAV sample
  • Multi-speaker dialogues - Support for conversations between two speakers
  • Real-time streaming - Receive audio chunks as they are generated (~80ms latency to first chunk on warm container)
  • Fast deployment - Get your API running in ~5 minutes
  • Free tier friendly - $30/month in free Modal credits

Requirements

Quick Setup

1. Install Modal

pip install modal

2. Authenticate with Modal

modal setup

This opens your browser to log in and authenticates your machine with Modal.

3. Clone and Deploy

git clone https://github.com/khariha/dia2-easy-tts.git
cd dia2-easy-tts

# Deploy to Modal (first time takes ~5 minutes to build image and download model weights)
modal deploy main.py

A successful deployment will show:

✓ App deployed! 🎉
└── 🔨 Created web function dia2 => https://your-app-name--dia2-easy-tts-dia2.modal.run

4. Test Your Deployment

# Health check
curl https://your-app-name--dia2-easy-tts-dia2.modal.run/health

# Generate speech (non-streaming)
curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Hello, this is a test of the Dia2 TTS system.' \
  -o output.wav

Or run the included test script (update BASE_URL first):

pip install requests sounddevice soundfile numpy
python test.py

API

POST /v1/audio/speech

Generate speech from a dialogue script.

Form parameters:

ParameterTypeDefaultDescription
scriptstringrequiredText with [S1]/[S2] speaker tags
speaker1_audiofileWAV file to clone voice for speaker 1
speaker2_audiofileWAV file to clone voice for speaker 2
streamboolfalseStream audio chunks as they are generated
chunk_framesint1Frames per streaming chunk (~80ms each)
cfg_scalefloat2.0Classifier-free guidance scale
cfg_filter_kint50Top-k filter for CFG logits
text_temperaturefloat0.6Sampling temperature for text tokens
text_top_kint50Top-k sampling for text tokens
audio_temperaturefloat0.8Sampling temperature for audio tokens
audio_top_kint50Top-k sampling for audio tokens
include_prefixboolfalseInclude prefix audio in the output
use_cuda_graphbooltrueEnable CUDA graph acceleration

Returns: WAV audio file (streaming or complete)

GET /health

Check service status and GPU availability.

Returns:

{
  "status": "healthy",
  "service": "Dia2 Easy TTS",
  "gpu_info": {
    "torch_info": {
      "cuda_available": true,
      "device_name": "NVIDIA A100-SXM4-40GB"
    }
  }
}

Usage Examples

Basic Synthesis

curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Your text here. [S2]With multiple speakers.' \
  -o output.wav

Streaming

curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Hello world' \
  -F 'stream=true' \
  -o output.wav

Voice Cloning

curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Hello [S2]World' \
  -F 'speaker1_audio=@/path/to/voice1.wav' \
  -F 'speaker2_audio=@/path/to/voice2.wav' \
  -F 'include_prefix=false' \
  -o output.wav

Python Client

import requests

BASE_URL = "https://your-app-name--dia2-easy-tts-dia2.modal.run"

script = """[S1] Welcome to our podcast.
[S2] Thanks for having me!
[S1] Let's dive right in."""

# Non-streaming
response = requests.post(
    f"{BASE_URL}/v1/audio/speech",
    data={"script": script},
    timeout=300,
)
with open("output.wav", "wb") as f:
    f.write(response.content)

# Streaming
with requests.post(
    f"{BASE_URL}/v1/audio/speech",
    data={"script": script, "stream": "true"},
    stream=True,
    timeout=300,
) as response:
    with open("output.wav", "wb") as f:
        for chunk in response.iter_content(chunk_size=None):
            if chunk:
                f.write(chunk)

JavaScript/TypeScript

// Non-streaming
const formData = new FormData();
formData.append('script', '[S1]Hello from JavaScript!');

const response = await fetch('https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech', {
  method: 'POST',
  body: formData,
});
const blob = await response.blob();

// Streaming
formData.append('stream', 'true');
const streamResponse = await fetch('...', { method: 'POST', body: formData });
const reader = streamResponse.body.getReader();
// pipe reader to Web Audio API or MediaSource for real-time playback

Script Format

Scripts use speaker tags to indicate who is speaking:

  • [S1] — Speaker 1 (uses speaker1_audio if provided)
  • [S2] — Speaker 2 (uses speaker2_audio if provided)
[S1] It was a dark and stormy night.
[S2] The wind howled through the trees.
[S1] Sarah pulled her coat tighter as she walked.

Configuration

Key constants at the top of main.py:

DIA2_MODEL_REPO    = "nari-labs/Dia2-2B"   # switch to Dia2-1B for faster/cheaper
DIA2_GPU_TYPE      = "A100"
DIA2_MAX_CONTAINERS = 1                     # max concurrent requests

Add min_containers=1 to the @app.function decorator to keep a warm container and eliminate cold starts (~38s), at the cost of continuous GPU charges.

Troubleshooting

First deployment is slow

The first deploy builds the image and downloads ~4GB of model weights. This is cached — subsequent deploys take ~30 seconds.

Cold start latency (~38s)

The first request after a period of inactivity loads the model into GPU memory. Set min_containers=1 in main.py to keep a container warm.

Request timeout

For long scripts, increase the client timeout:

requests.post(url, data=data, timeout=600)

Stopping a running app

modal app list          # find your app ID
modal app stop <app-id>

Acknowledgments

  • Dia2 — The TTS model powering this API
  • Modal — Serverless GPU infrastructure
  • FastAPI — Web framework

Contributors

khariha

8 commits

khariha/dia2-easy-tts

An easy way to deploy Dia2 TTS without a GPU

9

stars

8

commits

Python

primary language

Mar 2, 2026

updated

README

Dia2 Easy TTS

Deploy a production-ready Text-to-Speech API using Dia2 in minutes—no powerful GPU hardware required. Clone voices from short audio samples and generate natural-sounding speech with just a few API calls, including real-time streaming audio.

This project bundles a custom Dia2 TTS build (with streaming support) in a simple REST API deployed on Modal's serverless infrastructure.

Why use Modal?

  • Zero hardware setup - Runs entirely on Modal's cloud GPUs
  • OpenAI-compatible API - Standard /v1/audio/speech endpoint
  • Voice cloning - Clone any voice from a short WAV sample
  • Multi-speaker dialogues - Support for conversations between two speakers
  • Real-time streaming - Receive audio chunks as they are generated (~80ms latency to first chunk on warm container)
  • Fast deployment - Get your API running in ~5 minutes
  • Free tier friendly - $30/month in free Modal credits

Requirements

Quick Setup

1. Install Modal

pip install modal

2. Authenticate with Modal

modal setup

This opens your browser to log in and authenticates your machine with Modal.

3. Clone and Deploy

git clone https://github.com/khariha/dia2-easy-tts.git
cd dia2-easy-tts

# Deploy to Modal (first time takes ~5 minutes to build image and download model weights)
modal deploy main.py

A successful deployment will show:

✓ App deployed! 🎉
└── 🔨 Created web function dia2 => https://your-app-name--dia2-easy-tts-dia2.modal.run

4. Test Your Deployment

# Health check
curl https://your-app-name--dia2-easy-tts-dia2.modal.run/health

# Generate speech (non-streaming)
curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Hello, this is a test of the Dia2 TTS system.' \
  -o output.wav

Or run the included test script (update BASE_URL first):

pip install requests sounddevice soundfile numpy
python test.py

API

POST /v1/audio/speech

Generate speech from a dialogue script.

Form parameters:

ParameterTypeDefaultDescription
scriptstringrequiredText with [S1]/[S2] speaker tags
speaker1_audiofileWAV file to clone voice for speaker 1
speaker2_audiofileWAV file to clone voice for speaker 2
streamboolfalseStream audio chunks as they are generated
chunk_framesint1Frames per streaming chunk (~80ms each)
cfg_scalefloat2.0Classifier-free guidance scale
cfg_filter_kint50Top-k filter for CFG logits
text_temperaturefloat0.6Sampling temperature for text tokens
text_top_kint50Top-k sampling for text tokens
audio_temperaturefloat0.8Sampling temperature for audio tokens
audio_top_kint50Top-k sampling for audio tokens
include_prefixboolfalseInclude prefix audio in the output
use_cuda_graphbooltrueEnable CUDA graph acceleration

Returns: WAV audio file (streaming or complete)

GET /health

Check service status and GPU availability.

Returns:

{
  "status": "healthy",
  "service": "Dia2 Easy TTS",
  "gpu_info": {
    "torch_info": {
      "cuda_available": true,
      "device_name": "NVIDIA A100-SXM4-40GB"
    }
  }
}

Usage Examples

Basic Synthesis

curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Your text here. [S2]With multiple speakers.' \
  -o output.wav

Streaming

curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Hello world' \
  -F 'stream=true' \
  -o output.wav

Voice Cloning

curl -X POST https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech \
  -F 'script=[S1]Hello [S2]World' \
  -F 'speaker1_audio=@/path/to/voice1.wav' \
  -F 'speaker2_audio=@/path/to/voice2.wav' \
  -F 'include_prefix=false' \
  -o output.wav

Python Client

import requests

BASE_URL = "https://your-app-name--dia2-easy-tts-dia2.modal.run"

script = """[S1] Welcome to our podcast.
[S2] Thanks for having me!
[S1] Let's dive right in."""

# Non-streaming
response = requests.post(
    f"{BASE_URL}/v1/audio/speech",
    data={"script": script},
    timeout=300,
)
with open("output.wav", "wb") as f:
    f.write(response.content)

# Streaming
with requests.post(
    f"{BASE_URL}/v1/audio/speech",
    data={"script": script, "stream": "true"},
    stream=True,
    timeout=300,
) as response:
    with open("output.wav", "wb") as f:
        for chunk in response.iter_content(chunk_size=None):
            if chunk:
                f.write(chunk)

JavaScript/TypeScript

// Non-streaming
const formData = new FormData();
formData.append('script', '[S1]Hello from JavaScript!');

const response = await fetch('https://your-app-name--dia2-easy-tts-dia2.modal.run/v1/audio/speech', {
  method: 'POST',
  body: formData,
});
const blob = await response.blob();

// Streaming
formData.append('stream', 'true');
const streamResponse = await fetch('...', { method: 'POST', body: formData });
const reader = streamResponse.body.getReader();
// pipe reader to Web Audio API or MediaSource for real-time playback

Script Format

Scripts use speaker tags to indicate who is speaking:

  • [S1] — Speaker 1 (uses speaker1_audio if provided)
  • [S2] — Speaker 2 (uses speaker2_audio if provided)
[S1] It was a dark and stormy night.
[S2] The wind howled through the trees.
[S1] Sarah pulled her coat tighter as she walked.

Configuration

Key constants at the top of main.py:

DIA2_MODEL_REPO    = "nari-labs/Dia2-2B"   # switch to Dia2-1B for faster/cheaper
DIA2_GPU_TYPE      = "A100"
DIA2_MAX_CONTAINERS = 1                     # max concurrent requests

Add min_containers=1 to the @app.function decorator to keep a warm container and eliminate cold starts (~38s), at the cost of continuous GPU charges.

Troubleshooting

First deployment is slow

The first deploy builds the image and downloads ~4GB of model weights. This is cached — subsequent deploys take ~30 seconds.

Cold start latency (~38s)

The first request after a period of inactivity loads the model into GPU memory. Set min_containers=1 in main.py to keep a container warm.

Request timeout

For long scripts, increase the client timeout:

requests.post(url, data=data, timeout=600)

Stopping a running app

modal app list          # find your app ID
modal app stop <app-id>

Acknowledgments

  • Dia2 — The TTS model powering this API
  • Modal — Serverless GPU infrastructure
  • FastAPI — Web framework

Contributors

khariha

8 commits

Languages

Python

100.0%