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(),
]);
}
#[test]
#[ignore = "requires local tokenizer (WHISPERKIT_TEST_MODELS)"]
fn audio_shorter_than_window_clip_time_yields_no_windows() {
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(&vec![0.1; 14_400], &DecodingOptions::new())
.unwrap();
assert!(result.segments_slice().is_empty());
assert_eq!(result.text(), "");
assert_eq!(mock.counters().encode_calls(), 0);
}
#[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(&vec![0.1; 14_400], &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 too_short = vec![0.1f32; 14_400];
let results = kit.transcribe_all(&[&speech, &too_short, &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]);
}