unsloth/studio/backend/stream_token_store.py
Daniel Han e945f43652 Add dedicated streaming server for fast GGUF SSE inference
Adds a separate streaming server (Option B) that bypasses the main
app's asyncio event loop for GGUF token streaming.

When UNSLOTH_FAST_SSE=1 or UNSLOTH_STREAM_SERVER=1 is set, a
lightweight FastAPI app starts in a daemon thread on a random port.
The frontend negotiates a one-time token via /api/inference/stream-url
and streams directly from this server, falling back to the baseline
/v1/chat/completions transparently on failure.

Architecture:
- streaming_server.py: standalone FastAPI app with three paths:
  - Path A (hot): async httpx.AsyncClient streaming direct to
    llama-server. No cumulative-to-delta conversion needed since
    llama-server sends delta tokens natively via OpenAI SSE.
  - Path B: tool calling via asyncio.to_thread (tool execution
    is the bottleneck, not the streaming proxy).
  - Path C: non-streaming one-shot JSON response.
- stream_token_store.py: thread-safe one-time token store (10s TTL)
  shared between the main app and streaming server.
- routes/inference.py: /stream-url endpoint for token issuance,
  /direct-stream for Option C, /internal/consume-stream-token for
  future subprocess mode.
- llama_cpp.py: --api-key support so the streaming server can
  authenticate to llama-server directly.
- chat-api.ts: transparent fast-path negotiation with silent fallback.

Handles: vision (image_url + legacy image_base64), thinking models
(reasoning_content -> <think> tags), CORS preflight, friendly error
messages, stream_options usage/timings passthrough.

Only sends repeat_penalty when the client explicitly provides it,
avoiding the 24% TPS penalty from repetition scanning.
2026-03-27 02:03:30 +00:00

65 lines
2 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""
Thread-safe one-time token store for Option B (separate streaming server).
Tokens are short-lived (10 seconds) and consumed on first use.
"""
import threading
import time
import uuid
from typing import Optional
class StreamTokenStore:
"""Thread-safe store for short-lived, one-time-use streaming tokens."""
def __init__(self, ttl_seconds: float = 10.0) -> None:
self._ttl = ttl_seconds
self._lock = threading.Lock()
# token -> {"username": str, "expires": float}
self._tokens: dict[str, dict] = {}
def create_token(self, username: str) -> str:
"""Create a new one-time token for the given user. Returns the token string."""
token = uuid.uuid4().hex
expires = time.monotonic() + self._ttl
with self._lock:
self._purge_expired()
self._tokens[token] = {"username": username, "expires": expires}
return token
def consume_token(self, token: str) -> Optional[str]:
"""
Validate and consume a token. Returns the username if valid, None otherwise.
The token is deleted after consumption (one-time use).
"""
with self._lock:
self._purge_expired()
entry = self._tokens.pop(token, None)
if entry is None:
return None
if time.monotonic() > entry["expires"]:
return None
return entry["username"]
def _purge_expired(self) -> None:
"""Remove expired tokens. Must be called while holding _lock."""
now = time.monotonic()
expired = [k for k, v in self._tokens.items() if now > v["expires"]]
for k in expired:
del self._tokens[k]
# Module-level singleton
_store = StreamTokenStore()
def create_stream_token(username: str) -> str:
return _store.create_token(username)
def consume_stream_token(token: str) -> Optional[str]:
return _store.consume_token(token)