Skip to content

Adding conversion from PyObject to torch::stable::tensor - #183323

Closed
pggPL wants to merge 35 commits into
pytorch:mainfrom
pggPL:stable_python_obj
Closed

pggPL wants to merge 35 commits into
pytorch:mainfrom
pggPL:stable_python_obj

Conversation

@pggPL

@pggPL pggPL commented May 11, 2026 •

Copy link
Copy Markdown
Contributor

Hi,

I would like to propose a PR adding conversions from a Python object to
torch::stable::Tensor and vice versa.

Motivation

I work on the Transformer Engine
project. We want to use the torch stable ABI, but we cannot do it in the
recommended way of registering a custom op in libtorch. There are 2 issues:

  • we want to pass arbitrary Python objects with metadata into C++ (it's the core
    of our design),
  • something that is logically one op sometimes needs to call multiple C++
    functions (especially with complex fusions).

So we would like to do something like this:

  • register into the dispatcher via Python custom ops with opaque objects,
  • inside these ops, call C++ functions with Python objects as arguments,
  • if some arguments are tensors, use the functions introduced by this PR to
    convert them into stable tensors.

Approach

The two shims (torch_tensor_from_pyobject / torch_tensor_to_pyobject) live
in libtorch, but the actual PyObject <-> at::Tensor conversion needs
THPVariable_*, which lives in libtorch_python. To bridge that without libtorch
depending on libtorch_python (or on Python.h), the PR mirrors the existing
c10::impl::PyInterpreterVTable indirection:

  • an interface (PyObjectConversionInterface) plus a no-op default in libtorch,
    reachable through a global pointer;
  • a concrete implementation in libtorch_python that uses THPVariable_Unpack /
    THPVariable_Wrap, registered into libtorch at load time via static init (and
    reset to the no-op on teardown);
  • the C shims cross the ABI boundary as AtenTensorHandle and forward to
    whatever implementation is currently installed. AtenTensorHandle is confined
    to the shim layer; the vtable itself works in at::Tensor.

If libtorch_python is not loaded (e.g. a libtorch-only process), the no-op is
still installed and both shims return AOTI_TORCH_FAILURE with a clear message
instead of crashing (covered by a libtorch-only gtest).

A new class of shims

Existing stable-ABI shims are self-contained in libtorch. These are the first
shims that reach up from libtorch into libtorch_python for functionality that
only exists there (the CPython-facing tensor wrapper), using the vtable
indirection to keep the layering intact. The same mechanism can host future
Python-interop shims.


This PR was prepared with the help of Claude Opus.

pggPL added 2 commits April 20, 2026 17:44
Signed-off-by: Pawel Gadzinski <[email protected]>
# Conflicts:
#	torch/csrc/stable/c/shim_function_versions.txt
@pytorch-bot

pytorch-bot Bot commented May 11, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

✅ No Failures

As of commit 575d711 with merge base dcadb9d (image):
💚 Looks good so far! There are no failures yet. 💚

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

@pytorch-bot

pytorch-bot Bot commented May 11, 2026

Copy link
Copy Markdown

Attention! PyTorch one of the C-stable API file was changed

You MUST NOT change existing function declarations in this, as this header defines a stable C ABI. If you need to change the signature for a function, introduce a new v2 version of the function and modify code generation to target the new version of the function.

@linux-foundation-easycla

linux-foundation-easycla Bot commented May 11, 2026 •

Copy link
Copy Markdown

CLA Signed

The committers listed above are authorized under a signed CLA.

pggPL added 2 commits May 11, 2026 17:36
Drop the verbose docstrings on torch_tensor_from_pyobject /
torch_tensor_to_pyobject and their inline wrappers in favour of short //
comments matching the rest of torch/csrc/stable. No behaviour change.

Signed-off-by: Pawel Gadzinski <[email protected]>
# Conflicts:
#	torch/csrc/stable/c/shim_function_versions.txt
@pggPL
pggPL marked this pull request as ready for review July 1, 2026 13:05

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

Most majorly: The main difference between this and other shims is that it requires linking with libtorch_python.so which is a new dependency every other shim does not require. Architecturally I haven't discerned yet where they should live. Do you know if any of the Python APIs would make these APIs tied with CPython?

Regardless, we would need to carve out a new section of documentation and tests for this new class of shims that depend on both libtorch_python and libtorch.

Minorly, once the above is cleared:

  • Needs to be 2.14+ now
  • the STABLE_SHIM_VERSION (.lintrunner.toml:1827-1830) lint need to include the file
  • For missing tests. You should add tests in libtorch_agnostic_extension under 2.14, which will require adding a bit of scaffolding.

Comment thread torch/csrc/stable/python_interop.h Resolved
@albanD albanD added the triaged This issue has been looked at a team member, and triaged and prioritized into an appropriate module label Jul 2, 2026
Apply Jane Xu's review comments on the stable PyObject<->Tensor shim.

The header-only helpers in python_interop.h now use
STABLE_TORCH_ERROR_CODE_CHECK instead of TORCH_ERROR_CODE_CHECK. The new
shims are retargeted from 2.13 to 2.14: add the TORCH_VERSION_2_14_0 macro
to version.h and bump the version guards in python_interop.h and
c/python_shim.h (the shim_function_versions.txt entries were moved to
TORCH_VERSION_2_14_0 as part of the upstream merge). Finally, register
c/python_shim.h with the STABLE_SHIM_VERSION lint so the new stable C
entry points are version-checked.

Test Plan: Build/lint-config only change; no runtime behavior change. The
feature is gated on TORCH_VERSION_2_14_0, matching the version file. Note:
lintrunner was not run (not installed in this environment).

Authored by Claude.

Signed-off-by: Pawel Gadzinski <[email protected]>
@pytorch-bot

pytorch-bot Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Workflows were awaiting approval. CI has now been triggered for the ciflow labels on this PR.

@huydhn

huydhn commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

@pytorchbot rebase

(Need to rebase to get the lastest Docker build workflow change)

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

@pytorchbot started a rebase job onto refs/remotes/origin/viable/strict. Check the current status here

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

Rebase failed due to Command git -C /home/runner/work/pytorch/pytorch rebase --onto refs/remotes/origin/viable/strict cbf5bf0c4812c31268d1297700c342c6382c3670 pull/183323/head returned non-zero exit code 1

Rebasing (1/3)
Auto-merging build_variables.bzl
Auto-merging torch/csrc/stable/c/shim_function_versions.txt
CONFLICT (content): Merge conflict in torch/csrc/stable/c/shim_function_versions.txt
error: could not apply ed463844abe... code drop
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply ed463844abe... # code drop

Raised by https://github.com/pytorch/pytorch/actions/runs/28606412871

The PyObject<->Tensor conversion shims added for 2.14 are the first stable
shims that depend on libtorch_python rather than only libtorch. Give them a
dedicated subdirectory so that dependency is explicit and separated from the
libtorch-only stable ABI: c/python_shim.h -> python/c/shim.h, python_interop.h
-> python/interop.h, and python_shim.cpp -> python/shim.cpp, with
build_variables.bzl and the STABLE_SHIM_VERSION lint updated to match.

Test Plan: Path-only refactor; the tree builds and the shim symbol is exported
(verified in the follow-up commit). lintrunner was not run (not installed in
this environment).

Authored by Claude.

Signed-off-by: Pawel Gadzinski <[email protected]>
@pggPL
pggPL force-pushed the stable_python_obj branch from b57158d to 1195fac Compare July 2, 2026 17:05
@pggPL
pggPL force-pushed the stable_python_obj branch 2 times, most recently from a764ba3 to 019ae5a Compare July 2, 2026 17:09
Adds libtorch_python_interop_2_14, an abi3 (py_limited_api) test extension that
links torch_python and exercises torch::stable::from_pyobject / to_pyobject.
Because these shims depend on both libtorch and libtorch_python and require the
GIL, they form a separate class from the libtorch-only libtorch_agn_* family:
this extension links torch_python, is imported as a module, and exposes
PyMethodDef functions (entered from Python with the GIL held) rather than
torch.ops ops, which the dispatcher may run with the GIL released. It gets its
own test file (test_libtorch_python_interop.py) and a section in the stable-ABI
docs.

