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 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-21 16:26:16 +03:00
co-authored by Claude Opus 4.8
parent b6f17f2b3b
commit 9fb0ee267b
3 changed files with 85 additions and 15 deletions
+64 -12
View File
@@ -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))
+14
View File
@@ -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"
+7 -3
View File
@@ -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)