Skip to content

Fix: honor ASCEND_RT_VISIBLE_DEVICES at direct-HAL device-id sites - #1600

Merged
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
lterrac:fix/aicore-regs-visible-devices
Aug 2, 2026
Merged

Fix: honor ASCEND_RT_VISIBLE_DEVICES at direct-HAL device-id sites#1600
ChaoZheng109 merged 1 commit into
hw-native-sys:mainfrom
lterrac:fix/aicore-regs-visible-devices

Conversation

@lterrac

@lterrac lterrac commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Under ASCEND_RT_VISIBLE_DEVICES isolation a launcher renumbers the visible cards to logical 0..N-1 and hands simpler logical ids. ACL honors the variable for its own APIs (aclrtSetDevice, rtMalloc/rtMemcpy, streams, HCCL), but the chip's direct driver calls bypass ACL and index the driver-visible space. Passing them the ACL-logical id targets the wrong device: the AI-core register map and the AICPU topology probe — both on the chip-init path (init_aicore_register_addresses, probe_aicpu_topology) — fail with halMemCtl rc=42, while the ACL compute path works.

Fix

Add common/acl_hal_device.h::acl_to_hal_device_id, a single translation that carries the contract on its declaration: direct hal* / dsmi_* calls take the driver-visible id and must go through it; ACL/rt entry points take the logical id and must not. It validates the whole visible list (any malformed token → logged identity fallback) and is the identity when the variable is unset.

Route every direct-driver device-id site through it, on both arches:

  • a2a3: halMemCtl (register map) + halGetDeviceInfo* (AICPU topology).
  • a5: halResMap (register map) + halGetDeviceInfo* / dsmi_get_device_info (AICPU topology).

Verification

  • A new VIS-isolation scene test on both st-onboard jobs runs dummy_task under ASCEND_RT_VISIBLE_DEVICES with pytest --device 0, so CI exercises the chip-init translation on a real a2a3 and a real a5 node every run (pre-fix it fails with rc=42; post-fix it passes). Permanent regression barrier for anyone who adds a direct-HAL call site and forgets to translate.
  • Manually reproduced on a2a3 (VIS=6, logical 0 → physical 6): rc=42 gone, output identical to the legacy physical-id path.
  • The parser's malformed / out-of-range fallback policy is locked down by tests/ut/cpp/common/test_acl_hal_device.cpp.

Caveat

The a5 dsmi_get_device_info fallback in query_cpu_topo is only reached when halGetDeviceInfoByBuff fails first, so the VIS ST does not cover it. It is translated for consistency but assumes DSMI shares the HAL's driver-visible id space — flagged with an inline NOTE. If that turns out wrong it will surface on a real a5 node and can be revisited then.

@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: e36393f0-ebe3-449f-96b1-85d5c9e6c3b1

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

The host occupancy and AICore register paths now translate ACL logical device IDs through ASCEND_RT_VISIBLE_DEVICES before issuing HAL queries, with identity fallback for unset, malformed, invalid, or out-of-range mappings.

Changes

Device ID translation

Layer / File(s) Summary
Translate device IDs for HAL queries
src/a2a3/platform/onboard/host/aicpu_topology_probe.cpp, src/a2a3/platform/onboard/host/host_regs.cpp
Both paths parse ASCEND_RT_VISIBLE_DEVICES, map logical IDs to physical IDs, retain identity fallback, and use the translated IDs for occupancy, core-mask, and address-mapping HAL operations.

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

Poem

A rabbit hops through device lanes,
Mapping numbers, easing pains.
Visible cards are neatly found,
HAL receives the proper bound.
“Hop-hop!” says Bun, “the IDs align!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 main change: translating device IDs for direct HAL calls under ASCEND_RT_VISIBLE_DEVICES.
Description check ✅ Passed The description accurately explains the device-ID remapping problem, affected paths, implementation, and verification.

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: 1

