Veda predictor row: both official files always shown, each installed or downloadable
The row used to vanish as soon as one predictor was in models/veda, so the other could not be fetched from the app. Now each file shows its own state, the licence notice stays while a download button is shown, and a finished download refreshes the predictor list.
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@
|
||||
### Added
|
||||
|
||||
- **Sparse attention method "veda"** (H3 loader, SLA on, method veda; Experimental, UNTESTED on a GPU). A small learned predictor (Veda, arXiv 2605.30325) picks which 128-token tiles of the attention map matter and an INT8 Triton kernel computes only those, at the predictor's own trained budget (90% for text-to-video). It needs CUDA and Triton and a predictor file in ComfyUI/models/veda, chosen in "veda_predictor" (auto takes the text-to-video file, else the first). Works with LoRA (it sees q/k/v after the LoRA), MXFP8 (weights only: q/k/v stay bf16), Two stages (a size the predictor was not trained for uses the nearest tile plan, and the log says so), dense steps and the attention backend, which keeps the text refiner, masked calls and short sequences. sla_sparsity, the rising share and the references choice are not read in this first version. When Veda cannot run, the status says why and the chosen backend is installed on its own. One log line per run in the SLA line's wording (calls, tile pairs kept, asked vs real sparsity, share of full attention computed, steps at full attention). The Triton kernel is checked against an exact reference on the GPU before first use; the torch half (tiles, predictor, selection, the override on a real tiny H3) is tested on CPU.
|
||||
- **Veda predictor download** (Settings > Models, shown only for method veda and only while models/veda holds no predictor): a button per official file (text-to-video, reference-to-video) fetches it straight from Hugging Face on your click, with the MiniMax H3 Community License named and linked beside it. FunUI does not host or bundle the file. The download streams to a `.part` file and is kept only when its size and checksum match; taggers and this share one downloader (`core/fetch.py`).
|
||||
- **Veda predictor download** (Settings > Models, shown only for method veda): both official files with their state, installed or a button per missing one, so you can have both (text-to-video, reference-to-video) fetches it straight from Hugging Face on your click, with the MiniMax H3 Community License named and linked beside it. FunUI does not host or bundle the file. The download streams to a `.part` file and is kept only when its size and checksum match; taggers and this share one downloader (`core/fetch.py`).
|
||||
- **Run meter** (Settings > Engine > Sampling, off by default). Logs how grainy the picture is, across it and over time, and how much it still changes at each step, then compares with the previous run of the same seed. It adds no model call, so flipping one setting and rendering again shows what that setting did.
|
||||
- **Post settings for the finished video** (Settings > Engine > Post, all off or neutral by default): Smooth jagged edges (Light or Strong; FXAA-style, only strongly contrasting edge pixels are softened), Auto contrast, Auto brightness and Colour strength. Contrast and brightness are measured once on the whole clip and applied as one fixed curve, so nothing flickers from frame to frame. They run on the frames before encoding, so NVENC, its CPU fallback and every codec get the same picture. Save Video reads them from the pipeline's settings object (older saved pipelines are linked to it automatically). Tried on the CPU only.
|
||||
- **LoRA Loader: "Apply to stage"** (First, Second or Both, per slot). Shown only while the Two stages module is present and not switched off for the project; the feature itself may stay off in the sampler settings. A LoRA set to First works only in the smaller first stage, one set to Second only in the full-size steps; Two stages hands each stage the model with its own LoRAs, and ComfyUI re-patches the weights between the stages. Only the model changes for First or Second (the text encoder is encoded before sampling, so its strength is not used). When Two stages is off, or a run cannot be cut in two, those LoRAs are not applied and the Log says so. Both is the old behaviour. Tried on a small model on the CPU with every feature switched on; not on a GPU yet.
|
||||
|
||||
@@ -123,7 +123,7 @@ export const models = (app) => function mount() {
|
||||
return row;
|
||||
};
|
||||
|
||||
// The Veda predictor (method veda): shown only while models/veda holds none. The file is not ours to host (MiniMax H3 Community
|
||||
// The Veda predictors (method veda): both official files, each with its state. The files are not ours to host (MiniMax H3 Community
|
||||
// License), so the one way offered is a fetch from the official Hugging Face address on this click, with the licence beside it.
|
||||
const vedaRow = () => {
|
||||
const row = c.region.stack({ gap: "sm" });
|
||||
@@ -132,15 +132,17 @@ export const models = (app) => function mount() {
|
||||
let r;
|
||||
try { r = await api.vedaState(); } catch (err) { return row.set([c.hint.default({ text: `Could not ask about the Veda predictor: ${err.message}` })]); }
|
||||
if (gone) return;
|
||||
if ((r.files || []).length) { if (fetching) { fetching = false; specs = {}; describe().then(draw); } return row.set([]); } // it arrived: the file list is read again
|
||||
const busy = r.models.some((m) => m.state === "downloading");
|
||||
if (fetching && !busy) { fetching = false; specs = {}; describe().then(draw); } // a download ended: the predictor list is read again
|
||||
const mb = (n) => `${Math.round(n / 1048576)} MB`;
|
||||
row.set([c.banner.info({ text: "Veda needs its predictor file in ComfyUI/models/veda, and none is there. You can fetch it straight from Hugging Face; nothing is downloaded until you click." }),
|
||||
...r.models.map((m) => c.settingsRow.default({ label: `${m.title} · ${mb(m.size)}`, hint: m.state === "error" ? `Failed: ${m.error}` : m.state === "downloading" ? `${Math.floor(100 * m.done / m.size)}%` : m.hint,
|
||||
control: c.button.sm({ label: m.state === "downloading" ? "Downloading…" : m.state === "error" ? "Try again" : "Download", tone: "primary", disabled: busy,
|
||||
onClick: async () => { try { await api.vedaDownload(m.id); fetching = true; } catch (err) { c.toast.warn({ text: err.message }); } show(); } }) })),
|
||||
c.settingsRow.default({ label: `By downloading you accept the ${r.license.name}.`, hint: "FunUI does not host or bundle the file. It has its own terms of use, including who may use it and where.",
|
||||
control: c.button.sm({ label: "Read the licence", tone: "ghost", onClick: () => app.dom.openTab(r.license.url) }) })]);
|
||||
const offered = r.models.some((m) => m.state !== "ready");
|
||||
row.set([...((r.files || []).length ? [] : [c.banner.info({ text: "Veda needs its predictor file in ComfyUI/models/veda, and none is there. You can fetch it straight from Hugging Face; nothing is downloaded until you click." })]),
|
||||
...r.models.map((m) => c.settingsRow.default({ label: `${m.title} · ${mb(m.size)}`, hint: m.state === "ready" ? "Installed" : m.state === "error" ? `Failed: ${m.error}` : m.state === "downloading" ? `${Math.floor(100 * m.done / m.size)}%` : m.hint,
|
||||
control: m.state === "ready" ? c.button.sm({ label: "Installed", tone: "ghost", disabled: true })
|
||||
: c.button.sm({ label: m.state === "downloading" ? "Downloading…" : m.state === "error" ? "Try again" : "Download", tone: "primary", disabled: busy,
|
||||
onClick: async () => { try { await api.vedaDownload(m.id); fetching = true; } catch (err) { c.toast.warn({ text: err.message }); } show(); } }) })),
|
||||
...(offered ? [c.settingsRow.default({ label: `By downloading you accept the ${r.license.name}.`, hint: "FunUI does not host or bundle the file. It has its own terms of use, including who may use it and where.",
|
||||
control: c.button.sm({ label: "Read the licence", tone: "ghost", onClick: () => app.dom.openTab(r.license.url) }) })] : [])]);
|
||||
clearTimeout(timer);
|
||||
timer = busy ? setTimeout(() => { if (!gone && row.node && row.node.isConnected) show(); }, 1500) : null; // a redrawn page leaves its old row behind: that one stops asking
|
||||
};
|
||||
|
||||
@@ -176,9 +176,18 @@ test("the Veda predictor row appears only for method veda with no file, names th
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
assert.deepEqual(fetched, ["t2va"]);
|
||||
assert.ok([...page.node.querySelectorAll("button")].every((b) => b.textContent !== "Download" || b.disabled), "one download at a time");
|
||||
state.files = ["a.safetensors"]; state.models[0].state = "ready";
|
||||
state.files = ["a.safetensors"]; state.models[0].state = "ready"; state.models[1].state = "absent";
|
||||
page.node.remove();
|
||||
page = await open({ sla: true, sla_method: "veda" });
|
||||
assert.doesNotMatch(page.node.textContent, /Veda needs its predictor/, "a predictor in the folder: no row");
|
||||
const buttons = () => [...page.node.querySelectorAll("button")].map((b) => b.textContent);
|
||||
assert.doesNotMatch(page.node.textContent, /none is there/);
|
||||
assert.deepEqual(buttons().filter((t) => /Installed|Download/.test(t)), ["Installed", "Download"], "one installed, the other still offered");
|
||||
assert.match(page.node.textContent, /By downloading you accept/, "the licence stays while a button is shown");
|
||||
page.node.remove();
|
||||
state.files = ["a.safetensors", "b.safetensors"]; state.models[1].state = "ready";
|
||||
page = await open({ sla: true, sla_method: "veda" });
|
||||
assert.deepEqual(buttons().filter((t) => /Installed|Download/.test(t)), ["Installed", "Installed"], "both installed, no download button");
|
||||
assert.match(page.node.textContent, /T2VA \/ FL2VA/);
|
||||
assert.match(page.node.textContent, /R2VA/);
|
||||
page.node.remove();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user