Skip to content

[Code Health] a5 die-affinity core assignment: prerequisite questions the design depends on #2221

Description

@ChaoZheng109

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

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

  2. validate_cpu_topology() cannot catch it. It only checks fields >= 0, unique cpu_id, and unique (phy, ht). Both semantics pass.

  3. 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.

  4. 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-64DsmiSingleCpu; 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-580classify_aicpu_scenario(), the FG/PG1/PG2 criteria
  • tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp:304-320ComputesPg2SharingPolicy (assumes semantics A)
  • tests/ut/cpp/a5/test_aicpu_topology_fallback.cpp:351-363MatchesPg2ComputeOnlyDiagram (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):

  1. 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);
  2. 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

code healthTechnical debt, robustness, code quality

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions