Fix Chain Sampler widget desync + batch no-key infinite recursion
1) Widget order: moved the refinement_key_input forceInput SOCKET to the end of optional (after decode_tile_size + embed_guidance_source). A forceInput placed between widgets desynced ComfyUI's widgets_values mapping on reload -> the combo's "relative" landed on the decode_tile_size INT (NaN field). 2) RecursionError: _run_batch_training's no-key fallback called self.sample() with the variant markers still in positive -> sample() re-detected the batch -> re-entered _run_batch_training -> infinite loop (hit when Interactive Guessing/batch is armed without refinement_key_input wired). Now runs keyless as generate-only: loops every variant, concatenates to the IMAGES output, skips temp-save/manifest/rating. (Per-variant sub-calls are single-variant -> _split_batch_variants returns None -> no recursion.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e4a74dc3b9
commit
34bb018bd5
+35
-29
@@ -1984,22 +1984,23 @@ class FunPackLTXAVSceneChainSampler:
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
# Widget inputs first, then connection/forceInput sockets LAST. A forceInput
|
||||
# input placed between widgets desyncs ComfyUI's widgets_values mapping on reload
|
||||
# (the combo value lands on decode_tile_size -> NaN/"relative" in the INT field).
|
||||
"decode_tile_size": ("INT", {
|
||||
"default": 0, "min": 0, "max": 4096, "step": 64,
|
||||
"tooltip": "Tile size for VAE decode (0 = no tiling). Set to e.g. 512 if decode OOMs.",
|
||||
}),
|
||||
"embed_guidance_source": (["relative", "absolute"], {
|
||||
"default": "relative",
|
||||
"tooltip": "Which learned direction embed_guidance steers toward. Relative: this prompt's liked direction (needs refinement_key_input). Absolute: the global, prompt-agnostic taste direction the Refiner accumulates across all prompts — works with no key.",
|
||||
}),
|
||||
"refinement_key_input": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": False,
|
||||
"forceInput": True,
|
||||
"tooltip": "Connect to the same refinement key as your V2 Refiner. When wired, the sampler writes carry_i2v_guides, frame_overlap, and scene count into the refinement state so the Refiner can reason about what changed between rated runs.",
|
||||
}),
|
||||
# Appended last on purpose: inserting a widget earlier shifts every saved
|
||||
# widgets_values slot in existing workflows. New widgets go at the end.
|
||||
"embed_guidance_source": (["relative", "absolute"], {
|
||||
"default": "relative",
|
||||
"tooltip": "Which learned direction embed_guidance steers toward. Relative: this prompt's liked direction (needs refinement_key_input). Absolute: the global, prompt-agnostic taste direction the Refiner accumulates across all prompts — works with no key.",
|
||||
}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
@@ -2882,22 +2883,21 @@ class FunPackLTXAVSceneChainSampler:
|
||||
Reuses sample() per entry with only the seed changed, so each entry is a clean generation."""
|
||||
import json as _json, time as _time
|
||||
key = str(refinement_key_input or "").strip()
|
||||
if not key:
|
||||
print("[FunPackSceneChain] Batch Training requires refinement_key_input; running once instead.")
|
||||
return self.sample(
|
||||
model, vae, positive, negative, sampler, sigmas, seed, latent_template,
|
||||
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed=use_same_seed,
|
||||
carry_i2v_guides=carry_i2v_guides, mid_scene_guide=mid_scene_guide,
|
||||
mid_scene_guide_strength=mid_scene_guide_strength, embed_guidance=embed_guidance,
|
||||
embed_guidance_strength=embed_guidance_strength, embed_guidance_source=embed_guidance_source,
|
||||
transition_duration=transition_duration,
|
||||
decode_tile_size=decode_tile_size, refinement_key_input="",
|
||||
)
|
||||
stamp = _time.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
batch_dir, safe, rel = self._batch_dir(key, stamp)
|
||||
scene_prompts = [self._scene_text(c, i) for i, c in enumerate(positive[:max(1, int(max_scenes))])]
|
||||
manifest = {"key": key, "created": stamp,
|
||||
"subfolder": rel.replace(os.sep, "/"), "scene_prompts": scene_prompts, "items": []}
|
||||
# persist=True -> save each variant to temp + manifest for rating in Studio (needs a key).
|
||||
# No key -> generate-only: run every variant and concat to IMAGES, but don't save/rate.
|
||||
# (NEVER recurse into self.sample() with the marked positive — that loops forever.)
|
||||
persist = bool(key)
|
||||
stamp = safe = rel = batch_dir = None
|
||||
if persist:
|
||||
stamp = _time.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
batch_dir, safe, rel = self._batch_dir(key, stamp)
|
||||
scene_prompts = [self._scene_text(c, i) for i, c in enumerate(positive[:max(1, int(max_scenes))])]
|
||||
manifest = {"key": key, "created": stamp,
|
||||
"subfolder": rel.replace(os.sep, "/"), "scene_prompts": scene_prompts, "items": []}
|
||||
else:
|
||||
print("[FunPackSceneChain] Batch (no refinement_key): generating all variants to IMAGES, "
|
||||
"not saved for rating.")
|
||||
manifest = {"items": []}
|
||||
# Studio packed N entries into positive (each scene entry tagged 'funpack_batch_variant').
|
||||
# Sample one chain per entry. Entries may be genuinely different (shortcut variants) or
|
||||
# identical (seed-only batch — Studio packed N copies); either way each saves its own cond.
|
||||
@@ -2925,6 +2925,8 @@ class FunPackLTXAVSceneChainSampler:
|
||||
# videos back-to-back, not just the last one. The sub-call already decoded them.
|
||||
if isinstance(out[1], torch.Tensor) and out[1].dim() == 4 and out[1].shape[1] > 8:
|
||||
batch_images.append(out[1].detach().cpu())
|
||||
if not persist:
|
||||
continue # generate-only: no temp save / manifest
|
||||
out_latent = out[0]
|
||||
iid = f"{safe}_{stamp}_{idx:02d}"
|
||||
latent_path = os.path.join(batch_dir, f"{iid}.latent.pt")
|
||||
@@ -2961,13 +2963,17 @@ class FunPackLTXAVSceneChainSampler:
|
||||
and len(pos_i[0]) > 1 and isinstance(pos_i[0][1], dict) else None),
|
||||
"rating": None,
|
||||
})
|
||||
try:
|
||||
with open(os.path.join(batch_dir, "batch.json"), "w") as f:
|
||||
_json.dump(manifest, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"[FunPackSceneChain] batch manifest save failed: {e}")
|
||||
status = (f"Batch Training complete: {manifest['iterations']} generation(s) in "
|
||||
f"temp/{rel.replace(os.sep, '/')} (cleared on restart) — rate them in Studio.")
|
||||
if persist:
|
||||
manifest["iterations"] = len(manifest["items"])
|
||||
try:
|
||||
with open(os.path.join(batch_dir, "batch.json"), "w") as f:
|
||||
_json.dump(manifest, f, indent=2)
|
||||
except Exception as e:
|
||||
print(f"[FunPackSceneChain] batch manifest save failed: {e}")
|
||||
status = (f"Batch Training complete: {len(manifest['items'])} generation(s) in "
|
||||
f"temp/{rel.replace(os.sep, '/')} (cleared on restart) — rate them in Studio.")
|
||||
else:
|
||||
status = f"Batch (generate-only): {len(entries)} variant(s) → IMAGES; no key, not saved for rating."
|
||||
print(f"[FunPackSceneChain] {status}")
|
||||
if last is None:
|
||||
raise RuntimeError("Batch Training produced no output.")
|
||||
|
||||
Reference in New Issue
Block a user