self-hosted/ai
§01·recipe · image

Qwen-Image-2.1 on Apple M2 Max 64 GB: Full bf16 in mflux, --low-ram for 2K, ComfyUI Edits

imageintermediate64GB+ VRAMSep 24, 2026

This intermediate recipe sets up Qwen-Image-2.1 on the Apple M2 Max, needing about 64 GB of VRAM.

models
tools
prerequisites
  • Research or evaluation use only: the Qwen Research License Agreement grants rights for non-commercial purposes only (sections 1.i and 2.a)
  • Apple M2 Max with 64 GB unified memory (either GPU bin, 30-core or 38-core; the bf16 path on this page needs a 64 GB Mac)
  • macOS 14 Sonoma or newer (the MLX 0.32 wheels mflux 0.20.0 needs start at macOS 14)
  • uv, and Python 3.10 or newer, for mflux 0.20.0
  • 34 GB free disk for the text-to-image path, plus 33 GB for the ComfyUI editing files

What You'll Build

A local Qwen-Image-2.1 setup on an Apple M2 Max with 64 GB of unified memory. It has two parts:

  • Text to image and image to image run in mflux 0.20.0, an MLX implementation that runs on the Mac's GPU through Metal. This page runs it at its default, full bf16 weights, which is what mflux's own model README recommends for a 64 GB machine. --low-ram is the switch for more headroom, and it is required at 2K.
  • Instruction-based editing with reference images runs in ComfyUI v0.37.2 on PyTorch's MPS backend, because mflux cannot edit this model yet. Use the bf16 files, which are what every public Mac edit run found that names its files used, and the --cpu-vae switch on the stable PyTorch 2.14.0.

