@simulatte/doppler 0.1.4 → 0.1.6

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 (199) hide show
  1. package/README.md +26 -10
  2. package/package.json +30 -6
  3. package/src/client/doppler-api.browser.d.ts +1 -0
  4. package/src/client/doppler-api.browser.js +288 -0
  5. package/src/client/doppler-api.js +1 -1
  6. package/src/client/doppler-provider/types.js +1 -1
  7. package/src/config/execution-contract-check.d.ts +33 -0
  8. package/src/config/execution-contract-check.js +72 -0
  9. package/src/config/execution-v0-contract-check.d.ts +94 -0
  10. package/src/config/execution-v0-contract-check.js +251 -0
  11. package/src/config/execution-v0-graph-contract-check.d.ts +20 -0
  12. package/src/config/execution-v0-graph-contract-check.js +64 -0
  13. package/src/config/kernel-path-contract-check.d.ts +76 -0
  14. package/src/config/kernel-path-contract-check.js +479 -0
  15. package/src/config/kernel-path-loader.d.ts +16 -0
  16. package/src/config/kernel-path-loader.js +54 -0
  17. package/src/config/kernels/kernel-ref-digests.js +39 -27
  18. package/src/config/kernels/registry.json +598 -2
  19. package/src/config/loader.js +81 -48
  20. package/src/config/merge-contract-check.d.ts +16 -0
  21. package/src/config/merge-contract-check.js +321 -0
  22. package/src/config/merge-helpers.d.ts +58 -0
  23. package/src/config/merge-helpers.js +54 -0
  24. package/src/config/merge.js +21 -6
  25. package/src/config/presets/models/janus-text.json +2 -0
  26. package/src/config/presets/models/qwen3.json +9 -2
  27. package/src/config/presets/models/transformer.json +5 -0
  28. package/src/config/quantization-contract-check.d.ts +12 -0
  29. package/src/config/quantization-contract-check.js +91 -0
  30. package/src/config/required-inference-fields-contract-check.d.ts +24 -0
  31. package/src/config/required-inference-fields-contract-check.js +237 -0
  32. package/src/config/schema/browser-suite-metrics.schema.d.ts +17 -0
  33. package/src/config/schema/browser-suite-metrics.schema.js +46 -0
  34. package/src/config/schema/conversion-report.schema.d.ts +40 -0
  35. package/src/config/schema/conversion-report.schema.js +108 -0
  36. package/src/config/schema/doppler.schema.js +12 -18
  37. package/src/config/schema/index.d.ts +22 -0
  38. package/src/config/schema/index.js +18 -0
  39. package/src/config/schema/inference-defaults.schema.js +3 -0
  40. package/src/config/schema/inference.schema.d.ts +9 -0
  41. package/src/config/schema/kernel-path.schema.d.ts +6 -0
  42. package/src/config/schema/manifest.schema.d.ts +6 -0
  43. package/src/config/schema/manifest.schema.js +3 -0
  44. package/src/converter/core.d.ts +10 -0
  45. package/src/converter/core.js +27 -2
  46. package/src/converter/parsers/diffusion.js +63 -3
  47. package/src/converter/rope-config.js +42 -0
  48. package/src/gpu/device.js +58 -0
  49. package/src/gpu/kernels/attention.js +98 -0
  50. package/src/gpu/kernels/bias_add.wgsl +8 -6
  51. package/src/gpu/kernels/bias_add_f16.wgsl +8 -5
  52. package/src/gpu/kernels/conv2d.js +1 -1
  53. package/src/gpu/kernels/conv2d.wgsl +7 -8
  54. package/src/gpu/kernels/conv2d_f16.wgsl +7 -8
  55. package/src/gpu/kernels/depthwise_conv2d.d.ts +29 -0
  56. package/src/gpu/kernels/depthwise_conv2d.js +99 -0
  57. package/src/gpu/kernels/depthwise_conv2d.wgsl +55 -0
  58. package/src/gpu/kernels/depthwise_conv2d_f16.wgsl +59 -0
  59. package/src/gpu/kernels/grouped_pointwise_conv2d.d.ts +27 -0
  60. package/src/gpu/kernels/grouped_pointwise_conv2d.js +93 -0
  61. package/src/gpu/kernels/grouped_pointwise_conv2d.wgsl +44 -0
  62. package/src/gpu/kernels/grouped_pointwise_conv2d_f16.wgsl +48 -0
  63. package/src/gpu/kernels/index.d.ts +30 -0
  64. package/src/gpu/kernels/index.js +25 -0
  65. package/src/gpu/kernels/matmul.js +25 -0
  66. package/src/gpu/kernels/pixel_shuffle.js +1 -1
  67. package/src/gpu/kernels/pixel_shuffle.wgsl +4 -5
  68. package/src/gpu/kernels/pixel_shuffle_f16.wgsl +4 -5
  69. package/src/gpu/kernels/relu.d.ts +18 -0
  70. package/src/gpu/kernels/relu.js +58 -0
  71. package/src/gpu/kernels/relu.wgsl +22 -0
  72. package/src/gpu/kernels/relu_f16.wgsl +24 -0
  73. package/src/gpu/kernels/repeat_channels.d.ts +21 -0
  74. package/src/gpu/kernels/repeat_channels.js +60 -0
  75. package/src/gpu/kernels/repeat_channels.wgsl +28 -0
  76. package/src/gpu/kernels/repeat_channels_f16.wgsl +30 -0
  77. package/src/gpu/kernels/residual.js +44 -8
  78. package/src/gpu/kernels/residual.wgsl +6 -3
  79. package/src/gpu/kernels/residual_f16.wgsl +2 -1
  80. package/src/gpu/kernels/residual_f16_vec4.wgsl +2 -1
  81. package/src/gpu/kernels/residual_vec4.wgsl +2 -1
  82. package/src/gpu/kernels/rmsnorm.js +58 -6
  83. package/src/gpu/kernels/rmsnorm.wgsl +14 -6
  84. package/src/gpu/kernels/rmsnorm_f16.wgsl +10 -2
  85. package/src/gpu/kernels/rope.d.ts +2 -0
  86. package/src/gpu/kernels/rope.js +11 -1
  87. package/src/gpu/kernels/rope.wgsl +56 -40
  88. package/src/gpu/kernels/sana_linear_attention.d.ts +27 -0
  89. package/src/gpu/kernels/sana_linear_attention.js +121 -0
  90. package/src/gpu/kernels/sana_linear_attention_apply.wgsl +43 -0
  91. package/src/gpu/kernels/sana_linear_attention_apply_f16.wgsl +46 -0
  92. package/src/gpu/kernels/sana_linear_attention_summary.wgsl +51 -0
  93. package/src/gpu/kernels/sana_linear_attention_summary_f16.wgsl +53 -0
  94. package/src/gpu/kernels/silu.d.ts +1 -0
  95. package/src/gpu/kernels/silu.js +32 -14
  96. package/src/gpu/kernels/silu.wgsl +19 -9
  97. package/src/gpu/kernels/silu_f16.wgsl +19 -9
  98. package/src/gpu/kernels/transpose.js +15 -2
  99. package/src/gpu/kernels/transpose.wgsl +5 -6
  100. package/src/gpu/kernels/upsample2d.js +2 -1
  101. package/src/gpu/kernels/upsample2d.wgsl +6 -9
  102. package/src/gpu/kernels/upsample2d_f16.wgsl +6 -9
  103. package/src/gpu/kernels/utils.js +16 -1
  104. package/src/index-browser.d.ts +1 -1
  105. package/src/index-browser.js +2 -2
  106. package/src/index.js +1 -1
  107. package/src/inference/browser-harness.js +109 -23
  108. package/src/inference/pipelines/diffusion/init.js +14 -0
  109. package/src/inference/pipelines/diffusion/pipeline.js +215 -77
  110. package/src/inference/pipelines/diffusion/sana-transformer.d.ts +53 -0
  111. package/src/inference/pipelines/diffusion/sana-transformer.js +738 -0
  112. package/src/inference/pipelines/diffusion/scheduler.d.ts +17 -1
  113. package/src/inference/pipelines/diffusion/scheduler.js +91 -3
  114. package/src/inference/pipelines/diffusion/text-encoder-gpu.d.ts +11 -4
  115. package/src/inference/pipelines/diffusion/text-encoder-gpu.js +282 -0
  116. package/src/inference/pipelines/diffusion/text-encoder.js +18 -1
  117. package/src/inference/pipelines/diffusion/types.d.ts +4 -0
  118. package/src/inference/pipelines/diffusion/vae.js +782 -78
  119. package/src/inference/pipelines/text/attention/record.js +11 -2
  120. package/src/inference/pipelines/text/attention/run.js +11 -2
  121. package/src/inference/pipelines/text/chat-format.js +25 -1
  122. package/src/inference/pipelines/text/config.d.ts +9 -0
  123. package/src/inference/pipelines/text/config.js +69 -2
  124. package/src/inference/pipelines/text/execution-plan.js +23 -31
  125. package/src/inference/pipelines/text/execution-v0.js +43 -95
  126. package/src/inference/pipelines/text/ffn/standard.js +3 -0
  127. package/src/inference/pipelines/text/init.d.ts +4 -0
  128. package/src/inference/pipelines/text/init.js +56 -9
  129. package/src/inference/pipelines/text/layer.js +11 -0
  130. package/src/inference/pipelines/text.js +4 -0
  131. package/src/inference/tokenizers/bundled.js +156 -33
  132. package/src/rules/execution-rules-contract-check.d.ts +17 -0
  133. package/src/rules/execution-rules-contract-check.js +245 -0
  134. package/src/rules/kernels/depthwise-conv2d.rules.json +6 -0
  135. package/src/rules/kernels/grouped-pointwise-conv2d.rules.json +6 -0
  136. package/src/rules/kernels/relu.rules.json +6 -0
  137. package/src/rules/kernels/repeat-channels.rules.json +6 -0
  138. package/src/rules/kernels/sana-linear-attention.rules.json +6 -0
  139. package/src/rules/layer-pattern-contract-check.d.ts +17 -0
  140. package/src/rules/layer-pattern-contract-check.js +231 -0
  141. package/src/rules/rule-registry.d.ts +28 -0
  142. package/src/rules/rule-registry.js +38 -0
  143. package/src/rules/tooling/command-runtime.rules.json +18 -0
  144. package/src/tooling/command-api.d.ts +27 -1
  145. package/src/tooling/command-api.js +142 -3
  146. package/src/tooling/conversion-config-materializer.d.ts +24 -0
  147. package/src/tooling/conversion-config-materializer.js +99 -0
  148. package/src/tooling/lean-execution-contract-runner.d.ts +43 -0
  149. package/src/tooling/lean-execution-contract-runner.js +158 -0
  150. package/src/tooling/node-browser-command-runner.d.ts +4 -0
  151. package/src/tooling/node-browser-command-runner.js +58 -3
  152. package/src/tooling/node-command-runner.js +15 -0
  153. package/src/tooling/node-convert.d.ts +10 -0
  154. package/src/tooling/node-converter.js +59 -0
  155. package/src/tooling/node-webgpu.js +11 -89
  156. package/src/training/checkpoint-watch.d.ts +7 -0
  157. package/src/training/checkpoint-watch.js +106 -0
  158. package/src/training/checkpoint.d.ts +6 -1
  159. package/src/training/checkpoint.js +12 -2
  160. package/src/training/distillation/artifacts.d.ts +71 -0
  161. package/src/training/distillation/artifacts.js +132 -0
  162. package/src/training/distillation/checkpoint-watch.d.ts +10 -0
  163. package/src/training/distillation/checkpoint-watch.js +57 -0
  164. package/src/training/distillation/dataset.d.ts +59 -0
  165. package/src/training/distillation/dataset.js +337 -0
  166. package/src/training/distillation/eval.d.ts +34 -0
  167. package/src/training/distillation/eval.js +310 -0
  168. package/src/training/distillation/index.d.ts +29 -0
  169. package/src/training/distillation/index.js +29 -0
  170. package/src/training/distillation/runtime.d.ts +20 -0
  171. package/src/training/distillation/runtime.js +121 -0
  172. package/src/training/distillation/scoreboard.d.ts +6 -0
  173. package/src/training/distillation/scoreboard.js +8 -0
  174. package/src/training/distillation/stage-a.d.ts +45 -0
  175. package/src/training/distillation/stage-a.js +338 -0
  176. package/src/training/distillation/stage-b.d.ts +24 -0
  177. package/src/training/distillation/stage-b.js +20 -0
  178. package/src/training/index.d.ts +10 -0
  179. package/src/training/index.js +10 -0
  180. package/src/training/lora-pipeline.d.ts +40 -0
  181. package/src/training/lora-pipeline.js +796 -0
  182. package/src/training/operator-artifacts.d.ts +62 -0
  183. package/src/training/operator-artifacts.js +140 -0
  184. package/src/training/operator-command.d.ts +5 -0
  185. package/src/training/operator-command.js +453 -0
  186. package/src/training/operator-eval.d.ts +48 -0
  187. package/src/training/operator-eval.js +230 -0
  188. package/src/training/operator-scoreboard.d.ts +5 -0
  189. package/src/training/operator-scoreboard.js +44 -0
  190. package/src/training/runner.d.ts +52 -0
  191. package/src/training/runner.js +29 -4
  192. package/src/training/suite.d.ts +112 -0
  193. package/src/training/suite.js +9 -9
  194. package/src/training/workloads.d.ts +164 -0
  195. package/src/training/workloads.js +539 -0
  196. package/src/version.d.ts +2 -0
  197. package/src/version.js +2 -0
  198. package/tools/convert-safetensors-node.js +47 -0
  199. package/tools/doppler-cli.js +252 -41
