loras: load Z-Image via ZImagePipeline (fix 4-bit text-encoder quant-state crash)

QLoRA training crashed in the Qwen3 TEXT ENCODER: the unsloth build quantizes it
too, and loading components piecemeal (AutoModel/AutoencoderKL by subfolder) left
bitsandbytes' 4-bit quant-state unreconstructed -> "FP4 quantization state not
initialized" / AssertionError in Linear4bit.forward.

- Load all components via ZImagePipeline.from_pretrained (the proven inference
  loader, which loads each 4-bit component correctly), then LoRA-train only the
  transformer. VAE/text-encoder are encoded once then freed (4-bit modules can't
  .to('cpu'), so drop refs + free).
- Disable gradient checkpointing for the 4-bit path: the recompute can desync bnb's
  quant-state. At 512/batch-1 the 4-bit base + activations fit without it.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RdMufYvtTbtGDWsiZVoXce
parent 26bdb59e
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API # Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here. # metadata and the admin web UI read from here.
__version__ = "0.1.19" __version__ = "0.1.20"
# Configure the CUDA caching allocator BEFORE torch is imported anywhere. # Configure the CUDA caching allocator BEFORE torch is imported anywhere.
# expandable_segments lets the allocator return freed pages to the driver even # expandable_segments lets the allocator return freed pages to the driver even
......
...@@ -1484,47 +1484,31 @@ def _train_dit(req, base_path, images, instance_prompt, ...@@ -1484,47 +1484,31 @@ def _train_dit(req, base_path, images, instance_prompt,
m = (ms - bs) / (mx - base) m = (ms - bs) / (mx - base)
return seq_len * m + (bs - m * base) return seq_len * m + (bs - m * base)
# ── Load components ──────────────────────────────────────────────────────── # ── Load via the ZImagePipeline (the proven inference loader) ──────────────
_set_progress(status="preparing", message=f"loading Z-Image VAE: {base_path}") # The unsloth build quantizes the TEXT ENCODER too; loading components piecemeal
vae = AutoencoderKL.from_pretrained(base_path, subfolder="vae", torch_dtype=torch.float32) # (AutoModel/AutoencoderKL by subfolder) leaves bitsandbytes' 4-bit quant-state
vae.requires_grad_(False); vae.eval() # unreconstructed -> AssertionError in Linear4bit.forward. The pipeline loads
_set_progress(status="preparing", message="loading Z-Image text encoder (Qwen3)") # every component exactly as inference does (which works), 4-bit where the
tokenizer = AutoTokenizer.from_pretrained(base_path, subfolder="tokenizer") # checkpoint specifies, so the frozen base stays ~4 GB on-GPU (no offload, fast).
text_encoder = AutoModel.from_pretrained(base_path, subfolder="text_encoder", # We LoRA-train only the transformer.
torch_dtype=compute_dtype, trust_remote_code=True) _set_progress(status="preparing", message=f"loading Z-Image pipeline: {base_path}")
text_encoder.requires_grad_(False); text_encoder.eval() pipe = ZImagePipeline.from_pretrained(base_path, torch_dtype=compute_dtype)
# Load the transformer in 4-bit (QLoRA): the frozen base stays ~4 GB so it fits vae = pipe.vae
# on the GPU with no CPU offload — far faster than the full bf16 model, and it tokenizer = pipe.tokenizer
# lets us train directly on the cached 4-bit (e.g. unsloth) build instead of text_encoder = pipe.text_encoder
# downloading the full one. An already-4-bit checkpoint (embedded quant config) transformer = pipe.transformer
# is loaded as-is; a full checkpoint is quantized on load.
_set_progress(status="preparing", message="loading Z-Image transformer (4-bit QLoRA)") def _to_dev(m):
_q4 = _DiffBnb(load_in_4bit=True, bnb_4bit_quant_type="nf4", """Move a component to the GPU unless it's bnb-4-bit (already device-mapped;
bnb_4bit_compute_dtype=compute_dtype) if _DiffBnb else None .to() on a 4-bit module raises)."""
transformer = None
if _q4 is not None:
try: try:
transformer = ZImageTransformer2DModel.from_pretrained( return m.to(device)
base_path, subfolder="transformer", torch_dtype=compute_dtype, except Exception:
quantization_config=_q4) return m
except Exception as _qe:
# Already-quantized checkpoints reject an explicit config — load embedded.
print(f" [lora][zimage] explicit 4-bit load failed ({_qe}); using the "
f"checkpoint's embedded quantization", flush=True)
transformer = None
if transformer is None:
transformer = ZImageTransformer2DModel.from_pretrained(
base_path, subfolder="transformer", torch_dtype=compute_dtype)
_tr_quantized = bool(getattr(transformer, "is_loaded_in_4bit", False) or
getattr(transformer, "is_quantized", False))
if not _tr_quantized:
# bf16 fallback (no bnb): place on GPU explicitly (4-bit models are already
# device-mapped by bitsandbytes).
transformer = transformer.to(device)
# ── VAE encode reference images → scaled latents ─────────────────────────── # ── VAE encode reference images → scaled latents ───────────────────────────
_set_progress(status="preparing", message="encoding reference images (VAE)") _set_progress(status="preparing", message="encoding reference images (VAE)")
vae.to(device) vae = _to_dev(vae); vae.requires_grad_(False); vae.eval()
sf = float(getattr(vae.config, "scaling_factor", 1.0) or 1.0) sf = float(getattr(vae.config, "scaling_factor", 1.0) or 1.0)
shf = float(getattr(vae.config, "shift_factor", 0.0) or 0.0) shf = float(getattr(vae.config, "shift_factor", 0.0) or 0.0)
spatial = (int(resolution) // 16) * 16 spatial = (int(resolution) // 16) * 16
...@@ -1541,11 +1525,15 @@ def _train_dit(req, base_path, images, instance_prompt, ...@@ -1541,11 +1525,15 @@ def _train_dit(req, base_path, images, instance_prompt,
lat = vae.encode(px).latent_dist.sample() lat = vae.encode(px).latent_dist.sample()
lat = (lat - shf) * sf lat = (lat - shf) * sf
latents_list.append(lat.to(compute_dtype).cpu()) latents_list.append(lat.to(compute_dtype).cpu())
vae.to("cpu"); _free_train_vram() try:
vae.to("cpu")
except Exception:
pass
pipe.vae = None; vae = None; _free_train_vram()
# ── Qwen3 text encode of the instance prompt (chat template, hidden[-2]) ──── # ── Qwen3 text encode of the instance prompt (chat template, hidden[-2]) ────
_set_progress(status="preparing", message="encoding prompt (Qwen3)") _set_progress(status="preparing", message="encoding prompt (Qwen3)")
text_encoder.to(device) text_encoder = _to_dev(text_encoder); text_encoder.requires_grad_(False); text_encoder.eval()
with torch.no_grad(): with torch.no_grad():
templated = tokenizer.apply_chat_template( templated = tokenizer.apply_chat_template(
[{"role": "user", "content": instance_prompt}], [{"role": "user", "content": instance_prompt}],
...@@ -1556,7 +1544,11 @@ def _train_dit(req, base_path, images, instance_prompt, ...@@ -1556,7 +1544,11 @@ def _train_dit(req, base_path, images, instance_prompt,
hs = text_encoder(input_ids=ids, attention_mask=msk, hs = text_encoder(input_ids=ids, attention_mask=msk,
output_hidden_states=True).hidden_states[-2] output_hidden_states=True).hidden_states[-2]
prompt_embed = hs[0][msk[0]].to(compute_dtype).cpu() # [seq, dim] prompt_embed = hs[0][msk[0]].to(compute_dtype).cpu() # [seq, dim]
text_encoder.to("cpu"); _free_train_vram() try:
text_encoder.to("cpu")
except Exception:
pass
pipe.text_encoder = None; text_encoder = None; _free_train_vram()
# ── LoRA on the transformer ──────────────────────────────────────────────── # ── LoRA on the transformer ────────────────────────────────────────────────
lora_cfg = PeftLoraConfig(r=rank, lora_alpha=rank, init_lora_weights="gaussian", lora_cfg = PeftLoraConfig(r=rank, lora_alpha=rank, init_lora_weights="gaussian",
...@@ -1564,16 +1556,12 @@ def _train_dit(req, base_path, images, instance_prompt, ...@@ -1564,16 +1556,12 @@ def _train_dit(req, base_path, images, instance_prompt,
transformer.requires_grad_(False) transformer.requires_grad_(False)
transformer.add_adapter(lora_cfg, adapter_name="default") transformer.add_adapter(lora_cfg, adapter_name="default")
_hooks = [] _hooks = []
try: # Gradient checkpointing is intentionally OFF: with a bnb-4-bit frozen base the
transformer.enable_gradient_checkpointing() # checkpoint recompute can desync bitsandbytes' quant-state (the same failure
# Make checkpointing track grads through the frozen/4-bit base: force the # class as the text-encoder load). LoRA grads still reach the adapters without it
# input-embedding output to require grad (Z-Image's input proj is the # (each LoRA layer's own params keep its output in the autograd graph), and at
# all_x_embedder ModuleDict). Without this, QLoRA grads never reach the # 512 res / batch 1 the ~4 GB 4-bit base + activations fit. Re-enable it (plus an
# attention LoRA layers. Handles removed at job end. # all_x_embedder requires-grad hook) only if higher-res training OOMs.
for _emb in transformer.all_x_embedder.values():
_hooks.append(_emb.register_forward_hook(lambda m, i, o: o.requires_grad_(True)))
except Exception as _ge:
print(f" [lora][zimage] gradient-checkpointing setup skipped: {_ge}", flush=True)
transformer.train() transformer.train()
lora_params = [p for p in transformer.parameters() if p.requires_grad] lora_params = [p for p in transformer.parameters() if p.requires_grad]
if not lora_params: if not lora_params:
...@@ -1671,7 +1659,15 @@ def _train_dit(req, base_path, images, instance_prompt, ...@@ -1671,7 +1659,15 @@ def _train_dit(req, base_path, images, instance_prompt,
transformer.delete_adapters("default") transformer.delete_adapters("default")
except Exception: except Exception:
pass pass
try:
pipe.transformer = None
except Exception:
pass
del transformer del transformer
try:
del pipe
except Exception:
pass
_free_train_vram() _free_train_vram()
path = _lora_weight_file(name) or save_dir path = _lora_weight_file(name) or save_dir
_set_progress(active=False, status="done", message="done", path=path) _set_progress(active=False, status="done", message="done", path=path)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment