Auto-sync: 2026-06-25 22:43:25 (part 28)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/INSTALLER +1 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/METADATA +48 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/RECORD +0 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/WHEEL +5 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/entry_points.txt +5 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/LICENSE +84 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/NOTICE +456 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/top_level.txt +2 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/iter/utils.py +60 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/__init__.py +20 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/callable.py +67 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/combinatorics.py +132 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/combining.py +111 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/grouping.py +76 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/utils.py +61 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/__init__.py +0 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/common.py +414 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/decoder.py +389 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/snapshot.py +65 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/dataset.py +511 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/distributed.py +157 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/graph.py +163 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/graph_settings.py +173 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/sampler.py +354 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/deterministic.py +22 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/dlpack.py +231 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/file_baton.py +63 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/flop_counter.py +1017 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/__init__.py +1 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/constants.py +66 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/cuda_to_hip_mappings.py +0 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/hipify_python.py +1175 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/version.py +1 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hooks.py +257 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/jit/__init__.py +0 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/jit/log_extract.py +118 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/mkldnn.py +238 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/mobile_optimizer.py +135 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/__init__.py +450 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/__main__.py +5 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/code.js +689 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/htm.mjs +2 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/preact.mjs +2 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/skeleton.html +21 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_zoo.py +2 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/module_tracker.py +160 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/serialization/__init__.py +1 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/serialization/config.py +25 -0
- outputs/audit_venv/lib/python3.11/site-packages/torch/utils/show_pickle.py +151 -0
.gitattributes
CHANGED
|
@@ -131,3 +131,5 @@ outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cpu.so filter
|
|
| 131 |
outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda.so filter=lfs diff=lfs merge=lfs -text
|
| 132 |
outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda_linalg.so filter=lfs diff=lfs merge=lfs -text
|
| 133 |
outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_python.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 131 |
outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda.so filter=lfs diff=lfs merge=lfs -text
|
| 132 |
outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda_linalg.so filter=lfs diff=lfs merge=lfs -text
|
| 133 |
outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_python.so filter=lfs diff=lfs merge=lfs -text
|
| 134 |
+
outputs/audit_venv/lib/python3.11/site-packages/yaml/_yaml.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 135 |
+
outputs/external_vla_export_maniskill_full_no_images/train.jsonl filter=lfs diff=lfs merge=lfs -text
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/METADATA
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.2
|
| 2 |
+
Name: torch
|
| 3 |
+
Version: 2.12.1+computecanada
|
| 4 |
+
Summary: Tensors and Dynamic neural networks in Python with strong GPU acceleration
|
| 5 |
+
Description-Content-Type: text/markdown
|
| 6 |
+
Keywords: pytorch,machine learning
|
| 7 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 8 |
+
Classifier: Intended Audience :: Developers
|
| 9 |
+
Classifier: Intended Audience :: Education
|
| 10 |
+
Classifier: Intended Audience :: Science/Research
|
| 11 |
+
Classifier: Topic :: Scientific/Engineering
|
| 12 |
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
| 13 |
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
| 14 |
+
Classifier: Topic :: Software Development
|
| 15 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 16 |
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
| 17 |
+
Classifier: Programming Language :: C++
|
| 18 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.14
|
| 24 |
+
Author-email: PyTorch Team <packages@pytorch.org>
|
| 25 |
+
Project-URL: Homepage, https://pytorch.org
|
| 26 |
+
Project-URL: Repository, https://github.com/pytorch/pytorch
|
| 27 |
+
Project-URL: Documentation, https://pytorch.org/docs
|
| 28 |
+
Project-URL: Issue Tracker, https://github.com/pytorch/pytorch/issues
|
| 29 |
+
Project-URL: Forum, https://discuss.pytorch.org
|
| 30 |
+
Requires-Python: >=3.10
|
| 31 |
+
Requires-Dist: filelock
|
| 32 |
+
Requires-Dist: typing-extensions>=4.10.0
|
| 33 |
+
Requires-Dist: setuptools<82
|
| 34 |
+
Requires-Dist: sympy>=1.13.3
|
| 35 |
+
Requires-Dist: networkx>=2.5.1
|
| 36 |
+
Requires-Dist: jinja2
|
| 37 |
+
Requires-Dist: fsspec>=0.8.5
|
| 38 |
+
Requires-Dist: optree>=0.13.0; extra == "optree"
|
| 39 |
+
Requires-Dist: opt-einsum>=3.3; extra == "opt-einsum"
|
| 40 |
+
Requires-Dist: pyyaml; extra == "pyyaml"
|
| 41 |
+
Provides-Extra: optree
|
| 42 |
+
Provides-Extra: opt-einsum
|
| 43 |
+
Provides-Extra: pyyaml
|
| 44 |
+
License-File: LICENSE
|
| 45 |
+
License-File: NOTICE
|
| 46 |
+
Dynamic: license-file
|
| 47 |
+
|
| 48 |
+
BSD-3-Clause
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/RECORD
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: wheelfile 0.0.8
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: cp311-cp311-linux_x86_64
|
| 5 |
+
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
torchrun = torch.distributed.run:main
|
| 3 |
+
|
| 4 |
+
[torchrun.logs_specs]
|
| 5 |
+
default = torch.distributed.elastic.multiprocessing:DefaultLogsSpecs
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From PyTorch:
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2016- Facebook, Inc (Adam Paszke)
|
| 4 |
+
Copyright (c) 2014- Facebook, Inc (Soumith Chintala)
|
| 5 |
+
Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
|
| 6 |
+
Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
|
| 7 |
+
Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
|
| 8 |
+
Copyright (c) 2011-2013 NYU (Clement Farabet)
|
| 9 |
+
Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
|
| 10 |
+
Copyright (c) 2006 Idiap Research Institute (Samy Bengio)
|
| 11 |
+
Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
|
| 12 |
+
|
| 13 |
+
From Caffe2:
|
| 14 |
+
|
| 15 |
+
Copyright (c) 2016-present, Facebook Inc. All rights reserved.
|
| 16 |
+
|
| 17 |
+
All contributions by Facebook:
|
| 18 |
+
Copyright (c) 2016 Facebook Inc.
|
| 19 |
+
|
| 20 |
+
All contributions by Google:
|
| 21 |
+
Copyright (c) 2015 Google Inc.
|
| 22 |
+
All rights reserved.
|
| 23 |
+
|
| 24 |
+
All contributions by Yangqing Jia:
|
| 25 |
+
Copyright (c) 2015 Yangqing Jia
|
| 26 |
+
All rights reserved.
|
| 27 |
+
|
| 28 |
+
All contributions by Kakao Brain:
|
| 29 |
+
Copyright 2019-2020 Kakao Brain
|
| 30 |
+
|
| 31 |
+
All contributions by Cruise LLC:
|
| 32 |
+
Copyright (c) 2022 Cruise LLC.
|
| 33 |
+
All rights reserved.
|
| 34 |
+
|
| 35 |
+
All contributions by Tri Dao:
|
| 36 |
+
Copyright (c) 2024 Tri Dao.
|
| 37 |
+
All rights reserved.
|
| 38 |
+
|
| 39 |
+
All contributions by Arm:
|
| 40 |
+
Copyright (c) 2021, 2023-2025 Arm Limited and/or its affiliates
|
| 41 |
+
|
| 42 |
+
All contributions from Caffe:
|
| 43 |
+
Copyright(c) 2013, 2014, 2015, the respective contributors
|
| 44 |
+
All rights reserved.
|
| 45 |
+
|
| 46 |
+
All other contributions:
|
| 47 |
+
Copyright(c) 2015, 2016 the respective contributors
|
| 48 |
+
All rights reserved.
|
| 49 |
+
|
| 50 |
+
Caffe2 uses a copyright model similar to Caffe: each contributor holds
|
| 51 |
+
copyright over their contributions to Caffe2. The project versioning records
|
| 52 |
+
all such contribution and copyright details. If a contributor wants to further
|
| 53 |
+
mark their specific copyright on a particular contribution, they should
|
| 54 |
+
indicate their copyright solely in the commit message of the change when it is
|
| 55 |
+
committed.
|
| 56 |
+
|
| 57 |
+
All rights reserved.
|
| 58 |
+
|
| 59 |
+
Redistribution and use in source and binary forms, with or without
|
| 60 |
+
modification, are permitted provided that the following conditions are met:
|
| 61 |
+
|
| 62 |
+
1. Redistributions of source code must retain the above copyright
|
| 63 |
+
notice, this list of conditions and the following disclaimer.
|
| 64 |
+
|
| 65 |
+
2. Redistributions in binary form must reproduce the above copyright
|
| 66 |
+
notice, this list of conditions and the following disclaimer in the
|
| 67 |
+
documentation and/or other materials provided with the distribution.
|
| 68 |
+
|
| 69 |
+
3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories America
|
| 70 |
+
and IDIAP Research Institute nor the names of its contributors may be
|
| 71 |
+
used to endorse or promote products derived from this software without
|
| 72 |
+
specific prior written permission.
|
| 73 |
+
|
| 74 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
| 75 |
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
| 76 |
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
| 77 |
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
| 78 |
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
| 79 |
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
| 80 |
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
| 81 |
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
| 82 |
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
| 83 |
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
| 84 |
+
POSSIBILITY OF SUCH DAMAGE.
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/NOTICE
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
=======================================================================
|
| 2 |
+
Software under third_party
|
| 3 |
+
=======================================================================
|
| 4 |
+
Software libraries under third_party are provided as github submodule
|
| 5 |
+
links, and their content is not part of the Caffe2 codebase. Their
|
| 6 |
+
licences can be found under the respective software repositories.
|
| 7 |
+
|
| 8 |
+
=======================================================================
|
| 9 |
+
Earlier BSD License
|
| 10 |
+
=======================================================================
|
| 11 |
+
Early development of Caffe2 in 2015 and early 2016 is licensed under the
|
| 12 |
+
BSD license. The license is attached below:
|
| 13 |
+
|
| 14 |
+
All contributions by Facebook:
|
| 15 |
+
Copyright (c) 2016 Facebook Inc.
|
| 16 |
+
|
| 17 |
+
All contributions by Google:
|
| 18 |
+
Copyright (c) 2015 Google Inc.
|
| 19 |
+
All rights reserved.
|
| 20 |
+
|
| 21 |
+
All contributions by Yangqing Jia:
|
| 22 |
+
Copyright (c) 2015 Yangqing Jia
|
| 23 |
+
All rights reserved.
|
| 24 |
+
|
| 25 |
+
All contributions by Kakao Brain:
|
| 26 |
+
Copyright 2019-2020 Kakao Brain
|
| 27 |
+
|
| 28 |
+
All other contributions:
|
| 29 |
+
Copyright(c) 2015, 2016 the respective contributors
|
| 30 |
+
All rights reserved.
|
| 31 |
+
|
| 32 |
+
Redistribution and use in source and binary forms, with or without
|
| 33 |
+
modification, are permitted provided that the following conditions are met:
|
| 34 |
+
|
| 35 |
+
1. Redistributions of source code must retain the above copyright notice, this
|
| 36 |
+
list of conditions and the following disclaimer.
|
| 37 |
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
| 38 |
+
this list of conditions and the following disclaimer in the documentation
|
| 39 |
+
and/or other materials provided with the distribution.
|
| 40 |
+
|
| 41 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 42 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 43 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 44 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
| 45 |
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 46 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| 47 |
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
| 48 |
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 49 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 50 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
=======================================================================
|
| 54 |
+
Caffe's BSD License
|
| 55 |
+
=======================================================================
|
| 56 |
+
Some parts of the caffe2 code is derived from the original Caffe code, which is
|
| 57 |
+
created by Yangqing Jia and is now a BSD-licensed open-source project. The Caffe
|
| 58 |
+
license is as follows:
|
| 59 |
+
|
| 60 |
+
COPYRIGHT
|
| 61 |
+
|
| 62 |
+
All contributions by the University of California:
|
| 63 |
+
Copyright (c) 2014, The Regents of the University of California (Regents)
|
| 64 |
+
All rights reserved.
|
| 65 |
+
|
| 66 |
+
All other contributions:
|
| 67 |
+
Copyright (c) 2014, the respective contributors
|
| 68 |
+
All rights reserved.
|
| 69 |
+
|
| 70 |
+
Caffe uses a shared copyright model: each contributor holds copyright over
|
| 71 |
+
their contributions to Caffe. The project versioning records all such
|
| 72 |
+
contribution and copyright details. If a contributor wants to further mark
|
| 73 |
+
their specific copyright on a particular contribution, they should indicate
|
| 74 |
+
their copyright solely in the commit message of the change when it is
|
| 75 |
+
committed.
|
| 76 |
+
|
| 77 |
+
LICENSE
|
| 78 |
+
|
| 79 |
+
Redistribution and use in source and binary forms, with or without
|
| 80 |
+
modification, are permitted provided that the following conditions are met:
|
| 81 |
+
|
| 82 |
+
1. Redistributions of source code must retain the above copyright notice, this
|
| 83 |
+
list of conditions and the following disclaimer.
|
| 84 |
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
| 85 |
+
this list of conditions and the following disclaimer in the documentation
|
| 86 |
+
and/or other materials provided with the distribution.
|
| 87 |
+
|
| 88 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 89 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 90 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 91 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
| 92 |
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 93 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| 94 |
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
| 95 |
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 96 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 97 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 98 |
+
|
| 99 |
+
CONTRIBUTION AGREEMENT
|
| 100 |
+
|
| 101 |
+
By contributing to the BVLC/caffe repository through pull-request, comment,
|
| 102 |
+
or otherwise, the contributor releases their content to the
|
| 103 |
+
license and copyright terms herein.
|
| 104 |
+
|
| 105 |
+
=======================================================================
|
| 106 |
+
Caffe2's Apache License
|
| 107 |
+
=======================================================================
|
| 108 |
+
|
| 109 |
+
This repo contains Caffe2 code, which was previously licensed under
|
| 110 |
+
Apache License Version 2.0:
|
| 111 |
+
|
| 112 |
+
Apache License
|
| 113 |
+
Version 2.0, January 2004
|
| 114 |
+
http://www.apache.org/licenses/
|
| 115 |
+
|
| 116 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 117 |
+
|
| 118 |
+
1. Definitions.
|
| 119 |
+
|
| 120 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 121 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 122 |
+
|
| 123 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 124 |
+
the copyright owner that is granting the License.
|
| 125 |
+
|
| 126 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 127 |
+
other entities that control, are controlled by, or are under common
|
| 128 |
+
control with that entity. For the purposes of this definition,
|
| 129 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 130 |
+
direction or management of such entity, whether by contract or
|
| 131 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 132 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 133 |
+
|
| 134 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 135 |
+
exercising permissions granted by this License.
|
| 136 |
+
|
| 137 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 138 |
+
including but not limited to software source code, documentation
|
| 139 |
+
source, and configuration files.
|
| 140 |
+
|
| 141 |
+
"Object" form shall mean any form resulting from mechanical
|
| 142 |
+
transformation or translation of a Source form, including but
|
| 143 |
+
not limited to compiled object code, generated documentation,
|
| 144 |
+
and conversions to other media types.
|
| 145 |
+
|
| 146 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 147 |
+
Object form, made available under the License, as indicated by a
|
| 148 |
+
copyright notice that is included in or attached to the work
|
| 149 |
+
(an example is provided in the Appendix below).
|
| 150 |
+
|
| 151 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 152 |
+
form, that is based on (or derived from) the Work and for which the
|
| 153 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 154 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 155 |
+
of this License, Derivative Works shall not include works that remain
|
| 156 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 157 |
+
the Work and Derivative Works thereof.
|
| 158 |
+
|
| 159 |
+
"Contribution" shall mean any work of authorship, including
|
| 160 |
+
the original version of the Work and any modifications or additions
|
| 161 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 162 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 163 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 164 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 165 |
+
means any form of electronic, verbal, or written communication sent
|
| 166 |
+
to the Licensor or its representatives, including but not limited to
|
| 167 |
+
communication on electronic mailing lists, source code control systems,
|
| 168 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 169 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 170 |
+
excluding communication that is conspicuously marked or otherwise
|
| 171 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 172 |
+
|
| 173 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 174 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 175 |
+
subsequently incorporated within the Work.
|
| 176 |
+
|
| 177 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 178 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 179 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 180 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 181 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 182 |
+
Work and such Derivative Works in Source or Object form.
|
| 183 |
+
|
| 184 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 185 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 186 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 187 |
+
(except as stated in this section) patent license to make, have made,
|
| 188 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 189 |
+
where such license applies only to those patent claims licensable
|
| 190 |
+
by such Contributor that are necessarily infringed by their
|
| 191 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 192 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 193 |
+
institute patent litigation against any entity (including a
|
| 194 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 195 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 196 |
+
or contributory patent infringement, then any patent licenses
|
| 197 |
+
granted to You under this License for that Work shall terminate
|
| 198 |
+
as of the date such litigation is filed.
|
| 199 |
+
|
| 200 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 201 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 202 |
+
modifications, and in Source or Object form, provided that You
|
| 203 |
+
meet the following conditions:
|
| 204 |
+
|
| 205 |
+
(a) You must give any other recipients of the Work or
|
| 206 |
+
Derivative Works a copy of this License; and
|
| 207 |
+
|
| 208 |
+
(b) You must cause any modified files to carry prominent notices
|
| 209 |
+
stating that You changed the files; and
|
| 210 |
+
|
| 211 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 212 |
+
that You distribute, all copyright, patent, trademark, and
|
| 213 |
+
attribution notices from the Source form of the Work,
|
| 214 |
+
excluding those notices that do not pertain to any part of
|
| 215 |
+
the Derivative Works; and
|
| 216 |
+
|
| 217 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 218 |
+
distribution, then any Derivative Works that You distribute must
|
| 219 |
+
include a readable copy of the attribution notices contained
|
| 220 |
+
within such NOTICE file, excluding those notices that do not
|
| 221 |
+
pertain to any part of the Derivative Works, in at least one
|
| 222 |
+
of the following places: within a NOTICE text file distributed
|
| 223 |
+
as part of the Derivative Works; within the Source form or
|
| 224 |
+
documentation, if provided along with the Derivative Works; or,
|
| 225 |
+
within a display generated by the Derivative Works, if and
|
| 226 |
+
wherever such third-party notices normally appear. The contents
|
| 227 |
+
of the NOTICE file are for informational purposes only and
|
| 228 |
+
do not modify the License. You may add Your own attribution
|
| 229 |
+
notices within Derivative Works that You distribute, alongside
|
| 230 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 231 |
+
that such additional attribution notices cannot be construed
|
| 232 |
+
as modifying the License.
|
| 233 |
+
|
| 234 |
+
You may add Your own copyright statement to Your modifications and
|
| 235 |
+
may provide additional or different license terms and conditions
|
| 236 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 237 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 238 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 239 |
+
the conditions stated in this License.
|
| 240 |
+
|
| 241 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 242 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 243 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 244 |
+
this License, without any additional terms or conditions.
|
| 245 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 246 |
+
the terms of any separate license agreement you may have executed
|
| 247 |
+
with Licensor regarding such Contributions.
|
| 248 |
+
|
| 249 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 250 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 251 |
+
except as required for reasonable and customary use in describing the
|
| 252 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 253 |
+
|
| 254 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 255 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 256 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 257 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 258 |
+
implied, including, without limitation, any warranties or conditions
|
| 259 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 260 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 261 |
+
appropriateness of using or redistributing the Work and assume any
|
| 262 |
+
risks associated with Your exercise of permissions under this License.
|
| 263 |
+
|
| 264 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 265 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 266 |
+
unless required by applicable law (such as deliberate and grossly
|
| 267 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 268 |
+
liable to You for damages, including any direct, indirect, special,
|
| 269 |
+
incidental, or consequential damages of any character arising as a
|
| 270 |
+
result of this License or out of the use or inability to use the
|
| 271 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 272 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 273 |
+
other commercial damages or losses), even if such Contributor
|
| 274 |
+
has been advised of the possibility of such damages.
|
| 275 |
+
|
| 276 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 277 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 278 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 279 |
+
or other liability obligations and/or rights consistent with this
|
| 280 |
+
License. However, in accepting such obligations, You may act only
|
| 281 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 282 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 283 |
+
defend, and hold each Contributor harmless for any liability
|
| 284 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 285 |
+
of your accepting any such warranty or additional liability.
|
| 286 |
+
|
| 287 |
+
=======================================================================
|
| 288 |
+
Cephes's 3-Clause BSD License
|
| 289 |
+
=======================================================================
|
| 290 |
+
|
| 291 |
+
Code derived from implementations in the Cephes Math Library should mention
|
| 292 |
+
its derivation and reference the following license:
|
| 293 |
+
|
| 294 |
+
3-Clause BSD License for the Cephes Math Library
|
| 295 |
+
Copyright (c) 2018, Steven Moshier
|
| 296 |
+
All rights reserved.
|
| 297 |
+
|
| 298 |
+
Redistribution and use in source and binary forms, with or without
|
| 299 |
+
modification, are permitted provided that the following conditions are met:
|
| 300 |
+
|
| 301 |
+
* Redistributions of source code must retain the above copyright
|
| 302 |
+
notice, this list of conditions and the following disclaimer.
|
| 303 |
+
|
| 304 |
+
* Redistributions in binary form must reproduce the above copyright
|
| 305 |
+
notice, this list of conditions and the following disclaimer in the
|
| 306 |
+
documentation and/or other materials provided with the distribution.
|
| 307 |
+
|
| 308 |
+
* Neither the name of the nor the
|
| 309 |
+
names of its contributors may be used to endorse or promote products
|
| 310 |
+
derived from this software without specific prior written permission.
|
| 311 |
+
|
| 312 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 313 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 314 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 315 |
+
DISCLAIMED. IN NO EVENT SHALL Steven Moshier BE LIABLE FOR ANY
|
| 316 |
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| 317 |
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| 318 |
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
| 319 |
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 320 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
| 321 |
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
=======================================================================
|
| 325 |
+
SciPy's 3-Clause BSD License
|
| 326 |
+
=======================================================================
|
| 327 |
+
|
| 328 |
+
Code derived from implementations in SciPy should mention its derivation
|
| 329 |
+
and reference the following license:
|
| 330 |
+
|
| 331 |
+
Copyright (c) 2001-2002 Enthought, Inc. 2003-2019, SciPy Developers.
|
| 332 |
+
All rights reserved.
|
| 333 |
+
|
| 334 |
+
Redistribution and use in source and binary forms, with or without
|
| 335 |
+
modification, are permitted provided that the following conditions
|
| 336 |
+
are met:
|
| 337 |
+
|
| 338 |
+
1. Redistributions of source code must retain the above copyright
|
| 339 |
+
notice, this list of conditions and the following disclaimer.
|
| 340 |
+
|
| 341 |
+
2. Redistributions in binary form must reproduce the above
|
| 342 |
+
copyright notice, this list of conditions and the following
|
| 343 |
+
disclaimer in the documentation and/or other materials provided
|
| 344 |
+
with the distribution.
|
| 345 |
+
|
| 346 |
+
3. Neither the name of the copyright holder nor the names of its
|
| 347 |
+
contributors may be used to endorse or promote products derived
|
| 348 |
+
from this software without specific prior written permission.
|
| 349 |
+
|
| 350 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| 351 |
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| 352 |
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| 353 |
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| 354 |
+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| 355 |
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| 356 |
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| 357 |
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| 358 |
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| 359 |
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 360 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| 361 |
+
|
| 362 |
+
=======================================================================
|
| 363 |
+
Boost's 1.0 Software License
|
| 364 |
+
=======================================================================
|
| 365 |
+
|
| 366 |
+
Code derived from implementations in Boost 1.0 should mention its
|
| 367 |
+
derivation and reference the following license:
|
| 368 |
+
|
| 369 |
+
Boost Software License - Version 1.0 - August 17th, 2003
|
| 370 |
+
|
| 371 |
+
Permission is hereby granted, free of charge, to any person or organization
|
| 372 |
+
obtaining a copy of the software and accompanying documentation covered by
|
| 373 |
+
this license (the "Software") to use, reproduce, display, distribute,
|
| 374 |
+
execute, and transmit the Software, and to prepare derivative works of the
|
| 375 |
+
Software, and to permit third-parties to whom the Software is furnished to
|
| 376 |
+
do so, all subject to the following:
|
| 377 |
+
|
| 378 |
+
The copyright notices in the Software and this entire statement, including
|
| 379 |
+
the above license grant, this restriction and the following disclaimer,
|
| 380 |
+
must be included in all copies of the Software, in whole or in part, and
|
| 381 |
+
all derivative works of the Software, unless such copies or derivative
|
| 382 |
+
works are solely in the form of machine-executable object code generated by
|
| 383 |
+
a source language processor.
|
| 384 |
+
|
| 385 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 386 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 387 |
+
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
| 388 |
+
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
| 389 |
+
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
| 390 |
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
| 391 |
+
DEALINGS IN THE SOFTWARE.
|
| 392 |
+
|
| 393 |
+
END OF TERMS AND CONDITIONS
|
| 394 |
+
|
| 395 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 396 |
+
|
| 397 |
+
To apply the Apache License to your work, attach the following
|
| 398 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 399 |
+
replaced with your own identifying information. (Don't include
|
| 400 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 401 |
+
comment syntax for the file format. We also recommend that a
|
| 402 |
+
file or class name and description of purpose be included on the
|
| 403 |
+
same "printed page" as the copyright notice for easier
|
| 404 |
+
identification within third-party archives.
|
| 405 |
+
|
| 406 |
+
Copyright [yyyy] [name of copyright owner]
|
| 407 |
+
|
| 408 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 409 |
+
you may not use this file except in compliance with the License.
|
| 410 |
+
You may obtain a copy of the License at
|
| 411 |
+
|
| 412 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 413 |
+
|
| 414 |
+
Unless required by applicable law or agreed to in writing, software
|
| 415 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 416 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 417 |
+
See the License for the specific language governing permissions and
|
| 418 |
+
limitations under the License.
|
| 419 |
+
|
| 420 |
+
=======================================================================
|
| 421 |
+
PILLOW-SIMD Software License
|
| 422 |
+
=======================================================================
|
| 423 |
+
|
| 424 |
+
Code derived from implementations in PILLOW-SIMD should mention its derivation
|
| 425 |
+
and reference the following license:
|
| 426 |
+
|
| 427 |
+
The Python Imaging Library (PIL) is
|
| 428 |
+
|
| 429 |
+
Copyright © 1997-2011 by Secret Labs AB
|
| 430 |
+
Copyright © 1995-2011 by Fredrik Lundh
|
| 431 |
+
|
| 432 |
+
Pillow is the friendly PIL fork. It is
|
| 433 |
+
|
| 434 |
+
Copyright © 2010-2022 by Alex Clark and contributors
|
| 435 |
+
|
| 436 |
+
Like PIL, Pillow is licensed under the open source HPND License:
|
| 437 |
+
|
| 438 |
+
By obtaining, using, and/or copying this software and/or its associated
|
| 439 |
+
documentation, you agree that you have read, understood, and will comply
|
| 440 |
+
with the following terms and conditions:
|
| 441 |
+
|
| 442 |
+
Permission to use, copy, modify, and distribute this software and its
|
| 443 |
+
associated documentation for any purpose and without fee is hereby granted,
|
| 444 |
+
provided that the above copyright notice appears in all copies, and that
|
| 445 |
+
both that copyright notice and this permission notice appear in supporting
|
| 446 |
+
documentation, and that the name of Secret Labs AB or the author not be
|
| 447 |
+
used in advertising or publicity pertaining to distribution of the software
|
| 448 |
+
without specific, written prior permission.
|
| 449 |
+
|
| 450 |
+
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
|
| 451 |
+
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
|
| 452 |
+
IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL,
|
| 453 |
+
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
| 454 |
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
| 455 |
+
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
| 456 |
+
PERFORMANCE OF THIS SOFTWARE.
|
outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchgen
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/iter/utils.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import warnings
|
| 3 |
+
from collections.abc import Iterable, Iterator, Sized
|
| 4 |
+
from typing import TypeVar
|
| 5 |
+
|
| 6 |
+
from torch.utils.data.datapipes.datapipe import IterDataPipe
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
_T = TypeVar("_T")
|
| 10 |
+
|
| 11 |
+
__all__ = ["IterableWrapperIterDataPipe"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class IterableWrapperIterDataPipe(IterDataPipe[_T]):
|
| 15 |
+
r"""
|
| 16 |
+
Wraps an iterable object to create an IterDataPipe.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
iterable: Iterable object to be wrapped into an IterDataPipe
|
| 20 |
+
deepcopy: Option to deepcopy input iterable object for each
|
| 21 |
+
iterator. The copy is made when the first element is read in ``iter()``.
|
| 22 |
+
|
| 23 |
+
.. note::
|
| 24 |
+
If ``deepcopy`` is explicitly set to ``False``, users should ensure
|
| 25 |
+
that the data pipeline doesn't contain any in-place operations over
|
| 26 |
+
the iterable instance to prevent data inconsistency across iterations.
|
| 27 |
+
|
| 28 |
+
Example:
|
| 29 |
+
>>> # xdoctest: +SKIP
|
| 30 |
+
>>> from torchdata.datapipes.iter import IterableWrapper
|
| 31 |
+
>>> dp = IterableWrapper(range(10))
|
| 32 |
+
>>> list(dp)
|
| 33 |
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(self, iterable: Iterable[_T], deepcopy: bool = True) -> None:
|
| 37 |
+
self.iterable = iterable
|
| 38 |
+
self.deepcopy = deepcopy
|
| 39 |
+
|
| 40 |
+
def __iter__(self) -> Iterator[_T]:
|
| 41 |
+
source_data = self.iterable
|
| 42 |
+
if self.deepcopy:
|
| 43 |
+
try:
|
| 44 |
+
source_data = copy.deepcopy(self.iterable)
|
| 45 |
+
# For the case that data cannot be deep-copied,
|
| 46 |
+
# all in-place operations will affect iterable variable.
|
| 47 |
+
# When this DataPipe is iterated second time, it will
|
| 48 |
+
# yield modified items.
|
| 49 |
+
except TypeError:
|
| 50 |
+
warnings.warn(
|
| 51 |
+
"The input iterable can not be deepcopied, "
|
| 52 |
+
"please be aware of in-place modification would affect source data.",
|
| 53 |
+
stacklevel=2,
|
| 54 |
+
)
|
| 55 |
+
yield from source_data
|
| 56 |
+
|
| 57 |
+
def __len__(self) -> int:
|
| 58 |
+
if isinstance(self.iterable, Sized):
|
| 59 |
+
return len(self.iterable)
|
| 60 |
+
raise TypeError(f"{type(self).__name__} instance doesn't have valid length")
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Functional DataPipe
|
| 2 |
+
from torch.utils.data.datapipes.map.callable import MapperMapDataPipe as Mapper
|
| 3 |
+
from torch.utils.data.datapipes.map.combinatorics import (
|
| 4 |
+
ShufflerIterDataPipe as Shuffler,
|
| 5 |
+
)
|
| 6 |
+
from torch.utils.data.datapipes.map.combining import (
|
| 7 |
+
ConcaterMapDataPipe as Concater,
|
| 8 |
+
ZipperMapDataPipe as Zipper,
|
| 9 |
+
)
|
| 10 |
+
from torch.utils.data.datapipes.map.grouping import BatcherMapDataPipe as Batcher
|
| 11 |
+
from torch.utils.data.datapipes.map.utils import (
|
| 12 |
+
SequenceWrapperMapDataPipe as SequenceWrapper,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
__all__ = ["Batcher", "Concater", "Mapper", "SequenceWrapper", "Shuffler", "Zipper"]
|
| 17 |
+
|
| 18 |
+
# Please keep this list sorted
|
| 19 |
+
if __all__ != sorted(__all__):
|
| 20 |
+
raise AssertionError("__all__ is not sorted")
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/callable.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from collections.abc import Callable
|
| 3 |
+
from typing import TypeVar
|
| 4 |
+
|
| 5 |
+
from torch.utils.data.datapipes._decorator import functional_datapipe
|
| 6 |
+
from torch.utils.data.datapipes.datapipe import MapDataPipe
|
| 7 |
+
from torch.utils.data.datapipes.utils.common import _check_unpickable_fn
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
__all__ = ["MapperMapDataPipe", "default_fn"]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
_T_co = TypeVar("_T_co", covariant=True)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# Default function to return each item directly
|
| 17 |
+
# In order to keep datapipe picklable, eliminates the usage
|
| 18 |
+
# of python lambda function
|
| 19 |
+
def default_fn(data):
|
| 20 |
+
return data
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
@functional_datapipe("map")
|
| 24 |
+
class MapperMapDataPipe(MapDataPipe[_T_co]):
|
| 25 |
+
r"""
|
| 26 |
+
Apply the input function over each item from the source DataPipe (functional name: ``map``).
|
| 27 |
+
|
| 28 |
+
The function can be any regular Python function or partial object. Lambda
|
| 29 |
+
function is not recommended as it is not supported by pickle.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
datapipe: Source MapDataPipe
|
| 33 |
+
fn: Function being applied to each item
|
| 34 |
+
|
| 35 |
+
Example:
|
| 36 |
+
>>> # xdoctest: +SKIP
|
| 37 |
+
>>> from torchdata.datapipes.map import SequenceWrapper, Mapper
|
| 38 |
+
>>> def add_one(x):
|
| 39 |
+
... return x + 1
|
| 40 |
+
>>> dp = SequenceWrapper(range(10))
|
| 41 |
+
>>> map_dp_1 = dp.map(add_one)
|
| 42 |
+
>>> list(map_dp_1)
|
| 43 |
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
| 44 |
+
>>> map_dp_2 = Mapper(dp, lambda x: x + 1)
|
| 45 |
+
>>> list(map_dp_2)
|
| 46 |
+
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
datapipe: MapDataPipe
|
| 50 |
+
fn: Callable
|
| 51 |
+
|
| 52 |
+
def __init__(
|
| 53 |
+
self,
|
| 54 |
+
datapipe: MapDataPipe,
|
| 55 |
+
fn: Callable = default_fn,
|
| 56 |
+
) -> None:
|
| 57 |
+
super().__init__()
|
| 58 |
+
self.datapipe = datapipe
|
| 59 |
+
_check_unpickable_fn(fn)
|
| 60 |
+
self.fn = fn # type: ignore[assignment]
|
| 61 |
+
|
| 62 |
+
def __len__(self) -> int:
|
| 63 |
+
# pyrefly: ignore [bad-argument-type]
|
| 64 |
+
return len(self.datapipe)
|
| 65 |
+
|
| 66 |
+
def __getitem__(self, index) -> _T_co:
|
| 67 |
+
return self.fn(self.datapipe[index])
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/combinatorics.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import random
|
| 3 |
+
from collections.abc import Iterator
|
| 4 |
+
from typing import TypeVar
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch.utils.data.datapipes.datapipe import IterDataPipe, MapDataPipe
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
__all__ = ["ShufflerIterDataPipe"]
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
_T_co = TypeVar("_T_co", covariant=True)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# @functional_datapipe('shuffle')
|
| 17 |
+
class ShufflerIterDataPipe(IterDataPipe[_T_co]):
|
| 18 |
+
r"""
|
| 19 |
+
Shuffle the input MapDataPipe via its indices (functional name: ``shuffle``).
|
| 20 |
+
|
| 21 |
+
When it is used with :class:`~torch.utils.data.DataLoader`, the methods to
|
| 22 |
+
set up random seed are different based on :attr:`num_workers`.
|
| 23 |
+
|
| 24 |
+
For single-process mode (:attr:`num_workers == 0`), the random seed is set before
|
| 25 |
+
the :class:`~torch.utils.data.DataLoader` in the main process. For multi-process
|
| 26 |
+
mode (:attr:`num_worker > 0`), ``worker_init_fn`` is used to set up a random seed
|
| 27 |
+
for each worker process.
|
| 28 |
+
|
| 29 |
+
Args:
|
| 30 |
+
datapipe: MapDataPipe being shuffled
|
| 31 |
+
indices: a list of indices of the MapDataPipe. If not provided, we assume it uses 0-based indexing
|
| 32 |
+
|
| 33 |
+
Example:
|
| 34 |
+
>>> # xdoctest: +SKIP
|
| 35 |
+
>>> from torchdata.datapipes.map import SequenceWrapper
|
| 36 |
+
>>> dp = SequenceWrapper(range(10))
|
| 37 |
+
>>> shuffle_dp = dp.shuffle().set_seed(0)
|
| 38 |
+
>>> list(shuffle_dp)
|
| 39 |
+
[7, 8, 1, 5, 3, 4, 2, 0, 9, 6]
|
| 40 |
+
>>> list(shuffle_dp)
|
| 41 |
+
[6, 1, 9, 5, 2, 4, 7, 3, 8, 0]
|
| 42 |
+
>>> # Reset seed for Shuffler
|
| 43 |
+
>>> shuffle_dp = shuffle_dp.set_seed(0)
|
| 44 |
+
>>> list(shuffle_dp)
|
| 45 |
+
[7, 8, 1, 5, 3, 4, 2, 0, 9, 6]
|
| 46 |
+
|
| 47 |
+
Note:
|
| 48 |
+
Even thought this ``shuffle`` operation takes a ``MapDataPipe`` as the input, it would return an
|
| 49 |
+
``IterDataPipe`` rather than a ``MapDataPipe``, because ``MapDataPipe`` should be non-sensitive to
|
| 50 |
+
the order of data order for the sake of random reads, but ``IterDataPipe`` depends on the order
|
| 51 |
+
of data during data-processing.
|
| 52 |
+
"""
|
| 53 |
+
|
| 54 |
+
datapipe: MapDataPipe[_T_co]
|
| 55 |
+
_enabled: bool
|
| 56 |
+
_seed: int | None
|
| 57 |
+
_rng: random.Random
|
| 58 |
+
|
| 59 |
+
def __init__(
|
| 60 |
+
self,
|
| 61 |
+
datapipe: MapDataPipe[_T_co],
|
| 62 |
+
*,
|
| 63 |
+
indices: list | None = None,
|
| 64 |
+
) -> None:
|
| 65 |
+
super().__init__()
|
| 66 |
+
self.datapipe = datapipe
|
| 67 |
+
# pyrefly: ignore [bad-argument-type]
|
| 68 |
+
self.indices = list(range(len(datapipe))) if indices is None else indices
|
| 69 |
+
self._enabled = True
|
| 70 |
+
self._seed = None
|
| 71 |
+
self._rng = random.Random()
|
| 72 |
+
self._shuffled_indices: list = self.indices
|
| 73 |
+
|
| 74 |
+
def set_shuffle(self, shuffle=True):
|
| 75 |
+
self._enabled = shuffle
|
| 76 |
+
return self
|
| 77 |
+
|
| 78 |
+
def set_seed(self, seed: int):
|
| 79 |
+
self._seed = seed
|
| 80 |
+
return self
|
| 81 |
+
|
| 82 |
+
def __iter__(self) -> Iterator[_T_co]:
|
| 83 |
+
if not self._enabled:
|
| 84 |
+
for idx in self.indices:
|
| 85 |
+
yield self.datapipe[idx]
|
| 86 |
+
else:
|
| 87 |
+
while self._shuffled_indices:
|
| 88 |
+
idx = self._shuffled_indices.pop()
|
| 89 |
+
yield self.datapipe[idx]
|
| 90 |
+
|
| 91 |
+
def reset(self) -> None:
|
| 92 |
+
if self._enabled and self._seed is None:
|
| 93 |
+
self._seed = int(torch.empty((), dtype=torch.int64).random_().item())
|
| 94 |
+
self._rng.seed(self._seed)
|
| 95 |
+
self._seed = None
|
| 96 |
+
self._shuffled_indices = self._rng.sample(self.indices, len(self.indices))
|
| 97 |
+
|
| 98 |
+
def __len__(self) -> int:
|
| 99 |
+
# pyrefly: ignore [bad-argument-type]
|
| 100 |
+
return len(self.datapipe)
|
| 101 |
+
|
| 102 |
+
def __getstate__(self):
|
| 103 |
+
state = (
|
| 104 |
+
self.datapipe,
|
| 105 |
+
self.indices,
|
| 106 |
+
self._enabled,
|
| 107 |
+
self._seed,
|
| 108 |
+
self._rng.getstate(),
|
| 109 |
+
self._shuffled_indices,
|
| 110 |
+
self._valid_iterator_id,
|
| 111 |
+
self._number_of_samples_yielded,
|
| 112 |
+
)
|
| 113 |
+
if IterDataPipe.getstate_hook is not None:
|
| 114 |
+
return IterDataPipe.getstate_hook(state)
|
| 115 |
+
return state
|
| 116 |
+
|
| 117 |
+
def __setstate__(self, state):
|
| 118 |
+
(
|
| 119 |
+
self.datapipe,
|
| 120 |
+
self.indices,
|
| 121 |
+
self._enabled,
|
| 122 |
+
self._seed,
|
| 123 |
+
rng_state,
|
| 124 |
+
self._shuffled_indices,
|
| 125 |
+
self._valid_iterator_id,
|
| 126 |
+
self._number_of_samples_yielded,
|
| 127 |
+
) = state
|
| 128 |
+
self._rng = random.Random()
|
| 129 |
+
self._rng.setstate(rng_state)
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
MapDataPipe.register_datapipe_as_function("shuffle", ShufflerIterDataPipe)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/combining.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from collections.abc import Sized
|
| 3 |
+
from typing import TypeVar
|
| 4 |
+
|
| 5 |
+
from torch.utils.data.datapipes._decorator import functional_datapipe
|
| 6 |
+
from torch.utils.data.datapipes.datapipe import MapDataPipe
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
__all__ = ["ConcaterMapDataPipe", "ZipperMapDataPipe"]
|
| 10 |
+
|
| 11 |
+
_T_co = TypeVar("_T_co", covariant=True)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@functional_datapipe("concat")
|
| 15 |
+
class ConcaterMapDataPipe(MapDataPipe):
|
| 16 |
+
r"""
|
| 17 |
+
Concatenate multiple Map DataPipes (functional name: ``concat``).
|
| 18 |
+
|
| 19 |
+
The new index of is the cumulative sum of source DataPipes.
|
| 20 |
+
For example, if there are 2 source DataPipes both with length 5,
|
| 21 |
+
index 0 to 4 of the resulting `ConcatMapDataPipe` would refer to
|
| 22 |
+
elements of the first DataPipe, and 5 to 9 would refer to elements
|
| 23 |
+
of the second DataPipe.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
datapipes: Map DataPipes being concatenated
|
| 27 |
+
|
| 28 |
+
Example:
|
| 29 |
+
>>> # xdoctest: +SKIP
|
| 30 |
+
>>> from torchdata.datapipes.map import SequenceWrapper
|
| 31 |
+
>>> dp1 = SequenceWrapper(range(3))
|
| 32 |
+
>>> dp2 = SequenceWrapper(range(3))
|
| 33 |
+
>>> concat_dp = dp1.concat(dp2)
|
| 34 |
+
>>> list(concat_dp)
|
| 35 |
+
[0, 1, 2, 0, 1, 2]
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
datapipes: tuple[MapDataPipe]
|
| 39 |
+
|
| 40 |
+
def __init__(self, *datapipes: MapDataPipe) -> None:
|
| 41 |
+
if len(datapipes) == 0:
|
| 42 |
+
raise ValueError("Expected at least one DataPipe, but got nothing")
|
| 43 |
+
if not all(isinstance(dp, MapDataPipe) for dp in datapipes):
|
| 44 |
+
raise TypeError("Expected all inputs to be `MapDataPipe`")
|
| 45 |
+
# pyrefly: ignore [unsafe-overlap]
|
| 46 |
+
if not all(isinstance(dp, Sized) for dp in datapipes):
|
| 47 |
+
raise TypeError("Expected all inputs to be `Sized`")
|
| 48 |
+
self.datapipes = datapipes # type: ignore[assignment]
|
| 49 |
+
|
| 50 |
+
def __getitem__(self, index) -> _T_co: # type: ignore[type-var]
|
| 51 |
+
offset = 0
|
| 52 |
+
for dp in self.datapipes:
|
| 53 |
+
# pyrefly: ignore [bad-argument-type]
|
| 54 |
+
if index - offset < len(dp):
|
| 55 |
+
return dp[index - offset]
|
| 56 |
+
else:
|
| 57 |
+
# pyrefly: ignore [bad-argument-type]
|
| 58 |
+
offset += len(dp)
|
| 59 |
+
raise IndexError(f"Index {index} is out of range.")
|
| 60 |
+
|
| 61 |
+
def __len__(self) -> int:
|
| 62 |
+
# pyrefly: ignore [bad-argument-type]
|
| 63 |
+
return sum(len(dp) for dp in self.datapipes)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@functional_datapipe("zip")
|
| 67 |
+
class ZipperMapDataPipe(MapDataPipe[tuple[_T_co, ...]]):
|
| 68 |
+
r"""
|
| 69 |
+
Aggregates elements into a tuple from each of the input DataPipes (functional name: ``zip``).
|
| 70 |
+
|
| 71 |
+
This MataPipe is out of bound as soon as the shortest input DataPipe is exhausted.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
*datapipes: Map DataPipes being aggregated
|
| 75 |
+
|
| 76 |
+
Example:
|
| 77 |
+
>>> # xdoctest: +SKIP
|
| 78 |
+
>>> from torchdata.datapipes.map import SequenceWrapper
|
| 79 |
+
>>> dp1 = SequenceWrapper(range(3))
|
| 80 |
+
>>> dp2 = SequenceWrapper(range(10, 13))
|
| 81 |
+
>>> zip_dp = dp1.zip(dp2)
|
| 82 |
+
>>> list(zip_dp)
|
| 83 |
+
[(0, 10), (1, 11), (2, 12)]
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
datapipes: tuple[MapDataPipe[_T_co], ...]
|
| 87 |
+
|
| 88 |
+
def __init__(self, *datapipes: MapDataPipe[_T_co]) -> None:
|
| 89 |
+
if len(datapipes) == 0:
|
| 90 |
+
raise ValueError("Expected at least one DataPipe, but got nothing")
|
| 91 |
+
if not all(isinstance(dp, MapDataPipe) for dp in datapipes):
|
| 92 |
+
raise TypeError("Expected all inputs to be `MapDataPipe`")
|
| 93 |
+
# pyrefly: ignore [unsafe-overlap]
|
| 94 |
+
if not all(isinstance(dp, Sized) for dp in datapipes):
|
| 95 |
+
raise TypeError("Expected all inputs to be `Sized`")
|
| 96 |
+
self.datapipes = datapipes
|
| 97 |
+
|
| 98 |
+
def __getitem__(self, index) -> tuple[_T_co, ...]:
|
| 99 |
+
res = []
|
| 100 |
+
for dp in self.datapipes:
|
| 101 |
+
try:
|
| 102 |
+
res.append(dp[index])
|
| 103 |
+
except IndexError as e:
|
| 104 |
+
raise IndexError(
|
| 105 |
+
f"Index {index} is out of range for one of the input MapDataPipes {dp}."
|
| 106 |
+
) from e
|
| 107 |
+
return tuple(res)
|
| 108 |
+
|
| 109 |
+
def __len__(self) -> int:
|
| 110 |
+
# pyrefly: ignore [bad-argument-type]
|
| 111 |
+
return min(len(dp) for dp in self.datapipes)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/grouping.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from collections.abc import Sized
|
| 3 |
+
from typing import TypeVar
|
| 4 |
+
|
| 5 |
+
from torch.utils.data.datapipes._decorator import functional_datapipe
|
| 6 |
+
from torch.utils.data.datapipes.datapipe import DataChunk, MapDataPipe
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
__all__ = ["BatcherMapDataPipe"]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
_T = TypeVar("_T")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@functional_datapipe("batch")
|
| 16 |
+
class BatcherMapDataPipe(MapDataPipe[DataChunk]):
|
| 17 |
+
r"""
|
| 18 |
+
Create mini-batches of data (functional name: ``batch``).
|
| 19 |
+
|
| 20 |
+
An outer dimension will be added as ``batch_size`` if ``drop_last`` is set to ``True``,
|
| 21 |
+
or ``length % batch_size`` for the last batch if ``drop_last`` is set to ``False``.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
datapipe: Iterable DataPipe being batched
|
| 25 |
+
batch_size: The size of each batch
|
| 26 |
+
drop_last: Option to drop the last batch if it's not full
|
| 27 |
+
|
| 28 |
+
Example:
|
| 29 |
+
>>> # xdoctest: +SKIP
|
| 30 |
+
>>> from torchdata.datapipes.map import SequenceWrapper
|
| 31 |
+
>>> dp = SequenceWrapper(range(10))
|
| 32 |
+
>>> batch_dp = dp.batch(batch_size=2)
|
| 33 |
+
>>> list(batch_dp)
|
| 34 |
+
[[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
datapipe: MapDataPipe
|
| 38 |
+
batch_size: int
|
| 39 |
+
drop_last: bool
|
| 40 |
+
|
| 41 |
+
def __init__(
|
| 42 |
+
self,
|
| 43 |
+
datapipe: MapDataPipe[_T],
|
| 44 |
+
batch_size: int,
|
| 45 |
+
drop_last: bool = False,
|
| 46 |
+
wrapper_class: type[DataChunk] = DataChunk,
|
| 47 |
+
) -> None:
|
| 48 |
+
if batch_size <= 0:
|
| 49 |
+
raise AssertionError("Batch size is required to be larger than 0!")
|
| 50 |
+
super().__init__()
|
| 51 |
+
self.datapipe = datapipe
|
| 52 |
+
self.batch_size = batch_size
|
| 53 |
+
self.drop_last = drop_last
|
| 54 |
+
self.wrapper_class = wrapper_class
|
| 55 |
+
|
| 56 |
+
def __getitem__(self, index) -> DataChunk:
|
| 57 |
+
batch: list = []
|
| 58 |
+
indices = range(index * self.batch_size, (index + 1) * self.batch_size)
|
| 59 |
+
try:
|
| 60 |
+
batch.extend(self.datapipe[i] for i in indices)
|
| 61 |
+
return self.wrapper_class(batch)
|
| 62 |
+
except IndexError as e:
|
| 63 |
+
if not self.drop_last and len(batch) > 0:
|
| 64 |
+
return self.wrapper_class(batch)
|
| 65 |
+
else:
|
| 66 |
+
raise IndexError(f"Index {index} is out of bound.") from e
|
| 67 |
+
|
| 68 |
+
def __len__(self) -> int:
|
| 69 |
+
# pyrefly: ignore [unsafe-overlap]
|
| 70 |
+
if isinstance(self.datapipe, Sized):
|
| 71 |
+
if self.drop_last:
|
| 72 |
+
return len(self.datapipe) // self.batch_size
|
| 73 |
+
else:
|
| 74 |
+
return (len(self.datapipe) + self.batch_size - 1) // self.batch_size
|
| 75 |
+
else:
|
| 76 |
+
raise TypeError(f"{type(self).__name__} instance doesn't have valid length")
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/map/utils.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import warnings
|
| 3 |
+
from collections.abc import Mapping, Sequence
|
| 4 |
+
from typing import Any, TypeVar
|
| 5 |
+
|
| 6 |
+
from torch.utils.data.datapipes.datapipe import MapDataPipe
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
_T = TypeVar("_T")
|
| 10 |
+
|
| 11 |
+
__all__ = ["SequenceWrapperMapDataPipe"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class SequenceWrapperMapDataPipe(MapDataPipe[_T]):
|
| 15 |
+
r"""
|
| 16 |
+
Wraps a sequence object into a MapDataPipe.
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
sequence: Sequence object to be wrapped into an MapDataPipe
|
| 20 |
+
deepcopy: Option to deepcopy input sequence object
|
| 21 |
+
|
| 22 |
+
.. note::
|
| 23 |
+
If ``deepcopy`` is set to False explicitly, users should ensure
|
| 24 |
+
that data pipeline doesn't contain any in-place operations over
|
| 25 |
+
the iterable instance, in order to prevent data inconsistency
|
| 26 |
+
across iterations.
|
| 27 |
+
|
| 28 |
+
Example:
|
| 29 |
+
>>> # xdoctest: +SKIP
|
| 30 |
+
>>> from torchdata.datapipes.map import SequenceWrapper
|
| 31 |
+
>>> dp = SequenceWrapper(range(10))
|
| 32 |
+
>>> list(dp)
|
| 33 |
+
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
| 34 |
+
>>> dp = SequenceWrapper({"a": 100, "b": 200, "c": 300, "d": 400})
|
| 35 |
+
>>> dp["a"]
|
| 36 |
+
100
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
sequence: Sequence[_T] | Mapping[Any, _T]
|
| 40 |
+
|
| 41 |
+
def __init__(
|
| 42 |
+
self, sequence: Sequence[_T] | Mapping[Any, _T], deepcopy: bool = True
|
| 43 |
+
) -> None:
|
| 44 |
+
if deepcopy:
|
| 45 |
+
try:
|
| 46 |
+
self.sequence = copy.deepcopy(sequence)
|
| 47 |
+
except TypeError:
|
| 48 |
+
warnings.warn(
|
| 49 |
+
"The input sequence can not be deepcopied, "
|
| 50 |
+
"please be aware of in-place modification would affect source data",
|
| 51 |
+
stacklevel=2,
|
| 52 |
+
)
|
| 53 |
+
self.sequence = sequence
|
| 54 |
+
else:
|
| 55 |
+
self.sequence = sequence
|
| 56 |
+
|
| 57 |
+
def __getitem__(self, index: int) -> _T:
|
| 58 |
+
return self.sequence[index]
|
| 59 |
+
|
| 60 |
+
def __len__(self) -> int:
|
| 61 |
+
return len(self.sequence)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/__init__.py
ADDED
|
File without changes
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/common.py
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import fnmatch
|
| 3 |
+
import functools
|
| 4 |
+
import inspect
|
| 5 |
+
import os
|
| 6 |
+
import warnings
|
| 7 |
+
from collections.abc import Callable, Iterable
|
| 8 |
+
from io import IOBase
|
| 9 |
+
from typing import Any, NoReturn
|
| 10 |
+
|
| 11 |
+
from torch.utils._import_utils import dill_available
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
__all__ = [
|
| 15 |
+
"validate_input_col",
|
| 16 |
+
"StreamWrapper",
|
| 17 |
+
"get_file_binaries_from_pathnames",
|
| 18 |
+
"get_file_pathnames_from_root",
|
| 19 |
+
"match_masks",
|
| 20 |
+
"validate_pathname_binary_tuple",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# BC for torchdata
|
| 25 |
+
DILL_AVAILABLE = dill_available()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def validate_input_col(fn: Callable, input_col: int | tuple | list | None) -> None:
|
| 29 |
+
"""
|
| 30 |
+
Check that function used in a callable datapipe works with the input column.
|
| 31 |
+
|
| 32 |
+
This simply ensures that the number of positional arguments matches the size
|
| 33 |
+
of the input column. The function must not contain any non-default
|
| 34 |
+
keyword-only arguments.
|
| 35 |
+
|
| 36 |
+
Examples:
|
| 37 |
+
>>> # xdoctest: +SKIP("Failing on some CI machines")
|
| 38 |
+
>>> def f(a, b, *, c=1):
|
| 39 |
+
>>> return a + b + c
|
| 40 |
+
>>> def f_def(a, b=1, *, c=1):
|
| 41 |
+
>>> return a + b + c
|
| 42 |
+
>>> assert validate_input_col(f, [1, 2])
|
| 43 |
+
>>> assert validate_input_col(f_def, 1)
|
| 44 |
+
>>> assert validate_input_col(f_def, [1, 2])
|
| 45 |
+
|
| 46 |
+
Notes:
|
| 47 |
+
If the function contains variable positional (`inspect.VAR_POSITIONAL`) arguments,
|
| 48 |
+
for example, f(a, *args), the validator will accept any size of input column
|
| 49 |
+
greater than or equal to the number of positional arguments.
|
| 50 |
+
(in this case, 1).
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
fn: The function to check.
|
| 54 |
+
input_col: The input column to check.
|
| 55 |
+
|
| 56 |
+
Raises:
|
| 57 |
+
ValueError: If the function is not compatible with the input column.
|
| 58 |
+
"""
|
| 59 |
+
try:
|
| 60 |
+
sig = inspect.signature(fn)
|
| 61 |
+
except (
|
| 62 |
+
ValueError
|
| 63 |
+
): # Signature cannot be inspected, likely it is a built-in fn or written in C
|
| 64 |
+
return
|
| 65 |
+
if isinstance(input_col, (list, tuple)):
|
| 66 |
+
input_col_size = len(input_col)
|
| 67 |
+
else:
|
| 68 |
+
input_col_size = 1
|
| 69 |
+
|
| 70 |
+
pos = []
|
| 71 |
+
var_positional = False
|
| 72 |
+
non_default_kw_only = []
|
| 73 |
+
|
| 74 |
+
for p in sig.parameters.values():
|
| 75 |
+
if p.kind in (
|
| 76 |
+
inspect.Parameter.POSITIONAL_ONLY,
|
| 77 |
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
| 78 |
+
):
|
| 79 |
+
pos.append(p)
|
| 80 |
+
elif p.kind is inspect.Parameter.VAR_POSITIONAL:
|
| 81 |
+
var_positional = True
|
| 82 |
+
elif p.kind is inspect.Parameter.KEYWORD_ONLY:
|
| 83 |
+
if p.default is p.empty:
|
| 84 |
+
non_default_kw_only.append(p)
|
| 85 |
+
else:
|
| 86 |
+
continue
|
| 87 |
+
|
| 88 |
+
if isinstance(fn, functools.partial):
|
| 89 |
+
fn_name = getattr(fn.func, "__name__", repr(fn.func))
|
| 90 |
+
else:
|
| 91 |
+
fn_name = getattr(fn, "__name__", repr(fn))
|
| 92 |
+
|
| 93 |
+
if len(non_default_kw_only) > 0:
|
| 94 |
+
raise ValueError(
|
| 95 |
+
f"The function {fn_name} takes {len(non_default_kw_only)} "
|
| 96 |
+
f"non-default keyword-only parameters, which is not allowed."
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
if len(sig.parameters) < input_col_size:
|
| 100 |
+
if not var_positional:
|
| 101 |
+
raise ValueError(
|
| 102 |
+
f"The function {fn_name} takes {len(sig.parameters)} "
|
| 103 |
+
f"parameters, but {input_col_size} are required."
|
| 104 |
+
)
|
| 105 |
+
else:
|
| 106 |
+
if len(pos) > input_col_size:
|
| 107 |
+
if any(p.default is p.empty for p in pos[input_col_size:]):
|
| 108 |
+
raise ValueError(
|
| 109 |
+
f"The function {fn_name} takes {len(pos)} "
|
| 110 |
+
f"positional parameters, but {input_col_size} are required."
|
| 111 |
+
)
|
| 112 |
+
elif len(pos) < input_col_size:
|
| 113 |
+
if not var_positional:
|
| 114 |
+
raise ValueError(
|
| 115 |
+
f"The function {fn_name} takes {len(pos)} "
|
| 116 |
+
f"positional parameters, but {input_col_size} are required."
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _is_local_fn(fn):
|
| 121 |
+
# Functions or Methods
|
| 122 |
+
if hasattr(fn, "__code__"):
|
| 123 |
+
return fn.__code__.co_flags & inspect.CO_NESTED
|
| 124 |
+
# Callable Objects
|
| 125 |
+
else:
|
| 126 |
+
if hasattr(fn, "__qualname__"):
|
| 127 |
+
return "<locals>" in fn.__qualname__
|
| 128 |
+
fn_type = type(fn)
|
| 129 |
+
if hasattr(fn_type, "__qualname__"):
|
| 130 |
+
return "<locals>" in fn_type.__qualname__
|
| 131 |
+
return False
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def _check_unpickable_fn(fn: Callable) -> None:
|
| 135 |
+
"""
|
| 136 |
+
Check function is pickable or not.
|
| 137 |
+
|
| 138 |
+
If it is a lambda or local function, a UserWarning will be raised. If it's not a callable function, a TypeError will be raised.
|
| 139 |
+
"""
|
| 140 |
+
if not callable(fn):
|
| 141 |
+
raise TypeError(f"A callable function is expected, but {type(fn)} is provided.")
|
| 142 |
+
|
| 143 |
+
# Extract function from partial object
|
| 144 |
+
# Nested partial function is automatically expanded as a single partial object
|
| 145 |
+
if isinstance(fn, functools.partial):
|
| 146 |
+
fn = fn.func
|
| 147 |
+
|
| 148 |
+
# Local function
|
| 149 |
+
if _is_local_fn(fn) and not dill_available():
|
| 150 |
+
warnings.warn(
|
| 151 |
+
"Local function is not supported by pickle, please use "
|
| 152 |
+
"regular python function or functools.partial instead.",
|
| 153 |
+
stacklevel=2,
|
| 154 |
+
)
|
| 155 |
+
return
|
| 156 |
+
|
| 157 |
+
# Lambda function
|
| 158 |
+
if hasattr(fn, "__name__") and fn.__name__ == "<lambda>" and not dill_available():
|
| 159 |
+
warnings.warn(
|
| 160 |
+
"Lambda function is not supported by pickle, please use "
|
| 161 |
+
"regular python function or functools.partial instead.",
|
| 162 |
+
stacklevel=2,
|
| 163 |
+
)
|
| 164 |
+
return
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def match_masks(name: str, masks: str | list[str]) -> bool:
|
| 168 |
+
# empty mask matches any input name
|
| 169 |
+
if not masks:
|
| 170 |
+
return True
|
| 171 |
+
|
| 172 |
+
if isinstance(masks, str):
|
| 173 |
+
return fnmatch.fnmatch(name, masks)
|
| 174 |
+
|
| 175 |
+
for mask in masks:
|
| 176 |
+
if fnmatch.fnmatch(name, mask):
|
| 177 |
+
return True
|
| 178 |
+
return False
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def get_file_pathnames_from_root(
|
| 182 |
+
root: str,
|
| 183 |
+
masks: str | list[str],
|
| 184 |
+
recursive: bool = False,
|
| 185 |
+
abspath: bool = False,
|
| 186 |
+
non_deterministic: bool = False,
|
| 187 |
+
) -> Iterable[str]:
|
| 188 |
+
# print out an error message and raise the error out
|
| 189 |
+
def onerror(err: OSError) -> NoReturn:
|
| 190 |
+
warnings.warn(err.filename + " : " + err.strerror, stacklevel=2)
|
| 191 |
+
raise err
|
| 192 |
+
|
| 193 |
+
if os.path.isfile(root):
|
| 194 |
+
path = root
|
| 195 |
+
if abspath:
|
| 196 |
+
path = os.path.abspath(path)
|
| 197 |
+
fname = os.path.basename(path)
|
| 198 |
+
if match_masks(fname, masks):
|
| 199 |
+
yield path
|
| 200 |
+
else:
|
| 201 |
+
for path, dirs, files in os.walk(root, onerror=onerror):
|
| 202 |
+
if abspath:
|
| 203 |
+
path = os.path.abspath(path)
|
| 204 |
+
if not non_deterministic:
|
| 205 |
+
files.sort()
|
| 206 |
+
for f in files:
|
| 207 |
+
if match_masks(f, masks):
|
| 208 |
+
yield os.path.join(path, f)
|
| 209 |
+
if not recursive:
|
| 210 |
+
break
|
| 211 |
+
if not non_deterministic:
|
| 212 |
+
# Note that this is in-place modifying the internal list from `os.walk`
|
| 213 |
+
# This only works because `os.walk` doesn't shallow copy before turn
|
| 214 |
+
# https://github.com/python/cpython/blob/f4c03484da59049eb62a9bf7777b963e2267d187/Lib/os.py#L407
|
| 215 |
+
dirs.sort()
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def get_file_binaries_from_pathnames(
|
| 219 |
+
pathnames: Iterable, mode: str, encoding: str | None = None
|
| 220 |
+
):
|
| 221 |
+
if not isinstance(pathnames, Iterable):
|
| 222 |
+
pathnames = [
|
| 223 |
+
pathnames,
|
| 224 |
+
]
|
| 225 |
+
|
| 226 |
+
if mode in ("b", "t"):
|
| 227 |
+
mode = "r" + mode
|
| 228 |
+
|
| 229 |
+
for pathname in pathnames:
|
| 230 |
+
if not isinstance(pathname, str):
|
| 231 |
+
raise TypeError(
|
| 232 |
+
f"Expected string type for pathname, but got {type(pathname)}"
|
| 233 |
+
)
|
| 234 |
+
yield pathname, StreamWrapper(open(pathname, mode, encoding=encoding)) # noqa:SIM115
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def validate_pathname_binary_tuple(data: tuple[str, IOBase]) -> None:
|
| 238 |
+
if not isinstance(data, tuple):
|
| 239 |
+
raise TypeError(
|
| 240 |
+
f"pathname binary data should be tuple type, but it is type {type(data)}"
|
| 241 |
+
)
|
| 242 |
+
if len(data) != 2:
|
| 243 |
+
raise TypeError(
|
| 244 |
+
f"pathname binary stream tuple length should be 2, but got {len(data)}"
|
| 245 |
+
)
|
| 246 |
+
if not isinstance(data[0], str):
|
| 247 |
+
raise TypeError(
|
| 248 |
+
f"pathname within the tuple should have string type pathname, but it is type {type(data[0])}"
|
| 249 |
+
)
|
| 250 |
+
if not isinstance(data[1], IOBase) and not isinstance(data[1], StreamWrapper):
|
| 251 |
+
raise TypeError(
|
| 252 |
+
f"binary stream within the tuple should have IOBase or"
|
| 253 |
+
f"its subclasses as type, but it is type {type(data[1])}"
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
# Deprecated function names and its corresponding DataPipe type and kwargs for the `_deprecation_warning` function
|
| 258 |
+
_iter_deprecated_functional_names: dict[str, dict] = {}
|
| 259 |
+
_map_deprecated_functional_names: dict[str, dict] = {}
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def _deprecation_warning(
|
| 263 |
+
old_class_name: str,
|
| 264 |
+
*,
|
| 265 |
+
deprecation_version: str,
|
| 266 |
+
removal_version: str,
|
| 267 |
+
old_functional_name: str = "",
|
| 268 |
+
old_argument_name: str = "",
|
| 269 |
+
new_class_name: str = "",
|
| 270 |
+
new_functional_name: str = "",
|
| 271 |
+
new_argument_name: str = "",
|
| 272 |
+
deprecate_functional_name_only: bool = False,
|
| 273 |
+
) -> None:
|
| 274 |
+
if new_functional_name and not old_functional_name:
|
| 275 |
+
raise ValueError(
|
| 276 |
+
"Old functional API needs to be specified for the deprecation warning."
|
| 277 |
+
)
|
| 278 |
+
if new_argument_name and not old_argument_name:
|
| 279 |
+
raise ValueError(
|
| 280 |
+
"Old argument name needs to be specified for the deprecation warning."
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
if old_functional_name and old_argument_name:
|
| 284 |
+
raise ValueError(
|
| 285 |
+
"Deprecating warning for functional API and argument should be separated."
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
msg = f"`{old_class_name}()`"
|
| 289 |
+
if deprecate_functional_name_only and old_functional_name:
|
| 290 |
+
msg = f"{msg}'s functional API `.{old_functional_name}()` is"
|
| 291 |
+
elif old_functional_name:
|
| 292 |
+
msg = f"{msg} and its functional API `.{old_functional_name}()` are"
|
| 293 |
+
elif old_argument_name:
|
| 294 |
+
msg = f"The argument `{old_argument_name}` of {msg} is"
|
| 295 |
+
else:
|
| 296 |
+
msg = f"{msg} is"
|
| 297 |
+
msg = (
|
| 298 |
+
f"{msg} deprecated since {deprecation_version} and will be removed in {removal_version}."
|
| 299 |
+
f"\nSee https://github.com/pytorch/data/issues/163 for details."
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
if new_class_name or new_functional_name:
|
| 303 |
+
msg = f"{msg}\nPlease use"
|
| 304 |
+
if new_class_name:
|
| 305 |
+
msg = f"{msg} `{new_class_name}()`"
|
| 306 |
+
if new_class_name and new_functional_name:
|
| 307 |
+
msg = f"{msg} or"
|
| 308 |
+
if new_functional_name:
|
| 309 |
+
msg = f"{msg} `.{new_functional_name}()`"
|
| 310 |
+
msg = f"{msg} instead."
|
| 311 |
+
|
| 312 |
+
if new_argument_name:
|
| 313 |
+
msg = f"{msg}\nPlease use `{old_class_name}({new_argument_name}=)` instead."
|
| 314 |
+
|
| 315 |
+
warnings.warn(msg, FutureWarning, stacklevel=2)
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
class StreamWrapper:
|
| 319 |
+
"""
|
| 320 |
+
StreamWrapper is introduced to wrap file handler generated by DataPipe operation like `FileOpener`.
|
| 321 |
+
|
| 322 |
+
StreamWrapper would guarantee the wrapped file handler is closed when it's out of scope.
|
| 323 |
+
"""
|
| 324 |
+
|
| 325 |
+
session_streams: dict[Any, int] = {}
|
| 326 |
+
debug_unclosed_streams: bool = False
|
| 327 |
+
|
| 328 |
+
def __init__(self, file_obj, parent_stream=None, name=None) -> None:
|
| 329 |
+
self.file_obj = file_obj
|
| 330 |
+
self.child_counter = 0
|
| 331 |
+
self.parent_stream = parent_stream
|
| 332 |
+
self.close_on_last_child = False
|
| 333 |
+
self.name = name
|
| 334 |
+
self.closed = False
|
| 335 |
+
if parent_stream is not None:
|
| 336 |
+
if not isinstance(parent_stream, StreamWrapper):
|
| 337 |
+
raise RuntimeError(
|
| 338 |
+
f"Parent stream should be StreamWrapper, {type(parent_stream)} was given"
|
| 339 |
+
)
|
| 340 |
+
parent_stream.child_counter += 1
|
| 341 |
+
self.parent_stream = parent_stream
|
| 342 |
+
if StreamWrapper.debug_unclosed_streams:
|
| 343 |
+
StreamWrapper.session_streams[self] = 1
|
| 344 |
+
|
| 345 |
+
@classmethod
|
| 346 |
+
def close_streams(cls, v, depth=0) -> None:
|
| 347 |
+
"""Traverse structure and attempts to close all found StreamWrappers on best effort basis."""
|
| 348 |
+
if depth > 10:
|
| 349 |
+
return
|
| 350 |
+
if isinstance(v, StreamWrapper):
|
| 351 |
+
v.close()
|
| 352 |
+
else:
|
| 353 |
+
# Traverse only simple structures
|
| 354 |
+
if isinstance(v, dict):
|
| 355 |
+
for vv in v.values():
|
| 356 |
+
cls.close_streams(vv, depth=depth + 1)
|
| 357 |
+
elif isinstance(v, (list, tuple)):
|
| 358 |
+
for vv in v:
|
| 359 |
+
cls.close_streams(vv, depth=depth + 1)
|
| 360 |
+
|
| 361 |
+
def __getattr__(self, name):
|
| 362 |
+
file_obj = self.__dict__["file_obj"]
|
| 363 |
+
return getattr(file_obj, name)
|
| 364 |
+
|
| 365 |
+
def close(self, *args, **kwargs) -> None:
|
| 366 |
+
if self.closed:
|
| 367 |
+
return
|
| 368 |
+
if StreamWrapper.debug_unclosed_streams:
|
| 369 |
+
del StreamWrapper.session_streams[self]
|
| 370 |
+
if hasattr(self, "parent_stream") and self.parent_stream is not None:
|
| 371 |
+
self.parent_stream.child_counter -= 1
|
| 372 |
+
if (
|
| 373 |
+
not self.parent_stream.child_counter
|
| 374 |
+
and self.parent_stream.close_on_last_child
|
| 375 |
+
):
|
| 376 |
+
self.parent_stream.close()
|
| 377 |
+
try:
|
| 378 |
+
self.file_obj.close(*args, **kwargs)
|
| 379 |
+
except AttributeError:
|
| 380 |
+
pass
|
| 381 |
+
self.closed = True
|
| 382 |
+
|
| 383 |
+
def autoclose(self) -> None:
|
| 384 |
+
"""Automatically close stream when all child streams are closed or if there are none."""
|
| 385 |
+
self.close_on_last_child = True
|
| 386 |
+
if self.child_counter == 0:
|
| 387 |
+
self.close()
|
| 388 |
+
|
| 389 |
+
def __dir__(self):
|
| 390 |
+
attrs = list(self.__dict__.keys()) + list(StreamWrapper.__dict__.keys())
|
| 391 |
+
attrs += dir(self.file_obj)
|
| 392 |
+
return list(set(attrs))
|
| 393 |
+
|
| 394 |
+
def __del__(self) -> None:
|
| 395 |
+
if not self.closed:
|
| 396 |
+
self.close()
|
| 397 |
+
|
| 398 |
+
def __iter__(self):
|
| 399 |
+
yield from self.file_obj
|
| 400 |
+
|
| 401 |
+
def __next__(self):
|
| 402 |
+
return next(self.file_obj)
|
| 403 |
+
|
| 404 |
+
def __repr__(self) -> str:
|
| 405 |
+
if self.name is None:
|
| 406 |
+
return f"StreamWrapper<{self.file_obj!r}>"
|
| 407 |
+
else:
|
| 408 |
+
return f"StreamWrapper<{self.name},{self.file_obj!r}>"
|
| 409 |
+
|
| 410 |
+
def __getstate__(self):
|
| 411 |
+
return self.file_obj
|
| 412 |
+
|
| 413 |
+
def __setstate__(self, obj):
|
| 414 |
+
self.file_obj = obj
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/decoder.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
# This file takes partial of the implementation from NVIDIA's webdataset at here:
|
| 3 |
+
# https://github.com/tmbdev/webdataset/blob/master/webdataset/autodecode.py
|
| 4 |
+
|
| 5 |
+
import io
|
| 6 |
+
import json
|
| 7 |
+
import os.path
|
| 8 |
+
import pickle
|
| 9 |
+
import tempfile
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from torch.utils.data.datapipes.utils.common import StreamWrapper
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
"Decoder",
|
| 17 |
+
"ImageHandler",
|
| 18 |
+
"MatHandler",
|
| 19 |
+
"audiohandler",
|
| 20 |
+
"basichandlers",
|
| 21 |
+
"extension_extract_fn",
|
| 22 |
+
"handle_extension",
|
| 23 |
+
"imagehandler",
|
| 24 |
+
"mathandler",
|
| 25 |
+
"videohandler",
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
################################################################
|
| 30 |
+
# handle basic datatypes
|
| 31 |
+
################################################################
|
| 32 |
+
def basichandlers(extension: str, data):
|
| 33 |
+
"""Transforms raw data (byte stream) into python objects.
|
| 34 |
+
|
| 35 |
+
Looks at the extension and loads the data into a python object supporting
|
| 36 |
+
the corresponding extension.
|
| 37 |
+
|
| 38 |
+
Args:
|
| 39 |
+
extension (str): The file extension
|
| 40 |
+
data (byte stream): Data to load into a python object.
|
| 41 |
+
|
| 42 |
+
Returns:
|
| 43 |
+
object: The data loaded into a corresponding python object
|
| 44 |
+
supporting the extension.
|
| 45 |
+
|
| 46 |
+
Example:
|
| 47 |
+
>>> import pickle
|
| 48 |
+
>>> data = pickle.dumps("some data")
|
| 49 |
+
>>> new_data = basichandlers("pickle", data)
|
| 50 |
+
>>> new_data
|
| 51 |
+
some data
|
| 52 |
+
|
| 53 |
+
The transformation of data for extensions are:
|
| 54 |
+
- txt, text, transcript: utf-8 decoded data of str format
|
| 55 |
+
- cls, cls2, class, count, index, inx, id: int
|
| 56 |
+
- json, jsn: json loaded data
|
| 57 |
+
- pickle, pyd: pickle loaded data
|
| 58 |
+
- pt: torch loaded data
|
| 59 |
+
"""
|
| 60 |
+
|
| 61 |
+
if extension in "txt text transcript":
|
| 62 |
+
return data.decode("utf-8")
|
| 63 |
+
|
| 64 |
+
if extension in ["cls", "cls2", "class", "count", "index", "inx", "id"]:
|
| 65 |
+
try:
|
| 66 |
+
return int(data)
|
| 67 |
+
except ValueError:
|
| 68 |
+
return None
|
| 69 |
+
|
| 70 |
+
if extension in "json jsn":
|
| 71 |
+
return json.loads(data)
|
| 72 |
+
|
| 73 |
+
if extension in ["pyd", "pickle"]:
|
| 74 |
+
return pickle.loads(data)
|
| 75 |
+
|
| 76 |
+
if extension == "pt":
|
| 77 |
+
stream = io.BytesIO(data)
|
| 78 |
+
return torch.load(stream)
|
| 79 |
+
|
| 80 |
+
# if extension in "ten tb".split():
|
| 81 |
+
# from . import tenbin
|
| 82 |
+
# return tenbin.decode_buffer(data)
|
| 83 |
+
|
| 84 |
+
# if extension in "mp msgpack msg".split():
|
| 85 |
+
# import msgpack
|
| 86 |
+
# return msgpack.unpackb(data)
|
| 87 |
+
|
| 88 |
+
return None
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
################################################################
|
| 92 |
+
# handle images
|
| 93 |
+
################################################################
|
| 94 |
+
imagespecs = {
|
| 95 |
+
"l8": ("numpy", "uint8", "l"),
|
| 96 |
+
"rgb8": ("numpy", "uint8", "rgb"),
|
| 97 |
+
"rgba8": ("numpy", "uint8", "rgba"),
|
| 98 |
+
"l": ("numpy", "float", "l"),
|
| 99 |
+
"rgb": ("numpy", "float", "rgb"),
|
| 100 |
+
"rgba": ("numpy", "float", "rgba"),
|
| 101 |
+
"torchl8": ("torch", "uint8", "l"),
|
| 102 |
+
"torchrgb8": ("torch", "uint8", "rgb"),
|
| 103 |
+
"torchrgba8": ("torch", "uint8", "rgba"),
|
| 104 |
+
"torchl": ("torch", "float", "l"),
|
| 105 |
+
"torchrgb": ("torch", "float", "rgb"),
|
| 106 |
+
"torch": ("torch", "float", "rgb"),
|
| 107 |
+
"torchrgba": ("torch", "float", "rgba"),
|
| 108 |
+
"pill": ("pil", None, "l"),
|
| 109 |
+
"pil": ("pil", None, "rgb"),
|
| 110 |
+
"pilrgb": ("pil", None, "rgb"),
|
| 111 |
+
"pilrgba": ("pil", None, "rgba"),
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def handle_extension(extensions, f):
|
| 116 |
+
"""
|
| 117 |
+
Return a decoder handler function for the list of extensions.
|
| 118 |
+
|
| 119 |
+
Extensions can be a space separated list of extensions.
|
| 120 |
+
Extensions can contain dots, in which case the corresponding number
|
| 121 |
+
of extension components must be present in the key given to f.
|
| 122 |
+
Comparisons are case insensitive.
|
| 123 |
+
Examples:
|
| 124 |
+
handle_extension("jpg jpeg", my_decode_jpg) # invoked for any file.jpg
|
| 125 |
+
handle_extension("seg.jpg", special_case_jpg) # invoked only for file.seg.jpg
|
| 126 |
+
"""
|
| 127 |
+
extensions = extensions.lower().split()
|
| 128 |
+
|
| 129 |
+
def g(key, data):
|
| 130 |
+
extension = key.lower().split(".")
|
| 131 |
+
|
| 132 |
+
for target in extensions:
|
| 133 |
+
target = target.split(".")
|
| 134 |
+
if len(target) > len(extension):
|
| 135 |
+
continue
|
| 136 |
+
|
| 137 |
+
if extension[-len(target) :] == target:
|
| 138 |
+
return f(data)
|
| 139 |
+
return None
|
| 140 |
+
|
| 141 |
+
return g
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class ImageHandler:
|
| 145 |
+
"""
|
| 146 |
+
Decode image data using the given `imagespec`.
|
| 147 |
+
|
| 148 |
+
The `imagespec` specifies whether the image is decoded
|
| 149 |
+
to numpy/torch/pi, decoded to uint8/float, and decoded
|
| 150 |
+
to l/rgb/rgba:
|
| 151 |
+
|
| 152 |
+
- l8: numpy uint8 l
|
| 153 |
+
- rgb8: numpy uint8 rgb
|
| 154 |
+
- rgba8: numpy uint8 rgba
|
| 155 |
+
- l: numpy float l
|
| 156 |
+
- rgb: numpy float rgb
|
| 157 |
+
- rgba: numpy float rgba
|
| 158 |
+
- torchl8: torch uint8 l
|
| 159 |
+
- torchrgb8: torch uint8 rgb
|
| 160 |
+
- torchrgba8: torch uint8 rgba
|
| 161 |
+
- torchl: torch float l
|
| 162 |
+
- torchrgb: torch float rgb
|
| 163 |
+
- torch: torch float rgb
|
| 164 |
+
- torchrgba: torch float rgba
|
| 165 |
+
- pill: pil None l
|
| 166 |
+
- pil: pil None rgb
|
| 167 |
+
- pilrgb: pil None rgb
|
| 168 |
+
- pilrgba: pil None rgba
|
| 169 |
+
"""
|
| 170 |
+
|
| 171 |
+
def __init__(self, imagespec) -> None:
|
| 172 |
+
if imagespec not in list(imagespecs.keys()):
|
| 173 |
+
raise AssertionError(f"unknown image specification: {imagespec}")
|
| 174 |
+
self.imagespec = imagespec.lower()
|
| 175 |
+
|
| 176 |
+
def __call__(self, extension, data):
|
| 177 |
+
if extension.lower() not in ["jpg", "jpeg", "png", "ppm", "pgm", "pbm", "pnm"]:
|
| 178 |
+
return None
|
| 179 |
+
|
| 180 |
+
try:
|
| 181 |
+
import numpy as np
|
| 182 |
+
except ModuleNotFoundError as e:
|
| 183 |
+
raise ModuleNotFoundError(
|
| 184 |
+
"Package `numpy` is required to be installed for default image decoder."
|
| 185 |
+
"Please use `pip install numpy` to install the package"
|
| 186 |
+
) from e
|
| 187 |
+
|
| 188 |
+
try:
|
| 189 |
+
import PIL.Image
|
| 190 |
+
except ModuleNotFoundError as e:
|
| 191 |
+
raise ModuleNotFoundError(
|
| 192 |
+
"Package `PIL` is required to be installed for default image decoder."
|
| 193 |
+
"Please use `pip install Pillow` to install the package"
|
| 194 |
+
) from e
|
| 195 |
+
|
| 196 |
+
imagespec = self.imagespec
|
| 197 |
+
atype, etype, mode = imagespecs[imagespec]
|
| 198 |
+
|
| 199 |
+
with io.BytesIO(data) as stream:
|
| 200 |
+
img = PIL.Image.open(stream)
|
| 201 |
+
img.load()
|
| 202 |
+
img = img.convert(mode.upper())
|
| 203 |
+
if atype == "pil":
|
| 204 |
+
return img
|
| 205 |
+
elif atype == "numpy":
|
| 206 |
+
result = np.asarray(img)
|
| 207 |
+
if result.dtype != np.uint8:
|
| 208 |
+
raise AssertionError(
|
| 209 |
+
f"numpy image array should be type uint8, but got {result.dtype}"
|
| 210 |
+
)
|
| 211 |
+
if etype == "uint8":
|
| 212 |
+
return result
|
| 213 |
+
else:
|
| 214 |
+
return result.astype("f") / 255.0
|
| 215 |
+
elif atype == "torch":
|
| 216 |
+
result = np.asarray(img)
|
| 217 |
+
if result.dtype != np.uint8:
|
| 218 |
+
raise AssertionError(
|
| 219 |
+
f"numpy image array should be type uint8, but got {result.dtype}"
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
if etype == "uint8":
|
| 223 |
+
result = np.array(result.transpose(2, 0, 1))
|
| 224 |
+
return torch.tensor(result)
|
| 225 |
+
else:
|
| 226 |
+
result = np.array(result.transpose(2, 0, 1))
|
| 227 |
+
return torch.tensor(result) / 255.0
|
| 228 |
+
return None
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def imagehandler(imagespec):
|
| 232 |
+
return ImageHandler(imagespec)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
################################################################
|
| 236 |
+
# torch video
|
| 237 |
+
################################################################
|
| 238 |
+
def videohandler(extension, data):
|
| 239 |
+
if extension not in [
|
| 240 |
+
"mp4",
|
| 241 |
+
"ogv",
|
| 242 |
+
"mjpeg",
|
| 243 |
+
"avi",
|
| 244 |
+
"mov",
|
| 245 |
+
"h264",
|
| 246 |
+
"mpg",
|
| 247 |
+
"webm",
|
| 248 |
+
"wmv",
|
| 249 |
+
]:
|
| 250 |
+
return None
|
| 251 |
+
|
| 252 |
+
try:
|
| 253 |
+
import torchvision.io
|
| 254 |
+
except ImportError as e:
|
| 255 |
+
raise ModuleNotFoundError(
|
| 256 |
+
"Package `torchvision` is required to be installed for default video file loader."
|
| 257 |
+
"Please use `pip install torchvision`"
|
| 258 |
+
"to install the package"
|
| 259 |
+
) from e
|
| 260 |
+
|
| 261 |
+
with tempfile.TemporaryDirectory() as dirname:
|
| 262 |
+
fname = os.path.join(dirname, f"file.{extension}")
|
| 263 |
+
with open(fname, "wb") as stream:
|
| 264 |
+
stream.write(data)
|
| 265 |
+
return torchvision.io.read_video(fname)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
################################################################
|
| 269 |
+
# torchaudio
|
| 270 |
+
################################################################
|
| 271 |
+
def audiohandler(extension, data):
|
| 272 |
+
if extension not in ["flac", "mp3", "sox", "wav", "m4a", "ogg", "wma"]:
|
| 273 |
+
return None
|
| 274 |
+
|
| 275 |
+
try:
|
| 276 |
+
import torchaudio # type: ignore[import]
|
| 277 |
+
except ImportError as e:
|
| 278 |
+
raise ModuleNotFoundError(
|
| 279 |
+
"Package `torchaudio` is required to be installed for default audio file loader."
|
| 280 |
+
"Please use `pip install torchaudio`"
|
| 281 |
+
"to install the package"
|
| 282 |
+
) from e
|
| 283 |
+
|
| 284 |
+
with tempfile.TemporaryDirectory() as dirname:
|
| 285 |
+
fname = os.path.join(dirname, f"file.{extension}")
|
| 286 |
+
with open(fname, "wb") as stream:
|
| 287 |
+
stream.write(data)
|
| 288 |
+
return torchaudio.load(fname)
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
################################################################
|
| 292 |
+
# mat
|
| 293 |
+
################################################################
|
| 294 |
+
class MatHandler:
|
| 295 |
+
def __init__(self, **loadmat_kwargs) -> None:
|
| 296 |
+
try:
|
| 297 |
+
import scipy.io as sio
|
| 298 |
+
except ImportError as e:
|
| 299 |
+
raise ModuleNotFoundError(
|
| 300 |
+
"Package `scipy` is required to be installed for mat file."
|
| 301 |
+
"Please use `pip install scipy`"
|
| 302 |
+
"to install the package"
|
| 303 |
+
) from e
|
| 304 |
+
self.sio = sio
|
| 305 |
+
self.loadmat_kwargs = loadmat_kwargs
|
| 306 |
+
|
| 307 |
+
def __call__(self, extension, data):
|
| 308 |
+
if extension != "mat":
|
| 309 |
+
return None
|
| 310 |
+
with io.BytesIO(data) as stream:
|
| 311 |
+
return self.sio.loadmat(stream, **self.loadmat_kwargs)
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
def mathandler(**loadmat_kwargs):
|
| 315 |
+
return MatHandler(**loadmat_kwargs)
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
################################################################
|
| 319 |
+
# a sample decoder
|
| 320 |
+
################################################################
|
| 321 |
+
# Extract extension from pathname
|
| 322 |
+
def extension_extract_fn(pathname):
|
| 323 |
+
ext = os.path.splitext(pathname)[1]
|
| 324 |
+
# Remove dot
|
| 325 |
+
if ext:
|
| 326 |
+
ext = ext[1:]
|
| 327 |
+
return ext
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class Decoder:
|
| 331 |
+
"""
|
| 332 |
+
Decode key/data sets using a list of handlers.
|
| 333 |
+
|
| 334 |
+
For each key/data item, this iterates through the list of
|
| 335 |
+
handlers until some handler returns something other than None.
|
| 336 |
+
"""
|
| 337 |
+
|
| 338 |
+
def __init__(self, *handler, key_fn=extension_extract_fn) -> None:
|
| 339 |
+
self.handlers = list(handler) if handler else []
|
| 340 |
+
self.key_fn = key_fn
|
| 341 |
+
|
| 342 |
+
# Insert new handler from the beginning of handlers list to make sure the new
|
| 343 |
+
# handler having the highest priority
|
| 344 |
+
def add_handler(self, *handler) -> None:
|
| 345 |
+
if not handler:
|
| 346 |
+
return
|
| 347 |
+
self.handlers = list(handler) + self.handlers
|
| 348 |
+
|
| 349 |
+
@staticmethod
|
| 350 |
+
def _is_stream_handle(data):
|
| 351 |
+
obj_to_check = data.file_obj if isinstance(data, StreamWrapper) else data
|
| 352 |
+
return isinstance(obj_to_check, (io.BufferedIOBase, io.RawIOBase))
|
| 353 |
+
|
| 354 |
+
def decode1(self, key, data):
|
| 355 |
+
if not data:
|
| 356 |
+
return data
|
| 357 |
+
|
| 358 |
+
# if data is a stream handle, we need to read all the content before decoding
|
| 359 |
+
if Decoder._is_stream_handle(data):
|
| 360 |
+
ds = data
|
| 361 |
+
# The behavior of .read can differ between streams (e.g. HTTPResponse), hence this is used instead
|
| 362 |
+
data = b"".join(data)
|
| 363 |
+
ds.close()
|
| 364 |
+
|
| 365 |
+
for f in self.handlers:
|
| 366 |
+
result = f(key, data)
|
| 367 |
+
if result is not None:
|
| 368 |
+
return result
|
| 369 |
+
return data
|
| 370 |
+
|
| 371 |
+
def decode(self, data):
|
| 372 |
+
result = {}
|
| 373 |
+
# single data tuple(pathname, data stream)
|
| 374 |
+
if isinstance(data, tuple):
|
| 375 |
+
data = [data]
|
| 376 |
+
|
| 377 |
+
if data is not None:
|
| 378 |
+
for k, v in data:
|
| 379 |
+
# TODO: xinyu, figure out why Nvidia do this?
|
| 380 |
+
if k[0] == "_":
|
| 381 |
+
if isinstance(v, bytes):
|
| 382 |
+
v = v.decode("utf-8")
|
| 383 |
+
result[k] = v
|
| 384 |
+
continue
|
| 385 |
+
result[k] = self.decode1(self.key_fn(k), v)
|
| 386 |
+
return result
|
| 387 |
+
|
| 388 |
+
def __call__(self, data):
|
| 389 |
+
return self.decode(data)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/datapipes/utils/snapshot.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from torch.utils.data.datapipes._hook_iterator import _SnapshotState
|
| 3 |
+
from torch.utils.data.datapipes.datapipe import IterDataPipe
|
| 4 |
+
from torch.utils.data.graph_settings import apply_random_seed
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# TODO: Caveats
|
| 8 |
+
# 1. Caller (either the ReadingService or DataLoader) must pass in the initial RNG
|
| 9 |
+
# 2. `in_batch_shuffle` and `bucketbatch` are not compatible with this because they currently
|
| 10 |
+
# lack the option to `set_seed`.
|
| 11 |
+
def _simple_graph_snapshot_restoration(
|
| 12 |
+
datapipe: IterDataPipe, n_iterations: int, rng=None
|
| 13 |
+
) -> None:
|
| 14 |
+
r"""
|
| 15 |
+
Fast-forward the given DataPipe and its parents by ``n_iterations``, re-doing computations to restore a snapshot.
|
| 16 |
+
|
| 17 |
+
For instance, applying this function to the final DataPipe of a graph will restore the snapshot
|
| 18 |
+
(via fast-forward) every DataPipe within the graph.
|
| 19 |
+
|
| 20 |
+
After you deserialize a DataPipe, you can use its `_number_of_samples_yielded` attribute as the input
|
| 21 |
+
to this function to forward the DataPipe.
|
| 22 |
+
|
| 23 |
+
A DataPipe cannot be restored twice in a row unless there is an iteration started between the restoration
|
| 24 |
+
attempts.
|
| 25 |
+
|
| 26 |
+
Note:
|
| 27 |
+
This is the simplest but least efficient way to fast-forward a DataPipe. Usage of other fast-forwarding
|
| 28 |
+
methods (custom ones if necessary) are recommended.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
datapipe: IterDataPipe to be fast-forwarded
|
| 32 |
+
n_iterations: number of iterations to fast-forward
|
| 33 |
+
rng: ``Optional[torch.Generator]``. If not ``None``, this RNG will be used for shuffling. The generator
|
| 34 |
+
should be in its `initial` state as it was first passed into ``DataLoader`` or ``ReadingService``.
|
| 35 |
+
"""
|
| 36 |
+
if datapipe._snapshot_state == _SnapshotState.Restored:
|
| 37 |
+
raise RuntimeError(
|
| 38 |
+
"Snapshot restoration cannot be applied. You can only restore simple snapshot to the graph "
|
| 39 |
+
"if your graph has not been restored."
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# For this snapshot restoration function, we want the DataPipe to be at its initial state prior to
|
| 43 |
+
# simple fast-forwarding. Therefore, we need to call `reset` twice, because if `SnapshotState` is `Restored`,
|
| 44 |
+
# the first reset will not actually reset.
|
| 45 |
+
datapipe.reset() # This ensures `SnapshotState` is `Iterating` by this point, even if it was `Restored`.
|
| 46 |
+
# pyrefly: ignore [bad-argument-type]
|
| 47 |
+
apply_random_seed(datapipe, rng)
|
| 48 |
+
|
| 49 |
+
remainder = n_iterations
|
| 50 |
+
it = iter(datapipe) # This always reset the DataPipe if it hasn't already.
|
| 51 |
+
while remainder > 0:
|
| 52 |
+
try:
|
| 53 |
+
next(it)
|
| 54 |
+
remainder -= 1
|
| 55 |
+
except StopIteration as e:
|
| 56 |
+
raise RuntimeError(
|
| 57 |
+
f"Fast-forward {datapipe} by {n_iterations} iterations "
|
| 58 |
+
"exceeds the number of samples available."
|
| 59 |
+
) from e
|
| 60 |
+
datapipe._fast_forward_iterator = it
|
| 61 |
+
# While the DataPipe has `_fast_forward_iterator`, `next()` will get result from there instead of elsewhere.
|
| 62 |
+
|
| 63 |
+
# This will prevent the DataPipe from resetting in the `iter()` call
|
| 64 |
+
# If another DataPipe is consuming it, it won't have to start over again
|
| 65 |
+
datapipe._snapshot_state = _SnapshotState.Restored
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/dataset.py
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import bisect
|
| 3 |
+
import itertools
|
| 4 |
+
import math
|
| 5 |
+
import warnings
|
| 6 |
+
from collections.abc import Sequence
|
| 7 |
+
|
| 8 |
+
# UP006 wants 'Iterable' to be imported from collections.abc but it needs to
|
| 9 |
+
# stay from typing for now due to BC concerns. In particular several internal
|
| 10 |
+
# targets fail to typecheck with:
|
| 11 |
+
# TypeError: Cannot create a consistent method resolution order (MRO) for
|
| 12 |
+
# bases Iterable, Generic
|
| 13 |
+
from typing import cast, Generic, Iterable, TypeVar # noqa: UP035
|
| 14 |
+
from typing_extensions import deprecated
|
| 15 |
+
|
| 16 |
+
# No 'default_generator' in torch/__init__.pyi
|
| 17 |
+
from torch import default_generator, Generator, randperm, Tensor
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
"Dataset",
|
| 22 |
+
"IterableDataset",
|
| 23 |
+
"TensorDataset",
|
| 24 |
+
"StackDataset",
|
| 25 |
+
"ConcatDataset",
|
| 26 |
+
"ChainDataset",
|
| 27 |
+
"Subset",
|
| 28 |
+
"random_split",
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
_T = TypeVar("_T")
|
| 33 |
+
_T_co = TypeVar("_T_co", covariant=True)
|
| 34 |
+
_T_dict = dict[str, _T_co]
|
| 35 |
+
_T_tuple = tuple[_T_co, ...]
|
| 36 |
+
_T_stack = TypeVar("_T_stack", _T_tuple, _T_dict)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class Dataset(Generic[_T_co]):
|
| 40 |
+
r"""An abstract class representing a :class:`Dataset`.
|
| 41 |
+
|
| 42 |
+
All datasets that represent a map from keys to data samples should subclass
|
| 43 |
+
it. All subclasses should overwrite :meth:`__getitem__`, supporting fetching a
|
| 44 |
+
data sample for a given key. Subclasses could also optionally overwrite
|
| 45 |
+
:meth:`__len__`, which is expected to return the size of the dataset by many
|
| 46 |
+
:class:`~torch.utils.data.Sampler` implementations and the default options
|
| 47 |
+
of :class:`~torch.utils.data.DataLoader`. Subclasses could also
|
| 48 |
+
optionally implement :meth:`__getitems__`, for speedup batched samples
|
| 49 |
+
loading. This method accepts list of indices of samples of batch and returns
|
| 50 |
+
list of samples.
|
| 51 |
+
|
| 52 |
+
.. note::
|
| 53 |
+
:class:`~torch.utils.data.DataLoader` by default constructs an index
|
| 54 |
+
sampler that yields integral indices. To make it work with a map-style
|
| 55 |
+
dataset with non-integral indices/keys, a custom sampler must be provided.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __getitem__(self, index) -> _T_co:
|
| 59 |
+
raise NotImplementedError("Subclasses of Dataset should implement __getitem__.")
|
| 60 |
+
|
| 61 |
+
# def __getitems__(self, indices: List) -> List[_T_co]:
|
| 62 |
+
# Not implemented to prevent false-positives in fetcher check in
|
| 63 |
+
# torch.utils.data._utils.fetch._MapDatasetFetcher
|
| 64 |
+
|
| 65 |
+
def __add__(self, other: "Dataset[_T_co]") -> "ConcatDataset[_T_co]":
|
| 66 |
+
return ConcatDataset([self, other])
|
| 67 |
+
|
| 68 |
+
# No `def __len__(self)` default?
|
| 69 |
+
# See NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
|
| 70 |
+
# in pytorch/torch/utils/data/sampler.py
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class IterableDataset(Dataset[_T_co], Iterable[_T_co]):
|
| 74 |
+
r"""An iterable Dataset.
|
| 75 |
+
|
| 76 |
+
All datasets that represent an iterable of data samples should subclass it.
|
| 77 |
+
Such form of datasets is particularly useful when data come from a stream.
|
| 78 |
+
|
| 79 |
+
All subclasses should overwrite :meth:`__iter__`, which would return an
|
| 80 |
+
iterator of samples in this dataset.
|
| 81 |
+
|
| 82 |
+
When a subclass is used with :class:`~torch.utils.data.DataLoader`, each
|
| 83 |
+
item in the dataset will be yielded from the :class:`~torch.utils.data.DataLoader`
|
| 84 |
+
iterator. When :attr:`num_workers > 0`, each worker process will have a
|
| 85 |
+
different copy of the dataset object, so it is often desired to configure
|
| 86 |
+
each copy independently to avoid having duplicate data returned from the
|
| 87 |
+
workers. :func:`~torch.utils.data.get_worker_info`, when called in a worker
|
| 88 |
+
process, returns information about the worker. It can be used in either the
|
| 89 |
+
dataset's :meth:`__iter__` method or the :class:`~torch.utils.data.DataLoader` 's
|
| 90 |
+
:attr:`worker_init_fn` option to modify each copy's behavior.
|
| 91 |
+
|
| 92 |
+
Example 1: splitting workload across all workers in :meth:`__iter__`::
|
| 93 |
+
|
| 94 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_DATALOADER)
|
| 95 |
+
>>> # xdoctest: +SKIP("Fails on MacOS12")
|
| 96 |
+
>>> class MyIterableDataset(torch.utils.data.IterableDataset):
|
| 97 |
+
... def __init__(self, start, end):
|
| 98 |
+
... super(MyIterableDataset).__init__()
|
| 99 |
+
... assert end > start, "this example only works with end >= start"
|
| 100 |
+
... self.start = start
|
| 101 |
+
... self.end = end
|
| 102 |
+
...
|
| 103 |
+
... def __iter__(self):
|
| 104 |
+
... worker_info = torch.utils.data.get_worker_info()
|
| 105 |
+
... if worker_info is None: # single-process data loading, return the full iterator
|
| 106 |
+
... iter_start = self.start
|
| 107 |
+
... iter_end = self.end
|
| 108 |
+
... else: # in a worker process
|
| 109 |
+
... # split workload
|
| 110 |
+
... per_worker = int(math.ceil((self.end - self.start) / float(worker_info.num_workers)))
|
| 111 |
+
... worker_id = worker_info.id
|
| 112 |
+
... iter_start = self.start + worker_id * per_worker
|
| 113 |
+
... iter_end = min(iter_start + per_worker, self.end)
|
| 114 |
+
... return iter(range(iter_start, iter_end))
|
| 115 |
+
...
|
| 116 |
+
>>> # should give same set of data as range(3, 7), i.e., [3, 4, 5, 6].
|
| 117 |
+
>>> ds = MyIterableDataset(start=3, end=7)
|
| 118 |
+
|
| 119 |
+
>>> # Single-process loading
|
| 120 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=0)))
|
| 121 |
+
[tensor([3]), tensor([4]), tensor([5]), tensor([6])]
|
| 122 |
+
|
| 123 |
+
>>> # xdoctest: +REQUIRES(POSIX)
|
| 124 |
+
>>> # Multi-process loading with two worker processes
|
| 125 |
+
>>> # Worker 0 fetched [3, 4]. Worker 1 fetched [5, 6].
|
| 126 |
+
>>> # xdoctest: +IGNORE_WANT("non deterministic")
|
| 127 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=2)))
|
| 128 |
+
[tensor([3]), tensor([5]), tensor([4]), tensor([6])]
|
| 129 |
+
|
| 130 |
+
>>> # With even more workers
|
| 131 |
+
>>> # xdoctest: +IGNORE_WANT("non deterministic")
|
| 132 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=12)))
|
| 133 |
+
[tensor([3]), tensor([5]), tensor([4]), tensor([6])]
|
| 134 |
+
|
| 135 |
+
Example 2: splitting workload across all workers using :attr:`worker_init_fn`::
|
| 136 |
+
|
| 137 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_DATALOADER)
|
| 138 |
+
>>> class MyIterableDataset(torch.utils.data.IterableDataset):
|
| 139 |
+
... def __init__(self, start, end):
|
| 140 |
+
... super(MyIterableDataset).__init__()
|
| 141 |
+
... assert end > start, "this example only works with end >= start"
|
| 142 |
+
... self.start = start
|
| 143 |
+
... self.end = end
|
| 144 |
+
...
|
| 145 |
+
... def __iter__(self):
|
| 146 |
+
... return iter(range(self.start, self.end))
|
| 147 |
+
...
|
| 148 |
+
>>> # should give same set of data as range(3, 7), i.e., [3, 4, 5, 6].
|
| 149 |
+
>>> ds = MyIterableDataset(start=3, end=7)
|
| 150 |
+
|
| 151 |
+
>>> # Single-process loading
|
| 152 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=0)))
|
| 153 |
+
[3, 4, 5, 6]
|
| 154 |
+
>>>
|
| 155 |
+
>>> # Directly doing multi-process loading yields duplicate data
|
| 156 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=2)))
|
| 157 |
+
[3, 3, 4, 4, 5, 5, 6, 6]
|
| 158 |
+
|
| 159 |
+
>>> # Define a `worker_init_fn` that configures each dataset copy differently
|
| 160 |
+
>>> def worker_init_fn(worker_id):
|
| 161 |
+
... worker_info = torch.utils.data.get_worker_info()
|
| 162 |
+
... dataset = worker_info.dataset # the dataset copy in this worker process
|
| 163 |
+
... overall_start = dataset.start
|
| 164 |
+
... overall_end = dataset.end
|
| 165 |
+
... # configure the dataset to only process the split workload
|
| 166 |
+
... per_worker = int(math.ceil((overall_end - overall_start) / float(worker_info.num_workers)))
|
| 167 |
+
... worker_id = worker_info.id
|
| 168 |
+
... dataset.start = overall_start + worker_id * per_worker
|
| 169 |
+
... dataset.end = min(dataset.start + per_worker, overall_end)
|
| 170 |
+
...
|
| 171 |
+
|
| 172 |
+
>>> # Mult-process loading with the custom `worker_init_fn`
|
| 173 |
+
>>> # Worker 0 fetched [3, 4]. Worker 1 fetched [5, 6].
|
| 174 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=2, worker_init_fn=worker_init_fn)))
|
| 175 |
+
[3, 5, 4, 6]
|
| 176 |
+
|
| 177 |
+
>>> # With even more workers
|
| 178 |
+
>>> print(list(torch.utils.data.DataLoader(ds, num_workers=12, worker_init_fn=worker_init_fn)))
|
| 179 |
+
[3, 4, 5, 6]
|
| 180 |
+
"""
|
| 181 |
+
|
| 182 |
+
def __add__(self, other: Dataset[_T_co]):
|
| 183 |
+
return ChainDataset([self, other])
|
| 184 |
+
|
| 185 |
+
# No `def __len__(self)` default? Subclasses raise `TypeError` when needed.
|
| 186 |
+
# See NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
class TensorDataset(Dataset[tuple[Tensor, ...]]):
|
| 190 |
+
r"""Dataset wrapping tensors.
|
| 191 |
+
|
| 192 |
+
Each sample will be retrieved by indexing tensors along the first dimension.
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
*tensors (Tensor): tensors that have the same size of the first dimension.
|
| 196 |
+
"""
|
| 197 |
+
|
| 198 |
+
tensors: tuple[Tensor, ...]
|
| 199 |
+
|
| 200 |
+
def __init__(self, *tensors: Tensor) -> None:
|
| 201 |
+
if any(tensors[0].size(0) != tensor.size(0) for tensor in tensors):
|
| 202 |
+
raise AssertionError("Size mismatch between tensors")
|
| 203 |
+
self.tensors = tensors
|
| 204 |
+
|
| 205 |
+
def __getitem__(self, index):
|
| 206 |
+
return tuple(tensor[index] for tensor in self.tensors)
|
| 207 |
+
|
| 208 |
+
def __len__(self) -> int:
|
| 209 |
+
return self.tensors[0].size(0)
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
class StackDataset(Dataset[_T_stack]):
|
| 213 |
+
r"""Dataset as a stacking of multiple datasets.
|
| 214 |
+
|
| 215 |
+
This class is useful to assemble different parts of complex input data, given as datasets.
|
| 216 |
+
|
| 217 |
+
Example:
|
| 218 |
+
>>> # xdoctest: +SKIP
|
| 219 |
+
>>> images = ImageDataset()
|
| 220 |
+
>>> texts = TextDataset()
|
| 221 |
+
>>> tuple_stack = StackDataset(images, texts)
|
| 222 |
+
>>> tuple_stack[0] == (images[0], texts[0])
|
| 223 |
+
>>> dict_stack = StackDataset(image=images, text=texts)
|
| 224 |
+
>>> dict_stack[0] == {"image": images[0], "text": texts[0]}
|
| 225 |
+
|
| 226 |
+
Args:
|
| 227 |
+
*args (Dataset): Datasets for stacking returned as tuple.
|
| 228 |
+
**kwargs (Dataset): Datasets for stacking returned as dict.
|
| 229 |
+
"""
|
| 230 |
+
|
| 231 |
+
datasets: tuple | dict
|
| 232 |
+
|
| 233 |
+
def __init__(self, *args: Dataset[_T_co], **kwargs: Dataset[_T_co]) -> None:
|
| 234 |
+
if args:
|
| 235 |
+
if kwargs:
|
| 236 |
+
raise ValueError(
|
| 237 |
+
"Supported either ``tuple``- (via ``args``) or"
|
| 238 |
+
"``dict``- (via ``kwargs``) like input/output, but both types are given."
|
| 239 |
+
)
|
| 240 |
+
self._length = len(args[0]) # type: ignore[arg-type]
|
| 241 |
+
if any(self._length != len(dataset) for dataset in args): # type: ignore[arg-type]
|
| 242 |
+
raise ValueError("Size mismatch between datasets")
|
| 243 |
+
self.datasets = args
|
| 244 |
+
elif kwargs:
|
| 245 |
+
tmp = list(kwargs.values())
|
| 246 |
+
self._length = len(tmp[0]) # type: ignore[arg-type]
|
| 247 |
+
if any(self._length != len(dataset) for dataset in tmp): # type: ignore[arg-type]
|
| 248 |
+
raise ValueError("Size mismatch between datasets")
|
| 249 |
+
self.datasets = kwargs
|
| 250 |
+
else:
|
| 251 |
+
raise ValueError("At least one dataset should be passed")
|
| 252 |
+
|
| 253 |
+
def __getitem__(self, index):
|
| 254 |
+
if isinstance(self.datasets, dict):
|
| 255 |
+
return {k: dataset[index] for k, dataset in self.datasets.items()}
|
| 256 |
+
return tuple(dataset[index] for dataset in self.datasets)
|
| 257 |
+
|
| 258 |
+
def __getitems__(self, indices: list):
|
| 259 |
+
# add batched sampling support when parent datasets supports it.
|
| 260 |
+
if isinstance(self.datasets, dict):
|
| 261 |
+
dict_batch: list[_T_dict] = [{} for _ in indices]
|
| 262 |
+
for k, dataset in self.datasets.items():
|
| 263 |
+
if callable(getattr(dataset, "__getitems__", None)):
|
| 264 |
+
items = dataset.__getitems__(indices) # type: ignore[attr-defined]
|
| 265 |
+
if len(items) != len(indices):
|
| 266 |
+
raise ValueError(
|
| 267 |
+
"Nested dataset's output size mismatch."
|
| 268 |
+
f" Expected {len(indices)}, got {len(items)}"
|
| 269 |
+
)
|
| 270 |
+
for data, d_sample in zip(items, dict_batch, strict=True):
|
| 271 |
+
d_sample[k] = data
|
| 272 |
+
else:
|
| 273 |
+
for idx, d_sample in zip(indices, dict_batch, strict=True):
|
| 274 |
+
d_sample[k] = dataset[idx]
|
| 275 |
+
return dict_batch
|
| 276 |
+
|
| 277 |
+
# tuple data
|
| 278 |
+
list_batch: list[list] = [[] for _ in indices]
|
| 279 |
+
for dataset in self.datasets:
|
| 280 |
+
if callable(getattr(dataset, "__getitems__", None)):
|
| 281 |
+
items = dataset.__getitems__(indices) # type: ignore[attr-defined]
|
| 282 |
+
if len(items) != len(indices):
|
| 283 |
+
raise ValueError(
|
| 284 |
+
"Nested dataset's output size mismatch."
|
| 285 |
+
f" Expected {len(indices)}, got {len(items)}"
|
| 286 |
+
)
|
| 287 |
+
for data, t_sample in zip(items, list_batch, strict=True):
|
| 288 |
+
t_sample.append(data)
|
| 289 |
+
else:
|
| 290 |
+
for idx, t_sample in zip(indices, list_batch, strict=True):
|
| 291 |
+
t_sample.append(dataset[idx])
|
| 292 |
+
tuple_batch: list[_T_tuple] = [tuple(sample) for sample in list_batch]
|
| 293 |
+
return tuple_batch
|
| 294 |
+
|
| 295 |
+
def __len__(self) -> int:
|
| 296 |
+
return self._length
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
class ConcatDataset(Dataset[_T_co]):
|
| 300 |
+
r"""Dataset as a concatenation of multiple datasets.
|
| 301 |
+
|
| 302 |
+
This class is useful to assemble different existing datasets.
|
| 303 |
+
|
| 304 |
+
Args:
|
| 305 |
+
datasets (sequence): List of datasets to be concatenated
|
| 306 |
+
"""
|
| 307 |
+
|
| 308 |
+
datasets: list[Dataset[_T_co]]
|
| 309 |
+
cumulative_sizes: list[int]
|
| 310 |
+
|
| 311 |
+
@staticmethod
|
| 312 |
+
def cumsum(sequence):
|
| 313 |
+
r, s = [], 0
|
| 314 |
+
for e in sequence:
|
| 315 |
+
l = len(e)
|
| 316 |
+
r.append(l + s)
|
| 317 |
+
s += l
|
| 318 |
+
return r
|
| 319 |
+
|
| 320 |
+
def __init__(self, datasets: Iterable[Dataset]) -> None:
|
| 321 |
+
super().__init__()
|
| 322 |
+
self.datasets = list(datasets)
|
| 323 |
+
if len(self.datasets) == 0:
|
| 324 |
+
raise AssertionError("datasets should not be an empty iterable")
|
| 325 |
+
for d in self.datasets:
|
| 326 |
+
if isinstance(d, IterableDataset):
|
| 327 |
+
raise AssertionError("ConcatDataset does not support IterableDataset")
|
| 328 |
+
self.cumulative_sizes = self.cumsum(self.datasets)
|
| 329 |
+
|
| 330 |
+
def __len__(self) -> int:
|
| 331 |
+
return self.cumulative_sizes[-1]
|
| 332 |
+
|
| 333 |
+
def __getitem__(self, idx):
|
| 334 |
+
if idx < 0:
|
| 335 |
+
if -idx > len(self):
|
| 336 |
+
raise ValueError(
|
| 337 |
+
"absolute value of index should not exceed dataset length"
|
| 338 |
+
)
|
| 339 |
+
idx = len(self) + idx
|
| 340 |
+
dataset_idx = bisect.bisect_right(self.cumulative_sizes, idx)
|
| 341 |
+
if dataset_idx == 0:
|
| 342 |
+
sample_idx = idx
|
| 343 |
+
else:
|
| 344 |
+
sample_idx = idx - self.cumulative_sizes[dataset_idx - 1]
|
| 345 |
+
return self.datasets[dataset_idx][sample_idx]
|
| 346 |
+
|
| 347 |
+
@property
|
| 348 |
+
@deprecated(
|
| 349 |
+
"`cummulative_sizes` attribute is renamed to `cumulative_sizes`",
|
| 350 |
+
category=FutureWarning,
|
| 351 |
+
)
|
| 352 |
+
def cummulative_sizes(self):
|
| 353 |
+
return self.cumulative_sizes
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
class ChainDataset(IterableDataset):
|
| 357 |
+
r"""Dataset for chaining multiple :class:`IterableDataset` s.
|
| 358 |
+
|
| 359 |
+
This class is useful to assemble different existing dataset streams. The
|
| 360 |
+
chaining operation is done on-the-fly, so concatenating large-scale
|
| 361 |
+
datasets with this class will be efficient.
|
| 362 |
+
|
| 363 |
+
Args:
|
| 364 |
+
datasets (iterable of IterableDataset): datasets to be chained together
|
| 365 |
+
"""
|
| 366 |
+
|
| 367 |
+
def __init__(self, datasets: Iterable[Dataset]) -> None:
|
| 368 |
+
super().__init__()
|
| 369 |
+
self.datasets = datasets
|
| 370 |
+
|
| 371 |
+
def __iter__(self):
|
| 372 |
+
for d in self.datasets:
|
| 373 |
+
if not isinstance(d, IterableDataset):
|
| 374 |
+
raise AssertionError("ChainDataset only supports IterableDataset")
|
| 375 |
+
yield from d
|
| 376 |
+
|
| 377 |
+
def __len__(self) -> int:
|
| 378 |
+
total = 0
|
| 379 |
+
for d in self.datasets:
|
| 380 |
+
if not isinstance(d, IterableDataset):
|
| 381 |
+
raise AssertionError("ChainDataset only supports IterableDataset")
|
| 382 |
+
total += len(d) # type: ignore[arg-type]
|
| 383 |
+
return total
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
class Subset(Dataset[_T_co]):
|
| 387 |
+
r"""
|
| 388 |
+
Subset of a dataset at specified indices.
|
| 389 |
+
|
| 390 |
+
.. note::
|
| 391 |
+
When subclassing `Subset` and overriding `__getitem__`, you **must** also
|
| 392 |
+
override `__getitems__` to ensure `DataLoader` works correctly with your
|
| 393 |
+
custom logic. If you override only `__getitem__`, a `NotImplementedError`
|
| 394 |
+
will be raised when using `DataLoader`.
|
| 395 |
+
|
| 396 |
+
A simple implementation of `__getitems__` can delegate to `__getitem__`:
|
| 397 |
+
|
| 398 |
+
.. code-block:: python
|
| 399 |
+
|
| 400 |
+
def __getitems__(self, indices):
|
| 401 |
+
return [self.__getitem__(idx) for idx in indices]
|
| 402 |
+
|
| 403 |
+
For better performance, consider implementing batch-aware logic in
|
| 404 |
+
`__getitems__` instead of calling `__getitem__` multiple times.
|
| 405 |
+
|
| 406 |
+
Args:
|
| 407 |
+
dataset (Dataset): The whole Dataset
|
| 408 |
+
indices (sequence): Indices in the whole set selected for subset
|
| 409 |
+
"""
|
| 410 |
+
|
| 411 |
+
dataset: Dataset[_T_co]
|
| 412 |
+
indices: Sequence[int]
|
| 413 |
+
|
| 414 |
+
def __init__(self, dataset: Dataset[_T_co], indices: Sequence[int]) -> None:
|
| 415 |
+
self.dataset = dataset
|
| 416 |
+
self.indices = indices
|
| 417 |
+
|
| 418 |
+
# Check if __getitem__ is overridden but __getitems__ is not
|
| 419 |
+
if (
|
| 420 |
+
type(self).__getitem__ is not Subset.__getitem__
|
| 421 |
+
and type(self).__getitems__ is Subset.__getitems__
|
| 422 |
+
):
|
| 423 |
+
raise NotImplementedError(
|
| 424 |
+
f"{type(self).__name__} overrides __getitem__ but not __getitems__. "
|
| 425 |
+
"When subclassing Subset and overriding __getitem__, you must also override "
|
| 426 |
+
"__getitems__ to ensure DataLoader works correctly with your custom logic. "
|
| 427 |
+
"A simple implementation:\n\n"
|
| 428 |
+
"def __getitems__(self, indices):\n"
|
| 429 |
+
" return [self.__getitem__(idx) for idx in indices]"
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
def __getitem__(self, idx):
|
| 433 |
+
if isinstance(idx, list):
|
| 434 |
+
return self.dataset[[self.indices[i] for i in idx]]
|
| 435 |
+
return self.dataset[self.indices[idx]]
|
| 436 |
+
|
| 437 |
+
def __getitems__(self, indices: list[int]) -> list[_T_co]:
|
| 438 |
+
# add batched sampling support when parent dataset supports it.
|
| 439 |
+
# see torch.utils.data._utils.fetch._MapDatasetFetcher
|
| 440 |
+
if callable(getattr(self.dataset, "__getitems__", None)):
|
| 441 |
+
return self.dataset.__getitems__([self.indices[idx] for idx in indices]) # type: ignore[attr-defined]
|
| 442 |
+
else:
|
| 443 |
+
return [self.dataset[self.indices[idx]] for idx in indices]
|
| 444 |
+
|
| 445 |
+
def __len__(self) -> int:
|
| 446 |
+
return len(self.indices)
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
def random_split(
|
| 450 |
+
dataset: Dataset[_T],
|
| 451 |
+
lengths: Sequence[int | float],
|
| 452 |
+
generator: Generator | None = default_generator,
|
| 453 |
+
) -> list[Subset[_T]]:
|
| 454 |
+
r"""
|
| 455 |
+
Randomly split a dataset into non-overlapping new datasets of given lengths.
|
| 456 |
+
|
| 457 |
+
If a list of fractions that sum up to 1 is given,
|
| 458 |
+
the lengths will be computed automatically as
|
| 459 |
+
floor(frac * len(dataset)) for each fraction provided.
|
| 460 |
+
|
| 461 |
+
After computing the lengths, if there are any remainders, 1 count will be
|
| 462 |
+
distributed in round-robin fashion to the lengths
|
| 463 |
+
until there are no remainders left.
|
| 464 |
+
|
| 465 |
+
Optionally fix the generator for reproducible results, e.g.:
|
| 466 |
+
|
| 467 |
+
Example:
|
| 468 |
+
>>> # xdoctest: +SKIP
|
| 469 |
+
>>> generator1 = torch.Generator().manual_seed(42)
|
| 470 |
+
>>> generator2 = torch.Generator().manual_seed(42)
|
| 471 |
+
>>> random_split(range(10), [3, 7], generator=generator1)
|
| 472 |
+
>>> random_split(range(30), [0.3, 0.3, 0.4], generator=generator2)
|
| 473 |
+
|
| 474 |
+
Args:
|
| 475 |
+
dataset (Dataset): Dataset to be split
|
| 476 |
+
lengths (sequence): lengths or fractions of splits to be produced
|
| 477 |
+
generator (Generator): Generator used for the random permutation.
|
| 478 |
+
"""
|
| 479 |
+
if math.isclose(sum(lengths), 1) and sum(lengths) <= 1:
|
| 480 |
+
subset_lengths: list[int] = []
|
| 481 |
+
for i, frac in enumerate(lengths):
|
| 482 |
+
if frac < 0 or frac > 1:
|
| 483 |
+
raise ValueError(f"Fraction at index {i} is not between 0 and 1")
|
| 484 |
+
n_items_in_split = math.floor(len(dataset) * frac) # type: ignore[arg-type]
|
| 485 |
+
subset_lengths.append(n_items_in_split)
|
| 486 |
+
remainder = len(dataset) - sum(subset_lengths) # type: ignore[arg-type]
|
| 487 |
+
# add 1 to all the lengths in round-robin fashion until the remainder is 0
|
| 488 |
+
for i in range(remainder):
|
| 489 |
+
idx_to_add_at = i % len(subset_lengths)
|
| 490 |
+
subset_lengths[idx_to_add_at] += 1
|
| 491 |
+
lengths = subset_lengths
|
| 492 |
+
for i, length in enumerate(lengths):
|
| 493 |
+
if length == 0:
|
| 494 |
+
warnings.warn(
|
| 495 |
+
f"Length of split at index {i} is 0. "
|
| 496 |
+
f"This might result in an empty dataset.",
|
| 497 |
+
stacklevel=2,
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
# Cannot verify that dataset is Sized
|
| 501 |
+
if sum(lengths) != len(dataset): # type: ignore[arg-type]
|
| 502 |
+
raise ValueError(
|
| 503 |
+
"Sum of input lengths does not equal the length of the input dataset!"
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
indices = randperm(sum(lengths), generator=generator).tolist() # type: ignore[arg-type, call-overload]
|
| 507 |
+
lengths = cast(Sequence[int], lengths)
|
| 508 |
+
return [
|
| 509 |
+
Subset(dataset, indices[offset - length : offset])
|
| 510 |
+
for offset, length in zip(itertools.accumulate(lengths), lengths, strict=True)
|
| 511 |
+
]
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/distributed.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
from collections.abc import Iterator
|
| 3 |
+
from typing import TypeVar
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.distributed as dist
|
| 7 |
+
from torch.utils.data.dataset import Dataset
|
| 8 |
+
from torch.utils.data.sampler import Sampler
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = ["DistributedSampler"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
_T_co = TypeVar("_T_co", covariant=True)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class DistributedSampler(Sampler[_T_co]):
|
| 18 |
+
r"""Sampler that restricts data loading to a subset of the dataset.
|
| 19 |
+
|
| 20 |
+
It is especially useful in conjunction with
|
| 21 |
+
:class:`torch.nn.parallel.DistributedDataParallel`. In such a case, each
|
| 22 |
+
process can pass a :class:`~torch.utils.data.DistributedSampler` instance as a
|
| 23 |
+
:class:`~torch.utils.data.DataLoader` sampler, and load a subset of the
|
| 24 |
+
original dataset that is exclusive to it.
|
| 25 |
+
|
| 26 |
+
.. note::
|
| 27 |
+
Dataset is assumed to be of constant size and that any instance of it always
|
| 28 |
+
returns the same elements in the same order.
|
| 29 |
+
|
| 30 |
+
Args:
|
| 31 |
+
dataset: Dataset used for sampling.
|
| 32 |
+
num_replicas (int, optional): Number of processes participating in
|
| 33 |
+
distributed training. By default, :attr:`world_size` is retrieved from the
|
| 34 |
+
current distributed group.
|
| 35 |
+
rank (int, optional): Rank of the current process within :attr:`num_replicas`.
|
| 36 |
+
By default, :attr:`rank` is retrieved from the current distributed
|
| 37 |
+
group.
|
| 38 |
+
shuffle (bool, optional): If ``True`` (default), sampler will shuffle the
|
| 39 |
+
indices.
|
| 40 |
+
seed (int, optional): random seed used to shuffle the sampler if
|
| 41 |
+
:attr:`shuffle=True`. This number should be identical across all
|
| 42 |
+
processes in the distributed group. Default: ``0``.
|
| 43 |
+
drop_last (bool, optional): if ``True``, then the sampler will drop the
|
| 44 |
+
tail of the data to make it evenly divisible across the number of
|
| 45 |
+
replicas. If ``False``, the sampler will add extra indices to make
|
| 46 |
+
the data evenly divisible across the replicas. Default: ``False``.
|
| 47 |
+
|
| 48 |
+
.. warning::
|
| 49 |
+
In distributed mode, calling the :meth:`set_epoch` method at
|
| 50 |
+
the beginning of each epoch **before** creating the :class:`DataLoader` iterator
|
| 51 |
+
is necessary to make shuffling work properly across multiple epochs. Otherwise,
|
| 52 |
+
the same ordering will be always used.
|
| 53 |
+
|
| 54 |
+
Example::
|
| 55 |
+
|
| 56 |
+
>>> # xdoctest: +SKIP
|
| 57 |
+
>>> sampler = DistributedSampler(dataset) if is_distributed else None
|
| 58 |
+
>>> loader = DataLoader(dataset, shuffle=(sampler is None),
|
| 59 |
+
... sampler=sampler)
|
| 60 |
+
>>> for epoch in range(start_epoch, n_epochs):
|
| 61 |
+
... if is_distributed:
|
| 62 |
+
... sampler.set_epoch(epoch)
|
| 63 |
+
... train(loader)
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __init__(
|
| 67 |
+
self,
|
| 68 |
+
dataset: Dataset,
|
| 69 |
+
num_replicas: int | None = None,
|
| 70 |
+
rank: int | None = None,
|
| 71 |
+
shuffle: bool = True,
|
| 72 |
+
seed: int = 0,
|
| 73 |
+
drop_last: bool = False,
|
| 74 |
+
) -> None:
|
| 75 |
+
if num_replicas is None:
|
| 76 |
+
if not dist.is_available():
|
| 77 |
+
raise RuntimeError("Requires distributed package to be available")
|
| 78 |
+
num_replicas = dist.get_world_size()
|
| 79 |
+
if rank is None:
|
| 80 |
+
if not dist.is_available():
|
| 81 |
+
raise RuntimeError("Requires distributed package to be available")
|
| 82 |
+
rank = dist.get_rank()
|
| 83 |
+
if rank >= num_replicas or rank < 0:
|
| 84 |
+
raise ValueError(
|
| 85 |
+
f"Invalid rank {rank}, rank should be in the interval [0, {num_replicas - 1}]"
|
| 86 |
+
)
|
| 87 |
+
self.dataset = dataset
|
| 88 |
+
self.num_replicas = num_replicas
|
| 89 |
+
self.rank = rank
|
| 90 |
+
self.epoch = 0
|
| 91 |
+
self.drop_last = drop_last
|
| 92 |
+
# If the dataset length is evenly divisible by # of replicas, then there
|
| 93 |
+
# is no need to drop any data, since the dataset will be split equally.
|
| 94 |
+
if self.drop_last and len(self.dataset) % self.num_replicas != 0: # type: ignore[arg-type]
|
| 95 |
+
# Split to nearest available length that is evenly divisible.
|
| 96 |
+
# This is to ensure each rank receives the same amount of data when
|
| 97 |
+
# using this Sampler.
|
| 98 |
+
self.num_samples = math.ceil(
|
| 99 |
+
(len(self.dataset) - self.num_replicas) / self.num_replicas # type: ignore[arg-type]
|
| 100 |
+
)
|
| 101 |
+
else:
|
| 102 |
+
self.num_samples = math.ceil(len(self.dataset) / self.num_replicas) # type: ignore[arg-type]
|
| 103 |
+
self.total_size = self.num_samples * self.num_replicas
|
| 104 |
+
self.shuffle = shuffle
|
| 105 |
+
self.seed = seed
|
| 106 |
+
|
| 107 |
+
def __iter__(self) -> Iterator[_T_co]:
|
| 108 |
+
if self.shuffle:
|
| 109 |
+
# deterministically shuffle based on epoch and seed
|
| 110 |
+
g = torch.Generator()
|
| 111 |
+
g.manual_seed(self.seed + self.epoch)
|
| 112 |
+
indices = torch.randperm(len(self.dataset), generator=g).tolist() # type: ignore[arg-type]
|
| 113 |
+
else:
|
| 114 |
+
indices = list(range(len(self.dataset))) # type: ignore[arg-type]
|
| 115 |
+
|
| 116 |
+
if not self.drop_last:
|
| 117 |
+
# add extra samples to make it evenly divisible
|
| 118 |
+
padding_size = self.total_size - len(indices)
|
| 119 |
+
if padding_size <= len(indices):
|
| 120 |
+
indices += indices[:padding_size]
|
| 121 |
+
else:
|
| 122 |
+
indices += (indices * math.ceil(padding_size / len(indices)))[
|
| 123 |
+
:padding_size
|
| 124 |
+
]
|
| 125 |
+
else:
|
| 126 |
+
# remove tail of data to make it evenly divisible.
|
| 127 |
+
indices = indices[: self.total_size]
|
| 128 |
+
if len(indices) != self.total_size:
|
| 129 |
+
raise AssertionError(
|
| 130 |
+
f"Number of indices ({len(indices)}) does not match total_size ({self.total_size})"
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# subsample
|
| 134 |
+
indices = indices[self.rank : self.total_size : self.num_replicas]
|
| 135 |
+
if len(indices) != self.num_samples:
|
| 136 |
+
raise AssertionError(
|
| 137 |
+
f"Number of subsampled indices ({len(indices)}) does not match num_samples ({self.num_samples})"
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
# pyrefly: ignore [bad-return]
|
| 141 |
+
return iter(indices)
|
| 142 |
+
|
| 143 |
+
def __len__(self) -> int:
|
| 144 |
+
return self.num_samples
|
| 145 |
+
|
| 146 |
+
def set_epoch(self, epoch: int) -> None:
|
| 147 |
+
r"""
|
| 148 |
+
Set the epoch for this sampler.
|
| 149 |
+
|
| 150 |
+
When :attr:`shuffle=True`, this ensures all replicas
|
| 151 |
+
use a different random ordering for each epoch. Otherwise, the next iteration of this
|
| 152 |
+
sampler will yield the same ordering.
|
| 153 |
+
|
| 154 |
+
Args:
|
| 155 |
+
epoch (int): Epoch number.
|
| 156 |
+
"""
|
| 157 |
+
self.epoch = epoch
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/graph.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import io
|
| 3 |
+
import pickle
|
| 4 |
+
import warnings
|
| 5 |
+
from collections.abc import Collection
|
| 6 |
+
|
| 7 |
+
from torch.utils._import_utils import dill_available
|
| 8 |
+
from torch.utils.data.datapipes.datapipe import IterDataPipe, MapDataPipe
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = ["traverse", "traverse_dps"]
|
| 12 |
+
|
| 13 |
+
DataPipe = IterDataPipe | MapDataPipe
|
| 14 |
+
# pyrefly: ignore [invalid-type-alias]
|
| 15 |
+
DataPipeGraph = dict[int, tuple[DataPipe, "DataPipeGraph"]]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _stub_unpickler() -> str:
|
| 19 |
+
return "STUB"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# TODO(VitalyFedyunin): Make sure it works without dill module installed
|
| 23 |
+
def _list_connected_datapipes(
|
| 24 |
+
scan_obj: DataPipe, only_datapipe: bool, cache: set[int]
|
| 25 |
+
) -> list[DataPipe]:
|
| 26 |
+
f = io.BytesIO()
|
| 27 |
+
p = pickle.Pickler(
|
| 28 |
+
f
|
| 29 |
+
) # Not going to work for lambdas, but dill infinite loops on typing and can't be used as is
|
| 30 |
+
if dill_available():
|
| 31 |
+
from dill import Pickler as dill_Pickler
|
| 32 |
+
|
| 33 |
+
d = dill_Pickler(f)
|
| 34 |
+
else:
|
| 35 |
+
d = None
|
| 36 |
+
|
| 37 |
+
captured_connections = []
|
| 38 |
+
|
| 39 |
+
def getstate_hook(ori_state):
|
| 40 |
+
state = None
|
| 41 |
+
if isinstance(ori_state, dict):
|
| 42 |
+
state = {}
|
| 43 |
+
for k, v in ori_state.items():
|
| 44 |
+
if isinstance(v, (IterDataPipe, MapDataPipe, Collection)):
|
| 45 |
+
state[k] = v
|
| 46 |
+
elif isinstance(ori_state, (tuple, list)):
|
| 47 |
+
state = [] # type: ignore[assignment]
|
| 48 |
+
for v in ori_state:
|
| 49 |
+
if isinstance(v, (IterDataPipe, MapDataPipe, Collection)):
|
| 50 |
+
state.append(v) # type: ignore[attr-defined]
|
| 51 |
+
elif isinstance(ori_state, (IterDataPipe, MapDataPipe, Collection)):
|
| 52 |
+
state = ori_state # type: ignore[assignment]
|
| 53 |
+
return state
|
| 54 |
+
|
| 55 |
+
def reduce_hook(obj):
|
| 56 |
+
if obj == scan_obj or id(obj) in cache:
|
| 57 |
+
raise NotImplementedError
|
| 58 |
+
else:
|
| 59 |
+
captured_connections.append(obj)
|
| 60 |
+
# Adding id to remove duplicate DataPipe serialized at the same level
|
| 61 |
+
cache.add(id(obj))
|
| 62 |
+
return _stub_unpickler, ()
|
| 63 |
+
|
| 64 |
+
datapipe_classes: tuple[type[DataPipe]] = (IterDataPipe, MapDataPipe) # type: ignore[assignment]
|
| 65 |
+
|
| 66 |
+
try:
|
| 67 |
+
for cls in datapipe_classes:
|
| 68 |
+
cls.set_reduce_ex_hook(reduce_hook)
|
| 69 |
+
if only_datapipe:
|
| 70 |
+
cls.set_getstate_hook(getstate_hook)
|
| 71 |
+
try:
|
| 72 |
+
p.dump(scan_obj)
|
| 73 |
+
except (pickle.PickleError, AttributeError, TypeError):
|
| 74 |
+
if dill_available():
|
| 75 |
+
# pyrefly: ignore [missing-attribute]
|
| 76 |
+
d.dump(scan_obj)
|
| 77 |
+
else:
|
| 78 |
+
raise
|
| 79 |
+
finally:
|
| 80 |
+
for cls in datapipe_classes:
|
| 81 |
+
cls.set_reduce_ex_hook(None)
|
| 82 |
+
if only_datapipe:
|
| 83 |
+
cls.set_getstate_hook(None)
|
| 84 |
+
if dill_available():
|
| 85 |
+
from dill import extend as dill_extend
|
| 86 |
+
|
| 87 |
+
dill_extend(False) # Undo change to dispatch table
|
| 88 |
+
return captured_connections
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def traverse_dps(datapipe: DataPipe) -> DataPipeGraph:
|
| 92 |
+
r"""
|
| 93 |
+
Traverse the DataPipes and their attributes to extract the DataPipe graph.
|
| 94 |
+
|
| 95 |
+
This only looks into the attribute from each DataPipe that is either a
|
| 96 |
+
DataPipe and a Python collection object such as ``list``, ``tuple``,
|
| 97 |
+
``set`` and ``dict``.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
datapipe: the end DataPipe of the graph
|
| 101 |
+
Returns:
|
| 102 |
+
A graph represented as a nested dictionary, where keys are ids of DataPipe instances
|
| 103 |
+
and values are tuples of DataPipe instance and the sub-graph
|
| 104 |
+
"""
|
| 105 |
+
cache: set[int] = set()
|
| 106 |
+
return _traverse_helper(datapipe, only_datapipe=True, cache=cache)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def traverse(datapipe: DataPipe, only_datapipe: bool | None = None) -> DataPipeGraph:
|
| 110 |
+
r"""
|
| 111 |
+
Traverse the DataPipes and their attributes to extract the DataPipe graph.
|
| 112 |
+
|
| 113 |
+
[Deprecated]
|
| 114 |
+
When ``only_dataPipe`` is specified as ``True``, it would only look into the
|
| 115 |
+
attribute from each DataPipe that is either a DataPipe and a Python collection object
|
| 116 |
+
such as ``list``, ``tuple``, ``set`` and ``dict``.
|
| 117 |
+
|
| 118 |
+
Note:
|
| 119 |
+
This function is deprecated. Please use `traverse_dps` instead.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
datapipe: the end DataPipe of the graph
|
| 123 |
+
only_datapipe: If ``False`` (default), all attributes of each DataPipe are traversed.
|
| 124 |
+
This argument is deprecating and will be removed after the next release.
|
| 125 |
+
Returns:
|
| 126 |
+
A graph represented as a nested dictionary, where keys are ids of DataPipe instances
|
| 127 |
+
and values are tuples of DataPipe instance and the sub-graph
|
| 128 |
+
"""
|
| 129 |
+
msg = (
|
| 130 |
+
"`traverse` function and will be removed after 1.13. "
|
| 131 |
+
"Please use `traverse_dps` instead."
|
| 132 |
+
)
|
| 133 |
+
if not only_datapipe:
|
| 134 |
+
msg += " And, the behavior will be changed to the equivalent of `only_datapipe=True`."
|
| 135 |
+
warnings.warn(msg, FutureWarning, stacklevel=2)
|
| 136 |
+
if only_datapipe is None:
|
| 137 |
+
only_datapipe = False
|
| 138 |
+
cache: set[int] = set()
|
| 139 |
+
return _traverse_helper(datapipe, only_datapipe, cache)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# Add cache here to prevent infinite recursion on DataPipe
|
| 143 |
+
def _traverse_helper(
|
| 144 |
+
datapipe: DataPipe, only_datapipe: bool, cache: set[int]
|
| 145 |
+
) -> DataPipeGraph:
|
| 146 |
+
if not isinstance(datapipe, (IterDataPipe, MapDataPipe)):
|
| 147 |
+
raise RuntimeError(
|
| 148 |
+
f"Expected `IterDataPipe` or `MapDataPipe`, but {type(datapipe)} is found"
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
dp_id = id(datapipe)
|
| 152 |
+
if dp_id in cache:
|
| 153 |
+
return {}
|
| 154 |
+
cache.add(dp_id)
|
| 155 |
+
# Using cache.copy() here is to prevent the same DataPipe pollutes the cache on different paths
|
| 156 |
+
items = _list_connected_datapipes(datapipe, only_datapipe, cache.copy())
|
| 157 |
+
d: DataPipeGraph = {dp_id: (datapipe, {})}
|
| 158 |
+
for item in items:
|
| 159 |
+
# Using cache.copy() here is to prevent recursion on a single path rather than global graph
|
| 160 |
+
# Single DataPipe can present multiple times in different paths in graph
|
| 161 |
+
# pyrefly: ignore [no-matching-overload]
|
| 162 |
+
d[dp_id][1].update(_traverse_helper(item, only_datapipe, cache.copy()))
|
| 163 |
+
return d
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/graph_settings.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import inspect
|
| 3 |
+
import warnings
|
| 4 |
+
from typing import Any
|
| 5 |
+
from typing_extensions import deprecated
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from torch.utils.data.datapipes.iter.sharding import (
|
| 9 |
+
_ShardingIterDataPipe,
|
| 10 |
+
SHARDING_PRIORITIES,
|
| 11 |
+
)
|
| 12 |
+
from torch.utils.data.graph import DataPipe, DataPipeGraph, traverse_dps
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
"apply_random_seed",
|
| 17 |
+
"apply_sharding",
|
| 18 |
+
"apply_shuffle_seed",
|
| 19 |
+
"apply_shuffle_settings",
|
| 20 |
+
"get_all_graph_pipes",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_all_graph_pipes(graph: DataPipeGraph) -> list[DataPipe]:
|
| 25 |
+
return _get_all_graph_pipes_helper(graph, set())
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _get_all_graph_pipes_helper(
|
| 29 |
+
graph: DataPipeGraph, id_cache: set[int]
|
| 30 |
+
) -> list[DataPipe]:
|
| 31 |
+
results: list[DataPipe] = []
|
| 32 |
+
for dp_id, (datapipe, sub_graph) in graph.items():
|
| 33 |
+
if dp_id in id_cache:
|
| 34 |
+
continue
|
| 35 |
+
id_cache.add(dp_id)
|
| 36 |
+
results.append(datapipe)
|
| 37 |
+
results.extend(_get_all_graph_pipes_helper(sub_graph, id_cache))
|
| 38 |
+
return results
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _is_sharding_datapipe(datapipe: DataPipe) -> bool:
|
| 42 |
+
return isinstance(datapipe, _ShardingIterDataPipe) or (
|
| 43 |
+
hasattr(datapipe, "apply_sharding")
|
| 44 |
+
and inspect.ismethod(datapipe.apply_sharding)
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def apply_sharding(
|
| 49 |
+
datapipe: DataPipe,
|
| 50 |
+
num_of_instances: int,
|
| 51 |
+
instance_id: int,
|
| 52 |
+
sharding_group=SHARDING_PRIORITIES.DEFAULT,
|
| 53 |
+
) -> DataPipe:
|
| 54 |
+
r"""
|
| 55 |
+
Apply dynamic sharding over the ``sharding_filter`` DataPipe that has a method ``apply_sharding``.
|
| 56 |
+
|
| 57 |
+
RuntimeError will be raised when multiple ``sharding_filter`` are presented in the same branch.
|
| 58 |
+
"""
|
| 59 |
+
graph = traverse_dps(datapipe)
|
| 60 |
+
|
| 61 |
+
def _helper(graph, prev_applied=None) -> None:
|
| 62 |
+
for dp, sub_graph in graph.values():
|
| 63 |
+
applied = None
|
| 64 |
+
if _is_sharding_datapipe(dp):
|
| 65 |
+
if prev_applied is not None:
|
| 66 |
+
raise RuntimeError(
|
| 67 |
+
"Sharding twice on a single pipeline is likely unintended and will cause data loss. "
|
| 68 |
+
f"Sharding already applied to {prev_applied} while trying to apply to {dp}"
|
| 69 |
+
)
|
| 70 |
+
# For BC, only provide sharding_group if accepted
|
| 71 |
+
sig = inspect.signature(dp.apply_sharding)
|
| 72 |
+
if len(sig.parameters) < 3:
|
| 73 |
+
dp.apply_sharding(num_of_instances, instance_id)
|
| 74 |
+
else:
|
| 75 |
+
dp.apply_sharding(
|
| 76 |
+
num_of_instances, instance_id, sharding_group=sharding_group
|
| 77 |
+
)
|
| 78 |
+
applied = dp
|
| 79 |
+
if applied is None:
|
| 80 |
+
applied = prev_applied
|
| 81 |
+
_helper(sub_graph, applied)
|
| 82 |
+
|
| 83 |
+
_helper(graph)
|
| 84 |
+
|
| 85 |
+
return datapipe
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _is_shuffle_datapipe(datapipe: DataPipe) -> bool:
|
| 89 |
+
return (
|
| 90 |
+
hasattr(datapipe, "set_shuffle")
|
| 91 |
+
and hasattr(datapipe, "set_seed")
|
| 92 |
+
and inspect.ismethod(datapipe.set_shuffle)
|
| 93 |
+
and inspect.ismethod(datapipe.set_seed)
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def apply_shuffle_settings(datapipe: DataPipe, shuffle: bool | None = None) -> DataPipe:
|
| 98 |
+
r"""
|
| 99 |
+
Traverse the graph of ``DataPipes`` to find and set shuffle attribute.
|
| 100 |
+
|
| 101 |
+
Apply the method to each `DataPipe` that has APIs of ``set_shuffle``
|
| 102 |
+
and ``set_seed``.
|
| 103 |
+
|
| 104 |
+
Args:
|
| 105 |
+
datapipe: DataPipe that needs to set shuffle attribute
|
| 106 |
+
shuffle: Shuffle option (default: ``None`` and no-op to the graph)
|
| 107 |
+
"""
|
| 108 |
+
if shuffle is None:
|
| 109 |
+
return datapipe
|
| 110 |
+
|
| 111 |
+
graph = traverse_dps(datapipe)
|
| 112 |
+
all_pipes = get_all_graph_pipes(graph)
|
| 113 |
+
shufflers = [pipe for pipe in all_pipes if _is_shuffle_datapipe(pipe)]
|
| 114 |
+
if not shufflers and shuffle:
|
| 115 |
+
warnings.warn(
|
| 116 |
+
"`shuffle=True` was set, but the datapipe does not contain a `Shuffler`. Adding one at the end. "
|
| 117 |
+
"Be aware that the default buffer size might not be sufficient for your task.",
|
| 118 |
+
stacklevel=2,
|
| 119 |
+
)
|
| 120 |
+
datapipe = datapipe.shuffle()
|
| 121 |
+
shufflers = [
|
| 122 |
+
datapipe,
|
| 123 |
+
]
|
| 124 |
+
|
| 125 |
+
for shuffler in shufflers:
|
| 126 |
+
shuffler.set_shuffle(shuffle)
|
| 127 |
+
|
| 128 |
+
return datapipe
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@deprecated(
|
| 132 |
+
"`apply_shuffle_seed` is deprecated since 1.12 and will be removed in the future releases. "
|
| 133 |
+
"Please use `apply_random_seed` instead.",
|
| 134 |
+
category=FutureWarning,
|
| 135 |
+
)
|
| 136 |
+
def apply_shuffle_seed(datapipe: DataPipe, rng: Any) -> DataPipe:
|
| 137 |
+
return apply_random_seed(datapipe, rng)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def _is_random_datapipe(datapipe: DataPipe) -> bool:
|
| 141 |
+
return hasattr(datapipe, "set_seed") and inspect.ismethod(datapipe.set_seed)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def apply_random_seed(datapipe: DataPipe, rng: torch.Generator) -> DataPipe:
|
| 145 |
+
r"""
|
| 146 |
+
Traverse the graph of ``DataPipes`` to find random ``DataPipe`` with an API of ``set_seed``.
|
| 147 |
+
|
| 148 |
+
Then set the random seed based on the provided RNG to those ``DataPipe``.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
datapipe: DataPipe that needs to set randomness
|
| 152 |
+
rng: Random number generator to generate random seeds
|
| 153 |
+
"""
|
| 154 |
+
graph = traverse_dps(datapipe)
|
| 155 |
+
all_pipes = get_all_graph_pipes(graph)
|
| 156 |
+
# Using a set to track id of DataPipe to prevent setting randomness per DataPipe more than once.
|
| 157 |
+
# And, `id` is used in case of unhashable DataPipe
|
| 158 |
+
cache = set()
|
| 159 |
+
random_datapipes = []
|
| 160 |
+
for pipe in all_pipes:
|
| 161 |
+
if id(pipe) in cache:
|
| 162 |
+
continue
|
| 163 |
+
if _is_random_datapipe(pipe):
|
| 164 |
+
random_datapipes.append(pipe)
|
| 165 |
+
cache.add(id(pipe))
|
| 166 |
+
|
| 167 |
+
for pipe in random_datapipes:
|
| 168 |
+
random_seed = int(
|
| 169 |
+
torch.empty((), dtype=torch.int64).random_(generator=rng).item()
|
| 170 |
+
)
|
| 171 |
+
pipe.set_seed(random_seed)
|
| 172 |
+
|
| 173 |
+
return datapipe
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/data/sampler.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import itertools
|
| 3 |
+
from collections.abc import Iterable, Iterator, Sequence, Sized
|
| 4 |
+
from typing import Generic, TypeVar
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# Note: For benchmarking changes to samplers, see:
|
| 10 |
+
# /benchmarks/data/samplers_bench.py
|
| 11 |
+
# This benchmark compares the performance of different sampler implementations
|
| 12 |
+
# and can be used to evaluate the impact of optimizations.
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
__all__ = [
|
| 16 |
+
"BatchSampler",
|
| 17 |
+
"RandomSampler",
|
| 18 |
+
"Sampler",
|
| 19 |
+
"SequentialSampler",
|
| 20 |
+
"SubsetRandomSampler",
|
| 21 |
+
"WeightedRandomSampler",
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
_T_co = TypeVar("_T_co", covariant=True)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class Sampler(Generic[_T_co]):
|
| 29 |
+
r"""Base class for all Samplers.
|
| 30 |
+
|
| 31 |
+
Every Sampler subclass has to provide an :meth:`__iter__` method, providing a
|
| 32 |
+
way to iterate over indices or lists of indices (batches) of dataset elements,
|
| 33 |
+
and may provide a :meth:`__len__` method that returns the length of the returned iterators.
|
| 34 |
+
|
| 35 |
+
Example:
|
| 36 |
+
>>> # xdoctest: +SKIP
|
| 37 |
+
>>> class AccedingSequenceLengthSampler(Sampler[int]):
|
| 38 |
+
>>> def __init__(self, data: List[str]) -> None:
|
| 39 |
+
>>> self.data = data
|
| 40 |
+
>>>
|
| 41 |
+
>>> def __len__(self) -> int:
|
| 42 |
+
>>> return len(self.data)
|
| 43 |
+
>>>
|
| 44 |
+
>>> def __iter__(self) -> Iterator[int]:
|
| 45 |
+
>>> sizes = torch.tensor([len(x) for x in self.data])
|
| 46 |
+
>>> yield from torch.argsort(sizes).tolist()
|
| 47 |
+
>>>
|
| 48 |
+
>>> class AccedingSequenceLengthBatchSampler(Sampler[List[int]]):
|
| 49 |
+
>>> def __init__(self, data: List[str], batch_size: int) -> None:
|
| 50 |
+
>>> self.data = data
|
| 51 |
+
>>> self.batch_size = batch_size
|
| 52 |
+
>>>
|
| 53 |
+
>>> def __len__(self) -> int:
|
| 54 |
+
>>> return (len(self.data) + self.batch_size - 1) // self.batch_size
|
| 55 |
+
>>>
|
| 56 |
+
>>> def __iter__(self) -> Iterator[List[int]]:
|
| 57 |
+
>>> sizes = torch.tensor([len(x) for x in self.data])
|
| 58 |
+
>>> for batch in torch.chunk(torch.argsort(sizes), len(self)):
|
| 59 |
+
>>> yield batch.tolist()
|
| 60 |
+
|
| 61 |
+
.. note:: The :meth:`__len__` method isn't strictly required by
|
| 62 |
+
:class:`~torch.utils.data.DataLoader`, but is expected in any
|
| 63 |
+
calculation involving the length of a :class:`~torch.utils.data.DataLoader`.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def __iter__(self) -> Iterator[_T_co]:
|
| 67 |
+
raise NotImplementedError
|
| 68 |
+
|
| 69 |
+
# NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
|
| 70 |
+
#
|
| 71 |
+
# Many times we have an abstract class representing a collection/iterable of
|
| 72 |
+
# data, e.g., `torch.utils.data.Sampler`, with its subclasses optionally
|
| 73 |
+
# implementing a `__len__` method. In such cases, we must make sure to not
|
| 74 |
+
# provide a default implementation, because both straightforward default
|
| 75 |
+
# implementations have their issues:
|
| 76 |
+
#
|
| 77 |
+
# + `return NotImplemented`:
|
| 78 |
+
# Calling `len(subclass_instance)` raises:
|
| 79 |
+
# TypeError: 'NotImplementedType' object cannot be interpreted as an integer
|
| 80 |
+
#
|
| 81 |
+
# + `raise NotImplementedError`:
|
| 82 |
+
# This prevents triggering some fallback behavior. E.g., the built-in
|
| 83 |
+
# `list(X)` tries to call `len(X)` first, and executes a different code
|
| 84 |
+
# path if the method is not found or `NotImplemented` is returned, while
|
| 85 |
+
# raising a `NotImplementedError` will propagate and make the call fail
|
| 86 |
+
# where it could have used `__iter__` to complete the call.
|
| 87 |
+
#
|
| 88 |
+
# Thus, the only two sensible things to do are
|
| 89 |
+
#
|
| 90 |
+
# + **not** provide a default `__len__`.
|
| 91 |
+
#
|
| 92 |
+
# + raise a `TypeError` instead, which is what Python uses when users call
|
| 93 |
+
# a method that is not defined on an object.
|
| 94 |
+
# (@ssnl verifies that this works on at least Python 3.7.)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class SequentialSampler(Sampler[int]):
|
| 98 |
+
r"""Samples elements sequentially, always in the same order.
|
| 99 |
+
|
| 100 |
+
Args:
|
| 101 |
+
data_source (Sized): data source to sample from. Must implement __len__.
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
data_source: Sized
|
| 105 |
+
|
| 106 |
+
def __init__(self, data_source: Sized) -> None:
|
| 107 |
+
self.data_source = data_source
|
| 108 |
+
|
| 109 |
+
def __iter__(self) -> Iterator[int]:
|
| 110 |
+
return iter(range(len(self.data_source)))
|
| 111 |
+
|
| 112 |
+
def __len__(self) -> int:
|
| 113 |
+
return len(self.data_source)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
class RandomSampler(Sampler[int]):
|
| 117 |
+
r"""Samples elements randomly. If without replacement, then sample from a shuffled dataset.
|
| 118 |
+
|
| 119 |
+
If with replacement, then user can specify :attr:`num_samples` to draw.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
data_source (Sized): data source to sample from. Must implement __len__.
|
| 123 |
+
replacement (bool): samples are drawn on-demand with replacement if ``True``, default=``False``
|
| 124 |
+
num_samples (int): number of samples to draw, default=`len(dataset)`.
|
| 125 |
+
generator (Generator): Generator used in sampling.
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
data_source: Sized
|
| 129 |
+
replacement: bool
|
| 130 |
+
|
| 131 |
+
def __init__(
|
| 132 |
+
self,
|
| 133 |
+
data_source: Sized,
|
| 134 |
+
replacement: bool = False,
|
| 135 |
+
num_samples: int | None = None,
|
| 136 |
+
generator=None,
|
| 137 |
+
) -> None:
|
| 138 |
+
self.data_source = data_source
|
| 139 |
+
self.replacement = replacement
|
| 140 |
+
self._num_samples = num_samples
|
| 141 |
+
self.generator = generator
|
| 142 |
+
|
| 143 |
+
if not isinstance(self.replacement, bool):
|
| 144 |
+
raise TypeError(
|
| 145 |
+
f"replacement should be a boolean value, but got replacement={self.replacement}"
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
if not isinstance(self.num_samples, int) or self.num_samples <= 0:
|
| 149 |
+
raise ValueError(
|
| 150 |
+
f"num_samples should be a positive integer value, but got num_samples={self.num_samples}"
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
@property
|
| 154 |
+
def num_samples(self) -> int:
|
| 155 |
+
# dataset size might change at runtime
|
| 156 |
+
if self._num_samples is None:
|
| 157 |
+
return len(self.data_source)
|
| 158 |
+
return self._num_samples
|
| 159 |
+
|
| 160 |
+
def __iter__(self) -> Iterator[int]:
|
| 161 |
+
n = len(self.data_source)
|
| 162 |
+
if self.generator is None:
|
| 163 |
+
seed = int(torch.empty((), dtype=torch.int64).random_().item())
|
| 164 |
+
generator = torch.Generator()
|
| 165 |
+
generator.manual_seed(seed)
|
| 166 |
+
else:
|
| 167 |
+
generator = self.generator
|
| 168 |
+
|
| 169 |
+
if self.replacement:
|
| 170 |
+
for _ in range(self.num_samples // 32):
|
| 171 |
+
yield from torch.randint(
|
| 172 |
+
high=n, size=(32,), dtype=torch.int64, generator=generator
|
| 173 |
+
).tolist()
|
| 174 |
+
yield from torch.randint(
|
| 175 |
+
high=n,
|
| 176 |
+
size=(self.num_samples % 32,),
|
| 177 |
+
dtype=torch.int64,
|
| 178 |
+
generator=generator,
|
| 179 |
+
).tolist()
|
| 180 |
+
else:
|
| 181 |
+
for _ in range(self.num_samples // n):
|
| 182 |
+
yield from torch.randperm(n, generator=generator).tolist()
|
| 183 |
+
yield from torch.randperm(n, generator=generator).tolist()[
|
| 184 |
+
: self.num_samples % n
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
+
def __len__(self) -> int:
|
| 188 |
+
return self.num_samples
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
class SubsetRandomSampler(Sampler[int]):
|
| 192 |
+
r"""Samples elements randomly from a given list of indices, without replacement.
|
| 193 |
+
|
| 194 |
+
Args:
|
| 195 |
+
indices (sequence): a sequence of indices
|
| 196 |
+
generator (Generator): Generator used in sampling.
|
| 197 |
+
"""
|
| 198 |
+
|
| 199 |
+
indices: Sequence[int]
|
| 200 |
+
|
| 201 |
+
def __init__(self, indices: Sequence[int], generator=None) -> None:
|
| 202 |
+
self.indices = indices
|
| 203 |
+
self.generator = generator
|
| 204 |
+
|
| 205 |
+
def __iter__(self) -> Iterator[int]:
|
| 206 |
+
for i in torch.randperm(len(self.indices), generator=self.generator).tolist():
|
| 207 |
+
yield self.indices[i]
|
| 208 |
+
|
| 209 |
+
def __len__(self) -> int:
|
| 210 |
+
return len(self.indices)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
class WeightedRandomSampler(Sampler[int]):
|
| 214 |
+
r"""Samples elements from ``[0,..,len(weights)-1]`` with given probabilities (weights).
|
| 215 |
+
|
| 216 |
+
Args:
|
| 217 |
+
weights (sequence) : a sequence of weights, not necessary summing up to one
|
| 218 |
+
num_samples (int): number of samples to draw
|
| 219 |
+
replacement (bool): if ``True``, samples are drawn with replacement.
|
| 220 |
+
If not, they are drawn without replacement, which means that when a
|
| 221 |
+
sample index is drawn for a row, it cannot be drawn again for that row.
|
| 222 |
+
generator (Generator): Generator used in sampling.
|
| 223 |
+
|
| 224 |
+
Example:
|
| 225 |
+
>>> # xdoctest: +IGNORE_WANT("non-deterministic")
|
| 226 |
+
>>> list(
|
| 227 |
+
... WeightedRandomSampler(
|
| 228 |
+
... [0.1, 0.9, 0.4, 0.7, 3.0, 0.6], 5, replacement=True
|
| 229 |
+
... )
|
| 230 |
+
... )
|
| 231 |
+
[4, 4, 1, 4, 5]
|
| 232 |
+
>>> list(
|
| 233 |
+
... WeightedRandomSampler(
|
| 234 |
+
... [0.9, 0.4, 0.05, 0.2, 0.3, 0.1], 5, replacement=False
|
| 235 |
+
... )
|
| 236 |
+
... )
|
| 237 |
+
[0, 1, 4, 3, 2]
|
| 238 |
+
"""
|
| 239 |
+
|
| 240 |
+
weights: torch.Tensor
|
| 241 |
+
num_samples: int
|
| 242 |
+
replacement: bool
|
| 243 |
+
|
| 244 |
+
def __init__(
|
| 245 |
+
self,
|
| 246 |
+
weights: Sequence[float],
|
| 247 |
+
num_samples: int,
|
| 248 |
+
replacement: bool = True,
|
| 249 |
+
generator=None,
|
| 250 |
+
) -> None:
|
| 251 |
+
if (
|
| 252 |
+
not isinstance(num_samples, int)
|
| 253 |
+
or isinstance(num_samples, bool)
|
| 254 |
+
or num_samples <= 0
|
| 255 |
+
):
|
| 256 |
+
raise ValueError(
|
| 257 |
+
f"num_samples should be a positive integer value, but got num_samples={num_samples}"
|
| 258 |
+
)
|
| 259 |
+
if not isinstance(replacement, bool):
|
| 260 |
+
raise ValueError(
|
| 261 |
+
f"replacement should be a boolean value, but got replacement={replacement}"
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
weights_tensor = torch.as_tensor(weights, dtype=torch.double)
|
| 265 |
+
if len(weights_tensor.shape) != 1:
|
| 266 |
+
raise ValueError(
|
| 267 |
+
"weights should be a 1d sequence but given "
|
| 268 |
+
f"weights have shape {tuple(weights_tensor.shape)}"
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
self.weights = weights_tensor
|
| 272 |
+
self.num_samples = num_samples
|
| 273 |
+
self.replacement = replacement
|
| 274 |
+
self.generator = generator
|
| 275 |
+
|
| 276 |
+
def __iter__(self) -> Iterator[int]:
|
| 277 |
+
rand_tensor = torch.multinomial(
|
| 278 |
+
self.weights, self.num_samples, self.replacement, generator=self.generator
|
| 279 |
+
)
|
| 280 |
+
yield from iter(rand_tensor.tolist())
|
| 281 |
+
|
| 282 |
+
def __len__(self) -> int:
|
| 283 |
+
return self.num_samples
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class BatchSampler(Sampler[list[int]]):
|
| 287 |
+
r"""Wraps another sampler to yield a mini-batch of indices.
|
| 288 |
+
|
| 289 |
+
Args:
|
| 290 |
+
sampler (Sampler or Iterable): Base sampler. Can be any iterable object
|
| 291 |
+
batch_size (int): Size of mini-batch.
|
| 292 |
+
drop_last (bool): If ``True``, the sampler will drop the last batch if
|
| 293 |
+
its size would be less than ``batch_size``
|
| 294 |
+
|
| 295 |
+
Example:
|
| 296 |
+
>>> list(
|
| 297 |
+
... BatchSampler(
|
| 298 |
+
... SequentialSampler(range(10)), batch_size=3, drop_last=False
|
| 299 |
+
... )
|
| 300 |
+
... )
|
| 301 |
+
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
|
| 302 |
+
>>> list(
|
| 303 |
+
... BatchSampler(SequentialSampler(range(10)), batch_size=3, drop_last=True)
|
| 304 |
+
... )
|
| 305 |
+
[[0, 1, 2], [3, 4, 5], [6, 7, 8]]
|
| 306 |
+
"""
|
| 307 |
+
|
| 308 |
+
def __init__(
|
| 309 |
+
self,
|
| 310 |
+
sampler: Sampler[int] | Iterable[int],
|
| 311 |
+
batch_size: int,
|
| 312 |
+
drop_last: bool,
|
| 313 |
+
) -> None:
|
| 314 |
+
# Since collections.abc.Iterable does not check for `__getitem__`, which
|
| 315 |
+
# is one way for an object to be an iterable, we don't do an `isinstance`
|
| 316 |
+
# check here.
|
| 317 |
+
if (
|
| 318 |
+
not isinstance(batch_size, int)
|
| 319 |
+
or isinstance(batch_size, bool)
|
| 320 |
+
or batch_size <= 0
|
| 321 |
+
):
|
| 322 |
+
raise ValueError(
|
| 323 |
+
f"batch_size should be a positive integer value, but got batch_size={batch_size}"
|
| 324 |
+
)
|
| 325 |
+
if not isinstance(drop_last, bool):
|
| 326 |
+
raise ValueError(
|
| 327 |
+
f"drop_last should be a boolean value, but got drop_last={drop_last}"
|
| 328 |
+
)
|
| 329 |
+
self.sampler = sampler
|
| 330 |
+
self.batch_size = batch_size
|
| 331 |
+
self.drop_last = drop_last
|
| 332 |
+
|
| 333 |
+
def __iter__(self) -> Iterator[list[int]]:
|
| 334 |
+
sampler_iter = iter(self.sampler)
|
| 335 |
+
if self.drop_last:
|
| 336 |
+
# Create multiple references to the same iterator
|
| 337 |
+
args = [sampler_iter] * self.batch_size
|
| 338 |
+
for batch_droplast in zip(*args, strict=False):
|
| 339 |
+
yield [*batch_droplast]
|
| 340 |
+
else:
|
| 341 |
+
batch = [*itertools.islice(sampler_iter, self.batch_size)]
|
| 342 |
+
while batch:
|
| 343 |
+
yield batch
|
| 344 |
+
batch = [*itertools.islice(sampler_iter, self.batch_size)]
|
| 345 |
+
|
| 346 |
+
def __len__(self) -> int:
|
| 347 |
+
# Can only be called if self.sampler has __len__ implemented
|
| 348 |
+
# We cannot enforce this condition, so we turn off typechecking for the
|
| 349 |
+
# implementation below.
|
| 350 |
+
# Somewhat related: see NOTE [ Lack of Default `__len__` in Python Abstract Base Classes ]
|
| 351 |
+
if self.drop_last:
|
| 352 |
+
return len(self.sampler) // self.batch_size # type: ignore[arg-type]
|
| 353 |
+
else:
|
| 354 |
+
return (len(self.sampler) + self.batch_size - 1) // self.batch_size # type: ignore[arg-type]
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/deterministic.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import sys
|
| 3 |
+
import types
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class _Deterministic(types.ModuleType):
|
| 9 |
+
@property
|
| 10 |
+
def fill_uninitialized_memory(self):
|
| 11 |
+
"""
|
| 12 |
+
Whether to fill uninitialized memory with a known value when
|
| 13 |
+
:meth:`torch.use_deterministic_algorithms()` is set to ``True``.
|
| 14 |
+
"""
|
| 15 |
+
return torch._C._get_deterministic_fill_uninitialized_memory()
|
| 16 |
+
|
| 17 |
+
@fill_uninitialized_memory.setter
|
| 18 |
+
def fill_uninitialized_memory(self, mode):
|
| 19 |
+
return torch._C._set_deterministic_fill_uninitialized_memory(mode)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
sys.modules[__name__].__class__ = _Deterministic
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/dlpack.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import enum
|
| 5 |
+
|
| 6 |
+
from torch._C import _to_dlpack as to_dlpack
|
| 7 |
+
from torch.types import Device as _Device
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"DLDeviceType",
|
| 11 |
+
"from_dlpack",
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
class DLDeviceType(enum.IntEnum):
|
| 15 |
+
# Enums as in DLPack specification (aten/src/ATen/dlpack.h)
|
| 16 |
+
kDLCPU = 1,
|
| 17 |
+
kDLCUDA = 2,
|
| 18 |
+
kDLCUDAHost = 3,
|
| 19 |
+
kDLOpenCL = 4,
|
| 20 |
+
kDLVulkan = 7,
|
| 21 |
+
kDLMetal = 8,
|
| 22 |
+
kDLVPI = 9,
|
| 23 |
+
kDLROCM = 10,
|
| 24 |
+
kDLROCMHost = 11,
|
| 25 |
+
kDLExtDev = 12,
|
| 26 |
+
kDLCUDAManaged = 13,
|
| 27 |
+
kDLOneAPI = 14,
|
| 28 |
+
kDLWebGPU = 15,
|
| 29 |
+
kDLHexagon = 16,
|
| 30 |
+
kDLMAIA = 17,
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
torch._C._add_docstr(to_dlpack, r"""to_dlpack(tensor) -> PyCapsule
|
| 34 |
+
|
| 35 |
+
Returns an opaque object (a "DLPack capsule") representing the tensor.
|
| 36 |
+
|
| 37 |
+
.. note::
|
| 38 |
+
``to_dlpack`` is a legacy DLPack interface. The capsule it returns
|
| 39 |
+
cannot be used for anything in Python other than use it as input to
|
| 40 |
+
``from_dlpack``. The more idiomatic use of DLPack is to call
|
| 41 |
+
``from_dlpack`` directly on the tensor object - this works when that
|
| 42 |
+
object has a ``__dlpack__`` method, which PyTorch and most other
|
| 43 |
+
libraries indeed have now.
|
| 44 |
+
|
| 45 |
+
.. warning::
|
| 46 |
+
Only call ``from_dlpack`` once per capsule produced with ``to_dlpack``.
|
| 47 |
+
Behavior when a capsule is consumed multiple times is undefined.
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
tensor: a tensor to be exported
|
| 51 |
+
|
| 52 |
+
The DLPack capsule shares the tensor's memory.
|
| 53 |
+
""")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# TODO: add a typing.Protocol to be able to tell Mypy that only objects with
|
| 57 |
+
# __dlpack__ and __dlpack_device__ methods are accepted.
|
| 58 |
+
def from_dlpack(
|
| 59 |
+
ext_tensor: Any,
|
| 60 |
+
*,
|
| 61 |
+
device: _Device | None = None,
|
| 62 |
+
copy: bool | None = None
|
| 63 |
+
) -> 'torch.Tensor':
|
| 64 |
+
"""from_dlpack(ext_tensor) -> Tensor
|
| 65 |
+
|
| 66 |
+
Converts a tensor from an external library into a ``torch.Tensor``.
|
| 67 |
+
|
| 68 |
+
The returned PyTorch tensor will share the memory with the input tensor
|
| 69 |
+
(which may have come from another library). Note that in-place operations
|
| 70 |
+
will therefore also affect the data of the input tensor. This may lead to
|
| 71 |
+
unexpected issues (e.g., other libraries may have read-only flags or
|
| 72 |
+
immutable data structures), so the user should only do this if they know
|
| 73 |
+
for sure that this is fine.
|
| 74 |
+
|
| 75 |
+
Args:
|
| 76 |
+
ext_tensor (object with ``__dlpack__`` attribute, or a DLPack capsule):
|
| 77 |
+
The tensor or DLPack capsule to convert.
|
| 78 |
+
|
| 79 |
+
If ``ext_tensor`` is a tensor (or ndarray) object, it must support
|
| 80 |
+
the ``__dlpack__`` protocol (i.e., have a ``ext_tensor.__dlpack__``
|
| 81 |
+
method). Otherwise ``ext_tensor`` may be a DLPack capsule, which is
|
| 82 |
+
an opaque ``PyCapsule`` instance, typically produced by a
|
| 83 |
+
``to_dlpack`` function or method.
|
| 84 |
+
|
| 85 |
+
device (torch.device or str or None): An optional PyTorch device
|
| 86 |
+
specifying where to place the new tensor. If None (default), the
|
| 87 |
+
new tensor will be on the same device as ``ext_tensor``.
|
| 88 |
+
|
| 89 |
+
copy (bool or None): An optional boolean indicating whether or not to copy
|
| 90 |
+
``self``. If None, PyTorch will copy only if necessary.
|
| 91 |
+
|
| 92 |
+
Examples::
|
| 93 |
+
|
| 94 |
+
>>> import torch.utils.dlpack
|
| 95 |
+
>>> t = torch.arange(4)
|
| 96 |
+
|
| 97 |
+
# Convert a tensor directly (supported in PyTorch >= 1.10)
|
| 98 |
+
>>> t2 = torch.from_dlpack(t)
|
| 99 |
+
>>> t2[:2] = -1 # show that memory is shared
|
| 100 |
+
>>> t2
|
| 101 |
+
tensor([-1, -1, 2, 3])
|
| 102 |
+
>>> t
|
| 103 |
+
tensor([-1, -1, 2, 3])
|
| 104 |
+
|
| 105 |
+
# The old-style DLPack usage, with an intermediate capsule object
|
| 106 |
+
>>> capsule = torch.utils.dlpack.to_dlpack(t)
|
| 107 |
+
>>> capsule
|
| 108 |
+
<capsule object "dltensor" at ...>
|
| 109 |
+
>>> t3 = torch.from_dlpack(capsule)
|
| 110 |
+
>>> t3
|
| 111 |
+
tensor([-1, -1, 2, 3])
|
| 112 |
+
>>> t3[0] = -9 # now we're sharing memory between 3 tensors
|
| 113 |
+
>>> t3
|
| 114 |
+
tensor([-9, -1, 2, 3])
|
| 115 |
+
>>> t2
|
| 116 |
+
tensor([-9, -1, 2, 3])
|
| 117 |
+
>>> t
|
| 118 |
+
tensor([-9, -1, 2, 3])
|
| 119 |
+
|
| 120 |
+
"""
|
| 121 |
+
|
| 122 |
+
if hasattr(ext_tensor, '__dlpack__'):
|
| 123 |
+
# Only populate kwargs if any of the optional arguments are, in fact, not None. Otherwise,
|
| 124 |
+
# leave them out, since we might end up falling back to no-extra-kwargs __dlpack__ call.
|
| 125 |
+
kwargs: dict[str, Any] = {}
|
| 126 |
+
kwargs["max_version"] = (1, 0)
|
| 127 |
+
|
| 128 |
+
# Track copy request for potential manual handling
|
| 129 |
+
requested_copy = copy
|
| 130 |
+
producer_handled_copy = True
|
| 131 |
+
cross_device_transfer = False # Will be set to True if device transfer is needed
|
| 132 |
+
|
| 133 |
+
if copy is not None:
|
| 134 |
+
kwargs["copy"] = copy
|
| 135 |
+
|
| 136 |
+
# Parse the device parameter.
|
| 137 |
+
# At this moment, it can either be a torch.device or a str representing
|
| 138 |
+
# a torch.device, e.g. "cpu", "cuda", etc.
|
| 139 |
+
# Get source device first (we need it to detect cross-device transfers)
|
| 140 |
+
ext_device = ext_tensor.__dlpack_device__()
|
| 141 |
+
|
| 142 |
+
if device is not None:
|
| 143 |
+
if isinstance(device, str):
|
| 144 |
+
device = torch.device(device)
|
| 145 |
+
if not isinstance(device, torch.device):
|
| 146 |
+
raise AssertionError(f"from_dlpack: unsupported device type: {type(device)}")
|
| 147 |
+
|
| 148 |
+
# Convert target device to DLPack format
|
| 149 |
+
target_dl_device = torch._C._torchDeviceToDLDevice(device)
|
| 150 |
+
|
| 151 |
+
# Detect cross-device transfer by comparing source and target devices
|
| 152 |
+
# E.g. CPU->CUDA, cuda:0->cuda:1, etc.
|
| 153 |
+
cross_device_transfer = (ext_device != target_dl_device)
|
| 154 |
+
|
| 155 |
+
# Only pass dl_device to producer if NOT cross-device transfer
|
| 156 |
+
if not cross_device_transfer:
|
| 157 |
+
kwargs["dl_device"] = target_dl_device
|
| 158 |
+
|
| 159 |
+
# Cross-device transfer always requires a copy
|
| 160 |
+
if cross_device_transfer and copy is False:
|
| 161 |
+
raise ValueError(
|
| 162 |
+
f"cannot move DLPack tensor from device {ext_device} to {target_dl_device} "
|
| 163 |
+
"without copying. Set copy=None or copy=True."
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
# ext_device is either CUDA or ROCm, we need to pass the current
|
| 167 |
+
# stream
|
| 168 |
+
if ext_device[0] in (DLDeviceType.kDLCUDA, DLDeviceType.kDLROCM):
|
| 169 |
+
stream = torch.cuda.current_stream(f'cuda:{ext_device[1]}')
|
| 170 |
+
# cuda_stream is the pointer to the stream and it is a public
|
| 171 |
+
# attribute, but it is not documented
|
| 172 |
+
# The array API specify that the default legacy stream must be passed
|
| 173 |
+
# with a value of 1 for CUDA
|
| 174 |
+
# https://data-apis.org/array-api/latest/API_specification/array_object.html?dlpack-self-stream-none#dlpack-self-stream-none
|
| 175 |
+
is_cuda = ext_device[0] == DLDeviceType.kDLCUDA
|
| 176 |
+
# Since pytorch is not using PTDS by default, lets directly pass
|
| 177 |
+
# the legacy stream
|
| 178 |
+
stream_ptr = 1 if is_cuda and stream.cuda_stream == 0 else stream.cuda_stream
|
| 179 |
+
kwargs["stream"] = stream_ptr
|
| 180 |
+
|
| 181 |
+
# Try different parameter combinations until one works
|
| 182 |
+
dlpack = None
|
| 183 |
+
|
| 184 |
+
# Attempt 1: Try with all the parameters
|
| 185 |
+
try:
|
| 186 |
+
dlpack = ext_tensor.__dlpack__(**kwargs)
|
| 187 |
+
except TypeError:
|
| 188 |
+
pass
|
| 189 |
+
|
| 190 |
+
# Attempt 2: Remove max_version
|
| 191 |
+
if dlpack is None:
|
| 192 |
+
kwargs.pop("max_version", None)
|
| 193 |
+
try:
|
| 194 |
+
dlpack = ext_tensor.__dlpack__(**kwargs)
|
| 195 |
+
except TypeError:
|
| 196 |
+
pass
|
| 197 |
+
|
| 198 |
+
# Attempt 3: Remove copy
|
| 199 |
+
if dlpack is None:
|
| 200 |
+
kwargs.pop("copy", None)
|
| 201 |
+
producer_handled_copy = False
|
| 202 |
+
try:
|
| 203 |
+
dlpack = ext_tensor.__dlpack__(**kwargs)
|
| 204 |
+
except TypeError:
|
| 205 |
+
pass
|
| 206 |
+
|
| 207 |
+
# Attempt 4: Remove dl_device
|
| 208 |
+
if dlpack is None:
|
| 209 |
+
kwargs.pop("dl_device", None)
|
| 210 |
+
dlpack = ext_tensor.__dlpack__(**kwargs)
|
| 211 |
+
|
| 212 |
+
tensor = torch._C._from_dlpack(dlpack)
|
| 213 |
+
|
| 214 |
+
# Manual copy if producer didn't handle it (cross-device already copies via .to())
|
| 215 |
+
if requested_copy is True and not producer_handled_copy and not cross_device_transfer:
|
| 216 |
+
tensor = tensor.clone()
|
| 217 |
+
|
| 218 |
+
# Handle cross-device transfer by moving tensor to target device
|
| 219 |
+
if cross_device_transfer:
|
| 220 |
+
tensor = tensor.to(device)
|
| 221 |
+
|
| 222 |
+
return tensor
|
| 223 |
+
|
| 224 |
+
else:
|
| 225 |
+
if device is not None or copy is not None:
|
| 226 |
+
raise AssertionError(
|
| 227 |
+
"device and copy kwargs not supported when ext_tensor is already a DLPack capsule."
|
| 228 |
+
)
|
| 229 |
+
# Old versions just call the converter
|
| 230 |
+
dlpack = ext_tensor
|
| 231 |
+
return torch._C._from_dlpack(dlpack)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/file_baton.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import warnings
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class FileBaton:
|
| 8 |
+
"""A primitive, file-based synchronization utility."""
|
| 9 |
+
|
| 10 |
+
def __init__(self, lock_file_path, wait_seconds=0.1, warn_after_seconds=None) -> None:
|
| 11 |
+
"""
|
| 12 |
+
Create a new :class:`FileBaton`.
|
| 13 |
+
|
| 14 |
+
Args:
|
| 15 |
+
lock_file_path: The path to the file used for locking.
|
| 16 |
+
wait_seconds: The seconds to periodically sleep (spin) when
|
| 17 |
+
calling ``wait()``.
|
| 18 |
+
warn_after_seconds: The seconds to wait before showing
|
| 19 |
+
lock file path to warn existing lock file.
|
| 20 |
+
"""
|
| 21 |
+
self.lock_file_path = lock_file_path
|
| 22 |
+
self.wait_seconds = wait_seconds
|
| 23 |
+
self.fd = None
|
| 24 |
+
self.warn_after_seconds = warn_after_seconds
|
| 25 |
+
|
| 26 |
+
def try_acquire(self) -> bool | None:
|
| 27 |
+
"""
|
| 28 |
+
Try to atomically create a file under exclusive access.
|
| 29 |
+
|
| 30 |
+
Returns:
|
| 31 |
+
True if the file could be created, else False.
|
| 32 |
+
"""
|
| 33 |
+
try:
|
| 34 |
+
self.fd = os.open(self.lock_file_path, os.O_CREAT | os.O_EXCL)
|
| 35 |
+
return True
|
| 36 |
+
except FileExistsError:
|
| 37 |
+
return False
|
| 38 |
+
|
| 39 |
+
def wait(self) -> None:
|
| 40 |
+
"""
|
| 41 |
+
Periodically sleeps for a certain amount until the baton is released.
|
| 42 |
+
|
| 43 |
+
The amount of time slept depends on the ``wait_seconds`` parameter
|
| 44 |
+
passed to the constructor.
|
| 45 |
+
"""
|
| 46 |
+
has_warned = False
|
| 47 |
+
|
| 48 |
+
start_time = time.time()
|
| 49 |
+
while os.path.exists(self.lock_file_path):
|
| 50 |
+
time.sleep(self.wait_seconds)
|
| 51 |
+
|
| 52 |
+
if self.warn_after_seconds is not None:
|
| 53 |
+
if time.time() - start_time > self.warn_after_seconds and not has_warned:
|
| 54 |
+
warnings.warn(f'Waited on lock file "{self.lock_file_path}" for '
|
| 55 |
+
f'{self.warn_after_seconds} seconds.', stacklevel=2)
|
| 56 |
+
has_warned = True
|
| 57 |
+
|
| 58 |
+
def release(self) -> None:
|
| 59 |
+
"""Release the baton and removes its file."""
|
| 60 |
+
if self.fd is not None:
|
| 61 |
+
os.close(self.fd)
|
| 62 |
+
|
| 63 |
+
os.remove(self.lock_file_path)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/flop_counter.py
ADDED
|
@@ -0,0 +1,1017 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from types import NoneType
|
| 3 |
+
import logging
|
| 4 |
+
import torch
|
| 5 |
+
from torch.utils._pytree import tree_map, tree_flatten, tree_unflatten
|
| 6 |
+
from .module_tracker import ModuleTracker
|
| 7 |
+
from typing import Any, TypeVar
|
| 8 |
+
from collections.abc import Callable
|
| 9 |
+
from collections.abc import Iterator
|
| 10 |
+
from typing_extensions import ParamSpec
|
| 11 |
+
from collections import defaultdict
|
| 12 |
+
from torch.utils._python_dispatch import TorchDispatchMode
|
| 13 |
+
from math import prod
|
| 14 |
+
from functools import wraps
|
| 15 |
+
import warnings
|
| 16 |
+
|
| 17 |
+
__all__ = ["FlopCounterMode", "register_flop_formula"]
|
| 18 |
+
|
| 19 |
+
_T = TypeVar("_T")
|
| 20 |
+
_P = ParamSpec("_P")
|
| 21 |
+
|
| 22 |
+
log = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from triton.runtime.jit import JITFunction as _JITFunction
|
| 27 |
+
except ImportError:
|
| 28 |
+
if any(getattr(torch.version, attr, None) is not None for attr in ["cuda", "hip", "xpu"]):
|
| 29 |
+
log.warning("triton not found; flop counting will not work for triton kernels")
|
| 30 |
+
_JITFunction = NoneType
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
aten = torch.ops.aten
|
| 34 |
+
|
| 35 |
+
def get_shape(i):
|
| 36 |
+
if isinstance(i, torch.Tensor):
|
| 37 |
+
return i.shape
|
| 38 |
+
return i
|
| 39 |
+
|
| 40 |
+
flop_registry: dict[Any, Any] = {}
|
| 41 |
+
|
| 42 |
+
def shape_wrapper(f):
|
| 43 |
+
@wraps(f)
|
| 44 |
+
def nf(*args, out_val=None, **kwargs):
|
| 45 |
+
args, kwargs, out_shape = tree_map(get_shape, (args, kwargs, out_val))
|
| 46 |
+
return f(*args, out_shape=out_shape, **kwargs)
|
| 47 |
+
return nf
|
| 48 |
+
|
| 49 |
+
def register_flop_formula(targets, get_raw=False) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
|
| 50 |
+
|
| 51 |
+
def register_fun(flop_formula: Callable[_P, _T]) -> Callable[_P, _T]:
|
| 52 |
+
if not get_raw:
|
| 53 |
+
flop_formula = shape_wrapper(flop_formula)
|
| 54 |
+
|
| 55 |
+
def register(target) -> None:
|
| 56 |
+
if not (isinstance(target, (torch._ops.OpOverloadPacket, _JITFunction))):
|
| 57 |
+
raise ValueError(
|
| 58 |
+
f"register_flop_formula(targets): expected each target to be "
|
| 59 |
+
f"OpOverloadPacket (i.e. torch.ops.mylib.foo), or JitFunction"
|
| 60 |
+
f", got {target} which is of type {type(target)}")
|
| 61 |
+
if target in flop_registry:
|
| 62 |
+
raise RuntimeError(f"duplicate registrations for {target}")
|
| 63 |
+
flop_registry[target] = flop_formula
|
| 64 |
+
|
| 65 |
+
# To handle allowing multiple aten_ops at once
|
| 66 |
+
torch.utils._pytree.tree_map_(register, targets)
|
| 67 |
+
|
| 68 |
+
return flop_formula
|
| 69 |
+
|
| 70 |
+
return register_fun
|
| 71 |
+
|
| 72 |
+
@register_flop_formula(aten.mm)
|
| 73 |
+
def mm_flop(a_shape, b_shape, *args, out_shape=None, **kwargs) -> int:
|
| 74 |
+
"""Count flops for matmul."""
|
| 75 |
+
# Inputs should be a list of length 2.
|
| 76 |
+
# Inputs contains the shapes of two matrices.
|
| 77 |
+
m, k = a_shape
|
| 78 |
+
k2, n = b_shape
|
| 79 |
+
if k != k2:
|
| 80 |
+
raise AssertionError(f"matmul: inner dimensions must match (k == k2), got {k} and {k2}")
|
| 81 |
+
# NB(chilli): Should be 2 * k - 1 technically for FLOPs.
|
| 82 |
+
return m * n * 2 * k
|
| 83 |
+
|
| 84 |
+
@register_flop_formula(aten.addmm)
|
| 85 |
+
def addmm_flop(self_shape, a_shape, b_shape, out_shape=None, **kwargs) -> int:
|
| 86 |
+
"""Count flops for addmm."""
|
| 87 |
+
return mm_flop(a_shape, b_shape)
|
| 88 |
+
|
| 89 |
+
@register_flop_formula(aten.bmm)
|
| 90 |
+
def bmm_flop(a_shape, b_shape, out_shape=None, **kwargs) -> int:
|
| 91 |
+
"""Count flops for the bmm operation."""
|
| 92 |
+
# Inputs should be a list of length 2.
|
| 93 |
+
# Inputs contains the shapes of two tensor.
|
| 94 |
+
b, m, k = a_shape
|
| 95 |
+
b2, k2, n = b_shape
|
| 96 |
+
if b != b2:
|
| 97 |
+
raise AssertionError(f"bmm: batch dimensions must match (b == b2), got {b} and {b2}")
|
| 98 |
+
if k != k2:
|
| 99 |
+
raise AssertionError(f"bmm: inner dimensions must match (k == k2), got {k} and {k2}")
|
| 100 |
+
# NB(chilli): Should be 2 * k - 1 technically for FLOPs.
|
| 101 |
+
flop = b * m * n * 2 * k
|
| 102 |
+
return flop
|
| 103 |
+
|
| 104 |
+
@register_flop_formula(aten.baddbmm)
|
| 105 |
+
def baddbmm_flop(self_shape, a_shape, b_shape, out_shape=None, **kwargs) -> int:
|
| 106 |
+
"""Count flops for the baddbmm operation."""
|
| 107 |
+
# Inputs should be a list of length 3.
|
| 108 |
+
# Inputs contains the shapes of three tensors.
|
| 109 |
+
return bmm_flop(a_shape, b_shape)
|
| 110 |
+
|
| 111 |
+
@register_flop_formula(aten._scaled_mm)
|
| 112 |
+
def _scaled_mm_flop(
|
| 113 |
+
a_shape,
|
| 114 |
+
b_shape,
|
| 115 |
+
scale_a_shape,
|
| 116 |
+
scale_b_shape,
|
| 117 |
+
bias_shape=None,
|
| 118 |
+
scale_result_shape=None,
|
| 119 |
+
out_dtype=None,
|
| 120 |
+
use_fast_accum=False,
|
| 121 |
+
out_shape=None,
|
| 122 |
+
**kwargs,
|
| 123 |
+
) -> int:
|
| 124 |
+
"""Count flops for _scaled_mm."""
|
| 125 |
+
return mm_flop(a_shape, b_shape)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def conv_flop_count(
|
| 129 |
+
x_shape: list[int],
|
| 130 |
+
w_shape: list[int],
|
| 131 |
+
out_shape: list[int],
|
| 132 |
+
transposed: bool = False,
|
| 133 |
+
) -> int:
|
| 134 |
+
"""Count flops for convolution.
|
| 135 |
+
|
| 136 |
+
Note only multiplication is
|
| 137 |
+
counted. Computation for bias are ignored.
|
| 138 |
+
Flops for a transposed convolution are calculated as
|
| 139 |
+
flops = (x_shape[2:] * prod(w_shape) * batch_size).
|
| 140 |
+
Args:
|
| 141 |
+
x_shape (list(int)): The input shape before convolution.
|
| 142 |
+
w_shape (list(int)): The filter shape.
|
| 143 |
+
out_shape (list(int)): The output shape after convolution.
|
| 144 |
+
transposed (bool): is the convolution transposed
|
| 145 |
+
Returns:
|
| 146 |
+
int: the number of flops
|
| 147 |
+
"""
|
| 148 |
+
batch_size = x_shape[0]
|
| 149 |
+
conv_shape = (x_shape if transposed else out_shape)[2:]
|
| 150 |
+
c_out, c_in, *filter_size = w_shape
|
| 151 |
+
|
| 152 |
+
"""
|
| 153 |
+
General idea here is that for a regular conv, for each point in the output
|
| 154 |
+
spatial dimension we convolve the filter with something (hence
|
| 155 |
+
`prod(conv_shape) * prod(filter_size)` ops). Then, this gets multiplied by
|
| 156 |
+
1. batch_size, 2. the cross product of input and weight channels.
|
| 157 |
+
|
| 158 |
+
For the transpose, it's not each point in the *output* spatial dimension but
|
| 159 |
+
each point in the *input* spatial dimension.
|
| 160 |
+
"""
|
| 161 |
+
# NB(chilli): I don't think this properly accounts for padding :think:
|
| 162 |
+
# NB(chilli): Should be 2 * c_in - 1 technically for FLOPs.
|
| 163 |
+
flop = prod(conv_shape) * prod(filter_size) * batch_size * c_out * c_in * 2
|
| 164 |
+
return flop
|
| 165 |
+
|
| 166 |
+
@register_flop_formula([aten.convolution,
|
| 167 |
+
aten._convolution,
|
| 168 |
+
aten.cudnn_convolution,
|
| 169 |
+
aten._slow_conv2d_forward,
|
| 170 |
+
aten.convolution_overrideable])
|
| 171 |
+
def conv_flop(x_shape, w_shape, _bias, _stride, _padding, _dilation, transposed, *args, out_shape=None, **kwargs) -> int:
|
| 172 |
+
"""Count flops for convolution."""
|
| 173 |
+
# pyrefly: ignore [bad-argument-type]
|
| 174 |
+
return conv_flop_count(x_shape, w_shape, out_shape, transposed=transposed)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
@register_flop_formula(aten.convolution_backward)
|
| 178 |
+
def conv_backward_flop(
|
| 179 |
+
grad_out_shape,
|
| 180 |
+
x_shape,
|
| 181 |
+
w_shape,
|
| 182 |
+
_bias,
|
| 183 |
+
_stride,
|
| 184 |
+
_padding,
|
| 185 |
+
_dilation,
|
| 186 |
+
transposed,
|
| 187 |
+
_output_padding,
|
| 188 |
+
_groups,
|
| 189 |
+
output_mask,
|
| 190 |
+
out_shape) -> int:
|
| 191 |
+
|
| 192 |
+
def t(shape):
|
| 193 |
+
return [shape[1], shape[0]] + list(shape[2:])
|
| 194 |
+
flop_count = 0
|
| 195 |
+
|
| 196 |
+
"""
|
| 197 |
+
Let's say we have a regular 1D conv
|
| 198 |
+
{A, B, C} [inp]
|
| 199 |
+
{i, j} [weight]
|
| 200 |
+
=> (conv)
|
| 201 |
+
{Ai + Bj, Bi + Cj} [out]
|
| 202 |
+
|
| 203 |
+
And as a reminder, the transposed conv of the above is
|
| 204 |
+
=> {Ai, Aj + Bi, Bj + Ci, Cj} [transposed conv out]
|
| 205 |
+
|
| 206 |
+
For the backwards of conv, we now have
|
| 207 |
+
{D, E} [grad_out]
|
| 208 |
+
{A, B, C} [inp]
|
| 209 |
+
{i, j} [weight]
|
| 210 |
+
|
| 211 |
+
# grad_inp as conv_transpose(grad_out, weight)
|
| 212 |
+
Let's first compute grad_inp. To do so, we can simply look at all the
|
| 213 |
+
multiplications that each element of inp is involved in. For example, A is
|
| 214 |
+
only involved in the first element of the output (and thus only depends upon
|
| 215 |
+
D in grad_out), and C is only involved in the last element of the output
|
| 216 |
+
(and thus only depends upon E in grad_out)
|
| 217 |
+
|
| 218 |
+
{Di, Dj + Ei, Ej} [grad_inp]
|
| 219 |
+
|
| 220 |
+
Note that this corresponds to the below conv_transpose. This gives us the
|
| 221 |
+
output_mask[0] branch, which is grad_inp.
|
| 222 |
+
|
| 223 |
+
{D, E} [inp (grad_out)]
|
| 224 |
+
{i, j} [weight]
|
| 225 |
+
=> (conv_transpose)
|
| 226 |
+
{Di, Dj + Ei, Ej} [out (grad_inp)]
|
| 227 |
+
|
| 228 |
+
I leave the fact that grad_inp for a transposed conv is just conv(grad_out,
|
| 229 |
+
weight) as an exercise for the reader.
|
| 230 |
+
|
| 231 |
+
# grad_weight as conv(inp, grad_out)
|
| 232 |
+
To compute grad_weight, we again look at the terms in the output, which as
|
| 233 |
+
a reminder is:
|
| 234 |
+
=> {Ai + Bj, Bi + Cj} [out]
|
| 235 |
+
=> {D, E} [grad_out]
|
| 236 |
+
If we manually compute the gradient for the weights, we see it's
|
| 237 |
+
{AD + BE, BD + CE} [grad_weight]
|
| 238 |
+
|
| 239 |
+
This corresponds to the below conv
|
| 240 |
+
{A, B, C} [inp]
|
| 241 |
+
{D, E} [weight (grad_out)]
|
| 242 |
+
=> (conv)
|
| 243 |
+
{AD + BE, BD + CE} [out (grad_weight)]
|
| 244 |
+
|
| 245 |
+
# grad_weight of transposed conv as conv(grad_out, inp)
|
| 246 |
+
As a reminder, the terms of the output of a transposed conv are:
|
| 247 |
+
=> {Ai, Aj + Bi, Bj + Ci, Cj} [transposed conv out]
|
| 248 |
+
=> {D, E, F, G} [grad_out]
|
| 249 |
+
|
| 250 |
+
Manually computing the gradient for the weights, we see it's
|
| 251 |
+
{AD + BE + CF, AE + BF + CG} [grad_weight]
|
| 252 |
+
|
| 253 |
+
This corresponds to the below conv
|
| 254 |
+
{D, E, F, G} [inp (grad_out)]
|
| 255 |
+
{A, B, C} [weight (inp)]
|
| 256 |
+
=> (conv)
|
| 257 |
+
{AD + BE + CF, AE + BF + CG} [out (grad_weight)]
|
| 258 |
+
|
| 259 |
+
For the full backwards formula, there are also some details involving
|
| 260 |
+
transpose of the batch/channel dimensions and groups, but I skip those for
|
| 261 |
+
the sake of brevity (and they're pretty similar to matmul backwards)
|
| 262 |
+
|
| 263 |
+
Check [conv backwards decomposition as conv forwards]
|
| 264 |
+
"""
|
| 265 |
+
# grad_inp as conv_transpose(grad_out, weight)
|
| 266 |
+
if output_mask[0]:
|
| 267 |
+
grad_input_shape = get_shape(out_shape[0])
|
| 268 |
+
flop_count += conv_flop_count(grad_out_shape, w_shape, grad_input_shape, not transposed)
|
| 269 |
+
|
| 270 |
+
if output_mask[1]:
|
| 271 |
+
grad_weight_shape = get_shape(out_shape[1])
|
| 272 |
+
if transposed:
|
| 273 |
+
# grad_weight of transposed conv as conv(grad_out, inp)
|
| 274 |
+
flop_count += conv_flop_count(t(grad_out_shape), t(x_shape), t(grad_weight_shape), transposed=False)
|
| 275 |
+
else:
|
| 276 |
+
# grad_weight as conv(inp, grad_out)
|
| 277 |
+
flop_count += conv_flop_count(t(x_shape), t(grad_out_shape), t(grad_weight_shape), transposed=False)
|
| 278 |
+
|
| 279 |
+
return flop_count
|
| 280 |
+
|
| 281 |
+
def sdpa_flop_count(query_shape, key_shape, value_shape):
|
| 282 |
+
"""
|
| 283 |
+
Count flops for self-attention.
|
| 284 |
+
|
| 285 |
+
Supports GQA (grouped-query attention) where key/value have fewer heads
|
| 286 |
+
than the query. The kernel broadcasts KV heads to match query heads.
|
| 287 |
+
"""
|
| 288 |
+
b, h_q, s_q, d_q = query_shape
|
| 289 |
+
_b2, h_kv, s_k, _d2 = key_shape
|
| 290 |
+
_b3, _h3, _s3, d_v = value_shape
|
| 291 |
+
if not (b == _b2 == _b3 and h_kv == _h3 and d_q == _d2 and s_k == _s3):
|
| 292 |
+
raise AssertionError(
|
| 293 |
+
f"sdpa_flop_count: query/key/value shapes are incompatible: "
|
| 294 |
+
f"q={query_shape}, k={key_shape}, v={value_shape}"
|
| 295 |
+
)
|
| 296 |
+
if h_q < h_kv or h_q % h_kv != 0:
|
| 297 |
+
raise AssertionError(
|
| 298 |
+
f"sdpa_flop_count: query heads ({h_q}) must be a multiple of "
|
| 299 |
+
f"key/value heads ({h_kv})"
|
| 300 |
+
)
|
| 301 |
+
total_flops = 0
|
| 302 |
+
# q: [b, h_q, s_q, d_q] @ k: [b, h_q, d_q, s_k] -> scores: [b, h_q, s_q, s_k]
|
| 303 |
+
total_flops += bmm_flop((b * h_q, s_q, d_q), (b * h_q, d_q, s_k))
|
| 304 |
+
# scores: [b, h_q, s_q, s_k] @ v: [b, h_q, s_k, d_v] -> out: [b, h_q, s_q, d_v]
|
| 305 |
+
total_flops += bmm_flop((b * h_q, s_q, s_k), (b * h_q, s_k, d_v))
|
| 306 |
+
return total_flops
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
@register_flop_formula([aten._scaled_dot_product_efficient_attention,
|
| 310 |
+
aten._scaled_dot_product_flash_attention,
|
| 311 |
+
aten._scaled_dot_product_cudnn_attention])
|
| 312 |
+
def sdpa_flop(query_shape, key_shape, value_shape, *args, out_shape=None, **kwargs) -> int:
|
| 313 |
+
"""Count flops for self-attention."""
|
| 314 |
+
# NB: We aren't accounting for causal attention here
|
| 315 |
+
return sdpa_flop_count(query_shape, key_shape, value_shape)
|
| 316 |
+
|
| 317 |
+
|
| 318 |
+
def _offsets_to_lengths(offsets, max_len):
|
| 319 |
+
"""
|
| 320 |
+
If the offsets tensor is fake, then we don't know the actual lengths.
|
| 321 |
+
In that case, we can just assume the worst case; each batch has max length.
|
| 322 |
+
"""
|
| 323 |
+
from torch._subclasses.fake_tensor import FakeTensor
|
| 324 |
+
from torch._subclasses.functional_tensor import FunctionalTensor
|
| 325 |
+
if not isinstance(offsets, (FakeTensor, FunctionalTensor)) and offsets.device.type != "meta":
|
| 326 |
+
return offsets.diff().tolist()
|
| 327 |
+
return [max_len] * (offsets.size(0) - 1)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def _unpack_flash_attention_nested_shapes(
|
| 331 |
+
*,
|
| 332 |
+
query,
|
| 333 |
+
key,
|
| 334 |
+
value,
|
| 335 |
+
grad_out=None,
|
| 336 |
+
cum_seq_q,
|
| 337 |
+
cum_seq_k,
|
| 338 |
+
max_q,
|
| 339 |
+
max_k,
|
| 340 |
+
) -> Iterator[tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...] | None]]:
|
| 341 |
+
"""
|
| 342 |
+
Given inputs to a flash_attention_(forward|backward) kernel, this will handle behavior for
|
| 343 |
+
NestedTensor inputs by effectively unbinding the NestedTensor and yielding the shapes for
|
| 344 |
+
each batch element.
|
| 345 |
+
|
| 346 |
+
In the case that this isn't a NestedTensor kernel, then it just yields the original shapes.
|
| 347 |
+
"""
|
| 348 |
+
if cum_seq_q is not None:
|
| 349 |
+
# This means we should be dealing with a Nested Jagged Tensor query.
|
| 350 |
+
# The inputs will have shape (sum(sequence len), heads, dimension)
|
| 351 |
+
# In comparison, non-Nested inputs have shape (batch, heads, sequence len, dimension)
|
| 352 |
+
# To deal with this, we convert to a shape of (batch, heads, max_seq_len, dimension)
|
| 353 |
+
# So the flops calculation in this case is an overestimate of the actual flops.
|
| 354 |
+
if len(key.shape) != 3:
|
| 355 |
+
raise AssertionError("sdpa_flop_count: expected key.shape to be 3-dimensional")
|
| 356 |
+
if len(value.shape) != 3:
|
| 357 |
+
raise AssertionError("sdpa_flop_count: expected value.shape to be 3-dimensional")
|
| 358 |
+
if grad_out is not None and grad_out.shape != query.shape:
|
| 359 |
+
raise AssertionError("sdpa_flop_count: grad_out.shape must match query.shape when provided")
|
| 360 |
+
_, h_q, d_q = query.shape
|
| 361 |
+
_, h_k, d_k = key.shape
|
| 362 |
+
_, h_v, d_v = value.shape
|
| 363 |
+
if cum_seq_q is None:
|
| 364 |
+
raise AssertionError("sdpa_flop_count: cum_seq_q must not be None")
|
| 365 |
+
if cum_seq_k is None:
|
| 366 |
+
raise AssertionError("sdpa_flop_count: cum_seq_k must not be None")
|
| 367 |
+
if cum_seq_q.shape != cum_seq_k.shape:
|
| 368 |
+
raise AssertionError("sdpa_flop_count: cum_seq_q and cum_seq_k must have the same shape")
|
| 369 |
+
seq_q_lengths = _offsets_to_lengths(cum_seq_q, max_q)
|
| 370 |
+
seq_k_lengths = _offsets_to_lengths(cum_seq_k, max_k)
|
| 371 |
+
for (seq_q_len, seq_k_len) in zip(seq_q_lengths, seq_k_lengths, strict=True):
|
| 372 |
+
new_query_shape = (1, h_q, seq_q_len, d_q)
|
| 373 |
+
new_key_shape = (1, h_k, seq_k_len, d_k)
|
| 374 |
+
new_value_shape = (1, h_v, seq_k_len, d_v)
|
| 375 |
+
new_grad_out_shape = new_query_shape if grad_out is not None else None
|
| 376 |
+
yield new_query_shape, new_key_shape, new_value_shape, new_grad_out_shape
|
| 377 |
+
return
|
| 378 |
+
|
| 379 |
+
yield query.shape, key.shape, value.shape, grad_out.shape if grad_out is not None else None
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def _unpack_efficient_attention_nested_shapes(
|
| 383 |
+
*,
|
| 384 |
+
query,
|
| 385 |
+
key,
|
| 386 |
+
value,
|
| 387 |
+
grad_out=None,
|
| 388 |
+
cu_seqlens_q,
|
| 389 |
+
cu_seqlens_k,
|
| 390 |
+
max_seqlen_q,
|
| 391 |
+
max_seqlen_k,
|
| 392 |
+
) -> Iterator[tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...] | None]]:
|
| 393 |
+
"""
|
| 394 |
+
Given inputs to a efficient_attention_(forward|backward) kernel, this will handle behavior for
|
| 395 |
+
NestedTensor inputs by effectively unbinding the NestedTensor and yielding the shapes for
|
| 396 |
+
each batch element.
|
| 397 |
+
|
| 398 |
+
In the case that this isn't a NestedTensor kernel, then it just yields the original shapes.
|
| 399 |
+
"""
|
| 400 |
+
if cu_seqlens_q is not None:
|
| 401 |
+
# Unlike flash_attention_forward, we get a 4D tensor instead of a 3D tensor for efficient attention.
|
| 402 |
+
#
|
| 403 |
+
# This means we should be dealing with a Nested Jagged Tensor query.
|
| 404 |
+
# The inputs will have shape (sum(sequence len), heads, dimension)
|
| 405 |
+
# In comparison, non-Nested inputs have shape (batch, heads, sequence len, dimension)
|
| 406 |
+
# To deal with this, we convert to a shape of (batch, heads, max_seq_len, dimension)
|
| 407 |
+
# So the flops calculation in this case is an overestimate of the actual flops.
|
| 408 |
+
if len(key.shape) != 4:
|
| 409 |
+
raise AssertionError("_unpack_efficient_attention_nested_shapes: expected key.shape to be 4-dimensional")
|
| 410 |
+
if len(value.shape) != 4:
|
| 411 |
+
raise AssertionError("_unpack_efficient_attention_nested_shapes: expected value.shape to be 4-dimensional")
|
| 412 |
+
if grad_out is not None and grad_out.shape != query.shape:
|
| 413 |
+
raise AssertionError("_unpack_efficient_attention_nested_shapes: grad_out.shape must match query.shape when provided")
|
| 414 |
+
_, _, h_q, d_q = query.shape
|
| 415 |
+
_, _, h_k, d_k = key.shape
|
| 416 |
+
_, _, h_v, d_v = value.shape
|
| 417 |
+
if cu_seqlens_q is None:
|
| 418 |
+
raise AssertionError("_unpack_efficient_attention_nested_shapes: cu_seqlens_q must not be None")
|
| 419 |
+
if cu_seqlens_k is None:
|
| 420 |
+
raise AssertionError("_unpack_efficient_attention_nested_shapes: cu_seqlens_k must not be None")
|
| 421 |
+
if cu_seqlens_q.shape != cu_seqlens_k.shape:
|
| 422 |
+
raise AssertionError("_unpack_efficient_attention_nested_shapes: "
|
| 423 |
+
"cu_seqlens_q and cu_seqlens_k must have the same shape")
|
| 424 |
+
seqlens_q = _offsets_to_lengths(cu_seqlens_q, max_seqlen_q)
|
| 425 |
+
seqlens_k = _offsets_to_lengths(cu_seqlens_k, max_seqlen_k)
|
| 426 |
+
for len_q, len_k in zip(seqlens_q, seqlens_k, strict=True):
|
| 427 |
+
new_query_shape = (1, h_q, len_q, d_q)
|
| 428 |
+
new_key_shape = (1, h_k, len_k, d_k)
|
| 429 |
+
new_value_shape = (1, h_v, len_k, d_v)
|
| 430 |
+
new_grad_out_shape = new_query_shape if grad_out is not None else None
|
| 431 |
+
yield new_query_shape, new_key_shape, new_value_shape, new_grad_out_shape
|
| 432 |
+
return
|
| 433 |
+
|
| 434 |
+
yield query.shape, key.shape, value.shape, grad_out.shape if grad_out is not None else None
|
| 435 |
+
|
| 436 |
+
|
| 437 |
+
@register_flop_formula(aten._flash_attention_forward, get_raw=True)
|
| 438 |
+
def _flash_attention_forward_flop(
|
| 439 |
+
query,
|
| 440 |
+
key,
|
| 441 |
+
value,
|
| 442 |
+
cum_seq_q,
|
| 443 |
+
cum_seq_k,
|
| 444 |
+
max_q,
|
| 445 |
+
max_k,
|
| 446 |
+
*args,
|
| 447 |
+
out_shape=None,
|
| 448 |
+
**kwargs
|
| 449 |
+
) -> int:
|
| 450 |
+
"""Count flops for self-attention."""
|
| 451 |
+
# NB: We aren't accounting for causal attention here
|
| 452 |
+
# in case this is a nested tensor, we unpack the individual batch elements
|
| 453 |
+
# and then sum the flops per batch element
|
| 454 |
+
sizes = _unpack_flash_attention_nested_shapes(
|
| 455 |
+
query=query,
|
| 456 |
+
key=key,
|
| 457 |
+
value=value,
|
| 458 |
+
cum_seq_q=cum_seq_q,
|
| 459 |
+
cum_seq_k=cum_seq_k,
|
| 460 |
+
max_q=max_q,
|
| 461 |
+
max_k=max_k,
|
| 462 |
+
)
|
| 463 |
+
return sum(
|
| 464 |
+
sdpa_flop_count(query_shape, key_shape, value_shape)
|
| 465 |
+
for query_shape, key_shape, value_shape, _ in sizes
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
@register_flop_formula(aten._efficient_attention_forward, get_raw=True)
|
| 470 |
+
def _efficient_attention_forward_flop(
|
| 471 |
+
query,
|
| 472 |
+
key,
|
| 473 |
+
value,
|
| 474 |
+
bias,
|
| 475 |
+
cu_seqlens_q,
|
| 476 |
+
cu_seqlens_k,
|
| 477 |
+
max_seqlen_q,
|
| 478 |
+
max_seqlen_k,
|
| 479 |
+
*args,
|
| 480 |
+
**kwargs
|
| 481 |
+
) -> int:
|
| 482 |
+
"""Count flops for self-attention."""
|
| 483 |
+
# NB: We aren't accounting for causal attention here
|
| 484 |
+
# in case this is a nested tensor, we unpack the individual batch elements
|
| 485 |
+
# and then sum the flops per batch element
|
| 486 |
+
sizes = _unpack_efficient_attention_nested_shapes(
|
| 487 |
+
query=query,
|
| 488 |
+
key=key,
|
| 489 |
+
value=value,
|
| 490 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 491 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 492 |
+
max_seqlen_q=max_seqlen_q,
|
| 493 |
+
max_seqlen_k=max_seqlen_k,
|
| 494 |
+
)
|
| 495 |
+
return sum(
|
| 496 |
+
sdpa_flop_count(query_shape, key_shape, value_shape)
|
| 497 |
+
for query_shape, key_shape, value_shape, _ in sizes
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def sdpa_backward_flop_count(grad_out_shape, query_shape, key_shape, value_shape):
|
| 502 |
+
b, h_q, s_q, d_q = query_shape
|
| 503 |
+
_b2, h_kv, s_k, _d2 = key_shape
|
| 504 |
+
_b3, _h3, _s3, d_v = value_shape
|
| 505 |
+
_b4, _h4, _s4, _d4 = grad_out_shape
|
| 506 |
+
if not (b == _b2 == _b3 == _b4 and h_kv == _h3 and h_q == _h4):
|
| 507 |
+
raise AssertionError(
|
| 508 |
+
"sdpa_backward_flop_count: batch/heads mismatch among tensors"
|
| 509 |
+
)
|
| 510 |
+
if h_q < h_kv or h_q % h_kv != 0:
|
| 511 |
+
raise AssertionError(
|
| 512 |
+
f"sdpa_backward_flop_count: query heads ({h_q}) must be a multiple of "
|
| 513 |
+
f"key/value heads ({h_kv})"
|
| 514 |
+
)
|
| 515 |
+
if not (d_q == _d2 and d_v == _d4 and s_k == _s3 and s_q == _s4):
|
| 516 |
+
raise AssertionError(
|
| 517 |
+
"sdpa_backward_flop_count: grad_out/value/key/query shapes are incompatible"
|
| 518 |
+
)
|
| 519 |
+
total_flops = 0
|
| 520 |
+
# Step 1: We recompute the scores matrix.
|
| 521 |
+
# q: [b, h_q, s_q, d_q] @ k: [b, h_q, d_q, s_k] -> scores: [b, h_q, s_q, s_k]
|
| 522 |
+
total_flops += bmm_flop((b * h_q, s_q, d_q), (b * h_q, d_q, s_k))
|
| 523 |
+
|
| 524 |
+
# Step 2: We propagate the gradients through the score @ v operation.
|
| 525 |
+
# gradOut: [b, h_q, s_q, d_v] @ v: [b, h_q, d_v, s_k] -> gradScores: [b, h_q, s_q, s_k]
|
| 526 |
+
total_flops += bmm_flop((b * h_q, s_q, d_v), (b * h_q, d_v, s_k))
|
| 527 |
+
# scores: [b, h_q, s_k, s_q] @ gradOut: [b, h_q, s_q, d_v] -> gradV: [b, h_q, s_k, d_v]
|
| 528 |
+
total_flops += bmm_flop((b * h_q, s_k, s_q), (b * h_q, s_q, d_v))
|
| 529 |
+
|
| 530 |
+
# Step 3: We propagate th gradients through the k @ v operation
|
| 531 |
+
# gradScores: [b, h_q, s_q, s_k] @ k: [b, h_q, s_k, d_q] -> gradQ: [b, h_q, s_q, d_q]
|
| 532 |
+
total_flops += bmm_flop((b * h_q, s_q, s_k), (b * h_q, s_k, d_q))
|
| 533 |
+
# q: [b, h_q, d_q, s_q] @ gradScores: [b, h_q, s_q, s_k] -> gradK: [b, h_q, d_q, s_k]
|
| 534 |
+
total_flops += bmm_flop((b * h_q, d_q, s_q), (b * h_q, s_q, s_k))
|
| 535 |
+
return total_flops
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
@register_flop_formula([aten._scaled_dot_product_efficient_attention_backward,
|
| 539 |
+
aten._scaled_dot_product_flash_attention_backward,
|
| 540 |
+
aten._scaled_dot_product_cudnn_attention_backward])
|
| 541 |
+
def sdpa_backward_flop(grad_out_shape, query_shape, key_shape, value_shape, *args, out_shape=None, **kwargs) -> int:
|
| 542 |
+
"""Count flops for self-attention backward."""
|
| 543 |
+
return sdpa_backward_flop_count(grad_out_shape, query_shape, key_shape, value_shape)
|
| 544 |
+
|
| 545 |
+
@register_flop_formula(aten._flash_attention_backward, get_raw=True)
|
| 546 |
+
def _flash_attention_backward_flop(
|
| 547 |
+
grad_out,
|
| 548 |
+
query,
|
| 549 |
+
key,
|
| 550 |
+
value,
|
| 551 |
+
out, # named _out_shape to avoid kwarg collision with out_shape created in wrapper
|
| 552 |
+
logsumexp,
|
| 553 |
+
cum_seq_q,
|
| 554 |
+
cum_seq_k,
|
| 555 |
+
max_q,
|
| 556 |
+
max_k,
|
| 557 |
+
*args,
|
| 558 |
+
**kwargs,
|
| 559 |
+
) -> int:
|
| 560 |
+
# in case this is a nested tensor, we unpack the individual batch elements
|
| 561 |
+
# and then sum the flops per batch element
|
| 562 |
+
shapes = _unpack_flash_attention_nested_shapes(
|
| 563 |
+
query=query,
|
| 564 |
+
key=key,
|
| 565 |
+
value=value,
|
| 566 |
+
grad_out=grad_out,
|
| 567 |
+
cum_seq_q=cum_seq_q,
|
| 568 |
+
cum_seq_k=cum_seq_k,
|
| 569 |
+
max_q=max_q,
|
| 570 |
+
max_k=max_k,
|
| 571 |
+
)
|
| 572 |
+
return sum(
|
| 573 |
+
sdpa_backward_flop_count(grad_out_shape, query_shape, key_shape, value_shape)
|
| 574 |
+
for query_shape, key_shape, value_shape, grad_out_shape in shapes
|
| 575 |
+
)
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
@register_flop_formula(aten._efficient_attention_backward, get_raw=True)
|
| 579 |
+
def _efficient_attention_backward_flop(
|
| 580 |
+
grad_out,
|
| 581 |
+
query,
|
| 582 |
+
key,
|
| 583 |
+
value,
|
| 584 |
+
bias,
|
| 585 |
+
out, # named _out to avoid kwarg collision with out created in wrapper
|
| 586 |
+
cu_seqlens_q,
|
| 587 |
+
cu_seqlens_k,
|
| 588 |
+
max_seqlen_q,
|
| 589 |
+
max_seqlen_k,
|
| 590 |
+
*args,
|
| 591 |
+
**kwargs,
|
| 592 |
+
) -> int:
|
| 593 |
+
# in case this is a nested tensor, we unpack the individual batch elements
|
| 594 |
+
# and then sum the flops per batch element
|
| 595 |
+
shapes = _unpack_efficient_attention_nested_shapes(
|
| 596 |
+
query=query,
|
| 597 |
+
key=key,
|
| 598 |
+
value=value,
|
| 599 |
+
grad_out=grad_out,
|
| 600 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 601 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 602 |
+
max_seqlen_q=max_seqlen_q,
|
| 603 |
+
max_seqlen_k=max_seqlen_k,
|
| 604 |
+
)
|
| 605 |
+
return sum(
|
| 606 |
+
sdpa_backward_flop_count(grad_out_shape, query_shape, key_shape, value_shape)
|
| 607 |
+
for query_shape, key_shape, value_shape, grad_out_shape in shapes
|
| 608 |
+
)
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def _varlen_attn_forward_flop(
|
| 612 |
+
query,
|
| 613 |
+
key,
|
| 614 |
+
value,
|
| 615 |
+
cu_seq_q,
|
| 616 |
+
cu_seq_k,
|
| 617 |
+
max_q,
|
| 618 |
+
max_k,
|
| 619 |
+
*args,
|
| 620 |
+
out_val=None,
|
| 621 |
+
**kwargs,
|
| 622 |
+
) -> int:
|
| 623 |
+
"""Count flops for varlen_attn forward."""
|
| 624 |
+
sizes = _unpack_flash_attention_nested_shapes(
|
| 625 |
+
query=query,
|
| 626 |
+
key=key,
|
| 627 |
+
value=value,
|
| 628 |
+
cum_seq_q=cu_seq_q,
|
| 629 |
+
cum_seq_k=cu_seq_k if cu_seq_k is not None else cu_seq_q,
|
| 630 |
+
max_q=max_q,
|
| 631 |
+
max_k=max_k,
|
| 632 |
+
)
|
| 633 |
+
return sum(
|
| 634 |
+
sdpa_flop_count(query_shape, key_shape, value_shape)
|
| 635 |
+
for query_shape, key_shape, value_shape, _ in sizes
|
| 636 |
+
)
|
| 637 |
+
|
| 638 |
+
|
| 639 |
+
def _varlen_attn_out_flop(
|
| 640 |
+
out,
|
| 641 |
+
query,
|
| 642 |
+
key,
|
| 643 |
+
value,
|
| 644 |
+
cu_seq_q,
|
| 645 |
+
cu_seq_k,
|
| 646 |
+
max_q,
|
| 647 |
+
max_k,
|
| 648 |
+
*args,
|
| 649 |
+
out_val=None,
|
| 650 |
+
**kwargs,
|
| 651 |
+
) -> int:
|
| 652 |
+
"""Count flops for varlen_attn_out forward."""
|
| 653 |
+
return _varlen_attn_forward_flop(
|
| 654 |
+
query, key, value, cu_seq_q, cu_seq_k, max_q, max_k,
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
|
| 658 |
+
def _varlen_attn_backward_flop(
|
| 659 |
+
grad_out,
|
| 660 |
+
query,
|
| 661 |
+
key,
|
| 662 |
+
value,
|
| 663 |
+
out,
|
| 664 |
+
lse,
|
| 665 |
+
cu_seq_q,
|
| 666 |
+
cu_seq_k,
|
| 667 |
+
max_q,
|
| 668 |
+
max_k,
|
| 669 |
+
*args,
|
| 670 |
+
out_val=None,
|
| 671 |
+
**kwargs,
|
| 672 |
+
) -> int:
|
| 673 |
+
"""Count flops for varlen_attn backward."""
|
| 674 |
+
sizes = _unpack_flash_attention_nested_shapes(
|
| 675 |
+
query=query,
|
| 676 |
+
key=key,
|
| 677 |
+
value=value,
|
| 678 |
+
grad_out=grad_out,
|
| 679 |
+
cum_seq_q=cu_seq_q,
|
| 680 |
+
cum_seq_k=cu_seq_k,
|
| 681 |
+
max_q=max_q,
|
| 682 |
+
max_k=max_k,
|
| 683 |
+
)
|
| 684 |
+
return sum(
|
| 685 |
+
sdpa_backward_flop_count(grad_out_shape, query_shape, key_shape, value_shape)
|
| 686 |
+
for query_shape, key_shape, value_shape, grad_out_shape in sizes
|
| 687 |
+
)
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
flop_registry = {
|
| 691 |
+
aten.mm: mm_flop,
|
| 692 |
+
aten.addmm: addmm_flop,
|
| 693 |
+
aten.bmm: bmm_flop,
|
| 694 |
+
aten.baddbmm: baddbmm_flop,
|
| 695 |
+
aten._scaled_mm: _scaled_mm_flop,
|
| 696 |
+
aten.convolution: conv_flop,
|
| 697 |
+
aten._convolution: conv_flop,
|
| 698 |
+
aten.cudnn_convolution: conv_flop,
|
| 699 |
+
aten.convolution_overrideable: conv_flop,
|
| 700 |
+
aten._slow_conv2d_forward: conv_flop,
|
| 701 |
+
aten.convolution_backward: conv_backward_flop,
|
| 702 |
+
aten._scaled_dot_product_efficient_attention: sdpa_flop,
|
| 703 |
+
aten._scaled_dot_product_flash_attention: sdpa_flop,
|
| 704 |
+
aten._scaled_dot_product_cudnn_attention: sdpa_flop,
|
| 705 |
+
aten._scaled_dot_product_efficient_attention_backward: sdpa_backward_flop,
|
| 706 |
+
aten._scaled_dot_product_flash_attention_backward: sdpa_backward_flop,
|
| 707 |
+
aten._scaled_dot_product_cudnn_attention_backward: sdpa_backward_flop,
|
| 708 |
+
aten._flash_attention_forward: _flash_attention_forward_flop,
|
| 709 |
+
aten._efficient_attention_forward: _efficient_attention_forward_flop,
|
| 710 |
+
aten._flash_attention_backward: _flash_attention_backward_flop,
|
| 711 |
+
aten._efficient_attention_backward: _efficient_attention_backward_flop,
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
def normalize_tuple(x):
|
| 715 |
+
if not isinstance(x, tuple):
|
| 716 |
+
return (x,)
|
| 717 |
+
return x
|
| 718 |
+
|
| 719 |
+
|
| 720 |
+
# Define the suffixes for different orders of magnitude
|
| 721 |
+
suffixes = ["", "K", "M", "B", "T"]
|
| 722 |
+
# Thanks BingChat!
|
| 723 |
+
def get_suffix_str(number):
|
| 724 |
+
# Find the index of the appropriate suffix based on the number of digits
|
| 725 |
+
# with some additional overflow.
|
| 726 |
+
# i.e. 1.01B should be displayed as 1001M, not 1.001B
|
| 727 |
+
index = max(0, min(len(suffixes) - 1, (len(str(number)) - 2) // 3))
|
| 728 |
+
return suffixes[index]
|
| 729 |
+
|
| 730 |
+
def convert_num_with_suffix(number, suffix):
|
| 731 |
+
index = suffixes.index(suffix)
|
| 732 |
+
# Divide the number by 1000^index and format it to two decimal places
|
| 733 |
+
value = f"{number / 1000 ** index:.3f}"
|
| 734 |
+
# Return the value and the suffix as a string
|
| 735 |
+
return value + suffixes[index]
|
| 736 |
+
|
| 737 |
+
def convert_to_percent_str(num, denom) -> str:
|
| 738 |
+
if denom == 0:
|
| 739 |
+
return "0%"
|
| 740 |
+
return f"{num / denom:.2%}"
|
| 741 |
+
|
| 742 |
+
def _pytreeify_preserve_structure(f):
|
| 743 |
+
@wraps(f)
|
| 744 |
+
def nf(args):
|
| 745 |
+
flat_args, spec = tree_flatten(args)
|
| 746 |
+
out = f(*flat_args)
|
| 747 |
+
return tree_unflatten(out, spec)
|
| 748 |
+
|
| 749 |
+
return nf
|
| 750 |
+
|
| 751 |
+
|
| 752 |
+
class FlopCounterMode:
|
| 753 |
+
"""
|
| 754 |
+
``FlopCounterMode`` is a context manager that counts the number of flops within its context.
|
| 755 |
+
|
| 756 |
+
It does this using a ``TorchDispatchMode``.
|
| 757 |
+
|
| 758 |
+
It also supports hierarchical output by passing a module (or list of
|
| 759 |
+
modules) to FlopCounterMode on construction. If you do not need hierarchical
|
| 760 |
+
output, you do not need to use it with a module.
|
| 761 |
+
|
| 762 |
+
Example usage
|
| 763 |
+
|
| 764 |
+
.. code-block:: python
|
| 765 |
+
|
| 766 |
+
mod = ...
|
| 767 |
+
with FlopCounterMode(mod) as flop_counter:
|
| 768 |
+
mod.sum().backward()
|
| 769 |
+
|
| 770 |
+
"""
|
| 771 |
+
|
| 772 |
+
def __init__(
|
| 773 |
+
self,
|
| 774 |
+
mods: torch.nn.Module | list[torch.nn.Module] | None = None,
|
| 775 |
+
depth: int = 2,
|
| 776 |
+
display: bool = True,
|
| 777 |
+
custom_mapping: dict[Any, Any] | None = None) -> None:
|
| 778 |
+
super().__init__()
|
| 779 |
+
self.flop_counts: dict[str, dict[Any, int]] = defaultdict(lambda: defaultdict(int))
|
| 780 |
+
self.depth = depth
|
| 781 |
+
self.display = display
|
| 782 |
+
self.mode: _FlopCounterMode | None = None
|
| 783 |
+
if custom_mapping is None:
|
| 784 |
+
custom_mapping = {}
|
| 785 |
+
if mods is not None:
|
| 786 |
+
warnings.warn("mods argument is not needed anymore, you can stop passing it", stacklevel=2)
|
| 787 |
+
self.flop_registry = {
|
| 788 |
+
**flop_registry,
|
| 789 |
+
**{k: v if getattr(v, "_get_raw", False) else shape_wrapper(v) for k, v in custom_mapping.items()}
|
| 790 |
+
}
|
| 791 |
+
self.mod_tracker = ModuleTracker()
|
| 792 |
+
|
| 793 |
+
def get_total_flops(self) -> int:
|
| 794 |
+
return sum(self.flop_counts['Global'].values())
|
| 795 |
+
|
| 796 |
+
def get_flop_counts(self) -> dict[str, dict[Any, int]]:
|
| 797 |
+
"""Return the flop counts as a dictionary of dictionaries.
|
| 798 |
+
|
| 799 |
+
The outer
|
| 800 |
+
dictionary is keyed by module name, and the inner dictionary is keyed by
|
| 801 |
+
operation name.
|
| 802 |
+
|
| 803 |
+
Returns:
|
| 804 |
+
Dict[str, Dict[Any, int]]: The flop counts as a dictionary.
|
| 805 |
+
"""
|
| 806 |
+
return {k: dict(v) for k, v in self.flop_counts.items()}
|
| 807 |
+
|
| 808 |
+
def get_table(self, depth=None):
|
| 809 |
+
if depth is None:
|
| 810 |
+
depth = self.depth
|
| 811 |
+
if depth is None:
|
| 812 |
+
depth = 999999
|
| 813 |
+
|
| 814 |
+
|
| 815 |
+
import tabulate
|
| 816 |
+
|
| 817 |
+
tabulate.PRESERVE_WHITESPACE = True
|
| 818 |
+
header = ["Module", "FLOP", "% Total"]
|
| 819 |
+
values = []
|
| 820 |
+
global_flops = self.get_total_flops()
|
| 821 |
+
global_suffix = get_suffix_str(global_flops)
|
| 822 |
+
is_global_subsumed = False
|
| 823 |
+
|
| 824 |
+
def process_mod(mod_name, depth):
|
| 825 |
+
nonlocal is_global_subsumed
|
| 826 |
+
|
| 827 |
+
total_flops = sum(self.flop_counts[mod_name].values())
|
| 828 |
+
|
| 829 |
+
is_global_subsumed |= total_flops >= global_flops
|
| 830 |
+
|
| 831 |
+
padding = " " * depth
|
| 832 |
+
values = []
|
| 833 |
+
values.append([
|
| 834 |
+
padding + mod_name,
|
| 835 |
+
convert_num_with_suffix(total_flops, global_suffix),
|
| 836 |
+
convert_to_percent_str(total_flops, global_flops)
|
| 837 |
+
])
|
| 838 |
+
for k, v in self.flop_counts[mod_name].items():
|
| 839 |
+
values.append([
|
| 840 |
+
padding + " - " + str(k),
|
| 841 |
+
convert_num_with_suffix(v, global_suffix),
|
| 842 |
+
convert_to_percent_str(v, global_flops)
|
| 843 |
+
])
|
| 844 |
+
return values
|
| 845 |
+
|
| 846 |
+
for mod in sorted(self.flop_counts.keys()):
|
| 847 |
+
if mod == 'Global':
|
| 848 |
+
continue
|
| 849 |
+
mod_depth = mod.count(".") + 1
|
| 850 |
+
if mod_depth > depth:
|
| 851 |
+
continue
|
| 852 |
+
|
| 853 |
+
cur_values = process_mod(mod, mod_depth - 1)
|
| 854 |
+
values.extend(cur_values)
|
| 855 |
+
|
| 856 |
+
# We do a bit of messing around here to only output the "Global" value
|
| 857 |
+
# if there are any FLOPs in there that aren't already fully contained by
|
| 858 |
+
# a module.
|
| 859 |
+
if 'Global' in self.flop_counts and not is_global_subsumed:
|
| 860 |
+
for value in values:
|
| 861 |
+
value[0] = " " + value[0]
|
| 862 |
+
|
| 863 |
+
values = process_mod('Global', 0) + values
|
| 864 |
+
|
| 865 |
+
if len(values) == 0:
|
| 866 |
+
values = [["Global", "0", "0%"]]
|
| 867 |
+
|
| 868 |
+
return tabulate.tabulate(values, headers=header, colalign=("left", "right", "right"))
|
| 869 |
+
|
| 870 |
+
# NB: This context manager is NOT reentrant
|
| 871 |
+
def __enter__(self):
|
| 872 |
+
self.flop_counts.clear()
|
| 873 |
+
self.mod_tracker.__enter__()
|
| 874 |
+
self.mode = _FlopCounterMode(self)
|
| 875 |
+
self.mode.__enter__()
|
| 876 |
+
return self
|
| 877 |
+
|
| 878 |
+
def __exit__(self, *args):
|
| 879 |
+
if self.mode is None:
|
| 880 |
+
raise AssertionError("Internal error: FlopCounter.__exit__ called but mode is None")
|
| 881 |
+
b = self.mode.__exit__(*args)
|
| 882 |
+
self.mode = None # break cycles
|
| 883 |
+
self.mod_tracker.__exit__()
|
| 884 |
+
if self.display:
|
| 885 |
+
print(self.get_table(self.depth))
|
| 886 |
+
return b
|
| 887 |
+
|
| 888 |
+
def _count_flops(self, func_packet, out, args, kwargs):
|
| 889 |
+
if func_packet in self.flop_registry:
|
| 890 |
+
flop_count_func = self.flop_registry[func_packet]
|
| 891 |
+
flop_count = flop_count_func(*args, **kwargs, out_val=out) # type: ignore[operator]
|
| 892 |
+
for par in set(self.mod_tracker.parents):
|
| 893 |
+
self.flop_counts[par][func_packet] += flop_count
|
| 894 |
+
return out
|
| 895 |
+
|
| 896 |
+
class _FlopCounterMode(TorchDispatchMode):
|
| 897 |
+
supports_higher_order_operators = True
|
| 898 |
+
|
| 899 |
+
def __init__(self, counter: FlopCounterMode) -> None:
|
| 900 |
+
self.counter = counter
|
| 901 |
+
|
| 902 |
+
def _execute_with_isolated_flop_counting(self, branch_fn, operands):
|
| 903 |
+
"""Execute a branch function and capture its FLOP counts without
|
| 904 |
+
affecting self.counter.flop_counts
|
| 905 |
+
|
| 906 |
+
Args:
|
| 907 |
+
branch_fn: The branch function to execute
|
| 908 |
+
operands: Arguments to pass to the branch function
|
| 909 |
+
|
| 910 |
+
Returns:
|
| 911 |
+
Tuple of (result, flop_counts) where result is the branch output
|
| 912 |
+
and flop_counts is a copy of the FLOP counts after execution
|
| 913 |
+
"""
|
| 914 |
+
import copy
|
| 915 |
+
checkpointed_flop_counts = copy.copy(self.counter.flop_counts)
|
| 916 |
+
with self:
|
| 917 |
+
result = branch_fn(*operands)
|
| 918 |
+
flop_counts = copy.copy(self.counter.flop_counts)
|
| 919 |
+
self.counter.flop_counts = checkpointed_flop_counts
|
| 920 |
+
return result, flop_counts
|
| 921 |
+
|
| 922 |
+
def _handle_higher_order_ops(self, func, types, args, kwargs):
|
| 923 |
+
is_triton = func in {torch.ops.higher_order.triton_kernel_wrapper_mutation,
|
| 924 |
+
torch.ops.higher_order.triton_kernel_wrapper_functional}
|
| 925 |
+
if is_triton:
|
| 926 |
+
from torch._higher_order_ops.triton_kernel_wrap import get_kernel
|
| 927 |
+
# Special case - look in the triton flop registry for the kernel
|
| 928 |
+
from triton.runtime.jit import JITFunction
|
| 929 |
+
kernel_name = get_kernel(kwargs["kernel_idx"])
|
| 930 |
+
# Unwrap heuristics if they are present
|
| 931 |
+
while not isinstance(kernel_name, JITFunction):
|
| 932 |
+
if hasattr(kernel_name, "fn"):
|
| 933 |
+
kernel_name = kernel_name.fn
|
| 934 |
+
else:
|
| 935 |
+
break
|
| 936 |
+
return self.counter._count_flops(kernel_name, None, args, kwargs)
|
| 937 |
+
elif func is torch.ops.higher_order.cond:
|
| 938 |
+
# The flop counter for cond counts the upper bound of flops.
|
| 939 |
+
# For example, if a matmul is executed 2 times in true branch
|
| 940 |
+
# but only 1 time in the false branch, the flop counter will
|
| 941 |
+
# record the larger number of flops, i.e. 2 times.
|
| 942 |
+
pred, true_branch, false_branch, operands = args
|
| 943 |
+
# Step 1: Count flops for true branch and false branch separately
|
| 944 |
+
true_out, true_flop_counts = self._execute_with_isolated_flop_counting(
|
| 945 |
+
true_branch, operands
|
| 946 |
+
)
|
| 947 |
+
if true_out is NotImplemented:
|
| 948 |
+
return NotImplemented
|
| 949 |
+
|
| 950 |
+
false_out, false_flop_counts = self._execute_with_isolated_flop_counting(
|
| 951 |
+
false_branch, operands
|
| 952 |
+
)
|
| 953 |
+
if false_out is NotImplemented:
|
| 954 |
+
return NotImplemented
|
| 955 |
+
|
| 956 |
+
# Step 2: merge flop counts
|
| 957 |
+
all_mod_keys = set(true_flop_counts.keys()) | set(false_flop_counts.keys())
|
| 958 |
+
merged_flop_counts = {}
|
| 959 |
+
for outer_key in all_mod_keys:
|
| 960 |
+
true_func_counts = true_flop_counts[outer_key]
|
| 961 |
+
false_func_counts = false_flop_counts[outer_key]
|
| 962 |
+
|
| 963 |
+
merged_func_counts = {}
|
| 964 |
+
all_func_keys = set(true_func_counts.keys()) | set(false_func_counts.keys())
|
| 965 |
+
|
| 966 |
+
for func_key in all_func_keys:
|
| 967 |
+
true_val = true_func_counts.get(func_key, 0)
|
| 968 |
+
false_val = false_func_counts.get(func_key, 0)
|
| 969 |
+
merged_func_counts[func_key] = max(true_val, false_val)
|
| 970 |
+
|
| 971 |
+
merged_flop_counts[outer_key] = merged_func_counts
|
| 972 |
+
|
| 973 |
+
# Step 3: update the counter with merged counts
|
| 974 |
+
for outer_key, inner_dict in merged_flop_counts.items():
|
| 975 |
+
self.counter.flop_counts[outer_key].update(inner_dict)
|
| 976 |
+
|
| 977 |
+
# It doesn't matter which one we return since true_fn and false_fn return
|
| 978 |
+
# output with the same structure.
|
| 979 |
+
return true_out
|
| 980 |
+
else:
|
| 981 |
+
return NotImplemented
|
| 982 |
+
|
| 983 |
+
def __torch_dispatch__(self, func, types, args=(), kwargs=None):
|
| 984 |
+
kwargs = kwargs if kwargs else {}
|
| 985 |
+
|
| 986 |
+
# Skip ops from non-standard dispatch_sizes_strides_policy such as NJT
|
| 987 |
+
if func in {torch.ops.aten.sym_is_contiguous.default,
|
| 988 |
+
torch.ops.aten.is_contiguous.default,
|
| 989 |
+
torch.ops.aten.is_contiguous.memory_format,
|
| 990 |
+
torch.ops.aten.is_strides_like_format.default,
|
| 991 |
+
torch.ops.aten.is_non_overlapping_and_dense.default,
|
| 992 |
+
torch.ops.aten.size.default,
|
| 993 |
+
torch.ops.aten.sym_size.default,
|
| 994 |
+
torch.ops.aten.stride.default,
|
| 995 |
+
torch.ops.aten.sym_stride.default,
|
| 996 |
+
torch.ops.aten.storage_offset.default,
|
| 997 |
+
torch.ops.aten.sym_storage_offset.default,
|
| 998 |
+
torch.ops.aten.numel.default,
|
| 999 |
+
torch.ops.aten.sym_numel.default,
|
| 1000 |
+
torch.ops.aten.dim.default,
|
| 1001 |
+
torch.ops.prim.layout.default}:
|
| 1002 |
+
|
| 1003 |
+
return NotImplemented
|
| 1004 |
+
|
| 1005 |
+
if isinstance(func, torch._ops.HigherOrderOperator):
|
| 1006 |
+
return self._handle_higher_order_ops(func, types, args, kwargs)
|
| 1007 |
+
|
| 1008 |
+
# If we don't have func in flop_registry, see if it can decompose
|
| 1009 |
+
if func not in self.counter.flop_registry and func is not torch.ops.prim.device.default:
|
| 1010 |
+
with self:
|
| 1011 |
+
r = func.decompose(*args, **kwargs)
|
| 1012 |
+
if r is not NotImplemented:
|
| 1013 |
+
return r
|
| 1014 |
+
|
| 1015 |
+
# no further decomposition; execute & count flops
|
| 1016 |
+
out = func(*args, **kwargs)
|
| 1017 |
+
return self.counter._count_flops(func._overloadpacket, out, args, kwargs)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .version import __version__
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/constants.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Constants for annotations in the mapping.
|
| 2 |
+
|
| 3 |
+
The constants defined here are used to annotate the mapping tuples in cuda_to_hip_mappings.py.
|
| 4 |
+
They are based on
|
| 5 |
+
https://github.com/ROCm/HIPIFY/blob/master/src/Statistics.h
|
| 6 |
+
and fall in three categories: 1) type of mapping, 2) API of mapping, 3) unsupported
|
| 7 |
+
mapping.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import warnings
|
| 11 |
+
warnings.warn("hipify's constants.py is no longer used as of version 2.0.0", FutureWarning)
|
| 12 |
+
|
| 13 |
+
CONV_VERSION = 0,
|
| 14 |
+
CONV_INIT = 1
|
| 15 |
+
CONV_DEVICE = 2
|
| 16 |
+
CONV_MEM = 3
|
| 17 |
+
CONV_KERN = 4
|
| 18 |
+
CONV_COORD_FUNC = 5
|
| 19 |
+
CONV_MATH_FUNC = 6
|
| 20 |
+
CONV_DEVICE_FUNC = 7
|
| 21 |
+
CONV_SPECIAL_FUNC = 8
|
| 22 |
+
CONV_STREAM = 9
|
| 23 |
+
CONV_EVENT = 10
|
| 24 |
+
CONV_OCCUPANCY = 11
|
| 25 |
+
CONV_CONTEXT = 12
|
| 26 |
+
CONV_PEER = 13
|
| 27 |
+
CONV_MODULE = 14
|
| 28 |
+
CONV_CACHE = 15
|
| 29 |
+
CONV_EXEC = 16
|
| 30 |
+
CONV_ERROR = 17
|
| 31 |
+
CONV_DEF = 18
|
| 32 |
+
CONV_TEX = 19
|
| 33 |
+
CONV_GL = 20
|
| 34 |
+
CONV_GRAPHICS = 21
|
| 35 |
+
CONV_SURFACE = 22
|
| 36 |
+
CONV_JIT = 23
|
| 37 |
+
CONV_D3D9 = 24
|
| 38 |
+
CONV_D3D10 = 25
|
| 39 |
+
CONV_D3D11 = 26
|
| 40 |
+
CONV_VDPAU = 27
|
| 41 |
+
CONV_EGL = 28
|
| 42 |
+
CONV_THREAD = 29
|
| 43 |
+
CONV_OTHER = 30
|
| 44 |
+
CONV_INCLUDE = 31
|
| 45 |
+
CONV_INCLUDE_CUDA_MAIN_H = 32
|
| 46 |
+
CONV_TYPE = 33
|
| 47 |
+
CONV_LITERAL = 34
|
| 48 |
+
CONV_NUMERIC_LITERAL = 35
|
| 49 |
+
CONV_LAST = 36
|
| 50 |
+
|
| 51 |
+
API_DRIVER = 37
|
| 52 |
+
API_RUNTIME = 38
|
| 53 |
+
API_BLAS = 39
|
| 54 |
+
API_SPECIAL = 40
|
| 55 |
+
API_RAND = 41
|
| 56 |
+
API_LAST = 42
|
| 57 |
+
API_FFT = 43
|
| 58 |
+
API_RTC = 44
|
| 59 |
+
API_ROCTX = 45
|
| 60 |
+
API_PYT_EXT = 46
|
| 61 |
+
|
| 62 |
+
HIP_UNSUPPORTED = 47
|
| 63 |
+
API_PYTORCH = 1337
|
| 64 |
+
API_CAFFE2 = 1338
|
| 65 |
+
API_C10 = 1339
|
| 66 |
+
API_ROCMSMI = 1340
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/cuda_to_hip_mappings.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/hipify_python.py
ADDED
|
@@ -0,0 +1,1175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# mypy: allow-untyped-defs
|
| 3 |
+
""" The Python Hipify script.
|
| 4 |
+
##
|
| 5 |
+
# Copyright (c) 2015-2016 Advanced Micro Devices, Inc. All rights reserved.
|
| 6 |
+
# 2017-2018 Advanced Micro Devices, Inc. and
|
| 7 |
+
# Facebook Inc. All rights reserved.
|
| 8 |
+
#
|
| 9 |
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 10 |
+
# of this software and associated documentation files (the "Software"), to deal
|
| 11 |
+
# in the Software without restriction, including without limitation the rights
|
| 12 |
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 13 |
+
# copies of the Software, and to permit persons to whom the Software is
|
| 14 |
+
# furnished to do so, subject to the following conditions:
|
| 15 |
+
#
|
| 16 |
+
# The above copyright notice and this permission notice shall be included in
|
| 17 |
+
# all copies or substantial portions of the Software.
|
| 18 |
+
#
|
| 19 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 20 |
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 21 |
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 22 |
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 23 |
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 24 |
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
| 25 |
+
# THE SOFTWARE.
|
| 26 |
+
"""
|
| 27 |
+
import argparse
|
| 28 |
+
import fnmatch
|
| 29 |
+
import re
|
| 30 |
+
import shutil
|
| 31 |
+
import sys
|
| 32 |
+
import os
|
| 33 |
+
import warnings
|
| 34 |
+
|
| 35 |
+
from .cuda_to_hip_mappings import CUDA_TO_HIP_MAPPINGS
|
| 36 |
+
from .cuda_to_hip_mappings import MATH_TRANSPILATIONS
|
| 37 |
+
from .cuda_to_hip_mappings import CAFFE2_PATH_MAPPINGS
|
| 38 |
+
|
| 39 |
+
from collections.abc import Iterator
|
| 40 |
+
from collections.abc import Mapping, Iterable
|
| 41 |
+
from enum import Enum
|
| 42 |
+
import functools
|
| 43 |
+
import hashlib
|
| 44 |
+
|
| 45 |
+
def _deprecated(name):
|
| 46 |
+
warnings.warn(f"hipify version 2.0.0 no longer uses function {name}", FutureWarning, stacklevel=2)
|
| 47 |
+
|
| 48 |
+
class CurrentState(Enum):
|
| 49 |
+
INITIALIZED = 1
|
| 50 |
+
DONE = 2
|
| 51 |
+
|
| 52 |
+
class HipifyResult:
|
| 53 |
+
def __init__(self, current_state, hipified_path) -> None:
|
| 54 |
+
self.current_state = current_state
|
| 55 |
+
self.hipified_path = hipified_path
|
| 56 |
+
self.status = ""
|
| 57 |
+
|
| 58 |
+
def __str__(self) -> str:
|
| 59 |
+
return (f"HipifyResult:: current_state: {self.current_state}, hipified_path : {self.hipified_path}, status: {self.status}")
|
| 60 |
+
|
| 61 |
+
HipifyFinalResult = dict[str, HipifyResult]
|
| 62 |
+
HIPIFY_C_BREADCRUMB = "// !!! This is a file automatically generated by hipify!!!\n"
|
| 63 |
+
HIPIFY_FINAL_RESULT: HipifyFinalResult = {}
|
| 64 |
+
|
| 65 |
+
# Hardcode the PyTorch template map
|
| 66 |
+
"""This dictionary provides the mapping from PyTorch kernel template types
|
| 67 |
+
to their actual types."""
|
| 68 |
+
PYTORCH_TEMPLATE_MAP = {"Dtype": "scalar_t", "T": "scalar_t"}
|
| 69 |
+
|
| 70 |
+
__all__ = ['InputError', 'openf', 'bcolors', 'GeneratedFileCleaner', 'match_extensions', 'matched_files_iter',
|
| 71 |
+
'preprocess_file_and_save_result', 'compute_stats', 'add_dim3', 'processKernelLaunches', 'find_closure_group',
|
| 72 |
+
'find_bracket_group', 'find_parentheses_group', 'replace_math_functions', 'hip_header_magic', 'replace_extern_shared',
|
| 73 |
+
'get_hip_file_path', 'is_out_of_place', 'is_pytorch_file', 'is_cusparse_file', 'is_special_file', 'is_caffe2_gpu_file',
|
| 74 |
+
'Trie', 'preprocessor', 'file_specific_replacement', 'file_add_header',
|
| 75 |
+
'fix_static_global_kernels', 'extract_arguments', 'str2bool', 'CurrentState', 'HipifyResult', 'hipify']
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class InputError(Exception):
|
| 79 |
+
# Exception raised for errors in the input.
|
| 80 |
+
|
| 81 |
+
def __init__(self, message) -> None:
|
| 82 |
+
super().__init__(message)
|
| 83 |
+
self.message = message
|
| 84 |
+
|
| 85 |
+
def __str__(self) -> str:
|
| 86 |
+
return f"Input error: {self.message}"
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def openf(filename, mode):
|
| 90 |
+
return open(filename, mode, errors='ignore')
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
# Color coding for printing
|
| 94 |
+
class bcolors:
|
| 95 |
+
HEADER = '\033[95m'
|
| 96 |
+
OKBLUE = '\033[94m'
|
| 97 |
+
OKGREEN = '\033[92m'
|
| 98 |
+
WARNING = '\033[93m'
|
| 99 |
+
FAIL = '\033[91m'
|
| 100 |
+
ENDC = '\033[0m'
|
| 101 |
+
BOLD = '\033[1m'
|
| 102 |
+
UNDERLINE = '\033[4m'
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# To the programmer, the output of hipify most likely are intermediates.
|
| 106 |
+
# This class allows users of hipify to ask for a cleanup by running the
|
| 107 |
+
# hipify and compilation in a with instantiating this context manager class
|
| 108 |
+
# with keep_intermediates=False.
|
| 109 |
+
# The main usecase is the cpp_extensions, specifically the load method.
|
| 110 |
+
# It is a good idea to keep intermediates (in case of errors or to
|
| 111 |
+
# not recompile unchanged files), but in cases where you don't want to
|
| 112 |
+
# keep them (e.g. in the CI), this can be used to remove files.
|
| 113 |
+
class GeneratedFileCleaner:
|
| 114 |
+
"""Context Manager to clean up generated files"""
|
| 115 |
+
def __init__(self, keep_intermediates=False) -> None:
|
| 116 |
+
self.keep_intermediates = keep_intermediates
|
| 117 |
+
self.files_to_clean = set()
|
| 118 |
+
self.dirs_to_clean = []
|
| 119 |
+
|
| 120 |
+
def __enter__(self):
|
| 121 |
+
return self
|
| 122 |
+
|
| 123 |
+
def open(self, fn, *args, **kwargs):
|
| 124 |
+
if not os.path.exists(fn):
|
| 125 |
+
self.files_to_clean.add(os.path.abspath(fn))
|
| 126 |
+
|
| 127 |
+
return open(fn, *args, **kwargs)
|
| 128 |
+
|
| 129 |
+
def makedirs(self, dn, exist_ok=False) -> None:
|
| 130 |
+
parent, n = os.path.split(dn)
|
| 131 |
+
if not n:
|
| 132 |
+
parent, n = os.path.split(parent)
|
| 133 |
+
if parent and n and not os.path.exists(parent):
|
| 134 |
+
self.makedirs(parent, exist_ok=True)
|
| 135 |
+
if not os.path.isdir(dn) or not exist_ok:
|
| 136 |
+
os.mkdir(dn)
|
| 137 |
+
self.dirs_to_clean.append(os.path.abspath(dn))
|
| 138 |
+
|
| 139 |
+
def __exit__(self, type, value, traceback):
|
| 140 |
+
if not self.keep_intermediates:
|
| 141 |
+
for f in self.files_to_clean:
|
| 142 |
+
os.unlink(f)
|
| 143 |
+
for d in self.dirs_to_clean[::-1]:
|
| 144 |
+
os.rmdir(d)
|
| 145 |
+
|
| 146 |
+
# Follow UNIX convention for paths to use '/' instead of '\\' on Windows
|
| 147 |
+
def _to_unix_path(path: str) -> str:
|
| 148 |
+
return path.replace(os.sep, '/')
|
| 149 |
+
|
| 150 |
+
def match_extensions(filename: str, extensions: Iterable) -> bool:
|
| 151 |
+
"""Helper method to see if filename ends with certain extension"""
|
| 152 |
+
return any(filename.endswith(e) for e in extensions)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def _fnmatch(filepath, patterns):
|
| 156 |
+
return any(fnmatch.fnmatch(filepath, pattern) for pattern in patterns)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def matched_files_iter(
|
| 160 |
+
root_path: str,
|
| 161 |
+
includes: Iterable = (),
|
| 162 |
+
ignores: Iterable = (),
|
| 163 |
+
extensions: Iterable = (),
|
| 164 |
+
out_of_place_only: bool = False,
|
| 165 |
+
is_pytorch_extension: bool = False) -> Iterator[str]:
|
| 166 |
+
|
| 167 |
+
exact_matches = set(includes)
|
| 168 |
+
|
| 169 |
+
# This is a very rough heuristic; really, we want to avoid scanning
|
| 170 |
+
# any file which is not checked into source control, but this script
|
| 171 |
+
# needs to work even if you're in a Git or Hg checkout, so easier to
|
| 172 |
+
# just block the biggest time sinks that won't matter in the
|
| 173 |
+
# end.
|
| 174 |
+
for (abs_dirpath, dirs, filenames) in os.walk(root_path, topdown=True):
|
| 175 |
+
rel_dirpath = os.path.relpath(abs_dirpath, root_path)
|
| 176 |
+
if rel_dirpath == '.':
|
| 177 |
+
# Blah blah blah O(n) blah blah
|
| 178 |
+
if ".git" in dirs:
|
| 179 |
+
dirs.remove(".git")
|
| 180 |
+
if "build" in dirs:
|
| 181 |
+
dirs.remove("build")
|
| 182 |
+
if "third_party" in dirs:
|
| 183 |
+
dirs.remove("third_party")
|
| 184 |
+
dirs.append("third_party/nvfuser")
|
| 185 |
+
for filename in filenames:
|
| 186 |
+
filepath = _to_unix_path(os.path.join(abs_dirpath, filename))
|
| 187 |
+
# We respect extensions, UNLESS you wrote the entire
|
| 188 |
+
# filename verbatim, in which case we always accept it
|
| 189 |
+
if (
|
| 190 |
+
_fnmatch(filepath, includes)
|
| 191 |
+
and (not _fnmatch(filepath, ignores))
|
| 192 |
+
and (match_extensions(filepath, extensions) or filepath in exact_matches)
|
| 193 |
+
):
|
| 194 |
+
yield filepath
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def preprocess_file_and_save_result(
|
| 198 |
+
output_directory: str,
|
| 199 |
+
filepath: str,
|
| 200 |
+
all_files: Iterable,
|
| 201 |
+
header_include_dirs: Iterable,
|
| 202 |
+
stats: dict[str, list],
|
| 203 |
+
hip_clang_launch: bool,
|
| 204 |
+
is_pytorch_extension: bool,
|
| 205 |
+
clean_ctx: GeneratedFileCleaner,
|
| 206 |
+
show_progress: bool) -> None:
|
| 207 |
+
fin_path = os.path.abspath(os.path.join(output_directory, filepath))
|
| 208 |
+
hipify_result = HipifyResult(current_state=CurrentState.INITIALIZED, hipified_path=fin_path)
|
| 209 |
+
HIPIFY_FINAL_RESULT[fin_path] = hipify_result
|
| 210 |
+
result = preprocessor(output_directory, filepath, all_files, header_include_dirs, stats,
|
| 211 |
+
hip_clang_launch, is_pytorch_extension, clean_ctx, show_progress)
|
| 212 |
+
|
| 213 |
+
# Show what happened
|
| 214 |
+
if show_progress and "ignored" not in result.status:
|
| 215 |
+
print(
|
| 216 |
+
fin_path, "->",
|
| 217 |
+
result.hipified_path, result.status, flush=True)
|
| 218 |
+
|
| 219 |
+
HIPIFY_FINAL_RESULT[fin_path] = result
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def compute_stats(stats) -> None:
|
| 223 |
+
unsupported_calls = {cuda_call for (cuda_call, _filepath) in stats["unsupported_calls"]}
|
| 224 |
+
|
| 225 |
+
# Print the number of unsupported calls
|
| 226 |
+
print(f"Total number of unsupported CUDA function calls: {len(unsupported_calls):d}")
|
| 227 |
+
|
| 228 |
+
# Print the list of unsupported calls
|
| 229 |
+
print(", ".join(unsupported_calls))
|
| 230 |
+
|
| 231 |
+
# Print the number of kernel launches
|
| 232 |
+
print(f"\nTotal number of replaced kernel launches: {len(stats['kernel_launches']):d}")
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def add_dim3(kernel_string, cuda_kernel):
|
| 236 |
+
'''adds dim3() to the second and third arguments in the kernel launch'''
|
| 237 |
+
count = 0
|
| 238 |
+
closure = 0
|
| 239 |
+
kernel_string = kernel_string.replace("<<<", "").replace(">>>", "")
|
| 240 |
+
arg_locs: list[dict[str, int]] = [{} for _ in range(2)]
|
| 241 |
+
arg_locs[count]['start'] = 0
|
| 242 |
+
for ind, c in enumerate(kernel_string):
|
| 243 |
+
if count > 1:
|
| 244 |
+
break
|
| 245 |
+
if c == "(":
|
| 246 |
+
closure += 1
|
| 247 |
+
elif c == ")":
|
| 248 |
+
closure -= 1
|
| 249 |
+
if (c == "," or ind == len(kernel_string) - 1) and closure == 0:
|
| 250 |
+
arg_locs[count]['end'] = ind + (c != ",")
|
| 251 |
+
count += 1
|
| 252 |
+
if count < 2:
|
| 253 |
+
arg_locs[count]['start'] = ind + 1
|
| 254 |
+
|
| 255 |
+
first_arg_raw = kernel_string[arg_locs[0]['start']:arg_locs[0]['end'] + 1]
|
| 256 |
+
second_arg_raw = kernel_string[arg_locs[1]['start']:arg_locs[1]['end']]
|
| 257 |
+
|
| 258 |
+
first_arg_clean = kernel_string[arg_locs[0]['start']:arg_locs[0]['end']].replace("\n", "").strip(" ")
|
| 259 |
+
second_arg_clean = kernel_string[arg_locs[1]['start']:arg_locs[1]['end']].replace("\n", "").strip(" ")
|
| 260 |
+
|
| 261 |
+
first_arg_dim3 = f"dim3({first_arg_clean})"
|
| 262 |
+
second_arg_dim3 = f"dim3({second_arg_clean})"
|
| 263 |
+
|
| 264 |
+
first_arg_raw_dim3 = first_arg_raw.replace(first_arg_clean, first_arg_dim3)
|
| 265 |
+
second_arg_raw_dim3 = second_arg_raw.replace(second_arg_clean, second_arg_dim3)
|
| 266 |
+
cuda_kernel = cuda_kernel.replace(first_arg_raw + second_arg_raw, first_arg_raw_dim3 + second_arg_raw_dim3)
|
| 267 |
+
return cuda_kernel
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
RE_KERNEL_LAUNCH = re.compile(r'([ ]+)(detail?)::[ ]+\\\n[ ]+')
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def processKernelLaunches(string, stats):
|
| 274 |
+
""" Replace the CUDA style Kernel launches with the HIP style kernel launches."""
|
| 275 |
+
# Concat the namespace with the kernel names. (Find cleaner way of doing this later).
|
| 276 |
+
string = RE_KERNEL_LAUNCH.sub(lambda inp: f"{inp.group(1)}{inp.group(2)}::", string)
|
| 277 |
+
|
| 278 |
+
def grab_method_and_template(in_kernel):
|
| 279 |
+
# The positions for relevant kernel components.
|
| 280 |
+
pos = {
|
| 281 |
+
"kernel_launch": {"start": in_kernel["start"], "end": in_kernel["end"]},
|
| 282 |
+
"kernel_name": {"start": -1, "end": -1},
|
| 283 |
+
"template": {"start": -1, "end": -1}
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
# Count for balancing template
|
| 287 |
+
count = {"<>": 0}
|
| 288 |
+
|
| 289 |
+
# Status for whether we are parsing a certain item.
|
| 290 |
+
START = 0
|
| 291 |
+
AT_TEMPLATE = 1
|
| 292 |
+
AFTER_TEMPLATE = 2
|
| 293 |
+
AT_KERNEL_NAME = 3
|
| 294 |
+
|
| 295 |
+
status = START
|
| 296 |
+
|
| 297 |
+
# Parse the string character by character
|
| 298 |
+
for i in range(pos["kernel_launch"]["start"] - 1, -1, -1):
|
| 299 |
+
char = string[i]
|
| 300 |
+
|
| 301 |
+
# Handle Templating Arguments
|
| 302 |
+
if status in (START, AT_TEMPLATE):
|
| 303 |
+
if char == ">":
|
| 304 |
+
if status == START:
|
| 305 |
+
status = AT_TEMPLATE
|
| 306 |
+
pos["template"]["end"] = i
|
| 307 |
+
count["<>"] += 1
|
| 308 |
+
|
| 309 |
+
if char == "<":
|
| 310 |
+
count["<>"] -= 1
|
| 311 |
+
if count["<>"] == 0 and (status == AT_TEMPLATE):
|
| 312 |
+
pos["template"]["start"] = i
|
| 313 |
+
status = AFTER_TEMPLATE
|
| 314 |
+
|
| 315 |
+
# Handle Kernel Name
|
| 316 |
+
if status != AT_TEMPLATE:
|
| 317 |
+
if string[i].isalnum() or string[i] in {'(', ')', '_', ':', '#'}:
|
| 318 |
+
if status != AT_KERNEL_NAME:
|
| 319 |
+
status = AT_KERNEL_NAME
|
| 320 |
+
pos["kernel_name"]["end"] = i
|
| 321 |
+
|
| 322 |
+
# Case: Kernel name starts the string.
|
| 323 |
+
if i == 0:
|
| 324 |
+
pos["kernel_name"]["start"] = 0
|
| 325 |
+
|
| 326 |
+
# Finished
|
| 327 |
+
return [(pos["kernel_name"]), (pos["template"]), (pos["kernel_launch"])]
|
| 328 |
+
|
| 329 |
+
else:
|
| 330 |
+
# Potential ending point if we're already traversing a kernel's name.
|
| 331 |
+
if status == AT_KERNEL_NAME:
|
| 332 |
+
pos["kernel_name"]["start"] = i
|
| 333 |
+
|
| 334 |
+
# Finished
|
| 335 |
+
return [(pos["kernel_name"]), (pos["template"]), (pos["kernel_launch"])]
|
| 336 |
+
|
| 337 |
+
def find_kernel_bounds(string):
|
| 338 |
+
"""Finds the starting and ending points for all kernel launches in the string."""
|
| 339 |
+
kernel_end = 0
|
| 340 |
+
kernel_positions = []
|
| 341 |
+
|
| 342 |
+
# Continue until we cannot find any more kernels anymore.
|
| 343 |
+
while string.find("<<<", kernel_end) != -1:
|
| 344 |
+
# Get kernel starting position (starting from the previous ending point)
|
| 345 |
+
kernel_start = string.find("<<<", kernel_end)
|
| 346 |
+
|
| 347 |
+
# Get kernel ending position (adjust end point past the >>>)
|
| 348 |
+
kernel_end = string.find(">>>", kernel_start) + 3
|
| 349 |
+
if kernel_end <= 0:
|
| 350 |
+
raise InputError("no kernel end found")
|
| 351 |
+
|
| 352 |
+
# Add to list of traversed kernels
|
| 353 |
+
kernel_positions.append({"start": kernel_start, "end": kernel_end,
|
| 354 |
+
"group": string[kernel_start: kernel_end]})
|
| 355 |
+
|
| 356 |
+
return kernel_positions
|
| 357 |
+
|
| 358 |
+
# Replace comments and string literals from the code so that find_kernel_bounds does not
|
| 359 |
+
# wrongly capture kernels in comments and string literals.
|
| 360 |
+
# This function replaces them with "x" to keep positions.
|
| 361 |
+
def mask_comments(string):
|
| 362 |
+
in_comment = ''
|
| 363 |
+
prev_c = ''
|
| 364 |
+
new_string = ''
|
| 365 |
+
for c in string:
|
| 366 |
+
if in_comment == '':
|
| 367 |
+
# Outside comments
|
| 368 |
+
if c == '/' and prev_c == '/':
|
| 369 |
+
in_comment = '//'
|
| 370 |
+
elif c == '*' and prev_c == '/':
|
| 371 |
+
in_comment = '/*'
|
| 372 |
+
elif c == '"' and prev_c != '\\' and prev_c != "'":
|
| 373 |
+
in_comment = '"'
|
| 374 |
+
elif in_comment == '//':
|
| 375 |
+
# In // xxx
|
| 376 |
+
if c == '\r' or c == '\n':
|
| 377 |
+
in_comment = ''
|
| 378 |
+
elif in_comment == '/*':
|
| 379 |
+
# In /* xxx */
|
| 380 |
+
if c == '/' and prev_c == '*':
|
| 381 |
+
in_comment = ''
|
| 382 |
+
elif in_comment == '"':
|
| 383 |
+
# In ""
|
| 384 |
+
if c == '"' and prev_c != '\\':
|
| 385 |
+
in_comment = ''
|
| 386 |
+
prev_c = c
|
| 387 |
+
if in_comment == '':
|
| 388 |
+
new_string += c
|
| 389 |
+
else:
|
| 390 |
+
new_string += 'x'
|
| 391 |
+
return new_string
|
| 392 |
+
|
| 393 |
+
# Grab positional ranges of all kernel launches
|
| 394 |
+
get_kernel_positions = list(find_kernel_bounds(mask_comments(string)))
|
| 395 |
+
output_string = string
|
| 396 |
+
|
| 397 |
+
# Replace each CUDA kernel with a HIP kernel.
|
| 398 |
+
for kernel in get_kernel_positions:
|
| 399 |
+
# Get kernel components
|
| 400 |
+
params = grab_method_and_template(kernel)
|
| 401 |
+
|
| 402 |
+
# Find parenthesis after kernel launch
|
| 403 |
+
parenthesis = string.find("(", kernel["end"])
|
| 404 |
+
|
| 405 |
+
# Extract cuda kernel
|
| 406 |
+
cuda_kernel = string[params[0]["start"]:parenthesis + 1]
|
| 407 |
+
kernel_string = string[kernel['start']:kernel['end']]
|
| 408 |
+
end_param_index = 0 if params[1]['end'] == -1 else 1
|
| 409 |
+
kernel_name_with_template = string[params[0]['start']:params[end_param_index]['end'] + 1]
|
| 410 |
+
cuda_kernel_dim3 = add_dim3(kernel_string, cuda_kernel)
|
| 411 |
+
# Keep number of kernel launch params consistent (grid dims, group dims, stream, dynamic shared size)
|
| 412 |
+
num_klp = len(extract_arguments(0, kernel["group"].replace("<<<", "(").replace(">>>", ")")))
|
| 413 |
+
|
| 414 |
+
hip_kernel = "hipLaunchKernelGGL(" + cuda_kernel_dim3[0:-1].replace(
|
| 415 |
+
">>>", ", 0" * (4 - num_klp) + ">>>").replace("<<<", ", ").replace(
|
| 416 |
+
">>>", ", ").replace(kernel_name_with_template, "(" + kernel_name_with_template + ")")
|
| 417 |
+
|
| 418 |
+
# Replace cuda kernel with hip kernel
|
| 419 |
+
output_string = output_string.replace(cuda_kernel, hip_kernel)
|
| 420 |
+
|
| 421 |
+
# Update the statistics
|
| 422 |
+
stats["kernel_launches"].append(hip_kernel)
|
| 423 |
+
|
| 424 |
+
return output_string
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def find_closure_group(input_string, start, group):
|
| 428 |
+
"""Generalization for finding a balancing closure group
|
| 429 |
+
|
| 430 |
+
if group = ["(", ")"], then finds the first balanced parentheses.
|
| 431 |
+
if group = ["{", "}"], then finds the first balanced bracket.
|
| 432 |
+
|
| 433 |
+
Given an input string, a starting position in the input string, and the group type,
|
| 434 |
+
find_closure_group returns the positions of group[0] and group[1] as a tuple.
|
| 435 |
+
|
| 436 |
+
Example:
|
| 437 |
+
>>> find_closure_group("(hi)", 0, ["(", ")"])
|
| 438 |
+
(0, 3)
|
| 439 |
+
"""
|
| 440 |
+
|
| 441 |
+
inside_parenthesis = False
|
| 442 |
+
parens = 0
|
| 443 |
+
pos = start
|
| 444 |
+
p_start, p_end = -1, -1
|
| 445 |
+
|
| 446 |
+
while pos < len(input_string):
|
| 447 |
+
if input_string[pos] == group[0]:
|
| 448 |
+
if inside_parenthesis is False:
|
| 449 |
+
inside_parenthesis = True
|
| 450 |
+
parens = 1
|
| 451 |
+
p_start = pos
|
| 452 |
+
else:
|
| 453 |
+
parens += 1
|
| 454 |
+
elif input_string[pos] == group[1] and inside_parenthesis:
|
| 455 |
+
parens -= 1
|
| 456 |
+
|
| 457 |
+
if parens == 0:
|
| 458 |
+
p_end = pos
|
| 459 |
+
return p_start, p_end
|
| 460 |
+
|
| 461 |
+
pos += 1
|
| 462 |
+
return None, None
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def find_bracket_group(input_string, start):
|
| 466 |
+
"""Finds the first balanced parentheses."""
|
| 467 |
+
return find_closure_group(input_string, start, group=["{", "}"])
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def find_parentheses_group(input_string, start):
|
| 471 |
+
"""Finds the first balanced bracket."""
|
| 472 |
+
return find_closure_group(input_string, start, group=["(", ")"])
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
RE_ASSERT = re.compile(r"\bassert[ ]*\(")
|
| 476 |
+
|
| 477 |
+
|
| 478 |
+
def replace_math_functions(input_string):
|
| 479 |
+
"""FIXME: Temporarily replace std:: invocations of math functions
|
| 480 |
+
with non-std:: versions to prevent linker errors NOTE: This
|
| 481 |
+
can lead to correctness issues when running tests, since the
|
| 482 |
+
correct version of the math function (exp/expf) might not get
|
| 483 |
+
called. Plan is to remove this function once HIP supports
|
| 484 |
+
std:: math function calls inside device code
|
| 485 |
+
|
| 486 |
+
"""
|
| 487 |
+
output_string = input_string
|
| 488 |
+
for func in MATH_TRANSPILATIONS:
|
| 489 |
+
output_string = output_string.replace(fr'{func}(', f'{MATH_TRANSPILATIONS[func]}(')
|
| 490 |
+
|
| 491 |
+
return output_string
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
RE_SYNCTHREADS = re.compile(r":?:?\b(__syncthreads)\b(\w*\()")
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
def hip_header_magic(input_string):
|
| 498 |
+
"""If the file makes kernel builtin calls and does not include the cuda_runtime.h header,
|
| 499 |
+
then automatically add an #include to match the "magic" includes provided by NVCC.
|
| 500 |
+
TODO:
|
| 501 |
+
Update logic to ignore cases where the cuda_runtime.h is included by another file.
|
| 502 |
+
"""
|
| 503 |
+
|
| 504 |
+
# Copy the input.
|
| 505 |
+
output_string = input_string
|
| 506 |
+
|
| 507 |
+
# Check if one of the following headers is already included.
|
| 508 |
+
headers = ["hip/hip_runtime.h", "hip/hip_runtime_api.h"]
|
| 509 |
+
if any(re.search(fr'#include ("{ext}"|<{ext}>)', output_string) for ext in headers):
|
| 510 |
+
return output_string
|
| 511 |
+
|
| 512 |
+
# Rough logic to detect if we're inside device code
|
| 513 |
+
hasDeviceLogic: int
|
| 514 |
+
hasDeviceLogic = "hipLaunchKernelGGL" in output_string
|
| 515 |
+
hasDeviceLogic += "__global__" in output_string
|
| 516 |
+
hasDeviceLogic += "__shared__" in output_string
|
| 517 |
+
hasDeviceLogic += RE_SYNCTHREADS.search(output_string) is not None
|
| 518 |
+
|
| 519 |
+
# If device logic found, provide the necessary header.
|
| 520 |
+
if hasDeviceLogic:
|
| 521 |
+
output_string = '#include "hip/hip_runtime.h"\n' + input_string
|
| 522 |
+
|
| 523 |
+
return output_string
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
RE_EXTERN_SHARED = re.compile(r"extern\s+([\w\(\)]+)?\s*__shared__\s+([\w:<>\s]+)\s+(\w+)\s*\[\s*\]\s*;")
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
def replace_extern_shared(input_string):
|
| 530 |
+
"""
|
| 531 |
+
Match 'extern __shared__ type foo[];' syntax and use HIP_DYNAMIC_SHARED() MACRO instead.
|
| 532 |
+
See: https://github.com/ROCm/hip/blob/master/docs/markdown/hip_kernel_language.md#__shared__
|
| 533 |
+
Examples:
|
| 534 |
+
"extern __shared__ char smemChar[];"
|
| 535 |
+
=> "HIP_DYNAMIC_SHARED( char, smemChar)"
|
| 536 |
+
"extern __shared__ unsigned char smem[];"
|
| 537 |
+
=> "HIP_DYNAMIC_SHARED( unsigned char, my_smem)"
|
| 538 |
+
"""
|
| 539 |
+
output_string = input_string
|
| 540 |
+
output_string = RE_EXTERN_SHARED.sub(
|
| 541 |
+
lambda inp: f"HIP_DYNAMIC_SHARED({inp.group(1) or ''} {inp.group(2)}, {inp.group(3)})", output_string)
|
| 542 |
+
|
| 543 |
+
return output_string
|
| 544 |
+
|
| 545 |
+
|
| 546 |
+
def get_hip_file_path(rel_filepath, is_pytorch_extension=False):
|
| 547 |
+
"""
|
| 548 |
+
Returns the new name of the hipified file
|
| 549 |
+
"""
|
| 550 |
+
# At the moment, some PyTorch source files are HIPified in place. The predicate
|
| 551 |
+
# is_out_of_place tells us if this is the case or not.
|
| 552 |
+
if os.path.isabs(rel_filepath):
|
| 553 |
+
raise AssertionError("rel_filepath must be a relative path")
|
| 554 |
+
if not is_pytorch_extension and not is_out_of_place(rel_filepath):
|
| 555 |
+
return rel_filepath
|
| 556 |
+
|
| 557 |
+
dirpath, filename = os.path.split(rel_filepath)
|
| 558 |
+
root, ext = os.path.splitext(filename)
|
| 559 |
+
|
| 560 |
+
# Here's the plan:
|
| 561 |
+
#
|
| 562 |
+
# In general, we need to disambiguate the HIPified filename so that
|
| 563 |
+
# it gets a different name from the original filename, so
|
| 564 |
+
# that we don't overwrite the original file
|
| 565 |
+
#
|
| 566 |
+
# There's a lot of different naming conventions across PyTorch,
|
| 567 |
+
# but the general recipe is to convert occurrences
|
| 568 |
+
# of cuda/gpu to hip, and add hip if there are no occurrences
|
| 569 |
+
# of cuda/gpu anywhere.
|
| 570 |
+
#
|
| 571 |
+
# Concretely, we do the following:
|
| 572 |
+
#
|
| 573 |
+
# - If there is a directory component named "cuda", replace
|
| 574 |
+
# it with "hip", AND
|
| 575 |
+
#
|
| 576 |
+
# - If the file name contains "CUDA", replace it with "HIP", AND
|
| 577 |
+
#
|
| 578 |
+
# - ALWAYS replace '.cu' with '.hip', because those files
|
| 579 |
+
# contain CUDA kernels that needs to be hipified and processed with
|
| 580 |
+
# hip compiler
|
| 581 |
+
#
|
| 582 |
+
# - If we are not hipifying a PyTorch extension, and the parent
|
| 583 |
+
# directory name did not change as a result of the above
|
| 584 |
+
# transformations, insert "hip" in the file path
|
| 585 |
+
# as the direct parent folder of the file
|
| 586 |
+
#
|
| 587 |
+
# - If we are hipifying a PyTorch extension, and the parent directory
|
| 588 |
+
# name as well as the filename (incl. extension) did not change as
|
| 589 |
+
# a result of the above transformations, insert "_hip" in the filename
|
| 590 |
+
#
|
| 591 |
+
# This isn't set in stone; we might adjust this to support other
|
| 592 |
+
# naming conventions.
|
| 593 |
+
|
| 594 |
+
if ext == '.cu':
|
| 595 |
+
ext = '.hip'
|
| 596 |
+
|
| 597 |
+
orig_filename = filename
|
| 598 |
+
orig_dirpath = dirpath
|
| 599 |
+
|
| 600 |
+
dirpath = dirpath.replace('cuda', 'hip')
|
| 601 |
+
dirpath = dirpath.replace('CUDA', 'HIP')
|
| 602 |
+
dirpath = dirpath.replace('THC', 'THH')
|
| 603 |
+
|
| 604 |
+
root = root.replace('cuda', 'hip')
|
| 605 |
+
root = root.replace('CUDA', 'HIP')
|
| 606 |
+
# Special case to handle caffe2/core/THCCachingAllocator
|
| 607 |
+
if dirpath != "caffe2/core":
|
| 608 |
+
root = root.replace('THC', 'THH')
|
| 609 |
+
|
| 610 |
+
if not is_pytorch_extension and dirpath == orig_dirpath:
|
| 611 |
+
dirpath = os.path.join(dirpath, 'hip')
|
| 612 |
+
|
| 613 |
+
if is_pytorch_extension and dirpath == orig_dirpath and (root + ext) == orig_filename:
|
| 614 |
+
root = root + "_hip"
|
| 615 |
+
|
| 616 |
+
return os.path.join(dirpath, root + ext)
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
def is_out_of_place(rel_filepath) -> bool:
|
| 620 |
+
if os.path.isabs(rel_filepath):
|
| 621 |
+
raise AssertionError("rel_filepath must be a relative path")
|
| 622 |
+
if rel_filepath.startswith("torch/"):
|
| 623 |
+
return False
|
| 624 |
+
if rel_filepath.startswith("third_party/nvfuser/"):
|
| 625 |
+
return False
|
| 626 |
+
if rel_filepath.startswith("tools/autograd/templates/"):
|
| 627 |
+
return False
|
| 628 |
+
return True
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
# Keep this synchronized with includes/ignores in build_amd.py
|
| 632 |
+
def is_pytorch_file(rel_filepath) -> bool:
|
| 633 |
+
_deprecated("is_pytorch_file")
|
| 634 |
+
if os.path.isabs(rel_filepath):
|
| 635 |
+
raise AssertionError("rel_filepath must be a relative path")
|
| 636 |
+
if rel_filepath.startswith("aten/"):
|
| 637 |
+
if rel_filepath.startswith("aten/src/ATen/core/"):
|
| 638 |
+
return False
|
| 639 |
+
return True
|
| 640 |
+
if rel_filepath.startswith("torch/"):
|
| 641 |
+
return True
|
| 642 |
+
if rel_filepath.startswith("third_party/nvfuser/"):
|
| 643 |
+
return True
|
| 644 |
+
if rel_filepath.startswith("third_party/fbgemm/"):
|
| 645 |
+
return True
|
| 646 |
+
if rel_filepath.startswith("third_party/mslk/"):
|
| 647 |
+
return True
|
| 648 |
+
if rel_filepath.startswith("tools/autograd/templates/"):
|
| 649 |
+
return True
|
| 650 |
+
if rel_filepath.startswith("test/cpp/c10d/"):
|
| 651 |
+
return True
|
| 652 |
+
return False
|
| 653 |
+
|
| 654 |
+
|
| 655 |
+
def is_cusparse_file(rel_filepath):
|
| 656 |
+
_deprecated("is_cusparse_file")
|
| 657 |
+
if is_pytorch_file(rel_filepath):
|
| 658 |
+
return "sparse" in rel_filepath.lower()
|
| 659 |
+
return False
|
| 660 |
+
|
| 661 |
+
|
| 662 |
+
def is_special_file(rel_filepath) -> bool:
|
| 663 |
+
_deprecated("is_special_file")
|
| 664 |
+
if is_pytorch_file(rel_filepath):
|
| 665 |
+
if "sparse" in rel_filepath.lower():
|
| 666 |
+
return True
|
| 667 |
+
elif "linalg" in rel_filepath.lower():
|
| 668 |
+
if "batchlinearalgebralibblas" in rel_filepath.lower():
|
| 669 |
+
return False # don't use "special" mappings for this specific linalg cublas file
|
| 670 |
+
return True
|
| 671 |
+
return False
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
def is_caffe2_gpu_file(rel_filepath):
|
| 675 |
+
_deprecated("is_caffe2_gpu_file")
|
| 676 |
+
if os.path.isabs(rel_filepath):
|
| 677 |
+
raise AssertionError("rel_filepath must be a relative path")
|
| 678 |
+
if rel_filepath.startswith("c10/cuda"):
|
| 679 |
+
return True
|
| 680 |
+
filename = os.path.basename(rel_filepath)
|
| 681 |
+
_, ext = os.path.splitext(filename)
|
| 682 |
+
|
| 683 |
+
return ('gpu' in filename or ext in ['.cu', '.cuh']) and ('cudnn' not in filename)
|
| 684 |
+
|
| 685 |
+
|
| 686 |
+
class TrieNode:
|
| 687 |
+
"""A Trie node whose children are represented as a directory of char: TrieNode.
|
| 688 |
+
A special char '' represents end of word
|
| 689 |
+
"""
|
| 690 |
+
|
| 691 |
+
def __init__(self) -> None:
|
| 692 |
+
self.children = {}
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
class Trie:
|
| 696 |
+
"""Creates a Trie out of a list of words. The trie can be exported to a Regex pattern.
|
| 697 |
+
The corresponding Regex should match much faster than a simple Regex union."""
|
| 698 |
+
|
| 699 |
+
def __init__(self) -> None:
|
| 700 |
+
"""Initialize the trie with an empty root node."""
|
| 701 |
+
self.root = TrieNode()
|
| 702 |
+
self._hash = hashlib.md5(usedforsecurity=False)
|
| 703 |
+
self._digest = self._hash.digest()
|
| 704 |
+
|
| 705 |
+
def add(self, word) -> None:
|
| 706 |
+
"""Add a word to the Trie. """
|
| 707 |
+
self._hash.update(word.encode())
|
| 708 |
+
self._digest = self._hash.digest()
|
| 709 |
+
node = self.root
|
| 710 |
+
|
| 711 |
+
for char in word:
|
| 712 |
+
node.children.setdefault(char, TrieNode())
|
| 713 |
+
node = node.children[char]
|
| 714 |
+
node.children[''] = True # Mark the end of the word
|
| 715 |
+
|
| 716 |
+
def dump(self):
|
| 717 |
+
"""Return the root node of Trie. """
|
| 718 |
+
return self.root
|
| 719 |
+
|
| 720 |
+
def quote(self, char):
|
| 721 |
+
""" Escape a char for regex. """
|
| 722 |
+
return re.escape(char)
|
| 723 |
+
|
| 724 |
+
def search(self, word):
|
| 725 |
+
"""Search whether word is present in the Trie.
|
| 726 |
+
Returns True if yes, else return False"""
|
| 727 |
+
node = self.root
|
| 728 |
+
for char in word:
|
| 729 |
+
if char in node.children:
|
| 730 |
+
node = node.children[char]
|
| 731 |
+
else:
|
| 732 |
+
return False
|
| 733 |
+
|
| 734 |
+
# make sure to check the end-of-word marker present
|
| 735 |
+
return '' in node.children
|
| 736 |
+
|
| 737 |
+
@functools.lru_cache # noqa: B019
|
| 738 |
+
def _pattern(self, root, digest):
|
| 739 |
+
"""Convert a Trie into a regular expression pattern
|
| 740 |
+
|
| 741 |
+
Memoized on the hash digest of the trie, which is built incrementally
|
| 742 |
+
during add().
|
| 743 |
+
"""
|
| 744 |
+
node = root
|
| 745 |
+
|
| 746 |
+
if "" in node.children and len(node.children.keys()) == 1:
|
| 747 |
+
return None
|
| 748 |
+
|
| 749 |
+
alt = [] # store alternative patterns
|
| 750 |
+
cc = [] # store char to char classes
|
| 751 |
+
q = 0 # for node representing the end of word
|
| 752 |
+
for char in sorted(node.children.keys()):
|
| 753 |
+
if isinstance(node.children[char], TrieNode):
|
| 754 |
+
try:
|
| 755 |
+
recurse = self._pattern(node.children[char], self._digest)
|
| 756 |
+
alt.append(self.quote(char) + recurse)
|
| 757 |
+
except Exception:
|
| 758 |
+
cc.append(self.quote(char))
|
| 759 |
+
else:
|
| 760 |
+
q = 1
|
| 761 |
+
cconly = not len(alt) > 0
|
| 762 |
+
|
| 763 |
+
if len(cc) > 0:
|
| 764 |
+
if len(cc) == 1:
|
| 765 |
+
alt.append(cc[0])
|
| 766 |
+
else:
|
| 767 |
+
alt.append('[' + ''.join(cc) + ']')
|
| 768 |
+
|
| 769 |
+
if len(alt) == 1:
|
| 770 |
+
result = alt[0]
|
| 771 |
+
else:
|
| 772 |
+
result = "(?:" + "|".join(alt) + ")"
|
| 773 |
+
|
| 774 |
+
if q:
|
| 775 |
+
if cconly:
|
| 776 |
+
result += "?"
|
| 777 |
+
else:
|
| 778 |
+
result = f"(?:{result})?"
|
| 779 |
+
return result
|
| 780 |
+
|
| 781 |
+
def pattern(self):
|
| 782 |
+
"""Export the Trie to a regex pattern."""
|
| 783 |
+
return self._pattern(self.root, self._digest)
|
| 784 |
+
|
| 785 |
+
def export_to_regex(self):
|
| 786 |
+
"""Export the Trie to a regex pattern."""
|
| 787 |
+
return self._pattern(self.root, self._digest)
|
| 788 |
+
|
| 789 |
+
PYTORCH_TRIE = Trie()
|
| 790 |
+
PYTORCH_MAP: dict[str, object] = {}
|
| 791 |
+
|
| 792 |
+
for mapping in CUDA_TO_HIP_MAPPINGS:
|
| 793 |
+
if not isinstance(mapping, Mapping):
|
| 794 |
+
raise TypeError("Expected each mapping in CUDA_TO_HIP_MAPPINGS to be a Mapping")
|
| 795 |
+
for src, dst in mapping.items():
|
| 796 |
+
PYTORCH_TRIE.add(src)
|
| 797 |
+
PYTORCH_MAP[src] = dst
|
| 798 |
+
|
| 799 |
+
RE_PYTORCH_PREPROCESSOR = re.compile(fr'(?<=\W)({PYTORCH_TRIE.export_to_regex()})(?=\W)')
|
| 800 |
+
|
| 801 |
+
RE_QUOTE_HEADER = re.compile(r'#include "([^"]+)"')
|
| 802 |
+
RE_ANGLE_HEADER = re.compile(r'#include <([^>]+)>')
|
| 803 |
+
RE_THC_GENERIC_FILE = re.compile(r'#define THC_GENERIC_FILE "([^"]+)"')
|
| 804 |
+
RE_CU_SUFFIX = re.compile(r'\.cu\b') # be careful not to pick up .cuh
|
| 805 |
+
|
| 806 |
+
"""
|
| 807 |
+
Returns a HipifyResult object with the following details:
|
| 808 |
+
"hipified_path" : absolute path of hipified source file
|
| 809 |
+
"status" : "ok" if hipified file was written out
|
| 810 |
+
"skipped" if an identical hipified file already existed or hipified file couldn't be written out
|
| 811 |
+
"ignored" if the source file was a hipified file itself or not meant to be hipified
|
| 812 |
+
"current_state" : CurrentState.INITIALIZED if source file is first ready to be hipified
|
| 813 |
+
CurrentState.DONE if source file is done with hipification process
|
| 814 |
+
"""
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
def preprocessor(
|
| 818 |
+
output_directory: str,
|
| 819 |
+
filepath: str,
|
| 820 |
+
all_files: Iterable,
|
| 821 |
+
header_include_dirs: Iterable,
|
| 822 |
+
stats: dict[str, list],
|
| 823 |
+
hip_clang_launch: bool,
|
| 824 |
+
is_pytorch_extension: bool,
|
| 825 |
+
clean_ctx: GeneratedFileCleaner,
|
| 826 |
+
show_progress: bool) -> HipifyResult:
|
| 827 |
+
""" Executes the CUDA -> HIP conversion on the specified file. """
|
| 828 |
+
fin_path = os.path.abspath(os.path.join(output_directory, filepath))
|
| 829 |
+
filepath = _to_unix_path(filepath)
|
| 830 |
+
hipify_result = HIPIFY_FINAL_RESULT[fin_path]
|
| 831 |
+
if filepath not in all_files:
|
| 832 |
+
hipify_result.hipified_path = None
|
| 833 |
+
hipify_result.status = "[ignored, not to be hipified]"
|
| 834 |
+
hipify_result.current_state = CurrentState.DONE
|
| 835 |
+
return hipify_result
|
| 836 |
+
|
| 837 |
+
rel_filepath = _to_unix_path(os.path.relpath(filepath, output_directory))
|
| 838 |
+
|
| 839 |
+
with open(fin_path, encoding='utf-8') as fin:
|
| 840 |
+
if fin.readline() == HIPIFY_C_BREADCRUMB:
|
| 841 |
+
hipify_result.hipified_path = None
|
| 842 |
+
hipify_result.status = "[ignored, input is hipified output]"
|
| 843 |
+
hipify_result.current_state = CurrentState.DONE
|
| 844 |
+
return hipify_result
|
| 845 |
+
fin.seek(0)
|
| 846 |
+
output_source = fin.read()
|
| 847 |
+
|
| 848 |
+
orig_output_source = output_source
|
| 849 |
+
|
| 850 |
+
# get_hip_file_path needs a relative path to work correctly
|
| 851 |
+
fout_path = os.path.abspath(os.path.join(output_directory, get_hip_file_path(rel_filepath, is_pytorch_extension)))
|
| 852 |
+
if not os.path.exists(os.path.dirname(fout_path)):
|
| 853 |
+
clean_ctx.makedirs(os.path.dirname(fout_path))
|
| 854 |
+
|
| 855 |
+
# unsupported_calls statistics reporting is broken atm
|
| 856 |
+
def pt_repl(m):
|
| 857 |
+
return PYTORCH_MAP[m.group(0)]
|
| 858 |
+
|
| 859 |
+
output_source = RE_PYTORCH_PREPROCESSOR.sub(pt_repl, output_source)
|
| 860 |
+
|
| 861 |
+
# TODO: Remove CAFFE2_PATH_MAPPINGS. They were necessary for Meta-internal builds.
|
| 862 |
+
# Apply CAFFE2 path mappings (simple string replacement for paths containing slashes)
|
| 863 |
+
# Need to be careful to avoid double-transformations when source file has #ifdef blocks
|
| 864 |
+
# with HIP-specific paths already in them (e.g., caffe2/core/hip/context_gpu.h)
|
| 865 |
+
for cuda_path, hip_path in CAFFE2_PATH_MAPPINGS.items():
|
| 866 |
+
# Use regex to ensure we don't match paths that already have been hipified
|
| 867 |
+
# We need to avoid transforming "caffe2/core/hip/context_gpu.h" when looking for "caffe2/core/context_gpu.h"
|
| 868 |
+
# The key insight: if hip_path contains /hip/ and cuda_path doesn't, we need to be careful
|
| 869 |
+
if "/hip/" in hip_path and "/hip/" not in cuda_path:
|
| 870 |
+
# Only replace cuda_path if it's not preceded by "/hip/"
|
| 871 |
+
# Use negative lookbehind to prevent matching already-hipified paths
|
| 872 |
+
# The pattern checks that the cuda_path is not immediately preceded by "/hip/"
|
| 873 |
+
pattern = r'(?<!/hip/)' + re.escape(cuda_path)
|
| 874 |
+
output_source = re.sub(pattern, hip_path, output_source)
|
| 875 |
+
else:
|
| 876 |
+
# Simple replacement when no /hip/ involved or both have it
|
| 877 |
+
output_source = output_source.replace(cuda_path, hip_path)
|
| 878 |
+
|
| 879 |
+
# Header rewrites
|
| 880 |
+
def mk_repl(templ, include_current_dir=True):
|
| 881 |
+
def repl(m):
|
| 882 |
+
f = m.group(1)
|
| 883 |
+
filename = os.path.basename(f)
|
| 884 |
+
if (
|
| 885 |
+
f.startswith(("ATen/cuda",
|
| 886 |
+
"ATen/native/cuda",
|
| 887 |
+
"ATen/native/nested/cuda",
|
| 888 |
+
"ATen/native/quantized/cuda",
|
| 889 |
+
"ATen/native/sparse/cuda",
|
| 890 |
+
"ATen/native/transformers/cuda",
|
| 891 |
+
"THC/")) or
|
| 892 |
+
(f.startswith("THC") and not f.startswith("THCP"))
|
| 893 |
+
):
|
| 894 |
+
return templ.format(get_hip_file_path(m.group(1), is_pytorch_extension))
|
| 895 |
+
# if filename is one of the files being hipified for this extension
|
| 896 |
+
if (is_pytorch_extension and any(s.endswith(filename) for s in all_files)):
|
| 897 |
+
header_dir = None
|
| 898 |
+
header_filepath = None
|
| 899 |
+
# If include_current_dir True, look first in same dir as the including source file
|
| 900 |
+
if include_current_dir:
|
| 901 |
+
header_dir_to_check = os.path.dirname(fin_path)
|
| 902 |
+
header_path_to_check = os.path.abspath(os.path.join(header_dir_to_check, f))
|
| 903 |
+
if os.path.exists(header_path_to_check):
|
| 904 |
+
header_dir = header_dir_to_check
|
| 905 |
+
header_filepath = header_path_to_check
|
| 906 |
+
# If not found, look in include dirs one by one and first match wins
|
| 907 |
+
if header_filepath is None:
|
| 908 |
+
for header_include_dir in header_include_dirs:
|
| 909 |
+
header_dir_to_check = os.path.join(output_directory, header_include_dir)
|
| 910 |
+
header_path_to_check = os.path.abspath(os.path.join(header_dir_to_check, f))
|
| 911 |
+
if os.path.exists(header_path_to_check):
|
| 912 |
+
header_dir = header_dir_to_check
|
| 913 |
+
header_filepath = header_path_to_check
|
| 914 |
+
# If header file not found, keep as is
|
| 915 |
+
if header_filepath is None:
|
| 916 |
+
return m.group(0)
|
| 917 |
+
# Hipify header file first if needed
|
| 918 |
+
if header_filepath not in HIPIFY_FINAL_RESULT:
|
| 919 |
+
preprocess_file_and_save_result(output_directory,
|
| 920 |
+
header_filepath,
|
| 921 |
+
all_files, header_include_dirs, stats, hip_clang_launch,
|
| 922 |
+
is_pytorch_extension, clean_ctx, show_progress)
|
| 923 |
+
elif header_filepath in HIPIFY_FINAL_RESULT:
|
| 924 |
+
header_result = HIPIFY_FINAL_RESULT[header_filepath]
|
| 925 |
+
if header_result.current_state == CurrentState.INITIALIZED:
|
| 926 |
+
# get_hip_file_path needs a relative path to work correctly
|
| 927 |
+
header_rel_path = os.path.relpath(header_filepath, output_directory)
|
| 928 |
+
header_fout_path = os.path.abspath(os.path.join(output_directory,
|
| 929 |
+
get_hip_file_path(header_rel_path, is_pytorch_extension)))
|
| 930 |
+
header_result.hipified_path = header_fout_path
|
| 931 |
+
HIPIFY_FINAL_RESULT[header_filepath] = header_result
|
| 932 |
+
return templ.format(os.path.relpath(header_fout_path if header_fout_path is not None
|
| 933 |
+
else header_filepath, header_dir))
|
| 934 |
+
hipified_header_filepath = HIPIFY_FINAL_RESULT[header_filepath].hipified_path
|
| 935 |
+
return templ.format(_to_unix_path(os.path.relpath(hipified_header_filepath if hipified_header_filepath is not None
|
| 936 |
+
else header_filepath, header_dir)))
|
| 937 |
+
|
| 938 |
+
return m.group(0)
|
| 939 |
+
return repl
|
| 940 |
+
output_source = RE_QUOTE_HEADER.sub(mk_repl('#include "{0}"', True), output_source)
|
| 941 |
+
output_source = RE_ANGLE_HEADER.sub(mk_repl('#include <{0}>', False), output_source)
|
| 942 |
+
output_source = RE_THC_GENERIC_FILE.sub(mk_repl('#define THC_GENERIC_FILE "{0}"'), output_source)
|
| 943 |
+
|
| 944 |
+
# CMakeLists.txt rewrites
|
| 945 |
+
if filepath.endswith('CMakeLists.txt'):
|
| 946 |
+
output_source = output_source.replace('CUDA', 'HIP')
|
| 947 |
+
output_source = output_source.replace('THC', 'THH')
|
| 948 |
+
output_source = RE_CU_SUFFIX.sub('.hip', output_source)
|
| 949 |
+
|
| 950 |
+
# Perform Kernel Launch Replacements
|
| 951 |
+
if not hip_clang_launch:
|
| 952 |
+
output_source = processKernelLaunches(output_source, stats)
|
| 953 |
+
|
| 954 |
+
# Replace std:: with non-std:: versions
|
| 955 |
+
if (filepath.endswith((".cu", ".cuh"))) and "PowKernel" not in filepath:
|
| 956 |
+
output_source = replace_math_functions(output_source)
|
| 957 |
+
|
| 958 |
+
# Include header if device code is contained.
|
| 959 |
+
output_source = hip_header_magic(output_source)
|
| 960 |
+
|
| 961 |
+
# Replace the extern __shared__
|
| 962 |
+
# NOTE: No longer needed after transition from hcc to hipclang.
|
| 963 |
+
# output_source = replace_extern_shared(output_source)
|
| 964 |
+
|
| 965 |
+
# Don't write out identical hipified files for extensions if dirpath has not changed
|
| 966 |
+
if (
|
| 967 |
+
is_pytorch_extension
|
| 968 |
+
and orig_output_source == output_source
|
| 969 |
+
and os.path.dirname(fin_path) == os.path.dirname(fout_path)
|
| 970 |
+
):
|
| 971 |
+
hipify_result.hipified_path = fin_path
|
| 972 |
+
hipify_result.status = "[skipped, no changes]"
|
| 973 |
+
hipify_result.current_state = CurrentState.DONE
|
| 974 |
+
return hipify_result
|
| 975 |
+
|
| 976 |
+
# Add hipify breadcrumb for C-style files to avoid re-hipification
|
| 977 |
+
if fin_path != fout_path and match_extensions(fin_path, (".cu", ".cuh", ".c", ".cc", ".cpp", ".h", ".hpp")):
|
| 978 |
+
output_source = HIPIFY_C_BREADCRUMB + output_source
|
| 979 |
+
|
| 980 |
+
do_write = True
|
| 981 |
+
if os.path.exists(fout_path):
|
| 982 |
+
with open(fout_path, encoding='utf-8') as fout_old:
|
| 983 |
+
do_write = fout_old.read() != output_source
|
| 984 |
+
if do_write:
|
| 985 |
+
try:
|
| 986 |
+
with clean_ctx.open(fout_path, 'w', encoding='utf-8') as fout:
|
| 987 |
+
fout.write(output_source)
|
| 988 |
+
hipify_result.hipified_path = fout_path
|
| 989 |
+
hipify_result.status = "[ok]"
|
| 990 |
+
hipify_result.current_state = CurrentState.DONE
|
| 991 |
+
return hipify_result
|
| 992 |
+
except OSError as e:
|
| 993 |
+
print(f'{bcolors.WARNING}Failed to save {fout_path} with "{e.strerror}", leaving {fin_path} unchanged.{bcolors.ENDC}',
|
| 994 |
+
file=sys.stderr)
|
| 995 |
+
hipify_result.hipified_path = fin_path
|
| 996 |
+
hipify_result.status = "[skipped, no permissions]"
|
| 997 |
+
hipify_result.current_state = CurrentState.DONE
|
| 998 |
+
return hipify_result
|
| 999 |
+
else:
|
| 1000 |
+
hipify_result.hipified_path = fout_path
|
| 1001 |
+
hipify_result.status = "[skipped, already hipified]"
|
| 1002 |
+
hipify_result.current_state = CurrentState.DONE
|
| 1003 |
+
return hipify_result
|
| 1004 |
+
|
| 1005 |
+
def file_specific_replacement(filepath, search_string, replace_string, strict=False) -> None:
|
| 1006 |
+
with openf(filepath, "r+") as f:
|
| 1007 |
+
contents = f.read()
|
| 1008 |
+
if strict:
|
| 1009 |
+
contents = re.sub(fr'\b({re.escape(search_string)})\b', lambda x: replace_string, contents)
|
| 1010 |
+
else:
|
| 1011 |
+
contents = contents.replace(search_string, replace_string)
|
| 1012 |
+
f.seek(0)
|
| 1013 |
+
f.write(contents)
|
| 1014 |
+
f.truncate()
|
| 1015 |
+
|
| 1016 |
+
|
| 1017 |
+
def file_add_header(filepath, header) -> None:
|
| 1018 |
+
with openf(filepath, "r+") as f:
|
| 1019 |
+
contents = f.read()
|
| 1020 |
+
if header[0] != "<" and header[-1] != ">":
|
| 1021 |
+
header = f'"{header}"'
|
| 1022 |
+
contents = (f'#include {header} \n') + contents
|
| 1023 |
+
f.seek(0)
|
| 1024 |
+
f.write(contents)
|
| 1025 |
+
f.truncate()
|
| 1026 |
+
|
| 1027 |
+
|
| 1028 |
+
def fix_static_global_kernels(in_txt):
|
| 1029 |
+
"""Static global kernels in HIP results in a compilation error."""
|
| 1030 |
+
in_txt = in_txt.replace(" __global__ static", "__global__")
|
| 1031 |
+
return in_txt
|
| 1032 |
+
|
| 1033 |
+
|
| 1034 |
+
RE_INCLUDE = re.compile(r"#include .*\n")
|
| 1035 |
+
|
| 1036 |
+
|
| 1037 |
+
def extract_arguments(start, string):
|
| 1038 |
+
"""
|
| 1039 |
+
Return the list of arguments in the upcoming function parameter closure.
|
| 1040 |
+
Example:
|
| 1041 |
+
string (input): '(blocks, threads, 0, THCState_getCurrentStream(state))'
|
| 1042 |
+
arguments (output): [{'start': 1, 'end': 7}, {'start': 8, 'end': 16}, \
|
| 1043 |
+
{'start': 17, 'end': 19}, {'start': 20, 'end': 53}]
|
| 1044 |
+
"""
|
| 1045 |
+
|
| 1046 |
+
arguments = []
|
| 1047 |
+
closures = {
|
| 1048 |
+
"<": 0,
|
| 1049 |
+
"(": 0
|
| 1050 |
+
}
|
| 1051 |
+
current_position = start
|
| 1052 |
+
argument_start_pos = current_position + 1
|
| 1053 |
+
|
| 1054 |
+
# Search for final parenthesis
|
| 1055 |
+
while current_position < len(string):
|
| 1056 |
+
if string[current_position] == "(":
|
| 1057 |
+
closures["("] += 1
|
| 1058 |
+
elif string[current_position] == ")":
|
| 1059 |
+
closures["("] -= 1
|
| 1060 |
+
elif string[current_position] == "<":
|
| 1061 |
+
closures["<"] += 1
|
| 1062 |
+
elif string[current_position] == ">" and string[current_position - 1] != "-" and closures["<"] > 0:
|
| 1063 |
+
closures["<"] -= 1
|
| 1064 |
+
|
| 1065 |
+
# Finished all arguments
|
| 1066 |
+
if closures["("] == 0 and closures["<"] == 0:
|
| 1067 |
+
# Add final argument
|
| 1068 |
+
arguments.append({"start": argument_start_pos, "end": current_position})
|
| 1069 |
+
break
|
| 1070 |
+
|
| 1071 |
+
# Finished current argument
|
| 1072 |
+
if closures["("] == 1 and closures["<"] == 0 and string[current_position] == ",":
|
| 1073 |
+
arguments.append({"start": argument_start_pos, "end": current_position})
|
| 1074 |
+
argument_start_pos = current_position + 1
|
| 1075 |
+
|
| 1076 |
+
current_position += 1
|
| 1077 |
+
|
| 1078 |
+
return arguments
|
| 1079 |
+
|
| 1080 |
+
|
| 1081 |
+
def str2bool(v : str) -> bool:
|
| 1082 |
+
"""ArgumentParser doesn't support type=bool. Thus, this helper method will convert
|
| 1083 |
+
from possible string types to True / False."""
|
| 1084 |
+
if v.lower() in ('yes', 'true', 't', 'y', '1'):
|
| 1085 |
+
return True
|
| 1086 |
+
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
|
| 1087 |
+
return False
|
| 1088 |
+
else:
|
| 1089 |
+
raise argparse.ArgumentTypeError('Boolean value expected.')
|
| 1090 |
+
|
| 1091 |
+
|
| 1092 |
+
def hipify(
|
| 1093 |
+
project_directory: str,
|
| 1094 |
+
show_detailed: bool = False,
|
| 1095 |
+
extensions: Iterable = (".cu", ".cuh", ".c", ".cc", ".cpp", ".h", ".in", ".hpp"),
|
| 1096 |
+
header_extensions: Iterable = (".cuh", ".h", ".hpp"),
|
| 1097 |
+
output_directory: str = "",
|
| 1098 |
+
header_include_dirs: Iterable = (),
|
| 1099 |
+
includes: Iterable = ('*',),
|
| 1100 |
+
extra_files: Iterable = (),
|
| 1101 |
+
out_of_place_only: bool = False,
|
| 1102 |
+
ignores: Iterable = (),
|
| 1103 |
+
show_progress: bool = True,
|
| 1104 |
+
hip_clang_launch: bool = False,
|
| 1105 |
+
is_pytorch_extension: bool = False,
|
| 1106 |
+
hipify_extra_files_only: bool = False,
|
| 1107 |
+
clean_ctx: GeneratedFileCleaner | None = None
|
| 1108 |
+
) -> HipifyFinalResult:
|
| 1109 |
+
if project_directory == "":
|
| 1110 |
+
project_directory = os.getcwd()
|
| 1111 |
+
|
| 1112 |
+
# Verify the project directory exists.
|
| 1113 |
+
if not os.path.exists(project_directory):
|
| 1114 |
+
print("The project folder specified does not exist.")
|
| 1115 |
+
sys.exit(1)
|
| 1116 |
+
|
| 1117 |
+
# If no output directory, provide a default one.
|
| 1118 |
+
if not output_directory:
|
| 1119 |
+
project_directory.rstrip("/")
|
| 1120 |
+
output_directory = project_directory + "_amd"
|
| 1121 |
+
|
| 1122 |
+
if project_directory != output_directory:
|
| 1123 |
+
includes = [include.replace(project_directory, output_directory) for include in includes]
|
| 1124 |
+
ignores = [ignore.replace(project_directory, output_directory) for ignore in ignores]
|
| 1125 |
+
|
| 1126 |
+
# Copy from project directory to output directory if not done already.
|
| 1127 |
+
if not os.path.exists(output_directory):
|
| 1128 |
+
shutil.copytree(project_directory, output_directory)
|
| 1129 |
+
|
| 1130 |
+
includes = list(map(_to_unix_path, includes))
|
| 1131 |
+
ignores = list(map(_to_unix_path, ignores))
|
| 1132 |
+
|
| 1133 |
+
all_files = list(matched_files_iter(output_directory, includes=includes,
|
| 1134 |
+
ignores=ignores, extensions=extensions,
|
| 1135 |
+
out_of_place_only=out_of_place_only,
|
| 1136 |
+
is_pytorch_extension=is_pytorch_extension))
|
| 1137 |
+
all_files_set = set(all_files)
|
| 1138 |
+
|
| 1139 |
+
for f in extra_files:
|
| 1140 |
+
if not os.path.isabs(f):
|
| 1141 |
+
f = os.path.join(output_directory, f)
|
| 1142 |
+
if f not in all_files_set:
|
| 1143 |
+
all_files.append(f)
|
| 1144 |
+
|
| 1145 |
+
# List all files in header_include_paths to ensure they are hipified
|
| 1146 |
+
from pathlib import Path
|
| 1147 |
+
for header_include_dir in header_include_dirs:
|
| 1148 |
+
if os.path.isabs(header_include_dir):
|
| 1149 |
+
header_include_dir_path = Path(header_include_dir)
|
| 1150 |
+
else:
|
| 1151 |
+
header_include_dir_path = Path(os.path.join(output_directory, header_include_dir))
|
| 1152 |
+
all_files.extend(
|
| 1153 |
+
str(path) for path in header_include_dir_path.rglob('*') if path.is_file()
|
| 1154 |
+
and _fnmatch(str(path), includes)
|
| 1155 |
+
and (not _fnmatch(str(path), ignores))
|
| 1156 |
+
and match_extensions(path.name, header_extensions)
|
| 1157 |
+
)
|
| 1158 |
+
|
| 1159 |
+
if clean_ctx is None:
|
| 1160 |
+
clean_ctx = GeneratedFileCleaner(keep_intermediates=True)
|
| 1161 |
+
|
| 1162 |
+
# Preprocessing statistics.
|
| 1163 |
+
stats: dict[str, list] = {"unsupported_calls": [], "kernel_launches": []}
|
| 1164 |
+
|
| 1165 |
+
for filepath in (all_files if not hipify_extra_files_only else extra_files):
|
| 1166 |
+
preprocess_file_and_save_result(output_directory, filepath, all_files, header_include_dirs,
|
| 1167 |
+
stats, hip_clang_launch, is_pytorch_extension, clean_ctx, show_progress)
|
| 1168 |
+
|
| 1169 |
+
print(bcolors.OKGREEN + "Successfully preprocessed all matching files." + bcolors.ENDC, file=sys.stderr)
|
| 1170 |
+
|
| 1171 |
+
# Show detailed summary
|
| 1172 |
+
if show_detailed:
|
| 1173 |
+
compute_stats(stats)
|
| 1174 |
+
|
| 1175 |
+
return HIPIFY_FINAL_RESULT
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hipify/version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
__version__ = '2.0.0'
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/hooks.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import torch
|
| 3 |
+
from collections import OrderedDict
|
| 4 |
+
import weakref
|
| 5 |
+
import warnings
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
__all__ = ["RemovableHandle", "unserializable_hook", "warn_if_has_hooks", "BackwardHook"]
|
| 9 |
+
|
| 10 |
+
class RemovableHandle:
|
| 11 |
+
r"""
|
| 12 |
+
A handle which provides the capability to remove a hook.
|
| 13 |
+
|
| 14 |
+
Args:
|
| 15 |
+
hooks_dict (dict): A dictionary of hooks, indexed by hook ``id``.
|
| 16 |
+
extra_dict (Union[dict, List[dict]]): An additional dictionary or list of
|
| 17 |
+
dictionaries whose keys will be deleted when the same keys are
|
| 18 |
+
removed from ``hooks_dict``.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
id: int
|
| 22 |
+
next_id: int = 0
|
| 23 |
+
|
| 24 |
+
def __init__(self, hooks_dict: Any, *, extra_dict: Any = None) -> None:
|
| 25 |
+
self.hooks_dict_ref = weakref.ref(hooks_dict)
|
| 26 |
+
self.id = RemovableHandle.next_id
|
| 27 |
+
RemovableHandle.next_id += 1
|
| 28 |
+
|
| 29 |
+
self.extra_dict_ref: tuple = ()
|
| 30 |
+
if isinstance(extra_dict, dict):
|
| 31 |
+
self.extra_dict_ref = (weakref.ref(extra_dict),)
|
| 32 |
+
elif isinstance(extra_dict, list):
|
| 33 |
+
self.extra_dict_ref = tuple(weakref.ref(d) for d in extra_dict)
|
| 34 |
+
|
| 35 |
+
def remove(self) -> None:
|
| 36 |
+
hooks_dict = self.hooks_dict_ref()
|
| 37 |
+
if hooks_dict is not None and self.id in hooks_dict:
|
| 38 |
+
del hooks_dict[self.id]
|
| 39 |
+
|
| 40 |
+
for ref in self.extra_dict_ref:
|
| 41 |
+
extra_dict = ref()
|
| 42 |
+
if extra_dict is not None and self.id in extra_dict:
|
| 43 |
+
del extra_dict[self.id]
|
| 44 |
+
|
| 45 |
+
def __getstate__(self):
|
| 46 |
+
if self.extra_dict_ref is None:
|
| 47 |
+
return (self.hooks_dict_ref(), self.id)
|
| 48 |
+
else:
|
| 49 |
+
return (self.hooks_dict_ref(), self.id, tuple(ref() for ref in self.extra_dict_ref))
|
| 50 |
+
|
| 51 |
+
def __setstate__(self, state) -> None:
|
| 52 |
+
if state[0] is None:
|
| 53 |
+
# create a dead reference
|
| 54 |
+
self.hooks_dict_ref = weakref.ref(OrderedDict())
|
| 55 |
+
else:
|
| 56 |
+
self.hooks_dict_ref = weakref.ref(state[0])
|
| 57 |
+
self.id = state[1]
|
| 58 |
+
RemovableHandle.next_id = max(RemovableHandle.next_id, self.id + 1)
|
| 59 |
+
|
| 60 |
+
if len(state) < 3 or state[2] is None:
|
| 61 |
+
self.extra_dict_ref = ()
|
| 62 |
+
else:
|
| 63 |
+
self.extra_dict_ref = tuple(weakref.ref(d) for d in state[2])
|
| 64 |
+
|
| 65 |
+
def __enter__(self) -> "RemovableHandle":
|
| 66 |
+
return self
|
| 67 |
+
|
| 68 |
+
def __exit__(self, type: Any, value: Any, tb: Any) -> None:
|
| 69 |
+
self.remove()
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def unserializable_hook(f):
|
| 73 |
+
"""
|
| 74 |
+
Mark a function as an unserializable hook with this decorator.
|
| 75 |
+
|
| 76 |
+
This suppresses warnings that would otherwise arise if you attempt
|
| 77 |
+
to serialize a tensor that has a hook.
|
| 78 |
+
"""
|
| 79 |
+
f.__torch_unserializable__ = True
|
| 80 |
+
return f
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def warn_if_has_hooks(tensor) -> None:
|
| 84 |
+
if tensor._backward_hooks:
|
| 85 |
+
for k in tensor._backward_hooks:
|
| 86 |
+
hook = tensor._backward_hooks[k]
|
| 87 |
+
if not hasattr(hook, "__torch_unserializable__"):
|
| 88 |
+
warnings.warn(f"backward hook {repr(hook)} on tensor will not be "
|
| 89 |
+
"serialized. If this is expected, you can "
|
| 90 |
+
"decorate the function with @torch.utils.hooks.unserializable_hook "
|
| 91 |
+
"to suppress this warning", stacklevel=2)
|
| 92 |
+
|
| 93 |
+
class BackwardHook:
|
| 94 |
+
"""
|
| 95 |
+
A wrapper class to implement nn.Module backward hooks.
|
| 96 |
+
|
| 97 |
+
It handles:
|
| 98 |
+
- Ignoring non-Tensor inputs and replacing them by None before calling the user hook
|
| 99 |
+
- Generating the proper Node to capture a set of Tensor's gradients
|
| 100 |
+
- Linking the gradients captures for the outputs with the gradients captured for the input
|
| 101 |
+
- Calling the user hook once both output and input gradients are available
|
| 102 |
+
"""
|
| 103 |
+
|
| 104 |
+
def __init__(self, module, user_hooks, user_pre_hooks) -> None:
|
| 105 |
+
self.user_hooks = user_hooks
|
| 106 |
+
self.user_pre_hooks = user_pre_hooks
|
| 107 |
+
self.module = module
|
| 108 |
+
|
| 109 |
+
self.grad_outputs = None
|
| 110 |
+
self.n_outputs = -1
|
| 111 |
+
self.output_tensors_index = None
|
| 112 |
+
self.n_inputs = -1
|
| 113 |
+
self.input_tensors_index = None
|
| 114 |
+
|
| 115 |
+
def _pack_with_none(self, indices, values, size):
|
| 116 |
+
res = [None] * size
|
| 117 |
+
for idx, val in zip(indices, values, strict=True):
|
| 118 |
+
res[idx] = val
|
| 119 |
+
|
| 120 |
+
return tuple(res)
|
| 121 |
+
|
| 122 |
+
def _unpack_none(self, indices, values):
|
| 123 |
+
res = [values[idx] for idx in indices]
|
| 124 |
+
|
| 125 |
+
return tuple(res)
|
| 126 |
+
|
| 127 |
+
def _set_user_hook(self, grad_fn) -> None:
|
| 128 |
+
def hook(grad_input, _):
|
| 129 |
+
if self.grad_outputs is None:
|
| 130 |
+
# This happens because the gradient in your nn.Module flows to
|
| 131 |
+
# the Module's input without " passing through the Module's
|
| 132 |
+
# output, e.g. when you're doing double backward.
|
| 133 |
+
return
|
| 134 |
+
res = self._pack_with_none(self.input_tensors_index, grad_input, self.n_inputs)
|
| 135 |
+
|
| 136 |
+
for hook in self.user_hooks:
|
| 137 |
+
out = hook(self.module, res, self.grad_outputs)
|
| 138 |
+
|
| 139 |
+
if out is None:
|
| 140 |
+
continue
|
| 141 |
+
|
| 142 |
+
if len(out) != len(res):
|
| 143 |
+
raise RuntimeError("Backward hook returned an invalid number of grad_input, "
|
| 144 |
+
f"got {len(out)}, but expected {len(res)}")
|
| 145 |
+
|
| 146 |
+
res = out
|
| 147 |
+
|
| 148 |
+
self.grad_outputs = None
|
| 149 |
+
|
| 150 |
+
return self._unpack_none(self.input_tensors_index, res)
|
| 151 |
+
|
| 152 |
+
grad_fn.register_hook(hook)
|
| 153 |
+
|
| 154 |
+
def _apply_on_tensors(self, fn, args):
|
| 155 |
+
# Can be used to apply the given function to the tensors contained in the
|
| 156 |
+
# args. Will return updated args and the tensors indices
|
| 157 |
+
tensors_idx = []
|
| 158 |
+
tensors = []
|
| 159 |
+
|
| 160 |
+
requires_grad = False
|
| 161 |
+
for i, arg in enumerate(args):
|
| 162 |
+
if isinstance(arg, torch.Tensor):
|
| 163 |
+
tensors_idx.append(i)
|
| 164 |
+
tensors.append(arg)
|
| 165 |
+
requires_grad |= arg.requires_grad
|
| 166 |
+
|
| 167 |
+
if not (requires_grad and torch.is_grad_enabled()):
|
| 168 |
+
return args, None
|
| 169 |
+
|
| 170 |
+
new_tensors = torch.nn.modules._functions.BackwardHookFunction.apply(*tensors)
|
| 171 |
+
if len(new_tensors) == 0:
|
| 172 |
+
raise RuntimeError("Cannot set Module backward hook for a Module with no input Tensors.")
|
| 173 |
+
|
| 174 |
+
grad_fns = [t.grad_fn for t in new_tensors if t.grad_fn is not None and t.grad_fn.name() == "BackwardHookFunctionBackward"]
|
| 175 |
+
if len(grad_fns) == 0:
|
| 176 |
+
raise RuntimeError("Error while setting up backward hooks. Please open "
|
| 177 |
+
"an issue with a code sample to reproduce this.")
|
| 178 |
+
|
| 179 |
+
fn(grad_fns[0])
|
| 180 |
+
|
| 181 |
+
arg_list = list(args)
|
| 182 |
+
for idx, val in zip(tensors_idx, new_tensors, strict=True):
|
| 183 |
+
arg_list[idx] = val
|
| 184 |
+
|
| 185 |
+
if type(args) is tuple:
|
| 186 |
+
out = tuple(arg_list)
|
| 187 |
+
else:
|
| 188 |
+
out = type(args)(*arg_list)
|
| 189 |
+
return out, tensors_idx
|
| 190 |
+
|
| 191 |
+
def setup_input_hook(self, args):
|
| 192 |
+
def fn(grad_fn) -> None:
|
| 193 |
+
self._set_user_hook(grad_fn)
|
| 194 |
+
|
| 195 |
+
res, input_idx = self._apply_on_tensors(fn, args)
|
| 196 |
+
self.n_inputs = len(args)
|
| 197 |
+
self.input_tensors_index = input_idx
|
| 198 |
+
return res
|
| 199 |
+
|
| 200 |
+
def setup_output_hook(self, args):
|
| 201 |
+
def fn(grad_fn) -> None:
|
| 202 |
+
def hook(_, grad_output):
|
| 203 |
+
self.grad_outputs = self._pack_with_none(self.output_tensors_index,
|
| 204 |
+
grad_output,
|
| 205 |
+
self.n_outputs)
|
| 206 |
+
|
| 207 |
+
if self.user_pre_hooks:
|
| 208 |
+
expected_len = len(self.grad_outputs)
|
| 209 |
+
for user_pre_hook in self.user_pre_hooks:
|
| 210 |
+
hook_grad_outputs = user_pre_hook(self.module, self.grad_outputs)
|
| 211 |
+
if hook_grad_outputs is None:
|
| 212 |
+
continue
|
| 213 |
+
|
| 214 |
+
actual_len = len(hook_grad_outputs)
|
| 215 |
+
if actual_len != expected_len:
|
| 216 |
+
raise RuntimeError("Backward pre hook returned an invalid number of grad_output, "
|
| 217 |
+
f"got {actual_len}, but expected {expected_len}")
|
| 218 |
+
self.grad_outputs = hook_grad_outputs
|
| 219 |
+
|
| 220 |
+
# We need to be able to clear self.grad_outputs but also return it
|
| 221 |
+
local_grad_outputs = self.grad_outputs
|
| 222 |
+
|
| 223 |
+
# Special case if no input required gradients, this hook should call the user
|
| 224 |
+
# hook directly
|
| 225 |
+
if self.input_tensors_index is None:
|
| 226 |
+
warnings.warn("Full backward hook is firing when gradients are computed "
|
| 227 |
+
"with respect to module outputs since no inputs require gradients. See "
|
| 228 |
+
"https://docs.pytorch.org/docs/main/generated/torch.nn.Module.html#torch.nn.Module.register_full_backward_hook " # noqa: B950
|
| 229 |
+
"for more details.",
|
| 230 |
+
stacklevel=5)
|
| 231 |
+
grad_inputs = self._pack_with_none([], [], self.n_inputs)
|
| 232 |
+
for user_hook in self.user_hooks:
|
| 233 |
+
res = user_hook(self.module, grad_inputs, self.grad_outputs)
|
| 234 |
+
if res is not None and not (isinstance(res, tuple) and all(el is None for el in res)):
|
| 235 |
+
raise RuntimeError("Backward hook for Modules where no input requires "
|
| 236 |
+
"gradient should always return None or None for all gradients.")
|
| 237 |
+
self.grad_outputs = None
|
| 238 |
+
|
| 239 |
+
if local_grad_outputs is not None:
|
| 240 |
+
if self.output_tensors_index is None:
|
| 241 |
+
raise AssertionError("output_tensors_index should not be None when grad_outputs is not None")
|
| 242 |
+
return tuple(local_grad_outputs[i] for i in self.output_tensors_index)
|
| 243 |
+
|
| 244 |
+
grad_fn.register_hook(hook)
|
| 245 |
+
|
| 246 |
+
is_tuple = True
|
| 247 |
+
if not isinstance(args, tuple):
|
| 248 |
+
args = (args,)
|
| 249 |
+
is_tuple = False
|
| 250 |
+
|
| 251 |
+
res, output_idx = self._apply_on_tensors(fn, args)
|
| 252 |
+
self.n_outputs = len(args)
|
| 253 |
+
self.output_tensors_index = output_idx
|
| 254 |
+
|
| 255 |
+
if not is_tuple:
|
| 256 |
+
res = res[0]
|
| 257 |
+
return res
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/jit/__init__.py
ADDED
|
File without changes
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/jit/log_extract.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from contextlib import contextmanager
|
| 3 |
+
from typing import Any, cast
|
| 4 |
+
import random
|
| 5 |
+
import torch
|
| 6 |
+
import time
|
| 7 |
+
from torch.utils.benchmark import Timer
|
| 8 |
+
|
| 9 |
+
def extract_ir(filename: str) -> list[str]:
|
| 10 |
+
BEGIN = "<GRAPH_EXPORT>"
|
| 11 |
+
END = "</GRAPH_EXPORT>"
|
| 12 |
+
pfx = None
|
| 13 |
+
graphs = []
|
| 14 |
+
with open(filename) as f:
|
| 15 |
+
split_strs = f.read().split(BEGIN)
|
| 16 |
+
for i, split_str in enumerate(split_strs):
|
| 17 |
+
if i == 0:
|
| 18 |
+
continue
|
| 19 |
+
end_loc = split_str.find(END)
|
| 20 |
+
if end_loc == -1:
|
| 21 |
+
continue
|
| 22 |
+
s = split_str[:end_loc]
|
| 23 |
+
pfx = split_strs[i - 1].splitlines()[-1]
|
| 24 |
+
lines = [x[len(pfx):] for x in s.splitlines(keepends=True)]
|
| 25 |
+
graphs.append(''.join(lines))
|
| 26 |
+
|
| 27 |
+
return graphs
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def make_tensor_from_type(inp_type: torch._C.TensorType):
|
| 31 |
+
size = inp_type.sizes()
|
| 32 |
+
stride = inp_type.strides()
|
| 33 |
+
device = inp_type.device()
|
| 34 |
+
dtype = inp_type.dtype()
|
| 35 |
+
if size is None:
|
| 36 |
+
raise AssertionError("make_tensor_from_type: 'size' is None (inp_type.sizes() returned None)")
|
| 37 |
+
if stride is None:
|
| 38 |
+
raise AssertionError("make_tensor_from_type: 'stride' is None (inp_type.strides() returned None)")
|
| 39 |
+
if device is None:
|
| 40 |
+
raise AssertionError("make_tensor_from_type: 'device' is None (inp_type.device() returned None)")
|
| 41 |
+
if dtype is None:
|
| 42 |
+
raise AssertionError("make_tensor_from_type: 'dtype' is None (inp_type.dtype() returned None)")
|
| 43 |
+
return torch.empty_strided(size=size, stride=stride, device=device, dtype=dtype)
|
| 44 |
+
|
| 45 |
+
def load_graph_and_inputs(ir: str) -> tuple[Any, list[Any]]:
|
| 46 |
+
graph = torch._C.parse_ir(ir, parse_tensor_constants=True)
|
| 47 |
+
graph.makeMultiOutputIntoTuple()
|
| 48 |
+
inputs = []
|
| 49 |
+
for inp in graph.inputs():
|
| 50 |
+
if isinstance(inp.type(), torch._C.FloatType):
|
| 51 |
+
inputs.append(random.uniform(.1, 100))
|
| 52 |
+
elif isinstance(inp.type(), torch._C.IntType):
|
| 53 |
+
inputs.append(random.randint(1, 100))
|
| 54 |
+
elif isinstance(inp.type(), torch._C.TensorType):
|
| 55 |
+
tensorType = cast(torch._C.TensorType, inp.type())
|
| 56 |
+
inputs.append(make_tensor_from_type(tensorType))
|
| 57 |
+
elif isinstance(inp.type(), torch._C.BoolType):
|
| 58 |
+
inputs.append(random.randint(0, 1) == 1)
|
| 59 |
+
else:
|
| 60 |
+
raise NotImplementedError(f"A default value is not implemented for type {inp.type()}")
|
| 61 |
+
|
| 62 |
+
func = torch._C._create_function_from_graph("forward", graph)
|
| 63 |
+
torch._C._jit_pass_erase_shape_information(func.graph)
|
| 64 |
+
return (func, inputs)
|
| 65 |
+
|
| 66 |
+
def time_cuda(fn, inputs, test_runs):
|
| 67 |
+
t = Timer(stmt="fn(*inputs)", globals={"fn": fn, "inputs" : inputs})
|
| 68 |
+
times = t.blocked_autorange()
|
| 69 |
+
return times.median * 1000 # time in ms
|
| 70 |
+
|
| 71 |
+
def time_cpu(fn, inputs, test_runs):
|
| 72 |
+
s = time.perf_counter()
|
| 73 |
+
for _ in range(test_runs):
|
| 74 |
+
fn(*inputs)
|
| 75 |
+
e = time.perf_counter()
|
| 76 |
+
return (e - s) / test_runs * 1000 # time in ms
|
| 77 |
+
|
| 78 |
+
def run_test(ir, inputs, *, warmup_runs=10, test_runs=20) -> float:
|
| 79 |
+
graph, _ = load_graph_and_inputs(ir)
|
| 80 |
+
for _ in range(warmup_runs):
|
| 81 |
+
graph(*inputs)
|
| 82 |
+
|
| 83 |
+
is_cpu = None
|
| 84 |
+
for input in inputs:
|
| 85 |
+
if isinstance(input, torch.Tensor):
|
| 86 |
+
is_cpu = input.device.type == "cpu"
|
| 87 |
+
break
|
| 88 |
+
if is_cpu is None:
|
| 89 |
+
raise AssertionError("No tensor found in inputs")
|
| 90 |
+
|
| 91 |
+
out = time_cpu(graph, inputs, test_runs) if is_cpu else time_cuda(graph, inputs, test_runs)
|
| 92 |
+
return out
|
| 93 |
+
|
| 94 |
+
@contextmanager
|
| 95 |
+
def no_fuser(*args, **kwargs):
|
| 96 |
+
old_optimize = torch._C._get_graph_executor_optimize(False)
|
| 97 |
+
try:
|
| 98 |
+
yield
|
| 99 |
+
finally:
|
| 100 |
+
torch._C._get_graph_executor_optimize(old_optimize)
|
| 101 |
+
|
| 102 |
+
def run_baseline_no_fusion(ir, inputs) -> float:
|
| 103 |
+
with no_fuser():
|
| 104 |
+
return run_test(ir, inputs)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def run_nnc(ir, inputs, dynamic) -> float:
|
| 108 |
+
try:
|
| 109 |
+
strat = [("DYNAMIC", 10)] if dynamic else [("STATIC", 10)]
|
| 110 |
+
old_strat = torch.jit.set_fusion_strategy(strat)
|
| 111 |
+
with torch.jit.fuser("fuser1"):
|
| 112 |
+
return run_test(ir, inputs)
|
| 113 |
+
finally:
|
| 114 |
+
torch.jit.set_fusion_strategy(old_strat)
|
| 115 |
+
|
| 116 |
+
def run_nvfuser(ir, inputs) -> float:
|
| 117 |
+
with torch.jit.fuser("fuser2"):
|
| 118 |
+
return run_test(ir, inputs)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/mkldnn.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class MkldnnLinear(torch.jit.ScriptModule):
|
| 6 |
+
def __init__(self, dense_module, dtype) -> None:
|
| 7 |
+
super().__init__()
|
| 8 |
+
self.register_buffer('weight', dense_module.weight.to_mkldnn(dtype))
|
| 9 |
+
if dense_module.bias is not None:
|
| 10 |
+
# Bias can be fp32 or bf16 for OneDNN bf16 path, but for good accuracy,
|
| 11 |
+
# we use fp32 dtype.
|
| 12 |
+
self.register_buffer('bias', dense_module.bias.to_mkldnn())
|
| 13 |
+
else:
|
| 14 |
+
# TODO: Remove this once ScriptModule supports registering None buffer
|
| 15 |
+
self.register_buffer(
|
| 16 |
+
'bias',
|
| 17 |
+
torch.zeros([dense_module.weight.size(0)], dtype=torch.float).to_mkldnn())
|
| 18 |
+
|
| 19 |
+
@torch.jit.script_method
|
| 20 |
+
def __getstate__(self):
|
| 21 |
+
return (self.weight.to_dense(), self.bias.to_dense(), self.training)
|
| 22 |
+
|
| 23 |
+
@torch.jit.script_method
|
| 24 |
+
def __setstate__(self, state):
|
| 25 |
+
self.weight = state[0].to_mkldnn()
|
| 26 |
+
self.bias = state[1].to_mkldnn()
|
| 27 |
+
self.training = state[2]
|
| 28 |
+
|
| 29 |
+
@torch.jit.script_method
|
| 30 |
+
def forward(self, x):
|
| 31 |
+
x_mkldnn = x if x.is_mkldnn else x.to_mkldnn()
|
| 32 |
+
y_mkldnn = torch._C._nn.mkldnn_linear(x_mkldnn, self.weight, self.bias)
|
| 33 |
+
y = y_mkldnn if x.is_mkldnn else y_mkldnn.to_dense()
|
| 34 |
+
return y
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class _MkldnnConvNd(torch.jit.ScriptModule):
|
| 38 |
+
"""Common base of MkldnnConv1d and MkldnnConv2d."""
|
| 39 |
+
|
| 40 |
+
__constants__ = ['stride', 'padding', 'dilation', 'groups']
|
| 41 |
+
|
| 42 |
+
def __init__(self, dense_module) -> None:
|
| 43 |
+
super().__init__()
|
| 44 |
+
|
| 45 |
+
self.stride = dense_module.stride
|
| 46 |
+
self.padding = dense_module.padding
|
| 47 |
+
self.dilation = dense_module.dilation
|
| 48 |
+
self.groups = dense_module.groups
|
| 49 |
+
|
| 50 |
+
if dense_module.bias is not None:
|
| 51 |
+
self.register_buffer('bias', dense_module.bias.to_mkldnn())
|
| 52 |
+
else:
|
| 53 |
+
# Bias can be fp32 or bf16 for OneDNN bf16 path, but for good accuracy,
|
| 54 |
+
# we use fp32 dtype.
|
| 55 |
+
# TODO: Remove this once ScriptModule supports registering None buffer
|
| 56 |
+
self.register_buffer(
|
| 57 |
+
'bias',
|
| 58 |
+
torch.zeros([dense_module.weight.size(0)], dtype=torch.float).to_mkldnn())
|
| 59 |
+
|
| 60 |
+
@torch.jit.script_method
|
| 61 |
+
def __getstate__(self):
|
| 62 |
+
return (self.weight.to_dense(), self.bias.to_dense(), self.training)
|
| 63 |
+
|
| 64 |
+
@torch.jit.script_method
|
| 65 |
+
def forward(self, x):
|
| 66 |
+
return torch.mkldnn_convolution(
|
| 67 |
+
x,
|
| 68 |
+
self.weight,
|
| 69 |
+
self.bias,
|
| 70 |
+
self.padding,
|
| 71 |
+
self.stride,
|
| 72 |
+
self.dilation,
|
| 73 |
+
self.groups)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class MkldnnConv1d(_MkldnnConvNd):
|
| 77 |
+
def __init__(self, dense_module, dtype) -> None:
|
| 78 |
+
super().__init__(dense_module)
|
| 79 |
+
|
| 80 |
+
self.register_buffer('weight', dense_module.weight.to_mkldnn(dtype))
|
| 81 |
+
|
| 82 |
+
@torch.jit.script_method
|
| 83 |
+
def __setstate__(self, state):
|
| 84 |
+
self.weight = state[0].to_mkldnn()
|
| 85 |
+
self.bias = state[1].to_mkldnn()
|
| 86 |
+
self.training = state[2]
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
class MkldnnConv2d(_MkldnnConvNd):
|
| 90 |
+
def __init__(self, dense_module, dtype) -> None:
|
| 91 |
+
super().__init__(dense_module)
|
| 92 |
+
|
| 93 |
+
self.register_buffer('weight', torch._C._nn.mkldnn_reorder_conv2d_weight(
|
| 94 |
+
dense_module.weight.to_mkldnn(dtype),
|
| 95 |
+
self.padding,
|
| 96 |
+
self.stride,
|
| 97 |
+
self.dilation,
|
| 98 |
+
self.groups))
|
| 99 |
+
|
| 100 |
+
@torch.jit.script_method
|
| 101 |
+
def __setstate__(self, state):
|
| 102 |
+
self.weight = torch._C._nn.mkldnn_reorder_conv2d_weight(
|
| 103 |
+
state[0].to_mkldnn(),
|
| 104 |
+
self.padding,
|
| 105 |
+
self.stride,
|
| 106 |
+
self.dilation,
|
| 107 |
+
self.groups)
|
| 108 |
+
self.bias = state[1].to_mkldnn()
|
| 109 |
+
self.training = state[2]
|
| 110 |
+
|
| 111 |
+
class MkldnnConv3d(_MkldnnConvNd):
|
| 112 |
+
def __init__(self, dense_module, dtype) -> None:
|
| 113 |
+
super().__init__(dense_module)
|
| 114 |
+
|
| 115 |
+
self.register_buffer('weight', torch._C._nn.mkldnn_reorder_conv3d_weight(
|
| 116 |
+
dense_module.weight.to_mkldnn(dtype),
|
| 117 |
+
self.padding,
|
| 118 |
+
self.stride,
|
| 119 |
+
self.dilation,
|
| 120 |
+
self.groups))
|
| 121 |
+
|
| 122 |
+
@torch.jit.script_method
|
| 123 |
+
def __setstate__(self, state):
|
| 124 |
+
self.weight = torch._C._nn.mkldnn_reorder_conv3d_weight(
|
| 125 |
+
state[0].to_mkldnn(),
|
| 126 |
+
self.padding,
|
| 127 |
+
self.stride,
|
| 128 |
+
self.dilation,
|
| 129 |
+
self.groups)
|
| 130 |
+
self.bias = state[1].to_mkldnn()
|
| 131 |
+
self.training = state[2]
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class MkldnnBatchNorm(torch.jit.ScriptModule):
|
| 135 |
+
__constants__ = ['exponential_average_factor', 'eps']
|
| 136 |
+
|
| 137 |
+
def __init__(self, dense_module) -> None:
|
| 138 |
+
super().__init__()
|
| 139 |
+
|
| 140 |
+
if dense_module.training:
|
| 141 |
+
raise AssertionError("Only support eval mode batchnorm for mkldnn path now")
|
| 142 |
+
if not dense_module.track_running_stats:
|
| 143 |
+
raise AssertionError("Only support track_running_stats=True for mkldnn path now")
|
| 144 |
+
if not dense_module.affine:
|
| 145 |
+
raise AssertionError("Only support affine=True for mkldnn path now")
|
| 146 |
+
|
| 147 |
+
if dense_module.momentum is None:
|
| 148 |
+
self.exponential_average_factor = 0.0
|
| 149 |
+
else:
|
| 150 |
+
self.exponential_average_factor = dense_module.momentum
|
| 151 |
+
self.eps = dense_module.eps
|
| 152 |
+
|
| 153 |
+
self.register_buffer('weight', dense_module.weight.to_mkldnn())
|
| 154 |
+
self.register_buffer('bias', dense_module.bias.to_mkldnn())
|
| 155 |
+
self.register_buffer('running_mean', dense_module.running_mean.to_mkldnn())
|
| 156 |
+
self.register_buffer('running_var', dense_module.running_var.to_mkldnn())
|
| 157 |
+
|
| 158 |
+
@torch.jit.script_method
|
| 159 |
+
def __getstate__(self):
|
| 160 |
+
weight = self.weight.to_dense()
|
| 161 |
+
bias = self.bias.to_dense()
|
| 162 |
+
running_mean = self.running_mean.to_dense()
|
| 163 |
+
running_var = self.running_var.to_dense()
|
| 164 |
+
return (weight, bias, running_mean, running_var, self.training)
|
| 165 |
+
|
| 166 |
+
@torch.jit.script_method
|
| 167 |
+
def __setstate__(self, state):
|
| 168 |
+
self.weight = state[0].to_mkldnn()
|
| 169 |
+
self.bias = state[1].to_mkldnn()
|
| 170 |
+
self.running_mean = state[2].to_mkldnn()
|
| 171 |
+
self.running_var = state[3].to_mkldnn()
|
| 172 |
+
self.training = state[4]
|
| 173 |
+
|
| 174 |
+
@torch.jit.script_method
|
| 175 |
+
def forward(self, x):
|
| 176 |
+
return torch.batch_norm(
|
| 177 |
+
x,
|
| 178 |
+
self.weight,
|
| 179 |
+
self.bias,
|
| 180 |
+
self.running_mean,
|
| 181 |
+
self.running_var,
|
| 182 |
+
False, # training
|
| 183 |
+
self.exponential_average_factor,
|
| 184 |
+
self.eps,
|
| 185 |
+
False, # cuda_enabled
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
class MkldnnPrelu(torch.jit.ScriptModule):
|
| 189 |
+
def __init__(self, dense_module, dtype) -> None:
|
| 190 |
+
super().__init__()
|
| 191 |
+
self.register_buffer('weight', dense_module.weight.to_mkldnn(dtype))
|
| 192 |
+
|
| 193 |
+
@torch.jit.script_method
|
| 194 |
+
def __getstate__(self):
|
| 195 |
+
return (self.weight.to_dense(), self.training)
|
| 196 |
+
|
| 197 |
+
@torch.jit.script_method
|
| 198 |
+
def __setstate__(self, state):
|
| 199 |
+
self.weight = state[0].to_mkldnn()
|
| 200 |
+
self.training = state[1]
|
| 201 |
+
|
| 202 |
+
@torch.jit.script_method
|
| 203 |
+
def forward(self, x):
|
| 204 |
+
x_mkldnn = x if x.is_mkldnn else x.to_mkldnn()
|
| 205 |
+
y_mkldnn = torch.prelu(x_mkldnn, self.weight)
|
| 206 |
+
y = y_mkldnn if x.is_mkldnn else y_mkldnn.to_dense()
|
| 207 |
+
return y
|
| 208 |
+
|
| 209 |
+
def to_mkldnn(module, dtype=torch.float):
|
| 210 |
+
if dtype not in (torch.float, torch.bfloat16, torch.half):
|
| 211 |
+
raise AssertionError("MKLDNN only support float, bfloat16, and half path now")
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
def m_fn(m, d):
|
| 215 |
+
if isinstance(m, torch.nn.Linear):
|
| 216 |
+
return MkldnnLinear(m, d)
|
| 217 |
+
elif isinstance(m, torch.nn.Conv1d):
|
| 218 |
+
return MkldnnConv1d(m, d)
|
| 219 |
+
elif isinstance(m, torch.nn.Conv2d):
|
| 220 |
+
return MkldnnConv2d(m, d)
|
| 221 |
+
elif isinstance(m, torch.nn.Conv3d):
|
| 222 |
+
return MkldnnConv3d(m, d)
|
| 223 |
+
elif isinstance(m, (torch.nn.BatchNorm2d, torch.nn.BatchNorm3d)):
|
| 224 |
+
# For batchnorm bf16 path, OneDNN requires weight and bias need fp32 dtype.
|
| 225 |
+
# so it doesn't need dtype argument.
|
| 226 |
+
return MkldnnBatchNorm(m)
|
| 227 |
+
elif isinstance(m, torch.nn.PReLU):
|
| 228 |
+
return MkldnnPrelu(m, d)
|
| 229 |
+
else:
|
| 230 |
+
return m
|
| 231 |
+
|
| 232 |
+
def m_fn_rec(m, d):
|
| 233 |
+
new_m = m_fn(m, d)
|
| 234 |
+
for name, sub_m in m.named_children():
|
| 235 |
+
setattr(new_m, name, m_fn_rec(sub_m, d))
|
| 236 |
+
return new_m
|
| 237 |
+
|
| 238 |
+
return m_fn_rec(module, dtype)
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/mobile_optimizer.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
"""This module contains utility method for mobile model optimization and lint."""
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from enum import Enum
|
| 6 |
+
from torch._C import _MobileOptimizerType as MobileOptimizerType
|
| 7 |
+
from typing import AnyStr
|
| 8 |
+
|
| 9 |
+
class LintCode(Enum):
|
| 10 |
+
BUNDLED_INPUT = 1
|
| 11 |
+
REQUIRES_GRAD = 2
|
| 12 |
+
DROPOUT = 3
|
| 13 |
+
BATCHNORM = 4
|
| 14 |
+
|
| 15 |
+
def optimize_for_mobile(
|
| 16 |
+
script_module: torch.jit.ScriptModule,
|
| 17 |
+
optimization_blocklist: set[MobileOptimizerType] | None = None,
|
| 18 |
+
preserved_methods: list[AnyStr] | None = None,
|
| 19 |
+
backend: str = 'CPU') -> torch.jit.RecursiveScriptModule:
|
| 20 |
+
"""
|
| 21 |
+
Optimize a torch script module for mobile deployment.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
script_module: An instance of torch script module with type of ScriptModule.
|
| 25 |
+
optimization_blocklist: A set with type of MobileOptimizerType. When set is not passed,
|
| 26 |
+
optimization method will run all the optimizer pass; otherwise, optimizer
|
| 27 |
+
method will run the optimization pass that is not included inside optimization_blocklist.
|
| 28 |
+
preserved_methods: A list of methods that needed to be preserved when freeze_module pass is invoked
|
| 29 |
+
backend: Device type to use for running the result model ('CPU'(default), 'Vulkan' or 'Metal').
|
| 30 |
+
Returns:
|
| 31 |
+
A new optimized torch script module
|
| 32 |
+
"""
|
| 33 |
+
if not isinstance(script_module, torch.jit.ScriptModule):
|
| 34 |
+
raise TypeError(
|
| 35 |
+
f'Got {type(script_module)}, but ScriptModule is expected.')
|
| 36 |
+
|
| 37 |
+
if optimization_blocklist is None:
|
| 38 |
+
optimization_blocklist = set()
|
| 39 |
+
|
| 40 |
+
if preserved_methods is None:
|
| 41 |
+
preserved_methods = []
|
| 42 |
+
|
| 43 |
+
# Convert potential byte arrays into strings (if there is any) to pass type checking
|
| 44 |
+
# Here we use a new name as assigning it back to preserved_methods will invoke
|
| 45 |
+
# mypy errors (i.e. List[AnyStr] = List[str])
|
| 46 |
+
preserved_methods_str: list[str] = [str(method) for method in preserved_methods]
|
| 47 |
+
|
| 48 |
+
bundled_inputs_attributes = _get_bundled_inputs_preserved_attributes(script_module, preserved_methods_str)
|
| 49 |
+
if all(hasattr(script_module, method) for method in bundled_inputs_attributes):
|
| 50 |
+
preserved_methods_str = list(set(preserved_methods_str + bundled_inputs_attributes))
|
| 51 |
+
|
| 52 |
+
non_exist_methods = [method for method in preserved_methods_str if not hasattr(script_module, method)]
|
| 53 |
+
if non_exist_methods:
|
| 54 |
+
raise AttributeError(
|
| 55 |
+
f"The following methods to preserve do not exist in script_module: {', '.join(non_exist_methods)}")
|
| 56 |
+
|
| 57 |
+
backend = backend.lower()
|
| 58 |
+
if backend == 'cpu':
|
| 59 |
+
optimized_cpp_module = torch._C._jit_pass_optimize_for_mobile(
|
| 60 |
+
script_module._c,
|
| 61 |
+
optimization_blocklist,
|
| 62 |
+
preserved_methods_str)
|
| 63 |
+
elif backend == 'vulkan':
|
| 64 |
+
optimized_cpp_module = torch._C._jit_pass_vulkan_optimize_for_mobile(
|
| 65 |
+
script_module._c,
|
| 66 |
+
optimization_blocklist,
|
| 67 |
+
preserved_methods_str)
|
| 68 |
+
elif backend == 'metal':
|
| 69 |
+
optimized_cpp_module = torch._C._jit_pass_metal_optimize_for_mobile(script_module._c, preserved_methods_str)
|
| 70 |
+
else:
|
| 71 |
+
raise TypeError("Unknown backend, must be one of 'CPU', 'Vulkan' or 'Metal'")
|
| 72 |
+
|
| 73 |
+
return torch.jit._recursive.wrap_cpp_module(optimized_cpp_module)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def generate_mobile_module_lints(script_module: torch.jit.ScriptModule):
|
| 77 |
+
"""
|
| 78 |
+
Generate a list of lints for a given torch script module.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
script_module: An instance of torch script module with type of ScriptModule.
|
| 82 |
+
|
| 83 |
+
Returns:
|
| 84 |
+
lint_map: A list of dictionary that contains modules lints
|
| 85 |
+
"""
|
| 86 |
+
if not isinstance(script_module, torch.jit.ScriptModule):
|
| 87 |
+
raise TypeError(
|
| 88 |
+
f'Got {type(script_module)}, but ScriptModule is expected.')
|
| 89 |
+
|
| 90 |
+
lint_list = []
|
| 91 |
+
|
| 92 |
+
if not hasattr(script_module, "_generate_bundled_inputs_for_forward"):
|
| 93 |
+
lint_list.append({"name": LintCode.BUNDLED_INPUT.name, "message": "No bundled input for forward, please add bundled inputs "
|
| 94 |
+
"before saving the module using torch.utils.bundled_inputs.augment_model_with_bundled_inputs."})
|
| 95 |
+
|
| 96 |
+
for name, param in script_module.named_parameters():
|
| 97 |
+
if param.requires_grad:
|
| 98 |
+
lint_list.append({"name": LintCode.REQUIRES_GRAD.name, "message": f"Param {name} requires grad, "
|
| 99 |
+
"please set torch.no_grad() to reduce memory usage and improve computation speed during "
|
| 100 |
+
"inference phase."})
|
| 101 |
+
|
| 102 |
+
op_names = torch.jit.export_opnames(script_module)
|
| 103 |
+
for op_name in op_names:
|
| 104 |
+
if "dropout" in op_name:
|
| 105 |
+
lint_list.append({"name": LintCode.DROPOUT.name,
|
| 106 |
+
"message": f"Operator {op_name} exists, remember to call eval() before "
|
| 107 |
+
"saving the module.and call torch.utils.mobile_optimizer.optimize_for_mobile to drop dropout "
|
| 108 |
+
"operator."})
|
| 109 |
+
if "batch_norm" in op_name:
|
| 110 |
+
lint_list.append({"name": LintCode.BATCHNORM.name,
|
| 111 |
+
"message": f"Operator {op_name} exists, remember to call eval() before "
|
| 112 |
+
"saving the module and call torch.utils.mobile_optimizer.optimize_for_mobile to drop batch_norm "
|
| 113 |
+
"operator."})
|
| 114 |
+
|
| 115 |
+
return lint_list
|
| 116 |
+
|
| 117 |
+
def _get_bundled_inputs_preserved_attributes(script_module: torch.jit.ScriptModule, preserved_methods: list[str]) -> list[str]:
|
| 118 |
+
|
| 119 |
+
bundled_inputs_attributes = []
|
| 120 |
+
# Has bundled inputs for forward
|
| 121 |
+
if hasattr(script_module, 'get_all_bundled_inputs'):
|
| 122 |
+
bundled_inputs_attributes.append('get_all_bundled_inputs')
|
| 123 |
+
bundled_inputs_attributes.append('get_num_bundled_inputs')
|
| 124 |
+
|
| 125 |
+
# Bundled inputs in module after the change that introduced bundled inputs for multiple functions
|
| 126 |
+
if hasattr(script_module, 'get_bundled_inputs_functions_and_info'):
|
| 127 |
+
bundled_inputs_attributes.append('get_bundled_inputs_functions_and_info')
|
| 128 |
+
all_info = script_module.get_bundled_inputs_functions_and_info()
|
| 129 |
+
for function_name in all_info:
|
| 130 |
+
if function_name not in preserved_methods:
|
| 131 |
+
bundled_inputs_attributes.append(function_name)
|
| 132 |
+
bundled_inputs_attributes.append("get_all_bundled_inputs_for_" + function_name)
|
| 133 |
+
bundled_inputs_attributes.append("_bundled_inputs_deflated_" + function_name)
|
| 134 |
+
|
| 135 |
+
return bundled_inputs_attributes
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/__init__.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# mypy: allow-untyped-defs
|
| 3 |
+
"""
|
| 4 |
+
model_dump: a one-stop shop for TorchScript model inspection.
|
| 5 |
+
|
| 6 |
+
The goal of this tool is to provide a simple way to extract lots of
|
| 7 |
+
useful information from a TorchScript model and make it easy for humans
|
| 8 |
+
to consume. It (mostly) replaces zipinfo, common uses of show_pickle,
|
| 9 |
+
and various ad-hoc analysis notebooks.
|
| 10 |
+
|
| 11 |
+
The tool extracts information from the model and serializes it as JSON.
|
| 12 |
+
That JSON can then be rendered by an HTML+JS page, either by
|
| 13 |
+
loading the JSON over HTTP or producing a fully self-contained page
|
| 14 |
+
with all of the code and data burned-in.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
# Maintainer notes follow.
|
| 18 |
+
"""
|
| 19 |
+
The implementation strategy has tension between 3 goals:
|
| 20 |
+
- Small file size.
|
| 21 |
+
- Fully self-contained.
|
| 22 |
+
- Easy, modern JS environment.
|
| 23 |
+
Using Preact and HTM achieves 1 and 2 with a decent result for 3.
|
| 24 |
+
However, the models I tested with result in ~1MB JSON output,
|
| 25 |
+
so even using something heavier like full React might be tolerable
|
| 26 |
+
if the build process can be worked out.
|
| 27 |
+
|
| 28 |
+
One principle I have followed that I think is very beneficial
|
| 29 |
+
is to keep the JSON data as close as possible to the model
|
| 30 |
+
and do most of the rendering logic on the client.
|
| 31 |
+
This makes for easier development (just refresh, usually),
|
| 32 |
+
allows for more laziness and dynamism, and lets us add more
|
| 33 |
+
views of the same data without bloating the HTML file.
|
| 34 |
+
|
| 35 |
+
Currently, this code doesn't actually load the model or even
|
| 36 |
+
depend on any part of PyTorch. I don't know if that's an important
|
| 37 |
+
feature to maintain, but it's probably worth preserving the ability
|
| 38 |
+
to run at least basic analysis on models that cannot be loaded.
|
| 39 |
+
|
| 40 |
+
I think the easiest way to develop this code is to cd into model_dump and
|
| 41 |
+
run "python -m http.server", then load http://localhost:8000/skeleton.html
|
| 42 |
+
in the browser. In another terminal, run
|
| 43 |
+
"python -m torch.utils.model_dump --style=json FILE > \
|
| 44 |
+
torch/utils/model_dump/model_info.json"
|
| 45 |
+
every time you update the Python code or model.
|
| 46 |
+
When you update JS, just refresh.
|
| 47 |
+
|
| 48 |
+
Possible improvements:
|
| 49 |
+
- Fix various TODO comments in this file and the JS.
|
| 50 |
+
- Make the HTML much less janky, especially the auxiliary data panel.
|
| 51 |
+
- Make the auxiliary data panel start small, expand when
|
| 52 |
+
data is available, and have a button to clear/contract.
|
| 53 |
+
- Clean up the JS. There's a lot of copypasta because
|
| 54 |
+
I don't really know how to use Preact.
|
| 55 |
+
- Make the HTML render and work nicely inside a Jupyter notebook.
|
| 56 |
+
- Add the ability for JS to choose the URL to load the JSON based
|
| 57 |
+
on the page URL (query or hash). That way we could publish the
|
| 58 |
+
inlined skeleton once and have it load various JSON blobs.
|
| 59 |
+
- Add a button to expand all expandable sections so ctrl-F works well.
|
| 60 |
+
- Add hyperlinking from data to code, and code to code.
|
| 61 |
+
- Add hyperlinking from debug info to Diffusion.
|
| 62 |
+
- Make small tensor contents available.
|
| 63 |
+
- Do something nice for quantized models
|
| 64 |
+
(they probably don't work at all right now).
|
| 65 |
+
"""
|
| 66 |
+
|
| 67 |
+
import argparse
|
| 68 |
+
import io
|
| 69 |
+
import itertools
|
| 70 |
+
import json
|
| 71 |
+
import os
|
| 72 |
+
import pickle
|
| 73 |
+
import pprint
|
| 74 |
+
import re
|
| 75 |
+
import sys
|
| 76 |
+
import urllib.parse
|
| 77 |
+
import zipfile
|
| 78 |
+
from pathlib import Path
|
| 79 |
+
import warnings
|
| 80 |
+
|
| 81 |
+
import torch.utils.show_pickle
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
DEFAULT_EXTRA_FILE_SIZE_LIMIT = 16 * 1024
|
| 85 |
+
|
| 86 |
+
__all__ = ['get_storage_info', 'hierarchical_pickle', 'get_model_info', 'get_inline_skeleton',
|
| 87 |
+
'burn_in_info', 'get_info_and_burn_skeleton']
|
| 88 |
+
|
| 89 |
+
def get_storage_info(storage):
|
| 90 |
+
if not isinstance(storage, torch.utils.show_pickle.FakeObject):
|
| 91 |
+
raise AssertionError(f"storage is not FakeObject: {type(storage)}")
|
| 92 |
+
if storage.module != "pers":
|
| 93 |
+
raise AssertionError(f"storage.module is not 'pers': {storage.module!r}")
|
| 94 |
+
if storage.name != "obj":
|
| 95 |
+
raise AssertionError(f"storage.name is not 'obj': {storage.name!r}")
|
| 96 |
+
if storage.state is not None:
|
| 97 |
+
raise AssertionError(f"storage.state is not None: {storage.state!r}")
|
| 98 |
+
if not isinstance(storage.args, tuple):
|
| 99 |
+
raise AssertionError(f"storage.args is not a tuple: {type(storage.args)}")
|
| 100 |
+
if len(storage.args) != 1:
|
| 101 |
+
raise AssertionError(f"len(storage.args) is not 1: {len(storage.args)}")
|
| 102 |
+
sa = storage.args[0]
|
| 103 |
+
if not isinstance(sa, tuple):
|
| 104 |
+
raise AssertionError(f"sa is not a tuple: {type(sa)}")
|
| 105 |
+
if len(sa) != 5:
|
| 106 |
+
raise AssertionError(f"len(sa) is not 5: {len(sa)}")
|
| 107 |
+
if sa[0] != "storage":
|
| 108 |
+
raise AssertionError(f"sa[0] is not 'storage': {sa[0]!r}")
|
| 109 |
+
if not isinstance(sa[1], torch.utils.show_pickle.FakeClass):
|
| 110 |
+
raise AssertionError(f"sa[1] is not FakeClass: {type(sa[1])}")
|
| 111 |
+
if sa[1].module != "torch":
|
| 112 |
+
raise AssertionError(f"sa[1].module is not 'torch': {sa[1].module!r}")
|
| 113 |
+
if not sa[1].name.endswith("Storage"):
|
| 114 |
+
raise AssertionError(f"sa[1].name does not end with 'Storage': {sa[1].name!r}")
|
| 115 |
+
storage_info = [sa[1].name.replace("Storage", "")] + list(sa[2:])
|
| 116 |
+
return storage_info
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def hierarchical_pickle(data):
|
| 120 |
+
if isinstance(data, (bool, int, float, str, type(None))):
|
| 121 |
+
return data
|
| 122 |
+
if isinstance(data, list):
|
| 123 |
+
return [hierarchical_pickle(d) for d in data]
|
| 124 |
+
if isinstance(data, tuple):
|
| 125 |
+
return {
|
| 126 |
+
"__tuple_values__": hierarchical_pickle(list(data)),
|
| 127 |
+
}
|
| 128 |
+
if isinstance(data, dict):
|
| 129 |
+
return {
|
| 130 |
+
"__is_dict__": True,
|
| 131 |
+
"keys": hierarchical_pickle(list(data.keys())),
|
| 132 |
+
"values": hierarchical_pickle(list(data.values())),
|
| 133 |
+
}
|
| 134 |
+
if isinstance(data, torch.utils.show_pickle.FakeObject):
|
| 135 |
+
typename = f"{data.module}.{data.name}"
|
| 136 |
+
if (
|
| 137 |
+
typename.startswith(('__torch__.', 'torch.jit.LoweredWrapper.', 'torch.jit.LoweredModule.'))
|
| 138 |
+
):
|
| 139 |
+
if data.args != ():
|
| 140 |
+
raise AssertionError("data.args is not ()")
|
| 141 |
+
return {
|
| 142 |
+
"__module_type__": typename,
|
| 143 |
+
"state": hierarchical_pickle(data.state),
|
| 144 |
+
}
|
| 145 |
+
if typename == "torch._utils._rebuild_tensor_v2":
|
| 146 |
+
if data.state is not None:
|
| 147 |
+
raise AssertionError("data.state is not None")
|
| 148 |
+
storage, offset, size, stride, requires_grad, *_ = data.args
|
| 149 |
+
storage_info = get_storage_info(storage)
|
| 150 |
+
return {"__tensor_v2__": [storage_info, offset, size, stride, requires_grad]}
|
| 151 |
+
if typename == "torch._utils._rebuild_qtensor":
|
| 152 |
+
if data.state is not None:
|
| 153 |
+
raise AssertionError("data.state is not None")
|
| 154 |
+
storage, offset, size, stride, quantizer, requires_grad, *_ = data.args
|
| 155 |
+
storage_info = get_storage_info(storage)
|
| 156 |
+
if not isinstance(quantizer, tuple):
|
| 157 |
+
raise AssertionError("quantizer is not a tuple")
|
| 158 |
+
if not isinstance(quantizer[0], torch.utils.show_pickle.FakeClass):
|
| 159 |
+
raise AssertionError("quantizer[0] is not a FakeClass")
|
| 160 |
+
if quantizer[0].module != "torch":
|
| 161 |
+
raise AssertionError("quantizer[0].module is not torch")
|
| 162 |
+
if quantizer[0].name == "per_tensor_affine":
|
| 163 |
+
if len(quantizer) != 3:
|
| 164 |
+
raise AssertionError("len(quantizer) is not 3")
|
| 165 |
+
if not isinstance(quantizer[1], float):
|
| 166 |
+
raise AssertionError("quantizer[1] is not a float")
|
| 167 |
+
if not isinstance(quantizer[2], int):
|
| 168 |
+
raise AssertionError("quantizer[2] is not an int")
|
| 169 |
+
quantizer_extra = list(quantizer[1:3])
|
| 170 |
+
else:
|
| 171 |
+
quantizer_extra = []
|
| 172 |
+
quantizer_json = [quantizer[0].name] + quantizer_extra
|
| 173 |
+
return {"__qtensor__": [storage_info, offset, size, stride, quantizer_json, requires_grad]}
|
| 174 |
+
if typename == "torch.jit._pickle.restore_type_tag":
|
| 175 |
+
if data.state is not None:
|
| 176 |
+
raise AssertionError("data.state is not None")
|
| 177 |
+
obj, typ = data.args
|
| 178 |
+
if not isinstance(typ, str):
|
| 179 |
+
raise AssertionError("typ is not a string")
|
| 180 |
+
return hierarchical_pickle(obj)
|
| 181 |
+
if re.fullmatch(r"torch\.jit\._pickle\.build_[a-z]+list", typename):
|
| 182 |
+
if data.state is not None:
|
| 183 |
+
raise AssertionError("data.state is not None")
|
| 184 |
+
ls, = data.args
|
| 185 |
+
if not isinstance(ls, list):
|
| 186 |
+
raise AssertionError("ls is not a list")
|
| 187 |
+
return hierarchical_pickle(ls)
|
| 188 |
+
if typename == "torch.device":
|
| 189 |
+
if data.state is not None:
|
| 190 |
+
raise AssertionError("data.state is not None")
|
| 191 |
+
name, = data.args
|
| 192 |
+
if not isinstance(name, str):
|
| 193 |
+
raise AssertionError("name is not a string")
|
| 194 |
+
# Just forget that it was a device and return the name.
|
| 195 |
+
return name
|
| 196 |
+
if typename == "builtin.UnicodeDecodeError":
|
| 197 |
+
if data.state is not None:
|
| 198 |
+
raise AssertionError("data.state is not None")
|
| 199 |
+
msg, = data.args
|
| 200 |
+
if not isinstance(msg, str):
|
| 201 |
+
raise AssertionError("msg is not a string")
|
| 202 |
+
# Hack: Pretend this is a module so we don't need custom serialization.
|
| 203 |
+
# Hack: Wrap the message in a tuple so it looks like a nice state object.
|
| 204 |
+
# TODO: Undo at least that second hack. We should support string states.
|
| 205 |
+
return {
|
| 206 |
+
"__module_type__": typename,
|
| 207 |
+
"state": hierarchical_pickle((msg,)),
|
| 208 |
+
}
|
| 209 |
+
raise Exception(f"Can't prepare fake object of type for JS: {typename}") # noqa: TRY002
|
| 210 |
+
raise Exception(f"Can't prepare data of type for JS: {type(data)}") # noqa: TRY002
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def get_model_info(
|
| 214 |
+
path_or_file,
|
| 215 |
+
title=None,
|
| 216 |
+
extra_file_size_limit=DEFAULT_EXTRA_FILE_SIZE_LIMIT):
|
| 217 |
+
"""Get JSON-friendly information about a model.
|
| 218 |
+
|
| 219 |
+
The result is suitable for being saved as model_info.json,
|
| 220 |
+
or passed to burn_in_info.
|
| 221 |
+
"""
|
| 222 |
+
|
| 223 |
+
if isinstance(path_or_file, os.PathLike):
|
| 224 |
+
default_title = os.fspath(path_or_file)
|
| 225 |
+
file_size = path_or_file.stat().st_size # type: ignore[attr-defined]
|
| 226 |
+
elif isinstance(path_or_file, str):
|
| 227 |
+
default_title = path_or_file
|
| 228 |
+
file_size = Path(path_or_file).stat().st_size
|
| 229 |
+
else:
|
| 230 |
+
default_title = "buffer"
|
| 231 |
+
path_or_file.seek(0, io.SEEK_END)
|
| 232 |
+
file_size = path_or_file.tell()
|
| 233 |
+
path_or_file.seek(0)
|
| 234 |
+
|
| 235 |
+
title = title or default_title
|
| 236 |
+
|
| 237 |
+
with zipfile.ZipFile(path_or_file) as zf:
|
| 238 |
+
path_prefix = None
|
| 239 |
+
zip_files = []
|
| 240 |
+
# pyrefly: ignore [bad-assignment]
|
| 241 |
+
for zi in zf.infolist():
|
| 242 |
+
prefix = re.sub("/.*", "", zi.filename)
|
| 243 |
+
if path_prefix is None:
|
| 244 |
+
path_prefix = prefix
|
| 245 |
+
elif prefix != path_prefix:
|
| 246 |
+
raise Exception(f"Mismatched prefixes: {path_prefix} != {prefix}") # noqa: TRY002
|
| 247 |
+
zip_files.append(
|
| 248 |
+
{
|
| 249 |
+
"filename": zi.filename,
|
| 250 |
+
"compression": zi.compress_type,
|
| 251 |
+
"compressed_size": zi.compress_size,
|
| 252 |
+
"file_size": zi.file_size,
|
| 253 |
+
}
|
| 254 |
+
)
|
| 255 |
+
if path_prefix is None:
|
| 256 |
+
raise AssertionError("path_prefix is None")
|
| 257 |
+
version = zf.read(path_prefix + "/version").decode("utf-8").strip()
|
| 258 |
+
|
| 259 |
+
def get_pickle(name):
|
| 260 |
+
if path_prefix is None:
|
| 261 |
+
raise AssertionError("path_prefix is None")
|
| 262 |
+
with zf.open(path_prefix + f"/{name}.pkl") as handle:
|
| 263 |
+
raw = torch.utils.show_pickle.DumpUnpickler(handle, catch_invalid_utf8=True).load()
|
| 264 |
+
return hierarchical_pickle(raw)
|
| 265 |
+
|
| 266 |
+
model_data = get_pickle("data")
|
| 267 |
+
constants = get_pickle("constants")
|
| 268 |
+
|
| 269 |
+
# Intern strings that are likely to be reused.
|
| 270 |
+
# Pickle automatically detects shared structure,
|
| 271 |
+
# so reused strings are stored efficiently.
|
| 272 |
+
# However, JSON has no way of representing this,
|
| 273 |
+
# so we have to do it manually.
|
| 274 |
+
interned_strings : dict[str, int] = {}
|
| 275 |
+
|
| 276 |
+
def intern(s):
|
| 277 |
+
if s not in interned_strings:
|
| 278 |
+
interned_strings[s] = len(interned_strings)
|
| 279 |
+
return interned_strings[s]
|
| 280 |
+
|
| 281 |
+
code_files = {}
|
| 282 |
+
for zi in zf.infolist():
|
| 283 |
+
if not zi.filename.endswith(".py"):
|
| 284 |
+
continue
|
| 285 |
+
with zf.open(zi) as handle:
|
| 286 |
+
raw_code = handle.read()
|
| 287 |
+
with zf.open(zi.filename + ".debug_pkl") as handle:
|
| 288 |
+
raw_debug = handle.read()
|
| 289 |
+
|
| 290 |
+
# Parse debug info and add begin/end markers if not present
|
| 291 |
+
# to ensure that we cover the entire source code.
|
| 292 |
+
debug_info_t = pickle.loads(raw_debug)
|
| 293 |
+
text_table = None
|
| 294 |
+
|
| 295 |
+
if (len(debug_info_t) == 3 and
|
| 296 |
+
isinstance(debug_info_t[0], str) and
|
| 297 |
+
debug_info_t[0] == 'FORMAT_WITH_STRING_TABLE'):
|
| 298 |
+
_, text_table, content = debug_info_t
|
| 299 |
+
|
| 300 |
+
def parse_new_format(line):
|
| 301 |
+
# (0, (('', '', 0), 0, 0))
|
| 302 |
+
num, ((text_indexes, fname_idx, offset), start, end), tag = line
|
| 303 |
+
text = ''.join(text_table[x] for x in text_indexes) # type: ignore[index]
|
| 304 |
+
fname = text_table[fname_idx] # type: ignore[index]
|
| 305 |
+
return num, ((text, fname, offset), start, end), tag
|
| 306 |
+
|
| 307 |
+
debug_info_t = map(parse_new_format, content)
|
| 308 |
+
|
| 309 |
+
debug_info = list(debug_info_t)
|
| 310 |
+
if not debug_info:
|
| 311 |
+
debug_info.append((0, (('', '', 0), 0, 0)))
|
| 312 |
+
if debug_info[-1][0] != len(raw_code):
|
| 313 |
+
debug_info.append((len(raw_code), (('', '', 0), 0, 0)))
|
| 314 |
+
|
| 315 |
+
code_parts = []
|
| 316 |
+
for di, di_next in itertools.pairwise(debug_info):
|
| 317 |
+
start, source_range, *_ = di
|
| 318 |
+
end = di_next[0]
|
| 319 |
+
if end <= start:
|
| 320 |
+
raise AssertionError("end is not greater than start")
|
| 321 |
+
source, s_start, s_end = source_range
|
| 322 |
+
s_text, s_file, s_line = source
|
| 323 |
+
# TODO: Handle this case better. TorchScript ranges are in bytes,
|
| 324 |
+
# but JS doesn't really handle byte strings.
|
| 325 |
+
# if bytes and chars are not equivalent for this string,
|
| 326 |
+
# zero out the ranges so we don't highlight the wrong thing.
|
| 327 |
+
if len(s_text) != len(s_text.encode("utf-8")):
|
| 328 |
+
s_start = 0
|
| 329 |
+
s_end = 0
|
| 330 |
+
text = raw_code[start:end]
|
| 331 |
+
code_parts.append([text.decode("utf-8"), intern(s_file), s_line, intern(s_text), s_start, s_end])
|
| 332 |
+
code_files[zi.filename] = code_parts
|
| 333 |
+
|
| 334 |
+
extra_files_json_pattern = re.compile(re.escape(path_prefix) + "/extra/.*\\.json")
|
| 335 |
+
extra_files_jsons = {}
|
| 336 |
+
for zi in zf.infolist():
|
| 337 |
+
if not extra_files_json_pattern.fullmatch(zi.filename):
|
| 338 |
+
continue
|
| 339 |
+
if zi.file_size > extra_file_size_limit:
|
| 340 |
+
continue
|
| 341 |
+
with zf.open(zi) as handle:
|
| 342 |
+
try:
|
| 343 |
+
json_content = json.load(handle)
|
| 344 |
+
extra_files_jsons[zi.filename] = json_content
|
| 345 |
+
except json.JSONDecodeError:
|
| 346 |
+
extra_files_jsons[zi.filename] = "INVALID JSON"
|
| 347 |
+
|
| 348 |
+
always_render_pickles = {
|
| 349 |
+
"bytecode.pkl",
|
| 350 |
+
}
|
| 351 |
+
extra_pickles = {}
|
| 352 |
+
for zi in zf.infolist():
|
| 353 |
+
if not zi.filename.endswith(".pkl"):
|
| 354 |
+
continue
|
| 355 |
+
with zf.open(zi) as handle:
|
| 356 |
+
# TODO: handle errors here and just ignore the file?
|
| 357 |
+
# NOTE: For a lot of these files (like bytecode),
|
| 358 |
+
# we could get away with just unpickling, but this should be safer.
|
| 359 |
+
obj = torch.utils.show_pickle.DumpUnpickler(handle, catch_invalid_utf8=True).load()
|
| 360 |
+
buf = io.StringIO()
|
| 361 |
+
pprint.pprint(obj, buf)
|
| 362 |
+
contents = buf.getvalue()
|
| 363 |
+
# Checked the rendered length instead of the file size
|
| 364 |
+
# because pickles with shared structure can explode in size during rendering.
|
| 365 |
+
if os.path.basename(zi.filename) not in always_render_pickles and \
|
| 366 |
+
len(contents) > extra_file_size_limit:
|
| 367 |
+
continue
|
| 368 |
+
extra_pickles[zi.filename] = contents
|
| 369 |
+
|
| 370 |
+
return {
|
| 371 |
+
"model": {
|
| 372 |
+
"title": title,
|
| 373 |
+
"file_size": file_size,
|
| 374 |
+
"version": version,
|
| 375 |
+
"zip_files": zip_files,
|
| 376 |
+
"interned_strings": list(interned_strings),
|
| 377 |
+
"code_files": code_files,
|
| 378 |
+
"model_data": model_data,
|
| 379 |
+
"constants": constants,
|
| 380 |
+
"extra_files_jsons": extra_files_jsons,
|
| 381 |
+
"extra_pickles": extra_pickles,
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def get_inline_skeleton():
|
| 387 |
+
"""Get a fully-inlined skeleton of the frontend.
|
| 388 |
+
|
| 389 |
+
The returned HTML page has no external network dependencies for code.
|
| 390 |
+
It can load model_info.json over HTTP, or be passed to burn_in_info.
|
| 391 |
+
"""
|
| 392 |
+
|
| 393 |
+
import importlib.resources
|
| 394 |
+
|
| 395 |
+
# pyrefly: ignore [bad-argument-type]
|
| 396 |
+
skeleton = importlib.resources.read_text(__package__, "skeleton.html")
|
| 397 |
+
# pyrefly: ignore [bad-argument-type]
|
| 398 |
+
js_code = importlib.resources.read_text(__package__, "code.js")
|
| 399 |
+
for js_module in ["preact", "htm"]:
|
| 400 |
+
# pyrefly: ignore [bad-argument-type]
|
| 401 |
+
js_lib = importlib.resources.read_binary(__package__, f"{js_module}.mjs")
|
| 402 |
+
js_url = "data:application/javascript," + urllib.parse.quote(js_lib)
|
| 403 |
+
js_code = js_code.replace(f"https://unpkg.com/{js_module}?module", js_url)
|
| 404 |
+
skeleton = skeleton.replace(' src="./code.js">', ">\n" + js_code)
|
| 405 |
+
return skeleton
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
def burn_in_info(skeleton, info):
|
| 409 |
+
"""Burn model info into the HTML skeleton.
|
| 410 |
+
|
| 411 |
+
The result will render the hard-coded model info and
|
| 412 |
+
have no external network dependencies for code or data.
|
| 413 |
+
"""
|
| 414 |
+
|
| 415 |
+
# Note that Python's json serializer does not escape slashes in strings.
|
| 416 |
+
# Since we're inlining this JSON directly into a script tag, a string
|
| 417 |
+
# containing "</script>" would end the script prematurely and
|
| 418 |
+
# mess up our page. Unconditionally escape fixes that.
|
| 419 |
+
return skeleton.replace(
|
| 420 |
+
"BURNED_IN_MODEL_INFO = null",
|
| 421 |
+
"BURNED_IN_MODEL_INFO = " + json.dumps(info, sort_keys=True).replace("/", "\\/"))
|
| 422 |
+
|
| 423 |
+
|
| 424 |
+
def get_info_and_burn_skeleton(path_or_bytesio, **kwargs):
|
| 425 |
+
model_info = get_model_info(path_or_bytesio, **kwargs)
|
| 426 |
+
skeleton = get_inline_skeleton()
|
| 427 |
+
page = burn_in_info(skeleton, model_info)
|
| 428 |
+
return page
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def main(argv, *, stdout=None) -> None:
|
| 432 |
+
warnings.warn("torch.utils.model_dump is deprecated and will be removed in a future PyTorch release.", stacklevel=2)
|
| 433 |
+
parser = argparse.ArgumentParser()
|
| 434 |
+
parser.add_argument("--style", choices=["json", "html"])
|
| 435 |
+
parser.add_argument("--title")
|
| 436 |
+
parser.add_argument("model")
|
| 437 |
+
args = parser.parse_args(argv[1:])
|
| 438 |
+
|
| 439 |
+
info = get_model_info(args.model, title=args.title)
|
| 440 |
+
|
| 441 |
+
output = stdout or sys.stdout
|
| 442 |
+
|
| 443 |
+
if args.style == "json":
|
| 444 |
+
output.write(json.dumps(info, sort_keys=True) + "\n")
|
| 445 |
+
elif args.style == "html":
|
| 446 |
+
skeleton = get_inline_skeleton()
|
| 447 |
+
page = burn_in_info(skeleton, info)
|
| 448 |
+
output.write(page)
|
| 449 |
+
else:
|
| 450 |
+
raise Exception("Invalid style") # noqa: TRY002
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/__main__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
import sys
|
| 3 |
+
from . import main
|
| 4 |
+
|
| 5 |
+
sys.exit(main(sys.argv))
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/code.js
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { h, Component, render } from 'https://unpkg.com/preact?module';
|
| 2 |
+
import htm from 'https://unpkg.com/htm?module';
|
| 3 |
+
|
| 4 |
+
const html = htm.bind(h);
|
| 5 |
+
|
| 6 |
+
const BURNED_IN_MODEL_INFO = null;
|
| 7 |
+
|
| 8 |
+
// https://stackoverflow.com/a/20732091
|
| 9 |
+
function humanFileSize(size) {
|
| 10 |
+
if (size == 0) { return "0 B"; }
|
| 11 |
+
var i = Math.floor( Math.log(size) / Math.log(1024) );
|
| 12 |
+
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
function caret(down) {
|
| 16 |
+
return down ? "\u25BE" : "\u25B8";
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
class Blamer {
|
| 20 |
+
constructor() {
|
| 21 |
+
this.blame_on_click = false;
|
| 22 |
+
this.aux_content_pane = null;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
setAuxContentPane(pane) {
|
| 26 |
+
this.aux_content_pane = pane;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
readyBlame() {
|
| 30 |
+
this.blame_on_click = true;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
maybeBlame(arg) {
|
| 34 |
+
if (!this.blame_on_click) {
|
| 35 |
+
return;
|
| 36 |
+
}
|
| 37 |
+
this.blame_on_click = false;
|
| 38 |
+
if (!this.aux_content_pane) {
|
| 39 |
+
return;
|
| 40 |
+
}
|
| 41 |
+
this.aux_content_pane.doBlame(arg);
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
let blame = new Blamer();
|
| 46 |
+
|
| 47 |
+
class Hider extends Component {
|
| 48 |
+
constructor() {
|
| 49 |
+
super();
|
| 50 |
+
this.state = { shown: null };
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
componentDidMount() {
|
| 54 |
+
this.setState({ shown: this.props.shown === "true" });
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
render({name, children}, {shown}) {
|
| 58 |
+
let my_caret = html`<span class=caret onClick=${() => this.click()} >${caret(shown)}</span>`;
|
| 59 |
+
return html`<div data-hider-title=${name} data-shown=${shown}>
|
| 60 |
+
<h2>${my_caret} ${name}</h2>
|
| 61 |
+
<div>${shown ? this.props.children : []}</div></div>`;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
click() {
|
| 65 |
+
this.setState({shown: !this.state.shown});
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
function ModelSizeSection({model: {file_size, zip_files}}) {
|
| 70 |
+
let store_size = 0;
|
| 71 |
+
let compr_size = 0;
|
| 72 |
+
for (const zi of zip_files) {
|
| 73 |
+
if (zi.compression === 0) {
|
| 74 |
+
// TODO: Maybe check that compressed_size === file_size.
|
| 75 |
+
store_size += zi.compressed_size;
|
| 76 |
+
} else {
|
| 77 |
+
compr_size += zi.compressed_size;
|
| 78 |
+
}
|
| 79 |
+
}
|
| 80 |
+
let zip_overhead = file_size - store_size - compr_size;
|
| 81 |
+
// TODO: Better formatting. Right-align this.
|
| 82 |
+
return html`
|
| 83 |
+
<${Hider} name="Model Size" shown=true>
|
| 84 |
+
<pre>.
|
| 85 |
+
Model size: ${file_size} (${humanFileSize(file_size)})
|
| 86 |
+
Stored files: ${store_size} (${humanFileSize(store_size)})
|
| 87 |
+
Compressed files: ${compr_size} (${humanFileSize(compr_size)})
|
| 88 |
+
Zip overhead: ${zip_overhead} (${humanFileSize(zip_overhead)})
|
| 89 |
+
</pre><//>`;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
function StructuredDataSection({name, data, shown}) {
|
| 93 |
+
return html`
|
| 94 |
+
<${Hider} name=${name} shown=${shown}>
|
| 95 |
+
<div style="font-family:monospace;">
|
| 96 |
+
<${StructuredData} data=${data} indent="" prefix=""/>
|
| 97 |
+
</div><//>`;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
class StructuredData extends Component {
|
| 101 |
+
constructor() {
|
| 102 |
+
super();
|
| 103 |
+
this.state = { shown: false };
|
| 104 |
+
|
| 105 |
+
this.INLINE_TYPES = new Set(["boolean", "number", "string"])
|
| 106 |
+
this.IGNORED_STATE_KEYS = new Set(["training", "_is_full_backward_hook"])
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
click() {
|
| 110 |
+
this.setState({shown: !this.state.shown});
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
expando(data) {
|
| 114 |
+
if (data === null || this.INLINE_TYPES.has(typeof(data))) {
|
| 115 |
+
return false;
|
| 116 |
+
}
|
| 117 |
+
if (typeof(data) != "object") {
|
| 118 |
+
throw new Error("Not an object");
|
| 119 |
+
}
|
| 120 |
+
if (Array.isArray(data)) {
|
| 121 |
+
// TODO: Maybe show simple lists and tuples on one line.
|
| 122 |
+
return true;
|
| 123 |
+
}
|
| 124 |
+
if (data.__tuple_values__) {
|
| 125 |
+
// TODO: Maybe show simple lists and tuples on one line.
|
| 126 |
+
return true;
|
| 127 |
+
}
|
| 128 |
+
if (data.__is_dict__) {
|
| 129 |
+
// TODO: Maybe show simple (empty?) dicts on one line.
|
| 130 |
+
return true;
|
| 131 |
+
}
|
| 132 |
+
if (data.__module_type__) {
|
| 133 |
+
return true;
|
| 134 |
+
}
|
| 135 |
+
if (data.__tensor_v2__) {
|
| 136 |
+
return false;
|
| 137 |
+
}
|
| 138 |
+
if (data.__qtensor__) {
|
| 139 |
+
return false;
|
| 140 |
+
}
|
| 141 |
+
throw new Error("Can't handle data type.", data);
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
renderHeadline(data) {
|
| 145 |
+
if (data === null) {
|
| 146 |
+
return "None";
|
| 147 |
+
}
|
| 148 |
+
if (typeof(data) == "boolean") {
|
| 149 |
+
const sd = String(data);
|
| 150 |
+
return sd.charAt(0).toUpperCase() + sd.slice(1);
|
| 151 |
+
}
|
| 152 |
+
if (typeof(data) == "number") {
|
| 153 |
+
return JSON.stringify(data);
|
| 154 |
+
}
|
| 155 |
+
if (typeof(data) == "string") {
|
| 156 |
+
return JSON.stringify(data);
|
| 157 |
+
}
|
| 158 |
+
if (typeof(data) != "object") {
|
| 159 |
+
throw new Error("Not an object");
|
| 160 |
+
}
|
| 161 |
+
if (Array.isArray(data)) {
|
| 162 |
+
return "list([";
|
| 163 |
+
}
|
| 164 |
+
if (data.__tuple_values__) {
|
| 165 |
+
return "tuple((";
|
| 166 |
+
}
|
| 167 |
+
if (data.__is_dict__) {
|
| 168 |
+
return "dict({";
|
| 169 |
+
}
|
| 170 |
+
if (data.__module_type__) {
|
| 171 |
+
return data.__module_type__ + "()";
|
| 172 |
+
}
|
| 173 |
+
if (data.__tensor_v2__) {
|
| 174 |
+
const [storage, offset, size, stride, grad] = data.__tensor_v2__;
|
| 175 |
+
const [dtype, key, device, numel] = storage;
|
| 176 |
+
return this.renderTensor(
|
| 177 |
+
"tensor", dtype, key, device, numel, offset, size, stride, grad, []);
|
| 178 |
+
}
|
| 179 |
+
if (data.__qtensor__) {
|
| 180 |
+
const [storage, offset, size, stride, quantizer, grad] = data.__qtensor__;
|
| 181 |
+
const [dtype, key, device, numel] = storage;
|
| 182 |
+
let extra_parts = [];
|
| 183 |
+
if (quantizer[0] == "per_tensor_affine") {
|
| 184 |
+
extra_parts.push(`scale=${quantizer[1]}`);
|
| 185 |
+
extra_parts.push(`zero_point=${quantizer[2]}`);
|
| 186 |
+
} else {
|
| 187 |
+
extra_parts.push(`quantizer=${quantizer[0]}`);
|
| 188 |
+
}
|
| 189 |
+
return this.renderTensor(
|
| 190 |
+
"qtensor", dtype, key, device, numel, offset, size, stride, grad, extra_parts);
|
| 191 |
+
}
|
| 192 |
+
throw new Error("Can't handle data type.", data);
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
renderTensor(
|
| 196 |
+
prefix,
|
| 197 |
+
dtype,
|
| 198 |
+
storage_key,
|
| 199 |
+
device,
|
| 200 |
+
storage_numel,
|
| 201 |
+
offset,
|
| 202 |
+
size,
|
| 203 |
+
stride,
|
| 204 |
+
grad,
|
| 205 |
+
extra_parts) {
|
| 206 |
+
let parts = [
|
| 207 |
+
"(" + size.join(",") + ")",
|
| 208 |
+
dtype,
|
| 209 |
+
];
|
| 210 |
+
parts.push(...extra_parts);
|
| 211 |
+
if (device != "cpu") {
|
| 212 |
+
parts.push(device);
|
| 213 |
+
}
|
| 214 |
+
if (grad) {
|
| 215 |
+
parts.push("grad");
|
| 216 |
+
}
|
| 217 |
+
// TODO: Check stride and indicate if the tensor is channels-last or non-contiguous
|
| 218 |
+
// TODO: Check size, stride, offset, and numel and indicate if
|
| 219 |
+
// the tensor doesn't use all data in storage.
|
| 220 |
+
// TODO: Maybe show key?
|
| 221 |
+
void(offset);
|
| 222 |
+
void(stride);
|
| 223 |
+
void(storage_key);
|
| 224 |
+
void(storage_numel);
|
| 225 |
+
return prefix + "(" + parts.join(", ") + ")";
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
renderBody(indent, data) {
|
| 229 |
+
if (data === null || this.INLINE_TYPES.has(typeof(data))) {
|
| 230 |
+
throw "Should not reach here."
|
| 231 |
+
}
|
| 232 |
+
if (typeof(data) != "object") {
|
| 233 |
+
throw new Error("Not an object");
|
| 234 |
+
}
|
| 235 |
+
if (Array.isArray(data)) {
|
| 236 |
+
let new_indent = indent + "\u00A0\u00A0";
|
| 237 |
+
let parts = [];
|
| 238 |
+
for (let idx = 0; idx < data.length; idx++) {
|
| 239 |
+
// Does it make sense to put explicit index numbers here?
|
| 240 |
+
parts.push(html`<br/><${StructuredData} prefix=${idx + ": "} indent=${new_indent} data=${data[idx]} />`);
|
| 241 |
+
}
|
| 242 |
+
return parts;
|
| 243 |
+
}
|
| 244 |
+
if (data.__tuple_values__) {
|
| 245 |
+
// Handled the same as lists.
|
| 246 |
+
return this.renderBody(indent, data.__tuple_values__);
|
| 247 |
+
}
|
| 248 |
+
if (data.__is_dict__) {
|
| 249 |
+
let new_indent = indent + "\u00A0\u00A0";
|
| 250 |
+
let parts = [];
|
| 251 |
+
for (let idx = 0; idx < data.keys.length; idx++) {
|
| 252 |
+
if (typeof(data.keys[idx]) != "string") {
|
| 253 |
+
parts.push(html`<br/>${new_indent}Non-string key`);
|
| 254 |
+
} else {
|
| 255 |
+
parts.push(html`<br/><${StructuredData} prefix=${data.keys[idx] + ": "} indent=${new_indent} data=${data.values[idx]} />`);
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
return parts;
|
| 259 |
+
}
|
| 260 |
+
if (data.__module_type__) {
|
| 261 |
+
const mstate = data.state;
|
| 262 |
+
if (mstate === null || typeof(mstate) != "object") {
|
| 263 |
+
throw new Error("Bad module state");
|
| 264 |
+
}
|
| 265 |
+
let new_indent = indent + "\u00A0\u00A0";
|
| 266 |
+
let parts = [];
|
| 267 |
+
if (mstate.__is_dict__) {
|
| 268 |
+
// TODO: Less copy/paste between this and normal dicts.
|
| 269 |
+
for (let idx = 0; idx < mstate.keys.length; idx++) {
|
| 270 |
+
if (typeof(mstate.keys[idx]) != "string") {
|
| 271 |
+
parts.push(html`<br/>${new_indent}Non-string key`);
|
| 272 |
+
} else if (this.IGNORED_STATE_KEYS.has(mstate.keys[idx])) {
|
| 273 |
+
// Do nothing.
|
| 274 |
+
} else {
|
| 275 |
+
parts.push(html`<br/><${StructuredData} prefix=${mstate.keys[idx] + ": "} indent=${new_indent} data=${mstate.values[idx]} />`);
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
} else if (mstate.__tuple_values__) {
|
| 279 |
+
parts.push(html`<br/><${StructuredData} prefix="" indent=${new_indent} data=${mstate} />`);
|
| 280 |
+
} else if (mstate.__module_type__) {
|
| 281 |
+
// We normally wouldn't have the state of a module be another module,
|
| 282 |
+
// but we use "modules" to encode special values (like Unicode decode
|
| 283 |
+
// errors) that might be valid states. Just go with it.
|
| 284 |
+
parts.push(html`<br/><${StructuredData} prefix="" indent=${new_indent} data=${mstate} />`);
|
| 285 |
+
} else {
|
| 286 |
+
throw new Error("Bad module state");
|
| 287 |
+
}
|
| 288 |
+
return parts;
|
| 289 |
+
}
|
| 290 |
+
if (data.__tensor_v2__) {
|
| 291 |
+
throw "Should not reach here."
|
| 292 |
+
}
|
| 293 |
+
if (data.__qtensor__) {
|
| 294 |
+
throw "Should not reach here."
|
| 295 |
+
}
|
| 296 |
+
throw new Error("Can't handle data type.", data);
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
render({data, indent, prefix}, {shown}) {
|
| 300 |
+
const exp = this.expando(data) ? html`<span class=caret onClick=${() => this.click()} >${caret(shown)} </span>` : "";
|
| 301 |
+
const headline = this.renderHeadline(data);
|
| 302 |
+
const body = shown ? this.renderBody(indent, data) : "";
|
| 303 |
+
return html`${indent}${exp}${prefix}${headline}${body}`;
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
function ZipContentsSection({model: {zip_files}}) {
|
| 308 |
+
// TODO: Add human-readable sizes?
|
| 309 |
+
// TODO: Add sorting options?
|
| 310 |
+
// TODO: Add hierarchical collapsible tree?
|
| 311 |
+
return html`
|
| 312 |
+
<${Hider} name="Zip Contents" shown=false>
|
| 313 |
+
<table>
|
| 314 |
+
<thead>
|
| 315 |
+
<tr>
|
| 316 |
+
<th>Mode</th>
|
| 317 |
+
<th>Size</th>
|
| 318 |
+
<th>Compressed</th>
|
| 319 |
+
<th>Name</th>
|
| 320 |
+
</tr>
|
| 321 |
+
</thead>
|
| 322 |
+
<tbody style="font-family:monospace;">
|
| 323 |
+
${zip_files.map(zf => html`<tr>
|
| 324 |
+
<td>${{0: "store", 8: "deflate"}[zf.compression] || zf.compression}</td>
|
| 325 |
+
<td>${zf.file_size}</td>
|
| 326 |
+
<td>${zf.compressed_size}</td>
|
| 327 |
+
<td>${zf.filename}</td>
|
| 328 |
+
</tr>`)}
|
| 329 |
+
</tbody>
|
| 330 |
+
</table><//>`;
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
function CodeSection({model: {code_files}}) {
|
| 334 |
+
return html`
|
| 335 |
+
<${Hider} name="Code" shown=false>
|
| 336 |
+
<div>
|
| 337 |
+
${Object.entries(code_files).map(([fn, code]) => html`<${OneCodeSection}
|
| 338 |
+
filename=${fn} code=${code} />`)}
|
| 339 |
+
</div><//>`;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
class OneCodeSection extends Component {
|
| 343 |
+
constructor() {
|
| 344 |
+
super();
|
| 345 |
+
this.state = { shown: false };
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
click() {
|
| 349 |
+
const shown = !this.state.shown;
|
| 350 |
+
this.setState({shown: shown});
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
render({filename, code}, {shown}) {
|
| 354 |
+
const header = html`
|
| 355 |
+
<h3 style="font-family:monospace;">
|
| 356 |
+
<span class=caret onClick=${() => this.click()} >${caret(shown)} </span>
|
| 357 |
+
${filename}</h3>
|
| 358 |
+
`;
|
| 359 |
+
if (!shown) {
|
| 360 |
+
return header;
|
| 361 |
+
}
|
| 362 |
+
return html`
|
| 363 |
+
${header}
|
| 364 |
+
<pre>${code.map(c => this.renderBlock(c))}</pre>
|
| 365 |
+
`;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
renderBlock([text, ist_file, line, ist_s_text, s_start, s_end]) {
|
| 369 |
+
return html`<span
|
| 370 |
+
onClick=${() => blame.maybeBlame({ist_file, line, ist_s_text, s_start, s_end})}
|
| 371 |
+
>${text}</span>`;
|
| 372 |
+
}
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
function ExtraJsonSection({files}) {
|
| 376 |
+
return html`
|
| 377 |
+
<${Hider} name="Extra files (JSON)" shown=false>
|
| 378 |
+
<div>
|
| 379 |
+
<p>Use "Log Raw Model Info" for hierarchical view in browser console.</p>
|
| 380 |
+
${Object.entries(files).map(([fn, json]) => html`<${OneJsonSection}
|
| 381 |
+
filename=${fn} json=${json} />`)}
|
| 382 |
+
</div><//>`;
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
class OneJsonSection extends Component {
|
| 386 |
+
constructor() {
|
| 387 |
+
super();
|
| 388 |
+
this.state = { shown: false };
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
click() {
|
| 392 |
+
const shown = !this.state.shown;
|
| 393 |
+
this.setState({shown: shown});
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
render({filename, json}, {shown}) {
|
| 397 |
+
const header = html`
|
| 398 |
+
<h3 style="font-family:monospace;">
|
| 399 |
+
<span class=caret onClick=${() => this.click()} >${caret(shown)} </span>
|
| 400 |
+
${filename}</h3>
|
| 401 |
+
`;
|
| 402 |
+
if (!shown) {
|
| 403 |
+
return header;
|
| 404 |
+
}
|
| 405 |
+
return html`
|
| 406 |
+
${header}
|
| 407 |
+
<pre>${JSON.stringify(json, null, 2)}</pre>
|
| 408 |
+
`;
|
| 409 |
+
}
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
function ExtraPicklesSection({files}) {
|
| 413 |
+
return html`
|
| 414 |
+
<${Hider} name="Extra Pickles" shown=false>
|
| 415 |
+
<div>
|
| 416 |
+
${Object.entries(files).map(([fn, content]) => html`<${OnePickleSection}
|
| 417 |
+
filename=${fn} content=${content} />`)}
|
| 418 |
+
</div><//>`;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
class OnePickleSection extends Component {
|
| 422 |
+
constructor() {
|
| 423 |
+
super();
|
| 424 |
+
this.state = { shown: false };
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
click() {
|
| 428 |
+
const shown = !this.state.shown;
|
| 429 |
+
this.setState({shown: shown});
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
render({filename, content}, {shown}) {
|
| 433 |
+
const header = html`
|
| 434 |
+
<h3 style="font-family:monospace;">
|
| 435 |
+
<span class=caret onClick=${() => this.click()} >${caret(shown)} </span>
|
| 436 |
+
${filename}</h3>
|
| 437 |
+
`;
|
| 438 |
+
if (!shown) {
|
| 439 |
+
return header;
|
| 440 |
+
}
|
| 441 |
+
return html`
|
| 442 |
+
${header}
|
| 443 |
+
<pre>${content}</pre>
|
| 444 |
+
`;
|
| 445 |
+
}
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
function assertStorageAreEqual(key, lhs, rhs) {
|
| 449 |
+
if (lhs.length !== rhs.length ||
|
| 450 |
+
!lhs.every((val, idx) => val === rhs[idx])) {
|
| 451 |
+
throw new Error("Storage mismatch for key '" + key + "'");
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
function computeTensorMemory(numel, dtype) {
|
| 456 |
+
const sizes = {
|
| 457 |
+
"Byte": 1,
|
| 458 |
+
"Char": 1,
|
| 459 |
+
"Short": 2,
|
| 460 |
+
"Int": 4,
|
| 461 |
+
"Long": 8,
|
| 462 |
+
"Half": 2,
|
| 463 |
+
"Float": 4,
|
| 464 |
+
"Double": 8,
|
| 465 |
+
"ComplexHalf": 4,
|
| 466 |
+
"ComplexFloat": 8,
|
| 467 |
+
"ComplexDouble": 16,
|
| 468 |
+
"Bool": 1,
|
| 469 |
+
"QInt8": 1,
|
| 470 |
+
"QUInt8": 1,
|
| 471 |
+
"QInt32": 4,
|
| 472 |
+
"BFloat16": 2,
|
| 473 |
+
};
|
| 474 |
+
let dtsize = sizes[dtype];
|
| 475 |
+
if (!dtsize) {
|
| 476 |
+
throw new Error("Unrecognized dtype: " + dtype);
|
| 477 |
+
}
|
| 478 |
+
return numel * dtsize;
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
// TODO: Maybe track by dtype as well.
|
| 482 |
+
// TODO: Maybe distinguish between visible size and storage size.
|
| 483 |
+
function getTensorStorages(data) {
|
| 484 |
+
if (data === null) {
|
| 485 |
+
return new Map();
|
| 486 |
+
}
|
| 487 |
+
if (typeof(data) == "boolean") {
|
| 488 |
+
return new Map();
|
| 489 |
+
}
|
| 490 |
+
if (typeof(data) == "number") {
|
| 491 |
+
return new Map();
|
| 492 |
+
}
|
| 493 |
+
if (typeof(data) == "string") {
|
| 494 |
+
return new Map();
|
| 495 |
+
}
|
| 496 |
+
if (typeof(data) != "object") {
|
| 497 |
+
throw new Error("Not an object");
|
| 498 |
+
}
|
| 499 |
+
if (Array.isArray(data)) {
|
| 500 |
+
let result = new Map();
|
| 501 |
+
for (const item of data) {
|
| 502 |
+
const tensors = getTensorStorages(item);
|
| 503 |
+
for (const [key, storage] of tensors.entries()) {
|
| 504 |
+
if (!result.has(key)) {
|
| 505 |
+
result.set(key, storage);
|
| 506 |
+
} else {
|
| 507 |
+
const old_storage = result.get(key);
|
| 508 |
+
assertStorageAreEqual(key, old_storage, storage);
|
| 509 |
+
}
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
return result;
|
| 513 |
+
}
|
| 514 |
+
if (data.__tuple_values__) {
|
| 515 |
+
return getTensorStorages(data.__tuple_values__);
|
| 516 |
+
}
|
| 517 |
+
if (data.__is_dict__) {
|
| 518 |
+
return getTensorStorages(data.values);
|
| 519 |
+
}
|
| 520 |
+
if (data.__module_type__) {
|
| 521 |
+
return getTensorStorages(data.state);
|
| 522 |
+
}
|
| 523 |
+
if (data.__tensor_v2__) {
|
| 524 |
+
const [storage, offset, size, stride, grad] = data.__tensor_v2__;
|
| 525 |
+
const [dtype, key, device, numel] = storage;
|
| 526 |
+
return new Map([[key, storage]]);
|
| 527 |
+
}
|
| 528 |
+
if (data.__qtensor__) {
|
| 529 |
+
const [storage, offset, size, stride, quantizer, grad] = data.__qtensor__;
|
| 530 |
+
const [dtype, key, device, numel] = storage;
|
| 531 |
+
return new Map([[key, storage]]);
|
| 532 |
+
}
|
| 533 |
+
throw new Error("Can't handle data type.", data);
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
function getTensorMemoryByDevice(pickles) {
|
| 537 |
+
let all_tensors = [];
|
| 538 |
+
for (const [name, pickle] of pickles) {
|
| 539 |
+
const tensors = getTensorStorages(pickle);
|
| 540 |
+
all_tensors.push(...tensors.values());
|
| 541 |
+
}
|
| 542 |
+
let result = {};
|
| 543 |
+
for (const storage of all_tensors.values()) {
|
| 544 |
+
const [dtype, key, device, numel] = storage;
|
| 545 |
+
const size = computeTensorMemory(numel, dtype);
|
| 546 |
+
result[device] = (result[device] || 0) + size;
|
| 547 |
+
}
|
| 548 |
+
return result;
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
// Make this a separate component so it is rendered lazily.
|
| 552 |
+
class OpenTensorMemorySection extends Component {
|
| 553 |
+
render({model: {model_data, constants}}) {
|
| 554 |
+
let sizes = getTensorMemoryByDevice(new Map([
|
| 555 |
+
["data", model_data],
|
| 556 |
+
["constants", constants],
|
| 557 |
+
]));
|
| 558 |
+
return html`
|
| 559 |
+
<table>
|
| 560 |
+
<thead>
|
| 561 |
+
<tr>
|
| 562 |
+
<th>Device</th>
|
| 563 |
+
<th>Bytes</th>
|
| 564 |
+
<th>Human</th>
|
| 565 |
+
</tr>
|
| 566 |
+
</thead>
|
| 567 |
+
<tbody style="font-family:monospace;">
|
| 568 |
+
${Object.entries(sizes).map(([dev, size]) => html`<tr>
|
| 569 |
+
<td>${dev}</td>
|
| 570 |
+
<td>${size}</td>
|
| 571 |
+
<td>${humanFileSize(size)}</td>
|
| 572 |
+
</tr>`)}
|
| 573 |
+
</tbody>
|
| 574 |
+
</table>`;
|
| 575 |
+
}
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
function TensorMemorySection({model}) {
|
| 579 |
+
return html`
|
| 580 |
+
<${Hider} name="Tensor Memory" shown=false>
|
| 581 |
+
<${OpenTensorMemorySection} model=${model} /><//>`;
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
class AuxContentPane extends Component {
|
| 585 |
+
constructor() {
|
| 586 |
+
super();
|
| 587 |
+
this.state = {
|
| 588 |
+
blame_info: null,
|
| 589 |
+
};
|
| 590 |
+
}
|
| 591 |
+
|
| 592 |
+
doBlame(arg) {
|
| 593 |
+
this.setState({...this.state, blame_info: arg});
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
render({model: {interned_strings}}, {blame_info}) {
|
| 597 |
+
let blame_content = "";
|
| 598 |
+
if (blame_info) {
|
| 599 |
+
const {ist_file, line, ist_s_text, s_start, s_end} = blame_info;
|
| 600 |
+
let s_text = interned_strings[ist_s_text];
|
| 601 |
+
if (s_start != 0 || s_end != s_text.length) {
|
| 602 |
+
let prefix = s_text.slice(0, s_start);
|
| 603 |
+
let main = s_text.slice(s_start, s_end);
|
| 604 |
+
let suffix = s_text.slice(s_end);
|
| 605 |
+
s_text = html`${prefix}<strong>${main}</strong>${suffix}`;
|
| 606 |
+
}
|
| 607 |
+
blame_content = html`
|
| 608 |
+
<h3>${interned_strings[ist_file]}:${line}</h3>
|
| 609 |
+
<pre>${s_start}:${s_end}</pre>
|
| 610 |
+
<pre>${s_text}</pre><br/>
|
| 611 |
+
`;
|
| 612 |
+
}
|
| 613 |
+
return html`
|
| 614 |
+
<button onClick=${() => blame.readyBlame()}>Blame Code</button>
|
| 615 |
+
<br/>
|
| 616 |
+
${blame_content}
|
| 617 |
+
`;
|
| 618 |
+
}
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
class App extends Component {
|
| 622 |
+
constructor() {
|
| 623 |
+
super();
|
| 624 |
+
this.state = {
|
| 625 |
+
err: false,
|
| 626 |
+
model: null,
|
| 627 |
+
};
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
componentDidMount() {
|
| 631 |
+
const app = this;
|
| 632 |
+
if (BURNED_IN_MODEL_INFO !== null) {
|
| 633 |
+
app.setState({model: BURNED_IN_MODEL_INFO});
|
| 634 |
+
} else {
|
| 635 |
+
fetch("./model_info.json").then(function(response) {
|
| 636 |
+
if (!response.ok) {
|
| 637 |
+
throw new Error("Response not ok.");
|
| 638 |
+
}
|
| 639 |
+
return response.json();
|
| 640 |
+
}).then(function(body) {
|
| 641 |
+
app.setState({model: body});
|
| 642 |
+
}).catch(function(error) {
|
| 643 |
+
console.log("Top-level error: ", error);
|
| 644 |
+
});
|
| 645 |
+
}
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
componentDidCatch(error) {
|
| 649 |
+
void(error);
|
| 650 |
+
this.setState({...this.state, err: true});
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
render(_, {err}) {
|
| 654 |
+
if (this.state.model === null) {
|
| 655 |
+
return html`<h1>Loading...</h1>`;
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
const model = this.state.model.model;
|
| 659 |
+
|
| 660 |
+
let error_msg = "";
|
| 661 |
+
if (err) {
|
| 662 |
+
error_msg = html`<h2 style="background:red">An error occurred. Check console</h2>`;
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
return html`
|
| 666 |
+
${error_msg}
|
| 667 |
+
<div id=main_content style="position:absolute;width:99%;height:79%;overflow:scroll">
|
| 668 |
+
<h1>TorchScript Model (version ${model.version}): ${model.title}</h1>
|
| 669 |
+
<button onClick=${() => console.log(model)}>Log Raw Model Info</button>
|
| 670 |
+
<${ModelSizeSection} model=${model}/>
|
| 671 |
+
<${StructuredDataSection} name="Model Data" data=${model.model_data} shown=true/>
|
| 672 |
+
<${StructuredDataSection} name="Constants" data=${model.constants} shown=false/>
|
| 673 |
+
<${ZipContentsSection} model=${model}/>
|
| 674 |
+
<${CodeSection} model=${model}/>
|
| 675 |
+
<${ExtraJsonSection} files=${model.extra_files_jsons}/>
|
| 676 |
+
<${ExtraPicklesSection} files=${model.extra_pickles}/>
|
| 677 |
+
<${TensorMemorySection} model=${model}/>
|
| 678 |
+
</div>
|
| 679 |
+
<div id=aux_content style="position:absolute;width:99%;top:80%;height:20%;overflow:scroll">
|
| 680 |
+
<${AuxContentPane}
|
| 681 |
+
err=${this.state.error}
|
| 682 |
+
model=${model}
|
| 683 |
+
ref=${(p) => blame.setAuxContentPane(p)}/>
|
| 684 |
+
</div>
|
| 685 |
+
`;
|
| 686 |
+
}
|
| 687 |
+
}
|
| 688 |
+
|
| 689 |
+
render(h(App), document.body);
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/htm.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// HTM, Apache License
|
| 2 |
+
var n=function(t,s,r,e){var u;s[0]=0;for(var h=1;h<s.length;h++){var p=s[h++],a=s[h]?(s[0]|=p?1:2,r[s[h++]]):s[++h];3===p?e[0]=a:4===p?e[1]=Object.assign(e[1]||{},a):5===p?(e[1]=e[1]||{})[s[++h]]=a:6===p?e[1][s[++h]]+=a+"":p?(u=t.apply(a,n(t,a,r,["",null])),e.push(u),a[0]?s[0]|=2:(s[h-2]=0,s[h]=u)):e.push(a)}return e},t=new Map;export default function(s){var r=t.get(this);return r||(r=new Map,t.set(this,r)),(r=n(this,r.get(s)||(r.set(s,r=function(n){for(var t,s,r=1,e="",u="",h=[0],p=function(n){1===r&&(n||(e=e.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?h.push(0,n,e):3===r&&(n||e)?(h.push(3,n,e),r=2):2===r&&"..."===e&&n?h.push(4,n,0):2===r&&e&&!n?h.push(5,0,!0,e):r>=5&&((e||!n&&5===r)&&(h.push(r,0,e,s),r=6),n&&(h.push(r,n,0,s),r=6)),e=""},a=0;a<n.length;a++){a&&(1===r&&p(),p(a));for(var l=0;l<n[a].length;l++)t=n[a][l],1===r?"<"===t?(p(),h=[h],r=3):e+=t:4===r?"--"===e&&">"===t?(r=1,e=""):e=t+e[0]:u?t===u?u="":e+=t:'"'===t||"'"===t?u=t:">"===t?(p(),r=1):r&&("="===t?(r=5,s=e,e=""):"/"===t&&(r<5||">"===n[a][l+1])?(p(),3===r&&(h=h[0]),r=h,(h=h[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(p(),r=2):e+=t),3===r&&"!--"===e&&(r=4,h=h[0])}return p(),h}(s)),r),arguments,[])).length>1?r:r[0]}
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/preact.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Preact, MIT License
|
| 2 |
+
var n,l,u,i,t,o,r={},f=[],e=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;function c(e,n){for(var t in n)e[t]=n[t];return e}function s(e){var n=e.parentNode;n&&n.removeChild(e)}function a(e,n,t){var _,l,o,r=arguments,i={};for(o in n)"key"==o?_=n[o]:"ref"==o?l=n[o]:i[o]=n[o];if(arguments.length>3)for(t=[t],o=3;o<arguments.length;o++)t.push(r[o]);if(null!=t&&(i.children=t),"function"==typeof e&&null!=e.defaultProps)for(o in e.defaultProps)void 0===i[o]&&(i[o]=e.defaultProps[o]);return v(e,i,_,l,null)}function v(e,t,_,l,o){var r={type:e,props:t,key:_,ref:l,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++n.__v:o};return null!=n.vnode&&n.vnode(r),r}function h(){return{current:null}}function y(e){return e.children}function p(e,n){this.props=e,this.context=n}function d(e,n){if(null==n)return e.__?d(e.__,e.__.__k.indexOf(e)+1):null;for(var t;n<e.__k.length;n++)if(null!=(t=e.__k[n])&&null!=t.__e)return t.__e;return"function"==typeof e.type?d(e):null}function _(e){var n,t;if(null!=(e=e.__)&&null!=e.__c){for(e.__e=e.__c.base=null,n=0;n<e.__k.length;n++)if(null!=(t=e.__k[n])&&null!=t.__e){e.__e=e.__c.base=t.__e;break}return _(e)}}function k(e){(!e.__d&&(e.__d=!0)&&u.push(e)&&!b.__r++||t!==n.debounceRendering)&&((t=n.debounceRendering)||i)(b)}function b(){for(var e;b.__r=u.length;)e=u.sort(function(e,n){return e.__v.__b-n.__v.__b}),u=[],e.some(function(e){var n,t,l,o,r,i;e.__d&&(r=(o=(n=e).__v).__e,(i=n.__P)&&(t=[],(l=c({},o)).__v=o.__v+1,I(i,o,l,n.__n,void 0!==i.ownerSVGElement,null!=o.__h?[r]:null,t,null==r?d(o):r,o.__h),T(t,o),o.__e!=r&&_(o)))})}function m(e,n,t,_,l,o,i,u,s,c){var p,a,h,m,k,b,C,P=_&&_.__k||f,S=P.length;for(t.__k=[],p=0;p<n.length;p++)if(null!=(m=t.__k[p]=null==(m=n[p])||"boolean"==typeof m?null:"string"==typeof m||"number"==typeof m||"bigint"==typeof m?v(null,m,null,null,m):Array.isArray(m)?v(y,{children:m},null,null,null):m.__b>0?v(m.type,m.props,m.key,null,m.__v):m)){if(m.__=t,m.__b=t.__b+1,null===(h=P[p])||h&&m.key==h.key&&m.type===h.type)P[p]=void 0;else for(a=0;a<S;a++){if((h=P[a])&&m.key==h.key&&m.type===h.type){P[a]=void 0;break}h=null}I(e,m,h=h||r,l,o,i,u,s,c),k=m.__e,(a=m.ref)&&h.ref!=a&&(C||(C=[]),h.ref&&C.push(h.ref,null,m),C.push(a,m.__c||k,m)),null!=k?(null==b&&(b=k),"function"==typeof m.type&&null!=m.__k&&m.__k===h.__k?m.__d=s=g(m,s,e):s=x(e,m,h,P,k,s),c||"option"!==t.type?"function"==typeof t.type&&(t.__d=s):e.value=""):s&&h.__e==s&&s.parentNode!=e&&(s=d(h))}for(t.__e=b,p=S;p--;)null!=P[p]&&("function"==typeof t.type&&null!=P[p].__e&&P[p].__e==t.__d&&(t.__d=d(_,p+1)),L(P[p],P[p]));if(C)for(p=0;p<C.length;p++)z(C[p],C[++p],C[++p])}function g(e,n,t){var _,l;for(_=0;_<e.__k.length;_++)(l=e.__k[_])&&(l.__=e,n="function"==typeof l.type?g(l,n,t):x(t,l,l,e.__k,l.__e,n));return n}function w(e,n){return n=n||[],null==e||"boolean"==typeof e||(Array.isArray(e)?e.some(function(e){w(e,n)}):n.push(e)),n}function x(e,n,t,_,l,o){var r,i,u;if(void 0!==n.__d)r=n.__d,n.__d=void 0;else if(null==t||l!=o||null==l.parentNode)e:if(null==o||o.parentNode!==e)e.appendChild(l),r=null;else{for(i=o,u=0;(i=i.nextSibling)&&u<_.length;u+=2)if(i==l)break e;e.insertBefore(l,o),r=o}return void 0!==r?r:l.nextSibling}function A(e,n,t,_,l){var o;for(o in t)"children"===o||"key"===o||o in n||C(e,o,null,t[o],_);for(o in n)l&&"function"!=typeof n[o]||"children"===o||"key"===o||"value"===o||"checked"===o||t[o]===n[o]||C(e,o,n[o],t[o],_)}function P(n,t,_){"-"===t[0]?n.setProperty(t,_):n[t]=null==_?"":"number"!=typeof _||e.test(t)?_:_+"px"}function C(e,n,t,_,l){var o;e:if("style"===n)if("string"==typeof t)e.style.cssText=t;else{if("string"==typeof _&&(e.style.cssText=_=""),_)for(n in _)t&&n in t||P(e.style,n,"");if(t)for(n in t)_&&t[n]===_[n]||P(e.style,n,t[n])}else if("o"===n[0]&&"n"===n[1])o=n!==(n=n.replace(/Capture$/,"")),n=n.toLowerCase()in e?n.toLowerCase().slice(2):n.slice(2),e.l||(e.l={}),e.l[n+o]=t,t?_||e.addEventListener(n,o?H:$,o):e.removeEventListener(n,o?H:$,o);else if("dangerouslySetInnerHTML"!==n){if(l)n=n.replace(/xlink[H:h]/,"h").replace(/sName$/,"s");else if("href"!==n&&"list"!==n&&"form"!==n&&"tabIndex"!==n&&"download"!==n&&n in e)try{e[n]=null==t?"":t;break e}catch(e){}"function"==typeof t||(null!=t&&(!1!==t||"a"===n[0]&&"r"===n[1])?e.setAttribute(n,t):e.removeAttribute(n))}}function $(e){this.l[e.type+!1](n.event?n.event(e):e)}function H(e){this.l[e.type+!0](n.event?n.event(e):e)}function I(e,t,_,l,o,r,i,u,s){var f,a,d,h,v,k,g,b,C,x,P,S=t.type;if(void 0!==t.constructor)return null;null!=_.__h&&(s=_.__h,u=t.__e=_.__e,t.__h=null,r=[u]),(f=n.__b)&&f(t);try{e:if("function"==typeof S){if(b=t.props,C=(f=S.contextType)&&l[f.__c],x=f?C?C.props.value:f.__:l,_.__c?g=(a=t.__c=_.__c).__=a.__E:("prototype"in S&&S.prototype.render?t.__c=a=new S(b,x):(t.__c=a=new p(b,x),a.constructor=S,a.render=M),C&&C.sub(a),a.props=b,a.state||(a.state={}),a.context=x,a.__n=l,d=a.__d=!0,a.__h=[]),null==a.__s&&(a.__s=a.state),null!=S.getDerivedStateFromProps&&(a.__s==a.state&&(a.__s=c({},a.__s)),c(a.__s,S.getDerivedStateFromProps(b,a.__s))),h=a.props,v=a.state,d)null==S.getDerivedStateFromProps&&null!=a.componentWillMount&&a.componentWillMount(),null!=a.componentDidMount&&a.__h.push(a.componentDidMount);else{if(null==S.getDerivedStateFromProps&&b!==h&&null!=a.componentWillReceiveProps&&a.componentWillReceiveProps(b,x),!a.__e&&null!=a.shouldComponentUpdate&&!1===a.shouldComponentUpdate(b,a.__s,x)||t.__v===_.__v){a.props=b,a.state=a.__s,t.__v!==_.__v&&(a.__d=!1),a.__v=t,t.__e=_.__e,t.__k=_.__k,t.__k.forEach(function(e){e&&(e.__=t)}),a.__h.length&&i.push(a);break e}null!=a.componentWillUpdate&&a.componentWillUpdate(b,a.__s,x),null!=a.componentDidUpdate&&a.__h.push(function(){a.componentDidUpdate(h,v,k)})}a.context=x,a.props=b,a.state=a.__s,(f=n.__r)&&f(t),a.__d=!1,a.__v=t,a.__P=e,f=a.render(a.props,a.state,a.context),a.state=a.__s,null!=a.getChildContext&&(l=c(c({},l),a.getChildContext())),d||null==a.getSnapshotBeforeUpdate||(k=a.getSnapshotBeforeUpdate(h,v)),P=null!=f&&f.type===y&&null==f.key?f.props.children:f,m(e,Array.isArray(P)?P:[P],t,_,l,o,r,i,u,s),a.base=t.__e,t.__h=null,a.__h.length&&i.push(a),g&&(a.__E=a.__=null),a.__e=!1}else null==r&&t.__v===_.__v?(t.__k=_.__k,t.__e=_.__e):t.__e=j(_.__e,t,_,l,o,r,i,s);(f=n.diffed)&&f(t)}catch(e){t.__v=null,(s||null!=r)&&(t.__e=u,t.__h=!!s,r[r.indexOf(u)]=null),n.__e(e,t,_)}}function T(e,t){n.__c&&n.__c(t,e),e.some(function(t){try{e=t.__h,t.__h=[],e.some(function(e){e.call(t)})}catch(e){n.__e(e,t.__v)}})}function j(e,n,t,_,l,o,i,u){var c,p,a,d,h=t.props,v=n.props,y=n.type,k=0;if("svg"===y&&(l=!0),null!=o)for(;k<o.length;k++)if((c=o[k])&&(c===e||(y?c.localName==y:3==c.nodeType))){e=c,o[k]=null;break}if(null==e){if(null===y)return document.createTextNode(v);e=l?document.createElementNS("http://www.w3.org/2000/svg",y):document.createElement(y,v.is&&v),o=null,u=!1}if(null===y)h===v||u&&e.data===v||(e.data=v);else{if(o=o&&f.slice.call(e.childNodes),p=(h=t.props||r).dangerouslySetInnerHTML,a=v.dangerouslySetInnerHTML,!u){if(null!=o)for(h={},d=0;d<e.attributes.length;d++)h[e.attributes[d].name]=e.attributes[d].value;(a||p)&&(a&&(p&&a.__html==p.__html||a.__html===e.innerHTML)||(e.innerHTML=a&&a.__html||""))}if(A(e,v,h,l,u),a)n.__k=[];else if(k=n.props.children,m(e,Array.isArray(k)?k:[k],n,t,_,l&&"foreignObject"!==y,o,i,e.firstChild,u),null!=o)for(k=o.length;k--;)null!=o[k]&&s(o[k]);u||("value"in v&&void 0!==(k=v.value)&&(k!==e.value||"progress"===y&&!k)&&C(e,"value",k,h.value,!1),"checked"in v&&void 0!==(k=v.checked)&&k!==e.checked&&C(e,"checked",k,h.checked,!1))}return e}function z(e,t,_){try{"function"==typeof e?e(t):e.current=t}catch(e){n.__e(e,_)}}function L(e,t,_){var l,o,r;if(n.unmount&&n.unmount(e),(l=e.ref)&&(l.current&&l.current!==e.__e||z(l,null,t)),_||"function"==typeof e.type||(_=null!=(o=e.__e)),e.__e=e.__d=void 0,null!=(l=e.__c)){if(l.componentWillUnmount)try{l.componentWillUnmount()}catch(e){n.__e(e,t)}l.base=l.__P=null}if(l=e.__k)for(r=0;r<l.length;r++)l[r]&&L(l[r],t,_);null!=o&&s(o)}function M(e,n,t){return this.constructor(e,t)}function N(e,t,_){var l,o,i;n.__&&n.__(e,t),o=(l="function"==typeof _)?null:_&&_.__k||t.__k,i=[],I(t,e=(!l&&_||t).__k=a(y,null,[e]),o||r,r,void 0!==t.ownerSVGElement,!l&&_?[_]:o?null:t.firstChild?f.slice.call(t.childNodes):null,i,!l&&_?_:o?o.__e:t.firstChild,l),T(i,e)}function O(e,n){N(e,n,O)}function S(e,n,t){var _,l,o,r=arguments,i=c({},e.props);for(o in n)"key"==o?_=n[o]:"ref"==o?l=n[o]:i[o]=n[o];if(arguments.length>3)for(t=[t],o=3;o<arguments.length;o++)t.push(r[o]);return null!=t&&(i.children=t),v(e.type,i,_||e.key,l||e.ref,null)}function q(e,n){var t={__c:n="__cC"+o++,__:e,Consumer:function(e,n){return e.children(n)},Provider:function(e){var t,_;return this.getChildContext||(t=[],(_={})[n]=this,this.getChildContext=function(){return _},this.shouldComponentUpdate=function(e){this.props.value!==e.value&&t.some(k)},this.sub=function(e){t.push(e);var n=e.componentWillUnmount;e.componentWillUnmount=function(){t.splice(t.indexOf(e),1),n&&n.call(e)}}),e.children}};return t.Provider.__=t.Consumer.contextType=t}n={__e:function(e,n){for(var t,_,l;n=n.__;)if((t=n.__c)&&!t.__)try{if((_=t.constructor)&&null!=_.getDerivedStateFromError&&(t.setState(_.getDerivedStateFromError(e)),l=t.__d),null!=t.componentDidCatch&&(t.componentDidCatch(e),l=t.__d),l)return t.__E=t}catch(n){e=n}throw e},__v:0},l=function(e){return null!=e&&void 0===e.constructor},p.prototype.setState=function(e,n){var t;t=null!=this.__s&&this.__s!==this.state?this.__s:this.__s=c({},this.state),"function"==typeof e&&(e=e(c({},t),this.props)),e&&c(t,e),null!=e&&this.__v&&(n&&this.__h.push(n),k(this))},p.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),k(this))},p.prototype.render=y,u=[],i="function"==typeof Promise?Promise.prototype.then.bind(Promise.resolve()):setTimeout,b.__r=0,o=0;export{N as render,O as hydrate,a as createElement,a as h,y as Fragment,h as createRef,l as isValidElement,p as Component,S as cloneElement,q as createContext,w as toChildArray,n as options};
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_dump/skeleton.html
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>TorchScript Model</title>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<style>
|
| 7 |
+
table, th, td {
|
| 8 |
+
border: 1px solid black;
|
| 9 |
+
border-collapse: collapse;
|
| 10 |
+
}
|
| 11 |
+
.caret {
|
| 12 |
+
cursor: pointer;
|
| 13 |
+
user-select: none;
|
| 14 |
+
}
|
| 15 |
+
</style>
|
| 16 |
+
<script type="module" src="./code.js"></script>
|
| 17 |
+
</head>
|
| 18 |
+
|
| 19 |
+
<body>
|
| 20 |
+
</body>
|
| 21 |
+
</html>
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/model_zoo.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# torchvision imports tqdm from here.
|
| 2 |
+
from torch.hub import tqdm, load_state_dict_from_url as load_url # noqa: F401
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/module_tracker.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import logging
|
| 3 |
+
import weakref
|
| 4 |
+
from typing import TYPE_CHECKING
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch.autograd.graph import register_multi_grad_hook
|
| 8 |
+
from torch.nn.modules.module import (
|
| 9 |
+
register_module_forward_hook,
|
| 10 |
+
register_module_forward_pre_hook,
|
| 11 |
+
)
|
| 12 |
+
from torch.utils._pytree import tree_flatten
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
if TYPE_CHECKING:
|
| 16 |
+
from torch.utils.hooks import RemovableHandle
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
__all__ = ["ModuleTracker"]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class ModuleTracker:
|
| 26 |
+
"""
|
| 27 |
+
``ModuleTracker`` is a context manager that tracks the nn.Module hierarchy during execution
|
| 28 |
+
so that other system can query which Module is currently being executed (or its backward is being
|
| 29 |
+
executed).
|
| 30 |
+
|
| 31 |
+
You can access the ``parents`` attribute on this context manager to get the set of all the
|
| 32 |
+
Modules currently being executed via their fqn (fully qualified name, also used as the key within
|
| 33 |
+
the state_dict).
|
| 34 |
+
You can access the ``is_bw`` attribute to know if you are currently running in backward or not.
|
| 35 |
+
|
| 36 |
+
Note that ``parents`` is never empty and always contains the "Global" key. The ``is_bw`` flag
|
| 37 |
+
will remain ``True`` after the forward until another Module is executed. If you need it to be
|
| 38 |
+
more accurate, please submit an issue requesting this. Adding a map from fqn to the module instance
|
| 39 |
+
is possible but not done yet, please submit an issue requesting this if you need it.
|
| 40 |
+
|
| 41 |
+
Example usage
|
| 42 |
+
|
| 43 |
+
.. code-block:: python
|
| 44 |
+
|
| 45 |
+
mod = torch.nn.Linear(2, 2)
|
| 46 |
+
|
| 47 |
+
with ModuleTracker() as tracker:
|
| 48 |
+
# Access anything during the forward pass
|
| 49 |
+
def my_linear(m1, m2, bias):
|
| 50 |
+
print(f"Current modules: {tracker.parents}")
|
| 51 |
+
return torch.mm(m1, m2.t()) + bias
|
| 52 |
+
|
| 53 |
+
torch.nn.functional.linear = my_linear
|
| 54 |
+
|
| 55 |
+
mod(torch.rand(2, 2))
|
| 56 |
+
|
| 57 |
+
"""
|
| 58 |
+
|
| 59 |
+
parents: set[str]
|
| 60 |
+
"""
|
| 61 |
+
A Set containing the fqn for each module currently running their forward
|
| 62 |
+
"""
|
| 63 |
+
|
| 64 |
+
def __init__(self) -> None:
|
| 65 |
+
self.parents = {"Global"}
|
| 66 |
+
self._known_modules: weakref.WeakKeyDictionary = weakref.WeakKeyDictionary()
|
| 67 |
+
self._seen_modules: weakref.WeakSet = weakref.WeakSet()
|
| 68 |
+
self._has_callback = False
|
| 69 |
+
self._hooks: list[RemovableHandle] = []
|
| 70 |
+
|
| 71 |
+
def _maybe_set_engine_callback(self) -> None:
|
| 72 |
+
# This assumes no concurrent calls to backward
|
| 73 |
+
if self._has_callback:
|
| 74 |
+
return
|
| 75 |
+
|
| 76 |
+
def callback() -> None:
|
| 77 |
+
self.parents = {"Global"}
|
| 78 |
+
self._has_callback = False
|
| 79 |
+
|
| 80 |
+
torch.autograd.Variable._execution_engine.queue_callback(callback)
|
| 81 |
+
self._has_callback = True
|
| 82 |
+
|
| 83 |
+
@property
|
| 84 |
+
def is_bw(self):
|
| 85 |
+
"""
|
| 86 |
+
A boolean marking if this is currently running during the backward pass or not
|
| 87 |
+
"""
|
| 88 |
+
return torch._C._current_graph_task_id() != -1
|
| 89 |
+
|
| 90 |
+
def _get_mod_name(self, mod):
|
| 91 |
+
if mod not in self._known_modules:
|
| 92 |
+
self._known_modules[mod] = type(mod).__name__
|
| 93 |
+
mod_name = self._known_modules[mod]
|
| 94 |
+
if mod not in self._seen_modules:
|
| 95 |
+
for name, submod in mod.named_children():
|
| 96 |
+
self._known_modules[submod] = f"{mod_name}.{name}"
|
| 97 |
+
self._get_mod_name(submod)
|
| 98 |
+
self._seen_modules.add(mod)
|
| 99 |
+
return mod_name
|
| 100 |
+
|
| 101 |
+
def _get_append_fn(self, name, is_bw):
|
| 102 |
+
def fn(*args) -> None:
|
| 103 |
+
if is_bw:
|
| 104 |
+
self._maybe_set_engine_callback()
|
| 105 |
+
if name in self.parents:
|
| 106 |
+
logger.info(
|
| 107 |
+
"The module hierarchy tracking seems to be broken as this Module was already entered. %s during %s",
|
| 108 |
+
name,
|
| 109 |
+
"backward" if is_bw else "forward",
|
| 110 |
+
)
|
| 111 |
+
self.parents.add(name)
|
| 112 |
+
|
| 113 |
+
return fn
|
| 114 |
+
|
| 115 |
+
def _get_pop_fn(self, name, is_bw):
|
| 116 |
+
def fn(*args) -> None:
|
| 117 |
+
if name in self.parents:
|
| 118 |
+
self.parents.remove(name)
|
| 119 |
+
else:
|
| 120 |
+
logger.info(
|
| 121 |
+
"The Module hierarchy tracking is confused as we're exiting a Module that was never entered. %s during %s",
|
| 122 |
+
name,
|
| 123 |
+
"backward" if is_bw else "forward",
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
return fn
|
| 127 |
+
|
| 128 |
+
def _fw_pre_hook(self, mod, input) -> None:
|
| 129 |
+
name = self._get_mod_name(mod)
|
| 130 |
+
self._get_append_fn(name, False)()
|
| 131 |
+
|
| 132 |
+
args, _ = tree_flatten(input)
|
| 133 |
+
tensors = [a for a in args if isinstance(a, torch.Tensor) and a.requires_grad]
|
| 134 |
+
if tensors:
|
| 135 |
+
self._hooks.append(
|
| 136 |
+
register_multi_grad_hook(tensors, self._get_pop_fn(name, True))
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
def _fw_post_hook(self, mod, input, output) -> None:
|
| 140 |
+
name = self._get_mod_name(mod)
|
| 141 |
+
self._get_pop_fn(name, False)()
|
| 142 |
+
|
| 143 |
+
args, _ = tree_flatten(output)
|
| 144 |
+
tensors = [a for a in args if isinstance(a, torch.Tensor) and a.requires_grad]
|
| 145 |
+
if tensors:
|
| 146 |
+
self._hooks.append(
|
| 147 |
+
register_multi_grad_hook(tensors, self._get_append_fn(name, True))
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
def __enter__(self):
|
| 151 |
+
self._fw_pre_handle = register_module_forward_pre_hook(self._fw_pre_hook)
|
| 152 |
+
self._fw_post_handle = register_module_forward_hook(self._fw_post_hook)
|
| 153 |
+
return self
|
| 154 |
+
|
| 155 |
+
def __exit__(self, *args):
|
| 156 |
+
self._fw_pre_handle.remove()
|
| 157 |
+
self._fw_post_handle.remove()
|
| 158 |
+
for hook in self._hooks:
|
| 159 |
+
hook.remove()
|
| 160 |
+
self._hooks.clear()
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/serialization/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from . import config
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/serialization/config.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from typing import Optional as _Optional, TYPE_CHECKING as _TYPE_CHECKING
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
if _TYPE_CHECKING:
|
| 6 |
+
from torch.serialization import LoadEndianness as _LoadEndianess
|
| 7 |
+
|
| 8 |
+
from torch.utils._config_module import install_config_module as _install_config_module
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class load:
|
| 12 |
+
mmap: bool = False
|
| 13 |
+
endianness: _Optional["_LoadEndianess"] = None
|
| 14 |
+
# MAP_PRIVATE = 2
|
| 15 |
+
mmap_flags: int | None = None if sys.platform == "win32" else 2
|
| 16 |
+
calculate_storage_offsets: bool = False
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class save:
|
| 20 |
+
compute_crc32: bool = True
|
| 21 |
+
use_pinned_memory_for_d2h: bool = False
|
| 22 |
+
storage_alignment: int = 64
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
_install_config_module(sys.modules[__name__])
|
outputs/audit_venv/lib/python3.11/site-packages/torch/utils/show_pickle.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# mypy: allow-untyped-defs
|
| 3 |
+
import sys
|
| 4 |
+
import pickle
|
| 5 |
+
import struct
|
| 6 |
+
import pprint
|
| 7 |
+
import zipfile
|
| 8 |
+
import fnmatch
|
| 9 |
+
from typing import Any, IO
|
| 10 |
+
|
| 11 |
+
__all__ = ["FakeObject", "FakeClass", "DumpUnpickler", "main"]
|
| 12 |
+
|
| 13 |
+
class FakeObject:
|
| 14 |
+
def __init__(self, module, name, args) -> None:
|
| 15 |
+
self.module = module
|
| 16 |
+
self.name = name
|
| 17 |
+
self.args = args
|
| 18 |
+
# NOTE: We don't distinguish between state never set and state set to None.
|
| 19 |
+
self.state = None
|
| 20 |
+
|
| 21 |
+
def __repr__(self) -> str:
|
| 22 |
+
state_str = "" if self.state is None else f"(state={self.state!r})"
|
| 23 |
+
return f"{self.module}.{self.name}{self.args!r}{state_str}"
|
| 24 |
+
|
| 25 |
+
def __setstate__(self, state):
|
| 26 |
+
self.state = state
|
| 27 |
+
|
| 28 |
+
@staticmethod
|
| 29 |
+
def pp_format(printer, obj, stream, indent, allowance, context, level) -> None:
|
| 30 |
+
if not obj.args and obj.state is None:
|
| 31 |
+
stream.write(repr(obj))
|
| 32 |
+
return
|
| 33 |
+
if obj.state is None:
|
| 34 |
+
stream.write(f"{obj.module}.{obj.name}")
|
| 35 |
+
printer._format(obj.args, stream, indent + 1, allowance + 1, context, level)
|
| 36 |
+
return
|
| 37 |
+
if not obj.args:
|
| 38 |
+
stream.write(f"{obj.module}.{obj.name}()(state=\n")
|
| 39 |
+
indent += printer._indent_per_level
|
| 40 |
+
stream.write(" " * indent)
|
| 41 |
+
printer._format(obj.state, stream, indent, allowance + 1, context, level + 1)
|
| 42 |
+
stream.write(")")
|
| 43 |
+
return
|
| 44 |
+
raise Exception("Need to implement") # noqa: TRY002
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class FakeClass:
|
| 48 |
+
def __init__(self, module, name) -> None:
|
| 49 |
+
self.module = module
|
| 50 |
+
self.name = name
|
| 51 |
+
self.__new__ = self.fake_new # type: ignore[assignment]
|
| 52 |
+
|
| 53 |
+
def __repr__(self) -> str:
|
| 54 |
+
return f"{self.module}.{self.name}"
|
| 55 |
+
|
| 56 |
+
def __call__(self, *args):
|
| 57 |
+
return FakeObject(self.module, self.name, args)
|
| 58 |
+
|
| 59 |
+
def fake_new(self, *args):
|
| 60 |
+
return FakeObject(self.module, self.name, args[1:])
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class DumpUnpickler(pickle._Unpickler): # type: ignore[name-defined]
|
| 64 |
+
def __init__(
|
| 65 |
+
self,
|
| 66 |
+
file,
|
| 67 |
+
*,
|
| 68 |
+
catch_invalid_utf8=False,
|
| 69 |
+
**kwargs) -> None:
|
| 70 |
+
super().__init__(file, **kwargs)
|
| 71 |
+
self.catch_invalid_utf8 = catch_invalid_utf8
|
| 72 |
+
|
| 73 |
+
def find_class(self, module, name):
|
| 74 |
+
return FakeClass(module, name)
|
| 75 |
+
|
| 76 |
+
def persistent_load(self, pid):
|
| 77 |
+
return FakeObject("pers", "obj", (pid,))
|
| 78 |
+
|
| 79 |
+
dispatch = dict(pickle._Unpickler.dispatch) # type: ignore[attr-defined]
|
| 80 |
+
|
| 81 |
+
# Custom objects in TorchScript are able to return invalid UTF-8 strings
|
| 82 |
+
# from their pickle (__getstate__) functions. Install a custom loader
|
| 83 |
+
# for strings that catches the decode exception and replaces it with
|
| 84 |
+
# a sentinel object.
|
| 85 |
+
def load_binunicode(self) -> None:
|
| 86 |
+
strlen, = struct.unpack("<I", self.read(4)) # type: ignore[attr-defined]
|
| 87 |
+
if strlen > sys.maxsize:
|
| 88 |
+
raise Exception("String too long.") # noqa: TRY002
|
| 89 |
+
str_bytes = self.read(strlen) # type: ignore[attr-defined]
|
| 90 |
+
obj: Any
|
| 91 |
+
try:
|
| 92 |
+
obj = str(str_bytes, "utf-8", "surrogatepass")
|
| 93 |
+
except UnicodeDecodeError as exn:
|
| 94 |
+
if not self.catch_invalid_utf8:
|
| 95 |
+
raise
|
| 96 |
+
obj = FakeObject("builtin", "UnicodeDecodeError", (str(exn),))
|
| 97 |
+
self.append(obj) # type: ignore[attr-defined]
|
| 98 |
+
dispatch[pickle.BINUNICODE[0]] = load_binunicode # type: ignore[assignment]
|
| 99 |
+
|
| 100 |
+
@classmethod
|
| 101 |
+
def dump(cls, in_stream, out_stream):
|
| 102 |
+
value = cls(in_stream).load()
|
| 103 |
+
pprint.pprint(value, stream=out_stream)
|
| 104 |
+
return value
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def main(argv, output_stream=None) -> int | None:
|
| 108 |
+
if len(argv) != 2:
|
| 109 |
+
# Don't spam stderr if not using stdout.
|
| 110 |
+
if output_stream is not None:
|
| 111 |
+
raise Exception("Pass argv of length 2.") # noqa: TRY002
|
| 112 |
+
sys.stderr.write("usage: show_pickle PICKLE_FILE\n")
|
| 113 |
+
sys.stderr.write(" PICKLE_FILE can be any of:\n")
|
| 114 |
+
sys.stderr.write(" path to a pickle file\n")
|
| 115 |
+
sys.stderr.write(" file.zip@member.pkl\n")
|
| 116 |
+
sys.stderr.write(" file.zip@*/pattern.*\n")
|
| 117 |
+
sys.stderr.write(" (shell glob pattern for members)\n")
|
| 118 |
+
sys.stderr.write(" (only first match will be shown)\n")
|
| 119 |
+
return 2
|
| 120 |
+
|
| 121 |
+
fname = argv[1]
|
| 122 |
+
handle: IO[bytes]
|
| 123 |
+
if "@" not in fname:
|
| 124 |
+
with open(fname, "rb") as handle:
|
| 125 |
+
DumpUnpickler.dump(handle, output_stream)
|
| 126 |
+
else:
|
| 127 |
+
zfname, mname = fname.split("@", 1)
|
| 128 |
+
with zipfile.ZipFile(zfname) as zf:
|
| 129 |
+
if "*" not in mname:
|
| 130 |
+
with zf.open(mname) as handle:
|
| 131 |
+
DumpUnpickler.dump(handle, output_stream)
|
| 132 |
+
else:
|
| 133 |
+
found = False
|
| 134 |
+
for info in zf.infolist():
|
| 135 |
+
if fnmatch.fnmatch(info.filename, mname):
|
| 136 |
+
with zf.open(info) as handle:
|
| 137 |
+
DumpUnpickler.dump(handle, output_stream)
|
| 138 |
+
found = True
|
| 139 |
+
break
|
| 140 |
+
if not found:
|
| 141 |
+
raise Exception(f"Could not find member matching {mname} in {zfname}") # noqa: TRY002
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
# This hack works on every version of Python I've tested.
|
| 146 |
+
# I've tested on the following versions:
|
| 147 |
+
# 3.7.4
|
| 148 |
+
if True:
|
| 149 |
+
pprint.PrettyPrinter._dispatch[FakeObject.__repr__] = FakeObject.pp_format # type: ignore[attr-defined]
|
| 150 |
+
|
| 151 |
+
sys.exit(main(sys.argv))
|