@simulatte/doppler 0.1.2 → 0.1.4
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 +8 -3
- package/package.json +3 -1
- package/src/client/doppler-api.d.ts +80 -0
- package/src/client/doppler-api.js +298 -0
- 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 +49 -0
- package/src/config/execution-contract-check.js +245 -0
- package/src/config/loader.js +40 -21
- package/src/config/presets/models/janus-text.json +25 -0
- package/src/converter/core.js +22 -9
- package/src/converter/tokenizer-utils.js +17 -3
- package/src/formats/rdrr/validation.js +13 -0
- package/src/index-browser.d.ts +1 -0
- package/src/index-browser.js +1 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/inference/browser-harness.js +102 -16
- package/src/inference/pipelines/text/execution-v0.js +127 -8
- package/src/inference/pipelines/text/finiteness-policy.js +1 -1
- package/src/inference/pipelines/text/init.js +41 -10
- package/src/inference/pipelines/text.js +7 -1
- package/src/tooling/lean-execution-contract.d.ts +16 -0
- package/src/tooling/lean-execution-contract.js +81 -0
- package/src/tooling/node-webgpu.js +22 -1
- package/tools/doppler-cli.js +52 -5
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { DEFAULT_BATCHING_DEFAULTS, DEFAULT_GENERATION_CONFIG } from './schema/inference-defaults.schema.js';
|
|
2
|
+
import { DEFAULT_KVCACHE_CONFIG } from './schema/kvcache.schema.js';
|
|
3
|
+
|
|
4
|
+
const KV_LAYOUTS = new Set(['contiguous', 'paged', 'tiered', 'bdpa']);
|
|
5
|
+
const PHASES = new Set(['prefill', 'decode', 'both']);
|
|
6
|
+
const COLD_QUANT_MODES = new Set(['none', 'int8', 'int4']);
|
|
7
|
+
const ATTENTION_OPS = new Set(['attention']);
|
|
8
|
+
const EMBED_OPS = new Set(['embed', 'gather']);
|
|
9
|
+
const SAMPLE_OPS = new Set(['sample']);
|
|
10
|
+
const BDPA_MAX_HEAD_DIM = 256;
|
|
11
|
+
const BDPA_MAX_KV_LEN = 2048;
|
|
12
|
+
const TIERED_MAX_QUANT_HEAD_DIM = 256;
|
|
13
|
+
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function assertManifestObject(manifest) {
|
|
19
|
+
if (!isPlainObject(manifest)) {
|
|
20
|
+
throw new Error('execution contract: manifest must be an object.');
|
|
21
|
+
}
|
|
22
|
+
return manifest;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertPositiveInteger(value, label) {
|
|
26
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
27
|
+
throw new Error(`execution contract: ${label} must be a non-negative integer.`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assertPositiveIntegerOrDefault(value, fallback, label) {
|
|
33
|
+
if (value == null) {
|
|
34
|
+
return assertPositiveInteger(fallback, `${label} fallback`);
|
|
35
|
+
}
|
|
36
|
+
return assertPositiveInteger(value, label);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeKVLayout(value) {
|
|
40
|
+
const normalized = String(value ?? DEFAULT_KVCACHE_CONFIG.layout).trim().toLowerCase();
|
|
41
|
+
if (!KV_LAYOUTS.has(normalized)) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`execution contract: unsupported KV layout "${value}". ` +
|
|
44
|
+
`Expected one of ${[...KV_LAYOUTS].join(', ')}.`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizePhase(value, label) {
|
|
51
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
52
|
+
if (!PHASES.has(normalized)) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`execution contract: ${label} must be one of ${[...PHASES].join(', ')}.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeColdQuantMode(kvcache) {
|
|
61
|
+
const tieringMode = String(
|
|
62
|
+
kvcache?.tiering?.mode
|
|
63
|
+
?? DEFAULT_KVCACHE_CONFIG.tiering.mode
|
|
64
|
+
).trim().toLowerCase();
|
|
65
|
+
if (tieringMode === 'off' || tieringMode === 'fp16') {
|
|
66
|
+
return 'none';
|
|
67
|
+
}
|
|
68
|
+
if (!COLD_QUANT_MODES.has(tieringMode)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`execution contract: unsupported tiered cold quant mode "${tieringMode}".`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return tieringMode;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function classifyOp(op) {
|
|
77
|
+
const normalized = String(op ?? '').trim().toLowerCase();
|
|
78
|
+
if (!normalized) {
|
|
79
|
+
throw new Error('execution contract: execution step op is required.');
|
|
80
|
+
}
|
|
81
|
+
if (ATTENTION_OPS.has(normalized)) return 'attention';
|
|
82
|
+
if (EMBED_OPS.has(normalized)) return 'embed';
|
|
83
|
+
if (SAMPLE_OPS.has(normalized)) return 'sample';
|
|
84
|
+
if (normalized.includes('norm')) return 'norm';
|
|
85
|
+
if (normalized.includes('residual')) return 'residual';
|
|
86
|
+
if (normalized.endsWith('_proj') || normalized.startsWith('rope_') || normalized === 'activation') {
|
|
87
|
+
return 'projection';
|
|
88
|
+
}
|
|
89
|
+
return 'other';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function sanitizeLeanModuleName(value) {
|
|
93
|
+
const raw = String(value ?? 'GeneratedExecutionContractCheck').trim();
|
|
94
|
+
const alnum = raw.replace(/[^A-Za-z0-9_]/g, '_');
|
|
95
|
+
if (!alnum) {
|
|
96
|
+
return 'GeneratedExecutionContractCheck';
|
|
97
|
+
}
|
|
98
|
+
if (/^[A-Za-z_]/.test(alnum)) {
|
|
99
|
+
return alnum;
|
|
100
|
+
}
|
|
101
|
+
return `Generated_${alnum}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function extractExecutionContractFacts(manifest) {
|
|
105
|
+
const normalizedManifest = assertManifestObject(manifest);
|
|
106
|
+
const modelId = String(normalizedManifest.modelId ?? 'model').trim() || 'model';
|
|
107
|
+
const architecture = isPlainObject(normalizedManifest.architecture)
|
|
108
|
+
? normalizedManifest.architecture
|
|
109
|
+
: {};
|
|
110
|
+
const inference = isPlainObject(normalizedManifest.inference)
|
|
111
|
+
? normalizedManifest.inference
|
|
112
|
+
: {};
|
|
113
|
+
const sessionDefaults = isPlainObject(inference.sessionDefaults)
|
|
114
|
+
? inference.sessionDefaults
|
|
115
|
+
: {};
|
|
116
|
+
const execution = isPlainObject(inference.execution)
|
|
117
|
+
? inference.execution
|
|
118
|
+
: {};
|
|
119
|
+
const kvcache = isPlainObject(sessionDefaults.kvcache)
|
|
120
|
+
? sessionDefaults.kvcache
|
|
121
|
+
: {};
|
|
122
|
+
const decodeLoop = isPlainObject(sessionDefaults.decodeLoop)
|
|
123
|
+
? sessionDefaults.decodeLoop
|
|
124
|
+
: {};
|
|
125
|
+
|
|
126
|
+
const steps = Array.isArray(execution.steps)
|
|
127
|
+
? execution.steps.map((step, index) => {
|
|
128
|
+
if (!isPlainObject(step)) {
|
|
129
|
+
throw new Error(`execution contract: execution.steps[${index}] must be an object.`);
|
|
130
|
+
}
|
|
131
|
+
const id = String(step.id ?? '').trim();
|
|
132
|
+
if (!id) {
|
|
133
|
+
throw new Error(`execution contract: execution.steps[${index}].id is required.`);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
id,
|
|
137
|
+
phase: normalizePhase(step.phase, `execution.steps[${index}].phase`),
|
|
138
|
+
opClass: classifyOp(step.op),
|
|
139
|
+
};
|
|
140
|
+
})
|
|
141
|
+
: [];
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
modelId,
|
|
145
|
+
session: {
|
|
146
|
+
layout: normalizeKVLayout(kvcache.layout),
|
|
147
|
+
disableCommandBatching: decodeLoop.disableCommandBatching
|
|
148
|
+
?? DEFAULT_GENERATION_CONFIG.disableCommandBatching,
|
|
149
|
+
decodeBatchSize: assertPositiveIntegerOrDefault(
|
|
150
|
+
decodeLoop.batchSize,
|
|
151
|
+
DEFAULT_BATCHING_DEFAULTS.batchSize,
|
|
152
|
+
'sessionDefaults.decodeLoop.batchSize'
|
|
153
|
+
),
|
|
154
|
+
headDim: assertPositiveInteger(architecture.headDim, 'architecture.headDim'),
|
|
155
|
+
kvLen: assertPositiveIntegerOrDefault(
|
|
156
|
+
architecture.maxSeqLen ?? kvcache.maxSeqLen,
|
|
157
|
+
DEFAULT_KVCACHE_CONFIG.maxSeqLen,
|
|
158
|
+
'architecture.maxSeqLen'
|
|
159
|
+
),
|
|
160
|
+
coldQuantMode: normalizeColdQuantMode(kvcache),
|
|
161
|
+
},
|
|
162
|
+
steps,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function validateExecutionContractFacts(facts) {
|
|
167
|
+
const errors = [];
|
|
168
|
+
const checks = [];
|
|
169
|
+
const modelId = String(facts?.modelId ?? 'model');
|
|
170
|
+
const session = facts?.session ?? {};
|
|
171
|
+
const steps = Array.isArray(facts?.steps) ? facts.steps : [];
|
|
172
|
+
|
|
173
|
+
const incompatibleStep = session.layout === 'bdpa'
|
|
174
|
+
? steps.find((step) => step.opClass === 'attention' && (step.phase === 'prefill' || step.phase === 'both'))
|
|
175
|
+
: null;
|
|
176
|
+
if (incompatibleStep) {
|
|
177
|
+
errors.push(
|
|
178
|
+
`[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" is decode-only, ` +
|
|
179
|
+
`but step "${incompatibleStep.id}" declares ${incompatibleStep.phase} attention.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
checks.push({
|
|
183
|
+
id: `${modelId}.steps`,
|
|
184
|
+
ok: incompatibleStep == null,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const sessionErrorCount = errors.length;
|
|
188
|
+
if (session.layout === 'bdpa') {
|
|
189
|
+
if (session.disableCommandBatching !== true) {
|
|
190
|
+
errors.push(
|
|
191
|
+
'[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires ' +
|
|
192
|
+
'sessionDefaults.decodeLoop.disableCommandBatching=true.'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (session.decodeBatchSize > 1) {
|
|
196
|
+
errors.push(
|
|
197
|
+
`[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires ` +
|
|
198
|
+
`sessionDefaults.decodeLoop.batchSize <= 1; got ${session.decodeBatchSize}.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (session.headDim > BDPA_MAX_HEAD_DIM) {
|
|
202
|
+
errors.push(
|
|
203
|
+
`[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires architecture.headDim <= ${BDPA_MAX_HEAD_DIM}; ` +
|
|
204
|
+
`got ${session.headDim}.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
if (session.kvLen > BDPA_MAX_KV_LEN) {
|
|
208
|
+
errors.push(
|
|
209
|
+
`[ExecutionContract] sessionDefaults.kvcache.layout="bdpa" requires architecture.maxSeqLen <= ${BDPA_MAX_KV_LEN}; ` +
|
|
210
|
+
`got ${session.kvLen}.`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (
|
|
216
|
+
session.layout === 'tiered'
|
|
217
|
+
&& session.coldQuantMode !== 'none'
|
|
218
|
+
&& session.headDim > TIERED_MAX_QUANT_HEAD_DIM
|
|
219
|
+
) {
|
|
220
|
+
errors.push(
|
|
221
|
+
`[ExecutionContract] sessionDefaults.kvcache.layout="tiered" with cold quantization requires ` +
|
|
222
|
+
`architecture.headDim <= ${TIERED_MAX_QUANT_HEAD_DIM}; got ${session.headDim}.`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
checks.push({
|
|
227
|
+
id: `${modelId}.session`,
|
|
228
|
+
ok: errors.length === sessionErrorCount,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
ok: errors.length === 0,
|
|
233
|
+
errors,
|
|
234
|
+
checks,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function validateManifestExecutionContract(manifest) {
|
|
239
|
+
const facts = extractExecutionContractFacts(manifest);
|
|
240
|
+
const evaluation = validateExecutionContractFacts(facts);
|
|
241
|
+
return {
|
|
242
|
+
...evaluation,
|
|
243
|
+
facts,
|
|
244
|
+
};
|
|
245
|
+
}
|
package/src/config/loader.js
CHANGED
|
@@ -14,6 +14,7 @@ const gemma3Preset = await loadJson('./presets/models/gemma3.json', import.meta.
|
|
|
14
14
|
const translateGemmaPreset = await loadJson('./presets/models/translategemma.json', import.meta.url, 'Failed to load preset');
|
|
15
15
|
const embeddingGemmaPreset = await loadJson('./presets/models/embeddinggemma.json', import.meta.url, 'Failed to load preset');
|
|
16
16
|
const functiongemmaPreset = await loadJson('./presets/models/functiongemma.json', import.meta.url, 'Failed to load preset');
|
|
17
|
+
const janusTextPreset = await loadJson('./presets/models/janus-text.json', import.meta.url, 'Failed to load preset');
|
|
17
18
|
const llama3Preset = await loadJson('./presets/models/llama3.json', import.meta.url, 'Failed to load preset');
|
|
18
19
|
const mixtralPreset = await loadJson('./presets/models/mixtral.json', import.meta.url, 'Failed to load preset');
|
|
19
20
|
const deepseekPreset = await loadJson('./presets/models/deepseek.json', import.meta.url, 'Failed to load preset');
|
|
@@ -36,6 +37,7 @@ export const PRESET_REGISTRY = {
|
|
|
36
37
|
translategemma: translateGemmaPreset,
|
|
37
38
|
embeddinggemma: embeddingGemmaPreset,
|
|
38
39
|
functiongemma: functiongemmaPreset,
|
|
40
|
+
janus_text: janusTextPreset,
|
|
39
41
|
llama3: llama3Preset,
|
|
40
42
|
mixtral: mixtralPreset,
|
|
41
43
|
deepseek: deepseekPreset,
|
|
@@ -85,6 +87,7 @@ export const PRESET_DETECTION_ORDER = [
|
|
|
85
87
|
// Most specific first (model variants)
|
|
86
88
|
'functiongemma',
|
|
87
89
|
'embeddinggemma',
|
|
90
|
+
'janus_text',
|
|
88
91
|
'modernbert',
|
|
89
92
|
'diffusion',
|
|
90
93
|
// Model families (check more specific patterns first)
|
|
@@ -107,8 +110,9 @@ export function detectPreset(
|
|
|
107
110
|
config,
|
|
108
111
|
architecture
|
|
109
112
|
) {
|
|
113
|
+
const nestedTextConfig = getNestedTextConfig(config);
|
|
110
114
|
const archLower = (architecture || '').toLowerCase();
|
|
111
|
-
const modelType = (config.model_type
|
|
115
|
+
const modelType = String(nestedTextConfig?.model_type ?? config.model_type ?? '').toLowerCase();
|
|
112
116
|
// Weak hint case: architecture fallback is often model_type copy (e.g. qwen2).
|
|
113
117
|
const hintsAreWeak = !archLower || !modelType || archLower === modelType;
|
|
114
118
|
|
|
@@ -317,41 +321,56 @@ function assertArchitecture(manifest, architecture) {
|
|
|
317
321
|
// =============================================================================
|
|
318
322
|
|
|
319
323
|
function extractArchitectureFromConfig(config) {
|
|
324
|
+
const nestedTextConfig = getNestedTextConfig(config);
|
|
320
325
|
return {
|
|
321
|
-
numLayers: config.num_hidden_layers ?? config.n_layer ?? config.blockCount,
|
|
322
|
-
hiddenSize: config.hidden_size ?? config.n_embd ?? config.embeddingLength,
|
|
323
|
-
intermediateSize: config.intermediate_size ?? config.n_inner ?? config.feedForwardLength,
|
|
324
|
-
numAttentionHeads: config.num_attention_heads ?? config.n_head ?? config.attentionHeadCount,
|
|
325
|
-
numKeyValueHeads: config.num_key_value_heads ?? config.attentionHeadCountKV,
|
|
326
|
-
headDim: config.head_dim,
|
|
327
|
-
vocabSize: config.vocab_size ?? config.vocabSize,
|
|
328
|
-
maxSeqLen: config.max_position_embeddings ?? config.n_positions ?? config.contextLength,
|
|
329
|
-
ropeTheta: config.rope_theta ?? config.ropeFreqBase,
|
|
330
|
-
rmsNormEps: config.rms_norm_eps ?? config.attentionLayerNormRMSEpsilon,
|
|
326
|
+
numLayers: config.num_hidden_layers ?? nestedTextConfig?.num_hidden_layers ?? config.n_layer ?? config.blockCount,
|
|
327
|
+
hiddenSize: config.hidden_size ?? nestedTextConfig?.hidden_size ?? config.n_embd ?? config.embeddingLength,
|
|
328
|
+
intermediateSize: config.intermediate_size ?? nestedTextConfig?.intermediate_size ?? config.n_inner ?? config.feedForwardLength,
|
|
329
|
+
numAttentionHeads: config.num_attention_heads ?? nestedTextConfig?.num_attention_heads ?? config.n_head ?? config.attentionHeadCount,
|
|
330
|
+
numKeyValueHeads: config.num_key_value_heads ?? nestedTextConfig?.num_key_value_heads ?? config.attentionHeadCountKV,
|
|
331
|
+
headDim: config.head_dim ?? nestedTextConfig?.head_dim,
|
|
332
|
+
vocabSize: config.vocab_size ?? nestedTextConfig?.vocab_size ?? config.vocabSize,
|
|
333
|
+
maxSeqLen: config.max_position_embeddings ?? nestedTextConfig?.max_position_embeddings ?? config.n_positions ?? config.contextLength,
|
|
334
|
+
ropeTheta: config.rope_theta ?? nestedTextConfig?.rope_theta ?? config.ropeFreqBase,
|
|
335
|
+
rmsNormEps: config.rms_norm_eps ?? nestedTextConfig?.rms_norm_eps ?? config.attentionLayerNormRMSEpsilon,
|
|
331
336
|
};
|
|
332
337
|
}
|
|
333
338
|
|
|
334
339
|
function extractInferenceFromConfig(config) {
|
|
340
|
+
const nestedTextConfig = getNestedTextConfig(config);
|
|
335
341
|
return {
|
|
336
342
|
attention: {
|
|
337
|
-
slidingWindow: config.sliding_window,
|
|
338
|
-
attnLogitSoftcapping: config.attn_logit_softcapping,
|
|
339
|
-
attentionOutputGate: config.attn_output_gate,
|
|
343
|
+
slidingWindow: config.sliding_window ?? nestedTextConfig?.sliding_window,
|
|
344
|
+
attnLogitSoftcapping: config.attn_logit_softcapping ?? nestedTextConfig?.attn_logit_softcapping,
|
|
345
|
+
attentionOutputGate: config.attn_output_gate ?? nestedTextConfig?.attn_output_gate,
|
|
340
346
|
},
|
|
341
347
|
output: {
|
|
342
|
-
finalLogitSoftcapping: config.final_logit_softcapping,
|
|
343
|
-
tieWordEmbeddings: config.tie_word_embeddings,
|
|
344
|
-
scaleEmbeddings: config.scale_embeddings,
|
|
348
|
+
finalLogitSoftcapping: config.final_logit_softcapping ?? nestedTextConfig?.final_logit_softcapping,
|
|
349
|
+
tieWordEmbeddings: config.tie_word_embeddings ?? nestedTextConfig?.tie_word_embeddings,
|
|
350
|
+
scaleEmbeddings: config.scale_embeddings ?? nestedTextConfig?.scale_embeddings,
|
|
345
351
|
},
|
|
346
|
-
pipeline: config.pipeline,
|
|
352
|
+
pipeline: config.pipeline ?? nestedTextConfig?.pipeline,
|
|
347
353
|
rope: {
|
|
348
|
-
ropeTheta: config.rope_theta ?? config.ropeFreqBase,
|
|
349
|
-
ropeScalingType: config.rope_scaling_type,
|
|
350
|
-
ropeScalingFactor: config.rope_scaling_factor,
|
|
354
|
+
ropeTheta: config.rope_theta ?? nestedTextConfig?.rope_theta ?? config.ropeFreqBase,
|
|
355
|
+
ropeScalingType: config.rope_scaling_type ?? nestedTextConfig?.rope_scaling_type,
|
|
356
|
+
ropeScalingFactor: config.rope_scaling_factor ?? nestedTextConfig?.rope_scaling_factor,
|
|
351
357
|
},
|
|
352
358
|
};
|
|
353
359
|
}
|
|
354
360
|
|
|
361
|
+
function getNestedTextConfig(config) {
|
|
362
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
if (config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)) {
|
|
366
|
+
return config.text_config;
|
|
367
|
+
}
|
|
368
|
+
if (config.language_config && typeof config.language_config === 'object' && !Array.isArray(config.language_config)) {
|
|
369
|
+
return config.language_config;
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
|
|
355
374
|
function extractTokenizerFromManifest(manifest) {
|
|
356
375
|
if (!manifest.tokenizer) return {};
|
|
357
376
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "janus_text",
|
|
3
|
+
"name": "Janus Text Core",
|
|
4
|
+
"extends": "transformer",
|
|
5
|
+
"modelType": "transformer",
|
|
6
|
+
|
|
7
|
+
"inference": {
|
|
8
|
+
"normalization": {
|
|
9
|
+
"rmsNormEps": 1e-6
|
|
10
|
+
},
|
|
11
|
+
"rope": {
|
|
12
|
+
"ropeTheta": 10000
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
"tokenizer": {
|
|
17
|
+
"addBosToken": true,
|
|
18
|
+
"addEosToken": false
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
"detection": {
|
|
22
|
+
"architecturePatterns": ["JanusTextForCausalLM"],
|
|
23
|
+
"modelTypePatterns": ["janus_text"]
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/converter/core.js
CHANGED
|
@@ -76,7 +76,8 @@ function resolveTokenizerField(tokenizerConfig, ...keys) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function resolveTokenizerVocabSize(tokenizerConfig, rawConfig, architecture) {
|
|
79
|
-
const
|
|
79
|
+
const nestedTextConfig = getNestedTextConfig(rawConfig);
|
|
80
|
+
const configVocab = rawConfig?.vocab_size ?? nestedTextConfig?.vocab_size;
|
|
80
81
|
const tokenizerVocab = tokenizerConfig?.vocab_size ?? tokenizerConfig?.vocabSize;
|
|
81
82
|
const archVocab = architecture?.vocabSize;
|
|
82
83
|
return tokenizerVocab ?? configVocab ?? archVocab ?? null;
|
|
@@ -223,21 +224,22 @@ function toFloat32ForQ4K(tensorData, sourceDtype, tensorName) {
|
|
|
223
224
|
|
|
224
225
|
function resolveConfigTokenId(rawConfig, key) {
|
|
225
226
|
const direct = rawConfig?.[key];
|
|
226
|
-
const nested = rawConfig?.
|
|
227
|
+
const nested = getNestedTextConfig(rawConfig)?.[key];
|
|
227
228
|
return resolveTokenizerId(direct ?? nested);
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
function resolveConfigTokenIds(rawConfig, key) {
|
|
231
232
|
const direct = rawConfig?.[key];
|
|
232
|
-
const nested = rawConfig?.
|
|
233
|
+
const nested = getNestedTextConfig(rawConfig)?.[key];
|
|
233
234
|
return resolveTokenizerIds(direct ?? nested);
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
function resolveMoEConfigNumber(rawConfig, ...keys) {
|
|
238
|
+
const nestedTextConfig = getNestedTextConfig(rawConfig);
|
|
237
239
|
for (const key of keys) {
|
|
238
240
|
const direct = rawConfig?.[key];
|
|
239
241
|
if (Number.isFinite(direct) && direct > 0) return Number(direct);
|
|
240
|
-
const nested =
|
|
242
|
+
const nested = nestedTextConfig?.[key];
|
|
241
243
|
if (Number.isFinite(nested) && nested > 0) return Number(nested);
|
|
242
244
|
}
|
|
243
245
|
return null;
|
|
@@ -317,7 +319,7 @@ function resolveIntermediateSizeFromTensors(architecture, model, tensorLocations
|
|
|
317
319
|
if (typeof current !== 'number' || !Number.isFinite(current) || current <= 0) {
|
|
318
320
|
return architecture;
|
|
319
321
|
}
|
|
320
|
-
const modelType = String(rawConfig?.model_type ?? rawConfig?.
|
|
322
|
+
const modelType = String(rawConfig?.model_type ?? getNestedTextConfig(rawConfig)?.model_type ?? '').toLowerCase();
|
|
321
323
|
if (modelType !== 'lfm2') {
|
|
322
324
|
return architecture;
|
|
323
325
|
}
|
|
@@ -359,7 +361,7 @@ function resolveMoEExpertFormat(rawConfig, resolvedModelType, quantizationInfo,
|
|
|
359
361
|
const modelType = String(
|
|
360
362
|
resolvedModelType ??
|
|
361
363
|
rawConfig?.model_type ??
|
|
362
|
-
rawConfig?.
|
|
364
|
+
getNestedTextConfig(rawConfig)?.model_type ??
|
|
363
365
|
''
|
|
364
366
|
).toLowerCase();
|
|
365
367
|
if (modelType.includes('gpt_oss') || modelType.includes('gpt-oss') || modelType.includes('gptoss')) {
|
|
@@ -725,9 +727,7 @@ export function extractArchitecture(config, ggufConfig) {
|
|
|
725
727
|
|
|
726
728
|
// Try HuggingFace config first
|
|
727
729
|
if (config && Object.keys(config).length > 0) {
|
|
728
|
-
const textConfig = (
|
|
729
|
-
config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)
|
|
730
|
-
) ? config.text_config : null;
|
|
730
|
+
const textConfig = getNestedTextConfig(config);
|
|
731
731
|
const fromConfig = (...keys) => {
|
|
732
732
|
const values = [];
|
|
733
733
|
for (const key of keys) {
|
|
@@ -860,6 +860,19 @@ export function extractArchitecture(config, ggufConfig) {
|
|
|
860
860
|
throw new Error('Missing model config: cannot extract architecture');
|
|
861
861
|
}
|
|
862
862
|
|
|
863
|
+
function getNestedTextConfig(config) {
|
|
864
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
if (config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)) {
|
|
868
|
+
return config.text_config;
|
|
869
|
+
}
|
|
870
|
+
if (config.language_config && typeof config.language_config === 'object' && !Array.isArray(config.language_config)) {
|
|
871
|
+
return config.language_config;
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
|
|
863
876
|
|
|
864
877
|
export function buildTensorMap(tensors, shardSize) {
|
|
865
878
|
if (!shardSize || shardSize <= 0) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
|
|
2
|
+
const nestedTextConfig = getNestedTextConfig(config);
|
|
2
3
|
const candidateSources = [
|
|
3
4
|
tokenizer?.eosTokenId,
|
|
4
5
|
tokenizer?.eos_token_id,
|
|
@@ -7,9 +8,9 @@ export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
|
|
|
7
8
|
tokenizerJson?.special_tokens?.eos,
|
|
8
9
|
tokenizerJson?.special_tokens?.eos_token_id,
|
|
9
10
|
config?.eos_token_id,
|
|
10
|
-
|
|
11
|
+
nestedTextConfig?.eos_token_id,
|
|
11
12
|
config?.eos_token_ids,
|
|
12
|
-
|
|
13
|
+
nestedTextConfig?.eos_token_ids,
|
|
13
14
|
];
|
|
14
15
|
|
|
15
16
|
for (const candidate of candidateSources) {
|
|
@@ -23,7 +24,7 @@ export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
|
|
|
23
24
|
tokenizerJson?.specialTokens?.eos_token,
|
|
24
25
|
tokenizerJson?.special_tokens?.eos_token,
|
|
25
26
|
config?.eos_token,
|
|
26
|
-
|
|
27
|
+
nestedTextConfig?.eos_token,
|
|
27
28
|
];
|
|
28
29
|
|
|
29
30
|
for (const candidate of eosTokenStringCandidates) {
|
|
@@ -48,6 +49,19 @@ export function resolveEosTokenId({ config, tokenizer, tokenizerJson }) {
|
|
|
48
49
|
throw new Error('Missing eos_token_id. Provide eos_token_id in config or tokenizer metadata.');
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
function getNestedTextConfig(config) {
|
|
53
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (config.text_config && typeof config.text_config === 'object' && !Array.isArray(config.text_config)) {
|
|
57
|
+
return config.text_config;
|
|
58
|
+
}
|
|
59
|
+
if (config.language_config && typeof config.language_config === 'object' && !Array.isArray(config.language_config)) {
|
|
60
|
+
return config.language_config;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
51
65
|
function normalizeEosTokenId(value) {
|
|
52
66
|
if (Array.isArray(value)) {
|
|
53
67
|
if (value.length === 0 || value.some((id) => typeof id !== 'number')) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateTensorConfigConsistency } from './tensor-config-validator.js';
|
|
2
|
+
import { validateManifestExecutionContract } from '../../config/execution-contract-check.js';
|
|
2
3
|
|
|
3
4
|
export function validateManifest(manifest) {
|
|
4
5
|
const errors = [];
|
|
@@ -196,5 +197,17 @@ export function validateManifest(manifest) {
|
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
if (!isDiffusion && !isEnergy && errors.length === 0) {
|
|
201
|
+
try {
|
|
202
|
+
const executionContract = validateManifestExecutionContract(manifest);
|
|
203
|
+
for (const error of executionContract.errors) {
|
|
204
|
+
errors.push(error);
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
208
|
+
errors.push(`[ExecutionContract] ${message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
199
212
|
return { valid: errors.length === 0, errors, warnings };
|
|
200
213
|
}
|
package/src/index-browser.d.ts
CHANGED
package/src/index-browser.js
CHANGED
package/src/index.d.ts
CHANGED