feat: add native Intel XPU (torch.xpu) device support - #9401
feat: add native Intel XPU (torch.xpu) device support#9401LexiconCode wants to merge 32 commits into
Conversation
4f2d1e7 to
c15e2b6
Compare
e3c67cf to
348c87c
Compare
348c87c to
20bb0d8
Compare
20bb0d8 to
409bd1a
Compare
lstein
left a comment
There was a problem hiding this comment.
Thanks for this — it's a genuinely well-scoped contribution. I ran an adversarial review (the goal being to break it rather than to evaluate it), and I want to lead with what held up, because a lot did:
- No CUDA / MPS / CPU regression found. Every refactor I traced is behaviour-preserving on the existing backends: the
wan_latents_to_videorestructure intototal_vram: int | Noneis identical for CUDA, CPU, MPS andcpu_onlyVAEs;torch.cuda.OutOfMemoryError→torch.OutOfMemoryErroris a no-op (they're the same object); themodel_cache.pydevice-label change still yields"cuda device"for an index-less CUDA device. - Packaging is clean.
uv export --frozen --extra {cpu,cuda,rocm}is byte-identical before and after, except one added comment line. The 958-lineuv.lockchurn is entirely conflict-marker rewriting from addingxputo the conflicts group — zero removals, zero version changes on shared packages.uv lock --checkpasses under both current uv and the CI-pinned 0.6.10. - The partial-loading machinery really is device-agnostic. I read
cached_model_with_partial_load.pyand all oftorch_module_autocast/looking for CUDA assumptions now reachable viarunning_with_dedicated_vram— no pinned memory, no streams, no.cuda(), no dtype assumptions. Your call to enable it on XPU is sound. - Your thread-locality comment is correct. I checked
c10/xpu/XPUFunctions.cppin v2.7.1:thread_local DeviceIndex curDeviceIndex. The multi-worker pinning is sound, and I couldn't construct an interleaving wherenormalize("xpu")resolves to another worker's GPU. - The test suite is thorough and passes locally (3859 passed, 119 skipped, 8 xfailed).
Requesting changes for the CI failures and a handful of real defects below.
1. CI is red on four checks (all mechanical)
invokeai/app/services/invocation_stats/invocation_stats_default.py:98 — the new ternary is ~158 chars; uv tool run ruff@0.11.2 format --check . rewrites it. This is the python-checks failure.
invokeai/app/services/events/events_common.py:143 — the source still reads "...(set only when running on a CUDA GPU)", but the committed openapi.json:45725 and schema.ts:18420 read "...on a GPU". It looks like the generated artifacts picked up an edit that didn't land in the Python. That single line is the entire openapi-checks and typegen-checks failure — fix the description in events_common.py and regenerate.
docs/src/generated/settings.json — not regenerated after the device / generation_devices description changes in config_default.py. This is the check-and-build failure (pnpm run generate-docs-data).
2. The XPU mem_get_info fallback feeds a formula that assumes different semantics
TorchDevice.xpu_mem_get_info falls back to total_memory - torch.xpu.memory_reserved(device). But memory_reserved() counts only this process's caching-allocator blocks, whereas torch.cuda.mem_get_info's free is a driver-global query. ModelCache._get_vram_available then applies the CUDA formula to both:
vram_available_to_process = vram_free + vram_allocatedwhich is only sound when vram_free is driver-global. The fallback is therefore high by (other processes + the Level-Zero context + compiled kernel binaries) — on Intel that's routinely 0.5–1.5 GB, and the Arc is usually also the display GPU.
Concrete trigger, using the exact scenario the docstring cites as the fallback's reason to exist — Arc 16 GiB in a passthrough VM, ~2 GiB held by the compositor + L0 context, fresh process, default device_working_mem_gb = 3:
- Fallback reports
free = 16 GiB→vram_available_to_process = 16 GiB _load_locked_modelsees 13 GiB available and fully loads a 12 GiB transformer- Real free is now ~2 GiB against a 3 GiB working-memory contract → OOM on the first denoise step
- The cache still believes it has room, so
_offload_unlocked_modelsfrees nothing on retry
The docstring's "budget hint rather than a guarantee" is fair, but the caller treats it as a hard budget. The same root cause hits attention.py:28 (auto_detect_slice_size picks "balanced" where it should pick "max").
Sub-case that's worse: if get_device_properties also fails, the function returns (0, 0). Work the arithmetic through _get_vram_available:
vram_available_to_process = 0 + allocated
vram_total_available_to_cache = allocated - working_mem
vram_cur_available_to_cache = allocated - working_mem - _get_vram_in_use() # == allocated
= -working_mem # -3 GiB, forever, regardless of device state
Every subsequent lock() then evicts all unlocked models, unloads the model being locked, and calls partial_load_to_vram(-3221225472) — so everything runs permanently with per-layer CPU→GPU autocast and no error is surfaced. Meanwhile _calc_ram_available_to_model_cache takes the total or None branch and sizes a healthy-looking 32 GB RAM cache, so the logs look fine. Note this doesn't need a probe failure: max(total - reserved, 0) produces the same lock-in whenever reserved > total.
Minor, same function: the except (RuntimeError, AttributeError) doesn't catch what _lazy_init actually raises —
>>> TorchDevice.xpu_mem_get_info(torch.device('xpu', 0))
AssertionError: Torch not compiled with XPU enabled
Same for get_device_name at devices.py:158. Reaching it needs an XPU execution device on a non-XPU torch, which get_generation_devices rejects at startup — but the clause doesn't match its own documented failure mode. (Also: torch.xpu.mem_get_info does exist in 2.7.1, so the AttributeError arm is dead weight rather than a bug.)
3. The FP8 probe runs on the wrong device during idle-GPU offload
Three things compound here:
_device_supports_fp8_storage(...)is the first statement in_should_use_fp8, before every exclusion — so it fires on the very first model load of any kind (tokenizer, VAE, anything).torch.zeros(2, device="xpu")is index-less, so it resolves through the thread's current XPU device._maybe_offload_to_idle_gpure-pins only InvokeAI's thread-local viaTorchDevice.set_session_device(borrowed_device)— it never callstorch.xpu.set_device, unlike worker startup atsession_processor_default.py:631-636, which does both.
So with generation_devices: [xpu:0, xpu:1] and offload_text_encoders_to_idle_gpus on (the default):
- Worker A is pinned to xpu:0 (
torch.xpu.set_device(xpu:0)on that thread) - A hits a
CompelInvocation(idle_gpu_offloadable=True) and borrows xpu:1 - The
ModelLoader's_torch_deviceis xpu:1 - The probe allocates on xpu:0 — the busy denoise GPU the offload existed to protect — and xpu:1's answer is inferred from xpu:0
load_model is also reachable from API/install threads, where the probe forces XPU lazy SYCL init on the FastAPI thread.
Two related issues in the same helper:
@lru_cacheis keyed on the device type, not the device. Your own docstring says float8 support is build/driver dependent and "emerging" on Xe2 — that's a per-device property. On a discrete Arc + Iris Xe iGPU, whichever device probes first decides for both; on the wrong one,_apply_fp8_to_nn_modulecasts every Linear/Conv weight and the pre-hook upcast raises at forward time.- A transient failure is memoized forever, silently.
except Exception: return Falsewithmaxsize=None, no logging, andcache_clear()is never called outside tests. The probe fires during a model load — i.e. exactly when the device may be full — sotorch.OutOfMemoryError(aRuntimeErrorsubclass), a UR/L0 transient, or "Cannot re-initialize XPU in forked subprocess" all permanently disable FP8 for the process. The only symptom is 2× VRAM and thrashing; the only cure is a restart. At minimum: log the exception, and don't cache failures.
Lastly, the probe doesn't exercise the runtime path. It tests float32 → float8_e4m3fn → float16 on XPU, but at runtime the storage cast happens on CPU (params are still CPU-resident at load time), and the XPU-side operations are the fp8 host→device copy plus the pre-hook's fp8 → bf16 upcast (compute_dtype is typically bf16 for Krea-2/FLUX). A build where fp8→fp16 works but fp8→bf16 doesn't would probe True and then fail at forward.
4. try_borrow lost its device-type filter
device_pool.py:99 checks only exclude.type not in _OFFLOAD_DEVICE_TYPES, then takes any registered key != exclude_key. Before this PR only CUDA devices could ever be registered, so cross-type borrowing was structurally impossible; now both types share one pool.
generation_devices: ["cuda:0", "xpu:0"] is accepted by both validators (config_default.py:285, app_info.py:123) and by TorchDevice.get_generation_devices, so a cuda:0 session would be handed xpu:0 for its text encoder. Reachability is low (the pinned +cu128 / +xpu wheels are mutually exclusive, so this needs a custom build), but the fix is one line — filter candidates by key.startswith(exclude.type).
5. Design question: Intel integrated GPUs
Unlike CUDA, where every enumerated device is a discrete accelerator, Level Zero enumerates the CPU's iGPU alongside any discrete card (and on Data Center GPU Max, each tile separately unless ZE_FLAT_DEVICE_HIERARCHY=COMPOSITE). Two consequences:
devices.py:220-221—_all_available_devicestakesrange(torch.xpu.device_count())with no filter, so on the mainstream Arc configuration (iGPU + discrete Arc)generation_devices: autoresolves to[xpu:0, xpu:1]and dispatches half the queue to the iGPU._OFFLOAD_DEVICE_TYPESalso makes it a borrow target, sotry_borrowmay hand it an 8.9 GB text encoder.model_cache.py:599—running_with_dedicated_vram = type in ("cuda", "xpu")treats an iGPU as having dedicated VRAM. The comment three lines above states the policy MPS is excluded for — "memory is shared with the CPU" — which is exactly an iGPU's topology, yet it takes theelif "xpu"branch at:1103rather than the MPS branch at:1107that deliberately usespsutil.virtual_memory().available.
On, say, a 16 GB Lunar Lake laptop: get_device_properties(igpu).total_memory reports a large share of system RAM, so heuristic 2 sizes the RAM cache from it and _get_vram_available budgets the same DRAM again — doubled once more by keep_ram_copy_of_weights=True. Heuristic 2's stated intent ("cap the RAM cache at 1× VRAM") is meaningless when "VRAM" is the RAM being capped.
I don't think torch exposes a clean is_integrated flag (I checked XPUDeviceProp.h — there's architecture and gpu_eu_count, but nothing direct), so this may be a name/arch heuristic or simply a documented ZE_AFFINITY_MASK note. Happy to hear what you think is right; I'd rather not guess for you.
6. Smaller items
pins.jsonhas noxpuentry, so the launcher will have no XPU install option — users would be stuck with the manualpip install invokeai[xpu]from your description. Worth adding alongside the extra.vae_working_memory.py:207(untouched by this PR) is the only estimator that branches on backend, and it branchesis_rocm = torch.version.hip is not None— so XPU silently gets the CUDA constants (2900/1600), which assume flash/efficient attention. If XPU's SDPA falls back to math attention the way ROCm's does, the right constants are ~2–4× larger. Given your note that XPU exhaustion hangs rather than raising, this is an unpleasant one to guess wrong on, and Qwen isn't in your QA list. Worth a spot-check on Qwen Image / Qwen Image Edit.invocation_stats_default.py:31,memory_snapshot.py:50,model_cache.py:1333all dispatch ontorch.cuda.is_available()first. On a mixed NVIDIA + Arc box running onxpu:0, node stats and the summary report0.0 GBwhile the Arc is full, and the cache log prints"CUDA Memory Allocated: 0.0 MB". Diagnostic-only, but it'd make XPU bug reports hard to act on. Unlike_get_vram_in_use, none of these consultself._execution_device.anima_latents_to_image.py:59—"out_of_host_memory"is Intel's catch-all for driver-side resource failures (kernel compilation, handle exhaustion), not just host OOM, so a genuinely broken decode gets a pointless tiled retry. Bounded and re-raises, so cosmetic. (The lowercasing itself is correct — I checked every needle.)- Metadata only:
buildSD1Graph.ts:144/buildSDXLGraph.ts:159writerand_device: 'cuda'into image metadata on XPU machines. - Docs: nothing under
docs/mentionsxpuor the[xpu]extra. You offered an Intel install section — yes please.
Nothing here is architectural; the shape of the change is right. §1 is mechanical, §2 and §3 are the ones I'd want fixed before merge, and §5 is a genuine question rather than a demand. Thanks again for the care that went into this, and for the detailed QA notes — they made the review much easier.
|
Tried this on Fedora 44 on my Framework 13 Pro which has the new PantherLake X7 385H with Intel Arc B390 integrated graphics, 32GB system RAM and it ran just fine. Performance seems better than my Apple MacBook Pro 14 with M2 Max chip (30GPU cores). Full install requires:
Only tested SDXL so far but will try other models shortly. |
Additive xpu branches only: device selection and normalization, float16 default, VRAM queries with a passthrough-VM fallback (missing SYCL free-memory aspect), fp8 layerwise casting via a runtime probe, VAE auto-tiling, partial loading, stats/OOM handling, multi-GPU parallel session execution (device enumeration, config/API validation, worker pinning, and the generation-device options endpoint), and the auxiliary image utilities (depth/SAM/DINO pipelines accept xpu instead of falling back to CPU; cache clearing is device-agnostic). CUDA (incl. ROCm), MPS, and CPU behavior unchanged. Verified end to end on Arc Pro B70 hardware, including dual-GPU worker startup.
Mock-based, mirroring the CUDA/MPS suites: device choice, dtype, normalize, the xpu_mem_get_info fallback branches, and multi-GPU generation_devices resolution/validation/labeling on XPU. Also makes the auto-without-CUDA generation-devices test hermetic on XPU machines.
torch 2.7.1+xpu / torchvision 0.22.1+xpu / pytorch-triton-xpu 3.3.1 from the torch-xpu index, gated to linux-x86_64 and win_amd64; uv.lock regenerated.
Regenerate openapi.json and schema.ts for the xpu device values.
The Anima VAE decode catches OOM and retries once with tiling, which caps peak allocation. Detection matched `torch.cuda.OutOfMemoryError` or the words "out of memory" in the message, so it missed XPU entirely: torch's XPU backend does not raise a recoverable `torch.OutOfMemoryError` on exhaustion, it surfaces the Level Zero/UR result code as a plain RuntimeError -- and `UR_RESULT_ERROR_OUT_OF_DEVICE_MEMORY` contains no spaces, so the existing substring never matched. The decode therefore failed outright instead of retrying tiled. Match the `*_OUT_OF_DEVICE_MEMORY` / `*_OUT_OF_HOST_MEMORY` spellings (both UR and ZE prefixes) alongside the existing conditions, and fold the cuDNN/cuBLAS checks into the same case-insensitive comparison. Extends the existing parametrized retry test with the three XPU spellings; each was verified to fail before this change. Note the driver behaviour itself is not reproducible on the hardware used here -- this stack overcommits into host RAM and hangs rather than raising -- so the tests pin the classifier, not the driver.
Matches the committed openapi/schema artifacts, which already say "on a GPU".
(0, 0) made the cache's available-VRAM arithmetic collapse to a constant -working_mem budget for the life of the process. Also widen the except: the failure type moves between torch releases (RuntimeError for the missing SYCL aspect, AssertionError from _lazy_init), and warn once when the blind estimate is in use.
…ing failures The probe allocated via an index-less "xpu", which resolves through the thread's current XPU device rather than the device being loaded onto -- so during idle-GPU encoder offload it measured the busy denoise GPU. It was also keyed on device type, letting one device decide for another, and memoised transient failures (it runs during a load, when the device may be momentarily full) with no way back but a restart. Also probe the bf16 upcast, which is the runtime path for Krea-2/FLUX.
Worker startup set both the session device and torch's per-thread current device; the offload borrow set only the former, leaving index-less allocations on the worker's own GPU. Extracted the shared helper and guarded it on backend availability.
generation_devices accepts a mixed list, so a cuda session could be handed an xpu device for its text encoder.
torch exposes no is-integrated flag, but Level Zero does (ZE_DEVICE_PROPERTY_FLAG_INTEGRATED), and its loader already ships with the torch+xpu runtime -- so no new dependency and no compiled extension. Use it to keep iGPUs out of `generation_devices: auto` when a discrete GPU exists, and to stop budgeting them as dedicated VRAM (they share system RAM, like MPS). An unknown answer keeps the previous behaviour, an iGPU-only machine keeps its device, and an explicit device list can still opt one in.
Gives the launcher an Intel install option instead of requiring a manual pip install of the extra.
All three sites dispatched on torch.cuda.is_available() first, so a mixed NVIDIA + Arc box running on xpu reported a constant 0.0 GB and logged "CUDA Memory Allocated" -- which would make XPU bug reports unactionable.
… OOM needle XPU SDPA was measured on Arc Pro B70 / torch 2.13+xpu: peak memory doubles when sequence length doubles (2.00x across 2048-16384; 2.0 MB at seq=16384 vs 512 MB for a materialised score matrix). So XPU is in CUDA's O(area) regime, not ROCm's math-attention regime, and the existing constants are correct rather than accidental.
Was hardcoded to 'cuda' for any non-CPU noise, which is wrong on Arc. Falls back to 'cuda' when the device query has not resolved, so Nvidia metadata is unchanged.
The probe was the first statement in _should_use_fp8, so it allocated on the GPU during the first load of any model at all -- tokenizer, VAE, scheduler -- and on API/install threads it forced XPU lazy SYCL init on a thread that never generates. Moved below the exclusions.
The blind estimate (total minus this process's reserved bytes) is what made _get_vram_available over-commit on a shared GPU: it feeds a formula that assumes a driver-global figure. Sysman's zesMemoryGetState reports that figure and is often available when the SYCL ext_intel_free_memory aspect is not, so try it before estimating. Measured on Arc Pro B70 with 16 GiB held by another process: Sysman reported 15.553 GiB free, the estimate 31.725 -- a 16.172 GiB error, exactly the foreign allocation. Sysman is not a guaranteed substitute (torch's query bottoms out in the same layer), so the estimate remains as a last resort.
The storage cast happens on CPU while params are still CPU-resident, then the fp8 tensor is copied to the device and the pre-hook upcasts there. Probing all three steps on the device would pass on a build where the host->device fp8 copy or one upcast target fails, and break at forward time instead. Verified on Arc Pro B70 / torch 2.13+xpu: the full sequence works on both cards.
torch.xpu.get_device_name goes through _lazy_init, which raises AssertionError on a build without XPU. Naming is used only for labels and logs, so fall back to the device string rather than propagating.
Returning None for a device with no index would skip the driver-global query and fall through to the blind estimate with no visible symptom. Callers currently always pass a concrete device, so this is a latent hazard rather than a live bug.
Handles come back from (c_void_p * n)() as plain Python ints, and ctypes converts an undeclared int argument to a C int -- 32 bits. Any handle above 2**31 was being silently truncated; a direct test of that path segfaults. It happened to work on the B70 because the handles fit. Also: release the idle-GPU borrow if re-pinning raises (the setup was outside the try, so a failure there stranded the lock for the life of the process), and report a failing fp8 probe once per device instead of on every model load.
Setting a process-wide environment variable from a read-only query leaks into child processes. It also bought nothing: the variable only gates Sysman on runtimes predating zesInit and must be set before Level Zero initialises, which torch has already done by then. Verified on Arc Pro B70 that zesInit succeeds with the variable unset.
level_zero: cache the loader so it is opened and its prototypes configured once rather than twice, share the driver/device enumeration and its ordering guard between the two probes, and collapse the Sysman pair of globals into one nullable tuple. Also: fp8 support cache is a set (it only ever stored True), the pbr_maps empty_cache is routed through TorchDevice like the PR's other conversions, the shared-memory VRAM branch stops re-testing the device type it matched on, `_auto_generation_devices` partitions in one pass, and rand_device only answers when every generation device is the same accelerator. Merges three duplicate mem_get_info tests into one parametrized case and drops two fp8 probe tests fully subsumed by the cast-sequence test.
Intel's XPU backend matured considerably after 2.7.1: torch.xpu.mem_get_info() works on driver/kernel combinations where it previously raised, and the oneAPI user-space runtime ships with the wheel, so upgrading torch upgrades it too. Follows the rocm extra, which already pins ahead of cpu/cuda. pytorch-triton-xpu was renamed triton-xpu upstream. The darwin/aarch64 fallbacks stay on 2.7.1 to match the other extras and the project's torch<2.8.0 constraint on darwin. cpu/cuda/rocm exports are unchanged package-for-package (196/211/197); the only delta is a dropped "via pytorch-triton-xpu" comment annotation from the rename.
d2d1c40 to
8cdca1a
Compare
8cdca1a to
4c6363e
Compare
|
@lstein I've tried my best to address your concerns including igpu. §1 All four CI failures fixed — the 151-char line, events_common.py wording, and regenerated settings.json. §2 Fixed, and measured. On torch 2.7.1 mem_get_info() genuinely raises on my Arc Pro B70 — so the blind estimate was the only source, exactly as you described. Added a Level Zero Sysman tier between native and the estimate; it returned 29.04/31.72 GiB where native failed. With 16 GiB held by another process the estimate over-reports by 16.17 GiB while Sysman is correct. (0, 0) now raises rather than collapsing the budget, and the except is broad since the type moves between torch releases. attention.py:28 fixes with it. §3 Probe moved below the exclusions, targets the given device, caches per-device, no longer memoises failures, and now mirrors the runtime path (CPU cast → host→device copy → bf16/fp16 upcast). §4 Borrows restricted to the excluded device's own type. §5 Level Zero does expose it — ZE_DEVICE_PROPERTY_FLAG_INTEGRATED, read via ctypes against the loader that already ships with torch+xpu. iGPUs are dropped from auto when a discrete GPU exists, and no longer counted as dedicated VRAM. Unknown answers and iGPU-only machines keep current behaviour. I don't have the hardware to test this! §6 All done. On the VAE constants — I measured XPU SDPA scaling instead of guessing: peak memory doubles as sequence length doubles (2.00× across 2048→16384), so XPU is in CUDA's O(area) regime and those constants are right. Also bumped the extra to torch 2.13.0+xpu. Measured on the same hardware, identical code, only torch differing: SD1.5 −9%, SDXL −10%, Z-Image −16%, Wan video −33%. cpu/cuda/rocm exports are unchanged package-for-package. One open question. @fishd72 reports an Arc B390 iGPU below. My §5 change switches partial loading off there, and makes heuristic 2's cap inert. I've asked for numbers before assuming that's an improvement. Related: _calc_ram_available_to_model_cache sizes from virtual_memory().total while _get_vram_available uses .available — on shared memory those are the same pool. Worth a separate look. @fishd72 Thanks integrated-GPU report! Four things if you have a moment:
import ctypes, ctypes.util, struct, psutil, torch
lib = ctypes.CDLL(ctypes.util.find_library("ze_loader") or "libze_loader.so.1")
u32p, vpp = ctypes.POINTER(ctypes.c_uint32), ctypes.POINTER(ctypes.c_void_p)
for fn, args in (("zeInit",[ctypes.c_uint32]), ("zeDriverGet",[u32p,vpp]),
("zeDeviceGet",[ctypes.c_void_p,u32p,vpp]),
("zeDeviceGetProperties",[ctypes.c_void_p,ctypes.c_void_p])):
f = getattr(lib, fn); f.argtypes, f.restype = args, ctypes.c_int
assert lib.zeInit(0) == 0, "zeInit failed"
def enum(fn, parent=None):
n = ctypes.c_uint32(0); head = [parent] if parent is not None else []
getattr(lib, fn)(*head, ctypes.byref(n), None)
arr = (ctypes.c_void_p * n.value)()
getattr(lib, fn)(*head, ctypes.byref(n), arr)
return list(arr[:n.value])
for i, d in enumerate(dv for drv in enum("zeDriverGet") for dv in enum("zeDeviceGet", drv)):
buf = ctypes.create_string_buffer(368); struct.pack_into("I", buf, 0, 3)
lib.zeDeviceGetProperties(d, buf)
name = buf.raw[112:368].split(b"\0")[0].decode(errors="replace")
print(f"device {i}: integrated={bool(struct.unpack_from('I', buf, 28)[0] & 1)} name={name!r}")
vm = psutil.virtual_memory()
print(f"torch {torch.__version__} xpu devices {torch.xpu.device_count()}")
for i in range(torch.xpu.device_count()):
p = torch.xpu.get_device_properties(torch.device("xpu", i))
print(f" xpu:{i} total_memory {p.total_memory/2**30:.1f} GB")
print(f"RAM total {vm.total/2**30:.1f} GB available {vm.available/2**30:.1f} GB")Read-only — it just queries the driver. |
|
|
Heuristic 2 previously only applied on CUDA (total_cuda_vram_bytes); this PR extended it to XPU. On an integrated GPU that caps the RAM cache against the same RAM, which is your point exactly. Fixed by treating integrated devices as having no dedicated VRAM, so total_vram_bytes stays None — restoring the pre-PR behaviour for that device class rather than inventing a new one. Separately, and pre-existing: heuristic 1 sizes from psutil.virtual_memory().total. On @fishd72's B390 that yields a 13.44 GB cache against 13 GB actually available mid-generation — 103% of what exists. Sizing from available would give ~10 GB, but it's unstable to sample once at startup and it changes MPS too, so I've left it alone. Any other thoughts on memory management? |
|
Got round to testing Anima, Flux and Z-Image... I don't know a lot about these models but Anima Base and NovaCartoonAM work fine, I tried Anima Turbo V1-Q8 and it quickly ran out of RAM and crashed, Flux.1 schnell seems ok, and Z-image Turbo Q4 seemed ok, but Q8 would run out of RAM and crash. In case it matters, I have the following settings in my |
Summary
Native Intel XPU (torch.xpu) support for Arc / Battlemage GPUs.
Disclaimer: AI was used. However you can expect mutual respect for InvokeAI and person to person communication. I would appreciate feedback.
I've tried to keep this pr minimal. Most of the changes are for the new
xpuback-end or memory probe — CUDA (incl. ROCm), MPS, and CPU paths are unchanged.xpu/xpu:Ndevice selection, fp16 defaulttorch.xpu.mem_get_info(), with a fallback for setups missing the SYCL free-memory aspect (e.g. GPU passthrough VMs)[xpu]torch 2.13.0+Known limitations:
torch.OutOfMemoryError; the preflightmem_get_infobudgeting is the operative defense..cuda(), so enabling them is a small follow-up; until then GGUF and fp8 are the in-app quantized paths. However I'm unsure of the downstream effects of upgrading bitsandbytes on other back-ends.Related branches:
fix/fp8-dequant-bf16(fp8 checkpoint load-RAM fix),fix/flux-diffusers-vae(FLUX VAE classification fix).Related Issues / Discussions
torch.xpuroute instead, which needs no separate runtime; ONNX/OpenVINO could complement it later)QA Instructions
Verified end to end on an Intel Arc Pro B70 (torch 2.7.1+xpu, Linux):
To reproduce on Arc hardware:
pip install invokeai[xpu], leavedevice: auto, generate. On any machine:pytest tests/backend/util/test_devices.py(44 tests, no GPU required).Merge Plan
No special care needed.
uv.lockis re-resolved for the[xpu]extra (additive);openapi.json/schema.tsregenerated for thedevicefield values.WHL file for Windows or Linux
invokeai-6.14.0a0-py3-none-any.zip Updated 8/2/2026
Checklist
What's Newcopy (if doing a release after this PR)