Skip to content

Commit 5c132a6

Browse files
authored
Merge branch 'main' into recipe/torch_export_models
2 parents 189748d + f7d06b6 commit 5c132a6

23 files changed

+820
-771
lines changed

.github/workflows/build-tutorials.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ jobs:
4444
4545
- name: Checkout Tutorials
4646
uses: actions/checkout@v3
47+
with:
48+
fetch-depth: 0
4749

4850
- name: Setup Linux
4951
uses: pytorch/pytorch/.github/actions/setup-linux@main
@@ -115,6 +117,8 @@ jobs:
115117
116118
- name: Checkout Tutorials
117119
uses: actions/checkout@v3
120+
with:
121+
fetch-depth: 0
118122

119123
- name: Setup Linux
120124
uses: pytorch/pytorch/.github/actions/setup-linux@main

.jenkins/insert_last_verified.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import json
2+
import os
3+
import subprocess
4+
import sys
5+
from datetime import datetime
6+
7+
from bs4 import BeautifulSoup
8+
9+
10+
json_file_path = "tutorials-review-data.json"
11+
12+
# paths to skip from the post-processing script
13+
paths_to_skip = [
14+
"beginner/examples_autograd/two_layer_net_custom_function", # not present in the repo
15+
"beginner/examples_nn/two_layer_net_module", # not present in the repo
16+
"beginner/examples_tensor/two_layer_net_numpy", # not present in the repo
17+
"beginner/examples_tensor/two_layer_net_tensor", # not present in the repo
18+
"beginner/examples_autograd/two_layer_net_autograd", # not present in the repo
19+
"beginner/examples_nn/two_layer_net_optim", # not present in the repo
20+
"beginner/examples_nn/two_layer_net_nn", # not present in the repo
21+
"intermediate/coding_ddpg", # not present in the repo - will delete the carryover
22+
]
23+
# Mapping of source directories to build directories
24+
source_to_build_mapping = {
25+
"beginner": "beginner_source",
26+
"recipes": "recipes_source",
27+
"distributed": "distributed",
28+
"intermediate": "intermediate_source",
29+
"prototype": "prototype_source",
30+
"advanced": "advanced_source",
31+
"": "", # root dir for index.rst
32+
}
33+
34+
def get_git_log_date(file_path, git_log_args):
35+
try:
36+
result = subprocess.run(
37+
["git", "log"] + git_log_args + ["--", file_path],
38+
capture_output=True,
39+
text=True,
40+
check=True,
41+
)
42+
if result.stdout:
43+
date_str = result.stdout.splitlines()[0]
44+
return datetime.strptime(date_str, "%a, %d %b %Y %H:%M:%S %z")
45+
except subprocess.CalledProcessError:
46+
pass
47+
raise ValueError(f"Could not find date for {file_path}")
48+
49+
def get_creation_date(file_path):
50+
return get_git_log_date(file_path, ["--diff-filter=A", "--format=%aD"]).strftime("%b %d, %Y")
51+
52+
53+
def get_last_updated_date(file_path):
54+
return get_git_log_date(file_path, ["-1", "--format=%aD"]).strftime("%b %d, %Y")
55+
56+
# Try to find the source file with the given base path and the extensions .rst and .py
57+
def find_source_file(base_path):
58+
for ext in [".rst", ".py"]:
59+
source_file_path = base_path + ext
60+
if os.path.exists(source_file_path):
61+
return source_file_path
62+
return None
63+
64+
65+
# Function to process a JSON file and insert the "Last Verified" information into the HTML files
66+
def process_json_file(build_dir , json_file_path):
67+
with open(json_file_path, "r", encoding="utf-8") as json_file:
68+
json_data = json.load(json_file)
69+
70+
for entry in json_data:
71+
path = entry["Path"]
72+
last_verified = entry["Last Verified"]
73+
status = entry.get("Status", "")
74+
if path in paths_to_skip:
75+
print(f"Skipping path: {path}")
76+
continue
77+
if status in ["needs update", "not verified"]:
78+
formatted_last_verified = "Not Verified"
79+
elif last_verified:
80+
try:
81+
last_verified_date = datetime.strptime(last_verified, "%Y-%m-%d")
82+
formatted_last_verified = last_verified_date.strftime("%b %d, %Y")
83+
except ValueError:
84+
formatted_last_verified = "Unknown"
85+
else:
86+
formatted_last_verified = "Not Verified"
87+
if status == "deprecated":
88+
formatted_last_verified += "Deprecated"
89+
90+
for build_subdir, source_subdir in source_to_build_mapping.items():
91+
if path.startswith(build_subdir):
92+
html_file_path = os.path.join(build_dir, path + ".html")
93+
base_source_path = os.path.join(
94+
source_subdir, path[len(build_subdir) + 1 :]
95+
)
96+
source_file_path = find_source_file(base_source_path)
97+
break
98+
else:
99+
print(f"Warning: No mapping found for path {path}")
100+
continue
101+
102+
if not os.path.exists(html_file_path):
103+
print(
104+
f"Warning: HTML file not found for path {html_file_path}."
105+
"If this is a new tutorial, please add it to the audit JSON file and set the Verified status and todays's date."
106+
)
107+
continue
108+
109+
if not source_file_path:
110+
print(f"Warning: Source file not found for path {base_source_path}.")
111+
continue
112+
113+
created_on = get_creation_date(source_file_path)
114+
last_updated = get_last_updated_date(source_file_path)
115+
116+
with open(html_file_path, "r", encoding="utf-8") as file:
117+
soup = BeautifulSoup(file, "html.parser")
118+
# Check if the <p> tag with class "date-info-last-verified" already exists
119+
existing_date_info = soup.find("p", {"class": "date-info-last-verified"})
120+
if existing_date_info:
121+
print(
122+
f"Warning: <p> tag with class 'date-info-last-verified' already exists in {html_file_path}"
123+
)
124+
continue
125+
126+
h1_tag = soup.find("h1") # Find the h1 tag to insert the dates
127+
if h1_tag:
128+
date_info_tag = soup.new_tag("p", **{"class": "date-info-last-verified"})
129+
date_info_tag["style"] = "color: #6c6c6d; font-size: small;"
130+
# Add the "Created On", "Last Updated", and "Last Verified" information
131+
date_info_tag.string = (
132+
f"Created On: {created_on} | "
133+
f"Last Updated: {last_updated} | "
134+
f"Last Verified: {formatted_last_verified}"
135+
)
136+
# Insert the new tag after the <h1> tag
137+
h1_tag.insert_after(date_info_tag)
138+
# Save back to the HTML.
139+
with open(html_file_path, "w", encoding="utf-8") as file:
140+
file.write(str(soup))
141+
else:
142+
print(f"Warning: <h1> tag not found in {html_file_path}")
143+
144+
145+
def main():
146+
if len(sys.argv) < 2:
147+
print("Error: Build directory not provided. Exiting.")
148+
exit(1)
149+
build_dir = sys.argv[1]
150+
print(f"Build directory: {build_dir}")
151+
process_json_file(build_dir , json_file_path)
152+
print(
153+
"Finished processing JSON file. Please check the output for any warnings. "
154+
"Pages like `nlp/index.html` are generated only during the full `make docs` "
155+
"or `make html` build. Warnings about these files when you run `make html-noplot` "
156+
"can be ignored."
157+
)
158+
159+
if __name__ == "__main__":
160+
main()

Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,29 @@ download:
8686
wget https://www.cis.upenn.edu/~jshi/ped_html/PennFudanPed.zip -P $(DATADIR)
8787
unzip -o $(DATADIR)/PennFudanPed.zip -d intermediate_source/data/
8888

89+
download-last-reviewed-json:
90+
@echo "Downloading tutorials-review-data.json..."
91+
curl -o tutorials-review-data.json https://raw.githubusercontent.com/pytorch/tutorials/refs/heads/last-reviewed-data-json/tutorials-review-data.json
92+
@echo "Finished downloading tutorials-review-data.json."
8993
docs:
9094
make download
95+
make download-last-reviewed-json
9196
make html
97+
@python .jenkins/insert_last_verified.py $(BUILDDIR)/html
9298
rm -rf docs
9399
cp -r $(BUILDDIR)/html docs
94100
touch docs/.nojekyll
101+
rm -rf tutorials-review-data.json
95102

96103
html-noplot:
97104
$(SPHINXBUILD) -D plot_gallery=0 -b html $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html"
98105
# bash .jenkins/remove_invisible_code_block_batch.sh "$(BUILDDIR)/html"
99106
@echo
107+
make download-last-reviewed-json
100108
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
109+
@echo "Running post-processing script to insert 'Last Verified' dates..."
110+
@python .jenkins/insert_last_verified.py $(BUILDDIR)/html
111+
rm -rf tutorials-review-data.json
101112

102113
clean-cache:
103114
make clean

advanced_source/cpp_custom_ops.rst

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ Custom C++ and CUDA Operators
1919
* PyTorch 2.4 or later
2020
* Basic understanding of C++ and CUDA programming
2121

22+
.. note::
23+
24+
This tutorial will also work on AMD ROCm with no additional modifications.
25+
2226
PyTorch offers a large library of operators that work on Tensors (e.g. torch.add, torch.sum, etc).
2327
However, you may wish to bring a new custom operator to PyTorch. This tutorial demonstrates the
2428
blessed path to authoring a custom operator written in C++/CUDA.
@@ -63,9 +67,47 @@ Using ``cpp_extension`` is as simple as writing the following ``setup.py``:
6367
6468
If you need to compile CUDA code (for example, ``.cu`` files), then instead use
6569
`torch.utils.cpp_extension.CUDAExtension <https://pytorch.org/docs/stable/cpp_extension.html#torch.utils.cpp_extension.CUDAExtension>`_.
66-
Please see how
67-
`extension-cpp <https://github.com/pytorch/extension-cpp>`_ for an example for
68-
how this is set up.
70+
Please see `extension-cpp <https://github.com/pytorch/extension-cpp>`_ for an
71+
example for how this is set up.
72+
73+
Starting with PyTorch 2.6, you can now build a single wheel for multiple CPython
74+
versions (similar to what you would do for pure python packages). In particular,
75+
if your custom library adheres to the `CPython Stable Limited API
76+
<https://docs.python.org/3/c-api/stable.html>`_ or avoids CPython entirely, you
77+
can build one Python agnostic wheel against a minimum supported CPython version
78+
through setuptools' ``py_limited_api`` flag, like so:
79+
80+
.. code-block:: python
81+
82+
from setuptools import setup, Extension
83+
from torch.utils import cpp_extension
84+
85+
setup(name="extension_cpp",
86+
ext_modules=[
87+
cpp_extension.CppExtension(
88+
"extension_cpp",
89+
["python_agnostic_code.cpp"],
90+
py_limited_api=True)],
91+
cmdclass={'build_ext': cpp_extension.BuildExtension},
92+
options={"bdist_wheel": {"py_limited_api": "cp39"}}
93+
)
94+
95+
Note that you must specify ``py_limited_api=True`` both within ``setup``
96+
and also as an option to the ``"bdist_wheel"`` command with the minimal supported
97+
Python version (in this case, 3.9). This ``setup`` would build one wheel that could
98+
be installed across multiple Python versions ``python>=3.9``. Please see
99+
`torchao <https://github.com/pytorch/ao>`_ for an example.
100+
101+
.. note::
102+
103+
You must verify independently that the built wheel is truly Python agnostic.
104+
Specifying ``py_limited_api`` does not check for any guarantees, so it is possible
105+
to build a wheel that looks Python agnostic but will crash, or worse, be silently
106+
incorrect, in another Python environment. Take care to avoid using unstable CPython
107+
APIs, for example APIs from libtorch_python (in particular pytorch/python bindings,)
108+
and to only use APIs from libtorch (aten objects, operators and the dispatcher).
109+
For example, to give access to custom ops from Python, the library should register
110+
the ops through the dispatcher (covered below!).
69111

70112
Defining the custom op and adding backend implementations
71113
---------------------------------------------------------
@@ -177,7 +219,7 @@ operator specifies how to compute the metadata of output tensors given the metad
177219
The FakeTensor kernel should return dummy Tensors of your choice with
178220
the correct Tensor metadata (shape/strides/``dtype``/device).
179221

180-
We recommend that this be done from Python via the `torch.library.register_fake` API,
222+
We recommend that this be done from Python via the ``torch.library.register_fake`` API,
181223
though it is possible to do this from C++ as well (see
182224
`The Custom Operators Manual <https://pytorch.org/docs/main/notes/custom_operators.html>`_
183225
for more details).
@@ -188,7 +230,9 @@ for more details).
188230
# before calling ``torch.library`` APIs that add registrations for the
189231
# C++ custom operator(s). The following import loads our
190232
# C++ custom operator definitions.
191-
# See the next section for more details.
233+
# Note that if you are striving for Python agnosticism, you should use
234+
# the ``load_library(...)`` API call instead. See the next section for
235+
# more details.
192236
from . import _C
193237
194238
@torch.library.register_fake("extension_cpp::mymuladd")
@@ -214,7 +258,10 @@ of two ways:
214258
1. If you're following this tutorial, importing the Python C extension module
215259
we created will load the C++ custom operator definitions.
216260
2. If your C++ custom operator is located in a shared library object, you can
217-
also use ``torch.ops.load_library("/path/to/library.so")`` to load it.
261+
also use ``torch.ops.load_library("/path/to/library.so")`` to load it. This
262+
is the blessed path for Python agnosticism, as you will not have a Python C
263+
extension module to import. See `torchao __init__.py <https://github.com/pytorch/ao/blob/881e84b4398eddcea6fee4d911fc329a38b5cd69/torchao/__init__.py#L26-L28>`_
264+
for an example.
218265

