diff --git a/.github/ISSUE_TEMPLATE/hardware-validation.yml b/.github/ISSUE_TEMPLATE/hardware-validation.yml new file mode 100644 index 0000000..3e725bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/hardware-validation.yml @@ -0,0 +1,69 @@ +name: Hardware validation report +description: Report native Intel GPU results and the checks still needed. +title: "[Hardware] GPU / OS / backend" +labels: ["help wanted", "untested"] +body: + - type: markdown + attributes: + value: | + Help record which Intel GPU environments have been tested. Include failures as well as successes, and mark checks you have not performed as "not run". + See the [validation checklist](https://github.com/offbyonebit/arc-llama/issues/71). Remove tokens and private details before attaching logs. + - type: input + id: hardware + attributes: + label: GPU and memory + description: Exact GPU model, dedicated/shared memory, and number of GPUs. + placeholder: Arc A770 16 GiB, one GPU + validations: + required: true + - type: textarea + id: environment + attributes: + label: Software environment + description: OS/build, kernel (Linux), Intel graphics driver, Arc Llama version, Python version, llama.cpp runtime build/tag, and oneAPI version if using SYCL. + validations: + required: true + - type: dropdown + id: backend + attributes: + label: Backend tested + options: + - Vulkan + - SYCL + - Both (report results separately) + - Not run + validations: + required: true + - type: textarea + id: diagnostics + attributes: + label: Diagnostic output + description: Paste output from arc-llama --version, arc-llama gpus, and arc-llama doctor. These commands do not run inference. State any command you did not run. + render: text + validations: + required: true + - type: textarea + id: configuration + attributes: + label: Model and configuration + description: Model repository/file, quantization, recipe, context size, GPU selection/offload, and any changed settings. If inference was not run, say so. + validations: + required: true + - type: textarea + id: results + attributes: + label: Observed results + description: Record cold start, model switching, streaming, non-streaming, and shutdown as pass/fail/not run. Include symptoms, timing, or throughput if measured. For multiple backends, distinguish each result. + value: | + Cold start: + Model switching: + Streaming: + Non-streaming: + Shutdown: + validations: + required: true + - type: textarea + id: logs + attributes: + label: Logs and remaining problems + description: Relevant errors, reproducible steps, or logs. Include remaining checks and unexpected GPU/memory behavior. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d29e423..f1e0461 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: - name: Test with pytest # Print every skip reason so an environment change cannot hide missing # coverage in an otherwise green job. - run: pytest tests/ -q -ra --tb=short -p no:cacheprovider + run: pytest tests/ -W error -q -ra --tb=short -p no:cacheprovider browser-tests: # Real-browser regression suite for the bundled web UI. Kept separate diff --git a/README.md b/README.md index 187fdc3..98a598e 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,23 @@ curl http://127.0.0.1:11437/v1/chat/completions \ }' ``` +## Tested hardware + +The recorded native validation below used the **0.9.0rc2 candidate** and the +models and recipes in the linked report. It does not establish validation for +every driver, model, or later release. + +| GPU | Platform | Backend | Recorded validation | +| --- | --- | --- | --- | +| Arc Pro B60 (24 GiB) | Linux | Vulkan and SYCL | [Native release validation](docs/release-validation-linux-2026-10-03.md) | +| Alchemist (A-series) | Linux / Windows | Vulkan / SYCL | Hardware reports [requested](https://github.com/offbyonebit/arc-llama/issues/71) | +| Lunar Lake | Linux / Windows | Vulkan / SYCL | Hardware reports [requested](https://github.com/offbyonebit/arc-llama/issues/71) | +| Arc Pro B60 | Windows | Vulkan / SYCL | Native Windows validation [requested](https://github.com/offbyonebit/arc-llama/issues/69) | + +Have one of these devices? [Submit a hardware validation report](https://github.com/offbyonebit/arc-llama/issues/new?template=hardware-validation.yml) +with your environment, configuration, and observed results. Reports can include +checks you have not run; mark them as “not run” so the remaining coverage is clear. + ## Requirements ### Linux diff --git a/pyproject.toml b/pyproject.toml index 206954e..ef61ce2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ mcp = [ "mcp>=1.0,<2", ] dev = [ + "httpx2>=2.13.1", "pytest>=8.0", "pytest-asyncio>=0.23", "ruff>=0.5", diff --git a/tests/test_launcher.py b/tests/test_launcher.py index c6230a5..325636a 100644 --- a/tests/test_launcher.py +++ b/tests/test_launcher.py @@ -319,9 +319,12 @@ def poll(self): try: srv.start(log_dir=log_dir) assert log_dir.exists() + assert srv.is_running is True finally: subprocess.Popen = original_popen - assert srv.is_running is True + # Discard the mock child before closing real log/lock handles. + srv.process = None + srv.stop() @pytest.mark.asyncio async def test_wait_ready_true_when_healthy(self, monkeypatch: pytest.MonkeyPatch): @@ -573,12 +576,15 @@ def poll(self): subprocess.Popen = _fake_popen try: srv.start(log_dir=log_dir) + assert called["kwargs"]["creationflags"] == getattr( + subprocess, "CREATE_NEW_PROCESS_GROUP", 0 + ) + assert "preexec_fn" not in called["kwargs"] finally: subprocess.Popen = original_popen - assert called["kwargs"]["creationflags"] == getattr( - subprocess, "CREATE_NEW_PROCESS_GROUP", 0 - ) - assert "preexec_fn" not in called["kwargs"] + # Discard the mock child before closing real log/lock handles. + srv.process = None + srv.stop() def test_stop_sends_ctrl_break_then_force_kills_tree_on_timeout(self, monkeypatch): from arc_llama import launcher as launcher_mod diff --git a/uv.lock b/uv.lock index d4d22e9..1503f4a 100644 --- a/uv.lock +++ b/uv.lock @@ -67,6 +67,7 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "build" }, + { name = "httpx2" }, { name = "mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -93,6 +94,7 @@ requires-dist = [ { name = "fastembed", marker = "extra == 'semantic'", specifier = ">=0.3.0" }, { name = "gguf", specifier = ">=0.10" }, { name = "httpx", specifier = ">=0.27" }, + { name = "httpx2", marker = "extra == 'dev'", specifier = ">=2.13.1" }, { name = "huggingface-hub", specifier = ">=0.20" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.0,<2" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10" }, @@ -618,6 +620,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/f3/1db7aa2bc2524062192bb0e0323969492d1883152a232fe36eea65f4e35c/httpcore2-2.13.1.tar.gz", hash = "sha256:e0aa977abe17e69a3b820a24542a6fa88702676d83880b8d194dcd18408e5103", size = 68071, upload-time = "2026-09-23T07:47:22.372Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/ba/a4568248771ce81957bfb7cc600264a40fbcda092391ee1c415c50be4bea/httpcore2-2.13.1-py3-none-any.whl", hash = "sha256:e1e05d4f25f7d7d496bfb96748f6f4b67657b03da069b3a68c36069f3db73d0a", size = 83423, upload-time = "2026-09-23T07:47:19.365Z" }, +] + [[package]] name = "httptools" version = "0.8.0" @@ -692,6 +707,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] +[[package]] +name = "httpx2" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio", marker = "sys_platform != 'emscripten'" }, + { name = "httpcore2", marker = "sys_platform != 'emscripten'" }, + { name = "httpx2-jsfetch", marker = "python_full_version >= '3.12' and sys_platform == 'emscripten'" }, + { name = "idna" }, + { name = "truststore", marker = "sys_platform != 'emscripten'" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/44/474bef2a0e9d90f1715d32cb98b0738695ca17ba324095fb2497ed7fbd59/httpx2-2.13.1.tar.gz", hash = "sha256:e48744a19e3af5ee48313d0ce5fe941d5422fae5705ea922a4aabf94d7800dfa", size = 100405, upload-time = "2026-09-23T07:47:23.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/9c/6fe8931fd9f381042a9e4c7d5a7b4cbf7016b252bec0c99a49fce42c3326/httpx2-2.13.1-py3-none-any.whl", hash = "sha256:6dff50fabc270ee5fd25d845d0b078ed20564579744d6d962850975996d2f9a4", size = 95597, upload-time = "2026-09-23T07:47:20.995Z" }, +] + +[[package]] +name = "httpx2-jsfetch" +version = "1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/0e5636363151a2a1795e0a77617168b9ca438e1748ec05fc9b5687f93d64/httpx2_jsfetch-1.0.tar.gz", hash = "sha256:70a0e3eabfef7cce5ad9c629f7d01ca05e418f586646f4ddf14782e4c1454c60", size = 6872, upload-time = "2026-08-07T00:13:07.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/43/832f631d32e4f1211caa2ba368317739fe71f0b8530e4c9d15dc454bac2a/httpx2_jsfetch-1.0-py3-none-any.whl", hash = "sha256:cb916b707601e69a07721aabc8f3f6659be3a6893bc1ff5c6f9e02241df2da32", size = 6382, upload-time = "2026-08-07T00:13:06.567Z" }, +] + [[package]] name = "huggingface-hub" version = "1.19.0" @@ -2443,6 +2484,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typer" version = "0.25.1"