Skip to content

Commit 0849369

Browse files
authored
Merge pull request #4 from Jefreesujit/feat/update-models
Update Models
2 parents 4f771d9 + b24c369 commit 0849369

8 files changed

Lines changed: 239 additions & 19 deletions

File tree

src/app/audio-helpers.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AudioTranscriptionChunk } from "../types";
1+
import type { AudioTranscriptionChunk, ModelDescriptor } from "../types";
22

33
export const EMPTY_TRANSCRIPTION_MESSAGE =
44
"No speech was detected in that clip. Try recording closer to the microphone or upload a clearer file.";
@@ -21,3 +21,17 @@ export const normalizeTranscriptionText = (
2121

2222
return chunkText;
2323
};
24+
25+
export const resolveVoiceSpeakerEmbeddings = (
26+
model: Pick<ModelDescriptor, "runtime">,
27+
voiceId: string | undefined,
28+
) => {
29+
if (voiceId) {
30+
const matchedVoice = model.runtime.voices?.find((voice) => voice.id === voiceId);
31+
if (matchedVoice?.speakerEmbeddingsUrl) {
32+
return matchedVoice.speakerEmbeddingsUrl;
33+
}
34+
}
35+
36+
return model.runtime.speakerEmbeddingsUrl;
37+
};

src/models.ts

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,68 @@ export const CURATED_MODELS: ModelDescriptor[] = [
383383
visionLoaderKind: "lfm2_5_vl",
384384
},
385385
}),
386+
createVisionModel({
387+
id: "onnx-community/gemma-4-E2B-it-qat-mobile-ONNX",
388+
label: "Gemma 4 E2B Mobile",
389+
summary:
390+
"QAT mobile Gemma 4 export for experimental multimodal chat on stronger browsers.",
391+
publisher: "onnx-community",
392+
paramsLabel: "2B params",
393+
parameterTier: "L",
394+
estimatedDownloadLabel: "~2.4 GB multimodal download",
395+
category: "desktop_experimental",
396+
starter: false,
397+
tested: false,
398+
hf: {
399+
modelId: "onnx-community/gemma-4-E2B-it-qat-mobile-ONNX",
400+
pipelineTag: "any-to-any",
401+
libraryName: "transformers.js",
402+
tags: ["transformers.js", "any-to-any", "conversational", "gemma4"],
403+
baseModel: "google/gemma-4-E2B-it-qat-mobile-transformers",
404+
hasChatTemplate: true,
405+
},
406+
runtime: {
407+
contextWindowTokens: 32768,
408+
visionLoaderKind: "gemma_any_to_any",
409+
visionDtypes: {
410+
audio_encoder: "q2f16",
411+
decoder_model_merged: "q2f16",
412+
embed_tokens: "q2f16",
413+
vision_encoder: "fp16",
414+
},
415+
},
416+
}),
417+
createVisionModel({
418+
id: "onnx-community/gemma-3n-E2B-it-ONNX",
419+
label: "Gemma 3n E2B",
420+
summary:
421+
"Gemma 3n's experimental any-to-any browser export with text, image, and audio towers.",
422+
publisher: "onnx-community",
423+
paramsLabel: "2B params",
424+
parameterTier: "L",
425+
estimatedDownloadLabel: "Large multimodal browser download",
426+
category: "desktop_experimental",
427+
starter: false,
428+
tested: false,
429+
hf: {
430+
modelId: "onnx-community/gemma-3n-E2B-it-ONNX",
431+
pipelineTag: "image-text-to-text",
432+
libraryName: "transformers.js",
433+
tags: ["transformers.js", "image-text-to-text", "conversational", "gemma3n"],
434+
baseModel: "google/gemma-3n-E2B-it",
435+
hasChatTemplate: true,
436+
},
437+
runtime: {
438+
contextWindowTokens: 32768,
439+
visionLoaderKind: "gemma_any_to_any",
440+
visionDtypes: {
441+
audio_encoder: "q4f16",
442+
decoder_model_merged: "q4f16",
443+
embed_tokens: "q4f16",
444+
vision_encoder: "fp16",
445+
},
446+
},
447+
}),
386448
createTextModel({
387449
id: "HuggingFaceTB/SmolLM3-3B-ONNX",
388450
label: "SmolLM3 3B",
@@ -432,29 +494,34 @@ export const CURATED_MODELS: ModelDescriptor[] = [
432494
contextWindowTokens: 32768,
433495
},
434496
}),
435-
createTextModel({
436-
id: "onnx-community/gemma-4-E2B-it-ONNX",
437-
label: "Gemma 4 2B",
438-
summary: "Experimental new Gemma model with strong reasoning capabilities.",
497+
createVisionModel({
498+
id: "onnx-community/gemma-4-E4B-it-qat-mobile-ONNX",
499+
label: "Gemma 4 E4B Mobile",
500+
summary:
501+
"Larger QAT mobile Gemma 4 variant for desktop-class multimodal experiments in-browser.",
439502
publisher: "onnx-community",
440-
paramsLabel: "2B params",
503+
paramsLabel: "4B params",
441504
parameterTier: "L",
442-
estimatedDownloadLabel: "Desktop experimental download",
505+
estimatedDownloadLabel: "~3.0 GB multimodal download",
443506
category: "desktop_experimental",
444507
starter: false,
445508
tested: false,
446509
hf: {
447-
modelId: "onnx-community/gemma-4-E2B-it-ONNX",
448-
pipelineTag: "text-generation",
510+
modelId: "onnx-community/gemma-4-E4B-it-qat-mobile-ONNX",
511+
pipelineTag: "any-to-any",
449512
libraryName: "transformers.js",
450-
tags: ["transformers.js", "text-generation", "conversational"],
451-
baseModel: "google/gemma-4-E2B-it",
513+
tags: ["transformers.js", "any-to-any", "conversational", "gemma4"],
514+
baseModel: "google/gemma-4-E4B-it-qat-mobile-transformers",
452515
hasChatTemplate: true,
453516
},
454517
runtime: {
455-
preferredDtype: "q4f16",
456-
fallbackDtype: "q4",
457518
contextWindowTokens: 32768,
519+
visionLoaderKind: "gemma_any_to_any",
520+
visionDtypes: {
521+
decoder_model_merged: "q2f16",
522+
embed_tokens: "q2f16",
523+
vision_encoder: "fp16",
524+
},
458525
},
459526
}),
460527
];
@@ -640,6 +707,59 @@ export const CURATED_AUDIO_MODELS: ModelDescriptor[] = [
640707
voices: [{ id: "default", label: "Default" }],
641708
},
642709
}),
710+
createTtsModel({
711+
id: "onnx-community/Kokoro-82M-v1.0-ONNX",
712+
label: "Kokoro 82M",
713+
summary:
714+
"Compact browser TTS model with stronger natural voices and multiple speaker presets.",
715+
publisher: "onnx-community",
716+
paramsLabel: "82M params",
717+
parameterTier: "S",
718+
estimatedDownloadLabel: "~95 MB download",
719+
category: "audio_smaller",
720+
tested: false,
721+
hf: {
722+
modelId: "onnx-community/Kokoro-82M-v1.0-ONNX",
723+
pipelineTag: "text-to-speech",
724+
libraryName: "transformers.js",
725+
tags: ["transformers.js", "text-to-speech", "kokoro"],
726+
baseModel: "hexgrad/Kokoro-82M",
727+
},
728+
runtime: {
729+
contextWindowTokens: 0,
730+
preferredDtype: "q4f16",
731+
fallbackDtype: "q4",
732+
audioLoaderKind: "pipeline_tts",
733+
audioSampleRate: 24000,
734+
defaultVoice: "af_bella",
735+
voices: [
736+
{
737+
id: "af_bella",
738+
label: "Bella",
739+
speakerEmbeddingsUrl:
740+
"https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices/af_bella.bin",
741+
},
742+
{
743+
id: "af_sarah",
744+
label: "Sarah",
745+
speakerEmbeddingsUrl:
746+
"https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices/af_sarah.bin",
747+
},
748+
{
749+
id: "am_michael",
750+
label: "Michael",
751+
speakerEmbeddingsUrl:
752+
"https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices/am_michael.bin",
753+
},
754+
{
755+
id: "bf_emma",
756+
label: "Emma",
757+
speakerEmbeddingsUrl:
758+
"https://huggingface.co/onnx-community/Kokoro-82M-v1.0-ONNX/resolve/main/voices/bf_emma.bin",
759+
},
760+
],
761+
},
762+
}),
643763
];
644764

