Harden Exact reconcile and soften post-Generate warm.
Overwrite foreign steps/cfg in sparse patches when the user did not ask for params, and skip the cold-load path when Ollama already has the chat model resident. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+34
-11
@@ -667,6 +667,12 @@
|
||||
const clearSessionKeys = [];
|
||||
for (const key of EXACT_GENERATE_PARAM_KEYS) {
|
||||
if (out[key] != null) {
|
||||
if (!userParamIntent && defaults[key] != null && String(out[key]) !== String(defaults[key])) {
|
||||
out[key] = defaults[key];
|
||||
if (sessionExact?.[key] != null && String(sessionExact[key]) !== String(defaults[key])) {
|
||||
clearSessionKeys.push(key);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (userParamIntent && sessionExact?.[key] != null) {
|
||||
@@ -1140,7 +1146,7 @@
|
||||
continue;
|
||||
}
|
||||
const eps = key === "steps" ? 0.5 : 0.051;
|
||||
if (patch && patch[key] != null && !numClose(patch[key], want, eps)) {
|
||||
if (userParamIntent && patch && patch[key] != null && !numClose(patch[key], want, eps)) {
|
||||
continue;
|
||||
}
|
||||
if (userParamIntent && sessionExact && sessionExact[key] != null) {
|
||||
@@ -2361,7 +2367,7 @@ ${patch.prompt}`;
|
||||
if (want == null) {
|
||||
return false;
|
||||
}
|
||||
if (patch?.[k] != null && String(patch[k]) !== String(want)) {
|
||||
if (userIntent && patch?.[k] != null && String(patch[k]) !== String(want)) {
|
||||
return false;
|
||||
}
|
||||
if (userIntent && state.sessionExact?.[k] != null) {
|
||||
@@ -5496,16 +5502,16 @@ ${patch.prompt}`;
|
||||
return new Promise((resolve) => {
|
||||
const model = $2("sa_model")?.value;
|
||||
if (!model || typeof genericRequest !== "function") {
|
||||
resolve(false);
|
||||
resolve({ ok: false, alreadyResident: false });
|
||||
return;
|
||||
}
|
||||
if (!force && !state.llmParked) {
|
||||
resolve(false);
|
||||
resolve({ ok: false, alreadyResident: false });
|
||||
return;
|
||||
}
|
||||
const baseUrl = $2("sa_base_url")?.value || "http://127.0.0.1:11434";
|
||||
let settled = false;
|
||||
const finish = (ok) => {
|
||||
const finish = (ok, alreadyResident = false) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
@@ -5514,10 +5520,19 @@ ${patch.prompt}`;
|
||||
if (ok) {
|
||||
state.expectColdLoad = false;
|
||||
}
|
||||
resolve(!!ok);
|
||||
resolve({ ok: !!ok, alreadyResident: !!alreadyResident });
|
||||
};
|
||||
setTimeout(() => finish(false), 18e4);
|
||||
genericRequest("AssistentWarmLlm", { baseUrl, model }, () => finish(true), 0, () => finish(false));
|
||||
genericRequest(
|
||||
"AssistentWarmLlm",
|
||||
{ baseUrl, model },
|
||||
(data) => {
|
||||
const skipped = !!(data && (data.skipped === "already_resident" || data.already_resident === true));
|
||||
finish(true, skipped);
|
||||
},
|
||||
0,
|
||||
() => finish(false)
|
||||
);
|
||||
});
|
||||
}
|
||||
function cancelWaitForNewImage() {
|
||||
@@ -5845,7 +5860,8 @@ ${patch.prompt}`;
|
||||
}
|
||||
state.generating = false;
|
||||
setInterruptVisible(state.busy);
|
||||
state.expectColdLoad = true;
|
||||
const parkedBeforeWarm = !!state.llmParked;
|
||||
state.expectColdLoad = parkedBeforeWarm;
|
||||
if (jobs && lastSrc && state.selectedGenResultId && epoch === state.chatEpoch) {
|
||||
const row = state.genResults.find((r) => r.id === state.selectedGenResultId);
|
||||
if (row?.patch) {
|
||||
@@ -5857,9 +5873,16 @@ ${patch.prompt}`;
|
||||
const multiDone = !!(jobs && finishedGenResultCount() > 1);
|
||||
const willAutoCritique = !multiDone && !!$2("sa_auto_critique")?.checked;
|
||||
if (state.view === "chat" && paneVisible && !willAutoCritique && epoch === state.chatEpoch) {
|
||||
startBusyUi("warming");
|
||||
setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026");
|
||||
await warmLlm({ force: true });
|
||||
if (parkedBeforeWarm || state.expectColdLoad) {
|
||||
startBusyUi("warming");
|
||||
setStatus("\u0412\u043E\u0437\u0432\u0440\u0430\u0449\u0430\u044E LLM \u0432 GPU\u2026");
|
||||
}
|
||||
const warmResult = await warmLlm({ force: true });
|
||||
if (warmResult?.alreadyResident) {
|
||||
state.expectColdLoad = false;
|
||||
} else if (!warmResult?.ok) {
|
||||
state.expectColdLoad = true;
|
||||
}
|
||||
}
|
||||
if (epoch !== state.chatEpoch) {
|
||||
return null;
|
||||
|
||||
@@ -4,9 +4,7 @@ SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat +
|
||||
|
||||
**Turn model:** one user message is one *turn*. A turn may fan out into nested LLM *hops* — Krea prompt prep, empty-patch retry, vision, auto-critique. Hops share one `HOP_BUDGET`, never re-read the user's text (their prompt is client-authored), and pass the busy gate that blocks new user sends. What a reply does to generation state is decided once, in `resolveTurnIntent`: the model's `actions:["generate"]` / `look_at` win, RU intent heuristics only back it up when the model forgets, and an explicit «запомни, не генерируй» vetoes both.
|
||||
|
||||
**Version 0.14.1** — On Generate, force Exact turbo/raw params when live Swarm still has foreign leftovers (e.g. steps 20 / cfg 7). `detectKreaProfileName` no longer invents turbo for unlabeled ckpts (realismByStableYogi → raw). Live context injects `krea_profile` + `recommended_params`. RU «шаг/шагами» counts as param intent. Soft sparse-prompt exception when session ≠ Exact profile.
|
||||
|
||||
**Version 0.14.1** — Generate always merges Exact turbo|raw `steps`/`cfg`/`sigma_shift` (client-authoritative; sparse LLM omit is safe). Park/warm skip no-op Ollama round-trips when the chat model is already (un)loaded via `/api/ps`. Builds on 0.14.0.
|
||||
**Version 0.14.1** — Force Exact turbo/raw numbers on Generate when live Swarm still has foreign leftovers (e.g. steps 20 / cfg 7), including when the profile *label* is turbo/raw. detectKreaProfileName no longer invents turbo for unlabeled ckpts (realismByStableYogi → raw). Live context injects krea_profile + ecommended_params. RU «шаг/шагами» counts as param intent. Soft sparse-prompt exception when session ≠ Exact profile. Post-Generate warm still runs with park off, but skips the cold path when Ollama already has the chat model resident.
|
||||
|
||||
**Version 0.14.0** — **Чат = сессия генерации**: у каждого чата свои params/LoRA/checkpoint/кадр/refs; модель шлёт sparse-дельту + `generate`/`look_at`/`ask`; без вкладки Карточки и Civitai/wanted hops. **Сжатие контекста**: rolling-саммари той же Ollama-моделью, чип бюджета `N / num_ctx`, авто перед отправкой, `/compress`.
|
||||
|
||||
|
||||
+34
-10
@@ -1353,7 +1353,7 @@
|
||||
if (want == null) {
|
||||
return false;
|
||||
}
|
||||
if (patch?.[k] != null && String(patch[k]) !== String(want)) {
|
||||
if (userIntent && patch?.[k] != null && String(patch[k]) !== String(want)) {
|
||||
return false;
|
||||
}
|
||||
if (userIntent && state.sessionExact?.[k] != null) {
|
||||
@@ -4751,20 +4751,25 @@
|
||||
}
|
||||
|
||||
/** Re-load chat model into VRAM. force=true after Generate even without park — Krea often evicts Ollama. */
|
||||
/**
|
||||
* Re-load chat model into VRAM. force=true after Generate even without park —
|
||||
* Krea often evicts Ollama. Soft: AssistentWarmLlm skipped=already_resident
|
||||
* clears expectColdLoad without a fake cold path. Resolves { ok, alreadyResident }.
|
||||
*/
|
||||
function warmLlm({ force = false } = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const model = $('sa_model')?.value;
|
||||
if (!model || typeof genericRequest !== 'function') {
|
||||
resolve(false);
|
||||
resolve({ ok: false, alreadyResident: false });
|
||||
return;
|
||||
}
|
||||
if (!force && !state.llmParked) {
|
||||
resolve(false);
|
||||
resolve({ ok: false, alreadyResident: false });
|
||||
return;
|
||||
}
|
||||
const baseUrl = $('sa_base_url')?.value || 'http://127.0.0.1:11434';
|
||||
let settled = false;
|
||||
const finish = (ok) => {
|
||||
const finish = (ok, alreadyResident = false) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
@@ -4773,11 +4778,20 @@
|
||||
if (ok) {
|
||||
state.expectColdLoad = false;
|
||||
}
|
||||
resolve(!!ok);
|
||||
resolve({ ok: !!ok, alreadyResident: !!alreadyResident });
|
||||
};
|
||||
// VL cold-load can exceed a minute — don't time out the flag early.
|
||||
setTimeout(() => finish(false), 180000);
|
||||
genericRequest('AssistentWarmLlm', { baseUrl, model }, () => finish(true), 0, () => finish(false));
|
||||
genericRequest(
|
||||
'AssistentWarmLlm',
|
||||
{ baseUrl, model },
|
||||
(data) => {
|
||||
const skipped = !!(data && (data.skipped === 'already_resident' || data.already_resident === true));
|
||||
finish(true, skipped);
|
||||
},
|
||||
0,
|
||||
() => finish(false),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5130,7 +5144,10 @@
|
||||
|
||||
state.generating = false;
|
||||
setInterruptVisible(state.busy);
|
||||
state.expectColdLoad = true;
|
||||
// Soft cold flag: only assume eviction when we parked. Otherwise wait for
|
||||
// warmLlm — already_resident means keep chatting without a cold path.
|
||||
const parkedBeforeWarm = !!state.llmParked;
|
||||
state.expectColdLoad = parkedBeforeWarm;
|
||||
|
||||
if (jobs && lastSrc && state.selectedGenResultId && epoch === state.chatEpoch) {
|
||||
const row = state.genResults.find((r) => r.id === state.selectedGenResultId);
|
||||
@@ -5144,9 +5161,16 @@
|
||||
const multiDone = !!(jobs && finishedGenResultCount() > 1);
|
||||
const willAutoCritique = !multiDone && !!$('sa_auto_critique')?.checked;
|
||||
if (state.view === 'chat' && paneVisible && !willAutoCritique && epoch === state.chatEpoch) {
|
||||
startBusyUi('warming');
|
||||
setStatus('Возвращаю LLM в GPU…');
|
||||
await warmLlm({ force: true });
|
||||
if (parkedBeforeWarm || state.expectColdLoad) {
|
||||
startBusyUi('warming');
|
||||
setStatus('Возвращаю LLM в GPU…');
|
||||
}
|
||||
const warmResult = await warmLlm({ force: true });
|
||||
if (warmResult?.alreadyResident) {
|
||||
state.expectColdLoad = false;
|
||||
} else if (!warmResult?.ok) {
|
||||
state.expectColdLoad = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (epoch !== state.chatEpoch) {
|
||||
|
||||
+8
-2
@@ -82,8 +82,14 @@ export function exactKeysToForce(live, defaults, {
|
||||
continue;
|
||||
}
|
||||
const eps = key === 'steps' ? 0.5 : 0.051;
|
||||
// Patch asked for a non-Exact number — honor it.
|
||||
if (patch && patch[key] != null && !numClose(patch[key], want, eps)) {
|
||||
// Honor intentional non-Exact patch only when the user asked for params.
|
||||
// Sparse LLM echoes of live 20/7 must not block Exact (turbo/raw label -> numbers).
|
||||
if (
|
||||
userParamIntent
|
||||
&& patch
|
||||
&& patch[key] != null
|
||||
&& !numClose(patch[key], want, eps)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (userParamIntent && sessionExact && sessionExact[key] != null) {
|
||||
|
||||
+12
-1
@@ -372,7 +372,7 @@ export function resolveExactProfileDefaults({ exact, profiles, profileName } = {
|
||||
}
|
||||
|
||||
/**
|
||||
* On generate, inject Exact profile steps/cfg/sigma when the patch omitted them.
|
||||
* On generate, inject Exact profile steps/cfg/sigma when omitted, and overwrite foreign leftovers when the user did not ask for params.
|
||||
* Client is authoritative so sparse LLM deltas are safe. Honors explicit patch values
|
||||
* and sessionExact when the user asked for params this turn.
|
||||
* Returns clearSessionKeys so the UI apply path is not blocked by stale sessionExact.
|
||||
@@ -392,6 +392,17 @@ export function mergeExactParamsForGenerate(patch, {
|
||||
const clearSessionKeys = [];
|
||||
for (const key of EXACT_GENERATE_PARAM_KEYS) {
|
||||
if (out[key] != null) {
|
||||
// Sparse LLM may echo live leftovers (20/7). Without user intent, force Exact.
|
||||
if (
|
||||
!userParamIntent
|
||||
&& defaults[key] != null
|
||||
&& String(out[key]) !== String(defaults[key])
|
||||
) {
|
||||
out[key] = defaults[key];
|
||||
if (sessionExact?.[key] != null && String(sessionExact[key]) !== String(defaults[key])) {
|
||||
clearSessionKeys.push(key);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (userParamIntent && sessionExact?.[key] != null) {
|
||||
|
||||
@@ -69,6 +69,14 @@ describe('kreaProfile', () => {
|
||||
// Intentional non-Exact patch value is kept.
|
||||
assert.deepEqual(
|
||||
exactKeysToForce({ steps: 20, cfg: 7, sigma_shift: 1.15 }, turbo, { patch: { cfg: 7 } }),
|
||||
['steps', 'cfg'],
|
||||
);
|
||||
assert.deepEqual(
|
||||
exactKeysToForce(
|
||||
{ steps: 20, cfg: 7, sigma_shift: 1.15 },
|
||||
turbo,
|
||||
{ patch: { cfg: 7 }, userParamIntent: true },
|
||||
),
|
||||
['steps'],
|
||||
);
|
||||
assert.deepEqual(
|
||||
|
||||
+13
-1
@@ -123,10 +123,22 @@ describe('session.js', () => {
|
||||
|
||||
const explicit = mergeExactParamsForGenerate(
|
||||
{ generate: true, steps: 12, cfg: 2 },
|
||||
{ exact, profiles: exact.profiles, profileName: 'turbo' },
|
||||
{ exact, profiles: exact.profiles, profileName: 'turbo', userParamIntent: true },
|
||||
);
|
||||
assert.equal(explicit.patch.steps, 12);
|
||||
assert.equal(explicit.patch.cfg, 2);
|
||||
|
||||
const forced = mergeExactParamsForGenerate(
|
||||
{ generate: true, steps: 20, cfg: 7 },
|
||||
{
|
||||
exact: { generation: { steps: 8, cfg: 1 }, profiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 } } },
|
||||
profiles: { turbo: { steps: 8, cfg: 1, sigma_shift: 1.15 } },
|
||||
profileName: 'turbo',
|
||||
userParamIntent: false,
|
||||
},
|
||||
);
|
||||
assert.equal(forced.patch.steps, 8);
|
||||
assert.equal(forced.patch.cfg, 1);
|
||||
});
|
||||
|
||||
it('resolveExactProfileDefaults picks raw over generation defaults', () => {
|
||||
|
||||
Reference in New Issue
Block a user