File size: 4,133 Bytes
12e0e4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
"""Evidence that this process runs on CPU only, instead of a label that says so.

The status used to publish `gpu_enabled: False` and `n_gpu_layers: 0` as
hardcoded literals. Nothing checked anything, so if the installed wheel had CUDA
compiled in and a load site ever asked for offload, the status would still have
said "cpu-only".

This lives in its own module so a test can execute it. `app.py` pulls in gradio
and the whole Minecraft stack, which a unit test should not need to satisfy.

IMPORTANT -- why the binary query is opt-in
-------------------------------------------
`llama_cpp.llama_supports_gpu_offload()` must NOT be called before
`llama_backend_init()`. Measured on this project: calling it first raises
`OSError [WinError -1073741795]`, i.e. NTSTATUS 0xC000001D
STATUS_ILLEGAL_INSTRUCTION -- the call runs code that the CPU cannot execute
before the backend is registered. On Linux the equivalent is SIGILL, which
kills the process and cannot be caught by try/except, so an unconditional call
at import would have taken the Space down.

The library listing below needs no native call and is enough on its own: if the
wheel ships no CUDA/Vulkan/Metal/ROCm backend, it structurally cannot offload a
layer. The binary query stays available behind LLAMA_BINARY_PROBE=1 for a
deliberate check after a model has been loaded.
"""

from __future__ import annotations

import os
from pathlib import Path

# Every load site must ask for zero offloaded layers. Omitting the argument is
# not the same as passing zero: it lets the library decide.
REQUIRED_OFFLOAD_ARGUMENTS = {
    "n_gpu_layers": 0,
    "offload_kqv": False,
}

GPU_LIBRARY_TOKENS = ("cuda", "cublas", "vulkan", "metal", "hip", "rocm")
LIBRARY_SUFFIXES = (".so", ".dll", ".dylib")

BINARY_PROBE_ENABLED = os.environ.get("LLAMA_BINARY_PROBE", "0") == "1"


def backend_libraries() -> list[str]:
    """Names of the native backend libraries actually shipped with llama_cpp.

    Pure filesystem inspection: no native call, so it is safe at any time.
    """
    try:
        import llama_cpp
    except ImportError:
        return []
    base = Path(llama_cpp.__file__).resolve().parent
    return sorted(
        path.name
        for path in base.rglob("*")
        if path.is_file() and path.suffix.lower() in LIBRARY_SUFFIXES
    )


def binary_offload_support():
    """Ask the linked binary whether it can offload. Risky before backend init.

    Returns True, False, or a string explaining why the answer is unavailable.
    Never raises.
    """
    try:
        from llama_cpp import llama_cpp as _backend

        return bool(_backend.llama_supports_gpu_offload())
    except BaseException as exc:  # noqa: BLE001 - a probe must not take the process down
        return f"unavailable: {type(exc).__name__}"


def cpu_only_report(probe_binary: bool | None = None) -> dict:
    """Report whether this process can use a GPU, rather than asserting it cannot."""
    if probe_binary is None:
        probe_binary = BINARY_PROBE_ENABLED

    libraries = backend_libraries()
    cuda_libraries = sorted(
        name for name in libraries if any(token in name.lower() for token in GPU_LIBRARY_TOKENS)
    )

    report = {
        "gpu_offload_supported": binary_offload_support() if probe_binary else "not probed",
        "gpu_binary_probe_enabled": probe_binary,
        "gpu_backend_libraries": libraries,
        "cuda_libraries_present": cuda_libraries,
        "n_gpu_layers_requested": REQUIRED_OFFLOAD_ARGUMENTS["n_gpu_layers"],
    }
    # CPU-only is structural when the wheel ships no GPU backend at all, and
    # enforced when every load site additionally asks for zero layers.
    report["cpu_only"] = (
        not cuda_libraries and report["n_gpu_layers_requested"] == 0
    )
    return report


def cpu_only_summary(report: dict) -> str:
    return (
        f"cpu_only={report['cpu_only']} "
        f"gpu_libs={report['gpu_backend_libraries']} "
        f"cuda_libs={report['cuda_libraries_present'] or 'none'} "
        f"n_gpu_layers={report['n_gpu_layers_requested']} "
        f"binary_probe={report['gpu_offload_supported']}"
    )