Interactive Guessing engine: conditioning-spread ladder (Phase 2)

New variant producer for Batch Training that, instead of seed/shortcut
variation, scales the conditioning's spread (sigma) on VIDEO channels by a
linear ramp across N rungs, freezing everything else including the noise
seed so the only variable is conditioning strength. Up amplifies toward
overbake, down dampens. Each rung records funpack_guess_factor (in cond
meta + batch manifest) so the ladder is interpretable and Phase-3 learning
can read each rung. Audio channels untouched (scale_conditioning_spread
reuses the LTXAV channel split). Wired refine_v2 -> _v2_pack_batch
(guess_mode branch -> _v2_pack_guess_ladder) and the Studio refine_v2 call
(guess_mode/direction/range from refiner settings).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-04 15:27:30 +03:00
co-authored by Claude Opus 4.8
parent 1d5db355d4
commit 7a6821930a
2 changed files with 80 additions and 3 deletions
+77 -3
View File
@@ -413,6 +413,38 @@ def protect_audio_channels(steered, original):
return steered
def scale_conditioning_spread(cond, factor):
"""Scale the conditioning's per-token spread (its 'sigma') on VIDEO channels only by `factor`,
mean-preserving per token; audio channels are left untouched (LTXAV channel split). factor>1
amplifies the prompt's expression (toward overbake), <1 dampens it. Used by Interactive
Guessing to build a strength ladder, and by the learned safe-spread cap."""
try:
if not isinstance(cond, torch.Tensor) or abs(float(factor) - 1.0) < 1e-6:
return cond
v = ltxav_video_channels(cond.shape[-1])
out = cond.clone()
vid = out[..., :v]
m = vid.mean(dim=-1, keepdim=True)
out[..., :v] = m + (vid - m) * float(factor)
return out
except Exception:
return cond
def conditioning_spread(cond):
"""Robust per-token video-channel spread (mean std over tokens) of a conditioning tensor —
the scalar Interactive Guessing's ladder and cap reason about. None if not measurable."""
try:
if not isinstance(cond, torch.Tensor):
return None
v = ltxav_video_channels(cond.shape[-1])
vid = cond[..., :v].float()
m = vid.mean(dim=-1, keepdim=True)
return float(((vid - m) ** 2).mean().clamp_min(1e-12).sqrt().item())
except Exception:
return None
def refinement_state_path(refinement_key, mode, prefix="refine", extension="json"):
base_dir = os.path.dirname(os.path.abspath(__file__))
refinements_dir = os.path.join(base_dir, "refinements")
@@ -11449,7 +11481,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
value_guidance=True, latent=None, seed_output_connected=False,
steer_mode="relative", absolute_strength=0.6,
_seed=None, _seed_source="fresh seed", _scene_seeds=None, _velocity_keys=None,
batch_variants=1):
batch_variants=1, guess_mode=False, guess_direction="up", guess_range=1.0):
seed = int(_seed) if _seed is not None else random.randint(1, 0xffffffffffffffff)
encode_cache = {}
linked_refinement_key = str(refinement_key_input or "").strip()
@@ -12288,7 +12320,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
output_conditioning, batch_variants, _raw_positive_prompt, seed, clip,
encode_cache, source_image, prompt_to_encode, global_state, learning_profile,
repair_feedback, current_family_slot, _vf_for_memory, _concept_dir,
_concept_strength, _current_final)
_concept_strength, _current_final,
guess_mode=guess_mode, guess_direction=guess_direction, guess_range=guess_range)
return (
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength),
status,
@@ -12317,9 +12350,44 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
video_latent,
)
def _v2_pack_guess_ladder(self, output_conditioning, n, direction, guess_range, seed):
"""Interactive Guessing: build N variants that scale the conditioning's spread (sigma) on
video channels by a LINEAR ramp, freezing everything else INCLUDING the noise seed so the
only variable across the ladder is conditioning strength. Variant 0 = base (factor 1.0);
the last = 1.0 +/- guess_range (up amplifies, down dampens). Each variant records
'funpack_guess_factor' so the rating ladder is interpretable and Phase-3 learning can read
each rung's strength."""
n = max(2, int(n))
guess_range = abs(float(guess_range or 1.0))
up = str(direction or "up").lower() != "down"
packed = []
for i in range(n):
t = i / (n - 1)
factor = (1.0 + guess_range * t) if up else (1.0 - min(0.95, guess_range) * t)
for entry in output_conditioning:
cond = entry[0]
meta = entry[1] if len(entry) > 1 and isinstance(entry[1], dict) else {}
m = dict(meta)
m["funpack_batch_variant"] = i
m["funpack_guess_factor"] = round(float(factor), 4)
packed.append((scale_conditioning_spread(cond, factor), m))
# Freeze the noise: every variant shares the same per-scene seeds, so only the spread varies.
base = random.Random(int(seed) & 0xffffffffffffffff).randint(1, 0xffffffffffffffff)
scene_counter = {}
for entry in packed:
m = entry[1]
vi = int(m.get("funpack_batch_variant", 0))
si = scene_counter.get(vi, 0)
m["funpack_scene_seed"] = (base + si) & 0xffffffffffffffff
scene_counter[vi] = si + 1
print(f"[FunPackVideoRefinerV2] Interactive Guessing: packed {n} rungs "
f"(direction={'up' if up else 'down'}, range={guess_range}, seed frozen).")
return packed
def _v2_pack_batch(self, output_conditioning, n, raw_prompt, seed, clip, encode_cache,
source_image, base_prompt, global_state, learning_profile, repair_feedback,
intent_family_slot, vf, concept_dir, concept_strength, current_final_texts):
intent_family_slot, vf, concept_dir, concept_strength, current_final_texts,
guess_mode=False, guess_direction="up", guess_range=1.0):
"""Pack N Batch-Training variant entries onto the FINAL conditioning, each tagged
'funpack_batch_variant' so the Chain Sampler samples one chain per variant. Single-scene
output -> re-resolve shortcuts per variant (different option picks). Multi-scene output
@@ -12329,6 +12397,9 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
n = max(1, int(n or 1))
if n <= 1 or not output_conditioning:
return output_conditioning
# Interactive Guessing: a conditioning-spread ladder (seed frozen), not shortcut/seed variants.
if guess_mode:
return self._v2_pack_guess_ladder(output_conditioning, n, guess_direction, guess_range, seed)
# Build the N variant entries. Single-scene output -> _v2_build_batch_variants (re-resolves
# shortcuts per variant). Multi-scene (transition split) -> duplicate the whole scene-set
# per variant. Each variant tags ALL its scene entries with the same index.
@@ -13818,6 +13889,9 @@ class FunPackStudio:
refinement_key=key,
_velocity_keys=_vel_commit_keys,
batch_variants=int(rf.get("batch_variants", 1) or 1),
guess_mode=bool(rf.get("guess_mode", False)),
guess_direction=str(rf.get("guess_direction", "up") or "up"),
guess_range=float(rf.get("guess_range", 1.0) or 1.0),
reset_session=reset_session,
lora_stack=active_lora_stack,
im_feeling_lucky=im_feeling_lucky,
+3
View File
@@ -2956,6 +2956,9 @@ class FunPackLTXAVSceneChainSampler:
"latent": os.path.basename(latent_path) if latent_path else None,
"preview": os.path.basename(preview_path) if has_preview else None,
"cond": cond_name,
"guess_factor": (pos_i[0][1].get("funpack_guess_factor")
if pos_i and isinstance(pos_i[0], (list, tuple))
and len(pos_i[0]) > 1 and isinstance(pos_i[0][1], dict) else None),
"rating": None,
})
try: