Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions core/core.func
Original file line number Diff line number Diff line change
Expand Up @@ -1856,8 +1856,12 @@ prompt_select() {
return 0
fi

# Check if running in a TTY
if [[ ! -t 0 ]]; then
# The read below takes /dev/tty, not stdin, so a piped or exhausted stdin says
# nothing about whether the operator can answer. Testing -t 0 here meant that
# `curl ... | bash` silently took the default without ever printing the menu --
# which on a mixed-GPU host handed the container the iGPU and never said so.
# Ask whenever a terminal is reachable at all.
if [[ ! -r /dev/tty ]]; then
echo "${options[$((default - 1))]}"
return 0
fi
Expand Down
173 changes: 105 additions & 68 deletions incus/backend.func
Original file line number Diff line number Diff line change
Expand Up @@ -654,80 +654,117 @@ incus_configure_device_passthrough() {
fi

if [[ "$gpu_count" -gt 0 ]]; then
selected_gpu="${available_gpus[0]}"
# Several types can be attached together, and picking exactly one used to
# default to the first -- always INTEL, by collection order. On a host with
# an iGPU beside a discrete card that silently attached the iGPU only.
local -a selected_types=("${available_gpus[@]}")
if [[ "$gpu_count" -gt 1 ]]; then
if declare -f prompt_select >/dev/null 2>&1; then
selected_gpu=$(prompt_select "Which GPU type to passthrough?" 1 60 "${available_gpus[@]}")
echo -e "\n${INFO} Multiple GPU types detected:"
local gi
for gi in "${!available_gpus[@]}"; do
echo " $((gi + 1))) ${available_gpus[$gi]}"
done
echo " A) Pass through ALL of them"

local type_selection=""
read -r -t 60 -p "Select GPU type(s) (1-${gpu_count}, comma-separated, A=all) [timeout 60s, default=all]: " type_selection </dev/tty || type_selection=""
type_selection="${type_selection^^}"

if [[ -n "$type_selection" && "$type_selection" != "A" ]]; then
local -a gnums=()
local gnum
IFS=',' read -ra gnums <<<"$type_selection"
selected_types=()
for gnum in "${gnums[@]}"; do
gnum="${gnum// /}"
if [[ "$gnum" =~ ^[0-9]+$ ]] && ((gnum >= 1 && gnum <= gpu_count)); then
selected_types+=("${available_gpus[$((gnum - 1))]}")
fi
done
# A spare device is harmless; a missing one is the bug being fixed.
if [[ ${#selected_types[@]} -eq 0 ]]; then
msg_warn "No valid selection - passing through all detected GPUs"
selected_types=("${available_gpus[@]}")
fi
fi
fi
selected_gpu="${selected_gpu^^}"

local sel added=0 failed=0 idx=0
case "$selected_gpu" in
INTEL)
for sel in "${INTEL_GPUS[@]}"; do
if _incus_add_gpu_physical "gpu${idx}" "$sel"; then
added=$((added + 1))
else
failed=$((failed + 1))
msg_warn "Could not add Intel GPU (${sel})"

# idx is shared across types so the device names stay unique.
local sel added failed idx=0
local -a configured_types=()
for selected_gpu in "${selected_types[@]}"; do
added=0
failed=0
case "$selected_gpu" in
INTEL)
for sel in "${INTEL_GPUS[@]}"; do
if _incus_add_gpu_physical "gpu${idx}" "$sel"; then
added=$((added + 1))
else
failed=$((failed + 1))
msg_warn "Could not add Intel GPU (${sel})"
fi
idx=$((idx + 1))
done
# Vendor fallback if PCI/id adds failed entirely
if [[ "$added" -eq 0 ]]; then
_incus_add_gpu_physical "gpu0" "vendorid=8086" && added=1 || true
fi
idx=$((idx + 1))
done
# Vendor fallback if PCI/id adds failed entirely
if [[ "$added" -eq 0 ]]; then
_incus_add_gpu_physical "gpu0" "vendorid=8086" && added=1 || true
fi
if [[ "$added" -gt 0 ]]; then
export GPU_TYPE="INTEL"
msg_ok "Intel GPU passthrough configured (${added})"
fi
;;
AMD)
for sel in "${AMD_GPUS[@]}"; do
if _incus_add_gpu_physical "gpu${idx}" "$sel"; then
added=$((added + 1))
else
failed=$((failed + 1))
msg_warn "Could not add AMD GPU (${sel})"
if [[ "$added" -gt 0 ]]; then
configured_types+=("INTEL")
msg_ok "Intel GPU passthrough configured (${added})"
fi
idx=$((idx + 1))
done
if [[ "$added" -eq 0 ]]; then
_incus_add_gpu_physical "gpu0" "vendorid=1002" && added=1 || true
fi
if [[ "$added" -gt 0 ]]; then
export GPU_TYPE="AMD"
msg_ok "AMD GPU passthrough configured (${added})"
fi
;;
NVIDIA)
for sel in "${NVIDIA_GPUS[@]}"; do
if _incus_add_gpu_physical "gpu${idx}" "$sel"; then
added=$((added + 1))
else
failed=$((failed + 1))
msg_warn "Could not add NVIDIA GPU (${sel})"
;;
AMD)
for sel in "${AMD_GPUS[@]}"; do
if _incus_add_gpu_physical "gpu${idx}" "$sel"; then
added=$((added + 1))
else
failed=$((failed + 1))
msg_warn "Could not add AMD GPU (${sel})"
fi
idx=$((idx + 1))
done
if [[ "$added" -eq 0 ]]; then
_incus_add_gpu_physical "gpu0" "vendorid=1002" && added=1 || true
fi
idx=$((idx + 1))
done
if [[ "$added" -eq 0 ]]; then
_incus_add_gpu_physical "gpu0" "vendorid=10de" && added=1 || true
fi
# Optional NVIDIA char nodes (driver interface)
local ndev nidx=0
for ndev in "${NVIDIA_CHAR_DEVS[@]}"; do
local nname
nname=$(basename "$ndev" | tr -c 'A-Za-z0-9' '_')
incus config device add "${CT_NAME}" "nvidia_${nname}_${nidx}" unix-char "source=${ndev}" "path=${ndev}" >>"${LOGFILE:-$INCUS_BUILD_LOG}" 2>&1 || true
nidx=$((nidx + 1))
done
if [[ "$added" -gt 0 || "$nidx" -gt 0 ]]; then
export GPU_TYPE="NVIDIA"
msg_ok "NVIDIA GPU passthrough configured (gpu=${added}, char=${nidx})"
fi
;;
esac
if [[ "$added" -gt 0 ]]; then
configured_types+=("AMD")
msg_ok "AMD GPU passthrough configured (${added})"
fi
;;
NVIDIA)
for sel in "${NVIDIA_GPUS[@]}"; do
if _incus_add_gpu_physical "gpu${idx}" "$sel"; then
added=$((added + 1))
else
failed=$((failed + 1))
msg_warn "Could not add NVIDIA GPU (${sel})"
fi
idx=$((idx + 1))
done
if [[ "$added" -eq 0 ]]; then
_incus_add_gpu_physical "gpu0" "vendorid=10de" && added=1 || true
fi
# Optional NVIDIA char nodes (driver interface)
local ndev nidx=0
for ndev in "${NVIDIA_CHAR_DEVS[@]}"; do
local nname
nname=$(basename "$ndev" | tr -c 'A-Za-z0-9' '_')
incus config device add "${CT_NAME}" "nvidia_${nname}_${nidx}" unix-char "source=${ndev}" "path=${ndev}" >>"${LOGFILE:-$INCUS_BUILD_LOG}" 2>&1 || true
nidx=$((nidx + 1))
done
if [[ "$added" -gt 0 || "$nidx" -gt 0 ]]; then
configured_types+=("NVIDIA")
msg_ok "NVIDIA GPU passthrough configured (gpu=${added}, char=${nidx})"
fi
;;
esac
done

# Space separated, so a reader can test for one type without caring how
# many others came with it.
export GPU_TYPE="${configured_types[*]}"

if [[ -z "${GPU_TYPE:-}" ]]; then
msg_warn "GPU passthrough requested but no device could be attached (check permissions / incus info --resources)"
Expand Down
22 changes: 20 additions & 2 deletions lib/hwaccel.func
Original file line number Diff line number Diff line change
Expand Up @@ -867,8 +867,26 @@ NVIDIA_PIN
# ldconfig rather than dpkg: the library can arrive by bind mount too, and what
# matters is whether a CUDA program can link it. Not a non-zero return, since
# acceleration is optional and install scripts run under an ERR trap.
if ldconfig -p 2>/dev/null | grep -q 'libcuda\.so\.1'; then
msg_ok "NVIDIA GPU configured (host driver ${nvidia_host_version})"
# Libraries alone are not enough, and this is the half that used to be missed:
# lspci sees every GPU on the host whether or not it was passed through, so
# this function will happily install the userspace stack for a card the
# container cannot reach. That reported success while torch.cuda.is_available()
# stayed False, and the real fault -- no device nodes -- went unmentioned.
local -a nvidia_nodes=()
local _nvdev
for _nvdev in /dev/nvidia*; do
[[ -c "$_nvdev" ]] && nvidia_nodes+=("$_nvdev")
done

local have_libcuda=0
ldconfig -p 2>/dev/null | grep -q 'libcuda\.so\.1' && have_libcuda=1

if [[ $have_libcuda -eq 1 && ${#nvidia_nodes[@]} -gt 0 ]]; then
msg_ok "NVIDIA GPU configured (host driver ${nvidia_host_version}, ${#nvidia_nodes[@]} device nodes)"
elif [[ ${#nvidia_nodes[@]} -eq 0 ]]; then
msg_error "NVIDIA GPU not usable: no /dev/nvidia* device nodes in this container"
msg_warn "The userspace driver is in place, but the devices were never passed through."
msg_warn "On the host, check the container config lists /dev/nvidia0, /dev/nvidiactl and /dev/nvidia-uvm."
else
msg_error "NVIDIA GPU not usable: libcuda.so.1 is not available in this container"
msg_warn "The host driver is ${nvidia_host_version}. CUDA workloads will fail until a matching userspace library is present."
Expand Down
Loading
Loading