Repository navigation
Implement the portable kernel for _embedding_bag.out - #23871
yenhao-huang wants to merge 18 commits into
Conversation
`_embedding_bag.out` had no portable kernel and was not listed for ATen mode
either.
All three modes are covered: sum, mean and max, with per-sample weights (sum
only, as ATen requires), `include_last_offset`, and `padding_idx` rows skipped
and left out of the bag counts. Indices and offsets may be Long or Int.
`scale_grad_by_freq` and `sparse` only matter to the backward pass and are
ignored.
The three auxiliary outputs exist for the backward pass, and the ATen CPU
kernel fills only what each mode needs, so this matches it rather than
inventing a shape: `offset2bag` is empty for sum and one entry per index
otherwise, `bag_size` stays zero for sum and holds the per-bag counts
otherwise, and `max_indices` is {num_bags, dim} holding the row each feature's
maximum came from for max, and a copy of `bag_size` for mean.
Tests compare every mode, per-sample weights, padding_idx, include_last_offset
and an empty bag against torch.ops.aten._embedding_bag, for all four outputs.
The portable suite is 1648 passed, 0 failed.
Fixes pytorch#11323
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@claude please review |
|
Claude finished @JakeStevens's task in 1m 19s —— View job Summary: One thing blocks this PR: the kernel never bounds-checks Detailed reviewPR Review: #23871SummaryThis PR adds a portable Security
Code Quality
Testing
RecommendationRequest Changes: the missing index bounds check is a memory-safety bug. The other items can be fixed in the same pass. |
…61016 Fix embedding bag validation and low-precision accumulation
Move offset and index validation into check_offsets_and_indices, fill offset2bag and bag_size in one pass over each bag, and keep the main loop to output and max index computation. Use promoteTypes and is_reduced_floating_point_v instead of hand-written equivalents. Outputs are unchanged except that bag_size and max_indices are now also filled when out is empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…61016 Simplify embedding bag kernel structure
|
@JakeSteven thanks for triggering the review. All points are addressed. S1. Indices read without a bounds check
C1. Offsets aren't validated
C2. Mixed index dtypes are accepted
C3. Half/BF16 lose precision
T1. Most supported inputs aren't tested
T2. No failure tests
All 27 |
|
Sorry, I pinged the wrong name. @JakeStevens could you rerun CI again? |
|
Thanks for responding to the first pass. A few more pieces of feedback:
|
…path ATen only skips offset2bag for sum on its fast path. With padding_idx, Double weights, or non-contiguous inputs, export allocates offset2bag with num_indices entries, so resizing it to zero failed before computing the result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Export preserves transposed runtime inputs, but the reduction assumed contiguous rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With include_last_offset, ATen assigns indices past the final offset to the last bag for max and slow-path reductions, while its sum/mean fast path stops at the final offset but still divides mean by the full bag size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@JakeStevens, thanks for the review. Here's the fixes:
Added fast-path selection logic to match ATen, ensuring the correct
Used tensor strides to read weights and write outputs instead of assuming contiguous storage.
Updated the final bag boundaries to match ATen's behavior across modes and paths. Please let me know if there’s anything else. |
JakeStevens
left a comment
There was a problem hiding this comment.
Thanks, the stride and partial-final-offset fixes look good. Two blockers left:
- BF16 sum in exported models. Export sizes
offset2bagwithmeta_embedding_bag, which doesn't count BF16 as fast path, unlike eager ATen. A BF16 sum with no padding gets a staticoffset2bagof lengthnum_indices, and the kernel's resize to{0}fails. Since sum never reads it, keep and fill whatever length the caller passed (num_indicesor 0) instead of predicting it. Please add a BF16 test for the static layout. - Frozen weights. With
requires_grad=False,embedding_baglowers to_embedding_bag_forward_only, which failsto_edge. Please adddecomps[aten._embedding_bag_forward_only.default] = aten._embedding_bag.defaultto_default_decomposition_table()inexir/tracer.py(same pattern as_is_all_true), plus an exir test. I checked that this lowers to_embedding_bag.outand matches eager.
Nits: fill offset2bag when there are zero bags but indices are nonempty; check the weight dtype before the empty-out early return; consider looping over indices on the outside for performance.
Export sizes offset2bag with meta_embedding_bag, whose fast path differs from eager ATen (e.g. for BFloat16), so a sum could get num_indices entries where the kernel expected zero. Sum never reads offset2bag, so fill whichever layout the caller allocated instead of predicting it. With zero bags, fill offset2bag with -1 as ATen does rather than leaving it unwritten, and reject non-floating weights before the empty-output early return. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
embedding_bag with frozen weights exports to _embedding_bag_forward_only, which is not core ATen, so to_edge failed. It only differs from _embedding_bag in skipping tensors saved for the backward pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@JakeStevens, thanks for the review. Here are the fixes:
Sum now keeps and fills whatever length the caller passed (
Added the Nits: zero bags now fill Please let me know if there's anything else. |
JakeStevens
left a comment
There was a problem hiding this comment.
Thanks, the BF16 static offset2bag and _embedding_bag_forward_only fixes look good. Two blockers left:
- Long indices with Int offsets fail in exported models. The kernel requires the auxiliary outputs to have
promoteTypes(indices, offsets)(op_embedding_bag.cpp:85-94). That matches eager ATen. But export plans these outputs withmeta_embedding_bag, which allocates all three withoffsets.new_empty(...), so they get the offsets dtype. AnEmbeddingBagexported with int64 indices and int32 offsets fails with InvalidArgument on every run.index_at/set_indexalready handle either width, so please accept Int or Long for each output independently. Also add an exir test with int32 offsets, sincetest_dtypesallocates Long outputs by hand and misses this. - The tests fail on the ATen variant.
op_embedding_bag_testis registered for["aten", "portable"]with nois_atenguards. For Float/Half/BF16 sum, ATen resizesoffset2bagto[0], soSumStaticOffset2BagAllDtypesfails there.WrongAuxiliaryDtypeRejectedandDecreasingOffsetsRejectedprobably fail too, because ATen casts on copy and doesn't check offset monotonicity. Please wrap the ExecuTorch-only cases inET_SKIP_IF(SupportedFeatures::get()->is_aten, ...), asop_embedding_test.cpp:222does.
Nits:
fills_offset2bagdepends onoffset2bag.numel()from the previous call. With dynamic shapes, a BF16 sum output planned at the upper bound gets shrunk to 0 on the first smaller input and never refills. Mirroring meta's fast-path predicate directly (Float/Half, unit strides, no padding) would remove the dependence on state.- Please remove
aten::_embedding_bagfromUNSUPPORTED_PORTABLE_OPSinbackends/test/suite/runner.py, so the existingtest_embedding_bag.pysuite runs against this kernel. - Please extend
test_embedding_bag_frozen_weightto cover BF16 and mean/max. Right now it only exercises Float sum, where meta already gives a size-0offset2bag. - The loop order is still bag → column → index, with
index_atin the innermost loop. Looping over indices on the outside and accumulating whole rows would be much friendlier to the cache.
|
lintrunner failing as well |
Eager ATen gives offset2bag, bag_size and max_indices the promoted indices/offsets dtype, but export plans them with meta_embedding_bag, which uses the offsets dtype. Long indices with Int offsets therefore failed with InvalidArgument on every run of an exported model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
op_embedding_bag_test also runs against the ATen kernel, which resizes offset2bag to [0] for sum on its fast path. Replaying every case against ATen's _embedding_bag.out showed SumStaticOffset2BagAllDtypes is the only existing one that fails there; WrongAuxiliaryDtypeRejected and DecreasingOffsetsRejected still throw under ATen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@JakeStevens, thanks for the review. Here are the fixes:
Each auxiliary output now accepts Int or Long independently. Added an exir test with int32 offsets (sum/mean/max), which fails with InvalidArgument before the fix.
Wrapped Lintrunner is fixed in Please let me know if there's anything else. |
Summary
Fixes #11323
ExecuTorch has standalone embedding and reduction kernels, but no portable kernel for
_embedding_bag.out. This adds a kernel that looks up embedding rows and reduces multiple bags in one operator call, supporting sum, mean, and max modes.The implementation supports per-sample weights for sum mode,
include_last_offset,padding_idx, and Int or Long indices and offsets. It also produces the auxiliary outputs in the shapes and forms expected by the ATen CPU operator.Algorithm
File changes
kernels/portable/cpu/op_embedding_bag.cpp: implements the portable kernel.kernels/aten/functions.yamlandkernels/portable/functions.yaml: declare and register_embedding_bag.out.kernels/test/op_embedding_bag_test.cpp: adds cases for sum, mean, max, weighted sum, padding, final offset, empty bags, and invalid per-sample weights.kernels/test/CMakeLists.txt,kernels/test/targets.bzl, andshim_et/xplat/executorch/kernels/portable/op_registration_util.bzl: include the kernel and tests in the build targets.Test plan
Ran the targeted portable kernel tests on Linux:
Result:
portable_kernels_testpassed; CTest reported 1/1 test target passed, 0 failed.cc @larryliu0820 @manuelcandales @JakeStevens @nil-is-all