Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions examples/run_voice_clone_service.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -euo pipefail

usage() {
cat <<'EOF'
Usage: run_voice_clone_service.sh [options] [-- extra-args]

Options:
-g, --gpu ID GPU id to use (default: 4)
-p, --port PORT Port to bind (default: 8001)
-m, --model MODEL Model id or path (default: Qwen/Qwen3-TTS-12Hz-1.7B-Base)
-s, --voice-store DIR Voice store directory (default: <repo>/voice_store)
-d, --device DEVICE Device for device_map (default: cuda:0)
--ip IP Bind IP (default: 0.0.0.0)
-h, --help Show this help

Env overrides: GPU_ID, PORT, MODEL, VOICE_STORE, DEVICE, IP, PYTHON_BIN
EOF
}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

GPU_ID="${GPU_ID:-4}"
PORT="${PORT:-8001}"
MODEL="${MODEL:-Qwen/Qwen3-TTS-12Hz-1.7B-Base}"
VOICE_STORE="${VOICE_STORE:-${PROJECT_ROOT}/voice_store}"
DEVICE="${DEVICE:-cuda:0}"
IP="${IP:-0.0.0.0}"
PYTHON_BIN="${PYTHON_BIN:-python3}"

EXTRA_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-g|--gpu)
GPU_ID="$2"
shift 2
;;
-p|--port)
PORT="$2"
shift 2
;;
-m|--model)
MODEL="$2"
shift 2
;;
-s|--voice-store)
VOICE_STORE="$2"
shift 2
;;
-d|--device)
DEVICE="$2"
shift 2
;;
--ip)
IP="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
--)
shift
EXTRA_ARGS+=("$@")
break
;;
*)
EXTRA_ARGS+=("$1")
shift
;;
esac
done

mkdir -p "$VOICE_STORE"

CUDA_VISIBLE_DEVICES="$GPU_ID" "$PYTHON_BIN" \
"$SCRIPT_DIR/voice_clone_service.py" \
--device "$DEVICE" \
--ip "$IP" \
--port "$PORT" \
--model "$MODEL" \
--voice-store "$VOICE_STORE" \
"${EXTRA_ARGS[@]}"
84 changes: 84 additions & 0 deletions examples/run_voice_clone_stream_service.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -euo pipefail

usage() {
cat <<'EOF'
Usage: run_voice_clone_stream_service.sh [options] [-- extra-args]

Options:
-g, --gpu ID GPU id to use (default: 4)
-p, --port PORT Port to bind (default: 8002)
-m, --model MODEL Model id or path (default: Qwen/Qwen3-TTS-12Hz-1.7B-Base)
-s, --voice-store DIR Voice store directory (default: <repo>/voice_store)
-d, --device DEVICE Device for device_map (default: cuda:0)
--ip IP Bind IP (default: 0.0.0.0)
-h, --help Show this help

Env overrides: GPU_ID, PORT, MODEL, VOICE_STORE, DEVICE, IP, PYTHON_BIN
EOF
}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"

GPU_ID="${GPU_ID:-5}"
PORT="${PORT:-8002}"
MODEL="${MODEL:-Qwen/Qwen3-TTS-12Hz-1.7B-Base}"
VOICE_STORE="${VOICE_STORE:-${PROJECT_ROOT}/voice_store}"
DEVICE="${DEVICE:-cuda:0}"
IP="${IP:-0.0.0.0}"
PYTHON_BIN="${PYTHON_BIN:-python3}"

EXTRA_ARGS=()
while [[ $# -gt 0 ]]; do
case "$1" in
-g|--gpu)
GPU_ID="$2"
shift 2
;;
-p|--port)
PORT="$2"
shift 2
;;
-m|--model)
MODEL="$2"
shift 2
;;
-s|--voice-store)
VOICE_STORE="$2"
shift 2
;;
-d|--device)
DEVICE="$2"
shift 2
;;
--ip)
IP="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
--)
shift
EXTRA_ARGS+=("$@")
break
;;
*)
EXTRA_ARGS+=("$1")
shift
;;
esac
done

mkdir -p "$VOICE_STORE"

CUDA_VISIBLE_DEVICES="$GPU_ID" "$PYTHON_BIN" \
"$SCRIPT_DIR/streaming/voice_clone_stream_service.py" \
--device "$DEVICE" \
--ip "$IP" \
--port "$PORT" \
--model "$MODEL" \
--voice-store "$VOICE_STORE" \
"${EXTRA_ARGS[@]}"
72 changes: 72 additions & 0 deletions examples/streaming/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Voice Clone Streaming Service (SSE)

# Streaming voice cloning service

This service streams voice-clone audio chunks as Server-Sent Events (SSE) or binary PCM.

Files live under `examples/streaming/`.

Constraints
- Single-sample only (one `text`, one `voice_id` per request).
- Base 12Hz models only (e.g. `Qwen/Qwen3-TTS-12Hz-1.7B-Base`).
- SSE payload is base64-encoded `int16` PCM audio.

Start
```bash
./Qwen3-TTS/examples/run_voice_clone_stream_service.sh
```

Test page
```text
http://localhost:8002/
```

Request example
```bash
curl -N -X POST \
-H "Content-Type: application/json" \
-d '{
"voice_id": "YOUR_VOICE_ID",
"text": "Hello, this is streaming TTS.",
"chunk_size": 8,
"left_context_size": 25
}' \
http://localhost:8002/voice/stream
```

Binary PCM stream
```text
POST /voice/stream_pcm

Binary protocol:
- 12-byte header:
- magic: "QTS1" (4 bytes)
- sample_rate: uint32 LE
- sample_width_bytes: uint16 LE (2 for int16)
- channels: uint16 LE (1)
- Then repeated frames:
- frame_len_bytes: uint32 LE
- raw PCM int16 bytes
- End of stream: frame_len_bytes = 0
```

Model switching
```bash
curl -X POST \
-H "Content-Type: application/json" \
-d '{"model_id":"Qwen/Qwen3-TTS-12Hz-0.6B-Base","safe_mode":true}' \
http://localhost:8002/model/load
```

Available models from the voice store
```bash
curl http://localhost:8002/models
```

Notes
- Lower `chunk_size` yields faster first audio but more overhead.
- `left_context_size` improves continuity at chunk boundaries.
- FlashAttention is enabled by default; use `--no-flash-attn` to disable.
- TF32 matmul/cudnn is enabled by default on CUDA; use `--no-tf32` to disable.
- Model switching returns HTTP 409 if a stream is active.
- Safe mode disables sub-talker sampling for stability.
Loading