diff --git a/CHANGELOG.md b/CHANGELOG.md index 14fc6619..22372396 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Fixed `FunPack Hybrid Euler 2S Sampler` restart timing so `restart_trigger_pct` Improved `FunPack LoRA Loader` rerun performance by caching recently used raw LoRA files, model-mapped LoRA patches, and per-block fingerprint analysis. +Reworked `FunPack Video Refiner` ratings around explicit missing-axis signals: `Perfect`, single missing axes, paired missing axes, and `Awful`. + ## [2.1.3] - 2026-04-24 ### Changed diff --git a/README.md b/README.md index ab32edb1..5313f01c 100644 --- a/README.md +++ b/README.md @@ -8,14 +8,15 @@ Updated UI for `FunPack Apply LoRA Weights`. Now it's a one-line rgthree-like st Updated rating for `FunPack Video Refiner`. Now it uses a rating system more understandable to end user: -- Rating `"I like it"` - resulting video is an exact match or really close to what was requested; -- Rating `Missing details` - really close to what was requested, main concept is present but some details/actions were omitted; -- Rating `Missing concept` - visually pleasent in terms of quality/anatomy/style/scene, but neither main concept nor details/actions are present; -- Rating `Missing quality` - visually pleasent, but lacking proper anatomy/style/scene and neither main concept nor details/actions are present; -- Rating `I don't like it` - visual garbage, totally not matching the requirements. +- Rating `Perfect` - resulting video is an exact match or really close to what was requested; +- Rating `Missing details` - concept and quality are present, but some prompt details/actions were omitted; +- Rating `Missing concept` - visually pleasant, but characters or subjects are not doing what was requested; +- Rating `Missing quality` - concept and details are attempted, but anatomy/style/scene quality is messy; +- Pair ratings such as `Missing details + concept`, `Missing details + quality`, and `Missing concept + quality` - boost the named missing axes together; +- Rating `Awful` - details, concept, and quality are all missing. - Rating `-Just forget it-` - in case of failing sampling or any other result when you are unable to see the video to rate it, select this rating so the previous generation won't count in learning process. -Updated logic for `FunPack Video Refiner`. The original conditioning is still stored for prompt/conditioning change detection, but after the first `I like it` / `9-10` rating the active refinement reference switches to the liked generated conditioning. Later liked results update that reference as a running average, while `I don't like it` rolls back to the latest better-rated conditioning before pushing away from the failed direction. +Updated logic for `FunPack Video Refiner`. The original conditioning is still stored for prompt/conditioning change detection, but after the first `Perfect` rating the active refinement reference switches to the liked generated conditioning. Later perfect results update that reference as a running average, while `Awful` can roll back to the latest better-rated conditioning before boosting all missing axes. ## Dev Branch diff --git a/conditioning.py b/conditioning.py index 8fc1fe94..14a4caed 100644 --- a/conditioning.py +++ b/conditioning.py @@ -27,12 +27,15 @@ LORA_REFINER_TYPE_PROFILES = { } RATING_LABELS = [ - "I like it", + "-Just forget it-", + "Perfect", "Missing details", "Missing concept", "Missing quality", - "I don't like it", - "-Just forget it-", + "Missing details + concept", + "Missing details + quality", + "Missing concept + quality", + "Awful", ] RATING_PROFILES = { @@ -45,66 +48,119 @@ RATING_PROFILES = { "quality_signal": 0.0, "concept_signal": 0.0, "detail_signal": 0.0, + "missing_axes": [], "prompt_emphasis": 0.0, "skip_learning": True, }, - "I like it": { + "Perfect": { "key": "like", - "level": 5, + "level": 8, "legacy_score": 10, - "legacy_range": "9-10", + "legacy_range": "keep", "reward": 1.0, "quality_signal": 1.0, "concept_signal": 1.0, "detail_signal": 1.0, + "missing_axes": [], "prompt_emphasis": 0.45, }, "Missing details": { "key": "missing_details", - "level": 4, + "level": 7, "legacy_score": 8, - "legacy_range": "7-8", + "legacy_range": "boost details", "reward": 0.45, "quality_signal": 0.85, "concept_signal": 0.70, - "detail_signal": -0.55, + "detail_signal": -1.0, + "missing_axes": ["details"], "prompt_emphasis": 0.65, }, "Missing concept": { "key": "missing_concept", - "level": 3, + "level": 6, "legacy_score": 6, - "legacy_range": "5-6", - "reward": -0.35, - "quality_signal": 0.65, - "concept_signal": -0.90, - "detail_signal": -0.70, + "legacy_range": "boost concept", + "reward": 0.10, + "quality_signal": 0.85, + "concept_signal": -1.0, + "detail_signal": 0.20, + "missing_axes": ["concept"], "prompt_emphasis": 0.95, }, "Missing quality": { "key": "missing_quality", - "level": 2, + "level": 5, "legacy_score": 4, - "legacy_range": "3-4", - "reward": -0.75, - "quality_signal": -0.90, - "concept_signal": -0.75, - "detail_signal": -0.70, + "legacy_range": "boost quality", + "reward": -0.25, + "quality_signal": -1.0, + "concept_signal": 0.65, + "detail_signal": 0.65, + "missing_axes": ["quality"], "prompt_emphasis": 0.55, }, - "I don't like it": { - "key": "dislike", + "Missing details + concept": { + "key": "missing_details_concept", + "level": 4, + "legacy_score": 5, + "legacy_range": "boost details+concept", + "reward": -0.05, + "quality_signal": 0.75, + "concept_signal": -1.0, + "detail_signal": -1.0, + "missing_axes": ["details", "concept"], + "prompt_emphasis": 1.05, + }, + "Missing details + quality": { + "key": "missing_details_quality", + "level": 3, + "legacy_score": 3, + "legacy_range": "boost details+quality", + "reward": -0.35, + "quality_signal": -1.0, + "concept_signal": 0.50, + "detail_signal": -1.0, + "missing_axes": ["details", "quality"], + "prompt_emphasis": 0.82, + }, + "Missing concept + quality": { + "key": "missing_concept_quality", + "level": 2, + "legacy_score": 2, + "legacy_range": "boost concept+quality", + "reward": -0.55, + "quality_signal": -1.0, + "concept_signal": -1.0, + "detail_signal": 0.15, + "missing_axes": ["concept", "quality"], + "prompt_emphasis": 1.02, + }, + "Awful": { + "key": "awful", "level": 1, "legacy_score": 2, - "legacy_range": "1-2", - "reward": -1.0, + "legacy_range": "boost all", + "reward": -0.85, "quality_signal": -1.0, "concept_signal": -1.0, "detail_signal": -1.0, - "prompt_emphasis": -0.65, + "missing_axes": ["details", "concept", "quality"], + "prompt_emphasis": 1.20, + "rollback_on_failure": True, }, } +RATING_ALIASES = { + "I like it": "Perfect", + "I don't like it": "Awful", + "Missing details + concepts": "Missing details + concept", + "Missing concept + details": "Missing details + concept", + "Missing quality + details": "Missing details + quality", + "Missing quality + concept": "Missing concept + quality", + "Missing everything": "Awful", +} + CATEGORY_FEEDBACK_MAP = { 1: "general", 2: "concept", @@ -122,6 +178,7 @@ def _clamp(value, low, high): def normalize_refiner_rating(value): if isinstance(value, str): cleaned = value.strip() + cleaned = RATING_ALIASES.get(cleaned, cleaned) if cleaned in RATING_PROFILES: return dict(RATING_PROFILES[cleaned], label=cleaned) try: @@ -136,7 +193,7 @@ def normalize_refiner_rating(value): legacy_score = int(_clamp(legacy_score, 1, 10)) if legacy_score >= 9: - label = "I like it" + label = "Perfect" elif legacy_score >= 7: label = "Missing details" elif legacy_score >= 5: @@ -144,7 +201,7 @@ def normalize_refiner_rating(value): elif legacy_score >= 3: label = "Missing quality" else: - label = "I don't like it" + label = "Awful" profile = dict(RATING_PROFILES[label], label=label) profile["legacy_score"] = legacy_score @@ -1934,12 +1991,20 @@ class FunPackVideoRefiner: rating_profile = rating_profile or normalize_refiner_rating(rating) rating_key = rating_profile.get("key", "") + missing_axes = set(rating_profile.get("missing_axes", [])) if rating_key == "like": lr = 0.035 - elif rating_key in {"missing_quality", "dislike"}: + elif missing_axes: + lr = 0.018 + if "details" in missing_axes: + lr += 0.006 + if "concept" in missing_axes: + lr += 0.012 + if "quality" in missing_axes: + lr += 0.035 + lr = min(0.065, lr) + elif rating_key == "dislike": lr = 0.055 - elif rating_key == "missing_concept": - lr = 0.030 else: lr = 0.018 target_delta = (saved_samples - current) * nonzero_mask.to(dtype=current.dtype) @@ -2059,6 +2124,8 @@ class FunPackVideoRefiner: rating_profile = rating_profile or normalize_refiner_rating(rating) rating_key = rating_profile.get("key", "") + missing_axes = set(rating_profile.get("missing_axes", [])) + missing_count = len(missing_axes) memory = global_adaptive.setdefault("lora_weight_memory", {}) suggestions = {} status_parts = [] @@ -2090,7 +2157,7 @@ class FunPackVideoRefiner: if rating_key == "like": state["good_streak"] = int(state.get("good_streak", 0)) + 1 state["bad_streak"] = 0 - elif rating_key in {"missing_quality", "dislike"}: + elif rating_key == "dislike" or {"concept", "quality"}.issubset(missing_axes): state["bad_streak"] = int(state.get("bad_streak", 0)) + 1 state["good_streak"] = 0 else: @@ -2101,7 +2168,7 @@ class FunPackVideoRefiner: stable_offset = state.get("stable_offset_ratio") culprit_score = float(state.get("culprit_score", 0.0)) culprit_hits = int(state.get("culprit_hits", 0)) - if stable_offset is not None and rating_key in {"like", "missing_details"}: + if stable_offset is not None and (rating_key == "like" or missing_axes == {"details"}): offset = 0.72 * offset + 0.28 * float(stable_offset) step = profile["step"] * max(0.15, relation) @@ -2135,53 +2202,49 @@ class FunPackVideoRefiner: stable_offset = 0.78 * float(stable_offset) + 0.22 * offset state["stable_offset_ratio"] = _clamp(stable_offset, min_offset, max_offset) offset = state["stable_offset_ratio"] - elif rating_key == "missing_details": - culprit_score *= 0.86 + elif missing_axes: + if "concept" in missing_axes or "quality" in missing_axes: + state["stable_offset_ratio"] = None + + culprit_score *= 0.74 if rating_key == "awful" else 0.82 culprit_hits = max(0, culprit_hits - 1) - if relation >= 0.25 and lora_type in {"concept", "character", "general"}: - value_mult = 0.85 + min(1.2, max(0.5, concept_importance)) * 0.20 - offset += step * (0.30 + relation * 0.45) * value_mult + axis_boost = 0.0 + + if "details" in missing_axes: + if relation >= 0.12 and lora_type in {"concept", "character", "general", "style"}: + value_mult = 0.85 + min(1.2, max(0.5, concept_importance)) * 0.20 + axis_boost += (0.26 + relation * 0.34) * value_mult + elif lora_type == "quality": + axis_boost += 0.06 + + if "concept" in missing_axes: + if lora_type in {"concept", "character"}: + if is_primary_concept_lora: + axis_boost += (0.95 + abs(reward) * 0.45) * concept_match_strength + elif relation > 0.08: + axis_boost += 0.34 + relation * 0.40 + elif lora_type == "general" and relation >= 0.25: + axis_boost += 0.25 + relation * 0.25 + + if "quality" in missing_axes: + if lora_type == "quality": + axis_boost += 0.90 + abs(reward) * 0.35 + elif lora_type == "style" and relation >= 0.25: + axis_boost += 0.20 + relation * 0.20 + elif lora_type == "general" and relation >= 0.20: + axis_boost += 0.16 + relation * 0.15 + + if axis_boost > 0.0: + if rating_key == "awful": + axis_boost *= 1.25 + max_offset = max(max_offset, profile["bad_max_offset"] * (0.62 + 0.10 * missing_count)) + relation_floor = 0.35 if missing_count >= 2 else 0.22 + boost_step = profile["step"] * max(relation_floor, effective_relation) + offset += boost_step * axis_boost elif relation <= 0.05: - offset *= 0.96 - elif stable_offset is not None: - offset = 0.78 * offset + 0.22 * float(stable_offset) - elif rating_key == "missing_concept": - state["stable_offset_ratio"] = None - if is_primary_concept_lora: - max_offset = max(max_offset, profile["bad_max_offset"] * 0.82) - offset += step * (1.10 + abs(reward) * 1.25) * concept_match_strength - culprit_score *= 0.70 - culprit_hits = max(0, culprit_hits - 1) - elif lora_type in {"concept", "character"} and relation > 0.08: - culprit_score = _clamp(culprit_score * 0.75 + 0.35 * max(0.25, relation), 0.0, 2.5) - culprit_hits = culprit_hits + 1 if culprit_score >= 0.65 else culprit_hits - min_offset = min(min_offset, profile["bad_min_offset"] * 0.42) - offset -= step * (0.55 + abs(reward)) * max(0.25, relation) - elif relation <= 0.05: - offset *= 0.96 + offset *= 0.97 else: - offset *= 0.92 - elif rating_key == "missing_quality": - state["stable_offset_ratio"] = None - if lora_type == "quality": - max_offset = max(max_offset, profile["bad_max_offset"] * 0.70) - offset += step * (1.00 + abs(reward) * 0.85) * max(0.45, effective_relation) - culprit_score *= 0.76 - elif is_primary_concept_lora: - max_offset = max(max_offset, profile["bad_max_offset"] * 0.55) - offset += step * (0.62 + abs(reward) * 0.55) * concept_match_strength - culprit_score *= 0.82 - elif lora_type in {"concept", "character"} and relation > 0.08: - culprit_score = _clamp(culprit_score * 0.78 + 0.42 * max(0.25, relation), 0.0, 2.5) - culprit_hits = culprit_hits + 1 if culprit_score >= 0.65 else culprit_hits - min_offset = min(min_offset, profile["bad_min_offset"] * 0.35) - offset -= step * (0.72 + abs(reward)) * max(0.25, relation) - else: - culprit_score = _clamp(culprit_score * 0.84 + 0.18 * max(0.0, relation), 0.0, 2.5) - if relation <= 0.05: - offset *= 0.94 - else: - offset -= step * 0.25 * max(0.20, relation) + offset *= 0.94 elif rating_key == "dislike": severity = _clamp((5.0 - float(rating)) / 4.0, 0.0, 1.0) culprit_signal = max(0.20, effective_relation) * (0.70 + base_abs * 0.30) @@ -2226,6 +2289,7 @@ class FunPackVideoRefiner: action = ( "invert" if model_weight < 0.0 else "mute" if model_weight == 0.0 else + "hold" if abs(model_weight - base_model) < 1e-6 else "reduce" if abs(model_weight) < abs(base_model) else "boost" ) @@ -2711,7 +2775,8 @@ class FunPackVideoRefiner: rollback_reference_found = False rollback_rating_label = "" - if rating_key == "dislike" and not source_changed and len(history) > 1: + rollback_requested = bool(rating_profile.get("rollback_on_failure")) or rating_key == "dislike" + if rollback_requested and not source_changed and len(history) > 1: current_level = int(rating_profile.get("level", 1)) for entry in reversed(history[:-1]): entry_profile = normalize_refiner_rating(entry.get("rating_label", entry.get("rating", 0))) @@ -2731,7 +2796,7 @@ class FunPackVideoRefiner: except Exception: continue if ( - rating_key == "dislike" and + rollback_requested and not source_changed and not rollback_reference_found and liked_reference is not None and @@ -2827,7 +2892,7 @@ class FunPackVideoRefiner: if rating_key == "like": good_ratio = 0.9 * good_ratio + 0.1 * 1.0 expl = max(0.015, expl * 0.96) - elif rating_key == "missing_details": + elif set(rating_profile.get("missing_axes", [])) == {"details"}: good_ratio = 0.9 * good_ratio + 0.1 * 0.55 expl = min(0.12, expl * 1.02) else: @@ -2838,7 +2903,7 @@ class FunPackVideoRefiner: sim_threshold = global_adaptive["dynamic_sim_threshold"] is_close = (not is_new_prompt) and (similarity >= sim_threshold) - if (is_close and history) or (rating_key == "dislike" and rollback_reference_found and history): + if (is_close and history) or (rollback_requested and rollback_reference_found and history): last_entry = history[-1] mod_data = last_entry.get("modified_embeds") if mod_data is not None: @@ -2855,7 +2920,7 @@ class FunPackVideoRefiner: if reference_mode == "liked average" and rating_key == "like": noise_scale = 0.0 noise = torch.randn_like(reference) * noise_scale - if rating_key == "dislike" and rollback_reference_found: + if rollback_requested and rollback_reference_found: new_delta = (-prev_delta * 0.9) + (momentum * 0.25) else: multiplier = max(0.05, 1.0 + reward * 1.45) diff --git a/docs/FunPackGemmaEmbeddingRefiner.md b/docs/FunPackGemmaEmbeddingRefiner.md index fa7f0431..63e70d00 100644 --- a/docs/FunPackGemmaEmbeddingRefiner.md +++ b/docs/FunPackGemmaEmbeddingRefiner.md @@ -10,18 +10,21 @@ This node sits after your text encoder and adjusts positive conditioning based o **rating**: Feedback for the previous result: -- `I like it`: good result, or close enough to reinforce. -- `Missing details`: the main idea is there, but smaller requested details are missing or changed. -- `Missing concept`: the output is usable, but the main requested thing did not show up. -- `Missing quality`: the idea may be present, but the result is too messy to count as good. -- `I don't like it`: bad result overall. +- `Perfect`: good result, or close enough to reinforce. +- `Missing details`: concept and quality are present, but smaller requested details are missing or changed. +- `Missing concept`: the output looks good, but characters, subjects, or actions are not doing what was requested. +- `Missing quality`: concept and details are attempted, but the result is visually messy. +- `Missing details + concept`: details and concept both need stronger prompt/LoRA support. +- `Missing details + quality`: details and quality both need stronger prompt/LoRA support. +- `Missing concept + quality`: concept and quality both need stronger prompt/LoRA support. +- `Awful`: details, concept, and quality are all missing. - `-Just forget it-`: do not learn from this run. Use it for bad seeds, broken references, interrupted generations, or workflow mistakes. -Older numeric workflows are still understood internally: `9-10` maps to `I like it`, `7-8` to `Missing details`, `5-6` to `Missing concept`, `3-4` to `Missing quality`, and `1-2` to `I don't like it`. +Older numeric workflows are still understood internally and mapped to the closest label. Older `I like it` and `I don't like it` values are accepted as aliases for `Perfect` and `Awful`. -Positive ratings are treated as direct reference updates. The node keeps the original upstream conditioning so it can detect prompt or conditioning changes, but once the first `I like it` / `9-10` result is rated, the active refinement reference switches to the conditioning that produced that liked result. Later liked ratings update that active reference as a running average. +Positive ratings are treated as direct reference updates. The node keeps the original upstream conditioning so it can detect prompt or conditioning changes, but once the first `Perfect` result is rated, the active refinement reference switches to the conditioning that produced that liked result. Later perfect ratings update that active reference as a running average. -`I don't like it` is the strongest push-back signal. When possible, the node rolls back to the latest conditioning in the prompt history that was rated higher, then adjusts away from the failed conditioning instead of continuing from it. +Missing ratings are corrective signals. They boost the named missing concepts and matching LoRAs instead of acting like hidden numeric scores. `Awful` means all three axes are missing and can roll back to the latest better conditioning before boosting the full prompt/LoRA support. **refinement_key**: Name of the refinement session. The node stores its learned refinement data under this key so you can continue training the same style later. diff --git a/docs/FunPackLoraWorkflow.md b/docs/FunPackLoraWorkflow.md index 2ff3d77f..7a34320d 100644 --- a/docs/FunPackLoraWorkflow.md +++ b/docs/FunPackLoraWorkflow.md @@ -62,11 +62,12 @@ The refiner compares each LoRA's type and filename with the concepts it extracte Examples: -- `I like it` gently reinforces matching LoRAs. -- `Missing details` nudges related concept, character, and general LoRAs upward. -- `Missing concept` favors the best concept or character match and can reduce weaker competing concept LoRAs. -- `Missing quality` can favor quality LoRAs and reduce unrelated concept LoRAs. -- `I don't like it` can mute or invert a LoRA if it keeps hurting the same prompt. +- `Perfect` gently reinforces matching LoRAs. +- `Missing details` nudges related concept, character, style, and general LoRAs upward. +- `Missing concept` boosts matching concept and character LoRAs. +- `Missing quality` boosts quality LoRAs, with small support from matching style/general LoRAs. +- Pair ratings boost both named axes in the same run. +- `Awful` boosts details, concept, and quality together. - `-Just forget it-` skips LoRA suggestion updates for that run. If `lora_stack` is not connected, conditioning, sigma, and latent refinement still work, but LoRA suggestions are not updated. diff --git a/docs/FunPackVideoRefiner.md b/docs/FunPackVideoRefiner.md index 33afd775..dfd7c07f 100644 --- a/docs/FunPackVideoRefiner.md +++ b/docs/FunPackVideoRefiner.md @@ -2,4 +2,4 @@ See [`FunPackGemmaEmbeddingRefiner.md`](FunPackGemmaEmbeddingRefiner.md) for the full documentation. The old filename is kept as a compatibility documentation path for users and workflows that still know the node by its former name. -Current reference behavior: original conditioning is kept for prompt/conditioning change detection, `I like it` / `9-10` switches active refinement to the liked generated conditioning average, and `I don't like it` rolls back to the latest better-rated conditioning before pushing away. +Current reference behavior: original conditioning is kept for prompt/conditioning change detection, `Perfect` switches active refinement to the liked generated conditioning average, and `Awful` can roll back to the latest better-rated conditioning before boosting missing details, concept, and quality support.