llama.cpp 需 b9503(2026 年 6 月)或更新版本,MODEL=12b 需 b9512,旧版缺 Gemma 4 音频支持或加载 mmproj 崩溃;首次运行会自动下载约 5.7 GB 模型和 TTS 模型;默认端口 8000,需在浏览器授予摄像头和麦克风权限。
parlor
fikrikarimApache-2.0活跃维护在本地设备上跑实时多模态 AI 的 Python 项目,整合语音识别、语音合成和本地大模型,实现类似 GPT-Live 的实时对话体验,支持 Apple Silicon 和 MLX。
On-device, real-time multimodal AI with features similar to GPT-Live
git clone https://github.com/fikrikarim/parlor.git
cd parlor
curl -LsSf https://astral.sh/uv/install.sh | sh
brew install llama.cpp
uv sync
uv run parlormacOS 需 Apple Silicon,Linux 需受支持的 GPU;默认 E4B 模型约需 6 GB 空闲内存,e2b 约 4 GB,12b 约 8 GB。README 未说明具体 GPU 型号。
本地实时多模态语音助手,摄像头加麦克风对话,类似 GPT-Live 但全在设备上跑
在本地设备上跑实时多模态 AI 的 Python 项目,整合语音识别、语音合成和本地大模型,实现类似 GPT-Live 的实时对话体验,支持 Apple Silicon 和 MLX。
核心能力
- 浏览器端 Silero VAD 加 smart-turn-v3 判断是否说完
- Gemma 4 经 llama.cpp 同时听和看并流式回复
- 动作走独立语法约束 JSON 头,不混入语音
- 支持打断、定时器、实时翻译和只听模式
- 可选接入 OpenAI 兼容端点做后台联网研究
容易踩的坑
- v2.0.0最新正式版2026-08-02 · 2026-08-02 19:20 UTC
Parlor v2 is a near-complete rebuild of the pipeline toward one goal: GPT-Live-class conversation, fully on-device. Inference moved from LiteRT-LM to llama.cpp, turn-taking moved from LLM judgment to a dedicated audio classifier, and actions moved out of the speech stream into a grammar-forced JSON head — each decision measured before it was made (see
benchmarks/).New features
- Background research. "Find the best pizza in Rome right now" hands the task to a frontier model on any OpenAI-compatible endpoint (OpenRouter + web search by default) while the conversation continues; the answer is woven back in as a spoken delivery at the next idle moment. Off unless
REASONER_API_KEYis set — without it Parlor stays fully on-device. - Timers. "Set a timer for three minutes for the pasta" — the server owns the clock (a turn-based model can't ring into silence;
benchmarks/timerprobe.py), the model announces the ring in any mode, and a countdown chip with a cancel button tracks it. - Live translation mode. "Translate everything I say into English" turns Parlor into a consecutive interpreter: each utterance rendered after a short silence, no conversational replies, until you say "stop translating" or hit the stop chip.
- Just-listen mode. "Just listen for a while, I want to think out loud" makes it a silent scribe: every utterance transcribed on screen, nothing spoken back, until you address it again.
- A sense of time. The model is told how much quiet preceded a turn, how long research took, and when the session started — so "how long was I gone?" gets a real answer.
- Turn-taking you can see. While an utterance is held mid-thought, its bubble shows the classifier's confidence ("sounds unfinished (12%) — still listening").
Engine and architecture
- llama.cpp backend, replacing LiteRT-LM. Runs Google's official Gemma 4 QAT q4_0 GGUFs through a spawned llama-server and its prefix cache, which unlocks speech-overlap prefill: audio chunks and the camera frame are pushed through the cache while you're still talking, so long questions start answering almost as fast as short ones. Barge-in aborts generation server-side. Startup discovers both
llama-serverand the newer unifiedllamabinary, and enforces the build floor Gemma 4 audio needs (b9503+, b9512 forMODEL=12b) with install-method-aware upgrade hints. - Model switch.
MODEL=e2b|e4b|12bpicks the Gemma 4 size; the default is now E4B (noticeably better answers, ~1.8x E2B latency, ~6 GB RAM). - smart-turn-v3 turn detection, replacing LLM FINISHED/WAIT judgment.
benchmarks/turnbench.pyon labelled human speech: the 8M-parameter classifier scores 0.96 accuracy at 19ms; every Gemma variant is at or near chance while costing 0.6–3.6s. Incomplete utterances are held and merged into the next turn; a false hold is answered anyway after 2.5s of continued silence. - Decoupled JSON action head, replacing in-band control tags. The spoken reply is now pure speech; timers, mode switches, and research requests are decided by a second grammar-forced JSON request over the same prompt cache, hidden under TTS playback.
benchmarks/archbench.py: head recall 1.0 vs tags 0.955 — and the tags' miss is a spoken promise the server never keeps, the worst failure a voice assistant has. Markup leaking into TTS is now structurally impossible. Client and wire protocol unchanged. - Transcript-first streaming. The reply opens with a transcript of what was heard (leading beats trailing: WER 0.00 vs 0.39 on long utterances), shown on screen ~0.3s in, then is spoken sentence-by-sentence while still generating.
- Context. 32k window, with rotation driven by real
prompt_tokensfrom llama-server instead of an estimate, dropping whole exchanges — no more silent truncation-induced "forgetting".
Latency (Apple M3 Pro,
MODEL=e2b, end of utterance → first audio)Turn v1.0.0 v2.0.0 Short question (~2s speech) ~1. - Background research. "Find the best pizza in Rome right now" hands the task to a frontier model on any OpenAI-compatible endpoint (OpenRouter + web search by default) while the conversation continues; the answer is woven back in as a spoken delivery at the next idle moment. Off unless
- v1.0.02026-07-29 · 2026-07-29 06:59 UTC
查看更新说明
Initial release. This is a retroactive release, v1.0.0 shipped on April 7, 2026.
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Parlor
Fully on-device, real-time multimodal AI, with features similar to GPT-Live.
https://github.com/user-attachments/assets/0833b0ec-9e48-46dc-9fde-c3ac4a70abf9
Research preview. This is an early experiment. Expect rough edges and bugs.
Why?
I'm self-hosting a totally free voice AI on my home server to help people learn speaking English. It has hundreds of monthly active users, and I spent weeks improving it until it's almost on par with ChatGPT Advanced Voice.
Then, my heart broke when OpenAI released GPT-Live, which is miles ahead of ChatGPT Advanced Voice. GPT-Live is so good that I use it almost every day. It made my app feel obsolete. I either need to redirect my users to GPT-Live, or update the app to match it. I chose to try, with the additional constraint that it had to run 100% locally on my MacBook M3 Pro.
My first attempt was to fine-tune Gemma 4 12B to behave like a full-duplex model. Something like grafting a decision tick + speech head to the model. It failed after multiple trials. For now, I think a classic cascade system is still better. We just need to wait until a benevolent frontier AI company releases a full-duplex model that's on par with GPT-Live.
AI disclosure
This software is developed with strong assistance from Claude and with humans leading the ideas, testing, and debugging. We say this openly because it shaped how the project was built. If you are not happy with AI-developed code, this software is not for you. This disclosure is inspired by ds4.
Everything above this section is human-written, everything from here down is AI-generated.
How it works
Browser (mic + camera)
│
│ WebSocket (audio PCM + JPEG frames)
▼
FastAPI server
├── smart-turn-v3 (~20ms) → did you finish your thought?
├── Gemma 4 E4B via llama.cpp (QAT q4_0) → hears + sees, streams the reply
│ └── action head (same model, JSON) → timers · modes · research
├── Kokoro TTS (MLX on Mac, ONNX on Linux) → speaks sentence-by-sentence
└── background reasoner (optional) → frontier model for web research
│
│ WebSocket (transcript + streamed audio chunks)
▼
Browser (playback + transcript)
- Background research (optional). "Find the best pizza in Rome right now" hands the task to a frontier model on any OpenAI-compatible endpoint (OpenRouter + web search by default) while the conversation continues; the answer is spoken when it arrives. Off unless
REASONER_API_KEYis set — without it Parlor stays fully on-device. - Hands-free turn-taking. Browser-side Silero VAD with a ~200ms silence cutoff — no push-to-talk. Pipecat's smart-turn-v3 then judges whether you actually finished your thought: mid-thought pauses are held silently until you continue, or answered once you stay quiet.
- Everything streams. The reply opens with a transcript of what was heard (on screen immediately — committing to it first measurably improves accuracy), then is spoken sentence-by-sentence while still generating. Your camera frame and speech are pushed through llama.cpp's prompt cache while you're still talking, so even long questions start answering almost instantly.
- Barge-in. Speak over the AI to interrupt it; generation is aborted server-side. Echo is handled without the browser's echo canceller — or just wear headphones.
- Actions never ride the speech. Timers, mode switches, and research requests are decided by a separate grammar-forced JSON request over the same prompt cache, hidden under TTS playback. The spoken reply stays pure speech, so control markup can never leak into TTS and a promise made aloud is always kept (recall 1.0 vs 0.955 for in-band tags —
benchmarks/archbench.py). - Timers. "Set a timer for three minutes for the pasta" — the server owns the clock (a turn-based model can't ring into silence —
benchmarks/timerprobe.py), the model announces the ring out loud in any mode, and a countdown chip with a cancel button tracks it. - Live translation mode. "Translate everything I say into English" turns Parlor into a consecutive interpreter: each utterance rendered after a short silence, no conversational replies, in any language Gemma understands — until you say "stop translating" or hit the stop chip.
- Just-listen mode. "Just listen for a while, I want to think out loud" makes it a silent scribe: every utterance transcribed on screen, nothing spoken back, until you address it again.
- A sense of time. The model is told how much quiet preceded a turn, how long research took, and when the session started — so "how long was I gone?" gets a real answer.
Requirements
- Python 3.12+
- llama.cpp (
brew install llama.cppon macOS; other platforms: install guide). Needs build b9503 (June 2026) or newer, b9512 forMODEL=12b— older builds lack Gemma 4 audio or crash loading its mmproj - macOS with Apple Silicon, or Linux with a supported GPU
- ~6 GB free RAM for the default E4B model (
MODEL=e2bfits in ~4 GB)
Quick start
git clone https://github.com/fikrikarim/parlor.git
cd parlor
# Install uv and llama.cpp if you don't have them
curl -LsSf https://astral.sh/uv/install.sh | sh
brew install llama.cpp
uv sync
uv run parlor
Open http://localhost:8000, grant camera and microphone access, and start talking.
Models are downloaded automatically on first run (~5.7 GB for Gemma 4 E4B QAT + its multimodal projector, plus TTS models).
Configuration
Set these in your shell or a .env at the repo root. The common ones:
| Variable | Default | Description |
|---|---|---|
MODEL | e4b | Gemma 4 size: e2b (fastest), e4b (better answers, ~1.8x e2b latency), 12b (needs ~8GB) |
PORT | 8000 | Server port |
REASONER_API_KEY | (unset — research off) | API key enabling background research |
REASONER_BASE_URL | https://openrouter.ai/api/v1 | Any OpenAI-compatible chat endpoint |
REASONER_MODEL | openai/gpt-5.6-luna | Model the endpoint should run |
The full list — local model paths, llama.cpp tuning, TTS backend, test hooks — is in docs/configuration.md.
Performance (Apple M3 Pro)
Measured with MODEL=e2b from end of utterance to first audio heard (add ~200ms of VAD silence detection on top; the default E4B is roughly 1.8x these numbers, with noticeably better answers). The camera frame and the speech itself are prefilled while you're still speaking, and the reply opens with the transcript line (its decode time is included below — the price of accurate transcripts):
| Turn | First audio | Turn complete |
|---|---|---|
| Short question (~2s speech) | ~0.7s | ~1.3s |
| Short question + camera | ~0.8s | ~1.3s |
| Long question (~9s speech), streamed | ~1.3-1.4s | ~2.1-2.3s |
| Long question + camera | ~1.5s | ~2.3s |
Reproduce with the end-to-end benchmark (real spoken audio, synthesized locally). Run it before and after a change to see the impact:
uv run parlor # terminal 1
uv run python benchmarks/bench.py --label before --out benchmarks/results/before.json # terminal 2
# ...make changes, restart the server...
uv run python benchmarks/bench.py --label after --out benchmarks/results/after.json
uv run python benchmarks/compare.py benchmarks/results/before.json benchmarks/results/after.json
Testing
An end-to-end suite spawns the real server (llama.cpp, TTS, turn detector) and drives it over WebSocket with synthesized speech — including degraded audio (clipped word endings, noise, other voices) that reproduces live-mic failure modes:
uv run pytest # ~1 minute + model load
Set PARLOR_TEST_URL=ws://localhost:8000/ws to run it against an
already-running server. Browser-only behavior (echo at speaker volume, VAD
feel, multilingual speech) still needs a live mic and ears.
Project structure
src/parlor/
├── server.py # FastAPI app + per-connection conversation loop
├── llama.py # llama-server lifecycle + chat API client
├── pipeline.py # Streaming turn pipeline (decode → sentences → TTS)
├── actions.py # Action decider (grammar-forced JSON: timers, modes, research)
├── reasoner.py # Background research delegation (OpenAI-compatible)
├── modes.py # Session modes (conversation, translate, listen)
├── turn_detector.py # smart-turn-v3 end-of-turn classifier
├── tts.py # Platform-aware TTS (MLX on Mac, ONNX on Linux)
└── web/ # Frontend (markup, styles, app logic: VAD, camera, playback)
tests/ # End-to-end test suite (uv run pytest)
benchmarks/
├── bench.py # End-to-end latency benchmark
├── fixtures.py # Spoken-audio fixtures (synthesized locally)
├── compare.py # Diff two benchmark result files
├── turnbench.py # Turn-detection accuracy benchmark
├── archbench.py # In-band control tags vs decoupled action head
├── camerabench.py # Attach-every-turn vs camera-as-tool-call
└── timerprobe.py # Why the server owns the timer clock
Acknowledgments
- Gemma 4 by Google DeepMind
- llama.cpp by Georgi Gerganov and contributors
- Kokoro TTS by Hexgrad
- Silero VAD for browser voice activity detection
- smart-turn-v3 end-of-turn detection by Pipecat