Test Plan:
Built a CPU torch at 2.14 on the workstation and ran:
    python -m pytest test/cpp_extensions/test_libtorch_python_interop.py -v
Result: 4 passed -- round-trip shares storage, to_pyobject with an explicit
py_type (torch.nn.Parameter), real work on the tensor (torch::stable::sum), and
a non-tensor input raising. Confirmed the built _C.so is abi3 (named _C.so, no
cpXXX suffix), links libtorch_python, and resolves torch_tensor_from_pyobject /
torch_tensor_to_pyobject from it.

Authored by Claude.

Signed-off-by: Pawel Gadzinski <[email protected]>
@pggPL
pggPL force-pushed the stable_python_obj branch from 019ae5a to d03e9fb Compare July 2, 2026 17:20
pggPL added 2 commits July 2, 2026 19:26
torch_tensor_from_pyobject / to_pyobject touch Python objects, so calling them
without the GIL is undefined behavior. Add a PyGILState_Check() assertion to each
so misuse (for example calling from a boxed STABLE_TORCH_LIBRARY op whose kernel
the dispatcher may run with the GIL released) fails with a clear error instead of
a data race. The check runs in libtorch_python and does not itself require the
GIL.

Test Plan:
Rebuilt torch at 2.14 and reran:
    python -m pytest test/cpp_extensions/test_libtorch_python_interop.py -v
Result: 4 passed. The extension calls the shims from Python-entered PyMethodDef
functions, so the GIL is held and the assertions pass.

Authored by Claude.

Signed-off-by: Pawel Gadzinski <[email protected]>
The stable-ABI note points at external, pinned examples (e.g. the FA3 repo), not
internal test files. Remove the "runnable example see test/cpp_extensions/..."
pointer, which was the only such reference and would rot if the tests move.

Test Plan: docs-only change.

Authored by Claude.

Signed-off-by: Pawel Gadzinski <[email protected]>
@pggPL

pggPL commented Jul 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @janeyx99!

Agreed this is a new kind of shim and needs a clear place to live. For now I moved it into a separate torch/csrc/stable/python/ dir. The idea is simple: anything in that dir is allowed to depend on libtorch_python, and the rest of stable/ stays libtorch-only. Happy to put it somewhere else if you have a better spot in mind.

On your question about whether the Python APIs tie this to CPython — for the user side, they don't:

  • PyObject* is passed as a void*, so the public header doesn't include Python.h. The signatures don't lock a user to any CPython version.
  • I checked that a user extension using this APIs can still be abi3 (py_limited_api) in tests,
  • The CPython-specific code is only in the libtorch_python implementation, which already depends on CPython anyway, so nothing extra leaks to users.

The one thing these give up compared to the rest of the stable ABI is running without Python at all (libtorch-only). They need libtorch_python loaded. That's unavoidable when you're converting Python objects — I noted it in the docs.

For the docs I added a "Python interop shims" section in docs/source/notes/libtorch_stable_abi.md.

For the tests I added a separate test extension and test file rather than reusing the libtorch_agn_* ones, because this is a different class of shim:

  • it depends on libtorch_python (links torch_python), while the libtorch_agn_* extensions are deliberately libtorch-only, and
  • it's called as a plain Python-facing function (the shims need the GIL held), not as a torch.ops op like the libtorch_agn_* tests.

Please let me know what do you think.

@pggPL

pggPL commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@pytorchbot merge -r

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

@pytorchbot started a rebase job onto refs/remotes/origin/viable/strict. Check the current status here

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

Rebase failed due to Command git -C /home/runner/work/pytorch/pytorch rebase --onto refs/remotes/origin/viable/strict dcadb9d8a582cb6d1915534ded43ba7b2a877c5c pull/183323/head returned non-zero exit code 1

