Skip to content

feat: add committed_device_memory() API (L1 byte counter + L2/L3 query) - #1592

Merged
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
sunghajung6688:cdm
Jul 30, 2026
Merged

ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
sunghajung6688:cdm

Conversation

@sunghajung6688

@sunghajung6688 sunghajung6688 commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

L1: MemoryAllocator tracks committed bytes (ptr->size map + counter). L2: ChipWorker.committed_device_memory property + C ABI committed_device_memory_ctx. L3: control_committed_device_memory control op (mirrors control_malloc across the endpoint hierarchy) + Orchestrator::committed_device_memory + nanobind binding; Python _CTRL_COMMITTED_DEVICE_MEMORY, child-handler, Worker facade L3 branch, orchestrator wrapper. Lets downstream runtimes read simpler's authoritative HBM total (which aclrtGetMemInfo may undercount).

Tests: tests/ut/cpp/common/test_memory_allocator.cpp (C++), tests/ut/py/test_worker/test_committed_memory_sim.py (Python sim).

Closes #1457

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f64bdcc-db48-4e0a-a004-82f558efb29e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds committed device-memory accounting to host and simulation allocators, runtime C APIs, ChipWorker, hierarchical worker control, and Python worker interfaces. Unit and simulation tests cover allocation changes, cleanup, and level-specific behavior.

Changes

Committed device memory

Layer / File(s) Summary
Allocator byte accounting
src/common/platform/..., src/a2a3/..., src/a5/...
Allocators track pointer sizes and maintain committed byte totals across allocation, freeing, finalization, and destruction.
Runtime memory bridge
src/common/worker/..., src/common/platform/..., python/bindings/task_interface.cpp, python/simpler/task_interface.py
Runtime accessors and C APIs expose allocator committed bytes through ChipWorker.
Hierarchical worker query
src/common/hierarchical/..., python/simpler/worker.py, python/simpler/orchestrator.py, python/bindings/worker_bind.h
Mailbox control, orchestrator forwarding, and Python APIs query committed memory for local and next-level workers.
Allocator and worker validation
tests/ut/cpp/..., tests/ut/py/test_worker/test_committed_memory_sim.py
Tests validate counters, cleanup behavior, allocation changes, and level-specific worker responses.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Worker
  participant Orchestrator
  participant ChipWorker
  participant MemoryAllocator
  Worker->>Orchestrator: committed_device_memory(worker_id)
  Orchestrator->>ChipWorker: query committed memory
  ChipWorker->>MemoryAllocator: committed_bytes()
  MemoryAllocator-->>ChipWorker: byte total
  ChipWorker-->>Orchestrator: byte total
  Orchestrator-->>Worker: byte total
Loading

Poem

A rabbit counts each byte with care,
From little buffers tucked somewhere.
It hops through workers, queues, and mail,
Reports the sum without fail.
Allocate, free—then numbers clear!
“Hop-hop!” says Bunny, “memory’s here!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the new committed_device_memory API across L1-L3 layers.
Description check ✅ Passed The description accurately matches the changeset and describes the allocator, worker, and test additions.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/simpler/worker.py`:
- Around line 5809-5826: Update committed_device_memory to explicitly raise
NotImplementedError with a message matching “level-3” when a non-level-2 worker
has no chip workers or device IDs, before calling _check_chip_worker_id.
Preserve the existing level-2 behavior and normal level-3 forwarding through
_orch.committed_device_memory for configured chip workers.

In `@tests/ut/py/test_worker/test_committed_memory_sim.py`:
- Around line 75-79: Update the L3 NotImplementedError test around Worker and
committed_device_memory so it uses a child-backed L3 configuration with a worker
ID sourced from the child IDs, ensuring the assertion exercises L3 query
forwarding rather than the _chip_shms worker-ID range check. Preserve the
expected NotImplementedError match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c796d20e-72ae-47f2-8cef-45abbc6d7466

📥 Commits

Reviewing files that changed from the base of the PR and between f260e7d and 5a7c8c6.

📒 Files selected for processing (23)
  • python/bindings/task_interface.cpp
  • python/bindings/worker_bind.h
  • python/simpler/orchestrator.py
  • python/simpler/task_interface.py
  • python/simpler/worker.py
  • src/a2a3/platform/onboard/host/memory_allocator.cpp
  • src/a5/platform/onboard/host/memory_allocator.cpp
  • src/common/hierarchical/orchestrator.cpp
  • src/common/hierarchical/orchestrator.h
  • src/common/hierarchical/worker_manager.cpp
  • src/common/hierarchical/worker_manager.h
  • src/common/platform/include/host/memory_allocator.h
  • src/common/platform/onboard/host/c_api_shared.cpp
  • src/common/platform/onboard/host/device_runner_base.h
  • src/common/platform/sim/host/c_api_shared.cpp
  • src/common/platform/sim/host/device_runner_base.h
  • src/common/platform/sim/host/memory_allocator.cpp
  • src/common/worker/chip_worker.cpp
  • src/common/worker/chip_worker.h
  • src/common/worker/pto_runtime_c_api.h
  • tests/ut/cpp/CMakeLists.txt
  • tests/ut/cpp/common/test_memory_allocator.cpp
  • tests/ut/py/test_worker/test_committed_memory_sim.py

Comment thread python/simpler/worker.py
Comment thread tests/ut/py/test_worker/test_committed_memory_sim.py
@sunghajung6688

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sunghajung6688
sunghajung6688 force-pushed the cdm branch 2 times, most recently from fa5c72b to 3f4c177 Compare July 30, 2026 07:15
@ChaoZheng109

ChaoZheng109 commented Jul 30, 2026 •

Copy link
Copy Markdown
Collaborator

Docstring: "excludes profiling buffers" is platform-dependent and wrong for a2a3

(Correcting my earlier comment — the runtime call chain I cited was inaccurate; the conclusion holds, but the mechanism and, more importantly, the a2a3-vs-a5 split are below.)

committed_device_memory() docstrings (in pto_runtime_c_api.h, python/bindings/task_interface.cpp, python/simpler/worker.py) claim it "Excludes HCCL/VMM comm windows and profiling buffers." The "profiling buffers" half is platform-dependent, and wrong on a2a3:

a2a3 — DFX buffers ARE counted. init_pmu / init_dep_gen / init_scope_stats / init_args_dump in src/a2a3/platform/onboard/host/device_runner.cpp pass alloc_cb = [this](size_t s){ return mem_alloc_.alloc(s); } to each collector, so their device buffers go through the tracked MemoryAllocator.

a5 — DFX buffers are NOT counted. src/a5/platform/onboard/host/device_runner.cpp wires every collector (l2_swimlane, dump, pmu, scope_stats, dep_gen) to prof_alloc_cb, which is a raw rtMalloc ("a5 onboard goes directly through CANN runtime — no per-allocation tracking"). These bypass mem_alloc_ entirely.

So the same docstring is correct for a5 and wrong for a2a3. Please either make the two platforms consistent, or state the exclusion is platform-specific.

These allocations also happen at runtime, not just init (a2a3 path). A background thread started in ProfilerBase::start() runs mgmt_replenish_loop() (profiler_base.h), which each iteration calls replenish_recycled_pools() → for any (kind, shard) whose recycled_count < clamped_recycled_warm_target → BufferPoolManager::allocate_recycled_batch() → alloc_and_register_block() → ops_.alloc (= mem_alloc_.alloc on a2a3). So on a2a3 the committed counter genuinely grows during serving as the recycled pools warm up to their (bounded) target — it is not a one-shot init cost.

Consequence for the consumer (#1457). committed_device_memory() is a live, point-in-time value, not a high-water-mark. On a2a3 it will keep rising after warm-up as DFX pools fill; on a5 it never reflects DFX at all. A KV-cache size computed from a single warm-up reading can therefore still under-account — the runtime-collision failure mode (2) in #1457. Worth deciding whether a peak_committed_bytes() high-water-mark (and platform-consistent DFX tracking) is in scope.

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Coverage scope of committed_device_memory() — points to be aware of in this PR (completeness can follow up)

committed_bytes() counts only device memory routed through the host-side MemoryAllocator (mem_alloc_.alloc). I audited every device-allocation site in src/ and classified them. This PR is fine to land as the L1/L2/L3 plumbing, but the number it reports is a subset of simpler's real committed HBM. Flagging the gaps so they're a conscious decision, not a silent one — the missing pieces can be closed in a later PR.

Counted today (through mem_alloc_): the three arenas (gm_heap / gm_sm / runtime_arena_pool — the ~1.1GB in #1457), user tensors, chip-callable buffers, AICore reg buffers, host-side api->device_malloc mid-run pooled-arena growth, and a2a3 DFX collectors.

Not counted (bypass mem_alloc_):

# Site Memory Recommendation
1 comm_hccl.cpp (a2a3+a5): aclrtMalloc (CommContext) + aclrtMallocPhysical/aclrtReserveMemAddress (VMM comm windows / peer buffers) Can be large under distributed serving Follow-up. Do not force through mem_alloc_ — the VMM reserve/map path is a different lifecycle than rtMalloc and HCCL windows have RDMA alignment/registration constraints. Add a comm_committed_bytes_ counter in the comm layer and sum it at the query layer.
2 a5 device_runner.cpp prof_alloc_cb = raw rtMalloc for all DFX collectors DFX pools Should fix (small, ideally in this PR). a2a3 already routes DFX through mem_alloc_; a5 just needs the same capturing-lambda alloc_cb/free_cb. Removes the platform inconsistency and matches the docstring.
3 load_aicpu_op.cpp DeviceBuf::aclrtMalloc (inner-.so upload staging) Transient, RAII-freed at init Leave out. Counting a short-lived init buffer would make the number spike-then-drop and misrepresent steady state.
4 AICPU-side aicpu_device_malloc (halMemAlloc, device-tier) Device-side dynamic alloc Out of scope (architectural). The AICPU cannot reach the host MemoryAllocator; tracking would need a device-side counter mirrored back via shared memory. No runtime caller found in trb today.
5 force_reset_device 64-byte probe (device_runner.cpp) 64 B, immediately freed Leave out. Health-check poke, not real footprint.

Framing recommendation: treat committed_device_memory() as "sum of simpler's committed-HBM counters" rather than "one allocator's byte count". Today that sum has one term (mem_alloc_); #1 adds a comm term and #4 could add a device-side term later. For this PR the minimal asks are: (a) do #2 or at least document the a2a3/a5 asymmetry, and (b) have the docstring state that comm/VMM windows are excluded (already partly there) and that the value is a live snapshot, not a peak — so downstream (#1457) knows it must add comm accounting and headroom itself until the follow-up lands.

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Two smaller items still open on the latest revision

1. (should fix) The L2 sim test doesn't verify the arena is counted — the one path #1457 actually cares about.

tests/ut/py/test_worker/test_committed_memory_sim.py:

baseline = worker.committed_device_memory()
assert isinstance(baseline, int)
assert baseline >= 0  # arenas are committed during init

The comment claims "arenas are committed during init", but >= 0 is trivially true and passes even if baseline == 0. The later assertions (after_alloc >= baseline + size, after_free <= after_alloc - size) only verify delta-tracking of the explicit malloc/free — they'd pass just as well if the arena were invisible. So the core issue-driving path (simpler's own arena is included in the reported number) is unverified. Suggest asserting baseline > 0 (the three arenas are committed by init()), or explicitly triggering an arena commit and asserting the counter rose.

2. (consider) The L3 NotImplementedError message conflates "no children" with "aggregation not implemented".

python/simpler/worker.py:

if not self._chip_shms:
    raise NotImplementedError("committed_device_memory is not yet aggregated across level-3 forked workers")
self._check_chip_worker_id(worker_id)
return int(self._orch.committed_device_memory(worker_id))

The exception fires only when there are no forked chip workers (nothing to aggregate). When there are children — the case where aggregation would actually matter — it silently returns a single worker's bytes (the docstring already tells the caller to "sum across worker_ids"). So the wording is backwards relative to the behavior. Suggest something like "level-3 committed_device_memory requires at least one forked chip worker" (or, if a true aggregate is intended, actually sum across worker_ids here instead of leaving it to the caller).

@sunghajung6688
sunghajung6688 force-pushed the cdm branch 8 times, most recently from 7821911 to eb6773b Compare July 30, 2026 09:23
Downstream runtimes size their caches from `aclrtGetMemInfo`, which can
undercount simpler's own device HBM (the rtMalloc-backed ring-heap arenas
are invisible to it). Expose simpler's authoritative committed-HBM total so
consumers can subtract it explicitly. Closes hw-native-sys#1457.

The value is plumbed through all three tiers:

- L1 (MemoryAllocator): replace the pointer set with a ptr->size map and a
  running committed-bytes counter, exposed as `committed_bytes()`.
- L2 (per-chip): `DeviceRunnerBase::committed_device_memory()` surfaces the
  counter over the C ABI (`committed_device_memory_ctx`) and through
  `ChipWorker`, with a nanobind read-only property.
- L3 (hierarchical): a `control_committed_device_memory` control op mirrors
  `control_malloc` across the endpoint hierarchy (`Orchestrator` +
  `worker_manager`); Python adds the `_CTRL_COMMITTED_DEVICE_MEMORY`
  child-handler, and the `Worker` facade reads in-process at level 2 and
  forwards to the forked chip child at level 3.

Scope: counts memory routed through the host `MemoryAllocator` (user tensors
+ pooled arenas + runtime buffers); excludes HCCL/VMM comm windows.

Tests:
- tests/ut/cpp/common/test_memory_allocator.cpp — C++ byte-counter unit test
- tests/ut/py/test_worker/test_committed_memory_sim.py — Python sim, L2 query
  and L3 not-implemented guard
@ChaoZheng109
ChaoZheng109 merged commit ce81c97 into hw-native-sys:main Jul 30, 2026
32 of 33 checks passed
ChaoZheng109 added a commit that referenced this pull request Jul 31, 2026
…evice_memory counts them (#1602)

The a5 profiling collectors (l2_swimlane, args_dump, pmu, scope_stats,
dep_gen) allocated their device buffers via raw rtMalloc (prof_alloc_cb /
prof_free_cb), bypassing the per-runner MemoryAllocator. As a result their
HBM was invisible to committed_device_memory() (added in #1592), while a2a3
already routes DFX through mem_alloc_ and counts it — a platform-inconsistent
gap the #1592 docstring ("excludes HCCL/VMM comm windows", implying profiling
buffers ARE counted) now describes correctly for both platforms.

Replace the raw-rtMalloc callbacks with capturing `alloc_cb` / `free_cb`
lambdas that call mem_alloc_.alloc / mem_alloc_.free, matching a2a3's
init_pmu / init_dep_gen / init_scope_stats. Both allocators use the same
rtMalloc(RT_MEMORY_HBM) underneath, so the device-buffer properties are
unchanged; the only effect is that DFX buffers are now tracked and counted.

Completes the a5 coverage deferred from #1592 to keep that PR single-purpose.
Onboard a5 DFX scene tests (test_pmu / test_dep_gen / test_scope_stats /
test_args_dump) exercise the new alloc path.
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.

[Feature] Expose simpler's committed NPU memory (rtMalloc pool is invisible to aclrtGetMemInfo)

2 participants