🤖 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 `@src/a2a3/platform/onboard/host/host_regs.cpp`:
- Around line 54-79: The hal_physical_device_id parsing in
src/a2a3/platform/onboard/host/host_regs.cpp:54-79 and the corresponding mapping
logic in src/a2a3/platform/onboard/host/aicpu_topology_probe.cpp:65-90 must
validate the entire ASCEND_RT_VISIBLE_DEVICES list before remapping. For each
strtol result, require a delimiter or end-of-string, reject ERANGE and negative
values, and continue validating tokens even after finding the selected logical
ID; return the original logical ID whenever any token is invalid.
🪄 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: 2479d69e-04c1-4ade-aaa9-6c0c5d22351b

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd6c3b and bb8a0c2.

📒 Files selected for processing (2)
  • src/a2a3/platform/onboard/host/aicpu_topology_probe.cpp
  • src/a2a3/platform/onboard/host/host_regs.cpp

Comment thread src/a2a3/platform/onboard/host/host_regs.cpp Outdated
@lterrac
lterrac force-pushed the fix/aicore-regs-visible-devices branch from bb8a0c2 to cc6a7e8 Compare July 30, 2026 12:20
@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Could you give some background on ASCEND_RT_VISIBLE_DEVICES here — which caller
sets it, and why? Nothing in this repo sets it, and simpler's own fork-per-chip
path passes the real device id instead, so I can't tell from the description
whether this needs fixing on the simpler side. Some context on how the variable
ends up set would let me judge that.

@ChaoZheng109 ChaoZheng109 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The direction is right and the diagnosis is convincing: HAL sits below ACL and has
no notion of ASCEND_RT_VISIBLE_DEVICES, so a call site that hands an ACL logical
id to halMemCtl / halGetDeviceInfo* targets the wrong device under VIS
isolation. I checked every dlsym'd HAL symbol on the a2a3 side and the three
sites this PR converts are all of them — profiling_copy.cpp (SVM/rtMemcpy)
and the remaining init_* in device_runner.cpp go through ACL/rt and correctly
keep the logical id. Within a2a3, the coverage is complete.

Requesting changes on two blocking points plus a missing test; the rest is in the
inline comments.


1. Blocking — a5 has the same defect and this PR does not touch it

The title and description say "AI-core register path" without qualifying the arch,
but only a2a3 is changed. a5 has the identical class of bug:

  • src/a5/platform/onboard/host/host_regs.cpp:62
    halFunc(static_cast<uint32_t>(device_id), &map_info, &map_addr, &len)halResMap
  • src/a5/platform/onboard/host/aicpu_topology_probe.cpp:103halGetDeviceInfo
  • src/a5/platform/onboard/host/aicpu_topology_probe.cpp:116 and :122halGetDeviceInfoByBuff
    and the dsmi variant, both inside query_cpu_topo

Note that a5 has one more call site than a2a3: query_cpu_topo has no a2a3
counterpart, so porting the a2a3 change verbatim would silently leave it behind.

Could you either extend the fix to a5, or state explicitly in the description why
a5 is out of scope and qualify the title accordingly? As it stands a reader will
assume both arches are covered.

2. Blocking — the comm_hccl.cpp precedent in the description doesn't hold

comm_hccl.cpp already accounts for this remapping; these two paths did not.

I couldn't reproduce this claim. a2a3's comm_hccl.cpp contains no mention of any
VISIBLE_DEVICES variable at all. The only two mentions in the tree are a5's
comm_hccl.cpp:569 and :987, and they differ from this PR on both counts:

  • they refer to ASCEND_VISIBLE_DEVICES (container-level, Ascend Docker
    Runtime), not ASCEND_RT_VISIBLE_DEVICES (process-level, ACL) — different
    variables with different scopes;
  • they describe an unresolved limitation, not an id translation:
    aclrtDevicePeerAccessStatus cannot resolve a peer that a fork'd single-device
    process never aclrtSetDevice'd, so the code degrades to a warning plus a
    file_barrier (see #1018). There is no logical→physical mapping there to
    follow as precedent.

Since this sentence is the PR's main "there is prior art for this" argument, could
you reword it? Otherwise the next reader will go looking for a reference
implementation in comm_hccl.cpp that isn't there.

3. Missing test — and the harness for it already exists

All the new risk lives in hal_physical_device_id, a pure function whose body is
almost entirely defensive parsing (6x, ERANGE, negatives, out-of-range index,
empty). None of it is covered.

That's fixable at near-zero cost: tests/ut/cpp/CMakeLists.txt:745-761 already
defines test_a2a3_aicpu_affinity_select, which compiles
aicpu_topology_probe.cpp directly
and is labelled no_hardware. No new target,
no device needed — the only blocker is that the helper is static, so it has
internal linkage and the test can't see it. Extracting it into a shared header
(see my inline comment on the duplication) solves both at once.

Suggested cases for tests/ut/cpp/a2a3/test_aicpu_affinity_select.cpp:
unset · "6" · "4,5,6,7" at logical 2 · "6x" → identity · "4,5" at logical 7
(out of range) → identity · empty string · leading/embedded spaces.

Per .claude/rules/discipline.md §3 a bugfix should leave a regression barrier
behind. The device-level repro genuinely needs VIS-isolated hardware, but the
parser does not, and the parser is where the new failure modes are.

4. Nit — worth a line in the troubleshooting docs

"ACL compute path works, but halMemCtl returns rc=42" is a failure signature
that is essentially impossible to derive from first principles. Two existing
places would take it:

  • docs/troubleshooting/device-error-codes.md — one row for the signature
  • docs/dynamic-linking.md:127 already lists
    halMemCtl | Onboard host_regs.cpp | Register address mapping | a2a3 only
    a parenthetical that HAL call sites take driver-visible ids, not ACL ids, would
    land naturally there

Per .claude/rules/doc-consistency.md §4 this should ride in the same commit.

// back to the physical id the HAL expects; identity when the var is unset
// (legacy / no isolation). Without this, a chip forked under VIS remapping
// queries the wrong physical device's registers and halMemCtl fails (rc=42).
static int64_t hal_physical_device_id(int64_t logical_id) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This 40-line function is duplicated verbatim into aicpu_topology_probe.cpp:66,
and both files compile into the same host_runtime target. If a5 gets the same
fix (see the review body), it becomes four copies of one parser.

Could this move to a single shared header both arches can include? That also
un-blocks the unit test — as a file-local static it has internal linkage, so
test_a2a3_aicpu_affinity_select (which already compiles this TU) cannot reach it.

While moving it, I'd ask for the interface itself to carry the rule, rather
than relying on each call site to remember. The bug this PR fixes is precisely
"one call site forgot", and the current shape — translate inline, at each site —
has the same failure mode for every HAL call site added in the future. Two cheap
things would go a long way:

  • a name that states the conversion rather than the destination, e.g.
    acl_to_hal_device_id(), so hal_fn(acl_to_hal_device_id(device_id)) reads as
    a layer crossing at the point of use;
  • a doc comment on the declaration stating the contract explicitly: every direct
    hal* call takes a driver-visible id and must go through this; ACL/rt entry
    points (aclrtSetDevice, rtMalloc, rtMemcpy, stream APIs) take the logical
    id and must not.

That way the next person adding a dlsym'd HAL call has the rule in front of them
instead of having to rediscover it via an rc=42.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — moved the parser into a shared common/acl_hal_device.h as acl_to_hal_device_id; both a2a3 files and a5 now route through it, and the declaration carries the contract (direct hal*/dsmi_* take the driver-visible id and must go through it; aclrtSetDevice/rtMalloc/rtMemcpy/stream APIs take the logical id and must not). Now reachable by a unit test: tests/ut/cpp/common/test_acl_hal_device.cpp.

}
errno = 0;
char *end = nullptr;
long val = std::strtol(p, &end, 10);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

.claude/rules/codestyle.md §8 asks host-side code (src/{arch}/**/host/ included)
to be modern C++ and reserve C idioms for the host↔device POD boundary, which this
isn't. The raw const char * walk plus std::strtol plus errno is squarely the
C shape the rule steers away from.

std::string_view + std::from_chars expresses the same validation — including the
"token must end at a delimiter" check, which from_chars gives you directly via the
returned ptr — in noticeably fewer lines, and drops the errno global entirely
(from_chars reports overflow through ec == std::errc::result_out_of_range).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — rewritten with std::string_view + std::from_chars in the shared header. The "token must end at a delimiter" check comes from the returned ptr, overflow from ec == std::errc::result_out_of_range; errno is gone.

struct AddrMapInPara in_map_para;
struct AddrMapOutPara out_map_para;
in_map_para.devid = device_id;
in_map_para.devid = phys_device_id;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

in_map_para.devid is now phys_device_id, but the EACCES retry warning below at
line 169 still prints the logical id:

LOG_WARN(
    "halMemCtl rc=13 (EACCES) on devid=%lld attempt %d/%d, retrying after %d ms",
    (long long)device_id, ...
);

Under VIS isolation — the exact scenario this PR targets — that line names a devid
that did not fail. It matters more than a usual logging nit here, because the
comment right above it carries physical-numbering field knowledge ("the failure
consistently lands on dev=11 (last die of last chip in the 8-11 range)"), which a
logical id would quietly invalidate.

Suggest printing phys_device_id, or both (devid=%lld (acl=%lld)).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the EACCES retry warning now names the driver-visible id that actually failed, with the acl id alongside: devid=%lld (acl=%lld).

// Reject no-digits, overflow, negative, or a token not terminated by a
// list delimiter / end-of-string (e.g. "6x").
if (end == p || errno == ERANGE || val < 0 || (*end != '\0' && *end != ' ' && *end != ',')) {
return logical_id;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The "validate the whole list, else don't remap at all" policy is a good call, and
the comment explains it well. My concern is that the fallback is completely silent.

If someone typos the variable, this returns the logical id and the process goes on
to hit the wrong device — reproducing the very rc=42 this PR exists to eliminate,
with nothing in the log pointing at the environment. A LOG_WARN naming the
offending value would turn a silent mis-target back into a two-second diagnosis.

Same for the out-of-range case at the end (physical_id still -1 because the list
is shorter than logical_id): it currently shares the identity return with
"variable unset", but semantically it's a definite misconfiguration, not a
no-isolation default. Worth distinguishing in the log.

And on the success path, a LOG_DEBUG("device id: acl %lld -> hal %lld") would make
the whole class of problem self-evident in any future bug report.

These are diagnostics only, no behavior gating, so .claude/rules/env-macro-gating.md
doesn't apply.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the shared helper now logs every non-remap path: LOG_WARN naming the offending value on a malformed list; a distinct LOG_WARN for the out-of-range (list-too-short) case, kept separate from the unset/no-isolation default; and LOG_DEBUG("device id: acl %lld -> hal %lld") on the success path.

return cached_fn;
}

// ASCEND_RT_VISIBLE_DEVICES remaps ACL logical device ids to a 0..N-1 range,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Minor wording: the value returned here is the driver-visible device id, which is
not necessarily the absolute physical id. If container-level ASCEND_VISIBLE_DEVICES
is also in play — a5's comm_hccl.cpp:569 documents that combination — the driver's
own device numbering is already remapped, so ASCEND_RT_VISIBLE_DEVICES indexes into
that set, not into npu-smi's numbering.

Doesn't affect correctness (indexing the driver-visible set is exactly what's needed),
but "driver-visible device id" would save the next person from comparing this value
against npu-smi output inside a container and concluding the mapping is broken.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed — the header doc now calls it the driver-visible id and notes it need not equal the absolute physical (npu-smi) id when a container-level ASCEND_VISIBLE_DEVICES is also remapping the driver's numbering.

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

Follow-up on point 4 of my review — my wording there ("one row for the signature")
was imprecise. To be concrete: no new doc file is needed, both places already
exist.

1. docs/troubleshooting/device-error-codes.md, the existing
### Host-side CANN codes section (line 105).

Not a row in that table — the table holds ACL_ERROR_RT_* 5xxxxx codes, whereas
rc=42 is a raw halMemCtl return, so it wouldn't fit the format. A short
paragraph after the table, in the same shape as the existing 507018 paragraph
below it, is the right form. The section's own preamble already frames it:

These come from the driver, not from the runtime. ... a CANN number can reach
you from output we do not wrap (a driver log, a raw ACL return)

A bare halMemCtl return is exactly that. Something along the lines of: under
ASCEND_RT_VISIBLE_DEVICES, halMemCtl / halGetDeviceInfo* return failures
(observed: rc=42) while every ACL/rt call on the same device succeeds — HAL
indexes by driver-visible id and does not honor the ACL remapping.
The
ACL-works-but-HAL-fails combination is the part that's hard to derive, so it's
worth saying explicitly.

2. docs/dynamic-linking.md:127 — the row is already there:

| halMemCtl | Onboard host_regs.cpp | Register address mapping | a2a3 only |

Only the last cell needs extending, to note that HAL call sites take the
driver-visible id rather than the ACL logical id. No new row, no new section.

Either one alone would do the job; (1) is the higher-value of the two since it's
the page someone lands on while actually debugging.

@lterrac

lterrac commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Could you give some background on ASCEND_RT_VISIBLE_DEVICES here — which caller sets it, and why? Nothing in this repo sets it, and simpler's own fork-per-chip path passes the real device id instead...

@ChaoZheng109 Good question — background below.

Who sets it, and why. Nothing in this repo sets it, and that's expected. Like CUDA_VISIBLE_DEVICES, it's set by whatever launches simpler to isolate NPUs, and the app is only meant to consume logical ids. Concrete setters:

On "the fork-per-chip path passes the real device id". Under VIS isolation the id we're handed is the logical one. VIS's whole job is to hide the physical numbering and renumber the visible cards to 0..N-1; the launcher gives us logical ids, so what fork-per-chip forwards is logical, not physical. That is correct for everything that goes through ACL.

Why it still needs fixing here. ACL reads the variable and remaps for its own APIs (aclrtSetDevice, rtMalloc/rtMemcpy, streams, HCCL) — those honor it. But the chip worker also makes direct driver calls that bypass ACL: halMemCtl/halResMap (AI-core register map) and halGetDeviceInfo* / dsmi_get_device_info (AICPU topology). Those index the driver-visible space, so handing them the ACL-logical id targets the wrong device. The "logical everywhere, ACL maps it" contract silently breaks at exactly these sites, because ACL is not in the loop for them.

Reproduced on a2a3 (single card, ASCEND_RT_VISIBLE_DEVICES=6, worker given logical 0):

  • aclrtSetDevice(0) → physical 6 ✓ (ACL honored it);
  • halMemCtl(devid=0) → queries physical 0, which this process never set up → halMemCtl rc=42 at init_aicore_register_addresses, and chip init fails.

With the translation, the same isolated launch produces output identical to the legacy (physical-id, no-VIS) path.

The helper is identity when the variable is unset, so it is a no-op for any non-isolated deployment (your "passes the real device id" case) and only engages once a launcher has actually renumbered the devices.

@lterrac
lterrac force-pushed the fix/aicore-regs-visible-devices branch 2 times, most recently from 80cbb07 to 79fed80 Compare July 31, 2026 12:18
@lterrac

lterrac commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

a5 now included — plus what's verified vs assumed

Following the "if a5 gets the same fix, four copies" point, I pulled the parser into the shared common/acl_hal_device.h and routed a5's direct-driver sites through it as well, so both arches (and any future one) share one implementation and one contract.

Verified on hardware (a2a3): reproduced the halMemCtl rc=42 under ASCEND_RT_VISIBLE_DEVICES=6 with the worker handed logical 0; the translation makes it disappear and the model produces output identical to the legacy physical-id (no-VIS) path.

Applied but NOT verified (a5): we don't have an a5 node, so the a5 changes are by analogy to the a2a3 fix:

  • halResMap (register map) and halGetDeviceInfo* (AICPU topology) are the same direct-HAL family as a2a3 → same reasoning, high confidence — but a5's register path uses a different HAL entry (halResMap) that we haven't watched fail-and-recover.
  • dsmi_get_device_info is driver-level and bypasses ACL, so it needs the same translation — but it additionally assumes DSMI indexes the same driver-visible id space as the HAL. DSMI carries its own logic/phy id concepts (dsmi_get_phyid_from_logicid); if that space differs, that one cast is wrong. Flagged inline.

Happy to go whichever way you prefer: (a) split a5 into a follow-up and land a2a3 alone; (b) drop the DSMI translation and leave a TODO if the id-space assumption is too shaky; or (c) keep as-is if someone can validate on an a5 box.

@lterrac

lterrac commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

@ChaoZheng109 every suggestions has been addressed. Let me know if this is ok for you or if anything else is needed from my side. As I was saying in the previous post, unfortunately I do not have direct access to an A5 :(

@lterrac
lterrac force-pushed the fix/aicore-regs-visible-devices branch from 79fed80 to d086ac1 Compare July 31, 2026 13:07
@ChaoZheng109

ChaoZheng109 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

unfortunately I do not have direct access to an A5 :(

That's not a blocker — CI does. The st-onboard-a5 job runs on a real a5 node,
same as st-onboard-a2a3. So instead of you validating a5 by hand, add an ST that
runs under VIS isolation on both arches and let CI do it, on every run.

Why this is cheap: no new test logic is needed. All the translated sites
(halMemCtl, halGetDeviceInfoByBuff, halGetDeviceInfo, and on a5 halResMap)
sit on the chip-init pathinit_aicore_register_addresses and
probe_aicpu_topology run every time an onboard chip comes up. So any onboard
scene test already exercises every one of them. All that's missing is one
invocation with the variable actually set. dummy_task is the smallest, and it
exists under both arches:

  • tests/st/a2a3/tensormap_and_ringbuffer/dummy_task
  • tests/st/a5/tensormap_and_ringbuffer/dummy_task

Following the existing CI step pattern, the key is variable gets the physical id,
--device gets logical 0
:

task-submit --timeout 900 --max-time 900 --device auto --device-num 1 \
  --env ASCEND_RT_VISIBLE_DEVICES="$TASK_DEVICE" \
  --run "python -m pytest tests/st/a2a3/tensormap_and_ringbuffer/dummy_task \
         --platform a2a3 --device 0 -v --pto-session-timeout 600"

and the a5 twin with --platform a5 and the a5 path. Pre-fix this fails with the
halMemCtl rc=42 you reported; post-fix it passes. Must be single-card
(--device-num 1), since VIS then exposes exactly one device as logical 0.

What it buys:

  • your one-off manual a2a3 verification becomes a permanent regression barrier —
    the next person who adds a direct HAL call site and forgets to translate gets a
    red check instead of an rc=42 in production;
  • a5's HAL sites go from "applied but not verified" to verified on every CI run,
    without you owning a box. That resolves your option (c) directly, and continuously.

On the unit test you added — it's a good test of the parser, and I'd keep it;
the malformed/out-of-range fallback policy is subtle and worth locking down. But
note it doesn't cover this fix: it only includes the header and asserts on the
returned integer, so it would pass unchanged on a branch where the helper exists
and no call site was ever wired to it. The defect was never "the parser
computes the wrong number", it was "the call sites pass the wrong id" — and that
layer is exactly what the VIS ST covers. The two are complementary, not
alternatives.

One thing it still won't cover: the DSMI fallback in a5's query_cpu_topo is
only reached when halGetDeviceInfoByBuff fails first, so a green VIS ST says
nothing about it either way. That's fine to keep as you have it — it sits behind
an already-failed primary path, and the NOTE you left in the code records the
assumption for whoever eventually hits it. No action needed there; if it turns out
to be wrong we'll find out on a real a5 node and fix it then.

Worth a quick local check that VIS doesn't upset anything else in the harness
(device validation, multi-card numbering assumptions) before wiring the steps in —
I've confirmed task-submit inherits the caller's environment and that the
invocation shape fits the existing steps, but I haven't run it.

@ChaoZheng109

Copy link
Copy Markdown
Collaborator

@lterrac Thanks for turning these around so quickly — the shared header, the a5
coverage, the added diagnostics and the parser rewrite all look good, and the
contract comment on acl_to_hal_device_id is exactly what I was after.

Three things left from my side:

  1. Add the VIS-isolated ST on both arches — details in my earlier comment.
    This is the one that matters most: it turns your manual a2a3 verification into
    a permanent check, and gets a5 verified in CI without you owning a box.

  2. CIut, ut-a2a3 and ut-a5 are red on the latest run.

  3. PR title and description — both still describe the original a2a3-only
    shape, and the comm_hccl.cpp already accounts for this remapping line isn't
    accurate (details in my review). Worth getting right: this repo squash-merges,
    so the description becomes the permanent commit body — and it's the only place
    the "a5 applied but not hardware-verified" caveat will survive the merge.

Nothing else from me beyond these three.

@lterrac
lterrac force-pushed the fix/aicore-regs-visible-devices branch 2 times, most recently from 092c060 to 82e363e Compare July 31, 2026 14:24
@lterrac lterrac changed the title Fix: honor ASCEND_RT_VISIBLE_DEVICES in AI-core register path Fix: honor ASCEND_RT_VISIBLE_DEVICES at direct-HAL device-id sites Jul 31, 2026
@lterrac

lterrac commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — the "CI does it" framing is exactly right. All three done:

1. VIS-isolation ST on both arches (st-onboard-a2a3 / st-onboard-a5): runs dummy_task under ASCEND_RT_VISIBLE_DEVICES with pytest --device 0, single card. Before wiring it I ran the local check you suggested on our a2a3 node — dummy_task passes at identity (VIS=0) and, more to the point, at non-identity (granted card 6 → VIS=6, logical 0 → physical 6), which is exactly the path that throws rc=42 without the fix. I used export ASCEND_RT_VISIBLE_DEVICES=$TASK_DEVICE inside --run rather than --env, matching the existing steps' \$TASK_DEVICE pattern (the granted id is only known post-grant).

2. CI red — the affinity ST compiles aicpu_topology_probe.cpp, which now includes the shared header, but its target was missing src/common/platform/include. Added it via a named SIMPLER_COMMON_PLATFORM_DIR (mirroring SIMPLER_LOG_DIR), so ut builds again.

3. Title + description updated to the both-arches shape, dropped the inaccurate comm_hccl.cpp already accounts for this remapping line, and moved the DSMI caveat into the description so it survives the squash-merge.

Also rebased onto main (resolved a ci.yml conflict against your -m 'not sdma' quarantine change). DSMI left as-is per your note — translated with the inline NOTE, behind the already-failed primary path.

@ChaoZheng109
ChaoZheng109 force-pushed the fix/aicore-regs-visible-devices branch 3 times, most recently from 068f542 to bb5a156 Compare August 1, 2026 08:52
Under `ASCEND_RT_VISIBLE_DEVICES` isolation a launcher renumbers the visible
cards to logical `0..N-1` and hands simpler logical ids. ACL honors the
variable for its own entry points, but the chip's direct driver calls bypass
ACL and index the driver-visible space. Passing them the ACL-logical id
targets the wrong device: the AI-core register map and the AICPU topology
probe -- both on the chip-init path (`init_aicore_register_addresses`,
`probe_aicpu_topology`) -- fail with `halMemCtl rc=42`, while the ACL compute
path works.

`common/acl_hal_device.h::acl_to_hal_device_id` is the single translation, and
carries the contract on its declaration: direct `hal*` / `dsmi_*` calls take
the driver-visible id and must go through it; ACL/rt entry points take the
logical id and must not. It validates the whole visible list before trusting
any entry -- one malformed token means the environment is misconfigured, so it
falls back to the unremapped id and logs why -- and is the identity when the
variable is unset. Every direct-driver device-id site now routes through it:
`halMemCtl` plus `halGetDeviceInfo*` on a2a3, `halResMap` plus
`halGetDeviceInfo*` / `dsmi_get_device_info` on a5.

Two layers of coverage, because the parser and the call sites fail
differently:

- `tests/ut/cpp/common/test_acl_hal_device.cpp` pins the parser's remap and
  its malformed / out-of-range fallback policy. Hardware-free.
- `tests/st/vis_isolation` runs `dummy_task` in a subprocess with the granted
  cards exposed under the variable, so both onboard jobs exercise the
  translation on real a2a3 and a5 silicon every run. Chip init is the only
  path needed: `init_aicore_register_addresses` and `probe_aicpu_topology` run
  on every bring-up, so one scene test covers every translated site. A unit
  test cannot reach this layer -- it passes on a branch where the helper
  exists and no call site was ever wired to it.

The ST addresses a logical id whose card differs from its position, which is
what makes the mapping observable; addressing one where they coincide would
pass under the identity mapping. The visible list is ascending because
`Runtime::GetVisibleDevices` rejects any other order with RT_ALL_ORDER_ERROR,
voiding the whole list, after which even `rtSetDevice` fails with 107001
before a single HAL call is reached. The subprocess is load-bearing too: the
variable must be set before ACL init, and mutating it in-process would leak
into the session's pooled workers.

Verified on hardware: reproduced `halMemCtl rc=42` on a2a3 under
`ASCEND_RT_VISIBLE_DEVICES=6` with the worker handed logical `0`; the
translation makes it disappear and the model produces output identical to the
legacy physical-id path.

One caveat. The a5 `dsmi_get_device_info` fallback in `query_cpu_topo` is only
reached when `halGetDeviceInfoByBuff` fails first, so the ST does not cover
it. It is translated for consistency but assumes DSMI shares the HAL's
driver-visible id space -- flagged with an inline NOTE. If that turns out
wrong it will surface on a real a5 node and can be revisited then.

Co-authored-by: ChaoZheng109 <zhengchao47@huawei.com>
@ChaoZheng109

Copy link
Copy Markdown
Collaborator

@lterrac I pushed to your branch directly (maintainer access) rather than sending
you another round of review comments — CI is fully green now and I didn't want to
sit on it. Everything is squashed into one commit under your authorship, with me
as co-author. Please look it over; happy to revert any of it if you disagree.

1. VIS verification moved from ci.yml into a scene test

.github/workflows/ci.yml is now byte-identical to main. The two
VIS-isolation scene test steps are replaced by
tests/st/vis_isolation/test_vis_isolation.py, which both onboard jobs pick up
through the existing pytest examples tests/st. The platforms marker keeps it
off the sim runners.

Reason: this repo has been moving CI selection out of ci.yml and into the
test tree — #1601 folded the dedicated qwen step back into the sweep, #1609
replaced path-based --ignore with a pytest marker so "the sweep and the
dedicated step can no longer drift apart". Two hand-maintained steps are the
shape both of those removed.

This also cleaned up something the rebase had resurrected: the
Qwen3-14B 40-layer decode (a2a3) step, which #1601 deleted on purpose. It was
back in your branch, so qwen would have run twice per a2a3 onboard job (38 GiB
fixture, its own card).

2. Two CI build failures

test_a5_aicpu_topology_fallback compiles a5/aicpu_topology_probe.cpp, which
your change made include the shared header. That target needed two things it
didn't have, and it broke ut, ut-a2a3 and ut-a5:

  • src/common/platform/include in its include dirs (compile error);
  • a no-op unified_log_debug stub (link error — acl_to_hal_device_id logs the
    applied mapping at LOG_DEBUG, and the target links no logger). Its a2a3
    sibling already had the stub.

Note this target arrived on main via #1552 after you first hit this, which is
why the a2a3 sibling was already correct and this one wasn't.

3. The ST was passing without proving anything

Worth flagging because it's the interesting one. The first version asked for one
card and exposed it as logical 0. On its first a5 run the pool granted card
0
, so logical 0 mapped to driver-visible 0 — the identity mapping the test
exists to reject. It went green while proving nothing.

It now takes two cards and addresses the logical id whose card differs from its
position, so the mapping is always observable, and skips with a reason on the
rare all-identity grant instead of claiming coverage it doesn't have.

Getting there turned up a constraint worth knowing about: my first attempt listed
the cards descending to force a non-identity mapping, and both onboard jobs
failed at rtSetDevice(0) with 107001 (ACL_ERROR_RT_INVALID_DEVICEID) —
before any HAL call. Runtime::GetVisibleDevices (runtime.cc) rejects a
non-ascending list with RT_ALL_ORDER_ERROR and voids the whole mapping:

Failed to set visible device. The invalid device is 0 and the input visible device is 14,9.

So ASCEND_RT_VISIBLE_DEVICES must be ascending. That same function also
confirms your helper's semantics is right — deviceInfo[logical] = physical,
indexed by position.

Result

All checks green. Both onboard jobs exercised a genuine non-identity mapping this
run:

  • st-onboard-a2a3 — granted [4, 7]VIS=4,7, logical 0 → card 4
  • st-onboard-a5 — granted [3, 0]VIS=0,3, logical 1 → card 3

dsmi_get_device_info left exactly as you had it, per my earlier note — still
translated, still carrying your inline NOTE.

@lterrac

lterrac commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Hi @ChaoZheng109, thanks for jumping in and reducing the review cycles, I really appreciate the help. Everything looks good to me, so if there is nothing left you can merge this PR :D

@ChaoZheng109
ChaoZheng109 merged commit 9a30e6d into hw-native-sys:main Aug 2, 2026
18 checks passed
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