@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
@@ -15,9 +15,14 @@ export function chooseDefinedWithSource(path, overrideValue, fallbackValue, sour
15
15
  }
16
16
 
17
17
  export function mergeShallowObject(base, override) {
18
- if (!override || typeof override !== 'object' || Array.isArray(override)) {
18
+ if (override === undefined) {
19
19
  return base;
20
20
  }
21
+ if (override === null || typeof override !== 'object' || Array.isArray(override)) {
22
+ throw new Error(
23
+ 'DopplerConfigError: shallow object overrides must be plain objects when provided explicitly.'
24
+ );
25
+ }
21
26
  return { ...base, ...override };
22
27
  }
23
28
 
@@ -29,17 +34,133 @@ export function replaceSubtree(overrideValue, fallbackValue) {
29
34
  return chooseNullish(overrideValue, fallbackValue);
30
35
  }
31
36
 
37
+ const DEFAULT_KERNEL_PATH_POLICY = Object.freeze({
38
+ mode: 'locked',
39
+ sourceScope: Object.freeze(['model', 'manifest']),
40
+ onIncompatible: 'error',
41
+ });
42
+
43
+ const VALID_KERNEL_PATH_POLICY_SOURCES = new Set([
44
+ 'model',
45
+ 'manifest',
46
+ 'config',
47
+ 'execution-v0',
48
+ ]);
49
+
50
+ function normalizeKernelPathPolicyMode(value) {
51
+ if (value === undefined) {
52
+ return DEFAULT_KERNEL_PATH_POLICY.mode;
53
+ }
54
+ const normalized = String(value).trim().toLowerCase();
55
+ if (normalized === 'locked' || normalized === 'capability-aware') {
56
+ return normalized;
57
+ }
58
+ throw new Error(
59
+ `DopplerConfigError: runtime.inference.kernelPathPolicy.mode must be "locked" or "capability-aware"; got ${JSON.stringify(value)}.`
60
+ );
61
+ }
62
+
63
+ function normalizeKernelPathPolicySource(source) {
64
+ const normalized = String(source ?? '').trim().toLowerCase();
65
+ if (!normalized) {
66
+ throw new Error(
67
+ 'DopplerConfigError: runtime.inference.kernelPathPolicy.sourceScope entries must be non-empty strings.'
68
+ );
69
+ }
70
+ if (normalized === 'runtime') {
71
+ throw new Error(
72
+ 'DopplerConfigError: runtime.inference.kernelPathPolicy.sourceScope does not accept legacy "runtime". Use "config".'
73
+ );
74
+ }
75
+ if (normalized === 'execution_v0') {
76
+ throw new Error(
77
+ 'DopplerConfigError: runtime.inference.kernelPathPolicy.sourceScope does not accept legacy "execution_v0". Use "execution-v0".'
78
+ );
79
+ }
80
+ if (!VALID_KERNEL_PATH_POLICY_SOURCES.has(normalized)) {
81
+ throw new Error(
82
+ `DopplerConfigError: runtime.inference.kernelPathPolicy.sourceScope entries must be model|manifest|config|execution-v0; got ${JSON.stringify(source)}.`
83
+ );
84
+ }
85
+ return normalized;
86
+ }
87
+
88
+ function normalizeKernelPathPolicySourceScope(value) {
89
+ if (value === undefined) {
90
+ return [...DEFAULT_KERNEL_PATH_POLICY.sourceScope];
91
+ }
92
+ if (!Array.isArray(value) || value.length === 0) {
93
+ throw new Error(
94
+ 'DopplerConfigError: runtime.inference.kernelPathPolicy.sourceScope must be a non-empty array.'
95
+ );
96
+ }
97
+ return [...new Set(value.map((source) => normalizeKernelPathPolicySource(source)))];
98
+ }
99
+
100
+ function normalizeKernelPathPolicyOnIncompatible(value) {
101
+ if (value === undefined) {
102
+ return DEFAULT_KERNEL_PATH_POLICY.onIncompatible;
103
+ }
104
+ const normalized = String(value).trim().toLowerCase();
105
+ if (normalized === 'error' || normalized === 'remap') {
106
+ return normalized;
107
+ }
108
+ throw new Error(
109
+ `DopplerConfigError: runtime.inference.kernelPathPolicy.onIncompatible must be "error" or "remap"; got ${JSON.stringify(value)}.`
110
+ );
111
+ }
112
+
113
+ function assertKernelPathPolicyObject(value, label) {
114
+ if (value === undefined) {
115
+ return;
116
+ }
117
+ if (value === null) {
118
+ throw new Error(`DopplerConfigError: ${label} must not be null.`);
119
+ }
120
+ if (typeof value !== 'object' || Array.isArray(value)) {
121
+ throw new Error(
122
+ `DopplerConfigError: ${label} must be an object.`
123
+ );
124
+ }
125
+ }
126
+
127
+ function assertKernelPathPolicySourceAliasesCompatible(policy, label) {
128
+ if (!policy || policy.sourceScope === undefined || policy.allowSources === undefined) {
129
+ return;
130
+ }
131
+
132
+ const sourceScope = normalizeKernelPathPolicySourceScope(policy.sourceScope);
133
+ const allowSources = normalizeKernelPathPolicySourceScope(policy.allowSources);
134
+ const aliasesMatch = sourceScope.length === allowSources.length
135
+ && sourceScope.every((value, index) => value === allowSources[index]);
136
+
137
+ if (!aliasesMatch) {
138
+ throw new Error(
139
+ `DopplerConfigError: ${label}.sourceScope and ${label}.allowSources must match exactly when both are provided.`
140
+ );
141
+ }
142
+ }
143
+
32
144
  export function mergeKernelPathPolicy(basePolicy, overridePolicy) {
145
+ assertKernelPathPolicyObject(basePolicy, 'runtime.inference.kernelPathPolicy');
146
+ assertKernelPathPolicyObject(overridePolicy, 'runtime.inference.kernelPathPolicy');
147
+ assertKernelPathPolicySourceAliasesCompatible(basePolicy, 'runtime.inference.kernelPathPolicy');
148
+ assertKernelPathPolicySourceAliasesCompatible(overridePolicy, 'runtime.inference.kernelPathPolicy');
33
149
  const base = basePolicy ?? {};
34
150
  const override = overridePolicy ?? {};
35
- const baseSourceScope = base.sourceScope ?? base.allowSources;
36
- const overrideSourceScope = override.sourceScope ?? override.allowSources;
37
- const sourceScope = overrideSourceScope ?? baseSourceScope;
151
+ const sourceScope = normalizeKernelPathPolicySourceScope(
152
+ override.sourceScope
153
+ ?? override.allowSources
154
+ ?? base.sourceScope
155
+ ?? base.allowSources
156
+ );
38
157
  return {
39
- mode: override.mode ?? base.mode,
158
+ mode: normalizeKernelPathPolicyMode(override.mode ?? base.mode),
40
159
  sourceScope,
41
- allowSources: sourceScope,
42
- onIncompatible: override.onIncompatible ?? base.onIncompatible,
160
+ allowSources: [...sourceScope],
161
+ onIncompatible: normalizeKernelPathPolicyOnIncompatible(
162
+ override.onIncompatible ?? base.onIncompatible
163
+ ),
43
164
  };
44
165
  }
45
166
 
@@ -54,6 +54,7 @@ export interface MergedInferenceConfig {
54
54
  ffn: ManifestFFNSchema;
55
55
  rope: ManifestRoPESchema;
56
56
  output: ManifestOutputSchema;
57
+ pipeline: ManifestInferenceSchema['pipeline'];
57
58
  layerPattern: ManifestLayerPatternSchema | null;
58
59
  chatTemplate: ManifestChatTemplateSchema;
59
60
  defaultKernelPath: string | null;
@@ -333,12 +333,22 @@ export function mergeConfig(
333
333
  sources
334
334
  );
335
335
 
336
+ let pipeline = manifestInf.pipeline;
337
+ const runtimePipeline = runtimeOverrides?.pipeline;
338
+ if (runtimePipeline !== undefined) {
339
+ pipeline = runtimePipeline;
340
+ sources.set('inference.pipeline', 'runtime');
341
+ } else {
342
+ sources.set('inference.pipeline', 'manifest');
343
+ }
344
+
336
345
  const inference = {
337
346
  attention: mergeAttention(manifestInf.attention, runtimeOverrides?.attention, sources),
338
347
  normalization: mergeNormalization(manifestInf.normalization, runtimeOverrides?.normalization, sources),
339
348
  ffn: mergeFFN(manifestInf.ffn, runtimeOverrides?.ffn, sources),
340
349
  rope: mergeRoPE(manifestInf.rope, runtimeOverrides?.rope, sources),
341
350
  output: mergeOutput(manifestInf.output, runtimeOverrides?.output, sources),
351
+ pipeline,
342
352
  layerPattern,
343
353
  chatTemplate,
344
354
  defaultKernelPath,
@@ -2,6 +2,7 @@ import { log } from '../debug/index.js';
2
2
  import { PARAM_CATEGORIES, CategoryRules } from './param-categories.js';
3
3
  import { TOOLING_INTENTS, TOOLING_DIAGNOSTICS } from './schema/tooling.schema.js';
4
4
  import { validateEcosystemConfig } from './schema/ecosystem.schema.js';
5
+ import { isPlainObject } from '../utils/plain-object.js';
5
6
 
6
7
  export function validateCallTimeOptions(options) {
7
8
  if (!options) return;
@@ -33,7 +34,23 @@ export function validateCallTimeOptions(options) {
33
34
  }
34
35
 
35
36
  export function validateRuntimeOverrides(overrides) {
37
+ if (!isPlainObject(overrides)) {
38
+ throw new Error('DopplerConfigError: runtime overrides must be an object when provided.');
39
+ }
40
+
41
+ assertRequiredRuntimeOverrideNotNull(overrides, 'shared');
42
+ assertRequiredRuntimeOverrideNotNull(overrides, 'loading');
43
+ assertRequiredRuntimeOverrideNotNull(overrides, 'inference');
44
+ assertRequiredRuntimeOverrideNotNull(overrides, 'emulation');
45
+ assertRequiredRuntimeOverrideNotNull(overrides?.inference, 'batching', 'runtime.inference');
46
+ assertRequiredRuntimeOverrideNotNull(overrides?.inference, 'compute', 'runtime.inference');
47
+ assertRequiredRuntimeOverrideNotNull(overrides?.inference, 'generation', 'runtime.inference');
48
+ assertRequiredRuntimeOverrideNotNull(overrides?.inference, 'kernelPathPolicy', 'runtime.inference');
49
+
36
50
  const modelOverrides = overrides?.inference?.modelOverrides;
51
+ if (modelOverrides !== undefined && modelOverrides !== null && !isPlainObject(modelOverrides)) {
52
+ throw new Error('DopplerConfigError: runtime.inference.modelOverrides must be an object when provided.');
53
+ }
37
54
  if (!modelOverrides) return;
38
55
 
39
56
  const params = flattenObject(modelOverrides);
@@ -214,6 +231,15 @@ function validateKernelPathPolicy(label, value) {
214
231
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
215
232
  throw new Error(`DopplerConfigError: ${label} must be an object.`);
216
233
  }
234
+ if (
235
+ value.sourceScope !== undefined
236
+ && value.allowSources !== undefined
237
+ && !arraysEqual(value.sourceScope, value.allowSources)
238
+ ) {
239
+ throw new Error(
240
+ `DopplerConfigError: ${label}.sourceScope and ${label}.allowSources must match exactly when both are provided.`
241
+ );
242
+ }
217
243
  if (value.mode !== 'locked' && value.mode !== 'capability-aware') {
218
244
  throw new Error(`DopplerConfigError: ${label}.mode must be "locked" or "capability-aware".`);
219
245
  }
@@ -224,12 +250,31 @@ function validateKernelPathPolicy(label, value) {
224
250
  if (value.onIncompatible !== 'error' && value.onIncompatible !== 'remap') {
225
251
  throw new Error(`DopplerConfigError: ${label}.onIncompatible must be "error" or "remap".`);
226
252
  }
227
- const validSources = new Set(['model', 'manifest', 'config', 'runtime', 'execution-v0']);
253
+ const validSources = new Set(['model', 'manifest', 'config', 'execution-v0']);
228
254
  for (const source of sourceScope) {
229
255
  if (!validSources.has(source)) {
230
256
  throw new Error(
231
- `DopplerConfigError: ${label}.sourceScope entries must be model|manifest|config|runtime|execution-v0.`
257
+ `DopplerConfigError: ${label}.sourceScope entries must be model|manifest|config|execution-v0.`
232
258
  );
233
259
  }
234
260
  }
235
261
  }
262
+
263
+ function assertRequiredRuntimeOverrideNotNull(container, key, prefix = 'runtime') {
264
+ if (!isPlainObject(container) || !Object.prototype.hasOwnProperty.call(container, key)) {
265
+ return;
266
+ }
267
+ if (container[key] === null) {
268
+ throw new Error(`DopplerConfigError: ${prefix}.${key} must not be null.`);
269
+ }
270
+ }
271
+
272
+ function arraysEqual(left, right) {
273
+ if (!Array.isArray(left) || !Array.isArray(right)) {
274
+ return false;
275
+ }
276
+ if (left.length !== right.length) {
277
+ return false;
278
+ }
279
+ return left.every((value, index) => value === right[index]);
280
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
- "id": "gemma2-q4k-dequant-f32a",
3
- "name": "Gemma 2 Q4K Dequant (F32 activations)",
4
- "description": "Q4K weights dequantized to F16 with F32 activations. Non-fused compatibility path with no subgroup requirement.",
2
+ "id": "gemma2-q4k-dequant-f32a-nosubgroups",
3
+ "name": "Gemma 2 Q4K Dequant (F32 activations, no subgroups)",
4
+ "description": "Q4K weights dequantized to F16 with F32 activations. Non-fused path with no subgroup requirement that still requires shader-f16 kernels.",
5
5
  "activationDtype": "f32",
6
6
 
7
7
  "decode": {
@@ -0,0 +1,223 @@
1
+ {
2
+ "id": "gemma3-f16-fused-f32a-online-streamingprefill",
3
+ "name": "Gemma 3 F16 (F32 activations, online, streaming prefill)",
4
+ "description": "F16 weights with F32 activations, online decode attention, and streaming prefill attention for Gemma 3 stability-sensitive runs.",
5
+ "activationDtype": "f32",
6
+ "kvDtype": "f16",
7
+ "decode": {
8
+ "steps": [
9
+ {
10
+ "op": "input_norm",
11
+ "kernel": "rmsnorm.wgsl",
12
+ "entry": "main"
13
+ },
14
+ {
15
+ "op": "q_proj",
16
+ "kernel": "matmul_gemv_subgroup.wgsl",
17
+ "entry": "main_vec4",
18
+ "weights": "layer.{L}.self_attn.q_proj"
19
+ },
20
+ {
21
+ "op": "k_proj",
22
+ "kernel": "matmul_gemv_subgroup.wgsl",
23
+ "entry": "main_vec4",
24
+ "weights": "layer.{L}.self_attn.k_proj"
25
+ },
26
+ {
27
+ "op": "v_proj",
28
+ "kernel": "matmul_gemv_subgroup.wgsl",
29
+ "entry": "main_vec4",
30
+ "weights": "layer.{L}.self_attn.v_proj"
31
+ },
32
+ {
33
+ "op": "rope_q",
34
+ "kernel": "rope.wgsl",
35
+ "entry": "main"
36
+ },
37
+ {
38
+ "op": "rope_k",
39
+ "kernel": "rope.wgsl",
40
+ "entry": "main"
41
+ },
42
+ {
43
+ "op": "attention",
44
+ "kernel": "attention_decode_online_f16kv.wgsl",
45
+ "entry": "main"
46
+ },
47
+ {
48
+ "op": "o_proj",
49
+ "kernel": "matmul_gemv_subgroup.wgsl",
50
+ "entry": "main_vec4",
51
+ "weights": "layer.{L}.self_attn.o_proj"
52
+ },
53
+ {
54
+ "op": "attn_residual",
55
+ "kernel": "residual.wgsl",
56
+ "entry": "main"
57
+ },
58
+ {
59
+ "op": "post_attn_norm",
60
+ "kernel": "rmsnorm.wgsl",
61
+ "entry": "main"
62
+ },
63
+ {
64
+ "op": "gate_proj",
65
+ "kernel": "matmul_gemv_subgroup.wgsl",
66
+ "entry": "main_vec4",
67
+ "weights": "layer.{L}.mlp.gate_proj"
68
+ },
69
+ {
70
+ "op": "up_proj",
71
+ "kernel": "matmul_gemv_subgroup.wgsl",
72
+ "entry": "main_vec4",
73
+ "weights": "layer.{L}.mlp.up_proj"
74
+ },
75
+ {
76
+ "op": "activation",
77
+ "kernel": "gelu.wgsl",
78
+ "entry": "main",
79
+ "constants": {
80
+ "HAS_GATE": true
81
+ }
82
+ },
83
+ {
84
+ "op": "down_proj",
85
+ "kernel": "matmul_gemv_subgroup.wgsl",
86
+ "entry": "main_vec4",
87
+ "weights": "layer.{L}.mlp.down_proj"
88
+ },
89
+ {
90
+ "op": "ffn_residual",
91
+ "kernel": "residual.wgsl",
92
+ "entry": "main"
93
+ }
94
+ ]
95
+ },
96
+ "prefill": {
97
+ "steps": [
98
+ {
99
+ "op": "input_norm",
100
+ "kernel": "rmsnorm.wgsl",
101
+ "entry": "main"
102
+ },
103
+ {
104
+ "op": "q_proj",
105
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
106
+ "entry": "main",
107
+ "weights": "layer.{L}.self_attn.q_proj"
108
+ },
109
+ {
110
+ "op": "k_proj",
111
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
112
+ "entry": "main",
113
+ "weights": "layer.{L}.self_attn.k_proj"
114
+ },
115
+ {
116
+ "op": "v_proj",
117
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
118
+ "entry": "main",
119
+ "weights": "layer.{L}.self_attn.v_proj"
120
+ },
121
+ {
122
+ "op": "rope_q",
123
+ "kernel": "rope.wgsl",
124
+ "entry": "main"
125
+ },
126
+ {
127
+ "op": "rope_k",
128
+ "kernel": "rope.wgsl",
129
+ "entry": "main"
130
+ },
131
+ {
132
+ "op": "attention",
133
+ "kernel": "attention_streaming_f16kv.wgsl",
134
+ "entry": "main"
135
+ },
136
+ {
137
+ "op": "o_proj",
138
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
139
+ "entry": "main",
140
+ "weights": "layer.{L}.self_attn.o_proj"
141
+ },
142
+ {
143
+ "op": "attn_residual",
144
+ "kernel": "residual.wgsl",
145
+ "entry": "main"
146
+ },
147
+ {
148
+ "op": "post_attn_norm",
149
+ "kernel": "rmsnorm.wgsl",
150
+ "entry": "main"
151
+ },
152
+ {
153
+ "op": "gate_proj",
154
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
155
+ "entry": "main",
156
+ "weights": "layer.{L}.mlp.gate_proj"
157
+ },
158
+ {
159
+ "op": "up_proj",
160
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
161
+ "entry": "main",
162
+ "weights": "layer.{L}.mlp.up_proj"
163
+ },
164
+ {
165
+ "op": "activation",
166
+ "kernel": "gelu.wgsl",
167
+ "entry": "main",
168
+ "constants": {
169
+ "HAS_GATE": true
170
+ }
171
+ },
172
+ {
173
+ "op": "down_proj",
174
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
175
+ "entry": "main",
176
+ "weights": "layer.{L}.mlp.down_proj"
177
+ },
178
+ {
179
+ "op": "ffn_residual",
180
+ "kernel": "residual.wgsl",
181
+ "entry": "main"
182
+ }
183
+ ]
184
+ },
185
+ "preLayer": [
186
+ {
187
+ "op": "embed",
188
+ "kernel": "gather.wgsl",
189
+ "entry": "main",
190
+ "weights": "embed_tokens"
191
+ }
192
+ ],
193
+ "postLayer": [
194
+ {
195
+ "op": "final_norm",
196
+ "kernel": "rmsnorm.wgsl",
197
+ "entry": "main"
198
+ },
199
+ {
200
+ "op": "lm_head",
201
+ "kernel": "matmul_gemv_subgroup.wgsl",
202
+ "entry": "main_multicol",
203
+ "weights": "lm_head",
204
+ "constants": {
205
+ "MULTICOL_COLS_PER_WG": 64,
206
+ "MULTICOL_THREADS_PER_COL": 4
207
+ }
208
+ },
209
+ {
210
+ "op": "lm_head_prefill",
211
+ "kernel": "matmul_f16w_f32a_tiled.wgsl",
212
+ "entry": "main",
213
+ "weights": "lm_head"
214
+ }
215
+ ],
216
+ "sampling": [
217
+ {
218
+ "op": "sample",
219
+ "kernel": "sample.wgsl",
220
+ "entry": "sample_single_pass"
221
+ }
222
+ ]
223
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
- "id": "gemma3-q4k-dequant-f32a",
3
- "name": "Gemma 3 Q4K Dequant (F32 activations)",
4
- "description": "Q4K weights dequantized to F16 with F32 activations for improved stability on small Gemma 3 checkpoints.",
2
+ "id": "gemma3-q4k-dequant-f32a-nosubgroups",
3
+ "name": "Gemma 3 Q4K Dequant (F32 activations, no subgroups)",
4
+ "description": "Q4K weights dequantized to F16 with F32 activations. Subgroup-free non-online path for Gemma 3 that still requires shader-f16 kernels.",
5
5
  "activationDtype": "f32",
6
6
  "kvDtype": "f16",
7
7
 
@@ -16,11 +16,18 @@
16
16
  "notes": "Default Gemma 2 Q4K dequant path for baseline F16 activation workflows."
17
17
  },
18
18
  {
19
- "id": "gemma2-q4k-dequant-f32a",
20
- "file": "gemma2-q4k-dequant-f32a.json",
19
+ "id": "gemma2-q4k-dequant-f32a-nosubgroups",
20
+ "file": "gemma2-q4k-dequant-f32a-nosubgroups.json",
21
21
  "status": "canonical",
22
- "statusReason": "compatibility",
23
- "notes": "Subgroup-free Gemma 2 Q4K dequant path with F32 activations."
22
+ "statusReason": "subgroup-free",
23
+ "notes": "Subgroup-free Gemma 2 Q4K dequant path with F32 activations. Still requires shader-f16 kernels."
24
+ },
25
+ {
26
+ "id": "gemma2-q4k-dequant-f32a",
27
+ "aliasOf": "gemma2-q4k-dequant-f32a-nosubgroups",
28
+ "status": "legacy",
29
+ "statusReason": "compatibility-alias",
30
+ "notes": "Legacy alias for gemma2-q4k-dequant-f32a-nosubgroups."
24
31
  },
25
32
  {
26
33
  "id": "gemma2-f16-f16a",
@@ -50,6 +57,13 @@
50
57
  "statusReason": "benchmark-probe",
51
58
  "notes": "Experimental fused F32 pipeline variant for fast decode."
52
59
  },
60
+ {
61
+ "id": "gemma3-f16-fused-f32a-online-streamingprefill",
62
+ "file": "gemma3-f16-fused-f32a-online-streamingprefill.json",
63
+ "status": "experimental",
64
+ "statusReason": "stability-probe",
65
+ "notes": "Gemma 3 F16/F32 online path with streaming prefill attention instead of the small-tile prefill kernel."
66
+ },
53
67
  {
54
68
  "id": "gemma3-q4k-dequant-f16a-online",
55
69
  "file": "gemma3-q4k-dequant-f16a-online.json",
@@ -58,11 +72,18 @@
58
72
  "notes": "Experimental Gemma 3 Q4K path using online decode attention on subgroup-capable GPUs."
59
73
  },
60
74
  {
61
- "id": "gemma3-q4k-dequant-f32a",
62
- "file": "gemma3-q4k-dequant-f32a.json",
75
+ "id": "gemma3-q4k-dequant-f32a-nosubgroups",
76
+ "file": "gemma3-q4k-dequant-f32a-nosubgroups.json",
63
77
  "status": "canonical",
64
- "statusReason": "accuracy-first",
65
- "notes": "Gemma 3 Q4K dequant path with F32 activation for quality-first runs."
78
+ "statusReason": "subgroup-free",
79
+ "notes": "Subgroup-free Gemma 3 Q4K dequant path with F32 activations. Still requires shader-f16 kernels."
80
+ },
81
+ {
82
+ "id": "gemma3-q4k-dequant-f32a",
83
+ "aliasOf": "gemma3-q4k-dequant-f32a-nosubgroups",
84
+ "status": "legacy",
85
+ "statusReason": "compatibility-alias",
86
+ "notes": "Legacy alias for gemma3-q4k-dequant-f32a-nosubgroups."
66
87
  },
67
88
  {
68
89
  "id": "gemma3-q4k-dequant-f32a-online",
@@ -40,8 +40,8 @@
40
40
  "f32": "gemma2-f16-f32a"
41
41
  },
42
42
  "q4k": {
43
- "f16": "gemma2-q4k-dequant-f32a",
44
- "f32": "gemma2-q4k-dequant-f32a"
43
+ "f16": "gemma2-q4k-dequant-f32a-nosubgroups",
44
+ "f32": "gemma2-q4k-dequant-f32a-nosubgroups"
45
45
  }
46
46
  }
47
47
  },
@@ -6,7 +6,7 @@
6
6
  "owner": "doppler-core",
7
7
  "createdAtUtc": "2026-02-25T00:00:00Z",
8
8
  "extends": "modes/bench",
9
- "model": "gemma-3-1b-it-wq4k-ef16-hf16",
9
+ "model": "gemma-3-1b-it-q4k-ehf16-af32",
10
10
  "runtime": {
11
11
  "shared": {
12
12
  "benchmark": {
@@ -7,7 +7,7 @@
7
7
  "owner": "doppler-core",
8
8
  "createdAtUtc": "2026-02-25T00:00:00Z",
9
9
  "extends": "default",
10
- "model": "gemma-3-1b-it-wq4k-ef16-hf16",
10
+ "model": "gemma-3-1b-it-q4k-ehf16-af32",
11
11
  "runtime": {
12
12
  "shared": {
13
13
  "tooling": {
@@ -6,7 +6,7 @@
6
6
  "owner": "doppler-core",
7
7
  "createdAtUtc": "2026-02-25T00:00:00Z",
8
8
  "extends": "modes/bench",
9
- "model": "gemma-3-1b-it-wf16-ef16-hf16",
9
+ "model": "gemma-3-1b-it-f16-af32",
10
10
  "runtime": {
11
11
  "shared": {
12
12
  "tooling": {
@@ -1,20 +1,13 @@
1
1
  {
2
2
  "id": "kernels/dequant-f16-q4k",
3
3
  "name": "dequant-f16-q4k",
4
- "description": "Runtime alias preset for Gemma 2 Q4K dequant-to-F16 kernel path.",
4
+ "description": "Deprecated alias for kernels/gemma2-q4k-dequant-f16a.",
5
5
  "intent": "investigate",
6
- "stability": "canonical",
6
+ "stability": "deprecated",
7
7
  "owner": "doppler-core",
8
8
  "createdAtUtc": "2026-02-25T00:00:00Z",
9
- "extends": "default",
10
- "runtime": {
11
- "inference": {
12
- "kernelPath": "gemma2-q4k-dequant-f16a",
13
- "kernelPathPolicy": {
14
- "mode": "capability-aware",
15
- "sourceScope": ["config", "model", "manifest", "execution-v0"],
16
- "onIncompatible": "remap"
17
- }
18
- }
19
- }
9
+ "deprecatedAtUtc": "2026-03-08T00:00:00Z",
10
+ "replacementId": "kernels/gemma2-q4k-dequant-f16a",
11
+ "extends": "kernels/gemma2-q4k-dequant-f16a",
12
+ "runtime": {}
20
13
  }