Image Generation & Editing

Qwen-Image-2.1 turns a prompt into an image, or a prompt plus one or more reference images into an edited image. This page covers the download, the run recipe, the defaults, and the settings that decide how long an image takes.

💡

These commands follow the same conventions as the text-model recipes — the Hugging Face CLI, a source build from the repository root, and --backend swapped to match your hardware. See Download & run, per family.

Qwen-Image-2.1 (prompt → image, prompt + image → edited image)

The shortest route is the ready-made config/qwen-image-2.1.json. It pins repository revisions and SHA-256 checksums and downloads whatever is missing — four files, about 10.29 GiB — into $TENSORSHARP_MODELS/qwen-image-2.1/, or models/qwen-image-2.1/ in the repository when TENSORSHARP_MODELS is unset. The configuration selects ggml_metal; on an NVIDIA machine with the CUDA backend built, append --backend ggml_cuda; the native CPU backend is ggml_cpu, and --backend cpu runs everything in pure C# (see On a CPU).

dotnet build TensorSharp.Cli/TensorSharp.Cli.csproj -c Release
dotnet build TensorSharp.Server.Host/TensorSharp.Server.Host.csproj -c Release

# Text-to-image
dotnet run --project TensorSharp.Cli -c Release --no-build -- \
  --config config/qwen-image-2.1.json \
  --prompt 'A small orange cat beside a blue ceramic vase, soft daylight, detailed photograph' \
  --width 2048 --height 2048 --diffusion-steps 40 --cfg 1 \
  --diffusion-seed 42 --output generated.png

# Image editing
dotnet run --project TensorSharp.Cli -c Release --no-build -- \
  --config config/qwen-image-2.1.json \
  --image generated.png \
  --prompt 'Change the blue vase to a red vase. Preserve the cat, lighting and composition.' \
  --width 2048 --height 2048 --diffusion-steps 40 --cfg 1 \
  --diffusion-seed 42 --output edited.png

# Server + Web UI
dotnet run --project TensorSharp.Server.Host -c Release --no-build -- \
  --config config/qwen-image-2.1.json --host 127.0.0.1 --port 5000

To fetch the four files yourself instead, pass the companions explicitly:

hf download Abiray/Qwen-Image-2.1-GGUF qwen_image_2.1_Q4_K_M.gguf --local-dir models
hf download Comfy-Org/Qwen-Image-2.1 vae/qwen_image_2.1_vae_bf16.safetensors --local-dir models
hf download Qwen/Qwen3-VL-8B-Instruct-GGUF Qwen3VL-8B-Instruct-Q4_K_M.gguf mmproj-Qwen3VL-8B-Instruct-F16.gguf --local-dir models

dotnet TensorSharp.Server.Host/bin/TensorSharp.Server.Host.dll --model models/qwen_image_2.1_Q4_K_M.gguf \
    --qwen-image-vae models/vae/qwen_image_2.1_vae_bf16.safetensors \
    --qwen-image-vl models/Qwen3VL-8B-Instruct-Q4_K_M.gguf \
    --qwen-image-mmproj models/mmproj-Qwen3VL-8B-Instruct-F16.gguf \
    --backend ggml_cuda
ComponentFileSource
Diffusion transformerqwen_image_2.1_Q4_K_M.ggufAbiray/Qwen-Image-2.1-GGUF
Dedicated VAEqwen_image_2.1_vae_bf16.safetensorsComfy-Org/Qwen-Image-2.1
Text encoderQwen3VL-8B-Instruct-Q4_K_M.ggufQwen/Qwen3-VL-8B-Instruct-GGUF
Vision encoder for editingmmproj-Qwen3VL-8B-Instruct-F16.ggufSame Qwen3-VL repository

The download size is not peak inference memory: runtime memory also includes activations, decoding buffers and working weights, and larger images and multiple references increase it.

Other Qwen-Image-2.1 GGUFs load as well. A file with no metadata at all, such as Unsloth's qwen-image-2.1-Q8_0.gguf (tensors under a model.diffusion_model. prefix included), is recognized from its tensor layout — never from its file name. The companion scan matches a qwen_image_2.1_vae safetensors file and names containing qwen3vl-8b or qwen3-vl-8b, so a projector saved as mmproj-BF16.gguf needs --qwen-image-mmproj. On an M5 Pro the Unsloth Q8_0 file generated 1024×1024 at 40 steps in 337.7 s, against 383.1 s for stable-diffusion.cpp in the same single-run comparison. Only 2.1 loads: an earlier Qwen-Image or Qwen-Image-Edit-2511 transformer is refused at load (exit code 2).

Qwen-Image-2.1-Turbo (8 steps)

Qwen-Image-2.1-Turbo is the same 7B transformer distilled to 8 Euler steps at CFG 1 on one fixed schedule; its VAE, text encoder and vision projector are the 2.1 files above. config/qwen-image-2.1-turbo.json downloads the AtomicChat Qwen-Image-2.1-Turbo-AD-Q4_K.gguf (4.20 GB) into qwen-image-2.1/ under the models root, next to the 2.1 files above under their published names, so a folder that already holds them fetches only the transformer. Qwen-Image-2.1-Turbo-Q8_0.gguf (7.59 GB) is the transformer closest to the full-precision checkpoint (the card's LPIPS against BF16: 0.037, against 0.147 for AD-Q4_K); both use the same Q4_K_M text encoder, which the card measures moving pictures about as far as AD-Q4_K does.

dotnet run --project TensorSharp.Cli -c Release --no-build -- \
  --config config/qwen-image-2.1-turbo.json \
  --prompt 'a neon sign that reads "OPEN LATE", rainy night' \
  --width 1024 --height 1024 --diffusion-seed 42 --output turbo.png

Image generation & editing (Qwen-Image-2.1)

The loaded qwen_image GGUF is the Qwen-Image-2.1 diffusion transformer. TensorSharp resolves its companions next to it — the dedicated 2.1 VAE, the Qwen3-VL-8B text encoder and, for editing, the Qwen3-VL mmproj vision encoder — or takes them from --qwen-image-vae / --qwen-image-vl / --qwen-image-mmproj (env: TS_QWEN_IMAGE_VAE / TS_QWEN_IMAGE_TE / TS_QWEN_IMAGE_MMPROJ). Native RGBA input and PNG output preserve transparency: the Qwen3-VL conditioning branch composites the reference over white while the VAE keeps alpha.

The speed levers are resolution and step count. The biggest cut in steps is a step-distillation LoRA plug-in, which replaces the 40-step default with 4–8 transformer passes: on an M5 Pro, 1024×1024 at Viggle Turbo's 6 steps took 54.5 s end to end (see LoRA plug-ins). A 2K square has four times the latent image tokens of a 1K square and more attention work, so --width 1024 --height 1024 is the draft setting. --diffusion-steps 25 --cfg 1 is the faster profile used by the official ComfyUI generation and editing workflows; 40 steps remains the default, and fewer steps are a quality/speed trade-off rather than equivalent quality. CFG 1 already means one transformer pass per step. For a quick smoke test, 256×256 and one step verifies loading and the end-to-end data path, not image quality. Editing also gains from the default-on prefix KV cache.

Run it from the CLI (--prompt, plus --image to edit), the Web UI (a prompt without an attachment generates an image; attach one or more images to edit, with live denoising previews), the HTTP API (/api/image-generate and /api/image-edit, each with a /stream variant), or in-process from C# via QwenImageModel.GenerateImage() / EditImage(). Image operations are serialized because the diffusion pipeline shares mutable working state. Full details and the validation history are in the repository's Qwen-Image-2.1 card.

Edit a selected region

In TensorAgent or the server Web UI, attach photos, choose Select area beside a thumbnail, paint or erase the region, and choose Use selection. That photo becomes the editing target; other photos remain references. Load Qwen-Image 2.1, describe the change and send. The editor supports mouse, touch and stylus, zoom/pan, undo/redo, inversion and edge softness. Compare original reviews the result; Edit again restores the photos, selection and prompt.

A saved selection preserves the target's original dimensions and exact decoded RGB/alpha pixels outside the selection. White edits and black protects in a grayscale mask; an alpha mask uses transparent pixels to edit. The mask must match the first image's dimensions. Edge feathering fades inward. Optional crop mode processes the selected region with context, then places it back on the original canvas; it can reduce work but has less surrounding context. Instruction-following inside the selection still depends on the model.

dotnet run --project TensorSharp.Cli -c Release --no-build -- \
  --config config/qwen-image-2.1.json \
  --image photo.png --mask selection.png --mask-mode grayscale \
  --prompt 'Change the selected vase to red ceramic' \
  --mask-feather 8 --mask-crop --mask-crop-padding 96 \
  --width 1024 --height 1024 --diffusion-seed 42 --output edited.png

--mask-invert reverses the selection. Feathering is 0–1024 source pixels; crop padding is 0–16384 (default 64). The editor has no fixed megapixel or per-side limit; practical image sizes depend on browser memory and canvas support. HEIC/HEIF selection editing uses a separate full-resolution PNG. The same mask fields work through the JSON, multipart and SSE image-edit APIs and QwenImageParams in C#. Detailed semantics and validation limits are in the model card.

On a CPU (--backend cpu)

--backend cpu runs the whole pipeline in managed C#, with no GGML call: the transformer (8-bit activations on a multi-row integer GEMM, as ggml-cpu quantizes them; tiled flash attention; unmerged LoRA), a host-memory prefix KV cache, the Qwen3-VL text encoder on the same integer GEMM and the vision encoder on a packed F32 GEMM, and the VAE as implicit-im2col packed convolutions on a pool with one thread per logical CPU. A request that names no size renders at 1024×1024 (1 MP) on this backend; ggml_cpu and the GPU backends keep 2048×2048. --tp is refused on this backend. Measured on an i7-11800H (8 cores / 16 threads, AVX-512, 32 GB), text-to-image, seed 42:

Runcpuggml_cpuImage vs ggml_cpu
256×256, 2 steps16.9–17.1 s (VAE decode 2.8–2.9 s)36.1–36.4 s (VAE decode 10.8–11.2 s)37.7 dB PSNR, SSIM 0.984
512×512, Pruna 5-step LoRA118 s (17.6–21.7 s per step)295.7 s (VAE decode 50.3 s)32.9 dB PSNR, SSIM 0.972

The images differ because the managed transformer sums in another order than ggml-cpu and keeps F32 where ggml-cpu uses an F16 GELU table. TS_QWEN21_CPU_MATMUL=f32 (F32 activations against dequantized weight tiles, the default before) is slower (20.4 s and 169.6 s for the two runs above) and numerically steadier. Two larger runs were measured with that F32 transformer: 1024×1024 with the Pruna LoRA 756 s against 1101 s on ggml_cpu, and a 512×512 edit 224 s against 353 s. Memory is the limit on a CPU: a size whose estimated peak does not fit the machine is refused before any work (TS_QWEN_IMAGE_CPU_MEMORY_CHECK=0 overrides); a 1024×1024 Pruna generation peaked at 3.7 GiB of commit, a 2048×2048 VAE encode and decode at 10.5 GiB. The kernel switches (TS_QWEN21_CPU_*, TS_QWEN_TE_CPU_MATMUL, TS_CPU_GEMM_THREADS) are in the model card.

Prefix KV cache

The text and reference-image tokens are modulated at t = 0 and never attend to the image being generated, so their keys and values are the same at every denoising step. TensorSharp implements the official prefix KV cache: step 1 runs the whole sequence and stores each block's prefix keys and values on the device, and every later step runs only the target image's tokens. It is on by default (TS_QWEN21_PREFIX_CACHE=0 turns it off) and stores exactly what attention reads — F16 on Metal and with CUDA flash attention, F32 otherwise — so with the default storage the output PNGs were byte-identical to uncached runs. A CFG run keeps one cache per branch, and every cache is released before the VAE decode. The cost is memory: 512 KiB per prefix token per branch in F16, about 2 GiB for each 1-megapixel reference. A cache may use at most half the memory the device reports free (TS_QWEN21_PREFIX_CACHE_MAX_MIB caps it further) and is otherwise declined with a warning; TS_QWEN21_PREFIX_CACHE_TYPE=q8_0 or q8_0_v stores it in 8 bits, saving memory at 5–13% more time per step and a small rounding of the output.

It pays in proportion to the prefix's share of the sequence. A 1024² edit on an M5 Pro went from 17.97 s to 9.65–10.38 s per step with one reference, and from 33.32 s to 11.46 s with two (2.91×); on an A40 (ggml_cuda) the one-reference edit went from 2.547 s to 1.328 s. Generation, whose prefix is only the prompt, gains 1–3%, and at 2048² editing gains less because the target dominates. Details and the Vulkan numbers are in the Qwen-Image-2.1 card.

Multiple GPUs

--tp N on ggml_cuda or ggml_vulkan shards the diffusion transformer Megatron-style: each GPU holds 32/N whole attention heads and 12,288/N MLP columns, the two row-parallel products in each block are summed across GPUs, and each GPU caches the prefix of its own heads. N must divide the 32 heads — 2, 4, 8 or 16 on one machine — and each weight's quantized blocks must stay whole when it is sharded, which the load checks per weight type; only 2 GPUs have been measured. The text encoder, vision encoder and VAE stay on the first GPU, and multi-node groups are refused. On two A40s it measured 1.34× per step for 1024² generation, 1.42× for a one-reference 1024² edit and 1.54–1.57× at 2048²; under ggml_vulkan, which reduces through host memory, it was 0.86× — slower than one GPU. Partial sums are added in a different order, so outputs are not bit-identical to a single GPU: composition and quality match, while details can differ. See the card for memory per GPU and the numerical checks.

LoRA plug-ins

--lora adds a LoRA to the diffusion transformer on the CLI and the server alike: a style, an editing skill, or a step-distillation adapter that replaces the 40-step default with 4–8 transformer passes. Repeat it to stack plug-ins; --lora-scale sets the strength of the preceding --lora and --lora-config names its companion config (a TensorSharp LoRA config, a PEFT adapter_config.json or a VideoX-Fun pdd_config.json). The fourteen ready-made plug-ins in config/lora/ download and hash-check their weights on first use and carry the adapter's strength and sampling recipe.

# Viggle Turbo: 6 steps and CFG 1 from the plug-in's recipe (4–8 steps supported)
dotnet run --project TensorSharp.Cli -c Release --no-build -- \
  --config config/qwen-image-2.1.json \
  --lora config/lora/qwen-image-2.1-viggle-turbo.json \
  --prompt 'A small orange cat beside a blue ceramic vase, soft daylight, detailed photograph' \
  --width 1024 --height 1024 --diffusion-seed 42 --output turbo.png

# The server applies its startup set to every image request
dotnet run --project TensorSharp.Server.Host -c Release --no-build -- \
  --config config/qwen-image-2.1.json --lora config/lora/qwen-image-2.1-pruna-8step.json

The flag reference and the plug-in config format are in the repository's USAGE.md, and the packing and tensor-parallel details in the Qwen-Image-2.1 card.