@simulatte/doppler 0.1.4 → 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 (103) hide show
  1. package/README.md +4 -3
  2. package/package.json +25 -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.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 +12 -0
  18. package/src/config/kernels/registry.json +556 -0
  19. package/src/config/loader.js +50 -46
  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 +3 -6
  25. package/src/config/presets/models/janus-text.json +2 -0
  26. package/src/config/quantization-contract-check.d.ts +12 -0
  27. package/src/config/quantization-contract-check.js +91 -0
  28. package/src/config/required-inference-fields-contract-check.d.ts +24 -0
  29. package/src/config/required-inference-fields-contract-check.js +231 -0
  30. package/src/config/schema/browser-suite-metrics.schema.d.ts +17 -0
  31. package/src/config/schema/browser-suite-metrics.schema.js +46 -0
  32. package/src/config/schema/conversion-report.schema.d.ts +40 -0
  33. package/src/config/schema/conversion-report.schema.js +108 -0
  34. package/src/config/schema/doppler.schema.js +12 -18
  35. package/src/config/schema/index.d.ts +22 -0
  36. package/src/config/schema/index.js +18 -0
  37. package/src/converter/core.d.ts +10 -0
  38. package/src/converter/core.js +27 -2
  39. package/src/converter/parsers/diffusion.js +63 -3
  40. package/src/gpu/kernels/depthwise_conv2d.d.ts +29 -0
  41. package/src/gpu/kernels/depthwise_conv2d.js +98 -0
  42. package/src/gpu/kernels/depthwise_conv2d.wgsl +58 -0
  43. package/src/gpu/kernels/depthwise_conv2d_f16.wgsl +62 -0
  44. package/src/gpu/kernels/grouped_pointwise_conv2d.d.ts +27 -0
  45. package/src/gpu/kernels/grouped_pointwise_conv2d.js +92 -0
  46. package/src/gpu/kernels/grouped_pointwise_conv2d.wgsl +47 -0
  47. package/src/gpu/kernels/grouped_pointwise_conv2d_f16.wgsl +51 -0
  48. package/src/gpu/kernels/index.d.ts +30 -0
  49. package/src/gpu/kernels/index.js +25 -0
  50. package/src/gpu/kernels/relu.d.ts +18 -0
  51. package/src/gpu/kernels/relu.js +45 -0
  52. package/src/gpu/kernels/relu.wgsl +21 -0
  53. package/src/gpu/kernels/relu_f16.wgsl +23 -0
  54. package/src/gpu/kernels/repeat_channels.d.ts +21 -0
  55. package/src/gpu/kernels/repeat_channels.js +60 -0
  56. package/src/gpu/kernels/repeat_channels.wgsl +29 -0
  57. package/src/gpu/kernels/repeat_channels_f16.wgsl +31 -0
  58. package/src/gpu/kernels/sana_linear_attention.d.ts +27 -0
  59. package/src/gpu/kernels/sana_linear_attention.js +122 -0
  60. package/src/gpu/kernels/sana_linear_attention_apply.wgsl +44 -0
  61. package/src/gpu/kernels/sana_linear_attention_apply_f16.wgsl +47 -0
  62. package/src/gpu/kernels/sana_linear_attention_summary.wgsl +47 -0
  63. package/src/gpu/kernels/sana_linear_attention_summary_f16.wgsl +49 -0
  64. package/src/index-browser.d.ts +1 -1
  65. package/src/index-browser.js +2 -2
  66. package/src/index.js +1 -1
  67. package/src/inference/browser-harness.js +62 -22
  68. package/src/inference/pipelines/diffusion/init.js +14 -0
  69. package/src/inference/pipelines/diffusion/pipeline.js +206 -77
  70. package/src/inference/pipelines/diffusion/sana-transformer.d.ts +53 -0
  71. package/src/inference/pipelines/diffusion/sana-transformer.js +738 -0
  72. package/src/inference/pipelines/diffusion/scheduler.d.ts +17 -1
  73. package/src/inference/pipelines/diffusion/scheduler.js +91 -3
  74. package/src/inference/pipelines/diffusion/text-encoder-gpu.d.ts +6 -4
  75. package/src/inference/pipelines/diffusion/text-encoder-gpu.js +270 -0
  76. package/src/inference/pipelines/diffusion/text-encoder.js +18 -1
  77. package/src/inference/pipelines/diffusion/types.d.ts +4 -0
  78. package/src/inference/pipelines/diffusion/vae.js +782 -78
  79. package/src/inference/pipelines/text/config.d.ts +5 -0
  80. package/src/inference/pipelines/text/config.js +1 -1
  81. package/src/inference/pipelines/text/execution-v0.js +14 -93
  82. package/src/rules/execution-rules-contract-check.d.ts +17 -0
  83. package/src/rules/execution-rules-contract-check.js +245 -0
  84. package/src/rules/kernels/depthwise-conv2d.rules.json +6 -0
  85. package/src/rules/kernels/grouped-pointwise-conv2d.rules.json +6 -0
  86. package/src/rules/kernels/relu.rules.json +6 -0
  87. package/src/rules/kernels/repeat-channels.rules.json +6 -0
  88. package/src/rules/kernels/sana-linear-attention.rules.json +6 -0
  89. package/src/rules/layer-pattern-contract-check.d.ts +17 -0
  90. package/src/rules/layer-pattern-contract-check.js +231 -0
  91. package/src/rules/rule-registry.d.ts +28 -0
  92. package/src/rules/rule-registry.js +38 -0
  93. package/src/tooling/conversion-config-materializer.d.ts +24 -0
  94. package/src/tooling/conversion-config-materializer.js +99 -0
  95. package/src/tooling/lean-execution-contract-runner.d.ts +43 -0
  96. package/src/tooling/lean-execution-contract-runner.js +158 -0
  97. package/src/tooling/node-convert.d.ts +10 -0
  98. package/src/tooling/node-converter.js +59 -0
  99. package/src/tooling/node-webgpu.js +9 -9
  100. package/src/version.d.ts +2 -0
  101. package/src/version.js +2 -0
  102. package/tools/convert-safetensors-node.js +47 -0
  103. package/tools/doppler-cli.js +115 -1
@@ -0,0 +1,231 @@
1
+ import { selectByRules } from '../gpu/kernels/rule-matcher.js';
2
+ import { computeGlobalLayers } from '../config/schema/inference.schema.js';
3
+
4
+ function isPlainObject(value) {
5
+ return value != null && typeof value === 'object' && !Array.isArray(value);
6
+ }
7
+
8
+ function matchesExactObject(actual, expected) {
9
+ if (!isPlainObject(actual) || !isPlainObject(expected)) {
10
+ return false;
11
+ }
12
+ const actualKeys = Object.keys(actual).sort();
13
+ const expectedKeys = Object.keys(expected).sort();
14
+ if (actualKeys.length !== expectedKeys.length) {
15
+ return false;
16
+ }
17
+ for (let i = 0; i < actualKeys.length; i += 1) {
18
+ if (actualKeys[i] !== expectedKeys[i]) {
19
+ return false;
20
+ }
21
+ }
22
+ for (const key of expectedKeys) {
23
+ const expectedValue = expected[key];
24
+ const actualValue = actual[key];
25
+ if (isPlainObject(expectedValue)) {
26
+ if (!matchesExactObject(actualValue, expectedValue)) {
27
+ return false;
28
+ }
29
+ continue;
30
+ }
31
+ if (actualValue !== expectedValue) {
32
+ return false;
33
+ }
34
+ }
35
+ return true;
36
+ }
37
+
38
+ function expectedPatternKind(context) {
39
+ if (context.patternType === 'alternating' && context.globalPattern === 'even') {
40
+ return 'alternating_even';
41
+ }
42
+ if (context.patternType === 'alternating' && context.globalPattern === 'odd') {
43
+ return 'alternating_odd';
44
+ }
45
+ if (context.patternType === 'every_n') {
46
+ return 'every_n';
47
+ }
48
+ return null;
49
+ }
50
+
51
+ function expectedLayerType(context) {
52
+ if (context.patternKind === 'alternating_even') {
53
+ return context.isEven ? 'full_attention' : 'sliding_attention';
54
+ }
55
+ if (context.patternKind === 'alternating_odd') {
56
+ return context.isEven ? 'sliding_attention' : 'full_attention';
57
+ }
58
+ if (context.patternKind === 'every_n') {
59
+ return context.isStride ? 'full_attention' : 'sliding_attention';
60
+ }
61
+ return null;
62
+ }
63
+
64
+ function enumeratePatternKindContexts() {
65
+ const patternTypes = ['alternating', 'every_n', 'custom', null];
66
+ const globalPatterns = ['even', 'odd', 'every_n', null];
67
+ const contexts = [];
68
+ for (const patternType of patternTypes) {
69
+ for (const globalPattern of globalPatterns) {
70
+ contexts.push({ patternType, globalPattern });
71
+ }
72
+ }
73
+ return contexts;
74
+ }
75
+
76
+ function enumerateLayerTypeContexts() {
77
+ const patternKinds = ['alternating_even', 'alternating_odd', 'every_n'];
78
+ const booleans = [true, false];
79
+ const contexts = [];
80
+ for (const patternKind of patternKinds) {
81
+ for (const isEven of booleans) {
82
+ for (const isStride of booleans) {
83
+ contexts.push({ patternKind, isEven, isStride });
84
+ }
85
+ }
86
+ }
87
+ return contexts;
88
+ }
89
+
90
+ function checkRuleShape(rules, expected, label) {
91
+ if (!Array.isArray(rules)) {
92
+ return {
93
+ ok: false,
94
+ errors: [`[LayerPatternContract] ${label} must be an array.`],
95
+ };
96
+ }
97
+ if (rules.length !== expected.length) {
98
+ return {
99
+ ok: false,
100
+ errors: [`[LayerPatternContract] ${label} must contain exactly ${expected.length} rules; got ${rules.length}.`],
101
+ };
102
+ }
103
+ const errors = [];
104
+ for (let i = 0; i < expected.length; i += 1) {
105
+ if (!matchesExactObject(rules[i]?.match, expected[i].match) || rules[i]?.value !== expected[i].value) {
106
+ errors.push(`[LayerPatternContract] ${label} rule[${i}] drifted from the expected decision table.`);
107
+ break;
108
+ }
109
+ }
110
+ return {
111
+ ok: errors.length === 0,
112
+ errors,
113
+ };
114
+ }
115
+
116
+ function checkRuleSemantics(rules, contexts, expectedValue, label) {
117
+ const errors = [];
118
+ for (const context of contexts) {
119
+ const actual = selectByRules(rules, context);
120
+ const expected = expectedValue(context);
121
+ if (actual !== expected) {
122
+ errors.push(
123
+ `[LayerPatternContract] ${label} mismatched context ${JSON.stringify(context)}: ` +
124
+ `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}.`
125
+ );
126
+ break;
127
+ }
128
+ }
129
+ return {
130
+ ok: errors.length === 0,
131
+ errors,
132
+ sampledContexts: contexts.length,
133
+ };
134
+ }
135
+
136
+ function checkGlobalLayerSemantics() {
137
+ const checks = [
138
+ {
139
+ id: 'inference.layerPattern.computeGlobalLayers.even',
140
+ actual: computeGlobalLayers({ type: 'alternating', globalPattern: 'even' }, 6),
141
+ expected: [0, 2, 4],
142
+ },
143
+ {
144
+ id: 'inference.layerPattern.computeGlobalLayers.odd',
145
+ actual: computeGlobalLayers({ type: 'alternating', globalPattern: 'odd' }, 6),
146
+ expected: [1, 3, 5],
147
+ },
148
+ {
149
+ id: 'inference.layerPattern.computeGlobalLayers.every_n_offset',
150
+ actual: computeGlobalLayers({ type: 'every_n', period: 6, offset: 5 }, 12),
151
+ expected: [5, 11],
152
+ },
153
+ {
154
+ id: 'inference.layerPattern.computeGlobalLayers.every_n_negative_offset',
155
+ actual: computeGlobalLayers({ type: 'every_n', period: 6, offset: -1 }, 12),
156
+ expected: [5, 11],
157
+ },
158
+ ];
159
+ const errors = [];
160
+ const results = [];
161
+ for (const entry of checks) {
162
+ const ok = JSON.stringify(entry.actual) === JSON.stringify(entry.expected);
163
+ results.push({ id: entry.id, ok });
164
+ if (!ok) {
165
+ errors.push(
166
+ `[LayerPatternContract] ${entry.id} expected ${JSON.stringify(entry.expected)}, got ${JSON.stringify(entry.actual)}.`
167
+ );
168
+ }
169
+ }
170
+ return {
171
+ checks: results,
172
+ errors,
173
+ };
174
+ }
175
+
176
+ export function buildLayerPatternContractArtifact(ruleGroup) {
177
+ const errors = [];
178
+ const checks = [];
179
+ const patternKindRules = ruleGroup?.patternKind;
180
+ const layerTypeRules = ruleGroup?.layerType;
181
+
182
+ const patternKindShape = checkRuleShape(patternKindRules, [
183
+ { match: { patternType: 'alternating', globalPattern: 'even' }, value: 'alternating_even' },
184
+ { match: { patternType: 'alternating', globalPattern: 'odd' }, value: 'alternating_odd' },
185
+ { match: { patternType: 'every_n' }, value: 'every_n' },
186
+ { match: {}, value: null },
187
+ ], 'patternKind');
188
+ errors.push(...patternKindShape.errors);
189
+ checks.push({ id: 'inference.layerPattern.patternKind.shape', ok: patternKindShape.ok });
190
+
191
+ const patternKindSemantics = Array.isArray(patternKindRules)
192
+ ? checkRuleSemantics(patternKindRules, enumeratePatternKindContexts(), expectedPatternKind, 'patternKind')
193
+ : { ok: false, errors: ['[LayerPatternContract] patternKind is unavailable for semantic check.'], sampledContexts: 0 };
194
+ errors.push(...patternKindSemantics.errors);
195
+ checks.push({ id: 'inference.layerPattern.patternKind.semantics', ok: patternKindSemantics.ok });
196
+
197
+ const layerTypeShape = checkRuleShape(layerTypeRules, [
198
+ { match: { patternKind: 'alternating_even', isEven: true }, value: 'full_attention' },
199
+ { match: { patternKind: 'alternating_even' }, value: 'sliding_attention' },
200
+ { match: { patternKind: 'alternating_odd', isEven: false }, value: 'full_attention' },
201
+ { match: { patternKind: 'alternating_odd' }, value: 'sliding_attention' },
202
+ { match: { patternKind: 'every_n', isStride: true }, value: 'full_attention' },
203
+ { match: { patternKind: 'every_n' }, value: 'sliding_attention' },
204
+ ], 'layerType');
205
+ errors.push(...layerTypeShape.errors);
206
+ checks.push({ id: 'inference.layerPattern.layerType.shape', ok: layerTypeShape.ok });
207
+
208
+ const layerTypeSemantics = Array.isArray(layerTypeRules)
209
+ ? checkRuleSemantics(layerTypeRules, enumerateLayerTypeContexts(), expectedLayerType, 'layerType')
210
+ : { ok: false, errors: ['[LayerPatternContract] layerType is unavailable for semantic check.'], sampledContexts: 0 };
211
+ errors.push(...layerTypeSemantics.errors);
212
+ checks.push({ id: 'inference.layerPattern.layerType.semantics', ok: layerTypeSemantics.ok });
213
+
214
+ const globalLayerSemantics = checkGlobalLayerSemantics();
215
+ errors.push(...globalLayerSemantics.errors);
216
+ checks.push(...globalLayerSemantics.checks);
217
+
218
+ return {
219
+ schemaVersion: 1,
220
+ source: 'doppler',
221
+ ok: errors.length === 0,
222
+ checks,
223
+ errors,
224
+ stats: {
225
+ patternKindRules: Array.isArray(patternKindRules) ? patternKindRules.length : 0,
226
+ layerTypeRules: Array.isArray(layerTypeRules) ? layerTypeRules.length : 0,
227
+ patternKindContexts: patternKindSemantics.sampledContexts,
228
+ layerTypeContexts: layerTypeSemantics.sampledContexts,
229
+ },
230
+ };
231
+ }
@@ -46,3 +46,31 @@ export declare function registerRuleGroup(
46
46
  group: RuleGroup,
47
47
  rules: Record<string, RuleSet>
48
48
  ): void;
49
+
50
+ export declare function getInferenceExecutionRulesContractArtifact(): {
51
+ schemaVersion: 1;
52
+ source: 'doppler';
53
+ ok: boolean;
54
+ checks: Array<{ id: string; ok: boolean }>;
55
+ errors: string[];
56
+ stats: {
57
+ decodeRecorderRules: number;
58
+ batchDecodeRules: number;
59
+ decodeRecorderContexts: number;
60
+ batchDecodeContexts: number;
61
+ };
62
+ };
63
+
64
+ export declare function getInferenceLayerPatternContractArtifact(): {
65
+ schemaVersion: 1;
66
+ source: 'doppler';
67
+ ok: boolean;
68
+ checks: Array<{ id: string; ok: boolean }>;
69
+ errors: string[];
70
+ stats: {
71
+ patternKindRules: number;
72
+ layerTypeRules: number;
73
+ patternKindContexts: number;
74
+ layerTypeContexts: number;
75
+ };
76
+ };
@@ -1,8 +1,11 @@
1
1
  import { selectByRules } from '../gpu/kernels/rule-matcher.js';
2
+ import { buildInferenceExecutionRulesContractArtifact } from './execution-rules-contract-check.js';
3
+ import { buildLayerPatternContractArtifact } from './layer-pattern-contract-check.js';
2
4
  import { loadJson } from '../utils/load-json.js';
3
5
 
4
6
  const attentionRules = await loadJson('./kernels/attention.rules.json', import.meta.url, 'Failed to load rules');
5
7
  const conv2dRules = await loadJson('./kernels/conv2d.rules.json', import.meta.url, 'Failed to load rules');
8
+ const depthwiseConv2dRules = await loadJson('./kernels/depthwise-conv2d.rules.json', import.meta.url, 'Failed to load rules');
6
9
  const dequantRules = await loadJson('./kernels/dequant.rules.json', import.meta.url, 'Failed to load rules');
7
10
  const energyRules = await loadJson('./kernels/energy.rules.json', import.meta.url, 'Failed to load rules');
8
11
  const fusedFfnRules = await loadJson('./kernels/fused-ffn.rules.json', import.meta.url, 'Failed to load rules');
@@ -10,6 +13,7 @@ const fusedMatmulResidualRules = await loadJson('./kernels/fused-matmul-residual
10
13
  const fusedMatmulRmsnormRules = await loadJson('./kernels/fused-matmul-rmsnorm.rules.json', import.meta.url, 'Failed to load rules');
11
14
  const gatherRules = await loadJson('./kernels/gather.rules.json', import.meta.url, 'Failed to load rules');
12
15
  const geluRules = await loadJson('./kernels/gelu.rules.json', import.meta.url, 'Failed to load rules');
16
+ const groupedPointwiseConv2dRules = await loadJson('./kernels/grouped-pointwise-conv2d.rules.json', import.meta.url, 'Failed to load rules');
13
17
  const groupnormRules = await loadJson('./kernels/groupnorm.rules.json', import.meta.url, 'Failed to load rules');
14
18
  const kvQuantizeRules = await loadJson('./kernels/kv_quantize.rules.json', import.meta.url, 'Failed to load rules');
15
19
  const layernormRules = await loadJson('./kernels/layernorm.rules.json', import.meta.url, 'Failed to load rules');
@@ -18,9 +22,12 @@ const kernelMoeRules = await loadJson('./kernels/moe.rules.json', import.meta.ur
18
22
  const kernelMoeGptOssRules = await loadJson('./kernels/moe.rules.gptoss.json', import.meta.url, 'Failed to load rules');
19
23
  const modulateRules = await loadJson('./kernels/modulate.rules.json', import.meta.url, 'Failed to load rules');
20
24
  const pixelShuffleRules = await loadJson('./kernels/pixel_shuffle.rules.json', import.meta.url, 'Failed to load rules');
25
+ const repeatChannelsRules = await loadJson('./kernels/repeat-channels.rules.json', import.meta.url, 'Failed to load rules');
26
+ const reluRules = await loadJson('./kernels/relu.rules.json', import.meta.url, 'Failed to load rules');
21
27
  const residualRules = await loadJson('./kernels/residual.rules.json', import.meta.url, 'Failed to load rules');
22
28
  const rmsnormRules = await loadJson('./kernels/rmsnorm.rules.json', import.meta.url, 'Failed to load rules');
23
29
  const ropeRules = await loadJson('./kernels/rope.rules.json', import.meta.url, 'Failed to load rules');
30
+ const sanaLinearAttentionRules = await loadJson('./kernels/sana-linear-attention.rules.json', import.meta.url, 'Failed to load rules');
24
31
  const sampleRules = await loadJson('./kernels/sample.rules.json', import.meta.url, 'Failed to load rules');
25
32
  const scaleRules = await loadJson('./kernels/scale.rules.json', import.meta.url, 'Failed to load rules');
26
33
  const siluRules = await loadJson('./kernels/silu.rules.json', import.meta.url, 'Failed to load rules');
@@ -46,6 +53,24 @@ const toolingCommandRuntimeRules = await loadJson(
46
53
  import.meta.url,
47
54
  'Failed to load rules'
48
55
  );
56
+ const INFERENCE_EXECUTION_RULES_CONTRACT_ARTIFACT = buildInferenceExecutionRulesContractArtifact(
57
+ inferenceExecutionRules
58
+ );
59
+ if (!INFERENCE_EXECUTION_RULES_CONTRACT_ARTIFACT.ok) {
60
+ throw new Error(
61
+ `RuleRegistry: inference.execution rules contract failed: ` +
62
+ `${INFERENCE_EXECUTION_RULES_CONTRACT_ARTIFACT.errors.join(' | ')}`
63
+ );
64
+ }
65
+ const INFERENCE_LAYER_PATTERN_CONTRACT_ARTIFACT = buildLayerPatternContractArtifact(
66
+ layerPatternRules
67
+ );
68
+ if (!INFERENCE_LAYER_PATTERN_CONTRACT_ARTIFACT.ok) {
69
+ throw new Error(
70
+ `RuleRegistry: inference.layerPattern rules contract failed: ` +
71
+ `${INFERENCE_LAYER_PATTERN_CONTRACT_ARTIFACT.errors.join(' | ')}`
72
+ );
73
+ }
49
74
 
50
75
  const RULE_SETS = {
51
76
  shared: {
@@ -54,6 +79,7 @@ const RULE_SETS = {
54
79
  kernels: {
55
80
  attention: attentionRules,
56
81
  conv2d: conv2dRules,
82
+ depthwiseConv2d: depthwiseConv2dRules,
57
83
  dequant: dequantRules,
58
84
  energy: energyRules,
59
85
  fusedFfn: fusedFfnRules,
@@ -61,6 +87,7 @@ const RULE_SETS = {
61
87
  fusedMatmulRmsnorm: fusedMatmulRmsnormRules,
62
88
  gather: gatherRules,
63
89
  gelu: geluRules,
90
+ groupedPointwiseConv2d: groupedPointwiseConv2dRules,
64
91
  groupnorm: groupnormRules,
65
92
  kv_quantize: kvQuantizeRules,
66
93
  layernorm: layernormRules,
@@ -69,9 +96,12 @@ const RULE_SETS = {
69
96
  moeGptoss: kernelMoeGptOssRules,
70
97
  modulate: modulateRules,
71
98
  pixel_shuffle: pixelShuffleRules,
99
+ repeatChannels: repeatChannelsRules,
100
+ relu: reluRules,
72
101
  residual: residualRules,
73
102
  rmsnorm: rmsnormRules,
74
103
  rope: ropeRules,
104
+ sanaLinearAttention: sanaLinearAttentionRules,
75
105
  sample: sampleRules,
76
106
  scale: scaleRules,
77
107
  silu: siluRules,
@@ -133,6 +163,14 @@ export function registerRuleGroup(domain, group, rules) {
133
163
  RULE_SETS[domain][group] = rules;
134
164
  }
135
165
 
166
+ export function getInferenceExecutionRulesContractArtifact() {
167
+ return INFERENCE_EXECUTION_RULES_CONTRACT_ARTIFACT;
168
+ }
169
+
170
+ export function getInferenceLayerPatternContractArtifact() {
171
+ return INFERENCE_LAYER_PATTERN_CONTRACT_ARTIFACT;
172
+ }
173
+
136
174
  function resolveRuleValue(value, context) {
137
175
  if (Array.isArray(value)) {
138
176
  return value.map((entry) => resolveRuleValue(entry, context));
@@ -0,0 +1,24 @@
1
+ export declare function extractTensorEntriesFromManifest(
2
+ manifest: Record<string, unknown>
3
+ ): Array<{
4
+ name: string;
5
+ dtype: unknown;
6
+ shape: unknown;
7
+ role: unknown;
8
+ layout: unknown;
9
+ }>;
10
+
11
+ export declare function resolveMaterializedManifestFromConversionConfig(
12
+ conversionConfigInput: Record<string, unknown>,
13
+ manifest: Record<string, unknown>
14
+ ): {
15
+ modelId: string;
16
+ modelType: string;
17
+ architecture: Record<string, unknown> | null;
18
+ inference: Record<string, unknown> | null;
19
+ };
20
+
21
+ export declare function inferConversionConfigModelId(
22
+ configPath: string,
23
+ conversionConfigInput: Record<string, unknown>
24
+ ): string;
@@ -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
+ }