name: qwen-image-edit-aipc-finetune description: > End-to-end runbook for fine-tuning Qwen-Image-Edit with LoRA on a single Intel AI PC (Core Ultra processor) Windows laptop. Use this skill whenever a user wants to fine-tune or personalize Qwen-Image-Edit on their own dataset using Intel AI PC hardware, mentions XPU training or NF4 QLoRA on Windows — even if they don't use those exact terms. Covers: dataset preparation, hardware probe → recommended training config, oneAPI / conda environment setup, XPU framework adaptation, NF4 QLoRA memory optimizations, training execution, and post-training visual comparison.
Qwen-Image-Edit LoRA Fine-Tuning on Intel AI PC (Windows)
1. Background & Scope
This skill is the end-to-end runbook for fine-tuning Qwen-Image-Edit with LoRA on a single Intel AI PC (Core Ultra processor) Windows laptop. It is written to be consumed by an agent acting on behalf of a non-expert user.
Scope: this skill targets NF4 QLoRA only. Other recipes (bf16 / fp16 LoRA, full fine-tuning, etc.) are out of scope — 4-bit quantization of the transformer is what makes the workload fit on AI PC unified memory.
Recipe: NF4-quantized transformer + LoRA +
bitsandbytes.optim.Adam8bit + cache-first workflow + mode-aware component
loading. Single-card XPU training under accelerate launch with
distributed_type: NO and mixed_precision: 'no'. The recommender (§5)
selects a sensible default config matched to AI PC hardware, with RAM-tier
adjustments where applicable.
Workflow: §2 human pre-flight → §3 dataset prep → §4 conda env → §5 hardware probe + recommended config → §6 framework adaptation → §7 conditional memory optimizations → §8 training → §9 validation. §11 walks the full sequence with PASS signals at each step.
The skill assumes the user has already cloned qwen-image-finetune. The
agent walks them through §6 to apply XPU adaptations to the clone — these
are not auto-applied. After §6, the rest is largely automatable.
2. Pre-flight (human prerequisites)
The agent cannot complete the items below unattended. The user works through them once before signaling the agent to proceed at §3. Order matters where noted (e.g., Visual Studio is a prerequisite for oneAPI).
2.1 Hardware sanity
- Intel AI PC with Core Ultra processor and Intel iGPU.
2.2 Disk space
The estimates below cover training-related components only (model, data, outputs, conda env). Prerequisite tools required by §2 (Intel Arc driver, Intel oneAPI Base Toolkit, Visual Studio, etc.) need additional space on top of this — consult their respective installers for current sizes.
Estimate at minimum ~65 GB free on the target drive for training components; ~80 GB if both NF4 pre-quantizations (§7.1 + §7.2) are performed. Sizes below are as displayed in Windows File Explorer (binary, GiB — e.g. Windows shows a 9.82 GiB file as "9.82 GB"):
| Item | Approx. size |
|---|---|
| Qwen-Image-Edit model repository (the original, unmodified) | ~54 GB |
| NF4-quantized transformer output (§7.1, optional) | ~10 GB |
| NF4-quantized text encoder output (§7.2, optional) | ~5.5 GB |
| Dataset (depends on yours; character-composition reference is small) | ~1-5 GB |
| Embedding cache (proportional to dataset size) | ~0.5 MB/sample (e.g. ~18 MB for 35 samples) |
| Training checkpoints (LoRA adapters only; lightweight) | ~50 MB × N checkpoints |
| Conda env | ~6 GB |
2.3 Windows registry settings (admin Command Prompt)
Two registry tweaks make AI PC fine-tuning materially smoother. Open an admin Command Prompt (right-click Start → "Terminal (Admin)" or search "cmd" and run as administrator):
Long-path support — model file paths can exceed Windows' 260-char limit:
powershell -Command "Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1"
Shared GPU memory ceiling — AI PC iGPUs use unified memory; raising this
ceiling lets the OS commit more memory to the GPU process during the NF4 cache
phase. Open Registry Editor (Win+R → regedit), navigate to:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\MemoryManager
Change SystemPartitionCommitLimitPercentage to a higher value (e.g. 75
from the default ~57). Reboot for both changes to take effect.
2.4 Intel GPU driver
Install the latest Intel Arc Graphics driver:
https://www.intel.com/content/www/us/en/download/785597/intel-arc-graphics-windows.html
Verify after install: Windows Device Manager → Display adapters → "Intel Arc Graphics" appears without warnings.
2.5 Conda / Python
Install Miniforge from conda-forge:
https://conda-forge.org/download/
Confirm conda --version works in CMD after install (may require new shell
session).
2.6 Visual Studio Community (oneAPI prerequisite)
Required to provide the C++ build toolchain that oneAPI uses for Triton JIT kernel compilation:
https://visualstudio.microsoft.com/vs/community/
During install, check the "Desktop development with C++" workload. Other workloads are not required.
2.7 Intel oneAPI Base Toolkit
Required for bitsandbytes Triton-backed XPU kernels (NF4 quantize on first
load, Adam8bit.step() every step). Install after §2.6 (oneAPI's
installer detects the C++ toolchain).
Match the oneAPI version to the torch+xpu version you plan to install
in §4:
torch+xpu |
Triton package | oneAPI version |
|---|---|---|
2.9.0+xpu |
pytorch-triton-xpu 3.5 |
2025.2 |
2.10.0+xpu |
triton-xpu 3.6 |
2025.3 |
2.11.0+xpu |
triton-xpu 3.7 |
2025.3 |
2.12.0+xpu |
triton-xpu 3.7.1 |
2025.3 |
Download from:
https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html
Default install path: C:\Program Files (x86)\Intel\oneAPI. The training
launcher (templates/launchers/train_xpu.bat) invokes setvars.bat from
this path; if you install elsewhere, update the launcher.
If your
torch+xpuversion isn't listed above (e.g. pip pulls a newer release than this skill anticipates): infer the matching oneAPI from pip's installed Intel transitive dependencies. Complete §2.4 (conda) + §4.1-§4.2 first (create env +pip install torch --index-url https://download.pytorch.org/whl/xpu), then in the activated env run:pip list | findstr /I "intel- onemkl-sycl-"The major version of the
intel-*andonemkl-sycl-*packages tells you which oneAPI release to install (e.g. seeing2025.3.x→ install Intel oneAPI Base Toolkit 2025.3). After oneAPI installs, return to §4.3 to install the remaining project dependencies.
Already have oneAPI installed? If oneAPI is already on your machine,
consider installing the torch+xpu version that matches your existing oneAPI
rather than upgrading oneAPI. Check which oneAPI version you have (see below),
then install the corresponding torch+xpu in §4.2 — this is often less work
than reinstalling oneAPI.
Verify after install (fresh CMD):
call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" --force
where icpx
icpx --version
where icpx must print a real path. To read the exact toolkit version (e.g.
2025.3), look at the InstalledDir line in the icpx --version output —
the version directory in the path is the toolkit version:
InstalledDir: C:\Program Files (x86)\Intel\oneAPI\compiler\2025.3\bin\compiler
^^^^^
this is the toolkit version
(icpx --version also prints Compiler 2025.3.3 ... — note the three-part
compiler build version may differ slightly from the two-part toolkit version
2025.3 in the path. Use the path number to match against the §2.7 table.)
Alternatively, open C:\Program Files (x86)\Intel\oneAPI\Installer\installer.exe
to see the installed toolkit version in the UI.
If setvars.bat reports 'vars.bat' is not recognized, see §10.
2.8 Download Qwen-Image-Edit model
Qwen-Image-Edit is an open model — no HuggingFace account or token is required.
Download to a local directory that the agent will use as
pretrained_model_name_or_path (~54 GB):
huggingface-cli download Qwen/Qwen-Image-Edit-2511 --local-dir <path\to\model>
(https://huggingface.co/Qwen/Qwen-Image-Edit-2511)
Earlier versions of Qwen-Image-Edit (e.g., Qwen-Image-Edit-2509) should also work with this skill in principle; replace the repo ID and local-dir path accordingly.
2.9 Pre-flight checklist
Confirm each item before signaling the agent to proceed:
- AI PC with Core Ultra processor confirmed
- ≥ 80 GB free disk space on target drive
- Long-path registry enabled; GPU shared memory ceiling adjusted; reboot done
- Intel Arc Graphics driver installed (Device Manager shows iGPU clean)
- conda installed (
conda --versionworks) - Visual Studio Community installed with "Desktop development with C++" workload
- Intel oneAPI Base Toolkit installed (
where icpxworks in a fresh CMD aftersetvars.bat) - Qwen-Image-Edit model downloaded to a local directory
-
qwen-image-finetuneproject cloned to a local directory - Training dataset prepared per §3 (or ready to be prepared at §3)
3. Dataset Preparation
The qwen-image-finetune framework (qflux.data.dataset.ImageDataset) supports
three dataset formats. Pick whichever fits the user's workflow; all three are
validated by templates/dataset_validate.py.
3.1 Format options
| Format | When to use | Schema |
|---|---|---|
| Local directory | Quick iteration on a custom dataset | Stem-paired files: <stem>.png (target) + <stem>.png (control) + <stem>.txt (prompt), under training_images/ and control_images/ subdirs |
| HuggingFace dataset | Sharing / public datasets | Repo or local parquet dir; columns: target_image, control_images (list), prompt, optional control_mask |
| CSV | Migrating from another pipeline | Columns: path_target, path_control (one or more path_control_N), prompt, optional path_mask |
Detailed schemas live in src/qflux/data/dataset.py (ImageDataset.__init__
docstring). The §5 recommender emits a data: block matching whichever format
the user has.
3.2 Local directory layout
dataset_root/
├── training_images/
│ ├── sampleA.png # target image — the desired OUTPUT after the edit
│ └── sampleA.txt # required: prompt text (the edit instruction)
└── control_images/
├── sampleA.png # main control — INPUT condition (e.g. background scene)
├── sampleA_control_1.png # optional: 2nd control (e.g. character reference image)
└── sampleA_mask.png # optional: mask of the edited region (character silhouette)
Naming rules (from qflux.data.dataset.ImageDataset documentation):
- Extra control images: append
_control_1,_control_2, … to the base name - Mask:
<base>_mask.pngin eithercontrol_images/ortraining_images/ - Prompt:
.txtfile with the same base name; if present in both dirs,training_images/wins
Alternative directory names accepted: images / target_images / target for targets; control / condition_images / controls for controls.
Multi-control example (character composition task): sampleA.png = background scene, sampleA_control_1.png = character on white background, sampleA_mask.png = character silhouette in the final composition, prompt = "Add the character to the image". This is the pattern used by the TsienDragon/character-composition reference dataset.
Mask usage: when a mask is present, edit_mask_loss applies higher training weight to the masked region, focusing the LoRA on the area that changed between control and target. Useful when only part of the image is edited.
3.3 Sample size guidance
| Sample count | Expected outcome |
|---|---|
| < 5 | Hard floor — dataset_validate.py rejects; too few to cache meaningfully |
| 5–19 | Smoke test only; rapid overfitting expected within the first tens of steps |
| 20–50 | Practical starting point for a narrow task (single subject, single edit type, single viewpoint set). Community experience confirms this range works for specific character/product LoRAs. ² |
| 50–100 | Better generalization; recommended if the edit should work across varying scenes or lighting. A 50-image rendered dataset has been validated for complex spatial transformations. ³ |
| 100–200+ | For more general edit styles that should work across many different scenes and subjects. ² |
Quality beats quantity. Adding low-quality or inconsistent pairs actively harms the LoRA — they introduce noise the model cannot learn a clean pattern from. ¹ ²
Single-task LoRA preferred. If you want to train multiple distinct edits (e.g., style + object replacement), train separate LoRAs rather than one mixed dataset. Multi-task LoRA often causes the tasks to interfere with each other. ²
templates/dataset_validate.py enforces the floor of 5 samples and warns below 30. The framework caches embeddings per-sample (§7.4), so larger datasets cost more upfront cache time but no extra per-step cost during fit.
¹ FlyMyAI LoRA Trainer, https://github.com/FlyMyAI/flymyai-lora-trainer, Aug 2025. Uses a different training framework; cited for model-level image quality guidance applicable to Qwen-Image-Edit.
² HuggingFace Forums: "Question about lora fine tune qwen-image-edit" (John6666, Nov 2025), https://discuss.huggingface.co/t/question-about-lora-fine-tune-qwen-image-edit/170633. Qwen-Image-Edit-specific community guidance; cited for dataset size ranges, quality advice, and single-task LoRA recommendation.
³ とりにく, "vast.AIでQwen image Edit 2509のLoRA学習", https://note.com/tori29umai/n/n256f30d51669, Sept 2025. Uses Musubi Tuner (a separate training framework); dataset construction and spatial transformation advice is model-level and framework-independent.
3.4 Control image and target image — the edit pair
Qwen-Image-Edit is an image editing model, not an image generation model. Every training sample teaches the model one specific edit:
| Part | Role | Example |
|---|---|---|
| Control image(s) | The input condition(s) — what the user provides | Background scene, character reference, source style |
| Target image | The desired output — what the model should produce | The same scene after the edit is applied |
| Prompt | The edit instruction | "Add the character to the image" |
The key difference from caption-based LoRA training (e.g. for image generation): the prompt describes what to do, not what the result looks like.
Multi-control paradigm — the framework natively supports multiple control images per sample, which is useful when the edit requires more than one reference:
- Main control (
sampleA.png): the primary input scene or reference - Additional controls (
sampleA_control_1.png,_control_2.png, …): supplementary references (e.g. a character sheet, a style reference) - Mask (
sampleA_mask.png): region of interest — tells the model which part of the image the edit targets
Verified example (character-composition task): the TsienDragon/character-composition dataset is one verified instance of this setup — it uses two controls (a background image and a character-on-white-background image) plus a mask of the character's silhouette, with a fixed prompt "Add the character to the image". The target is the character correctly composited into the background. User-created datasets following the same format work equally well.
General task examples (for reference, not exhaustive):
- Viewpoint change: control = source angle; target = desired angle; prompt = describe the viewpoint transformation ³
- Object addition: control = scene; target = scene with object added; prompt = "Add [object] to the image"
- Style change: control = original; target = restyled; prompt = describe the style change
Important: control and target must share the same subject/context. Unrelated images in a pair prevent the model from learning a coherent edit mapping.
Practical tip: synthetic or rendered control images (e.g. 3D, game engine) produce very consistent results because they eliminate photographic variation that the model might otherwise try to replicate. ³
³ とりにく, "vast.AIでQwen image Edit 2509のLoRA学習", https://note.com/tori29umai/n/n256f30d51669, Sept 2025. Uses Musubi Tuner (a separate training framework); dataset construction advice is model-level and framework-independent.
3.5 Image quality guidelines
- Resolution: source images should be at least as large as your
target_sizein each dimension. The training preprocessor crops/resizes source images down totarget_size— if the source is smaller, it gets upscaled first (quality loss). Practical guide by training tier:target_size [384, 672](default): source images ≥ 672px in the longer dimension. 512px+ short-side is fine.target_size [512, 768]: source images ≥ 768px in the longer dimension. 512×512 would require upscaling and is not ideal.- Higher source resolution than your
target_sizeis always fine; the crop just has more to choose from. (Recommended range fromdocs/guide/data-preparation.md: 512×512 to 1024×1024 — this is appropriate for the defaulttarget_size [384, 672]and most configurations used with this skill.)
- Format: JPG, JPEG, PNG, WebP — all supported by the framework. RGB (3-channel) required.
- Consistency within dataset: maintain consistent image quality, lighting style, and subject framing across pairs. Inconsistency is a common cause of blurry or incoherent model outputs. ¹
- Quality over quantity: more low-quality pairs actively harm the LoRA — they introduce noise the model cannot learn a clean pattern from. ¹
- Avoid: watermarks or text overlays in control/target images; heavy JPEG artefacts; mixing very different visual styles (e.g., anime and photorealistic) in the same dataset without a compelling reason.
¹ FlyMyAI LoRA Trainer, https://github.com/FlyMyAI/flymyai-lora-trainer, Aug 2025. Uses a different training framework; cited for model-level image quality guidance applicable to Qwen-Image-Edit.
3.6 Prompt strategy
The framework expects prompts to be descriptive editing instructions — text that tells the model what to do, not what the result looks like.
(From docs/guide/data-preparation.md: "Descriptive editing instructions"; recommended length 10–200 words)
| Prompt type | Example | Use |
|---|---|---|
| Edit instruction (correct) | "Add the character to the image" | For image editing LoRA training |
| Target image caption (incorrect for editing) | "A character standing in a room" | Trains the model to generate, not edit |
Key guidelines:
- Describe the transformation: "Add the character to the image" teaches what to do (placement); "A character in a room" only describes what exists in the result — the model cannot learn the editing operation from it.
- Consistency within a dataset: if all samples share the same edit type (e.g., character composition), a fixed or near-fixed prompt like "Add the character to the image" is valid and has been shown to work well. The model learns the edit from the image pairs; the prompt anchors what operation is being requested.
- Specificity when edits vary: if your dataset covers multiple edit types or subjects, prompts should distinguish them — e.g. "Add the character to the outdoor scene" vs "Add the character to the interior scene" — so the model learns to condition on the instruction, not just pattern-match visually.
- Length: 10–200 words accepted by the framework. Short, clear instructions work well for specific tasks; longer prompts are appropriate when the edit is complex or context-dependent.
- Avoid ambiguity: "Edit the image" teaches nothing — a prompt must specify what kind of edit.
3.7 Validation step
Before committing to a training run, validate the dataset:
python templates/dataset_validate.py <path>
# Local dir: python templates/dataset_validate.py <path/to/dataset>
# HF parquet: python templates/dataset_validate.py <path/to/parquet-dir>
# CSV: python templates/dataset_validate.py <path/to/dataset.csv>
JSON verdict format:
{
"valid": true,
"format": "local",
"sample_count": 35,
"issues": [],
"warnings": []
}
Exit code 0 = valid (proceed to §4); 1 = invalid (fix before proceeding). Issues are blockers; warnings are advisory (e.g., small sample count fine for smoke test, problematic for real training).
Common issues caught: missing training_images/ or control_images/ subdir;
target images without matching control or prompt; sample count too low.
Manual check after the script passes — dataset_validate.py only checks
format and structure; it cannot verify semantic correctness. Before starting
a full training run, inspect 3–5 random pairs by eye:
- Does the target image look like a plausible result of applying the prompt to the control image(s)?
- Is it clear what changed between control and target, and does the prompt describe that change?
- Is the subject/scene the same in control and target (they are before/after, not unrelated images)?
If any pair fails this check, the data has a consistency problem that will reduce LoRA quality. Finding and fixing a few bad pairs early is much cheaper than diagnosing a poorly-trained model later.
3.8 Hold out a test set
Before finalising your dataset, set aside at least 3 samples that will not be used for training. These become your held-out test set for §9.2 post-training visual comparison (base model vs. trained LoRA).
Choosing which samples to hold out:
- Pick samples that are representative of the edit you are teaching — the comparison is only meaningful if the test images resemble the training distribution.
- Do not reuse training samples as test samples. The model has seen those images during training, so any apparent quality difference is unreliable.
For HuggingFace / parquet datasets: the test split parquet
(<dataset_dir>/data/test-*.parquet) is usually provided automatically.
Use it directly in §9.2; no extra step needed here.
For local directory datasets: physically move the held-out pairs to a
separate folder (e.g. test/) before running dataset_validate.py. Point
--test-parquet in §9.2 at this folder (or adapt inference_compare.py
to load local images directly).
If you are working from a small dataset where holding out samples would leave too few for training (see §3.3 thresholds), collect a few additional pairs specifically for testing rather than reducing the training set.
4. Environment Setup
§2 covered the system-level prerequisites (oneAPI, driver, conda). §4 sets up the Python training environment. One-time per machine; reuse across runs.
4.1 Create the conda env
conda create -n qwen-image-edit-xpu python=3.12 -y
conda activate qwen-image-edit-xpu
(If a torch+xpu base env already exists locally, prefer cloning it —
conda create -n qwen-image-edit-xpu --clone <existing-xpu-env> — to avoid
re-downloading the torch wheel.)
4.2 Install torch+xpu
Recommended: latest wheel (let pip pick the version)
pip install torch torchvision --index-url https://download.pytorch.org/whl/xpu
Minimum supported: torch >= 2.9.0+xpu. Confirm the installed version
matches your oneAPI version (§2.7 version table). For example,
torch 2.11.0+xpu requires oneAPI 2025.3.
Pin a specific version (if your oneAPI is already installed and you want to match it, rather than upgrade oneAPI):
pip install torch==2.11.0 torchvision --index-url https://download.pytorch.org/whl/xpu
torchvision follows torch automatically from the same XPU channel — no
need to pin it separately.
4.3 Install project dependencies
Before running pip install, the upstream requirements.txt needs two
edits (a fresh clone of tsiendragon/qwen-image-finetune ships with both
problems):
1. Comment out transformer_engine[pytorch] — CUDA-only, no XPU equivalent.
Installing it will fail on a machine without NVCC:
# requirements.txt
- transformer_engine[pytorch]
+ # XPU: transformer_engine is CUDA-only — comment out
+ # transformer_engine[pytorch]
2. Pin bitsandbytes>=0.48.2 — the unversioned bitsandbytes line may
resolve to an older build that lacks the XPU backend:
- bitsandbytes
+ bitsandbytes>=0.48.2
Then install:
pip install -r requirements.txt
If §6 adaptation has already been applied (the repo has device_utils.py
and the XPU patches in place), this is the only required pre-install edit.
4.4 Activate oneAPI in the current shell
The training launcher (templates/launchers/train_xpu.bat) handles this
automatically; for ad-hoc verification:
call "C:\Program Files (x86)\Intel\oneAPI\setvars.bat" --force
If this reports 'vars.bat' is not recognized errors, see §10.1: clear
NoDefaultCurrentDirectoryInExePath across the call. The launcher template
does this; for a manual session, run from a CMD opened directly (not from
inside another wrapper shell).
4.5 Verify
python templates\probe_hw.py
PASS criteria — JSON output must show:
xpu_available: truetorch_versionending in+xpubnb_version>=0.48.2bnb_xpu_available: trueoneapi_versionnon-null (only if §4.4 setvars activated; expected null for fresh sessions before launcher runs)
If any of the first four fail, do NOT proceed to §6 — fix the environment first.
5. Hardware Probe → Config Recommendation
Two scripts produce a training YAML matched to the user's machine:
python templates/probe_hw.py > probe.json
python templates/recommend_config.py --probe probe.json --out config.yaml
probe_hw.py emits hardware/software JSON (schema in its own header).
recommend_config.py reads that JSON, applies the rules table below,
and emits a complete training YAML.
The YAML output contains TODO placeholders for machine-specific paths the user (or agent) must fill in:
model.pretrained_model_name_or_path(original Qwen-Image-Edit model directory)model.transformer_path(pre-quantized NF4 dir from §7.1 — optional; omit to use online NF4 quantization)data.init_args.dataset_path(validated dataset; see §3)logging.output_dirandcache.cache_dir
Pass --dataset-path <path> to the recommender to fill the dataset_path
placeholder inline.
The recommender also prints a list of selected §7 catalog patterns to stderr — these are the conditional optimizations from §7 that apply to this machine.
5.1 Recommended settings
Minimum required: 32 GB AI PC. 16 GB is not supported: the NF4 DiT alone requires ~10 GB XPU allocation, leaving insufficient headroom for training activations on a 16 GB unified-memory machine. The minimum practical tier is 32 GB.
The recommender emits the following settings. Each row includes the reason and test evidence where available.
| Setting | Value | Why |
|---|---|---|
model.quantize_type |
nf4 |
Scope of this skill; NF4 QLoRA only |
model.transformer_path |
<pre-quant dir or null> |
Optional. Online NF4 uses shard-by-shard streaming so memory impact is negligible. Value of pre-quantizing is startup time (saves one quantization pass per run). Recommended for multiple training iterations. See §7.1. |
Pre-quantize text_encoder (model.text_encoder_path) |
optional; see §7.2 | Reduces text_encoder from ~15 GB to ~5.5 GB (NF4). Reduces cache-phase RAM by ~8 GB on 32 GB machines — eliminates heavy paging. See §7.2 for measured effect and prep steps. |
cache.use_cache (§7.4) |
true |
Required. Without caching, the trainer keeps text_encoder active during fit to encode prompts per batch while the NF4 DiT is also loaded — combined memory pressure crashes on 32 GB machines. Cache-first runs text_encoder and VAE at cache time, writes pre-computed embeddings and image latents to disk, then frees text_encoder before the NF4 DiT loads for fit. |
| Mode-aware loading (§7.3) | always | Enforces the above separation at the code level |
data.processor.target_size |
[384, 672] default |
Balanced default; three-tier options: |
↳ [256, 448] |
Lower XPU demand | Significantly faster per step; fast iteration and prototyping |
↳ [384, 672] |
Default | Balanced default |
↳ [512, 768] |
Higher (tight on 32 GB) | Significantly slower; system RAM reaches the 32 GB ceiling on 32 GB machines |
train.gradient_checkpointing |
true |
Recommended at all resolutions; required at ≥[384,672]. Without it, activation memory causes XPU out-of-memory during attention at the default and larger resolutions. Reducing LoRA rank does not resolve this. |
train.max_train_steps |
1000 |
Practical starting point for a narrow task on a ~35-sample dataset. Reduce to 50–200 for a quick pipeline sanity check; increase for larger datasets or more complex edits. Monitor smooth loss — stop when it plateaus across consecutive checkpoints. |
train.gradient_accumulation_steps |
2 |
Effective batch-size > 1 without extra per-step memory |
optimizer.class_path |
bitsandbytes.optim.Adam8bit |
Memory difference vs torch.optim.Adam is negligible at this LoRA scale. Real value: measurably faster per step via Triton-backed optimizer. |
lora.dtype |
"bf16" |
Optional; stores LoRA adapter weights in bf16 vs fp32 default (~48 MB savings at r=16 — negligible). §6.6 NF4-A2 autocast handles forward compute dtype; this is a complementary parameter-storage setting. |
lora.r |
16 |
Memory-neutral — activation memory dominates, not LoRA parameters. Quality choice; adjust freely between 8–32. |
data.batch_size |
1 |
Single GPU, minimum |
data.num_workers |
1 |
Windows spawn-mode; qflux pydantic schema rejects 0 |
train.mixed_precision |
"no" |
accelerate's bf16 path wraps torch.autocast("cuda") — does not dispatch to XPU. qflux wraps the NF4 forward pass in torch.autocast("xpu", ...) internally (see §6). |
5.2 Manual override
If the probe is incorrect (e.g. oneapi_version = null because setvars.bat
hasn't run yet — see §4) or different settings are desired:
- Edit the generated YAML directly. §5.1 documents what each key does and when to set it.
- Or hand-edit
probe.jsonbefore piping into the recommender.
The recommender is conservative — it picks the safest config that fits.
Loosen settings (larger batch, larger LoRA rank, lower grad-accum) only after
the conservative config trains successfully and you verify XPU memory has
headroom (via templates/monitor_xpu_memory.py).
HuggingFace / parquet dataset path:
--dataset-pathexpects a local directory withtraining_images/andcontrol_images/subdirectories (see §3.2). If your dataset is in HuggingFace parquet format (downloaded via HF Hub), the recommender will emit a plain string path that the qflux loader cannot parse. Edit the generated YAML'sdata.init_args.dataset_pathto use the dict format before training:data: init_args: dataset_path: - repo_id: <path/to/local-parquet-dir> split: train
6. Framework Adaptation
Scope: §6 describes adapting the original
tsiendragon/qwen-image-finetunerepository (CUDA-only, unmodified). If you cloned an already-adapted fork, some or all of these changes may already be present — run the §6.6 import test to confirm before re-applying anything.
The qflux codebase (qwen-image-finetune's package) hardcodes torch.cuda.*
calls — it's a custom training loop, not built on HF Trainer's auto-XPU
detection. The adaptations below are required before training will run on XPU.
6.1 Device-agnostic helpers (device_utils.py)
Replace each torch.cuda.* call in the framework with a device-agnostic
wrapper that checks XPU first. This confines the XPU adaptation to the
framework's own source files and leaves third-party libraries (Triton,
bitsandbytes, accelerate) untouched.
In qwen-image-finetune the wrappers live at src/qflux/utils/device_utils.py.
Verify the following helpers are present; if adapting a fresh clone, create
this file before any other change.
# src/qflux/utils/device_utils.py
# enable XPU: device-agnostic helpers — call these instead of torch.cuda.* directly.
import contextlib
import torch
def device_empty_cache() -> None:
if torch.xpu.is_available(): # enable XPU
torch.xpu.empty_cache()
elif torch.cuda.is_available():
torch.cuda.empty_cache()
def device_synchronize() -> None:
if torch.xpu.is_available(): # enable XPU
torch.xpu.synchronize()
elif torch.cuda.is_available():
torch.cuda.synchronize()
def device_manual_seed_all(seed: int) -> None:
if torch.xpu.is_available(): # enable XPU
torch.xpu.manual_seed_all(seed)
else:
torch.cuda.manual_seed_all(seed)
def device_context(device):
"""Device context manager replacing torch.cuda.device().
torch.xpu.device("cpu") raises; this helper returns nullcontext() for
CPU and the correct accelerator context otherwise.
"""
device_str = str(device) if device is not None else "cpu"
if device_str == "cpu":
return contextlib.nullcontext()
if torch.xpu.is_available(): # enable XPU
return torch.xpu.device(device)
return torch.cuda.device(device)
Replace each direct torch.cuda.* call in the framework:
| Original | Replacement | Import |
|---|---|---|
torch.cuda.empty_cache() |
device_empty_cache() |
from qflux.utils.device_utils import device_empty_cache |
torch.cuda.synchronize() |
device_synchronize() |
from qflux.utils.device_utils import device_synchronize |
torch.cuda.manual_seed_all(s) |
device_manual_seed_all(s) |
from qflux.utils.device_utils import device_manual_seed_all |
with torch.cuda.device(dev): |
with device_context(dev): |
from qflux.utils.device_utils import device_context |
6.2 Find and fix residual patterns
The device_utils.py helpers cover functional calls. A separate scan is
needed for string-based device comparisons and CUDA-only library imports,
which helpers cannot address:
grep -rn "torch\.cuda\.\|flash_attention_2\|transformer_engine\|device\.type.*cuda" src/qflux/
| Pattern | Fix |
|---|---|
flash_attention_2 (in from_pretrained calls or config.json) |
Replace with "sdpa". Flash Attention 2 is CUDA-only |
transformer_engine import / use |
Comment out — CUDA-only library |
device.type == "cuda" literal compare |
Add an xpu branch (string compare can't be patched) |
mp.set_start_method("spawn", force=True) |
Guard with if not torch.xpu.is_available(): .... Windows defaults to spawn already; force=True raises RuntimeError: context has already been set |
torch.cuda.device(device) context manager |
Replace with device_context(device) from device_utils.py. torch.xpu.device("cpu") raises when device="cpu" (e.g. text_encoder placed on CPU for inference); device_context() returns nullcontext() for CPU, the correct accelerator context otherwise |
train_epoch() ignores max_train_steps — runs num_epochs × dataset_size steps instead |
Add a guard at the top of the per-batch loop in train_epoch(): if self.global_step >= self.config.train.max_train_steps: return |
Example device.type fix in a config validator:
if d.type == "cuda":
if not torch.cuda.is_available():
raise ValueError(f"CUDA not available but got device={d}.")
# enable XPU: validate XPU device availability
if d.type == "xpu" and not torch.xpu.is_available():
raise ValueError(f"XPU not available but got device={d}.")
6.3 accelerate single-card XPU config
qflux.main launches via accelerate launch --config_file <path>. Ship an
XPU-specific config alongside the CUDA one (e.g., accelerate_config_xpu.yaml):
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: NO
mixed_precision: 'no'
num_machines: 1
num_processes: 1
main_training_function: main
dynamo_backend: 'no'
use_cpu: false
deepspeed_config: {}
Key differences from a typical CUDA multi-GPU config:
| Field | CUDA multi-GPU | XPU single-card | Why |
|---|---|---|---|
distributed_type |
MULTI_GPU |
NO |
Single process; no collective ops |
mixed_precision |
bf16 |
'no' |
accelerate's bf16 wraps torch.autocast("cuda", ...) — does not dispatch to XPU. Apply torch.autocast("xpu", ...) in the trainer instead (§6.6 NF4-A2) |
dynamo_backend |
inductor |
'no' |
XPU torch.compile is experimental; accelerate's wrapper often fails through it |
YAML gotcha:
dynamo_backend: nounquoted parses as YAML booleanFalse; accelerate then calls.upper()on it and raisesAttributeError: 'bool' object has no attribute 'upper'. Always quote'no'(and same formixed_precision: 'no').
6.4 Pydantic schema relaxation for num_workers
Many CUDA-origin frameworks (qflux included) declare num_workers with a
> 0 constraint. On Windows under spawn start-method, classes registered
via trust_remote_code are not picklable across processes — workers crash
on dataset iteration. Setting num_workers = 0 would fix it but the schema
rejects 0.
Relax the constraint to >= 0 in the dataset config validator. The
recommender (§5) defaults to 1 (since 0 disables multiprocessing entirely
and slows data loading), but 0 should at least be acceptable as an escape
hatch for the picklability case.
6.5 BitsAndBytes XPU backend (informational)
Knowing which bnb operations dispatch to which backend on XPU helps the
agent diagnose failures and explains why §2 requires Visual Studio + oneAPI.
For bitsandbytes >= 0.48.2:
| Operation | Backend | When it runs |
|---|---|---|
dequantize_4bit |
SYCL | Every NF4 forward and backward pass |
dequantize_blockwise |
SYCL | Used alongside NF4 dequant |
gemv_4bit |
SYCL | Inference-only single-token path |
quantize_4bit, quantize_blockwise |
Triton | Initial quantization (e.g. §7.1 pre-quantize step) |
optimizer_update_8bit_blockwise, optimizer_update_32bit |
Triton | Every bnb.optim.Adam8bit.step() call |
Key consequence: NF4 forward/backward does not use a dedicated NF4 gemm kernel — bnb dequantizes the 4-bit weight via SYCL and hands off to PyTorch's native XPU matmul. Only the optimizer step and initial quantization actually go through Triton.
Practical impact: if a training run loads a pre-quantized NF4
checkpoint (§7.1 output) and uses Adam8bit, Triton kernel JIT compile
runs on the first optimizer step — this is why §2.7 oneAPI + §2.6 Visual
Studio C++ workload are required.
The dispatch above is empirical for bitsandbytes 0.49.2. Newer versions
may shift; if odd failures appear, check bitsandbytes/backends/xpu/ops.py
in your installed bnb version for the current registration.
6.6 NF4 QLoRA required patches
When adapting the framework for NF4 QLoRA, two additional patches are required beyond the general XPU adaptation in §6.1-§6.4. In qwen-image-finetune these are already applied; verify they are present after applying §6.
NF4-A1. torch_dtype on from_pretrained
Symptom if missing: RuntimeError from attention op with a dtype
mismatch between fp16/bf16/fp32 tensors on the first forward pass.
Why: without torch_dtype, non-quantized params (norms, biases) stay at
the checkpoint's stored dtype. PEFT LoRA adapters def
…(truncated)