Rebasing (1/31)
Auto-merging build_variables.bzl
Auto-merging torch/csrc/stable/c/shim_function_versions.txt
CONFLICT (content): Merge conflict in torch/csrc/stable/c/shim_function_versions.txt
Auto-merging torch/csrc/stable/version.h
CONFLICT (content): Merge conflict in torch/csrc/stable/version.h
error: could not apply ed463844abe... code drop
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply ed463844abe... # code drop

Raised by https://github.com/pytorch/pytorch/actions/runs/30452283556

@pggPL

pggPL commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@pytorchbot merge

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

Merge started

Your change will be merged once all checks pass (ETA 0-4 Hours).

Learn more about merging in the wiki.

Questions? Feedback? Please reach out to the PyTorch DevX Team

Advanced Debugging
Check the merge workflow status
here

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

Merge failed

Reason: 3 mandatory check(s) failed. The first few are:

Dig deeper by viewing the failures on hud

Details for Dev Infra team Raised by workflow job

Failing merge rule: Core Maintainers

@kshitij12345

Copy link
Copy Markdown
Collaborator

@pytorchbot merge

@pytorchmergebot

Copy link
Copy Markdown
Collaborator

Merge started

Your change will be merged once all checks pass (ETA 0-4 Hours).

Learn more about merging in the wiki.

Questions? Feedback? Please reach out to the PyTorch DevX Team

Advanced Debugging
Check the merge workflow status
here

janeyx99 pushed a commit to pggPL/pytorch that referenced this pull request Oct 6, 2026
Follow-up to pytorch#183323, which introduced the PyObjectConversionInterface
vtable so libtorch-only stable shims can convert PyObjects using
functionality that lives in libtorch_python. This extends the same
mechanism (no new machinery) with torch.dtype <-> ScalarType and
torch.device <-> Device conversions, which python-registered custom ops
need alongside tensors.

Review in the order the pieces fit together: the vtable interface and
its erroring no-op default (torch/csrc/PyObjectConversion.{h,cpp}), the
concrete THPDtype/THPDevice-based implementation in libtorch_python
(torch/csrc/PyObjectConversionPythonImpl.cpp), the C shims
(torch/csrc/shim_common.cpp, torch/csrc/stable/c/shim.h,
shim_function_versions.txt; all versioned at 2.14), the header-only
helpers (torch/csrc/stable/pyobject.h), then tests and docs.

Unlike the tensor shims there is no handle type: a dtype crosses the
ABI boundary as the int32_t code already used by aoti_torch_dtype_*(),
and a device as (device_type, device_index) int32_t in the
aoti_torch_device_type_*() encoding, matching how aoti_torch_get_dtype
/ _get_device_type / _get_device_index encode them. The pyobject.h
helpers translate the codes through torch::stable::detail::from/to like
Tensor::scalar_type() / Tensor::device(), so extension-side and
libtorch-side enum values stay decoupled.

Test Plan:
Minimal CPU build (BUILD_TEST=1, PYTORCH_BUILD_VERSION=2.14.0a0) on a
linux workstation; all touched TUs recompiled and torch imports:

```
pip install -e . -v --no-build-isolation
```

Libtorch-only no-op fallback (links libtorch, not libtorch_python):

```
./build/bin/test_shim --gtest_filter="TorchPyObjectConversion.*"
```

3 tests pass. Extension-side interop tests (rebuilt libtorch_agn_2_14):

```
cd test/cpp_extensions/libtorch_agn_2_14_extension && pip install -e . -v --no-build-isolation
cd .. && python -m pytest test_libtorch_agnostic.py -q -k "pyobject"
```

9 passed (5 new: dtype/device roundtrips incl. float8 and cuda:1,
tensor_dtype/tensor_device end to end, non-dtype/non-device error
cases). lintrunner (CLANGFORMAT, RUFF, PYFMT, ...) and the
STABLE_SHIM_VERSION / STABLE_SHIM_USAGE adapters are clean on all
touched files.

This PR was authored with the help of an AI assistant (Claude).

Signed-off-by: Pawel Gadzinski <[email protected]>
janeyx99 pushed a commit to pggPL/pytorch that referenced this pull request Oct 6, 2026
Follow-up to pytorch#183323, which introduced the PyObjectConversionInterface
vtable so libtorch-only stable shims can convert PyObjects using
functionality that lives in libtorch_python. This extends the same
mechanism (no new machinery) with torch.dtype <-> ScalarType and
torch.device <-> Device conversions, which python-registered custom ops
need alongside tensors.