219266

220267
Adding training (autograd) support for an operator

advanced_source/cpp_frontend.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -969,15 +969,15 @@ the data loader every epoch and then write the GAN training code:
969969
discriminator->zero_grad();
970970
torch::Tensor real_images = batch.data;
971971
torch::Tensor real_labels = torch::empty(batch.data.size(0)).uniform_(0.8, 1.0);
972-
torch::Tensor real_output = discriminator->forward(real_images);
972+
torch::Tensor real_output = discriminator->forward(real_images).reshape(real_labels.sizes());
973973
torch::Tensor d_loss_real = torch::binary_cross_entropy(real_output, real_labels);
974974
d_loss_real.backward();
975975
976976
// Train discriminator with fake images.
977977
torch::Tensor noise = torch::randn({batch.data.size(0), kNoiseSize, 1, 1});
978978
torch::Tensor fake_images = generator->forward(noise);
979979
torch::Tensor fake_labels = torch::zeros(batch.data.size(0));
980-
torch::Tensor fake_output = discriminator->forward(fake_images.detach());
980+
torch::Tensor fake_output = discriminator->forward(fake_images.detach()).reshape(fake_labels.sizes());
981981
torch::Tensor d_loss_fake = torch::binary_cross_entropy(fake_output, fake_labels);
982982
d_loss_fake.backward();
983983
@@ -987,7 +987,7 @@ the data loader every epoch and then write the GAN training code:
987987
// Train generator.
988988
generator->zero_grad();
989989
fake_labels.fill_(1);
990-
fake_output = discriminator->forward(fake_images);
990+
fake_output = discriminator->forward(fake_images).reshape(fake_labels.sizes());
991991
torch::Tensor g_loss = torch::binary_cross_entropy(fake_output, fake_labels);
992992
g_loss.backward();
993993
generator_optimizer.step();

advanced_source/custom_ops_landing_page.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ You may wish to author a custom operator from Python (as opposed to C++) if:
2323
respect to ``torch.compile`` and ``torch.export``.
2424
- you have some Python bindings to C++/CUDA kernels and want those to compose with PyTorch
2525
subsystems (like ``torch.compile`` or ``torch.autograd``)
26+
- you are using Python (and not a C++-only environment like AOTInductor).
2627

2728
Integrating custom C++ and/or CUDA code with PyTorch
2829
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

advanced_source/pendulum.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
3434
In the process, we will touch three crucial components of TorchRL:
3535
36-
* `environments <https://pytorch.org/rl/reference/envs.html>`__
37-
* `transforms <https://pytorch.org/rl/reference/envs.html#transforms>`__
38-
* `models (policy and value function) <https://pytorch.org/rl/reference/modules.html>`__
36+
* `environments <https://pytorch.org/rl/stable/reference/envs.html>`__
37+
* `transforms <https://pytorch.org/rl/stable/reference/envs.html#transforms>`__
38+
* `models (policy and value function) <https://pytorch.org/rl/stable/reference/modules.html>`__
3939
4040
"""
4141

@@ -384,7 +384,7 @@ def _reset(self, tensordict):
384384
# convenient shortcuts to the content of the output and input spec containers.
385385
#
386386
# TorchRL offers multiple :class:`~torchrl.data.TensorSpec`
387-
# `subclasses <https://pytorch.org/rl/reference/data.html#tensorspec>`_ to
387+
# `subclasses <https://pytorch.org/rl/stable/reference/data.html#tensorspec>`_ to
388388
# encode the environment's input and output characteristics.
389389
#
390390
# Specs shape

advanced_source/python_custom_ops.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
.. _python-custom-ops-tutorial:
55
6-
Python Custom Operators
6+
Custom Python Operators
77
=======================
88
99
.. grid:: 2
@@ -30,6 +30,12 @@
3030
into the function).
3131
- Adding training support to an arbitrary Python function
3232
33+
Use :func:`torch.library.custom_op` to create Python custom operators.
34+
Use the C++ ``TORCH_LIBRARY`` APIs to create C++ custom operators (these
35+
work in Python-less environments).
36+
See the `Custom Operators Landing Page <https://pytorch.org/tutorials/advanced/custom_ops_landing_page.html>`_
37+
for more details.
38+
3339
Please note that if your operation can be expressed as a composition of
3440
existing PyTorch operators, then there is usually no need to use the custom operator
3541
API -- everything (for example ``torch.compile``, training support) should

0 commit comments

Comments
 (0)