From f8f7d9f7f77eeaecba633116284a9ab8e86adc97 Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 14:29:20 -0700 Subject: [PATCH 01/11] chore: consolidate docker files into a single location and complete services Signed-off-by: Brooke Storm --- .github/actions/changes/action.yaml | 7 + .github/workflows/ci.yaml | 28 +- Makefile | 25 + docker-bake.hcl | 982 ++++++++++++++++-- docker/Dockerfile.auditor-tasks | 158 +++ docker/Dockerfile.bake | 133 +++ docker/Dockerfile.nmp-api | 65 ++ .../Dockerfile.nmp-automodel-base | 2 +- .../Dockerfile.nmp-automodel-tasks | 0 .../Dockerfile.nmp-automodel-training | 2 +- docker/Dockerfile.nmp-core | 36 + docker/Dockerfile.nmp-cpu-tasks | 25 + docker/Dockerfile.nmp-customizer | 213 ++++ docker/Dockerfile.nmp-customizer-tasks | 151 +++ .../Dockerfile.nmp-unsloth-training | 0 docker/Dockerfile.safe-synthesizer-tasks | 201 ++++ docker/README.md | 12 + .../automodel}/Dockerfile.platform-workspace | 8 +- .../docker => docker/automodel}/README.md | 0 .../automodel}/cherry-picks/e6d2930a.diff | 2 +- .../automodel}/no_override_requirements.txt | 0 .../automodel}/pyproject.workspace.toml | 3 +- docker/base/Dockerfile.faiss-gpu-wheel | 95 ++ .../base}/Dockerfile.mamba-wheel | 18 +- docker/base/Dockerfile.nmp-gpu-base | 58 ++ docker/base/Dockerfile.nmp-gpu-base-py312 | 46 + docker/base/Dockerfile.nmp-jobs-launcher | 15 + docker/base/Dockerfile.nmp-python-base | 76 ++ docker/base/Dockerfile.nmp-studio-ui | 89 ++ docker/base/Dockerfile.nmp-workspace | 18 + docker/base/Dockerfile.policy-wasm | 20 + docker/customizer-patch/layers.py.diff | 120 +++ docker/customizer-patch/model.py.diff | 25 + docker/customizer-patch/patch.sh | 14 + docker/dockerfiles/README.md | 13 + .../services/data-designer/Dockerfile.bake | 81 ++ .../services/data-designer/Dockerfile.base | 38 + .../services/guardrails/Dockerfile.bake | 98 ++ .../guardrails/callouts/Dockerfile.bake | 54 + .../services/intake/Dockerfile.bake | 54 + docker/locks/README.md | 50 + .../mamba-wheel-build-py311/pyproject.toml | 0 .../locks/mamba-wheel-build-py311/uv.lock | 0 .../mamba-wheel-build-py312/pyproject.toml | 0 .../locks/mamba-wheel-build-py312/uv.lock | 0 docker/scripts/cve-cleanup.sh | 49 + .../unsloth}/Dockerfile.platform-workspace | 8 +- .../docker => docker/unsloth}/README.md | 4 +- .../unsloth}/pyproject.workspace.toml | 3 +- .../skills/nemo-customizer/SKILL.md | 4 +- .../references/troubleshooting.md | 8 +- plugins/nemo-unsloth/README.md | 4 +- services/automodel/docker/docker-bake.hcl | 4 - services/automodel/docker/locks/README.md | 11 - services/unsloth/docker/docker-bake.hcl | 4 - 55 files changed, 2993 insertions(+), 141 deletions(-) create mode 100644 docker/Dockerfile.auditor-tasks create mode 100644 docker/Dockerfile.bake create mode 100644 docker/Dockerfile.nmp-api rename {services/automodel/docker => docker}/Dockerfile.nmp-automodel-base (98%) rename {services/automodel/docker => docker}/Dockerfile.nmp-automodel-tasks (100%) rename {services/automodel/docker => docker}/Dockerfile.nmp-automodel-training (95%) create mode 100644 docker/Dockerfile.nmp-core create mode 100644 docker/Dockerfile.nmp-cpu-tasks create mode 100644 docker/Dockerfile.nmp-customizer create mode 100644 docker/Dockerfile.nmp-customizer-tasks rename {services/unsloth/docker => docker}/Dockerfile.nmp-unsloth-training (100%) create mode 100644 docker/Dockerfile.safe-synthesizer-tasks create mode 100644 docker/README.md rename {services/automodel/docker => docker/automodel}/Dockerfile.platform-workspace (71%) rename {services/automodel/docker => docker/automodel}/README.md (100%) rename {services/automodel/docker => docker/automodel}/cherry-picks/e6d2930a.diff (99%) rename {services/automodel/docker => docker/automodel}/no_override_requirements.txt (100%) rename {services/automodel/docker => docker/automodel}/pyproject.workspace.toml (82%) create mode 100644 docker/base/Dockerfile.faiss-gpu-wheel rename {services/automodel/docker => docker/base}/Dockerfile.mamba-wheel (92%) create mode 100644 docker/base/Dockerfile.nmp-gpu-base create mode 100644 docker/base/Dockerfile.nmp-gpu-base-py312 create mode 100644 docker/base/Dockerfile.nmp-jobs-launcher create mode 100644 docker/base/Dockerfile.nmp-python-base create mode 100644 docker/base/Dockerfile.nmp-studio-ui create mode 100644 docker/base/Dockerfile.nmp-workspace create mode 100644 docker/base/Dockerfile.policy-wasm create mode 100644 docker/customizer-patch/layers.py.diff create mode 100644 docker/customizer-patch/model.py.diff create mode 100755 docker/customizer-patch/patch.sh create mode 100644 docker/dockerfiles/README.md create mode 100644 docker/dockerfiles/services/data-designer/Dockerfile.bake create mode 100644 docker/dockerfiles/services/data-designer/Dockerfile.base create mode 100644 docker/dockerfiles/services/guardrails/Dockerfile.bake create mode 100644 docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake create mode 100644 docker/dockerfiles/services/intake/Dockerfile.bake create mode 100644 docker/locks/README.md rename {services/automodel/docker => docker}/locks/mamba-wheel-build-py311/pyproject.toml (100%) rename {services/automodel/docker => docker}/locks/mamba-wheel-build-py311/uv.lock (100%) rename {services/automodel/docker => docker}/locks/mamba-wheel-build-py312/pyproject.toml (100%) rename {services/automodel/docker => docker}/locks/mamba-wheel-build-py312/uv.lock (100%) create mode 100644 docker/scripts/cve-cleanup.sh rename {services/unsloth/docker => docker/unsloth}/Dockerfile.platform-workspace (73%) rename {services/unsloth/docker => docker/unsloth}/README.md (98%) rename {services/unsloth/docker => docker/unsloth}/pyproject.workspace.toml (79%) delete mode 100644 services/automodel/docker/docker-bake.hcl delete mode 100644 services/automodel/docker/locks/README.md delete mode 100644 services/unsloth/docker/docker-bake.hcl diff --git a/.github/actions/changes/action.yaml b/.github/actions/changes/action.yaml index b2e637234f..e8420259ae 100644 --- a/.github/actions/changes/action.yaml +++ b/.github/actions/changes/action.yaml @@ -25,6 +25,9 @@ outputs: web-studio: description: "'true' if any web files changed" value: ${{ steps.filter.outputs.web-studio }} + docker: + description: "'true' if any Docker build files changed" + value: ${{ steps.filter.outputs.docker }} runs: using: "composite" @@ -56,3 +59,7 @@ runs: - 'tools/**' web-studio: - 'web/**' + docker: + - 'docker-bake.hcl' + - 'docker/**' + - 'Makefile' diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0ea89f79e1..c3fe97f1a3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,6 +30,7 @@ jobs: docs: ${{ steps.changes.outputs.docs }} web-studio: ${{ steps.changes.outputs.web-studio }} tools: ${{ steps.changes.outputs.tools }} + docker: ${{ steps.changes.outputs.docker }} steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - uses: ./.github/actions/changes @@ -56,6 +57,31 @@ jobs: run: | "${RUNNER_TEMP}/actionlint/actionlint" + docker-canary: + name: Docker bake graph + needs: [changes] + if: > + !cancelled() && ( + github.event_name == 'workflow_dispatch' || + needs.changes.outputs.docker == 'true' + ) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Print bake groups + shell: bash + run: | + make docker-print TARGET=docker-cpu + make docker-print TARGET=docker-gpu + make docker-print TARGET=docker-auditor + make docker-print TARGET=docker-python-base + make docker-print TARGET=gpu-wheels + make docker-print TARGET=nmp-automodel + make docker-print TARGET=nmp-unsloth + lint: name: Lint all runs-on: ubuntu-latest @@ -755,6 +781,7 @@ jobs: needs: - changes - actionlint + - docker-canary - lint - policy-wasm - python-unit-test-tools @@ -797,4 +824,3 @@ jobs: printf '%s\n' "${NEEDS_JSON}" \ | jq -e 'to_entries | map(.value.result) | all(. as $result | ["success", "skipped"] | any($result == .))' - diff --git a/Makefile b/Makefile index 574057e2c0..53a348633c 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,31 @@ help: @echo "Makefile commands:" @grep -E '^[a-zA-Z_-][a-zA-Z0-9_/-]*:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +DOCKER_BAKE_FILE ?= docker-bake.hcl +DOCKER_TARGET ?= $(if $(TARGET),$(TARGET),docker-cpu) +DOCKER_PLATFORMS ?= $(BUILD_ARCH) +DOCKER_PLATFORM_SET = $(if $(DOCKER_PLATFORMS),--set "*.platform=$(DOCKER_PLATFORMS)",) + +.PHONY: docker-list-targets +docker-list-targets: ## List Docker bake targets + docker buildx bake -f $(DOCKER_BAKE_FILE) --list=targets + +.PHONY: docker-print +docker-print: ## Print Docker bake graph for TARGET, default docker-cpu + docker buildx bake -f $(DOCKER_BAKE_FILE) --print $(DOCKER_TARGET) + +.PHONY: docker-build +docker-build: ## Build Docker bake TARGET without pushing, default docker-cpu + docker buildx bake -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) + +.PHONY: docker-load +docker-load: ## Build and load single-platform Docker bake TARGET, default docker-cpu + docker buildx bake -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) $(DOCKER_PLATFORM_SET) --load + +.PHONY: docker-push +docker-push: ## Build and push Docker bake TARGET, default docker-cpu + docker buildx bake -f $(DOCKER_BAKE_FILE) $(DOCKER_TARGET) --push + .PHONY: refresh-openapi refresh-openapi: ## Generate the OpenAPI specification uv run --frozen script/generate-openapi-spec.sh diff --git a/docker-bake.hcl b/docker-bake.hcl index 9fdf9ecad4..b282f2db33 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,67 +1,105 @@ -# NeMo Platform GPU image bake — run from Platform repo root (context = "."). -# -# Groups: -# nmp-automodel-gpu-wheels causal-conv1d-wheel, mamba-ssm-wheel -# nmp-automodel base, tasks, training, smoke-test targets -# nmp-unsloth nmp-unsloth-training -# -# Automodel — inspect wheels (no build): -# docker buildx bake --print -f docker-bake.hcl nmp-automodel-gpu-wheels -# -# Automodel — build and push wheels: -# export WHEELS_REGISTRY=my-registry/nemo-platform-dev -# export WHEELS_TAG=$(git rev-parse --short HEAD) -# docker buildx bake -f docker-bake.hcl nmp-automodel-gpu-wheels --push -# -# Automodel — build runtime images: -# docker buildx bake -f docker-bake.hcl nmp-automodel-base-builder -# -# Unsloth — local build (--load): -# docker buildx bake -f docker-bake.hcl nmp-unsloth-training --load \ -# --set "*.platform=linux/amd64" -# -# Unsloth — push to registry: -# export IMAGE_REGISTRY=my-registry/nemo-platform-dev -# export BAKE_TAG=$(git rev-parse --short HEAD) -# docker buildx bake -f docker-bake.hcl nmp-unsloth-training --push \ -# --set "*.platform=linux/amd64" -# -# Published tags: -# ${IMAGE_REGISTRY}/nmp-automodel-{base,tasks,training}:${BAKE_TAG} -# ${IMAGE_REGISTRY}/nmp-unsloth-training:${BAKE_TAG} - -# --------------------------------------------------------------------------- -# Shared / automodel variables -# --------------------------------------------------------------------------- +####### +# NeMo Platform Docker build target configuration. +####### + +variable "CACHE_REGISTRY" { + default = "gitlab-master.nvidia.com:5005/aire/microservices/nmp" # alternatively "harbor.aire.nvidia.com/nmp" +} + +variable "CACHE_REGISTRY_BACKUP" { + default = "" +} + +variable "CACHE_VERSION" { + default = "cache" +} variable "IMAGE_REGISTRY" { - default = "my-registry/nemo-platform-dev" + default = "my-registry" } +# Registry where pinned base images are published (nmp-python-base, nmp-customizer-tasks-base, etc.) +# In CI this matches CI_REGISTRY_IMAGE; locally override if needed. variable "BASE_REGISTRY" { - default = "my-registry/nemo-platform-dev" + default = "gitlab-master.nvidia.com:5005/aire/microservices/nmp" +} + +variable "DISTROLESS_BASE" { + default = "nvcr.io/nvidia/distroless/python:3.11-v4.0.3" +} + +variable "DOCKERHUB_MIRROR" { + default = "dockerhub.nvidia.com" } variable "WHEELS_REGISTRY" { - default = "my-registry/nemo-platform-dev" + default = "gitlab-master.nvidia.com:5005/aire/microservices/nmp" } variable "BAKE_TAG" { default = "local" } +# Pin for nmp-python-base (built by nmp-python-base-builder; run that target to update) +variable "BASE_TAG_PYTHON" { + # From MR: https://gitlab-master.nvidia.com/aire/microservices/nmp/-/merge_requests/7315 + default = "dbdc61fdb117f48bc9569b4274afe836cf0de030" +} + +# Pin for nmp-automodel-base. variable "BASE_TAG_AUTOMODEL" { - default = "local" + # From MR: https://gitlab-master.nvidia.com/aire/microservices/nmp/-/merge_requests/7315 + default = "dbdc61fdb117f48bc9569b4274afe836cf0de030" +} + +# Pin for nmp-customizer-tasks-base (built by nmp-customizer-tasks-base-builder) +variable "BASE_TAG_CUSTOMIZER_TASKS" { + # From MR: https://gitlab-master.nvidia.com/aire/microservices/nmp/-/merge_requests/7315 + default = "dbdc61fdb117f48bc9569b4274afe836cf0de030" } +# The tag for base images if needed variable "WHEELS_TAG" { default = "3fd6986ff173b598446ffac06d9be3f84b482495" } +variable "BAKE_CACHE_SOURCE_BRANCH" { + default = "" +} + +variable "BAKE_CACHE_TARGET_BRANCH" { + default = "" +} + +variable "PUBLISH_LATEST" { + default = false +} + +variable "CI_COMMIT_SHA" { + default = "" +} + +variable "BUILD_ARCH" { + default = "" +} + variable "CUDA_VERSION" { default = "12.8.1" } +variable "SAFE_SYNTHESIZER_CONTAINER_VARIANT" { + default = "cu129" +} + +variable "FAISS_VERSION" { + default = "v1.9.0" +} + +variable "FAISS_CUDA_ARCHS" { + default = "75;80;86;90" +} + +# Versions for the mamba wheel builder. variable "MAMBA_22_COMMIT" { default = "6b32be06d026e170b3fdaf3ae6282c5a6ff57b06" } @@ -74,33 +112,180 @@ variable "CAUSAL_CONV1D_VERSION" { default = "v1.5.3" } -# For local builds: --set "*.platform=linux/amd64" -variable "BUILD_PLATFORMS" { - default = ["linux/amd64", "linux/arm64"] +function "get_faiss_wheel_image" { + params = [] + result = "${WHEELS_REGISTRY}/faiss-gpu-wheel:${WHEELS_TAG}" } -# --------------------------------------------------------------------------- -# Automodel helpers -# --------------------------------------------------------------------------- +function "get_causal_conv1d_wheel_image" { + params = [] + result = "${WHEELS_REGISTRY}/causal-conv1d-wheel:${WHEELS_TAG}" +} + +function "get_mamba_ssm_wheel_image" { + params = [] + result = "${WHEELS_REGISTRY}/mamba-ssm-wheel:${WHEELS_TAG}" +} + +function "get_arch_tag" { + params = [] + result = BUILD_ARCH == "linux/arm64" ? "linux-arm64" : "linux-amd64" +} + +function "base_tags" { + params = [name] + result = [ + notequal(BAKE_TAG, "") ? "${IMAGE_REGISTRY}/${name}:${BAKE_TAG}" : "", + ] +} function "wheel_tags" { params = [name] - result = ["${WHEELS_REGISTRY}/${name}:${WHEELS_TAG}"] + result = [ + notequal(WHEELS_TAG, "") ? "${WHEELS_REGISTRY}/${name}:${WHEELS_TAG}" : "", + ] } -function "get_causal_conv1d_wheel_image" { +function "sha_and_maybe_latest_tags" { + params = [name] + result = [ + notequal(BAKE_TAG, "") ? "${IMAGE_REGISTRY}/${name}:${BAKE_TAG}" : "", + PUBLISH_LATEST ? "${IMAGE_REGISTRY}/${name}:latest" : "", + and(notequal(BAKE_TAG, ""), and(notequal("", CI_COMMIT_SHA), notequal(BAKE_TAG, CI_COMMIT_SHA))) ? "${IMAGE_REGISTRY}/${name}:${CI_COMMIT_SHA}" : "", + ] +} + +function "maybe_registry_cache_to" { + params = [name] + result = [ + and(notequal(BAKE_CACHE_TARGET_BRANCH, ""), notequal(BUILD_ARCH, "")) ? "type=registry,ref=${CACHE_REGISTRY}/${name}:${CACHE_VERSION}-${BAKE_CACHE_TARGET_BRANCH}-${get_arch_tag()},mode=max,compression=zstd,force-compression=true" : "" + ] +} + +function "image_output" { params = [] - result = "${WHEELS_REGISTRY}/causal-conv1d-wheel:${WHEELS_TAG}" + result = ["type=image,compression=zstd,force-compression=true"] } -function "get_mamba_ssm_wheel_image" { +function "maybe_registry_cache_from" { + params = [name] + result = [ + notequal(BAKE_CACHE_SOURCE_BRANCH, "") ? "type=registry,ref=${CACHE_REGISTRY}/${name}:${CACHE_VERSION}-${BAKE_CACHE_SOURCE_BRANCH}-linux-arm64" : "", + notequal(BAKE_CACHE_SOURCE_BRANCH, "") ? "type=registry,ref=${CACHE_REGISTRY}/${name}:${CACHE_VERSION}-${BAKE_CACHE_SOURCE_BRANCH}-linux-amd64" : "", + and(notequal(CACHE_REGISTRY_BACKUP, ""), notequal(BAKE_CACHE_SOURCE_BRANCH, "")) ? "type=registry,ref=${CACHE_REGISTRY_BACKUP}/${name}:${CACHE_VERSION}-${BAKE_CACHE_SOURCE_BRANCH}-linux-arm64" : "", + and(notequal(CACHE_REGISTRY_BACKUP, ""), notequal(BAKE_CACHE_SOURCE_BRANCH, "")) ? "type=registry,ref=${CACHE_REGISTRY_BACKUP}/${name}:${CACHE_VERSION}-${BAKE_CACHE_SOURCE_BRANCH}-linux-amd64" : "", + ] +} + +function "get_platforms" { params = [] - result = "${WHEELS_REGISTRY}/mamba-ssm-wheel:${WHEELS_TAG}" + result = BUILD_ARCH != "" ? [BUILD_ARCH] : ["linux/amd64", "linux/arm64"] +} + +# Semantic groups for parallel CI builds + +# Safe synthesizer job and api service +group "docker-safe-synthesizer" { + targets = [ + "safe-synthesizer-docker", + "safe-synthesizer-api-docker", + ] +} + +# Auditor images +group "docker-auditor" { + targets = [ + "auditor-tasks-docker", + ] +} + +# Data designer API and job image +group "docker-data" { + targets = [ + "data-designer-docker", + ] +} + +# Legacy groups for backward compatibility +group "docker-amd64-only" { + targets = [ + "customizer-rl-docker", + "customizer-tasks-docker", + "docker-gpu" + ] +} + +group "all-multi-platform" { + targets = [ + "docker-multi-platform", + ] +} + +group "docker-multi-platform" { + targets = [ + "docker-cpu", + "customizer-megatron-docker", + "auditor-tasks-docker", + ] +} + +group "docker-python-base" { + targets = [ + "nmp-python-base-builder", + "nmp-python-dev-base-builder", + ] +} + +group "all-arm64" { + targets = [ + "docker-multi-platform", + ] +} + +group "all-amd64" { + targets = [ + "docker-multi-platform", + "docker-amd64-only", + ] +} + +group "docker" { + targets = [ + "docker-cpu", + "docker-gpu", + "docker-customizer", + "docker-auditor", + ] +} + +# ============================================================================= +# Consolidated Container Builds +# ============================================================================= +# Consolidated container images for Python services and task runners. + +# Build groups for consolidated containers +group "docker-cpu" { + targets = [ + "nmp-api-docker", + "nmp-core-docker", + "nmp-cpu-tasks-docker", + ] +} + +group "docker-gpu" { + targets = [ + "safe-synthesizer-tasks-docker", + "safe-synthesizer-tasks-smoke-test", + ] } -# --------------------------------------------------------------------------- -# Groups -# --------------------------------------------------------------------------- +group "gpu-wheels" { + targets = [ + "faiss-gpu-wheel", + "causal-conv1d-wheel", + "mamba-ssm-wheel", + ] +} group "nmp-automodel-gpu-wheels" { targets = [ @@ -120,94 +305,656 @@ group "nmp-automodel" { } group "nmp-unsloth" { - targets = ["nmp-unsloth-training"] + targets = [ + "nmp-unsloth-training", + ] +} + +# Final Customizer Training Images - WARNING: No not build base CUDA images using shared buildkit +# Only build the final image. All base image builds should happen using arch specific runners. +group "docker-customizer" { + targets = [ + "customizer-rl-docker", + "customizer-rl-smoke-test", + "customizer-tasks-docker", + "customizer-tasks-smoke-test", + ] +} + +# Base images for consolidated containers +target "nmp-python-base" { + target = "nmp-python-base" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-python-base" + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_PYTHON = "${BASE_TAG_PYTHON}" + } + cache-from = maybe_registry_cache_from("nmp-python-base") + platforms = get_platforms() +} + +target "nmp-python-base-builder" { + target = "nmp-python-base-builder" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-python-base" + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_PYTHON = "${BASE_TAG_PYTHON}" + } + cache-to = maybe_registry_cache_to("nmp-python-base") + cache-from = maybe_registry_cache_from("nmp-python-base") + tags = base_tags("nmp-python-base") + output = image_output() + platforms = get_platforms() +} + +target "nmp-python-dev-base" { + target = "nmp-python-dev-base" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-python-base" + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_PYTHON = "${BASE_TAG_PYTHON}" + } + cache-from = maybe_registry_cache_from("nmp-python-dev-base") + platforms = get_platforms() +} + +target "nmp-python-dev-base-builder" { + target = "nmp-python-dev-base-builder" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-python-base" + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_PYTHON = "${BASE_TAG_PYTHON}" + } + cache-to = maybe_registry_cache_to("nmp-python-dev-base") + cache-from = maybe_registry_cache_from("nmp-python-dev-base") + tags = base_tags("nmp-python-dev-base") + output = image_output() + platforms = get_platforms() +} + +target "nmp-jobs-launcher" { + target = "artifacts" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-jobs-launcher" + platforms = get_platforms() +} + +target "nmp-studio-ui" { + target = "artifacts" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-studio-ui" + platforms = get_platforms() + args = { + VITE_VERSION_SHA = CI_COMMIT_SHA + DOCKERHUB_MIRROR = DOCKERHUB_MIRROR + } +} + +target "nmp-gpu-base" { + target = "nmp-gpu-base" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-gpu-base" + args = { + CUDA_VERSION = CUDA_VERSION + } + platforms = get_platforms() +} + +target "nmp-gpu-base-py312" { + target = "nmp-gpu-base-py312" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-gpu-base-py312" + args = { + CUDA_VERSION = CUDA_VERSION + } + platforms = get_platforms() +} + +target "nmp-gpu-runtime-base" { + target = "nmp-gpu-runtime-base" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-gpu-base" + contexts = { + nmp-gpu-base = "target:nmp-gpu-base" + } + platforms = get_platforms() +} + +# Shared workspace layer (copy all workspace files for uv sync) +target "nmp-workspace" { + target = "nmp-workspace" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-workspace" + contexts = { + nmp-python-base = "target:nmp-python-base" + } + args = { + NMP_BASE = "nmp-python-base" + } + platforms = get_platforms() +} + +target "nmp-gpu-workspace" { + target = "nmp-workspace" + context = "." + dockerfile = "docker/base/Dockerfile.nmp-workspace" + contexts = { + nmp-gpu-base = "target:nmp-gpu-base" + } + args = { + NMP_BASE = "nmp-gpu-base" + } + platforms = get_platforms() +} + +# NMP API - All Python services (core + application) +target "nmp-api-docker" { + target = "runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-api" + contexts = { + nmp-python-base = "target:nmp-python-base" + nmp-workspace = "target:nmp-workspace" + nmp-jobs-launcher = "target:nmp-jobs-launcher" + nmp-studio-ui = "target:nmp-studio-ui" + policy-wasm-artifacts = "target:root-policy-wasm-artifacts" + } + args = { + NMP_PLATFORM_VERSION = notequal(BAKE_TAG, "") ? BAKE_TAG : "dev" + NMP_CODE_REVISION = notequal(CI_COMMIT_SHA, "") ? CI_COMMIT_SHA : "dev" + } + cache-to = maybe_registry_cache_to("nmp-api") + cache-from = maybe_registry_cache_from("nmp-api") + tags = sha_and_maybe_latest_tags("nmp-api") + output = image_output() + platforms = get_platforms() +} + +# NMP Core - Core infrastructure services only +target "nmp-core-docker" { + target = "runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-core" + contexts = { + nmp-python-base = "target:nmp-python-base" + nmp-workspace = "target:nmp-workspace" + nmp-jobs-launcher = "target:nmp-jobs-launcher" + policy-wasm-artifacts = "target:root-policy-wasm-artifacts" + } + cache-to = maybe_registry_cache_to("nmp-core") + cache-from = maybe_registry_cache_from("nmp-core") + tags = sha_and_maybe_latest_tags("nmp-core") + output = image_output() + platforms = get_platforms() +} + +# NMP CPU Tasks - CPU-only batch task execution +target "nmp-cpu-tasks-docker" { + target = "runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-cpu-tasks" + contexts = { + nmp-python-base = "target:nmp-python-base" + nmp-workspace = "target:nmp-workspace" + } + cache-to = maybe_registry_cache_to("nmp-cpu-tasks") + cache-from = maybe_registry_cache_from("nmp-cpu-tasks") + tags = sha_and_maybe_latest_tags("nmp-cpu-tasks") + output = image_output() + platforms = get_platforms() +} + +# Safe Synthesizer Tasks - GPU-intensive Safe Synthesizer task execution + +# NMP Customizer Tasks - GPU Tasks for Customizer (transformers>=5.0.0, mamba-ssm 2.3.0) + +# Builder for the Customizer Tasks base image +target "nmp-customizer-tasks-base-builder" { + target = "nmp-customizer-tasks-base-builder" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer-tasks" + cache-to = maybe_registry_cache_to("nmp-customizer-tasks-base") + cache-from = maybe_registry_cache_from("nmp-customizer-tasks-base") + tags = base_tags("nmp-customizer-tasks-base") + output = image_output() + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" + FAISS_WHEEL_IMAGE = get_faiss_wheel_image() + CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() + MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + } + contexts = { + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" + } + platforms = ["linux/amd64"] +} + +# The base image for Customizer Tasks +target "nmp-customizer-tasks-base" { + target = "nmp-customizer-tasks-base" + context = "." + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" + FAISS_WHEEL_IMAGE = get_faiss_wheel_image() + CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() + MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + } + dockerfile = "docker/Dockerfile.nmp-customizer-tasks" + platforms = get_platforms() } -# --------------------------------------------------------------------------- -# Automodel — GPU wheels -# --------------------------------------------------------------------------- + +# FAISS GPU Wheel Builder +# Clones FAISS from GitHub and produces a Python wheel with CUDA support. +# The wheel lives at /faiss/wheels/*.whl inside the image. +target "faiss-gpu-wheel" { + target = "faiss-gpu-wheel" + context = "." + dockerfile = "docker/base/Dockerfile.faiss-gpu-wheel" + cache-to = maybe_registry_cache_to("faiss-gpu-wheel") + cache-from = maybe_registry_cache_from("faiss-gpu-wheel") + tags = wheel_tags("faiss-gpu-wheel") + output = image_output() + args = { + FAISS_VERSION = FAISS_VERSION + CUDA_ARCHS = FAISS_CUDA_ARCHS + } + platforms = get_platforms() +} + +# Mamba Wheel Builders +# Builds Python wheels for mamba-ssm and causal-conv1d in parallel. +# Both only ship source distributions on PyPI; this pre-builds them for +# amd64 and arm64. The wheels live at /wheels/*.whl inside the image. target "causal-conv1d-wheel" { target = "causal-conv1d-wheel" context = "." - dockerfile = "services/automodel/docker/Dockerfile.mamba-wheel" + dockerfile = "docker/base/Dockerfile.mamba-wheel" + cache-to = maybe_registry_cache_to("causal-conv1d-wheel") + cache-from = maybe_registry_cache_from("causal-conv1d-wheel") tags = wheel_tags("causal-conv1d-wheel") + output = image_output() args = { CUDA_VERSION = CUDA_VERSION CAUSAL_CONV1D_VERSION = CAUSAL_CONV1D_VERSION } - platforms = BUILD_PLATFORMS + platforms = get_platforms() } target "mamba-ssm-wheel" { target = "mamba-ssm-wheel" context = "." - dockerfile = "services/automodel/docker/Dockerfile.mamba-wheel" + dockerfile = "docker/base/Dockerfile.mamba-wheel" + cache-to = maybe_registry_cache_to("mamba-ssm-wheel") + cache-from = maybe_registry_cache_from("mamba-ssm-wheel") tags = wheel_tags("mamba-ssm-wheel") + output = image_output() args = { CUDA_VERSION = CUDA_VERSION MAMBA_22_COMMIT = MAMBA_22_COMMIT MAMBA_23_COMMIT = MAMBA_23_COMMIT } - platforms = BUILD_PLATFORMS + platforms = get_platforms() +} + + +target "safe-synthesizer-tasks-docker" { + target = "runtime" + context = "." + dockerfile = "docker/Dockerfile.safe-synthesizer-tasks" + args = { + CONTAINER_VARIANT = "${SAFE_SYNTHESIZER_CONTAINER_VARIANT}" + } + cache-to = maybe_registry_cache_to("safe-synthesizer-tasks") + cache-from = maybe_registry_cache_from("safe-synthesizer-tasks") + tags = sha_and_maybe_latest_tags("safe-synthesizer-tasks") + output = image_output() + #platforms = get_platforms() + platforms = ["linux/amd64"] +} + +# Smoke test - built in parallel with safe-synthesizer-tasks-docker, never pushed. +# Fails the build if any critical import fails (missing package or ABI mismatch). +target "safe-synthesizer-tasks-smoke-test" { + target = "smoke-test" + context = "." + dockerfile = "docker/Dockerfile.safe-synthesizer-tasks" + args = { + CONTAINER_VARIANT = "${SAFE_SYNTHESIZER_CONTAINER_VARIANT}" + } + cache-from = maybe_registry_cache_from("safe-synthesizer-tasks") + output = ["type=cacheonly"] + platforms = ["linux/amd64"] +} + +# root +target "root-artifact-base" { + target = "root-artifact-base" + context = "." + dockerfile = "docker/Dockerfile.bake" +} + +target "root-uv-artifacts" { + target = "root-uv-artifacts" + context = "." + dockerfile = "docker/Dockerfile.bake" +} + +target "root-distroless-base-3-11" { + target = "root-distroless-base-3-11" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() + args = { + DISTROLESS_BASE = DISTROLESS_BASE + } +} + +target "root-lib-source-artifacts" { + target = "root-lib-source-artifacts" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-service-source-artifacts" { + target = "root-service-source-artifacts" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-script-source-artifacts" { + target = "root-script-source-artifacts" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-golang-base" { + target = "root-golang-base" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-golang-base-1-24" { + target = "root-golang-base-1-24" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-golang-base-1-25" { + target = "root-golang-base-1-25" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-golang-artifacts" { + target = "root-golang-artifacts" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +# Auth policy WASM bundle (built from Rego during container build; multi-arch). +target "root-policy-wasm-artifacts" { + target = "root-policy-wasm-artifacts" + context = "." + dockerfile = "docker/base/Dockerfile.policy-wasm" + platforms = get_platforms() +} + +target "root-busybox" { + target = "root-busybox" + context = "." + dockerfile = "docker/Dockerfile.bake" + platforms = get_platforms() +} + +target "root-nmp-persistence-test" { + target = "root-nmp-persistence-test" + context = "." + dockerfile = "docker/Dockerfile.bake" + output = ["type=cacheonly"] + no-cache-filter = ["root-nmp-persistence-test"] +} + +target "root-nmp-common-test" { + target = "root-nmp-common-test" + context = "." + dockerfile = "docker/Dockerfile.bake" + output = ["type=cacheonly"] + no-cache-filter = ["root-nmp-common-test"] +} + +target "root-nemo-platform-test" { + target = "root-nemo-platform-test" + context = "." + dockerfile = "docker/Dockerfile.bake" + output = ["type=cacheonly"] + no-cache-filter = ["root-nemo-platform-test"] +} + +target "buildkit-test" { + target = "buildkit-test" + context = "." + dockerfile = "docker/Dockerfile.bake" + output = ["type=cacheonly"] + no-cache-filter = ["buildkit-test"] + platforms = get_platforms() +} + +# Customizer + +# Customizer base targets (used as context injection) +target "customizer-megatron-base" { + target = "customizer-megatron-base" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + platforms = get_platforms() +} + +target "customizer-rl-shared-base" { + target = "customizer-rl-shared-base" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + # nemo-rl only for amd64 at the moment + platforms = ["linux/amd64"] +} + +target "customizer-combined-base" { + target = "customizer-combined-base" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + platforms = ["linux/amd64"] +} + +# Customizer Base Build Targets + +# Customizer Final Images - base injected via contexts +target "customizer-combined-docker" { + target = "customizer-runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + contexts = { + nmp-workspace = "target:nmp-workspace" + customizer-base = "target:customizer-combined-base" + } + cache-to = maybe_registry_cache_to("customizer-combined") + cache-from = maybe_registry_cache_from("customizer-combined") + tags = sha_and_maybe_latest_tags("customizer-combined") + output = image_output() + platforms = ["linux/amd64"] +} + +target "customizer-megatron-docker" { + target = "customizer-runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + contexts = { + nmp-workspace = "target:nmp-workspace" + customizer-base = "target:customizer-megatron-base" + } + cache-to = maybe_registry_cache_to("customizer-megatron") + cache-from = maybe_registry_cache_from("customizer-megatron") + tags = sha_and_maybe_latest_tags("customizer-megatron") + output = image_output() + platforms = get_platforms() +} + +target "customizer-tasks-smoke-test" { + target = "smoke-test" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer-tasks" + contexts = { + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" + } + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" + FAISS_WHEEL_IMAGE = get_faiss_wheel_image() + CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() + MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + } + cache-from = maybe_registry_cache_from("customizer-tasks") + output = ["type=cacheonly"] + platforms = ["linux/amd64"] +} + +target "customizer-tasks-docker" { + target = "runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer-tasks" + contexts = { + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" + } + cache-to = maybe_registry_cache_to("customizer-tasks") + cache-from = maybe_registry_cache_from("customizer-tasks") + tags = sha_and_maybe_latest_tags("customizer-tasks") + output = image_output() + args = { + BASE_REGISTRY = "${BASE_REGISTRY}" + BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" + FAISS_WHEEL_IMAGE = get_faiss_wheel_image() + CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() + MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + } + platforms = ["linux/amd64"] +} + +target "customizer-rl-docker" { + target = "customizer-runtime" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + contexts = { + nmp-workspace = "target:nmp-workspace" + customizer-base = "target:customizer-rl-shared-base" + } + cache-to = maybe_registry_cache_to("customizer-rl") + cache-from = maybe_registry_cache_from("customizer-rl") + tags = sha_and_maybe_latest_tags("customizer-rl") + output = image_output() + # only amd64 for now, until the next release of nemo-rl + platforms = ["linux/amd64"] +} + +target "customizer-rl-smoke-test" { + target = "smoke-test" + context = "." + dockerfile = "docker/Dockerfile.nmp-customizer" + contexts = { + nmp-workspace = "target:nmp-workspace" + customizer-base = "target:customizer-rl-shared-base" + } + args = { + SMOKE_MARKER = "smoke_customizer_rl" + } + cache-from = maybe_registry_cache_from("customizer-rl") + output = ["type=cacheonly"] + platforms = ["linux/amd64"] } + +# Automodel and Unsloth + target "automodel-platform-workspace" { target = "platform-workspace" context = "." - dockerfile = "services/automodel/docker/Dockerfile.platform-workspace" - platforms = BUILD_PLATFORMS + dockerfile = "docker/automodel/Dockerfile.platform-workspace" + platforms = get_platforms() } target "nmp-automodel-base-builder" { target = "nmp-automodel-base" context = "." - dockerfile = "services/automodel/docker/Dockerfile.nmp-automodel-base" + dockerfile = "docker/Dockerfile.nmp-automodel-base" no-cache-filter = ["automodel-clone"] - tags = ["${IMAGE_REGISTRY}/nmp-automodel-base:${BAKE_TAG}"] + cache-to = maybe_registry_cache_to("nmp-automodel-base") + cache-from = maybe_registry_cache_from("nmp-automodel-base") + tags = base_tags("nmp-automodel-base") + output = image_output() args = { CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() } - platforms = BUILD_PLATFORMS + platforms = get_platforms() } target "nmp-automodel-tasks-docker" { target = "runtime" context = "." - dockerfile = "services/automodel/docker/Dockerfile.nmp-automodel-tasks" + dockerfile = "docker/Dockerfile.nmp-automodel-tasks" contexts = { platform-workspace = "target:automodel-platform-workspace" nmp-automodel-base = "target:nmp-automodel-base-builder" } - tags = ["${IMAGE_REGISTRY}/nmp-automodel-tasks:${BAKE_TAG}"] + cache-to = maybe_registry_cache_to("nmp-automodel-tasks") + cache-from = maybe_registry_cache_from("nmp-automodel-tasks") + tags = sha_and_maybe_latest_tags("nmp-automodel-tasks") + output = image_output() args = { BASE_REGISTRY = BASE_REGISTRY BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL } - platforms = BUILD_PLATFORMS + platforms = get_platforms() } target "nmp-automodel-training-docker" { target = "runtime" context = "." - dockerfile = "services/automodel/docker/Dockerfile.nmp-automodel-training" + dockerfile = "docker/Dockerfile.nmp-automodel-training" contexts = { platform-workspace = "target:automodel-platform-workspace" nmp-automodel-base = "target:nmp-automodel-base-builder" } - tags = ["${IMAGE_REGISTRY}/nmp-automodel-training:${BAKE_TAG}"] + cache-to = maybe_registry_cache_to("nmp-automodel-training") + cache-from = maybe_registry_cache_from("nmp-automodel-training") + tags = sha_and_maybe_latest_tags("nmp-automodel-training") + output = image_output() args = { BASE_REGISTRY = BASE_REGISTRY BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL } - platforms = BUILD_PLATFORMS + platforms = get_platforms() } target "nmp-automodel-tasks-smoke-test" { target = "smoke-test" context = "." - dockerfile = "services/automodel/docker/Dockerfile.nmp-automodel-tasks" + dockerfile = "docker/Dockerfile.nmp-automodel-tasks" contexts = { platform-workspace = "target:automodel-platform-workspace" nmp-automodel-base = "target:nmp-automodel-base-builder" @@ -217,14 +964,15 @@ target "nmp-automodel-tasks-smoke-test" { BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL SMOKE_MARKER = "smoke_nmp_automodel_tasks" } - output = ["type=cacheonly"] - platforms = BUILD_PLATFORMS + cache-from = maybe_registry_cache_from("nmp-automodel-tasks") + output = ["type=cacheonly"] + platforms = get_platforms() } target "nmp-automodel-training-smoke-test" { target = "smoke-test" context = "." - dockerfile = "services/automodel/docker/Dockerfile.nmp-automodel-training" + dockerfile = "docker/Dockerfile.nmp-automodel-training" contexts = { platform-workspace = "target:automodel-platform-workspace" nmp-automodel-base = "target:nmp-automodel-base-builder" @@ -234,29 +982,89 @@ target "nmp-automodel-training-smoke-test" { BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL SMOKE_MARKER = "smoke_nmp_automodel_training" } - output = ["type=cacheonly"] - platforms = BUILD_PLATFORMS + cache-from = maybe_registry_cache_from("nmp-automodel-training") + output = ["type=cacheonly"] + platforms = get_platforms() } -# --------------------------------------------------------------------------- -# Unsloth -# --------------------------------------------------------------------------- - target "unsloth-platform-workspace" { context = "." - dockerfile = "services/unsloth/docker/Dockerfile.platform-workspace" + dockerfile = "docker/unsloth/Dockerfile.platform-workspace" target = "platform-workspace" output = ["type=cacheonly"] - platforms = BUILD_PLATFORMS + platforms = get_platforms() } target "nmp-unsloth-training" { context = "." - dockerfile = "services/unsloth/docker/Dockerfile.nmp-unsloth-training" + dockerfile = "docker/Dockerfile.nmp-unsloth-training" target = "runtime" contexts = { platform-workspace = "target:unsloth-platform-workspace" } - tags = ["${IMAGE_REGISTRY}/nmp-unsloth-training:${BAKE_TAG}"] - platforms = BUILD_PLATFORMS + cache-to = maybe_registry_cache_to("nmp-unsloth-training") + cache-from = maybe_registry_cache_from("nmp-unsloth-training") + tags = sha_and_maybe_latest_tags("nmp-unsloth-training") + output = image_output() + platforms = get_platforms() +} + +# Guardrails Callout service (Envoy ext_proc gRPC) +target "guardrails-callout-test" { + target = "test" + contexts = { + root-golang-base = "target:root-golang-base-1-25" + } + cache-from = maybe_registry_cache_from("guardrails-callout") + context = "services/guardrails/callouts" + dockerfile = "../../../docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake" + output = ["type=cacheonly"] + no-cache-filter = ["test"] +} + +target "guardrails-callout-docker" { + target = "docker" + contexts = { + root-golang-base = "target:root-golang-base-1-25" + } + context = "services/guardrails/callouts" + dockerfile = "../../../docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake" + cache-to = maybe_registry_cache_to("guardrails-callout") + cache-from = maybe_registry_cache_from("guardrails-callout") + tags = sha_and_maybe_latest_tags("guardrails-callout") + output = image_output() + platforms = get_platforms() +} + +# Optional: mock LLM backend +# Do not add to the docker group to avoid publishing. +target "guardrails-callout-mock-llm" { + target = "mock-llm" + contexts = { + root-golang-base = "target:root-golang-base-1-25" + } + context = "services/guardrails/callouts" + dockerfile = "../../../docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake" + tags = sha_and_maybe_latest_tags("guardrails-callout-mock-llm") + output = image_output() + platforms = get_platforms() +} + +# Auditor +target "auditor-tasks-docker" { + target = "release" + context = "." + contexts = { + root-lib-source-artifacts = "target:root-lib-source-artifacts" + root-busybox = "target:root-busybox" + nmp-python-base = "target:nmp-python-base" + nmp-python-dev-base = "target:nmp-python-dev-base" + root-distroless-base-3-11 = "target:root-distroless-base-3-11" + } + dockerfile = "docker/Dockerfile.auditor-tasks" + cache-to = maybe_registry_cache_to("auditor-tasks") + cache-from = maybe_registry_cache_from("auditor-tasks") + tags = sha_and_maybe_latest_tags("auditor-tasks") + output = image_output() + platforms = get_platforms() } diff --git a/docker/Dockerfile.auditor-tasks b/docker/Dockerfile.auditor-tasks new file mode 100644 index 0000000000..f6d7846f5c --- /dev/null +++ b/docker/Dockerfile.auditor-tasks @@ -0,0 +1,158 @@ +# hadolint global ignore=DL3059 +# distroless images don't have a real shell - & and | are not enabled + +ARG NMP_PYTHON_BASE=nmp-python-base + +ARG CACHE_HOME=/tmp/.cache + +FROM nmp-python-dev-base AS py-builder + +ARG TARGETARCH + +ARG CACHE_HOME +RUN mkdir -p ${CACHE_HOME} && chmod 777 ${CACHE_HOME} + +# Create the directory structure to match the relative paths in pyproject.toml +# Copy dependencies that auditor references +# Copy the workspace lock file and pyproject.toml for workspace dependency resolution +COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages +COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk +COPY --from=root-lib-source-artifacts /artifacts/skills /app/skills +COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs +COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi +COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + +# Install Python dependencies using uv with workspace lock file +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --no-dev --no-install-workspace --no-editable --package=nemo-auditor-plugin + +# Copy auditor service source code +# We need much more because the wheel installation depends on these files +# being present +COPY plugins/ ./plugins/ +COPY skills/ ./skills/ +COPY services/ ./services/ + +# Install the auditor package +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-editable --no-dev --package=nemo-auditor-plugin + +# Set environment variables +ENV PYTHONPATH=/app/plugins/nemo-auditor/src:/app/packages/nmp_common/src:/app/sdk/python/nemo-platform/src +ENV PATH="/app/.venv/bin:$PATH" + +RUN uv venv --no-project /app/.garak_venv +# CVE remediation for garak venv (transitive deps pinned by garak that are below safe versions) +# litellm: CVE-2026-35029 (affects <1.83.0) +# langchain-core: CVE-2025-68664 (affects <1.2.5) + GHSA-qh6h-p6c9-ff54 (affects <1.2.22) +RUN --mount=type=cache,target=/root/.cache/uv \ + VIRTUAL_ENV=/app/.garak_venv \ + uv sync --frozen --active --project /app/plugins/nemo-auditor/third-party --no-dev --no-editable && \ + uv pip install --python /app/.garak_venv/bin/python "setuptools>=78.1.1" && \ + uv pip install --no-deps --python /app/.garak_venv/bin/python \ + "litellm>=1.83.0" \ + "urllib3>=2.6.3" \ + "aiohttp>=3.13.3" \ + "pyasn1>=0.6.3" \ + "protobuf>=6.33.5,<7.0.0" \ + "langchain-core>=1.2.22" \ + "orjson>=3.11.6" \ + "nltk>=3.9.3" + +# CVE Remediation +RUN --mount=type=cache,target=/root/.cache/uv \ + uv pip install --no-deps \ + "urllib3>=2.6.3" \ + "protobuf>=5.29.6,<6.0.0" \ + "aiohttp>=3.13.3" \ + "pyasn1>=0.6.3" \ + "wheel>=0.46.2" \ + "orjson>=3.11.6" \ + "nltk>=3.9.3" + +FROM ${NMP_PYTHON_BASE} AS base +ARG USERNAME=nvs +ARG USER_UID=1000 +ARG USER_GID=1000 +WORKDIR /app +RUN groupadd --gid ${USER_GID} ${USERNAME} && \ + useradd --uid ${USER_UID} --gid ${USER_GID} --create-home --shell /bin/bash ${USERNAME} + +FROM base AS core-final + +# Copying handful commands into the destination image +COPY --from=root-busybox /bin/chmod /bin/chmod +COPY --from=root-busybox /bin/sh /bin/sh +COPY --from=root-busybox /bin/ls /bin/ls + +# Commands needed by auditor service +COPY --from=root-busybox /bin/mkdir /bin/mkdir +COPY --from=root-busybox /bin/ln /bin/ln +COPY --from=root-busybox /bin/rm /bin/rm +COPY --from=root-busybox /bin/cp /bin/cp +COPY --from=root-busybox /bin/wget /bin/wget +COPY --from=root-busybox /bin/tar /bin/tar +COPY --from=root-busybox /bin/mv /bin/mv +COPY --from=root-busybox /bin/cat /bin/cat +COPY --from=root-busybox /bin/head /bin/head +COPY --from=root-busybox /bin/tail /bin/tail +COPY --from=root-busybox /bin/grep /bin/grep + +# Debugging utilities +COPY --from=root-busybox /bin/ps /bin/ps +COPY --from=root-busybox /bin/which /bin/which +COPY --from=root-busybox /bin/find /bin/find +COPY --from=root-busybox /bin/more /bin/more +COPY --from=root-busybox /bin/less /bin/less +COPY --from=root-busybox /bin/ip /bin/ip +COPY --from=root-busybox /bin/top /bin/top +COPY --from=root-busybox /bin/kill /bin/kill +COPY --from=root-busybox /bin/netstat /bin/netstat +COPY --from=root-busybox /bin/wc /bin/wc + +WORKDIR /app/plugins/nemo-auditor + +# note: PY_VERSION is set within the container +ENV PYTHONPATH=/app/plugins/nemo-auditor/src:/app/packages/nmp_common/src:/app/sdk/python/nemo-platform/src +ENV PATH="/app/.venv/bin:$PATH" + +# Copy installed packages and application +COPY --chown=nvs:nvs --from=py-builder /app /app +ARG CACHE_HOME +COPY --chown=nvs:nvs --from=py-builder ${CACHE_HOME} ${CACHE_HOME} + +ENV USERNAME=nvs \ + HOME=/home/nvs +USER nvs + +FROM core-final AS dev + +USER nvs + +WORKDIR /app/plugins/nemo-auditor + +# Set environment variables for auditor service +ARG CACHE_HOME +ENV AUDITOR_HOST="0.0.0.0" \ + AUDITOR_PORT="5000" \ + CACHE_HOME=${CACHE_HOME} + +# Start the application +EXPOSE 5000 + +CMD ["sh", "-c", "python -m uvicorn src.auditor.server:app --host $AUDITOR_HOST --port $AUDITOR_PORT"] + +FROM core-final AS release + +WORKDIR /app/plugins/nemo-auditor + +# Set environment variables for auditor service +ARG CACHE_HOME +ENV AUDITOR_HOST="0.0.0.0" \ + AUDITOR_PORT="5000" \ + CACHE_HOME=${CACHE_HOME} + +# Start the application +EXPOSE 5000 + +CMD ["sh", "-c", "python -m uvicorn src.auditor.server:app --host $AUDITOR_HOST --port $AUDITOR_PORT"] diff --git a/docker/Dockerfile.bake b/docker/Dockerfile.bake new file mode 100644 index 0000000000..69a1a1a77e --- /dev/null +++ b/docker/Dockerfile.bake @@ -0,0 +1,133 @@ +####### +# Common Build Targets. Please use `docker buildx bake` to build. See `README.md` +####### + +ARG DISTROLESS_BASE=gitlab-master.nvidia.com:5005/aire/platform/ci-cd/images/distroless-nmp/python3.11:1.0.7 + +#root-uv-binary-base + FROM ghcr.io/astral-sh/uv:0.9.14 AS root-uv-binary-base + +#root-lib-source-artifacts + FROM scratch AS root-lib-source-artifacts + WORKDIR /artifacts + COPY packages /artifacts/packages + COPY sdk /artifacts/sdk + COPY skills /artifacts/skills + COPY docs /artifacts/docs + COPY openapi /artifacts/openapi + COPY uv.lock pyproject.toml /artifacts/ + +#root-service-source-artifacts: + FROM scratch AS root-service-source-artifacts + COPY services /artifacts/services + +#root-script-source-artifacts: + FROM scratch AS root-script-source-artifacts + COPY ./script /artifacts/script + +#root-python-3-11: + # Copy uv and install other utilities + FROM python:3.11.13-slim-bookworm AS root-python-3-11 + RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + g++ \ + git \ + libcurl4 \ + libcurl3-gnutls \ + libffi-dev \ + libpq-dev \ + libssl-dev \ + postgresql \ + postgresql-client \ + postgresql-common \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + + WORKDIR /app + COPY --from=root-uv-binary-base /uv /bin/uv + +#root-busybox: + FROM busybox:1.37.0-musl AS root-busybox + +#root-distroless-base-3-11 + FROM ${DISTROLESS_BASE} AS root-distroless-base-3-11 + +#root-golang-base: + FROM golang:1.23 AS root-golang-base + RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz | tar -xzv && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/ + +#root-golang-base-1-24: + FROM golang:1.24 AS root-golang-base-1-24 + RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz | tar -xzv && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/ + +#root-golang-base-1-25: + FROM golang:1.25 AS root-golang-base-1-25 + RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v2.11.4/golangci-lint-2.11.4-linux-amd64.tar.gz -o golangci-lint.tar.gz \ + && echo "200c5b7503f67b59a6743ccf32133026c174e272b930ee79aa2aa6f37aca7ef1 golangci-lint.tar.gz" | sha256sum -c \ + && tar -xzf golangci-lint.tar.gz \ + && mv golangci-lint-2.11.4-linux-amd64/golangci-lint /usr/local/bin/ \ + && rm -rf golangci-lint.tar.gz golangci-lint-2.11.4-linux-amd64 + +#root-golang-artifacts: + FROM scratch AS root-golang-artifacts + COPY .golangci.yml /artifacts/ + +# TESTS +#nmp-persistence-base: + FROM root-python-3-11 AS root-nmp-persistence-base + COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs + COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi + RUN uv sync --frozen --no-install-project --no-install-workspace --package=nmp-persistence + COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages + COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk + RUN uv sync --frozen --package=nmp-persistence + +#nmp-persistence-test: + FROM root-nmp-persistence-base AS root-nmp-persistence-test + RUN uv run --no-sync pytest packages/nmp_persistence + +#nmp-common-base: + FROM root-python-3-11 AS root-nmp-common-base + COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs + COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi + RUN uv sync --frozen --no-install-project --no-install-workspace --package=nmp-common + COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages + COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk + RUN uv sync --frozen --package=nmp-common + +#nmp-common-test: + FROM root-nmp-common-base AS root-nmp-common-test + RUN uv run --no-sync pytest packages/nmp_common + +#nemo-platform-base: + FROM root-python-3-11 AS root-nemo-platform-base + COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs + COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi + RUN uv sync --frozen --no-install-project --no-install-workspace --package=nemoplatform + COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages + COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk + COPY --from=root-service-source-artifacts /artifacts/services /app/services + + # nemoplatform is a top level package + COPY src README.md /app/ + RUN uv sync --frozen --no-dev + +#nemo-platform-test: + FROM root-nemo-platform-base AS root-nemo-platform-test + COPY ./tests /app/tests + RUN uv run --no-sync pytest tests/integration + +#buildkit-test: + FROM alpine:latest AS buildkit-test + RUN ps exua -T + RUN netstat -tnlp + RUN df -h + RUN uname -a + RUN cat /proc/stat | grep "cpu " + RUN cat /proc/loadavg + RUN top -bmH -n1 diff --git a/docker/Dockerfile.nmp-api b/docker/Dockerfile.nmp-api new file mode 100644 index 0000000000..7bcab091a4 --- /dev/null +++ b/docker/Dockerfile.nmp-api @@ -0,0 +1,65 @@ +# syntax=docker/dockerfile:1 +# NMP API - All Python services (core + application) + +ARG NMP_PYTHON_BASE=nmp-python-base + +FROM ${NMP_PYTHON_BASE} AS builder +COPY --from=nmp-workspace . . + +# Prevents annoy (a dependency of the nemoguardrails library) from generating CPU-specific instructions +# that cause Guardrails to crash at inference-time with SIGILL on AVX2-only CPUs (e.g. AMD EPYC Zen 2). +# When ANNOY_COMPILER_ARGS is set, annoy uses these arguments as-is, replacing its defaults. +ENV ANNOY_COMPILER_ARGS="-D_CRT_SECURE_NO_WARNINGS,-DANNOYLIB_MULTITHREADED_BUILD" + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --only-group functional-services --no-editable + +# annoy must be reinstalled from the committed lockfile without wheel cache reuse: uv's wheel cache +# key does not include env vars, so the first sync can reuse a stale native-compiled wheel that +# ignores ANNOY_COMPILER_ARGS. +RUN --mount=type=cache,target=/root/.cache/uv \ + VIRTUAL_ENV=/app/.venv \ + uv sync --frozen --active --only-group functional-services --no-editable \ + --reinstall-package annoy --refresh-package annoy --no-binary-package annoy --no-cache + +# Pre-install DuckDB aws + httpfs so OTLP log ingestion works without outbound network. +# Download the extension artifacts directly over HTTPS and install from local files +# because DuckDB's default remote INSTALL path can be blocked or flaky in some build environments. +RUN sh /app/script/install_duckdb_extensions.sh + +# Download embedding model used by `nemoguardrails` library. +# Uses snapshot_download directly to avoid importing onnxruntime, which crashes under QEMU (ARM64 cross-compilation). +# Stored in /tmp/fastembed_cache (fastembed's default cache location) and copied to the runtime image below. +RUN uv run --no-sync python -c 'import os, tempfile; from huggingface_hub import snapshot_download; snapshot_download(repo_id="qdrant/all-MiniLM-L6-v2-onnx", cache_dir=os.path.join(tempfile.gettempdir(), "fastembed_cache"))' + +FROM ${NMP_PYTHON_BASE} AS runtime +ARG USERNAME=nvs +ARG USER_UID=1000 +ARG USER_GID=1000 +WORKDIR /app +RUN groupadd --gid ${USER_GID} ${USERNAME} && \ + useradd --uid ${USER_UID} --gid ${USER_GID} --create-home --shell /bin/bash ${USERNAME} +ENV USERNAME=nvs \ + HOME=/home/nvs +# DuckDB extension cache (aws, httpfs) for S3 log storage +COPY --chown=nvs:nvs --from=builder /root/.duckdb /root/.duckdb +COPY --chown=nvs:nvs --from=builder /root/.duckdb /home/nvs/.duckdb +COPY --chown=nvs:nvs --from=builder /app/.venv /app/.venv +# Authz evaluates the packaged policy WASM from nmp.core.auth.assets at runtime. +COPY --chown=nvs:nvs --from=policy-wasm-artifacts /artifacts/policy.wasm /app/.venv/lib/python3.11/site-packages/nmp/core/auth/assets/policy.wasm +ENV PATH="/app/.venv/bin:$PATH" +COPY --chown=nvs:nvs --from=nmp-jobs-launcher /artifacts/jobs-launcher /tools/jobs-launcher +COPY --chown=nvs:nvs --from=nmp-studio-ui /artifacts /static/studio +COPY --chown=nvs:nvs --from=builder /app/plugins/nemo-data-designer/tiktoken-cache /app/tiktoken-cache +ENV TIKTOKEN_CACHE_DIR=/app/tiktoken-cache +COPY --chown=nvs:nvs --from=builder /tmp/fastembed_cache /tmp/fastembed_cache +ARG NMP_PLATFORM_VERSION=dev +ARG NMP_CODE_REVISION= +ENV NMP_PLATFORM_VERSION=${NMP_PLATFORM_VERSION} +ENV NMP_CODE_REVISION=${NMP_CODE_REVISION} +EXPOSE 8080 +USER 0 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health/ready')"] +ENTRYPOINT ["nemo", "services", "run"] +CMD ["--host", "0.0.0.0"] diff --git a/services/automodel/docker/Dockerfile.nmp-automodel-base b/docker/Dockerfile.nmp-automodel-base similarity index 98% rename from services/automodel/docker/Dockerfile.nmp-automodel-base rename to docker/Dockerfile.nmp-automodel-base index 6ffc87f694..974e29d684 100644 --- a/services/automodel/docker/Dockerfile.nmp-automodel-base +++ b/docker/Dockerfile.nmp-automodel-base @@ -33,7 +33,7 @@ ENV PATH="/opt/venv/bin:/root/.local/bin:$PATH" RUN uv venv ${UV_PROJECT_ENVIRONMENT} --system-site-packages COPY --from=automodel-clone /opt/Automodel /opt/Automodel -COPY services/automodel/docker/cherry-picks /opt/cherry-picks +COPY docker/automodel/cherry-picks /opt/cherry-picks RUN cd /opt/Automodel && patch -p1 < /opt/cherry-picks/e6d2930a.diff RUN cd /opt/Automodel && \ diff --git a/services/automodel/docker/Dockerfile.nmp-automodel-tasks b/docker/Dockerfile.nmp-automodel-tasks similarity index 100% rename from services/automodel/docker/Dockerfile.nmp-automodel-tasks rename to docker/Dockerfile.nmp-automodel-tasks diff --git a/services/automodel/docker/Dockerfile.nmp-automodel-training b/docker/Dockerfile.nmp-automodel-training similarity index 95% rename from services/automodel/docker/Dockerfile.nmp-automodel-training rename to docker/Dockerfile.nmp-automodel-training index 660f17af0a..1a646630c1 100644 --- a/services/automodel/docker/Dockerfile.nmp-automodel-training +++ b/docker/Dockerfile.nmp-automodel-training @@ -28,7 +28,7 @@ RUN mkdir -p /home/${USERNAME}/.cache && \ RUN --mount=type=cache,target=/root/.cache/uv \ uv pip install --python ${VIRTUAL_ENV}/bin/python --no-cache \ - --overrides /app/services/automodel/docker/no_override_requirements.txt \ + --overrides /app/docker/automodel/no_override_requirements.txt \ -e /app/sdk/python/nemo-platform \ -e /app/packages/nemo_platform_plugin \ -e /app/packages/nmp_common \ diff --git a/docker/Dockerfile.nmp-core b/docker/Dockerfile.nmp-core new file mode 100644 index 0000000000..6b2fb80371 --- /dev/null +++ b/docker/Dockerfile.nmp-core @@ -0,0 +1,36 @@ +# syntax=docker/dockerfile:1 +# NMP Core - Minimal core infrastructure APIs + +ARG NMP_PYTHON_BASE=nmp-python-base + +FROM ${NMP_PYTHON_BASE} AS builder +COPY --from=nmp-workspace . . +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --only-group core-services --no-editable +# Pre-install DuckDB aws + httpfs so OTLP log ingestion works without outbound network. +# Download the extension artifacts directly over HTTPS and install from local files +# because DuckDB's default remote INSTALL path can be blocked or flaky in some build environments. +RUN sh /app/script/install_duckdb_extensions.sh + +FROM ${NMP_PYTHON_BASE} AS runtime +ARG USERNAME=nvs +ARG USER_UID=1000 +ARG USER_GID=1000 +WORKDIR /app +RUN groupadd --gid ${USER_GID} ${USERNAME} && \ + useradd --uid ${USER_UID} --gid ${USER_GID} --create-home --shell /bin/bash ${USERNAME} +ENV USERNAME=nvs \ + HOME=/home/nvs +# DuckDB extension cache (aws, httpfs) for S3 log storage +COPY --chown=nvs:nvs --from=builder /root/.duckdb /root/.duckdb +COPY --chown=nvs:nvs --from=builder /root/.duckdb /home/nvs/.duckdb +COPY --chown=nvs:nvs --from=builder /app/.venv /app/.venv +# Authz evaluates the packaged policy WASM from nmp.core.auth.assets at runtime. +COPY --chown=nvs:nvs --from=policy-wasm-artifacts /artifacts/policy.wasm /app/.venv/lib/python3.11/site-packages/nmp/core/auth/assets/policy.wasm +ENV PATH="/app/.venv/bin:$PATH" +COPY --chown=nvs:nvs --from=nmp-jobs-launcher /artifacts/jobs-launcher /tools/jobs-launcher +ENTRYPOINT ["nemo", "services", "run"] +EXPOSE 8080 +USER 0 +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8080/health/ready')"] diff --git a/docker/Dockerfile.nmp-cpu-tasks b/docker/Dockerfile.nmp-cpu-tasks new file mode 100644 index 0000000000..f7a83e0db1 --- /dev/null +++ b/docker/Dockerfile.nmp-cpu-tasks @@ -0,0 +1,25 @@ +# syntax=docker/dockerfile:1 +# NMP CPU Tasks - CPU-only batch task execution + +ARG NMP_PYTHON_BASE=nmp-python-base + +FROM ${NMP_PYTHON_BASE} AS builder +COPY --from=nmp-workspace . . +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --only-group cpu-tasks --no-editable +# Evaluator task entry points now come from the plugin; keep this until the +# upstream cpu-tasks group includes nemo-evaluator-plugin directly. +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --inexact --package nemo-evaluator-plugin --no-editable + +FROM ${NMP_PYTHON_BASE} AS runtime +ENV USERNAME=nvs \ + HOME=/home/nvs +COPY --chown=nvs:nvs --from=builder /app/.venv /app/.venv +COPY --chown=nvs:nvs --from=builder /app/plugins/nemo-data-designer/tiktoken-cache /app/tiktoken-cache +ENV TIKTOKEN_CACHE_DIR=/app/tiktoken-cache +ENV PATH="/app/.venv/bin:$PATH" +USER 0 +ENTRYPOINT ["nemo-platform"] +# Override CMD with "run" in orchestration (e.g., Helm, docker-compose) +CMD ["--help"] diff --git a/docker/Dockerfile.nmp-customizer b/docker/Dockerfile.nmp-customizer new file mode 100644 index 0000000000..4aec1fdca1 --- /dev/null +++ b/docker/Dockerfile.nmp-customizer @@ -0,0 +1,213 @@ +####### +# Legacy Customizer runtime Dockerfile. +# +# This file keeps the non-Automodel customizer images available while the team +# decides whether to retire them: +# - combined - includes nemo-rl plus Automodel/Megatron extras for comparison +# - rl - includes nemo-rl v0.4.0 +# - megatron - derived from nvidia/nemo:25.11.01 +# +# The old customizer-automodel image and base builder are intentionally not +# defined here. +####### + +ARG SMOKE_MARKER=smoke_customizer_rl + +# BASE IMAGES + +#customizer-rl-shared-base: +FROM nvcr.io/nvidia/nemo-rl:v0.4.0 AS customizer-rl-shared-base + +# setup virtual environment properly +ENV VIRTUAL_ENV=/opt/nemo_rl_venv \ + NO_OVERRIDE_REQUIREMENTS_PATH=/app/services/customizer/src/nmp/customizer/tasks/training/backends/nemo_rl/no_override_requirements.txt + +# nvcr.io/nvidia/nemo-rl:v0.4.0's VENV is built with root and the .cache and .local are unavailable... +RUN chmod 755 /root /root/.cache /root/.cache/uv /root/.local /root/.local/share /root/.local/share/uv + +# CVE fix: dirmngr (CVE-2025-68973): upgrade to 2.2.27-3ubuntu2.5+ +# CVE-2025-68973 (High, out-of-SLA): Upgrade gnupg packages. +# --only-upgrade: upgrades installed packages only; skips absent ones silently. +# Remove unused nsight cli which is out of date +RUN apt-get update && apt-get install -y --only-upgrade \ + gnupg gnupg2 gpg gpg-agent gpgconf gpgsm \ + dirmngr gnupg-utils gpgv \ + && dpkg-query -f '${binary:Package}\n' -W 'nsight*' | xargs -r apt-get purge -y \ + && apt-get autoremove \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +# Fix CVE security vulnerabilities in pre-installed packages. +# NOTE: These overrides are done here in the Dockerfile rather than in pyproject.toml +# because the vulnerable packages come pre-installed in the NGC base image venv +# (nvcr.io/nvidia/nemo-rl:v0.4.0), not from our uv workspace. The uv workspace's +# override-dependencies only affect packages resolved by our own services; they have +# no effect on packages already baked into the base image's virtual environment. +# --no-deps is essential here for two reasons: +# 1. The nemo-rl base image ships several packages with corrupted dist-info +# (missing METADATA files). Dependency resolution reads all installed +# package metadata, so it fails before installing anything. --no-deps +# skips resolution entirely. +# 2. Without --no-deps, uv would pull transitive dependencies and potentially +# replace NGC-optimized builds of torch, numpy, etc. with generic PyPI +# wheels, breaking GPU functionality. --no-deps installs only the listed +# packages, leaving all other installed packages untouched. +RUN uv pip install --upgrade setuptools && \ + uv pip install --no-deps --no-cache \ + 'anyio>=4.10.0' \ + "urllib3>=2.6.3" \ + "starlette>=0.49.1" \ + "protobuf>=5.29.6,<6.0.dev0" \ + "cryptography>=46.0.5" \ + "python-multipart>=0.0.20" \ + "aiohttp>=3.13.3" \ + "cbor2>=5.9.0" \ + "pyasn1>=0.6.3" \ + "xgrammar>=0.1.32" \ + "Pillow>=12.1.1" \ + "wheel>=0.46.2" \ + "nltk>=3.9.3" \ + 'mlflow>=3.8.0rc0' \ + 'filelock>=3.25.0' + +# ray CVE fix (GHSA-6wgj-66m2-xxp2, GHSA-gx77-xgc2-4888, GHSA-q279-jhrf-cc6v): +# ray 2.52+ restructured internal modules (ray._private to ray._common). A simple +# overlay leaves remnant files from the old version. We must: +# 1. Purge the old ray package directory and dist-info entirely +# 2. Remove pre-built worker venvs (/opt/ray_venvs) so NeMo-RL rebuilds them +# with the new ray version at runtime +# 3. Install the new ray wheel cleanly +RUN set -e && \ + rm -rf $VIRTUAL_ENV/lib/python3.12/site-packages/ray \ + $VIRTUAL_ENV/lib/python3.12/site-packages/ray-*.dist-info && \ + uv pip install --no-deps --no-cache 'ray>=2.52.0' && \ + for venv_dir in /opt/ray_venvs/*/; do \ + [ -d "$venv_dir/lib" ] || continue; \ + rm -rf "$venv_dir"/lib/python*/site-packages/ray \ + "$venv_dir"/lib/python*/site-packages/ray-*.dist-info; \ + uv pip install --no-deps --no-cache --python "$venv_dir/bin/python" 'ray>=2.52.0'; \ + done && \ + python3 -c "import ray; print(f'ray {ray.__version__} at {ray.__file__}'); from ray._common.ray_constants import LOGGING_ROTATE_BYTES; print('ray._common OK')" + +# vllm CVE fix (GHSA-4r2x-xpjr-7cvv, GHSA-3f6c-7fw2-ppm4, GHSA-7972-pg2x-xr59, +# GHSA-8fr4-5q9j-m8gm, GHSA-pmqf-x6x8-p7qw, GHSA-qh4c-xf7m-gxfc, +# GHSA-wr9h-g72x-mwhm, GHSA-79j6-g2m3-jgfw, GHSA-rxc4-3w6r-4v47): +# vllm is not used by the customizer's current training paths (DPO, SFT, LoRA). +# It ships in the nemo-rl base image for generation/rollout (GRPO) which is not +# yet implemented in v2. Removing it eliminates the CVEs without an upgrade. +RUN rm -rf $VIRTUAL_ENV/lib/python3.12/site-packages/vllm \ + $VIRTUAL_ENV/lib/python3.12/site-packages/vllm-*.dist-info && \ + for venv_dir in /opt/ray_venvs/*/; do \ + [ -d "$venv_dir/lib" ] || continue; \ + rm -rf "$venv_dir"/lib/python*/site-packages/vllm \ + "$venv_dir"/lib/python*/site-packages/vllm-*.dist-info; \ + done + +#customizer-combined-base: +FROM customizer-rl-shared-base AS customizer-combined-base +# add megatron and automodel, not installed by default +RUN --mount=type=cache,target=/root/.cache/uv \ + cd /opt/nemo-rl && UV_HTTP_TIMEOUT=120 uv sync --frozen --extra automodel --extra mcore + +#customizer-megatron-base +FROM nvcr.io/nvidia/nemo:25.11.01 AS customizer-megatron-base +# nemo contains most recent version of megatron +ENV VIRTUAL_ENV=/opt/venv + +# CVE fix: dirmngr (CVE-2025-68973): upgrade to 2.2.27-3ubuntu2.5+ +# CVE-2025-68973 (High, out-of-SLA): Upgrade gnupg packages. +# --only-upgrade: upgrades installed packages only; skips absent ones silently. +# Remove unused nsight cli which is out of date +# Remove vllm which is not used (remediation) +RUN apt-get update && apt-get install -y --only-upgrade \ + gnupg gnupg2 gpg gpg-agent gpgconf gpgsm \ + dirmngr gnupg-utils gpgv \ + && dpkg-query -f '${binary:Package}\n' -W 'nsight*' | xargs -r apt-get purge -y \ + && apt-get autoremove \ + && apt-get clean && rm -rf /var/lib/apt/lists/* && \ + rm -rf /usr/local/lib/python*/site-packages/vllm \ + /usr/local/lib/python*/site-packages/vllm-*.dist-info + +# Fix CVE security vulnerabilities in pre-installed packages. +# NOTE: These overrides are done here in the Dockerfile rather than in pyproject.toml +# because the vulnerable packages come pre-installed in the NGC base image venv +# (nvcr.io/nvidia/nemo:25.11.01), not from our uv workspace. The uv workspace's +# override-dependencies only affect packages resolved by our own services; they have +# no effect on packages already baked into the base image's virtual environment. +# nemo-toolkit is intentionally NOT upgraded here: it is tightly coupled to the +# NGC PyTorch build (nvcr.io/nvidia/nemo:25.11.01 uses a custom PyTorch 2.9.0a0 +# with CUDA 13.0 patches). Upgrading could silently replace the NGC build with a +# PyPI wheel lacking CUDA 13.0 support and break distributed training. +# Track via GHSA-9379-mwvr-7wxx/GHSA-hvjw-vp7g-39h5 (nemo-toolkit). +RUN uv pip install --no-cache --python $VIRTUAL_ENV/bin/python \ + "urllib3>=2.6.3" \ + "black>=26.3.1" \ + "cbor2>=5.9.0" \ + "pyasn1>=0.6.3" \ + "aiohttp>=3.13.3" \ + "xgrammar>=0.1.32" \ + "Pillow>=12.1.1" \ + "nbconvert>=7.17.0" \ + 'nemo-toolkit>=2.6.1' \ + 'tornado>=6.5.5' \ + "onnx>=1.21.0" +# onnx: CVE-2026-28500 has no upstream patch yet; track for future upgrade + +## FINAL RUNTIME IMAGE +FROM customizer-base AS customizer-runtime + +# Default environment variables +ENV GUNICORN_WORKERS=1 \ + PORT=8000 \ + HAYSTACK_TELEMETRY_ENABLED=False \ + HF_HUB_ENABLE_HF_TRANSFER=1 \ + OTEL_PYTHON_EXCLUDED_URLS="health" + +ARG USERNAME=ubuntu +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +COPY --chown=${USER_UID}:${USER_GID} --from=nmp-workspace / /app +ENV USERNAME=${USERNAME} \ + HOME=/home/${USERNAME} + +# Set up user home directory +RUN mkdir -p /home/$USERNAME/.cache \ + && ln -s /home/ubuntu /home/customizer \ + && chown -R $USERNAME /home/$USERNAME/ /home/customizer + +# Copy built venv and configure runtime +# If NO_OVERRIDE_REQUIREMENTS_PATH is set, add --overrides $NO_OVERRIDE_REQUIREMENTS_PATH to the uv pip install command +# NO_OVERRIDE_REQUIREMENTS_PATH is a requirements.txt formatted file that lists packages that should not be overridden +# when installing packages into the target $VIRTUAL_ENV +RUN uv pip install --python $VIRTUAL_ENV/bin/python --no-cache \ + ${NO_OVERRIDE_REQUIREMENTS_PATH:+--overrides $NO_OVERRIDE_REQUIREMENTS_PATH} \ + -e /app/services/customizer + +# CVE fix: python-multipart and black for all customizer images. +RUN uv pip install --python $VIRTUAL_ENV/bin/python --upgrade setuptools && \ + uv pip install --python $VIRTUAL_ENV/bin/python --no-cache \ + "python-multipart>=0.0.22" \ + "black>=26.3.1" + +COPY docker/scripts/cve-cleanup.sh /bin/ +RUN bash /bin/cve-cleanup.sh + +ENV LD_LIBRARY_PATH=/opt/amazon-efa-ofi/ofi/lib:/opt/amazon-efa-ofi/efa/lib:/opt/amazon/aws-ofi-nccl/lib:${LD_LIBRARY_PATH} + +USER ${USER_UID}:${USER_GID} + +# ============================================================================= +# Smoke test: verify critical imports resolve correctly. +# Catches two failure classes without requiring GPU hardware: +# - ModuleNotFoundError : package missing from the image +# - ImportError : CUDA extension .so has an undefined symbol +# (ABI mismatch). Surfaces at .so load time before +# any GPU device is needed. +# SMOKE_MARKER selects which test suite runs. +# Built as part of the docker-customizer bake group but never pushed. +# ============================================================================= +FROM customizer-runtime AS smoke-test +ARG SMOKE_MARKER +USER 0 +COPY tests/smoke_gpu/ /smoke_test/ +RUN uv pip install --python $VIRTUAL_ENV/bin/python --no-cache --reinstall pytest && \ + $VIRTUAL_ENV/bin/pytest /smoke_test/ -m ${SMOKE_MARKER} -v diff --git a/docker/Dockerfile.nmp-customizer-tasks b/docker/Dockerfile.nmp-customizer-tasks new file mode 100644 index 0000000000..36096ac744 --- /dev/null +++ b/docker/Dockerfile.nmp-customizer-tasks @@ -0,0 +1,151 @@ +# syntax=docker/dockerfile:1 +# NMP Customizer Tasks - GPU Tasks for Customizer +# Requirements: +# - nemo-platform +# - python 3.11 +# - mamba-ssm 2.3.0 (compatible with nemotron v3) +# - causal-conv1d + +# ============================================================================= +# Global ARGs (must be declared before first FROM to be usable in FROM statements) +# ============================================================================= +ARG BASE_TAG_CUSTOMIZER_TASKS=local +ARG FAISS_WHEEL_IMAGE=local +ARG CAUSAL_CONV1D_WHEEL_IMAGE=local +ARG MAMBA_SSM_WHEEL_IMAGE=local +ARG BASE_REGISTRY=gitlab-master.nvidia.com:5005/aire/microservices/nmp + +# ============================================================================= +# Base Image +# ============================================================================= +# Pulls a pre-built image from GitLab registry, which is built separately +# by the nmp-customizer-tasks-base-builder stage. +FROM ${BASE_REGISTRY}/nmp-customizer-tasks-base:$BASE_TAG_CUSTOMIZER_TASKS AS nmp-customizer-tasks-base + +# ============================================================================= +# FAISS wheel source (ARM64 only) +# ============================================================================= +# faiss-gpu-cu12 has no ARM64 wheel on PyPI. On linux/arm64 builds, pass +# the faiss-gpu-wheel image here so the locally-built wheel can be installed. +FROM ${FAISS_WHEEL_IMAGE} AS faiss-wheel-src + +# ============================================================================= +# Wheel sources (causal-conv1d + mamba-ssm) +# ============================================================================= +# Pre-built wheels from the gpu-wheels pipeline. +# Both packages only ship source distributions on PyPI and require nvcc to +# compile. Building them inline is slow; instead consume pre-built wheels +# published by the gpu-wheels CI pipeline. +FROM ${CAUSAL_CONV1D_WHEEL_IMAGE} AS causal-conv1d-wheel-src +FROM ${MAMBA_SSM_WHEEL_IMAGE} AS mamba-ssm-wheel-src + +# ============================================================================= +# Builder: Install all dependencies +# ============================================================================= +FROM nmp-gpu-base AS nmp-customizer-tasks-base-builder +COPY --from=nmp-workspace . . + +# ensuring we build using the same base as the runtime image, and with the currently configured dependencies +RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --group gpu-tasks --extra cu128 --no-editable + +# Install locally-built FAISS wheel on ARM64. +# faiss-gpu-cu12 is excluded by platform marker on aarch64; the equivalent +# FAISS GPU wheel is provided via the faiss-gpu-wheel-builder image. +# Pass FAISS_WHEEL_IMAGE=/faiss-gpu-wheel: when building for arm64. +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=from=faiss-wheel-src,target=/tmp/faiss-wheel-src \ + if ls /tmp/faiss-wheel-src/wheels/*.whl >/dev/null 2>&1; then \ + uv pip install --no-deps /tmp/faiss-wheel-src/wheels/*.whl; \ + fi + +# Install pre-built CUDA extension wheels (causal-conv1d, mamba-ssm) +# Wheels are built by the gpu-wheels pipeline. Update WHEELS_TAG in docker-bake.hcl +# when new wheels are published. +# Install mamba_ssm with --no-deps to avoid replacing the pinned torch/CUDA stack, +# then add its lightweight Python runtime dependency explicitly. +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=from=causal-conv1d-wheel-src,target=/tmp/causal-conv1d-wheel-src,readonly \ + --mount=from=mamba-ssm-wheel-src,target=/tmp/mamba-ssm-wheel-src,readonly \ + uv pip install --no-deps \ + /tmp/causal-conv1d-wheel-src/wheels/causal_conv1d-*cp311*.whl \ + /tmp/mamba-ssm-wheel-src/wheels/mamba_ssm-2.3.0-cp311*.whl && \ + uv pip install --no-deps "einops>=0.8,<0.9" + +# ============================================================================= +# Runtime: Copy packages in separate layers +# ============================================================================= +FROM nmp-gpu-base AS runtime +WORKDIR /app + +# Layer 1: NVIDIA CUDA libraries (~4.3GB) +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/nvidia \ + /app/.venv/lib/python3.11/site-packages/nvidia + +# Layer 2: PyTorch (~2GB) +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torch \ + /app/.venv/lib/python3.11/site-packages/torch +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torchgen \ + /app/.venv/lib/python3.11/site-packages/torchgen +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/functorch \ + /app/.venv/lib/python3.11/site-packages/functorch +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torch-*.dist-info \ + /app/.venv/lib/python3.11/site-packages/ + +# Layer 3: Inference engines (~3.4GB) +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/flashinfer_cubin \ + /app/.venv/lib/python3.11/site-packages/flashinfer_cubin +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/triton \ + /app/.venv/lib/python3.11/site-packages/triton + +#TODO needs to be built for arm64 +COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/xformers \ + /app/.venv/lib/python3.11/site-packages/xformers + +# Layer 4: CUDA Extensions (causal-conv1d, mamba-ssm) +COPY --from=nmp-customizer-tasks-base-builder /app/.venv/lib/python3.11/site-packages/causal_conv1d \ + /app/.venv/lib/python3.11/site-packages/causal_conv1d +COPY --from=nmp-customizer-tasks-base-builder /app/.venv/lib/python3.11/site-packages/mamba_ssm \ + /app/.venv/lib/python3.11/site-packages/mamba_ssm + +# Layer 5: Everything else (venv structure, bin/, dist-info, other packages) +RUN --mount=from=nmp-customizer-tasks-base-builder,source=/app/.venv,target=/mnt/venv,readonly \ + cd /mnt/venv && \ + tar --exclude='lib/python3.11/site-packages/nvidia' \ + --exclude='lib/python3.11/site-packages/torch' \ + --exclude='lib/python3.11/site-packages/torchgen' \ + --exclude='lib/python3.11/site-packages/functorch' \ + --exclude='lib/python3.11/site-packages/torch-*.dist-info' \ + --exclude='lib/python3.11/site-packages/flashinfer_cubin' \ + --exclude='lib/python3.11/site-packages/vllm' \ + --exclude='lib/python3.11/site-packages/triton' \ + --exclude='lib/python3.11/site-packages/xformers' \ + -cf - . | tar -xf - -C /app/.venv + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv pip install "transformers>=5.0.0,<5.1.0" + +# setuptools: GHSA-58pv-8j8x-9vj2 +RUN uv pip install --upgrade setuptools + +COPY docker/scripts/cve-cleanup.sh /bin/ +RUN bash /bin/cve-cleanup.sh + +ENV PATH="/app/.venv/bin:$PATH" +ENTRYPOINT ["nemo-platform"] +# Override CMD with "run" in orchestration (e.g., Helm, docker-compose) +CMD ["--help"] + +# ============================================================================= +# Smoke test: verify all customizer task imports resolve correctly. +# Catches two failure classes without requiring GPU hardware: +# - ModuleNotFoundError : package missing from the image +# - ImportError : CUDA extension .so has an undefined symbol +# (ABI mismatch). Surfaces at .so load time before +# any GPU device is needed. +# Built as part of the docker-customizer bake group but never pushed. +# ============================================================================= +FROM runtime AS smoke-test +COPY tests/smoke_gpu/ /smoke_test/ +RUN uv pip install --no-cache --reinstall pytest && \ + pytest /smoke_test/ -m smoke_customizer_tasks -v diff --git a/services/unsloth/docker/Dockerfile.nmp-unsloth-training b/docker/Dockerfile.nmp-unsloth-training similarity index 100% rename from services/unsloth/docker/Dockerfile.nmp-unsloth-training rename to docker/Dockerfile.nmp-unsloth-training diff --git a/docker/Dockerfile.safe-synthesizer-tasks b/docker/Dockerfile.safe-synthesizer-tasks new file mode 100644 index 0000000000..135c1f972c --- /dev/null +++ b/docker/Dockerfile.safe-synthesizer-tasks @@ -0,0 +1,201 @@ +# syntax=docker/dockerfile:1 +# Safe Synthesizer Tasks - GPU-intensive Safe Synthesizer task execution +# +# Layer splitting follows the standalone Safe Synthesizer CUDA image pattern: +# repeated uv installs solve the final environment while temporarily omitting +# heavy package families, allowing Docker to cache stable dependency layers. + +# ============================================================================= +# Global ARGs (must be declared before first FROM to be usable in FROM statements) +# ============================================================================= +ARG PYTHON_VERSION=3.13 +ARG PYTHON_IMAGE=python:${PYTHON_VERSION}-slim-bookworm +ARG UV_IMAGE=ghcr.io/astral-sh/uv:0.9.30 +ARG SAFE_SYNTHESIZER_RUNTIME_PACKAGE="nemo-safe-synthesizer[engine,cu129]==0.1.2" +ARG FLASHINFER_CU129_INDEX_URL="https://flashinfer.ai/whl/cu129" +ARG PYTORCH_CU129_INDEX_URL="https://download.pytorch.org/whl/cu129" +ARG VLLM_CU129_WHEEL="vllm @ https://github.com/vllm-project/vllm/releases/download/v0.20.0/vllm-0.20.0%2Bcu129-cp38-abi3-manylinux_2_31_x86_64.whl" + +# ============================================================================= +# uv binary +# ============================================================================= +FROM ${UV_IMAGE} AS uv + +# ============================================================================= +# Runtime +# ============================================================================= +FROM ${PYTHON_IMAGE} AS runtime +ARG PYTHON_IMAGE +ARG SAFE_SYNTHESIZER_RUNTIME_PACKAGE +ARG FLASHINFER_CU129_INDEX_URL +ARG PYTORCH_CU129_INDEX_URL +ARG VLLM_CU129_WHEEL +ARG USERNAME=nemo +ARG USER_UID=1000 +ARG USER_GID=1000 + +COPY --from=uv /uv /uvx /usr/local/bin/ + +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ + apt-get update && apt-get install -y --no-install-recommends \ + bash \ + ca-certificates \ + libgomp1 \ + tini && \ + groupadd --gid ${USER_GID} ${USERNAME} && \ + useradd --uid ${USER_UID} --gid ${USER_GID} --create-home --shell /bin/bash ${USERNAME} && \ + mkdir -p /workspace && \ + chown ${USER_UID}:${USER_GID} /workspace && \ + rm -rf /var/lib/apt/lists/* + +ENV UV_PROJECT_ENVIRONMENT=/opt/venv \ + PATH="/opt/venv/bin:${PATH}" \ + UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + UV_NO_INSTALLER_METADATA=1 \ + UV_NO_MANAGED_PYTHON=1 \ + UV_FROZEN=true \ + USERNAME=${USERNAME} \ + HOME=/home/${USERNAME} \ + XDG_CACHE_HOME=/home/${USERNAME}/.cache \ + MPLCONFIGDIR=/home/${USERNAME}/.cache/matplotlib \ + OUTLINES_CACHE_DIR=/home/${USERNAME}/.cache/nemo-safe-synthesizer/outlines \ + NVIDIA_VISIBLE_DEVICES=all \ + NVIDIA_DRIVER_CAPABILITIES=compute,utility + +WORKDIR /build + +LABEL org.opencontainers.image.title="Safe Synthesizer Tasks" \ + org.opencontainers.image.description="NeMo Platform GPU task runtime for Safe Synthesizer (${CONTAINER_VARIANT})" \ + org.opencontainers.image.vendor="NVIDIA" \ + org.opencontainers.image.licenses="Apache-2.0" \ + org.opencontainers.image.documentation="https://nvidia-nemo.github.io/Safe-Synthesizer/user-guide/docker/" \ + org.opencontainers.image.base.name="${PYTHON_IMAGE}" \ + com.nvidia.nemo.safe-synthesizer.variant="${CONTAINER_VARIANT}" + +# Install the Safe Synthesizer engine/CUDA runtime before copying Platform +# sources, so plugin-only edits do not invalidate the large dependency layers. +RUN uv venv /opt/venv +RUN printf '%s\n' \ + flashinfer-cubin \ + flashinfer-jit-cache \ + torch \ + torch-c-dlpack-ext \ + torchaudio \ + torchao \ + torchvision \ + triton \ + vllm \ + > /tmp/exclude-flashinfer-torch-vllm.txt && \ + printf '%s\n' \ + torch \ + torch-c-dlpack-ext \ + torchaudio \ + torchao \ + torchvision \ + triton \ + vllm \ + > /tmp/exclude-torch-vllm.txt && \ + printf '%s\n' \ + vllm \ + > /tmp/exclude-vllm.txt && \ + printf '%s\n' \ + "${SAFE_SYNTHESIZER_RUNTIME_PACKAGE}" \ + > /tmp/safe-synthesizer-runtime.txt && \ + printf '%s\n' \ + "${VLLM_CU129_WHEEL}" \ + > /tmp/vllm-cu129.txt + +RUN --mount=type=cache,target=/root/.cache/uv \ + UV_CACHE_DIR=/root/.cache/uv uv pip install --python /opt/venv/bin/python \ + --extra-index-url "${FLASHINFER_CU129_INDEX_URL}" \ + --extra-index-url "${PYTORCH_CU129_INDEX_URL}" \ + --index-strategy unsafe-best-match \ + --torch-backend cu129 \ + --excludes /tmp/exclude-flashinfer-torch-vllm.txt \ + --requirements /tmp/safe-synthesizer-runtime.txt + +# Add FlashInfer binary/cache wheels in their own layer. +RUN --mount=type=cache,target=/root/.cache/uv \ + UV_CACHE_DIR=/root/.cache/uv uv pip install --python /opt/venv/bin/python \ + --extra-index-url "${FLASHINFER_CU129_INDEX_URL}" \ + --extra-index-url "${PYTORCH_CU129_INDEX_URL}" \ + --index-strategy unsafe-best-match \ + --torch-backend cu129 \ + --excludes /tmp/exclude-torch-vllm.txt \ + --requirements /tmp/safe-synthesizer-runtime.txt + +# Add PyTorch, TorchVision, TorchAudio, TorchAO, and Triton separately from vLLM. +RUN --mount=type=cache,target=/root/.cache/uv \ + UV_CACHE_DIR=/root/.cache/uv uv pip install --python /opt/venv/bin/python \ + --extra-index-url "${FLASHINFER_CU129_INDEX_URL}" \ + --extra-index-url "${PYTORCH_CU129_INDEX_URL}" \ + --index-strategy unsafe-best-match \ + --torch-backend cu129 \ + --excludes /tmp/exclude-vllm.txt \ + --requirements /tmp/safe-synthesizer-runtime.txt + +# Install the remaining runtime dependencies, currently dominated by vLLM. +RUN --mount=type=cache,target=/root/.cache/uv \ + UV_CACHE_DIR=/root/.cache/uv uv pip install --python /opt/venv/bin/python \ + --extra-index-url "${FLASHINFER_CU129_INDEX_URL}" \ + --extra-index-url "${PYTORCH_CU129_INDEX_URL}" \ + --index-strategy unsafe-best-match \ + --torch-backend cu129 \ + --requirements /tmp/safe-synthesizer-runtime.txt \ + --requirements /tmp/vllm-cu129.txt + +COPY pyproject.toml uv.lock ./ +COPY packages/ packages/ +COPY sdk/python/nemo-platform sdk/python/nemo-platform +COPY plugins/nemo-safe-synthesizer plugins/nemo-safe-synthesizer + +# nemo-platform-sdk force-includes repo docs into its wheel. The task image does +# not use `nemo docs`, so provide the smallest valid docs snapshot instead of +# sending the full docs tree in the Docker context. +RUN mkdir -p docs && \ + printf 'site_name: NeMo Platform\n' > mkdocs.yml + +# Install the Safe Synthesizer plugin on top of the prebuilt runtime environment. +# Skip the `nemo-platform` wrapper wheel: it bundles every first-party plugin and +# service, which drags unrelated source trees into this task image. The runtime +# imports come from nemo-platform-sdk, nemo-platform-plugin, and nmp-common. +RUN --mount=type=cache,target=/root/.cache/uv \ + UV_CACHE_DIR=/root/.cache/uv uv sync --package nemo-safe-synthesizer-plugin --no-dev --no-editable --inexact \ + --no-install-package nemo-platform + +COPY docker/scripts/cve-cleanup.sh /bin/ +RUN bash /bin/cve-cleanup.sh +RUN mkdir -p "${XDG_CACHE_HOME}" "${MPLCONFIGDIR}" "${OUTLINES_CACHE_DIR}" && \ + chown -R ${USER_UID}:${USER_GID} "/home/${USERNAME}" /workspace + +USER ${USER_UID}:${USER_GID} +WORKDIR /workspace +ENTRYPOINT ["tini", "--", "python", "-m", "nemo_safe_synthesizer_plugin.tasks.safe_synthesizer"] +CMD ["--help"] + +# ============================================================================= +# Smoke test: verify Safe Synthesizer task imports resolve correctly. +# Catches two failure classes without requiring GPU hardware: +# - ModuleNotFoundError : package missing from the image +# - ImportError : CUDA extension .so has an undefined symbol — +# wheel compiled against a different PyTorch version +# (ABI mismatch). Surfaces at .so load time before +# any GPU device is needed. +# Built as part of the docker-gpu bake group but never pushed to the registry. +# ============================================================================= +FROM runtime AS smoke-test +RUN python - <<'PY' +import importlib + +for module in ( + "torch", + "vllm", + "nemo_safe_synthesizer_plugin.service", + "nemo_safe_synthesizer_plugin.tasks.safe_synthesizer.__main__", +): + print(f"importing {module}", flush=True) + importlib.import_module(module) +PY diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..11c1bda906 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,12 @@ +# Docker Builds + +Dockerfiles and Docker build support files live under this directory. The +canonical Buildx Bake file is `docker-bake.hcl`. + +Examples: + +```bash +docker buildx bake --print docker-cpu +docker buildx bake --print nmp-automodel +docker buildx bake --print docker-customizer +``` diff --git a/services/automodel/docker/Dockerfile.platform-workspace b/docker/automodel/Dockerfile.platform-workspace similarity index 71% rename from services/automodel/docker/Dockerfile.platform-workspace rename to docker/automodel/Dockerfile.platform-workspace index b8b77ae190..0a04ead70a 100644 --- a/services/automodel/docker/Dockerfile.platform-workspace +++ b/docker/automodel/Dockerfile.platform-workspace @@ -1,13 +1,13 @@ # syntax=docker/dockerfile:1 # Minimal Platform workspace slice for nmp-automodel container installs. # Used as a named build context (platform-workspace). -# Keep in sync with services/automodel/docker/pyproject.workspace.toml members. +# Keep in sync with docker/automodel/pyproject.workspace.toml members. FROM scratch AS platform-workspace -# Do not copy repo-root pyproject.toml/uv.lock — they reference the full monorepo workspace. -COPY services/automodel/docker/pyproject.workspace.toml pyproject.toml +# Use a reduced workspace file for this partial source tree. +COPY docker/automodel/pyproject.workspace.toml pyproject.toml # nemo-platform-sdk hatch build force-includes docs/ from repo root. -# docs/fern/openapi/openapi.yaml is a symlink to ../../../openapi/openapi.yaml — copy both. +# docs/fern/openapi/openapi.yaml is a symlink to ../../../openapi/openapi.yaml. COPY docs docs COPY openapi openapi COPY packages/nmp_build_tools packages/nmp_build_tools diff --git a/services/automodel/docker/README.md b/docker/automodel/README.md similarity index 100% rename from services/automodel/docker/README.md rename to docker/automodel/README.md diff --git a/services/automodel/docker/cherry-picks/e6d2930a.diff b/docker/automodel/cherry-picks/e6d2930a.diff similarity index 99% rename from services/automodel/docker/cherry-picks/e6d2930a.diff rename to docker/automodel/cherry-picks/e6d2930a.diff index caacdeb301..f15926a7d3 100644 --- a/services/automodel/docker/cherry-picks/e6d2930a.diff +++ b/docker/automodel/cherry-picks/e6d2930a.diff @@ -4,7 +4,7 @@ index ffd39951..f24b954a 100644 +++ b/nemo_automodel/components/checkpoint/checkpointing.py @@ -481,8 +481,10 @@ class Checkpointer: # We need to set them to False and call initialize_weights to re-initialize the weights. - + # Some models cannot call initialize_weights when sharded with DTensors: - # - Gemma3ForConditionalGeneration: requires setting a row to zeros in the embedding matrix, - # which is not supported for DTensors in the pinned torch version. diff --git a/services/automodel/docker/no_override_requirements.txt b/docker/automodel/no_override_requirements.txt similarity index 100% rename from services/automodel/docker/no_override_requirements.txt rename to docker/automodel/no_override_requirements.txt diff --git a/services/automodel/docker/pyproject.workspace.toml b/docker/automodel/pyproject.workspace.toml similarity index 82% rename from services/automodel/docker/pyproject.workspace.toml rename to docker/automodel/pyproject.workspace.toml index b63b105f7f..32838681c0 100644 --- a/services/automodel/docker/pyproject.workspace.toml +++ b/docker/automodel/pyproject.workspace.toml @@ -1,6 +1,5 @@ # Minimal uv workspace for nmp-automodel container image builds only. -# Replaces the repo-root pyproject.toml in Dockerfile.platform-workspace so -# partial COPY trees are not validated against the full monorepo workspace. +# Keeps uv validation scoped to the partial source tree copied into the image. [project] name = "nemo-platform-automodel-image" diff --git a/docker/base/Dockerfile.faiss-gpu-wheel b/docker/base/Dockerfile.faiss-gpu-wheel new file mode 100644 index 0000000000..2a3e1e8b38 --- /dev/null +++ b/docker/base/Dockerfile.faiss-gpu-wheel @@ -0,0 +1,95 @@ +# syntax=docker/dockerfile:1 +####### +# FAISS GPU Wheel Builder +# +# Builds the FAISS Python wheel with CUDA support. +# The resulting image stores the wheel at /wheels/*.whl. +# +# Downstream images can install the wheel with: +# COPY --from=faiss-gpu-wheel /wheels/*.whl /tmp/wheels/ +# RUN pip install /tmp/wheels/*.whl +# +# Build args: +# FAISS_VERSION - git tag/branch/SHA to clone (default: v1.9.0) +# CUDA_ARCHS - semicolon-separated SM targets (default: 75;80;86;90) +# CUDA_VERSION - CUDA devel image version (default: 12.8.1) +# +# CUDA_ARCHS reference: +# 75 = Turing (T4, RTX 2080) +# 80 = Ampere (A100) +# 86 = Ampere (A10G, RTX 3080/3090) +# 87 = Ampere (Jetson AGX Orin) +# 89 = Ada (RTX 4090) +# 90 = Hopper (H100) +####### + +ARG CUDA_VERSION=12.8.1 + +FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu22.04 AS faiss-gpu-wheel-builder + +ARG FAISS_VERSION=v1.9.0 +ARG CUDA_ARCHS="75;80;86;90" + +ENV DEBIAN_FRONTEND=noninteractive + +# Install build dependencies. +# python3.11 + python3.11-dev are available in ubuntu 22.04 universe. +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + libgflags-dev \ + libopenblas-dev \ + python3.11 \ + python3.11-dev \ + python3.11-venv \ + swig \ + && rm -rf /var/lib/apt/lists/* + +# Install uv and create a Python 3.11 venv with build-time deps. +# Ubuntu/Debian disables ensurepip for the system Python; uv manages its +# own Python toolchain and doesn't depend on pip or ensurepip. +# cmake is installed via PyPI to get a version >= 3.24 (Ubuntu 22.04 ships 3.22). +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +RUN uv venv --python 3.11 /opt/venv \ + && uv pip install --python /opt/venv/bin/python3 \ + "cmake>=3.24" ninja numpy packaging setuptools wheel + +ENV PATH="/opt/venv/bin:$PATH" + +# Clone FAISS at the requested version. +RUN git clone --depth 1 --branch ${FAISS_VERSION} \ + https://github.com/facebookresearch/faiss.git /faiss/src + +# Configure. +RUN cmake -B /faiss/build \ + -DFAISS_ENABLE_GPU=ON \ + -DFAISS_ENABLE_RAFT=OFF \ + -DFAISS_ENABLE_PYTHON=ON \ + -DFAISS_ENABLE_MKL=OFF \ + -DFAISS_ENABLE_EXTRAS=OFF \ + -DBUILD_TESTING=OFF \ + -DBLA_VENDOR=OpenBLAS \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCHS}" \ + -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++ \ + -DPython_EXECUTABLE=/opt/venv/bin/python3 \ + /faiss/src + +# Build CPU library, GPU library, and Python bindings. +RUN cmake --build /faiss/build --target faiss faiss_gpu swigfaiss \ + --parallel $(nproc) + +# Package into a wheel. +RUN cd /faiss/build/faiss/python \ + && python setup.py bdist_wheel \ + && mkdir -p /wheels \ + && cp dist/*.whl /wheels/ + +RUN ls -lh /wheels/ + +CMD ["ls", "-lh", "/wheels/"] + +FROM scratch AS faiss-gpu-wheel +COPY --from=faiss-gpu-wheel-builder /wheels /wheels diff --git a/services/automodel/docker/Dockerfile.mamba-wheel b/docker/base/Dockerfile.mamba-wheel similarity index 92% rename from services/automodel/docker/Dockerfile.mamba-wheel rename to docker/base/Dockerfile.mamba-wheel index 0d0cee1cca..06a0754c74 100644 --- a/services/automodel/docker/Dockerfile.mamba-wheel +++ b/docker/base/Dockerfile.mamba-wheel @@ -9,7 +9,7 @@ # Both only ship source distributions on PyPI and require nvcc to compile. # The two builds are independent stages so BuildKit runs them in parallel. # Each image stores its wheel at /wheels/*.whl. -# Build via Platform bake group: docker buildx bake -f docker-bake.hcl nmp-automodel-gpu-wheels +# Build via the gpu-wheels or nmp-automodel-gpu-wheels bake group. # # Build args: # CAUSAL_CONV1D_VERSION - git tag to build (default: v1.5.3) @@ -40,7 +40,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /usr/local/bin/uv -COPY services/automodel/docker/locks/mamba-wheel-build-py311 /opt/mamba-wheel-build-py311 +COPY docker/locks/mamba-wheel-build-py311 /opt/mamba-wheel-build-py311 # Create the Python 3.11 build venv from a committed lockfile. RUN uv venv --python 3.11 /opt/venv && \ @@ -70,7 +70,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -COPY services/automodel/docker/locks/mamba-wheel-build-py312 /opt/mamba-wheel-build-py312 +COPY docker/locks/mamba-wheel-build-py312 /opt/mamba-wheel-build-py312 # Create the Python 3.12 build venv from a committed lockfile. RUN uv venv --python 3.12 /opt/venv312 && \ @@ -97,7 +97,7 @@ COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /usr/local/bin/uv # ============================================================================= -# causal-conv1d wheel — Python 3.11 (for nmp-gpu-tasks and nmp-automodel-tasks) +# causal-conv1d wheel — Python 3.11 (for safe-synthesizer-tasks, nmp-customizer-tasks, nmp-automodel-tasks, and nmp-automodel-tasks) # ============================================================================= FROM mamba-wheel-base AS causal-conv1d-wheel-builder @@ -139,7 +139,7 @@ RUN mkdir -p /wheels && \ rm -rf /src/causal-conv1d # The final causal-conv1d-wheel image contains: -# - causal_conv1d-*-cp311-*.whl (for Python 3.11 consumers: nmp-gpu-tasks, nmp-automodel-tasks) +# - causal_conv1d-*-cp311-*.whl (for Python 3.11 consumers: safe-synthesizer-tasks, nmp-customizer-tasks, nmp-automodel-tasks) # - causal_conv1d-*-cp312-*.whl (for Python 3.12 consumers) # Consumers must pin the Python tag glob (e.g. causal_conv1d-*cp311*.whl) to select the right one. FROM scratch AS causal-conv1d-wheel @@ -148,7 +148,7 @@ COPY --from=causal-conv1d-wheel-builder-py312 /wheels /wheels COPY --from=causal-conv1d-wheel-builder-py312-cu13.1.1 /wheels /wheels/cu13.1.1 # ============================================================================= -# mamba-ssm 2.2.5 wheel — Python 3.11 (for nmp-gpu-tasks) +# mamba-ssm 2.2.5 wheel — Python 3.11 (for safe-synthesizer-tasks) # ============================================================================= FROM mamba-wheel-base AS mamba-ssm-wheel-builder @@ -182,7 +182,7 @@ RUN mkdir -p /wheels && \ rm -rf /src/mamba # ============================================================================= -# mamba-ssm 2.3.0 wheel — Python 3.11 (for nmp-automodel-tasks) +# mamba-ssm 2.3.0 wheel — Python 3.11 (for nmp-customizer-tasks) # ============================================================================= FROM mamba-wheel-base AS mamba-ssm-23-wheel-builder @@ -232,9 +232,9 @@ RUN mkdir -p /wheels && \ # The final mamba-ssm-wheel image contains four versions: -# - mamba_ssm-2.2.5-cp311-*.whl (from MAMBA_22_COMMIT=6b32be06, for nmp-gpu-tasks / Python 3.11) +# - mamba_ssm-2.2.5-cp311-*.whl (from MAMBA_22_COMMIT=6b32be06, for safe-synthesizer-tasks / Python 3.11) # - mamba_ssm-2.2.5-cp312-*.whl (from MAMBA_22_COMMIT=6b32be06, for Python 3.12 consumers, e.g. automodel) -# - mamba_ssm-2.3.0-cp311-*.whl (from v2.3.0, for nmp-automodel-tasks / Python 3.11) +# - mamba_ssm-2.3.0-cp311-*.whl (from v2.3.0, for nmp-customizer-tasks / Python 3.11) # - mamba_ssm-2.3.0-cp312-*.whl (from v2.3.0, for Python 3.12 consumers) # Consumers must pin both version AND Python tag glob to select the correct wheel. FROM scratch AS mamba-ssm-wheel diff --git a/docker/base/Dockerfile.nmp-gpu-base b/docker/base/Dockerfile.nmp-gpu-base new file mode 100644 index 0000000000..4b79e02128 --- /dev/null +++ b/docker/base/Dockerfile.nmp-gpu-base @@ -0,0 +1,58 @@ +# syntax=docker/dockerfile:1 +####### +# NMP GPU Base Image +# Base for GPU task containers such as safe-synthesizer-tasks +####### + +ARG CUDA_VERSION=12.8.1 +FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu22.04 AS nmp-gpu-base + +ARG DEBIAN_FRONTEND=noninteractive + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + git \ + libffi-dev \ + libssl-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# CVE fixes applied in this Dockerfile: +# - CVE-2025-68973 (High, out-of-SLA): Fixed by upgrading gnupg/dirmngr packages +# via apt-get install --only-upgrade gnupg gpg gpg-agent ... dirmngr gpgv +# (upgrades to 2.2.27-3ubuntu2.5+ for dirmngr and related gnupg packages) +# - CVE-2025-8194 (High, out-of-SLA): Fixed by bumping the Python base image to +# python:3.11.14 in docker/base/Dockerfile.nmp-base +# --only-upgrade: upgrades installed packages only; skips absent ones silently. +RUN apt-get update && apt-get install -y --only-upgrade \ + gnupg gnupg2 gpg gpg-agent gpgconf gpgsm \ + dirmngr gnupg-utils gpgv \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Copy uv binary from official image +COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv + +# python 3.11 required +RUN uv venv --seed --python=3.11 /app/.venv + +# CVE fixes: CVE-2026-24049 (wheel < 0.46.2) and CVE-2026-23949 (jaraco-context < 6.1.0) +# are vendored inside setuptools in the uv-managed Python distribution at +# /root/.local/share/uv/python/cpython-3.11.*/. Upgrade setuptools to >= 78.1.1 +# to pull in the patched vendored packages. +RUN $(uv python find 3.11) -m pip install --upgrade "setuptools>=78.1.1" + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + NVIDIA_VISIBLE_DEVICES=all \ + NVIDIA_DRIVER_CAPABILITIES=compute,utility \ + TORCH_CUDA_ARCH_LIST="7.0 7.5 8.0 8.6 8.9 9.0" \ + VIRTUAL_ENV=/app/.venv \ + PATH=/app/.venv/bin:$PATH diff --git a/docker/base/Dockerfile.nmp-gpu-base-py312 b/docker/base/Dockerfile.nmp-gpu-base-py312 new file mode 100644 index 0000000000..dc9dd17ae3 --- /dev/null +++ b/docker/base/Dockerfile.nmp-gpu-base-py312 @@ -0,0 +1,46 @@ +# syntax=docker/dockerfile:1 +####### +# NMP GPU Base Image (Python 3.12) +# Base for GPU task containers that require Python 3.12 +# (e.g. nmp-customizer-tasks) +####### + +ARG CUDA_VERSION=12.8.1 +FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu22.04 AS nmp-gpu-base-py312 + +ARG DEBIAN_FRONTEND=noninteractive + +# Install Python 3.12 and system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update && apt-get install -y --no-install-recommends \ + python3.12 \ + python3.12-venv \ + python3.12-dev \ + build-essential \ + ca-certificates \ + curl \ + git \ + libffi-dev \ + libssl-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Set Python 3.12 as default +RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.12 1 \ + && update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1 + +WORKDIR /app + +# Copy uv binary from official image +COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + NVIDIA_VISIBLE_DEVICES=all \ + NVIDIA_DRIVER_CAPABILITIES=compute,utility \ + TORCH_CUDA_ARCH_LIST="7.0 7.5 8.0 8.6 8.9 9.0" diff --git a/docker/base/Dockerfile.nmp-jobs-launcher b/docker/base/Dockerfile.nmp-jobs-launcher new file mode 100644 index 0000000000..14cfb601ad --- /dev/null +++ b/docker/base/Dockerfile.nmp-jobs-launcher @@ -0,0 +1,15 @@ +# syntax=docker/dockerfile:1 + +# require >= 1.25.8 for CVE-2026-25679 and others +FROM golang:1.25 AS build +COPY ./services/core/jobs/jobs-launcher /app/services/jobs-launcher +WORKDIR /app/services/jobs-launcher +RUN go mod download +RUN CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags="-s -w -extldflags '-static'" -trimpath -o jobs-launcher ./main.go +# Verify the binary works +RUN ./jobs-launcher help + +FROM scratch AS artifacts +WORKDIR /artifacts +COPY --from=build /app/services/jobs-launcher/jobs-launcher . +# Outputs the static binary to /artifacts/jobs-launcher diff --git a/docker/base/Dockerfile.nmp-python-base b/docker/base/Dockerfile.nmp-python-base new file mode 100644 index 0000000000..2d21fad563 --- /dev/null +++ b/docker/base/Dockerfile.nmp-python-base @@ -0,0 +1,76 @@ +# syntax=docker/dockerfile:1 +####### +# NMP Python Base Images +# +# Two images, both pinned to BASE_TAG_PYTHON: +# +# nmp-python-base - minimal runtime base (API, CPU tasks) +# nmp-python-dev-base - runtime base + dev tools for building native extensions (auditor) +####### + +# Pull stages: consume pre-built images from the registry. +ARG BASE_TAG_PYTHON=local +ARG BASE_REGISTRY=gitlab-master.nvidia.com:5005/aire/microservices/nmp +FROM ${BASE_REGISTRY}/nmp-python-base:${BASE_TAG_PYTHON} AS nmp-python-base +FROM ${BASE_REGISTRY}/nmp-python-dev-base:${BASE_TAG_PYTHON} AS nmp-python-dev-base + +# nmp-python-base-builder: Builds the minimal runtime base from scratch. +# Run the nmp-python-base-builder bake target to publish a new pinned version, +# then update BASE_TAG_PYTHON in docker-bake.hcl. +FROM python:3.11.14-slim-bookworm AS nmp-python-base-builder + +RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + curl \ + g++ \ + gcc \ + git \ + libcurl4 \ + libcurl3-gnutls \ + libffi-dev \ + libpq-dev \ + libssl-dev \ + postgresql-client \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && pip uninstall --yes setuptools wheel + +WORKDIR /app + +COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv + +# python 3.11 required +RUN uv venv --seed --python=3.11 /app/.venv + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + VIRTUAL_ENV=/app/.venv \ + PATH=/app/.venv/bin:$PATH + +# nmp-python-dev-base-builder: Extends the runtime base with development tools. +# Used as the builder base for services that compile native extensions (e.g., auditor). +FROM nmp-python-base-builder AS nmp-python-dev-base-builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + binutils \ + g++-11 \ + libisl23 \ + libgmp10 \ + libmpc3 \ + libmpfr6 \ + libpython3-dev \ + libzstd1 \ + unzip \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 60 \ + && update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 60 + +ENV CARGO_HOME="/etc/cargo" \ + RUSTUP_HOME="/etc/rustup" +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +ENV PATH="$CARGO_HOME/bin:$PATH" diff --git a/docker/base/Dockerfile.nmp-studio-ui b/docker/base/Dockerfile.nmp-studio-ui new file mode 100644 index 0000000000..a19db340a3 --- /dev/null +++ b/docker/base/Dockerfile.nmp-studio-ui @@ -0,0 +1,89 @@ +# syntax=docker/dockerfile:1 +ARG NODE_VERSION=22 +ARG DOCKERHUB_MIRROR=dockerhub.nvidia.com + +# --------------- BASE --------------- # +FROM ${DOCKERHUB_MIRROR}/node:${NODE_VERSION}-bookworm AS base +ENV PUPPETEER_SKIP_DOWNLOAD=true + +# Preserve the Platform repository layout so SDK generation can resolve +# repository-root-relative OpenAPI paths from web/packages/sdk. +WORKDIR /app/web + +# Setup pnpm through corepack (rarely changes, cache this early) +RUN npm i -g corepack@0.31.0 && \ + corepack enable pnpm + +# ---- Dependencies (changes less frequently) ---- +# Copy workspace config and lockfiles first +COPY web/pnpm-lock.yaml pnpm-lock.yaml +COPY web/pnpm-workspace.yaml pnpm-workspace.yaml +COPY web/package.json package.json + +# Copy package.json from each workspace package for dependency resolution. +COPY web/packages/sdk/package.json packages/sdk/package.json +COPY web/packages/common/package.json packages/common/package.json +COPY web/packages/testing/package.json packages/testing/package.json +COPY web/packages/studio/package.json packages/studio/package.json + +# The root web postinstall runs `pnpm --filter @nemo/sdk gen:all`, so the SDK +# generator and its OpenAPI inputs must exist before dependency installation. +COPY web/packages/sdk/generateAll.ts packages/sdk/generateAll.ts +COPY web/packages/sdk/orval.config.ts packages/sdk/orval.config.ts +COPY web/packages/sdk/orval packages/sdk/orval +COPY openapi /app/openapi +COPY plugins/nemo-data-designer/openapi /app/plugins/nemo-data-designer/openapi +COPY plugins/nemo-agents/openapi /app/plugins/nemo-agents/openapi +COPY plugins/nemo-safe-synthesizer/openapi /app/plugins/nemo-safe-synthesizer/openapi +COPY plugins/nemo-evaluator/openapi /app/plugins/nemo-evaluator/openapi + +# Install pnpm modules and generate SDK artifacts during postinstall. +RUN pnpm install --frozen-lockfile + +# ---- Source files (changes more frequently) ---- +# Copy workspace package source files. +COPY web/packages/sdk packages/sdk +COPY web/packages/common packages/common +COPY web/packages/testing packages/testing + +# Copy UI package source files +COPY web/packages/studio/public packages/studio/public +COPY web/packages/studio/src packages/studio/src +COPY web/packages/studio/*.ts packages/studio/ +COPY web/packages/studio/*.json packages/studio/ +COPY web/packages/studio/index.html packages/studio/index.html + +# --------------- BUILD --------------- # +FROM base AS build + +# Uses a single env file across all envs. FastAPI handles find/replace for values. +ARG ENV_FILE=".env.fastapi" +ARG NODE_ENV=production +ARG VITE_VERSION_SHA="" +RUN echo "Building Studio using env file ${ENV_FILE}" +COPY web/packages/studio/env/${ENV_FILE} packages/studio/env/.env + +# Build the UI with mode set for FastAPI env replacement for subpath routing. +# QEMU arm64 builds occasionally hang in Vite chunk rendering; bound each attempt +# so the Dockerfile can retry before the GitHub job-level timeout cancels the run. +RUN set -eu; \ + for attempt in 1 2 3; do \ + if VITE_VERSION_SHA="${VITE_VERSION_SHA}" NODE_ENV="${NODE_ENV}" \ + timeout --kill-after=30s 15m \ + pnpm --filter nemo-studio-ui --fail-if-no-match build:fastapi; then \ + exit 0; \ + fi; \ + status="$?"; \ + if [ "${status}" != "124" ] && [ "${status}" != "137" ]; then \ + exit "${status}"; \ + fi; \ + echo "Studio UI build timed out on attempt ${attempt}; retrying..."; \ + sleep "$((attempt * 10))"; \ + done; \ + echo "Studio UI build timed out after 3 attempts"; \ + exit 124 + +# Output stage - just the built assets +FROM scratch AS artifacts +WORKDIR /artifacts +COPY --from=build /app/web/packages/studio/dist . diff --git a/docker/base/Dockerfile.nmp-workspace b/docker/base/Dockerfile.nmp-workspace new file mode 100644 index 0000000000..7c8a3781cf --- /dev/null +++ b/docker/base/Dockerfile.nmp-workspace @@ -0,0 +1,18 @@ +# syntax=docker/dockerfile:1 +# Workspace file bundle - used as a named context source for uv sync stages + +FROM scratch AS nmp-workspace +COPY pyproject.toml uv.lock README.md ./ +COPY docs/ docs/ +COPY openapi/ openapi/ +COPY skills/ skills/ +COPY packages/ packages/ +COPY skills/ skills/ +COPY plugins/ plugins/ +COPY skills/ skills/ +COPY services/ services/ +COPY packages/nmp_platform/ entrypoint/ +COPY sdk/ sdk/ +COPY script/ script/ +COPY tools/ tools/ +COPY release/ release/ diff --git a/docker/base/Dockerfile.policy-wasm b/docker/base/Dockerfile.policy-wasm new file mode 100644 index 0000000000..6c03815aa3 --- /dev/null +++ b/docker/base/Dockerfile.policy-wasm @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 +FROM alpine:3.19 AS root-policy-wasm-builder +RUN apk add --no-cache curl tar +ARG OPA_VERSION=v1.8.0 +ARG TARGETARCH +RUN case "${TARGETARCH}" in \ + amd64|arm64) opa_arch="${TARGETARCH}" ;; \ + "") opa_arch="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac && \ + curl -fsSL -o /usr/local/bin/opa \ + "https://github.com/open-policy-agent/opa/releases/download/${OPA_VERSION}/opa_linux_${opa_arch}_static" && \ + chmod +x /usr/local/bin/opa +WORKDIR /build +COPY script/build_policy_wasm.sh /build/script/ +COPY services/core/auth/src/nmp/core/auth/app/policies /build/services/core/auth/src/nmp/core/auth/app/policies +RUN REPO_ROOT=/build OUTPUT_DIR=/artifacts sh /build/script/build_policy_wasm.sh + +FROM scratch AS root-policy-wasm-artifacts +COPY --from=root-policy-wasm-builder /artifacts/policy.wasm /artifacts/policy.wasm diff --git a/docker/customizer-patch/layers.py.diff b/docker/customizer-patch/layers.py.diff new file mode 100644 index 0000000000..2b5334be29 --- /dev/null +++ b/docker/customizer-patch/layers.py.diff @@ -0,0 +1,120 @@ +--- nemo_automodel/components/moe/layers.py 2026-02-24 13:38:17 ++++ nemo_automodel/components/moe/layers_new.py 2026-02-24 13:37:44 +@@ -17,8 +17,11 @@ + from typing import Literal, Optional + + import torch ++import torch.distributed as dist ++import torch.distributed.nn.functional as dist_nn_f + import torch.nn as nn + import torch.nn.functional as F ++from torch.autograd import Function + + from nemo_automodel.components.distributed.init_utils import get_world_size_safe + from nemo_automodel.components.models.common import BackendConfig, initialize_linear_module +@@ -88,8 +91,46 @@ + with shape [n_experts, dim, inter_dim] - 50% memory savings. + """ + return activation in ("swiglu", "quick_geglu") ++ ++ ++# ── EP variable-length collective helpers ── ++class _AllGatherConcatVarlenFn(Function): ++ """All-gather with variable local lengths and autograd-safe backward. ++ ++ Backward uses all-reduce + local narrow instead of reduce-scatter to avoid ++ monitoredBarrier deadlocks observed with mixed FSDP/EP backward collective ordering. ++ """ ++ ++ @staticmethod ++ def forward(ctx, local_tensor: torch.Tensor, group: dist.ProcessGroup, gathered_lens: list[int], max_len: int): ++ local_len = local_tensor.size(0) ++ if local_len < max_len: ++ pad_shape = (max_len - local_len,) + tuple(local_tensor.shape[1:]) ++ pad = torch.zeros(pad_shape, dtype=local_tensor.dtype, device=local_tensor.device) ++ local_padded = torch.cat([local_tensor, pad], dim=0) ++ else: ++ local_padded = local_tensor + ++ world_size = len(gathered_lens) ++ gathered = [torch.empty_like(local_padded) for _ in range(world_size)] ++ dist.all_gather(gathered, local_padded, group=group) ++ gathered = [g[:n] for g, n in zip(gathered, gathered_lens)] + ++ ctx.group = group ++ ctx.gathered_lens = gathered_lens ++ ctx.rank = dist.get_rank(group) ++ return torch.cat(gathered, dim=0) ++ ++ @staticmethod ++ def backward(ctx, grad_output: torch.Tensor): ++ grad_full = grad_output.contiguous() ++ start = sum(ctx.gathered_lens[: ctx.rank]) ++ local_len = ctx.gathered_lens[ctx.rank] ++ dist.all_reduce(grad_full, op=dist.ReduceOp.SUM, group=ctx.group) ++ grad_local = grad_full.narrow(0, start, local_len).contiguous() ++ return grad_local, None, None, None ++ ++ + class MLP(nn.Module): + """ + Multi-Layer Perceptron (MLP) used as a feed-forward layer. +@@ -326,11 +367,36 @@ + + # Replicate the tensor to all experts. This is sub-optimal but is + # used by this implementation for correctness. ++ # EP variable-length all-gather + if ep_size > 1: +- x = DTensor.from_local(x, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() +- weights = DTensor.from_local(weights.float(), device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() +- indices = DTensor.from_local(indices, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() +- token_mask = DTensor.from_local(token_mask, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() ++ ep_group = ep_mesh.get_group() ++ local_num_tokens = x.size(0) ++ ++ # Exchange per-rank token counts ++ local_len_t = torch.tensor([local_num_tokens], device=x.device, dtype=torch.int64) ++ gathered_len_t = [torch.zeros_like(local_len_t) for _ in range(ep_size)] ++ dist.all_gather(gathered_len_t, local_len_t, group=ep_group) ++ gathered_lens = [int(t.item()) for t in gathered_len_t] ++ max_len = max(gathered_lens) ++ ++ def _all_gather_dim0_var(local_tensor: torch.Tensor, *, differentiable: bool) -> torch.Tensor: ++ if differentiable: ++ return _AllGatherConcatVarlenFn.apply(local_tensor, ep_group, gathered_lens, max_len) ++ if max_len > local_tensor.size(0): ++ pad_shape = (max_len - local_tensor.size(0),) + tuple(local_tensor.shape[1:]) ++ pad = torch.zeros(pad_shape, dtype=local_tensor.dtype, device=local_tensor.device) ++ local_padded = torch.cat([local_tensor, pad], dim=0) ++ else: ++ local_padded = local_tensor ++ gathered = [torch.empty_like(local_padded) for _ in range(ep_size)] ++ dist.all_gather(gathered, local_padded, group=ep_group) ++ gathered = [g[:n] for g, n in zip(gathered, gathered_lens)] ++ return torch.cat(gathered, dim=0) ++ ++ x = _all_gather_dim0_var(x, differentiable=True) ++ weights = _all_gather_dim0_var(weights.float(), differentiable=False) ++ indices = _all_gather_dim0_var(indices, differentiable=False) ++ token_mask = _all_gather_dim0_var(token_mask, differentiable=False) + + n_local_experts = self.n_routed_experts // ep_size + experts_start_idx = ep_rank * n_local_experts +@@ -392,9 +458,15 @@ + ) + y[0] += expert_out[0] + ++ # Gradient anchor + if ep_size > 1: +- y = DTensor.from_local(y, device_mesh=ep_mesh, placements=[Partial()]) +- y = y.redistribute(placements=[Shard(0)]).to_local() ++ y = y + (x * 0.0) ++ ++ # Variable-length reduce: all_reduce + narrow to original per-rank token boundaries ++ if ep_size > 1: ++ y = dist_nn_f.all_reduce(y, op=dist.ReduceOp.SUM, group=ep_group) ++ start = sum(gathered_lens[:ep_rank]) ++ y = y.narrow(0, start, local_num_tokens).contiguous() + + return y + diff --git a/docker/customizer-patch/model.py.diff b/docker/customizer-patch/model.py.diff new file mode 100644 index 0000000000..a6b254918e --- /dev/null +++ b/docker/customizer-patch/model.py.diff @@ -0,0 +1,25 @@ +--- a/nemo_automodel/components/models/nemotron_v3/model.py ++++ b/nemo_automodel/components/models/nemotron_v3/model.py +@@ -18,7 +18,12 @@ + import torch.nn as nn + from transformers import AutoConfig + +-from nemo_automodel.components.models.common import BackendConfig, initialize_linear_module, initialize_rms_norm_module ++from nemo_automodel.components.models.common import ( ++ BackendConfig, ++ HFCheckpointingMixin, ++ initialize_linear_module, ++ initialize_rms_norm_module, ++) + from nemo_automodel.components.models.nemotron_v3.layers import NemotronV3Block + from nemo_automodel.components.models.nemotron_v3.state_dict_adapter import NemotronV3StateDictAdapter + from nemo_automodel.components.moe.fsdp_mixin import MoEFSDPSyncMixin +@@ -156,7 +161,7 @@ + block.init_weights(buffer_device=buffer_device) + + +-class NemotronHForCausalLM(nn.Module, MoEFSDPSyncMixin): ++class NemotronHForCausalLM(HFCheckpointingMixin, nn.Module, MoEFSDPSyncMixin): + """NemotronV3 model with language modeling head.""" + + @classmethod diff --git a/docker/customizer-patch/patch.sh b/docker/customizer-patch/patch.sh new file mode 100755 index 0000000000..d9588e2f02 --- /dev/null +++ b/docker/customizer-patch/patch.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euxo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +INSTALLATION_TARGET=${INSTALLATION_TARGET:-"/opt/Automodel"} + +# Add HFCheckpointingMixin to NemotronHForCausalLM (from commit 07b0700, without transformers v5 bump) +patch ${INSTALLATION_TARGET}/nemo_automodel/components/models/nemotron_v3/model.py ${SCRIPT_DIR}/model.py.diff + +# From https://github.com/NVIDIA-NeMo/Automodel/pull/1365/changes. +# File nemo_automodel/components/moe/experts.py was layers.py in old code. +patch ${INSTALLATION_TARGET}/nemo_automodel/components/moe/layers.py ${SCRIPT_DIR}/layers.py.diff diff --git a/docker/dockerfiles/README.md b/docker/dockerfiles/README.md new file mode 100644 index 0000000000..e57b80e47e --- /dev/null +++ b/docker/dockerfiles/README.md @@ -0,0 +1,13 @@ +# Service Dockerfile Parking Area + +This directory contains service Dockerfiles that have not been consolidated into +the top-level Dockerfile layout. Only files referenced by `docker-bake.hcl` are +active build inputs. + +Currently active: + +- `services/guardrails/callouts/Dockerfile.bake` via the `guardrails-callout-*` + bake targets. + +The other service Dockerfiles are retained for owner review and should be +removed if no owner confirms they are still needed. diff --git a/docker/dockerfiles/services/data-designer/Dockerfile.bake b/docker/dockerfiles/services/data-designer/Dockerfile.bake new file mode 100644 index 0000000000..5e975e7c7b --- /dev/null +++ b/docker/dockerfiles/services/data-designer/Dockerfile.bake @@ -0,0 +1,81 @@ +# syntax=docker/dockerfile:1.17 +# default ARG +ARG ASSETS_IMAGE="gitlab-master.nvidia.com:5005/aire/microservices/nmp/data-designer-assets" +ARG ASSETS_IMAGE_TAG="0.0.6" + +# Build stage for the data-designer service +#build-data-designer: + FROM root-python-3-11 AS build + + # Create the non-root user and adjust permissions + # This approach works for running in OpenShift and with volume mounts because: + # 1. OpenShift runs containers with arbitrary UIDs but using the root group (GID 0) + # 2. We're creating a specific user here, but the UID may be different in OpenShift + # 3. The important part is setting group permissions correctly + # We mainly create a user and move some directories, the rest of the permission changes happen below + RUN adduser --disabled-password --gecos "" nvs && \ + # Create logs directory with appropriate permissions + mkdir -p /app/services/data-designer/logs && \ + chmod -R g=u /app/services/data-designer/logs && \ + # Copy passwd file to allow user to be recognized in distroless image + cp /etc/passwd /app/passwd + + # Install dependencies + COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-install-project --no-dev --no-install-workspace --no-editable --package=data-designer-service + + # Copy source code + COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages + COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk + COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs + COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi + COPY . /app/services/data-designer + + # Make run.sh executable + RUN chmod +x /app/services/data-designer/run.sh + + # Generate constraints file + RUN uv pip compile services/data-designer/pyproject.toml -o constraints.txt && sed -i '/^-e file/d' constraints.txt + RUN mv /app/constraints.txt /app/services/data-designer/ + + # Copy README + COPY README.md /app + + # Install the package + RUN --mount=type=cache,target=/root/.cache/uv \ + uv sync --frozen --no-editable --no-dev --package=data-designer-service + + ENV PATH="/app/.venv/bin:$PATH" + +# Common configuration for both production and debug targets +#docker-data-designer: + FROM gitlab-master.nvidia.com:5005/aire/microservices/nmp/data-designer-base:0.0.6-split AS docker + + ENV DEFAULT_CONFIG_ID=default + ENV DEFAULT_LLM_PROVIDER=nim + ENV LOG_LEVEL=INFO + ENV NEMO_TELEMETRY_ENABLED=False + ENV NIM_ENDPOINT_URL=https://integrate.api.nvidia.com/v1 + ENV DEMO=False + + ENV PATH="/app/.venv/bin:$PATH" + + # Copy files from build stage with correct permissions + COPY --from=build --chown=nvs:root --chmod=g=u /app /app + COPY --from=build --chown=nvs:root --chmod=g=u /tmp /tmp + COPY --from=build --chown=nvs:root --chmod=g=u /app/passwd /etc/passwd + + COPY fixes/cve-2025-8194/tarfile.py.fix /usr/local/lib/python3.11/tarfile.py + + # Set user to nvs + # Note: OpenShift will override this with an arbitrary UID, + # but the permissions set in build and on copy will ensure it works + USER nvs + + # Set working directory + WORKDIR /app/services/data-designer + + # Run the api by default, but support switching to run batch job execution + ENTRYPOINT ["data-designer-service"] + CMD ["api"] diff --git a/docker/dockerfiles/services/data-designer/Dockerfile.base b/docker/dockerfiles/services/data-designer/Dockerfile.base new file mode 100644 index 0000000000..a526d1b894 --- /dev/null +++ b/docker/dockerfiles/services/data-designer/Dockerfile.base @@ -0,0 +1,38 @@ +# Target for downloading datasets from NGC +FROM ubuntu:24.04 AS assets-initial + +# Install dependencies +RUN apt update && apt install -y wget unzip + +ARG TARGETARCH +# Install the NGC cli tool +RUN case "$TARGETARCH" in \ + "amd64") \ + URL=https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions/4.6.6/files/ngccli_linux.zip ;; \ + "arm64") \ + URL=https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions/4.6.6/files/ngccli_arm64.zip ;; \ + *) \ + echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \ + esac && \ + wget --content-disposition $URL -O ngccli.zip && unzip ngccli.zip && chmod u+x ngc-cli/ngc + +# Build arg to specify which version of the datasets resource in NGC to use. +# Set this to "0.0.x-slim" for faster build (1K-row datasets instead of 1M-row) +ARG NEMOTRON_PERSONAS_DATASETS_VERSION + +# Create the final location for the data +RUN mkdir -p /data + +# Download the personas datasets resource and move to the expected path +RUN --mount=type=secret,id=NGC_CLI_API_KEY \ + NGC_CLI_API_KEY=$(cat /run/secrets/NGC_CLI_API_KEY) ngc-cli/ngc registry resource download-version --org nvidian nvidian/nemo-llm/nemotron-personas-datasets:$NEMOTRON_PERSONAS_DATASETS_VERSION && \ + mv nemotron-personas-datasets_v$NEMOTRON_PERSONAS_DATASETS_VERSION/datasets /data + + +FROM root-distroless-base-3-11 AS assets + +# Copy datasets from the assets image, split for better pull/push performance +COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/en_IN.parquet /app/data/data-designer/datasets/en_IN.parquet +COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/en_US.parquet /app/data/data-designer/datasets/en_US.parquet +COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/hi_IN.parquet /app/data/data-designer/datasets/hi_IN.parquet +COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/ja_JP.parquet /app/data/data-designer/datasets/ja_JP.parquet diff --git a/docker/dockerfiles/services/guardrails/Dockerfile.bake b/docker/dockerfiles/services/guardrails/Dockerfile.bake new file mode 100644 index 0000000000..4234e6aae9 --- /dev/null +++ b/docker/dockerfiles/services/guardrails/Dockerfile.bake @@ -0,0 +1,98 @@ +####### +# Guardrails Build Targets. Please use `docker buildx bake` to build. See `README.md` +####### + +#build-guardrails: + FROM root-python-3-11 AS build + + # This only installs all external deps of nemoguardrails and dep workspace members + ENV ANNOY_COMPILER_ARGS="-D_CRT_SECURE_NO_WARNINGS,-DANNOYLIB_MULTITHREADED_BUILD" + + RUN if [ "$TARGETARCH" = "arm64" ]; then \ + export ANNOY_COMPILER_ARGS="$ANNOY_COMPILER_ARGS,-march=armv8-a"; \ + else \ + export ANNOY_COMPILER_ARGS="$ANNOY_COMPILER_ARGS,-march=x86-64"; \ + fi && \ + echo $ANNOY_COMPILER_ARGS + + # Create the non-root user and adjust permissions + # This approach works for running in OpenShift because: + # 1. OpenShift runs containers with arbitrary UIDs but using the root group (GID 0) + # 2. We're creating a specific user here, but the UID may be different in OpenShift + # 3. The important part is setting group permissions correctly + # We mainly create a user and move some directories, the rest of the permission changes happen below + RUN adduser --disabled-password --gecos "" nvs && \ + # Create logs directory with appropriate permissions + mkdir -p /app/services/guardrails/logs && \ + chmod -R g=u /app/services/guardrails/logs && \ + # Copy passwd file to allow user to be recognized in distroless image + cp /etc/passwd /app/passwd + + COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + RUN uv sync --frozen --no-install-project --no-dev --no-install-workspace --no-editable --package=guardrails + + # Finally install the workspace members and the project + COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages + COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk + COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs + COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi + + COPY . /app/services/guardrails + + # Generate constraints.txt needed to extend our image and remove local dependencies + RUN uv pip compile services/guardrails/pyproject.toml -o constraints.txt && \ + sed -i '/^-e file/d' constraints.txt + + RUN mv /app/constraints.txt /app/services/guardrails/ + + COPY README.md /app + + RUN uv sync --frozen --no-editable --no-dev --package=guardrails + + ENV PATH="/app/.venv/bin:$PATH" + + # Download the `all-MiniLM-L6-v2` model + RUN python -c "from fastembed.embedding import TextEmbedding; TextEmbedding('sentence-transformers/all-MiniLM-L6-v2');" && \ + nemoguardrails --help + + +#test-guardrails: + FROM build AS test + RUN uv sync --dev --frozen --no-editable --package=guardrails # get dev deps + RUN uv run --no-sync pytest services/guardrails + +#docker-guardrails: + FROM root-distroless-base-3-11 AS docker + ENV GUARDRAILS_HOST="0.0.0.0" + ENV GUARDRAILS_PORT="7331" + ENV NIM_ENDPOINT_URL="https://integrate.api.nvidia.com/v1" + ENV DEFAULT_LLM_PROVIDER="nim" + ENV DEFAULT_CONFIG_ID="default" + ENV CONFIG_STORE_PATH="/app/services/guardrails/config-store" + ENV NEMO_GUARDRAILS_SERVER_ENABLE_CORS=False + ENV NEMO_GUARDRAILS_SERVER_ALLOWED_ORIGINS="*" + ENV TELEMETRY_ENABLED=False + ENV DEMO=FALSE + ENV LOG_LEVEL=${LOG_LEVEL:-INFO} + + ENV PATH="/app/.venv/bin:$PATH" + + # TODO: is this a sensible canonical location? should this be somewhere in `/etc` or `/opt`? + COPY --chown=nvs:root --chmod=g=u config-store /app/services/guardrails/config-store + # Copy files from build stage with correct permissions + COPY --from=build --chown=nvs:root --chmod=g=u /app /app + COPY --from=build --chown=nvs:root --chmod=g=u /tmp /tmp + COPY --from=build --chown=nvs:root --chmod=g=u /app/passwd /etc/passwd + + COPY fixes/cve-2025-8194/tarfile.py.fix /usr/local/lib/python3.11/tarfile.py + + # Set user to nvs for local testing + # Note: OpenShift will override this with an arbitrary UID, + # but the permissions set in build and on copy will ensure it works + USER nvs + + # Set the working directory to services/guardrails + WORKDIR /app/services/guardrails + + # CMD uvicorn was not setting PORT and HOST + ENTRYPOINT ["opentelemetry-instrument", "python", "/app/.venv/lib/python3.11/site-packages/guardrails/entrypoint.py"] diff --git a/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake b/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake new file mode 100644 index 0000000000..0596b3b469 --- /dev/null +++ b/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake @@ -0,0 +1,54 @@ +####### +# Guardrails Callout server build targets. Please use `docker buildx bake` to build. +####### + +#deps: base container for both build and test +FROM root-golang-base AS deps +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . + + +#build: build the Go binaries +FROM deps AS build +ARG TARGETOS TARGETARCH +# Build the Go application, creating a statically linked binary. +# CGO_ENABLED=0 is important for creating a pure Go binary without C dependencies. +# This allows it to run in a minimal 'scratch' image. +# GOOSE=$TARGETOS GOARCH=$TARGETARCH is important for cross platform builds where the build platform might not be the same as the target platform. +# In this case, the target platform is the source of truth for building the Go binaries +RUN CGO_ENABLED=0 GOOSE=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/main ./cmd/server/main.go +RUN CGO_ENABLED=0 GOOSE=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/mock-server ./cmd/mock-server/main.go + + +#gen-certs: Generate TLS certs. Since these certs are self-signed and internal, clients cannot verify them. +# these are mainly for running the service in GCP, where GCP ALB's don't verify certs, but do require TLS for encryption. +FROM alpine:3.22 AS gen-certs +RUN apk add --no-cache openssl +RUN mkdir -p /out/ssl_creds && \ + openssl req -x509 -newkey rsa:4096 -nodes -days 365 \ + -keyout /out/ssl_creds/server.key -out /out/ssl_creds/server.crt \ + -subj "/CN=guardrails-ext-proc-service" \ + -addext "subjectAltName = DNS:guardrails-ext-proc-service,DNS:localhost" + +#test: +FROM deps AS test +RUN go test -race -count=1 ./... + +#docker: final minimal image for server +FROM scratch AS docker +WORKDIR / +COPY --from=build /out/main /main +COPY --from=deps /app/config.yaml /config.yaml +COPY --from=gen-certs /out/ssl_creds /ssl_creds +ENV CONFIG_PATH=/config.yaml +EXPOSE 8443 8181 +ENTRYPOINT [ "/main" ] + +#mock-llm: for simulating fake chat completion chunks +FROM scratch AS mock-llm +WORKDIR / +COPY --from=build /out/mock-server /mock-server +EXPOSE 8000 +ENTRYPOINT [ "/mock-server" ] diff --git a/docker/dockerfiles/services/intake/Dockerfile.bake b/docker/dockerfiles/services/intake/Dockerfile.bake new file mode 100644 index 0000000000..8183eeab7f --- /dev/null +++ b/docker/dockerfiles/services/intake/Dockerfile.bake @@ -0,0 +1,54 @@ +####### +# Intake Build Targets. Please use `docker buildx bake` to build. See `README.md` +####### + +ARG TAG=latest + +#build-intake: + FROM root-python-3-11 AS build + COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ + RUN uv sync --frozen --no-install-project --no-dev --no-install-workspace --no-editable --package=intake + + # Copy Intake files + COPY . /app/services/intake + # Copy other root-level files we need to build Intake + COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages + COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk + COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs + COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi + + # Don't need the go packages + RUN rm -rf /app/packages/go + + ENV PATH="/app/.venv/bin:$PATH" + + RUN uv sync --frozen --no-editable --no-dev --package=intake + +#test-intake: + FROM build AS test + + # Update PYTHONPATH to ensure imports can be resolved + ENV PYTHONPATH=/app:/app/services/intake:/app/packages/nmp_common/src + # Disable testcontainers since we're using the postgres-test-fixture.sh script + ENV USE_EXTERNAL_POSTGRES=true + RUN uv sync --dev --frozen --no-editable --package=intake # get dev deps + + COPY --from=root-script-source-artifacts /artifacts/script/postgres-test-fixture.sh /script/postgres-test-fixture.sh + WORKDIR /app/services/intake + RUN --network=none bash /script/postgres-test-fixture.sh uv run --no-sync pytest -s + +#docker-intake: + FROM root-distroless-base-3-11 AS docker + + # Update PYTHONPATH to ensure imports can be resolved + ENV PYTHONPATH=/app:/app/services/intake:/app/packages/nmp_common/src + ENV PORT=8000 + ENV HOST=0.0.0.0 + ENV PATH="/app/.venv/bin:$PATH" + + COPY --from=build /app /app + + WORKDIR /app + + EXPOSE $PORT + ENTRYPOINT ["python", "/app/services/intake/src/entrypoint.py"] diff --git a/docker/locks/README.md b/docker/locks/README.md new file mode 100644 index 0000000000..6816818e72 --- /dev/null +++ b/docker/locks/README.md @@ -0,0 +1,50 @@ +# Docker lock projects + +This directory holds committed `uv` lock projects used by Docker builds that need +reproducible Python build environments. + +## Mamba wheel builder lockfiles + +`docker/base/Dockerfile.mamba-wheel` uses these lock projects: + +- `mamba-wheel-build-py311` +- `mamba-wheel-build-py312` + +Update the matching `pyproject.toml` first, then regenerate the lockfile from the +repo root: + +```bash +uv lock --project docker/locks/mamba-wheel-build-py311 --python 3.11 +uv lock --project docker/locks/mamba-wheel-build-py312 --python 3.12 +``` + +### Torch version sync + +The `torch` pin in these lockfiles **must** match the `torch` version in the +workspace `pyproject.toml` `[dependency-groups].cu128` section. If the versions +diverge, the CUDA extension wheels (mamba-ssm, causal-conv1d) will be compiled +against a different torch ABI than the runtime images install, causing +`undefined symbol` errors at import time. + +CI enforces this via `script/check-torch-version-sync.py` in the +`Docker Lock Lint` workflow. When bumping torch in the workspace, update the +lockfiles too: + +```bash +# 1. Edit both pyproject.toml files to set the new torch version +# 2. Regenerate lockfiles +uv lock --project docker/locks/mamba-wheel-build-py311 --python 3.11 +uv lock --project docker/locks/mamba-wheel-build-py312 --python 3.12 +``` + +### Verifying lockfiles + +If you change these lock projects, verify both Linux target environments still +resolve cleanly: + +```bash +uv sync --project docker/locks/mamba-wheel-build-py311 --locked --no-install-project --dry-run --python-platform x86_64-unknown-linux-gnu +uv sync --project docker/locks/mamba-wheel-build-py311 --locked --no-install-project --dry-run --python-platform aarch64-unknown-linux-gnu +uv sync --project docker/locks/mamba-wheel-build-py312 --locked --no-install-project --dry-run --python-platform x86_64-unknown-linux-gnu +uv sync --project docker/locks/mamba-wheel-build-py312 --locked --no-install-project --dry-run --python-platform aarch64-unknown-linux-gnu +``` diff --git a/services/automodel/docker/locks/mamba-wheel-build-py311/pyproject.toml b/docker/locks/mamba-wheel-build-py311/pyproject.toml similarity index 100% rename from services/automodel/docker/locks/mamba-wheel-build-py311/pyproject.toml rename to docker/locks/mamba-wheel-build-py311/pyproject.toml diff --git a/services/automodel/docker/locks/mamba-wheel-build-py311/uv.lock b/docker/locks/mamba-wheel-build-py311/uv.lock similarity index 100% rename from services/automodel/docker/locks/mamba-wheel-build-py311/uv.lock rename to docker/locks/mamba-wheel-build-py311/uv.lock diff --git a/services/automodel/docker/locks/mamba-wheel-build-py312/pyproject.toml b/docker/locks/mamba-wheel-build-py312/pyproject.toml similarity index 100% rename from services/automodel/docker/locks/mamba-wheel-build-py312/pyproject.toml rename to docker/locks/mamba-wheel-build-py312/pyproject.toml diff --git a/services/automodel/docker/locks/mamba-wheel-build-py312/uv.lock b/docker/locks/mamba-wheel-build-py312/uv.lock similarity index 100% rename from services/automodel/docker/locks/mamba-wheel-build-py312/uv.lock rename to docker/locks/mamba-wheel-build-py312/uv.lock diff --git a/docker/scripts/cve-cleanup.sh b/docker/scripts/cve-cleanup.sh new file mode 100644 index 0000000000..12982f7a80 --- /dev/null +++ b/docker/scripts/cve-cleanup.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -e + +# NOTE: Do NOT rm -rf /root/.cache/uv here. The nemo-rl base image installs +# packages using uv's default hard-link mode, so venv files are hard links to +# cache entries. Removing the cache in a separate Docker layer triggers +# overlayfs whiteouts that also hide the hard-linked venv files (e.g. +# typing_extensions, pydantic), breaking imports at runtime. + +# remove wandb-core gobinary (unused) +rm -f /app/.venv/lib/python3.11/site-packages/wandb/bin/wandb-core +rm -f /opt/venv/lib/python3.12/site-packages/wandb/bin/wandb-core + +# remove nsight cli (unused) - nic_sampler vulnerable +rm -rf /usr/local/cuda/NsightSystems-cli-* + +# Remove shadowed system packages causing CVEs. +# Uses targeted rm -rf instead of pip uninstall because the nemo-rl base image +# ships packages with corrupted dist-info (missing METADATA / broken RECORD +# files). pip uninstall reads RECORD to decide which files to delete; corrupted +# RECORDs can list files belonging to OTHER packages (pydantic, typing_extensions, +# etc.), causing pip to silently delete them as collateral damage. +rm -rf /opt/pytorch/pytorch/third_party/onnx +rm -rf /usr/local/lib/python3.12/dist-packages/cbor2 \ + /usr/local/lib/python3.12/dist-packages/cbor2-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/xgrammar \ + /usr/local/lib/python3.12/dist-packages/xgrammar-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/black \ + /usr/local/lib/python3.12/dist-packages/_black* \ + /usr/local/lib/python3.12/dist-packages/black-*.dist-info \ + /usr/local/lib/python3.12/dist-packages/blackd +rm -rf /usr/local/lib/python3.12/dist-packages/tornado \ + /usr/local/lib/python3.12/dist-packages/tornado-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/pyasn1 \ + /usr/local/lib/python3.12/dist-packages/pyasn1-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/aiohttp \ + /usr/local/lib/python3.12/dist-packages/aiohttp-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/onnx \ + /usr/local/lib/python3.12/dist-packages/onnx-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/python_multipart \ + /usr/local/lib/python3.12/dist-packages/python_multipart-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/urllib3 \ + /usr/local/lib/python3.12/dist-packages/urllib3-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/mlflow \ + /usr/local/lib/python3.12/dist-packages/mlflow-*.dist-info \ + /usr/local/lib/python3.12/dist-packages/mlflow_skinny-*.dist-info +rm -rf /usr/local/lib/python3.12/dist-packages/wandb \ + /usr/local/lib/python3.12/dist-packages/wandb-*.dist-info diff --git a/services/unsloth/docker/Dockerfile.platform-workspace b/docker/unsloth/Dockerfile.platform-workspace similarity index 73% rename from services/unsloth/docker/Dockerfile.platform-workspace rename to docker/unsloth/Dockerfile.platform-workspace index 6d4d46c55d..bb6fb9ec89 100644 --- a/services/unsloth/docker/Dockerfile.platform-workspace +++ b/docker/unsloth/Dockerfile.platform-workspace @@ -1,14 +1,14 @@ # syntax=docker/dockerfile:1 # Minimal Platform workspace slice for nmp-unsloth container installs. # Used as a named build context (platform-workspace). -# Keep in sync with services/unsloth/docker/pyproject.workspace.toml members. +# Keep in sync with docker/unsloth/pyproject.workspace.toml members. FROM scratch AS platform-workspace -# Do not copy repo-root pyproject.toml/uv.lock — they reference the full monorepo workspace. -COPY services/unsloth/docker/pyproject.workspace.toml pyproject.toml +# Use a reduced workspace file for this partial source tree. +COPY docker/unsloth/pyproject.workspace.toml pyproject.toml # nemo-platform-sdk's hatch build force-includes docs/ from the repo root # (see sdk/python/nemo-platform/pyproject.toml [tool.hatch.build.targets.wheel.force-include]). -# docs/fern/openapi/openapi.yaml is a symlink to ../../../openapi/openapi.yaml — copy both +# docs/fern/openapi/openapi.yaml is a symlink to ../../../openapi/openapi.yaml; copy both # trees so the symlink resolves at build time. COPY docs docs COPY openapi openapi diff --git a/services/unsloth/docker/README.md b/docker/unsloth/README.md similarity index 98% rename from services/unsloth/docker/README.md rename to docker/unsloth/README.md index f7b6645f3a..2f06cae0cb 100644 --- a/services/unsloth/docker/README.md +++ b/docker/unsloth/README.md @@ -141,7 +141,7 @@ docker buildx bake \ export IMAGE_REGISTRY="my-registry/nemo-platform-dev" export BAKE_TAG="$(git rev-parse --short HEAD)" docker buildx build \ - -f services/unsloth/docker/Dockerfile.nmp-unsloth-training \ + -f docker/Dockerfile.nmp-unsloth-training \ --output type=docker,dest=/tmp/nmp-unsloth-training.tar \ --target runtime \ -t "${IMAGE_REGISTRY}/nmp-unsloth-training:${BAKE_TAG}" \ @@ -292,7 +292,7 @@ nemo files filesets delete qwen-unsloth-smoke-out -w default ## Architecture notes -- **Step layout** mirrors `services/automodel/docker/`. The compiler in +- **Step layout** mirrors `docker/automodel/`. The compiler in `nmp.unsloth.app.jobs.compiler` emits the same 4-step `PlatformJobSpec` shape automodel emits; the only difference is the image and the training entrypoint module. diff --git a/services/unsloth/docker/pyproject.workspace.toml b/docker/unsloth/pyproject.workspace.toml similarity index 79% rename from services/unsloth/docker/pyproject.workspace.toml rename to docker/unsloth/pyproject.workspace.toml index b8ff159aff..c056cbee8a 100644 --- a/services/unsloth/docker/pyproject.workspace.toml +++ b/docker/unsloth/pyproject.workspace.toml @@ -1,6 +1,5 @@ # Minimal uv workspace for nmp-unsloth container image builds only. -# Replaces the repo-root pyproject.toml in Dockerfile.platform-workspace so -# partial COPY trees are not validated against the full monorepo workspace. +# Keeps uv validation scoped to the partial source tree copied into the image. [project] name = "nemo-platform-unsloth-image" diff --git a/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/SKILL.md b/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/SKILL.md index 1f43c37b73..5927f74608 100644 --- a/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/SKILL.md +++ b/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/SKILL.md @@ -90,7 +90,7 @@ Training never runs inside the `nemo` CLI process. After `submit`, the platform' - **Unsloth validation defaults** — when `dataset.validation_path` is set and `schedule.eval_steps` is omitted, the trainer runs validation once per effective epoch automatically. Report final `metrics.val_loss` from job status (see **Report to user**). Set `eval_steps` explicitly to override cadence. - **Do not use local `docker info`** to pick automodel vs unsloth. After auth, run `uv run nemo jobs list-execution-profiles -f json` against the user's platform (see `references/troubleshooting.md`). Default output is a table — **`-f json` is required** for scripting; parse **stdout only** (do not pipe `2>&1` into `json.load`). - **Do not merge stderr into stdout when parsing JSON** — `submit`, `explain`, and `-f json` commands write **JSON on stdout**; harmless warnings like `Configuration file not found, using defaults` go to **stderr**. Piping with **`2>&1`** before `json.load` raises `JSONDecodeError` even when submit **succeeded** — a common cause of **duplicate jobs** when the agent re-submits after a parse error. Parse stdout only; redirect stderr if needed (`2>/dev/null`). See `references/troubleshooting.md` § **Parsing CLI JSON**. -- For submit/image/plugin errors (both backends), read `references/troubleshooting.md`. Unsloth needs the `nmp-unsloth-training` container image on the **platform host's** Docker daemon (see `services/unsloth/docker/README.md`). +- For submit/image/plugin errors (both backends), read `references/troubleshooting.md`. Unsloth needs the `nmp-unsloth-training` container image on the **platform host's** Docker daemon (see `docker/unsloth/README.md`). - **Missing training image on a remote platform** — if the user gave a non-localhost `NEMO_BASE_URL` / `NMP_BASE_URL` (e.g. `10.0.0.51:8080`) and the job errors with `Failed to pull image`, `manifest unknown`, or missing `nmp-unsloth-training` / automodel training image: **do not** run `docker build`, `docker pull`, or `docker buildx bake` on the agent machine. Report with **Report to user** (use **Output adapter fileset (planned):** on error), then append on-target build steps from `references/troubleshooting.md` § **Missing training images**. ## Workflow @@ -412,7 +412,7 @@ There is no `parallelism` block, no TP / PP / DP, no GBS divisibility math. Mult | Symptom | Action | |---------|--------| | CUDA OOM | Halve `per_device_train_batch_size` (keep effective batch via `gradient_accumulation_steps`); then lower `model.max_seq_length`; then drop `lora.rank` to 8 | -| Missing `nmp-unsloth-training` image | Build/pull the Unsloth container image — see `references/troubleshooting.md` and `services/unsloth/docker/README.md` | +| Missing `nmp-unsloth-training` image | Build/pull the Unsloth container image — see `references/troubleshooting.md` and `docker/unsloth/README.md` | | `Unsloth training requires platform.runtime: docker` | Platform not using the Docker executor | Start platform with `platform.runtime: docker` and a GPU execution profile; training runs in containers on that host's Docker daemon | | Loss not moving | Raise `learning_rate` one step (e.g. `5e-5` → `1e-4`); confirm `apply_chat_template` matches the data shape; check the LoRA `target_modules` covers the right layers (defaults are Unsloth's 7-module set) | diff --git a/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/references/troubleshooting.md b/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/references/troubleshooting.md index 6cc4c87d75..2dc9b7ba00 100644 --- a/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/references/troubleshooting.md +++ b/plugins/nemo-customizer/src/nemo_customizer/skills/nemo-customizer/references/troubleshooting.md @@ -103,7 +103,7 @@ Job errors like `Failed to pull image … nmp-unsloth-training:… Not Found`, ` | Situation | Action | |-----------|--------| | **Remote platform** — user gave a host/URL (e.g. `10.0.0.51:8080`) or you set `NEMO_BASE_URL` / `NMP_BASE_URL` to something other than `http://127.0.0.1:8080` or `http://localhost:8080` | **Do not** run `docker build`, `docker pull`, or `docker buildx bake` on the agent machine — that only affects the agent's local daemon, not the remote platform. Tell the user they must build or load the image **on the target host** (the machine whose Docker daemon runs the GPU job steps). Report with **Report to user** in `SKILL.md`, then append **Report follow-up — missing image (remote platform)** below. Stop; do not retry submit until the user confirms the image is available on the target. | -| **Local platform** — default URL only (`127.0.0.1:8080` / `localhost:8080`) | Build or pull on **that same host** where `nemo services run` and Docker share a daemon. See build commands below and `services/unsloth/docker/README.md` (unsloth) or automodel docker docs. Set env vars **before** starting/restarting the platform. | +| **Local platform** — default URL only (`127.0.0.1:8080` / `localhost:8080`) | Build or pull on **that same host** where `nemo services run` and Docker share a daemon. See build commands below and `docker/unsloth/README.md` (unsloth) or automodel docker docs. Set env vars **before** starting/restarting the platform. | Image env vars are read when the platform starts (not per job): @@ -135,7 +135,7 @@ export NMP_UNSLOTH_TRAINING_IMAGE="${IMAGE_REGISTRY:-my-registry/nemo-platform-d nemo services restart ``` -Or push to a registry the target can pull from — see **Option B** in `services/unsloth/docker/README.md` — then set `NMP_UNSLOTH_TRAINING_IMAGE` to that full ref before restart. +Or push to a registry the target can pull from — see **Option B** in `docker/unsloth/README.md` — then set `NMP_UNSLOTH_TRAINING_IMAGE` to that full ref before restart. After the image is on the target, re-submit the same job JSON (use a fresh `output.name` if a prior partial run already registered an adapter). @@ -143,7 +143,7 @@ After the image is on the target, re-submit the same job JSON (use a fresh `outp When submit or poll returns a missing-image error and the base URL is **user-overridden**, start with the **Report to user** template in `SKILL.md` (status `error`, **Output adapter fileset (planned):**, Notes quoting the pull error and naming the target host). Then append these sections: -**What you need to do on the target host** — build or load the training image on the machine running the NeMo platform (where `docker info` works for the platform's daemon), set `NMP_UNSLOTH_TRAINING_IMAGE` or automodel image env vars, and restart platform services. Full steps: `services/unsloth/docker/README.md` (unsloth) or automodel docker docs. +**What you need to do on the target host** — build or load the training image on the machine running the NeMo platform (where `docker info` works for the platform's daemon), set `NMP_UNSLOTH_TRAINING_IMAGE` or automodel image env vars, and restart platform services. Full steps: `docker/unsloth/README.md` (unsloth) or automodel docker docs. **Re-submit after the image is available:** @@ -162,7 +162,7 @@ Then poll until terminal status. Offer to re-submit once the user confirms the i | `Unsloth does not support local run` | Used `run` instead of `submit` | `nemo customization unsloth submit -w ` | | `Unsloth training requires platform.runtime: docker` | Platform not configured for Docker GPU jobs | Start platform with Docker runtime and a GPU execution profile | | Unknown execution profile | Default `gpu` profile missing or wrong | Re-list profiles; pass `--profile ` on submit | -| Missing `nmp-unsloth-training` image / `Failed to pull image` / `manifest unknown` | Image not on the **platform host's** Docker daemon | **Remote platform** (`NEMO_BASE_URL` not localhost): tell user to build on the target — **do not** `docker build` locally. **Local platform**: build on same host; see **Missing training images** above and `services/unsloth/docker/README.md` | +| Missing `nmp-unsloth-training` image / `Failed to pull image` / `manifest unknown` | Image not on the **platform host's** Docker daemon | **Remote platform** (`NEMO_BASE_URL` not localhost): tell user to build on the target — **do not** `docker build` locally. **Local platform**: build on same host; see **Missing training images** above and `docker/unsloth/README.md` | | `torch.cuda.is_available()` False in training step logs | GPU not exposed to the container step | Confirm the execution profile is GPU-backed; check platform Docker GPU setup | | Job stuck in `active` after training step completes | Upload / model-entity steps still running | Keep polling top-level status (same as automodel) | diff --git a/plugins/nemo-unsloth/README.md b/plugins/nemo-unsloth/README.md index 39521b2bd2..8fd9468be2 100644 --- a/plugins/nemo-unsloth/README.md +++ b/plugins/nemo-unsloth/README.md @@ -10,7 +10,7 @@ Unsloth is **submit-only**: training executes remotely on the platform's GPU clu The plugin is part of `enabled-plugins` once `uv sync` runs. No GPU / ML deps are installed locally; only the container image needs them. -Container image build / push instructions live in [`services/unsloth/docker/README.md`](../../services/unsloth/docker/README.md). +Container image build / push instructions live in [`docker/unsloth/README.md`](../../docker/unsloth/README.md). ## Submit a training job @@ -86,5 +86,5 @@ The plugin imports two things from the service: - `plugins/nemo-customizer/` — the customization router hub. Owns `/apis/customization`, `nemo customization`, `client.customization`. - `services/unsloth/` — the heavy code this plugin delegates to. -- `services/unsloth/docker/` — Dockerfile + build instructions for the `nmp-unsloth-training` image. +- `docker/unsloth/` — Dockerfile + build instructions for the `nmp-unsloth-training` image. - `plugins/nemo-automodel/` — sibling plugin with the same submit shape. diff --git a/services/automodel/docker/docker-bake.hcl b/services/automodel/docker/docker-bake.hcl deleted file mode 100644 index d961287205..0000000000 --- a/services/automodel/docker/docker-bake.hcl +++ /dev/null @@ -1,4 +0,0 @@ -# Moved to Platform repo root: -# docker buildx bake -f docker-bake.hcl -# -# Context is "." (repo root when run from Platform/). Do not use ../../.. here. diff --git a/services/automodel/docker/locks/README.md b/services/automodel/docker/locks/README.md deleted file mode 100644 index 7b30378a50..0000000000 --- a/services/automodel/docker/locks/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Mamba / causal-conv1d wheel build locks - -Copied from `nmp/docker/locks/` for building `causal-conv1d-wheel` and `mamba-ssm-wheel` images from the Platform repo (see `Dockerfile.mamba-wheel` and `docker-bake.hcl` group `nmp-automodel-gpu-wheels`). - -To refresh locks after dependency changes: - -```bash -cd /path/to/Platform -uv lock --project services/automodel/docker/locks/mamba-wheel-build-py311 --python 3.11 -uv lock --project services/automodel/docker/locks/mamba-wheel-build-py312 --python 3.12 -``` diff --git a/services/unsloth/docker/docker-bake.hcl b/services/unsloth/docker/docker-bake.hcl deleted file mode 100644 index d961287205..0000000000 --- a/services/unsloth/docker/docker-bake.hcl +++ /dev/null @@ -1,4 +0,0 @@ -# Moved to Platform repo root: -# docker buildx bake -f docker-bake.hcl -# -# Context is "." (repo root when run from Platform/). Do not use ../../.. here. From 91a95ff890c88772460fdb02015a5f9e2efcf9db Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 14:35:08 -0700 Subject: [PATCH 02/11] chore: make the name better for the bake graph check Signed-off-by: Brooke Storm --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c3fe97f1a3..951cd04545 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -57,7 +57,7 @@ jobs: run: | "${RUNNER_TEMP}/actionlint/actionlint" - docker-canary: + docker-bake-graph: name: Docker bake graph needs: [changes] if: > From 87dd1b5e6ce1a506b770cad84515cceca90ce4bd Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 15:01:36 -0700 Subject: [PATCH 03/11] fix: correct typo in needs list Signed-off-by: Brooke Storm --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 951cd04545..143406673a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -781,7 +781,7 @@ jobs: needs: - changes - actionlint - - docker-canary + - docker-bake-graph - lint - policy-wasm - python-unit-test-tools From 1f5ea190bd31dc7c9d5cf141387f6faf66ed2fb9 Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 15:06:36 -0700 Subject: [PATCH 04/11] fix: correct a variable Signed-off-by: Brooke Storm --- docker-bake.hcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-bake.hcl b/docker-bake.hcl index b282f2db33..9ff8cf91cc 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -135,7 +135,7 @@ function "get_arch_tag" { function "base_tags" { params = [name] result = [ - notequal(BAKE_TAG, "") ? "${IMAGE_REGISTRY}/${name}:${BAKE_TAG}" : "", + notequal(BAKE_TAG, "") ? "${BASE_REGISTRY}/${name}:${BAKE_TAG}" : "", ] } From 2b3aaaf4c88dfcc5086f1027fffbd505b29878e7 Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 15:18:40 -0700 Subject: [PATCH 05/11] fix: remove cruft Signed-off-by: Brooke Storm --- docker-bake.hcl | 16 +--- docker/Dockerfile.bake | 2 +- docker/Dockerfile.nmp-customizer-tasks | 5 +- docker/base/Dockerfile.nmp-python-base | 2 +- .../services/data-designer/Dockerfile.bake | 81 ------------------- .../services/data-designer/Dockerfile.base | 38 --------- .../guardrails/callouts/Dockerfile.bake | 6 +- 7 files changed, 10 insertions(+), 140 deletions(-) delete mode 100644 docker/dockerfiles/services/data-designer/Dockerfile.bake delete mode 100644 docker/dockerfiles/services/data-designer/Dockerfile.base diff --git a/docker-bake.hcl b/docker-bake.hcl index 9ff8cf91cc..ced456a7c6 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -3,7 +3,7 @@ ####### variable "CACHE_REGISTRY" { - default = "gitlab-master.nvidia.com:5005/aire/microservices/nmp" # alternatively "harbor.aire.nvidia.com/nmp" + default = "my-registry" } variable "CACHE_REGISTRY_BACKUP" { @@ -21,7 +21,7 @@ variable "IMAGE_REGISTRY" { # Registry where pinned base images are published (nmp-python-base, nmp-customizer-tasks-base, etc.) # In CI this matches CI_REGISTRY_IMAGE; locally override if needed. variable "BASE_REGISTRY" { - default = "gitlab-master.nvidia.com:5005/aire/microservices/nmp" + default = "my-registry" } variable "DISTROLESS_BASE" { @@ -33,7 +33,7 @@ variable "DOCKERHUB_MIRROR" { } variable "WHEELS_REGISTRY" { - default = "gitlab-master.nvidia.com:5005/aire/microservices/nmp" + default = "my-registry" } variable "BAKE_TAG" { @@ -42,19 +42,16 @@ variable "BAKE_TAG" { # Pin for nmp-python-base (built by nmp-python-base-builder; run that target to update) variable "BASE_TAG_PYTHON" { - # From MR: https://gitlab-master.nvidia.com/aire/microservices/nmp/-/merge_requests/7315 default = "dbdc61fdb117f48bc9569b4274afe836cf0de030" } # Pin for nmp-automodel-base. variable "BASE_TAG_AUTOMODEL" { - # From MR: https://gitlab-master.nvidia.com/aire/microservices/nmp/-/merge_requests/7315 default = "dbdc61fdb117f48bc9569b4274afe836cf0de030" } # Pin for nmp-customizer-tasks-base (built by nmp-customizer-tasks-base-builder) variable "BASE_TAG_CUSTOMIZER_TASKS" { - # From MR: https://gitlab-master.nvidia.com/aire/microservices/nmp/-/merge_requests/7315 default = "dbdc61fdb117f48bc9569b4274afe836cf0de030" } @@ -199,13 +196,6 @@ group "docker-auditor" { ] } -# Data designer API and job image -group "docker-data" { - targets = [ - "data-designer-docker", - ] -} - # Legacy groups for backward compatibility group "docker-amd64-only" { targets = [ diff --git a/docker/Dockerfile.bake b/docker/Dockerfile.bake index 69a1a1a77e..546e290f44 100644 --- a/docker/Dockerfile.bake +++ b/docker/Dockerfile.bake @@ -2,7 +2,7 @@ # Common Build Targets. Please use `docker buildx bake` to build. See `README.md` ####### -ARG DISTROLESS_BASE=gitlab-master.nvidia.com:5005/aire/platform/ci-cd/images/distroless-nmp/python3.11:1.0.7 +ARG DISTROLESS_BASE=nvcr.io/nvidia/distroless/python:3.11-v4.0.3 #root-uv-binary-base FROM ghcr.io/astral-sh/uv:0.9.14 AS root-uv-binary-base diff --git a/docker/Dockerfile.nmp-customizer-tasks b/docker/Dockerfile.nmp-customizer-tasks index 36096ac744..10a6f034ee 100644 --- a/docker/Dockerfile.nmp-customizer-tasks +++ b/docker/Dockerfile.nmp-customizer-tasks @@ -13,13 +13,12 @@ ARG BASE_TAG_CUSTOMIZER_TASKS=local ARG FAISS_WHEEL_IMAGE=local ARG CAUSAL_CONV1D_WHEEL_IMAGE=local ARG MAMBA_SSM_WHEEL_IMAGE=local -ARG BASE_REGISTRY=gitlab-master.nvidia.com:5005/aire/microservices/nmp +ARG BASE_REGISTRY=my-registry # ============================================================================= # Base Image # ============================================================================= -# Pulls a pre-built image from GitLab registry, which is built separately -# by the nmp-customizer-tasks-base-builder stage. +# Pulls a pre-built image built by the nmp-customizer-tasks-base-builder stage. FROM ${BASE_REGISTRY}/nmp-customizer-tasks-base:$BASE_TAG_CUSTOMIZER_TASKS AS nmp-customizer-tasks-base # ============================================================================= diff --git a/docker/base/Dockerfile.nmp-python-base b/docker/base/Dockerfile.nmp-python-base index 2d21fad563..dd4efa3db9 100644 --- a/docker/base/Dockerfile.nmp-python-base +++ b/docker/base/Dockerfile.nmp-python-base @@ -10,7 +10,7 @@ # Pull stages: consume pre-built images from the registry. ARG BASE_TAG_PYTHON=local -ARG BASE_REGISTRY=gitlab-master.nvidia.com:5005/aire/microservices/nmp +ARG BASE_REGISTRY=my-registry FROM ${BASE_REGISTRY}/nmp-python-base:${BASE_TAG_PYTHON} AS nmp-python-base FROM ${BASE_REGISTRY}/nmp-python-dev-base:${BASE_TAG_PYTHON} AS nmp-python-dev-base diff --git a/docker/dockerfiles/services/data-designer/Dockerfile.bake b/docker/dockerfiles/services/data-designer/Dockerfile.bake deleted file mode 100644 index 5e975e7c7b..0000000000 --- a/docker/dockerfiles/services/data-designer/Dockerfile.bake +++ /dev/null @@ -1,81 +0,0 @@ -# syntax=docker/dockerfile:1.17 -# default ARG -ARG ASSETS_IMAGE="gitlab-master.nvidia.com:5005/aire/microservices/nmp/data-designer-assets" -ARG ASSETS_IMAGE_TAG="0.0.6" - -# Build stage for the data-designer service -#build-data-designer: - FROM root-python-3-11 AS build - - # Create the non-root user and adjust permissions - # This approach works for running in OpenShift and with volume mounts because: - # 1. OpenShift runs containers with arbitrary UIDs but using the root group (GID 0) - # 2. We're creating a specific user here, but the UID may be different in OpenShift - # 3. The important part is setting group permissions correctly - # We mainly create a user and move some directories, the rest of the permission changes happen below - RUN adduser --disabled-password --gecos "" nvs && \ - # Create logs directory with appropriate permissions - mkdir -p /app/services/data-designer/logs && \ - chmod -R g=u /app/services/data-designer/logs && \ - # Copy passwd file to allow user to be recognized in distroless image - cp /etc/passwd /app/passwd - - # Install dependencies - COPY --from=root-lib-source-artifacts /artifacts/uv.lock /artifacts/pyproject.toml /app/ - RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --no-install-project --no-dev --no-install-workspace --no-editable --package=data-designer-service - - # Copy source code - COPY --from=root-lib-source-artifacts /artifacts/packages /app/packages - COPY --from=root-lib-source-artifacts /artifacts/sdk /app/sdk - COPY --from=root-lib-source-artifacts /artifacts/docs /app/docs - COPY --from=root-lib-source-artifacts /artifacts/openapi /app/openapi - COPY . /app/services/data-designer - - # Make run.sh executable - RUN chmod +x /app/services/data-designer/run.sh - - # Generate constraints file - RUN uv pip compile services/data-designer/pyproject.toml -o constraints.txt && sed -i '/^-e file/d' constraints.txt - RUN mv /app/constraints.txt /app/services/data-designer/ - - # Copy README - COPY README.md /app - - # Install the package - RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --frozen --no-editable --no-dev --package=data-designer-service - - ENV PATH="/app/.venv/bin:$PATH" - -# Common configuration for both production and debug targets -#docker-data-designer: - FROM gitlab-master.nvidia.com:5005/aire/microservices/nmp/data-designer-base:0.0.6-split AS docker - - ENV DEFAULT_CONFIG_ID=default - ENV DEFAULT_LLM_PROVIDER=nim - ENV LOG_LEVEL=INFO - ENV NEMO_TELEMETRY_ENABLED=False - ENV NIM_ENDPOINT_URL=https://integrate.api.nvidia.com/v1 - ENV DEMO=False - - ENV PATH="/app/.venv/bin:$PATH" - - # Copy files from build stage with correct permissions - COPY --from=build --chown=nvs:root --chmod=g=u /app /app - COPY --from=build --chown=nvs:root --chmod=g=u /tmp /tmp - COPY --from=build --chown=nvs:root --chmod=g=u /app/passwd /etc/passwd - - COPY fixes/cve-2025-8194/tarfile.py.fix /usr/local/lib/python3.11/tarfile.py - - # Set user to nvs - # Note: OpenShift will override this with an arbitrary UID, - # but the permissions set in build and on copy will ensure it works - USER nvs - - # Set working directory - WORKDIR /app/services/data-designer - - # Run the api by default, but support switching to run batch job execution - ENTRYPOINT ["data-designer-service"] - CMD ["api"] diff --git a/docker/dockerfiles/services/data-designer/Dockerfile.base b/docker/dockerfiles/services/data-designer/Dockerfile.base deleted file mode 100644 index a526d1b894..0000000000 --- a/docker/dockerfiles/services/data-designer/Dockerfile.base +++ /dev/null @@ -1,38 +0,0 @@ -# Target for downloading datasets from NGC -FROM ubuntu:24.04 AS assets-initial - -# Install dependencies -RUN apt update && apt install -y wget unzip - -ARG TARGETARCH -# Install the NGC cli tool -RUN case "$TARGETARCH" in \ - "amd64") \ - URL=https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions/4.6.6/files/ngccli_linux.zip ;; \ - "arm64") \ - URL=https://api.ngc.nvidia.com/v2/resources/nvidia/ngc-apps/ngc_cli/versions/4.6.6/files/ngccli_arm64.zip ;; \ - *) \ - echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \ - esac && \ - wget --content-disposition $URL -O ngccli.zip && unzip ngccli.zip && chmod u+x ngc-cli/ngc - -# Build arg to specify which version of the datasets resource in NGC to use. -# Set this to "0.0.x-slim" for faster build (1K-row datasets instead of 1M-row) -ARG NEMOTRON_PERSONAS_DATASETS_VERSION - -# Create the final location for the data -RUN mkdir -p /data - -# Download the personas datasets resource and move to the expected path -RUN --mount=type=secret,id=NGC_CLI_API_KEY \ - NGC_CLI_API_KEY=$(cat /run/secrets/NGC_CLI_API_KEY) ngc-cli/ngc registry resource download-version --org nvidian nvidian/nemo-llm/nemotron-personas-datasets:$NEMOTRON_PERSONAS_DATASETS_VERSION && \ - mv nemotron-personas-datasets_v$NEMOTRON_PERSONAS_DATASETS_VERSION/datasets /data - - -FROM root-distroless-base-3-11 AS assets - -# Copy datasets from the assets image, split for better pull/push performance -COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/en_IN.parquet /app/data/data-designer/datasets/en_IN.parquet -COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/en_US.parquet /app/data/data-designer/datasets/en_US.parquet -COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/hi_IN.parquet /app/data/data-designer/datasets/hi_IN.parquet -COPY --from=assets-initial --chown=nvs:root --chmod=g=u data/datasets/ja_JP.parquet /app/data/data-designer/datasets/ja_JP.parquet diff --git a/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake b/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake index 0596b3b469..49b81dd4c6 100644 --- a/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake +++ b/docker/dockerfiles/services/guardrails/callouts/Dockerfile.bake @@ -16,10 +16,10 @@ ARG TARGETOS TARGETARCH # Build the Go application, creating a statically linked binary. # CGO_ENABLED=0 is important for creating a pure Go binary without C dependencies. # This allows it to run in a minimal 'scratch' image. -# GOOSE=$TARGETOS GOARCH=$TARGETARCH is important for cross platform builds where the build platform might not be the same as the target platform. +# GOOS=$TARGETOS GOARCH=$TARGETARCH is important for cross platform builds where the build platform might not be the same as the target platform. # In this case, the target platform is the source of truth for building the Go binaries -RUN CGO_ENABLED=0 GOOSE=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/main ./cmd/server/main.go -RUN CGO_ENABLED=0 GOOSE=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/mock-server ./cmd/mock-server/main.go +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/main ./cmd/server/main.go +RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /out/mock-server ./cmd/mock-server/main.go #gen-certs: Generate TLS certs. Since these certs are self-signed and internal, clients cannot verify them. From 3171062ea1c8ed71137392877951710b5fa36144 Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 15:51:14 -0700 Subject: [PATCH 06/11] fix: adjust things to be more local-build friendly Signed-off-by: Brooke Storm --- docker-bake.hcl | 161 +++++++++++++++-------- docker/Dockerfile.nmp-automodel-base | 6 +- docker/Dockerfile.nmp-automodel-tasks | 8 +- docker/Dockerfile.nmp-automodel-training | 8 +- docker/Dockerfile.nmp-customizer-tasks | 43 +++--- docker/base/Dockerfile.nmp-python-base | 5 +- docker/base/Dockerfile.nmp-studio-ui | 2 +- 7 files changed, 135 insertions(+), 98 deletions(-) diff --git a/docker-bake.hcl b/docker-bake.hcl index ced456a7c6..38b86f9e84 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -24,12 +24,48 @@ variable "BASE_REGISTRY" { default = "my-registry" } +variable "USE_PREBUILT_BASES" { + default = "" +} + +variable "PYTHON_BASE_TARGET" { + default = "" +} + +variable "PYTHON_DEV_BASE_TARGET" { + default = "" +} + +variable "CUSTOMIZER_TASKS_BASE_CONTEXT" { + default = "" +} + +variable "AUTOMODEL_BASE_CONTEXT" { + default = "" +} + +variable "USE_LOCAL_WHEELS" { + default = "" +} + +variable "FAISS_WHEEL_CONTEXT" { + default = "" +} + +variable "CAUSAL_CONV1D_WHEEL_CONTEXT" { + default = "" +} + +variable "MAMBA_SSM_WHEEL_CONTEXT" { + default = "" +} + variable "DISTROLESS_BASE" { default = "nvcr.io/nvidia/distroless/python:3.11-v4.0.3" } variable "DOCKERHUB_MIRROR" { - default = "dockerhub.nvidia.com" + default = "docker.io/library" } variable "WHEELS_REGISTRY" { @@ -136,6 +172,41 @@ function "base_tags" { ] } +function "python_base_target" { + params = [] + result = notequal(PYTHON_BASE_TARGET, "") ? PYTHON_BASE_TARGET : notequal(USE_PREBUILT_BASES, "") ? "nmp-python-base" : "nmp-python-base-builder" +} + +function "python_dev_base_target" { + params = [] + result = notequal(PYTHON_DEV_BASE_TARGET, "") ? PYTHON_DEV_BASE_TARGET : notequal(USE_PREBUILT_BASES, "") ? "nmp-python-dev-base" : "nmp-python-dev-base-builder" +} + +function "customizer_tasks_base_context" { + params = [] + result = notequal(CUSTOMIZER_TASKS_BASE_CONTEXT, "") ? CUSTOMIZER_TASKS_BASE_CONTEXT : notequal(USE_PREBUILT_BASES, "") ? "docker-image://${BASE_REGISTRY}/nmp-customizer-tasks-base:${BASE_TAG_CUSTOMIZER_TASKS}" : "target:nmp-customizer-tasks-base-builder" +} + +function "automodel_base_context" { + params = [] + result = notequal(AUTOMODEL_BASE_CONTEXT, "") ? AUTOMODEL_BASE_CONTEXT : notequal(USE_PREBUILT_BASES, "") ? "docker-image://${BASE_REGISTRY}/nmp-automodel-base:${BASE_TAG_AUTOMODEL}" : "target:nmp-automodel-base-builder" +} + +function "faiss_wheel_context" { + params = [] + result = notequal(FAISS_WHEEL_CONTEXT, "") ? FAISS_WHEEL_CONTEXT : notequal(USE_LOCAL_WHEELS, "") ? "target:faiss-gpu-wheel" : "docker-image://${get_faiss_wheel_image()}" +} + +function "causal_conv1d_wheel_context" { + params = [] + result = notequal(CAUSAL_CONV1D_WHEEL_CONTEXT, "") ? CAUSAL_CONV1D_WHEEL_CONTEXT : notequal(USE_LOCAL_WHEELS, "") ? "target:causal-conv1d-wheel" : "docker-image://${get_causal_conv1d_wheel_image()}" +} + +function "mamba_ssm_wheel_context" { + params = [] + result = notequal(MAMBA_SSM_WHEEL_CONTEXT, "") ? MAMBA_SSM_WHEEL_CONTEXT : notequal(USE_LOCAL_WHEELS, "") ? "target:mamba-ssm-wheel" : "docker-image://${get_mamba_ssm_wheel_image()}" +} + function "wheel_tags" { params = [name] result = [ @@ -313,7 +384,7 @@ group "docker-customizer" { # Base images for consolidated containers target "nmp-python-base" { - target = "nmp-python-base" + target = python_base_target() context = "." dockerfile = "docker/base/Dockerfile.nmp-python-base" args = { @@ -340,7 +411,7 @@ target "nmp-python-base-builder" { } target "nmp-python-dev-base" { - target = "nmp-python-dev-base" + target = python_dev_base_target() context = "." dockerfile = "docker/base/Dockerfile.nmp-python-base" args = { @@ -511,30 +582,26 @@ target "nmp-customizer-tasks-base-builder" { cache-from = maybe_registry_cache_from("nmp-customizer-tasks-base") tags = base_tags("nmp-customizer-tasks-base") output = image_output() - args = { - BASE_REGISTRY = "${BASE_REGISTRY}" - BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" - FAISS_WHEEL_IMAGE = get_faiss_wheel_image() - CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() - MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() - } contexts = { - nmp-gpu-base = "target:nmp-gpu-base" - nmp-workspace = "target:nmp-workspace" + causal-conv1d-wheel-image = causal_conv1d_wheel_context() + faiss-wheel-image = faiss_wheel_context() + mamba-ssm-wheel-image = mamba_ssm_wheel_context() + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" } platforms = ["linux/amd64"] } # The base image for Customizer Tasks target "nmp-customizer-tasks-base" { - target = "nmp-customizer-tasks-base" + target = "nmp-customizer-tasks-base-builder" context = "." - args = { - BASE_REGISTRY = "${BASE_REGISTRY}" - BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" - FAISS_WHEEL_IMAGE = get_faiss_wheel_image() - CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() - MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + contexts = { + causal-conv1d-wheel-image = causal_conv1d_wheel_context() + faiss-wheel-image = faiss_wheel_context() + mamba-ssm-wheel-image = mamba_ssm_wheel_context() + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" } dockerfile = "docker/Dockerfile.nmp-customizer-tasks" platforms = get_platforms() @@ -808,15 +875,12 @@ target "customizer-tasks-smoke-test" { context = "." dockerfile = "docker/Dockerfile.nmp-customizer-tasks" contexts = { - nmp-gpu-base = "target:nmp-gpu-base" - nmp-workspace = "target:nmp-workspace" - } - args = { - BASE_REGISTRY = "${BASE_REGISTRY}" - BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" - FAISS_WHEEL_IMAGE = get_faiss_wheel_image() - CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() - MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + nmp-customizer-tasks-base = customizer_tasks_base_context() + causal-conv1d-wheel-image = causal_conv1d_wheel_context() + faiss-wheel-image = faiss_wheel_context() + mamba-ssm-wheel-image = mamba_ssm_wheel_context() + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" } cache-from = maybe_registry_cache_from("customizer-tasks") output = ["type=cacheonly"] @@ -828,20 +892,17 @@ target "customizer-tasks-docker" { context = "." dockerfile = "docker/Dockerfile.nmp-customizer-tasks" contexts = { - nmp-gpu-base = "target:nmp-gpu-base" - nmp-workspace = "target:nmp-workspace" + nmp-customizer-tasks-base = customizer_tasks_base_context() + causal-conv1d-wheel-image = causal_conv1d_wheel_context() + faiss-wheel-image = faiss_wheel_context() + mamba-ssm-wheel-image = mamba_ssm_wheel_context() + nmp-gpu-base = "target:nmp-gpu-base" + nmp-workspace = "target:nmp-workspace" } cache-to = maybe_registry_cache_to("customizer-tasks") cache-from = maybe_registry_cache_from("customizer-tasks") tags = sha_and_maybe_latest_tags("customizer-tasks") output = image_output() - args = { - BASE_REGISTRY = "${BASE_REGISTRY}" - BASE_TAG_CUSTOMIZER_TASKS = "${BASE_TAG_CUSTOMIZER_TASKS}" - FAISS_WHEEL_IMAGE = get_faiss_wheel_image() - CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() - MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() - } platforms = ["linux/amd64"] } @@ -896,9 +957,9 @@ target "nmp-automodel-base-builder" { cache-from = maybe_registry_cache_from("nmp-automodel-base") tags = base_tags("nmp-automodel-base") output = image_output() - args = { - CAUSAL_CONV1D_WHEEL_IMAGE = get_causal_conv1d_wheel_image() - MAMBA_SSM_WHEEL_IMAGE = get_mamba_ssm_wheel_image() + contexts = { + causal-conv1d-wheel-image = causal_conv1d_wheel_context() + mamba-ssm-wheel-image = mamba_ssm_wheel_context() } platforms = get_platforms() } @@ -909,16 +970,12 @@ target "nmp-automodel-tasks-docker" { dockerfile = "docker/Dockerfile.nmp-automodel-tasks" contexts = { platform-workspace = "target:automodel-platform-workspace" - nmp-automodel-base = "target:nmp-automodel-base-builder" + nmp-automodel-base = automodel_base_context() } cache-to = maybe_registry_cache_to("nmp-automodel-tasks") cache-from = maybe_registry_cache_from("nmp-automodel-tasks") tags = sha_and_maybe_latest_tags("nmp-automodel-tasks") output = image_output() - args = { - BASE_REGISTRY = BASE_REGISTRY - BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL - } platforms = get_platforms() } @@ -928,16 +985,12 @@ target "nmp-automodel-training-docker" { dockerfile = "docker/Dockerfile.nmp-automodel-training" contexts = { platform-workspace = "target:automodel-platform-workspace" - nmp-automodel-base = "target:nmp-automodel-base-builder" + nmp-automodel-base = automodel_base_context() } cache-to = maybe_registry_cache_to("nmp-automodel-training") cache-from = maybe_registry_cache_from("nmp-automodel-training") tags = sha_and_maybe_latest_tags("nmp-automodel-training") output = image_output() - args = { - BASE_REGISTRY = BASE_REGISTRY - BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL - } platforms = get_platforms() } @@ -947,11 +1000,9 @@ target "nmp-automodel-tasks-smoke-test" { dockerfile = "docker/Dockerfile.nmp-automodel-tasks" contexts = { platform-workspace = "target:automodel-platform-workspace" - nmp-automodel-base = "target:nmp-automodel-base-builder" + nmp-automodel-base = automodel_base_context() } args = { - BASE_REGISTRY = BASE_REGISTRY - BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL SMOKE_MARKER = "smoke_nmp_automodel_tasks" } cache-from = maybe_registry_cache_from("nmp-automodel-tasks") @@ -965,11 +1016,9 @@ target "nmp-automodel-training-smoke-test" { dockerfile = "docker/Dockerfile.nmp-automodel-training" contexts = { platform-workspace = "target:automodel-platform-workspace" - nmp-automodel-base = "target:nmp-automodel-base-builder" + nmp-automodel-base = automodel_base_context() } args = { - BASE_REGISTRY = BASE_REGISTRY - BASE_TAG_AUTOMODEL = BASE_TAG_AUTOMODEL SMOKE_MARKER = "smoke_nmp_automodel_training" } cache-from = maybe_registry_cache_from("nmp-automodel-training") diff --git a/docker/Dockerfile.nmp-automodel-base b/docker/Dockerfile.nmp-automodel-base index 974e29d684..636a4b49a8 100644 --- a/docker/Dockerfile.nmp-automodel-base +++ b/docker/Dockerfile.nmp-automodel-base @@ -4,12 +4,10 @@ # PyTorch NGC base + Automodel + CUDA extension wheels for nmp-automodel images. # Publish target: nmp-automodel-base (slim image; builder is build-only). -ARG CAUSAL_CONV1D_WHEEL_IMAGE=local -ARG MAMBA_SSM_WHEEL_IMAGE=local ARG AUTOMODEL_COMMIT=0e9909f56ba48ef9761fc6f49323ba9d0a0835b2 -FROM ${CAUSAL_CONV1D_WHEEL_IMAGE} AS causal-conv1d-wheel-src -FROM ${MAMBA_SSM_WHEEL_IMAGE} AS mamba-ssm-wheel-src +FROM causal-conv1d-wheel-image AS causal-conv1d-wheel-src +FROM mamba-ssm-wheel-image AS mamba-ssm-wheel-src FROM alpine/git AS automodel-clone ARG AUTOMODEL_COMMIT diff --git a/docker/Dockerfile.nmp-automodel-tasks b/docker/Dockerfile.nmp-automodel-tasks index 416547451f..4e70b8a99c 100644 --- a/docker/Dockerfile.nmp-automodel-tasks +++ b/docker/Dockerfile.nmp-automodel-tasks @@ -2,13 +2,13 @@ # nmp-automodel tasks - file_io, model_entity, and other platform task steps. # Built on nmp-automodel-base (GPU-capable; runs on CPU or GPU nodes). -ARG BASE_TAG_AUTOMODEL=local -ARG BASE_REGISTRY=my-registry/nemo-platform-dev ARG SMOKE_MARKER=smoke_nmp_automodel_tasks -FROM ${BASE_REGISTRY}/nmp-automodel-base:${BASE_TAG_AUTOMODEL} AS nmp-automodel-base +# Supplied by bake. Local builds use the base builder target; CI can override +# the context to a pinned docker-image:// reference. +FROM nmp-automodel-base AS automodel-base -FROM nmp-automodel-base AS runtime +FROM automodel-base AS runtime # Pin uv for platform workspace installs (base may lack /bin/uv or PATH may prefer 0.10.x). COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv diff --git a/docker/Dockerfile.nmp-automodel-training b/docker/Dockerfile.nmp-automodel-training index 1a646630c1..10dc2cf079 100644 --- a/docker/Dockerfile.nmp-automodel-training +++ b/docker/Dockerfile.nmp-automodel-training @@ -2,13 +2,13 @@ # nmp-automodel training - GPU finetune step (nemo_automodel recipes + nmp-automodel package). # Same platform glue as tasks; separate image tag for the compiler training step. -ARG BASE_TAG_AUTOMODEL=local -ARG BASE_REGISTRY=my-registry/nemo-platform-dev ARG SMOKE_MARKER=smoke_nmp_automodel_training -FROM ${BASE_REGISTRY}/nmp-automodel-base:${BASE_TAG_AUTOMODEL} AS nmp-automodel-base +# Supplied by bake. Local builds use the base builder target; CI can override +# the context to a pinned docker-image:// reference. +FROM nmp-automodel-base AS automodel-base -FROM nmp-automodel-base AS runtime +FROM automodel-base AS runtime COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv ENV PATH="/bin:${PATH}" diff --git a/docker/Dockerfile.nmp-customizer-tasks b/docker/Dockerfile.nmp-customizer-tasks index 10a6f034ee..95ca16cc7f 100644 --- a/docker/Dockerfile.nmp-customizer-tasks +++ b/docker/Dockerfile.nmp-customizer-tasks @@ -6,27 +6,19 @@ # - mamba-ssm 2.3.0 (compatible with nemotron v3) # - causal-conv1d -# ============================================================================= -# Global ARGs (must be declared before first FROM to be usable in FROM statements) -# ============================================================================= -ARG BASE_TAG_CUSTOMIZER_TASKS=local -ARG FAISS_WHEEL_IMAGE=local -ARG CAUSAL_CONV1D_WHEEL_IMAGE=local -ARG MAMBA_SSM_WHEEL_IMAGE=local -ARG BASE_REGISTRY=my-registry - # ============================================================================= # Base Image # ============================================================================= -# Pulls a pre-built image built by the nmp-customizer-tasks-base-builder stage. -FROM ${BASE_REGISTRY}/nmp-customizer-tasks-base:$BASE_TAG_CUSTOMIZER_TASKS AS nmp-customizer-tasks-base +# Supplied by bake. Local builds use the base builder target; CI can override +# the context to a pinned docker-image:// reference. +FROM nmp-customizer-tasks-base AS customizer-tasks-base # ============================================================================= # FAISS wheel source (ARM64 only) # ============================================================================= -# faiss-gpu-cu12 has no ARM64 wheel on PyPI. On linux/arm64 builds, pass -# the faiss-gpu-wheel image here so the locally-built wheel can be installed. -FROM ${FAISS_WHEEL_IMAGE} AS faiss-wheel-src +# faiss-gpu-cu12 has no ARM64 wheel on PyPI. The bake file supplies this +# context from a wheel image or from the local faiss-gpu-wheel target. +FROM faiss-wheel-image AS faiss-wheel-src # ============================================================================= # Wheel sources (causal-conv1d + mamba-ssm) @@ -35,8 +27,8 @@ FROM ${FAISS_WHEEL_IMAGE} AS faiss-wheel-src # Both packages only ship source distributions on PyPI and require nvcc to # compile. Building them inline is slow; instead consume pre-built wheels # published by the gpu-wheels CI pipeline. -FROM ${CAUSAL_CONV1D_WHEEL_IMAGE} AS causal-conv1d-wheel-src -FROM ${MAMBA_SSM_WHEEL_IMAGE} AS mamba-ssm-wheel-src +FROM causal-conv1d-wheel-image AS causal-conv1d-wheel-src +FROM mamba-ssm-wheel-image AS mamba-ssm-wheel-src # ============================================================================= # Builder: Install all dependencies @@ -50,8 +42,7 @@ RUN --mount=type=cache,target=/root/.cache/uv \ # Install locally-built FAISS wheel on ARM64. # faiss-gpu-cu12 is excluded by platform marker on aarch64; the equivalent -# FAISS GPU wheel is provided via the faiss-gpu-wheel-builder image. -# Pass FAISS_WHEEL_IMAGE=/faiss-gpu-wheel: when building for arm64. +# FAISS GPU wheel is provided by the faiss-wheel-src context. RUN --mount=type=cache,target=/root/.cache/uv \ --mount=from=faiss-wheel-src,target=/tmp/faiss-wheel-src \ if ls /tmp/faiss-wheel-src/wheels/*.whl >/dev/null 2>&1; then \ @@ -78,27 +69,27 @@ FROM nmp-gpu-base AS runtime WORKDIR /app # Layer 1: NVIDIA CUDA libraries (~4.3GB) -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/nvidia \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/nvidia \ /app/.venv/lib/python3.11/site-packages/nvidia # Layer 2: PyTorch (~2GB) -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torch \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torch \ /app/.venv/lib/python3.11/site-packages/torch -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torchgen \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torchgen \ /app/.venv/lib/python3.11/site-packages/torchgen -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/functorch \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/functorch \ /app/.venv/lib/python3.11/site-packages/functorch -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torch-*.dist-info \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/torch-*.dist-info \ /app/.venv/lib/python3.11/site-packages/ # Layer 3: Inference engines (~3.4GB) -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/flashinfer_cubin \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/flashinfer_cubin \ /app/.venv/lib/python3.11/site-packages/flashinfer_cubin -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/triton \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/triton \ /app/.venv/lib/python3.11/site-packages/triton #TODO needs to be built for arm64 -COPY --from=nmp-customizer-tasks-base /app/.venv/lib/python3.11/site-packages/xformers \ +COPY --from=customizer-tasks-base /app/.venv/lib/python3.11/site-packages/xformers \ /app/.venv/lib/python3.11/site-packages/xformers # Layer 4: CUDA Extensions (causal-conv1d, mamba-ssm) diff --git a/docker/base/Dockerfile.nmp-python-base b/docker/base/Dockerfile.nmp-python-base index dd4efa3db9..c27e422a1a 100644 --- a/docker/base/Dockerfile.nmp-python-base +++ b/docker/base/Dockerfile.nmp-python-base @@ -8,15 +8,14 @@ # nmp-python-dev-base - runtime base + dev tools for building native extensions (auditor) ####### -# Pull stages: consume pre-built images from the registry. +# Registry stages: set USE_PREBUILT_BASES or PYTHON_*_BASE_TARGET in bake to +# consume pre-built images instead of the local builder stages. ARG BASE_TAG_PYTHON=local ARG BASE_REGISTRY=my-registry FROM ${BASE_REGISTRY}/nmp-python-base:${BASE_TAG_PYTHON} AS nmp-python-base FROM ${BASE_REGISTRY}/nmp-python-dev-base:${BASE_TAG_PYTHON} AS nmp-python-dev-base # nmp-python-base-builder: Builds the minimal runtime base from scratch. -# Run the nmp-python-base-builder bake target to publish a new pinned version, -# then update BASE_TAG_PYTHON in docker-bake.hcl. FROM python:3.11.14-slim-bookworm AS nmp-python-base-builder RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ diff --git a/docker/base/Dockerfile.nmp-studio-ui b/docker/base/Dockerfile.nmp-studio-ui index a19db340a3..5883c94b30 100644 --- a/docker/base/Dockerfile.nmp-studio-ui +++ b/docker/base/Dockerfile.nmp-studio-ui @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 ARG NODE_VERSION=22 -ARG DOCKERHUB_MIRROR=dockerhub.nvidia.com +ARG DOCKERHUB_MIRROR=docker.io/library # --------------- BASE --------------- # FROM ${DOCKERHUB_MIRROR}/node:${NODE_VERSION}-bookworm AS base From 43c767765bc49b1ba4a8e1ed550e1613ece805ef Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 16:18:14 -0700 Subject: [PATCH 07/11] fix: please the linter Signed-off-by: Brooke Storm --- .../src/nemo_platform_ext/quickstart/container.py | 3 ++- .../src/nemo_platform_ext/quickstart/preflight.py | 3 ++- .../nemo_platform_plugin/src/nemo_platform_plugin/config.py | 3 ++- packages/nmp_testing/src/nmp/testing/docker.py | 3 ++- .../nemo-platform/src/nemo_platform/quickstart/container.py | 3 ++- .../nemo-platform/src/nemo_platform/quickstart/preflight.py | 3 ++- services/core/inference-gateway/tests/integration/conftest.py | 3 ++- .../core/jobs/src/nmp/core/jobs/controllers/backends/docker.py | 3 ++- .../src/nmp/core/models/controllers/backends/docker/backend.py | 3 ++- .../models/controllers/backends/docker/creation_reconciler.py | 3 ++- services/core/models/tests/integration/conftest.py | 3 ++- services/intake/tests/integration/spans/conftest.py | 3 ++- 12 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.py b/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.py index 7c28627c32..7d31ace627 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/container.py @@ -20,11 +20,12 @@ logger = logging.getLogger(__name__) if typing.TYPE_CHECKING: - from docker import DockerClient from docker.models.containers import Container from docker.models.networks import Network from docker.types import Mount + from docker import DockerClient + class PullProgress(TypedDict): """Progress update from image pull operation.""" diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/preflight.py b/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/preflight.py index 9bb2307fef..f7f9290657 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/preflight.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/quickstart/preflight.py @@ -252,9 +252,10 @@ def add_pull_required_result(*, details: str | None = None) -> None: ) try: - import docker from docker.errors import DockerException, ImageNotFound + import docker + from .container import ContainerManager client = docker.from_env() diff --git a/packages/nemo_platform_plugin/src/nemo_platform_plugin/config.py b/packages/nemo_platform_plugin/src/nemo_platform_plugin/config.py index 0184bdc9a0..a624272580 100644 --- a/packages/nemo_platform_plugin/src/nemo_platform_plugin/config.py +++ b/packages/nemo_platform_plugin/src/nemo_platform_plugin/config.py @@ -77,7 +77,6 @@ def test_something(): from pathlib import Path from typing import Any, ClassVar, Literal, Self, Type, TypeVar -import docker import yaml from docker.errors import DockerException from pydantic import BaseModel, Field, field_validator, model_validator @@ -85,6 +84,8 @@ def test_something(): from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict from requests.exceptions import Timeout as RequestsTimeout +import docker + logger = logging.getLogger(__name__) NMP_PREFIX_BASE = "NMP_" diff --git a/packages/nmp_testing/src/nmp/testing/docker.py b/packages/nmp_testing/src/nmp/testing/docker.py index 9e203bfa37..65803a00f6 100644 --- a/packages/nmp_testing/src/nmp/testing/docker.py +++ b/packages/nmp_testing/src/nmp/testing/docker.py @@ -38,11 +38,12 @@ def mock_nim_image(docker_client): import tarfile from dataclasses import dataclass, field -import docker import pytest from docker.errors import APIError, BuildError, DockerException, ImageNotFound, NotFound from tenacity import retry, stop_after_attempt, wait_fixed +import docker + # ============================================================================= # Retry Configuration # ============================================================================= diff --git a/sdk/python/nemo-platform/src/nemo_platform/quickstart/container.py b/sdk/python/nemo-platform/src/nemo_platform/quickstart/container.py index 7c28627c32..7d31ace627 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/quickstart/container.py +++ b/sdk/python/nemo-platform/src/nemo_platform/quickstart/container.py @@ -20,11 +20,12 @@ logger = logging.getLogger(__name__) if typing.TYPE_CHECKING: - from docker import DockerClient from docker.models.containers import Container from docker.models.networks import Network from docker.types import Mount + from docker import DockerClient + class PullProgress(TypedDict): """Progress update from image pull operation.""" diff --git a/sdk/python/nemo-platform/src/nemo_platform/quickstart/preflight.py b/sdk/python/nemo-platform/src/nemo_platform/quickstart/preflight.py index 9bb2307fef..f7f9290657 100644 --- a/sdk/python/nemo-platform/src/nemo_platform/quickstart/preflight.py +++ b/sdk/python/nemo-platform/src/nemo_platform/quickstart/preflight.py @@ -252,9 +252,10 @@ def add_pull_required_result(*, details: str | None = None) -> None: ) try: - import docker from docker.errors import DockerException, ImageNotFound + import docker + from .container import ContainerManager client = docker.from_env() diff --git a/services/core/inference-gateway/tests/integration/conftest.py b/services/core/inference-gateway/tests/integration/conftest.py index 0e721e9742..cf79a13bc4 100644 --- a/services/core/inference-gateway/tests/integration/conftest.py +++ b/services/core/inference-gateway/tests/integration/conftest.py @@ -8,7 +8,6 @@ from typing import Any, Generator from unittest.mock import AsyncMock, MagicMock, patch -import docker import pytest from nemo_platform import AsyncNeMoPlatform, NeMoPlatform from nmp.core.inference_gateway.api.dependencies import global_model_cache @@ -32,6 +31,8 @@ get_worker_port_range, ) +import docker + # ============================================================================= # Constants # ============================================================================= diff --git a/services/core/jobs/src/nmp/core/jobs/controllers/backends/docker.py b/services/core/jobs/src/nmp/core/jobs/controllers/backends/docker.py index cd85caa5d1..74ce48e097 100644 --- a/services/core/jobs/src/nmp/core/jobs/controllers/backends/docker.py +++ b/services/core/jobs/src/nmp/core/jobs/controllers/backends/docker.py @@ -13,7 +13,6 @@ from concurrent.futures import ThreadPoolExecutor from typing import Generic, Literal, TypeVar -import docker import docker.types from docker.errors import APIError, ImageNotFound, NotFound from docker.models.containers import Container @@ -83,6 +82,8 @@ from opentelemetry import trace from pydantic import BaseModel, Field +import docker + tracer = trace.get_tracer(__name__) logger = logging.getLogger(__name__) diff --git a/services/core/models/src/nmp/core/models/controllers/backends/docker/backend.py b/services/core/models/src/nmp/core/models/controllers/backends/docker/backend.py index 8c04e4b031..ce9d824071 100644 --- a/services/core/models/src/nmp/core/models/controllers/backends/docker/backend.py +++ b/services/core/models/src/nmp/core/models/controllers/backends/docker/backend.py @@ -14,7 +14,6 @@ from logging import getLogger from typing import Any, Optional -import docker import httpx from docker.errors import APIError, NotFound from nemo_platform import NotFoundError @@ -42,6 +41,8 @@ from requests.exceptions import ReadTimeout from urllib3.exceptions import ReadTimeoutError as Urllib3ReadTimeoutError +import docker + logger = getLogger(__name__) diff --git a/services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py b/services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py index c8a5f674cd..784338959e 100644 --- a/services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py +++ b/services/core/models/src/nmp/core/models/controllers/backends/docker/creation_reconciler.py @@ -21,7 +21,6 @@ from typing import Any, Dict, Optional from urllib.parse import urljoin -import docker from docker.errors import APIError, ImageNotFound, NotFound from docker.models.containers import Container from docker.models.volumes import Volume @@ -49,6 +48,8 @@ from tenacity import before_sleep_log, retry, stop_after_attempt, wait_exponential from urllib3.exceptions import ReadTimeoutError as Urllib3ReadTimeoutError +import docker + logger = getLogger(__name__) # --------------------------------------------------------------------------- diff --git a/services/core/models/tests/integration/conftest.py b/services/core/models/tests/integration/conftest.py index 8c3cdacb1a..9837d52d1c 100644 --- a/services/core/models/tests/integration/conftest.py +++ b/services/core/models/tests/integration/conftest.py @@ -9,7 +9,6 @@ from typing import Any, Generator, Optional from unittest.mock import AsyncMock, MagicMock, patch -import docker import pytest from nemo_platform import AsyncNeMoPlatform, NeMoPlatform from nemo_platform.types.inference.model_deployment import ModelDeployment @@ -35,6 +34,8 @@ get_worker_port_range, ) +import docker + blockbuster = blockbuster_fixture(autouse=True) diff --git a/services/intake/tests/integration/spans/conftest.py b/services/intake/tests/integration/spans/conftest.py index 602605d514..3cd706d724 100644 --- a/services/intake/tests/integration/spans/conftest.py +++ b/services/intake/tests/integration/spans/conftest.py @@ -32,9 +32,10 @@ def _docker_available() -> bool: if find_spec("docker") is None: return False - import docker from docker.errors import DockerException + import docker + try: client = docker.from_env() try: From cadc81980af492696a0245bd0f6dd21a51008dba Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 16:37:39 -0700 Subject: [PATCH 08/11] fix: lots of coderabbit good ideas Signed-off-by: Brooke Storm --- docker/base/Dockerfile.nmp-gpu-base | 2 +- docker/base/Dockerfile.nmp-gpu-base-py312 | 5 +++++ docker/base/Dockerfile.nmp-jobs-launcher | 2 +- docker/base/Dockerfile.nmp-python-base | 17 ++++++++++++++++- docker/base/Dockerfile.nmp-workspace | 2 -- docker/base/Dockerfile.policy-wasm | 16 +++++++++++----- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docker/base/Dockerfile.nmp-gpu-base b/docker/base/Dockerfile.nmp-gpu-base index 4b79e02128..36450a5b6a 100644 --- a/docker/base/Dockerfile.nmp-gpu-base +++ b/docker/base/Dockerfile.nmp-gpu-base @@ -27,7 +27,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # - CVE-2025-8194 (High, out-of-SLA): Fixed by bumping the Python base image to # python:3.11.14 in docker/base/Dockerfile.nmp-base # --only-upgrade: upgrades installed packages only; skips absent ones silently. -RUN apt-get update && apt-get install -y --only-upgrade \ +RUN apt-get update && apt-get install -y --only-upgrade --no-install-recommends \ gnupg gnupg2 gpg gpg-agent gpgconf gpgsm \ dirmngr gnupg-utils gpgv \ && apt-get clean && rm -rf /var/lib/apt/lists/* diff --git a/docker/base/Dockerfile.nmp-gpu-base-py312 b/docker/base/Dockerfile.nmp-gpu-base-py312 index dc9dd17ae3..c6ef88a91f 100644 --- a/docker/base/Dockerfile.nmp-gpu-base-py312 +++ b/docker/base/Dockerfile.nmp-gpu-base-py312 @@ -36,6 +36,11 @@ WORKDIR /app # Copy uv binary from official image COPY --from=ghcr.io/astral-sh/uv:0.9.14 /uv /bin/uv +# CVE hardening for consumers that use the system Python 3.12 environment. +RUN uv pip install --system --break-system-packages --python /usr/bin/python3.12 \ + "setuptools>=78.1.1" \ + "wheel>=0.46.2" + # Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ diff --git a/docker/base/Dockerfile.nmp-jobs-launcher b/docker/base/Dockerfile.nmp-jobs-launcher index 14cfb601ad..e69edf05b3 100644 --- a/docker/base/Dockerfile.nmp-jobs-launcher +++ b/docker/base/Dockerfile.nmp-jobs-launcher @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # require >= 1.25.8 for CVE-2026-25679 and others -FROM golang:1.25 AS build +FROM golang:1.25.8 AS build COPY ./services/core/jobs/jobs-launcher /app/services/jobs-launcher WORKDIR /app/services/jobs-launcher RUN go mod download diff --git a/docker/base/Dockerfile.nmp-python-base b/docker/base/Dockerfile.nmp-python-base index c27e422a1a..fd21b04512 100644 --- a/docker/base/Dockerfile.nmp-python-base +++ b/docker/base/Dockerfile.nmp-python-base @@ -52,6 +52,8 @@ ENV PYTHONDONTWRITEBYTECODE=1 \ # nmp-python-dev-base-builder: Extends the runtime base with development tools. # Used as the builder base for services that compile native extensions (e.g., auditor). FROM nmp-python-base-builder AS nmp-python-dev-base-builder +ARG TARGETARCH +ARG RUSTUP_VERSION=1.28.2 RUN apt-get update && apt-get install -y --no-install-recommends \ binutils \ @@ -71,5 +73,18 @@ RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 60 \ ENV CARGO_HOME="/etc/cargo" \ RUSTUP_HOME="/etc/rustup" -RUN curl https://sh.rustup.rs -sSf | sh -s -- -y +RUN set -eux; \ + case "${TARGETARCH:-$(dpkg --print-architecture)}" in \ + amd64) rustup_target="x86_64-unknown-linux-gnu" ;; \ + arm64|aarch64) rustup_target="aarch64-unknown-linux-gnu" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH:-unknown}" >&2; exit 1 ;; \ + esac; \ + rustup_url="https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${rustup_target}/rustup-init"; \ + curl -fsSL -o /tmp/rustup-init "${rustup_url}"; \ + curl -fsSL -o /tmp/rustup-init.sha256 "${rustup_url}.sha256"; \ + rustup_sha256="$(cut -d ' ' -f 1 /tmp/rustup-init.sha256)"; \ + echo "${rustup_sha256} /tmp/rustup-init" | sha256sum -c -; \ + chmod +x /tmp/rustup-init; \ + /tmp/rustup-init -y; \ + rm -f /tmp/rustup-init /tmp/rustup-init.sha256 ENV PATH="$CARGO_HOME/bin:$PATH" diff --git a/docker/base/Dockerfile.nmp-workspace b/docker/base/Dockerfile.nmp-workspace index 7c8a3781cf..15c7748a1d 100644 --- a/docker/base/Dockerfile.nmp-workspace +++ b/docker/base/Dockerfile.nmp-workspace @@ -7,9 +7,7 @@ COPY docs/ docs/ COPY openapi/ openapi/ COPY skills/ skills/ COPY packages/ packages/ -COPY skills/ skills/ COPY plugins/ plugins/ -COPY skills/ skills/ COPY services/ services/ COPY packages/nmp_platform/ entrypoint/ COPY sdk/ sdk/ diff --git a/docker/base/Dockerfile.policy-wasm b/docker/base/Dockerfile.policy-wasm index 6c03815aa3..fb006c0fc4 100644 --- a/docker/base/Dockerfile.policy-wasm +++ b/docker/base/Dockerfile.policy-wasm @@ -3,14 +3,20 @@ FROM alpine:3.19 AS root-policy-wasm-builder RUN apk add --no-cache curl tar ARG OPA_VERSION=v1.8.0 ARG TARGETARCH -RUN case "${TARGETARCH}" in \ +RUN set -eux; \ + case "${TARGETARCH}" in \ amd64|arm64) opa_arch="${TARGETARCH}" ;; \ "") opa_arch="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')" ;; \ *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ - esac && \ - curl -fsSL -o /usr/local/bin/opa \ - "https://github.com/open-policy-agent/opa/releases/download/${OPA_VERSION}/opa_linux_${opa_arch}_static" && \ - chmod +x /usr/local/bin/opa + esac; \ + opa_asset="opa_linux_${opa_arch}_static"; \ + opa_url="https://github.com/open-policy-agent/opa/releases/download/${OPA_VERSION}/${opa_asset}"; \ + curl -fsSL -o "/tmp/${opa_asset}" "${opa_url}"; \ + curl -fsSL -o "/tmp/${opa_asset}.sha256" "${opa_url}.sha256"; \ + (cd /tmp && sha256sum -c "${opa_asset}.sha256"); \ + chmod +x "/tmp/${opa_asset}"; \ + mv "/tmp/${opa_asset}" /usr/local/bin/opa; \ + rm -f "/tmp/${opa_asset}.sha256" WORKDIR /build COPY script/build_policy_wasm.sh /build/script/ COPY services/core/auth/src/nmp/core/auth/app/policies /build/services/core/auth/src/nmp/core/auth/app/policies From 066c999393ca931e9fa3bfe79ee202d19a1e9f6b Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 16:47:59 -0700 Subject: [PATCH 09/11] fix: lots of coderabbit good ideas Signed-off-by: Brooke Storm --- docker/Dockerfile.bake | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.bake b/docker/Dockerfile.bake index 546e290f44..957491fe36 100644 --- a/docker/Dockerfile.bake +++ b/docker/Dockerfile.bake @@ -56,11 +56,19 @@ ARG DISTROLESS_BASE=nvcr.io/nvidia/distroless/python:3.11-v4.0.3 #root-golang-base: FROM golang:1.23 AS root-golang-base - RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz | tar -xzv && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/ + RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz -o golangci-lint.tar.gz \ + && echo "77cb0af99379d9a21d5dc8c38364d060e864a01bd2f3e30b5e8cc550c3a54111 golangci-lint.tar.gz" | sha256sum -c - \ + && tar -xzf golangci-lint.tar.gz \ + && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/ \ + && rm -rf golangci-lint.tar.gz golangci-lint-1.61.0-linux-amd64 #root-golang-base-1-24: FROM golang:1.24 AS root-golang-base-1-24 - RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz | tar -xzv && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/ + RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v1.61.0/golangci-lint-1.61.0-linux-amd64.tar.gz -o golangci-lint.tar.gz \ + && echo "77cb0af99379d9a21d5dc8c38364d060e864a01bd2f3e30b5e8cc550c3a54111 golangci-lint.tar.gz" | sha256sum -c - \ + && tar -xzf golangci-lint.tar.gz \ + && mv golangci-lint-1.61.0-linux-amd64/golangci-lint /usr/local/bin/ \ + && rm -rf golangci-lint.tar.gz golangci-lint-1.61.0-linux-amd64 #root-golang-base-1-25: FROM golang:1.25 AS root-golang-base-1-25 From 696eac413485d983942d7282401da425ec506a13 Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 17:11:33 -0700 Subject: [PATCH 10/11] fix: a coderabbit nit that could be helpful and remove dead code Signed-off-by: Brooke Storm --- docker/Dockerfile.safe-synthesizer-tasks | 1 + docker/customizer-patch/layers.py.diff | 120 ----------------------- docker/customizer-patch/model.py.diff | 25 ----- docker/customizer-patch/patch.sh | 14 --- 4 files changed, 1 insertion(+), 159 deletions(-) delete mode 100644 docker/customizer-patch/layers.py.diff delete mode 100644 docker/customizer-patch/model.py.diff delete mode 100755 docker/customizer-patch/patch.sh diff --git a/docker/Dockerfile.safe-synthesizer-tasks b/docker/Dockerfile.safe-synthesizer-tasks index 135c1f972c..9832c92564 100644 --- a/docker/Dockerfile.safe-synthesizer-tasks +++ b/docker/Dockerfile.safe-synthesizer-tasks @@ -30,6 +30,7 @@ ARG SAFE_SYNTHESIZER_RUNTIME_PACKAGE ARG FLASHINFER_CU129_INDEX_URL ARG PYTORCH_CU129_INDEX_URL ARG VLLM_CU129_WHEEL +ARG CONTAINER_VARIANT=cu129 ARG USERNAME=nemo ARG USER_UID=1000 ARG USER_GID=1000 diff --git a/docker/customizer-patch/layers.py.diff b/docker/customizer-patch/layers.py.diff deleted file mode 100644 index 2b5334be29..0000000000 --- a/docker/customizer-patch/layers.py.diff +++ /dev/null @@ -1,120 +0,0 @@ ---- nemo_automodel/components/moe/layers.py 2026-02-24 13:38:17 -+++ nemo_automodel/components/moe/layers_new.py 2026-02-24 13:37:44 -@@ -17,8 +17,11 @@ - from typing import Literal, Optional - - import torch -+import torch.distributed as dist -+import torch.distributed.nn.functional as dist_nn_f - import torch.nn as nn - import torch.nn.functional as F -+from torch.autograd import Function - - from nemo_automodel.components.distributed.init_utils import get_world_size_safe - from nemo_automodel.components.models.common import BackendConfig, initialize_linear_module -@@ -88,8 +91,46 @@ - with shape [n_experts, dim, inter_dim] - 50% memory savings. - """ - return activation in ("swiglu", "quick_geglu") -+ -+ -+# ── EP variable-length collective helpers ── -+class _AllGatherConcatVarlenFn(Function): -+ """All-gather with variable local lengths and autograd-safe backward. -+ -+ Backward uses all-reduce + local narrow instead of reduce-scatter to avoid -+ monitoredBarrier deadlocks observed with mixed FSDP/EP backward collective ordering. -+ """ -+ -+ @staticmethod -+ def forward(ctx, local_tensor: torch.Tensor, group: dist.ProcessGroup, gathered_lens: list[int], max_len: int): -+ local_len = local_tensor.size(0) -+ if local_len < max_len: -+ pad_shape = (max_len - local_len,) + tuple(local_tensor.shape[1:]) -+ pad = torch.zeros(pad_shape, dtype=local_tensor.dtype, device=local_tensor.device) -+ local_padded = torch.cat([local_tensor, pad], dim=0) -+ else: -+ local_padded = local_tensor - -+ world_size = len(gathered_lens) -+ gathered = [torch.empty_like(local_padded) for _ in range(world_size)] -+ dist.all_gather(gathered, local_padded, group=group) -+ gathered = [g[:n] for g, n in zip(gathered, gathered_lens)] - -+ ctx.group = group -+ ctx.gathered_lens = gathered_lens -+ ctx.rank = dist.get_rank(group) -+ return torch.cat(gathered, dim=0) -+ -+ @staticmethod -+ def backward(ctx, grad_output: torch.Tensor): -+ grad_full = grad_output.contiguous() -+ start = sum(ctx.gathered_lens[: ctx.rank]) -+ local_len = ctx.gathered_lens[ctx.rank] -+ dist.all_reduce(grad_full, op=dist.ReduceOp.SUM, group=ctx.group) -+ grad_local = grad_full.narrow(0, start, local_len).contiguous() -+ return grad_local, None, None, None -+ -+ - class MLP(nn.Module): - """ - Multi-Layer Perceptron (MLP) used as a feed-forward layer. -@@ -326,11 +367,36 @@ - - # Replicate the tensor to all experts. This is sub-optimal but is - # used by this implementation for correctness. -+ # EP variable-length all-gather - if ep_size > 1: -- x = DTensor.from_local(x, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() -- weights = DTensor.from_local(weights.float(), device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() -- indices = DTensor.from_local(indices, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() -- token_mask = DTensor.from_local(token_mask, device_mesh=ep_mesh, placements=[Shard(0)]).full_tensor() -+ ep_group = ep_mesh.get_group() -+ local_num_tokens = x.size(0) -+ -+ # Exchange per-rank token counts -+ local_len_t = torch.tensor([local_num_tokens], device=x.device, dtype=torch.int64) -+ gathered_len_t = [torch.zeros_like(local_len_t) for _ in range(ep_size)] -+ dist.all_gather(gathered_len_t, local_len_t, group=ep_group) -+ gathered_lens = [int(t.item()) for t in gathered_len_t] -+ max_len = max(gathered_lens) -+ -+ def _all_gather_dim0_var(local_tensor: torch.Tensor, *, differentiable: bool) -> torch.Tensor: -+ if differentiable: -+ return _AllGatherConcatVarlenFn.apply(local_tensor, ep_group, gathered_lens, max_len) -+ if max_len > local_tensor.size(0): -+ pad_shape = (max_len - local_tensor.size(0),) + tuple(local_tensor.shape[1:]) -+ pad = torch.zeros(pad_shape, dtype=local_tensor.dtype, device=local_tensor.device) -+ local_padded = torch.cat([local_tensor, pad], dim=0) -+ else: -+ local_padded = local_tensor -+ gathered = [torch.empty_like(local_padded) for _ in range(ep_size)] -+ dist.all_gather(gathered, local_padded, group=ep_group) -+ gathered = [g[:n] for g, n in zip(gathered, gathered_lens)] -+ return torch.cat(gathered, dim=0) -+ -+ x = _all_gather_dim0_var(x, differentiable=True) -+ weights = _all_gather_dim0_var(weights.float(), differentiable=False) -+ indices = _all_gather_dim0_var(indices, differentiable=False) -+ token_mask = _all_gather_dim0_var(token_mask, differentiable=False) - - n_local_experts = self.n_routed_experts // ep_size - experts_start_idx = ep_rank * n_local_experts -@@ -392,9 +458,15 @@ - ) - y[0] += expert_out[0] - -+ # Gradient anchor - if ep_size > 1: -- y = DTensor.from_local(y, device_mesh=ep_mesh, placements=[Partial()]) -- y = y.redistribute(placements=[Shard(0)]).to_local() -+ y = y + (x * 0.0) -+ -+ # Variable-length reduce: all_reduce + narrow to original per-rank token boundaries -+ if ep_size > 1: -+ y = dist_nn_f.all_reduce(y, op=dist.ReduceOp.SUM, group=ep_group) -+ start = sum(gathered_lens[:ep_rank]) -+ y = y.narrow(0, start, local_num_tokens).contiguous() - - return y - diff --git a/docker/customizer-patch/model.py.diff b/docker/customizer-patch/model.py.diff deleted file mode 100644 index a6b254918e..0000000000 --- a/docker/customizer-patch/model.py.diff +++ /dev/null @@ -1,25 +0,0 @@ ---- a/nemo_automodel/components/models/nemotron_v3/model.py -+++ b/nemo_automodel/components/models/nemotron_v3/model.py -@@ -18,7 +18,12 @@ - import torch.nn as nn - from transformers import AutoConfig - --from nemo_automodel.components.models.common import BackendConfig, initialize_linear_module, initialize_rms_norm_module -+from nemo_automodel.components.models.common import ( -+ BackendConfig, -+ HFCheckpointingMixin, -+ initialize_linear_module, -+ initialize_rms_norm_module, -+) - from nemo_automodel.components.models.nemotron_v3.layers import NemotronV3Block - from nemo_automodel.components.models.nemotron_v3.state_dict_adapter import NemotronV3StateDictAdapter - from nemo_automodel.components.moe.fsdp_mixin import MoEFSDPSyncMixin -@@ -156,7 +161,7 @@ - block.init_weights(buffer_device=buffer_device) - - --class NemotronHForCausalLM(nn.Module, MoEFSDPSyncMixin): -+class NemotronHForCausalLM(HFCheckpointingMixin, nn.Module, MoEFSDPSyncMixin): - """NemotronV3 model with language modeling head.""" - - @classmethod diff --git a/docker/customizer-patch/patch.sh b/docker/customizer-patch/patch.sh deleted file mode 100755 index d9588e2f02..0000000000 --- a/docker/customizer-patch/patch.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -euxo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -INSTALLATION_TARGET=${INSTALLATION_TARGET:-"/opt/Automodel"} - -# Add HFCheckpointingMixin to NemotronHForCausalLM (from commit 07b0700, without transformers v5 bump) -patch ${INSTALLATION_TARGET}/nemo_automodel/components/models/nemotron_v3/model.py ${SCRIPT_DIR}/model.py.diff - -# From https://github.com/NVIDIA-NeMo/Automodel/pull/1365/changes. -# File nemo_automodel/components/moe/experts.py was layers.py in old code. -patch ${INSTALLATION_TARGET}/nemo_automodel/components/moe/layers.py ${SCRIPT_DIR}/layers.py.diff From 3ec8f137bd294e47178caefbaeda336dcd206cd2 Mon Sep 17 00:00:00 2001 From: Brooke Storm Date: Thu, 11 Jun 2026 17:35:38 -0700 Subject: [PATCH 11/11] fix: remove more cruft and pin alpine Signed-off-by: Brooke Storm --- docker-bake.hcl | 8 -------- docker/Dockerfile.bake | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/docker-bake.hcl b/docker-bake.hcl index 38b86f9e84..8ebbc05772 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -252,14 +252,6 @@ function "get_platforms" { # Semantic groups for parallel CI builds -# Safe synthesizer job and api service -group "docker-safe-synthesizer" { - targets = [ - "safe-synthesizer-docker", - "safe-synthesizer-api-docker", - ] -} - # Auditor images group "docker-auditor" { targets = [ diff --git a/docker/Dockerfile.bake b/docker/Dockerfile.bake index 957491fe36..beac0a59bc 100644 --- a/docker/Dockerfile.bake +++ b/docker/Dockerfile.bake @@ -131,7 +131,7 @@ ARG DISTROLESS_BASE=nvcr.io/nvidia/distroless/python:3.11-v4.0.3 RUN uv run --no-sync pytest tests/integration #buildkit-test: - FROM alpine:latest AS buildkit-test + FROM alpine:3.24.0 AS buildkit-test RUN ps exua -T RUN netstat -tnlp RUN df -h