Skip to content

feat(atom): optimize atom pd performance - #209

Merged
xiaobochen-amd merged 20 commits into
mainfrom
dev/yaoc/atom-pr4045-opt-20261020
Oct 23, 2026
Merged

xiaobochen-amd merged 20 commits into
mainfrom
dev/yaoc/atom-pr4045-opt-20261020

Conversation

@ChengYao-amd

@ChengYao-amd ChengYao-amd commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

This PR has three parts:

  1. ATOM / aiter patches (7): shorter decode steps, and no more GC stalls in ATOM's API process.
  2. Router (Python and Rust): the ATOM PD decode takes the prompt token ids the prefill returned; /metrics no longer scrapes ATOM workers.
  3. GLM-5.2 4P4D kit: defaults follow ATOM's recipe and InferenceX PR #4045, with a Mooncake Store CPU tier.

Result: with the new kit defaults, C56–C112 run 5.0%–23.8% above InferenceX PR #4045, and the peak moves from 42.5K at C96 to 48.2K tok/s/GPU at C112.

1. ATOM / aiter patches (deploy/docker/patches/atom_nightly/)

These apply only with ATOM_NIGHTLY=1 (rocm/atom-dev:nightly_202610131505: ATOM 661ef766, aiter 9205e1059). Like the existing scripts there, they are strict: a drifted anchor fails the build. patch_aiter_custom_alltoall.py adds both aiter kernels below and rebuilds module_custom_all_reduce once, after clearing its JIT build directory. check_dcp_merge.py checks both kernels against RCCL on GPUs (bit-exact all-to-all, merge up to FP8 rounding, eager and inside a CUDA graph); run it on 4 or 8 GPUs after moving the base image.

patch change effect (MI355X, GLM-5.2 decode TP4 + DCP4)
patch_atom_api_gc_stalls.py ATOM API process: when serving online, finished sequences are no longer queued on CoreManager.outputs_queue (nothing reads it online), and the per-request context gauges are opt-in (ATOM_METRICS_PER_REQUEST_CONTEXT=1) Both API processes spent ~45% of the wall clock in gen-2 GC passes that grew with every completed request. C64 throughput +1.9%, mean TTFT 2.85 → 2.04 s
patch_atom_dcp_topk_custom_ag.py The sparse indexer's DCP top-k exchange goes through dcp_all_gather (aiter's IPC all-gather instead of pynccl) 1.3 MB/rank 54 → 31 µs, 24 per step
patch_aiter_mla_bmm_configs.py Per-M Triton configs for the MLA absorbed bmm (q-up N512/K192, v-up N256/K512), for M ≤ 512, swept on MI355X with outputs checked. The stock configs take a masked slow path whenever M is not a multiple of 64 Graph 40: q-up 19.1 → 8.4 µs, v-up 15.2 → 8.1 µs, about −1.4 ms per step
patch_aiter_custom_alltoall.py (first half) + patch_atom_dcp_custom_a2a.py New aiter alltoall_vec on the custom all-reduce's registered buffers and CUDA-graph path; the DCP merge uses it when it applies and RCCL otherwise Bit-identical to RCCL; 5.3 MB/rank 56 → 34 µs; −1.0…−1.6 ms per step
patch_aiter_glm52_decode_gemm_rows.py 27 tuned GLM-5.2 FP8 GEMM rows for M 144–240, only those at least 5% faster than the current path, as one more model_configs table; the build fails if aiter's tables already hold one of its keys qkv_a at M 160 14.9 → 11.5 µs; −0.9 ms per step at B33/B36
patch_aiter_custom_alltoall.py (second half) + patch_atom_dcp_custom_a2a_combine.py New aiter kernel alltoall_combine_quant: reads this rank's heads straight from the peers' registered buffers, merges them with the LSE weights and quantizes per token to FP8, so the received copy and the unpack launch go away Matches the RCCL path up to FP8 rounding (4.6e-6 of codes differ); 160 rows 44.7 → 38.1 µs per layer; −0.56 ms per step on average

Both Python entries of the all-to-all accept world sizes 2, 4 and 8 only, the sizes the kernels are instantiated for.

In total, at B40 with ~101K context, the patches in the first five rows take a decode step from 42.77 ms to about 40 ms (−6…−7%), and the one-kernel merge takes off another 0.2–0.6 ms.

2. Router (infera/, rust/router/)

  • The ATOM PD decode reuses the prefill's prompt token ids (Python and Rust):
    • The prefill leg asks for return_token_ids, and the hand-off puts the returned ids in the decode's kv_transfer_params['prompt_token_ids'].
    • ATOM's decode takes them as the prompt instead of rendering the chat template and tokenizing again (~0.25 s per request at 130K tokens).
    • A prefill that returns no ids, an empty list or negative ids, such as an older ATOM without the field, leaves the decode tokenizing as before.
    • Effect: C64 throughput +0.5%, TTFT p50 −0.09 s.
  • The frontend /metrics no longer scrapes ATOM workers (Python and Rust): the frontend reads nothing from ATOM's exposition, so each scrape only cost ATOM's API process one more /metrics render. sglang and vllm are unchanged.

3. GLM-5.2 4P4D kit (examples/atom_4p4d_glm5.2/)

  • Decode memory: mem util 0.94, --max-num-batched-tokens 2048 (DECODE_MAX_BATCHED_TOKENS) and AITER_REUSE_IDENTICAL_COMM_GROUPS=1.
    • KV blocks go from about 160K (previous settings) to about 198.5K (~12.7M tokens over the four DCP ranks).
    • The AgentX decode-prefix cliff moves from about C85 to about C100.
  • Mooncake: MC_NUM_QP_PER_EP=1 on every Mooncake peer (P, D and the Store), and ATOM's default 16 send workers (PREFILL_SEND_WORKERS is empty by default). With two QPs, 16 workers used to hang an RDMA write; with one QP the hang did not recur in 10 deployments.
  • CPU tier: PREFILL_CPU_TIER is store (default), lmcache or off.
    • With store, the new engine/store.sh starts a master and two owners, one per NUMA node with STORE_OWNER_GB=768 GiB each, backed by 2 MiB pages through glibc's THP malloc (no root needed).
    • The four PP stages share that pool, each through its own GPU's NIC, with one load worker.
    • store.sh first checks THP and the free memory of both NUMA nodes, and up.sh checks that the decode's batch budget covers (K+1) × 2 × CONC tokens.
    • The DCP prefill (PREFILL_PP=1) cannot use the Store: ATOM's Store offload refuses DCP, so up.sh stops that combination and the DCP prefill runs with PREFILL_CPU_TIER=lmcache.
  • Other:
    • TOPK_FORCE_PATH=one and --timeout-keep-alive 900 on both legs. The Rust router reuses idle connections, and ATOM's 5 s default closes them under it.
    • ROUTER_BACKEND now defaults to rust.
    • The compile-cache volume is keyed on max-num-batched-tokens too (<CTR>-cache-<role>-<image-id>-mbt<N>), because ATOM's compile cache is not.
    • agentx.sh records the CPU tier in the AgentX metadata: KV_OFFLOAD_BACKEND=mooncake for the Store, lmcache for LMCache, no offload for off.
  • New knobs: PREFILL_EXTRA_ENV, DECODE_EXTRA_ENV, DECODE_MAX_BATCHED_TOKENS, PREFILL_CPU_TIER, STORE_OWNER_GB, STORE_OWNER_DEVICES, and the Store ports.
  • Not taken from the recipe / PR #4045:
    • Decode graph sizes: here every 4 up to 2 × CONC, there every 8 up to 256.
    • max-num-seqs: here 2 × CONC, there 512.
    • OFFLOAD_PROFILE=1 (logging only).

4. Behavior changes / compatibility

change impact
The ATOM_NIGHTLY=1 image carries 7 more patches Applies to every model on that image. The GC fix applies to all of them; the DCP communication changes only touch MLA + DCP decode; the bmm configs and GEMM rows only match GLM-5.x shapes
atom:{prefill,decode}_request_context_tokens are no longer recorded by default Set ATOM_METRICS_PER_REQUEST_CONTEXT=1 when you need them (for example, for profiling)
ATOM PD request bodies The prefill gets return_token_ids: true; the decode's kv_transfer_params gets prompt_token_ids. ATOM's request models ignore unknown fields, so an older ATOM falls back to tokenizing
Router /metrics ATOM workers are no longer scraped (nothing was read from them)
Kit defaults The Store needs 768 GiB free per NUMA node and THP madvise or always; the DCP prefill needs PREFILL_CPU_TIER=lmcache. Decode 0.94 / 2048, one QP + 16 send workers, Rust router
Hosts where Store reads into HBM do not land On some MI355X hosts, Store reads into HBM do not land and the prefill's startup probe fails ("the chunk read back differs"). Use PREFILL_CPU_TIER=lmcache on such hosts (README gotcha 14)

5. Validation

Unit tests and checks:

  • CI has not run the tests yet: its lint job fails on main's infera/projection/core/projection/module_profilers/moe_mlp.py (unformatted since Projection accuracy: busiest-rank MoE load, measured decode sharding, sparse KV dtype #205), and the test jobs wait on lint. Run locally on the PR head with CI's commands: pytest -m "not slow and not integration" 2,791 passed, 148 skipped; cargo fmt --check, cargo clippy --all-targets -D warnings and cargo test pass; pre-commit passes on every file this PR touches; shellcheck on the kit is clean.
  • The 9 nightly patches apply to the pinned upstream sources in the Dockerfile's order and in reverse, a second run is a no-op, and module_custom_all_reduce rebuilds.
  • check_dcp_merge.py passes 24/24 on 4 and on 8 MI300X GPUs, and fails with the tolerance set to 0.
  • The image of 41bd647a builds on four different 8 × MI355X machines, and the results below come from it (96a3af86 after it changes only kit scripts, check_dcp_merge.py, docs and comments).

Correctness (real MTP acceptance):

configuration smoke GSM8K (200 questions) MTP acceptance
New defaults (Store) 6/6 (with long-prefix reuse) 0.975 0.799

Performance: AgentX with the InferenceX CI client, a cold start, warm-up and a 3600 s measured window per point, and forced MTP acceptance (K3, AL 2.99). GLM-5.2-MXFP4 on one 8 × MI355X node. Throughput is per GPU over all 8 GPUs and counts cache-hit input tokens. Every point had 0 errors and no JIT compile inside the measured window.

The new defaults (Store, Rust router; the four points split over three machines), against InferenceX PR #4045's 1P1D results (from that PR's own CI run, computed the same way from its published AgentX JSON; PR #4045 ran on other machines):

CONC input tok/s/GPU PR #4045 vs PR output tok/s/GPU P90 interactivity (this PR / #4045) TTFT p50 s (this PR / #4045) decode prefix hit requests
56 32,925 30,834 +6.8% 246.2 64.0 / 58.4 0.84 / 1.45 92.3% 7,049
72 40,075 38,180 +5.0% 321.9 52.1 / 48.9 1.05 / 1.93 84.1% 9,477
96 46,234 42,523 +8.7% 387.3 45.6 / 42.1 1.66 / 3.73 52.9% 11,291
112 48,154 38,904 +23.8% 406.0 42.1 / 43.0 3.64 / 14.78 25.1% 12,027
  • C56 and C72 sit before the cliff; most of their gain comes from the decode patches of section 1 (a shorter ITL).
  • C96 and C112 add the larger decode KV pool and the Store, whose asynchronous RDMA loads do not block the prefill. The peak therefore moves from PR #4045's C96 to beyond C112, and throughput is still rising from C96 to C112.
  • For reference, main's kit README reports 30,116 tok/s/GPU at C96 (LMCache, Python router, measured on another machine).

