quant: fix settings showing "GPTQModel not installed" when it is installed

is_available() reported false in the running engine even though gptqmodel + fast
kernels import fine in a fresh process. Two causes:

1. capabilities() cached a DEGRADED result (gptqmodel imported but the inner
   gptqmodel.utils.backend BACKEND import transiently came up empty, e.g. when the
   first call landed mid model-load). That empty-backends result stuck for the whole
   process life, so the settings page said "GPTQModel not installed" until restart.
   Now a degraded (available-but-no-backends) result is NOT cached — re-detect next
   call; only a clean positive or a genuine ImportError is cached.

2. is_available() gated on a SPECIFIC fast kernel being detected. GPTQModel always
   has a Triton/torch fallback and picks the kernel at load, so availability now
   gates only on gptqmodel importing; backends stay informational.

Also: /admin/api/quantize-capabilities re-detects live (capabilities(refresh=True))
so the settings page never serves a stale cache.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RdMufYvtTbtGDWsiZVoXce
parent e3d0d35c
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API # Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here. # metadata and the admin web UI read from here.
__version__ = "0.1.16" __version__ = "0.1.17"
# Configure the CUDA caching allocator BEFORE torch is imported anywhere. # Configure the CUDA caching allocator BEFORE torch is imported anywhere.
# expandable_segments lets the allocator return freed pages to the driver even # expandable_segments lets the allocator return freed pages to the driver even
......
...@@ -1907,8 +1907,11 @@ async def api_model_disable(request: Request, username: str = Depends(require_ad ...@@ -1907,8 +1907,11 @@ async def api_model_disable(request: Request, username: str = Depends(require_ad
async def api_quantize_capabilities(username: str = Depends(require_admin)): async def api_quantize_capabilities(username: str = Depends(require_admin)):
"""Report whether fast-kernel (GPTQ/AWQ) quantization is available + any jobs.""" """Report whether fast-kernel (GPTQ/AWQ) quantization is available + any jobs."""
from codai.models import quant from codai.models import quant
# refresh=True: the settings page must get a LIVE re-detect, never a stale
# degraded cache from an early in-process call.
caps = quant.capabilities(refresh=True)
return { return {
"capabilities": quant.capabilities(), "capabilities": caps,
"available": quant.is_available(), "available": quant.is_available(),
"jobs": quant.all_jobs(), "jobs": quant.all_jobs(),
} }
......
...@@ -58,14 +58,27 @@ def capabilities(refresh: bool = False) -> Dict[str, Any]: ...@@ -58,14 +58,27 @@ def capabilities(refresh: bool = False) -> Dict[str, Any]:
caps["available"] = True caps["available"] = True
except Exception as e: # ImportError or a broken transitive dep except Exception as e: # ImportError or a broken transitive dep
caps["error"] = str(e) caps["error"] = str(e)
# Don't cache a DEGRADED result — gptqmodel imported but no fast-kernel
# backend was detected. That inner BACKEND import can transiently come up
# empty (e.g. when the first call lands mid model-load), and caching it would
# wrongly report quantization "unavailable" for the whole process life (the
# settings page then says "GPTQModel not installed" until a restart). Leave
# the cache unset so the next call re-detects; a clean positive or a genuine
# ImportError is stable and gets cached.
if caps["available"] and not caps["backends"]:
return caps
_caps_cache = caps _caps_cache = caps
return caps return caps
def is_available() -> bool: def is_available() -> bool:
"""True when GPTQModel imports and at least one fast kernel is present.""" """True when GPTQModel imports. The fast-kernel backends (Marlin/ExLlama/Triton)
c = capabilities() are informational only — GPTQModel always has a Triton/torch fallback and picks
return bool(c["available"] and c["backends"]) the runtime kernel at load — so availability is NOT gated on a specific kernel
being detected. That detection can transiently report empty and would otherwise
disable quantization (and mislabel it "GPTQModel not installed") for the whole
process."""
return bool(capabilities().get("available"))
# -------------------------------------------------------------------------------- # --------------------------------------------------------------------------------
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment