Files
2026-07-13 12:29:01 +08:00

503 lines
16 KiB
Python

"""Tests for compatibility checking."""
from whichllm.constants import _GiB
from whichllm.engine.compatibility import check_compatibility
from whichllm.hardware.memory import estimate_usable_ram
from whichllm.hardware.types import GPUInfo, HardwareInfo
from whichllm.models.types import GGUFVariant, ModelInfo
def _make_model(
params: int = 7_000_000_000, context_length: int | None = None
) -> ModelInfo:
return ModelInfo(
id="test/model",
family_id="test/model",
name="model",
parameter_count=params,
context_length=context_length,
)
def _make_variant(size: int = 4_000_000_000) -> GGUFVariant:
return GGUFVariant(
filename="model-Q4_K_M.gguf", quant_type="Q4_K_M", file_size_bytes=size
)
def _make_hardware(
vram: int = 0, ram: int = 16 * 1024**3, disk: int = 100 * 1024**3, **gpu_kwargs
) -> HardwareInfo:
gpus = []
if vram > 0:
gpus.append(
GPUInfo(
name="Test GPU",
vendor=gpu_kwargs.get("vendor", "nvidia"),
vram_bytes=vram,
compute_capability=gpu_kwargs.get("cc", (8, 6)),
memory_bandwidth_gbps=gpu_kwargs.get("bw", 500.0),
)
)
return HardwareInfo(
gpus=gpus,
cpu_name="Test CPU",
cpu_cores=8,
has_avx2=True,
ram_bytes=ram,
disk_free_bytes=disk,
os="linux",
)
def test_full_gpu_fit():
model = _make_model()
variant = _make_variant(4_000_000_000)
hw = _make_hardware(vram=24 * 1024**3) # 24GB VRAM
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "full_gpu"
def test_partial_offload():
model = _make_model()
variant = _make_variant(20_000_000_000) # 20GB model
hw = _make_hardware(vram=8 * 1024**3, ram=64 * 1024**3) # 8GB VRAM, 64GB RAM
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "partial_offload"
assert 0.0 < result.offload_ratio < 1.0
assert any("offload" in w.lower() for w in result.warnings)
def test_usable_vram_budget_can_turn_full_gpu_into_partial_offload():
model = _make_model()
variant = _make_variant(7_000_000_000)
hw = _make_hardware(vram=8 * _GiB, ram=64 * _GiB)
hw.gpus[0].usable_vram_bytes = 6 * _GiB
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "partial_offload"
assert result.vram_available_bytes == 6 * _GiB
def test_ram_budget_limits_partial_offload_pool():
model = _make_model()
variant = _make_variant(20_000_000_000)
hw = _make_hardware(vram=8 * _GiB, ram=64 * _GiB)
hw.ram_budget_bytes = 4 * _GiB
result = check_compatibility(model, variant, hw)
assert result.can_run is False
assert "Insufficient memory" in result.warnings[-1]
def test_ram_budget_caps_shared_memory_gpu_fit_pool():
model = _make_model()
variant = _make_variant(12_000_000_000)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="Apple M2",
vendor="apple",
vram_bytes=16 * _GiB,
usable_vram_bytes=15 * _GiB,
memory_bandwidth_gbps=100.0,
shared_memory=True,
)
],
cpu_name="Apple M2",
cpu_cores=8,
ram_bytes=16 * _GiB,
ram_budget_bytes=8 * _GiB,
disk_free_bytes=100 * _GiB,
os="darwin",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is False
assert result.vram_available_bytes == 8 * _GiB
def test_shared_memory_manual_vram_override_caps_available_gpu_memory():
model = _make_model(8_000_000_000)
variant = _make_variant(4_000_000_000)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="Intel UHD Graphics",
vendor="intel",
vram_bytes=1 * _GiB,
shared_memory=True,
vram_overridden=True,
)
],
cpu_name="Intel CPU",
cpu_cores=8,
ram_bytes=32 * _GiB,
disk_free_bytes=100 * _GiB,
os="linux",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.vram_available_bytes == 1 * _GiB
assert result.fit_type == "cpu_only"
def test_shared_memory_amd_apu_uses_system_memory_pool():
model = _make_model(120_000_000_000)
variant = _make_variant(55_000_000_000)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="STRXLGEN",
vendor="amd",
vram_bytes=512 * 1024**2,
memory_bandwidth_gbps=256.0,
shared_memory=True,
)
],
cpu_name="AMD Ryzen AI MAX+ 395",
cpu_cores=16,
ram_bytes=128 * 1024**3,
disk_free_bytes=200 * 1024**3,
os="linux",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "full_gpu"
assert result.vram_available_bytes == estimate_usable_ram(hw.ram_bytes)
assert not any("offload" in w.lower() for w in result.warnings)
assert not any("cpu only" in w.lower() for w in result.warnings)
def test_windows_shared_memory_amd_apu_does_not_emit_rocm_warning():
model = _make_model(8_000_000_000)
variant = _make_variant(6_000_000_000)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="AMD Ryzen AI 9 HX 370 w/ Radeon 890M",
vendor="amd",
vram_bytes=0,
memory_bandwidth_gbps=120.0,
shared_memory=True,
)
],
cpu_name="AMD Ryzen AI 9 HX 370",
cpu_cores=12,
ram_bytes=16 * 1024**3,
disk_free_bytes=100 * 1024**3,
os="windows",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "full_gpu"
assert result.vram_available_bytes == estimate_usable_ram(hw.ram_bytes)
assert not any("rocm" in w.lower() for w in result.warnings)
assert not any("offload" in w.lower() for w in result.warnings)
def test_shared_memory_igpu_is_not_summed_with_dedicated_gpu():
model = _make_model(20_000_000_000)
variant = _make_variant(14 * 1024**3)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="NVIDIA GeForce RTX 4060",
vendor="nvidia",
vram_bytes=8 * 1024**3,
memory_bandwidth_gbps=272.0,
),
GPUInfo(
name="Intel(R) Arc(TM) Graphics",
vendor="intel",
vram_bytes=0,
shared_memory=True,
),
],
cpu_name="Intel CPU",
cpu_cores=12,
ram_bytes=32 * 1024**3,
disk_free_bytes=100 * 1024**3,
os="windows",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "partial_offload"
assert result.vram_available_bytes == 8 * 1024**3
assert any("offloaded to CPU RAM" in w for w in result.warnings)
def test_homogeneous_multi_gpu_uses_conservative_fit_budget():
model = _make_model(1_000_000_000)
variant = _make_variant(int(46 * _GiB))
hw = HardwareInfo(
gpus=[
GPUInfo(
name="NVIDIA GeForce RTX 4090",
vendor="nvidia",
vram_bytes=24 * _GiB,
compute_capability=(8, 9),
memory_bandwidth_gbps=1008.0,
),
GPUInfo(
name="NVIDIA GeForce RTX 4090",
vendor="nvidia",
vram_bytes=24 * _GiB,
compute_capability=(8, 9),
memory_bandwidth_gbps=1008.0,
),
],
cpu_name="Test CPU",
cpu_cores=16,
ram_bytes=128 * _GiB,
disk_free_bytes=200 * _GiB,
os="linux",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "partial_offload"
assert result.uses_multi_gpu is True
assert result.vram_available_bytes == 48 * _GiB
assert result.multi_gpu_effective_vram_bytes is not None
assert result.multi_gpu_effective_vram_bytes < result.vram_available_bytes
assert any("conservative layer-split budget" in w for w in result.warnings)
def test_heterogeneous_multi_gpu_warns_about_split_assumptions():
model = _make_model()
variant = _make_variant(20 * _GiB)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="NVIDIA GeForce RTX 4090",
vendor="nvidia",
vram_bytes=24 * _GiB,
compute_capability=(8, 9),
memory_bandwidth_gbps=1008.0,
),
GPUInfo(
name="NVIDIA GeForce RTX 3060",
vendor="nvidia",
vram_bytes=12 * _GiB,
compute_capability=(8, 6),
memory_bandwidth_gbps=360.0,
),
],
cpu_name="Test CPU",
cpu_cores=16,
ram_bytes=64 * _GiB,
disk_free_bytes=200 * _GiB,
os="linux",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.uses_multi_gpu is True
assert result.multi_gpu_effective_vram_bytes is not None
assert result.multi_gpu_effective_vram_bytes < 36 * _GiB
assert any("Heterogeneous multi-GPU" in w for w in result.warnings)
def test_multiple_shared_memory_gpus_are_not_summed():
model = _make_model(120_000_000_000)
variant = _make_variant(70 * _GiB)
hw = HardwareInfo(
gpus=[
GPUInfo(
name="Integrated GPU A",
vendor="amd",
vram_bytes=0,
memory_bandwidth_gbps=120.0,
shared_memory=True,
),
GPUInfo(
name="Integrated GPU B",
vendor="intel",
vram_bytes=0,
shared_memory=True,
),
],
cpu_name="Test CPU",
cpu_cores=16,
ram_bytes=64 * _GiB,
disk_free_bytes=200 * _GiB,
os="linux",
)
result = check_compatibility(model, variant, hw)
assert result.vram_available_bytes == estimate_usable_ram(hw.ram_bytes)
assert result.multi_gpu_effective_vram_bytes is None
assert result.fit_type == "cpu_only"
assert any("shared-memory GPUs are not pooled" in w for w in result.warnings)
def test_apple_silicon_does_not_double_count_unified_memory():
"""Apple Silicon uses unified memory: vram_bytes IS the system RAM.
The fit checker must not add a separate offload pool on top."""
model = _make_model(70_000_000_000)
variant = _make_variant(40_000_000_000) # 40 GB model
hw = HardwareInfo(
gpus=[
GPUInfo(
name="Apple M2 Max",
vendor="apple",
vram_bytes=32 * 1024**3, # 32 GB unified memory
memory_bandwidth_gbps=400.0,
shared_memory=True,
)
],
cpu_name="Apple M2 Max",
cpu_cores=12,
ram_bytes=32 * 1024**3,
disk_free_bytes=200 * 1024**3,
os="darwin",
)
result = check_compatibility(model, variant, hw)
# Model (40 GB) exceeds unified memory (32 GB). There is no separate
# CPU RAM pool to spill into, so this must NOT be partial_offload.
assert result.fit_type != "partial_offload", (
"Apple Silicon should not get partial_offload — unified memory "
"cannot be double-counted as GPU VRAM + CPU RAM offload pool"
)
assert not any("offloaded to CPU RAM" in w for w in result.warnings)
def test_apple_silicon_full_gpu_fit():
"""A model that fits within unified memory should be full_gpu."""
model = _make_model(7_000_000_000)
variant = _make_variant(4_000_000_000) # 4 GB model
hw = HardwareInfo(
gpus=[
GPUInfo(
name="Apple M4 Pro",
vendor="apple",
vram_bytes=24 * 1024**3,
memory_bandwidth_gbps=273.0,
shared_memory=True,
)
],
cpu_name="Apple M4 Pro",
cpu_cores=14,
ram_bytes=24 * 1024**3,
disk_free_bytes=200 * 1024**3,
os="darwin",
)
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "full_gpu"
assert not any("offload" in w.lower() for w in result.warnings)
def test_apple_silicon_vendor_guard_handles_legacy_shared_memory_false():
"""Even if a cached/older GPUInfo has shared_memory=False, the
vendor=='apple' guard should still prevent double-counting."""
model = _make_model(70_000_000_000)
variant = _make_variant(40_000_000_000) # 40 GB model
hw = HardwareInfo(
gpus=[
GPUInfo(
name="Apple M2 Max",
vendor="apple",
vram_bytes=32 * 1024**3,
memory_bandwidth_gbps=400.0,
shared_memory=False, # legacy/cached object
)
],
cpu_name="Apple M2 Max",
cpu_cores=12,
ram_bytes=32 * 1024**3,
disk_free_bytes=200 * 1024**3,
os="darwin",
)
result = check_compatibility(model, variant, hw)
assert result.fit_type != "partial_offload", (
"vendor='apple' guard must prevent double-counting even when "
"shared_memory=False (cached/older GPUInfo)"
)
assert not any("offloaded to CPU RAM" in w for w in result.warnings)
def test_cpu_only():
model = _make_model(1_000_000_000)
variant = _make_variant(600_000_000)
hw = _make_hardware(vram=0, ram=16 * 1024**3) # No GPU
result = check_compatibility(model, variant, hw)
assert result.can_run is True
assert result.fit_type == "cpu_only"
def test_insufficient_memory():
model = _make_model(70_000_000_000)
variant = _make_variant(40_000_000_000)
hw = _make_hardware(vram=0, ram=8 * 1024**3) # Only 8GB RAM, no GPU
result = check_compatibility(model, variant, hw)
assert result.can_run is False
def test_low_compute_capability():
model = _make_model()
variant = _make_variant(4_000_000_000)
hw = _make_hardware(vram=24 * 1024**3, cc=(4, 0)) # Very old GPU
result = check_compatibility(model, variant, hw)
assert result.can_run is True # Still runs, just with warning
assert any("compute capability" in w.lower() for w in result.warnings)
def test_insufficient_disk():
model = _make_model()
variant = _make_variant(50_000_000_000) # 50GB file
hw = _make_hardware(vram=80 * 1024**3, disk=10 * 1024**3) # Only 10GB disk
result = check_compatibility(model, variant, hw)
assert result.can_run is False
assert any("disk" in w.lower() for w in result.warnings)
def test_context_fits_true_when_model_supports():
model = _make_model(context_length=131072)
variant = _make_variant()
hw = _make_hardware(vram=24 * 1024**3)
result = check_compatibility(model, variant, hw, context_length=32768)
assert result.context_fits is True
def test_context_fits_false_when_model_too_small():
model = _make_model(context_length=8192)
variant = _make_variant()
hw = _make_hardware(vram=24 * 1024**3)
result = check_compatibility(model, variant, hw, context_length=32768)
assert result.context_fits is False
assert any("max context" in w.lower() for w in result.warnings)
def test_context_fits_unknown_is_true():
model = _make_model(context_length=None)
variant = _make_variant()
hw = _make_hardware(vram=24 * 1024**3)
result = check_compatibility(model, variant, hw, context_length=32768)
assert result.context_fits is True
assert not any("max context" in w.lower() for w in result.warnings)