Skip to content

Commit 99bfb92

Browse files
committed
feat: add FunASR speech recognition backend
Signed-off-by: zhifu gao <zhifu.gzf@alibaba-inc.com>
1 parent d521608 commit 99bfb92

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
@@ -1456,6 +1456,31 @@
14561456
nvidia-l4t-cuda-12: "nvidia-l4t-faster-qwen3-tts"
14571457
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-faster-qwen3-tts"
14581458
icon: https://cdn-avatars.huggingface.co/v1/production/uploads/620760a26e3b7210c2ff1943/-s1gyJfvbE1RgO5iBeNOi.png
1459+
- &funasr
1460+
urls:
1461+
- https://github.com/modelscope/FunASR
1462+
description: |
1463+
FunASR is an industrial-grade speech recognition toolkit supporting 50+ languages.
1464+
Includes SenseVoice (170x realtime, emotion detection), Paraformer (highest Chinese
1465+
accuracy), and built-in VAD, punctuation restoration, and speaker diarization.
1466+
tags:
1467+
- speech-recognition
1468+
- ASR
1469+
- multilingual
1470+
license: mit
1471+
name: "funasr"
1472+
alias: "funasr"
1473+
capabilities:
1474+
nvidia: "cuda12-funasr"
1475+
amd: "rocm-funasr"
1476+
metal: "metal-funasr"
1477+
default: "cpu-funasr"
1478+
nvidia-cuda-13: "cuda13-funasr"
1479+
nvidia-cuda-12: "cuda12-funasr"
1480+
nvidia-l4t: "nvidia-l4t-funasr"
1481+
nvidia-l4t-cuda-12: "nvidia-l4t-funasr"
1482+
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-funasr"
1483+
icon: https://avatars.githubusercontent.com/u/109454077
14591484
- &qwen-asr
14601485
urls:
14611486
- https://github.com/QwenLM/Qwen3-ASR
@@ -5902,3 +5927,86 @@
59025927
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-supertonic"
59035928
mirrors:
59045929
- localai/localai-backends:master-metal-darwin-arm64-supertonic
5930+
## funasr
5931+
- !!merge <<: *funasr
5932+
name: "funasr-development"
5933+
capabilities:
5934+
nvidia: "cuda12-funasr-development"
5935+
amd: "rocm-funasr-development"
5936+
nvidia-l4t: "nvidia-l4t-funasr-development"
5937+
metal: "metal-funasr-development"
5938+
default: "cpu-funasr-development"
5939+
nvidia-cuda-13: "cuda13-funasr-development"
5940+
nvidia-cuda-12: "cuda12-funasr-development"
5941+
nvidia-l4t-cuda-12: "nvidia-l4t-funasr-development"
5942+
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-funasr-development"
5943+
- !!merge <<: *funasr
5944+
name: "cpu-funasr"
5945+
uri: "quay.io/go-skynet/local-ai-backends:latest-cpu-funasr"
5946+
mirrors:
5947+
- localai/localai-backends:latest-cpu-funasr
5948+
- !!merge <<: *funasr
5949+
name: "cpu-funasr-development"
5950+
uri: "quay.io/go-skynet/local-ai-backends:master-cpu-funasr"
5951+
mirrors:
5952+
- localai/localai-backends:master-cpu-funasr
5953+
- !!merge <<: *funasr
5954+
name: "cuda12-funasr"
5955+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-funasr"
5956+
mirrors:
5957+
- localai/localai-backends:latest-gpu-nvidia-cuda-12-funasr
5958+
- !!merge <<: *funasr
5959+
name: "cuda12-funasr-development"
5960+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-12-funasr"
5961+
mirrors:
5962+
- localai/localai-backends:master-gpu-nvidia-cuda-12-funasr
5963+
- !!merge <<: *funasr
5964+
name: "cuda13-funasr"
5965+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-funasr"
5966+
mirrors:
5967+
- localai/localai-backends:latest-gpu-nvidia-cuda-13-funasr
5968+
- !!merge <<: *funasr
5969+
name: "cuda13-funasr-development"
5970+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-13-funasr"
5971+
mirrors:
5972+
- localai/localai-backends:master-gpu-nvidia-cuda-13-funasr
5973+
- !!merge <<: *funasr
5974+
name: "rocm-funasr"
5975+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-rocm-hipblas-funasr"
5976+
mirrors:
5977+
- localai/localai-backends:latest-gpu-rocm-hipblas-funasr
5978+
- !!merge <<: *funasr
5979+
name: "rocm-funasr-development"
5980+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-rocm-hipblas-funasr"
5981+
mirrors:
5982+
- localai/localai-backends:master-gpu-rocm-hipblas-funasr
5983+
- !!merge <<: *funasr
5984+
name: "nvidia-l4t-funasr"
5985+
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-funasr"
5986+
mirrors:
5987+
- localai/localai-backends:latest-nvidia-l4t-funasr
5988+
- !!merge <<: *funasr
5989+
name: "nvidia-l4t-funasr-development"
5990+
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-funasr"
5991+
mirrors:
5992+
- localai/localai-backends:master-nvidia-l4t-funasr
5993+
- !!merge <<: *funasr
5994+
name: "cuda13-nvidia-l4t-arm64-funasr"
5995+
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-cuda-13-arm64-funasr"
5996+
mirrors:
5997+
- localai/localai-backends:latest-nvidia-l4t-cuda-13-arm64-funasr
5998+
- !!merge <<: *funasr
5999+
name: "cuda13-nvidia-l4t-arm64-funasr-development"
6000+
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-cuda-13-arm64-funasr"
6001+
mirrors:
6002+
- localai/localai-backends:master-nvidia-l4t-cuda-13-arm64-funasr
6003+
- !!merge <<: *funasr
6004+
name: "metal-funasr"
6005+
uri: "quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-funasr"
6006+
mirrors:
6007+
- localai/localai-backends:latest-metal-darwin-arm64-funasr
6008+
- !!merge <<: *funasr
6009+
name: "metal-funasr-development"
6010+
uri: "quay.io/go-skynet/local-ai-backends:master-metal-darwin-arm64-funasr"
6011+
mirrors:
6012+
- 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)