Review in the order the pieces fit together: the vtable interface and
its erroring no-op default (torch/csrc/PyObjectConversion.{h,cpp}), the
concrete THPDtype/THPDevice-based implementation in libtorch_python
(torch/csrc/PyObjectConversionPythonImpl.cpp), the C shims
(torch/csrc/shim_common.cpp, torch/csrc/stable/c/shim.h,
shim_function_versions.txt; all versioned at 2.14), the header-only
helpers (torch/csrc/stable/pyobject.h), then tests and docs.

Unlike the tensor shims there is no handle type: a dtype crosses the
ABI boundary as the int32_t code already used by aoti_torch_dtype_*(),
and a device as (device_type, device_index) int32_t in the
aoti_torch_device_type_*() encoding, matching how aoti_torch_get_dtype
/ _get_device_type / _get_device_index encode them. The pyobject.h
helpers translate the codes through torch::stable::detail::from/to like
Tensor::scalar_type() / Tensor::device(), so extension-side and
libtorch-side enum values stay decoupled.

Test Plan:
Minimal CPU build (BUILD_TEST=1, PYTORCH_BUILD_VERSION=2.14.0a0) on a
linux workstation; all touched TUs recompiled and torch imports:

```
pip install -e . -v --no-build-isolation
```

Libtorch-only no-op fallback (links libtorch, not libtorch_python):

```
./build/bin/test_shim --gtest_filter="TorchPyObjectConversion.*"
```

3 tests pass. Extension-side interop tests (rebuilt libtorch_agn_2_14):

```
cd test/cpp_extensions/libtorch_agn_2_14_extension && pip install -e . -v --no-build-isolation
cd .. && python -m pytest test_libtorch_agnostic.py -q -k "pyobject"
```

9 passed (5 new: dtype/device roundtrips incl. float8 and cuda:1,
tensor_dtype/tensor_device end to end, non-dtype/non-device error
cases). lintrunner (CLANGFORMAT, RUFF, PYFMT, ...) and the
STABLE_SHIM_VERSION / STABLE_SHIM_USAGE adapters are clean on all
touched files.

This PR was authored with the help of an AI assistant (Claude).

Signed-off-by: Pawel Gadzinski <[email protected]>
janeyx99 pushed a commit to pggPL/pytorch that referenced this pull request Oct 6, 2026
Follow-up to pytorch#183323, which introduced the PyObjectConversionInterface
vtable so libtorch-only stable shims can convert PyObjects using
functionality that lives in libtorch_python. This extends the same
mechanism (no new machinery) with torch.dtype <-> ScalarType and
torch.device <-> Device conversions, which python-registered custom ops
need alongside tensors.

Review in the order the pieces fit together: the vtable interface and
its erroring no-op default (torch/csrc/PyObjectConversion.{h,cpp}), the
concrete THPDtype/THPDevice-based implementation in libtorch_python
(torch/csrc/PyObjectConversionPythonImpl.cpp), the C shims
(torch/csrc/shim_common.cpp, torch/csrc/stable/c/shim.h,
shim_function_versions.txt; all versioned at 2.14), the header-only
helpers (torch/csrc/stable/pyobject.h), then tests and docs.

Unlike the tensor shims there is no handle type: a dtype crosses the
ABI boundary as the int32_t code already used by aoti_torch_dtype_*(),
and a device as (device_type, device_index) int32_t in the
aoti_torch_device_type_*() encoding, matching how aoti_torch_get_dtype
/ _get_device_type / _get_device_index encode them. The pyobject.h
helpers translate the codes through torch::stable::detail::from/to like
Tensor::scalar_type() / Tensor::device(), so extension-side and
libtorch-side enum values stay decoupled.

Test Plan:
Minimal CPU build (BUILD_TEST=1, PYTORCH_BUILD_VERSION=2.14.0a0) on a
linux workstation; all touched TUs recompiled and torch imports:

