@simulatte/doppler 0.1.5 → 0.1.6
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 +23 -8
- package/package.json +7 -4
- package/src/config/kernels/kernel-ref-digests.js +39 -39
- package/src/config/kernels/registry.json +42 -2
- package/src/config/loader.js +31 -2
- package/src/config/merge.js +18 -0
- package/src/config/presets/models/qwen3.json +9 -2
- package/src/config/presets/models/transformer.json +5 -0
- package/src/config/required-inference-fields-contract-check.js +6 -0
- package/src/config/schema/inference-defaults.schema.js +3 -0
- package/src/config/schema/inference.schema.d.ts +9 -0
- package/src/config/schema/kernel-path.schema.d.ts +6 -0
- package/src/config/schema/manifest.schema.d.ts +6 -0
- package/src/config/schema/manifest.schema.js +3 -0
- package/src/converter/rope-config.js +42 -0
- package/src/gpu/device.js +58 -0
- package/src/gpu/kernels/attention.js +98 -0
- package/src/gpu/kernels/bias_add.wgsl +8 -6
- package/src/gpu/kernels/bias_add_f16.wgsl +8 -5
- package/src/gpu/kernels/conv2d.js +1 -1
- package/src/gpu/kernels/conv2d.wgsl +7 -8
- package/src/gpu/kernels/conv2d_f16.wgsl +7 -8
- package/src/gpu/kernels/depthwise_conv2d.js +2 -1
- package/src/gpu/kernels/depthwise_conv2d.wgsl +6 -9
- package/src/gpu/kernels/depthwise_conv2d_f16.wgsl +6 -9
- package/src/gpu/kernels/grouped_pointwise_conv2d.js +2 -1
- package/src/gpu/kernels/grouped_pointwise_conv2d.wgsl +6 -9
- package/src/gpu/kernels/grouped_pointwise_conv2d_f16.wgsl +6 -9
- package/src/gpu/kernels/matmul.js +25 -0
- package/src/gpu/kernels/pixel_shuffle.js +1 -1
- package/src/gpu/kernels/pixel_shuffle.wgsl +4 -5
- package/src/gpu/kernels/pixel_shuffle_f16.wgsl +4 -5
- package/src/gpu/kernels/relu.js +15 -2
- package/src/gpu/kernels/relu.wgsl +2 -1
- package/src/gpu/kernels/relu_f16.wgsl +2 -1
- package/src/gpu/kernels/repeat_channels.js +1 -1
- package/src/gpu/kernels/repeat_channels.wgsl +4 -5
- package/src/gpu/kernels/repeat_channels_f16.wgsl +4 -5
- package/src/gpu/kernels/residual.js +44 -8
- package/src/gpu/kernels/residual.wgsl +6 -3
- package/src/gpu/kernels/residual_f16.wgsl +2 -1
- package/src/gpu/kernels/residual_f16_vec4.wgsl +2 -1
- package/src/gpu/kernels/residual_vec4.wgsl +2 -1
- package/src/gpu/kernels/rmsnorm.js +58 -6
- package/src/gpu/kernels/rmsnorm.wgsl +14 -6
- package/src/gpu/kernels/rmsnorm_f16.wgsl +10 -2
- package/src/gpu/kernels/rope.d.ts +2 -0
- package/src/gpu/kernels/rope.js +11 -1
- package/src/gpu/kernels/rope.wgsl +56 -40
- package/src/gpu/kernels/sana_linear_attention.js +1 -2
- package/src/gpu/kernels/sana_linear_attention_apply.wgsl +4 -5
- package/src/gpu/kernels/sana_linear_attention_apply_f16.wgsl +4 -5
- package/src/gpu/kernels/sana_linear_attention_summary.wgsl +4 -0
- package/src/gpu/kernels/sana_linear_attention_summary_f16.wgsl +4 -0
- package/src/gpu/kernels/silu.d.ts +1 -0
- package/src/gpu/kernels/silu.js +32 -14
- package/src/gpu/kernels/silu.wgsl +19 -9
- package/src/gpu/kernels/silu_f16.wgsl +19 -9
- package/src/gpu/kernels/transpose.js +15 -2
- package/src/gpu/kernels/transpose.wgsl +5 -6
- package/src/gpu/kernels/upsample2d.js +2 -1
- package/src/gpu/kernels/upsample2d.wgsl +6 -9
- package/src/gpu/kernels/upsample2d_f16.wgsl +6 -9
- package/src/gpu/kernels/utils.js +16 -1
- package/src/inference/browser-harness.js +47 -1
- package/src/inference/pipelines/diffusion/pipeline.js +15 -6
- package/src/inference/pipelines/diffusion/text-encoder-gpu.d.ts +5 -0
- package/src/inference/pipelines/diffusion/text-encoder-gpu.js +27 -15
- package/src/inference/pipelines/text/attention/record.js +11 -2
- package/src/inference/pipelines/text/attention/run.js +11 -2
- package/src/inference/pipelines/text/chat-format.js +25 -1
- package/src/inference/pipelines/text/config.d.ts +4 -0
- package/src/inference/pipelines/text/config.js +68 -1
- package/src/inference/pipelines/text/execution-plan.js +23 -31
- package/src/inference/pipelines/text/execution-v0.js +29 -2
- package/src/inference/pipelines/text/ffn/standard.js +3 -0
- package/src/inference/pipelines/text/init.d.ts +4 -0
- package/src/inference/pipelines/text/init.js +56 -9
- package/src/inference/pipelines/text/layer.js +11 -0
- package/src/inference/pipelines/text.js +4 -0
- package/src/inference/tokenizers/bundled.js +156 -33
- package/src/rules/tooling/command-runtime.rules.json +18 -0
- package/src/tooling/command-api.d.ts +27 -1
- package/src/tooling/command-api.js +142 -3
- package/src/tooling/node-browser-command-runner.d.ts +4 -0
- package/src/tooling/node-browser-command-runner.js +58 -3
- package/src/tooling/node-command-runner.js +15 -0
- package/src/tooling/node-webgpu.js +9 -87
- package/src/training/checkpoint-watch.d.ts +7 -0
- package/src/training/checkpoint-watch.js +106 -0
- package/src/training/checkpoint.d.ts +6 -1
- package/src/training/checkpoint.js +12 -2
- package/src/training/distillation/artifacts.d.ts +71 -0
- package/src/training/distillation/artifacts.js +132 -0
- package/src/training/distillation/checkpoint-watch.d.ts +10 -0
- package/src/training/distillation/checkpoint-watch.js +57 -0
- package/src/training/distillation/dataset.d.ts +59 -0
- package/src/training/distillation/dataset.js +337 -0
- package/src/training/distillation/eval.d.ts +34 -0
- package/src/training/distillation/eval.js +310 -0
- package/src/training/distillation/index.d.ts +29 -0
- package/src/training/distillation/index.js +29 -0
- package/src/training/distillation/runtime.d.ts +20 -0
- package/src/training/distillation/runtime.js +121 -0
- package/src/training/distillation/scoreboard.d.ts +6 -0
- package/src/training/distillation/scoreboard.js +8 -0
- package/src/training/distillation/stage-a.d.ts +45 -0
- package/src/training/distillation/stage-a.js +338 -0
- package/src/training/distillation/stage-b.d.ts +24 -0
- package/src/training/distillation/stage-b.js +20 -0
- package/src/training/index.d.ts +10 -0
- package/src/training/index.js +10 -0
- package/src/training/lora-pipeline.d.ts +40 -0
- package/src/training/lora-pipeline.js +796 -0
- package/src/training/operator-artifacts.d.ts +62 -0
- package/src/training/operator-artifacts.js +140 -0
- package/src/training/operator-command.d.ts +5 -0
- package/src/training/operator-command.js +453 -0
- package/src/training/operator-eval.d.ts +48 -0
- package/src/training/operator-eval.js +230 -0
- package/src/training/operator-scoreboard.d.ts +5 -0
- package/src/training/operator-scoreboard.js +44 -0
- package/src/training/runner.d.ts +52 -0
- package/src/training/runner.js +29 -4
- package/src/training/suite.d.ts +112 -0
- package/src/training/suite.js +9 -9
- package/src/training/workloads.d.ts +164 -0
- package/src/training/workloads.js +539 -0
- package/src/version.js +1 -1
- package/tools/doppler-cli.js +137 -40
|
@@ -27,6 +27,24 @@
|
|
|
27
27
|
"intent": "verify"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"match": {
|
|
32
|
+
"command": "lora"
|
|
33
|
+
},
|
|
34
|
+
"value": {
|
|
35
|
+
"suite": null,
|
|
36
|
+
"intent": null
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"match": {
|
|
41
|
+
"command": "distill"
|
|
42
|
+
},
|
|
43
|
+
"value": {
|
|
44
|
+
"suite": null,
|
|
45
|
+
"intent": null
|
|
46
|
+
}
|
|
47
|
+
},
|
|
30
48
|
{
|
|
31
49
|
"match": {},
|
|
32
50
|
"value": {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { ConverterConfigSchema } from '../config/schema/converter.schema.js';
|
|
2
2
|
|
|
3
|
-
export type ToolingCommand = 'convert' | 'debug' | 'bench' | 'verify';
|
|
3
|
+
export type ToolingCommand = 'convert' | 'debug' | 'bench' | 'verify' | 'lora' | 'distill';
|
|
4
4
|
export type ToolingSurface = 'browser' | 'node';
|
|
5
5
|
export type ToolingSuite = 'kernels' | 'inference' | 'training' | 'bench' | 'debug' | 'diffusion' | 'energy';
|
|
6
6
|
export type ToolingIntent = 'verify' | 'investigate' | 'calibrate' | null;
|
|
7
7
|
export type ToolingTrainingStage = 'stage1_joint' | 'stage2_base' | 'stage_a' | 'stage_b';
|
|
8
|
+
export type ToolingDistillAction = 'run' | 'stage-a' | 'stage-b' | 'eval' | 'watch' | 'compare' | 'quality-gate' | 'subsets';
|
|
9
|
+
export type ToolingLoraAction = 'run' | 'eval' | 'watch' | 'export' | 'compare' | 'quality-gate' | 'activate';
|
|
8
10
|
|
|
9
11
|
export interface ToolingConvertExecutionPayload {
|
|
10
12
|
workers?: number | null;
|
|
@@ -25,6 +27,7 @@ export interface ToolingConvertPayload {
|
|
|
25
27
|
|
|
26
28
|
export interface ToolingCommandRequestInput {
|
|
27
29
|
command: ToolingCommand;
|
|
30
|
+
action?: ToolingDistillAction | ToolingLoraAction;
|
|
28
31
|
suite?: ToolingSuite;
|
|
29
32
|
modelId?: string;
|
|
30
33
|
trainingTests?: string[];
|
|
@@ -65,6 +68,17 @@ export interface ToolingCommandRequestInput {
|
|
|
65
68
|
inputDir?: string;
|
|
66
69
|
outputDir?: string;
|
|
67
70
|
convertPayload?: ToolingConvertPayload;
|
|
71
|
+
workloadPath?: string;
|
|
72
|
+
runRoot?: string;
|
|
73
|
+
checkpointPath?: string;
|
|
74
|
+
checkpointId?: string;
|
|
75
|
+
checkpointStep?: number;
|
|
76
|
+
stageId?: string;
|
|
77
|
+
stageArtifact?: string;
|
|
78
|
+
subsetManifest?: string;
|
|
79
|
+
evalDatasetId?: string;
|
|
80
|
+
pollIntervalMs?: number;
|
|
81
|
+
stopWhenIdle?: boolean;
|
|
68
82
|
captureOutput?: boolean;
|
|
69
83
|
keepPipeline?: boolean;
|
|
70
84
|
report?: Record<string, unknown> | null;
|
|
@@ -76,6 +90,7 @@ export interface ToolingCommandRequest {
|
|
|
76
90
|
command: ToolingCommand;
|
|
77
91
|
suite: ToolingSuite | null;
|
|
78
92
|
intent: ToolingIntent;
|
|
93
|
+
action: ToolingDistillAction | ToolingLoraAction | null;
|
|
79
94
|
modelId: string | null;
|
|
80
95
|
trainingTests: string[] | null;
|
|
81
96
|
trainingStage: ToolingTrainingStage | null;
|
|
@@ -115,6 +130,17 @@ export interface ToolingCommandRequest {
|
|
|
115
130
|
inputDir: string | null;
|
|
116
131
|
outputDir: string | null;
|
|
117
132
|
convertPayload: ToolingConvertPayload | null;
|
|
133
|
+
workloadPath: string | null;
|
|
134
|
+
runRoot: string | null;
|
|
135
|
+
checkpointPath: string | null;
|
|
136
|
+
checkpointId: string | null;
|
|
137
|
+
checkpointStep: number | null;
|
|
138
|
+
stageId: string | null;
|
|
139
|
+
stageArtifact: string | null;
|
|
140
|
+
subsetManifest: string | null;
|
|
141
|
+
evalDatasetId: string | null;
|
|
142
|
+
pollIntervalMs: number | null;
|
|
143
|
+
stopWhenIdle: boolean | null;
|
|
118
144
|
captureOutput: boolean;
|
|
119
145
|
keepPipeline: boolean;
|
|
120
146
|
report: Record<string, unknown> | null;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { isPlainObject } from '../utils/plain-object.js';
|
|
2
2
|
import { selectRuleValue } from '../rules/rule-registry.js';
|
|
3
3
|
|
|
4
|
-
const TOOLING_COMMAND_SET = ['convert', 'debug', 'bench', 'verify'];
|
|
4
|
+
const TOOLING_COMMAND_SET = ['convert', 'debug', 'bench', 'verify', 'lora', 'distill'];
|
|
5
5
|
const TOOLING_SURFACE_SET = ['browser', 'node'];
|
|
6
6
|
const TOOLING_SUITE_SET = ['kernels', 'inference', 'training', 'bench', 'debug', 'diffusion', 'energy'];
|
|
7
7
|
const TOOLING_INTENT_SET = ['verify', 'investigate', 'calibrate'];
|
|
8
8
|
const VERIFY_SUITES = ['kernels', 'inference', 'training', 'diffusion', 'energy'];
|
|
9
9
|
const TRAINING_STAGE_SET = ['stage1_joint', 'stage2_base', 'stage_a', 'stage_b'];
|
|
10
|
+
const DISTILL_ACTION_SET = ['run', 'stage-a', 'stage-b', 'eval', 'watch', 'compare', 'quality-gate', 'subsets'];
|
|
11
|
+
const LORA_ACTION_SET = ['run', 'eval', 'watch', 'export', 'compare', 'quality-gate', 'activate'];
|
|
10
12
|
const TRAINING_COMMAND_SCHEMA_VERSION = 1;
|
|
11
13
|
|
|
12
14
|
export const TOOLING_COMMANDS = Object.freeze([...TOOLING_COMMAND_SET]);
|
|
@@ -82,6 +84,15 @@ function asOptionalForceResumeReason(value, label) {
|
|
|
82
84
|
return reason;
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
function asOptionalAction(value, label, allowed) {
|
|
88
|
+
const action = asOptionalString(value, label);
|
|
89
|
+
if (!action) return null;
|
|
90
|
+
if (!allowed.includes(action)) {
|
|
91
|
+
throw new Error(`tooling command: ${label} must be one of ${allowed.join(', ')}.`);
|
|
92
|
+
}
|
|
93
|
+
return action;
|
|
94
|
+
}
|
|
95
|
+
|
|
85
96
|
function assertCommand(value) {
|
|
86
97
|
const command = asOptionalString(value, 'command');
|
|
87
98
|
if (!command) {
|
|
@@ -246,6 +257,7 @@ function normalizeConvert(raw) {
|
|
|
246
257
|
command: 'convert',
|
|
247
258
|
suite: null,
|
|
248
259
|
intent: null,
|
|
260
|
+
action: null,
|
|
249
261
|
modelId: null,
|
|
250
262
|
trainingTests: null,
|
|
251
263
|
trainingStage: null,
|
|
@@ -285,6 +297,113 @@ function normalizeConvert(raw) {
|
|
|
285
297
|
inputDir,
|
|
286
298
|
outputDir,
|
|
287
299
|
convertPayload: payload,
|
|
300
|
+
workloadPath: null,
|
|
301
|
+
runRoot: null,
|
|
302
|
+
checkpointPath: null,
|
|
303
|
+
checkpointId: null,
|
|
304
|
+
checkpointStep: null,
|
|
305
|
+
stageId: null,
|
|
306
|
+
stageArtifact: null,
|
|
307
|
+
subsetManifest: null,
|
|
308
|
+
evalDatasetId: null,
|
|
309
|
+
pollIntervalMs: null,
|
|
310
|
+
stopWhenIdle: null,
|
|
311
|
+
captureOutput: false,
|
|
312
|
+
keepPipeline: false,
|
|
313
|
+
report: asOptionalObject(raw.report, 'report'),
|
|
314
|
+
timestamp: raw.timestamp ?? null,
|
|
315
|
+
searchParams: raw.searchParams ?? null,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function normalizeTrainingOperatorCommand(raw, command) {
|
|
320
|
+
const allowedActions = command === 'distill' ? DISTILL_ACTION_SET : LORA_ACTION_SET;
|
|
321
|
+
const action = asOptionalAction(raw.action, 'action', allowedActions);
|
|
322
|
+
if (!action) {
|
|
323
|
+
throw new Error(`tooling command: ${command} requires action.`);
|
|
324
|
+
}
|
|
325
|
+
const workloadPath = asOptionalString(raw.workloadPath, 'workloadPath');
|
|
326
|
+
const runRoot = asOptionalString(raw.runRoot, 'runRoot');
|
|
327
|
+
const checkpointPath = asOptionalString(raw.checkpointPath, 'checkpointPath');
|
|
328
|
+
const checkpointId = asOptionalString(raw.checkpointId, 'checkpointId');
|
|
329
|
+
const checkpointStep = asOptionalPositiveInteger(raw.checkpointStep, 'checkpointStep');
|
|
330
|
+
const stageId = asOptionalString(raw.stageId, 'stageId');
|
|
331
|
+
const stageArtifact = asOptionalString(raw.stageArtifact, 'stageArtifact');
|
|
332
|
+
const subsetManifest = asOptionalString(raw.subsetManifest, 'subsetManifest');
|
|
333
|
+
const evalDatasetId = asOptionalString(raw.evalDatasetId, 'evalDatasetId');
|
|
334
|
+
const pollIntervalMs = asOptionalPositiveInteger(raw.pollIntervalMs, 'pollIntervalMs');
|
|
335
|
+
const stopWhenIdle = asOptionalBoolean(raw.stopWhenIdle, 'stopWhenIdle');
|
|
336
|
+
if (!workloadPath && !runRoot) {
|
|
337
|
+
throw new Error(`tooling command: ${command} requires workloadPath or runRoot.`);
|
|
338
|
+
}
|
|
339
|
+
if ((action === 'eval' || action === 'export') && !checkpointPath && !runRoot) {
|
|
340
|
+
throw new Error(`tooling command: ${command} ${action} requires checkpointPath or runRoot.`);
|
|
341
|
+
}
|
|
342
|
+
if (action === 'watch' && !runRoot) {
|
|
343
|
+
throw new Error(`tooling command: ${command} watch requires runRoot.`);
|
|
344
|
+
}
|
|
345
|
+
if ((action === 'compare' || action === 'quality-gate') && !runRoot) {
|
|
346
|
+
throw new Error(`tooling command: ${command} ${action} requires runRoot.`);
|
|
347
|
+
}
|
|
348
|
+
if (command === 'distill' && action === 'stage-b' && !stageArtifact && !runRoot) {
|
|
349
|
+
throw new Error('tooling command: distill stage-b requires stageArtifact or runRoot.');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
command,
|
|
354
|
+
suite: null,
|
|
355
|
+
intent: null,
|
|
356
|
+
action,
|
|
357
|
+
modelId: null,
|
|
358
|
+
trainingTests: null,
|
|
359
|
+
trainingStage: null,
|
|
360
|
+
trainingConfig: null,
|
|
361
|
+
stage1Artifact: null,
|
|
362
|
+
stage1ArtifactHash: null,
|
|
363
|
+
ulArtifactDir: null,
|
|
364
|
+
stageAArtifact: null,
|
|
365
|
+
stageAArtifactHash: null,
|
|
366
|
+
distillArtifactDir: null,
|
|
367
|
+
teacherModelId: null,
|
|
368
|
+
studentModelId: null,
|
|
369
|
+
distillDatasetId: null,
|
|
370
|
+
distillDatasetPath: null,
|
|
371
|
+
distillLanguagePair: null,
|
|
372
|
+
distillSourceLangs: null,
|
|
373
|
+
distillTargetLangs: null,
|
|
374
|
+
distillPairAllowlist: null,
|
|
375
|
+
strictPairContract: null,
|
|
376
|
+
distillShardIndex: null,
|
|
377
|
+
distillShardCount: null,
|
|
378
|
+
resumeFrom: null,
|
|
379
|
+
forceResume: null,
|
|
380
|
+
forceResumeReason: null,
|
|
381
|
+
forceResumeSource: null,
|
|
382
|
+
checkpointOperator: null,
|
|
383
|
+
trainingSchemaVersion: null,
|
|
384
|
+
trainingBenchSteps: null,
|
|
385
|
+
checkpointEvery: null,
|
|
386
|
+
workloadType: 'training',
|
|
387
|
+
modelUrl: null,
|
|
388
|
+
cacheMode: asOptionalCacheMode(raw.cacheMode, 'cacheMode'),
|
|
389
|
+
loadMode: asOptionalLoadMode(raw.loadMode, 'loadMode'),
|
|
390
|
+
runtimePreset: asOptionalString(raw.runtimePreset, 'runtimePreset'),
|
|
391
|
+
runtimeConfigUrl: asOptionalString(raw.runtimeConfigUrl, 'runtimeConfigUrl'),
|
|
392
|
+
runtimeConfig: asOptionalObject(raw.runtimeConfig, 'runtimeConfig'),
|
|
393
|
+
inputDir: null,
|
|
394
|
+
outputDir: null,
|
|
395
|
+
convertPayload: null,
|
|
396
|
+
workloadPath,
|
|
397
|
+
runRoot,
|
|
398
|
+
checkpointPath,
|
|
399
|
+
checkpointId,
|
|
400
|
+
checkpointStep,
|
|
401
|
+
stageId,
|
|
402
|
+
stageArtifact,
|
|
403
|
+
subsetManifest,
|
|
404
|
+
evalDatasetId,
|
|
405
|
+
pollIntervalMs,
|
|
406
|
+
stopWhenIdle,
|
|
288
407
|
captureOutput: false,
|
|
289
408
|
keepPipeline: false,
|
|
290
409
|
report: asOptionalObject(raw.report, 'report'),
|
|
@@ -428,6 +547,7 @@ function normalizeSuiteCommand(raw, command) {
|
|
|
428
547
|
command,
|
|
429
548
|
suite,
|
|
430
549
|
intent: runtimeContract.intent,
|
|
550
|
+
action: null,
|
|
431
551
|
modelId,
|
|
432
552
|
trainingTests,
|
|
433
553
|
trainingStage,
|
|
@@ -469,6 +589,17 @@ function normalizeSuiteCommand(raw, command) {
|
|
|
469
589
|
inputDir: null,
|
|
470
590
|
outputDir: null,
|
|
471
591
|
convertPayload: null,
|
|
592
|
+
workloadPath: null,
|
|
593
|
+
runRoot: null,
|
|
594
|
+
checkpointPath: null,
|
|
595
|
+
checkpointId: null,
|
|
596
|
+
checkpointStep: null,
|
|
597
|
+
stageId: null,
|
|
598
|
+
stageArtifact: null,
|
|
599
|
+
subsetManifest: null,
|
|
600
|
+
evalDatasetId: null,
|
|
601
|
+
pollIntervalMs: null,
|
|
602
|
+
stopWhenIdle: null,
|
|
472
603
|
captureOutput: asOptionalBoolean(raw.captureOutput, 'captureOutput') ?? false,
|
|
473
604
|
keepPipeline: asOptionalBoolean(raw.keepPipeline, 'keepPipeline') ?? false,
|
|
474
605
|
report: asOptionalObject(raw.report, 'report'),
|
|
@@ -485,6 +616,9 @@ export function normalizeToolingCommandRequest(input) {
|
|
|
485
616
|
if (command === 'convert') {
|
|
486
617
|
return normalizeConvert(input);
|
|
487
618
|
}
|
|
619
|
+
if (command === 'lora' || command === 'distill') {
|
|
620
|
+
return normalizeTrainingOperatorCommand(input, command);
|
|
621
|
+
}
|
|
488
622
|
return normalizeSuiteCommand(input, command);
|
|
489
623
|
}
|
|
490
624
|
|
|
@@ -514,8 +648,13 @@ export function ensureCommandSupportedOnSurface(commandRequest, surface) {
|
|
|
514
648
|
throw new Error(`tooling command: unsupported surface "${surface}".`);
|
|
515
649
|
}
|
|
516
650
|
|
|
517
|
-
|
|
518
|
-
|
|
651
|
+
if (
|
|
652
|
+
normalizedSurface === 'browser'
|
|
653
|
+
&& (request.command === 'lora' || request.command === 'distill')
|
|
654
|
+
) {
|
|
655
|
+
throw new Error(`tooling command: ${request.command} is currently Node-only and must fail closed on browser.`);
|
|
656
|
+
}
|
|
657
|
+
|
|
519
658
|
return {
|
|
520
659
|
request,
|
|
521
660
|
surface: normalizedSurface,
|
|
@@ -7,6 +7,10 @@ import type { BrowserCommandRunResult } from './browser-command-runner.js';
|
|
|
7
7
|
|
|
8
8
|
export interface NodeBrowserCommandRunOptions {
|
|
9
9
|
staticRootDir?: string;
|
|
10
|
+
staticMounts?: Array<{
|
|
11
|
+
urlPrefix: string;
|
|
12
|
+
rootDir: string;
|
|
13
|
+
}>;
|
|
10
14
|
baseUrl?: string;
|
|
11
15
|
host?: string;
|
|
12
16
|
port?: number;
|
|
@@ -67,8 +67,61 @@ function resolveStaticPath(rootDir, requestPath) {
|
|
|
67
67
|
return candidate;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
function normalizeStaticMounts(mounts = []) {
|
|
71
|
+
if (!Array.isArray(mounts)) {
|
|
72
|
+
throw new Error('browser command: staticMounts must be an array.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return mounts.map((mount, index) => {
|
|
76
|
+
if (!mount || typeof mount !== 'object' || Array.isArray(mount)) {
|
|
77
|
+
throw new Error(`browser command: staticMounts[${index}] must be an object.`);
|
|
78
|
+
}
|
|
79
|
+
const urlPrefix = String(mount.urlPrefix || '').trim();
|
|
80
|
+
const rootDir = String(mount.rootDir || '').trim();
|
|
81
|
+
if (!urlPrefix.startsWith('/')) {
|
|
82
|
+
throw new Error(`browser command: staticMounts[${index}].urlPrefix must start with "/".`);
|
|
83
|
+
}
|
|
84
|
+
if (!rootDir) {
|
|
85
|
+
throw new Error(`browser command: staticMounts[${index}].rootDir is required.`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
urlPrefix: urlPrefix.replace(/\/+$/u, '') || '/',
|
|
89
|
+
rootDir: path.resolve(rootDir),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findStaticRootForRequest(rootDir, mounts, requestPath) {
|
|
95
|
+
const normalizedPath = String(requestPath || '/');
|
|
96
|
+
let bestMount = null;
|
|
97
|
+
|
|
98
|
+
for (const mount of mounts) {
|
|
99
|
+
const prefix = mount.urlPrefix;
|
|
100
|
+
if (normalizedPath !== prefix && !normalizedPath.startsWith(`${prefix}/`)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!bestMount || prefix.length > bestMount.urlPrefix.length) {
|
|
104
|
+
bestMount = mount;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!bestMount) {
|
|
109
|
+
return {
|
|
110
|
+
effectiveRootDir: rootDir,
|
|
111
|
+
effectivePath: normalizedPath,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const relativePath = normalizedPath.slice(bestMount.urlPrefix.length) || '/';
|
|
116
|
+
return {
|
|
117
|
+
effectiveRootDir: bestMount.rootDir,
|
|
118
|
+
effectivePath: relativePath.startsWith('/') ? relativePath : `/${relativePath}`,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function resolveFileForRequest(rootDir, mounts, requestPath) {
|
|
123
|
+
const { effectiveRootDir, effectivePath } = findStaticRootForRequest(rootDir, mounts, requestPath);
|
|
124
|
+
const resolved = resolveStaticPath(effectiveRootDir, effectivePath);
|
|
72
125
|
if (!resolved) return null;
|
|
73
126
|
|
|
74
127
|
let stats;
|
|
@@ -99,6 +152,7 @@ async function createStaticFileServer(options = {}) {
|
|
|
99
152
|
const rootDir = path.resolve(
|
|
100
153
|
options.rootDir || fileURLToPath(new URL('../../', import.meta.url))
|
|
101
154
|
);
|
|
155
|
+
const staticMounts = normalizeStaticMounts(options.staticMounts || []);
|
|
102
156
|
const host = String(options.host || DEFAULT_HOST);
|
|
103
157
|
const port = Number.isFinite(options.port) ? Math.max(0, Math.floor(options.port)) : 0;
|
|
104
158
|
|
|
@@ -120,7 +174,7 @@ async function createStaticFileServer(options = {}) {
|
|
|
120
174
|
return;
|
|
121
175
|
}
|
|
122
176
|
|
|
123
|
-
const resolved = await resolveFileForRequest(rootDir, pathname);
|
|
177
|
+
const resolved = await resolveFileForRequest(rootDir, staticMounts, pathname);
|
|
124
178
|
if (!resolved) {
|
|
125
179
|
res.statusCode = 404;
|
|
126
180
|
res.end('File not found');
|
|
@@ -545,6 +599,7 @@ export async function runBrowserCommandInNode(commandRequest, options = {}) {
|
|
|
545
599
|
? null
|
|
546
600
|
: await createStaticFileServer({
|
|
547
601
|
rootDir: options.staticRootDir,
|
|
602
|
+
staticMounts: options.staticMounts,
|
|
548
603
|
host: options.host,
|
|
549
604
|
port: serverPort,
|
|
550
605
|
}).catch((error) => {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getActiveKernelPathSource,
|
|
19
19
|
setActiveKernelPath,
|
|
20
20
|
} from '../config/kernel-path-loader.js';
|
|
21
|
+
import { runTrainingOperatorCommand } from '../training/operator-command.js';
|
|
21
22
|
|
|
22
23
|
function asOptionalPlainObject(value, label) {
|
|
23
24
|
if (value == null) return null;
|
|
@@ -90,6 +91,20 @@ export async function runNodeCommand(commandRequest, options = {}) {
|
|
|
90
91
|
});
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
if (request.command === 'lora' || request.command === 'distill') {
|
|
95
|
+
const gpuOptionalActions = new Set(['compare', 'quality-gate', 'subsets']);
|
|
96
|
+
installNodeFileFetchShim();
|
|
97
|
+
if (!gpuOptionalActions.has(request.action)) {
|
|
98
|
+
await assertNodeWebGPUSupport();
|
|
99
|
+
}
|
|
100
|
+
const result = await runTrainingOperatorCommand(request);
|
|
101
|
+
return createToolingSuccessEnvelope({
|
|
102
|
+
surface: 'node',
|
|
103
|
+
request,
|
|
104
|
+
result,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
93
108
|
await assertNodeWebGPUSupport();
|
|
94
109
|
const modules = await loadRuntimeModules();
|
|
95
110
|
const runtimeBridge = {
|
|
@@ -2,17 +2,6 @@ import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
4
|
|
|
5
|
-
const DOPPLER_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
6
|
-
|
|
7
|
-
const DEFAULT_LOCAL_DOE_PROVIDER_PATH = resolve(
|
|
8
|
-
dirname(fileURLToPath(import.meta.url)),
|
|
9
|
-
'..',
|
|
10
|
-
'..',
|
|
11
|
-
'..',
|
|
12
|
-
'fawn',
|
|
13
|
-
'nursery',
|
|
14
|
-
'webgpu-doe',
|
|
15
|
-
);
|
|
16
5
|
const DEFAULT_DOE_PROVIDER_CREATE_ARGS = 'enable-dawn-features=allow_unsafe_apis';
|
|
17
6
|
|
|
18
7
|
function hasNavigatorGpu() {
|
|
@@ -59,20 +48,7 @@ function resolveCandidateModuleSpecifier(candidate) {
|
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
function resolveDefaultWebgpuModuleSpecifiers() {
|
|
62
|
-
|
|
63
|
-
const localCandidates = [
|
|
64
|
-
resolve(process.cwd(), '..', 'fawn', 'nursery', 'webgpu-doe'),
|
|
65
|
-
DEFAULT_LOCAL_DOE_PROVIDER_PATH,
|
|
66
|
-
];
|
|
67
|
-
for (const localCandidate of localCandidates) {
|
|
68
|
-
const resolvedPath = resolveNodeModuleFilePath(localCandidate);
|
|
69
|
-
if (resolvedPath) {
|
|
70
|
-
specifiers.push(pathToFileURL(resolvedPath).href);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
specifiers.push('@simulatte/webgpu-doe');
|
|
74
|
-
specifiers.push('webgpu');
|
|
75
|
-
return [...new Set(specifiers)];
|
|
51
|
+
return ['@simulatte/webgpu', 'webgpu'];
|
|
76
52
|
}
|
|
77
53
|
|
|
78
54
|
function resolveWebgpuModuleSpecifiers() {
|
|
@@ -89,80 +65,26 @@ function resolveWebgpuModuleSpecifiers() {
|
|
|
89
65
|
};
|
|
90
66
|
}
|
|
91
67
|
|
|
92
|
-
function resolveWorkspaceWebgpuProviderPath() {
|
|
93
|
-
const candidates = [
|
|
94
|
-
resolve(process.cwd(), 'node_modules', 'webgpu'),
|
|
95
|
-
resolve(DOPPLER_ROOT, 'node_modules', 'webgpu'),
|
|
96
|
-
];
|
|
97
|
-
for (const candidate of candidates) {
|
|
98
|
-
const resolvedPath = resolveNodeModuleFilePath(candidate);
|
|
99
|
-
if (resolvedPath) {
|
|
100
|
-
return resolvedPath;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
68
|
function isDoeWebgpuSpecifier(specifier) {
|
|
107
|
-
if (specifier === '@simulatte/webgpu
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
if (typeof specifier !== 'string') {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
if (specifier.includes('/webgpu-doe/')) {
|
|
69
|
+
if (specifier === '@simulatte/webgpu') {
|
|
114
70
|
return true;
|
|
115
71
|
}
|
|
116
|
-
return specifier
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
function resolveDoeProviderOverride(specifier) {
|
|
120
|
-
const explicitProvider = process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
|
|
121
|
-
if (typeof explicitProvider === 'string' && explicitProvider.trim().length > 0) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
if (!isDoeWebgpuSpecifier(specifier)) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
return resolveWorkspaceWebgpuProviderPath();
|
|
72
|
+
return typeof specifier === 'string'
|
|
73
|
+
&& specifier.startsWith('file://')
|
|
74
|
+
&& specifier.includes('@simulatte/webgpu');
|
|
128
75
|
}
|
|
129
76
|
|
|
130
77
|
async function importWithProviderOverride(specifier) {
|
|
131
|
-
const providerOverride = resolveDoeProviderOverride(specifier);
|
|
132
78
|
const shouldApplyCreateArgsDefault = isDoeWebgpuSpecifier(specifier)
|
|
133
79
|
&& !(typeof process.env.FAWN_WEBGPU_CREATE_ARGS === 'string' && process.env.FAWN_WEBGPU_CREATE_ARGS.trim().length > 0);
|
|
134
|
-
if (!
|
|
135
|
-
|
|
136
|
-
return import(specifier);
|
|
137
|
-
}
|
|
138
|
-
process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
|
|
139
|
-
try {
|
|
140
|
-
return await import(specifier);
|
|
141
|
-
} finally {
|
|
142
|
-
delete process.env.FAWN_WEBGPU_CREATE_ARGS;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const hadProvider = Object.prototype.hasOwnProperty.call(process.env, 'FAWN_WEBGPU_NODE_PROVIDER_MODULE');
|
|
146
|
-
const previousProvider = process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
|
|
147
|
-
const hadCreateArgs = Object.prototype.hasOwnProperty.call(process.env, 'FAWN_WEBGPU_CREATE_ARGS');
|
|
148
|
-
const previousCreateArgs = process.env.FAWN_WEBGPU_CREATE_ARGS;
|
|
149
|
-
process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE = providerOverride;
|
|
150
|
-
if (!hadCreateArgs) {
|
|
151
|
-
process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
|
|
80
|
+
if (!shouldApplyCreateArgsDefault) {
|
|
81
|
+
return import(specifier);
|
|
152
82
|
}
|
|
83
|
+
process.env.FAWN_WEBGPU_CREATE_ARGS = DEFAULT_DOE_PROVIDER_CREATE_ARGS;
|
|
153
84
|
try {
|
|
154
85
|
return await import(specifier);
|
|
155
86
|
} finally {
|
|
156
|
-
|
|
157
|
-
process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE = previousProvider;
|
|
158
|
-
} else {
|
|
159
|
-
delete process.env.FAWN_WEBGPU_NODE_PROVIDER_MODULE;
|
|
160
|
-
}
|
|
161
|
-
if (hadCreateArgs) {
|
|
162
|
-
process.env.FAWN_WEBGPU_CREATE_ARGS = previousCreateArgs;
|
|
163
|
-
} else {
|
|
164
|
-
delete process.env.FAWN_WEBGPU_CREATE_ARGS;
|
|
165
|
-
}
|
|
87
|
+
delete process.env.FAWN_WEBGPU_CREATE_ARGS;
|
|
166
88
|
}
|
|
167
89
|
}
|
|
168
90
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function watchFinalizedCheckpoints(options: {
|
|
2
|
+
checkpointsDir: string;
|
|
3
|
+
manifestPath: string;
|
|
4
|
+
pollIntervalMs?: number | null;
|
|
5
|
+
stopWhenIdle?: boolean;
|
|
6
|
+
onCheckpoint: (markerPath: string) => Promise<void> | void;
|
|
7
|
+
}): Promise<{ ok: true; processedCount: number; manifestPath: string }>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { writeJsonArtifact } from './operator-artifacts.js';
|
|
5
|
+
|
|
6
|
+
async function listCheckpointMarkers(checkpointsDir) {
|
|
7
|
+
const absoluteDir = resolve(String(checkpointsDir));
|
|
8
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
9
|
+
const markers = [];
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
if (!entry.isDirectory()) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const entryPath = join(absoluteDir, entry.name);
|
|
15
|
+
const markerPath = join(entryPath, 'checkpoint.complete.json');
|
|
16
|
+
try {
|
|
17
|
+
await readFile(markerPath, 'utf8');
|
|
18
|
+
markers.push(markerPath);
|
|
19
|
+
continue;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error?.code !== 'ENOENT') {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
markers.push(...await listCheckpointMarkers(entryPath));
|
|
26
|
+
}
|
|
27
|
+
return markers.sort((left, right) => left.localeCompare(right));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function ensureDirectoryExists(directoryPath) {
|
|
31
|
+
try {
|
|
32
|
+
const entries = await readdir(directoryPath, { withFileTypes: true });
|
|
33
|
+
return Array.isArray(entries);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error?.code === 'ENOENT') {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function readProcessedManifest(manifestPath) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = await readFile(manifestPath, 'utf8');
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
const processed = Array.isArray(parsed?.processedCheckpointMarkers)
|
|
47
|
+
? parsed.processedCheckpointMarkers.filter((entry) => typeof entry === 'string')
|
|
48
|
+
: [];
|
|
49
|
+
return new Set(processed);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error?.code === 'ENOENT') {
|
|
52
|
+
return new Set();
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function watchFinalizedCheckpoints(options) {
|
|
59
|
+
const checkpointsDir = resolve(String(options.checkpointsDir));
|
|
60
|
+
const manifestPath = resolve(String(options.manifestPath));
|
|
61
|
+
const pollIntervalMs = Number.isFinite(options.pollIntervalMs)
|
|
62
|
+
? Math.max(100, Math.floor(options.pollIntervalMs))
|
|
63
|
+
: 2000;
|
|
64
|
+
const stopWhenIdle = options.stopWhenIdle === true;
|
|
65
|
+
const onCheckpoint = typeof options.onCheckpoint === 'function'
|
|
66
|
+
? options.onCheckpoint
|
|
67
|
+
: null;
|
|
68
|
+
if (!onCheckpoint) {
|
|
69
|
+
throw new Error('watchFinalizedCheckpoints requires onCheckpoint(markerPath).');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const processed = await readProcessedManifest(manifestPath);
|
|
73
|
+
let idlePolls = 0;
|
|
74
|
+
for (;;) {
|
|
75
|
+
const checkpointsExist = await ensureDirectoryExists(checkpointsDir);
|
|
76
|
+
const markers = checkpointsExist
|
|
77
|
+
? await listCheckpointMarkers(checkpointsDir)
|
|
78
|
+
: [];
|
|
79
|
+
let sawNewMarker = false;
|
|
80
|
+
for (const markerPath of markers) {
|
|
81
|
+
if (processed.has(markerPath)) continue;
|
|
82
|
+
sawNewMarker = true;
|
|
83
|
+
await onCheckpoint(markerPath);
|
|
84
|
+
processed.add(markerPath);
|
|
85
|
+
await writeJsonArtifact(manifestPath, {
|
|
86
|
+
artifactType: 'training_checkpoint_watch_manifest',
|
|
87
|
+
schemaVersion: 1,
|
|
88
|
+
generatedAt: new Date().toISOString(),
|
|
89
|
+
processedCheckpointMarkers: [...processed].sort((left, right) => left.localeCompare(right)),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (!sawNewMarker) {
|
|
93
|
+
idlePolls += 1;
|
|
94
|
+
if (stopWhenIdle && idlePolls > 0) {
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
processedCount: processed.size,
|
|
98
|
+
manifestPath,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
idlePolls = 0;
|
|
103
|
+
}
|
|
104
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, pollIntervalMs));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -23,7 +23,12 @@ export declare function saveCheckpoint(
|
|
|
23
23
|
key: string,
|
|
24
24
|
data: unknown,
|
|
25
25
|
options?: CheckpointStoreOptions
|
|
26
|
-
): Promise<
|
|
26
|
+
): Promise<{
|
|
27
|
+
key: string;
|
|
28
|
+
path: string | null;
|
|
29
|
+
metadata: Record<string, unknown>;
|
|
30
|
+
data: unknown;
|
|
31
|
+
}>;
|
|
27
32
|
|
|
28
33
|
export declare function loadCheckpoint(
|
|
29
34
|
key: string,
|