Skip to content

Commit 74c6596

Browse files
committed
feat: add FunASR speech recognition backend
Signed-off-by: LauraGPT <LauraGPT@users.noreply.github.com>
1 parent 2388686 commit 74c6596

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
@@ -1167,6 +1167,31 @@
11671167
nvidia-l4t-cuda-12: "nvidia-l4t-faster-qwen3-tts"
11681168
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-faster-qwen3-tts"
11691169
icon: https://cdn-avatars.huggingface.co/v1/production/uploads/620760a26e3b7210c2ff1943/-s1gyJfvbE1RgO5iBeNOi.png
1170+
- &funasr
1171+
urls:
1172+
- https://github.com/modelscope/FunASR
1173+
description: |
1174+
FunASR is an industrial-grade speech recognition toolkit supporting 50+ languages.
1175+
Includes SenseVoice (170x realtime, emotion detection), Paraformer (highest Chinese
1176+
accuracy), and built-in VAD, punctuation restoration, and speaker diarization.
1177+
tags:
1178+
- speech-recognition
1179+
- ASR
1180+
- multilingual
1181+
license: mit
1182+
name: "funasr"
1183+
alias: "funasr"
1184+
capabilities:
1185+
nvidia: "cuda12-funasr"
1186+
amd: "rocm-funasr"
1187+
metal: "metal-funasr"
1188+
default: "cpu-funasr"
1189+
nvidia-cuda-13: "cuda13-funasr"
1190+
nvidia-cuda-12: "cuda12-funasr"
1191+
nvidia-l4t: "nvidia-l4t-funasr"
1192+
nvidia-l4t-cuda-12: "nvidia-l4t-funasr"
1193+
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-funasr"
1194+
icon: https://avatars.githubusercontent.com/u/109454077
11701195
- &qwen-asr
11711196
urls:
11721197
- https://github.com/QwenLM/Qwen3-ASR
@@ -5161,3 +5186,86 @@
51615186
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-supertonic"
51625187
mirrors:
51635188
- localai/localai-backends:master-cpu-supertonic
5189+
## funasr
5190+
- !!merge <<: *funasr
5191+
name: "funasr-development"
5192+
capabilities:
5193+
nvidia: "cuda12-funasr-development"
5194+
amd: "rocm-funasr-development"
5195+
nvidia-l4t: "nvidia-l4t-funasr-development"
5196+
metal: "metal-funasr-development"
5197+
default: "cpu-funasr-development"
5198+
nvidia-cuda-13: "cuda13-funasr-development"
5199+
nvidia-cuda-12: "cuda12-funasr-development"
5200+
nvidia-l4t-cuda-12: "nvidia-l4t-funasr-development"
5201+
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-funasr-development"
5202+
- !!merge <<: *funasr
5203+
name: "cpu-funasr"
5204+
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-funasr"
5205+
mirrors:
5206+
- localai/localai-backends:latest-cpu-funasr
5207+
- !!merge <<: *funasr
5208+
name: "cpu-funasr-development"
5209+
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-funasr"
5210+
mirrors:
5211+
- localai/localai-backends:master-cpu-funasr
5212+
- !!merge <<: *funasr
5213+
name: "cuda12-funasr"
5214+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-funasr"
5215+
mirrors:
5216+
- localai/localai-backends:latest-gpu-nvidia-cuda-12-funasr
5217+
- !!merge <<: *funasr
5218+
name: "cuda12-funasr-development"
5219+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-12-funasr"
5220+
mirrors:
5221+
- localai/localai-backends:master-gpu-nvidia-cuda-12-funasr
5222+
- !!merge <<: *funasr
5223+
name: "cuda13-funasr"
5224+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-funasr"
5225+
mirrors:
5226+
- localai/localai-backends:latest-gpu-nvidia-cuda-13-funasr
5227+
- !!merge <<: *funasr
5228+
name: "cuda13-funasr-development"
5229+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-13-funasr"
5230+
mirrors:
5231+
- localai/localai-backends:master-gpu-nvidia-cuda-13-funasr
5232+
- !!merge <<: *funasr
5233+
name: "rocm-funasr"
5234+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-rocm-hipblas-funasr"
5235+
mirrors:
5236+
- localai/localai-backends:latest-gpu-rocm-hipblas-funasr
5237+
- !!merge <<: *funasr
5238+
name: "rocm-funasr-development"
5239+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-rocm-hipblas-funasr"
5240+
mirrors:
5241+
- localai/localai-backends:master-gpu-rocm-hipblas-funasr
5242+
- !!merge <<: *funasr
5243+
name: "nvidia-l4t-funasr"
5244+
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-funasr"
5245+
mirrors:
5246+
- localai/localai-backends:latest-nvidia-l4t-funasr
5247+
- !!merge <<: *funasr
5248+
name: "nvidia-l4t-funasr-development"
5249+
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-funasr"
5250+
mirrors:
5251+
- localai/localai-backends:master-nvidia-l4t-funasr
5252+
- !!merge <<: *funasr
5253+
name: "cuda13-nvidia-l4t-arm64-funasr"
5254+
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-cuda-13-arm64-funasr"
5255+
mirrors:
5256+
- localai/localai-backends:latest-nvidia-l4t-cuda-13-arm64-funasr
5257+
- !!merge <<: *funasr
5258+
name: "cuda13-nvidia-l4t-arm64-funasr-development"
5259+
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-cuda-13-arm64-funasr"
5260+
mirrors:
5261+
- localai/localai-backends:master-nvidia-l4t-cuda-13-arm64-funasr
5262+
- !!merge <<: *funasr
5263+
name: "metal-funasr"
5264+
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-funasr"
5265+
mirrors:
5266+
- localai/localai-backends:latest-metal-darwin-arm64-funasr
5267+
- !!merge <<: *funasr
5268+
name: "metal-funasr-development"
5269+
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-funasr"
5270+
mirrors:
5271+
- 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)