Qwen-Image-2.1 is a 7B single-stream diffusion transformer (DiT). It has 32 layers and 32 attention heads of width 128 (the repo's transformer/config.json), a Qwen3-VL-8B text encoder and a 64-channel RGBA autoencoder, and it was released on 2026-09-20. This page was written from public sources on 2026-09-24; the checks it describes ran between 11:30 and 14:19 UTC that day.

⚠️ Non-commercial only. The weights ship under the Qwen Research License Agreement. Section 1.i defines "Non-Commercial": it "shall mean for research or evaluation purposes only." Section 2.a grants the rights only for that use, "FOR NON-COMMERCIAL PURPOSES ONLY". Section 3.a asks you to pass a copy of the agreement on with any derivative. Every repack named on this page is a derivative bound by the same terms, whatever its own card says (one says Apache-2.0; see Other paths). This is a reader's summary, not legal advice.

Hardware data: Apple M2 Max, 64 GB unified memory (12-core CPU; 30- or 38-core GPU; 400 GB/s) · nothing on this page was measured on an M2 Max by this site · default GPU budget 48.000 GiB; the bf16 default fits it with a thin margin, --low-ram with a wide one · See benchmark data

ℹ️ Nothing on this page was measured by this site. No part of it was run on an M2 Max, or on any other Mac, by this site. The memory figures are byte counts, arithmetic, and other people's reports; each report is attributed where it appears and names its own machine. /check/qwen-image-2-1/m2-max returned verdict: unknown with no benchmarks when this page was written. If you run it, post your numbers.

Requirements

ComponentMinimumThis recipe
Unified memory64 GB for mflux's bf16 default (a 48.000 GiB GPU budget)Apple M2 Max, 64 GB (48.000 GiB budget), not measured by this site; the budget below is derived (/contribute)
OSmacOS 14 Sonoma
Storage33,131,600,661 B for mflux's first download; plus 32,440,124,920 B for the ComfyUI edit files— byte counts from the Hugging Face tree API
Softwaremflux 0.20.0 (Python 3.10+); ComfyUI v0.37.2 with PyTorch 2.14.0 for editing

The machine

Apple's spec pages for the 14-inch and 16-inch MacBook Pro (2023) and the Mac Studio (2023) sell the M2 Max as follows:

GPU coresMemory bandwidthMemory sizes
30400 GB/s32 GB or 64 GB
38400 GB/s32 GB, 64 GB or 96 GB

(Apple gives one bandwidth figure for the M2 Max chip; 96 GB is listed for the 38-core GPU only.)

A 64 GB M2 Max can have either GPU. The recipe on this page is the same on both, because memory decides it. Speed would differ with the core count, but this page has no M2 Max speed figure (Results).

The GPU budget, and what happens above it

A Mac has no separate VRAM. The GPU works in the same memory as everything else, and Metal reports a default ceiling for it as recommendedMaxWorkingSetSize. The value depends on the memory size and, in the logs found for this page, on the macOS version.

On macOS 13 to 15 the logs show exact fractions: two thirds of the memory up to 32 GB, three quarters from 36 GB.

  • 64 GB → 51,539,607,552 B = 48.000 GiB. Printed as recommendedMaxWorkingSetSize = 51539.61 MB by M2 Max machines:
  • 48 GB → 36.000 GiB, printed as 38654.71 MB by an M3 Max with 48 GB on macOS Sonoma 14.5 in a LLamaSharp report. This matters for this page's min_vram_gb below.
  • 32 GB → 21.333 GiB, printed as 22906.50 MB by a 32 GB M1 Max Mac Studio on macOS 13 in llama.cpp #22800.

On macOS 26 the logs found show larger values. These are the ones that name their machine:

  • 64 GB: 55662.79 MB (51.840 GiB, 81.0%), from an M1 Max with 64 GB on macOS 26.4 in LM Studio #1728. An M3 Max on macOS 26.5.2 prints the same value in Handy #1578, without stating its memory.
  • 32 GB: 26800.60 MB (24.960 GiB, 78.0%), from a 32 GB M1 Pro on macOS Tahoe 26.3.1 in LrGenius #136. A 32 GB M2 Max on macOS 26.3 prints 25769.80 MB (24.000 GiB) instead, in llama.cpp #24413, so the macOS 26 value is not the same on every machine.

None of them is from a 64 GB M2 Max, and the one M2 Max log on macOS 26, the 32 GB machine in #24413, reads exactly three quarters, so a 64 GB M2 Max may still read 48.000 GiB there. Every figure on this page uses the older, smaller budget, so if your Mac reports one of the macOS 26 values, the margins are wider than shown. (The macOS 26 values come from reading the 142 logs among the 438 GitHub issues created since 2025-10-01 that mention recommendedMaxWorkingSetSize, on 2026-09-24.) If you want your own value, mflux installs MLX, and MLX's device_info returns it:

uv run --with "mlx==0.32.2" python -c "import mlx.core as mx; print(mx.device_info()['max_recommended_working_set_size'] / 2**30, 'GiB')"
# 48.0 on a 64 GB Mac on macOS 13–15; 51.84 in the macOS 26 logs above (none from an M2 Max)

Going over the budget does not stop either runtime on this page. It makes the Mac swap.

  • MLX 0.32.2. Its allocator (allocator.cpp, lines 63–64 and 108–157) sets its limit at 1.5 × the recommended size, capped at 95% of the machine's memory. Its docstring calls that limit a guideline (memory.cpp). It starts releasing its buffer cache near 95% of the recommended size. On a memory shortfall it throws only when Metal refuses a buffer outright.
  • PyTorch 2.14.0's MPS allocator. It raises MPS backend out of memory past 1.7 × the recommended size, or earlier only if Metal cannot supply the buffer (MPSAllocator.h, lines 388 and 471; MPSAllocator.mm, lines 452–471).

So a job that is too big shows up as memory pressure and a slow, swapping Mac long before it shows up as an error. Stay inside the budget and you avoid both.

What mflux holds, stage by stage

mflux keeps the text encoder in bf16 at every -q setting. Its weight definition says so (skip_quantization=True # Quantization causes significant semantic degradation, qwen21_weight_definition.py). It loads only the encoder's language layers: the canonical encoder index has 750 tensors, 351 of them vision tower plus an lm_head, and the project's own exports hold 399 tensors: the text layers plus rotary_emb.inv_freq.

At the default, nothing is quantized. The sizes are from the project's own bf16 export, mflux-community/qwen-image-2-1-mflux-bf16, written by mflux 0.20.0:

PartBytesGiB
Text encoder (bf16, text layers only)15,136,877,87914.097
Diffusion model (bf16)14,235,024,06513.257
VAE1,323,990,9151.233
All three30,695,892,85928.588

What the code holds, and when (mflux 0.20.0, read at the tag):

  • Weights load lazily. Every part is read through MLX's mx.load, and nothing evaluates them at load (weight_loader.py), so each part takes memory when it is first used.

  • Nothing is computed until the first denoising step. mflux builds the prompt embedding without evaluating it (qwen21_prompt_encoder.py), and the first evaluation is mx.eval(latents) inside the denoising loop (qwen_image_21.py, line 123). The text encoder's pass and the first denoising step therefore run in one evaluation. img2img's VAE encode joins them on the default path, and with --low-ram too when the target size is at most 512 px on both sides (mflux scales the photo to --width/--height before it encodes it). For a larger target (the 1024×1024 command below is one), --low-ram tiles the encode and the tiler evaluates each tile as it goes (vae_tiler.py, lines 29–31 and 75), so the VAE's encoder is read before the loop.

  • The text encoder is dropped on every run, before it has run. With or without --low-ram, the CLI registers a memory saver that deletes mflux's reference to the text encoder and clears MLX's cache just before the loop. The branch without the flag carries the comment "Always evict text encoders after encoding" (callback_manager.py, lines 83–120; memory_saver.py, lines 42–48 and 77–97). For this model the prompt is always in mflux's prompt cache by then, so runs with several seeds drop it too. The pending prompt embedding still refers to the encoder's weights, so they are read, used and released during the first step. Without --low-ram, released buffers can stay in MLX's buffer cache, which mflux does not cap in that mode, until mflux clears the cache after the loop; MLX may also reuse them for later allocations, or reclaim them itself once memory nears 95% of the budget (allocator.cpp, lines 64 and 130–137). With --low-ram, the 1 GB cap releases them.

  • mflux's model README says otherwise. It says the text encoder "is never quantized and stays resident" (model README). The CLI code at the same tag does not keep it resident. This page follows the code.

  • How much is held at once during the first step is up to MLX, which decides at run time when each weight is read and freed. mflux's code does not fix it, and no measurement of it was found. The weight sums are upper bounds, not observed values:

    • text to image: the text encoder and the diffusion model, 29,371,901,944 B = 27.355 GiB;
    • image to image: all three, 28.588 GiB, because the VAE's encoder has been read by then.

    Against 48.000 GiB, the larger still leaves 19.412 GiB for the activations, the VAE decode and macOS's GPU use.

  • From the second step on, text to image holds the diffusion model's 13.257 GiB of weights, in both modes; the VAE is read only at the decode. Image to image also keeps the VAE's encoder weights it read, up to 14.490 GiB in all.

  • What --low-ram adds (callback_manager.py, lines 88–99; memory_saver.py, lines 25–30):

    • a 1 GB cap on MLX's buffer cache;
    • a tiled VAE decode, and a tiled VAE encode for img2img (Qwen-Image-2.1's VAE does not opt out of implicit tiling);
    • a reset of the peak-memory counter after the model is built, which excludes almost nothing, since the weights are read during the run;
    • on a run with one seed and at most one input image, deleting the diffusion model after the last step, so that the decode runs with only the VAE held.

What the bf16 default peaks at. The port's author reports, in PR #736, about 46 GB with bf16 weights at 1024² and 40 steps. The PR does not say whether --low-ram was on, or how memory was read. That run was on an M5 Max. This page uses the figure as a memory data point, not as a timing; that the same run takes about as much memory on an M2 Max is an inference, because MLX's allocations follow the model and the settings.

  • Against 48.000 GiB, 46 GB leaves 5.16 GiB if it is decimal GB, or 2.00 GiB if it is GiB. The PR does not say which, or which instrument it used.
  • mflux's own README gives the same picture: "On Apple Silicon the bf16 default is both the fastest and the most accurate path", and a 64 GB machine is, in its words, the comfortable default, with -q 8 for tighter memory (model README).

Why the margin is thinner than it looks. MLX's own peak counter can miss the VAE decode.

  • mlx-serve's authors measured a 1024² decode of this model's VAE in their own MLX engine at an 18.1 GB process footprint, while mlx_get_peak_memory read 4.9 GB. MLX's 3×3 convolution keeps an unfolded copy of its input outside its counters (docs/gotchas/models-media.md).
  • mflux's default path decodes untiled, with the diffusion model still held. If the 46 GB was read from MLX's counter, the real footprint at the decode may be higher than 46 GB, and that could exceed the 2–5 GiB margin. That is inference: no measurement of mflux's decode footprint on this model turned up in the searches listed under Results.
  • If the Mac swaps, add --low-ram. On a one-seed run it deletes the diffusion model before the decode. It also tiles the decode (the remedy mlx-serve applied; banding brought its footprint to 9.5 GB) and caps MLX's buffer cache, which releases the text encoder's buffers once the first step has used them.
    • Measured effect on other Macs, with -q 8: about 30 GB without it at 1024², from The-Focus-AI/qwen-image-2.1-mlx, whose launcher passes -q 8 and no --low-ram on a 64 GB M4 Max (a memory reading; no timing from that page is used here), against about 15.5 GB with it on a 32 GB M2 Max, whose owner reports in Rapid-MLX #3642 that the flag roughly halved memory use (resolution and method not stated). No bf16 measurement with the flag was found.
    • If that is not enough, use -q 8 as well (a 7.045 GiB diffusion model instead of 13.257, from the second step on; only the pre-quantized mflux-community/qwen-image-2-1-mflux-q8 avoids reading the bf16 weights in the first step). Troubleshooting has the details.

Why min_vram_gb is 64

min_vram_gb is the smallest unified memory Apple sells on which this page's install runs inside the default budget, with no sysctl. The install is mflux's bf16 default, with a reported peak of about 46 GB.

  • 64 GB gives 48.000 GiB: +5.16 GiB if that peak is decimal GB, +2.00 GiB if GiB.
  • 48 GB gives 36.000 GiB: −6.84 GiB or −10.00 GiB. The 48 GB M3 Max and M4 Max pages therefore use -q 8 --low-ram instead.
  • The lighter install is an option on this chip too. The same -q 8 --low-ram command has run on a 32 GB M2 Max: the author of Rapid-MLX #3642 reports it comfortable at 32 GB, using about 15.5 GB, without saying how that was read. That is a memory data point, with no timing.

The floor for this page is 64.

Installation

1. mflux 0.20.0

mflux 0.20.0 went up on PyPI on 2026-09-21. Its release adds mflux-generate-qwen-2.1 for text-to-image and image-to-image, with -q 8 / -q 4 quantization of the diffusion model. Its tag is commit ada53237. The project lives at mflux-community/mflux now; filipstrand/mflux redirects there. Install it as a uv tool, as its README does, but pinned to 0.20.0 (the README's uv tool install --upgrade mflux takes whatever is newest):

uv tool install --python 3.12 "mflux==0.20.0"
mflux-generate-qwen-2.1 --help | head -5

There is nothing CUDA-shaped to install: no cu12x/cu13x wheel index, no FlashAttention, no bitsandbytes. mflux's PyPI metadata requires Python 3.10 or newer and mlx>=0.32.0,<0.33.0 on macOS. The MLX 0.32.2 Metal wheels are built for macOS 14 and later (PyPI), which is where this page's macOS 14 floor comes from. The same metadata also pulls in PyTorch (torch>=2.13.0) as a dependency.

2. Weights

With no --model, the first run downloads the official repo at Qwen/Qwen-Image-2.1 into ~/.cache/huggingface (the model's registered model_name, model_config.py). Only the folders mflux reads come down: transformer/, text_encoder/, vae/ and processor/, 33,131,600,661 B in total at revision 790c9263. -q 8 then quantizes the diffusion model as it loads, on every run.

Optional: smaller and pre-quantized. mflux-community/qwen-image-2-1-mflux-q8 is 24,036,988,319 B. It holds the same text encoder and VAE, and a 7,564,689,541 B 8-bit diffusion model.

  • It sits in the mflux project's own Hugging Face organisation, announced in the project's discussion #743 by a repository collaborator. Its safetensors metadata says mflux_version: 0.20.0.
  • It has no model card and no licence field. The licence is the Qwen Research License above; the repo just does not say so.
  • Load it by name, and do not add -q: the stored quantization is used. It trades the bf16 default's accuracy, and by mflux's own account some speed, for headroom.
mflux-generate-qwen-2.1 --model mflux-community/qwen-image-2-1-mflux-q8 --base-model qwen-image-2.1 \
  --low-ram --prompt "..." --steps 40 --seed 42 --output out.png

Not recommended: mlx-community/Qwen-Image-2.1-MLX-4bit.

  • It quantizes the text encoder to 4-bit, which mflux itself refuses to do.
  • In mflux issue #748 it produced purple-and-green static on mflux 0.20.0. The reporter suspected a corrupt download, and a collaborator suggested an integrity check.
  • The same reporter's image came out right with the mflux-community 4-bit export.

3. For editing only: ComfyUI v0.37.2 on MPS

ComfyUI supports Qwen-Image-2.1 in core since PR #16400. Tag v0.37.2 (commit 830232b8) was the newest tag when this was written; the newest release was still v0.37.0. The README at that tag tells Apple Silicon users to install a PyTorch nightly. This page pins the stable PyTorch 2.14.0 instead and works around its one known defect for this model with --cpu-vae (Running, Editing). The fix for that defect is on PyTorch's main branch, not in 2.14.0, and whether a given nightly carries it is not something this page verified. Pick where ComfyUI will live and set COMFY to it. Every block below uses that variable, so set it again in any new terminal. Then, in a fresh Python 3.12 virtual environment made with the uv from step 1:

export COMFY="$HOME/ComfyUI"
uv venv --seed --python 3.12 ~/comfy-venv && source ~/comfy-venv/bin/activate
pip install "torch==2.14.0" torchvision torchaudio
git clone --branch v0.37.2 https://github.com/Comfy-Org/ComfyUI "${COMFY:?set COMFY first, see step 3}"
pip install -r "${COMFY:?set COMFY first, see step 3}/requirements.txt"
python -c "import torch; print(torch.__version__, torch.backends.mps.is_available())"
# expect: 2.14.0 True

The version pairing is taken from PyPI metadata:

  • torchvision 0.29.0 requires exactly torch==2.14.0.
  • requirements.txt at the tag leaves torch unpinned and pins comfy-kitchen==0.2.35. comfy-kitchen ships a pure-Python wheel, and its eager backend is the one that runs on a Mac.

Download the bf16 diffusion model and text encoder plus the VAE, from the Comfy-Org repack. The repo's folder names match ComfyUI's models/ layout:

source ~/comfy-venv/bin/activate
pip install -U huggingface_hub
hf download Comfy-Org/Qwen-Image-2.1 \
  diffusion_models/qwen_image_2.1_bf16.safetensors \
  text_encoders/qwen3vl_8b_bf16.safetensors \
  vae/qwen_image_2.1_vae_bf16.safetensors \
  --revision 9a44dbdb47cefd046be9c0a13476192f34c8db8e --local-dir "${COMFY:?set COMFY first, see step 3}/models"
shasum -a 256 "$COMFY/models/diffusion_models/qwen_image_2.1_bf16.safetensors"
# 89f4158d066cc33906a199fca85634f766892dd78f49b6698dabf187ac86c4bc
shasum -a 256 "$COMFY/models/text_encoders/qwen3vl_8b_bf16.safetensors"
# 68bdc82bc1b66851162ae656225e7e2068166b603db19bd5d5a3b90eb12669a9
shasum -a 256 "$COMFY/models/vae/qwen_image_2.1_vae_bf16.safetensors"
# bb21f7473051e1ac368515dd3f2e15cd44d7a11748ee8823e1ddca3e4876b7c9

The three files are 14,230,280,616 + 17,534,334,616 + 675,509,688 = 32,440,124,920 B = 30.212 GiB.

Running

Text to image

mflux-generate-qwen-2.1 \
  --prompt "A neon shop sign that reads \"QWEN IMAGE 2.1\", rainy night, reflections on wet pavement" \
  --steps 40 --seed 42 --output neon.png

With no -q, this is the bf16 default. The defaults at the tag are 1024×1024, 40 steps and guidance 1.0; the note in ComfyUI's edit template says the official pipeline uses about 40–50 steps with euler (mflux's model README; CLI qwen21_generate.py).

  • Headroom. Add --low-ram if other big apps are open or Activity Monitor's memory pressure leaves green. On a one-seed run it deletes the diffusion model before the decode; it also tiles the decode and caps MLX's buffer cache (Requirements).
  • The printout. mflux prints Peak MLX memory: … GB at the end of every run (CLI qwen21_generate.py, lines 69–71). That figure is MLX's counter in decimal GB, with the caveat above; with --low-ram it is reset after the model is built, which excludes almost nothing, because the weights are read during the run.
  • Guidance. A negative prompt with --guidance above 1 turns on true classifier-free guidance, which runs the diffusion model twice per step. With no negative prompt the second pass is skipped.
  • Transparency. mflux's VAE decode returns RGB only, per its model README, so a transparent PNG needs another path. Two Mac reports were found. In ComfyUI #16433, a 64 GB M4 Pro Mac mini user got "a transparent image background of the subjects" from the edit template (settings not stated), before hitting the VAE-encode bug below. On stable-diffusion.cpp's Metal backend, a 36 GB M4 Max got backgrounds that decode mostly opaque white (#2024).

Native 2K

The model generates natively at up to 2048×2048 (the note in ComfyUI's edit template says so). On this Mac, use --low-ram for 2K:

mflux-generate-qwen-2.1 --low-ram --width 2048 --height 2048 --prompt "..." --seed 7 --output big.png

A 2048² image is 16,384 latent tokens against 4,096 at 1024².

  • Why --low-ram is required (inference). The bf16 default already sits 2–5 GiB under the budget at 1024². At 2048² the activations grow with the token count, and so does the untiled decode, where MLX's counter undercounts (Requirements). --low-ram tiles the decode, deletes the diffusion model before it on a one-seed run, and caps MLX's buffer cache. (Neither mode keeps the text encoder past the first step.)
  • Not measured. No 2048² mflux run on an M2 Max was found, so neither time nor memory is quoted here.

Image to image

mflux-generate-qwen-2.1 --image photo.png 0.4 --width 1024 --height 1024 --prompt "the same street at night, neon signs" --output night.png

--image PATH STRENGTH is the current flag. The model README's --image-path / --image-strength still work, and the default strength is 0.4.

  • Set the size. Without --width and --height, the output takes the input photo's size (dimension_resolver.py). A 12 MP phone photo would then generate at 4032×3024, far past the 2K case above.
  • The encoder. mflux encodes the input with its own MLX implementation of the VAE encoder. That is not the code path with the PyTorch MPS defect that breaks ComfyUI's edits below.
  • Verification. The port's author reports img2img verified end to end in PR #736. That run was on a different Mac.

Editing (ComfyUI on MPS)

mflux 0.20.0 cannot edit Qwen-Image-2.1. Its model README (line 95) lists the edit/instruction variant and LoRA mappings under "Not yet supported". Two community pull requests that add editing, #741 and #749, were open and unmerged at 12:04 UTC on 2026-09-24. For edits, use the ComfyUI install from step 3.

  1. Start it with --cpu-vae:

    source ~/comfy-venv/bin/activate
    cd "${COMFY:?set COMFY first, see step 3}" && python main.py --cpu-vae
    

    On stable PyTorch 2.14.0 the VAE's encoder is broken on MPS; only the decoder works. ComfyUI #16433 measures an encode-then-decode round trip:

    VAE onPSNR against the input
    MPS (bf16, and again with --fp32-vae)6.60 dB
    CPU, with --cpu-vae49.10 dB
    • What is broken. Every edit encodes its reference images, so every edit on MPS is silently degraded, with no error. Text-to-image only decodes and is clean.
    • Who confirmed it. A second reporter reproduced it on an M3 Max with torch 2.13.0 and traced it to PyTorch's MPS F.pad (pytorch#194922). A commenter measured a PyTorch 2.15 development build as fixed.
    • The torch versions. The fix commits (72bca5e6, 2026-08-31, and ea3ba8f9, 2026-09-02) are on main. They are not in 2.14.0: GitHub's compare API reads them as diverged from the v2.14.0 tag, while a release-branch commit reads as behind it. The 2.14 release branch had one commit after the tag, an unrelated SVD fix.
    • ComfyUI itself. At v0.37.2 it still calls the padding op in AvgDown3D (vae2_2.py, line 256).
    • What --cpu-vae costs (inference): the VAE's encode and decode move to the CPU cores. On unified memory no data crosses a bus, so the cost is CPU time; no isolated timing was found.
  2. Load the template. Open Qwen Image 2.1: Image Edit from the templates (image_qwen_image_2_1_image_edit.json, unchanged since 371a7b7171). Its loaders sit inside the template's Image Edit (Qwen Image 2.1) subgraph node, which shows them as unet_name, clip_name and vae_name widgets, beside resolution. They ask for the int8 files. Set unet_name to qwen_image_2.1_bf16.safetensors and clip_name to qwen3vl_8b_bf16.safetensors; vae_name is already the bf16 VAE.

    • Why bf16. Every public Mac edit run found that names its files used the bf16 pair: #16435's report from an M5 Max, and a 64 GB M4 Pro Mac mini on ComfyUI 0f74f7fb (v0.37.0 plus one commit; its report). That user writes that --cpu-vae made the edit work. Two more Mac edit runs name no files: that user's earlier runs from the default template, whose loaders ask for the int8 files, and a 24 GB M4 Pro Mac mini in the vendor's discussion #36 (three references at 1280×736; runtime and files not stated). No Mac edit run that names the template's int8 files was found. The one Mac run of an int8 file found is text-to-image: revfactory's ComfyUI 0.37.0 run of the int8 text encoder with a GGUF Q8_0 diffusion model, on a 36 GB M4 Max. The GGUF model loads through a custom node (a fork of ComfyUI-GGUF), which is not part of this page's install.
    • What the int8 files would do. At v0.37.2, on MPS, ComfyUI marks int8 layers as emulated. It keeps the weights int8 in memory and dequantizes them on each call, because PyTorch 2.14.0's torch._int_mm has no MPS kernel, and ComfyUI v0.37.2 turns int8 compute off on every MPS device whatever the PyTorch version (supports_int8_compute() and ops.py lines 1195–1201 at the tag). The fused-MLP path honours that since PR #16285 (merged 2026-09-12, in the tags since v0.36.0). That is a reading of the code, not a run.
  3. Set resolution to 1056 on the subgraph node before you edit. The template starts at 0 (its note: "This template starts at 0: no resize beyond a multiple of 32."). #16435's original report, filed from an Apple M5 Max on both MPS and CPU, found edits broken (over-sharpened and speckled) whenever a reference resolved to a 1024 grid, which a 1024×1024 image does at the template's 0. A second report in the thread, from an NVIDIA card, found a different grid broken. resolution 1056 was clean in both.

  4. Queue it. The template samples 25 steps, cfg 1, euler/simple.

What runs where on a Mac (code at the tag):

  • Device and loader. ComfyUI sets its memory state to SHARED on MPS (model_management.py, line 595), and the log says Set vram state to: SHARED. DynamicVRAM is enabled only on NVIDIA and recent ROCm (main.py, lines 264–270), and #16433's Mac log prints comfy-aimdo unsupported operating system: Darwin.
  • The text encoder runs on the CPU cores in fp16 unless you pass --gpu-only (text_encoder_device(), lines 1219–1230). The logs read CLIP/text encoder model load device: cpu … dtype: torch.float16. Expect prompt encoding to be slower than it would be on the GPU; that is an inference, not a measurement.
  • The diffusion model runs on MPS. With --cpu-vae the log also shows VAE load device: cpu. If it says mps, the flag did not take.
  • Memory. On the GPU side: the 13.253 GiB bf16 diffusion model, plus the edit cache, plus activations. In system memory: the 16.330 GiB text encoder and, with --cpu-vae, the VAE.
  • The edit cache. QwenImage21Cache at auto puts it on MPS when system RAM has four times its size free (qwen_image21/model.py, lines 240–263). On mps, get_free_memory returns available system RAM, not the Metal budget. One 1024² reference costs 2 (K and V) × 32 blocks × 4,096 × 2 bytes per token × 4,096 tokens = 2,147,483,648 B = 2.000 GiB.
  • Headroom. The GPU side for one reference comes to 15.253 GiB before activations, well inside 48.000 GiB.
  • The whole machine. The diffusion model (13.253 GiB), one reference's cache (2.000 GiB), the text encoder in CPU memory (16.330 GiB) and the VAE on the CPU in float32 (1.258 GiB; ComfyUI's vae_dtype falls back to float32 on a CPU device) come to 32.841 GiB before macOS, Python and activations. That fits a 64 GB Mac with room. The public Mac edit runs found that name a machine are on 24, 64 and 128 GB Macs, none on a 48 GB one. ComfyUI's own planner reads total RAM as its VRAM (Total VRAM 49152 MB on a 48 GB Mac in #16284), so it will not keep you inside the Metal budget by itself; keep an eye on memory pressure. Do not run it while mflux is generating.

Other paths, not the ones this page installs

mlx-serve. mlx-serve v26.9.5 (2026-09-21) is a native-MLX server that added 2.1 text-to-image and image-to-image from two pre-quantized packs, with no edit and no LoRA (PR #477).

  • OS floor. It needs macOS 26.2 or newer (README at the tag) and installs through Homebrew.
  • Size. Its 8-bit pack, ddalcu/Qwen-Image-2.1-MLX-Serve-8bit, is 17,647,912,015 B = 16.436 GiB. It quantizes the text encoder to 8-bit as well, where mflux keeps it bf16.
  • Memory. On a 64 GB Mac its own staging rule keeps the text encoder resident: 16.436 GiB of weights plus its flat 4 GiB allowance is 20.436 GiB, under three quarters of the budget (gen.zig, lines 3892–3906).
  • Runs. The only published run found that gives a machine and numbers is its author's, on a different Mac with 32 GB.
  • Its card is wrong on two points. It still says the pack cannot run on any released build, but the feature shipped in v26.9.5. It also declares Apache-2.0, while the repo's own LICENSE file is the Qwen Research License Agreement byte for byte. The Qwen licence is the one that applies.

stable-diffusion.cpp. stable-diffusion.cpp (Metal backend) supports 2.1 since PR #1994, merged 2026-09-20.

  • Docs and builds. A first-party guide covers text-to-image and editing with repeatable -r reference images. It also covers a prefix cache of about 4 GiB per condition at 4,096 tokens. Prebuilt macOS arm64 zips come with each master build. They are built on macOS 26.6.2, and no minimum macOS is stated.
  • Mac runs. The Mac runs found in its tracker are text-to-image: an M1 Pro in PR #2038, and a 36 GB M4 Max in #2024.
  • Open Metal issues. #2024 reports transparent backgrounds decoding opaque white. PR #2043 traces white squares to the VAE's forced FP16 overflowing, and notes that Metal lacks an F32 convolution. Both were open on 2026-09-24; at 12:21 UTC that PR's author added on #2024 that the fix will slow Metal down, because some operations fall back to the CPU.
  • No Mac edit run. None appeared among its issues and pull requests updated between 2026-09-19 and 12:13 UTC on 2026-09-24, so this page does not offer it as the edit path.

Also not covered.

  • Draw Things added 2.1 to its open-source engine on 2026-09-21. But the App Store build current on 2026-09-24 (26.0914.0) was released on 2026-09-17, before that work.
  • mzbac/qwen.image2.1 is an MLX Swift port that also edits; it is built from source with Xcode.

Turbo LoRA. The mflux lead cannot load 2.1 LoRAs (its README, line 95). Viggle's step-distilled Qwen-Image-2.1-viggle-turbo is a diffusers/ComfyUI artefact. As read on 2026-09-24 (revision b77064be), its card names no Mac, MPS or MLX path. No Apple run of it turned up in the searches listed under Results.

Results

  • Speed: omitted. No time for Qwen-Image-2.1 on an M2 Max was found. Searched on 2026-09-24 (11:50–12:20 UTC):

    • Hugging Face cards. Of the model cards among the 408 repos matching the model's name (282 non-empty), none names an M2 Max.
    • GitHub READMEs. 174 repositories from GitHub's repository search, both result pages. None names an M2 Max.
    • Trackers. mflux's issues, pull requests and discussions; mlx-serve's; ComfyUI's 210 threads updated since 2026-09-19 (the Mac ones are from an M5 Max, an M3 Max and an M4 Pro); stable-diffusion.cpp's 75.
    • Hugging Face discussions. Qwen's 41 and Comfy-Org's 16 Qwen-Image-2.1 discussions.
    • Draw Things. Its source and release notes.

    Times from other chips do not transfer: an M3, M4 or M5-generation chip is a different part from this one.

  • Memory:

    • The bf16 weights are 28.588 GiB in all. The weights held at once during the first step are bounded by 27.355 GiB for text to image and 28.588 GiB for image to image; no measurement of the actual figure was found. From the second denoising step on, text to image holds 13.257 GiB (up to 14.490 GiB for image to image), with or without --low-ram, against a 48.000 GiB budget.
    • The only reported peak for the bf16 default is about 46 GB, from another Mac (Requirements).
    • The ComfyUI edit setup puts 13.253 GiB of diffusion model plus about 2 GiB of cache per reference on the GPU.
  • Quality notes:

    • mflux's port reports channel correlation 0.989–0.992 against a full diffusers reference (PR #736; bf16, on an M5 Max).
    • The author of Rapid-MLX #3642 reports, on a 32 GB M2 Max, that -q 8 output is visibly better than -q 4.
    • mflux keeps the text encoder in bf16 because, per its weight definition, quantizing it degrades the prompt.
    • The bf16 default is the path the port's author measured parity on. No comparison of -q 8 against bf16 image quality on this model was found.

For live data, see /check/qwen-image-2-1/m2-max. If you time this on an M2 Max (and say which GPU bin), the page needs your number: /contribute.

Troubleshooting

The Mac slows to a crawl, the fans spin up, and Activity Monitor's memory pressure turns yellow or red

That is what "over budget" looks like here: neither MLX nor PyTorch-MPS errors at the budget line (Requirements). They keep allocating, and macOS swaps.

  • Add --low-ram. On a one-seed run it deletes the diffusion model before the decode; it also tiles the decode and caps MLX's buffer cache at 1 GB.
  • If the Mac still swaps, add -q 8 too, or use mflux-community/qwen-image-2-1-mflux-q8. That is the install the 48 GB Mac pages use. -q 8 holds 7.045 GiB of diffusion weights from the second step on; only the pre-quantized repo avoids reading the bf16 weights in the first step.
  • Quit other large apps.
  • Do not run mflux and ComfyUI at the same time.

A long-running script grows until the Mac swaps

Run one process per image. The author of The-Focus-AI/qwen-image-2.1-mlx writes, of their 64 GB M4 Max: "One long-lived process held the text encoder and the Metal buffer cache until the machine started swapping and the process was killed." The docstring of their batch_generate.py contrasts that process with mflux's CLI, so it was presumably their own loop over mflux's Python API; the README does not say. That script now starts a new process for each image, and so should yours. mflux's own CLI drops the text encoder before the first step, releases its weights during that step, and clears MLX's cache after each image's loop (memory_saver.py, lines 61–75). Several --seed values in one call keep the diffusion model loaded between images, by design (callback_manager.py, lines 92 and 111).

Output is flat purple-and-green static

Seen in mflux #748 with mlx-community/Qwen-Image-2.1-MLX-4bit.

  • Use this page's files instead: the canonical repo (the default), or mflux-community/qwen-image-2-1-mflux-q8.
  • If you suspect a broken download, verify the cache (hf cache verify <repo>), as the collaborator there suggested.

ComfyUI edits come out noisy, embossed or olive-tinted, with no error

That is #16433: the MPS VAE encoder on PyTorch 2.14.0. Restart with python main.py --cpu-vae, then check that the log says VAE load device: cpu.

ComfyUI edits are over-sharpened and speckled, or ignore the instruction

That is #16435. Set resolution to 1056 on the template's subgraph node, as in step 3. At the template's 0, a 1024×1024 reference lands on the 1024 grid that the thread's original, Mac-based report found broken.

NotImplementedError: The operator 'aten::_int_mm' is not currently implemented for the MPS device

Most likely you are on a ComfyUI older than v0.36.0 with int8 files. The fused MLP path bypassed the int8 fallback until PR #16285, as reported in #16284. Use v0.37.2, and the bf16 files this page recommends.

RuntimeError: MPS backend out of memory … Use PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 to disable upper limit

PyTorch's hard limit (1.7 × the budget) was reached, or Metal could not supply a buffer. Do not set the ratio to 0.0. PyTorch's own message warns it may cause system failure. Close other apps, and keep one reference image per edit until you know your headroom.

common questions
How much VRAM does Qwen-Image-2.1 need?

About 64 GB — the minimum this recipe targets.

Which GPUs is Qwen-Image-2.1 tested on?

Apple M2 Max (64 GB).

How hard is this setup?

Intermediate — follow the steps above.

next