645765
export const CURATED_AUDIO_MODELS_BY_ID = Object.fromEntries(

src/test/audio-helpers.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { describe, expect, it } from "vitest";
33
import {
44
EMPTY_TRANSCRIPTION_MESSAGE,
55
normalizeTranscriptionText,
6+
resolveVoiceSpeakerEmbeddings,
67
} from "../app/audio-helpers";
8+
import { createModelDescriptor } from "./factories";
79

810
describe("audio transcription helpers", () => {
911
it("keeps direct transcription text when available", () => {
@@ -27,4 +29,32 @@ describe("audio transcription helpers", () => {
2729
expect(EMPTY_TRANSCRIPTION_MESSAGE).toMatch(/No speech was detected/i);
2830
expect(normalizeTranscriptionText("", [])).toBe("");
2931
});
32+
33+
it("resolves voice-specific speaker embeddings when a named voice is selected", () => {
34+
const model = createModelDescriptor({
35+
task: "tts",
36+
runtime: {
37+
voices: [
38+
{
39+
id: "voice-a",
40+
label: "Voice A",
41+
speakerEmbeddingsUrl: "https://example.com/voice-a.bin",
42+
},
43+
{
44+
id: "voice-b",
45+
label: "Voice B",
46+
speakerEmbeddingsUrl: "https://example.com/voice-b.bin",
47+
},
48+
],
49+
speakerEmbeddingsUrl: "https://example.com/default.bin",
50+
},
51+
});
52+
53+
expect(resolveVoiceSpeakerEmbeddings(model, "voice-b")).toBe(
54+
"https://example.com/voice-b.bin",
55+
);
56+
expect(resolveVoiceSpeakerEmbeddings(model, "missing")).toBe(
57+
"https://example.com/default.bin",
58+
);
59+
});
3060
});

src/test/factories.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const createDeviceCapabilities = (
1717
});
1818

1919
export const createModelDescriptor = (
20-
overrides: Partial<ModelDescriptor> & {
20+
overrides: Partial<Omit<ModelDescriptor, "hf" | "runtime">> & {
2121
hf?: Partial<ModelDescriptor["hf"]>;
2222
runtime?: Partial<ModelDescriptor["runtime"]>;
2323
} = {},

src/test/model-helpers.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
getFallbackThreadModel,
99
getRecommendedModel,
1010
} from "../app/model-helpers";
11-
import { CURATED_MODELS_BY_ID } from "../models";
11+
import { CURATED_AUDIO_MODELS_BY_ID, CURATED_MODELS_BY_ID } from "../models";
1212
import { saveLastModel } from "../storage";
1313
import { createDeviceCapabilities, createModelDescriptor } from "./factories";
1414

@@ -44,10 +44,25 @@ describe("chat model defaults", () => {
4444
);
4545

4646
expect(modelIds).toContain("onnx-community/Supertonic-TTS-ONNX");
47+
expect(modelIds).toContain("onnx-community/Kokoro-82M-v1.0-ONNX");
4748
expect(modelIds).toContain("Xenova/speecht5_tts");
4849
expect(modelIds).not.toContain("onnx-community/moonshine-base-ONNX");
4950
});
5051

52+
it("keeps the lightweight Gemma default while exposing the new Gemma mobile variants", () => {
53+
expect(getRecommendedModel(createDeviceCapabilities(), {})?.id).toBe(
54+
"onnx-community/gemma-3-270m-it-ONNX",
55+
);
56+
expect(CURATED_MODELS_BY_ID["onnx-community/gemma-4-E2B-it-qat-mobile-ONNX"])
57+
.toBeDefined();
58+
expect(CURATED_MODELS_BY_ID["onnx-community/gemma-4-E4B-it-qat-mobile-ONNX"])
59+
.toBeDefined();
60+
expect(CURATED_MODELS_BY_ID["onnx-community/gemma-3n-E2B-it-ONNX"])
61+
.toBeDefined();
62+
expect(CURATED_AUDIO_MODELS_BY_ID["onnx-community/Kokoro-82M-v1.0-ONNX"])
63+
.toBeDefined();
64+
});
65+
5166
it("filters recent audio models by task", () => {
5267
const recentModels = [
5368
createModelDescriptor({

src/types.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,24 @@ export type CuratedCategoryKey =
8181
| "audio_smaller"
8282
| "audio_desktop_experimental";
8383
export type Dtype = "q4f16" | "q4" | "q8" | "int8" | "uint8" | "fp16" | "fp32";
84-
export type VisionLoaderKind = "qwen3_5" | "lfm2_5_vl";
84+
export type VisionLoaderKind = "qwen3_5" | "lfm2_5_vl" | "gemma_any_to_any";
85+
export type VisionRuntimeDtype = Dtype | "auto" | "bnb4" | "q2f16" | "q8f16";
8586
export type AudioLoaderKind =
8687
| "pipeline_asr"
8788
| "pipeline_tts"
8889
| "speecht5_tts"
8990
| "supertonic_tts";
91+
export type VoiceDescriptor = {
92+
id: string;
93+
label: string;
94+
speakerEmbeddingsUrl?: string;
95+
};
96+
export type VisionDtypeConfig = Partial<
97+
Record<
98+
"audio_encoder" | "decoder_model_merged" | "embed_tokens" | "vision_encoder",
99+
VisionRuntimeDtype
100+
>
101+
>;
90102
export type ChatPersistenceStatus =
91103
| "ready"
92104
| "fallback_local_storage"
@@ -127,12 +139,13 @@ export type ModelRuntimeConfig = {
127139
contextWindowTokens: number;
128140
chatTemplateOptions?: Record<string, boolean | number | string>;
129141
visionLoaderKind?: VisionLoaderKind;
142+
visionDtypes?: VisionDtypeConfig;
130143
audioLoaderKind?: AudioLoaderKind;
131144
supportsTimestamps?: boolean;
132145
audioSampleRate?: number;
133146
defaultLanguage?: string;
134147
defaultVoice?: string;
135-
voices?: Array<{ id: string; label: string }>;
148+
voices?: VoiceDescriptor[];
136149
speakerEmbeddingsUrl?: string;
137150
};
138151

src/worker/generation.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
TextStreamer,
66
} from "@huggingface/transformers";
77

8+
import { resolveVoiceSpeakerEmbeddings } from "../app/audio-helpers";
89
import type {
910
GenerationOptions,
1011
ModelDescriptor,
@@ -389,7 +390,7 @@ export const generateSpeech = async ({
389390
model,
390391
requestId,
391392
text,
392-
voice: _voice,
393+
voice,
393394
speed,
394395
}: GenerateSpeechParams) => {
395396
await session.ensureModelReady(model);
@@ -409,8 +410,9 @@ export const generateSpeech = async ({
409410
}
410411

411412
const output = await speechSynthesizer(text, {
412-
speaker_embeddings: model.runtime.speakerEmbeddingsUrl,
413+
speaker_embeddings: resolveVoiceSpeakerEmbeddings(model, voice),
413414
speed,
415+
voice,
414416
});
415417

416418
return {

src/worker/model-session.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,32 @@ export const createModelSession = (postMessageToUi: WorkerMessagePoster) => {
429429
} satisfies LoadResources;
430430
}
431431

432+
if (model.runtime.visionLoaderKind === "gemma_any_to_any") {
433+
const [nextProcessor, nextVisionModel] = await Promise.all([
434+
AutoProcessor.from_pretrained(model.hf.modelId, {
435+
progress_callback: progressHandler,
436+
}),
437+
AutoModelForImageTextToText.from_pretrained(model.hf.modelId, {
438+
dtype: (model.runtime.visionDtypes ?? {
439+
embed_tokens: "q4f16",
440+
vision_encoder: "fp16",
441+
decoder_model_merged: "q4f16",
442+
audio_encoder: "q4f16",
443+
}) as any,
444+
device: "webgpu",
445+
progress_callback: progressHandler,
446+
}),
447+
]);
448+
449+
return {
450+
textGenerator: null,
451+
processor: nextProcessor,
452+
visionModel: nextVisionModel as VisionModelInstance,
453+
speechRecognizer: null,
454+
speechSynthesizer: null,
455+
} satisfies LoadResources;
456+
}
457+
432458
throw new Error(
433459
"This vision loader is not supported in the browser worker yet.",
434460
);

0 commit comments

Comments
 (0)