Powrót do strony głównej

FLUX.2-dev GGUF Q4_K_M: 29 GB VRAM na Apple Silicon

Analiza zużycia pamięci przez model FLUX.2-dev GGUF Q4_K_M na Apple M3 Pro wykazała szczyt 28,92 GB zamiast oczekiwanych 19 GB. Główne pozycje zużycia — MPS-overhead i dekwantyzacja wag. Zaproponowano optymalizacje z inkrementalnym przenoszeniem i empty_cache, obniżające szczyt o 1,5 GB.

29 GB VRAM w FLUX.2-dev GGUF: analiza i optymalizacje na M3 Pro
Advertisement 728x90

Dlaczego FLUX.2-dev GGUF Q4_K_M zużywa 29 GB pamięci VRAM na Apple Silicon?

Model FLUX.2-dev w wersji skwantyzowanej GGUF Q4_K_M powinien teoretycznie zajmować około 19 GB pamięci, ale w praktyce na Apple Silicon z chipem M3 Pro osiąga szczytowy poziom 28,92 GB. Analiza wykazuje, że dodatkowe gigabajty są zużywane przez narzut MPS, dekodowanie wag oraz przesyłanie danych między CPU a GPU w jednolitej pamięci.

Architektura ładowania i kolejne wyładowanie

FLUX.2-dev używa tekstowego enkodera Mistral-Small-3.2-24B, który w pełnej wersji wymaga 45 GB pamięci VRAM. W przypadku Apple Silicon stosowana jest wersja 4-bitowa poprzez MLX, zużywająca 16 GB. Proces działa jako kolejne wyładowanie:

  • Załadowanie enkodera MLX (~13 GB).
  • Kodowanie promptu do embeddings na CPU.
  • Wyładowanie enkodera z wyczyszczeniem cache.
  • Załadowanie modelu DiT w formacie GGUF Q4_K_M (~19 GB na dysku).
  • Inicjalizacja VAE i schedulera na CPU.
  • Przeniesienie całego pipeline’a na MPS.

Teoretyczny szczyt to maksimum rozmiarów komponentów, ale rzeczywistość różni się z powodu specyfiki jednolitej pamięci.

Google AdInline article slot

Środowisko testowe

  • macOS: 26.3.1 (Tahoe)
  • Chip: Apple M3 Pro, 36 GB jednolitej pamięci
  • Python: 3.14.3
  • PyTorch: 2.11.0
  • diffusers: 0.38.0.dev
  • mlx-lm: 0.31.1

Profiling prowadzony był za pomocą phys_footprint (analog metryki Activity Monitor) oraz metryk MPS w PyTorch. psutil.rss nie uwzględnia alokacji MPS.

Szczegółowa analiza zużycia pamięci

Pomiar etapowy

| Etap | phys_footprint | MPS driver | MPS current |

|------|----------------|------------|-------------|

Google AdInline article slot

| Podstawa | 0,27 GB | 0 | 0 |

| Enkoder MLX załadowany | 13,22 GB | 0 | 0 |

| GGUF na CPU | 19,23 GB | 0 | 0 |

Google AdInline article slot

| pipe.to(mps) | 23,79 GB | 20,06 GB | 18,75 GB |

| Pośrodku inferencji | 26,50 GB | 25,62 GB | 18,78 GB |

Szczyt 28,92 GB pojawia się podczas pipe.to("mps"), ponieważ jednocześnie istnieją kopie danych na CPU i MPS.

Anatomia 28 GB

  • DiT Q4_K_M (uint8): 18,59 GB
  • VAE (bf16): 0,16 GB
  • Narzut puli MPS: +1,31 GB
  • IOKit/Metal graphCache: ~3,73 GB (shader'y, tabele stron, limbo buffers)

Wagi GGUF pozostają w formacie uint8 na MPS, dekodowanie do bf16 odbywa się w trakcie forward pass po warstwach. Cache tymczasowych tensorów bf16 (do 6,92 GB) gromadzi się stopniowo.

Optymalizacje zmniejszające szczyt

  • Krokowe przenoszenie na MPS: Przenoszenie parametrów krok po kroku z gc.collect() co 30 parametrów.
def _move_to_device_incremental(pipe, device):
    n = 0
    for attr in list(vars(pipe)):
        comp = getattr(pipe, attr, None)
        if not isinstance(comp, torch.nn.Module):
            continue
        for param in comp.parameters():
            param.data = param.data.to(device)
            n += 1
            if n % 30 == 0:
                gc.collect()
        for buf in comp.buffers():
            buf.data = buf.data.to(device)
        gc.collect()
    torch.mps.synchronize()
    torch.mps.empty_cache()
  • empty_cache() między krokami: Callback w trakcie inferencji z torch.mps.empty_cache() do wyczyszczenia cache dekodowania.
  • Flush po generacji: Po zakończeniu inferencji — gc.collect() + torch.mps.empty_cache().

Wyniki optymalizacji

| Metryka | Przed | Po | Delta |

|---------|-------|----|-------|

| Szczyt phys_footprint | 28,92 GB | 27,36 GB | -1,56 GB |

| Stała po inferencji | 26,25 GB | 21,76 GB | -4,49 GB |

| Czas generacji (1 krok) | 2:04 | 2:11 | +7s |

Szczyt spada dzięki inkrementalnemu GC podczas ładowania, stan stały dzięki flush’owi cache. Nieoptymalizowalny narzut MPS/IOKit wynosi ok. 3,5 GB.

Co ważne:

  • Minimalny stan stały: ~24 GB, szczyt inferencji ~27 GB.
  • Główny koszt: 18,59 GB na wagach uint8 DiT + 3,5 GB narzutu MPS.
  • Dekodowanie do bf16 dodaje 3–5 GB buforów tymczasowych.
  • Przesyłanie z CPU do MPS podwaja szczyt ze względu na jednolitą pamięć.
  • Optymalizacje dają -1,5 GB szczytu i -4,5 GB stanu stałego bez utraty jakości.

Rzeczywisty minimum dla FLUX.2-dev GGUF na M3 Pro to 24+ GB jednolitej pamięci. Dla mniejszych ilości potrzebna jest bardziej agresywna kwantyzacja lub wyładowanie na CPU.

— Editorial Team

Advertisement 728x90

Czytaj dalej