@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
@@ -0,0 +1,99 @@
1
+ import path from 'node:path';
2
+
3
+ import { createConverterConfig } from '../config/schema/index.js';
4
+ import { resolveConversionPlan } from '../converter/conversion-plan.js';
5
+
6
+ function toSafeString(value) {
7
+ if (typeof value !== 'string') return '';
8
+ const trimmed = value.trim();
9
+ return trimmed || '';
10
+ }
11
+
12
+ function normalizeQuantizationTag(value) {
13
+ const raw = toSafeString(value).toUpperCase();
14
+ if (!raw) return 'f16';
15
+ if (raw === 'Q4_K_M' || raw === 'Q4_K') return 'q4k';
16
+ return raw.toLowerCase();
17
+ }
18
+
19
+ function resolveArchitectureHint(architecture) {
20
+ if (!architecture) return '';
21
+ if (typeof architecture === 'string') return architecture;
22
+ return (
23
+ toSafeString(architecture.id)
24
+ || toSafeString(architecture.name)
25
+ || toSafeString(architecture.type)
26
+ || ''
27
+ );
28
+ }
29
+
30
+ function resolveHeadDim(architecture) {
31
+ const headDim = Number(architecture?.headDim ?? architecture?.head_dim);
32
+ return Number.isFinite(headDim) && headDim > 0 ? headDim : null;
33
+ }
34
+
35
+ function extractSourceQuantization(manifest) {
36
+ const explicitWeights = toSafeString(manifest?.quantizationInfo?.weights);
37
+ if (explicitWeights) return explicitWeights;
38
+ const explicitQuant = toSafeString(manifest?.quantization);
39
+ if (explicitQuant) return explicitQuant;
40
+ return 'f16';
41
+ }
42
+
43
+ function buildRefreshRawConfig(manifest) {
44
+ const baseConfig = (manifest?.config && typeof manifest.config === 'object')
45
+ ? { ...manifest.config }
46
+ : {};
47
+ const manifestLayerTypes = manifest?.inference?.layerPattern?.layerTypes;
48
+ if (Array.isArray(manifestLayerTypes) && manifestLayerTypes.length > 0) {
49
+ return {
50
+ ...baseConfig,
51
+ layer_types: [...manifestLayerTypes],
52
+ };
53
+ }
54
+ return baseConfig;
55
+ }
56
+
57
+ export function extractTensorEntriesFromManifest(manifest) {
58
+ if (!(manifest?.tensors && typeof manifest.tensors === 'object' && !Array.isArray(manifest.tensors))) {
59
+ return [];
60
+ }
61
+ return Object.entries(manifest.tensors).map(([name, tensor]) => ({
62
+ name,
63
+ dtype: tensor?.dtype ?? null,
64
+ shape: tensor?.shape ?? null,
65
+ role: tensor?.role ?? null,
66
+ layout: tensor?.layout ?? null,
67
+ }));
68
+ }
69
+
70
+ export function resolveMaterializedManifestFromConversionConfig(conversionConfigInput, manifest) {
71
+ const converterConfig = createConverterConfig(conversionConfigInput);
72
+ const tensorEntries = extractTensorEntriesFromManifest(manifest);
73
+ const architecture = manifest?.architecture && typeof manifest.architecture === 'object'
74
+ ? manifest.architecture
75
+ : null;
76
+ const plan = resolveConversionPlan({
77
+ rawConfig: buildRefreshRawConfig(manifest),
78
+ tensors: tensorEntries,
79
+ converterConfig,
80
+ sourceQuantization: normalizeQuantizationTag(extractSourceQuantization(manifest)),
81
+ modelKind: manifest?.modelType === 'diffusion' ? 'diffusion' : 'transformer',
82
+ architectureHint: resolveArchitectureHint(manifest?.architecture),
83
+ architectureConfig: architecture,
84
+ headDim: resolveHeadDim(architecture),
85
+ presetOverride: converterConfig?.presets?.model || manifest?.inference?.presetId || null,
86
+ });
87
+ return {
88
+ modelId: manifest?.modelId ?? converterConfig?.output?.modelBaseId ?? 'unknown',
89
+ modelType: manifest?.modelType ?? plan?.modelType ?? 'transformer',
90
+ architecture: manifest?.architecture ?? null,
91
+ inference: plan?.manifestInference ?? null,
92
+ };
93
+ }
94
+
95
+ export function inferConversionConfigModelId(configPath, conversionConfigInput) {
96
+ const configuredId = toSafeString(conversionConfigInput?.output?.modelBaseId);
97
+ if (configuredId) return configuredId;
98
+ return path.basename(String(configPath), path.extname(String(configPath)));
99
+ }
@@ -0,0 +1,43 @@
1
+ export interface LeanExecutionContractRunResult {
2
+ ok: boolean;
3
+ toolchainRef: string | null;
4
+ generatedPath: string;
5
+ moduleName: string;
6
+ facts: Record<string, unknown>;
7
+ }
8
+
9
+ export declare function resolveLeanBinary(): string;
10
+
11
+ export declare function runLeanCheck(options: {
12
+ sourcePath: string;
13
+ rootDir: string;
14
+ }): {
15
+ ok: boolean;
16
+ toolchainRef: string;
17
+ };
18
+
19
+ export declare function writeExecutionContractLeanModuleForManifest(
20
+ manifest: Record<string, unknown>,
21
+ options?: {
22
+ rootDir?: string;
23
+ moduleName?: string;
24
+ emitPath?: string | null;
25
+ }
26
+ ): {
27
+ rootDir: string;
28
+ facts: Record<string, unknown>;
29
+ moduleName: string;
30
+ source: string;
31
+ generatedPath: string;
32
+ tempDir: string | null;
33
+ };
34
+
35
+ export declare function runLeanExecutionContractForManifest(
36
+ manifest: Record<string, unknown>,
37
+ options?: {
38
+ rootDir?: string;
39
+ moduleName?: string;
40
+ emitPath?: string | null;
41
+ check?: boolean;
42
+ }
43
+ ): LeanExecutionContractRunResult;
@@ -0,0 +1,158 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ import {
7
+ extractExecutionContractFacts,
8
+ renderExecutionContractLeanModule,
9
+ sanitizeLeanModuleName,
10
+ } from './lean-execution-contract.js';
11
+
12
+ export function resolveLeanBinary() {
13
+ const elanLean = path.join(os.homedir(), '.elan', 'bin', 'lean');
14
+ if (fs.existsSync(elanLean)) {
15
+ return elanLean;
16
+ }
17
+ const probe = spawnSync('bash', ['-lc', 'command -v lean'], { encoding: 'utf8' });
18
+ if (probe.status === 0) {
19
+ const resolved = probe.stdout.trim();
20
+ if (resolved) {
21
+ return resolved;
22
+ }
23
+ }
24
+ throw new Error('lean binary not found. Install Lean with elan first.');
25
+ }
26
+
27
+ function runLeanCommand({ leanBin, toolchainRef, buildDir, rootDir, sourcePath, outputPath }) {
28
+ const result = spawnSync(
29
+ leanBin,
30
+ [`+${toolchainRef}`, '-o', outputPath, sourcePath],
31
+ {
32
+ cwd: rootDir,
33
+ encoding: 'utf8',
34
+ env: {
35
+ ...process.env,
36
+ LEAN_PATH: `${buildDir}:${path.join(rootDir, 'lean')}`,
37
+ },
38
+ }
39
+ );
40
+ if (result.stdout) {
41
+ process.stdout.write(result.stdout);
42
+ }
43
+ if (result.stderr) {
44
+ process.stderr.write(result.stderr);
45
+ }
46
+ if (result.status !== 0) {
47
+ throw new Error(`lean exited with status ${result.status}`);
48
+ }
49
+ }
50
+
51
+ export function runLeanCheck({ sourcePath, rootDir }) {
52
+ const toolchainVersion = process.env.DOPPLER_LEAN_VERSION ?? '4.16.0';
53
+ const toolchainRef = toolchainVersion.startsWith('v')
54
+ ? `leanprover/lean4:${toolchainVersion}`
55
+ : `leanprover/lean4:v${toolchainVersion}`;
56
+ const leanBin = resolveLeanBinary();
57
+ const buildDir = fs.mkdtempSync(path.join(os.tmpdir(), 'doppler-lean-execution-contract-'));
58
+ try {
59
+ fs.mkdirSync(path.join(buildDir, 'Doppler'), { recursive: true });
60
+ runLeanCommand({
61
+ leanBin,
62
+ toolchainRef,
63
+ buildDir,
64
+ rootDir,
65
+ sourcePath: path.join(rootDir, 'lean', 'Doppler', 'Model.lean'),
66
+ outputPath: path.join(buildDir, 'Doppler', 'Model.olean'),
67
+ });
68
+ runLeanCommand({
69
+ leanBin,
70
+ toolchainRef,
71
+ buildDir,
72
+ rootDir,
73
+ sourcePath: path.join(rootDir, 'lean', 'Doppler', 'ExecutionContract.lean'),
74
+ outputPath: path.join(buildDir, 'Doppler', 'ExecutionContract.olean'),
75
+ });
76
+ const generatedOutput = path.join(buildDir, 'GeneratedExecutionContractCheck.olean');
77
+ const result = spawnSync(
78
+ leanBin,
79
+ [`+${toolchainRef}`, '-o', generatedOutput, sourcePath],
80
+ {
81
+ cwd: rootDir,
82
+ encoding: 'utf8',
83
+ env: {
84
+ ...process.env,
85
+ LEAN_PATH: `${buildDir}:${path.join(rootDir, 'lean')}`,
86
+ },
87
+ }
88
+ );
89
+ if (result.stdout) {
90
+ process.stdout.write(result.stdout);
91
+ }
92
+ if (result.stderr) {
93
+ process.stderr.write(result.stderr);
94
+ }
95
+ if (result.status !== 0) {
96
+ throw new Error(`lean exited with status ${result.status}`);
97
+ }
98
+ const overallMatch = result.stdout.match(/executionContractOverall:(pass|fail)/);
99
+ if (!overallMatch) {
100
+ throw new Error('unable to parse executionContractOverall from Lean output.');
101
+ }
102
+ return { ok: overallMatch[1] === 'pass', toolchainRef };
103
+ } finally {
104
+ fs.rmSync(buildDir, { recursive: true, force: true });
105
+ }
106
+ }
107
+
108
+ export function writeExecutionContractLeanModuleForManifest(manifest, options = {}) {
109
+ const rootDir = path.resolve(String(options.rootDir ?? process.cwd()));
110
+ const facts = extractExecutionContractFacts(manifest);
111
+ const moduleName = sanitizeLeanModuleName(options.moduleName ?? `${facts.modelId}_ExecutionContractCheck`);
112
+ const source = renderExecutionContractLeanModule(facts, { moduleName });
113
+ const tempDir = options.emitPath
114
+ ? null
115
+ : fs.mkdtempSync(path.join(rootDir, 'lean', '.generated-'));
116
+ const generatedPath = options.emitPath
117
+ ? path.resolve(rootDir, String(options.emitPath))
118
+ : path.join(tempDir, `${moduleName}.lean`);
119
+ fs.mkdirSync(path.dirname(generatedPath), { recursive: true });
120
+ fs.writeFileSync(generatedPath, source);
121
+ return {
122
+ rootDir,
123
+ facts,
124
+ moduleName,
125
+ source,
126
+ generatedPath,
127
+ tempDir,
128
+ };
129
+ }
130
+
131
+ export function runLeanExecutionContractForManifest(manifest, options = {}) {
132
+ const generated = writeExecutionContractLeanModuleForManifest(manifest, options);
133
+ try {
134
+ if (options.check === false) {
135
+ return {
136
+ ok: true,
137
+ toolchainRef: null,
138
+ generatedPath: generated.generatedPath,
139
+ moduleName: generated.moduleName,
140
+ facts: generated.facts,
141
+ };
142
+ }
143
+ const result = runLeanCheck({
144
+ sourcePath: generated.generatedPath,
145
+ rootDir: generated.rootDir,
146
+ });
147
+ return {
148
+ ...result,
149
+ generatedPath: generated.generatedPath,
150
+ moduleName: generated.moduleName,
151
+ facts: generated.facts,
152
+ };
153
+ } finally {
154
+ if (!options.emitPath && generated.tempDir) {
155
+ fs.rmSync(generated.tempDir, { recursive: true, force: true });
156
+ }
157
+ }
158
+ }
@@ -7,6 +7,10 @@ import type { BrowserCommandRunResult } from './browser-command-runner.js';
7
7
 
