Add OpenAI STT API with VAD and streaming server. - #341
Conversation
…le is 300~400MB with prompt response < 0.1 second.
LauraGPT
left a comment
There was a problem hiding this comment.
I built this exact head (742aacb) from a clean CMake build before reviewing. The server feature is useful, but the current PR is not safe to merge yet because its only end-to-end test silently skips the produced binary and several documented/API behaviors are not implemented correctly.
Reproduction from runtime/llama.cpp:
cmake -S . -B build-pr341-review -DCMAKE_BUILD_TYPE=Release
cmake --build build-pr341-review -j8
BIN_DIR="$PWD/build-pr341-review/bin" tests/run_server_smoke.sh
# SKIP server-smoke (.../sensevoice_server)
build-pr341-review/bin/sensevoice-server --max-conn
# unknown option --max-conn
Please address the inline blockers and add a CI-visible test that cannot turn a missing server binary into a successful run. I will re-run the clean build and protocol tests on the revised head.
Signed-off-by: LauraGPT <lauragpt@users.noreply.github.com>
All requested changes are addressed in 558bd67. The official llama.cpp runtime workflow passed the full build, unit tests, and server contract suite.
|
Maintainer follow-up: I pushed Verified on a clean Release build:
The server now enforces bounded connections/audio/session sizes, safely rejects malformed JSON, emits compliant SRT/VTT, reuses inference results, and has a non-skipping CI contract test. |
Summary
A sensevoice_server — OpenAI-compatible STT server (SenseVoiceSmall on ggml)
Streaming + file transcription over HTTP, backed by the SenseVoiceSmall ggml
runtime with built-in FSMN-VAD. One self-contained C++ binary, CPU-only, no
Python at runtime.
User impact
An OpenAI compatible STT streaming server for SenseVoiceSmall model.
Provides two OpenAI-compatible surfaces:
POST /v1/audio/transcriptions— transcribe an uploaded audio file(any format decodable by miniaudio → 16k mono) in
json/text/verbose_json/srt/vtt, or as an SSE stream./v1/realtime?intent=transcription— stream base64PCM16 chunks and receive incremental partial transcripts plus VAD
end-pointed final transcripts (OpenAI realtime transcription protocol).
Running it
-m, --modelSenseVoice GGUF (required)-vad, --vadFSMN-VAD GGUF — used for sentence/utterance end-pointing-ngl, --ngl <N>layers offloaded to CUDA GPU; any value > 0 runs the whole modelon device 0 (default
0= CPU). Requires a build with-DGGML_CUDA=ON; the serverfalls back to CPU when no CUDA device is present. The fbank frontend and FSMN-VAD
always stay on CPU.
--keep-tagskeep<|lang|>/<|emo|>/<|event|>meta tokens in text--threads <N>ggml compute threads (default 8)--partial-ms <N>WS partial-transcription cadence, ms (default 400)--vad-maxseg <ms>max single VAD segment (default 30000)--vad-slot-ms <ms>idle slot: when no audible chunk arrives within this window theVAD buffers are finalized and reset until new chunks come in (default 2000).
Prevents a connected-but-idle session (mic live, nobody talking) from keeping the
VAD busy re-scoring quiet chunks.
--read-timeout-s <N>socket read timeout, s (default 300)--web <dir>serve the directory at/(mic webui; openhttp://host:port/)host(default127.0.0.1) andport(default8040)Build
Same build as the CLI tools (fetches pinned llama.cpp; static, self-contained):
REST: file transcription
Upload any audio file (wav/mp3/flac/ogg/...). FSMN-VAD segments it internally;
each segment is transcribed and results are concatenated.
Other OpenAI-compatible endpoints:
GET /v1/models(model list) andGET /health(liveness).Web UI — live mic transcription
A zero-build browser UI (
webui/index.html) streams the microphone to therealtime endpoint and renders dictation live:
sensevoice_server -m model/sensevoice-small-q8.gguf -vad model/fsmn-vad.gguf \ --web runtime/llama.cpp/sensevoice-server/webui # open http://127.0.0.1:8040/encoded, and sent as
input_audio_buffer.appendchunks.place from
...transcription.deltamessages....transcription.completedmoves it to itsown line and a fresh interim line starts.
field lets you point the page at a server running elsewhere (no
--webrequired).
Realtime WebSocket: streaming transcription
Client protocol (OpenAI realtime transcription):
session.update(setturn_detection.type: "server_vad"or"none")input_audio_buffer.append—{"audio": "<base64 pcm16>"}(16 kHz mono)input_audio_buffer.commit— force-endpoint the current turninput_audio_buffer.clear— reset the session buffersession.createdinput_audio_buffer.speech_started/stopped(VAD turn detection)conversation.item.input_audio_transcription.delta— incremental partialsinput_audio_buffer.committed,conversation.item.createdconversation.item.input_audio_transcription.completed— finaltranscriptThe audio buffer is 16 kHz mono PCM16; base64-encode every chunk and stream
it via
input_audio_buffer.append. FSMN-VAD runs incrementally on each chunk:it end-points utterances, emits
speech_started/speech_stopped, and streamshypothesis deltas (throttled by
--partial-ms) while speech is open. On segmentfinalization the full transcript arrives in
...transcription.completed.Idle CPU: VAD only scores frames when new audio actually arrives. Chunks with no
real signal are written straight into the score cache as silence votes without
running the NN, and once
--vad-slot-mselapses with no audible chunk the openVAD state and session buffer are reset, so a connected-but-quiet client parks at
~0% CPU until the next speech burst.
Try it with the bundled client (zero-dependency, stdlib socket RFC6455):
Implementation notes
duplicated verbatim from the validated
funasr-sensevoiceCLI so the CLI andits goldens stay untouched.
a faithful copy of the
funasr_vad.h/ E2EVadModel state machine — segmentboundaries match the whole-file VAD tool (e.g.
770 5980onsample.wav).DataSink).Long-lived WebSockets run on their own detached thread per connection
(
PerThreadTaskQueue), not the fixed pool.count / bit depth).
Tests
# end-to-end: REST json/text/verbose_json/SSE + WS streaming vs the golden transcript MODEL_GGUF=model/sensevoice-small-q8.gguf VAD_GGUF=model/fsmn-vad.gguf \ runtime/llama.cpp/tests/run_server_smoke.shtests/stream_client.pyis the WS test client;tests/run_regression.shalsoruns the server smoke when the binary and models are present.
Model, API, and runtime impact
Validation
Screenshots, logs, or transcripts
Add command output, before/after transcripts, emotion/event tags, screenshots, or logs when they help reviewers understand the change.
Notes for reviewers
Mention known limitations, skipped optional dependencies, or follow-up work.