Skip to content

fix(studio): Windows GGUF cancel hang + CPU spinlock overhead (#5692)#5749

Merged
Imagineer99 merged 16 commits into
unslothai:mainfrom
anmolxlight:fix-5692-win-gguf-cancel-hang
Jun 15, 2026
Merged

fix(studio): Windows GGUF cancel hang + CPU spinlock overhead (#5692)#5749
Imagineer99 merged 16 commits into
unslothai:mainfrom
anmolxlight:fix-5692-win-gguf-cancel-hang

Conversation

@anmolxlight

Copy link
Copy Markdown
Contributor

Summary

Two fixes for Windows-native GGUF inference via llama-server (unsloth Studio).

Issue 1 — GPU/CUDA Hang on Stream Cancellation (Socket Leak)

When a user cancels a streaming chat completion midway (stops generation, closes tab), llama-server continues decoding in the background because the httpx Keep-Alive connection masks the downstream socket closure.

Changes in studio/backend/routes/inference.py:

  • Add Connection: close header to all httpx requests proxying to llama-server, preventing Keep-Alive from masking downstream socket closure.
  • Introduce _await_disconnect_then_close background watcher that polls request.is_disconnected() every 100ms and calls resp.aclose() immediately when the client disconnects. This runs alongside the existing cancel-POST watcher and covers client aborts that never reach the /cancel endpoint (tab close, proxy aborts, Colab, mobile navigation, etc.).
  • Change all StreamingResponse Connection: keep-alive headers to Connection: close.

Issue 2 — High CPU Spinlock & KV Cache Backup Overhead

During GGUF inference on Windows, CPU usage spikes to 100% even when the model is fully GPU-offloaded (-ngl -1), causing thermal throttling and fan noise. Two root causes:

  1. OpenMP spinlock: Default Windows OpenMP spawns threads up to logical core count to await GPU completion, wasting CPU cycles.
  2. KV cache sync: llama-server copies prompt cache checkpoints to system RAM by default, causing massive WDDM driver and PCI-E bandwidth overhead on Windows.

Changes in studio/backend/core/inference/llama_cpp.py:

  • Set OMP_WAIT_POLICY=PASSIVE and OMP_NUM_THREADS=2 in the llama-server subprocess environment on Windows to prevent OpenMP from spin-waiting on all logical cores while the GPU decodes.
  • Limit --threads to 2 on Windows when the model is fully GPU-offloaded (-ngl -1). Auto-detect otherwise.
  • Pass --cache-ram 0 --ctx-checkpoints 0 --no-cache-prompt --checkpoint-every-n-tokens -1 on Windows to disable prompt-cache snapshots that copy KV cache to system RAM over the WDDM/PCI-E bus.

Fixes #5692.

…hai#5692)

Two fixes for Windows-native GGUF inference via llama-server:

**Issue 1 — GPU/CUDA Hang on Stream Cancellation:**
- Add `Connection: close` header to all httpx requests proxying to
  llama-server, preventing Keep-Alive from masking downstream socket
  closure.
- Introduce `_await_disconnect_then_close` background watcher that
  polls `request.is_disconnected()` every 100ms and calls
  `resp.aclose()` immediately when the client disconnects. This runs
  alongside the existing cancel-POST watcher and covers client aborts
  that never reach the /cancel endpoint (tab close, proxy aborts,
  Colab, mobile navigation, etc.).
- Change all StreamingResponse `Connection: keep-alive` headers to
  `Connection: close`.

**Issue 2 — High CPU Spinlock & KV Cache Backup Overhead:**
- Set OMP_WAIT_POLICY=PASSIVE and OMP_NUM_THREADS=2 in the
  llama-server subprocess environment on Windows to prevent OpenMP
  from spin-waiting on all logical cores while the GPU decodes.
- Limit `--threads` to 2 on Windows when the model is fully
  GPU-offloaded (`-ngl -1`). Auto-detect otherwise.
- Pass `--cache-ram 0 --ctx-checkpoints 0 --no-cache-prompt
  --checkpoint-every-n-tokens -1` on Windows to disable prompt-cache
  snapshots that copy KV cache to system RAM over the WDDM/PCI-E bus.

Closes unslothai#5692.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several optimizations and resource management improvements for inference, particularly targeting Windows environments. Key changes include limiting CPU threads and setting passive OpenMP wait policies when models are fully GPU-offloaded, disabling KV-cache checkpoints to system RAM to reduce PCI-E overhead, and implementing a background watcher to close response streams immediately upon client disconnect. Feedback from the review suggests simplifying the initialization logic for the GPU offload flag and recommends logging exceptions instead of using silent pass blocks when closing responses to improve debuggability.

Comment thread studio/backend/core/inference/llama_cpp.py Outdated
Comment thread studio/backend/routes/inference.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 881eb11e18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +437 to +440
while not await request.is_disconnected():
await asyncio.sleep(0.1)
try:
await resp.aclose()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark disconnect-driven closes as cancellation

When _await_disconnect_then_close closes resp on client disconnect, it never sets cancel_event, but both passthrough streamers treat RemoteProtocolError/ReadError/CloseError as ignorable only when cancel_event.is_set(). In the exact prefill-blocked case this watcher was added for, the loop never reaches its own request.is_disconnected() check, so the close is interpreted as an upstream error and re-raised instead of a normal disconnect path.

Useful? React with 👍 / 👎.

Comment thread studio/backend/core/inference/llama_cpp.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8addf2ac83

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread studio/backend/core/inference/llama_cpp.py Outdated
- Simplify _fully_gpu_offloaded init: default to False, only set True
  in the gpu_indices branch, drop redundant else.
- Log exceptions in _await_disconnect_then_close at debug level instead
  of silent pass, per review suggestion.
@danielhanchen

Copy link
Copy Markdown
Member

Reviewed against pr-5749-head (8addf2a). Diagnoses match the linked #5692 root causes; the keep-alive socket leak and the OpenMP/cache-RAM mitigations are the right shape. Three correctness issues that I think are worth a follow-up before merge, all confirmed by reading the live tree.

1. _await_disconnect_then_close does not set cancel_event

studio/backend/routes/inference.py:422-444. Both passthrough streamers (_anthropic_passthrough_stream, _openai_passthrough_stream) treat RemoteProtocolError / ReadError / CloseError as ignorable only when cancel_event.is_set():

# inference.py:5088-5090 (anthropic), 5513-5517 (openai)
except (httpx.RemoteProtocolError, httpx.ReadError, httpx.CloseError):
    if not cancel_event.is_set():
        raise

In the exact prefill-blocked scenario the watcher exists for, the in-loop fallback if await request.is_disconnected(): cancel_event.set() (lines 5074 / 5501) is unreachable. When the watcher's resp.aclose() unblocks the iterator, cancel_event is still clear, so the re-raise fires. In the OpenAI path it falls into the broader except Exception at line 5518, which emits a synthetic data: {"error": ...} SSE chunk to a client that has already disconnected and logs "openai passthrough stream error". In the Anthropic path the broad except Exception at 5091 swallows it under "anthropic_messages passthrough stream error". Either way you get a spurious error in logs/metrics for every prefill-time disconnect.

Suggested fix is a one-liner plus signature change:

async def _await_disconnect_then_close(request, resp, cancel_event) -> None:
    ...
    try:
        while not await request.is_disconnected():
            await asyncio.sleep(0.1)
        cancel_event.set()                       # mirror the in-loop path
        try:
            await resp.aclose()
        except Exception as e:
            logger.debug("Failed to close response on disconnect: %s", e)
    except asyncio.CancelledError:
        return

Both call sites (inference.py:5068, inference.py:5494) then pass cancel_event through.

2. Windows OMP env vars are unconditional

studio/backend/core/inference/llama_cpp.py:3164-3182. The companion --threads 2 change at line 2983 is correctly gated if _sys.platform == "win32" and _fully_gpu_offloaded, but the OMP env setup is not:

if sys.platform == "win32":
    ...
    env.setdefault("OMP_WAIT_POLICY", "PASSIVE")
    env.setdefault("OMP_NUM_THREADS", "2")

The PR comment explicitly cites the full-GPU-offload case, so this looks like an oversight rather than intent. Windows CPU-only and partial-offload users on many-core boxes silently get capped at 2 OpenMP threads, which is a real regression for the workloads that still need CPU compute.

_fully_gpu_offloaded is defined at line 2967 and is still in scope here, so the fix is:

if _fully_gpu_offloaded:
    env.setdefault("OMP_WAIT_POLICY", "PASSIVE")
    env.setdefault("OMP_NUM_THREADS", "2")

3. Windows cache-disabling flags are unconditional

studio/backend/core/inference/llama_cpp.py:3121-3137 appends --cache-ram 0 --ctx-checkpoints 0 --no-cache-prompt --checkpoint-every-n-tokens -1 for every Windows launch. The rationale (WDDM / PCI-E bus overhead from VRAM-to-RAM KV checkpoints) only applies when the KV cache actually lives in VRAM. For CPU-only or partial-offload Windows runs, --no-cache-prompt defeats prefix reuse across turns, which is a likely regression for chat workflows.

Same fix shape:

if _sys.platform == "win32" and _fully_gpu_offloaded:
    cmd.extend(["--cache-ram", "0", "--ctx-checkpoints", "0",
                "--no-cache-prompt", "--checkpoint-every-n-tokens", "-1"])

Notes (non-blocking)

  • _fully_gpu_offloaded at line 2967 is only set on the gpu_indices is not None branch and not on the use_fit branch. The existing --threads 2 gate already inherits this scoping, so reusing the same predicate for fixes 2 and 3 is consistent. If --fit on can also fully offload in practice, widening the predicate would also pull fit-mode into the existing thread cap, which seems desirable. Worth a separate look.
  • The Connection: close header on the upstream client.build_request(...) calls is fine since both passthroughs already pin httpx.Limits(max_keepalive_connections=0) on the client, so there is no pool to defeat. Mentioning for future readers.
  • cancel_watcher and disconnect_watcher can race to aclose() the same resp. The except Exception: pass / logger.debug swallows make this harmless, but a one-line note in the watcher docstring would save the next reader the trace.
  • No test under tests/studio/ exercises _await_disconnect_then_close or the prefill-RemoteProtocolError sequence. A focused unit test that mocks request.is_disconnected() returning True, asserts cancel_event becomes set, and asserts no spurious SSE error chunk is emitted, would lock fix 1 in.

I applied the three patches above to a local checkout of this branch and ran pytest tests/studio/test_stream_cancel_registration_timing.py tests/studio/test_cancel_atomicity.py tests/studio/test_cancel_id_wiring.py tests/studio/test_llama_cpp_wall_clock_cap.py (48 tests, all green). Cannot reproduce the Windows GGUF numbers from here, but happy to run more if there is a Linux-side concern I missed.

@danielhanchen

Copy link
Copy Markdown
Member

Quick follow-up with a git-am-able patch and additional validation results.

Copy-pasteable patch

The three review fixes as a single commit applied against PR head 8addf2ac. Tested locally + via CI on ubuntu-latest, macos-14, windows-latest.

From 01e78eb86df3ff9f832b83e194a929469a35d2bd Mon Sep 17 00:00:00 2001
Subject: review: address codex-bot findings on PR 5749

---
 studio/backend/core/inference/llama_cpp.py | 22 ++++++++++++---------
 studio/backend/routes/inference.py         | 13 +++++++++---
 2 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/studio/backend/core/inference/llama_cpp.py b/studio/backend/core/inference/llama_cpp.py
@@ -3118,12 +3118,13 @@ class LlamaCppBackend:
                 else:
                     self._api_key = None

-                # On Windows, llama-server's default KV-cache checkpoints write
-                # prompt-cache snapshots to system RAM over the WDDM driver /
-                # PCI-E bus.  This adds substantial overhead even when the model
-                # is fully GPU-offloaded.  Disable all checkpoint machinery to
-                # keep the cache entirely in VRAM.  #5692.
-                if _sys.platform == "win32":
+                # On Windows with full GPU offload, llama-server's default
+                # KV-cache checkpoints write prompt-cache snapshots to system
+                # RAM over the WDDM driver / PCI-E bus.  Disable all checkpoint
+                # machinery to keep the cache entirely in VRAM.  Scoped to
+                # full-offload only: CPU-only / partial-offload Windows runs
+                # still benefit from prompt caching across turns.  #5692.
+                if _sys.platform == "win32" and _fully_gpu_offloaded:
                     cmd.extend(
                         [
                             "--cache-ram",
@@ -3177,9 +3178,12 @@ class LlamaCppBackend:
                     # PASSIVE tells the runtime to yield instead of spinning;
                     # limiting to 2 threads keeps the few remaining CPU-side
                     # operations (tokenisation, copy) from monopolising the
-                    # scheduler.  #5692.
-                    env.setdefault("OMP_WAIT_POLICY", "PASSIVE")
-                    env.setdefault("OMP_NUM_THREADS", "2")
+                    # scheduler.  Scoped to full-offload only so CPU-only and
+                    # partial-offload runs keep their default OpenMP parallelism.
+                    # #5692.
+                    if _fully_gpu_offloaded:
+                        env.setdefault("OMP_WAIT_POLICY", "PASSIVE")
+                        env.setdefault("OMP_NUM_THREADS", "2")
                 else:
                     # Linux: set LD_LIBRARY_PATH for shared libs next to the binary
                     # and CUDA runtime libs (libcudart, libcublas, etc.)
diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py
@@ -419,7 +419,7 @@ async def _await_cancel_then_close(cancel_event, resp) -> None:
         return


-async def _await_disconnect_then_close(request, resp) -> None:
+async def _await_disconnect_then_close(request, resp, cancel_event) -> None:
     """Watch ``request.is_disconnected()`` and close ``resp`` when the client
     disconnects (tab closed, navigation away, fetch abort).  This prevents
     llama-server from continuing to decode after the client is gone — a
@@ -432,10 +432,17 @@ async def _await_disconnect_then_close(request, resp) -> None:
     Starlette Request directly, so it covers client-initiated aborts that
     never reach the /cancel POST path (proxied fetches, Colab, mobile
     tab-close, etc.).
+
+    Sets ``cancel_event`` before closing ``resp`` so the streamer's
+    ``except (RemoteProtocolError, ReadError, CloseError)`` clause -- which
+    only suppresses the re-raise when ``cancel_event.is_set()`` -- treats
+    the watcher-driven close as a normal client cancellation rather than
+    an upstream error.  Mirrors the in-loop fallback path.
     """
     try:
         while not await request.is_disconnected():
             await asyncio.sleep(0.1)
+        cancel_event.set()
         try:
             await resp.aclose()
         except Exception as e:
@@ -5065,7 +5072,7 @@ async def _anthropic_passthrough_stream(
                 _await_cancel_then_close(cancel_event, resp)
             )
             disconnect_watcher = asyncio.create_task(
-                _await_disconnect_then_close(request, resp)
+                _await_disconnect_then_close(request, resp, cancel_event)
             )
             lines_iter = resp.aiter_lines()
             async for raw_line in lines_iter:
@@ -5491,7 +5498,7 @@ async def _openai_passthrough_stream(
                 _await_cancel_then_close(cancel_event, resp)
             )
             disconnect_watcher = asyncio.create_task(
-                _await_disconnect_then_close(request, resp)
+                _await_disconnect_then_close(request, resp, cancel_event)
             )
             try:
                 lines_iter = resp.aiter_lines()

Additional validation since the previous comment

Probe Result
Anthropic passthrough /v1/messages (probe1 abort mid-prefill + probe2 cancel POST + drain) 559 / 5689 chunks, 0 error chunks either path
GPU utilization decay after cancel (12 runs, B200, gemma-3-1b-Q4_K_XL) time-to-≤5% util: median 0.58s, p95 0.76s, max 0.88s
Race test: 30 iterations firing client aclose() + cancel-POST in the same event-loop tick 0 error chunks total; server healthy after every iteration
Soak: 1000 cancel events with psutil sampling every 50 0 errors, 0 failed, +57 MB Studio RSS (plateaued by iter 200), +43 MB llama-server RSS, 0 FD growth (68 → 68), all health checks green, 324s elapsed
Happy-path smoke: 10 streaming + 10 non-streaming completions after the patch 20/20 succeed
pytest tests/studio/ (existing suite) with patches applied 146/146 pass

The race test stresses the failure mode the disconnect-watcher's new cancel_event.set() is most likely to perturb (both watchers calling aclose() near-simultaneously). The soak's flat FD count rules out asyncio.Task leaks from the new background task creation.

The Anthropic-path cancel POST returns {"cancelled": 0} in some runs — looks like the cancel registry key path differs slightly between OpenAI and Anthropic, but the wire stays clean regardless, so this is unrelated to the changes in this PR. Worth a separate look later if you care about cancel-POST always matching for /v1/messages.

- _await_disconnect_then_close: set cancel_event before resp.aclose() so
  the streamer's RemoteProtocolError handler treats the watcher-driven
  close as cancellation, not an upstream error. Both call sites pass
  cancel_event through.
- Windows --cache-ram / --no-cache-prompt / --ctx-checkpoints block: gate
  on _fully_gpu_offloaded so CPU and partial-offload Windows runs keep
  prompt-cache reuse across turns.
- Windows OMP_WAIT_POLICY / OMP_NUM_THREADS env: same gate so CPU and
  partial-offload Windows runs keep default OpenMP parallelism.
@danielhanchen

Copy link
Copy Markdown
Member

Pushed commit 38903b64 to address the unaddressed bot review findings.

Three changes, all behaviour-preserving outside the targeted scenarios:

  1. _await_disconnect_then_close(request, resp, cancel_event): now sets cancel_event before resp.aclose(). The two passthrough streamers' except (RemoteProtocolError, ReadError, CloseError) clause only suppresses the re-raise when cancel_event.is_set(), so the watcher-driven close was previously surfacing as an upstream error (synthetic SSE error chunk on the OpenAI path, error-level log entry on the Anthropic path). Both call sites updated to pass cancel_event through.
  2. Windows --cache-ram 0 --ctx-checkpoints 0 --no-cache-prompt --checkpoint-every-n-tokens -1: gated on _fully_gpu_offloaded so CPU-only and partial-offload Windows runs keep prompt-cache reuse across turns.
  3. Windows OMP_WAIT_POLICY=PASSIVE and OMP_NUM_THREADS=2: same gate so CPU and partial-offload Windows runs keep their default OpenMP parallelism instead of getting silently capped at 2 threads.

Local + cross-platform validation:

  • 146/146 existing tests/studio/* pass with the patch applied.
  • New regression-lock tests pass on ubuntu-latest, macos-14, and windows-latest (separate validation branch, not part of this PR).
  • 1000-cancel soak on a Linux GPU box: 0 errors, 0 file-descriptor growth (68 -> 68), 57 MB RSS growth plateaued by iter 200.
  • GPU utilization decay after cancel (12 runs): time to <=5% util median 0.58s, p95 0.76s, max 0.88s.
  • Race test (30 iterations firing client aclose() + cancel-POST in the same event-loop tick): 0 error chunks, healthy after every iteration.
  • Anthropic /v1/messages passthrough probe: 6248 chunks across both abort + cancel scenarios, 0 error chunks.

Let me know if you want any of these test files added to tests/studio/ as a separate commit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38903b6402

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread studio/backend/core/inference/llama_cpp.py Outdated


- Drop the function-local `import sys as _sys` introduced as an F823
  workaround; remove the redundant in-function `import os`/`import sys`
  block so module-level imports resolve sys/os instead. F823 no longer
  triggers because no shadowing import remains inside load_model.
- Rename `_fully_gpu_offloaded` and `_t` to `fully_gpu_offloaded` and
  `threads_arg`. Underscore-prefixed names usually mean private/module-
  level; plain locals match Python style for in-function temporaries.

No behavior change. ruff clean, py_compile clean, 35 studio cancel-
infra tests + 13 launch-gating AST locks + 6 disconnect-watcher locks
+ 4 spoof live-import tests all pass.
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

danielhanchen added a commit to danielhanchen/unsloth-staging-2 that referenced this pull request May 26, 2026
The Windows-launch-gating AST regression locks pinned the exact
variable names from the upstream PR head (`_fully_gpu_offloaded`,
`_sys`, `_t`). The follow-up cleanup commit on PR unslothai#5749 drops the
underscore prefixes, which would otherwise break the locks on the
next upstream sync. Accept either form so the lock survives the
rename.

19 tests still pass on the current staging-branch source.
@Imagineer99 Imagineer99 self-assigned this Jun 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d992261380

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread studio/backend/core/inference/llama_cpp.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc83bb72e5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread studio/backend/core/inference/llama_cpp.py
Comment thread studio/backend/core/inference/llama_cpp.py Outdated
@Imagineer99

Copy link
Copy Markdown
Collaborator
image

CI passes on fork.

@Imagineer99 Imagineer99 merged commit 9f694ab into unslothai:main Jun 15, 2026
25 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Windows Native GGUF Inference: GPU/CUDA Hang on Cancel & High CPU Spinlock Overhead

4 participants