8
8
  export interface NodeBrowserCommandRunOptions {
9
9
  staticRootDir?: string;
10
+ staticMounts?: Array<{
11
+ urlPrefix: string;
12
+ rootDir: string;
13
+ }>;
10
14
  baseUrl?: string;
11
15
  host?: string;
12
16
  port?: number;
@@ -67,8 +67,61 @@ function resolveStaticPath(rootDir, requestPath) {
67
67
  return candidate;
68
68
  }
69
69
 
70
- async function resolveFileForRequest(rootDir, requestPath) {
71
- const resolved = resolveStaticPath(rootDir, requestPath);
70
+ function normalizeStaticMounts(mounts = []) {
71
+ if (!Array.isArray(mounts)) {
72
+ throw new Error('browser command: staticMounts must be an array.');
73
+ }
74
+
75
+ return mounts.map((mount, index) => {
76
+ if (!mount || typeof mount !== 'object' || Array.isArray(mount)) {
77
+ throw new Error(`browser command: staticMounts[${index}] must be an object.`);
78
+ }
79
+ const urlPrefix = String(mount.urlPrefix || '').trim();
80
+ const rootDir = String(mount.rootDir || '').trim();
81
+ if (!urlPrefix.startsWith('/')) {
82
+ throw new Error(`browser command: staticMounts[${index}].urlPrefix must start with "/".`);
83
+ }
84
+ if (!rootDir) {
85
+ throw new Error(`browser command: staticMounts[${index}].rootDir is required.`);
86
+ }
87
+ return {
88
+ urlPrefix: urlPrefix.replace(/\/+$/u, '') || '/',
89
+ rootDir: path.resolve(rootDir),
90
+ };
91
+ });
92
+ }
93
+
94
+ function findStaticRootForRequest(rootDir, mounts, requestPath) {
95
+ const normalizedPath = String(requestPath || '/');
96
+ let bestMount = null;
97
+
98
+ for (const mount of mounts) {
99
+ const prefix = mount.urlPrefix;
100
+ if (normalizedPath !== prefix && !normalizedPath.startsWith(`${prefix}/`)) {
101
+ continue;
102
+ }
103
+ if (!bestMount || prefix.length > bestMount.urlPrefix.length) {
104
+ bestMount = mount;
105
+ }
106
+ }
107
+
108
+ if (!bestMount) {
109
+ return {
110
+ effectiveRootDir: rootDir,
111
+ effectivePath: normalizedPath,
112
+ };
113
+ }
114
+
115
+ const relativePath = normalizedPath.slice(bestMount.urlPrefix.length) || '/';
116
+ return {
117
+ effectiveRootDir: bestMount.rootDir,
118
+ effectivePath: relativePath.startsWith('/') ? relativePath : `/${relativePath}`,
119
+ };
120
+ }
121
+
122
+ async function resolveFileForRequest(rootDir, mounts, requestPath) {
123
+ const { effectiveRootDir, effectivePath } = findStaticRootForRequest(rootDir, mounts, requestPath);
124
+ const resolved = resolveStaticPath(effectiveRootDir, effectivePath);
72
125
  if (!resolved) return null;
73
126
 
74
127
  let stats;
@@ -99,6 +152,7 @@ async function createStaticFileServer(options = {}) {
99
152
  const rootDir = path.resolve(
100
153
  options.rootDir || fileURLToPath(new URL('../../', import.meta.url))
101
154
  );
155
+ const staticMounts = normalizeStaticMounts(options.staticMounts || []);
102
156
  const host = String(options.host || DEFAULT_HOST);
103
157
  const port = Number.isFinite(options.port) ? Math.max(0, Math.floor(options.port)) : 0;
104
158
 
@@ -120,7 +174,7 @@ async function createStaticFileServer(options = {}) {
120
174
  return;
121
175
  }
122
176
 
123
- const resolved = await resolveFileForRequest(rootDir, pathname);
177
+ const resolved = await resolveFileForRequest(rootDir, staticMounts, pathname);
124
178
  if (!resolved) {
125
179
  res.statusCode = 404;
126
180
  res.end('File not found');
@@ -545,6 +599,7 @@ export async function runBrowserCommandInNode(commandRequest, options = {}) {
545
599
  ? null
546
600
  : await createStaticFileServer({
547
601
  rootDir: options.staticRootDir,
602
+ staticMounts: options.staticMounts,
548
603
  host: options.host,
549
604
  port: serverPort,
550
605
  }).catch((error) => {
@@ -18,6 +18,7 @@ import {
18
18
  getActiveKernelPathSource,
19
19
  setActiveKernelPath,
20
20
  } from '../config/kernel-path-loader.js';
21
+ import { runTrainingOperatorCommand } from '../training/operator-command.js';
21
22
 
22
23
  function asOptionalPlainObject(value, label) {
23
24
  if (value == null) return null;
@@ -90,6 +91,20 @@ export async function runNodeCommand(commandRequest, options = {}) {
90
91
  });
91
92
  }
92
93
 
94
+ if (request.command === 'lora' || request.command === 'distill') {
95
+ const gpuOptionalActions = new Set(['compare', 'quality-gate', 'subsets']);
96
+ installNodeFileFetchShim();
97
+ if (!gpuOptionalActions.has(request.action)) {
98
+ await assertNodeWebGPUSupport();
99
+ }
100
+ const result = await runTrainingOperatorCommand(request);
101
+ return createToolingSuccessEnvelope({
102
+ surface: 'node',
103
+ request,
104
+ result,
105
+ });
106
+ }
107
+
93
108
  await assertNodeWebGPUSupport();
94
109
  const modules = await loadRuntimeModules();
95
110
  const runtimeBridge = {
@@ -1,4 +1,8 @@
1
1
  import type { ConverterConfigSchema } from '../config/schema/converter.schema.js';
2
+ import type { ExecutionContractArtifact } from '../config/execution-contract-check.js';
3
+ import type { ExecutionV0GraphContractArtifact } from '../config/execution-v0-graph-contract-check.js';
4
+ import type { ManifestRequiredInferenceFieldsArtifact } from '../config/required-inference-fields-contract-check.js';
5
+ import type { SavedReportInfo } from '../storage/reports.js';
2
6
 
3
7
  export interface NodeConvertProgress {
4
8
  stage: string | null;
@@ -34,6 +38,12 @@ export interface ConvertSafetensorsDirectoryResult {
34
38
  manifest: Record<string, unknown>;
35
39
  shardCount: number;
36
40
  tensorCount: number;
41
+ executionContractArtifact: ExecutionContractArtifact | null;
42
+ executionV0GraphContractArtifact: ExecutionV0GraphContractArtifact | null;
43
+ layerPatternContractArtifact: Record<string, unknown> | null;
44
+ requiredInferenceFieldsArtifact: ManifestRequiredInferenceFieldsArtifact | null;
45
+ report: Record<string, unknown>;
46
+ reportInfo: SavedReportInfo;
37
47
  presetId: string;
38
48
  modelType: string;
39
49
  outputDir: string;
@@ -7,6 +7,11 @@ import { bootstrapNodeWebGPU } from './node-webgpu.js';
7
7
  import { isPlainObject } from '../utils/plain-object.js';
8
8
  import { selectRuleValue } from '../rules/rule-registry.js';
9
9
  import { log, trace } from '../debug/index.js';
10
+ import { saveReport } from '../storage/reports.js';
11
+ import {
12
+ CONVERSION_REPORT_SCHEMA_VERSION,
13
+ validateConversionReport,
14
+ } from '../config/schema/conversion-report.schema.js';
10
15
 
11
16
  function asPositiveInteger(value, label) {
12
17
  if (!Number.isInteger(value) || value < 1) {
@@ -594,6 +599,44 @@ function normalizeTokenizerManifest(manifest) {
594
599
  return manifest;
595
600
  }
596
601
 
602
+ function buildConvertReport(result, context) {
603
+ const manifest = result?.manifest ?? null;
604
+ const inference = manifest?.inference && typeof manifest.inference === 'object'
605
+ ? manifest.inference
606
+ : null;
607
+ return validateConversionReport({
608
+ schemaVersion: CONVERSION_REPORT_SCHEMA_VERSION,
609
+ suite: 'convert',
610
+ command: 'convert',
611
+ modelId: manifest?.modelId ?? context.modelId ?? 'unknown',
612
+ timestamp: manifest?.metadata?.convertedAt ?? new Date().toISOString(),
613
+ source: 'doppler',
614
+ result: {
615
+ presetId: context.presetId ?? null,
616
+ modelType: context.modelType ?? null,
617
+ outputDir: context.outputDir ?? null,
618
+ shardCount: result?.shardCount ?? null,
619
+ tensorCount: result?.tensorCount ?? null,
620
+ totalSize: result?.totalSize ?? null,
621
+ },
622
+ manifest: manifest
623
+ ? {
624
+ quantization: manifest.quantization ?? null,
625
+ quantizationInfo: manifest.quantizationInfo ?? null,
626
+ inference: {
627
+ presetId: inference?.presetId ?? null,
628
+ schema: inference?.schema ?? null,
629
+ defaultKernelPath: inference?.defaultKernelPath ?? null,
630
+ },
631
+ }
632
+ : null,
633
+ executionContractArtifact: result?.executionContractArtifact ?? null,
634
+ executionV0GraphContractArtifact: result?.executionV0GraphContractArtifact ?? null,
635
+ layerPatternContractArtifact: result?.layerPatternContractArtifact ?? null,
636
+ requiredInferenceFieldsArtifact: result?.requiredInferenceFieldsArtifact ?? null,
637
+ });
638
+ }
639
+
597
640
  function createNodeTensorTransformer(options) {
598
641
  const pool = options?.pool;
599
642
  const execution = options?.execution;
@@ -1212,10 +1255,26 @@ export async function convertSafetensorsDirectory(options) {
1212
1255
  normalizeTokenizerManifest(result.manifest);
1213
1256
  await io.writeManifest(result.manifest);
1214
1257
 
1258
+ const report = buildConvertReport(result, {
1259
+ presetId,
1260
+ modelType: resolvedModelType,
1261
+ outputDir,
1262
+ modelId: result.manifest?.modelId ?? modelId,
1263
+ });
1264
+ const reportInfo = await saveReport(report.modelId, report, {
1265
+ timestamp: report.timestamp,
1266
+ });
1267
+
1215
1268
  return {
1216
1269
  manifest: result.manifest,
1217
1270
  shardCount: result.shardCount,
1218
1271
  tensorCount: result.tensorCount,
1272
+ executionContractArtifact: result.executionContractArtifact ?? null,
1273
+ executionV0GraphContractArtifact: result.executionV0GraphContractArtifact ?? null,
1274
+ layerPatternContractArtifact: result.layerPatternContractArtifact ?? null,
1275
+ requiredInferenceFieldsArtifact: result.requiredInferenceFieldsArtifact ?? null,
1276
+ report,
1277
+ reportInfo,
1219
1278
  presetId,
1220
1279
  modelType: resolvedModelType,
1221
1280
  outputDir,
@@ -2,17 +2,6 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
2
2
  import { dirname, isAbsolute, resolve } from 'node:path';
3
3
  import { fileURLToPath, pathToFileURL } from 'node:url';
4
4
 
5
- const DOPPLER_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
6
-
7
- const DEFAULT_LOCAL_DOE_PROVIDER_PATH = resolve(
8
- dirname(fileURLToPath(import.meta.url)),
9
- '..',
10
- '..',
11
- '..',
12
- 'fawn',
13
- 'nursery',
14
- 'webgpu-core',
15
- );
16
5
  const DEFAULT_DOE_PROVIDER_CREATE_ARGS = 'enable-dawn-features=allow_unsafe_apis';
17
6
 
18
7
  function hasNavigatorGpu() {
@@ -59,20 +48,7 @@ function resolveCandidateModuleSpecifier(candidate) {
59
48
  }
60
49
 
61
50
  function resolveDefaultWebgpuModuleSpecifiers() {
62
- const specifiers = [];
63
- const localCandidates = [
64
- resolve(process.cwd(), '..', 'fawn', 'nursery', 'webgpu-core'),
65
- DEFAULT_LOCAL_DOE_PROVIDER_PATH,
66
- ];
67
- for (const localCandidate of localCandidates) {
68
- const resolvedPath = resolveNodeModuleFilePath(localCandidate);
69
- if (resolvedPath) {
70
- specifiers.push(pathToFileURL(resolvedPath).href);
71
- }
72
- }
73
- specifiers.push('@doe/webgpu-core');
74
- specifiers.push('webgpu');
75
- return [...new Set(specifiers)];
51
+ return ['@simulatte/webgpu', 'webgpu'];
76
52
  }
77
53
 
78
54
  function resolveWebgpuModuleSpecifiers() {
@@ -89,80 +65,26 @@ function resolveWebgpuModuleSpecifiers() {
89
65
  };
90
66
  }
91
67
 
92
- function resolveWorkspaceWebgpuProviderPath() {
93
- const candidates = [
94
- resolve(process.cwd(), 'node_modules', 'webgpu'),
95
- resolve(DOPPLER_ROOT, 'node_modules', 'webgpu'),
96
- ];
97
- for (const candidate of candidates) {
98
- const resolvedPath = resolveNodeModuleFilePath(candidate);
99
- if (resolvedPath) {
100
- return resolvedPath;
101
- }
102
- }
103
- return null;
104
- }
105
-
106
- function isDoeWebgpuCoreSpecifier(specifier) {
107
- if (specifier === '@doe/webgpu-core') {
108
- return true;
109
- }
110
- if (typeof specifier !== 'string') {
111
- return false;
112
- }
113
- if (specifier.includes('/webgpu-core/')) {
68
+ function isDoeWebgpuSpecifier(specifier) {
69
+ if (specifier === '@simulatte/webgpu') {
114
70
  return true;
115
71
  }
116
- return specifier.includes('webgpu-core') && specifier.startsWith('file://');
117
- }
118
-
119
- function resolveDoeProviderOverride(specifier) {
120
- const explicitProvider = process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
121
- if (typeof explicitProvider === 'string' && explicitProvider.trim().length > 0) {
122
- return null;
123
- }
124
- if (!isDoeWebgpuCoreSpecifier(specifier)) {
125
- return null;
126
- }
127
- return resolveWorkspaceWebgpuProviderPath();
72
+ return typeof specifier === 'string'
73
+ && specifier.startsWith('file://')
74
+ && specifier.includes('@simulatte/webgpu');
128
75
  }
129
76
 
130
77
  async function importWithProviderOverride(specifier) {
131
- const providerOverride = resolveDoeProviderOverride(specifier);
132
- const shouldApplyCreateArgsDefault = isDoeWebgpuCoreSpecifier(specifier)
78
+ const shouldApplyCreateArgsDefault = isDoeWebgpuSpecifier(specifier)
133
79
  && !(typeof process.env.FAWN_WEBGPU_CREATE_ARGS === 'string' && process.env.FAWN_WEBGPU_CREATE_ARGS.trim().length > 0);
134
- if (!providerOverride) {
135
- if (!shouldApplyCreateArgsDefault) {
136
- return import(specifier);
137
- }
138
- process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
139
- try {
140
- return await import(specifier);
141
- } finally {
142
- delete process.env.FAWN_WEBGPU_CREATE_ARGS;
143
- }
144
- }
145
- const hadProvider = Object.prototype.hasOwnProperty.call(process.env, 'FAWN_WEBGPU_NODE_PROVIDER_MODULE');
146
- const previousProvider = process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
147
- const hadCreateArgs = Object.prototype.hasOwnProperty.call(process.env, 'FAWN_WEBGPU_CREATE_ARGS');
148
- const previousCreateArgs = process.env.FAWN_WEBGPU_CREATE_ARGS;
149
- process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE = providerOverride;
150
- if (!hadCreateArgs) {
151
- process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
80
+ if (!shouldApplyCreateArgsDefault) {
81
+ return import(specifier);
152
82
  }
83
+ process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
153
84
  try {
154
85
  return await import(specifier);
155
86
  } finally {
156
- if (hadProvider) {
157
- process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE = previousProvider;
158
- } else {
159
- delete process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
160
- }
161
- if (hadCreateArgs) {
162
- process.env.FAWN_WEBGPU_CREATE_ARGS = previousCreateArgs;
163
- } else {
164
- delete process.env.FAWN_WEBGPU_CREATE_ARGS;
165
- }
87
+ delete process.env.FAWN_WEBGPU_CREATE_ARGS;
166
88
  }
167
89
  }
168
90
 
@@ -0,0 +1,7 @@
1
+ export declare function watchFinalizedCheckpoints(options: {
2
+ checkpointsDir: string;
3
+ manifestPath: string;
4
+ pollIntervalMs?: number | null;
5
+ stopWhenIdle?: boolean;
6
+ onCheckpoint: (markerPath: string) => Promise<void> | void;
7
+ }): Promise<{ ok: true; processedCount: number; manifestPath: string }>;