LoRA training: preview strength setting, negative allowed

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpnF7GwNGMbngU11wjym7P
This commit is contained in:
Claude
2026-10-18 21:16:11 +00:00
parent 107bc05f91
commit 0da95575da
3 changed files with 8 additions and 2 deletions
+3 -1
View File
@@ -114,6 +114,7 @@ export default {
await session.flush();
const settings = settingsOf(doc);
delete settings.preset; // a starting point the person picked, not a setting of the trainer
delete settings.preview_strength; // used by the previews only
settings.trigger = doc.trigger || "";
settings.postfix = doc.postfix || "";
if (resumeId) settings.resume = resumeId;
@@ -197,6 +198,7 @@ export default {
refreshLive();
}
/** A picture or clip made by the pipeline with the LoRA so far; when `auto`, training carries on afterwards. */
const previewStrength = () => { const v = Number(settingsOf(session.doc).preview_strength); return Number.isFinite(v) ? v : 1; };
async function previewNow(auto) {
const paused = ui.run.paused;
if (!paused || !paused.preview) return;
@@ -204,7 +206,7 @@ export default {
setRun({ state: "previewing", message: `Making a preview with the LoRA at step ${paused.step}` });
draw();
let out;
try { out = app.runner && app.runner.preview ? await app.runner.preview({ lora: paused.preview }) : { error: "Previews need the app's Generate." }; } catch (err) { out = { error: err.message }; }
try { out = app.runner && app.runner.preview ? await app.runner.preview({ lora: paused.preview, strength: previewStrength() }) : { error: "Previews need the app's Generate." }; } catch (err) { out = { error: err.message }; }
if (out.image) {
const im = out.image;
ui.previews.push({ id: `p${ui.previews.length + 1}`, step: paused.step, src: { kind: "output", type: im.type || "output", subfolder: im.subfolder || "", filename: im.filename }, media_kind: VIDEO.test(im.filename) ? "video" : "image" });
+2 -1
View File
@@ -4,7 +4,7 @@ import { stillOf } from "./state.js";
/** The node's own defaults. A set remembers what was changed from them. */
export const DEFAULTS = {
name: "funpack_lora", use: "all", preview_every: 0, save_every: 0, steps: 500, learning_rate: 0.0001, rank: 16, algorithm: "LoRA", optimizer: "AdamW", loss: "MSE",
name: "funpack_lora", use: "all", preview_every: 0, preview_strength: 1, save_every: 0, steps: 500, learning_rate: 0.0001, rank: 16, algorithm: "LoRA", optimizer: "AdamW", loss: "MSE",
batch_size: 1, grad_accumulation: 1, resolution: 512, aspect_buckets: false, frames: 33, fps: 0, start: "start", seed: 0,
training_dtype: "bf16", lora_dtype: "bf16", gradient_checkpointing: true, offloading: false, bypass: false, swap_blocks: 0,
};
@@ -94,6 +94,7 @@ export function trainStep({ app, session, go, ui }) {
num("learning_rate", "Learning rate", "How big each step is. Lower is safer and slower.", { min: 0.0000001, max: 1, step: 0.0001, precision: 7 }),
num("rank", "Rank", "How much it can learn. Higher holds more detail and makes a bigger file.", { min: 1, max: 128, step: 1 }),
num("save_every", "Save a copy every (steps)", "0 saves only the finished LoRA. Otherwise a copy of the LoRA so far goes to your loras folder every this many steps, named like yourname_step200, so you can pick the best moment.", { min: 0, max: 100000, step: 10 }),
num("preview_strength", "Preview strength", "How strongly the LoRA is applied in previews. 1 is the usual; use a negative value (for example -1) to see a LoRA trained to be subtracted. Only for previews: it is not part of the training.", { min: -10, max: 10, step: 0.1, precision: 2 }),
num("preview_every", "Preview every (steps)", "0 never. Otherwise training waits every this many steps while your pipeline makes a picture or clip with the LoRA so far (the picked scene, the project's own settings), then carries on. Needs a LoRA loader in the pipeline; each wait loads the models again.", { min: 0, max: 100000, step: 10 })] }),
c.group.default({ label: "Pictures and clips", rows: [
num("resolution", "Size", "Pixels per side. Everything is fitted to about this area.", { min: 64, max: 2048, step: 32 }),
@@ -296,6 +296,7 @@ test("Tags step: a clip's frame count and rate show beside its label", async ()
test("a preview step pauses the run, the pipeline makes a preview with the LoRA so far, and training carries on", async () => {
const f = fake();
f.doc.settings = { preview_strength: -0.8 };
const previews = [];
f.app.runner = { preview: async (o) => { previews.push(o); return { image: { filename: "pv.mp4", subfolder: "", type: "output" } }; } };
let queued = 0, polls = 0;
@@ -318,6 +319,8 @@ test("a preview step pauses the run, the pipeline makes a preview with the LoRA
await wait(3600);
assert.equal(previews.length, 1);
assert.equal(previews[0].lora, "cats_preview.safetensors");
assert.equal(previews[0].strength, -0.8, "the preview uses the strength set for it, here a negative one");
assert.ok(!("preview_strength" in f.calls.find((c) => c[0] === "plan")[2]), "and it is not sent to the trainer");
assert.equal(queued, 2, "training was queued again to carry on");
const plans = f.calls.filter((c) => c[0] === "plan");
assert.equal(plans[1][2].resume, "r1");