Files
hao-ai-lab-FastVideo/v2/platform/backends/torch_kernels.py
T
SolitaryThinker 1e58d90d02 [feat] real torch/CUDA backend (written-not-run) behind the cuda cells
Implement the GPU backend the substrate was built for: Platform.detect() ->
cuda resolves real torch adapters + torch solver ops instead of the numpy
rungs, with the existing loops/policies/scheduler/training unchanged.

WRITTEN-NOT-RUN: this environment has no GPU/torch, so the torch code is
grounded in the verbatim real fastvideo APIs (DiT forward signature confirmed
from source) but cannot be executed/verified here. It is gated available=False
(CPU mini stays green; importing the backends never imports torch), with every
on-box confirm point marked `# BRINGUP` and an ordered checklist in
platform/backends/GPU_BRINGUP.md.

- torch_adapters.py: TorchWanDiT / TorchWanVAE / TorchT5Encoder wrap the real
  module named by each card's load_id and bridge it to the mini's duck-typed
  surface (numpy<->torch at the boundary; loop math stays numpy fp32). DiT
  weight-surface (copy_from/blend_from/clone) for serving sync; mse_grad_step
  raises (GPU training is a separate workstream).
- torch_kernels.py: flow_match_step / flow_sde_step as plain torch elementwise.
  Grounded conclusion from the kernel audit: fastvideo-kernel ships NO fused
  solver kernel (only attention/norm/quant primitives), so the cuda solver is
  torch, registered at arch generic with an honest source string.
- torch_cuda.py: rewritten as lazy trampolines (torch imported only inside
  builder/kernel bodies). Adds the missing vae + text_encoder cuda components
  (they'd otherwise silently fall back to the toy) and corrects the dishonest
  "fastvideo-kernel:flow_*" labels.
- ComponentSpec.checkpoint: the weights source for the torch adapter (risk A;
  the one field the cards didn't carry). Empty on the CPU toys.
- 7 CPU-verifiable wiring tests: honest registration/sources, torch-free import,
  cuda-resolves-real-cells-not-toy, build-fails-loudly-without-torch.

204 tests pass (CPU). The torch path needs a GPU box to verify (GPU_BRINGUP.md).
2026-07-04 17:15:34 +00:00

58 lines
2.6 KiB
Python

"""Torch solver-step kernels for the GPU backend (design_v3 §17).
GROUND TRUTH (from the fastvideo-kernel audit): there is NO fused flow-match / SDE *solver* kernel.
fastvideo-kernel ships only primitives — sparse/tiled attention, RMS/LayerNorm, int8 GEMM/quant.
So the GPU solver step is a plain torch elementwise op with the SAME math as ``loop/sampler.py``'s
numpy reference (the consistency oracle that the accel stand-in already proves matches bit-for-bit).
WRITTEN-NOT-RUN: imports torch; loaded only lazily from ``torch_cuda.py``. Under the current numpy
loop surface these marshal numpy↔torch at the boundary (a torch-native surface would keep the latent
on-device through forward→combine→solver — the perf follow-up, Risk G in GPU_BRINGUP.md).
"""
from __future__ import annotations
import numpy as np
import torch
def _as_torch(a):
"""numpy → cuda torch (passthrough if already torch). Returns (tensor, was_numpy)."""
if torch.is_tensor(a):
return a, False
return torch.as_tensor(np.asarray(a), device="cuda"), True
def _back(t, like_numpy: bool):
return t.detach().to("cpu", torch.float32).numpy() if like_numpy else t
def flow_match_step(x_t, velocity, sigma_t, sigma_next):
"""One deterministic flow-match Euler step — identical to sampler.flow_match_euler_step."""
xt, was_np = _as_torch(x_t)
v, _ = _as_torch(velocity)
return _back(xt + (float(sigma_next) - float(sigma_t)) * v, was_np)
def flow_sde_step(x_t, velocity, sigma_t, sigma_next, *, noise=None, prev_sample=None, noise_scale=0.7):
"""FlowGRPO stochastic step + Gaussian log-prob — the torch port of
sampler.flow_sde_step_with_logprob. Returns (prev_sample, log_prob, mean, eff_std)."""
xt, was_np = _as_torch(x_t)
v, _ = _as_torch(velocity)
xt = xt.double()
v = v.double()
s = min(float(sigma_t), 0.9999)
dt = float(sigma_next) - float(sigma_t)
std = float((s / (1.0 - s)) ** 0.5 * noise_scale)
denom = 2.0 * max(s, 1e-6)
mean = xt * (1.0 + std ** 2 / denom * dt) + v * (1.0 + std ** 2 * (1.0 - s) / denom) * dt
eff_std = max(std * max(-dt, 1e-12) ** 0.5, 1e-6)
if prev_sample is None:
n = _as_torch(noise)[0].double() if noise is not None else torch.zeros_like(xt)
prev = mean + eff_std * n
else:
prev = _as_torch(prev_sample)[0].double()
var = eff_std ** 2
log_prob = float((-((prev - mean) ** 2) / (2.0 * var)
- float(np.log(eff_std)) - 0.5 * float(np.log(2.0 * np.pi))).mean().item())
return (_back(prev.float(), was_np), log_prob, _back(mean.float(), was_np), float(eff_std))