Skip to content

[MLAS] Add Apple Accelerate (vForce) Tanh kernel (depends on #32001) - #32036

Closed
Justin Chu (justinchuby) wants to merge 22 commits into
microsoft:mainfrom
justinchuby:nxrt/mlas-apple-tanh-vforce
Closed

Justin Chu (justinchuby) wants to merge 22 commits into
microsoft:mainfrom
justinchuby:nxrt/mlas-apple-tanh-vforce

Conversation

@justinchuby

@justinchuby Justin Chu (justinchuby) commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Outcome / Decision: CLOSING — not merging

Decision: this PR will be closed, not merged. The real Apple Silicon Release benchmark below disproves the optimization premise this PR was built on: apple_accelerate_vforce (BM_TanhDispatch) is slower than the existing portable polynomial kernel (BM_TanhPortableKernel) at every one of the 18 sizes tested (N = 1, 15, 16, 31, 32, 63, 64, 127, 128, 255, 256, 511, 512, 1024, 4096, 16384, 65536, 262144) — honest range ~1.1x–5x slower, median ~2.2x slower. There is no size regime in the data where vForce wins. See the full per-size table in Validation below.

  • Correctness is not in question. All 10 TanhAppleAccelerate.* tests passed on real Apple Silicon CI hardware (10/10, both Debug and Release matrix legs) — the kernel is correct, just not faster.
  • Real hardware evidence, not CI noise. Run 31647787148: Release job (36m29s, correctness + the benchmark A/B) and Debug job (54m44s, full build.py --test suite). Both green. This was the first (and, per this decision, only) time any revision of this PR executed on real Apple hardware.
  • Why close rather than leave open/draft: Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001 is an infrastructure-only umbrella option (onnxruntime_USE_APPLE_ACCELERATE) intended to let real Accelerate-backed kernels opt in behind it. Merging this PR as the option's first concrete kernel would make enabling that option a pessimization for Tanh — every caller on the ONNX Tanh op, the Gelu tanh-approximation, the SVM classifier, and RNN/GRU/LSTM cell helpers would get consistently slower code than the portable kernel already ships. That is not an acceptable trade for an opt-in performance option, and there is no evidence in hand (across 18 sizes, on real hardware) that a narrower size-gated variant would help either — the ratio is roughly flat across three orders of magnitude of N, not converging toward parity or a crossover at any tested size.
  • What should be salvaged separately (not in this PR): the reusable test/CI methodology developed here is sound and worth keeping for the next Accelerate-backed kernel candidate (e.g. a future SGEMM/BNNS/vDSP PR building on Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001): the finite out-of-range poison-value technique for proving a kernel actually wrote every output element (kPoisonValue = 123456.75f, proven via an injected no-write bug under qemu-aarch64-static); the list-then-verify fail-loud CI pattern for both gtest (--gtest_list_tests) and benchmark (--benchmark_list_tests=true) steps, which catches a filter silently matching zero tests/benchmarks; the A/B benchmark pattern with an explicit dispatch-label assertion (apple_accelerate_vforce presence check) so a misconfigured build can't silently compare the portable kernel against itself; and the direct IEEE-754/ONNX-contract semantic tests for NaN/±Inf/±0/subnormal/FLT_MIN inputs, independent of parity against the portable kernel. None of this is Tanh-specific and all of it transfers to a kernel that might actually show a win.
  • This PR itself, and its dependency Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001, remain unmerged; closing this PR does not require any changes to Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001.

Summary

First concrete kernel built on top of #32001 (onnxruntime_USE_APPLE_ACCELERATE, infrastructure-only, still open/unmerged). Adds MlasTanhKernelAppleAccelerate, which computes MLAS's Tanh activation via Apple Accelerate's vForce vvtanhf() on macOS arm64 only, gated entirely behind the (default-off) onnxruntime_USE_APPLE_ACCELERATE compile option added in #32001.

  • Depends on Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001. This branch is stacked on nxrt/mlas-apple-framework-option and will need to be rebased once that PR merges. Do not review/merge this before Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001.
  • Scope: macOS arm64 only. No Intel Mac, no universal2, no iOS/tvOS/visionOS. Matches the squad's standing Apple-scope directive.
  • Public API only. vvtanhf(float* y, const float* x, const int* n) is a public, long-stable vForce API (macOS 10.4+ / iOS 5.0+) — verified against the actual Accelerate.framework headers, not just documentation. No private/hand-declared BNNS structs are used.
  • One kernel family. Accelerate cblas SGEMM, BNNS, and vDSP are explicitly out of scope for this PR and will be separate follow-ups.

Why Tanh / vForce

Surveyed MLAS's elementwise/activation kernels (Erf, Tanh, Exp, Gelu) for the smallest, highest-value, provably-reachable candidate with a stable public API:

  • vForce has no erf function (checked actual SDK headers through macOS 11.3), ruling out Erf despite it being MLAS's most self-contained kernel.
  • vvtanhf is public and stable, and MlasComputeTanh is reachable from several real, high-value call sites: the ONNX Tanh activation op, the Gelu tanh-approximation, the SVM classifier, and RNN/GRU/LSTM cell helpers — several of which call it with Input == Output (in-place).
  • On plain ARM64 (Apple Silicon has no SVE), MlasComputeTanh<float> currently bypasses the platform dispatch-table mechanism entirely and calls the generic kernel directly (the #if MLAS_TARGET_AMD64 || MLAS_TARGET_RISCV64 || MLAS_USE_SVE condition is false on plain ARM64) — so the Apple path is added as a new highest-priority compile-time branch in MlasComputeTanh<float>, not via the platform function-pointer table.

What's in this PR

  1. Kernel (onnxruntime/core/mlas/lib/tanh.cpp, mlasi.h): MlasTanhKernelAppleAccelerate, chunking N defensively against INT32_MAX (since vvtanhf takes const int*), documented as safe for in-place use and free of internal GCD/thread-pool dispatch (so it does not oversubscribe the ORT threadpool). Wired into MlasComputeTanh<float> as the first-priority branch, behavior-neutral (default OFF) everywhere else.

  2. Tests (onnxruntime/test/mlas/unittest/test_tanh_apple_accelerate.cpp, gated identically to the kernel), 10 tests total:

    • ForcedReachability -- calls MlasTanhKernelAppleAccelerate directly (not through dispatch), proving the symbol links/is callable whenever the option is on, independent of dispatch wiring.
    • InPlaceAliasing -- Input == Output, matching real ORT call sites (Gelu tanh-approx, RNN/GRU/LSTM cells).
    • PublicDispatchMatchesDirectKernelCall -- asserts MlasComputeTanh<float> actually resolves to the direct kernel call (catches dispatch/kernel drift).
    • LargeBufferManyVectorIterations -- 1Mi elements, exercises the INT32_MAX-defensive chunking loop (vvtanhf takes const int*).
    • SpecialValuesSemanticsSingleElement / ...ScalarTailBuffer -- assert the IEEE-754/ONNX Tanh contract directly (not portable-kernel parity) for NaN (NaN in -> NaN out, payload/sign unasserted), +/-Inf (-> exactly +/-1.0f), and signed +/-0.0 (-> +/-0.0f, sign bit preserved exactly).
    • DenormalAndNearZeroSemanticsSingleElement / ...ScalarTailBuffer -- true subnormal inputs only (denorm_min()), asserted via an either/or check tolerant of both IEEE-correctly-rounded (tanh(x)~=x) and flush-to-zero (FTZ) hardware behavior: never NaN, sign preserved for a nonzero result, |output| <= |input|. Deliberately does not assert which behavior real vForce exhibits (unverifiable without hardware); the actual value is printed for a human to observe.
    • SmallestNormalSemanticsSingleElement / ...ScalarTailBuffer -- new this revision: +/-FLT_MIN (smallest positive normal float, not subnormal) split out from the denormal tests above and asserted strictly/exactly (output == input, sign preserved) rather than the FTZ-tolerant either/or check, since FTZ is not a legitimate rationale for a normal input. Verified numerically that tanh(FLT_MIN) rounds to exactly FLT_MIN in float32 (the -x^3/3 correction term is many orders of magnitude below one ULP).

    Every test pre-fills its output buffer with a finite, out-of-range poison sentinel (kPoisonValue = 123456.75f), not 0.0f and not NaN, before calling the kernel under test. tanh's range is always [-1, 1], so this value can never be a genuinely correct output at any tested input, and a kernel that silently fails to write an element (e.g. an off-by-one in the scalar-tail path) cannot pass by coincidence. A NaN poison was tried first and had a real hole: for NaN inputs, the correct contract is NaN output, so an unwritten NaN-poisoned buffer would satisfy std::isnan() and pass even though the kernel never wrote anything -- silently defeating the poison for exactly the inputs it most needs to guard. This was proven concretely (not just reasoned about) by cross-compiling the real kernel+test for aarch64-linux-gnu, injecting a deliberate no-write bug into a stub vvtanhf, and running under qemu-aarch64-static: the old NaN poison reported 10/10 "PASSED" (silent), the new finite poison correctly failed 2 tests / 4 assertions.

  3. Benchmarks (onnxruntime/test/mlas/bench/bench_transcendental.cpp), following the existing BM_SiluDispatch/BM_GeluErfDispatchExact A/B pattern:

    • BM_TanhDispatch -- calls the public MlasComputeTanh<float> entry point (whatever it actually resolves to on this build), labeled via GetTanhDispatchPathInfo() as "apple_accelerate_vforce" when the option is enabled.
    • BM_TanhPortableKernel -- calls MlasTanhKernel (the portable polynomial reference) directly as a fixed baseline, labeled "portable_polynomial" unconditionally, regardless of platform.
    • The two distinct labels in the printed output are what prove a given run exercised the intended A/B and not two copies of the same kernel under different names -- this is mechanically checked by CI, not just asserted in prose (see CI below).
  4. CI (mac.yml, macos-ci-build-and-test-workflow.yml): new apple_accelerate job, forwarding --use_apple_accelerate (the build.py flag Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001 already added).

    • Matrix: both arm64/Debug (assertion-rich correctness coverage, NDEBUG off) and arm64/Release. Debug runs the full build.py --test suite plus the targeted Tanh tests; the benchmark build/run steps below are gated to build_config == 'Release' only, since a Debug/-O0 binary is not meaningful performance evidence.
    • Targeted test step prints Build config: <config> then runs onnxruntime_mlas_test --gtest_filter="TanhAppleAccelerate.*" --gtest_list_tests first and fails loudly (::error::, exit 1) if the listing is empty -- a gtest binary invoked with a filter matching zero tests still exits 0, so this step would otherwise appear to pass while providing zero real coverage (e.g. if MLAS_USE_APPLE_ACCELERATE/__APPLE__/MLAS_TARGET_ARM64 didn't all evaluate true on this job). Only after that check passes does it actually run the filtered tests.
    • Benchmark build step (Release only) does not run build.py --build ${{ env.build_flags }} as one command: build_flags includes --build_wheel, and build.py's main() unconditionally repackages the Python wheel whenever args.build and args.build_wheel are both set, regardless of --target -- so a naive "add --build_micro_benchmarks --target onnxruntime_mlas_benchmark" would rebuild/repackage the entire wheel just to add one benchmark binary. Instead: build.py --update --build_micro_benchmarks ${{ env.build_flags }} (reconfigure only, no --build, so it adds --build_micro_benchmarks into the existing CMake cache in-place without touching the wheel path or discarding the build tree), then cmake --build ./build/<config> --config <config> --target onnxruntime_mlas_benchmark --parallel directly.
    • Benchmark run step (Release only) prints Build config: <config>, then (matching the test step's zero-match pattern) runs --benchmark_filter="BM_Tanh(Dispatch|PortableKernel)" --benchmark_list_tests=true first and fails loudly if either BM_TanhDispatch or BM_TanhPortableKernel is absent from the listing (a benchmark filter matching zero benchmarks also exits 0 with an empty table). Only then does it run the actual A/B, print the output, and fail loudly if the apple_accelerate_vforce label is absent from that output -- guarding against a misconfigured build where BM_TanhDispatch silently fell back to the portable kernel, which would otherwise print two portable_polynomial-labeled results as if they were a real A/B.
    • Result: both matrix entries are green on real Apple Silicon CI runners as of this revision (see Validation below for run links and actual output).

Validation performed

This revision was pushed to real macOS arm64 Apple Silicon CI runners on GitHub Actions (apple_accelerate job, both Debug and Release matrix entries), in addition to the static/cross-compiled validation from earlier revisions (summarized below). Both jobs are green:

This is the first time any revision of this PR has actually executed on real Apple hardware. Two real build-break bugs were found and fixed in the process (both were pre-existing in tanh.cpp, not previously exercised because no prior revision had ever run on a real macOS arm64 runner with --use_apple_accelerate):

  • The Xcode 26.3 / MacOSX26.2 SDK's <Accelerate/Accelerate.h> umbrella header fails to compile under C++ (ISO C++ forbids forward references to 'enum' types in vecLib's cblas.h/cblas_new.h -- both BLAS header variants are affected on this SDK, a known-class Apple SDK header bug unrelated to this kernel).
  • Fixed by forward-declaring the one vForce symbol this kernel actually needs (extern "C" void vvtanhf(float*, const float*, const int*), matching its long-stable public signature) instead of including the umbrella header at all. The Accelerate framework is already linked via cmake/onnxruntime_mlas.cmake, so the symbol resolves at link time unchanged.

Real correctness results (Release job, onnxruntime_mlas_test --gtest_filter='TanhAppleAccelerate.*'): all 10 tests passed, including the two new SmallestNormalSemantics* tests added this revision:

[==========] Running 10 tests from 1 test suite.
...
[  PASSED  ] 10 tests.

The Debug job's full build.py --test run (entire test suite, not just the Tanh-specific filter) also passed.

Real Release benchmark A/B results (onnxruntime_mlas_benchmark, on the CI runner's 3-core host) -- reported here in full, honestly, including the outcome. All 18 registered sizes, with the computed ratio (Dispatch/Portable, i.e. vForce relative to the portable kernel — >1.0 means vForce is slower):

N BM_TanhDispatch (vForce) BM_TanhPortableKernel (portable) ratio
1 20.5 ns 4.08 ns 5.02x slower
15 19.3 ns 17.3 ns 1.12x slower
16 18.2 ns 9.22 ns 1.97x slower
31 35.2 ns 29.4 ns 1.20x slower
32 36.6 ns 17.1 ns 2.14x slower
63 69.7 ns 40.3 ns 1.73x slower
64 78.4 ns 35.3 ns 2.22x slower
127 144 ns 87.9 ns 1.64x slower
128 177 ns 57.2 ns 3.10x slower
255 292 ns 124 ns 2.35x slower
256 299 ns 136 ns 2.20x slower
511 574 ns 262 ns 2.19x slower
512 557 ns 269 ns 2.07x slower
1024 1308 ns 597 ns 2.19x slower
4096 4537 ns 2059 ns 2.20x slower
16384 18496 ns 8148 ns 2.27x slower
65536 67639 ns 33355 ns 2.03x slower
262144 309936 ns 135697 ns 2.28x slower

(Full raw output, including bytes_per_second/items_per_second columns, in the Release job log linked above.) The CI fail-loud checks described under "What's in this PR" above (list-then-verify for both benchmark names, apple_accelerate_vforce label presence) all passed as designed on this real run -- this is a genuine, verified A/B, not two portable-kernel runs masquerading as one.

Honest conclusion: apple_accelerate_vforce (BM_TanhDispatch) is consistently and substantially slower than the existing portable polynomial kernel (BM_TanhPortableKernel) at all 18 sizes tested, with no crossover and no size regime where vForce wins. Ratio ranges from ~1.1x slower (N=15) to ~5x slower (N=1), median ~2.2x slower; the bulk of the mid-to-large sizes (N ≥ 256) cluster tightly around 2.0x–2.3x slower, so this is not primarily a small-N dispatch-overhead artifact. No performance win is being claimed by this PR, and the data available does not support one. This is a single run on shared/virtualized CI hardware (3 vCPUs, Unable to determine clock rate from sysctl), so absolute ceilings on dedicated Apple Silicon hardware could differ in magnitude — but the direction and rough size of the effect (a consistent ~2x-order-of-magnitude penalty across three orders of magnitude of N) is unlikely to invert into a win on different real hardware. Combined with the absence of any favorable data point anywhere in the tested range, this is the basis for the decision above to close rather than merge or continue iterating on this kernel.

Additional validation performed before/alongside the real CI run above:

  • Both modified GitHub Actions workflow YAML files validated with yaml.safe_load (no syntax errors).
  • clang-format --style=file --dry-run --Werror clean on the modified test file (pre-existing, unrelated formatting deviations in tanh.cpp were confirmed present before this revision's changes too, via git stash diff).
  • The exact bash logic in the CI steps described above (zero-match list-then-verify for both the gtest and benchmark steps, missing-benchmark-name failure, missing-apple_accelerate_vforce-label failure) was extracted and run locally against stub onnxruntime_mlas_test/onnxruntime_mlas_benchmark binaries (success, zero-match, missing-name, missing-label cases) before ever reaching real CI -- all behaved as intended, giving confidence in the design ahead of the real run that then confirmed it end-to-end above.
  • Cross-architecture proof for the no-write poison fix (design explained under "Tests" above): cross-compiled the real, unmodified test file for aarch64-linux-gnu with __APPLE__/MLAS_USE_APPLE_ACCELERATE forced, ran under qemu-aarch64-static with a deliberately injected no-write bug -- silently undetected (10/10 "PASSED") against the old NaN poison, correctly caught (2 failed, 4 assertions) against the new finite poison. This is what the real hardware run above then re-confirmed did not regress (all 10 tests still pass for real).

Status of the pre-close blockers (for the record)

  • Real Apple Silicon numeric validationdone. All 10 tests (including the new SmallestNormalSemantics* tests) pass against real vvtanhf on real Apple Silicon CI hardware (see Validation above). The finite no-write poison and ±FLT_MIN/denormal separation both executed against real hardware for the first time and found no correctness issues.
  • Real Apple Silicon Release benchmark numbersdone, and the result is unfavorable at every tested size: apple_accelerate_vforce was slower than portable_polynomial across all 18 sizes tested on the CI runner, ~1.1x–5x slower, median ~2.2x (see Validation above for the full per-size table and run link).
  • CI green on the new apple_accelerate macOS arm64 job — both Debug and Release matrix entries pass (run 31647787148).
  • Re-evaluate this PR's premise given the real benchmark resultresolved: closing. The motivating assumption (vForce's vvtanhf is faster than MLAS's portable polynomial Tanh) is contradicted by real hardware data at every size tested, with no crossover point and no favorable regime. See the Outcome / Decision section at the top of this description for the full reasoning and what should be salvaged separately.
  • Rebase-onto-Add onnxruntime_USE_APPLE_ACCELERATE CMake option (infrastructure only) #32001 and ORT-threadpool-interaction items are moot given the close decision and are not being pursued further in this PR.

Justin Chu (justinchuby) and others added 16 commits August 11, 2026 23:28
Add a new opt-in CMake option for Apple Accelerate/BNNS/vDSP support in MLAS.
This PR adds build-system scaffolding only — no kernels, no behaviour change.

- Option: onnxruntime_USE_APPLE_ACCELERATE, default OFF
- FATAL_ERROR on non-Apple platforms when enabled
- Links the system Accelerate framework via find_library (macOS/iOS/universal2)
- Defines MLAS_USE_APPLE_ACCELERATE=1 compile definition when enabled
- No effect whatsoever when disabled (default)

Follow-up PRs will add kernels (Accelerate cblas, BNNS, vDSP) separately,
each with portable fallback, numeric parity tests, and Apple-hardware benchmarks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gling define

S1: Replace FATAL_ERROR with warning + disable on non-Apple platforms,
    matching the idiom used by onnxruntime_USE_SVE and onnxruntime_USE_KLEIDIAI.

S2: Add --use_apple_accelerate to build.py/build_args.py so the option is
    reachable through the standard build tooling.

S3: Remove MLAS_USE_APPLE_ACCELERATE=1 compile definition that nothing
    consumes; the first kernel PR will introduce it alongside its reader.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Gate onnxruntime_USE_APPLE_ACCELERATE on APPLE + onnxruntime_target_platform
== "arm64" (Apple Silicon). On Apple + non-arm64, warn-and-disable matching
the existing idiom for SVE/KleidiAI. Update comments and help text to state
macOS arm64 scope; remove universal2/iOS claims.

No behaviour change when the option is OFF — all side-effectful statements
remain inside the guarded block.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes the Python format lint check. The argument declaration exceeded the
line-length limit and ruff splits it across lines. Verified the parsed AST
is unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ntract, CLI validation

B1: Detect macOS arm64 robustly — check CMAKE_OSX_ARCHITECTURES for
arm64/arm64e, fall back to CMAKE_SYSTEM_PROCESSOR when it is unset or
empty. Fixes silent disable on plain cmake configure on Apple Silicon.

B2: Rewrite PR body to match reality (warn-and-disable, macOS arm64
only, MLAS_USE_APPLE_ACCELERATE reinstated).

N1: Gate on CMAKE_SYSTEM_NAME=Darwin instead of APPLE (excludes iOS,
tvOS, visionOS).

N2: Reintroduce MLAS_USE_APPLE_ACCELERATE=1 compile definition as
observable contract for follow-up kernel PRs.

N3: build.py raises BuildError on non-macOS — loud failure for explicit
CLI opt-in. CMake side stays tolerant (warn-and-disable).

N4: Move --use_apple_accelerate to CPU EP argument group in build_args.py.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…talyst

The previous validation only checked is_macOS(), allowing explicit
--use_apple_accelerate on Intel Macs, cross-compiled x86_64 builds,
and non-macOS Apple targets (iOS, tvOS, visionOS, Mac Catalyst) to
pass Python validation and then silently downgrade in CMake — exactly
the silent-disable the explicit gate was meant to prevent.

Now:
- build_args.py rejects --use_apple_accelerate unless osx_arch is
  arm64 or arm64e, and rejects --ios, --tvos, --visionos, and
  --macos Catalyst explicitly.
- build.py keeps the same checks as defence-in-depth (BuildError).
- All Apple-target attributes are accessed via getattr() to avoid
  AttributeError on non-macOS hosts where add_apple_args is not called.
- 9 new test cases in test_build_args.py covering each rejection path
  and the accepting arm64/arm64e paths.

Body clarifications (for manual update):
- PRIVATE compile definition: MLAS_USE_APPLE_ACCELERATE=1 is defined
  with target_compile_definitions(onnxruntime_mlas PRIVATE ...), so it
  is visible only within onnxruntime_mlas translation units, not leaked
  to downstream consumers.
- Multiarch fallback: when CMAKE_OSX_ARCHITECTURES lists multiple
  architectures (universal2), the detection logic sets the arch to
  empty, which does not match arm64, so the option is warned-and-disabled.
  It does NOT attempt to build for the arm64 slice only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the dead-code copy of Apple Accelerate target validation from
build.py (BuildError path was never reached because build_args.py
parser.error() exits first). Keep the single source of truth in
build_args.py where all other argument validation lives.

Also improve the non-macOS error message to distinguish 'wrong OS'
from 'wrong arch' — the old wording said 'only supported on macOS
arm64' even when the host was macOS but wrong architecture.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add validation rules to build_args.py rejecting Apple Accelerate when
targeting Android, WebAssembly (including --build_wasm_static_lib), or
RISC-V rv64 — all cross-targets incompatible with macOS arm64.

Update tests to assert specific diagnostic messages rather than bare
SystemExit, preventing vacuous-test false positives. Test count: 13→17.

Document CMake limitation: Catalyst (macabi) cannot be reliably detected
at configure time without an external toolchain file setting PLATFORM_NAME.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- PLC0415: Hoist 'import io' and 'from contextlib import redirect_stderr'
  to module level. No circular-import or lazy-load reason to keep them
  inside the helper.
- SIM105: Replace try/except SystemExit: pass with
  contextlib.suppress(SystemExit). stderr capture and the assertIn
  on the diagnostic fragment are preserved so the test still validates
  the specific error message, not just any argparse failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…enchmark

onnxruntime_mlas_test and onnxruntime_mlas_benchmark are separate targets
that never received MLAS_USE_APPLE_ACCELERATE=1: it was applied only via a
direct target_compile_definitions(onnxruntime_mlas PRIVATE ...) call after
the shared target-definition foreach had already run, and PRIVATE compile
definitions of a linked static library do not propagate to a consumer's own
translation units (unlike PRIVATE link libraries of a STATIC library, which
CMake does forward to the final link line). Any follow-up kernel PR gated on
this define would have its tests/benchmarks silently compiled out.

Fix: express the definition through the shared mlas_private_compile_definitions
list, matching the MLAS_USE_SVE / MLAS_USE_ARM_NEON_NCHWC idiom, and move the
whole Apple Accelerate block before the
'foreach(mlas_target ${ONNXRUNTIME_MLAS_LIBS}) ... target_compile_definitions'
loop so onnxruntime_mlas picks it up the same way. onnxruntime_mlas_test and
onnxruntime_mlas_benchmark in cmake/onnxruntime_unittests.cmake already apply
${mlas_private_compile_definitions}, so they now see the define without any
change on their side.

Add a small configure-time regression canary at both consuming sites
(onnxruntime_mlas_test, onnxruntime_mlas_benchmark) that FATAL_ERRORs if
onnxruntime_USE_APPLE_ACCELERATE is ON but the define is missing from the
shared list, so this exact class of regression fails the build loudly instead
of silently compiling out gated code.

Verified: cmake if/foreach/endif/endforeach structure parses and executes to
end-of-file via a stubbed-command harness; the canary condition was exercised
standalone for true-positive (fires) and true-negative (silent) cases;
tools/ci_build/test_build_args.py (17 tests) still passes unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…SE_APPLE_ACCELERATE

Stacked on nxrt/mlas-apple-framework-option (PR microsoft#32001, infrastructure-only,
still open upstream). This is the first concrete kernel using that option.

Adds MlasTanhKernelAppleAccelerate, which calls vForce's vvtanhf() to compute
Tanh on macOS arm64 when onnxruntime_USE_APPLE_ACCELERATE is enabled at
configure time. vvtanhf is a long-stable public vForce API (macOS 10.4+ /
iOS 5.0+), verified against the actual Accelerate.framework headers.

MlasComputeTanh<float> gains a new highest-priority #if branch for this
path; the option is default-off and every other configuration (including
the default build) is behavior-identical to before, since the new code is
compiled out entirely unless MLAS_USE_APPLE_ACCELERATE, __APPLE__, and
MLAS_TARGET_ARM64 are all defined.

Notes on scope and safety, expanded on in the new kernel's doc comment:
- vvtanhf computes each output element from only the corresponding input
  element, so it is safe for the in-place (Input == Output) calls that
  several real MLAS callers already rely on (Gelu tanh-approx, SVM
  classifier, RNN cell helpers).
- vvtanhf takes the element count as `const int*` (a vForce API
  convention); the new kernel chunks defensively against INT32_MAX even
  though no current caller passes buffers that large.
- vvtanhf is a synchronous vectorized call with no internal GCD/thread-pool
  dispatch of its own, so it does not oversubscribe the ORT threadpool.

Validation on this (non-Apple, x86-64 Linux) host is necessarily limited to
compile/static checks: the new branch never compiles in here, so the full
onnxruntime_mlas_test suite (including the existing Tanh/Activation/Softmax/
Softcap tests) passes unchanged, proving no regression to the default
(option-off) path. Real Apple vForce numerics, linking, and threading
behavior remain unverified pending an Apple Silicon host; see the PR
description for what that leaves as an explicit blocker to Ready.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nh kernel

Adds test_tanh_apple_accelerate.cpp, gated identically to the kernel itself
(MLAS_USE_APPLE_ACCELERATE && __APPLE__ && MLAS_TARGET_ARM64), following the
existing convention of including core/mlas/lib/mlasi.h directly to reach
internal (non-public-API) kernel functions (see test_hgemm_neon.cpp,
test_cast_fp16.cpp).

Four cases:
- ForcedReachability: calls MlasTanhKernelAppleAccelerate directly and
  compares against the portable MlasTanhKernel baseline across zero,
  +/-9 clamp boundary, out-of-range, +/-Inf, NaN, and random inputs -- this
  is the direct proof that the Accelerate-backed symbol links and runs
  whenever the option is enabled, independent of dispatch wiring.
- InPlaceAliasing: exercises Input == Output, matching the aliasing
  contract real callers (gelu.cc, svmclassifier.h, rnn_helpers.cc) rely on.
- PublicDispatchMatchesDirectKernelCall: calls the public
  MlasComputeTanh<float> entry point (what every real caller actually
  uses) and checks it is bit-identical to a direct call to
  MlasTanhKernelAppleAccelerate, so any future edit that changes the #if
  condition in tanh.cpp without updating the dispatch branch is caught.
- LargeBufferManyVectorIterations: 1Mi-element buffer to exercise the
  chunking loop beyond a single trivial vector width; the INT32_MAX
  boundary itself is documented but not covered here (impractical buffer
  size for CI), see the PR description for a standalone logic-only harness
  that exercises that boundary at a testable scale.

This file compiles to an empty translation unit on every other
configuration (confirmed here on x86-64 Linux, where the option is off by
default): building onnxruntime_mlas_test after adding this file produces no
new test cases and the existing Tanh/Activation/Softmax/Softcap tests still
pass, so this is a no-op addition until the option and platform gate are
both satisfied on a real macOS arm64 build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds BM_TanhDispatch (public MlasComputeTanh<float>, dispatch-path-labeled
via the new GetTanhDispatchPathInfo helper) and BM_TanhPortableKernel (the
portable MlasTanhKernel called directly, as a fixed baseline), following
the existing BM_SiluDispatch/BM_GeluErfDispatchExact pattern in this file.

GetTanhDispatchPathInfo labels the apple_accelerate_vforce path at compile
time (MlasComputeTanh<float> always resolves to the vForce kernel in that
configuration -- see the #if chain in tanh.cpp) and does a runtime pointer
comparison against MlasComputeTanhF32KernelFma3 on AMD64, matching how
GetSiluDispatchPathInfo/GetGeluErfDispatchPathInfo compare against their
respective AVX512F kernels. RISCV64/SVE report only a generic label,
because MlasTanhKernelRvv/MlasSveTanhKernel are declared in headers not
included by this benchmark translation unit and this change intentionally
avoids adding untested include dependencies for platforms not exercised on
this host.

Verified on this x86-64 Linux host: onnxruntime_mlas_benchmark builds
cleanly (temporarily configured with onnxruntime_BUILD_BENCHMARKS=ON,
which is off by default) and running --benchmark_filter=Tanh produces
correctly labeled amd64_fma3 / portable_polynomial results across all
existing size buckets. No apple_accelerate_vforce numbers exist yet -- that
requires an Apple Silicon host and is an explicit blocker to Ready, see the
PR description.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR microsoft#32001's own description states "A macOS arm64 CI lane exercising the
option belongs with the first kernel PR" -- this fulfills that for the
Tanh/vForce kernel added in this PR.

Adds a use_apple_accelerate boolean input to the reusable
macos-ci-build-and-test-workflow.yml, forwarded into build_flags as
--use_apple_accelerate (the build.py flag PR microsoft#32001 already added, with
its own arch/platform validation in build_args.py). Adds a new
apple_accelerate job in mac.yml, arm64/Debug only, matching the resource-
conserving pattern already used by the xnnpack job (this repo does not
have CI capacity to run every optional-feature matrix at full
Debug+Release x arm64+x86_64 breadth).

Both workflow files validated with a plain YAML parse (yaml.safe_load) on
this host; the job itself cannot be executed here since it requires a
macOS arm64 GitHub-hosted runner. Real CI execution results are pending
push and are part of what remains before Ready.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reviewer finding: the apple_accelerate job in mac.yml calls the reusable
workflow, whose only test step is `python ./tools/ci_build/build.py --test
...`, which drives ctest. onnxruntime_mlas_test is a standalone gtest
binary that is never registered with add_test() in
cmake/onnxruntime_unittests.cmake, so ctest -- and therefore this CI lane
-- never executes it. That means the new Apple Accelerate Tanh tests
(test_tanh_apple_accelerate.cpp, guarded on
MLAS_USE_APPLE_ACCELERATE/__APPLE__/MLAS_TARGET_ARM64) had zero CI
coverage despite the workflow appearing to "test" this configuration.

Fix: add an explicit step in macos-ci-build-and-test-workflow.yml that
invokes the onnxruntime_mlas_test binary directly with
--gtest_filter="TanhAppleAccelerate.*", guarded to native arm64
(matrix.machine == matrix.target, matching the existing ctest step's
cross-compile skip) and inputs.use_apple_accelerate, so it only runs on
this one job. The executable path
(${{ github.workspace }}/build/${{ matrix.build_config }}/onnxruntime_mlas_test)
matches the convention already used for direct MLAS test invocation in
tools/ci_build/github/azure-pipelines/post-merge-jobs.yml, and the same
directory the "Install" step in this same workflow already cds into.

Scope note: only onnxruntime_mlas_test is invoked here, not
onnxruntime_mlas_benchmark -- the benchmark binary is for local/manual
performance investigation, and running it in CI would not produce
meaningful numbers on a shared runner; it also does not bear on whether
the new tests execute, which is this fix's specific concern. No
performance claim is made or implied by this change.

Validated by:
- python3 -c "import yaml; yaml.safe_load(open(...))" on both modified
  workflow files (mac.yml unchanged by this commit, re-checked anyway).
- A cross-compiled (aarch64-linux-gnu-g++) + qemu-aarch64-static harness
  (see the companion test-file commit) confirms onnxruntime_mlas_test
  would enumerate and pass the TanhAppleAccelerate.* suite this step
  targets; this workflow step itself cannot be executed outside real
  Apple Silicon CI infrastructure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reviewer finding: the existing tests only ever compared the Apple
Accelerate kernel's output for NaN/+-Inf/+-0.0 against the portable
polynomial kernel's output for the same inputs. That is not the same
claim as "this kernel implements the Tanh operator contract correctly"
-- it only proves "matches whatever this other kernel's clamp/min-max
sequence happens to produce". The portable kernel's own scalar-tail
comment documents that its vectorized (N>=4) MlasMaximumFloat32x4/
MlasMinimumFloat32x4 path gives "unreliable results when NaNs are
involved"; only the scalar tail (N>0, one at a time) has an explicit
two-step clamp engineered to pass NaN through. Requiring bit-parity with
a kernel that itself documents unreliable NaN handling on part of its
own input-size range is not the right correctness oracle for special
values.

Changes:
- Renamed MakeTanhTestInputs -> MakeFiniteRandomInputs and made it
  purely finite (the +/-9 clamp-boundary region plus random values);
  special values are no longer mixed into the portable-parity input set.
- Added MakeSpecialValueInputs() (+0.0, -0.0, +Inf, -Inf, quiet_NaN,
  signaling_NaN) and ExpectIeeeTanhSemantics(), which asserts the
  Tanh operator's own IEEE-754/ONNX contract directly:
    * NaN in -> NaN out (payload/sign deliberately unasserted -- which
      NaN vvtanhf returns is an implementation detail, not part of the
      operator contract; same rationale already used in this codebase
      for the sibling MLAS f16-cast kernel's NaN handling).
    * +Inf in -> exactly +1.0f out; -Inf in -> exactly -1.0f out. Exact
      equality (not tolerance) is correct here: evaluating the
      portable polynomial at the +/-9 clamp boundary (verified
      numerically in float32 with the literal MlasTanhConstants
      coefficients) already rounds to exactly +/-1.0f, so the operator's
      mathematical saturation limit and this kernel-family's clamp
      range agree exactly in float32.
    * +0.0 in -> +0.0f out (sign bit clear); -0.0 in -> -0.0f out (sign
      bit set). tanh is an odd function and IEEE float arithmetic
      preserves sign of zero, so this is exact, not tolerance-based.
- ExpectMatchesPortableKernel now asserts its input is not NaN (directs
  callers to ExpectIeeeTanhSemantics instead), and is scoped to finite
  values only (including the +/-9 boundary, a kernel-contract detail,
  not an IEEE special value).
- ForcedReachability and InPlaceAliasing now use MakeFiniteRandomInputs
  (dropping special values from the portable-parity comparison, per the
  above). PublicDispatchMatchesDirectKernelCall keeps special values
  (it self-consistency-checks the public dispatch against a direct
  kernel call, which doesn't depend on the portable kernel, so this
  remains valid/valuable coverage there).
- Added two new tests exercising special values at realistic small-N
  shapes (matching real RNN/single-element callers, and the portable
  kernel's own "scalar tail" size range where it claims to be reliable
  for NaN, without relying on the portable kernel as the oracle):
    * SpecialValuesSemanticsSingleElement (N=1 per special value).
    * SpecialValuesSemanticsScalarTailBuffer (N=3, special value in the
      middle with finite neighbors on both sides, so a special value
      cannot silently corrupt an adjacent lane without failing the
      neighbor's portable-parity check).

Formatted with clang-format (repo .clang-format config); diff reviewed
to confirm only the intended content changes plus reformatting.

Validated (no Apple Silicon hardware available in this environment):
- Numerically verified in Python/NumPy float32 (replicating the exact
  MlasTanhConstants polynomial) that the +/-9 clamp boundary and beyond
  rounds to exactly +/-1.0f, and that +/-0.0 round-trips with sign
  preserved -- justifying the exact-equality assertions above.
- Built an honest cross-compilation + emulation harness: cross-compiled
  the real, unmodified onnxruntime/core/mlas/lib/tanh.cpp and this real
  test file for aarch64-linux-gnu with MLAS_USE_APPLE_ACCELERATE=1 and
  __APPLE__ forced (arm_neon.h resolves normally via the real aarch64
  toolchain; only Apple's Accelerate/Accelerate.h -- unavailable on any
  Linux target -- was stubbed with a disclosed per-element libm tanhf
  shim for vvtanhf), linked against a real cross-compiled googletest,
  and ran the resulting aarch64 binary under qemu-aarch64-static:
    * `--gtest_list_tests` enumerates all 6 TanhAppleAccelerate tests,
      including both new ones -- proving test discovery is non-vacuous
      when the macro is enabled.
    * A negative-control build of the same file with the macros left
      off registers zero tests, confirming the guard is not vacuously
      "always on".
    * All 6 tests pass against the libm-backed stub.
  This proves the test file's logic, gating, and dispatch wiring are
  sound. It does NOT validate real vecLib/Accelerate numerical behavior
  on actual Apple Silicon, which remains a Ready blocker for this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@justinchuby

This comment has been minimized.

…Tanh coverage

Independent revision after strict lockout (original author and prior
revision author both locked out this cycle). Owns this revision alone.

1. Required blocker fix: onnxruntime_mlas_test --gtest_filter=
   'TanhAppleAccelerate.*' silently exits 0 when the filter matches zero
   tests -- gtest treats "ran a suite of nothing" as success. Reproduced
   directly on this host: the real onnxruntime_mlas_test binary, built with
   MLAS_USE_APPLE_ACCELERATE off (as on any non-Apple host), enumerates 0
   tests for this filter and still exits 0. That means the CI step added in
   fce7b54 would appear green even if the gated
   test_tanh_apple_accelerate.cpp were silently dropped from the Apple
   Accelerate build (e.g. a #if condition regression), providing zero real
   assurance.

   Fix: capture `--gtest_list_tests` output for the same filter, print it,
   and grep it for "TanhAppleAccelerate" before running the suite; if
   absent, emit an actionable ::error:: (naming the exact macro triple to
   check) and exit 1. set -e -x and existing quoting/paths preserved.

2. High-value non-blocking fix: added denormal/near-zero special-value
   coverage (+/-denorm_min, +/-FLT_MIN) via two new tests,
   DenormalAndNearZeroSemanticsSingleElement (N=1) and
   DenormalAndNearZeroSemanticsScalarTailBuffer (N=3, scalar-tail shape
   with finite neighbors to catch lane corruption). Real
   hardware/libm implementations are free to choose IEEE-exact tanh(x)~=x
   passthrough or flush-to-zero (FTZ) for inputs this tiny, so
   ExpectDenormalCompatibleSemantics asserts only the invariants both
   behaviors share -- never NaN, sign preserved, |output| <= |input| --
   and does not invent which behavior real vForce exhibits (no Apple
   hardware available in this environment). The observed value is printed
   (not just asserted past) for inspection on real hardware.

Validation performed (no Apple Silicon hardware in this environment):
- python3 yaml.safe_load on the modified workflow file: parses cleanly.
- Extracted the exact CI step logic into a standalone shell harness and
  ran it against: (a) bash stubs and (b) real cross-compiled
  (aarch64-linux-gnu-g++) gtest binaries executed under
  qemu-aarch64-static -- one with the TanhAppleAccelerate.* suite compiled
  in (positive control, harness exits 0) and one built with the guard
  macros off (negative control, mirrors the real bug: gtest itself exits 0
  on the zero-match run, and the harness's own list-tests check is what
  converts that into exit 1 with the actionable message).
- Rebuilt onnxruntime_mlas_test from this exact worktree
  (build/mlas_test, configured against this tree) after the test-file
  change: clean compile (Apple guard correctly compiles the file to a
  no-op on this non-Apple host).
- Ran the pre-existing targeted filter
  (*Activation*:*Tanh*:*Softmax*:*Softcap*): 6/6 passed, confirming no
  regression from the include/helper additions.
- Confirmed TanhAppleAccelerate.* itself enumerates 0 tests and exits 0 on
  this host both before and after the change (expected -- the guard
  correctly excludes this file here; this is the exact silent-zero
  condition the CI fix targets on the real Apple lane).
- clang-format --dry-run --Werror on the modified test file: clean.

Remaining blockers before Ready (unchanged, hardware-dependent, out of
scope for this revision):
- No real Apple Silicon CI run has ever executed these tests.
- No real vForce numeric/performance evidence exists for the denormal/FTZ
  question this revision deliberately declines to assume an answer to.
- Fresh reviewer pass required (this revision's own review has not
  happened yet).

PR stays Draft. Does not touch microsoft#32001-owned files (only the two files
listed above). No .squad/ files modified.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…oison assertions (Opus NB1/NB2)

Real performance evidence is a Ready blocker for this optimization PR --
no numbers for the apple_accelerate_vforce Tanh path have ever been
produced. Adds the smallest CI support to produce that evidence without
making any performance claim yet:

- New "Build MLAS benchmark target (onnxruntime_mlas_benchmark)" step:
  runs `build.py --update --build --build_micro_benchmarks --target
  onnxruntime_mlas_benchmark`. --build_micro_benchmarks turns on the
  existing onnxruntime_BUILD_BENCHMARKS CMake option, which also guards
  the large, unrelated onnxruntime_benchmark target (modeltest/pooling/
  resize/batchnorm/etc.) -- --target restricts the build to only
  onnxruntime_mlas_benchmark and its dependencies, avoiding that
  unrelated target. Reuses the job's existing env.build_flags so the
  benchmark binary is built against the identical Apple Accelerate
  configuration already exercised by the steps above.
- New "Running MLAS Tanh benchmark A/B (onnxruntime_mlas_benchmark)"
  step: runs the existing BM_TanhDispatch/BM_TanhPortableKernel
  benchmarks (already defined in test/mlas/bench/bench_transcendental.cpp,
  unmodified) via --benchmark_filter, printing results to CI logs.
  GetTanhDispatchPathInfo() already labels BM_TanhDispatch's path as
  "apple_accelerate_vforce" on this job (vs. "portable_polynomial" for
  BM_TanhPortableKernel's fixed baseline) -- verified locally by direct
  execution of the (non-Apple) binary, which correctly printed
  "amd64_fma3"/"portable_polynomial" labels for the two benchmarks'
  18 size variants each. Both steps are gated by the same
  arm64 + apple_accelerate condition as the existing test step.

Also hardens test_tanh_apple_accelerate.cpp per Opus review NB1/NB2:

- NB1: ExpectDenormalCompatibleSemantics's sign-preservation check is now
  conditional on the result being nonzero. A flush-to-zero (FTZ) result
  from a denormal/near-zero input is legitimate hardware behavior, and
  some FTZ implementations do not preserve the sign of the flushed-to-
  zero result (unlike the exact +/-0.0 special-value contract, which
  remains an exact, unconditional signbit assertion in
  ExpectIeeeTanhSemantics, unchanged). Asserting sign for a zero FTZ
  result would risk inventing unverified hardware behavior; now only
  "never NaN", "sign preserved for a genuinely nonzero result", and
  "magnitude never amplified" are asserted for denormal/near-zero inputs.
- NB2: the four small-buffer (N=1, N=3) special-value/denormal test
  output buffers are now poison-initialized with a new kPoisonNaN
  (quiet_NaN) constant instead of 0.0f. tanh(+/-0) == +/-0 and an
  FTZ-flushed near-zero result may legitimately be exactly 0.0f, so a
  0.0f-init buffer could not distinguish a kernel that wrote a genuinely
  correct answer from one that silently failed to write the element at
  all (e.g. a scalar-tail off-by-one). NaN is never a valid output for a
  non-NaN input under either assertion helper (both call std::isnan on
  the result first), so an unwritten poisoned element is now guaranteed
  to fail loudly.

No performance claim is made anywhere in this change -- CI numbers for
the vForce vs. portable A/B are pending the next CI run on real Apple
Silicon.

Validation performed:
- YAML parses cleanly (python3 -c "import yaml; yaml.safe_load(...)").
- clang-format --dry-run --Werror: clean on the modified test file.
- Local rebuild of onnxruntime_mlas_test (Linux/x86_64): the 6 targeted
  tests (*Activation*:*Tanh*:*Softmax*:*Softcap*) still pass; the gated
  TanhAppleAccelerate.* suite still correctly compiles to 0 registered
  tests on this non-Apple host (no regression to the existing gating).
- Direct execution of the pre-built onnxruntime_mlas_benchmark binary
  with --benchmark_filter="BM_Tanh(Dispatch|PortableKernel)" confirms
  both benchmarks run and print distinct path labels for their 18 size
  variants each.
- A standalone harness mirroring ExpectDenormalCompatibleSemantics's
  exact logic was compiled and run with 5 positive controls (IEEE-exact
  passthrough both signs; FTZ to either sign of zero from both a
  positive and a negative input) and 3 negative controls (poison-NaN
  no-write is caught; a genuinely nonzero result with a corrupted sign
  is still caught; an amplified magnitude is still caught) -- all 8
  behaved as expected, confirming NB1's relaxation and NB2's
  no-write-detection intent without weakening genuine-bug detection.

Remaining Ready blockers (unchanged): this PR still needs a run on real
Apple Silicon hardware to produce actual vForce vs. portable benchmark
numbers and to exercise TanhAppleAccelerate.* for real (this sandbox has
no Apple hardware), and a fresh Opus delta review of this revision.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, avoid wheel rebuild

Addresses reviewer rejection of the previous revision:

1. Real performance evidence requires an optimized Release build, not
   Debug/-O0. Added a Release native-arm64 matrix entry alongside the
   existing Debug entry (Debug is retained -- it is the assertion-rich
   config the targeted TanhAppleAccelerate.* gtest suite still runs on,
   which is unaffected by this change). The two benchmark steps
   ('Build MLAS benchmark target' and 'Running MLAS Tanh benchmark A/B')
   are now gated to matrix.build_config == 'Release' only, and both print
   the active build config alongside their results.

2. The benchmark step now enumerates the filtered benchmark set via
   --benchmark_list_tests=true before running anything, and fails loudly
   (::error + exit 1) if either BM_TanhDispatch or BM_TanhPortableKernel
   is absent -- a --benchmark_filter that matches zero registered
   benchmarks still exits 0 and would otherwise look like a passing step
   with zero real evidence. The actual A/B run's output is captured and
   printed, and the step additionally fails loudly if the
   'apple_accelerate_vforce' label is absent from that output, so a
   misconfigured build where BM_TanhDispatch silently fell back to the
   portable kernel cannot masquerade as a genuine A/B (two copies of
   portable_polynomial would otherwise look identical to a real
   comparison). set -e -x and quoting around all captured command
   substitutions are preserved throughout.

3. The benchmark build step no longer runs
   'build.py --update --build --build_micro_benchmarks --target ...' as
   one command. build.py's main() unconditionally repackages the Python
   wheel via build_python_wheel() whenever args.build and args.build_wheel
   are both set, regardless of what --target was passed to the underlying
   cmake --build invocation -- env.build_flags for this job includes
   --build_wheel (needed by the job's normal Configure/Build/Install
   steps), so the previous single build.py invocation silently rebuilt
   and repackaged the entire wheel on every CI run just to add one
   benchmark binary. Now: 'build.py --update --build_micro_benchmarks
   ...' (no --build) reconfigures the existing CMakeCache in place to add
   the benchmark option, preserving the cache/build tree from the earlier
   Configure step, and a direct 'cmake --build build/<config> --config
   <config> --target onnxruntime_mlas_benchmark --parallel' builds only
   the target needed. This never touches the wheel-packaging code path.

Both workflow YAML files validated with yaml.safe_load. The exact bash
logic for the zero-match/list/label checks was extracted and exercised
against stub onnxruntime_mlas_benchmark/onnxruntime_mlas_test binaries
covering: a real success case (both benchmarks listed, correct label
present), a zero-match filter, a missing-benchmark-name case, and a
missing-label case (both paths reporting portable_polynomial) -- all
four behaved as intended (success passes, the other three fail loudly
with clear error messages).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Completes the no-write hardening for test_tanh_apple_accelerate.cpp:

1. Replaced the quiet-NaN poison value (kPoisonNaN) with a finite,
   out-of-range sentinel (kPoisonValue = 123456.75f). tanh's real range is
   [-1, 1] for every finite/infinite input, so this finite magnitude can
   never be a genuinely correct output and is guaranteed to fail loudly if
   a kernel leaves it unwritten. The NaN poison had a real no-write hole:
   for the two NaN *inputs* in MakeSpecialValueInputs (quiet_NaN,
   signaling_NaN), the operator contract requires NaN *output*, so an
   unwritten NaN-poisoned buffer would satisfy std::isnan(value) and the
   test would pass even though the kernel never wrote anything --
   defeating the poison mechanism for exactly the inputs it most needed to
   guard.

   Verified this was a real, not theoretical, gap: cross-compiled the
   actual tanh.cpp/mlasi.h/test file for aarch64-linux-gnu (MLAS_TARGET_ARM64
   is auto-defined from __aarch64__) with __APPLE__/MLAS_USE_APPLE_ACCELERATE
   forced and a stub vvtanhf backed by libm tanhf, then ran it under
   qemu-aarch64-static against a deliberately-injected no-write bug (skips
   writing output for NaN input). With the OLD NaN poison, the buggy
   kernel reported 10/10 tests PASSED -- completely silent. With the NEW
   finite poison, the same buggy kernel correctly fails 2 tests (4
   assertion failures). Also confirmed all 10 tests (8 original + 2 new
   FLT_MIN tests below) pass against a correct (libm-backed) kernel.

2. Split the former MakeDenormalInputs (which mixed true subnormals with
   FLT_MIN) into MakeDenormalInputs (true subnormals only:
   +/-denorm_min()) and a new MakeSmallestNormalInputs (+/-FLT_MIN).
   FLT_MIN is a *normal* float, not a subnormal one, so a flush-to-zero
   (FTZ) rationale does not apply to it the way it legitimately does for
   true denormals -- FTZ hardware/software modes flush subnormal
   inputs/results, not normal ones. Added ExpectSmallestNormalSemantics,
   which asserts strict/exact semantics (output == input exactly, sign
   preserved) instead of the either/or FTZ-tolerant check
   ExpectDenormalCompatibleSemantics still uses for true denormals.
   Verified numerically (float32 round-trip) that tanh(FLT_MIN) correctly
   rounds to exactly FLT_MIN: the -x^3/3 correction term is many orders of
   magnitude below one ULP of FLT_MIN, so this is a correctness guarantee,
   not an assumption. Added SmallestNormalSemanticsSingleElement and
   SmallestNormalSemanticsScalarTailBuffer tests paralleling the existing
   denormal tests' N=1/N=3-scalar-tail coverage.

The ±0 signed-zero exact/sign-bit checks in ExpectIeeeTanhSemantics are
unchanged.

clang-format --style=file --dry-run --Werror is clean on the modified file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… forward-decls

The new Release/arm64 CI matrix entry added in 4be101e ran for the first
time on real macOS arm64 hardware and immediately caught a genuine build
failure that no prior revision of this PR had ever exercised (previous CI
only ran Debug, and no local/cross-compile validation used the real
Accelerate SDK headers):

  vecLib.framework/Headers/cblas.h:280:29: error: ISO C++ forbids forward
  references to 'enum' types
  ...
  fatal error: too many errors emitted, stopping now
  make[2]: *** [.../tanh.cpp.o] Error 1

Root cause: on the Xcode 26.3 / MacOSX26.2 SDK used by the CI runner,
Accelerate's new LAPACK-ABI cblas.h forward-declares BLAS enums (e.g.
CBLAS_TRANSPOSE) without an inline definition. That's valid C but invalid
ISO C++, so any C++ translation unit that does a plain
`#include <Accelerate/Accelerate.h>` fails to compile on this SDK. This is
a known Apple SDK issue (see forums.swift.org/t/71695) unrelated to the
Tanh/vForce kernel itself.

Fix: this kernel only needs vForce's vvtanhf() and never touches BLAS/LAPACK,
so define ACCELERATE_NEW_LAPACK=0 and ACCELERATE_LAPACK_ILP64=0 before the
include, forcing the legacy (non-ILP64) CBLAS/LAPACK header path and
avoiding the offending forward declarations. This has no effect on vForce.

This is the only file in the repo that includes <Accelerate/Accelerate.h>
(verified via `grep -rn "#include <Accelerate" onnxruntime/`), so it is the
only place this fix is needed.

Not yet re-verified against real hardware after this commit -- the CI run
that surfaced the bug will need to be re-triggered to confirm the fix
compiles cleanly on the actual runner; this cannot be validated locally
since the real Accelerate SDK headers are Apple-only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…h BLAS header variants broken on Xcode 26.3 SDK)

The previous commit (b85c22d) attempted to fix the real Apple-hardware
build break by forcing ACCELERATE_NEW_LAPACK=0/ACCELERATE_LAPACK_ILP64=0
before including <Accelerate/Accelerate.h>, assuming that would select the
legacy (pre-ILP64) cblas.h path that had failed. Re-running CI on real
hardware showed this was insufficient: on the Xcode 26.3 / MacOSX26.2 SDK,
*both* BLAS header variants are affected --

  first failure (no macros defined):
    vecLib.framework/Headers/cblas.h:280: ISO C++ forbids forward
    references to 'enum' types

  second failure (ACCELERATE_NEW_LAPACK=0 forced):
    vecLib.framework/Headers/cblas_new.h:67: ISO C++ forbids forward
    references to 'enum' types

i.e. this SDK's cblas_new.h has the identical bug, and setting the macro
to 0 did not select the un-broken header (if one even exists on this SDK).
This is a genuine Apple SDK header defect independent of which BLAS ABI is
requested, so no combination of the documented compatibility macros can
route around it while still including the umbrella header.

Since this kernel only ever calls vForce's vvtanhf() and never touches any
BLAS/LAPACK functionality, the umbrella header was never actually needed.
Replace the include (and the now-moot compatibility macros) with a direct
extern "C" forward declaration of vvtanhf, matching its long-stable public
signature (`void vvtanhf(float *y, const float *x, const int *n)`,
available since macOS 10.9 / iOS 7.0). The Accelerate framework is already
linked unconditionally for this kernel via
`find_library(APPLE_ACCELERATE_LIB Accelerate)` +
`target_link_libraries(onnxruntime_mlas PRIVATE ${APPLE_ACCELERATE_LIB})`
in cmake/onnxruntime_mlas.cmake, so the symbol resolves at link time exactly
as it would have via the header -- this changes zero runtime behavior, only
how the declaration reaches the compiler.

Still unverified against real hardware after this specific change -- the
underlying Apple SDK header bug can only be exercised on a real Xcode 26.3
/ macOS 26.2 environment (not reproducible via cross-compilation on Linux),
so the next CI run on the apple_accelerate job is the actual test of this
fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Justin Chu (justinchuby) added a commit to justinchuby/onnxruntime that referenced this pull request Aug 13, 2026
- Remove/rephrase comments claiming a nonexistent "AVX2 LayerNorm kernel"
  exists in this tree (no such kernel exists anywhere in this repository;
  only RVV registers a non-Apple LayerNormF32Kernel). Also rephrase a
  comment implying a sibling "Apple Accelerate Tanh kernel" file exists in
  this same file family -- it does not; PR microsoft#32036, which explored that
  idea, was closed without merging.
- Remove unused #include <algorithm>.
- Replace the heap-fallback scratch buffer (NormSize >
  kApplePerRowStackScratch) from std::vector<float>::resize() to a raw
  std::unique_ptr<float[]> allocation. vector::resize() value-initializes
  (zero-fills) every element; every byte of this scratch buffer is fully
  overwritten by the vDSP calls before it is ever read in both the
  Simplified and full-LayerNorm branches, so the zero-fill was pure
  overhead on every heap-fallback call (NormSize > 8192, e.g. GPT-3-class
  12288 hidden dim).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Justin Chu (justinchuby) added a commit to justinchuby/onnxruntime that referenced this pull request Aug 13, 2026
… sizes

Independent review found two issues with bench_layernorm_apple_accelerate.cpp:

1. BM_LayerNormDispatch/BM_RMSNormDispatch called MlasLayerNormF32 directly
   without checking whether a kernel is actually registered. On any
   platform/config where GetMlasPlatform().LayerNormF32Kernel is nullptr
   (everything except this option on ARM64, or RVV), MlasLayerNormF32
   returns false and does nothing at all -- it does not fall back to a
   scalar implementation itself, contrary to what a comment here claimed.
   The benchmark would therefore silently time a no-op and report a fake,
   near-zero-cost throughput number labeled (correctly, per
   DispatchPathLabel) "no_kernel_registered", which is easy to miss.
   Fixed: RunLayerNormBenchmark now checks HasRegisteredKernel() up front
   and calls state.SkipWithMessage(...) for the two dispatch benchmarks
   when no kernel is registered, rather than silently benchmarking nothing.

2. No benchmark size exceeded kApplePerRowStackScratch (8192), so the
   heap-fallback scratch path's real performance (relevant for e.g.
   GPT-3-class 12288 hidden dim) was completely unverified -- the claimed
   speedup numbers in the PR description only covered the on-stack path.
   Added 12288 and 16384 to the benchmark size list.

Also fixed a comment claiming an "AVX2 (x86)" kernel registers
LayerNormF32Kernel; no such kernel exists in this repository (only RVV
does), and rephrased the PR microsoft#32036 reference to make clear it is a closed,
unmerged PR, not code present in this tree.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Justin Chu (justinchuby) added a commit to justinchuby/onnxruntime that referenced this pull request Aug 13, 2026
- Remove comments claiming test_tanh_apple_accelerate.cpp and a "sibling
  AVX2 LayerNorm kernel's test suite" exist in this repository. Neither
  exists here: PR microsoft#32036 (which added an Apple Accelerate Tanh kernel and
  its own test file) was closed without merging, and no AVX2 LayerNorm
  kernel/test exists anywhere in this tree.
- Add NormSize == 8192 to ForcedReachability. This is the exact
  kApplePerRowStackScratch boundary (see layernorm.cpp): the on-stack vs.
  heap-fallback scratch condition is strictly greater-than, so 8192 takes
  the on-stack path and was previously untested (LargeNormSizeHeapFallback
  only covers 8193/16384, the heap side of the boundary).
- Harden NearEnough against a latent (not currently reachable by any caller
  in this file) Inf-vs-Inf comparison trap: fabs(inf - inf) is NaN, which
  would make the function incorrectly reject two equal same-sign
  infinities. Added an explicit isinf check before the subtraction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

1 participant