# Paraformer on llama.cpp / GGUF Run **Paraformer** (the non-autoregressive ASR model) on the [llama.cpp](https://github.com/ggml-org/llama.cpp) / ggml stack — **CPU, edge, a single binary, no Python at runtime**. Like whisper.cpp, but for Paraformer. ## Why this exists Paraformer normally runs on PyTorch / ONNX. This runtime ports it to **ggml + GGUF** so it runs CPU-only, offline, embedded in a C/C++ app, with quantized weights — laptops, phones, edge boxes, no GPU and no Python. (For high-QPS GPU serving, the PyTorch path is still the way.) ## Architecture Paraformer is **non-autoregressive**: it predicts all output tokens in one pass. ``` audio.wav (16k mono) │ kaldi 80-mel fbank + LFR + CMVN (C++) ▼ features [T, 560] │ SANM encoder (50 layers: LN + fused QKV + FSMN + FFN) (ggml) ▼ encoder_out [T, 512] │ CIF predictor: conv1d → sigmoid → α; integrate-and-fire (host) ▼ acoustic embeds [N_tok, 512] (N_tok = number of output tokens) │ SANM decoder (16 layers: FFN → FSMN self-attn → cross-attn to encoder) (ggml) ▼ logits [N_tok, vocab] → argmax → token ids → text ``` CIF (Continuous Integrate-and-Fire) walks the encoder output accumulating a predicted "weight" α per frame; each time the running sum crosses 1.0 it emits one acoustic token. This both decides the token count and produces the acoustic embeddings the decoder consumes. The SANM encoder/FSMN/attention primitives are shared with the Fun-ASR-Nano and SenseVoice runtimes. ## Quickstart **1. Build:** ```bash git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp cp -r /path/to/runtime/llama.cpp/funasr-paraformer examples/ echo 'add_subdirectory(funasr-paraformer)' >> examples/CMakeLists.txt cmake -B build -DGGML_NATIVE=ON -DLLAMA_CURL=OFF cmake --build build -j --target llama-funasr-paraformer ``` **2. Convert weights** (needs the checkpoint, e.g. `funasr/paraformer-zh`): ```bash python runtime/llama.cpp/export_paraformer_gguf.py \ --model_pt /model.pt --mvn /am.mvn \ --out paraformer.gguf # f32, ~863 MB python runtime/llama.cpp/export_paraformer_gguf.py --wtype f16 \ --model_pt /model.pt --mvn /am.mvn \ --out paraformer-f16.gguf # half size ``` **3. Transcribe:** ```bash build/bin/llama-funasr-paraformer -m paraformer.gguf -a audio.wav # prints transcription text (--ids for raw) ``` Expected output: ``` 我想问我在滨海新区有房我一直没有照顾孩子...你觉得这是正常的想法吗 [paraformer] T=742 N_tok=105 enc 1.24s dec 0.48s ``` ## Accuracy & validation - Decoded text is **character-for-character identical** to the FunASR `AutoModel` output on a benchmark clip; the CIF token count matches exactly (105/105). - Stage-by-stage vs PyTorch: encoder cosine 0.997, acoustic embeds cosine 0.993 (the small residual is the reference frontend's random `dither=1.0`; the C++ front end is deterministic, dither=0). - Encode ≈ 1.2 s + decode ≈ 0.5 s on CPU for a 44 s clip. ## Tips & gotchas - **CMVN IS applied** (unlike SenseVoice): `(fbank + shift) * scale`, per-dim (560), from `am.mvn`. Parsing note: `am.mvn` has three `[...]` blocks — `[Splice idx]`, `[AddShift=shift]`, `[Rescale=scale]`; use the two 560-length vectors. Getting this wrong makes the CIF predictor emit ~4× too few tokens. - **CIF/predictor runs on host** (it's a sequential integrate-and-fire loop); the encoder and decoder run in ggml. - The decoder self-attention is **FSMN-only** (no QK attention); cross-attention attends to the encoder output. The decoder FFN has an internal LayerNorm and the second linear has no bias. - WAV input assumes 16 kHz mono PCM16. ## Files ``` funasr-paraformer/ ggml runtime: WAV → token ids export_paraformer_gguf.py export encoder + predictor + decoder + CMVN to GGUF detok.py token-id → text (tokens.json) ``` ## Roadmap - Built-in detok; timestamps (CIF peaks give alignment); arbitrary WAV / resampling; encoder/decoder quantization.