@tryhamster/gerbil 1.1.2 → 1.1.3

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 (62) hide show
  1. package/README.md +18 -16
  2. package/dist/cli.mjs +1 -1
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/gpu/hooks.d.mts +3 -3
  5. package/dist/gpu/hooks.mjs +5 -5
  6. package/dist/gpu/hooks.mjs.map +1 -1
  7. package/dist/index-Dgmb2kE3.d.mts.map +1 -1
  8. package/package.json +1 -2
  9. package/docs/PROJECT-STATE.md +0 -321
  10. package/docs/adding-a-model-family.md +0 -280
  11. package/docs/ai-sdk.md +0 -205
  12. package/docs/architecture/README.md +0 -84
  13. package/docs/architecture/caching.md +0 -227
  14. package/docs/architecture/inference.md +0 -176
  15. package/docs/architecture/overview.md +0 -189
  16. package/docs/architecture/streaming.md +0 -261
  17. package/docs/architecture/webgpu.md +0 -213
  18. package/docs/browser.md +0 -762
  19. package/docs/cli.md +0 -155
  20. package/docs/embeddings.md +0 -156
  21. package/docs/frameworks.md +0 -90
  22. package/docs/gerbil-site-native-migration.md +0 -217
  23. package/docs/gpu-engine/architectures.md +0 -398
  24. package/docs/gpu-engine/ir.md +0 -372
  25. package/docs/gpu-engine/kernels.md +0 -718
  26. package/docs/gpu-engine/paper.html +0 -1759
  27. package/docs/gpu-engine/paper.md +0 -2109
  28. package/docs/gpu-engine/safetensors.md +0 -312
  29. package/docs/gpu-engine/tokenizer.md +0 -302
  30. package/docs/kernel-research-queue.md +0 -85
  31. package/docs/mcp-client.md +0 -224
  32. package/docs/mcp.md +0 -109
  33. package/docs/memory-rag.md +0 -91
  34. package/docs/memory.md +0 -301
  35. package/docs/metal-safari-intel.md +0 -190
  36. package/docs/mobile-failure-diagnosis.md +0 -124
  37. package/docs/mobile.md +0 -99
  38. package/docs/observability.md +0 -230
  39. package/docs/onnx-removal-plan.md +0 -339
  40. package/docs/repl.md +0 -473
  41. package/docs/research/autoresearch-portable.md +0 -933
  42. package/docs/research/dispatch-reduction-hivemind.md +0 -84
  43. package/docs/research/ios-safari-model-caching.md +0 -117
  44. package/docs/research/mobile-webgpu-speed-fusion.md +0 -135
  45. package/docs/research/native-stt-model-selection.md +0 -49
  46. package/docs/research/native-tts-model-selection.md +0 -90
  47. package/docs/research/native-vs-chromium-decision.md +0 -152
  48. package/docs/research/nemotron-mamba2-inference.md +0 -910
  49. package/docs/research/qwen35-multimodal.md +0 -293
  50. package/docs/research/qwen36-gemma4-targets.md +0 -337
  51. package/docs/research/sota-embedding-models.md +0 -179
  52. package/docs/research/sota-mobile-models-2026.md +0 -263
  53. package/docs/research/sota-modality-models.md +0 -202
  54. package/docs/research/tps-baselines.md +0 -71
  55. package/docs/research/webgpu-m4-reference.md +0 -104
  56. package/docs/site-update-plan.md +0 -155
  57. package/docs/skills.md +0 -261
  58. package/docs/structured-output.md +0 -123
  59. package/docs/stt.md +0 -111
  60. package/docs/tools.md +0 -304
  61. package/docs/tts.md +0 -147
  62. 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.