@tryhamster/gerbil 1.0.8 → 1.0.9

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 (37) hide show
  1. package/dist/cli.mjs +7 -7
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/frameworks/express.mjs +1 -1
  4. package/dist/frameworks/fastify.mjs +1 -1
  5. package/dist/frameworks/hono.mjs +1 -1
  6. package/dist/frameworks/next.mjs +1 -1
  7. package/dist/frameworks/trpc.mjs +1 -1
  8. package/dist/gerbil-9sGHvqkj.mjs +4 -0
  9. package/dist/{gerbil-D9fzoBGM.mjs → gerbil-CWg6xm9N.mjs} +2 -2
  10. package/dist/{gerbil-D9fzoBGM.mjs.map → gerbil-CWg6xm9N.mjs.map} +1 -1
  11. package/dist/gpu/index.mjs +2 -2
  12. package/dist/{gpu-oLuWWd5G.mjs → gpu-D4lQhTy0.mjs} +2 -2
  13. package/dist/{gpu-oLuWWd5G.mjs.map → gpu-D4lQhTy0.mjs.map} +1 -1
  14. package/dist/index-CGYIAChm.d.mts.map +1 -1
  15. package/dist/index-Dgmb2kE3.d.mts.map +1 -1
  16. package/dist/index.mjs +4 -4
  17. package/dist/integrations/ai-sdk.mjs +1 -1
  18. package/dist/integrations/langchain.mjs +1 -1
  19. package/dist/integrations/llamaindex.mjs +1 -1
  20. package/dist/integrations/mcp.mjs +4 -4
  21. package/dist/{mcp-Cobm_wmI.mjs → mcp-CYp2qO9G.mjs} +3 -3
  22. package/dist/{mcp-Cobm_wmI.mjs.map → mcp-CYp2qO9G.mjs.map} +1 -1
  23. package/dist/{moonshine-stt-C2PR8fM4.mjs → moonshine-stt--Wauoxwa.mjs} +24 -3
  24. package/dist/{moonshine-stt-C2PR8fM4.mjs.map → moonshine-stt--Wauoxwa.mjs.map} +1 -1
  25. package/dist/moonshine-stt-DHrcX9mv.mjs +4 -0
  26. package/dist/{one-liner-rqFVBbNs.mjs → one-liner-CdFRpqJz.mjs} +2 -2
  27. package/dist/{one-liner-rqFVBbNs.mjs.map → one-liner-CdFRpqJz.mjs.map} +1 -1
  28. package/dist/{repl-BJVyb3Qf.mjs → repl-DmzIMwop.mjs} +3 -3
  29. package/dist/skills/index.d.mts +2 -2
  30. package/dist/skills/index.mjs +3 -3
  31. package/dist/{skills-c8rCxj2n.mjs → skills-C7fENinY.mjs} +2 -2
  32. package/dist/{skills-c8rCxj2n.mjs.map → skills-C7fENinY.mjs.map} +1 -1
  33. package/docs/kernel-research-queue.md +83 -57
  34. package/docs/research/autoresearch-portable.md +29 -0
  35. package/package.json +2 -2
  36. package/dist/gerbil-BOsuusm3.mjs +0 -4
  37. package/dist/moonshine-stt-D0EXyr4g.mjs +0 -4
