Spaces:
Running on Zero
Running on Zero
Upload app.py
Browse files
app.py
CHANGED
|
@@ -8,6 +8,7 @@ gradio API for every request; `reference_encoder` stays here, next to the autoen
|
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
import json
|
|
|
|
| 11 |
import os
|
| 12 |
import random
|
| 13 |
import re
|
|
@@ -17,6 +18,11 @@ import time
|
|
| 17 |
import traceback
|
| 18 |
from functools import cache
|
| 19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
# Before anything that could initialize CUDA: `import spaces` patches `torch.cuda` so the 72 GiB load can happen at
|
| 21 |
# startup rather than on GPU time.
|
| 22 |
import spaces
|
|
@@ -44,7 +50,7 @@ MAX_SEQUENCE = int(os.environ.get("H3_MAX_SEQUENCE", "90000"))
|
|
| 44 |
# An attached adapter adds its own layers and their activations to the same card, so the ceiling above is not the
|
| 45 |
# ceiling any more. Refusing a request that is over the reduced one is a sentence on screen; letting it through is a
|
| 46 |
# dead worker and a bare "runtime error".
|
| 47 |
-
LORA_SEQUENCE_FRACTION = float(os.environ.get("H3_LORA_SEQUENCE_FRACTION", "0.
|
| 48 |
|
| 49 |
|
| 50 |
def sequence_ceiling(loras=()) -> int:
|
|
@@ -995,6 +1001,13 @@ def apply_loras(transformer, loras) -> list[str]:
|
|
| 995 |
|
| 996 |
for name in list(getattr(transformer, "peft_config", None) or {}):
|
| 997 |
transformer.delete_adapters(name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 998 |
|
| 999 |
names, scales = [], []
|
| 1000 |
for index, (path, scale) in enumerate(loras):
|
|
@@ -1011,6 +1024,14 @@ def apply_loras(transformer, loras) -> list[str]:
|
|
| 1011 |
else:
|
| 1012 |
state_dict = _convert_kohya_lora(state_dict) or state_dict
|
| 1013 |
state_dict = _fit_to_transformer(transformer, state_dict)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1014 |
name = f"lora{index}"
|
| 1015 |
try:
|
| 1016 |
transformer.load_lora_adapter(state_dict, adapter_name=name, prefix=_lora_prefix(state_dict))
|
|
@@ -1021,6 +1042,8 @@ def apply_loras(transformer, loras) -> list[str]:
|
|
| 1021 |
"It is almost always an adapter for another partition or another base model - a `transformer/` "
|
| 1022 |
"adapter does not fit the `transformer_ref/` half this Space runs."
|
| 1023 |
) from error
|
|
|
|
|
|
|
| 1024 |
names.append(name)
|
| 1025 |
scales.append(float(scale))
|
| 1026 |
|
|
@@ -1036,6 +1059,11 @@ def apply_loras(transformer, loras) -> list[str]:
|
|
| 1036 |
param.data = param.data.to(device=base.device, dtype=base.dtype)
|
| 1037 |
|
| 1038 |
transformer.set_adapters(names, scales)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1039 |
return names
|
| 1040 |
|
| 1041 |
|
|
@@ -1222,11 +1250,47 @@ def _generate(prompt_embeds, text_token_tags, references, height, width, num_fra
|
|
| 1222 |
state = _run()
|
| 1223 |
except RuntimeError as error:
|
| 1224 |
message = str(error).lower()
|
| 1225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1226 |
raise
|
| 1227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
_backend("native")
|
| 1229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1230 |
|
| 1231 |
return state.get("videos")[0], state.get("audio")[0].cpu(), state.get("sampling_rate")
|
| 1232 |
|
|
|
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
import json
|
| 11 |
+
import gc
|
| 12 |
import os
|
| 13 |
import random
|
| 14 |
import re
|
|
|
|
| 18 |
import traceback
|
| 19 |
from functools import cache
|
| 20 |
|
| 21 |
+
# Before torch exists: 72 GiB of resident weights leave the rest of the card in pieces, and a request that needs one
|
| 22 |
+
# more large contiguous block then fails on free memory it cannot use in one piece. Expandable segments let the
|
| 23 |
+
# allocator grow a block instead of hunting for one, which is the single biggest difference on this Space.
|
| 24 |
+
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 25 |
+
|
| 26 |
# Before anything that could initialize CUDA: `import spaces` patches `torch.cuda` so the 72 GiB load can happen at
|
| 27 |
# startup rather than on GPU time.
|
| 28 |
import spaces
|
|
|
|
| 50 |
# An attached adapter adds its own layers and their activations to the same card, so the ceiling above is not the
|
| 51 |
# ceiling any more. Refusing a request that is over the reduced one is a sentence on screen; letting it through is a
|
| 52 |
# dead worker and a bare "runtime error".
|
| 53 |
+
LORA_SEQUENCE_FRACTION = float(os.environ.get("H3_LORA_SEQUENCE_FRACTION", "0.75"))
|
| 54 |
|
| 55 |
|
| 56 |
def sequence_ceiling(loras=()) -> int:
|
|
|
|
| 1001 |
|
| 1002 |
for name in list(getattr(transformer, "peft_config", None) or {}):
|
| 1003 |
transformer.delete_adapters(name)
|
| 1004 |
+
gc.collect()
|
| 1005 |
+
if torch.cuda.is_available():
|
| 1006 |
+
torch.cuda.empty_cache()
|
| 1007 |
+
|
| 1008 |
+
base_dtype = next(
|
| 1009 |
+
(param.dtype for key, param in transformer.named_parameters() if ".lora_" not in key), torch.bfloat16
|
| 1010 |
+
)
|
| 1011 |
|
| 1012 |
names, scales = [], []
|
| 1013 |
for index, (path, scale) in enumerate(loras):
|
|
|
|
| 1024 |
else:
|
| 1025 |
state_dict = _convert_kohya_lora(state_dict) or state_dict
|
| 1026 |
state_dict = _fit_to_transformer(transformer, state_dict)
|
| 1027 |
+
# Cast on the host, before anything crosses onto the card. A float32 adapter injected as-is is put on the
|
| 1028 |
+
# card at float32 and cast afterwards, so for a moment both copies are resident - on top of 72 GiB of
|
| 1029 |
+
# weights that is exactly the block the allocator cannot find.
|
| 1030 |
+
state_dict = {
|
| 1031 |
+
key: (value.to(base_dtype) if hasattr(value, "is_floating_point") and value.is_floating_point()
|
| 1032 |
+
else value)
|
| 1033 |
+
for key, value in state_dict.items()
|
| 1034 |
+
}
|
| 1035 |
name = f"lora{index}"
|
| 1036 |
try:
|
| 1037 |
transformer.load_lora_adapter(state_dict, adapter_name=name, prefix=_lora_prefix(state_dict))
|
|
|
|
| 1042 |
"It is almost always an adapter for another partition or another base model - a `transformer/` "
|
| 1043 |
"adapter does not fit the `transformer_ref/` half this Space runs."
|
| 1044 |
) from error
|
| 1045 |
+
del state_dict
|
| 1046 |
+
gc.collect()
|
| 1047 |
names.append(name)
|
| 1048 |
scales.append(float(scale))
|
| 1049 |
|
|
|
|
| 1059 |
param.data = param.data.to(device=base.device, dtype=base.dtype)
|
| 1060 |
|
| 1061 |
transformer.set_adapters(names, scales)
|
| 1062 |
+
# The load and the cast both leave freed blocks behind; handing them back before the denoise starts is what
|
| 1063 |
+
# keeps the first large allocation of the run from failing on a card that has the memory but not in one piece.
|
| 1064 |
+
gc.collect()
|
| 1065 |
+
if torch.cuda.is_available():
|
| 1066 |
+
torch.cuda.empty_cache()
|
| 1067 |
return names
|
| 1068 |
|
| 1069 |
|
|
|
|
| 1250 |
state = _run()
|
| 1251 |
except RuntimeError as error:
|
| 1252 |
message = str(error).lower()
|
| 1253 |
+
kernel = "no available kernel" in message or "cudnn" in message
|
| 1254 |
+
# `NVML_SUCCESS == r INTERNAL ASSERT FAILED` out of `CUDACachingAllocator.cpp` is not a bug to report to
|
| 1255 |
+
# PyTorch: it is the allocator failing to grow, i.e. out of memory, with its own error path falling over on
|
| 1256 |
+
# the way to saying so. Same treatment as a plain OOM.
|
| 1257 |
+
starved = (
|
| 1258 |
+
"out of memory" in message
|
| 1259 |
+
or "nvml" in message
|
| 1260 |
+
or "cudacachingallocator" in message
|
| 1261 |
+
or "cuda error" in message
|
| 1262 |
+
)
|
| 1263 |
+
if not (kernel or starved):
|
| 1264 |
raise
|
| 1265 |
+
|
| 1266 |
+
gc.collect()
|
| 1267 |
+
try:
|
| 1268 |
+
torch.cuda.empty_cache()
|
| 1269 |
+
torch.cuda.synchronize()
|
| 1270 |
+
except Exception: # noqa: BLE001
|
| 1271 |
+
pass
|
| 1272 |
_backend("native")
|
| 1273 |
+
print(f"[ref2va] first attempt failed ({type(error).__name__}); retrying once on a cleared card", flush=True)
|
| 1274 |
+
|
| 1275 |
+
try:
|
| 1276 |
+
state = _run()
|
| 1277 |
+
except RuntimeError as second:
|
| 1278 |
+
second_message = str(second).lower()
|
| 1279 |
+
if not (
|
| 1280 |
+
"out of memory" in second_message
|
| 1281 |
+
or "nvml" in second_message
|
| 1282 |
+
or "cudacachingallocator" in second_message
|
| 1283 |
+
):
|
| 1284 |
+
raise
|
| 1285 |
+
traceback.print_exc()
|
| 1286 |
+
raise gr.Error(
|
| 1287 |
+
f"The card ran out of memory at {width}x{height}, {num_frames / FPS:.1f} s, "
|
| 1288 |
+
f"{len(references)} reference(s)"
|
| 1289 |
+
f"{f' and {len(loras or ())} lora' if loras else ''}. "
|
| 1290 |
+
"A lora takes its share of the same card, so a setting that worked without one can be too much "
|
| 1291 |
+
"with it. Pick a smaller canvas — a 1:1 one is the smallest — shorten the duration, or drop a "
|
| 1292 |
+
"reference, and it goes through."
|
| 1293 |
+
) from second
|
| 1294 |
|
| 1295 |
return state.get("videos")[0], state.get("audio")[0].cpu(), state.get("sampling_rate")
|
| 1296 |
|