Problem
In a development sandbox the container that runs an inference service based on ONNX Runtime repeatedly enters a crash‑loop. The container exits with a SIGSEGV during model initialization:
2026-10-22 14:03:12.345 INFO [onnxruntime] Loading model from /models/large_model.onnx
2026-10-22 14:03:12.347 FATAL [onnxruntime] Segmentation fault (core dumped)
2026-10-22 14:03:12.348 ERROR container runtime: container terminated with exit code 139
The model is > 2 GB on disk, uses dynamic input shapes, and the sandbox imposes a 4 GiB memory cgroup limit. The issue does not appear when a smaller, static‑shape model is used.
Root Cause
The crash is caused by an unbounded memory allocation performed by ONNX Runtime when it attempts to allocate tensor buffers for the dynamic shape dimensions. The runtime computes the maximum possible size based on the model’s dim_param values, which for this model are left unspecified (e.g., batch_size: -1, seq_len: -1). In the sandbox the process is confined to a 4 GiB cgroup; the allocation request exceeds that limit, the kernel delivers a SIGSEGV instead of a clean OOMKilled because the allocation occurs inside a native thread without a proper guard page.
Additional contributing factors:
- ONNX Runtime’s default memory arena disables aggressive memory trimming for dynamic shapes.
- The sandbox’s
/dev/shmsize is limited to 64 MiB, causing failures when the runtime tries to map large temporary buffers. - Model is stored as a single monolithic
.onnxfile; the runtime loads the entire graph into memory before it can apply shape‑inference optimizations.
Debug
Step‑by‑step investigation that reproduced the failure and isolated the cause.
- Inspect container logs for the exact point of failure.
docker logs inference-sandbox-1
# ... logs shown above ...
- Enable core dump generation inside the container to capture the faulting address.
# In Dockerfile
RUN echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern
RUN ulimit -c unlimited
- Run gdb on the core file to see the allocation stack.
gdb /usr/local/bin/onnxruntime_core /tmp/core.ort.12345
(gdb) bt
#0 malloc + 0x...
#1 Ort::Session::Init + 0x...
#2 ModelLoader::LoadModel + 0x...
#3 main + 0x...
- Check cgroup memory limits that the sandbox enforces.
# From host
cat /sys/fs/cgroup/memory/docker/$(docker inspect -f '{{.Id}}' inference-sandbox-1)/memory.limit_in_bytes
4194304000 # 4 GiB
- Enable ONNX Runtime diagnostics to log memory allocation attempts.
export ORT_LOG_SEVERITY=VERBOSE
export ORT_ENABLE_CPU_MEM_LIMIT=1
export ORT_CPU_MEM_LIMIT=3500 # MiB
Running the container with these variables produced the following log entry before the crash:
2026-10-22 14:03:12.340 VERBOSE [memory] Requested allocation of 7.8 GiB for tensor 'input_0' (shape: [?, ?, 1024])
Solution
The fix consists of two complementary actions: limit the runtime’s memory demand and adjust the sandbox resources.
1. Constrain dynamic shape dimensions in the model
Replace unconstrained dimensions with realistic upper bounds using onnx.shape_inference or by editing the model with onnx-simplifier.
# Before (model definition)
input_0: float32[?, ?, 1024]
# After (bounded)
input_0: float32[1..8, 1..256, 1024]
2. Enable ONNX Runtime memory‑limit mode
Set environment variables so the runtime aborts allocation before it triggers a segfault.
# Dockerfile snippet
ENV ORT_ENABLE_CPU_MEM_LIMIT=1
ENV ORT_CPU_MEM_LIMIT=3000 # MiB, below cgroup limit
ENV ORT_DISABLE_MEM_PATTERN=0
3. Increase sandbox /dev/shm and memory cgroup (development only)
If bounding the shape is not possible, raise the sandbox limits to accommodate the model.
# docker run
docker run -d \
--shm-size=1g \
--memory=8g \
--memory-swap=8g \
-e ORT_ENABLE_CPU_MEM_LIMIT=0 \
my-onnx-inference:latest
4. Externalize large weight tensors
Convert the model to use external_data for weights > 2 GB, reducing the in‑memory footprint during graph loading.
python - <<'PY'
import onnx
model = onnx.load('large_model.onnx')
onnx.save_model(model, 'large_model_external.onnx', save_as_external_data=True, location='weights.bin')
PY
Before / After comparison of Dockerfile
| Before | After |
|---|---|
|
|
Verify
After applying the changes, the container should start and remain healthy.
# Deploy
docker compose up -d inference
# Check status
docker ps -f name=inference
# Expected: Up (healthy)
# Log verification
docker logs inference | grep "Segmentation fault"
# No output
# Health endpoint
curl -s http://localhost:8080/healthz
# {"status":"ok"}
Additionally, confirm that the runtime reports the bounded allocation:
2026-10-22 14:05:01.112 VERBOSE [memory] Requested allocation of 1.2 GiB for tensor 'input_0' (shape: [8, 256, 1024])
Prevent
- Always bound dynamic dimensions in production models; leave
-1only for truly variable axes with known limits. - Enable
ORT_ENABLE_CPU_MEM_LIMITin non‑production environments to catch runaway allocations early. - Store models larger than 2 GB with external data files to avoid monolithic memory mapping.
- Configure sandbox
/dev/shmto at least 256 MiB for ONNX Runtime, which uses shared memory for intermediate tensors. - Integrate a CI step that runs
onnxruntime_testwith a memory‑stress flag to detect potential segfaults before deployment.
Related Topic Hub: Model Serving Troubleshooting Hub
FAQ
- Why does the container crash with
SIGSEGVinstead of being OOM‑killed?
ONNX Runtime performs a largemallocinside a native thread. When the allocation exceeds the cgroup limit, the kernel returns an invalid address, leading to a segmentation fault before the OOM killer can intervene. - Can I keep the model fully dynamic and still avoid crashes?
Yes, by setting realistic upper bounds on dynamic dimensions (e.g.,batch_size: 1..16) and enablingORT_ENABLE_CPU_MEM_LIMITso the runtime enforces those bounds at load time. - Is increasing the sandbox memory limit a safe long‑term solution?
It mitigates the symptom but does not address the underlying unbounded allocation. In production you should bound shapes or externalize weights to keep memory usage predictable. - How do I know which tensor caused the oversized allocation?
SetORT_LOG_SEVERITY=VERBOSE. The runtime logs each allocation with the tensor name and requested size, as shown in the verification logs. - Does GPU execution avoid this issue?
GPU memory allocation follows similar rules; an unbounded shape will still request the maximum possible GPU buffer and can cause a driver‑level crash. Bounding dimensions is required for both CPU and GPU execution.