@@ -1,59 +1,85 @@
1
- # Kernel optimization research queue — derived from the Gemma-4 WebGPU Space
2
-
3
- Source reference: `webml-community/gemma-4-webgpu-kernels` (custom WGSL, no ONNX —
4
- same approach as Gerbil). Running `google/gemma-4-E2B-it-qat`. Kernel grep of its
5
- `gemma-4-e2b.js`: **24 `enable subgroups`, 44 `subgroupAdd`, 36 `enable f16`, flash
6
- attention, `unpack4xU8` int4, mixed 2/4-bit packing**. Gerbil today: only 4 `enable
7
- subgroups` / 7 `subgroupAdd` / 15 f16, no flash-attn WGSL.
8
-
9
- Each test = an A/B on Gerbil's existing harness (`scripts/engine/benchmark.mjs`,
10
- `optimize.mjs`, `results.jsonl`, the iPad runner for on-device tok/s). Metric:
11
- decode tok/s + prefill ms + peak GPU/heap MB, on M-series desktop AND iPad/iPhone
12
- (mobile is the target). Gate: keep only if it beats baseline on-device without
13
- quality regression.
14
-
15
- ## Generalizable to the whole engine (validate before shipping)
16
-
17
- 1. **Subgroup-reduced GEMV (int4 decode matvec)** — HIGHEST PRIORITY.
18
- Hypothesis: replacing Gerbil's `workgroupBarrier` reduction in `MatVecInt4`
19
- with a `subgroupAdd` reduction (the Space's GEMV pattern) cuts the decode
20
- reduction cost decode is Gerbil's documented mobile bottleneck.
21
- NOTE: Gerbil's autoresearch tried "subgroups-matvec" before and it was
22
- inconclusive/reverted re-run with the Space's *exact* pattern (it's a
23
- shipped proof it works on broad WebGPU incl. Apple). Test wg sizes 64/128/256.
24
-
25
- 2. **f16 scales/zeros in the int4 dequant** — already flagged as "THE documented
26
- top desktop lever" in prior research; the Space confirms pervasive f16.
27
- Hypothesis: f16 scales/zeros halve the dequant bandwidth. A/B f16 vs f32
28
- scales in `MatVecInt4` / `MatMulInt4`.
29
-
30
- 3. **`unpack4xU8` for int4 nibble unpack.** The Space uses the WGSL builtin
31
- (`packed & 0x0F0F0F0F`, `>>4`). Hypothesis: faster/cleaner than manual shifts.
32
- A/B Gerbil's current unpack vs `unpack4xU8`.
33
-
34
- 4. **Flash-attention WGSL.** The Space ships one; Gerbil has none.
35
- Hypothesis: lower attention peak memory + fewer dispatches (helps the iOS OOM
36
- *and* tok/s). Port a tiled flash-attn kernel, A/B vs current attention on
37
- prefill/decode + peak memory.
38
-
39
- 5. **Mixed 2/4-bit quantization** (the Space's `0x03`/`>>6` masks ⇒ 2-bit layers).
40
- Hypothesis: 2-bit for tolerant layers cuts memory (directly relevant to the
41
- iOS OOM) at acceptable quality. Bigger change (quant pipeline)defer behind
42
- 1–4, needs a mixed-bit checkpoint + per-layer bit assignment.
43
-
44
- ## Gemma-4 specific — prep now (QAT is obviously the way)
45
-
46
- - **Repack `google/gemma-4-E2B-it-qat` into Gerbil's INT4 safetensors layout.**
47
- The checkpoint is a custom/transformers-mobile format NOT directly loadable by
48
- Gerbil's MLX/GPTQ loader. Write a converter (their group-size scale+zeroPoint
49
- int4 Gerbil's packed-uint32 + scales/biases layout). QAT int4 holds
50
- near-bf16 quality at int4 size strictly better than the PTQ MLX-4bit shipped
51
- today, same memory.
52
- - **Add `gemma-4-E2B-it-qat` as a supported model** once repacked (Gemma-4 e2b
53
- is already a supported architecture; this is a new quantized checkpoint).
54
- - Context: Qwen 3.5 0.8B/2B remain the launch defaults; Gemma-4-QAT is a
55
- quality option + the kernel reference, not a default switch.
1
+ # Kernel optimization research queue — data-backed from the Gemma-4 challenge corpus
2
+
3
+ Source of truth: `scripts/research/out/gemma-results.jsonl` (532 experiments harvested
4
+ from the HF `gemma-challenge` bucket; `harvest-gemma-bucket.mjs` re-runs it). 241
5
+ legit rows (ppl 1.5–2.31). Analyzed by a multi-agent workflow that separated
6
+ WebGPU-portable wins from vLLM/CUDA server noise.
7
+
8
+ **Headline reframe:** the 412-TPS leaderboard top is largely **server-only** —
9
+ its biggest delta is `feopt2` (orjson + HTTP/JSON serialization; no browser
10
+ analog) on a trained vLLM speculative drafter. Stripping serving noise, the
11
+ portable wins attack Gerbil's two confirmed bottlenecks exactly: **dispatch-bound
12
+ decode** and the **270 MB vocab OOM**. IGNORE the 412/506/511 headlines as porting
13
+ targets (server serialization + FA3 + ppl spent past Gerbil's 2.31 guardrail).
14
+
15
+ ## THE CONVERGENCE (do these first — they fix the iOS OOM *and* boost TPS)
16
+
17
+ **#1 · vocabprune (lm_head row-prune + logits scatter)** — directly-portable, gemma4.
18
+ Prune the LM head 262144 32k–128k emittable rows, run the INT4 GEMV/argmax over
19
+ the small head, scatter logits back to full vocab (−inf complement) before sampling.
20
+ *Corpus proof:* `braiam-fable onegraph-pck04-vocabprune` 262144→32768 = **+21.3 TPS
21
+ (311→332) AND ppl improved 2.0266→2.0091**; `dixie pck04b 16k` verify bytes
22
+ **~335MB→~21MB/step**. (16k fails MMLU-Pro 0.596; 128k clears 0.688 keep ~128k
23
+ for a general assistant; still halves the head.) Pure load-time data transform,
24
+ **no new WGSL**: slice lm_head q/scales/zeros to keep-rows, set `config.vocab_size`
25
+ (argmax `executor.ts:1016` + matmul_int4 N already read it), add scatter-to-full.
26
+ *Test:* vocab {full, 131072, 32768} × {decode TPS, resident MB, ppl, MMLU-Pro/GSM8K} on M-series + iPad.
27
+
28
+ **#2 · Extend the prune to embed_tokens + the Gemma-4 PLE table** — directly-portable, gemma4.
29
+ NOT in the corpus (the competition ran on servers with no load-time memory limit;
30
+ they only pruned the head for *speed*). **Gerbil's OOM is at model LOAD**, and the
31
+ Gemma-4 `embed_tokens_per_layer` (PLE) table is **~1.17 GB at 4-bit the single
32
+ largest resident tensor** (`model-loader.ts:87, ~1916`). Apply the same keep-set
33
+ row-slice to `embed_tokens` + the PLE table so the *resident* weight actually
34
+ shrinks. **Highest-leverage iOS-OOM action.** *Test:* peak resident MB at load with
35
+ embed/PLE pruned to {full,131072,32768} on iPad; confirm no OOM at the risky tier.
36
+
37
+ **#4 · Untie + INT4 the lm_head** — directly-portable, gemma4. Prerequisite for #1
38
+ (a tied head can't be row-pruned independently). `model-loader.ts:1844-1847`
39
+ currently does the tied fallback (lm_head→embed_tokens). Untie + `quantizeInt4`
40
+ (`quantize.ts:32`, DEFAULT_GROUP_SIZE=128 already matches the corpus g128 sweet spot).
41
+ *Corpus proof:* `foffee int4-lmhead` **118.3 TPS / ppl 2.00672.68× over bf16**
42
+ (kills the ~1.34GB bf16 head read/step); `senpai int4_g128_lmhead` 126.4 / 2.0057,
43
+ MMLU-Pro 0.668 / GSM8K 0.850.
44
+
45
+ ## Whole-engine, portable (after the OOM convergence)
46
+
47
+ **#3 · onegraph-analog (fewer submits, no per-token readback on WebKit)**
48
+ maps-to-webgpu-analog. Gerbil already built the Dawn version: `forwardArgmax`
49
+ (`executor.ts:971-1024`, argmax stays on GPU), `submitGreedyDecodeStep`
50
+ (`executor.ts:1109`, PIPELINE_DEPTH=2, submits without awaiting). **Port that
51
+ no-readback shape to the Safari/iOS path**; raise `webkitGroupSize` toward
52
+ llama.cpp's ~64/CB; no host-JS branch in the hot loop (the `lffn` trap: a host
53
+ `os.path.exists` in the graph cost 420→368 TPS). *Test:* sweep `?group=N` ∈ {1,8,32,64} for correctness + TPS on iPad.
54
+
55
+ **#5 · More decode-op fusion** directly-portable. Gerbil already fuses
56
+ (`fuseDualMatVecDecode`/`fuseDualKVCacheAppend`/`fuseGatedOProj`/`fuseDualRMSNorm`,
57
+ `executor.ts:507-517`). Next: fuse final RMSNorm→lm_head, and argmax into the
58
+ lm_head tail. On WebKit each removed dispatch = a removed submit+drain. *Test:*
59
+ diff `[executor] decode: N dispatches/token` + parity per fusion.
60
+
61
+ **#6 · W4A16 QAT checkpoint** — directly-portable, gemma4. QAT int4 is a quality
62
+ GAIN, not a cost: stock bf16 44.0 / 2.302 → W4A16 QAT **~95.6 / 2.0056** (ppl-guard,
63
+ steve, speed-demon-ne, infinityoptimizer all reproduce). **PREFERRED:** requant
64
+ `google/gemma-4-E4B-it-qat-q4_0-unquantized` through the **existing** `quantizeInt4`
65
+ (g128) — zero new loader code, reuses the safetensors path. **ALT:** add a
66
+ compressed-tensors/pack-quantized adapter like `repackGPTQ` (`gptq-adapter.ts:41`)
67
+ to consume the official `…-qat-w4a16-ct` directly (`model-loader.ts:1074` only knows
68
+ `quant_method=='gptq'` today). *Test:* current post-hoc int4 vs QAT-requant int4 at
69
+ identical g128: ppl + MMLU-Pro/GSM8K (expect QAT lower ppl), TPS unchanged.
70
+
71
+ **#7 · splitkv (KV layout f16/packed/head-split)** — maps-to-webgpu-analog.
72
+ `deja-vu splitkv-honest-16k` 377.8 / 2.002 (best ppl-2.00 run) but n=1, terse body.
73
+ `executor.ts allocateKVCacheBuffers ~1652` + `DualKVCacheAppend ~1936` + `softmax.wgsl`.
74
+
75
+ **#8 · Speculative decoding** — DEFER until #1–#3 land. Biggest raw multiplier
76
+ (~2–3.5×) but needs a **second model in memory** (fights the OOM #1/#2 just relieved)
77
+ and a batched verify that depends on the #3 onegraph-analog (per-token submit+drain
78
+ eats the acceptance win). No draft/verify infra exists in `src/gpu` today. Add
79
+ relaxed/typical acceptance (eps=0.5, +~6%) only as an OPT-IN sampler flag (it changes
80
+ emitted tokens; off by default for an honest on-device assistant).
56
81
 
57
82
  ## Sequence
58
- P0 (iOS OOM crash) first do NOT start kernel work until the playground loads.
59
- Then 1 234 (each gated on-device), Gemma repack in parallel (independent).
83
+ 1 → 4 → 2 (the OOM convergenceload-time data transforms, no new WGSL, fixes iOS
84
+ OOM + boosts TPS)6 (QAT requant, parallel) 3 (WebKit onegraph-analog) 5
85
+ (fusion) → 7 (KV) → 8 (spec decode, last). Each gated on-device (ppl + MMLU-Pro/GSM8K).
@@ -902,3 +902,32 @@ From Karpathy:
902
902
  - [trevin-creator/autoresearch-mlx](https://github.com/trevin-creator/autoresearch-mlx) — Apple Silicon port
903
903
  - [DeepWiki: autoresearch](https://deepwiki.com/karpathy/autoresearch) — detailed system documentation
904
904
  - [DeepWiki: autoresearch-mlx](https://deepwiki.com/trevin-creator/autoresearch-mlx) — MLX port documentation
905
+
906
+ ---
907
+
908
+ ## Gerbil application: lever taxonomy + dual-track gating (from the Gemma-4 corpus, 2026-06-21)
909
+
910
+ We mined a 532-experiment Gemma-4 optimization competition (`scripts/research/out/gemma-results.jsonl`, harvested by `harvest-gemma-bucket.mjs`; analysis in `docs/kernel-research-queue.md`). It validated the loop above AND taught us how to point Gerbil's own autoresearch. Five methodology learnings:
911
+
912
+ ### 1. The metric is TPS *under a quality guardrail* — never raw TPS
913
+ The competition maximized decode TPS gated by perplexity ≤ ~2.31 (and downstream MMLU-Pro / GSM8K / GPQA at the extremes). Raw TPS gets gamed instantly (we found rows at `tps=1.79e308`, `1000000`, `ppl=0`). **Gerbil's autoresearch metric must be `TPS subject to ppl ≤ guard AND eval-suite ≥ floor`.** A speedup that moves ppl past the guard or fails MMLU-Pro is a REVERT, not a keep. (E.g. vocabprune to 16k rows hit 330 TPS but failed MMLU-Pro 0.596; 128k cleared 0.688 — only the 128k variant is a "keep".)
914
+
915
+ ### 2. Device-class gating — a win on one class can CRASH another
916
+ The same kernel change has three regimes: **server/Dawn** (no memory limit, batching), **iPad WebKit** (tolerates `webkitGroupSize` up to ~128 → ~3.4× decode), **iPhone WebKit** (group>1 HARD-CRASHES the GPU process — the "third device class"). An experiment is only "kept" once validated on the **target device class**. Autoresearch must run with a device-class tag and never promote a change that regresses/crashes a class it's meant to support. (This is why the decided "Production submit strategy" must be a *crash-surviving auto-probe*, not a global default.)
917
+
918
+ ### 3. Two tracks — same engine, different lever sets
919
+ Gerbil runs both **client WebGPU (browser, iOS/desktop)** and **server (Node + Dawn)**. The corpus's biggest headline wins were **server-only** (vLLM serving infra: HTTP/JSON serialization `feopt2`, continuous batching, FA3 CUDA tiling) — irrelevant to a single-user browser but **directly relevant to Gerbil-on-server**. So run autoresearch as TWO tracks with different `program.md` constraints: WebGPU-mobile (dispatch-bound + memory-tight) and server (batching, larger models, spec-decode with a small draft). Don't discard server wins — route them to the server track.
920
+
921
+ ### 4. Filter portable-from-infra
922
+ Most corpus "improvements" were serving/infrastructure, not engine. Before queueing an experiment, classify it: directly-portable (a WGSL kernel / decode-loop change), maps-to-WebGPU-analog (e.g. CUDA-graph → fewer `queue.submit()`s; the `onegraph` family → Gerbil's `forwardArgmax`/PIPELINE_DEPTH path), or infra-only (skip for the mobile track). Only the first two belong in the WebGPU autoresearch loop.
923
+
924
+ ### 5. Per-model AND whole-engine levers
925
+ Some levers are family-specific (Gemma-4's PLE table; a model's QAT checkpoint; vocab size); most are whole-engine (dispatch/launch reduction, op fusion, KV layout, int4 packing group-size). Run autoresearch **per-model** to catch arch-specific wins (e.g. Gemma PLE pruning) AND **overall** on a shared kernel set. Note Gerbil already captures several corpus levers (int4-*tied* head, CPU-streamed PLE, extensive decode fusion, the `forwardArgmax` Dawn path) — so the baseline is higher than the competition's; target the *unshipped* levers (the `webkitGroupSize` auto-probe, vocab prune, QAT requant) not the already-done ones.
926
+
927
+ ### The lever taxonomy to seed Gerbil's `program.md`
928
+ Ranked, with bottleneck attacked (full detail + corpus evidence + file:line in `docs/kernel-research-queue.md`):
929
+ - **Launch/dispatch reduction** (whole-engine; mobile decode is dispatch-bound): the `webkitGroupSize` crash-surviving auto-probe (decided in paper §19, never shipped — default still 1), more op fusion, no host-JS in the hot loop.
930
+ - **Vocab/memory** (per-model, large-vocab; fixes iOS load OOM): prune lm_head/embed/PLE to a freq keepset + logits scatter; relevant to Qwen3.5 (248K vocab) too, not just Gemma.
931
+ - **Quantization** (per-checkpoint): QAT int4 (quality *gain*), int4 packing group-size sweet spot (g128), mixed 2/4-bit.
932
+ - **KV** (whole-engine): f16/packed/head-split layout.
933
+ - **Speculative decoding** (server track first): small draft + batched verify; ~2–3.5× but needs a 2nd model in memory (fights mobile OOM) → server/large-model regime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryhamster/gerbil",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Local LLM inference for Node.js. GPU-accelerated. Zero config. Works standalone or with Vercel AI SDK.",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -216,6 +216,6 @@
216
216
  "test:watch": "vitest",
217
217
  "changeset": "changeset",
218
218
  "version": "changeset version",
219
- "release": "pnpm publish && changeset tag"
219
+ "release": "changeset publish"
220
220
  }
221
221
  }
@@ -1,4 +0,0 @@
1
- import { t as Gerbil } from "./gerbil-D9fzoBGM.mjs";
2
- import "./utils-DKO55ZmZ.mjs";
3
-
4
- export { Gerbil };
@@ -1,4 +0,0 @@
1
- import "./architectures-C1I5V3Dt.mjs";
2
- import { t as MoonshineSTT } from "./moonshine-stt-C2PR8fM4.mjs";
3
-
4
- export { MoonshineSTT };