From fba24e39660312fff29b46bc0a1612e2154c8202 Mon Sep 17 00:00:00 2001
From: gspark-etri
Date: Mon, 19 Oct 2026 18:29:13 +0900
Subject: [PATCH] Node daemon image ghcr.io/ai-computing/rm-accel; chart 0.5.0
uses it
- Dockerfile: python:3.11-slim + rm-abstract (no torch) + rm-core v0.1.1 + kubernetes + numpy.
Vendor tools stay on the host; /host/usr/bin is on the image's PATH.
- Image workflow: build and smoke-test on PRs, publish :master and :X.Y.Z / :X.Y from v* tags
with GITHUB_TOKEN (packages: write).
- Chart 0.5.0 (appVersion 0.4.2): default image ghcr.io/ai-computing/rm-accel, new value hostBin
(default /usr/bin) mounting the host's vendor tools, so the init-container workaround is no
longer needed. hostDev and the writable /tmp stay. CI lints the chart.
Co-Authored-By: Claude Opus 5.5
---
.dockerignore | 7 ++
.github/workflows/ci.yml | 13 ++++
.github/workflows/image.yml | 72 +++++++++++++++++++
CHANGELOG.md | 14 ++++
Dockerfile | 28 ++++++++
README.ko.md | 22 +++---
README.md | 22 +++---
ai-rm.yaml | 2 +-
deploy/helm/rm-abstract-node/Chart.yaml | 4 +-
.../rm-abstract-node/templates/daemonset.yaml | 14 +++-
deploy/helm/rm-abstract-node/values.yaml | 13 ++--
pyproject.toml | 2 +-
src/rm_abstract/__init__.py | 2 +-
13 files changed, 188 insertions(+), 27 deletions(-)
create mode 100644 .dockerignore
create mode 100644 .github/workflows/image.yml
create mode 100644 Dockerfile
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..4f04ac4
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,7 @@
+*
+!pyproject.toml
+!README.md
+!LICENSE
+!src/
+**/__pycache__
+**/*.egg-info
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c547780..b33a8c7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -48,6 +48,19 @@ jobs:
python-version: '3.11'
- run: python tools/check_sensitive.py .
+ # Node daemon chart: default values and the simulated-cluster values must render
+ chart:
+ name: Helm chart lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: azure/setup-helm@v4
+ with:
+ version: v3.16.4
+ - run: helm lint deploy/helm/rm-abstract-node
+ - run: helm lint deploy/helm/rm-abstract-node --set provider=sim --set hostBin= --set hostDev=false
+ - run: helm template rm-abstract-node deploy/helm/rm-abstract-node > /dev/null
+
# Unit tests (multiple Python versions)
test:
name: Test (Python ${{ matrix.python-version }})
diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml
new file mode 100644
index 0000000..1b44b5b
--- /dev/null
+++ b/.github/workflows/image.yml
@@ -0,0 +1,72 @@
+name: Image
+
+# Node daemon image ghcr.io/ai-computing/rm-accel (Dockerfile).
+# Pull requests build and smoke-test it; master pushes :master, tags vX.Y.Z push :X.Y.Z and :X.Y.
+on:
+ push:
+ branches: [master]
+ tags: ['v*']
+ pull_request:
+ branches: [master]
+ paths: [Dockerfile, .dockerignore, pyproject.toml, 'src/**', .github/workflows/image.yml]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ packages: write
+
+env:
+ IMAGE: ghcr.io/ai-computing/rm-accel
+
+jobs:
+ image:
+ name: Build and publish node daemon image
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Tags
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.IMAGE }}
+ tags: |
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=ref,event=branch
+ type=sha,format=short
+
+ - name: Build
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ load: true
+ tags: ${{ env.IMAGE }}:test
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Smoke test (sim provider, no cluster)
+ run: |
+ docker run --rm ${{ env.IMAGE }}:test --provider sim --once --dry-run --node ci-node > objects.json
+ python3 -c "import json; o = json.load(open('objects.json')); kinds = [i['kind'] for i in o['items']]; print(kinds); assert kinds.count('AcceleratorProfile') == 2"
+ docker run --rm --entrypoint sh ${{ env.IMAGE }}:test -c 'echo "$PATH" | grep -q /host/usr/bin'
+
+ - name: Log in to ghcr.io
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Push
+ if: github.event_name != 'pull_request'
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-from: type=gha
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bc5ae19..fb2f94a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
# Changelog
+## 0.4.2 / Chart 0.5.0 — 2026-10-19
+
+### Added
+- Node daemon image `ghcr.io/ai-computing/rm-accel` ([`Dockerfile`](Dockerfile)): python:3.11-slim with
+ rm-abstract (no torch), rm-core v0.1.1, the Kubernetes client and numpy. Vendor tools are not baked in.
+- Workflow `Image`: builds and smoke-tests the image on pull requests, publishes `:master` from master and
+ `:X.Y.Z` / `:X.Y` from `vX.Y.Z` tags (GITHUB_TOKEN, `packages: write`).
+- Chart value `hostBin` (default `/usr/bin`): the host's vendor tools mounted read-only at `/host/usr/bin`,
+ which is on the image's `PATH`.
+
+### Changed
+- Chart default image `ghcr.io/ai-computing/rm-accel` (was the never-published `rm-abstract-node`), tag =
+ `appVersion` 0.4.2. Sites no longer need the init-container workaround; the extension points stay.
+
## Chart 0.4.2 — 2026-10-19
Found while running the daemon on the live cluster (ai-rm end-to-end run).
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..48af044
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,28 @@
+# ai-rm L1 node daemon image (ghcr.io/ai-computing/rm-accel): python -m rm_abstract.airm.daemon
+#
+# Only what the daemon needs: rm-abstract (without its torch dependency; discovery runs the vendor
+# tools), rm-core, the Kubernetes client and numpy. Vendor CLI tools (nvidia-smi, furiosa-smi,
+# rbln-stat) are not baked in: they match the host's driver, so the Helm chart mounts the host's
+# /usr/bin read-only at /host/usr/bin (value hostBin), which is on PATH here.
+#
+# docker build -t ghcr.io/ai-computing/rm-accel:dev .
+# docker run --rm ghcr.io/ai-computing/rm-accel:dev --provider sim --once --dry-run
+FROM python:3.11-slim
+
+ARG RM_CORE_REF=v0.1.1
+
+ENV PYTHONDONTWRITEBYTECODE=1 \
+ PYTHONUNBUFFERED=1 \
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
+ PATH=/usr/local/bin:/usr/bin:/bin:/host/usr/bin
+
+COPY pyproject.toml README.md LICENSE /src/
+COPY src /src/src
+RUN pip install --no-cache-dir --no-deps /src \
+ "https://github.com/ai-computing/rm-core/archive/refs/tags/${RM_CORE_REF}.tar.gz" \
+ && pip install --no-cache-dir "kubernetes>=24.2.0" "numpy>=1.20,<3" \
+ && rm -rf /src \
+ && python -m rm_abstract.airm.daemon --provider sim --once --dry-run >/dev/null
+
+# Root: the vendor tools read the device nodes (the chart runs the container privileged).
+ENTRYPOINT ["python", "-m", "rm_abstract.airm.daemon"]
diff --git a/README.ko.md b/README.ko.md
index 1cc93b8..846a153 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -13,7 +13,7 @@
-
+
@@ -127,28 +127,34 @@ for acc in provider.discover():
[`deploy/helm/rm-abstract-node`](deploy/helm/rm-abstract-node)는 데몬을 DaemonSet으로 설치하고 ServiceAccount와
ClusterRole(`acceleratorprofiles` CRUD, `nodes` get/patch)을 만듭니다. `AcceleratorProfile` CRD는
[rm-core](https://github.com/ai-computing/rm-core/tree/v0.1.1/k8s)에 있고 llm-d-setup / ai-rm umbrella가 설치합니다.
-이미지는 사이트별로 `image.repository` / `image.tag`로 지정합니다.
+이미지는 `ghcr.io/ai-computing/rm-accel`(태그 = chart `appVersion`)이며, [`Dockerfile`](Dockerfile)로 빌드되어
+[Image 워크플로](.github/workflows/image.yml)가 `master`와 `v*` 태그에서 배포합니다.
```bash
-helm template rm-abstract-node deploy/helm/rm-abstract-node --set image.repository=/rm-abstract-node
+helm template rm-abstract-node deploy/helm/rm-abstract-node # 배포 이미지, 호스트 도구
+helm template rm-abstract-node deploy/helm/rm-abstract-node --set provider=sim --set hostBin= --set hostDev=false
+docker build -t rm-accel:dev . && docker run --rm rm-accel:dev --provider sim --once --dry-run
```
-Chart 0.4.2: 컨테이너 실행 환경과 사이트 확장 지점
+Chart 0.5.0: 이미지, 컨테이너 실행 환경과 사이트 확장 지점
| 값 | 기본값 | 의미 |
|---|---|---|
| `privileged` | `true` | 벤더 도구가 장치 노드를 읽기 위해 필요 |
| `hostDev` | `true` | 호스트 `/dev`를 실시간으로 마운트해, 드라이버가 다시 만든 장치(예: pod가 놓아준 ATOM)도 계속 탐지되게 함 |
+| `hostBin` | `/usr/bin` | 벤더 도구가 있는 호스트 디렉터리, `/host/usr/bin`에 읽기 전용으로 마운트(이미지 `PATH`에 포함); `""`이면 끔 |
| `command` | `[]` | 데몬 명령 재정의 (기본 `python -m rm_abstract.airm.daemon`) |
| `extraEnv` | `[]` | 데몬 env에 덧붙일 `{name, value}` 항목 |
| `initContainers` | `[]` | pod init container, 그대로 반영 |
| `extraVolumes` / `extraVolumeMounts` | `[]` | pod volume / 데몬 volumeMount, 그대로 반영 |
-루트 파일시스템은 읽기 전용이고 `/tmp`만 쓰기 가능합니다(`rbln-stat`이 `TMPDIR` 아래에 스스로 압축을 풉니다).
-`rm-abstract-node` 이미지가 배포되기 전까지는 확장 지점을 써서 일반 Python 이미지로 데몬을 실행할 수 있습니다.
-[`values.yaml`](deploy/helm/rm-abstract-node/values.yaml)과 [변경 이력](CHANGELOG.md)을 참고하세요.
+이미지에는 rm-abstract(torch 제외), rm-core, Kubernetes 클라이언트, numpy가 들어 있고 벤더 도구는 없습니다. 도구는
+호스트 드라이버와 맞아야 하므로 `hostBin`으로 호스트에서 가져옵니다. 루트 파일시스템은 읽기 전용이고 `/tmp`만 쓰기
+가능합니다(`rbln-stat`이 `TMPDIR` 아래에 스스로 압축을 풉니다). 배포 이미지에는 확장 지점이 더 이상 필요 없고, 자체
+이미지나 패키지를 쓰는 사이트를 위해 남겨 둡니다. [`values.yaml`](deploy/helm/rm-abstract-node/values.yaml)과
+[변경 이력](CHANGELOG.md)을 참고하세요.
## 🔁 vLLM 호환 API
@@ -246,7 +252,7 @@ bash scripts/run_examples.sh # 예제 전체 실행 (모델·
## 🗺 로드맵
- [x] rm-core v0.1 `AcceleratorProvider`, 노드 데몬, Helm chart
-- [ ] `rm-abstract-node` 컨테이너 이미지 배포
+- [x] 노드 데몬 컨테이너 이미지 `ghcr.io/ai-computing/rm-accel`
- [ ] `LLM` API를 통한 RNGD / ATOM 추론 검증
- [ ] Optimus Prime용 엔진 백엔드 어댑터 (ai-rm [엔진 통합 설계](https://github.com/ai-computing/ai-rm/blob/master/docs/ko/engine-integration.md))
- [ ] 장치 모델 확장: 파티션, 인터커넥트 토폴로지, 실시간 사용률
diff --git a/README.md b/README.md
index 94c146b..29f5036 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
@@ -127,28 +127,34 @@ Applying to a cluster needs the Kubernetes client: `pip install -e ".[k8s]"`.
[`deploy/helm/rm-abstract-node`](deploy/helm/rm-abstract-node) installs the daemon as a DaemonSet with a
ServiceAccount and ClusterRole (`acceleratorprofiles` CRUD, `nodes` get/patch). The `AcceleratorProfile` CRD
ships with [rm-core](https://github.com/ai-computing/rm-core/tree/v0.1.1/k8s) and is installed by llm-d-setup / the
-ai-rm umbrella. Set the image for your site with `image.repository` / `image.tag`.
+ai-rm umbrella chart. The image is `ghcr.io/ai-computing/rm-accel` (tag = chart `appVersion`), built from the
+[`Dockerfile`](Dockerfile) and published by the [Image workflow](.github/workflows/image.yml) on `master` and on `v*` tags.
```bash
-helm template rm-abstract-node deploy/helm/rm-abstract-node --set image.repository=/rm-abstract-node
+helm template rm-abstract-node deploy/helm/rm-abstract-node # published image, host tools
+helm template rm-abstract-node deploy/helm/rm-abstract-node --set provider=sim --set hostBin= --set hostDev=false
+docker build -t rm-accel:dev . && docker run --rm rm-accel:dev --provider sim --once --dry-run
```
-Chart 0.4.2: container runtime and site extension points
+Chart 0.5.0: image, container runtime and site extension points
| Value | Default | Meaning |
|---|---|---|
| `privileged` | `true` | Vendor tools read the device nodes |
| `hostDev` | `true` | Mount the host's `/dev` live, so a device the driver re-creates (e.g. an ATOM released by a pod) stays in discovery |
+| `hostBin` | `/usr/bin` | Host directory with the vendor tools, mounted read-only at `/host/usr/bin` (on the image's `PATH`); `""` disables |
| `command` | `[]` | Override the daemon command (default `python -m rm_abstract.airm.daemon`) |
| `extraEnv` | `[]` | `{name, value}` entries appended to the daemon's env |
| `initContainers` | `[]` | Pod init containers, verbatim |
| `extraVolumes` / `extraVolumeMounts` | `[]` | Pod volumes / daemon volume mounts, verbatim |
-The root filesystem is read-only with a writable `/tmp` (`rbln-stat` unpacks itself under `TMPDIR`). Until the
-`rm-abstract-node` image is published, the extension points let a site run the daemon from a stock Python image.
-See [`values.yaml`](deploy/helm/rm-abstract-node/values.yaml) and the [changelog](CHANGELOG.md).
+The image carries rm-abstract (without torch), rm-core, the Kubernetes client and numpy, but no vendor tools: those
+must match the host's driver, so they come from the host through `hostBin`. The root filesystem is read-only with a
+writable `/tmp` (`rbln-stat` unpacks itself under `TMPDIR`). The extension points are no longer needed for the
+published image; they remain for sites with their own image or packages. See
+[`values.yaml`](deploy/helm/rm-abstract-node/values.yaml) and the [changelog](CHANGELOG.md).
## 🔁 vLLM-compatible API
@@ -248,7 +254,7 @@ Guides: [contributing](docs/CONTRIBUTING.md) · [adding a device](docs/ADDING_RE
## 🗺 Roadmap
- [x] rm-core v0.1 `AcceleratorProvider`, node daemon, Helm chart
-- [ ] Published `rm-abstract-node` container image
+- [x] Node daemon container image `ghcr.io/ai-computing/rm-accel`
- [ ] Validated RNGD / ATOM inference through the `LLM` API
- [ ] Engine-facing backend adapters for Optimus Prime (see the ai-rm [engine plan](https://github.com/ai-computing/ai-rm/blob/master/docs/engine-integration.md))
- [ ] Wider device model: partitions, interconnect topology, live utilization
diff --git a/ai-rm.yaml b/ai-rm.yaml
index 8f61fd4..3dc81ed 100644
--- a/ai-rm.yaml
+++ b/ai-rm.yaml
@@ -14,7 +14,7 @@ deploy:
target: k8s
manifests: null
chart: deploy/helm/rm-abstract-node
- image: null
+ image: ghcr.io/ai-computing/rm-accel
config:
env_example: null
interfaces:
diff --git a/deploy/helm/rm-abstract-node/Chart.yaml b/deploy/helm/rm-abstract-node/Chart.yaml
index df1e03f..eef9619 100644
--- a/deploy/helm/rm-abstract-node/Chart.yaml
+++ b/deploy/helm/rm-abstract-node/Chart.yaml
@@ -2,5 +2,5 @@ apiVersion: v2
name: rm-abstract-node
description: ai-rm L1 node daemon - publishes each node's accelerators as AcceleratorProfiles and node labels
type: application
-version: 0.4.2
-appVersion: "0.4.1"
+version: 0.5.0
+appVersion: "0.4.2"
diff --git a/deploy/helm/rm-abstract-node/templates/daemonset.yaml b/deploy/helm/rm-abstract-node/templates/daemonset.yaml
index bb91e84..9be5a40 100644
--- a/deploy/helm/rm-abstract-node/templates/daemonset.yaml
+++ b/deploy/helm/rm-abstract-node/templates/daemonset.yaml
@@ -41,7 +41,7 @@ spec:
command:
{{- toYaml .Values.command | nindent 12 }}
{{- else }}
- command: ["python", "-m", "rm_abstract.airm.daemon"]
+ command: ["python", "-m", "rm_abstract.airm.daemon"] # the image's entrypoint
{{- end }}
env:
- name: AIRM_NODE_NAME
@@ -79,6 +79,12 @@ spec:
- name: dev
mountPath: /dev
{{- end }}
+ {{- if .Values.hostBin }}
+ # host vendor tools, on the image's PATH
+ - name: host-bin
+ mountPath: /host/usr/bin
+ readOnly: true
+ {{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
@@ -90,6 +96,12 @@ spec:
hostPath:
path: /dev
{{- end }}
+ {{- if .Values.hostBin }}
+ - name: host-bin
+ hostPath:
+ path: {{ .Values.hostBin }}
+ type: Directory
+ {{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
diff --git a/deploy/helm/rm-abstract-node/values.yaml b/deploy/helm/rm-abstract-node/values.yaml
index 08d5459..227c667 100644
--- a/deploy/helm/rm-abstract-node/values.yaml
+++ b/deploy/helm/rm-abstract-node/values.yaml
@@ -4,8 +4,8 @@
enabled: true
image:
- repository: ghcr.io/ai-computing/rm-abstract-node # rm-abstract[k8s] + vendor tools (nvidia-smi, furiosa-smi, rbln-stat); override per site
- tag: "" # default: chart appVersion
+ repository: ghcr.io/ai-computing/rm-accel # Dockerfile: rm-abstract + rm-core + kubernetes, no vendor tools
+ tag: "" # default: chart appVersion (image tags follow the rm-accel release, e.g. 0.4.2)
pullPolicy: IfNotPresent
airm:
@@ -22,14 +22,17 @@ privileged: true
# Mount the host's /dev live. A privileged container otherwise sees /dev as it was at start, and a
# device that a driver re-creates (e.g. after a pod releases an ATOM) drops out of discovery.
hostDev: true
+# The vendor tools (nvidia-smi, furiosa-smi, rbln-stat) come from the host, matching its driver: this
+# host directory is mounted read-only at /host/usr/bin, which is on the image's PATH. "" disables
+# (e.g. the sim provider, or an image that carries its own tools).
+hostBin: /usr/bin
serviceAccount:
create: true
name: ""
-# Site extension points (all empty by default). A site without the published image can run a
-# stock Python image: an init container installs rm-abstract[k8s] into a volume on PYTHONPATH,
-# and the host's vendor tools are mounted read-only and appended to PATH.
+# Site extension points (all empty by default): the published image needs none of them. A site
+# with its own image or packages can still add an init container, env, volumes and mounts.
command: [] # default: python -m rm_abstract.airm.daemon
extraEnv: [] # [{name, value}] appended to the daemon's env
initContainers: [] # pod initContainers, verbatim
diff --git a/pyproject.toml b/pyproject.toml
index fa806ed..9f5799b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rm-abstract"
-version = "0.4.1"
+version = "0.4.2"
description = "Accelerator abstraction for heterogeneous AI infrastructure: GPU/NPU/CPU discovery, AcceleratorProfile publishing and a vLLM-compatible API (ai-rm L1)"
readme = "README.md"
license = {text = "BSD-3-Clause"}
diff --git a/src/rm_abstract/__init__.py b/src/rm_abstract/__init__.py
index 29f3de7..85d0e4e 100644
--- a/src/rm_abstract/__init__.py
+++ b/src/rm_abstract/__init__.py
@@ -38,7 +38,7 @@
# Modular components
from .registry import PluginRegistry, PluginType, discover_all, get_registry
-__version__ = "0.4.1"
+__version__ = "0.4.2"
__all__ = [
# Main LLM interface (recommended)
"LLM",