[HIP] [OPUS] [FlyDSL] Bound AITER worker and compiler fanout - #5164
Open
Qubitium wants to merge 28 commits into
Open
[HIP] [OPUS] [FlyDSL] Bound AITER worker and compiler fanout#5164Qubitium wants to merge 28 commits into
Qubitium wants to merge 28 commits into
Conversation
The AOT driver and compile API diverged when both were introduced on May 29, 2025 in commit 01864fa. The driver passed an HSACO path and item sizes to an API expecting a GQA ratio and dtype names. Align the GQA-16 AOT matrix with the compile API, consume executor results so worker failures propagate, and add the first unit coverage for aiter/aot/asm_mla_decode_fwd.py.
Round the observed cold-build peak up to a readable 1.5 GiB per-worker memory budget instead of storing the exact telemetry byte count.
The generic AOT worker limiter incorrectly mixed CPU and memory capacity into a GPU process pool while PID mapping, restart thresholds, and logging still used the GPU count. Resolve tuner process count only from visible and explicitly selected GPUs, while retaining per-worker nested compiler limits. Add regression coverage for default, explicit, and excessive process-count requests.
Clamp the final derived worker count and normalize nonpositive MAX_JOBS overrides back into the environment. Cover zero memory, missing CPU count, exhausted task capacity, nonpositive overrides, and nested subprocess limits.
Replace scattered cores-minus-one and inline 0.8 formulas with one dependency-free helper that returns floor(logical CPUs times 0.8), clamped to at least one. Use it from setup, JIT, C++ extension, AOT worker sizing, and telemetry. Represent the approximately observed per-worker peak as a fixed 1,500,000,000-byte value. Automatic AOT workers are the minimum of the memory-derived, CPU-derived, and cgroup task budgets, with a floor of one.
Make get_worker_count a zero-argument centralized policy and remove stale default=16 calls. Delete FlyDSL worker-count defaults and duplicate memory sizing so it only caps centralized workers by submitted job count. Add a regression assertion that the worker-count API accepts no caller-provided fallback.
Rename CPU_UTILIZATION to CPU_CORE_COUNT_UTILIZATION so the constant cannot be mistaken for measured runtime CPU utilization.
Contributor
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
PR title tags & labels: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR seems large but it all centered around the oom crash I encountered on a single vm with 1 slice of MI350X that randomly killed vm processes (tmux).
Basically there are lots of places of where disjointed fan-out was happening. The fix was a centralize fan-out control for the entire AITER pkg. No more everyone who needs a threadpool doing their own, and wrong, calculations.
The estimated fan-out in this PR is still a best-telemetry based estimate but is much better than the previous static control with sometimes incorrect upper/lower bounds based on out-dated or inaccurate metrics.
I have reviewed every delta and designed the refractor.
https://x.com/qubitium/status/2094621657226985567?s=20
Summary
This draft fixes nine validated bugs present at the
origin/mainmerge base and adds seven worker-policy improvements. The final automatic policy uses process-available CPUs and the lower of host-available and container-available memory; process-pool children always force nested compiler fanout to one.Validated bugs present in
origin/mainand fixed here (9, ordered by severity)1. Top-level AOT limits did not bound nested compiler fanout
aiter_worker_limits.py:224-240, covered attests/test_worker_awareness.py:203-217andtests/test_worker_awareness.py:233-245.2. Nested Ninja division could produce
-j0, meaning unlimited jobsPREBUILD_THREAD_NUMand truncated the result. One job divided across five parents became zero; Ninja interprets-j0as unlimited jobs.aiter_worker_limits.py:213-216, covered attests/test_worker_awareness.py:219-226.3. Automatic memory sizing ignored container limits
MemAvailablealone is unsafe in a memory-limited container. An 8 GB container on a host with 256 GB free could size from 256 GB and be OOM-killed.psutil.virtual_memory().available; cgroup v2/v1 discovery and finite ancestor limit/usage handling are ataiter_worker_limits.py:33-178. v2, v1, ancestor, and 256 GB-host/8 GB-container regressions are covered attests/test_worker_awareness.py:53-149.4. The memory-per-worker estimate materially under-sized compiler RSS
aiter_worker_limits.py:6-8andaiter_worker_limits.py:181-210. Memory capping is covered attests/test_worker_awareness.py:194-201.5. CPU sizing could use CPUs unavailable to the process
os.cpu_count(), which may report host CPUs outside the process affinity/cpuset. Other call sites used unrelated static defaults, allowing CPU oversubscription.os.process_cpu_count(), falls back to affinity, and never returns fewer than one ataiter_worker_limits.py:14-30, covered attests/test_worker_awareness.py:23-39.6. Generic
MAX_JOBScollided with parent frameworksMAX_JOBS, allowing vLLM, SGLang, PyTorch, or another parent framework to accidentally control and potentially oversubscribe AITER fanout, while AITER could mutate the framework's own setting.AITER_MAX_JOBScontrols AITER. Namespace isolation is covered attests/test_worker_awareness.py:167-173.7. MLA decode AOT called the compile API with the wrong ABI
aiter/aot/asm_mla_decode_fwd.pypassed an HSACO path and integer item sizes to a compile function that expectsgqa_ratio,q_dtype, andkv_dtype. Both sides diverged when introduced on May 29, 2025 in commit01864fa8e2347421bc5c314de8b584777ea991ec.aiter/aot/asm_mla_decode_fwd.py:7-28, with signature and invocation coverage attests/test_asm_mla_decode_aot.py:16-45.8. AOT drivers passed strings as
ProcessPoolExecutor.max_workersos.environ.get("MAX_JOBS", "16")directly toProcessPoolExecutor. The default is the string"16"; Python rejects it withTypeError: '<=' not supported between instances of 'str' and 'int'. PA-v1 had the same failure wheneverMAX_JOBSwas set. The merge-base code is visible ataiter/aot/asm_mla_decode_fwd.py:46-49,aiter/aot/pa.py:97-100, andaiter/aot/pa_v1.py:104-107.get_worker_count_for(...); MLA verifies the exact integer constructor call attests/test_asm_mla_decode_aot.py:48-70.9. AOT worker exceptions were silently discarded
executor.map(...)without consuming its iterator. Worker compilation failures could therefore remain unraised in the parent.tests/test_asm_mla_decode_aot.py:72-94; sampling submits all families before consuming them ataiter/aot/sampling.py:82-106.Improvements and hardening in the final diff (7)
aiter_worker_limits.pypolicy instead of arbitrary per-caller defaults.aiter_worker_limits.py:219-221.tests/test_sampling_aot.py:9-42.mp_tunerremains GPU-count-driven; only compiler descendants are constrained. GPU-count behavior is covered inop_tests/tuning_tests/test_mp_tuner_logic.py.tests/test_worker_entrypoints.py.AITER_MAX_JOBSis documented and tested as an unsafe expert override that deliberately bypasses automatic CPU/memory caps ataiter_worker_limits.py:189-210andtests/test_worker_awareness.py:158-165.Final policy
AITER_MAX_JOBSis the sole AITER top-level override; genericMAX_JOBSis ignored and preserved. An explicit value bypasses automatic CPU and memory caps and can oversubscribe or OOM the process/container.min(floor(process_available_cpus * 0.80), min(psutil_host_available, finite_cgroup_remaining) / 1_500_000_000), with a floor of one. When no finite cgroup memory limit exists, host availability is used.mp_tunerremains GPU-count-driven.The editable installation and policy are documented at
README.md:105-149,docs/installation.rst:80-89, andaiter/aot/flydsl/README.md:60-68.Telemetry basis
The intentionally untracked cold MI350X diagnostic observed 23 live workers, 32.8 GB aggregate RSS, and approximately 1.43 GB normalized peak RSS per worker. The final fixed estimate is rounded up to a readable decimal 1.5 GB. On the current 24-logical-CPU container, no finite cgroup memory limit is configured; the final policy reports CPU 19 and memory 180 budgets and selects 19 workers.
Validation
git diff --checkpasses.AITER_MAX_JOBS, and preserves inherited genericMAX_JOBS.aiter/aot/flydsl/grouped_moe.pyandop_tests/test_flydsl_grouped_gemm_gfx1250.py.