Skip to content

Commit c2ddbf3

Browse files
committed
feat: add FunASR speech recognition backend
1 parent 4912c9b commit c2ddbf3

15 files changed

Lines changed: 330 additions & 0 deletions

backend/index.yaml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,31 @@
10021002
nvidia-l4t-cuda-12: "nvidia-l4t-faster-qwen3-tts"
10031003
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-faster-qwen3-tts"
10041004
icon: https://cdn-avatars.huggingface.co/v1/production/uploads/620760a26e3b7210c2ff1943/-s1gyJfvbE1RgO5iBeNOi.png
1005+
- &funasr
1006+
urls:
1007+
- https://github.com/modelscope/FunASR
1008+
description: |
1009+
FunASR is an industrial-grade speech recognition toolkit supporting 50+ languages.
1010+
Includes SenseVoice (170x realtime, emotion detection), Paraformer (highest Chinese
1011+
accuracy), and built-in VAD, punctuation restoration, and speaker diarization.
1012+
tags:
1013+
- speech-recognition
1014+
- ASR
1015+
- multilingual
1016+
license: mit
1017+
name: "funasr"
1018+
alias: "funasr"
1019+
capabilities:
1020+
nvidia: "cuda12-funasr"
1021+
amd: "rocm-funasr"
1022+
metal: "metal-funasr"
1023+
default: "cpu-funasr"
1024+
nvidia-cuda-13: "cuda13-funasr"
1025+
nvidia-cuda-12: "cuda12-funasr"
1026+
nvidia-l4t: "nvidia-l4t-funasr"
1027+
nvidia-l4t-cuda-12: "nvidia-l4t-funasr"
1028+
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-funasr"
1029+
icon: https://avatars.githubusercontent.com/u/109454077
10051030
- &qwen-asr
10061031
urls:
10071032
- https://github.com/QwenLM/Qwen3-ASR
@@ -4586,3 +4611,86 @@
45864611
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-12-sherpa-onnx"
45874612
mirrors:
45884613
- localai/localai-backends:master-gpu-nvidia-cuda-12-sherpa-onnx
4614+
## funasr
4615+
- !!merge <<: *funasr
4616+
name: "funasr-development"
4617+
capabilities:
4618+
nvidia: "cuda12-funasr-development"
4619+
amd: "rocm-funasr-development"
4620+
nvidia-l4t: "nvidia-l4t-funasr-development"
4621+
metal: "metal-funasr-development"
4622+
default: "cpu-funasr-development"
4623+
nvidia-cuda-13: "cuda13-funasr-development"
4624+
nvidia-cuda-12: "cuda12-funasr-development"
4625+
nvidia-l4t-cuda-12: "nvidia-l4t-funasr-development"
4626+
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-funasr-development"
4627+
- !!merge <<: *funasr
4628+
name: "cpu-funasr"
4629+
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-funasr"
4630+
mirrors:
4631+
- localai/localai-backends:latest-cpu-funasr
4632+
- !!merge <<: *funasr
4633+
name: "cpu-funasr-development"
4634+
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-funasr"
4635+
mirrors:
4636+
- localai/localai-backends:master-cpu-funasr
4637+
- !!merge <<: *funasr
4638+
name: "cuda12-funasr"
4639+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-funasr"
4640+
mirrors:
4641+
- localai/localai-backends:latest-gpu-nvidia-cuda-12-funasr
4642+
- !!merge <<: *funasr
4643+
name: "cuda12-funasr-development"
4644+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-12-funasr"
4645+
mirrors:
4646+
- localai/localai-backends:master-gpu-nvidia-cuda-12-funasr
4647+
- !!merge <<: *funasr
4648+
name: "cuda13-funasr"
4649+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-funasr"
4650+
mirrors:
4651+
- localai/localai-backends:latest-gpu-nvidia-cuda-13-funasr
4652+
- !!merge <<: *funasr
4653+
name: "cuda13-funasr-development"
4654+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-13-funasr"
4655+
mirrors:
4656+
- localai/localai-backends:master-gpu-nvidia-cuda-13-funasr
4657+
- !!merge <<: *funasr
4658+
name: "rocm-funasr"
4659+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-rocm-hipblas-funasr"
4660+
mirrors:
4661+
- localai/localai-backends:latest-gpu-rocm-hipblas-funasr
4662+
- !!merge <<: *funasr
4663+
name: "rocm-funasr-development"
4664+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-rocm-hipblas-funasr"
4665+
mirrors:
4666+
- localai/localai-backends:master-gpu-rocm-hipblas-funasr
4667+
- !!merge <<: *funasr
4668+
name: "nvidia-l4t-funasr"
4669+
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-funasr"
4670+
mirrors:
4671+
- localai/localai-backends:latest-nvidia-l4t-funasr
4672+
- !!merge <<: *funasr
4673+
name: "nvidia-l4t-funasr-development"
4674+
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-funasr"
4675+
mirrors:
4676+
- localai/localai-backends:master-nvidia-l4t-funasr
4677+
- !!merge <<: *funasr
4678+
name: "cuda13-nvidia-l4t-arm64-funasr"
4679+
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-cuda-13-arm64-funasr"
4680+
mirrors:
4681+
- localai/localai-backends:latest-nvidia-l4t-cuda-13-arm64-funasr
4682+
- !!merge <<: *funasr
4683+
name: "cuda13-nvidia-l4t-arm64-funasr-development"
4684+
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-cuda-13-arm64-funasr"
4685+
mirrors:
4686+
- localai/localai-backends:master-nvidia-l4t-cuda-13-arm64-funasr
4687+
- !!merge <<: *funasr
4688+
name: "metal-funasr"
4689+
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-funasr"
4690+
mirrors:
4691+
- localai/localai-backends:latest-metal-darwin-arm64-funasr
4692+
- !!merge <<: *funasr
4693+
name: "metal-funasr-development"
4694+
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-funasr"
4695+
mirrors:
4696+
- localai/localai-backends:master-metal-darwin-arm64-funasr

backend/python/funasr/Makefile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.PHONY: funasr
2+
funasr:
3+
bash install.sh
4+
5+
.PHONY: run
6+
run: funasr
7+
@echo "Running funasr..."
8+
bash run.sh
9+
@echo "funasr run."
10+
11+
.PHONY: test
12+
test: funasr
13+
@echo "Testing funasr..."
14+
bash test.sh
15+
@echo "funasr tested."
16+
17+
.PHONY: protogen-clean
18+
protogen-clean:
19+
$(RM) backend_pb2_grpc.py backend_pb2.py
20+
21+
.PHONY: clean
22+
clean: protogen-clean
23+
rm -rf venv __pycache__

backend/python/funasr/backend.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#!/usr/bin/env python3
2+
"""
3+
gRPC backend for LocalAI wrapping FunASR (SenseVoice / Paraformer).
4+
"""
5+
from concurrent import futures
6+
import time
7+
import argparse
8+
import signal
9+
import sys
10+
import os
11+
import backend_pb2
12+
import backend_pb2_grpc
13+
import torch
14+
15+
import grpc
16+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
17+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
18+
from grpc_auth import get_auth_interceptors
19+
20+
21+
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
22+
MAX_WORKERS = int(os.environ.get('PYTHON_GRPC_MAX_WORKERS', '1'))
23+
24+
25+
class BackendServicer(backend_pb2_grpc.BackendServicer):
26+
def Health(self, request, context):
27+
return backend_pb2.Reply(message=bytes("OK", 'utf-8'))
28+
29+
def LoadModel(self, request, context):
30+
from funasr import AutoModel
31+
32+
device = "cpu"
33+
if request.CUDA and torch.cuda.is_available():
34+
device = "cuda"
35+
mps_available = hasattr(torch.backends, "mps") and torch.backends.mps.is_available()
36+
if mps_available:
37+
device = "mps"
38+
39+
model_id = request.Model or "iic/SenseVoiceSmall"
40+
41+
try:
42+
print(f"Loading FunASR model: {model_id} on {device}", file=sys.stderr)
43+
self.model = AutoModel(
44+
model=model_id,
45+
vad_model="fsmn-vad",
46+
device=device,
47+
disable_update=True,
48+
)
49+
print("FunASR model loaded successfully", file=sys.stderr)
50+
except Exception as err:
51+
print(f"[ERROR] LoadModel failed: {err}", file=sys.stderr)
52+
import traceback
53+
traceback.print_exc(file=sys.stderr)
54+
return backend_pb2.Result(success=False, message=str(err))
55+
56+
return backend_pb2.Result(message="Model loaded successfully", success=True)
57+
58+
def AudioTranscription(self, request, context):
59+
result_segments = []
60+
text = ""
61+
try:
62+
audio_path = request.dst
63+
if not audio_path or not os.path.exists(audio_path):
64+
print(f"Error: Audio file not found: {audio_path}", file=sys.stderr)
65+
return backend_pb2.TranscriptResult(segments=[], text="")
66+
67+
language = None
68+
if request.language and request.language.strip():
69+
language = request.language.strip()
70+
71+
kwargs = {}
72+
if language:
73+
kwargs["language"] = language
74+
75+
results = self.model.generate(input=audio_path, **kwargs)
76+
77+
if not results:
78+
return backend_pb2.TranscriptResult(segments=[], text="")
79+
80+
for idx, r in enumerate(results):
81+
seg_text = r.get("text", "") if isinstance(r, dict) else str(r)
82+
text += seg_text
83+
result_segments.append(backend_pb2.TranscriptSegment(
84+
id=idx,
85+
start=0,
86+
end=0,
87+
text=seg_text,
88+
))
89+
90+
except Exception as err:
91+
print(f"Error in AudioTranscription: {err}", file=sys.stderr)
92+
import traceback
93+
traceback.print_exc(file=sys.stderr)
94+
return backend_pb2.TranscriptResult(segments=[], text="")
95+
96+
return backend_pb2.TranscriptResult(segments=result_segments, text=text)
97+
98+
99+
def serve(address):
100+
server = grpc.server(
101+
futures.ThreadPoolExecutor(max_workers=MAX_WORKERS),
102+
options=[
103+
('grpc.max_message_length', 50 * 1024 * 1024),
104+
('grpc.max_send_message_length', 50 * 1024 * 1024),
105+
('grpc.max_receive_message_length', 50 * 1024 * 1024),
106+
],
107+
interceptors=get_auth_interceptors(),
108+
)
109+
backend_pb2_grpc.add_BackendServicer_to_server(BackendServicer(), server)
110+
server.add_insecure_port(address)
111+
server.start()
112+
print("Server started. Listening on: " + address, file=sys.stderr)
113+
114+
def signal_handler(sig, frame):
115+
print("Received termination signal. Shutting down...")
116+
server.stop(0)
117+
sys.exit(0)
118+
119+
signal.signal(signal.SIGINT, signal_handler)
120+
signal.signal(signal.SIGTERM, signal_handler)
121+
122+
try:
123+
while True:
124+
time.sleep(_ONE_DAY_IN_SECONDS)
125+
except KeyboardInterrupt:
126+
server.stop(0)
127+
128+
129+
if __name__ == "__main__":
130+
parser = argparse.ArgumentParser(description="Run the gRPC server.")
131+
parser.add_argument("--addr", default="localhost:50051", help="The address to bind the server to.")
132+
args = parser.parse_args()
133+
serve(args.addr)

backend/python/funasr/install.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
set -e
3+
4+
EXTRA_PIP_INSTALL_FLAGS="--no-build-isolation"
5+
6+
backend_dir=$(dirname $0)
7+
if [ -d $backend_dir/common ]; then
8+
source $backend_dir/common/libbackend.sh
9+
else
10+
source $backend_dir/../common/libbackend.sh
11+
fi
12+
13+
if [ "x${BUILD_PROFILE}" == "xintel" ]; then
14+
EXTRA_PIP_INSTALL_FLAGS+=" --upgrade --index-strategy=unsafe-first-match"
15+
fi
16+
17+
PYTHON_VERSION="3.12"
18+
PYTHON_PATCH="12"
19+
PY_STANDALONE_TAG="20251120"
20+
21+
installRequirements
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--extra-index-url https://download.pytorch.org/whl/cpu
2+
torch
3+
funasr
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--extra-index-url https://download.pytorch.org/whl/cu121
2+
torch
3+
funasr
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--extra-index-url https://download.pytorch.org/whl/cu131
2+
torch
3+
funasr
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--extra-index-url https://download.pytorch.org/whl/rocm6.1
2+
torch
3+
funasr
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
torch
2+
funasr
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
funasr

0 commit comments

Comments
 (0)