Add chunk_memory_mode: auto — container-memory-aware adaptive chunking - #1104
Add chunk_memory_mode: auto — container-memory-aware adaptive chunking#1104vincentgong7 wants to merge 1 commit into
Conversation
1f1f5c9 to
f799b82
Compare
Adaptive chunking sizes chunks against a static `chunk_size` byte budget that the user must hand-tune per machine, and it targets host RAM rather than the container's cgroup limit — so on memory-limited containers it can over-commit and get OOM-killed. This adds an opt-in `chunk_memory_mode: auto` that derives the chunk memory budget from the process's real memory ceiling at runtime and hardens the adaptive sizing. When `chunk_memory_mode: auto`: - Budget = (memory_limit - current usage) * chunk_memory_safety_factor, where memory_limit is read from the cgroup (v2 memory.max, then v1, then psutil) — the limit that actually OOM-kills the process. No machine-specific chunk_size to tune. - Multiprocess: the shared budget is divided by the real per-step worker count (read from the num_processes injectable); num_processes:0 auto-derives the worker count from the available non-reclaimable memory / chunk_worker_target_budget. - Chunks are sized from the budget vs the chunk's INCREMENTAL memory (not absolute rss), so a large memory-mapped shared skim buffer (reclaimable page cache) does not pollute the measurement and collapse chunks to a single row. - A budget floor keeps chunking active under pressure (a zero budget would disable chunking and process all choosers at once); a growth cap and an incremental peak-backoff bound overshoot; an optional circuit-breaker warns as memory nears the limit. All new behavior is gated on `chunk_memory_mode` (default `fixed` = current behavior), so existing runs are unaffected. Adds unit tests (core/test/test_mem.py, test_chunk_robust.py).
f799b82 to
77df117
Compare
|
Thanks for your proposed contribution. I'm sure you already see the test failures. Based on the content and format of your contribution, I strongly suspect you are using AI for this (if not -- kudos to you on getting this far). There is no restriction on doing so for this project, and indeed I asked for an AI review, which found at least the following issues:
Is the benefit demonstrated?No. There are no runtime comparisons, repeated trials, peak-cgroup-memory traces, multiple model configurations, or sensitivity tests across memory limits and worker counts. The tests are particularly weak for a memory-control feature: the integration test has three choosers, and there are no tests for production sizing, multiprocess aggregate pressure, zero headroom, the per-worker floor, or actual OOM prevention. The PR description also claims tests for shared-memory parsing and worker recommendations that do not appear in the submitted test files. Linux core tests have passed, but the overall CI run was still in progress when I checked. Recommended scopeInstead of trying to fix this, perhaps try a different approach. Useful, lower-risk features could include:
In short: cgroup-aware guardrails are useful; the case for memory-maximizing adaptive chunk optimization has not been made here. |
Summary
The current adaptive chunking mechanism uses a manually tuned, static
chunk_sizeand effectivelyassumes that host RAM is fully available. This makes the chunk size easy to misconfigure, particularly
in memory-constrained environments such as Kubernetes pods, where the process may unknowingly exceed its
cgroup memory limit and be terminated by the OOM killer.
To address this, we introduce an opt-in
chunk_memory_mode: auto. When enabled, adaptive chunkingdetermines its chunk size at runtime based on the process's actual memory limit, eliminating the need
for per-machine tuning and reducing the risk of container OOM failures.
The change is fully backward compatible: the default
fixedmode preserves the existing behavior.Motivation
How adaptive chunking currently works. For each submodel, ActivitySim divides choosers into batches
("chunks") sized to fit within memory. The chunk size is determined using the user-configured
chunk_size, which represents the approximate amount of RAM available for batch processing.In training mode, ActivitySim measures the actual memory usage of each submodel and caches an estimated
per-row memory footprint. The
adaptiveandproductionmodes then reuse and refine these cachedestimates to determine the appropriate number of rows per chunk.
The problems with that.
chunk_sizeis a static number the user must hand-tune to the machine (RAM, cores, #households,skim size) and re-tune for every new machine or model — the docs themselves describe a trial-and-error
procedure to find it.
cgroup limit, which is usually well below host RAM. A
chunk_sizeset from host RAM — or the commonchunk_size: 0/ "use most of the RAM" guidance — over-commits and the run is OOM-killed, often deepinto a long multiprocess run.
num_processesmust also be hand-tuned against the sameRAM; too many workers for the available memory OOMs, and the right number changes per machine.
How this change solves them.
chunk_memory_mode: autoderives the chunk budget from the process'sreal memory ceiling (the cgroup limit that actually OOM-kills it) at runtime, divides it correctly
across the real worker count (and can auto-pick that count), and hardens the sizing so a large shared
memory-mapped skim buffer can't distort it. The result needs no per-machine
chunk_sizetuning anddoes not exceed the container's memory limit.
What it does (when
chunk_memory_mode: auto)(memory_limit − current usage) × chunk_memory_safety_factor,where
memory_limitcomes from the cgroup (v2memory.max→ v1memory.limit_in_bytes→psutilhost RAM). No machine-specific
chunk_sizeto set.num_processes: 0to auto-derive the worker count from available (non-reclaimable) memory and achunk_worker_target_budgetper-worker target.memory growth, so a large memory-mapped shared skim buffer (reclaimable page cache) does not distort
the row-size estimate and collapse chunks to a single row.
and process all choosers at once); a growth cap and an incremental peak-backoff bound overshoot; an
optional circuit-breaker warns as memory nears the limit.
It reuses the existing adaptive-chunking machinery (ChunkSizer / observed-memory / row-size cache) —
an enhancement of adaptive chunking, not a replacement.
New settings (all under
auto; defaults preserve current behavior)chunk_memory_modefixedautoderives the budget from the real ceiling;fixed= staticchunk_sizechunk_memory_safety_factorchunk_worker_target_budgetnum_processes: 0chunk_growth_capchunk_row_size_marginchunk_memory_circuit_breakerchunk_memory_abort_ratioBackward compatibility
All new behavior is gated on
chunk_memory_mode. The defaultfixedreturns the staticchunk_sizeverbatim (asserted by a test) — existing runs are unaffected.
Testing
activitysim/core/test/test_mem.py(cgroup limit / available / shmem parsing, worker-countrecommendation) and
test_chunk_robust.py(fixed = legacy; auto budget bounds + safety scaling;auto-mode
simple_simulateoutput matches fixed-mode). 15 tests pass.container that the equivalent fixed-
chunk_sizeconfig could not fit — auto-mode completed with no OOM.Docs
docs/core.rst— new "Automatic memory-aware chunking (chunk_memory_mode: auto)" subsection.