curvedinf's picture
download
raw
27.7 kB
#!/usr/bin/env python3
from __future__ import annotations
import argparse
from bisect import bisect_right
from concurrent.futures import ProcessPoolExecutor, as_completed
import functools
import json
import math
import os
from pathlib import Path
import re
import shutil
import subprocess
import time
import unicodedata
import cv2
import matplotlib
import numpy as np
ROOT = Path(__file__).resolve().parents[1]
def log(msg: str) -> None:
ts = time.strftime("%H:%M:%S")
print(f"[{ts}] {msg}", flush=True)
def resolve_path(p: str) -> Path:
path = Path(p)
if path.is_absolute():
return path
return (ROOT / path).resolve()
def parse_cache_steps(cache_dir: Path) -> tuple[list[str], list[int], list[Path], list[Path]]:
uv = {}
c4 = {}
uv_re = re.compile(r"^uv_step(\d+)\.npy$")
c4_re = re.compile(r"^c4_step(\d+)\.npy$")
for p in cache_dir.glob("uv_step*.npy"):
m = uv_re.match(p.name)
if m:
uv[m.group(1)] = p.resolve()
for p in cache_dir.glob("c4_step*.npy"):
m = c4_re.match(p.name)
if m:
c4[m.group(1)] = p.resolve()
steps = sorted(set(uv.keys()) & set(c4.keys()), key=lambda s: int(s))
if len(steps) < 2:
raise RuntimeError(f"Need >=2 matching cache steps in {cache_dir}, found {len(steps)}")
step_vals = [int(s) for s in steps]
uv_paths = [uv[s] for s in steps]
c4_paths = [c4[s] for s in steps]
return steps, step_vals, uv_paths, c4_paths
VAL_LOSS_RE = re.compile(r"\bstep:(\d+)/\d+\b.*?\bval_loss:([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)\b")
def parse_stepwise_val_loss_points(log_path: Path) -> tuple[list[int], list[float]]:
if not log_path.exists():
return [], []
lines = log_path.read_text(encoding="utf-8", errors="ignore").splitlines()
by_step: dict[int, float] = {}
for line in lines:
m = VAL_LOSS_RE.search(line)
if not m:
continue
try:
step = int(m.group(1))
val = float(m.group(2))
except ValueError:
continue
if math.isfinite(val):
by_step[step] = float(val)
if not by_step:
return [], []
steps = sorted(by_step.keys())
vals = [by_step[s] for s in steps]
return steps, vals
def load_completions(completion_cache_json: Path, step_strs: list[str]) -> list[str]:
if not completion_cache_json.exists():
return ["" for _ in step_strs]
obj = json.loads(completion_cache_json.read_text(encoding="utf-8"))
cache_steps = obj.get("checkpoint_steps", [])
cache_vals = obj.get("checkpoint_completions", [])
if not isinstance(cache_steps, list) or not isinstance(cache_vals, list):
return ["" for _ in step_strs]
step_to_completion = {str(s): str(v) for s, v in zip(cache_steps, cache_vals)}
return [step_to_completion.get(s, "") for s in step_strs]
def shift_subspace_colors(base_rgb: np.ndarray, t01: np.ndarray) -> np.ndarray:
t = np.clip(t01, 0.0, 1.0).astype(np.float32)
base = np.broadcast_to(base_rgb.reshape(1, 3).astype(np.float32), (t.shape[0], 3))
out = np.empty_like(base)
low = t < 0.5
if np.any(low):
dark_scale = 0.35 + 1.30 * t[low]
out[low] = base[low] * dark_scale[:, None]
if np.any(~low):
light_mix = (t[~low] - 0.5) * 1.30
out[~low] = base[~low] * (1.0 - light_mix[:, None]) + light_mix[:, None]
return np.clip(out, 0.0, 1.0)
def update_cell_raster(
cell: np.ndarray,
uv: np.ndarray,
c4: np.ndarray,
*,
umin: float,
umax: float,
vmin: float,
vmax: float,
c4min: float,
c4max: float,
base_colors: np.ndarray,
alpha: float = 0.86,
) -> np.ndarray:
h, w = cell.shape[:2]
n_sub = int(uv.shape[0])
acc = np.zeros((h, w, 3), dtype=np.float32)
cnt = np.zeros((h, w), dtype=np.float32)
ur = max(float(umax - umin), 1e-8)
vr = max(float(vmax - vmin), 1e-8)
c4r = max(float(c4max - c4min), 1e-8)
for s in range(n_sub):
us = uv[s, :, 0]
vs = uv[s, :, 1]
xs = ((us - umin) / ur) * (w - 1)
ys = ((vs - vmin) / vr) * (h - 1)
px = np.round(xs).astype(np.int32)
py = np.round((h - 1) - ys).astype(np.int32)
valid = (px >= 0) & (px < w) & (py >= 0) & (py < h)
if not np.any(valid):
continue
px = px[valid]
py = py[valid]
t = (c4[s, valid] - c4min) / c4r
cols = shift_subspace_colors(base_colors[s], t)
np.add.at(acc, (py, px), cols)
np.add.at(cnt, (py, px), 1.0)
mask = cnt > 0
if np.any(mask):
avg = np.zeros_like(acc)
avg[mask] = acc[mask] / cnt[mask][:, None]
cell[mask] = cell[mask] * (1.0 - alpha) + avg[mask] * alpha
return cell
@functools.lru_cache(maxsize=4096)
def _text_mask_supersampled(
text: str,
font_face: int,
font_scale: float,
thickness: int,
ss: int,
) -> tuple[np.ndarray, int, int]:
pad = 1
(w0, h0), b0 = cv2.getTextSize(text, font_face, font_scale, thickness)
low_w = max(1, int(w0 + 2 * pad))
low_h = max(1, int(h0 + b0 + 2 * pad))
hi_w = low_w * ss
hi_h = low_h * ss
mask_hi = np.zeros((hi_h, hi_w), dtype=np.uint8)
cv2.putText(
mask_hi,
text,
(pad * ss, (h0 + pad) * ss),
font_face,
float(font_scale) * ss,
255,
max(1, int(thickness) * ss),
cv2.LINE_AA,
)
mask = cv2.resize(mask_hi, (low_w, low_h), interpolation=cv2.INTER_AREA)
return mask, h0, pad
def draw_text_antialiased(
frame: np.ndarray,
text: str,
org: tuple[int, int],
font_face: int,
font_scale: float,
color_bgr: tuple[int, int, int],
thickness: int = 1,
ss: int = 4,
) -> None:
text = (
str(text)
.replace("\u2018", "'")
.replace("\u2019", "'")
.replace("\u201A", "'")
.replace("\u201B", "'")
.replace("\u201C", '"')
.replace("\u201D", '"')
.replace("\u2013", "-")
.replace("\u2014", "-")
.replace("\u2026", "...")
.replace("\u00A0", " ")
)
text = unicodedata.normalize("NFKC", text)
safe = []
for ch in text:
o = ord(ch)
if 32 <= o <= 126:
safe.append(ch)
continue
approx = unicodedata.normalize("NFKD", ch).encode("ascii", "ignore").decode("ascii")
safe.append(approx if approx else "?")
text = "".join(safe)
x, y = int(org[0]), int(org[1])
mask, h0, pad = _text_mask_supersampled(text, int(font_face), float(font_scale), int(thickness), max(2, int(ss)))
x0 = x - pad
y0 = y - h0 - pad
x1 = x0 + mask.shape[1]
y1 = y0 + mask.shape[0]
h, w = frame.shape[:2]
cx0 = max(0, x0)
cy0 = max(0, y0)
cx1 = min(w, x1)
cy1 = min(h, y1)
if cx0 >= cx1 or cy0 >= cy1:
return
mx0 = cx0 - x0
my0 = cy0 - y0
mx1 = mx0 + (cx1 - cx0)
my1 = my0 + (cy1 - cy0)
alpha = (mask[my0:my1, mx0:mx1].astype(np.float32) / 255.0)[:, :, None]
if not np.any(alpha > 0):
return
roi = frame[cy0:cy1, cx0:cx1].astype(np.float32)
col = np.array(color_bgr, dtype=np.float32).reshape(1, 1, 3)
out = roi * (1.0 - alpha) + col * alpha
frame[cy0:cy1, cx0:cx1] = np.clip(out, 0.0, 255.0).astype(np.uint8)
def dynamic_layer_bounds(
uv: np.ndarray,
c4: np.ndarray,
fallback: dict[str, float],
) -> tuple[float, float, float, float, float, float]:
u = uv[:, :, 0].reshape(-1)
v = uv[:, :, 1].reshape(-1)
c = c4.reshape(-1)
if u.size == 0 or v.size == 0:
return (
float(fallback["umin"]),
float(fallback["umax"]),
float(fallback["vmin"]),
float(fallback["vmax"]),
float(fallback["c4min"]),
float(fallback["c4max"]),
)
uq0, uq1 = np.quantile(u, [0.01, 0.99])
vq0, vq1 = np.quantile(v, [0.01, 0.99])
uc = 0.5 * float(uq0 + uq1)
vc = 0.5 * float(vq0 + vq1)
r = 0.52 * max(float(uq1 - uq0), float(vq1 - vq0), 1e-4)
umin, umax = uc - r, uc + r
vmin, vmax = vc - r, vc + r
cq0, cq1 = np.quantile(c, [0.02, 0.98])
c4min, c4max = float(cq0), float(cq1)
if not np.isfinite(umin) or not np.isfinite(umax) or abs(umax - umin) < 1e-6:
umin, umax = float(fallback["umin"]), float(fallback["umax"])
if not np.isfinite(vmin) or not np.isfinite(vmax) or abs(vmax - vmin) < 1e-6:
vmin, vmax = float(fallback["vmin"]), float(fallback["vmax"])
if not np.isfinite(c4min) or not np.isfinite(c4max) or abs(c4max - c4min) < 1e-6:
c4min, c4max = float(fallback["c4min"]), float(fallback["c4max"])
return umin, umax, vmin, vmax, c4min, c4max
def build_layer_bounds(uv_paths: list[Path], c4_paths: list[Path], n_layers: int) -> list[dict[str, float]]:
bounds = [
{"umin": np.inf, "umax": -np.inf, "vmin": np.inf, "vmax": -np.inf, "c4min": np.inf, "c4max": -np.inf}
for _ in range(n_layers)
]
for uv_path, c4_path in zip(uv_paths, c4_paths):
uv = np.load(uv_path, mmap_mode="r")
c4 = np.load(c4_path, mmap_mode="r")
for i in range(n_layers):
uvi = uv[i].astype(np.float32, copy=False)
c4i = c4[i].astype(np.float32, copy=False)
b = bounds[i]
b["umin"] = min(float(b["umin"]), float(np.min(uvi[:, :, 0])))
b["umax"] = max(float(b["umax"]), float(np.max(uvi[:, :, 0])))
b["vmin"] = min(float(b["vmin"]), float(np.min(uvi[:, :, 1])))
b["vmax"] = max(float(b["vmax"]), float(np.max(uvi[:, :, 1])))
b["c4min"] = min(float(b["c4min"]), float(np.min(c4i)))
b["c4max"] = max(float(b["c4max"]), float(np.max(c4i)))
for b in bounds:
if not np.isfinite(b["umin"]) or not np.isfinite(b["umax"]):
b["umin"], b["umax"] = -1.0, 1.0
if not np.isfinite(b["vmin"]) or not np.isfinite(b["vmax"]):
b["vmin"], b["vmax"] = -1.0, 1.0
if not np.isfinite(b["c4min"]) or not np.isfinite(b["c4max"]):
b["c4min"], b["c4max"] = -1.0, 1.0
if abs(b["umax"] - b["umin"]) < 1e-6:
b["umin"] -= 1.0
b["umax"] += 1.0
if abs(b["vmax"] - b["vmin"]) < 1e-6:
b["vmin"] -= 1.0
b["vmax"] += 1.0
if abs(b["c4max"] - b["c4min"]) < 1e-6:
b["c4min"] -= 1.0
b["c4max"] += 1.0
return bounds
def render_frame_png(
*,
fi: int,
transition_frames: int,
checkpoint_count: int,
checkpoint_step_values: list[int],
checkpoint_completions: list[str],
completion_prompt: str,
tokens_per_step: float,
val_loss_steps: list[int],
val_loss_values: list[float],
val_loss_label: str,
total_tokens_b: float,
uv_cache_paths: list[str],
c4_cache_paths: list[str],
layer_bounds: list[dict[str, float]],
base_colors: np.ndarray,
width: int,
height: int,
cols: int,
margin_l: int,
margin_t: int,
cell_w: int,
cell_h: int,
gx: int,
gy: int,
out_png: str,
) -> int:
if fi >= transition_frames:
i0 = checkpoint_count - 2
i1 = checkpoint_count - 1
seg_t = 1.0
ck_display_idx = checkpoint_count - 1
else:
tau = fi / float(max(1, transition_frames - 1))
p = tau * float(checkpoint_count - 1)
i0 = int(math.floor(p))
i0 = min(i0, checkpoint_count - 2)
i1 = i0 + 1
seg_t = p - float(i0)
ck_display_idx = int(round(p))
ck_display_idx = max(0, min(checkpoint_count - 1, ck_display_idx))
uv0 = np.load(uv_cache_paths[i0], mmap_mode="r")
c40 = np.load(c4_cache_paths[i0], mmap_mode="r")
uv1 = np.load(uv_cache_paths[i1], mmap_mode="r")
c41 = np.load(c4_cache_paths[i1], mmap_mode="r")
n_layers = int(uv0.shape[0])
n_subspaces = int(uv0.shape[1])
frame = np.full((height, width, 3), 255, dtype=np.uint8)
for layer in range(n_layers):
r = layer // cols
c = layer % cols
x0 = margin_l + c * (cell_w + gx)
y0 = margin_t + r * (cell_h + gy)
uv = uv0[layer].astype(np.float32, copy=False) * (1.0 - seg_t) + uv1[layer].astype(np.float32, copy=False) * seg_t
c4 = c40[layer].astype(np.float32, copy=False) * (1.0 - seg_t) + c41[layer].astype(np.float32, copy=False) * seg_t
cell = np.full((cell_h, cell_w, 3), 1.0, dtype=np.float32)
b = layer_bounds[layer]
umin, umax, vmin, vmax, c4min, c4max = dynamic_layer_bounds(uv, c4, b)
cell = update_cell_raster(
cell,
uv,
c4,
umin=umin,
umax=umax,
vmin=vmin,
vmax=vmax,
c4min=c4min,
c4max=c4max,
base_colors=base_colors,
alpha=0.86,
)
cell_u8 = np.clip(cell * 255.0, 0.0, 255.0).astype(np.uint8)
frame[y0 : y0 + cell_h, x0 : x0 + cell_w] = cell_u8
cv2.rectangle(frame, (x0, y0), (x0 + cell_w - 1, y0 + cell_h - 1), (210, 210, 210), 1)
draw_text_antialiased(frame, f"L{layer}", (x0 + 3, y0 + 11), cv2.FONT_HERSHEY_SIMPLEX, 0.30, (35, 35, 35), 1)
step_interp = float(checkpoint_step_values[i0]) * (1.0 - seg_t) + float(checkpoint_step_values[i1]) * seg_t
tokens_b = step_interp * float(tokens_per_step) / 1e9
display_val_loss = None
if val_loss_steps:
idx = bisect_right(val_loss_steps, int(math.floor(step_interp))) - 1
if idx >= 0:
display_val_loss = float(val_loss_values[idx])
draw_text_antialiased(frame, "How do LLM decoders change over training?", (20, 26), cv2.FONT_HERSHEY_SIMPLEX, 0.72, (25, 25, 25), 2)
tokens_line = f"Tokens trained: {tokens_b:0.3f}B"
if display_val_loss is not None:
tokens_line += f" {val_loss_label}: {display_val_loss:0.3f}"
draw_text_antialiased(frame, tokens_line, (20, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.56, (40, 40, 40), 1)
completion_text = checkpoint_completions[ck_display_idx] if 0 <= ck_display_idx < len(checkpoint_completions) else ""
completion_line = f'Completion for "{completion_prompt}": {completion_text}'
draw_text_antialiased(frame, completion_line, (20, 72), cv2.FONT_HERSHEY_SIMPLEX, 0.40, (45, 45, 45), 1)
lx = width - 420
ly = 14
cv2.rectangle(frame, (lx - 8, ly - 6), (width - 16, ly + 56), (245, 245, 245), -1)
cv2.rectangle(frame, (lx - 8, ly - 6), (width - 16, ly + 56), (160, 160, 160), 1)
draw_text_antialiased(frame, "Subspace key", (lx, ly + 8), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (30, 30, 30), 1)
for s in range(n_subspaces):
cx = lx + (s % 8) * 48
cy = ly + 22 + (s // 8) * 18
col = tuple(int(x) for x in (base_colors[s] * 255.0)[::-1])
cv2.rectangle(frame, (cx, cy - 7), (cx + 12, cy + 5), col, -1)
cv2.rectangle(frame, (cx, cy - 7), (cx + 12, cy + 5), (0, 0, 0), 1)
draw_text_antialiased(frame, f"S{s}", (cx + 16, cy + 4), cv2.FONT_HERSHEY_SIMPLEX, 0.35, (40, 40, 40), 1)
note_lines = [
f"The charts above show all 128 layers of an 18-million parameter transformer model while being trained on {total_tokens_b:0.1f} billion tokens.",
"Each layer has 16x K-Splanifolds (fast spline-manifolds) which encode geometric transformations instead of a neural network MLP.",
"Model architecture uses a byte-level vocab and 8x 16-layer AttnRes blocks.",
]
line_h = 15
box_pad_top = 7
box_pad_bottom = 7
box_h = box_pad_top + box_pad_bottom + line_h * len(note_lines)
box_top = height - 8 - box_h
cv2.rectangle(frame, (14, box_top), (width - 14, height - 8), (245, 245, 245), -1)
cv2.rectangle(frame, (14, box_top), (width - 14, height - 8), (160, 160, 160), 1)
for i, line in enumerate(note_lines):
draw_text_antialiased(frame, line, (22, box_top + box_pad_top + 11 + i * line_h), cv2.FONT_HERSHEY_SIMPLEX, 0.42, (35, 35, 35), 1)
ok = cv2.imwrite(out_png, frame, [cv2.IMWRITE_PNG_COMPRESSION, 2])
if not ok:
raise RuntimeError(f"Failed to write frame image: {out_png}")
return fi
def main() -> None:
parser = argparse.ArgumentParser(description="Render decoder projection progression video from cached projection arrays.")
parser.add_argument("--cache-dir", default="data/cache", help="Directory containing uv_stepXXXXXX.npy and c4_stepXXXXXX.npy")
parser.add_argument("--completion-cache-json", default="data/completions.json", help="Checkpoint completion cache json")
parser.add_argument("--training-log", default="data/training_log.txt", help="Training log for step-wise val_loss display")
parser.add_argument("--music", default="data/music/hydrostatic_mind.mp3", help="Optional background music file")
parser.add_argument("--no-music", action="store_true", help="Disable background music mux")
parser.add_argument("--music-volume", type=float, default=0.8, help="Music volume multiplier")
parser.add_argument("--music-fade-sec", type=float, default=5.0, help="Outro fade duration in seconds")
parser.add_argument("--duration-sec", type=float, default=158.0, help="Transition duration")
parser.add_argument("--end-hold-sec", type=float, default=5.0, help="Hold last checkpoint")
parser.add_argument("--fps", type=int, default=60, help="Frames per second")
parser.add_argument("--width", type=int, default=1920)
parser.add_argument("--height", type=int, default=1080)
parser.add_argument("--workers", type=int, default=max(1, os.cpu_count() or 1), help="Parallel frame workers")
parser.add_argument("--tokens-per-step", type=float, default=500000.0)
parser.add_argument("--completion-prompt", default="The cat is")
parser.add_argument("--val-loss-label", default="Val loss (byte vocab, 500k tokens, fineweb)")
parser.add_argument("--ffmpeg-preset", default="medium")
parser.add_argument("--ffmpeg-crf", type=int, default=14, help="Used only when --video-bitrate is unset")
parser.add_argument("--ffmpeg-pix-fmt", default="yuv420p")
parser.add_argument("--ffmpeg-threads", type=int, default=0)
parser.add_argument("--video-bitrate", default="14M", help="Set target bitrate (e.g. 14M). Empty disables CBR mode")
parser.add_argument("--video-maxrate", default=None)
parser.add_argument("--video-bufsize", default=None)
parser.add_argument(
"--keep-frame-pngs",
dest="keep_frame_pngs",
action="store_true",
default=True,
help="Keep rendered frame PNGs (default: on).",
)
parser.add_argument("--cleanup-frame-pngs", dest="keep_frame_pngs", action="store_false")
parser.add_argument("--output", default="outputs/subspace_projection_progression_video.mp4")
args = parser.parse_args()
cache_dir = resolve_path(args.cache_dir)
completion_json = resolve_path(args.completion_cache_json)
training_log = resolve_path(args.training_log)
music_path = resolve_path(args.music)
output_path = resolve_path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
step_strs, step_vals, uv_paths, c4_paths = parse_cache_steps(cache_dir)
completions = load_completions(completion_json, step_strs)
val_loss_steps, val_loss_values = parse_stepwise_val_loss_points(training_log)
total_tokens_b = float(step_vals[-1]) * float(args.tokens_per_step) / 1e9
log(f"Checkpoints from cache: {len(step_strs)} ({step_strs[0]}..{step_strs[-1]})")
if val_loss_steps:
log(
"Loaded val_loss points: "
f"{len(val_loss_steps)} entries, first={val_loss_steps[0]}, "
f"latest={val_loss_steps[-1]}:{val_loss_values[-1]:0.4f}"
)
uv0 = np.load(uv_paths[0], mmap_mode="r")
n_layers = int(uv0.shape[0])
n_subspaces = int(uv0.shape[1])
cols = 16
rows = int(math.ceil(n_layers / cols))
layer_bounds = build_layer_bounds(uv_paths, c4_paths, n_layers)
width = int(args.width)
height = int(args.height)
fps = max(1, int(args.fps))
transition_frames = max(2, int(round(float(args.duration_sec) * fps)))
hold_frames = max(0, int(round(float(args.end_hold_sec) * fps)))
frames = transition_frames + hold_frames
margin_l, margin_r = 24, 24
margin_t, margin_b = 92, 56
gx, gy = 2, 2
cell_w = int((width - margin_l - margin_r - (cols - 1) * gx) / cols)
cell_h = int((height - margin_t - margin_b - (rows - 1) * gy) / rows)
base_colors = matplotlib.colormaps["tab20"](np.linspace(0.0, 1.0, n_subspaces, endpoint=False))[:, :3].astype(np.float32)
frame_dir = output_path.parent / f"{output_path.stem}_frames"
if frame_dir.exists():
shutil.rmtree(frame_dir)
frame_dir.mkdir(parents=True, exist_ok=True)
log(f"Rendering {frames} frames -> {frame_dir}")
uv_strs = [str(p) for p in uv_paths]
c4_strs = [str(p) for p in c4_paths]
workers = max(1, int(args.workers))
if workers == 1:
for fi in range(frames):
out_png = frame_dir / f"frame_{fi:06d}.png"
render_frame_png(
fi=fi,
transition_frames=transition_frames,
checkpoint_count=len(step_strs),
checkpoint_step_values=step_vals,
checkpoint_completions=completions,
completion_prompt=str(args.completion_prompt),
tokens_per_step=float(args.tokens_per_step),
val_loss_steps=val_loss_steps,
val_loss_values=val_loss_values,
val_loss_label=str(args.val_loss_label),
total_tokens_b=float(total_tokens_b),
uv_cache_paths=uv_strs,
c4_cache_paths=c4_strs,
layer_bounds=layer_bounds,
base_colors=base_colors,
width=width,
height=height,
cols=cols,
margin_l=margin_l,
margin_t=margin_t,
cell_w=cell_w,
cell_h=cell_h,
gx=gx,
gy=gy,
out_png=str(out_png),
)
if fi % max(1, fps) == 0 or fi == frames - 1:
log(f"Rendered {fi + 1}/{frames}")
else:
futures = []
done = 0
with ProcessPoolExecutor(max_workers=workers) as ex:
for fi in range(frames):
out_png = frame_dir / f"frame_{fi:06d}.png"
futures.append(
ex.submit(
render_frame_png,
fi=fi,
transition_frames=transition_frames,
checkpoint_count=len(step_strs),
checkpoint_step_values=step_vals,
checkpoint_completions=completions,
completion_prompt=str(args.completion_prompt),
tokens_per_step=float(args.tokens_per_step),
val_loss_steps=val_loss_steps,
val_loss_values=val_loss_values,
val_loss_label=str(args.val_loss_label),
total_tokens_b=float(total_tokens_b),
uv_cache_paths=uv_strs,
c4_cache_paths=c4_strs,
layer_bounds=layer_bounds,
base_colors=base_colors,
width=width,
height=height,
cols=cols,
margin_l=margin_l,
margin_t=margin_t,
cell_w=cell_w,
cell_h=cell_h,
gx=gx,
gy=gy,
out_png=str(out_png),
)
)
for fut in as_completed(futures):
_ = fut.result()
done += 1
if done % max(1, fps) == 0 or done == frames:
log(f"Rendered {done}/{frames}")
if shutil.which("ffmpeg") is None:
raise RuntimeError("ffmpeg is required")
video_only = output_path.parent / f"{output_path.stem}.video_only.mp4"
ffmpeg_cmd = [
"ffmpeg",
"-y",
"-framerate",
str(fps),
"-i",
str(frame_dir / "frame_%06d.png"),
"-c:v",
"libx264",
"-preset",
str(args.ffmpeg_preset),
"-pix_fmt",
str(args.ffmpeg_pix_fmt),
]
if args.video_bitrate:
ffmpeg_cmd += ["-b:v", str(args.video_bitrate)]
ffmpeg_cmd += ["-maxrate", str(args.video_maxrate or args.video_bitrate)]
default_bufsize = f"{int(float(str(args.video_bitrate).rstrip('M')) * 2)}M" if str(args.video_bitrate).endswith("M") else str(args.video_bitrate)
ffmpeg_cmd += ["-bufsize", str(args.video_bufsize or default_bufsize)]
else:
ffmpeg_cmd += ["-crf", str(int(args.ffmpeg_crf))]
if int(args.ffmpeg_threads) > 0:
ffmpeg_cmd += ["-threads", str(int(args.ffmpeg_threads))]
ffmpeg_cmd += ["-movflags", "+faststart", str(video_only)]
log("Encoding video stream")
subprocess.run(ffmpeg_cmd, check=True)
if args.no_music or not music_path.exists():
shutil.move(str(video_only), str(output_path))
else:
fade_start = max(0.0, float(args.duration_sec + args.end_hold_sec) - float(args.music_fade_sec))
mux_cmd = [
"ffmpeg",
"-y",
"-i",
str(video_only),
"-i",
str(music_path),
"-filter_complex",
(
f"[1:a]atrim=0:{float(args.duration_sec + args.end_hold_sec):.6f},"
f"asetpts=PTS-STARTPTS,volume={float(args.music_volume):.6f},"
f"afade=t=out:st={fade_start:.6f}:d={float(args.music_fade_sec):.6f}[a]"
),
"-map",
"0:v:0",
"-map",
"[a]",
"-c:v",
"copy",
"-c:a",
"aac",
"-b:a",
"192k",
"-shortest",
"-movflags",
"+faststart",
str(output_path),
]
log("Muxing music")
subprocess.run(mux_cmd, check=True)
video_only.unlink(missing_ok=True)
if not args.keep_frame_pngs:
shutil.rmtree(frame_dir, ignore_errors=True)
meta = {
"output": str(output_path),
"cache_dir": str(cache_dir),
"checkpoint_steps": step_strs,
"checkpoint_step_values": step_vals,
"tokens_per_step": float(args.tokens_per_step),
"completion_cache_json": str(completion_json),
"training_log": str(training_log),
"val_loss_steps": [int(x) for x in val_loss_steps],
"val_loss_values": [float(x) for x in val_loss_values],
"duration_sec": float(args.duration_sec),
"end_hold_sec": float(args.end_hold_sec),
"fps": int(fps),
"frames": int(frames),
"resolution": [int(width), int(height)],
"grid": {"cols": int(cols), "rows": int(rows), "layers": int(n_layers)},
"subspaces": int(n_subspaces),
"music": None if args.no_music else str(music_path),
"music_volume": float(args.music_volume),
"music_fade_sec": float(args.music_fade_sec),
"frame_dir": str(frame_dir),
}
meta_path = output_path.with_suffix(".json")
meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
log(f"Wrote {output_path}")
log(f"Wrote {meta_path}")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
27.7 kB
·
Xet hash:
365c115b3795426a3427a8f6ec03e456f2c1089ab52d4fee7e50da897b48e3b5

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.