- JoyAI-Echo GGUF nodes β multishot fixes + automation patch
- 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)
- 3. Split per-domain negative lever (
- Applying
- Files in this package
JoyAI-Echo GGUF nodes β multishot fixes + automation patch
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_distillation/utils.py # tiled VAE decode
prompts/long_story_writer_system_prompt.md # (optional) de-musicked + character-age edits
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)
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).
(
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).
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__.