use std::path::PathBuf;
use super::*;
use crate::audio::whisper::{
audio::vad::VoiceActivityDetector,
backend::{ModelDims, mock::MockBackend},
error::{SegmentError, TranscribeError, VadError},
options::{AlignmentGather, ChunkingStrategy, DecodingOptions},
tokenizer::{SpecialTokens, WhisperTokenizer},
};
fn tiny_tokenizer() -> WhisperTokenizer {
let root = std::env::var_os("WHISPERKIT_TEST_MODELS")
.map_or_else(crate::tests::models_root, PathBuf::from);
WhisperTokenizer::from_folder(root.join("tokenizers/whisper-tiny")).unwrap()
}
fn special() -> SpecialTokens {
SpecialTokens::whisper_defaults()
}
fn ts(index: u32) -> u32 {
special().time_token_begin() + index
}
fn script_clean_window(mock: &mut MockBackend, word: u32) {
let s = special();
mock.push_token_steps(&[
s.english_token(),
s.transcribe_token(),
ts(0),
word,
ts(100),
ts(100),
s.end_token(),
]);
}
fn script_clean_window_aligned(mock: &mut MockBackend, word: u32) {
let s = special();
let script = [
s.english_token(),
s.transcribe_token(),
ts(0),
word,
ts(100),
ts(100),
s.end_token(),
];
for (step, token) in script.iter().enumerate() {
let mut row = vec![0.0f32; 100];
row[step + 1] = 1.0;
mock.push_step_with_alignment(one_hot(*token), row);
}
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn audio_shorter_than_window_clip_time_decodes_one_window_clipped_to_it() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window_aligned(&mut mock, hello);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 14_400], &DecodingOptions::new())
.unwrap();
assert_eq!(mock.counters().encode_calls(), 1, "one window, not none");
assert_eq!(result.text(), "Hello");
assert_eq!(result.segments_slice().len(), 1);
let segment = &result.segments_slice()[0];
assert!((segment.start() - 0.0).abs() < 1e-4);
assert!(
(segment.end() - 0.9).abs() < 1e-4,
"clipped back to the 0.9 s of audio, got {}",
segment.end()
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn short_clips_each_decode_one_window_clipped_to_their_bounds() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(48_000)
.with_n_audio_ctx(100),
);
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window_aligned(&mut mock, hello);
let task = TranscribeTask::new(&mock, &t);
let clips = [(0.5_f32, 0.9_f32), (1.9, 2.65), (3.65, 5.2)];
let options = DecodingOptions::new().with_clip_timestamps(
clips
.iter()
.flat_map(|&(start, end)| [start, end])
.collect::<Vec<_>>(),
);
let result = task.run(&vec![0.1; 96_000], &options).unwrap();
assert_eq!(mock.counters().encode_calls(), 3, "one window per clip");
let spans: Vec<(f32, f32)> = result
.segments_slice()
.iter()
.map(|segment| (segment.start(), segment.end()))
.collect();
assert_eq!(spans.len(), 3, "one segment per clip, got {spans:?}");
for (index, (&(start, end), &(clip_start, clip_end))) in spans.iter().zip(&clips).enumerate() {
assert!(
(start - clip_start).abs() < 1e-3,
"segment {index} starts at its clip"
);
let expected_end = if index < 2 {
clip_end
} else {
clip_start + 2.0
};
assert!(
(end - expected_end).abs() < 1e-3,
"segment {index} ends at {end}, expected {expected_end}"
);
}
assert_eq!(result.text(), "Hello Hello Hello");
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_short_clip_on_the_default_options_loses_the_words_the_padding_holds() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let world = t.encode(" world").unwrap()[0];
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
let script: [(u32, core::ops::RangeInclusive<usize>); 8] = [
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(hello, 3..=25),
(world, 40..=60),
(ts(75), 61..=65),
(ts(75), 66..=66),
(s.end_token(), 67..=67),
];
for (token, frames) in script {
let mut row = vec![0.0f32; 100];
for frame in frames {
row[frame] = 1.0;
}
mock.push_step_with_alignment(one_hot(token), row);
}
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 6_400], &DecodingOptions::new())
.unwrap();
assert_eq!(result.text(), "Hello", "the padding's word is gone");
assert_eq!(result.segments_slice().len(), 1);
let segment = &result.segments_slice()[0];
assert!(
segment.end() <= 0.4 + 1e-4,
"cut at the clip, got {}",
segment.end()
);
assert!(
segment.words_slice().is_empty(),
"the caller asked for no words"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn an_unattributed_short_clip_keeps_no_text_past_its_last_timestamp_inside() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 14_400], &DecodingOptions::new())
.unwrap();
assert_eq!(mock.counters().encode_calls(), 1, "one window");
assert_eq!(result.text(), "", "no text the padding may hold");
assert!(result.segments_slice().is_empty());
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn an_unattributed_short_clip_keeps_no_text_that_no_timestamp_closes() {
let t = tiny_tokenizer();
let s = special();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
mock.push_token_steps(&[
s.english_token(),
s.transcribe_token(),
ts(0),
hello,
s.end_token(),
]);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 14_400], &DecodingOptions::new())
.unwrap();
assert_eq!(mock.counters().encode_calls(), 1, "one window");
assert_eq!(result.text(), "", "no text the padding may hold");
assert!(result.segments_slice().is_empty());
}
fn script_aligned(mock: &mut MockBackend, script: &[(u32, core::ops::RangeInclusive<usize>)]) {
for (token, frames) in script {
let mut row = vec![0.0f32; 100];
for frame in frames.clone() {
row[frame] = 1.0;
}
mock.push_step_with_alignment(one_hot(*token), row);
}
}
fn short_clip_transcript(
t: &WhisperTokenizer,
script: &[(u32, core::ops::RangeInclusive<usize>)],
) -> String {
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
script_aligned(&mut mock, script);
let task = TranscribeTask::new(&mock, t);
task
.run(&vec![0.1; 6_400], &DecodingOptions::new())
.unwrap()
.text()
.to_owned()
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_short_clip_keeps_a_word_the_visible_merge_drops() {
let t = tiny_tokenizer();
let s = special();
let token = |text: &str| t.encode(text).unwrap()[0];
let text = short_clip_transcript(
&t,
&[
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(token(" Hello"), 3..=10),
(token(")"), 11..=12),
(token("]"), 13..=25),
(token(" world"), 40..=60),
(ts(75), 61..=65),
(ts(75), 66..=66),
(s.end_token(), 67..=67),
],
);
assert_eq!(
text, "Hello)]",
"the padding's word is gone, the clip's kept"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_padding_word_the_visible_list_misplaces_still_leaves_its_own_segment() {
let t = tiny_tokenizer();
let s = special();
let token = |text: &str| t.encode(text).unwrap()[0];
let text = short_clip_transcript(
&t,
&[
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(token(" Hello"), 3..=8),
(token("!"), 9..=9),
(token("!"), 10..=10),
(ts(10), 11..=11),
(ts(10), 12..=25),
(token(" world"), 40..=60),
(ts(15), 61..=65),
(ts(15), 66..=66),
(s.end_token(), 67..=67),
],
);
assert_eq!(text, "Hello!!", "the padding's word does not come back");
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn an_inside_word_the_visible_list_misplaces_still_keeps_its_own_segment() {
let t = tiny_tokenizer();
let s = special();
let token = |text: &str| t.encode(text).unwrap()[0];
let text = short_clip_transcript(
&t,
&[
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(token(" Hello"), 3..=6),
(token("!"), 7..=7),
(token("!"), 8..=8),
(ts(10), 9..=9),
(ts(10), 10..=10),
(token(" world"), 11..=25),
(token(" there"), 40..=60),
(ts(15), 61..=65),
(ts(15), 66..=66),
(s.end_token(), 67..=67),
],
);
assert_eq!(text, "Hello!! world", "the inside word stays");
}
fn short_clip_transcript_with(
t: &WhisperTokenizer,
options: &DecodingOptions,
script: &[(u32, core::ops::RangeInclusive<usize>)],
) -> String {
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
script_aligned(&mut mock, script);
let task = TranscribeTask::new(&mock, t);
task
.run(&vec![0.1; 6_400], options)
.unwrap()
.text()
.to_owned()
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_window_decoded_behind_a_prompt_attributes_its_words_as_one_without() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let world = t.encode(" world").unwrap()[0];
let window: [(u32, core::ops::RangeInclusive<usize>); 8] = [
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(hello, 3..=25),
(world, 40..=60),
(ts(75), 61..=65),
(ts(75), 66..=66),
(s.end_token(), 67..=67),
];
let without = short_clip_transcript_with(&t, &DecodingOptions::new(), &window);
assert_eq!(without, "Hello", "the padding's word is gone");
let earlier = t.encode(" Earlier").unwrap()[0];
let words = t.encode(" words").unwrap()[0];
let prompted = DecodingOptions::new().with_prompt_tokens(vec![earlier, words]);
let prompt_steps = [
(earlier, 4..=6),
(words, 7..=9),
(s.start_of_transcript_token(), 10..=12),
];
let behind =
short_clip_transcript_with(&t, &prompted, &[&prompt_steps[..], &window[..]].concat());
assert_eq!(
behind, without,
"behind a prompt, the same words are attributed"
);
}
fn spread(
t: &WhisperTokenizer,
text: &str,
frames: core::ops::RangeInclusive<usize>,
) -> Vec<(u32, core::ops::RangeInclusive<usize>)> {
let tokens = t.encode(text).unwrap();
let (first, last) = (*frames.start(), *frames.end());
let share = (last + 1 - first) / tokens.len();
tokens
.iter()
.enumerate()
.map(|(index, &token)| {
let from = first + index * share;
let to = if index + 1 == tokens.len() {
last
} else {
from + share - 1
};
(token, from..=to)
})
.collect()
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_phrase_the_clip_cut_keeps_a_word_weighed_by_the_tokens_it_kept() {
let t = tiny_tokenizer();
let s = special();
let mut script = vec![
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
];
script.extend(spread(&t, "中", 3..=8));
script.extend(spread(&t, "文", 9..=25));
script.extend(spread(&t, "幻", 40..=48));
script.extend(spread(&t, "觉", 49..=60));
script.extend([
(ts(75), 61..=65),
(ts(75), 66..=66),
(s.end_token(), 67..=67),
]);
let (segments, words) = short_clip_words(
&t,
&DecodingOptions::new()
.with_language("zh")
.with_word_timestamps(),
&script,
);
assert_eq!(words, [["中文"]], "the kept part of the phrase");
let segment = &segments[0];
let kept: Vec<u32> = [&[ts(0)][..], &t.encode("中文").unwrap()].concat();
let logged: Vec<f32> = segment
.token_log_probs_slice()
.iter()
.skip_while(|&&(token, _)| token != ts(0))
.take(kept.len())
.map(|&(_, log_prob)| log_prob)
.collect();
assert_eq!(logged.len(), kept.len(), "{segment:?}");
let own = (logged.iter().sum::<f32>() / logged.len() as f32).exp();
let word = &segment.words_slice()[0];
assert!(
(word.probability() - (own * 100.0).round() / 100.0).abs() < 1e-6,
"weighed by its own {} tokens: {} for {own}",
kept.len(),
word.probability()
);
assert!(
word.end() <= segment.end(),
"it ends inside its segment: {word:?} in {segment:?}"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_word_grouped_with_a_restated_timestamp_is_weighed_by_its_own_text() {
let t = tiny_tokenizer();
let s = special();
let mut script = vec![
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(25), 2..=2),
];
script.extend(spread(&t, "中文", 3..=14));
script.extend([
(ts(40), 15..=17),
(ts(40), 18..=18),
(s.end_token(), 19..=19),
]);
let (segments, words) = short_clip_words(
&t,
&DecodingOptions::new()
.with_language("zh")
.with_word_timestamps(),
&script,
);
assert_eq!(words, [["中文"]], "the segment and its word kept");
let segment = &segments[0];
let text = t.encode("中文").unwrap();
let pairs = segment.token_log_probs_slice();
let at = pairs
.iter()
.position(|&(token, _)| token == ts(25))
.expect("the sampled timestamp's pair");
assert!(
segment.tokens_slice()[at] != ts(25) && segment.tokens_slice()[at] >= ts(0),
"its first timestamp restated: {segment:?}"
);
assert_eq!(
&segment.tokens_slice()[at + 1..=at + text.len()],
text.as_slice(),
"{segment:?}"
);
let sampled: Vec<f32> = pairs[at + 1..=at + text.len()]
.iter()
.map(|&(_, log_prob)| log_prob)
.collect();
let mean = |log_probs: &[f32]| log_probs.iter().sum::<f32>() / log_probs.len() as f32;
let rounded = |probability: f32| (probability * 100.0).round() / 100.0;
let own = rounded(mean(&sampled).exp());
let with_the_timestamp = rounded(mean(&[&[pairs[at].1][..], &sampled].concat()).exp());
assert_ne!(
own, with_the_timestamp,
"the timestamp's log probability tells the two apart"
);
let word = &segment.words_slice()[0];
assert!(
(word.probability() - own).abs() < 1e-6,
"weighed by 中 and 文 alone: {} for {own} (with the timestamp {with_the_timestamp})",
word.probability()
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_segment_whose_timestamps_run_backwards_is_retimed_from_its_raw_words() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let script = [
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(s.no_timestamps_token(), 2..=2),
(ts(15), 3..=3),
(hello, 4..=9),
(ts(5), 10..=10),
(ts(5), 11..=11),
(s.end_token(), 12..=12),
];
let (segments, words) = short_clip_words(
&t,
&DecodingOptions::new()
.with_without_timestamps()
.with_word_timestamps(),
&script,
);
let bounds: Vec<(f32, f32)> = segments
.iter()
.map(|segment| (segment.start(), segment.end()))
.collect();
assert_eq!(
words,
[[" Hello"]],
"the text kept, its word with it: {bounds:?}"
);
let (start, end) = bounds[0];
assert!(
start < end && end <= 0.4,
"retimed in order, inside the clip: {bounds:?}"
);
let word = &segments[0].words_slice()[0];
assert!(
start <= word.start() && word.start() <= word.end() && word.end() <= end,
"its word held inside its bounds: {word:?} in {bounds:?}"
);
let (plain, _) = short_clip_words(
&t,
&DecodingOptions::new().with_without_timestamps(),
&script,
);
let plain_bounds: Vec<(f32, f32)> = plain
.iter()
.map(|segment| (segment.start(), segment.end()))
.collect();
assert_eq!(
plain_bounds, bounds,
"the same segment with word timings or without"
);
}
#[test]
fn an_alignment_pass_is_recorded_with_its_time() {
let mut timings = crate::audio::whisper::result::TranscriptionTimings::new();
let pause = core::time::Duration::from_millis(5);
let answer = super::record_alignment_pass(&mut timings, || {
std::thread::sleep(pause);
7
});
assert_eq!(answer, 7, "the pass's own answer");
assert_eq!(timings.total_timestamp_alignment_runs(), 1.0, "one run");
let first = timings.decoding_word_timestamps();
assert!(first >= pause.as_secs_f64(), "its time: {first}");
super::record_alignment_pass(&mut timings, || std::thread::sleep(pause));
assert_eq!(timings.total_timestamp_alignment_runs(), 2.0, "two runs");
assert!(
timings.decoding_word_timestamps() >= first + pause.as_secs_f64(),
"both times: {}",
timings.decoding_word_timestamps()
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_short_clips_visible_words_are_an_alignment_pass_of_their_own() {
let t = tiny_tokenizer();
let script = hello_world_there(&t);
let run = |options: &DecodingOptions| {
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
script_aligned(&mut mock, &script);
TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 6_400], options)
.unwrap()
};
let plain = run(&DecodingOptions::new());
let worded = run(&DecodingOptions::new().with_word_timestamps());
assert!(
!worded.segments_slice().is_empty()
&& worded
.segments_slice()
.iter()
.any(|segment| !segment.words_slice().is_empty()),
"the clip keeps its text, and its words"
);
assert_eq!(
plain.timings().total_timestamp_alignment_runs(),
1.0,
"the clip-back's attribution alone"
);
assert_eq!(
worded.timings().total_timestamp_alignment_runs(),
2.0,
"the attribution, then the visible words' own alignment"
);
assert!(
worded.timings().decoding_word_timestamps() > 0.0,
"their time recorded"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_squeezed_word_at_the_clip_end_is_kept_with_word_timings_or_without() {
let t = tiny_tokenizer();
let s = special();
let hi = t.encode(" hi").unwrap()[0];
let row = |frames: &[(core::ops::RangeInclusive<usize>, f32)], rest: f32| {
let mut row = vec![rest; 100];
for (range, value) in frames {
for frame in range.clone() {
row[frame] = *value;
}
}
row
};
let steps: Vec<(u32, Vec<f32>)> = vec![
(s.english_token(), row(&[(0..=0, 1.0)], 0.0)),
(s.transcribe_token(), row(&[(1..=1, 1.0)], 0.0)),
(ts(25), row(&[(2..=19, 1.0)], -10.0)),
(hi, row(&[(20..=20, 0.5)], -10.0)),
(ts(45), row(&[(20..=40, 1.0)], 0.0)),
(ts(45), row(&[(41..=41, 1.0)], 0.0)),
(s.end_token(), row(&[(42..=42, 1.0)], 0.0)),
];
let run = |options: &DecodingOptions| {
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
for (token, row) in &steps {
mock.push_step_with_alignment(one_hot(*token), row.clone());
}
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 6_400], options)
.unwrap();
let segments: Vec<(String, f32, f32)> = result
.segments_slice()
.iter()
.map(|segment| (segment.text().to_owned(), segment.start(), segment.end()))
.collect();
(result.text().to_owned(), segments)
};
let without = run(&DecodingOptions::new());
assert_eq!(without.0, "hi", "the squeezed word is kept: {without:?}");
assert!(
without.1.iter().all(|(_, start, end)| start == end),
"its segment has no length: {without:?}"
);
let with = run(&DecodingOptions::new().with_word_timestamps());
assert_eq!(
with, without,
"the same transcript and segments with word timings"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn no_special_token_starts_ends_or_cuts_a_textual_raw_word() {
let t = tiny_tokenizer();
let s = special();
let mut script = vec![
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
];
script.extend(spread(&t, "中", 3..=8));
script.extend(spread(&t, "文", 9..=25));
script.extend(spread(&t, "幻", 40..=48));
script.extend(spread(&t, "觉", 49..=60));
script.extend([
(ts(75), 61..=65),
(ts(75), 66..=66),
(s.end_token(), 67..=67),
]);
let text = short_clip_transcript_with(&t, &DecodingOptions::new().with_language("zh"), &script);
assert_eq!(
text, "中文",
"the characters the padding holds go, exactly them"
);
}
fn hello_world_there(t: &WhisperTokenizer) -> Vec<(u32, core::ops::RangeInclusive<usize>)> {
let s = special();
let token = |text: &str| t.encode(text).unwrap()[0];
vec![
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(token(" Hello"), 3..=6),
(token("!"), 7..=7),
(token("!"), 8..=8),
(ts(10), 9..=9),
(ts(10), 10..=10),
(token(" world"), 11..=25),
(token(" there"), 40..=60),
(ts(15), 61..=65),
(ts(15), 66..=66),
(s.end_token(), 67..=67),
]
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn no_segment_is_rejected_on_bounds_the_visible_words_gave_it() {
let t = tiny_tokenizer();
let text = short_clip_transcript_with(
&t,
&DecodingOptions::new().with_word_timestamps(),
&hello_world_there(&t),
);
assert_eq!(text, "Hello!! world", "the inside word stays");
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn the_visible_words_are_derived_from_what_the_clip_back_kept() {
let t = tiny_tokenizer();
let s = special();
let a = t.encode(" A").unwrap()[0];
let period = t.encode(".").unwrap()[0];
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
script_aligned(
&mut mock,
&[
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
(a, 3..=22),
(period, 30..=40),
(ts(50), 41..=45),
(ts(50), 46..=46),
(s.end_token(), 47..=47),
],
);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(
&vec![0.1; 6_400],
&DecodingOptions::new().with_word_timestamps(),
)
.unwrap();
assert_eq!(result.text(), "A", "the padding's period is cut");
let words: Vec<(String, Vec<u32>)> = result
.segments_slice()
.iter()
.flat_map(|segment| {
segment
.words_slice()
.iter()
.map(|word| (word.word().to_owned(), word.tokens_slice().to_vec()))
})
.collect();
assert_eq!(
words,
[(" A".to_owned(), vec![a])],
"the visible words are the kept text's"
);
for segment in result.segments_slice() {
for word in segment.words_slice() {
assert!(
word
.tokens_slice()
.iter()
.all(|token| segment.tokens_slice().contains(token)),
"a word holds only tokens its segment kept: {words:?}"
);
}
}
}
fn short_clip_words(
t: &WhisperTokenizer,
options: &DecodingOptions,
script: &[(u32, core::ops::RangeInclusive<usize>)],
) -> (Vec<TranscriptionSegment>, Vec<Vec<String>>) {
clip_words(t, options, script, 6_400)
}
fn clip_words(
t: &WhisperTokenizer,
options: &DecodingOptions,
script: &[(u32, core::ops::RangeInclusive<usize>)],
samples: usize,
) -> (Vec<TranscriptionSegment>, Vec<Vec<String>>) {
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
script_aligned(&mut mock, script);
let result = TranscribeTask::new(&mock, t)
.run(&vec![0.1; samples], options)
.unwrap();
let segments = result.segments_slice().to_vec();
let words = segments
.iter()
.map(|segment| {
segment
.words_slice()
.iter()
.map(|word| word.word().to_owned())
.collect()
})
.collect();
(segments, words)
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn each_segment_derives_its_visible_words_from_its_own_words() {
let t = tiny_tokenizer();
let (segments, words) = short_clip_words(
&t,
&DecodingOptions::new().with_word_timestamps(),
&hello_world_there(&t),
);
assert_eq!(words.len(), 2, "both segments kept: {words:?}");
assert_eq!(
words[1],
[" world"],
"the second segment keeps its own word: {words:?}"
);
for segment in &segments {
for word in segment.words_slice() {
assert!(
word
.tokens_slice()
.iter()
.all(|token| segment.tokens_slice().contains(token)),
"a word holds only tokens of its segment: {words:?}"
);
}
}
}
type TimedWord = (String, f32, f32);
fn timed_words(
t: &WhisperTokenizer,
options: &DecodingOptions,
script: &[(u32, core::ops::RangeInclusive<usize>)],
samples: usize,
) -> (Vec<(f32, f32)>, Vec<Vec<TimedWord>>) {
let (segments, _) = clip_words(t, options, script, samples);
let bounds = segments
.iter()
.map(|segment| (segment.start(), segment.end()))
.collect();
let words = segments
.iter()
.map(|segment| {
segment
.words_slice()
.iter()
.map(|word| (word.word().to_owned(), word.start(), word.end()))
.collect()
})
.collect();
(bounds, words)
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_clipped_window_groups_its_visible_words_as_the_caller_asked() {
let t = tiny_tokenizer();
let s = special();
let mut script = vec![
(s.english_token(), 0..=0),
(s.transcribe_token(), 1..=1),
(ts(0), 2..=2),
];
script.extend(spread(&t, "中文字幕", 3..=14));
script.extend([
(ts(15), 15..=17),
(ts(15), 18..=18),
(s.end_token(), 19..=19),
]);
let options = DecodingOptions::new()
.with_language("zh")
.with_word_timestamps()
.with_alignment_gather(AlignmentGather::SwiftParity);
let (unclipped_bounds, unclipped) = timed_words(&t, &options, &script, 19_200);
let (clipped_bounds, clipped) = timed_words(&t, &options, &script, 6_400);
let texts = |words: &[Vec<TimedWord>]| {
words
.iter()
.map(|segment| {
segment
.iter()
.map(|(word, _, _)| word.clone())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
};
assert_eq!(
texts(&unclipped),
[["中文字幕"]],
"the caller's grouping: one word from the timestamp on"
);
assert_eq!(
texts(&clipped),
texts(&unclipped),
"the clipped window groups as the window that is not"
);
assert_eq!(
clipped_bounds.len(),
1,
"one segment: {clipped_bounds:?} (unclipped {unclipped_bounds:?})"
);
let (start, end) = clipped_bounds[0];
assert!(
start == 0.0 && (end - 0.3).abs() < 1e-6,
"its own bounds, <|0.00|> to <|0.30|>: {clipped_bounds:?}"
);
let held: Vec<Vec<TimedWord>> = unclipped
.iter()
.map(|segment| {
segment
.iter()
.map(|(word, from, to)| (word.clone(), from.clamp(start, end), to.clamp(start, end)))
.collect()
})
.collect();
assert_eq!(
clipped, held,
"timed as the window that is not, through the caller's gather, held in its bounds"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn zero_window_run_observes_no_language_in_provenance() {
let t = tiny_tokenizer();
let mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let task = TranscribeTask::new(&mock, &t);
let result = task.run(&[], &DecodingOptions::new()).unwrap();
assert_eq!(mock.counters().encode_calls(), 0, "no window decoded");
assert_eq!(result.language(), "en", "the display fallback is kept");
assert_eq!(
result.task_facts().observed_language(),
None,
"nothing was observed, so the result records no detected language"
);
assert_eq!(
result.task_facts().drew_from_rng(),
Some(false),
"a run that decoded no window POSITIVELY drew nothing"
);
assert_eq!(
result.task_facts().early_stopped(),
Some(false),
"and was truncated by nothing"
);
let provenance = crate::audio::whisper::provenance::Provenance::for_result(
&DecodingOptions::new(),
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
);
assert_eq!(
provenance.task_facts().observed_language(),
None,
"and neither does the provenance -- absent, not fabricated"
);
assert!(
provenance.is_reproducible(),
"an honest zero-window run reproduces byte-for-byte -- it did nothing to redo"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn single_window_run_produces_segments_and_text() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 32_000], &DecodingOptions::new())
.unwrap();
assert_eq!(result.text(), "Hello"); assert_eq!(result.segments_slice().len(), 1);
let segment = &result.segments_slice()[0];
assert!((segment.start() - 0.0).abs() < 1e-4);
assert!((segment.end() - 2.0).abs() < 1e-4);
assert_eq!(mock.counters().resets(), 1, "state reset after the window");
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn windowing_advances_seek_by_last_timestamp_and_decodes_again() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
let s = special();
mock.push_token_steps(&[
s.english_token(),
s.transcribe_token(),
ts(0),
hello,
ts(50), ts(50),
s.end_token(),
]);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 48_000], &DecodingOptions::new())
.unwrap();
assert_eq!(result.segments_slice().len(), 2);
assert!(
(result.segments_slice()[1].start() - 1.0).abs() < 1e-4,
"time offset applied"
);
assert_eq!(mock.counters().encode_calls(), 2);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn fallback_ladder_retries_with_rising_temperature_then_accepts() {
let t = tiny_tokenizer();
let s = special();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
for _ in 0..3 {
for _ in 0..4 {
let mut flat = vec![0.0f32; 51865];
flat[s.end_token() as usize] = -20.0;
mock.push_step(flat);
}
mock.push_token_step(s.end_token());
}
let options = DecodingOptions::new()
.with_without_timestamps()
.maybe_first_token_logprob_threshold(None)
.maybe_compression_ratio_threshold(None)
.with_temperature_fallback_count(2); let task = TranscribeTask::new(&mock, &t);
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
let counters = mock.counters();
assert_eq!(
counters.resets(),
3 + 1,
"3 fallback resets + 1 window reset"
);
assert_eq!(
result.segments_slice().len(),
1,
"lump branch: one full-window segment"
);
assert!(
(result.segments_slice()[0].temperature() - 0.4).abs() < 1e-3,
"final attempt at temperature 0.0 + 2 * 0.2"
);
}
fn script_exhausting_fallback_window(mock: &mut MockBackend, end_token: u32) {
for _ in 0..3 {
for _ in 0..4 {
let mut flat = vec![0.0f32; 51865];
flat[end_token as usize] = -20.0;
mock.push_step(flat);
}
mock.push_token_step(end_token);
}
}
fn exhausting_fallback_options(seed: Option<u64>) -> DecodingOptions {
DecodingOptions::new()
.with_temperature(0.3)
.with_without_timestamps()
.maybe_first_token_logprob_threshold(None)
.maybe_compression_ratio_threshold(None)
.with_temperature_fallback_count(2) .maybe_seed(seed)
}
fn run_exhausting_fallback(t: &WhisperTokenizer, seed: Option<u64>) -> TranscriptionResult {
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_exhausting_fallback_window(&mut mock, special().end_token());
let task = TranscribeTask::new(&mock, t);
let audio = vec![0.1f32; 32_000];
task
.run(&audio, &exhausting_fallback_options(seed))
.unwrap()
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn seeded_fallback_ladder_is_bit_reproducible_across_runs() {
let t = tiny_tokenizer();
let runs: Vec<TranscriptionResult> = (0..4)
.map(|_| run_exhausting_fallback(&t, Some(7)))
.collect();
let reference = &runs[0];
assert_eq!(reference.segments_slice().len(), 1);
let reference_tokens = reference.segments_slice()[0].tokens_slice();
for (index, run) in runs.iter().enumerate().skip(1) {
assert_eq!(run.segments_slice().len(), 1);
assert_eq!(
run.segments_slice()[0].tokens_slice(),
reference_tokens,
"run {index} diverged from run 0: same seed, same fallback ladder -> byte-identical sampled tokens"
);
assert_eq!(run.text(), reference.text(), "run {index} text diverged");
}
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn seeded_fallback_ladder_differs_across_seeds() {
let t = tiny_tokenizer();
let a = run_exhausting_fallback(&t, Some(7));
let b = run_exhausting_fallback(&t, Some(99));
assert_ne!(
a.segments_slice()[0].tokens_slice(),
b.segments_slice()[0].tokens_slice(),
"different seeds must not sample the same tokens"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn unseeded_fallback_ladder_still_runs_without_threading_a_seed() {
let t = tiny_tokenizer();
let result = run_exhausting_fallback(&t, None);
assert_eq!(result.segments_slice().len(), 1, "lump branch, as scripted");
assert!(
(result.segments_slice()[0].temperature() - (0.3 + 2.0 * 0.2)).abs() < 1e-3,
"ladder still exhausts to the final (highest-temperature) attempt"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn compression_failure_marks_swallowed_error() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let build = || {
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
for _ in 0..2 {
mock.push_token_steps(&[2425, 2425, 2425, hello, s.end_token()]);
}
mock
};
let options = DecodingOptions::new()
.with_temperature(0.3)
.with_without_timestamps()
.maybe_first_token_logprob_threshold(None)
.maybe_logprob_threshold(None)
.with_temperature_fallback_count(1) .with_seed(7);
let compute = crate::audio::whisper::options::ComputeOptions::new();
crate::audio::whisper::text::fault::reset_compression_faults();
crate::audio::whisper::text::fault::fail_compression_on_call(1);
let mock1 = build();
let result1 = TranscribeTask::new(&mock1, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
crate::audio::whisper::text::fault::reset_compression_faults();
assert_eq!(
result1.task_facts().had_swallowed_error(),
Some(true),
"attempt 0's swallowed compression error -- its +inf ratio drove the fallback -- must be recorded"
);
assert!(
(result1.segments_slice()[0].temperature() - 0.5).abs() < 1e-3,
"the swallowed +inf rejected attempt 0 (0.3); attempt 1 (0.5) was accepted"
);
assert!(
!crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &result1)
.is_reproducible(),
"a transcript that hinged on a swallowed OS error is not byte-reproducible"
);
let mock2 = build();
let result2 = TranscribeTask::new(&mock2, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_ne!(
result2.task_facts().had_swallowed_error(),
Some(true),
"a clean run swallowed nothing"
);
assert!(
(result2.segments_slice()[0].temperature() - 0.3).abs() < 1e-3,
"with no swallowed error attempt 0 (0.3) is accepted directly -- no fallback"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn segment_discovery_callback_fires_per_window() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let discovered = std::sync::Mutex::new(Vec::<usize>::new());
let callback: &(dyn Fn(&[crate::audio::whisper::result::TranscriptionSegment]) + Sync) =
&|segments| discovered.lock().unwrap().push(segments.len());
let task = TranscribeTask::new(&mock, &t).with_segment_callback(callback);
task
.run(&vec![0.1; 32_000], &DecodingOptions::new())
.unwrap();
assert_eq!(discovered.lock().unwrap().as_slice(), &[1]);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn failed_probe_rederives_language_from_that_attempts_decode() {
let t = tiny_tokenizer();
let es = t.token_to_id("<|es|>").unwrap();
let hello = t.encode(" Hello").unwrap()[0];
let s = special();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
mock.push_token_steps(&[
es,
s.transcribe_token(),
ts(0),
hello,
ts(100),
ts(100),
s.end_token(),
]);
mock.fail_on_call(3);
let options = DecodingOptions::new()
.with_detect_language()
.with_temperature_fallback_count(1)
.maybe_first_token_logprob_threshold(Some(-0.5))
.maybe_logprob_threshold(None);
let task = TranscribeTask::new(&mock, &t);
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(
result.language(),
"en",
"stale probe language must not survive a failed re-probe"
);
assert_eq!(result.timings().total_decoding_fallbacks(), 0.0);
assert_eq!(mock.counters().resets(), 4);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn detect_language_pinned_deviation_actually_runs_the_probe() {
let t = tiny_tokenizer();
let hello = t.encode(" Hello").unwrap()[0];
let base_options = || {
DecodingOptions::new()
.maybe_use_prefill_prompt(false)
.with_temperature_fallback_count(0)
.maybe_first_token_logprob_threshold(Some(-0.5))
.maybe_logprob_threshold(None)
};
let mut no_probe = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
no_probe.push_token_step(hello);
let options_no_probe = base_options().maybe_detect_language(false);
assert!(!options_no_probe.detect_language());
TranscribeTask::new(&no_probe, &t)
.run(&vec![0.1; 32_000], &options_no_probe)
.unwrap();
let mut with_probe = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
with_probe.push_token_step(hello);
let options_with_probe = base_options();
assert!(options_with_probe.detect_language());
TranscribeTask::new(&with_probe, &t)
.run(&vec![0.1; 32_000], &options_with_probe)
.unwrap();
let base = no_probe.counters();
let probed = with_probe.counters();
assert_eq!(
probed.decode_steps(),
base.decode_steps() + 1,
"probe adds exactly one decode_step call"
);
assert_eq!(
probed.resets(),
base.resets() + 1,
"probe adds exactly one reset_decoder_state call"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn all_masked_probe_does_not_fabricate_language() {
use crate::audio::whisper::backend::InferenceBackend;
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let hello = t.encode(" Hello").unwrap()[0];
let all_masked = |vocab: usize| vec![f32::NEG_INFINITY; vocab];
let mut probe_mock = MockBackend::new();
let vocab = probe_mock.dims().vocab();
probe_mock.push_step(all_masked(vocab));
let features = probe_mock.extract_features(&[0.0f32; 16]).unwrap();
let encoded = probe_mock.encode(&features).unwrap();
let mut state = probe_mock.new_decoder_state().unwrap();
let mut sampler = GreedyTokenSampler::new(0.0, s.end_token(), &DecodingOptions::new());
let mut timings = TranscriptionTimings::new();
let probe = decode::detect_language(
&probe_mock,
&encoded,
&mut state,
&t,
&mut sampler,
&mut timings,
)
.unwrap();
assert!(
probe.language_probs_slice().is_empty(),
"an all-masked probe sampled no language token, so it recorded no probability"
);
assert_eq!(
probe.language(),
crate::audio::whisper::constants::DEFAULT_LANGUAGE_CODE,
"with nothing sampled the probe reports the Swift-faithful \"en\" DISPLAY default"
);
let options = DecodingOptions::new()
.with_detect_language()
.with_without_timestamps()
.maybe_first_token_logprob_threshold(None);
let mut mock_a = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let vocab_a = mock_a.dims().vocab();
mock_a.push_step(all_masked(vocab_a)); mock_a.push_token_steps(&[2425, 2425, hello, s.end_token()]);
let result_a = TranscribeTask::new(&mock_a, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
result_a.language(),
crate::audio::whisper::constants::DEFAULT_LANGUAGE_CODE,
"the display language is the Swift-faithful \"en\" default"
);
assert_eq!(
result_a.task_facts().observed_language(),
None,
"an all-masked probe observed nothing, so no language may be recorded"
);
let mut mock_b = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let vocab_b = mock_b.dims().vocab();
mock_b.push_step(all_masked(vocab_b));
mock_b.push_token_steps(&[2425, 2425, es, s.end_token()]);
let result_b = TranscribeTask::new(&mock_b, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
result_b.language(),
crate::audio::whisper::constants::DEFAULT_LANGUAGE_CODE,
"the display language is still the forced-prefill \"en\""
);
assert_eq!(
result_b.task_facts().observed_language(),
Some("es"),
"the genuinely predicted \"es\" is the observation, not a fabricated \"en\""
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn genuine_observation_survives_a_later_failed_probe() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
mock.push_token_steps(&[
es,
s.transcribe_token(),
ts(0),
hello,
ts(50),
ts(50),
s.end_token(),
]);
mock.fail_on_call(9);
let options = DecodingOptions::new().with_detect_language();
let task = TranscribeTask::new(&mock, &t);
let result = task.run(&vec![0.1; 48_000], &options).unwrap();
assert_eq!(mock.counters().encode_calls(), 2, "two windows decoded");
assert_eq!(
result.task_facts().observed_language(),
Some("es"),
"window 1's genuine detection must survive window 2's failed probe"
);
}
fn probe_logprob_for(t: &WhisperTokenizer, token: u32) -> f32 {
use crate::audio::whisper::backend::InferenceBackend;
let mut mock = MockBackend::new();
mock.push_token_step(token);
let features = mock.extract_features(&[0.0f32; 16]).unwrap();
let encoded = mock.encode(&features).unwrap();
let mut state = mock.new_decoder_state().unwrap();
let mut sampler = GreedyTokenSampler::new(0.0, special().end_token(), &DecodingOptions::new());
let mut timings = TranscriptionTimings::new();
let probe =
decode::detect_language(&mock, &encoded, &mut state, t, &mut sampler, &mut timings).unwrap();
let probs = probe.language_probs_slice();
assert_eq!(
probs.len(),
1,
"a one-hot probe records exactly one language"
);
probs[0].1
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn every_kept_probe_reaches_the_result_as_a_spanned_observation() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let ja = t.token_to_id("<|ja|>").unwrap();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new()
.with_dims(ModelDims::new().with_window_samples(16_000))
.with_continuous_script();
for language in [es, ja] {
mock.push_token_steps(&[
language, language,
s.transcribe_token(),
ts(0),
hello,
ts(50),
ts(50),
s.end_token(),
]);
}
let options = DecodingOptions::new().with_detect_language();
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 48_000], &options)
.unwrap();
assert_eq!(mock.counters().encode_calls(), 2, "two windows decoded");
assert_eq!(
result.language(),
"ja",
"the DISPLAY language is still last-write-wins -- window 2's probe"
);
assert_eq!(
result.task_facts().observed_language(),
Some("es"),
"the OBSERVATION is still first-genuine-wins -- window 1's probe"
);
let observations = result.language_observations_slice();
assert_eq!(
observations.len(),
2,
"one entry per window that kept a probe"
);
let languages: Vec<&str> = observations
.iter()
.map(|observation| observation.detection().language())
.collect();
assert_eq!(languages, ["es", "ja"], "in window order, not fold order");
let spans: Vec<(f32, f32)> = observations
.iter()
.map(|observation| (observation.start(), observation.end()))
.collect();
for (index, (expected, actual)) in [(0.0f32, 1.0f32), (1.0, 2.0)]
.into_iter()
.zip(spans.iter().copied())
.enumerate()
{
assert!(
(expected.0 - actual.0).abs() < 1e-3 && (expected.1 - actual.1).abs() < 1e-3,
"window {index} spans the audio it decoded: expected {expected:?}, got {actual:?}"
);
}
for (observation, token) in observations.iter().zip([es, ja]) {
let probs = observation.detection().probs_slice();
assert_eq!(
probs.len(),
1,
"the probe records its single sampled language"
);
assert_eq!(probs[0].0, observation.detection().language());
assert!(
(probs[0].1 - probe_logprob_for(&t, token)).abs() < 1e-6,
"the carried confidence is the probe's own, got {}",
probs[0].1
);
}
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn an_observation_spans_exactly_what_its_own_window_segments_do() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new()
.with_dims(ModelDims::new().with_window_samples(16_000))
.with_continuous_script();
for _ in 0..2 {
mock.push_token_steps(&[es, es, s.transcribe_token(), hello, s.end_token()]);
}
let options = DecodingOptions::new()
.with_detect_language()
.with_window_clip_time(0.0);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 24_000], &options)
.unwrap();
assert_eq!(
mock.counters().encode_calls(),
2,
"a full 1 s window and a 0.5 s tail"
);
let observations = result.language_observations_slice();
assert_eq!(observations.len(), 2, "both windows kept a probe");
let segments = result.segments_slice();
for (index, (seek, samples)) in [(0usize, 16_000usize), (16_000, 8_000)]
.into_iter()
.enumerate()
{
let expected = segment::window_span(seek, samples);
let observation = &observations[index];
assert_eq!(
(observation.start(), observation.end()),
expected,
"window {index} observation spans the audio it held, not the zero pad"
);
let window_segments: Vec<&TranscriptionSegment> = segments
.iter()
.filter(|segment| segment.seek() == seek)
.collect();
assert_eq!(
window_segments.len(),
1,
"window {index} lumps into one segment"
);
assert_eq!(
(window_segments[0].start(), window_segments[0].end()),
expected,
"window {index} segment carries the SAME span its observation does"
);
}
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn no_probe_records_no_observation() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let options = DecodingOptions::new();
assert!(
!options.detect_language(),
"no probe: prefill is on by default"
);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(mock.counters().encode_calls(), 1, "one window decoded");
assert_eq!(result.language(), "en", "the display fallback is kept");
assert!(
result.language_observations_slice().is_empty(),
"no probe ran, so no observation may be recorded"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_retried_window_records_only_the_kept_attempts_probe() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let mut mock = MockBackend::new()
.with_dims(ModelDims::new().with_window_samples(16_000))
.with_continuous_script();
for _ in 0..2 {
mock.push_token_step(es);
for _ in 0..4 {
let mut flat = vec![0.0f32; 51865];
flat[s.end_token() as usize] = -20.0;
mock.push_step(flat);
}
mock.push_token_step(s.end_token());
}
let options = DecodingOptions::new()
.with_detect_language()
.with_without_timestamps()
.maybe_first_token_logprob_threshold(None)
.maybe_compression_ratio_threshold(None)
.with_temperature_fallback_count(1); let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(mock.counters().encode_calls(), 1, "one window, retried");
assert_eq!(
mock.counters().decode_steps(),
12,
"two attempts, each a probe step plus five decode steps"
);
let observations = result.language_observations_slice();
assert_eq!(observations.len(), 1, "one window, one kept probe");
assert_eq!(observations[0].detection().language(), "es");
assert!(
(observations[0].start() - 0.0).abs() < 1e-3 && (observations[0].end() - 1.0).abs() < 1e-3,
"the observation spans the window, not an attempt: got {:?}",
(observations[0].start(), observations[0].end())
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn vad_chunked_observations_reanchor_and_concatenate() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
mock.push_token_steps(&[
es,
s.transcribe_token(),
ts(0),
hello,
ts(100),
ts(100),
s.end_token(),
]);
let kit = WhisperKit::with_backend(mock, t);
let mut audio = vec![0.1f32; 96_000];
audio[32_000..35_200].fill(0.0);
audio[64_000..67_200].fill(0.0);
let options = DecodingOptions::new()
.with_chunking_strategy(ChunkingStrategy::Vad)
.with_detect_language();
let result = kit.transcribe(&audio, &options).unwrap();
assert_eq!(result.text(), "Hello Hello Hello", "per-chunk texts joined");
let observations = result.language_observations_slice();
assert_eq!(observations.len(), 3, "every chunk's kept probe is carried");
for observation in observations {
assert_eq!(observation.detection().language(), "es");
}
let starts: Vec<f32> = observations
.iter()
.map(crate::audio::whisper::transcribe::LanguageObservation::start)
.collect();
for (index, expected) in [0.0f32, 2.1, 4.1].into_iter().enumerate() {
assert!(
(starts[index] - expected).abs() < 1e-3,
"chunk {index} observation re-anchored to {expected}, got {}",
starts[index]
);
}
let ends: Vec<f32> = observations
.iter()
.map(crate::audio::whisper::transcribe::LanguageObservation::end)
.collect();
for (index, expected) in [2.1f32, 4.1, 6.0].into_iter().enumerate() {
assert!(
(ends[index] - expected).abs() < 1e-3,
"chunk {index} observation ends at {expected}, got {}",
ends[index]
);
}
assert_eq!(
result.task_facts().observed_language(),
Some("es"),
"the folded observation is unchanged by the list beside it"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_predicted_language_is_recorded_over_the_forced_display_language() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
mock.push_token_steps(&[
s.english_token(),
s.transcribe_token(),
s.no_timestamps_token(),
es,
2425,
s.end_token(),
]);
let options = DecodingOptions::new().with_without_timestamps();
assert!(
!options.detect_language(),
"no probe: prefill is on by default"
);
let task = TranscribeTask::new(&mock, &t);
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(
result.language(),
"en",
"the DISPLAY language is the forced-prefill <|en|>, kept Swift-faithful"
);
assert_eq!(
result.task_facts().observed_language(),
Some("es"),
"the DETECTION is the PREDICTED <|es|>, not the forced display <|en|>"
);
assert_eq!(
crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
)
.task_facts()
.observed_language(),
Some("es"),
"provenance records the predicted detection, never the forced display"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn transcribe_all_preserves_order_across_scoped_threads() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let kit = WhisperKit::with_backend(mock, t);
let a = vec![0.1f32; 32_000];
let b = vec![0.1f32; 32_000];
let results = kit.transcribe_all(&[&a, &b], &DecodingOptions::new());
assert_eq!(results.len(), 2);
for result in results {
assert_eq!(result.unwrap().text(), "Hello");
}
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn vad_chunked_transcribe_reanchors_and_merges() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let kit = WhisperKit::with_backend(mock, t);
let mut audio = vec![0.1f32; 96_000];
audio[32_000..35_200].fill(0.0);
audio[64_000..67_200].fill(0.0);
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let result = kit.transcribe(&audio, &options).unwrap();
assert_eq!(result.text(), "Hello Hello Hello", "per-chunk texts joined");
let segments = result.segments_slice();
assert_eq!(segments.len(), 3);
let ids: Vec<usize> = segments.iter().map(|s| s.id()).collect();
assert_eq!(
ids,
vec![0, 1, 2],
"merge re-ids result_index + segment_index"
);
let starts: Vec<f32> = segments.iter().map(|s| s.start()).collect();
assert!((starts[0] - 0.0).abs() < 1e-3);
assert!(
(starts[1] - 2.1).abs() < 1e-3,
"chunk 2 re-anchored, got {}",
starts[1]
);
assert!(
(starts[2] - 4.1).abs() < 1e-3,
"chunk 3 re-anchored, got {}",
starts[2]
);
assert_eq!(
result.task_facts().worker_schedule(),
Some([0, 1, 2].as_slice()),
"every chunk survived -- the ordered coordinates concatenate to [0, 1, 2]",
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn worker_schedule_is_unknown_when_a_vad_chunk_errors() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
mock.fail_on_call(1); let kit = WhisperKit::with_backend(mock, t);
let mut audio = vec![0.1f32; 96_000];
audio[32_000..35_200].fill(0.0);
audio[64_000..67_200].fill(0.0);
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let result = kit.transcribe(&audio, &options).unwrap();
assert_eq!(
result.text(),
"Hello Hello",
"chunk 0 errored and was dropped; only chunks 1 and 2 survive",
);
assert_eq!(
result.task_facts().worker_schedule(),
None,
"an errored chunk taints the ordered schedule to unknown -- NOT the survivors' [1, 2]",
);
assert_eq!(
result.task_facts().had_swallowed_error(),
Some(true),
"the dropped chunk's swallowed error is recorded as an outcome fact",
);
assert_eq!(
result.task_facts().decoded_span(),
SpanKnowledge::AtLeast(2),
"an errored chunk leaves the exact total unknown, but the two surviving chunks' \
ordinals still lower-bound the run's span (round 12; was the bound-less None)",
);
let compute = crate::audio::whisper::options::ComputeOptions::new();
assert!(
!crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &result)
.is_reproducible(),
"a run that silently dropped a chunk cannot promise byte reproducibility",
);
let second = kit.transcribe(&audio, &options).unwrap();
assert_eq!(
second.text(),
"Hello Hello Hello",
"the second same-input call keeps chunk 0 -- a different transcript",
);
assert_ne!(
result.text(),
second.text(),
"same object, audio, and options -> different output: not reproducible",
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn vad_detector_swap_changes_chunk_boundaries() {
struct AlwaysActiveVad;
impl VoiceActivityDetector for AlwaysActiveVad {
fn voice_activity(&self, samples: &[f32]) -> Vec<bool> {
vec![true; samples.len().div_ceil(self.frame_length_samples())]
}
fn frame_length_samples(&self) -> usize {
crate::audio::whisper::audio::vad::DEFAULT_FRAME_LENGTH_SAMPLES
}
}
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let kit = WhisperKit::with_backend(mock, t).with_vad_detector(Box::new(AlwaysActiveVad));
let mut audio = vec![0.1f32; 96_000];
audio[32_000..35_200].fill(0.0);
audio[64_000..67_200].fill(0.0);
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let result = kit.transcribe(&audio, &options).unwrap();
assert_eq!(result.text(), "Hello Hello", "2 whole-window chunks, not 3");
let segments = result.segments_slice();
assert_eq!(segments.len(), 2);
assert!((segments[0].start() - 0.0).abs() < 1e-3);
assert!(
(segments[1].start() - 3.0).abs() < 1e-3,
"chunk 2 starts at the whole-window boundary (48_000 / 16_000), got {}",
segments[1].start()
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn transcribe_surfaces_a_latched_vad_failure_instead_of_ok() {
#[derive(Default)]
struct FailingVad {
fired: std::sync::Mutex<bool>,
}
impl VoiceActivityDetector for FailingVad {
fn voice_activity(&self, samples: &[f32]) -> Vec<bool> {
*self.fired.lock().unwrap() = true;
vec![false; samples.len().div_ceil(self.frame_length_samples())]
}
fn frame_length_samples(&self) -> usize {
crate::audio::whisper::audio::vad::DEFAULT_FRAME_LENGTH_SAMPLES
}
fn detection_generation(&self) -> u64 {
u64::from(*self.fired.lock().unwrap())
}
fn last_detection_error(&self) -> Option<Box<dyn std::error::Error + Send + Sync + 'static>> {
self
.fired
.lock()
.unwrap()
.then(|| "scripted VAD inference failure".into())
}
}
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let kit = WhisperKit::with_backend(mock, t).with_vad_detector(Box::new(FailingVad::default()));
let audio = vec![0.1f32; 96_000];
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let error = kit
.transcribe(&audio, &options)
.expect_err("a latched hard VAD failure must fail transcription, not return Ok");
assert!(
matches!(error, TranscribeError::Vad(VadError::Detection(_))),
"expected TranscribeError::Vad, got {error:?}"
);
assert!(
error
.to_string()
.contains("voice-activity detection failed"),
"the error should render the VAD stage, got: {error}"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn concurrent_transcribe_cannot_steal_a_latched_vad_failure() {
use std::sync::{
Arc, Barrier, Mutex,
atomic::{AtomicBool, Ordering},
};
struct Shared {
generation: Mutex<u64>,
last_error: Mutex<Option<String>>,
coordinated: AtomicBool,
after_latch: Barrier,
after_steal: Barrier,
}
impl Shared {
fn steal(&self) {
self.last_error.lock().unwrap().take();
}
}
struct RacingVad(Arc<Shared>);
impl VoiceActivityDetector for RacingVad {
fn voice_activity(&self, samples: &[f32]) -> Vec<bool> {
let s = &self.0;
if !s.coordinated.swap(true, Ordering::SeqCst) {
*s.generation.lock().unwrap() += 1;
*s.last_error.lock().unwrap() = Some("scripted concurrent VAD failure".to_owned());
s.after_latch.wait(); s.after_steal.wait(); }
vec![false; samples.len().div_ceil(self.frame_length_samples())]
}
fn frame_length_samples(&self) -> usize {
crate::audio::whisper::audio::vad::DEFAULT_FRAME_LENGTH_SAMPLES
}
fn detection_generation(&self) -> u64 {
*self.0.generation.lock().unwrap()
}
fn last_detection_error(&self) -> Option<Box<dyn std::error::Error + Send + Sync + 'static>> {
self.0.last_error.lock().unwrap().clone().map(Into::into)
}
}
let shared = Arc::new(Shared {
generation: Mutex::new(0),
last_error: Mutex::new(None),
coordinated: AtomicBool::new(false),
after_latch: Barrier::new(2),
after_steal: Barrier::new(2),
});
let t = tiny_tokenizer();
let mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
let kit =
WhisperKit::with_backend(mock, t).with_vad_detector(Box::new(RacingVad(shared.clone())));
let audio = vec![0.1f32; 96_000];
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let result = std::thread::scope(|scope| {
let a = scope.spawn(|| kit.transcribe(&audio, &options));
let b_shared = Arc::clone(&shared);
let b = scope.spawn(move || {
b_shared.after_latch.wait(); b_shared.steal(); b_shared.after_steal.wait(); });
b.join().unwrap();
a.join().unwrap()
});
assert!(
matches!(result, Err(TranscribeError::Vad(VadError::Detection(_)))),
"A must fail closed on its own generation delta even though B drained the \
recorded error mid-race, got {result:?}"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn zero_max_window_seek_terminates() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new().with_max_window_seek(0);
let result = task.run(&vec![0.1; 16_003], &options).unwrap();
assert_eq!(
result.segments_slice().len(),
3,
"one window per floored sample"
);
let options = DecodingOptions::new().with_max_window_seek(usize::MAX);
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(
result.segments_slice().len(),
1,
"uncapped-in-practice advance"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn out_of_range_clip_end_terminates_at_physical_audio() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
let s = special();
mock.push_token_steps(&[
s.english_token(),
s.transcribe_token(),
s.no_timestamps_token(),
hello,
s.end_token(),
]);
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new()
.with_without_timestamps()
.with_clip_timestamps(vec![0.0, 4.0]); let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(result.segments_slice().len(), 2);
assert_eq!(
mock.counters().encode_calls(),
2,
"no empty-window inference"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn early_stop_does_not_leak_into_fallback_retries() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let calls = std::sync::Mutex::new(0usize);
let callback: &(
dyn Fn(&crate::audio::whisper::result::TranscriptionProgress) -> Option<bool> + Sync
) = &|_progress| {
let mut seen = calls.lock().unwrap();
*seen += 1;
Some(*seen != 6)
};
let task = TranscribeTask::new(&mock, &t).with_progress_callback(callback);
let options = DecodingOptions::new()
.with_temperature_fallback_count(1)
.maybe_first_token_logprob_threshold(None)
.maybe_logprob_threshold(Some(-0.1));
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(result.text(), "Hello", "the retry ran to completion");
assert_eq!(
mock.counters().decode_steps(),
13,
"6 stopped steps + 7 full-retry steps"
);
assert_eq!(result.timings().total_decoding_fallbacks(), 0.0);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_rejected_attempts_early_stop_survives_the_fallback_selection() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let compute = crate::audio::whisper::options::ComputeOptions::new();
let options = DecodingOptions::new()
.with_temperature_fallback_count(1)
.with_temperature_increment_on_fallback(0.0)
.maybe_first_token_logprob_threshold(None)
.maybe_logprob_threshold(Some(-0.1));
let uncut = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(uncut.text(), "Hello");
assert_eq!(uncut.task_facts().early_stopped(), Some(false));
assert_eq!(
uncut.task_facts().drew_from_rng(),
Some(false),
"greedy retry never draws"
);
let uncut_prov =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &uncut);
assert!(uncut_prov.is_reproducible());
let calls = std::sync::Mutex::new(0usize);
let callback: &(
dyn Fn(&crate::audio::whisper::result::TranscriptionProgress) -> Option<bool> + Sync
) = &|_progress| {
let mut seen = calls.lock().unwrap();
*seen += 1;
Some(*seen != 6)
};
let truncated = TranscribeTask::new(&mock, &t)
.with_progress_callback(callback)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
truncated.text(),
"Hello",
"the accepted retry still ran to completion"
);
assert_eq!(
truncated.task_facts().early_stopped(),
Some(true),
"the REJECTED attempt's early stop must survive the fallback selection"
);
assert_eq!(
truncated.task_facts().drew_from_rng(),
Some(false),
"the greedy retry never draws, so early_stopped is the ONLY differing fact"
);
let trunc_prov =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &truncated);
assert_eq!(trunc_prov.task_facts().early_stopped(), Some(true));
assert!(
!trunc_prov.is_reproducible(),
"a callback truncation -- even of a rejected attempt -- is not reproducible from options alone"
);
assert_ne!(
uncut_prov, trunc_prov,
"the surviving early-stop fact distinguishes two runs whose text is identical"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_callback_truncation_is_recorded_and_is_not_reproducible() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let options = DecodingOptions::new();
let compute = crate::audio::whisper::options::ComputeOptions::new();
let full = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
full.task_facts().early_stopped(),
Some(false),
"no callback truncated the full run"
);
let full_prov =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &full);
assert!(
full_prov.is_reproducible(),
"a greedy, un-truncated run reproduces from options alone"
);
let stop: &(
dyn Fn(&crate::audio::whisper::result::TranscriptionProgress) -> Option<bool> + Sync
) = &|_progress| Some(false);
let truncated = TranscribeTask::new(&mock, &t)
.with_progress_callback(stop)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
truncated.task_facts().early_stopped(),
Some(true),
"the callback's Some(false) truncated this run"
);
let trunc_prov =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &truncated);
assert_eq!(
trunc_prov.task_facts().early_stopped(),
Some(true),
"provenance records the early-stop outcome"
);
assert!(
!trunc_prov.is_reproducible(),
"a callback-truncated transcript is not reproducible from the record alone"
);
assert_ne!(
full_prov, trunc_prov,
"the early-stop outcome distinguishes two runs that differ only in the callback"
);
let segment = full
.segments_slice()
.first()
.expect("the full greedy run produced a segment");
let seg_prov =
crate::audio::whisper::provenance::Provenance::for_segment(&options, &compute, segment, false);
assert!(
!seg_prov.is_reproducible(),
"for_segment cannot observe the truncation, so it must not promise reproducibility"
);
assert!(
full_prov.is_reproducible(),
"the complete facts (for_result on the un-truncated run) do promise it"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn window_id_offset_is_recorded_in_the_result_and_provenance() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let options = DecodingOptions::new();
let compute = crate::audio::whisper::options::ComputeOptions::new();
let worker0 = TranscribeTask::new(&mock, &t)
.with_window_id_offset(0)
.run(&vec![0.1; 32_000], &options)
.unwrap();
let worker3 = TranscribeTask::new(&mock, &t)
.with_window_id_offset(3)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(worker0.task_facts().worker_schedule(), Some([0].as_slice()));
assert_eq!(worker3.task_facts().worker_schedule(), Some([3].as_slice()));
let prov0 =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &worker0);
let prov3 =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &worker3);
assert_eq!(prov0.task_facts().worker_schedule(), Some([0].as_slice()));
assert_eq!(prov3.task_facts().worker_schedule(), Some([3].as_slice()));
assert_ne!(
prov0, prov3,
"the worker coordinate distinguishes two runs that differ only in it"
);
}
fn one_hot(token: u32) -> Vec<f32> {
let mut logits = vec![0.0f32; 51865];
logits[token as usize] = 10.0;
logits
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn window_loop_attaches_word_timings_when_enabled() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
let script = [
s.english_token(),
s.transcribe_token(),
ts(0),
hello,
ts(100),
ts(100),
s.end_token(),
];
for (step, token) in script.iter().enumerate() {
let mut row = vec![0.0f32; 100];
row[(step + 1) * 10] = 1.0;
mock.push_step_with_alignment(one_hot(*token), row);
}
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new().with_word_timestamps();
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(result.segments_slice().len(), 1);
let words = result.segments_slice()[0].words_slice();
assert!(!words.is_empty(), "word timings attached");
let joined: String = words.iter().map(|w| w.word()).collect();
assert_eq!(crate::audio::whisper::text::normalized(&joined), "hello");
for word in words {
assert!(word.end() >= word.start());
assert!(
(0.0..=2.5).contains(&word.end()),
"timings inside the window"
);
}
assert!((result.segments_slice()[0].end() - words.last().unwrap().end()).abs() < 1e-4);
}
fn word_timestamp_removed_segment_result(drop_blank_audio: bool) -> TranscriptionResult {
let t = tiny_tokenizer();
let s = special();
let hello = 2425u32;
let world = 1002u32;
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(200),
);
let script = [
s.english_token(),
s.transcribe_token(),
s.no_timestamps_token(), ts(0),
hello,
ts(25),
ts(25),
ts(25),
world,
ts(50),
ts(50),
s.end_token(),
];
for (step, token) in script.iter().enumerate() {
let mut row = vec![0.0f32; 200];
row[step + 1] = 1.0;
mock.push_step_with_alignment(one_hot(*token), row);
}
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new()
.with_word_timestamps()
.with_without_timestamps()
.maybe_drop_blank_audio(drop_blank_audio);
let result = task.run(&vec![0.1; 48_000], &options).unwrap();
assert_eq!(mock.counters().encode_calls(), 2, "two windows decoded");
result
}
fn word_timestamp_removed_segment_ids(drop_blank_audio: bool) -> Vec<usize> {
word_timestamp_removed_segment_result(drop_blank_audio)
.segments_slice()
.iter()
.map(TranscriptionSegment::id)
.collect()
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn word_timestamps_removing_a_segment_keeps_ids_unique_and_monotonic() {
let ids = word_timestamp_removed_segment_ids(true);
assert_eq!(
ids,
vec![0, 2, 3, 5],
"survivor ids stay unique and monotonic across the removed segment (drop ON)"
);
let unique: std::collections::HashSet<usize> = ids.iter().copied().collect();
assert_eq!(unique.len(), ids.len(), "no id collision across windows");
assert!(
ids.windows(2).all(|w| w[0] < w[1]),
"survivor ids stay strictly monotonic"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn word_timestamps_drop_cleared_reproduces_swifts_duplicate_ids() {
let ids = word_timestamp_removed_segment_ids(false);
assert_eq!(
ids,
vec![0, 2, 2, 4],
"clearing drop_blank_audio reproduces Swift's duplicate survivor ids (drop OFF)"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn word_timestamps_drop_cleared_records_all_allocated_ordinals() {
let result = word_timestamp_removed_segment_result(false);
assert_eq!(
result
.segments_slice()
.iter()
.map(TranscriptionSegment::id)
.collect::<Vec<_>>(),
vec![0, 2, 2, 4],
"clearing drop_blank_audio keeps Swift's duplicate survivor ids (drop OFF)",
);
assert_eq!(
result.task_facts().decoded_span(),
SpanKnowledge::Exact(6),
"the stored span is the ordinals ALLOCATED (2 windows x 3), not the survivor \
count (4) the false-path id base advanced by",
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn word_timestamps_off_leaves_segments_wordless() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 32_000], &DecodingOptions::new())
.unwrap();
assert!(result.segments_slice()[0].words_slice().is_empty());
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn silence_skipped_window_with_word_timestamps_surfaces_a_segment_error() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
for (token, peak) in [
(s.english_token(), 1),
(s.transcribe_token(), 2),
(ts(0), 3),
(hello, 4),
(ts(100), 5),
(ts(100), 6),
(s.end_token(), 7),
] {
push_aligned(&mut mock, token, peak);
}
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new()
.with_word_timestamps()
.with_no_speech_threshold(-0.1)
.maybe_logprob_threshold(None);
let err = task.run(&vec![0.1; 32_000], &options).unwrap_err();
assert!(
matches!(
err,
TranscribeError::Segment(SegmentError::InvalidAlignmentShape(ref shape))
if shape.rows() == 0
),
"got: {err:?}"
);
}
fn blank_audio_tokens(t: &WhisperTokenizer) -> Vec<u32> {
t.encode(&format!(
" {}",
crate::audio::whisper::constants::BLANK_AUDIO_MARKER
))
.unwrap()
}
fn script_blank_audio_window(mock: &mut MockBackend, t: &WhisperTokenizer) {
let s = special();
let mut steps = vec![s.english_token(), s.transcribe_token(), ts(0)];
steps.extend(blank_audio_tokens(t));
steps.extend([ts(100), ts(100), s.end_token()]);
mock.push_token_steps(&steps);
}
fn script_speech_blank_speech_window(mock: &mut MockBackend, t: &WhisperTokenizer) {
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let world = t.encode(" World").unwrap()[0];
let mut steps = vec![
s.english_token(),
s.transcribe_token(),
ts(0),
hello,
ts(50),
ts(50),
];
steps.extend(blank_audio_tokens(t));
steps.extend([ts(100), ts(100), world, ts(150), ts(150), s.end_token()]);
mock.push_token_steps(&steps);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn blank_audio_segment_is_dropped_by_default() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut mock, &t);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 32_000], &DecodingOptions::new())
.unwrap();
assert!(
result.segments_slice().is_empty(),
"got: {:?}",
result.segments_slice()
);
assert_eq!(result.text(), "", "got: {:?}", result.text());
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn blank_audio_segment_is_emitted_when_drop_is_cleared() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut mock, &t);
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new().maybe_drop_blank_audio(false);
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(result.segments_slice().len(), 1);
assert_eq!(
result.text(),
crate::audio::whisper::constants::BLANK_AUDIO_MARKER
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn blank_audio_drop_keeps_surrounding_speech_and_preserves_ids() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_speech_blank_speech_window(&mut mock, &t);
let task = TranscribeTask::new(&mock, &t);
let result = task
.run(&vec![0.1; 32_000], &DecodingOptions::new())
.unwrap();
let segments = result.segments_slice();
assert_eq!(
segments.len(),
2,
"blank dropped, speech kept: {segments:?}"
);
assert_eq!(
segments.iter().map(|s| s.id()).collect::<Vec<_>>(),
vec![0, 2],
"survivors keep their decoded ids; the dropped segment leaves a gap"
);
for segment in segments {
assert!(
!segment
.text()
.contains(crate::audio::whisper::constants::BLANK_AUDIO_MARKER),
"no surviving segment carries the marker: {segment:?}"
);
}
assert_eq!(result.text(), "Hello World", "got: {:?}", result.text());
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn blank_audio_between_speech_is_kept_when_drop_is_cleared() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_speech_blank_speech_window(&mut mock, &t);
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new().maybe_drop_blank_audio(false);
let result = task.run(&vec![0.1; 32_000], &options).unwrap();
assert_eq!(result.segments_slice().len(), 3);
assert!(
result
.text()
.contains(crate::audio::whisper::constants::BLANK_AUDIO_MARKER),
"got: {:?}",
result.text()
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn vad_chunked_blank_audio_does_not_leave_bare_separators() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
script_blank_audio_window(&mut mock, &t);
let kit = WhisperKit::with_backend(mock, t);
let mut audio = vec![0.1f32; 96_000];
audio[32_000..35_200].fill(0.0);
audio[64_000..67_200].fill(0.0);
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let result = kit.transcribe(&audio, &options).unwrap();
assert_eq!(
result.text(),
"",
"three emptied chunks must not join into bare separators, got {:?}",
result.text()
);
assert!(result.segments_slice().is_empty());
assert_eq!(
result.timings().total_audio_processing_runs(),
3.0,
"every chunk's timings must still be in the merged sums"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn vad_chunked_blank_audio_is_joined_verbatim_when_drop_is_cleared() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(48_000));
script_blank_audio_window(&mut mock, &t);
let kit = WhisperKit::with_backend(mock, t);
let mut audio = vec![0.1f32; 96_000];
audio[32_000..35_200].fill(0.0);
audio[64_000..67_200].fill(0.0);
let options = DecodingOptions::new()
.with_chunking_strategy(ChunkingStrategy::Vad)
.maybe_drop_blank_audio(false);
let result = kit.transcribe(&audio, &options).unwrap();
let marker = crate::audio::whisper::constants::BLANK_AUDIO_MARKER;
assert_eq!(result.text(), format!("{marker} {marker} {marker}"));
assert_eq!(result.segments_slice().len(), 3);
}
fn short_clip_batch(
kit: &WhisperKit<MockBackend>,
options: &DecodingOptions,
) -> TranscriptionResult {
let speech = vec![0.1f32; 32_000];
let no_audio: Vec<f32> = Vec::new();
let results = kit.transcribe_all(&[&speech, &no_audio, &speech], options);
let results: Vec<TranscriptionResult> = results.into_iter().map(Result::unwrap).collect();
assert_eq!(results[1].text(), "", "the middle clip runs no window");
merge_transcription_results_with_options(&results, options)
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn transcribe_all_batch_merged_by_hand_has_no_bare_separators() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let kit = WhisperKit::with_backend(mock, t);
let merged = short_clip_batch(&kit, &DecodingOptions::new());
assert_eq!(
merged.text(),
"Hello Hello",
"an empty result must not become a doubled space, got {:?}",
merged.text()
);
assert_eq!(merged.timings().total_audio_processing_runs(), 2.0);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn transcribe_all_batch_keeps_the_bare_separator_when_the_drop_is_cleared() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let kit = WhisperKit::with_backend(mock, t);
let options = DecodingOptions::new().maybe_drop_blank_audio(false);
let merged = short_clip_batch(&kit, &options);
assert_eq!(
merged.text(),
"Hello Hello",
"the bare separator must survive the cleared drop (Swift parity)"
);
assert_eq!(merged.timings().total_audio_processing_runs(), 2.0);
}
fn blank_falls_back_to_point_two() -> DecodingOptions {
DecodingOptions::new()
.with_compression_ratio_threshold(0.8)
.with_temperature_fallback_count(1)
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_window_accepted_above_zero_can_decode_the_blank_marker_and_be_dropped() {
let t = tiny_tokenizer();
let options = blank_falls_back_to_point_two();
assert_eq!(options.seed(), None, "unseeded is the default");
let mut observed_mock =
MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut observed_mock, &t);
let observed = TranscribeTask::new(&observed_mock, &t)
.run(
&vec![0.1; 32_000],
&options.clone().maybe_drop_blank_audio(false),
)
.unwrap();
assert_eq!(
observed.text(),
crate::audio::whisper::constants::BLANK_AUDIO_MARKER
);
assert_eq!(
observed.segments_slice()[0].temperature(),
0.2,
"the ladder must have climbed, or this proves nothing"
);
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut mock, &t);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert!(result.segments_slice().is_empty(), "the drop emptied it");
assert_eq!(
result.task_facts().drew_from_rng(),
Some(true),
"the sampling must survive the segment that carried it"
);
let provenance = crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
);
assert!(
!provenance.is_reproducible(),
"an unseeded sampled window was dropped: this transcript cannot be \
promised byte-for-byte"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_rejected_nonzero_attempt_is_recorded_even_when_the_window_is_accepted_greedily() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
for _ in 0..24 {
mock.push_step(vec![0.0f32; 51865]);
}
let options = DecodingOptions::new()
.with_language("en") .with_without_timestamps()
.with_temperature(-0.2)
.with_temperature_fallback_count(1) .with_first_token_logprob_threshold(-1.5)
.maybe_compression_ratio_threshold(None)
.maybe_logprob_threshold(None);
assert_eq!(options.seed(), None, "unseeded is the default");
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert!(
mock.counters().encode_calls() > 0,
"a window must actually decode, or this proves nothing"
);
assert_eq!(
result.task_facts().drew_from_rng(),
Some(true),
"attempt 0 drew at -0.2, even though the window was accepted greedily at 0.0"
);
let provenance = crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
);
assert!(
!provenance.is_reproducible(),
"an unseeded rejected draw cannot be promised byte-for-byte"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_nonzero_temperature_that_never_samples_records_no_sampling() {
let t = tiny_tokenizer();
let mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let options = DecodingOptions::new()
.with_language("en") .with_without_timestamps() .with_temperature(0.3) .with_sample_length(0) .with_temperature_fallback_count(0);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert!(
mock.counters().encode_calls() > 0,
"the window must actually decode (at temperature 0.3) for the fact to matter"
);
assert_eq!(
result.task_facts().drew_from_rng(),
Some(false),
"a zero-iteration decode never draws, whatever the temperature -- the old \
`temperature != 0.0` inference wrongly recorded this window as sampled"
);
assert_eq!(
result.segments_slice().len(),
1,
"the zero-iteration window still lumps into one segment"
);
assert_eq!(
result.segments_slice()[0].temperature(),
0.3,
"the segment carries the accepted 0.3 rung, though nothing was drawn"
);
let provenance = crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
);
assert_eq!(
provenance.task_facts().drew_from_rng(),
Some(false),
"the 0.3 segment must not be read as a draw"
);
assert!(
provenance.is_reproducible(),
"a zero-iteration decode drew nothing, so it is reproducible despite the 0.3 segment"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn unseeded_sampling_survives_the_blank_audio_drop() {
let t = tiny_tokenizer();
let options = blank_falls_back_to_point_two();
let mut speech = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_clean_window(&mut speech, t.encode(" Hello").unwrap()[0]);
let mut blank = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut blank, &t);
let chunk_a = TranscribeTask::new(&speech, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
let chunk_b = TranscribeTask::new(&blank, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
chunk_a.segments_slice()[0].temperature(),
0.0,
"A is greedy"
);
assert_eq!(chunk_a.task_facts().drew_from_rng(), Some(false));
assert!(chunk_b.segments_slice().is_empty(), "B was emptied");
assert_eq!(
chunk_b.task_facts().drew_from_rng(),
Some(true),
"B sampled, and said so"
);
let merged = crate::audio::whisper::result::merge_transcription_results_with_options(
&[chunk_a, chunk_b],
&options,
);
assert_eq!(merged.text(), "Hello");
assert!(
merged
.segments_slice()
.iter()
.all(|segment| segment.temperature() == 0.0),
"every SURVIVING segment is greedy — which is exactly why inferring the \
answer from them was wrong"
);
assert_eq!(merged.task_facts().drew_from_rng(), Some(true));
let compute = crate::audio::whisper::options::ComputeOptions::new();
let provenance =
crate::audio::whisper::provenance::Provenance::for_result(&options, &compute, &merged);
assert_eq!(
provenance.effective_temperature(),
Some(0.0),
"the surviving segments really do all say 0.0 — the fix must NOT come \
from changing this"
);
assert!(
!provenance.is_reproducible(),
"REGRESSION: an unseeded sampled window was filtered out, and the record \
went back to promising byte-reproducibility it cannot honor"
);
let seeded = options.clone().with_seed(7);
let mut speech = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_clean_window(&mut speech, t.encode(" Hello").unwrap()[0]);
let mut blank = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut blank, &t);
let merged_seeded = crate::audio::whisper::result::merge_transcription_results_with_options(
&[
TranscribeTask::new(&speech, &t)
.run(&vec![0.1; 32_000], &seeded)
.unwrap(),
TranscribeTask::new(&blank, &t)
.run(&vec![0.1; 32_000], &seeded)
.unwrap(),
],
&seeded,
);
assert_eq!(merged_seeded.task_facts().drew_from_rng(), Some(true));
assert!(
crate::audio::whisper::provenance::Provenance::for_result(&seeded, &compute, &merged_seeded)
.is_reproducible(),
"a seed makes the same sampled window replayable"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn unseeded_sampling_survives_a_no_speech_window_with_no_segments() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_clean_window(&mut mock, t.encode(" Hello").unwrap()[0]);
let options = DecodingOptions::new()
.with_temperature(0.5)
.with_no_speech_threshold(-0.1)
.maybe_logprob_threshold(None);
assert_eq!(options.seed(), None);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert!(
result.segments_slice().is_empty(),
"the no-speech window contributed nothing"
);
assert_eq!(
result.task_facts().drew_from_rng(),
Some(true),
"it still sampled at 0.5, and the record has to know"
);
assert!(
!crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
)
.is_reproducible()
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn a_greedy_run_stays_reproducible_through_the_drop() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
script_blank_audio_window(&mut mock, &t);
let options = DecodingOptions::new();
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert!(result.segments_slice().is_empty());
assert_eq!(
result.task_facts().drew_from_rng(),
Some(false),
"greedy throughout: the sampler was never consulted"
);
assert!(
crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
)
.is_reproducible(),
"an empty greedy transcript reproduces exactly"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn unseeded_draw_survives_an_errored_vad_chunk_drop() {
let t = tiny_tokenizer();
let s = special();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(32_000));
mock.push_token_steps(&[2425, 1002, 2425, 1002, s.end_token()]);
mock.fail_on_call(2);
let kit = WhisperKit::with_backend(mock, t);
let options = DecodingOptions::new()
.with_chunking_strategy(ChunkingStrategy::Vad)
.with_language("en") .with_temperature(0.2);
assert_eq!(options.seed(), None, "unseeded is the default");
let result = kit.transcribe(&vec![0.1; 40_000], &options).unwrap();
assert!(
result.segments_slice().is_empty(),
"the errored chunk was dropped, so nothing survives"
);
assert_eq!(
result.task_facts().drew_from_rng(),
Some(true),
"the dropped chunk's unseeded draw must survive into the merged result"
);
assert!(
!crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
)
.is_reproducible(),
"an unseeded draw happened (in a dropped chunk), so the transcript is not reproducible"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn probe_detection_survives_an_errored_vad_chunk_drop() {
let t = tiny_tokenizer();
let s = special();
let es = t.token_to_id("<|es|>").unwrap();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(32_000));
mock.push_token_steps(&[es, 1002, 2425, s.end_token()]);
mock.fail_on_call(2);
let kit = WhisperKit::with_backend(mock, t);
let options = DecodingOptions::new()
.with_chunking_strategy(ChunkingStrategy::Vad)
.with_detect_language();
let result = kit.transcribe(&vec![0.1; 40_000], &options).unwrap();
assert!(
result.segments_slice().is_empty(),
"the errored chunk was dropped, so nothing survives"
);
assert_eq!(
result.task_facts().observed_language(),
Some("es"),
"the dropped chunk's probe detection must survive into the merged result"
);
assert_eq!(
crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
)
.task_facts()
.observed_language(),
Some("es"),
"and provenance records the detection the dropped chunk made"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn predicted_language_survives_an_errored_vad_chunk_drop() {
let t = tiny_tokenizer();
let es = t.token_to_id("<|es|>").unwrap();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(32_000));
mock.push_token_steps(&[2425, 2425, 2425, es]);
mock.fail_on_call(5);
let kit = WhisperKit::with_backend(mock, t);
let options = DecodingOptions::new()
.with_chunking_strategy(ChunkingStrategy::Vad)
.with_without_timestamps();
assert!(
!options.detect_language(),
"no probe runs, so <|es|> is a PREDICTION"
);
let result = kit.transcribe(&vec![0.1; 40_000], &options).unwrap();
assert!(
result.segments_slice().is_empty(),
"the errored chunk was dropped, so nothing survives"
);
assert_eq!(
result.task_facts().observed_language(),
Some("es"),
"the dropped chunk's PREDICTED language must survive into the merged result"
);
assert_eq!(
crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
)
.task_facts()
.observed_language(),
Some("es"),
"and provenance records the prediction the dropped chunk made"
);
}
#[test]
fn recover_vad_run_facts_carries_sink_facts_with_explicit_schedule_and_span() {
let sink = TaskFacts::observed_clean().with_observed_language(Some("es".into()));
let facts = recover_vad_run_facts(sink, Some(vec![1]), SpanKnowledge::Exact(1));
assert_eq!(
facts.observed_language(),
Some("es"),
"the sink's earliest ingested language is carried, even from a dropped chunk",
);
assert_eq!(facts.drew_from_rng(), Some(false), "and its draw watch");
assert_eq!(
facts.early_stopped(),
Some(false),
"and its early-stop watch"
);
assert_eq!(
facts.worker_schedule(),
Some([1].as_slice()),
"the caller-folded schedule is set explicitly, not absorbed to None",
);
assert_eq!(
facts.decoded_span(),
SpanKnowledge::Exact(1),
"and the merged surviving result's id span, likewise",
);
}
#[test]
fn recover_vad_run_facts_keeps_a_zero_chunk_run_clean_and_known_empty() {
let facts = recover_vad_run_facts(
TaskFacts::observed_clean(),
Some(Vec::new()),
SpanKnowledge::Exact(0),
);
assert_eq!(facts.drew_from_rng(), Some(false));
assert_eq!(facts.early_stopped(), Some(false));
assert_eq!(
facts.decoded_span(),
SpanKnowledge::Exact(0),
"a zero-chunk run KNOWS it allocated no ordinals -- Exact(0), not wholly-unknown",
);
let known_empty: &[usize] = &[];
assert_eq!(
facts.worker_schedule(),
Some(known_empty),
"a zero-chunk run KNOWS zero workers ran -- Some([]), never unknown None",
);
assert!(
facts.is_reproducible_under(false),
"a zero-chunk run drew nothing and was truncated by nothing -- reproducible",
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn vad_run_with_zero_chunks_is_known_clean_not_unknown() {
let t = tiny_tokenizer();
let mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(8_000));
let kit = WhisperKit::with_backend(mock, t);
let options = DecodingOptions::new().with_chunking_strategy(ChunkingStrategy::Vad);
let result = kit.transcribe(&vec![0.1; 12_000], &options).unwrap();
assert_eq!(
kit.backend().counters().encode_calls(),
0,
"no chunk decoded -- a genuine zero-chunk VAD run",
);
assert!(result.segments_slice().is_empty());
assert_eq!(
result.task_facts().drew_from_rng(),
Some(false),
"the run watched and POSITIVELY drew nothing",
);
assert_eq!(
result.task_facts().early_stopped(),
Some(false),
"and was truncated by nothing",
);
let known_empty: &[usize] = &[];
assert_eq!(
result.task_facts().worker_schedule(),
Some(known_empty),
"a zero-chunk VAD run observed zero workers -- Some([]), never unknown None",
);
let provenance = crate::audio::whisper::provenance::Provenance::for_result(
&options,
&crate::audio::whisper::options::ComputeOptions::new(),
&result,
);
assert_eq!(
provenance.task_facts().worker_schedule(),
Some(known_empty),
"and provenance carries the known-empty schedule verbatim",
);
assert!(
provenance.is_reproducible(),
"a zero-chunk run did nothing to redo -- reproducible",
);
}
#[test]
fn load_timings_stamp_writes_all_seven_fields_as_seconds() {
let load = LoadTimings {
model_loading: Duration::from_millis(750),
prewarm_load_time: Duration::from_millis(250),
encoder_load: Duration::from_millis(125),
decoder_load: Duration::from_millis(375),
encoder_specialization: Duration::from_millis(625),
decoder_specialization: Duration::from_millis(875),
tokenizer_load_time: Duration::from_millis(500),
};
let mut timings = TranscriptionTimings::new();
load.stamp(&mut timings);
assert_eq!(timings.model_loading(), 0.75);
assert_eq!(timings.prewarm_load_time(), 0.25);
assert_eq!(timings.encoder_load_time(), 0.125);
assert_eq!(timings.decoder_load_time(), 0.375);
assert_eq!(timings.encoder_specialization_time(), 0.625);
assert_eq!(timings.decoder_specialization_time(), 0.875);
assert_eq!(timings.tokenizer_load_time(), 0.5);
assert_eq!(timings.decoding_loop(), 0.0);
}
#[test]
fn load_timings_default_stamps_all_zero() {
let mut timings = TranscriptionTimings::new();
LoadTimings::default().stamp(&mut timings);
for value in [
timings.model_loading(),
timings.prewarm_load_time(),
timings.encoder_load_time(),
timings.decoder_load_time(),
timings.encoder_specialization_time(),
timings.decoder_specialization_time(),
timings.tokenizer_load_time(),
] {
assert_eq!(value, 0.0);
}
}
fn seg_word_tuples(seg: &TranscriptionSegment) -> Vec<(String, f32, f32)> {
seg
.words_slice()
.iter()
.map(|w| (w.word().to_string(), w.start(), w.end()))
.collect()
}
fn push_aligned(mock: &mut MockBackend, token: u32, peak: usize) {
let mut row = vec![0.0f32; 100];
row[peak] = 1.0;
mock.push_step_with_alignment(one_hot(token), row);
}
fn four_shape_alignment_fixture(t: &WhisperTokenizer) -> MockBackend {
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let world = t.encode(" World").unwrap()[0];
let again = t.encode(" again").unwrap()[0];
let more = t.encode(" more").unwrap()[0];
let alpha = t.encode(" alpha").unwrap()[0];
let beta = t.encode(" beta").unwrap()[0];
let gamma = t.encode(" gamma").unwrap()[0];
let x = s.no_timestamps_token(); let mut mock = MockBackend::new()
.with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
)
.with_continuous_script();
for (token, peak) in [
(s.english_token(), 3),
(s.transcribe_token(), 5),
(x, 7),
(hello, 9),
(ts(25), 11),
(ts(25), 13),
(world, 30), (again, 20), (more, 40), (s.end_token(), 30), ] {
push_aligned(&mut mock, token, peak);
}
for (token, peak) in [
(s.english_token(), 5),
(s.transcribe_token(), 8),
(x, 11),
(alpha, 14),
(beta, 20), (gamma, 40), (s.end_token(), 45), ] {
push_aligned(&mut mock, token, peak);
}
for (token, peak) in [
(s.english_token(), 5),
(s.transcribe_token(), 8),
(x, 11),
(hello, 14),
(ts(25), 17),
(ts(25), 19),
(world, 22),
(ts(50), 25),
(s.end_token(), 48),
] {
push_aligned(&mut mock, token, peak);
}
for (token, peak) in [
(s.english_token(), 5),
(s.transcribe_token(), 8),
(x, 11),
(hello, 14),
(ts(50), 17),
(ts(50), 19),
(s.end_token(), 48),
] {
push_aligned(&mut mock, token, peak);
}
mock
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn four_window_shapes_read_stale_and_dropped_alignment_rows() {
let t = tiny_tokenizer();
let options = DecodingOptions::new()
.with_word_timestamps()
.with_without_timestamps()
.maybe_drop_blank_audio(false)
.with_alignment_gather(AlignmentGather::Complete);
let mock = four_shape_alignment_fixture(&t);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 80_000], &options)
.unwrap();
assert_eq!(mock.counters().encode_calls(), 4, "exactly four windows");
let segs = result.segments_slice();
let ids: Vec<usize> = segs.iter().map(TranscriptionSegment::id).collect();
assert_eq!(
ids,
vec![0, 1, 2, 3, 4, 5],
"six segments across four windows"
);
let words: Vec<Vec<(String, f32, f32)>> = segs.iter().map(seg_word_tuples).collect();
let w = |text: &str, start: f32, end: f32| (text.to_string(), start, end);
assert_eq!(words[0], vec![w(" Hello", 0.14, 0.18)]);
assert_eq!(
words[1],
vec![
w(" World", 0.24, 0.26),
w(" again", 0.26, 0.4),
w(" more", 0.4, 0.8),
]
);
assert_eq!(
words[2],
vec![
w(" alpha", 1.22, 1.28),
w(" beta", 1.28, 1.4),
w(" gamma", 1.4, 1.4),
]
);
assert_eq!(words[3], vec![w(" Hello", 2.22, 2.28)]);
assert_eq!(words[4], vec![w(" World", 2.38, 2.44)]);
assert_eq!(words[5], vec![w(" Hello", 3.22, 3.28)]);
let twin = TranscribeTask::new(&four_shape_alignment_fixture(&t), &t)
.run(&vec![0.1; 80_000], &options)
.unwrap();
let twin_words: Vec<Vec<(String, f32, f32)>> =
twin.segments_slice().iter().map(seg_word_tuples).collect();
assert_eq!(words, twin_words, "the fixture is deterministic (AC-7)");
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn cap_hit_window_drops_the_completing_row() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_n_audio_ctx(100));
push_aligned(&mut mock, s.english_token(), 3);
push_aligned(&mut mock, s.transcribe_token(), 5);
push_aligned(&mut mock, s.no_timestamps_token(), 7);
for step in 3..220 {
push_aligned(&mut mock, hello, (8 + (step - 3) / 25).min(15));
}
push_aligned(&mut mock, hello, 20); push_aligned(&mut mock, hello, 40); push_aligned(&mut mock, hello, 30); let options = DecodingOptions::new()
.with_word_timestamps()
.with_without_timestamps()
.maybe_drop_blank_audio(false)
.maybe_compression_ratio_threshold(None)
.maybe_logprob_threshold(None)
.maybe_first_token_logprob_threshold(None)
.with_alignment_gather(AlignmentGather::Complete);
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 480_000], &options)
.unwrap();
assert_eq!(
mock.counters().decode_steps(),
223,
"the 224-token cap fired (a single attempt of MAX_TOKEN_CONTEXT - 1 steps), not an EOT sample"
);
assert_eq!(mock.counters().encode_calls(), 1, "a single window");
let segs = result.segments_slice();
assert_eq!(segs.len(), 1, "one lump segment");
let last = segs[0]
.words_slice()
.last()
.expect("the cap window carries words");
assert_eq!(last.word(), " Hello");
assert_eq!((last.start(), last.end()), (0.4, 0.8), "row 223 dropped");
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn zero_commit_window_skips_word_timestamps() {
let t = tiny_tokenizer();
let s = special();
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(100),
);
push_aligned(&mut mock, s.end_token(), 20); let options = DecodingOptions::new().with_word_timestamps();
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(result.segments_slice().len(), 1, "one lump segment");
let seg = &result.segments_slice()[0];
assert!(
seg.words_slice().is_empty(),
"zero-commit window attaches no words (hasAlignment gate shut)"
);
assert_eq!((seg.start(), seg.end()), (0.0, 1.0));
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn alignment_less_window_with_text_tokens_skips_word_timestamps() {
let t = tiny_tokenizer();
let mut mock = MockBackend::new().with_dims(ModelDims::new().with_window_samples(16_000));
let hello = t.encode(" Hello").unwrap()[0];
script_clean_window(&mut mock, hello);
let options = DecodingOptions::new().with_word_timestamps();
let result = TranscribeTask::new(&mock, &t)
.run(&vec![0.1; 32_000], &options)
.unwrap();
assert_eq!(
result.text(),
"Hello",
"real decoded text, unlike the zero-commit companion"
);
assert_eq!(result.segments_slice().len(), 1);
let seg = &result.segments_slice()[0];
assert!(
seg.words_slice().is_empty(),
"no alignment ever committed -> hasAlignment gate shut -> word-timestamp \
block skipped"
);
assert!((seg.start() - 0.0).abs() < 1e-4);
assert!((seg.end() - 2.0).abs() < 1e-4);
}
#[test]
fn last_speech_timestamp_seed_divides_in_f64_above_two_pow_24() {
let seek = 16_777_219usize;
let f64_path = last_speech_timestamp_seed(seek);
let f32_path = seek as f32 / crate::audio::whisper::constants::SAMPLE_RATE as f32;
assert_ne!(
f64_path, f32_path,
"the two divide orders diverge above 2^24"
);
assert_eq!(f64_path, 1048.5762_f32, "helper: f64 divide, one narrowing");
assert_eq!(
f32_path, 1048.5763_f32,
"pre-fix: f32 numerator pre-rounded"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn skip_special_tokens_governs_segment_text_on_both_writer_branches() {
let t = tiny_tokenizer();
let begin = special().special_token_begin();
let base = DecodingOptions::new()
.with_word_timestamps()
.with_without_timestamps()
.maybe_drop_blank_audio(false);
let on = TranscribeTask::new(&four_shape_alignment_fixture(&t), &t)
.run(&vec![0.1; 80_000], &base.clone().with_skip_special_tokens())
.unwrap();
for seg in on.segments_slice() {
assert!(
!seg.text().contains("<|"),
"skip_special_tokens leaves no marker: {:?}",
seg.text()
);
let word_only: Vec<u32> = seg
.tokens_slice()
.iter()
.copied()
.filter(|&token| token < begin)
.collect();
assert_eq!(seg.text(), t.decode(&word_only, false).unwrap());
}
assert!(!on.text().contains("<|"), "joined transcript is clean");
let off = TranscribeTask::new(&four_shape_alignment_fixture(&t), &t)
.run(&vec![0.1; 80_000], &base)
.unwrap();
assert!(
off
.segments_slice()
.iter()
.all(|seg| seg.text().contains("<|")),
"the default renders special/timestamp markers into every segment"
);
assert!(
!off.text().contains("<|"),
"joined transcript is special-filtered regardless of the flag"
);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn swift_parity_gather_moves_the_first_windows_end_and_the_next_seek() {
let t = tiny_tokenizer();
let s = special();
let hello = t.encode(" Hello").unwrap()[0];
let cols = 100usize;
let rows = 6usize;
let pitch = crate::audio::whisper::segment::coreml_f16_row_pitch(rows, cols).unwrap();
assert_eq!(
(rows * cols).saturating_sub(4 * pitch).min(cols),
88,
"this host measured a Float16 row pitch of {pitch} for {cols} columns; the reference \
host's 128 is what puts the gather's cut inside row 4 and past row 5, which is the \
whole discriminating shape of this fixture"
);
assert_eq!(
(rows * cols).saturating_sub(5 * pitch).min(cols),
0,
"row 5 must be gathered entirely blank under `SwiftParity`"
);
let run = |gather| {
let mut mock = MockBackend::new().with_dims(
ModelDims::new()
.with_window_samples(16_000)
.with_n_audio_ctx(cols),
);
let script = [
s.english_token(),
s.transcribe_token(),
ts(0),
hello,
ts(40),
ts(40),
s.end_token(),
];
for (step, token) in script.iter().enumerate() {
let row: Vec<f32> = (0..cols)
.map(|column| match step {
3 if column < 60 => 0.5,
3 => -0.1,
4 if column >= 44 => 1.0,
_ => 0.0,
})
.collect();
mock.push_step_with_alignment(one_hot(*token), row);
}
let task = TranscribeTask::new(&mock, &t);
let options = DecodingOptions::new()
.with_word_timestamps()
.with_alignment_gather(gather);
let result = task.run(&vec![0.1; 48_000], &options).unwrap();
let seeks: Vec<usize> = result
.segments_slice()
.iter()
.map(TranscriptionSegment::seek)
.collect();
let ends: Vec<f32> = result
.segments_slice()
.iter()
.map(TranscriptionSegment::end)
.collect();
(mock.counters().encode_calls(), seeks, ends)
};
let (complete_windows, complete_seeks, complete_ends) = run(AlignmentGather::Complete);
let (parity_windows, parity_seeks, parity_ends) = run(AlignmentGather::SwiftParity);
assert_eq!(
complete_ends[0], 0.88,
"row 5's plateau ends window 1 at col 44"
);
assert_eq!(
parity_ends[0], 1.18,
"with row 5 blank, row 4 holds it to col 59"
);
assert_eq!(complete_seeks[1], 14_080, "0.88 s * 16 kHz");
assert_eq!(parity_seeks[1], 18_880, "1.18 s * 16 kHz");
assert_ne!(complete_seeks[1], parity_seeks[1]);
assert_eq!((complete_windows, parity_windows), (3, 2));
assert_eq!(complete_seeks, vec![0, 14_080, 28_160]);
assert_eq!(parity_seeks, vec![0, 18_880]);
}
fn windows_opened(clip: (usize, usize), advance: usize, window_padding: usize) -> usize {
let (start, end) = clip;
let mut seek = start;
let mut first = true;
let mut windows = 0;
while opens_window(seek, end, window_padding, first) {
first = false;
windows += 1;
seek += advance.min(end - seek);
}
windows
}
#[test]
fn every_non_empty_clip_opens_a_window_and_the_padding_still_guards_the_rest() {
const PADDING: usize = 16_000;
const ADVANCE: usize = 48_000;
let table = [
((8_000, 14_400), 1, 0), ((30_000, 42_000), 1, 0), ((100_000, 116_000), 1, 0), ((0, 6_400), 1, 0), ((60_000, 84_800), 1, 1), ((90_000, 90_000), 0, 0), ((200_000, 299_200), 2, 2), ((400_000, 536_000), 3, 3), ];
for (clip, windows, swift) in table {
assert_eq!(
windows_opened(clip, ADVANCE, PADDING),
windows,
"clip {clip:?} (Swift's guard opened {swift})"
);
}
}
const SPECIAL: u32 = 1_000;
const TIME: u32 = SPECIAL + 2;
fn letters(ids: &[u32]) -> Result<String, TokenizerError> {
Ok(
ids
.iter()
.map(|&id| {
if id < SPECIAL {
char::from(b'a' + (id as u8 - 1)).to_string()
} else {
format!("<{id}>")
}
})
.collect(),
)
}
fn word(tokens: &[u32], start: f32, end: f32) -> crate::audio::whisper::result::WordTiming {
crate::audio::whisper::result::WordTiming::new("w", tokens.to_vec(), start, end, 0.5)
}
fn raw(span: (usize, usize), start: f32, end: f32) -> RawWord {
let samples = |seconds: f32| (f64::from(seconds) * 16_000.0).round() as usize;
RawWord::new(span, samples(start), samples(end))
}
fn timed_segment(
tokens: &[u32],
start: f32,
end: f32,
words: Vec<crate::audio::whisper::result::WordTiming>,
) -> TranscriptionSegment {
TranscriptionSegment::new()
.with_start(start)
.with_end(end)
.with_text(letters(tokens).expect("the fixture decodes"))
.with_tokens(tokens.to_vec())
.with_token_log_probs(
tokens
.iter()
.map(|&token| (token, -(token as f32) / 100.0))
.collect::<Vec<_>>(),
)
.with_words(words)
}
fn kept_showing(
segments: Vec<(TranscriptionSegment, Vec<RawWord>)>,
seek: usize,
samples: usize,
skip_special_tokens: bool,
) -> Vec<crate::audio::whisper::segment::KeptSegment> {
let (segments, attribution): (Vec<_>, Vec<_>) = segments.into_iter().unzip();
clip_back_to_window(
segments,
&attribution,
(seek, samples),
(SPECIAL, TIME),
skip_special_tokens,
letters,
)
.expect("the fixture decodes")
}
fn clipped_showing(
segments: Vec<(TranscriptionSegment, Vec<RawWord>)>,
seek: usize,
samples: usize,
skip_special_tokens: bool,
) -> Vec<TranscriptionSegment> {
kept_showing(segments, seek, samples, skip_special_tokens)
.into_iter()
.map(|(segment, _, _)| segment)
.collect()
}
fn kept(
segments: Vec<(TranscriptionSegment, Vec<RawWord>)>,
seek: usize,
samples: usize,
) -> Vec<crate::audio::whisper::segment::KeptSegment> {
kept_showing(segments, seek, samples, false)
}
fn spans(raw: &[RawWord]) -> Vec<(usize, usize)> {
raw.iter().map(RawWord::span).collect()
}
fn clipped(
segments: Vec<(TranscriptionSegment, Vec<RawWord>)>,
seek: usize,
samples: usize,
) -> Vec<TranscriptionSegment> {
clipped_showing(segments, seek, samples, false)
}
#[test]
fn a_padded_windows_segments_are_clipped_back_to_its_clip() {
let clip = clipped(
vec![
(
timed_segment(&[TIME, 1, TIME + 10, 2, TIME + 50], 0.0, 1.0, Vec::new()),
Vec::new(),
),
(
timed_segment(&[SPECIAL + 1, 1, 2], 0.0, 1.0, Vec::new()),
Vec::new(),
),
(
timed_segment(&[TIME + 5, 3, TIME + 15], 0.1, 0.3, Vec::new()),
Vec::new(),
),
(timed_segment(&[4], 0.4, 1.2, Vec::new()), Vec::new()),
],
0,
6_400,
);
let spans: Vec<(f32, f32)> = clip.iter().map(|s| (s.start(), s.end())).collect();
assert_eq!(
spans.len(),
2,
"the unplaceable segment and the one wholly in the padding are dropped"
);
assert_eq!(spans[0].0, 0.0);
assert!((spans[0].1 - 0.4).abs() < 1e-6, "cut at the clip's end");
assert_eq!(clip[0].tokens_slice(), &[TIME, 1, TIME + 10, TIME + 20]);
assert_eq!(
clip[0].text(),
"<1002>a<1012><1022>",
"with no raw words, the text up to the last timestamp inside the clip"
);
assert_eq!(spans[1], (0.1, 0.3));
assert_eq!(clip[1].tokens_slice(), &[TIME + 5, 3, TIME + 15]);
}
#[test]
fn the_clip_back_answers_where_each_kept_token_sat_in_its_window() {
let clip = kept(
vec![
(
timed_segment(&[TIME, 1, TIME + 10, 2, TIME + 50], 0.0, 1.0, Vec::new()),
Vec::new(),
),
(
timed_segment(&[SPECIAL + 1, 1, 2], 0.0, 1.0, Vec::new()),
Vec::new(),
),
(
timed_segment(&[TIME + 5, 3, TIME + 15], 0.1, 0.3, Vec::new()),
Vec::new(),
),
(timed_segment(&[4], 0.4, 1.2, Vec::new()), Vec::new()),
],
0,
6_400,
);
let survivors: Vec<&[usize]> = clip
.iter()
.map(|(_, _, positions)| positions.as_slice())
.collect();
assert_eq!(
survivors,
[&[0, 1, 2, 4][..], &[8, 9, 10][..]],
"each kept token's position in the window"
);
for (segment, _, positions) in &clip {
assert_eq!(
positions.len(),
segment.tokens_slice().len(),
"one position per kept token"
);
}
}
#[test]
fn a_segment_starting_at_the_clip_end_is_dropped_in_samples() {
for seek in [0usize, 57_600_000] {
let (window_start, clip_end) = crate::audio::whisper::segment::window_span(seek, 6_400);
let at_end = window_start + 20.0 * crate::audio::whisper::constants::SECONDS_PER_TIME_TOKEN;
let before_end = window_start + 19.0 * crate::audio::whisper::constants::SECONDS_PER_TIME_TOKEN;
let clip = clipped(
vec![
(
timed_segment(&[TIME, 1, TIME + 19], window_start, before_end, Vec::new()),
Vec::new(),
),
(
timed_segment(&[2], at_end, clip_end + 0.5, Vec::new()),
Vec::new(),
),
],
seek,
6_400,
);
assert_eq!(
clip.len(),
1,
"seek {seek}: the segment at token 20 starts at the clip's end"
);
assert_eq!(clip[0].tokens_slice(), &[TIME, 1, TIME + 19]);
}
}
#[test]
fn a_word_in_the_padding_leaves_its_segment_and_the_segment_is_rebuilt() {
let segment = timed_segment(
&[SPECIAL + 1, SPECIAL + 2, 1, 2, 3, 4, SPECIAL + 52],
0.0,
1.0,
vec![
word(&[1, 2], 0.0, 0.25),
word(&[3], 0.3, 0.45),
word(&[4], 0.6, 0.8),
],
);
let segment_raw = vec![
raw((2, 4), 0.0, 0.25),
raw((4, 5), 0.3, 0.45),
raw((5, 6), 0.6, 0.8),
];
let all_padding = timed_segment(&[5], 0.38, 0.9, vec![word(&[5], 0.5, 0.9)]);
let all_padding_raw = vec![raw((0, 1), 0.5, 0.9)];
let squeezed = timed_segment(
&[6, 7],
0.1,
0.4,
vec![word(&[6], 0.1, 0.3), word(&[7], 0.4, 0.4)],
);
let squeezed_raw = vec![raw((0, 1), 0.1, 0.3), raw((1, 2), 0.4, 0.4)];
let clip = kept(
vec![
(segment, segment_raw),
(all_padding, all_padding_raw),
(squeezed, squeezed_raw),
],
0,
6_400,
);
assert_eq!(clip.len(), 2, "the all-padding segment is dropped");
assert_eq!(
clip[1].0.tokens_slice(),
&[6, 7],
"a word of no length at the end stays"
);
assert_eq!(
spans(&clip[1].1),
[(0, 1), (1, 2)],
"both its raw words kept"
);
assert_eq!(
spans(&clip[0].1),
[(2, 4), (4, 5)],
"the crossing word kept, the padding word gone"
);
let kept = &clip[0].0;
assert_eq!(
kept.tokens_slice(),
&[SPECIAL + 1, SPECIAL + 2, 1, 2, 3, TIME + 20],
"the end boundary states the clamped span (R2)"
);
assert_eq!(
kept
.token_log_probs_slice()
.iter()
.map(|&(token, _)| token)
.collect::<Vec<_>>(),
vec![SPECIAL + 1, SPECIAL + 2, 1, 2, 3, SPECIAL + 52],
"the log probabilities follow their tokens; the rewritten step keeps what was sampled"
);
assert_eq!(kept.text(), "<1001><1002>abc<1022>");
assert!(
kept.words_slice().is_empty(),
"no visible word is the clip-back's to keep"
);
let segment = timed_segment(
&[SPECIAL + 1, 1, 2, 4],
0.0,
1.0,
vec![word(&[1, 2], 0.0, 0.2), word(&[4], 0.5, 0.7)],
);
let clip = clipped_showing(
vec![(segment, vec![raw((1, 3), 0.0, 0.2), raw((3, 4), 0.5, 0.7)])],
0,
6_400,
true,
);
assert_eq!(clip[0].text(), "ab");
}
#[test]
fn a_padding_word_goes_even_when_the_words_do_not_cover_the_text() {
let short = timed_segment(
&[TIME, 1, 2, 3, TIME + 15],
0.0,
0.3,
vec![word(&[1], 0.0, 0.2), word(&[2], 0.45, 0.6)],
);
let kept = kept(
vec![(short, vec![raw((1, 2), 0.0, 0.2), raw((2, 3), 0.45, 0.6)])],
0,
6_400,
);
assert_eq!(spans(&kept[0].1), [(1, 2)], "the padding word is not kept");
let clip: Vec<TranscriptionSegment> = kept.into_iter().map(|(segment, _, _)| segment).collect();
assert_eq!(clip.len(), 1);
assert_eq!(clip[0].tokens_slice(), &[TIME, 1, TIME + 15]);
assert_eq!(
clip[0]
.token_log_probs_slice()
.iter()
.map(|&(token, _)| token)
.collect::<Vec<_>>(),
vec![TIME, 1, TIME + 15]
);
assert_eq!(clip[0].text(), "<1002>a<1017>");
}
#[test]
fn a_word_on_the_ten_millisecond_grid_that_crosses_the_end_is_kept() {
for seek in [0usize, 57_600_000] {
let (window_start, clip_end) = crate::audio::whisper::segment::window_span(seek, 6_320);
let segment = timed_segment(
&[1, 2],
window_start,
window_start + 0.4,
vec![
word(&[1], window_start, window_start + 0.2),
word(&[2], window_start + 0.39, window_start + 0.4),
],
);
let clip = kept(
vec![(segment, vec![raw((0, 1), 0.0, 0.2), raw((1, 2), 0.39, 0.4)])],
seek,
6_320,
);
assert_eq!(clip.len(), 1, "seek {seek}");
assert_eq!(
spans(&clip[0].1),
[(0, 1), (1, 2)],
"seek {seek}: the boundary word is kept"
);
assert_eq!(clip[0].0.text(), "ab", "seek {seek}");
assert!(
(clip[0].0.end() - clip_end).abs() < 1e-3,
"seek {seek}: and the segment ends at the clip's end"
);
}
}
#[test]
fn a_kept_segment_states_its_clamped_span_in_its_timestamp_tokens() {
let rebuilt = timed_segment(
&[TIME, 1, 2, TIME + 50],
0.0,
1.0,
vec![word(&[1], 0.0, 0.3), word(&[2], 0.5, 0.9)],
);
let rebuilt_raw = vec![raw((1, 2), 0.0, 0.3), raw((2, 3), 0.5, 0.9)];
let whole = timed_segment(&[TIME + 5, 3, TIME + 10, TIME + 40], 0.1, 0.8, Vec::new());
let clip = clipped(vec![(rebuilt, rebuilt_raw), (whole, Vec::new())], 0, 6_320);
assert_eq!(clip.len(), 2);
assert_eq!(clip[0].tokens_slice(), &[TIME, 1, TIME + 19]);
assert_eq!(
clip[0].token_log_probs_slice(),
&[
(TIME, -(TIME as f32) / 100.0),
(1, -0.01),
(TIME + 50, -((TIME + 50) as f32) / 100.0)
],
"the rewritten step keeps the pair that was sampled"
);
assert_eq!(clip[0].text(), "<1002>a<1021>");
assert_eq!(clip[1].tokens_slice(), &[TIME + 5, 3, TIME + 10, TIME + 19]);
assert_eq!(
clip[1].text(),
"<1007>c<1012><1021>",
"its text up to its last timestamp inside, and its end restated"
);
let whole = timed_segment(&[TIME + 5, 3, TIME + 10, TIME + 40], 0.1, 0.8, Vec::new());
let clip = clipped_showing(vec![(whole, Vec::new())], 0, 6_320, true);
assert_eq!(clip[0].tokens_slice(), &[TIME + 5, 3, TIME + 10, TIME + 19]);
assert_eq!(clip[0].text(), "c");
}
#[test]
fn the_cut_falls_at_the_raw_words_position_whatever_the_merge_dropped() {
use crate::audio::whisper::{
constants::{APPEND_PUNCTUATION, PREPEND_PUNCTUATION},
result::WordTiming,
segment::merge_punctuations,
};
let bang = 9u32;
let aligned = [
WordTiming::new(" A", vec![1], 0.0, 0.1, 0.9),
WordTiming::new("!", vec![bang], 0.1, 0.15, 0.9),
WordTiming::new("!", vec![bang], 0.15, 0.2, 0.9),
WordTiming::new(" B", vec![2], 0.5, 0.7, 0.9),
];
let visible = merge_punctuations(&aligned, PREPEND_PUNCTUATION, APPEND_PUNCTUATION);
let visible_words: Vec<&str> = visible.iter().map(WordTiming::word).collect();
assert_eq!(visible_words, ["!!", " B"], "Swift's merge drops `A`");
let segment = timed_segment(&[TIME, 1, bang, bang, 2, TIME + 50], 0.0, 1.0, visible);
let clip = kept(
vec![(
segment,
vec![
raw((1, 2), 0.0, 0.1),
raw((2, 3), 0.1, 0.15),
raw((3, 4), 0.15, 0.2),
raw((4, 5), 0.5, 0.7),
],
)],
0,
6_400,
);
assert_eq!(clip.len(), 1, "the segment is kept");
assert_eq!(clip[0].0.tokens_slice(), &[TIME, 1, bang, bang, TIME + 20]);
assert_eq!(
spans(&clip[0].1),
[(1, 2), (2, 3), (3, 4)],
"`A ! !` kept, the padding word gone"
);
}
#[test]
fn no_timestamp_is_left_outside_the_clip() {
let lump = timed_segment(
&[TIME + 20, 3, TIME + 25],
0.0,
0.5,
vec![word(&[3], 0.1, 0.3)],
);
let clip = clipped(vec![(lump, vec![raw((1, 2), 0.1, 0.3)])], 0, 6_320);
assert_eq!(clip.len(), 1);
assert_eq!(clip[0].tokens_slice(), &[TIME, 3, TIME + 19]);
assert_eq!(clip[0].text(), "<1002>c<1021>");
assert_eq!(clip[0].start(), 0.0, "the start its first timestamp states");
}
#[test]
fn the_timestamps_state_the_bounds_the_segment_keeps() {
let lump = timed_segment(&[TIME + 40, 3, TIME + 50], 0.8, 1.0, Vec::new());
let clip = clipped(vec![(lump, vec![raw((1, 2), 0.1, 0.2)])], 0, 8_000);
assert_eq!(clip.len(), 1);
assert_eq!(
clip[0].tokens_slice(),
&[TIME + 5, 3, TIME + 10],
"the bounds' own 20 ms steps"
);
assert!(
(clip[0].start() - 0.1).abs() < 1e-6 && (clip[0].end() - 0.2).abs() < 1e-6,
"the fields the tokens state: {}..{}",
clip[0].start(),
clip[0].end()
);
assert_eq!(clip[0].text(), "<1007>c<1012>");
let inner = timed_segment(
&[TIME + 40, 3, TIME + 45, 4, TIME + 50],
0.8,
1.0,
Vec::new(),
);
let clip = clipped(
vec![(inner, vec![raw((1, 2), 0.1, 0.2), raw((3, 4), 0.25, 0.3)])],
0,
8_000,
);
assert_eq!(clip.len(), 1);
assert_eq!(
clip[0].tokens_slice(),
&[TIME + 5, 3, 4, TIME + 15],
"the inner timestamp past the span is removed"
);
assert_eq!(
clip[0].token_log_probs_slice(),
&[
(TIME + 40, -((TIME + 40) as f32) / 100.0),
(3, -0.03),
(4, -0.04),
(TIME + 50, -((TIME + 50) as f32) / 100.0)
],
"the removed timestamp takes its pair; a restated one keeps what was sampled"
);
assert_eq!(clip[0].text(), "<1007>cd<1017>");
}
#[test]
fn a_reversed_segment_is_found_before_its_bounds_are_clamped() {
let reversed = || timed_segment(&[TIME + 30, 3, TIME + 25], 0.6, 0.5, Vec::new());
let clip = clipped(vec![(reversed(), vec![raw((1, 2), 0.1, 0.2)])], 0, 8_000);
assert_eq!(clip.len(), 1, "the segment keeps its text");
assert_eq!(
clip[0].tokens_slice(),
&[TIME + 5, 3, TIME + 10],
"its timestamps state the raw word's span"
);
assert!(
(clip[0].start() - 0.1).abs() < 1e-6 && (clip[0].end() - 0.2).abs() < 1e-6,
"retimed to the raw word: {}..{}",
clip[0].start(),
clip[0].end()
);
let clip = clipped(vec![(reversed(), vec![raw((1, 2), 0.45, 0.55)])], 0, 8_000);
assert_eq!(clip.len(), 1);
assert_eq!(
clip[0].tokens_slice(),
&[TIME + 22, 3, TIME + 25],
"the raw word's start, and the clip's end"
);
assert!(
(clip[0].start() - 0.45).abs() < 1e-6 && (clip[0].end() - 0.5).abs() < 1e-6,
"retimed, then clamped: {}..{}",
clip[0].start(),
clip[0].end()
);
let unattributed = timed_segment(&[TIME + 20, 3, TIME + 10], 0.4, 0.2, Vec::new());
assert!(
clipped(vec![(unattributed, Vec::new())], 0, 8_000).is_empty(),
"a reversed segment no raw word attributes is dropped"
);
}
#[test]
fn an_unattributed_lump_keeps_only_the_text_a_timestamp_inside_the_clip_closes() {
let eot = SPECIAL;
let clip_end = crate::audio::whisper::segment::window_span(0, 6_400).1;
let closed_early = timed_segment(&[TIME, 1, TIME + 10, 2, eot], 0.0, 0.2, Vec::new());
let unclosed = timed_segment(&[TIME, 1, 2, eot], 0.0, clip_end, Vec::new());
let after_padding = timed_segment(&[TIME, 1, TIME + 30, 2, TIME + 10], 0.0, 0.2, Vec::new());
let clip = clipped(
vec![
(closed_early, Vec::new()),
(unclosed, Vec::new()),
(after_padding, Vec::new()),
],
0,
6_400,
);
assert_eq!(
clip.len(),
1,
"the lumps nothing inside the clip closes are dropped"
);
assert_eq!(clip[0].tokens_slice(), &[TIME, 1, TIME + 10, eot]);
assert_eq!(
clip[0]
.token_log_probs_slice()
.iter()
.map(|&(token, _)| token)
.collect::<Vec<_>>(),
vec![TIME, 1, TIME + 10, eot]
);
assert_eq!(clip[0].text(), "<1002>a<1012><1000>");
}
#[test]
fn past_its_last_word_a_segment_keeps_only_the_text_a_timestamp_inside_closes() {
let eot = SPECIAL;
let clip_end = crate::audio::whisper::segment::window_span(0, 6_400).1;
let words = || vec![word(&[1], 0.0, 0.1), word(&[2], 0.1, 0.2)];
let raws = || vec![raw((1, 2), 0.0, 0.1), raw((2, 3), 0.1, 0.2)];
let open_tail = timed_segment(&[TIME, 1, 2, 3, eot], 0.0, clip_end, words());
let closed_tail = timed_segment(&[TIME, 1, 2, 3, TIME + 15], 0.0, 0.3, words());
let clip = kept(vec![(open_tail, raws()), (closed_tail, raws())], 0, 6_400);
assert_eq!(clip.len(), 2);
assert_eq!(clip[0].0.tokens_slice(), &[TIME, 1, 2, eot]);
assert_eq!(clip[0].0.text(), "<1002>ab<1000>");
assert_eq!(spans(&clip[0].1), [(1, 2), (2, 3)], "both raw words kept");
assert_eq!(clip[1].0.tokens_slice(), &[TIME, 1, 2, 3, TIME + 15]);
assert_eq!(clip[1].0.text(), "<1002>abc<1017>");
}
#[test]
fn a_segment_with_no_text_is_dropped_whatever_was_removed() {
let clip = clipped(
vec![
(
timed_segment(&[TIME + 10, TIME + 10], 0.2, 0.2, Vec::new()),
Vec::new(),
),
(
timed_segment(&[TIME, TIME + 10], 0.0, 0.2, Vec::new()),
Vec::new(),
),
(
timed_segment(&[TIME + 10, 1, TIME + 10], 0.2, 0.2, Vec::new()),
Vec::new(),
),
(
timed_segment(&[TIME + 10, 2, TIME + 15], 0.2, 0.3, Vec::new()),
Vec::new(),
),
],
0,
6_400,
);
assert_eq!(clip.len(), 1, "only the segment with text and a span stays");
assert_eq!(clip[0].tokens_slice(), &[TIME + 10, 2, TIME + 15]);
}
#[test]
fn a_raw_word_belongs_to_the_segment_holding_its_first_token() {
use crate::audio::whisper::segment::own_raw_words;
let bang = 9u32;
let first = timed_segment(&[TIME, 1, bang, bang, TIME + 10], 0.0, 0.2, Vec::new());
let second = timed_segment(&[TIME + 10, 3, TIME + 15], 0.2, 0.3, Vec::new());
let units = [
((1, 2), 0, 5),
((2, 3), 5, 6),
((3, 4), 6, 7),
((6, 7), 10, 15),
((3, 7), 7, 13),
];
let owned = own_raw_words(&[first, second], &units);
let at = |frame: usize| frame * 320;
assert_eq!(
owned[0],
[
RawWord::new((1, 2), at(0), at(5)),
RawWord::new((2, 3), at(5), at(6)),
RawWord::new((3, 4), at(6), at(7)),
RawWord::new((3, 7), at(7), at(13)),
],
"the first segment's words, the straddling one among them"
);
assert_eq!(
owned[1],
[RawWord::new((1, 2), at(10), at(15))],
"`c` is the second's"
);
}
#[test]
fn an_hour_in_a_raw_word_crossing_the_clip_end_is_kept() {
use crate::audio::whisper::segment::own_raw_words;
let seek = 57_600_000;
let (window_start, _) = crate::audio::whisper::segment::window_span(seek, 6_088);
let segment = timed_segment(
&[TIME, 1, 2, TIME + 30],
window_start,
window_start + 0.6,
Vec::new(),
);
let units = [((1, 2), 0, 19), ((2, 3), 19, 25)];
let owned = own_raw_words(std::slice::from_ref(&segment), &units);
let clip = clipped_showing(vec![(segment, owned[0].clone())], seek, 6_088, true);
assert_eq!(clip.len(), 1, "the segment stays");
assert_eq!(
clip[0].text(),
"ab",
"the word crossing the clip's end is kept"
);
}