@simulatte/doppler 0.1.3 → 0.1.5

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 (114) hide show
  1. package/README.md +11 -5
  2. package/package.json +27 -4
  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.d.ts +80 -0
  6. package/src/client/doppler-api.js +298 -0
  7. package/src/client/doppler-provider/types.js +1 -1
  8. package/src/client/doppler-registry.d.ts +23 -0
  9. package/src/client/doppler-registry.js +88 -0
  10. package/src/client/doppler-registry.json +39 -0
  11. package/src/config/execution-contract-check.d.ts +82 -0
  12. package/src/config/execution-contract-check.js +317 -0
  13. package/src/config/execution-v0-contract-check.d.ts +94 -0
  14. package/src/config/execution-v0-contract-check.js +251 -0
  15. package/src/config/execution-v0-graph-contract-check.d.ts +20 -0
  16. package/src/config/execution-v0-graph-contract-check.js +64 -0
  17. package/src/config/kernel-path-contract-check.d.ts +76 -0
  18. package/src/config/kernel-path-contract-check.js +479 -0
  19. package/src/config/kernel-path-loader.d.ts +16 -0
  20. package/src/config/kernel-path-loader.js +54 -0
  21. package/src/config/kernels/kernel-ref-digests.js +12 -0
  22. package/src/config/kernels/registry.json +556 -0
  23. package/src/config/loader.js +90 -67
  24. package/src/config/merge-contract-check.d.ts +16 -0
  25. package/src/config/merge-contract-check.js +321 -0
  26. package/src/config/merge-helpers.d.ts +58 -0
  27. package/src/config/merge-helpers.js +54 -0
  28. package/src/config/merge.js +3 -6
  29. package/src/config/presets/models/janus-text.json +27 -0
  30. package/src/config/quantization-contract-check.d.ts +12 -0
  31. package/src/config/quantization-contract-check.js +91 -0
  32. package/src/config/required-inference-fields-contract-check.d.ts +24 -0
  33. package/src/config/required-inference-fields-contract-check.js +231 -0
  34. package/src/config/schema/browser-suite-metrics.schema.d.ts +17 -0
  35. package/src/config/schema/browser-suite-metrics.schema.js +46 -0
  36. package/src/config/schema/conversion-report.schema.d.ts +40 -0
  37. package/src/config/schema/conversion-report.schema.js +108 -0
  38. package/src/config/schema/doppler.schema.js +12 -18
  39. package/src/config/schema/index.d.ts +22 -0
  40. package/src/config/schema/index.js +18 -0
  41. package/src/converter/core.d.ts +10 -0
  42. package/src/converter/core.js +49 -11
  43. package/src/converter/parsers/diffusion.js +63 -3
  44. package/src/converter/tokenizer-utils.js +17 -3
  45. package/src/formats/rdrr/validation.js +13 -0
  46. package/src/gpu/kernels/depthwise_conv2d.d.ts +29 -0
  47. package/src/gpu/kernels/depthwise_conv2d.js +98 -0
  48. package/src/gpu/kernels/depthwise_conv2d.wgsl +58 -0
  49. package/src/gpu/kernels/depthwise_conv2d_f16.wgsl +62 -0
  50. package/src/gpu/kernels/grouped_pointwise_conv2d.d.ts +27 -0
  51. package/src/gpu/kernels/grouped_pointwise_conv2d.js +92 -0
  52. package/src/gpu/kernels/grouped_pointwise_conv2d.wgsl +47 -0
  53. package/src/gpu/kernels/grouped_pointwise_conv2d_f16.wgsl +51 -0
  54. package/src/gpu/kernels/index.d.ts +30 -0
  55. package/src/gpu/kernels/index.js +25 -0
  56. package/src/gpu/kernels/relu.d.ts +18 -0
  57. package/src/gpu/kernels/relu.js +45 -0
  58. package/src/gpu/kernels/relu.wgsl +21 -0
  59. package/src/gpu/kernels/relu_f16.wgsl +23 -0
  60. package/src/gpu/kernels/repeat_channels.d.ts +21 -0
  61. package/src/gpu/kernels/repeat_channels.js +60 -0
  62. package/src/gpu/kernels/repeat_channels.wgsl +29 -0
  63. package/src/gpu/kernels/repeat_channels_f16.wgsl +31 -0
  64. package/src/gpu/kernels/sana_linear_attention.d.ts +27 -0
  65. package/src/gpu/kernels/sana_linear_attention.js +122 -0
  66. package/src/gpu/kernels/sana_linear_attention_apply.wgsl +44 -0
  67. package/src/gpu/kernels/sana_linear_attention_apply_f16.wgsl +47 -0
  68. package/src/gpu/kernels/sana_linear_attention_summary.wgsl +47 -0
  69. package/src/gpu/kernels/sana_linear_attention_summary_f16.wgsl +49 -0
  70. package/src/index-browser.d.ts +1 -0
  71. package/src/index-browser.js +2 -1
  72. package/src/index.d.ts +1 -0
  73. package/src/index.js +2 -1
  74. package/src/inference/browser-harness.js +164 -38
  75. package/src/inference/pipelines/diffusion/init.js +14 -0
  76. package/src/inference/pipelines/diffusion/pipeline.js +206 -77
  77. package/src/inference/pipelines/diffusion/sana-transformer.d.ts +53 -0
  78. package/src/inference/pipelines/diffusion/sana-transformer.js +738 -0
  79. package/src/inference/pipelines/diffusion/scheduler.d.ts +17 -1
  80. package/src/inference/pipelines/diffusion/scheduler.js +91 -3
  81. package/src/inference/pipelines/diffusion/text-encoder-gpu.d.ts +6 -4
  82. package/src/inference/pipelines/diffusion/text-encoder-gpu.js +270 -0
  83. package/src/inference/pipelines/diffusion/text-encoder.js +18 -1
  84. package/src/inference/pipelines/diffusion/types.d.ts +4 -0
  85. package/src/inference/pipelines/diffusion/vae.js +782 -78
  86. package/src/inference/pipelines/text/config.d.ts +5 -0
  87. package/src/inference/pipelines/text/config.js +1 -1
  88. package/src/inference/pipelines/text/execution-v0.js +141 -101
  89. package/src/inference/pipelines/text/init.js +41 -10
  90. package/src/inference/pipelines/text.js +7 -1
  91. package/src/rules/execution-rules-contract-check.d.ts +17 -0
  92. package/src/rules/execution-rules-contract-check.js +245 -0
  93. package/src/rules/kernels/depthwise-conv2d.rules.json +6 -0
  94. package/src/rules/kernels/grouped-pointwise-conv2d.rules.json +6 -0
  95. package/src/rules/kernels/relu.rules.json +6 -0
  96. package/src/rules/kernels/repeat-channels.rules.json +6 -0
  97. package/src/rules/kernels/sana-linear-attention.rules.json +6 -0
  98. package/src/rules/layer-pattern-contract-check.d.ts +17 -0
  99. package/src/rules/layer-pattern-contract-check.js +231 -0
  100. package/src/rules/rule-registry.d.ts +28 -0
  101. package/src/rules/rule-registry.js +38 -0
  102. package/src/tooling/conversion-config-materializer.d.ts +24 -0
  103. package/src/tooling/conversion-config-materializer.js +99 -0
  104. package/src/tooling/lean-execution-contract-runner.d.ts +43 -0
  105. package/src/tooling/lean-execution-contract-runner.js +158 -0
  106. package/src/tooling/lean-execution-contract.d.ts +16 -0
  107. package/src/tooling/lean-execution-contract.js +81 -0
  108. package/src/tooling/node-convert.d.ts +10 -0
  109. package/src/tooling/node-converter.js +59 -0
  110. package/src/tooling/node-webgpu.js +30 -9
  111. package/src/version.d.ts +2 -0
  112. package/src/version.js +2 -0
  113. package/tools/convert-safetensors-node.js +47 -0
  114. package/tools/doppler-cli.js +167 -6
@@ -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
+ }
@@ -0,0 +1,16 @@
1
+ import type { ExecutionContractFacts } from '../config/execution-contract-check.js';
2
+
3
+ export interface RenderLeanExecutionContractOptions {
4
+ moduleName?: string;
5
+ }
6
+
7
+ export declare function sanitizeLeanModuleName(value: unknown): string;
8
+
9
+ export declare function extractExecutionContractFacts(
10
+ manifest: Record<string, unknown>
11
+ ): ExecutionContractFacts;
12
+
13
+ export declare function renderExecutionContractLeanModule(
14
+ facts: LeanExecutionContractFacts,
15
+ options?: RenderLeanExecutionContractOptions
16
+ ): string;
@@ -0,0 +1,81 @@
1
+ import {
2
+ extractExecutionContractFacts,
3
+ sanitizeLeanModuleName,
4
+ } from '../config/execution-contract-check.js';
5
+
6
+ export {
7
+ extractExecutionContractFacts,
8
+ sanitizeLeanModuleName,
9
+ } from '../config/execution-contract-check.js';
10
+
11
+ function asLeanString(value) {
12
+ return JSON.stringify(String(value));
13
+ }
14
+
15
+ function renderExecutionStep(step) {
16
+ return [
17
+ ' {',
18
+ ` id := ${asLeanString(step.id)},`,
19
+ ` phase := .${step.phase},`,
20
+ ` opClass := .${step.opClass},`,
21
+ ' }',
22
+ ].join('\n');
23
+ }
24
+
25
+ function renderCheckName(modelId, suffix) {
26
+ return `${modelId}.${suffix}`;
27
+ }
28
+
29
+ export function renderExecutionContractLeanModule(facts, options = {}) {
30
+ const moduleName = sanitizeLeanModuleName(options.moduleName ?? facts?.modelId ?? 'GeneratedExecutionContractCheck');
31
+ const modelId = String(facts?.modelId ?? 'model').trim() || 'model';
32
+ const session = facts?.session;
33
+ const steps = Array.isArray(facts?.steps) ? facts.steps : [];
34
+ if (!session || typeof session !== 'object') {
35
+ throw new Error('lean execution contract: facts.session is required.');
36
+ }
37
+
38
+ const renderedSteps = steps.length > 0
39
+ ? steps.map(renderExecutionStep).join(',\n')
40
+ : '';
41
+ const stepsLiteral = steps.length > 0
42
+ ? `[\n${renderedSteps}\n]`
43
+ : '[]';
44
+
45
+ return [
46
+ 'import Doppler.ExecutionContract',
47
+ '',
48
+ `def extractedModelId : String := ${asLeanString(modelId)}`,
49
+ '',
50
+ 'def extractedSession : SessionConfig := {',
51
+ ` layout := .${session.layout},`,
52
+ ` disableCommandBatching := ${session.disableCommandBatching ? 'true' : 'false'},`,
53
+ ` decodeBatchSize := ${session.decodeBatchSize},`,
54
+ ` headDim := ${session.headDim},`,
55
+ ` kvLen := ${session.kvLen},`,
56
+ ` coldQuantMode := .${session.coldQuantMode},`,
57
+ '}',
58
+ '',
59
+ `def extractedSteps : List ExecutionStep := ${stepsLiteral}`,
60
+ '',
61
+ 'def executionContractChecks : List (String × Bool) := [',
62
+ ` (${asLeanString(renderCheckName(modelId, 'steps'))}, allStepsCompatible extractedSteps extractedSession),`,
63
+ ` (${asLeanString(renderCheckName(modelId, 'session'))}, sessionConsistent extractedSession)`,
64
+ ']',
65
+ '',
66
+ 'def renderCheck (entry : String × Bool) : String :=',
67
+ ' let status := if entry.2 then "pass" else "fail"',
68
+ ' s!"{entry.1}: {status}"',
69
+ '',
70
+ 'def renderedChecks : List String :=',
71
+ ' executionContractChecks.map renderCheck',
72
+ '',
73
+ 'def executionContractOverall : Bool :=',
74
+ ' executionContractChecks.all (fun entry => entry.2)',
75
+ '',
76
+ '#eval s!"executionContractModule:' + moduleName + '"',
77
+ '#eval s!"executionContractOverall:{if executionContractOverall then "pass" else "fail"}"',
78
+ '#eval renderedChecks',
79
+ '',
80
+ ].join('\n');
81
+ }
@@ -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,
@@ -11,8 +11,9 @@ const DEFAULT_LOCAL_DOE_PROVIDER_PATH = resolve(
11
11
  '..',
12
12
  'fawn',
13
13
  'nursery',
14
- 'webgpu-core',
14
+ 'webgpu-doe',
15
15
  );
16
+ const DEFAULT_DOE_PROVIDER_CREATE_ARGS = 'enable-dawn-features=allow_unsafe_apis';
16
17
 
17
18
  function hasNavigatorGpu() {
18
19
  return typeof globalThis.navigator !== 'undefined' && !!globalThis.navigator?.gpu;
@@ -60,7 +61,7 @@ function resolveCandidateModuleSpecifier(candidate) {
60
61
  function resolveDefaultWebgpuModuleSpecifiers() {
61
62
  const specifiers = [];
62
63
  const localCandidates = [
63
- resolve(process.cwd(), '..', 'fawn', 'nursery', 'webgpu-core'),
64
+ resolve(process.cwd(), '..', 'fawn', 'nursery', 'webgpu-doe'),
64
65
  DEFAULT_LOCAL_DOE_PROVIDER_PATH,
65
66
  ];
66
67
  for (const localCandidate of localCandidates) {
@@ -69,7 +70,7 @@ function resolveDefaultWebgpuModuleSpecifiers() {
69
70
  specifiers.push(pathToFileURL(resolvedPath).href);
70
71
  }
71
72
  }
72
- specifiers.push('@doe/webgpu-core');
73
+ specifiers.push('@simulatte/webgpu-doe');
73
74
  specifiers.push('webgpu');
74
75
  return [...new Set(specifiers)];
75
76
  }
@@ -102,17 +103,17 @@ function resolveWorkspaceWebgpuProviderPath() {
102
103
  return null;
103
104
  }
104
105
 
105
- function isDoeWebgpuCoreSpecifier(specifier) {
106
- if (specifier === '@doe/webgpu-core') {
106
+ function isDoeWebgpuSpecifier(specifier) {
107
+ if (specifier === '@simulatte/webgpu-doe') {
107
108
  return true;
108
109
  }
109
110
  if (typeof specifier !== 'string') {
110
111
  return false;
111
112
  }
112
- if (specifier.includes('/webgpu-core/')) {
113
+ if (specifier.includes('/webgpu-doe/')) {
113
114
  return true;
114
115
  }
115
- return specifier.includes('webgpu-core') && specifier.startsWith('file://');
116
+ return specifier.includes('webgpu-doe') && specifier.startsWith('file://');
116
117
  }
117
118
 
118
119
  function resolveDoeProviderOverride(specifier) {
@@ -120,7 +121,7 @@ function resolveDoeProviderOverride(specifier) {
120
121
  if (typeof explicitProvider === 'string' && explicitProvider.trim().length > 0) {
121
122
  return null;
122
123
  }
123
- if (!isDoeWebgpuCoreSpecifier(specifier)) {
124
+ if (!isDoeWebgpuSpecifier(specifier)) {
124
125
  return null;
125
126
  }
126
127
  return resolveWorkspaceWebgpuProviderPath();
@@ -128,12 +129,27 @@ function resolveDoeProviderOverride(specifier) {
128
129
 
129
130
  async function importWithProviderOverride(specifier) {
130
131
  const providerOverride = resolveDoeProviderOverride(specifier);
132
+ const shouldApplyCreateArgsDefault = isDoeWebgpuSpecifier(specifier)
133
+ && !(typeof process.env.FAWN_WEBGPU_CREATE_ARGS === 'string' && process.env.FAWN_WEBGPU_CREATE_ARGS.trim().length > 0);
131
134
  if (!providerOverride) {
132
- return import(specifier);
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
+ }
133
144
  }
134
145
  const hadProvider = Object.prototype.hasOwnProperty.call(process.env, 'FAWN_WEBGPU_NODE_PROVIDER_MODULE');
135
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;
136
149
  process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE = providerOverride;
150
+ if (!hadCreateArgs) {
151
+ process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
152
+ }
137
153
  try {
138
154
  return await import(specifier);
139
155
  } finally {
@@ -142,6 +158,11 @@ async function importWithProviderOverride(specifier) {
142
158
  } else {
143
159
  delete process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
144
160
  }
161
+ if (hadCreateArgs) {
162
+ process.env.FAWN_WEBGPU_CREATE_ARGS = previousCreateArgs;
163
+ } else {
164
+ delete process.env.FAWN_WEBGPU_CREATE_ARGS;
165
+ }
145
166
  }
146
167
  }
147
168
 
@@ -0,0 +1,2 @@
1
+ export declare const DOPPLER_VERSION: string;
2
+ export declare const DOPPLER_PROVIDER_VERSION: string;
package/src/version.js ADDED
@@ -0,0 +1,2 @@
1
+ export const DOPPLER_VERSION = '0.1.5';
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) => {