Add bare-metal AArch64 platform support (RTOS / no OS) - #379
Conversation
|
Thanks for the interesting PR. There are 2 parts to this:
in XNNPack we may want to add uarch cortex_a320 to select appropriate microkernels. especially if KleidiAI is disabled.. perhaps treat it like a520 |
|
Thanks for the review! Per your suggestion I've split this into two PRs:
Addressing your points: A320 features: yes, A320 is ARMv9.2-A and implements the mandatory feature set (NEON, SVE2, dotprod, FP16, BF16, I8MM). See the Cortex-A320 TRM. We don't claim any of those statically though — the Zephyr backend reads ID_AA64ISAR0/1, PFR0/1, ZFR0, SMFR0 at runtime. MRS detection on other OSes: the MRS approach should work on any RTOS that runs the application at EL1 or higher. On Linux, applications run at EL0 where the ID registers are not directly accessible — Linux exposes them via the RTOS generalisation (FreeRTOS, etc.): the code is almost OS-agnostic — only XNNPACK uarch dispatch for A320: there's a follow-up XNNPACK patch ready that maps Plan B (XNNPACK self-detection without cpuinfo): the intent is to have A320 fully probed via cpuinfo once these pieces are in place. Duplicating detection in XNNPACK would mean two sources of truth. |
|
Gentle ping — anything else needed here, or is this ready to land? |
|
AI review has 4 concerns The pull request "Add Zephyr RTOS platform support for AArch64" generally makes sense as it addresses the need to run cpuinfo (and consequently libraries like XNNPACK and ExecuTorch) on However, there are a few technical issues and areas for improvement in the proposed changes: 1. Potential Breakage of Hexagon LoggingIn src/log.c , the PR groups Hexagon and Zephyr together and changes the logging mechanism for Hexagon: • Issue: This replaces Hexagon's use of qurt_printf (specific to QuRT RTOS) with standard printf . In Hexagon DSP environments, standard printf may not be redirected correctly or might not 2. Compilation Failure on 32-bit ARM (ARM32) Zephyr TargetsThe CMake changes append src/arm/zephyr/init.c if ZEPHYR_BASE is defined, but this is within a block that matches both 32-bit and 64-bit ARM architectures: • Issue: src/arm/zephyr/init.c contains AArch64-specific assembly (e.g., reading id_aa64isar0_el1 via mrs ) and calls cpuinfo_arm_decode_vendor_uarch with 3 arguments (which expects 4 3. CMAKE_SYSTEM_NAME Matching for ZephyrThe PR adds Generic to the allowed CMAKE_SYSTEM_NAME list in CMakeLists.txt to prevent warnings and initialization failures: • Issue: Zephyr's standard build system typically sets CMAKE_SYSTEM_NAME to Zephyr . If cpuinfo is integrated as a Zephyr module, it will likely see CMAKE_SYSTEM_NAME as Zephyr , which 4. POSIX DependencyIn src/init.c , the PR uses pthread_once for Zephyr initialization: • Note: This assumes that Zephyr has POSIX thread support enabled (e.g., CONFIG_PTHREAD_IPC ). While common for complex applications (like ExecuTorch), it restricts usage on minimal Zephyr SummaryThe core logic of reading ARM64 ID registers directly is correct and necessary for Zephyr. If the author addresses the Hexagon regression and ensures ARM32 builds do not break, the PR is solid |
fbarchard
left a comment
There was a problem hiding this comment.
should there be a bazel update with this?
the mrs approach apparently supports a white list for the cpuid range of values on modern linux, but when new fields are added, the white list needs to grow.
as a one off test I put a structured exception handler around mrs and actual instructions and the problem i ran into is you need a very new (aka future) version of linux kernel.
maybe some day...
we also have the basic detect in xnnpack, but to properly tune for a320 we'd want to at least confirm that behavior is very similar to a520 or something else.
a510 and a515 have microcoded instructions that are fixed in a520 if we tuned a kernel for a320/a520 they are similar?
on xnnpack side if you run a benchmark like f32_gemm_bench and find a kernel that is faster than what is selected in gemm_config.c send a PR to switch it. The build for android is
bazel build --config=android_arm64 -c opt bench/f32_gemm_bench
and the benchmark takes 3 parameters. 128 128 128 is a generic convolution size
Detect ISA features on AArch64 targets that run without an OS, that is under an RTOS or on bare metal, by reading the ID registers directly. This is not an alternative spelling of the Linux path, the two cannot share detection logic: there is no kernel to publish HWCAP here, and conversely the ID registers are inaccessible from Linux userspace. Reading them requires EL1, which is where an RTOS application runs. Nothing in the new file is tied to a particular RTOS. The single piece of external information needed is the core count, which comes from get_num_cpus(): Zephyr provides arch_num_cpus(), anything else falls back to a single core. Supporting another RTOS means adding a case there. cpuinfo_initialize() guards this path with a plain flag rather than pthread_once(), following the Emscripten-without-pthreads precedent, so that cpuinfo still builds where POSIX threads are unavailable or deliberately disabled to save space. Topology is reported as one package with one cluster. Cache geometry uses conservative Cortex-A defaults; reading CCSIDR_EL1 for the real values is left as a follow-up.
Select the bare-metal init when CMAKE_SYSTEM_NAME is "Generic", CMake's name for a target without an operating system, which is what both Zephyr and FreeRTOS set. "Generic" therefore also joins the list of recognised platforms so that cpuinfo_initialize() is not stubbed out for them. The source is added only for AArch64 targets: the ID register layout it reads does not apply to AArch32, so an ARM32 build must not pick it up. CPUINFO_BAREMETAL is defined for the library targets, selecting both that init path and its non-POSIX init guard.
Bare-metal targets have no file descriptors to write() to, so route logging through printf() there, the way the Hexagon path already does. Keep Hexagon on qurt_printf(): its RTOS does not necessarily redirect the standard printf(), which is why that path exists in the first place.
XNNPACK and cpuinfo carry Zephyr/Cortex-A320 changes that are not yet merged upstream: - XNNPACK: Cortex-A320 kernel selection (google/XNNPACK#10173, approved) - cpuinfo: Zephyr RTOS platform support for AArch64 (pytorch/cpuinfo#379) Drop this commit once both land and the upstream pins pick them up. The generic XNNPACK Zephyr platform support (google/XNNPACK#10060) and the Cortex-A320 MIDR decode (pytorch/cpuinfo#384) are already merged upstream and no longer carried here.
|
Thanks, this was useful feedback. I've restructured the PR rather than patched around the comments. Auditing our own code, essentially nothing in it was Zephyr-specific: of ~150 lines, the only Zephyr dependency was a single call to get the core count. Everything else is plain AArch64 ID-register reads. So the platform is now "bare metal", meaning an RTOS or no OS at all, rather than Zephyr:
On whether the MRS code could be shared with, or tested on, another OS via hwcap: I don't think the two paths can share detection logic, and it is worth being explicit about why. The distinction is not Zephyr versus Linux, it is EL1 versus userspace. hwcap only exists if a kernel publishes it, and there is no kernel here; conversely the ID registers are inaccessible from Linux userspace, which is precisely why the Linux path uses hwcap. Each path is the only option available in its own environment. The upside is that the new file is testable anywhere code runs at EL1. On the four points from the automated review:
On the XNNPACK uarch selection: that is google/XNNPACK#10173, which is approved. You suggested treating A320 like an A520, and it is worth separating two things: the uarch mapping only picks GEMM microkernel variants and tile sizes, while SVE2 and the other ISA features come from Verified on a Corstone-1000-A320 target (FVP, since silicon is not out yet): MIDR reads back 0x410fd8f0, part 0xd8f, which decodes to |
This PR adds cpuinfo support for AArch64 targets running the Zephyr
RTOS. It is motivated by ongoing work to bring XNNPACK and ExecuTorch
to Zephyr on Arm Cortex-A class cores (specifically the Arm
Corstone-1000 Edge-AI platform with Cortex-A320 and Ethos-U85 NPU).
Summary:
Platform init backend (
src/arm/zephyr/init.c): reads ARM64 IDregisters (
ID_AA64ISAR0/1_EL1,ID_AA64PFR0/1_EL1,ID_AA64ZFR0_EL1,ID_AA64SMFR0_EL1,MIDR_EL1) directly todetect ISA features. Features detected include AES, SHA1/2, CRC32,
atomics, dotprod, FP16, BF16, I8MM, SVE/SVE2, and SME/SME2 (with
sub-feature flags). Uarch is decoded via the existing
cpuinfo_arm_decode_vendor_uarch().Topology: single package/cluster with
arch_num_cpus()cores(reflecting Zephyr's
CONFIG_MP_MAX_NUM_CPUS). Cache geometry usesconservative Cortex-A defaults (64KB L1, 512KB L2, 64B lines) —
CCSIDR_EL1-based detection is left as a TODO.
CMake: recognise
CMAKE_SYSTEM_NAME=GenericwithZEPHYR_BASEdefined as a supported platform, and select the Zephyr ARM init
source for AArch64 targets.
Logging: use
printfon Zephyr (like the Hexagon path), sinceZephyr's POSIX layer does not provide the same
STDERR_FILENO/STDOUT_FILENOsemantics as Linux.Thread-safe initialisation uses
pthread_oncevia Zephyr's POSIXcompatibility layer.
Note: the ID register reads require EL1 or higher. Zephyr runs
application code at EL1 by default, so this is fine for normal use.
If
CONFIG_USERSPACEis enabled,cpuinfo_initialize()must becalled from a privileged context (e.g. main thread or
SYS_INIT)before any user-mode threads that depend on cpuinfo.
Tested on the Arm Corstone-1000-A320 FVP (Cortex-A320, ARMv9.2-A)
running Zephyr with XNNPACK inference. cpuinfo correctly detects
NEON, SVE2 (with VL=16 bytes), dotprod, FP16, and BF16; XNNPACK
selects KleidiAI NEON kernels accordingly and executes
ExecuTorch-exported models with expected output.
Commits: