Skip to content

feat: Windows support #9

Description

@offbyonebit

Windows support is doable because the core runtime is just launching llama-server with SYCL/Level-Zero env vars, and Intel oneAPI does run on Windows. The main work is removing the Linux-only assumptions.

Concrete plan mapped to the current codebase:

1. Config / data paths (src/arc_llama/config.py)

Today it hardcodes XDG paths (~/.config, ~/.local/share, ~/.local/state). On Windows these should map to %LOCALAPPDATA%. Use the platformdirs dependency:

from platformdirs import user_config_dir, user_data_dir, user_state_dir

This is cleaner than manual sys.platform checks and handles edge cases automatically.

2. GPU detection (src/arc_llama/detect.py)

_scan_pci() reads /sys/bus/pci/devices, which does not exist on Windows.

Add a Windows branch that enumerates GPUs via WMI/PowerShell:

Get-WmiObject Win32_VideoController | Select-Object Name,AdapterRAM,PNPDeviceID

Parse PNPDeviceID for VEN_8086&DEV_xxxx, extract the device ID, and look it up in arch.py. AdapterRAM gives a VRAM hint (may report shared memory on iGPUs). sycl-ls and clinfo also work on Windows so _enrich_with_clinfo() can stay as-is.

3. Subprocess lifecycle (src/arc_llama/launcher.py)

Biggest blocker. Currently uses:

  • preexec_fn=_preexec_isolate_and_pdeathsig
  • os.setsid()
  • libc.prctl(PR_SET_PDEATHSIG)
  • os.killpg(proc.pid, signal.SIGTERM/SIGKILL)

All POSIX-only. Refactor:

  • POSIX: keep existing behavior
  • Windows: use subprocess.Popen(..., creationflags=subprocess.CREATE_NEW_PROCESS_GROUP) and stop with proc.terminate() then proc.kill()
  • Drop preexec_fn entirely on Windows

For robust child cleanup equivalent to PR_SET_PDEATHSIG, use a job object:

import ctypes
kernel32 = ctypes.windll.kernel32
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000

def _configure_job_object():
    hJob = kernel32.CreateJobObjectW(None, None)
    info = JOBOBJECT_EXTENDED_LIMIT_INFORMATION()
    info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
    kernel32.SetInformationJobObject(hJob, 9, ctypes.byref(info), ctypes.sizeof(info))
    kernel32.AssignProcessToJobObject(hJob, kernel32.GetCurrentProcess())
    return hJob

4. CLI / doctor (src/arc_llama/cli.py)

  • os.uname().release → use platform.uname().release
  • /sys/module/xe, /sys/module/i915 → skip on Windows
  • lspci, id -nG, render/video groups → not applicable on Windows
  • /opt/intel/oneapi/setvars.sh → check Windows default (C:\Program Files (x86)\Intel\oneAPI\setvars.bat or setvars.ps1)
  • systemd command → no-op on Windows with a Task Scheduler hint
  • _resolve_llama_server() hardcoded Unix paths → skip on Windows, look for llama-server.exe

5. Packaging (pyproject.toml)

Change classifier from "Operating System :: POSIX :: Linux" to "Operating System :: OS Independent" (or add both Linux and Windows classifiers).

6. Tests

Several tests mock /sys/bus/pci/devices and assume POSIX. Add pytest.mark.skipif(sys.platform == "win32", ...) where needed or add equivalent Windows mocks.

7. Docs / UX

  • Remove "Linux only" language from README
  • Document that Windows users need a SYCL build of llama-server.exe and should run from an Intel oneAPI command prompt (or after running setvars.bat)
  • Replace systemd instructions with a note about Task Scheduler or manual arc-llama serve

Suggested order of attack

  1. config.py — platform-aware paths via platformdirs (low risk)
  2. launcher.py — guard POSIX-only code, add Windows process group handling
  3. detect.py — add WMI/PowerShell GPU scan
  4. cli.py — make doctor and systemd platform-aware
  5. pyproject.toml + README.md
  6. Run test suite on Windows, fix stragglers

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions