optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>) - #19119

Merged
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum
Apr 30, 2026
Merged

optimized: add grid_sampler_2d.out (NEON) and sum.IntList_out (Vectorized<float>)#19119
GregoryComer merged 7 commits into
pytorch:mainfrom
PolyCam:jgibson/upstream-optimized-grid-sum

Conversation

@jgibson2

@jgibson2jgibson2 commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Two new optimized CPU kernels registered alongside the existing optimized_kernels library. Both replace the portable reference kernel (still available as fallback for unsupported inputs) with vectorized implementations that accumulate in fp32, which also sidesteps the fp16 precision issue noted in #19117 for grid_sampler_2d bilinear.

Measured end-to-end on a real depth model (Pixel 9 / arm64-v8a, fp16 inputs, shapes representative of the model's hot path):

OpPortableThis PRSpeedup
grid_sampler_2d.out17.3 ms3.4 ms5.1×
sum.IntList_out (5 calls, aggregate)3.0 ms0.56 ms5.4×

grid_sampler_2d.out

aarch64 NEON, bilinear + zeros padding only (the dominant mode for depth / MVS / spatial transformer networks). Processes 4 channels per iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32 for weight computation and accumulation, cast back on store — the portable kernel's fp16 weight subtractions like (ix_se - ix) otherwise suffer catastrophic cancellation (same concern as #19117). Unsupported modes and non-aarch64 targets delegate to the portable kernel.

sum.IntList_out

at::vec::Vectorized<float>-based implementation of the single-dim reduction fast path (both innermost-contiguous and strided cases). Cross-architecture SIMD via PyTorch's existing vector abstraction; always accumulates in fp32 regardless of input dtype. Multi-dim reductions, dtype-converting reductions, and complex types delegate to portable.

Integration

  • Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of truth for both Buck and CMake builds.
  • optimized.yaml registers the ops with the standard opt_* naming convention used by sibling kernels.
  • kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16 flag to just op_grid_sampler_2d.cpp via set_source_files_properties, so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__ guards and falls through to portable on non-arm64 targets.

Test plan

  • Builds cleanly for Android arm64-v8a, Android x86_64 (via scripts/build_android_library.sh), and host (macOS / Apple Clang 21).
  • Existing kernels/test/op_grid_sampler_2d_test.cpp and op_sum_test.cpp unit tests continue to pass — both target the aten::sum_outf / aten::grid_sampler_2d_outf codegen-dispatched entry points, so they automatically exercise the optimized kernels when linked.
  • Numerical verification against an fp32 reference (run portable in fp32, cast to fp16) on the shapes the polycam depth model uses — all cases pass within fp16 ULP.
  • End-to-end Pixel 9 latency on a representative trained model matches the handwritten-NEON reference implementation to within run-to-run noise while producing more accurate fp16 outputs (fp32 accumulation).

Candidate successor to #19117 for the grid_sampler half — applies the same precision fix but at the optimized-kernel layer, so callers who link optimized_ops_lib get both the correctness fix and the speedup.

cc @larryliu0820@manuelcandales

…List_out
Two new optimized CPU kernels registered alongside the existing
optimized_kernels library. Both replace the portable reference kernel
(still available as fallback for unsupported inputs) with a vectorized
implementation that accumulates in fp32, avoiding the fp16 precision
issues noted in pytorch#19117 for grid_sampler_2d bilinear.
Measured end-to-end on a real depth model (Pixel 9, fp16 inputs, shapes
representative of the model's hot path):
| Op | Portable | This PR | Speedup |
| -------------------------------- | -------- | ------- | ------- |
| grid_sampler_2d.out | 17.3 ms | 3.4 ms | 5.1x |
| sum.IntList_out (5 calls, total) | 3.0 ms | 0.56 ms | 5.4x |
### grid_sampler_2d.out
aarch64 NEON, bilinear + zeros padding only. Processes 4 channels per
iteration with a vectorized FMA chain. fp16 inputs are promoted to fp32
for weight computation and accumulation, then cast back on store — the
portable kernel's fp16 weight subtractions like `(ix_se - ix)` otherwise
suffer catastrophic cancellation. Unsupported modes and non-aarch64
targets delegate to the portable kernel.
### sum.IntList_out
at::vec::Vectorized<float>-based implementation of the single-dim
reduction fast path (both innermost-contiguous and strided cases).
Cross-architecture SIMD via PyTorch's existing vector abstraction;
accumulates in fp32 regardless of input dtype. Multi-dim reductions,
dtype-converting reductions, and complex types delegate to portable.
### Integration
- Sources added to OPTIMIZED_KERNELS_SRCS in build_variables.bzl and to
OPTIMIZED_ATEN_OPS in op_registration_util.bzl. Single source of
truth for both Buck and CMake builds.
- optimized.yaml registers the ops with the standard opt_* naming
convention used by sibling kernels.
- kernels/optimized/CMakeLists.txt scopes the -march=armv8.2-a+fp16
flag to just op_grid_sampler_2d.cpp via set_source_files_properties,
so x86_64 builds are unaffected. The kernel has #ifdef __aarch64__
guards and falls through to portable on non-arm64 targets.
@pytorch-bot

pytorch-botBot commented Apr 24, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19119

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

❌ 2 New Failures, 8 Pending

As of commit 5050925 with merge base de8ce55 (image):

NEW FAILURES - The following jobs have failed:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-clameta-claBot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 24, 2026
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@pytorchbot label "release notes: none"

The NEON fast path indexes input/grid/out directly assuming contiguous
NCHW default-dim-order layout — no use of .strides() or .dim_order().
If the caller passes anything else (NHWC, transposed, strided, channels-
last), we'd read wrong memory and silently produce garbage output.
Add the same check pattern op_sum.cpp already uses at L150-151:
tensor_is_default_dim_order + tensor_is_contiguous on input, grid, and
out. If any fails, delegate to the portable kernel (which handles
arbitrary strides / dim orders correctly via .strides()).
No perf impact on the hot path — the checks are a handful of scalar
comparisons run once per call, and the common polycam depth model case
is already default-contiguous so the fast path is still taken.
@GregoryComer

GregoryComer commented Apr 24, 2026

Copy link
Copy Markdown
Contributor

@JacobSzwejbka@manuelcandales@digantdesai Do you have any concerns with conditionally linking an op in optimized only on some architectures?

Apply lintrunner -a auto-fixes to satisfy CI (CLANGFORMAT and
CMAKEFORMAT). No functional changes.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the PR! I left one comment about runtime gating the armv8.2+fp16 code. Other than that, it looks good.

We are currently looking at adding better support for in-tree CPU operator implementations with arch-specific dispatch, so this type of thing should become easier soon.

Comment threadkernels/optimized/CMakeLists.txt Outdated
)
set_source_files_properties(
${EXECUTORCH_ROOT}/kernels/optimized/cpu/op_grid_sampler_2d.cpp
PROPERTIES COMPILE_OPTIONS "-march=armv8.2-a+fp16"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to split out the native f16 path? Right now, it will potentially SIGILL on ARM hardware without f16 support. If possible, I'd recommend something like this:

  • Move the native f16 impl into a separate source file. Scope the march +fp16 to just this file.
  • Add a variant that does the f16<->f32 conversion in software.
  • In the top-level kernel, check hardware support using cpuinfo_has_arm_neon_fp16 and route to the implementation.

Address review feedback on pytorch#19119: the previous
op_grid_sampler_2d.cpp compiled the whole file with
-march=armv8.2-a+fp16, which meant the resulting binary would SIGILL on
ARMv8.0 / ARMv8.1 chips that lack the fp16 extension.
Split the fp16 path into two translation units:
* op_grid_sampler_2d_fp16_hw.cpp — hardware fp16 fast path. Uses
vld1_f16 / vcvt_f32_f16 / vfmaq_f32 / vcvt_f16_f32 / vst1_f16.
Compiled with -march=armv8.2-a+fp16 (flag scoped to this TU via
set_source_files_properties in CMake, and via compiler_flags on a
dedicated runtime.cxx_library in targets.bzl).
* op_grid_sampler_2d.cpp — hosts the fp32 NEON path, a new fp16
software-convert path, and the runtime dispatcher. Plain ARMv8
only. The SW path converts fp16<->fp32 via c10::Half's portable
operator float() / constructor (no hardware fp16 instructions) and
does all compute on NEON fp32 lanes. Slower per conversion than the
HW path but safe on any ARMv8 CPU.
The dispatcher calls cpuinfo_initialize() + cpuinfo_has_arm_neon_fp16()
(cpuinfo already transitively linked via extension_threadpool) and
routes to the appropriate variant. fp32 inputs use the unchanged NEON
fp32 path; any unsupported layout/padding/interpolation still falls
through to the portable kernel.
Buck: adds a new runtime.cxx_library(op_grid_sampler_2d_fp16_hw) in
kernels/optimized/cpu/targets.bzl with the +fp16 compile flag gated on
ovr_config//cpu:arm64, and wires op_grid_sampler_2d to depend on it and
on cpuinfo.
No behavior change on fp32 inputs. fp16 inputs on +fp16-capable chips
keep the existing fast path at the same speed; fp16 inputs on chips
without the extension now run the SW variant instead of crashing.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Addressed the SIGILL concern — new commit 53697e9 splits the fp16 path into HW and SW variants with runtime dispatch:

  • op_grid_sampler_2d_fp16_hw.cpp (new) — hardware fp16 fast path. Uses vld1_f16 / vcvt_f32_f16 / vcvt_f16_f32 / vst1_f16. Compiled with -march=armv8.2-a+fp16 (scoped via set_source_files_properties in CMake; compiler_flags gated on ovr_config//cpu:arm64 in the Buck target).
  • op_grid_sampler_2d.cpp — now hosts the fp32 path, a new fp16 software-convert path, and the runtime dispatcher. Plain ARMv8 only. The SW path converts fp16↔fp32 via c10::Half's portable conversions (no hardware fp16 instructions) and does all compute on NEON fp32 lanes — slower per conversion but safe on any ARMv8 chip.

Dispatcher is at op_grid_sampler_2d.cpp:394-425. Key lines:

if (input.scalar_type() == ScalarType::Half) {
if (cpuinfo_initialize() && cpuinfo_has_arm_neon_fp16()) {
// HW path (in _fp16_hw.cpp)
}
// fall through to SW path (in this file)
}

cpuinfo is already transitively linked via extension_threadpool, so the dependency was a one-line Buck addition. No change to behavior on fp16-capable chips (Pixel 9, S24 FE, etc.); chips without the +fp16 extension now run the SW variant instead of raising SIGILL.

One thing I'd appreciate guidance on: the fp16 SW and HW paths share ~60 lines of loop body verbatim — same FMA chain, same indexing, same weight math. I kept them copy-pasted for clarity rather than macro/template gymnastics. Happy to DRY that up if you'd prefer.

Buck's op_registration_util._enforce_deps rejects any dep starting with
`:op_` on the theory that op_targets should not depend on other
op_targets. The previously-named `:op_grid_sampler_2d_fp16_hw` tripped
that check when op_grid_sampler_2d (which is an op_target) declared it
as a dep.
Rename the internal helper library to `grid_sampler_2d_fp16_hw_impl`,
matching the existing `add_sub_impl` / `binary_ops` naming for
op-specific implementation helpers. No change to file contents, CMake,
or the C++ dispatch — only the Buck target name and the corresponding
dep reference.
@meta-codesync

Copy link
Copy Markdown
Contributor

@GregoryComer has imported this pull request. If you are a Meta employee, you can view this in D102420839.

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

@jgibson2

Copy link
Copy Markdown
ContributorAuthor

Thanks, looks good. Can you make two quick changes? I think we should be good to merge after that.

  1. Move the grid_sampler_2d_bilinear_fp16_hw definition to a header file. Some build configurations with -Wmissing-prototypes fail currently. I'll see if I can add this to OSS CI for better signal in the future.
  2. Update the CMake build to split out the neon fp16 target, similar to buck? This will make sure it doesn't autovectorize the fallback path with neonfp16 instructions.

Thanks!

Changes are pushed -- please check the CMake implementation, as that was a Claude change and I don't understand the nuances of using an object library enough to validate it myself (beyond checking the build)...

Three changes consolidated for review:
1. Move the forward declaration of grid_sampler_2d_bilinear_fp16_hw out
of op_grid_sampler_2d.cpp into a new header
kernels/optimized/cpu/op_grid_sampler_2d_fp16_hw.h. The function has
external linkage (the dispatcher in op_grid_sampler_2d.cpp calls into
it across translation units), and prior to this its definition site
had no prior prototype visible — which trips -Wmissing-prototypes on
build configurations that enable it. Both .cpp files now include the
shared header. The function body stays in op_grid_sampler_2d_fp16_hw.cpp
because that TU is the only one compiled with -march=armv8.2-a+fp16,
so it cannot be inlined into a header. The header itself uses void* for
input/output buffers and is fp16-free, so callers don't need the
+fp16 march flag just to declare or call it.
2. Split the fp16 HW path into its own CMake target. Previously the
-march=armv8.2-a+fp16 flag was scoped per-source-file via
set_source_files_properties on the sole TU inside the optimized_kernels
library. That works for a clean non-LTO build, but with ThinLTO or
cross-TU optimizations the flag boundary becomes fuzzy and the fallback
path in op_grid_sampler_2d.cpp could in principle be auto-vectorized
into fp16 NEON instructions — exactly the SIGILL hazard the runtime
dispatch is meant to prevent. Build the file as an OBJECT library
(grid_sampler_2d_fp16_hw_impl) with target-scoped -march flag and link
it into optimized_kernels via $<BUILD_LOCAL_INTERFACE:...> so the
object code is baked into liboptimized_kernels.a at archive time and
the OBJECT target is kept out of the install EXPORT set. Mirrors the
existing buck `grid_sampler_2d_fp16_hw_impl` cxx_library.
3. Gate the optimized fast paths on input/grid/out dtype match. Each fast
path assumes a single dtype across all three tensors:
fp32 NEON path: data_ptr<float>() on all three
fp16 HW path: void* pointers reinterpret_cast<__fp16*> on all three
fp16 SW NEON: data_ptr<c10::Half>() on all three
Until now the dispatcher gated only on input.scalar_type(). The
reinterpret_casts in the fp16 HW kernel are particularly load-bearing
because their behavior on a mismatched dtype would be silent
corruption (reading int64/double bytes as __fp16 stride). The
data_ptr<T>() runtime check exists but is not guaranteed in release
builds. Add a dtypes_match clause at the top of the fast-path
eligibility check that requires all three scalar types equal; fall
back to the portable kernel otherwise. The portable kernel handles
arbitrary dtype combinations correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jgibson2
jgibson2force-pushed the jgibson/upstream-optimized-grid-sum branch from ed5a100 to 93c93c1CompareApril 27, 2026 22:16
jgibson2 added a commit to PolyCam/executorch that referenced this pull request Apr 29, 2026
Sync NEON optimized kernels to current upstream PR (pytorch#19119)
@GregoryComer

Copy link
Copy Markdown
Contributor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target.
In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

Two missing buck deps that block the dispatcher from finding
grid_sampler_2d_bilinear_fp16_hw under selective-build configurations:
1. kernels/optimized/cpu/targets.bzl: declare op_grid_sampler_2d_fp16_hw.h
as an `exported_headers` of the grid_sampler_2d_fp16_hw_impl library.
Other support libraries in the same file (add_sub_impl, binary_ops,
fft_utils, moments_utils) all export their headers this way; without
it, the include from the dispatcher's TU resolves only by source-root
accident rather than declared header propagation.
2. shim_et/xplat/executorch/codegen/codegen.bzl: add the fp16_hw_impl
library and cpuinfo to get_optimized_lib_deps(). That list feeds
build_portable_lib's deps for dtype-selective builds, which bypass the
op_target dependency-resolution machinery and link directly against a
flat list of deps. A selective build that includes op_grid_sampler_2d
would fail to link without the fp16_hw_impl symbol; the dispatcher
calls cpuinfo_has_arm_neon_fp16(), so cpuinfo is needed for the same
reason.
@jgibson2

Copy link
Copy Markdown
ContributorAuthor

@jgibson2 I think we should be really close. Can you make two more changes? I can merge after that.

In kernels/optimized/cpu/targets.bzl - Add exported_headers = ["op_grid_sampler_2d_fp16_hw.h"], to the grid_sampler_2d_fp16_hw_impl target. In codegen/codegen.bzl - Add "//executorch/kernels/optimized/cpu:grid_sampler_2d_fp16_hw_impl", and "fbsource//third-party/cpuinfo:cpuinfo", to get_optimized_lib_deps?

Sorry for the churn, the buck build isn't fully exercised in GitHub actions. I verified everything passes with those two additions. If you can add those two I can go ahead and merge. Thanks!

So you're saying the buck stops here (☞゚ヮ゚)☞

Pushed!

@GregoryComerGregoryComer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for your patience with the buck build. Tests and build are green, so I'll go ahead and merge.

@GregoryComer
GregoryComer merged commit b384173 into pytorch:mainApr 30, 2026
168 of 176 checks passed
@jgibson2
jgibson2 deleted the jgibson/upstream-optimized-grid-sum branch April 30, 2026 14:53
@nil-is-allnil-is-all added the module: kernels Issues related to kernel libraries and utilities, and code under kernels/ label May 28, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.module: kernelsIssues related to kernel libraries and utilities, and code under kernels/release notes: noneDo not include this in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jgibson2@GregoryComer@nil-is-all