To reproduce a point (cold start; agentx.sh runs InferenceX 918524ff's client, not the CI client used above): CONC=96 MTP_AL=2.99 SESSION_AFFINITY=off ROUTER_EXTRA_ARGS="--breaker-failure-threshold 0" PREFILL_EXTRA_ARGS="--spec-decode-acceptance-length 2.99" bash engine/up.sh && bash engine/agentx/agentx.sh && bash engine/down.sh.

6. Known limitations / follow-ups

  • The Store does not work on some hosts: on some MI355X hosts every Store get into HBM leaves zeros, on one node or across nodes, with peer memory or DMA-BUF; other hosts are fine, and P→D is unaffected. Such hosts need LMCache, whose synchronous loads take 47–52% of each PP stage's time, so C96 / C112 drop to 30.7K / 23.3K and the peak is C72 (40.3K).
  • The patches are pinned to nightly_202610131505: when the base moves, the strict anchors fail the build and each script has to be re-checked. Neither the aiter side (custom all-to-all, merge kernel, two tuned tables) nor the ATOM side has been filed upstream yet; that has to happen before this configuration can go to InferenceX.
  • Real MTP acceptance varies between 0.59 and 0.82 across machines and configurations, cause unknown. The performance numbers above all use forced AL 2.99.
  • DCP prefill (PREFILL_PP=1) cannot use the Store, since ATOM's Store offload refuses DCP, so it runs with LMCache. It was validated with LMCache and the previous decode settings and has not been re-run with the new decode settings.
  • Session headers: the Rust router does not forward x-session-id / x-correlation-id to the engines (the Python router does).
  • One decode abort, not reproduced: in one C96 run the decode's rank 0 aborted with HSA_STATUS_ERROR_OUT_OF_RESOURCES while it computed the last 1,390 tokens of a 568K-token request itself. The rerun on another machine went through a 615K-token request of the same kind without errors; cause unknown.

7. Merging

Let CI run once main's lint passes again (the moe_mlp.py formatting fix goes to main separately). The 17 commits are self-contained and all carry Signed-off-by, so the history can be kept (merge or rebase). Before merging, fix the message of 14fb5592: it cites the pre-rebase hashes f7e46219 and 91910e57, which are now 949a8f5d and 8dd1f8b7. If the PR is squashed instead, the squashed message needs Signed-off-by too.

🤖 Generated with Claude Code

@ChengYao-amd
ChengYao-amd marked this pull request as ready for review October 22, 2026 02:51
ChengYao-amd and others added 17 commits October 22, 2026 06:46
Both ATOM API processes spent ~45% of the wall clock in stop-the-world
gen-2 GC passes that grew with every completed request (~0.7 s per TTFT
after an hour of AgentX C64).

- ATOM patch: finished sequences are no longer queued on
  CoreManager.outputs_queue when serving online (nothing reads it there),
  and the per-request context gauges are opt-in
  (ATOM_METRICS_PER_REQUEST_CONTEXT=1).
- Router: the frontend /metrics no longer scrapes engines it reads
  nothing from (ATOM), halving the /metrics renders of each ATOM API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…knob

- ATOM patch: the sparse indexer's DCP top-k candidate exchange (24
  gathers per decode step, ~1 ms) goes through dcp_all_gather, which
  picks aiter's IPC all-gather instead of pynccl.
- Kit: MTP_EXPERTS=mxfp4 quantizes the MTP layer's routed experts
  online to MXFP4 (they are BF16 in the checkpoint). Default bf16 keeps
  the previous online quant config byte for byte.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…ove 8

Default 4 keeps the previous 34 sizes at CONC 64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
aiter's gfx950 configs for the MLA q/v up-projection batched GEMM use
BLOCK_SIZE_M 64 from M_LEQ_64 up, so every decode M that is not a
multiple of 64 (graph 36/40/44, drafts from 36) takes the masked path
at 2-2.5x the time. One bucket per decode M with the sweep's fastest
config (MI355X, output-checked): ~1.4 ms per decode step at graph 40.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
The DCP decode merge runs one all_to_all_single per layer on RCCL (81
per GLM-5.2 step, ~3.3 ms), and no RCCL setting helps.

- aiter patch: alltoall_vec next to allgather_vec on the custom
  all-reduce buffers, reached through dispatchAllGather's gather_dim
  (-1000) so the registered-buffer and CUDA-graph paths apply as they
  are; CustomAllreduce.custom_all_to_all; module rebuilt at build time.
