@simulatte/doppler 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -5
- package/package.json +27 -4
- package/src/client/doppler-api.browser.d.ts +1 -0
- package/src/client/doppler-api.browser.js +288 -0
- package/src/client/doppler-api.d.ts +80 -0
- package/src/client/doppler-api.js +298 -0
- package/src/client/doppler-provider/types.js +1 -1
- package/src/client/doppler-registry.d.ts +23 -0
- package/src/client/doppler-registry.js +88 -0
- package/src/client/doppler-registry.json +39 -0
- package/src/config/execution-contract-check.d.ts +82 -0
- package/src/config/execution-contract-check.js +317 -0
- package/src/config/execution-v0-contract-check.d.ts +94 -0
- package/src/config/execution-v0-contract-check.js +251 -0
- package/src/config/execution-v0-graph-contract-check.d.ts +20 -0
- package/src/config/execution-v0-graph-contract-check.js +64 -0
- package/src/config/kernel-path-contract-check.d.ts +76 -0
- package/src/config/kernel-path-contract-check.js +479 -0
- package/src/config/kernel-path-loader.d.ts +16 -0
- package/src/config/kernel-path-loader.js +54 -0
- package/src/config/kernels/kernel-ref-digests.js +12 -0
- package/src/config/kernels/registry.json +556 -0
- package/src/config/loader.js +90 -67
- package/src/config/merge-contract-check.d.ts +16 -0
- package/src/config/merge-contract-check.js +321 -0
- package/src/config/merge-helpers.d.ts +58 -0
- package/src/config/merge-helpers.js +54 -0
- package/src/config/merge.js +3 -6
- package/src/config/presets/models/janus-text.json +27 -0
- package/src/config/quantization-contract-check.d.ts +12 -0
- package/src/config/quantization-contract-check.js +91 -0
- package/src/config/required-inference-fields-contract-check.d.ts +24 -0
- package/src/config/required-inference-fields-contract-check.js +231 -0
- package/src/config/schema/browser-suite-metrics.schema.d.ts +17 -0
- package/src/config/schema/browser-suite-metrics.schema.js +46 -0
- package/src/config/schema/conversion-report.schema.d.ts +40 -0
- package/src/config/schema/conversion-report.schema.js +108 -0
- package/src/config/schema/doppler.schema.js +12 -18
- package/src/config/schema/index.d.ts +22 -0
- package/src/config/schema/index.js +18 -0
- package/src/converter/core.d.ts +10 -0
- package/src/converter/core.js +49 -11
- package/src/converter/parsers/diffusion.js +63 -3
- package/src/converter/tokenizer-utils.js +17 -3
- package/src/formats/rdrr/validation.js +13 -0
- package/src/gpu/kernels/depthwise_conv2d.d.ts +29 -0
- package/src/gpu/kernels/depthwise_conv2d.js +98 -0
- package/src/gpu/kernels/depthwise_conv2d.wgsl +58 -0
- package/src/gpu/kernels/depthwise_conv2d_f16.wgsl +62 -0
- package/src/gpu/kernels/grouped_pointwise_conv2d.d.ts +27 -0
- package/src/gpu/kernels/grouped_pointwise_conv2d.js +92 -0
- package/src/gpu/kernels/grouped_pointwise_conv2d.wgsl +47 -0
- package/src/gpu/kernels/grouped_pointwise_conv2d_f16.wgsl +51 -0
- package/src/gpu/kernels/index.d.ts +30 -0
- package/src/gpu/kernels/index.js +25 -0
- package/src/gpu/kernels/relu.d.ts +18 -0
- package/src/gpu/kernels/relu.js +45 -0
- package/src/gpu/kernels/relu.wgsl +21 -0
- package/src/gpu/kernels/relu_f16.wgsl +23 -0
- package/src/gpu/kernels/repeat_channels.d.ts +21 -0
- package/src/gpu/kernels/repeat_channels.js +60 -0
- package/src/gpu/kernels/repeat_channels.wgsl +29 -0
- package/src/gpu/kernels/repeat_channels_f16.wgsl +31 -0
- package/src/gpu/kernels/sana_linear_attention.d.ts +27 -0
- package/src/gpu/kernels/sana_linear_attention.js +122 -0
- package/src/gpu/kernels/sana_linear_attention_apply.wgsl +44 -0
- package/src/gpu/kernels/sana_linear_attention_apply_f16.wgsl +47 -0
- package/src/gpu/kernels/sana_linear_attention_summary.wgsl +47 -0
- package/src/gpu/kernels/sana_linear_attention_summary_f16.wgsl +49 -0
- package/src/index-browser.d.ts +1 -0
- package/src/index-browser.js +2 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +2 -1
- package/src/inference/browser-harness.js +164 -38
- package/src/inference/pipelines/diffusion/init.js +14 -0
- package/src/inference/pipelines/diffusion/pipeline.js +206 -77
- package/src/inference/pipelines/diffusion/sana-transformer.d.ts +53 -0
- package/src/inference/pipelines/diffusion/sana-transformer.js +738 -0
- package/src/inference/pipelines/diffusion/scheduler.d.ts +17 -1
- package/src/inference/pipelines/diffusion/scheduler.js +91 -3
- package/src/inference/pipelines/diffusion/text-encoder-gpu.d.ts +6 -4
- package/src/inference/pipelines/diffusion/text-encoder-gpu.js +270 -0
- package/src/inference/pipelines/diffusion/text-encoder.js +18 -1
- package/src/inference/pipelines/diffusion/types.d.ts +4 -0
- package/src/inference/pipelines/diffusion/vae.js +782 -78
- package/src/inference/pipelines/text/config.d.ts +5 -0
- package/src/inference/pipelines/text/config.js +1 -1
- package/src/inference/pipelines/text/execution-v0.js +141 -101
- package/src/inference/pipelines/text/init.js +41 -10
- package/src/inference/pipelines/text.js +7 -1
- package/src/rules/execution-rules-contract-check.d.ts +17 -0
- package/src/rules/execution-rules-contract-check.js +245 -0
- package/src/rules/kernels/depthwise-conv2d.rules.json +6 -0
- package/src/rules/kernels/grouped-pointwise-conv2d.rules.json +6 -0
- package/src/rules/kernels/relu.rules.json +6 -0
- package/src/rules/kernels/repeat-channels.rules.json +6 -0
- package/src/rules/kernels/sana-linear-attention.rules.json +6 -0
- package/src/rules/layer-pattern-contract-check.d.ts +17 -0
- package/src/rules/layer-pattern-contract-check.js +231 -0
- package/src/rules/rule-registry.d.ts +28 -0
- package/src/rules/rule-registry.js +38 -0
- package/src/tooling/conversion-config-materializer.d.ts +24 -0
- package/src/tooling/conversion-config-materializer.js +99 -0
- package/src/tooling/lean-execution-contract-runner.d.ts +43 -0
- package/src/tooling/lean-execution-contract-runner.js +158 -0
- package/src/tooling/lean-execution-contract.d.ts +16 -0
- package/src/tooling/lean-execution-contract.js +81 -0
- package/src/tooling/node-convert.d.ts +10 -0
- package/src/tooling/node-converter.js +59 -0
- package/src/tooling/node-webgpu.js +30 -9
- package/src/version.d.ts +2 -0
- package/src/version.js +2 -0
- package/tools/convert-safetensors-node.js +47 -0
- package/tools/doppler-cli.js +167 -6
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { compileExecutionV0 } from '../inference/pipelines/text/execution-v0.js';
|
|
2
|
+
|
|
3
|
+
function classifyGraphError(error) {
|
|
4
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5
|
+
if (message.includes('reads slot "') && message.includes('before it is produced')) {
|
|
6
|
+
return {
|
|
7
|
+
slotGraphOk: false,
|
|
8
|
+
phaseBoundaryOk: true,
|
|
9
|
+
error: message,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
if (message.includes('reads carried slot "') && message.includes('Add explicit cast at phase boundary')) {
|
|
13
|
+
return {
|
|
14
|
+
slotGraphOk: true,
|
|
15
|
+
phaseBoundaryOk: false,
|
|
16
|
+
error: message,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
slotGraphOk: false,
|
|
21
|
+
phaseBoundaryOk: false,
|
|
22
|
+
error: message,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildExecutionV0GraphContractArtifact(options = {}) {
|
|
27
|
+
const modelId = String(options.modelId ?? 'model');
|
|
28
|
+
try {
|
|
29
|
+
const compiled = compileExecutionV0(options);
|
|
30
|
+
if (!compiled) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
source: 'doppler',
|
|
36
|
+
ok: true,
|
|
37
|
+
checks: [
|
|
38
|
+
{ id: `${modelId}.slotGraph`, ok: true },
|
|
39
|
+
{ id: `${modelId}.phaseBoundary`, ok: true },
|
|
40
|
+
],
|
|
41
|
+
errors: [],
|
|
42
|
+
stats: {
|
|
43
|
+
prefillSteps: compiled.resolvedSteps.prefill.length,
|
|
44
|
+
decodeSteps: compiled.resolvedSteps.decode.length,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
const classified = classifyGraphError(error);
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
source: 'doppler',
|
|
52
|
+
ok: false,
|
|
53
|
+
checks: [
|
|
54
|
+
{ id: `${modelId}.slotGraph`, ok: classified.slotGraphOk },
|
|
55
|
+
{ id: `${modelId}.phaseBoundary`, ok: classified.phaseBoundaryOk },
|
|
56
|
+
],
|
|
57
|
+
errors: [classified.error],
|
|
58
|
+
stats: {
|
|
59
|
+
prefillSteps: 0,
|
|
60
|
+
decodeSteps: 0,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface KernelPathContractEntryFacts {
|
|
2
|
+
id: string;
|
|
3
|
+
aliasOf: string | null;
|
|
4
|
+
hasFile: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface KernelPathFallbackMappingFacts {
|
|
8
|
+
primaryKernelPathId: string;
|
|
9
|
+
fallbackKernelPathId: string;
|
|
10
|
+
primaryActivationDtype: 'f16' | 'f32';
|
|
11
|
+
fallbackActivationDtype: 'f16' | 'f32' | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface KernelPathContractFacts {
|
|
15
|
+
registryId: string;
|
|
16
|
+
entries: KernelPathContractEntryFacts[];
|
|
17
|
+
fallbackMappings: KernelPathFallbackMappingFacts[];
|
|
18
|
+
fallbackRules?: Array<{
|
|
19
|
+
matchKernelPathId: string | null;
|
|
20
|
+
value: string | null;
|
|
21
|
+
isDefault: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
autoSelectRules?: Array<{
|
|
24
|
+
matchKernelPathRef: string | null;
|
|
25
|
+
allowCapabilityAutoSelection: boolean | null;
|
|
26
|
+
hasSubgroups: boolean | null;
|
|
27
|
+
valueKind: 'string' | 'context';
|
|
28
|
+
value: string;
|
|
29
|
+
isDefault: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface KernelPathContractCheckResult {
|
|
34
|
+
id: string;
|
|
35
|
+
ok: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface KernelPathContractValidationResult {
|
|
39
|
+
ok: boolean;
|
|
40
|
+
errors: string[];
|
|
41
|
+
checks: KernelPathContractCheckResult[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface KernelPathContractArtifact {
|
|
45
|
+
schemaVersion: 1;
|
|
46
|
+
source: 'doppler';
|
|
47
|
+
ok: boolean;
|
|
48
|
+
checks: KernelPathContractCheckResult[];
|
|
49
|
+
errors: string[];
|
|
50
|
+
stats: {
|
|
51
|
+
totalEntries: number;
|
|
52
|
+
aliasEntries: number;
|
|
53
|
+
canonicalEntries: number;
|
|
54
|
+
fallbackMappings: number;
|
|
55
|
+
fallbackRules: number;
|
|
56
|
+
autoSelectRules: number;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare function extractKernelPathContractFacts(
|
|
61
|
+
input: Record<string, unknown> | unknown[],
|
|
62
|
+
options?: {
|
|
63
|
+
registryId?: string;
|
|
64
|
+
}
|
|
65
|
+
): KernelPathContractFacts;
|
|
66
|
+
|
|
67
|
+
export declare function validateKernelPathContractFacts(
|
|
68
|
+
facts: KernelPathContractFacts
|
|
69
|
+
): KernelPathContractValidationResult;
|
|
70
|
+
|
|
71
|
+
export declare function buildKernelPathContractArtifact(
|
|
72
|
+
input: Record<string, unknown> | unknown[],
|
|
73
|
+
options?: {
|
|
74
|
+
registryId?: string;
|
|
75
|
+
}
|
|
76
|
+
): KernelPathContractArtifact;
|
|
@@ -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
|
*/
|