mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-20 21:14:14 +08:00
Enable CPP/CUDAExtension with py_limited_api for python agnosticism (#138088)
Getting tested with ao, but now there is a real test i added. ## What does this PR do? We want to allow custom PyTorch extensions to be able to build one wheel for multiple Python versions, in other words, achieve python agnosticism. It turns out that there is such a way that setuptools/Python provides already! Namely, if the user promises to use only the Python limited API in their extension, they can pass in `py_limited_api` to their Extension class and to the bdist_wheel command (with a min python version) in order to build 1 wheel that will suffice across multiple Python versions. Sounds lovely! Why don't people do that already with PyTorch? Well 2 things. This workflow is hardly documented (even searching for python agnostic specifically does not reveal many answers) so I'd expect that people simply don't know about it. But even if they did, _PyTorch_ custom Extensions would still not work because we always link torch_python, which does not abide by py_limited_api rules. So this is where this PR comes in! We respect when the user specifies py_limited_api and skip linking torch_python under that condition, allowing users to enroll in the provided functionality I just described. ## How do I know this PR works? I manually tested my silly little ultra_norm locally (with `import python_agnostic`) and wrote a test case for the extension showing that - torch_python doesn't show up in the ldd tree - no Py- symbols show up It may be a little confusing that our test case is actually python-free (more clean than python-agnostic) but it is sufficient (and not necessary) towards showing that this change works. Pull Request resolved: https://github.com/pytorch/pytorch/pull/138088 Approved by: https://github.com/ezyang, https://github.com/albanD
This commit is contained in:
committed by
PyTorch MergeBot
parent
fb02b40d27
commit
be27dbf2b8
@ -2,8 +2,11 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import unittest
|
||||
from itertools import repeat
|
||||
from pathlib import Path
|
||||
from typing import get_args, get_origin, Union
|
||||
|
||||
import torch
|
||||
@ -13,6 +16,7 @@ import torch.utils.cpp_extension
|
||||
from torch.testing._internal.common_cuda import TEST_CUDA
|
||||
from torch.testing._internal.common_utils import (
|
||||
IS_WINDOWS,
|
||||
shell,
|
||||
skipIfTorchDynamo,
|
||||
xfailIfTorchDynamo,
|
||||
)
|
||||
@ -164,6 +168,48 @@ class TestCppExtensionAOT(common.TestCase):
|
||||
test = cuda_dlink.add(a, b)
|
||||
self.assertEqual(test, ref)
|
||||
|
||||
@unittest.skipIf(not TEST_CUDA, "python_agnostic is a CUDA extension + needs CUDA")
|
||||
@unittest.skipIf(not common.IS_LINUX, "test requires linux tools ldd and nm")
|
||||
def test_python_agnostic(self):
|
||||
# For this test, run_test.py will call `python setup.py bdist_wheel` in the
|
||||
# cpp_extensions/python_agnostic_extension folder, where the extension and
|
||||
# setup calls specify py_limited_api to `True`. To approximate that the
|
||||
# extension is indeed python agnostic, we test
|
||||
# a. The extension wheel name contains "cp39-abi3", meaning the wheel
|
||||
# should be runnable for any Python 3 version after and including 3.9
|
||||
# b. The produced shared library does not have libtorch_python.so as a
|
||||
# dependency from the output of "ldd _C.so"
|
||||
# c. The .so does not need any python related symbols. We approximate
|
||||
# this by running "nm -u _C.so" and grepping that nothing starts with "Py"
|
||||
|
||||
dist_root = os.path.join("cpp_extensions", "python_agnostic_extension", "dist")
|
||||
matches = list(Path(dist_root).glob("*.whl"))
|
||||
self.assertEqual(len(matches), 1, msg=str(matches))
|
||||
whl_file = matches[0]
|
||||
self.assertRegex(str(whl_file), r".*python_agnostic-0\.0-cp39-abi3-.*\.whl")
|
||||
|
||||
build_root = os.path.join(
|
||||
"cpp_extensions", "python_agnostic_extension", "build"
|
||||
)
|
||||
matches = list(Path(build_root).glob("**/*.so"))
|
||||
self.assertEqual(len(matches), 1, msg=str(matches))
|
||||
so_file = matches[0]
|
||||
lddtree = subprocess.check_output(["ldd", so_file]).decode("utf-8")
|
||||
self.assertFalse("torch_python" in lddtree)
|
||||
|
||||
missing_symbols = subprocess.check_output(["nm", "-u", so_file]).decode("utf-8")
|
||||
self.assertFalse("Py" in missing_symbols)
|
||||
|
||||
# finally, clean up the folder
|
||||
cmd = [sys.executable, "setup.py", "clean"]
|
||||
return_code = shell(
|
||||
cmd,
|
||||
cwd=os.path.join("cpp_extensions", "python_agnostic_extension"),
|
||||
env=os.environ.copy(),
|
||||
)
|
||||
if return_code != 0:
|
||||
return return_code
|
||||
|
||||
|
||||
@torch.testing._internal.common_utils.markDynamoStrictTest
|
||||
class TestPybindTypeCasters(common.TestCase):
|
||||
|
Reference in New Issue
Block a user