From 7a6821930a6fb1982bbcb251d36c5dad96f1b274 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Thu, 4 Jun 2026 15:27:30 +0300 Subject: [PATCH] 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 --- conditioning.py | 80 +++++++++++++++++++++++++++++++++++++++++++++++-- samplers.py | 3 ++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/conditioning.py b/conditioning.py index ecd278df..84368c88 100644 --- a/conditioning.py +++ b/conditioning.py @@ -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, diff --git a/samplers.py b/samplers.py index b53971bc..6401d099 100644 --- a/samplers.py +++ b/samplers.py @@ -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: