Files
hao-ai-lab-FastVideo/fastvideo/models/pinned_offload.py
Hexu ZhaoandClaude Opus 5 10bdf5e076 [bugfix] frozen offload: no host copy for an already resident module
`load` took the host copies before checking which tensors were missing from
the device, so with `vae_cpu_offload=False` — where the loader builds the VAEs
on CUDA and `unload` is never called — it pulled about 11 GB per rank back over
PCIe once and kept a pinned mirror of it for the life of the process, for a
module that never leaves the device.

It now asks what is absent first and returns early when nothing is. The
offloaded path is unchanged: weights still round-trip and come back bit
identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 22:46:13 -07:00

89 lines
3.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""CPU offload for frozen inference modules, without the device-to-host copy.
``module.to("cpu")`` allocates fresh host storage and copies every parameter
and buffer back over PCIe. For a module that is only ever run under
``torch.no_grad()`` the weights on the device are byte-identical to the ones
that were copied in, so that copy is pure waste: ``unload`` points each
tensor's ``.data`` back at the host copy it came from and lets the device
storage go.
``load`` keeps one host copy per tensor, made the first time the module is
loaded, and copies it to the device. Net: one host-to-device copy per request
and no device-to-host copy at all, with the module living on the host between
requests exactly as before. Numerics are untouched -- the device tensors are
byte copies of the same weights -- and peak host memory can only go down,
because one buffer is reused instead of a new one being allocated per cycle.
The host copies are pinned when ``pin`` is set (``--pin-cpu-memory``, on by
default), which is what lets the remaining host-to-device copy run at full
PCIe speed instead of being staged through a bounce buffer. That trades the
module's size in non-pageable host memory for the bandwidth; ``pin=False``
keeps the copies pageable and everything else the same.
"""
from __future__ import annotations
import torch
from torch import nn
from fastvideo.logger import init_logger
from fastvideo.utils import is_pin_memory_available
logger = init_logger(__name__)
_HOST_ATTR = "_frozen_offload_host"
def _tensors(module: nn.Module):
yield from module.named_parameters(recurse=True)
for name, buf in module.named_buffers(recurse=True):
yield "buffer:" + name, buf
def _host_copies(module: nn.Module, pin: bool) -> dict[str, torch.Tensor]:
host = getattr(module, _HOST_ATTR, None)
if host is not None:
return host
host, total = {}, 0
for name, t in _tensors(module):
src = t.data if t.data.device.type == "cpu" else t.data.to("cpu")
if pin and not src.is_pinned():
src = src.pin_memory()
host[name] = src
total += src.numel() * src.element_size()
setattr(module, _HOST_ATTR, host)
logger.info("frozen offload: holding %.2f GB of %s on the host (%s)", total / 1e9, type(module).__name__,
"pinned" if pin else "pageable")
return host
def load(module: nn.Module, device: torch.device, pin: bool = True) -> nn.Module:
"""Put a frozen ``module`` on ``device``, copying from its host copies."""
if device.type != "cuda":
return module.to(device)
missing = [(name, t) for name, t in _tensors(module) if t.data.device != device]
if not missing:
# Already resident, which is what ``vae_cpu_offload=False`` leaves. Taking
# host copies here would pull the whole module back over PCIe once and keep
# a pinned mirror of it for the life of the process, for a module that never
# leaves the device.
return module
host = _host_copies(module, pin and is_pin_memory_available())
for name, t in missing:
src = host[name]
dst = torch.empty_like(src, device=device)
dst.copy_(src, non_blocking=src.is_pinned())
t.data = dst
return module
def unload(module: nn.Module) -> nn.Module:
"""Point a frozen ``module`` back at its host copies; no device-to-host copy."""
host = getattr(module, _HOST_ATTR, None)
if host is None:
return module.to("cpu")
for name, t in _tensors(module):
if t.data.device.type != "cpu":
t.data = host[name]
return module