auto-sync 2026-07-02T13:37:00Z workspace (part 27)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/INSTALLER +1 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/METADATA +48 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/RECORD +0 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/WHEEL +5 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/entry_points.txt +5 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/LICENSE +84 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/NOTICE +456 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/top_level.txt +2 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/activation.py +20 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/batchnorm.py +11 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/conv.py +29 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/dropout.py +14 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/embedding_ops.py +18 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/functional_modules.py +18 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/linear.py +14 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/normalization.py +26 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/rnn.py +11 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/utils.py +17 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/__init__.py +48 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_deprecation_utils.py +53 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/__init__.py +10 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/conv_expanded_weights.py +82 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/conv_utils.py +355 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/embedding_expanded_weights.py +88 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/expanded_weights_impl.py +186 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/expanded_weights_utils.py +188 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/group_norm_expanded_weights.py +106 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/instance_norm_expanded_weights.py +101 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/layer_norm_expanded_weights.py +88 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/linear_expanded_weights.py +63 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_named_member_accessor.py +381 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_per_sample_grad.py +126 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/clip_grad.py +298 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/convert_parameters.py +90 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/fusion.py +196 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/init.py +55 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/memory_format.py +174 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/parametrizations.py +636 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/parametrize.py +886 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/prune.py +1395 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/rnn.py +589 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/spectral_norm.py +364 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/stateless.py +279 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/weight_norm.py +165 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/numa/__init__.py +0 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/numa/binding.py +727 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/__init__.py +361 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/_constants.py +24 -0
- workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/_flags.py +55 -0
.gitattributes
CHANGED
|
@@ -232,3 +232,5 @@ workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cpu
|
|
| 232 |
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda.so filter=lfs diff=lfs merge=lfs -text
|
| 233 |
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda_linalg.so filter=lfs diff=lfs merge=lfs -text
|
| 234 |
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_python.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 232 |
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda.so filter=lfs diff=lfs merge=lfs -text
|
| 233 |
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_cuda_linalg.so filter=lfs diff=lfs merge=lfs -text
|
| 234 |
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/lib/libtorch_python.so filter=lfs diff=lfs merge=lfs -text
|
| 235 |
+
workspace/outputs/audit_venv/lib/python3.11/site-packages/yaml/_yaml.cpython-311-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 236 |
+
workspace/outputs/external_vla_export_maniskill_full_no_images/train.jsonl filter=lfs diff=lfs merge=lfs -text
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
workspace/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
|
workspace/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
|
|
|
workspace/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 |
+
|
workspace/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
|
workspace/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.
|
workspace/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.
|
workspace/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
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/activation.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.activation import (
|
| 12 |
+
ELU,
|
| 13 |
+
Hardswish,
|
| 14 |
+
LeakyReLU,
|
| 15 |
+
MultiheadAttention,
|
| 16 |
+
PReLU,
|
| 17 |
+
ReLU6,
|
| 18 |
+
Sigmoid,
|
| 19 |
+
Softmax,
|
| 20 |
+
)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/batchnorm.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.batchnorm import BatchNorm2d, BatchNorm3d
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/conv.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.conv import (
|
| 12 |
+
_reverse_repeat_padding,
|
| 13 |
+
Conv1d,
|
| 14 |
+
Conv2d,
|
| 15 |
+
Conv3d,
|
| 16 |
+
ConvTranspose1d,
|
| 17 |
+
ConvTranspose2d,
|
| 18 |
+
ConvTranspose3d,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
__all__ = [
|
| 23 |
+
"Conv1d",
|
| 24 |
+
"Conv2d",
|
| 25 |
+
"Conv3d",
|
| 26 |
+
"ConvTranspose1d",
|
| 27 |
+
"ConvTranspose2d",
|
| 28 |
+
"ConvTranspose3d",
|
| 29 |
+
]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/dropout.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.dropout import Dropout
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
__all__ = ["Dropout"]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/embedding_ops.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.embedding_ops import (
|
| 12 |
+
Embedding,
|
| 13 |
+
EmbeddingBag,
|
| 14 |
+
EmbeddingPackedParams,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
__all__ = ["EmbeddingPackedParams", "Embedding", "EmbeddingBag"]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/functional_modules.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.functional_modules import (
|
| 12 |
+
FloatFunctional,
|
| 13 |
+
FXFloatFunctional,
|
| 14 |
+
QFunctional,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
__all__ = ["FloatFunctional", "FXFloatFunctional", "QFunctional"]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/linear.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.linear import Linear, LinearPackedParams
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
__all__ = ["LinearPackedParams", "Linear"]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/normalization.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.normalization import (
|
| 12 |
+
GroupNorm,
|
| 13 |
+
InstanceNorm1d,
|
| 14 |
+
InstanceNorm2d,
|
| 15 |
+
InstanceNorm3d,
|
| 16 |
+
LayerNorm,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
__all__ = [
|
| 21 |
+
"LayerNorm",
|
| 22 |
+
"GroupNorm",
|
| 23 |
+
"InstanceNorm1d",
|
| 24 |
+
"InstanceNorm2d",
|
| 25 |
+
"InstanceNorm3d",
|
| 26 |
+
]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/rnn.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.rnn import LSTM
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/utils.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# flake8: noqa: F401
|
| 2 |
+
r"""Quantized Modules.
|
| 3 |
+
|
| 4 |
+
This file is in the process of migration to `torch/ao/nn/quantized`, and
|
| 5 |
+
is kept here for compatibility while the migration process is ongoing.
|
| 6 |
+
If you are adding a new entry/functionality, please, add it to the
|
| 7 |
+
appropriate file under the `torch/ao/nn/quantized/modules`,
|
| 8 |
+
while adding an import statement here.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from torch.ao.nn.quantized.modules.utils import (
|
| 12 |
+
_hide_packed_params_repr,
|
| 13 |
+
_ntuple_from_first,
|
| 14 |
+
_pair_from_first,
|
| 15 |
+
_quantize_weight,
|
| 16 |
+
WeightedQuantizedModule,
|
| 17 |
+
)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/__init__.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from . import parametrizations, parametrize, rnn, stateless
|
| 2 |
+
from .clip_grad import (
|
| 3 |
+
_clip_grads_with_norm_ as clip_grads_with_norm_,
|
| 4 |
+
_get_total_norm as get_total_norm,
|
| 5 |
+
clip_grad_norm,
|
| 6 |
+
clip_grad_norm_,
|
| 7 |
+
clip_grad_value_,
|
| 8 |
+
)
|
| 9 |
+
from .convert_parameters import parameters_to_vector, vector_to_parameters
|
| 10 |
+
from .fusion import (
|
| 11 |
+
fuse_conv_bn_eval,
|
| 12 |
+
fuse_conv_bn_weights,
|
| 13 |
+
fuse_linear_bn_eval,
|
| 14 |
+
fuse_linear_bn_weights,
|
| 15 |
+
)
|
| 16 |
+
from .init import skip_init
|
| 17 |
+
from .memory_format import (
|
| 18 |
+
convert_conv2d_weight_memory_format,
|
| 19 |
+
convert_conv3d_weight_memory_format,
|
| 20 |
+
)
|
| 21 |
+
from .spectral_norm import remove_spectral_norm, spectral_norm
|
| 22 |
+
from .weight_norm import remove_weight_norm, weight_norm
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
__all__ = [
|
| 26 |
+
"clip_grad_norm",
|
| 27 |
+
"clip_grad_norm_",
|
| 28 |
+
"clip_grads_with_norm_",
|
| 29 |
+
"clip_grad_value_",
|
| 30 |
+
"convert_conv2d_weight_memory_format",
|
| 31 |
+
"convert_conv3d_weight_memory_format",
|
| 32 |
+
"fuse_conv_bn_eval",
|
| 33 |
+
"fuse_conv_bn_weights",
|
| 34 |
+
"fuse_linear_bn_eval",
|
| 35 |
+
"fuse_linear_bn_weights",
|
| 36 |
+
"get_total_norm",
|
| 37 |
+
"parameters_to_vector",
|
| 38 |
+
"parametrizations",
|
| 39 |
+
"parametrize",
|
| 40 |
+
"remove_spectral_norm",
|
| 41 |
+
"remove_weight_norm",
|
| 42 |
+
"rnn",
|
| 43 |
+
"skip_init",
|
| 44 |
+
"spectral_norm",
|
| 45 |
+
"stateless",
|
| 46 |
+
"vector_to_parameters",
|
| 47 |
+
"weight_norm",
|
| 48 |
+
]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_deprecation_utils.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import importlib
|
| 2 |
+
import warnings
|
| 3 |
+
from collections.abc import Callable
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
_MESSAGE_TEMPLATE = (
|
| 7 |
+
r"Usage of '{old_location}' is deprecated; please use '{new_location}' instead."
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def lazy_deprecated_import(
|
| 12 |
+
all: list[str],
|
| 13 |
+
old_module: str,
|
| 14 |
+
new_module: str,
|
| 15 |
+
) -> Callable:
|
| 16 |
+
r"""Import utility to lazily import deprecated packages / modules / functional.
|
| 17 |
+
|
| 18 |
+
The old_module and new_module are also used in the deprecation warning defined
|
| 19 |
+
by the `_MESSAGE_TEMPLATE`.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
all: The list of the functions that are imported. Generally, the module's
|
| 23 |
+
__all__ list of the module.
|
| 24 |
+
old_module: Old module location
|
| 25 |
+
new_module: New module location / Migrated location
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
Callable to assign to the `__getattr__`
|
| 29 |
+
|
| 30 |
+
Usage:
|
| 31 |
+
|
| 32 |
+
# In the `torch/nn/quantized/functional.py`
|
| 33 |
+
from torch.nn.utils._deprecation_utils import lazy_deprecated_import
|
| 34 |
+
_MIGRATED_TO = "torch.ao.nn.quantized.functional"
|
| 35 |
+
__getattr__ = lazy_deprecated_import(
|
| 36 |
+
all=__all__,
|
| 37 |
+
old_module=__name__,
|
| 38 |
+
new_module=_MIGRATED_TO)
|
| 39 |
+
"""
|
| 40 |
+
warning_message = _MESSAGE_TEMPLATE.format(
|
| 41 |
+
old_location=old_module, new_location=new_module
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
def getattr_dunder(name: str) -> None:
|
| 45 |
+
if name in all:
|
| 46 |
+
# We are using the "RuntimeWarning" to make sure it is not
|
| 47 |
+
# ignored by default.
|
| 48 |
+
warnings.warn(warning_message, RuntimeWarning, stacklevel=2)
|
| 49 |
+
package = importlib.import_module(new_module)
|
| 50 |
+
return getattr(package, name)
|
| 51 |
+
raise AttributeError(f"Module {new_module!r} has no attribute {name!r}.")
|
| 52 |
+
|
| 53 |
+
return getattr_dunder
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .conv_expanded_weights import ConvPerSampleGrad
|
| 2 |
+
from .embedding_expanded_weights import EmbeddingPerSampleGrad
|
| 3 |
+
from .expanded_weights_impl import ExpandedWeight
|
| 4 |
+
from .group_norm_expanded_weights import GroupNormPerSampleGrad
|
| 5 |
+
from .instance_norm_expanded_weights import InstanceNormPerSampleGrad
|
| 6 |
+
from .layer_norm_expanded_weights import LayerNormPerSampleGrad
|
| 7 |
+
from .linear_expanded_weights import LinearPerSampleGrad
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
__all__ = ["ExpandedWeight"]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/conv_expanded_weights.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Callable
|
| 2 |
+
from typing import Any, TypeVar
|
| 3 |
+
from typing_extensions import ParamSpec
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
_P = ParamSpec("_P")
|
| 10 |
+
_R = TypeVar("_R")
|
| 11 |
+
|
| 12 |
+
from .conv_utils import (
|
| 13 |
+
conv_args_and_kwargs,
|
| 14 |
+
conv_backward,
|
| 15 |
+
conv_input_for_string_padding,
|
| 16 |
+
conv_picker,
|
| 17 |
+
)
|
| 18 |
+
from .expanded_weights_impl import ExpandedWeight, implements_per_sample_grads
|
| 19 |
+
from .expanded_weights_utils import forward_helper
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@implements_per_sample_grads(F.conv1d)
|
| 23 |
+
@implements_per_sample_grads(F.conv2d)
|
| 24 |
+
@implements_per_sample_grads(F.conv3d)
|
| 25 |
+
class ConvPerSampleGrad(torch.autograd.Function):
|
| 26 |
+
@staticmethod
|
| 27 |
+
# pyrefly: ignore [bad-override]
|
| 28 |
+
def forward(
|
| 29 |
+
ctx: Any,
|
| 30 |
+
kwarg_names: list[str],
|
| 31 |
+
conv_fn: Callable[_P, _R],
|
| 32 |
+
*expanded_args_and_kwargs: Any,
|
| 33 |
+
) -> torch.Tensor:
|
| 34 |
+
expanded_args, expanded_kwargs = conv_args_and_kwargs(
|
| 35 |
+
kwarg_names, expanded_args_and_kwargs
|
| 36 |
+
)
|
| 37 |
+
orig_input = expanded_args[0]
|
| 38 |
+
was_same_padding = expanded_kwargs["padding"] == "same"
|
| 39 |
+
|
| 40 |
+
if isinstance(expanded_kwargs["padding"], str):
|
| 41 |
+
# if padding is a string, we'll do the necessary padding (slowly) using F.pad
|
| 42 |
+
kernel_size = expanded_args[1].shape[2:]
|
| 43 |
+
padding, dilation = expanded_kwargs["padding"], expanded_kwargs["dilation"]
|
| 44 |
+
input = conv_input_for_string_padding(
|
| 45 |
+
conv_fn, padding, expanded_args[0], dilation, kernel_size
|
| 46 |
+
)
|
| 47 |
+
expanded_args = (input, expanded_args[1])
|
| 48 |
+
# since we've already done the padding, don't need any more
|
| 49 |
+
expanded_kwargs["padding"] = 0
|
| 50 |
+
|
| 51 |
+
output = forward_helper(conv_fn, expanded_args, expanded_kwargs)
|
| 52 |
+
input, weight = expanded_args
|
| 53 |
+
batched_dim_size = conv_picker(conv_fn, 3, 4, 5)
|
| 54 |
+
if input.dim() != batched_dim_size:
|
| 55 |
+
raise RuntimeError(
|
| 56 |
+
f"Expanded Weights only support convolution with batched input, got {conv_fn} with an"
|
| 57 |
+
f"unbatched input of dim {input.dim()}, expected input of dim {batched_dim_size}"
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
# pyrefly: ignore [invalid-type-var]
|
| 61 |
+
ctx.conv_fn = conv_fn
|
| 62 |
+
|
| 63 |
+
ctx.batch_size = orig_input.shape[0]
|
| 64 |
+
ctx.input_required_grad = orig_input.requires_grad
|
| 65 |
+
ctx.orig_input_shape = orig_input.shape
|
| 66 |
+
ctx.was_same_padding = was_same_padding
|
| 67 |
+
ctx.stride, ctx.padding = expanded_kwargs["stride"], expanded_kwargs["padding"]
|
| 68 |
+
ctx.dilation, ctx.groups = (
|
| 69 |
+
expanded_kwargs["dilation"],
|
| 70 |
+
expanded_kwargs["groups"],
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
if isinstance(weight, ExpandedWeight):
|
| 74 |
+
ctx.input = input
|
| 75 |
+
ctx.weight = weight
|
| 76 |
+
ctx.bias = expanded_kwargs["bias"]
|
| 77 |
+
|
| 78 |
+
return output
|
| 79 |
+
|
| 80 |
+
@staticmethod
|
| 81 |
+
def backward(ctx: Any, *grad_outputs: Any) -> Any:
|
| 82 |
+
return conv_backward(ctx.conv_fn, ctx, grad_outputs[0])
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/conv_utils.py
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from .expanded_weights_utils import (
|
| 9 |
+
set_grad_sample_if_exists,
|
| 10 |
+
unpack_expanded_weight_or_tensor,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
THRESHOLD = 32
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def conv_picker(func, conv1dOpt, conv2dOpt, conv3dOpt):
|
| 18 |
+
if func is F.conv1d:
|
| 19 |
+
return conv1dOpt
|
| 20 |
+
if func is F.conv2d:
|
| 21 |
+
return conv2dOpt
|
| 22 |
+
else:
|
| 23 |
+
if func is not F.conv3d:
|
| 24 |
+
raise AssertionError(
|
| 25 |
+
f"Expected func to be F.conv1d, F.conv2d, or F.conv3d, got {func}"
|
| 26 |
+
)
|
| 27 |
+
return conv3dOpt
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def conv_args_and_kwargs(kwarg_names, expanded_args_and_kwargs):
|
| 31 |
+
args = expanded_args_and_kwargs[: len(expanded_args_and_kwargs) - len(kwarg_names)]
|
| 32 |
+
kwargs = expanded_args_and_kwargs[
|
| 33 |
+
len(expanded_args_and_kwargs) - len(kwarg_names) :
|
| 34 |
+
]
|
| 35 |
+
kwargs = dict(zip(kwarg_names, kwargs, strict=True))
|
| 36 |
+
|
| 37 |
+
return conv_normalizer(*args, **kwargs)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def conv_normalizer(
|
| 41 |
+
input,
|
| 42 |
+
weight,
|
| 43 |
+
bias=None,
|
| 44 |
+
stride=1,
|
| 45 |
+
padding=0,
|
| 46 |
+
dilation=1,
|
| 47 |
+
groups=1,
|
| 48 |
+
):
|
| 49 |
+
return (input, weight), {
|
| 50 |
+
"bias": bias,
|
| 51 |
+
"stride": stride,
|
| 52 |
+
"padding": padding,
|
| 53 |
+
"dilation": dilation,
|
| 54 |
+
"groups": groups,
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def conv_input_for_string_padding(func, padding_style, input, dilation, kernel_size):
|
| 59 |
+
if padding_style == "valid":
|
| 60 |
+
return input
|
| 61 |
+
else:
|
| 62 |
+
padding = int_padding_for_string_padding(
|
| 63 |
+
func, padding_style, dilation, kernel_size
|
| 64 |
+
)
|
| 65 |
+
return F.pad(input, padding)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def int_padding_for_string_padding(func, padding_style, dilation, kernel_size):
|
| 69 |
+
def get_dilation(i):
|
| 70 |
+
return dilation[i] if isinstance(dilation, tuple) else dilation
|
| 71 |
+
|
| 72 |
+
if padding_style == "same":
|
| 73 |
+
padding: list[int] = []
|
| 74 |
+
# F.pad needs the padding in reverse order from what conv expects
|
| 75 |
+
for i in range(conv_picker(func, 0, 1, 2), -1, -1):
|
| 76 |
+
padding += conv_padding_for_same(get_dilation(i), kernel_size[i])
|
| 77 |
+
return padding
|
| 78 |
+
elif padding_style == "valid":
|
| 79 |
+
return conv_picker(func, 2, 4, 6) * (0,)
|
| 80 |
+
else:
|
| 81 |
+
raise RuntimeError(
|
| 82 |
+
f"got padding type of {padding_style}, only accept 'same' or 'valid'"
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def conv_padding_for_same(dilation, kernel_size):
|
| 87 |
+
total_pad = dilation * (kernel_size - 1)
|
| 88 |
+
left_pad = total_pad // 2
|
| 89 |
+
right_pad = total_pad - left_pad
|
| 90 |
+
return left_pad, right_pad
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def conv_backward(func, ctx, grad_output):
|
| 94 |
+
def weight_grad_sample(weight):
|
| 95 |
+
if batch_size < THRESHOLD and groups == 1:
|
| 96 |
+
return conv_group_weight_grad_sample(
|
| 97 |
+
ctx.input,
|
| 98 |
+
grad_output,
|
| 99 |
+
weight_shape,
|
| 100 |
+
stride,
|
| 101 |
+
padding,
|
| 102 |
+
dilation,
|
| 103 |
+
batch_size,
|
| 104 |
+
func,
|
| 105 |
+
)
|
| 106 |
+
else:
|
| 107 |
+
return conv_unfold_weight_grad_sample(
|
| 108 |
+
ctx.input,
|
| 109 |
+
grad_output,
|
| 110 |
+
weight_shape,
|
| 111 |
+
kernel_size,
|
| 112 |
+
stride,
|
| 113 |
+
padding,
|
| 114 |
+
dilation,
|
| 115 |
+
groups,
|
| 116 |
+
func,
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
def expand(param):
|
| 120 |
+
if isinstance(param, int):
|
| 121 |
+
return conv_picker(func, (param,), (param, param), (param, param, param))
|
| 122 |
+
else:
|
| 123 |
+
return param
|
| 124 |
+
|
| 125 |
+
def calc_total_padding(func, was_same, padding, dilation, kernel_size):
|
| 126 |
+
if was_same:
|
| 127 |
+
all_padding = int_padding_for_string_padding(
|
| 128 |
+
func, "same", dilation, kernel_size
|
| 129 |
+
)
|
| 130 |
+
# F.pad needs the padding in reverse order from what conv expects
|
| 131 |
+
total_padding = tuple(
|
| 132 |
+
all_padding[i] + all_padding[i - 1]
|
| 133 |
+
for i in range(len(all_padding) - 1, -1, -2)
|
| 134 |
+
)
|
| 135 |
+
return total_padding
|
| 136 |
+
else:
|
| 137 |
+
return tuple(2 * pad for pad in padding)
|
| 138 |
+
|
| 139 |
+
weight_shape = ctx.weight.shape
|
| 140 |
+
stride, padding, dilation, groups = (
|
| 141 |
+
expand(ctx.stride),
|
| 142 |
+
expand(ctx.padding),
|
| 143 |
+
expand(ctx.dilation),
|
| 144 |
+
ctx.groups,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
kernel_size = [weight_shape[i] for i in range(2, conv_picker(func, 3, 4, 5))]
|
| 148 |
+
|
| 149 |
+
batch_size = ctx.batch_size
|
| 150 |
+
results: list[torch.Tensor | None] = []
|
| 151 |
+
results.append(None) # for kwarg names
|
| 152 |
+
results.append(None) # for op reference
|
| 153 |
+
|
| 154 |
+
# "same" padding may give uneven padding on either side so we need to separate the "padding" attr and total padding
|
| 155 |
+
total_padding = calc_total_padding(
|
| 156 |
+
func, ctx.was_same_padding, padding, dilation, kernel_size
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
if ctx.input_required_grad:
|
| 160 |
+
output_padding = []
|
| 161 |
+
input_dims = conv_picker(func, 1, 2, 3)
|
| 162 |
+
for i in range(input_dims):
|
| 163 |
+
input_dim = ctx.orig_input_shape[2 + i]
|
| 164 |
+
output_padding.append(
|
| 165 |
+
(
|
| 166 |
+
total_padding[i]
|
| 167 |
+
+ input_dim
|
| 168 |
+
- (kernel_size[i] * dilation[i] - dilation[i] + 1)
|
| 169 |
+
)
|
| 170 |
+
% stride[i]
|
| 171 |
+
)
|
| 172 |
+
weight_ = unpack_expanded_weight_or_tensor(ctx.weight)
|
| 173 |
+
transpose_func = conv_picker(
|
| 174 |
+
func, F.conv_transpose1d, F.conv_transpose2d, F.conv_transpose3d
|
| 175 |
+
)
|
| 176 |
+
out = transpose_func(
|
| 177 |
+
grad_output,
|
| 178 |
+
weight_,
|
| 179 |
+
None,
|
| 180 |
+
stride,
|
| 181 |
+
padding,
|
| 182 |
+
tuple(output_padding),
|
| 183 |
+
groups,
|
| 184 |
+
dilation,
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
if ctx.was_same_padding:
|
| 188 |
+
for i in range(len(total_padding)):
|
| 189 |
+
out = torch.narrow(
|
| 190 |
+
out, 2 + i, total_padding[i] // 2, ctx.orig_input_shape[2 + i]
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
results.append(out)
|
| 194 |
+
else:
|
| 195 |
+
results.append(None)
|
| 196 |
+
# weight and bias don't compute batched gradients; no other arguments are differentiable
|
| 197 |
+
results = results + [None] * 6
|
| 198 |
+
|
| 199 |
+
# set grad_sample field for weight and bias with per sample gradients
|
| 200 |
+
set_grad_sample_if_exists(ctx.weight, weight_grad_sample)
|
| 201 |
+
set_grad_sample_if_exists(
|
| 202 |
+
ctx.bias, lambda _: grad_output.reshape(*grad_output.shape[:2], -1).sum(dim=2)
|
| 203 |
+
)
|
| 204 |
+
return tuple(results)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def conv_unfold_weight_grad_sample(
|
| 208 |
+
input,
|
| 209 |
+
grad_output,
|
| 210 |
+
weight_shape,
|
| 211 |
+
kernel_size,
|
| 212 |
+
stride,
|
| 213 |
+
padding,
|
| 214 |
+
dilation,
|
| 215 |
+
groups,
|
| 216 |
+
func,
|
| 217 |
+
):
|
| 218 |
+
n = input.shape[0]
|
| 219 |
+
in_channels = input.shape[1]
|
| 220 |
+
|
| 221 |
+
unfold_func = conv_picker(
|
| 222 |
+
func,
|
| 223 |
+
lambda: F.unfold(
|
| 224 |
+
input.unsqueeze(-2),
|
| 225 |
+
kernel_size=(1, kernel_size[0]),
|
| 226 |
+
dilation=(1, dilation[0]),
|
| 227 |
+
padding=(0, padding[0]),
|
| 228 |
+
stride=(1, stride[0]),
|
| 229 |
+
),
|
| 230 |
+
lambda: F.unfold(
|
| 231 |
+
input, kernel_size, dilation=dilation, padding=padding, stride=stride
|
| 232 |
+
),
|
| 233 |
+
lambda: unfold3d(input, kernel_size, padding, stride, dilation),
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
input = unfold_func()
|
| 237 |
+
grad_output = grad_output.reshape(n, -1, input.shape[-1])
|
| 238 |
+
|
| 239 |
+
# n=batch_sz; o=num_out_channels; p=(num_in_channels/groups)*kernel_sz
|
| 240 |
+
weight_grad_sample = torch.einsum("noq,npq->nop", grad_output, input)
|
| 241 |
+
# rearrange the above tensor and extract diagonals.
|
| 242 |
+
|
| 243 |
+
weight_grad_sample = weight_grad_sample.view(
|
| 244 |
+
n,
|
| 245 |
+
groups,
|
| 246 |
+
-1,
|
| 247 |
+
groups,
|
| 248 |
+
int(in_channels / groups),
|
| 249 |
+
math.prod(kernel_size),
|
| 250 |
+
)
|
| 251 |
+
weight_grad_sample = torch.einsum(
|
| 252 |
+
"ngrg...->ngr...", weight_grad_sample
|
| 253 |
+
).contiguous()
|
| 254 |
+
shape = [n] + list(weight_shape)
|
| 255 |
+
weight_grad_sample = weight_grad_sample.view(shape)
|
| 256 |
+
return weight_grad_sample
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def conv_group_weight_grad_sample(
|
| 260 |
+
input,
|
| 261 |
+
grad_output,
|
| 262 |
+
weight_shape,
|
| 263 |
+
stride,
|
| 264 |
+
padding,
|
| 265 |
+
dilation,
|
| 266 |
+
batch_size,
|
| 267 |
+
func,
|
| 268 |
+
):
|
| 269 |
+
I = input.shape[1]
|
| 270 |
+
O = grad_output.shape[1]
|
| 271 |
+
|
| 272 |
+
input_ = input.transpose(0, 1)
|
| 273 |
+
grad_output_ = grad_output.view(
|
| 274 |
+
grad_output.shape[0] * grad_output.shape[1], 1, *grad_output.shape[2:]
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
weight_grad_sample = func(
|
| 278 |
+
input_,
|
| 279 |
+
grad_output_,
|
| 280 |
+
None,
|
| 281 |
+
stride=dilation,
|
| 282 |
+
padding=padding,
|
| 283 |
+
dilation=stride,
|
| 284 |
+
groups=batch_size,
|
| 285 |
+
)
|
| 286 |
+
input_dims = conv_picker(func, 3, 4, 5)
|
| 287 |
+
for i in range(2, input_dims):
|
| 288 |
+
weight_grad_sample = weight_grad_sample.narrow(i, 0, weight_shape[i])
|
| 289 |
+
weight_grad_sample = weight_grad_sample.view(
|
| 290 |
+
I, batch_size, O, *weight_grad_sample.shape[2:]
|
| 291 |
+
)
|
| 292 |
+
weight_grad_sample = weight_grad_sample.movedim(0, 2)
|
| 293 |
+
return weight_grad_sample
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def unfold3d(
|
| 297 |
+
tensor,
|
| 298 |
+
kernel_size,
|
| 299 |
+
padding,
|
| 300 |
+
stride,
|
| 301 |
+
dilation,
|
| 302 |
+
):
|
| 303 |
+
r"""
|
| 304 |
+
Extract sliding local blocks from an batched input tensor.
|
| 305 |
+
|
| 306 |
+
:class:`torch.nn.Unfold` only supports 4D inputs (batched image-like tensors).
|
| 307 |
+
This method implements the same action for 5D inputs
|
| 308 |
+
Args:
|
| 309 |
+
tensor: An input tensor of shape ``(B, C, D, H, W)``.
|
| 310 |
+
kernel_size: the size of the sliding blocks
|
| 311 |
+
padding: implicit zero padding to be added on both sides of input
|
| 312 |
+
stride: the stride of the sliding blocks in the input spatial dimensions
|
| 313 |
+
dilation: the spacing between the kernel points.
|
| 314 |
+
Returns:
|
| 315 |
+
A tensor of shape ``(B, C * math.prod(kernel_size), L)``, where L - output spatial dimensions.
|
| 316 |
+
See :class:`torch.nn.Unfold` for more details
|
| 317 |
+
Example:
|
| 318 |
+
>>> # xdoctest: +SKIP
|
| 319 |
+
>>> B, C, D, H, W = 3, 4, 5, 6, 7
|
| 320 |
+
>>> tensor = torch.arange(1, B * C * D * H * W + 1.0).view(B, C, D, H, W)
|
| 321 |
+
>>> unfold3d(tensor, kernel_size=2, padding=0, stride=1).shape
|
| 322 |
+
torch.Size([3, 32, 120])
|
| 323 |
+
"""
|
| 324 |
+
|
| 325 |
+
if len(tensor.shape) != 5:
|
| 326 |
+
raise ValueError(
|
| 327 |
+
f"Input tensor must be of the shape [B, C, D, H, W]. Got{tensor.shape}"
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
if dilation != (1, 1, 1):
|
| 331 |
+
raise NotImplementedError(f"dilation={dilation} not supported.")
|
| 332 |
+
|
| 333 |
+
batch_size, channels, _, _, _ = tensor.shape
|
| 334 |
+
|
| 335 |
+
# Input shape: (B, C, D, H, W)
|
| 336 |
+
tensor = F.pad(
|
| 337 |
+
tensor, (padding[2], padding[2], padding[1], padding[1], padding[0], padding[0])
|
| 338 |
+
)
|
| 339 |
+
# Output shape: (B, C, D+2*padding[2], H+2*padding[1], W+2*padding[0])
|
| 340 |
+
|
| 341 |
+
tensor = tensor.unfold(dimension=2, size=kernel_size[0], step=stride[0])
|
| 342 |
+
tensor = tensor.unfold(dimension=3, size=kernel_size[1], step=stride[1])
|
| 343 |
+
tensor = tensor.unfold(dimension=4, size=kernel_size[2], step=stride[2])
|
| 344 |
+
# Output shape: (B, C, D_out, H_out, W_out, kernel_size[0], kernel_size[1], kernel_size[2])
|
| 345 |
+
# For D_out, H_out, W_out definitions see :class:`torch.nn.Unfold`
|
| 346 |
+
|
| 347 |
+
tensor = tensor.permute(0, 2, 3, 4, 1, 5, 6, 7)
|
| 348 |
+
# Output shape: (B, D_out, H_out, W_out, C, kernel_size[0], kernel_size[1], kernel_size[2])
|
| 349 |
+
|
| 350 |
+
tensor = tensor.reshape(
|
| 351 |
+
batch_size, -1, channels * math.prod(kernel_size)
|
| 352 |
+
).transpose(1, 2)
|
| 353 |
+
# Output shape: (B, D_out * H_out * W_out, C * kernel_size[0] * kernel_size[1] * kernel_size[2]
|
| 354 |
+
|
| 355 |
+
return tensor
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/embedding_expanded_weights.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
from .expanded_weights_impl import implements_per_sample_grads
|
| 7 |
+
from .expanded_weights_utils import (
|
| 8 |
+
forward_helper,
|
| 9 |
+
set_grad_sample_if_exists,
|
| 10 |
+
standard_kwargs,
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@implements_per_sample_grads(F.embedding)
|
| 15 |
+
class EmbeddingPerSampleGrad(torch.autograd.Function):
|
| 16 |
+
@staticmethod
|
| 17 |
+
# pyrefly: ignore [bad-override]
|
| 18 |
+
def forward(
|
| 19 |
+
ctx: Any, kwarg_names: list[str], _: Any, *expanded_args_and_kwargs: Any
|
| 20 |
+
) -> torch.Tensor:
|
| 21 |
+
expanded_args, expanded_kwargs = standard_kwargs(
|
| 22 |
+
kwarg_names, expanded_args_and_kwargs
|
| 23 |
+
)
|
| 24 |
+
if len(expanded_args[0].shape) == 1:
|
| 25 |
+
raise RuntimeError(
|
| 26 |
+
f"Expanded Weights needs an input with a batch size, got a 1D tensor, {expanded_args[0]}"
|
| 27 |
+
)
|
| 28 |
+
output = forward_helper(F.embedding, expanded_args, expanded_kwargs)
|
| 29 |
+
ctx.input, ctx.weight = expanded_args
|
| 30 |
+
ctx.padding_idx, ctx.scale_grad_by_freq = (
|
| 31 |
+
expanded_kwargs["padding_idx"],
|
| 32 |
+
expanded_kwargs["scale_grad_by_freq"],
|
| 33 |
+
)
|
| 34 |
+
ctx.sparse = expanded_kwargs["sparse"]
|
| 35 |
+
return output
|
| 36 |
+
|
| 37 |
+
@staticmethod
|
| 38 |
+
# pyrefly: ignore [bad-override]
|
| 39 |
+
def backward(
|
| 40 |
+
ctx: Any, grad_output: torch.Tensor
|
| 41 |
+
) -> tuple[torch.Tensor | None, ...]:
|
| 42 |
+
input, weight = ctx.input, ctx.weight
|
| 43 |
+
padding_idx, scale_grad_by_freq, sparse = (
|
| 44 |
+
ctx.padding_idx,
|
| 45 |
+
ctx.scale_grad_by_freq,
|
| 46 |
+
ctx.sparse,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
def weight_per_sample_grad(weight: torch.Tensor) -> torch.Tensor:
|
| 50 |
+
batch_size = input.shape[0]
|
| 51 |
+
embedding_dim = weight.shape[1]
|
| 52 |
+
index = (
|
| 53 |
+
input.unsqueeze(-1)
|
| 54 |
+
.expand(*input.shape, embedding_dim)
|
| 55 |
+
.reshape(batch_size, -1, embedding_dim)
|
| 56 |
+
)
|
| 57 |
+
grad_sample = torch.zeros( # type: ignore[attr-defined]
|
| 58 |
+
batch_size, *weight.shape, device=weight.device, dtype=grad_output.dtype
|
| 59 |
+
)
|
| 60 |
+
return grad_sample.scatter_add_(
|
| 61 |
+
1, index, grad_output.reshape(batch_size, -1, embedding_dim)
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
results: list[torch.Tensor | None] = []
|
| 65 |
+
results.append(None) # for kwarg names
|
| 66 |
+
results.append(None) # for op reference
|
| 67 |
+
|
| 68 |
+
if input.requires_grad:
|
| 69 |
+
bw_fn = torch.ops.aten.embedding_backward
|
| 70 |
+
results.append(
|
| 71 |
+
bw_fn(
|
| 72 |
+
grad_output,
|
| 73 |
+
input,
|
| 74 |
+
weight.shape[0],
|
| 75 |
+
padding_idx,
|
| 76 |
+
scale_grad_by_freq,
|
| 77 |
+
sparse,
|
| 78 |
+
)
|
| 79 |
+
)
|
| 80 |
+
else:
|
| 81 |
+
results.append(None)
|
| 82 |
+
|
| 83 |
+
# weight doesn't compute batched gradients; no other arguments are differentiable (2 not saved from forward)
|
| 84 |
+
results = results + [None] * 6
|
| 85 |
+
|
| 86 |
+
# set grad_sample field for weight with per sample gradients
|
| 87 |
+
set_grad_sample_if_exists(weight, weight_per_sample_grad)
|
| 88 |
+
return tuple(results)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/expanded_weights_impl.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import functools
|
| 3 |
+
from collections.abc import Callable
|
| 4 |
+
from contextlib import contextmanager
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch._decomp import decomposition_table
|
| 8 |
+
from torch.utils._pytree import tree_map_only
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
HANDLED_FUNCTIONS: dict[Callable, torch.autograd.Function] = {}
|
| 12 |
+
|
| 13 |
+
aten = torch._ops.ops.aten
|
| 14 |
+
# __torch_function__ runs before the pydispatcher so we need to manually use the same
|
| 15 |
+
# decompositions indexed by their torch equivalent
|
| 16 |
+
expanded_weights_rnn_decomps = {
|
| 17 |
+
# func: (input_decomp, data_decomp)
|
| 18 |
+
torch.rnn_relu: (
|
| 19 |
+
decomposition_table[aten.rnn_relu.input],
|
| 20 |
+
decomposition_table[aten.rnn_relu.data],
|
| 21 |
+
),
|
| 22 |
+
torch.rnn_tanh: (
|
| 23 |
+
decomposition_table[aten.rnn_tanh.input],
|
| 24 |
+
decomposition_table[aten.rnn_tanh.data],
|
| 25 |
+
),
|
| 26 |
+
torch.lstm: (
|
| 27 |
+
decomposition_table[aten.lstm.input],
|
| 28 |
+
decomposition_table[aten.lstm.data],
|
| 29 |
+
),
|
| 30 |
+
torch.gru: (
|
| 31 |
+
decomposition_table[aten.gru.input],
|
| 32 |
+
decomposition_table[aten.gru.data],
|
| 33 |
+
),
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# all of the RNN decomps run linear with the batch dimension second, even if batch_first was set
|
| 38 |
+
@contextmanager
|
| 39 |
+
def batch_second(args, kwargs):
|
| 40 |
+
def set_batch_second(ew) -> None:
|
| 41 |
+
ew.set_batch_first(False)
|
| 42 |
+
|
| 43 |
+
def reset_batch_first(ew) -> None:
|
| 44 |
+
ew.set_batch_first(True)
|
| 45 |
+
|
| 46 |
+
tree_map_only(ExpandedWeight, set_batch_second, args)
|
| 47 |
+
tree_map_only(ExpandedWeight, set_batch_second, kwargs)
|
| 48 |
+
try:
|
| 49 |
+
yield
|
| 50 |
+
finally:
|
| 51 |
+
tree_map_only(ExpandedWeight, reset_batch_first, args)
|
| 52 |
+
tree_map_only(ExpandedWeight, reset_batch_first, kwargs)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
# to support packed sequences, we need to allow for smaller batches. Expanded weights represents the largest batch
|
| 56 |
+
@contextmanager
|
| 57 |
+
def allow_smaller_batches(args, kwargs):
|
| 58 |
+
def allow(ew) -> None:
|
| 59 |
+
ew.set_allow_smaller_batches(True)
|
| 60 |
+
|
| 61 |
+
def reset(ew) -> None:
|
| 62 |
+
ew.set_allow_smaller_batches(False)
|
| 63 |
+
|
| 64 |
+
tree_map_only(ExpandedWeight, allow, args)
|
| 65 |
+
tree_map_only(ExpandedWeight, allow, kwargs)
|
| 66 |
+
try:
|
| 67 |
+
yield
|
| 68 |
+
finally:
|
| 69 |
+
tree_map_only(ExpandedWeight, reset, args)
|
| 70 |
+
tree_map_only(ExpandedWeight, reset, kwargs)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
@contextmanager
|
| 74 |
+
def setup_rnn(use_input_variant, args, kwargs):
|
| 75 |
+
with (
|
| 76 |
+
batch_second(args, kwargs)
|
| 77 |
+
if use_input_variant
|
| 78 |
+
else allow_smaller_batches(args, kwargs)
|
| 79 |
+
):
|
| 80 |
+
yield
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def implements_per_sample_grads(torch_function):
|
| 84 |
+
@functools.wraps(torch_function)
|
| 85 |
+
def decorator(autograd_func):
|
| 86 |
+
HANDLED_FUNCTIONS[torch_function] = autograd_func
|
| 87 |
+
return autograd_func
|
| 88 |
+
|
| 89 |
+
return decorator
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# ExpandedWeight represents a weight (parameter) Tensor that has an expanded
|
| 93 |
+
# batch dimension. Operations on the ExpandedWeight Tensor act exactly like
|
| 94 |
+
# those without an expanded batch dimension but a call to .backward() populates
|
| 95 |
+
# the original (unexpanded) tensor with per-sample-gradients for in the grad_sample field
|
| 96 |
+
#
|
| 97 |
+
# ExpandedWeight has a fallback that always fails since we cannot know what the batch
|
| 98 |
+
# dimension of the input tensor is and therefore cannot know if this is a valid call
|
| 99 |
+
#
|
| 100 |
+
# This is a __torch_function__ object but it could have also been a Tensor Extension
|
| 101 |
+
# with a dispatch key.
|
| 102 |
+
#
|
| 103 |
+
# Needs to be a tensor subclass to allow reparameterization
|
| 104 |
+
class ExpandedWeight(torch.Tensor):
|
| 105 |
+
def __init__(self, orig_weight, batch_size, loss_reduction) -> None:
|
| 106 |
+
self.batch_size = batch_size
|
| 107 |
+
self.batch_first = True
|
| 108 |
+
self.allow_smaller_batches = False
|
| 109 |
+
self.orig_weight = orig_weight
|
| 110 |
+
self.loss_reduction = loss_reduction
|
| 111 |
+
|
| 112 |
+
handled_functions = HANDLED_FUNCTIONS
|
| 113 |
+
|
| 114 |
+
def __new__(cls, orig_weight, batch_size, loss_reduction):
|
| 115 |
+
if not isinstance(orig_weight, torch.Tensor):
|
| 116 |
+
raise RuntimeError(
|
| 117 |
+
f"Can only make Expanded Weights of Tensors, got {type(orig_weight).__name__}"
|
| 118 |
+
)
|
| 119 |
+
if not orig_weight.requires_grad:
|
| 120 |
+
raise RuntimeError(
|
| 121 |
+
"Can only build ExpandedWeights objects of tensors that require_grad"
|
| 122 |
+
)
|
| 123 |
+
ret = torch.Tensor._make_subclass(cls, orig_weight, True)
|
| 124 |
+
return ret
|
| 125 |
+
|
| 126 |
+
@classmethod
|
| 127 |
+
def __torch_function__(cls, func, _, args=(), kwargs=None):
|
| 128 |
+
if kwargs is None:
|
| 129 |
+
kwargs = {}
|
| 130 |
+
if func in expanded_weights_rnn_decomps:
|
| 131 |
+
# in aten, choosing the input or data variants is done by parsing logic. This mimics some of that
|
| 132 |
+
decomp_opts = expanded_weights_rnn_decomps[func]
|
| 133 |
+
use_input_variant = isinstance(
|
| 134 |
+
# pyrefly: ignore [bad-index]
|
| 135 |
+
args[2],
|
| 136 |
+
list,
|
| 137 |
+
) # data variant uses a list here
|
| 138 |
+
decomp = decomp_opts[0] if use_input_variant else decomp_opts[1]
|
| 139 |
+
|
| 140 |
+
if decomp is not None:
|
| 141 |
+
with setup_rnn(use_input_variant, args, kwargs):
|
| 142 |
+
return decomp(*args, **kwargs)
|
| 143 |
+
if func is torch._cudnn_rnn_flatten_weight:
|
| 144 |
+
# since we aren't using the fused cuda kernels for RNNs, don't do this
|
| 145 |
+
return
|
| 146 |
+
if func in cls.handled_functions:
|
| 147 |
+
return cls.handled_functions[func].apply(
|
| 148 |
+
tuple(kwargs.keys()), func, *(args + tuple(kwargs.values()))
|
| 149 |
+
)
|
| 150 |
+
# We cannot use a fallback here because we do not know the batch dimension for any regular tensor inputs,
|
| 151 |
+
# i.e. torch.add(torch.Tensor, ExpandedWeight)
|
| 152 |
+
raise RuntimeError(
|
| 153 |
+
f"Expanded Weights encountered but cannot handle function {func.__name__}"
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
@property
|
| 157 |
+
def dtype(self): # type: ignore[override]
|
| 158 |
+
return self.orig_weight.dtype
|
| 159 |
+
|
| 160 |
+
@property
|
| 161 |
+
def data(self): # type: ignore[override]
|
| 162 |
+
return self.orig_weight.data
|
| 163 |
+
|
| 164 |
+
@property
|
| 165 |
+
def shape(self): # type: ignore[override]
|
| 166 |
+
return self.orig_weight.shape
|
| 167 |
+
|
| 168 |
+
@property
|
| 169 |
+
def device(self): # type: ignore[override]
|
| 170 |
+
return self.orig_weight.device
|
| 171 |
+
|
| 172 |
+
@property
|
| 173 |
+
def is_cuda(self): # type: ignore[override]
|
| 174 |
+
return self.orig_weight.is_cuda
|
| 175 |
+
|
| 176 |
+
def data_ptr(self):
|
| 177 |
+
return self.orig_weight.data_ptr()
|
| 178 |
+
|
| 179 |
+
def get_device(self):
|
| 180 |
+
return self.orig_weight.get_device()
|
| 181 |
+
|
| 182 |
+
def set_allow_smaller_batches(self, is_allow_smaller_batches) -> None:
|
| 183 |
+
self.allow_smaller_batches = is_allow_smaller_batches
|
| 184 |
+
|
| 185 |
+
def set_batch_first(self, is_batch_first=True) -> None:
|
| 186 |
+
self.batch_first = is_batch_first
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/expanded_weights_utils.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from .expanded_weights_impl import ExpandedWeight
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def is_batch_first(expanded_args_and_kwargs):
|
| 9 |
+
batch_first = None
|
| 10 |
+
# pyrefly: ignore [bad-assignment]
|
| 11 |
+
for arg in expanded_args_and_kwargs:
|
| 12 |
+
if not isinstance(arg, ExpandedWeight):
|
| 13 |
+
continue
|
| 14 |
+
|
| 15 |
+
if not batch_first:
|
| 16 |
+
batch_first = arg.batch_first
|
| 17 |
+
elif arg.batch_first != batch_first:
|
| 18 |
+
raise RuntimeError(
|
| 19 |
+
"Got conflicting batch_first arguments in the same layer"
|
| 20 |
+
)
|
| 21 |
+
return batch_first
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def standard_kwargs(kwarg_names, expanded_args):
|
| 25 |
+
r"""Separate args and kwargs from `__torch_function__`s that standardize kwargs.
|
| 26 |
+
|
| 27 |
+
Most `__torch_function__`s standardize the kwargs that they give, so this will separate
|
| 28 |
+
the args and kwargs they pass. Functions that don't are linear and convAND.
|
| 29 |
+
"""
|
| 30 |
+
kwarg_values = expanded_args[len(expanded_args) - len(kwarg_names) :]
|
| 31 |
+
expanded_args_without_kwargs = expanded_args[
|
| 32 |
+
: len(expanded_args) - len(kwarg_names)
|
| 33 |
+
]
|
| 34 |
+
expanded_kwargs = dict(zip(kwarg_names, kwarg_values, strict=True))
|
| 35 |
+
return expanded_args_without_kwargs, expanded_kwargs
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def forward_helper(func, expanded_args, expanded_kwargs):
|
| 39 |
+
r"""Compute the forward pass for a function that has expanded weight(s) passed to it.
|
| 40 |
+
|
| 41 |
+
It will run the forward pass where all ExpandedWeights are their original
|
| 42 |
+
weight. It runs checks on the given arguments and detaches the outputs.
|
| 43 |
+
|
| 44 |
+
.. note:: First argument in :attr:`expanded_args` must be the input with the batch
|
| 45 |
+
dimension as the first element of the shape
|
| 46 |
+
|
| 47 |
+
.. note:: :attr:`func` must return a Tensor or tuple of Tensors
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
func: The function to be called
|
| 51 |
+
expanded_args: Arguments to be passed to :attr:`func`. Will include arguments
|
| 52 |
+
that need to be unpacked because they are ExpandedWeights
|
| 53 |
+
expanded_kwargs: Keyword arguments to be passed to :attr:`func`.
|
| 54 |
+
Similar to :attr:`expanded_args`.
|
| 55 |
+
"""
|
| 56 |
+
unexpanded_args, unexpanded_kwargs = _check_and_unexpand_args(
|
| 57 |
+
func, expanded_args, expanded_kwargs
|
| 58 |
+
)
|
| 59 |
+
return func(*unexpanded_args, **unexpanded_kwargs)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def _check_and_unexpand_args(func, expanded_args, expanded_kwargs):
|
| 63 |
+
# input must be the first argument passed
|
| 64 |
+
input = expanded_args[0]
|
| 65 |
+
if isinstance(input, ExpandedWeight):
|
| 66 |
+
raise RuntimeError(
|
| 67 |
+
"Expanded Weights do not support inputs that are also ExpandedWeights. "
|
| 68 |
+
f"Input must be a Tensor, got {type(input).__name__} in function {func.__name__}"
|
| 69 |
+
)
|
| 70 |
+
if not isinstance(input, torch.Tensor):
|
| 71 |
+
raise RuntimeError(
|
| 72 |
+
"Expanded Weights requires a Tensor as the first input to get the batch dimension, "
|
| 73 |
+
f"got {type(input).__name__} in function {func.__name__}"
|
| 74 |
+
)
|
| 75 |
+
if len(input.shape) == 0:
|
| 76 |
+
raise RuntimeError(
|
| 77 |
+
f"Expanded Weights requires a batch dimension but got an input of size 0 in function {func.__name__}"
|
| 78 |
+
)
|
| 79 |
+
if input.shape[0] == 0:
|
| 80 |
+
raise RuntimeError(
|
| 81 |
+
"0 is not a valid batch size for Expanded Weights but got input tensor of "
|
| 82 |
+
f"{input} in function {func.__name__}"
|
| 83 |
+
)
|
| 84 |
+
for arg in expanded_args + tuple(expanded_kwargs.values()):
|
| 85 |
+
if not isinstance(arg, ExpandedWeight):
|
| 86 |
+
continue
|
| 87 |
+
batch_size = input.shape[0] if arg.batch_first else input.shape[1]
|
| 88 |
+
if (arg.allow_smaller_batches and batch_size > arg.batch_size) or (
|
| 89 |
+
not arg.allow_smaller_batches and arg.batch_size != batch_size
|
| 90 |
+
):
|
| 91 |
+
raise RuntimeError(
|
| 92 |
+
"Expected ExpandedWeights to have batch size matching input but got "
|
| 93 |
+
f"input batch size of {batch_size} with ExpandedWeight of batch size {arg.batch_size}"
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
loss_reduction: str | None = None
|
| 97 |
+
for arg in expanded_args + tuple(expanded_kwargs.values()):
|
| 98 |
+
if isinstance(arg, ExpandedWeight):
|
| 99 |
+
if loss_reduction is None:
|
| 100 |
+
loss_reduction = arg.loss_reduction
|
| 101 |
+
elif loss_reduction != arg.loss_reduction:
|
| 102 |
+
raise RuntimeError(
|
| 103 |
+
"Expected ExpandedWeights to all have the same loss_reduction argument but got one"
|
| 104 |
+
f"with {loss_reduction} and one with {arg.loss_reduction}"
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
unexpanded_args = tuple(
|
| 108 |
+
arg.orig_weight if isinstance(arg, ExpandedWeight) else arg
|
| 109 |
+
for arg in expanded_args
|
| 110 |
+
)
|
| 111 |
+
unexpanded_kwargs = {
|
| 112 |
+
name: arg.orig_weight if isinstance(arg, ExpandedWeight) else arg
|
| 113 |
+
for (name, arg) in expanded_kwargs.items()
|
| 114 |
+
}
|
| 115 |
+
return unexpanded_args, unexpanded_kwargs
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def maybe_scale_by_batch_size(grad_sample, expanded_weight):
|
| 119 |
+
if expanded_weight.loss_reduction == "mean":
|
| 120 |
+
return grad_sample * expanded_weight.batch_size
|
| 121 |
+
else:
|
| 122 |
+
return grad_sample
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def set_grad_sample_if_exists(maybe_expanded_weight, per_sample_grad_fn) -> None:
|
| 126 |
+
unpacked = unpack_expanded_weight_or_tensor(maybe_expanded_weight)
|
| 127 |
+
if isinstance(maybe_expanded_weight, ExpandedWeight):
|
| 128 |
+
grad_sample_contribution = maybe_scale_by_batch_size(
|
| 129 |
+
per_sample_grad_fn(unpacked), maybe_expanded_weight
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
if maybe_expanded_weight.batch_size > grad_sample_contribution.shape[0]:
|
| 133 |
+
# this only passes the other checks if the arg allows smaller batch sizes
|
| 134 |
+
intermediate = torch.zeros(
|
| 135 |
+
maybe_expanded_weight.batch_size,
|
| 136 |
+
*grad_sample_contribution.shape[1:],
|
| 137 |
+
dtype=grad_sample_contribution.dtype,
|
| 138 |
+
device=grad_sample_contribution.device,
|
| 139 |
+
)
|
| 140 |
+
intermediate[: grad_sample_contribution.shape[0]] = grad_sample_contribution
|
| 141 |
+
grad_sample_contribution = intermediate
|
| 142 |
+
|
| 143 |
+
if hasattr(unpacked, "grad_sample") and unpacked.grad_sample is not None:
|
| 144 |
+
unpacked.grad_sample = unpacked.grad_sample + grad_sample_contribution
|
| 145 |
+
else:
|
| 146 |
+
unpacked.grad_sample = grad_sample_contribution
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def unpack_expanded_weight_or_tensor(maybe_expanded_weight, func=lambda x: x):
|
| 150 |
+
if isinstance(maybe_expanded_weight, ExpandedWeight):
|
| 151 |
+
orig_weight = maybe_expanded_weight.orig_weight
|
| 152 |
+
return func(orig_weight)
|
| 153 |
+
elif (
|
| 154 |
+
isinstance(maybe_expanded_weight, torch.Tensor)
|
| 155 |
+
and not maybe_expanded_weight.requires_grad
|
| 156 |
+
):
|
| 157 |
+
return func(maybe_expanded_weight)
|
| 158 |
+
elif isinstance(maybe_expanded_weight, torch.Tensor):
|
| 159 |
+
raise RuntimeError(
|
| 160 |
+
"ExpandedWeights currently does not support a mixture of ExpandedWeight parameters "
|
| 161 |
+
"and normal Parameters. Please file and issue with pytorch/pytorch"
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def sum_over_all_but_batch_and_last_n(
|
| 166 |
+
tensor: torch.Tensor,
|
| 167 |
+
n_dims: int,
|
| 168 |
+
) -> torch.Tensor:
|
| 169 |
+
r"""
|
| 170 |
+
Calculate the sum over all dimensions, except the first (batch dimension), and excluding the last n_dims.
|
| 171 |
+
|
| 172 |
+
This function will ignore the first dimension and it will
|
| 173 |
+
not aggregate over the last n_dims dimensions.
|
| 174 |
+
Args:
|
| 175 |
+
tensor: An input tensor of shape ``(B, ..., X[n_dims-1])``.
|
| 176 |
+
n_dims: Number of dimensions to keep.
|
| 177 |
+
Example:
|
| 178 |
+
>>> tensor = torch.ones(1, 2, 3, 4, 5)
|
| 179 |
+
>>> sum_over_all_but_batch_and_last_n(tensor, n_dims=2).shape
|
| 180 |
+
torch.Size([1, 4, 5])
|
| 181 |
+
Returns:
|
| 182 |
+
A tensor of shape ``(B, ..., X[n_dims-1])``
|
| 183 |
+
"""
|
| 184 |
+
if tensor.dim() == n_dims + 1:
|
| 185 |
+
return tensor
|
| 186 |
+
else:
|
| 187 |
+
dims = list(range(1, tensor.dim() - n_dims))
|
| 188 |
+
return tensor.sum(dim=dims)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/group_norm_expanded_weights.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import operator
|
| 3 |
+
from functools import reduce
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from .expanded_weights_impl import ExpandedWeight, implements_per_sample_grads
|
| 9 |
+
from .expanded_weights_utils import (
|
| 10 |
+
forward_helper,
|
| 11 |
+
set_grad_sample_if_exists,
|
| 12 |
+
standard_kwargs,
|
| 13 |
+
unpack_expanded_weight_or_tensor,
|
| 14 |
+
)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@implements_per_sample_grads(F.group_norm)
|
| 18 |
+
class GroupNormPerSampleGrad(torch.autograd.Function):
|
| 19 |
+
@staticmethod
|
| 20 |
+
# pyrefly: ignore [bad-override]
|
| 21 |
+
def forward(ctx, kwarg_names, _, *expanded_args_and_kwargs):
|
| 22 |
+
expanded_args, expanded_kwargs = standard_kwargs(
|
| 23 |
+
kwarg_names, expanded_args_and_kwargs
|
| 24 |
+
)
|
| 25 |
+
input, num_groups = expanded_args
|
| 26 |
+
N = input.shape[0]
|
| 27 |
+
C = input.shape[1]
|
| 28 |
+
HxW = reduce(operator.mul, input.shape[2:], 1)
|
| 29 |
+
weight, bias, eps = (
|
| 30 |
+
expanded_kwargs["weight"],
|
| 31 |
+
expanded_kwargs["bias"],
|
| 32 |
+
expanded_kwargs["eps"],
|
| 33 |
+
)
|
| 34 |
+
output, mean, rstd = forward_helper(
|
| 35 |
+
torch.native_group_norm,
|
| 36 |
+
(input, weight, bias, N, C, HxW, num_groups, eps),
|
| 37 |
+
{},
|
| 38 |
+
)
|
| 39 |
+
ctx.input, ctx.num_groups = input, num_groups
|
| 40 |
+
ctx.weight, ctx.eps = weight, eps
|
| 41 |
+
ctx.mean, ctx.rstd = mean, rstd
|
| 42 |
+
if isinstance(bias, ExpandedWeight):
|
| 43 |
+
ctx.bias = bias
|
| 44 |
+
if input.requires_grad and isinstance(weight, ExpandedWeight):
|
| 45 |
+
ctx.weight = weight
|
| 46 |
+
return output
|
| 47 |
+
|
| 48 |
+
@staticmethod
|
| 49 |
+
# pyrefly: ignore [bad-override]
|
| 50 |
+
def backward(ctx, grad_output):
|
| 51 |
+
input, num_groups = ctx.input, ctx.num_groups
|
| 52 |
+
weight, bias, eps = ctx.weight, ctx.bias, ctx.eps
|
| 53 |
+
mean, rstd = ctx.mean, ctx.rstd
|
| 54 |
+
|
| 55 |
+
results: list[torch.Tensor | None] = []
|
| 56 |
+
results.append(None) # for kwarg names
|
| 57 |
+
results.append(None) # for op reference
|
| 58 |
+
|
| 59 |
+
if input.requires_grad:
|
| 60 |
+
weight_c = unpack_expanded_weight_or_tensor(
|
| 61 |
+
weight, lambda t: t.contiguous()
|
| 62 |
+
)
|
| 63 |
+
input_c = input.contiguous()
|
| 64 |
+
grad_output_c = (
|
| 65 |
+
grad_output.contiguous() if grad_output is not None else None
|
| 66 |
+
)
|
| 67 |
+
N = input.shape[0]
|
| 68 |
+
C = input.shape[1]
|
| 69 |
+
HxW = 1
|
| 70 |
+
for s in input.shape[2:]:
|
| 71 |
+
HxW *= s
|
| 72 |
+
bw_fn = torch.ops.aten.native_group_norm_backward
|
| 73 |
+
results.append(
|
| 74 |
+
bw_fn(
|
| 75 |
+
grad_output_c,
|
| 76 |
+
input_c,
|
| 77 |
+
mean,
|
| 78 |
+
rstd,
|
| 79 |
+
weight_c,
|
| 80 |
+
N,
|
| 81 |
+
C,
|
| 82 |
+
HxW,
|
| 83 |
+
num_groups,
|
| 84 |
+
(True, False, False),
|
| 85 |
+
)[0]
|
| 86 |
+
)
|
| 87 |
+
else:
|
| 88 |
+
results.append(None)
|
| 89 |
+
|
| 90 |
+
# weight and bias don't compute batched gradients; no other arguments are differentiable
|
| 91 |
+
results = results + [None] * 4
|
| 92 |
+
|
| 93 |
+
# set grad_sample field for weight and bias with per sample gradients
|
| 94 |
+
if hasattr(ctx, "weight"):
|
| 95 |
+
set_grad_sample_if_exists(
|
| 96 |
+
weight,
|
| 97 |
+
lambda _: torch.einsum(
|
| 98 |
+
"ni...->ni",
|
| 99 |
+
F.group_norm(input, num_groups, eps=eps) * grad_output,
|
| 100 |
+
),
|
| 101 |
+
)
|
| 102 |
+
if hasattr(ctx, "bias"):
|
| 103 |
+
set_grad_sample_if_exists(
|
| 104 |
+
bias, lambda _: torch.einsum("ni...->ni", grad_output)
|
| 105 |
+
)
|
| 106 |
+
return tuple(results)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/instance_norm_expanded_weights.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from functools import partial
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
|
| 7 |
+
from .expanded_weights_impl import implements_per_sample_grads
|
| 8 |
+
from .expanded_weights_utils import (
|
| 9 |
+
forward_helper,
|
| 10 |
+
set_grad_sample_if_exists,
|
| 11 |
+
standard_kwargs,
|
| 12 |
+
unpack_expanded_weight_or_tensor,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@implements_per_sample_grads(F.instance_norm)
|
| 17 |
+
class InstanceNormPerSampleGrad(torch.autograd.Function):
|
| 18 |
+
@staticmethod
|
| 19 |
+
# pyrefly: ignore [bad-override]
|
| 20 |
+
def forward(ctx, kwarg_names, _, *expanded_args_and_kwargs):
|
| 21 |
+
instance_norm = partial(torch.instance_norm, cudnn_enabled=True)
|
| 22 |
+
expanded_args, expanded_kwargs = standard_kwargs(
|
| 23 |
+
kwarg_names, expanded_args_and_kwargs
|
| 24 |
+
)
|
| 25 |
+
output = forward_helper(instance_norm, expanded_args, expanded_kwargs)
|
| 26 |
+
ctx.input = expanded_args[0]
|
| 27 |
+
ctx.running_mean, ctx.running_var = (
|
| 28 |
+
expanded_kwargs["running_mean"],
|
| 29 |
+
expanded_kwargs["running_var"],
|
| 30 |
+
)
|
| 31 |
+
ctx.weight, ctx.bias, ctx.eps = (
|
| 32 |
+
expanded_kwargs["weight"],
|
| 33 |
+
expanded_kwargs["bias"],
|
| 34 |
+
expanded_kwargs["eps"],
|
| 35 |
+
)
|
| 36 |
+
return output
|
| 37 |
+
|
| 38 |
+
@staticmethod
|
| 39 |
+
# pyrefly: ignore [bad-override]
|
| 40 |
+
def backward(ctx, grad_output):
|
| 41 |
+
input, running_mean, running_var = ctx.input, ctx.running_mean, ctx.running_var
|
| 42 |
+
weight, bias, eps = ctx.weight, ctx.bias, ctx.eps
|
| 43 |
+
|
| 44 |
+
results: list[torch.Tensor | None] = []
|
| 45 |
+
results.append(None) # for kwarg names
|
| 46 |
+
results.append(None) # for op reference
|
| 47 |
+
if input.requires_grad:
|
| 48 |
+
b = input.shape[0]
|
| 49 |
+
c = input.shape[1]
|
| 50 |
+
new_shape = (1, b * c, *input.shape[2:])
|
| 51 |
+
|
| 52 |
+
weight_ = unpack_expanded_weight_or_tensor(
|
| 53 |
+
weight, lambda orig_weight: orig_weight.repeat(b)
|
| 54 |
+
)
|
| 55 |
+
running_mean_ = running_mean.repeat(b) if running_mean is not None else None
|
| 56 |
+
running_var_ = running_var.repeat(b) if running_var is not None else None
|
| 57 |
+
input_reshaped = input.contiguous().view(new_shape)
|
| 58 |
+
grad_output_reshaped = grad_output.contiguous().view(new_shape)
|
| 59 |
+
mean = torch.mean(
|
| 60 |
+
input_reshaped, (0,) + tuple(range(2, input.dim())), False
|
| 61 |
+
)
|
| 62 |
+
var = torch.var(
|
| 63 |
+
input_reshaped,
|
| 64 |
+
(0,) + tuple(range(2, input.dim())),
|
| 65 |
+
keepdim=False,
|
| 66 |
+
unbiased=False,
|
| 67 |
+
)
|
| 68 |
+
rstd = 1 / torch.sqrt(var + eps)
|
| 69 |
+
|
| 70 |
+
# must use native batch norm since it supports all inputs. This may have used cuda or openmi during the forward but
|
| 71 |
+
# it didn't save the metadata, so we don't know during the backward
|
| 72 |
+
res = torch.ops.aten.native_batch_norm_backward(
|
| 73 |
+
grad_output_reshaped,
|
| 74 |
+
input_reshaped,
|
| 75 |
+
weight_,
|
| 76 |
+
running_mean_,
|
| 77 |
+
running_var_,
|
| 78 |
+
mean,
|
| 79 |
+
rstd,
|
| 80 |
+
True,
|
| 81 |
+
eps,
|
| 82 |
+
(True, False, False),
|
| 83 |
+
)
|
| 84 |
+
results.append(res[0].reshape(input.shape))
|
| 85 |
+
else:
|
| 86 |
+
results.append(None)
|
| 87 |
+
|
| 88 |
+
# weight and bias don't compute batched gradients; no other arguments are differentiable (2 are not saved from the forward)
|
| 89 |
+
results = results + [None] * 7
|
| 90 |
+
|
| 91 |
+
# set grad_sample field for weight and bias with per sample gradients
|
| 92 |
+
set_grad_sample_if_exists(
|
| 93 |
+
weight,
|
| 94 |
+
lambda _: torch.einsum(
|
| 95 |
+
"ni...->ni", F.instance_norm(input, eps=eps) * grad_output
|
| 96 |
+
),
|
| 97 |
+
)
|
| 98 |
+
set_grad_sample_if_exists(
|
| 99 |
+
bias, lambda _: torch.einsum("ni...->ni", grad_output)
|
| 100 |
+
)
|
| 101 |
+
return tuple(results)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/layer_norm_expanded_weights.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
from .expanded_weights_impl import ExpandedWeight, implements_per_sample_grads
|
| 7 |
+
from .expanded_weights_utils import (
|
| 8 |
+
forward_helper,
|
| 9 |
+
set_grad_sample_if_exists,
|
| 10 |
+
standard_kwargs,
|
| 11 |
+
sum_over_all_but_batch_and_last_n,
|
| 12 |
+
unpack_expanded_weight_or_tensor,
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@implements_per_sample_grads(F.layer_norm)
|
| 17 |
+
class LayerNormPerSampleGrad(torch.autograd.Function):
|
| 18 |
+
@staticmethod
|
| 19 |
+
# pyrefly: ignore [bad-override]
|
| 20 |
+
def forward(ctx, kwarg_names, _, *expanded_args_and_kwargs):
|
| 21 |
+
expanded_args, expanded_kwargs = standard_kwargs(
|
| 22 |
+
kwarg_names, expanded_args_and_kwargs
|
| 23 |
+
)
|
| 24 |
+
input = expanded_args[0]
|
| 25 |
+
normalized_shape = expanded_args[1]
|
| 26 |
+
if len(input.shape) <= len(normalized_shape):
|
| 27 |
+
raise RuntimeError(
|
| 28 |
+
"Expanded Weights: Layer norm should not normalize over batch dimension for per sample gradient"
|
| 29 |
+
f"computations but got that normalized shape, {normalized_shape}, matched input shape."
|
| 30 |
+
)
|
| 31 |
+
output, mean, rstd = forward_helper(
|
| 32 |
+
torch.native_layer_norm, expanded_args, expanded_kwargs
|
| 33 |
+
)
|
| 34 |
+
ctx.args = expanded_args
|
| 35 |
+
|
| 36 |
+
if input.requires_grad or isinstance(expanded_kwargs["weight"], ExpandedWeight):
|
| 37 |
+
ctx.weight = expanded_kwargs["weight"]
|
| 38 |
+
if input.requires_grad or isinstance(expanded_kwargs["bias"], ExpandedWeight):
|
| 39 |
+
ctx.bias = expanded_kwargs["bias"]
|
| 40 |
+
ctx.eps = expanded_kwargs["eps"]
|
| 41 |
+
ctx.mean, ctx.rstd = mean, rstd
|
| 42 |
+
return output
|
| 43 |
+
|
| 44 |
+
@staticmethod
|
| 45 |
+
# pyrefly: ignore [bad-override]
|
| 46 |
+
def backward(ctx, grad_output):
|
| 47 |
+
def weight_per_sample_grad(weight):
|
| 48 |
+
return sum_over_all_but_batch_and_last_n(
|
| 49 |
+
F.layer_norm(input, normalized_shape, eps=ctx.eps) * grad_output,
|
| 50 |
+
weight.dim(),
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
input, normalized_shape = ctx.args
|
| 54 |
+
mean, rstd = ctx.mean, ctx.rstd
|
| 55 |
+
|
| 56 |
+
results: list[torch.Tensor | None] = []
|
| 57 |
+
results.append(None) # for kwarg names
|
| 58 |
+
results.append(None) # for op reference
|
| 59 |
+
if input.requires_grad:
|
| 60 |
+
weight_ = unpack_expanded_weight_or_tensor(ctx.weight)
|
| 61 |
+
bias_ = unpack_expanded_weight_or_tensor(ctx.bias)
|
| 62 |
+
results.append(
|
| 63 |
+
torch.ops.aten.native_layer_norm_backward(
|
| 64 |
+
grad_output,
|
| 65 |
+
input,
|
| 66 |
+
normalized_shape,
|
| 67 |
+
mean,
|
| 68 |
+
rstd,
|
| 69 |
+
weight_,
|
| 70 |
+
bias_,
|
| 71 |
+
(True, False, False),
|
| 72 |
+
)[0]
|
| 73 |
+
)
|
| 74 |
+
else:
|
| 75 |
+
results.append(None)
|
| 76 |
+
|
| 77 |
+
# weight and bias don't compute batched gradients; no other arguments are differentiable
|
| 78 |
+
results = results + [None] * 4
|
| 79 |
+
|
| 80 |
+
# set grad_sample field for weight and bias with per sample gradients
|
| 81 |
+
if hasattr(ctx, "weight"):
|
| 82 |
+
set_grad_sample_if_exists(ctx.weight, weight_per_sample_grad)
|
| 83 |
+
if hasattr(ctx, "bias"):
|
| 84 |
+
set_grad_sample_if_exists(
|
| 85 |
+
ctx.bias,
|
| 86 |
+
lambda bias: sum_over_all_but_batch_and_last_n(grad_output, bias.dim()),
|
| 87 |
+
)
|
| 88 |
+
return tuple(results)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/linear_expanded_weights.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
|
| 6 |
+
from .expanded_weights_impl import implements_per_sample_grads
|
| 7 |
+
from .expanded_weights_utils import (
|
| 8 |
+
forward_helper,
|
| 9 |
+
is_batch_first,
|
| 10 |
+
set_grad_sample_if_exists,
|
| 11 |
+
unpack_expanded_weight_or_tensor,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@implements_per_sample_grads(F.linear)
|
| 16 |
+
class LinearPerSampleGrad(torch.autograd.Function):
|
| 17 |
+
@staticmethod
|
| 18 |
+
# pyrefly: ignore [bad-override]
|
| 19 |
+
def forward(ctx, _, __, *expanded_args_and_kwargs):
|
| 20 |
+
if len(expanded_args_and_kwargs[0].shape) <= 1:
|
| 21 |
+
raise RuntimeError(
|
| 22 |
+
"Input does not have a batch dimension. Expanded Weights expected input "
|
| 23 |
+
f"of at least rank 2, got of rank {len(expanded_args_and_kwargs[0].shape)}"
|
| 24 |
+
)
|
| 25 |
+
expanded_kwargs = {
|
| 26 |
+
"bias": expanded_args_and_kwargs[2]
|
| 27 |
+
if len(expanded_args_and_kwargs) == 3
|
| 28 |
+
else None
|
| 29 |
+
}
|
| 30 |
+
expanded_args = expanded_args_and_kwargs[:2]
|
| 31 |
+
ctx.batch_first = is_batch_first(expanded_args_and_kwargs)
|
| 32 |
+
output = forward_helper(F.linear, expanded_args, expanded_kwargs)
|
| 33 |
+
ctx.args = expanded_args
|
| 34 |
+
ctx.kwargs = expanded_kwargs
|
| 35 |
+
return output
|
| 36 |
+
|
| 37 |
+
@staticmethod
|
| 38 |
+
# pyrefly: ignore [bad-override]
|
| 39 |
+
def backward(ctx, grad_output):
|
| 40 |
+
input, weight = ctx.args
|
| 41 |
+
bias = ctx.kwargs["bias"]
|
| 42 |
+
results: list[torch.Tensor | None] = []
|
| 43 |
+
results.append(None) # for kwarg_names
|
| 44 |
+
results.append(None) # for op reference
|
| 45 |
+
|
| 46 |
+
if input.requires_grad:
|
| 47 |
+
results.append(grad_output.matmul(unpack_expanded_weight_or_tensor(weight)))
|
| 48 |
+
else:
|
| 49 |
+
results.append(None)
|
| 50 |
+
results.extend([None] * 2) # weight and bias don't compute batched gradients
|
| 51 |
+
|
| 52 |
+
if not ctx.batch_first:
|
| 53 |
+
grad_output = grad_output.transpose(0, 1)
|
| 54 |
+
input = input.transpose(0, 1)
|
| 55 |
+
|
| 56 |
+
# weight and bias get their grad_sample fields set directly if they exist
|
| 57 |
+
set_grad_sample_if_exists(
|
| 58 |
+
weight, lambda _: torch.einsum("n...i,n...j->nij", grad_output, input)
|
| 59 |
+
)
|
| 60 |
+
set_grad_sample_if_exists(
|
| 61 |
+
bias, lambda _: torch.einsum("n...k->nk", grad_output)
|
| 62 |
+
)
|
| 63 |
+
return tuple(results)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_named_member_accessor.py
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This source code is licensed under the BSD-style license found in the
|
| 2 |
+
# LICENSE file in the root directory of this source tree.
|
| 3 |
+
|
| 4 |
+
from collections.abc import Iterable
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
_MISSING: torch.Tensor = object() # type: ignore[assignment]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def set_tensor(module: "torch.nn.Module", name: str, tensor: torch.Tensor) -> None:
|
| 13 |
+
if not isinstance(module, torch.nn.Module):
|
| 14 |
+
raise TypeError(f"{module} is not an instance of torch.nn.Module")
|
| 15 |
+
if not isinstance(tensor, torch.Tensor) and tensor is not None:
|
| 16 |
+
raise TypeError(f"{tensor} is not an instance of torch.Tensor")
|
| 17 |
+
if "." in name:
|
| 18 |
+
raise KeyError('tensor name can\'t contain "."')
|
| 19 |
+
if name == "":
|
| 20 |
+
raise KeyError('tensor name can\'t be empty string ""')
|
| 21 |
+
if name in module._parameters:
|
| 22 |
+
module._parameters[name] = tensor # type: ignore[assignment]
|
| 23 |
+
elif name in module._buffers:
|
| 24 |
+
module._buffers[name] = tensor
|
| 25 |
+
else:
|
| 26 |
+
setattr(module, name, tensor)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def swap_tensor(
|
| 30 |
+
module: "torch.nn.Module",
|
| 31 |
+
name: str,
|
| 32 |
+
tensor: torch.Tensor,
|
| 33 |
+
allow_missing: bool = False,
|
| 34 |
+
) -> torch.Tensor:
|
| 35 |
+
if not isinstance(module, torch.nn.Module):
|
| 36 |
+
raise TypeError(f"{module} is not an instance of torch.nn.Module")
|
| 37 |
+
if (
|
| 38 |
+
tensor is not _MISSING
|
| 39 |
+
and not isinstance(tensor, torch.Tensor)
|
| 40 |
+
and tensor is not None
|
| 41 |
+
):
|
| 42 |
+
raise TypeError(f"{tensor} is not an instance of torch.Tensor")
|
| 43 |
+
if "." in name:
|
| 44 |
+
raise KeyError('tensor name can\'t contain "."')
|
| 45 |
+
if name == "":
|
| 46 |
+
raise KeyError('tensor name can\'t be empty string ""')
|
| 47 |
+
|
| 48 |
+
orig_tensor: torch.Tensor
|
| 49 |
+
if name in module._parameters:
|
| 50 |
+
orig_tensor = module._parameters[name] # type: ignore[assignment]
|
| 51 |
+
if tensor is not _MISSING:
|
| 52 |
+
module._parameters[name] = tensor # type: ignore[assignment]
|
| 53 |
+
else:
|
| 54 |
+
del module._parameters[name]
|
| 55 |
+
elif name in module._buffers:
|
| 56 |
+
orig_tensor = module._buffers[name] # type: ignore[assignment]
|
| 57 |
+
if tensor is not _MISSING:
|
| 58 |
+
module._buffers[name] = tensor
|
| 59 |
+
else:
|
| 60 |
+
del module._buffers[name]
|
| 61 |
+
else:
|
| 62 |
+
if hasattr(module, name):
|
| 63 |
+
orig_tensor = getattr(module, name)
|
| 64 |
+
else:
|
| 65 |
+
if not allow_missing:
|
| 66 |
+
raise AttributeError(f"{module._get_name()} has no attribute `{name}`")
|
| 67 |
+
orig_tensor = _MISSING
|
| 68 |
+
if (
|
| 69 |
+
orig_tensor is not _MISSING
|
| 70 |
+
and not isinstance(orig_tensor, torch.Tensor)
|
| 71 |
+
and orig_tensor is not None
|
| 72 |
+
):
|
| 73 |
+
raise TypeError(
|
| 74 |
+
f"attribute `{name}`: {orig_tensor} is not an instance of torch.Tensor"
|
| 75 |
+
)
|
| 76 |
+
if tensor is not _MISSING:
|
| 77 |
+
setattr(module, name, tensor)
|
| 78 |
+
elif hasattr(module, name):
|
| 79 |
+
delattr(module, name)
|
| 80 |
+
# pyrefly: ignore [bad-return]
|
| 81 |
+
return orig_tensor
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def swap_submodule(
|
| 85 |
+
module: "torch.nn.Module",
|
| 86 |
+
name: str,
|
| 87 |
+
submodule: "torch.nn.Module",
|
| 88 |
+
) -> "torch.nn.Module":
|
| 89 |
+
if not isinstance(module, torch.nn.Module):
|
| 90 |
+
raise TypeError(f"{module} is not an instance of torch.nn.Module")
|
| 91 |
+
if not isinstance(submodule, torch.nn.Module):
|
| 92 |
+
raise TypeError(f"{submodule} is not an instance of torch.nn.Module")
|
| 93 |
+
if "." in name:
|
| 94 |
+
raise KeyError('submodule name can\'t contain "."')
|
| 95 |
+
if name == "":
|
| 96 |
+
raise KeyError('submodule name can\'t be empty string ""')
|
| 97 |
+
if name not in module._modules:
|
| 98 |
+
raise KeyError(f"submodule {name} does not exist")
|
| 99 |
+
|
| 100 |
+
orig_submodule = module._modules[name]
|
| 101 |
+
if not isinstance(orig_submodule, torch.nn.Module):
|
| 102 |
+
raise TypeError(f"{name} attribute is not an instance of torch.nn.Module")
|
| 103 |
+
module._modules[name] = submodule
|
| 104 |
+
return orig_submodule
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class NamedMemberAccessor:
|
| 108 |
+
"""
|
| 109 |
+
A class that provides a way to access the submodules and parameters/buffers of a module.
|
| 110 |
+
|
| 111 |
+
It provides caching mechanism to speed up submodule lookups.
|
| 112 |
+
This is useful for functional programming to manipulate the module state.
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
def __init__(self, module: "torch.nn.Module") -> None:
|
| 116 |
+
self.module = module
|
| 117 |
+
self.memo: dict[str, torch.nn.Module] = {}
|
| 118 |
+
|
| 119 |
+
# Nested attribute access
|
| 120 |
+
|
| 121 |
+
def get_submodule(self, name: str) -> "torch.nn.Module":
|
| 122 |
+
"""
|
| 123 |
+
Return the submodule specified by the given path.
|
| 124 |
+
|
| 125 |
+
For example, to get the submodule mod.layer1.conv1,
|
| 126 |
+
use accessor.get_submodule("layer1.conv1")
|
| 127 |
+
|
| 128 |
+
Compare to mod.get_submodule("layer1.conv1"), this method will cache the
|
| 129 |
+
intermediate submodule access to speed up future lookups.
|
| 130 |
+
"""
|
| 131 |
+
if not name:
|
| 132 |
+
return self.module
|
| 133 |
+
|
| 134 |
+
if name in self.memo:
|
| 135 |
+
return self.memo[name]
|
| 136 |
+
else:
|
| 137 |
+
prefix, dot, attr = name.rpartition(".")
|
| 138 |
+
if dot:
|
| 139 |
+
module = self.get_submodule(prefix)
|
| 140 |
+
else:
|
| 141 |
+
module = self.module
|
| 142 |
+
try:
|
| 143 |
+
submodule = getattr(module, attr)
|
| 144 |
+
except AttributeError as ex:
|
| 145 |
+
raise AttributeError(
|
| 146 |
+
f"{module._get_name()} has no attribute `{attr}`"
|
| 147 |
+
) from ex
|
| 148 |
+
if not isinstance(submodule, torch.nn.Module):
|
| 149 |
+
raise TypeError(
|
| 150 |
+
f"submodule `{name}`: {submodule} is not an instance of torch.nn.Module"
|
| 151 |
+
)
|
| 152 |
+
self.memo[name] = submodule
|
| 153 |
+
return submodule
|
| 154 |
+
|
| 155 |
+
def swap_submodule(self, path: str, value: "torch.nn.Module") -> "torch.nn.Module":
|
| 156 |
+
"""
|
| 157 |
+
Swap the submodule specified by the given ``path`` to ``value``.
|
| 158 |
+
|
| 159 |
+
For example, to swap the attribute mod.layer1.conv1 use
|
| 160 |
+
``accessor.swap_submodule("layer1.conv1", conv2)``.
|
| 161 |
+
"""
|
| 162 |
+
prefix, _, attr = path.rpartition(".")
|
| 163 |
+
return swap_submodule(self.get_submodule(prefix), attr, value)
|
| 164 |
+
|
| 165 |
+
def get_tensor(self, name: str) -> torch.Tensor:
|
| 166 |
+
"""
|
| 167 |
+
Get the tensor specified by the given path to value.
|
| 168 |
+
|
| 169 |
+
For example, to get the attribute mod.layer1.conv1.weight,
|
| 170 |
+
use accessor.get_tensor('layer1.conv1.weight')
|
| 171 |
+
|
| 172 |
+
Compare to mod.get_parameter("layer1.conv1.weight"), this method will
|
| 173 |
+
cache the intermediate submodule access to speed up future lookups.
|
| 174 |
+
"""
|
| 175 |
+
prefix, _, attr = name.rpartition(".")
|
| 176 |
+
submodule = self.get_submodule(prefix)
|
| 177 |
+
try:
|
| 178 |
+
tensor = getattr(submodule, attr)
|
| 179 |
+
except AttributeError as ex:
|
| 180 |
+
raise AttributeError(
|
| 181 |
+
f"{submodule._get_name()} has no attribute `{name}`"
|
| 182 |
+
) from ex
|
| 183 |
+
if not isinstance(tensor, torch.Tensor) and tensor is not None:
|
| 184 |
+
raise TypeError(f"{tensor} is not an instance of torch.Tensor")
|
| 185 |
+
return tensor # type: ignore[return-value]
|
| 186 |
+
|
| 187 |
+
def set_tensor(self, name: str, value: torch.Tensor) -> None:
|
| 188 |
+
"""
|
| 189 |
+
Set the attribute specified by the given path to value.
|
| 190 |
+
|
| 191 |
+
For example, to set the attribute mod.layer1.conv1.weight,
|
| 192 |
+
use accessor.set_tensor("layer1.conv1.weight", value)
|
| 193 |
+
"""
|
| 194 |
+
prefix, _, attr = name.rpartition(".")
|
| 195 |
+
set_tensor(self.get_submodule(prefix), attr, value)
|
| 196 |
+
|
| 197 |
+
def del_tensor(self, name: str) -> None:
|
| 198 |
+
"""
|
| 199 |
+
Delete the attribute specified by the given path.
|
| 200 |
+
|
| 201 |
+
For example, to delete the attribute mod.layer1.conv1.weight,
|
| 202 |
+
use accessor.del_tensor("layer1.conv1.weight")
|
| 203 |
+
"""
|
| 204 |
+
prefix, _, attr = name.rpartition(".")
|
| 205 |
+
submodule = self.get_submodule(prefix)
|
| 206 |
+
try:
|
| 207 |
+
delattr(submodule, attr)
|
| 208 |
+
except AttributeError as ex:
|
| 209 |
+
raise AttributeError(
|
| 210 |
+
f"{submodule._get_name()} has no attribute `{name}`"
|
| 211 |
+
) from ex
|
| 212 |
+
|
| 213 |
+
def swap_tensor(
|
| 214 |
+
self, name: str, value: torch.Tensor, allow_missing: bool = False
|
| 215 |
+
) -> torch.Tensor:
|
| 216 |
+
"""
|
| 217 |
+
Swap the attribute specified by the given path to value.
|
| 218 |
+
|
| 219 |
+
For example, to swap the attribute mod.layer1.conv1.weight,
|
| 220 |
+
use accessor.swap_tensor("layer1.conv1.weight", value)
|
| 221 |
+
"""
|
| 222 |
+
prefix, _, attr = name.rpartition(".")
|
| 223 |
+
return swap_tensor(
|
| 224 |
+
self.get_submodule(prefix), attr, value, allow_missing=allow_missing
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
# Batched operations
|
| 228 |
+
|
| 229 |
+
def get_tensors(self, names: Iterable[str]) -> list[torch.Tensor]:
|
| 230 |
+
"""
|
| 231 |
+
Get the tensors specified by the given paths.
|
| 232 |
+
|
| 233 |
+
For example, to get the attributes mod.layer1.conv1.weight and
|
| 234 |
+
mod.layer1.conv1.bias, use accessor.get_tensors(["layer1.conv1.weight",
|
| 235 |
+
"layer1.conv1.bias"])
|
| 236 |
+
"""
|
| 237 |
+
return [self.get_tensor(name) for name in names]
|
| 238 |
+
|
| 239 |
+
def set_tensors(self, names: Iterable[str], values: Iterable[torch.Tensor]) -> None:
|
| 240 |
+
"""
|
| 241 |
+
Set the attributes specified by the given paths to values.
|
| 242 |
+
|
| 243 |
+
For example, to set the attributes mod.layer1.conv1.weight and
|
| 244 |
+
mod.layer1.conv1.bias, use accessor.set_tensors(["layer1.conv1.weight",
|
| 245 |
+
"layer1.conv1.bias"], [weight, bias])
|
| 246 |
+
"""
|
| 247 |
+
if not isinstance(names, (list, tuple)):
|
| 248 |
+
names = list(names)
|
| 249 |
+
if not isinstance(values, (list, tuple)):
|
| 250 |
+
values = list(values)
|
| 251 |
+
if len(names) != len(values):
|
| 252 |
+
raise AssertionError(
|
| 253 |
+
f"names and values must have the same length, "
|
| 254 |
+
f"got {len(names)} names and {len(values)} values"
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
for name, value in zip(names, values, strict=True):
|
| 258 |
+
self.set_tensor(name, value)
|
| 259 |
+
|
| 260 |
+
def set_tensors_dict(self, named_tensors: dict[str, torch.Tensor]) -> None:
|
| 261 |
+
"""
|
| 262 |
+
Set the attributes specified by the given paths to values.
|
| 263 |
+
|
| 264 |
+
For example, to set the attributes mod.layer1.conv1.weight and
|
| 265 |
+
mod.layer1.conv1.bias, use accessor.set_tensors_dict({
|
| 266 |
+
"layer1.conv1.weight": weight,
|
| 267 |
+
"layer1.conv1.bias": bias,
|
| 268 |
+
})
|
| 269 |
+
"""
|
| 270 |
+
for name, value in named_tensors.items():
|
| 271 |
+
self.set_tensor(name, value)
|
| 272 |
+
|
| 273 |
+
def del_tensors(self, names: Iterable[str]) -> None:
|
| 274 |
+
"""
|
| 275 |
+
Delete the attributes specified by the given paths.
|
| 276 |
+
|
| 277 |
+
For example, to delete the attributes mod.layer1.conv1.weight and
|
| 278 |
+
mod.layer1.conv1.bias, use accessor.del_tensors(["layer1.conv1.weight",
|
| 279 |
+
"layer1.conv1.bias"])
|
| 280 |
+
"""
|
| 281 |
+
for name in names:
|
| 282 |
+
self.del_tensor(name)
|
| 283 |
+
|
| 284 |
+
def swap_tensors(
|
| 285 |
+
self,
|
| 286 |
+
names: Iterable[str],
|
| 287 |
+
values: Iterable[torch.Tensor],
|
| 288 |
+
allow_missing: bool = False,
|
| 289 |
+
) -> list[torch.Tensor]:
|
| 290 |
+
"""
|
| 291 |
+
Swap the attributes specified by the given paths to values.
|
| 292 |
+
|
| 293 |
+
For example, to swap the attributes mod.layer1.conv1.weight and
|
| 294 |
+
mod.layer1.conv1.bias, use accessor.swap_tensors(["layer1.conv1.weight",
|
| 295 |
+
"layer1.conv1.bias"], [weight, bias])
|
| 296 |
+
"""
|
| 297 |
+
if not isinstance(names, (list, tuple)):
|
| 298 |
+
names = list(names)
|
| 299 |
+
if not isinstance(values, (list, tuple)):
|
| 300 |
+
values = list(values)
|
| 301 |
+
if len(names) != len(values):
|
| 302 |
+
raise AssertionError(
|
| 303 |
+
f"names and values must have the same length, "
|
| 304 |
+
f"got {len(names)} names and {len(values)} values"
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
return [
|
| 308 |
+
self.swap_tensor(name, value, allow_missing=allow_missing)
|
| 309 |
+
for name, value in zip(names, values, strict=True)
|
| 310 |
+
]
|
| 311 |
+
|
| 312 |
+
def swap_tensors_dict(
|
| 313 |
+
self, named_tensors: dict[str, torch.Tensor], allow_missing: bool = False
|
| 314 |
+
) -> tuple[dict[str, torch.Tensor], list[str]]:
|
| 315 |
+
"""
|
| 316 |
+
Swap the attributes specified by the given paths to values.
|
| 317 |
+
|
| 318 |
+
For example, to swap the attributes mod.layer1.conv1.weight and
|
| 319 |
+
mod.layer1.conv1.bias, use accessor.swap_tensors_dict({
|
| 320 |
+
"layer1.conv1.weight": weight,
|
| 321 |
+
"layer1.conv1.bias": bias,
|
| 322 |
+
})
|
| 323 |
+
"""
|
| 324 |
+
orig_named_tensors = {}
|
| 325 |
+
missing_keys = []
|
| 326 |
+
try:
|
| 327 |
+
for name, tensor in named_tensors.items():
|
| 328 |
+
orig_tensor = self.swap_tensor(name, tensor, allow_missing=True)
|
| 329 |
+
if orig_tensor is _MISSING:
|
| 330 |
+
missing_keys.append(name)
|
| 331 |
+
orig_named_tensors[name] = orig_tensor
|
| 332 |
+
except Exception:
|
| 333 |
+
# Swap back if any exception occurs
|
| 334 |
+
for name, orig_tensor in orig_named_tensors.items():
|
| 335 |
+
self.swap_tensor(name, orig_tensor, allow_missing=True)
|
| 336 |
+
raise
|
| 337 |
+
if missing_keys and not allow_missing:
|
| 338 |
+
# Swap back if any key is missing when allow_missing is False
|
| 339 |
+
for name, orig_tensor in orig_named_tensors.items():
|
| 340 |
+
self.swap_tensor(name, orig_tensor, allow_missing=True)
|
| 341 |
+
raise RuntimeError(f"Missing key(s): {', '.join(map(repr, missing_keys))}.")
|
| 342 |
+
return orig_named_tensors, missing_keys
|
| 343 |
+
|
| 344 |
+
def check_keys(self, keys: Iterable[str]) -> tuple[list[str], list[str]]:
|
| 345 |
+
"""Check that the given keys are valid."""
|
| 346 |
+
keys = set(keys)
|
| 347 |
+
valid_keys = {name for name, _ in self.named_tensors(remove_duplicate=False)}
|
| 348 |
+
missing_keys = valid_keys - keys
|
| 349 |
+
unexpected_keys = keys - valid_keys
|
| 350 |
+
return sorted(missing_keys), sorted(unexpected_keys)
|
| 351 |
+
|
| 352 |
+
# Shortcut methods
|
| 353 |
+
|
| 354 |
+
def named_parameters(
|
| 355 |
+
self,
|
| 356 |
+
remove_duplicate: bool = True,
|
| 357 |
+
) -> Iterable[tuple[str, torch.Tensor]]:
|
| 358 |
+
"""Iterate over all the parameters in the module."""
|
| 359 |
+
yield from self.module.named_parameters(remove_duplicate=remove_duplicate)
|
| 360 |
+
|
| 361 |
+
def named_buffers(
|
| 362 |
+
self,
|
| 363 |
+
remove_duplicate: bool = True,
|
| 364 |
+
) -> Iterable[tuple[str, torch.Tensor]]:
|
| 365 |
+
"""Iterate over all the buffers in the module."""
|
| 366 |
+
yield from self.module.named_buffers(remove_duplicate=remove_duplicate)
|
| 367 |
+
|
| 368 |
+
def named_tensors(
|
| 369 |
+
self,
|
| 370 |
+
remove_duplicate: bool = True,
|
| 371 |
+
) -> Iterable[tuple[str, torch.Tensor]]:
|
| 372 |
+
"""Iterate over all the tensors in the module."""
|
| 373 |
+
yield from self.module.named_parameters(remove_duplicate=remove_duplicate)
|
| 374 |
+
yield from self.module.named_buffers(remove_duplicate=remove_duplicate)
|
| 375 |
+
|
| 376 |
+
def named_modules(
|
| 377 |
+
self,
|
| 378 |
+
remove_duplicate: bool = True,
|
| 379 |
+
) -> Iterable[tuple[str, "torch.nn.Module"]]:
|
| 380 |
+
"""Iterate over all the modules in the module."""
|
| 381 |
+
yield from self.module.named_modules(remove_duplicate=remove_duplicate)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_per_sample_grad.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import functools
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from torch.nn.utils._expanded_weights.expanded_weights_impl import ExpandedWeight
|
| 6 |
+
from torch.utils import _pytree as pytree
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# dependency on `functional_call` means that this can't be exposed in utils
|
| 10 |
+
# without creating circular dependency
|
| 11 |
+
def call_for_per_sample_grads(
|
| 12 |
+
module,
|
| 13 |
+
*,
|
| 14 |
+
batch_size=None,
|
| 15 |
+
loss_reduction="sum",
|
| 16 |
+
batch_first=True,
|
| 17 |
+
):
|
| 18 |
+
r"""
|
| 19 |
+
Return a forward function for a module, populating grad_sample with per sample gradients on backward invocation.
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
module: The ``nn.Module`` to get per sample gradients with respect to. All trainable
|
| 23 |
+
parameters will compute per sample gradients, located in a ``grad_sample``
|
| 24 |
+
field when ``backward`` is invoked
|
| 25 |
+
batch_size: The batch size of the input. If None is passed, all tensor arguments in args and kwargs must have
|
| 26 |
+
the same batch size, which is the size of the first dimension. Otherwise, it must be passed manually.
|
| 27 |
+
Default: None
|
| 28 |
+
loss_reduction: Indicates if the loss reduction (for aggregating the gradients) is a sum or a mean operation. If
|
| 29 |
+
"mean", per sample gradients will be scaled by the batch size to offset the crossbatch interaction from
|
| 30 |
+
running mean across a batch. Must be "mean" or "sum". Default: "sum"
|
| 31 |
+
batch_first: Indicates if the batch dimension is the first dimension. If True, the batch dimension is the first
|
| 32 |
+
dimension. If False, it's the second dimension. Default: True.
|
| 33 |
+
|
| 34 |
+
Examples::
|
| 35 |
+
>>> # xdoctest: +SKIP
|
| 36 |
+
>>> model = nn.Linear(4, 3)
|
| 37 |
+
>>> batched_input = torch.randn(5, 4) # batch size of 5
|
| 38 |
+
>>> res = call_for_per_sample_grads(model)(batched_input).sum()
|
| 39 |
+
>>> res.backward()
|
| 40 |
+
>>> assert model.weight.shape == (3, 4)
|
| 41 |
+
>>> assert model.weight.grad_sample.shape == (5, 3, 4)
|
| 42 |
+
>>> assert model.weight.grad is None
|
| 43 |
+
>>> assert model.bias.shape == (3,)
|
| 44 |
+
>>> assert model.bias.grad_sample.shape == (5, 3)
|
| 45 |
+
>>> assert model.bias.grad is None
|
| 46 |
+
|
| 47 |
+
An example using "mean" loss reduction. The grad_sample fields will be scaled by batch_size from what they would be
|
| 48 |
+
if we ran the same code with loss_reduction="sum". This is because the mean at the end will scale all
|
| 49 |
+
grad_outputs by 1 / batch_size from cross batch interaction.
|
| 50 |
+
>>> model = nn.Linear(4, 3)
|
| 51 |
+
>>> batched_input = torch.randn(5, 4) # batch size of 5
|
| 52 |
+
>>> res = call_for_per_sample_grads(model, 5, loss_reduction="mean")(
|
| 53 |
+
... batched_input
|
| 54 |
+
... ).mean()
|
| 55 |
+
>>> res.backward()
|
| 56 |
+
|
| 57 |
+
Note::
|
| 58 |
+
Does not work with any `nn.RNN`, including `nn.GRU` or `nn.LSTM`. Please use custom
|
| 59 |
+
rewrites that wrap an `nn.Linear` module. See Opacus for an example
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def maybe_build_expanded_weight(og_tensor, batch_size):
|
| 63 |
+
if og_tensor.requires_grad:
|
| 64 |
+
return ExpandedWeight(og_tensor, batch_size, loss_reduction)
|
| 65 |
+
else:
|
| 66 |
+
return og_tensor
|
| 67 |
+
|
| 68 |
+
def compute_batch_size(*args, **kwargs):
|
| 69 |
+
args_and_kwargs = pytree.arg_tree_leaves(*args, **kwargs)
|
| 70 |
+
batch_size = None
|
| 71 |
+
for arg in args_and_kwargs:
|
| 72 |
+
if not isinstance(arg, torch.Tensor):
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
arg_batch_size = arg.shape[0] if batch_first else arg.shape[1]
|
| 76 |
+
if batch_size is not None and batch_size != arg_batch_size:
|
| 77 |
+
raise RuntimeError(
|
| 78 |
+
"When computing batch size, found at least one input with batch size "
|
| 79 |
+
f"{batch_size} and one with batch size {arg_batch_size}. Please specify it "
|
| 80 |
+
"explicitly using the batch size kwarg in call_for_per_sample_grads"
|
| 81 |
+
)
|
| 82 |
+
batch_size = arg_batch_size
|
| 83 |
+
if batch_size is None:
|
| 84 |
+
raise RuntimeError(
|
| 85 |
+
"Unable to find a tensor in the passed args and kwargs. They may not be pytree-able "
|
| 86 |
+
"and so ExpandedWeights cannot compute the batch size from the inputs. Please specify "
|
| 87 |
+
"it explicitly"
|
| 88 |
+
)
|
| 89 |
+
return batch_size
|
| 90 |
+
|
| 91 |
+
if loss_reduction not in ["sum", "mean"]:
|
| 92 |
+
raise RuntimeError(
|
| 93 |
+
f"Expected loss_reduction argument to be sum or mean, got {loss_reduction}"
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
if not isinstance(module, torch.nn.Module):
|
| 97 |
+
raise RuntimeError(
|
| 98 |
+
f"Module passed must be nn.Module, got {type(module).__name__}"
|
| 99 |
+
)
|
| 100 |
+
if not (batch_size is None or isinstance(batch_size, int)):
|
| 101 |
+
raise RuntimeError(
|
| 102 |
+
f"Batch size passed must be None or an integer, got {type(batch_size).__name__}"
|
| 103 |
+
)
|
| 104 |
+
if batch_size is not None and batch_size < 1:
|
| 105 |
+
raise RuntimeError(f"Batch size must be positive, got {batch_size}")
|
| 106 |
+
for weight in module.parameters():
|
| 107 |
+
if hasattr(weight, "grad_sample") and weight.grad_sample is not None: # type: ignore[attr-defined]
|
| 108 |
+
raise RuntimeError(
|
| 109 |
+
"Current Expanded Weights accumulates the gradients, which will be incorrect for multiple "
|
| 110 |
+
f"calls without clearing gradients. Please clear out the grad_sample parameter of {weight} or "
|
| 111 |
+
"post an issue to pytorch/pytorch to prioritize correct behavior"
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
@functools.wraps(module.forward)
|
| 115 |
+
def wrapper(*args, **kwargs):
|
| 116 |
+
wrapper_batch_size = batch_size
|
| 117 |
+
if wrapper_batch_size is None:
|
| 118 |
+
wrapper_batch_size = compute_batch_size(*args, **kwargs)
|
| 119 |
+
|
| 120 |
+
params = {
|
| 121 |
+
name: maybe_build_expanded_weight(value, wrapper_batch_size)
|
| 122 |
+
for (name, value) in module.named_parameters()
|
| 123 |
+
}
|
| 124 |
+
return torch.func.functional_call(module, params, args, kwargs)
|
| 125 |
+
|
| 126 |
+
return wrapper
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/clip_grad.py
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-decorators
|
| 2 |
+
# mypy: allow-untyped-defs
|
| 3 |
+
import functools
|
| 4 |
+
import types
|
| 5 |
+
import typing
|
| 6 |
+
import warnings
|
| 7 |
+
from collections.abc import Callable
|
| 8 |
+
from typing import cast, TypeAlias, TypeVar
|
| 9 |
+
from typing_extensions import deprecated, ParamSpec
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from torch import Tensor
|
| 13 |
+
from torch.utils._foreach_utils import (
|
| 14 |
+
_device_has_foreach_support,
|
| 15 |
+
_group_tensors_by_device_and_dtype,
|
| 16 |
+
_has_foreach_support,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
__all__: list[str] = [
|
| 21 |
+
"clip_grad_norm",
|
| 22 |
+
"clip_grad_norm_",
|
| 23 |
+
"clip_grad_value_",
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
_tensor_or_tensors: TypeAlias = torch.Tensor | typing.Iterable[torch.Tensor] # noqa: PYI042
|
| 28 |
+
|
| 29 |
+
_P = ParamSpec("_P")
|
| 30 |
+
_R = TypeVar("_R")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _no_grad(func: Callable[_P, _R]) -> Callable[_P, _R]:
|
| 34 |
+
"""
|
| 35 |
+
This wrapper is needed to avoid a circular import when using @torch.no_grad on the exposed functions
|
| 36 |
+
clip_grad_norm_ and clip_grad_value_ themselves.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
def _no_grad_wrapper(*args, **kwargs):
|
| 40 |
+
with torch.no_grad():
|
| 41 |
+
return func(*args, **kwargs)
|
| 42 |
+
|
| 43 |
+
functools.update_wrapper(_no_grad_wrapper, func)
|
| 44 |
+
# pyrefly: ignore [bad-return]
|
| 45 |
+
return _no_grad_wrapper
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@_no_grad
|
| 49 |
+
def _get_total_norm(
|
| 50 |
+
tensors: _tensor_or_tensors,
|
| 51 |
+
norm_type: float = 2.0,
|
| 52 |
+
error_if_nonfinite: bool = False,
|
| 53 |
+
foreach: bool | None = None,
|
| 54 |
+
) -> torch.Tensor:
|
| 55 |
+
r"""Compute the norm of an iterable of tensors.
|
| 56 |
+
|
| 57 |
+
The norm is computed over the norms of the individual tensors, as if the norms of
|
| 58 |
+
the individual tensors were concatenated into a single vector.
|
| 59 |
+
|
| 60 |
+
Args:
|
| 61 |
+
tensors (Iterable[Tensor] or Tensor): an iterable of Tensors or a
|
| 62 |
+
single Tensor that will be normalized
|
| 63 |
+
norm_type (float): type of the used p-norm. Can be ``'inf'`` for
|
| 64 |
+
infinity norm.
|
| 65 |
+
error_if_nonfinite (bool): if True, an error is thrown if the total
|
| 66 |
+
norm of :attr:`tensors` is ``nan``, ``inf``, or ``-inf``.
|
| 67 |
+
Default: ``False``
|
| 68 |
+
foreach (bool): use the faster foreach-based implementation.
|
| 69 |
+
If ``None``, use the foreach implementation for CUDA and CPU native tensors and silently
|
| 70 |
+
fall back to the slow implementation for other device types.
|
| 71 |
+
Default: ``None``
|
| 72 |
+
|
| 73 |
+
Returns:
|
| 74 |
+
Total norm of the tensors (viewed as a single vector).
|
| 75 |
+
"""
|
| 76 |
+
if isinstance(tensors, torch.Tensor):
|
| 77 |
+
tensors = [tensors]
|
| 78 |
+
else:
|
| 79 |
+
tensors = list(tensors)
|
| 80 |
+
norm_type = float(norm_type)
|
| 81 |
+
if len(tensors) == 0:
|
| 82 |
+
return torch.tensor(0.0)
|
| 83 |
+
first_device = tensors[0].device
|
| 84 |
+
grouped_tensors: dict[
|
| 85 |
+
tuple[torch.device, torch.dtype], tuple[list[list[Tensor]], list[int]]
|
| 86 |
+
] = _group_tensors_by_device_and_dtype( # pyrefly: ignore [bad-assignment]
|
| 87 |
+
[tensors] # type: ignore[list-item]
|
| 88 |
+
) # type: ignore[assignment]
|
| 89 |
+
|
| 90 |
+
norms: list[Tensor] = []
|
| 91 |
+
for (device, _), ([device_tensors], _) in grouped_tensors.items():
|
| 92 |
+
if (foreach is None and _has_foreach_support(device_tensors, device)) or (
|
| 93 |
+
foreach and _device_has_foreach_support(device)
|
| 94 |
+
):
|
| 95 |
+
norms.extend(torch._foreach_norm(device_tensors, norm_type))
|
| 96 |
+
elif foreach:
|
| 97 |
+
raise RuntimeError(
|
| 98 |
+
f"foreach=True was passed, but can't use the foreach API on {device.type} tensors"
|
| 99 |
+
)
|
| 100 |
+
else:
|
| 101 |
+
norms.extend(
|
| 102 |
+
[torch.linalg.vector_norm(g, norm_type) for g in device_tensors]
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
total_norm = torch.linalg.vector_norm(
|
| 106 |
+
torch.stack([norm.to(first_device) for norm in norms]), norm_type
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
if error_if_nonfinite and torch.logical_or(total_norm.isnan(), total_norm.isinf()):
|
| 110 |
+
raise RuntimeError(
|
| 111 |
+
f"The total norm of order {norm_type} for gradients from "
|
| 112 |
+
"`parameters` is non-finite, so it cannot be clipped. To disable "
|
| 113 |
+
"this error and scale the gradients by the non-finite norm anyway, "
|
| 114 |
+
"set `error_if_nonfinite=False`"
|
| 115 |
+
)
|
| 116 |
+
return total_norm
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@_no_grad
|
| 120 |
+
def _clip_grads_with_norm_(
|
| 121 |
+
parameters: _tensor_or_tensors,
|
| 122 |
+
max_norm: float,
|
| 123 |
+
total_norm: torch.Tensor,
|
| 124 |
+
foreach: bool | None = None,
|
| 125 |
+
) -> None:
|
| 126 |
+
r"""Scale the gradients of an iterable of parameters given a pre-calculated total norm and desired max norm.
|
| 127 |
+
|
| 128 |
+
The gradients will be scaled by the following calculation
|
| 129 |
+
|
| 130 |
+
.. math::
|
| 131 |
+
grad = grad * \min(\frac{max\_norm}{total\_norm + 1e-6}, 1)
|
| 132 |
+
|
| 133 |
+
Gradients are modified in-place.
|
| 134 |
+
|
| 135 |
+
Note: The scale coefficient is clamped to a maximum of 1.0 to prevent gradient amplification.
|
| 136 |
+
This ensures that gradients are only scaled down when the total norm exceeds max_norm.
|
| 137 |
+
|
| 138 |
+
This function is equivalent to :func:`torch.nn.utils.clip_grad_norm_` with a pre-calculated
|
| 139 |
+
total norm.
|
| 140 |
+
|
| 141 |
+
Args:
|
| 142 |
+
parameters (Iterable[Tensor] or Tensor): an iterable of Tensors or a
|
| 143 |
+
single Tensor that will have gradients normalized
|
| 144 |
+
max_norm (float): max norm of the gradients
|
| 145 |
+
total_norm (Tensor): total norm of the gradients to use for clipping
|
| 146 |
+
foreach (bool): use the faster foreach-based implementation.
|
| 147 |
+
If ``None``, use the foreach implementation for CUDA and CPU native tensors and silently
|
| 148 |
+
fall back to the slow implementation for other device types.
|
| 149 |
+
Default: ``None``
|
| 150 |
+
|
| 151 |
+
Returns:
|
| 152 |
+
None
|
| 153 |
+
"""
|
| 154 |
+
if isinstance(parameters, torch.Tensor):
|
| 155 |
+
parameters = [parameters]
|
| 156 |
+
grads = [p.grad for p in parameters if p.grad is not None]
|
| 157 |
+
max_norm = float(max_norm)
|
| 158 |
+
if len(grads) == 0:
|
| 159 |
+
return
|
| 160 |
+
grouped_grads: dict[
|
| 161 |
+
tuple[torch.device, torch.dtype], tuple[list[list[Tensor]], list[int]]
|
| 162 |
+
] = _group_tensors_by_device_and_dtype([grads]) # type: ignore[assignment]
|
| 163 |
+
|
| 164 |
+
clip_coef = max_norm / (total_norm + 1e-6)
|
| 165 |
+
# Note: multiplying by the clamped coef is redundant when the coef is clamped to 1, but doing so
|
| 166 |
+
# avoids a `if clip_coef < 1:` conditional which can require a CPU <=> device synchronization
|
| 167 |
+
# when the gradients do not reside in CPU memory.
|
| 168 |
+
clip_coef_clamped = torch.clamp(clip_coef, max=1.0)
|
| 169 |
+
for (device, _), ([device_grads], _) in grouped_grads.items():
|
| 170 |
+
if (foreach is None and _has_foreach_support(device_grads, device)) or (
|
| 171 |
+
foreach and _device_has_foreach_support(device)
|
| 172 |
+
):
|
| 173 |
+
torch._foreach_mul_(device_grads, clip_coef_clamped.to(device))
|
| 174 |
+
elif foreach:
|
| 175 |
+
raise RuntimeError(
|
| 176 |
+
f"foreach=True was passed, but can't use the foreach API on {device.type} tensors"
|
| 177 |
+
)
|
| 178 |
+
else:
|
| 179 |
+
clip_coef_clamped_device = clip_coef_clamped.to(device)
|
| 180 |
+
for g in device_grads:
|
| 181 |
+
g.mul_(clip_coef_clamped_device)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
@_no_grad
|
| 185 |
+
def clip_grad_norm_(
|
| 186 |
+
parameters: _tensor_or_tensors,
|
| 187 |
+
max_norm: float,
|
| 188 |
+
norm_type: float = 2.0,
|
| 189 |
+
error_if_nonfinite: bool = False,
|
| 190 |
+
foreach: bool | None = None,
|
| 191 |
+
) -> torch.Tensor:
|
| 192 |
+
r"""Clip the gradient norm of an iterable of parameters.
|
| 193 |
+
|
| 194 |
+
The norm is computed over the norms of the individual gradients of all parameters,
|
| 195 |
+
as if the norms of the individual gradients were concatenated into a single vector.
|
| 196 |
+
Gradients are modified in-place.
|
| 197 |
+
|
| 198 |
+
This function is equivalent to :func:`torch.nn.utils.get_total_norm` followed by
|
| 199 |
+
:func:`torch.nn.utils.clip_grads_with_norm_` with the ``total_norm`` returned by ``get_total_norm``.
|
| 200 |
+
|
| 201 |
+
Args:
|
| 202 |
+
parameters (Iterable[Tensor] or Tensor): an iterable of Tensors or a
|
| 203 |
+
single Tensor that will have gradients normalized
|
| 204 |
+
max_norm (float): max norm of the gradients
|
| 205 |
+
norm_type (float, optional): type of the used p-norm. Can be ``'inf'`` for
|
| 206 |
+
infinity norm. Default: 2.0
|
| 207 |
+
error_if_nonfinite (bool, optional): if True, an error is thrown if the total
|
| 208 |
+
norm of the gradients from :attr:`parameters` is ``nan``,
|
| 209 |
+
``inf``, or ``-inf``. Default: False
|
| 210 |
+
foreach (bool, optional): use the faster foreach-based implementation.
|
| 211 |
+
If ``None``, use the foreach implementation for CUDA and CPU native tensors and silently
|
| 212 |
+
fall back to the slow implementation for other device types.
|
| 213 |
+
Default: ``None``
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
Total norm of the parameter gradients (viewed as a single vector).
|
| 217 |
+
"""
|
| 218 |
+
if isinstance(parameters, torch.Tensor):
|
| 219 |
+
parameters = [parameters]
|
| 220 |
+
else:
|
| 221 |
+
is_generator = isinstance(parameters, types.GeneratorType)
|
| 222 |
+
# prevent generators from being exhausted
|
| 223 |
+
parameters = list(parameters)
|
| 224 |
+
if is_generator and len(parameters) == 0:
|
| 225 |
+
warnings.warn(
|
| 226 |
+
"`parameters` is an empty generator, no gradient clipping will occur.",
|
| 227 |
+
stacklevel=3,
|
| 228 |
+
)
|
| 229 |
+
grads = [p.grad for p in parameters if p.grad is not None]
|
| 230 |
+
total_norm = _get_total_norm(grads, norm_type, error_if_nonfinite, foreach)
|
| 231 |
+
_clip_grads_with_norm_(parameters, max_norm, total_norm, foreach)
|
| 232 |
+
return total_norm
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
@deprecated(
|
| 236 |
+
"`torch.nn.utils.clip_grad_norm` is now deprecated "
|
| 237 |
+
"in favor of `torch.nn.utils.clip_grad_norm_`.",
|
| 238 |
+
category=FutureWarning,
|
| 239 |
+
)
|
| 240 |
+
def clip_grad_norm(
|
| 241 |
+
parameters: _tensor_or_tensors,
|
| 242 |
+
max_norm: float,
|
| 243 |
+
norm_type: float = 2.0,
|
| 244 |
+
error_if_nonfinite: bool = False,
|
| 245 |
+
foreach: bool | None = None,
|
| 246 |
+
) -> torch.Tensor:
|
| 247 |
+
r"""Clip the gradient norm of an iterable of parameters.
|
| 248 |
+
|
| 249 |
+
.. warning::
|
| 250 |
+
This method is now deprecated in favor of
|
| 251 |
+
:func:`torch.nn.utils.clip_grad_norm_`.
|
| 252 |
+
"""
|
| 253 |
+
return clip_grad_norm_(parameters, max_norm, norm_type, error_if_nonfinite, foreach)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
@_no_grad
|
| 257 |
+
def clip_grad_value_(
|
| 258 |
+
parameters: _tensor_or_tensors,
|
| 259 |
+
clip_value: float,
|
| 260 |
+
foreach: bool | None = None,
|
| 261 |
+
) -> None:
|
| 262 |
+
r"""Clip the gradients of an iterable of parameters at specified value.
|
| 263 |
+
|
| 264 |
+
Gradients are modified in-place.
|
| 265 |
+
|
| 266 |
+
Args:
|
| 267 |
+
parameters (Iterable[Tensor] or Tensor): an iterable of Tensors or a
|
| 268 |
+
single Tensor that will have gradients normalized
|
| 269 |
+
clip_value (float): maximum allowed value of the gradients.
|
| 270 |
+
The gradients are clipped in the range
|
| 271 |
+
:math:`\left[\text{-clip\_value}, \text{clip\_value}\right]`
|
| 272 |
+
foreach (bool, optional): use the faster foreach-based implementation
|
| 273 |
+
If ``None``, use the foreach implementation for CUDA and CPU native tensors and
|
| 274 |
+
silently fall back to the slow implementation for other device types.
|
| 275 |
+
Default: ``None``
|
| 276 |
+
"""
|
| 277 |
+
if isinstance(parameters, torch.Tensor):
|
| 278 |
+
parameters = [parameters]
|
| 279 |
+
clip_value = float(clip_value)
|
| 280 |
+
|
| 281 |
+
grads = [p.grad for p in parameters if p.grad is not None]
|
| 282 |
+
# pyrefly: ignore [bad-argument-type]
|
| 283 |
+
grouped_grads = _group_tensors_by_device_and_dtype([grads])
|
| 284 |
+
|
| 285 |
+
for (device, _), ([grads], _) in grouped_grads.items():
|
| 286 |
+
if (
|
| 287 |
+
foreach is None
|
| 288 |
+
and _has_foreach_support(cast(list[Tensor], grads), device=device)
|
| 289 |
+
) or (foreach and _device_has_foreach_support(device)):
|
| 290 |
+
torch._foreach_clamp_min_(cast(list[Tensor], grads), -clip_value)
|
| 291 |
+
torch._foreach_clamp_max_(cast(list[Tensor], grads), clip_value)
|
| 292 |
+
elif foreach:
|
| 293 |
+
raise RuntimeError(
|
| 294 |
+
f"foreach=True was passed, but can't use the foreach API on {device.type} tensors"
|
| 295 |
+
)
|
| 296 |
+
else:
|
| 297 |
+
for grad in grads:
|
| 298 |
+
cast(Tensor, grad).clamp_(min=-clip_value, max=clip_value)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/convert_parameters.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Iterable
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def parameters_to_vector(parameters: Iterable[torch.Tensor]) -> torch.Tensor:
|
| 7 |
+
r"""Flatten an iterable of parameters into a single vector.
|
| 8 |
+
|
| 9 |
+
Args:
|
| 10 |
+
parameters (Iterable[Tensor]): an iterable of Tensors that are the
|
| 11 |
+
parameters of a model.
|
| 12 |
+
|
| 13 |
+
Returns:
|
| 14 |
+
The parameters represented by a single vector
|
| 15 |
+
"""
|
| 16 |
+
# Flag for the device where the parameter is located
|
| 17 |
+
param_device = None
|
| 18 |
+
|
| 19 |
+
vec = []
|
| 20 |
+
for param in parameters:
|
| 21 |
+
# Ensure the parameters are located in the same device
|
| 22 |
+
param_device = _check_param_device(param, param_device)
|
| 23 |
+
|
| 24 |
+
vec.append(param.view(-1))
|
| 25 |
+
return torch.cat(vec)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def vector_to_parameters(vec: torch.Tensor, parameters: Iterable[torch.Tensor]) -> None:
|
| 29 |
+
r"""Copy slices of a vector into an iterable of parameters.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
vec (Tensor): a single vector representing the parameters of a model.
|
| 33 |
+
parameters (Iterable[Tensor]): an iterable of Tensors that are the
|
| 34 |
+
parameters of a model.
|
| 35 |
+
"""
|
| 36 |
+
# Ensure vec of type Tensor
|
| 37 |
+
if not isinstance(vec, torch.Tensor):
|
| 38 |
+
raise TypeError(f"expected torch.Tensor, but got: {torch.typename(vec)}")
|
| 39 |
+
# Flag for the device where the parameter is located
|
| 40 |
+
param_device = None
|
| 41 |
+
|
| 42 |
+
# Pointer for slicing the vector for each parameter
|
| 43 |
+
pointer = 0
|
| 44 |
+
for param in parameters:
|
| 45 |
+
# Ensure the parameters are located in the same device
|
| 46 |
+
param_device = _check_param_device(param, param_device)
|
| 47 |
+
|
| 48 |
+
# The length of the parameter
|
| 49 |
+
num_param = param.numel()
|
| 50 |
+
# Slice the vector, reshape it, and replace the old data of the parameter
|
| 51 |
+
param.data = vec[pointer : pointer + num_param].view_as(param).data
|
| 52 |
+
|
| 53 |
+
# Increment the pointer
|
| 54 |
+
pointer += num_param
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def _check_param_device(param: torch.Tensor, old_param_device: int | None) -> int:
|
| 58 |
+
r"""Check if the parameters are located on the same device.
|
| 59 |
+
|
| 60 |
+
Currently, the conversion between model parameters and single vector form is not supported
|
| 61 |
+
for multiple allocations, e.g. parameters in different GPUs/PrivateUse1s, or mixture of CPU/GPU/PrivateUse1.
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
param ([Tensor]): a Tensor of a parameter of a model
|
| 65 |
+
old_param_device (int): the device where the first parameter of a
|
| 66 |
+
model is allocated.
|
| 67 |
+
|
| 68 |
+
Returns:
|
| 69 |
+
old_param_device (int): report device for the first time
|
| 70 |
+
"""
|
| 71 |
+
# Meet the first parameter
|
| 72 |
+
support_device_types = ["cuda", torch._C._get_privateuse1_backend_name()]
|
| 73 |
+
if old_param_device is None:
|
| 74 |
+
old_param_device = (
|
| 75 |
+
param.get_device() if param.device.type in support_device_types else -1
|
| 76 |
+
)
|
| 77 |
+
else:
|
| 78 |
+
warn = False
|
| 79 |
+
if (
|
| 80 |
+
param.device.type in support_device_types
|
| 81 |
+
): # Check if in same GPU/PrivateUse1
|
| 82 |
+
warn = param.get_device() != old_param_device
|
| 83 |
+
else: # Check if in CPU
|
| 84 |
+
warn = old_param_device != -1
|
| 85 |
+
if warn:
|
| 86 |
+
raise TypeError(
|
| 87 |
+
"Found two parameters on different devices, "
|
| 88 |
+
"this is currently not supported."
|
| 89 |
+
)
|
| 90 |
+
return old_param_device
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/fusion.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import copy
|
| 4 |
+
from typing import TypeVar
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
__all__ = [
|
| 10 |
+
"fuse_conv_bn_eval",
|
| 11 |
+
"fuse_conv_bn_weights",
|
| 12 |
+
"fuse_linear_bn_eval",
|
| 13 |
+
"fuse_linear_bn_weights",
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
ConvT = TypeVar("ConvT", bound="torch.nn.modules.conv._ConvNd")
|
| 17 |
+
LinearT = TypeVar("LinearT", bound="torch.nn.Linear")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def fuse_conv_bn_eval(
|
| 21 |
+
conv: ConvT,
|
| 22 |
+
bn: torch.nn.modules.batchnorm._BatchNorm,
|
| 23 |
+
transpose: bool = False,
|
| 24 |
+
) -> ConvT:
|
| 25 |
+
r"""Fuse a convolutional module and a BatchNorm module into a single, new convolutional module.
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
conv (torch.nn.modules.conv._ConvNd): A convolutional module.
|
| 29 |
+
bn (torch.nn.modules.batchnorm._BatchNorm): A BatchNorm module.
|
| 30 |
+
transpose (bool, optional): If True, transpose the convolutional weight. Defaults to False.
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
torch.nn.modules.conv._ConvNd: The fused convolutional module.
|
| 34 |
+
|
| 35 |
+
.. note::
|
| 36 |
+
Both ``conv`` and ``bn`` must be in eval mode, and ``bn`` must have its running buffers computed.
|
| 37 |
+
"""
|
| 38 |
+
if conv.training or bn.training:
|
| 39 |
+
raise AssertionError("Fusion only for eval!")
|
| 40 |
+
fused_conv = copy.deepcopy(conv)
|
| 41 |
+
|
| 42 |
+
if bn.running_mean is None or bn.running_var is None:
|
| 43 |
+
raise AssertionError("bn.running_mean and bn.running_var must not be None")
|
| 44 |
+
fused_conv.weight, fused_conv.bias = fuse_conv_bn_weights(
|
| 45 |
+
fused_conv.weight,
|
| 46 |
+
fused_conv.bias,
|
| 47 |
+
bn.running_mean,
|
| 48 |
+
bn.running_var,
|
| 49 |
+
bn.eps,
|
| 50 |
+
bn.weight,
|
| 51 |
+
bn.bias,
|
| 52 |
+
transpose,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
return fused_conv
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def fuse_conv_bn_weights(
|
| 59 |
+
conv_w: torch.Tensor,
|
| 60 |
+
conv_b: torch.Tensor | None,
|
| 61 |
+
bn_rm: torch.Tensor,
|
| 62 |
+
bn_rv: torch.Tensor,
|
| 63 |
+
bn_eps: float,
|
| 64 |
+
bn_w: torch.Tensor | None,
|
| 65 |
+
bn_b: torch.Tensor | None,
|
| 66 |
+
transpose: bool = False,
|
| 67 |
+
) -> tuple[torch.nn.Parameter, torch.nn.Parameter]:
|
| 68 |
+
r"""Fuse convolutional module parameters and BatchNorm module parameters into new convolutional module parameters.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
conv_w (torch.Tensor): Convolutional weight.
|
| 72 |
+
conv_b (Optional[torch.Tensor]): Convolutional bias.
|
| 73 |
+
bn_rm (torch.Tensor): BatchNorm running mean.
|
| 74 |
+
bn_rv (torch.Tensor): BatchNorm running variance.
|
| 75 |
+
bn_eps (float): BatchNorm epsilon.
|
| 76 |
+
bn_w (Optional[torch.Tensor]): BatchNorm weight.
|
| 77 |
+
bn_b (Optional[torch.Tensor]): BatchNorm bias.
|
| 78 |
+
transpose (bool, optional): If True, transpose the conv weight. Defaults to False.
|
| 79 |
+
|
| 80 |
+
Returns:
|
| 81 |
+
Tuple[torch.nn.Parameter, torch.nn.Parameter]: Fused convolutional weight and bias.
|
| 82 |
+
"""
|
| 83 |
+
conv_weight_dtype = conv_w.dtype
|
| 84 |
+
conv_bias_dtype = conv_b.dtype if conv_b is not None else conv_weight_dtype
|
| 85 |
+
if conv_b is None:
|
| 86 |
+
conv_b = torch.zeros_like(bn_rm)
|
| 87 |
+
if bn_w is None:
|
| 88 |
+
bn_w = torch.ones_like(bn_rm)
|
| 89 |
+
if bn_b is None:
|
| 90 |
+
bn_b = torch.zeros_like(bn_rm)
|
| 91 |
+
bn_var_rsqrt = torch.rsqrt(bn_rv + bn_eps)
|
| 92 |
+
|
| 93 |
+
if transpose:
|
| 94 |
+
shape = [1, -1] + [1] * (len(conv_w.shape) - 2)
|
| 95 |
+
else:
|
| 96 |
+
shape = [-1, 1] + [1] * (len(conv_w.shape) - 2)
|
| 97 |
+
|
| 98 |
+
fused_conv_w = (conv_w * (bn_w * bn_var_rsqrt).reshape(shape)).to(
|
| 99 |
+
dtype=conv_weight_dtype
|
| 100 |
+
)
|
| 101 |
+
fused_conv_b = ((conv_b - bn_rm) * bn_var_rsqrt * bn_w + bn_b).to(
|
| 102 |
+
dtype=conv_bias_dtype
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
return (
|
| 106 |
+
torch.nn.Parameter(fused_conv_w, conv_w.requires_grad),
|
| 107 |
+
torch.nn.Parameter(fused_conv_b, conv_b.requires_grad),
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def fuse_linear_bn_eval(
|
| 112 |
+
linear: LinearT,
|
| 113 |
+
bn: torch.nn.modules.batchnorm._BatchNorm,
|
| 114 |
+
) -> LinearT:
|
| 115 |
+
r"""Fuse a linear module and a BatchNorm module into a single, new linear module.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
linear (torch.nn.Linear): A Linear module.
|
| 119 |
+
bn (torch.nn.modules.batchnorm._BatchNorm): A BatchNorm module.
|
| 120 |
+
|
| 121 |
+
Returns:
|
| 122 |
+
torch.nn.Linear: The fused linear module.
|
| 123 |
+
|
| 124 |
+
.. note::
|
| 125 |
+
Both ``linear`` and ``bn`` must be in eval mode, and ``bn`` must have its running buffers computed.
|
| 126 |
+
"""
|
| 127 |
+
if linear.training or bn.training:
|
| 128 |
+
raise AssertionError("Fusion only for eval!")
|
| 129 |
+
fused_linear = copy.deepcopy(linear)
|
| 130 |
+
|
| 131 |
+
"""
|
| 132 |
+
Linear-BN needs to be fused while preserving the shapes of linear weight/bias.
|
| 133 |
+
To preserve the shapes of linear weight/bias, the channel dim of bn needs to be broadcastable with the last dim of linear,
|
| 134 |
+
because bn operates over the channel dim, (N, C_in, H, W) while linear operates over the last dim, (*, H_in).
|
| 135 |
+
To be broadcastable, the number of features in bn and
|
| 136 |
+
the number of output features from linear must satisfy the following condition:
|
| 137 |
+
1. they are equal, or
|
| 138 |
+
2. the number of features in bn is 1
|
| 139 |
+
Otherwise, skip the folding path
|
| 140 |
+
"""
|
| 141 |
+
if linear.out_features != bn.num_features and bn.num_features != 1:
|
| 142 |
+
raise AssertionError(
|
| 143 |
+
f"To fuse, linear.out_features == bn.num_features or bn.num_features == 1, "
|
| 144 |
+
f"got linear.out_features={linear.out_features} and bn.num_features={bn.num_features}"
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
if bn.running_mean is None or bn.running_var is None:
|
| 148 |
+
raise AssertionError("bn.running_mean and bn.running_var must not be None")
|
| 149 |
+
fused_linear.weight, fused_linear.bias = fuse_linear_bn_weights(
|
| 150 |
+
fused_linear.weight,
|
| 151 |
+
fused_linear.bias,
|
| 152 |
+
bn.running_mean,
|
| 153 |
+
bn.running_var,
|
| 154 |
+
bn.eps,
|
| 155 |
+
bn.weight,
|
| 156 |
+
bn.bias,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
return fused_linear
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def fuse_linear_bn_weights(
|
| 163 |
+
linear_w: torch.Tensor,
|
| 164 |
+
linear_b: torch.Tensor | None,
|
| 165 |
+
bn_rm: torch.Tensor,
|
| 166 |
+
bn_rv: torch.Tensor,
|
| 167 |
+
bn_eps: float,
|
| 168 |
+
bn_w: torch.Tensor,
|
| 169 |
+
bn_b: torch.Tensor,
|
| 170 |
+
) -> tuple[torch.nn.Parameter, torch.nn.Parameter]:
|
| 171 |
+
r"""Fuse linear module parameters and BatchNorm module parameters into new linear module parameters.
|
| 172 |
+
|
| 173 |
+
Args:
|
| 174 |
+
linear_w (torch.Tensor): Linear weight.
|
| 175 |
+
linear_b (Optional[torch.Tensor]): Linear bias.
|
| 176 |
+
bn_rm (torch.Tensor): BatchNorm running mean.
|
| 177 |
+
bn_rv (torch.Tensor): BatchNorm running variance.
|
| 178 |
+
bn_eps (float): BatchNorm epsilon.
|
| 179 |
+
bn_w (torch.Tensor): BatchNorm weight.
|
| 180 |
+
bn_b (torch.Tensor): BatchNorm bias.
|
| 181 |
+
|
| 182 |
+
Returns:
|
| 183 |
+
Tuple[torch.nn.Parameter, torch.nn.Parameter]: Fused linear weight and bias.
|
| 184 |
+
"""
|
| 185 |
+
linear_weight_dtype = linear_w.dtype
|
| 186 |
+
linear_bias_dtype = linear_b.dtype if linear_b is not None else linear_weight_dtype
|
| 187 |
+
if linear_b is None:
|
| 188 |
+
linear_b = torch.zeros_like(bn_rm)
|
| 189 |
+
bn_scale = bn_w * torch.rsqrt(bn_rv + bn_eps)
|
| 190 |
+
|
| 191 |
+
fused_w = linear_w * bn_scale.unsqueeze(-1).to(dtype=linear_weight_dtype)
|
| 192 |
+
fused_b = ((linear_b - bn_rm) * bn_scale + bn_b).to(dtype=linear_bias_dtype)
|
| 193 |
+
|
| 194 |
+
return torch.nn.Parameter(fused_w, linear_w.requires_grad), torch.nn.Parameter(
|
| 195 |
+
fused_b, linear_b.requires_grad
|
| 196 |
+
)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/init.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import inspect
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def skip_init(module_cls, *args, **kwargs):
|
| 8 |
+
r"""
|
| 9 |
+
Given a module class object and args / kwargs, instantiate the module without initializing parameters / buffers.
|
| 10 |
+
|
| 11 |
+
This can be useful if initialization is slow or if custom initialization will
|
| 12 |
+
be performed, making the default initialization unnecessary. There are some caveats to this, due to
|
| 13 |
+
the way this function is implemented:
|
| 14 |
+
|
| 15 |
+
1. The module must accept a `device` arg in its constructor that is passed to any parameters
|
| 16 |
+
or buffers created during construction.
|
| 17 |
+
|
| 18 |
+
2. The module must not perform any computation on parameters in its constructor except
|
| 19 |
+
initialization (i.e. functions from :mod:`torch.nn.init`).
|
| 20 |
+
|
| 21 |
+
If these conditions are satisfied, the module can be instantiated with parameter / buffer values
|
| 22 |
+
uninitialized, as if having been created using :func:`torch.empty`.
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
module_cls: Class object; should be a subclass of :class:`torch.nn.Module`
|
| 26 |
+
args: args to pass to the module's constructor
|
| 27 |
+
kwargs: kwargs to pass to the module's constructor
|
| 28 |
+
|
| 29 |
+
Returns:
|
| 30 |
+
Instantiated module with uninitialized parameters / buffers
|
| 31 |
+
|
| 32 |
+
Example::
|
| 33 |
+
|
| 34 |
+
>>> # xdoctest: +IGNORE_WANT("non-deterministic")
|
| 35 |
+
>>> import torch
|
| 36 |
+
>>> m = torch.nn.utils.skip_init(torch.nn.Linear, 5, 1)
|
| 37 |
+
>>> m.weight
|
| 38 |
+
Parameter containing:
|
| 39 |
+
tensor([[0.0000e+00, 1.5846e+29, 7.8307e+00, 2.5250e-29, 1.1210e-44]],
|
| 40 |
+
requires_grad=True)
|
| 41 |
+
>>> m2 = torch.nn.utils.skip_init(torch.nn.Linear, in_features=6, out_features=1)
|
| 42 |
+
>>> m2.weight
|
| 43 |
+
Parameter containing:
|
| 44 |
+
tensor([[-1.4677e+24, 4.5915e-41, 1.4013e-45, 0.0000e+00, -1.4677e+24,
|
| 45 |
+
4.5915e-41]], requires_grad=True)
|
| 46 |
+
|
| 47 |
+
"""
|
| 48 |
+
if not issubclass(module_cls, torch.nn.Module):
|
| 49 |
+
raise RuntimeError(f"Expected a Module; got {module_cls}")
|
| 50 |
+
if "device" not in inspect.signature(module_cls).parameters:
|
| 51 |
+
raise RuntimeError("Module must support a 'device' arg to skip initialization")
|
| 52 |
+
|
| 53 |
+
final_device = kwargs.pop("device", "cpu")
|
| 54 |
+
kwargs["device"] = "meta"
|
| 55 |
+
return module_cls(*args, **kwargs).to_empty(device=final_device)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/memory_format.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import TypeVar
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
_M = TypeVar("_M", bound="torch.nn.Module")
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def convert_conv2d_weight_memory_format(
|
| 12 |
+
module: _M, memory_format: torch.memory_format
|
| 13 |
+
) -> _M:
|
| 14 |
+
r"""Convert ``memory_format`` of ``nn.Conv2d.weight`` to ``memory_format``.
|
| 15 |
+
|
| 16 |
+
The conversion recursively applies to nested ``nn.Module``, including ``module``.
|
| 17 |
+
Note that it only changes the memory_format, but not the semantics of each dimensions.
|
| 18 |
+
This function is used to facilitate the computation to adopt NHWC kernels, which
|
| 19 |
+
provides considerable speed up for fp16 data on CUDA devices with compute capability >= 7.0
|
| 20 |
+
|
| 21 |
+
.. note::
|
| 22 |
+
Calling ``model.to(memory_format=torch.channels_last)`` is more aggressive
|
| 23 |
+
than the utility function ``convert_conv2d_weight_memory_format``. Any
|
| 24 |
+
layer with 4d weight will be affected by ``model.to``, which does not
|
| 25 |
+
necessarily benefit from conversion to specified ``memory_format``.
|
| 26 |
+
One place we are confident in is that NHWC(channels_last) conversion for
|
| 27 |
+
convolution in cuDNN, as it is beneficial to run convolution in NHWC,
|
| 28 |
+
even in cases where we have to apply permutation to input tensors.
|
| 29 |
+
|
| 30 |
+
Hence our strategy here is to convert only the weight of convolution to
|
| 31 |
+
channels_last. This ensures that;
|
| 32 |
+
1. Fast convolution kernels will be used, the benefit of which could
|
| 33 |
+
outweigh overhead of permutation (if input is not in the same format).
|
| 34 |
+
2. No unnecessary permutations are applied on layers that do not benefit
|
| 35 |
+
from memory_format conversion.
|
| 36 |
+
|
| 37 |
+
The optimal case is that, layers between convolution layers are channels
|
| 38 |
+
last compatible. Input tensor would be permuted to channels last when it
|
| 39 |
+
encounters the first convolution layer and stay in that memory format.
|
| 40 |
+
Hence following convolutions will not need to permute its input tensor.
|
| 41 |
+
|
| 42 |
+
In case where a channels last incompatible layer is between convolution
|
| 43 |
+
layers, we need to permute the input tensor back to contiguous format
|
| 44 |
+
for that layer. The input tensor will go through the remaining layers in
|
| 45 |
+
contiguous format and be permuted to channels last when it encounters
|
| 46 |
+
another convolution layer. There's no point in propagating that
|
| 47 |
+
permutation to an earlier layer, as most layers are quite agnostic to
|
| 48 |
+
``memory_format``.
|
| 49 |
+
|
| 50 |
+
This claim might change when PyTorch supports fusion of permutation, as
|
| 51 |
+
there might have been a better spot to fuse the permutation other than
|
| 52 |
+
immediately before a convolution.
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
module (nn.Module): ``nn.Conv2d`` & ``nn.ConvTranspose2d`` or container
|
| 56 |
+
``nn.Module``
|
| 57 |
+
memory_format: user specified ``memory_format``,
|
| 58 |
+
e.g. ``torch.channels_last`` or ``torch.contiguous_format``
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
The original module with updated ``nn.Conv2d``
|
| 62 |
+
|
| 63 |
+
Example:
|
| 64 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_CUDA)
|
| 65 |
+
>>> # xdoctest: +REQUIRES(env:CUBLAS_WORKSPACE_CONFIG)
|
| 66 |
+
>>> input = torch.randint(
|
| 67 |
+
... 1, 10, (2, 8, 4, 4), dtype=torch.float16, device="cuda"
|
| 68 |
+
... )
|
| 69 |
+
>>> model = nn.Sequential(
|
| 70 |
+
>>> nn.Conv2d(8, 4, 3)).cuda().half()
|
| 71 |
+
>>> # This is identical to:
|
| 72 |
+
>>> # nn.utils.convert_conv2d_weight_memory_format(model, torch.channels_last)
|
| 73 |
+
>>> model = nn.utils.convert_conv2d_weight_memory_format(
|
| 74 |
+
... model, torch.channels_last
|
| 75 |
+
... )
|
| 76 |
+
>>> out = model(input)
|
| 77 |
+
"""
|
| 78 |
+
# TODO: expand this to `_ConvNd` when channels_last support is extended
|
| 79 |
+
# beyond only 4d tensors.
|
| 80 |
+
if isinstance(module, (torch.nn.Conv2d, torch.nn.ConvTranspose2d)):
|
| 81 |
+
weight_data = module.weight.detach().clone(memory_format=memory_format)
|
| 82 |
+
module.weight.data = weight_data.resize_(
|
| 83 |
+
weight_data.size(), memory_format=memory_format
|
| 84 |
+
)
|
| 85 |
+
for child in module.children():
|
| 86 |
+
convert_conv2d_weight_memory_format(child, memory_format)
|
| 87 |
+
|
| 88 |
+
return module
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def convert_conv3d_weight_memory_format(
|
| 92 |
+
module: _M, memory_format: torch.memory_format
|
| 93 |
+
) -> _M:
|
| 94 |
+
r"""Convert ``memory_format`` of ``nn.Conv3d.weight`` to ``memory_format``
|
| 95 |
+
The conversion recursively applies to nested ``nn.Module``, including ``module``.
|
| 96 |
+
Note that it only changes the memory_format, but not the semantics of each dimensions.
|
| 97 |
+
This function is used to facilitate the computation to adopt NHWC kernels, which
|
| 98 |
+
provides considerable speed up for fp16 data on CUDA devices with compute capability >= 7.0
|
| 99 |
+
|
| 100 |
+
.. note::
|
| 101 |
+
Calling ``model.to(memory_format=torch.channels_last_3d)`` is more aggressive
|
| 102 |
+
than the utility function ``convert_conv3d_weight_memory_format``. Any
|
| 103 |
+
layer with 4d weight will be affected by ``model.to``, which does not
|
| 104 |
+
necessarily benefit from conversion to specified ``memory_format``.
|
| 105 |
+
One place we are confident in is that NDHWC(channels_last_3d) conversion for
|
| 106 |
+
convolution in cuDNN, as it is beneficial to run convolution in NDHWC,
|
| 107 |
+
even in cases where we have to apply permutation to input tensors.
|
| 108 |
+
|
| 109 |
+
Hence our strategy here is to convert only the weight of convolution to
|
| 110 |
+
channels_last_3d. This ensures that;
|
| 111 |
+
1. Fast convolution kernels will be used, the benefit of which could
|
| 112 |
+
outweigh overhead of permutation (if input is not in the same format).
|
| 113 |
+
2. No unnecessary permutations are applied on layers that do not benefit
|
| 114 |
+
from memory_format conversion.
|
| 115 |
+
|
| 116 |
+
The optimal case is that, layers between convolution layers are channels
|
| 117 |
+
last compatible. Input tensor would be permuted to channels last when it
|
| 118 |
+
encounters the first convolution layer and stay in that memory format.
|
| 119 |
+
Hence following convolutions will not need to permute its input tensor.
|
| 120 |
+
|
| 121 |
+
In case where a channels last incompatible layer is between convolution
|
| 122 |
+
layers, we need to permute the input tensor back to contiguous format
|
| 123 |
+
for that layer. The input tensor will go through the remaining layers in
|
| 124 |
+
contiguous format and be permuted to channels last when it encounters
|
| 125 |
+
another convolution layer. There's no point in propagating that
|
| 126 |
+
permutation to an earlier layer, as most layers are quite agnostic to
|
| 127 |
+
``memory_format``.
|
| 128 |
+
|
| 129 |
+
This claim might change when PyTorch supports fusion of permutation, as
|
| 130 |
+
there might have been a better spot to fuse the permutation other than
|
| 131 |
+
immediately before a convolution.
|
| 132 |
+
|
| 133 |
+
Args:
|
| 134 |
+
module (nn.Module): ``nn.Conv3d`` & ``nn.ConvTranspose3d`` or container
|
| 135 |
+
``nn.Module``
|
| 136 |
+
memory_format: user specified ``memory_format``,
|
| 137 |
+
e.g. ``torch.channels_last`` or ``torch.contiguous_format``
|
| 138 |
+
|
| 139 |
+
Returns:
|
| 140 |
+
The original module with updated ``nn.Conv3d``
|
| 141 |
+
|
| 142 |
+
Example:
|
| 143 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_CUDA)
|
| 144 |
+
>>> # xdoctest: +REQUIRES(env:CUBLAS_WORKSPACE_CONFIG)
|
| 145 |
+
>>> input = torch.randint(
|
| 146 |
+
... 1, 10, (2, 8, 4, 4, 4), dtype=torch.float16, device="cuda"
|
| 147 |
+
... )
|
| 148 |
+
>>> model = nn.Sequential(
|
| 149 |
+
>>> nn.Conv3d(8, 4, 3)).cuda().half()
|
| 150 |
+
>>> # This is identical to:
|
| 151 |
+
>>> # nn.utils.convert_conv3d_weight_memory_format(model, torch.channels_last_3d)
|
| 152 |
+
>>> model = nn.utils.convert_conv3d_weight_memory_format(
|
| 153 |
+
... model, torch.channels_last_3d
|
| 154 |
+
... )
|
| 155 |
+
>>> out = model(input)
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
# TODO: expand this to `_ConvNd` when channels_last support is extended
|
| 159 |
+
# beyond only 4d tensors.
|
| 160 |
+
if isinstance(module, (torch.nn.Conv3d, torch.nn.ConvTranspose3d)):
|
| 161 |
+
weight_data = module.weight.detach().clone(memory_format=memory_format)
|
| 162 |
+
module.weight.data = weight_data.resize_(
|
| 163 |
+
weight_data.size(), memory_format=memory_format
|
| 164 |
+
)
|
| 165 |
+
for child in module.children():
|
| 166 |
+
convert_conv3d_weight_memory_format(child, memory_format)
|
| 167 |
+
|
| 168 |
+
return module
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
__all__ = [
|
| 172 |
+
"convert_conv2d_weight_memory_format",
|
| 173 |
+
"convert_conv3d_weight_memory_format",
|
| 174 |
+
]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/parametrizations.py
ADDED
|
@@ -0,0 +1,636 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from enum import auto, Enum
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
from torch import Tensor
|
| 7 |
+
from torch.nn.modules import Module
|
| 8 |
+
from torch.nn.utils import parametrize
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = ["orthogonal", "spectral_norm", "weight_norm"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _is_orthogonal(Q, eps=None):
|
| 15 |
+
n, k = Q.size(-2), Q.size(-1)
|
| 16 |
+
Id = torch.eye(k, dtype=Q.dtype, device=Q.device)
|
| 17 |
+
# A reasonable eps, but not too large
|
| 18 |
+
eps = 10.0 * n * torch.finfo(Q.dtype).eps
|
| 19 |
+
return torch.allclose(Q.mH @ Q, Id, atol=eps)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _make_orthogonal(A):
|
| 23 |
+
"""Assume that A is a tall matrix.
|
| 24 |
+
|
| 25 |
+
Compute the Q factor s.t. A = QR (A may be complex) and diag(R) is real and non-negative.
|
| 26 |
+
"""
|
| 27 |
+
X, tau = torch.geqrf(A)
|
| 28 |
+
Q = torch.linalg.householder_product(X, tau)
|
| 29 |
+
# The diagonal of X is the diagonal of R (which is always real) so we normalise by its signs
|
| 30 |
+
Q *= X.diagonal(dim1=-2, dim2=-1).sgn().unsqueeze(-2)
|
| 31 |
+
return Q
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class _OrthMaps(Enum):
|
| 35 |
+
matrix_exp = auto()
|
| 36 |
+
cayley = auto()
|
| 37 |
+
householder = auto()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class _Orthogonal(Module):
|
| 41 |
+
base: Tensor
|
| 42 |
+
|
| 43 |
+
def __init__(
|
| 44 |
+
self, weight, orthogonal_map: _OrthMaps, *, use_trivialization=True
|
| 45 |
+
) -> None:
|
| 46 |
+
super().__init__()
|
| 47 |
+
|
| 48 |
+
# Note [Householder complex]
|
| 49 |
+
# For complex tensors, it is not possible to compute the tensor `tau` necessary for
|
| 50 |
+
# linalg.householder_product from the reflectors.
|
| 51 |
+
# To see this, note that the reflectors have a shape like:
|
| 52 |
+
# 0 0 0
|
| 53 |
+
# * 0 0
|
| 54 |
+
# * * 0
|
| 55 |
+
# which, for complex matrices, give n(n-1) (real) parameters. Now, you need n^2 parameters
|
| 56 |
+
# to parametrize the unitary matrices. Saving tau on its own does not work either, because
|
| 57 |
+
# not every combination of `(A, tau)` gives a unitary matrix, meaning that if we optimise
|
| 58 |
+
# them as independent tensors we would not maintain the constraint
|
| 59 |
+
# An equivalent reasoning holds for rectangular matrices
|
| 60 |
+
if weight.is_complex() and orthogonal_map == _OrthMaps.householder:
|
| 61 |
+
raise ValueError(
|
| 62 |
+
"The householder parametrization does not support complex tensors."
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
self.shape = weight.shape
|
| 66 |
+
self.orthogonal_map = orthogonal_map
|
| 67 |
+
if use_trivialization:
|
| 68 |
+
self.register_buffer("base", None)
|
| 69 |
+
|
| 70 |
+
def forward(self, X: torch.Tensor) -> torch.Tensor:
|
| 71 |
+
n, k = X.size(-2), X.size(-1)
|
| 72 |
+
transposed = n < k
|
| 73 |
+
if transposed:
|
| 74 |
+
X = X.mT
|
| 75 |
+
n, k = k, n
|
| 76 |
+
# Here n > k and X is a tall matrix
|
| 77 |
+
if (
|
| 78 |
+
self.orthogonal_map == _OrthMaps.matrix_exp
|
| 79 |
+
or self.orthogonal_map == _OrthMaps.cayley
|
| 80 |
+
):
|
| 81 |
+
# We just need n x k - k(k-1)/2 parameters
|
| 82 |
+
X = X.tril()
|
| 83 |
+
if n != k:
|
| 84 |
+
# Embed into a square matrix
|
| 85 |
+
X = torch.cat(
|
| 86 |
+
[X, X.new_zeros(n, n - k).expand(*X.shape[:-2], -1, -1)], dim=-1
|
| 87 |
+
)
|
| 88 |
+
A = X - X.mH
|
| 89 |
+
# A is skew-symmetric (or skew-hermitian)
|
| 90 |
+
if self.orthogonal_map == _OrthMaps.matrix_exp:
|
| 91 |
+
Q = torch.matrix_exp(A)
|
| 92 |
+
elif self.orthogonal_map == _OrthMaps.cayley:
|
| 93 |
+
# Computes the Cayley retraction (I+A/2)(I-A/2)^{-1}
|
| 94 |
+
Id = torch.eye(n, dtype=A.dtype, device=A.device)
|
| 95 |
+
Q = torch.linalg.solve(
|
| 96 |
+
torch.add(Id, A, alpha=-0.5), torch.add(Id, A, alpha=0.5)
|
| 97 |
+
)
|
| 98 |
+
# Q is now orthogonal (or unitary) of size (..., n, n)
|
| 99 |
+
if n != k:
|
| 100 |
+
# pyrefly: ignore [unbound-name]
|
| 101 |
+
Q = Q[..., :k]
|
| 102 |
+
# Q is now the size of the X (albeit perhaps transposed)
|
| 103 |
+
else:
|
| 104 |
+
# X is real here, as we do not support householder with complex numbers
|
| 105 |
+
A = X.tril(diagonal=-1)
|
| 106 |
+
tau = 2.0 / (1.0 + (A * A).sum(dim=-2))
|
| 107 |
+
Q = torch.linalg.householder_product(A, tau)
|
| 108 |
+
# The diagonal of X is 1's and -1's
|
| 109 |
+
# We do not want to differentiate through this or update the diagonal of X hence the casting
|
| 110 |
+
Q = Q * X.diagonal(dim1=-2, dim2=-1).int().unsqueeze(-2)
|
| 111 |
+
|
| 112 |
+
if hasattr(self, "base"):
|
| 113 |
+
# pyrefly: ignore [unbound-name]
|
| 114 |
+
Q = self.base @ Q
|
| 115 |
+
if transposed:
|
| 116 |
+
# pyrefly: ignore [unbound-name]
|
| 117 |
+
Q = Q.mT
|
| 118 |
+
return Q # type: ignore[possibly-undefined]
|
| 119 |
+
|
| 120 |
+
@torch.autograd.no_grad()
|
| 121 |
+
def right_inverse(self, Q: torch.Tensor) -> torch.Tensor:
|
| 122 |
+
if Q.shape != self.shape:
|
| 123 |
+
raise ValueError(
|
| 124 |
+
f"Expected a matrix or batch of matrices of shape {self.shape}. "
|
| 125 |
+
f"Got a tensor of shape {Q.shape}."
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
Q_init = Q
|
| 129 |
+
n, k = Q.size(-2), Q.size(-1)
|
| 130 |
+
transpose = n < k
|
| 131 |
+
if transpose:
|
| 132 |
+
Q = Q.mT
|
| 133 |
+
n, k = k, n
|
| 134 |
+
|
| 135 |
+
# We always make sure to always copy Q in every path
|
| 136 |
+
if not hasattr(self, "base"):
|
| 137 |
+
# Note [right_inverse expm cayley]
|
| 138 |
+
# If we do not have use_trivialization=True, we just implement the inverse of the forward
|
| 139 |
+
# map for the Householder. To see why, think that for the Cayley map,
|
| 140 |
+
# we would need to find the matrix X \in R^{n x k} such that:
|
| 141 |
+
# Y = torch.cat([X.tril(), X.new_zeros(n, n - k).expand(*X.shape[:-2], -1, -1)], dim=-1)
|
| 142 |
+
# A = Y - Y.mH
|
| 143 |
+
# cayley(A)[:, :k]
|
| 144 |
+
# gives the original tensor. It is not clear how to do this.
|
| 145 |
+
# Perhaps via some algebraic manipulation involving the QR like that of
|
| 146 |
+
# Corollary 2.2 in Edelman, Arias and Smith?
|
| 147 |
+
if (
|
| 148 |
+
self.orthogonal_map == _OrthMaps.cayley
|
| 149 |
+
or self.orthogonal_map == _OrthMaps.matrix_exp
|
| 150 |
+
):
|
| 151 |
+
raise NotImplementedError(
|
| 152 |
+
"It is not possible to assign to the matrix exponential "
|
| 153 |
+
"or the Cayley parametrizations when use_trivialization=False."
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
# If parametrization == _OrthMaps.householder, make Q orthogonal via the QR decomposition.
|
| 157 |
+
# Here Q is always real because we do not support householder and complex matrices.
|
| 158 |
+
# See note [Householder complex]
|
| 159 |
+
A, tau = torch.geqrf(Q)
|
| 160 |
+
# We want to have a decomposition X = QR with diag(R) > 0, as otherwise we could
|
| 161 |
+
# decompose an orthogonal matrix Q as Q = (-Q)@(-Id), which is a valid QR decomposition
|
| 162 |
+
# The diagonal of Q is the diagonal of R from the qr decomposition
|
| 163 |
+
A.diagonal(dim1=-2, dim2=-1).sign_()
|
| 164 |
+
# Equality with zero is ok because LAPACK returns exactly zero when it does not want
|
| 165 |
+
# to use a particular reflection
|
| 166 |
+
A.diagonal(dim1=-2, dim2=-1)[tau == 0.0] *= -1
|
| 167 |
+
return A.mT if transpose else A
|
| 168 |
+
else:
|
| 169 |
+
if n == k:
|
| 170 |
+
# We check whether Q is orthogonal
|
| 171 |
+
if not _is_orthogonal(Q):
|
| 172 |
+
Q = _make_orthogonal(Q)
|
| 173 |
+
else: # Is orthogonal
|
| 174 |
+
Q = Q.clone()
|
| 175 |
+
else:
|
| 176 |
+
# Complete Q into a full n x n orthogonal matrix
|
| 177 |
+
N = torch.randn(
|
| 178 |
+
*(Q.size()[:-2] + (n, n - k)), dtype=Q.dtype, device=Q.device
|
| 179 |
+
)
|
| 180 |
+
Q = torch.cat([Q, N], dim=-1)
|
| 181 |
+
Q = _make_orthogonal(Q)
|
| 182 |
+
self.base = Q
|
| 183 |
+
|
| 184 |
+
# It is necessary to return the -Id, as we use the diagonal for the
|
| 185 |
+
# Householder parametrization. Using -Id makes:
|
| 186 |
+
# householder(torch.zeros(m,n)) == torch.eye(m,n)
|
| 187 |
+
# Poor man's version of eye_like
|
| 188 |
+
neg_Id = torch.zeros_like(Q_init)
|
| 189 |
+
neg_Id.diagonal(dim1=-2, dim2=-1).fill_(-1.0)
|
| 190 |
+
return neg_Id
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def orthogonal(
|
| 194 |
+
module: Module,
|
| 195 |
+
name: str = "weight",
|
| 196 |
+
orthogonal_map: str | None = None,
|
| 197 |
+
*,
|
| 198 |
+
use_trivialization: bool = True,
|
| 199 |
+
) -> Module:
|
| 200 |
+
r"""Apply an orthogonal or unitary parametrization to a matrix or a batch of matrices.
|
| 201 |
+
|
| 202 |
+
Letting :math:`\mathbb{K}` be :math:`\mathbb{R}` or :math:`\mathbb{C}`, the parametrized
|
| 203 |
+
matrix :math:`Q \in \mathbb{K}^{m \times n}` is **orthogonal** as
|
| 204 |
+
|
| 205 |
+
.. math::
|
| 206 |
+
|
| 207 |
+
\begin{align*}
|
| 208 |
+
Q^{\text{H}}Q &= \mathrm{I}_n \mathrlap{\qquad \text{if }m \geq n}\\
|
| 209 |
+
QQ^{\text{H}} &= \mathrm{I}_m \mathrlap{\qquad \text{if }m < n}
|
| 210 |
+
\end{align*}
|
| 211 |
+
|
| 212 |
+
where :math:`Q^{\text{H}}` is the conjugate transpose when :math:`Q` is complex
|
| 213 |
+
and the transpose when :math:`Q` is real-valued, and
|
| 214 |
+
:math:`\mathrm{I}_n` is the `n`-dimensional identity matrix.
|
| 215 |
+
In plain words, :math:`Q` will have orthonormal columns whenever :math:`m \geq n`
|
| 216 |
+
and orthonormal rows otherwise.
|
| 217 |
+
|
| 218 |
+
If the tensor has more than two dimensions, we consider it as a batch of matrices of shape `(..., m, n)`.
|
| 219 |
+
|
| 220 |
+
The matrix :math:`Q` may be parametrized via three different ``orthogonal_map`` in terms of the original tensor:
|
| 221 |
+
|
| 222 |
+
- ``"matrix_exp"``/``"cayley"``:
|
| 223 |
+
the :func:`~torch.matrix_exp` :math:`Q = \exp(A)` and the `Cayley map`_
|
| 224 |
+
:math:`Q = (\mathrm{I}_n + A/2)(\mathrm{I}_n - A/2)^{-1}` are applied to a skew-symmetric
|
| 225 |
+
:math:`A` to give an orthogonal matrix.
|
| 226 |
+
- ``"householder"``: computes a product of Householder reflectors
|
| 227 |
+
(:func:`~torch.linalg.householder_product`).
|
| 228 |
+
|
| 229 |
+
``"matrix_exp"``/``"cayley"`` often make the parametrized weight converge faster than
|
| 230 |
+
``"householder"``, but they are slower to compute for very thin or very wide matrices.
|
| 231 |
+
|
| 232 |
+
If ``use_trivialization=True`` (default), the parametrization implements the "Dynamic Trivialization Framework",
|
| 233 |
+
where an extra matrix :math:`B \in \mathbb{K}^{n \times n}` is stored under
|
| 234 |
+
``module.parametrizations.weight[0].base``. This helps the
|
| 235 |
+
convergence of the parametrized layer at the expense of some extra memory use.
|
| 236 |
+
See `Trivializations for Gradient-Based Optimization on Manifolds`_ .
|
| 237 |
+
|
| 238 |
+
Initial value of :math:`Q`:
|
| 239 |
+
If the original tensor is not parametrized and ``use_trivialization=True`` (default), the initial value
|
| 240 |
+
of :math:`Q` is that of the original tensor if it is orthogonal (or unitary in the complex case)
|
| 241 |
+
and it is orthogonalized via the QR decomposition otherwise (see :func:`torch.linalg.qr`).
|
| 242 |
+
Same happens when it is not parametrized and ``orthogonal_map="householder"`` even when ``use_trivialization=False``.
|
| 243 |
+
Otherwise, the initial value is the result of the composition of all the registered
|
| 244 |
+
parametrizations applied to the original tensor.
|
| 245 |
+
|
| 246 |
+
.. note::
|
| 247 |
+
This function is implemented using the parametrization functionality
|
| 248 |
+
in :func:`~torch.nn.utils.parametrize.register_parametrization`.
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
.. _`Cayley map`: https://en.wikipedia.org/wiki/Cayley_transform#Matrix_map
|
| 252 |
+
.. _`Trivializations for Gradient-Based Optimization on Manifolds`: https://arxiv.org/abs/1909.09501
|
| 253 |
+
|
| 254 |
+
Args:
|
| 255 |
+
module (nn.Module): module on which to register the parametrization.
|
| 256 |
+
name (str, optional): name of the tensor to make orthogonal. Default: ``"weight"``.
|
| 257 |
+
orthogonal_map (str, optional): One of the following: ``"matrix_exp"``, ``"cayley"``, ``"householder"``.
|
| 258 |
+
Default: ``"matrix_exp"`` if the matrix is square or complex, ``"householder"`` otherwise.
|
| 259 |
+
use_trivialization (bool, optional): whether to use the dynamic trivialization framework.
|
| 260 |
+
Default: ``True``.
|
| 261 |
+
|
| 262 |
+
Returns:
|
| 263 |
+
The original module with an orthogonal parametrization registered to the specified
|
| 264 |
+
weight
|
| 265 |
+
|
| 266 |
+
Example::
|
| 267 |
+
|
| 268 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_LAPACK)
|
| 269 |
+
>>> orth_linear = orthogonal(nn.Linear(20, 40))
|
| 270 |
+
>>> orth_linear
|
| 271 |
+
ParametrizedLinear(
|
| 272 |
+
in_features=20, out_features=40, bias=True
|
| 273 |
+
(parametrizations): ModuleDict(
|
| 274 |
+
(weight): ParametrizationList(
|
| 275 |
+
(0): _Orthogonal()
|
| 276 |
+
)
|
| 277 |
+
)
|
| 278 |
+
)
|
| 279 |
+
>>> # xdoctest: +IGNORE_WANT
|
| 280 |
+
>>> Q = orth_linear.weight
|
| 281 |
+
>>> torch.dist(Q.T @ Q, torch.eye(20))
|
| 282 |
+
tensor(4.9332e-07)
|
| 283 |
+
"""
|
| 284 |
+
weight = getattr(module, name, None)
|
| 285 |
+
if not isinstance(weight, Tensor):
|
| 286 |
+
raise ValueError(
|
| 287 |
+
f"Module '{module}' has no parameter or buffer with name '{name}'"
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
# We could implement this for 1-dim tensors as the maps on the sphere
|
| 291 |
+
# but I believe it'd bite more people than it'd help
|
| 292 |
+
if weight.ndim < 2:
|
| 293 |
+
raise ValueError(
|
| 294 |
+
"Expected a matrix or batch of matrices. "
|
| 295 |
+
f"Got a tensor of {weight.ndim} dimensions."
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
if orthogonal_map is None:
|
| 299 |
+
orthogonal_map = (
|
| 300 |
+
"matrix_exp"
|
| 301 |
+
if weight.size(-2) == weight.size(-1) or weight.is_complex()
|
| 302 |
+
else "householder"
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
orth_enum = getattr(_OrthMaps, orthogonal_map, None)
|
| 306 |
+
if orth_enum is None:
|
| 307 |
+
raise ValueError(
|
| 308 |
+
'orthogonal_map has to be one of "matrix_exp", "cayley", "householder". '
|
| 309 |
+
f"Got: {orthogonal_map}"
|
| 310 |
+
)
|
| 311 |
+
orth = _Orthogonal(weight, orth_enum, use_trivialization=use_trivialization)
|
| 312 |
+
parametrize.register_parametrization(module, name, orth, unsafe=True)
|
| 313 |
+
return module
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
class _WeightNorm(Module):
|
| 317 |
+
def __init__(
|
| 318 |
+
self,
|
| 319 |
+
dim: int | None = 0,
|
| 320 |
+
) -> None:
|
| 321 |
+
super().__init__()
|
| 322 |
+
if dim is None:
|
| 323 |
+
dim = -1
|
| 324 |
+
self.dim = dim
|
| 325 |
+
|
| 326 |
+
def forward(self, weight_g, weight_v):
|
| 327 |
+
return torch._weight_norm(weight_v, weight_g, self.dim)
|
| 328 |
+
|
| 329 |
+
def right_inverse(self, weight):
|
| 330 |
+
weight_g = torch.norm_except_dim(weight, 2, self.dim)
|
| 331 |
+
weight_v = weight
|
| 332 |
+
|
| 333 |
+
return weight_g, weight_v
|
| 334 |
+
|
| 335 |
+
|
| 336 |
+
def weight_norm(module: Module, name: str = "weight", dim: int = 0):
|
| 337 |
+
r"""Apply weight normalization to a parameter in the given module.
|
| 338 |
+
|
| 339 |
+
.. math::
|
| 340 |
+
\mathbf{w} = g \dfrac{\mathbf{v}}{\|\mathbf{v}\|}
|
| 341 |
+
|
| 342 |
+
Weight normalization is a reparameterization that decouples the magnitude
|
| 343 |
+
of a weight tensor from its direction. This replaces the parameter specified
|
| 344 |
+
by :attr:`name` with two parameters: one specifying the magnitude
|
| 345 |
+
and one specifying the direction.
|
| 346 |
+
|
| 347 |
+
By default, with ``dim=0``, the norm is computed independently per output
|
| 348 |
+
channel/plane. To compute a norm over the entire weight tensor, use
|
| 349 |
+
``dim=None``.
|
| 350 |
+
|
| 351 |
+
See https://arxiv.org/abs/1602.07868
|
| 352 |
+
|
| 353 |
+
Args:
|
| 354 |
+
module (Module): containing module
|
| 355 |
+
name (str, optional): name of weight parameter
|
| 356 |
+
dim (int, optional): dimension over which to compute the norm
|
| 357 |
+
|
| 358 |
+
Returns:
|
| 359 |
+
The original module with the weight norm hook
|
| 360 |
+
|
| 361 |
+
Example::
|
| 362 |
+
|
| 363 |
+
>>> m = weight_norm(nn.Linear(20, 40), name='weight')
|
| 364 |
+
>>> m
|
| 365 |
+
ParametrizedLinear(
|
| 366 |
+
in_features=20, out_features=40, bias=True
|
| 367 |
+
(parametrizations): ModuleDict(
|
| 368 |
+
(weight): ParametrizationList(
|
| 369 |
+
(0): _WeightNorm()
|
| 370 |
+
)
|
| 371 |
+
)
|
| 372 |
+
)
|
| 373 |
+
>>> m.parametrizations.weight.original0.size()
|
| 374 |
+
torch.Size([40, 1])
|
| 375 |
+
>>> m.parametrizations.weight.original1.size()
|
| 376 |
+
torch.Size([40, 20])
|
| 377 |
+
|
| 378 |
+
"""
|
| 379 |
+
_weight_norm = _WeightNorm(dim)
|
| 380 |
+
parametrize.register_parametrization(module, name, _weight_norm, unsafe=True)
|
| 381 |
+
|
| 382 |
+
def _weight_norm_compat_hook(
|
| 383 |
+
state_dict,
|
| 384 |
+
prefix,
|
| 385 |
+
local_metadata,
|
| 386 |
+
strict,
|
| 387 |
+
missing_keys,
|
| 388 |
+
unexpected_keys,
|
| 389 |
+
error_msgs,
|
| 390 |
+
) -> None:
|
| 391 |
+
g_key = f"{prefix}{name}_g"
|
| 392 |
+
v_key = f"{prefix}{name}_v"
|
| 393 |
+
if g_key in state_dict and v_key in state_dict:
|
| 394 |
+
original0 = state_dict.pop(g_key)
|
| 395 |
+
original1 = state_dict.pop(v_key)
|
| 396 |
+
state_dict[f"{prefix}parametrizations.{name}.original0"] = original0
|
| 397 |
+
state_dict[f"{prefix}parametrizations.{name}.original1"] = original1
|
| 398 |
+
|
| 399 |
+
module._register_load_state_dict_pre_hook(_weight_norm_compat_hook)
|
| 400 |
+
return module
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
class _SpectralNorm(Module):
|
| 404 |
+
def __init__(
|
| 405 |
+
self,
|
| 406 |
+
weight: torch.Tensor,
|
| 407 |
+
n_power_iterations: int = 1,
|
| 408 |
+
dim: int = 0,
|
| 409 |
+
eps: float = 1e-12,
|
| 410 |
+
) -> None:
|
| 411 |
+
super().__init__()
|
| 412 |
+
ndim = weight.ndim
|
| 413 |
+
if dim >= ndim or dim < -ndim:
|
| 414 |
+
raise IndexError(
|
| 415 |
+
"Dimension out of range (expected to be in range of "
|
| 416 |
+
f"[-{ndim}, {ndim - 1}] but got {dim})"
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
if n_power_iterations <= 0:
|
| 420 |
+
raise ValueError(
|
| 421 |
+
"Expected n_power_iterations to be positive, but "
|
| 422 |
+
f"got n_power_iterations={n_power_iterations}"
|
| 423 |
+
)
|
| 424 |
+
self.dim = dim if dim >= 0 else dim + ndim
|
| 425 |
+
self.eps = eps
|
| 426 |
+
if ndim > 1:
|
| 427 |
+
# For ndim == 1 we do not need to approximate anything (see _SpectralNorm.forward)
|
| 428 |
+
self.n_power_iterations = n_power_iterations
|
| 429 |
+
weight_mat = self._reshape_weight_to_matrix(weight)
|
| 430 |
+
h, w = weight_mat.size()
|
| 431 |
+
|
| 432 |
+
u = weight_mat.new_empty(h).normal_(0, 1)
|
| 433 |
+
v = weight_mat.new_empty(w).normal_(0, 1)
|
| 434 |
+
self.register_buffer("_u", F.normalize(u, dim=0, eps=self.eps))
|
| 435 |
+
self.register_buffer("_v", F.normalize(v, dim=0, eps=self.eps))
|
| 436 |
+
|
| 437 |
+
# Start with u, v initialized to some reasonable values by performing a number
|
| 438 |
+
# of iterations of the power method
|
| 439 |
+
self._power_method(weight_mat, 15)
|
| 440 |
+
|
| 441 |
+
def _reshape_weight_to_matrix(self, weight: torch.Tensor) -> torch.Tensor:
|
| 442 |
+
# Precondition
|
| 443 |
+
if weight.ndim <= 1:
|
| 444 |
+
raise AssertionError(
|
| 445 |
+
f"Expected weight to have more than 1 dimension, got {weight.ndim}"
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
if self.dim != 0:
|
| 449 |
+
# permute dim to front
|
| 450 |
+
weight = weight.permute(
|
| 451 |
+
self.dim, *(d for d in range(weight.dim()) if d != self.dim)
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
return weight.flatten(1)
|
| 455 |
+
|
| 456 |
+
@torch.autograd.no_grad()
|
| 457 |
+
def _power_method(self, weight_mat: torch.Tensor, n_power_iterations: int) -> None:
|
| 458 |
+
# See original note at torch/nn/utils/spectral_norm.py
|
| 459 |
+
# NB: If `do_power_iteration` is set, the `u` and `v` vectors are
|
| 460 |
+
# updated in power iteration **in-place**. This is very important
|
| 461 |
+
# because in `DataParallel` forward, the vectors (being buffers) are
|
| 462 |
+
# broadcast from the parallelized module to each module replica,
|
| 463 |
+
# which is a new module object created on the fly. And each replica
|
| 464 |
+
# runs its own spectral norm power iteration. So simply assigning
|
| 465 |
+
# the updated vectors to the module this function runs on will cause
|
| 466 |
+
# the update to be lost forever. And the next time the parallelized
|
| 467 |
+
# module is replicated, the same randomly initialized vectors are
|
| 468 |
+
# broadcast and used!
|
| 469 |
+
#
|
| 470 |
+
# Therefore, to make the change propagate back, we rely on two
|
| 471 |
+
# important behaviors (also enforced via tests):
|
| 472 |
+
# 1. `DataParallel` doesn't clone storage if the broadcast tensor
|
| 473 |
+
# is already on correct device; and it makes sure that the
|
| 474 |
+
# parallelized module is already on `device[0]`.
|
| 475 |
+
# 2. If the out tensor in `out=` kwarg has correct shape, it will
|
| 476 |
+
# just fill in the values.
|
| 477 |
+
# Therefore, since the same power iteration is performed on all
|
| 478 |
+
# devices, simply updating the tensors in-place will make sure that
|
| 479 |
+
# the module replica on `device[0]` will update the _u vector on the
|
| 480 |
+
# parallelized module (by shared storage).
|
| 481 |
+
#
|
| 482 |
+
# However, after we update `u` and `v` in-place, we need to **clone**
|
| 483 |
+
# them before using them to normalize the weight. This is to support
|
| 484 |
+
# backproping through two forward passes, e.g., the common pattern in
|
| 485 |
+
# GAN training: loss = D(real) - D(fake). Otherwise, engine will
|
| 486 |
+
# complain that variables needed to do backward for the first forward
|
| 487 |
+
# (i.e., the `u` and `v` vectors) are changed in the second forward.
|
| 488 |
+
|
| 489 |
+
# Precondition
|
| 490 |
+
if weight_mat.ndim <= 1:
|
| 491 |
+
raise AssertionError(
|
| 492 |
+
f"Expected weight_mat to have more than 1 dimension, got {weight_mat.ndim}"
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
for _ in range(n_power_iterations):
|
| 496 |
+
# Spectral norm of weight equals to `u^T W v`, where `u` and `v`
|
| 497 |
+
# are the first left and right singular vectors.
|
| 498 |
+
# This power iteration produces approximations of `u` and `v`.
|
| 499 |
+
self._u = F.normalize(
|
| 500 |
+
torch.mv(weight_mat, self._v), # type: ignore[has-type]
|
| 501 |
+
dim=0,
|
| 502 |
+
eps=self.eps,
|
| 503 |
+
out=self._u, # type: ignore[has-type]
|
| 504 |
+
)
|
| 505 |
+
self._v = F.normalize(
|
| 506 |
+
torch.mv(weight_mat.H, self._u), # type: ignore[has-type]
|
| 507 |
+
dim=0,
|
| 508 |
+
eps=self.eps,
|
| 509 |
+
out=self._v, # type: ignore[has-type]
|
| 510 |
+
)
|
| 511 |
+
|
| 512 |
+
def forward(self, weight: torch.Tensor) -> torch.Tensor:
|
| 513 |
+
if weight.ndim == 1:
|
| 514 |
+
# Faster and more exact path, no need to approximate anything
|
| 515 |
+
return F.normalize(weight, dim=0, eps=self.eps)
|
| 516 |
+
else:
|
| 517 |
+
weight_mat = self._reshape_weight_to_matrix(weight)
|
| 518 |
+
if self.training:
|
| 519 |
+
self._power_method(weight_mat, self.n_power_iterations)
|
| 520 |
+
# See above on why we need to clone
|
| 521 |
+
u = self._u.clone(memory_format=torch.contiguous_format)
|
| 522 |
+
v = self._v.clone(memory_format=torch.contiguous_format)
|
| 523 |
+
# The proper way of computing this should be through F.bilinear, but
|
| 524 |
+
# it seems to have some efficiency issues:
|
| 525 |
+
# https://github.com/pytorch/pytorch/issues/58093
|
| 526 |
+
sigma = torch.vdot(u, torch.mv(weight_mat, v))
|
| 527 |
+
return weight / sigma
|
| 528 |
+
|
| 529 |
+
def right_inverse(self, value: torch.Tensor) -> torch.Tensor:
|
| 530 |
+
# we may want to assert here that the passed value already
|
| 531 |
+
# satisfies constraints
|
| 532 |
+
return value
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
def spectral_norm(
|
| 536 |
+
module: Module,
|
| 537 |
+
name: str = "weight",
|
| 538 |
+
n_power_iterations: int = 1,
|
| 539 |
+
eps: float = 1e-12,
|
| 540 |
+
dim: int | None = None,
|
| 541 |
+
) -> Module:
|
| 542 |
+
r"""Apply spectral normalization to a parameter in the given module.
|
| 543 |
+
|
| 544 |
+
.. math::
|
| 545 |
+
\mathbf{W}_{SN} = \dfrac{\mathbf{W}}{\sigma(\mathbf{W})},
|
| 546 |
+
\sigma(\mathbf{W}) = \max_{\mathbf{h}: \mathbf{h} \ne 0} \dfrac{\|\mathbf{W} \mathbf{h}\|_2}{\|\mathbf{h}\|_2}
|
| 547 |
+
|
| 548 |
+
When applied on a vector, it simplifies to
|
| 549 |
+
|
| 550 |
+
.. math::
|
| 551 |
+
\mathbf{x}_{SN} = \dfrac{\mathbf{x}}{\|\mathbf{x}\|_2}
|
| 552 |
+
|
| 553 |
+
Spectral normalization stabilizes the training of discriminators (critics)
|
| 554 |
+
in Generative Adversarial Networks (GANs) by reducing the Lipschitz constant
|
| 555 |
+
of the model. :math:`\sigma` is approximated performing one iteration of the
|
| 556 |
+
`power method`_ every time the weight is accessed. If the dimension of the
|
| 557 |
+
weight tensor is greater than 2, it is reshaped to 2D in power iteration
|
| 558 |
+
method to get spectral norm.
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
See `Spectral Normalization for Generative Adversarial Networks`_ .
|
| 562 |
+
|
| 563 |
+
.. _`power method`: https://en.wikipedia.org/wiki/Power_iteration
|
| 564 |
+
.. _`Spectral Normalization for Generative Adversarial Networks`: https://arxiv.org/abs/1802.05957
|
| 565 |
+
|
| 566 |
+
.. note::
|
| 567 |
+
This function is implemented using the parametrization functionality
|
| 568 |
+
in :func:`~torch.nn.utils.parametrize.register_parametrization`. It is a
|
| 569 |
+
reimplementation of :func:`torch.nn.utils.spectral_norm`.
|
| 570 |
+
|
| 571 |
+
.. note::
|
| 572 |
+
When this constraint is registered, the singular vectors associated to the largest
|
| 573 |
+
singular value are estimated rather than sampled at random. These are then updated
|
| 574 |
+
performing :attr:`n_power_iterations` of the `power method`_ whenever the tensor
|
| 575 |
+
is accessed with the module on `training` mode.
|
| 576 |
+
|
| 577 |
+
.. note::
|
| 578 |
+
If the `_SpectralNorm` module, i.e., `module.parametrization.weight[idx]`,
|
| 579 |
+
is in training mode on removal, it will perform another power iteration.
|
| 580 |
+
If you'd like to avoid this iteration, set the module to eval mode
|
| 581 |
+
before its removal.
|
| 582 |
+
|
| 583 |
+
Args:
|
| 584 |
+
module (nn.Module): containing module
|
| 585 |
+
name (str, optional): name of weight parameter. Default: ``"weight"``.
|
| 586 |
+
n_power_iterations (int, optional): number of power iterations to
|
| 587 |
+
calculate spectral norm. Default: ``1``.
|
| 588 |
+
eps (float, optional): epsilon for numerical stability in
|
| 589 |
+
calculating norms. Default: ``1e-12``.
|
| 590 |
+
dim (int, optional): dimension corresponding to number of outputs.
|
| 591 |
+
Default: ``0``, except for modules that are instances of
|
| 592 |
+
ConvTranspose{1,2,3}d, when it is ``1``
|
| 593 |
+
|
| 594 |
+
Returns:
|
| 595 |
+
The original module with a new parametrization registered to the specified
|
| 596 |
+
weight
|
| 597 |
+
|
| 598 |
+
Example::
|
| 599 |
+
|
| 600 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_LAPACK)
|
| 601 |
+
>>> # xdoctest: +IGNORE_WANT("non-deterministic")
|
| 602 |
+
>>> snm = spectral_norm(nn.Linear(20, 40))
|
| 603 |
+
>>> snm
|
| 604 |
+
ParametrizedLinear(
|
| 605 |
+
in_features=20, out_features=40, bias=True
|
| 606 |
+
(parametrizations): ModuleDict(
|
| 607 |
+
(weight): ParametrizationList(
|
| 608 |
+
(0): _SpectralNorm()
|
| 609 |
+
)
|
| 610 |
+
)
|
| 611 |
+
)
|
| 612 |
+
>>> torch.linalg.matrix_norm(snm.weight, 2)
|
| 613 |
+
tensor(1.0081, grad_fn=<AmaxBackward0>)
|
| 614 |
+
"""
|
| 615 |
+
weight = getattr(module, name, None)
|
| 616 |
+
if not isinstance(weight, Tensor):
|
| 617 |
+
raise ValueError(
|
| 618 |
+
f"Module '{module}' has no parameter or buffer with name '{name}'"
|
| 619 |
+
)
|
| 620 |
+
|
| 621 |
+
if dim is None:
|
| 622 |
+
if isinstance(
|
| 623 |
+
module,
|
| 624 |
+
(
|
| 625 |
+
torch.nn.ConvTranspose1d,
|
| 626 |
+
torch.nn.ConvTranspose2d,
|
| 627 |
+
torch.nn.ConvTranspose3d,
|
| 628 |
+
),
|
| 629 |
+
):
|
| 630 |
+
dim = 1
|
| 631 |
+
else:
|
| 632 |
+
dim = 0
|
| 633 |
+
parametrize.register_parametrization(
|
| 634 |
+
module, name, _SpectralNorm(weight, n_power_iterations, dim, eps)
|
| 635 |
+
)
|
| 636 |
+
return module
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/parametrize.py
ADDED
|
@@ -0,0 +1,886 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-decorators
|
| 2 |
+
# mypy: allow-untyped-defs
|
| 3 |
+
import collections
|
| 4 |
+
import copyreg
|
| 5 |
+
from collections.abc import Sequence
|
| 6 |
+
from contextlib import contextmanager
|
| 7 |
+
from copy import deepcopy
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
from torch import Tensor
|
| 11 |
+
from torch.__future__ import get_swap_module_params_on_conversion
|
| 12 |
+
from torch._library.opaque_object import is_opaque_reference_type
|
| 13 |
+
from torch._opaque_base import OpaqueBase
|
| 14 |
+
from torch.nn.modules.container import Module, ModuleDict, ModuleList
|
| 15 |
+
from torch.nn.parameter import Parameter
|
| 16 |
+
from torch.utils._python_dispatch import is_traceable_wrapper_subclass
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
__all__ = [
|
| 20 |
+
"cached",
|
| 21 |
+
"ParametrizationList",
|
| 22 |
+
"register_parametrization",
|
| 23 |
+
"is_parametrized",
|
| 24 |
+
"remove_parametrizations",
|
| 25 |
+
"type_before_parametrizations",
|
| 26 |
+
"transfer_parametrizations_and_params",
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
_cache_enabled = 0
|
| 30 |
+
_cache: dict[tuple[int, str], Tensor | None] = {}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@contextmanager
|
| 34 |
+
def cached():
|
| 35 |
+
r"""Context manager that enables the caching system within parametrizations registered with :func:`register_parametrization`.
|
| 36 |
+
|
| 37 |
+
The value of the parametrized objects is computed and cached the first time
|
| 38 |
+
they are required when this context manager is active. The cached values are
|
| 39 |
+
discarded when leaving the context manager.
|
| 40 |
+
|
| 41 |
+
This is useful when using a parametrized parameter more than once in the forward pass.
|
| 42 |
+
An example of this is when parametrizing the recurrent kernel of an RNN or when
|
| 43 |
+
sharing weights.
|
| 44 |
+
|
| 45 |
+
The simplest way to activate the cache is by wrapping the forward pass of the neural network
|
| 46 |
+
|
| 47 |
+
.. code-block:: python
|
| 48 |
+
|
| 49 |
+
import torch.nn.utils.parametrize as P
|
| 50 |
+
|
| 51 |
+
...
|
| 52 |
+
with P.cached():
|
| 53 |
+
output = model(inputs)
|
| 54 |
+
|
| 55 |
+
in training and evaluation. One may also wrap the parts of the modules that use
|
| 56 |
+
several times the parametrized tensors. For example, the loop of an RNN with a
|
| 57 |
+
parametrized recurrent kernel:
|
| 58 |
+
|
| 59 |
+
.. code-block:: python
|
| 60 |
+
|
| 61 |
+
with P.cached():
|
| 62 |
+
for x in xs:
|
| 63 |
+
out_rnn = self.rnn_cell(x, out_rnn)
|
| 64 |
+
"""
|
| 65 |
+
global _cache
|
| 66 |
+
global _cache_enabled
|
| 67 |
+
_cache_enabled += 1
|
| 68 |
+
try:
|
| 69 |
+
yield
|
| 70 |
+
finally:
|
| 71 |
+
_cache_enabled -= 1
|
| 72 |
+
if not _cache_enabled:
|
| 73 |
+
_cache = {}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _register_parameter_or_buffer(module, name, X) -> None:
|
| 77 |
+
if isinstance(X, Parameter):
|
| 78 |
+
module.register_parameter(name, X)
|
| 79 |
+
else:
|
| 80 |
+
module.register_buffer(name, X)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def _maybe_set(dest: Tensor, src: Tensor) -> None:
|
| 84 |
+
should_swap = (
|
| 85 |
+
get_swap_module_params_on_conversion() or is_traceable_wrapper_subclass(dest)
|
| 86 |
+
)
|
| 87 |
+
if should_swap:
|
| 88 |
+
if isinstance(dest, Parameter) and not isinstance(src, Parameter):
|
| 89 |
+
src = Parameter(src, requires_grad=dest.requires_grad)
|
| 90 |
+
torch.utils.swap_tensors(dest, src)
|
| 91 |
+
else:
|
| 92 |
+
dest.set_(src) # type: ignore[call-overload]
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class ParametrizationList(ModuleList):
|
| 96 |
+
r"""A sequential container that holds and manages the original parameters or buffers of a parametrized :class:`torch.nn.Module`.
|
| 97 |
+
|
| 98 |
+
It is the type of ``module.parametrizations[tensor_name]`` when ``module[tensor_name]``
|
| 99 |
+
has been parametrized with :func:`register_parametrization`.
|
| 100 |
+
|
| 101 |
+
If the first registered parametrization has a ``right_inverse`` that returns one tensor or
|
| 102 |
+
does not have a ``right_inverse`` (in which case we assume that ``right_inverse`` is the identity),
|
| 103 |
+
it will hold the tensor under the name ``original``.
|
| 104 |
+
If it has a ``right_inverse`` that returns more than one tensor, these will be registered as
|
| 105 |
+
``original0``, ``original1``, ...
|
| 106 |
+
|
| 107 |
+
.. warning::
|
| 108 |
+
This class is used internally by :func:`register_parametrization`. It is documented
|
| 109 |
+
here for completeness. It shall not be instantiated by the user.
|
| 110 |
+
|
| 111 |
+
Args:
|
| 112 |
+
modules (sequence): sequence of modules representing the parametrizations
|
| 113 |
+
original (Parameter or Tensor): parameter or buffer that is parametrized
|
| 114 |
+
unsafe (bool): a boolean flag that denotes whether the parametrization
|
| 115 |
+
may change the dtype and shape of the tensor. Default: `False`
|
| 116 |
+
Warning: the parametrization is not checked for consistency upon registration.
|
| 117 |
+
Enable this flag at your own risk.
|
| 118 |
+
"""
|
| 119 |
+
|
| 120 |
+
original: Tensor
|
| 121 |
+
unsafe: bool
|
| 122 |
+
|
| 123 |
+
def __init__(
|
| 124 |
+
self,
|
| 125 |
+
modules: Sequence[Module],
|
| 126 |
+
original: Tensor | Parameter,
|
| 127 |
+
unsafe: bool = False,
|
| 128 |
+
) -> None:
|
| 129 |
+
# We require this because we need to treat differently the first parametrization
|
| 130 |
+
# This should never throw, unless this class is used from the outside
|
| 131 |
+
if len(modules) == 0:
|
| 132 |
+
raise ValueError("ParametrizationList requires one or more modules.")
|
| 133 |
+
|
| 134 |
+
super().__init__(modules)
|
| 135 |
+
self.unsafe = unsafe
|
| 136 |
+
|
| 137 |
+
# In plain words:
|
| 138 |
+
# module.weight must keep its dtype and shape.
|
| 139 |
+
# Furthermore, if there is no right_inverse or the right_inverse returns a tensor,
|
| 140 |
+
# this should be of the same dtype as the original tensor
|
| 141 |
+
#
|
| 142 |
+
# We check that the following invariants hold:
|
| 143 |
+
# X = module.weight
|
| 144 |
+
# Y = param.right_inverse(X)
|
| 145 |
+
# assert isinstance(Y, Tensor) or
|
| 146 |
+
# (isinstance(Y, collections.abc.Sequence) and all(isinstance(t, Tensor) for t in Y))
|
| 147 |
+
# Z = param(Y) if isinstance(Y, Tensor) else param(*Y)
|
| 148 |
+
# # Consistency checks
|
| 149 |
+
# assert X.dtype == Z.dtype and X.shape == Z.shape
|
| 150 |
+
# # If it has one input, this allows to be able to use set_ to be able to
|
| 151 |
+
# # move data to/from the original tensor without changing its id (which is what the
|
| 152 |
+
# # optimizer uses to track parameters)
|
| 153 |
+
# if isinstance(Y, Tensor)
|
| 154 |
+
# assert X.dtype == Y.dtype
|
| 155 |
+
# Below we use original = X, new = Y
|
| 156 |
+
|
| 157 |
+
original_shape = original.shape
|
| 158 |
+
original_dtype = original.dtype
|
| 159 |
+
|
| 160 |
+
# Compute new
|
| 161 |
+
with torch.no_grad():
|
| 162 |
+
new = original
|
| 163 |
+
for module in reversed(self): # type: ignore[call-overload]
|
| 164 |
+
if hasattr(module, "right_inverse"):
|
| 165 |
+
try:
|
| 166 |
+
new = module.right_inverse(new) # type: ignore[operator]
|
| 167 |
+
except NotImplementedError:
|
| 168 |
+
pass
|
| 169 |
+
# else, or if it throws, we assume that right_inverse is the identity
|
| 170 |
+
|
| 171 |
+
if not isinstance(new, Tensor) and not isinstance(new, Sequence):
|
| 172 |
+
raise ValueError(
|
| 173 |
+
"'right_inverse' must return a Tensor or a Sequence of tensors (list, tuple...). "
|
| 174 |
+
f"Got {type(new).__name__}"
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Set the number of original tensors
|
| 178 |
+
self.is_tensor = isinstance(new, Tensor)
|
| 179 |
+
self.ntensors = 1 if self.is_tensor else len(new)
|
| 180 |
+
|
| 181 |
+
# Register the tensor(s)
|
| 182 |
+
if self.is_tensor:
|
| 183 |
+
# pyrefly: ignore [missing-attribute]
|
| 184 |
+
if original.dtype != new.dtype:
|
| 185 |
+
raise ValueError(
|
| 186 |
+
"When `right_inverse` outputs one tensor, it may not change the dtype.\n"
|
| 187 |
+
f"original.dtype: {original.dtype}\n"
|
| 188 |
+
# pyrefly: ignore [missing-attribute]
|
| 189 |
+
f"right_inverse(original).dtype: {new.dtype}"
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
# pyrefly: ignore [missing-attribute]
|
| 193 |
+
if original.device != new.device:
|
| 194 |
+
raise ValueError(
|
| 195 |
+
"When `right_inverse` outputs one tensor, it may not change the device.\n"
|
| 196 |
+
f"original.device: {original.device}\n"
|
| 197 |
+
# pyrefly: ignore [missing-attribute]
|
| 198 |
+
f"right_inverse(original).device: {new.device}"
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# Set the original to original so that the user does not need to re-register the parameter
|
| 202 |
+
# manually in the optimiser
|
| 203 |
+
with torch.no_grad():
|
| 204 |
+
# pyrefly: ignore [bad-argument-type]
|
| 205 |
+
_maybe_set(original, new)
|
| 206 |
+
_register_parameter_or_buffer(self, "original", original)
|
| 207 |
+
else:
|
| 208 |
+
for i, originali in enumerate(new):
|
| 209 |
+
match originali:
|
| 210 |
+
case OpaqueBase():
|
| 211 |
+
if not is_opaque_reference_type(type(originali)):
|
| 212 |
+
raise ValueError(
|
| 213 |
+
f"'right_inverse' must return a Tensor or a reference-type "
|
| 214 |
+
f"opaque. Got element {i} of the sequence with type "
|
| 215 |
+
f"{type(originali).__name__}."
|
| 216 |
+
)
|
| 217 |
+
setattr(self, f"original{i}", originali)
|
| 218 |
+
case Tensor():
|
| 219 |
+
# If the original tensor was a Parameter that required grad, we expect the user to
|
| 220 |
+
# add the new parameters to the optimizer after registering the parametrization
|
| 221 |
+
# (this is documented)
|
| 222 |
+
if isinstance(original, Parameter):
|
| 223 |
+
originali = Parameter(originali, original.requires_grad)
|
| 224 |
+
originali.requires_grad_(original.requires_grad)
|
| 225 |
+
_register_parameter_or_buffer(self, f"original{i}", originali)
|
| 226 |
+
case _:
|
| 227 |
+
raise ValueError(
|
| 228 |
+
"'right_inverse' must return a Tensor or a Sequence of tensors "
|
| 229 |
+
"(list, tuple...). "
|
| 230 |
+
f"Got element {i} of the sequence with type {type(originali).__name__}."
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
if not self.unsafe:
|
| 234 |
+
# Consistency checks:
|
| 235 |
+
# Since f : A -> B, right_inverse : B -> A, Z and original should live in B
|
| 236 |
+
# Z = forward(right_inverse(original))
|
| 237 |
+
Z = self()
|
| 238 |
+
if not isinstance(Z, Tensor):
|
| 239 |
+
raise ValueError(
|
| 240 |
+
f"A parametrization must return a tensor. Got {type(Z).__name__}."
|
| 241 |
+
)
|
| 242 |
+
if Z.dtype != original_dtype:
|
| 243 |
+
raise ValueError(
|
| 244 |
+
"Registering a parametrization may not change the dtype of the tensor, unless `unsafe` flag is enabled.\n"
|
| 245 |
+
f"unparametrized dtype: {original_dtype}\n"
|
| 246 |
+
f"parametrized dtype: {Z.dtype}"
|
| 247 |
+
)
|
| 248 |
+
if Z.shape != original_shape:
|
| 249 |
+
raise ValueError(
|
| 250 |
+
"Registering a parametrization may not change the shape of the tensor, unless `unsafe` flag is enabled.\n"
|
| 251 |
+
f"unparametrized shape: {original_shape}\n"
|
| 252 |
+
f"parametrized shape: {Z.shape}"
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
def right_inverse(self, value: Tensor) -> None:
|
| 256 |
+
r"""Call the ``right_inverse`` methods of the parametrizations in the inverse registration order.
|
| 257 |
+
|
| 258 |
+
Then, it stores the result in ``self.original`` if ``right_inverse`` outputs one tensor
|
| 259 |
+
or in ``self.original0``, ``self.original1``, ... if it outputs several.
|
| 260 |
+
|
| 261 |
+
Args:
|
| 262 |
+
value (Tensor): Value to which initialize the module
|
| 263 |
+
"""
|
| 264 |
+
# All the exceptions in this function should almost never throw.
|
| 265 |
+
# They could throw if, for example, right_inverse function returns a different
|
| 266 |
+
# dtype when given a different input, which should most likely be caused by a
|
| 267 |
+
# bug in the user's code
|
| 268 |
+
|
| 269 |
+
with torch.no_grad():
|
| 270 |
+
# See https://github.com/pytorch/pytorch/issues/53103
|
| 271 |
+
for module in reversed(self): # type: ignore[call-overload]
|
| 272 |
+
if hasattr(module, "right_inverse"):
|
| 273 |
+
value = module.right_inverse(value) # type: ignore[operator]
|
| 274 |
+
else:
|
| 275 |
+
raise RuntimeError(
|
| 276 |
+
f"parametrization {type(module).__name__} does not implement "
|
| 277 |
+
"right_inverse."
|
| 278 |
+
)
|
| 279 |
+
if self.is_tensor:
|
| 280 |
+
# These exceptions should only throw when a right_inverse function does not
|
| 281 |
+
# return the same dtype for every input, which should most likely be caused by a bug
|
| 282 |
+
if not isinstance(value, Tensor):
|
| 283 |
+
raise ValueError(
|
| 284 |
+
f"`right_inverse` should return a tensor. Got {type(value).__name__}"
|
| 285 |
+
)
|
| 286 |
+
if value.dtype != self.original.dtype:
|
| 287 |
+
raise ValueError(
|
| 288 |
+
f"The tensor returned by `right_inverse` has dtype {value.dtype} "
|
| 289 |
+
f"while `original` has dtype {self.original.dtype}"
|
| 290 |
+
)
|
| 291 |
+
# We know that the result is going to have the same dtype
|
| 292 |
+
_maybe_set(self.original, value)
|
| 293 |
+
else:
|
| 294 |
+
if not isinstance(value, collections.abc.Sequence):
|
| 295 |
+
raise ValueError(
|
| 296 |
+
"'right_inverse' must return a sequence of tensors. "
|
| 297 |
+
f"Got {type(value).__name__}."
|
| 298 |
+
)
|
| 299 |
+
if len(value) != self.ntensors:
|
| 300 |
+
raise ValueError(
|
| 301 |
+
"'right_inverse' must return a sequence of tensors of length "
|
| 302 |
+
f"{self.ntensors}. Got a sequence of length {len(value)}."
|
| 303 |
+
)
|
| 304 |
+
for i, tensor in enumerate(value):
|
| 305 |
+
original_i = getattr(self, f"original{i}")
|
| 306 |
+
match tensor:
|
| 307 |
+
case OpaqueBase():
|
| 308 |
+
if is_opaque_reference_type(type(tensor)):
|
| 309 |
+
setattr(self, f"original{i}", tensor)
|
| 310 |
+
continue
|
| 311 |
+
# Fall-through
|
| 312 |
+
case Tensor():
|
| 313 |
+
if original_i.dtype != tensor.dtype:
|
| 314 |
+
raise ValueError(
|
| 315 |
+
f"Tensor {i} returned by `right_inverse` has dtype {tensor.dtype} "
|
| 316 |
+
f"while `original{i}` has dtype {original_i.dtype}"
|
| 317 |
+
)
|
| 318 |
+
_maybe_set(original_i, tensor)
|
| 319 |
+
continue
|
| 320 |
+
raise ValueError(
|
| 321 |
+
f"'right_inverse' must return a sequence of tensors "
|
| 322 |
+
f"or reference-type opaques. Got element {i} of type "
|
| 323 |
+
f"{type(tensor).__name__}."
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
def forward(self) -> Tensor:
|
| 327 |
+
if torch.jit.is_scripting():
|
| 328 |
+
raise RuntimeError("Parametrization is not working with scripting.")
|
| 329 |
+
# Unpack the originals for the first parametrization
|
| 330 |
+
if self.is_tensor:
|
| 331 |
+
x = self[0](self.original)
|
| 332 |
+
else:
|
| 333 |
+
originals = (getattr(self, f"original{i}") for i in range(self.ntensors))
|
| 334 |
+
x = self[0](*originals)
|
| 335 |
+
# It's not possible to call self[1:] here, so we have to be a bit more cryptic
|
| 336 |
+
# Also we want to skip all non-integer keys
|
| 337 |
+
curr_idx = 1
|
| 338 |
+
while hasattr(self, str(curr_idx)):
|
| 339 |
+
x = self[curr_idx](x)
|
| 340 |
+
curr_idx += 1
|
| 341 |
+
return x
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def _inject_new_class(module: Module) -> None:
|
| 345 |
+
r"""Set up a module to be parametrized.
|
| 346 |
+
|
| 347 |
+
This works by substituting the class of the module by a class
|
| 348 |
+
that extends it to be able to inject a property
|
| 349 |
+
|
| 350 |
+
Args:
|
| 351 |
+
module (nn.Module): module into which to inject the property
|
| 352 |
+
"""
|
| 353 |
+
cls = module.__class__
|
| 354 |
+
|
| 355 |
+
def default_deepcopy(self, memo):
|
| 356 |
+
# Just emulate a standard deepcopy procedure when __deepcopy__ doesn't exist in the current class.
|
| 357 |
+
obj = memo.get(id(self), None)
|
| 358 |
+
if obj is not None:
|
| 359 |
+
return obj
|
| 360 |
+
replica = self.__new__(self.__class__)
|
| 361 |
+
memo[id(self)] = replica
|
| 362 |
+
replica.__dict__ = deepcopy(self.__dict__, memo)
|
| 363 |
+
# Also save all slots if they exist.
|
| 364 |
+
slots_to_save = copyreg._slotnames(self.__class__) # type: ignore[attr-defined]
|
| 365 |
+
for slot in slots_to_save:
|
| 366 |
+
if hasattr(self, slot):
|
| 367 |
+
setattr(replica, slot, deepcopy(getattr(self, slot), memo))
|
| 368 |
+
return replica
|
| 369 |
+
|
| 370 |
+
def getstate(self):
|
| 371 |
+
raise RuntimeError(
|
| 372 |
+
"Serialization of parametrized modules is only "
|
| 373 |
+
"supported through state_dict(). See:\n"
|
| 374 |
+
"https://pytorch.org/tutorials/beginner/saving_loading_models.html"
|
| 375 |
+
"#saving-loading-a-general-checkpoint-for-inference-and-or-resuming-training"
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
dct = {"__getstate__": getstate}
|
| 379 |
+
# We don't allow serialization of parametrized modules but should still allow deepcopying.
|
| 380 |
+
# Default 'deepcopy' function invokes __deepcopy__ method instead of __getstate__ when it exists.
|
| 381 |
+
if not hasattr(cls, "__deepcopy__"):
|
| 382 |
+
dct["__deepcopy__"] = default_deepcopy # type: ignore[assignment]
|
| 383 |
+
|
| 384 |
+
param_cls = type(
|
| 385 |
+
f"Parametrized{cls.__name__}",
|
| 386 |
+
(cls,),
|
| 387 |
+
dct,
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
module.__class__ = param_cls
|
| 391 |
+
|
| 392 |
+
|
| 393 |
+
def _inject_property(module: Module, tensor_name: str) -> None:
|
| 394 |
+
r"""Injects a property into module[tensor_name].
|
| 395 |
+
|
| 396 |
+
It assumes that the class in the module has already been modified from its
|
| 397 |
+
original one using _inject_new_class and that the tensor under :attr:`tensor_name`
|
| 398 |
+
has already been moved out
|
| 399 |
+
|
| 400 |
+
Args:
|
| 401 |
+
module (nn.Module): module into which to inject the property
|
| 402 |
+
tensor_name (str): name of the name of the property to create
|
| 403 |
+
"""
|
| 404 |
+
# We check the precondition.
|
| 405 |
+
# This should never fire if register_parametrization is correctly implemented
|
| 406 |
+
if hasattr(module, tensor_name):
|
| 407 |
+
raise AssertionError(f"Module already has an attribute named '{tensor_name}'")
|
| 408 |
+
|
| 409 |
+
@torch.jit.unused
|
| 410 |
+
def get_cached_parametrization(parametrization) -> Tensor:
|
| 411 |
+
global _cache
|
| 412 |
+
key = (id(module), tensor_name)
|
| 413 |
+
tensor = _cache.get(key)
|
| 414 |
+
if tensor is None:
|
| 415 |
+
tensor = parametrization()
|
| 416 |
+
_cache[key] = tensor
|
| 417 |
+
return tensor
|
| 418 |
+
|
| 419 |
+
def get_parametrized(self) -> Tensor:
|
| 420 |
+
if torch.jit.is_scripting():
|
| 421 |
+
raise RuntimeError("Parametrization is not working with scripting.")
|
| 422 |
+
parametrization = self.parametrizations[tensor_name]
|
| 423 |
+
# pyrefly: ignore [redundant-condition]
|
| 424 |
+
if _cache_enabled:
|
| 425 |
+
if torch.jit.is_scripting():
|
| 426 |
+
# Scripting
|
| 427 |
+
raise RuntimeError(
|
| 428 |
+
"Caching is not implemented for scripting. "
|
| 429 |
+
"Either disable caching or avoid scripting."
|
| 430 |
+
)
|
| 431 |
+
elif torch._C._get_tracing_state() is not None:
|
| 432 |
+
# Tracing
|
| 433 |
+
raise RuntimeError(
|
| 434 |
+
"Cannot trace a model while caching parametrizations."
|
| 435 |
+
)
|
| 436 |
+
else:
|
| 437 |
+
return get_cached_parametrization(parametrization)
|
| 438 |
+
else:
|
| 439 |
+
# If caching is not active, this function just evaluates the parametrization
|
| 440 |
+
return parametrization()
|
| 441 |
+
|
| 442 |
+
def set_original(self, value: Tensor) -> None:
|
| 443 |
+
if torch.jit.is_scripting():
|
| 444 |
+
raise RuntimeError("Parametrization is not working with scripting.")
|
| 445 |
+
self.parametrizations[tensor_name].right_inverse(value)
|
| 446 |
+
|
| 447 |
+
setattr(module.__class__, tensor_name, property(get_parametrized, set_original))
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def register_parametrization(
|
| 451 |
+
module: Module,
|
| 452 |
+
tensor_name: str,
|
| 453 |
+
parametrization: Module,
|
| 454 |
+
*,
|
| 455 |
+
unsafe: bool = False,
|
| 456 |
+
) -> Module:
|
| 457 |
+
r"""Register a parametrization to a tensor in a module.
|
| 458 |
+
|
| 459 |
+
Assume that ``tensor_name="weight"`` for simplicity. When accessing ``module.weight``,
|
| 460 |
+
the module will return the parametrized version ``parametrization(module.weight)``.
|
| 461 |
+
If the original tensor requires a gradient, the backward pass will differentiate
|
| 462 |
+
through :attr:`parametrization`, and the optimizer will update the tensor accordingly.
|
| 463 |
+
|
| 464 |
+
The first time that a module registers a parametrization, this function will add an attribute
|
| 465 |
+
``parametrizations`` to the module of type :class:`~ParametrizationList`.
|
| 466 |
+
|
| 467 |
+
The list of parametrizations on the tensor ``weight`` will be accessible under
|
| 468 |
+
``module.parametrizations.weight``.
|
| 469 |
+
|
| 470 |
+
The original tensor will be accessible under
|
| 471 |
+
``module.parametrizations.weight.original``.
|
| 472 |
+
|
| 473 |
+
Parametrizations may be concatenated by registering several parametrizations
|
| 474 |
+
on the same attribute.
|
| 475 |
+
|
| 476 |
+
The training mode of a registered parametrization is updated on registration
|
| 477 |
+
to match the training mode of the host module
|
| 478 |
+
|
| 479 |
+
Parametrized parameters and buffers have an inbuilt caching system that can be activated
|
| 480 |
+
using the context manager :func:`cached`.
|
| 481 |
+
|
| 482 |
+
A :attr:`parametrization` may optionally implement a method with signature
|
| 483 |
+
|
| 484 |
+
.. code-block:: python
|
| 485 |
+
|
| 486 |
+
def right_inverse(self, X: Tensor) -> Union[Tensor, Sequence[Tensor]]
|
| 487 |
+
|
| 488 |
+
This method is called on the unparametrized tensor when the first parametrization
|
| 489 |
+
is registered to compute the initial value of the original tensor.
|
| 490 |
+
If this method is not implemented, the original tensor will be just the unparametrized tensor.
|
| 491 |
+
|
| 492 |
+
If all the parametrizations registered on a tensor implement `right_inverse` it is possible
|
| 493 |
+
to initialize a parametrized tensor by assigning to it, as shown in the example below.
|
| 494 |
+
|
| 495 |
+
It is possible for the first parametrization to depend on several inputs.
|
| 496 |
+
This may be implemented returning a tuple of tensors from ``right_inverse``
|
| 497 |
+
(see the example implementation of a ``RankOne`` parametrization below).
|
| 498 |
+
|
| 499 |
+
In this case, the unconstrained tensors are also located under ``module.parametrizations.weight``
|
| 500 |
+
with names ``original0``, ``original1``,...
|
| 501 |
+
|
| 502 |
+
.. note::
|
| 503 |
+
|
| 504 |
+
If unsafe=False (default) both the forward and right_inverse methods will be called
|
| 505 |
+
once to perform a number of consistency checks.
|
| 506 |
+
If unsafe=True, then right_inverse will be called if the tensor is not parametrized,
|
| 507 |
+
and nothing will be called otherwise.
|
| 508 |
+
|
| 509 |
+
.. note::
|
| 510 |
+
|
| 511 |
+
In most situations, ``right_inverse`` will be a function such that
|
| 512 |
+
``forward(right_inverse(X)) == X`` (see
|
| 513 |
+
`right inverse <https://en.wikipedia.org/wiki/Inverse_function#Right_inverses>`_).
|
| 514 |
+
Sometimes, when the parametrization is not surjective, it may be reasonable
|
| 515 |
+
to relax this.
|
| 516 |
+
|
| 517 |
+
.. warning::
|
| 518 |
+
|
| 519 |
+
If a parametrization depends on several inputs, :func:`~register_parametrization`
|
| 520 |
+
will register a number of new parameters. If such parametrization is registered
|
| 521 |
+
after the optimizer is created, these new parameters will need to be added manually
|
| 522 |
+
to the optimizer. See :meth:`torch.Optimizer.add_param_group`.
|
| 523 |
+
|
| 524 |
+
Args:
|
| 525 |
+
module (nn.Module): module on which to register the parametrization
|
| 526 |
+
tensor_name (str): name of the parameter or buffer on which to register
|
| 527 |
+
the parametrization
|
| 528 |
+
parametrization (nn.Module): the parametrization to register
|
| 529 |
+
Keyword args:
|
| 530 |
+
unsafe (bool): a boolean flag that denotes whether the parametrization
|
| 531 |
+
may change the dtype and shape of the tensor. Default: `False`
|
| 532 |
+
Warning: the parametrization is not checked for consistency upon registration.
|
| 533 |
+
Enable this flag at your own risk.
|
| 534 |
+
|
| 535 |
+
Raises:
|
| 536 |
+
ValueError: if the module does not have a parameter or a buffer named :attr:`tensor_name`
|
| 537 |
+
|
| 538 |
+
Examples:
|
| 539 |
+
>>> # xdoctest: +REQUIRES(env:TORCH_DOCTEST_LAPACK)
|
| 540 |
+
>>> import torch
|
| 541 |
+
>>> import torch.nn as nn
|
| 542 |
+
>>> import torch.nn.utils.parametrize as P
|
| 543 |
+
>>>
|
| 544 |
+
>>> class Symmetric(nn.Module):
|
| 545 |
+
>>> def forward(self, X):
|
| 546 |
+
>>> return X.triu() + X.triu(1).T # Return a symmetric matrix
|
| 547 |
+
>>>
|
| 548 |
+
>>> def right_inverse(self, A):
|
| 549 |
+
>>> return A.triu()
|
| 550 |
+
>>>
|
| 551 |
+
>>> m = nn.Linear(5, 5)
|
| 552 |
+
>>> P.register_parametrization(m, "weight", Symmetric())
|
| 553 |
+
>>> print(torch.allclose(m.weight, m.weight.T)) # m.weight is now symmetric
|
| 554 |
+
True
|
| 555 |
+
>>> A = torch.rand(5, 5)
|
| 556 |
+
>>> A = A + A.T # A is now symmetric
|
| 557 |
+
>>> m.weight = A # Initialize the weight to be the symmetric matrix A
|
| 558 |
+
>>> print(torch.allclose(m.weight, A))
|
| 559 |
+
True
|
| 560 |
+
|
| 561 |
+
>>> class RankOne(nn.Module):
|
| 562 |
+
>>> def forward(self, x, y):
|
| 563 |
+
>>> # Form a rank 1 matrix multiplying two vectors
|
| 564 |
+
>>> return x.unsqueeze(-1) @ y.unsqueeze(-2)
|
| 565 |
+
>>>
|
| 566 |
+
>>> def right_inverse(self, Z):
|
| 567 |
+
>>> # Project Z onto the rank 1 matrices
|
| 568 |
+
>>> U, S, Vh = torch.linalg.svd(Z, full_matrices=False)
|
| 569 |
+
>>> # Return rescaled singular vectors
|
| 570 |
+
>>> s0_sqrt = S[0].sqrt().unsqueeze(-1)
|
| 571 |
+
>>> return U[..., :, 0] * s0_sqrt, Vh[..., 0, :] * s0_sqrt
|
| 572 |
+
>>>
|
| 573 |
+
>>> linear_rank_one = P.register_parametrization(
|
| 574 |
+
... nn.Linear(4, 4), "weight", RankOne()
|
| 575 |
+
... )
|
| 576 |
+
>>> print(torch.linalg.matrix_rank(linear_rank_one.weight).item())
|
| 577 |
+
1
|
| 578 |
+
|
| 579 |
+
"""
|
| 580 |
+
parametrization.train(module.training)
|
| 581 |
+
if is_parametrized(module, tensor_name):
|
| 582 |
+
# Correctness checks.
|
| 583 |
+
# If A is the space of tensors with shape and dtype equal to module.weight
|
| 584 |
+
# we check that parametrization.forward and parametrization.right_inverse are
|
| 585 |
+
# functions from A to A
|
| 586 |
+
if not unsafe:
|
| 587 |
+
Y = getattr(module, tensor_name)
|
| 588 |
+
X = parametrization(Y)
|
| 589 |
+
if not isinstance(X, Tensor):
|
| 590 |
+
raise ValueError(
|
| 591 |
+
f"A parametrization must return a tensor. Got {type(X).__name__}."
|
| 592 |
+
)
|
| 593 |
+
if X.dtype != Y.dtype:
|
| 594 |
+
raise ValueError(
|
| 595 |
+
"Registering a parametrization may not change the dtype of the tensor, unless the `unsafe` flag is enabled.\n"
|
| 596 |
+
f"module.{tensor_name}.dtype: {Y.dtype}\n"
|
| 597 |
+
f"parametrization(module.{tensor_name}).dtype: {X.dtype}"
|
| 598 |
+
)
|
| 599 |
+
if X.shape != Y.shape:
|
| 600 |
+
raise ValueError(
|
| 601 |
+
"Registering a parametrization may not change the shape of the tensor, unless the `unsafe` flag is enabled.\n"
|
| 602 |
+
f"module.{tensor_name}.shape: {Y.shape}\n"
|
| 603 |
+
f"parametrization(module.{tensor_name}).shape: {X.shape}"
|
| 604 |
+
)
|
| 605 |
+
if hasattr(parametrization, "right_inverse"):
|
| 606 |
+
try:
|
| 607 |
+
Z = parametrization.right_inverse(X) # type: ignore[operator]
|
| 608 |
+
except NotImplementedError:
|
| 609 |
+
pass
|
| 610 |
+
else:
|
| 611 |
+
if not isinstance(Z, Tensor):
|
| 612 |
+
raise ValueError(
|
| 613 |
+
f"parametrization.right_inverse must return a tensor. Got: {type(Z).__name__}"
|
| 614 |
+
)
|
| 615 |
+
if Z.dtype != Y.dtype:
|
| 616 |
+
raise ValueError(
|
| 617 |
+
"The tensor returned by parametrization.right_inverse must have the same dtype "
|
| 618 |
+
f"as module.{tensor_name}, unless the `unsafe` flag is enabled.\n"
|
| 619 |
+
f"module.{tensor_name}.dtype: {Y.dtype}\n"
|
| 620 |
+
f"returned dtype: {Z.dtype}"
|
| 621 |
+
)
|
| 622 |
+
if Z.shape != Y.shape:
|
| 623 |
+
raise ValueError(
|
| 624 |
+
"The tensor returned by parametrization.right_inverse must have the same shape "
|
| 625 |
+
f"as module.{tensor_name}, unless the `unsafe` flag is enabled.\n"
|
| 626 |
+
f"module.{tensor_name}.shape: {Y.shape}\n"
|
| 627 |
+
f"returned shape: {Z.shape}"
|
| 628 |
+
)
|
| 629 |
+
# else right_inverse is assumed to be the identity
|
| 630 |
+
|
| 631 |
+
# add the new parametrization to the parametrization list
|
| 632 |
+
if not isinstance(module.parametrizations, ModuleDict):
|
| 633 |
+
raise AssertionError(
|
| 634 |
+
f"Expected module.parametrizations to be a ModuleDict, "
|
| 635 |
+
f"got {type(module.parametrizations).__name__}"
|
| 636 |
+
)
|
| 637 |
+
module.parametrizations[tensor_name].append(parametrization) # type: ignore[operator]
|
| 638 |
+
# If unsafe was True in previous parametrization, keep it enabled
|
| 639 |
+
module.parametrizations[tensor_name].unsafe |= unsafe # type: ignore[index, union-attr, operator]
|
| 640 |
+
elif tensor_name in module._buffers or tensor_name in module._parameters:
|
| 641 |
+
# Set the parametrization mechanism
|
| 642 |
+
# Fetch the original buffer or parameter
|
| 643 |
+
original = getattr(module, tensor_name)
|
| 644 |
+
# We create this early to check for possible errors
|
| 645 |
+
parametrizations = ParametrizationList(
|
| 646 |
+
[parametrization], original, unsafe=unsafe
|
| 647 |
+
)
|
| 648 |
+
# Delete the previous parameter or buffer
|
| 649 |
+
delattr(module, tensor_name)
|
| 650 |
+
# If this is the first parametrization registered on the module,
|
| 651 |
+
# we prepare the module to inject the property
|
| 652 |
+
if not is_parametrized(module):
|
| 653 |
+
# Change the class
|
| 654 |
+
_inject_new_class(module)
|
| 655 |
+
# Inject a ``ModuleDict`` into the instance under module.parametrizations
|
| 656 |
+
module.parametrizations = ModuleDict()
|
| 657 |
+
# Add a property into the class
|
| 658 |
+
_inject_property(module, tensor_name)
|
| 659 |
+
# Add a ParametrizationList
|
| 660 |
+
if not isinstance(module.parametrizations, ModuleDict):
|
| 661 |
+
raise AssertionError(
|
| 662 |
+
f"Expected module.parametrizations to be a ModuleDict, "
|
| 663 |
+
f"got {type(module.parametrizations).__name__}"
|
| 664 |
+
)
|
| 665 |
+
module.parametrizations[tensor_name] = parametrizations
|
| 666 |
+
else:
|
| 667 |
+
raise ValueError(
|
| 668 |
+
f"Module '{module}' does not have a parameter, a buffer, or a "
|
| 669 |
+
f"parametrized element with name '{tensor_name}'"
|
| 670 |
+
)
|
| 671 |
+
return module
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
def is_parametrized(module: Module, tensor_name: str | None = None) -> bool:
|
| 675 |
+
r"""Determine if a module has a parametrization.
|
| 676 |
+
|
| 677 |
+
Args:
|
| 678 |
+
module (nn.Module): module to query
|
| 679 |
+
tensor_name (str, optional): name of the parameter in the module
|
| 680 |
+
Default: ``None``
|
| 681 |
+
Returns:
|
| 682 |
+
``True`` if :attr:`module` has a parametrization for the parameter named :attr:`tensor_name`,
|
| 683 |
+
or if it has any parametrization when :attr:`tensor_name` is ``None``;
|
| 684 |
+
otherwise ``False``
|
| 685 |
+
"""
|
| 686 |
+
parametrizations = getattr(module, "parametrizations", None)
|
| 687 |
+
if parametrizations is None or not isinstance(parametrizations, ModuleDict):
|
| 688 |
+
return False
|
| 689 |
+
if tensor_name is None:
|
| 690 |
+
# Check that there is at least one parametrized buffer or Parameter
|
| 691 |
+
return len(parametrizations) > 0
|
| 692 |
+
else:
|
| 693 |
+
return tensor_name in parametrizations
|
| 694 |
+
|
| 695 |
+
|
| 696 |
+
def remove_parametrizations(
|
| 697 |
+
module: Module,
|
| 698 |
+
tensor_name: str,
|
| 699 |
+
leave_parametrized: bool = True,
|
| 700 |
+
) -> Module:
|
| 701 |
+
r"""Remove the parametrizations on a tensor in a module.
|
| 702 |
+
|
| 703 |
+
- If ``leave_parametrized=True``, ``module[tensor_name]`` will be set to
|
| 704 |
+
its current output. In this case, the parametrization shall not change the ``dtype``
|
| 705 |
+
of the tensor.
|
| 706 |
+
- If ``leave_parametrized=False``, ``module[tensor_name]`` will be set to
|
| 707 |
+
the unparametrised tensor in ``module.parametrizations[tensor_name].original``.
|
| 708 |
+
This is only possible when the parametrization depends on just one tensor.
|
| 709 |
+
|
| 710 |
+
Args:
|
| 711 |
+
module (nn.Module): module from which remove the parametrization
|
| 712 |
+
tensor_name (str): name of the parametrization to be removed
|
| 713 |
+
leave_parametrized (bool, optional): leave the attribute :attr:`tensor_name` parametrized.
|
| 714 |
+
Default: ``True``
|
| 715 |
+
|
| 716 |
+
Returns:
|
| 717 |
+
Module: module
|
| 718 |
+
|
| 719 |
+
Raises:
|
| 720 |
+
ValueError: if ``module[tensor_name]`` is not parametrized
|
| 721 |
+
ValueError: if ``leave_parametrized=False`` and the parametrization depends on several tensors
|
| 722 |
+
"""
|
| 723 |
+
if not is_parametrized(module, tensor_name):
|
| 724 |
+
raise ValueError(
|
| 725 |
+
f"Module {module} does not have a parametrization on {tensor_name}"
|
| 726 |
+
)
|
| 727 |
+
|
| 728 |
+
# Fetch the original tensor
|
| 729 |
+
if not isinstance(module.parametrizations, ModuleDict):
|
| 730 |
+
raise AssertionError(
|
| 731 |
+
f"Expected module.parametrizations to be a ModuleDict, "
|
| 732 |
+
f"got {type(module.parametrizations).__name__}"
|
| 733 |
+
)
|
| 734 |
+
parametrizations = module.parametrizations[tensor_name]
|
| 735 |
+
|
| 736 |
+
if parametrizations.is_tensor:
|
| 737 |
+
original = parametrizations.original
|
| 738 |
+
if not isinstance(original, torch.Tensor):
|
| 739 |
+
raise AssertionError(
|
| 740 |
+
f"Expected original to be a Tensor (is_tensor promised us a Tensor), "
|
| 741 |
+
f"got {type(original).__name__}"
|
| 742 |
+
)
|
| 743 |
+
if leave_parametrized:
|
| 744 |
+
with torch.no_grad():
|
| 745 |
+
t = getattr(module, tensor_name)
|
| 746 |
+
# We know they have the same dtype because we have checked this when registering the
|
| 747 |
+
# parametrizations. As such, we can use set_
|
| 748 |
+
# We do this so that the parameter does not to change the id()
|
| 749 |
+
# This way the user does not need to update the optimizer
|
| 750 |
+
with torch.no_grad():
|
| 751 |
+
if type(original) is torch.Tensor:
|
| 752 |
+
_maybe_set(original, t)
|
| 753 |
+
else:
|
| 754 |
+
try:
|
| 755 |
+
_maybe_set(original, t)
|
| 756 |
+
except RuntimeError as e:
|
| 757 |
+
# TODO: Fix this for tensor subclasses that are parameters:
|
| 758 |
+
# RuntimeError: set_storage is not allowed on a Tensor created from .data or .detach().
|
| 759 |
+
raise RuntimeError(
|
| 760 |
+
"Calling remove_parametrizations() with leave_parametrized=True "
|
| 761 |
+
"for a parameter that is an instance of a tensor subclass requires "
|
| 762 |
+
"set_() to be implemented correctly for the tensor subclass."
|
| 763 |
+
"Alternatively, one can opt into the swap_tensors path"
|
| 764 |
+
"Either set leave_parametrized=False or provide a working implementation"
|
| 765 |
+
"for set_() in the tensor subclass or set "
|
| 766 |
+
"torch.__future__.set_swap_module_params_on_conversion(True)."
|
| 767 |
+
) from e
|
| 768 |
+
else:
|
| 769 |
+
if leave_parametrized:
|
| 770 |
+
# We cannot use no_grad because we need to know whether one or more
|
| 771 |
+
# original tensors required grad
|
| 772 |
+
t = getattr(module, tensor_name)
|
| 773 |
+
# We'll have to trust the user to add it to the optimizer
|
| 774 |
+
original = Parameter(t) if t.requires_grad else t
|
| 775 |
+
else:
|
| 776 |
+
raise ValueError(
|
| 777 |
+
"Cannot leave unparametrized (`leave_parametrized=False`) a tensor "
|
| 778 |
+
"that is parametrized in terms of a sequence of tensors."
|
| 779 |
+
)
|
| 780 |
+
|
| 781 |
+
# Delete the property that manages the parametrization
|
| 782 |
+
delattr(module.__class__, tensor_name)
|
| 783 |
+
# Delete the ParametrizationList
|
| 784 |
+
del module.parametrizations[tensor_name]
|
| 785 |
+
|
| 786 |
+
# Restore the parameter / buffer into the main class
|
| 787 |
+
_register_parameter_or_buffer(module, tensor_name, original)
|
| 788 |
+
|
| 789 |
+
# Roll back the parametrized class if no other buffer or parameter
|
| 790 |
+
# is currently parametrized in this class
|
| 791 |
+
if not is_parametrized(module):
|
| 792 |
+
delattr(module, "parametrizations")
|
| 793 |
+
# Restore class
|
| 794 |
+
orig_cls = module.__class__.__bases__[0]
|
| 795 |
+
module.__class__ = orig_cls
|
| 796 |
+
return module
|
| 797 |
+
|
| 798 |
+
|
| 799 |
+
def type_before_parametrizations(module: Module) -> type:
|
| 800 |
+
r"""Return the module type before parametrizations were applied and if not, then it returns the module type.
|
| 801 |
+
|
| 802 |
+
Args:
|
| 803 |
+
module (nn.Module): module to get type of
|
| 804 |
+
"""
|
| 805 |
+
if is_parametrized(module):
|
| 806 |
+
return module.__class__.__bases__[0]
|
| 807 |
+
else:
|
| 808 |
+
return type(module)
|
| 809 |
+
|
| 810 |
+
|
| 811 |
+
def transfer_parametrizations_and_params(
|
| 812 |
+
from_module: Module,
|
| 813 |
+
to_module: Module,
|
| 814 |
+
tensor_name: str | None = None,
|
| 815 |
+
) -> Module:
|
| 816 |
+
r"""Transfer parametrizations and the parameters they parametrize from :attr:`from_module` to :attr:`to_module`.
|
| 817 |
+
|
| 818 |
+
If :attr:`tensor_name` is specified, only transfers the specified parameter, otherwise
|
| 819 |
+
transfers all parametrized parameters. If those parameters do not exist in to_module, it will create them.
|
| 820 |
+
Does nothing if from_module is not parametrized.
|
| 821 |
+
|
| 822 |
+
Args:
|
| 823 |
+
from_module (nn.Module): module to transfer from
|
| 824 |
+
to_module (nn.Module): module to transfer to
|
| 825 |
+
tensor_name (str, optional): parameter to transfer
|
| 826 |
+
|
| 827 |
+
Returns:
|
| 828 |
+
Module: to_module
|
| 829 |
+
"""
|
| 830 |
+
if is_parametrized(from_module):
|
| 831 |
+
if not isinstance(from_module.parametrizations, ModuleDict):
|
| 832 |
+
raise AssertionError(
|
| 833 |
+
f"Expected from_module.parametrizations to be a ModuleDict, "
|
| 834 |
+
f"got {type(from_module.parametrizations).__name__}"
|
| 835 |
+
)
|
| 836 |
+
|
| 837 |
+
# get list of all params or the single param to transfer
|
| 838 |
+
parameters_to_transfer: list | ModuleDict = (
|
| 839 |
+
from_module.parametrizations if tensor_name is None else [tensor_name]
|
| 840 |
+
)
|
| 841 |
+
|
| 842 |
+
if not hasattr(parameters_to_transfer, "__iter__"):
|
| 843 |
+
raise AssertionError(
|
| 844 |
+
f"Expected parameters_to_transfer to be iterable, "
|
| 845 |
+
f"got {type(parameters_to_transfer).__name__}"
|
| 846 |
+
)
|
| 847 |
+
for parameter_name in parameters_to_transfer:
|
| 848 |
+
# initialize the to-be-transferred param in to_module if it doesn't exist already
|
| 849 |
+
if not hasattr(to_module, parameter_name):
|
| 850 |
+
setattr(
|
| 851 |
+
to_module,
|
| 852 |
+
parameter_name,
|
| 853 |
+
Parameter(getattr(from_module, parameter_name)),
|
| 854 |
+
)
|
| 855 |
+
|
| 856 |
+
# apply the params's parametrizations to to_module
|
| 857 |
+
for param_func in from_module.parametrizations[ # type: ignore[attr-defined]
|
| 858 |
+
parameter_name
|
| 859 |
+
]:
|
| 860 |
+
register_parametrization(to_module, parameter_name, param_func)
|
| 861 |
+
if not isinstance(to_module.parametrizations, ModuleDict):
|
| 862 |
+
raise AssertionError(
|
| 863 |
+
f"Expected to_module.parametrizations to be a ModuleDict, "
|
| 864 |
+
f"got {type(to_module.parametrizations).__name__}"
|
| 865 |
+
)
|
| 866 |
+
|
| 867 |
+
# make values match, original values can be stored in either original or
|
| 868 |
+
# original0, original1..., need to check both cases
|
| 869 |
+
if hasattr(from_module.parametrizations[parameter_name], "original"):
|
| 870 |
+
to_module.parametrizations[
|
| 871 |
+
parameter_name
|
| 872 |
+
].original = from_module.parametrizations[parameter_name].original
|
| 873 |
+
else:
|
| 874 |
+
num = 0
|
| 875 |
+
orig_num = "original" + str(num)
|
| 876 |
+
# loop through each original# until all values have been set
|
| 877 |
+
while hasattr(from_module.parametrizations[parameter_name], orig_num):
|
| 878 |
+
setattr(
|
| 879 |
+
to_module.parametrizations[parameter_name],
|
| 880 |
+
orig_num,
|
| 881 |
+
getattr(from_module.parametrizations[parameter_name], orig_num),
|
| 882 |
+
)
|
| 883 |
+
num = num + 1
|
| 884 |
+
orig_num = "original" + str(num)
|
| 885 |
+
|
| 886 |
+
return to_module
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/prune.py
ADDED
|
@@ -0,0 +1,1395 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
r"""Pruning methods."""
|
| 3 |
+
|
| 4 |
+
import numbers
|
| 5 |
+
from abc import ABC, abstractmethod
|
| 6 |
+
from collections.abc import Iterable
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class BasePruningMethod(ABC):
|
| 12 |
+
r"""Abstract base class for creation of new pruning techniques.
|
| 13 |
+
|
| 14 |
+
Provides a skeleton for customization requiring the overriding of methods
|
| 15 |
+
such as :meth:`compute_mask` and :meth:`apply`.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
_tensor_name: str
|
| 19 |
+
|
| 20 |
+
def __call__(self, module, inputs):
|
| 21 |
+
r"""Multiply the mask into original tensor and store the result.
|
| 22 |
+
|
| 23 |
+
Multiplies the mask (stored in ``module[name + '_mask']``)
|
| 24 |
+
into the original tensor (stored in ``module[name + '_orig']``)
|
| 25 |
+
and stores the result into ``module[name]`` by using :meth:`apply_mask`.
|
| 26 |
+
|
| 27 |
+
Args:
|
| 28 |
+
module (nn.Module): module containing the tensor to prune
|
| 29 |
+
inputs: not used.
|
| 30 |
+
"""
|
| 31 |
+
setattr(module, self._tensor_name, self.apply_mask(module))
|
| 32 |
+
|
| 33 |
+
@abstractmethod
|
| 34 |
+
def compute_mask(self, t, default_mask):
|
| 35 |
+
r"""Compute and returns a mask for the input tensor ``t``.
|
| 36 |
+
|
| 37 |
+
Starting from a base ``default_mask`` (which should be a mask of ones
|
| 38 |
+
if the tensor has not been pruned yet), generate a random mask to
|
| 39 |
+
apply on top of the ``default_mask`` according to the specific pruning
|
| 40 |
+
method recipe.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
t (torch.Tensor): tensor representing the importance scores of the
|
| 44 |
+
parameter to prune.
|
| 45 |
+
default_mask (torch.Tensor): Base mask from previous pruning
|
| 46 |
+
iterations, that need to be respected after the new mask is
|
| 47 |
+
applied. Same dims as ``t``.
|
| 48 |
+
|
| 49 |
+
Returns:
|
| 50 |
+
mask (torch.Tensor): mask to apply to ``t``, of same dims as ``t``
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
def apply_mask(self, module):
|
| 54 |
+
r"""Simply handles the multiplication between the parameter being pruned and the generated mask.
|
| 55 |
+
|
| 56 |
+
Fetches the mask and the original tensor from the module
|
| 57 |
+
and returns the pruned version of the tensor.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
module (nn.Module): module containing the tensor to prune
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
pruned_tensor (torch.Tensor): pruned version of the input tensor
|
| 64 |
+
"""
|
| 65 |
+
# to carry out the multiplication, the mask needs to have been computed,
|
| 66 |
+
# so the pruning method must know what tensor it's operating on
|
| 67 |
+
if self._tensor_name is None:
|
| 68 |
+
raise AssertionError(
|
| 69 |
+
f"Module {module} has to be pruned"
|
| 70 |
+
) # this gets set in apply()
|
| 71 |
+
mask = getattr(module, self._tensor_name + "_mask")
|
| 72 |
+
orig = getattr(module, self._tensor_name + "_orig")
|
| 73 |
+
pruned_tensor = mask.to(dtype=orig.dtype) * orig
|
| 74 |
+
return pruned_tensor
|
| 75 |
+
|
| 76 |
+
@classmethod
|
| 77 |
+
def apply(cls, module, name, *args, importance_scores=None, **kwargs):
|
| 78 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 79 |
+
|
| 80 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 81 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 82 |
+
and the pruning mask.
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
module (nn.Module): module containing the tensor to prune
|
| 86 |
+
name (str): parameter name within ``module`` on which pruning
|
| 87 |
+
will act.
|
| 88 |
+
args: arguments passed on to a subclass of
|
| 89 |
+
:class:`BasePruningMethod`
|
| 90 |
+
importance_scores (torch.Tensor): tensor of importance scores (of
|
| 91 |
+
same shape as module parameter) used to compute mask for pruning.
|
| 92 |
+
The values in this tensor indicate the importance of the
|
| 93 |
+
corresponding elements in the parameter being pruned.
|
| 94 |
+
If unspecified or None, the parameter will be used in its place.
|
| 95 |
+
kwargs: keyword arguments passed on to a subclass of a
|
| 96 |
+
:class:`BasePruningMethod`
|
| 97 |
+
"""
|
| 98 |
+
|
| 99 |
+
def _get_composite_method(cls, module, name, *args, **kwargs):
|
| 100 |
+
# Check if a pruning method has already been applied to
|
| 101 |
+
# `module[name]`. If so, store that in `old_method`.
|
| 102 |
+
old_method = None
|
| 103 |
+
found = 0
|
| 104 |
+
# there should technically be only 1 hook with hook.name == name
|
| 105 |
+
# assert this using `found`
|
| 106 |
+
hooks_to_remove = []
|
| 107 |
+
for k, hook in module._forward_pre_hooks.items():
|
| 108 |
+
# if it exists, take existing thing, remove hook, then
|
| 109 |
+
# go through normal thing
|
| 110 |
+
if isinstance(hook, BasePruningMethod) and hook._tensor_name == name:
|
| 111 |
+
old_method = hook
|
| 112 |
+
hooks_to_remove.append(k)
|
| 113 |
+
found += 1
|
| 114 |
+
if found > 1:
|
| 115 |
+
raise AssertionError(
|
| 116 |
+
f"Avoid adding multiple pruning hooks to the "
|
| 117 |
+
f"same tensor {name} of module {module}. Use a PruningContainer."
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
for k in hooks_to_remove:
|
| 121 |
+
del module._forward_pre_hooks[k]
|
| 122 |
+
|
| 123 |
+
# Apply the new pruning method, either from scratch or on top of
|
| 124 |
+
# the previous one.
|
| 125 |
+
method = cls(*args, **kwargs) # new pruning
|
| 126 |
+
# Have the pruning method remember what tensor it's been applied to
|
| 127 |
+
method._tensor_name = name
|
| 128 |
+
|
| 129 |
+
# combine `methods` with `old_method`, if `old_method` exists
|
| 130 |
+
if old_method is not None: # meaning that there was a hook
|
| 131 |
+
# if the hook is already a pruning container, just add the
|
| 132 |
+
# new pruning method to the container
|
| 133 |
+
if isinstance(old_method, PruningContainer):
|
| 134 |
+
old_method.add_pruning_method(method)
|
| 135 |
+
method = old_method # rename old_method --> method
|
| 136 |
+
|
| 137 |
+
# if the hook is simply a single pruning method, create a
|
| 138 |
+
# container, add the old pruning method and the new one
|
| 139 |
+
elif isinstance(old_method, BasePruningMethod):
|
| 140 |
+
container = PruningContainer(old_method)
|
| 141 |
+
# Have the pruning method remember the name of its tensor
|
| 142 |
+
# setattr(container, '_tensor_name', name)
|
| 143 |
+
container.add_pruning_method(method)
|
| 144 |
+
method = container # rename container --> method
|
| 145 |
+
return method
|
| 146 |
+
|
| 147 |
+
method = _get_composite_method(cls, module, name, *args, **kwargs)
|
| 148 |
+
# at this point we have no forward_pre_hooks but we could have an
|
| 149 |
+
# active reparameterization of the tensor if another pruning method
|
| 150 |
+
# had been applied (in which case `method` would be a PruningContainer
|
| 151 |
+
# and not a simple pruning method).
|
| 152 |
+
|
| 153 |
+
# Pruning is to be applied to the module's tensor named `name`,
|
| 154 |
+
# starting from the state it is found in prior to this iteration of
|
| 155 |
+
# pruning. The pruning mask is calculated based on importances scores.
|
| 156 |
+
|
| 157 |
+
orig = getattr(module, name)
|
| 158 |
+
if importance_scores is not None:
|
| 159 |
+
if importance_scores.shape != orig.shape:
|
| 160 |
+
raise AssertionError(
|
| 161 |
+
f"importance_scores should have the same shape as parameter "
|
| 162 |
+
f"{name} of {module}, got {importance_scores.shape} vs {orig.shape}"
|
| 163 |
+
)
|
| 164 |
+
else:
|
| 165 |
+
importance_scores = orig
|
| 166 |
+
|
| 167 |
+
# If this is the first time pruning is applied, take care of moving
|
| 168 |
+
# the original tensor to a new parameter called name + '_orig' and
|
| 169 |
+
# and deleting the original parameter
|
| 170 |
+
if not isinstance(method, PruningContainer):
|
| 171 |
+
# copy `module[name]` to `module[name + '_orig']`
|
| 172 |
+
module.register_parameter(name + "_orig", orig)
|
| 173 |
+
# temporarily delete `module[name]`
|
| 174 |
+
del module._parameters[name]
|
| 175 |
+
default_mask = torch.ones_like(orig) # temp
|
| 176 |
+
# If this is not the first time pruning is applied, all of the above
|
| 177 |
+
# has been done before in a previous pruning iteration, so we're good
|
| 178 |
+
# to go
|
| 179 |
+
else:
|
| 180 |
+
default_mask = (
|
| 181 |
+
getattr(module, name + "_mask")
|
| 182 |
+
.detach()
|
| 183 |
+
.clone(memory_format=torch.contiguous_format)
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
# Use try/except because if anything goes wrong with the mask
|
| 187 |
+
# computation etc., you'd want to roll back.
|
| 188 |
+
try:
|
| 189 |
+
# get the final mask, computed according to the specific method
|
| 190 |
+
mask = method.compute_mask(importance_scores, default_mask=default_mask)
|
| 191 |
+
# reparameterize by saving mask to `module[name + '_mask']`...
|
| 192 |
+
module.register_buffer(name + "_mask", mask)
|
| 193 |
+
# ... and the new pruned tensor to `module[name]`
|
| 194 |
+
setattr(module, name, method.apply_mask(module))
|
| 195 |
+
# associate the pruning method to the module via a hook to
|
| 196 |
+
# compute the function before every forward() (compile by run)
|
| 197 |
+
module.register_forward_pre_hook(method)
|
| 198 |
+
|
| 199 |
+
except Exception as e:
|
| 200 |
+
if not isinstance(method, PruningContainer):
|
| 201 |
+
orig = getattr(module, name + "_orig")
|
| 202 |
+
module.register_parameter(name, orig)
|
| 203 |
+
del module._parameters[name + "_orig"]
|
| 204 |
+
raise e
|
| 205 |
+
|
| 206 |
+
return method
|
| 207 |
+
|
| 208 |
+
def prune(self, t, default_mask=None, importance_scores=None):
|
| 209 |
+
r"""Compute and returns a pruned version of input tensor ``t``.
|
| 210 |
+
|
| 211 |
+
According to the pruning rule specified in :meth:`compute_mask`.
|
| 212 |
+
|
| 213 |
+
Args:
|
| 214 |
+
t (torch.Tensor): tensor to prune (of same dimensions as
|
| 215 |
+
``default_mask``).
|
| 216 |
+
importance_scores (torch.Tensor): tensor of importance scores (of
|
| 217 |
+
same shape as ``t``) used to compute mask for pruning ``t``.
|
| 218 |
+
The values in this tensor indicate the importance of the
|
| 219 |
+
corresponding elements in the ``t`` that is being pruned.
|
| 220 |
+
If unspecified or None, the tensor ``t`` will be used in its place.
|
| 221 |
+
default_mask (torch.Tensor, optional): mask from previous pruning
|
| 222 |
+
iteration, if any. To be considered when determining what
|
| 223 |
+
portion of the tensor that pruning should act on. If None,
|
| 224 |
+
default to a mask of ones.
|
| 225 |
+
|
| 226 |
+
Returns:
|
| 227 |
+
pruned version of tensor ``t``.
|
| 228 |
+
"""
|
| 229 |
+
if importance_scores is not None:
|
| 230 |
+
if importance_scores.shape != t.shape:
|
| 231 |
+
raise AssertionError(
|
| 232 |
+
f"importance_scores should have the same shape as tensor t, "
|
| 233 |
+
f"got {importance_scores.shape} vs {t.shape}"
|
| 234 |
+
)
|
| 235 |
+
else:
|
| 236 |
+
importance_scores = t
|
| 237 |
+
default_mask = default_mask if default_mask is not None else torch.ones_like(t)
|
| 238 |
+
return t * self.compute_mask(importance_scores, default_mask=default_mask)
|
| 239 |
+
|
| 240 |
+
def remove(self, module) -> None:
|
| 241 |
+
r"""Remove the pruning reparameterization from a module.
|
| 242 |
+
|
| 243 |
+
The pruned parameter named ``name`` remains permanently pruned,
|
| 244 |
+
and the parameter named ``name+'_orig'`` is removed from the parameter list.
|
| 245 |
+
Similarly, the buffer named ``name+'_mask'`` is removed from the buffers.
|
| 246 |
+
|
| 247 |
+
Note:
|
| 248 |
+
Pruning itself is NOT undone or reversed!
|
| 249 |
+
"""
|
| 250 |
+
# before removing pruning from a tensor, it has to have been applied
|
| 251 |
+
if self._tensor_name is None:
|
| 252 |
+
raise AssertionError(
|
| 253 |
+
f"Module {module} has to be pruned before pruning can be removed"
|
| 254 |
+
) # this gets set in apply()
|
| 255 |
+
|
| 256 |
+
# to update module[name] to latest trained weights
|
| 257 |
+
weight = self.apply_mask(module) # masked weights
|
| 258 |
+
|
| 259 |
+
# delete and reset
|
| 260 |
+
if hasattr(module, self._tensor_name):
|
| 261 |
+
delattr(module, self._tensor_name)
|
| 262 |
+
orig = module._parameters[self._tensor_name + "_orig"]
|
| 263 |
+
orig.data = weight.data
|
| 264 |
+
del module._parameters[self._tensor_name + "_orig"]
|
| 265 |
+
del module._buffers[self._tensor_name + "_mask"]
|
| 266 |
+
setattr(module, self._tensor_name, orig)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class PruningContainer(BasePruningMethod):
|
| 270 |
+
"""Container holding a sequence of pruning methods for iterative pruning.
|
| 271 |
+
|
| 272 |
+
Keeps track of the order in which pruning methods are applied and handles
|
| 273 |
+
combining successive pruning calls.
|
| 274 |
+
|
| 275 |
+
Accepts as argument an instance of a BasePruningMethod or an iterable of
|
| 276 |
+
them.
|
| 277 |
+
"""
|
| 278 |
+
|
| 279 |
+
def __init__(self, *args) -> None:
|
| 280 |
+
self._pruning_methods: tuple[BasePruningMethod, ...] = ()
|
| 281 |
+
if not isinstance(args, Iterable): # only 1 item
|
| 282 |
+
self._tensor_name = args._tensor_name
|
| 283 |
+
self.add_pruning_method(args)
|
| 284 |
+
|
| 285 |
+
elif len(args) == 1: # only 1 item in a tuple
|
| 286 |
+
self._tensor_name = args[0]._tensor_name
|
| 287 |
+
|
| 288 |
+
self.add_pruning_method(args[0])
|
| 289 |
+
else: # manual construction from list or other iterable (or no args)
|
| 290 |
+
for method in args:
|
| 291 |
+
self.add_pruning_method(method)
|
| 292 |
+
|
| 293 |
+
def add_pruning_method(self, method) -> None:
|
| 294 |
+
r"""Add a child pruning ``method`` to the container.
|
| 295 |
+
|
| 296 |
+
Args:
|
| 297 |
+
method (subclass of BasePruningMethod): child pruning method
|
| 298 |
+
to be added to the container.
|
| 299 |
+
"""
|
| 300 |
+
# check that we're adding a pruning method to the container
|
| 301 |
+
if not isinstance(method, BasePruningMethod) and method is not None:
|
| 302 |
+
raise TypeError(f"{type(method)} is not a BasePruningMethod subclass")
|
| 303 |
+
elif method is not None and self._tensor_name != method._tensor_name:
|
| 304 |
+
raise ValueError(
|
| 305 |
+
"Can only add pruning methods acting on "
|
| 306 |
+
f"the parameter named '{self._tensor_name}' to PruningContainer {self}."
|
| 307 |
+
+ f" Found '{method._tensor_name}'"
|
| 308 |
+
)
|
| 309 |
+
# if all checks passed, add to _pruning_methods tuple
|
| 310 |
+
self._pruning_methods += (method,) # type: ignore[operator]
|
| 311 |
+
|
| 312 |
+
def __len__(self) -> int:
|
| 313 |
+
return len(self._pruning_methods)
|
| 314 |
+
|
| 315 |
+
def __iter__(self):
|
| 316 |
+
return iter(self._pruning_methods)
|
| 317 |
+
|
| 318 |
+
def __getitem__(self, idx):
|
| 319 |
+
return self._pruning_methods[idx]
|
| 320 |
+
|
| 321 |
+
def compute_mask(self, t, default_mask):
|
| 322 |
+
r"""Apply the latest ``method`` by computing the new partial masks and returning its combination with the ``default_mask``.
|
| 323 |
+
|
| 324 |
+
The new partial mask should be computed on the entries or channels
|
| 325 |
+
that were not zeroed out by the ``default_mask``.
|
| 326 |
+
Which portions of the tensor ``t`` the new mask will be calculated from
|
| 327 |
+
depends on the ``PRUNING_TYPE`` (handled by the type handler):
|
| 328 |
+
|
| 329 |
+
* for 'unstructured', the mask will be computed from the raveled
|
| 330 |
+
list of nonmasked entries;
|
| 331 |
+
|
| 332 |
+
* for 'structured', the mask will be computed from the nonmasked
|
| 333 |
+
channels in the tensor;
|
| 334 |
+
|
| 335 |
+
* for 'global', the mask will be computed across all entries.
|
| 336 |
+
|
| 337 |
+
Args:
|
| 338 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 339 |
+
(of same dimensions as ``default_mask``).
|
| 340 |
+
default_mask (torch.Tensor): mask from previous pruning iteration.
|
| 341 |
+
|
| 342 |
+
Returns:
|
| 343 |
+
mask (torch.Tensor): new mask that combines the effects
|
| 344 |
+
of the ``default_mask`` and the new mask from the current
|
| 345 |
+
pruning ``method`` (of same dimensions as ``default_mask`` and
|
| 346 |
+
``t``).
|
| 347 |
+
"""
|
| 348 |
+
|
| 349 |
+
def _combine_masks(method, t, mask):
|
| 350 |
+
r"""Combine the masks from all pruning methods and returns a new mask.
|
| 351 |
+
|
| 352 |
+
Args:
|
| 353 |
+
method (a BasePruningMethod subclass): pruning method
|
| 354 |
+
currently being applied.
|
| 355 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 356 |
+
(of same dimensions as mask).
|
| 357 |
+
mask (torch.Tensor): mask from previous pruning iteration
|
| 358 |
+
|
| 359 |
+
Returns:
|
| 360 |
+
new_mask (torch.Tensor): new mask that combines the effects
|
| 361 |
+
of the old mask and the new mask from the current
|
| 362 |
+
pruning method (of same dimensions as mask and t).
|
| 363 |
+
"""
|
| 364 |
+
new_mask = mask # start off from existing mask
|
| 365 |
+
new_mask = new_mask.to(dtype=t.dtype)
|
| 366 |
+
|
| 367 |
+
# compute a slice of t onto which the new pruning method will operate
|
| 368 |
+
if method.PRUNING_TYPE == "unstructured":
|
| 369 |
+
# prune entries of t where the mask is 1
|
| 370 |
+
slc = mask == 1
|
| 371 |
+
|
| 372 |
+
# for struct pruning, exclude channels that have already been
|
| 373 |
+
# entirely pruned
|
| 374 |
+
elif method.PRUNING_TYPE == "structured":
|
| 375 |
+
if not hasattr(method, "dim"):
|
| 376 |
+
raise AttributeError(
|
| 377 |
+
"Pruning methods of PRUNING_TYPE "
|
| 378 |
+
'"structured" need to have the attribute `dim` defined.'
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
# find the channels to keep by removing the ones that have been
|
| 382 |
+
# zeroed out already (i.e. where sum(entries) == 0)
|
| 383 |
+
n_dims = t.dim() # "is this a 2D tensor? 3D? ..."
|
| 384 |
+
dim = method.dim
|
| 385 |
+
# convert negative indexing
|
| 386 |
+
if dim < 0:
|
| 387 |
+
dim = n_dims + dim
|
| 388 |
+
# if dim is still negative after subtracting it from n_dims
|
| 389 |
+
if dim < 0:
|
| 390 |
+
raise IndexError(
|
| 391 |
+
f"Index is out of bounds for tensor with dimensions {n_dims}"
|
| 392 |
+
)
|
| 393 |
+
# find channels along dim = dim that aren't already tots 0ed out
|
| 394 |
+
keep_channel = mask.sum(dim=[d for d in range(n_dims) if d != dim]) != 0
|
| 395 |
+
# create slice to identify what to prune
|
| 396 |
+
slc = [slice(None)] * n_dims
|
| 397 |
+
slc[dim] = keep_channel
|
| 398 |
+
|
| 399 |
+
elif method.PRUNING_TYPE == "global":
|
| 400 |
+
n_dims = len(t.shape) # "is this a 2D tensor? 3D? ..."
|
| 401 |
+
slc = [slice(None)] * n_dims
|
| 402 |
+
|
| 403 |
+
else:
|
| 404 |
+
raise ValueError(f"Unrecognized PRUNING_TYPE {method.PRUNING_TYPE}")
|
| 405 |
+
|
| 406 |
+
# compute the new mask on the unpruned slice of the tensor t
|
| 407 |
+
if isinstance(slc, list):
|
| 408 |
+
slc = tuple(slc)
|
| 409 |
+
partial_mask = method.compute_mask(t[slc], default_mask=mask[slc])
|
| 410 |
+
new_mask[slc] = partial_mask.to(dtype=new_mask.dtype)
|
| 411 |
+
|
| 412 |
+
return new_mask
|
| 413 |
+
|
| 414 |
+
method = self._pruning_methods[-1]
|
| 415 |
+
mask = _combine_masks(method, t, default_mask)
|
| 416 |
+
return mask
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class Identity(BasePruningMethod):
|
| 420 |
+
r"""Utility pruning method that does not prune any units but generates the pruning parametrization with a mask of ones."""
|
| 421 |
+
|
| 422 |
+
PRUNING_TYPE = "unstructured"
|
| 423 |
+
|
| 424 |
+
def compute_mask(self, t, default_mask):
|
| 425 |
+
mask = default_mask
|
| 426 |
+
return mask
|
| 427 |
+
|
| 428 |
+
@classmethod
|
| 429 |
+
def apply(cls, module, name): # type: ignore[override]
|
| 430 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 431 |
+
|
| 432 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 433 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 434 |
+
and the pruning mask.
|
| 435 |
+
|
| 436 |
+
Args:
|
| 437 |
+
module (nn.Module): module containing the tensor to prune
|
| 438 |
+
name (str): parameter name within ``module`` on which pruning
|
| 439 |
+
will act.
|
| 440 |
+
"""
|
| 441 |
+
return super().apply(module, name)
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
class RandomUnstructured(BasePruningMethod):
|
| 445 |
+
r"""Prune (currently unpruned) units in a tensor at random.
|
| 446 |
+
|
| 447 |
+
Args:
|
| 448 |
+
name (str): parameter name within ``module`` on which pruning
|
| 449 |
+
will act.
|
| 450 |
+
amount (int or float): quantity of parameters to prune.
|
| 451 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 452 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 453 |
+
absolute number of parameters to prune.
|
| 454 |
+
"""
|
| 455 |
+
|
| 456 |
+
PRUNING_TYPE = "unstructured"
|
| 457 |
+
|
| 458 |
+
def __init__(self, amount) -> None:
|
| 459 |
+
# Check range of validity of pruning amount
|
| 460 |
+
_validate_pruning_amount_init(amount)
|
| 461 |
+
self.amount = amount
|
| 462 |
+
|
| 463 |
+
def compute_mask(self, t, default_mask):
|
| 464 |
+
# Check that the amount of units to prune is not > than the number of
|
| 465 |
+
# parameters in t
|
| 466 |
+
tensor_size = t.nelement()
|
| 467 |
+
# Compute number of units to prune: amount if int,
|
| 468 |
+
# else amount * tensor_size
|
| 469 |
+
nparams_toprune = _compute_nparams_toprune(self.amount, tensor_size)
|
| 470 |
+
# This should raise an error if the number of units to prune is larger
|
| 471 |
+
# than the number of units in the tensor
|
| 472 |
+
_validate_pruning_amount(nparams_toprune, tensor_size)
|
| 473 |
+
|
| 474 |
+
mask = default_mask.clone(memory_format=torch.contiguous_format)
|
| 475 |
+
|
| 476 |
+
if nparams_toprune != 0: # k=0 not supported by torch.kthvalue
|
| 477 |
+
prob = torch.rand_like(t)
|
| 478 |
+
topk = torch.topk(prob.view(-1), k=nparams_toprune)
|
| 479 |
+
mask.view(-1)[topk.indices] = 0
|
| 480 |
+
|
| 481 |
+
return mask
|
| 482 |
+
|
| 483 |
+
@classmethod
|
| 484 |
+
def apply(cls, module, name, amount): # type: ignore[override]
|
| 485 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 486 |
+
|
| 487 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 488 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 489 |
+
and the pruning mask.
|
| 490 |
+
|
| 491 |
+
Args:
|
| 492 |
+
module (nn.Module): module containing the tensor to prune
|
| 493 |
+
name (str): parameter name within ``module`` on which pruning
|
| 494 |
+
will act.
|
| 495 |
+
amount (int or float): quantity of parameters to prune.
|
| 496 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 497 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 498 |
+
absolute number of parameters to prune.
|
| 499 |
+
"""
|
| 500 |
+
return super().apply(module, name, amount=amount)
|
| 501 |
+
|
| 502 |
+
|
| 503 |
+
class L1Unstructured(BasePruningMethod):
|
| 504 |
+
r"""Prune (currently unpruned) units in a tensor by zeroing out the ones with the lowest L1-norm.
|
| 505 |
+
|
| 506 |
+
Args:
|
| 507 |
+
amount (int or float): quantity of parameters to prune.
|
| 508 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 509 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 510 |
+
absolute number of parameters to prune.
|
| 511 |
+
"""
|
| 512 |
+
|
| 513 |
+
PRUNING_TYPE = "unstructured"
|
| 514 |
+
|
| 515 |
+
def __init__(self, amount) -> None:
|
| 516 |
+
# Check range of validity of pruning amount
|
| 517 |
+
_validate_pruning_amount_init(amount)
|
| 518 |
+
self.amount = amount
|
| 519 |
+
|
| 520 |
+
def compute_mask(self, t, default_mask):
|
| 521 |
+
# Check that the amount of units to prune is not > than the number of
|
| 522 |
+
# parameters in t
|
| 523 |
+
tensor_size = t.nelement()
|
| 524 |
+
# Compute number of units to prune: amount if int,
|
| 525 |
+
# else amount * tensor_size
|
| 526 |
+
nparams_toprune = _compute_nparams_toprune(self.amount, tensor_size)
|
| 527 |
+
# This should raise an error if the number of units to prune is larger
|
| 528 |
+
# than the number of units in the tensor
|
| 529 |
+
_validate_pruning_amount(nparams_toprune, tensor_size)
|
| 530 |
+
|
| 531 |
+
mask = default_mask.clone(memory_format=torch.contiguous_format)
|
| 532 |
+
|
| 533 |
+
if nparams_toprune != 0: # k=0 not supported by torch.kthvalue
|
| 534 |
+
# largest=True --> top k; largest=False --> bottom k
|
| 535 |
+
# Prune the smallest k
|
| 536 |
+
topk = torch.topk(torch.abs(t).view(-1), k=nparams_toprune, largest=False)
|
| 537 |
+
# topk will have .indices and .values
|
| 538 |
+
mask.view(-1)[topk.indices] = 0
|
| 539 |
+
|
| 540 |
+
return mask
|
| 541 |
+
|
| 542 |
+
@classmethod
|
| 543 |
+
def apply(cls, module, name, amount, importance_scores=None): # type: ignore[override]
|
| 544 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 545 |
+
|
| 546 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 547 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 548 |
+
and the pruning mask.
|
| 549 |
+
|
| 550 |
+
Args:
|
| 551 |
+
module (nn.Module): module containing the tensor to prune
|
| 552 |
+
name (str): parameter name within ``module`` on which pruning
|
| 553 |
+
will act.
|
| 554 |
+
amount (int or float): quantity of parameters to prune.
|
| 555 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 556 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 557 |
+
absolute number of parameters to prune.
|
| 558 |
+
importance_scores (torch.Tensor): tensor of importance scores (of same
|
| 559 |
+
shape as module parameter) used to compute mask for pruning.
|
| 560 |
+
The values in this tensor indicate the importance of the corresponding
|
| 561 |
+
elements in the parameter being pruned.
|
| 562 |
+
If unspecified or None, the module parameter will be used in its place.
|
| 563 |
+
"""
|
| 564 |
+
return super().apply(
|
| 565 |
+
module, name, amount=amount, importance_scores=importance_scores
|
| 566 |
+
)
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
class RandomStructured(BasePruningMethod):
|
| 570 |
+
r"""Prune entire (currently unpruned) channels in a tensor at random.
|
| 571 |
+
|
| 572 |
+
Args:
|
| 573 |
+
amount (int or float): quantity of parameters to prune.
|
| 574 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 575 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 576 |
+
absolute number of parameters to prune.
|
| 577 |
+
dim (int, optional): index of the dim along which we define
|
| 578 |
+
channels to prune. Default: -1.
|
| 579 |
+
"""
|
| 580 |
+
|
| 581 |
+
PRUNING_TYPE = "structured"
|
| 582 |
+
|
| 583 |
+
def __init__(self, amount, dim=-1) -> None:
|
| 584 |
+
# Check range of validity of amount
|
| 585 |
+
_validate_pruning_amount_init(amount)
|
| 586 |
+
self.amount = amount
|
| 587 |
+
self.dim = dim
|
| 588 |
+
|
| 589 |
+
def compute_mask(self, t, default_mask):
|
| 590 |
+
r"""Compute and returns a mask for the input tensor ``t``.
|
| 591 |
+
|
| 592 |
+
Starting from a base ``default_mask`` (which should be a mask of ones
|
| 593 |
+
if the tensor has not been pruned yet), generate a random mask to
|
| 594 |
+
apply on top of the ``default_mask`` by randomly zeroing out channels
|
| 595 |
+
along the specified dim of the tensor.
|
| 596 |
+
|
| 597 |
+
Args:
|
| 598 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 599 |
+
default_mask (torch.Tensor): Base mask from previous pruning
|
| 600 |
+
iterations, that need to be respected after the new mask is
|
| 601 |
+
applied. Same dims as ``t``.
|
| 602 |
+
|
| 603 |
+
Returns:
|
| 604 |
+
mask (torch.Tensor): mask to apply to ``t``, of same dims as ``t``
|
| 605 |
+
|
| 606 |
+
Raises:
|
| 607 |
+
IndexError: if ``self.dim >= len(t.shape)``
|
| 608 |
+
"""
|
| 609 |
+
# Check that tensor has structure (i.e. more than 1 dimension) such
|
| 610 |
+
# that the concept of "channels" makes sense
|
| 611 |
+
_validate_structured_pruning(t)
|
| 612 |
+
|
| 613 |
+
# Check that self.dim is a valid dim to index t, else raise IndexError
|
| 614 |
+
_validate_pruning_dim(t, self.dim)
|
| 615 |
+
|
| 616 |
+
# Check that the amount of channels to prune is not > than the number of
|
| 617 |
+
# channels in t along the dim to prune
|
| 618 |
+
tensor_size = t.shape[self.dim]
|
| 619 |
+
# Compute number of units to prune: amount if int,
|
| 620 |
+
# else amount * tensor_size
|
| 621 |
+
nparams_toprune = _compute_nparams_toprune(self.amount, tensor_size)
|
| 622 |
+
# This should raise an error if the number of units to prune is larger
|
| 623 |
+
# than the number of units in the tensor
|
| 624 |
+
_validate_pruning_amount(nparams_toprune, tensor_size)
|
| 625 |
+
|
| 626 |
+
# Compute binary mask by initializing it to all 0s and then filling in
|
| 627 |
+
# 1s wherever topk.indices indicates, along self.dim.
|
| 628 |
+
# mask has the same shape as tensor t
|
| 629 |
+
def make_mask(t, dim, nchannels, nchannels_toprune):
|
| 630 |
+
# generate a random number in [0, 1] to associate to each channel
|
| 631 |
+
prob = torch.rand(nchannels)
|
| 632 |
+
# generate mask for each channel by 0ing out the channels that
|
| 633 |
+
# got assigned the k = nchannels_toprune lowest values in prob
|
| 634 |
+
threshold = torch.kthvalue(prob, k=nchannels_toprune).values
|
| 635 |
+
channel_mask = prob > threshold
|
| 636 |
+
|
| 637 |
+
mask = torch.zeros_like(t)
|
| 638 |
+
slc = [slice(None)] * len(t.shape)
|
| 639 |
+
slc[dim] = channel_mask
|
| 640 |
+
slc = tuple(slc)
|
| 641 |
+
mask[slc] = 1
|
| 642 |
+
return mask
|
| 643 |
+
|
| 644 |
+
if nparams_toprune == 0: # k=0 not supported by torch.kthvalue
|
| 645 |
+
mask = default_mask
|
| 646 |
+
else:
|
| 647 |
+
# apply the new structured mask on top of prior (potentially
|
| 648 |
+
# unstructured) mask
|
| 649 |
+
mask = make_mask(t, self.dim, tensor_size, nparams_toprune)
|
| 650 |
+
mask *= default_mask.to(dtype=mask.dtype)
|
| 651 |
+
return mask
|
| 652 |
+
|
| 653 |
+
@classmethod
|
| 654 |
+
def apply(cls, module, name, amount, dim=-1): # type: ignore[override]
|
| 655 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 656 |
+
|
| 657 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 658 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 659 |
+
and the pruning mask.
|
| 660 |
+
|
| 661 |
+
Args:
|
| 662 |
+
module (nn.Module): module containing the tensor to prune
|
| 663 |
+
name (str): parameter name within ``module`` on which pruning
|
| 664 |
+
will act.
|
| 665 |
+
amount (int or float): quantity of parameters to prune.
|
| 666 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 667 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 668 |
+
absolute number of parameters to prune.
|
| 669 |
+
dim (int, optional): index of the dim along which we define
|
| 670 |
+
channels to prune. Default: -1.
|
| 671 |
+
"""
|
| 672 |
+
return super().apply(module, name, amount=amount, dim=dim)
|
| 673 |
+
|
| 674 |
+
|
| 675 |
+
class LnStructured(BasePruningMethod):
|
| 676 |
+
r"""Prune entire (currently unpruned) channels in a tensor based on their L\ ``n``-norm.
|
| 677 |
+
|
| 678 |
+
Args:
|
| 679 |
+
amount (int or float): quantity of channels to prune.
|
| 680 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 681 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 682 |
+
absolute number of parameters to prune.
|
| 683 |
+
n (int, float, inf, -inf, 'fro', 'nuc'): See documentation of valid
|
| 684 |
+
entries for argument ``p`` in :func:`torch.norm`.
|
| 685 |
+
dim (int, optional): index of the dim along which we define
|
| 686 |
+
channels to prune. Default: -1.
|
| 687 |
+
"""
|
| 688 |
+
|
| 689 |
+
PRUNING_TYPE = "structured"
|
| 690 |
+
|
| 691 |
+
def __init__(self, amount, n, dim=-1) -> None:
|
| 692 |
+
# Check range of validity of amount
|
| 693 |
+
_validate_pruning_amount_init(amount)
|
| 694 |
+
self.amount = amount
|
| 695 |
+
self.n = n
|
| 696 |
+
self.dim = dim
|
| 697 |
+
|
| 698 |
+
def compute_mask(self, t, default_mask):
|
| 699 |
+
r"""Compute and returns a mask for the input tensor ``t``.
|
| 700 |
+
|
| 701 |
+
Starting from a base ``default_mask`` (which should be a mask of ones
|
| 702 |
+
if the tensor has not been pruned yet), generate a mask to apply on
|
| 703 |
+
top of the ``default_mask`` by zeroing out the channels along the
|
| 704 |
+
specified dim with the lowest L\ ``n``-norm.
|
| 705 |
+
|
| 706 |
+
Args:
|
| 707 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 708 |
+
default_mask (torch.Tensor): Base mask from previous pruning
|
| 709 |
+
iterations, that need to be respected after the new mask is
|
| 710 |
+
applied. Same dims as ``t``.
|
| 711 |
+
|
| 712 |
+
Returns:
|
| 713 |
+
mask (torch.Tensor): mask to apply to ``t``, of same dims as ``t``
|
| 714 |
+
|
| 715 |
+
Raises:
|
| 716 |
+
IndexError: if ``self.dim >= len(t.shape)``
|
| 717 |
+
"""
|
| 718 |
+
# Check that tensor has structure (i.e. more than 1 dimension) such
|
| 719 |
+
# that the concept of "channels" makes sense
|
| 720 |
+
_validate_structured_pruning(t)
|
| 721 |
+
# Check that self.dim is a valid dim to index t, else raise IndexError
|
| 722 |
+
_validate_pruning_dim(t, self.dim)
|
| 723 |
+
|
| 724 |
+
# Check that the amount of channels to prune is not > than the number of
|
| 725 |
+
# channels in t along the dim to prune
|
| 726 |
+
tensor_size = t.shape[self.dim]
|
| 727 |
+
# Compute number of units to prune: amount if int,
|
| 728 |
+
# else amount * tensor_size
|
| 729 |
+
nparams_toprune = _compute_nparams_toprune(self.amount, tensor_size)
|
| 730 |
+
nparams_tokeep = tensor_size - nparams_toprune
|
| 731 |
+
# This should raise an error if the number of units to prune is larger
|
| 732 |
+
# than the number of units in the tensor
|
| 733 |
+
_validate_pruning_amount(nparams_toprune, tensor_size)
|
| 734 |
+
|
| 735 |
+
# Structured pruning prunes entire channels so we need to know the
|
| 736 |
+
# L_n norm along each channel to then find the topk based on this
|
| 737 |
+
# metric
|
| 738 |
+
norm = _compute_norm(t, self.n, self.dim)
|
| 739 |
+
# largest=True --> top k; largest=False --> bottom k
|
| 740 |
+
# Keep the largest k channels along dim=self.dim
|
| 741 |
+
topk = torch.topk(norm, k=nparams_tokeep, largest=True)
|
| 742 |
+
# topk will have .indices and .values
|
| 743 |
+
|
| 744 |
+
# Compute binary mask by initializing it to all 0s and then filling in
|
| 745 |
+
# 1s wherever topk.indices indicates, along self.dim.
|
| 746 |
+
# mask has the same shape as tensor t
|
| 747 |
+
def make_mask(t, dim, indices):
|
| 748 |
+
# init mask to 0
|
| 749 |
+
mask = torch.zeros_like(t)
|
| 750 |
+
# e.g.: slc = [None, None, None], if len(t.shape) = 3
|
| 751 |
+
slc = [slice(None)] * len(t.shape)
|
| 752 |
+
# replace a None at position=dim with indices
|
| 753 |
+
# e.g.: slc = [None, None, [0, 2, 3]] if dim=2 & indices=[0,2,3]
|
| 754 |
+
slc[dim] = indices
|
| 755 |
+
slc = tuple(slc)
|
| 756 |
+
# use slc to slice mask and replace all its entries with 1s
|
| 757 |
+
# e.g.: mask[:, :, [0, 2, 3]] = 1
|
| 758 |
+
mask[slc] = 1
|
| 759 |
+
return mask
|
| 760 |
+
|
| 761 |
+
if nparams_toprune == 0: # k=0 not supported by torch.kthvalue
|
| 762 |
+
mask = default_mask
|
| 763 |
+
else:
|
| 764 |
+
mask = make_mask(t, self.dim, topk.indices)
|
| 765 |
+
mask *= default_mask.to(dtype=mask.dtype)
|
| 766 |
+
|
| 767 |
+
return mask
|
| 768 |
+
|
| 769 |
+
@classmethod
|
| 770 |
+
def apply(cls, module, name, amount, n, dim, importance_scores=None): # type: ignore[override]
|
| 771 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 772 |
+
|
| 773 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 774 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 775 |
+
and the pruning mask.
|
| 776 |
+
|
| 777 |
+
Args:
|
| 778 |
+
module (nn.Module): module containing the tensor to prune
|
| 779 |
+
name (str): parameter name within ``module`` on which pruning
|
| 780 |
+
will act.
|
| 781 |
+
amount (int or float): quantity of parameters to prune.
|
| 782 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 783 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 784 |
+
absolute number of parameters to prune.
|
| 785 |
+
n (int, float, inf, -inf, 'fro', 'nuc'): See documentation of valid
|
| 786 |
+
entries for argument ``p`` in :func:`torch.norm`.
|
| 787 |
+
dim (int): index of the dim along which we define channels to
|
| 788 |
+
prune.
|
| 789 |
+
importance_scores (torch.Tensor): tensor of importance scores (of same
|
| 790 |
+
shape as module parameter) used to compute mask for pruning.
|
| 791 |
+
The values in this tensor indicate the importance of the corresponding
|
| 792 |
+
elements in the parameter being pruned.
|
| 793 |
+
If unspecified or None, the module parameter will be used in its place.
|
| 794 |
+
"""
|
| 795 |
+
return super().apply(
|
| 796 |
+
module,
|
| 797 |
+
name,
|
| 798 |
+
amount=amount,
|
| 799 |
+
n=n,
|
| 800 |
+
dim=dim,
|
| 801 |
+
importance_scores=importance_scores,
|
| 802 |
+
)
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
class CustomFromMask(BasePruningMethod):
|
| 806 |
+
PRUNING_TYPE = "global"
|
| 807 |
+
|
| 808 |
+
def __init__(self, mask) -> None:
|
| 809 |
+
self.mask = mask
|
| 810 |
+
|
| 811 |
+
def compute_mask(self, t, default_mask):
|
| 812 |
+
if default_mask.shape != self.mask.shape:
|
| 813 |
+
raise AssertionError(
|
| 814 |
+
f"default_mask shape {default_mask.shape} must match "
|
| 815 |
+
f"self.mask shape {self.mask.shape}"
|
| 816 |
+
)
|
| 817 |
+
mask = default_mask * self.mask.to(dtype=default_mask.dtype)
|
| 818 |
+
return mask
|
| 819 |
+
|
| 820 |
+
@classmethod
|
| 821 |
+
def apply(cls, module, name, mask): # type: ignore[override]
|
| 822 |
+
r"""Add pruning on the fly and reparameterization of a tensor.
|
| 823 |
+
|
| 824 |
+
Adds the forward pre-hook that enables pruning on the fly and
|
| 825 |
+
the reparameterization of a tensor in terms of the original tensor
|
| 826 |
+
and the pruning mask.
|
| 827 |
+
|
| 828 |
+
Args:
|
| 829 |
+
module (nn.Module): module containing the tensor to prune
|
| 830 |
+
name (str): parameter name within ``module`` on which pruning
|
| 831 |
+
will act.
|
| 832 |
+
"""
|
| 833 |
+
return super().apply(module, name, mask=mask)
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
def identity(module, name):
|
| 837 |
+
r"""Apply pruning reparameterization without pruning any units.
|
| 838 |
+
|
| 839 |
+
Applies pruning reparameterization to the tensor corresponding to the
|
| 840 |
+
parameter called ``name`` in ``module`` without actually pruning any
|
| 841 |
+
units. Modifies module in place (and also return the modified module)
|
| 842 |
+
by:
|
| 843 |
+
|
| 844 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 845 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 846 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 847 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 848 |
+
``name+'_orig'``.
|
| 849 |
+
|
| 850 |
+
Note:
|
| 851 |
+
The mask is a tensor of ones.
|
| 852 |
+
|
| 853 |
+
Args:
|
| 854 |
+
module (nn.Module): module containing the tensor to prune.
|
| 855 |
+
name (str): parameter name within ``module`` on which pruning
|
| 856 |
+
will act.
|
| 857 |
+
|
| 858 |
+
Returns:
|
| 859 |
+
module (nn.Module): modified (i.e. pruned) version of the input module
|
| 860 |
+
|
| 861 |
+
Examples:
|
| 862 |
+
>>> # xdoctest: +SKIP
|
| 863 |
+
>>> m = prune.identity(nn.Linear(2, 3), "bias")
|
| 864 |
+
>>> print(m.bias_mask)
|
| 865 |
+
tensor([1., 1., 1.])
|
| 866 |
+
"""
|
| 867 |
+
Identity.apply(module, name)
|
| 868 |
+
return module
|
| 869 |
+
|
| 870 |
+
|
| 871 |
+
def random_unstructured(module, name, amount):
|
| 872 |
+
r"""Prune tensor by removing random (currently unpruned) units.
|
| 873 |
+
|
| 874 |
+
Prunes tensor corresponding to parameter called ``name`` in ``module``
|
| 875 |
+
by removing the specified ``amount`` of (currently unpruned) units
|
| 876 |
+
selected at random.
|
| 877 |
+
Modifies module in place (and also return the modified module) by:
|
| 878 |
+
|
| 879 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 880 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 881 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 882 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 883 |
+
``name+'_orig'``.
|
| 884 |
+
|
| 885 |
+
Args:
|
| 886 |
+
module (nn.Module): module containing the tensor to prune
|
| 887 |
+
name (str): parameter name within ``module`` on which pruning
|
| 888 |
+
will act.
|
| 889 |
+
amount (int or float): quantity of parameters to prune.
|
| 890 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 891 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 892 |
+
absolute number of parameters to prune.
|
| 893 |
+
|
| 894 |
+
Returns:
|
| 895 |
+
module (nn.Module): modified (i.e. pruned) version of the input module
|
| 896 |
+
|
| 897 |
+
Examples:
|
| 898 |
+
>>> # xdoctest: +SKIP
|
| 899 |
+
>>> m = prune.random_unstructured(nn.Linear(2, 3), "weight", amount=1)
|
| 900 |
+
>>> torch.sum(m.weight_mask == 0)
|
| 901 |
+
tensor(1)
|
| 902 |
+
|
| 903 |
+
"""
|
| 904 |
+
RandomUnstructured.apply(module, name, amount)
|
| 905 |
+
return module
|
| 906 |
+
|
| 907 |
+
|
| 908 |
+
def l1_unstructured(module, name, amount, importance_scores=None):
|
| 909 |
+
r"""Prune tensor by removing units with the lowest L1-norm.
|
| 910 |
+
|
| 911 |
+
Prunes tensor corresponding to parameter called ``name`` in ``module``
|
| 912 |
+
by removing the specified `amount` of (currently unpruned) units with the
|
| 913 |
+
lowest L1-norm.
|
| 914 |
+
Modifies module in place (and also return the modified module)
|
| 915 |
+
by:
|
| 916 |
+
|
| 917 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 918 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 919 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 920 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 921 |
+
``name+'_orig'``.
|
| 922 |
+
|
| 923 |
+
Args:
|
| 924 |
+
module (nn.Module): module containing the tensor to prune
|
| 925 |
+
name (str): parameter name within ``module`` on which pruning
|
| 926 |
+
will act.
|
| 927 |
+
amount (int or float): quantity of parameters to prune.
|
| 928 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 929 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 930 |
+
absolute number of parameters to prune.
|
| 931 |
+
importance_scores (torch.Tensor): tensor of importance scores (of same
|
| 932 |
+
shape as module parameter) used to compute mask for pruning.
|
| 933 |
+
The values in this tensor indicate the importance of the corresponding
|
| 934 |
+
elements in the parameter being pruned.
|
| 935 |
+
If unspecified or None, the module parameter will be used in its place.
|
| 936 |
+
|
| 937 |
+
Returns:
|
| 938 |
+
module (nn.Module): modified (i.e. pruned) version of the input module
|
| 939 |
+
|
| 940 |
+
Examples:
|
| 941 |
+
>>> # xdoctest: +SKIP
|
| 942 |
+
>>> m = prune.l1_unstructured(nn.Linear(2, 3), "weight", amount=0.2)
|
| 943 |
+
>>> m.state_dict().keys()
|
| 944 |
+
odict_keys(['bias', 'weight_orig', 'weight_mask'])
|
| 945 |
+
"""
|
| 946 |
+
L1Unstructured.apply(
|
| 947 |
+
module, name, amount=amount, importance_scores=importance_scores
|
| 948 |
+
)
|
| 949 |
+
return module
|
| 950 |
+
|
| 951 |
+
|
| 952 |
+
def random_structured(module, name, amount, dim):
|
| 953 |
+
r"""Prune tensor by removing random channels along the specified dimension.
|
| 954 |
+
|
| 955 |
+
Prunes tensor corresponding to parameter called ``name`` in ``module``
|
| 956 |
+
by removing the specified ``amount`` of (currently unpruned) channels
|
| 957 |
+
along the specified ``dim`` selected at random.
|
| 958 |
+
Modifies module in place (and also return the modified module)
|
| 959 |
+
by:
|
| 960 |
+
|
| 961 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 962 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 963 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 964 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 965 |
+
``name+'_orig'``.
|
| 966 |
+
|
| 967 |
+
Args:
|
| 968 |
+
module (nn.Module): module containing the tensor to prune
|
| 969 |
+
name (str): parameter name within ``module`` on which pruning
|
| 970 |
+
will act.
|
| 971 |
+
amount (int or float): quantity of parameters to prune.
|
| 972 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 973 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 974 |
+
absolute number of parameters to prune.
|
| 975 |
+
dim (int): index of the dim along which we define channels to prune.
|
| 976 |
+
|
| 977 |
+
Returns:
|
| 978 |
+
module (nn.Module): modified (i.e. pruned) version of the input module
|
| 979 |
+
|
| 980 |
+
Examples:
|
| 981 |
+
>>> # xdoctest: +SKIP
|
| 982 |
+
>>> m = prune.random_structured(nn.Linear(5, 3), "weight", amount=3, dim=1)
|
| 983 |
+
>>> columns_pruned = int(sum(torch.sum(m.weight, dim=0) == 0))
|
| 984 |
+
>>> print(columns_pruned)
|
| 985 |
+
3
|
| 986 |
+
"""
|
| 987 |
+
RandomStructured.apply(module, name, amount, dim)
|
| 988 |
+
return module
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
def ln_structured(module, name, amount, n, dim, importance_scores=None):
|
| 992 |
+
r"""Prune tensor by removing channels with the lowest L\ ``n``-norm along the specified dimension.
|
| 993 |
+
|
| 994 |
+
Prunes tensor corresponding to parameter called ``name`` in ``module``
|
| 995 |
+
by removing the specified ``amount`` of (currently unpruned) channels
|
| 996 |
+
along the specified ``dim`` with the lowest L\ ``n``-norm.
|
| 997 |
+
Modifies module in place (and also return the modified module)
|
| 998 |
+
by:
|
| 999 |
+
|
| 1000 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 1001 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 1002 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 1003 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 1004 |
+
``name+'_orig'``.
|
| 1005 |
+
|
| 1006 |
+
Args:
|
| 1007 |
+
module (nn.Module): module containing the tensor to prune
|
| 1008 |
+
name (str): parameter name within ``module`` on which pruning
|
| 1009 |
+
will act.
|
| 1010 |
+
amount (int or float): quantity of parameters to prune.
|
| 1011 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 1012 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 1013 |
+
absolute number of parameters to prune.
|
| 1014 |
+
n (int, float, inf, -inf, 'fro', 'nuc'): See documentation of valid
|
| 1015 |
+
entries for argument ``p`` in :func:`torch.norm`.
|
| 1016 |
+
dim (int): index of the dim along which we define channels to prune.
|
| 1017 |
+
importance_scores (torch.Tensor): tensor of importance scores (of same
|
| 1018 |
+
shape as module parameter) used to compute mask for pruning.
|
| 1019 |
+
The values in this tensor indicate the importance of the corresponding
|
| 1020 |
+
elements in the parameter being pruned.
|
| 1021 |
+
If unspecified or None, the module parameter will be used in its place.
|
| 1022 |
+
|
| 1023 |
+
Returns:
|
| 1024 |
+
module (nn.Module): modified (i.e. pruned) version of the input module
|
| 1025 |
+
|
| 1026 |
+
Examples:
|
| 1027 |
+
>>> from torch.nn.utils import prune
|
| 1028 |
+
>>> m = prune.ln_structured(
|
| 1029 |
+
... nn.Conv2d(5, 3, 2), "weight", amount=0.3, dim=1, n=float("-inf")
|
| 1030 |
+
... )
|
| 1031 |
+
"""
|
| 1032 |
+
LnStructured.apply(
|
| 1033 |
+
module, name, amount, n, dim, importance_scores=importance_scores
|
| 1034 |
+
)
|
| 1035 |
+
return module
|
| 1036 |
+
|
| 1037 |
+
|
| 1038 |
+
def global_unstructured(
|
| 1039 |
+
parameters, pruning_method, importance_scores=None, **kwargs
|
| 1040 |
+
) -> None:
|
| 1041 |
+
r"""
|
| 1042 |
+
Globally prunes tensors corresponding to all parameters in ``parameters`` by applying the specified ``pruning_method``.
|
| 1043 |
+
|
| 1044 |
+
Modifies modules in place by:
|
| 1045 |
+
|
| 1046 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 1047 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 1048 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 1049 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 1050 |
+
``name+'_orig'``.
|
| 1051 |
+
|
| 1052 |
+
Args:
|
| 1053 |
+
parameters (Iterable of (module, name) tuples): parameters of
|
| 1054 |
+
the model to prune in a global fashion, i.e. by aggregating all
|
| 1055 |
+
weights prior to deciding which ones to prune. module must be of
|
| 1056 |
+
type :class:`nn.Module`, and name must be a string.
|
| 1057 |
+
pruning_method (function): a valid pruning function from this module,
|
| 1058 |
+
or a custom one implemented by the user that satisfies the
|
| 1059 |
+
implementation guidelines and has ``PRUNING_TYPE='unstructured'``.
|
| 1060 |
+
importance_scores (dict): a dictionary mapping (module, name) tuples to
|
| 1061 |
+
the corresponding parameter's importance scores tensor. The tensor
|
| 1062 |
+
should be the same shape as the parameter, and is used for computing
|
| 1063 |
+
mask for pruning.
|
| 1064 |
+
If unspecified or None, the parameter will be used in place of its
|
| 1065 |
+
importance scores.
|
| 1066 |
+
kwargs: other keyword arguments such as:
|
| 1067 |
+
amount (int or float): quantity of parameters to prune across the
|
| 1068 |
+
specified parameters.
|
| 1069 |
+
If ``float``, should be between 0.0 and 1.0 and represent the
|
| 1070 |
+
fraction of parameters to prune. If ``int``, it represents the
|
| 1071 |
+
absolute number of parameters to prune.
|
| 1072 |
+
|
| 1073 |
+
Raises:
|
| 1074 |
+
TypeError: if ``PRUNING_TYPE != 'unstructured'``
|
| 1075 |
+
|
| 1076 |
+
Note:
|
| 1077 |
+
Since global structured pruning doesn't make much sense unless the
|
| 1078 |
+
norm is normalized by the size of the parameter, we now limit the
|
| 1079 |
+
scope of global pruning to unstructured methods.
|
| 1080 |
+
|
| 1081 |
+
Examples:
|
| 1082 |
+
>>> from torch.nn.utils import prune
|
| 1083 |
+
>>> from collections import OrderedDict
|
| 1084 |
+
>>> net = nn.Sequential(
|
| 1085 |
+
... OrderedDict(
|
| 1086 |
+
... [
|
| 1087 |
+
... ("first", nn.Linear(10, 4)),
|
| 1088 |
+
... ("second", nn.Linear(4, 1)),
|
| 1089 |
+
... ]
|
| 1090 |
+
... )
|
| 1091 |
+
... )
|
| 1092 |
+
>>> parameters_to_prune = (
|
| 1093 |
+
... (net.first, "weight"),
|
| 1094 |
+
... (net.second, "weight"),
|
| 1095 |
+
... )
|
| 1096 |
+
>>> prune.global_unstructured(
|
| 1097 |
+
... parameters_to_prune,
|
| 1098 |
+
... pruning_method=prune.L1Unstructured,
|
| 1099 |
+
... amount=10,
|
| 1100 |
+
... )
|
| 1101 |
+
>>> print(sum(torch.nn.utils.parameters_to_vector(net.buffers()) == 0))
|
| 1102 |
+
tensor(10)
|
| 1103 |
+
|
| 1104 |
+
"""
|
| 1105 |
+
# ensure parameters is a list or generator of tuples
|
| 1106 |
+
if not isinstance(parameters, Iterable):
|
| 1107 |
+
raise TypeError("global_unstructured(): parameters is not an Iterable")
|
| 1108 |
+
|
| 1109 |
+
importance_scores = importance_scores if importance_scores is not None else {}
|
| 1110 |
+
if not isinstance(importance_scores, dict):
|
| 1111 |
+
raise TypeError("global_unstructured(): importance_scores must be of type dict")
|
| 1112 |
+
|
| 1113 |
+
# flatten importance scores to consider them all at once in global pruning
|
| 1114 |
+
relevant_importance_scores = torch.nn.utils.parameters_to_vector(
|
| 1115 |
+
# pyrefly: ignore [bad-argument-type]
|
| 1116 |
+
[
|
| 1117 |
+
importance_scores.get((module, name), getattr(module, name))
|
| 1118 |
+
for (module, name) in parameters
|
| 1119 |
+
]
|
| 1120 |
+
)
|
| 1121 |
+
# similarly, flatten the masks (if they exist), or use a flattened vector
|
| 1122 |
+
# of 1s of the same dimensions as t
|
| 1123 |
+
default_mask = torch.nn.utils.parameters_to_vector(
|
| 1124 |
+
[
|
| 1125 |
+
getattr(module, name + "_mask", torch.ones_like(getattr(module, name)))
|
| 1126 |
+
for (module, name) in parameters
|
| 1127 |
+
]
|
| 1128 |
+
)
|
| 1129 |
+
|
| 1130 |
+
# use the canonical pruning methods to compute the new mask, even if the
|
| 1131 |
+
# parameter is now a flattened out version of `parameters`
|
| 1132 |
+
container = PruningContainer()
|
| 1133 |
+
container._tensor_name = "temp" # to make it match that of `method`
|
| 1134 |
+
method = pruning_method(**kwargs)
|
| 1135 |
+
method._tensor_name = "temp" # to make it match that of `container`
|
| 1136 |
+
if method.PRUNING_TYPE != "unstructured":
|
| 1137 |
+
raise TypeError(
|
| 1138 |
+
'Only "unstructured" PRUNING_TYPE supported for '
|
| 1139 |
+
f"the `pruning_method`. Found method {pruning_method} of type {method.PRUNING_TYPE}"
|
| 1140 |
+
)
|
| 1141 |
+
|
| 1142 |
+
container.add_pruning_method(method)
|
| 1143 |
+
|
| 1144 |
+
# use the `compute_mask` method from `PruningContainer` to combine the
|
| 1145 |
+
# mask computed by the new method with the pre-existing mask
|
| 1146 |
+
final_mask = container.compute_mask(relevant_importance_scores, default_mask)
|
| 1147 |
+
|
| 1148 |
+
# Pointer for slicing the mask to match the shape of each parameter
|
| 1149 |
+
pointer = 0
|
| 1150 |
+
for module, name in parameters:
|
| 1151 |
+
param = getattr(module, name)
|
| 1152 |
+
# The length of the parameter
|
| 1153 |
+
num_param = param.numel()
|
| 1154 |
+
# Slice the mask, reshape it
|
| 1155 |
+
param_mask = final_mask[pointer : pointer + num_param].view_as(param)
|
| 1156 |
+
# Assign the correct pre-computed mask to each parameter and add it
|
| 1157 |
+
# to the forward_pre_hooks like any other pruning method
|
| 1158 |
+
custom_from_mask(module, name, mask=param_mask)
|
| 1159 |
+
|
| 1160 |
+
# Increment the pointer to continue slicing the final_mask
|
| 1161 |
+
pointer += num_param
|
| 1162 |
+
|
| 1163 |
+
|
| 1164 |
+
def custom_from_mask(module, name, mask):
|
| 1165 |
+
r"""Prune tensor corresponding to parameter called ``name`` in ``module`` by applying the pre-computed mask in ``mask``.
|
| 1166 |
+
|
| 1167 |
+
Modifies module in place (and also return the modified module) by:
|
| 1168 |
+
|
| 1169 |
+
1) adding a named buffer called ``name+'_mask'`` corresponding to the
|
| 1170 |
+
binary mask applied to the parameter ``name`` by the pruning method.
|
| 1171 |
+
2) replacing the parameter ``name`` by its pruned version, while the
|
| 1172 |
+
original (unpruned) parameter is stored in a new parameter named
|
| 1173 |
+
``name+'_orig'``.
|
| 1174 |
+
|
| 1175 |
+
Args:
|
| 1176 |
+
module (nn.Module): module containing the tensor to prune
|
| 1177 |
+
name (str): parameter name within ``module`` on which pruning
|
| 1178 |
+
will act.
|
| 1179 |
+
mask (Tensor): binary mask to be applied to the parameter.
|
| 1180 |
+
|
| 1181 |
+
Returns:
|
| 1182 |
+
module (nn.Module): modified (i.e. pruned) version of the input module
|
| 1183 |
+
|
| 1184 |
+
Examples:
|
| 1185 |
+
>>> from torch.nn.utils import prune
|
| 1186 |
+
>>> m = prune.custom_from_mask(
|
| 1187 |
+
... nn.Linear(5, 3), name="bias", mask=torch.tensor([0, 1, 0])
|
| 1188 |
+
... )
|
| 1189 |
+
>>> print(m.bias_mask)
|
| 1190 |
+
tensor([0., 1., 0.])
|
| 1191 |
+
|
| 1192 |
+
"""
|
| 1193 |
+
CustomFromMask.apply(module, name, mask)
|
| 1194 |
+
return module
|
| 1195 |
+
|
| 1196 |
+
|
| 1197 |
+
def remove(module, name):
|
| 1198 |
+
r"""Remove the pruning reparameterization from a module and the pruning method from the forward hook.
|
| 1199 |
+
|
| 1200 |
+
The pruned parameter named ``name`` remains permanently pruned, and the parameter
|
| 1201 |
+
named ``name+'_orig'`` is removed from the parameter list. Similarly,
|
| 1202 |
+
the buffer named ``name+'_mask'`` is removed from the buffers.
|
| 1203 |
+
|
| 1204 |
+
Note:
|
| 1205 |
+
Pruning itself is NOT undone or reversed!
|
| 1206 |
+
|
| 1207 |
+
Args:
|
| 1208 |
+
module (nn.Module): module containing the tensor to prune
|
| 1209 |
+
name (str): parameter name within ``module`` on which pruning
|
| 1210 |
+
will act.
|
| 1211 |
+
|
| 1212 |
+
Examples:
|
| 1213 |
+
>>> m = random_unstructured(nn.Linear(5, 7), name="weight", amount=0.2)
|
| 1214 |
+
>>> m = remove(m, name="weight")
|
| 1215 |
+
"""
|
| 1216 |
+
for k, hook in module._forward_pre_hooks.items():
|
| 1217 |
+
if isinstance(hook, BasePruningMethod) and hook._tensor_name == name:
|
| 1218 |
+
hook.remove(module)
|
| 1219 |
+
del module._forward_pre_hooks[k]
|
| 1220 |
+
return module
|
| 1221 |
+
|
| 1222 |
+
raise ValueError(
|
| 1223 |
+
f"Parameter '{name}' of module {module} has to be pruned before pruning can be removed"
|
| 1224 |
+
)
|
| 1225 |
+
|
| 1226 |
+
|
| 1227 |
+
def is_pruned(module) -> bool:
|
| 1228 |
+
r"""Check if a module is pruned by looking for pruning pre-hooks.
|
| 1229 |
+
|
| 1230 |
+
Check whether ``module`` is pruned by looking for
|
| 1231 |
+
``forward_pre_hooks`` in its modules that inherit from the
|
| 1232 |
+
:class:`BasePruningMethod`.
|
| 1233 |
+
|
| 1234 |
+
Args:
|
| 1235 |
+
module (nn.Module): object that is either pruned or unpruned
|
| 1236 |
+
|
| 1237 |
+
Returns:
|
| 1238 |
+
binary answer to whether ``module`` is pruned.
|
| 1239 |
+
|
| 1240 |
+
Examples:
|
| 1241 |
+
>>> from torch.nn.utils import prune
|
| 1242 |
+
>>> m = nn.Linear(5, 7)
|
| 1243 |
+
>>> print(prune.is_pruned(m))
|
| 1244 |
+
False
|
| 1245 |
+
>>> prune.random_unstructured(m, name="weight", amount=0.2)
|
| 1246 |
+
>>> print(prune.is_pruned(m))
|
| 1247 |
+
True
|
| 1248 |
+
"""
|
| 1249 |
+
for _, submodule in module.named_modules():
|
| 1250 |
+
for hook in submodule._forward_pre_hooks.values():
|
| 1251 |
+
if isinstance(hook, BasePruningMethod):
|
| 1252 |
+
return True
|
| 1253 |
+
return False
|
| 1254 |
+
|
| 1255 |
+
|
| 1256 |
+
def _validate_pruning_amount_init(amount) -> None:
|
| 1257 |
+
r"""Validate helper to check the range of amount at init.
|
| 1258 |
+
|
| 1259 |
+
Args:
|
| 1260 |
+
amount (int or float): quantity of parameters to prune.
|
| 1261 |
+
If float, should be between 0.0 and 1.0 and represent the
|
| 1262 |
+
fraction of parameters to prune. If int, it represents the
|
| 1263 |
+
absolute number of parameters to prune.
|
| 1264 |
+
|
| 1265 |
+
Raises:
|
| 1266 |
+
ValueError: if amount is a float not in [0, 1], or if it's a negative
|
| 1267 |
+
integer.
|
| 1268 |
+
TypeError: if amount is neither a float nor an integer.
|
| 1269 |
+
|
| 1270 |
+
Note:
|
| 1271 |
+
This does not take into account the number of parameters in the
|
| 1272 |
+
tensor to be pruned, which is known only at prune.
|
| 1273 |
+
"""
|
| 1274 |
+
if not isinstance(amount, numbers.Real):
|
| 1275 |
+
raise TypeError(f"Invalid type for amount: {amount}. Must be int or float.")
|
| 1276 |
+
|
| 1277 |
+
if (isinstance(amount, numbers.Integral) and amount < 0) or (
|
| 1278 |
+
not isinstance(amount, numbers.Integral) # so it's a float
|
| 1279 |
+
and (float(amount) > 1.0 or float(amount) < 0.0)
|
| 1280 |
+
):
|
| 1281 |
+
raise ValueError(
|
| 1282 |
+
f"amount={amount} should either be a float in the range [0, 1] or a non-negative integer"
|
| 1283 |
+
)
|
| 1284 |
+
|
| 1285 |
+
|
| 1286 |
+
def _validate_pruning_amount(amount, tensor_size) -> None:
|
| 1287 |
+
r"""Validate that the pruning amount is meaningful wrt to the size of the data.
|
| 1288 |
+
|
| 1289 |
+
Validation helper to check that the amount of parameters to prune
|
| 1290 |
+
is meaningful wrt to the size of the data (`tensor_size`).
|
| 1291 |
+
|
| 1292 |
+
Args:
|
| 1293 |
+
amount (int or float): quantity of parameters to prune.
|
| 1294 |
+
If float, should be between 0.0 and 1.0 and represent the
|
| 1295 |
+
fraction of parameters to prune. If int, it represents the
|
| 1296 |
+
absolute number of parameters to prune.
|
| 1297 |
+
tensor_size (int): absolute number of parameters in the tensor
|
| 1298 |
+
to prune.
|
| 1299 |
+
"""
|
| 1300 |
+
# TODO: consider removing this check and allowing users to specify
|
| 1301 |
+
# a number of units to prune that is greater than the number of units
|
| 1302 |
+
# left to prune. In this case, the tensor will just be fully pruned.
|
| 1303 |
+
|
| 1304 |
+
if isinstance(amount, numbers.Integral) and amount > tensor_size:
|
| 1305 |
+
raise ValueError(
|
| 1306 |
+
f"amount={amount} should be smaller than the number of parameters to prune={tensor_size}"
|
| 1307 |
+
)
|
| 1308 |
+
|
| 1309 |
+
|
| 1310 |
+
def _validate_structured_pruning(t) -> None:
|
| 1311 |
+
r"""Validate that the tensor to be pruned is at least 2-Dimensional.
|
| 1312 |
+
|
| 1313 |
+
Validation helper to check that the tensor to be pruned is multi-
|
| 1314 |
+
dimensional, such that the concept of "channels" is well-defined.
|
| 1315 |
+
|
| 1316 |
+
Args:
|
| 1317 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 1318 |
+
|
| 1319 |
+
Raises:
|
| 1320 |
+
ValueError: if the tensor `t` is not at least 2D.
|
| 1321 |
+
"""
|
| 1322 |
+
shape = t.shape
|
| 1323 |
+
if len(shape) <= 1:
|
| 1324 |
+
raise ValueError(
|
| 1325 |
+
"Structured pruning can only be applied to "
|
| 1326 |
+
"multidimensional tensors. Found tensor of shape "
|
| 1327 |
+
f"{shape} with {len(shape)} dims"
|
| 1328 |
+
)
|
| 1329 |
+
|
| 1330 |
+
|
| 1331 |
+
def _compute_nparams_toprune(amount, tensor_size):
|
| 1332 |
+
r"""Convert the pruning amount from a percentage to absolute value.
|
| 1333 |
+
|
| 1334 |
+
Since amount can be expressed either in absolute value or as a
|
| 1335 |
+
percentage of the number of units/channels in a tensor, this utility
|
| 1336 |
+
function converts the percentage to absolute value to standardize
|
| 1337 |
+
the handling of pruning.
|
| 1338 |
+
|
| 1339 |
+
Args:
|
| 1340 |
+
amount (int or float): quantity of parameters to prune.
|
| 1341 |
+
If float, should be between 0.0 and 1.0 and represent the
|
| 1342 |
+
fraction of parameters to prune. If int, it represents the
|
| 1343 |
+
absolute number of parameters to prune.
|
| 1344 |
+
tensor_size (int): absolute number of parameters in the tensor
|
| 1345 |
+
to prune.
|
| 1346 |
+
|
| 1347 |
+
Returns:
|
| 1348 |
+
int: the number of units to prune in the tensor
|
| 1349 |
+
"""
|
| 1350 |
+
# incorrect type already checked in _validate_pruning_amount_init
|
| 1351 |
+
if isinstance(amount, numbers.Integral):
|
| 1352 |
+
return amount
|
| 1353 |
+
else:
|
| 1354 |
+
return round(amount * tensor_size)
|
| 1355 |
+
|
| 1356 |
+
|
| 1357 |
+
def _validate_pruning_dim(t, dim) -> None:
|
| 1358 |
+
r"""Validate that the pruning dimension is within the bounds of the tensor dimension.
|
| 1359 |
+
|
| 1360 |
+
Args:
|
| 1361 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 1362 |
+
dim (int): index of the dim along which we define channels to prune
|
| 1363 |
+
"""
|
| 1364 |
+
if dim >= t.dim():
|
| 1365 |
+
raise IndexError(f"Invalid index {dim} for tensor of size {t.shape}")
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
def _compute_norm(t, n, dim):
|
| 1369 |
+
r"""Compute the L_n-norm of a tensor along all dimensions except for the specified dimension.
|
| 1370 |
+
|
| 1371 |
+
The L_n-norm will be computed across all entries in tensor `t` along all dimension
|
| 1372 |
+
except for the one identified by dim.
|
| 1373 |
+
Example: if `t` is of shape, say, 3x2x4 and dim=2 (the last dim),
|
| 1374 |
+
then norm will have Size [4], and each entry will represent the
|
| 1375 |
+
`L_n`-norm computed using the 3x2=6 entries for each of the 4 channels.
|
| 1376 |
+
|
| 1377 |
+
Args:
|
| 1378 |
+
t (torch.Tensor): tensor representing the parameter to prune
|
| 1379 |
+
n (int, float, inf, -inf, 'fro', 'nuc'): See documentation of valid
|
| 1380 |
+
entries for argument p in torch.norm
|
| 1381 |
+
dim (int): dim identifying the channels to prune
|
| 1382 |
+
|
| 1383 |
+
Returns:
|
| 1384 |
+
norm (torch.Tensor): L_n norm computed across all dimensions except
|
| 1385 |
+
for `dim`. By construction, `norm.shape = t.shape[-1]`.
|
| 1386 |
+
"""
|
| 1387 |
+
# dims = all axes, except for the one identified by `dim`
|
| 1388 |
+
dims = list(range(t.dim()))
|
| 1389 |
+
# convert negative indexing
|
| 1390 |
+
if dim < 0:
|
| 1391 |
+
dim = dims[dim]
|
| 1392 |
+
dims.remove(dim)
|
| 1393 |
+
|
| 1394 |
+
norm = torch.norm(t, p=n, dim=dims)
|
| 1395 |
+
return norm
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/rnn.py
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
from collections.abc import Callable, Iterable
|
| 3 |
+
from typing import Any, NamedTuple, TypeVar
|
| 4 |
+
from typing_extensions import Self
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch import _VF, Tensor
|
| 8 |
+
from torch.utils._typing_utils import copy_method_params
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
"PackedSequence",
|
| 13 |
+
"invert_permutation",
|
| 14 |
+
"pack_padded_sequence",
|
| 15 |
+
"pad_packed_sequence",
|
| 16 |
+
"pad_sequence",
|
| 17 |
+
"unpad_sequence",
|
| 18 |
+
"pack_sequence",
|
| 19 |
+
"unpack_sequence",
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
_T = TypeVar("_T")
|
| 23 |
+
_R = TypeVar("_R")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class PackedSequence_(NamedTuple):
|
| 27 |
+
data: torch.Tensor
|
| 28 |
+
batch_sizes: torch.Tensor
|
| 29 |
+
sorted_indices: torch.Tensor | None
|
| 30 |
+
unsorted_indices: torch.Tensor | None
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def bind(optional: _T | None, fn: Callable[[_T], _R]) -> _R | None:
|
| 34 |
+
if optional is None:
|
| 35 |
+
return None
|
| 36 |
+
return fn(optional)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class PackedSequence(PackedSequence_):
|
| 40 |
+
r"""Holds the data and list of :attr:`batch_sizes` of a packed sequence.
|
| 41 |
+
|
| 42 |
+
All RNN modules accept packed sequences as inputs.
|
| 43 |
+
|
| 44 |
+
Note:
|
| 45 |
+
Instances of this class should never be created manually. They are meant
|
| 46 |
+
to be instantiated by functions like :func:`pack_padded_sequence`.
|
| 47 |
+
|
| 48 |
+
Batch sizes represent the number elements at each sequence step in
|
| 49 |
+
the batch, not the varying sequence lengths passed to
|
| 50 |
+
:func:`pack_padded_sequence`. For instance, given data ``abc`` and ``x``
|
| 51 |
+
the :class:`PackedSequence` would contain data ``axbc`` with
|
| 52 |
+
``batch_sizes=[2,1,1]``.
|
| 53 |
+
|
| 54 |
+
Attributes:
|
| 55 |
+
data (Tensor): Tensor containing packed sequence
|
| 56 |
+
batch_sizes (Tensor): Tensor of integers holding
|
| 57 |
+
information about the batch size at each sequence step
|
| 58 |
+
sorted_indices (Tensor, optional): Tensor of integers holding how this
|
| 59 |
+
:class:`PackedSequence` is constructed from sequences.
|
| 60 |
+
unsorted_indices (Tensor, optional): Tensor of integers holding how this
|
| 61 |
+
to recover the original sequences with correct order.
|
| 62 |
+
|
| 63 |
+
.. note::
|
| 64 |
+
:attr:`data` can be on arbitrary device and of arbitrary dtype.
|
| 65 |
+
:attr:`sorted_indices` and :attr:`unsorted_indices` must be ``torch.int64``
|
| 66 |
+
tensors on the same device as :attr:`data`.
|
| 67 |
+
|
| 68 |
+
However, :attr:`batch_sizes` should always be a CPU ``torch.int64`` tensor.
|
| 69 |
+
|
| 70 |
+
This invariant is maintained throughout :class:`PackedSequence` class,
|
| 71 |
+
and all functions that construct a :class:`PackedSequence` in PyTorch
|
| 72 |
+
(i.e., they only pass in tensors conforming to this constraint).
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
def __new__(
|
| 76 |
+
cls,
|
| 77 |
+
data: Tensor,
|
| 78 |
+
batch_sizes: Tensor | None = None,
|
| 79 |
+
sorted_indices: Tensor | None = None,
|
| 80 |
+
unsorted_indices: Tensor | None = None,
|
| 81 |
+
) -> Self:
|
| 82 |
+
return super().__new__(
|
| 83 |
+
cls,
|
| 84 |
+
*_packed_sequence_init_args(
|
| 85 |
+
data, batch_sizes, sorted_indices, unsorted_indices
|
| 86 |
+
),
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
# NOTE [ device and dtype of a PackedSequence ]
|
| 90 |
+
#
|
| 91 |
+
# See the note above in doc string (starting with ":attr:`data` can be on
|
| 92 |
+
# arbitrary device...").
|
| 93 |
+
def pin_memory(self) -> Self:
|
| 94 |
+
# Why not convert `batch_sizes`?
|
| 95 |
+
# See NOTE [ device and dtype of a PackedSequence ]
|
| 96 |
+
return type(self)(
|
| 97 |
+
self.data.pin_memory(),
|
| 98 |
+
self.batch_sizes,
|
| 99 |
+
bind(self.sorted_indices, lambda t: t.pin_memory()),
|
| 100 |
+
bind(self.unsorted_indices, lambda t: t.pin_memory()),
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
@copy_method_params(torch.Tensor.to)
|
| 104 |
+
def to(self, *args: Any, **kwargs: Any) -> Self:
|
| 105 |
+
r"""Perform dtype and/or device conversion on `self.data`.
|
| 106 |
+
|
| 107 |
+
It has similar signature as :meth:`torch.Tensor.to`
|
| 108 |
+
|
| 109 |
+
.. note::
|
| 110 |
+
|
| 111 |
+
If the ``self.data`` Tensor already has the correct :class:`torch.dtype`
|
| 112 |
+
and :class:`torch.device`, then ``self`` is returned.
|
| 113 |
+
Otherwise, returns a copy with the desired configuration.
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
# Why not convert `batch_sizes`?
|
| 117 |
+
# See NOTE [ device and dtype of a PackedSequence ]
|
| 118 |
+
data = self.data.to(*args, **kwargs)
|
| 119 |
+
if data is self.data:
|
| 120 |
+
return self
|
| 121 |
+
else:
|
| 122 |
+
_device, _dtype, non_blocking, convert_to_format = torch._C._nn._parse_to(
|
| 123 |
+
*args, **kwargs
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
# Does not forward device or dtype arg/kwargs, device is set from data.device
|
| 127 |
+
def call_to(t: torch.Tensor) -> torch.Tensor:
|
| 128 |
+
return t.to(
|
| 129 |
+
data.device,
|
| 130 |
+
non_blocking=non_blocking,
|
| 131 |
+
memory_format=convert_to_format,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
sorted_indices = bind(self.sorted_indices, call_to)
|
| 135 |
+
unsorted_indices = bind(self.unsorted_indices, call_to)
|
| 136 |
+
return type(self)(data, self.batch_sizes, sorted_indices, unsorted_indices)
|
| 137 |
+
|
| 138 |
+
@copy_method_params(torch.Tensor.cuda)
|
| 139 |
+
def cuda(self, *args: Any, **kwargs: Any) -> Self:
|
| 140 |
+
# Tests to see if 'cuda' should be added to kwargs
|
| 141 |
+
ex = torch.tensor((), dtype=self.data.dtype, device=self.data.device).to(
|
| 142 |
+
*args, **kwargs
|
| 143 |
+
)
|
| 144 |
+
if ex.is_cuda:
|
| 145 |
+
return self.to(*args, **kwargs)
|
| 146 |
+
kwargs["device"] = "cuda"
|
| 147 |
+
return self.to(*args, **kwargs)
|
| 148 |
+
|
| 149 |
+
@copy_method_params(torch.Tensor.cpu)
|
| 150 |
+
def cpu(self, *args: Any, **kwargs: Any) -> Self:
|
| 151 |
+
ex = torch.tensor((), dtype=self.data.dtype, device=self.data.device).to(
|
| 152 |
+
*args, **kwargs
|
| 153 |
+
)
|
| 154 |
+
if ex.device.type == "cpu":
|
| 155 |
+
return self.to(*args, **kwargs)
|
| 156 |
+
kwargs["device"] = "cpu"
|
| 157 |
+
return self.to(*args, **kwargs)
|
| 158 |
+
|
| 159 |
+
def double(self) -> Self:
|
| 160 |
+
return self.to(dtype=torch.double)
|
| 161 |
+
|
| 162 |
+
def float(self) -> Self:
|
| 163 |
+
return self.to(dtype=torch.float)
|
| 164 |
+
|
| 165 |
+
def half(self) -> Self:
|
| 166 |
+
return self.to(dtype=torch.half)
|
| 167 |
+
|
| 168 |
+
def long(self) -> Self:
|
| 169 |
+
return self.to(dtype=torch.long)
|
| 170 |
+
|
| 171 |
+
def int(self) -> Self:
|
| 172 |
+
return self.to(dtype=torch.int)
|
| 173 |
+
|
| 174 |
+
def short(self) -> Self:
|
| 175 |
+
return self.to(dtype=torch.short)
|
| 176 |
+
|
| 177 |
+
def char(self) -> Self:
|
| 178 |
+
return self.to(dtype=torch.int8)
|
| 179 |
+
|
| 180 |
+
def byte(self) -> Self:
|
| 181 |
+
return self.to(dtype=torch.uint8)
|
| 182 |
+
|
| 183 |
+
@property
|
| 184 |
+
def is_cuda(self) -> bool:
|
| 185 |
+
r"""Return true if `self.data` stored on a gpu."""
|
| 186 |
+
return self.data.is_cuda
|
| 187 |
+
|
| 188 |
+
def is_pinned(self) -> bool:
|
| 189 |
+
r"""Return true if `self.data` stored on in pinned memory."""
|
| 190 |
+
return self.data.is_pinned()
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
# TorchScript doesn't support constructors on named tuples, so we use this helper
|
| 194 |
+
# method to construct PackedSequence
|
| 195 |
+
def _packed_sequence_init_args(
|
| 196 |
+
data: Tensor,
|
| 197 |
+
batch_sizes: Tensor | None = None,
|
| 198 |
+
sorted_indices: Tensor | None = None,
|
| 199 |
+
unsorted_indices: Tensor | None = None,
|
| 200 |
+
) -> tuple[Tensor, Tensor, Tensor | None, Tensor | None]:
|
| 201 |
+
# NB: if unsorted_indices is provided, it should be the inverse permutation
|
| 202 |
+
# to sorted_indices. Don't assert it here because the PackedSequence ctor
|
| 203 |
+
# should only be used internally.
|
| 204 |
+
|
| 205 |
+
if unsorted_indices is None:
|
| 206 |
+
unsorted_indices = invert_permutation(sorted_indices)
|
| 207 |
+
|
| 208 |
+
# support being called as `PackedSequence(data, batch_sizes, sorted_indices)`
|
| 209 |
+
if batch_sizes is not None:
|
| 210 |
+
# TODO: Re-enable this check (.type isn't supported in TorchScript)
|
| 211 |
+
if batch_sizes.device.type != "cpu":
|
| 212 |
+
raise ValueError(
|
| 213 |
+
"batch_sizes should always be on CPU. "
|
| 214 |
+
"Instances of PackedSequence should never be created manually. "
|
| 215 |
+
"They should be instantiated by functions like pack_sequence "
|
| 216 |
+
"and pack_padded_sequences in nn.utils.rnn. "
|
| 217 |
+
"https://pytorch.org/docs/stable/nn.html#torch.nn.utils.rnn.pack_sequence"
|
| 218 |
+
)
|
| 219 |
+
return data, batch_sizes, sorted_indices, unsorted_indices
|
| 220 |
+
|
| 221 |
+
# support being called as `PackedSequence((data, batch_sizes), *, sorted_indices)`
|
| 222 |
+
else:
|
| 223 |
+
if not (isinstance(data, (list, tuple)) and len(data) == 2):
|
| 224 |
+
raise AssertionError("Expected data to be a list or tuple of length 2")
|
| 225 |
+
return data[0], data[1], sorted_indices, unsorted_indices
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def _packed_sequence_init(
|
| 229 |
+
data: Tensor,
|
| 230 |
+
batch_sizes: Tensor | None = None,
|
| 231 |
+
sorted_indices: Tensor | None = None,
|
| 232 |
+
unsorted_indices: Tensor | None = None,
|
| 233 |
+
) -> PackedSequence:
|
| 234 |
+
data, batch_sizes, sorted_indices, unsorted_indices = _packed_sequence_init_args(
|
| 235 |
+
data, batch_sizes, sorted_indices, unsorted_indices
|
| 236 |
+
)
|
| 237 |
+
return PackedSequence(data, batch_sizes, sorted_indices, unsorted_indices)
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def invert_permutation(permutation: Tensor | None) -> Tensor | None:
|
| 241 |
+
"""Returns the inverse of ``permutation``.
|
| 242 |
+
|
| 243 |
+
This is useful for converting between sorted and unsorted indices in
|
| 244 |
+
a :class:`~nn.utils.rnn.PackedSequence`.
|
| 245 |
+
|
| 246 |
+
Args:
|
| 247 |
+
permutation (Tensor, optional): a 1-D tensor of indices to invert
|
| 248 |
+
"""
|
| 249 |
+
if permutation is None:
|
| 250 |
+
return None
|
| 251 |
+
output = torch.empty_like(permutation, memory_format=torch.legacy_contiguous_format)
|
| 252 |
+
output.scatter_(
|
| 253 |
+
0, permutation, torch.arange(0, permutation.numel(), device=permutation.device)
|
| 254 |
+
)
|
| 255 |
+
return output
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def pack_padded_sequence(
|
| 259 |
+
input: Tensor,
|
| 260 |
+
lengths: Tensor | list[int],
|
| 261 |
+
batch_first: bool = False,
|
| 262 |
+
enforce_sorted: bool = True,
|
| 263 |
+
) -> PackedSequence:
|
| 264 |
+
r"""Packs a Tensor containing padded sequences of variable length.
|
| 265 |
+
|
| 266 |
+
:attr:`input` can be of size ``T x B x *`` (if :attr:`batch_first` is ``False``)
|
| 267 |
+
or ``B x T x *`` (if :attr:`batch_first` is ``True``) where ``T`` is the length
|
| 268 |
+
of the longest sequence, ``B`` is the batch size, and ``*`` is any number of dimensions
|
| 269 |
+
(including 0).
|
| 270 |
+
|
| 271 |
+
For unsorted sequences, use `enforce_sorted = False`. If :attr:`enforce_sorted` is
|
| 272 |
+
``True``, the sequences should be sorted by length in a decreasing order, i.e.
|
| 273 |
+
``input[:,0]`` should be the longest sequence, and ``input[:,B-1]`` the shortest
|
| 274 |
+
one. `enforce_sorted = True` is only necessary for ONNX export.
|
| 275 |
+
|
| 276 |
+
It is an inverse operation to :func:`pad_packed_sequence`, and hence :func:`pad_packed_sequence`
|
| 277 |
+
can be used to recover the underlying tensor packed in :class:`PackedSequence`.
|
| 278 |
+
|
| 279 |
+
Note:
|
| 280 |
+
This function accepts any input that has at least two dimensions. You
|
| 281 |
+
can apply it to pack the labels, and use the output of the RNN with
|
| 282 |
+
them to compute the loss directly. A Tensor can be retrieved from
|
| 283 |
+
a :class:`PackedSequence` object by accessing its ``.data`` attribute.
|
| 284 |
+
|
| 285 |
+
Args:
|
| 286 |
+
input (Tensor): padded batch of variable length sequences.
|
| 287 |
+
lengths (Tensor or list(int)): list of sequence lengths of each batch
|
| 288 |
+
element (must be on the CPU if provided as a tensor).
|
| 289 |
+
batch_first (bool, optional): if ``True``, the input is expected in ``B x T x *``
|
| 290 |
+
format, ``T x B x *`` otherwise. Default: ``False``.
|
| 291 |
+
enforce_sorted (bool, optional): if ``True``, the input is expected to
|
| 292 |
+
contain sequences sorted by length in a decreasing order. If
|
| 293 |
+
``False``, the input will get sorted unconditionally. Default: ``True``.
|
| 294 |
+
|
| 295 |
+
.. warning::
|
| 296 |
+
The dim of ``input`` tensor will be truncated if its length larger than
|
| 297 |
+
correspond value in ``length``.
|
| 298 |
+
|
| 299 |
+
Returns:
|
| 300 |
+
a :class:`PackedSequence` object
|
| 301 |
+
"""
|
| 302 |
+
if not isinstance(lengths, torch.Tensor):
|
| 303 |
+
if torch._C._get_tracing_state():
|
| 304 |
+
warnings.warn(
|
| 305 |
+
"pack_padded_sequence has been called with a Python list of "
|
| 306 |
+
"sequence lengths. The tracer cannot track the data flow of Python "
|
| 307 |
+
"values, and it will treat them as constants, likely rendering "
|
| 308 |
+
"the trace incorrect for any other combination of lengths.",
|
| 309 |
+
stacklevel=2,
|
| 310 |
+
)
|
| 311 |
+
lengths = torch.as_tensor(lengths, dtype=torch.int64, device="cpu")
|
| 312 |
+
else:
|
| 313 |
+
lengths = lengths.to(dtype=torch.int64)
|
| 314 |
+
|
| 315 |
+
if enforce_sorted:
|
| 316 |
+
sorted_indices = None
|
| 317 |
+
else:
|
| 318 |
+
lengths, sorted_indices = torch.sort(lengths, descending=True)
|
| 319 |
+
sorted_indices = sorted_indices.to(input.device)
|
| 320 |
+
batch_dim = 0 if batch_first else 1
|
| 321 |
+
input = input.index_select(batch_dim, sorted_indices)
|
| 322 |
+
|
| 323 |
+
data, batch_sizes = _VF._pack_padded_sequence(input, lengths, batch_first)
|
| 324 |
+
return _packed_sequence_init(data, batch_sizes, sorted_indices, None)
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def pad_packed_sequence(
|
| 328 |
+
sequence: PackedSequence,
|
| 329 |
+
batch_first: bool = False,
|
| 330 |
+
padding_value: float = 0.0,
|
| 331 |
+
total_length: int | None = None,
|
| 332 |
+
) -> tuple[Tensor, Tensor]:
|
| 333 |
+
r"""Pad a packed batch of variable length sequences.
|
| 334 |
+
|
| 335 |
+
It is an inverse operation to :func:`pack_padded_sequence`.
|
| 336 |
+
|
| 337 |
+
The returned Tensor's data will be of size ``T x B x *`` (if :attr:`batch_first` is ``False``)
|
| 338 |
+
or ``B x T x *`` (if :attr:`batch_first` is ``True``) , where ``T`` is the length of the longest
|
| 339 |
+
sequence and ``B`` is the batch size.
|
| 340 |
+
|
| 341 |
+
Example:
|
| 342 |
+
>>> from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence
|
| 343 |
+
>>> seq = torch.tensor([[1, 2, 0], [3, 0, 0], [4, 5, 6]])
|
| 344 |
+
>>> lens = [2, 1, 3]
|
| 345 |
+
>>> packed = pack_padded_sequence(
|
| 346 |
+
... seq, lens, batch_first=True, enforce_sorted=False
|
| 347 |
+
... )
|
| 348 |
+
>>> packed
|
| 349 |
+
PackedSequence(data=tensor([4, 1, 3, 5, 2, 6]), batch_sizes=tensor([3, 2, 1]),
|
| 350 |
+
sorted_indices=tensor([2, 0, 1]), unsorted_indices=tensor([1, 2, 0]))
|
| 351 |
+
>>> seq_unpacked, lens_unpacked = pad_packed_sequence(packed, batch_first=True)
|
| 352 |
+
>>> seq_unpacked
|
| 353 |
+
tensor([[1, 2, 0],
|
| 354 |
+
[3, 0, 0],
|
| 355 |
+
[4, 5, 6]])
|
| 356 |
+
>>> lens_unpacked
|
| 357 |
+
tensor([2, 1, 3])
|
| 358 |
+
|
| 359 |
+
.. note::
|
| 360 |
+
:attr:`total_length` is useful to implement the
|
| 361 |
+
``pack sequence -> recurrent network -> unpack sequence`` pattern in a
|
| 362 |
+
:class:`~torch.nn.Module` wrapped in :class:`~torch.nn.DataParallel`.
|
| 363 |
+
See :ref:`this FAQ section <pack-rnn-unpack-with-data-parallelism>` for
|
| 364 |
+
details.
|
| 365 |
+
|
| 366 |
+
Args:
|
| 367 |
+
sequence (PackedSequence): batch to pad
|
| 368 |
+
batch_first (bool, optional): if ``True``, the output will be in ``B x T x *``
|
| 369 |
+
format, ``T x B x *`` otherwise.
|
| 370 |
+
padding_value (float, optional): values for padded elements.
|
| 371 |
+
total_length (int, optional): if not ``None``, the output will be padded to
|
| 372 |
+
have length :attr:`total_length`. This method will throw :class:`ValueError`
|
| 373 |
+
if :attr:`total_length` is less than the max sequence length in
|
| 374 |
+
:attr:`sequence`.
|
| 375 |
+
|
| 376 |
+
Returns:
|
| 377 |
+
Tuple of Tensor containing the padded sequence, and a Tensor
|
| 378 |
+
containing the list of lengths of each sequence in the batch.
|
| 379 |
+
Batch elements will be re-ordered as they were ordered originally when
|
| 380 |
+
the batch was passed to ``pack_padded_sequence`` or ``pack_sequence``.
|
| 381 |
+
"""
|
| 382 |
+
max_seq_length = sequence.batch_sizes.size(0)
|
| 383 |
+
if total_length is not None:
|
| 384 |
+
if total_length < max_seq_length:
|
| 385 |
+
raise ValueError(
|
| 386 |
+
"Expected total_length to be at least the length "
|
| 387 |
+
"of the longest sequence in input, but got "
|
| 388 |
+
f"total_length={total_length} and max sequence length being {max_seq_length}"
|
| 389 |
+
)
|
| 390 |
+
max_seq_length = total_length
|
| 391 |
+
padded_output, lengths = _VF._pad_packed_sequence(
|
| 392 |
+
sequence.data, sequence.batch_sizes, batch_first, padding_value, max_seq_length
|
| 393 |
+
)
|
| 394 |
+
unsorted_indices = sequence.unsorted_indices
|
| 395 |
+
if unsorted_indices is not None:
|
| 396 |
+
batch_dim = 0 if batch_first else 1
|
| 397 |
+
return (
|
| 398 |
+
padded_output.index_select(batch_dim, unsorted_indices),
|
| 399 |
+
lengths[unsorted_indices.cpu()],
|
| 400 |
+
)
|
| 401 |
+
return padded_output, lengths
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
# NOTE: for JIT-compatibility, we need to be more restrictive here and use specific types instead of Iterable.
|
| 405 |
+
def pad_sequence(
|
| 406 |
+
sequences: Tensor | list[Tensor],
|
| 407 |
+
batch_first: bool = False,
|
| 408 |
+
padding_value: float = 0.0,
|
| 409 |
+
padding_side: str = "right",
|
| 410 |
+
) -> Tensor:
|
| 411 |
+
r"""Pad a list of variable length Tensors with :attr:`padding_value`.
|
| 412 |
+
|
| 413 |
+
``pad_sequence`` stacks a list of Tensors along a new dimension, and pads them
|
| 414 |
+
to equal length. :attr:`sequences` can be list of sequences with size ``L x *``,
|
| 415 |
+
where `L` is length of the sequence and ``*`` is any number of dimensions
|
| 416 |
+
(including ``0``). If :attr:`batch_first` is ``False``, the output is of size
|
| 417 |
+
``T x B x *``, and ``B x T x *`` otherwise, where ``B`` is the batch size
|
| 418 |
+
(the number of elements in :attr:`sequences`), ``T`` is the length of the longest
|
| 419 |
+
sequence.
|
| 420 |
+
|
| 421 |
+
Example:
|
| 422 |
+
>>> from torch.nn.utils.rnn import pad_sequence
|
| 423 |
+
>>> a = torch.ones(25, 300)
|
| 424 |
+
>>> b = torch.ones(22, 300)
|
| 425 |
+
>>> c = torch.ones(15, 300)
|
| 426 |
+
>>> pad_sequence([a, b, c]).size()
|
| 427 |
+
torch.Size([25, 3, 300])
|
| 428 |
+
|
| 429 |
+
Note:
|
| 430 |
+
This function returns a Tensor of size ``T x B x *`` or ``B x T x *``
|
| 431 |
+
where `T` is the length of the longest sequence. This function assumes
|
| 432 |
+
trailing dimensions and type of all the Tensors in sequences are same.
|
| 433 |
+
|
| 434 |
+
Args:
|
| 435 |
+
sequences (list[Tensor]): list of variable length sequences.
|
| 436 |
+
batch_first (bool, optional): if ``True``, the output will be in ``B x T x *``
|
| 437 |
+
format, ``T x B x *`` otherwise.
|
| 438 |
+
padding_value (float, optional): value for padded elements. Default: ``0``.
|
| 439 |
+
padding_side (str, optional): the side to pad the sequences on.
|
| 440 |
+
Default: ``'right'``.
|
| 441 |
+
|
| 442 |
+
Returns:
|
| 443 |
+
Tensor of size ``T x B x *`` if :attr:`batch_first` is ``False``.
|
| 444 |
+
Tensor of size ``B x T x *`` otherwise
|
| 445 |
+
"""
|
| 446 |
+
if not (torch.jit.is_tracing() or torch.jit.is_scripting()):
|
| 447 |
+
# JIT doesn't support `Iterable`
|
| 448 |
+
if not isinstance(sequences, Iterable):
|
| 449 |
+
msg = (
|
| 450 |
+
"pad_sequence: Expected iterable for input sequences, but got arg of type: "
|
| 451 |
+
f"{type(sequences)}"
|
| 452 |
+
)
|
| 453 |
+
raise RuntimeError(msg)
|
| 454 |
+
|
| 455 |
+
# In JIT context this leads to,
|
| 456 |
+
# RuntimeError: cannot statically infer the expected size of a list in this context
|
| 457 |
+
sequences = tuple(sequences) # type: ignore[assignment]
|
| 458 |
+
else:
|
| 459 |
+
# For JIT, we only support Union[Tensor, Tuple[Tensor]]
|
| 460 |
+
if isinstance(sequences, torch.Tensor):
|
| 461 |
+
sequences = sequences.unbind(0) # type: ignore[assignment]
|
| 462 |
+
|
| 463 |
+
# assuming trailing dimensions and type of all the Tensors
|
| 464 |
+
# in sequences are same and fetching those from sequences[0]
|
| 465 |
+
return torch._C._nn.pad_sequence(
|
| 466 |
+
sequences, # type: ignore[arg-type]
|
| 467 |
+
batch_first,
|
| 468 |
+
padding_value,
|
| 469 |
+
padding_side, # type: ignore[arg-type]
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def unpad_sequence(
|
| 474 |
+
padded_sequences: Tensor,
|
| 475 |
+
lengths: Tensor,
|
| 476 |
+
batch_first: bool = False,
|
| 477 |
+
) -> list[Tensor]:
|
| 478 |
+
r"""Unpad padded Tensor into a list of variable length Tensors.
|
| 479 |
+
|
| 480 |
+
``unpad_sequence`` unstacks padded Tensor into a list of variable length Tensors.
|
| 481 |
+
|
| 482 |
+
Example:
|
| 483 |
+
>>> from torch.nn.utils.rnn import pad_sequence, unpad_sequence
|
| 484 |
+
>>> a = torch.ones(25, 300)
|
| 485 |
+
>>> b = torch.ones(22, 300)
|
| 486 |
+
>>> c = torch.ones(15, 300)
|
| 487 |
+
>>> sequences = [a, b, c]
|
| 488 |
+
>>> padded_sequences = pad_sequence(sequences)
|
| 489 |
+
>>> lengths = torch.as_tensor([v.size(0) for v in sequences])
|
| 490 |
+
>>> unpadded_sequences = unpad_sequence(padded_sequences, lengths)
|
| 491 |
+
>>> torch.allclose(sequences[0], unpadded_sequences[0])
|
| 492 |
+
True
|
| 493 |
+
>>> torch.allclose(sequences[1], unpadded_sequences[1])
|
| 494 |
+
True
|
| 495 |
+
>>> torch.allclose(sequences[2], unpadded_sequences[2])
|
| 496 |
+
True
|
| 497 |
+
|
| 498 |
+
Args:
|
| 499 |
+
padded_sequences (Tensor): padded sequences.
|
| 500 |
+
lengths (Tensor): length of original (unpadded) sequences.
|
| 501 |
+
batch_first (bool, optional): whether batch dimension first or not. Default: ``False``.
|
| 502 |
+
|
| 503 |
+
Returns:
|
| 504 |
+
a list of :class:`Tensor` objects
|
| 505 |
+
"""
|
| 506 |
+
unpadded_sequences = []
|
| 507 |
+
|
| 508 |
+
if not batch_first:
|
| 509 |
+
padded_sequences.transpose_(0, 1)
|
| 510 |
+
|
| 511 |
+
max_length = padded_sequences.shape[1]
|
| 512 |
+
idx = torch.arange(max_length, device=lengths.device)
|
| 513 |
+
|
| 514 |
+
for seq, length in zip(padded_sequences, lengths, strict=True):
|
| 515 |
+
mask = idx < length
|
| 516 |
+
unpacked_seq = seq[mask]
|
| 517 |
+
unpadded_sequences.append(unpacked_seq)
|
| 518 |
+
|
| 519 |
+
return unpadded_sequences
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def pack_sequence(
|
| 523 |
+
sequences: list[Tensor],
|
| 524 |
+
enforce_sorted: bool = True,
|
| 525 |
+
) -> PackedSequence:
|
| 526 |
+
r"""Packs a list of variable length Tensors.
|
| 527 |
+
|
| 528 |
+
Consecutive call of the next functions: ``pad_sequence``, ``pack_padded_sequence``.
|
| 529 |
+
|
| 530 |
+
``sequences`` should be a list of Tensors of size ``L x *``, where `L` is
|
| 531 |
+
the length of a sequence and `*` is any number of trailing dimensions,
|
| 532 |
+
including ``0``.
|
| 533 |
+
|
| 534 |
+
For unsorted sequences, use `enforce_sorted = False`. If ``enforce_sorted``
|
| 535 |
+
is ``True``, the sequences should be sorted in the order of decreasing length.
|
| 536 |
+
``enforce_sorted = True`` is only necessary for ONNX export.
|
| 537 |
+
|
| 538 |
+
Example:
|
| 539 |
+
>>> from torch.nn.utils.rnn import pack_sequence
|
| 540 |
+
>>> a = torch.tensor([1, 2, 3])
|
| 541 |
+
>>> b = torch.tensor([4, 5])
|
| 542 |
+
>>> c = torch.tensor([6])
|
| 543 |
+
>>> pack_sequence([a, b, c])
|
| 544 |
+
PackedSequence(data=tensor([1, 4, 6, 2, 5, 3]), batch_sizes=tensor([3, 2, 1]), sorted_indices=None, unsorted_indices=None)
|
| 545 |
+
|
| 546 |
+
Args:
|
| 547 |
+
sequences (list[Tensor]): A list of sequences of decreasing length.
|
| 548 |
+
enforce_sorted (bool, optional): if ``True``, checks that the input
|
| 549 |
+
contains sequences sorted by length in a decreasing order. If
|
| 550 |
+
``False``, this condition is not checked. Default: ``True``.
|
| 551 |
+
|
| 552 |
+
Returns:
|
| 553 |
+
a :class:`PackedSequence` object
|
| 554 |
+
"""
|
| 555 |
+
lengths = torch.as_tensor([v.size(0) for v in sequences])
|
| 556 |
+
return pack_padded_sequence(
|
| 557 |
+
pad_sequence(sequences), lengths, enforce_sorted=enforce_sorted
|
| 558 |
+
)
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
def unpack_sequence(packed_sequences: PackedSequence) -> list[Tensor]:
|
| 562 |
+
r"""Unpack PackedSequence into a list of variable length Tensors.
|
| 563 |
+
|
| 564 |
+
``packed_sequences`` should be a PackedSequence object.
|
| 565 |
+
|
| 566 |
+
Example:
|
| 567 |
+
>>> from torch.nn.utils.rnn import pack_sequence, unpack_sequence
|
| 568 |
+
>>> a = torch.tensor([1, 2, 3])
|
| 569 |
+
>>> b = torch.tensor([4, 5])
|
| 570 |
+
>>> c = torch.tensor([6])
|
| 571 |
+
>>> sequences = [a, b, c]
|
| 572 |
+
>>> print(sequences)
|
| 573 |
+
[tensor([1, 2, 3]), tensor([4, 5]), tensor([6])]
|
| 574 |
+
>>> packed_sequences = pack_sequence(sequences)
|
| 575 |
+
>>> print(packed_sequences)
|
| 576 |
+
PackedSequence(data=tensor([1, 4, 6, 2, 5, 3]), batch_sizes=tensor([3, 2, 1]), sorted_indices=None, unsorted_indices=None)
|
| 577 |
+
>>> unpacked_sequences = unpack_sequence(packed_sequences)
|
| 578 |
+
>>> print(unpacked_sequences)
|
| 579 |
+
[tensor([1, 2, 3]), tensor([4, 5]), tensor([6])]
|
| 580 |
+
|
| 581 |
+
Args:
|
| 582 |
+
packed_sequences (PackedSequence): A PackedSequence object.
|
| 583 |
+
|
| 584 |
+
Returns:
|
| 585 |
+
a list of :class:`Tensor` objects
|
| 586 |
+
"""
|
| 587 |
+
padded_sequences, lengths = pad_packed_sequence(packed_sequences, batch_first=True)
|
| 588 |
+
unpacked_sequences = unpad_sequence(padded_sequences, lengths, batch_first=True)
|
| 589 |
+
return unpacked_sequences
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/spectral_norm.py
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
"""Spectral Normalization from https://arxiv.org/abs/1802.05957."""
|
| 3 |
+
|
| 4 |
+
from typing import Any, TypeVar
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from torch.nn.modules import Module
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = [
|
| 12 |
+
"SpectralNorm",
|
| 13 |
+
"SpectralNormLoadStateDictPreHook",
|
| 14 |
+
"SpectralNormStateDictHook",
|
| 15 |
+
"spectral_norm",
|
| 16 |
+
"remove_spectral_norm",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class SpectralNorm:
|
| 21 |
+
# Invariant before and after each forward call:
|
| 22 |
+
# u = F.normalize(W @ v)
|
| 23 |
+
# NB: At initialization, this invariant is not enforced
|
| 24 |
+
|
| 25 |
+
_version: int = 1
|
| 26 |
+
# At version 1:
|
| 27 |
+
# made `W` not a buffer,
|
| 28 |
+
# added `v` as a buffer, and
|
| 29 |
+
# made eval mode use `W = u @ W_orig @ v` rather than the stored `W`.
|
| 30 |
+
name: str
|
| 31 |
+
dim: int
|
| 32 |
+
n_power_iterations: int
|
| 33 |
+
eps: float
|
| 34 |
+
|
| 35 |
+
def __init__(
|
| 36 |
+
self,
|
| 37 |
+
name: str = "weight",
|
| 38 |
+
n_power_iterations: int = 1,
|
| 39 |
+
dim: int = 0,
|
| 40 |
+
eps: float = 1e-12,
|
| 41 |
+
) -> None:
|
| 42 |
+
self.name = name
|
| 43 |
+
self.dim = dim
|
| 44 |
+
if n_power_iterations <= 0:
|
| 45 |
+
raise ValueError(
|
| 46 |
+
"Expected n_power_iterations to be positive, but "
|
| 47 |
+
f"got n_power_iterations={n_power_iterations}"
|
| 48 |
+
)
|
| 49 |
+
self.n_power_iterations = n_power_iterations
|
| 50 |
+
self.eps = eps
|
| 51 |
+
|
| 52 |
+
def reshape_weight_to_matrix(self, weight: torch.Tensor) -> torch.Tensor:
|
| 53 |
+
weight_mat = weight
|
| 54 |
+
if self.dim != 0:
|
| 55 |
+
# permute dim to front
|
| 56 |
+
weight_mat = weight_mat.permute(
|
| 57 |
+
self.dim, *[d for d in range(weight_mat.dim()) if d != self.dim]
|
| 58 |
+
)
|
| 59 |
+
height = weight_mat.size(0)
|
| 60 |
+
return weight_mat.reshape(height, -1)
|
| 61 |
+
|
| 62 |
+
def compute_weight(self, module: Module, do_power_iteration: bool) -> torch.Tensor:
|
| 63 |
+
# NB: If `do_power_iteration` is set, the `u` and `v` vectors are
|
| 64 |
+
# updated in power iteration **in-place**. This is very important
|
| 65 |
+
# because in `DataParallel` forward, the vectors (being buffers) are
|
| 66 |
+
# broadcast from the parallelized module to each module replica,
|
| 67 |
+
# which is a new module object created on the fly. And each replica
|
| 68 |
+
# runs its own spectral norm power iteration. So simply assigning
|
| 69 |
+
# the updated vectors to the module this function runs on will cause
|
| 70 |
+
# the update to be lost forever. And the next time the parallelized
|
| 71 |
+
# module is replicated, the same randomly initialized vectors are
|
| 72 |
+
# broadcast and used!
|
| 73 |
+
#
|
| 74 |
+
# Therefore, to make the change propagate back, we rely on two
|
| 75 |
+
# important behaviors (also enforced via tests):
|
| 76 |
+
# 1. `DataParallel` doesn't clone storage if the broadcast tensor
|
| 77 |
+
# is already on correct device; and it makes sure that the
|
| 78 |
+
# parallelized module is already on `device[0]`.
|
| 79 |
+
# 2. If the out tensor in `out=` kwarg has correct shape, it will
|
| 80 |
+
# just fill in the values.
|
| 81 |
+
# Therefore, since the same power iteration is performed on all
|
| 82 |
+
# devices, simply updating the tensors in-place will make sure that
|
| 83 |
+
# the module replica on `device[0]` will update the _u vector on the
|
| 84 |
+
# parallelized module (by shared storage).
|
| 85 |
+
#
|
| 86 |
+
# However, after we update `u` and `v` in-place, we need to **clone**
|
| 87 |
+
# them before using them to normalize the weight. This is to support
|
| 88 |
+
# backproping through two forward passes, e.g., the common pattern in
|
| 89 |
+
# GAN training: loss = D(real) - D(fake). Otherwise, engine will
|
| 90 |
+
# complain that variables needed to do backward for the first forward
|
| 91 |
+
# (i.e., the `u` and `v` vectors) are changed in the second forward.
|
| 92 |
+
weight = getattr(module, self.name + "_orig")
|
| 93 |
+
u = getattr(module, self.name + "_u")
|
| 94 |
+
v = getattr(module, self.name + "_v")
|
| 95 |
+
weight_mat = self.reshape_weight_to_matrix(weight)
|
| 96 |
+
|
| 97 |
+
if do_power_iteration:
|
| 98 |
+
with torch.no_grad():
|
| 99 |
+
for _ in range(self.n_power_iterations):
|
| 100 |
+
# Spectral norm of weight equals to `u^T W v`, where `u` and `v`
|
| 101 |
+
# are the first left and right singular vectors.
|
| 102 |
+
# This power iteration produces approximations of `u` and `v`.
|
| 103 |
+
v = F.normalize(
|
| 104 |
+
torch.mv(weight_mat.t(), u), dim=0, eps=self.eps, out=v
|
| 105 |
+
)
|
| 106 |
+
u = F.normalize(torch.mv(weight_mat, v), dim=0, eps=self.eps, out=u)
|
| 107 |
+
if self.n_power_iterations > 0:
|
| 108 |
+
# See above on why we need to clone
|
| 109 |
+
u = u.clone(memory_format=torch.contiguous_format)
|
| 110 |
+
v = v.clone(memory_format=torch.contiguous_format)
|
| 111 |
+
|
| 112 |
+
sigma = torch.dot(u, torch.mv(weight_mat, v))
|
| 113 |
+
weight = weight / sigma
|
| 114 |
+
return weight
|
| 115 |
+
|
| 116 |
+
def remove(self, module: Module) -> None:
|
| 117 |
+
with torch.no_grad():
|
| 118 |
+
weight = self.compute_weight(module, do_power_iteration=False)
|
| 119 |
+
delattr(module, self.name)
|
| 120 |
+
delattr(module, self.name + "_u")
|
| 121 |
+
delattr(module, self.name + "_v")
|
| 122 |
+
delattr(module, self.name + "_orig")
|
| 123 |
+
module.register_parameter(self.name, torch.nn.Parameter(weight.detach()))
|
| 124 |
+
|
| 125 |
+
def __call__(self, module: Module, inputs: Any) -> None:
|
| 126 |
+
setattr(
|
| 127 |
+
module,
|
| 128 |
+
self.name,
|
| 129 |
+
self.compute_weight(module, do_power_iteration=module.training),
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
def _solve_v_and_rescale(self, weight_mat, u, target_sigma):
|
| 133 |
+
# Tries to returns a vector `v` s.t. `u = F.normalize(W @ v)`
|
| 134 |
+
# (the invariant at top of this class) and `u @ W @ v = sigma`.
|
| 135 |
+
# This uses pinverse in case W^T W is not invertible.
|
| 136 |
+
v = torch.linalg.multi_dot(
|
| 137 |
+
[weight_mat.t().mm(weight_mat).pinverse(), weight_mat.t(), u.unsqueeze(1)]
|
| 138 |
+
).squeeze(1)
|
| 139 |
+
return v.mul_(target_sigma / torch.dot(u, torch.mv(weight_mat, v)))
|
| 140 |
+
|
| 141 |
+
@staticmethod
|
| 142 |
+
def apply(
|
| 143 |
+
module: Module, name: str, n_power_iterations: int, dim: int, eps: float
|
| 144 |
+
) -> "SpectralNorm":
|
| 145 |
+
for hook in module._forward_pre_hooks.values():
|
| 146 |
+
if isinstance(hook, SpectralNorm) and hook.name == name:
|
| 147 |
+
raise RuntimeError(
|
| 148 |
+
f"Cannot register two spectral_norm hooks on the same parameter {name}"
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
fn = SpectralNorm(name, n_power_iterations, dim, eps)
|
| 152 |
+
weight = module._parameters[name]
|
| 153 |
+
if weight is None:
|
| 154 |
+
raise ValueError(
|
| 155 |
+
f"`SpectralNorm` cannot be applied as parameter `{name}` is None"
|
| 156 |
+
)
|
| 157 |
+
if isinstance(weight, torch.nn.parameter.UninitializedParameter):
|
| 158 |
+
raise ValueError(
|
| 159 |
+
"The module passed to `SpectralNorm` can't have uninitialized parameters. "
|
| 160 |
+
"Make sure to run the dummy forward before applying spectral normalization"
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
with torch.no_grad():
|
| 164 |
+
weight_mat = fn.reshape_weight_to_matrix(weight)
|
| 165 |
+
|
| 166 |
+
h, w = weight_mat.size()
|
| 167 |
+
# randomly initialize `u` and `v`
|
| 168 |
+
u = F.normalize(weight.new_empty(h).normal_(0, 1), dim=0, eps=fn.eps)
|
| 169 |
+
v = F.normalize(weight.new_empty(w).normal_(0, 1), dim=0, eps=fn.eps)
|
| 170 |
+
|
| 171 |
+
delattr(module, fn.name)
|
| 172 |
+
module.register_parameter(fn.name + "_orig", weight)
|
| 173 |
+
# We still need to assign weight back as fn.name because all sorts of
|
| 174 |
+
# things may assume that it exists, e.g., when initializing weights.
|
| 175 |
+
# However, we can't directly assign as it could be an nn.Parameter and
|
| 176 |
+
# gets added as a parameter. Instead, we register weight.data as a plain
|
| 177 |
+
# attribute.
|
| 178 |
+
setattr(module, fn.name, weight.data)
|
| 179 |
+
module.register_buffer(fn.name + "_u", u)
|
| 180 |
+
module.register_buffer(fn.name + "_v", v)
|
| 181 |
+
|
| 182 |
+
module.register_forward_pre_hook(fn)
|
| 183 |
+
module._register_state_dict_hook(SpectralNormStateDictHook(fn))
|
| 184 |
+
module._register_load_state_dict_pre_hook(SpectralNormLoadStateDictPreHook(fn))
|
| 185 |
+
return fn
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class SpectralNormLoadStateDictPreHook:
|
| 189 |
+
# See docstring of SpectralNorm._version on the changes to spectral_norm.
|
| 190 |
+
def __init__(self, fn) -> None:
|
| 191 |
+
self.fn = fn
|
| 192 |
+
|
| 193 |
+
# For state_dict with version None, (assuming that it has gone through at
|
| 194 |
+
# least one training forward), we have
|
| 195 |
+
#
|
| 196 |
+
# u = F.normalize(W_orig @ v)
|
| 197 |
+
# W = W_orig / sigma, where sigma = u @ W_orig @ v
|
| 198 |
+
#
|
| 199 |
+
# To compute `v`, we solve `W_orig @ x = u`, and let
|
| 200 |
+
# v = x / (u @ W_orig @ x) * (W / W_orig).
|
| 201 |
+
def __call__(
|
| 202 |
+
self,
|
| 203 |
+
state_dict,
|
| 204 |
+
prefix,
|
| 205 |
+
local_metadata,
|
| 206 |
+
strict,
|
| 207 |
+
missing_keys,
|
| 208 |
+
unexpected_keys,
|
| 209 |
+
error_msgs,
|
| 210 |
+
) -> None:
|
| 211 |
+
fn = self.fn
|
| 212 |
+
version = local_metadata.get("spectral_norm", {}).get(
|
| 213 |
+
fn.name + ".version", None
|
| 214 |
+
)
|
| 215 |
+
if version is None or version < 1:
|
| 216 |
+
weight_key = prefix + fn.name
|
| 217 |
+
if (
|
| 218 |
+
version is None
|
| 219 |
+
and all(weight_key + s in state_dict for s in ("_orig", "_u", "_v"))
|
| 220 |
+
and weight_key not in state_dict
|
| 221 |
+
):
|
| 222 |
+
# Detect if it is the updated state dict and just missing metadata.
|
| 223 |
+
# This could happen if the users are crafting a state dict themselves,
|
| 224 |
+
# so we just pretend that this is the newest.
|
| 225 |
+
return
|
| 226 |
+
has_missing_keys = False
|
| 227 |
+
for suffix in ("_orig", "", "_u"):
|
| 228 |
+
key = weight_key + suffix
|
| 229 |
+
if key not in state_dict:
|
| 230 |
+
has_missing_keys = True
|
| 231 |
+
if strict:
|
| 232 |
+
missing_keys.append(key)
|
| 233 |
+
if has_missing_keys:
|
| 234 |
+
return
|
| 235 |
+
with torch.no_grad():
|
| 236 |
+
weight_orig = state_dict[weight_key + "_orig"]
|
| 237 |
+
weight = state_dict.pop(weight_key)
|
| 238 |
+
sigma = (weight_orig / weight).mean()
|
| 239 |
+
weight_mat = fn.reshape_weight_to_matrix(weight_orig)
|
| 240 |
+
u = state_dict[weight_key + "_u"]
|
| 241 |
+
v = fn._solve_v_and_rescale(weight_mat, u, sigma)
|
| 242 |
+
state_dict[weight_key + "_v"] = v
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
class SpectralNormStateDictHook:
|
| 246 |
+
# See docstring of SpectralNorm._version on the changes to spectral_norm.
|
| 247 |
+
def __init__(self, fn) -> None:
|
| 248 |
+
self.fn = fn
|
| 249 |
+
|
| 250 |
+
def __call__(self, module, state_dict, prefix, local_metadata) -> None:
|
| 251 |
+
if "spectral_norm" not in local_metadata:
|
| 252 |
+
local_metadata["spectral_norm"] = {}
|
| 253 |
+
key = self.fn.name + ".version"
|
| 254 |
+
if key in local_metadata["spectral_norm"]:
|
| 255 |
+
raise RuntimeError(f"Unexpected key in metadata['spectral_norm']: {key}")
|
| 256 |
+
local_metadata["spectral_norm"][key] = self.fn._version
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
T_module = TypeVar("T_module", bound=Module)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def spectral_norm(
|
| 263 |
+
module: T_module,
|
| 264 |
+
name: str = "weight",
|
| 265 |
+
n_power_iterations: int = 1,
|
| 266 |
+
eps: float = 1e-12,
|
| 267 |
+
dim: int | None = None,
|
| 268 |
+
) -> T_module:
|
| 269 |
+
r"""Apply spectral normalization to a parameter in the given module.
|
| 270 |
+
|
| 271 |
+
.. math::
|
| 272 |
+
\mathbf{W}_{SN} = \dfrac{\mathbf{W}}{\sigma(\mathbf{W})},
|
| 273 |
+
\sigma(\mathbf{W}) = \max_{\mathbf{h}: \mathbf{h} \ne 0} \dfrac{\|\mathbf{W} \mathbf{h}\|_2}{\|\mathbf{h}\|_2}
|
| 274 |
+
|
| 275 |
+
Spectral normalization stabilizes the training of discriminators (critics)
|
| 276 |
+
in Generative Adversarial Networks (GANs) by rescaling the weight tensor
|
| 277 |
+
with spectral norm :math:`\sigma` of the weight matrix calculated using
|
| 278 |
+
power iteration method. If the dimension of the weight tensor is greater
|
| 279 |
+
than 2, it is reshaped to 2D in power iteration method to get spectral
|
| 280 |
+
norm. This is implemented via a hook that calculates spectral norm and
|
| 281 |
+
rescales weight before every :meth:`~Module.forward` call.
|
| 282 |
+
|
| 283 |
+
See `Spectral Normalization for Generative Adversarial Networks`_ .
|
| 284 |
+
|
| 285 |
+
.. _`Spectral Normalization for Generative Adversarial Networks`: https://arxiv.org/abs/1802.05957
|
| 286 |
+
|
| 287 |
+
Args:
|
| 288 |
+
module (nn.Module): containing module
|
| 289 |
+
name (str, optional): name of weight parameter
|
| 290 |
+
n_power_iterations (int, optional): number of power iterations to
|
| 291 |
+
calculate spectral norm
|
| 292 |
+
eps (float, optional): epsilon for numerical stability in
|
| 293 |
+
calculating norms
|
| 294 |
+
dim (int, optional): dimension corresponding to number of outputs,
|
| 295 |
+
the default is ``0``, except for modules that are instances of
|
| 296 |
+
ConvTranspose{1,2,3}d, when it is ``1``
|
| 297 |
+
|
| 298 |
+
Returns:
|
| 299 |
+
The original module with the spectral norm hook
|
| 300 |
+
|
| 301 |
+
.. note::
|
| 302 |
+
This function has been reimplemented as
|
| 303 |
+
:func:`torch.nn.utils.parametrizations.spectral_norm` using the new
|
| 304 |
+
parametrization functionality in
|
| 305 |
+
:func:`torch.nn.utils.parametrize.register_parametrization`. Please use
|
| 306 |
+
the newer version. This function will be deprecated in a future version
|
| 307 |
+
of PyTorch.
|
| 308 |
+
|
| 309 |
+
Example::
|
| 310 |
+
|
| 311 |
+
>>> m = spectral_norm(nn.Linear(20, 40))
|
| 312 |
+
>>> m
|
| 313 |
+
Linear(in_features=20, out_features=40, bias=True)
|
| 314 |
+
>>> m.weight_u.size()
|
| 315 |
+
torch.Size([40])
|
| 316 |
+
|
| 317 |
+
"""
|
| 318 |
+
if dim is None:
|
| 319 |
+
if isinstance(
|
| 320 |
+
module,
|
| 321 |
+
(
|
| 322 |
+
torch.nn.ConvTranspose1d,
|
| 323 |
+
torch.nn.ConvTranspose2d,
|
| 324 |
+
torch.nn.ConvTranspose3d,
|
| 325 |
+
),
|
| 326 |
+
):
|
| 327 |
+
dim = 1
|
| 328 |
+
else:
|
| 329 |
+
dim = 0
|
| 330 |
+
SpectralNorm.apply(module, name, n_power_iterations, dim, eps)
|
| 331 |
+
|
| 332 |
+
return module
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def remove_spectral_norm(module: T_module, name: str = "weight") -> T_module:
|
| 336 |
+
r"""Remove the spectral normalization reparameterization from a module.
|
| 337 |
+
|
| 338 |
+
Args:
|
| 339 |
+
module (Module): containing module
|
| 340 |
+
name (str, optional): name of weight parameter
|
| 341 |
+
|
| 342 |
+
Example:
|
| 343 |
+
>>> m = spectral_norm(nn.Linear(40, 10))
|
| 344 |
+
>>> remove_spectral_norm(m)
|
| 345 |
+
"""
|
| 346 |
+
for k, hook in module._forward_pre_hooks.items():
|
| 347 |
+
if isinstance(hook, SpectralNorm) and hook.name == name:
|
| 348 |
+
hook.remove(module)
|
| 349 |
+
del module._forward_pre_hooks[k]
|
| 350 |
+
break
|
| 351 |
+
else:
|
| 352 |
+
raise ValueError(f"spectral_norm of '{name}' not found in {module}")
|
| 353 |
+
|
| 354 |
+
for k, hook in module._state_dict_hooks.items():
|
| 355 |
+
if isinstance(hook, SpectralNormStateDictHook) and hook.fn.name == name:
|
| 356 |
+
del module._state_dict_hooks[k]
|
| 357 |
+
break
|
| 358 |
+
|
| 359 |
+
for k, hook in module._load_state_dict_pre_hooks.items():
|
| 360 |
+
if isinstance(hook, SpectralNormLoadStateDictPreHook) and hook.fn.name == name:
|
| 361 |
+
del module._load_state_dict_pre_hooks[k]
|
| 362 |
+
break
|
| 363 |
+
|
| 364 |
+
return module
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/stateless.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
import contextlib
|
| 3 |
+
from typing import Any
|
| 4 |
+
from typing_extensions import deprecated
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch import Tensor
|
| 8 |
+
from torch.nn.utils._named_member_accessor import NamedMemberAccessor
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
__all__ = ["functional_call"]
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _untie_named_tensors_map(
|
| 15 |
+
module: "torch.nn.Module",
|
| 16 |
+
parameters_and_buffers: dict[str, Tensor],
|
| 17 |
+
) -> dict[str, Tensor]:
|
| 18 |
+
"""
|
| 19 |
+
Unties all tied tensors in the module to parameters_and_buffers.
|
| 20 |
+
|
| 21 |
+
This function returns a new untied_parameters_and_buffers dictionary and leave the original
|
| 22 |
+
untied_parameters_and_buffers dictionary unchanged. It adds new (missing) keys for tied tensors
|
| 23 |
+
in the module to untied_parameters_and_buffers. The value of the new key is the user-given value
|
| 24 |
+
in the original parameters_and_buffers dictionary.
|
| 25 |
+
|
| 26 |
+
If there are more than one user-given values for the same tied tensor, it will raise an error.
|
| 27 |
+
|
| 28 |
+
For example, if the module has two tied weights self.foo and self.tied_foo and the user passes
|
| 29 |
+
{'foo': foo_value, ...}, this will return {'foo': foo_value, 'tied_foo': foo_value, ...}. If the
|
| 30 |
+
user passes {'foo': foo_value, 'tied_foo': tied_foo_value, ...}, it will raise an error. If the
|
| 31 |
+
user passes {'foo': foo_value, 'tied_foo': foo_value, ...}, it will not raise an error.
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
module (torch.nn.Module): the module to determine which tensors are tied.
|
| 35 |
+
parameters_and_buffers (Dict[str, Tensor]): a map of {name: tensor} for reparamaterizing the module.
|
| 36 |
+
|
| 37 |
+
Returns:
|
| 38 |
+
A new untied version of the parameters_and_buffers dictionary.
|
| 39 |
+
|
| 40 |
+
Raises:
|
| 41 |
+
ValueError: if there are more than one user-given values for the same tied tensor.
|
| 42 |
+
"""
|
| 43 |
+
# A map of {name: tensor} for all tensors (including tied ones) in the module.
|
| 44 |
+
all_named_tensors: dict[str, Tensor] = {}
|
| 45 |
+
all_named_tensors.update(module.named_parameters(remove_duplicate=False))
|
| 46 |
+
all_named_tensors.update(module.named_buffers(remove_duplicate=False))
|
| 47 |
+
|
| 48 |
+
# A map of {tensor: set(all_tied_names)} for all tensor names in the module.
|
| 49 |
+
tensor_to_tied_names_map: dict[Tensor, set[str]] = {}
|
| 50 |
+
for name, tensor in all_named_tensors.items():
|
| 51 |
+
if tensor not in tensor_to_tied_names_map:
|
| 52 |
+
tensor_to_tied_names_map[tensor] = set()
|
| 53 |
+
tensor_to_tied_names_map[tensor].add(name)
|
| 54 |
+
|
| 55 |
+
# A map of {tied_name: set(all_tied_names)} for all tensor names in the module.
|
| 56 |
+
# If a name is not tied, it will not be in this map.
|
| 57 |
+
tied_names_map: dict[str, set[str]] = {}
|
| 58 |
+
for tied_names in tensor_to_tied_names_map.values():
|
| 59 |
+
if len(tied_names) > 1:
|
| 60 |
+
for tied_name in tied_names:
|
| 61 |
+
tied_names_map[tied_name] = tied_names
|
| 62 |
+
|
| 63 |
+
# Make sure the user didn't pass multiple values for the same tied tensor.
|
| 64 |
+
given_names = set(parameters_and_buffers.keys())
|
| 65 |
+
# same as given_names.intersection(tied_names_map.keys()) but dynamo can't
|
| 66 |
+
# handle that
|
| 67 |
+
given_names_for_tied_tensors: set[str] = set()
|
| 68 |
+
for name in given_names:
|
| 69 |
+
if name in tied_names_map:
|
| 70 |
+
given_names_for_tied_tensors.add(name)
|
| 71 |
+
|
| 72 |
+
for given_name in given_names_for_tied_tensors:
|
| 73 |
+
tied_names = tied_names_map[given_name]
|
| 74 |
+
if (
|
| 75 |
+
# Detect if there are multiple keys present for the same tied tensor.
|
| 76 |
+
len(tied_names.intersection(given_names_for_tied_tensors)) > 1
|
| 77 |
+
# Only raise an error if the user passed multiple values for the same tied tensor.
|
| 78 |
+
# If all given values are the same, don't raise.
|
| 79 |
+
and len({parameters_and_buffers[tied_name] for tied_name in tied_names})
|
| 80 |
+
!= 1
|
| 81 |
+
):
|
| 82 |
+
raise ValueError(
|
| 83 |
+
f"functional_call got multiple values for keys {sorted(tied_names)}, "
|
| 84 |
+
f"which are tied. Consider using tie_weights=False"
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
# Untie the given named tensor map
|
| 88 |
+
# Make a copy for not modifying the original dict
|
| 89 |
+
untied_parameters_and_buffers = parameters_and_buffers.copy()
|
| 90 |
+
for given_name in given_names_for_tied_tensors:
|
| 91 |
+
for tied_name in tied_names_map[given_name]:
|
| 92 |
+
untied_parameters_and_buffers[tied_name] = parameters_and_buffers[
|
| 93 |
+
given_name
|
| 94 |
+
]
|
| 95 |
+
return untied_parameters_and_buffers
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
@contextlib.contextmanager
|
| 99 |
+
def _reparametrize_module(
|
| 100 |
+
module: "torch.nn.Module",
|
| 101 |
+
parameters_and_buffers: dict[str, Tensor],
|
| 102 |
+
tie_weights: bool = False,
|
| 103 |
+
strict: bool = False,
|
| 104 |
+
stack_weights: bool = False,
|
| 105 |
+
):
|
| 106 |
+
if tie_weights:
|
| 107 |
+
untied_parameters_and_buffers = _untie_named_tensors_map(
|
| 108 |
+
module, parameters_and_buffers
|
| 109 |
+
)
|
| 110 |
+
else:
|
| 111 |
+
untied_parameters_and_buffers = parameters_and_buffers
|
| 112 |
+
|
| 113 |
+
accessor = NamedMemberAccessor(module)
|
| 114 |
+
if strict:
|
| 115 |
+
missing_keys, unexpected_keys = accessor.check_keys(
|
| 116 |
+
untied_parameters_and_buffers
|
| 117 |
+
)
|
| 118 |
+
error_msgs = []
|
| 119 |
+
if len(unexpected_keys) > 0:
|
| 120 |
+
error_msgs.append(
|
| 121 |
+
f"Unexpected key(s): {', '.join(map(repr, unexpected_keys))}."
|
| 122 |
+
)
|
| 123 |
+
if len(missing_keys) > 0:
|
| 124 |
+
error_msgs.append(f"Missing key(s): {', '.join(map(repr, missing_keys))}.")
|
| 125 |
+
if len(error_msgs) > 0:
|
| 126 |
+
raise RuntimeError(
|
| 127 |
+
"Error(s) in reparametrizing for {}:\n\t{}".format(
|
| 128 |
+
module._get_name(), "\n\t".join(error_msgs)
|
| 129 |
+
)
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
orig_parameters_and_buffers: dict[str, Tensor] = {}
|
| 133 |
+
try:
|
| 134 |
+
orig_parameters_and_buffers, _ = accessor.swap_tensors_dict(
|
| 135 |
+
untied_parameters_and_buffers, allow_missing=True
|
| 136 |
+
)
|
| 137 |
+
yield
|
| 138 |
+
finally:
|
| 139 |
+
if stack_weights:
|
| 140 |
+
# When stacking is enabled, we will restore the weights in LIFO order.
|
| 141 |
+
orig_parameters_and_buffers = dict(
|
| 142 |
+
reversed(orig_parameters_and_buffers.items())
|
| 143 |
+
)
|
| 144 |
+
new_parameters_and_buffers, _ = accessor.swap_tensors_dict(
|
| 145 |
+
orig_parameters_and_buffers, allow_missing=True
|
| 146 |
+
)
|
| 147 |
+
# Sometimes the module is not completely stateless and has some in-place modifications on
|
| 148 |
+
# the _parameters and _buffers dictionaries.
|
| 149 |
+
# Write the changed parameters and buffers back to the original dict.
|
| 150 |
+
parameters_and_buffers.update(
|
| 151 |
+
{
|
| 152 |
+
k: new_parameters_and_buffers[k]
|
| 153 |
+
for k in parameters_and_buffers
|
| 154 |
+
if k in new_parameters_and_buffers
|
| 155 |
+
}
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
@deprecated(
|
| 160 |
+
"`torch.nn.utils.stateless.functional_call` is deprecated as of PyTorch 2.0 "
|
| 161 |
+
"and will be removed in a future version of PyTorch. "
|
| 162 |
+
"Please use `torch.func.functional_call` instead which is a drop-in replacement.",
|
| 163 |
+
category=FutureWarning,
|
| 164 |
+
)
|
| 165 |
+
def functional_call(
|
| 166 |
+
module: "torch.nn.Module",
|
| 167 |
+
parameters_and_buffers: dict[str, Tensor],
|
| 168 |
+
args: Any | tuple | None = None,
|
| 169 |
+
kwargs: dict[str, Any] | None = None,
|
| 170 |
+
*,
|
| 171 |
+
tie_weights: bool = True,
|
| 172 |
+
strict: bool = False,
|
| 173 |
+
):
|
| 174 |
+
r"""Perform a functional call on the module by replacing the module parameters and buffers with the provided ones.
|
| 175 |
+
|
| 176 |
+
.. warning::
|
| 177 |
+
|
| 178 |
+
This API is deprecated as of PyTorch 2.0 and will be removed in a future
|
| 179 |
+
version of PyTorch. Please use :func:`torch.func.functional_call` instead,
|
| 180 |
+
which is a drop-in replacement for this API.
|
| 181 |
+
|
| 182 |
+
.. note:: If the module has active parametrizations, passing a value in the
|
| 183 |
+
:attr:`parameters_and_buffers` argument with the name set to the regular parameter
|
| 184 |
+
name will completely disable the parametrization.
|
| 185 |
+
If you want to apply the parametrization function to the value passed
|
| 186 |
+
please set the key as ``{submodule_name}.parametrizations.{parameter_name}.original``.
|
| 187 |
+
|
| 188 |
+
.. note:: If the module performs in-place operations on parameters/buffers, these will be reflected
|
| 189 |
+
in the `parameters_and_buffers` input.
|
| 190 |
+
|
| 191 |
+
Example::
|
| 192 |
+
|
| 193 |
+
>>> a = {'foo': torch.zeros(())}
|
| 194 |
+
>>> # xdoctest: +SKIP
|
| 195 |
+
>>> mod = Foo() # does self.foo = self.foo + 1
|
| 196 |
+
>>> print(mod.foo) # tensor(0.)
|
| 197 |
+
>>> functional_call(mod, a, torch.ones(()))
|
| 198 |
+
>>> print(mod.foo) # tensor(0.)
|
| 199 |
+
>>> print(a['foo']) # tensor(1.)
|
| 200 |
+
|
| 201 |
+
.. note:: If the module has tied weights, whether or not functional_call respects the tying is determined by the
|
| 202 |
+
tie_weights flag.
|
| 203 |
+
|
| 204 |
+
Example::
|
| 205 |
+
|
| 206 |
+
>>> a = {'foo': torch.zeros(())}
|
| 207 |
+
>>> # xdoctest: +SKIP
|
| 208 |
+
>>> mod = Foo() # has both self.foo and self.foo_tied which are tied. Returns x + self.foo + self.foo_tied
|
| 209 |
+
>>> print(mod.foo) # tensor(1.)
|
| 210 |
+
>>> mod(torch.zeros(())) # tensor(2.)
|
| 211 |
+
>>> functional_call(mod, a, torch.zeros(())) # tensor(0.) since it will change self.foo_tied too
|
| 212 |
+
>>> functional_call(mod, a, torch.zeros(()), tie_weights=False) # tensor(1.)--self.foo_tied is not updated
|
| 213 |
+
>>> new_a = {'foo': torch.zeros(()), 'foo_tied': torch.zeros(())}
|
| 214 |
+
>>> functional_call(mod, new_a, torch.zeros()) # tensor(0.)
|
| 215 |
+
|
| 216 |
+
Args:
|
| 217 |
+
module (torch.nn.Module): the module to call
|
| 218 |
+
parameters_and_buffers (dict of str and Tensor): the parameters that will be used in
|
| 219 |
+
the module call.
|
| 220 |
+
args (Any or tuple): arguments to be passed to the module call. If not a tuple, considered a single argument.
|
| 221 |
+
kwargs (dict): keyword arguments to be passed to the module call
|
| 222 |
+
tie_weights (bool, optional): If True, then parameters and buffers tied in the original model will be treated as
|
| 223 |
+
tied in the reparamaterized version. Therefore, if True and different values are passed for the tied
|
| 224 |
+
parameters and buffers, it will error. If False, it will not respect the originally tied parameters and
|
| 225 |
+
buffers unless the values passed for both weights are the same. Default: True.
|
| 226 |
+
strict (bool, optional): If True, then the parameters and buffers passed in must match the parameters and
|
| 227 |
+
buffers in the original module. Therefore, if True and there are any missing or unexpected keys, it will
|
| 228 |
+
error. Default: False.
|
| 229 |
+
|
| 230 |
+
Returns:
|
| 231 |
+
Any: the result of calling ``module``.
|
| 232 |
+
"""
|
| 233 |
+
return _functional_call(
|
| 234 |
+
module,
|
| 235 |
+
parameters_and_buffers,
|
| 236 |
+
args,
|
| 237 |
+
kwargs,
|
| 238 |
+
tie_weights=tie_weights,
|
| 239 |
+
strict=strict,
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def _functional_call(
|
| 244 |
+
module: "torch.nn.Module",
|
| 245 |
+
parameters_and_buffers: dict[str, Tensor],
|
| 246 |
+
args: Any | tuple | None = None,
|
| 247 |
+
kwargs: dict[str, Any] | None = None,
|
| 248 |
+
*,
|
| 249 |
+
tie_weights: bool = True,
|
| 250 |
+
strict: bool = False,
|
| 251 |
+
):
|
| 252 |
+
# TODO allow kwargs such as unsafe and others for parametrization
|
| 253 |
+
if (
|
| 254 |
+
torch.jit.is_tracing()
|
| 255 |
+
or torch.jit.is_scripting()
|
| 256 |
+
or isinstance(
|
| 257 |
+
module,
|
| 258 |
+
(
|
| 259 |
+
torch.jit.RecursiveScriptModule,
|
| 260 |
+
torch.jit.ScriptModule,
|
| 261 |
+
torch.jit.ScriptFunction,
|
| 262 |
+
),
|
| 263 |
+
)
|
| 264 |
+
):
|
| 265 |
+
raise RuntimeError("The stateless API can't be used with Jitted modules")
|
| 266 |
+
if isinstance(module, torch.nn.DataParallel):
|
| 267 |
+
raise RuntimeError(
|
| 268 |
+
"The stateless API can't be used with nn.DataParallel module"
|
| 269 |
+
)
|
| 270 |
+
if kwargs is None:
|
| 271 |
+
kwargs = {}
|
| 272 |
+
if args is None:
|
| 273 |
+
args = ()
|
| 274 |
+
elif not isinstance(args, tuple):
|
| 275 |
+
args = (args,)
|
| 276 |
+
with _reparametrize_module(
|
| 277 |
+
module, parameters_and_buffers, tie_weights=tie_weights, strict=strict
|
| 278 |
+
):
|
| 279 |
+
return module(*args, **kwargs)
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/weight_norm.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
r"""Weight Normalization from https://arxiv.org/abs/1602.07868."""
|
| 3 |
+
|
| 4 |
+
from typing import Any, TypeVar
|
| 5 |
+
from typing_extensions import deprecated
|
| 6 |
+
|
| 7 |
+
from torch import _weight_norm, norm_except_dim
|
| 8 |
+
from torch.nn.modules import Module
|
| 9 |
+
from torch.nn.parameter import Parameter, UninitializedParameter
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
__all__ = ["WeightNorm", "weight_norm", "remove_weight_norm"]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class WeightNorm:
|
| 16 |
+
name: str
|
| 17 |
+
dim: int
|
| 18 |
+
|
| 19 |
+
def __init__(self, name: str, dim: int) -> None:
|
| 20 |
+
if dim is None:
|
| 21 |
+
dim = -1
|
| 22 |
+
self.name = name
|
| 23 |
+
self.dim = dim
|
| 24 |
+
|
| 25 |
+
# TODO Make return type more specific
|
| 26 |
+
def compute_weight(self, module: Module) -> Any:
|
| 27 |
+
g = getattr(module, self.name + "_g")
|
| 28 |
+
v = getattr(module, self.name + "_v")
|
| 29 |
+
return _weight_norm(v, g, self.dim)
|
| 30 |
+
|
| 31 |
+
@staticmethod
|
| 32 |
+
@deprecated(
|
| 33 |
+
"`torch.nn.utils.weight_norm` is deprecated "
|
| 34 |
+
"in favor of `torch.nn.utils.parametrizations.weight_norm`.",
|
| 35 |
+
category=FutureWarning,
|
| 36 |
+
)
|
| 37 |
+
def apply(module, name: str, dim: int) -> "WeightNorm":
|
| 38 |
+
for hook in module._forward_pre_hooks.values():
|
| 39 |
+
if isinstance(hook, WeightNorm) and hook.name == name:
|
| 40 |
+
raise RuntimeError(
|
| 41 |
+
f"Cannot register two weight_norm hooks on the same parameter {name}"
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
if dim is None:
|
| 45 |
+
dim = -1
|
| 46 |
+
|
| 47 |
+
fn = WeightNorm(name, dim)
|
| 48 |
+
|
| 49 |
+
weight = getattr(module, name)
|
| 50 |
+
if isinstance(weight, UninitializedParameter):
|
| 51 |
+
raise ValueError(
|
| 52 |
+
"The module passed to `WeightNorm` can't have uninitialized parameters. "
|
| 53 |
+
"Make sure to run the dummy forward before applying weight normalization"
|
| 54 |
+
)
|
| 55 |
+
# remove w from parameter list
|
| 56 |
+
del module._parameters[name]
|
| 57 |
+
|
| 58 |
+
# add g and v as new parameters and express w as g/||v|| * v
|
| 59 |
+
module.register_parameter(
|
| 60 |
+
name + "_g", Parameter(norm_except_dim(weight, 2, dim).data)
|
| 61 |
+
)
|
| 62 |
+
module.register_parameter(name + "_v", Parameter(weight.data))
|
| 63 |
+
setattr(module, name, fn.compute_weight(module))
|
| 64 |
+
|
| 65 |
+
# recompute weight before every forward()
|
| 66 |
+
module.register_forward_pre_hook(fn)
|
| 67 |
+
|
| 68 |
+
return fn
|
| 69 |
+
|
| 70 |
+
def remove(self, module: Module) -> None:
|
| 71 |
+
weight = self.compute_weight(module)
|
| 72 |
+
delattr(module, self.name)
|
| 73 |
+
del module._parameters[self.name + "_g"]
|
| 74 |
+
del module._parameters[self.name + "_v"]
|
| 75 |
+
setattr(module, self.name, Parameter(weight.data))
|
| 76 |
+
|
| 77 |
+
def __call__(self, module: Module, inputs: Any) -> None:
|
| 78 |
+
setattr(module, self.name, self.compute_weight(module))
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
T_module = TypeVar("T_module", bound=Module)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def weight_norm(module: T_module, name: str = "weight", dim: int = 0) -> T_module:
|
| 85 |
+
r"""Apply weight normalization to a parameter in the given module.
|
| 86 |
+
|
| 87 |
+
.. math::
|
| 88 |
+
\mathbf{w} = g \dfrac{\mathbf{v}}{\|\mathbf{v}\|}
|
| 89 |
+
|
| 90 |
+
Weight normalization is a reparameterization that decouples the magnitude
|
| 91 |
+
of a weight tensor from its direction. This replaces the parameter specified
|
| 92 |
+
by :attr:`name` (e.g. ``'weight'``) with two parameters: one specifying the magnitude
|
| 93 |
+
(e.g. ``'weight_g'``) and one specifying the direction (e.g. ``'weight_v'``).
|
| 94 |
+
Weight normalization is implemented via a hook that recomputes the weight
|
| 95 |
+
tensor from the magnitude and direction before every :meth:`~Module.forward`
|
| 96 |
+
call.
|
| 97 |
+
|
| 98 |
+
By default, with ``dim=0``, the norm is computed independently per output
|
| 99 |
+
channel/plane. To compute a norm over the entire weight tensor, use
|
| 100 |
+
``dim=None``.
|
| 101 |
+
|
| 102 |
+
See https://arxiv.org/abs/1602.07868
|
| 103 |
+
|
| 104 |
+
.. warning::
|
| 105 |
+
|
| 106 |
+
This function is deprecated. Use :func:`torch.nn.utils.parametrizations.weight_norm`
|
| 107 |
+
which uses the modern parametrization API. The new ``weight_norm`` is compatible
|
| 108 |
+
with ``state_dict`` generated from old ``weight_norm``.
|
| 109 |
+
|
| 110 |
+
Migration guide:
|
| 111 |
+
|
| 112 |
+
* The magnitude (``weight_g``) and direction (``weight_v``) are now expressed
|
| 113 |
+
as ``parametrizations.weight.original0`` and ``parametrizations.weight.original1``
|
| 114 |
+
respectively. If this is bothering you, please comment on
|
| 115 |
+
https://github.com/pytorch/pytorch/issues/102999
|
| 116 |
+
|
| 117 |
+
* To remove the weight normalization reparameterization, use
|
| 118 |
+
:func:`torch.nn.utils.parametrize.remove_parametrizations`.
|
| 119 |
+
|
| 120 |
+
* The weight is no longer recomputed once at module forward; instead, it will
|
| 121 |
+
be recomputed on every access. To restore the old behavior, use
|
| 122 |
+
:func:`torch.nn.utils.parametrize.cached` before invoking the module
|
| 123 |
+
in question.
|
| 124 |
+
|
| 125 |
+
Args:
|
| 126 |
+
module (Module): containing module
|
| 127 |
+
name (str, optional): name of weight parameter
|
| 128 |
+
dim (int, optional): dimension over which to compute the norm
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
The original module with the weight norm hook
|
| 132 |
+
|
| 133 |
+
Example::
|
| 134 |
+
|
| 135 |
+
>>> m = weight_norm(nn.Linear(20, 40), name='weight')
|
| 136 |
+
>>> m
|
| 137 |
+
Linear(in_features=20, out_features=40, bias=True)
|
| 138 |
+
>>> m.weight_g.size()
|
| 139 |
+
torch.Size([40, 1])
|
| 140 |
+
>>> m.weight_v.size()
|
| 141 |
+
torch.Size([40, 20])
|
| 142 |
+
|
| 143 |
+
"""
|
| 144 |
+
WeightNorm.apply(module, name, dim)
|
| 145 |
+
return module
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def remove_weight_norm(module: T_module, name: str = "weight") -> T_module:
|
| 149 |
+
r"""Remove the weight normalization reparameterization from a module.
|
| 150 |
+
|
| 151 |
+
Args:
|
| 152 |
+
module (Module): containing module
|
| 153 |
+
name (str, optional): name of weight parameter
|
| 154 |
+
|
| 155 |
+
Example:
|
| 156 |
+
>>> m = weight_norm(nn.Linear(20, 40))
|
| 157 |
+
>>> remove_weight_norm(m)
|
| 158 |
+
"""
|
| 159 |
+
for k, hook in module._forward_pre_hooks.items():
|
| 160 |
+
if isinstance(hook, WeightNorm) and hook.name == name:
|
| 161 |
+
hook.remove(module)
|
| 162 |
+
del module._forward_pre_hooks[k]
|
| 163 |
+
return module
|
| 164 |
+
|
| 165 |
+
raise ValueError(f"weight_norm of '{name}' not found in {module}")
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/numa/__init__.py
ADDED
|
File without changes
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/numa/binding.py
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
In NUMA (Non-Uniform Memory Access) systems, accessing memory on remote NUMA
|
| 3 |
+
nodes incurs additional latency. PyTorch provides NUMA binding utilities to
|
| 4 |
+
promote memory locality by binding worker processes to CPUs near their assigned GPUs.
|
| 5 |
+
|
| 6 |
+
In practice, NUMA binding typically results in 1-10% overall performance improvements,
|
| 7 |
+
but some workloads may obtain much greater benefits or none at all.
|
| 8 |
+
|
| 9 |
+
To enable NUMA binding, use the ``--numa-binding`` flag with :ref:`torchrun <launcher-api>`, e.g.:
|
| 10 |
+
|
| 11 |
+
.. code-block:: bash
|
| 12 |
+
|
| 13 |
+
torchrun --numa-binding=node --nproc_per_node=8 train.py
|
| 14 |
+
|
| 15 |
+
Alternatively, pass :class:`NumaOptions` to ``LaunchConfig``
|
| 16 |
+
when using ``elastic_launch``.
|
| 17 |
+
|
| 18 |
+
See :class:`AffinityMode` for available binding modes.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
import os
|
| 22 |
+
import shutil
|
| 23 |
+
import traceback
|
| 24 |
+
from collections import defaultdict
|
| 25 |
+
from collections.abc import Callable, Iterable
|
| 26 |
+
from dataclasses import asdict, dataclass
|
| 27 |
+
from enum import Enum
|
| 28 |
+
from functools import wraps
|
| 29 |
+
from logging import getLogger
|
| 30 |
+
from typing import ParamSpec, TypeVar
|
| 31 |
+
|
| 32 |
+
import torch
|
| 33 |
+
from torch._utils_internal import signpost_event
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
__all__ = [
|
| 37 |
+
"AffinityMode",
|
| 38 |
+
"NumaOptions",
|
| 39 |
+
]
|
| 40 |
+
|
| 41 |
+
logger = getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class AffinityMode(str, Enum):
|
| 45 |
+
NODE = "node"
|
| 46 |
+
"""
|
| 47 |
+
Each worker process and its threads will be bound to all the CPUs
|
| 48 |
+
on the NUMA node containing the GPU whose local index equals the worker's local rank.
|
| 49 |
+
If in doubt, use this option rather than the others.
|
| 50 |
+
|
| 51 |
+
**Ex.:** If GPU 3 (i.e. ``torch.device("cuda:3")``) lives on NUMA node 1, then the worker
|
| 52 |
+
whose local rank is 3 will only be able to run on the CPUs of NUMA node 1.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
SOCKET = "socket"
|
| 56 |
+
"""
|
| 57 |
+
Each worker process and its threads will be bound to all the CPUs on all the NUMA nodes of the
|
| 58 |
+
socket containing the GPU whose local index equals the worker's local rank.
|
| 59 |
+
|
| 60 |
+
**Ex.:** If socket 0 contains GPU 3 and NUMA nodes 0-1, then the worker whose
|
| 61 |
+
local rank is 3 will be bound to the CPUs of NUMA nodes 0-1.
|
| 62 |
+
|
| 63 |
+
For cases where there is only one NUMA node per socket anyway, this is equivalent to NODE.
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
EXCLUSIVE = "exclusive"
|
| 67 |
+
"""
|
| 68 |
+
Each worker process and its threads will be bound to an exclusive subset of CPUs
|
| 69 |
+
on the NUMA node containing the GPU whose local index equals the worker's local rank.
|
| 70 |
+
The CPUs on the NUMA node are divided evenly among all GPUs on that node, so no two
|
| 71 |
+
workers share the same CPU cores.
|
| 72 |
+
|
| 73 |
+
**Ex.:** If NUMA node 1 has 16 physical cores and GPUs 2 and 3, then the worker whose
|
| 74 |
+
local rank is 2 will be bound to cores 0-7, and the worker whose local rank is 3 will
|
| 75 |
+
be bound to cores 8-15.
|
| 76 |
+
"""
|
| 77 |
+
|
| 78 |
+
CORE_COMPLEX = "core-complex"
|
| 79 |
+
"""
|
| 80 |
+
Each worker process and its threads will be bound to a single core complex (a group of cores
|
| 81 |
+
sharing the same L3 cache) on the NUMA node containing the GPU whose local index equals
|
| 82 |
+
the worker's local rank. Each worker is bound to a different core complex when possible.
|
| 83 |
+
|
| 84 |
+
**Ex.:** If NUMA node 1 has two core complexes (cores 0-7 sharing one L3 cache, cores 8-15
|
| 85 |
+
sharing another) and GPUs 2 and 3, then the worker whose local rank is 2 will be bound to
|
| 86 |
+
cores 0-7, and the worker whose local rank is 3 will be bound to cores 8-15.
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@dataclass(frozen=True)
|
| 91 |
+
class NumaOptions:
|
| 92 |
+
affinity_mode: AffinityMode
|
| 93 |
+
|
| 94 |
+
should_fall_back_if_binding_fails: bool = False
|
| 95 |
+
"""
|
| 96 |
+
If ``True``, we will silence any exceptions that occur during NUMA binding itself
|
| 97 |
+
rather than raising them.
|
| 98 |
+
|
| 99 |
+
There are no expected exceptions, so avoid using this option. Its purpose is simply
|
| 100 |
+
to mitigate crash risk while conducting mass rollouts of NUMA binding.
|
| 101 |
+
"""
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _maybe_wrap_command_args_with_numa_binding(
|
| 105 |
+
command_args: tuple[str, ...],
|
| 106 |
+
*,
|
| 107 |
+
gpu_index: int,
|
| 108 |
+
numa_options: NumaOptions | None,
|
| 109 |
+
) -> tuple[str, ...]:
|
| 110 |
+
"""
|
| 111 |
+
Wraps command arguments with numactl to apply NUMA CPU binding.
|
| 112 |
+
|
| 113 |
+
This function prepends numactl with appropriate CPU affinity flags to the
|
| 114 |
+
provided command arguments, binding the process to CPUs associated with
|
| 115 |
+
the specified GPU's NUMA node.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
command_args: The original command arguments to wrap.
|
| 119 |
+
gpu_index: The index of the GPU that will be used by the subprocess.
|
| 120 |
+
numa_options: Configuration for NUMA binding behavior. If None, returns
|
| 121 |
+
the original command_args unchanged.
|
| 122 |
+
|
| 123 |
+
Returns:
|
| 124 |
+
Tuple of command arguments, potentially wrapped with numactl for NUMA binding.
|
| 125 |
+
Returns the original command_args if numa_options is None or if binding fails
|
| 126 |
+
and fallback is enabled.
|
| 127 |
+
"""
|
| 128 |
+
if numa_options is None:
|
| 129 |
+
return command_args
|
| 130 |
+
|
| 131 |
+
kwargs = {
|
| 132 |
+
"command_args": command_args,
|
| 133 |
+
"gpu_index": gpu_index,
|
| 134 |
+
"numa_options": asdict(numa_options),
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
try:
|
| 138 |
+
logical_cpu_indices = _get_validated_logical_cpus_to_bind_to(
|
| 139 |
+
gpu_index=gpu_index,
|
| 140 |
+
numa_options=numa_options,
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
wrapped_command_args = _assemble_numactl_command_args(
|
| 144 |
+
original_command_args=command_args,
|
| 145 |
+
logical_cpu_indices=logical_cpu_indices,
|
| 146 |
+
)
|
| 147 |
+
signpost_event(
|
| 148 |
+
category="numa_binding",
|
| 149 |
+
name="apply_success",
|
| 150 |
+
parameters={
|
| 151 |
+
**kwargs,
|
| 152 |
+
"wrapped_command": wrapped_command_args,
|
| 153 |
+
},
|
| 154 |
+
)
|
| 155 |
+
return wrapped_command_args
|
| 156 |
+
except Exception:
|
| 157 |
+
# pyrefly: ignore [bad-argument-type]
|
| 158 |
+
_handle_exception(numa_options=numa_options, logger_kwargs=kwargs)
|
| 159 |
+
return command_args
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
_TParams = ParamSpec("_TParams")
|
| 163 |
+
_TReturn = TypeVar("_TReturn")
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def _maybe_wrap_with_numa_binding(
|
| 167 |
+
func: Callable[_TParams, _TReturn],
|
| 168 |
+
*,
|
| 169 |
+
gpu_index: int,
|
| 170 |
+
numa_options: NumaOptions | None,
|
| 171 |
+
) -> Callable[_TParams, _TReturn]:
|
| 172 |
+
"""
|
| 173 |
+
Wraps a function to apply NUMA CPU binding before execution.
|
| 174 |
+
|
| 175 |
+
This decorator applies NUMA CPU affinity to all threads in the current process
|
| 176 |
+
before calling the wrapped function, binding them to CPUs associated with the
|
| 177 |
+
specified GPU's NUMA node.
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
func: The function to wrap with NUMA binding.
|
| 181 |
+
gpu_index: The index of the GPU that will be used.
|
| 182 |
+
numa_options: Configuration for NUMA binding behavior. If None, returns
|
| 183 |
+
the original function unchanged.
|
| 184 |
+
|
| 185 |
+
Returns:
|
| 186 |
+
A wrapped function that applies NUMA binding before execution, or the
|
| 187 |
+
original function if numa_options is None.
|
| 188 |
+
"""
|
| 189 |
+
if numa_options is None:
|
| 190 |
+
return func
|
| 191 |
+
|
| 192 |
+
@wraps(func)
|
| 193 |
+
def wrapped(*args: _TParams.args, **kwargs: _TParams.kwargs) -> _TReturn:
|
| 194 |
+
_maybe_apply_numa_binding_to_current_process(
|
| 195 |
+
gpu_index=gpu_index,
|
| 196 |
+
# pyrefly: ignore [bad-argument-type]
|
| 197 |
+
numa_options=numa_options,
|
| 198 |
+
)
|
| 199 |
+
return func(*args, **kwargs)
|
| 200 |
+
|
| 201 |
+
return wrapped
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def _maybe_apply_numa_binding_to_current_process(
|
| 205 |
+
*, gpu_index: int, numa_options: NumaOptions
|
| 206 |
+
) -> None:
|
| 207 |
+
kwargs = {
|
| 208 |
+
"gpu_index": gpu_index,
|
| 209 |
+
"numa_options": asdict(numa_options),
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
try:
|
| 213 |
+
logical_cpu_indices = _get_validated_logical_cpus_to_bind_to(
|
| 214 |
+
gpu_index=gpu_index,
|
| 215 |
+
numa_options=numa_options,
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
_bind_all_threads_in_current_process_to_logical_cpus(
|
| 219 |
+
logical_cpu_indices=logical_cpu_indices
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
signpost_event(
|
| 223 |
+
category="numa_binding",
|
| 224 |
+
name="apply_success",
|
| 225 |
+
parameters={
|
| 226 |
+
**kwargs,
|
| 227 |
+
"logical_cpu_indices": _get_ranges_str_from_ints(logical_cpu_indices),
|
| 228 |
+
},
|
| 229 |
+
)
|
| 230 |
+
except Exception:
|
| 231 |
+
# pyrefly: ignore [bad-argument-type]
|
| 232 |
+
_handle_exception(numa_options=numa_options, logger_kwargs=kwargs)
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def _assemble_numactl_command_args(
|
| 236 |
+
*, original_command_args: tuple[str, ...], logical_cpu_indices: set[int]
|
| 237 |
+
) -> tuple[str, ...]:
|
| 238 |
+
return (
|
| 239 |
+
"numactl",
|
| 240 |
+
f"--physcpubind={_get_ranges_str_from_ints(logical_cpu_indices)}",
|
| 241 |
+
*original_command_args,
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _handle_exception(
|
| 246 |
+
*, numa_options: NumaOptions, logger_kwargs: dict[str, object]
|
| 247 |
+
) -> None:
|
| 248 |
+
signpost_event(
|
| 249 |
+
category="numa_binding",
|
| 250 |
+
name="apply_exception",
|
| 251 |
+
parameters={
|
| 252 |
+
**logger_kwargs,
|
| 253 |
+
"traceback": traceback.format_exc(),
|
| 254 |
+
},
|
| 255 |
+
)
|
| 256 |
+
logger.exception("Failed to apply NUMA binding for input=%r", logger_kwargs)
|
| 257 |
+
if numa_options.should_fall_back_if_binding_fails:
|
| 258 |
+
logger.warning(
|
| 259 |
+
"Continuing executing without applying NUMA binding, despite exception %s",
|
| 260 |
+
traceback.format_exc(),
|
| 261 |
+
)
|
| 262 |
+
return
|
| 263 |
+
# This function is called within an except block, so silence the warning
|
| 264 |
+
# about raise without an exception.
|
| 265 |
+
raise # noqa: PLE0704
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def _get_validated_logical_cpus_to_bind_to(
|
| 269 |
+
*,
|
| 270 |
+
gpu_index: int,
|
| 271 |
+
numa_options: NumaOptions,
|
| 272 |
+
) -> set[int]:
|
| 273 |
+
logical_cpu_indices = _get_logical_cpus_to_bind_to(
|
| 274 |
+
gpu_index=gpu_index, numa_options=numa_options
|
| 275 |
+
)
|
| 276 |
+
_raise_if_binding_invalid(logical_cpu_indices=logical_cpu_indices)
|
| 277 |
+
|
| 278 |
+
return logical_cpu_indices
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def _raise_if_binding_invalid(*, logical_cpu_indices: set[int]) -> None:
|
| 282 |
+
# NOTE: numactl CLI is only actually necessary for the str entrypoint path,
|
| 283 |
+
# but for simplicity we will just check it no matter what.
|
| 284 |
+
if shutil.which("numactl") is None:
|
| 285 |
+
raise RuntimeError("numactl CLI is required for NUMA binding")
|
| 286 |
+
|
| 287 |
+
if not logical_cpu_indices:
|
| 288 |
+
raise RuntimeError("Must bind to a non-empty set of CPU indices")
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _bind_all_threads_in_current_process_to_logical_cpus(
|
| 292 |
+
*, logical_cpu_indices: set[int]
|
| 293 |
+
) -> None:
|
| 294 |
+
# Save the original affinity of the main thread before changing it
|
| 295 |
+
# pyrefly: ignore [missing-attribute]
|
| 296 |
+
original_main_thread_affinity = os.sched_getaffinity(0) # type: ignore[attr-defined]
|
| 297 |
+
|
| 298 |
+
# 0 represents the current thread.
|
| 299 |
+
# This is outside the try/except because the main thread should always bind successfully.
|
| 300 |
+
# pyrefly: ignore [missing-attribute]
|
| 301 |
+
os.sched_setaffinity(0, logical_cpu_indices) # type: ignore[attr-defined]
|
| 302 |
+
|
| 303 |
+
for tid_str in os.listdir("/proc/self/task"):
|
| 304 |
+
try:
|
| 305 |
+
tid = int(tid_str)
|
| 306 |
+
# pyrefly: ignore [missing-attribute]
|
| 307 |
+
tid_affinity = os.sched_getaffinity(tid) # type: ignore[attr-defined]
|
| 308 |
+
|
| 309 |
+
# Defensive check to ensure we do not overwrite affinity on any threads
|
| 310 |
+
# that have already had their affinity set elsewhere.
|
| 311 |
+
if tid_affinity == original_main_thread_affinity:
|
| 312 |
+
# pyrefly: ignore [missing-attribute]
|
| 313 |
+
os.sched_setaffinity(tid, logical_cpu_indices) # type: ignore[attr-defined]
|
| 314 |
+
except Exception:
|
| 315 |
+
# Thread may have exited or otherwise become invalid
|
| 316 |
+
pass
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def _get_logical_cpus_to_bind_to(
|
| 320 |
+
*,
|
| 321 |
+
gpu_index: int,
|
| 322 |
+
numa_options: NumaOptions,
|
| 323 |
+
) -> set[int]:
|
| 324 |
+
"""
|
| 325 |
+
Args:
|
| 326 |
+
gpu_index: The index of the GPU that will be used by the subprocess.
|
| 327 |
+
Example: 0
|
| 328 |
+
numa_options: See NumaOptions for details.
|
| 329 |
+
|
| 330 |
+
Returns:
|
| 331 |
+
Set of logical CPU indices to bind to.
|
| 332 |
+
"""
|
| 333 |
+
if numa_options.affinity_mode == AffinityMode.NODE:
|
| 334 |
+
logical_cpus = _node_get_logical_cpus_to_bind_to(gpu_index=gpu_index)
|
| 335 |
+
elif numa_options.affinity_mode == AffinityMode.SOCKET:
|
| 336 |
+
logical_cpus = _socket_get_logical_cpus_to_bind_to(gpu_index=gpu_index)
|
| 337 |
+
elif numa_options.affinity_mode == AffinityMode.EXCLUSIVE:
|
| 338 |
+
logical_cpus = _exclusive_get_logical_cpus_to_bind_to(gpu_index=gpu_index)
|
| 339 |
+
elif numa_options.affinity_mode == AffinityMode.CORE_COMPLEX:
|
| 340 |
+
logical_cpus = _core_complex_get_logical_cpus_to_bind_to(gpu_index=gpu_index)
|
| 341 |
+
else:
|
| 342 |
+
raise ValueError(f"Affinity mode {numa_options.affinity_mode} not supported.")
|
| 343 |
+
|
| 344 |
+
return logical_cpus
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def _node_get_logical_cpus_to_bind_to(*, gpu_index: int) -> set[int]:
|
| 348 |
+
"""
|
| 349 |
+
Core logic of 'node' numa strategy.
|
| 350 |
+
"""
|
| 351 |
+
numa_node_index = _get_numa_node_index_for_gpu_index(gpu_index=gpu_index)
|
| 352 |
+
|
| 353 |
+
return _get_allowed_logical_cpu_indices_for_numa_node(
|
| 354 |
+
numa_node_index=numa_node_index
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def _socket_get_logical_cpus_to_bind_to(*, gpu_index: int) -> set[int]:
|
| 359 |
+
"""
|
| 360 |
+
Core logic of 'socket' numa strategy.
|
| 361 |
+
"""
|
| 362 |
+
numa_node_index_of_gpu = _get_numa_node_index_for_gpu_index(gpu_index=gpu_index)
|
| 363 |
+
socket_index = _get_socket_index_for_numa_node(
|
| 364 |
+
numa_node_index=numa_node_index_of_gpu
|
| 365 |
+
)
|
| 366 |
+
numa_node_indices = _get_numa_node_indices_for_socket_index(
|
| 367 |
+
socket_index=socket_index
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
logical_cpus = set()
|
| 371 |
+
for numa_node_index in numa_node_indices:
|
| 372 |
+
logical_cpus.update(
|
| 373 |
+
_get_allowed_logical_cpu_indices_for_numa_node(
|
| 374 |
+
numa_node_index=numa_node_index
|
| 375 |
+
)
|
| 376 |
+
)
|
| 377 |
+
|
| 378 |
+
return logical_cpus
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def _exclusive_get_logical_cpus_to_bind_to(*, gpu_index: int) -> set[int]:
|
| 382 |
+
"""
|
| 383 |
+
Core logic of 'exclusive' numa strategy.
|
| 384 |
+
"""
|
| 385 |
+
numa_node_index = _get_numa_node_index_for_gpu_index(gpu_index=gpu_index)
|
| 386 |
+
|
| 387 |
+
gpu_indices = _get_gpu_indices_for_numa_node(numa_node_index=numa_node_index)
|
| 388 |
+
gpu_indices = sorted(gpu_indices)
|
| 389 |
+
original_gpu_relative_index = gpu_indices.index(gpu_index)
|
| 390 |
+
|
| 391 |
+
allowed_logical_cpu_indices = _get_allowed_logical_cpu_indices_for_numa_node(
|
| 392 |
+
numa_node_index=numa_node_index
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# Arbitrarily use the min logical cpu index on the physical core to
|
| 396 |
+
# represent the physical core.
|
| 397 |
+
physical_core_to_allowed_logical_cpu_indices = _group_by(
|
| 398 |
+
allowed_logical_cpu_indices,
|
| 399 |
+
lambda logical_cpu_index: min(
|
| 400 |
+
_get_logical_cpu_indices_sharing_same_physical_core_as(
|
| 401 |
+
logical_cpu_index=logical_cpu_index
|
| 402 |
+
)
|
| 403 |
+
),
|
| 404 |
+
)
|
| 405 |
+
# Sort the dict for consistency (dicts maintain order in Python)
|
| 406 |
+
physical_core_to_allowed_logical_cpu_indices = dict(
|
| 407 |
+
sorted(physical_core_to_allowed_logical_cpu_indices.items())
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
+
num_physical_cores_per_gpu = len(
|
| 411 |
+
physical_core_to_allowed_logical_cpu_indices
|
| 412 |
+
) // len(gpu_indices)
|
| 413 |
+
# Often, the number of physical cores will not be perfectly divisible by the number
|
| 414 |
+
# of GPUs. In those cases, give the lowest GPU indices an extra core
|
| 415 |
+
num_gpus_to_give_one_extra_physical_core = len(
|
| 416 |
+
physical_core_to_allowed_logical_cpu_indices
|
| 417 |
+
) % len(gpu_indices)
|
| 418 |
+
|
| 419 |
+
if num_physical_cores_per_gpu < 1:
|
| 420 |
+
raise RuntimeError(
|
| 421 |
+
f"There are only {len(physical_core_to_allowed_logical_cpu_indices)} physical cores on {numa_node_index=},"
|
| 422 |
+
+ f" but there are {len(gpu_indices)} GPUs associated with this NUMA node."
|
| 423 |
+
)
|
| 424 |
+
|
| 425 |
+
# Compute slice indices for this GPU
|
| 426 |
+
start = original_gpu_relative_index * num_physical_cores_per_gpu + min(
|
| 427 |
+
original_gpu_relative_index, num_gpus_to_give_one_extra_physical_core
|
| 428 |
+
)
|
| 429 |
+
end = (
|
| 430 |
+
start
|
| 431 |
+
+ num_physical_cores_per_gpu
|
| 432 |
+
+ (
|
| 433 |
+
1
|
| 434 |
+
if original_gpu_relative_index < num_gpus_to_give_one_extra_physical_core
|
| 435 |
+
else 0
|
| 436 |
+
)
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
# Slice and flatten the logical CPUs from the selected physical cores
|
| 440 |
+
logical_cpu_indices_for_original_gpu = {
|
| 441 |
+
logical_cpu_index
|
| 442 |
+
for logical_cpu_indices in list(
|
| 443 |
+
physical_core_to_allowed_logical_cpu_indices.values()
|
| 444 |
+
)[start:end]
|
| 445 |
+
for logical_cpu_index in logical_cpu_indices
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
return logical_cpu_indices_for_original_gpu
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
def _core_complex_get_logical_cpus_to_bind_to(*, gpu_index: int) -> set[int]:
|
| 452 |
+
"""
|
| 453 |
+
Core logic of 'core-complex' numa strategy.
|
| 454 |
+
|
| 455 |
+
Each GPU is assigned a full core complex (group of cores sharing L3 cache)
|
| 456 |
+
within its affined NUMA node.
|
| 457 |
+
"""
|
| 458 |
+
numa_node_index = _get_numa_node_index_for_gpu_index(gpu_index=gpu_index)
|
| 459 |
+
|
| 460 |
+
gpu_indices = _get_gpu_indices_for_numa_node(numa_node_index=numa_node_index)
|
| 461 |
+
gpu_indices = sorted(gpu_indices)
|
| 462 |
+
original_gpu_relative_index = gpu_indices.index(gpu_index)
|
| 463 |
+
|
| 464 |
+
allowed_logical_cpu_indices = _get_allowed_logical_cpu_indices_for_numa_node(
|
| 465 |
+
numa_node_index=numa_node_index
|
| 466 |
+
)
|
| 467 |
+
|
| 468 |
+
# Arbitrarily use the min logical cpu index on the max level cache
|
| 469 |
+
# to represent the max level cache.
|
| 470 |
+
max_level_cache_to_allowed_logical_cpu_indices = _group_by(
|
| 471 |
+
allowed_logical_cpu_indices,
|
| 472 |
+
lambda logical_cpu_index: min(
|
| 473 |
+
_get_logical_cpus_sharing_same_max_level_cache_as(
|
| 474 |
+
logical_cpu_index=logical_cpu_index
|
| 475 |
+
)
|
| 476 |
+
),
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
max_level_cache_to_allowed_logical_cpu_indices = dict(
|
| 480 |
+
sorted(
|
| 481 |
+
max_level_cache_to_allowed_logical_cpu_indices.items(),
|
| 482 |
+
# First, prioritize caches with more available cpus
|
| 483 |
+
# Second, prioritize lower index cpus (just for clarity/consistency)
|
| 484 |
+
key=lambda item: (-len(item[1]), item[0]),
|
| 485 |
+
)
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
cache_index_for_original_gpu = original_gpu_relative_index % len(
|
| 489 |
+
max_level_cache_to_allowed_logical_cpu_indices
|
| 490 |
+
)
|
| 491 |
+
logical_cpu_indices_for_original_gpu = list(
|
| 492 |
+
max_level_cache_to_allowed_logical_cpu_indices.values()
|
| 493 |
+
)[cache_index_for_original_gpu]
|
| 494 |
+
|
| 495 |
+
return logical_cpu_indices_for_original_gpu
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
K = TypeVar("K")
|
| 499 |
+
V = TypeVar("V")
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def _group_by(values: Iterable[V], get_key: Callable[[V], K]) -> dict[K, set[V]]:
|
| 503 |
+
"""
|
| 504 |
+
Groups elements with same key into sets.
|
| 505 |
+
"""
|
| 506 |
+
key_to_values: defaultdict[K, set[V]] = defaultdict(set)
|
| 507 |
+
for value in values:
|
| 508 |
+
key = get_key(value)
|
| 509 |
+
key_to_values[key].add(value)
|
| 510 |
+
return key_to_values
|
| 511 |
+
|
| 512 |
+
|
| 513 |
+
def _get_logical_cpu_indices_sharing_same_physical_core_as(
|
| 514 |
+
*, logical_cpu_index: int
|
| 515 |
+
) -> set[int]:
|
| 516 |
+
thread_siblings_list_absolute_path = (
|
| 517 |
+
f"/sys/devices/system/cpu/cpu{logical_cpu_index}/topology/thread_siblings_list"
|
| 518 |
+
)
|
| 519 |
+
with open(thread_siblings_list_absolute_path) as f:
|
| 520 |
+
return _get_set_of_int_from_ranges_str(f.read())
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
def _get_logical_cpus_sharing_same_max_level_cache_as(
|
| 524 |
+
*, logical_cpu_index: int
|
| 525 |
+
) -> set[int]:
|
| 526 |
+
cpu_cache_dir_absolute_path = (
|
| 527 |
+
f"/sys/devices/system/cpu/cpu{logical_cpu_index}/cache"
|
| 528 |
+
)
|
| 529 |
+
|
| 530 |
+
max_level = -1
|
| 531 |
+
logical_cpus_sharing_max_level_cache = set()
|
| 532 |
+
for entry in os.listdir(cpu_cache_dir_absolute_path):
|
| 533 |
+
if not entry.startswith("index") or not entry[5:].isdecimal():
|
| 534 |
+
continue
|
| 535 |
+
cache_index_absolute_path = os.path.join(cpu_cache_dir_absolute_path, entry)
|
| 536 |
+
|
| 537 |
+
# Filter out other cache types like Instruction
|
| 538 |
+
type_absolute_path = os.path.join(cache_index_absolute_path, "type")
|
| 539 |
+
with open(type_absolute_path) as type_file:
|
| 540 |
+
if type_file.read().strip() not in {"Unified", "Data"}:
|
| 541 |
+
continue
|
| 542 |
+
|
| 543 |
+
level_absolute_path = os.path.join(cache_index_absolute_path, "level")
|
| 544 |
+
with open(level_absolute_path) as level_file:
|
| 545 |
+
level = int(level_file.read())
|
| 546 |
+
if level <= max_level:
|
| 547 |
+
continue
|
| 548 |
+
|
| 549 |
+
max_level = level
|
| 550 |
+
shared_cpu_list_absolute_path = os.path.join(
|
| 551 |
+
cache_index_absolute_path, "shared_cpu_list"
|
| 552 |
+
)
|
| 553 |
+
with open(shared_cpu_list_absolute_path) as share_cpu_list_file:
|
| 554 |
+
logical_cpus_sharing_max_level_cache = _get_set_of_int_from_ranges_str(
|
| 555 |
+
share_cpu_list_file.read()
|
| 556 |
+
)
|
| 557 |
+
|
| 558 |
+
return logical_cpus_sharing_max_level_cache
|
| 559 |
+
|
| 560 |
+
|
| 561 |
+
def _get_allowed_logical_cpu_indices_for_numa_node(*, numa_node_index: int) -> set[int]:
|
| 562 |
+
all_cpu_indices = _get_cpu_indices_for_numa_node_MAYBE_NOT_ALLOWED(
|
| 563 |
+
numa_node_index=numa_node_index
|
| 564 |
+
)
|
| 565 |
+
allowed_cpu_indices = _get_allowed_cpu_indices_for_current_thread()
|
| 566 |
+
return all_cpu_indices & allowed_cpu_indices
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
def _get_cpu_indices_for_numa_node_MAYBE_NOT_ALLOWED(
|
| 570 |
+
*, numa_node_index: int
|
| 571 |
+
) -> set[int]:
|
| 572 |
+
"""
|
| 573 |
+
Returns:
|
| 574 |
+
Indices of all CPUs associated with numa_node_index. However, the list
|
| 575 |
+
is not filtered based on whether the thread is allowed to use them.
|
| 576 |
+
"""
|
| 577 |
+
cpulist_absolute_path = f"/sys/devices/system/node/node{numa_node_index}/cpulist"
|
| 578 |
+
try:
|
| 579 |
+
with open(cpulist_absolute_path) as f:
|
| 580 |
+
cpu_range_str = f.read()
|
| 581 |
+
except FileNotFoundError as e:
|
| 582 |
+
raise RuntimeError(
|
| 583 |
+
f"Could not determine CPUs corresponding to {numa_node_index=}."
|
| 584 |
+
) from e
|
| 585 |
+
return _get_set_of_int_from_ranges_str(cpu_range_str)
|
| 586 |
+
|
| 587 |
+
|
| 588 |
+
def _get_gpu_count() -> int:
|
| 589 |
+
return torch.cuda.device_count()
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
def _get_numa_node_index_for_gpu_index(*, gpu_index: int) -> int:
|
| 593 |
+
device_properties = torch.cuda.get_device_properties(gpu_index)
|
| 594 |
+
|
| 595 |
+
domain = device_properties.pci_domain_id # type: ignore[attr-defined]
|
| 596 |
+
bus = device_properties.pci_bus_id # type: ignore[attr-defined]
|
| 597 |
+
device = device_properties.pci_device_id # type: ignore[attr-defined]
|
| 598 |
+
|
| 599 |
+
# Format to sysfs PCI address: "0000:dc:00.0"
|
| 600 |
+
pci_addr = f"{domain:04x}:{bus:02x}:{device:02x}.0"
|
| 601 |
+
|
| 602 |
+
pci_numa_node_absolute_path = f"/sys/bus/pci/devices/{pci_addr}/numa_node"
|
| 603 |
+
with open(pci_numa_node_absolute_path) as f:
|
| 604 |
+
# In systems with only one NUMA node, this will
|
| 605 |
+
# often be saved as -1. In those cases, there is obviously
|
| 606 |
+
# at least one numa node, 0, so we use that.
|
| 607 |
+
return max(int(f.read().strip()), 0)
|
| 608 |
+
|
| 609 |
+
|
| 610 |
+
def _get_gpu_indices_for_numa_node(*, numa_node_index: int) -> set[int]:
|
| 611 |
+
return {
|
| 612 |
+
gpu_index
|
| 613 |
+
for gpu_index in range(_get_gpu_count())
|
| 614 |
+
if _get_numa_node_index_for_gpu_index(gpu_index=gpu_index) == numa_node_index
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
def _get_socket_index_for_numa_node(*, numa_node_index: int) -> int:
|
| 619 |
+
arbitrary_cpu_index = _get_arbitrary_allowed_cpu_index_for_numa_node(
|
| 620 |
+
numa_node_index=numa_node_index
|
| 621 |
+
)
|
| 622 |
+
|
| 623 |
+
return _get_socket_index_for_cpu(cpu_index=arbitrary_cpu_index)
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def _get_socket_index_for_cpu(*, cpu_index: int) -> int:
|
| 627 |
+
package_id_absolute_path = (
|
| 628 |
+
f"/sys/devices/system/cpu/cpu{cpu_index}/topology/physical_package_id"
|
| 629 |
+
)
|
| 630 |
+
try:
|
| 631 |
+
with open(package_id_absolute_path) as f:
|
| 632 |
+
return int(f.read().strip())
|
| 633 |
+
except FileNotFoundError as e:
|
| 634 |
+
raise RuntimeError(f"Could not determine socket for {cpu_index=}") from e
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
def _get_arbitrary_allowed_cpu_index_for_numa_node(*, numa_node_index: int) -> int:
|
| 638 |
+
return min(
|
| 639 |
+
_get_allowed_logical_cpu_indices_for_numa_node(numa_node_index=numa_node_index)
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
def _get_set_of_int_from_ranges_str(ranges_str: str) -> set[int]:
|
| 644 |
+
"""
|
| 645 |
+
Util for parsing a string of int ranges, as in a sysfs file.
|
| 646 |
+
|
| 647 |
+
Args:
|
| 648 |
+
ranges_str: E.g., "0-2,4,6-7"
|
| 649 |
+
|
| 650 |
+
Returns:
|
| 651 |
+
E.g., {0, 1, 2, 4, 6, 7}
|
| 652 |
+
"""
|
| 653 |
+
ints: set[int] = set()
|
| 654 |
+
for range_str in ranges_str.split(","):
|
| 655 |
+
range_str = range_str.strip()
|
| 656 |
+
if not range_str:
|
| 657 |
+
continue
|
| 658 |
+
if "-" in range_str:
|
| 659 |
+
start_str, end_str = range_str.split("-")
|
| 660 |
+
start, end = int(start_str), int(end_str)
|
| 661 |
+
ints.update(range(start, end + 1))
|
| 662 |
+
else:
|
| 663 |
+
ints.add(int(range_str))
|
| 664 |
+
return ints
|
| 665 |
+
|
| 666 |
+
|
| 667 |
+
def _get_ranges_str_from_ints(ints: Iterable[int]) -> str:
|
| 668 |
+
"""
|
| 669 |
+
Convert a set of integers to a compact string with ranges.
|
| 670 |
+
|
| 671 |
+
Args:
|
| 672 |
+
ints: E.g., {0, 1, 2, 4, 6, 7}
|
| 673 |
+
|
| 674 |
+
Returns:
|
| 675 |
+
E.g., "0-2,4,6-7"
|
| 676 |
+
"""
|
| 677 |
+
if not ints:
|
| 678 |
+
return ""
|
| 679 |
+
|
| 680 |
+
sorted_ints = sorted(ints)
|
| 681 |
+
ranges = []
|
| 682 |
+
start = prev = sorted_ints[0]
|
| 683 |
+
|
| 684 |
+
for num in sorted_ints[1:]:
|
| 685 |
+
if num == prev + 1:
|
| 686 |
+
prev = num
|
| 687 |
+
else:
|
| 688 |
+
if start == prev:
|
| 689 |
+
ranges.append(f"{start}")
|
| 690 |
+
else:
|
| 691 |
+
ranges.append(f"{start}-{prev}")
|
| 692 |
+
start = prev = num
|
| 693 |
+
|
| 694 |
+
# Append the last range
|
| 695 |
+
if start == prev:
|
| 696 |
+
ranges.append(f"{start}")
|
| 697 |
+
else:
|
| 698 |
+
ranges.append(f"{start}-{prev}")
|
| 699 |
+
|
| 700 |
+
return ",".join(ranges)
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def _get_systemwide_numa_node_indices() -> set[int]:
|
| 704 |
+
with open("/sys/devices/system/node/possible") as f:
|
| 705 |
+
possible_nodes_str = f.read()
|
| 706 |
+
|
| 707 |
+
return _get_set_of_int_from_ranges_str(possible_nodes_str)
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
def _get_numa_node_indices_for_socket_index(*, socket_index: int) -> set[int]:
|
| 711 |
+
systemwide_numa_node_indices = _get_systemwide_numa_node_indices()
|
| 712 |
+
|
| 713 |
+
matching_numa_node_indices = set()
|
| 714 |
+
for numa_node_index in systemwide_numa_node_indices:
|
| 715 |
+
arbitrary_cpu_index = _get_arbitrary_allowed_cpu_index_for_numa_node(
|
| 716 |
+
numa_node_index=numa_node_index
|
| 717 |
+
)
|
| 718 |
+
if socket_index == _get_socket_index_for_cpu(cpu_index=arbitrary_cpu_index):
|
| 719 |
+
matching_numa_node_indices.add(numa_node_index)
|
| 720 |
+
|
| 721 |
+
return matching_numa_node_indices
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def _get_allowed_cpu_indices_for_current_thread() -> set[int]:
|
| 725 |
+
# 0 denotes current thread
|
| 726 |
+
# pyrefly: ignore [missing-attribute]
|
| 727 |
+
return os.sched_getaffinity(0) # type:ignore[attr-defined]
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/__init__.py
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mypy: allow-untyped-defs
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
__all__ = [
|
| 6 |
+
# Modules
|
| 7 |
+
"errors",
|
| 8 |
+
"ops",
|
| 9 |
+
# Public functions
|
| 10 |
+
"export",
|
| 11 |
+
"is_in_onnx_export",
|
| 12 |
+
# Base error
|
| 13 |
+
"OnnxExporterError",
|
| 14 |
+
"ONNXProgram",
|
| 15 |
+
"ExportableModule",
|
| 16 |
+
"InputObserver",
|
| 17 |
+
]
|
| 18 |
+
|
| 19 |
+
from typing import Any, TYPE_CHECKING
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from torch._C import _onnx as _C_onnx
|
| 23 |
+
from torch._C._onnx import ( # Deprecated members that are excluded from __all__
|
| 24 |
+
OperatorExportTypes as OperatorExportTypes,
|
| 25 |
+
TensorProtoDataType as TensorProtoDataType,
|
| 26 |
+
TrainingMode as TrainingMode,
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
from . import errors, ops
|
| 30 |
+
from ._internal.exporter._exportable_module import ExportableModule
|
| 31 |
+
from ._internal.exporter._input_observer import InputObserver
|
| 32 |
+
from ._internal.exporter._onnx_program import ONNXProgram
|
| 33 |
+
from ._internal.torchscript_exporter import ( # Deprecated members that are excluded from __all__
|
| 34 |
+
symbolic_helper,
|
| 35 |
+
symbolic_opset10,
|
| 36 |
+
symbolic_opset9,
|
| 37 |
+
utils,
|
| 38 |
+
)
|
| 39 |
+
from ._internal.torchscript_exporter._type_utils import (
|
| 40 |
+
JitScalarType, # Deprecated members that are excluded from __all__
|
| 41 |
+
)
|
| 42 |
+
from ._internal.torchscript_exporter.utils import ( # Deprecated members that are excluded from __all__
|
| 43 |
+
register_custom_op_symbolic,
|
| 44 |
+
select_model_mode_for_export, # pyrefly: ignore # deprecated
|
| 45 |
+
unregister_custom_op_symbolic,
|
| 46 |
+
)
|
| 47 |
+
from .errors import OnnxExporterError
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
if TYPE_CHECKING:
|
| 51 |
+
import os
|
| 52 |
+
from collections.abc import Callable, Collection, Mapping, Sequence
|
| 53 |
+
|
| 54 |
+
# Set namespace for exposed private names
|
| 55 |
+
ONNXProgram.__module__ = "torch.onnx"
|
| 56 |
+
ExportableModule.__module__ = "torch.onnx"
|
| 57 |
+
OnnxExporterError.__module__ = "torch.onnx"
|
| 58 |
+
InputObserver.__module__ = "torch.onnx"
|
| 59 |
+
|
| 60 |
+
# TODO(justinchuby): Remove these two properties
|
| 61 |
+
producer_name = "pytorch"
|
| 62 |
+
producer_version = _C_onnx.PRODUCER_VERSION
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def export(
|
| 66 |
+
model: torch.nn.Module
|
| 67 |
+
| torch.export.ExportedProgram
|
| 68 |
+
| torch.jit.ScriptModule
|
| 69 |
+
| torch.jit.ScriptFunction,
|
| 70 |
+
args: tuple[Any, ...] = (),
|
| 71 |
+
f: str | os.PathLike | None = None,
|
| 72 |
+
*,
|
| 73 |
+
kwargs: dict[str, Any] | None = None,
|
| 74 |
+
verbose: bool | None = None,
|
| 75 |
+
input_names: Sequence[str] | None = None,
|
| 76 |
+
output_names: Sequence[str] | None = None,
|
| 77 |
+
opset_version: int | None = None,
|
| 78 |
+
dynamo: bool = True,
|
| 79 |
+
# Dynamo only options
|
| 80 |
+
external_data: bool = True,
|
| 81 |
+
dynamic_shapes: dict[str, Any] | tuple[Any, ...] | list[Any] | None = None,
|
| 82 |
+
custom_translation_table: dict[Callable, Callable] | None = None,
|
| 83 |
+
report: bool = False,
|
| 84 |
+
optimize: bool = True,
|
| 85 |
+
verify: bool = False,
|
| 86 |
+
profile: bool = False,
|
| 87 |
+
dump_exported_program: bool = False,
|
| 88 |
+
artifacts_dir: str | os.PathLike = ".",
|
| 89 |
+
# BC options
|
| 90 |
+
export_params: bool = True,
|
| 91 |
+
keep_initializers_as_inputs: bool = False,
|
| 92 |
+
dynamic_axes: Mapping[str, Mapping[int, str]]
|
| 93 |
+
| Mapping[str, Sequence[int]]
|
| 94 |
+
| None = None,
|
| 95 |
+
# Deprecated options
|
| 96 |
+
training: _C_onnx.TrainingMode = _C_onnx.TrainingMode.EVAL,
|
| 97 |
+
operator_export_type: _C_onnx.OperatorExportTypes = _C_onnx.OperatorExportTypes.ONNX,
|
| 98 |
+
do_constant_folding: bool = True,
|
| 99 |
+
custom_opsets: Mapping[str, int] | None = None,
|
| 100 |
+
export_modules_as_functions: bool | Collection[type[torch.nn.Module]] = False,
|
| 101 |
+
autograd_inlining: bool = True,
|
| 102 |
+
) -> ONNXProgram | None:
|
| 103 |
+
r"""Exports a model into ONNX format.
|
| 104 |
+
|
| 105 |
+
Setting ``dynamo=True`` enables the new ONNX export logic
|
| 106 |
+
which is based on :class:`torch.export.ExportedProgram` and a more modern
|
| 107 |
+
set of translation logic. This is the recommended and default way to export models
|
| 108 |
+
to ONNX.
|
| 109 |
+
|
| 110 |
+
When ``dynamo=True``:
|
| 111 |
+
|
| 112 |
+
The exporter tries the following strategies to get an ExportedProgram for conversion to ONNX.
|
| 113 |
+
|
| 114 |
+
#. If the model is already an ExportedProgram, it will be used as-is.
|
| 115 |
+
#. Use :func:`torch.export.export` and set ``strict=False``.
|
| 116 |
+
#. Use :func:`torch.export.export` and set ``strict=True``.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
model: The model to be exported.
|
| 120 |
+
args: Example positional inputs. Any non-Tensor arguments will be hard-coded into the
|
| 121 |
+
exported model; any Tensor arguments will become inputs of the exported model,
|
| 122 |
+
in the order they occur in the tuple.
|
| 123 |
+
f: Path to the output ONNX model file. E.g. "model.onnx". This argument is kept for
|
| 124 |
+
backward compatibility. It is recommended to leave unspecified (None)
|
| 125 |
+
and use the returned :class:`torch.onnx.ONNXProgram` to serialize the model
|
| 126 |
+
to a file instead.
|
| 127 |
+
kwargs: Optional example keyword inputs.
|
| 128 |
+
verbose: Whether to enable verbose logging.
|
| 129 |
+
input_names: names to assign to the input nodes of the graph, in order.
|
| 130 |
+
output_names: names to assign to the output nodes of the graph, in order.
|
| 131 |
+
opset_version: The version of the
|
| 132 |
+
`default (ai.onnx) opset <https://github.com/onnx/onnx/blob/master/docs/Operators.md>`_
|
| 133 |
+
to target. You should set ``opset_version`` according to the supported opset versions
|
| 134 |
+
of the runtime backend or compiler you want to run the exported model with.
|
| 135 |
+
Leave as default (``None``) to use the recommended version, or refer to
|
| 136 |
+
the ONNX operators documentation for more information.
|
| 137 |
+
dynamo: Whether to export the model with ``torch.export`` ExportedProgram instead of TorchScript.
|
| 138 |
+
external_data: Whether to save the model weights as an external data file.
|
| 139 |
+
This is required for models with large weights that exceed the ONNX file size limit (2GB).
|
| 140 |
+
When False, the weights are saved in the ONNX file with the model architecture.
|
| 141 |
+
dynamic_shapes: A dictionary or a tuple of dynamic shapes for the model inputs. Refer to
|
| 142 |
+
:func:`torch.export.export` for more details. This is only used (and preferred) when dynamo is True.
|
| 143 |
+
Note that dynamic_shapes is designed to be used when the model is exported with dynamo=True, while
|
| 144 |
+
dynamic_axes is used when dynamo=False.
|
| 145 |
+
custom_translation_table: A dictionary of custom decompositions for operators in the model.
|
| 146 |
+
The dictionary should have the callable target in the fx Node as the key (e.g. ``torch.ops.aten.stft.default``),
|
| 147 |
+
and the value should be a function that builds that graph using ONNX Script. This option
|
| 148 |
+
is only valid when dynamo is True.
|
| 149 |
+
report: Whether to generate a markdown report for the export process. This option
|
| 150 |
+
is only valid when dynamo is True.
|
| 151 |
+
optimize: Whether to optimize the exported model. This option
|
| 152 |
+
is only valid when dynamo is True. Default is True.
|
| 153 |
+
verify: Whether to verify the exported model using ONNX Runtime. This option
|
| 154 |
+
is only valid when dynamo is True.
|
| 155 |
+
profile: Whether to profile the export process. This option
|
| 156 |
+
is only valid when dynamo is True.
|
| 157 |
+
dump_exported_program: Whether to dump the :class:`torch.export.ExportedProgram` to a file.
|
| 158 |
+
This is useful for debugging the exporter. This option is only valid when dynamo is True.
|
| 159 |
+
artifacts_dir: The directory to save the debugging artifacts like the report and the serialized
|
| 160 |
+
exported program. This option is only valid when dynamo is True.
|
| 161 |
+
export_params: **When ``f`` is specified**: If false, parameters (weights) will not be exported.
|
| 162 |
+
|
| 163 |
+
You can also leave it unspecified and use the returned :class:`torch.onnx.ONNXProgram`
|
| 164 |
+
to control how initializers are treated when serializing the model.
|
| 165 |
+
keep_initializers_as_inputs: **When ``f`` is specified**: If True, all the
|
| 166 |
+
initializers (typically corresponding to model weights) in the
|
| 167 |
+
exported graph will also be added as inputs to the graph. If False,
|
| 168 |
+
then initializers are not added as inputs to the graph, and only
|
| 169 |
+
the user inputs are added as inputs.
|
| 170 |
+
|
| 171 |
+
Set this to True if you intend to supply model weights at runtime.
|
| 172 |
+
Set it to False if the weights are static to allow for better optimizations
|
| 173 |
+
(e.g. constant folding) by backends/runtimes.
|
| 174 |
+
|
| 175 |
+
You can also leave it unspecified and use the returned :class:`torch.onnx.ONNXProgram`
|
| 176 |
+
to control how initializers are treated when serializing the model.
|
| 177 |
+
dynamic_axes:
|
| 178 |
+
Deprecated: Prefer specifying ``dynamic_shapes`` when ``dynamo=True``.
|
| 179 |
+
|
| 180 |
+
By default the exported model will have the shapes of all input and output tensors
|
| 181 |
+
set to exactly match those given in ``args``. To specify axes of tensors as
|
| 182 |
+
dynamic (i.e. known only at run-time), set ``dynamic_axes`` to a dict with schema:
|
| 183 |
+
|
| 184 |
+
* KEY (str): an input or output name. Each name must also be provided in ``input_names`` or
|
| 185 |
+
``output_names``.
|
| 186 |
+
* VALUE (dict or list): If a dict, keys are axis indices and values are axis names. If a
|
| 187 |
+
list, each element is an axis index.
|
| 188 |
+
|
| 189 |
+
For example::
|
| 190 |
+
|
| 191 |
+
class SumModule(torch.nn.Module):
|
| 192 |
+
def forward(self, x):
|
| 193 |
+
return torch.sum(x, dim=1)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
torch.onnx.export(
|
| 197 |
+
SumModule(),
|
| 198 |
+
(torch.ones(2, 2),),
|
| 199 |
+
"onnx.pb",
|
| 200 |
+
input_names=["x"],
|
| 201 |
+
output_names=["sum"],
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
Produces::
|
| 205 |
+
|
| 206 |
+
input {
|
| 207 |
+
name: "x"
|
| 208 |
+
...
|
| 209 |
+
shape {
|
| 210 |
+
dim {
|
| 211 |
+
dim_value: 2 # axis 0
|
| 212 |
+
}
|
| 213 |
+
dim {
|
| 214 |
+
dim_value: 2 # axis 1
|
| 215 |
+
...
|
| 216 |
+
output {
|
| 217 |
+
name: "sum"
|
| 218 |
+
...
|
| 219 |
+
shape {
|
| 220 |
+
dim {
|
| 221 |
+
dim_value: 2 # axis 0
|
| 222 |
+
...
|
| 223 |
+
|
| 224 |
+
While::
|
| 225 |
+
|
| 226 |
+
torch.onnx.export(
|
| 227 |
+
SumModule(),
|
| 228 |
+
(torch.ones(2, 2),),
|
| 229 |
+
"onnx.pb",
|
| 230 |
+
input_names=["x"],
|
| 231 |
+
output_names=["sum"],
|
| 232 |
+
dynamic_axes={
|
| 233 |
+
# dict value: manually named axes
|
| 234 |
+
"x": {0: "my_custom_axis_name"},
|
| 235 |
+
# list value: automatic names
|
| 236 |
+
"sum": [0],
|
| 237 |
+
},
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
Produces::
|
| 241 |
+
|
| 242 |
+
input {
|
| 243 |
+
name: "x"
|
| 244 |
+
...
|
| 245 |
+
shape {
|
| 246 |
+
dim {
|
| 247 |
+
dim_param: "my_custom_axis_name" # axis 0
|
| 248 |
+
}
|
| 249 |
+
dim {
|
| 250 |
+
dim_value: 2 # axis 1
|
| 251 |
+
...
|
| 252 |
+
output {
|
| 253 |
+
name: "sum"
|
| 254 |
+
...
|
| 255 |
+
shape {
|
| 256 |
+
dim {
|
| 257 |
+
dim_param: "sum_dynamic_axes_1" # axis 0
|
| 258 |
+
...
|
| 259 |
+
|
| 260 |
+
training: Deprecated option. Instead, set the training mode of the model before exporting.
|
| 261 |
+
operator_export_type: Deprecated option. Only ONNX is supported.
|
| 262 |
+
do_constant_folding: Deprecated option.
|
| 263 |
+
custom_opsets: Deprecated option.
|
| 264 |
+
export_modules_as_functions: Deprecated option.
|
| 265 |
+
autograd_inlining: Deprecated option.
|
| 266 |
+
|
| 267 |
+
Returns:
|
| 268 |
+
:class:`torch.onnx.ONNXProgram` if dynamo is True, otherwise None.
|
| 269 |
+
|
| 270 |
+
.. versionchanged:: 2.6
|
| 271 |
+
``training`` is now deprecated. Instead, set the training mode of the model before exporting.
|
| 272 |
+
``operator_export_type`` is now deprecated. Only ONNX is supported.
|
| 273 |
+
``do_constant_folding`` is now deprecated. It is always enabled.
|
| 274 |
+
``export_modules_as_functions`` is now deprecated.
|
| 275 |
+
``autograd_inlining`` is now deprecated.
|
| 276 |
+
.. versionchanged:: 2.7
|
| 277 |
+
``optimize`` is now True by default.
|
| 278 |
+
.. versionchanged:: 2.9
|
| 279 |
+
``dynamo`` is now True by default.
|
| 280 |
+
.. versionchanged:: 2.11
|
| 281 |
+
``fallback`` option has been removed.
|
| 282 |
+
"""
|
| 283 |
+
if dynamo is True or isinstance(
|
| 284 |
+
model, (torch.export.ExportedProgram, ExportableModule)
|
| 285 |
+
):
|
| 286 |
+
from torch.onnx._internal.exporter import _compat
|
| 287 |
+
|
| 288 |
+
if isinstance(args, torch.Tensor):
|
| 289 |
+
args = (args,)
|
| 290 |
+
|
| 291 |
+
return _compat.export_compat(
|
| 292 |
+
model,
|
| 293 |
+
args,
|
| 294 |
+
f,
|
| 295 |
+
kwargs=kwargs,
|
| 296 |
+
export_params=export_params,
|
| 297 |
+
verbose=verbose,
|
| 298 |
+
input_names=input_names,
|
| 299 |
+
output_names=output_names,
|
| 300 |
+
opset_version=opset_version,
|
| 301 |
+
custom_translation_table=custom_translation_table,
|
| 302 |
+
dynamic_axes=dynamic_axes,
|
| 303 |
+
keep_initializers_as_inputs=keep_initializers_as_inputs,
|
| 304 |
+
external_data=external_data,
|
| 305 |
+
dynamic_shapes=dynamic_shapes,
|
| 306 |
+
report=report,
|
| 307 |
+
optimize=optimize,
|
| 308 |
+
verify=verify,
|
| 309 |
+
profile=profile,
|
| 310 |
+
dump_exported_program=dump_exported_program,
|
| 311 |
+
artifacts_dir=artifacts_dir,
|
| 312 |
+
)
|
| 313 |
+
else:
|
| 314 |
+
import warnings
|
| 315 |
+
|
| 316 |
+
from ._internal.torchscript_exporter.utils import export
|
| 317 |
+
|
| 318 |
+
warnings.warn(
|
| 319 |
+
"You are using the legacy TorchScript-based ONNX export. Starting in PyTorch 2.9, "
|
| 320 |
+
"the new torch.export-based ONNX exporter has become the default. "
|
| 321 |
+
"Learn more about the new export logic: https://docs.pytorch.org/docs/stable/onnx_export.html. "
|
| 322 |
+
"For exporting control flow: "
|
| 323 |
+
"https://pytorch.org/tutorials/beginner/onnx/export_control_flow_model_to_onnx_tutorial.html",
|
| 324 |
+
category=DeprecationWarning,
|
| 325 |
+
stacklevel=2,
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
if dynamic_shapes:
|
| 329 |
+
raise ValueError(
|
| 330 |
+
"The exporter only supports dynamic shapes "
|
| 331 |
+
"through parameter dynamic_axes when dynamo=False."
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
export(
|
| 335 |
+
model,
|
| 336 |
+
args,
|
| 337 |
+
f, # type: ignore[arg-type]
|
| 338 |
+
kwargs=kwargs,
|
| 339 |
+
export_params=export_params,
|
| 340 |
+
verbose=verbose is True,
|
| 341 |
+
input_names=input_names,
|
| 342 |
+
output_names=output_names,
|
| 343 |
+
opset_version=opset_version,
|
| 344 |
+
dynamic_axes=dynamic_axes,
|
| 345 |
+
keep_initializers_as_inputs=keep_initializers_as_inputs,
|
| 346 |
+
training=training,
|
| 347 |
+
operator_export_type=operator_export_type,
|
| 348 |
+
do_constant_folding=do_constant_folding,
|
| 349 |
+
custom_opsets=custom_opsets,
|
| 350 |
+
export_modules_as_functions=export_modules_as_functions,
|
| 351 |
+
autograd_inlining=autograd_inlining,
|
| 352 |
+
)
|
| 353 |
+
return None
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
def is_in_onnx_export() -> bool:
|
| 357 |
+
"""Returns whether it is in the middle of ONNX export."""
|
| 358 |
+
from torch.onnx._internal.exporter import _flags
|
| 359 |
+
from torch.onnx._internal.torchscript_exporter._globals import GLOBALS
|
| 360 |
+
|
| 361 |
+
return GLOBALS.in_onnx_export or _flags._is_onnx_exporting
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/_constants.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Constant values used in ONNX."""
|
| 2 |
+
|
| 3 |
+
ONNX_ARCHIVE_MODEL_PROTO_NAME = "__MODEL_PROTO"
|
| 4 |
+
|
| 5 |
+
ONNX_BASE_OPSET = 9
|
| 6 |
+
ONNX_MIN_OPSET = 7
|
| 7 |
+
ONNX_MAX_OPSET = 23
|
| 8 |
+
ONNX_TORCHSCRIPT_EXPORTER_MAX_OPSET = 20
|
| 9 |
+
ONNX_DEFAULT_OPSET = 20
|
| 10 |
+
ONNX_CONSTANT_FOLDING_MIN_OPSET = 9
|
| 11 |
+
|
| 12 |
+
PYTORCH_GITHUB_ISSUES_URL = "https://github.com/pytorch/pytorch/issues"
|
| 13 |
+
|
| 14 |
+
INT64_MAX = 9223372036854775807
|
| 15 |
+
INT32_MAX = 2147483647
|
| 16 |
+
INT16_MAX = 32767
|
| 17 |
+
INT8_MAX = 127
|
| 18 |
+
UINT8_MAX = 255
|
| 19 |
+
|
| 20 |
+
INT64_MIN = -9223372036854775808
|
| 21 |
+
INT32_MIN = -2147483648
|
| 22 |
+
INT16_MIN = -32768
|
| 23 |
+
INT8_MIN = -128
|
| 24 |
+
UINT8_MIN = 0
|
workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/_flags.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Internal feature flags for torch.onnx.
|
| 2 |
+
|
| 3 |
+
NOTE: These flags are experimental only. Any flag here can be removed at any
|
| 4 |
+
time without notice.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
logger = logging.getLogger(__name__)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _load_boolean_flag(
|
| 15 |
+
name: str,
|
| 16 |
+
*,
|
| 17 |
+
this_will: str,
|
| 18 |
+
deprecated: bool = False,
|
| 19 |
+
default: bool = False,
|
| 20 |
+
) -> bool:
|
| 21 |
+
"""Load a boolean flag from environment variable.
|
| 22 |
+
|
| 23 |
+
Args:
|
| 24 |
+
name: The name of the environment variable.
|
| 25 |
+
this_will: A string that describes what this flag will do.
|
| 26 |
+
deprecated: Whether this flag is deprecated.
|
| 27 |
+
default: The default value if envvar not defined.
|
| 28 |
+
"""
|
| 29 |
+
undefined = os.getenv(name) is None
|
| 30 |
+
state = os.getenv(name) == "1"
|
| 31 |
+
if state:
|
| 32 |
+
if deprecated:
|
| 33 |
+
logger.error(
|
| 34 |
+
"Experimental flag %s is deprecated. Please remove it from your environment.",
|
| 35 |
+
name,
|
| 36 |
+
)
|
| 37 |
+
else:
|
| 38 |
+
logger.warning(
|
| 39 |
+
"Experimental flag %s is enabled. This will %s.", name, this_will
|
| 40 |
+
)
|
| 41 |
+
if undefined:
|
| 42 |
+
state = default
|
| 43 |
+
return state
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
ENABLE_DRAFT_EXPORT: bool = _load_boolean_flag(
|
| 47 |
+
"TORCH_ONNX_ENABLE_DRAFT_EXPORT",
|
| 48 |
+
this_will="enable torch.export.draft_export as a strategy for capturing models",
|
| 49 |
+
default=False,
|
| 50 |
+
)
|
| 51 |
+
PREFER_DEFERRED_RUNTIME_ASSERTS_OVER_GUARDS: bool = _load_boolean_flag(
|
| 52 |
+
"TORCH_ONNX_PREFER_DEFERRED_RUNTIME_ASSERTS_OVER_GUARDS",
|
| 53 |
+
this_will="set prefer_deferred_runtime_asserts_over_guards when calling torch.export",
|
| 54 |
+
default=True,
|
| 55 |
+
)
|