Validate video latent refinement inputs

This commit is contained in:
digital-garbage
2026-04-23 18:58:37 +03:00
parent 1f26b45f1b
commit 99bd659db5
3 changed files with 50 additions and 3 deletions
+36 -1
View File
@@ -176,6 +176,20 @@ def latent_samples(latent):
return samples if isinstance(samples, torch.Tensor) else None
def latent_sample_type_name(latent):
if not isinstance(latent, dict):
return "missing"
samples = latent.get("samples")
if samples is None:
return "missing samples"
return f"{type(samples).__module__}.{type(samples).__name__}"
def latent_is_plain_video_tensor(latent):
samples = latent_samples(latent)
return samples is not None and latent.get("type") != "audio"
def cpu_tensor_bundle(latent):
if not isinstance(latent, dict):
return {}
@@ -202,6 +216,12 @@ def latent_from_tensor_bundle(bundle):
class FunPackVideoRefiner:
LATENT_OUTPUT_INDEX = 6
NO_LATENT_REFERENCE_ERROR = "No available latent to operate. Please connect reference latent to input of Video Refiner."
WRONG_LATENT_ERROR = (
"Video Refiner latent input must be a plain video LATENT with tensor samples. "
"Audio latents and LTX audio/video combined NestedTensor latents are not supported here. "
"Connect only the video latent to Video Refiner, then feed the refined video latent into "
"LTXVConcatAVLatent as video_latent."
)
SAVED_LATENT_ONLY_STATUS = (
"Running refinement on saved latent. Changing reference latent shape and size will cause no effect to generation."
)
@@ -1393,6 +1413,11 @@ class FunPackVideoRefiner:
def _latent_refinement_disabled(self, latent):
return clone_latent(latent), "Latent: disabled because latent refinement input/output path is not fully connected."
def _raise_wrong_latent(self, latent):
sample_type = latent_sample_type_name(latent)
latent_type = latent.get("type", "unspecified") if isinstance(latent, dict) else "missing"
raise ValueError(f"{self.WRONG_LATENT_ERROR} Received samples={sample_type}, type={latent_type}.")
def _save_latent_reference(self, latent, refinement_key, mode):
samples = latent_samples(latent)
if samples is None:
@@ -1420,6 +1445,9 @@ class FunPackVideoRefiner:
return False
def _refine_latent(self, latent, refinement_key, mode, rating, reward, global_adaptive):
if isinstance(latent, dict) and not latent_is_plain_video_tensor(latent):
self._raise_wrong_latent(latent)
current = latent_samples(latent)
saved_bundle, load_status = self._load_saved_latent(refinement_key, mode)
@@ -1485,6 +1513,10 @@ class FunPackVideoRefiner:
refined_samples = current + latent_delta
refined_samples = torch.where(nonzero_mask, refined_samples, current)
refined_samples = torch.nan_to_num(refined_samples, nan=0.0)
range_min = torch.minimum(current, saved_samples)
range_max = torch.maximum(current, saved_samples)
refined_samples = torch.maximum(torch.minimum(refined_samples, range_max), range_min)
refined_latent["samples"] = refined_samples
latent_state["momentum"] = tensor_to_serializable(momentum.detach().cpu())
@@ -1493,7 +1525,7 @@ class FunPackVideoRefiner:
latent_state["nonzero_ratio"] = round(float(nonzero_mask.float().mean().item()), 6)
latent_state["status"] = (
f"Latent: adjusted shape {tuple(current.shape)} | nonzero {latent_state['nonzero_ratio']:.1%} | "
f"max step {max_step:.5f}"
f"max step {max_step:.5f} | bounded to input/reference range"
)
return refined_latent, latent_state["status"]
@@ -2429,6 +2461,9 @@ class FunPackSaveRefinementLatent:
return float("nan")
def save_latent(self, latent, refinement_key, mode):
if isinstance(latent, dict) and not latent_is_plain_video_tensor(latent):
FunPackVideoRefiner()._raise_wrong_latent(latent)
samples = latent_samples(latent)
if samples is None:
return (clone_latent(latent), "No latent samples tensor found.")
+11 -1
View File
@@ -58,6 +58,16 @@ Use this node when you want to iteratively tune prompt conditioning based on you
Use `FunPack Save Refinement Latent` to store a reference latent tensor bundle under a `refinement_key` and tokenizer `mode`. When `FunPack Video Refiner` later receives a latent input with the same key and mode, it loads that saved latent and applies a small rating-driven adjustment to the latent output.
For LTX audio/video workflows, the latent path must use video latents only:
- Split sampler output with `LTXVSeparateAVLatent`.
- Send the video latent output to `FunPack Save Refinement Latent`.
- Send the plain video latent to `FunPack Video Refiner` latent input.
- Send `FunPack Video Refiner` `refined_latent` to `LTXVConcatAVLatent` `video_latent`.
- Send the original audio latent to `LTXVConcatAVLatent` `audio_latent`.
Do not connect the combined AV latent from `LTXVConcatAVLatent`, the sampler AV output, or the audio latent to this node. Those use LTX nested/audio latent structures and are intentionally rejected with a clear error.
Latent refinement follows the connection state exactly:
- If the latent input is disconnected and `refined_latent` is disconnected, latent tweaking is disabled.
@@ -66,7 +76,7 @@ Latent refinement follows the connection state exactly:
- If the latent input is connected and `refined_latent` is disconnected, latent tweaking is disabled.
- If both latent input and `refined_latent` are connected but no saved latent exists, the node passes the input latent through unchanged and saves it as the current reference.
- If both are connected and the saved latent shape does not match the current input, the node rewrites the reference and passes the current input through unchanged.
- If both are connected and the saved latent shape matches the current input, the node tweaks the latent using the connected input as the base and the saved latent as the learned reference.
- If both are connected and the saved latent shape matches the current input, the node tweaks the latent using the connected input as the base and the saved latent as the learned reference. The refined tensor is bounded to the current/reference value range to avoid out-of-range latent values.
Resetting the session deletes the saved latent reference too.
+3 -1
View File
@@ -4,7 +4,7 @@ This node saves a latent tensor bundle under a refinement key so `FunPack Video
## Parameters
**latent**: Latent tensor bundle to save.
**latent**: Plain video latent tensor bundle to save. In LTX audio/video workflows, connect the video output from `LTXVSeparateAVLatent`, not the audio latent or combined AV latent.
**refinement_key**: Name of the refinement session. Use the same key in `FunPack Video Refiner`.
@@ -19,3 +19,5 @@ This node saves a latent tensor bundle under a refinement key so `FunPack Video
## Notes
The saved latent lives in FunPack's local `refinements` folder as a PyTorch tensor file. The refiner only uses it when both the key and mode match and the `refined_latent` output is connected. If the refiner receives a latent and no saved reference exists, it saves the incoming latent automatically. Zero-valued latent positions are treated as intentional and are not refined.
Audio latents and LTX audio/video combined `NestedTensor` latents are intentionally rejected. Use only the video latent side, then reconnect the refined result to `LTXVConcatAVLatent` as `video_latent`.