anhtld commited on
Commit
db405d0
·
verified ·
1 Parent(s): c4fa0be

auto-sync 2026-07-02T13:37:00Z workspace (part 27)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/INSTALLER +1 -0
  3. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/METADATA +48 -0
  4. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/RECORD +0 -0
  5. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/WHEEL +5 -0
  6. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/entry_points.txt +5 -0
  7. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/LICENSE +84 -0
  8. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/licenses/NOTICE +456 -0
  9. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch-2.12.1+computecanada.dist-info/top_level.txt +2 -0
  10. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/activation.py +20 -0
  11. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/batchnorm.py +11 -0
  12. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/conv.py +29 -0
  13. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/dropout.py +14 -0
  14. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/embedding_ops.py +18 -0
  15. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/functional_modules.py +18 -0
  16. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/linear.py +14 -0
  17. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/normalization.py +26 -0
  18. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/rnn.py +11 -0
  19. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/quantized/modules/utils.py +17 -0
  20. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/__init__.py +48 -0
  21. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_deprecation_utils.py +53 -0
  22. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/__init__.py +10 -0
  23. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/conv_expanded_weights.py +82 -0
  24. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/conv_utils.py +355 -0
  25. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/embedding_expanded_weights.py +88 -0
  26. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/expanded_weights_impl.py +186 -0
  27. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/expanded_weights_utils.py +188 -0
  28. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/group_norm_expanded_weights.py +106 -0
  29. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/instance_norm_expanded_weights.py +101 -0
  30. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/layer_norm_expanded_weights.py +88 -0
  31. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_expanded_weights/linear_expanded_weights.py +63 -0
  32. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_named_member_accessor.py +381 -0
  33. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/_per_sample_grad.py +126 -0
  34. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/clip_grad.py +298 -0
  35. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/convert_parameters.py +90 -0
  36. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/fusion.py +196 -0
  37. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/init.py +55 -0
  38. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/memory_format.py +174 -0
  39. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/parametrizations.py +636 -0
  40. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/parametrize.py +886 -0
  41. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/prune.py +1395 -0
  42. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/rnn.py +589 -0
  43. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/spectral_norm.py +364 -0
  44. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/stateless.py +279 -0
  45. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/nn/utils/weight_norm.py +165 -0
  46. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/numa/__init__.py +0 -0
  47. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/numa/binding.py +727 -0
  48. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/__init__.py +361 -0
  49. workspace/outputs/audit_venv/lib/python3.11/site-packages/torch/onnx/_constants.py +24 -0
  50. 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
+ )