mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
fix(krea2): edit workflow ran community checkpoints on turbo's control set
Removing modelLocked at the ecosystem level unlocked img2img:edit too, where the variant is chosen per-version: any id that wasn't `raw` fell to editTurbo, whose 15-step / cfg-2 ceilings sit below what an undistilled finetune needs. The six finetunes #4602 names are all full finetunes, so on edit they were not merely mis-defaulted but undriveable, with no way for the user to fix it. Fall back to editRaw instead — symmetric with the txt2img fallback this PR already established, behaviour-identical for both official bases, and it adds no new silently-substituting (ecosystem, workflow) pair to the #3520 population. Also: - ask "is this official?" in one place (isOfficialKrea2Version) rather than denylisting raw/turbo in the handler, so a fifth build is one edit - cover the txt2img path: diffusionModel absent on the official builds and present on community ones, plus the FAL size tiers and checkpoint+LoRA - add krea2-graph.test.ts pinning the unlock itself. With modelLocked on, the checkpoint clamp in common.ts rewrote every non-official id before the handler ever ran, and no test observed it — the feature could be reverted green - drop comments this change made false: the header claimed edit picks its variant regardless of version, and training.ts called the checkpoint locked Coverage is still the gate: all 201 community Krea 2 checkpoint models are covered:false today, so generation coverage needs flipping before the #4602 finetunes can actually run or enter auctions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1123,7 +1123,7 @@ export const ecosystemSupport: EcosystemSupport[] = [
|
||||
{ ecosystemId: ECO.Ernie, supportType: 'generation', modelTypes: checkpointAndLora },
|
||||
{ ecosystemId: ECO.Ernie, supportType: 'training', modelTypes: loraOnly },
|
||||
|
||||
// Krea 2 - base/turbo comfy variants support LoRA and community checkpoints (medium/large FAL tiers do not); LoRA training via AI-Toolkit
|
||||
// Krea 2 - raw/turbo comfy variants support LoRA and community checkpoints (medium/large FAL tiers do not); LoRA training via AI-Toolkit
|
||||
{
|
||||
ecosystemId: ECO.Krea2,
|
||||
supportType: 'generation',
|
||||
|
||||
@@ -4,11 +4,14 @@ import { createKrea2Input } from '../ecosystems/krea2.handler';
|
||||
import { krea2VersionIds } from '~/shared/data-graph/generation/krea2-graph';
|
||||
import type { GenerationHandlerCtx } from '../orchestration-new.service';
|
||||
|
||||
const communityVersionId = 8888;
|
||||
|
||||
const airsByVersionId: Record<number, string> = {
|
||||
[krea2VersionIds.raw]: 'urn:air:krea2:checkpoint:civitai:2656567@3072329',
|
||||
[krea2VersionIds.turbo]: 'urn:air:krea2:checkpoint:civitai:2656567@3072332',
|
||||
[krea2VersionIds.large]: 'urn:air:krea2:checkpoint:civitai:2656567@2983022',
|
||||
9001: 'urn:air:krea2:lora:civitai:9000@9001',
|
||||
[communityVersionId]: 'urn:air:krea2:checkpoint:civitai:8000@8888',
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
@@ -37,6 +40,54 @@ const editData = {
|
||||
images: [{ url: 'https://example.com/a.png' }],
|
||||
};
|
||||
|
||||
const createData = {
|
||||
ecosystem: 'Krea2',
|
||||
workflow: 'txt2img',
|
||||
prompt: 'a cat in a hat',
|
||||
quantity: 1,
|
||||
aspectRatio: { value: '1:1', width: 1024, height: 1024 },
|
||||
};
|
||||
|
||||
describe('createKrea2Input — txt2img', () => {
|
||||
it.each([
|
||||
['raw', krea2VersionIds.raw, 'raw'],
|
||||
['turbo', krea2VersionIds.turbo, 'turbo'],
|
||||
])('runs the official %s build on its own weights', async (_label, id, model) => {
|
||||
const result = await input({ ...createData, model: { id } });
|
||||
|
||||
expect(result).toMatchObject({
|
||||
engine: 'comfy',
|
||||
ecosystem: 'krea2',
|
||||
model,
|
||||
operation: 'createImage',
|
||||
});
|
||||
expect(result.diffusionModel).toBeUndefined();
|
||||
});
|
||||
|
||||
it('runs a community checkpoint on the raw build via diffusionModel', async () => {
|
||||
const result = await input({
|
||||
...createData,
|
||||
model: { id: communityVersionId },
|
||||
resources: [{ id: 9001, strength: 0.8 }],
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
engine: 'comfy',
|
||||
ecosystem: 'krea2',
|
||||
model: 'raw',
|
||||
operation: 'createImage',
|
||||
diffusionModel: airsByVersionId[communityVersionId],
|
||||
loras: { [airsByVersionId[9001]]: 0.8 },
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the FAL size tiers off the comfy path', async () => {
|
||||
const result = await input({ ...createData, model: { id: krea2VersionIds.large } });
|
||||
|
||||
expect(result).toMatchObject({ engine: 'fal', model: 'krea2', size: 'large' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('createKrea2Input — img2img:edit', () => {
|
||||
it.each([
|
||||
['turbo', krea2VersionIds.turbo],
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* Krea 2 Ecosystem Handler
|
||||
*
|
||||
* The Krea 2 checkpoint is locked, but its version selector splits across two
|
||||
* engines (see krea2-graph.ts):
|
||||
* The Krea 2 version selector splits across two engines (see krea2-graph.ts):
|
||||
*
|
||||
* - medium/large → FAL engine (Krea2FalImageGenInput). Size tiers, no LoRA;
|
||||
* exposes creativity + style references.
|
||||
@@ -27,7 +26,11 @@ import type {
|
||||
import { removeEmpty } from '~/utils/object-helpers';
|
||||
import type { GenerationGraphTypes } from '~/shared/data-graph/generation/generation-graph';
|
||||
import type { ResourceData } from '~/shared/data-graph/generation/common';
|
||||
import { krea2VersionIds, krea2VersionIdToSize } from '~/shared/data-graph/generation/krea2-graph';
|
||||
import {
|
||||
isOfficialKrea2Version,
|
||||
krea2VersionIds,
|
||||
krea2VersionIdToSize,
|
||||
} from '~/shared/data-graph/generation/krea2-graph';
|
||||
import { defineHandler } from './handler-factory';
|
||||
|
||||
type EcosystemGraphOutput = Extract<GenerationGraphTypes['Ctx'], { ecosystem: string }>;
|
||||
@@ -89,18 +92,14 @@ export const createKrea2Input = defineHandler<Krea2Ctx, [ImageGenStepTemplate]>(
|
||||
const images = 'images' in data ? data.images?.map((x) => x.url) : undefined;
|
||||
if (isEdit && !images?.length) throw new Error('At least one image is required to edit');
|
||||
|
||||
// `model: 'edit'` selects the edit graph, not a checkpoint — the base build it
|
||||
// runs on rides along as a diffusionModel AIR (same override the LTX handler
|
||||
// uses to point at a community fine-tune).
|
||||
// `diffusionModel` overrides the weights the named build loads (same override
|
||||
// the LTX handler uses to point at a community fine-tune). Edit always needs it:
|
||||
// `model: 'edit'` names the edit graph, not a checkpoint.
|
||||
let diffusionModel: string | undefined;
|
||||
if (isEdit) {
|
||||
if (!data.model) throw new Error('A Krea 2 base model is required to edit');
|
||||
diffusionModel = ctx.airs.getOrThrow(data.model.id);
|
||||
} else if (
|
||||
data.model &&
|
||||
data.model.id !== krea2VersionIds.raw &&
|
||||
data.model.id !== krea2VersionIds.turbo
|
||||
) {
|
||||
} else if (data.model && !isOfficialKrea2Version(data.model.id)) {
|
||||
diffusionModel = ctx.airs.getOrThrow(data.model.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { generationGraph } from './generation-graph';
|
||||
import { isOfficialKrea2Version, krea2VersionIds } from './krea2-graph';
|
||||
import type { GenerationCtx } from './context';
|
||||
|
||||
const ext: GenerationCtx = {
|
||||
limits: { maxQuantity: 4, maxResources: 9, vidQuantity: 1 },
|
||||
user: { isMember: true, tier: 'gold' },
|
||||
gateRules: [],
|
||||
};
|
||||
|
||||
const communityVersionId = 8888;
|
||||
|
||||
function init(workflow: string, modelId: number) {
|
||||
const graph = generationGraph as any;
|
||||
graph.init(
|
||||
{
|
||||
workflow,
|
||||
ecosystem: 'Krea2',
|
||||
model: { id: modelId, baseModel: 'Krea 2', model: { type: 'Checkpoint' } },
|
||||
},
|
||||
ext
|
||||
);
|
||||
return graph;
|
||||
}
|
||||
|
||||
const selectedModelId = (g: any) => g.getSnapshot().model?.id;
|
||||
|
||||
describe('krea2 community checkpoints', () => {
|
||||
// Pins the unlock: with `modelLocked` on, common.ts's checkpoint clamp rewrote
|
||||
// every non-official id to the ecosystem default before the handler ever ran.
|
||||
it('lets a non-official checkpoint through on txt2img', () => {
|
||||
expect(selectedModelId(init('txt2img', communityVersionId))).toBe(communityVersionId);
|
||||
});
|
||||
|
||||
it('gives it the comfy raw controls, not the FAL ones', () => {
|
||||
const g = init('txt2img', communityVersionId);
|
||||
expect(g.hasNode('cfgScale')).toBe(true);
|
||||
expect(g.hasNode('steps')).toBe(true);
|
||||
expect(g.hasNode('creativity')).toBe(false);
|
||||
});
|
||||
|
||||
// An official build missing from krea2VersionIdToVariant reads as community, and
|
||||
// the handler sends it to the orchestrator as an override on the raw build.
|
||||
it.each(Object.entries(krea2VersionIds))('recognises official %s', (_name, id) => {
|
||||
expect(isOfficialKrea2Version(id)).toBe(true);
|
||||
});
|
||||
|
||||
it('still gives the official size tiers the FAL controls', () => {
|
||||
const g = init('txt2img', krea2VersionIds.medium);
|
||||
expect(g.hasNode('creativity')).toBe(true);
|
||||
expect(g.hasNode('cfgScale')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('krea2 img2img:edit bases', () => {
|
||||
const cfgMax = (g: any) => g.getSnapshot('cfgScale').meta.max;
|
||||
const stepsMax = (g: any) => g.getSnapshot('steps').meta.max;
|
||||
|
||||
it.each([
|
||||
['turbo', krea2VersionIds.turbo, 2, 15],
|
||||
['raw', krea2VersionIds.raw, 10, 60],
|
||||
])('keeps the official %s base on its own control range', (_l, id, cfg, steps) => {
|
||||
const g = init('img2img:edit', id);
|
||||
expect(cfgMax(g)).toBe(cfg);
|
||||
expect(stepsMax(g)).toBe(steps);
|
||||
});
|
||||
|
||||
// Turbo's ceilings sit below what an undistilled finetune needs, so landing a
|
||||
// community checkpoint there leaves the user no way to drive it.
|
||||
it('gives a community checkpoint the full-step range, not turbo ceilings', () => {
|
||||
const g = init('img2img:edit', communityVersionId);
|
||||
expect(selectedModelId(g)).toBe(communityVersionId);
|
||||
expect(cfgMax(g)).toBe(10);
|
||||
expect(stepsMax(g)).toBe(60);
|
||||
});
|
||||
});
|
||||
@@ -16,10 +16,10 @@
|
||||
* - turbo: distilled, low-step variant
|
||||
* Controls: aspectRatio / resources (LoRA) / negativePrompt / cfgScale / steps / seed.
|
||||
*
|
||||
* The selected version is mapped to a `krea2Variant` discriminator
|
||||
* ('fal' | 'raw' | 'turbo') that swaps in the engine-appropriate controls. The
|
||||
* `img2img:edit` workflow selects a fourth 'edit' variant regardless of version
|
||||
* — Krea 2 exposes editing as an operation, not as its own build.
|
||||
* The selected version is mapped to a `krea2Variant` discriminator that swaps in
|
||||
* the engine-appropriate controls. `img2img:edit` maps to editRaw/editTurbo —
|
||||
* still per-version, because Krea 2 exposes editing as an operation on either
|
||||
* build, not as its own build.
|
||||
*/
|
||||
|
||||
import z from 'zod';
|
||||
@@ -95,11 +95,6 @@ export const krea2VersionIdToSize = new Map<number, Krea2Size>([
|
||||
[krea2VersionIds.large, 'large'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Map version ID → control-set variant. medium/large share the FAL control set;
|
||||
* raw/turbo each get the comfy control set. Unknown IDs (community checkpoints)
|
||||
* fall back to 'raw'.
|
||||
*/
|
||||
const krea2VersionIdToVariant = new Map<number, Krea2Variant>([
|
||||
[krea2VersionIds.medium, 'fal'],
|
||||
[krea2VersionIds.large, 'fal'],
|
||||
@@ -107,6 +102,8 @@ const krea2VersionIdToVariant = new Map<number, Krea2Variant>([
|
||||
[krea2VersionIds.turbo, 'turbo'],
|
||||
]);
|
||||
|
||||
export const isOfficialKrea2Version = (id: number) => krea2VersionIdToVariant.has(id);
|
||||
|
||||
// =============================================================================
|
||||
// Aspect Ratios
|
||||
// =============================================================================
|
||||
@@ -337,13 +334,14 @@ export const krea2Graph = new DataGraph<
|
||||
priorityOptions: krea2PriorityRatios,
|
||||
})
|
||||
)
|
||||
// Derive the control-set variant from the selected version, then swap in the
|
||||
// engine-appropriate controls (FAL: creativity/styleRefs; comfy: LoRA/cfg/steps).
|
||||
// Unknown ids are community checkpoints. Only the comfy builds can load one via
|
||||
// `diffusionModel`, so they fall back off the FAL tiers — and to the full-step
|
||||
// build, since turbo's 15-step / cfg-2 ceilings can't drive an undistilled model.
|
||||
.computed(
|
||||
'krea2Variant',
|
||||
(ctx): Krea2Variant => {
|
||||
if (ctx.workflow === 'img2img:edit')
|
||||
return ctx.model?.id === krea2VersionIds.raw ? 'editRaw' : 'editTurbo';
|
||||
return ctx.model?.id === krea2VersionIds.turbo ? 'editTurbo' : 'editRaw';
|
||||
return (ctx.model?.id ? krea2VersionIdToVariant.get(ctx.model.id) : undefined) ?? 'raw';
|
||||
},
|
||||
['model', 'workflow']
|
||||
|
||||
@@ -357,7 +357,7 @@ export const trainingModelInfo: {
|
||||
description: "Krea AI's in-house image generation model.",
|
||||
// Krea 2 is an AI-Toolkit-only ecosystem, so this AIR is NOT sent as the orchestrator
|
||||
// `model` (the orchestrator resolves the base model from the ecosystem); it's only used for
|
||||
// UI display / getModel. Points at the locked Krea 2 generation checkpoint.
|
||||
// UI display / getModel. Points at a Krea 2 generation checkpoint.
|
||||
air: 'urn:air:krea2:checkpoint:civitai:2656567@2983022',
|
||||
baseModel: 'Krea 2',
|
||||
isNew: true,
|
||||
|
||||
Reference in New Issue
Block a user