fix(ltx,processing): address pr #4819 review

review feedback from vladmandic on commit 80fde086f.

- run_ltx: drop the inline random.seed() / randrange resolve and use
  processing.fix_seed(p) right after the StableDiffusionProcessingVideo
  construction. p.seed carries the resolved int across every stage
  (latent_pass, both upsample paths, refine, post-refine vae_decode).
  matches the existing fix_seed(p) call sites in img2img.py:30,
  video_run.py:101, xyz_grid.py:266 and 8 others.

- process_decode: add AudioFrameList(list) subclass and attach_audio
  helper to carry output.audio onto the returned frame list. mirrors
  the existing output.bytes early-return contract: samples.audio
  survives downstream so process_images_inner can collect it via the
  Processed.audio kwarg.

- drop the p.audio_capture transit in process_diffusers and the
  fallback read in process_images_inner. p is input params, not
  output state.
This commit is contained in:
CalamitousFelicitousness
2026-05-04 02:47:33 +01:00
parent 80fde086f9
commit 4523e7c3ac
3 changed files with 26 additions and 22 deletions
+7 -15
View File
@@ -170,15 +170,6 @@ def run_ltx(task_id,
yield from abort(f'Video: cls={shared.sd_model.__class__.__name__} selected model is not LTX', ok=True)
return
# get_generator(-1) reseeds globally per call, so every stage would otherwise
# roll an uncorrelated seed. Resolve once and thread the int through.
import random
if seed is None or int(seed) < 0:
random.seed()
resolved_seed = int(random.randrange(4294967294))
else:
resolved_seed = int(seed)
# Lightricks TI2VidTwoStagesPipeline: Stage 1 at half-res, 2x upsample, Stage 2 refine at target.
# Auto-couple when the user picks Refine but not Upsample. Both Dev and Distilled refine paths
# expect upsampled latents; same-res refine on Distilled produces oversaturation. Condition
@@ -262,7 +253,7 @@ def run_ltx(task_id,
prompt=prompt,
negative_prompt=negative,
styles=styles,
seed=resolved_seed,
seed=int(seed) if seed is not None else -1,
sampler_name=sampler_name,
sampler_shift=float(sampler_shift),
steps=int(steps),
@@ -275,6 +266,7 @@ def run_ltx(task_id,
vae_type='Default',
vae_tile_frames=16,
)
processing.fix_seed(p)
p.scripts = None
p.script_args = None
p.do_not_save_grid = True
@@ -362,7 +354,7 @@ def run_ltx(task_id,
mp4_fps=mp4_fps,
conditions=conditions,
image_cond_noise_scale=image_cond_noise_scale if caps.supports_image_cond_noise_scale else None,
seed=resolved_seed,
seed=p.seed,
image=p.task_args.get('image'),
)
else:
@@ -414,7 +406,7 @@ def run_ltx(task_id,
up_args = {
'width': final_w,
'height': final_h,
'generator': get_generator(resolved_seed),
'generator': get_generator(p.seed),
'output_type': 'latent',
}
if latents.ndim == 4:
@@ -435,7 +427,7 @@ def run_ltx(task_id,
'height': final_h,
'num_frames': get_frames(frames),
'latents_normalized': False,
'generator': get_generator(resolved_seed),
'generator': get_generator(p.seed),
'output_type': 'latent',
}
if latents.ndim == 4:
@@ -470,7 +462,7 @@ def run_ltx(task_id,
'height': final_h,
'num_frames': get_frames(frames),
'num_inference_steps': steps,
'generator': get_generator(resolved_seed),
'generator': get_generator(p.seed),
'callback_on_step_end': diffusers_callback,
'output_type': 'pil',
}
@@ -573,7 +565,7 @@ def run_ltx(task_id,
try:
if torch.is_tensor(latents):
# 0.9.x returns raw latents with output_type='latent'; 2.x pre-denormalizes.
frames_out = vae_decode(latents, decode_timestep if caps.supports_decode_timestep else 0.0, resolved_seed, denormalize=caps.family == '0.9')
frames_out = vae_decode(latents, decode_timestep if caps.supports_decode_timestep else 0.0, p.seed, denormalize=caps.family == '0.9')
else:
frames_out = latents
except AssertionError as e:
+1 -1
View File
@@ -528,7 +528,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed:
output_images.append(batch_image)
infotexts.append(batch_infotext)
audio = getattr(samples, 'audio', None) or getattr(p, 'audio_capture', None)
audio = getattr(samples, 'audio', None)
if shared.cmd_opts.lowvram:
devices.torch_gc(force=True, reason='lowvram')
+18 -6
View File
@@ -452,17 +452,32 @@ def process_refine(p: processing.StableDiffusionProcessing, output):
return output
class AudioFrameList(list):
"""list subclass with an audio attribute. Plain lists can't hold attributes,
so process_decode uses this when the pipeline output includes audio."""
audio = None
def attach_audio(results, audio):
if audio is None:
return results
wrapped = AudioFrameList(results if isinstance(results, list) else list(results))
wrapped.audio = audio
return wrapped
def process_decode(p: processing.StableDiffusionProcessing, output):
shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model, exclude=['vae'])
if output is not None:
if hasattr(output, 'bytes') and output.bytes is not None:
log.debug(f'Generated: bytes={len(output.bytes)}')
return output
audio = getattr(output, 'audio', None)
if not hasattr(output, 'images') and hasattr(output, 'frames'):
log.debug(f'Generated: frames={len(output.frames[0])}')
output.images = output.frames[0]
if output.images is not None and len(output.images) > 0 and isinstance(output.images[0], Image.Image):
return output.images
return attach_audio(output.images, audio)
model = shared.sd_model if not is_refiner_enabled(p) else shared.sd_refiner
if not hasattr(model, 'vae'):
if hasattr(model, 'pipe') and hasattr(model.pipe, 'vae'):
@@ -506,8 +521,9 @@ def process_decode(p: processing.StableDiffusionProcessing, output):
results = []
else:
log.warning('Processing: no results')
audio = None
results = []
return results
return attach_audio(results, audio)
def update_pipeline(sd_model, p: processing.StableDiffusionProcessing):
@@ -628,10 +644,6 @@ def process_diffusers(p: processing.StableDiffusionProcessing):
timer.process.add('lora', lora_common.timer.total)
lora_common.timer.clear(complete=True)
# process_decode flattens video output to a frame list and drops the audio attribute;
# stash it on `p` so video pipelines can recover it after process_images returns.
if output is not None and getattr(output, 'audio', None) is not None:
p.audio_capture = output.audio
results = process_decode(p, output)
timer.process.record('decode')