@tryhamster/gerbil 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +18 -16
  2. package/dist/{architectures-wan0swXu.mjs → architectures-Ol7yUmKf.mjs} +128 -68
  3. package/dist/architectures-Ol7yUmKf.mjs.map +1 -0
  4. package/dist/cli.mjs +8 -8
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{defaults-9komdrbY.mjs → defaults-bJcmOaRG.mjs} +2 -2
  7. package/dist/defaults-bJcmOaRG.mjs.map +1 -0
  8. package/dist/frameworks/express.mjs +1 -1
  9. package/dist/frameworks/fastify.mjs +1 -1
  10. package/dist/frameworks/hono.mjs +1 -1
  11. package/dist/frameworks/next.d.mts +2 -2
  12. package/dist/frameworks/next.mjs +1 -1
  13. package/dist/frameworks/trpc.mjs +1 -1
  14. package/dist/gerbil-BjGA5L2E.mjs +4 -0
  15. package/dist/{gerbil-DZbWbIm2.d.mts → gerbil-CCVOEU-J.d.mts} +2 -2
  16. package/dist/{gerbil-DZbWbIm2.d.mts.map → gerbil-CCVOEU-J.d.mts.map} +1 -1
  17. package/dist/{gerbil-CQspp8fS.mjs → gerbil-Cv7rXGOV.mjs} +2 -2
  18. package/dist/{gerbil-CQspp8fS.mjs.map → gerbil-Cv7rXGOV.mjs.map} +1 -1
  19. package/dist/gpu/hooks.d.mts +27 -10
  20. package/dist/gpu/hooks.d.mts.map +1 -1
  21. package/dist/gpu/hooks.mjs +19 -12
  22. package/dist/gpu/hooks.mjs.map +1 -1
  23. package/dist/gpu/index.d.mts +2 -2
  24. package/dist/gpu/index.mjs +5 -5
  25. package/dist/{gpu-BYCQ5aTI.mjs → gpu-BWLMlkVW.mjs} +78 -21
  26. package/dist/gpu-BWLMlkVW.mjs.map +1 -0
  27. package/dist/{index-CKszv3lt.d.mts → index-CSLqgNu-.d.mts} +212 -133
  28. package/dist/index-CSLqgNu-.d.mts.map +1 -0
  29. package/dist/index.d.mts +2 -2
  30. package/dist/index.mjs +6 -6
  31. package/dist/integrations/ai-sdk.mjs +1 -1
  32. package/dist/integrations/langchain.mjs +1 -1
  33. package/dist/integrations/llamaindex.mjs +1 -1
  34. package/dist/integrations/mcp.d.mts +2 -2
  35. package/dist/integrations/mcp.mjs +4 -4
  36. package/dist/{mcp-CszII8GJ.mjs → mcp-C0yKaH39.mjs} +3 -3
  37. package/dist/{mcp-CszII8GJ.mjs.map → mcp-C0yKaH39.mjs.map} +1 -1
  38. package/dist/{moonshine-stt-DDUm5WfP.mjs → moonshine-stt-C4GKK-h5.mjs} +45 -5
  39. package/dist/{moonshine-stt-DDUm5WfP.mjs.map → moonshine-stt-C4GKK-h5.mjs.map} +1 -1
  40. package/dist/moonshine-stt-zJ9YEi1X.mjs +4 -0
  41. package/dist/{one-liner-DVche8Eg.mjs → one-liner-DfJh2O0X.mjs} +2 -2
  42. package/dist/{one-liner-DVche8Eg.mjs.map → one-liner-DfJh2O0X.mjs.map} +1 -1
  43. package/dist/repl-DTSxHD6t.mjs +9 -0
  44. package/dist/skills/index.d.mts +2 -2
  45. package/dist/skills/index.d.mts.map +1 -1
  46. package/dist/skills/index.mjs +3 -3
  47. package/dist/{skills-BQI-VSn2.mjs → skills-B5cDRVJ1.mjs} +2 -2
  48. package/dist/{skills-BQI-VSn2.mjs.map → skills-B5cDRVJ1.mjs.map} +1 -1
  49. package/package.json +1 -2
  50. package/dist/architectures-wan0swXu.mjs.map +0 -1
  51. package/dist/defaults-9komdrbY.mjs.map +0 -1
  52. package/dist/gerbil-tHnYb9nZ.mjs +0 -4
  53. package/dist/gpu-BYCQ5aTI.mjs.map +0 -1
  54. package/dist/index-CKszv3lt.d.mts.map +0 -1
  55. package/dist/moonshine-stt-BtR-KiSP.mjs +0 -4
  56. package/dist/repl-BYJA2U7t.mjs +0 -9
  57. package/docs/PROJECT-STATE.md +0 -321
  58. package/docs/adding-a-model-family.md +0 -280
  59. package/docs/ai-sdk.md +0 -205
  60. package/docs/architecture/README.md +0 -84
  61. package/docs/architecture/caching.md +0 -227
  62. package/docs/architecture/inference.md +0 -176
  63. package/docs/architecture/overview.md +0 -189
  64. package/docs/architecture/streaming.md +0 -261
  65. package/docs/architecture/webgpu.md +0 -213
  66. package/docs/browser.md +0 -762
  67. package/docs/cli.md +0 -155
  68. package/docs/embeddings.md +0 -156
  69. package/docs/frameworks.md +0 -90
  70. package/docs/gerbil-site-native-migration.md +0 -217
  71. package/docs/gpu-engine/architectures.md +0 -398
  72. package/docs/gpu-engine/ir.md +0 -372
  73. package/docs/gpu-engine/kernels.md +0 -718
  74. package/docs/gpu-engine/paper.html +0 -1759
  75. package/docs/gpu-engine/paper.md +0 -2109
  76. package/docs/gpu-engine/safetensors.md +0 -312
  77. package/docs/gpu-engine/tokenizer.md +0 -302
  78. package/docs/kernel-research-queue.md +0 -85
  79. package/docs/mcp-client.md +0 -224
  80. package/docs/mcp.md +0 -109
  81. package/docs/memory-rag.md +0 -91
  82. package/docs/memory.md +0 -301
  83. package/docs/metal-safari-intel.md +0 -190
  84. package/docs/mobile-failure-diagnosis.md +0 -124
  85. package/docs/mobile.md +0 -99
  86. package/docs/observability.md +0 -230
  87. package/docs/onnx-removal-plan.md +0 -339
  88. package/docs/repl.md +0 -473
  89. package/docs/research/autoresearch-portable.md +0 -933
  90. package/docs/research/dispatch-reduction-hivemind.md +0 -84
  91. package/docs/research/ios-safari-model-caching.md +0 -117
  92. package/docs/research/mobile-webgpu-speed-fusion.md +0 -135
  93. package/docs/research/native-stt-model-selection.md +0 -49
  94. package/docs/research/native-tts-model-selection.md +0 -90
  95. package/docs/research/native-vs-chromium-decision.md +0 -152
  96. package/docs/research/nemotron-mamba2-inference.md +0 -910
  97. package/docs/research/qwen35-multimodal.md +0 -293
  98. package/docs/research/qwen36-gemma4-targets.md +0 -337
  99. package/docs/research/sota-embedding-models.md +0 -179
  100. package/docs/research/sota-mobile-models-2026.md +0 -263
  101. package/docs/research/sota-modality-models.md +0 -202
  102. package/docs/research/tps-baselines.md +0 -71
  103. package/docs/research/webgpu-m4-reference.md +0 -104
  104. package/docs/site-update-plan.md +0 -155
  105. package/docs/skills.md +0 -261
  106. package/docs/structured-output.md +0 -123
  107. package/docs/stt.md +0 -111
  108. package/docs/tools.md +0 -304
  109. package/docs/tts.md +0 -147
  110. package/docs/vision.md +0 -158
package/docs/tools.md DELETED
@@ -1,304 +0,0 @@
1
- # Gerbil Tools & Agents
2
-
3
- Gerbil supports tool calling with Qwen3 models, enabling agentic workflows where the LLM can call functions to accomplish tasks.
4
-
5
- ## Quick Start
6
-
7
- ### Simple Tool (Recommended)
8
-
9
- Create a file in `.gerbil/tools/` with the `.tool.ts` extension:
10
-
11
- ```typescript
12
- // .gerbil/tools/get_weather.tool.ts
13
-
14
- export default {
15
- name: "get_weather",
16
- description: "Get current weather for a city",
17
- parameters: {
18
- city: { type: "string", description: "City name" },
19
- },
20
- execute: async (params: { city: string }) => {
21
- return `Weather in ${params.city}: 72°F, sunny`;
22
- },
23
- };
24
- ```
25
-
26
- That's it! Gerbil automatically loads tools from `.gerbil/tools/` when the REPL starts.
27
-
28
- ### Advanced Tool (with Zod)
29
-
30
- For more complex validation, use `defineTool` with Zod schemas:
31
-
32
- ```typescript
33
- import { defineTool } from "@tryhamster/gerbil";
34
- import { z } from "zod";
35
-
36
- export const weatherTool = defineTool({
37
- name: "get_weather",
38
- description: "Get current weather for a city",
39
- parameters: z.object({
40
- city: z.string().describe("City name"),
41
- units: z.enum(["celsius", "fahrenheit"]).optional().default("fahrenheit"),
42
- }),
43
- execute: async ({ city, units }) => {
44
- return `Weather in ${city}: ${units === "celsius" ? "22°C" : "72°F"}, sunny`;
45
- },
46
- });
47
- ```
48
-
49
- ## Project Tools
50
-
51
- Store your custom tools in `.gerbil/tools/`:
52
-
53
- ```
54
- .gerbil/
55
- └── tools/
56
- ├── get_weather.tool.ts
57
- ├── search_docs.tool.ts
58
- └── calculator.tool.ts
59
- ```
60
-
61
- ### Tool File Format
62
-
63
- Each `.tool.ts` file exports a config object:
64
-
65
- ```typescript
66
- // .gerbil/tools/my_tool.tool.ts
67
-
68
- export default {
69
- name: "my_tool", // Tool name (snake_case)
70
- description: "What the tool does", // LLM uses this to decide when to call
71
- parameters: { // Optional input parameters
72
- param1: { type: "string", description: "First param" },
73
- param2: { type: "number", optional: true },
74
- },
75
- execute: async (params) => {
76
- // Your implementation
77
- return "result string";
78
- },
79
- };
80
- ```
81
-
82
- ### Managing Tools in REPL
83
-
84
- Press `3` or select **Tools** from the menu to:
85
- - View all registered tools (builtin + project)
86
- - Create new tools with the guided wizard
87
- - Execute tools directly with `x`
88
- - Open tool files in VS Code with `o`
89
-
90
- ## Built-in Tools
91
-
92
- ### `gerbil_docs`
93
-
94
- Search Gerbil documentation for any topic.
95
-
96
- ```typescript
97
- import { docsTool } from "@tryhamster/gerbil";
98
-
99
- const result = await docsTool({ query: "streaming" });
100
- // Returns documentation about streaming responses
101
- ```
102
-
103
- **Available topics:**
104
- - `quickstart` — Getting started
105
- - `generate` — Text generation options
106
- - `stream` — Streaming responses
107
- - `json` — Structured JSON output
108
- - `thinking` — Chain-of-thought reasoning
109
- - `embed` — Embeddings
110
- - `models` — Available models
111
- - `ai-sdk` — Vercel AI SDK integration
112
- - `next`, `express`, `react`, `hono` — Framework integrations
113
- - `skills` — Skills system
114
- - `cli` — CLI commands
115
- - `tools` — Tool calling
116
-
117
- ## Tool Examples
118
-
119
- ### Calculator
120
-
121
- ```typescript
122
- // .gerbil/tools/calculator.tool.ts
123
-
124
- export default {
125
- name: "calculator",
126
- description: "Perform basic math calculations",
127
- parameters: {
128
- expression: { type: "string", description: "Math expression like '2 + 2'" },
129
- },
130
- execute: async (params: { expression: string }) => {
131
- try {
132
- // Simple evaluator (use a proper math lib in production)
133
- const result = Function(`return ${params.expression}`)();
134
- return `${params.expression} = ${result}`;
135
- } catch {
136
- return `Error: Invalid expression "${params.expression}"`;
137
- }
138
- },
139
- };
140
- ```
141
-
142
- ### File Reader
143
-
144
- ```typescript
145
- // .gerbil/tools/read_file.tool.ts
146
-
147
- import fs from "fs/promises";
148
-
149
- export default {
150
- name: "read_file",
151
- description: "Read contents of a file",
152
- parameters: {
153
- path: { type: "string", description: "File path to read" },
154
- },
155
- execute: async (params: { path: string }) => {
156
- try {
157
- const content = await fs.readFile(params.path, "utf-8");
158
- return content.slice(0, 2000); // Limit output size
159
- } catch (e) {
160
- return `Error reading file: ${e}`;
161
- }
162
- },
163
- };
164
- ```
165
-
166
- ### API Caller
167
-
168
- ```typescript
169
- // .gerbil/tools/fetch_url.tool.ts
170
-
171
- export default {
172
- name: "fetch_url",
173
- description: "Fetch content from a URL",
174
- parameters: {
175
- url: { type: "string", description: "URL to fetch" },
176
- },
177
- execute: async (params: { url: string }) => {
178
- try {
179
- const res = await fetch(params.url);
180
- const text = await res.text();
181
- return text.slice(0, 2000);
182
- } catch (e) {
183
- return `Error fetching URL: ${e}`;
184
- }
185
- },
186
- };
187
- ```
188
-
189
- ### Joke Generator
190
-
191
- ```typescript
192
- // .gerbil/tools/generate_joke.tool.ts
193
-
194
- export default {
195
- name: "generate_joke",
196
- description: "Generates a joke based on a theme",
197
- parameters: {
198
- theme: { type: "string", description: "The theme (optional)", optional: true },
199
- },
200
- execute: async (params: { theme?: string }) => {
201
- const theme = params?.theme || "programming";
202
- const jokes: Record<string, string> = {
203
- programming: "Why do programmers prefer dark mode? Because light attracts bugs!",
204
- coffee: "Why do Java developers wear glasses? Because they can't C#!",
205
- ai: "Why did the neural network break up with the algorithm? Too many trust issues!",
206
- };
207
- return jokes[theme.toLowerCase()] || `Why did the ${theme} cross the road? To optimize the other side!`;
208
- },
209
- };
210
- ```
211
-
212
- ## Tool Registry API
213
-
214
- ```typescript
215
- import {
216
- defineTool,
217
- getTool,
218
- listTools,
219
- getToolDefinitions,
220
- loadProjectTools
221
- } from "@tryhamster/gerbil";
222
-
223
- // Load tools from .gerbil/tools/
224
- await loadProjectTools();
225
-
226
- // Get tool by name
227
- const tool = getTool("get_weather");
228
- await tool({ city: "NYC" });
229
-
230
- // List all tool names
231
- console.log(listTools());
232
- // ["gerbil_docs", "get_weather", "calculator"]
233
-
234
- // Get all definitions (for prompt injection)
235
- const definitions = getToolDefinitions();
236
- ```
237
-
238
- ## Using Tools in Chat
239
-
240
- ### REPL Agent Mode
241
-
242
- The easiest way to use tools is in the REPL's Agent mode:
243
-
244
- ```bash
245
- gerbil repl
246
- # Press ⌘A (Cmd+A) to toggle agent mode
247
- ```
248
-
249
- **Toggle shortcuts:**
250
- - **⌘A (Cmd+A)**: Toggle agent mode on/off (works anywhere)
251
- - **⌘T (Cmd+T)**: Toggle thinking mode on/off
252
-
253
- When agent mode is enabled:
254
- - Shows `+tools` badge in the header
255
- - Tool calls display in cyan boxes with results
256
- - Works with any chat persona
257
-
258
- In Agent mode, the model can:
259
- 1. See available tools in its system prompt
260
- 2. Call tools using the `<tool_call>` format
261
- 3. Receive tool results and synthesize a response
262
-
263
- ### Programmatic Usage
264
-
265
- ```typescript
266
- import {
267
- formatToolsForPrompt,
268
- parseToolCall,
269
- executeToolCall,
270
- getToolDefinitions
271
- } from "@tryhamster/gerbil";
272
-
273
- // Add tools to system prompt
274
- const tools = getToolDefinitions();
275
- const systemPrompt = `You are a helpful assistant.\n\n${formatToolsForPrompt(tools)}`;
276
-
277
- // Generate response
278
- const response = await gerbil.generate(userQuery, { system: systemPrompt });
279
-
280
- // Check for tool calls
281
- const toolCall = parseToolCall(response.text);
282
- if (toolCall) {
283
- const result = await executeToolCall(toolCall.tool, toolCall.params);
284
- // Continue conversation with tool result
285
- }
286
- ```
287
-
288
- ## Best Practices
289
-
290
- 1. **Clear descriptions** — The model uses descriptions to decide when to call tools
291
- 2. **Handle missing params** — Use defaults for optional parameters
292
- 3. **Limit output size** — Long tool outputs consume context (aim for <2000 chars)
293
- 4. **Return strings** — Execute function must return a string
294
- 5. **Graceful errors** — Catch exceptions and return helpful error messages
295
- 6. **Snake_case names** — Use `get_weather` not `getWeather`
296
-
297
- ## Model Compatibility
298
-
299
- Tool calling works best with:
300
- - **Qwen3-0.6B** — Excellent tool calling support, fast
301
- - **Qwen3-1.7B** — Better reasoning, still fast
302
- - **Qwen2.5 models** — Good support
303
-
304
- Smaller models (SmolLM2) may struggle with consistent tool call formatting.
package/docs/tts.md DELETED
@@ -1,147 +0,0 @@
1
- # Text-to-Speech
2
-
3
- Gerbil's native WebGPU engine synthesizes speech with **Kani-TTS-2** — an LFM2-350M
4
- codec-LM backbone driving NVIDIA's NeMo **NanoCodec** decoder (FSQ + causal HiFi-GAN).
5
- `engine.speak()` returns **22.05 kHz mono PCM**, fully on-device, no ONNX.
6
-
7
- > **Pre-1.0.** Kani-TTS-2 is the only TTS path. The old Kokoro / Supertonic ONNX /
8
- > transformers.js lane has been removed. The `Gerbil`-class `speak()` method still works for
9
- > backward compatibility but is now a thin wrapper over the native engine (see
10
- > [below](#gerbil-class-speak-native-wrapper)).
11
-
12
- ## Quick Start
13
-
14
- ### Node
15
-
16
- ```typescript
17
- import { WebGPUEngine } from "@tryhamster/gerbil/gpu";
18
-
19
- const engine = await WebGPUEngine.create({ repo: "nineninesix/kani-tts-2-en" });
20
-
21
- const { pcm, sampleRate, audioSeconds } = await engine.speak("Hello, I'm Gerbil!");
22
- // pcm: Float32Array in [-1, 1], sampleRate === 22050
23
- console.log(`${audioSeconds.toFixed(2)}s of audio`);
24
-
25
- engine.destroy();
26
- ```
27
-
28
- ### Options
29
-
30
- ```typescript
31
- const result = await engine.speak("Speak with feeling.", {
32
- languageTag: "en_us", // prepended as "{tag}: {text}" (default "en_us")
33
- temperature: 1.0, // sampling temperature (default 1.0)
34
- topP: 0.95, // nucleus threshold (default 0.95)
35
- repetitionPenalty: 1.1, // (default 1.1)
36
- maxFrames: 2000, // cap audio length (default: unbounded up to maxSeqLen)
37
- });
38
- ```
39
-
40
- `speak()` requires a Kani-TTS-2 checkpoint (architecture `KaniTTS2ForCausalLM`). The
41
- NanoCodec decoder checkpoint is downloaded lazily on first `speak()` call.
42
-
43
- ## Result
44
-
45
- ```typescript
46
- interface SpeakResult {
47
- /** Mono PCM in [-1, 1]. */
48
- pcm: Float32Array;
49
- /** Sample rate — always 22050. */
50
- sampleRate: number;
51
- /** Number of audio frames decoded. */
52
- frames: number;
53
- /** Audio duration in seconds. */
54
- audioSeconds: number;
55
- }
56
- ```
57
-
58
- ## Standalone KaniTTS
59
-
60
- `engine.speak()` lazily builds a `KaniTTS` engine under the hood. You can also use it
61
- directly (e.g. without a separate text model):
62
-
63
- ```typescript
64
- import { KaniTTS } from "@tryhamster/gerbil/gpu";
65
-
66
- const tts = await KaniTTS.create({
67
- repo: "nineninesix/kani-tts-2-en",
68
- // codecRepo defaults to the NeMo 22 kHz NanoCodec MLX checkpoint
69
- });
70
-
71
- const { pcm, sampleRate } = await tts.speak("Hello world!");
72
- tts.destroy();
73
- ```
74
-
75
- ## Playing / Saving Audio
76
-
77
- ### Browser (Web Audio API)
78
-
79
- ```typescript
80
- const ctx = new AudioContext({ sampleRate });
81
- const buffer = ctx.createBuffer(1, pcm.length, sampleRate);
82
- buffer.copyToChannel(pcm, 0);
83
- const source = ctx.createBufferSource();
84
- source.buffer = buffer;
85
- source.connect(ctx.destination);
86
- source.start();
87
- ```
88
-
89
- ### Node (save to WAV)
90
-
91
- ```typescript
92
- import { writeFileSync } from "node:fs";
93
-
94
- function saveWav(filename: string, audio: Float32Array, sampleRate: number) {
95
- const buffer = Buffer.alloc(44 + audio.length * 2);
96
- buffer.write("RIFF", 0);
97
- buffer.writeUInt32LE(36 + audio.length * 2, 4);
98
- buffer.write("WAVE", 8);
99
- buffer.write("fmt ", 12);
100
- buffer.writeUInt32LE(16, 16);
101
- buffer.writeUInt16LE(1, 20); // PCM
102
- buffer.writeUInt16LE(1, 22); // mono
103
- buffer.writeUInt32LE(sampleRate, 24);
104
- buffer.writeUInt32LE(sampleRate * 2, 28);
105
- buffer.writeUInt16LE(2, 32);
106
- buffer.writeUInt16LE(16, 34);
107
- buffer.write("data", 36);
108
- buffer.writeUInt32LE(audio.length * 2, 40);
109
- for (let i = 0; i < audio.length; i++) {
110
- const s = Math.max(-1, Math.min(1, audio[i]));
111
- buffer.writeInt16LE(Math.round(s * 32767), 44 + i * 2);
112
- }
113
- writeFileSync(filename, buffer);
114
- }
115
-
116
- const { pcm, sampleRate } = await engine.speak("Hello!");
117
- saveWav("output.wav", pcm, sampleRate); // 22.05 kHz mono
118
- // macOS: execSync("afplay output.wav");
119
- ```
120
-
121
- ## Model
122
-
123
- ### Kani-TTS-2
124
-
125
- - **Backbone:** LFM2-350M codec-LM (architecture `KaniTTS2ForCausalLM`)
126
- - **Codec:** NVIDIA NeMo NanoCodec (FSQ + causal HiFi-GAN), validated bit-exact
127
- - **Sample rate:** 22.05 kHz
128
- - **Repos:** `nineninesix/kani-tts-2-en`. License varies by variant (the `kani-tts-2-en`
129
- checkpoint is LFM1.0/other; a 450M variant is Apache 2.0).
130
-
131
- ---
132
-
133
- ## `Gerbil`-class `speak()` (native wrapper)
134
-
135
- > The Kokoro-82M / Supertonic-66M ONNX/transformers.js TTS lane has been removed. The
136
- > `Gerbil`-class `speak()` method below now runs the native Kani-TTS-2 engine under the hood
137
- > (it requires WebGPU and returns 22.05 kHz PCM). The browser `useSpeech` hook is gone — use
138
- > `useTTS` from `@tryhamster/gerbil/gpu/hooks`. The AI SDK `gerbil.speech()` provider also
139
- > routes through this native path.
140
-
141
- ```typescript
142
- import { Gerbil } from "@tryhamster/gerbil";
143
-
144
- const g = new Gerbil();
145
- const result = await g.speak("Hello!");
146
- // result.audio = Float32Array, result.sampleRate = 22050 (native Kani-TTS-2)
147
- ```
package/docs/vision.md DELETED
@@ -1,158 +0,0 @@
1
- # Vision (Image → Text)
2
-
3
- Gerbil's native WebGPU engine runs **vision language models** image-in → text-out. Two
4
- vision towers are supported natively, both with the same `describeImage` API:
5
-
6
- - **Qwen3.5 ViT** — bundled with `Qwen/Qwen3.5-0.8B` (the 0.8B text model's own built-in
7
- vision tower). Bit-exact vs HuggingFace transformers.
8
- - **Gemma 4 ViT** — bundled with `mlx-community/gemma-4-e2b-it-4bit`.
9
-
10
- There is no separate "vision model" — vision is the text model's own ViT, loaded on
11
- demand with `enableVision: true`.
12
-
13
- > **Pre-1.0.** This is the native engine surface. The legacy `Gerbil` class (ONNX /
14
- > transformers.js) once exposed an `images:` array on `generate()`; that path is retired and
15
- > not documented here.
16
-
17
- ## Quick Start
18
-
19
- ### Node
20
-
21
- In Node you decode the image to RGB pixels yourself (HWC layout, 0..255), then pass
22
- `{ pixels, width, height }`. The engine handles smart-resize, normalization, and patchify
23
- internally to match the HF image processor.
24
-
25
- ```typescript
26
- import { WebGPUEngine } from "@tryhamster/gerbil/gpu";
27
-
28
- const engine = await WebGPUEngine.create({
29
- repo: "Qwen/Qwen3.5-0.8B", // BF16 checkpoint ships the ViT weights
30
- enableVision: true,
31
- });
32
-
33
- // pixels: Uint8ClampedArray | Uint8Array | Float32Array, RGB, length = width*height*3
34
- const { text, tokensPerSecond } = await engine.describeImage(
35
- { pixels, width, height },
36
- "What's in this image?",
37
- { maxTokens: 150 },
38
- );
39
-
40
- console.log(text, `(${tokensPerSecond.toFixed(1)} tok/s)`);
41
- engine.destroy();
42
- ```
43
-
44
- ### React (Browser)
45
-
46
- The React hook decodes URLs and data-URLs for you, so you can pass an image source string
47
- directly. Load with `enableVision: true`.
48
-
49
- ```tsx
50
- import { useEngine } from "@tryhamster/gerbil/gpu/hooks";
51
-
52
- function ImageDescriber() {
53
- const { describeImage, completion, isLoading, isGenerating } = useEngine({
54
- model: "Qwen/Qwen3.5-0.8B",
55
- enableVision: true,
56
- autoLoad: true,
57
- });
58
-
59
- if (isLoading) return <div>Loading vision model…</div>;
60
-
61
- return (
62
- <div>
63
- <button
64
- disabled={isGenerating}
65
- onClick={() =>
66
- describeImage("https://example.com/photo.jpg", "Describe this image in detail")
67
- }
68
- >
69
- Describe
70
- </button>
71
- <p>{completion}</p>
72
- </div>
73
- );
74
- }
75
- ```
76
-
77
- `describeImage` accepts either an image source string (data URL or http(s) URL) or
78
- pre-decoded `{ pixels, width, height }`. The hook caps the longest side at ~448px before
79
- decoding — ViT attention memory scales with patch-count², so this keeps mobile coherent.
80
-
81
- ## Supported Towers
82
-
83
- | Tower | Load from | dtype | Notes |
84
- |-------|-----------|-------|-------|
85
- | Qwen3.5 ViT | `Qwen/Qwen3.5-0.8B` (`enableVision: true`) | BF16 (ships ViT) | Bit-exact vs HF (per-token cosine 1.000000) |
86
- | Gemma 4 ViT | `mlx-community/gemma-4-e2b-it-4bit` (`enableVision: true`) | MLX 4-bit | Native projector + multimodal-embedder norms |
87
-
88
- > The MLX 4-bit Qwen3.5 repo (`mlx-community/Qwen3.5-0.8B-4bit`) is text-only — load the
89
- > BF16 `Qwen/Qwen3.5-0.8B` repo to get the ViT weights for vision.
90
-
91
- ## How it works
92
-
93
- `describeImage` runs the full image-in → text-out pipeline natively:
94
-
95
- 1. **Preprocess** — pixels are smart-resized, normalized, and patchified to match the HF
96
- image processor (`preprocessImage` / `preprocessImageGemma4`).
97
- 2. **Encode** — the ViT turns patches into merged image-embedding tokens (`encodeImage`).
98
- 3. **Splice** — image tokens are scattered into the `image_token_id` rows of the text
99
- sequence (`EmbedSplice` in the multimodal graph).
100
- 4. **Decode** — Qwen3.5 uses multimodal M-RoPE positions; Gemma 4 uses standard sequential
101
- 1D RoPE. The LM then generates the description.
102
-
103
- The low-level pieces are exposed if you need them: `engine.encodeImage(patches, gridTHW)`
104
- returns the merged image tokens, and `engine.hasVision` reports whether the engine was
105
- built with a ViT.
106
-
107
- ## Image input
108
-
109
- ```typescript
110
- // Pre-decoded RGB pixels (Node, or browser if you've already decoded)
111
- await engine.describeImage({ pixels, width, height }, prompt);
112
-
113
- // Already-built patch tensor + grid (skips host preprocessing — for reference parity)
114
- await engine.describeImage({ patches, gridTHW: [1, gridH, gridW] }, prompt);
115
- ```
116
-
117
- In React, the hook also accepts a source string:
118
-
119
- ```tsx
120
- describeImage("data:image/png;base64,iVBOR…", "What is this?");
121
- describeImage("https://example.com/photo.jpg", "Describe it"); // must be CORS-accessible
122
- ```
123
-
124
- ## Options
125
-
126
- `describeImage(image, prompt?, options?)` takes the standard generation options:
127
-
128
- ```typescript
129
- await engine.describeImage(image, "Describe this image.", {
130
- maxTokens: 150,
131
- sampling: { temperature: 0.7 },
132
- stopSequences: ["\n\n"],
133
- onToken: (t) => process.stdout.write(t),
134
- });
135
- ```
136
-
137
- Returns a `GenerateResult`: `{ text, tokensGenerated, tokensPerSecond, totalTime, finishReason }`.
138
-
139
- ## Performance Tips
140
-
141
- - **Resize before sending.** ViT attention cost grows with the square of the patch count.
142
- The React hook already caps the longest side at 448px; in Node, downscale large photos
143
- (≈448–512px longest side) before decoding to pixels.
144
- - **Mobile.** On iOS/iPadOS the engine reserves fewer vision patches by default to stay
145
- under the WebKit GPU watchdog and memory budget; very large images may still need a
146
- smaller `maxVisionPatches` (a `WebGPUEngine.create` option).
147
-
148
- ## Troubleshooting
149
-
150
- ### "describeImage() requires a vision encoder"
151
-
152
- Load with `{ enableVision: true }` on a vision-capable checkpoint (Qwen3.5 BF16 or Gemma 4).
153
- The text-only MLX-4bit Qwen3.5 repo does not ship the ViT weights.
154
-
155
- ### Out of memory on mobile
156
-
157
- Use a smaller image and/or lower `maxVisionPatches` at create time. WebKit kills the page
158
- content process around 1.5–2 GB.