Wire Qwen Turbo toggle through Studio 2 and the runner.
Force 6/1 when Turbo is on, skip negatives, keep PE in front, and fail closed if the r128 LoRA is missing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,11 +10,14 @@ import { join } from 'node:path';
|
||||
import template from '../../assets/studio2_minimax_native.json';
|
||||
import qwen21Template from '../../assets/studio2_qwen21_t2i.json'
|
||||
import qwen21EditTemplate from '../../assets/studio2_qwen21_edit.json'
|
||||
import qwen21T2iTurboTemplate from '../../assets/studio2_qwen21_t2i_turbo.json'
|
||||
import qwen21EditTurboTemplate from '../../assets/studio2_qwen21_edit_turbo.json'
|
||||
import qwen21PeT2iTemplate from '../../assets/studio2_qwen21_pe_t2i.json'
|
||||
import qwen21PeEditTemplate from '../../assets/studio2_qwen21_pe_edit.json';
|
||||
import { nativeVideoGraph, attachHeroReference, applyResolvedImageSize } from '~/shared/studio2/graphs.mjs';
|
||||
import { compilePrompt, scopedFile } from '~/shared/studio2/contracts.mjs';
|
||||
import { resolveQwen21Size } from '~/shared/studio2/qwen21-size.mjs';
|
||||
import { QWEN21_TURBO_LORA, QWEN21_TURBO_SIGMAS, QWEN21_TURBO_STEPS } from '~/shared/studio2/qwen21-turbo.mjs';
|
||||
import { createJob, restoreJob, getJob, emitJob, type Job } from '../jobs';
|
||||
import { markStudioLive, onLiveVideoSettled, type StudioJob } from '../studioQueue';
|
||||
import { readRecord, saveRecord, studio2Root, records } from './store';
|
||||
@@ -235,12 +238,20 @@ async function prepareGraph(r: any) {
|
||||
else if (q.engine === 'qwen21') {
|
||||
if (!['generate', 'edit'].includes(q.mode))
|
||||
throw new Error('Qwen 2.1 supports Generate and Edit.')
|
||||
const turbo = !!q.turbo
|
||||
if (turbo) {
|
||||
q.lora = QWEN21_TURBO_LORA
|
||||
q.sigmas = QWEN21_TURBO_SIGMAS
|
||||
s.steps = QWEN21_TURBO_STEPS
|
||||
s.cfg = 1
|
||||
}
|
||||
const negative = turbo ? '' : stylePrompt(q.imageStyles, true)
|
||||
if (q.mode === 'edit') {
|
||||
if (!a) throw new Error('Choose the source still.')
|
||||
let prompt = String(q.compiledPrompt || '')
|
||||
if (!/<image1>/i.test(prompt))
|
||||
prompt = `Keep the subject in <image1>. ${prompt}`.trim()
|
||||
graph = structuredClone(qwen21EditTemplate)
|
||||
graph = structuredClone(turbo ? qwen21EditTurboTemplate : qwen21EditTemplate)
|
||||
graph['10'].inputs.image = a
|
||||
if (b) {
|
||||
graph['11'] = {
|
||||
@@ -251,42 +262,60 @@ async function prepareGraph(r: any) {
|
||||
graph['9'].inputs['images.image_2'] = ['11', 0]
|
||||
}
|
||||
graph['9'].inputs.prompt = prompt
|
||||
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true)
|
||||
graph['9'].inputs.negative_prompt = negative
|
||||
// Official edit: sampler latent comes from TextEncode (sized from image_1).
|
||||
// Do not inject EmptyLatentImage — a mismatched empty canvas shifts the edit.
|
||||
graph['9'].inputs.resolution = 1024
|
||||
graph['15'].inputs.seed = s.seed
|
||||
graph['15'].inputs.steps = s.steps || 25
|
||||
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||
graph['15'].inputs.sampler_name = 'euler'
|
||||
graph['15'].inputs.scheduler = 'simple'
|
||||
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 edit latent'
|
||||
if (turbo) {
|
||||
graph['7'].inputs.lora_name = QWEN21_TURBO_LORA
|
||||
graph['7'].inputs.strength = 1.0
|
||||
graph['31'].inputs.noise_seed = s.seed
|
||||
graph['33'].inputs.nodes = QWEN21_TURBO_SIGMAS
|
||||
graph['36'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 edit latent'
|
||||
r.graphId = 'studio2_qwen21_edit_turbo.json'
|
||||
} else {
|
||||
graph['15'].inputs.seed = s.seed
|
||||
graph['15'].inputs.steps = s.steps || 25
|
||||
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||
graph['15'].inputs.sampler_name = 'euler'
|
||||
graph['15'].inputs.scheduler = 'simple'
|
||||
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 edit latent'
|
||||
r.graphId = 'studio2_qwen21_edit.json'
|
||||
}
|
||||
r.sampleDenoise = null
|
||||
r.heroReferenceAttached = false
|
||||
r.graphId = 'studio2_qwen21_edit.json'
|
||||
} else {
|
||||
// Bench aspect is law for Generate. PE wh_ratio is advisory only (never sizes the canvas).
|
||||
const size = resolveQwen21Size(s.aspect)
|
||||
s.width = size.width
|
||||
s.height = size.height
|
||||
graph = structuredClone(qwen21Template)
|
||||
graph = structuredClone(turbo ? qwen21T2iTurboTemplate : qwen21Template)
|
||||
graph['9'].inputs.prompt = q.compiledPrompt
|
||||
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true)
|
||||
// TextEncode conditioning only — EmptyLatentImage (node 16) owns canvas size, matching the official T2I template.
|
||||
graph['9'].inputs.negative_prompt = negative
|
||||
graph['9'].inputs.resolution = 1024
|
||||
graph['16'].inputs.width = size.width
|
||||
graph['16'].inputs.height = size.height
|
||||
graph['15'].inputs.seed = s.seed
|
||||
graph['15'].inputs.steps = s.steps || 25
|
||||
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||
graph['15'].inputs.sampler_name = 'euler'
|
||||
graph['15'].inputs.scheduler = 'simple'
|
||||
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 empty latent'
|
||||
if (turbo) {
|
||||
graph['7'].inputs.lora_name = QWEN21_TURBO_LORA
|
||||
graph['7'].inputs.strength = 1.0
|
||||
graph['31'].inputs.noise_seed = s.seed
|
||||
graph['33'].inputs.nodes = QWEN21_TURBO_SIGMAS
|
||||
graph['36'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 empty latent'
|
||||
r.graphId = 'studio2_qwen21_t2i_turbo.json'
|
||||
} else {
|
||||
graph['15'].inputs.seed = s.seed
|
||||
graph['15'].inputs.steps = s.steps || 25
|
||||
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||
graph['15'].inputs.sampler_name = 'euler'
|
||||
graph['15'].inputs.scheduler = 'simple'
|
||||
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 empty latent'
|
||||
r.graphId = 'studio2_qwen21_t2i.json'
|
||||
}
|
||||
r.sampleDenoise = null
|
||||
r.heroReferenceAttached = false
|
||||
r.graphId = 'studio2_qwen21_t2i.json'
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -344,7 +373,15 @@ async function run(r: any, job: Job) {
|
||||
throw new Error('Cancelled');
|
||||
update(r, 'submitting');
|
||||
emitJob(job, { type: 'status', message: 'Generating…' });
|
||||
const queued = await queuePrompt(graph, job.clientId);
|
||||
let queued: { prompt_id: string; number?: number }
|
||||
try {
|
||||
queued = await queuePrompt(graph, job.clientId);
|
||||
} catch (error: any) {
|
||||
const msg = String(error?.statusMessage || error?.message || error)
|
||||
if (r.request?.turbo && /lora|ViggleTurbo|not in list|value_not_in_list/i.test(msg))
|
||||
throw new Error(`Qwen Turbo LoRA missing on host (${QWEN21_TURBO_LORA}). Install the r128 file under models/loras and restart Comfy.`)
|
||||
throw error
|
||||
}
|
||||
job.promptId = queued.prompt_id;
|
||||
r.promptId = queued.prompt_id;
|
||||
r.renderStartedAt = Date.now();
|
||||
|
||||
Reference in New Issue
Block a user