Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
"""The unified-pool vendor quirk must never misclassify a discrete card.
|
||||
|
||||
On unified-memory NVIDIA devices under Windows, nvidia-smi answers from
|
||||
a WDDM carve-out while the CUDA allocator addresses the whole pool at
|
||||
full bandwidth. Budgeting from smi there produces false "larger than
|
||||
your GPU memory" rows and -ot CPU pinning that measures far slower than
|
||||
letting the allocator place everything.
|
||||
|
||||
The other direction is the regression this file guards: a workstation
|
||||
card must never be budgeted as UMA. The driver's INTEGRATED attribute
|
||||
decides when readable (both directions); the attribute-less engine
|
||||
fallback needs two independent numeric gates, each alone unmeetable by
|
||||
any discrete card."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hermes_cli.local_runtime.hardware as hw
|
||||
|
||||
GIB = 1 << 30
|
||||
|
||||
# Representative unified-memory device shape: a 48 GiB box whose smi
|
||||
# reports only the 16 GiB carve-out while the allocator sees the pool.
|
||||
UMA_SMI_TOTAL = 16320 << 20
|
||||
UMA_POOL = 46464 << 20
|
||||
UMA_RAM = 48 * GIB
|
||||
|
||||
|
||||
def _no_cache(monkeypatch):
|
||||
monkeypatch.setattr(hw, "_pool_probe_cache", None)
|
||||
|
||||
|
||||
# ── _unified_pool_bytes: the classification gate ─────────────
|
||||
|
||||
|
||||
def test_integrated_attribute_wins_positive(monkeypatch):
|
||||
"""Driver says integrated=True -> unified, no numeric gates needed."""
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (UMA_POOL, True))
|
||||
assert hw._unified_pool_bytes(UMA_SMI_TOTAL, UMA_RAM) == UMA_POOL
|
||||
|
||||
|
||||
def test_integrated_attribute_wins_negative(monkeypatch):
|
||||
"""Driver says integrated=False -> discrete, even when the numbers
|
||||
would pass both fallback gates (attribute outranks arithmetic)."""
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (UMA_POOL, False))
|
||||
assert hw._unified_pool_bytes(UMA_SMI_TOTAL, UMA_RAM) is None
|
||||
|
||||
|
||||
def test_engine_fallback_uma_shape_passes(monkeypatch):
|
||||
"""Attribute unreadable (engine fallback): the carve-out shape passes
|
||||
both gates — pool several times the smi report, pool ~= RAM."""
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (UMA_POOL, None))
|
||||
assert hw._unified_pool_bytes(UMA_SMI_TOTAL, UMA_RAM) == UMA_POOL
|
||||
|
||||
|
||||
def test_discrete_card_agreeing_within_rounding_stays_discrete(monkeypatch):
|
||||
"""A healthy discrete card: allocator and smi agree within rounding.
|
||||
Fails the disagreement gate regardless of box RAM."""
|
||||
smi = 24 * GIB
|
||||
monkeypatch.setattr(hw, "_device_pool_view",
|
||||
lambda: (smi + (200 << 20), None))
|
||||
assert hw._unified_pool_bytes(smi, 24 * GIB) is None
|
||||
assert hw._unified_pool_bytes(smi, 256 * GIB) is None
|
||||
|
||||
|
||||
def test_ram_matched_workstation_card_stays_discrete(monkeypatch):
|
||||
"""The nastiest lookalike: a 48 GB card in a 48 GB box. smi and the
|
||||
allocator AGREE (both say 48), so the disagreement gate fails even
|
||||
though pool == RAM would pass the size gate."""
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (48 * GIB, None))
|
||||
assert hw._unified_pool_bytes(48 * GIB, 48 * GIB) is None
|
||||
|
||||
|
||||
def test_pool_smaller_than_ram_fraction_stays_discrete(monkeypatch):
|
||||
"""Disagreement without the RAM-sized-pool signature stays discrete:
|
||||
a hypothetical card whose allocator over-reports 2x in a huge-RAM box
|
||||
is a driver bug to distrust, not a unified pool."""
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (32 * GIB, None))
|
||||
assert hw._unified_pool_bytes(16 * GIB, 128 * GIB) is None
|
||||
|
||||
|
||||
def test_no_probe_available_stays_discrete(monkeypatch):
|
||||
"""No driver API, no engine binary -> exactly today's behavior."""
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: None)
|
||||
assert hw._unified_pool_bytes(UMA_SMI_TOTAL, UMA_RAM) is None
|
||||
|
||||
|
||||
# ── probe_budget wiring ──────────────────────────────────────
|
||||
|
||||
|
||||
def _uma_machine(monkeypatch, *, view):
|
||||
_no_cache(monkeypatch)
|
||||
monkeypatch.setattr(hw, "_nvidia_vram",
|
||||
lambda: (UMA_SMI_TOTAL, 14848 << 20))
|
||||
monkeypatch.setattr(hw, "_ram_bytes",
|
||||
lambda: (UMA_RAM, 32 * GIB))
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: view)
|
||||
|
||||
|
||||
def test_budget_unified_pool_planning(monkeypatch):
|
||||
"""Planning budget on unified memory: the allocator pool itself minus
|
||||
headroom, uma=True, ram_available=0 — host memory must not
|
||||
double-count as spill room. NO OS-RAM clamp: carved-out memory is
|
||||
invisible to GlobalMemoryStatusEx (a larger carve shrinks reported
|
||||
OS RAM while the pool stays constant), so clamping to OS RAM would
|
||||
throw away exactly the carved capacity."""
|
||||
_uma_machine(monkeypatch, view=(UMA_POOL, True))
|
||||
b = hw.probe_budget(planning=True)
|
||||
assert b.uma is True
|
||||
assert b.ram_available_bytes == 0
|
||||
assert b.total_device_bytes == UMA_POOL
|
||||
assert b.usable_vram_bytes == int(UMA_POOL * (1 - hw._UMA_HEADROOM_FRACTION))
|
||||
# The whole point: the budget must dwarf the carve-out.
|
||||
assert b.usable_vram_bytes > 2 * UMA_SMI_TOTAL
|
||||
|
||||
|
||||
def test_budget_unified_pool_live_counts_dedicated_free_plus_os_available(monkeypatch):
|
||||
"""Live budget = smi-free + OS-available (each side alone under-counts:
|
||||
smi free saturates at the carve-out, OS-available can't see it)."""
|
||||
_uma_machine(monkeypatch, view=(UMA_POOL, True))
|
||||
b = hw.probe_budget(planning=False)
|
||||
assert b.uma is True
|
||||
live = (14848 << 20) + 32 * GIB
|
||||
assert b.usable_vram_bytes == int(min(UMA_POOL, live)
|
||||
* (1 - hw._UMA_HEADROOM_FRACTION))
|
||||
|
||||
|
||||
def test_budget_unified_no_smi_still_classifies(monkeypatch):
|
||||
"""nvidia-smi off PATH must not change the verdict: the driver API
|
||||
(system loader, PATH-independent) still classifies unified, planning
|
||||
still budgets the full pool, and live falls back to OS-available."""
|
||||
_no_cache(monkeypatch)
|
||||
monkeypatch.setattr(hw, "_nvidia_vram", lambda: None)
|
||||
monkeypatch.setattr(hw, "_ram_bytes", lambda: (UMA_RAM, 32 * GIB))
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (UMA_POOL, True))
|
||||
planning = hw.probe_budget(planning=True)
|
||||
assert planning.uma is True
|
||||
assert planning.total_device_bytes == UMA_POOL
|
||||
assert planning.usable_vram_bytes == int(
|
||||
UMA_POOL * (1 - hw._UMA_HEADROOM_FRACTION))
|
||||
live = hw.probe_budget(planning=False)
|
||||
assert live.usable_vram_bytes == int(32 * GIB * (1 - hw._UMA_HEADROOM_FRACTION))
|
||||
|
||||
|
||||
def test_budget_discrete_unchanged_when_probe_says_discrete(monkeypatch):
|
||||
"""integrated=False keeps the existing discrete path bit-for-bit."""
|
||||
_uma_machine(monkeypatch, view=(UMA_POOL, False))
|
||||
b = hw.probe_budget(planning=True)
|
||||
assert b.uma is False
|
||||
assert b.total_device_bytes == UMA_SMI_TOTAL
|
||||
margin = max(hw._MARGIN_FLOOR, int(UMA_SMI_TOTAL * hw._MARGIN_FRACTION))
|
||||
assert b.usable_vram_bytes == UMA_SMI_TOTAL - margin
|
||||
assert b.ram_available_bytes == UMA_RAM
|
||||
|
||||
|
||||
def test_budget_discrete_unchanged_when_probe_unavailable(monkeypatch):
|
||||
_uma_machine(monkeypatch, view=None)
|
||||
b = hw.probe_budget(planning=True)
|
||||
assert b.uma is False
|
||||
assert b.ram_available_bytes == UMA_RAM
|
||||
|
||||
|
||||
def test_engine_fallback_without_smi_stays_conservative(monkeypatch):
|
||||
"""Engine-fallback view (no INTEGRATED verdict) + no smi numbers: the
|
||||
disagreement gate has nothing to compare against, so the quirk stays
|
||||
off and budgeting falls to the conservative RAM-as-UMA path — an
|
||||
attribute-less pool claim alone must never flip the verdict."""
|
||||
_no_cache(monkeypatch)
|
||||
monkeypatch.setattr(hw, "_nvidia_vram", lambda: None)
|
||||
monkeypatch.setattr(hw, "_ram_bytes", lambda: (UMA_RAM, 32 * GIB))
|
||||
monkeypatch.setattr(hw, "_device_pool_view", lambda: (UMA_POOL, None))
|
||||
b = hw.probe_budget(planning=True)
|
||||
assert b.uma is True
|
||||
assert b.total_device_bytes == UMA_RAM # RAM path, not the pool
|
||||
|
||||
|
||||
def test_smi_resolver_caches_and_survives_empty_path(monkeypatch):
|
||||
"""The resolver consults PATH first, and a resolution (hit or miss) is
|
||||
cached for the process."""
|
||||
calls = []
|
||||
monkeypatch.setattr(hw, "_smi_path_cache", None)
|
||||
monkeypatch.setattr(hw.shutil, "which",
|
||||
lambda name: calls.append(name) or "/usr/bin/nvidia-smi")
|
||||
assert hw._nvidia_smi_path() == "/usr/bin/nvidia-smi"
|
||||
assert hw._nvidia_smi_path() == "/usr/bin/nvidia-smi"
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
# ── probe cache ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_pool_probe_hit_is_cached_for_process(monkeypatch):
|
||||
_no_cache(monkeypatch)
|
||||
calls = []
|
||||
|
||||
def probe():
|
||||
calls.append(1)
|
||||
return (UMA_POOL, True)
|
||||
|
||||
monkeypatch.setattr(hw, "_cuda_driver_pool", probe)
|
||||
monkeypatch.setattr(hw, "_engine_device_pool", lambda: None)
|
||||
assert hw._device_pool_view() == (UMA_POOL, True)
|
||||
assert hw._device_pool_view() == (UMA_POOL, True)
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
def test_pool_probe_miss_retries_after_ttl(monkeypatch):
|
||||
"""A miss must not be permanent: the engine binary can appear
|
||||
mid-session via the pane's runtime install."""
|
||||
_no_cache(monkeypatch)
|
||||
monkeypatch.setattr(hw, "_cuda_driver_pool", lambda: None)
|
||||
answers = [None, (UMA_POOL, None)]
|
||||
monkeypatch.setattr(hw, "_engine_device_pool", lambda: answers.pop(0))
|
||||
|
||||
now = [1000.0]
|
||||
monkeypatch.setattr(hw.time, "monotonic", lambda: now[0])
|
||||
assert hw._device_pool_view() is None
|
||||
now[0] += 1.0 # inside TTL: cached miss, no re-probe
|
||||
assert hw._device_pool_view() is None
|
||||
now[0] += hw._POOL_NEGATIVE_TTL_S + 1.0
|
||||
assert hw._device_pool_view() == (UMA_POOL, None)
|
||||
assert not answers
|
||||
|
||||
|
||||
# ── device-line parsing (engine fallback) ────────────────────
|
||||
|
||||
|
||||
def test_device_line_regex_handles_parenthesized_names():
|
||||
"""Device names may contain their own parentheses — the LAST
|
||||
parenthesized group must win."""
|
||||
line = (" CUDA0: NVIDIA Example Device (1234-core Example GPU) "
|
||||
"(46464 MiB, 46284 MiB free)")
|
||||
m = hw._DEVICE_LINE_RE.search(line)
|
||||
assert m and int(m.group(1)) == 46464
|
||||
Reference in New Issue
Block a user