Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,37 @@ describe('compileCanvasGraph', () => {
}
);

it('records the SD 3 encoder overrides used for a Canvas image', () => {
const clipG = { ...clipEmbed, key: 'clip-g', name: 'CLIP G', variant: 'gigantic' };
const { backendGraph } = compile(sd3Model, 'img2img', {
settings: { clipGEmbedModel: clipG, clipLEmbedModel: clipEmbed, t5EncoderModel: t5Encoder },
});

expect(backendGraph.nodes.model_loader).toMatchObject({
clip_g_model: clipG,
clip_l_model: clipEmbed,
t5_encoder_model: t5Encoder,
});
const metadata = getNodeByType(backendGraph, 'core_metadata');
expect(metadata).toMatchObject({
clip_g_model: clipG,
clip_l_model: clipEmbed,
generation_mode: 'sd3_img2img',
t5_encoder: t5Encoder,
});
expect(getEdge(backendGraph, 'canvas_output', 'metadata')?.source.node_id).toBe(metadata?.id);
});

it('leaves bundled SD 3 encoders unrecorded when no override was selected', () => {
const { backendGraph } = compile(sd3Model, 'img2img');
const metadata = getNodeByType(backendGraph, 'core_metadata');

expect(metadata).toBeDefined();
expect(metadata?.clip_g_model).toBeUndefined();
expect(metadata?.clip_l_model).toBeUndefined();
expect(metadata?.t5_encoder).toBeUndefined();
});

it('feeds the encode node from the same VAE source as the decode node', () => {
const { backendGraph } = compile(sd1Model, 'img2img');
const encode = getNodeByType(backendGraph, 'i2l');
Expand Down
3 changes: 3 additions & 0 deletions invokeai/frontend/webv2/src/features/generation/core/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,10 @@ const buildSD3Graph = (
addEdge(graph, negCond, 'conditioning', denoise, 'negative_conditioning');
addEdge(graph, seed, 'value', denoise, 'seed');
addMetadata(graph, output, settings, model, 'sd3_txt2img', projectSettings, {
clip_g_model: modelLoader.clip_g_model,
clip_l_model: modelLoader.clip_l_model,
scheduler: undefined,
t5_encoder: modelLoader.t5_encoder_model,
vae: vaeModel ?? undefined,
...(shouldUsePidDecode(settings, model.base) ? getPidMetadata(settings) : {}),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2332,6 +2332,8 @@ describe('remix round trip', () => {

expect([...recorded].sort()).toEqual([
'clipEmbedModel',
'clipGEmbedModel',
'clipLEmbedModel',
'componentSourceModel',
'gemma2EncoderModel',
'ideogram4UnconditionalModel',
Expand All @@ -2349,6 +2351,61 @@ describe('remix round trip', () => {
});
});

describe('SD 3 encoder recall', () => {
const model: MainModelConfig = { base: 'sd-3', key: 'sd3-model', name: 'SD 3', type: 'main' };
const t5: ComponentModelConfig = { base: 'any', key: 'sd3-t5', name: 'T5', type: 't5_encoder' };
const clipL: ComponentModelConfig = {
base: 'any',
key: 'sd3-clip-l',
name: 'CLIP L',
type: 'clip_embed',
variant: 'large',
};
const clipG: ComponentModelConfig = { ...clipL, key: 'sd3-clip-g', name: 'CLIP G', variant: 'gigantic' };
const currentComponents = {
clipGEmbedModel: { ...clipG, key: 'current-clip-g' },
clipLEmbedModel: { ...clipL, key: 'current-clip-l' },
t5EncoderModel: { ...t5, key: 'current-t5' },
};
const recall = (metadata: Record<string, unknown>, kind: ImageRecallKind = 'all') =>
buildImageRecallSettings({
currentValues: createValues({ model, modelKey: model.key, ...currentComponents }),
image,
kind,
metadata: { model: { key: model.key }, ...metadata },
models: [model, t5, clipL, clipG, ...Object.values(currentComponents)],
supportedModels: [model],
vaeModels: [],
});

it.each(['all', 'remix'] as const)('restores the recorded overrides with %s on the same main model', (kind) => {
const result = recall(
{ clip_g_model: { key: clipG.key }, clip_l_model: { key: clipL.key }, t5_encoder: { key: t5.key } },
kind
);

expect(result?.values).toMatchObject({
clipGEmbedModel: clipG,
clipLEmbedModel: clipL,
model,
t5EncoderModel: t5,
});
expect(result?.fields).toContain('components');
});

it.each([
['missing metadata', {}],
['unavailable encoders', { clip_g_model: { key: 'missing-g' }, clip_l_model: { key: 'missing-l' } }],
['a non-CLIP model', { clip_g_model: { key: t5.key }, clip_l_model: { key: t5.key } }],
['swapped CLIP variants', { clip_g_model: { key: clipL.key }, clip_l_model: { key: clipG.key } }],
] as const)('keeps the current compatible overrides with %s', (_label, metadata) => {
const result = recall(metadata);

expect(result?.values).toMatchObject(currentComponents);
expect(result?.fields).not.toContain('components');
});
});

describe('SDXL Refiner recall', () => {
const refiner: MainModelConfig = {
base: 'sdxl-refiner',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,8 @@ type RecalledComponentSetting = keyof Pick<
GenerateWidgetValues,
| 'animaInpaintAdapterModel'
| 'clipEmbedModel'
| 'clipGEmbedModel'
| 'clipLEmbedModel'
| 'componentSourceModel'
| 'gemma2EncoderModel'
| 'ideogram4UnconditionalModel'
Expand Down Expand Up @@ -879,6 +881,8 @@ const RECALLED_COMPONENTS: readonly RecalledComponent[] = [
{ isMainModel: true, metadataKey: 'wan_transformer_low_noise', setting: 'wanLowNoiseModel' },
{ metadataKey: 'anima_inpaint_adapter', setting: 'animaInpaintAdapterModel' },
{ metadataKey: 'clip_embed_model', setting: 'clipEmbedModel' },
{ metadataKey: 'clip_g_model', setting: 'clipGEmbedModel' },
{ metadataKey: 'clip_l_model', setting: 'clipLEmbedModel' },
{ metadataKey: 'gemma2_encoder', setting: 'gemma2EncoderModel' },
{ metadataKey: 'mistral_encoder', setting: 'mistralEncoderModel' },
{ metadataKey: 'pid_decoder', setting: 'pidDecoderModel' },
Expand Down
Loading