Indic-Parler-TTS gave OpenAudio Suite real speech in Tamil, Telugu, Bengali, and Marathi. IndicWhisper gave it self-hosted transcription — for exactly one of those languages. Hindi. The app could speak four Indian languages it couldn’t reliably hear, which is the kind of asymmetry that’s easy to miss until someone points at it directly.
Someone did, by sharing a link, not a request: vaani.iisc.ac.in, IISc’s SPIRE Lab and ARTPARK’s speech project. Buried in the collection was SraVaani-1.0, released days earlier — an ASR model covering 65 Indian languages and dialects, from the twenty scheduled languages down to Garo, Bundeli, and Marwari. Small, MIT-licensed, and on paper exactly the gap-closer IndicWhisper’s own docstring had been waiting for since it shipped: “v1 scope: Hindi only… before expanding.”
The training trick worth stopping for
Most model-candidate write-ups in this stack are about deployment mechanics, not architecture. This one earns an exception. Before SraVaani ever sees a transcribed sentence, its encoder goes through a stage that has nothing to do with text at all: speech representations are contrastively aligned to frozen SigLIP2 image embeddings, using 11.8 million image-audio pairs already sitting in the underlying Vaani dataset — recordings of speakers describing images they were shown. No new data collected, no new transcription done. Just squeezing a second signal out of recordings that already existed, before the expensive part — actually transcribing 30,000+ hours across 65 languages — ever starts.
The published results back the idea directly: the accuracy gains concentrate hardest on the languages with the least transcribed data. Garo goes from a 69.4% word error rate on the next-best system to 9.5%. That’s not a model that got generically better. That’s a model that got specifically better at exactly the languages hardest to get better at through more data alone.
A gate, and a dependency neither flash dev run mentioned
SraVaani is gated on Hugging Face — auto-approved, no manual review, but still a real click a human has to make before any token can pull the weights. First checkpoint: does this project’s own HF_TOKEN already have access? A direct fetch answered that immediately.
One request to accept the license later, the same fetch returned 200. With access sorted, the worker — built directly off IndicWhisper’s shape, same GPU tier, same network-volume caching, same no-CPU-fallback posture — hit its first real failure on the very first flash dev call:
SraVaani ships custom modeling code, loaded via trust_remote_code=True, alongside a TorchScript-traced weights file rather than a standard checkpoint format — and that custom code needs sentencepiece for its tokenizer, a dependency transformers/torch/soundfile don’t pull in on their own.
One line added to the worker’s dependency list, one full restart — dependency changes don’t hot-reload the way function-body edits do — and the same Hindi test clip that had failed a moment earlier came back correctly transcribed.
Verifying without inventing a comparison
Testing needed real Hindi audio, and the project already had a way to make it without leaving the codebase: Kokoro’s hf_alpha voice, the same trick IndicWhisper’s own field notes used first.
Correct, minus the punctuation ASR doesn’t restore. Same result against the real deployed endpoint, not just the flash dev proxy, and the same result again through the actual Transcribe page in a browser, new “Indian languages (self-hosted)” toggle selected, zero console errors.
What didn’t happen: a formal side-by-side against IndicWhisper. Running one honestly would have meant redeploying an endpoint torn down after its own earlier verification, purely to generate a second number to compare against — a real GPU bill for a comparison the Hindi transcript above already answered qualitatively. IndicWhisper stays live. SraVaani was added as a second, parallel language="indic" path, not a replacement.
The test that didn’t prove anything, on purpose
One more request came in mid-verification: try Marwari specifically — TTS, then STT, round-trip. Marwari is genuinely on SraVaani’s list of 65. It is not on any TTS engine’s list this project has deployed — Kokoro speaks Hindi and nothing else Indian, Indic-Parler-TTS speaks four languages, none of them Marwari.
The honest option was the cloud voice model, with the honest caveat stated before running anything: it has no verified Marwari training. A best-effort translation went in. Something else came back.
The greeting landed. The name landed. The one phrase that’s actually distinct Marwari vocabulary, not shared with Hindi — “थे कियां हो,” how are you — came back garbled. Which side broke, the TTS mispronouncing a word it was never trained on or the ASR mishearing it, isn’t answerable from this one test. Calling it anything more than that would have turned a real curiosity into a fake result.
Where it landed
stt_sravaani.py mirrors stt_indicwhisper.py’s RunPod-calling shape exactly — no local fallback, same async /run plus poll pattern. POST /transcribe’s language field now carries a third value alongside the existing two: "hi" for IndicWhisper, "indic" for SraVaani’s own auto language ID across all 65, anything else falling through to OpenRouter Whisper unchanged. Verified: 155 passed, 8 skipped, zero regressions, plus the leaked flash dev endpoint — separate from the real one flash deploy creates, first documented deploying Kokoro — found and torn down for what is now the third time this stack has hit it.
Six models in, the bug is never in the paper. It’s always the thing that only shows up once something has to survive a cold start, a redeploy, and a teardown, unattended, on someone else’s infrastructure.
What’s different this time isn’t the shape of the bug — it’s the shape of the decision after fixing it. Not “does this replace what we had,” but “does this earn a place next to it” — verified on the one language that mattered most before touching anything, and left honestly unresolved on the one language nobody could actually check.