Skip to content

[Phase 2] Add NEON SIMD path for CPU Adam on AArch64 - #8453

Open
PKUWZP wants to merge 3 commits into
masterfrom
mps-neon
Open

[Phase 2] Add NEON SIMD path for CPU Adam on AArch64#8453
PKUWZP wants to merge 3 commits into
masterfrom
mps-neon

Conversation

@PKUWZP

@PKUWZP PKUWZP commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Phase 2 of Apple Silicon support (follow-up to #8293/#8300/#8335): the CPU Adam kernel — the ZeRO-Offload optimizer path — ran scalar on AArch64 machines without SVE, which includes every Apple Silicon Mac. This adds a 4-lane NEON implementation of the existing SIMD macro layer.

Changes

  • csrc/includes/simd.h — a __NEON__ branch defining the full macro set (SIMD_LOAD/STORE/SET/ADD/MUL/FMA/SQRT/DIV/AND/ANDNOT/OR/XOR, width 4):
    • fp16 via the hardware converters (vcvt_f32_f16 / vcvt_f16_f32).
    • bf16 via the same round-to-nearest-even + NaN-quieting flow as the AVX512 store_16_f32_as_bf16_nearest (using vaddhn_u32 for the add-and-take-high-half step); loads are widen+shift.
    • x86 andnot(x, y) = ~x & y maps to vbicq(y, x) — operand order preserved (documented in a comment).
    • The bf16 simd_load/simd_store guards widen from AVX512-only to AVX512-or-NEON.
  • csrc/includes/cpu_adam.hStep_AVX's non-AVX512 bf16 bailout is lifted for NEON (this was silently sending bf16 back to the scalar tail); the two Adam gates widen to include __NEON__.
  • csrc/adam/cpu_adam_impl.cpp — same gate widening (4 sites, including kZenAdamAlign). The NEON branch sits before the existing __SVE__ alternative and they remain mutually exclusive builder-emitted defines.
  • op_builder/builder.pysimd_width() advertises -D__NEON__ for ARM_8 without SVE. 32-bit ARM keeps __SCALAR__: the vdivq_f32/vsqrtq_f32 intrinsics used are A64-only.
  • op_builder/mps/cpu_adam.py — switches from -D__SCALAR__ to -D__NEON__.
  • Lion/Adagrad/AIO gates are untouched and keep their current scalar behavior on ARM (candidate follow-ups).

Measured on Apple M5 Max (macOS 26.3, Apple clang, Homebrew libomp)

DeepSpeedCPUAdam step, 50M params, 10-step average, vs the -D__SCALAR__ build of the same tree:

dtype scalar NEON speedup
fp32 11.5 ms 3.8 ms 3.0×
fp16 11.5 ms 3.3 ms 3.5×
bf16 12.7 ms 4.9 ms 2.6×

Correctness

  • NEON and scalar builds produce bit-identical fp16 results on identical inputs (5 steps, 1M params).
  • All dtypes (fp32/fp16/bf16 params; fp32 and bf16 moments) match an fp32 torch.optim.Adam/AdamW oracle within storage rounding, at sizes exercising pure-SIMD, SIMD+scalar-tail (1000003), and sub-width (3) paths.
  • Existing suites on the M5 Max: test_cpu_adam.py + test_hybrid_adam.py + test_adamw.py — 122 passed, 7 skipped. The mps-torch-latest CI workflow JIT-builds this kernel in its offload configs, so the NEON path is exercised upstream on every touching PR.

The CPU Adam kernel ran scalar on AArch64 machines without SVE, which
includes every Apple Silicon Mac (the ZeRO-Offload optimizer path on
that platform). simd.h gains a 4-lane NEON implementation of the
existing SIMD macro set, following the AVX512 semantics: fp16 through
the hardware converters, bf16 through the same round-to-nearest-even
with nan-quieting flow as store_16_f32_as_bf16_nearest, and x86
andnot's operand order preserved. Step_AVX's non-AVX512 bf16 bailout
is lifted for NEON since the bf16 converters exist there.

Builders: simd_width() advertises -D__NEON__ for AArch64 without SVE
(the vdivq/vsqrtq intrinsics used are A64-only, so 32-bit ARM keeps
the scalar path) and the MPS cpu_adam builder switches from
-D__SCALAR__ accordingly. Lion/Adagrad gates are left untouched and
keep their scalar behavior.

Measured on an M5 Max (macOS 26.3, clang, libomp), DeepSpeedCPUAdam
step on 50M params, 10-step average, vs the -D__SCALAR__ build:
fp32 11.5 -> 3.8 ms, fp16 11.5 -> 3.3 ms, bf16 12.7 -> 4.9 ms.
NEON and scalar builds produce bit-identical fp16 results on identical
inputs; all dtypes match a fp32 torch.optim oracle within storage
rounding, including sizes exercising the SIMD/scalar tail boundary.

Signed-off-by: PKUWZP <zhipeng.rainbowserie@gmail.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T04:53:53.248131Z dfd9591 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@PKUWZP PKUWZP changed the title Add NEON SIMD path for CPU Adam on AArch64 [Phase 2] Add NEON SIMD path for CPU Adam on AArch64 Sep 8, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dfd95919b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
size_t rounded_size = 0;
#if defined(__AVX512__) or defined(__AVX256__)
#if defined(__AVX512__) or defined(__AVX256__) or defined(__NEON__)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Sign off the commit

This non-merge commit has no Signed-off-by trailer, so it violates the repository's commit requirement and will be rejected by the DCO check; recreate the commit using --signoff.

AGENTS.md reference: AGENTS.md:L8-L8

Useful? React with 👍 / 👎.

Comment thread op_builder/builder.py
elif cpu_info['arch'] == 'ARM_8':
# NEON is baseline on AArch64; vdivq/vsqrtq used by simd.h are A64-only,
# so do not advertise it for 32-bit ARM.
return '-D__NEON__'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update the AArch64 CPU Adam documentation

Returning __NEON__ here makes non-SVE AArch64 builds vectorized for fp32, fp16, and bf16, but docs/_tutorials/zero-offload.md:78 still explicitly tells users that these CPUs and non-fp32 types use the scalar implementation. Update that guidance as part of this feature so users do not make tuning decisions from the now-incorrect capability description.

AGENTS.md reference: AGENTS.md:L26-L26

Useful? React with 👍 / 👎.

{
size_t rounded_size = 0;
#if defined(__AVX512__) or defined(__AVX256__)
#if defined(__AVX512__) or defined(__AVX256__) or defined(__NEON__)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report the active NEON implementation

When info logging is enabled, this new __NEON__ dispatch executes the vector kernel, but create_adam_optimizer has no corresponding __NEON__ logging branch and therefore prints that the optimizer was created with scalar arithmetic. This makes the normal DeepSpeedCPUAdam startup diagnostic incorrect on every affected AArch64 build; add a NEON case alongside the AVX and SVE cases.

Useful? React with 👍 / 👎.

{
size_t rounded_size = 0;
#if defined(__AVX512__) or defined(__AVX256__)
#if defined(__AVX512__) or defined(__AVX256__) or defined(__NEON__)

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.

Use __NEON__ to filter may have impact on SVE device, @xylian86 to confirm.

cxx_args hardcoded -std=c++17, which lands after cpp_extension's own
-std flag and overrides it. torch 2.14's headers require C++20, so the
JIT build broke on CI runners with current torch while still compiling
against torch 2.13 locally. Drop the flag; cpp_extension supplies the
standard its headers need.

Signed-off-by: PKUWZP <zhipeng.rainbowserie@gmail.com>
@delock

delock commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Hi @PKUWZP , do you think using some different macro as NEON as path selection? NEON is built-in macro when running ARM compiler. I'm worrying that for ARM chips with SVE feature, the code here would direct C++ code into neon path which may not be intended. Use a macro slightly different would be a better choice.

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