[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mfluxImageGenerate } from "./mflux-provider.mjs";
|
||||
|
||||
// 40GB clears the 32GB klein tier and the 8GB schnell tier below it; the 64GB
|
||||
// qwen tier stays out of reach. Two rungs is what makes demotion observable.
|
||||
const bothTiersSpecs = { availableRamMB: 40000, gpu: { present: true } };
|
||||
const SNAPSHOT = "/tmp/hf-snapshot";
|
||||
|
||||
// exec stub covering all three shells-out mflux does: the PATH probe, the
|
||||
// idempotent `hf download`, and the generate itself.
|
||||
function stubExec({ failGenerateFor = [], failWhichFor = [] } = {}) {
|
||||
const calls = [];
|
||||
const exec = (...call) => {
|
||||
calls.push(call);
|
||||
const [bin, argv] = call;
|
||||
if (bin === "which") {
|
||||
if (failWhichFor.includes(argv[0])) throw new Error("not found");
|
||||
return "";
|
||||
}
|
||||
if (bin === "hf") return `Fetching 6 files...\n${SNAPSHOT}\n`;
|
||||
if (failGenerateFor.some((id) => argv.join(" ").includes(id))) {
|
||||
const err = new Error("exit 1");
|
||||
err.stderr = "mlx.core.metal: out of memory";
|
||||
throw err;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
return { calls, exec };
|
||||
}
|
||||
|
||||
const generateCalls = (calls) =>
|
||||
calls.filter(([bin]) => bin !== "which" && bin !== "hf").map(([, argv]) => argv.join(" "));
|
||||
|
||||
test("no local model fits: falls through to the upsell without shelling out", async () => {
|
||||
const { calls, exec } = stubExec();
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: { availableRamMB: 100, gpu: { present: true } } },
|
||||
exec,
|
||||
() => true,
|
||||
);
|
||||
|
||||
assert.equal(result, null);
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test("a top tier that cannot run demotes to the next fitting tier", async (t) => {
|
||||
const errors = [];
|
||||
t.mock.method(console, "error", (message) => errors.push(message));
|
||||
const { calls, exec } = stubExec({ failGenerateFor: ["flux2-klein-4b"] });
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: bothTiersSpecs },
|
||||
exec,
|
||||
() => true,
|
||||
);
|
||||
|
||||
assert.ok(result, "the schnell tier still produced an image");
|
||||
assert.equal(result.metadata.provider, "mflux.flux-schnell-mflux-q4");
|
||||
const generated = generateCalls(calls);
|
||||
assert.equal(generated.length, 2, "klein attempted first, then schnell");
|
||||
assert.match(generated[0], /flux2-klein-4b/);
|
||||
assert.match(generated[1], /--model schnell/);
|
||||
assert.equal(errors.length, 1, "the demotion is reported, not silent");
|
||||
assert.match(errors[0], /flux2-klein-mflux-q4\) failed/);
|
||||
});
|
||||
|
||||
test("a snapshot that will not resolve demotes rather than failing outright", async (t) => {
|
||||
const errors = [];
|
||||
t.mock.method(console, "error", (message) => errors.push(message));
|
||||
const calls = [];
|
||||
const exec = (...call) => {
|
||||
calls.push(call);
|
||||
const [bin, argv] = call;
|
||||
if (bin === "which") return "";
|
||||
// klein's weights won't download; schnell's do
|
||||
if (bin === "hf") {
|
||||
if (argv[1].includes("FLUX.2-klein")) throw new Error("403 Forbidden");
|
||||
return `${SNAPSHOT}\n`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: bothTiersSpecs },
|
||||
exec,
|
||||
() => true,
|
||||
);
|
||||
|
||||
assert.ok(result, "demoted past the ungettable weights");
|
||||
assert.equal(result.metadata.provider, "mflux.flux-schnell-mflux-q4");
|
||||
assert.equal(errors.length, 1);
|
||||
assert.match(errors[0], /hf download failed/);
|
||||
});
|
||||
|
||||
test("runner missing from PATH reports the install hint per tier and returns null", async (t) => {
|
||||
const errors = [];
|
||||
t.mock.method(console, "error", (message) => errors.push(message));
|
||||
const { exec } = stubExec({ failWhichFor: ["mflux-generate"] });
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: bothTiersSpecs },
|
||||
exec,
|
||||
() => true,
|
||||
);
|
||||
|
||||
assert.equal(result, null);
|
||||
assert.equal(errors.length, 2, "both fitting tiers reported");
|
||||
assert.match(errors[0], /uv pip install mflux/);
|
||||
// each hint states that tier's download cost before the user commits
|
||||
assert.match(errors[0], /GB of weights to/);
|
||||
assert.match(errors[1], /GB of weights to/);
|
||||
});
|
||||
|
||||
// Same per-attempt temp path, same orphaning risk as the LTX provider: a
|
||||
// partial png from a failed tier must not survive a lower tier succeeding.
|
||||
const outputOf = (argv) => argv[argv.indexOf("--output") + 1];
|
||||
const attemptedOutputs = (calls) =>
|
||||
calls.filter(([bin]) => bin !== "which" && bin !== "hf").map(([, argv]) => outputOf(argv));
|
||||
|
||||
test("a failed attempt's partial output is discarded before demoting", async (t) => {
|
||||
t.mock.method(console, "error", () => {});
|
||||
const unlinked = [];
|
||||
const { calls, exec } = stubExec({ failGenerateFor: ["flux2-klein-4b"] });
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: bothTiersSpecs },
|
||||
exec,
|
||||
() => true,
|
||||
(path) => unlinked.push(path),
|
||||
);
|
||||
|
||||
assert.ok(result, "the schnell tier still produced an image");
|
||||
assert.deepEqual(unlinked, [attemptedOutputs(calls)[0]], "the failed klein partial is removed");
|
||||
});
|
||||
|
||||
test("every tier failing discards every partial, one per attempt", async (t) => {
|
||||
t.mock.method(console, "error", () => {});
|
||||
const unlinked = [];
|
||||
const { calls, exec } = stubExec({ failGenerateFor: ["flux2-klein-4b", "schnell"] });
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: bothTiersSpecs },
|
||||
exec,
|
||||
() => true,
|
||||
(path) => unlinked.push(path),
|
||||
);
|
||||
|
||||
assert.equal(result, null);
|
||||
assert.deepEqual(unlinked, attemptedOutputs(calls), "nothing is left behind");
|
||||
});
|
||||
|
||||
test("a successful generation is never discarded", async () => {
|
||||
const unlinked = [];
|
||||
const { exec } = stubExec();
|
||||
|
||||
const result = await mfluxImageGenerate(
|
||||
"a red bicycle",
|
||||
{ specs: bothTiersSpecs },
|
||||
exec,
|
||||
() => true,
|
||||
(path) => unlinked.push(path),
|
||||
);
|
||||
|
||||
assert.ok(result);
|
||||
assert.deepEqual(unlinked, [], "the returned artifact must survive");
|
||||
});
|
||||
Reference in New Issue
Block a user