tsiendragon/qwen-image-finetune/qwen-image-edit-aipc-finetune-05-adaptation
Claude Code and agent skill by tsiendragon/qwen-image-finetune
- community
Checking that the SKILL.md file is still in the repository.
Step 5 of 8 of the Qwen-Image-Edit AI PC fine-tuning walkthrough. Use after qwen-image-edit-aipc-finetune-04-config. Apply all required XPU/NF4 source patches to the qwen-image-finetune clone: device_utils.py, residual CUDA/flash-attention fixes, the accelerate XPU config, Pydantic schema patches, the NF4 QLoRA autocast/dtype patches, mode-aware loading, and (only for QwenImageEditPlus models) the Plus trainer load_model. Ends with an import test and an adaptation checker. Only use once step 4 config lint passes.
Install
npx vskill@latest install tsiendragon/qwen-image-finetune/qwen-image-edit-aipc-finetune-05-adaptationSKILL.md
Step 5 — Framework Adaptation (XPU + NF4 Patches)
Series position: Step 5 of 8.
Scope: this step adapts a clone oftsiendragon/qwen-image-finetunefor XPU. Theframework is a custom training loop (not HF Trainer), so it hardcodestorch.cuda.*andCUDA-only paths. If your clone is already adapted (hasdevice_utils.pyand the patchesbelow), some or all steps may already be done — run the §5.8 import test + checker to confirmbefore re-applying anything.
Prerequisites:
config.yamlwithtrainer:set (Step 4) — determines whether the Plus section (§5.7) applies- The
qwen-image-finetuneproject directory (Step 1)
Next step: when adaptation_check.py exits 0, proceed to skill qwen-image-edit-aipc-finetune-06-quantize.
Which trainer files to adapt
§5.1–§5.6 are framework-wide and always apply, including to the base trainer (`qwen_image_edit_trainer.py`) — even when your model uses the Plus trainer, because QwenImageEditPlusTrainer inherits the base trainer's training loop (that's where the NF4-A2 autocast lives) and its device-wrapped helpers. So: always do §5.1–§5.6.
Then, only if `detect_trainer.py` selected `QwenImageEditPlus` (Step 4), also do §5.7 — adapting the Plus trainer's own load_model. (The Plus trainer overrides only load_model and the multi-image methods; everything else it inherits from the base trainer you already adapted.)
5.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. Create this file (before any other change if adapting a fresh clone) with exactly this content:
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 |
Import the helpers from the submodule directly (as in the table); there is no need to re-export them from qflux/utils/__init__.py.
5.2 Find and fix residual patterns
The helpers cover functional calls. A separate scan is needed for string-based device comparisons and CUDA-only library imports, which helpers cannot address:
The scan covers both trainer files (qwen_image_edit_trainer.py and qwen_image_edit_plus_trainer.py) — fix every hit in whichever one(s) your model uses.
| 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) | |
Hardcoded model id in load_model — load_vae("Qwen/Qwen-Image-Edit", …), load_qwenvl("Qwen/Qwen-Image-Edit", …) (base) or "Qwen/Qwen-Image-Edit-2509" (Plus) | Replace with self.config.model.pretrained_model_name_or_path. The hardcoded id makes the trainer fetch that repo from the network instead of using the model you already downloaded locally (Step 1) — wasting time, and loading the wrong model's VAE/text_encoder if you trained on a different checkpoint. Point it at your local model dir. | |
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 | |
train_epoch() ignores max_train_steps — runs num_epochs × dataset_size steps instead | Two edits, both needed. (1) Guard the per-batch loop in train_epoch(): if self.global_step >= self.config.train.max_train_steps: return. (2) Break the epoch loop in fit() on the same condition: if self.training_interrupted or self.global_step >= self.config.train.max_train_steps: break. See the note below — edit (1) alone stops training but not the epoch loop. |
Example device.type fix in a config validator:
Why the `max_train_steps` fix needs both edits.train_epoch()'s guard saves a finalcheckpoint andreturns — but it only returns fromtrain_epoch().fit()'sfor epoch in range(...)loop keeps going, so every remaining epoch re-enterstrain_epoch(),trips the guard again, and writes another full resumable checkpoint before returning. Withnum_epochsset well above the epoch countmax_train_stepsactually reaches (the Step 4recommender's default pairing), that is one redundant checkpoint directory per remaining epoch,each carrying a complete copy of the optimizer state — enough to fill a disk on a long run. Thesymptom is a pile ofcheckpoint-last-<epoch>-<step>-lastdirectories that all share the samestep number and differ only by epoch. Nothing prunes them:train.checkpoints_total_limitisdeclared in the schema but never wired intoProjectConfiguration, so it has no effect.The copies are byte-identical, so a run that skipped edit (2) still trained correctly — the costis disk and save time, not the model.adaptation_check.pyreports the missing break as a WARNfor that reason, not an ERROR.Breaking the epoch loop as well makes the guard write exactly one final checkpoint.fit()'s ownclosingsave_checkpoint(...)reuses the same epoch and step, hence the same directory name, so itoverwrites rather than adding another.
5.3 accelerate single-card XPU config
qflux.main launches via accelerate launch --config_file <path>. Create an XPU-specific config accelerate_config_xpu.yaml in the project root:
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' | Set 'no' so this file matches what actually runs: base_trainer passes a hardcoded mixed_precision="no" to Accelerator, and an explicit argument overrides whatever the launch config says. Mixed precision is applied by the trainer's own torch.autocast (§5.5.3 NF4-A2) | |
dynamo_backend | inductor | 'no' | This recipe does not use torch.compile; leave the wrapper out of the path |
YAML gotcha:dynamo_backend: nounquoted parses as YAML booleanFalse; accelerate thencalls.upper()on it and raisesAttributeError: 'bool' object has no attribute 'upper'.Always quote'no'(and same formixed_precision: 'no').
5.4 Pydantic schema patches
Two edits to src/qflux/data/config.py so the NF4 config the recommender emits validates against the upstream schema.
a) Add the `quantize_type` field to `ModelConfig`. Upstream ModelConfig has quantize: bool but no `quantize_type` field, and it is declared with ConfigDict(extra="forbid") — so any config carrying model.quantize_type: nf4 fails immediately with pydantic ... Extra inputs are not permitted. Add the field:
The NF4 loader (§5.5 / Step 6) reads quantize_type == "nf4" to pass use_nf4=True; without this field the whole NF4 recipe is unreachable.
b) Relax `num_workers` to `>= 0`. Upstream declares 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, and num_workers = 0 (the fix) is rejected by the schema. Relax the constraint to >= 0 in the dataset config validator. The recommender defaults to 1, but 0 should be acceptable as an escape hatch. (This is one of two Windows spawn-related fixes — the other is the mp.set_start_method guard in §5.2; they are independent.)
Cache key default (`prompt_empty_drop_keys`). UpstreamCacheConfigdefaults this to["prompt_embed", ...]— note the missings. The cached key isprompt_embeds, socaption-dropout raisesKeyError: 'prompt_embed'. The recommender sidesteps this by **emittingcache.prompt_empty_drop_keys: ["prompt_embeds", "prompt_embeds_mask"]** in the config, so nosource edit is required as long as you use the generated config. (If you hand-write a config,set this key yourself.)
5.5 BitsAndBytes XPU backend + NF4 QLoRA required patches
5.5.1 BitsAndBytes XPU backend (informational)
Knowing which bnb operations dispatch to which backend on XPU helps diagnose failures and explains why Step 1 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. Step 6 pre-quantize) | |
optimizer_update_8bit_blockwise, optimizer_update_32bit | Triton | Every bnb.optim.Adam8bit.step() call |
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/qwen-image-edit-aipc-finetune-05-adaptation/SKILL.md
Popularity
- Repository stars
- 253
- Repository forks
- 27
- 7-day trend score
- 28
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/tsiendragon/qwen-image-finetune/qwen-image-edit-aipc-finetune-05-adaptation)