Skip to content

fix(build): cap ONNX Runtime build parallelism to avoid runner OOM - #348

Merged
mc-nv merged 1 commit into
mainfrom
mchornyi/TRI-1550/cap-tritonserver-build-parallelism-to-avoid-runner-ooms
Jul 8, 2026
Merged

fix(build): cap ONNX Runtime build parallelism to avoid runner OOM#348
mc-nv merged 1 commit into
mainfrom
mchornyi/TRI-1550/cap-tritonserver-build-parallelism-to-avoid-runner-ooms

Conversation

@mc-nv

@mc-nv mc-nv commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What does the PR do?

Caps the ONNX Runtime build parallelism in the generated Dockerfile so fully-uncached
builds no longer OOM the GitLab runner hosts (the "nested ONNX Runtime build" called out in
TRI-1550).

Previously gen_ort_dockerfile.py passed a bare --parallel to ORT's build.sh, which
expands to multiprocessing.cpu_count(). Combined with per-nvcc multi-arch compilation
(7 CUDA archs), that exhausted memory on swap-less builders and the OOM killer terminated
runner/system daemons instead of the build.

The parallelism is now computed in Python at Dockerfile-generation time and baked into the
build step as literals — e.g. --parallel 16 --nvcc_threads 2:

  • usable_cpu_count() honors CPU affinity / cgroup pinning (Linux), with a cpu_count()
    fallback.
  • available_memory_gb() reads MemAvailable from /proc/meminfo.
  • build_parallelism() budgets ~2 GB of RAM per concurrent compiler slot and bounds the
    product parallel_jobs * nvcc_threads by that budget.
  • The assumed budget is capped at MAX_BUILD_MEMORY_GB (64 GB): never plan for more than
    that, nor for more than is actually available; assume the cap when memory is unknown.

Baking literals keeps the build stage free of shell logic, so it is portable across the
Debian (apt) and RHEL (dnf) base-image paths.

Related PRs:

triton-inference-server/server#8873

Where should the reviewer start?

tools/gen_ort_dockerfile.py — the new usable_cpu_count() / available_memory_gb() /
build_parallelism() helpers, the MAX_BUILD_MEMORY_GB constant, and the ./build.sh
RUN step (bare --parallel removed from COMMON_BUILD_ARGS, now
--parallel <jobs> --nvcc_threads 2).

Test plan:

  • Verified the generated Dockerfile RUN line renders as clean literals (no shell logic) and
    Python parses.
  • Validated the parallelism math across host profiles:
    • 128 c / 512 GB → --parallel 16 --nvcc_threads 2 (budget capped at 64 GB, ~64 GB peak)
    • 32 c / 48 GB → --parallel 12 --nvcc_threads 2
    • 16 c / 32 GB → --parallel 8 --nvcc_threads 2
    • 8 c / 4 GB → --parallel 1 --nvcc_threads 2
    • memory unknown → assumes 64 GB, bounded by cores
  • Validate on the affected builder / arm-builder jobs that a fully uncached build no
    longer OOMs the runner.

Caveats:

  • The 64 GB budget and ~2 GB/slot figure are named constants at the top of the file, easy
    to retune.
  • Values are computed on the host running gen_ort_dockerfile.py (same runner that then
    runs docker build); a --memory-limited build container smaller than host RAM is not
    reflected. Same model as the server/build.py change.

Background

GitLab build jobs were failing with logs abruptly stopping because runner hosts OOM'd
during fully uncached builds — unbounded parallelism in the nested ONNX Runtime build on
swap-less builders.

Related Issues:

  • Resolves: TRI-1550

The generated Dockerfile passed a bare `--parallel` to ONNX Runtime's
build.sh, which expands to multiprocessing.cpu_count(). Combined with
per-nvcc multi-arch compilation, fully-uncached builds exhausted memory
on swap-less builders and the OOM killer terminated runner/system daemons
instead of the build.

Compute the parallelism in Python at Dockerfile-generation time and bake
literal `--parallel <jobs> --nvcc_threads 2` into the build step:

- usable_cpu_count() honors CPU affinity/cgroup pinning (Linux), falling
  back to cpu_count() elsewhere.
- available_memory_gb() reads MemAvailable from /proc/meminfo.
- build_parallelism() budgets ~2 GB of RAM per concurrent compiler slot
  and bounds the product `parallel_jobs * nvcc_threads` by that budget.
- The assumed budget is capped at MAX_BUILD_MEMORY_GB (64 GB): never plan
  for more than that, nor for more than is actually available; assume the
  cap when memory is unknown.

Baking literals keeps the build stage free of shell logic, so it is
portable across the Debian (apt) and RHEL (dnf) base-image paths.

Relates to TRI-1550.
Comment thread tools/gen_ort_dockerfile.py Dismissed
@mc-nv
mc-nv marked this pull request as ready for review July 8, 2026 21:01
@mc-nv
mc-nv merged commit c296a09 into main Jul 8, 2026
3 checks passed
mc-nv added a commit that referenced this pull request Jul 8, 2026
) (#349)

The generated Dockerfile passed a bare `--parallel` to ONNX Runtime's
build.sh, which expands to multiprocessing.cpu_count(). Combined with
per-nvcc multi-arch compilation, fully-uncached builds exhausted memory
on swap-less builders and the OOM killer terminated runner/system daemons
instead of the build.

Compute the parallelism in Python at Dockerfile-generation time and bake
literal `--parallel <jobs> --nvcc_threads 2` into the build step:

- usable_cpu_count() honors CPU affinity/cgroup pinning (Linux), falling
  back to cpu_count() elsewhere.
- available_memory_gb() reads MemAvailable from /proc/meminfo.
- build_parallelism() budgets ~2 GB of RAM per concurrent compiler slot
  and bounds the product `parallel_jobs * nvcc_threads` by that budget.
- The assumed budget is capped at MAX_BUILD_MEMORY_GB (64 GB): never plan
  for more than that, nor for more than is actually available; assume the
  cap when memory is unknown.

Baking literals keeps the build stage free of shell logic, so it is
portable across the Debian (apt) and RHEL (dnf) base-image paths.

Relates to TRI-1550.
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.

3 participants