third-eye / cohere_stt.py
mitvho09's picture
Upload folder using huggingface_hub
031e3f9 verified
Raw
History Blame Contribute Delete
4.32 kB
from __future__ import annotations
import io
import os
from functools import lru_cache
import numpy as np
MODEL_ID = "CohereLabs/cohere-transcribe-03-2026"
@lru_cache(maxsize=1)
def load_model():
import torch
from transformers import AutoProcessor, CohereAsrForConditionalGeneration
token = os.environ.get("HF_TOKEN")
if not token:
raise RuntimeError(
"HF_TOKEN is required. Set the third-eye-hf Modal secret with a valid token "
"that has access to CohereLabs/cohere-transcribe-03-2026."
)
# PERF FIX: load in half precision and pin to a single GPU explicitly.
# The previous `device_map="auto"` + default float32 caused the model to run
# ~1s/forward-pass (effectively on CPU / partially offloaded), making each
# transcription take minutes. bf16 on a single CUDA device matches the
# model card's "blazing fast" benchmark (RTFx ~500).
cuda = torch.cuda.is_available()
dtype = (
torch.bfloat16
if cuda and torch.cuda.is_bf16_supported()
else (torch.float16 if cuda else torch.float32)
)
try:
processor = AutoProcessor.from_pretrained(
MODEL_ID,
token=token,
)
model = CohereAsrForConditionalGeneration.from_pretrained(
MODEL_ID,
token=token,
torch_dtype=dtype,
)
except OSError as e:
if "gated" in str(e).lower() or "403" in str(e):
raise RuntimeError(
f"HF token does not have access to {MODEL_ID}. "
f"Visit https://huggingface.co/{MODEL_ID} and accept the license agreement, "
f"then update the third-eye-hf Modal secret."
) from e
raise
if cuda:
model = model.to("cuda")
model.eval()
# Diagnostic: proves on the next deploy whether the model is truly on GPU.
# If `device` is cpu here, that is the real cause of slow transcription.
param = next(model.parameters())
print(
f"[third-eye STT] loaded {MODEL_ID} | cuda_available={cuda} "
f"| device={param.device} | dtype={param.dtype}",
flush=True,
)
return processor, model
def transcribe_wav_bytes(audio_bytes: bytes, language: str = "en") -> str:
import time
import librosa
import soundfile as sf
import torch
audio, sample_rate = sf.read(io.BytesIO(audio_bytes), dtype="float32")
if audio.ndim > 1:
audio = np.mean(audio, axis=1)
if sample_rate != 16_000:
audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=16_000)
# ACCURACY FIX: strip leading/trailing silence. The ASR model hallucinates
# plausible-but-fake words over silent padding (e.g. appending "Yes, sir."
# after the real question), which then corrupts the question sent to the
# vision model. Trimming silence removes the trigger. Fall back to the raw
# audio if trimming would leave less than ~0.1s of signal.
trimmed, _ = librosa.effects.trim(audio, top_db=30)
if trimmed.size >= 1_600:
audio = trimmed
processor, model = load_model()
inputs = processor(
audio,
sampling_rate=16_000,
return_tensors="pt",
language=language,
)
inputs = inputs.to(model.device, dtype=model.dtype)
# PERF FIX: force greedy decoding (num_beams=1, do_sample=False). If the model's
# generation_config defaulted to beam search, every token cost several forward
# passes (the repeated "6/6" progress bars in the logs). Greedy is also the most
# faithful choice for transcription. max_new_tokens trimmed to 128 (speech is short).
start = time.time()
with torch.inference_mode():
generated_ids = model.generate(
**inputs,
max_new_tokens=128,
num_beams=1,
do_sample=False,
)
print(
f"[third-eye STT] generate: {time.time() - start:.2f}s "
f"for {int(generated_ids.shape[-1])} tokens",
flush=True,
)
decoded = processor.decode(generated_ids, skip_special_tokens=True)
if isinstance(decoded, list):
decoded = decoded[0]
return str(decoded).strip()