```
pip install -e . -v --no-build-isolation
```

Libtorch-only no-op fallback (links libtorch, not libtorch_python):

```
./build/bin/test_shim --gtest_filter="TorchPyObjectConversion.*"
```

3 tests pass. Extension-side interop tests (rebuilt libtorch_agn_2_14):

```
cd test/cpp_extensions/libtorch_agn_2_14_extension && pip install -e . -v --no-build-isolation
cd .. && python -m pytest test_libtorch_agnostic.py -q -k "pyobject"
```

9 passed (5 new: dtype/device roundtrips incl. float8 and cuda:1,
tensor_dtype/tensor_device end to end, non-dtype/non-device error
cases). lintrunner (CLANGFORMAT, RUFF, PYFMT, ...) and the
STABLE_SHIM_VERSION / STABLE_SHIM_USAGE adapters are clean on all
touched files.

This PR was authored with the help of an AI assistant (Claude).

Signed-off-by: Pawel Gadzinski <[email protected]>
pytorchmergebot pushed a commit that referenced this pull request Oct 7, 2026
…be (2.15+) (#192774)

Follow-up to #183323, extending the Python interop shims with dtype/device conversions and a tensor type-check probe, using the same `PyObjectConversionInterface` vtable mechanism (no new machinery).

## Summary
  - Add `torch::stable::dtype_from_pyobject` and `dtype_to_pyobject` for converting between `torch.dtype` and
  `torch::headeronly::ScalarType`.
  - Add `torch::stable::device_from_pyobject` and `device_to_pyobject` for converting between `torch.device` and `torch::stable::Device`.
  - Add `torch::stable::is_tensor_pyobject` for checking whether an object’s actual C-level type is `torch.Tensor` or a genuine subclass.
    - It deliberately ignores spoofed `__class__` attributes.
    - It uses the same structural check as `tensor_from_pyobject`, making it safe as a conversion guard.
  - Add the corresponding stable C shims, versioned for PyTorch 2.15.
    - Dtypes cross the ABI as the existing `int32_t` `ScalarType` encoding.
    - Devices cross as an `int32_t` device type and index, with `-1` representing no index.
    - Inputs are validated before narrowing to the internal enum representations.
  - Implement the conversions in `libtorch_python` through the existing `PyObjectConversionInterface` vtable. Extensions continue to link only against `libtorch`, but `libtorch_python` must be loaded and the GIL must be held.
  - Translate dtype and device values through the stable enum mappings. Valid Python values outside the supported stable subset produce an explicit error.
  - Document reference ownership, GIL requirements, runtime requirements, and the supported conversion behavior.

## Release targeting

These APIs are intentionally marked `TORCH_VERSION_2_15_0` because this change will be cherry-picked into the 2.15 release branch.

## Test plan
- [x] `python -m pytest test/cpp_extensions/test_libtorch_agnostic.py -k pyobject -v` — parametrized roundtrips (7 dtypes incl. float8; cpu/meta/cuda/cuda:1 devices, incl. an index-less device that must not come back as cuda:0), end-to-end `Tensor::scalar_type()`/`device()` -> Python object, the probe on tensors / a tensor subclass / non-tensors, error cases for non-dtype/non-device inputs
- [x] `test_shim --gtest_filter="TorchPyObjectConversion.*"` — 6 tests: no-op fallback returns `AOTI_TORCH_FAILURE` (not a crash) without libtorch_python, and out-of-range dtype/device codes are rejected with clear errors

---
_This PR was prepared with the help of Claude._

Pull Request resolved: #192774
Approved by: https://github.com/janeyx99

Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Jane Xu <[email protected]>
atalman pushed a commit that referenced this pull request Oct 8, 2026
…be (2.15+) (#200183)

Add stable dtype and device PyObject conversions and an is_tensor probe (2.15+) (#192774)

Follow-up to #183323, extending the Python interop shims with dtype/device conversions and a tensor type-check probe, using the same `PyObjectConversionInterface` vtable mechanism (no new machinery).

## Summary
  - Add `torch::stable::dtype_from_pyobject` and `dtype_to_pyobject` for converting between `torch.dtype` and
  `torch::headeronly::ScalarType`.
  - Add `torch::stable::device_from_pyobject` and `device_to_pyobject` for converting between `torch.device` and `torch::stable::Device`.
  - Add `torch::stable::is_tensor_pyobject` for checking whether an object’s actual C-level type is `torch.Tensor` or a genuine subclass.
    - It deliberately ignores spoofed `__class__` attributes.
    - It uses the same structural check as `tensor_from_pyobject`, making it safe as a conversion guard.
  - Add the corresponding stable C shims, versioned for PyTorch 2.15.
    - Dtypes cross the ABI as the existing `int32_t` `ScalarType` encoding.
    - Devices cross as an `int32_t` device type and index, with `-1` representing no index.
    - Inputs are validated before narrowing to the internal enum representations.
  - Implement the conversions in `libtorch_python` through the existing `PyObjectConversionInterface` vtable. Extensions continue to link only against `libtorch`, but `libtorch_python` must be loaded and the GIL must be held.
  - Translate dtype and device values through the stable enum mappings. Valid Python values outside the supported stable subset produce an explicit error.
  - Document reference ownership, GIL requirements, runtime requirements, and the supported conversion behavior.

## Release targeting

These APIs are intentionally marked `TORCH_VERSION_2_15_0` because this change will be cherry-picked into the 2.15 release branch.

## Test plan
- [x] `python -m pytest test/cpp_extensions/test_libtorch_agnostic.py -k pyobject -v` — parametrized roundtrips (7 dtypes incl. float8; cpu/meta/cuda/cuda:1 devices, incl. an index-less device that must not come back as cuda:0), end-to-end `Tensor::scalar_type()`/`device()` -> Python object, the probe on tensors / a tensor subclass / non-tensors, error cases for non-dtype/non-device inputs
- [x] `test_shim --gtest_filter="TorchPyObjectConversion.*"` — 6 tests: no-op fallback returns `AOTI_TORCH_FAILURE` (not a crash) without libtorch_python, and out-of-range dtype/device codes are rejected with clear errors

---
_This PR was prepared with the help of Claude._

Pull Request resolved: #192774
Approved by: https://github.com/janeyx99



(cherry picked from commit 6d0ba1a)

Co-authored-by: Pawel Gadzinski <[email protected]>
Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Jane Xu <[email protected]>
cyyever pushed a commit to BakeLab/pytorch that referenced this pull request Oct 11, 2026
…be (2.15+) (pytorch#192774)

Follow-up to pytorch#183323, extending the Python interop shims with dtype/device conversions and a tensor type-check probe, using the same `PyObjectConversionInterface` vtable mechanism (no new machinery).

## Summary
  - Add `torch::stable::dtype_from_pyobject` and `dtype_to_pyobject` for converting between `torch.dtype` and
  `torch::headeronly::ScalarType`.
  - Add `torch::stable::device_from_pyobject` and `device_to_pyobject` for converting between `torch.device` and `torch::stable::Device`.
  - Add `torch::stable::is_tensor_pyobject` for checking whether an object’s actual C-level type is `torch.Tensor` or a genuine subclass.
    - It deliberately ignores spoofed `__class__` attributes.
    - It uses the same structural check as `tensor_from_pyobject`, making it safe as a conversion guard.
  - Add the corresponding stable C shims, versioned for PyTorch 2.15.
    - Dtypes cross the ABI as the existing `int32_t` `ScalarType` encoding.
    - Devices cross as an `int32_t` device type and index, with `-1` representing no index.
    - Inputs are validated before narrowing to the internal enum representations.
  - Implement the conversions in `libtorch_python` through the existing `PyObjectConversionInterface` vtable. Extensions continue to link only against `libtorch`, but `libtorch_python` must be loaded and the GIL must be held.
  - Translate dtype and device values through the stable enum mappings. Valid Python values outside the supported stable subset produce an explicit error.
  - Document reference ownership, GIL requirements, runtime requirements, and the supported conversion behavior.

## Release targeting

These APIs are intentionally marked `TORCH_VERSION_2_15_0` because this change will be cherry-picked into the 2.15 release branch.

## Test plan
- [x] `python -m pytest test/cpp_extensions/test_libtorch_agnostic.py -k pyobject -v` — parametrized roundtrips (7 dtypes incl. float8; cpu/meta/cuda/cuda:1 devices, incl. an index-less device that must not come back as cuda:0), end-to-end `Tensor::scalar_type()`/`device()` -> Python object, the probe on tensors / a tensor subclass / non-tensors, error cases for non-dtype/non-device inputs
- [x] `test_shim --gtest_filter="TorchPyObjectConversion.*"` — 6 tests: no-op fallback returns `AOTI_TORCH_FAILURE` (not a crash) without libtorch_python, and out-of-range dtype/device codes are rejected with clear errors

---
_This PR was prepared with the help of Claude._

Pull Request resolved: pytorch#192774
Approved by: https://github.com/janeyx99

Co-authored-by: Claude Fable 5 <[email protected]>
Co-authored-by: Jane Xu <[email protected]>
leofang added a commit to leofang/cuda-python that referenced this pull request Oct 30, 2026
Keeps the 2.3 lower bound (required for the AOTI metadata functions).
The dispatch in _tensor_bridge.view_as_torch_tensor now picks between:

  * torch >= 2.14 -- stable torch_tensor_from_pyobject shim
    (pytorch/pytorch#183323; part of torch's stable C ABI contract)
  * torch 2.3 - 2.13 -- pyobj_to_aten_handle pointer-arithmetic,
    validated against the two known THPVariable layouts

Future torch releases >= 2.14 are automatically covered by the shim,
removing the per-release cap bump treadmill.
leofang added a commit to NVIDIA/cuda-python that referenced this pull request Oct 31, 2026
…ersion cap (#3033)

* cuda.core: bump torch tensor-bridge upper cap to 2.14

* cuda.core: use torch 2.14+ stable PyObject->Tensor shim as primary path

PyTorch >= 2.14 exposes torch_tensor_from_pyobject /
aoti_torch_delete_tensor_object as part of the stable C ABI (landed via
pytorch/pytorch#183323).  These let us obtain an AtenTensorHandle from a
Python tensor object without peering at THPVariable's internal layout.

Dispatch is driven by *symbol presence* via dlsym / GetProcAddress on
first call to view_as_torch_tensor:
  - resolved -> primary path (owned handle, deleted after metadata reads)
  - NULL     -> fall back to the existing pyobj_to_aten_handle
                pointer-arithmetic trick, which stays correct for
                torch 2.3-2.13 (the only versions where this matters).

The outer version gate in _memoryview.pyx (``2.3 <= torch <= 2.14``) is
unchanged -- it still bounds the fallback.  Once we drop support for
torch < 2.14 we can remove the upper bound entirely because the shim
itself is part of torch's stable ABI contract.

Diagnostics kept module-private:
  - _get_pyobj_path_counts() / _reset_pyobj_path_counts()
  - _get_shim_available()
  - _set_use_shim(bool) to force the fallback for A/B benchmarking.

* cuda.core: drop benchmark toggle and path counters from tensor bridge

* cuda.core: drop upper torch version cap, shim covers 2.14+

Keeps the 2.3 lower bound (required for the AOTI metadata functions).
The dispatch in _tensor_bridge.view_as_torch_tensor now picks between:

  * torch >= 2.14 -- stable torch_tensor_from_pyobject shim
    (pytorch/pytorch#183323; part of torch's stable C ABI contract)
  * torch 2.3 - 2.13 -- pyobj_to_aten_handle pointer-arithmetic,
    validated against the two known THPVariable layouts

Future torch releases >= 2.14 are automatically covered by the shim,
removing the per-release cap bump treadmill.

* cuda.core: regenerate _tensor_bridge.pyi for the new ctypedefs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ciflow/docker ciflow/trunk Trigger trunk jobs on your pull request Merged open source release notes: cpp release notes category topic: new features topic category triaged This issue has been looked at a team member, and triaged and prioritized into an appropriate module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants