@simulatte/doppler 0.1.5 → 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 (130) hide show
  1. package/README.md +23 -8
  2. package/package.json +7 -4
  3. package/src/config/kernels/kernel-ref-digests.js +39 -39
  4. package/src/config/kernels/registry.json +42 -2
  5. package/src/config/loader.js +31 -2
  6. package/src/config/merge.js +18 -0
  7. package/src/config/presets/models/qwen3.json +9 -2
  8. package/src/config/presets/models/transformer.json +5 -0
  9. package/src/config/required-inference-fields-contract-check.js +6 -0
  10. package/src/config/schema/inference-defaults.schema.js +3 -0
  11. package/src/config/schema/inference.schema.d.ts +9 -0
  12. package/src/config/schema/kernel-path.schema.d.ts +6 -0
  13. package/src/config/schema/manifest.schema.d.ts +6 -0
  14. package/src/config/schema/manifest.schema.js +3 -0
  15. package/src/converter/rope-config.js +42 -0
  16. package/src/gpu/device.js +58 -0
  17. package/src/gpu/kernels/attention.js +98 -0
  18. package/src/gpu/kernels/bias_add.wgsl +8 -6
  19. package/src/gpu/kernels/bias_add_f16.wgsl +8 -5
  20. package/src/gpu/kernels/conv2d.js +1 -1
  21. package/src/gpu/kernels/conv2d.wgsl +7 -8
  22. package/src/gpu/kernels/conv2d_f16.wgsl +7 -8
  23. package/src/gpu/kernels/depthwise_conv2d.js +2 -1
  24. package/src/gpu/kernels/depthwise_conv2d.wgsl +6 -9
  25. package/src/gpu/kernels/depthwise_conv2d_f16.wgsl +6 -9
  26. package/src/gpu/kernels/grouped_pointwise_conv2d.js +2 -1
  27. package/src/gpu/kernels/grouped_pointwise_conv2d.wgsl +6 -9
  28. package/src/gpu/kernels/grouped_pointwise_conv2d_f16.wgsl +6 -9
  29. package/src/gpu/kernels/matmul.js +25 -0
  30. package/src/gpu/kernels/pixel_shuffle.js +1 -1
  31. package/src/gpu/kernels/pixel_shuffle.wgsl +4 -5
  32. package/src/gpu/kernels/pixel_shuffle_f16.wgsl +4 -5
  33. package/src/gpu/kernels/relu.js +15 -2
  34. package/src/gpu/kernels/relu.wgsl +2 -1
  35. package/src/gpu/kernels/relu_f16.wgsl +2 -1
  36. package/src/gpu/kernels/repeat_channels.js +1 -1
  37. package/src/gpu/kernels/repeat_channels.wgsl +4 -5
  38. package/src/gpu/kernels/repeat_channels_f16.wgsl +4 -5
  39. package/src/gpu/kernels/residual.js +44 -8
  40. package/src/gpu/kernels/residual.wgsl +6 -3
  41. package/src/gpu/kernels/residual_f16.wgsl +2 -1
  42. package/src/gpu/kernels/residual_f16_vec4.wgsl +2 -1
  43. package/src/gpu/kernels/residual_vec4.wgsl +2 -1
  44. package/src/gpu/kernels/rmsnorm.js +58 -6
  45. package/src/gpu/kernels/rmsnorm.wgsl +14 -6
  46. package/src/gpu/kernels/rmsnorm_f16.wgsl +10 -2
  47. package/src/gpu/kernels/rope.d.ts +2 -0
  48. package/src/gpu/kernels/rope.js +11 -1
  49. package/src/gpu/kernels/rope.wgsl +56 -40
  50. package/src/gpu/kernels/sana_linear_attention.js +1 -2
  51. package/src/gpu/kernels/sana_linear_attention_apply.wgsl +4 -5
  52. package/src/gpu/kernels/sana_linear_attention_apply_f16.wgsl +4 -5
  53. package/src/gpu/kernels/sana_linear_attention_summary.wgsl +4 -0
  54. package/src/gpu/kernels/sana_linear_attention_summary_f16.wgsl +4 -0
  55. package/src/gpu/kernels/silu.d.ts +1 -0
  56. package/src/gpu/kernels/silu.js +32 -14
  57. package/src/gpu/kernels/silu.wgsl +19 -9
  58. package/src/gpu/kernels/silu_f16.wgsl +19 -9
  59. package/src/gpu/kernels/transpose.js +15 -2
  60. package/src/gpu/kernels/transpose.wgsl +5 -6
  61. package/src/gpu/kernels/upsample2d.js +2 -1
  62. package/src/gpu/kernels/upsample2d.wgsl +6 -9
  63. package/src/gpu/kernels/upsample2d_f16.wgsl +6 -9
  64. package/src/gpu/kernels/utils.js +16 -1
  65. package/src/inference/browser-harness.js +47 -1
  66. package/src/inference/pipelines/diffusion/pipeline.js +15 -6
  67. package/src/inference/pipelines/diffusion/text-encoder-gpu.d.ts +5 -0
  68. package/src/inference/pipelines/diffusion/text-encoder-gpu.js +27 -15
  69. package/src/inference/pipelines/text/attention/record.js +11 -2
  70. package/src/inference/pipelines/text/attention/run.js +11 -2
  71. package/src/inference/pipelines/text/chat-format.js +25 -1
  72. package/src/inference/pipelines/text/config.d.ts +4 -0
  73. package/src/inference/pipelines/text/config.js +68 -1
  74. package/src/inference/pipelines/text/execution-plan.js +23 -31
  75. package/src/inference/pipelines/text/execution-v0.js +29 -2
  76. package/src/inference/pipelines/text/ffn/standard.js +3 -0
  77. package/src/inference/pipelines/text/init.d.ts +4 -0
  78. package/src/inference/pipelines/text/init.js +56 -9
  79. package/src/inference/pipelines/text/layer.js +11 -0
  80. package/src/inference/pipelines/text.js +4 -0
  81. package/src/inference/tokenizers/bundled.js +156 -33
  82. package/src/rules/tooling/command-runtime.rules.json +18 -0
  83. package/src/tooling/command-api.d.ts +27 -1
  84. package/src/tooling/command-api.js +142 -3
  85. package/src/tooling/node-browser-command-runner.d.ts +4 -0
  86. package/src/tooling/node-browser-command-runner.js +58 -3
  87. package/src/tooling/node-command-runner.js +15 -0
  88. package/src/tooling/node-webgpu.js +9 -87
  89. package/src/training/checkpoint-watch.d.ts +7 -0
  90. package/src/training/checkpoint-watch.js +106 -0
  91. package/src/training/checkpoint.d.ts +6 -1
  92. package/src/training/checkpoint.js +12 -2
  93. package/src/training/distillation/artifacts.d.ts +71 -0
  94. package/src/training/distillation/artifacts.js +132 -0
  95. package/src/training/distillation/checkpoint-watch.d.ts +10 -0
  96. package/src/training/distillation/checkpoint-watch.js +57 -0
  97. package/src/training/distillation/dataset.d.ts +59 -0
  98. package/src/training/distillation/dataset.js +337 -0
  99. package/src/training/distillation/eval.d.ts +34 -0
  100. package/src/training/distillation/eval.js +310 -0
  101. package/src/training/distillation/index.d.ts +29 -0
  102. package/src/training/distillation/index.js +29 -0
  103. package/src/training/distillation/runtime.d.ts +20 -0
  104. package/src/training/distillation/runtime.js +121 -0
  105. package/src/training/distillation/scoreboard.d.ts +6 -0
  106. package/src/training/distillation/scoreboard.js +8 -0
  107. package/src/training/distillation/stage-a.d.ts +45 -0
  108. package/src/training/distillation/stage-a.js +338 -0
  109. package/src/training/distillation/stage-b.d.ts +24 -0
  110. package/src/training/distillation/stage-b.js +20 -0
  111. package/src/training/index.d.ts +10 -0
  112. package/src/training/index.js +10 -0
  113. package/src/training/lora-pipeline.d.ts +40 -0
  114. package/src/training/lora-pipeline.js +796 -0
  115. package/src/training/operator-artifacts.d.ts +62 -0
  116. package/src/training/operator-artifacts.js +140 -0
  117. package/src/training/operator-command.d.ts +5 -0
  118. package/src/training/operator-command.js +453 -0
  119. package/src/training/operator-eval.d.ts +48 -0
  120. package/src/training/operator-eval.js +230 -0
  121. package/src/training/operator-scoreboard.d.ts +5 -0
  122. package/src/training/operator-scoreboard.js +44 -0
  123. package/src/training/runner.d.ts +52 -0
  124. package/src/training/runner.js +29 -4
  125. package/src/training/suite.d.ts +112 -0
  126. package/src/training/suite.js +9 -9
  127. package/src/training/workloads.d.ts +164 -0
  128. package/src/training/workloads.js +539 -0
  129. package/src/version.js +1 -1
  130. package/tools/doppler-cli.js +137 -40
@@ -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
 
@@ -726,6 +802,13 @@ function toSummary(result) {
726
802
  return `converted ${result.manifest.modelId} (${result.tensorCount} tensors, ${result.shardCount} shards)${contractStatus}${graphStatus}`;
727
803
  }
728
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}`;
810
+ }
811
+
729
812
  const suite = result.suite || result.report?.suite || 'suite';
730
813
  const modelId = result.modelId || result.report?.modelId || 'unknown';
731
814
  const passed = Number.isFinite(result.passed) ? result.passed : null;
@@ -1122,6 +1205,20 @@ function printConvertReportSummary(result) {
1122
1205
 
1123
1206
  function printMetricsSummary(result) {
1124
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
+ }
1125
1222
  const suite = String(result.suite || '');
1126
1223
  const metrics = result.metrics;
1127
1224
  if (!metrics || typeof metrics !== 'object') return;