init: VibeVoice ASR service
This commit is contained in:
commit
0f910cc605
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
venv/
|
||||
logs/
|
||||
files/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pid
|
||||
*.log
|
||||
57
app/vibevoice_asr_app.py
Normal file
57
app/vibevoice_asr_app.py
Normal file
@ -0,0 +1,57 @@
|
||||
"""VibeVoice ASR — ahserver app with same API as fastwhisper."""
|
||||
import asyncio
|
||||
import json
|
||||
from appPublic.worker import get_event_loop
|
||||
from appPublic.log import debug
|
||||
from vv import load_vibevoice_asr
|
||||
from ahserver.webapp import webapp
|
||||
from ahserver.configuredServer import add_startup
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from appPublic.registerfunction import RegisterFunction
|
||||
|
||||
|
||||
async def transcribe(request, *args, **kw):
|
||||
env = request._run_ns
|
||||
payload = dict(env.params_kw)
|
||||
ret = await env.vibevoice.submit_task(payload)
|
||||
return ret
|
||||
|
||||
|
||||
async def get_status(request, *args, **kw):
|
||||
env = request._run_ns
|
||||
data = await env.vibevoice.get_status(env.params_kw.task_id)
|
||||
return data
|
||||
|
||||
|
||||
async def asr(request, *args, **kw):
|
||||
env = request._run_ns
|
||||
payload = json.loads(json.dumps(dict(env.params_kw)))
|
||||
debug(f'asr(): payload={payload}')
|
||||
ret = await env.vibevoice.submit_task(payload)
|
||||
while True:
|
||||
data = await env.vibevoice.get_status(ret['task_id'])
|
||||
if data['status'] in ['SUCCEEDED', 'FAILED']:
|
||||
break
|
||||
await asyncio.sleep(0.2)
|
||||
return data
|
||||
|
||||
|
||||
async def start_engine(*args, **kw):
|
||||
debug('starting VibeVoice ASR engine...')
|
||||
env = ServerEnv()
|
||||
asyncio.create_task(env.vibevoice.run())
|
||||
debug('VibeVoice ASR engine started')
|
||||
|
||||
|
||||
def init():
|
||||
rf = RegisterFunction()
|
||||
rf.register('asr', asr)
|
||||
rf.register('transcribe', transcribe)
|
||||
rf.register('get_status', get_status)
|
||||
load_vibevoice_asr()
|
||||
add_startup(start_engine)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = get_event_loop()
|
||||
webapp(init)
|
||||
43
conf/config.json
Normal file
43
conf/config.json
Normal file
@ -0,0 +1,43 @@
|
||||
{
|
||||
"filesroot": "$[workdir]$/files",
|
||||
"model_path": "/share/models/VibeVoice-ASR-7B",
|
||||
"redis_url": "redis://127.0.0.1:6379",
|
||||
"worker_cnt": 2,
|
||||
"logger": {
|
||||
"name": "vibevoice-asr",
|
||||
"levelname": "info",
|
||||
"logfile": "$[workdir]$/logs/vibevoice.log"
|
||||
},
|
||||
"website": {
|
||||
"paths": [
|
||||
["$[workdir]$/wwwroot", ""]
|
||||
],
|
||||
"client_max_size": 10000,
|
||||
"host": "0.0.0.0",
|
||||
"port": 9926,
|
||||
"coding": "utf-8",
|
||||
"indexes": ["index.html", "index.dspy", "index.ui"],
|
||||
"startswiths": [
|
||||
{
|
||||
"leading": "/api/asr",
|
||||
"registerfunction": "asr"
|
||||
},
|
||||
{
|
||||
"leading": "/api/transcribe",
|
||||
"registerfunction": "transcribe"
|
||||
},
|
||||
{
|
||||
"leading": "/api/status",
|
||||
"registerfunction": "get_status"
|
||||
}
|
||||
],
|
||||
"processors": [
|
||||
[".tmpl", "tmpl"],
|
||||
[".app", "app"],
|
||||
[".ui", "bui"],
|
||||
[".dspy", "dspy"]
|
||||
],
|
||||
"session_max_time": 3000,
|
||||
"session_issue_time": 2500
|
||||
}
|
||||
}
|
||||
9
vv/__init__.py
Normal file
9
vv/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
"""VibeVoice ASR init — register engine on ServerEnv."""
|
||||
from vv.engine import VibeVoiceASREngine
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
|
||||
def load_vibevoice_asr():
|
||||
env = ServerEnv()
|
||||
engine = VibeVoiceASREngine()
|
||||
env.vibevoice = engine
|
||||
106
vv/engine.py
Normal file
106
vv/engine.py
Normal file
@ -0,0 +1,106 @@
|
||||
"""
|
||||
VibeVoice ASR engine v4 — Pure VibeVoice source (model + processor).
|
||||
Patched AutoModel.register(..., exist_ok=True) for Transformers 5.x compat.
|
||||
"""
|
||||
import torch
|
||||
import sys
|
||||
sys.path.insert(0, "/share/ymq/VibeVoice")
|
||||
|
||||
from longtasks.longtasks import LongTasks
|
||||
from appPublic.worker import awaitify
|
||||
from appPublic.jsonConfig import getConfig
|
||||
from appPublic.log import debug
|
||||
from ahserver.filestorage import FileStorage
|
||||
|
||||
from vibevoice.modular.modeling_vibevoice_asr import VibeVoiceASRForConditionalGeneration
|
||||
from vibevoice.processor.vibevoice_asr_processor import VibeVoiceASRProcessor
|
||||
|
||||
|
||||
class VibeVoiceASREngine(LongTasks):
|
||||
def __init__(self):
|
||||
self.config = getConfig()
|
||||
super().__init__(self.config.redis_url, 'vibevoice-asr', worker_cnt=self.config.worker_cnt)
|
||||
self.load_models()
|
||||
|
||||
def load_models(self):
|
||||
model_path = self.config.model_path
|
||||
debug('loading VibeVoice ASR on cuda:0...')
|
||||
self.model = VibeVoiceASRForConditionalGeneration.from_pretrained(
|
||||
model_path,
|
||||
dtype=torch.bfloat16,
|
||||
device_map="cuda:0",
|
||||
attn_implementation="sdpa",
|
||||
trust_remote_code=True,
|
||||
ignore_mismatched_sizes=True,
|
||||
)
|
||||
self.model.eval()
|
||||
self.processor = VibeVoiceASRProcessor.from_pretrained(
|
||||
model_path,
|
||||
language_model_pretrained_name="/share/models/Qwen2.5-7B",
|
||||
)
|
||||
debug('VibeVoice ASR loaded')
|
||||
|
||||
async def process_task(self, payload, workerid=None):
|
||||
webpath = payload.get('audio_file')
|
||||
if not webpath:
|
||||
return {"task_status": "error", "message": "no audio_file"}
|
||||
fs = FileStorage()
|
||||
fpath = fs.realPath(webpath)
|
||||
f = awaitify(self._transcribe)
|
||||
return await f(fpath)
|
||||
|
||||
def _transcribe(self, fpath):
|
||||
inputs = self.processor(
|
||||
audio=fpath, sampling_rate=None, return_tensors="pt",
|
||||
padding=True, add_generation_prompt=True)
|
||||
device = torch.device("cuda:0")
|
||||
inputs = {k: v.to(device) if isinstance(v, torch.Tensor) else v
|
||||
for k, v in inputs.items()}
|
||||
with torch.no_grad():
|
||||
gen = self.model.generate(**inputs, max_new_tokens=4096,
|
||||
temperature=0.0, do_sample=False)
|
||||
text = self.processor.decode(gen[0], skip_special_tokens=True)
|
||||
try:
|
||||
segs = self.processor.post_process_transcription(text)
|
||||
except Exception as e:
|
||||
debug(f'parse failed: {e}')
|
||||
segs = []
|
||||
|
||||
# Fallback: direct JSON extraction from model output
|
||||
if not segs and text:
|
||||
import re, json as _json
|
||||
# Try full JSON first
|
||||
m = re.search(r'assistant\n(.+?)(?:<\|im_end\|>)', text, re.DOTALL)
|
||||
json_str = m.group(1).strip() if m else ''
|
||||
if not json_str and 'assistant\n[' in text:
|
||||
json_str = text[text.find('assistant\n[')+10:]
|
||||
if json_str:
|
||||
# Handle truncated JSON: find last complete object
|
||||
try:
|
||||
raw_segs = _json.loads(json_str)
|
||||
except Exception:
|
||||
last_good = json_str.rfind('"}')
|
||||
if last_good > 0:
|
||||
try:
|
||||
raw_segs = _json.loads(json_str[:last_good+2] + ']')
|
||||
except Exception:
|
||||
raw_segs = []
|
||||
else:
|
||||
raw_segs = []
|
||||
for s in raw_segs:
|
||||
segs.append({
|
||||
'start_time': s.get('Start', 0),
|
||||
'end_time': s.get('End', 0),
|
||||
'speaker_id': s.get('Speaker', 0),
|
||||
'text': s.get('Content', ''),
|
||||
})
|
||||
|
||||
ws, ft = [], []
|
||||
for s in segs:
|
||||
ws.append([s.get('start_time',0), s.get('end_time',0), s.get('text',''), []])
|
||||
ft.append(s.get('text',''))
|
||||
return {
|
||||
'task_status': 'SUCCEEDED', 'language': 'auto',
|
||||
'language_probability': 1.0, 'content': ' '.join(ft),
|
||||
'segments': ws, 'raw_output': text, 'vibevoice_segments': segs,
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user