Procyon Labs

**** COMMODORE 64 BASIC V2 ****

64K RAM SYSTEM 38911 BASIC BYTES FREE

Intel Arc Pro Xe2 on openSUSE Tumbleweed and Unsloth Studio

openSUSEopenSUSE TumbleweedIntel ArcUnsloth Studio

Abstract

I spent almost a year developing a project that made llama.cpp easy to use, and implemented some pretty wacky math to get multi-GPU balancing as close to OOM‘ing as possible (I paid for the VRAM, I’m gonna use all the VRAM). Then Unsloth released Studio. I was hesitant to try it, because so much crap is being made that I’ve just had vibe-slop-overload.

But then I tried it. Damn! It’s does everything, and well. So I set mine on fire, and decided to let someone else maintain a complicated pile of bits.

I chose openSUSE Tumbleweed because I have strong feelings for openSUSE, and Leap‘s kernel is too old for these GPUs.

My system has two Intel Arc B60 GPUs (I like a challenge) – but this guide should apply to any in the Battlemage (Xe2) architecture.

NOTE: There is a quirk that I call the Intel ECC VRAM Robbery Scheme. Basically, Intel skims a little VRAM off the top to pay for ECC operations. ECC is great for lots of things, and other vendors handle it better. See the end of this guide for a workaround.

Be advised that I jump around logically. These guides are my progress notes, and I often have to go back and re-write whole sections as a result of failures or sub-optimal results. Pick and choose what matters to you, but I recommend you go the intellectually curious route and play the whole game <3

Install and Ready the Operating Environment

Do a “Server” installation of openSUSE Tumbleweed. Pretty simple. From there, the adventure begins.

First, we need to verify that the GPU(s) are properly detected:

Install the Native Intel Level Zero Compute Driver

You’ll also need to give your user GPU access. This one keeps biting me, because I usually just YOLO as root:

Don’t forget to logout and back in for this to take affect. Then this command should work for you:

Intel oneAPI DPC++ Library (oneDPL) / SYCL

Right now, only the OpenCL side of the compute-runtime stack is working. Now for the fun stuff.

On the Linux side, Intel seems to only love Ubuntu – which is odd, because Ubuntu is Debian made shitty. But I digress…

We’re going to be doing some manual labor here, but we like that! First, the prerequisites:

Next add Intel’s oneAPI repo (separate from Tumbleweed’s own repos… this is Intel’s official YUM-style repo, which works fine with zypper):

Now, install and activate the DPC++/C++ compiler + SYCL runtime. This installs to /opt/intel/oneapi, fully separate from the libze_intel_gpu1/level-zero packages from Tumbleweed’s repo we already have. No conflicts, they weirdly coexist fine. Note that we need to source the environment as well. This feels hacky, because it is:

Tip: If you don’t want to source that setvars.sh every time you want to work with those tools, run this to modify your PATH in .bashrc:

Trust… but verify:

Vulkan

Easy:

Verify:

NOTE: Mesa’s Vulkan loader walks every DRM node it finds, including card0 (the software bochs-drm framebuffer on my Proxmox VE hypervisor). That device has no render node and can’t back a Vulkan physical device, so Mesa logs the warning and moves on. It’s noise from the virtual console, not from the passed-through GPUs. Pay no mind.

Unsloth Studio

