use rustc_hash::FxHashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WhisperVariant {
V1Multilingual,
V2Multilingual,
V3Multilingual,
EnglishOnly,
}
impl WhisperVariant {
pub fn from_name(name: &str) -> Option<Self> {
match name.to_ascii_lowercase().as_str() {
"whisper_v1" | "whisper-v1" | "whisper-multilingual-v1" => Some(Self::V1Multilingual),
"whisper_v2" | "whisper-v2" | "whisper" | "whisper-multilingual" => {
Some(Self::V2Multilingual)
}
"whisper_v3" | "whisper-v3" | "whisper-large-v3" => Some(Self::V3Multilingual),
"whisper_en" | "whisper.en" | "whisper-en" => Some(Self::EnglishOnly),
_ => None,
}
}
pub fn vocab_size(self) -> usize {
(self.first_timestamp_token_id() + 1500 + 1) as usize
}
pub fn sot_token_id(self) -> u32 {
match self {
Self::EnglishOnly => 50257,
_ => 50258,
}
}
pub fn eos_token_id(self) -> u32 {
self.sot_token_id() - 1
}
pub fn translate_token_id(self) -> u32 {
self.sot_token_id() + 1 + self.languages().len() as u32
}
pub fn transcribe_token_id(self) -> u32 {
self.translate_token_id() + 1
}
pub fn notimestamps_token_id(self) -> u32 {
self.transcribe_token_id() + 4
}
pub fn first_timestamp_token_id(self) -> u32 {
self.notimestamps_token_id() + 1
}
pub fn no_speech_token(self) -> &'static str {
match self {
Self::V1Multilingual | Self::EnglishOnly => "<|nocaptions|>",
Self::V2Multilingual | Self::V3Multilingual => "<|nospeech|>",
}
}
pub fn languages(self) -> &'static [&'static str] {
match self {
Self::V3Multilingual => WHISPER_LANGUAGES_V3,
Self::V1Multilingual | Self::V2Multilingual | Self::EnglishOnly => {
WHISPER_LANGUAGES_V1V2
}
}
}
pub fn language_token_id(self, code: &str) -> Option<u32> {
let code = if code == "iw" { "he" } else { code };
let pos = self.languages().iter().position(|&l| l == code)?;
Some(self.sot_token_id() + 1 + pos as u32)
}
}
pub fn whisper_special_tokens(variant: WhisperVariant) -> FxHashMap<String, u32> {
let mut m = FxHashMap::default();
m.insert("<|endoftext|>".to_string(), variant.eos_token_id());
m.insert("<|startoftranscript|>".to_string(), variant.sot_token_id());
let lang_base = variant.sot_token_id() + 1;
for (i, &lang) in variant.languages().iter().enumerate() {
m.insert(format!("<|{lang}|>"), lang_base + i as u32);
if lang == "he" {
m.insert("<|iw|>".to_string(), lang_base + i as u32);
}
}
m.insert("<|translate|>".to_string(), variant.translate_token_id());
m.insert("<|transcribe|>".to_string(), variant.transcribe_token_id());
let notimestamps = variant.notimestamps_token_id();
m.insert("<|startoflm|>".to_string(), notimestamps - 3);
m.insert("<|startofprev|>".to_string(), notimestamps - 2);
m.insert(variant.no_speech_token().to_string(), notimestamps - 1);
m.insert("<|notimestamps|>".to_string(), notimestamps);
let first_ts = variant.first_timestamp_token_id();
for k in 0..1501u32 {
let seconds = k as f32 * 0.02;
let label = format!("<|{seconds:.2}|>");
m.insert(label, first_ts + k);
}
m
}
#[rustfmt::skip]
pub const WHISPER_LANGUAGES_V1V2: &[&str] = &[
"en", "zh", "de", "es", "ru", "ko", "fr", "ja", "pt", "tr",
"pl", "ca", "nl", "ar", "sv", "it", "id", "hi", "fi", "vi",
"he", "uk", "el", "ms", "cs", "ro", "da", "hu", "ta", "no",
"th", "ur", "hr", "bg", "lt", "la", "mi", "ml", "cy", "sk",
"te", "fa", "lv", "bn", "sr", "az", "sl", "kn", "et", "mk",
"br", "eu", "is", "hy", "ne", "mn", "bs", "kk", "sq", "sw",
"gl", "mr", "pa", "si", "km", "sn", "yo", "so", "af", "oc",
"ka", "be", "tg", "sd", "gu", "am", "yi", "lo", "uz", "fo",
"ht", "ps", "tk", "nn", "mt", "sa", "lb", "my", "bo", "tl",
"mg", "as", "tt", "haw", "ln", "ha", "ba", "jw", "su",
];
#[rustfmt::skip]
pub const WHISPER_LANGUAGES_V3: &[&str] = &[
"en", "zh", "de", "es", "ru", "ko", "fr", "ja", "pt", "tr",
"pl", "ca", "nl", "ar", "sv", "it", "id", "hi", "fi", "vi",
"he", "uk", "el", "ms", "cs", "ro", "da", "hu", "ta", "no",
"th", "ur", "hr", "bg", "lt", "la", "mi", "ml", "cy", "sk",
"te", "fa", "lv", "bn", "sr", "az", "sl", "kn", "et", "mk",
"br", "eu", "is", "hy", "ne", "mn", "bs", "kk", "sq", "sw",
"gl", "mr", "pa", "si", "km", "sn", "yo", "so", "af", "oc",
"ka", "be", "tg", "sd", "gu", "am", "yi", "lo", "uz", "fo",
"ht", "ps", "tk", "nn", "mt", "sa", "lb", "my", "bo", "tl",
"mg", "as", "tt", "haw", "ln", "ha", "ba", "jw", "su", "yue",
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v1_v2_has_99_languages() {
assert_eq!(WHISPER_LANGUAGES_V1V2.len(), 99);
assert_eq!(WhisperVariant::V2Multilingual.languages().len(), 99);
}
#[test]
fn v3_has_100_languages() {
assert_eq!(WHISPER_LANGUAGES_V3.len(), 100);
assert_eq!(*WHISPER_LANGUAGES_V3.last().unwrap(), "yue");
}
#[test]
fn multilingual_eos_sot() {
for v in [
WhisperVariant::V1Multilingual,
WhisperVariant::V2Multilingual,
WhisperVariant::V3Multilingual,
] {
assert_eq!(v.eos_token_id(), 50257);
assert_eq!(v.sot_token_id(), 50258);
}
}
#[test]
fn english_only_eos_sot() {
assert_eq!(WhisperVariant::EnglishOnly.eos_token_id(), 50256);
assert_eq!(WhisperVariant::EnglishOnly.sot_token_id(), 50257);
}
#[test]
fn english_only_special_token_layout() {
let v = WhisperVariant::EnglishOnly;
let sp = whisper_special_tokens(v);
assert_eq!(v.languages().len(), 99);
assert_eq!(sp.get("<|startoftranscript|>"), Some(&50257));
assert_eq!(sp.get("<|en|>"), Some(&50258));
assert_eq!(sp.get("<|translate|>"), Some(&50357));
assert_eq!(sp.get("<|transcribe|>"), Some(&50358));
assert_eq!(sp.get("<|startoflm|>"), Some(&50359));
assert_eq!(sp.get("<|startofprev|>"), Some(&50360));
assert_eq!(sp.get("<|nocaptions|>"), Some(&50361));
assert_eq!(sp.get("<|nospeech|>"), None);
assert_eq!(sp.get("<|notimestamps|>"), Some(&50362));
assert_eq!(sp.get("<|0.00|>"), Some(&50363));
assert_eq!(sp.get("<|30.00|>"), Some(&51863));
}
#[test]
fn vocab_size_matches_last_timestamp() {
for v in [
WhisperVariant::V1Multilingual,
WhisperVariant::V2Multilingual,
WhisperVariant::V3Multilingual,
WhisperVariant::EnglishOnly,
] {
let sp = whisper_special_tokens(v);
let max_id = sp.values().copied().max().unwrap();
assert_eq!(v.vocab_size(), (max_id + 1) as usize);
}
assert_eq!(WhisperVariant::V2Multilingual.vocab_size(), 51865);
assert_eq!(WhisperVariant::V3Multilingual.vocab_size(), 51866);
assert_eq!(WhisperVariant::EnglishOnly.vocab_size(), 51864);
}
#[test]
fn language_token_id_lookup() {
assert_eq!(
WhisperVariant::V2Multilingual.language_token_id("en"),
Some(50259)
);
assert_eq!(
WhisperVariant::V3Multilingual.language_token_id("yue"),
Some(50259 + 99)
);
assert_eq!(
WhisperVariant::V2Multilingual.language_token_id("yue"),
None
);
}
#[test]
fn special_tokens_v2_coverage() {
let sp = whisper_special_tokens(WhisperVariant::V2Multilingual);
assert_eq!(sp.get("<|endoftext|>"), Some(&50257));
assert_eq!(sp.get("<|startoftranscript|>"), Some(&50258));
assert_eq!(sp.get("<|en|>"), Some(&50259));
assert_eq!(sp.get("<|translate|>"), Some(&50358));
assert_eq!(sp.get("<|transcribe|>"), Some(&50359));
assert_eq!(sp.get("<|nospeech|>"), Some(&50362));
assert_eq!(sp.get("<|notimestamps|>"), Some(&50363));
assert_eq!(sp.get("<|0.00|>"), Some(&50364));
assert_eq!(sp.get("<|30.00|>"), Some(&51864));
}
#[test]
fn special_tokens_v3_offsets() {
let sp = whisper_special_tokens(WhisperVariant::V3Multilingual);
assert_eq!(sp.get("<|yue|>"), Some(&(50259 + 99)));
assert_eq!(sp.get("<|translate|>"), Some(&50359));
assert_eq!(sp.get("<|notimestamps|>"), Some(&50364));
assert_eq!(sp.get("<|0.00|>"), Some(&50365));
}
#[test]
fn special_token_count() {
let sp = whisper_special_tokens(WhisperVariant::V2Multilingual);
assert_eq!(sp.len(), 2 + 99 + 4 + 1 + 1 + 1501 + 1);
}
#[test]
fn hebrew_iw_alias_matches_he() {
for v in [
WhisperVariant::V2Multilingual,
WhisperVariant::V3Multilingual,
WhisperVariant::EnglishOnly,
] {
let sp = whisper_special_tokens(v);
let he = sp.get("<|he|>").copied();
assert!(he.is_some());
assert_eq!(sp.get("<|iw|>").copied(), he);
}
}
#[test]
fn from_name_parses_aliases() {
assert_eq!(
WhisperVariant::from_name("whisper-v1"),
Some(WhisperVariant::V1Multilingual)
);
assert_eq!(
WhisperVariant::from_name("whisper_v1"),
Some(WhisperVariant::V1Multilingual)
);
assert_eq!(
WhisperVariant::from_name("Whisper"),
Some(WhisperVariant::V2Multilingual)
);
assert_eq!(
WhisperVariant::from_name("whisper-v2"),
Some(WhisperVariant::V2Multilingual)
);
assert_eq!(
WhisperVariant::from_name("whisper-large-v3"),
Some(WhisperVariant::V3Multilingual)
);
assert_eq!(
WhisperVariant::from_name("whisper.en"),
Some(WhisperVariant::EnglishOnly)
);
assert_eq!(WhisperVariant::from_name("not-a-model"), None);
}
}