Instructions to use ApacheOne/Wan2.2-Animate-2-14B-OrbitQuant-W4A4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use ApacheOne/Wan2.2-Animate-2-14B-OrbitQuant-W4A4 with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image, export_to_video # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("ApacheOne/Wan2.2-Animate-2-14B-OrbitQuant-W4A4", dtype=torch.bfloat16, device_map="cuda") pipe.to("cuda") prompt = "A man with short gray hair plays a red electric guitar." image = load_image( "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/guitar-man.png" ) output = pipe(image=image, prompt=prompt).frames[0] export_to_video(output, "output.mp4") - Notebooks
- Google Colab
- Kaggle
File size: 6,049 Bytes
f2c0505 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | from __future__ import annotations
import torch
class Animate2KVCacheCPUOffloader:
"""Keep Wan-Animate-2 reference K/V caches in CPU memory.
The upstream source stores one immutable pre-RoPE K and V tensor per
transformer layer. `forward_gen` only reads those tensors. This allows us
to preserve one CPU master copy after `forward_ref`, load only the
current layer's K/V to CUDA immediately before its generation block, and
restore the dictionary entry to the CPU master immediately after the block.
This does not quantize or otherwise alter K/V; it is a placement change only.
"""
def __init__(
self, model: torch.nn.Module, device: str | torch.device = "cuda", *, pin_memory: bool = False
):
self.model = model
self.device = torch.device(device)
self.pin_memory = bool(pin_memory)
self.handles = []
self._masters: dict[tuple[int, int, str], torch.Tensor] = {}
self._current_cache_pair: tuple[int, int] | None = None
self.ref_offload_bytes = 0
self.gen_load_bytes = 0
self.gen_loads = 0
self.layers_offloaded = set()
self.peak_layer_bytes = 0
@staticmethod
def _nbytes(t: torch.Tensor) -> int:
return int(t.numel() * t.element_size())
def _cpu_copy(self, t: torch.Tensor) -> torch.Tensor:
if t.device.type == "cpu" and (not self.pin_memory or t.is_pinned()):
return t
# Keeping an entire 40-layer 480p/720p KV cache page-locked can itself
# exhaust host pinned memory. Default to ordinary CPU memory; an
# explicit cpu-pinned mode is available on hosts with ample RAM.
out = torch.empty_like(t, device="cpu", pin_memory=self.pin_memory)
out.copy_(t, non_blocking=False)
return out
def _begin_cache_pair(self, k_cache: dict, v_cache: dict, index: int):
pair = (id(k_cache), id(v_cache))
# Every segment creates fresh dicts. When layer 0 of a new reference
# pass arrives, release the previous segment's pinned masters.
if int(index) == 0 and self._current_cache_pair != pair:
self._masters.clear()
self._current_cache_pair = pair
def _pre(self, module, args, kwargs):
if kwargs.get("method") != "forward_gen" or len(args) < 4:
return
_x, index, k_cache, v_cache = args[:4]
index = int(index)
pair = (id(k_cache), id(v_cache))
for kind, cache in (("k", k_cache), ("v", v_cache)):
key = (pair[0] if kind == "k" else pair[1], index, kind)
master = self._masters.get(key)
if master is None:
# This indicates the caller bypassed the reference pass or the
# cache dictionaries changed unexpectedly. Do not silently
# fall back to GPU residency.
raise RuntimeError(
f"missing CPU {kind.upper()} master for Animate-2 layer {index}; "
"forward_ref must complete before forward_gen"
)
gpu = master.to(self.device, non_blocking=self.pin_memory)
cache[index] = gpu
self.gen_load_bytes += self._nbytes(master)
self.gen_loads += 1
def _post(self, module, args, kwargs, output):
method = kwargs.get("method")
if method not in {"forward_ref", "forward_gen"} or len(args) < 4:
return output
_x, index, k_cache, v_cache = args[:4]
index = int(index)
pair = (id(k_cache), id(v_cache))
if method == "forward_ref":
self._begin_cache_pair(k_cache, v_cache, index)
layer_bytes = 0
for kind, cache in (("k", k_cache), ("v", v_cache)):
t = cache.get(index)
if t is None:
raise RuntimeError(f"Animate-2 reference pass did not populate {kind}_cache[{index}]")
master = self._cpu_copy(t.detach())
key = (pair[0] if kind == "k" else pair[1], index, kind)
self._masters[key] = master
cache[index] = master
nb = self._nbytes(master)
self.ref_offload_bytes += nb
layer_bytes += nb
self.layers_offloaded.add(index)
self.peak_layer_bytes = max(self.peak_layer_bytes, layer_bytes)
else:
# K/V are immutable in forward_gen. Restore the already-existing
# pinned master instead of copying the CUDA tensor back to host.
for kind, cache in (("k", k_cache), ("v", v_cache)):
key = (pair[0] if kind == "k" else pair[1], index, kind)
master = self._masters.get(key)
if master is None:
raise RuntimeError(f"lost CPU {kind.upper()} master for layer {index}")
cache[index] = master
return output
def install(self):
if self.device.type != "cuda":
raise ValueError("Animate2KVCacheCPUOffloader requires a CUDA device")
for block in self.model.blocks:
self.handles.append(block.register_forward_pre_hook(self._pre, with_kwargs=True))
self.handles.append(block.register_forward_hook(self._post, with_kwargs=True))
return self
def remove(self):
for handle in self.handles:
handle.remove()
self.handles.clear()
self._masters.clear()
def report(self) -> dict:
return {
"mode": "pinned_cpu_per_layer_stream" if self.pin_memory else "cpu_per_layer_stream",
"pinned_host_master": self.pin_memory,
"layers_offloaded": len(self.layers_offloaded),
"reference_offload_gib": self.ref_offload_bytes / 2**30,
"generation_h2d_gib": self.gen_load_bytes / 2**30,
"generation_layer_loads": int(self.gen_loads),
"peak_single_layer_cache_gib": self.peak_layer_bytes / 2**30,
"full_cache_gpu_residency": False,
}
|