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 @@ ai-rm L1 계층 프로토콜 v0.1 릴리스 v0.4.1 - Helm chart 0.4.2 + Helm chart 0.5.0 Python 3.9+ 라이선스: BSD-3-Clause

@@ -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 @@ ai-rm layer L1 Protocol v0.1 Release v0.4.1 - Helm chart 0.4.2 + Helm chart 0.5.0 Python 3.9+ License: BSD-3-Clause

@@ -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",