Co-authored-by: leo <yixingwang@YIXINGs-MacBook-Pro.local> Co-authored-by: Aryan Kumar <aryan5v@users.noreply.github.com>
652 lines
28 KiB
JavaScript
652 lines
28 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import { spawnSync } from "node:child_process";
|
|
import test from "node:test";
|
|
import { runInNewContext } from "node:vm";
|
|
|
|
import { parse } from "yaml";
|
|
import {
|
|
loadIndex,
|
|
loadDeployment,
|
|
getOptions,
|
|
resolveConfig,
|
|
CookbookValidationError,
|
|
} from "../js/cookbook-config.mjs";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
|
|
function catalog() {
|
|
return {
|
|
id: "example/rest",
|
|
model: { id: "Example/Video" },
|
|
workload: "t2v",
|
|
runtime: {
|
|
id: "fastvideo-cuda-rest",
|
|
interface: "rest",
|
|
launch_argv: ["fastvideo", "serve", "--config", "config.yaml"],
|
|
server_defaults: { host: "0.0.0.0", port: 8000 },
|
|
},
|
|
env: { BACKEND: "special attention" },
|
|
base_config: {
|
|
generator: {
|
|
model_path: "Example/Video",
|
|
engine: { compile: { enabled: true } },
|
|
pipeline: { experimental: { hidden: [1000, 757, 522] } },
|
|
},
|
|
server: { host: "0.0.0.0", port: 8000, served_model_name: "video" },
|
|
default_request: { sampling: { num_frames: 81, seed: 5 }, output: { return_frames: false } },
|
|
},
|
|
controls: [
|
|
{ path: "server.port", schema: { type: "integer", minimum: 1, maximum: 65535, default: 8000 } },
|
|
{ path: "server.host", schema: { type: "string", default: "0.0.0.0" } },
|
|
{ path: "server.served_model_name", schema: { anyOf: [{ type: "string" }, { type: "null" }], default: null } },
|
|
{ path: "generator.engine.compile.enabled", schema: { type: "boolean", default: false } },
|
|
{ path: "default_request.sampling.num_frames", schema: { type: "integer", minimum: 1, default: 61 } },
|
|
{ path: "default_request.sampling.guidance_scale", schema: { type: "number", default: 5 } },
|
|
{
|
|
path: "default_request.sampling.seed",
|
|
schema: { anyOf: [{ type: "integer" }, { type: "null" }], default: null },
|
|
},
|
|
],
|
|
};
|
|
}
|
|
const field = (data, path) => data.controls.find((control) => control.path === path).schema;
|
|
const indexModel = () => ({
|
|
id: "example",
|
|
title: "Example",
|
|
model_id: "Example/Video",
|
|
deployments: [
|
|
{
|
|
id: "example/rest",
|
|
label: "REST",
|
|
workload: "t2v",
|
|
runtime: "fastvideo-cuda-rest",
|
|
catalog_url: "recipes/example/rest.json",
|
|
},
|
|
],
|
|
});
|
|
const response = (data) => ({ ok: true, json: async () => data });
|
|
|
|
function topologyCatalog() {
|
|
const data = catalog();
|
|
data.runtime.topology_defaults = { num_gpus: 1, tp_size: -1, sp_size: -1, hsdp_replicate_dim: 1, hsdp_shard_dim: -1 };
|
|
Object.assign(data.base_config.generator.engine, {
|
|
num_gpus: 4,
|
|
use_fsdp_inference: false,
|
|
parallelism: { tp_size: 1, sp_size: 4 },
|
|
});
|
|
for (const path of [
|
|
"generator.engine.num_gpus",
|
|
...Object.keys(data.runtime.topology_defaults)
|
|
.filter((key) => key !== "num_gpus")
|
|
.map((key) => `generator.engine.parallelism.${key}`),
|
|
]) {
|
|
data.controls.push({ path, schema: { type: "integer" } });
|
|
}
|
|
return data;
|
|
}
|
|
|
|
test("browser bundle exposes reusable methods and its error class with no DOM or fetch side effects", () => {
|
|
const context = {
|
|
document: new Proxy({}, { get: () => assert.fail("Core must not read the DOM") }),
|
|
document$: { subscribe: () => assert.fail("Core must not subscribe to page navigation") },
|
|
fetch: () => assert.fail("Importing the core must not fetch"),
|
|
};
|
|
runInNewContext(
|
|
readFileSync(new URL("../assets/cookbook-config/cookbook-config.js", import.meta.url), "utf8"),
|
|
context,
|
|
);
|
|
assert.deepEqual(Object.keys(context.FastVideoConfigCookbook).sort(), [
|
|
"CookbookValidationError",
|
|
"getOptions",
|
|
"loadDeployment",
|
|
"loadIndex",
|
|
"resolveConfig",
|
|
]);
|
|
const api = context.FastVideoConfigCookbook;
|
|
assert.equal(api.resolveConfig(catalog(), { "server.port": 9000 }).config.server.port, 9000);
|
|
assert.throws(() => api.resolveConfig(catalog(), { "server.port": 0 }), api.CookbookValidationError);
|
|
const commonjs = require("../assets/cookbook-config/cookbook-config.js");
|
|
assert.deepEqual(commonjs.resolveConfig(catalog()), resolveConfig(catalog()));
|
|
});
|
|
|
|
test("getOptions returns all enabled metadata and current values without substituting declared defaults", () => {
|
|
const data = catalog();
|
|
data.base_config.default_request.prompt = ["First prompt"];
|
|
data.controls.push({ path: "default_request.prompt", schema: { type: "array", items: { type: "string" } } });
|
|
const before = JSON.stringify(data);
|
|
const options = getOptions(data);
|
|
assert.deepEqual(
|
|
options.map((item) => item.path),
|
|
data.controls.map((item) => item.path),
|
|
);
|
|
options.forEach((item, index) => assert.deepEqual(item.schema, data.controls[index].schema));
|
|
const value = (items, path) => items.find((item) => item.path === path).value;
|
|
assert.equal(value(options, "default_request.sampling.num_frames"), 81);
|
|
assert.equal(value(options, "default_request.sampling.guidance_scale"), undefined);
|
|
const updated = getOptions(
|
|
data,
|
|
resolveConfig(data, {
|
|
"generator.engine.compile.enabled": false,
|
|
"default_request.sampling.seed": null,
|
|
"default_request.sampling.guidance_scale": 0,
|
|
}).config,
|
|
);
|
|
assert.equal(value(updated, "generator.engine.compile.enabled"), false);
|
|
assert.equal(value(updated, "default_request.sampling.seed"), null);
|
|
assert.equal(value(updated, "default_request.sampling.guidance_scale"), 0);
|
|
const prompt = options.find((item) => item.path === "default_request.prompt");
|
|
prompt.value.push("A UI edit");
|
|
prompt.schema.items.type = "number";
|
|
assert.equal(JSON.stringify(data), before);
|
|
});
|
|
|
|
test("loadIndex and loadDeployment preserve the response URL, metadata and caller abort signal", async () => {
|
|
const model = indexModel();
|
|
const controller = new AbortController(),
|
|
calls = [];
|
|
const data = { ...catalog(), guide: { url: "../../cookbook/guide/" } };
|
|
const context = await loadIndex("https://example.test/old/index.json", {
|
|
signal: controller.signal,
|
|
fetcher: async (url, options) => {
|
|
calls.push({ url, signal: options.signal });
|
|
return {
|
|
ok: true,
|
|
url: "https://example.test/project/assets/cookbook-config/index.json",
|
|
json: async () => ({ models: [model] }),
|
|
};
|
|
},
|
|
});
|
|
assert.deepEqual(context, { models: [model], url: "https://example.test/project/assets/cookbook-config/index.json" });
|
|
const result = await loadDeployment(context, "example/rest", {
|
|
signal: controller.signal,
|
|
fetcher: async (url, options) => {
|
|
calls.push({ url, signal: options.signal });
|
|
return { ok: true, json: async () => data };
|
|
},
|
|
});
|
|
assert.equal(calls[1].url, "https://example.test/project/assets/cookbook-config/recipes/example/rest.json");
|
|
assert.ok(calls.every((call) => call.signal === controller.signal));
|
|
assert.deepEqual(result, data);
|
|
await assert.rejects(
|
|
loadDeployment(context, "example/mlx", { fetcher: () => assert.fail("Unlisted deployment fetched") }),
|
|
/No exported deployment/,
|
|
);
|
|
await assert.rejects(
|
|
loadDeployment(context, "example/rest", {
|
|
fetcher: async () => ({ ok: true, json: async () => ({ id: "other/rest" }) }),
|
|
}),
|
|
/Unexpected deployment/,
|
|
);
|
|
await assert.rejects(loadIndex(context.url, { fetcher: async () => ({ ok: false, status: 404 }) }), /HTTP 404/);
|
|
await assert.rejects(
|
|
loadDeployment(context, "example/rest", { fetcher: async () => ({ ok: false, status: 503 }) }),
|
|
/HTTP 503/,
|
|
);
|
|
});
|
|
|
|
test("malformed indexes reject with model or deployment context before any catalog fetch", async () => {
|
|
const examples = [
|
|
[null, /Recipe index/],
|
|
[{ models: {} }, /models/],
|
|
[{ models: [null] }, /models\[0\]/],
|
|
[{ models: [{ ...indexModel(), title: "" }] }, /title/],
|
|
[{ models: [{ ...indexModel(), deployments: null }] }, /deployments/],
|
|
[{ models: [{ ...indexModel(), deployments: [null] }] }, /deployments\[0\]/],
|
|
[
|
|
{ models: [{ ...indexModel(), deployments: [{ ...indexModel().deployments[0], catalog_url: "http://[" }] }] },
|
|
/catalog_url/,
|
|
],
|
|
[{ models: [indexModel(), indexModel()] }, /unique model ID/],
|
|
[{ models: [indexModel(), { ...indexModel(), id: "other" }] }, /unique deployment ID/],
|
|
];
|
|
for (const [data, message] of examples) {
|
|
await assert.rejects(
|
|
loadIndex("https://example.test/index.json", { fetcher: async () => response(data) }),
|
|
message,
|
|
);
|
|
}
|
|
await assert.rejects(
|
|
loadDeployment({ models: [null], url: "https://example.test/index.json" }, "example/rest", {
|
|
fetcher: () => assert.fail("Malformed index fetched a catalog"),
|
|
}),
|
|
/models\[0\]/,
|
|
);
|
|
});
|
|
|
|
test("malformed deployment catalogs reject the exact consumed container or field", async () => {
|
|
const index = { models: [indexModel()], url: "https://example.test/index.json" };
|
|
const examples = [
|
|
[null, /catalog object/],
|
|
[{ ...catalog(), base_config: [] }, /base_config/],
|
|
[{ ...catalog(), base_config: { server: [] } }, /example\/rest.*base_config.server/],
|
|
[{ ...catalog(), base_config: { server: null } }, /example\/rest.*base_config.server/],
|
|
[{ ...catalog(), base_config: { server: { host: 123 } } }, /example\/rest.*base_config.server.host/],
|
|
[{ ...catalog(), base_config: { server: { port: "8000" } } }, /example\/rest.*base_config.server.port/],
|
|
[
|
|
{ ...catalog(), base_config: { server: { served_model_name: false } } },
|
|
/example\/rest.*base_config.server.served_model_name/,
|
|
],
|
|
[{ ...catalog(), controls: null }, /controls/],
|
|
[{ ...catalog(), controls: [{ path: "server.port", schema: null }] }, /server.port.*schema|schema.*server.port/],
|
|
[
|
|
{ ...catalog(), controls: [{ path: "server.__proto__.polluted", schema: {} }] },
|
|
/example\/rest.*controls\[0\].path/,
|
|
],
|
|
[{ ...catalog(), controls: [catalog().controls[0], catalog().controls[0]] }, /unique control path/],
|
|
[{ ...catalog(), model: {} }, /model.id/],
|
|
[{ ...catalog(), env: { INVALID: false } }, /env/],
|
|
[{ ...catalog(), workload: "i2v" }, /workload/],
|
|
[{ ...catalog(), runtime: null }, /runtime/],
|
|
[{ ...catalog(), runtime: { ...catalog().runtime, id: "other" } }, /runtime.id/],
|
|
[{ ...catalog(), runtime: { ...catalog().runtime, launch_argv: [] } }, /launch_argv/],
|
|
[
|
|
{ ...catalog(), runtime: { ...catalog().runtime, server_defaults: { host: "localhost", port: "8000" } } },
|
|
/server_defaults.port/,
|
|
],
|
|
[
|
|
{
|
|
...catalog(),
|
|
runtime: {
|
|
...catalog().runtime,
|
|
server_defaults: {
|
|
...catalog().runtime.server_defaults,
|
|
served_model_name: ["alias"],
|
|
},
|
|
},
|
|
},
|
|
/example\/rest.*runtime.server_defaults.served_model_name/,
|
|
],
|
|
[{ ...catalog(), runtime: { ...catalog().runtime, topology_defaults: {} } }, /topology_defaults.num_gpus/],
|
|
[{ ...catalog(), guide: { url: null } }, /guide.url/],
|
|
];
|
|
for (const [data, message] of examples) {
|
|
await assert.rejects(loadDeployment(index, "example/rest", { fetcher: async () => response(data) }), message);
|
|
}
|
|
});
|
|
|
|
test("catalog loading accepts an omitted or partial baseline server and nullable aliases", async () => {
|
|
const index = { models: [indexModel()], url: "https://example.test/index.json" };
|
|
for (const baseline of [{}, { server: {} }, { server: { served_model_name: null } }]) {
|
|
const data = { ...catalog(), base_config: baseline, controls: [] };
|
|
const loaded = await loadDeployment(index, "example/rest", { fetcher: async () => response(data) });
|
|
assert.match(resolveConfig(loaded).clientCommand, /127\.0\.0\.1:8000/);
|
|
assert.deepEqual(loaded.base_config, baseline);
|
|
}
|
|
});
|
|
|
|
test("loading observes real abort signals before and after JSON completion", async () => {
|
|
for (const operation of ["index", "deployment"]) {
|
|
for (const stage of ["fetch", "json"]) {
|
|
const controller = new AbortController();
|
|
const fetcher = async (_url, { signal }) => {
|
|
assert.equal(signal, controller.signal);
|
|
if (stage === "fetch") controller.abort();
|
|
return {
|
|
ok: true,
|
|
json: async () => {
|
|
if (stage === "json") controller.abort();
|
|
return operation === "index" ? { models: [indexModel()] } : catalog();
|
|
},
|
|
};
|
|
};
|
|
const settings = { signal: controller.signal, fetcher };
|
|
const pending =
|
|
operation === "index"
|
|
? loadIndex("https://example.test/index.json", settings)
|
|
: loadDeployment(
|
|
{ models: [indexModel()], url: "https://example.test/index.json" },
|
|
"example/rest",
|
|
settings,
|
|
);
|
|
await assert.rejects(pending, { name: "AbortError" });
|
|
}
|
|
}
|
|
});
|
|
|
|
test("structured field errors retain Ajv details without sharing mutable validation state", () => {
|
|
const data = catalog();
|
|
let portFailure;
|
|
assert.throws(
|
|
() => resolveConfig(data, { "server.port": 0 }),
|
|
(error) => {
|
|
portFailure = error;
|
|
assert.ok(error instanceof Error && error instanceof CookbookValidationError);
|
|
assert.equal(error.path, "server.port");
|
|
assert.equal(error.errors[0].keyword, "minimum");
|
|
assert.equal(error.errors[0].instancePath, "");
|
|
assert.match(error.message, /server.port/);
|
|
return true;
|
|
},
|
|
);
|
|
const before = structuredClone(portFailure.errors);
|
|
assert.throws(() => resolveConfig(data, { "server.port": "8000" }), CookbookValidationError);
|
|
assert.deepEqual(portFailure.errors, before);
|
|
const path = "generator.pipeline.experimental.settings";
|
|
data.controls.push({
|
|
path,
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
layers: { type: "array", items: { type: "integer", minimum: 1 } },
|
|
},
|
|
required: ["layers"],
|
|
},
|
|
});
|
|
assert.throws(
|
|
() => resolveConfig(data, { [path]: { layers: [0] } }),
|
|
(error) => {
|
|
assert.equal(error.path, path);
|
|
assert.equal(error.errors[0].instancePath, "/layers/0");
|
|
assert.equal(error.errors[0].params.limit, 1);
|
|
return true;
|
|
},
|
|
);
|
|
assert.throws(
|
|
() => resolveConfig(data, { "server.unselected": 1 }),
|
|
(error) => {
|
|
assert.equal(error.path, "server.unselected");
|
|
assert.equal(error.errors[0].keyword, "editable");
|
|
return true;
|
|
},
|
|
);
|
|
assert.throws(
|
|
() => resolveConfig(data, null),
|
|
(error) => {
|
|
assert.equal(error.path, null);
|
|
assert.equal(error.errors[0].keyword, "configuration");
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("topology errors identify the editor and related GPU count field", () => {
|
|
assert.throws(
|
|
() => resolveConfig(topologyCatalog(), { "generator.engine.parallelism.sp_size": 3 }),
|
|
(error) => {
|
|
assert.ok(error instanceof CookbookValidationError);
|
|
assert.equal(error.path, "generator.engine.parallelism.sp_size");
|
|
assert.equal(error.errors[0].keyword, "topology");
|
|
assert.deepEqual(error.errors[0].params.relatedPaths, ["generator.engine.num_gpus"]);
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("streaming controls are editable only for WebSocket deployments", () => {
|
|
const data = catalog();
|
|
data.runtime.interface = "websocket";
|
|
data.base_config.streaming = { session: { timeout: 30 } };
|
|
data.controls.push({ path: "streaming.session.timeout", schema: { type: "number", minimum: 1 } });
|
|
const result = resolveConfig(data, { "streaming.session.timeout": 90 });
|
|
assert.equal(result.config.streaming.session.timeout, 90);
|
|
assert.equal(result.clientRequest, null);
|
|
assert.equal(result.websocketUrl, "ws://127.0.0.1:8000/v1/stream");
|
|
assert.match(result.clientCommand, /\/health/);
|
|
assert.throws(() => resolveConfig(data, { "streaming.session.timeout": 0 }), CookbookValidationError);
|
|
data.runtime.interface = "rest";
|
|
assert.throws(() => resolveConfig(data, { "streaming.session.timeout": 90 }), /Invalid configuration path/);
|
|
});
|
|
|
|
test("unedited download preserves the entire explicit baseline and display defaults stay unsaved", () => {
|
|
const data = catalog(),
|
|
before = JSON.stringify(data);
|
|
const result = resolveConfig(data);
|
|
assert.deepEqual(result.config, data.base_config);
|
|
assert.equal(result.config.default_request.sampling.num_frames, 81);
|
|
assert.equal(result.config.default_request.sampling.guidance_scale, undefined);
|
|
assert.equal(JSON.stringify(data), before);
|
|
assert.deepEqual(result.config.generator.pipeline.experimental.hidden, [1000, 757, 522]);
|
|
});
|
|
|
|
test("edits preserve hidden settings; empty edits reset to baseline without mutation", () => {
|
|
const data = catalog();
|
|
const initial = resolveConfig(data);
|
|
const edited = resolveConfig(data, {
|
|
"server.port": 9000,
|
|
"default_request.sampling.num_frames": 121,
|
|
"generator.engine.compile.enabled": false,
|
|
});
|
|
assert.equal(edited.config.server.port, 9000);
|
|
assert.equal(edited.config.default_request.sampling.num_frames, 121);
|
|
assert.equal(edited.config.generator.engine.compile.enabled, false);
|
|
assert.deepEqual(edited.config.generator.pipeline, initial.config.generator.pipeline);
|
|
assert.equal(edited.config.default_request.output.return_frames, false);
|
|
assert.deepEqual(resolveConfig(data), initial);
|
|
});
|
|
|
|
test("only manifest-selected controls can be edited, including existing hidden paths", () => {
|
|
for (const path of [
|
|
"server.extra",
|
|
"generator.model_path",
|
|
"generator.pipeline.experimental.hidden",
|
|
"generator.__proto__.polluted",
|
|
"default_request.output.return_frames",
|
|
]) {
|
|
assert.throws(() => resolveConfig(catalog(), { [path]: 1 }), /not an editable recipe control/);
|
|
}
|
|
assert.equal({}.polluted, undefined);
|
|
});
|
|
|
|
test("types, bounds, enum and nullable choices use standard field schema validation", () => {
|
|
const data = catalog();
|
|
for (const value of [0, 65536, 1.5, "8000", NaN, Infinity]) {
|
|
assert.throws(() => resolveConfig(data, { "server.port": value }), /server.port/);
|
|
}
|
|
assert.throws(() => resolveConfig(data, { "generator.engine.compile.enabled": "false" }));
|
|
assert.throws(() => resolveConfig(data, []));
|
|
assert.throws(() => resolveConfig(data, null));
|
|
field(data, "default_request.sampling.num_frames").enum = [81, 121];
|
|
assert.throws(() => resolveConfig(data, { "default_request.sampling.num_frames": 99 }), /allowed values/);
|
|
assert.equal(
|
|
resolveConfig(data, { "default_request.sampling.seed": null }).config.default_request.sampling.seed,
|
|
null,
|
|
);
|
|
assert.equal(resolveConfig(data, { "default_request.sampling.seed": 0 }).config.default_request.sampling.seed, 0);
|
|
});
|
|
|
|
test("arrays replace complete values and missing defaults remain inherited", () => {
|
|
const data = catalog();
|
|
data.controls.push({
|
|
path: "default_request.prompt",
|
|
schema: { anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }, { type: "null" }] },
|
|
});
|
|
assert.equal(resolveConfig(data).config.default_request.prompt, undefined);
|
|
assert.deepEqual(resolveConfig(data, { "default_request.prompt": ["One", "Two"] }).config.default_request.prompt, [
|
|
"One",
|
|
"Two",
|
|
]);
|
|
assert.throws(() => resolveConfig(data, { "default_request.prompt": [1] }));
|
|
});
|
|
|
|
test("sample requests follow effective alias, host and port without repinning request defaults", () => {
|
|
const result = resolveConfig(catalog(), { "server.port": 9000, "server.served_model_name": "my-video" });
|
|
assert.match(result.clientCommand, /http:\/\/127\.0\.0\.1:9000\/v1\/videos\/sync/);
|
|
assert.deepEqual(result.clientRequest, { model: "my-video", prompt: "A river flowing through a peaceful forest" });
|
|
assert.match(result.clientCommand, /--output output.mp4/);
|
|
for (const host of ["::", "[::]"])
|
|
assert.match(resolveConfig(catalog(), { "server.host": host }).clientCommand, /\[::1\]:8000/);
|
|
assert.equal(resolveConfig(catalog(), { "server.served_model_name": null }).clientRequest.model, "Example/Video");
|
|
const image = catalog();
|
|
image.workload = "i2v";
|
|
assert.equal(resolveConfig(image).clientRequest.input_reference, "/absolute/path/to/first-frame.png");
|
|
});
|
|
|
|
test("LoRA sample requests use the startup adapter instead of an edited server alias", () => {
|
|
const data = catalog();
|
|
const components = { lora_path: "/models/adapter.safetensors", lora_nickname: "my-adapter" };
|
|
data.base_config.generator.pipeline.components = components;
|
|
const edits = { "server.served_model_name": "different-server-name" };
|
|
assert.equal(resolveConfig(data, edits).clientRequest.model, "my-adapter");
|
|
delete components.lora_nickname;
|
|
assert.equal(Object.hasOwn(resolveConfig(data, edits).clientRequest, "model"), false);
|
|
components.lora_path = null;
|
|
assert.equal(resolveConfig(data, edits).clientRequest.model, "different-server-name");
|
|
});
|
|
|
|
test("environment variables are present and shell quoted without evaluation", () => {
|
|
const data = catalog();
|
|
data.env = { BACKEND: "it's literal $(touch /never-run)" };
|
|
const result = resolveConfig(data);
|
|
assert.match(result.command, /^BACKEND=/);
|
|
assert.ok(result.command.endsWith("fastvideo serve --config config.yaml"));
|
|
const values = ["", "a b", "it's literal", "$(printf unexpected)", "`date`"];
|
|
data.runtime.launch_argv = ["printf", "%s\\0", ...values];
|
|
const probe = spawnSync("/bin/sh", ["-c", resolveConfig(data).command], { encoding: "utf8" });
|
|
assert.equal(probe.status, 0);
|
|
assert.deepEqual(probe.stdout.split("\0").slice(0, -1), values);
|
|
});
|
|
|
|
test("YAML preserves literal types and scientific notation", () => {
|
|
const yaml = (value) => {
|
|
const data = catalog();
|
|
data.base_config = value;
|
|
data.controls = [];
|
|
return resolveConfig(data).yaml;
|
|
};
|
|
assert.equal(
|
|
yaml({ off: false, zero: 0, empty: "", auto: null, list: ["a", true] }),
|
|
'"off": false\n"zero": 0\n"empty": ""\n"auto": null\n"list": ["a",true]\n',
|
|
);
|
|
assert.equal(
|
|
yaml({ small: 1e-7, negative: -1e-7, text: "1e-7" }),
|
|
'"small": 1.0e-7\n"negative": -1.0e-7\n"text": "1e-7"\n',
|
|
);
|
|
assert.throws(() => yaml({ large: 1e21 }), /safe integer/);
|
|
});
|
|
|
|
test("YAML 1.1 preserves ambiguous string keys in hidden settings and editable objects", () => {
|
|
const data = catalog();
|
|
const values = Object.fromEntries(
|
|
["off", "on", "yes", "no", "true", "false", "null", "NULL", "Yes", "NO", "On", "OFF", "~", "01", "a:b", 'a"b'].map(
|
|
(key, index) => [key, [false, 0, null, "yes"][index % 4]],
|
|
),
|
|
);
|
|
const path = "generator.pipeline.experimental.custom";
|
|
data.base_config.generator.pipeline.experimental.hidden = { ...values, nested: values, list: [values] };
|
|
data.controls.push({ path, schema: { type: "object" } });
|
|
for (const edits of [{}, { [path]: values }]) {
|
|
const result = resolveConfig(data, edits);
|
|
assert.deepEqual(parse(result.yaml, { version: "1.1" }), result.config);
|
|
}
|
|
});
|
|
|
|
test("caller edits, catalog and resolved configuration do not share nested values", () => {
|
|
const data = catalog();
|
|
data.base_config.default_request.prompt = [{ text: "Baseline", tags: ["baseline"] }];
|
|
data.controls.push({
|
|
path: "default_request.prompt",
|
|
schema: {
|
|
type: "array",
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
text: { type: "string" },
|
|
tags: { type: "array", items: { type: "string" } },
|
|
},
|
|
required: ["text", "tags"],
|
|
additionalProperties: false,
|
|
},
|
|
},
|
|
});
|
|
const selections = { "default_request.prompt": [{ text: "Selected", tags: ["selected"] }] };
|
|
const catalogBefore = JSON.stringify(data),
|
|
selectionsBefore = JSON.stringify(selections);
|
|
const result = resolveConfig(data, selections);
|
|
result.config.default_request.prompt[0].tags.push("Result edit");
|
|
result.config.default_request.prompt.push({ text: "Another result", tags: [] });
|
|
result.config.generator.pipeline.experimental.hidden.push(1);
|
|
assert.equal(JSON.stringify(selections), selectionsBefore);
|
|
assert.equal(JSON.stringify(data), catalogBefore);
|
|
const resultBefore = JSON.stringify(result.config);
|
|
selections["default_request.prompt"][0].tags.push("Caller edit");
|
|
selections["default_request.prompt"].push({ text: "Another caller", tags: [] });
|
|
data.base_config.generator.pipeline.experimental.hidden.push(2);
|
|
assert.equal(JSON.stringify(result.config), resultBefore);
|
|
assert.deepEqual(data.base_config.default_request.prompt, [{ text: "Baseline", tags: ["baseline"] }]);
|
|
});
|
|
|
|
test("GPU edits require compatible concrete degrees without silently clamping topology or FSDP", () => {
|
|
const data = topologyCatalog(),
|
|
before = JSON.stringify(data);
|
|
assert.throws(() => resolveConfig(data, { "generator.engine.num_gpus": 2 }), /sp_size.*at most num_gpus \(2\)/);
|
|
const valid = resolveConfig(data, {
|
|
"generator.engine.num_gpus": 2,
|
|
"generator.engine.parallelism.sp_size": 2,
|
|
"generator.engine.parallelism.tp_size": 2,
|
|
});
|
|
assert.equal(valid.config.generator.engine.num_gpus, 2);
|
|
assert.deepEqual(valid.config.generator.engine.parallelism, { tp_size: 2, sp_size: 2 });
|
|
assert.equal(valid.config.generator.engine.use_fsdp_inference, false);
|
|
for (const value of [0, -2, 1.5, 3, 8]) {
|
|
assert.throws(() => resolveConfig(data, { "generator.engine.parallelism.tp_size": value }), /tp_size/);
|
|
}
|
|
for (const value of [0, -1, 1.5]) {
|
|
assert.throws(() => resolveConfig(data, { "generator.engine.num_gpus": value }), /num_gpus/);
|
|
}
|
|
assert.equal(JSON.stringify(data), before);
|
|
});
|
|
|
|
test("native -1 degree sentinels and omitted topology defaults remain inherited", () => {
|
|
const data = topologyCatalog();
|
|
const edits = Object.fromEntries(
|
|
["tp_size", "sp_size", "hsdp_shard_dim"].map((key) => [`generator.engine.parallelism.${key}`, -1]),
|
|
);
|
|
const result = resolveConfig(data, { ...edits, "generator.engine.num_gpus": 2 });
|
|
assert.equal(result.config.generator.engine.parallelism.tp_size, -1);
|
|
assert.equal(result.config.generator.engine.parallelism.sp_size, -1);
|
|
assert.equal(result.config.generator.engine.parallelism.hsdp_shard_dim, -1);
|
|
assert.throws(
|
|
() => resolveConfig(data, { "generator.engine.parallelism.hsdp_replicate_dim": -1 }),
|
|
/positive integer/,
|
|
);
|
|
delete data.base_config.generator.engine.num_gpus;
|
|
delete data.base_config.generator.engine.parallelism;
|
|
assert.deepEqual(resolveConfig(data).config, data.base_config);
|
|
});
|
|
|
|
test("getOptions previews GPU-dependent limits without requiring a complete valid edit or mutating inputs", () => {
|
|
const data = topologyCatalog(),
|
|
before = JSON.stringify(data),
|
|
edits = { "generator.engine.num_gpus": 2 };
|
|
const option = (options, path) => options.find((item) => item.path === path);
|
|
const initial = getOptions(data),
|
|
preview = getOptions(data, data.base_config, edits);
|
|
const path = "generator.engine.parallelism.sp_size";
|
|
assert.equal(option(initial, path).schema.maximum, 4);
|
|
assert.equal(option(preview, path).schema.maximum, 2);
|
|
assert.equal(option(preview, path).value, 4);
|
|
assert.equal(option(preview, path).schema.minimum, -1);
|
|
assert.match(option(preview, path).schema.description, /divide num_gpus evenly/);
|
|
assert.equal(option(preview, "generator.engine.parallelism.hsdp_replicate_dim").schema.minimum, 1);
|
|
assert.ok(
|
|
Number.isNaN(
|
|
option(getOptions(data, data.base_config, { "generator.engine.num_gpus": NaN }), "generator.engine.num_gpus")
|
|
.value,
|
|
),
|
|
);
|
|
assert.equal(JSON.stringify(data), before);
|
|
assert.deepEqual(edits, { "generator.engine.num_gpus": 2 });
|
|
delete data.runtime.topology_defaults;
|
|
assert.equal(option(getOptions(data), path).schema.maximum, undefined);
|
|
assert.equal(resolveConfig(data, { "generator.engine.num_gpus": 2 }).config.generator.engine.num_gpus, 2);
|
|
});
|
|
|
|
test("YAML output rejects unsafe numbers independently of authored field ranges", () => {
|
|
const data = catalog();
|
|
const path = "default_request.sampling.seed";
|
|
for (const value of [Number.MAX_SAFE_INTEGER + 1, Number.MIN_SAFE_INTEGER - 1]) {
|
|
assert.throws(() => resolveConfig(data, { [path]: value }), /safe integer/);
|
|
}
|
|
for (const value of [Infinity, -Infinity, NaN, Number.MAX_SAFE_INTEGER + 1]) {
|
|
const hidden = structuredClone(data);
|
|
hidden.base_config.generator.pipeline.experimental.unselected_numbers = [value];
|
|
assert.throws(() => resolveConfig(hidden), /finite|safe integer/);
|
|
}
|
|
assert.equal(
|
|
resolveConfig(data, { [path]: Number.MAX_SAFE_INTEGER }).config.default_request.sampling.seed,
|
|
Number.MAX_SAFE_INTEGER,
|
|
);
|
|
});
|