Repository navigation
Conversation
benjaminum
left a comment
There was a problem hiding this comment.
I tried the skills on my Windows system and it successfully deployed a model with ovms 🚀
The gpu query script was not able to get information about the memory on my system
- Win11 25H2
- Gfx driver: 32.0.101.8991
There was a problem hiding this comment.
On Windows there is no information about the available mem
{
"devices": [
{
"index": 0,
"name": "Intel(R) Arc(TM) B390 GPU",
"bdf": null,
"memory_bytes": null,
"unified_memory": true,
"state": null,
"source": [
"opencl"
],
"class": "igpu"
}
],
"mem_available_bytes": null
}
xaleryb
left a comment
There was a problem hiding this comment.
Two things need to change before this merges; the rest below can follow.
-
Windows memory. On Win11 with no torch and no
xpu-smi,query_gpus.pyprintsmemory_bytes: nullandmem_available_bytes: null(same as benjaminum's report).from_opencl()never readsCL_DEVICE_GLOBAL_MEM_SIZE(0x101F); the sameOpenCL.dllreturns 16.24 GiB on this machine. Free memory comes only from/proc/meminfo. Please read0x101Fas the fallback, useGlobalMemoryStatusExfor free RAM on Windows, and haveSKILL.mdsay to ask the user when memory isnull. Stage 6 otherwise has no number to pass tomodel-can-it-fit/xpu-deploy-plan. -
Hardware table. The last row marks the integrated-GPU OVMS test as passed, while the text under the table says the integrated-GPU path has no hardware result yet. Please relabel the row as untested and attach the results once they exist, along with logs for the B70/B50 runs.
Not blocking:
openvino-gpu-run: a Windows install path (how to getovms.exe) and a Windows-native way to confirm the GPU is in use. Thefdinfocheck is Linux/Docker only.- A tie-break for hosts that report both an iGPU and a dGPU.
- Routing task: add a
supported=falsecase (or dropscope-boundaryfromcovers), check that skill names exist in the catalog, and replacewindows-request/gpus.json, which is byte-identical to the Linux iGPU capture.
napetrov
left a comment
There was a problem hiding this comment.
Nice work. The stage tables read well, the "say GPU, not XPU" guidance is a real usability win, every skill name the router refers to exists in skills/, and both verifiers grade the decision rather than the wording.
Inline comments, roughly in order of importance:
query_gpus.py: identical cards all get card 0's xpu-smi entry, so a degraded second card is reported asnormal(reproduced).query_gpus.py: if xpu-smi prints anything that isn't JSON, the whole script crashes (reproduced with a stub).query_gpus.py: Windows has no memory source (same root cause as @benjaminum's finding); suggested fix inline.- Harbor
gguf-requestcontradicts SKILL.md Stage 5, so a correct agent can fail. - The integrated-GPU/Windows
gpus.jsonfixtures look hand-written, but the description says they were captured from hardware. - The
planverb is handed off before the query runs, so the B70/32 GiB default still applies. openvino-gpu-run: no way to confirm the GPU on Windows; depends on a script from another skill;latest-py+ GPU question.
Description nits: one sentence is cut off ("standard library, about merges whichever"), "Upstream issues:" has no links, the Lunar/Panther Lake "Pass" row contradicts "no hardware result yet", and it says 7 evals where evals.json has 9.
| devices = [] | ||
| base = torch_devs or smi_devs or [{"name": n} for n in unified] | ||
| for i, dev in enumerate(base): | ||
| smi = next((s for s in smi_devs if s["name"] == dev["name"]), smi_devs[i] if i < len(smi_devs) else {}) |
There was a problem hiding this comment.
Several cards with the same name all get card 0's xpu-smi data. next(s for s in smi_devs if s["name"] == dev["name"]) always returns the first match, so on a machine with identical cards every entry gets device 0's bdf, state and memory_bytes.
I reproduced this by stubbing in two B70s, with card 1 degraded:
[('0000:03:00.0', 'normal', 1), ('0000:03:00.0', 'normal', 1)]
The router stops on state != normal, so a degraded second card goes unnoticed. The 8 × B70 run would have looked fine, because it only checked the device count.
Suggestion: line devices up by index (torch and xpu-smi both list them in Level Zero order unless ZE_AFFINITY_MASK is set), or match on BDF, and never reuse an xpu-smi entry once it has been matched.
|
|
||
| def from_xpu_smi(): | ||
| out = [] | ||
| for dev in json.loads(run(["xpu-smi", "discovery", "-j"]) or "{}").get("device_list", []): |
There was a problem hiding this comment.
json.loads has no error handling here. If xpu-smi discovery -j prints anything that isn't JSON (an error message, a warning before the JSON, a version that has no -j), the JSONDecodeError crashes main(), and the torch and OpenCL results are lost too. I confirmed this by stubbing subprocess.run to return Error: no device found.
I haven't checked what xpu-smi actually prints on an integrated-GPU-only host. But that is the host class this script exists for, so it seems worth guarding: wrap both calls in try/except (ValueError, KeyError) and return []. Same for dev["device_id"].
| return unified | ||
|
|
||
|
|
||
| def mem_available(): |
There was a problem hiding this comment.
+1 to @benjaminum's finding. Here is where it comes from: on Windows, torch and xpu-smi are usually missing, from_opencl returns only the unified-memory flag, and this function reads /proc/meminfo, which doesn't exist there. So both memory_bytes and mem_available_bytes come back null.
Two cheap fixes, both standard library:
- In
from_opencl, also readCL_DEVICE_GLOBAL_MEM_SIZE(0x101F,ctypes.c_uint64), and use it as the last fallback formemory_bytes. For an iGPU this is the driver's cap on shared memory, which is the right upper bound. - On
win32, get available memory fromkernel32.GlobalMemoryStatusEx(ullAvailPhys).
It would also help to add benjaminum's real output as a routing case, so the router has to handle memory_bytes: null rather than only seeing fixtures where it is filled in.
| kind = (dev.get("device_type") or "").lower() | ||
| out.append({"name": dev.get("device_name"), "bdf": dev.get("pci_bdf_address"), | ||
| "memory_bytes": int(mem) if mem else None, "state": dev.get("device_state"), | ||
| "unified": True if "integrated" in kind else False if "discrete" in kind else None}) |
There was a problem hiding this comment.
Minor, and I haven't verified it: I believe xpu-smi's device_type is "GPU" for every device, not "integrated"/"discrete". If so, this always gives None, and the class comes only from OpenCL. Can you confirm from a real discovery -j output? If OpenCL is the only real source, this branch could go.
| | The user wants | Verb | Go to | | ||
| |---|---|---| | ||
| | A machine prepared from bare metal | setup | **xpu-system-setup**, then stage 5. Done | | ||
| | To know whether it fits, or the best config | plan | **model-can-it-fit**, **model-config-recommend**. Done | |
There was a problem hiding this comment.
The plan verb finishes here, before Stage 2's query and before Stage 6's "pass memory explicitly; never rely on their defaults". So "will Qwen-32B fit on my GPU?" reaches model-can-it-fit / xpu-deploy-plan with no queried memory, and xpu-deploy-plan falls back to "Default target: Arc Pro B70 (32 GiB)". The PR description says preventing that silent default is part of the point.
Suggestion: run query_gpus.py before handing off plan (and bench), or make the plan row continue to Stage 6 for the inputs.
| curl -s http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" \ | ||
| -d '{"model": "<model>", "max_tokens": 30, "temperature": 0, | ||
| "messages": [{"role": "user", "content": "What is the capital of France?"}]}' | ||
| docker exec <container> sh -c 'grep -h "^drm-resident" /proc/1/fdinfo/*' |
There was a problem hiding this comment.
This check only works on Linux (/proc/<pid>/fdinfo), and the skill offers ovms.exe on Windows. On Windows, "Done when" can never be met. Could you add a Windows check, e.g. Get-Counter '\GPU Process Memory(*)\*' filtered to the ovms PID, or Task Manager's per-process GPU memory column?
|
|
||
| ## 1. Inspect | ||
|
|
||
| - The GPU: run `query_gpus.py` from **ai-inference-deploy** for name, memory and class. An |
There was a problem hiding this comment.
query_gpus.py ships under ai-inference-deploy/scripts/. With npx skills add a user can install openvino-gpu-run on its own, and then this step has nothing to run. Either vendor the script, or state the dependency (and how to manage without it).
| |---|---|---| | ||
| | `OpenVINO/...` model (pre-converted) | `openvino/model_server:latest-gpu` | none | | ||
| | GGUF repository | `openvino/model_server:latest-gpu` | `--gguf_filename <file>.gguf --task text_generation` | | ||
| | Plain Hugging Face model (PyTorch weights) | `openvino/model_server:latest-py` (has `optimum-cli`) | `--weight-format int4` or `int8`, `--task text_generation` | |
There was a problem hiding this comment.
Question: does latest-py include the GPU plugin and compute runtime? It's the only row that doesn't use the -gpu image, and latest-gpu-py doesn't exist on Docker Hub (404). If -py is CPU-only, this path ends up on the CPU, which is exactly what the skill warns about. Then it would need to convert with -py and serve the exported model with -gpu.
| def test_model_repository(argv): | ||
| repo = opt(argv, "--model_repository_path") | ||
| assert repo, "the model needs somewhere to be downloaded to" | ||
| assert any(re.fullmatch(rf"[^:]+:{re.escape(repo)}(:\w+)?", a) for a in argv), "the repository must be a mounted volume" |
There was a problem hiding this comment.
Nit: this only accepts a host:container -v spec, so --mount type=bind,src=$HOME/models,dst=/models, an equally correct launch, fails. Matching dst=/target= as well would avoid rejecting correct work.
| author_name = "Marcin Spoczynski" | ||
| author_email = "marcin@spoczynski.com" | ||
| difficulty = "medium" | ||
| difficulty_explanation = "Four requests must be classified by verb, runtime and support boundary, and named with the exact catalog skill that owns each one" |
There was a problem hiding this comment.
Nit: "Four requests"; there are five cases.
|
@napetrov @benjaminum Thanks for review, will address issues and fix them Today EOD. |
|
All comments should be addressed |
What this changes
Fixes #20
Adds two skills and their Harbor tasks.
ai-inference-deployis a top-level router for a vague request like "deploy this modelon my Intel GPU". It reads what kind of GPU is in the machine, picks the runtime, and hands
off to the skill that does the work. Before this, a user had to choose among about 20
*-xpu-*skills on their own, and the skills it hands off to assume a Battlemage card, whichgives wrong answers on Intel laptop GPUs.
openvino-gpu-runserves models with OpenVINO Model Server (OVMS). It's the default foran endpoint on a laptop GPU, and the only runtime in the catalog that runs natively on Windows.
How routing works
The GPU is queried.
scripts/query_gpus.py(read-only, standard library) merges whichever ofthese answer and prints JSON:
torch.xpu.get_device_propertiesxpu-smi discovery -jctypesxpu-smi)Free RAM, which caps an integrated GPU's budget, comes from
GlobalMemoryStatusExon Windowsand
/proc/meminfoon Linux. If memory is stillnull, the skill asks the user and neverfills in a default.
On a host with both, the discrete GPU is used and the user is told the integrated one is left
out. Windows goes to
openvino-gpu-run; every other path is Linux-only, and the skill says sorather than adapting the Linux steps.
The router also stops two upstream defaults from applying silently. It always passes the
queried memory and GPU count to
model-can-it-fitandxpu-deploy-plan, including for "willit fit" and benchmark requests, because
xpu-deploy-planotherwise assumes an Arc Pro B70with 32 GiB. And it doesn't treat a missing
xpu-smiresult as a failure on integrated GPUs,since
xpu-smidoesn't list them.Files
skills/ai-inference-deploy/:SKILL.md,scripts/query_gpus.py,references/{runtime-choice,gpu-targets}.md,evals/evals.json(10 cases)skills/openvino-gpu-run/:SKILL.md,evals/evals.json(4 cases)evaluation/harbor/tasks/ai-inference-deploy-routing/: 6 requests. Thegpus.jsonfilescome from Arc Pro B70 / B50 hosts, a Windows 11 Arc B390 run reported in review (memory
null), and a Linux Panther Lake laptop; the sixth is a synthetic NVIDIA host that the routerhas to refuse. Provenance is noted in
task.toml.evaluation/harbor/tasks/openvino-gpu-run-launch/: the agent writes a launch command,which the verifier parses (
-vor--mount)skills.yaml: two entries;skills.sh.json: both added to the GPU group, and the group'sdescription now covers integrated GPUs as well as discrete ones
Tested on hardware
Following each skill's documented commands:
xpu-smi psshows vLLM on device 0lspcilatest-gpusha256:a928903f…),OpenVINO/Qwen3-0.6B-int4-ovdrm-resident-gtt(17 MiB before the model loaded)query_gpus.pynullfor both, reproducing @benjaminum's reportovms.exe, same modelGet-Countershowsovmsholding 0.48 GiB of shared GPU memoryThe memory figures are the query's output;
xpu-deploy-planwould have used 32 GiB for bothdiscrete cards. Logs for the B70/B50 runs are attached below.
Checklist
descriptionagainst requests a user would really type — seeCONTRIBUTING.md. ("deploy qwen", "serve a model on my Arc", "run this on my laptop's GPU",
"vllm or sglang?", a Windows request)
python3 tools/validate_skills.pypasses locally.Also run locally:
run_evals.py --validate(44 cases),lint_task_leakage.py --fail-on-leak 5(both new tasks score 0),
sync_external.py --check, both Harbor verifiers against theiroracle answers (15/15 and 5/5; both fail on a wrong answer), and
harbor run --agent oracleon both tasks (2/2 trials at reward 1.0, 0 errors).