import init, * as rlx from "./pkg/rlx_web.js";
import { createRank } from "./cluster-protocol.js";
import { channelBus, proxyBus } from "./cluster-bus.js";
const id = crypto.getRandomValues(new Uint32Array(1))[0] >>> 0;
let node = null;
const busError = (text) => self.postMessage({ t: "log", text: `bus: ${text}` });
async function buildRank(want, model, batch, seed) {
const tryGpu = want === "webgpu" || want === "auto";
if (tryGpu && typeof rlx.init_webgpu === "function") {
try {
await rlx.init_webgpu();
} catch (e) {
self.postMessage({ t: "log", text: `init_webgpu failed: ${e?.message ?? e}` });
}
}
try {
return new rlx.ClusterWorker(model, batch, seed, want);
} catch (e) {
if (want === "cpu") throw e;
self.postMessage({
t: "log",
text: `${want} unavailable (${e?.message ?? e}) — falling back to cpu`,
});
return new rlx.ClusterWorker(model, batch, seed, "cpu");
}
}
async function validate(rank) {
const probe = await rank.grads(0);
const loss = probe[0];
if (!Number.isFinite(loss)) return `${rank.backend} returned a non-finite loss (${loss})`;
if (Math.abs(loss) < 1e-6) {
return `${rank.backend} returned a loss of exactly ${loss} — the backend ran but produced zeros`;
}
return null;
}
self.onmessage = async (ev) => {
const m = ev.data;
if (m.t === "start") {
if (!node) {
await init();
let rank = await buildRank(m.backend ?? "auto", m.model, m.batch, m.seed);
const bad = await validate(rank);
if (bad) {
self.postMessage({ t: "log", text: `${bad} — falling back to cpu` });
rank = new rlx.ClusterWorker(m.model, m.batch, m.seed, "cpu");
}
if (rank.fallback) {
self.postMessage({ t: "log", text: `${rank.backend}: ${rank.fallback}` });
}
node = createRank({
id,
bus:
m.transport === "mesh"
? proxyBus(self, { onError: busError, precision: m.precision })
: channelBus("rlx-cluster", { onError: busError, precision: m.precision }),
rank,
model: m.model,
backend: rank.backend,
precision: m.precision ?? "f32",
post: (state) => self.postMessage(state),
});
}
node.start(m.lr);
} else if (m.t === "stop") {
node?.stop();
} else if (m.t === "lr") {
node?.setLr(m.lr);
}
};