v0.4.0: adaptive VRAM — auto-detect max n_ctx based on free GPU memory
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
"""memwalk — ask AI about any codebase via cached SSM state."""
|
"""memwalk — ask AI about any codebase via cached SSM state."""
|
||||||
|
|
||||||
__version__ = "0.3.0"
|
__version__ = "0.4.0"
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ def digest(
|
|||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
source = Path(path).expanduser().resolve()
|
source = Path(path).expanduser().resolve()
|
||||||
|
|
||||||
|
if n_ctx is None:
|
||||||
|
from .gpu import auto_n_ctx
|
||||||
|
chosen = auto_n_ctx()
|
||||||
|
console.print(f"[dim]Adaptive n_ctx = {chosen:,}[/dim]")
|
||||||
|
|
||||||
if split:
|
if split:
|
||||||
with console.status(f"Discovering subdirectories in {source}…"):
|
with console.status(f"Discovering subdirectories in {source}…"):
|
||||||
results = engine_digest_subdirs(cfg, source, n_ctx=n_ctx,
|
results = engine_digest_subdirs(cfg, source, n_ctx=n_ctx,
|
||||||
|
|||||||
+2
-1
@@ -12,6 +12,7 @@ from memba import Session
|
|||||||
|
|
||||||
from . import cache, corpus
|
from . import cache, corpus
|
||||||
from .config import Config
|
from .config import Config
|
||||||
|
from .gpu import auto_n_ctx
|
||||||
|
|
||||||
# Prompt that frames the ingest call so the assistant turn stored in state
|
# Prompt that frames the ingest call so the assistant turn stored in state
|
||||||
# is *substantive* (not "noted") — avoids the contextual inertia bug we
|
# is *substantive* (not "noted") — avoids the contextual inertia bug we
|
||||||
@@ -64,7 +65,7 @@ def digest(
|
|||||||
if not source_path.exists() or not source_path.is_dir():
|
if not source_path.exists() or not source_path.is_dir():
|
||||||
raise NotADirectoryError(source_path)
|
raise NotADirectoryError(source_path)
|
||||||
|
|
||||||
n_ctx = n_ctx or cfg.n_ctx
|
n_ctx = auto_n_ctx(n_ctx if n_ctx else None)
|
||||||
files = corpus.collect_files(source_path)
|
files = corpus.collect_files(source_path)
|
||||||
if not files:
|
if not files:
|
||||||
raise RuntimeError(f"No source files found under {source_path}")
|
raise RuntimeError(f"No source files found under {source_path}")
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""GPU VRAM probing and adaptive n_ctx estimation."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
_NEMOTRON_PROFILE: list[tuple[int, float]] = [
|
||||||
|
(8192, 4.5),
|
||||||
|
(16384, 5.0),
|
||||||
|
(32768, 5.5),
|
||||||
|
(65536, 6.5),
|
||||||
|
(131072, 8.5),
|
||||||
|
(262144, 12.0),
|
||||||
|
(524288, 18.0),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def probe_free_vram_mb() -> int | None:
|
||||||
|
"""Return free VRAM in MiB, or None if no NVIDIA GPU detected."""
|
||||||
|
if shutil.which("nvidia-smi"):
|
||||||
|
try:
|
||||||
|
out = subprocess.check_output(
|
||||||
|
[
|
||||||
|
"nvidia-smi",
|
||||||
|
"--query-gpu=memory.free",
|
||||||
|
"--format=csv,noheader,nounits",
|
||||||
|
],
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
first_line = out.strip().splitlines()[0].strip()
|
||||||
|
return int(float(first_line))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pynvml # type: ignore[import-untyped]
|
||||||
|
pynvml.nvmlInit()
|
||||||
|
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
|
||||||
|
info = pynvml.nvmlDeviceGetMemoryInfo(handle)
|
||||||
|
pynvml.nvmlShutdown()
|
||||||
|
return info.free // (1024 * 1024)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def estimate_max_n_ctx(free_vram_mb: int, *, headroom_mb: int = 1536) -> int:
|
||||||
|
"""Return the largest n_ctx from the profile that fits in free VRAM."""
|
||||||
|
usable_mb = free_vram_mb - headroom_mb
|
||||||
|
if usable_mb <= 0:
|
||||||
|
return 8192
|
||||||
|
|
||||||
|
best = 8192
|
||||||
|
for n_ctx, needed_gb in _NEMOTRON_PROFILE:
|
||||||
|
needed_mb = int(needed_gb * 1024)
|
||||||
|
if needed_mb <= usable_mb:
|
||||||
|
best = n_ctx
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
def auto_n_ctx(preferred: int | None = None) -> int:
|
||||||
|
"""Return n_ctx to use: preferred if given, else GPU-adaptive."""
|
||||||
|
if preferred is not None and preferred > 0:
|
||||||
|
return preferred
|
||||||
|
|
||||||
|
free_mb = probe_free_vram_mb()
|
||||||
|
if free_mb is None:
|
||||||
|
return 32768
|
||||||
|
return estimate_max_n_ctx(free_mb)
|
||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "memwalk"
|
name = "memwalk"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
description = "Ask AI about any codebase — local, cached, SSM-state-backed exploration via memba + Nemotron"
|
description = "Ask AI about any codebase — local, cached, SSM-state-backed exploration via memba + Nemotron"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
|
|||||||
Reference in New Issue
Block a user