mirror of
https://github.com/gufo-org/toolboxes.git
synced 2026-11-07 10:46:07 -10:00
feat(toolboxes): implement reproducible gufo toolboxes for Strix Halo
This commit is contained in:
commit
ea7a8e0d91
13 files changed
+1073
No files matched your search
@@ -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}"
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,4 @@
|
||||
result
|
||||
result-*
|
||||
.direnv/
|
||||
amd-strix-halo-toolboxes/
|
||||
@@ -0,0 +1,102 @@
|
||||
# Gufo Toolboxes for AMD Strix Halo
|
||||
|
||||
[](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.
|
||||
@@ -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).*
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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}";
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
Executable
+196
@@ -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
@@ -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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user