h3-blackwell-runtime/src/h3_blackwell_runtime/packing.py

207 lines
10 KiB
Python
Raw Normal View History

2026-08-12 14:12:42 +07:00
"""Direct prompt-only H3 packed-token construction."""
import math
import torch
import torch.nn.functional as functional
FRAME_PER_TOKEN = (1, 4, 4, 4, 4)
FRAME_RESCALE = 5.0 / 3.0
def patchify_video(latent: torch.Tensor) -> torch.Tensor:
batch, channels, frames, height, width = latent.shape
if batch != 1:
raise ValueError("H3 supports batch size one.")
if height % 2 or width % 2:
raise ValueError("H3 video latent dimensions must be divisible by two.")
return latent.reshape(batch, channels, frames, 1, height // 2, 2, width // 2, 2).permute(0, 2, 4, 6, 1, 3, 5, 7).reshape(-1, channels * 4)
def pack_audio(latent: torch.Tensor) -> torch.Tensor:
2026-08-13 00:55:58 +07:00
return latent[0].permute(1, 2, 0).reshape(-1, latent.shape[1]).transpose(0, 1).contiguous().transpose(0, 1)
2026-08-12 14:12:42 +07:00
def unpatchify_video(rows: torch.Tensor, frames: int, latent_height: int, latent_width: int) -> torch.Tensor:
height, width = latent_height // 2, latent_width // 2
x = rows.reshape(1, frames, height, width, 24, 1, 2, 2).permute(0, 4, 1, 5, 2, 6, 3, 7)
return x.reshape(1, 24, frames, latent_height, latent_width)
def _axis(dim: int, area: float) -> torch.Tensor:
ratio, count = dim / area, dim // 2
return (torch.arange(count, dtype=torch.float64) * ratio / count + (1 - ratio) / 2) * 32
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
def _frame_positions(height: int, width: int) -> torch.Tensor:
"""(t ignored) area-normalized (h, w) grid of one latent frame's 2x2-patch rows."""
area = math.sqrt(height * width)
ys, xs = torch.meshgrid(_axis(height, area), _axis(width, area), indexing="ij")
return torch.stack((ys.flatten(), xs.flatten()), dim=-1) # [frame_rows, 2]
2026-08-12 14:12:42 +07:00
def _video_positions(frames: int, height: int, width: int, offset: float) -> torch.Tensor:
area = math.sqrt(height * width)
ys, xs = torch.meshgrid(_axis(height, area), _axis(width, area), indexing="ij")
spatial = torch.stack((ys.flatten(), xs.flatten()), dim=-1)
spans = torch.tensor([FRAME_RESCALE * FRAME_PER_TOKEN[index % 5] for index in range(frames)], dtype=torch.float64)
times = offset + torch.cat((torch.zeros(1, dtype=torch.float64), spans[:-1].cumsum(0)))
result = torch.empty(frames, spatial.shape[0], 3, dtype=torch.float64)
result[:, :, 0], result[:, :, 1:] = times[:, None], spatial[None]
return result.reshape(-1, 3)
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
def _cond_positions(frames: int, cond_t: float, height: int, width: int) -> torch.Tensor:
spatial = _frame_positions(height, width)
g = torch.empty(spatial.shape[0], 3, dtype=torch.float64)
g[:, 0] = cond_t
g[:, 1:] = spatial
return g
def _video_t_spans(n: int) -> list[float]:
return [FRAME_RESCALE * FRAME_PER_TOKEN[k % 5] for k in range(n)]
2026-08-12 14:12:42 +07:00
def _audio_positions(steps: int, offset: float, width: int, height: int) -> torch.Tensor:
area = math.sqrt(height * width)
x_axis = _axis(width, area)
result = torch.zeros(steps * 2, 3, dtype=torch.float64)
result[:, 0] = (offset + torch.arange(steps, dtype=torch.float64)).repeat(2)
result[:steps, 2], result[steps:, 2] = x_axis[0], x_axis[-1]
return result
class H3PromptPacker:
"""Build `[text | audio | video]` tokens for prompt-only H3 T2V."""
def __init__(self, checkpoint):
2026-08-13 00:55:58 +07:00
self.video_weight = checkpoint.tensor("video_patch_proj.weight", dtype=torch.bfloat16).to(torch.float32)
self.video_bias = checkpoint.tensor("video_patch_proj.bias", dtype=torch.bfloat16).to(torch.float32)
self.audio_weight = checkpoint.tensor("audio_patch_proj.weight", dtype=torch.bfloat16).to(torch.float32)
self.audio_bias = checkpoint.tensor("audio_patch_proj.bias", dtype=torch.bfloat16).to(torch.float32)
2026-08-12 14:12:42 +07:00
self.text_weight = checkpoint.tensor("condition_proj.weight", dtype=torch.bfloat16)
self.text_bias = checkpoint.tensor("condition_proj.bias", dtype=torch.bfloat16)
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
VISUAL_COND_TIMESTEP = 0.999
2026-08-13 22:18:13 +07:00
def __call__(
self,
text: torch.Tensor,
video: torch.Tensor,
audio: torch.Tensor,
sigma: float | torch.Tensor,
2026-08-13 22:18:13 +07:00
model_timesteps: torch.Tensor | None = None,
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
*,
text_token_tags: torch.Tensor | None = None,
cond_latents: list[torch.Tensor] | None = None,
frame_count: int | None = None,
seed: int = 0,
) -> tuple[torch.Tensor, torch.Tensor, list[tuple[int, int, int]], torch.Tensor, tuple[int, int, int], tuple[int, int, int]]:
"""Build ``[text | cond | audio | video]`` packed rows for (fl2va) H3.
``text`` is the refined text span (width 5376 when already refined, 5120
for raw Qwen states); ``text_token_tags`` is the per-token DiT modality tag
(1=text, 0=video over vision pads). ``cond_latents`` are normalized keyframe
latents ``[1,24,1,H/16,W/16]`` spliced right after the text as non-denoised
cond rows with their own near-1 timestep. Returns
``(hidden, times, segments, positions, video_seg, audio_seg)`` where
``segments`` rows are ``t_row*3 + modality_tag``.
"""
2026-08-12 14:12:42 +07:00
if text.shape[-1] == 5120:
text_rows = functional.linear(text[0].to(self.text_weight.dtype), self.text_weight, self.text_bias).to(torch.bfloat16)
elif text.shape[-1] == 5376:
text_rows = text[0].to(torch.bfloat16)
else:
raise ValueError("H3 text states must be Qwen 5120-wide or refined 5376-wide.")
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
text_length = text_rows.shape[0]
latent_t, latent_h, latent_w = video.shape[2], video.shape[-2], video.shape[-1]
frame_rows = (latent_h // 2) * (latent_w // 2)
2026-08-13 21:29:52 +07:00
video_rows = functional.linear(patchify_video(video.to(torch.bfloat16)).float(), self.video_weight, self.video_bias).to(torch.bfloat16)
audio_rows = functional.linear(pack_audio(audio.to(torch.bfloat16)).float(), self.audio_weight, self.audio_bias).to(torch.bfloat16)
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
cond_rows = None
if cond_latents:
cond_patches = []
# every cond video restarts the same CPU RNG stream (Comfy _cond_video_rows)
for idx, z in enumerate(cond_latents):
r = patchify_video(z.to(torch.float32))
if self.VISUAL_COND_TIMESTEP < 1.0:
gen = torch.Generator("cpu").manual_seed(int(seed))
noise = torch.randn(r.shape, generator=gen, dtype=torch.float32)
r = self.VISUAL_COND_TIMESTEP * r + (1.0 - self.VISUAL_COND_TIMESTEP) * noise.to(r.device)
cond_patches.append(r)
cond_rows = functional.linear(torch.cat(cond_patches, dim=0), self.video_weight.to(torch.float32), self.video_bias.to(torch.float32)).to(torch.bfloat16)
2026-08-13 22:18:13 +07:00
if model_timesteps is None:
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
video_sigma = torch.as_tensor(sigma, device=text_rows.device, dtype=torch.float32).clamp(min=1e-6)
2026-08-13 22:18:13 +07:00
base = video_sigma / (12.0 + video_sigma * (1.0 - 12.0))
audio_sigma = 3.0 * base / (1.0 + (3.0 - 1.0) * base)
video_time, audio_time = (1.0 - video_sigma).item(), (1.0 - audio_sigma).item()
else:
Add direct first/last-frame (fl2va) keyframe conditioning Wire full fl2va into the direct H3 runtime so first/last keyframes flow through VAE encode -> Qwen vision tokens -> DiT cond segments: - vae_encoder.py: direct encoder-only H3 video VAE (causal 3D convs, reflect spatial padding, causal temporal padding, single-frame tap truncation, tiling, FP32 moments + mean/std normalization). - qwen3vl_vision.py: Qwen3-VL-32B visual tower (27 blocks, 2D rotary, deepstack mergers) ported to match the Comfy reference exactly (head_dim=72, no-bias proj, LayerNorm blocks, split-half apply_rope), plus Qwen image preprocess, mrope ids/freqs, DiT token tags, keyframe resize (first=stretch / last=center cover-crop matching Comfy common_upscale), and build_fl2va_presentation. - qwen3vl_text.py: split-half apply_rope, _embed_rows/_run_layers, optional mrope position_ids + DeepStack injection at the first three decoder layers at visual positions. - packing.py: H3PromptPacker builds [text | cond | audio | video] with tag-run text spans, cond rows (first/last cond_t anchors, VISUAL_COND_TIMESTEP=0.999 noise augmentation via CPU-seeded RNG), three-timestep row table (t_row*3 + modality_tag), and rope positions. - runtime.py: load VAE encoder + vision tower; generate() accepts first_frame/last_frame, builds the fl2va presentation, encodes keyframes, and passes text_token_tags/cond_latents/frame_count/seed to the sampler. - sampler.py: thread pack kwargs + seed. - serve_hot_runtime.py / direct_t2v_preview.py: /generate and --first-frame/--last-frame accept image paths or base64.
2026-08-19 20:17:41 +07:00
times_override = model_timesteps.to(device=text_rows.device, dtype=torch.float32).flatten()
unique_override = sorted(times_override.tolist())
video_time, audio_time = unique_override[0], unique_override[-1]
has_vis_cond = cond_rows is not None
cond_time = max(video_time, self.VISUAL_COND_TIMESTEP)
unique_times = sorted({video_time, audio_time} | ({cond_time} if has_vis_cond else set()))
t_row = {value: index for index, value in enumerate(unique_times)}
times = torch.tensor(unique_times, device=text_rows.device, dtype=torch.float32)
# Assemble hidden in segment order: text | cond | audio | video.
parts = [text_rows]
offsets = [0]
for block in ((cond_rows, "cond"), (audio_rows, "audio"), (video_rows, "video")):
if block[0] is None:
continue
parts.append(block[0])
offsets.append(offsets[-1] + block[0].shape[0])
hidden = torch.cat(parts, dim=0)
audio_length = audio_rows.shape[0]
cond_length = cond_rows.shape[0] if cond_rows is not None else 0
# Positions: text rows, cond (first/last t anchors), audio, video.
text_positions = torch.stack((torch.arange(text_length, dtype=torch.float64), torch.zeros(text_length), torch.zeros(text_length)), dim=-1)
position_blocks = [text_positions]
if cond_rows is not None and cond_latents:
spans = _video_t_spans(latent_t)
cond_t_values = [
float(text_length) if idx == 0 else (float(text_length) + sum(spans) - FRAME_RESCALE if frame_count is not None and idx == len(cond_latents) - 1 else float(text_length))
for idx in range(len(cond_latents))
]
position_blocks.append(torch.cat([_cond_positions(frame_rows, cond_t, latent_h, latent_w) for cond_t in cond_t_values], dim=0))
position_blocks.append(_audio_positions(audio.shape[-1], float(text_length + cond_length), latent_w, latent_h))
position_blocks.append(_video_positions(latent_t, latent_h, latent_w, float(text_length + cond_length + audio_length)))
positions = torch.cat(position_blocks, dim=0)
# mod_segments: (start, stop, t_row*3 + tag).
segments: list[tuple[int, int, int]] = []
if text_token_tags is not None:
tags = text_token_tags.view(-1).tolist()
row_base = t_row[video_time] * 3
run_start = 0
for i in range(1, text_length + 1):
if i == text_length or tags[i] != tags[run_start]:
segments.append((run_start, i, row_base + int(tags[run_start])))
run_start = i
else:
segments.append((0, text_length, t_row[video_time] * 3 + 1))
cursor_start = text_length
if cond_rows is not None:
segments.append((cursor_start, cursor_start + cond_rows.shape[0], t_row[cond_time] * 3 + 0))
cursor_start += cond_rows.shape[0]
segments.append((cursor_start, cursor_start + audio_length, t_row[audio_time] * 3 + 2))
cursor_start += audio_length
video_start = cursor_start
video_stop = video_start + video_rows.shape[0]
segments.append((video_start, video_stop, t_row[video_time] * 3 + 0))
video_segment = (video_start, video_stop, t_row[video_time])
audio_start = text_length + cond_length
audio_segment = (audio_start, audio_start + audio_length, t_row[audio_time])
return hidden, times, segments, positions, video_segment, audio_segment