@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,479 @@
1
+ import { selectByRules } from '../gpu/kernels/rule-matcher.js';
2
+
3
+ const SUPPORTED_DTYPES = new Set(['f16', 'f32']);
4
+ const DTYPE_RANK = Object.freeze({
5
+ f16: 1,
6
+ f32: 2,
7
+ });
8
+ const DTYPE_BYTES = Object.freeze({
9
+ f16: 2,
10
+ f32: 4,
11
+ });
12
+
13
+ function isPlainObject(value) {
14
+ return value != null && typeof value === 'object' && !Array.isArray(value);
15
+ }
16
+
17
+ function normalizeKernelPathContractDtype(value, label) {
18
+ const normalized = String(value ?? '').trim().toLowerCase();
19
+ if (!SUPPORTED_DTYPES.has(normalized)) {
20
+ throw new Error(
21
+ `kernel-path contract: ${label} must be one of ${[...SUPPORTED_DTYPES].join(', ')}.`
22
+ );
23
+ }
24
+ return normalized;
25
+ }
26
+
27
+ function normalizeRegistryEntry(entry, index) {
28
+ if (!isPlainObject(entry)) {
29
+ throw new Error(`kernel-path contract: entries[${index}] must be an object.`);
30
+ }
31
+ const id = String(entry.id ?? '').trim();
32
+ if (!id) {
33
+ throw new Error(`kernel-path contract: entries[${index}].id is required.`);
34
+ }
35
+ const aliasOf = typeof entry.aliasOf === 'string' && entry.aliasOf.trim() !== ''
36
+ ? entry.aliasOf.trim()
37
+ : null;
38
+ const hasFile = typeof entry.file === 'string' && entry.file.trim() !== '';
39
+ if (!aliasOf && !hasFile) {
40
+ throw new Error(
41
+ `kernel-path contract: entries[${index}] must include file or aliasOf.`
42
+ );
43
+ }
44
+ return {
45
+ id,
46
+ aliasOf,
47
+ hasFile,
48
+ };
49
+ }
50
+
51
+ function normalizeFallbackMapping(mapping, index) {
52
+ if (!isPlainObject(mapping)) {
53
+ throw new Error(`kernel-path contract: fallbackMappings[${index}] must be an object.`);
54
+ }
55
+ const primaryKernelPathId = String(mapping.primaryKernelPathId ?? '').trim();
56
+ const fallbackKernelPathId = String(mapping.fallbackKernelPathId ?? '').trim();
57
+ if (!primaryKernelPathId) {
58
+ throw new Error(`kernel-path contract: fallbackMappings[${index}].primaryKernelPathId is required.`);
59
+ }
60
+ if (!fallbackKernelPathId) {
61
+ throw new Error(`kernel-path contract: fallbackMappings[${index}].fallbackKernelPathId is required.`);
62
+ }
63
+ return {
64
+ primaryKernelPathId,
65
+ fallbackKernelPathId,
66
+ primaryActivationDtype: normalizeKernelPathContractDtype(
67
+ mapping.primaryActivationDtype,
68
+ `fallbackMappings[${index}].primaryActivationDtype`
69
+ ),
70
+ fallbackActivationDtype: mapping.fallbackActivationDtype == null
71
+ ? null
72
+ : normalizeKernelPathContractDtype(
73
+ mapping.fallbackActivationDtype,
74
+ `fallbackMappings[${index}].fallbackActivationDtype`
75
+ ),
76
+ };
77
+ }
78
+
79
+ function normalizeFallbackRule(rule, index) {
80
+ if (!isPlainObject(rule)) {
81
+ throw new Error(`kernel-path contract: fallbackRules[${index}] must be an object.`);
82
+ }
83
+ const match = isPlainObject(rule.match) ? rule.match : {};
84
+ const rawKernelPathId = match.kernelPathId;
85
+ const matchKernelPathId = typeof rawKernelPathId === 'string' && rawKernelPathId.trim() !== ''
86
+ ? rawKernelPathId.trim()
87
+ : null;
88
+ const value = rule.value == null ? null : String(rule.value).trim();
89
+ if (value === '') {
90
+ throw new Error(`kernel-path contract: fallbackRules[${index}].value must be null or a non-empty string.`);
91
+ }
92
+ return {
93
+ matchKernelPathId,
94
+ value,
95
+ isDefault: Object.keys(match).length === 0,
96
+ };
97
+ }
98
+
99
+ function normalizeAutoSelectRule(rule, index) {
100
+ if (!isPlainObject(rule)) {
101
+ throw new Error(`kernel-path contract: autoSelectRules[${index}] must be an object.`);
102
+ }
103
+ const match = isPlainObject(rule.match) ? rule.match : {};
104
+ const rawKernelPathRef = match.kernelPathRef;
105
+ const matchKernelPathRef = typeof rawKernelPathRef === 'string' && rawKernelPathRef.trim() !== ''
106
+ ? rawKernelPathRef.trim()
107
+ : null;
108
+ const allowCapabilityAutoSelection = typeof match.allowCapabilityAutoSelection === 'boolean'
109
+ ? match.allowCapabilityAutoSelection
110
+ : null;
111
+ const hasSubgroups = typeof match.hasSubgroups === 'boolean'
112
+ ? match.hasSubgroups
113
+ : null;
114
+ const value = rule.value;
115
+ if (typeof value === 'string' && value.trim() !== '') {
116
+ return {
117
+ matchKernelPathRef,
118
+ allowCapabilityAutoSelection,
119
+ hasSubgroups,
120
+ valueKind: 'string',
121
+ value: value.trim(),
122
+ isDefault: Object.keys(match).length === 0,
123
+ };
124
+ }
125
+ if (isPlainObject(value) && Object.keys(value).length === 1 && typeof value.context === 'string') {
126
+ return {
127
+ matchKernelPathRef,
128
+ allowCapabilityAutoSelection,
129
+ hasSubgroups,
130
+ valueKind: 'context',
131
+ value: value.context.trim(),
132
+ isDefault: Object.keys(match).length === 0,
133
+ };
134
+ }
135
+ throw new Error(
136
+ `kernel-path contract: autoSelectRules[${index}].value must be a non-empty string ` +
137
+ 'or a { context: ... } directive.'
138
+ );
139
+ }
140
+
141
+ function findAliasCycles(entriesById) {
142
+ const visited = new Set();
143
+ const visiting = new Set();
144
+ const stack = [];
145
+ const cycles = [];
146
+
147
+ function walk(id) {
148
+ if (visited.has(id)) {
149
+ return;
150
+ }
151
+ if (visiting.has(id)) {
152
+ const cycleStart = stack.indexOf(id);
153
+ const cycle = cycleStart >= 0 ? [...stack.slice(cycleStart), id] : [id, id];
154
+ cycles.push(cycle);
155
+ return;
156
+ }
157
+ visiting.add(id);
158
+ stack.push(id);
159
+ const nextId = entriesById.get(id)?.aliasOf ?? null;
160
+ if (nextId) {
161
+ walk(nextId);
162
+ }
163
+ stack.pop();
164
+ visiting.delete(id);
165
+ visited.add(id);
166
+ }
167
+
168
+ for (const id of entriesById.keys()) {
169
+ walk(id);
170
+ }
171
+
172
+ return cycles;
173
+ }
174
+
175
+ export function extractKernelPathContractFacts(input, options = {}) {
176
+ const registryId = String(options.registryId ?? input?.registryId ?? 'kernel-path-registry').trim()
177
+ || 'kernel-path-registry';
178
+ const rawEntries = Array.isArray(input)
179
+ ? input
180
+ : Array.isArray(input?.entries)
181
+ ? input.entries
182
+ : null;
183
+ if (!rawEntries) {
184
+ throw new Error('kernel-path contract: entries must be an array or an object with entries.');
185
+ }
186
+
187
+ const entries = rawEntries.map(normalizeRegistryEntry);
188
+ const seenIds = new Set();
189
+ for (const entry of entries) {
190
+ if (seenIds.has(entry.id)) {
191
+ throw new Error(`kernel-path contract: duplicate registry entry id "${entry.id}".`);
192
+ }
193
+ seenIds.add(entry.id);
194
+ }
195
+
196
+ const rawFallbackMappings = Array.isArray(input?.fallbackMappings) ? input.fallbackMappings : [];
197
+ const fallbackMappings = rawFallbackMappings.map(normalizeFallbackMapping);
198
+ const rawFallbackRules = Array.isArray(input?.fallbackRules) ? input.fallbackRules : [];
199
+ const fallbackRules = rawFallbackRules.map(normalizeFallbackRule);
200
+ const rawAutoSelectRules = Array.isArray(input?.autoSelectRules) ? input.autoSelectRules : [];
201
+ const autoSelectRules = rawAutoSelectRules.map(normalizeAutoSelectRule);
202
+
203
+ return {
204
+ registryId,
205
+ entries,
206
+ fallbackMappings,
207
+ fallbackRules,
208
+ autoSelectRules,
209
+ };
210
+ }
211
+
212
+ export function validateKernelPathContractFacts(facts) {
213
+ const errors = [];
214
+ const checks = [];
215
+ const registryId = String(facts?.registryId ?? 'kernel-path-registry');
216
+ const entries = Array.isArray(facts?.entries) ? facts.entries : [];
217
+ const fallbackMappings = Array.isArray(facts?.fallbackMappings) ? facts.fallbackMappings : [];
218
+ const fallbackRules = Array.isArray(facts?.fallbackRules) ? facts.fallbackRules : [];
219
+ const autoSelectRules = Array.isArray(facts?.autoSelectRules) ? facts.autoSelectRules : [];
220
+ const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
221
+
222
+ const missingAliasTargets = entries.filter((entry) => entry.aliasOf && !entriesById.has(entry.aliasOf));
223
+ for (const entry of missingAliasTargets) {
224
+ errors.push(
225
+ `[KernelPathContract] registry entry "${entry.id}" aliases missing target "${entry.aliasOf}".`
226
+ );
227
+ }
228
+ checks.push({
229
+ id: `${registryId}.aliasTargets`,
230
+ ok: missingAliasTargets.length === 0,
231
+ });
232
+
233
+ const aliasCycles = findAliasCycles(entriesById);
234
+ for (const cycle of aliasCycles) {
235
+ errors.push(`[KernelPathContract] alias cycle detected: ${cycle.join(' -> ')}.`);
236
+ }
237
+ checks.push({
238
+ id: `${registryId}.aliasCycles`,
239
+ ok: aliasCycles.length === 0,
240
+ });
241
+
242
+ let fallbackTargetErrors = 0;
243
+ let fallbackDtypeErrors = 0;
244
+ for (const mapping of fallbackMappings) {
245
+ if (!entriesById.has(mapping.primaryKernelPathId)) {
246
+ fallbackTargetErrors += 1;
247
+ errors.push(
248
+ `[KernelPathContract] finiteness fallback mapping references unknown primary kernel path ` +
249
+ `"${mapping.primaryKernelPathId}".`
250
+ );
251
+ continue;
252
+ }
253
+ if (!entriesById.has(mapping.fallbackKernelPathId) || mapping.fallbackActivationDtype == null) {
254
+ fallbackTargetErrors += 1;
255
+ errors.push(
256
+ `[KernelPathContract] finiteness fallback mapping references unknown fallback kernel path ` +
257
+ `"${mapping.fallbackKernelPathId}" for "${mapping.primaryKernelPathId}".`
258
+ );
259
+ continue;
260
+ }
261
+ if (DTYPE_RANK[mapping.fallbackActivationDtype] < DTYPE_RANK[mapping.primaryActivationDtype]) {
262
+ fallbackDtypeErrors += 1;
263
+ errors.push(
264
+ `[KernelPathContract] finiteness fallback "${mapping.primaryKernelPathId}" -> ` +
265
+ `"${mapping.fallbackKernelPathId}" narrows activation dtype ` +
266
+ `${mapping.primaryActivationDtype} -> ${mapping.fallbackActivationDtype}.`
267
+ );
268
+ continue;
269
+ }
270
+ if (DTYPE_BYTES[mapping.fallbackActivationDtype] < DTYPE_BYTES[mapping.primaryActivationDtype]) {
271
+ fallbackDtypeErrors += 1;
272
+ errors.push(
273
+ `[KernelPathContract] finiteness fallback "${mapping.primaryKernelPathId}" -> ` +
274
+ `"${mapping.fallbackKernelPathId}" reduces bytes per element ` +
275
+ `${mapping.primaryActivationDtype} -> ${mapping.fallbackActivationDtype}.`
276
+ );
277
+ }
278
+ }
279
+
280
+ checks.push({
281
+ id: `${registryId}.finitenessFallbackTargets`,
282
+ ok: fallbackTargetErrors === 0,
283
+ });
284
+ checks.push({
285
+ id: `${registryId}.finitenessFallbackDtypes`,
286
+ ok: fallbackDtypeErrors === 0,
287
+ });
288
+
289
+ let fallbackRuleShapeErrors = 0;
290
+ let fallbackRuleCoverageErrors = 0;
291
+ let fallbackRuleTargetErrors = 0;
292
+ if (fallbackRules.length > 0) {
293
+ const defaultRules = fallbackRules.filter((rule) => rule.isDefault);
294
+ if (defaultRules.length !== 1 || defaultRules[0].value !== null || fallbackRules[fallbackRules.length - 1] !== defaultRules[0]) {
295
+ fallbackRuleShapeErrors += 1;
296
+ errors.push(
297
+ '[KernelPathContract] finiteness fallback rules must end with exactly one default `{ match: {}, value: null }` rule.'
298
+ );
299
+ }
300
+ const seenRuleIds = new Set();
301
+ for (const rule of fallbackRules) {
302
+ if (rule.isDefault) continue;
303
+ if (!rule.matchKernelPathId) {
304
+ fallbackRuleShapeErrors += 1;
305
+ errors.push('[KernelPathContract] non-default finiteness fallback rules must match on kernelPathId.');
306
+ continue;
307
+ }
308
+ if (seenRuleIds.has(rule.matchKernelPathId)) {
309
+ fallbackRuleShapeErrors += 1;
310
+ errors.push(
311
+ `[KernelPathContract] duplicate finiteness fallback rule for "${rule.matchKernelPathId}".`
312
+ );
313
+ }
314
+ seenRuleIds.add(rule.matchKernelPathId);
315
+ if (!entriesById.has(rule.matchKernelPathId)) {
316
+ fallbackRuleTargetErrors += 1;
317
+ errors.push(
318
+ `[KernelPathContract] finiteness fallback rule references unknown primary kernel path "${rule.matchKernelPathId}".`
319
+ );
320
+ }
321
+ if (rule.value != null && !entriesById.has(rule.value)) {
322
+ fallbackRuleTargetErrors += 1;
323
+ errors.push(
324
+ `[KernelPathContract] finiteness fallback rule references unknown fallback kernel path "${rule.value}" for "${rule.matchKernelPathId}".`
325
+ );
326
+ }
327
+ }
328
+ for (const entry of entries) {
329
+ const selected = selectByRules(
330
+ fallbackRules.map((rule) => ({
331
+ match: rule.isDefault ? {} : { kernelPathId: rule.matchKernelPathId },
332
+ value: rule.value,
333
+ })),
334
+ { kernelPathId: entry.id }
335
+ );
336
+ if (!(selected === null || (typeof selected === 'string' && selected.length > 0))) {
337
+ fallbackRuleCoverageErrors += 1;
338
+ errors.push(
339
+ `[KernelPathContract] finiteness fallback rules did not yield a valid result for "${entry.id}".`
340
+ );
341
+ }
342
+ }
343
+ }
344
+ checks.push({
345
+ id: `${registryId}.finitenessFallbackRuleShape`,
346
+ ok: fallbackRuleShapeErrors === 0,
347
+ });
348
+ checks.push({
349
+ id: `${registryId}.finitenessFallbackRuleTargets`,
350
+ ok: fallbackRuleTargetErrors === 0,
351
+ });
352
+ checks.push({
353
+ id: `${registryId}.finitenessFallbackRuleCoverage`,
354
+ ok: fallbackRuleCoverageErrors === 0,
355
+ });
356
+
357
+ let autoSelectShapeErrors = 0;
358
+ let autoSelectTargetErrors = 0;
359
+ let autoSelectCoverageErrors = 0;
360
+ if (autoSelectRules.length > 0) {
361
+ const defaultRules = autoSelectRules.filter((rule) => rule.isDefault);
362
+ if (
363
+ defaultRules.length !== 1
364
+ || defaultRules[0].valueKind !== 'context'
365
+ || defaultRules[0].value !== 'kernelPathRef'
366
+ || autoSelectRules[autoSelectRules.length - 1] !== defaultRules[0]
367
+ ) {
368
+ autoSelectShapeErrors += 1;
369
+ errors.push(
370
+ '[KernelPathContract] autoSelect rules must end with exactly one default `{ match: {}, value: { context: "kernelPathRef" } }` rule.'
371
+ );
372
+ }
373
+ for (const rule of autoSelectRules) {
374
+ if (rule.isDefault) continue;
375
+ if (rule.allowCapabilityAutoSelection !== true) {
376
+ autoSelectShapeErrors += 1;
377
+ errors.push('[KernelPathContract] non-default autoSelect rules must require allowCapabilityAutoSelection=true.');
378
+ }
379
+ if (!rule.matchKernelPathRef) {
380
+ autoSelectShapeErrors += 1;
381
+ errors.push('[KernelPathContract] non-default autoSelect rules must match on kernelPathRef.');
382
+ }
383
+ if (rule.hasSubgroups == null) {
384
+ autoSelectShapeErrors += 1;
385
+ errors.push('[KernelPathContract] non-default autoSelect rules must match on hasSubgroups.');
386
+ }
387
+ if (rule.valueKind === 'context') {
388
+ autoSelectShapeErrors += 1;
389
+ errors.push('[KernelPathContract] only the default autoSelect rule may use a context directive.');
390
+ }
391
+ if (rule.matchKernelPathRef && !entriesById.has(rule.matchKernelPathRef)) {
392
+ autoSelectTargetErrors += 1;
393
+ errors.push(
394
+ `[KernelPathContract] autoSelect rule references unknown kernelPathRef "${rule.matchKernelPathRef}".`
395
+ );
396
+ }
397
+ if (rule.valueKind === 'string' && !entriesById.has(rule.value)) {
398
+ autoSelectTargetErrors += 1;
399
+ errors.push(
400
+ `[KernelPathContract] autoSelect rule remaps to unknown kernel path "${rule.value}".`
401
+ );
402
+ }
403
+ }
404
+ const resolvedAutoSelectRules = autoSelectRules.map((rule) => ({
405
+ match: rule.isDefault
406
+ ? {}
407
+ : {
408
+ allowCapabilityAutoSelection: rule.allowCapabilityAutoSelection,
409
+ hasSubgroups: rule.hasSubgroups,
410
+ kernelPathRef: rule.matchKernelPathRef,
411
+ },
412
+ value: rule.valueKind === 'context'
413
+ ? { context: rule.value }
414
+ : rule.value,
415
+ }));
416
+ for (const entry of entries) {
417
+ for (const allowCapabilityAutoSelection of [true, false]) {
418
+ for (const hasSubgroups of [true, false]) {
419
+ const selected = selectByRules(resolvedAutoSelectRules, {
420
+ kernelPathRef: entry.id,
421
+ allowCapabilityAutoSelection,
422
+ hasSubgroups,
423
+ });
424
+ const resolved = isPlainObject(selected) && selected.context === 'kernelPathRef'
425
+ ? entry.id
426
+ : selected;
427
+ if (typeof resolved !== 'string' || !resolved.length || !entriesById.has(resolved)) {
428
+ autoSelectCoverageErrors += 1;
429
+ errors.push(
430
+ `[KernelPathContract] autoSelect rules did not yield a valid kernel path for ` +
431
+ `"${entry.id}" (allowCapabilityAutoSelection=${allowCapabilityAutoSelection}, hasSubgroups=${hasSubgroups}).`
432
+ );
433
+ break;
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
439
+ checks.push({
440
+ id: `${registryId}.autoSelectRuleShape`,
441
+ ok: autoSelectShapeErrors === 0,
442
+ });
443
+ checks.push({
444
+ id: `${registryId}.autoSelectRuleTargets`,
445
+ ok: autoSelectTargetErrors === 0,
446
+ });
447
+ checks.push({
448
+ id: `${registryId}.autoSelectRuleCoverage`,
449
+ ok: autoSelectCoverageErrors === 0,
450
+ });
451
+
452
+ return {
453
+ ok: errors.length === 0,
454
+ errors,
455
+ checks,
456
+ };
457
+ }
458
+
459
+ export function buildKernelPathContractArtifact(input, options = {}) {
460
+ const facts = extractKernelPathContractFacts(input, options);
461
+ const evaluation = validateKernelPathContractFacts(facts);
462
+ const aliasEntries = facts.entries.filter((entry) => entry.aliasOf != null).length;
463
+
464
+ return {
465
+ schemaVersion: 1,
466
+ source: 'doppler',
467
+ ok: evaluation.ok,
468
+ checks: evaluation.checks,
469
+ errors: evaluation.errors,
470
+ stats: {
471
+ totalEntries: facts.entries.length,
472
+ aliasEntries,
473
+ canonicalEntries: facts.entries.length - aliasEntries,
474
+ fallbackMappings: facts.fallbackMappings.length,
475
+ fallbackRules: facts.fallbackRules?.length ?? 0,
476
+ autoSelectRules: facts.autoSelectRules?.length ?? 0,
477
+ },
478
+ };
479
+ }
@@ -22,6 +22,22 @@ export function getKernelPath(id: string): KernelPathSchema | null;
22
22
  */
23
23
  export function listKernelPaths(): string[];
24
24
 
25
+ export function getKernelPathContractArtifact(): {
26
+ schemaVersion: 1;
27
+ source: 'doppler';
28
+ ok: boolean;
29
+ checks: Array<{ id: string; ok: boolean }>;
30
+ errors: string[];
31
+ stats: {
32
+ totalEntries: number;
33
+ aliasEntries: number;
34
+ canonicalEntries: number;
35
+ fallbackMappings: number;
36
+ fallbackRules: number;
37
+ autoSelectRules: number;
38
+ };
39
+ };
40
+
25
41
  /**
26
42
  * Resolve a kernel path reference to a full schema.
27
43
  */
@@ -1,6 +1,8 @@
1
1
  import { DEFAULT_ENTRY } from './schema/kernel-path.schema.js';
2
2
  import { KERNEL_CONFIGS } from '../gpu/kernels/utils.js';
3
+ import { selectByRules } from '../gpu/kernels/rule-matcher.js';
3
4
  import { loadJson } from '../utils/load-json.js';
5
+ import { buildKernelPathContractArtifact } from './kernel-path-contract-check.js';
4
6
 
5
7
  // =============================================================================
6
8
  // Built-in Kernel Paths (imported at build time)
@@ -77,6 +79,11 @@ const KERNEL_PATH_REGISTRY_INDEX = new Map(
77
79
  );
78
80
 
79
81
  const KERNEL_PATH_REGISTRY = Object.create(null);
82
+ const KERNEL_PATH_RULES = await loadJson(
83
+ '../rules/inference/kernel-path.rules.json',
84
+ import.meta.url,
85
+ 'Failed to load kernel path rules'
86
+ );
80
87
 
81
88
  const resolveKernelPathConfig = (id, chain = new Set()) => {
82
89
  if (KERNEL_PATH_REGISTRY[id] !== undefined) {
@@ -117,6 +124,42 @@ for (const entry of KERNEL_PATH_REGISTRY_ENTRIES) {
117
124
  resolveKernelPathConfig(entry.id);
118
125
  }
119
126
 
127
+ const KERNEL_PATH_FINITENESS_FALLBACK_MAPPINGS = KERNEL_PATH_REGISTRY_ENTRIES
128
+ .map((entry) => {
129
+ const fallbackKernelPathId = selectByRules(
130
+ Array.isArray(KERNEL_PATH_RULES?.finitenessFallback) ? KERNEL_PATH_RULES.finitenessFallback : [],
131
+ { kernelPathId: entry.id }
132
+ );
133
+ if (typeof fallbackKernelPathId !== 'string' || fallbackKernelPathId.length === 0) {
134
+ return null;
135
+ }
136
+ return {
137
+ primaryKernelPathId: entry.id,
138
+ fallbackKernelPathId,
139
+ primaryActivationDtype: KERNEL_PATH_REGISTRY[entry.id]?.activationDtype ?? null,
140
+ fallbackActivationDtype: KERNEL_PATH_REGISTRY[fallbackKernelPathId]?.activationDtype ?? null,
141
+ };
142
+ })
143
+ .filter(Boolean);
144
+
145
+ const KERNEL_PATH_CONTRACT_ARTIFACT = buildKernelPathContractArtifact(
146
+ {
147
+ registryId: 'builtin-kernel-paths',
148
+ entries: KERNEL_PATH_REGISTRY_ENTRIES,
149
+ fallbackMappings: KERNEL_PATH_FINITENESS_FALLBACK_MAPPINGS,
150
+ fallbackRules: Array.isArray(KERNEL_PATH_RULES?.finitenessFallback)
151
+ ? KERNEL_PATH_RULES.finitenessFallback
152
+ : [],
153
+ autoSelectRules: Array.isArray(KERNEL_PATH_RULES?.autoSelect)
154
+ ? KERNEL_PATH_RULES.autoSelect
155
+ : [],
156
+ }
157
+ );
158
+
159
+ if (!KERNEL_PATH_CONTRACT_ARTIFACT.ok) {
160
+ throw new Error(KERNEL_PATH_CONTRACT_ARTIFACT.errors[0]);
161
+ }
162
+
120
163
  // =============================================================================
121
164
  // Public API
122
165
  // =============================================================================
@@ -129,6 +172,17 @@ export function listKernelPaths() {
129
172
  return Object.keys(KERNEL_PATH_REGISTRY);
130
173
  }
131
174
 
175
+ export function getKernelPathContractArtifact() {
176
+ return {
177
+ schemaVersion: KERNEL_PATH_CONTRACT_ARTIFACT.schemaVersion,
178
+ source: KERNEL_PATH_CONTRACT_ARTIFACT.source,
179
+ ok: KERNEL_PATH_CONTRACT_ARTIFACT.ok,
180
+ checks: KERNEL_PATH_CONTRACT_ARTIFACT.checks.map((entry) => ({ ...entry })),
181
+ errors: [...KERNEL_PATH_CONTRACT_ARTIFACT.errors],
182
+ stats: { ...KERNEL_PATH_CONTRACT_ARTIFACT.stats },
183
+ };
184
+ }
185
+
132
186
  export function resolveKernelPath(ref) {
133
187
  if (typeof ref === 'string') {
134
188
  const path = getKernelPath(ref);
@@ -57,6 +57,8 @@ export const KERNEL_REF_CONTENT_DIGESTS = Object.freeze({
57
57
  "conv2d_f16.wgsl#main": "aa139e9f0270873acbc1c4b3cbacff4d224cae7247b520ec129a4f068eb6ed59",
58
58
  "conv2d.wgsl#main": "484a676692d2b8097daeefe42e2296a1f8b3ef11abfd7b41df6cdcdf16b7a8fd",
59
59
  "cross_entropy_loss.wgsl#main": "5a48087bdec94184432c90ce5b345e1eadbdfcb13b9793ecee8052bc7392239c",
60
+ "depthwise_conv2d_f16.wgsl#main": "d5d8d195b1449e39715340af4a0759da4b44b54f6a3cfbdfa6abe743b0f1d002",
61
+ "depthwise_conv2d.wgsl#main": "e5da160f505e18508619b78ba30f9bde0c84689a166df06cb59ef0e6591c6faf",
60
62
  "dequant_f16_out_vec4.wgsl#main_vec4": "61c20e6c71c1c8421b4ec202dbd26292a6300587bd44c314f2a6c6d9d9442c3a",
61
63
  "dequant_f16_out.wgsl#main": "94d61843d56f9a3bbc6b7c2b95dc6ecbba3f6a262b2c4086a076f69a8c38ccae",
62
64
  "dequant_f16_rowwise.wgsl#main": "f5bf7cef950b52d65cee6121dbaa176244d3221045b3b6386b3be47f23ce17dc",
@@ -116,6 +118,8 @@ export const KERNEL_REF_CONTENT_DIGESTS = Object.freeze({
116
118
  "gelu.wgsl#main": "a9007ea08aaff98f9be08f1e0490a6bcf252883eac5513de876ab9ce918865e6",
117
119
  "gptoss_mxfp4_expert_fused.wgsl#main_expert": "3159e8cd81da13f909cf905e6d35307fefe1dcbbdf1b2b8e8ff0ce923bd71180",
118
120
  "gptoss_router_topk.wgsl#softmax_topk": "86e4ea709c0c0084d09c6a4cd07710dc14f380e03f91b8ed9ec871b310be49f1",
121
+ "grouped_pointwise_conv2d_f16.wgsl#main": "11bcaefc5929b2e3c1ba338ebea6a28d2cac26553be8b00f51bfddbabf513be7",
122
+ "grouped_pointwise_conv2d.wgsl#main": "c0d5cdec0743b4ee337a8df95bda442e617c1678e3d1b6e20ec692d500ede50d",
119
123
  "groupnorm_apply_f16.wgsl#main": "cfd850b87944ac1c03ba7bd98136db556dadd8a70611e351d82d297299a7cd02",
120
124
  "groupnorm_apply.wgsl#main": "b09b8f2f57dcdfa1a0366daa30d3910feb134204652c711d2ba564e566b5a334",
121
125
  "groupnorm_stats_f16.wgsl#main": "fb76f78ce668ea8459110335698fe4b09a2425fc71deed3bab67efd7641c3199",
@@ -153,6 +157,10 @@ export const KERNEL_REF_CONTENT_DIGESTS = Object.freeze({
153
157
  "moe_offsets.wgsl#build_offsets": "3ea004145fa234659408cdeb0d4d802adff1037c9c5c03af146b3734cc69dd27",
154
158
  "pixel_shuffle_f16.wgsl#main": "57903a9c19cecc56371b2198402745127115680d266c3ce609201be9119aa359",
155
159
  "pixel_shuffle.wgsl#main": "845b88700b1b46d18cde6f2ec11bb89512c90d7e148763e74ce2a4173fd99b21",
160
+ "relu_f16.wgsl#main": "fc6134aabe43081b42ce8507d8f374092d0f2e03316aa42c25dd50229dc0ee40",
161
+ "relu.wgsl#main": "ca2c9bfa0acb9ece3b7e67de5209e00e553602b3917d23aca10338c1e6f01e27",
162
+ "repeat_channels_f16.wgsl#main": "e7e4d9164752e782d482db40256d0d86d96f784aa7debdb72faf3261b9bdd737",
163
+ "repeat_channels.wgsl#main": "ad0e34925c8c1173b9f0d92fa6e3808d039f82b3d9ad943b0a75b213ee1776e5",
156
164
  "residual_f16_vec4.wgsl#add_vec4": "30e9226fb6636e2f01e65b1dc8e93c8e849a87acec6215342fc114996da1ed41",
157
165
  "residual_f16.wgsl#main": "d392433f3065d1caf68b033219f4ffacf022dc1f90fc3cf3fd620e4ba49f3219",
158
166
  "residual_vec4.wgsl#add_vec4": "ef011d1683e62887db712da563e783d12fdc80c152955661137d2dca612d7d6a",
@@ -186,6 +194,10 @@ export const KERNEL_REF_CONTENT_DIGESTS = Object.freeze({
186
194
  "sample.wgsl#find_topk_phase2": "940b216e605d22096da5aca65950a8030866fc5a39e7fdf484d69a832de1b63a",
187
195
  "sample.wgsl#sample_single_pass": "4412357e84113ee2f1bc0dc8bf89e314c2ab482c89c14ca016ea9949d16a9d0c",
188
196
  "sample.wgsl#softmax_and_sample": "7172c60e76430fbe130e530e3564b569b45eccf193987b32d6f52bd6bbcc9f08",
197
+ "sana_linear_attention_apply_f16.wgsl#main": "e47366b94d40c4388e631b5bf93f8d61ef4e52cc65ffcd3b08d9d170616bb138",
198
+ "sana_linear_attention_apply.wgsl#main": "59cad7974c644fd910af776ad85a9a2c43c00492d4d1152fdc8373ecbb8bba18",
199
+ "sana_linear_attention_summary_f16.wgsl#main": "e3c040bb6469d37fc78eb22c1cc3e0456301607e461bbcdf5365a583c5d260d2",
200
+ "sana_linear_attention_summary.wgsl#main": "20c7ecdbcd1c73c0f9937c3cdac07b4b6edfe8618bf6f66281806343fd41b122",
189
201
  "scale.wgsl#main": "44ec481452b586307957163e3d65c9d02561d3f2f3db633f906f5488b1ea1ca4",
190
202
  "scale.wgsl#main_inplace": "020824c7118a59c461ce81f1c2cd01b7c2a3f1aab326392b7d48d4448a0c2ed1",
191
203
  "scatter_add_dynamic_f16_weights.wgsl#scatter_add_dynamic": "42799e745bc445b199b1cbc384bc12bb9372ed1599af3260a803cefc8dd35497",