From 9fb0ee267b7e512694231cd3d4e281466869e363 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 21 Jun 2026 16:26:16 +0300 Subject: [PATCH] path-planner Phase 2d: conditioning-variant routing Route the conditioning variant from per-config outcome history, via the one charter-safe surface: the INTERNAL value-function ascend+search (not user knobs, so no WYSIWYG / hidden-settings violation). Sampler-owned visual ops stay on the sampler (boundary law kept). Directly serves the original complaint (Perfect not locking, Awful reproducing): - _v2_path_conditioning_plan: matching-arm Beta posteriors -> liked-rate mean -> "lock" (>=0.6: hold the liked conditioning, skip the stochastic search), "explore" (<=0.4: widen the search to flee a disliked region), else "normal" - value_function.search gains eps_scale to widen/tighten the search radius - threaded conditioning_plan through ascend -> apply_scene_refinement_keys -> finalize; computed once per run from path_outcomes + prompt concepts; logged Only active once the per-key VF is ready, since it rides the existing ascend path. Co-Authored-By: Claude Opus 4.8 --- conditioning.py | 76 ++++++++++++++++++++++++----- tests/test_v2_category_alignment.py | 14 ++++++ value_function.py | 10 ++-- 3 files changed, 85 insertions(+), 15 deletions(-) diff --git a/conditioning.py b/conditioning.py index bbe4a801..c614acf1 100644 --- a/conditioning.py +++ b/conditioning.py @@ -7303,6 +7303,32 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): return True, f"path planner: explore fresh (best arm θ={best_theta:.2f} < fresh {fresh_theta:.2f})" return False, f"path planner: exploit arm θ={best_theta:.2f} (n={int(best_arm.get('n_pulls', 0))})" + def _v2_path_conditioning_plan(self, path_memory, concepts): + """Route the CONDITIONING variant for the next gen from the path-outcome memory. + + Aggregates the Beta posteriors of every concept-matching arm into one liked-rate mean + and returns a directive for ``_v2_ascend_conditioning``: + * "lock" — liked region: keep the conditioning stable (skip the stochastic search) + so a good direction is held in place rather than perturbed away. + * "explore" — disliked region: widen the value-function search to push toward a + different conditioning ("do something else"). + * "normal" — ambiguous: today's ascend + standard search. + Returns ``(mode, reason)``, or ``(None, "")`` with no path data (caller keeps default).""" + arms = self._v2_path_matching_arms(path_memory, concepts) + if not arms: + return None, "" + alpha, beta = 1.0, 1.0 + for arm in arms: + a, b = self._v2_path_arm_beta(arm) + alpha += a - 1.0 + beta += b - 1.0 + mean = alpha / (alpha + beta) + if mean >= 0.6: + return "lock", f"path planner: lock conditioning (liked-rate {mean:.2f})" + if mean <= 0.4: + return "explore", f"path planner: explore conditioning (liked-rate {mean:.2f})" + return "normal", f"path planner: normal conditioning (liked-rate {mean:.2f})" + def _v2_choose_successful_seed(self, refinement_key, prompt, fallback_seed, seed_output_connected=False, reset_session=False): fallback_seed = int(fallback_seed) if not seed_output_connected: @@ -12476,6 +12502,14 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): # conditioning just before each return below — so it wraps whatever output_conditioning # ends up being: single-scene OR multi-scene (transition split). Works WITH transitions. _do_batch = int(batch_variants or 1) > 1 and not learning_mode and not prompt_only_mode + # Path planner: route the conditioning variant from per-config outcome history — lock a + # liked region's conditioning, widen the search to flee a disliked one. None with no data. + _conditioning_plan, _conditioning_plan_reason = self._v2_path_conditioning_plan( + global_state.get("path_outcomes", {}), + self._v2_seed_concepts_from_text(prompt_to_encode), + ) + if _conditioning_plan_reason: + print(f"[FunPackVideoRefinerV2] {_conditioning_plan_reason}") if split_by_transitions: try: scene_texts = split_scene_texts @@ -12519,7 +12553,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): guess_mode=guess_mode, guess_direction=guess_direction, guess_range=guess_range, guess_freeze_seed=guess_freeze_seed) return ( - self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile), + self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile, conditioning_plan=_conditioning_plan), status, training_info, loss_graph, @@ -12537,7 +12571,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): repair_feedback, current_family_slot, _vf_for_memory, _concept_dir, _concept_strength, _current_final) return ( - self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile), + self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=_guess_spread_cap, temporal_style=temporal_style, temporal_fallback_text=prompt_to_encode, scene_refinement_keys=scene_refinement_keys, learning_profile=learning_profile, conditioning_plan=_conditioning_plan), status + enhancement_status, training_info, loss_graph, @@ -12704,11 +12738,16 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): f"({len(cache)} distinct prompt expansion(s)).") return out - def _v2_ascend_conditioning(self, conditioning_list, refinement_key, apply=True): + def _v2_ascend_conditioning(self, conditioning_list, refinement_key, apply=True, conditioning_plan=None): """Gradient-ascend each conditioning tensor toward higher predicted reward. No-op if disabled (apply=False), or if the value function is missing or not yet ready (< MIN_SAMPLES ratings). The VF still trains in the background when this is - disabled — only the application of guidance is skipped.""" + disabled — only the application of guidance is skipped. + + conditioning_plan (from _v2_path_conditioning_plan) routes the stochastic search: + "lock" skips it (hold a liked region's conditioning), "explore" widens it (push a + disliked region elsewhere), otherwise the standard search runs.""" + plan = conditioning_plan if isinstance(conditioning_plan, str) else None if not apply or not conditioning_list or not refinement_key: return conditioning_list try: @@ -12727,18 +12766,26 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): tensor, extra = entry[0], entry[1] # Gradient ascent first (exploit known gradient direction) c = vf.ascend(tensor) - # Monte Carlo search around ascended point (explore neighbourhood) - c = vf.search(c) + # Monte Carlo search around ascended point. The path planner routes its breadth: + # lock = hold the liked conditioning (no search), explore = wider search. + if plan == "lock": + pass + elif plan == "explore": + c = vf.search(c, n=32, eps_scale=2.0) + else: + c = vf.search(c) c = protect_audio_channels(c, tensor) # keep audio on the original conditioning ascended.append((c, extra)) - print(f"[FunPackRefiner] Conditioning ascent+search applied ({vf.n_trained} samples, {len(ascended)} scene(s))") + print(f"[FunPackRefiner] Conditioning ascent+search applied " + f"({vf.n_trained} samples, {len(ascended)} scene(s), plan={plan or 'normal'})") return ascended except Exception as e: print(f"[FunPackRefiner] Conditioning ascent failed: {e}") return conditioning_list def _v2_apply_scene_refinement_keys(self, conditioning_list, scene_refinement_keys, - default_key, value_guidance=True, learning_profile=None): + default_key, value_guidance=True, learning_profile=None, + conditioning_plan=None): """Relative-mode output steering with per-scene multi-key support. For each scene entry, look up the non-default keys whose shortcuts fired in it: @@ -12753,7 +12800,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): return conditioning_list has_keys = any(scene_refinement_keys or []) if not has_keys: - return self._v2_ascend_conditioning(conditioning_list, default_key, apply=value_guidance) + return self._v2_ascend_conditioning(conditioning_list, default_key, apply=value_guidance, + conditioning_plan=conditioning_plan) profile = learning_profile or dict(V2_RATING_PROFILES["Initial discovery"], label="Initial discovery") key_state_cache = {} @@ -12781,7 +12829,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): keys = set(scene_refinement_keys[scene_index] or set()) if not keys: # default-key path for this scene (per-entry so it matches no-key behaviour) - out.append(self._v2_ascend_conditioning([entry], default_key, apply=value_guidance)[0]) + out.append(self._v2_ascend_conditioning([entry], default_key, apply=value_guidance, + conditioning_plan=conditioning_plan)[0]) continue # Each key contributes only its learned DIRECTIONS here (per-prompt # conditioning memory). The value-function ascend+search runs ONCE on the @@ -12821,7 +12870,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): # project default key (the most-trained, most stable VF) rather than any # one scene key. try: - merged = self._v2_ascend_conditioning([(merged, meta)], default_key, apply=value_guidance)[0][0] + merged = self._v2_ascend_conditioning([(merged, meta)], default_key, apply=value_guidance, + conditioning_plan=conditioning_plan)[0][0] except Exception as error: print(f"[FunPackStudio] Merged scene ascend failed: {error}") merged = protect_audio_channels(merged, cond) @@ -13063,7 +13113,8 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): def _v2_finalize_conditioning(self, conditioning_list, refinement_key, value_guidance, steer_mode, absolute_strength, spread_cap=None, temporal_style="natural", temporal_fallback_text="", - scene_refinement_keys=None, learning_profile=None): + scene_refinement_keys=None, learning_profile=None, + conditioning_plan=None): """Single output hook for both steering modes. Relative = per-key VF ascend (current behaviour). Absolute = global taste pull. Both = layer them. Finally, if Interactive Guessing has learned a safe-spread ceiling, clamp the output conditioning's video-channel @@ -13079,6 +13130,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner): out = self._v2_apply_scene_refinement_keys( out, scene_refinement_keys, refinement_key, value_guidance=value_guidance, learning_profile=learning_profile, + conditioning_plan=conditioning_plan, ) if mode in ("absolute", "both"): out = self._v2_apply_absolute(out, float(absolute_strength)) diff --git a/tests/test_v2_category_alignment.py b/tests/test_v2_category_alignment.py index 791e4527..e0341c35 100644 --- a/tests/test_v2_category_alignment.py +++ b/tests/test_v2_category_alignment.py @@ -2029,6 +2029,20 @@ def test_path_planner_exploits_liked_arm_far_more_than_disliked_arm(): assert disliked_explores > 240 +def test_path_conditioning_plan_locks_liked_explores_disliked(): + refiner = FunPackVideoRefinerV2() + liked = {"a": {"concepts": ["neon"], "outcomes": {"like": 10.0}}} + disliked = {"a": {"concepts": ["neon"], "outcomes": {"awful": 10.0}}} + mixed = {"a": {"concepts": ["neon"], "outcomes": {"like": 5.0, "awful": 5.0}}} + + assert refiner._v2_path_conditioning_plan(liked, ["neon"])[0] == "lock" + assert refiner._v2_path_conditioning_plan(disliked, ["neon"])[0] == "explore" + assert refiner._v2_path_conditioning_plan(mixed, ["neon"])[0] == "normal" + assert refiner._v2_path_conditioning_plan({}, ["neon"]) == (None, "") + # No concept overlap -> no matching arms -> defer. + assert refiner._v2_path_conditioning_plan(liked, ["desert"]) == (None, "") + + def test_split_by_transitions_attaches_scene_seeds(tmp_path): refiner = FunPackVideoRefinerV2() state_path = tmp_path / "state.json" diff --git a/value_function.py b/value_function.py index 2da426d5..6dc71199 100644 --- a/value_function.py +++ b/value_function.py @@ -117,14 +117,18 @@ class OnlineValueFunction(nn.Module): prev_reward = reward_val return c.detach().to(orig_device, orig_dtype) - def search(self, conditioning, n=16): - """Monte Carlo conditioning search: score N random perturbations, return the best.""" + def search(self, conditioning, n=16, eps_scale=1.0): + """Monte Carlo conditioning search: score N random perturbations, return the best. + + eps_scale widens (>1) or tightens (<1) the perturbation radius. The path planner uses + a wider radius to push a disliked config region toward a different conditioning, and the + caller skips this search entirely to LOCK a liked region's conditioning in place.""" if not self.is_ready(): return conditioning mlp_device = next(self.parameters()).device orig_device = conditioning.device orig_dtype = conditioning.dtype - eps = conditioning.float().norm().item() * 0.025 + eps = conditioning.float().norm().item() * 0.025 * float(eps_scale) with torch.inference_mode(False), torch.no_grad(): c_base = torch.empty(conditioning.shape, dtype=torch.float32, device=mlp_device) c_base.copy_(conditioning)