Spaces:
Running on Zero
Running on Zero
Commit ·
ae1d43b
1
Parent(s): 39867e3
Load text_encoder as int8 (bitsandbytes) when TEXT_ENCODER_INT8_REPO is set
Browse filesbitsandbytes 8bit models can't be moved between devices with `.to()`
(unconditional in transformers, unlike the version-gated allowance for
4bit), so this can't reuse the transformer's cpu-load-then-.to(device)
pattern. Instead the quantized text_encoder is loaded directly onto the
target CUDA device on first inference call per worker, guarded so it
only happens once. Also fixes a crash: high_detail mode's pre-decode
text_encoder.to("cpu") offload is skipped once text_encoder is int8,
since .to() would raise for it regardless of target device.
Repo id comes from the TEXT_ENCODER_INT8_REPO secret; falls back to the
existing bf16 text_encoder when unset.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- app.py +39 -2
- requirements.txt +1 -0
app.py
CHANGED
|
@@ -75,6 +75,7 @@ from mode import Mode
|
|
| 75 |
print("[startup] importing diffusers...", flush=True)
|
| 76 |
from diffusers import FlowMatchEulerDiscreteScheduler
|
| 77 |
from diffusers.models.normalization import RMSNorm
|
|
|
|
| 78 |
print("[startup] importing QwenImageEditPlusPipeline...", flush=True)
|
| 79 |
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
| 80 |
print("[startup] importing QwenImageTransformer2DModel...", flush=True)
|
|
@@ -501,6 +502,37 @@ def _log_gpu_properties(cuda_ok):
|
|
| 501 |
# net in case that estimate is off.
|
| 502 |
_FAST_PATH_MIN_GB = 40
|
| 503 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
|
| 505 |
def _place_pipe_on_device(cuda_ok, gpu_props, t0):
|
| 506 |
if getattr(pipe.transformer, "_hf_hook", None) is not None:
|
|
@@ -546,14 +578,18 @@ def _make_step_callback(steps, timer, t0, mode: Mode):
|
|
| 546 |
# Dropping it (~15GB) ahead of VAE decode's fp32-upcast memory spike only pays off
|
| 547 |
# when that spike is big enough to need the headroom (see Mode.offloads_text_encoder_before_decode).
|
| 548 |
# Also skipped when accelerate hooks are managing placement (offload-fallback path)
|
| 549 |
-
# to avoid fighting their own device bookkeeping
|
|
|
|
|
|
|
| 550 |
if step_idx == steps - 1 and getattr(pipeline.text_encoder, "_hf_hook", None) is None:
|
| 551 |
-
if mode.offloads_text_encoder_before_decode:
|
| 552 |
_offload_t0 = time.perf_counter()
|
| 553 |
pipeline.text_encoder.to("cpu")
|
| 554 |
torch.cuda.empty_cache()
|
| 555 |
_offload_ms = (time.perf_counter() - _offload_t0) * 1000
|
| 556 |
print(f"[infer] text_encoder offload to cpu — {_offload_ms:.0f}ms | t={time.perf_counter()-t0:.1f}s")
|
|
|
|
|
|
|
| 557 |
else:
|
| 558 |
print(f"[infer] skipping text_encoder offload for mode={mode.value} (ample headroom at this resolution)")
|
| 559 |
delta_ms = (now - (step_times[-2] if len(step_times) > 1 else t0)) * 1000
|
|
@@ -582,6 +618,7 @@ def _infer_gpu(pil_images, prompt, seed, guidance_scale, steps, width, height, m
|
|
| 582 |
_log_infer_start(prompt, steps, guidance_scale, seed, gpu_duration, mode)
|
| 583 |
gpu_props = _log_gpu_properties(_cuda_ok)
|
| 584 |
|
|
|
|
| 585 |
_place_pipe_on_device(_cuda_ok, gpu_props, t0)
|
| 586 |
print(f"[infer] {_gpu_mem_str(_cuda_ok)} — t={time.perf_counter()-t0:.1f}s")
|
| 587 |
|
|
|
|
| 75 |
print("[startup] importing diffusers...", flush=True)
|
| 76 |
from diffusers import FlowMatchEulerDiscreteScheduler
|
| 77 |
from diffusers.models.normalization import RMSNorm
|
| 78 |
+
from transformers import Qwen2_5_VLForConditionalGeneration
|
| 79 |
print("[startup] importing QwenImageEditPlusPipeline...", flush=True)
|
| 80 |
from qwenimage.pipeline_qwenimage_edit_plus import QwenImageEditPlusPipeline
|
| 81 |
print("[startup] importing QwenImageTransformer2DModel...", flush=True)
|
|
|
|
| 502 |
# net in case that estimate is off.
|
| 503 |
_FAST_PATH_MIN_GB = 40
|
| 504 |
|
| 505 |
+
# int8 (bitsandbytes) quantized text_encoder, produced offline from the FireRed
|
| 506 |
+
# text_encoder's bf16 weights (~8.75GB vs ~15.4GB for the bf16 original). Repo id comes
|
| 507 |
+
# from a secret rather than being hardcoded here.
|
| 508 |
+
_TEXT_ENCODER_INT8_REPO = os.environ.get("TEXT_ENCODER_INT8_REPO")
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def _ensure_int8_text_encoder(cuda_ok, t0):
|
| 512 |
+
# bitsandbytes 8bit modules can't be moved between devices with `.to()` (transformers
|
| 513 |
+
# raises unconditionally for 8bit, unlike the version-gated allowance for 4bit), so this
|
| 514 |
+
# can't follow the fp8 transformer's cpu-load-then-.to(device) pattern — it must be loaded
|
| 515 |
+
# directly onto the target CUDA device, which is only visible inside this @spaces.GPU call.
|
| 516 |
+
# Guarded so it only runs once per worker; diffusers' pipe.to(device) in
|
| 517 |
+
# _place_pipe_on_device already knows to skip an 8bit-quantized module it finds pre-placed.
|
| 518 |
+
if not cuda_ok or not _TEXT_ENCODER_INT8_REPO or getattr(pipe, "_text_encoder_is_int8", False):
|
| 519 |
+
return
|
| 520 |
+
try:
|
| 521 |
+
_t_load = time.perf_counter()
|
| 522 |
+
quantized = Qwen2_5_VLForConditionalGeneration.from_pretrained(
|
| 523 |
+
_TEXT_ENCODER_INT8_REPO,
|
| 524 |
+
device_map={"": device},
|
| 525 |
+
dtype=torch.bfloat16,
|
| 526 |
+
)
|
| 527 |
+
pipe.text_encoder = quantized
|
| 528 |
+
pipe._text_encoder_is_int8 = True
|
| 529 |
+
print(
|
| 530 |
+
f"[infer] loaded int8 text_encoder from {_TEXT_ENCODER_INT8_REPO} — "
|
| 531 |
+
f"{(time.perf_counter()-_t_load)*1000:.0f}ms | t={time.perf_counter()-t0:.1f}s"
|
| 532 |
+
)
|
| 533 |
+
except Exception as e:
|
| 534 |
+
print(f"[infer] WARNING: int8 text_encoder load failed, keeping bf16: {type(e).__name__}: {e}")
|
| 535 |
+
|
| 536 |
|
| 537 |
def _place_pipe_on_device(cuda_ok, gpu_props, t0):
|
| 538 |
if getattr(pipe.transformer, "_hf_hook", None) is not None:
|
|
|
|
| 578 |
# Dropping it (~15GB) ahead of VAE decode's fp32-upcast memory spike only pays off
|
| 579 |
# when that spike is big enough to need the headroom (see Mode.offloads_text_encoder_before_decode).
|
| 580 |
# Also skipped when accelerate hooks are managing placement (offload-fallback path)
|
| 581 |
+
# to avoid fighting their own device bookkeeping, and when text_encoder is int8
|
| 582 |
+
# (bitsandbytes) quantized — `.to()` is unconditionally unsupported for 8bit models
|
| 583 |
+
# (would raise), and its ~8.75GB footprint needs this safety net less anyway.
|
| 584 |
if step_idx == steps - 1 and getattr(pipeline.text_encoder, "_hf_hook", None) is None:
|
| 585 |
+
if mode.offloads_text_encoder_before_decode and not getattr(pipeline, "_text_encoder_is_int8", False):
|
| 586 |
_offload_t0 = time.perf_counter()
|
| 587 |
pipeline.text_encoder.to("cpu")
|
| 588 |
torch.cuda.empty_cache()
|
| 589 |
_offload_ms = (time.perf_counter() - _offload_t0) * 1000
|
| 590 |
print(f"[infer] text_encoder offload to cpu — {_offload_ms:.0f}ms | t={time.perf_counter()-t0:.1f}s")
|
| 591 |
+
elif getattr(pipeline, "_text_encoder_is_int8", False):
|
| 592 |
+
print("[infer] skipping text_encoder offload (int8, .to() unsupported / smaller footprint)")
|
| 593 |
else:
|
| 594 |
print(f"[infer] skipping text_encoder offload for mode={mode.value} (ample headroom at this resolution)")
|
| 595 |
delta_ms = (now - (step_times[-2] if len(step_times) > 1 else t0)) * 1000
|
|
|
|
| 618 |
_log_infer_start(prompt, steps, guidance_scale, seed, gpu_duration, mode)
|
| 619 |
gpu_props = _log_gpu_properties(_cuda_ok)
|
| 620 |
|
| 621 |
+
_ensure_int8_text_encoder(_cuda_ok, t0)
|
| 622 |
_place_pipe_on_device(_cuda_ok, gpu_props, t0)
|
| 623 |
print(f"[infer] {_gpu_mem_str(_cuda_ok)} — t={time.perf_counter()-t0:.1f}s")
|
| 624 |
|
requirements.txt
CHANGED
|
@@ -2,6 +2,7 @@ accelerate==1.14.0
|
|
| 2 |
diffusers==0.39.0
|
| 3 |
peft==0.19.1
|
| 4 |
transformers==4.57.1
|
|
|
|
| 5 |
huggingface_hub
|
| 6 |
pyarrow
|
| 7 |
sentencepiece
|
|
|
|
| 2 |
diffusers==0.39.0
|
| 3 |
peft==0.19.1
|
| 4 |
transformers==4.57.1
|
| 5 |
+
bitsandbytes==0.47.0
|
| 6 |
huggingface_hub
|
| 7 |
pyarrow
|
| 8 |
sentencepiece
|