mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-13 06:06:38 +00:00
Follow-up toae259a9f58after an independent review: - The 136-character task name broke the 80-character name lint (tests/lint/repository/test_name_emoji_and_length.py). Names are short again; the device/driver/host matrix lives in the role README. - Both stats now always run and lookup('gpu') requires both registers, instead of reading a skipped device stat as "no device". - A bare host that shows /dev/nvidia0 without nvidia-smi stops the deploy with a message naming the fix. It used to die on the missing binary anyway;ae259a9f58would have run its GPU services on the CPU silently. A container in that state (a swarm lab node, the dev container without its GPU override) still runs on the CPU. - svc-ai-s1 decided its CUDA build from its own stat of /dev/nvidia0 and nvidia-container-runtime while its runtime came from lookup('gpu'), so a host with the toolkit but no nvidia-smi would have built a CUDA image that gets no GPU. S1_GPU now reads lookup('gpu'), the one detection. - The libretranslate-engine README and the s1 TODO describe the new detection. test_gpu_detection walks the real tasks over device, driver and container; against the original 00_core.yml it fails in the driverless-container, driverless-bare and device-and-driver cells. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.4 KiB
Python
96 lines
3.4 KiB
Python
"""Whether a service gets a GPU: it asked for one AND the host carries one.
|
|
|
|
Two facts have to agree and used to be restated at every call site. The
|
|
declaration lives in ``applications`` under ``services.<service>.gpu``; the
|
|
hardware is ``sys-svc-container``'s two stats on the target host: the device
|
|
node ``/dev/nvidia0`` (``sys_svc_container_nvidia_device``) and the driver's
|
|
``nvidia-smi`` (``sys_svc_container_nvidia_driver``). Asking only the
|
|
declaration selects a ``-cuda`` image on a host that has no device, and that
|
|
tag has no arm64 manifest at all, so the build fails with ``no match for
|
|
platform in manifest``. A privileged container sees the host's device node
|
|
without the driver, so the node alone does not make a GPU host either.
|
|
|
|
The stats are target-host facts, so this reads the registers out of the
|
|
play's variables rather than inspecting the controller it runs on.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from ansible.errors import AnsibleError
|
|
from ansible.module_utils.parsing.convert_bool import boolean
|
|
from ansible.plugins.loader import lookup_loader
|
|
from ansible.plugins.lookup import LookupBase
|
|
|
|
from plugins.filter.resource_filter import resource_filter
|
|
|
|
DEVICE_FACT = "sys_svc_container_nvidia_device"
|
|
DRIVER_FACT = "sys_svc_container_nvidia_driver"
|
|
|
|
|
|
def _stat_exists(variables: dict, fact: str) -> bool:
|
|
"""Whether a stat register of the target host found its path.
|
|
|
|
Args:
|
|
variables: the play's variables, carrying the register.
|
|
fact: name of the register.
|
|
|
|
Returns:
|
|
False while the register is absent, which is also the answer before
|
|
``sys-svc-container`` has run.
|
|
"""
|
|
register = variables.get(fact) or {}
|
|
if not isinstance(register, dict):
|
|
return False
|
|
return bool((register.get("stat") or {}).get("exists"))
|
|
|
|
|
|
def _device_present(variables: dict) -> bool:
|
|
"""Whether the target host carries an NVIDIA device node and its driver.
|
|
|
|
Args:
|
|
variables: the play's variables, carrying both registers.
|
|
|
|
Returns:
|
|
True only when both stats found their path.
|
|
"""
|
|
return _stat_exists(variables, DEVICE_FACT) and _stat_exists(variables, DRIVER_FACT)
|
|
|
|
|
|
class LookupModule(LookupBase):
|
|
def run(
|
|
self,
|
|
terms: list[Any],
|
|
variables: dict | None = None,
|
|
**kwargs: Any,
|
|
) -> list[bool]:
|
|
"""Return ``[bool]``: the service is GPU-accelerated on this host.
|
|
|
|
Args:
|
|
terms: ``application_id`` and optionally the service name; the
|
|
service defaults to the application's own entity.
|
|
variables: the play's variables.
|
|
|
|
Raises:
|
|
AnsibleError: wrong number of terms.
|
|
"""
|
|
if not 1 <= len(terms) <= 2:
|
|
raise AnsibleError(
|
|
"gpu: expected 1 or 2 terms: lookup('gpu', application_id[, service])"
|
|
)
|
|
application_id = str(terms[0])
|
|
service_name = str(terms[1]) if len(terms) == 2 else ""
|
|
|
|
templar = getattr(self, "_templar", None)
|
|
variables = variables or getattr(templar, "available_variables", {}) or {}
|
|
|
|
applications = lookup_loader.get(
|
|
"applications", loader=self._loader, templar=templar
|
|
).run([], variables=variables)[0]
|
|
|
|
wanted = resource_filter(
|
|
applications, application_id, "gpu", service_name, False
|
|
)
|
|
return [boolean(wanted, strict=False) and _device_present(variables)]
|