feat(toolboxes): implement reproducible gufo toolboxes for Strix Halo

This commit is contained in:
Federico Izzo committed 2026-08-27 17:00:52 +02:00
commit ea7a8e0d91
13 files changed
+1073

No files matched your search

+92
View File
@@ -0,0 +1,92 @@
name: Build & Publish Gufo Toolboxes
on:
push:
branches:
- main
workflow_dispatch:
inputs:
images:
description: 'Toolboxes to build ("gufo-runtime", "gufo-dev", or "all")'
required: false
default: 'all'
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_PREFIX: ghcr.io/${{ github.repository }}
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
matrix_json: ${{ steps.matrix.outputs.matrix_json }}
steps:
- id: matrix
shell: bash
run: |
IN='${{ github.event.inputs.images || "all" }}'
if [[ "$IN" == "all" || -z "$IN" ]]; then
echo 'matrix_json=["gufo-runtime","gufo-dev"]' >> "$GITHUB_OUTPUT"
else
IN_CLEAN=$(echo "$IN" | tr -d '[:space:]')
echo "matrix_json=[\"${IN_CLEAN//,/\",\"}\"]" >> "$GITHUB_OUTPUT"
fi
build-and-push:
needs: prepare
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target: ${{ fromJson(needs.prepare.outputs.matrix_json) }}
steps:
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache
sudo docker system prune --all --force
df -h
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
extra_nix_config: |
experimental-features = nix-command flakes
accept-flake-config = true
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build OCI image with Nix
run: |
echo "🔨 Building ${{ matrix.target }}-image with Nix..."
nix build .#packages.x86_64-linux.${{ matrix.target }}-image --out-link result-${{ matrix.target }}
- name: Load and Push image to GHCR
run: |
set -euo pipefail
BUILD_TS=$(date +%Y%m%dT%H%M%S)
IMAGE_NAME="${{ env.IMAGE_PREFIX }}/${{ matrix.target }}"
echo "📥 Loading image into Docker daemon..."
docker load < result-${{ matrix.target }}
echo "🏷️ Tagging image..."
docker tag "${IMAGE_NAME}:latest" "${IMAGE_NAME}:${BUILD_TS}"
echo "🚀 Pushing latest..."
docker push "${IMAGE_NAME}:latest"
echo "🚀 Pushing immutable timestamp tag ${BUILD_TS}..."
docker push "${IMAGE_NAME}:${BUILD_TS}"
+24
View File
@@ -0,0 +1,24 @@
name: Prune Old Container Images
on:
schedule:
# Run once a week at midnight Sunday
- cron: '0 0 * * 0'
workflow_dispatch:
permissions:
packages: write
jobs:
prune:
runs-on: ubuntu-latest
steps:
- name: Prune old container versions in GHCR
uses: snok/container-retention-policy@v3
with:
image-names: "gufo-runtime,gufo-dev"
cut-off: 30d
account-type: org
org-name: gufo-org
keep-at-least: 3
token: ${{ secrets.GITHUB_TOKEN }}
+4
View File
@@ -0,0 +1,4 @@
result
result-*
.direnv/
amd-strix-halo-toolboxes/
+102
View File
@@ -0,0 +1,102 @@
# Gufo Toolboxes for AMD Strix Halo
[![CI](https://github.com/gufo-org/toolboxes/actions/workflows/build_and_publish.yml/badge.svg)](https://github.com/gufo-org/toolboxes/actions/workflows/build_and_publish.yml)
Reproducible container toolboxes for local LLM inference, serving, development, and profiling on **AMD Ryzen AI Max ("Strix Halo")** systems (`gfx1151` RDNA 3.5 GPU + XDNA2 NPU).
Built directly from **Nix flake derivations** to guarantee bit-for-bit reproducibility and optimal layered caching across Fedora (Toolbx), Ubuntu (Distrobox), and Arch Linux hosts.
---
## 📦 Supported Toolboxes
| Container Name | Image (GHCR) | Description |
| :--- | :--- | :--- |
| **`gufo-runtime`** | `ghcr.io/gufo-org/toolboxes/gufo-runtime:latest` | Lightweight production inference runtime for `gufo serve`, `gufo prompt`, `gufo bench`, and `gufo diagnose`. Includes ROCm `gfx1151` and XRT/XDNA2 runtimes. |
| **`gufo-dev`** | `ghcr.io/gufo-org/toolboxes/gufo-dev:latest` | Interactive development & profiling container. Includes full ROCm SDK, HIP compiler, `rocprofv3`, Clang tools, CMake, and Ninja for kernel development. |
---
## 🚀 Quick Start
### 1. Requirements
- Linux kernel `>= 6.18.4` (see [Host Configuration](docs/host-configuration.md))
- User in `video` and `render` groups (`sudo usermod -aG video,render $USER`)
- `podman` + `toolbox` (Fedora) or `distrobox` (Ubuntu/Debian/Arch)
### 2. Create and Enter Toolbox
Use the provided `refresh-toolboxes.sh` script, which automatically configures Strix Halo hardware passthroughs (`/dev/kfd`, `/dev/dri`, `/dev/accel*`):
```bash
# Clone the repository
git clone https://github.com/gufo-org/toolboxes.git
cd toolboxes
# Create the runtime toolbox
./refresh-toolboxes.sh gufo-runtime
# Enter the toolbox
toolbox enter gufo-runtime # on Fedora
# OR
distrobox enter gufo-runtime # on Ubuntu / Arch
```
### 3. Verify Hardware & Run Inference
Inside the toolbox:
```bash
# Check GPU and NPU hardware status
gufo diagnose
# Run an interactive prompt
gufo prompt --model /path/to/model.gguf -p "What is AMD Strix Halo unified memory architecture?"
# Benchmark token generation
gufo bench --model /path/to/model.gguf -p 512 -n 128
```
---
## 🛠️ Maintainers: Building Images from Source (Nix)
For maintainers or developers who want to rebuild the container images from source using Nix:
```bash
# Stream the OCI image directly into local Podman/Docker:
nix run .#stream-gufo-runtime | podman load
# Or build the image tarball:
nix build .#packages.x86_64-linux.gufo-runtime-image
```
---
## ⚙️ Background Serving (Systemd)
Run `gufo serve` as a persistent background user service:
```bash
mkdir -p ~/.config/systemd/user
cp systemd/gufo-serve.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user start gufo-serve
systemctl --user enable gufo-serve
```
See [docs/systemd.md](docs/systemd.md) for full configuration options.
---
## 📚 Documentation
- [Quick Start Guide](docs/quickstart.md)
- [Host Configuration (Kernel, Firmware, Groups)](docs/host-configuration.md)
- [Systemd Integration](docs/systemd.md)
---
## 📜 License
Apache-2.0.
+61
View File
@@ -0,0 +1,61 @@
# Host Configuration Guide for AMD Strix Halo
This document details host-level configuration for running Gufo Toolboxes reliably on AMD Ryzen AI Max ("Strix Halo", `gfx1151` GPU + XDNA2 NPU).
---
## 1. Kernel and Firmware Requirements
### Recommended Setup
- **Linux Kernel**: `>= 6.18.4` (e.g. `6.18.9+`). Kernels older than 6.18.4 exhibit stability bugs and crashes under sustained ROCm compute on gfx1151.
- **Linux Firmware**: `20260110` or later.
- **Avoid**: `linux-firmware-20251125` (breaks ROCm initialization on gfx1151).
### Kernel Boot Parameters
Add the following parameters to your bootloader (e.g. GRUB or systemd-boot):
```text
amdgpu.gttsize=65536
```
*(On 128 GiB unified memory systems, this ensures sufficient GTT allocation for large model weights and KV caches).*
---
## 2. Device Permissions and User Groups
Ensure your host user account belongs to the required hardware access groups:
```bash
sudo usermod -aG video,render $USER
```
Verify device existence:
```bash
# GPU ROCm / KFD interface
ls -l /dev/kfd /dev/dri/renderD*
# XDNA2 NPU interface
ls -l /dev/accel/accel* /dev/amdxdna 2>/dev/null || echo "NPU driver not loaded"
```
---
## 3. Ulimits (Locked Memory)
Large unified memory buffers require unlimited memlock limits. In `/etc/security/limits.d/99-gufo.conf`:
```text
* soft memlock unlimited
* hard memlock unlimited
```
---
## 4. Power Profiles
For sustained LLM inference on Strix Halo laptops or mini PCs, switch power profiles to `throughput-performance`:
```bash
sudo tuned-adm profile throughput-performance
```
*(Or install the provided `systemd/gpu-workload-watch` service to automate this on GPU activity).*
+95
View File
@@ -0,0 +1,95 @@
# Gufo Toolboxes Quick Start
This guide gets you up and running with **Gufo Toolboxes** on AMD Ryzen AI Max ("Strix Halo") systems featuring the `gfx1151` RDNA 3.5 GPU and XDNA2 NPU.
---
## 1. Prerequisites
- **Host OS**: Fedora 42/43/44, Ubuntu 24.04+, Arch Linux, or openSUSE Tumbleweed.
- **Kernel**: Linux kernel >= `6.18.4` (older kernels have known gfx1151 stability issues).
- **Firmware**: Avoid `linux-firmware-20251125` due to known ROCm regressions.
- **Container Tooling**:
- Fedora: `toolbox` and `podman`
- Ubuntu / Debian / Arch: `distrobox` and `podman`
---
## 2. Choosing a Toolbox
| Toolbox | Image | Use Case |
| :--- | :--- | :--- |
| **`gufo-runtime`** | `ghcr.io/gufo-org/toolboxes/gufo-runtime:latest` | Lightweight production inference & model serving (`gufo serve`, `gufo prompt`, `gufo bench`) |
| **`gufo-dev`** | `ghcr.io/gufo-org/toolboxes/gufo-dev:latest` | Development, C++/HIP kernel tuning, and profiling (`rocprofv3`, Clang, CMake, Ninja) |
---
## 3. Creating and Entering the Toolbox
### Using `refresh-toolboxes.sh` (Recommended)
The included `refresh-toolboxes.sh` script automatically detects your OS (`toolbox` or `distrobox`) and configures all Strix Halo GPU (`/dev/kfd`, `/dev/dri`) and NPU (`/dev/accel*`) passthroughs:
```bash
# Pull & create gufo-runtime
./refresh-toolboxes.sh gufo-runtime
# Or create the development toolbox
./refresh-toolboxes.sh gufo-dev
# Or build the image locally from Nix without pulling from GHCR
./refresh-toolboxes.sh --local gufo-runtime
```
### Manual Creation
#### Fedora (`toolbox`)
```bash
toolbox create gufo-runtime \
--image ghcr.io/gufo-org/toolboxes/gufo-runtime:latest \
-- --device /dev/dri --device /dev/kfd --device /dev/accel/accel0 \
--group-add video --group-add render --group-add sudo \
--security-opt seccomp=unconfined --ulimit memlock=-1
toolbox enter gufo-runtime
```
#### Ubuntu / Debian / Arch (`distrobox`)
```bash
distrobox create \
--name gufo-runtime \
--image ghcr.io/gufo-org/toolboxes/gufo-runtime:latest \
--additional-flags "--device /dev/dri --device /dev/kfd --device /dev/accel/accel0 --group-add video --group-add render --group-add sudo --security-opt seccomp=unconfined --ulimit memlock=-1"
distrobox enter gufo-runtime
```
---
## 4. Verifying Hardware Acceleration
Inside the toolbox, run:
```bash
# Verify GPU / NPU hardware diagnostics
gufo diagnose
```
---
## 5. Running Inference & Server
### Interactive Prompt
```bash
gufo prompt --model /path/to/model.gguf -p "Explain quantum computing in simple terms."
```
### Benchmark
```bash
gufo bench --model /path/to/model.gguf -p 512 -n 128
```
### Server
```bash
gufo serve --model /path/to/model.gguf --host 0.0.0.0 --port 8080
```
+56
View File
@@ -0,0 +1,56 @@
# Systemd Integration for Gufo Server
You can run `gufo serve` as a background systemd user service via Podman.
---
## 1. Directory Setup
Create the local models folder on your host:
```bash
mkdir -p ~/.local/share/gufo/models
```
Place your model GGUF file inside:
```bash
cp /path/to/my-model.gguf ~/.local/share/gufo/models/model.gguf
```
---
## 2. Installing the Service
Copy the systemd service file into your user services directory:
```bash
mkdir -p ~/.config/systemd/user/
cp systemd/gufo-serve.service ~/.config/systemd/user/
```
Reload the systemd user daemon:
```bash
systemctl --user daemon-reload
```
---
## 3. Managing the Service
```bash
# Start the server
systemctl --user start gufo-serve
# Enable on boot (persistent login session)
systemctl --user enable gufo-serve
loginctl enable-linger $USER
# Check status and logs
systemctl --user status gufo-serve
journalctl --user -u gufo-serve -f
# Stop the server
systemctl --user stop gufo-serve
```
The server will be reachable on `http://127.0.0.1:8080`.
Generated
+62
View File
@@ -0,0 +1,62 @@
{
"nodes": {
"gufo-engine": {
"inputs": {
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1787839541,
"narHash": "sha256-yHvZodZEC0v2hL9AQEaw4XFXzexMeMT7uT/yu69JK6w=",
"ref": "refs/heads/main",
"rev": "81ba5df82e2d57dd899f53c0f1aaf5e98fd8cbc9",
"revCount": 200,
"type": "git",
"url": "ssh://[email protected]/francescobozzo/strix-halo.cpp.git"
},
"original": {
"type": "git",
"url": "ssh://[email protected]/francescobozzo/strix-halo.cpp.git"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1786384358,
"narHash": "sha256-RzPPiWeUtuvymnpuEWsdtzli5w4kjZs49FqEs3/1u+I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2fcb964de67fcf60b43471c55d5d99e61a9ccb5a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1787498568,
"narHash": "sha256-9i/VTdusq/+NM/tz+J1Re+ojkMB8MBf0QshnYfzHz30=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "56c02bc00adcf003215cc4bd996d6efaf4cff188",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"gufo-engine": "gufo-engine",
"nixpkgs": "nixpkgs_2"
}
}
},
"root": "root",
"version": 7
}
+246
View File
@@ -0,0 +1,246 @@
{
description = "Gufo Toolboxes - Reproducible OCI toolboxes for AMD Strix Halo (gfx1151 GPU + XDNA2 NPU)";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
gufo-engine = {
url = "git+ssh://git@github.com/francescobozzo/strix-halo.cpp.git";
};
};
outputs =
{
self,
nixpkgs,
gufo-engine,
}:
let
supportedSystems = [ "x86_64-linux" ];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgs = forAllSystems (system: import nixpkgs { inherit system; });
# Common base packages needed for Toolbx and Distrobox compatibility
commonToolboxPkgs =
system:
let
p = pkgs.${system};
in
[
p.bashInteractive
p.coreutils
p.findutils
p.gnugrep
p.gnused
p.gawk
p.procps
p.shadow
p.sudo
p.util-linux
p.glibcLocales
p.cacert
p.radeontop
p.which
p.curl
p.jq
p.less
p.ncurses
];
# Common /etc and /bin initialization setup for toolbx/distrobox
extraSetupCommands =
system:
let
p = pkgs.${system};
in
''
mkdir -p bin usr/bin usr/sbin usr/lib usr/lib64 tmp etc etc/pam.d var/tmp var/log
chmod 1777 tmp var/tmp
# Standard symlinks
ln -sf /bin/bash bin/sh
ln -sf /bin/bash usr/bin/sh
ln -sf /bin/env usr/bin/env
# Create gufo alias / compatibility wrapper if strix binary is present
if [ -f bin/strix ]; then
ln -sf strix bin/gufo
fi
# Ensure etc is writable and clean any read-only links from packages
chmod -R u+w etc 2>/dev/null || true
rm -f etc/passwd etc/group etc/shadow etc/sudoers etc/os-release
# Basic /etc files for toolbox user injection & pam
cat > etc/passwd << 'EOF'
root:x:0:0:root:/root:/bin/bash
nobody:x:65534:65534:Nobody:/:/bin/false
EOF
cat > etc/group << 'EOF'
root:x:0:
video:x:44:
render:x:107:
wheel:x:10:
sudo:x:27:
users:x:100:
nogroup:x:65534:
EOF
cat > etc/shadow << 'EOF'
root:!::0:::::
nobody:!::0:::::
EOF
chmod 0640 etc/shadow
cat > etc/sudoers << 'EOF'
root ALL=(ALL:ALL) ALL
%wheel ALL=(ALL:ALL) NOPASSWD: ALL
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
EOF
chmod 0440 etc/sudoers
# pam.d config for user elevation
cat > etc/pam.d/other << 'EOF'
auth sufficient pam_permit.so
account sufficient pam_permit.so
password sufficient pam_permit.so
session sufficient pam_permit.so
EOF
# os-release to identify as Gufo Toolbox (Toolbx requires ID/NAME)
cat > etc/os-release << 'EOF'
NAME="Gufo Toolbox"
ID=gufo
ID_LIKE="fedora"
VERSION_ID="1.0"
PRETTY_NAME="Gufo Strix Halo Toolbox (gfx1151 + XDNA2)"
HOME_URL="https://github.com/gufo-org/toolboxes"
SUPPORT_URL="https://github.com/gufo-org/toolboxes/issues"
BUG_REPORT_URL="https://github.com/gufo-org/toolboxes/issues"
EOF
ln -sf ../etc/os-release usr/lib/os-release
'';
commonEnv = system: [
"PATH=/bin:/usr/bin:/usr/local/bin"
"LANG=en_US.UTF-8"
"LC_ALL=en_US.UTF-8"
"ROCM_PATH=${pkgs.${system}.rocmPackages.clr}"
"HIP_PLATFORM=amd"
];
in
{
packages = forAllSystems (
system:
let
p = pkgs.${system};
engine = gufo-engine.packages.${system}.default;
in
{
# 1. gufo-runtime: Lightweight serving & inference OCI image
gufo-runtime-image = p.dockerTools.buildLayeredImage {
name = "ghcr.io/gufo-org/toolboxes/gufo-runtime";
tag = "latest";
contents = [ engine ] ++ (commonToolboxPkgs system);
extraCommands = extraSetupCommands system;
config = {
Env = commonEnv system;
Cmd = [ "/bin/bash" ];
};
};
# Streamed variant for fast local loading via `nix run .#stream-gufo-runtime | podman load`
stream-gufo-runtime = p.dockerTools.streamLayeredImage {
name = "ghcr.io/gufo-org/toolboxes/gufo-runtime";
tag = "latest";
contents = [ engine ] ++ (commonToolboxPkgs system);
extraCommands = extraSetupCommands system;
config = {
Env = commonEnv system;
Cmd = [ "/bin/bash" ];
};
};
# 2. gufo-dev: Development, kernel tuning & profiling OCI image
gufo-dev-image = p.dockerTools.buildLayeredImage {
name = "ghcr.io/gufo-org/toolboxes/gufo-dev";
tag = "latest";
contents = [
engine
p.git
p.cmake
p.ninja
p.gdb
p.clang-tools
p.rocmPackages.rocprofiler-sdk
p.rocmPackages.clr
p.rocmPackages.hipblas
p.rocmPackages.hipblaslt
p.rocmPackages.hipcub
p.rocmPackages.rocprim
p.rocmPackages.rocwmma
] ++ (commonToolboxPkgs system);
extraCommands = extraSetupCommands system;
config = {
Env = commonEnv system ++ [
"ROCM_PATH=${p.rocmPackages.clr}"
"STRIX_HIPCUB_ROOT=${p.rocmPackages.hipcub}"
"STRIX_ROCPRIM_ROOT=${p.rocmPackages.rocprim}"
"STRIX_ROCWMMA_ROOT=${p.rocmPackages.rocwmma}"
];
Cmd = [ "/bin/bash" ];
};
};
stream-gufo-dev = p.dockerTools.streamLayeredImage {
name = "ghcr.io/gufo-org/toolboxes/gufo-dev";
tag = "latest";
contents = [
engine
p.git
p.cmake
p.ninja
p.gdb
p.clang-tools
p.rocmPackages.rocprofiler-sdk
p.rocmPackages.clr
p.rocmPackages.hipblas
p.rocmPackages.hipblaslt
p.rocmPackages.hipcub
p.rocmPackages.rocprim
p.rocmPackages.rocwmma
] ++ (commonToolboxPkgs system);
extraCommands = extraSetupCommands system;
config = {
Env = commonEnv system ++ [
"ROCM_PATH=${p.rocmPackages.clr}"
"STRIX_HIPCUB_ROOT=${p.rocmPackages.hipcub}"
"STRIX_ROCPRIM_ROOT=${p.rocmPackages.rocprim}"
"STRIX_ROCWMMA_ROOT=${p.rocmPackages.rocwmma}"
];
Cmd = [ "/bin/bash" ];
};
};
default = self.packages.${system}.gufo-runtime-image;
}
);
apps = forAllSystems (
system:
let
p = pkgs.${system};
in
{
stream-gufo-runtime = {
type = "app";
program = "${self.packages.${system}.stream-gufo-runtime}";
};
stream-gufo-dev = {
type = "app";
program = "${self.packages.${system}.stream-gufo-dev}";
};
}
);
};
}
+196
View File
@@ -0,0 +1,196 @@
#!/usr/bin/env bash
set -euo pipefail
# Gufo Toolboxes Refresh & Provisioning Script
# Target: AMD Ryzen AI Max "Strix Halo" (gfx1151 GPU + XDNA2 NPU)
declare -A IMAGES
IMAGES["gufo-runtime"]="ghcr.io/gufo-org/toolboxes/gufo-runtime:latest"
IMAGES["gufo-dev"]="ghcr.io/gufo-org/toolboxes/gufo-dev:latest"
FORCE_RECREATE=false
usage() {
cat << EOF
Usage: $0 [options] [all|gufo-runtime|gufo-dev]
A helper script to create and manage Gufo Toolboxes on AMD Strix Halo.
Requires only Podman/Docker and Toolbox/Distrobox (no Nix required).
Options:
-f, --force Remove existing toolbox before recreating
-h, --help Show this help message
Available Toolboxes:
- gufo-runtime Lightweight inference runtime (gufo serve, prompt, bench, diagnose)
- gufo-dev Full development, profiling (rocprofv3), and kernel tuning environment
- all Create/refresh both toolboxes
Examples:
$0 gufo-runtime
$0 gufo-dev
$0 -f all
EOF
exit 1
}
# Parse options
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-f|--force)
FORCE_RECREATE=true
shift
;;
-h|--help)
usage
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL[@]}"
if [[ $# -lt 1 ]]; then
usage
fi
TARGET="$1"
SELECTED_TOOLBOXES=()
if [[ "$TARGET" == "all" ]]; then
SELECTED_TOOLBOXES=("gufo-runtime" "gufo-dev")
elif [[ -v IMAGES["$TARGET"] ]]; then
SELECTED_TOOLBOXES=("$TARGET")
else
echo "Error: Unknown target '$TARGET'" >&2
usage
fi
# Detect container runtime (podman vs docker)
CONTAINER_ENGINE=""
if command -v podman >/dev/null 2>&1; then
CONTAINER_ENGINE="podman"
elif command -v docker >/dev/null 2>&1; then
CONTAINER_ENGINE="docker"
else
echo "Error: Neither 'podman' nor 'docker' is installed." >&2
exit 1
fi
# Detect toolbox CLI (toolbox vs distrobox vs standalone container engine)
TOOLBOX_CMD=""
if [[ -f /etc/os-release ]]; then
. /etc/os-release
if [[ "${ID:-}" =~ ^(ubuntu|debian)$ ]] && command -v distrobox >/dev/null 2>&1; then
TOOLBOX_CMD="distrobox"
fi
fi
if [[ -z "$TOOLBOX_CMD" ]]; then
if command -v toolbox >/dev/null 2>&1; then
TOOLBOX_CMD="toolbox"
elif command -v distrobox >/dev/null 2>&1; then
TOOLBOX_CMD="distrobox"
else
TOOLBOX_CMD="$CONTAINER_ENGINE"
echo "ℹ️ Neither 'toolbox' nor 'distrobox' found; using '$CONTAINER_ENGINE' directly."
fi
fi
# Detect Strix Halo Devices
PASSTHROUGH_ARGS=()
# 1. GPU (gfx1151)
if [[ -e /dev/kfd ]]; then
PASSTHROUGH_ARGS+=("--device" "/dev/kfd")
else
echo "⚠️ Warning: /dev/kfd not found. ROCm compute might fail without proper kernel driver." >&2
fi
if [[ -d /dev/dri ]]; then
PASSTHROUGH_ARGS+=("--device" "/dev/dri")
fi
# 2. XDNA2 NPU (/dev/accel* or /dev/amdxdna)
NPU_FOUND=false
for accel in /dev/accel/accel* /dev/amdxdna; do
if [[ -e "$accel" ]]; then
PASSTHROUGH_ARGS+=("--device" "$accel")
NPU_FOUND=true
fi
done
if [[ "$NPU_FOUND" = true ]]; then
echo "⚡ XDNA2 NPU device detected and mapped."
else
echo "ℹ️ Note: No XDNA2 NPU device (/dev/accel/accel* or /dev/amdxdna) detected."
fi
# 3. Optional RDMA / InfiniBand
if [[ -d /dev/infiniband ]]; then
echo "⚡ InfiniBand/RoCE devices detected."
PASSTHROUGH_ARGS+=("--device" "/dev/infiniband" "--group-add" "rdma")
fi
# Permissions and capabilities
PASSTHROUGH_ARGS+=(
"--group-add" "video"
"--group-add" "render"
"--security-opt" "seccomp=unconfined"
"--ulimit" "memlock=-1"
)
# Process each selected toolbox
for TB in "${SELECTED_TOOLBOXES[@]}"; do
IMG="${IMAGES[$TB]}"
echo "=================================================="
echo "📦 Processing Toolbox: $TB"
echo "=================================================="
echo "⬇️ Pulling image $IMG..."
"$CONTAINER_ENGINE" pull "$IMG" || {
echo "⚠️ Failed to pull $IMG. Please check your network connection or image tag." >&2
exit 1
}
# Check if container already exists
CONTAINER_EXISTS=false
if "$CONTAINER_ENGINE" container exists "$TB" 2>/dev/null || "$CONTAINER_ENGINE" inspect "$TB" >/dev/null 2>&1; then
CONTAINER_EXISTS=true
fi
if [[ "$CONTAINER_EXISTS" = true ]]; then
if [[ "$FORCE_RECREATE" = true ]]; then
echo "🗑️ Removing existing container '$TB'..."
if [[ "$TOOLBOX_CMD" == "distrobox" ]]; then
distrobox rm -f "$TB"
else
toolbox rm -f "$TB"
fi
else
echo "ℹ️ Toolbox '$TB' already exists. Use -f or --force to recreate."
continue
fi
fi
echo "🚀 Creating toolbox '$TB' using $TOOLBOX_CMD..."
if [[ "$TOOLBOX_CMD" == "distrobox" ]]; then
distrobox create \
--name "$TB" \
--image "$IMG" \
--additional-flags "${PASSTHROUGH_ARGS[*]}"
else
toolbox create "$TB" \
--image "$IMG" \
-- "${PASSTHROUGH_ARGS[@]}"
fi
echo "✅ Successfully created '$TB'!"
echo "👉 Enter with: $TOOLBOX_CMD enter $TB"
done
echo ""
echo "🎉 All requested toolboxes are ready."
+92
View File
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -u
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
load_threshold=20
idle_threshold=10
load_seconds=2
idle_seconds=60
tuned_command_timeout_seconds=20
tuned_reconcile_seconds=15
tuned_restart_after_failures=2
ectool_command_timeout_seconds=10
fan_reconcile_seconds=15
ectool_bin="$(command -v ectool || true)"
tuned_adm_bin="$(command -v tuned-adm || true)"
systemctl_bin="$(command -v systemctl || true)"
timeout_bin="$(command -v timeout || true)"
systemd_notify_bin="$(command -v systemd-notify || true)"
fan_mode="unknown"
fan_reconcile_count=0
tuned_mode="unknown"
tuned_failures=0
tuned_reconcile_count=0
load_count=0
idle_count=0
no_workload_count=0
gpu_busy_file=""
tuned_worker_pid=""
for candidate in /sys/class/drm/card*/device/gpu_busy_percent; do
if [[ -r "${candidate}" ]] && [[ "$(< "${candidate%/gpu_busy_percent}/vendor")" == "0x1002" ]]; then
gpu_busy_file="${candidate}"
break
fi
done
if [[ -z "${gpu_busy_file}" ]]; then
echo "No AMDGPU gpu_busy_percent counter found" >&2
exit 1
fi
gpu_workload_is_running() {
local proc comm lower
for proc in /proc/[0-9]*; do
[[ -r "${proc}/comm" ]] || continue
read -r comm < "${proc}/comm" 2>/dev/null || continue
lower="${comm,,}"
case "${lower}" in
gufo*|strix*|llama-*|ds4-*|*hipfire*|*vllm*)
return 0
;;
esac
done
return 1
}
apply_tuned_profile() {
local profile="$1"
if [[ -n "${tuned_adm_bin}" ]]; then
"${timeout_bin}" "${tuned_command_timeout_seconds}" "${tuned_adm_bin}" profile "${profile}" >/dev/null 2>&1 || true
fi
}
echo "Starting Gufo GPU Workload Watcher on ${gpu_busy_file}..."
while true; do
read -r busy < "${gpu_busy_file}" 2>/dev/null || busy=0
if gpu_workload_is_running && (( busy >= load_threshold )); then
(( load_count++ ))
idle_count=0
if (( load_count >= load_seconds )) && [[ "${tuned_mode}" != "throughput-performance" ]]; then
echo "GPU active (${busy}%). Switching to throughput-performance profile."
apply_tuned_profile "throughput-performance"
tuned_mode="throughput-performance"
fi
else
(( idle_count++ ))
load_count=0
if (( idle_count >= idle_seconds )) && [[ "${tuned_mode}" != "balanced" ]]; then
echo "GPU idle (${busy}%). Switching to balanced profile."
apply_tuned_profile "balanced"
tuned_mode="balanced"
fi
fi
sleep 1
done
@@ -0,0 +1,14 @@
[Unit]
Description=Manage power profile for Gufo GPU inference workloads
Wants=tuned.service
After=multi-user.target tuned.service
[Service]
Type=simple
ExecStart=/usr/local/sbin/gpu-workload-watch
Restart=always
RestartSec=2s
LimitCORE=0
[Install]
WantedBy=multi-user.target
+29
View File
@@ -0,0 +1,29 @@
[Unit]
Description=Gufo Inference Server (AMD Strix Halo gfx1151 + XDNA2)
After=network.target local-fs.target
Documentation=https://github.com/gufo-org/toolboxes
[Service]
Type=simple
Environment=HOST=127.0.0.1
Environment=PORT=8080
Environment=MODEL_PATH=%h/.local/share/gufo/models/model.gguf
Environment=THREADS=8
# Run inside the gufo-runtime container using podman
ExecStart=/usr/bin/podman run --rm --name gufo-serve \
--device /dev/kfd --device /dev/dri \
--device /dev/accel/accel0 \
--group-add video --group-add render \
--security-opt seccomp=unconfined --ulimit memlock=-1 \
-v %h/.local/share/gufo/models:/models:ro,z \
-p ${PORT}:${PORT} \
ghcr.io/gufo-org/toolboxes/gufo-runtime:latest \
gufo serve --model /models/model.gguf --host ${HOST} --port ${PORT} --threads ${THREADS}
ExecStop=/usr/bin/podman stop -t 10 gufo-serve
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target