Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
c8cebed
feat(installer): add guarded GPU access policy
MioYuuIH Jul 28, 2026
c6701a7
feat(installer): classify local GPU hardware
MioYuuIH Jul 28, 2026
3e2bf6b
feat(installer): wire GPU access into workflows
MioYuuIH Jul 28, 2026
0ea9978
feat(installer): emit resolved render GID overlays
MioYuuIH Jul 28, 2026
50ecc64
feat(chart): model GPU render GID
MioYuuIH Jul 28, 2026
2432536
fix(runtime): separate storage and GPU groups
MioYuuIH Jul 28, 2026
cff8981
feat(hub): enforce GPU render group policy
MioYuuIH Jul 28, 2026
3489075
fix(images): remove embedded GPU permission policy
MioYuuIH Jul 28, 2026
5176884
feat(ansible): define canonical GPU access contract
MioYuuIH Jul 28, 2026
5911029
feat(ansible): apply verified GPU device policy
MioYuuIH Jul 28, 2026
f22777a
feat(ansible): integrate unified GPU access role
MioYuuIH Jul 28, 2026
132917e
feat(deploy): add validated config generation primitives
MioYuuIH Jul 28, 2026
e1e8382
feat(deploy): publish generated artifacts atomically
MioYuuIH Jul 28, 2026
1333e15
feat(deploy): resolve unanimous fleet GPU policy
MioYuuIH Jul 28, 2026
d41f301
feat(deploy): discover fleet GPU access state
MioYuuIH Jul 28, 2026
0f5a1cb
feat(deploy): stage atomic PXE GPU finalization
MioYuuIH Jul 28, 2026
27f36f1
feat(deploy): generate and validate GPU artifacts
MioYuuIH Jul 28, 2026
09c10f0
feat(ansible): admit and finalize PXE GPU rootfs
MioYuuIH Jul 28, 2026
a45d2e8
docs(deploy): document unified GPU permission flow
MioYuuIH Jul 28, 2026
5fa4856
docs(deploy): align automatic GPU workflow references
MioYuuIH Jul 28, 2026
c781cb5
fix(tests): isolate GPU role skill dependencies
MioYuuIH Jul 28, 2026
db73537
refactor(hub): remove GPU group injection
MioYuuIH Jul 28, 2026
3f2c322
refactor(chart): remove GPU GID settings
MioYuuIH Jul 28, 2026
075f29d
fix(runtime): keep storage group only
MioYuuIH Jul 28, 2026
ee6c31c
refactor(installer): simplify GPU host policy
MioYuuIH Jul 28, 2026
d5e373c
refactor(installer): remove GPU GID workflow
MioYuuIH Jul 28, 2026
3bc557f
refactor(installer): remove GPU GID overlays
MioYuuIH Jul 28, 2026
e5252db
refactor(ansible): simplify GPU access role
MioYuuIH Jul 28, 2026
b1fd9a3
refactor(ansible): simplify PXE GPU policy
MioYuuIH Jul 28, 2026
117b43b
refactor(deploy): simplify GPU discovery
MioYuuIH Jul 28, 2026
f217d1f
refactor(deploy): remove draft GPU inputs
MioYuuIH Jul 28, 2026
2d28d6c
refactor(deploy): render boolean GPU policy
MioYuuIH Jul 28, 2026
709ee3f
refactor(deploy): simplify GPU manifests
MioYuuIH Jul 28, 2026
131f2f1
refactor(deploy): remove PXE finalizer
MioYuuIH Jul 28, 2026
0d09573
docs: update installer stages
MioYuuIH Jul 28, 2026
d9d973d
docs(deploy): document GPU infrastructure contract
MioYuuIH Jul 28, 2026
e74c40d
docs(ansible): document GID-free policy
MioYuuIH Jul 28, 2026
a87aba0
docs(k8s): require external GPU device management
MioYuuIH Jul 28, 2026
8c3ba1f
docs(skills): align GPU deployment workflow
MioYuuIH Jul 28, 2026
c64b8cd
docs(deploy): restore GPU setup commands
MioYuuIH Jul 28, 2026
1ca3cb7
refactor(installer): install AMD GPU udev package
MioYuuIH Jul 28, 2026
2f87d33
refactor(installer): pass GPU package context
MioYuuIH Jul 28, 2026
36dc762
feat(installer): bundle AMD GPU udev package
MioYuuIH Jul 28, 2026
be284a6
refactor(ansible): install AMD GPU udev package
MioYuuIH Jul 28, 2026
4e15da9
fix(pxe): install GPU udev package safely
MioYuuIH Jul 28, 2026
04d8ee3
docs: describe AMD GPU udev package
MioYuuIH Jul 28, 2026
5e898d9
docs(deploy): document AMD GPU udev package
MioYuuIH Jul 28, 2026
3cf219b
docs(skills): align AMD GPU package workflow
MioYuuIH Jul 28, 2026
544e29c
feat(deploy): validate direct SSH inventory
MioYuuIH Jul 28, 2026
0e513f4
fix(ansible): require explicit GPU access flags
MioYuuIH Jul 28, 2026
61a49a5
docs(deploy): restore direct SSH workflow
MioYuuIH Jul 28, 2026
dae1f1e
docs(skills): retain generated deployment workflow
MioYuuIH Jul 28, 2026
ee7ac6e
fix(validation): allow auto only for direct SSH inventory
MioYuuIH Jul 29, 2026
6875615
feat(ansible): resolve automatic GPU access
MioYuuIH Jul 29, 2026
8853384
refactor(deploy): reuse GPU sysfs detection
MioYuuIH Jul 29, 2026
f39f890
test(deploy): preserve generated boolean policies
MioYuuIH Jul 29, 2026
5431365
docs(deploy): document automatic GPU detection
MioYuuIH Jul 29, 2026
f47b5b2
docs(ansible): document automatic GPU policy
MioYuuIH Jul 29, 2026
e38dd7d
docs(skills): distinguish direct and generated GPU policy
MioYuuIH Jul 29, 2026
d64b48d
test(installer): reduce GPU orchestration coverage
MioYuuIH Jul 29, 2026
31afced
test(installer): consolidate GPU package safety coverage
MioYuuIH Jul 29, 2026
5c1dbd9
test(deploy): trim generated config validation coverage
MioYuuIH Jul 29, 2026
21e775a
test(deploy): consolidate GPU artifact policy coverage
MioYuuIH Jul 29, 2026
7259d4e
test(ansible): narrow GPU role contract coverage
MioYuuIH Jul 29, 2026
8f1bf45
test(image): focus GPU permission ownership contract
MioYuuIH Jul 29, 2026
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
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,15 @@ A successful install looks like this:

```text
This operation needs root privileges. Requesting sudo password...
✓ [1/8] Detecting GPU (0.2s)
✓ [2/8] Generating values overlay (initial) (0.0s)
✓ [3/8] Installing helm + k9s (0.0s)
✓ [4/8] Installing K3s (single-node) (3.8s)
✓ [5/8] Pulling custom + external images (25.0s)
✓ [6/8] Deploying ROCm GPU device plugin + node labeller (0.2s)
✓ [7/8] Refreshing values overlay from node labels (0.2s)
✓ [8/8] Deploying JupyterHub runtime (helm install + wait) (9.2s)
✓ [1/9] Detecting GPU (0.2s)
✓ [2/9] Provisioning GPU device access (0.1s)
✓ [3/9] Generating values overlay (initial) (0.0s)
✓ [4/9] Installing helm + k9s (0.0s)
✓ [5/9] Installing K3s (single-node) (3.8s)
✓ [6/9] Pulling custom + external images (25.0s)
✓ [7/9] Deploying ROCm GPU device plugin + node labeller (0.2s)
✓ [8/9] Refreshing values overlay from node labels (0.2s)
✓ [9/9] Deploying JupyterHub runtime (helm install + wait) (9.2s)

_ _ _ ____ _ _ ____ _ _
/ \ | | | | _ \ | | ___ __ _ _ __ _ __ (_)_ __ __ _ / ___| | ___ _ _ __| |
Expand All @@ -111,6 +112,13 @@ This operation needs root privileges. Requesting sudo password...
kubectl is configured at $HOME/.kube/config; try `kubectl get nodes`
```

