Retour à l'accueil

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

Analyse de la consommation mémoire par le modèle FLUX.2-dev GGUF Q4_K_M sur Apple M3 Pro révèle un pic de 28.92 GB au lieu des 19 GB attendus. Principaux postes de dépense — MPS-overhead et déquantisation des poids. Optimisations proposées avec transfert incrémental et empty_cache, réduisant le pic de 1.5 GB.

29 GB VRAM dans FLUX.2-dev GGUF : décomposition et optimisations sur M3 Pro
Advertisement 728x90

Pourquoi FLUX.2-dev GGUF Q4_K_M consomme 29 Go de VRAM sur Apple Silicon

Le modèle FLUX.2-dev en format quantifié GGUF Q4_K_M devrait théoriquement utiliser environ 19 Go de mémoire, mais sur Apple Silicon avec un processeur M3 Pro, il atteint un pic à 28,92 Go. L'analyse révèle que ces gigaoctets supplémentaires proviennent de la surcharge MPS, de la déquantification des poids et des transferts de données entre CPU et GPU dans la mémoire unifiée.

Architecture de chargement et déchargement séquentiel

FLUX.2-dev utilise l'encodeur de texte Mistral-Small-3.2-24B, qui nécessite 45 Go de VRAM en précision pleine. Pour Apple Silicon, une version 4 bits via MLX est utilisée, consommant 16 Go. Le processus suit un schéma de déchargement séquentiel :

  • Charger l'encodeur MLX (~13 Go).
  • Encoder la requête en embeddings sur le CPU.
  • Décharger l'encodeur et vider le cache.
  • Charger le modèle DiT (GGUF Q4_K_M) depuis le disque (~19 Go).
  • Initialiser le VAE et le planificateur sur le CPU.
  • Transférer toute la chaîne vers MPS.

Le pic théorique correspond au maximum des tailles individuelles des composants, mais la réalité diffère en raison du comportement de la mémoire unifiée.

Google AdInline article slot

Environnement de test

  • macOS : 26.3.1 (Tahoe)
  • Processeur : Apple M3 Pro, 36 Go de mémoire unifiée
  • Python : 3.14.3
  • PyTorch : 2.11.0
  • diffusers : 0.38.0.dev
  • mlx-lm : 0.31.1

Le profilage a été effectué avec phys_footprint (similaire à Monitor d'activité) et les métriques MPS de PyTorch. psutil.rss ne prend pas en compte les allocations MPS.

Détail de la consommation mémoire

Mesures étape par étape

| Étape | phys_footprint | Pilote MPS | MPS actuel |

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

Google AdInline article slot

| Baseline | 0,27 Go | 0 | 0 |

| Encodeur MLX chargé | 13,22 Go | 0 | 0 |

| GGUF sur CPU | 19,23 Go | 0 | 0 |

Google AdInline article slot

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

| Milieu de l'inférence | 26,50 Go | 25,62 Go | 18,78 Go |

Le pic de 28,92 Go survient lors de pipe.to("mps") en raison de la présence simultanée de copies CPU et MPS des données.

Composition du pic à 28 Go

  • DiT Q4_K_M (uint8) : 18,59 Go
  • VAE (bf16) : 0,16 Go
  • Surcharge du pool MPS : +1,31 Go
  • IOKit/Metal graphCache : ~3,73 Go (shaders, tables de pages, tampons limbo)

Les poids GGUF restent en uint8 sur MPS ; la déquantification en bf16 s’effectue couche par couche pendant le passage avant. Des tenseurs bf16 temporaires (jusqu’à 6,92 Go) s’accumulent dans le cache.

Stratégies d'optimisation pour réduire le pic mémoire

  • Transfert MPS incrémental : Déplacer les paramètres un par un avec gc.collect() tous les 30 paramètres.
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()
  • Vidage de cache entre étapes : Utiliser un rappel pendant les étapes d'inférence avec torch.mps.empty_cache() pour vider le cache de déquantification.
  • Vidage post-génération : Après l'inférence, exécuter gc.collect() suivi de torch.mps.empty_cache().

Résultats des optimisations

| Métrique | Avant | Après | Variation |

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

| Pic phys_footprint | 28,92 Go | 27,36 Go | -1,56 Go |

| État stable après inférence | 26,25 Go | 21,76 Go | -4,49 Go |

| Temps de génération (1 étape) | 2:04 | 2:11 | +7s |

La réduction du pic provient de la collecte de déchets incrémentielle durant le chargement ; l'amélioration de l'état stable résulte du vidage du cache. La surcharge MPS/IOKit non optimisable reste à ~3,5 Go.

Points clés :

  • État stable minimum : ~24 Go, pic d'inférence ~27 Go.
  • Coût principal : 18,59 Go pour les poids DiT uint8 + 3,5 Go de surcharge MPS.
  • La déquantification en bf16 ajoute 3–5 Go de tampons temporaires.
  • Le transfert CPU→MPS double le pic en raison de la mémoire unifiée.
  • Les optimisations permettent une réduction de -1,5 Go au pic et de -4,5 Go à l'état stable sans perte de qualité.

Le minimum réel pour FLUX.2-dev GGUF sur M3 Pro est de 24+ Go de mémoire unifiée. Pour des configurations plus petites, une quantification plus agressive ou un déchargement sur CPU est nécessaire.

— Editorial Team

Advertisement 728x90

Lire ensuite