someone-in-the-world Claude Sonnet 5 commited on
Commit
ae1d43b
·
1 Parent(s): 39867e3

Load text_encoder as int8 (bitsandbytes) when TEXT_ENCODER_INT8_REPO is set

Browse files

bitsandbytes 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>

Files changed (2) hide show
  1. app.py +39 -2
  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