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:
co-authored by
Claude Opus 4.8
parent
5731315b4b
commit
2bd9318bb3
+49
-7
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user