- JoyAI-Echo GGUF nodes β multishot fixes + automation patch
- Also here: Multishot Lite (core nodes only)
- Which download do I need?
- Files in this package
- Bug fixes
- Features
- 3. Split per-domain negative lever (
JoyEcho_TextEncode) - 4. Passthrough mode (
JoyEcho_LLMEnhance) - 5. Reference-image conditioning β I2V-as-reference (
JoyEcho_Generate) - 6. Shot transitions (
JoyEcho_Generate) - 7. fp8 transformer quantization (
JoyEcho_ModelLoader) - 8. Tiled VAE decode (
JoyEcho_Generate) - 9. Model dropdown (
JoyEcho_ModelLoader) - 10. LoRA loading hardening (
JoyEcho_ModelLoader+libs/.../fuse_loras.py) - 11. Automation / batching nodes (new)
- 12. GPU encode hot-swap (
JoyEcho_TextEncode) - 13.
encoder_fp8(JoyEcho_ModelLoader) - 14.
fp8_scaled_mm(JoyEcho_ModelLoader) - native fp8 compute - 15.
resident_blocks(JoyEcho_Generate) - 16. Hires-fix second pass (
JoyEcho_Generate) - 17. Reference scheduling upgrades (
JoyEcho_RefPicker+_Generate) - 18. Robustness
- 19. Finishing: who builds your master (READ THIS before touching hires)
- 3. Split per-domain negative lever (
- Applying
- Credits
- Also here: Multishot Lite (core nodes only)
JoyAI-Echo GGUF nodes β multishot fixes + automation patch
Nodes, workflow, issues and releases: https://github.com/jlucasmcrell/joyai-echo-multishot-patch
All models: https://huggingface.co/joeygambino
Before you dive in - a word on expectations. This is a community-built, bleeding-edge pipeline: a 22B audio+video model with cross-shot memory, running on consumer hardware. It is powerful, but it is not one-click - your first clean render will likely take some tuning to YOUR machine (VRAM, system RAM and pagefile, and which model build: bf16 / fp8 / GGUF / INT8). No two rigs behave identically.
INSTRUCTIONS.mdcovers install, per-VRAM settings, and every failure mode reported so far. **If you get stuck, open a discussion
- I answer, I troubleshoot, and most of the fixes in this pack exist because someone reported something.** You will not be left hanging.
Also here: Multishot Lite (core nodes only)
A second, much simpler workflow lives in this repo for people who want chained talking shots without installing anything:
It now has its own home: https://huggingface.co/joeygambino/ltx23-multishot-lite (standalone docs + issues) Β· GitHub: https://github.com/jlucasmcrell/ltx23-multishot-lite
Mirrored here for convenience:
LTX23_Multishot_Lite_v1.0.zip β full docs in
MULTISHOT_LITE.md Β· workflow at
workflow/LTX23_Multishot_Lite.json
- 100% stock ComfyUI nodes. No custom packs, nothing to keep in sync.
- Two shots that genuinely continue each other β shot 2 opens on shot 1's exact last frame.
- Mode 1 (default): the character speaks your prompt's quoted line in a reference voice you supply (3β5s sample + the LTX-2.3 ID-LoRA). Mode 2: bypass one node per shot and the model invents a voice.
- Both shots joined β video and audio β and refined into one
FINALfile.
It is NOT a replacement for the pack below. Lite has no memory bank: continuity comes only from the handed-over frame, so identity drifts over many shots. A paired audio+video memory bank cannot run on a stock sampler β that is precisely why the node pack on this page exists. Use Lite for a quick two-shot piece with no dependencies; use the pack when one character has to hold across a whole scene.
Which download do I need?
(For the node pack β if you want the dependency-free Lite workflow instead, grab LTX23_Multishot_Lite_v1.0.zip above.)
Just the zip: ComfyUI_JoyAI_Echo_v1.5_COMPLETE.zip. That is the whole thing -
every patch file, the workflow, an example prompt file, and step-by-step
instructions. Nothing else on this page is required.
The loose .py files listed above the zip are the same files, unzipped, there
for people who want to diff or cherry-pick. You do not need both, and you should
not download both.
One thing to know before you start: this is a patch over an existing pack, not
a standalone install. You need RealRebelAI's ComfyUI_JoyAI_Echo_GGUF_Nodes
installed first, then you MERGE these files over it (replace when asked) - do not
delete or replace the whole folder. The pack prints a clear startup error if it
detects a replace-instead-of-merge install.
Full walkthrough: INSTRUCTIONS.md (also inside the zip).
A set of bug fixes and features layered on top of the community
ComfyUI_JoyAI_Echo_GGUF_Nodes pack (the Rebels GGUF loader stack around
JoyAI-Echo). Everything here targets the multi-shot path (JoyEcho_Generate
- the discrete Rebels loaders /
JoyEcho_ModelLoader).
This is a patch drop, not a standalone pack: copy these files over a working
install of the same pack (back up first). The files are interdependent β in
particular nodes.py calls new signatures added to the two libs/ files, so
apply them together.
Tested on an RTX 5090 (32 GB) and a 3090 (24 GB), ComfyUI 0.26β0.27, torch 2.8β2.11, with the JoyAI-Echo bf16 release and self-built Q8 GGUFs.
Files in this package
nodes.py # JoyEcho_TextEncode / _Generate / _ModelLoader / _LLMEnhance
__init__.py # registrations for the new nodes
rebels_loaders.py # discrete GGUF loaders (text-encoder fixes)
joyecho_prompt_source.py (new node) # one dropdown: .txt briefs + .json scripts
joyecho_ref_picker.py (new node) # auto reference-image picker by character name
joyecho_ref_batch.py (new node) # None-tolerant image batcher
joyecho_script_picker.py (new node) # JSON dropdown (superseded by PromptSource)
libs/ltx_distillation/models/ltx_wrapper.py # fp8 quantization passthrough
libs/ltx_core/loader/fuse_loras.py # kohya-LoRA fusion + alpha scaling + fuse telemetry
libs/ltx_core/quantization/policy.py # fp8_scaled_mm_torch policy (+ sm_89 gate)
libs/ltx_core/quantization/fp8_torch_mm.py # native-fp8 Linear forward (torch._scaled_mm)
libs/ltx_distillation/utils.py # tiled VAE decode
libs/ltx_distillation/inference/memory_multishot.py # memory bank TRIM FIX (critical)
libs/ltx_distillation/inference/bidirectional_pipeline.py # dtype hardening vs fp8 params
libs/ltx_distillation/inference/memory_bidirectional_pipeline.py # dtype hardening vs fp8 params
prompts/long_story_writer_system_prompt.md # (optional) de-musicked + character-age edits
The files are interdependent - apply the whole set together, never cherry-pick (a nodes.py newer than its libs/ raises AttributeError at load).
Bug fixes
1. enable_audio_memory=False silently disabled ALL cross-shot memory
The pack computed audio_memory_latent=None when audio memory was off, and the
video memory-bank save was gated on that latent being non-None β so with
audio memory off (the standard anti-drone setting) the bank never filled and
cross-shot identity silently died (symptom: memory_size=0 every shot even
with memory_max_size=7; a new face each shot).
Fix: memory storage is now unconditional; enable_audio_memory gates only the
audio-memory injection path. Verify: console memory_size= should climb
0,1,2,β¦ capped at your memory_max_size. (nodes.py)
1b. Memory bank trim was a NO-OP whenever memory_max_size <= num_fix_frames (CRITICAL)
PairedAudioVideoMemoryBank._trim() computed tail[-keep_tail:] - and when
keep_tail == 0 (e.g. the common max_size=3 / num_fix_frames=3 combo),
tail[-0:] is the WHOLE list, so the bank grew unbounded: every shot
conditioned on EVERY prior shot. Symptom: console memory_size= climbing
0,1,2,...,N-1 past your cap, and severe compounding quality degradation over
long runs (waxy skin, contrast crush, smearing by the late shots - the "gets
worse as it goes" failure). Fixed with a proper zero-tail branch + anchor
clamp; memory_size= now freezes at your cap. This one fix eliminated the entire
long-run degradation in our tests. (libs/.../memory_multishot.py)
2. GGUF text-encoder loader (RebelsJE_TextEncoder)
Two fixes so a text-only Gemma-3 GGUF loads cleanly:
- meta-strip: drop
vision_tower/multi_modal_projector/lm_head(the text-only GGUF has no weights for them β "Cannot copy out of meta tensor"). - device-unify: pin the embeddings-processor to the encoder's actual device (GGUF Gemma runs on CPU while the connector was on cuda β addmm device mismatch).
- fp8 gemma scale-key layouts: the
our_fp8swap only recognized its own export layout (bare module names +.scale_weight); standard HF/comfy-style fp8 gemma files (<module>.weight+.weight_scale, e.g. community abliterated builds) silently loaded with zero modules swapped β the encoder stayed bf16 with no indication. Both layouts are now accepted (per-tensor scalar scales; per-channel scales are skipped and those modules stay bf16), and a loud warning prints if a file matches neither. (rebels_loaders.py)
Features
3. Split per-domain negative lever (JoyEcho_TextEncode)
The DMD pipeline has no CFG, so the only steering lever is embedding-space.
Instead of one negative_prompt/negative_scale that steers both branches,
this splits it:
negative_prompt_video/negative_scale_videoβ kills burned-in captions/subtitles. Working value ~0.5. Above ~0.8 it over-rotates the video context and locks every shot to shot 1's composition (scene-lock).negative_prompt_audio/negative_scale_audioβ kills invented music/score. Keep β€ ~0.4 or dialogue suffers. Steering is norm-preserving (RescaleCFG-style):cond' = renorm(cond + s*(cond β neg)). Old single-widget names still work as a fallback. (nodes.py)
4. Passthrough mode (JoyEcho_LLMEnhance)
mode = "passthrough (raw JSON, skip LLM)" β feed a finished
{"prompts":[...]} script straight through with no LLM call / no API key.
Auto-detects when story_idea already parses as that JSON. (nodes.py)
5. Reference-image conditioning β I2V-as-reference (JoyEcho_Generate)
New reference_image (IMAGE batch, up to 4). Identity references are prepended
as video-only conditioning clips at the memory-encode step β they are
never written into the paired audio/video bank. (An earlier attempt that
seeded refs into the bank with zero-filled audio latents injected loud
background noise with 2+ refs; video-only conditioning avoids it entirely.)
Also new: head_trim_frames (auto 8 with refs) drops the first N frames of each
shot, where the model morphs out of the reference/memory content. The trim is
applied once right after decode, so the final output, the per-shot preview
files, and any external concat of them stay frame-identical. (nodes.py)
6. Shot transitions (JoyEcho_Generate)
transition: cut (original) / dissolve (overlap cross-dissolve + equal-power
audio crossfade) / vhs_glitch (analog static burst at each boundary: snow,
tear bands, dropout lines + a raised-cosine tape-noise audio bed).
transition_frames, glitch_intensity tune it. (nodes.py)
7. fp8 transformer quantization (JoyEcho_ModelLoader)
New fp8_transformer toggle. Quantizes the DiT's attention/FF linear weights to
float8_e4m3fn at load, from the normal bf16 checkpoint (uses the vendored
ltx_core.quantization.QuantizationPolicy.fp8_cast() β upcasts per-layer at
inference). Roughly halves DiT weight memory and halves sequential-offload PCIe
traffic; keeps memory training + all tensors; VAEs/text-encoder/non-linears stay
bf16. Ignored when a GGUF DiT is selected (already quantized).
(nodes.py + libs/ltx_distillation/models/ltx_wrapper.py β new quantization
param; the quantized build path skips the post-load dtype cast that would
otherwise silently upcast fp8 back to bf16.)
8. Tiled VAE decode (JoyEcho_Generate)
Decoding a long high-res shot (e.g. 241f @ 1280Γ736) in one pass hard-aborts the
VAE decode on a 24β32 GB card (fatal cuDNN abort mid-conv, not a catchable OOM).
New decode_tiling (auto/on/off) routes decode through the vendored
VideoDecoder.tiled_decode β temporal-only 64-frame chunks with 24-frame
blended overlap (no spatial tiles β no spatial seams), streaming each chunk to
CPU. auto engages only above a size threshold, so small renders keep the
original single-pass decode bit-for-bit.
(nodes.py + libs/ltx_distillation/utils.py β decode_benchmark_sample gains
a video_tiling_config kwarg + _decode_video_tiled_uint8.)
9. Model dropdown (JoyEcho_ModelLoader)
New model_file combo lists every .safetensors / .gguf under the ComfyUI
checkpoints / diffusion_models / unet dirs. Pick a .safetensors β full
checkpoint (replaces checkpoint_path); pick a .gguf β DiT loaded from GGUF
while checkpoint_path still supplies the VAEs / vocoder / text connectors.
"(use checkpoint_path)" keeps the old typed-path behavior. A matching
lora_file dropdown lists every .safetensors under models/loras
(applied at lora_strength on the safetensors DiT path; ignored for GGUF). Plus a clear
early error if gemma_path is a .gguf/file/sidecar-less dir (this loader
needs the HF gemma-3-12b-it folder; GGUF Gemma only works via
RebelsJE_TextEncoder). (nodes.py)
10. LoRA loading hardening (JoyEcho_ModelLoader + libs/.../fuse_loras.py)
- A
lora_filedropdown picks LoRAs frommodels/loras(existinglora_strengthwidget applies). - Fusion now supports kohya naming (
lora_down/lora_up) in addition to PEFT (lora_A/lora_B), with standardalpha/rankscaling β previously a kohya-named LoRA silently did NOTHING (zero keys matched, no warning). - Fusion prints how many weights fused, and WARNS LOUDLY when a provided LoRA matched zero keys.
- The loader refuses ComfyUI-quantized checkpoints (
.comfy_quantmarker tensors, e.g. "fp8mixed learned" builds) with a clear error: this loader never applies their weight scales (the model would silently load mis-scaled) and LoRA fusion on them crashes with shape errors. Use bf16 checkpoints.
11. Automation / batching nodes (new)
JoyEcho_PromptSourceβ one dropdown listing LPFF-style.txtbriefs (from the inspire-pack prompts tree) and passthrough.jsonscripts (input/joyecho_prompts/). Multi-block briefs fan out like LoadPromptsFromFile. Emitsstory_idea(β LLMEnhance) +character(β RefPicker) +count. Replaces the LPFFβUnzipPrompt chain and lets you switch prompt sources with one dropdown instead of rewiring.JoyEcho_RefPickerβ auto-selects a character reference image from a folder tree keyed by character name (acharacter_pickdropdown of the folder names, a typed/wiredcharacterstring, or a prompt scan β dialogue mentions are stripped so only the on-screen subject wins). The dropdown survives model refreshes, an explicitly named character that matches no folder refuses to fall back to the prompt scan (a wiped/typo'd name can't silently become the wrong character's face), and the cache signature includes the prompt text (without it, ComfyUI could serve a cached pick from a previous queue item).on_no_match=no_referencereturns nothing so a batch keeps running.JoyEcho_RefBatchβ None-tolerant image batcher: combines up to 4 optional IMAGE inputs (e.g. two RefPickers for a two-character shot), skips missing refs, resizes mismatched sizes to the first image, outputsNoneif all are missing (Generate then just skips identity seeding). The stock KJNodesImageBatchMulticrashes with'NoneType' has no attribute 'shape'on a missing ref; this replaces it.JoyEcho_ScriptPickerβ JSON dropdown (superseded by PromptSource; kept for compatibility).
12. GPU encode hot-swap (JoyEcho_TextEncode)
With low_vram the Gemma encoder used to encode every shot on CPU (~10s+ per
shot). The encode pass now borrows the (idle) GPU when the encoder fits free
VRAM - with a fits-check, an OOM fallback to CPU, and a move-back before the
denoise phase. 20-shot encodes drop from minutes to seconds. (nodes.py)
13. encoder_fp8 (JoyEcho_ModelLoader)
Stores the Gemma encoder's linear weights as float8_e4m3fn with per-layer upcast at encode (encode runs once per item, so the upcast tax that makes fp8 slow on the DiT is irrelevant here). Wrapper drops ~24GB -> ~21GB and the GPU hot-swap engages on 32GB cards; JD's connector projections stay bf16.
14. fp8_scaled_mm (JoyEcho_ModelLoader) - native fp8 compute
Stores the DiT's attention/FF linears as fp8 AND runs the matmuls natively
via torch._scaled_mm - no per-layer upcast tax (measured x2.8 raw kernel /
x1.5 end-to-end vs bf16 on an RTX 5090). ~22GB resident enables
sequential_offload=False at moderate resolutions. REQUIREMENTS: sm_89+
GPU (RTX 40/50 - clear error on older cards, with a per-device runtime
fallback to upcast), and a bf16 source checkpoint (an fp8 FILE would load
every tensor fp8 with the cast skipped and crash the noise path - guarded
with a clear error). Tensorwise dynamic activation quant: A/B your content
before adopting.
15. resident_blocks (JoyEcho_Generate)
Sequential offload middle ground: pin the first N of 48 transformer blocks permanently on GPU, stream the rest. N=24 halves the per-step PCIe traffic; raise until VRAM is nearly full. Composes with fp8 modes (fp8 blocks are half the bytes both resident and streamed).
16. Hires-fix second pass (JoyEcho_Generate)
hires_factor (>1.0) + hires_denoise: after all shots render, each shot is
bicubic-upscaled, VAE re-encoded, re-noised at a tail sigma and re-denoised
through the DMD ladder at the TARGET resolution - the model synthesizes real
detail (RTX-class upscalers only sharpen what exists). Runs in 65-frame
windows with cross-fade (a 24GB card survives 1920x1088 refines); memory
bank and per-shot previews stay base-res; failures fall back to the base
frames. Audio is untouched.
17. Reference scheduling upgrades (JoyEcho_RefPicker + _Generate)
- Script-carried ref pinning:
{"prompts": [...], "refs": {"zara": "zara_file.png"}}pins a scene-matched reference per character (a full-scene ref SETS the render's scene - match it to the script). - Re-entry injection: a character returning after a 3+-shot absence gets their ref re-injected at the return shot automatically (the rolling memory window is 4; long absences otherwise re-invent the character).
- Generate's ref dedup is schedule-aware (the same image scheduled at two shots survives; cap 6 scheduled entries).
18. Robustness
- Pipelines no longer derive their working dtype from
next(parameters()).dtype(an fp8 first-param crashedtorch.randn); fp8 dtypes are skipped with a bfloat16 fallback. - fp8 gemma swap accepts both
.scale_weightand.weight_scalelayouts and warns loudly on zero matches instead of silently staying bf16.
19. Finishing: who builds your master (READ THIS before touching hires)
hires_factor is a ROUTING switch, not a quality slider - it decides which
pipeline builds your final video:
| you want | hires_factor | hires_denoise | master comes from |
|---|---|---|---|
| default: zero detail-shimmer | 1.0 | (ignored) | base shots, upscaled by AutoFinish (bicubic + contrast-adaptive sharpen - deterministic, seconds per shot) |
| synthesized detail (pores/hair), accepts slight per-frame texture shimmer | 1.5 | subtle / medium / strong | your refined shots, used as-is (AutoFinish skips its own upscale) |
| deterministic upscale baked into the shot files | 1.5 | spatial | spatial-upscaled shots - EVEN latent grids only (height AND width /32 must be even: 768-height yes, 736 no) |
| the old RTX path | 1.0 | (ignored) | base shots via RTXBatchVideoUpscale (upscale_mode: rtx (legacy) on the AutoFinish node) |
- Judge and publish only the
*_MASTER.mp4. The in-graph SaveVideo output (prefixedPREVIEWin the shipped workflow) is a convenience preview: its re-encode is bit-starved and shows artifacts the master does not have. This is a ComfyUI limitation, not a render problem. - Resolutions: everything works at any /32 size; only the
spatialhires mode additionally needs the /32 result EVEN on both axes (1280x768 and 1344x768 qualify; 1280x736 and 1536x864 do not - spatial will smear one edge and warn in the console). - All other widgets are genuinely free: seed, num_frames (long shots are fine - the old ~10s lip-sync limit was the fps bug, now fixed), fps, memory sizes, head_trim.
- The master pipeline re-encodes with
bf 0+tune grainend to end, so masters never reintroduce B-frame pumping.
Applying
- Back up your existing pack folder.
- Copy each file over the same relative path in
ComfyUI/custom_nodes/ComfyUI_JoyAI_Echo_GGUF_Nodes/. - Restart ComfyUI. New widgets append at the end of existing nodes, so
saved graphs keep their values; the four new nodes appear under the
JoyAI-Echocategory. PressRafter adding model files to refresh themodel_filedropdown.
The libs/ files must match the vendored ltx_core / ltx_distillation in
your pack (same JoyAI-Echo release). If your libs/ differ substantially,
cherry-pick the changes described above rather than overwriting.
Not included (intentionally): model weights, the gemma_assets/ tokenizer
binaries, .bak snapshots, and __pycache__.
Credits
This patch stands on other people's work:
- JD Joy Future Academy - JoyAI-Echo, the multishot memory model this whole stack serves (research / non-commercial license).
- Lightricks - LTX-2 / LTX-2.3 (LTX-2 Community License).
- TenStrip - the LTX2.3 DMD LoRAs; the hires
strong (tenstrip 4-step)mode uses his published upscale sigma ladder verbatim. - RealRebelAI - the Rebels GGUF loader stack this patches, and the Q6_K_RM GGUF whose tensor canon the self-built GGUFs mirror.
- Comfy-Org - the comfy-quants
int8_tensorwise+ ConvRot export recipe behind the INT8 checkpoint. - Google - Gemma 3 12B, the text encoder (Gemma license).