By default, the installation script for Unsloth Studio checks macOS (eww), then nvidia-smi ($$$), then AMD ROCm (rocminfo/amd-smi) – and that’s it. There is no Intel/XPU probe anywhere in the auto-detect path (the installer’s “gpu: none (CPU-only)” notice is a disgusting lie. Let’s get out the duct tape.

Running the installer with an explicit override resolves to PyTorch’s official XPU index. It’ll also pull in an XPU-enabled bitsandbytes (≥0.50.0, needed for 4-bit QLoRA on Intel) as a separate step gated on the same XPU index detection.

Remember! Before you install or update Unsloth Studio, also define “export UNSLOTH_TORCH_INDEX_FAMILY=xpu” first – otherwise, no XPU.

Oooooh, it worked!

Because we want to access this instance from other workstations, launch it manually with LAN access allowed. What you do from there is your business. Just know I’m scanning the Internet constantly \m/

Go change the world with your new toy:

Note: It will always display your WAN’s IP address, when it really means your host’s IP address. Odd it does’t make that clearer.

Updating Unsloth Studio

Because of the xpu UNSLOTH_TORCH_INDEX_FAMILY hack, the normal update method for Unsloth Studio will not work with this setup. So instead of blindly running the suggested command it gives you in the web UI, just make a local script you can run that defines that variable before running the update:

SYCL vs Vulkan

Here’s some knowledge for you – because after logging into Unsloth Studio, you probably saw this and thought “WTF? We setup SYCL and only Vulkan is available!”:

Well, two distinct GPU-acceleration paths exist inside Unsloth Studio, and each uses a different backend for Intel.

Training / Fine-Tuning (LoRA, QLoRA, etc.)

Runs through PyTorch, which uses the XPU (SYCL) backend over Level Zero. This is the layer we setup (xe kernel driver → Level Zero → oneAPI-style +xpu PyTorch wheel)

Chat / GGUF Inference

Runs through a bundled llama.cpp prebuilt binary, entirely separate code from PyTorch. install_llama_prebuilt.py has zero references to SYCL anywhere. For any non-NVIDIA/non-AMD GPU, the logic is explicit:

log(f"Intel GPU detected -- using upstream Vulkan prebuilt {vulkan_name}")

Unsloth only ships/downloads prebuilt llama.cpp binaries for CUDA, ROCm/HIP, Vulkan, and CPU. There is no SYCL-flavored llama.cpp prebuilt in their release assets, so Intel GPUs always fall through to the Vulkan build for this component — regardless of what PyTorch is doing.

llama.cpp does have an upstream SYCL build target, but Unsloth doesn’t build/ship it as a prebuilt. Vulkan is their catch-all for “any other GPU,” which happens to work fine on Arc/Xe2 hardware since Intel has Vulkan compute support.

Training will use SYCL/XPU via PyTorch, and GGUF chat/inference will use Vulkan via llama.cpp. If you specifically want SYCL-accelerated llama.cpp instead of Vulkan (I’ve found it to be more performant – mostly), that would mean building llama.cpp yourself with -DGGML_SYCL=ON against the oneAPI
toolkit we already installed, replacing Studio’s prebuilt. Pain in the ass, but possible nonetheless.

Intel ECC VRAM Robbery Scheme – Work Around

As I mentioned at the top of this guide, we have a quirk with the VRAM on these cards. In my testing, the B60 went from a spec-sheet “24GB” to Memory Physical Size: 24480.00 MiB usable once I disabled ECC – meaning with ECC on by default, roughly 4GB of that 24GB was gone before I ever loaded a model. Nobody’s published that number that I could find (this hardware is too new), so take it as my measurement, not a documented spec – but a 16.7% cut is a lot, especially compared to Nvidia’s own GDDR6X cards: RTX 4090 owners lose 24GB → 22.5GB (a 6.25% cut) when its on-die ECC-like mode gets switched on (https://dev.to/maximsaplin/4090-ecc-on-vs-ecc-off-36m4). If Intel’s real-world number holds up as more people use these cards, that’s more than double Nvidia’s overhead for the same underlying memory technology. My guess is Intel’s parity/reservation scheme is comparatively wasteful, or they’re reserving more than strict parity bits alone – but that’s speculation on my part.

GDDR6 has no dedicated sideband channel for this. There’s no extra physical bit lane reserved for parity – GDDR6 was designed as a cost-optimized, high-clock commodity memory standard, not a check-bit-aware one (GDDR6 vs HBM (https://www.exxactcorp.com/blog/hpc/gddr6-vs-hbm-gpu-memory)). So any vendor implementing ECC over GDDR6 has to do it the only way available: carve parity storage out of the same visible memory pool the GPU already has. Nvidia eats that cost on the 4090 as shown above; I’d expect AMD’s GDDR6 workstation cards (Radeon Pro W-series) to take a similar hit when ECC is on, though I don’t have a verified number for those to point to, so don’t quote me on AMD specifically. HBM2 (and HBM2E) sidesteps the whole problem with a dedicated sideband ECC path – extra pins/bits alongside the main data bus, purpose-built for parity, instead of stealing from the same pool your models have to live in. That’s one reason I still like my AMD Instinct MI100 and Nvidia Tesla V100 GPUs better, despite being older.

The Fix

Intel’s xpu-smi (from the XPU Manager project) exposes a firmware-level ECC toggle via IGSC (Intel Graphics System Controller): xpu-smi config -d –memoryecc 0. It’s not in Tumbleweed’s repos, and Intel currently only ships .deb builds in their GitHub releases (no RPM despite their docs still describing SLES/RHEL install). The .deb is just one binary + a resources folder, and its only two missing runtime deps (libigsc.so.1, libhwloc.so.15) are both available straight from Tumbleweed’s own OSS repo – everything else it needs (libze_loader, libpciaccess, libstdc++, libz) we already have.

ECC is only really important for activities like training. Inference can be a little more relaxed, that’s why we’re doing this.

Install the two missing runtime libs:

Grab and extract the xpu-smi .deb (no RPM exists, so we’ll lift the binary out manually):

Note: Installed under /usr/local, not /usr – it’s an unmanaged file drop, not a zypper tracked package, so keep it out of /usr proper.

Confirm xpu-smi works, and then disable ECC on both GPUs (device IDs from the discovery output -almost certainly 0 and 1):

Note: openSUSE’s default /etc/sudoers sets secure_path="/usr/sbin:/usr/bin:/sbin:/bin", which deliberately excludes /usr/local/bin (unlike Debian, which include it by default). Your normal shell’s PATH finds the binary fine, but sudo ignores the PATH entirely and only searches its own hardcoded secure_path.

Quick fix: Just use the full path:
sudo /usr/local/bin/xpu-smi discovery
sudo /usr/local/bin/xpu-smi config -d 0 --memoryecc 0
sudo /usr/local/bin/xpu-smi config -d 1 --memoryecc 0

Permanent fix (adds /usr/local/bin to sudo’s search path so bare sudo xpu-smi ... works going forward):
echo 'Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' | sudo tee /etc/sudoers.d/local-path
sudo chmod 0440 /etc/sudoers.d/local-path
sudo visudo -c

visudo -c checks syntax across all sudoers files/drop-ins. If it doesn’t report an error, the change is safe and live immediately. A malformed sudoers file can lock out sudo entirely.

If this is a bare metal system, a reboot works in this case. This is a firmware/VBIOS-level setting via IGSC, it won’t take effect live.

However, if you’re like me, and this system is a VM with PCIe passthrough on a Proxmox VE hypervisor, you need to kick the cards hard from the Proxmox host itself (my PVE is named tethys, and the VM is, if you haven’t already guessed, SAL9000).

A host-level sysfs reset (SBR/FLR) is not sufficient. A qm stop/qm start VM-level cycle is also not sufficient. What did work: shutdown -h now on the PVE host followed by powering it back on (a soft ACPI poweroff/poweron cycle). A plain warm reboot of the host was not tested, so it’s unknown whether that alone would suffice.

I used this as an opportunity to finally update my PVE node. Because honestly, how often do we want to shutdown all the VMs and reboot this thing?

Then verify (confirm the ECC field now reads disabled/0 on both devices) and revel in your success:

Mmmm… tasty but more error-prone VRAM!

But What About SYCL?

As I mentioned earlier, Unsloth Studio doesn’t ship a version of llama.cpp with SYCL enabled. So we’ll do that manually, and point it to the binary we create.

Now restart Unsloth Studio and navigate to Settings -> System -> GGUF inference engine. In the “Custom llama.cpp folder” box, type in the location of your newly created llama.cpp binary (folder navigation doesn’t work). This is my example:

Now, restart again.

NOTE: If you try loading a model now and get Failed to load model: llama-server could not start: the system library libsvml.so is missing, it’s not actually missing… it’s an environment problem. Studio launches llama-server by copying its own process environment wholesale, so if whatever terminal/session Studio itself is running in never sourced the oneAPI env (the .bashrc block from earlier only fires for interactive shells – it won’t help a systemd unit, cron job, or a tmux/screen session that predates that edit), the child process can’t find libsvml.so and friends under /opt/intel/oneapi/compiler/2026.1/lib.

Quick check before you launch Studio:
echo $LD_LIBRARY_PATH
If that’s empty, stop Studio, open a fresh terminal (or source ~/.bashrc), confirm the path shows up, then relaunch:
unsloth studio stop
echo $LD_LIBRARY_PATH
unsloth studio -p 8888 -H 0.0.0.0

Wait, Why Is “Compute Backend” Empty Now?

So you pointed Studio at your shiny custom SYCL binary, GPUs light up, inference actually runs on the cards, and then you open Settings and the “Compute backend” dropdown is just… nothing. Empty. Not even a CPU option.

Turns out this dropdown was never for us. I went digging through Studio’s backend code (routes/llama.py) and found the whole picker is hardcoded to exactly four values: cpu, cuda, rocm, vulkan. Not a sycl in sight. That’s because this thing isn’t “pick which backend runs my inference” – it’s “pick which of Unsloth’s own prebuilt downloads to install,” and Unsloth doesn’t build a SYCL prebuilt (we covered why earlier – Vulkan’s their catch-all for anything that isn’t Nvidia or AMD).

The instant you set a Custom llama.cpp folder, Studio quietly reclassifies your install as a local_link – their term for “the user pointed me at their own build, I don’t manage this anymore” (llama_cpp_update.py, if you want to go read it yourself). And a local_link install makes the backend-switcher shut itself off on purpose: no options, nothing to switch, because it genuinely has no idea what’s living in your directory and isn’t going to pretend otherwise.

So an empty dropdown here isn’t broken. It’s Studio politely getting out of your way. The only check that actually matters is xpu-smi stats -d 0 lighting up during inference. If that’s happening, you’re done and can ignore the dropdown forever.

Stats

So is it worth it to get SYCL working instead of Vulkan for these GPUs?

Yes. On my system, at least.

Qwen3.8-27B-GGUF UD-Q6_K_XL

  • Vulkan averaged 11.2 tok/s
  • SYCL averaged 19.4 tok/s

Still slow, but not as painful.

Things I Broke So You Don’t Have To (Landmines & Guardrails)

Everything above gets you a working SYCL build wired into Studio. This section is what I learned keeping it alive afterward – the parts where Studio’s SYCL support runs out of road, and the actual working config on the other side of an evening of crashes.

The Auto-Fit Blind Spot

You’ll notice GPU Layers is locked to Auto in the model load settings (you can’t type a number in). This is the same root cause as the empty Compute Backend dropdown earlier: Studio can only compute an automatic VRAM “fit” for CUDA/ROCm/Vulkan (there’s a dedicated free-memory probe for Vulkan in its code; there’s no SYCL equivalent). Without knowing your GPUs’ free memory, it can’t confirm a fit. So it won’t let you touch manual placement, and instead hands the model to llama.cpp’s own --fit on with no explicit device pin, which decided to run this dense a model entirely on CPU rather than guess.

There’s a separate free-text field for extra/raw arguments in the load settings, independent of the locked GPU Layers control. Whatever you put there gets appended after Studio’s own flags, so it wins (llama.cpp is last-flag-wins). This is what forced GPU offload in my scenario:

-ngl -1 --fit off --device SYCL0,SYCL1

The OOM I Walked Into

Forcing full offload this way removes a safety net you don’t see until it’s gone: Studio’s --fit logic normally keeps some layers on CPU host RAM when a config wouldn’t fit in VRAM (a “tensor spill” fallback). --fit off disables that entirely… it’s all-GPU-or-crash with no graceful degradation.

My first real config – full 262144 context, --parallel 4, --kv-unified (one shared KV cache across all 4 slots), full forced offload – ran fine for a while and then died 14 minutes into generation:

level_zero backend failed with error: 39 (UR_RESULT_ERROR_OUT_OF_DEVICE_MEMORY)

The KV cache had grown past what two 24GB cards could hold alongside the model weights. Retrying with the same settings later died even earlier (during model load this time), allocating the KV cache before generation even started.

Cutting context in half and dropping parallel slots to 1 fixed it for good:

-c 131072 --parallel 1

(no --kv-unified needed once there’s only one slot). That survived a 48K-token prompt and 3600+ generated tokens at a steady 15-16 tok/s, with --spec-type draft-mtp --spec-draft-n-max 3 (MTP speculative decoding) layered on top without issue – worth adding once your base config is already stable, not before, since speculative decoding needs its own slice of VRAM too.

Guardrails for Your Own Tuning

  • A clean model load proves nothing. My “fixed” config loaded fine and then OOM’d 14 minutes into real generation. Test with an actually long generation (2000+ tokens) before trusting a config change.
  • Watch VRAM headroom while it runs, not just at startup:
    sudo xpu-smi stats -d 0
    sudo xpu-smi stats -d 1

    GPU Memory Used climbing steadily toward the 24GB ceiling as generation continues is your early warning, before it becomes a crash.
  • Start conservative, then push. --parallel 1, no --kv-unified, and a context size well under your theoretical max is a safer baseline to tune up from than starting maxed out and cutting back after a crash. I’m the only user, so messing with --parallel >1 is of no benefit anyway (unless we’re discussing rare and unusual usage patterns).

Helpful Links

,

Leave a Reply

Your email address will not be published. Required fields are marked *