What You'll Build
A local Qwen-Image-2.1 setup on an Apple M4 Max with 48 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 with 8-bit diffusion weights and
--low-ram. - 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-vaeswitch 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 M4 Max, 48 GB unified memory (16-core CPU, 40-core GPU, 546 GB/s) · nothing on this page was measured on an M4 Max by this site · the timings under Results are a third party's, from a 64 GB M4 Max of the same bin · default GPU budget 36.000 GiB, which the recommended install stays inside with room to spare · See benchmark data
ℹ️ Nothing on this page was measured by this site. No part of it was run on an M4 Max, or on any other Mac, by this site. The timings and 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/m4-maxreturnedverdict: unknownwith no benchmarks when this page was written. If you run it, post your numbers.
Requirements
| Component | Minimum | This recipe |
|---|---|---|
| Unified memory | 32 GB for mflux -q 8 --low-ram text-to-image (a 21.333 GiB GPU budget on macOS 13–15); 48 GB for the ComfyUI edit set | Apple M4 Max, 48 GB (36.000 GiB budget), not measured by this site; the budget below is derived (/contribute) |
| OS | macOS 14 Sonoma | — |
| Storage | 33,131,600,661 B for mflux's first download, or 24,036,988,319 B pre-quantized; plus 32,440,124,920 B for the ComfyUI edit files | — byte counts from the Hugging Face tree API |
| Software | mflux 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 MacBook Pro (M4 Pro or M4 Max, 2024) and the Mac Studio (2025) list two M4 Max bins:
| Bin | Memory bandwidth | Memory sizes |
|---|---|---|
| 14-core CPU / 32-core GPU | 410 GB/s | 36 GB only |
| 16-core CPU / 40-core GPU | 546 GB/s | 48 GB, 64 GB or 128 GB |
A 48 GB M4 Max is therefore always the 16-core CPU / 40-core GPU part, at 546 GB/s. That also makes a 64 GB M4 Max the same GPU as this page's machine, with more memory; the timings under Results come from one. A 36 GB M4 Max is the slower bin.
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.
- 48 GB → 38,654,705,664 B = 36.000 GiB. Printed as
recommendedMaxWorkingSetSize = 38654.71 MB:- by an M4 Max in a comment on RunanywhereAI/wally #5. Its log also reads
Apple M4 Max … 36863 MiB free. The same commenter's later comment shows a binary refusing to run because it was built for a macOS 26.0 that is newer than theirs, so that log predates macOS 26; - by the M3 Max in a LLamaSharp report (48GB RAM and macOS Sonoma 14.5, per the comment before).
- by an M4 Max in a comment on RunanywhereAI/wally #5. Its log also reads
- 32 GB → 21.333 GiB, printed as
22906.50 MBby a 32 GB M1 Max Mac Studio on macOS 13 in llama.cpp #22800. That matters for this page'smin_vram_gbbelow.
On macOS 26 the logs found show larger values. These are the ones that name their machine:
- 48 GB:
40200.90 MB(37.440 GiB, 78.0%), from an M4 Pro with 48 GB on macOS Tahoe 26.3 in llama.cpp #20141. An M4 Pro on macOS 26.2 prints the same value in llama.cpp #22199; its memory is not stated, and 48 GB is inferred from the value. The wired limit of 38338 "MB" that mlx-serve reports for a 48 GB M5 Pro on macOS 26 in issue #126 is the same number in MiB. - 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 prints25769.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 48 GB M4 Max. 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')"
# 36.0 on a 48 GB Mac on macOS 13–15; 37.44 in the macOS 26 logs above
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 memorypast 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 8-bit export (mflux-community/qwen-image-2-1-mflux-q8, written by mflux 0.20.0) holds 399 tensors: the text layers plus rotary_emb.inv_freq.
The sizes, from that export's tree:
| Part | Bytes | GiB |
|---|---|---|
| Text encoder (bf16, text layers only) | 15,136,877,852 | 14.097 |
| Diffusion model (8-bit, MLX affine, group 64) | 7,564,689,541 | 7.045 |
| VAE | 1,323,990,909 | 1.233 |
| All three | 24,025,558,302 | 22.376 |
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 ismx.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-ramtoo when the target size is at most 512 px on both sides (mflux scales the photo to--width/--heightbefore it encodes it). For a larger target (the 1024×1024 command below is one),--low-ramtiles 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; no measurement isolates it, and the whole-run peaks below cover it. The 8-bit weight sums (not observed; the canonical download can exceed them transiently during step 1) are:
- text to image: the text encoder and the diffusion model, 22,701,567,393 B = 21.142 GiB;
- image to image: all three, 22.376 GiB, because the VAE's encoder has been read by then.
Against 36.000 GiB, the larger sum leaves 13.624 GiB for the activations, the VAE decode and macOS's GPU use.
-
On the canonical download, the 8-bit conversion also runs in that step.
-q 8loads the diffusion model's bf16 weights and builds the 8-bit layers from them without evaluating anything (weight_applier.py, lines 149–151 and 196–201), so the bf16 source, 14,230,315,061 B = 13.253 GiB, is read and converted during the first step too. The sums above use the 8-bit sizes, which is what the pre-quantized download holds; on the canonical download the transient total can be higher. revfactory's and #3642's readings below were taken on the canonical path. -
From the second step on, text to image holds the diffusion model's 7.045 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 8.278 GiB in all.
-
What
--low-ramadds (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 others measured. None of these runs was on a 48 GB M4 Max, the instruments are not all stated, and here they are memory data points (the one speed figure for this bin is under Results):
- The port's author. PR #736 reports, on an M5 Max at 1024² and 40 steps, a peak of about 46 GB with bf16 weights and about 30.7 GB with
-q 8. The PR does not say whether--low-ramwas on, or how memory was read. The bf16 figure is 6.84 GiB over this Mac's budget if it is decimal GB, and 10.00 GiB over if it is GiB, so bf16 is not this page's install. - JoyFusionAI's 8-bit card. JoyFusionAI's 8-bit card reports an M1 Max at 768×768 peaking at about 10 GB with
--low-ram, against 33–36 GB without it, instrument not stated. Its build is mflux commit8c00dab2; the two commits between that and the 0.20.0 tag do not change what--low-ramdoes for this model. The card credits the difference to the flag releasing the text encoder. At the tag the encoder is dropped either way. Its freed buffers are what the flag's 1 GB cache cap releases, and with the flag's other effects above that may account for the difference; that is an inference from the code. The top of that range, 36 GB, would be 2.47 GiB under this Mac's 36.000 GiB if it is decimal GB, and right at the edge if it is GiB. The-Focus-AI's 64 GB M4 Max reports 25 GB at 768×768 without the flag (see Results), so the two sources disagree, and neither names its instrument. - A 36 GB M4 Max. In revfactory/qwen-image-studio (README in Korean), the owner ran this page's exact command (
-q 8 --low-ram, 1024², 40 steps) on mflux 0.20.0. They report a maximum of 15.5 GB on MLX's counter, which--low-ramresets after the model is built, before any weight is read. A 36 GB M4 Max is the 14-core CPU / 32-core GPU bin at 410 GB/s, so its time is not a figure for this page. - A 64 GB M4 Max of this page's bin. The-Focus-AI/qwen-image-2.1-mlx reports about 30 GB at 1024² and 48 GB at 2048×1152, with
-q 8and without--low-ram. Its full table is under Results. - A 32 GB M2 Max. The author of Rapid-MLX #3642 runs the same command (
mflux-generate-qwen-2.1 … -q 8 --low-ramon the bf16 weights) and reports it comfortable at 32 GB, using about 15.5 GB. How that was read is not stated.
Why this page adds --low-ram. The measured peaks at 1024² with -q 8 are about 30 GB without the flag (The-Focus-AI's ~30 GB, Results; PR #736's 30.7 GB is similar, but the PR does not say whether the flag was on) and about 15.5 GB with it (revfactory at 1024² on MLX's counter; #3642 gives the same figure with no resolution or method stated). The ~15.5 GB readings sit close to the text encoder alone, 14.097 GiB (15.14 GB), and well under the encoder plus the diffusion model, 21.142 GiB (22.70 GB). That suggests the two are not fully held together during the first step; it is an inference. At 2K the flag is needed as well (Running).
One caveat about every "peak" above. MLX's own 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_memoryread 4.9 GB. MLX's 3×3 convolution keeps an unfolded copy of its input outside its counters (docs/gotchas/models-media.md). - mflux prints
Peak MLX memoryfrom that same counter, in decimal GB, at the end of every run. With--low-ramthe counter is reset after the model is built, before any weight is read. The counter also leaves out MLX's own buffer cache. --low-ramtiles the decode, which is the remedy mlx-serve applied (banding brought its footprint to 9.5 GB). That this also shrinks mflux's decode is inference, not a measurement.
Why min_vram_gb is 32
min_vram_gb is the smallest unified memory Apple sells on which this page's install runs inside the default budget, with no sysctl. It covers the mflux text-to-image path only. The ComfyUI edit set under Running needs about 32.8 GiB of the machine, i.e. a 48 GB Mac.
- 32 GB gives 21.333 GiB on macOS 13–15, and that is the basis here. The macOS 26 logs above read 24.960 GiB, and the one from a 32 GB M2 Max reads 24.000 GiB; that is a note, not the basis.
- The readings. Three reports, all from other chips, none with a timing used here:
- revfactory's 15.5 GB on MLX's counter, at 1024² with this exact command (above), is 14.436 GiB, 6.898 GiB under 21.333 GiB.
- The author of Rapid-MLX #3642 runs this exact command on a 32 GB M2 Max and reports it comfortable at 32 GB, at about 15.5 GB. The report states no resolution, no method and no macOS version.
- The same repository's owner reports valid 1024×1024 PNGs on an M2 Pro/32 GB Mac mini on 2026-09-22, from the official weights with the quantization not stated. That run went through Rapid-MLX's own mflux-based server, with its memory-saving and tiled-VAE path, not through mflux's CLI.
- MLX's counter leaves out its own buffer cache (capped at 1 GB by
--low-ram) and the decode's unfolded copy (which--low-ramtiles), so the real footprint sits somewhat above these readings.
- The 8-bit weight sums (not observed; the canonical download can exceed them transiently during step 1). The text encoder plus the 8-bit diffusion model, 21.142 GiB, is 0.191 GiB under 21.333 GiB before activations; all three, 22.376 GiB (image to image), would not fit. The two memory readings above, revfactory's and #3642's, were both taken on the canonical
-q 8path, so the bf16 conversion is inside them, and they stay far below both sums. The pre-quantizedmflux-community/qwen-image-2-1-mflux-q8skips that conversion in step 1, which makes it the lower-risk choice on a 32 GB Mac (by the code; no memory reading on that path was found). - No mflux run on a Mac with less than 32 GB turned up in the searches listed under Results.
So the floor is 32, and it rests on those readings, not on the code. On a 48 GB M4 Max, the all-three 8-bit sum leaves 13.624 GiB.
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: pre-quantized. mflux-community/qwen-image-2-1-mflux-q8 is 24,036,988,319 B, with the same three parts at the sizes in the table above.
- 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.
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:
torchvision0.29.0 requires exactlytorch==2.14.0.requirements.txtat the tag leavestorchunpinned and pinscomfy-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 -q 8 --low-ram \
--prompt "A neon shop sign that reads \"QWEN IMAGE 2.1\", rainy night, reflections on wet pavement" \
--steps 40 --seed 42 --output neon.png
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).
- Guidance. A negative prompt with
--guidanceabove 1 turns on true classifier-free guidance, which runs the diffusion model twice per step. With no negative prompt the second pass is skipped. - The printout. mflux prints
Peak MLX memory: … GBat the end of every run (CLIqwen21_generate.py, lines 69–71). That figure is MLX's counter in decimal GB, with the caveat above; with--low-ramit is reset after the model is built, which excludes almost nothing, because the weights are read during the run. - 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). Keep --low-ram on for it, which this install already does: it tiles the decode, and the untiled decode is where MLX's counter undercounts (Requirements).
mflux-generate-qwen-2.1 -q 8 --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², so each step costs several times as much. The-Focus-AI's M4 Max ran 2048×1152 with -q 8 and no --low-ram, at 19 seconds per step and a 48 GB peak (Results). That peak is over this Mac's 36.000 GiB budget whichever unit it is in, which is the case for --low-ram at 2K. No 2048² run with --low-ram was found, so its time and memory on this Mac are not known.
Image to image
mflux-generate-qwen-2.1 -q 8 --low-ram --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
--widthand--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.
-
Start it with
--cpu-vae:source ~/comfy-venv/bin/activate cd "${COMFY:?set COMFY first, see step 3}" && python main.py --cpu-vaeOn 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 on PSNR 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, andea3ba8f9, 2026-09-02) are on main. They are not in 2.14.0: GitHub's compare API reads them as diverged from thev2.14.0tag, 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.2it still calls the padding op inAvgDown3D(vae2_2.py, line 256). - What
--cpu-vaecosts (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.
-
Load the template. Open Qwen Image 2.1: Image Edit from the templates (
image_qwen_image_2_1_image_edit.json, unchanged since371a7b7171). Its loaders sit inside the template's Image Edit (Qwen Image 2.1) subgraph node, which shows them asunet_name,clip_nameandvae_namewidgets, besideresolution. They ask for the int8 files. Setunet_nametoqwen_image_2.1_bf16.safetensorsandclip_nametoqwen3vl_8b_bf16.safetensors;vae_nameis 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-vaemade 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'storch._int_mmhas no MPS kernel, and ComfyUI v0.37.2 turns int8 compute off on every MPS device whatever the PyTorch version (supports_int8_compute()andops.pylines 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.
- 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
-
Set
resolutionto 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.resolution1056 was clean in both. -
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
SHAREDon MPS (model_management.py, line 595), and the log saysSet vram state to: SHARED. DynamicVRAM is enabled only on NVIDIA and recent ROCm (main.py, lines 264–270), and #16433's Mac log printscomfy-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 readCLIP/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-vaethe log also showsVAE load device: cpu. If it saysmps, 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.
QwenImage21Cacheatautoputs it on MPS when system RAM has four times its size free (qwen_image21/model.py, lines 240–263). Onmps,get_free_memoryreturns 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 36.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_dtypefalls back to float32 on a CPU device) come to 32.841 GiB before macOS, Python and activations. That fits a 48 GB Mac with room, and not a 32 or 36 GB one, which is why this page'smin_vram_gbcovers text-to-image only. 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 MBon a 48 GB Mac in #16284), so it will not keep you inside the Metal budget by itself; keep an eye on memory pressure.
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 48 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
LICENSEfile 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
-rreference 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 (third party; one machine, the same chip and bin as this page's, with 64 GB). The-Focus-AI/qwen-image-2.1-mlx writes "Tested on a MacBook Pro with an M4 Max (40 GPU cores, 64 GB unified memory)." It ran mflux at the 0.20.0 tag's commit
ada53237, with its launcher's defaults:-q 8, guidance 1, 20 steps, and no--low-ram(generate.sh).Size Seconds per step Image Peak memory (their figure) 1024×1024 6.0 ~130 s at 20 steps; about four minutes at 40 ~30 GB 768×768 3.1 79 s at 20 steps 25 GB 640×640 2.2 ~45 s at 20 steps 22 GB 2048×1152 19 942 s at 50 steps 48 GB On precision, the same README: "At 1024, full bfloat16 was 5.1 seconds per step, 4-bit was 5.8, 8-bit was 6.0, and 6-bit was 6.8." mflux's own model README also calls bf16 the fastest path, measured on an M5 Max; it does not rank the quantized levels.
- What does not transfer. These are per-step times on the default path, on a 64 GB machine.
- That they hold on a 48 GB M4 Max is inference. It is the same GPU, and its ~30 GB peak at 1024² sits inside the 48 GB Mac's budget, so memory should not slow it. No measurement on a 48 GB M4 Max turned up in the searches listed below.
- Whether this page's
--low-ramchanges the per-step time is also unmeasured on this bin. It tiles the decode, deletes the diffusion model before it on a one-seed run and caps MLX's buffer cache at 1 GB. - They are the author's figures. The README does not say how many runs each row is, or which instrument its "peak" comes from.
- The 36 GB M4 Max run in Requirements is the slower bin and is not used here.
- What does not transfer. These are per-step times on the default path, on a 64 GB machine.
-
Memory:
- The 8-bit weight sums are 21.142 GiB for text to image and 22.376 GiB for image to image (not observed; the canonical download can exceed them transiently during step 1; Requirements). From the second denoising step on, text to image holds 7.045 GiB (up to 8.278 GiB for image to image), with or without
--low-ram, against a 36.000 GiB budget. - The reports on other Macs put this install at about 10 GB (JoyFusionAI, instrument not stated) and 15.5 GB (revfactory, MLX's counter; #3642, method not stated) (Requirements).
- The ComfyUI edit setup puts 13.253 GiB of diffusion model plus about 2 GiB of cache per reference on the GPU.
- The 8-bit weight sums are 21.142 GiB for text to image and 22.376 GiB for image to image (not observed; the canonical download can exceed them transiently during step 1; Requirements). From the second denoising step on, text to image holds 7.045 GiB (up to 8.278 GiB for image to image), with or without
-
Searches behind this page's absence claims, on 2026-09-24 (11:50–12:20 UTC):
- Hugging Face cards. The model cards among the 408 repos matching the model's name (282 non-empty). None names an M4 Max.
- GitHub READMEs. 174 repositories from GitHub's repository search, both result pages. The M4 Max machines among them are The-Focus-AI's 64 GB one (Speed, above) and revfactory's 36 GB one (Requirements); neither is a 48 GB M4 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.
-
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 8output is visibly better than-q 4. - mflux keeps the text encoder in bf16 because, per its weight definition, quantizing it degrades the prompt.
- No comparison of
-q 8against bf16 image quality on this model was found; the port's author timed and checked both, on another Mac.
For live data, see /check/qwen-image-2-1/m4-max. If you time this on a 48 GB M4 Max, and especially with --low-ram, 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.
- Check that
--low-ramis on. - Quit other large apps.
- Do not run mflux and ComfyUI at the same time.
- If you are on the canonical download, the pre-quantized
mflux-community/qwen-image-2-1-mflux-q8also skips the load-time quantization step.
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 with
-q 8, ormflux-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.