Speicheroptimierung für Stable Diffusion 3.5 Medium auf Apple Silicon M1
Das Stable Diffusion 3.5 Medium-Modell mit 2,5 Milliarden Parametern kann bis zu 21 GB Unified Memory auf Apple Silicon verbrauchen, hauptsächlich aufgrund des T5-XXL-Encoders (10,75 GB im fp16-Format). Durch optimiertes Laden lässt sich der Spitzenverbrauch auf nur noch 11,6 GB reduzieren – das ermöglicht eine reibungslose Nutzung auf M1-Macs mit 16 GB RAM. Dieser Leitfaden erläutert die Ursachen und stellt eine schrittweise Ladestrategie vor.
Ursachen für den Speicherspitzen:
- Gewichtsduplikation: Modell wird zunächst auf dem CPU geladen, dann kopiert nach MPS.
enable_model_cpu_offload()ist wirkungslos – CPU und MPS konkurrieren um den Unified Memory.- Beschleunigungs-Hooks mit
device_maphalten starke Referenzen, was die Garbage Collection verhindern.
Standardcode:
pipe = StableDiffusion3Pipeline.from_pretrained(
"stabilityai/stable-diffusion-3.5-medium",
torch_dtype=torch.float16, variant="fp16")
pipe.to("mps")
Spitzen-Speicherbedarf: 21,4 GB unabhängig von num_inference_steps.
Lösung: Schrittweises Laden
Aufteilung in Phasen: Prompt-Encoding (T5), Entladen, dann Generierung (DiT + VAE).
1. Laden mit device_map="balanced"
Direktes Laden in MPS ohne CPU-Staging:
pipe = StableDiffusion3Pipeline.from_pretrained(
path, torch_dtype=torch.float16, device_map="balanced")
T5-Spitze: ~11 GB.
2. Korrekte Hook-Entfernung
from accelerate.hooks import remove_hook_from_submodules
for attr in list(vars(pipe)):
comp = getattr(pipe, attr, None)
if isinstance(comp, torch.nn.Module):
remove_hook_from_submodules(comp)
setattr(pipe, attr, None)
del pipe
gc.collect()
torch.mps.synchronize()
torch.mps.empty_cache()
Ohne diesen Schritt bleibt T5 im Speicher (10,75 GB).
3. Vollständiger schrittweiser Ablauf
Phase 1: Encoder
enc_pipe = StableDiffusion3Pipeline.from_pretrained(path,
transformer=None, vae=None,
text_encoder=None, text_encoder_2=None,
tokenizer=None, tokenizer_2=None,
variant="fp16", torch_dtype=torch.float16,
device_map="balanced")
prompt_embeds, neg_embeds, pooled, neg_pooled = enc_pipe.encode_prompt(
prompt="ein Krieger mit Schwert",
prompt_2="ein Krieger mit Schwert",
prompt_3="ein Krieger mit Schwert",
device="mps", num_images_per_prompt=1)
Nach diesem Schritt entladen.
Phase 2: Generator
gen_pipe = StableDiffusion3Pipeline.from_pretrained(path,
text_encoder=None, text_encoder_2=None, text_encoder_3=None,
tokenizer=None, tokenizer_2=None, tokenizer_3=None,
variant="fp16", torch_dtype=torch.float16)
gen_pipe.to("mps")
image = gen_pipe(
prompt_embeds=prompt_embeds,
negative_prompt_embeds=neg_embeds,
pooled_prompt_embeds=pooled,
negative_pooled_prompt_embeds=neg_pooled,
num_inference_steps=40).images[0]
.to("mps") nutzt den T5-Cache wieder, senkt den DiT-Verbrauch von 11,5 GB auf 6 GB.
Speicherverbrauchsvergleich
| Phase | Vor Optimierung | Nach Optimierung |
|-------------------|------------------|-------------------|
| T5-Laden | 15,3 GB | 11,5 GB |
| T5 nach Entladen | 10,75 GB | 0,01 GB |
| DiT + Diffusion | 8,9 GB | 8,9 GB |
| Spitze | 21,4 GB | 11,6 GB |
Leistung auf Hardware
- M1 16 GB: 4:54 min (T5: 1:06, DiT: 3:34), Spitze 11–13 GB.
- M3 Pro 36 GB: 1:47 min (T5: 0:14, DiT: 1:27), Spitze 9–11 GB.
Auflösung: 512x512, 40 Inferenzschritte.
Wichtige Erkenntnisse:
- T5-XXL ist der Hauptverbraucher (~71 % des Gesamtspeichers).
device_map="balanced"kombiniert mit Hook-Entfernung ist entscheidend für das Entladen.- Schrittweises Laden senkt die Spitzenbelastung um 45 %.
- Funktioniert zuverlässig auf M1 16 GB Macs ohne Swapping.
.to("mps")bei DiT ermöglicht effiziente Cache-Wiederverwendung.
— Editorial Team
Noch keine Kommentare.