Skip to content

feat(asr/canary): speech translation (en ↔ 24 European languages) via prompt - #833

Open
Alex-Wengg wants to merge 3 commits into
feat/canary-asrfrom
feat/canary-translation
Open

feat(asr/canary): speech translation (en ↔ 24 European languages) via prompt#833
Alex-Wengg wants to merge 3 commits into
feat/canary-asrfrom
feat/canary-translation

Conversation

@Alex-Wengg

Copy link
Copy Markdown
Member

Summary

Stacked on #709. The canary-1b-v2 AED decoder selects its task from two prompt slots: transcription is source == target, translation is source != target. The already-shipped CoreML models therefore translate speech with no model change — one prompt token differs. This PR wires that up, benchmarks it, and fixes an IOSurface leak the benchmark exposed.

HF companion page: FluidInference/canary-speech-translation-coreml (prompt token map + benchmark hypotheses).

API

let manager = try await CanaryManager.load(source: .english, target: .german)
let german = try await manager.transcribe(audioURL: url)
swift run fluidaudiocli canary-transcribe audio.wav --translate-to de [--source-lang en]
  • CanaryLanguage: 25 languages (ISO 639-1) with vocab.json token ids, uniqueness-tested
  • CanaryConfig.makePrompt(source:target:pnc:) — builder verified byte-equal to the existing promptEnTranscribePnc for en/en
  • --translate-benchmark pairs.json: corpus BLEU (13a-style, validated within 0.3 of sacrebleu) + RTFx + hypotheses dump; Scripts/canary_fleurs_translation_pairs.py rebuilds FLEURS cross-language alignment (the fleurs-full cache loses sentence ids)

Benchmarks (FLEURS test, int4/ANE, M5 Pro)

Direction Pairs sacreBLEU chrF2 COMET RTFx Peak mem
en→de 641 29.98 59.86 82.70 10.3× 1.94 GB
de→en 862 33.06 62.15 83.50 14.7× 1.90 GB

NVIDIA's fp16 24-lang FLEURS averages: BLEU 29.4 / COMET 84.56 (en→X), BLEU 29.08 / COMET 79.30 (X→en).

Duration split — single-window (≤15s) quality matches published fp16; the >15s chunked path costs 7–10 COMET because the LCS seam merge assumes token-stable overlap text, which translations don't have:

Subset en→de COMET de→en COMET
≤15s 83.44 85.67
>15s (chunked) 73.88 78.59

A runtime warning now fires on >15s translation; VAD-aligned segmentation (≤15s pieces, no overlap merge) is the follow-up that recovers this.

IOSurface fix

A 641-file run crashed with IOSurface allocation failure: each decode step autoreleases MB-scale IOSurface-backed decoder/projection outputs (~60k predictions, never drained). Fixed with autoreleasepool per decode step and per window; pointers are re-derived inside the pool closure because UnsafeMutablePointer captures trip Swift 6 region-isolation errors. Peak memory now flat ~1.9GB across 862 files. This benefits all long Canary batch runs, not just translation.

Testing

  • swift build + swift format lint clean (XCTest unavailable locally; unit tests run in CI)
  • e2e validated: en audio → fluent German through the shipped int4/ANE models
  • 1,503 benchmark files, 0 failures

🤖 Generated with Claude Code

…ia prompt

The canary-1b-v2 AED decoder selects its task from two prompt slots:
transcription is source==target, translation is source!=target. The already-
shipped CoreML models therefore translate with no conversion change - only
the prompt differs. Verified against the converted models: en audio -> fluent
German at RTFx 8.1 (int4/ANE, 2.1 GB peak); fp16 is the fidelity build (int4
shows minor lexical quant drift).

Adds CanaryLanguage (25 langs, ISO 639-1, vocab.json token ids, uniqueness-
tested), CanaryConfig.makePrompt(source:target:pnc:), typed CanaryManager
init/load overloads, and canary-transcribe --translate-to/--source-lang.
Long-form translation logs a warning: the LCS seam merge assumes token-stable
overlap text, which holds for transcripts but not translations - segmenting
at pauses <= 15 s is the reliable path until a translation-aware seam
strategy exists.
…OSurface fix

Adds --translate-benchmark to canary-transcribe: runs a JSON manifest of
{audio, reference} pairs, reports in-process corpus BLEU-4 (13a-style,
sacrebleu-validated within 0.3) and RTFx, and writes hypotheses JSON for
exact sacrebleu/chrF scoring. Scripts/canary_fleurs_translation_pairs.py
(gitignore exception, matching the other benchmark scripts) rebuilds
cross-language FLEURS alignment lost by the fleurs-full cache: local wav ->
google/fleurs test.tsv row via normalized transcript -> aligned raw cased
reference in the target language.

Results, canary-1b-v2 int4/ANE on M5 Pro (sacrebleu / chrF2 / RTFx):
  en->de  641 pairs  BLEU 29.98  chrF2 59.86  10.3x
  de->en  862 pairs  BLEU 33.06  chrF2 62.15  14.7x
NVIDIA reports 29.4 (en->X) / 29.08 (X->en) FLEURS 24-lang fp16 averages -
the int4 CoreML build is at parity with published quality.

Also fixes IOSurface exhaustion in the greedy decode loop: each step
autoreleases MB-scale IOSurface-backed decoder/projection outputs, and a
641-file run crashed allocation-time before draining. Wrap each decode step
and each window in autoreleasepool; pointers are re-derived inside the pool
closure because UnsafeMutablePointer captures trip Swift 6 region-isolation
sending errors. Peak memory now stable at ~1.9 GB across 860+ files.
…eview fixes

Code-review fixes for the translation feature:

- makePrompt now throws for non-English X<->Y pairs (canary-1b-v2 is only
  trained en<->X; previously a de->fr prompt silently produced untrained-task
  output). Typed CanaryManager init/load propagate the throw; transcription
  (source==target) in any language stays valid. Covered by new unit test.
- CLI flag-combination validation: --translate-benchmark requires
  --translate-to; --benchmark and --translate-benchmark are mutually
  exclusive; --translate-to cannot combine with WER scoring
  (--benchmark/--reference); en<->X enforced with a friendly message.
  Previously a forgotten --translate-to benchmarked English transcription
  against German references and reported near-zero BLEU like a model
  regression.
- Validation errors print synchronously: AppLogger console mirroring is a
  detached Task in DEBUG builds, so logger.error immediately before return
  lost the message and the process exited 0 with no output.
- Language flags with a missing trailing value now error instead of silently
  proceeding as plain transcription; the two parse blocks are merged.
- BLEU tokenizer now matches mteval-13a digit handling: '.'/',' stay
  attached when both neighbors are ASCII digits ('3,5' is one token);
  previously numeral-heavy corpora systematically deviated from sacrebleu.
- hyps.json write failures now surface (previously double try? swallowed
  them and printed the success line even after a failed write).
- --max-files clamped to >= 0 (negative values crashed in prefix()).
- BLEUCalculatorTests wrapped in #if os(macOS) matching sibling CLI tests.
- Removed dead outer hptr binding left by the autoreleasepool refactor.
- FLEURS pair script downloads TSVs atomically (.part + rename) and rejects
  0-row parses, so an interrupted download can't poison later runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant