prefer VRAM by default, and let the suite run where production runs

Offload: the default becomes auto-borderline, not auto. `auto` condemns a model
on its ESTIMATE -- if used_vram_gb exceeds free VRAM it goes straight to CPU
offload without ever trying the card -- and that estimate is a measurement from
an earlier load in some cards and a hand-entered round number in others
(LTX-2.3-PinkCherry declared exactly 24.0 on a 24 GiB card). So a conservative
figure permanently exiled a model that would have fit.

The cost of being wrong is asymmetric, which is what decides it: guessing "fits"
wrongly costs one failed attempt and a fallback, while guessing "does not fit"
wrongly costs a CPU copy of every component and a PCIe trip per request for the
life of the install -- 28-37 GB of host RAM per video render on a 54 GB box with
no cgroup limit to stop it. The dropdown is relabelled by intent and reordered,
because the old labels were the trap: `auto` read as the sensible choice while
being the pessimistic one.

Tests: three files hardcoded /storage/coderai, so collection FAILED outright
inside the production image and every test in them had only ever been exercised
against the host's dependency set -- which is diffusers 0.37.0.dev0 with no
torchsde and no PyAV, against the image's 0.42.1 with both. The four Krea 2 edit
tests were silently skipping for want of Krea2Pipeline and now run (15 passed,
0 skipped in-image). Paths are derived from __file__ instead.
Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014T3rZozi1Ehk96NG1WesgC
parent 92a3d928
...@@ -1523,9 +1523,9 @@ window.__DEFAULT_WHISPER_SERVER_PATH__ = {{ default_whisper_server_path|tojson } ...@@ -1523,9 +1523,9 @@ window.__DEFAULT_WHISPER_SERVER_PATH__ = {{ default_whisper_server_path|tojson }
<div class="form-row" style="margin:0"> <div class="form-row" style="margin:0">
<label class="form-label">Strategy</label> <label class="form-label">Strategy</label>
<select id="cfg-offload-strategy" class="form-input"> <select id="cfg-offload-strategy" class="form-input">
<option value="auto">Auto (over-VRAM → straight to model offload)</option> <option value="auto-borderline">Prefer VRAM — try the card, offload only if it really will not fit (recommended)</option>
<option value="auto-borderline">Auto borderline-aware (try full-GPU if marginally over)</option> <option value="auto">Auto — offload as soon as the estimate exceeds free VRAM (never tries)</option>
<option value="none">None (GPU only)</option> <option value="none">None (GPU only, fails instead of offloading)</option>
<option value="model">CPU offload (model — active component on GPU)</option> <option value="model">CPU offload (model — active component on GPU)</option>
<option value="group">Group offload + stream (block-level, prefetched)</option> <option value="group">Group offload + stream (block-level, prefetched)</option>
<option value="balanced">Balanced (fill GPU, spill to CPU → disk)</option> <option value="balanced">Balanced (fill GPU, spill to CPU → disk)</option>
...@@ -4405,7 +4405,10 @@ function openCfgModal(idx, cfgIdx){ ...@@ -4405,7 +4405,10 @@ function openCfgModal(idx, cfgIdx){
document.getElementById('cfg-precision').value = s.precision || ''; document.getElementById('cfg-precision').value = s.precision || '';
document.getElementById('cfg-flash').checked = !!s.flash_attention; document.getElementById('cfg-flash').checked = !!s.flash_attention;
document.getElementById('cfg-noram').checked = !!s.no_ram; document.getElementById('cfg-noram').checked = !!s.no_ram;
document.getElementById('cfg-offload-strategy').value = s.offload_strategy || 'auto'; // Falls back to the VRAM-preferring strategy, not plain 'auto': 'auto'
// offloads on the ESTIMATE alone, which on a conservative or hand-entered
// used_vram_gb permanently exiles a model that would have fit.
document.getElementById('cfg-offload-strategy').value = s.offload_strategy || 'auto-borderline';
document.getElementById('cfg-offload-dir').value = s.offload_dir || _defaultOffloadDir; document.getElementById('cfg-offload-dir').value = s.offload_dir || _defaultOffloadDir;
_applyBackendCfgVisibility(m); _applyBackendCfgVisibility(m);
// gpu_split → "Split — <lead engine> first" (lead = the pinned engine, or the // gpu_split → "Split — <lead engine> first" (lead = the pinned engine, or the
......
...@@ -254,7 +254,24 @@ class ModelsConfig: ...@@ -254,7 +254,24 @@ class ModelsConfig:
class OffloadConfig: class OffloadConfig:
"""Offload configuration.""" """Offload configuration."""
directory: str = "./offload" directory: str = "./offload"
strategy: str = "auto" # Prefer VRAM; offload only when there is genuinely no other choice.
#
# `auto` condemns a model on its ESTIMATE: if used_vram_gb exceeds free
# VRAM it goes straight to CPU offload without ever trying. That estimate
# is a measurement from an earlier load in some cards and a hand-entered
# round number in others -- LTX-2.3-PinkCherry declares exactly 24.0 on a
# 24 GiB card -- so a conservative figure permanently exiles a model that
# would have fit. `auto-borderline` tries the card first when the overshoot
# is within _BORDERLINE_GB and falls back on a real OOM, which is the same
# outcome when it does not fit and a resident model when it does.
#
# The cost of being wrong is asymmetric, which is what makes this the right
# default: guessing "fits" wrongly costs one failed attempt and a fallback,
# while guessing "does not fit" wrongly costs a CPU copy of every component
# and a PCIe trip per request, for the life of the install. On the host that
# prompted this, video renders were holding 28-37 GB of host RAM each that
# way, on a box with 54 GB and no cgroup limit.
strategy: str = "auto-borderline"
max_gpu_percent: Optional[float] = None max_gpu_percent: Optional[float] = None
no_ram: bool = False no_ram: bool = False
load_in_4bit: bool = False load_in_4bit: bool = False
......
...@@ -4,7 +4,13 @@ import json ...@@ -4,7 +4,13 @@ import json
import sys import sys
AUTH_MODULE_PATH = "/storage/coderai/codai/admin/auth.py" # Relative to this file, not an absolute host path: the repo is mounted at a
# different place when the suite runs inside the production image (/src), and a
# hardcoded /storage/coderai made collection fail there outright -- so every
# test in this file was only ever exercised against the host's dependency set.
from pathlib import Path as _Path
AUTH_MODULE_PATH = str(_Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "auth.py")
spec = importlib.util.spec_from_file_location("test_admin_auth_module", AUTH_MODULE_PATH) spec = importlib.util.spec_from_file_location("test_admin_auth_module", AUTH_MODULE_PATH)
auth_module = importlib.util.module_from_spec(spec) auth_module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = auth_module sys.modules[spec.name] = auth_module
......
from pathlib import Path from pathlib import Path
import sys import sys
sys.path.insert(0, '/storage/coderai') sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from codai.api.audio_backends import detect_audio_backends, reset_audio_backend_cache from codai.api.audio_backends import detect_audio_backends, reset_audio_backend_cache
...@@ -41,6 +41,6 @@ def test_detect_audio_backends_prefers_deepfilter_for_restoration(monkeypatch): ...@@ -41,6 +41,6 @@ def test_detect_audio_backends_prefers_deepfilter_for_restoration(monkeypatch):
def test_requirements_include_audio_ml_dependencies(): def test_requirements_include_audio_ml_dependencies():
req = Path('/storage/coderai/requirements.txt').read_text().lower() req = (Path(__file__).resolve().parents[1] / 'requirements.txt').read_text().lower()
assert 'demucs' in req assert 'demucs' in req
assert 'deepfilternet' in req or 'df[' in req assert 'deepfilternet' in req or 'df[' in req
# CoderAI - OpenAI-compatible API server
# Copyright (C) 2026 Stefy Lanza <stefy@nexlab.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Stay in VRAM; offload only when there is no other choice.
`auto` condemns a model on its ESTIMATE: if `used_vram_gb` exceeds free VRAM it
goes straight to CPU offload without ever trying the card. That estimate is a
measurement from an earlier load in some cards and a hand-entered round number
in others -- LTX-2.3-PinkCherry declared exactly 24.0 on a 24 GiB card -- so a
conservative figure permanently exiles a model that would have fit.
The cost of being wrong is asymmetric, which is what decides the default:
guessing "fits" wrongly costs one failed attempt and a fallback, while guessing
"does not fit" wrongly costs a CPU copy of every component and a PCIe trip per
request for the life of the install. On the host that prompted this, video
renders were each holding 28-37 GB of host RAM that way, on a 54 GB box with no
cgroup limit to stop them.
"""
import io
import os
import re
import pytest
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def test_the_global_default_prefers_vram():
from codai.config import Config
assert Config().offload.strategy == "auto-borderline", (
"the default offload strategy no longer prefers VRAM; plain 'auto' "
"offloads on the estimate alone and never tries the card")
def test_the_model_page_falls_back_to_the_vram_preferring_strategy():
"""A card that says nothing must not inherit "offload first"."""
html = io.open(os.path.join(ROOT, "codai/admin/templates/models.html"),
encoding="utf-8").read()
m = re.search(r"cfg-offload-strategy'\)\.value = s\.offload_strategy \|\| '([a-z-]+)'",
html)
assert m, "the offload-strategy fallback could not be found"
assert m.group(1) == "auto-borderline", (
f"the model page falls back to {m.group(1)!r}")
def test_the_recommended_option_is_offered_first():
"""The option list is a recommendation; the safest-sounding one should not
be the pessimistic one."""
html = io.open(os.path.join(ROOT, "codai/admin/templates/models.html"),
encoding="utf-8").read()
block = html[html.index('id="cfg-offload-strategy"'):]
block = block[:block.index("</select>")]
values = re.findall(r'<option value="([a-z-]+)"', block)
assert values[0] == "auto-borderline", (
f"the first offered strategy is {values[0]!r}, not the VRAM-preferring one")
# Both are still available: an operator who wants the pessimistic one for a
# genuinely over-sized model should be able to say so.
assert "auto" in values and "model" in values
def test_both_auto_modes_reach_the_loader():
"""`auto-borderline` must be understood wherever `auto` is, or a card set
to it would be treated as an unknown strategy."""
for path in ("codai/api/video.py", "codai/backends/cuda.py"):
src = io.open(os.path.join(ROOT, path), encoding="utf-8").read()
assert "auto-borderline" in src, f"{path} does not handle auto-borderline"
def test_the_image_path_does_not_force_offload_for_the_auto_modes():
"""images.py decides offload from a list of strategies; neither auto mode
belongs in it, or "prefer VRAM" would mean the opposite."""
src = io.open(os.path.join(ROOT, "codai/api/images.py"), encoding="utf-8").read()
m = re.search(r"_offload_strategy in \(([^)]*)\)", src)
assert m, "the image offload decision could not be found"
listed = m.group(1)
assert "auto" not in listed, (
f"an auto mode is listed as forcing CPU offload: {listed}")
def test_the_borderline_band_is_a_named_number():
"""How far over free VRAM still counts as "worth trying" is a judgement;
it should be visible, not buried in an expression."""
src = io.open(os.path.join(ROOT, "codai/api/video.py"), encoding="utf-8").read()
assert "_BORDERLINE_GB" in src
m = re.search(r"_BORDERLINE_GB\s*=\s*([0-9.]+)", src)
assert m and float(m.group(1)) > 0
...@@ -351,7 +351,8 @@ def test_a_request_with_no_model_still_records_a_printable_model(): ...@@ -351,7 +351,8 @@ def test_a_request_with_no_model_still_records_a_printable_model():
def test_chat_template_wires_preview_shells_for_new_runnable_panels(): def test_chat_template_wires_preview_shells_for_new_runnable_panels():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "id=\"at-preview\"" in text assert "id=\"at-preview\"" in text
...@@ -371,7 +372,8 @@ def test_chat_template_wires_preview_shells_for_new_runnable_panels(): ...@@ -371,7 +372,8 @@ def test_chat_template_wires_preview_shells_for_new_runnable_panels():
def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_metadata(): def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_metadata():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "audioBackendHealth" in text assert "audioBackendHealth" in text
...@@ -382,7 +384,8 @@ def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_meta ...@@ -382,7 +384,8 @@ def test_chat_template_marks_full_quality_audio_panels_with_runtime_backend_meta
def test_chat_template_exposes_ml_preview_and_artifact_markers(): def test_chat_template_exposes_ml_preview_and_artifact_markers():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "buildStemPreviewData" in text assert "buildStemPreviewData" in text
...@@ -394,7 +397,8 @@ def test_chat_template_exposes_ml_preview_and_artifact_markers(): ...@@ -394,7 +397,8 @@ def test_chat_template_exposes_ml_preview_and_artifact_markers():
def test_studio_generation_panel_uses_wider_control_column(): def test_studio_generation_panel_uses_wider_control_column():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert ".gen-ctrl { width:min(380px,36vw); min-width:340px; max-width:420px;" in text assert ".gen-ctrl { width:min(380px,36vw); min-width:340px; max-width:420px;" in text
...@@ -402,7 +406,8 @@ def test_studio_generation_panel_uses_wider_control_column(): ...@@ -402,7 +406,8 @@ def test_studio_generation_panel_uses_wider_control_column():
def test_studio_output_surfaces_capability_warnings(): def test_studio_output_surfaces_capability_warnings():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "cap-output-note" in text assert "cap-output-note" in text
...@@ -411,7 +416,8 @@ def test_studio_output_surfaces_capability_warnings(): ...@@ -411,7 +416,8 @@ def test_studio_output_surfaces_capability_warnings():
def test_pipeline_tab_renders_builder_before_saved_custom_cards(): def test_pipeline_tab_renders_builder_before_saved_custom_cards():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
builder_index = text.index('id="pipe-builder-card"') builder_index = text.index('id="pipe-builder-card"')
...@@ -421,7 +427,8 @@ def test_pipeline_tab_renders_builder_before_saved_custom_cards(): ...@@ -421,7 +427,8 @@ def test_pipeline_tab_renders_builder_before_saved_custom_cards():
def test_pipeline_tab_drops_split_shell_layout(): def test_pipeline_tab_drops_split_shell_layout():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert 'class="pipe-shell"' not in text assert 'class="pipe-shell"' not in text
...@@ -431,7 +438,8 @@ def test_pipeline_tab_drops_split_shell_layout(): ...@@ -431,7 +438,8 @@ def test_pipeline_tab_drops_split_shell_layout():
def test_pipeline_tab_drops_split_selection_helpers(): def test_pipeline_tab_drops_split_selection_helpers():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "renderPipelineList" not in text assert "renderPipelineList" not in text
...@@ -446,7 +454,8 @@ def test_pipeline_tab_drops_split_selection_helpers(): ...@@ -446,7 +454,8 @@ def test_pipeline_tab_drops_split_selection_helpers():
def test_pipeline_tab_keeps_inline_saved_pipeline_cards(): def test_pipeline_tab_keeps_inline_saved_pipeline_cards():
template_path = "/storage/coderai/codai/admin/templates/chat.html" template_path = str(Path(__file__).resolve().parents[1]
/ "codai" / "admin" / "templates" / "chat.html")
text = open(template_path, "r", encoding="utf-8").read() text = open(template_path, "r", encoding="utf-8").read()
assert "renderCustomPipelineCards" in text assert "renderCustomPipelineCards" in text
......
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