Refiner V2 + Studio: add Absolute / Relative steer mode
Relative (default, unchanged) = per-prompt steering. Absolute = a keyless, prompt-agnostic "global taste" prior that pulls conditioning toward generally- liked directions regardless of the prompt; Both layers them. - New keyless Absolute store (FUNPACK_ABSOLUTE_KEY): pooled liked/disliked direction + global value function, learned across every rated generation. - Absolute rating semantics: a rating means "details I like in general" (not prompt adherence) — reward-driven liked/bad-dir storage, axis attribution bypassed. Perfect = "more of this everywhere". - _v2_finalize_conditioning replaces the per-key ascend hook on both returns; pure Absolute also bypasses per-prompt conditioning memory. - Scene Chain sampler: embed_guidance_source (relative/absolute) lets the per-step nudge draw from the global direction/value function. - Studio popup + V2 node expose Steer mode + Absolute strength. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ea602f9de0
commit
c823d5bf3e
@@ -4,6 +4,8 @@
|
||||
|
||||
### Added
|
||||
|
||||
Added an **Absolute / Relative steer mode** to Refiner V2 and Studio. *Relative* (the default, unchanged behaviour) is per-prompt: it learns and applies the best conditioning for one specific prompt. *Absolute* is prompt-agnostic — it accumulates a single global "taste" prior across **every** rated generation and pulls conditioning toward it regardless of the prompt. In Absolute mode a rating means "this has (or lacks) details I like **in general**", not prompt adherence: a Perfect means "I love this, give me more of it everywhere", and a low rating means "this is missing the details I like in general". The pull layers two engines — a pooled liked/disliked direction (the learned, automated analogue of the manual Conditioning Adjust phrase shift) and the keyless value function on top — and is applied at the conditioning output (`_v2_finalize_conditioning`). *Both* keeps the per-prompt fit and layers the global prior under it; pure *Absolute* bypasses per-prompt memory entirely. The global store learns from rated runs even with no refinement key wired (it is keyless by design). The Scene Chain sampler's `embed_guidance` gains an `embed_guidance_source` (`relative` / `absolute`) so the per-step nudge can draw from the global taste direction too. Studio exposes `Steer mode` + `Absolute strength` in the refiner panel.
|
||||
|
||||
Ported **velocity bias + reactive rescue** to the `FunPack Distilled Flow` sampler (`sample_funpack_distilled_flow`), reusing the same capture/memory/rescue machinery as the Hybrid Euler-2S and LTXAV/RF samplers. The sampler now exposes `velocity_bias_mode`, `velocity_bias_strength`, `velocity_bias_source`, `velocity_refinement_key`, `rescue_mode`, `rescue_threshold`, and `rescue_strength`, and these are wired into Studio's **Distilled Flow** sampler config panel (with the same blank/`default` velocity-key → wired-refinement-key fallback the Hybrid panel uses). Steering is applied magnitude-preservingly and, on packed LTXAV latents, confined to the video stream (audio-safe). Note: few-step distilled schedules may only land on one or two velocity targets, so apply/rescue fire less often than on an 8-step run; they no-op cleanly when no target matches. With everything off the sampler is byte-identical to the previous deterministic ODE.
|
||||
|
||||
## [2.7.7] - 2026-06-03
|
||||
|
||||
+210
-21
@@ -360,6 +360,12 @@ def update_refinement_sampler_context(refinement_key, sampler_context):
|
||||
print(f"[FunPack] update_refinement_sampler_context: failed to save context: {e}")
|
||||
|
||||
|
||||
# Fixed, prompt-agnostic key for the Absolute steering store. Unlike every other refinement
|
||||
# key (which hashes a specific prompt), this one collects liked/disliked directions across
|
||||
# ALL prompts into a single "global taste" file. Relative mode = per-prompt; Absolute = this.
|
||||
FUNPACK_ABSOLUTE_KEY = "__funpack_absolute_global__"
|
||||
|
||||
|
||||
def refinement_state_path(refinement_key, mode, prefix="refine", extension="json"):
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
refinements_dir = os.path.join(base_dir, "refinements")
|
||||
@@ -5950,6 +5956,16 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
"latent": ("LATENT", {
|
||||
"tooltip": "Optional video latent for creativity masking. Takes priority over any saved latent for this key. Connect your i2v or previous KSampler output here.",
|
||||
}),
|
||||
"steer_mode": (["relative", "absolute", "both"], {
|
||||
"default": "relative",
|
||||
"label": "Steer Mode",
|
||||
"tooltip": "Relative: per-prompt steering (finds the best conditioning for THIS prompt — default). Absolute: pull conditioning toward the global learned taste, regardless of prompt. Both: layer them.",
|
||||
}),
|
||||
"absolute_strength": ("FLOAT", {
|
||||
"default": 0.6, "min": 0.0, "max": 2.0, "step": 0.05,
|
||||
"label": "Absolute Strength",
|
||||
"tooltip": "How hard Absolute mode pulls toward the global taste direction. 0.6 is visible but non-destructive; raise for stronger override of the prompt.",
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11371,6 +11387,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
advisor_clip=None, feedback_prompt="", prompt_repair=True, temporal_style="natural",
|
||||
split_by_transitions=False, split_transition_placement="start", reference_injection=False,
|
||||
value_guidance=True, latent=None, seed_output_connected=False,
|
||||
steer_mode="relative", absolute_strength=0.6,
|
||||
_seed=None, _seed_source="fresh seed", _scene_seeds=None, _velocity_keys=None,
|
||||
batch_variants=1):
|
||||
seed = int(_seed) if _seed is not None else random.randint(1, 0xffffffffffffffff)
|
||||
@@ -11490,25 +11507,17 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
# value_guidance only controls APPLICATION (ascent below) — a user who runs with it
|
||||
# off still builds the VF, so enabling guidance later works immediately.
|
||||
if has_previous_run and refinement_key and not learning_profile.get("skip_learning"):
|
||||
try:
|
||||
import torch as _torch
|
||||
try:
|
||||
from .value_function import OnlineValueFunction
|
||||
except ImportError:
|
||||
from value_function import OnlineValueFunction
|
||||
payload = (previous_run or {}).get("conditioning")
|
||||
if isinstance(payload, dict):
|
||||
with _torch.inference_mode(False), _torch.enable_grad():
|
||||
cond_tensor = serializable_to_tensor(payload).clone()
|
||||
reward = float(learning_profile.get("reward", 0.0))
|
||||
vf_path = refinement_state_path(refinement_key, "value_fn", prefix="refine_v2", extension="pt")
|
||||
vf = OnlineValueFunction.load_or_create(vf_path, hidden_dim=cond_tensor.shape[-1])
|
||||
if vf is not None:
|
||||
vf.train_on(cond_tensor, reward)
|
||||
vf.save(vf_path)
|
||||
print(f"[FunPackRefiner] Value function updated — {vf.n_trained} samples, buffer={len(vf.buffer_c)}")
|
||||
except Exception as e:
|
||||
print(f"[FunPackRefiner] Value function training failed: {e}")
|
||||
n = self._v2_train_value_function(
|
||||
refinement_state_path(refinement_key, "value_fn", prefix="refine_v2", extension="pt"),
|
||||
(previous_run or {}).get("conditioning"),
|
||||
float(learning_profile.get("reward", 0.0)),
|
||||
)
|
||||
if n is not None:
|
||||
print(f"[FunPackRefiner] Value function updated — {n} samples")
|
||||
# Absolute store: the same rating also feeds the keyless, prompt-agnostic taste prior.
|
||||
# Runs even with no refinement_key (Absolute is global), so standalone runs still build it.
|
||||
if has_previous_run:
|
||||
self._v2_learn_absolute(previous_run, learning_profile)
|
||||
if has_previous_run and not learning_profile.get("skip_learning"):
|
||||
self._v2_update_streaks(global_state, learning_profile, update_conditioning_strength=not prompt_only_mode)
|
||||
repair_feedback, repair_persistence_status = self._v2_active_repair_feedback(
|
||||
@@ -11771,6 +11780,13 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
elif prompt_only_mode:
|
||||
refined = cond
|
||||
adaptation_status = "Adaptation: Prompt only mode; conditioning vectors passed through unchanged."
|
||||
elif str(steer_mode).lower() == "absolute":
|
||||
# Pure Absolute: skip the per-prompt conditioning memory entirely. Steering happens
|
||||
# only via the keyless global-taste pull in _v2_finalize_conditioning, so the result
|
||||
# is moved toward general preferences "despite the prompt". (Both keeps per-prompt
|
||||
# memory and layers the global pull on top.)
|
||||
refined = cond
|
||||
adaptation_status = "Adaptation: Absolute mode; per-prompt memory bypassed, global taste pull applied at output."
|
||||
else:
|
||||
_current_final = [t for t in (prompt_to_encode or "").split(",") if len(t.strip()) > 1]
|
||||
_prev_final = previous_run.get("final_phrase_texts", []) if isinstance(previous_run, dict) else []
|
||||
@@ -12214,7 +12230,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
repair_feedback, current_family_slot, _vf_for_memory, _concept_dir,
|
||||
_concept_strength, _current_final)
|
||||
return (
|
||||
self._v2_ascend_conditioning(output_conditioning, refinement_key, apply=value_guidance),
|
||||
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength),
|
||||
status,
|
||||
training_info,
|
||||
loss_graph,
|
||||
@@ -12232,7 +12248,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
repair_feedback, current_family_slot, _vf_for_memory, _concept_dir,
|
||||
_concept_strength, _current_final)
|
||||
return (
|
||||
self._v2_ascend_conditioning(output_conditioning, refinement_key, apply=value_guidance),
|
||||
self._v2_finalize_conditioning(output_conditioning, refinement_key, value_guidance, steer_mode, absolute_strength),
|
||||
status + enhancement_status,
|
||||
training_info,
|
||||
loss_graph,
|
||||
@@ -12379,6 +12395,177 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
print(f"[FunPackRefiner] Conditioning ascent failed: {e}")
|
||||
return conditioning_list
|
||||
|
||||
# ====================== ABSOLUTE STEERING (prompt-agnostic) ======================
|
||||
# Relative steering (everything above) keys every learned direction to one prompt's
|
||||
# state file. Absolute steering accumulates the SAME pooled, channel-space directions
|
||||
# into a single keyless file so they describe a global "taste" that applies to any
|
||||
# prompt. It is the learned, automated analogue of FunPackConditioningAdjust: instead
|
||||
# of a user typing a phrase + strength, the direction is the running average of what
|
||||
# was rated liked (vs. the global source mean), applied as a broadcast shift.
|
||||
|
||||
def _v2_absolute_state_path(self):
|
||||
return refinement_state_path(FUNPACK_ABSOLUTE_KEY, "clip", prefix=self.V2_STATE_PREFIX)
|
||||
|
||||
def _v2_absolute_vf_path(self):
|
||||
return refinement_state_path(FUNPACK_ABSOLUTE_KEY, "value_fn", prefix=self.V2_STATE_PREFIX, extension="pt")
|
||||
|
||||
def _v2_load_absolute_global(self):
|
||||
"""Load the keyless Absolute store and return (full_state, global_dict)."""
|
||||
path = self._v2_absolute_state_path()
|
||||
state = None
|
||||
if os.path.exists(path):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
state = json.load(f)
|
||||
except (json.JSONDecodeError, OSError, ValueError):
|
||||
state = None
|
||||
if not isinstance(state, dict):
|
||||
state = {"version": 2, "global": {}}
|
||||
global_state = state.setdefault("global", {})
|
||||
global_state.setdefault("axis_conditioning_memory", {})
|
||||
return state, global_state
|
||||
|
||||
def _v2_save_absolute_global(self, state):
|
||||
path = self._v2_absolute_state_path()
|
||||
try:
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f)
|
||||
except OSError as e:
|
||||
print(f"[FunPackRefiner] Absolute store save failed: {e}")
|
||||
|
||||
def _v2_train_value_function(self, vf_path, payload, reward):
|
||||
"""Add one (conditioning, reward) sample to the value function at vf_path. Shared by
|
||||
the per-key (relative) and keyless (absolute) reward assets. Returns n_trained or None."""
|
||||
if not isinstance(payload, dict):
|
||||
return None
|
||||
try:
|
||||
import torch as _torch
|
||||
try:
|
||||
from .value_function import OnlineValueFunction
|
||||
except ImportError:
|
||||
from value_function import OnlineValueFunction
|
||||
with _torch.inference_mode(False), _torch.enable_grad():
|
||||
cond_tensor = serializable_to_tensor(payload).clone()
|
||||
vf = OnlineValueFunction.load_or_create(vf_path, hidden_dim=cond_tensor.shape[-1])
|
||||
if vf is not None:
|
||||
vf.train_on(cond_tensor, float(reward))
|
||||
vf.save(vf_path)
|
||||
return vf.n_trained
|
||||
except Exception as e:
|
||||
print(f"[FunPackRefiner] Value function training failed: {e}")
|
||||
return None
|
||||
|
||||
def _v2_learn_absolute(self, previous_run, learning_profile):
|
||||
"""Fold the just-rated generation into the prompt-agnostic Absolute store.
|
||||
|
||||
Rating semantics differ from Relative here. Relative asks "does this match the
|
||||
prompt?" and attributes credit per missing/satisfied axis. Absolute asks "do I like
|
||||
these details IN GENERAL, regardless of the prompt?" — so we deliberately IGNORE the
|
||||
prompt-specific axis attribution and steer purely on the overall reward:
|
||||
* a high rating ("Perfect" = I love this and want more of this in general) pushes
|
||||
the global liked direction toward this generation;
|
||||
* a low rating ("missing details I like in general") pushes the disliked direction.
|
||||
Pooled across every prompt, this converges on a single global-taste prior. Also trains
|
||||
the keyless value function. Runs independent of refinement_key — Absolute is keyless."""
|
||||
if not isinstance(previous_run, dict) or learning_profile.get("skip_learning"):
|
||||
return
|
||||
payload = previous_run.get("conditioning")
|
||||
if not isinstance(payload, dict):
|
||||
return
|
||||
reward = float(learning_profile.get("reward", 0.0))
|
||||
try:
|
||||
state, global_state = self._v2_load_absolute_global()
|
||||
self._v2_update_session_mean(global_state, previous_run.get("source_conditioning"))
|
||||
session_mean = global_state.get("session_source_mean")
|
||||
if reward >= 0.3:
|
||||
# "more of this, generally"
|
||||
self._v2_store_direction(global_state.setdefault("liked_dir", {}), payload, session_mean)
|
||||
elif reward <= 0.0:
|
||||
# "this lacks the details I like, generally"
|
||||
self._v2_store_direction(global_state.setdefault("bad_dir", {}), payload, session_mean)
|
||||
# the mild-positive band (0, 0.3) is too ambiguous to move either pole
|
||||
global_state["total_iterations"] = int(global_state.get("total_iterations", 0)) + 1
|
||||
self._v2_save_absolute_global(state)
|
||||
except Exception as e:
|
||||
print(f"[FunPackRefiner] Absolute learning failed: {e}")
|
||||
n = self._v2_train_value_function(self._v2_absolute_vf_path(), payload, reward)
|
||||
if n is not None:
|
||||
print(f"[FunPackRefiner] Absolute value function updated — {n} samples")
|
||||
|
||||
def _v2_apply_absolute(self, conditioning_list, strength, value_blend=True):
|
||||
"""Pull every conditioning entry toward the global learned taste direction, regardless
|
||||
of prompt. Two layered engines: (1) the pooled liked direction (minus a global bad-dir
|
||||
repel), applied as a calibrated broadcast shift via _v2_apply_direction; (2) the keyless
|
||||
value-function ascent on top for the nonlinear component. No-op until each engine has
|
||||
enough data (liked/bad need direction_count>=3; VF needs MIN_SAMPLES)."""
|
||||
if not conditioning_list or strength <= 0:
|
||||
return conditioning_list
|
||||
try:
|
||||
_, global_state = self._v2_load_absolute_global()
|
||||
except Exception:
|
||||
return conditioning_list
|
||||
liked = global_state.get("liked_dir", {})
|
||||
bad = global_state.get("bad_dir", {})
|
||||
liked_ready = int(liked.get("direction_count", 0)) >= 3
|
||||
bad_ready = int(bad.get("direction_count", 0)) >= 3
|
||||
|
||||
gvf = None
|
||||
if value_blend:
|
||||
try:
|
||||
import torch as _torch
|
||||
try:
|
||||
from .value_function import OnlineValueFunction
|
||||
except ImportError:
|
||||
from value_function import OnlineValueFunction
|
||||
vf_path = self._v2_absolute_vf_path()
|
||||
if os.path.exists(vf_path):
|
||||
with _torch.inference_mode(False):
|
||||
candidate = OnlineValueFunction.load(vf_path)
|
||||
gvf = candidate if candidate.is_ready() else None
|
||||
except Exception:
|
||||
gvf = None
|
||||
|
||||
if not (liked_ready or bad_ready or gvf is not None):
|
||||
return conditioning_list
|
||||
|
||||
out = []
|
||||
applied = False
|
||||
for entry in conditioning_list:
|
||||
if not (isinstance(entry, (list, tuple)) and len(entry) >= 2 and isinstance(entry[0], torch.Tensor)):
|
||||
out.append(entry)
|
||||
continue
|
||||
mixed, extra = entry[0], entry[1]
|
||||
if liked_ready:
|
||||
mixed = self._v2_apply_direction(mixed, liked, strength)
|
||||
if bad_ready:
|
||||
mixed = self._v2_apply_direction(mixed, bad, strength * 0.5, negate=True)
|
||||
if gvf is not None:
|
||||
try:
|
||||
mixed = gvf.ascend(mixed)
|
||||
except Exception:
|
||||
pass
|
||||
out.append((mixed, extra))
|
||||
applied = True
|
||||
if applied:
|
||||
print(
|
||||
f"[FunPackRefiner] Absolute steer applied (strength={strength}, "
|
||||
f"liked={liked_ready}, bad={bad_ready}, value_fn={'on' if gvf is not None else 'off'})"
|
||||
)
|
||||
return out
|
||||
|
||||
def _v2_finalize_conditioning(self, conditioning_list, refinement_key, value_guidance,
|
||||
steer_mode, absolute_strength):
|
||||
"""Single output hook for both steering modes. Relative = per-key VF ascend (current
|
||||
behaviour). Absolute = global taste pull. Both = layer them."""
|
||||
mode = str(steer_mode or "relative").lower()
|
||||
out = conditioning_list
|
||||
if mode in ("relative", "both"):
|
||||
out = self._v2_ascend_conditioning(out, refinement_key, apply=value_guidance)
|
||||
if mode in ("absolute", "both"):
|
||||
out = self._v2_apply_absolute(out, float(absolute_strength))
|
||||
return out
|
||||
|
||||
|
||||
class FunPackSaveRefinementLatent:
|
||||
CATEGORY = "FunPack/Refinement"
|
||||
@@ -13576,6 +13763,8 @@ class FunPackStudio:
|
||||
split_transition_placement=split_transition_placement,
|
||||
reference_injection=reference_injection,
|
||||
value_guidance=value_guidance,
|
||||
steer_mode=str(rf.get("steer_mode", "relative") or "relative"),
|
||||
absolute_strength=float(rf.get("absolute_strength", 0.6) or 0.6),
|
||||
latent=latent,
|
||||
seed_output_connected=seed_output_connected,
|
||||
_seed=seed,
|
||||
|
||||
+29
-10
@@ -1694,6 +1694,10 @@ class FunPackLTXAVSceneChainSampler:
|
||||
"default": 0.02, "min": 0.005, "max": 0.1, "step": 0.005,
|
||||
"tooltip": "Per-step nudge strength toward the liked conditioning direction. Keep small — the direction is applied at every step so it compounds. 0.01-0.03 is typical.",
|
||||
}),
|
||||
"embed_guidance_source": (["relative", "absolute"], {
|
||||
"default": "relative",
|
||||
"tooltip": "Which learned direction embed_guidance steers toward. Relative: this prompt's liked direction (needs refinement_key_input). Absolute: the global, prompt-agnostic taste direction the Refiner accumulates across all prompts — works with no key.",
|
||||
}),
|
||||
"transition_duration": ("INT", {
|
||||
"default": 16, "min": 0, "max": 128, "step": 2,
|
||||
"tooltip": "Extra pixel frames of fade beyond the blend zone on each side of a scene boundary. 0 = disable all transition effects.",
|
||||
@@ -2163,6 +2167,14 @@ class FunPackLTXAVSceneChainSampler:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _absolute_key(self):
|
||||
"""The keyless global-taste key for Absolute embed_guidance (mirrors the Refiner)."""
|
||||
try:
|
||||
from .conditioning import FUNPACK_ABSOLUTE_KEY
|
||||
except ImportError:
|
||||
from conditioning import FUNPACK_ABSOLUTE_KEY
|
||||
return FUNPACK_ABSOLUTE_KEY
|
||||
|
||||
def _load_value_function(self, refinement_key):
|
||||
"""Load the online value function if trained and ready."""
|
||||
try:
|
||||
@@ -2314,6 +2326,7 @@ class FunPackLTXAVSceneChainSampler:
|
||||
carry_i2v_guides=False,
|
||||
mid_scene_guide=False, mid_scene_guide_strength=0.4,
|
||||
embed_guidance=False, embed_guidance_strength=0.02,
|
||||
embed_guidance_source="relative",
|
||||
transition_duration=16, decode_tile_size=0,
|
||||
refinement_key_input="", unique_id=None, prompt=None):
|
||||
if not isinstance(positive, list) or not positive:
|
||||
@@ -2330,7 +2343,7 @@ class FunPackLTXAVSceneChainSampler:
|
||||
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed,
|
||||
carry_i2v_guides, mid_scene_guide, mid_scene_guide_strength,
|
||||
embed_guidance, embed_guidance_strength, transition_duration,
|
||||
decode_tile_size, refinement_key_input,
|
||||
decode_tile_size, refinement_key_input, embed_guidance_source,
|
||||
)
|
||||
|
||||
max_scene_count = max(1, int(max_scenes))
|
||||
@@ -2346,21 +2359,25 @@ class FunPackLTXAVSceneChainSampler:
|
||||
boundary_entries = []
|
||||
cumulative_latent_frames = 0
|
||||
|
||||
# Load liked direction once for embed_guidance
|
||||
# Load liked direction once for embed_guidance. Source selects which learned direction:
|
||||
# 'relative' = this prompt's key; 'absolute' = the global, prompt-agnostic taste store
|
||||
# (keyless, so it works even without refinement_key_input).
|
||||
_liked_dir = None
|
||||
_value_fn = None
|
||||
if embed_guidance and refinement_key_input:
|
||||
_liked_dir = self._load_liked_direction(refinement_key_input)
|
||||
_eg_source = str(embed_guidance_source or "relative").lower()
|
||||
_eg_key = self._absolute_key() if _eg_source == "absolute" else refinement_key_input
|
||||
if embed_guidance and _eg_key:
|
||||
_liked_dir = self._load_liked_direction(_eg_key)
|
||||
if _liked_dir is None:
|
||||
print("[FunPackSceneChain] embed_guidance: no liked direction found (need 3+ liked generations)")
|
||||
print(f"[FunPackSceneChain] embed_guidance ({_eg_source}): no liked direction found (need 3+ liked generations)")
|
||||
else:
|
||||
_value_fn = self._load_value_function(refinement_key_input)
|
||||
_value_fn = self._load_value_function(_eg_key)
|
||||
if _value_fn:
|
||||
ready = _value_fn.is_ready()
|
||||
mode = f"value function ({_value_fn.n_trained} samples, ascent {'on' if ready else 'pending'})"
|
||||
else:
|
||||
mode = "fixed direction"
|
||||
print(f"[FunPackSceneChain] embed_guidance: active via {mode}, strength={embed_guidance_strength}")
|
||||
print(f"[FunPackSceneChain] embed_guidance ({_eg_source}): active via {mode}, strength={embed_guidance_strength}")
|
||||
|
||||
first_scene_seed = self._scene_seed(scene_conditionings[0])
|
||||
if first_scene_seed is None:
|
||||
@@ -2560,7 +2577,7 @@ class FunPackLTXAVSceneChainSampler:
|
||||
latent_template, num_frames_per_scene, frame_overlap, cfg, max_scenes,
|
||||
use_same_seed, carry_i2v_guides, mid_scene_guide, mid_scene_guide_strength,
|
||||
embed_guidance, embed_guidance_strength, transition_duration,
|
||||
decode_tile_size, refinement_key_input):
|
||||
decode_tile_size, refinement_key_input, embed_guidance_source="relative"):
|
||||
"""Sample one chain per Studio-packed variant entry (seed + index), persisting each result
|
||||
(latent + preview + per-entry cond + manifest) under ComfyUI temp for rating in Studio.
|
||||
Reuses sample() per entry with only the seed changed, so each entry is a clean generation."""
|
||||
@@ -2573,7 +2590,8 @@ class FunPackLTXAVSceneChainSampler:
|
||||
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed=use_same_seed,
|
||||
carry_i2v_guides=carry_i2v_guides, mid_scene_guide=mid_scene_guide,
|
||||
mid_scene_guide_strength=mid_scene_guide_strength, embed_guidance=embed_guidance,
|
||||
embed_guidance_strength=embed_guidance_strength, transition_duration=transition_duration,
|
||||
embed_guidance_strength=embed_guidance_strength, embed_guidance_source=embed_guidance_source,
|
||||
transition_duration=transition_duration,
|
||||
decode_tile_size=decode_tile_size, refinement_key_input="",
|
||||
)
|
||||
stamp = _time.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
@@ -2597,7 +2615,8 @@ class FunPackLTXAVSceneChainSampler:
|
||||
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed=use_same_seed,
|
||||
carry_i2v_guides=carry_i2v_guides, mid_scene_guide=mid_scene_guide,
|
||||
mid_scene_guide_strength=mid_scene_guide_strength, embed_guidance=embed_guidance,
|
||||
embed_guidance_strength=embed_guidance_strength, transition_duration=transition_duration,
|
||||
embed_guidance_strength=embed_guidance_strength, embed_guidance_source=embed_guidance_source,
|
||||
transition_duration=transition_duration,
|
||||
decode_tile_size=decode_tile_size, refinement_key_input=key,
|
||||
unique_id=None, prompt=None,
|
||||
)
|
||||
|
||||
+21
-1
@@ -69,7 +69,7 @@ function defaultSettings() {
|
||||
refinement_key: "",
|
||||
overrides: { refinement_key: false, feedback_prompt: false, user_intent_prompt: false, negative_prompt: false },
|
||||
scene_builder: { mode: "Pass-through", scene: NONE_SENTINEL, scene_name: "", aliases: "", scene_positive: "", scene_negative: "" },
|
||||
refiner: { mode: "Refine", advisor_mode: "Off", advisor_thinking: true, prompt_repair: true, im_feeling_lucky: false, reset_session: false, feedback_prompt: "", user_intent_prompt_override: "", negative_prompt: "", temporal_style: "natural", split_by_transitions: false, split_transition_placement: "start", reference_injection: false, vision_conditioning: true, value_guidance: true },
|
||||
refiner: { mode: "Refine", advisor_mode: "Off", advisor_thinking: true, prompt_repair: true, im_feeling_lucky: false, reset_session: false, feedback_prompt: "", user_intent_prompt_override: "", negative_prompt: "", temporal_style: "natural", split_by_transitions: false, split_transition_placement: "start", reference_injection: false, vision_conditioning: true, value_guidance: true, steer_mode: "relative", absolute_strength: 0.6 },
|
||||
advisor_llm: { enabled: false, model_path: "huihui-ai/Huihui-Qwen3-8B-abliterated-v2", dtype: "bfloat16" },
|
||||
loras: [],
|
||||
loras_config: { mode: "ltx2", per_block: false },
|
||||
@@ -1204,6 +1204,26 @@ function openPanel(node) {
|
||||
valueGuidanceToggle.inp.addEventListener("change", () => { settings.refiner.value_guidance = valueGuidanceToggle.inp.checked; });
|
||||
body.append(el("div", "funpack-studio-hint", "The value function always trains in the background on rated generations (cheap, no extra diffusion). This toggle applies the learned reward direction to conditioning (ascent + search) once 10+ gens are rated — on by default. Turn off for strict prompt fidelity; learning still accumulates either way. The chain sampler's 'embed_guidance' uses the same trained function per-step."));
|
||||
body.append(row("Value guidance", valueGuidanceToggle.wrap));
|
||||
|
||||
if (!settings.refiner.steer_mode) settings.refiner.steer_mode = "relative";
|
||||
const steerSelect = selectEl(["relative", "absolute", "both"], settings.refiner.steer_mode);
|
||||
steerSelect.addEventListener("change", () => { settings.refiner.steer_mode = steerSelect.value; });
|
||||
body.append(el("div", "funpack-studio-hint",
|
||||
"Relative: per-prompt steering — finds the best conditioning for THIS prompt (the default; learns from prompt-specific ratings). " +
|
||||
"Absolute: pulls conditioning toward a global learned taste that applies to ANY prompt; in this mode a rating means 'this has/lacks details I like in general' (Perfect = 'I love this, give me more of it everywhere'), independent of the prompt. " +
|
||||
"Both: layer the global taste prior under the per-prompt fit."));
|
||||
body.append(row("Steer mode", steerSelect));
|
||||
|
||||
if (settings.refiner.absolute_strength === undefined) settings.refiner.absolute_strength = 0.6;
|
||||
const absStrengthInput = numInput(settings.refiner.absolute_strength, 0.0, 2.0, 0.05);
|
||||
absStrengthInput.addEventListener("change", () => {
|
||||
const v = parseFloat(absStrengthInput.value);
|
||||
settings.refiner.absolute_strength = Number.isFinite(v) ? Math.max(0, Math.min(2, v)) : 0.6;
|
||||
});
|
||||
body.append(el("div", "funpack-studio-hint",
|
||||
"How hard Absolute/Both pull toward the global taste direction. 0.6 is visible but non-destructive; raise to override the prompt more strongly."));
|
||||
body.append(row("Absolute strength", absStrengthInput));
|
||||
|
||||
const vfActiveKey = () => settings.refinement_key || linkedRefinementKey(node);
|
||||
const vfExportBtn = btn("Export", "secondary");
|
||||
const vfImportBtn = btn("Import", "secondary");
|
||||
|
||||
Reference in New Issue
Block a user