The GPU access stage installs AMD's `amdgpu-insecure-instinct-udev-rules`
package, pinned to `30.30.4.0-2341068.24.04`. It sets mode `0666` only on
`/dev/kfd` and DRM `renderD*` nodes; `card*` keeps the normal system policy. The
device plugin remains a separate allocation layer, and the tested ROCm compute
path needs no supplemental GPU group. The offline `pack` bundle carries the
pinned deb for installation without network access.

See the full guide at [Quick Start](https://amdresearch.github.io/aup-learning-cloud/installation/quick-start.html) and [Single-Node Deployment](https://amdresearch.github.io/aup-learning-cloud/installation/single-node.html).

### Uninstall
Expand Down
44 changes: 36 additions & 8 deletions auplc_installer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
import time
from collections.abc import Sequence
from pathlib import Path
from typing import NoReturn

from auplc_installer import __version__
from auplc_installer.catalog import parse_selection_spec
from auplc_installer.gpu import (
detect_and_configure_gpu,
refine_gpu_config_from_node_labels,
)
from auplc_installer.gpu_access import provision_gpu_access
from auplc_installer.gpu_hardware import GpuHardware, classify_gpu_hardware
from auplc_installer.helm import (
deploy_runtime,
dev_quick_rollout,
Expand Down Expand Up @@ -319,6 +322,22 @@ def cmd_install(state: InstallerState, *, pull: bool) -> None:
keepalive.stop()


def _raise_unreachable_gpu_hardware(hardware: GpuHardware) -> NoReturn:
raise AssertionError(f"Unhandled GPU hardware classification: {hardware!r}")


def _provision_gpu_access_for_local_hardware(*, offline_mode: bool, bundle_dir: Path | None) -> None:
match classify_gpu_hardware():
case GpuHardware.GPU:
provision_gpu_access(offline_mode=offline_mode, bundle_dir=bundle_dir)
case GpuHardware.CPU:
return
case GpuHardware.UNKNOWN:
raise InstallerError("Could not determine local AMD GPU hardware; refusing to modify installer state")
case unreachable:
_raise_unreachable_gpu_hardware(unreachable)


def _cmd_install_inner(state: InstallerState, *, pull: bool) -> None:
"""Body of ``cmd_install`` after sudo session has been primed."""
# Pre-compute the image-stage label so the user knows up-front which path
Expand All @@ -330,13 +349,16 @@ def _cmd_install_inner(state: InstallerState, *, pull: bool) -> None:
else:
image_stage_label = "Pulling external images & building custom images"

total = 8
total = 9

with stage("Detecting GPU", idx=1, total=total):
detect_and_configure_gpu(state.gpu, gpu_type_override=state.gpu_type)

with stage("Provisioning GPU device access", idx=2, total=total):
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
paths = state.runtime_paths()

with stage("Generating values overlay (initial)", idx=2, total=total):
with stage("Generating values overlay (initial)", idx=3, total=total):
# First pass: use local detection so image pulls / builds get the
# right GPU_TARGET. Overlay is regenerated again below from
# labeller-published labels.
Expand All @@ -349,18 +371,18 @@ def _cmd_install_inner(state: InstallerState, *, pull: bool) -> None:
overlay_path=paths.overlay_path,
)

with stage("Installing helm + k9s", idx=3, total=total):
with stage("Installing helm + k9s", idx=4, total=total):
install_tools(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)

with stage("Installing K3s (single-node)", idx=4, total=total):
with stage("Installing K3s (single-node)", idx=5, total=total):
install_k3s_single_node(
offline_mode=state.offline_mode,
bundle_dir=state.bundle_dir,
use_docker=state.use_docker,
mirror_prefix=state.mirror_prefix,
)

with stage(image_stage_label, idx=5, total=total):
with stage(image_stage_label, idx=6, total=total):
if state.offline_mode and state.bundle_dir is not None:
load_offline_images(state.bundle_dir)
elif pull:
Expand Down Expand Up @@ -397,13 +419,13 @@ def _cmd_install_inner(state: InstallerState, *, pull: bool) -> None:
k3s_images_dir=state.k3s_images_dir,
)

