Category
Robustness (potential edge-case failure)
Component
AICPU Scheduler
Description
This is an umbrella issue. It tracks the questions that must be answered before the a5 "AICPU scheduler threads claim AICore clusters by die affinity" design can be finalized. Each question is independently verifiable; answers go in the comments. The design is settled only once every question has a conclusion.
The design goal in one line: each scheduler thread should prefer AICore clusters on its own die, and only go cross-die once its quota is full. That needs two facts at once:
- (a) which AICPU die each scheduler thread lands on
- (b) which AICore die each AICore cluster belongs to
Question 1 is about (a).
Question 1 — phy_cpu_id semantics under PG are undefined, so the cluster_id / die_id derivation can be silently wrong
Status: awaiting hardware verification
Fact 1: the driver does not report cluster/die — the runtime derives them
CPU_TOPO gives four fields per logical CPU (aicpu_topology_probe.cpp:58-64):
struct DsmiSingleCpu {
uint64_t cpu_mask; // SMT sibling mask
uint8_t cpu_id;
uint8_t is_share;
uint8_t phy_cpu_id;
uint8_t hyperthread_id;
};
No cluster_id, no die_id. Both are pure arithmetic inferences (live path :233-234, JSON fallback path :1158-1159):
entry.cluster_id = entry.phy_cpu_id / 2; // 2 physical CPUs per cluster
entry.die_id = entry.phy_cpu_id / 4; // 2 clusters per die
The internal design draft "A5 AICPU PG 场景拓扑与选核策略设计" (2026-07-30), §2.2, says the runtime obtains surviving-cluster and die distribution "through driver topology interfaces such as DSTI". The current code does not call DSTI — it infers everything from phy_cpu_id.
Fact 2: FG/PG classification rests entirely on those two inferences
classify_aicpu_scenario() (:570-580) does exactly two things: count distinct cluster_id values (4 → FG, 3 → PG1, 2 → PG2), then check that the die_id set is {0,1} and that the per-die cluster counts match the template.
The problem: nobody has confirmed what phy_cpu_id means after a cluster is masked off
- Semantics A — true physical numbering is preserved; faulted ids leave gaps.
- Semantics B — BIOS renumbers contiguously; surviving physical CPUs are renumbered from 0.
The design draft's §3.2 / §3.3 diagrams show semantics B ("6 survivors: CPU0…CPU5", "4 survivors: CPU0…CPU3") and explicitly warn: "CPU ids are drawn as the contiguous numbering BIOS exposes in each scenario… the implementation must not bind the diagram's CPU ids to fixed physical positions." The /2 and /4 derivation is only valid under semantics A.
Worked example 1: PG2 (Die0/Cluster1 and Die1/Cluster3 faulted)
Survivors: Cluster0 (Die0, true CPU0/1) and Cluster2 (Die1, true CPU4/5).
|
Semantics A (gaps) |
Semantics B (renumbered) |
phy_cpu_id |
{0,1,4,5} |
{0,1,2,3} |
cluster = phy/2 |
{0,2}, size 2 ✅ |
{0,1}, size 2 ✅ |
die = phy/4 |
{0,1} ✅ |
{0} ❌ |
clusters_per_die |
(1,1) ✅ |
— |
| Classification |
kPg2 ✅ |
kUnknown ❌ |
Worked example 2: PG1 where the faulted cluster is not the trailing one
The draft's §2.1 states the fault can land on either die. Say Die0/Cluster1 is faulted, leaving Cluster0 (Die0), Cluster2 (Die1), Cluster3 (Die1) — true distribution is Die0:1 / Die1:2.
|
Semantics A |
Semantics B |
phy_cpu_id |
{0,1,4,5,6,7} |
{0,1,2,3,4,5} |
cluster |
{0,2,3}, size 3 ✅ |
{0,1,2}, size 3 ✅ |
dies |
{0,1} ✅ |
{0,1} ✅ |
clusters_per_die |
(1,2) ✅ matches truth |
(2,1) ❌ inverted |
| Classification |
kPg1 ✅ die attribution correct |
kPg1 ✅ but die attribution is wrong |
Example 2 is the dangerous one: classification "succeeds", selection runs normally, and the result is silently wrong with no anomaly in any log. CPU4/CPU5 — truly on Die1, renumbered to phy 2/3 — are attributed to Die0.
Why this has not surfaced yet
-
Only FG has ever been measured. Both verified JSON entries (9599 FG, 9579) are fault-free topologies, and no PG machine has ever been dumped. FG has no fault, hence no renumbering, hence the derivation is necessarily correct — which is why production is fine today.
-
validate_cpu_topology() cannot catch it. It only checks fields >= 0, unique cpu_id, and unique (phy, ht). Both semantics pass.
-
The existing unit tests contradict each other on this very point:
test_aicpu_topology_fallback.cpp:304-320 ComputesPg2SharingPolicy builds the pool from make_physical_range(0, 1) + make_physical_range(4, 5), skipping phy 2/3 — i.e. it assumes semantics A.
test_aicpu_topology_fallback.cpp:351-363 MatchesPg2ComputeOnlyDiagram hand-writes {cpu_id=4, phy=2, cluster=2, die=1} — where cluster != phy/2 and die != phy/4, bypassing the derivation entirely.
Neither test exercises the derivation itself.
-
Getting it wrong still runs. A misclassification or a wrong die only degrades placement affinity; it does not break functionality. The draft's §4.7 admission criterion "every supported PG topology completes runs stably" would still pass.
Blast radius
|
Impact on today's CPU selection |
Impact on die-affinity assignment |
| Example 1 (PG2 → kUnknown) |
worse affinity, still runs |
die info degrades — not fatal |
| Example 2 (PG1 die inverted) |
worse affinity, still runs |
AICore clusters land on the wrong die → anti-affinity, worse than not doing it |
For today's code a wrong die is merely suboptimal. Die affinity consumes the die id as a hard input, so one bit wrong turns the feature negative. Hence this must be answered first.
What needs to be verified (please reply in a comment on this issue)
Run on a PG machine (one or two AICPU clusters faulted). If several are available, one PG1 and one PG2 would be ideal — and for PG1 especially, a machine whose faulted cluster is not the trailing one (the example-2 case).
# Build steps: tools/cann-examples/aicpu-device-query/README.md
export SIMPLER_DISPATCHER_SO=$REPO/build/lib/a5/dispatcher/libsimpler_aicpu_dispatcher.so
export SIMPLER_AICPU_QUERY_SO=$REPO/tools/cann-examples/aicpu-device-query/device/build/libaicpu_query.so
task-submit --device auto --device-num 1 \
--run "$REPO/tools/cann-examples/aicpu-device-query/host/build/query_device_hal \$TASK_DEVICE --json"
Please paste the full JSON verbatim, plus answers to these five:
| # |
Question |
How to decide |
| 1 |
How many clusters are actually faulted on this machine, and on which die? |
Confirm from the hardware/BIOS side — do not infer it from this tool's output |
| 2 |
Are the os_schedulable_cpus[].phy_cpu_id values contiguous? |
Gaps ⇒ semantics A; contiguous ⇒ semantics B or a trailing fault |
| 3 |
What scenario_type does the JSON report? |
Compare against the true fault count from #1 |
| 4 |
Does each CPU's die_id match the true die from #1? |
The decisive one |
| 5 |
Does surviving_clusters match the true surviving cluster ids? |
|
Notes:
--json emits only the OCCUPY-filtered os_schedulable_cpus, whereas classification runs over the full CPU_TOPO table. If possible, please additionally attach the unfiltered (cpu_id, phy_cpu_id, hyperthread_id, cpu_mask) list.
- If
--json fails because the SoC is unsupported, fall back to the pretty-print output of query_device_hal <dev> plus the host-side aicpu_topology_probe: CPU_TOPO source=... soc=... logical=N LOG_INFO line.
How the answer branches the design
| Verification result |
Conclusion |
Semantics A (gaps present, die_id matches truth) |
The derivation is trustworthy; die affinity is viable on PG1/PG2 |
Semantics B (contiguous, die_id disagrees with truth) |
The derivation is not trustworthy → either switch to reading cluster/die from DSTI, or fall back to plain round-robin on any PG topology |
Location
src/a5/platform/onboard/host/aicpu_topology_probe.cpp:58-64 — DsmiSingleCpu; the driver gives four fields, no cluster/die
src/a5/platform/onboard/host/aicpu_topology_probe.cpp:233-234 — live CPU_TOPO path derives cluster_id / die_id
src/a5/platform/onboard/host/aicpu_topology_probe.cpp:1158-1159 — the same derivation on the JSON fallback path
src/a5/platform/onboard/host/aicpu_topology_probe.cpp:570-580 — classify_aicpu_scenario(), the FG/PG1/PG2 criteria
tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp:304-320 — ComputesPg2SharingPolicy (assumes semantics A)
tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp:351-363 — MatchesPg2ComputeOnlyDiagram (hand-written cluster/die, bypasses the derivation)
src/a5/docs/hardware.md:200 — where the derivation is documented
Line numbers are against upstream/main @ 748c39fbe7825e8411f44e91b754a3b2449f633c.
Proposed Fix
Step 1 — get the verification result first. Nothing in the existing logic changes until then.
Step 2 — two items worth doing regardless of the answer (decoupled from die affinity, can ship as their own PR):
-
Add a cold-path diagnostic that prints the raw phy_cpu_id sequence and whether it is contiguous, so the next PG machine answers this question by simply running:
LOG_INFO("aicpu_topology: phy_cpu_ids=[%s] distinct=%zu contiguous=%d",
dump.c_str(), distinct_phy.size(), is_contiguous);
-
Resolve the contradiction between the two PG2 tests: either add a case that genuinely goes through probe_aicpu_topology's derivation, or state in a comment that MatchesPg2ComputeOnlyDiagram assumes cluster/die are supplied correctly upstream and does not cover the derivation. Today the two tests assume incompatible phy_cpu_id semantics for the same scenario.
Step 3 — when the die-affinity feature ships, gate it on a conservative predicate (leaves the existing classification untouched, so the blast radius is zero):
distinct_phy = deduplicated set of phy_cpu_id
size == 8 → FG, no fault, die trustworthy
max(distinct_phy) + 1 > size → gaps present ⇒ semantics A, die trustworthy
otherwise (contiguous, < 8) → indistinguishable ⇒ die not trustworthy, fall back to round-robin
This is fail-safe: it also conservatively rejects "semantics A + trailing-cluster fault" (where the die would in fact be correct), giving up a little benefit but never producing a wrong assignment. It can be relaxed once a PG dump lands here.
Follow-up questions
Questions 2, 3, … will be appended to this issue in the same shape (facts → problem → what to verify → how the answer branches the design). Known directions still to write up:
- Chain (b): whether the
worker_id → physical_core_id mapping is dense and die-monotone (this decides whether die affinity can preserve tmr's barrier-free init path)
- Whether the AICPU die numbering and the AICore die numbering refer to the same physical die (if inverted, affinity becomes anti-affinity)
- The measured magnitude of the die-affinity benefit (if it cannot be measured, do not build it)
Priority
Medium (minor risk, should fix in next few releases)
Category
Robustness (potential edge-case failure)
Component
AICPU Scheduler
Description
This is an umbrella issue. It tracks the questions that must be answered before the a5 "AICPU scheduler threads claim AICore clusters by die affinity" design can be finalized. Each question is independently verifiable; answers go in the comments. The design is settled only once every question has a conclusion.
The design goal in one line: each scheduler thread should prefer AICore clusters on its own die, and only go cross-die once its quota is full. That needs two facts at once:
Question 1 is about (a).
Question 1 —
phy_cpu_idsemantics under PG are undefined, so thecluster_id/die_idderivation can be silently wrongStatus: awaiting hardware verification
Fact 1: the driver does not report cluster/die — the runtime derives them
CPU_TOPO gives four fields per logical CPU (
aicpu_topology_probe.cpp:58-64):No
cluster_id, nodie_id. Both are pure arithmetic inferences (live path:233-234, JSON fallback path:1158-1159):Fact 2: FG/PG classification rests entirely on those two inferences
classify_aicpu_scenario()(:570-580) does exactly two things: count distinctcluster_idvalues (4 → FG, 3 → PG1, 2 → PG2), then check that thedie_idset is{0,1}and that the per-die cluster counts match the template.The problem: nobody has confirmed what
phy_cpu_idmeans after a cluster is masked offThe design draft's §3.2 / §3.3 diagrams show semantics B ("6 survivors: CPU0…CPU5", "4 survivors: CPU0…CPU3") and explicitly warn: "CPU ids are drawn as the contiguous numbering BIOS exposes in each scenario… the implementation must not bind the diagram's CPU ids to fixed physical positions." The
/2and/4derivation is only valid under semantics A.Worked example 1: PG2 (Die0/Cluster1 and Die1/Cluster3 faulted)
Survivors: Cluster0 (Die0, true CPU0/1) and Cluster2 (Die1, true CPU4/5).
phy_cpu_id{0,1,4,5}{0,1,2,3}cluster = phy/2{0,2}, size 2 ✅{0,1}, size 2 ✅die = phy/4{0,1}✅{0}❌clusters_per_die(1,1)✅Worked example 2: PG1 where the faulted cluster is not the trailing one
The draft's §2.1 states the fault can land on either die. Say Die0/Cluster1 is faulted, leaving Cluster0 (Die0), Cluster2 (Die1), Cluster3 (Die1) — true distribution is Die0:1 / Die1:2.
phy_cpu_id{0,1,4,5,6,7}{0,1,2,3,4,5}cluster{0,2,3}, size 3 ✅{0,1,2}, size 3 ✅dies{0,1}✅{0,1}✅clusters_per_dieExample 2 is the dangerous one: classification "succeeds", selection runs normally, and the result is silently wrong with no anomaly in any log. CPU4/CPU5 — truly on Die1, renumbered to phy 2/3 — are attributed to Die0.
Why this has not surfaced yet
Only FG has ever been measured. Both verified JSON entries (9599 FG, 9579) are fault-free topologies, and no PG machine has ever been dumped. FG has no fault, hence no renumbering, hence the derivation is necessarily correct — which is why production is fine today.
validate_cpu_topology()cannot catch it. It only checks fields >= 0, uniquecpu_id, and unique(phy, ht). Both semantics pass.The existing unit tests contradict each other on this very point:
test_aicpu_topology_fallback.cpp:304-320ComputesPg2SharingPolicybuilds the pool frommake_physical_range(0, 1)+make_physical_range(4, 5), skipping phy 2/3 — i.e. it assumes semantics A.test_aicpu_topology_fallback.cpp:351-363MatchesPg2ComputeOnlyDiagramhand-writes{cpu_id=4, phy=2, cluster=2, die=1}— wherecluster != phy/2anddie != phy/4, bypassing the derivation entirely.Neither test exercises the derivation itself.
Getting it wrong still runs. A misclassification or a wrong die only degrades placement affinity; it does not break functionality. The draft's §4.7 admission criterion "every supported PG topology completes runs stably" would still pass.
Blast radius
For today's code a wrong die is merely suboptimal. Die affinity consumes the die id as a hard input, so one bit wrong turns the feature negative. Hence this must be answered first.
What needs to be verified (please reply in a comment on this issue)
Run on a PG machine (one or two AICPU clusters faulted). If several are available, one PG1 and one PG2 would be ideal — and for PG1 especially, a machine whose faulted cluster is not the trailing one (the example-2 case).
Please paste the full JSON verbatim, plus answers to these five:
os_schedulable_cpus[].phy_cpu_idvalues contiguous?scenario_typedoes the JSON report?die_idmatch the true die from #1?surviving_clustersmatch the true surviving cluster ids?Notes:
--jsonemits only the OCCUPY-filteredos_schedulable_cpus, whereas classification runs over the full CPU_TOPO table. If possible, please additionally attach the unfiltered(cpu_id, phy_cpu_id, hyperthread_id, cpu_mask)list.--jsonfails because the SoC is unsupported, fall back to the pretty-print output ofquery_device_hal <dev>plus the host-sideaicpu_topology_probe: CPU_TOPO source=... soc=... logical=NLOG_INFO line.How the answer branches the design
die_idmatches truth)die_iddisagrees with truth)Location
src/a5/platform/onboard/host/aicpu_topology_probe.cpp:58-64—DsmiSingleCpu; the driver gives four fields, no cluster/diesrc/a5/platform/onboard/host/aicpu_topology_probe.cpp:233-234— live CPU_TOPO path derivescluster_id/die_idsrc/a5/platform/onboard/host/aicpu_topology_probe.cpp:1158-1159— the same derivation on the JSON fallback pathsrc/a5/platform/onboard/host/aicpu_topology_probe.cpp:570-580—classify_aicpu_scenario(), the FG/PG1/PG2 criteriatests/ut/cpp/a5/test_aicpu_topology_fallback.cpp:304-320—ComputesPg2SharingPolicy(assumes semantics A)tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp:351-363—MatchesPg2ComputeOnlyDiagram(hand-written cluster/die, bypasses the derivation)src/a5/docs/hardware.md:200— where the derivation is documentedLine numbers are against
upstream/main@748c39fbe7825e8411f44e91b754a3b2449f633c.Proposed Fix
Step 1 — get the verification result first. Nothing in the existing logic changes until then.
Step 2 — two items worth doing regardless of the answer (decoupled from die affinity, can ship as their own PR):
Add a cold-path diagnostic that prints the raw
phy_cpu_idsequence and whether it is contiguous, so the next PG machine answers this question by simply running:Resolve the contradiction between the two PG2 tests: either add a case that genuinely goes through
probe_aicpu_topology's derivation, or state in a comment thatMatchesPg2ComputeOnlyDiagramassumes cluster/die are supplied correctly upstream and does not cover the derivation. Today the two tests assume incompatiblephy_cpu_idsemantics for the same scenario.Step 3 — when the die-affinity feature ships, gate it on a conservative predicate (leaves the existing classification untouched, so the blast radius is zero):
This is fail-safe: it also conservatively rejects "semantics A + trailing-cluster fault" (where the die would in fact be correct), giving up a little benefit but never producing a wrong assignment. It can be relaxed once a PG dump lands here.
Follow-up questions
Questions 2, 3, … will be appended to this issue in the same shape (facts → problem → what to verify → how the answer branches the design). Known directions still to write up:
worker_id → physical_core_idmapping is dense and die-monotone (this decides whether die affinity can preserve tmr's barrier-free init path)Priority
Medium (minor risk, should fix in next few releases)