- ATOM patch: the DCP merge uses it when it applies, RCCL otherwise.

4 x MI355X, bit-identical to RCCL: 5.3 MB/rank 56 -> 34 us, 1.3 MB/rank
32 -> 13 us.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
aiter's GLM-5.2 a8w8_bpreshuffle table holds M = 64/128/256, so the MTP
verify at graph >= 36 (M = 144 ...) runs padded or default FlyDSL
kernels. Tuned M 144..240 for the nine GLM-5.2 shapes and kept the 27
rows at least 5% faster than the production path (e.g. qkv_a at M 160
14.9 -> 11.5 us), as an extra model_configs table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
Both ATOM legs rendered the chat template and tokenized the same prompt
(~0.25 s each at 130K tokens). The prefill leg now asks for
return_token_ids and the router passes them in the decode's
kv_transfer_params['prompt_token_ids'], which ATOM takes as the prompt
(and strips before the engine). A prefill that returns no ids leaves
decode tokenizing as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
Validated on the 4P4D kit: ~1 ms less per decode step, GSM8K 0.990 and
real MTP acceptance 0.780 (BF16: 0.975-0.985, 0.761). MTP_EXPERTS=bf16
keeps the checkpoint's BF16 experts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…mbine + FP8 quant)

With the quantized combine (PBM + per-token FP8 o_proj input), the DCP
decode merge ran pack -> custom all-to-all -> unpack/combine/quant. A new
aiter kernel reads this rank's heads straight from the peers' registered
buffers, merges them with the LSE weights and writes the FP8 rows and
scales, so the received copy and the unpack launch go away. The pack
writes a split layout (data, then fp32 LSE) for 16-byte loads.

Matches the RCCL path to FP8 rounding (4.6e-6 of codes differ); merge
microbenchmark at 160 rows 44.7 -> 38.1 us per layer; decode step
-0.2..-0.6 ms (B16-64); GSM8K 0.980.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…de container

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…skips ATOM

The Rust router side of two Python router changes (f7e46219, 91910e57):

- protocol.rs: the ATOM prefill leg asks for return_token_ids, and the
  hand-off passes the returned prompt ids to the decode in
  kv_transfer_params['prompt_token_ids'], so ATOM's decode skips rendering
  the chat template and tokenizing the same prompt (~0.25 s at 130K tokens).
  A reply without ids leaves the decode tokenizing as before.
- handlers.rs / metrics.rs: the frontend /metrics scrapes only engines it
  reads something from (sglang, vllm). An ATOM scrape only cost its API
  process a /metrics render on top of the client's own scrapes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…4045 settings

The kit's decode kept its own settings and its CPU tier was LMCache; this
takes the rest of ATOM's GLM-5.2 AgentX recipe and InferenceX PR #4045's 1P1D:

- decode: mem util 0.94, --max-num-batched-tokens 2048 (DECODE_MAX_BATCHED_TOKENS)
  and AITER_REUSE_IDENTICAL_COMM_GROUPS=1, ~196K KV blocks instead of ~156K,
  which moves the AgentX decode-prefix cliff from about C85 to C105.
- MC_NUM_QP_PER_EP=1 on every Mooncake peer and ATOM's default 16 send
  workers (PREFILL_SEND_WORKERS empty; 4 stays the fallback of gotcha 13).
- PREFILL_CPU_TIER=store (default): engine/store.sh starts a Mooncake Store
  master and one 768 GiB owner per NUMA node on the prefill's rails, backed
  by THP without root; the prefill offloads to it with one load worker.
  lmcache keeps the previous LMCache tier, off drops the CPU tier.
- TOPK_FORCE_PATH=one and --timeout-keep-alive 900 on both legs.
- ROUTER_BACKEND defaults to rust.

Graph sizes, max-num-seqs and the MXFP4 MTP experts stay the kit's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…to HBM do not land

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
… kit

Patches (deploy/docker/patches/atom_nightly/):
- patch_aiter_custom_alltoall_combine.py is merged into
  patch_aiter_custom_alltoall.py: one set of edits and one rebuild of
  module_custom_all_reduce, which now always clears the JIT build dir. The
  combine patch anchored on text the other one inserted, so the build
  depended on the glob order (bash under en_US.UTF-8 sorts _combine first).
  The patched sources are as before, plus the next item.
- Both Python entries refuse world sizes other than 2, 4 and 8, the only
  ones the kernels are instantiated for (should_custom_ag allows 6).
- patch_aiter_glm52_decode_gemm_rows.py fails the build when a table aiter
  merges it with already holds one of its keys; aiter would raise at engine
  start instead.
- check_dcp_merge.py: GPU check of both kernels against RCCL (bit-exact
  all-to-all, merge up to FP8 rounding, eager and from a CUDA graph), to
  run after a base move.
- README.md and patch.upstream.status.md list the new patches with their
  contract, drop conditions and upstream status (none filed yet), and the
  per-request context gauges the GC fix turns off.
- ruff format (0.13.0) on the PR's Python files; the bmm TUNED table stays
  compact inside fmt: off.

Router: the Python ATOM hand-off leaves out an empty or negative prompt-id
list, as the Rust one does; token_ids.py no longer says ATOM lacks
return_token_ids.

4P4D kit:
- DECODE_GRAPH_STEP is gone (no gain measured; 0 looped forever), and
  PREFILL_EXTRA_ENV joins DECODE_EXTRA_ENV, e.g. for the gauges.
- The JIT cache volume name carries the leg's --max-num-batched-tokens,
  which ATOM's compile cache does not key on.
- up.sh refuses a decode budget below (MTP_K + 1) x 2 x CONC; store.sh
  checks THP and each NUMA node's free memory before starting the owners.
- README and the manual walkthrough describe the new defaults: validation
  and reference numbers with the Store, gotchas 8, 11, 13 and 14, the DCP
  prefill's validated command with PREFILL_CPU_TIER=lmcache, max-num-seqs
  among the settings not taken from PR #4045, no machine names.

Checked: pytest tests/unit/{router,common,server,engine} 909 passed; ruff
0.13.0 format and check; shellcheck; all nightly patches applied to
rocm/atom-dev:nightly_202610131505 in both glob orders (a second run is a
no-op, the module rebuilds); check_dcp_merge.py passes on 4 MI300X GPUs and
fails with a zero tolerance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
InferenceX does not allow MXFP4 for the MTP draft, so the MTP layer 78
stays BF16 as in the checkpoint and MTP_EXPERTS goes away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
C56-C112 re-run on the Store after dropping the MXFP4 MTP experts:
1.4-2.5% below the MXFP4 runs (longer decode steps, ~198K instead of
~203K KV blocks) and still 5.0-23.8% above InferenceX PR #4045.
GSM8K 0.975 with real acceptance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
…s as Mooncake offload

Second round of review follow-ups at 41bd647.

4P4D kit:
- up.sh refuses PREFILL_PP=1 with PREFILL_CPU_TIER=store. ATOM's Mooncake
  Store offload rejects decode context parallelism at startup
  (kv_transfer/offload/mooncake_store/config.py), so the DCP prefill died
  after store.sh had mounted 2 x 768 GiB. env.sh, the README and the manual
  now say the DCP prefill takes the LMCache tier.
- agentx.sh writes the AgentX offload metadata from PREFILL_CPU_TIER. It
  only looked at PREFILL_OFFLOAD_GB, so Store runs (the default) were
  labelled LMCache and PREFILL_CPU_TIER=off runs DRAM offload. The Store is
  reported as KV_OFFLOAD_BACKEND=mooncake with mooncake_master's version, as
  in InferenceX PR #4045's results.
- Numbers after dropping the MXFP4 MTP experts: ~198.5K KV blocks (+24%),
  the decode-prefix cliff at about C100, and the manual's C56-C112
  throughput (32925 / 40075 / 46234 / 48154). The README no longer quotes
  the MXFP4 MTP runs; the LMCache comparison is labelled as an earlier image
  and kit.

Patches: check_dcp_merge.py runs on 4 or 8 GPUs only. On 2 GPUs a rank
merges 32 of GLM-5.2's 64 heads, more than the one-kernel merge takes, so
its merge cases failed by construction.

Router: comments say ATOM returns the prompt ids at the top level for
completions too.

Checked: up.sh dry run with a stub docker (PREFILL_PP=1 with the Store
refused; with lmcache or off, and PREFILL_PP=4, past the check); the
agentx.sh offload lines for every tier pass InferenceX 918524ff's
aggregation checks; check_dcp_merge.py exits on 2 ranks before touching a
GPU; pre-commit on the changed files, shellcheck, cargo test and clippy,
pytest of the touched router tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
@ChengYao-amd
ChengYao-amd force-pushed the dev/yaoc/atom-pr4045-opt-20261020 branch from 96a3af8 to 3424bf4 Compare October 22, 2026 06:55
ChengYao-amd and others added 3 commits October 22, 2026 10:45
…rashing

`cargo test` has died with SIGSEGV on every CI run since ubuntu-latest moved
to Ubuntu 26.04. gcc 15 there defaults to -march=x86-64-v3, so libzmq's copy
of its 64-byte-aligned command_t becomes a `vmovdqa ymm` store into a queue
chunk that zeromq-src allocates with plain malloc (16-byte aligned, because
it never defines HAVE_POSIX_MEMALIGN). It faults on the first zmq_connect,
i.e. in the kv_event subscriber tests. Ubuntu 24.04's gcc 13 targets plain
x86-64 and never emitted the aligned AVX store, which hid the bug.

Define HAVE_POSIX_MEMALIGN (and the cache-line size it aligns to) for the
x86_64 Linux C++ build through rust/.cargo/config.toml, which `cd rust &&
cargo build` in the Dockerfiles also picks up.

Verified on the ubuntu-26.04 runner: fmt, clippy and the full `cargo test`
pass 10 times out of 10 (10/10 crashed before); 24.04 is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
The agent_sys job has failed 24 env_mgr tests on every CI run since
ubuntu-latest moved to Ubuntu 26.04, whose runner image ships /usr/bin/bwrap
(24.04 did not). `select` prefers bubblewrap whenever it is on PATH, and
bubblewrap confines by exec: `apply` is a no-op in-process, and an ungranted
path inside the bwrap'd child lands on bwrap's tmpfs instead of returning
EACCES. Every denial those tests assert is Landlock's, so they ran against
the wrong mechanism. The sandbox itself is not weakened.

- conftest: an autouse fixture reports bwrap as absent to `select` when the
  kernel also has Landlock; `run_confined` reads the probe at call time so
  its forked child sees the same answer. `availability` still reports the
  real machine, and bubblewrap's branch keeps its fake-binary tests.
- test_wrap_argv_refuses_when_the_binary_vanished: after deleting the fake
  bwrap, PATH is only the emptied directory, not the original PATH that may
  hold a real bwrap.

Reproduced in an ubuntu:26.04 container with bubblewrap: the 24 CI failures
all pass after this; without bwrap the full suite is unchanged (2436 passed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
With `cargo test` no longer crashing, the rust job on Ubuntu 26.04 now
reaches the dsv4 parity step and fails: "sglang is installed but has no dsv4
encoder". The step used the runner's system Python, which is 3.14 there.
sglang 0.5.20+ ships only cp310-cp313 wheels, so `pip install sglang` falls
back to 0.5.9, the last release with a pure-Python wheel or sdist, from
before the dsv4 encoder existed. 24.04's Python 3.12 got the current release.

Pin 3.12 with setup-python for this step (0.5.22 dropped cp310, so the 3.10
the other jobs use would not do either).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: YaoCheng <Cheng.Yao2@amd.com>
@xiaobochen-amd
xiaobochen-amd merged commit adbb9dc into main Oct 23, 2026
9 of 16 checks passed
llying-001 added a commit that referenced this pull request Oct 23, 2026
…compares with our 4+4 C80 (ATOM +25% at equal interactivity)

Signed-off-by: liyingli <liyingli@amd.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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.

2 participants