Fun CN: stream fp8 DiT on CUDA inside @spaces.GPU xlarge (no host bf16)
Browse files
app.py
CHANGED
|
@@ -135,7 +135,7 @@ CN_FILE = os.environ.get(
|
|
| 135 |
GPU_SIZE = (os.environ.get("NFA_FUN_CN_GPU_SIZE") or "xlarge").strip().lower()
|
| 136 |
if GPU_SIZE not in ("large", "xlarge"):
|
| 137 |
GPU_SIZE = "xlarge"
|
| 138 |
-
GPU_DURATION = int(os.environ.get("NFA_FUN_CN_GPU_DURATION") or "
|
| 139 |
WEIGHT_DTYPE = torch.bfloat16
|
| 140 |
MEM_MODE = (
|
| 141 |
os.environ.get("NFA_FUN_CN_MEM_MODE") or "model_cpu_offload_and_qfloat8"
|
|
@@ -281,32 +281,38 @@ def _fp8_dtype_for_key(key: str) -> torch.dtype:
|
|
| 281 |
return torch.float8_e4m3fn
|
| 282 |
|
| 283 |
|
| 284 |
-
def _set_tensor(
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
| 290 |
from accelerate.utils import set_module_tensor_to_device
|
| 291 |
|
|
|
|
|
|
|
| 292 |
value = tensor.detach().to(dtype=WEIGHT_DTYPE)
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
del value
|
| 297 |
-
if _fp8_dtype_for_key(key) != torch.float8_e4m3fn:
|
| 298 |
-
return
|
| 299 |
-
# Walk to the Parameter and cast storage in-place (frees bf16 bits).
|
| 300 |
-
mod: torch.nn.Module = model
|
| 301 |
-
parts = key.split(".")
|
| 302 |
-
for p in parts[:-1]:
|
| 303 |
-
mod = getattr(mod, p)
|
| 304 |
-
leaf = parts[-1]
|
| 305 |
-
param = getattr(mod, leaf)
|
| 306 |
-
if isinstance(param, torch.nn.Parameter):
|
| 307 |
-
param.data = param.data.to(torch.float8_e4m3fn)
|
| 308 |
-
elif isinstance(param, torch.Tensor):
|
| 309 |
-
setattr(mod, leaf, param.to(torch.float8_e4m3fn))
|
| 310 |
|
| 311 |
|
| 312 |
def _stream_shards_into_model(
|
|
@@ -314,39 +320,46 @@ def _stream_shards_into_model(
|
|
| 314 |
shard_paths: list[str],
|
| 315 |
*,
|
| 316 |
label: str,
|
|
|
|
| 317 |
) -> None:
|
| 318 |
-
"""
|
| 319 |
-
from safetensors
|
| 320 |
|
| 321 |
-
|
|
|
|
| 322 |
loaded = 0
|
| 323 |
skipped = 0
|
| 324 |
for i, path in enumerate(shard_paths):
|
| 325 |
_check_load_deadline(f"{label}_shard_{i}")
|
| 326 |
print(
|
| 327 |
f"[nfa-fun-cn] {label} shard {i+1}/{len(shard_paths)} "
|
| 328 |
-
f"rss_max≈{_rss_gb():.1f}GB path={Path(path).name}",
|
| 329 |
flush=True,
|
| 330 |
)
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
| 349 |
gc.collect()
|
|
|
|
|
|
|
| 350 |
print(
|
| 351 |
f"[nfa-fun-cn] {label} stream done loaded={loaded} skipped={skipped} "
|
| 352 |
f"rss_max≈{_rss_gb():.1f}GB",
|
|
@@ -354,24 +367,16 @@ def _stream_shards_into_model(
|
|
| 354 |
)
|
| 355 |
|
| 356 |
|
| 357 |
-
def _init_missing_control_params(model: torch.nn.Module) -> None:
|
| 358 |
"""Mirror VideoX missing-key init for control blocks (zeros / clones)."""
|
| 359 |
from accelerate.utils import set_module_tensor_to_device
|
| 360 |
|
| 361 |
sd = {k: v for k, v in model.named_parameters()}
|
| 362 |
-
# named_parameters may still be meta; use state_dict meta shapes
|
| 363 |
meta_sd = model.state_dict()
|
| 364 |
-
missing = [
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
]
|
| 369 |
-
if not missing:
|
| 370 |
-
# Also detect uninitialized via device
|
| 371 |
-
missing = []
|
| 372 |
-
for name, param in model.named_parameters():
|
| 373 |
-
if param.device.type == "meta":
|
| 374 |
-
missing.append(name)
|
| 375 |
if not missing:
|
| 376 |
print("[nfa-fun-cn] no meta params left before Fun CN overlay", flush=True)
|
| 377 |
return
|
|
@@ -381,65 +386,72 @@ def _init_missing_control_params(model: torch.nn.Module) -> None:
|
|
| 381 |
for key in missing:
|
| 382 |
shape = tuple(meta_sd[key].shape)
|
| 383 |
dtype = _fp8_dtype_for_key(key)
|
| 384 |
-
# Prefer clone from non-control twin when VideoX does
|
| 385 |
twin = key.replace("control_", "")
|
| 386 |
if "control" in key and twin in sd and sd[twin].device.type != "meta":
|
| 387 |
value = sd[twin].detach().to(dtype=torch.bfloat16).to(dtype=dtype)
|
| 388 |
-
elif "after_proj" in key or "before_proj" in key:
|
| 389 |
-
value = torch.zeros(shape, dtype=dtype)
|
| 390 |
-
elif "bias" in key:
|
| 391 |
value = torch.zeros(shape, dtype=dtype)
|
| 392 |
else:
|
| 393 |
value = torch.zeros(shape, dtype=dtype)
|
| 394 |
-
set_module_tensor_to_device(
|
|
|
|
|
|
|
| 395 |
|
| 396 |
|
| 397 |
-
def _overlay_fun_cn(
|
|
|
|
|
|
|
| 398 |
"""Apply Fun CN Union weights without loading a second full DiT."""
|
| 399 |
from safetensors import safe_open
|
| 400 |
|
| 401 |
-
|
| 402 |
loaded = 0
|
| 403 |
skipped = 0
|
| 404 |
print(
|
| 405 |
-
f"[nfa-fun-cn] Fun CN overlay {cn_path.name} "
|
| 406 |
f"rss_max≈{_rss_gb():.1f}GB",
|
| 407 |
flush=True,
|
| 408 |
)
|
| 409 |
with safe_open(str(cn_path), framework="pt", device="cpu") as f:
|
| 410 |
keys = list(f.keys())
|
| 411 |
if keys == ["state_dict"]:
|
| 412 |
-
# rare wrapper — fall back to full load of inner dict only
|
| 413 |
from safetensors.torch import load_file
|
| 414 |
|
| 415 |
wrapped = load_file(str(cn_path))
|
| 416 |
inner = wrapped.get("state_dict", wrapped)
|
| 417 |
for key, tensor in inner.items():
|
| 418 |
-
if key not in
|
| 419 |
skipped += 1
|
| 420 |
continue
|
| 421 |
-
_set_tensor(model, key, tensor)
|
| 422 |
loaded += 1
|
| 423 |
del wrapped, inner
|
| 424 |
gc.collect()
|
| 425 |
else:
|
| 426 |
-
for key in keys:
|
| 427 |
-
if key not in
|
| 428 |
skipped += 1
|
| 429 |
continue
|
| 430 |
tensor = f.get_tensor(key)
|
| 431 |
-
if tuple(tensor.shape) !=
|
| 432 |
skipped += 1
|
|
|
|
| 433 |
continue
|
| 434 |
-
_set_tensor(model, key, tensor)
|
| 435 |
loaded += 1
|
| 436 |
del tensor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
gc.collect()
|
| 438 |
return loaded, skipped
|
| 439 |
|
| 440 |
|
| 441 |
-
def _load_control_transformer_fp8(model_name: str, cn_file: str):
|
| 442 |
-
"""Q8-class painter: empty meta → stream
|
| 443 |
global _FUN_CN_LOADED
|
| 444 |
import accelerate
|
| 445 |
from videox_fun.models.flux2_transformer2d_control import (
|
|
@@ -456,8 +468,8 @@ def _load_control_transformer_fp8(model_name: str, cn_file: str):
|
|
| 456 |
])
|
| 457 |
|
| 458 |
print(
|
| 459 |
-
"[nfa-fun-cn]
|
| 460 |
-
f"(NOT full bf16) mem={MEM_MODE} gpu_size={GPU_SIZE}",
|
| 461 |
flush=True,
|
| 462 |
)
|
| 463 |
with accelerate.init_empty_weights():
|
|
@@ -467,15 +479,19 @@ def _load_control_transformer_fp8(model_name: str, cn_file: str):
|
|
| 467 |
shards = sorted(glob.glob(os.path.join(shard_dir, "*.safetensors")))
|
| 468 |
if not shards:
|
| 469 |
raise FileNotFoundError(f"No transformer shards under {shard_dir}")
|
| 470 |
-
_stream_shards_into_model(
|
|
|
|
|
|
|
| 471 |
_check_load_deadline("after_dit_stream")
|
| 472 |
-
_init_missing_control_params(transformer)
|
| 473 |
_check_load_deadline("after_control_init")
|
| 474 |
-
loaded, skipped = _overlay_fun_cn(
|
|
|
|
|
|
|
| 475 |
_FUN_CN_LOADED = True
|
| 476 |
print(
|
| 477 |
f"[nfa-fun-cn] Fun CN loaded overlay_ok={loaded} skipped={skipped} "
|
| 478 |
-
f"rss_max≈{_rss_gb():.1f}GB (REAL Fun CN, fp8 painter)",
|
| 479 |
flush=True,
|
| 480 |
)
|
| 481 |
return transformer
|
|
@@ -508,9 +524,17 @@ def _load_local_quantized_te(te_root: Path):
|
|
| 508 |
|
| 509 |
|
| 510 |
def get_pipe(*, prepare_gpu_offload: bool = False):
|
| 511 |
-
"""
|
| 512 |
global _PIPE, _PIPE_OFFLOAD_READY, _GET_IMAGE_LATENT, _LOAD_T0, _FUN_CN_LOADED
|
| 513 |
if _PIPE is None:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 514 |
_LOAD_T0 = time.time()
|
| 515 |
_FUN_CN_LOADED = False
|
| 516 |
_ensure_weights()
|
|
@@ -525,14 +549,21 @@ def get_pipe(*, prepare_gpu_offload: bool = False):
|
|
| 525 |
_GET_IMAGE_LATENT = get_image_latent
|
| 526 |
model_name = str(MODEL_DIR)
|
| 527 |
cn_file = str(_resolve_cn_path())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 528 |
|
| 529 |
-
transformer = _load_control_transformer_fp8(
|
|
|
|
|
|
|
| 530 |
_check_load_deadline("post_fun_cn")
|
| 531 |
|
| 532 |
vae = AutoencoderKLFlux2.from_pretrained(model_name, subfolder="vae").to(
|
| 533 |
WEIGHT_DTYPE
|
| 534 |
)
|
| 535 |
-
# Tokenizer from base FLUX.2 mount (same vocab as TE).
|
| 536 |
tokenizer = PixtralProcessor.from_pretrained(model_name, subfolder="tokenizer")
|
| 537 |
text_encoder = _load_local_quantized_te(te_root)
|
| 538 |
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
|
|
@@ -547,7 +578,7 @@ def get_pipe(*, prepare_gpu_offload: bool = False):
|
|
| 547 |
)
|
| 548 |
elapsed = time.time() - _LOAD_T0
|
| 549 |
print(
|
| 550 |
-
f"[nfa-fun-cn] Flux2ControlPipeline
|
| 551 |
f"(REAL Fun CN + fp8 DiT + local TE) in {elapsed:.0f}s "
|
| 552 |
f"rss_max≈{_rss_gb():.1f}GB",
|
| 553 |
flush=True,
|
|
@@ -558,12 +589,9 @@ def get_pipe(*, prepare_gpu_offload: bool = False):
|
|
| 558 |
|
| 559 |
device = "cuda"
|
| 560 |
transformer = _PIPE.transformer
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
"model_full_load_and_qfloat8",
|
| 565 |
-
):
|
| 566 |
-
convert_weight_dtype_wrapper(transformer, WEIGHT_DTYPE)
|
| 567 |
if MEM_MODE == "sequential_cpu_offload":
|
| 568 |
_PIPE.enable_sequential_cpu_offload(device=device)
|
| 569 |
elif MEM_MODE in ("model_cpu_offload", "model_cpu_offload_and_qfloat8"):
|
|
@@ -602,7 +630,10 @@ def _generate_still_gpu(
|
|
| 602 |
if not prompt:
|
| 603 |
raise gr.Error("positive prompt is required")
|
| 604 |
depth = _prep_depth(depth_image, w, h)
|
|
|
|
| 605 |
pipe = get_pipe(prepare_gpu_offload=True)
|
|
|
|
|
|
|
| 606 |
control_latent = _GET_IMAGE_LATENT(depth, sample_size=[h, w])[:, :, 0]
|
| 607 |
inpaint_image = torch.zeros([1, 3, h, w])
|
| 608 |
mask_image = torch.ones([1, 1, h, w]) * 255
|
|
@@ -651,12 +682,7 @@ def generate_still(
|
|
| 651 |
"FUN_CN_REQUIRES_DEPTH: depth_image is required for real Fun ControlNet."
|
| 652 |
)
|
| 653 |
_ensure_weights()
|
| 654 |
-
#
|
| 655 |
-
get_pipe(prepare_gpu_offload=False)
|
| 656 |
-
if not _FUN_CN_LOADED:
|
| 657 |
-
raise RuntimeError(
|
| 658 |
-
"FUN_CN_LOAD_ABORT: pipeline built but Fun CN flag false"
|
| 659 |
-
)
|
| 660 |
return _generate_still_gpu(
|
| 661 |
positive,
|
| 662 |
negative or "",
|
|
|
|
| 135 |
GPU_SIZE = (os.environ.get("NFA_FUN_CN_GPU_SIZE") or "xlarge").strip().lower()
|
| 136 |
if GPU_SIZE not in ("large", "xlarge"):
|
| 137 |
GPU_SIZE = "xlarge"
|
| 138 |
+
GPU_DURATION = int(os.environ.get("NFA_FUN_CN_GPU_DURATION") or "600")
|
| 139 |
WEIGHT_DTYPE = torch.bfloat16
|
| 140 |
MEM_MODE = (
|
| 141 |
os.environ.get("NFA_FUN_CN_MEM_MODE") or "model_cpu_offload_and_qfloat8"
|
|
|
|
| 281 |
return torch.float8_e4m3fn
|
| 282 |
|
| 283 |
|
| 284 |
+
def _set_tensor(
|
| 285 |
+
model: torch.nn.Module,
|
| 286 |
+
key: str,
|
| 287 |
+
tensor: torch.Tensor,
|
| 288 |
+
*,
|
| 289 |
+
device: str,
|
| 290 |
+
) -> None:
|
| 291 |
+
"""Assign one weight; prefer CUDA float8 (CPU float8 hung on ZeroGPU host)."""
|
| 292 |
from accelerate.utils import set_module_tensor_to_device
|
| 293 |
|
| 294 |
+
target = _fp8_dtype_for_key(key)
|
| 295 |
+
# Always stage through bf16 for numeric stability, then target dtype on device.
|
| 296 |
value = tensor.detach().to(dtype=WEIGHT_DTYPE)
|
| 297 |
+
if target == torch.float8_e4m3fn and device.startswith("cuda"):
|
| 298 |
+
value = value.to(dtype=target)
|
| 299 |
+
set_module_tensor_to_device(
|
| 300 |
+
model, key, device=device, value=value, dtype=target
|
| 301 |
+
)
|
| 302 |
+
else:
|
| 303 |
+
set_module_tensor_to_device(
|
| 304 |
+
model, key, device=device, value=value, dtype=WEIGHT_DTYPE
|
| 305 |
+
)
|
| 306 |
+
if target == torch.float8_e4m3fn:
|
| 307 |
+
mod: torch.nn.Module = model
|
| 308 |
+
parts = key.split(".")
|
| 309 |
+
for p in parts[:-1]:
|
| 310 |
+
mod = getattr(mod, p)
|
| 311 |
+
leaf = parts[-1]
|
| 312 |
+
param = getattr(mod, leaf)
|
| 313 |
+
if isinstance(param, torch.nn.Parameter):
|
| 314 |
+
param.data = param.data.to(torch.float8_e4m3fn)
|
| 315 |
del value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
|
| 318 |
def _stream_shards_into_model(
|
|
|
|
| 320 |
shard_paths: list[str],
|
| 321 |
*,
|
| 322 |
label: str,
|
| 323 |
+
device: str,
|
| 324 |
) -> None:
|
| 325 |
+
"""Stream shards key-by-key (no full-shard RAM spike) into float8 on device."""
|
| 326 |
+
from safetensors import safe_open
|
| 327 |
|
| 328 |
+
# Shapes only — avoid pulling meta tensors repeatedly.
|
| 329 |
+
shape_map = {k: tuple(v.shape) for k, v in model.state_dict().items()}
|
| 330 |
loaded = 0
|
| 331 |
skipped = 0
|
| 332 |
for i, path in enumerate(shard_paths):
|
| 333 |
_check_load_deadline(f"{label}_shard_{i}")
|
| 334 |
print(
|
| 335 |
f"[nfa-fun-cn] {label} shard {i+1}/{len(shard_paths)} "
|
| 336 |
+
f"device={device} rss_max≈{_rss_gb():.1f}GB path={Path(path).name}",
|
| 337 |
flush=True,
|
| 338 |
)
|
| 339 |
+
with safe_open(path, framework="pt", device="cpu") as f:
|
| 340 |
+
keys = list(f.keys())
|
| 341 |
+
n_keys = len(keys)
|
| 342 |
+
for j, key in enumerate(keys):
|
| 343 |
+
if key not in shape_map:
|
| 344 |
+
skipped += 1
|
| 345 |
+
continue
|
| 346 |
+
tensor = f.get_tensor(key)
|
| 347 |
+
if tuple(tensor.shape) != shape_map[key]:
|
| 348 |
+
skipped += 1
|
| 349 |
+
del tensor
|
| 350 |
+
continue
|
| 351 |
+
_set_tensor(model, key, tensor, device=device)
|
| 352 |
+
loaded += 1
|
| 353 |
+
del tensor
|
| 354 |
+
if (j + 1) % 50 == 0 or (j + 1) == n_keys:
|
| 355 |
+
print(
|
| 356 |
+
f"[nfa-fun-cn] {label} shard {i+1} keys {j+1}/{n_keys} "
|
| 357 |
+
f"loaded={loaded} rss_max≈{_rss_gb():.1f}GB",
|
| 358 |
+
flush=True,
|
| 359 |
+
)
|
| 360 |
gc.collect()
|
| 361 |
+
if device.startswith("cuda"):
|
| 362 |
+
torch.cuda.empty_cache()
|
| 363 |
print(
|
| 364 |
f"[nfa-fun-cn] {label} stream done loaded={loaded} skipped={skipped} "
|
| 365 |
f"rss_max≈{_rss_gb():.1f}GB",
|
|
|
|
| 367 |
)
|
| 368 |
|
| 369 |
|
| 370 |
+
def _init_missing_control_params(model: torch.nn.Module, *, device: str) -> None:
|
| 371 |
"""Mirror VideoX missing-key init for control blocks (zeros / clones)."""
|
| 372 |
from accelerate.utils import set_module_tensor_to_device
|
| 373 |
|
| 374 |
sd = {k: v for k, v in model.named_parameters()}
|
|
|
|
| 375 |
meta_sd = model.state_dict()
|
| 376 |
+
missing = []
|
| 377 |
+
for name, param in model.named_parameters():
|
| 378 |
+
if param.device.type == "meta":
|
| 379 |
+
missing.append(name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
if not missing:
|
| 381 |
print("[nfa-fun-cn] no meta params left before Fun CN overlay", flush=True)
|
| 382 |
return
|
|
|
|
| 386 |
for key in missing:
|
| 387 |
shape = tuple(meta_sd[key].shape)
|
| 388 |
dtype = _fp8_dtype_for_key(key)
|
|
|
|
| 389 |
twin = key.replace("control_", "")
|
| 390 |
if "control" in key and twin in sd and sd[twin].device.type != "meta":
|
| 391 |
value = sd[twin].detach().to(dtype=torch.bfloat16).to(dtype=dtype)
|
| 392 |
+
elif "after_proj" in key or "before_proj" in key or "bias" in key:
|
|
|
|
|
|
|
| 393 |
value = torch.zeros(shape, dtype=dtype)
|
| 394 |
else:
|
| 395 |
value = torch.zeros(shape, dtype=dtype)
|
| 396 |
+
set_module_tensor_to_device(
|
| 397 |
+
model, key, device=device, value=value, dtype=dtype
|
| 398 |
+
)
|
| 399 |
|
| 400 |
|
| 401 |
+
def _overlay_fun_cn(
|
| 402 |
+
model: torch.nn.Module, cn_path: Path, *, device: str
|
| 403 |
+
) -> tuple[int, int]:
|
| 404 |
"""Apply Fun CN Union weights without loading a second full DiT."""
|
| 405 |
from safetensors import safe_open
|
| 406 |
|
| 407 |
+
shape_map = {k: tuple(v.shape) for k, v in model.state_dict().items()}
|
| 408 |
loaded = 0
|
| 409 |
skipped = 0
|
| 410 |
print(
|
| 411 |
+
f"[nfa-fun-cn] Fun CN overlay {cn_path.name} device={device} "
|
| 412 |
f"rss_max≈{_rss_gb():.1f}GB",
|
| 413 |
flush=True,
|
| 414 |
)
|
| 415 |
with safe_open(str(cn_path), framework="pt", device="cpu") as f:
|
| 416 |
keys = list(f.keys())
|
| 417 |
if keys == ["state_dict"]:
|
|
|
|
| 418 |
from safetensors.torch import load_file
|
| 419 |
|
| 420 |
wrapped = load_file(str(cn_path))
|
| 421 |
inner = wrapped.get("state_dict", wrapped)
|
| 422 |
for key, tensor in inner.items():
|
| 423 |
+
if key not in shape_map or tuple(tensor.shape) != shape_map[key]:
|
| 424 |
skipped += 1
|
| 425 |
continue
|
| 426 |
+
_set_tensor(model, key, tensor, device=device)
|
| 427 |
loaded += 1
|
| 428 |
del wrapped, inner
|
| 429 |
gc.collect()
|
| 430 |
else:
|
| 431 |
+
for j, key in enumerate(keys):
|
| 432 |
+
if key not in shape_map:
|
| 433 |
skipped += 1
|
| 434 |
continue
|
| 435 |
tensor = f.get_tensor(key)
|
| 436 |
+
if tuple(tensor.shape) != shape_map[key]:
|
| 437 |
skipped += 1
|
| 438 |
+
del tensor
|
| 439 |
continue
|
| 440 |
+
_set_tensor(model, key, tensor, device=device)
|
| 441 |
loaded += 1
|
| 442 |
del tensor
|
| 443 |
+
if (j + 1) % 50 == 0:
|
| 444 |
+
print(
|
| 445 |
+
f"[nfa-fun-cn] Fun CN overlay keys {j+1}/{len(keys)} "
|
| 446 |
+
f"loaded={loaded}",
|
| 447 |
+
flush=True,
|
| 448 |
+
)
|
| 449 |
gc.collect()
|
| 450 |
return loaded, skipped
|
| 451 |
|
| 452 |
|
| 453 |
+
def _load_control_transformer_fp8(model_name: str, cn_file: str, *, device: str):
|
| 454 |
+
"""Q8-class painter: empty meta → stream to device float8 → Fun CN."""
|
| 455 |
global _FUN_CN_LOADED
|
| 456 |
import accelerate
|
| 457 |
from videox_fun.models.flux2_transformer2d_control import (
|
|
|
|
| 468 |
])
|
| 469 |
|
| 470 |
print(
|
| 471 |
+
"[nfa-fun-cn] load Flux2Control float8 stream "
|
| 472 |
+
f"device={device} (NOT full bf16 host) mem={MEM_MODE} gpu_size={GPU_SIZE}",
|
| 473 |
flush=True,
|
| 474 |
)
|
| 475 |
with accelerate.init_empty_weights():
|
|
|
|
| 479 |
shards = sorted(glob.glob(os.path.join(shard_dir, "*.safetensors")))
|
| 480 |
if not shards:
|
| 481 |
raise FileNotFoundError(f"No transformer shards under {shard_dir}")
|
| 482 |
+
_stream_shards_into_model(
|
| 483 |
+
transformer, shards, label="DiT-fp8", device=device
|
| 484 |
+
)
|
| 485 |
_check_load_deadline("after_dit_stream")
|
| 486 |
+
_init_missing_control_params(transformer, device=device)
|
| 487 |
_check_load_deadline("after_control_init")
|
| 488 |
+
loaded, skipped = _overlay_fun_cn(
|
| 489 |
+
transformer, Path(cn_file), device=device
|
| 490 |
+
)
|
| 491 |
_FUN_CN_LOADED = True
|
| 492 |
print(
|
| 493 |
f"[nfa-fun-cn] Fun CN loaded overlay_ok={loaded} skipped={skipped} "
|
| 494 |
+
f"rss_max≈{_rss_gb():.1f}GB (REAL Fun CN, fp8 painter, {device})",
|
| 495 |
flush=True,
|
| 496 |
)
|
| 497 |
return transformer
|
|
|
|
| 524 |
|
| 525 |
|
| 526 |
def get_pipe(*, prepare_gpu_offload: bool = False):
|
| 527 |
+
"""Build Fun CN pipeline on CUDA when available (avoids host-RAM bf16 hang)."""
|
| 528 |
global _PIPE, _PIPE_OFFLOAD_READY, _GET_IMAGE_LATENT, _LOAD_T0, _FUN_CN_LOADED
|
| 529 |
if _PIPE is None:
|
| 530 |
+
if not prepare_gpu_offload:
|
| 531 |
+
# Do not CPU-preload DiT — that was the prior 35min hang.
|
| 532 |
+
print(
|
| 533 |
+
"[nfa-fun-cn] defer DiT+Fun CN load until @spaces.GPU "
|
| 534 |
+
f"(size={GPU_SIZE})",
|
| 535 |
+
flush=True,
|
| 536 |
+
)
|
| 537 |
+
return None
|
| 538 |
_LOAD_T0 = time.time()
|
| 539 |
_FUN_CN_LOADED = False
|
| 540 |
_ensure_weights()
|
|
|
|
| 549 |
_GET_IMAGE_LATENT = get_image_latent
|
| 550 |
model_name = str(MODEL_DIR)
|
| 551 |
cn_file = str(_resolve_cn_path())
|
| 552 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 553 |
+
if device != "cuda":
|
| 554 |
+
raise RuntimeError(
|
| 555 |
+
"FUN_CN_REQUIRES_CUDA: fp8 Fun CN load must run inside "
|
| 556 |
+
"@spaces.GPU (CPU path hangs / thrash)."
|
| 557 |
+
)
|
| 558 |
|
| 559 |
+
transformer = _load_control_transformer_fp8(
|
| 560 |
+
model_name, cn_file, device=device
|
| 561 |
+
)
|
| 562 |
_check_load_deadline("post_fun_cn")
|
| 563 |
|
| 564 |
vae = AutoencoderKLFlux2.from_pretrained(model_name, subfolder="vae").to(
|
| 565 |
WEIGHT_DTYPE
|
| 566 |
)
|
|
|
|
| 567 |
tokenizer = PixtralProcessor.from_pretrained(model_name, subfolder="tokenizer")
|
| 568 |
text_encoder = _load_local_quantized_te(te_root)
|
| 569 |
scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
|
|
|
|
| 578 |
)
|
| 579 |
elapsed = time.time() - _LOAD_T0
|
| 580 |
print(
|
| 581 |
+
f"[nfa-fun-cn] Flux2ControlPipeline ready "
|
| 582 |
f"(REAL Fun CN + fp8 DiT + local TE) in {elapsed:.0f}s "
|
| 583 |
f"rss_max≈{_rss_gb():.1f}GB",
|
| 584 |
flush=True,
|
|
|
|
| 589 |
|
| 590 |
device = "cuda"
|
| 591 |
transformer = _PIPE.transformer
|
| 592 |
+
convert_weight_dtype_wrapper(transformer, WEIGHT_DTYPE)
|
| 593 |
+
# DiT already resident on CUDA as fp8 — keep full GPU load (xlarge 96GB).
|
| 594 |
+
# TE stays CPU via pipeline hooks when using cpu_offload for VAE/TE only.
|
|
|
|
|
|
|
|
|
|
| 595 |
if MEM_MODE == "sequential_cpu_offload":
|
| 596 |
_PIPE.enable_sequential_cpu_offload(device=device)
|
| 597 |
elif MEM_MODE in ("model_cpu_offload", "model_cpu_offload_and_qfloat8"):
|
|
|
|
| 630 |
if not prompt:
|
| 631 |
raise gr.Error("positive prompt is required")
|
| 632 |
depth = _prep_depth(depth_image, w, h)
|
| 633 |
+
# Load DiT+Fun CN HERE (on GPU) — never full bf16 on host.
|
| 634 |
pipe = get_pipe(prepare_gpu_offload=True)
|
| 635 |
+
if pipe is None or not _FUN_CN_LOADED:
|
| 636 |
+
raise RuntimeError("FUN_CN_LOAD_ABORT: Fun CN not loaded on GPU")
|
| 637 |
control_latent = _GET_IMAGE_LATENT(depth, sample_size=[h, w])[:, :, 0]
|
| 638 |
inpaint_image = torch.zeros([1, 3, h, w])
|
| 639 |
mask_image = torch.ones([1, 1, h, w]) * 255
|
|
|
|
| 682 |
"FUN_CN_REQUIRES_DEPTH: depth_image is required for real Fun ControlNet."
|
| 683 |
)
|
| 684 |
_ensure_weights()
|
| 685 |
+
# Mounts only on host; DiT+Fun CN load is inside @spaces.GPU.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
return _generate_still_gpu(
|
| 687 |
positive,
|
| 688 |
negative or "",
|