@@ -5,6 +5,7 @@ import {
5
5
  } from './schema/index.js';
6
6
  import { createDopplerError, ERROR_CODES } from '../errors/index.js';
7
7
  import { loadJson } from '../utils/load-json.js';
8
+ import { chooseNullish, mergeLayeredShallowObjects } from './merge-helpers.js';
8
9
 
9
10
  // Static imports keep presets bundled for browser use.
10
11
  const transformerPreset = await loadJson('./presets/models/transformer.json', import.meta.url, 'Failed to load preset');
@@ -190,18 +191,21 @@ export function resolveConfig(
190
191
  // Note: Uses nullish coalesce (??) so null values fall through to next level.
191
192
  // This means explicit null in manifest = "use preset/default".
192
193
  const presetArch = preset.architecture || {};
193
- const numLayers = manifestArch.numLayers ?? presetArch.numLayers;
194
- const hiddenSize = manifestArch.hiddenSize ?? presetArch.hiddenSize;
195
- const intermediateSize = manifestArch.intermediateSize ?? presetArch.intermediateSize;
196
- const numAttentionHeads = manifestArch.numAttentionHeads ?? presetArch.numAttentionHeads;
197
- const numKeyValueHeads = manifestArch.numKeyValueHeads ?? presetArch.numKeyValueHeads ?? numAttentionHeads;
198
- const headDim = manifestArch.headDim ?? presetArch.headDim ?? (
199
- hiddenSize && numAttentionHeads ? hiddenSize / numAttentionHeads : undefined
194
+ const numLayers = chooseNullish(manifestArch.numLayers, presetArch.numLayers);
195
+ const hiddenSize = chooseNullish(manifestArch.hiddenSize, presetArch.hiddenSize);
196
+ const intermediateSize = chooseNullish(manifestArch.intermediateSize, presetArch.intermediateSize);
197
+ const numAttentionHeads = chooseNullish(manifestArch.numAttentionHeads, presetArch.numAttentionHeads);
198
+ const numKeyValueHeads = chooseNullish(
199
+ manifestArch.numKeyValueHeads,
200
+ chooseNullish(presetArch.numKeyValueHeads, numAttentionHeads)
200
201
  );
201
- const vocabSize = manifestArch.vocabSize ?? presetArch.vocabSize;
202
- const maxSeqLen = manifestArch.maxSeqLen ?? presetArch.maxSeqLen;
203
- const ropeTheta = manifestArch.ropeTheta ?? presetArch.ropeTheta;
204
- const rmsNormEps = manifestArch.rmsNormEps ?? presetArch.rmsNormEps;
202
+ const headDim = chooseNullish(manifestArch.headDim, chooseNullish(presetArch.headDim, (
203
+ hiddenSize && numAttentionHeads ? hiddenSize / numAttentionHeads : undefined
204
+ )));
205
+ const vocabSize = chooseNullish(manifestArch.vocabSize, presetArch.vocabSize);
206
+ const maxSeqLen = chooseNullish(manifestArch.maxSeqLen, presetArch.maxSeqLen);
207
+ const ropeTheta = chooseNullish(manifestArch.ropeTheta, presetArch.ropeTheta);
208
+ const rmsNormEps = chooseNullish(manifestArch.rmsNormEps, presetArch.rmsNormEps);
205
209
 
206
210
  const architecture = {
207
211
  numLayers,
@@ -226,44 +230,44 @@ export function resolveConfig(
226
230
  const manifestInference = extractInferenceFromConfig(manifest.config || {});
227
231
 
228
232
  const inference = {
229
- attention: {
230
- ...baseInference.attention,
231
- ...presetInference.attention,
232
- ...manifestInference.attention,
233
- },
234
- normalization: {
235
- ...baseInference.normalization,
236
- ...presetInference.normalization,
237
- },
238
- ffn: {
239
- ...baseInference.ffn,
240
- ...presetInference.ffn,
241
- },
242
- moe: presetInference.moe ?? baseInference.moe ?? null,
243
- output: {
244
- ...baseInference.output,
245
- ...presetInference.output,
246
- ...manifestInference.output,
247
- },
248
- layerPattern: presetInference.layerPattern ?? baseInference.layerPattern,
249
- rope: {
250
- ...baseInference.rope,
251
- ...presetInference.rope,
252
- ...manifestInference.rope,
253
- },
254
- pipeline: manifestInference.pipeline ?? presetInference.pipeline ?? baseInference.pipeline,
255
- chatTemplate: {
256
- ...baseInference.chatTemplate,
257
- ...presetInference.chatTemplate,
258
- },
259
- kernelPath: presetInference.kernelPath ?? baseInference.kernelPath,
233
+ attention: mergeLayeredShallowObjects(
234
+ baseInference.attention,
235
+ presetInference.attention,
236
+ manifestInference.attention
237
+ ),
238
+ normalization: mergeLayeredShallowObjects(
239
+ baseInference.normalization,
240
+ presetInference.normalization
241
+ ),
242
+ ffn: mergeLayeredShallowObjects(
243
+ baseInference.ffn,
244
+ presetInference.ffn
245
+ ),
246
+ moe: chooseNullish(presetInference.moe, chooseNullish(baseInference.moe, null)),
247
+ output: mergeLayeredShallowObjects(
248
+ baseInference.output,
249
+ presetInference.output,
250
+ manifestInference.output
251
+ ),
252
+ layerPattern: chooseNullish(presetInference.layerPattern, baseInference.layerPattern),
253
+ rope: mergeLayeredShallowObjects(
254
+ baseInference.rope,
255
+ presetInference.rope,
256
+ manifestInference.rope
257
+ ),
258
+ pipeline: chooseNullish(manifestInference.pipeline, chooseNullish(presetInference.pipeline, baseInference.pipeline)),
259
+ chatTemplate: mergeLayeredShallowObjects(
260
+ baseInference.chatTemplate,
261
+ presetInference.chatTemplate
262
+ ),
263
+ kernelPath: chooseNullish(presetInference.kernelPath, baseInference.kernelPath),
260
264
  };
261
265
 
262
266
  // Merge tokenizer config
263
- const tokenizer = {
264
- ...preset.tokenizer,
265
- ...extractTokenizerFromManifest(manifest),
266
- };
267
+ const tokenizer = mergeLayeredShallowObjects(
268
+ preset.tokenizer,
269
+ extractTokenizerFromManifest(manifest)
270
+ );
267
271
 
268
272
  // Sampling defaults
269
273
  const sampling = preset.sampling ?? {
@@ -322,6 +326,8 @@ function assertArchitecture(manifest, architecture) {
322
326
 
323
327
  function extractArchitectureFromConfig(config) {
324
328
  const nestedTextConfig = getNestedTextConfig(config);
329
+ const topLevelRoPEParameters = getFlatRoPEParameters(config);
330
+ const nestedRoPEParameters = getFlatRoPEParameters(nestedTextConfig);
325
331
  return {
326
332
  numLayers: config.num_hidden_layers ?? nestedTextConfig?.num_hidden_layers ?? config.n_layer ?? config.blockCount,
327
333
  hiddenSize: config.hidden_size ?? nestedTextConfig?.hidden_size ?? config.n_embd ?? config.embeddingLength,
@@ -331,13 +337,20 @@ function extractArchitectureFromConfig(config) {
331
337
  headDim: config.head_dim ?? nestedTextConfig?.head_dim,
332
338
  vocabSize: config.vocab_size ?? nestedTextConfig?.vocab_size ?? config.vocabSize,
333
339
  maxSeqLen: config.max_position_embeddings ?? nestedTextConfig?.max_position_embeddings ?? config.n_positions ?? config.contextLength,
334
- ropeTheta: config.rope_theta ?? nestedTextConfig?.rope_theta ?? config.ropeFreqBase,
340
+ ropeTheta: topLevelRoPEParameters?.rope_theta
341
+ ?? nestedRoPEParameters?.rope_theta
342
+ ?? config.rope_theta
343
+ ?? nestedTextConfig?.rope_theta
344
+ ?? config.ropeFreqBase,
335
345
  rmsNormEps: config.rms_norm_eps ?? nestedTextConfig?.rms_norm_eps ?? config.attentionLayerNormRMSEpsilon,
336
346
  };
337
347
  }
338
348
 
339
349
  function extractInferenceFromConfig(config) {
340
350
  const nestedTextConfig = getNestedTextConfig(config);
351
+ const topLevelRoPEParameters = getFlatRoPEParameters(config);
352
+ const nestedRoPEParameters = getFlatRoPEParameters(nestedTextConfig);
353
+ const ropeParameters = nestedRoPEParameters ?? topLevelRoPEParameters;
341
354
  return {
342
355
  attention: {
343
356
  slidingWindow: config.sliding_window ?? nestedTextConfig?.sliding_window,
@@ -351,7 +364,13 @@ function extractInferenceFromConfig(config) {
351
364
  },
352
365
  pipeline: config.pipeline ?? nestedTextConfig?.pipeline,
353
366
  rope: {
354
- ropeTheta: config.rope_theta ?? nestedTextConfig?.rope_theta ?? config.ropeFreqBase,
367
+ ropeTheta: ropeParameters?.rope_theta
368
+ ?? config.rope_theta
369
+ ?? nestedTextConfig?.rope_theta
370
+ ?? config.ropeFreqBase,
371
+ mropeInterleaved: ropeParameters?.mrope_interleaved,
372
+ mropeSection: Array.isArray(ropeParameters?.mrope_section) ? ropeParameters.mrope_section : undefined,
373
+ partialRotaryFactor: ropeParameters?.partial_rotary_factor,
355
374
  ropeScalingType: config.rope_scaling_type ?? nestedTextConfig?.rope_scaling_type,
356
375
  ropeScalingFactor: config.rope_scaling_factor ?? nestedTextConfig?.rope_scaling_factor,
357
376
  },
@@ -371,6 +390,20 @@ function getNestedTextConfig(config) {
371
390
  return null;
372
391
  }
373
392
 
393
+ function getFlatRoPEParameters(config) {
394
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
395
+ return null;
396
+ }
397
+ const ropeParameters = config.rope_parameters;
398
+ if (!ropeParameters || typeof ropeParameters !== 'object' || Array.isArray(ropeParameters)) {
399
+ return null;
400
+ }
401
+ if (ropeParameters.full_attention || ropeParameters.sliding_attention) {
402
+ return null;
403
+ }
404
+ return ropeParameters;
405
+ }
406
+
374
407
  function extractTokenizerFromManifest(manifest) {
375
408
  if (!manifest.tokenizer) return {};
376
409
 
@@ -0,0 +1,16 @@
1
+ export interface MergeContractCheckResult {
2
+ id: string;
3
+ ok: boolean;
4
+ detail: string;
5
+ mode: 'actual' | 'modeled';
6
+ }
7
+
8
+ export interface MergeContractArtifact {
9
+ schemaVersion: 1;
10
+ source: 'doppler';
11
+ ok: boolean;
12
+ checks: MergeContractCheckResult[];
13
+ errors: string[];
14
+ }
15
+
16
+ export declare function buildMergeContractArtifact(): MergeContractArtifact;
@@ -0,0 +1,321 @@
1
+ import { resolveConfig, resolvePreset } from './loader.js';
2
+ import {
3
+ chooseNullish,
4
+ chooseDefinedWithSource,
5
+ mergeExecutionPatchLists,
6
+ mergeKernelPathPolicy,
7
+ mergeLayeredShallowObjects,
8
+ mergeShallowObject,
9
+ replaceSubtree,
10
+ } from './merge-helpers.js';
11
+ import { mergeConfig } from './merge.js';
12
+ import { createDopplerConfig } from './schema/index.js';
13
+
14
+ function buildWitnessManifestForArchitecture() {
15
+ return {
16
+ modelId: 'merge-semantics-witness',
17
+ modelType: 'transformer',
18
+ architecture: {
19
+ numLayers: 2,
20
+ hiddenSize: 256,
21
+ intermediateSize: 512,
22
+ numAttentionHeads: 4,
23
+ numKeyValueHeads: 4,
24
+ headDim: 64,
25
+ vocabSize: 1024,
26
+ maxSeqLen: 2048,
27
+ ropeTheta: null,
28
+ rmsNormEps: 1e-6,
29
+ },
30
+ config: {},
31
+ };
32
+ }
33
+
34
+ function buildWitnessMergeManifest() {
35
+ return {
36
+ modelId: 'merge-overlay-witness',
37
+ inference: {
38
+ attention: {
39
+ queryPreAttnScalar: 1,
40
+ attentionBias: false,
41
+ attnLogitSoftcapping: null,
42
+ slidingWindow: 4096,
43
+ queryKeyNorm: false,
44
+ attentionOutputGate: false,
45
+ causal: true,
46
+ },
47
+ normalization: {
48
+ rmsNormEps: 1e-6,
49
+ rmsNormWeightOffset: 0,
50
+ postAttentionNorm: true,
51
+ preFeedforwardNorm: true,
52
+ postFeedforwardNorm: false,
53
+ },
54
+ ffn: {
55
+ activation: 'gelu',
56
+ gatedActivation: false,
57
+ swigluLimit: null,
58
+ },
59
+ rope: {
60
+ ropeTheta: 1000000,
61
+ ropeLocalTheta: null,
62
+ ropeScalingType: null,
63
+ ropeScalingFactor: null,
64
+ ropeLocalScalingType: null,
65
+ ropeLocalScalingFactor: null,
66
+ yarnBetaFast: null,
67
+ yarnBetaSlow: null,
68
+ yarnOriginalMaxPos: null,
69
+ ropeLocalYarnBetaFast: null,
70
+ ropeLocalYarnBetaSlow: null,
71
+ ropeLocalYarnOriginalMaxPos: null,
72
+ },
73
+ output: {
74
+ finalLogitSoftcapping: 30,
75
+ tieWordEmbeddings: true,
76
+ scaleEmbeddings: false,
77
+ embeddingTranspose: false,
78
+ embeddingVocabSize: 1024,
79
+ },
80
+ layerPattern: null,
81
+ chatTemplate: {
82
+ type: 'gemma',
83
+ enabled: true,
84
+ },
85
+ defaultKernelPath: 'gemma3-f16-fused-f16a-online',
86
+ },
87
+ architecture: {
88
+ headDim: 64,
89
+ maxSeqLen: 2048,
90
+ },
91
+ };
92
+ }
93
+
94
+ function recordCheck(results, id, ok, detail, mode = 'actual') {
95
+ results.push({ id, ok, detail, mode });
96
+ }
97
+
98
+ export function buildMergeContractArtifact() {
99
+ const checks = [];
100
+ const preset = resolvePreset('gemma3');
101
+ const resolved = resolveConfig(buildWitnessManifestForArchitecture(), 'gemma3');
102
+ recordCheck(
103
+ checks,
104
+ 'loader.architecture.nullish_null_falls_through',
105
+ resolved.architecture.ropeTheta === preset.architecture.ropeTheta,
106
+ `resolved ropeTheta=${resolved.architecture.ropeTheta}, preset ropeTheta=${preset.architecture.ropeTheta}`
107
+ );
108
+
109
+ const mergedUndefined = mergeConfig(buildWitnessMergeManifest(), {});
110
+ recordCheck(
111
+ checks,
112
+ 'runtime.mergeConfig.defined_overlay_missing_falls_through',
113
+ mergedUndefined.inference.defaultKernelPath === 'gemma3-f16-fused-f16a-online'
114
+ && mergedUndefined._sources.get('inference.defaultKernelPath') === 'manifest',
115
+ `value=${mergedUndefined.inference.defaultKernelPath}, source=${mergedUndefined._sources.get('inference.defaultKernelPath')}`
116
+ );
117
+
118
+ const mergedNull = mergeConfig(buildWitnessMergeManifest(), {
119
+ defaultKernelPath: null,
120
+ chatTemplate: {
121
+ enabled: null,
122
+ },
123
+ });
124
+ recordCheck(
125
+ checks,
126
+ 'runtime.mergeConfig.defined_overlay_preserves_null',
127
+ mergedNull.inference.defaultKernelPath === null
128
+ && mergedNull._sources.get('inference.defaultKernelPath') === 'runtime',
129
+ `value=${mergedNull.inference.defaultKernelPath}, source=${mergedNull._sources.get('inference.defaultKernelPath')}`
130
+ );
131
+ recordCheck(
132
+ checks,
133
+ 'runtime.inference.chatTemplate.spread_preserves_null',
134
+ mergedNull.inference.chatTemplate.enabled === null
135
+ && mergedNull._sources.get('inference.chatTemplate.enabled') === 'runtime',
136
+ `value=${String(mergedNull.inference.chatTemplate.enabled)}`
137
+ );
138
+
139
+ const runtimeConfig = createDopplerConfig({
140
+ runtime: {
141
+ inference: {
142
+ chatTemplate: {
143
+ enabled: null,
144
+ },
145
+ },
146
+ },
147
+ });
148
+ recordCheck(
149
+ checks,
150
+ 'runtime.schema.chatTemplate.spread_preserves_null',
151
+ runtimeConfig.runtime.inference.chatTemplate.enabled === null,
152
+ `value=${String(runtimeConfig.runtime.inference.chatTemplate.enabled)}`
153
+ );
154
+
155
+ const overlaySources = new Map();
156
+ const chosenRuntimeValue = chooseDefinedWithSource(
157
+ 'inference.defaultKernelPath',
158
+ null,
159
+ 'manifest-path',
160
+ overlaySources
161
+ );
162
+ recordCheck(
163
+ checks,
164
+ 'runtime.mergeHelpers.chooseDefinedWithSource.runtime_marks_source',
165
+ chosenRuntimeValue === null && overlaySources.get('inference.defaultKernelPath') === 'runtime',
166
+ `value=${String(chosenRuntimeValue)}, source=${overlaySources.get('inference.defaultKernelPath')}`,
167
+ 'actual'
168
+ );
169
+
170
+ const manifestSources = new Map();
171
+ const chosenManifestValue = chooseDefinedWithSource(
172
+ 'inference.defaultKernelPath',
173
+ undefined,
174
+ 'manifest-path',
175
+ manifestSources
176
+ );
177
+ recordCheck(
178
+ checks,
179
+ 'runtime.mergeHelpers.chooseDefinedWithSource.manifest_marks_source',
180
+ chosenManifestValue === 'manifest-path' && manifestSources.get('inference.defaultKernelPath') === 'manifest',
181
+ `value=${String(chosenManifestValue)}, source=${manifestSources.get('inference.defaultKernelPath')}`,
182
+ 'actual'
183
+ );
184
+
185
+ const executionPatchBase = {
186
+ set: [{ op: 'seed' }],
187
+ remove: ['old_step'],
188
+ add: [{ id: 'new_step' }],
189
+ };
190
+ const executionPatchOverride = {
191
+ set: null,
192
+ remove: [],
193
+ add: undefined,
194
+ };
195
+ const mergedExecutionPatch = {
196
+ ...mergeExecutionPatchLists(executionPatchBase, executionPatchOverride),
197
+ };
198
+ recordCheck(
199
+ checks,
200
+ 'runtime.inference.executionPatch.nullish_null_falls_through',
201
+ mergedExecutionPatch.set === executionPatchBase.set
202
+ && mergedExecutionPatch.add === executionPatchBase.add
203
+ && Array.isArray(mergedExecutionPatch.remove)
204
+ && mergedExecutionPatch.remove.length === 0,
205
+ `setLength=${mergedExecutionPatch.set.length}, removeLength=${mergedExecutionPatch.remove.length}, addLength=${mergedExecutionPatch.add.length}`,
206
+ 'actual'
207
+ );
208
+
209
+ const sessionBase = {
210
+ kvcache: {
211
+ layout: 'paged',
212
+ maxSeqLen: 8192,
213
+ kvDtype: 'f16',
214
+ },
215
+ decodeLoop: {
216
+ batchSize: 16,
217
+ disableCommandBatching: false,
218
+ },
219
+ };
220
+ const sessionOverride = {
221
+ kvcache: {
222
+ layout: 'tiered',
223
+ },
224
+ decodeLoop: {
225
+ batchSize: 1,
226
+ },
227
+ };
228
+ const mergedSession = {
229
+ kvcache: replaceSubtree(sessionOverride.kvcache, sessionBase.kvcache),
230
+ decodeLoop: replaceSubtree(sessionOverride.decodeLoop, sessionBase.decodeLoop),
231
+ };
232
+ recordCheck(
233
+ checks,
234
+ 'runtime.inference.session.subtree_override_replaces_base',
235
+ mergedSession.kvcache.layout === 'tiered'
236
+ && mergedSession.kvcache.maxSeqLen === undefined
237
+ && mergedSession.decodeLoop.batchSize === 1
238
+ && mergedSession.decodeLoop.disableCommandBatching === undefined,
239
+ `kvcacheKeys=${Object.keys(mergedSession.kvcache).join(',')}; decodeLoopKeys=${Object.keys(mergedSession.decodeLoop).join(',')}`,
240
+ 'actual'
241
+ );
242
+
243
+ const mergedChatTemplate = mergeShallowObject(
244
+ { type: 'base', enabled: true },
245
+ { enabled: null }
246
+ );
247
+ recordCheck(
248
+ checks,
249
+ 'runtime.mergeShallowObject.spread_preserves_null',
250
+ mergedChatTemplate.enabled === null && mergedChatTemplate.type === 'base',
251
+ `type=${mergedChatTemplate.type}, enabled=${String(mergedChatTemplate.enabled)}`,
252
+ 'actual'
253
+ );
254
+
255
+ const layeredAttention = mergeLayeredShallowObjects(
256
+ { slidingWindow: 4096, attentionBias: false },
257
+ { slidingWindow: 2048 },
258
+ { slidingWindow: null }
259
+ );
260
+ recordCheck(
261
+ checks,
262
+ 'loader.mergeLayeredShallowObjects.spread_preserves_null',
263
+ layeredAttention.slidingWindow === null && layeredAttention.attentionBias === false,
264
+ `slidingWindow=${String(layeredAttention.slidingWindow)}, attentionBias=${String(layeredAttention.attentionBias)}`,
265
+ 'actual'
266
+ );
267
+
268
+ const mergedKernelPathPolicy = mergeKernelPathPolicy(
269
+ {
270
+ mode: 'locked',
271
+ sourceScope: ['model', 'manifest'],
272
+ allowSources: ['model', 'manifest'],
273
+ onIncompatible: 'error',
274
+ },
275
+ {
276
+ allowSources: ['runtime', 'execution-v0'],
277
+ onIncompatible: 'remap',
278
+ }
279
+ );
280
+ recordCheck(
281
+ checks,
282
+ 'runtime.kernelPathPolicy.source_scope_mirrors_allow_sources',
283
+ Array.isArray(mergedKernelPathPolicy.sourceScope)
284
+ && Array.isArray(mergedKernelPathPolicy.allowSources)
285
+ && mergedKernelPathPolicy.sourceScope.length === 2
286
+ && mergedKernelPathPolicy.sourceScope[0] === 'runtime'
287
+ && mergedKernelPathPolicy.allowSources[1] === 'execution-v0'
288
+ && mergedKernelPathPolicy.onIncompatible === 'remap',
289
+ `sourceScope=${JSON.stringify(mergedKernelPathPolicy.sourceScope)}, allowSources=${JSON.stringify(mergedKernelPathPolicy.allowSources)}`,
290
+ 'actual'
291
+ );
292
+
293
+ const runtimeConfigWithKernelPathPolicy = createDopplerConfig({
294
+ runtime: {
295
+ inference: {
296
+ kernelPathPolicy: {
297
+ allowSources: ['runtime', 'execution-v0'],
298
+ },
299
+ },
300
+ },
301
+ });
302
+ recordCheck(
303
+ checks,
304
+ 'runtime.schema.kernelPathPolicy.helper_is_used',
305
+ Array.isArray(runtimeConfigWithKernelPathPolicy.runtime.inference.kernelPathPolicy.sourceScope)
306
+ && runtimeConfigWithKernelPathPolicy.runtime.inference.kernelPathPolicy.sourceScope[0] === 'runtime'
307
+ && runtimeConfigWithKernelPathPolicy.runtime.inference.kernelPathPolicy.allowSources[1] === 'execution-v0',
308
+ `policy=${JSON.stringify(runtimeConfigWithKernelPathPolicy.runtime.inference.kernelPathPolicy)}`,
309
+ 'actual'
310
+ );
311
+
312
+ const errors = checks.filter((entry) => !entry.ok).map((entry) => `[MergeContract] ${entry.id}: ${entry.detail}`);
313
+
314
+ return {
315
+ schemaVersion: 1,
316
+ source: 'doppler',
317
+ ok: errors.length === 0,
318
+ checks,
319
+ errors,
320
+ };
321
+ }
@@ -0,0 +1,58 @@
1
+ export declare function chooseNullish<T>(
2
+ overrideValue: T | null | undefined,
3
+ fallbackValue: T
4
+ ): T;
5
+
6
+ export declare function chooseDefined<T>(
7
+ overrideValue: T | undefined,
8
+ fallbackValue: T
9
+ ): T;
10
+
11
+ export declare function chooseDefinedWithSource<T>(
12
+ path: string,
13
+ overrideValue: T | undefined,
14
+ fallbackValue: T,
15
+ sources: Map<string, string> | null | undefined
16
+ ): T;
17
+
18
+ export declare function mergeShallowObject<T extends object>(
19
+ base: T,
20
+ override: Partial<T> | null | undefined
21
+ ): T;
22
+
23
+ export declare function mergeLayeredShallowObjects<T extends object>(
24
+ ...layers: Array<Partial<T> | null | undefined>
25
+ ): T;
26
+
27
+ export declare function replaceSubtree<T>(
28
+ overrideValue: T | null | undefined,
29
+ fallbackValue: T
30
+ ): T;
31
+
32
+ export declare function mergeKernelPathPolicy<T extends {
33
+ mode?: unknown;
34
+ sourceScope?: unknown;
35
+ allowSources?: unknown;
36
+ onIncompatible?: unknown;
37
+ }>(
38
+ basePolicy: T | null | undefined,
39
+ overridePolicy: T | null | undefined
40
+ ): {
41
+ mode: unknown;
42
+ sourceScope: unknown;
43
+ allowSources: unknown;
44
+ onIncompatible: unknown;
45
+ };
46
+
47
+ export declare function mergeExecutionPatchLists<T extends {
48
+ set?: unknown;
49
+ remove?: unknown;
50
+ add?: unknown;
51
+ }>(
52
+ basePatch: T | null | undefined,
53
+ overridePatch: T | null | undefined
54
+ ): {
55
+ set: unknown;
56
+ remove: unknown;
57
+ add: unknown;
58
+ };
@@ -0,0 +1,54 @@
1
+ export function chooseNullish(overrideValue, fallbackValue) {
2
+ return overrideValue ?? fallbackValue;
3
+ }
4
+
5
+ export function chooseDefined(overrideValue, fallbackValue) {
6
+ return overrideValue !== undefined ? overrideValue : fallbackValue;
7
+ }
8
+
9
+ export function chooseDefinedWithSource(path, overrideValue, fallbackValue, sources) {
10
+ const value = chooseDefined(overrideValue, fallbackValue);
11
+ if (sources && typeof sources.set === 'function') {
12
+ sources.set(path, overrideValue !== undefined ? 'runtime' : 'manifest');
13
+ }
14
+ return value;
15
+ }
16
+
17
+ export function mergeShallowObject(base, override) {
18
+ if (!override || typeof override !== 'object' || Array.isArray(override)) {
19
+ return base;
20
+ }
21
+ return { ...base, ...override };
22
+ }
23
+
24
+ export function mergeLayeredShallowObjects(...layers) {
25
+ return layers.reduce((merged, layer) => mergeShallowObject(merged, layer), {});
26
+ }
27
+
28
+ export function replaceSubtree(overrideValue, fallbackValue) {
29
+ return chooseNullish(overrideValue, fallbackValue);
30
+ }
31
+
32
+ export function mergeKernelPathPolicy(basePolicy, overridePolicy) {
33
+ const base = basePolicy ?? {};
34
+ const override = overridePolicy ?? {};
35
+ const baseSourceScope = base.sourceScope ?? base.allowSources;
36
+ const overrideSourceScope = override.sourceScope ?? override.allowSources;
37
+ const sourceScope = overrideSourceScope ?? baseSourceScope;
38
+ return {
39
+ mode: override.mode ?? base.mode,
40
+ sourceScope,
41
+ allowSources: sourceScope,
42
+ onIncompatible: override.onIncompatible ?? base.onIncompatible,
43
+ };
44
+ }
45
+
46
+ export function mergeExecutionPatchLists(basePatch, overridePatch) {
47
+ const base = basePatch ?? {};
48
+ const override = overridePatch ?? {};
49
+ return {
50
+ set: chooseNullish(override.set, chooseNullish(base.set, [])),
51
+ remove: chooseNullish(override.remove, chooseNullish(base.remove, [])),
52
+ add: chooseNullish(override.add, chooseNullish(base.add, [])),
53
+ };
54
+ }
@@ -1,3 +1,5 @@
1
+ import { chooseDefined, chooseDefinedWithSource } from './merge-helpers.js';
2
+
1
3
  // =============================================================================
2
4
  // Merge Implementation
3
5
  // =============================================================================
@@ -8,12 +10,7 @@ function overlay(
8
10
  runtimeValue,
9
11
  sources
10
12
  ) {
11
- if (runtimeValue !== undefined) {
12
- sources.set(path, 'runtime');
13
- return runtimeValue;
14
- }
15
- sources.set(path, 'manifest');
16
- return manifestValue;
13
+ return chooseDefinedWithSource(path, runtimeValue, manifestValue, sources);
17
14
  }
18
15
 
19
16
  function mergeAttention(
@@ -155,6 +152,24 @@ function mergeRoPE(
155
152
  runtime?.ropeLocalTheta,
156
153
  sources
157
154
  ),
155
+ mropeInterleaved: overlay(
156
+ `${prefix}.mropeInterleaved`,
157
+ manifest.mropeInterleaved,
158
+ runtime?.mropeInterleaved,
159
+ sources
160
+ ),
161
+ mropeSection: overlay(
162
+ `${prefix}.mropeSection`,
163
+ manifest.mropeSection,
164
+ runtime?.mropeSection,
165
+ sources
166
+ ),
167
+ partialRotaryFactor: overlay(
168
+ `${prefix}.partialRotaryFactor`,
169
+ manifest.partialRotaryFactor,
170
+ runtime?.partialRotaryFactor,
171
+ sources
172
+ ),
158
173
  ropeScalingType: overlay(
159
174
  `${prefix}.ropeScalingType`,
160
175
  manifest.ropeScalingType,
@@ -14,6 +14,8 @@
14
14
  },
15
15
 
16
16
  "tokenizer": {
17
+ "bosToken": "<|begin▁of▁sentence|>",
18
+ "eosTokens": ["<|end▁of▁sentence|>"],
17
19
  "addBosToken": true,
18
20
  "addEosToken": false
19
21
  },