Skip to content

docker: fix the ARM64 image build - #129

Open
amtellezfernandez wants to merge 1 commit into
NVlabs:mainfrom
amtellezfernandez:feat/arm64-docker-sync
Open

docker: fix the ARM64 image build#129
amtellezfernandez wants to merge 1 commit into
NVlabs:mainfrom
amtellezfernandez:feat/arm64-docker-sync

Conversation

@amtellezfernandez

@amtellezfernandez amtellezfernandez commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

The Dockerfile already picks an ARM64 base (FROM nvcr.io/nvidia/pytorch:25.08-py3 AS base-arm64), but the build fails further down, so that path is currently unreachable. This finishes it.

The target is DGX Spark (GB10). With these changes the runtime, controller, physics and trafficsim services build and run natively there, and we've run complete closed-loop rollouts on that hardware using the video-model renderer (runtime.renderer.kind=video_model, OmniDreams via FlashDreams), which suits GB10's unified memory. The one component still x86-only is the prebuilt NuRec/sensorsim image, which that renderer path doesn't use.

What breaks on aarch64

Four separate failures past base-image selection. All four fixes key off TARGETARCH, so the x86_64 path is untouched.

1. ARG TARGETARCH isn't in scope in the final stage. BuildKit's builtin platform args are always available to FROM lines, so FROM base-${TARGETARCH} resolves with no ARG at all. But an ARG after a FROM belongs to that stage, and only stages derived from it inherit the value. The existing declaration sits in the dcgm-exporter stage, which the final stage doesn't derive from, so any arch conditional there compares against the empty string on every architecture. It shows up as a step finishing suspiciously fast, not as an error, which is why static reading misses it. I checked this with minimal repro Dockerfiles covering global, ancestor-stage, non-ancestor-stage and no-ARG placements.

2. No CUDA apt repo on the ARM64 base. datacenter-gpu-manager-4-cuda12 comes from it and nvcr.io/nvidia/pytorch ships only plain Ubuntu ports repos, so apt-get install fails before anything else runs. The repo path is derived from the base image's own /etc/os-release, so it follows the base image across Ubuntu releases instead of pinning one.

3. --extra all --extra recipes can't resolve. alpasim-tools pulls PyQt5 (no aarch64 wheel), alpasim_driver/alpasim_plugins pull x86-only packages like tensordict through the learned-driver models, alpasim_wizard runs on the host, and alpasim-trafficsim pulls torch_geometric. The arm64 branch installs an explicit subset.

Why an explicit list and not a declared extra consumed via uv sync --extra <subset>: measured on GB10, going through the project resolution installs a +cpu torch on aarch64 (2.8.0+cpu, torch.version.cuda == None), while the direct install resolves 2.13.0+cu130 with CUDA available. Until torch sourcing handles aarch64, the sync path quietly produces a CUDA-less runtime image.

4. PyG extensions are x86-pinned. That step's wheel index points at 2.8.0+cu128 and publishes nothing for aarch64. Only alpasim-trafficsim needs those extensions and it's excluded above, so on arm64 they aren't needed at all.

Verification

Full docker build on GB10 hardware against current main:

  • Succeeds end to end, 33.1 GB image, zero errors. The log shows the conditionals expanding as if [ "arm64" = "arm64" ] and if [ "arm64" != "arm64" ], i.e. TARGETARCH resolving instead of empty (the failure mode from item 1).
  • Inside the image, all seven installed packages import on aarch64 (alpasim_controller, alpasim_runtime, alpasim_physics, alpasim_grpc, alpasim_utils, alpasim_plugins, and alpasim_eval via its top-level module eval), torch 2.13.0+cu130, torch.cuda.is_available() true with --gpus all.
  • The excluded packages really are absent: import PyQt5 and import tensordict both raise ModuleNotFoundError.

Item 1 is arguably a bug independent of ARM support, since it makes any arch conditional in the final stage compare against an empty string. Happy to split it out if the platform scope here needs more discussion.

Rebased onto current main after the August sync rewrote the Dockerfile, which is what the earlier conflict was. Single commit, Dockerfile only.

@copy-pr-bot

copy-pr-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@amtellezfernandez amtellezfernandez changed the title docker: fix ARM64 build (apt repo, TARGETARCH scoping, unneeded package installs) docker: fix ARM64 build Jul 26, 2026
@amtellezfernandez amtellezfernandez changed the title docker: fix ARM64 build docker: fix the ARM64 image build Aug 13, 2026
@amtellezfernandez

amtellezfernandez commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Some context on why this is worth taking: the Dockerfile already selects an ARM64 base (base-arm64, the NGC PyTorch image) — this PR completes what that split started. With these four changes the full stack (runtime, controller, physics, trafficsim) builds and runs natively on DGX Spark / GB10, and we have run complete closed-loop rollouts on that hardware end to end with the video-model renderer (runtime.renderer.kind=video_model, OmniDreams served through FlashDreams), which fits comfortably in GB10's unified memory.

One of the four changes is a bug independent of ARM support: ARG TARGETARCH is not in scope inside the final build stage, so any arch conditional there evaluates against the empty string on every architecture. If the platform-enablement scope is more than you want to own right now, I can split that fix into a minimal standalone PR and keep the ARM package policy here.

Rebased onto current main; single commit, Dockerfile only. Verification on GB10 hardware is in the description.

The Dockerfile selects an ARM64 base already, but the build fails past that point on
aarch64. Four changes, all keyed off TARGETARCH so the x86_64 path is untouched:

1. Re-declare `ARG TARGETARCH` inside the final stage. BuildKit's builtin
   platform args are always available to FROM lines, so `FROM base-${TARGETARCH}`
   resolves without any ARG at all -- but an ARG line after a FROM belongs to that
   stage, and is visible to RUN only there and in stages derived from it. The
   existing declaration sits inside the dcgm-exporter stage, which the final stage
   does not derive from, so any arch conditional there compares against the empty
   string on every architecture -- presenting as a step that completes suspiciously
   fast rather than as an error. Verified with minimal-repro Dockerfiles covering
   global, ancestor-stage, non-ancestor-stage and no-ARG placements.

2. Configure NVIDIA's CUDA apt repo on arm64: datacenter-gpu-manager-4-cuda12 comes from
   it and the ARM64 base image does not have it. The repo path is derived from the base
   image's own /etc/os-release rather than hardcoded, so it follows the base image.
3. Install an explicit package subset on arm64 instead of `--extra all --extra recipes`
   (see the docker_local proposal for why each is excluded).
4. Skip the PyG compiled extensions on arm64: that step's wheel index is pinned to an x86
   CUDA build (2.8.0+cu128) and publishes nothing for aarch64. Only alpasim-trafficsim
   needs them, and it is excluded above, so they are unneeded rather than unavailable.

Verified by a real `docker build` on NVIDIA GB10 (Grace Blackwell) against main @ 1e801ca:
33.1GB image, zero errors, all seven installed packages import on aarch64, torch
2.13.0+cu130, and torch.cuda.is_available() true with --gpus all.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant