Skip to content

fbgemm-xpu: add all_to_one_device for XPU - #140

Merged
dvrogozh merged 8 commits into
intel:mainfrom
mkrze:ptxpulib-185-all-to-one-device
Oct 7, 2026
Merged

dvrogozh merged 8 commits into
intel:mainfrom
mkrze:ptxpulib-185-all-to-one-device

Conversation

@mkrze

@mkrze mkrze commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

TorchRec's SeqEmbeddingsAllToOne uses fbgemm::all_to_one_device to gather sequence embedding outputs from several devices onto one. Without an XPU implementation, sharded QuantEmbeddingCollection inference across XPU devices fails with NotImplementedError.

This PR adds a host-side XPU implementation:

  • inputs must be XPU tensors and the target must be an indexed XPU device;
  • tensors already on the target are returned unchanged, preserving their views;
  • copied outputs are contiguous, matching CUDA;
  • copies use the non-blocking path when peer access is available and fall back to a synchronous copy otherwise;
  • the schema is registered only when FBGEMM has not already defined it.

The pooled merge operators (merge_pooled_embeddings and sum_reduce_to_one) remain unimplemented on XPU.

Testing uses a hybrid approach. FBGEMM's upstream all_to_one_device test now runs on XPU through the shared patch and checks values for inputs distributed across devices. Plugin-local tests cover the additional XPU contracts that upstream does not check: same-device aliasing, invalid inputs, contiguous copied outputs and copies on non-default streams. Multi-device tests require two XPUs and are skipped on single-GPU CI.

This PR also updates the existing test_int_nbit_lookup.py fixture to accept any PyTorch 2.14.x release, matching the torch~=2.14.0 dependency. The previous exact 2.14.0 check caused all tests in that file to fail during setup after CI started installing 2.14.1.

Validation:

  • BMG, one XPU, PyTorch 2.14.1: plugin suite passed; the patched upstream test passed with the unrelated CUDA-only tests skipped.
  • PVC, two XPU devices: all plugin-local tests passed, and the upstream Hypothesis test exercised both one- and two-device cases.
  • The no-P2P fallback cannot be exercised on the available hardware because the PVC devices have peer access and the BMG runner has one XPU; that path was verified against the copy implementation in the torch-xpu-ops revision pinned by PyTorch 2.14.

mkrze added 4 commits October 5, 2026 13:02
TorchRec's SeqEmbeddingsAllToOne gathers per-rank sequence embedding
outputs onto the output device with fbgemm::all_to_one_device. Without
an XPU implementation, sharded QuantEmbeddingCollection inference across
XPU devices fails in output distribution with NotImplementedError.

The XPU version keeps the CUDA contract: inputs must be XPU tensors, the
target an XPU device with an index, and tensors already on the target
are returned as they are. The others are copied with a non-blocking
Tensor.to under a guard for the target device, so PyTorch's XPU
cross-device copy orders the streams. The schema is defined only when
FBGEMM has not registered it.

The cross-device test needs two XPU devices and is skipped otherwise,
like the two-device cases in test_jagged_device_validation.py.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
Mirror FBGEMM's test_all_to_one_device: inputs round-robin over every
visible XPU, including row-strided views, gathered onto each device in
turn. Inputs already on the target must come back as the same views.
With one device the test still runs and checks the aliasing path.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
The Bandit Scanner workflow fails on B101 for bare asserts. Mark them
with "# nosec B101" as the other plugin tests do.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
The package depends on torch~=2.14.0, so pip resolves it to the newest
2.14.x. Since 2.14.1+xpu showed up on the XPU index, the exact version
check in the xpu fixture turns every test in the file into a setup error
(122 errors on bmg). Compare the minor version, which is what the
dependency actually promises.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
@mkrze mkrze changed the title [Draft, work in progress] fbgemm-xpu: add all_to_one_device for XPU fbgemm-xpu: add all_to_one_device for XPU Oct 6, 2026
@mkrze
mkrze marked this pull request as ready for review October 6, 2026 08:14

@aagalleg aagalleg 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.

Please address the following changes.

Comment thread packages/fbgemm-xpu/src/fbgemm_xpu/all_to_one_device.cpp Outdated
Comment thread packages/fbgemm-xpu/tests/test_all_to_one_device.py Outdated
Comment thread packages/fbgemm-xpu/src/fbgemm_xpu/all_to_one_device.cpp Outdated
# Copyright (c) 2026 Intel Corporation. All Rights Reserved.
# SPDX-License-Identifier: BSD-3-Clause

"""``all_to_one_device`` on XPU.

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.

Are there any tests we can reuse from fbgemm?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, FBGEMM has a test for it.

Addressed in 993e8bb with a hybrid approach: we now run the upstream test on XPU through the shared patch and dropped our copy of it, and keep a few local tests for what upstream doesn't check (no copy for tensors already on the target, contiguous copies, invalid devices, non-default streams).

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.

Thank you. One thing I really care about is reusing fbgemm tests as much as possible and keeping our own tests to the bare minimum. This way we increase our chances to be really compatible with the upstream and minimize our debt for potential upstreaming.

mkrze added 4 commits October 7, 2026 11:11
Without peer access between the source and target XPU, PyTorch's
cross-device copy stages the data through pageable host memory. With
non_blocking set, the device-to-host half is not waited on before the
host reads the staging buffer, so the result can be stale.

Use non_blocking only when at::xpu::get_p2p_access reports peer access
for the pair, the same query the XPU copy uses to choose its path, and
copy synchronously otherwise. CUDA instead requires peer access between
all devices; the fallback keeps systems without it usable.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
CUDA allocates every copied output with at::empty(tensor.sizes(), ...),
so copies are always contiguous. Tensor.to preserves the input layout,
so transposed and channels-last inputs came back non-contiguous on XPU.
Request a contiguous result. Tensors already on the target are still
returned unchanged, as on CUDA.

Add a two-device test with transposed, channels-last and row-strided
inputs.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
The test deleted its inputs after the call but never reused the freed
memory, so it could not detect a lifetime bug. Making it reuse the
memory does not help on current hardware either: kernels from two
streams on one XPU run in submission order on PVC and BMG, so a variant
that copies on the wrong stream still produced correct results.

Keep the test as a cross-device copy on non-default streams and drop
the lifetime claim from its name and docstring.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
FBGEMM's merge_pooled_embeddings_test.py has test_all_to_one_device,
which the plugin-local spread-over-devices test had copied. Enable that
upstream test on the current accelerator in the shared patch, following
reorder_batched_test.py: the class-level CUDA skip moves to the
individual tests, so the merge_pooled_embeddings and sum_reduce_to_one
tests stay CUDA-only. Add the module to test-fbgemm.

Drop the copied test. The plugin-local tests now cover only what the
upstream test does not check: views already on the target, contiguous
copies, invalid devices and non-default streams.

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>

@aagalleg aagalleg 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.

LGTM.

@dvrogozh dvrogozh 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.

LGTM

@dvrogozh
dvrogozh merged commit c5395b4 into intel:main Oct 7, 2026
17 checks passed
dvrogozh pushed a commit that referenced this pull request Oct 7, 2026
## Problem

Several fbgemm-xpu sparse operators submitted their kernels to the SYCL
queue of the *current* device instead of the device that holds their
tensors:

- `block_bucketize_sparse_features` /
`block_bucketize_sparse_features_inference`
- `populate_bucketized_permute`
- `permute_1D_sparse_data`, `permute_2D_sparse_data`
- `invert_permute`
- `jagged_index_select_2d_forward`

When the inputs live on another device, the kernel runs unordered with
the pending work on the data's device and can read inputs before they
are written. TorchRec row-wise QUANT inference hits this whenever its
output device is not the current device: on an eight-card Data Center
GPU Max host the bucketized indices were wrong in about half of the
forwards.

`expand_into_jagged_permute` was not affected: it already asked for the
stream of its input's device explicitly.

The same wrappers also checked only that inputs were XPU tensors, not
that they were on the *same* XPU, so mixed-device inputs could pass
pointers from another device to the selected queue and fail
asynchronously.

## Changes

- **Device guards.** Each operator now uses `SYCL_DEVICE_GUARD` on the
tensor that FBGEMM's CUDA implementation guards, at the same point:
`lengths` in the shared bucketize helper and in
`populate_bucketized_permute`, `indices` in permute 1D/2D, `permute` in
invert/expand permute, `values` in `jagged_index_select_2d_forward`.
This is the pattern the jagged and bounds-check kernels already use.
`expand_into_jagged_permute` now relies on the guard and the current
stream too, instead of selecting the device explicitly.
- **Same-device checks.** Like the CUDA wrappers
(`TENSORS_ON_SAME_CUDA_GPU_IF_NOT_OPTIONAL`), bucketize,
`populate_bucketized_permute`, permute 1D/2D (including optional
`weights`) and `jagged_index_select_2d_forward` now call
`TENSORS_ON_SAME_SYCL_XPU_IF_NOT_OPTIONAL` before selecting the queue.
`expand_into_jagged_permute` already had an equivalent check;
`invert_permute` has a single tensor.
- **Behaviour change:** mixed-device inputs now raise `RuntimeError: Not
all tensors were on the same XPU` instead of being accepted.
- **Tests:** new `tests/test_non_current_device.py` with 15 two-device
cases:
- 8 correctness cases: each operator runs on `xpu:1` while `xpu:0` is
current, with inputs that are still waiting for slow work on `xpu:1`,
compared exactly with FBGEMM's CPU kernels;
- 7 rejection cases for mixed-device inputs, including optional
`weights` on the wrong device.
- **Test fixture:** `test_int_nbit_lookup.py` accepted only torch
2.14.0, but `torch~=2.14.0` now installs 2.14.1, which turned every test
in that file into a setup error in CI. The fixture now accepts any
2.14.x (same change as in #140).

## CI coverage

The bug only shows up with two XPU devices (data on a device other than
the current one), so it cannot be reproduced on one GPU. Upstream FBGEMM
does not test this case either. **PR CI has a single GPU, so it collects
the 15 new tests and skips them** ("requires two XPU devices"), the same
way it already skips the two-device cases in
`test_jagged_device_validation.py`. A green CI run therefore does not
exercise this fix.

The tests are meant for multi-GPU validation, for example a nightly run
on a PVC machine with two cards. They need no extra setup and run as
part of the normal test suite. To run them by hand on such a machine:

ZE_FLAT_DEVICE_HIERARCHY=FLAT python -m pytest -rs
packages/fbgemm-xpu/tests/test_non_current_device.py

## Testing

- **PVC (Data Center GPU Max), two devices, PyTorch 2.14.1, manual:**
15/15 passed (repeated runs). On `main` the correctness case fails for
every affected operator and passes for `expand_into_jagged_permute`,
which was already correct.
- **BMG (Arc Pro B60), PyTorch 2.14.1, same setup as CI:** plugin suite
340 passed, 28 skipped (15 of the skips are the new two-device cases).
The patched upstream FBGEMM modules for these operators pass
(`expand_into_jagged_permute`, `jagged_index_select_2d`,
`block_bucketize`, `misc_ops`, `permute_sparse_features`).
- **Performance (BMG):** per-call latency of the affected operators,
`main` vs this branch, median of 3 alternating rounds: no change beyond
run-to-run noise. Re-measured for `expand_into_jagged_permute` after its
change.
- `ruff check` and `bandit -r` are clean.

---------

Signed-off-by: mkrze <mateusz.krzemieniewski@intel.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants