@simulatte/doppler 0.1.6 → 0.1.7

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 (316) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +16 -23
  3. package/package.json +14 -1
  4. package/src/adapters/adapter-registry.js +12 -1
  5. package/src/adapters/lora-loader.js +23 -6
  6. package/src/bridge/extension-client.d.ts +5 -0
  7. package/src/bridge/extension-client.js +40 -0
  8. package/src/bridge/index.d.ts +2 -1
  9. package/src/bridge/index.js +6 -4
  10. package/src/browser/browser-converter.js +26 -1
  11. package/src/browser/file-picker.js +6 -0
  12. package/src/browser/safetensors-parser-browser.js +84 -1
  13. package/src/browser/shard-io-browser.js +2 -2
  14. package/src/browser/tensor-source-download.js +8 -2
  15. package/src/browser/tensor-source-http.d.ts +1 -0
  16. package/src/browser/tensor-source-http.js +5 -1
  17. package/src/client/doppler-api.browser.js +20 -4
  18. package/src/client/doppler-api.js +19 -3
  19. package/src/client/doppler-provider/generation.js +12 -0
  20. package/src/client/doppler-provider/model-manager.d.ts +10 -0
  21. package/src/client/doppler-provider/model-manager.js +91 -19
  22. package/src/client/doppler-provider/source-runtime.d.ts +2 -1
  23. package/src/client/doppler-provider/source-runtime.js +132 -13
  24. package/src/client/doppler-registry.json +8 -7
  25. package/src/config/backward-registry-loader.js +17 -2
  26. package/src/config/execution-v0-contract-check.js +113 -15
  27. package/src/config/kernel-path-contract-check.js +57 -29
  28. package/src/config/kernel-path-loader.js +5 -36
  29. package/src/config/kernels/kernel-ref-digests.js +1 -1
  30. package/src/config/kernels/registry.js +14 -1
  31. package/src/config/kernels/registry.json +7 -5
  32. package/src/config/loader.d.ts +1 -1
  33. package/src/config/loader.js +12 -2
  34. package/src/config/merge-contract-check.js +59 -4
  35. package/src/config/merge-helpers.js +128 -7
  36. package/src/config/merge.d.ts +1 -0
  37. package/src/config/merge.js +10 -0
  38. package/src/config/param-validator.js +47 -2
  39. package/src/config/presets/kernel-paths/{gemma2-q4k-dequant-f32a.json → gemma2-q4k-dequant-f32a-nosubgroups.json} +3 -3
  40. package/src/config/presets/kernel-paths/gemma3-f16-fused-f32a-online-streamingprefill.json +223 -0
  41. package/src/config/presets/kernel-paths/{gemma3-q4k-dequant-f32a.json → gemma3-q4k-dequant-f32a-nosubgroups.json} +3 -3
  42. package/src/config/presets/kernel-paths/registry.json +29 -8
  43. package/src/config/presets/models/gemma2.json +2 -2
  44. package/src/config/presets/runtime/experiments/bench/gemma3-bench-q4k.json +1 -1
  45. package/src/config/presets/runtime/experiments/debug/gemma3-debug-q4k.json +1 -1
  46. package/src/config/presets/runtime/experiments/verify/gemma3-verify.json +1 -1
  47. package/src/config/presets/runtime/kernels/dequant-f16-q4k.json +6 -13
  48. package/src/config/presets/runtime/kernels/dequant-f32-q4k.json +6 -13
  49. package/src/config/presets/runtime/kernels/embeddinggemma-q4k-dequant-f32a.json +37 -0
  50. package/src/config/presets/runtime/kernels/fused-q4k.json +6 -13
  51. package/src/config/presets/runtime/kernels/gemma2-q4k-dequant-f16a.json +33 -0
  52. package/src/config/presets/runtime/kernels/gemma2-q4k-dequant-f32a-nosubgroups.json +33 -0
  53. package/src/config/presets/runtime/kernels/gemma2-q4k-fused-f32a.json +33 -0
  54. package/src/config/presets/runtime/kernels/safe-q4k.json +6 -13
  55. package/src/config/presets/runtime/platform/metal-apple-q4k.json +1 -1
  56. package/src/config/runtime.js +6 -1
  57. package/src/config/schema/debug.schema.d.ts +5 -0
  58. package/src/config/schema/doppler.schema.js +16 -21
  59. package/src/config/schema/inference-defaults.schema.js +3 -3
  60. package/src/config/schema/kernel-path.schema.d.ts +5 -1
  61. package/src/config/schema/kernel-thresholds.schema.js +12 -4
  62. package/src/config/schema/manifest.schema.d.ts +2 -1
  63. package/src/config/schema/manifest.schema.js +16 -3
  64. package/src/config/training-defaults.js +30 -22
  65. package/src/converter/conversion-plan.js +94 -9
  66. package/src/converter/core.d.ts +7 -0
  67. package/src/converter/core.js +14 -9
  68. package/src/converter/execution-v0-manifest.js +4 -1
  69. package/src/converter/index.d.ts +1 -0
  70. package/src/converter/index.js +1 -0
  71. package/src/converter/manifest-inference.js +43 -12
  72. package/src/converter/parsers/diffusion.js +0 -3
  73. package/src/converter/quantization-info.js +35 -15
  74. package/src/converter/shard-packer.d.ts +1 -1
  75. package/src/converter/shard-packer.js +4 -1
  76. package/src/debug/config.js +123 -11
  77. package/src/debug/signals.js +7 -1
  78. package/src/debug/tensor.d.ts +2 -0
  79. package/src/debug/tensor.js +13 -2
  80. package/src/distribution/p2p-control-plane.js +52 -12
  81. package/src/distribution/p2p-observability.js +43 -7
  82. package/src/distribution/p2p-webrtc-browser.js +20 -0
  83. package/src/distribution/shard-delivery.js +77 -26
  84. package/src/formats/gguf/types.js +33 -16
  85. package/src/formats/rdrr/groups.d.ts +12 -4
  86. package/src/formats/rdrr/groups.js +3 -6
  87. package/src/formats/rdrr/parsing.js +39 -2
  88. package/src/formats/rdrr/types.d.ts +2 -1
  89. package/src/gpu/command-recorder.js +86 -61
  90. package/src/gpu/device.d.ts +1 -0
  91. package/src/gpu/device.js +73 -19
  92. package/src/gpu/kernel-tuner/benchmarks.js +326 -316
  93. package/src/gpu/kernel-tuner/cache.js +71 -4
  94. package/src/gpu/kernel-tuner/tuner.js +22 -4
  95. package/src/gpu/kernels/attention.js +15 -34
  96. package/src/gpu/kernels/backward/adam.js +62 -58
  97. package/src/gpu/kernels/backward/attention_backward.js +257 -169
  98. package/src/gpu/kernels/backward/conv2d_backward.js +14 -1
  99. package/src/gpu/kernels/cast.js +191 -149
  100. package/src/gpu/kernels/check-stop.js +33 -44
  101. package/src/gpu/kernels/conv2d.js +27 -17
  102. package/src/gpu/kernels/cross_entropy_loss.js +21 -15
  103. package/src/gpu/kernels/depthwise_conv2d.js +36 -26
  104. package/src/gpu/kernels/dequant.js +178 -126
  105. package/src/gpu/kernels/energy.d.ts +3 -21
  106. package/src/gpu/kernels/energy.js +111 -88
  107. package/src/gpu/kernels/feature-check.js +1 -1
  108. package/src/gpu/kernels/fused_ffn.js +84 -65
  109. package/src/gpu/kernels/fused_matmul_residual.js +56 -33
  110. package/src/gpu/kernels/fused_matmul_rmsnorm.js +62 -45
  111. package/src/gpu/kernels/gather.js +33 -15
  112. package/src/gpu/kernels/gelu.js +19 -11
  113. package/src/gpu/kernels/grouped_pointwise_conv2d.js +33 -23
  114. package/src/gpu/kernels/groupnorm.js +34 -23
  115. package/src/gpu/kernels/kv-quantize.js +5 -2
  116. package/src/gpu/kernels/layernorm.js +35 -19
  117. package/src/gpu/kernels/logit-merge.js +5 -3
  118. package/src/gpu/kernels/matmul.js +58 -39
  119. package/src/gpu/kernels/modulate.js +23 -15
  120. package/src/gpu/kernels/moe.js +221 -175
  121. package/src/gpu/kernels/pixel_shuffle.js +22 -14
  122. package/src/gpu/kernels/relu.js +18 -10
  123. package/src/gpu/kernels/repeat_channels.js +25 -17
  124. package/src/gpu/kernels/residual.js +37 -27
  125. package/src/gpu/kernels/rmsnorm.js +57 -41
  126. package/src/gpu/kernels/rope.js +3 -0
  127. package/src/gpu/kernels/sample.js +27 -38
  128. package/src/gpu/kernels/sana_linear_attention.js +18 -10
  129. package/src/gpu/kernels/scale.js +18 -11
  130. package/src/gpu/kernels/shader-cache.js +4 -2
  131. package/src/gpu/kernels/silu.js +120 -72
  132. package/src/gpu/kernels/softmax.js +44 -25
  133. package/src/gpu/kernels/split_qkv.js +23 -13
  134. package/src/gpu/kernels/transpose.js +18 -10
  135. package/src/gpu/kernels/transpose.wgsl +5 -3
  136. package/src/gpu/kernels/upsample2d.js +21 -13
  137. package/src/gpu/kernels/utils.js +20 -13
  138. package/src/gpu/partitioned-buffer-pool.js +10 -2
  139. package/src/gpu/perf-guards.js +2 -9
  140. package/src/gpu/profiler.js +27 -22
  141. package/src/gpu/readback-utils.d.ts +16 -0
  142. package/src/gpu/readback-utils.js +41 -0
  143. package/src/gpu/submit-tracker.js +13 -0
  144. package/src/gpu/uniform-cache.d.ts +1 -0
  145. package/src/gpu/uniform-cache.js +30 -9
  146. package/src/hotswap/intent-bundle.js +6 -0
  147. package/src/hotswap/manifest.d.ts +10 -1
  148. package/src/hotswap/manifest.js +12 -2
  149. package/src/hotswap/runtime.js +30 -8
  150. package/src/index-browser.d.ts +44 -0
  151. package/src/index-browser.js +14 -0
  152. package/src/inference/browser-harness-contract-helpers.d.ts +5 -0
  153. package/src/inference/browser-harness-contract-helpers.js +28 -0
  154. package/src/inference/browser-harness-diffusion-energy-suites.d.ts +2 -0
  155. package/src/inference/browser-harness-diffusion-energy-suites.js +269 -0
  156. package/src/inference/browser-harness-model-helpers.d.ts +16 -0
  157. package/src/inference/browser-harness-model-helpers.js +217 -0
  158. package/src/inference/browser-harness-report-helpers.d.ts +7 -0
  159. package/src/inference/browser-harness-report-helpers.js +42 -0
  160. package/src/inference/browser-harness-runtime-helpers.d.ts +61 -0
  161. package/src/inference/browser-harness-runtime-helpers.js +415 -0
  162. package/src/inference/browser-harness-suite-helpers.d.ts +28 -0
  163. package/src/inference/browser-harness-suite-helpers.js +268 -0
  164. package/src/inference/browser-harness-text-helpers.d.ts +27 -0
  165. package/src/inference/browser-harness-text-helpers.js +788 -0
  166. package/src/inference/browser-harness.d.ts +6 -0
  167. package/src/inference/browser-harness.js +130 -1996
  168. package/src/inference/kv-cache/base.js +140 -94
  169. package/src/inference/kv-cache/tiered.js +5 -3
  170. package/src/inference/moe-router.js +88 -56
  171. package/src/inference/multi-model-network.js +5 -3
  172. package/src/inference/network-evolution.d.ts +11 -2
  173. package/src/inference/network-evolution.js +20 -21
  174. package/src/inference/pipelines/context.d.ts +3 -0
  175. package/src/inference/pipelines/context.js +142 -2
  176. package/src/inference/pipelines/diffusion/helpers.js +7 -2
  177. package/src/inference/pipelines/diffusion/pipeline.js +2 -1
  178. package/src/inference/pipelines/diffusion/sd3-transformer.js +10 -10
  179. package/src/inference/pipelines/diffusion/vae.js +3 -7
  180. package/src/inference/pipelines/energy/pipeline.js +27 -21
  181. package/src/inference/pipelines/energy/quintel.d.ts +5 -0
  182. package/src/inference/pipelines/energy/quintel.js +11 -0
  183. package/src/inference/pipelines/energy-head/row-head-pipeline.js +17 -13
  184. package/src/inference/pipelines/structured/json-head-pipeline.js +26 -11
  185. package/src/inference/pipelines/text/attention/projections.js +151 -101
  186. package/src/inference/pipelines/text/attention/record.js +62 -8
  187. package/src/inference/pipelines/text/attention/run.js +62 -8
  188. package/src/inference/pipelines/text/config.js +3 -4
  189. package/src/inference/pipelines/text/embed.js +2 -8
  190. package/src/inference/pipelines/text/execution-plan.js +41 -19
  191. package/src/inference/pipelines/text/execution-v0-contract-helpers.d.ts +59 -0
  192. package/src/inference/pipelines/text/execution-v0-contract-helpers.js +937 -0
  193. package/src/inference/pipelines/text/execution-v0-runtime-builders.d.ts +15 -0
  194. package/src/inference/pipelines/text/execution-v0-runtime-builders.js +279 -0
  195. package/src/inference/pipelines/text/execution-v0.js +62 -1013
  196. package/src/inference/pipelines/text/generator-steps.d.ts +46 -0
  197. package/src/inference/pipelines/text/generator-steps.js +298 -207
  198. package/src/inference/pipelines/text/generator.js +6 -23
  199. package/src/inference/pipelines/text/init.js +78 -20
  200. package/src/inference/pipelines/text/kernel-path-auto-select.js +2 -0
  201. package/src/inference/pipelines/text/kernel-trace.d.ts +2 -0
  202. package/src/inference/pipelines/text/kernel-trace.js +6 -0
  203. package/src/inference/pipelines/text/layer.js +3 -9
  204. package/src/inference/pipelines/text/linear-attention.d.ts +10 -0
  205. package/src/inference/pipelines/text/linear-attention.js +80 -6
  206. package/src/inference/pipelines/text/logits/gpu.js +10 -5
  207. package/src/inference/pipelines/text/logits/index.js +10 -11
  208. package/src/inference/pipelines/text/logits/utils.d.ts +7 -0
  209. package/src/inference/pipelines/text/logits/utils.js +9 -0
  210. package/src/inference/pipelines/text/lora-apply.js +50 -32
  211. package/src/inference/pipelines/text/model-load.js +279 -104
  212. package/src/inference/pipelines/text/moe-cache.js +5 -4
  213. package/src/inference/pipelines/text/moe-cpu-gptoss.js +74 -69
  214. package/src/inference/pipelines/text/moe-cpu.js +42 -38
  215. package/src/inference/pipelines/text/moe-gpu.js +110 -86
  216. package/src/inference/pipelines/text/ops.js +90 -90
  217. package/src/inference/pipelines/text/probes.js +9 -9
  218. package/src/inference/pipelines/text/weights.js +17 -7
  219. package/src/inference/pipelines/text.js +13 -1
  220. package/src/inference/speculative.d.ts +2 -2
  221. package/src/inference/speculative.js +4 -18
  222. package/src/inference/test-harness.d.ts +1 -1
  223. package/src/inference/test-harness.js +15 -5
  224. package/src/inference/tokenizer.d.ts +0 -5
  225. package/src/inference/tokenizer.js +4 -23
  226. package/src/inference/tokenizers/bpe.js +9 -0
  227. package/src/inference/tokenizers/bundled.js +20 -0
  228. package/src/inference/tokenizers/sentencepiece.js +12 -0
  229. package/src/loader/doppler-loader.js +38 -22
  230. package/src/loader/dtype-utils.js +3 -44
  231. package/src/loader/embedding-loader.js +7 -3
  232. package/src/loader/experts/expert-cache.js +13 -6
  233. package/src/loader/experts/expert-loader.js +10 -6
  234. package/src/loader/final-weights-loader.js +8 -4
  235. package/src/loader/layer-loader.js +2 -1
  236. package/src/loader/loader-state.js +2 -2
  237. package/src/loader/memory-monitor.js +8 -0
  238. package/src/loader/multi-model-loader.d.ts +14 -0
  239. package/src/loader/multi-model-loader.js +70 -24
  240. package/src/loader/shard-cache.js +81 -12
  241. package/src/loader/shard-resolver.js +25 -3
  242. package/src/loader/tensors/tensor-loader.js +209 -144
  243. package/src/loader/tensors/tensor-reader.js +76 -19
  244. package/src/loader/weight-downcast.js +1 -1
  245. package/src/memory/buffer-pool.d.ts +9 -1
  246. package/src/memory/buffer-pool.js +109 -44
  247. package/src/memory/unified-detect.js +1 -1
  248. package/src/rules/inference/kernel-path.rules.json +24 -8
  249. package/src/rules/rule-registry.js +25 -1
  250. package/src/storage/backends/opfs-store.js +68 -24
  251. package/src/storage/downloader.js +364 -83
  252. package/src/storage/index.d.ts +3 -0
  253. package/src/storage/index.js +3 -0
  254. package/src/storage/preflight.d.ts +2 -2
  255. package/src/storage/preflight.js +24 -2
  256. package/src/storage/quickstart-downloader.js +11 -5
  257. package/src/storage/registry.js +10 -4
  258. package/src/storage/reports.js +1 -1
  259. package/src/storage/shard-manager.d.ts +15 -1
  260. package/src/storage/shard-manager.js +51 -3
  261. package/src/storage/source-artifact-store.d.ts +52 -0
  262. package/src/storage/source-artifact-store.js +234 -0
  263. package/src/tooling/command-api-constants.d.ts +9 -0
  264. package/src/tooling/command-api-constants.js +9 -0
  265. package/src/tooling/command-api-family-normalizers.d.ts +9 -0
  266. package/src/tooling/command-api-family-normalizers.js +343 -0
  267. package/src/tooling/command-api-helpers.d.ts +25 -0
  268. package/src/tooling/command-api-helpers.js +262 -0
  269. package/src/tooling/command-api.js +16 -602
  270. package/src/tooling/command-envelope.js +4 -1
  271. package/src/tooling/command-runner-shared.js +52 -18
  272. package/src/tooling/lean-execution-contract.js +150 -3
  273. package/src/tooling/node-browser-command-runner.js +161 -271
  274. package/src/tooling/node-command-runner.js +29 -3
  275. package/src/tooling/node-converter.js +27 -1
  276. package/src/tooling/node-source-runtime.d.ts +1 -1
  277. package/src/tooling/node-source-runtime.js +84 -3
  278. package/src/tooling/node-webgpu.js +24 -21
  279. package/src/tooling/opfs-cache.js +21 -4
  280. package/src/tooling/runtime-input-composition.d.ts +38 -0
  281. package/src/tooling/runtime-input-composition.js +86 -0
  282. package/src/tooling/source-runtime-bundle.d.ts +40 -5
  283. package/src/tooling/source-runtime-bundle.js +261 -34
  284. package/src/tooling/source-runtime-materializer.d.ts +6 -0
  285. package/src/tooling/source-runtime-materializer.js +93 -0
  286. package/src/training/attention-backward.js +32 -17
  287. package/src/training/autograd.js +80 -52
  288. package/src/training/checkpoint-watch.d.ts +2 -1
  289. package/src/training/checkpoint-watch.js +39 -6
  290. package/src/training/checkpoint.js +40 -11
  291. package/src/training/clip.js +2 -1
  292. package/src/training/datasets/token-batch.js +20 -8
  293. package/src/training/distillation/checkpoint-watch.js +1 -0
  294. package/src/training/distillation/student-fixture.d.ts +22 -0
  295. package/src/training/distillation/student-fixture.js +846 -0
  296. package/src/training/distillation/suite-data.d.ts +45 -0
  297. package/src/training/distillation/suite-data.js +189 -0
  298. package/src/training/lora-pipeline.js +4 -7
  299. package/src/training/lora.js +26 -12
  300. package/src/training/loss.js +5 -6
  301. package/src/training/objectives/cross_entropy.js +2 -5
  302. package/src/training/objectives/distill_kd.js +4 -8
  303. package/src/training/objectives/distill_triplet.js +4 -8
  304. package/src/training/objectives/ul_stage2_base.js +4 -8
  305. package/src/training/operator-command.js +2 -0
  306. package/src/training/optimizer.js +19 -7
  307. package/src/training/runner.js +2 -1
  308. package/src/training/suite.js +18 -978
  309. package/src/training/tensor-factory.d.ts +9 -0
  310. package/src/training/tensor-factory.js +13 -0
  311. package/src/training/trainer.js +3 -5
  312. package/src/training/ul_dataset.js +3 -5
  313. package/src/training/workloads.js +70 -79
  314. package/src/version.js +1 -1
  315. package/tools/convert-safetensors-node.js +22 -16
  316. package/tools/doppler-cli.js +44 -25
package/CHANGELOG.md ADDED
@@ -0,0 +1,126 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@simulatte/doppler` are documented in this file.
4
+
5
+ This changelog is package-facing and release-oriented. Entries before `0.1.7`
6
+ were retrofitted from package version history, release commits, and release
7
+ docs so the `0.1.x` line has one conventional npm-visible history surface.
8
+
9
+ ## [0.1.7] - 2026-03-10
10
+
11
+ ### Added
12
+
13
+ - Added a conventional npm-facing changelog and included it in the published
14
+ package file list.
15
+ - Added stronger release-claim, quickstart-registry, local-model-integrity,
16
+ and browser diagnostics regression coverage.
17
+ - Added browser OPFS registry smoke workflows for text and embedding model
18
+ validation.
19
+
20
+ ### Changed
21
+
22
+ - Tightened release-facing model claims around the verified quickstart/catalog
23
+ set and regenerated the support and release matrices from current metadata.
24
+ - Synced the public quickstart registry from canonical catalog metadata instead
25
+ of maintaining it by hand.
26
+
27
+ ### Fixed
28
+
29
+ - Fixed a tensor-loader buffer ownership bug that corrupted returned weight
30
+ buffers and broke Gemma 3 1B generation.
31
+ - Fixed quickstart Hugging Face revision drift for registry-backed model IDs.
32
+ - Fixed multiple CI contract drifts across onboarding, release metadata,
33
+ support matrices, and generated benchmark fixtures.
34
+
35
+ ## [0.1.6] - 2026-03-07
36
+
37
+ ### Added
38
+
39
+ - Added stricter config and contract tests around runtime overrides, kernel-path
40
+ semantics, and release-support metadata.
41
+ - Added distillation helper extraction coverage for training suite refactors.
42
+
43
+ ### Changed
44
+
45
+ - Continued the execution-v0 and training orchestration refactor work so public
46
+ entrypoints read more like facades and less like inline policy code.
47
+ - Refreshed package exports, repository metadata, and release-facing support
48
+ surfaces for the npm package.
49
+
50
+ ### Fixed
51
+
52
+ - Preserved explicit `null` semantics for `runtime.inference.kernelPath` through
53
+ schema, runtime config, and harness paths.
54
+ - Fixed CI drift around onboarding, registry verification aliases, release
55
+ matrix metadata, and kernel-path preset naming.
56
+
57
+ ## [0.1.5] - 2026-03-06
58
+
59
+ ### Added
60
+
61
+ - Added diffusion kernel and contract work, plus additional Lean execution
62
+ contract sweep tooling.
63
+ - Added public API reference inventory and stronger registry workflow tooling.
64
+
65
+ ### Changed
66
+
67
+ - Expanded documentation around public APIs, registry workflow, hosted model
68
+ visibility, and release metadata.
69
+ - Tightened package exports and release checks for the public package surface.
70
+
71
+ ### Fixed
72
+
73
+ - Fixed hosted TranslateGemma visibility and registry metadata alignment across
74
+ docs, demos, and package surfaces.
75
+ - Removed incorrect self-dependency metadata from the published package.
76
+
77
+ ## [0.1.4] - 2026-03-05
78
+
79
+ ### Added
80
+
81
+ - Added Lean execution contract scripts and related package commands.
82
+ - Added translation prompt validation and quickstart/demo polish.
83
+
84
+ ### Fixed
85
+
86
+ - Fixed external resolution issues in conversion publication paths.
87
+ - Fixed quickstart-facing package and demo issues ahead of publication.
88
+
89
+ ## [0.1.3] - 2026-03-05
90
+
91
+ ### Changed
92
+
93
+ - Intermediate package metadata and dependency layout refresh during early npm
94
+ packaging work.
95
+
96
+ ## [0.1.2] - 2026-03-05
97
+
98
+ ### Changed
99
+
100
+ - Aligned build scripts, tests, docs, and package conventions with the active
101
+ workspace and release process.
102
+ - Refined README messaging and compatibility notes before npm publication.
103
+
104
+ ## [0.1.1] - 2026-03-05
105
+
106
+ ### Added
107
+
108
+ - Added benchmark vendor comparison docs, runtime patch documentation, and
109
+ refreshed evidence/chart surfaces for the package release.
110
+
111
+ ### Changed
112
+
113
+ - Moved vendor benchmark dependencies to development dependencies and kept the
114
+ runtime package dependency surface leaner.
115
+ - Refreshed package metadata, exports, and README/API positioning for the first
116
+ public npm publishing cycle.
117
+
118
+ ## [0.1.0] - 2025-12-23
119
+
120
+ ### Added
121
+
122
+ - Initial npm package release for Doppler.
123
+ - Browser and Node command surfaces, CLI entrypoint, loader/storage pipeline,
124
+ RDRR manifest handling, config schemas/presets, WebGPU kernel registry, text
125
+ inference pipeline, conversion tooling, benchmark tooling, tests, and demo
126
+ infrastructure.
package/README.md CHANGED
@@ -2,13 +2,7 @@
2
2
 
3
3
  Inference and training on raw WebGPU. Pure JS + WGSL.
4
4
 
5
- **[Live Demo](https://d4da.com)** · **[npm](https://www.npmjs.com/package/@simulatte/doppler)** · **[simulatte.world](https://simulatte.world)**
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @simulatte/doppler
11
- ```
5
+ ![Phase-latency comparison on one workload across models](https://raw.githubusercontent.com/clocksmith/doppler/main/benchmarks/vendors/results/compare_1b_multi-workload_favorable_phases.svg)
12
6
 
13
7
  ## Quick start
14
8
 
@@ -17,13 +11,25 @@ import { doppler } from '@simulatte/doppler';
17
11
 
18
12
  const model = await doppler.load('gemma3-270m');
19
13
 
20
- for await (const token of model.generate('Hello, world')) {
14
+ for await (const token of model.generate('Describe WebGPU briefly')) {
21
15
  process.stdout.write(token);
22
16
  }
23
17
  ```
24
18
 
19
+ Browser-first WebGPU inference and training with explicit manifest-driven
20
+ runtime behavior. Built for local models, streamed generation, adapter
21
+ hot-swap, and direct JS → WGSL → WebGPU execution.
22
+
25
23
  Registry IDs resolve to hosted RDRR artifacts from `Clocksmith/rdrr` by default. Tokens stream from a native `AsyncGenerator`. See [more examples](#more-examples) below or the canonical [Root API guide](https://github.com/clocksmith/doppler/blob/main/docs/api/root.md).
26
24
 
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @simulatte/doppler
29
+ ```
30
+
31
+ **[Live Demo](https://d4da.com)** · **[npm](https://www.npmjs.com/package/@simulatte/doppler)** · **[docs](https://github.com/clocksmith/doppler/blob/main/docs/INDEX.md)** · **[Project site](https://simulatte.world)**
32
+
27
33
  ## Why Doppler
28
34
 
29
35
  **JS → WGSL → WebGPU.** Direct JavaScript orchestration into native WebGPU kernels, avoiding ONNX runtimes, WASM blobs, and bridge layers.
@@ -36,8 +42,6 @@ Registry IDs resolve to hosted RDRR artifacts from `Clocksmith/rdrr` by default.
36
42
 
37
43
  ## Evidence
38
44
 
39
- ![Phase-latency comparison on one workload across models](https://raw.githubusercontent.com/clocksmith/doppler/main/benchmarks/vendors/results/compare_1b_multi-workload_favorable_phases.svg)
40
-
41
45
  Snapshot artifacts:
42
46
  - [g3-1b-p064-d064-t0-k1.compare.json](https://github.com/clocksmith/doppler/blob/main/benchmarks/vendors/fixtures/g3-1b-p064-d064-t0-k1.compare.json)
43
47
  - [lfm2-5-1-2b-p064-d064-t0-k1.compare.json](https://github.com/clocksmith/doppler/blob/main/benchmarks/vendors/fixtures/lfm2-5-1-2b-p064-d064-t0-k1.compare.json)
@@ -83,19 +87,8 @@ for await (const token of doppler('Hello', { model: 'gemma3-270m' })) {
83
87
  - Architecture: [docs/architecture.md](https://github.com/clocksmith/doppler/blob/main/docs/architecture.md)
84
88
  - Generated model support table: [docs/model-support-matrix.md](https://github.com/clocksmith/doppler/blob/main/docs/model-support-matrix.md)
85
89
 
86
- ## Current model support
87
-
88
- Verified right now:
89
- - `gemma-3-270m-it-wq4k-ef16-hf16`
90
- - `gemma-3-1b-it-wq4k-ef16-hf16`
91
- - `google-embeddinggemma-300m-wq4k-ef16`
92
- - `translategemma-4b-it-wq4k-ef16-hf16`
93
-
94
- Known failing right now:
95
- - `qwen-3-5-0-8b-wq4k-ef16-hf16-f16`
96
- - `qwen-3-5-2b-wq4k-ef16-hf16-f16`
97
-
98
- For the generated status table, including `loads but unverified` and `everything else`, see [docs/model-support-matrix.md](https://github.com/clocksmith/doppler/blob/main/docs/model-support-matrix.md).
90
+ Current model support is generated from the catalog and conversion registry.
91
+ See [docs/model-support-matrix.md](https://github.com/clocksmith/doppler/blob/main/docs/model-support-matrix.md) for the canonical verified, failing, and unverified status table.
99
92
 
100
93
  ## Environment requirements
101
94
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simulatte/doppler",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Browser-native WebGPU inference engine for local intent and inference loops",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -54,6 +54,8 @@
54
54
  "api:docs:sync": "node tools/sync-api-docs.js",
55
55
  "api:docs:check": "node tools/sync-api-docs.js --check",
56
56
  "verify:model": "node tools/doppler-cli.js verify",
57
+ "quickstart:sync": "node tools/sync-quickstart-registry.js",
58
+ "quickstart:check": "node tools/sync-quickstart-registry.js --check",
57
59
  "onboarding:check": "node tools/onboarding-tooling.js check",
58
60
  "onboarding:check:strict": "node tools/onboarding-tooling.js check --strict",
59
61
  "onboarding:scaffold": "node tools/onboarding-tooling.js scaffold",
@@ -73,12 +75,21 @@
73
75
  "external:rdrr:index": "node tools/sync-external-rdrr-index.js",
74
76
  "external:rdrr:index:check": "node tools/sync-external-rdrr-index.js --check",
75
77
  "verify:embeddinggemma-300m": "node tools/run-registry-verify.js embeddinggemma-300m",
78
+ "verify:gemma-3-1b-it-f16": "node tools/run-registry-verify.js gemma-3-1b-it-f16",
79
+ "verify:gemma-3-1b-it-f16-af32": "node tools/run-registry-verify.js gemma-3-1b-it-f16-af32",
80
+ "verify:gemma-3-1b-it-q4k-ehf16-af32": "node tools/run-registry-verify.js gemma-3-1b-it-q4k-ehf16-af32",
81
+ "verify:gemma-3-1b-it-wq4k-ef16-hf16": "node tools/run-registry-verify.js gemma-3-1b-it-wq4k-ef16-hf16",
82
+ "verify:gemma-3-270m-it-q4k-ehf16-af32": "node tools/run-registry-verify.js gemma-3-270m-it-q4k-ehf16-af32",
76
83
  "verify:gemma-3-270m-it-wq4k-ef16": "node tools/run-registry-verify.js gemma-3-270m-it-wq4k-ef16",
77
84
  "verify:gemma-3-270m-it-wq4k-ef16-hf16": "node tools/run-registry-verify.js gemma-3-270m-it-wq4k-ef16-hf16",
78
85
  "verify:gemma-3-270m-it-wq4k-ef16-hf16-f32": "node tools/run-registry-verify.js gemma-3-270m-it-wq4k-ef16-hf16-f32",
86
+ "verify:gemma3-1b": "node tools/run-registry-verify.js gemma3-1b",
87
+ "verify:gemma3-1b-f16": "node tools/run-registry-verify.js gemma3-1b-f16",
79
88
  "verify:gemma3-270m": "node tools/run-registry-verify.js gemma3-270m",
80
89
  "verify:google-embeddinggemma-300m": "node tools/run-registry-verify.js google-embeddinggemma-300m",
90
+ "verify:google-embeddinggemma-300m-q4k-ehf16-af32": "node tools/run-registry-verify.js google-embeddinggemma-300m-q4k-ehf16-af32",
81
91
  "verify:google-embeddinggemma-300m-wq4k-ef16": "node tools/run-registry-verify.js google-embeddinggemma-300m-wq4k-ef16",
92
+ "verify:google-gemma-3-1b-it": "node tools/run-registry-verify.js google-gemma-3-1b-it",
82
93
  "verify:google-gemma-3-270m-it": "node tools/run-registry-verify.js google-gemma-3-270m-it",
83
94
  "verify:google-translategemma-4b-it": "node tools/run-registry-verify.js google-translategemma-4b-it",
84
95
  "verify:qwen-3-5-0-8b": "node tools/run-registry-verify.js qwen-3-5-0-8b",
@@ -104,6 +115,7 @@
104
115
  },
105
116
  "./tooling": {
106
117
  "types": "./src/tooling-exports.d.ts",
118
+ "browser": "./src/tooling-exports.browser.js",
107
119
  "import": "./src/tooling-exports.js"
108
120
  },
109
121
  "./internal": {
@@ -149,6 +161,7 @@
149
161
  "src",
150
162
  "src/gpu/kernels/*.wgsl",
151
163
  "README.md",
164
+ "CHANGELOG.md",
152
165
  "LICENSE",
153
166
  "NOTICE",
154
167
  "BRANDING.md",
@@ -9,6 +9,12 @@ import { DEFAULT_ADAPTER_REGISTRY_CONFIG } from '../config/schema/index.js';
9
9
 
10
10
  const { dbName: DB_NAME, dbVersion: DB_VERSION, storeName: STORE_NAME } = DEFAULT_ADAPTER_REGISTRY_CONFIG;
11
11
 
12
+ function isNodeRuntime() {
13
+ return typeof process !== 'undefined'
14
+ && !!process.versions?.node
15
+ && typeof window === 'undefined';
16
+ }
17
+
12
18
 
13
19
  class IndexedDBStorage {
14
20
  #db = null;
@@ -163,8 +169,13 @@ export class AdapterRegistry {
163
169
  this.#storage = storage;
164
170
  } else if (typeof indexedDB !== 'undefined') {
165
171
  this.#storage = new IndexedDBStorage();
166
- } else {
172
+ } else if (isNodeRuntime()) {
167
173
  this.#storage = new MemoryStorage();
174
+ } else {
175
+ throw new Error(
176
+ 'AdapterRegistry requires IndexedDB in browser environments. ' +
177
+ 'Pass explicit storage or use createMemoryRegistry() for tests.'
178
+ );
168
179
  }
169
180
  }
170
181
 
@@ -82,6 +82,20 @@ async function computeSHA256(data) {
82
82
  return Array.from(hashArray).map(b => b.toString(16).padStart(2, '0')).join('');
83
83
  }
84
84
 
85
+ function assertCompleteAdapterLayers(adapter) {
86
+ for (const [layerIndex, layer] of adapter.layers.entries()) {
87
+ for (const [moduleName, weights] of Object.entries(layer)) {
88
+ const hasA = weights?.a instanceof Float32Array && weights.a.length > 0;
89
+ const hasB = weights?.b instanceof Float32Array && weights.b.length > 0;
90
+ if (!hasA || !hasB) {
91
+ throw new Error(
92
+ `LoRA adapter layer ${layerIndex} module ${moduleName} is incomplete; both lora_a and lora_b tensors are required.`
93
+ );
94
+ }
95
+ }
96
+ }
97
+ }
98
+
85
99
  // ============================================================================
86
100
  // Core Loading Functions
87
101
  // ============================================================================
@@ -156,14 +170,14 @@ export async function loadLoRAWeights(path, options = {}) {
156
170
  if (manifest.checksum && !options.skipVerify) {
157
171
  const algorithm = manifest.checksumAlgorithm;
158
172
  if (algorithm !== 'sha256') {
159
- log.warn('LoRA', `Unsupported checksum algorithm: ${algorithm}, skipping verification`);
173
+ throw new Error(`Unsupported LoRA checksum algorithm: ${algorithm}`);
160
174
  } else if (manifest.weightsPath) {
161
175
  // Compute checksum of the weights file
162
176
  const weightsData = await fetchWithBase(manifest.weightsPath);
163
177
  const computedHash = await computeSHA256(weightsData);
164
178
  checksumValid = computedHash.toLowerCase() === manifest.checksum.toLowerCase();
165
179
  if (!checksumValid) {
166
- log.warn('LoRA', `Checksum mismatch: expected ${manifest.checksum}, got ${computedHash}`);
180
+ throw new Error(`LoRA checksum mismatch: expected ${manifest.checksum}, got ${computedHash}`);
167
181
  }
168
182
  } else if (manifest.tensors && manifest.tensors.length > 0) {
169
183
  // For inline tensors, compute checksum over concatenated tensor data
@@ -187,7 +201,7 @@ export async function loadLoRAWeights(path, options = {}) {
187
201
  const computedHash = await computeSHA256(combined.buffer);
188
202
  checksumValid = computedHash.toLowerCase() === manifest.checksum.toLowerCase();
189
203
  if (!checksumValid) {
190
- log.warn('LoRA', `Checksum mismatch: expected ${manifest.checksum}, got ${computedHash}`);
204
+ throw new Error(`LoRA checksum mismatch: expected ${manifest.checksum}, got ${computedHash}`);
191
205
  }
192
206
  }
193
207
  }
@@ -223,8 +237,7 @@ export async function loadLoRAFromManifest(manifest, options = {}) {
223
237
  for (const tensor of tensors) {
224
238
  const parsed = parseTensorName(tensor.name);
225
239
  if (!parsed) {
226
- log.warn('LoRA', `Skipping unrecognized tensor: ${tensor.name}`);
227
- continue;
240
+ throw new Error(`Unrecognized LoRA tensor name: ${tensor.name}`);
228
241
  }
229
242
 
230
243
  const data = await toFloat32Array(tensor, options);
@@ -257,6 +270,7 @@ export async function loadLoRAFromManifest(manifest, options = {}) {
257
270
  }
258
271
  }
259
272
 
273
+ assertCompleteAdapterLayers(adapter);
260
274
  return adapter;
261
275
  }
262
276
 
@@ -312,7 +326,9 @@ export async function loadLoRAFromSafetensors(data, manifest) {
312
326
  if (tensorName === '__metadata__') continue;
313
327
 
314
328
  const parsed = parseTensorName(tensorName);
315
- if (!parsed) continue;
329
+ if (!parsed) {
330
+ throw new Error(`Unrecognized LoRA safetensors tensor name: ${tensorName}`);
331
+ }
316
332
 
317
333
  const [start, end] = tensorInfo.data_offsets;
318
334
  const tensorData = new Uint8Array(data, dataOffset + start, end - start);
@@ -359,6 +375,7 @@ export async function loadLoRAFromSafetensors(data, manifest) {
359
375
  adapter.layers.set(parsed.layer, layer);
360
376
  }
361
377
 
378
+ assertCompleteAdapterLayers(adapter);
362
379
  return adapter;
363
380
  }
364
381
 
@@ -90,6 +90,11 @@ export declare class ExtensionBridgeClient {
90
90
  */
91
91
  getStatus(): BridgeStatusType;
92
92
 
93
+ /**
94
+ * Get the connected extension target, if any.
95
+ */
96
+ getExtensionId(): string | null;
97
+
93
98
  /**
94
99
  * Check if connected
95
100
  */
@@ -64,6 +64,16 @@ export class ExtensionBridgeClient {
64
64
  throw new Error('Chrome extension API not available');
65
65
  }
66
66
 
67
+ if (this.#status === BridgeStatus.CONNECTING) {
68
+ throw new Error('Bridge client connection already in progress');
69
+ }
70
+ if (this.#status === BridgeStatus.CONNECTED) {
71
+ if ((extensionId ?? null) !== this.#extensionId) {
72
+ throw new Error('Bridge client already connected to a different extension target');
73
+ }
74
+ return;
75
+ }
76
+
67
77
  this.#extensionId = extensionId;
68
78
  this.#status = BridgeStatus.CONNECTING;
69
79
  this.#notifyStatusChange();
@@ -209,6 +219,11 @@ export class ExtensionBridgeClient {
209
219
 
210
220
 
211
221
  #handleMessage(message) {
222
+ if (message?.type === 'error') {
223
+ this.#handleExplicitError(message);
224
+ return;
225
+ }
226
+
212
227
  if (message.type !== 'binary' || !message.data) {
213
228
  log.warn('ExtensionBridge', `Unexpected message type: ${message.type}`);
214
229
  return;
@@ -308,6 +323,27 @@ export class ExtensionBridgeClient {
308
323
  }
309
324
  }
310
325
 
326
+ #handleExplicitError(message) {
327
+ const text = typeof message?.message === 'string' && message.message.length > 0
328
+ ? message.message
329
+ : 'Native bridge error';
330
+ const error = new Error(text);
331
+
332
+ this.#port = null;
333
+ this.#status = BridgeStatus.ERROR;
334
+ this.#notifyStatusChange();
335
+
336
+ for (const [, pending] of this.#pendingRequests) {
337
+ clearTimeout(pending.timeout);
338
+ pending.reject(error);
339
+ }
340
+ this.#pendingRequests.clear();
341
+
342
+ if (this.onError) {
343
+ this.onError(error);
344
+ }
345
+ }
346
+
311
347
 
312
348
  #handleDisconnect() {
313
349
  const error = chrome.runtime?.lastError;
@@ -340,6 +376,10 @@ export class ExtensionBridgeClient {
340
376
  return this.#status;
341
377
  }
342
378
 
379
+ getExtensionId() {
380
+ return this.#extensionId;
381
+ }
382
+
343
383
 
344
384
  isConnected() {
345
385
  return this.#status === BridgeStatus.CONNECTED;
@@ -64,5 +64,6 @@ export declare function createBridgeClient(
64
64
  export declare function readFileNative(
65
65
  path: string,
66
66
  offset?: number,
67
- length?: number
67
+ length?: number,
68
+ extensionId?: string | null
68
69
  ): Promise<Uint8Array>;
@@ -27,24 +27,26 @@ export {
27
27
 
28
28
 
29
29
  export async function createBridgeClient(extensionId = null) {
30
- const { ExtensionBridgeClient, isBridgeAvailable } = await import('./extension-client.js');
30
+ const { getBridgeClient, isBridgeAvailable } = await import('./extension-client.js');
31
31
 
32
32
  if (!isBridgeAvailable()) {
33
33
  throw new Error('Native bridge not available - Chrome extension API required');
34
34
  }
35
35
 
36
- const client = new ExtensionBridgeClient();
36
+ const client = getBridgeClient();
37
37
  await client.connect(extensionId);
38
38
  return client;
39
39
  }
40
40
 
41
41
 
42
- export async function readFileNative(path, offset = 0, length = 0) {
42
+ export async function readFileNative(path, offset = 0, length = 0, extensionId = null) {
43
43
  const { getBridgeClient } = await import('./extension-client.js');
44
44
  const client = getBridgeClient();
45
45
 
46
46
  if (!client.isConnected()) {
47
- await client.connect();
47
+ await client.connect(extensionId);
48
+ } else if ((extensionId ?? null) !== (client.getExtensionId?.() ?? null)) {
49
+ throw new Error('Bridge client already connected to a different extension target');
48
50
  }
49
51
 
50
52
  return client.read(path, offset, length);
@@ -160,6 +160,18 @@ function normalizePath(path) {
160
160
  return String(path || '').replace(/\\/g, '/');
161
161
  }
162
162
 
163
+ function attachRelativePath(file, relativePath) {
164
+ if (!file || !relativePath) return;
165
+ try {
166
+ Object.defineProperty(file, 'relativePath', {
167
+ value: relativePath,
168
+ configurable: true,
169
+ });
170
+ } catch {
171
+ // Ignore if File is non-extensible in this environment.
172
+ }
173
+ }
174
+
163
175
  function getBaseName(path) {
164
176
  const normalized = normalizePath(path);
165
177
  if (!normalized) return '';
@@ -924,8 +936,16 @@ export async function pickModelFiles() {
924
936
 
925
937
  async function collectFilesFromDirectory(
926
938
  dirHandle,
927
- files = []
939
+ files = [],
940
+ basePath = '',
941
+ depth = 0
928
942
  ) {
943
+ if (depth > 4) {
944
+ throw new Error(
945
+ `Model directory exceeds supported depth (4) near "${basePath || dirHandle?.name || '.'}". ` +
946
+ 'Choose a shallower directory root or flatten the model files.'
947
+ );
948
+ }
929
949
  const entries = dirHandle.values();
930
950
  for await (const entry of entries) {
931
951
  if (entry.kind === 'file') {
@@ -937,8 +957,13 @@ async function collectFilesFromDirectory(
937
957
  file.name.endsWith('.json') ||
938
958
  file.name === 'tokenizer.model'
939
959
  ) {
960
+ const relativePath = basePath ? `${basePath}/${entry.name}` : entry.name;
961
+ attachRelativePath(file, relativePath);
940
962
  files.push(file);
941
963
  }
964
+ } else if (entry.kind === 'directory') {
965
+ const nextBasePath = basePath ? `${basePath}/${entry.name}` : entry.name;
966
+ await collectFilesFromDirectory(entry, files, nextBasePath, depth + 1);
942
967
  }
943
968
  }
944
969
  return files;
@@ -140,6 +140,12 @@ async function collectModelFilesFromDirectory(
140
140
  const nextBasePath = basePath ? `${basePath}/${entry.name}` : entry.name;
141
141
  const subFiles = await collectModelFilesFromDirectory(subDirHandle, nextBasePath, maxDepth - 1);
142
142
  files.push(...subFiles);
143
+ } else if (entry.kind === 'directory') {
144
+ const nextBasePath = basePath ? `${basePath}/${entry.name}` : entry.name;
145
+ throw new Error(
146
+ `Model directory exceeds supported depth (${MODEL_DIRECTORY_MAX_DEPTH}) near "${nextBasePath}". ` +
147
+ 'Choose a shallower directory root or flatten the model files.'
148
+ );
143
149
  }
144
150
  }
145
151
 
@@ -53,6 +53,78 @@ async function readTextFromSource(source) {
53
53
  return new TextDecoder().decode(buffer);
54
54
  }
55
55
 
56
+ function resolveIndexedShardLayout(indexJson, fileMap) {
57
+ const weightMap = indexJson?.weight_map;
58
+ if (!weightMap || typeof weightMap !== 'object') {
59
+ throw new Error('Safetensors index JSON must include a weight_map object for sharded parsing.');
60
+ }
61
+
62
+ const referencedShards = new Set();
63
+ for (const [tensorName, shardNameRaw] of Object.entries(weightMap)) {
64
+ if (typeof tensorName !== 'string' || !tensorName.trim()) {
65
+ throw new Error('Safetensors index JSON weight_map contains an invalid tensor name.');
66
+ }
67
+ if (typeof shardNameRaw !== 'string' || !shardNameRaw.trim()) {
68
+ throw new Error(`Safetensors index JSON weight_map entry for "${tensorName}" must reference a shard filename.`);
69
+ }
70
+ referencedShards.add(shardNameRaw);
71
+ }
72
+
73
+ if (referencedShards.size === 0) {
74
+ throw new Error('Safetensors index JSON weight_map must reference at least one shard.');
75
+ }
76
+
77
+ const missingShards = [...referencedShards].filter((shardName) => !fileMap.has(shardName));
78
+ if (missingShards.length > 0) {
79
+ throw new Error(
80
+ `Safetensors sharded parse is missing indexed shard files: ${missingShards.join(', ')}`
81
+ );
82
+ }
83
+
84
+ const extraShards = [...fileMap.keys()].filter((shardName) => !referencedShards.has(shardName));
85
+ if (extraShards.length > 0) {
86
+ throw new Error(
87
+ `Safetensors sharded parse received shard files not referenced by index JSON: ${extraShards.join(', ')}`
88
+ );
89
+ }
90
+
91
+ return {
92
+ weightMap,
93
+ referencedShards,
94
+ };
95
+ }
96
+
97
+ function validateParsedShardsAgainstIndex(parsedShards, weightMap) {
98
+ const seenTensorNames = new Set();
99
+ for (const parsedShard of parsedShards) {
100
+ for (const tensor of parsedShard.parsed.tensors) {
101
+ seenTensorNames.add(tensor.name);
102
+ const mappedShard = weightMap[tensor.name];
103
+ if (typeof mappedShard !== 'string' || !mappedShard.trim()) {
104
+ throw new Error(
105
+ `Safetensors index JSON is missing a weight_map entry for tensor "${tensor.name}".`
106
+ );
107
+ }
108
+ if (mappedShard !== parsedShard.source.name) {
109
+ throw new Error(
110
+ `Safetensors index JSON routes tensor "${tensor.name}" to "${mappedShard}", ` +
111
+ `but it was found in "${parsedShard.source.name}".`
112
+ );
113
+ }
114
+ }
115
+ }
116
+
117
+ const missingTensorMappings = Object.entries(weightMap)
118
+ .filter(([tensorName]) => !seenTensorNames.has(tensorName))
119
+ .map(([tensorName]) => tensorName);
120
+ if (missingTensorMappings.length > 0) {
121
+ throw new Error(
122
+ `Safetensors index JSON references tensors not found in provided shard files: ` +
123
+ `${missingTensorMappings.join(', ')}`
124
+ );
125
+ }
126
+ }
127
+
56
128
  export async function parseSafetensorsFile(file) {
57
129
  const source = normalizeTensorSource(file);
58
130
  const headerSizeBuffer = await source.readRange(0, 8);
@@ -108,8 +180,15 @@ export async function parseSafetensorsSharded(
108
180
  metadata = indexJson.metadata || {};
109
181
  }
110
182
 
111
- const safetensorsSources = sources
183
+ let safetensorsSources = sources
112
184
  .filter((source) => source.name.endsWith('.safetensors'));
185
+ let weightMap = null;
186
+ if (indexJson) {
187
+ const indexedLayout = resolveIndexedShardLayout(indexJson, fileMap);
188
+ weightMap = indexedLayout.weightMap;
189
+ safetensorsSources = safetensorsSources
190
+ .filter((source) => indexedLayout.referencedShards.has(source.name));
191
+ }
113
192
  const parsedShards = await Promise.all(
114
193
  safetensorsSources.map(async (source) => {
115
194
  const parsed = await parseSafetensorsFile(source);
@@ -135,6 +214,10 @@ export async function parseSafetensorsSharded(
135
214
  }
136
215
  }
137
216
 
217
+ if (weightMap) {
218
+ validateParsedShardsAgainstIndex(parsedShards, weightMap);
219
+ }
220
+
138
221
  return {
139
222
  metadata,
140
223
  shards,
@@ -1,7 +1,7 @@
1
1
 
2
2
 
3
3
  import { generateShardFilename } from '../formats/rdrr/index.js';
4
- import { createStreamingHasher } from '../storage/shard-manager.js';
4
+ import { createStreamingHasher, getOpfsPathConfig } from '../storage/shard-manager.js';
5
5
 
6
6
 
7
7
  export class BrowserShardIO {
@@ -13,7 +13,7 @@ export class BrowserShardIO {
13
13
 
14
14
  static async create(modelId, options = {}) {
15
15
  const opfsRoot = await navigator.storage.getDirectory();
16
- const modelsDir = await opfsRoot.getDirectoryHandle('models', { create: true });
16
+ const modelsDir = await opfsRoot.getDirectoryHandle(getOpfsPathConfig().opfsRootDir, { create: true });
17
17
  const modelDir = await modelsDir.getDirectoryHandle(modelId, { create: true });
18
18
  return new BrowserShardIO(modelDir, options);
19
19
  }