path-planner Phase 2: seed-lever avoidance routing

Make path_outcomes ACT via the one Studio->sampler lever wired today: the seed
(FunPackStudio.run -> _v2_choose_successful_seed -> seed output -> sampler). No
new data paths invented.

The existing seed chooser only exploited liked seeds with zero avoidance — the
headline gap. Now:
- recorder tags each seed sample avoid = reward<0 and not skip_value_function
  (Wrong-* excluded: the seed was fine, the words/identity were off)
- _v2_path_disliked_seeds: seeds that gave a low-quality result for matching concepts
- _v2_choose_successful_seed redraws a disliked fallback, drops liked-then-later-
  disliked seeds from reuse, and reports how many it avoided

Broader config levers (guidance/lora) need new wiring and are deferred pending
graph-topology confirmation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-06-21 13:14:40 +03:00
co-authored by Claude Opus 4.8
parent 5731315b4b
commit 2bd9318bb3
2 changed files with 98 additions and 7 deletions
+49 -7
View File
@@ -7138,11 +7138,16 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
entry["concepts"] = signature["concepts"]
entry["config"] = signature["config"]
if seed is not None:
# "avoid": this seed draw produced a genuine LOW-QUALITY result, so the planner
# should not reuse it. Wrong-* ratings are excluded — they mean "good gen, the
# words/identity were off" (skip_value_function), i.e. the seed itself was fine.
avoid = bool(reward < 0.0 and not rating_profile.get("skip_value_function"))
seeds = entry.setdefault("seeds", [])
seeds.append({
"seed": seed,
"outcome": outcome_key,
"reward": round(reward, 6),
"avoid": avoid,
"iteration": int(iter_num),
})
entry["seeds"] = seeds[-V2_PATH_OUTCOME_MAX_SEEDS_PER_ARM:]
@@ -7217,6 +7222,31 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
global_state["successful_seed_memory"] = self._v2_trim_successful_seed_memory(memory)
return f"Seed memory: stored seed {seed} for {touched} concept(s)."
def _v2_path_disliked_seeds(self, path_memory, concepts):
"""Seeds that produced a genuine low-quality result for any concept-matching arm in
the path-outcome memory. These are AVOIDED when routing the next seed — the planner's
'do not repeat what the user disliked' guarantee (path-planner Phase 2)."""
disliked = set()
if not isinstance(path_memory, dict) or not path_memory:
return disliked
concept_set = {str(c).strip().lower() for c in (concepts or []) if str(c).strip()}
if not concept_set:
return disliked
for arm in path_memory.values():
if not isinstance(arm, dict):
continue
arm_concepts = {str(c).strip().lower() for c in (arm.get("concepts") or [])}
if not (arm_concepts & concept_set):
continue
for sample in arm.get("seeds", []) or []:
if not isinstance(sample, dict) or not sample.get("avoid"):
continue
try:
disliked.add(int(sample.get("seed")))
except (TypeError, ValueError):
continue
return disliked
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:
@@ -7224,10 +7254,17 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
if reset_session:
return fallback_seed, "fresh seed; session reset", None
state, _ = self._v2_load_state(refinement_key, reset_session=False)
memory = state.get("global", {}).get("successful_seed_memory", {}) if isinstance(state, dict) else {}
if not isinstance(memory, dict) or not memory:
return fallback_seed, "fresh seed; no successful seed memory", None
global_state = state.get("global", {}) if isinstance(state, dict) else {}
memory = global_state.get("successful_seed_memory", {}) if isinstance(global_state, dict) else {}
concepts = self._v2_seed_concepts_from_text(prompt)
# Path-planner AVOIDANCE: never reuse a seed that gave a disliked result for these
# concepts, and prefer a fresh seed if the fallback itself is one we should avoid.
disliked = self._v2_path_disliked_seeds(global_state.get("path_outcomes", {}), concepts)
avoided_note = f"; avoiding {len(disliked)} disliked seed(s)" if disliked else ""
while fallback_seed in disliked:
fallback_seed = random.randint(1, 0xffffffffffffffff)
if not isinstance(memory, dict) or not memory:
return fallback_seed, f"fresh seed; no successful seed memory{avoided_note}", None
candidates = []
for concept in concepts:
entry = memory.get(concept)
@@ -7235,11 +7272,16 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
continue
for item in entry.get("seeds", []) or []:
if isinstance(item, dict):
try:
if int(item.get("seed")) in disliked:
continue # liked once, disliked later — drop it
except (TypeError, ValueError):
pass
candidates.append((concept, item))
if not candidates:
return fallback_seed, "fresh seed; no concept seed match", None
return fallback_seed, f"fresh seed; no concept seed match{avoided_note}", None
if random.random() >= V2_SEED_REUSE_CHANCE:
return fallback_seed, "fresh seed; successful seed memory skipped", None
return fallback_seed, f"fresh seed; successful seed memory skipped{avoided_note}", None
candidates.sort(
key=lambda pair: (int(pair[1].get("hit_count", 0)), int(pair[1].get("last_iteration", 0))),
reverse=True,
@@ -7248,7 +7290,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
try:
seed = int(item.get("seed"))
except (TypeError, ValueError):
return fallback_seed, "fresh seed; selected memory seed invalid", None
return fallback_seed, f"fresh seed; selected memory seed invalid{avoided_note}", None
scene_seeds = []
for value in item.get("scene_seeds") or []:
try:
@@ -7256,7 +7298,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
except (TypeError, ValueError):
pass
scene_seeds = scene_seeds or None
return seed, f"successful seed memory: reused {seed} from '{concept}'", scene_seeds
return seed, f"successful seed memory: reused {seed} from '{concept}'{avoided_note}", scene_seeds
def _v2_conditioning_vector(self, conditioning):
if not isinstance(conditioning, torch.Tensor) or conditioning.dim() <= 1:
+49
View File
@@ -1934,6 +1934,55 @@ def test_successful_seed_reuse_matches_concepts(monkeypatch, tmp_path):
assert "successful seed memory" in source
def test_path_outcomes_marks_quality_dislikes_for_avoidance_but_not_wrong_ratings():
refiner = FunPackVideoRefinerV2()
gs = {"phrase_memory": {}, "path_outcomes": {}}
prompt = "woman walking through neon rain"
refiner._v2_update_path_outcomes(gs, _path_run(refiner, prompt, gs, seed=10), normalize_refiner_v2_rating("Awful"), 1)
refiner._v2_update_path_outcomes(gs, _path_run(refiner, prompt, gs, seed=20), normalize_refiner_v2_rating("Wrong appearance"), 2)
refiner._v2_update_path_outcomes(gs, _path_run(refiner, prompt, gs, seed=30), normalize_refiner_v2_rating("Perfect"), 3)
samples = {s["seed"]: s for arm in gs["path_outcomes"].values() for s in arm["seeds"]}
assert samples[10]["avoid"] is True # Awful = genuine low quality
assert samples[20]["avoid"] is False # Wrong appearance = seed was fine, words were off
assert samples[30]["avoid"] is False # Perfect
disliked = refiner._v2_path_disliked_seeds(gs["path_outcomes"], ["neon", "rain"])
assert disliked == {10}
def test_choose_seed_avoids_disliked_and_drops_later_disliked_reuse(monkeypatch, tmp_path):
refiner = FunPackVideoRefinerV2()
state_path = tmp_path / "state.json"
refiner._v2_state_path = lambda refinement_key: str(state_path)
state_path.write_text(json.dumps({
"version": 2,
"global": {
# 'neon' seed 777 was liked once, but the path memory later marks it disliked.
"successful_seed_memory": {
"neon": {"concept": "neon", "seeds": [{"seed": 777, "hit_count": 2, "last_iteration": 5}]}
},
"path_outcomes": {
"arm1": {
"concepts": ["neon", "rain"],
"seeds": [{"seed": 777, "avoid": True, "reward": -0.9}],
},
},
},
"prompt_histories": {},
"last_run": None,
}))
monkeypatch.setattr("conditioning.random.random", lambda: 0.05) # would reuse if any candidate left
seed, source, _ = refiner._v2_choose_successful_seed(
"seed-test", "KaiSa in neon rain", 123, seed_output_connected=True,
)
assert seed != 777, "a seed marked disliked in path memory must never be reused"
assert "avoiding 1 disliked seed" in source
def test_split_by_transitions_attaches_scene_seeds(tmp_path):
refiner = FunPackVideoRefinerV2()
state_path = tmp_path / "state.json"