with stage("Deploying ROCm GPU device plugin + node labeller", idx=6, total=total):
with stage("Deploying ROCm GPU device plugin + node labeller", idx=7, total=total):
deploy_rocm_gpu_device_plugin(
offline_mode=state.offline_mode,
bundle_dir=state.bundle_dir,
)

with stage("Refreshing values overlay from node labels", idx=7, total=total):
with stage("Refreshing values overlay from node labels", idx=8, total=total):
refine_gpu_config_from_node_labels(state.gpu)
generate_values_overlay(
state.gpu,
Expand All @@ -414,7 +436,7 @@ def _cmd_install_inner(state: InstallerState, *, pull: bool) -> None:
overlay_path=paths.overlay_path,
)

with stage("Deploying JupyterHub runtime (helm install + wait)", idx=8, total=total):
with stage("Deploying JupyterHub runtime (helm install + wait)", idx=9, total=total):
deploy_runtime(paths)

_print_success_banner()
Expand Down Expand Up @@ -582,6 +604,7 @@ def cmd_dev_quick(state: InstallerState) -> None:


def cmd_dev_deploy(state: InstallerState) -> None:
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
detect_and_configure_gpu(state.gpu, gpu_type_override=state.gpu_type)
paths = state.runtime_paths()
refine_gpu_config_from_node_labels(state.gpu)
Expand All @@ -597,6 +620,7 @@ def cmd_dev_deploy(state: InstallerState) -> None:


def cmd_dev_upgrade(state: InstallerState) -> None:
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
detect_and_configure_gpu(state.gpu, gpu_type_override=state.gpu_type)
paths = state.runtime_paths()
refine_gpu_config_from_node_labels(state.gpu)
Expand All @@ -613,6 +637,7 @@ def cmd_dev_upgrade(state: InstallerState) -> None:


def cmd_dev_reinstall(state: InstallerState) -> None:
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
with contextlib.suppress(InstallerError):
remove_runtime()
time.sleep(0.5)
Expand All @@ -623,6 +648,7 @@ def cmd_dev_reinstall(state: InstallerState) -> None:


def cmd_rt_install(state: InstallerState) -> None:
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
detect_and_configure_gpu(state.gpu, gpu_type_override=state.gpu_type)
paths = state.runtime_paths()
refine_gpu_config_from_node_labels(state.gpu)
Expand All @@ -638,6 +664,7 @@ def cmd_rt_install(state: InstallerState) -> None:


def cmd_rt_upgrade(state: InstallerState) -> None:
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
detect_and_configure_gpu(state.gpu, gpu_type_override=state.gpu_type)
paths = state.runtime_paths()
refine_gpu_config_from_node_labels(state.gpu)
Expand Down Expand Up @@ -678,6 +705,7 @@ def cmd_rt_remove(state: InstallerState) -> None:


def cmd_rt_reinstall(state: InstallerState) -> None:
_provision_gpu_access_for_local_hardware(offline_mode=state.offline_mode, bundle_dir=state.bundle_dir)
with contextlib.suppress(InstallerError):
remove_runtime()
time.sleep(0.5)
Expand Down
Loading
Loading