| 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."
|
| )
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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()
|
|
|
|
|
|
|
| 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)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|
|
|
|
|
|
|
|
|
| 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()
|
|
|