fix: output value function -- train on H3 too, not just LTXAV

explore_first_step reads the same output-space value function
output_guidance trains, but training/snapshotting was gated off for H3
entirely -- a decision made before explore_first_step existed, when
output_guidance (still correctly forced off there) was its only consumer.
Result: the value function could never reach MIN_SAMPLES on H3 no matter how
many generations were rated, so explore_first_step printed "not ready yet"
forever. Un-gate the snapshot save and the training call for H3; leave
output_guidance's own application (and the separate conditioning-payload VF
for embed_guidance/value_guidance) forced off as before -- that decision is
unaffected.

No new automated test: no existing fixture exercises this rating-commit code
path (FunPackVideoRefiner.run's post-scene training block) cheaply enough to
extend honestly; verified by full suite pass (1491/1491) and direct tracing
that no other call site re-introduces the gate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-09-22 14:43:33 +03:00
co-authored by Claude Sonnet 5
parent 68d1657a50
commit 77cf37e18d
2 changed files with 27 additions and 18 deletions
+20 -12
View File
@@ -10198,10 +10198,17 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
# value_guidance only controls APPLICATION (ascent below) - a user who runs with it # value_guidance only controls APPLICATION (ascent below) - a user who runs with it
# off still builds the VF, so enabling guidance later works immediately. # off still builds the VF, so enabling guidance later works immediately.
# #
# NOT on H3: nothing there reads either value function (value_guidance is forced # The conditioning-payload value function (below) is NOT trained on H3: it only
# off above; output_guidance is a sampler-side toggle forced off the same way), so # feeds embed_guidance/value_guidance/taste_nearest_prompt, all forced off there
# training them is pure waste that only helps a mechanism this session found does # (see self._is_h3 override in the sampler) because they were built and calibrated
# not help — h3_repr_steering has its own commit below, unconditional. # for LTXAV and have never been shown to do anything useful on H3 -- training it
# would be pure waste. The OUTPUT value function just below is a different story:
# explore_first_step reads it too (to select between candidate seeds), and that
# mechanism is not H3-excluded -- training it only for output_guidance's sake
# (also forced off on H3) silently starved explore_first_step of every sample it
# needed, permanently printing "not ready yet" no matter how much was rated. So
# only the conditioning-payload VF stays H3-gated; the output VF trains on every
# family. h3_repr_steering has its own commit below, unconditional either way.
if (has_previous_run and refinement_key and not learning_profile.get("skip_learning") if (has_previous_run and refinement_key and not learning_profile.get("skip_learning")
and self._v2_reward_admissible(learning_profile)): and self._v2_reward_admissible(learning_profile)):
if not _is_h3: if not _is_h3:
@@ -10212,14 +10219,15 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
) )
if n is not None: if n is not None:
print(f"[FunPackRefiner] Value function updated — {n} samples") print(f"[FunPackRefiner] Value function updated — {n} samples")
# output_guidance's value function, trained on the sampler's x0_snapshot from # output_guidance's value function, trained on the sampler's x0_snapshot from
# the run this rating scores (whole-run granularity — see # the run this rating scores (whole-run granularity — see
# _v2_train_output_value_function for why this sits at the single # _v2_train_output_value_function for why this sits at the single
# overall-reward call site, not the per-scene ones). # overall-reward call site, not the per-scene ones). Trained on every family
with self._v2_stage("output value function"): # (see the comment above this block) -- explore_first_step needs it on H3 too.
n_out = self._v2_train_output_value_function(refinement_key, float(learning_profile.get("reward", 0.0))) with self._v2_stage("output value function"):
if n_out is not None: n_out = self._v2_train_output_value_function(refinement_key, float(learning_profile.get("reward", 0.0)))
print(f"[FunPackRefiner] Output value function updated — {n_out} samples") if n_out is not None:
print(f"[FunPackRefiner] Output value function updated — {n_out} samples")
if _is_h3: if _is_h3:
try: try:
from . import h3_repr_steering as _rs from . import h3_repr_steering as _rs
+7 -6
View File
@@ -10224,18 +10224,19 @@ class FunPackLTXAVSceneChainSampler:
# silently skipped it, so the output value function NEVER received a sample on AV # silently skipped it, so the output value function NEVER received a sample on AV
# ("not ready yet (needs 10+)" forever). Snapshot the video stream (largest tensor), # ("not ready yet (needs 10+)" forever). Snapshot the video stream (largest tensor),
# matching the video-only convention of the in-flight guidance path. # matching the video-only convention of the in-flight guidance path.
# NOT gated on family any more: output_guidance's own APPLICATION is still forced off
# on H3 (self._is_h3 override above, untested/uncalibrated there), but explore_first_step
# reads this SAME value function to select between candidate seeds and is not
# H3-excluded -- skipping the snapshot here starved it permanently (0 samples, forever
# "not ready yet" regardless of rating count), the exact bug this comment used to cause
# for output_guidance on AV before the nested-tensor fix above.
if refinement_key_input and isinstance(output, dict): if refinement_key_input and isinstance(output, dict):
_snap = output.get("samples") _snap = output.get("samples")
if self._is_nested(_snap): if self._is_nested(_snap):
_parts = [t for t in _snap.unbind() if isinstance(t, torch.Tensor) and t.numel() > 0] _parts = [t for t in _snap.unbind() if isinstance(t, torch.Tensor) and t.numel() > 0]
_snap = max(_parts, key=lambda t: t.numel()) if _parts else None _snap = max(_parts, key=lambda t: t.numel()) if _parts else None
if isinstance(_snap, torch.Tensor): if isinstance(_snap, torch.Tensor):
if not self._is_h3: self._save_output_value_snapshot(refinement_key_input, _snap, None)
# NOT on H3: this snapshot only feeds output_guidance's value function,
# forced off for H3 (see self._is_h3 override above) — capturing it here
# anyway would just be banking video latents nothing ever reads, the same
# waste the "disable everything unrelated to REINS" pass was for.
self._save_output_value_snapshot(refinement_key_input, _snap, None)
# DynaShift pending candidate: same run/rating pairing as the snapshot, but # DynaShift pending candidate: same run/rating pairing as the snapshot, but
# the RAW video latent (fp16) — the rating decides whether it becomes a # the RAW video latent (fp16) — the rating decides whether it becomes a
# negative-bank entry, a positive-bank entry, or is discarded # negative-bank entry, a positive-bank entry, or is discarded