From c207091825ceb48553fdd36f1c04ff3cb28c82c7 Mon Sep 17 00:00:00 2001 From: Daniel Maddern Date: Thu, 13 Aug 2026 15:31:59 +0700 Subject: [PATCH] Report sampler replay boundaries --- tools/compare_fl2va_steps.py | 77 ++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/tools/compare_fl2va_steps.py b/tools/compare_fl2va_steps.py index 08ea018..cfd461d 100644 --- a/tools/compare_fl2va_steps.py +++ b/tools/compare_fl2va_steps.py @@ -1,4 +1,4 @@ -"""Compare direct FL2VA beta/RES steps with captured Comfy sampler state.""" +"""Compare direct FL2VA H3 and sampler boundaries with captured Comfy state.""" import glob @@ -6,48 +6,55 @@ import torch from h3_blackwell_runtime.checkpoint import H3Checkpoint from h3_blackwell_runtime.denoiser import H3PackedDenoiser -from h3_blackwell_runtime.packing import H3PromptPacker, unpatchify_video -from h3_blackwell_runtime.sampler import _audio_sigma, _unpack_audio, res_multistep_update +from h3_blackwell_runtime.packing import unpatchify_video +from h3_blackwell_runtime.sampler import res_multistep_update root = "/artifacts/fl2va-sampler-reference" +capture = "/artifacts/capture" initial = torch.load(f"{root}/initial.pt", map_location="cuda", weights_only=False) steps = [torch.load(path, map_location="cuda", weights_only=False) for path in sorted(glob.glob(f"{root}/step_*.pt"))] sigmas = initial["sigmas"].to("cuda") -checkpoint = H3Checkpoint("/models/minimax_h3_fl2va_pruned_nvfp4.safetensors") -captured_input = torch.load("/artifacts/capture/input.pt", map_location="cuda", weights_only=False) -text = captured_input["hidden"][:17].unsqueeze(0) -model = H3PackedDenoiser.from_checkpoint(checkpoint, attention_backend="sage2").eval() -packer = H3PromptPacker(checkpoint) - +model = H3PackedDenoiser.from_checkpoint( + H3Checkpoint("/models/minimax_h3_fl2va_pruned_nvfp4.safetensors"), attention_backend="sage2" +).eval() video_shape = (1, 24, 7, 12, 20) -audio_shape = (1, 32, 2, 37) video_count = torch.tensor(video_shape).prod().item() -old_video = old_audio = old_sigma = None +old_video = old_sigma = None + for index, reference in enumerate(steps): - sigma, sigma_down = sigmas[index], sigmas[index + 1] - packed_state = reference["x"].to("cuda").reshape(-1) - video = packed_state[:video_count].reshape(video_shape) - audio_carried = packed_state[video_count:].reshape(audio_shape) - native_audio = audio_carried * (_audio_sigma(sigma) / sigma) - hidden, times, segments, positions, video_segment, audio_segment = packer(text, video, native_audio, float(sigma)) - raw_video, raw_audio = model(hidden, times, positions, segments, video_segment, audio_segment) - velocity_video = -unpatchify_video(raw_video, video.shape[2], video.shape[-2], video.shape[-1]) - velocity_audio = -_unpack_audio(raw_audio) - carry = _audio_sigma(sigma) / sigma - velocity_audio = (1.0 - 4.0) * (audio_carried * carry) + (1.0 + 3.0 * _audio_sigma(sigma)) * velocity_audio - denoised = (video - sigma * velocity_video, audio_carried - sigma * velocity_audio) - reference_denoised = reference["denoised"].to("cuda").reshape(-1) - reference_denoised_video = reference_denoised[:video_count].reshape(video_shape) - denoised_delta = (denoised[0].float() - reference_denoised_video.float()).abs() - previous_sigma = sigmas[index - 1] if index else None - video = res_multistep_update(video, denoised[0], sigma, sigma_down, old_video, old_sigma, previous_sigma) - audio_carried = res_multistep_update(audio_carried, denoised[1], sigma, sigma_down, old_audio, old_sigma, previous_sigma) + h3_input = torch.load(f"{capture}/input_{index:02d}.pt", map_location="cuda", weights_only=False) + h3_output = torch.load(f"{capture}/output_{index:02d}.pt", map_location="cuda", weights_only=False) + with torch.inference_mode(): + raw_video, _ = model( + h3_input["hidden"].to("cuda"), + h3_input["timesteps"].to("cuda"), + h3_input["position_ids"].to("cuda"), + h3_input["segments"], + h3_output["video_segment"], + h3_output["audio_segment"], + ) + video_out = unpatchify_video(raw_video, h3_output["video"].shape[2], h3_output["video"].shape[3], h3_output["video"].shape[4]) + h3_delta = (video_out.float() - h3_output["video"].to("cuda").float()).abs() + + state_video = reference["x"].to("cuda").reshape(-1)[:video_count].reshape(video_shape) + reference_denoised = reference["denoised"].to("cuda").reshape(-1)[:video_count].reshape(video_shape) + converted_denoised = state_video + sigmas[index] * h3_output["video"].to("cuda") + denoised_delta = (converted_denoised.float() - reference_denoised.float()).abs() + if index + 1 < len(steps): - reference_latent = steps[index + 1]["x"].to("cuda").reshape(-1)[:video_count].reshape(video_shape) - latent_delta = (video.float() - reference_latent.float()).abs() - latent_text = f"latent_video_mean={latent_delta.mean().item():.6g} latent_video_max={latent_delta.max().item():.6g}" + previous_sigma = sigmas[index - 1] if index else None + updated = res_multistep_update(state_video, reference_denoised, sigmas[index], sigmas[index + 1], old_video, old_sigma, previous_sigma) + next_video = steps[index + 1]["x"].to("cuda").reshape(-1)[:video_count].reshape(video_shape) + update_delta = (updated.float() - next_video.float()).abs() + update_text = f"update_mean={update_delta.mean().item():.6g} update_max={update_delta.max().item():.6g}" else: - latent_text = "latent_video_mean=final-unobserved latent_video_max=final-unobserved" - print(f"step={index:02d} x0_video_mean={denoised_delta.mean().item():.6g} x0_video_max={denoised_delta.max().item():.6g} {latent_text}") - old_video, old_audio, old_sigma = denoised[0], denoised[1], sigma_down + update_text = "update_mean=final-unobserved update_max=final-unobserved" + + print( + f"step={index:02d} " + f"h3_mean={h3_delta.mean().item():.6g} h3_max={h3_delta.max().item():.6g} " + f"denoised_mean={denoised_delta.mean().item():.6g} denoised_max={denoised_delta.max().item():.6g} " + f"{update_text}" + ) + old_video, old_sigma = reference_denoised, sigmas[index + 1]