Offer Whisper's Translate to English only on models trained for it (canTranslate)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
036635e854
commit
02a5eab119
8 files changed
+151
-15
No files matched your search
+12
-3
@@ -37,6 +37,7 @@ import {
|
||||
formatModelSize,
|
||||
getModel,
|
||||
resolveDefaultDtype,
|
||||
taskForModel,
|
||||
} from "./lib/models";
|
||||
import { shouldWarnBeforeLoad, type MobileMemoryHint } from "./lib/mobileMemoryGuard";
|
||||
import {
|
||||
@@ -499,6 +500,9 @@ export function App() {
|
||||
function handleModelSelect(nextModelId: string) {
|
||||
if (busy || liveStatus !== "idle" || nextModelId === modelId) return;
|
||||
setModelId(nextModelId);
|
||||
// A "Translate to English" chosen on a model that can translate must not
|
||||
// silently carry over to one that cannot (D-WS4a).
|
||||
setTask((current) => taskForModel(nextModelId, current));
|
||||
setActiveDevice(null);
|
||||
setActiveThreads(null);
|
||||
setPartialText("");
|
||||
@@ -675,7 +679,7 @@ export function App() {
|
||||
// Language/task only apply to multilingual models; `.en` variants
|
||||
// take neither. The browser engine ignores modelId (worker holds it).
|
||||
modelId,
|
||||
...(multilingual && { language, task }),
|
||||
...(multilingual && { language, task: taskForModel(modelId, task) }),
|
||||
onPartial: (fragment) => {
|
||||
if (fileRun.current === run) setPartialText((text) => text + fragment);
|
||||
},
|
||||
@@ -752,7 +756,10 @@ export function App() {
|
||||
setLivePending(0);
|
||||
setLiveStatus("starting");
|
||||
|
||||
const options = (getModel(modelId)?.multilingual ?? false) ? { language, task } : {};
|
||||
const options =
|
||||
(getModel(modelId)?.multilingual ?? false)
|
||||
? { language, task: taskForModel(modelId, task) }
|
||||
: {};
|
||||
|
||||
if (useServer) {
|
||||
const wsUrl = new URL(`/api/transcribe/live?model=${modelId}`, window.location.href);
|
||||
@@ -988,6 +995,7 @@ export function App() {
|
||||
const allowed = ["onnx-community/whisper-large-v3-turbo", "onnx-community/whisper-medium.en_timestamped"];
|
||||
if (!allowed.includes(modelId) && !busy && liveStatus === "idle") {
|
||||
setModelId("onnx-community/whisper-large-v3-turbo");
|
||||
setTask((current) => taskForModel("onnx-community/whisper-large-v3-turbo", current));
|
||||
setActiveDevice(null);
|
||||
setActiveThreads(null);
|
||||
setPartialText("");
|
||||
@@ -1036,8 +1044,9 @@ export function App() {
|
||||
<TranscriptionOptions
|
||||
language={language}
|
||||
onLanguageChange={setLanguage}
|
||||
task={task}
|
||||
task={taskForModel(modelId, task)}
|
||||
onTaskChange={setTask}
|
||||
canTranslate={model.canTranslate}
|
||||
disabled={busy || liveActive}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -6,18 +6,26 @@ interface TranscriptionOptionsProps {
|
||||
onLanguageChange: (language: string) => void;
|
||||
task: WhisperTask;
|
||||
onTaskChange: (task: WhisperTask) => void;
|
||||
/**
|
||||
* Whether the model supports Whisper's own speech → English translation
|
||||
* (`WhisperModel.canTranslate`, D-WS4a). When false the Task control is not
|
||||
* rendered at all — transcribing is the only thing such a model does.
|
||||
*/
|
||||
canTranslate: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Language + task controls for multilingual models. The parent renders this
|
||||
* only when the selected model is multilingual — `.en` models take neither.
|
||||
* The task control only appears where the model can translate (D-WS4a).
|
||||
*/
|
||||
export function TranscriptionOptions({
|
||||
language,
|
||||
onLanguageChange,
|
||||
task,
|
||||
onTaskChange,
|
||||
canTranslate,
|
||||
disabled = false,
|
||||
}: TranscriptionOptionsProps) {
|
||||
return (
|
||||
@@ -37,17 +45,19 @@ export function TranscriptionOptions({
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Task
|
||||
<select
|
||||
value={task}
|
||||
disabled={disabled}
|
||||
onChange={(event) => onTaskChange(event.target.value as WhisperTask)}
|
||||
>
|
||||
<option value="transcribe">Transcribe</option>
|
||||
<option value="translate">Translate to English</option>
|
||||
</select>
|
||||
</label>
|
||||
{canTranslate && (
|
||||
<label>
|
||||
Task
|
||||
<select
|
||||
value={task}
|
||||
disabled={disabled}
|
||||
onChange={(event) => onTaskChange(event.target.value as WhisperTask)}
|
||||
>
|
||||
<option value="transcribe">Transcribe</option>
|
||||
<option value="translate">Translate to English</option>
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -42,6 +42,15 @@ export interface WhisperModel {
|
||||
displayName: string;
|
||||
/** False for `.en` (English-only) variants. */
|
||||
multilingual: boolean;
|
||||
/**
|
||||
* Whether Whisper's own `task: "translate"` (speech → English text) is
|
||||
* offered (D-WS4a). False for every `.en` model (no task token) and for
|
||||
* large-v3-turbo, which OpenAI did not train for translation — and for any
|
||||
* turbo-based fine-tune not tested for it. The UI hides "Translate to
|
||||
* English" where false; text translation of a finished transcript
|
||||
* (D-WS4b) works for every model regardless.
|
||||
*/
|
||||
canTranslate: boolean;
|
||||
/** Encoder parameter count, in millions (drives size estimates). */
|
||||
encoderParamsM: number;
|
||||
/** Decoder parameter count, in millions (drives size estimates). */
|
||||
@@ -174,6 +183,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "tiny.en",
|
||||
displayName: "Whisper Tiny (English)",
|
||||
multilingual: false,
|
||||
canTranslate: false,
|
||||
encoderParamsM: 8,
|
||||
decoderParamsM: 31,
|
||||
defaults: {
|
||||
@@ -188,6 +198,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "tiny",
|
||||
displayName: "Whisper Tiny",
|
||||
multilingual: true,
|
||||
canTranslate: true,
|
||||
encoderParamsM: 8,
|
||||
decoderParamsM: 31,
|
||||
defaults: { wasm: WASM_Q8, webgpu: WEBGPU_FP32, webnn: WEBNN_FP16 },
|
||||
@@ -197,6 +208,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "base.en",
|
||||
displayName: "Whisper Base (English)",
|
||||
multilingual: false,
|
||||
canTranslate: false,
|
||||
encoderParamsM: 20,
|
||||
decoderParamsM: 54,
|
||||
defaults: { wasm: WASM_Q8, webgpu: WEBGPU_FP32, webnn: WEBNN_FP16 },
|
||||
@@ -206,6 +218,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "base",
|
||||
displayName: "Whisper Base",
|
||||
multilingual: true,
|
||||
canTranslate: true,
|
||||
encoderParamsM: 20,
|
||||
decoderParamsM: 54,
|
||||
defaults: { wasm: WASM_Q8, webgpu: WEBGPU_FP32, webnn: WEBNN_FP16 },
|
||||
@@ -215,6 +228,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "small.en",
|
||||
displayName: "Whisper Small (English)",
|
||||
multilingual: false,
|
||||
canTranslate: false,
|
||||
encoderParamsM: 88,
|
||||
decoderParamsM: 156,
|
||||
defaults: { wasm: WASM_Q8, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 },
|
||||
@@ -224,6 +238,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "small",
|
||||
displayName: "Whisper Small",
|
||||
multilingual: true,
|
||||
canTranslate: true,
|
||||
encoderParamsM: 88,
|
||||
decoderParamsM: 156,
|
||||
defaults: { wasm: WASM_Q8, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 },
|
||||
@@ -237,6 +252,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "medium.en",
|
||||
displayName: "Whisper Medium (English)",
|
||||
multilingual: false,
|
||||
canTranslate: false,
|
||||
encoderParamsM: 306,
|
||||
decoderParamsM: 463,
|
||||
defaults: { wasm: WASM_Q4, webgpu: ACCEL_Q4, webnn: ACCEL_Q4 },
|
||||
@@ -246,6 +262,7 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "medium",
|
||||
displayName: "Whisper Medium",
|
||||
multilingual: true,
|
||||
canTranslate: true,
|
||||
encoderParamsM: 306,
|
||||
decoderParamsM: 463,
|
||||
defaults: { wasm: WASM_Q4, webgpu: ACCEL_Q4, webnn: ACCEL_Q4 },
|
||||
@@ -255,6 +272,9 @@ export const MODELS: readonly WhisperModel[] = [
|
||||
fasterWhisperModel: "large-v3-turbo",
|
||||
displayName: "Whisper Large v3 Turbo",
|
||||
multilingual: true,
|
||||
// Not trained for translation (OpenAI's turbo model card); its
|
||||
// "translate" output is unreliable (D-WS4a).
|
||||
canTranslate: false,
|
||||
encoderParamsM: 635,
|
||||
decoderParamsM: 174,
|
||||
defaults: { wasm: WASM_Q4, webgpu: WEBGPU_FP16_Q4, webnn: WEBNN_FP16 },
|
||||
@@ -274,6 +294,16 @@ export function getFasterWhisperModel(id: string): string | undefined {
|
||||
return getModel(id)?.fasterWhisperModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The task a run on `modelId` should actually use (D-WS4a): a "translate"
|
||||
* left over from a model that could translate falls back to "transcribe" on
|
||||
* one that cannot (large-v3-turbo, `.en`, unknown ids). App applies it on
|
||||
* every model switch and again when building each run's options.
|
||||
*/
|
||||
export function taskForModel(modelId: string, task: WhisperTask): WhisperTask {
|
||||
return task === "translate" && getModel(modelId)?.canTranslate !== true ? "transcribe" : task;
|
||||
}
|
||||
|
||||
/** Default dtype for a model on a given device. Unknown ids fall back to q8. */
|
||||
export function resolveDefaultDtype(
|
||||
modelId: string,
|
||||
|
||||
Reference in new issue
Block a user