@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 { validateRequiredInferenceFields } from '../inference/pipelines/text/config.js';
2
+
3
+ function cloneJson(value) {
4
+ if (typeof structuredClone === 'function') {
5
+ return structuredClone(value);
6
+ }
7
+ return JSON.parse(JSON.stringify(value));
8
+ }
9
+
10
+ function setPath(root, path, value) {
11
+ let current = root;
12
+ for (let i = 0; i < path.length - 1; i += 1) {
13
+ current = current[path[i]];
14
+ }
15
+ current[path[path.length - 1]] = value;
16
+ }
17
+
18
+ function deletePath(root, path) {
19
+ let current = root;
20
+ for (let i = 0; i < path.length - 1; i += 1) {
21
+ current = current[path[i]];
22
+ }
23
+ delete current[path[path.length - 1]];
24
+ }
25
+
26
+ function createValidInferenceFixture() {
27
+ return {
28
+ attention: {
29
+ queryPreAttnScalar: 256,
30
+ queryKeyNorm: true,
31
+ attentionBias: false,
32
+ causal: true,
33
+ slidingWindow: null,
34
+ attnLogitSoftcapping: null,
35
+ },
36
+ normalization: {
37
+ rmsNormWeightOffset: true,
38
+ rmsNormEps: 1e-6,
39
+ postAttentionNorm: true,
40
+ preFeedforwardNorm: true,
41
+ postFeedforwardNorm: true,
42
+ },
43
+ ffn: {
44
+ activation: 'gelu',
45
+ gatedActivation: true,
46
+ swigluLimit: null,
47
+ },
48
+ rope: {
49
+ ropeTheta: 1000000,
50
+ ropeScalingFactor: 1.0,
51
+ ropeScalingType: null,
52
+ ropeLocalTheta: null,
53
+ yarnBetaFast: null,
54
+ yarnBetaSlow: null,
55
+ yarnOriginalMaxPos: null,
56
+ },
57
+ output: {
58
+ tieWordEmbeddings: true,
59
+ scaleEmbeddings: true,
60
+ embeddingTranspose: false,
61
+ finalLogitSoftcapping: null,
62
+ embeddingVocabSize: null,
63
+ },
64
+ layerPattern: {
65
+ type: 'every_n',
66
+ globalPattern: null,
67
+ period: 6,
68
+ offset: 0,
69
+ },
70
+ chatTemplate: {
71
+ type: null,
72
+ enabled: true,
73
+ },
74
+ defaultKernelPath: 'unit-test',
75
+ };
76
+ }
77
+
78
+ const FIELD_CASES = Object.freeze([
79
+ { kind: 'nonNullable', path: ['attention', 'queryPreAttnScalar'], message: 'attention.queryPreAttnScalar is required' },
80
+ { kind: 'nonNullable', path: ['attention', 'queryKeyNorm'], message: 'attention.queryKeyNorm is required' },
81
+ { kind: 'nonNullable', path: ['attention', 'attentionBias'], message: 'attention.attentionBias is required' },
82
+ { kind: 'nonNullable', path: ['attention', 'causal'], message: 'attention.causal is required' },
83
+ { kind: 'nullable', path: ['attention', 'slidingWindow'], message: 'attention.slidingWindow must be explicitly set' },
84
+ { kind: 'nullable', path: ['attention', 'attnLogitSoftcapping'], message: 'attention.attnLogitSoftcapping must be explicitly set' },
85
+ { kind: 'nonNullable', path: ['normalization', 'rmsNormWeightOffset'], message: 'normalization.rmsNormWeightOffset is required' },
86
+ { kind: 'nonNullable', path: ['normalization', 'rmsNormEps'], message: 'normalization.rmsNormEps is required' },
87
+ { kind: 'nonNullable', path: ['normalization', 'postAttentionNorm'], message: 'normalization.postAttentionNorm is required' },
88
+ { kind: 'nonNullable', path: ['normalization', 'preFeedforwardNorm'], message: 'normalization.preFeedforwardNorm is required' },
89
+ { kind: 'nonNullable', path: ['normalization', 'postFeedforwardNorm'], message: 'normalization.postFeedforwardNorm is required' },
90
+ { kind: 'nonNullable', path: ['ffn', 'activation'], message: 'ffn.activation is required' },
91
+ { kind: 'nonNullable', path: ['ffn', 'gatedActivation'], message: 'ffn.gatedActivation is required' },
92
+ { kind: 'nullable', path: ['ffn', 'swigluLimit'], message: 'ffn.swigluLimit must be explicitly set' },
93
+ { kind: 'nonNullable', path: ['rope', 'ropeTheta'], message: 'rope.ropeTheta is required' },
94
+ { kind: 'nonNullable', path: ['rope', 'ropeScalingFactor'], message: 'rope.ropeScalingFactor is required' },
95
+ { kind: 'nullable', path: ['rope', 'ropeScalingType'], message: 'rope.ropeScalingType must be explicitly set' },
96
+ { kind: 'nullable', path: ['rope', 'ropeLocalTheta'], message: 'rope.ropeLocalTheta must be explicitly set' },
97
+ { kind: 'nullable', path: ['rope', 'yarnBetaFast'], message: 'rope.yarnBetaFast must be explicitly set' },
98
+ { kind: 'nullable', path: ['rope', 'yarnBetaSlow'], message: 'rope.yarnBetaSlow must be explicitly set' },
99
+ { kind: 'nullable', path: ['rope', 'yarnOriginalMaxPos'], message: 'rope.yarnOriginalMaxPos must be explicitly set' },
100
+ { kind: 'nonNullable', path: ['output', 'tieWordEmbeddings'], message: 'output.tieWordEmbeddings is required' },
101
+ { kind: 'nonNullable', path: ['output', 'scaleEmbeddings'], message: 'output.scaleEmbeddings is required' },
102
+ { kind: 'nonNullable', path: ['output', 'embeddingTranspose'], message: 'output.embeddingTranspose is required' },
103
+ { kind: 'nullable', path: ['output', 'finalLogitSoftcapping'], message: 'output.finalLogitSoftcapping must be explicitly set' },
104
+ { kind: 'nullable', path: ['output', 'embeddingVocabSize'], message: 'output.embeddingVocabSize must be explicitly set' },
105
+ { kind: 'nonNullable', path: ['layerPattern', 'type'], message: 'layerPattern.type is required' },
106
+ { kind: 'nullable', path: ['layerPattern', 'globalPattern'], message: 'layerPattern.globalPattern must be explicitly set' },
107
+ { kind: 'nullable', path: ['layerPattern', 'period'], message: 'layerPattern.period must be explicitly set' },
108
+ { kind: 'nullable', path: ['layerPattern', 'offset'], message: 'layerPattern.offset must be explicitly set' },
109
+ { kind: 'nullable', path: ['chatTemplate', 'type'], message: 'chatTemplate.type must be explicitly set' },
110
+ { kind: 'nonNullable', path: ['chatTemplate', 'enabled'], message: 'chatTemplate.enabled is required' },
111
+ ]);
112
+
113
+ export function buildRequiredInferenceFieldsContractArtifact() {
114
+ const errors = [];
115
+ const checks = [];
116
+ const baseInference = createValidInferenceFixture();
117
+
118
+ try {
119
+ validateRequiredInferenceFields(cloneJson(baseInference), 'required-fields-contract');
120
+ checks.push({ id: 'requiredInferenceFields.validFixture', ok: true });
121
+ } catch (error) {
122
+ errors.push(error instanceof Error ? error.message : String(error));
123
+ checks.push({ id: 'requiredInferenceFields.validFixture', ok: false });
124
+ }
125
+
126
+ for (const field of FIELD_CASES) {
127
+ const missingInference = cloneJson(baseInference);
128
+ if (field.kind === 'nonNullable') {
129
+ setPath(missingInference, field.path, null);
130
+ } else {
131
+ deletePath(missingInference, field.path);
132
+ }
133
+ let rejectsAsExpected = false;
134
+ try {
135
+ validateRequiredInferenceFields(missingInference, 'required-fields-contract');
136
+ } catch (error) {
137
+ const message = error instanceof Error ? error.message : String(error);
138
+ rejectsAsExpected = message.includes(field.message);
139
+ }
140
+ if (!rejectsAsExpected) {
141
+ errors.push(
142
+ `[RequiredInferenceFieldsContract] ${field.path.join('.')} did not produce the expected required-field failure.`
143
+ );
144
+ }
145
+ checks.push({
146
+ id: `requiredInferenceFields.${field.path.join('.')}.rejectsInvalid`,
147
+ ok: rejectsAsExpected,
148
+ });
149
+
150
+ if (field.kind === 'nullable') {
151
+ const nullableInference = cloneJson(baseInference);
152
+ setPath(nullableInference, field.path, null);
153
+ let nullableAccepted = true;
154
+ try {
155
+ validateRequiredInferenceFields(nullableInference, 'required-fields-contract');
156
+ } catch {
157
+ nullableAccepted = false;
158
+ }
159
+ if (!nullableAccepted) {
160
+ errors.push(
161
+ `[RequiredInferenceFieldsContract] ${field.path.join('.')} should allow explicit null but was rejected.`
162
+ );
163
+ }
164
+ checks.push({
165
+ id: `requiredInferenceFields.${field.path.join('.')}.acceptsNull`,
166
+ ok: nullableAccepted,
167
+ });
168
+ }
169
+ }
170
+
171
+ const customInference = cloneJson(baseInference);
172
+ customInference.layerPattern.type = 'custom';
173
+ delete customInference.layerPattern.layerTypes;
174
+ let customLayerTypesRejected = false;
175
+ try {
176
+ validateRequiredInferenceFields(customInference, 'required-fields-contract');
177
+ } catch (error) {
178
+ const message = error instanceof Error ? error.message : String(error);
179
+ customLayerTypesRejected = message.includes('layerPattern.layerTypes must be explicitly set for custom patterns');
180
+ }
181
+ if (!customLayerTypesRejected) {
182
+ errors.push('[RequiredInferenceFieldsContract] custom layerPattern without layerTypes was not rejected.');
183
+ }
184
+ checks.push({
185
+ id: 'requiredInferenceFields.layerPattern.layerTypes.rejectsMissingForCustom',
186
+ ok: customLayerTypesRejected,
187
+ });
188
+
189
+ return {
190
+ schemaVersion: 1,
191
+ source: 'doppler',
192
+ ok: errors.length === 0,
193
+ checks,
194
+ errors,
195
+ stats: {
196
+ fieldCases: FIELD_CASES.length,
197
+ nullableCases: FIELD_CASES.filter((field) => field.kind === 'nullable').length,
198
+ nonNullableCases: FIELD_CASES.filter((field) => field.kind === 'nonNullable').length,
199
+ },
200
+ };
201
+ }
202
+
203
+ export function buildManifestRequiredInferenceFieldsArtifact(inference, label = 'manifest.inference') {
204
+ const errors = [];
205
+ const checks = [];
206
+ let ok = true;
207
+ try {
208
+ validateRequiredInferenceFields(cloneJson(inference), label);
209
+ } catch (error) {
210
+ ok = false;
211
+ errors.push(error instanceof Error ? error.message : String(error));
212
+ }
213
+ checks.push({
214
+ id: `${label}.requiredInferenceFields`,
215
+ ok,
216
+ });
217
+ return {
218
+ schemaVersion: 1,
219
+ source: 'doppler',
220
+ scope: 'manifest',
221
+ label,
222
+ ok,
223
+ checks,
224
+ errors,
225
+ stats: {
226
+ fieldCases: FIELD_CASES.length,
227
+ nullableCases: FIELD_CASES.filter((field) => field.kind === 'nullable').length,
228
+ nonNullableCases: FIELD_CASES.filter((field) => field.kind === 'nonNullable').length,
229
+ },
230
+ };
231
+ }
@@ -0,0 +1,17 @@
1
+ export interface BrowserSuiteMetricsSchema {
2
+ schemaVersion: 1;
3
+ source: 'doppler';
4
+ suite: string;
5
+ executionContractArtifact: Record<string, unknown> | null;
6
+ executionV0GraphContractArtifact: Record<string, unknown> | null;
7
+ layerPatternContractArtifact: Record<string, unknown> | null;
8
+ requiredInferenceFieldsArtifact: Record<string, unknown> | null;
9
+ [key: string]: unknown;
10
+ }
11
+
12
+ export declare const BROWSER_SUITE_METRICS_SCHEMA_VERSION: 1;
13
+ export declare const DEFAULT_BROWSER_SUITE_METRICS: Readonly<BrowserSuiteMetricsSchema>;
14
+
15
+ export declare function validateBrowserSuiteMetrics(
16
+ metrics: Record<string, unknown>
17
+ ): BrowserSuiteMetricsSchema;
@@ -0,0 +1,46 @@
1
+ function assertPlainObject(value, label) {
2
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
3
+ throw new Error(`browser suite metrics: ${label} must be an object.`);
4
+ }
5
+ }
6
+
7
+ function assertString(value, label) {
8
+ if (typeof value !== 'string' || !value.trim()) {
9
+ throw new Error(`browser suite metrics: ${label} must be a non-empty string.`);
10
+ }
11
+ }
12
+
13
+ function assertNullablePlainObject(value, label) {
14
+ if (value == null) return;
15
+ assertPlainObject(value, label);
16
+ }
17
+
18
+ export const BROWSER_SUITE_METRICS_SCHEMA_VERSION = 1;
19
+
20
+ export const DEFAULT_BROWSER_SUITE_METRICS = Object.freeze({
21
+ schemaVersion: BROWSER_SUITE_METRICS_SCHEMA_VERSION,
22
+ source: 'doppler',
23
+ suite: 'inference',
24
+ executionContractArtifact: null,
25
+ executionV0GraphContractArtifact: null,
26
+ layerPatternContractArtifact: null,
27
+ requiredInferenceFieldsArtifact: null,
28
+ });
29
+
30
+ export function validateBrowserSuiteMetrics(metrics) {
31
+ assertPlainObject(metrics, 'metrics');
32
+ if (metrics.schemaVersion !== BROWSER_SUITE_METRICS_SCHEMA_VERSION) {
33
+ throw new Error(
34
+ `browser suite metrics: schemaVersion must be ${BROWSER_SUITE_METRICS_SCHEMA_VERSION}.`
35
+ );
36
+ }
37
+ if (metrics.source !== 'doppler') {
38
+ throw new Error('browser suite metrics: source must be "doppler".');
39
+ }
40
+ assertString(metrics.suite, 'suite');
41
+ assertNullablePlainObject(metrics.executionContractArtifact, 'executionContractArtifact');
42
+ assertNullablePlainObject(metrics.executionV0GraphContractArtifact, 'executionV0GraphContractArtifact');
43
+ assertNullablePlainObject(metrics.layerPatternContractArtifact, 'layerPatternContractArtifact');
44
+ assertNullablePlainObject(metrics.requiredInferenceFieldsArtifact, 'requiredInferenceFieldsArtifact');
45
+ return metrics;
46
+ }
@@ -0,0 +1,40 @@
1
+ export interface ConversionReportResultSchema {
2
+ presetId: string | null;
3
+ modelType: string | null;
4
+ outputDir: string | null;
5
+ shardCount: number | null;
6
+ tensorCount: number | null;
7
+ totalSize: number | null;
8
+ }
9
+
10
+ export interface ConversionReportManifestSchema {
11
+ quantization: string | null;
12
+ quantizationInfo: Record<string, unknown> | null;
13
+ inference: {
14
+ presetId: string | null;
15
+ schema: string | null;
16
+ defaultKernelPath: string | null;
17
+ } | null;
18
+ }
19
+
20
+ export interface ConversionReportSchema {
21
+ schemaVersion: 1;
22
+ suite: 'convert';
23
+ command: 'convert';
24
+ modelId: string;
25
+ timestamp: string;
26
+ source: 'doppler';
27
+ result: ConversionReportResultSchema;
28
+ manifest: ConversionReportManifestSchema | null;
29
+ executionContractArtifact: Record<string, unknown> | null;
30
+ executionV0GraphContractArtifact: Record<string, unknown> | null;
31
+ layerPatternContractArtifact: Record<string, unknown> | null;
32
+ requiredInferenceFieldsArtifact: Record<string, unknown> | null;
33
+ }
34
+
35
+ export declare const CONVERSION_REPORT_SCHEMA_VERSION: 1;
36
+ export declare const DEFAULT_CONVERSION_REPORT: ConversionReportSchema;
37
+
38
+ export declare function validateConversionReport(
39
+ report: Record<string, unknown>
40
+ ): ConversionReportSchema;
@@ -0,0 +1,108 @@
1
+ function assertPlainObject(value, label) {
2
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
3
+ throw new Error(`conversion report: ${label} must be an object.`);
4
+ }
5
+ }
6
+
7
+ function assertString(value, label) {
8
+ if (typeof value !== 'string' || !value.trim()) {
9
+ throw new Error(`conversion report: ${label} must be a non-empty string.`);
10
+ }
11
+ }
12
+
13
+ function assertNullableString(value, label) {
14
+ if (value === null || value === undefined) return;
15
+ assertString(value, label);
16
+ }
17
+
18
+ function assertNullableFiniteNumber(value, label) {
19
+ if (value === null || value === undefined) return;
20
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
21
+ throw new Error(`conversion report: ${label} must be a finite number when provided.`);
22
+ }
23
+ }
24
+
25
+ function assertNullablePlainObject(value, label) {
26
+ if (value === null || value === undefined) return;
27
+ assertPlainObject(value, label);
28
+ }
29
+
30
+ export const CONVERSION_REPORT_SCHEMA_VERSION = 1;
31
+
32
+ export const DEFAULT_CONVERSION_REPORT = Object.freeze({
33
+ schemaVersion: CONVERSION_REPORT_SCHEMA_VERSION,
34
+ suite: 'convert',
35
+ command: 'convert',
36
+ modelId: 'unknown',
37
+ timestamp: '1970-01-01T00:00:00.000Z',
38
+ source: 'doppler',
39
+ result: {
40
+ presetId: null,
41
+ modelType: null,
42
+ outputDir: null,
43
+ shardCount: null,
44
+ tensorCount: null,
45
+ totalSize: null,
46
+ },
47
+ manifest: {
48
+ quantization: null,
49
+ quantizationInfo: null,
50
+ inference: {
51
+ presetId: null,
52
+ schema: null,
53
+ defaultKernelPath: null,
54
+ },
55
+ },
56
+ executionContractArtifact: null,
57
+ executionV0GraphContractArtifact: null,
58
+ layerPatternContractArtifact: null,
59
+ requiredInferenceFieldsArtifact: null,
60
+ });
61
+
62
+ export function validateConversionReport(report) {
63
+ assertPlainObject(report, 'report');
64
+ if (report.schemaVersion !== CONVERSION_REPORT_SCHEMA_VERSION) {
65
+ throw new Error(
66
+ `conversion report: schemaVersion must be ${CONVERSION_REPORT_SCHEMA_VERSION}.`
67
+ );
68
+ }
69
+ if (report.suite !== 'convert') {
70
+ throw new Error('conversion report: suite must be "convert".');
71
+ }
72
+ if (report.command !== 'convert') {
73
+ throw new Error('conversion report: command must be "convert".');
74
+ }
75
+ if (report.source !== 'doppler') {
76
+ throw new Error('conversion report: source must be "doppler".');
77
+ }
78
+ assertString(report.modelId, 'modelId');
79
+ assertString(report.timestamp, 'timestamp');
80
+ assertPlainObject(report.result, 'result');
81
+ assertNullableString(report.result.presetId, 'result.presetId');
82
+ assertNullableString(report.result.modelType, 'result.modelType');
83
+ assertNullableString(report.result.outputDir, 'result.outputDir');
84
+ assertNullableFiniteNumber(report.result.shardCount, 'result.shardCount');
85
+ assertNullableFiniteNumber(report.result.tensorCount, 'result.tensorCount');
86
+ assertNullableFiniteNumber(report.result.totalSize, 'result.totalSize');
87
+
88
+ assertNullablePlainObject(report.manifest, 'manifest');
89
+ if (report.manifest) {
90
+ assertNullableString(report.manifest.quantization, 'manifest.quantization');
91
+ assertNullablePlainObject(report.manifest.quantizationInfo, 'manifest.quantizationInfo');
92
+ assertNullablePlainObject(report.manifest.inference, 'manifest.inference');
93
+ if (report.manifest.inference) {
94
+ assertNullableString(report.manifest.inference.presetId, 'manifest.inference.presetId');
95
+ assertNullableString(report.manifest.inference.schema, 'manifest.inference.schema');
96
+ assertNullableString(
97
+ report.manifest.inference.defaultKernelPath,
98
+ 'manifest.inference.defaultKernelPath'
99
+ );
100
+ }
101
+ }
102
+
103
+ assertNullablePlainObject(report.executionContractArtifact, 'executionContractArtifact');
104
+ assertNullablePlainObject(report.executionV0GraphContractArtifact, 'executionV0GraphContractArtifact');
105
+ assertNullablePlainObject(report.layerPatternContractArtifact, 'layerPatternContractArtifact');
106
+ assertNullablePlainObject(report.requiredInferenceFieldsArtifact, 'requiredInferenceFieldsArtifact');
107
+ return report;
108
+ }
@@ -3,6 +3,13 @@ import { DEFAULT_INFERENCE_DEFAULTS_CONFIG } from './inference-defaults.schema.j
3
3
  import { DEFAULT_SHARED_RUNTIME_CONFIG } from './shared-runtime.schema.js';
4
4
  import { DEFAULT_EMULATION_CONFIG, createEmulationConfig } from './emulation.schema.js';
5
5
  import { mergeEcosystemConfig } from './ecosystem.schema.js';
6
+ import {
7
+ chooseNullish,
8
+ mergeExecutionPatchLists,
9
+ mergeKernelPathPolicy,
10
+ mergeShallowObject,
11
+ replaceSubtree,
12
+ } from '../merge-helpers.js';
6
13
 
7
14
  // =============================================================================
8
15
  // Runtime Config (all non-model-specific settings)
@@ -172,8 +179,6 @@ function mergeInferenceConfig(
172
179
  const overrideExecutionPatch = overrides.executionPatch ?? {};
173
180
  const baseKernelPathPolicy = base.kernelPathPolicy ?? {};
174
181
  const overrideKernelPathPolicy = overrides.kernelPathPolicy ?? {};
175
- const baseKernelPathSourceScope = baseKernelPathPolicy.sourceScope ?? baseKernelPathPolicy.allowSources;
176
- const overrideKernelPathSourceScope = overrideKernelPathPolicy.sourceScope ?? overrideKernelPathPolicy.allowSources;
177
182
  const hasRuntimeKernelProfiles = Object.prototype.hasOwnProperty.call(
178
183
  overrideSessionCompute,
179
184
  'kernelProfiles'
@@ -236,15 +241,8 @@ function mergeInferenceConfig(
236
241
  pipeline: overrides.pipeline ?? base.pipeline,
237
242
  kernelPath: overrides.kernelPath ?? base.kernelPath,
238
243
  kernelPathSource: overrides.kernelPathSource ?? base.kernelPathSource,
239
- kernelPathPolicy: {
240
- mode: overrideKernelPathPolicy.mode ?? baseKernelPathPolicy.mode,
241
- sourceScope: overrideKernelPathSourceScope ?? baseKernelPathSourceScope,
242
- allowSources: overrideKernelPathSourceScope ?? baseKernelPathSourceScope,
243
- onIncompatible: overrideKernelPathPolicy.onIncompatible ?? baseKernelPathPolicy.onIncompatible,
244
- },
245
- chatTemplate: overrides.chatTemplate
246
- ? { ...base.chatTemplate, ...overrides.chatTemplate }
247
- : base.chatTemplate,
244
+ kernelPathPolicy: mergeKernelPathPolicy(baseKernelPathPolicy, overrideKernelPathPolicy),
245
+ chatTemplate: mergeShallowObject(base.chatTemplate, overrides.chatTemplate),
248
246
  session: {
249
247
  ...baseSession,
250
248
  ...overrideSession,
@@ -259,14 +257,10 @@ function mergeInferenceConfig(
259
257
  ? { kernelProfiles: overrideSessionCompute.kernelProfiles }
260
258
  : { kernelProfiles: baseSessionCompute.kernelProfiles }),
261
259
  },
262
- kvcache: overrideSession.kvcache ?? baseSession.kvcache,
263
- decodeLoop: overrideSession.decodeLoop ?? baseSession.decodeLoop,
264
- },
265
- executionPatch: {
266
- set: overrideExecutionPatch.set ?? baseExecutionPatch.set ?? [],
267
- remove: overrideExecutionPatch.remove ?? baseExecutionPatch.remove ?? [],
268
- add: overrideExecutionPatch.add ?? baseExecutionPatch.add ?? [],
260
+ kvcache: replaceSubtree(overrideSession.kvcache, baseSession.kvcache),
261
+ decodeLoop: replaceSubtree(overrideSession.decodeLoop, baseSession.decodeLoop),
269
262
  },
263
+ executionPatch: mergeExecutionPatchLists(baseExecutionPatch, overrideExecutionPatch),
270
264
  // Model-specific inference overrides (merged with manifest.inference at load time)
271
265
  modelOverrides: overrides.modelOverrides ?? base.modelOverrides,
272
266
  };
@@ -225,6 +225,28 @@ export {
225
225
  type ConversionIOSchema,
226
226
  } from './conversion.schema.js';
227
227
 
228
+ // =============================================================================
229
+ // Browser Suite Metrics Schema
230
+ // =============================================================================
231
+ export {
232
+ type BrowserSuiteMetricsSchema,
233
+ BROWSER_SUITE_METRICS_SCHEMA_VERSION,
234
+ DEFAULT_BROWSER_SUITE_METRICS,
235
+ validateBrowserSuiteMetrics,
236
+ } from './browser-suite-metrics.schema.js';
237
+
238
+ // =============================================================================
239
+ // Conversion Report Schema
240
+ // =============================================================================
241
+ export {
242
+ type ConversionReportResultSchema,
243
+ type ConversionReportManifestSchema,
244
+ type ConversionReportSchema,
245
+ CONVERSION_REPORT_SCHEMA_VERSION,
246
+ DEFAULT_CONVERSION_REPORT,
247
+ validateConversionReport,
248
+ } from './conversion-report.schema.js';
249
+
228
250
  // =============================================================================
229
251
  // Converter Schema
230
252
  // =============================================================================
@@ -55,6 +55,24 @@ export {
55
55
  ConversionStage,
56
56
  } from './conversion.schema.js';
57
57
 
58
+ // =============================================================================
59
+ // Browser Suite Metrics Schema
60
+ // =============================================================================
61
+ export {
62
+ BROWSER_SUITE_METRICS_SCHEMA_VERSION,
63
+ DEFAULT_BROWSER_SUITE_METRICS,
64
+ validateBrowserSuiteMetrics,
65
+ } from './browser-suite-metrics.schema.js';
66
+
67
+ // =============================================================================
68
+ // Conversion Report Schema
69
+ // =============================================================================
70
+ export {
71
+ CONVERSION_REPORT_SCHEMA_VERSION,
72
+ DEFAULT_CONVERSION_REPORT,
73
+ validateConversionReport,
74
+ } from './conversion-report.schema.js';
75
+
58
76
  // =============================================================================
59
77
  // Converter Schema
60
78
  // =============================================================================
@@ -27,6 +27,12 @@ import type {
27
27
  MoEConfigSchema,
28
28
  ConversionInfoSchema,
29
29
  } from '../config/schema/index.js';
30
+ import type { ExecutionContractArtifact } from '../config/execution-contract-check.js';
31
+ import type { ExecutionV0GraphContractArtifact } from '../config/execution-v0-graph-contract-check.js';
32
+ import type {
33
+ ManifestRequiredInferenceFieldsArtifact,
34
+ RequiredInferenceFieldsContractArtifact,
35
+ } from '../config/required-inference-fields-contract-check.js';
30
36
 
31
37
  export { generateShardFilename } from '../formats/rdrr/index.js';
32
38
 
@@ -144,6 +150,10 @@ export interface ConvertResult {
144
150
  shardCount: number;
145
151
  tensorCount: number;
146
152
  totalSize: number;
153
+ executionContractArtifact: ExecutionContractArtifact | null;
154
+ executionV0GraphContractArtifact: ExecutionV0GraphContractArtifact | null;
155
+ layerPatternContractArtifact: Record<string, unknown> | null;
156
+ requiredInferenceFieldsArtifact: ManifestRequiredInferenceFieldsArtifact | RequiredInferenceFieldsContractArtifact | null;
147
157
  }
148
158
 
149
159
  /** @deprecated Use ConversionIOSchema from config/schema */
@@ -9,15 +9,20 @@ import {
9
9
  formatBytes,
10
10
  } from '../config/schema/index.js';
11
11
 
12
- import { classifyTensorRole, generateShardFilename } from '../formats/rdrr/index.js';
12
+ import { classifyTensor, classifyTensorRole, generateShardFilename } from '../formats/rdrr/index.js';
13
13
  import { log } from '../debug/index.js';
14
- import { selectRuleValue } from '../rules/rule-registry.js';
14
+ import {
15
+ getInferenceLayerPatternContractArtifact,
16
+ selectRuleValue,
17
+ } from '../rules/rule-registry.js';
15
18
  import {
16
19
  createConverterConfig,
17
20
  detectPreset,
18
21
  listPresets,
19
22
  resolvePreset,
20
23
  } from '../config/index.js';
24
+ import { buildExecutionContractArtifact } from '../config/execution-contract-check.js';
25
+ import { buildManifestRequiredInferenceFieldsArtifact } from '../config/required-inference-fields-contract-check.js';
21
26
  import { buildManifestInference, inferEmbeddingOutputConfig } from './manifest-inference.js';
22
27
  import { resolveEosTokenId } from './tokenizer-utils.js';
23
28
  import {
@@ -1128,6 +1133,7 @@ export async function convertModel(model, io, options = {}) {
1128
1133
  }
1129
1134
  const totalTensors = tensors.length;
1130
1135
  const targetQuant = String(options.quantization ?? model.quantization ?? '').trim().toLowerCase();
1136
+ const tensorGroupModelType = String(options.modelType ?? model.modelType ?? 'transformer');
1131
1137
  const q4kLayout = normalizeQ4KLayout(options.quantizationInfo?.layout);
1132
1138
  const quantizeEmbeddings = resolveQuantizeEmbeddings(
1133
1139
  options.quantizationInfo ?? null,
@@ -1251,6 +1257,7 @@ export async function convertModel(model, io, options = {}) {
1251
1257
 
1252
1258
  // Record tensor location
1253
1259
  const role = classifyTensorRole(tensor.name);
1260
+ const group = classifyTensor(tensor.name, tensorGroupModelType);
1254
1261
 
1255
1262
  if (tensorSpans.length === 1) {
1256
1263
  tensorLocations[tensor.name] = {
@@ -1260,6 +1267,7 @@ export async function convertModel(model, io, options = {}) {
1260
1267
  shape: tensor.shape,
1261
1268
  dtype: outDtype,
1262
1269
  role,
1270
+ group,
1263
1271
  ...(outLayout ? { layout: outLayout } : {}),
1264
1272
  };
1265
1273
  } else {
@@ -1269,6 +1277,7 @@ export async function convertModel(model, io, options = {}) {
1269
1277
  shape: tensor.shape,
1270
1278
  dtype: outDtype,
1271
1279
  role,
1280
+ group,
1272
1281
  ...(outLayout ? { layout: outLayout } : {}),
1273
1282
  };
1274
1283
  }
@@ -1327,11 +1336,27 @@ export async function convertModel(model, io, options = {}) {
1327
1336
  totalSize: formatBytes(totalSize),
1328
1337
  });
1329
1338
 
1339
+ const executionContractArtifact = buildExecutionContractArtifact(manifest);
1340
+ const layerPatternContractArtifact = getInferenceLayerPatternContractArtifact();
1341
+ const requiredInferenceFieldsArtifact = manifest?.modelType === 'transformer'
1342
+ && manifest?.inference
1343
+ && typeof manifest.inference === 'object'
1344
+ && manifest.inference.attention
1345
+ && typeof manifest.inference.attention === 'object'
1346
+ ? buildManifestRequiredInferenceFieldsArtifact(
1347
+ manifest?.inference ?? null,
1348
+ `${manifest?.modelId ?? modelId}.inference`
1349
+ )
1350
+ : null;
1330
1351
  return {
1331
1352
  manifest,
1332
1353
  shardCount: shards.length,
1333
1354
  tensorCount: tensors.length,
1334
1355
  totalSize,
1356
+ executionContractArtifact,
1357
+ executionV0GraphContractArtifact: executionContractArtifact?.executionV0?.graph ?? null,
1358
+ layerPatternContractArtifact,
1359
+ requiredInferenceFieldsArtifact,
1335
1360
  };
1336
1361
  }
1337
1362