Linux Accelerator Images#
Optional NVIDIA GPU image chain#
Optional NVIDIA GPU image chain, built as a variant chain since 2026-09-22:
CROSS_VARIANT=nvidia bash linux/scripts/build-cross-chain.sh --target-arches amd64
(ENABLE_NVIDIA=true alone implies the variant). The chain inserts a gpu
stage (Dockerfile.nvidia) between the SHARED sdk and media, and every tag it
writes from there on carries -nvidia — :cross-toolchain-nvidia-<arch>,
:cross-media-nvidia-<arch>, :cross-android-nvidia-<arch>,
:latest-nvidia-<arch>, :latest-nvidia — so it can never overwrite the
default chain’s tags. Before 2026-09-22 ENABLE_NVIDIA=true changed only the
build args, and a GPU run pushed its bytes under the default cross-media-<arch>
and :latest. The rules: AGENTS.md § Image and tag naming.
linux/Dockerfile.nvidia: CUDA 13.4, cuDNN 9.27.0.42, TensorRT 11.4.0.106 (off by default), NCCL, cuBLAS/cuSPARSE/cuFFT, NVTX. The chain’sgpustage, after:cross-sdk-<arch>.linux/Dockerfile.media: Builds media stack with NVIDIA codec headers + ORT CUDA/TRT/cuDNN EPs whenENABLE_NVIDIA=true.linux/Dockerfile.android: Android SDK/NDK on top of the NVIDIA media layer.linux/Dockerfile.torch: Torch/Python add-on on top of the Android NVIDIA layer.linux/Dockerfile.torch: Final entrypoint image (:latest-nvidia-<arch>wrapper, indexed as:latest-nvidia).
NVIDIA GPU Build (Linux)#
Tag:
:latest-nvidia(a manifest; today one per-arch wrapper,:latest-nvidia-amd64— arm64 joins when the cross-sbsa lane exists), perAGENTS.md§ Image and tag naming. Published for amd64. The 2026-10-11 index (sha256:810f36b3…) ran on a GPU on 2026-10-12 (§ The GPU run on the published image). The old single-arch:nvidia(a 2026-04-22 build on the Ubuntu 24.04 base) and its stage tags were deleted from the registry on 2026-09-22.
Requirements:
Host driver >= 590.44 (for CUDA 13.4).
nvidia-container-toolkitinstalled and configured on the host.
--runtime=nvidiaor--gpus allpassed todocker run.
The NVIDIA variant chain inserts Dockerfile.nvidia as its gpu stage after the shared :cross-sdk-<arch> and before media. The later stages are the standard Dockerfiles with ENABLE_NVIDIA=true, which the chain derives from the variant.
Files involved:
File |
Purpose |
|---|---|
|
Installs CUDA 13.4, cuDNN 9.27.0.42, TensorRT 11.4.0.106, NCCL, cuBLAS, cuSPARSE, cuFFT, NVTX |
|
Media stack: conditionally builds ORT with CUDA/TRT/cuDNN EPs when |
|
Conditionally builds on top of the NVIDIA media image |
|
Conditionally tags the final entrypoint image |
|
ORT build script with CUDA, TensorRT, cuDNN EPs |
Build (orchestrated, amd64):
CROSS_VARIANT=nvidia bash linux/scripts/build-cross-chain.sh \
--target-arches amd64 --parallel-archs --log-dir ./out/build-logs/nvidia
What the variant changes, all in build-cross-chain.sh / stage-defs.sh:
It starts at
gpuand refuses--from-stage base|compiler|sdk: those are the default chain’s, so run that chain first when they are stale.ENABLE_TENSORRT=falseby default (owner decision 2026-09-22): the runtime payload carries no TensorRT 11. Its onlylibnvinferis DeepStream’s TensorRT 10 (§ TensorRT 10 beside 11). SetENABLE_TENSORRT=trueonly together with a TensorRT 11 copy into the package.amd64 only, and it pushes only from
CROSS_BUILD_PLATFORM=linux/amd64. Every target must BE the build platform’s arch:Dockerfile.nvidiainstalls the build platform’s CUDA and the ORT/OpenCV/TVM CUDA builds compile for it, so an arm64 target would get x86_64 GPU libraries under an arm64 tag. And the shared:cross-sdk-<arch>it builds on is the amd64 lane’s (no build-host infix), so off linux/amd64 a variant may only run--no-push— the Jetson lane below, which stays local.build-cross-stage.shapplies the same refusals.Its own state:
chain-status-nvidia.jsonandout/build-logs/nvidia/. Chains run strictly one at a time: a second chain refuses to start while the pidfile names a live one.The runtime lane budgets 180 GB (
CROSS_RUNTIME_LANE_GB), not 120.The wrappers take
onnxruntime-gpu+pytorch-cu130unless you pinONNX_PACKAGE/PYTORCH_EXTRA, and only that one onnxruntime flavour: the amd64 CPUonnxruntime_dnnlwheel is pruned from a GPU venv. Both flavours’cp3XYttwins stay in/opt/wheels-free-threaded, and an X.Yt venv takes the one its GIL store names (free-threaded wheels). The manifest is:latest-nvidiaover the:latest-nvidia-<arch>the run built. There is no route yet to add an arm64 entry: the Jetson lane’s image is host-infixed and local, and once the cross-sbsa lane exists it builds BOTH arches in one run — after which an amd64-only run is refused by the completeness gate, as for:latest.The standalone runtime helpers refuse a default output tag under a variant:
build-runtime-artifacts.sh/build-runtime-manifest.shread the variant’s android, so a prefix without-nvidia(a plain:latest) is an error, andbuild-runtime-artifacts.shdefaults to:latest-nvidia.
Run with GPU access:
sudo nerdctl run --rm -it --gpus all ghcr.io/kataglyphis/kataglyphis_beschleuniger:latest-nvidia
# or with nvidia runtime explicitly
sudo nerdctl run --rm -it --runtime=nvidia ghcr.io/kataglyphis/kataglyphis_beschleuniger:latest-nvidia
Version overrides (all have sensible defaults): CUDA_VERSION,
CUDNN_VERSION and TENSORRT_VERSION are versions.env pins, forwarded to
every stage as build-args. Change them there, not per command. The apt forms
(the <major>-<minor> package suffix, cuDNN major) are derived inside Dockerfile.nvidia.
Key differences from the standard build:
Feature |
Standard build |
NVIDIA build |
|---|---|---|
CUDA Toolkit |
Not installed |
CUDA 13.4 |
cuDNN |
Not installed |
cuDNN 9 |
TensorRT |
Not installed |
TensorRT 11.4.0.106 with |
NCCL |
Not installed |
Installed |
cuBLAS/cuSPARSE/cuFFT |
Not installed |
Installed |
NVTX |
Not installed |
Installed |
GStreamer nvcodec |
Auto-detected (off in builds) |
Always enabled |
ORT native EP |
CPU only |
CPU + CUDA + cuDNN (+ TensorRT when enabled) |
ORT Python Package |
|
|
PyTorch Extra |
|
|
ORT output dir |
|
Both cpu and |
Image tag |
|
|
The standard build’s release target is the multi-arch manifest :latest
(linux-build-basics.md § Image Hierarchy
owns the tag scheme). :latest-cross is its retired old name; details in
rancher-desktop-linux-containers.md § The image: always :latest.
NVIDIA on arm64 (SBSA): one image for servers and Jetson#
The arm64 GPU chain uses NVIDIA’s SBSA CUDA repository (the Arm-server
build), not JetPack. The same image targets a Grace-class server GPU and a
Jetson Orin, because newer L4T releases run SBSA CUDA against their Tegra
driver. Built natively on a Jetson AGX Orin (L4T R39) on 2026-09-21/22, base to
runtime, and run on its GPU: PyTorch, the ONNX Runtime CUDA EP, OpenCV CUDA and
an nvcc -arch=sm_87 kernel, each checked against a CPU result.
Building it on an arm64 host#
This is the sequence that ran on the Orin, before the chain had a gpu stage;
the names below are the variant names it would write today. It stays by hand
because every stage is --no-push there, and the orchestrator refuses a
--no-push run that resumes mid-chain (the variant chain always starts at
gpu). --no-push keeps every
stage local, so each child reads its parent from an OCI layout exported with
nerdctl save <tag> | tar -x -C <dir>:
export CROSS_BUILD_PLATFORM=linux/arm64 # build on, and only for, this host
A=(--target-arches arm64 --cross-targets arm64 --no-push)
R=ghcr.io/kataglyphis/kataglyphis_beschleuniger
# 1. base -> compiler -> sdk
bash linux/scripts/build-cross-chain.sh --to-stage sdk "${A[@]}"
# 2. the GPU layer on the sdk (export the sdk to an OCI layout first)
nerdctl build --platform linux/arm64 -f linux/Dockerfile.nvidia \
-t "$R:cross-toolchain-nvidia-arm64" \
--build-context "$R:cross-sdk-arm64=oci-layout://$SDK_OCI" \
--build-arg BASE_IMAGE="$R:cross-sdk-arm64" --build-arg UBUNTU_VERSION=26.04 \
--build-arg ENABLE_TENSORRT=false --build-arg CUDA_INSTALL_COMPAT=0 .
# 3. media and android, each FROM its parent's layout
export ENABLE_NVIDIA=true ENABLE_TENSORRT=false TVM_USE_CUDA=1
CROSS_MEDIA_BASE_IMAGE="$R:cross-toolchain-nvidia-arm64" CROSS_MEDIA_BASE_CONTEXT="$NVIDIA_OCI" \
bash linux/scripts/build-cross-chain.sh --only media "${A[@]}"
CROSS_ANDROID_BASE_IMAGE="$R:cross-media-nvidia-arm64" CROSS_ANDROID_BASE_CONTEXT="$MEDIA_OCI" \
bash linux/scripts/build-cross-chain.sh --only android "${A[@]}"
# 4. runtime: call the helper directly, with the android layout as the artifact
# (the directory must be named <root>-arm64)
CROSS_NO_PUSH=1 ARTIFACT_CONTEXT_ROOT="$ANDROID_OCI_ROOT" ARTIFACT_CONTEXT_MODE=oci \
bash linux/scripts/build-runtime-manifest.sh --image "$R:latest-nvidia-hostarm64" \
--target-arches arm64 --artifact-image-prefix "$R:cross-android-nvidia-hostarm64" \
--artifact-build-mode cross --skip-manifest
Step 4 does not go through build-cross-chain.sh --only runtime: a runtime run
whose android stage was not built in the same run PULLS the android image from
the registry, and would package the published generation instead of this one.
Every pin must match the one the lower stages were built with; the wrapper
smoke refuses a toolchain whose clang --version differs from LLVM_RELEASE.
The knobs that exist for this lane:
Knob |
Default |
What it does |
|---|---|---|
|
|
|
|
|
|
|
|
Memory budget per |
|
|
nvcc rejects the image’s GCC 16 by version. Set in |
CUDA_ARCHITECTURES carries 87 (Orin). The list stays ascending for
readability only: since 2026-09-23 nothing depends on its order, because the
trailing-90 → 90a rewrite ORT used to need is gone (AGENTS.md § GPU
architecture coverage). OpenCV ignores
CMAKE_CUDA_ARCHITECTURES on its default path and gets CUDA_ARCH_BIN in its
own dotted form. Build with the image’s GCC 16, never a downgraded
CUDAHOSTCXX: a GCC 15 host compiler produced the GLIBCXX link failures it
seemed to avoid.
What does NOT build on an arm64 host: the Android payloads. Google ships the
NDK and SDK build tools for x86_64 Linux only, so Dockerfile.android skips
them there and the stage passes media through.
What the runtime lane adds for a GPU image#
With ENABLE_NVIDIA=true the package copies the CUDA toolkit, cuDNN and NCCL
out of the media artifact (copy-media-payloads.sh), puts nvcc on PATH,
and fails when the toolkit is missing. The wrapper then resolves
ONNX_PACKAGE=onnxruntime-gpu and PYTORCH_EXTRA=pytorch-cu130 unless an
operator pinned them, and its own gates assert torch.version.cuda and
CUDAExecutionProvider. A --no-push run hands the wrapper the android wheels
as a directory context; why not an OCI one is in
failure-modes.md.
Running it on a Jetson#
Rootless nerdctl needs three extra flags, each answering one failure:
linux-host-setup.md § B2b.
linux/jetson-webcam/ is a working example:
USB-camera object detection at 30 fps with 13 ms GPU inference.
Known limits#
The official PyTorch
cu130wheels warn that they do not target compute capability 8.7. What was run worked; a kernel with no Orin code will fail. ORT, OpenCV and TVM are built here with nativesm_87.No TensorRT on this lane (see
ENABLE_TENSORRT).Nothing from this lane is published. A
--no-pushbuild on a Jetson tagslatest-nvidia-hostarm64-arm64locally (an image built before the variant naming islatest-cross-hostarm64-arm64). It does not become:latest-nvidia-arm64: the published arm64 entry is to come from a cross-sbsa lane on the amd64 host, which does not exist yet (BACKLOG.mdCON31: no arm64 route).
DeepStream (nvidia variant)#
On in every amd64 nvidia build, and never in
:latestor:latest-rocm. Since 2026-10-07 an nvidia chain on an amd64 build platform setsENABLE_DEEPSTREAM=trueitself (stage-defs.sh), andENABLE_DEEPSTREAM=falseopts one run out. An arm64 nvidia build gets no default, because DeepStream has no route there yet. A default or rocm chain refuses the flag. The owner allowed publishing it on 2026-10-01 (§ Licence) and chose the full TensorRT 10 builder resources on 2026-10-07, so the image keeps every GPU generation, the RTX 2080’s sm_75 included. Tracked as CON42 inBACKLOG.md.
NVIDIA DeepStream 9.1 is two things. Its
GStreamer plugins, utilities and apps are Apache-2.0 source. Its runtime (nvvideoconvert,
nvv4l2decoder, the tiler, libnvbufsurface, the tracker’s libnvds_nvmultiobjecttracker,
the nvds_* libraries) is a prebuilt .deb under NVIDIA’s SDK licence. The image unpacks
the .deb and builds the source against the image’s own GStreamer 1.29.2, not against the
Ubuntu 24.04 GStreamer 1.24 that NVIDIA targets.
CROSS_VARIANT=nvidia bash linux/scripts/build-cross-chain.sh \
--target-arches amd64 --parallel-archs --log-dir ./out/build-logs/nvidia
What ships#
Path |
What |
|---|---|
|
the runtime |
|
a link to it, which some upstream Makefiles and configs name |
|
the SDK headers, for building custom parsers and plugins |
|
NVIDIA’s licence, as the |
|
DeepStream’s own TensorRT 10 (§ TensorRT 10 beside 11) |
|
a link to |
|
the TensorRT 10 lib dir, the only DeepStream path on the loader path |
|
links into the TensorRT 10 prefix, which an engine build |
|
a link to NVIDIA’s NVDEC plugin, so the distro |
|
the new |
deepstream-9.1/lib is deliberately not on the loader path. libnvv4l2.so there
carries the SONAME libv4l2.so.0, so ldconfig would hand NVIDIA’s copy to every V4L2
user in the image (NVIDIA’s install.sh does exactly that, with an update-alternatives
entry). The libraries find each other through their RUNPATH
(/opt/nvidia/deepstream/deepstream-9.1/lib); measured 2026-10-01, every element resolves
that way with LD_LIBRARY_PATH unset.
The script is linux/scripts/05-frameworks/deepstream.sh (build in the media stage,
stage-runtime or assert-absent in the package stage). Every download is pinned in
versions.env (DEEPSTREAM_*) with a SHA256: the release .deb (GitHub’s asset digest),
the six TensorRT 10 debs (from the CUDA repo’s Packages index), and the source commits.
What needs a GPU host#
The driver’s own libraries:
libcuda.so.1andlibnvidia-ml.so.1come from the host, injected bynvidia-container-toolkit(--gpus all, or--device nvidia.com/gpu=allthrough CDI; rootless nerdctl:linux-host-setup.md§ B2c). NVIDIA states driver 595.58.03 or newer for DeepStream 9.1.The image’s
cuda-compatlibcuda (615.71.09) can take over. The CDI spec ofnvidia-container-toolkit1.20.1 runsnvidia-cdi-hook enable-cuda-compat --host-driver-version=595.58.03. In the container it left an/etc/ld.so.conf.d/00-compat-<uuid>.confnaming/usr/local/cuda-13.4/compat, solibcuda.so.1resolves to the image’s compat driver before the host’s, andnvidia-smireports CUDA 13.4 (the host: 13.2). Measured 2026-10-12 on the RTX 2080, a GeForce card, with the 595.58.03 open kernel module: CUDA initialises through the compat libcuda, and DeepStream runs on it and on the host’s own libcuda alike.Everything beyond registration. Without a GPU the gates prove the elements load and register. Measured without a driver,
nvstreammuxstops at PAUSED withCuda failure: status=100andUnable to set device in gst_nvstreammux_change_state(100 iscudaErrorNoDevice).
The gate command, from the sample data asset (not in the image):
deepstream-sample-data_9.1.0.deb,
671818070 bytes, sha256 21ee7ddcfdda83d56924f25ed124e314689bf65af8ca971b7404fdb292685e98
(GitHub’s asset digest, checked against the download), installed with dpkg -i. It runs in
the image’s environment, which sets
USE_NEW_NVSTREAMMUX=yes (§ nvstreammux: the new one only):
cd /opt/nvidia/deepstream/deepstream-9.1/samples/configs/deepstream-app
gst-launch-1.0 -e filesrc location=../../streams/sample_720p.h264 ! h264parse ! nvv4l2decoder \
! mux.sink_0 nvstreammux name=mux batch-size=1 \
! nvinfer config-file-path=config_infer_primary.txt ! fakesink
config_infer_primary.txt builds an FP16 engine from resnet18_trafficcamnet_pruned.onnx
on first use. The new mux has no width/height properties; nvinfer scales.
The GPU run (2026-10-01)#
Passed, on an RTX 2080 (sm_75, compute capability 7.5), host driver 595.58.03 through
CDI with rootless nerdctl. It ran in a throwaway container FROM the published :latest amd64
child as root: the variant’s CUDA 13.4.2 + cuDNN 9.26.0.51 installed the way
Dockerfile.nvidia does (ENABLE_TENSORRT=false), then deepstream.sh build and
deepstream.sh stage-runtime. The pipelines ran in the package stage’s environment (no CUDA
on LD_LIBRARY_PATH). Pass/fail and functional facts only; the licence forbids publishing
benchmark results (§ Licence).
Check |
Result |
|---|---|
The nine gate elements register against the real driver, fresh registry |
pass, no |
TensorRT engine build for sm_75 (engine file removed first) |
pass: |
|
pass with the plugin link (§ NVDEC through the distro libv4l2); without it, fail |
decode → |
pass: a pad probe on |
Two sources, batch 2, |
pass: 2884 frames from both pads, every object carries a tracker id |
|
pass, both to EOS |
An |
pass: boxes and labels drawn on the cars and people |
The same pipeline as the runtime user (uid 1001) |
pass |
CUDA 13.4 runs the 13.2-built kernels (phase 3) |
pass: |
The legacy |
fail, every time: § nvstreammux: the new one only |
Reproduce it in the package stage’s environment, not with Dockerfile.nvidia’s ENV. That
stage puts lib64/stubs on LD_LIBRARY_PATH (the package stage replaces the variable).
There, the registry scan’s hip plugin dlopen()s libcuda.so, gets the stub, and the
stub’s SONAME libcuda.so.1 then stands in for the driver for the rest of the scan: measured,
nvh264dec, nvh264enc and cudaconvert vanish from the registry and DeepStream’s
plugin_init logs Cuda failure: status=34 (cudaErrorStubLibrary). The build stages have
no GPU, so only a hand-run gate meets this.
Three defects surfaced, each fixed in the source: the builder-resource links, the libv4l2
plugin link, and the mux switch below. The published image carries all three, and the
2026-10-12 run used each of them. A fourth, outside DeepStream, turned the first into a
segfault:
failure-modes.md § An exception through std::call_once segfaults in libunwind.
The GPU run on the published image (2026-10-12)#
Passed, except nvblender. The published :latest-nvidia ran as pulled: index
sha256:810f36b38714b423652902ed35f4b221d36f868639a83a3b4da47c476b4a033e, amd64 manifest
sha256:23e3cc9e5bbbfc64d8395e1d171faa56c89e9b09cbb95bedcba225110fe4bdd5, built 2026-10-11 from
hub aabebdbc. The host is the one of 2026-10-01: the RTX 2080 (sm_75), driver 595.58.03 with
the open kernel module, rootless nerdctl 2.4.0 with CDI from nvidia-container-toolkit 1.20.1.
Every pipeline ran through the image’s entrypoint, in its own ENV, as root unless a row says
uid 1001. A pad-probe program, compiled in the image against sources/includes, counted the
metadata. Pass/fail and functional facts only (§ Licence).
Check |
Result |
|---|---|
|
pass: driver 595.58.03, |
The nine gate elements, fresh registry, real driver, as uid 1001 |
pass: no |
|
pass: |
The gate command above, no engine file |
pass: |
decode → |
pass: 1442 frames, all |
Two sources, batch 2, |
pass: 2884 frames from both pads, every object carries a tracker id |
|
pass: 1442 frames on each branch; 1442 frames |
All of the above as uid 1001, with uid 1001 building the engine in its own copy of the samples |
pass |
CUDA 13.4 under NVIDIA’s CUDA 13.2 builds |
pass: |
The host’s libcuda 595.58.03 in place of the compat one |
pass: the |
The legacy |
fail, as documented: |
The plugins no run had covered (CON42’s open question). Of the 40 files in lib/gst-plugins/,
21 are NVIDIA’s prebuilt ones (their .comment reads Ubuntu GCC 13.3.0, marked P below); the
rest were built here with GCC 16.2.0.
Element |
Result |
|---|---|
|
pass: 1442 frames to MP4, decoded back to 1442 by |
|
fail on this GPU: |
|
pass: 1443 frames |
|
pass: a JPEG round trip, the MJPEG sample (1442 frames), 1442 frames encoded |
|
pass: optical-flow metadata on all 1442 frames; the picture colours the moving bus, cyclist and car |
|
pass: a class map on all 1442 frames. The samples carry no segmentation model, so |
|
pass: 1442 frames each |
|
pass: the SONYC classifier on the sample WAV, two sources batched, labels on all 490 audio frames |
|
pass: the MP4 decodes back to 1442 frames with boxes and tracker ids; analytics metadata on every frame; EOS; 120 frames dewarped to 480 surfaces |
|
pass for the frames: 1141 crossed as CUDA surfaces, intact. EOS does not cross the socket |
|
pass |
|
not run: under Xvfb it gets Mesa’s EGL, no |
|
fail: § nvblender |
|
not run: their samples need a RealSense camera, Triton (not built), a model outside the sample data, or a GL display |
|
pass: 120 frames to 480 coherent surfaces, once the new mux has |
|
pass, each to EOS. |
A pipeline that decodes an MP4 through nvv4l2decoder prints gst_meta_api_type_has_tag: assertion 'tag != 0' failed two to four times. It does so on GStreamer 1.24.2 as well (below),
and nothing fails. Two consumer traps came out of the run, a registry scanned without a GPU and
an engine cache in the root-owned tree:
consumer-image-contract.md § DeepStream in :latest-nvidia.
nvblender: a read after unmap, and a second pad it ignores#
nvblender blends nothing on GStreamer 1.29.2. Every NvBufSurfTransformCompositeBlend call
fails with Error(-3) in NvBufSurfTransformCompositeBlend dropping buffer, and the plugin’s
own debug line names the cause: op_surf = (nil). It takes its output surface from
GstMapInfo.data after gst_buffer_unmap(), the read the legacy mux makes. Two measurements
prove it:
An
LD_PRELOADshim that restoresGstMapInfoafter unmap, as GStreamer < 1.28 left it, gives a validop_surfon every frame, and the blends succeed while input frames arrive. As a control, the same shim makes the legacy mux run: 1442 frames, 30214 objects.Ubuntu 24.04’s GStreamer 1.24.2, loaded in the same container with NVIDIA’s prebuilt plugins, gives a valid
op_surfwithout the shim.
The second defect is NVIDIA’s own. With two inputs nvblender never reaches EOS, on 1.24.2 as
on 1.29.2: it passes none of sink_1’s events (caps, segment, EOS) to GstVideoAggregator, so
the aggregator waits for sink_1 forever. Its criticals (invalid cast from 'GstNvblenderPad' to 'GstVideoAggregatorConvertPad', a NULL debug category, tag != 0) appear on 1.24.2 too.
The image cannot fix either: the plugin is NVIDIA’s binary, and the shim switches GStreamer’s
check off for every caller in the process. Do not use nvblender in this image.
TensorRT builder resources, by file name#
The first engine build stopped with
Error Code 6: API Usage Error (Unable to load library: libnvinfer_builder_resource_sm75.so.10.16.1 … No such file or directory).
libnvinfer.so.10 dlopen()s its builder resources by file name. Their SONAME is
do_not_link_against_nvinfer_builder_resource_sm75, and ld.so.cache holds only SONAMEs, so
the ld.so.conf entry for the private prefix does not help; dlopen() finds the file name
only in a default directory. A distro TensorRT lives there, the private copy does not.
deepstream.sh therefore links each libnvinfer_builder_resource_*.so.10.16.1 into
/usr/lib/<triplet>/ (in both build and stage-runtime), and the gates check the links.
The names carry the full TensorRT version, so they cannot collide with TensorRT 11’s.
NVDEC through the distro libv4l2#
Without the link, nvv4l2decoder opened /dev/nvidia0 as a plain V4L2 node:
Error getting capabilities for device '/dev/nvidia0': It isn't a v4l2 driver. NVDEC is
NVIDIA’s libv4l2 plugin (lib/libv4l/plugins/libcuvidv4l2_plugin.so), and the distro
libv4l2 loads plugins only from /usr/lib/<triplet>/libv4l/plugins/ (a compile-time path,
no environment override). stage-runtime links the plugin there; with the link the decoder
runs on the distro libv4l2, so the SONAME hijack in NVIDIA’s install.sh stays out.
Measured on a non-NVIDIA node (v4l2_open("/dev/null")): the plugin’s init returns NULL
and libv4l2 moves on. Without a driver its dlopen() fails on libcuda.so.1, which
libv4l2 also skips. GStreamer’s own v4l2src does not use libv4l2 by default.
nvstreammux: the new one only#
NVIDIA’s legacy nvstreammux is a prebuilt static archive
(libnvdsgst_multistream_legacy.a) linked into the source-built plugin, and it is the
default unless USE_NEW_NVSTREAMMUX=yes. On GStreamer 1.29.2 it accepts the first buffer and
returns GST_FLOW_ERROR with no message; the pipeline reports only
h264parse0: Internal data stream error … reason error (-5). Traced instruction by
instruction: the legacy chain maps the input buffer, unmaps it, and then reads
GstMapInfo.data. Since GStreamer 1.28, gst_memory_unmap() calls gst_map_info_clear(),
which sets memory, flags and data to NULL to catch exactly that use-after-unmap, so the
check sees NULL and fails. GStreamer 1.24, which NVIDIA builds against, left the struct
alone.
The new mux (gst-nvmultistream2, Apache-2.0 source, built here) has no such read, and
NVIDIA’s other readers of the switch (libnvdsgst_customhelper.so, libnvds_yml_parser.so)
follow the same variable. So Dockerfile.package sets ENV USE_NEW_NVSTREAMMUX=yes when
ENABLE_DEEPSTREAM=true and leaves it empty otherwise. Applications built for the legacy mux
must use the new mux’s properties (config-file-path, no width/height). A source with
several surfaces per frame, such as nvdewarper, needs num-surfaces-per-frame set: the new
mux asks upstream only when the property is 0, and it defaults to 1. Without it, measured on
2026-10-12, the mux takes no dewarped frame and pushes empty batches in a tight loop.
Other prebuilt NVIDIA plugins could carry the same read. The 2026-10-12 run drove 15 of the 21
prebuilt plugin files with sample data
(§ The GPU run on the published image):
nvblender carries it. The others ran to EOS with sound output, apart from the legacy mux
itself and the AV1 encoder, which Turing lacks. Not run: the three ds3d
plugins and nvdsvisionencoder (nothing in the sample data drives them), nveglglessink
(needs an NVIDIA-driven display) and UCX (not loadable).
GPU architectures. NVIDIA’s prebuilt kernels carry SASS for sm_75, 80, 89, 90, 100,
110, 120 and 121, plus PTX for sm_90. The two source components with CUDA code
(nvdsinfer, nvll_osd) build with nvcc’s default, sm_75 SASS plus sm_75 PTX, so they run
on every card from Turing on (newer ones JIT the PTX once). TensorRT 10.16 ships builder
resources for sm_75, 80, 86, 89, 90, 100 and 120. So the RTX 2080 (sm_75) can run
DeepStream, although the variant’s CUDA_ARCHITECTURES (86;87;89;120) leaves it out of
ONNX Runtime and OpenCV.
TensorRT 10 beside 11#
DeepStream 9.1 needs TensorRT 10, and the variant pins 11.3:
NVIDIA’s prebuilt
libnvds_nvmultiobjecttracker.so,libnvds_inferutils.soandlibnvds_utils.solinklibnvinfer.so.10(andlibnvonnxparser.so.10).The Apache-2.0
nvdsinferdoes not compile against TensorRT 11.3: 56 errors, fromIInt8Calibrator,BuilderFlag::kINT8/kFP16,kEXPLICIT_BATCH,IBuilder::platformHasFastFp16,ITensor::setTypeandILayer::setPrecision, all removed in 11.
So the image carries TensorRT 10.16.1.11 privately. NVIDIA publishes it only in the
ubuntu2404 CUDA repo, built for CUDA 13.2. The sonames differ (.10 vs .11), so the two
do not collide on disk or in ld.so.cache.
One process must not have TensorRT 11 in its global symbol scope. Both libraries
export the same unversioned symbols (createInferBuilder_INTERNAL, getInferLibVersion,
…). Measured 2026-10-01 with a library linked against libnvinfer.so.10, loaded after
libnvinfer.so.11:
TensorRT 11 opened with |
the |
|---|---|
|
101601 (its own, correct) |
|
110300 (TensorRT 11’s, the wrong ABI) |
GStreamer loads plugins with local binding, and ONNX Runtime opens its TensorRT EP the same
way, so the image’s own consumers stay apart. An application that links libnvinfer.so.11
directly, or dlopens it with RTLD_GLOBAL, must not also run DeepStream’s nvinfer or
nvtracker.
ENABLE_TENSORRT stays false for the variant; DeepStream does not need it.
So the published image carries no TensorRT 11 at all, and the clash above cannot happen in it
as shipped. Measured 2026-10-12: its only libnvinfer files are this TensorRT 10 (the library,
libnvinfer_plugin, libnvonnxparser and the eight builder resources), and ldconfig -p
resolves libnvinfer.so.10, libnvinfer_plugin.so.10 and libnvonnxparser.so.10 to its
prefix. ONNX Runtime’s GPU build has the CUDA EP only. /etc/ld.so.conf.d/tensorrt.conf still
names /usr/local/tensorrt/lib, which does not exist: configure-runtime.sh writes it for every
nvidia build.
CUDA 13.2 vs 13.4#
The runtime is built against CUDA 13.2, the variant ships 13.4.2. Measured: every CUDA
soname the tree needs (libcudart.so.13, libcublas.so.13, libcufft.so.12,
libnpp*.so.13, libnvjpeg.so.13) resolves against 13.4.2 through ld.so.conf, and ldd -r
reports no missing symbol in any library whose sonames all resolve. The RUNPATH entry
/usr/local/cuda-13.2/lib64 in NVIDIA’s binaries simply does not exist here. The kernels
run on 13.4 as well (§ The GPU run).
What the build changes against upstream#
Each of these is a measured failure of NVIDIA’s own build/build.sh flow in this image:
GCC 16. Its libstdc++ no longer includes
<cstdint>and<algorithm>transitively, and-Werrorturns its newnon-c-typedef-for-linkagewarning into an error. The build pre-includes a compat header throughCXX,CCandNVCC_PREPEND_FLAGS, and adds-Wno-error=non-c-typedef-for-linkagewhere the compiler is C++. Each Makefile keeps its own compiler:gstnvcustomhelpercompiles C withCXX:=gcc(g++ rejects its enum conversions),nvds_analyticscompiles C++ with make’s defaultcc, and aCC:=g++must stay g++ or libstdc++ goes unlinked. Affected:nvds_analytics,gst-nvdsmetamux,gst-nvdspreprocessand twods3dCUDA libraries.nvcc and
/usr/include/gstreamer-1.0.gst-nvvideotestsrchard-codes the distro include path for its CUDA file; the build hands nvcc/opt/gstreamer’s throughNVCC_CFLAGS, which that Makefile appends to.Only plugins in
lib/gst-plugins/. Upstream installslibpostprocess_impl.soandlibcustom2d_preprocess.sothere. The registry scandlclose()s them, which unloads libraries that registered static quark strings, and the next plugin segfaults ing_quark_from_static_string(seen withnvmultistreamtiler, from a clean registry). The build moves them tolib/. NVIDIA’s configs still name the old place: measured 2026-10-12, the sample data’sconfig_preprocess.txtstops withCould not parse custom lib pathuntil itscustom-lib-pathnameslib/libcustom2d_preprocess.so.Headers at
sources/includes, placed before the build:ds3d/dataloader/lidarsourceincludes from there, the full SDK’s layout./opt/nvidia/deepstream/deepstream, also before the build:nvmsgbroker,gst-nvdynamicsrcbinand others link from it.protoc: Ubuntu 26.04’s
protocis 3.21.12, the version upstream downloads, so the build uses it rather than a second copy.opentelemetry-cpp without gRPC:
nvds_rest_serveruses only the OTLP HTTP exporter.
Not built, each with its reason in DS_EXCLUDED (deepstream.sh): the Triton pair
(nvdsinferserver, gst-nvinferserver; no Triton server in the image), the two Azure IoT
adaptors (no azure-iot-sdk-c), gst-nvdsudp (NVIDIA’s login-gated Rivermax SDK) and
gst-dsexample-cuda (an example; upstream skips it too).
The gates#
linux/scripts/05-frameworks/deepstream-verify.sh, run at the end of the media build, in
the package stage (against the wired plugin link) and by the runtime smoke
(check_deepstream, which also fails a tree in any non-nvidia image):
Soname closure with
LD_LIBRARY_PATHunset.libcuda.so.1andlibnvidia-ml.so.1are the driver’s. Eleven other misses are documented exceptions (UCX, RealSense, libtorch, NVIDIA Maxine, the Triton filter, andlibnvdsgst_sparse4d.so, whoseRUNPATHNVIDIA left pointing atdeepstream-9.0); an exception that stops applying fails as stale. The third-party libraries built fornvds_rest_serverget an$ORIGINRUNPATH, since their own install paths do not exist in the image.One GStreamer: every plugin resolves
libgst*-1.0.so.0from/opt/gstreamer, and the tree ships no GStreamer core library.gst-inspect-1.0, which the registration gate runs, must load its core from there too.No libv4l2 hijack: the lib dir is on no
ld.so.conf, andlibv4l2.so.0resolves to the distro’s.Only plugins in
gst-plugins/, and a registry scan that does not crash. The check readsnmto the end: agrep -qunderpipefailSIGPIPEsnmon a large plugin, and the first run movedlibgstnvvideo4linux2.soout that way.The
dlopen()links: every TensorRT builder resource resolves by file name from the default directory (build and package), and the package’slibv4l2plugin dir holds the NVDEC plugin (DSV_WIRED=1only).Registration of
nvinfer,nvstreammux,nvvideoconvert,nvtracker,nvdsosd,nvv4l2decoder,nvmultistreamtiler,nvstreamdemuxandnvurisrcbin, with CUDA’s stublibcuda/libnvidia-mlstanding in for the driver.
The build stage resolves like the final image. The deepstream stage is FROM
gstreamer, which still carries a distro GStreamer 1.28 core: the toolchain’s
software-properties-common pulls in packagekit and with it libgstreamer1.0-0, and the
gstreamer stage’s gir1.2-gstreamer-1.0 and libgtk-4-dev add the base, GL and extra
libraries. The final image purges them (03-media/runtime/install-deps.sh) and ranks
/opt/gstreamer first through configure-runtime.sh’s 000-gstreamer.conf; the build
stage had neither. So ld.so gave even /opt/gstreamer/bin/gst-inspect-1.0 the 1.28 core,
which refused every component built against 1.29 (has incompatible version (plugin: 1.29, gst: 1.28), not loading), and libgstrtspserver-1.0.so.0, which only the prefix has, did
not resolve at all (run 20261010-193517). ds_build therefore writes the same
000-gstreamer.conf before its gates. The stage’s /etc never reaches the image, which
copies only /opt/nvidia from it.
Not covered by the gates: inference, NVDEC, a TensorRT engine build. The GPU runs above covered them, in a throwaway container on 2026-10-01 and on the published image on 2026-10-12; no lane has a GPU.
Licence: the owner decision#
The runtime .deb ships NVIDIA’s Software License Agreement for NVIDIA Software
Development Kits (v. September 14, 2021) with a DeepStream Supplement, as
LicenseAgreement.pdf. The repository’s README says all release assets fall under it. The
passages that decide publishing on GHCR:
§ 1.1 (iii): the licence to “Distribute those portions of the SDK that are identified in this Agreement as distributable, as incorporated in object code format into a software application that meets the distribution requirements”.
§ 1.2 (i): “Your application must have material additional functionality, beyond the included portions of the SDK.”
§ 2.2: “you may not distribute or sublicense the SDK as a stand-alone product.”
Supplement § 1: “The following portions of the SDK are distributable under the Agreement: the runtime files ending with “.so” as part of your application”.
Supplement § 3 I: “You are not permitted to disclose the results of any benchmarking or other competitive analysis relating to the SDK without the prior written permission from NVIDIA”. So this page records no DeepStream timings.
Supplement § 3 II: “The SDK is licensed for use with a computer system incorporating one or more NVIDIA GPU hardware products and running NVIDIA software drivers.”
What the image would redistribute that is not a .so: LicenseAgreement.pdf, the static
libnvdsgst_multistream_legacy.a and libnvds_service_maker_utils.a, and the
pyservicemaker wheel. TensorRT 10 falls under its own EULA, like the TensorRT 11 question
already open for :winamd64 (docs/third-party-licenses.md, eula-review).
Whether a general-purpose development image counts as an “application” with “material additional functionality” is a legal question, not a build one.
Decided 2026-10-01: the owner allows publishing it in :latest-nvidia. The chain no
longer refuses a push with ENABLE_DEEPSTREAM=true. What ships is held to the supplement’s
words where that costs nothing: the package stage deletes the two static .a archives (a
build-time input only), and LicenseAgreement.pdf ships beside the runtime. The
pyservicemaker wheel stays. No page here records DeepStream timings (Supplement § 3 I).
The media fan-out strategy, as AGENTS.md carried it#
Moved out of AGENTS.md on 2026-09-15 (owner decision D10), unedited except for this heading and the relative links. The RULES stayed there; this is the reference behind them.
Dockerfile.media uses a parallel multi-stage DAG (BuildKit runs independent stages concurrently):
base ─┬─ onnxruntime ───────┐
├─ litert ────────────┤
├─ opencv ────────────┼─ media-inputs ─ gstreamer ─ libcamera ─ final
├─ ffmpeg ────────────┤
└─ app-wheelhouse ────┘
--mount=type=cache(apt/ccache/sccache/uv/pip/cargo) keyed per-arch viaid=...-${TARGETARCH},sharing=locked.--mount=type=bind,readonlyfor per-library build scripts — no COPY layer, so editing one library’s scripts invalidates only that RUN, not downstream layers.--mount=type=tmpfsfor/tmpscratch (no layer bloat).COPY --linkfor layer-parallel copying from independent build stages.Shared/common files (
core/common.sh,activate-cross-python.sh,verify-media-artifacts.sh, 01-core helpers) are COPY’d in thebasestage (rarely change → stable cache).Runtime scripts are COPY’d only in the
finalstage (must persist in the published image; build scripts are NOT shipped).
Torch Add-on (Linux)#
Builds on the package image (BASE_IMAGE, default :latest-package-<arch>), and
the runtime lane builds it as the per-arch wrapper :latest-<arch>
(build-runtime-manifest.sh). The old standalone :torch tag was deleted from
the registry on 2026-08-27. A local build:
nerdctl build -t local/kataglyphis:torch-amd64 -f linux/Dockerfile.torch .
AMD GPU Build (Linux)#
Tag:
:latest-rocm(a manifest; per-arch wrapper:latest-rocm-amd64) — spelledrocm, notamd, because a variant never reads like an architecture (:latest-amd-amd64). Not published yet. The old single-arch:amd(a 2026-04-24 build on the Ubuntu 24.04 base) and its stage tags were deleted from the registry on 2026-09-22.
Requirements:
Host driver compatible with ROCm 10.1 (see the compatibility matrix).
--device=/dev/kfd --device=/dev/dripassed todocker run.
The ROCm variant chain inserts Dockerfile.amd as its gpu stage after the shared :cross-sdk-amd64 and before media; the later stages run with ENABLE_AMD=true.
Files involved:
File |
Purpose |
|---|---|
|
Installs ROCm 10.1 + MIGraphX 2.18 from AMD TheRock repo (HIP, MIOpen, RCCL, rocBLAS, rocFFT, MIGraphX) |
|
Media stack: conditionally builds ORT with MIGraphX EP when |
|
Conditionally builds on top of the AMD media image |
|
Conditionally tags the final entrypoint image |
|
ORT build script with MIGraphX EP |
Notes:
The ROCm version is pinned by
ROCM_VERSIONinlinux/scripts/01-core/versions.env.linux/scripts/01-core/setup-rocm-repo.shadds the TheRock apt repos in deb822.sourcesformat (stable.repo.amd.com), with core ROCm and MIGraphX as separate repo stanzas sharing the same GPG key and Origin (“AMD ROCm”). Package names use theamdrocm-*prefix, and every name the script installs carries the ROCm release (below).MIGRAPHX_VERSIONmoves together withROCM_VERSION.MIGraphX packages come from a separate repo path (
/rocm/migraphx/packages/ubuntu2604/) on the samestable.repo.amd.comhost. The toolchain image pins the AMD repo to provide only ROCm/MIGraphX packages via an apt pin on Origin “AMD ROCm”.The ONNX Runtime MIGraphX Execution Provider replaces the older ROCm EP. The build script passes
--use_migraphx --migraphx_home <MIGraphX's prefix>(/opt/rocm/extras-10on 10.1, below) instead of--use_rocm.The build produces an
onnxruntime-migraphxPython wheel (instead ofonnxruntime-rocm), and itscp3XYttwin from the same tree (free-threaded wheels).The media stage strips all external apt sources from the SDK base image and configures clean resolute-only sources to prevent cross-distro package conflicts. 01-core modules are bind-mounted into build stages so
media_common_init()can locate cross-build helpers.
Build (orchestrated):
CROSS_VARIANT=rocm bash linux/scripts/build-cross-chain.sh \
--target-arches amd64 --log-dir ./out/build-logs/rocm
Same variant rules as NVIDIA (starts at gpu, own state, one chain at a time),
plus: amd64 only — any other --target-arches is refused. The runtime lane
copies /opt/rocm into the package (copy-media-payloads.sh
copy_rocm_payload; publish_rocm_ld_path writes /etc/ld.so.conf.d/000-rocm.conf), and the wrappers
take onnxruntime-migraphx + the app’s pytorch-rocm10 extra. That extra reads
the same wheel line as the pinned PYTORCH_ROCM_INDEX (rocm7.14, the newest
carrying torch 2.14 for cp314 — there is no rocm10 line), so
assemble-torch-app.sh’s re-install of the PYTORCH_VERSION pair from it (with
deps, so triton-rocm moves with it) only acts if the two drift. The extra was
pytorch-rocm71 up to OrchestrANT v0.0.28; the wrapper builds the app’s
develop since 2026-09-29
(linux-cross-builds.md).
Run with GPU access:
sudo nerdctl run --rm -it --device=/dev/kfd --device=/dev/dri ghcr.io/kataglyphis/kataglyphis_beschleuniger:latest-rocm
The ROCm release is in every package name#
AMD’s stable suite is rolling: ROCm 10.1 joined it on 2026-09-30, beside 10.0.
A versionless metapackage such as amdrocm-core-dev depends on the newest
release’s package (amdrocm-core-dev10.1), so ROCM_VERSION=10.0 did not pin
the install. amdrocm-migraphx 2.17.0 still needs the 10.0 libraries, so one
install got both trees: 262 packages of 10.1 and 73 of 10.0 (apt-get install -s, 2026-10-07).
setup-rocm-repo.sh therefore installs versioned names only. rocm_packages
builds them from ROCM_VERSION and MIGRAPHX_VERSION:
Package |
ROCm 10.0 |
ROCm 10.1 and later 10.x |
|---|---|---|
core, runtime, blas, dnn, hipblas-common, fft, rccl, sparse, solver |
|
|
MIGraphX and its |
|
|
ASAN ( |
|
|
Any other release stops the script before its first download:
no MIGraphX package name known for ROCm <release>. Add the release’s MIGraphX name torocm_packagesfirst.A
MIGRAPHX_VERSIONthat the release does not ship stopsapt-get install:Version '2.18.0+rocm10.0.*' for 'amdrocm-migraphx' was not found.After the last install, every installed
amdrocm*package whose name carries a release must carryROCM_VERSION. Otherwise the build stops and lists them.test-rocm-packages.shfails whenversions.envnames a release thatrocm_packagesdoes not know, so a ROCm bump cannot skip this table.
Measured with apt-get install -s on 2026-10-07: the 10.0 list selects 251
packages, 249 named …10.0 plus the two MIGraphX ones (2.17.0+rocm10.0.0).
With ASAN it is 281, again all 10.0. The 10.1 list, the pin since that day,
selects 264: 262 named …10.1 (all 10.1.0-3) plus amdrocm10-migraphx and
amdrocm10-migraphx-dev at 2.18.0-1. With ASAN it is 298, again all 10.1. The
real install is 7.0 GB of downloads and 21.7 GB on disk, and the release check
passes on it.
Where ROCm 10.1 puts MIGraphX#
Measured on 2026-10-07: the 10.1 list installed in :latest, and the 10.0
packages’ file lists (dpkg-deb -c):
What |
ROCm 10.0 ( |
ROCm 10.1 ( |
|---|---|---|
MIGraphX headers, |
|
|
The other MIGraphX libraries, behind |
|
|
|
|
|
|
MIGraphX’s directory, with no HIP in it |
the |
In 10.1, /opt/rocm/{core,bin,include,lib} are all alternatives links into
core-10.1, beside a new core-10 link. core-10.1 carries HIP 7.16.26385 and
AMD clang 24.
Three places followed MIGraphX out of /opt/rocm/lib:
setup-rocm-repo.shwrites the directory holdinglibmigraphx_cinto/etc/ld.so.conf.d/rocm.conf, beside/opt/rocm/lib. It findsmigraphx.hppanywhere under/opt/rocm, and fails unlessldconfig -plistslibmigraphx_c. The first 10.1 install stopped atmigraphx.hpp not found.30-build-native-amd.shpasses ORT MIGraphX’s own prefix as--migraphx_home. It is the directory abovelib/cmake/migraphx, found underMIGRAPHX_HOME, which stays the ROCm root for the HIP probe. ORT’sfind_package(migraphx REQUIRED PATHS /opt/rocm)does not reachextras-10. A CMake probe that copies ORT v1.30.0’s lookup failed with/opt/rocm. With/opt/rocm/extras-10it found MIGraphX 2.18.0 and the external-data API, and linked and ran without a GPU. The script itself then built ORT v1.30.0 against the install in a throwaway:latestcontainer. It made anonnxruntime_migraphx-1.30.0cp314 wheel that listsMIGraphXExecutionProvider. With no GPU, a session loads the EP and stops atHIP failure 100: no ROCm-capable device, then falls back to CPU.publish_rocm_ld_pathadds the same directory to the runtime image’s000-rocm.conf, becauselibonnxruntime_providers_migraphx.soneedslibmigraphx_c.so.3.
The 10.1 layout also reached the CPU ORT build, which never asks for ROCm.
/opt/rocm/lib is now the merged core-10.1/lib, so /opt/rocm/lib/cmake holds
the amdrocm-dnn-dev10.1 (hipDNN) package’s flatbuffers 25 config, and
/opt/rocm/share/cmake an nlohmann_json one. /opt/rocm/bin is on PATH, and
CMake searches the parent of every PATH entry, so ORT’s FIND_PACKAGE_ARGS
took both and put -I/opt/rocm/include on onnxruntime_flatbuffers.
ort.fbs.h then stopped at FLATBUFFERS_VERSION_MAJOR == 23 with 25 == 23
(rocm chain 20261009-131840, media stage). Two arguments keep ROCm out:
append_onnx_native_base_build_argspassesCMAKE_DISABLE_FIND_PACKAGE_flatbuffers=TRUEto every native ORT build, so only ORT’s pinned 23.5.26 fetch serves flatbuffers. The MIGraphX build had carried that define on its own since 2026-09-27.append_onnx_rocm_isolation_argsgives the CPU buildCMAKE_IGNORE_PREFIX_PATH=${ROCM_HOME:-/opt/rocm}when that root exists, the Linux twin of the Windows rocm lane’s isolation argument. Its configure then names/opt/rocmin noflags.make. The MIGraphX build does not call it.
TheRock’s LLVM is clang 24 now. Dockerfile.package borrows libFuzzer only from
a same-major clang/23 copy, so a rocm image no longer finds one to borrow.
That changes nothing while the image’s own LLVM 23 ships libFuzzer, which
:latest does.
ROCm: what the first :latest-rocm run must carry (planned, 2026-09-22)#
Measured against AMD’s current docs and the live repo indexes before the first
rocm chain run. Everything here is verified against TheRock packages
(stable.repo.amd.com, amdrocm-*) on Ubuntu 26.04, which is what
setup-rocm-repo.sh installs — not the classic repo.radeon.com rocm-* set
most AMD pages still describe.
ASAN: a separate image, never :latest-rocm#
The owner asked for the AddressSanitizer packages alongside the normal ones
(install/asan.html).
They exist for ROCm 10.0 / Ubuntu 26.04, in a parallel repo path
(.../core/packages-asan/ubuntu2604/, same signing key), as
amdrocm-asan10.0, amdrocm-core-devel-asan10.0,
amdrocm-developer-tools-asan10.0, amdrocm-opencl-asan10.0,
amdrocm-core-sdk-asan10.0. Four facts decide the shape:
Size. Measured from that repo’s
Packages.gzon 2026-09-22: 143 packages,amdrocm-llvm-dev-asan10.0alone 61.7 GiB installed,amdrocm-llvm-asan10.029.3 GiB, 134.8 GiB for the full set. A:latest-rocmthat carries this is not shippable.GPU coverage. Every one of the 30 gfx-specific ASAN packages is gfx942 or gfx950 (MI300/MI350). On any other AMD GPU the instrumented libraries do nothing.
Not the headline feature. There is no ASAN MIGraphX and no ASAN PyTorch wheel line, so the two things this image exists for stay uninstrumented.
Co-installing hijacks
update-alternatives. The ASAN debs register the same alternatives as the normal packages (core,rocm-lib,rocm-bin,hipcc, …) with the same priority, so/opt/rocm/lib,/opt/rocm/binand/usr/bin/hipcccan silently resolve into/opt/rocm/core-asan-10.0. The outcome is a coin flip between rebuilds, not a deterministic last-wins.
ROCm 10.1 (the same Packages.gz, 2026-10-07) is far smaller but not otherwise
different. It has 94 ASAN packages and 47.0 GiB for the full set, and
amdrocm-llvm-dev-asan10.1 is 2.0 GiB. Counted the same way, 10.0 has 91
packages and 134.8 GiB. The gfx-specific packages are still gfx942 and gfx950
only.
Decided and implemented (owner, 2026-09-22): optional, and OFF. A >100 GiB
image is not acceptable as the default, so ENABLE_ROCM_ASAN (Dockerfile.amd,
default false) gates the whole thing:
The parallel
packages-asanrepo stanza is only written when the knob is on, and onlyamdrocm-asan${ROCM_VERSION}is installed — not the SDK metapackage that drags in the 61.7 GiBamdrocm-llvm-dev-asan10.0.After that install,
setup-rocm-repo.shre---sets every alternative whose value points intocore-asan-*back tocore-${ROCM_VERSION}and then ASSERTS that/opt/rocm/{core,lib,bin}andhipccresolve to the normal tree. The build fails if they do not.copy_rocm_payloaddeletes/opt/rocm/core-asan-*from the payload unless the knob is on, so a default:latest-rocmcannot carry it even if a builder installed it by hand.Runtime stays the consumer’s business and is never baked:
HSA_XNACK=1andLD_LIBRARY_PATH/LD_PRELOADinto the ASAN prefix, documented in the run recipe. The ASAN directories never enter/etc/ld.so.conf.d/:publish_rocm_ld_pathprunescore-asan-*, whoselibholds alibamdhip64of its own.
The tarball install the owner’s URL selects (i=tar) stays the better shape
if the alternatives dance ever misbehaves: it unpacks to a prefix we choose and
registers no alternatives at all. The apt route is what is wired, because it
keeps the signed-repo supply chain we already pin.
The install half ran once, in a throwaway container (10.1, 2026-10-07). No
image build has run it. setup-rocm-repo.sh ran with ENABLE_ROCM_ASAN=true
over the installed 10.1 list and exited 0:
It added 34 ASAN packages, a 6.2 GB download and 29 GB in
/opt/rocm/core-asan-10.1.No alternative points into
core-asanafterwards, and/opt/rocm/{core,lib,bin}andhipccresolve intocore-10.1.The release check passed over all 298 packages.
That run also showed that publish_rocm_ld_path would have put
core-asan-10.1/lib on the loader path.
The ASAN libraries only do anything on gfx942/gfx950 hardware, which no lane has.
The non-ASAN work the same sweep turned up#
# |
Change |
Why |
|---|---|---|
1 |
Install per-gfx metapackages instead of the all-architecture ones |
|
2 |
|
The wrapper has none of them today; every consumer recipe starts by setting them. |
3 |
Assert the installed ROCm and MIGraphX versions at the end of |
Done 2026-10-07, in |
4 |
Write the RESOLVED path into |
It currently writes the literal |
5 |
|
Our documented run line is incomplete; the image runs non-root and cannot open the device as shipped. |
6 |
Do NOT add |
Both are cargo-cult carried from old ROCm guides; the first weakens every consumer’s sandbox, the second silently lies about the GPU. |
7 |
Writable MIOpen cache/db paths |
The image is designed to run |
8 |
Build-time self-check that works with NO GPU |
|
9 |
Point consumers at AMD’s Container Runtime Toolkit (CDI), which supports nerdctl |
Cleaner than hand-rolled device flags, and it covers Ubuntu 26.04. |
10 |
Fix the Renovate comment on |
It watches a TheRock git tag, not the apt package we install. |
Checked and NOT applicable: PYTORCH_ROCM_ARCH (a source-build knob; we install
prebuilt wheels) and GPU_TARGETS/AMDGPU_TARGETS (no such knob for the
MIGraphX EP build). The kernel driver stays on the HOST — the image ships
userspace only, and that is correct.
Hailo (in the standard image)#
HailoRT + hailortcli + the hailonet element + TAPPAS (hailofilter,
hailocropper, hailooverlay, hailoaggregator, hailotracker, …) +
pyhailort are built into the standard runtime (:latest) for amd64 and
arm64, for hosts with a Hailo-10H accelerator (Hailo-8 support was dropped
2026-09-20; riscv64 has no HailoRT support). There is no separate Hailo tag:
the standalone :hailo variant and linux/Dockerfile.hailo were retired on
2026-09-22 as a duplicate of this build (owner directive: a variant exists only
for a stack that cannot ship in :latest). Design, upstream matrix and pins:
hailo-support.md. The PCIe kernel driver is host-only
(GPL-2.0, DKMS) — the image needs --device=/dev/hailo0.
Files involved:
File |
Purpose |
|---|---|
|
The Hailo |
|
Verified sources → offline CMake build → self-checks ( |
|
The build’s two switches, |
|
|
Build: the normal chain (make cross-build, or build-cross-chain.sh) —
nothing Hailo-specific to run. Two switches pick between the default (the
cached nested build, a real pyhailort) and the build as it was before
2026-09-24: hailo-support.md.
Run:
nerdctl run --rm -it --device=/dev/hailo0 ghcr.io/kataglyphis/kataglyphis_beschleuniger:latest
Edge accelerators#
These are host/device procedures for the boards the runtime artifacts get
deployed to. Host-side driver and performance setup is
Linux Host Setup. The images themselves are covered
elsewhere: Hailo-10H is built into :latest
(hailo-support.md), and a Jetson runs the arm64 SBSA GPU
image (above).
Hailo-10H: compiling an ONNX model to .hef#
The Hailo toolchain does not consume ONNX at runtime. A model goes through
three stages — parse, quantize, compile — and each emits an intermediate .har.
1. Parse. Let the parser infer the graph boundaries first:
hailo parser onnx /local/shared_with_docker/model.onnx --hw-arch hailo10h
If it cannot resolve the ends of the graph, pin them explicitly. The node names are model-specific — read them off the failure message or a Netron dump:
hailo parser onnx /local/shared_with_docker/model.onnx \
--hw-arch hailo10h \
--start-node-names images \
--end-node-names Conv_1058 Conv_1065 Conv_1088 \
--tensor-shapes "[1,3,640,640]"
2. Quantize. Needs a model script (.alls) and a calibration set:
hailo optimize /local/shared_with_docker/model.har \
--hw-arch hailo10h \
--output /local/shared_with_docker/model_quantized.har \
--model-script /local/shared_with_docker/model.alls \
--use-random-calib-set
A minimal .alls:
post_quantization_optimization(finetune, policy=enabled, learning_rate=1e-5, epochs=3, batch_size=16, dataset_size=64)
performance_param(compiler_optimization_level=2)
--use-random-calib-set is for smoke-testing the pipeline only — accuracy will
be poor. For a real run, supply images:
wget http://images.cocodataset.org/zips/val2017.zip
mkdir -p /local/shared_with_docker/coco/
unzip val2017.zip -d /local/shared_with_docker/coco/
Some tools want a single .npy instead of a directory:
import os
import numpy as np
from PIL import Image
image_dir = "./images_for_calibration"
output_file = "calib_data.npy"
image_size = (640, 640) # match the model input
num_images = 100
all_images = []
for i, file in enumerate(sorted(os.listdir(image_dir))):
if i >= num_images:
break
if file.lower().endswith((".jpg", ".jpeg", ".png")):
img = Image.open(os.path.join(image_dir, file)).convert("RGB")
img = img.resize(image_size)
all_images.append(np.array(img, dtype=np.uint8)) # uint8 for Hailo
np.save(output_file, np.stack(all_images))
print(f"Saved {len(all_images)} images to {output_file}")
3. Compile:
hailo compiler /local/shared_with_docker/model_quantized.har \
--output-dir /local/shared_with_docker/
For a model the Hailo Model Zoo already knows, the three steps collapse into one:
hailomz compile yolov9c \
--ckpt /local/shared_with_docker/yolov9c.onnx \
--classes 80 --hw-arch hailo10h \
--calib-path /local/shared_with_docker/coco/val2017/val2017
Inspect a compiled graph with hailo visualizer /path/to/model.har.
For a calibration set in TFRecord form rather than a directory of images, the Model Zoo ships the converters:
python hailo_model_zoo/datasets/create_coco_tfrecord.py val2017
python hailo_model_zoo/datasets/create_coco_tfrecord.py calib2017
Format reference: hailo_model_zoo DATA.rst.
Hailo-10H: the driver#
The PCIe module is not loaded automatically — after every reboot:
sudo modprobe hailo_pci
Before installing a new driver version, remove the old one or the DKMS build will collide with the loaded module:
lsmod | grep hailo
sudo modprobe -r hailo_pci
sudo dkms status
sudo dkms remove <module>/<version> --all
On a Raspberry Pi this sometimes requires a kernel built from source — see the Raspberry Pi kernel documentation.
NVIDIA Jetson#
Identify the board and capture a full spec dump before filing anything:
cat /proc/device-tree/model # e.g. NVIDIA Jetson Orin NX ...
inxi -Fxxx > jetson-specs.txt
Power mode gates clock speeds and therefore every benchmark number:
sudo nvpmodel -q # query the active mode
Never let unattended-upgrades touch Docker on a Jetson. In
/etc/apt/apt.conf.d/50unattended-upgrades, the "Nvidia:jetson" origin is
fine; adding "Docker:jammy" is not — the upgrade breaks the Jetson container
runtime integration and docker fails to start
(NVIDIA forum thread).
If it already happened, pin back:
sudo apt-get install -y --allow-downgrades \
docker-ce=5:27.5.1-1~ubuntu.22.04~jammy \
docker-ce-cli=5:27.5.1-1~ubuntu.22.04~jammy
The torchvision note below applies to NVIDIA’s JetPack wheels installed on the board itself. The SBSA image above needs none of it: NVIDIA on arm64 (SBSA).
torchvision must be built from source. The Jetson PyTorch wheels come from NVIDIA, not PyPI, and the matching torchvision is not published — installing it with pip pulls a build against the wrong torch:
sudo apt-get update
sudo apt-get install -y libjpeg-dev zlib1g-dev
# check the pytorch site for the torchvision tag matching your torch version
git clone --branch v0.20.0 https://github.com/pytorch/vision.git
cd vision
pip3 install -r requirements.txt
python3 setup.py install
If the board cannot reach any host, its resolver is the usual cause — see TLS handshake failures.