Skip to content
Merged
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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@ Notable changes to this project will be documented in this file.
Per-PR attribution and contributor credits are published automatically on the corresponding GitHub release page; this file is the curated, human-readable summary.

## [v0.6.0] - Unreleased
### Breaking changes
- `POST /dev/unload` is off by default; set `ALLOW_DEV_UNLOAD=true` to enable, otherwise returns 403. Shipped open in v0.5.0, now opt-in (#483).
- `/debug/*` routes also set off by default, continuation of above; to avoid unintentional exposure of internals (stack traces, temp storage, CPU/mem/GPU); set `ENABLE_DEBUG_ENDPOINTS=true` to enable, otherwise 403's.
- Removed lingering deprecated `/debug/session_pools`

### Changed
- Documented API stability: `/v1/*` is the stable surface; `/dev/*` and `/debug/*` are operational helpers that may change or move behind flags between minor releases.

### Fixed
- OpenAI voice aliases pointed at legacy v0.19 voicepacks that sound degraded on the v1.0 model. Added the proper v1.0 `bf_isabella` and repointed `nova` (`bf_v0isabella` -> `bf_isabella`), `alloy`, `ash`, `coral`, `echo` to their v1.0 voices. The `v0*` voices stay available by explicit name. (#479)
- `/dev/captioned_speech` returned `timestamps: null` for non-English espeak voices (es/fr/it/hi/pt). Word timestamps are now derived from the model's own phoneme durations (`pred_dur`), so they match the audio exactly; falls back to the old behavior when word counts can't be reconciled. English path unchanged, ja/zh keep previous behavior. (#484)

## [v0.5.0] - 2026-06-06
### Added
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
</p>

# <sub><sub>_`FastKoko`_ </sub></sub>
[![Changelog](https://img.shields.io/badge/changelog-white)](./CHANGELOG.md) [![Tests](https://img.shields.io/badge/tests-81-darkgreen)]()
[![Coverage](https://img.shields.io/badge/coverage-52%25-tan)]()
[![Changelog](https://img.shields.io/badge/changelog-white)](./CHANGELOG.md) [![Tests](https://img.shields.io/badge/tests-100-darkgreen)]()
[![Coverage](https://img.shields.io/badge/coverage-58%25-tan)]()

[![Kokoro](https://img.shields.io/badge/kokoro-0.9.4-BB5420)](https://github.com/hexgrad/kokoro)
[![Misaki](https://img.shields.io/badge/misaki-0.9.4-B8860B)](https://github.com/hexgrad/misaki)
[![Tested at Model Commit](https://img.shields.io/badge/model-1.0::9901c2b-blue)](https://huggingface.co/hexgrad/Kokoro-82M/commit/9901c2b79161b6e898b7ea857ae5298f47b8b0d6)

[![Try on Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Try%20on-Spaces-blue)](https://huggingface.co/spaces/Remsky/FastKoko) [![Downloads](https://img.shields.io/badge/downloads-1.4M%2B-2496ED?logo=docker&logoColor=white)](https://github.com/remsky?tab=packages&repo_name=Kokoro-FastAPI)
[![Try on Spaces](https://img.shields.io/badge/%F0%9F%A4%97%20Try%20on-Spaces-blue)](https://huggingface.co/spaces/Remsky/FastKoko) [![Downloads](https://img.shields.io/badge/downloads-1.8M%2B-2496ED?logo=docker&logoColor=white)](https://github.com/remsky?tab=packages&repo_name=Kokoro-FastAPI)



Expand Down Expand Up @@ -582,14 +582,16 @@ The city of [Worcester](/wˈʊstər/) is easy. [pause:1s] See?
<details>
<summary>Debug Endpoints</summary>

Monitor system state and resource usage with these endpoints:
Monitor system state and resource usage with these endpoints. The `/debug/*` routes expose host and process internals, so they are off by default; set `ENABLE_DEBUG_ENDPOINTS=true` to enable.

- `/debug/threads` - Get thread information and stack traces
- `/debug/storage` - Monitor temp file and output directory usage
- `/debug/system` - Get system information (CPU, memory, GPU)
- `POST /dev/unload` - Release model from VRAM; reloads lazily on next request
- `POST /dev/unload` - Release model from VRAM; reloads lazily on next request. Off by default; set `ALLOW_DEV_UNLOAD=true` to enable

Useful for debugging resource exhaustion or performance issues.

Stability: the `/v1/*` OpenAI-compatible routes are the stable API. `/dev/*` and `/debug/*` are operational helpers, and may change or move behind flags between minor releases.
</details>

<details>
Expand Down
3 changes: 3 additions & 0 deletions api/src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class Settings(BaseSettings):
False # Whether to allow saving combined voices locally
)
allow_dev_unload: bool = False # Whether to expose the POST /dev/unload endpoint
enable_debug_endpoints: bool = (
False # Whether to expose /debug/* host and process introspection routes
)

# Container absolute paths
model_dir: str = "/app/api/src/models" # Absolute path in container
Expand Down
2 changes: 1 addition & 1 deletion api/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ async def lifespan(app: FastAPI):
# Include routers
app.include_router(openai_router, prefix="/v1")
app.include_router(dev_router) # Development endpoints
app.include_router(debug_router) # Debug endpoints
app.include_router(debug_router) # Debug endpoints (403 unless enabled)
if settings.enable_web_player:
app.include_router(web_router, prefix="/web") # Web player static files

Expand Down
74 changes: 13 additions & 61 deletions api/src/routers/debug.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import threading
import time
from datetime import datetime

import psutil
import torch
from fastapi import APIRouter
from fastapi import APIRouter, Depends, HTTPException

from ..core.config import settings

try:
import GPUtil
Expand All @@ -13,7 +14,16 @@
except ImportError:
GPU_AVAILABLE = False

router = APIRouter(tags=["debug"])

async def _require_debug_enabled():
if not settings.enable_debug_endpoints:
raise HTTPException(
status_code=403,
detail={"error": "Debug endpoints are disabled"},
)


router = APIRouter(tags=["debug"], dependencies=[Depends(_require_debug_enabled)])


@router.get("/debug/threads")
Expand Down Expand Up @@ -149,61 +159,3 @@ async def get_system_info():
"network": network_info,
"gpu": gpu_info,
}


@router.get("/debug/session_pools")
async def get_session_pool_info():
"""Get information about ONNX session pools."""
from ..inference.model_manager import get_manager

manager = await get_manager()
pools = manager._session_pools
current_time = time.time()

pool_info = {}

# Get CPU pool info
if "onnx_cpu" in pools:
cpu_pool = pools["onnx_cpu"]
pool_info["cpu"] = {
"active_sessions": len(cpu_pool._sessions),
"max_sessions": cpu_pool._max_size,
"sessions": [
{"model": path, "age_seconds": current_time - info.last_used}
for path, info in cpu_pool._sessions.items()
],
}

# Get GPU pool info
if "onnx_gpu" in pools:
gpu_pool = pools["onnx_gpu"]
pool_info["gpu"] = {
"active_sessions": len(gpu_pool._sessions),
"max_streams": gpu_pool._max_size,
"available_streams": len(gpu_pool._available_streams),
"sessions": [
{
"model": path,
"age_seconds": current_time - info.last_used,
"stream_id": info.stream_id,
}
for path, info in gpu_pool._sessions.items()
],
}

# Add GPU memory info if available
if GPU_AVAILABLE:
try:
gpus = GPUtil.getGPUs()
if gpus:
gpu = gpus[0] # Assume first GPU
pool_info["gpu"]["memory"] = {
"total_mb": gpu.memoryTotal,
"used_mb": gpu.memoryUsed,
"free_mb": gpu.memoryFree,
"percent_used": (gpu.memoryUsed / gpu.memoryTotal) * 100,
}
except Exception:
pass

return pool_info
26 changes: 26 additions & 0 deletions api/tests/test_debug_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Tests for the /debug/* opt-in gate."""

from unittest.mock import patch

from fastapi.testclient import TestClient

from api.src.core.config import settings
from api.src.main import app

client = TestClient(app)


def test_debug_endpoints_403_when_disabled():
"""Disabled by default: every /debug/* route returns 403."""
for path in ["/debug/threads", "/debug/storage", "/debug/system"]:
response = client.get(path)
assert response.status_code == 403
assert response.json()["detail"]["error"] == "Debug endpoints are disabled"


def test_debug_endpoints_200_when_enabled():
with patch.object(settings, "enable_debug_endpoints", True):
response = client.get("/debug/threads")

assert response.status_code == 200
assert "total_threads" in response.json()
4 changes: 3 additions & 1 deletion docker/cpu/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ services:
- ONNX_ARENA_EXTEND_STRATEGY=kNextPowerOfTwo
- API_LOG_LEVEL=DEBUG
- DOWNLOAD_MODEL=true

# - ALLOW_DEV_UNLOAD=true
# - ENABLE_DEBUG_ENDPOINTS=true

# # Gradio UI service [Comment out everything below if you don't need it]
# gradio-ui:
# image: ghcr.io/remsky/kokoro-fastapi-ui:v${VERSION}
Expand Down
2 changes: 2 additions & 0 deletions docker/gpu/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ services:
- PYTHONUNBUFFERED=1
- API_LOG_LEVEL=DEBUG
- DOWNLOAD_MODEL=true
# - ALLOW_DEV_UNLOAD=true
# - ENABLE_DEBUG_ENDPOINTS=true
deploy:
resources:
reservations:
Expand Down
2 changes: 2 additions & 0 deletions docker/rocm/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ services:
- 8880:8880
environment:
- USE_GPU=true
# - ALLOW_DEV_UNLOAD=true
# - ENABLE_DEBUG_ENDPOINTS=true
- TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1
# MIOPEN_FIND_MODE=2 is set by default in the Dockerfile. It reuses the
# on-disk find DB and skips the expensive per-process kernel search.
Expand Down
Loading