Vili/scripts/test-gpu-tenancy.mjs
Meta Vens 5050de58b5 Gate: one slot judged where the GPU is taken, own VRAM discounted, truthful placement body, env refusal
acquire() judges at the slot: household signals first (admission), then a held slot (slot);
nothing queues, so a request can no longer wait on the worker lock past a changed gate. While
Vili's job holds the slot, and until free VRAM recovers to its pre-job level (capped), low free
VRAM is not the household's; full-screen and 3D always count. A sample with no resolved 3D
counters is a probe fault and the gate fails closed. The placement 503 carries no gate status.
A malformed VILI_GPU_* override refuses to start, naming the variable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 00:19:08 +02:00

367 lines
14 KiB
JavaScript

import assert from "node:assert/strict";
import test from "node:test";
import { admitThen, createGpuTenancy, gpuTenancyOptionsFromEnv, placementRefusal } from "./gpu-tenancy.mjs";
const SEC = 1000;
const CLEAR = { quns: 1, freeMiB: 14000, util3d: 1, engines: 8 };
function rig(options = {}) {
let t = 1_000_000;
const gate = createGpuTenancy({ now: () => t, ...options });
return {
gate,
t0: t,
at: () => t,
advance: (ms) => { t += ms; },
feed(sample, afterMs = 15 * SEC) {
t += afterMs;
gate.observe(sample);
},
};
}
function freeRig(options) {
const r = rig(options);
for (let i = 0; i < 13; i += 1) r.feed(CLEAR); // the 13th sample is 180 s after the first
assert.equal(r.gate.status().inUse, false, "precondition: gate is free");
return r;
}
test("hysteresis: busy holds for the clear window", () => {
const r = freeRig();
r.feed({ ...CLEAR, quns: 3 });
const busyAt = r.at();
assert.deepEqual(r.gate.status(), { inUse: true, since: busyAt, checkedAt: busyAt, probeOk: true });
for (let elapsed = 15; elapsed < 180; elapsed += 15) {
r.feed(CLEAR);
assert.equal(r.gate.status().inUse, true, `still held ${elapsed} s into the hold`);
assert.equal(r.gate.status().since, busyAt);
}
// a busy sample late in the hold restarts it
r.feed({ ...CLEAR, quns: 2 });
const restartAt = r.at();
r.feed(CLEAR); // the hold counts from the first clear sample
const holdStart = r.at();
for (let elapsed = 15; elapsed <= 165; elapsed += 15) {
r.feed(CLEAR);
assert.equal(r.gate.status().inUse, true, `restarted hold, ${elapsed} s clear`);
}
r.feed(CLEAR);
assert.equal(r.at() - holdStart, 180 * SEC);
assert.ok(r.at() > restartAt);
const freedAt = r.at();
assert.deepEqual(r.gate.status(), { inUse: false, since: freedAt, checkedAt: freedAt, probeOk: true });
r.feed(CLEAR);
assert.equal(r.gate.status().since, freedAt, "since moves only when the state turns");
r.feed({ ...CLEAR, freeMiB: 100 });
assert.deepEqual(r.gate.status(), { inUse: true, since: r.at(), checkedAt: r.at(), probeOk: true });
});
test("hysteresis: a busy sample just before the window ends restarts it", () => {
const r = rig();
r.feed(CLEAR);
for (let i = 0; i < 11; i += 1) r.feed(CLEAR);
assert.equal(r.gate.status().inUse, true, "165 s clear is not enough");
r.feed({ ...CLEAR, quns: 3 });
r.feed(CLEAR);
r.feed(CLEAR);
assert.equal(r.gate.status().inUse, true);
});
test("sustained 3D, immediate full-screen and VRAM", () => {
const spike = freeRig();
spike.feed({ ...CLEAR, util3d: 80 });
spike.feed(CLEAR);
assert.equal(spike.gate.status().inUse, false, "one 3D spike between clear samples");
const sustained = freeRig();
sustained.feed({ ...CLEAR, util3d: 80 });
assert.equal(sustained.gate.status().inUse, false, "first high sample alone");
sustained.feed({ ...CLEAR, util3d: 80 });
assert.equal(sustained.gate.status().inUse, true, "two consecutive high samples");
const edge = freeRig();
edge.feed({ ...CLEAR, util3d: 20 });
edge.feed({ ...CLEAR, util3d: 20 });
assert.equal(edge.gate.status().inUse, false, "exactly the threshold is not above it");
edge.feed({ ...CLEAR, util3d: 80 });
edge.feed({ ...CLEAR, util3d: 20 });
assert.equal(edge.gate.status().inUse, false, "high then exactly the threshold");
const edgeUp = freeRig();
edgeUp.feed({ ...CLEAR, util3d: 20 });
edgeUp.feed({ ...CLEAR, util3d: 80 });
assert.equal(edgeUp.gate.status().inUse, false, "exactly the threshold then high");
for (const [name, sample] of [
["QUNS_BUSY", { ...CLEAR, quns: 2 }],
["QUNS_RUNNING_D3D_FULL_SCREEN", { ...CLEAR, quns: 3 }],
["low free VRAM", { ...CLEAR, freeMiB: 10239 }],
]) {
const r = freeRig();
r.feed(sample);
assert.equal(r.gate.status().inUse, true, name);
}
const enough = freeRig();
enough.feed({ ...CLEAR, freeMiB: 10240 });
assert.equal(enough.gate.status().inUse, false, "free VRAM equal to the minimum is enough");
const otherStates = freeRig();
for (const quns of [1, 4, 5, 6, 7]) otherStates.feed({ ...CLEAR, quns });
assert.equal(otherStates.gate.status().inUse, false, "other notification states are not household use");
});
test("thresholds from the options are honored", () => {
const r = rig({ minFreeMiB: 500, util3dBusyPct: 90, clearHoldMs: 30 * SEC });
r.feed({ ...CLEAR, freeMiB: 600, util3d: 85 });
r.feed({ ...CLEAR, freeMiB: 600, util3d: 85 });
r.feed({ ...CLEAR, freeMiB: 600, util3d: 85 });
assert.equal(r.gate.status().inUse, false);
r.feed({ ...CLEAR, freeMiB: 400 });
assert.equal(r.gate.status().inUse, true);
});
test("start and stale fail closed", () => {
const r = rig();
assert.deepEqual(r.gate.status(), { inUse: true, since: r.t0, checkedAt: null, probeOk: true });
for (let i = 0; i < 13; i += 1) r.feed(CLEAR);
assert.equal(r.gate.status().inUse, false);
const lastSample = r.at();
r.advance(45 * SEC);
assert.equal(r.gate.status().probeOk, true, "exactly 3 intervals is not stale yet");
r.advance(1);
const stale = r.gate.status();
assert.equal(stale.inUse, true);
assert.equal(stale.probeOk, false);
assert.equal(stale.since, lastSample + 45 * SEC);
assert.equal(stale.checkedAt, lastSample);
r.feed(CLEAR, 0);
assert.equal(r.gate.status().inUse, true, "samples resumed but the hold has not passed");
assert.equal(r.gate.status().probeOk, true);
for (let i = 0; i < 11; i += 1) r.feed(CLEAR);
assert.equal(r.gate.status().inUse, true);
r.feed(CLEAR);
assert.equal(r.gate.status().inUse, false);
});
test("a stale start keeps since at the start when samples resume busy", () => {
const r = rig();
r.advance(60 * SEC);
r.feed({ ...CLEAR, quns: 3 }, 0);
assert.deepEqual(r.gate.status(), { inUse: true, since: r.t0, checkedAt: r.at(), probeOk: true });
});
test("a gate that never hears from its probe stays in use and not ok", () => {
const r = rig();
r.advance(46 * SEC);
assert.deepEqual(r.gate.status(), { inUse: true, since: r.t0, checkedAt: null, probeOk: false });
});
test("defer, not kill", async () => {
const r = freeRig();
let release;
let started = 0;
const running = admitThen(r.gate, () => {
started += 1;
return new Promise((resolve) => { release = () => resolve({ ok: true, value: "finished" }); });
});
assert.equal(started, 1);
r.feed({ ...CLEAR, quns: 3 });
assert.equal(r.gate.status().inUse, true, "the gate went busy while the job runs");
let secondCalls = 0;
const second = await admitThen(r.gate, async () => { secondCalls += 1; });
assert.equal(secondCalls, 0, "a second job is refused without being called");
assert.equal(second.code, "gpu-busy");
release();
assert.deepEqual(await running, { ok: true, value: "finished" });
});
test("a job that fails is not swallowed by the gate", async () => {
const r = freeRig();
await assert.rejects(admitThen(r.gate, async () => { throw new Error("job failed"); }), /job failed/);
});
test("typed busy result", async () => {
const r = rig();
r.feed({ ...CLEAR, quns: 3 });
const since = r.gate.status().since;
r.feed({ ...CLEAR, quns: 3 });
r.feed({ ...CLEAR, quns: 3 });
let calls = 0;
const refusal = await admitThen(r.gate, async () => { calls += 1; });
assert.deepEqual(refusal, {
ok: false,
code: "gpu-busy",
source: "admission",
gpu: { inUse: true, since, checkedAt: r.at(), probeOk: true },
});
assert.equal(calls, 0);
});
test("placement refusal is keyed on the typed code, never on text, and carries no gate status", () => {
assert.deepEqual(placementRefusal({ ok: false, code: "gpu-busy", error: "x" }, "vili-1"), {
ok: false, code: "gpu-busy", source: "placement", jobId: "vili-1",
});
assert.equal(placementRefusal({ ok: false, error: "gpu-busy: 0.50 GiB of VRAM free" }, "vili-1"), null);
assert.equal(placementRefusal({ ok: false, code: null }, "vili-1"), null);
assert.equal(placementRefusal(null, "vili-1"), null);
});
// ---- the slot: one job at a time, judged where the GPU is taken ----
const OWN = { ...CLEAR, freeMiB: 1800 }; // the encoder child holds the VRAM
test("slot: a second request while a job runs is refused as a slot refusal and never waits", async () => {
const r = freeRig();
let release;
const running = admitThen(r.gate, () => new Promise((resolve) => { release = () => resolve("done"); }));
let calls = 0;
const refused = await admitThen(r.gate, async () => { calls += 1; });
assert.deepEqual(refused, { ok: false, code: "gpu-busy", source: "slot" });
assert.equal(calls, 0);
const third = await admitThen(r.gate, async () => { calls += 1; });
assert.equal(third.source, "slot");
release();
assert.equal(await running, "done");
assert.equal(await admitThen(r.gate, async () => "next"), "next", "the slot frees when the job ends");
});
test("slot: a failing job still frees the slot", async () => {
const r = freeRig();
await assert.rejects(admitThen(r.gate, async () => { throw new Error("boom"); }), /boom/);
assert.equal(await admitThen(r.gate, async () => "after"), "after");
});
test("slot: the household's answer comes before the slot's", async () => {
const r = freeRig();
const running = admitThen(r.gate, () => new Promise(() => {}));
r.feed({ ...OWN, quns: 3 });
const refused = await admitThen(r.gate, async () => {});
assert.equal(refused.source, "admission");
assert.equal(refused.gpu.inUse, true);
void running;
});
test("own VRAM: a sequential batch of misses is never refused for Vili's own load", async () => {
const r = freeRig();
for (let miss = 1; miss <= 8; miss += 1) {
const result = await admitThen(r.gate, async () => {
for (let i = 0; i < 9; i += 1) r.feed(OWN); // about 2 minutes of child VRAM
return `miss ${miss}`;
});
assert.equal(result, `miss ${miss}`, `miss ${miss} was admitted`);
r.feed(CLEAR); // the child's VRAM has drained
assert.equal(r.gate.status().inUse, false, `gate free after miss ${miss}`);
}
});
test("own VRAM: the next request right after a job is admitted while the child's VRAM drains", async () => {
const r = freeRig();
await admitThen(r.gate, async () => { r.feed(OWN); r.feed(OWN); });
r.feed(OWN); // still draining after the slot freed
assert.equal(r.gate.status().inUse, false);
const second = await admitThen(r.gate, async () => {
r.feed(OWN);
return "second";
});
assert.equal(second, "second");
r.feed(CLEAR);
assert.equal(r.gate.status().inUse, false, "the baseline survives back-to-back jobs, so recovery is judged against the first");
});
test("own VRAM: low VRAM that outlives the drain is the household's, and refuses", async () => {
const r = freeRig({ sampleIntervalMs: 15 * SEC });
await admitThen(r.gate, async () => { r.feed(OWN); });
for (let i = 0; i < 3; i += 1) {
r.feed(OWN);
assert.equal(r.gate.status().inUse, false, `draining ${i}`);
}
r.feed(OWN); // 60 s after the release: the drain cap
assert.equal(r.gate.status().inUse, true);
assert.equal((await admitThen(r.gate, async () => {})).source, "admission");
});
test("own VRAM: partial recovery within the tolerance ends the drain, below it does not", async () => {
const within = freeRig();
await admitThen(within.gate, async () => { within.feed(OWN); });
within.feed({ ...CLEAR, freeMiB: 14000 - 1024 });
within.feed({ ...CLEAR, freeMiB: 5000 });
assert.equal(within.gate.status().inUse, true, "recovered, then VRAM below the minimum counts again");
const below = freeRig();
await admitThen(below.gate, async () => { below.feed(OWN); });
below.feed({ ...CLEAR, freeMiB: 14000 - 1025 });
below.feed({ ...CLEAR, freeMiB: 5000 });
assert.equal(below.gate.status().inUse, false, "still draining: not recovered to within the tolerance");
});
test("household signals mid-job count, and the next job is refused", async () => {
for (const [name, sample] of [
["full-screen", { ...OWN, quns: 2 }],
["exclusive full-screen", { ...OWN, quns: 3 }],
]) {
const r = freeRig();
await admitThen(r.gate, async () => {
r.feed(OWN);
r.feed(sample);
});
const refused = await admitThen(r.gate, async () => {});
assert.equal(refused.code, "gpu-busy", name);
assert.equal(refused.source, "admission", name);
}
const sustained = freeRig();
await admitThen(sustained.gate, async () => {
sustained.feed({ ...OWN, util3d: 90 });
sustained.feed({ ...OWN, util3d: 90 });
});
assert.equal((await admitThen(sustained.gate, async () => {})).source, "admission", "sustained 3D mid-job");
});
test("household VRAM that arrives mid-job is still there when the drain ends", async () => {
const r = freeRig();
await admitThen(r.gate, async () => { r.feed(OWN); });
for (let i = 0; i < 4; i += 1) r.feed({ ...OWN, freeMiB: 600 });
assert.equal(r.gate.status().inUse, true);
assert.equal((await admitThen(r.gate, async () => {})).source, "admission");
});
// ---- the sample that cannot see the 3D engine ----
test("a sample with no resolved 3D counters is a probe fault, not a clear sample", () => {
const r = rig();
for (let i = 0; i < 20; i += 1) r.feed({ ...CLEAR, engines: 0 });
assert.deepEqual(r.gate.status(), { inUse: true, since: r.t0, checkedAt: null, probeOk: false });
r.feed({ ...CLEAR, engines: 1 });
assert.equal(r.gate.status().checkedAt, r.at(), "one resolved counter is enough");
});
// ---- operator overrides ----
test("env overrides: valid values are read, absent ones are left to the defaults", () => {
assert.deepEqual(gpuTenancyOptionsFromEnv({}), {});
assert.deepEqual(gpuTenancyOptionsFromEnv({
VILI_GPU_SAMPLE_MS: "5000",
VILI_GPU_CLEAR_HOLD_MS: "0",
VILI_GPU_MIN_FREE_MIB: "8192.5",
VILI_GPU_UTIL3D_BUSY_PCT: "35",
}), { sampleIntervalMs: 5000, clearHoldMs: 0, minFreeMiB: 8192.5, util3dBusyPct: 35 });
});
test("env overrides: a malformed value refuses to start and names the variable, not the value", () => {
for (const name of ["VILI_GPU_SAMPLE_MS", "VILI_GPU_CLEAR_HOLD_MS", "VILI_GPU_MIN_FREE_MIB", "VILI_GPU_UTIL3D_BUSY_PCT"]) {
for (const bad of ["10240MiB", "", " ", "NaN", "Infinity", "-5"]) {
assert.throws(
() => gpuTenancyOptionsFromEnv({ [name]: bad }),
(error) => error.message.includes(name) && (bad.trim() === "" || !error.message.includes(bad)),
`${name}=${JSON.stringify(bad)}`,
);
}
}
assert.throws(() => gpuTenancyOptionsFromEnv({ VILI_GPU_SAMPLE_MS: "0" }), /VILI_GPU_SAMPLE_MS/, "a zero interval would spin the probe");
assert.doesNotThrow(() => gpuTenancyOptionsFromEnv({ VILI_GPU_CLEAR_HOLD_MS: "0" }));
});