Moteur d'inférence Rust pur sur Apple Silicon : kernels metal-rs bruts,
zéro Python, zéro dépendance à MLX ou CoreML. LLM, STT et TTS dans un seul
binaire, utilisables en bibliothèque ou derrière le serveur HTTP
OpenAI-compatible saragossa serve. Né comme le moteur du projet reti (agent
vocal local), il s'utilise seul.
Dans le paysage des backends d'inférence (Ollama, llama.cpp, vLLM, SGLang…), saragossa occupe le quadrant latence mono-utilisateur × Apple Silicon :
- vLLM/SGLang/TGI/LMDeploy exigent un GPU NVIDIA ; ici tout est Metal natif.
- Ollama et llama.cpp sont multi-plateformes généralistes ; saragossa est
optimisé pour UNE cible (GPU Apple Silicon, decode résident) et dépasse
mlx_lmsur les MoE à quantification comparable. - Optimisé latence locale (agent, copilote, boucle vocale), pas throughput multi-tenant : le serveur est mono-thread par choix.
- Cache chaud de préfixe par blocs avec snapshots GPU (même famille d'idées que le RadixAttention de SGLang) : le multi-turn ne repaye pas son historique.
| Domaine | Détail |
|---|---|
| LLM | Qwen3.x dense et MoE (27B/30B/35B-A3B), Gemma 4 dense (gemma4_unified) et MoE (gemma4), loader générique Llama/Mistral/Gemma 3 ; quantifs u4/u6/u8 gs32-128, scales/biases bf16 |
| STT | Whisper large-v3-turbo (encodeur + décodeur résidents, GEMM Neural-Accelerators bf16) |
| TTS | Qwen3-TTS : talker résident + codec GPU + streaming intra-phrase |
| Embeddings | e5-small pur Rust (CPU), pour la mémoire sémantique / le RAG |
| Serveur | HTTP multi-modèle mono-thread, pool LRU, cache chaud, garde OOM |
| Endpoint | Rôle |
|---|---|
GET /v1/models |
Modèles servis |
POST /v1/chat/completions |
Chat OpenAI-compatible (SSE ou non), response_format: {"type":"json_object"} |
POST /v1/messages |
Shim Anthropic Messages + tool_use (pilotable par Claude Code) |
POST /v1/audio/transcriptions |
STT Whisper (multipart WAV, opt-in --stt-model) |
POST /v1/audio/speech |
TTS Qwen3 (JSON → WAV, opt-in --tts-model) |
POST /v1/embeddings |
Embeddings e5-small (opt-in --embed-model) |
- Résidence GPU : en decode, 1 token = 1 command buffer, zéro readback ni
commit_and_waitpar couche. Le per-op CPU-orchestré n'existe qu'en repli. - Byte-identité comme gate : toute optimisation prouve qu'elle préserve la sortie (oracles md5 e2e, goldens STT/TTS) ; les dérives near-tie sont qualifiées (ids + top-5 + marge) et actées — jamais silencieuses.
- Tout est débrayable : chaque chemin optimisé a son kill-switch env ;
les flags sont centralisés dans
src/runtime_flags.rs.
- Decode 35B-A3B greedy @1k : 145,7 tok/s (
4bit, défaut prod) · 105,9 tok/s (oQ8) ; chemin prod T>0 devantmlx_lmjusqu'à 32k (89,8 vs 88,1 tok/s @32k oQ8, KV bf16). - Prefill 35B : 1,0 s @2k · 3,5 s @8k · 23,3 s @32k.
- STT Whisper turbo : rtf 0,107 · TTS : e2e 0,747, TTFA streaming ~1,2 s.
Ces chiffres valent pour leur contexte (matériel, modèle, longueur) — mesurez sur votre machine avant de figer un choix.
# Lance un chat interactif et télécharge le modèle HF s'il manque.
cargo run --release -p saragossa -- run mlx-community/Qwen3-4B-4bit
# Affiche les modèles déjà présents dans le cache Hugging Face local.
cargo run --release -p saragossa -- listPour les modèles gated (Gemma, par exemple), acceptez d'abord la licence sur la
page Hugging Face du modèle puis exportez HF_TOKEN.
# CLI de dev (le binaire requiert la feature devtools, activée par défaut) :
# génération LLM directe.
cargo run --release -p saragossa -- \
--model-dir models/Qwen3.6-35B-A3B-oQ8 --backend metal \
--prompt "Bonjour" --max-tokens 64 --temperature 0 --metricsEn bibliothèque, les points d'entrée sont qwen_loader (LLM), whisper (STT),
tts (TTS) et text_embedder (embeddings).
Serveur HTTP local mono-thread, multi-modèle. Le comportement par défaut
reste mono-utilisateur : l'état chaud est global au modèle tant qu'aucune clé de
session n'est fournie. Transport socket Unix par défaut
(/tmp/saragossa-serve.sock, chmod 0600) ; le TCP loopback exige un bearer
(--api-key ou SARAGOSSA_API_KEY). Deadline de lecture par connexion (30 s)
et plafond dur max_tokens (4096) débrayent les requêtes qui dérapent.
# OpenAI-compatible sur socket Unix.
cargo run --release -p saragossa -- serve \
--model qwen35=models/Qwen3.6-35B-A3B-oQ8
# TCP loopback + bearer, pour brancher Claude Code (shim Anthropic).
SARAGOSSA_API_KEY=local-dev cargo run --release -p saragossa -- serve \
--port 8081 --model qwen35=models/Qwen3.6-35B-A3B-oQ8# Claude Code parle au moteur local via /v1/messages.
ANTHROPIC_BASE_URL=http://127.0.0.1:8081 ANTHROPIC_API_KEY=local-dev claudePOST /v1/chat/completions accepte response_format: {"type":"json_object"}.
La sortie est contrainte par un automate JSON byte-level côté sampler : objet
racine obligatoire, chaînes/échappements/nombres/booléens/null et structures
imbriquées. L'EOT n'est admissible qu'après fermeture de l'objet racine.
En v1, seules ces requêtes guidées basculent sur le chemin de sampling CPU
(logits relus puis masqués avant sample). Les requêtes sans response_format,
ou avec {"type":"text"}, gardent le chemin résident/GPU existant. Le mode
{"type":"json_schema"} répond 501 : il est réservé à une version ultérieure.
Limites v1 : l'automate garantit la grammaire JSON (paires de surrogates
𐀀 incluses) mais pas la magnitude d'un nombre au-delà de f64 ;
en non-stream, un objet non fermé au budget max_tokens remonte une erreur
plutôt que du JSON tronqué ; en SSE, les deltas restent un préfixe JSON valide
et un objet non fermé se termine par un événement error de type
incomplete_json, sans [DONE] normal.
Les prompts sont découpés en blocs de 256 tokens (RETI_SERVE_PREFIX_BLOCK_TOKENS)
hachés en chaîne (SHA-256 de hash_précédent ‖ tokens) : un préfixe ne réutilise
un état que si toute la chaîne amont est identique. Chaque frontière de bloc
retient l'état de prompt CPU et son snapshot Metal (KV + état récurrent
linéaire GDN résident sur GPU), rechargé tel quel sur hit — donc seul le suffixe
est prérempli. Le cache est un LRU de 128 blocs (RETI_SERVE_PREFIX_CACHE_BLOCKS) ;
le header x-saragossa-reused-prefix-tokens rapporte la reprise.
Pour un frontal multi-utilisateur, envoyez x-saragossa-session: <id> sur
/v1/chat/completions ; à défaut, le champ OpenAI user sert de clé de session.
Sur /v1/messages, le shim Anthropic utilise le même header puis
metadata.user_id en repli. La clé dérive la racine du chaînage : deux sessions
distinctes ne partagent pas de blocs, et x-saragossa-reused-prefix-tokens ne
rapporte alors que la reprise intra-session. Sans clé, la racine historique
[0; 32] et le namespace global restent inchangés.
Cette isolation est un cloisonnement de cache, pas une frontière
d'authentification. Sur la socket Unix par défaut, sans bearer, tout appelant
autorisé par les permissions du fichier peut revendiquer n'importe quel
session-id. Pour une vraie frontière multi-tenant, authentifiez en amont
(gateway frontal grob) ou utilisez le bearer TCP de saragossa serve.
Le cap RETI_SERVE_PREFIX_BLOCKS_PER_SESSION limite en plus les évictions
intra-session sans changer le défaut mono-utilisateur.
- Garde OOM prédictive : projette l'empreinte process (
phys_footprintMach) plus le coût de la prochaine allocation contre le plus bas de trois plafonds — cap statique, mémoire hôte moins marge (2 Gio), working-set Metal recommandé. En cas de dépassement projeté, évince d'abord des blocs de cache, puis des modèles ; sinon refuse la requête (HTTP 503). - Pool de modèles LRU : jusqu'à 2 modèles résidents simultanés
(
RETI_SERVE_MODEL_POOL) ; charger un modèle de plus évince le moins récemment utilisé.
| Variable | Défaut | Rôle |
|---|---|---|
RETI_SERVE_PREFIX_CACHE |
on | Cache chaud de préfixe par blocs |
RETI_SERVE_PREFIX_BLOCK_TOKENS |
256 | Taille d'un bloc (tokens) |
RETI_SERVE_PREFIX_CACHE_BLOCKS |
128 | Capacité LRU du cache (blocs) |
RETI_SERVE_PREFIX_BLOCKS_PER_SESSION |
128 | Capacité LRU par session |
RETI_SERVE_LRU |
on | Pool LRU de modèles résidents |
RETI_SERVE_MODEL_POOL |
2 | Modèles résidents simultanés |
RETI_SERVE_OOM_GUARD |
on | Garde mémoire prédictive |
RETI_SERVE_MEMORY_HEADROOM_BYTES |
2 Gio | Marge hôte conservée hors process |
RETI_SERVE_MEMORY_CAP_BYTES |
auto | Plafond mémoire statique explicite |
| Feature | Défaut | Rôle |
|---|---|---|
metal |
oui | Kernels GPU Metal (macOS ; src/kernels.metal embarqué au build, compilé au runtime) |
devtools |
oui (lib) / requis (bin) | Harnais bench/diagnostic (DFlash, MTP, doctor) — exclu des binaires de prod |
Prérequis : Metal Toolchain (xcodebuild -downloadComponent MetalToolchain).
Double licence, au choix : MIT ou
Apache-2.0 (SPDX MIT OR Apache-2.0).