@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
package/src/version.js ADDED
@@ -0,0 +1,2 @@
1
+ export const DOPPLER_VERSION = '0.1.6';
2
+ export const DOPPLER_PROVIDER_VERSION = DOPPLER_VERSION;
@@ -126,6 +126,51 @@ function normalizeExecutionConfig(rawExecution) {
126
126
  };
127
127
  }
128
128
 
129
+ function printConvertContractSummary(result) {
130
+ const artifact = result?.executionContractArtifact;
131
+ if (!artifact || typeof artifact !== 'object') return;
132
+ const checks = Array.isArray(artifact.checks) ? artifact.checks : [];
133
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
134
+ const layout = artifact.session?.layout ?? 'n/a';
135
+ console.log(
136
+ `[contract] status=${artifact.ok === true ? 'pass' : 'fail'} ` +
137
+ `checks=${checks.length > 0 ? `${passedChecks}/${checks.length}` : 'n/a'} layout=${layout}`
138
+ );
139
+ if (artifact.ok !== true && Array.isArray(artifact.errors)) {
140
+ for (const error of artifact.errors.slice(0, 3)) {
141
+ console.log(`[contract] error=${String(error)}`);
142
+ }
143
+ }
144
+ const graphArtifact = result?.executionV0GraphContractArtifact;
145
+ if (graphArtifact && typeof graphArtifact === 'object') {
146
+ const checks = Array.isArray(graphArtifact.checks) ? graphArtifact.checks : [];
147
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
148
+ console.log(
149
+ `[graph] status=${graphArtifact.ok === true ? 'pass' : 'fail'} ` +
150
+ `checks=${checks.length > 0 ? `${passedChecks}/${checks.length}` : 'n/a'}`
151
+ );
152
+ }
153
+ for (const [label, extraArtifact] of [
154
+ ['layer-pattern', result?.layerPatternContractArtifact],
155
+ ['required-inference', result?.requiredInferenceFieldsArtifact],
156
+ ]) {
157
+ if (!extraArtifact || typeof extraArtifact !== 'object') continue;
158
+ const checks = Array.isArray(extraArtifact.checks) ? extraArtifact.checks : [];
159
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
160
+ console.log(
161
+ `[${label}] status=${extraArtifact.ok === true ? 'pass' : 'fail'} ` +
162
+ `checks=${checks.length > 0 ? `${passedChecks}/${checks.length}` : 'n/a'}`
163
+ );
164
+ }
165
+ }
166
+
167
+ function printConvertReportSummary(result) {
168
+ const reportInfo = result?.reportInfo;
169
+ if (!reportInfo || typeof reportInfo !== 'object') return;
170
+ if (typeof reportInfo.path !== 'string' || reportInfo.path.length === 0) return;
171
+ console.log(`[report] ${reportInfo.path}`);
172
+ }
173
+
129
174
  async function readJsonFile(filePath) {
130
175
  if (!filePath) return null;
131
176
  const raw = await fs.readFile(filePath, 'utf8');
@@ -172,6 +217,8 @@ async function main() {
172
217
  console.log(
173
218
  `[done] modelId=${result.manifest?.modelId ?? 'unknown'} preset=${result.presetId} modelType=${result.modelType} shards=${result.shardCount} tensors=${result.tensorCount}`
174
219
  );
220
+ printConvertContractSummary(result);
221
+ printConvertReportSummary(result);
175
222
  }
176
223
 
177
224
  main().catch((err) => {
@@ -66,6 +66,8 @@ function usage() {
66
66
  ' doppler debug --config <path.json|json> [--runtime-config <path|url|json>] [--surface auto|node|browser]',
67
67
  ' doppler bench --config <path.json|json> [--runtime-config <path|url|json>] [--surface auto|node|browser]',
68
68
  ' doppler verify --config <path.json|json> [--runtime-config <path|url|json>] [--surface auto|node|browser]',
69
+ ' doppler lora --config <path.json|json> [--surface auto|node]',
70
+ ' doppler distill --config <path.json|json> [--surface auto|node]',
69
71
  '',
70
72
  'Flags:',
71
73
  ' --config <path|json> Required command config payload (file path or JSON object string).',
@@ -337,7 +339,89 @@ function resolveStaticRootDir(browserOptions = {}) {
337
339
  return process.cwd();
338
340
  }
339
341
 
340
- async function resolveBrowserModelUrl(request, browserOptions = {}) {
342
+ function resolveRdrrRoot(options = {}) {
343
+ return path.resolve(asStringOrNull(options.rdrrRoot) || DEFAULT_EXTERNAL_RDRR_ROOT);
344
+ }
345
+
346
+ async function findResolvableModelCandidate(candidates) {
347
+ const discoveredManifestCandidates = [];
348
+
349
+ for (const candidate of candidates) {
350
+ if (!await pathExists(candidate.manifestPath)) {
351
+ continue;
352
+ }
353
+ discoveredManifestCandidates.push(candidate);
354
+
355
+ const modelDir = path.dirname(candidate.manifestPath);
356
+ try {
357
+ const files = await fs.readdir(modelDir, { withFileTypes: true });
358
+ const hasShards = files.some((entry) =>
359
+ entry.isFile() && /^shard_\d+\.bin$/u.test(entry.name)
360
+ );
361
+ if (hasShards) {
362
+ return { candidate, discoveredManifestCandidates };
363
+ }
364
+ } catch {
365
+ return { candidate, discoveredManifestCandidates };
366
+ }
367
+ }
368
+
369
+ return { candidate: null, discoveredManifestCandidates };
370
+ }
371
+
372
+ async function resolveExternalModelDirectory(rdrrRoot, modelId) {
373
+ const directModelDir = path.join(rdrrRoot, modelId);
374
+ const directManifestPath = path.join(directModelDir, 'manifest.json');
375
+ if (await pathExists(directManifestPath)) {
376
+ return {
377
+ modelDir: directModelDir,
378
+ manifestPath: directManifestPath,
379
+ };
380
+ }
381
+
382
+ let entries = [];
383
+ try {
384
+ entries = await fs.readdir(rdrrRoot, { withFileTypes: true });
385
+ } catch {
386
+ return null;
387
+ }
388
+
389
+ const matches = [];
390
+ for (const entry of entries) {
391
+ if (!entry.isDirectory()) {
392
+ continue;
393
+ }
394
+ const manifestPath = path.join(rdrrRoot, entry.name, 'manifest.json');
395
+ if (!await pathExists(manifestPath)) {
396
+ continue;
397
+ }
398
+ let manifest = null;
399
+ try {
400
+ manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
401
+ } catch {
402
+ continue;
403
+ }
404
+ if (manifest?.modelId !== modelId) {
405
+ continue;
406
+ }
407
+ matches.push({
408
+ modelDir: path.join(rdrrRoot, entry.name),
409
+ manifestPath,
410
+ });
411
+ }
412
+
413
+ if (matches.length > 1) {
414
+ const matchPaths = matches.map((match) => match.modelDir).join(', ');
415
+ throw new Error(
416
+ `Model "${modelId}" matched multiple external directories. ` +
417
+ `Disambiguate by setting request.modelUrl in --config. Matches: ${matchPaths}`
418
+ );
419
+ }
420
+
421
+ return matches[0] || null;
422
+ }
423
+
424
+ export async function resolveBrowserModelUrl(request, browserOptions = {}) {
341
425
  if (request.modelUrl || !request.modelId) {
342
426
  return request;
343
427
  }
@@ -353,49 +437,32 @@ async function resolveBrowserModelUrl(request, browserOptions = {}) {
353
437
  }
354
438
 
355
439
  const staticRootDir = resolveStaticRootDir(browserOptions);
356
- const curatedCandidate = {
440
+ const externalModel = await resolveExternalModelDirectory(resolveRdrrRoot(browserOptions), modelId);
441
+ const candidates = [
442
+ {
357
443
  modelUrl: `/models/curated/${encodedModelId}`,
358
444
  manifestPath: path.join(staticRootDir, 'models', 'curated', modelId, 'manifest.json'),
359
- };
360
- const localCandidate = {
445
+ },
446
+ {
361
447
  modelUrl: `/models/local/${encodedModelId}`,
362
448
  manifestPath: path.join(staticRootDir, 'models', 'local', modelId, 'manifest.json'),
363
- };
364
- const legacyCandidate = {
449
+ },
450
+ {
365
451
  modelUrl: `/models/${encodedModelId}`,
366
452
  manifestPath: path.join(staticRootDir, 'models', modelId, 'manifest.json'),
367
- };
368
- const candidates = [
369
- curatedCandidate,
370
- localCandidate,
371
- legacyCandidate,
453
+ },
454
+ {
455
+ modelUrl: `/models/external/${encodeURIComponent(path.basename(externalModel?.modelDir || modelId))}`,
456
+ manifestPath: externalModel?.manifestPath || path.join(resolveRdrrRoot(browserOptions), modelId, 'manifest.json'),
457
+ },
372
458
  ];
373
- const discoveredManifestCandidates = [];
374
459
 
375
- for (const candidate of candidates) {
376
- if (!await pathExists(candidate.manifestPath)) {
377
- continue;
378
- }
379
- discoveredManifestCandidates.push(candidate);
380
-
381
- const modelDir = path.dirname(candidate.manifestPath);
382
- try {
383
- const files = await fs.readdir(modelDir, { withFileTypes: true });
384
- const hasShards = files.some((entry) =>
385
- entry.isFile() && /^shard_\d+\.bin$/u.test(entry.name)
386
- );
387
- if (hasShards) {
388
- return {
389
- ...request,
390
- modelUrl: candidate.modelUrl,
391
- };
392
- }
393
- } catch {
394
- return {
395
- ...request,
396
- modelUrl: candidate.modelUrl,
397
- };
398
- }
460
+ const { candidate, discoveredManifestCandidates } = await findResolvableModelCandidate(candidates);
461
+ if (candidate) {
462
+ return {
463
+ ...request,
464
+ modelUrl: candidate.modelUrl,
465
+ };
399
466
  }
400
467
 
401
468
  if (discoveredManifestCandidates.length > 0) {
@@ -421,13 +488,13 @@ export async function resolveNodeModelUrl(request, options = {}) {
421
488
  }
422
489
 
423
490
  const modelId = String(request.modelId);
424
- const rdrrRoot = path.resolve(asStringOrNull(options.rdrrRoot) || DEFAULT_EXTERNAL_RDRR_ROOT);
425
- const manifestPath = path.join(rdrrRoot, modelId, 'manifest.json');
426
- if (!await pathExists(manifestPath)) {
491
+ const rdrrRoot = resolveRdrrRoot(options);
492
+ const externalModel = await resolveExternalModelDirectory(rdrrRoot, modelId);
493
+ if (!externalModel) {
427
494
  return request;
428
495
  }
429
496
 
430
- const modelDir = path.dirname(manifestPath);
497
+ const modelDir = externalModel.modelDir;
431
498
  try {
432
499
  const files = await fs.readdir(modelDir, { withFileTypes: true });
433
500
  const hasShards = files.some((entry) =>
@@ -593,10 +660,18 @@ function buildBrowserRunOptions(runConfig, jsonOutput, request = {}) {
593
660
  executablePath: asStringOrNull(browser.executablePath),
594
661
  runnerPath: asStringOrNull(browser.runnerPath),
595
662
  staticRootDir: asStringOrNull(browser.staticRootDir),
663
+ rdrrRoot: asStringOrNull(browser.rdrrRoot),
596
664
  baseUrl: asStringOrNull(browser.baseUrl),
597
665
  browserArgs: parseBrowserArgs(browser.browserArgs),
598
666
  headless: headed ? false : (explicitHeadless ?? true),
599
667
  };
668
+ const rdrrRoot = resolveRdrrRoot(options);
669
+ options.staticMounts = [
670
+ {
671
+ urlPrefix: '/models/external',
672
+ rootDir: rdrrRoot,
673
+ },
674
+ ];
600
675
 
601
676
  const port = parseNumberFlag(browser.port, 'run.browser.port');
602
677
  if (port !== null) {
@@ -644,6 +719,7 @@ function isNodeWebGPUFallbackCandidate(error, fallbackPolicy = DEFAULT_CLI_POLIC
644
719
  function isTrainingCommandFlow(request) {
645
720
  if (!request || typeof request !== 'object') return false;
646
721
  if (request.suite === 'training') return true;
722
+ if (request.command === 'lora' || request.command === 'distill') return true;
647
723
  return request.command === 'bench' && request.workloadType === 'training';
648
724
  }
649
725
 
@@ -713,7 +789,24 @@ function toSummary(result) {
713
789
  }
714
790
 
715
791
  if (result.manifest?.modelId) {
716
- return `converted ${result.manifest.modelId} (${result.tensorCount} tensors, ${result.shardCount} shards)`;
792
+ const contractStatus = result.executionContractArtifact?.ok === true
793
+ ? ' contract=pass'
794
+ : result.executionContractArtifact
795
+ ? ' contract=fail'
796
+ : '';
797
+ const graphStatus = result.executionV0GraphContractArtifact?.ok === true
798
+ ? ' graph=pass'
799
+ : result.executionV0GraphContractArtifact
800
+ ? ' graph=fail'
801
+ : '';
802
+ return `converted ${result.manifest.modelId} (${result.tensorCount} tensors, ${result.shardCount} shards)${contractStatus}${graphStatus}`;
803
+ }
804
+
805
+ if (result.kind === 'lora' || result.kind === 'distill') {
806
+ const workloadId = result.workloadId || 'unknown';
807
+ const action = result.action || 'run';
808
+ const runRoot = result.runRoot || 'n/a';
809
+ return `${result.kind} ${action} workload=${workloadId} runRoot=${runRoot}`;
717
810
  }
718
811
 
719
812
  const suite = result.suite || result.report?.suite || 'suite';
@@ -1014,8 +1107,118 @@ function printMemoryReport(result) {
1014
1107
  console.log(`[memory] ${parts.join(' ')}`);
1015
1108
  }
1016
1109
 
1110
+ function printExecutionContractSummary(result) {
1111
+ const artifact = result?.metrics?.executionContractArtifact;
1112
+ if (!artifact || typeof artifact !== 'object') return;
1113
+ const checks = Array.isArray(artifact.checks) ? artifact.checks : [];
1114
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
1115
+ const session = artifact.session && typeof artifact.session === 'object'
1116
+ ? artifact.session
1117
+ : null;
1118
+ const attentionPhases = artifact.steps?.attentionPhases && typeof artifact.steps.attentionPhases === 'object'
1119
+ ? artifact.steps.attentionPhases
1120
+ : null;
1121
+ const parts = [
1122
+ `status=${artifact.ok === true ? 'pass' : 'fail'}`,
1123
+ checks.length > 0 ? `checks=${passedChecks}/${checks.length}` : 'checks=n/a',
1124
+ ];
1125
+ if (session?.layout) {
1126
+ parts.push(`layout=${session.layout}`);
1127
+ }
1128
+ if (attentionPhases) {
1129
+ parts.push(
1130
+ `attn(prefill=${attentionPhases.prefill ?? 'n/a'},decode=${attentionPhases.decode ?? 'n/a'},both=${attentionPhases.both ?? 'n/a'})`
1131
+ );
1132
+ }
1133
+ console.log(`[contract] ${parts.join(' ')}`);
1134
+ if (artifact.ok !== true && Array.isArray(artifact.errors)) {
1135
+ for (const error of artifact.errors.slice(0, 3)) {
1136
+ console.log(`[contract] error=${quoteOneLine(error)}`);
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ function printExecutionV0GraphSummary(artifact) {
1142
+ if (!artifact || typeof artifact !== 'object') return;
1143
+ const checks = Array.isArray(artifact.checks) ? artifact.checks : [];
1144
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
1145
+ const stats = artifact.stats && typeof artifact.stats === 'object' ? artifact.stats : null;
1146
+ const parts = [
1147
+ `status=${artifact.ok === true ? 'pass' : 'fail'}`,
1148
+ checks.length > 0 ? `checks=${passedChecks}/${checks.length}` : 'checks=n/a',
1149
+ ];
1150
+ if (Number.isFinite(stats?.prefillSteps) || Number.isFinite(stats?.decodeSteps)) {
1151
+ parts.push(`steps(prefill=${stats?.prefillSteps ?? 'n/a'},decode=${stats?.decodeSteps ?? 'n/a'})`);
1152
+ }
1153
+ console.log(`[graph] ${parts.join(' ')}`);
1154
+ if (artifact.ok !== true && Array.isArray(artifact.errors)) {
1155
+ for (const error of artifact.errors.slice(0, 3)) {
1156
+ console.log(`[graph] error=${quoteOneLine(error)}`);
1157
+ }
1158
+ }
1159
+ }
1160
+
1161
+ function printSimpleArtifactSummary(label, artifact) {
1162
+ if (!artifact || typeof artifact !== 'object') return;
1163
+ const checks = Array.isArray(artifact.checks) ? artifact.checks : [];
1164
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
1165
+ console.log(
1166
+ `[${label}] status=${artifact.ok === true ? 'pass' : 'fail'} ` +
1167
+ `checks=${checks.length > 0 ? `${passedChecks}/${checks.length}` : 'n/a'}`
1168
+ );
1169
+ if (artifact.ok !== true && Array.isArray(artifact.errors)) {
1170
+ for (const error of artifact.errors.slice(0, 2)) {
1171
+ console.log(`[${label}] error=${quoteOneLine(error)}`);
1172
+ }
1173
+ }
1174
+ }
1175
+
1176
+ function printConvertContractSummary(result) {
1177
+ const artifact = result?.executionContractArtifact;
1178
+ if (!artifact || typeof artifact !== 'object') return;
1179
+ const checks = Array.isArray(artifact.checks) ? artifact.checks : [];
1180
+ const passedChecks = checks.filter((entry) => entry?.ok === true).length;
1181
+ const session = artifact.session && typeof artifact.session === 'object'
1182
+ ? artifact.session
1183
+ : null;
1184
+ console.log(
1185
+ `[contract] status=${artifact.ok === true ? 'pass' : 'fail'} ` +
1186
+ `checks=${checks.length > 0 ? `${passedChecks}/${checks.length}` : 'n/a'} ` +
1187
+ `layout=${session?.layout ?? 'n/a'}`
1188
+ );
1189
+ if (artifact.ok !== true && Array.isArray(artifact.errors)) {
1190
+ for (const error of artifact.errors.slice(0, 3)) {
1191
+ console.log(`[contract] error=${quoteOneLine(error)}`);
1192
+ }
1193
+ }
1194
+ printExecutionV0GraphSummary(result?.executionV0GraphContractArtifact);
1195
+ printSimpleArtifactSummary('layer-pattern', result?.layerPatternContractArtifact);
1196
+ printSimpleArtifactSummary('required-inference', result?.requiredInferenceFieldsArtifact);
1197
+ }
1198
+
1199
+ function printConvertReportSummary(result) {
1200
+ const reportInfo = result?.reportInfo;
1201
+ if (!reportInfo || typeof reportInfo !== 'object') return;
1202
+ if (typeof reportInfo.path !== 'string' || reportInfo.path.length === 0) return;
1203
+ console.log(`[report] ${reportInfo.path}`);
1204
+ }
1205
+
1017
1206
  function printMetricsSummary(result) {
1018
1207
  if (!result || typeof result !== 'object') return;
1208
+ if (result.kind === 'distill') {
1209
+ const stageCount = Array.isArray(result.stageResults) ? result.stageResults.length : 0;
1210
+ console.log(
1211
+ `[metrics] kind=distill action=${result.action || 'run'} stages=${stageCount} runRoot=${quoteOneLine(result.runRoot)}`
1212
+ );
1213
+ return;
1214
+ }
1215
+ if (result.kind === 'lora') {
1216
+ const exportCount = Array.isArray(result.exports) ? result.exports.length : 0;
1217
+ console.log(
1218
+ `[metrics] kind=lora action=${result.action || 'run'} exports=${exportCount} runRoot=${quoteOneLine(result.runRoot)}`
1219
+ );
1220
+ return;
1221
+ }
1019
1222
  const suite = String(result.suite || '');
1020
1223
  const metrics = result.metrics;
1021
1224
  if (!metrics || typeof metrics !== 'object') return;
@@ -1038,6 +1241,8 @@ function printMetricsSummary(result) {
1038
1241
  `prefill=${formatNumber(metrics.prefillTokensPerSec)} ` +
1039
1242
  `decode=${formatNumber(metrics.decodeTokensPerSec)}`
1040
1243
  );
1244
+ printExecutionContractSummary(result);
1245
+ printExecutionV0GraphSummary(metrics.executionV0GraphContractArtifact);
1041
1246
  return;
1042
1247
  }
1043
1248
 
@@ -1052,6 +1257,8 @@ function printMetricsSummary(result) {
1052
1257
  `median=${formatMs(metrics.medianEmbeddingMs)} avg=${formatMs(metrics.avgEmbeddingMs)} ` +
1053
1258
  `eps=${formatNumber(metrics.avgEmbeddingsPerSec)}`
1054
1259
  );
1260
+ printExecutionContractSummary(result);
1261
+ printExecutionV0GraphSummary(metrics.executionV0GraphContractArtifact);
1055
1262
  return;
1056
1263
  }
1057
1264
 
@@ -1073,6 +1280,8 @@ function printMetricsSummary(result) {
1073
1280
  `[metrics] latency first=${formatMs(metrics.firstTokenMs)} ` +
1074
1281
  `prefill=${formatMs(metrics.prefillMs)} decode=${formatMs(metrics.decodeMs)}`
1075
1282
  );
1283
+ printExecutionContractSummary(result);
1284
+ printExecutionV0GraphSummary(metrics.executionV0GraphContractArtifact);
1076
1285
  printDeviceInfo(result);
1077
1286
  printGpuPhases(metrics);
1078
1287
  printMemoryReport(result);
@@ -1190,6 +1399,8 @@ async function main() {
1190
1399
  }
1191
1400
 
1192
1401
  console.log(`[ok] ${toSummary(response.result)}`);
1402
+ printConvertContractSummary(response.result);
1403
+ printConvertReportSummary(response.result);
1193
1404
  printMetricsSummary(response.result);
1194
1405
  } catch (error) {
1195
1406
  if (jsonOutputRequested) {