gpu: performance split respects per-card VRAM cap; harden process naming

Problem 1 — the per-card cap was read and printed ("VRAM capped at 5.0 GB on
amd:...") but the PERFORMANCE split branch ignored it: it pushed the whole
overflow onto the slow card (_overflow * adj[i]/others == all of it when there's
one other card), so a 5 GB-capped RX 580 got ~47% of the layers and filled up.
Fix: treat each card's (capped) free VRAM as an absolute ceiling — fill the fast
lead card first up to its cap, then each other card up to ITS cap; the GPU budget
is bounded by sum(caps) and the remainder spills to CPU via the n_gpu_layers
auto-offload. RX 580 @5 GB now yields tensor_split [0.805, 0.195] (was [0.527,
0.473]). The VRAM (proportional) strategy already used the capped values.

Problem 2 — process naming: extract _set_proc_title() and ALSO re-assert it right
before the engine binds uvicorn (defensive), with CODERAI_ENGINE_BACKEND as a
fallback name. The per-engine CODERAI_ENGINE_NAME is verified distinct, so this
guarantees coderai-front / coderai-nvidia / coderai-radeon regardless of timing.
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011DDv7BchtZQWsnPG6Jm49m
parent f416117a
...@@ -1317,22 +1317,29 @@ class VulkanBackend(ModelBackend): ...@@ -1317,22 +1317,29 @@ class VulkanBackend(ModelBackend):
_need_total = (_exp_total + _kv_head) if _exp_total else None _need_total = (_exp_total + _kv_head) if _exp_total else None
if (_strategy == 'performance' and len(_adj) > 1 if (_strategy == 'performance' and len(_adj) > 1
and _need_total and _need_total > 0): and _need_total and _need_total > 0):
# Each card holds AT MOST its (capped) free VRAM ``_adj[i]`` — a
# per-card cap is an absolute ceiling, not just a ratio weight.
# The total that can live on GPU is therefore bounded by sum(_adj);
# anything beyond it spills to CPU (the n_gpu_layers auto-offload,
# which uses the same capped pool, makes room for that). Fill the
# fast lead card first up to its cap, then each other card up to
# ITS cap — so a 5 GB-capped slow card never gets more than 5 GB
# even when the model would otherwise overflow onto it.
_w = [0.0] * len(_adj) _w = [0.0] * len(_adj)
_main_cap = _adj[_mg] _gpu_budget = min(_need_total, sum(_adj))
if _need_total <= _main_cap: _rem = _gpu_budget
_w[_mg] = 1.0 # genuinely fits on the fast card alone _w[_mg] = max(0.0, min(_rem, _adj[_mg]))
else: _rem -= _w[_mg]
_overflow = _need_total - _main_cap for i in range(len(_adj)):
_others = sum(_adj[i] for i in range(len(_adj)) if i != _mg) if i != _mg and _rem > 0:
_w[_mg] = _main_cap _w[i] = max(0.0, min(_rem, _adj[i]))
for i in range(len(_adj)): _rem -= _w[i]
if i != _mg and _others > 0:
_w[i] = _overflow * (_adj[i] / _others)
_sw = sum(_w) _sw = sum(_w)
if _sw > 0: if _sw > 0:
_parsed = [round(x / _sw, 3) for x in _w] _parsed = [round(x / _sw, 3) for x in _w]
print(f" gpu split : PERFORMANCE tensor_split={_parsed} " print(f" gpu split : PERFORMANCE tensor_split={_parsed} "
f"(fast card GPU{_mg} first; free {[round(f,1) for f in _free_dev]} GB, " f"(fast card GPU{_mg} first; capped free "
f"{[round(f,1) for f in _adj]} GB of {[round(f,1) for f in _free_dev]} GB, "
f"model ~{_exp_total:.1f} GB + ~{_kv_head:.1f} GB KV/ctx)") f"model ~{_exp_total:.1f} GB + ~{_kv_head:.1f} GB KV/ctx)")
if not _parsed: if not _parsed:
_sum = sum(_adj) _sum = sum(_adj)
......
...@@ -329,6 +329,32 @@ def _repair_stale_model_paths(config_mgr) -> int: ...@@ -329,6 +329,32 @@ def _repair_stale_model_paths(config_mgr) -> int:
return fixed return fixed
def _set_proc_title():
"""Set this process's name by role (idempotent — safe to call more than once).
Engine → coderai-<name> (CODERAI_ENGINE_NAME, else CODERAI_ENGINE_BACKEND).
Front → coderai-front. Single-process → coderai.
Re-called right before the server binds so nothing that ran in between can leave
a stale title."""
try:
import setproctitle
except ImportError:
return
_argv = sys.argv[1:]
if "--engine-only" in _argv:
_ename = (os.environ.get("CODERAI_ENGINE_NAME")
or os.environ.get("CODERAI_ENGINE_BACKEND") or "engine")
name = f"coderai-{_ename}"
elif "--single-process" in _argv:
name = "coderai"
else:
name = "coderai-front"
try:
setproctitle.setproctitle(name)
except Exception:
pass
def main(): def main():
"""Main entry point for the codai server.""" """Main entry point for the codai server."""
# Suppress unraisable exceptions from LlamaModel.__del__ # Suppress unraisable exceptions from LlamaModel.__del__
...@@ -339,24 +365,10 @@ def main(): ...@@ -339,24 +365,10 @@ def main():
original_unraisablehook(unraisable) original_unraisablehook(unraisable)
sys.unraisablehook = suppress_llama_del_errors sys.unraisablehook = suppress_llama_del_errors
# Optional: set process name if setproctitle is available. Name by role so # Name the process by role so the front and each engine are distinguishable in
# the front and each engine are distinguishable in ps/top/htop: # ps/top/htop: front → coderai-front, per-backend engine → coderai-<enginename>
# front process → coderai-front # (name from env set at spawn; backend as fallback), legacy single → coderai.
# per-backend engine → coderai-<enginename> (name passed via env at spawn) _set_proc_title()
# legacy single proc → coderai
try:
import setproctitle
_argv = sys.argv[1:]
if "--engine-only" in _argv:
_ename = os.environ.get("CODERAI_ENGINE_NAME") or "engine"
_proc_name = f"coderai-{_ename}"
elif "--single-process" in _argv:
_proc_name = "coderai"
else:
_proc_name = "coderai-front"
setproctitle.setproctitle(_proc_name)
except ImportError:
pass
args = parse_args() args = parse_args()
...@@ -1342,6 +1354,10 @@ def main(): ...@@ -1342,6 +1354,10 @@ def main():
# UI live on the front, so don't advertise them here (it's just confusing). # UI live on the front, so don't advertise them here (it's just confusing).
print(f"[engine] serving on http://{bind_host}:{bind_port} " print(f"[engine] serving on http://{bind_host}:{bind_port} "
f"(internal — reach it via the front)") f"(internal — reach it via the front)")
# Re-assert the process name right before binding (defensive: nothing between
# startup and here should have changed it, but this guarantees the engine
# shows as coderai-<name> in ps/htop regardless).
_set_proc_title()
else: else:
bind_host = config.server.host bind_host = config.server.host
bind_port = config.server.port bind_port = config.server.port
......
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