A GPU pod stuck in `Pending` (the scheduler cannot place it on any node), or a card that never shows as schedulable, comes down to one of three causes: the node was never recognized as a GPU node, the driver did not activate, or the pod's request is wrong. Check them in that order. If all three check out and the pod is still `Pending`, no GPU node has a free slice left. See [Is the request well-formed?](#is-the-request-well-formed). ```mermaid flowchart TD A["GPU pod Pending"] --> B{"Node labelled
autopilot.syself.com/gpu=true?"} B -->|No| B1["Card not on allowlist,
no device plugin"] B -->|Yes| C{"nvidia.com/gpu
capacity greater than 0?"} C -->|No| C1["Activation problem,
check gpu-activate"] C -->|Yes| D{"Request nvidia.com/gpu: 1
in limits?"} D -->|No| D1["Fix the request"] D -->|Yes| E{"Free slice on
any GPU node?"} E -->|No| E1["All slices taken,
add a node or free one"] E -->|Yes| F["Pod schedules"] ``` ## Is the node labelled? Syself Autopilot labels a node `autopilot.syself.com/gpu=true` only when its card matches the [supported allowlist](/docs/hetzner/apalla/servers-and-nodes/provision/gpu-nodes) by PCI vendor and device ID. See [supported GPU cards](/docs/hetzner/apalla/servers-and-nodes/provision/gpu-nodes) for the current list. The NVIDIA device plugin is a managed cluster component. It is the piece that tells Kubernetes a node has GPUs, so pods can request them, and it deploys **only** on labelled nodes. No label means no device plugin, and no schedulable GPU. ```console $ kubectl get nodes -L autopilot.syself.com/gpu ``` If the label is missing, the card is not on the allowlist and is treated as a plain display adapter. GPUs are **bare metal only**; there is no Hetzner Cloud GPU type. The label check and the driver-load check are separate. The label matches an exact PCI vendor:device ID against the allowlist above. A card can load its driver without matching, so a loaded driver does not mean the node is labelled or schedulable. So a card can load its driver and still carry no `autopilot.syself.com/gpu` label. `nvidia-smi` working over SSH on the host is not proof the node is schedulable for GPU pods. The label is what the scheduler and the device plugin check. ## Did the driver activate? If the node is labelled but `nvidia.com/gpu` capacity is `0`, or a pod still will not schedule, check the GPU conditions and the activation service. A node condition is a status field Kubernetes keeps on the node object. **`GpuActivationFailed`** means the GPU is present but activation did not finish, so the container runtime cannot use it yet. It only raises a flag; nothing reboots or replaces the node for it. Read the activation logs: ```console $ journalctl -u gpu-activate.service -b ``` **`GpuFallenOffBus`** is a hardware fault, usually from power, heat, or a failing card. Rescheduling and reboots do not fix it, and Syself does not replace the node for it automatically. If you see it, the card needs attention: contact Hetzner to have it checked or swapped. See [Node problem detection and conditions](/docs/hetzner/apalla/servers-and-nodes/access/node-problem-detection-and-conditions) for how to read these. ## Is the request well-formed? Request exactly one GPU per container, in `limits`: ```yaml resources: limits: nvidia.com/gpu: 1 ``` A request for `nvidia.com/gpu: 2` is **rejected**, because the device plugin runs with `failRequestsGreaterThanOne`. One GPU per container is the rule. Check what the node advertises: ```console $ kubectl describe node | grep nvidia.com/gpu ``` > [!NOTE] > `Allocatable: 0` means the device plugin is missing or unhealthy, not that the GPU is busy. That points back to activation, not to your pod. `Capacity` and `Allocatable` show the fixed slice count per node (normally 4, see [When a card is shared](#when-a-card-is-shared)). They do not shrink as pods claim slices, so a nonzero `Allocatable` does not mean a slice is free. If the label, activation, and request all check out and the pod is still `Pending`, every slice on every GPU node is already claimed. Read the `Allocated resources` section further down in the same `describe` output, or list what already runs on the node: ```console $ kubectl describe node $ kubectl get pods -A -o wide --field-selector spec.nodeName= ``` Add another GPU node, or free a slice by removing a pod. ## When a card is shared More than one pod can land on the same card, and co-scheduled pods share its memory with no quota between them. So if a GPU job dies with a CUDA out-of-memory error, check what else is running on the card before you suspect the job. For how slicing and shared memory work, see [Share a GPU across pods](/docs/hetzner/apalla/workloads/specialized/share-a-gpu). To keep a card to a single pod, give the exclusive GPU servers their own pool and steer only that workload to it with a `nodeSelector` (Syself Autopilot does not support node taints). See [Add GPU nodes](/docs/hetzner/apalla/servers-and-nodes/provision/gpu-nodes) and [Node taints are not supported](/docs/hetzner/apalla/servers-and-nodes/scheduling/apply-taints). ## Collecting GPU logs ```console $ kubectl get pods -A -o wide --field-selector spec.nodeName= $ journalctl -u gpu-activate.service -b ``` If a job runs slower than expected, or fails with a CUDA out-of-memory error, the `kubectl get pods` command above shows what else is scheduled on the card. A shared card is the usual cause. From inside a GPU pod, `nvidia-smi` confirms the driver and card are visible. Still stuck once the label, activation, and request all check out? [Add GPU nodes](/docs/hetzner/apalla/servers-and-nodes/provision/gpu-nodes) covers the supported hardware, [Run GPU workloads](/docs/hetzner/apalla/workloads/specialized/run-gpu-workloads) covers scheduling a pod, and [Where to start troubleshooting](/docs/hetzner/apalla/support/where-to-start) covers everything else.