Просмотр исходного кода

Merge branch 'codex' of penta/whisper-stt-chatbot into master

OK
penta 16 часов назад
Родитель
Сommit
79f21797af
4 измененных файлов с 94 добавлено и 32 удалено
  1. 33 26
      app.py
  2. 17 0
      openshift-deployment-patch.json
  3. 37 0
      openshift.yaml
  4. 7 6
      requirements.txt

+ 33 - 26
app.py

@@ -5,8 +5,10 @@ import time
 import logging
 import os
 import sys
+import asyncio
+from threading import Lock
 
-from fastapi import FastAPI, UploadFile, File, WebSocket, WebSocketDisconnect
+from fastapi import FastAPI, UploadFile, File, WebSocket, WebSocketDisconnect, HTTPException
 from faster_whisper import WhisperModel
 
 def env_bool(name: str, default: bool = False) -> bool:
@@ -37,30 +39,41 @@ app = FastAPI()
 # Whisper model (shared)
 # =========================
 model = WhisperModel(MODEL_SIZE, device="cuda", compute_type="float32")
+# Vérifier les kernels CUDA avant de déclarer le service prêt.
+list(model.transcribe(np.zeros(16000, dtype=np.float32), language="fr", beam_size=1)[0])
+model_lock = Lock()
+
+def transcribe_audio(audio):
+    # Consommer le générateur sous le verrou, hors de la boucle asyncio.
+    with model_lock:
+        segments, info = model.transcribe(
+            audio, language="fr", vad_filter=VAD_ENABLED,
+            beam_size=1, condition_on_previous_text=False,
+        )
+        return " ".join(seg.text for seg in segments).strip(), info.language
+
+@app.get("/healthz")
+def healthz():
+    return {"status": "ready", "model": MODEL_SIZE}
 
 # =========================
 # REST API (Swagger / tests)
 # =========================
 @app.post("/transcribe")
-async def transcribe(file: UploadFile = File(...)):
+def transcribe(file: UploadFile = File(...)):
+    # Discord envoie au maximum 20 secondes de WAV stéréo 48 kHz.
     with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as tmp:
-        tmp.write(await file.read())
         tmp_path = tmp.name
-
-    segments, info = model.transcribe(
-        tmp_path,
-        language="fr",
-        vad_filter=True
-    )
-
-    os.remove(tmp_path)
-
-    text = " ".join(seg.text for seg in segments)
-
-    return {
-        "text": text,
-        "language": info.language
-    }
+        try:
+            data = file.file.read(8 * 1024 * 1024 + 1)
+            if len(data) > 8 * 1024 * 1024:
+                raise HTTPException(413, "Audio trop volumineux")
+            tmp.write(data)
+            tmp.flush()
+            text, language = transcribe_audio(tmp_path)
+            return {"text": text, "language": language}
+        finally:
+            os.remove(tmp_path)
 
 # =========================
 # WebSocket STT (streaming)
@@ -92,13 +105,7 @@ async def ws_transcribe(ws: WebSocket):
             duration = len(pcm) / SAMPLE_RATE
             logger.info(f"Transcription chunk {duration:.2f}s")
 
-            segments, _ = model.transcribe(
-                pcm,
-                language="fr",
-                vad_filter=VAD_ENABLED
-            )
-
-            text = " ".join(seg.text for seg in segments).strip()
+            text, _ = await asyncio.to_thread(transcribe_audio, pcm)
 
             logger.info(f"Texte: '{text}'")
 
@@ -112,4 +119,4 @@ async def ws_transcribe(ws: WebSocket):
         logger.info("WebSocket déconnecté")
     except Exception:
         logger.exception("Erreur WebSocket")
-        await ws.close()
+        await ws.close()

+ 17 - 0
openshift-deployment-patch.json

@@ -0,0 +1,17 @@
+[
+  {"op":"test","path":"/spec/template/spec/containers/0/name","value":"whisper-stt-chatbot"},
+  {"op":"add","path":"/metadata/annotations/image.openshift.io~1triggers","value":"[{\"from\":{\"kind\":\"ImageStreamTag\",\"name\":\"whisper-stt-chatbot:latest\",\"namespace\":\"localai\"},\"fieldPath\":\"spec.template.spec.containers[?(@.name==\\\"whisper-stt-chatbot\\\")].image\",\"paused\":true}]"},
+  {"op":"replace","path":"/spec/replicas","value":1},
+  {"op":"replace","path":"/spec/template/spec/containers/0/image","value":"image-registry.openshift-image-registry.svc:5000/localai/whisper-stt-chatbot@sha256:3c79e8d40bb63e912519e7017d7091520fcebcb4928023d5a902206d26ba7d0e"},
+  {"op":"add","path":"/spec/template/spec/serviceAccountName","value":"whisper-stt-chatbot"},
+  {"op":"add","path":"/spec/template/spec/automountServiceAccountToken","value":false},
+  {"op":"replace","path":"/spec/template/spec/affinity/nodeAffinity/requiredDuringSchedulingIgnoredDuringExecution/nodeSelectorTerms/0/matchExpressions/0/values","value":["chambre.local.pentou.ovh"]},
+  {"op":"add","path":"/spec/strategy","value":{"type":"Recreate"}},
+  {"op":"replace","path":"/spec/template/spec/containers/0/resources","value":{"limits":{"nvidia.com/gpu":"1"}}},
+  {"op":"replace","path":"/spec/template/spec/volumes","value":[{"name":"hf-cache","persistentVolumeClaim":{"claimName":"whisper-model-cache"}}]},
+  {"op":"replace","path":"/spec/template/spec/containers/0/env","value":[{"name":"HF_HOME","value":"/tmp/hf-cache"},{"name":"HUGGINGFACE_HUB_CACHE","value":"/tmp/hf-cache"},{"name":"WHISPER_LOG_LEVEL","value":"INFO"},{"name":"WHISPER_VAD","value":"true"},{"name":"MODEL_SIZE","value":"small"}]},
+  {"op":"replace","path":"/spec/template/spec/containers/0/args","value":["app:app","--host","0.0.0.0","--port","8000","--log-level","info"]},
+  {"op":"add","path":"/spec/template/spec/containers/0/startupProbe","value":{"httpGet":{"path":"/healthz","port":8000},"periodSeconds":10,"timeoutSeconds":5,"failureThreshold":90}},
+  {"op":"add","path":"/spec/template/spec/containers/0/readinessProbe","value":{"httpGet":{"path":"/healthz","port":8000},"periodSeconds":10,"timeoutSeconds":5}},
+  {"op":"add","path":"/spec/template/spec/containers/0/livenessProbe","value":{"httpGet":{"path":"/healthz","port":8000},"periodSeconds":30,"timeoutSeconds":5,"failureThreshold":5}}
+]

+ 37 - 0
openshift.yaml

@@ -0,0 +1,37 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: whisper-stt-chatbot
+  namespace: localai
+automountServiceAccountToken: false
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  name: whisper-model-cache
+  namespace: localai
+spec:
+  accessModes: [ReadWriteOnce]
+  storageClassName: ocs-storagecluster-ceph-rbd
+  resources:
+    requests:
+      storage: 5Gi
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: whisper-stt-ingress
+  namespace: localai
+spec:
+  podSelector:
+    matchLabels:
+      app: whisper-stt-chatbot
+  policyTypes: [Ingress]
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              app: satoko-chatbot
+      ports:
+        - protocol: TCP
+          port: 8000

+ 7 - 6
requirements.txt

@@ -1,6 +1,7 @@
-fastapi
-uvicorn[standard]
-faster-whisper
-soundfile
-numpy
-python-multipart
+fastapi==0.162.0
+uvicorn[standard]==0.54.0
+faster-whisper==1.2.1
+# CUDA 12 / cuDNN 8 de l'image.
+ctranslate2==4.4.0
+numpy<3
+python-multipart==0.0.32