pyborch 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- borch/__init__.py +796 -0
- borch/_base.py +472 -0
- borch/_data.py +443 -0
- borch/_fft.py +564 -0
- borch/_nn.py +4339 -0
- borch/_ops.py +10879 -0
- borch/_optim.py +1448 -0
- borch/_rnn.py +68 -0
- borch/_serialize.py +286 -0
- borch/_tensor.py +2283 -0
- borchvision.py +11277 -0
- pyborch-1.4.0.dist-info/METADATA +1605 -0
- pyborch-1.4.0.dist-info/RECORD +15 -0
- pyborch-1.4.0.dist-info/WHEEL +4 -0
- pyborch-1.4.0.dist-info/licenses/LICENSE +202 -0
borch/__init__.py
ADDED
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
"""borch — a thin PyTorch-shaped layer over numpy.
|
|
2
|
+
|
|
3
|
+
For practising PyTorch **syntax** in a browser (Pyodide) with nothing installed.
|
|
4
|
+
torch is not ported to wasm — hundreds of MB of native code, hand-tuned AVX and
|
|
5
|
+
NEON kernels that do not carry over to wasm SIMD, and OpenMP threads that want
|
|
6
|
+
headers Pyodide does not ship. And **none of that is needed to learn the
|
|
7
|
+
syntax.** numpy is enough.
|
|
8
|
+
|
|
9
|
+
## The design principle — an absent feature beats a wrong answer
|
|
10
|
+
|
|
11
|
+
A subset that behaves even slightly differently from the real thing teaches the
|
|
12
|
+
student something false. So **what is absent throws rather than approximating.**
|
|
13
|
+
It stops loudly rather than quietly producing a different value.
|
|
14
|
+
|
|
15
|
+
Outside the supported range a `BorchError` is raised, and the message says to do
|
|
16
|
+
it on your own machine.
|
|
17
|
+
|
|
18
|
+
## How that is guaranteed
|
|
19
|
+
|
|
20
|
+
Two layers.
|
|
21
|
+
|
|
22
|
+
1. `borch-check` — runs the same **lab tests** against real torch and against the
|
|
23
|
+
subset.
|
|
24
|
+
2. `borch-diff` — compares **the numbers of the same operation directly**,
|
|
25
|
+
independently of the labs (`tests/test_borch_diff.py`). The first alone sees
|
|
26
|
+
only the paths the labs walk, which was 73% of the subset, and backpropagation
|
|
27
|
+
sat in that blind spot.
|
|
28
|
+
|
|
29
|
+
The two checks now cover 86%. What is left is places where no value is at stake,
|
|
30
|
+
such as `__repr__`.
|
|
31
|
+
|
|
32
|
+
Something `borch-diff` actually caught: BatchNorm uses the biased variance for
|
|
33
|
+
the normalisation and the unbiased one for updating running_var. Biased in both
|
|
34
|
+
places is off by 2.6%.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
import builtins as _builtins
|
|
39
|
+
import inspect as _inspect
|
|
40
|
+
import math as _math
|
|
41
|
+
|
|
42
|
+
import numpy as _np
|
|
43
|
+
|
|
44
|
+
from ._base import (
|
|
45
|
+
BorchError, Size, _DEFAULT_DTYPE, _LINE_WIDTH, _NP_TO_DTYPE,
|
|
46
|
+
_PRINT_PRECISION, __all__, _float_formatter, _like_torch, _resolve, _tensor_repr,
|
|
47
|
+
_tensor_str, _unsupported, bool_, device, dtype, float32, float64, int64,
|
|
48
|
+
bfloat16, chalf, complex32, float16, half, int16, int32, long,
|
|
49
|
+
set_printoptions, short,
|
|
50
|
+
# The complex dtype names. `complex128` and `cdouble` exist **as names
|
|
51
|
+
# only** — trying to make one stops at the gate in `Tensor.__init__`.
|
|
52
|
+
cdouble, cfloat, complex128, complex64,
|
|
53
|
+
# The top-level numeric constants. **The five a coverage table counting only
|
|
54
|
+
# `callable` could not see.**
|
|
55
|
+
e, inf, nan, newaxis, pi,
|
|
56
|
+
)
|
|
57
|
+
from ._tensor import (
|
|
58
|
+
Tensor, _CATEGORY, _DEFAULT_BY_CATEGORY, _DataDescriptor, _GradMode, _MinMax, _RANK,
|
|
59
|
+
_category, _grad_mode, _no_bool_subtract, _promote, _scalar_category, _unbroadcast,
|
|
60
|
+
)
|
|
61
|
+
from ._ops import _out # noqa: E402
|
|
62
|
+
from ._ops import (
|
|
63
|
+
Generator, _Cuda, _ERF_A, _ERF_P, _INPLACE_UNARY, _Linalg, _Lstsq, _Namespace, _SVD,
|
|
64
|
+
_abs, _binary_math, _col2im, _compare, _cum_extreme, _diagonal_scatter, _erf64,
|
|
65
|
+
_erfc_pos, _expand_reduced, _gelu, _im2col, _index_at, _index_for,
|
|
66
|
+
_make_inplace, _mat, _nan_mask, _negate, _nm, _one_plus_erf64, _pad2d, _pair, _pick,
|
|
67
|
+
_pool_1d_over_last, _pool_all, _rng, _running_idx, _slice_at, _spread_max,
|
|
68
|
+
_unary, _wrap, _zero_grad, abs, absolute, acos, acosh,
|
|
69
|
+
adaptive_avg_pool2d, allclose, amax, amin, aminmax, arange, arccos, arccosh, arcsin,
|
|
70
|
+
arcsinh, arctan, arctanh, argsort, argwhere, as_tensor, asin, asinh, atan, atan2,
|
|
71
|
+
atanh, atleast_1d, atleast_2d, atleast_3d, avg_pool2d, bincount, bmm, cat, ceil,
|
|
72
|
+
cholesky, chunk, clamp, clip, conv1d, conv2d, conv3d, copysign, cos, cosh,
|
|
73
|
+
cosine_similarity, count_nonzero, cuda, cummax, cummin, cumprod, cumsum, deg2rad,
|
|
74
|
+
det, diag, diagflat, diagonal, diff, dist, dot, dropout, dsplit, eigh, einsum, elu,
|
|
75
|
+
embedding, empty, eq, equal, erf, erfc, exp, exp2, expand, expand_as, expm1, eye,
|
|
76
|
+
constant_pad_nd, dequantize, fake_quantize_per_channel_affine,
|
|
77
|
+
fake_quantize_per_tensor_affine, igamma, igammac, polygamma, resize_as_,
|
|
78
|
+
fft, fix, flip, fliplr, flipud, floor, frac, from_numpy, full, full_like, gather, ge,
|
|
79
|
+
gelu, gt, heaviside, hsplit, hypot, index_select, interpolate, inverse, isfinite,
|
|
80
|
+
isinf, isnan, kthvalue, l1_loss, layer_norm, ldexp, le, leaky_relu, linalg,
|
|
81
|
+
linspace, log, log10, log1p, log2, log_softmax, logaddexp, logaddexp2, logdet,
|
|
82
|
+
logical_and, logical_not, logical_or, logit, logsumexp, lstsq, lt, manual_seed,
|
|
83
|
+
masked_fill, masked_select, matmul, matrix_exp, matrix_power, matrix_rank,
|
|
84
|
+
max_pool1d,
|
|
85
|
+
max_pool2d, max_pool3d, maximum, median, minimum, mm, movedim, msort, multinomial,
|
|
86
|
+
nanmean, nanquantile, nansum, narrow, ne, neg, negative, nll_loss, no_grad, nonzero,
|
|
87
|
+
norm, normalize, ones, ones_like, outer, pad, pinverse, positive, pow, prod, qr,
|
|
88
|
+
quantile, rad2deg, rand, randint, randn, randperm, ravel, reciprocal, relu, repeat,
|
|
89
|
+
repeat_interleave, reshape, roll, rot90, round, rsqrt, select, sgn, sigmoid,
|
|
90
|
+
sign, signbit, silu, sin, sinc, sinh, slogdet, smooth_l1_loss, softmax, solve, sort,
|
|
91
|
+
split, sqrt, square, stack, svd, swapaxes, swapdims, tan, tanh, tensor, tile, topk,
|
|
92
|
+
trace, tril, triu, trunc, unbind, unflatten, unfold, unique, unsqueeze, vsplit,
|
|
93
|
+
where, xlogy, zeros, zeros_like,
|
|
94
|
+
# The ones torch offers under a second name — a name attached to what an
|
|
95
|
+
# operator already does.
|
|
96
|
+
add, adjoint, block_diag, broadcast_shapes, broadcast_tensors, broadcast_to,
|
|
97
|
+
column_stack, concat, concatenate, div, divide, dstack, floor_divide, fmod,
|
|
98
|
+
greater, greater_equal, hstack, less, less_equal, moveaxis, mul, multiply,
|
|
99
|
+
not_equal, remainder, row_stack, rsub, sub, subtract, t, true_divide, vstack,
|
|
100
|
+
# The ones with no computation of their own.
|
|
101
|
+
cross, empty_like, float_power, fmax, fmin, inner, isclose, isin, isneginf,
|
|
102
|
+
isposinf, isreal, kron, lerp, logical_xor, logspace, meshgrid, nan_to_num,
|
|
103
|
+
rand_like, randint_like, randn_like, scalar_tensor, std_mean, var_mean, vdot,
|
|
104
|
+
# The writing side of indexing.
|
|
105
|
+
bucketize, index_add, index_copy, index_fill, scatter, scatter_add,
|
|
106
|
+
searchsorted, take, take_along_dim,
|
|
107
|
+
# The numeric family. The last three are computed as series.
|
|
108
|
+
cdist, corrcoef, cov, cumulative_trapezoid, digamma, erfinv, lgamma,
|
|
109
|
+
tensordot, trapezoid,
|
|
110
|
+
# QR in reflector form. The partner to `linalg.householder_product`, so it
|
|
111
|
+
# exists at top level too.
|
|
112
|
+
geqrf,
|
|
113
|
+
# **The names that exist only at top level.** Some have a different
|
|
114
|
+
# signature from `F`'s, so the positions are moved.
|
|
115
|
+
alpha_dropout_, dropout_, feature_alpha_dropout_, feature_dropout,
|
|
116
|
+
feature_dropout_, grid_sampler, nan_to_num_,
|
|
117
|
+
# The top-level names that are **the same computation** as `F`'s (confirmed
|
|
118
|
+
# by measurement).
|
|
119
|
+
alpha_dropout, bilinear, celu_, channel_shuffle, embedding_bag,
|
|
120
|
+
feature_alpha_dropout, max_pool1d_with_indices, pixel_shuffle,
|
|
121
|
+
pixel_unshuffle, rrelu, rrelu_, selu_, threshold_,
|
|
122
|
+
# **The two with a different signature.** The top level is raw ATen, so the
|
|
123
|
+
# argument order and the enums differ — the `_aten` versions take that place
|
|
124
|
+
# and `F`'s keep their own names.
|
|
125
|
+
batch_norm_aten as batch_norm, ctc_loss_aten as ctc_loss,
|
|
126
|
+
# Gradient modes.
|
|
127
|
+
enable_grad, inference_mode, is_grad_enabled, is_inference,
|
|
128
|
+
is_inference_mode_enabled, set_grad_enabled,
|
|
129
|
+
# Random state.
|
|
130
|
+
get_rng_state, initial_seed, seed, set_rng_state,
|
|
131
|
+
# Introspection.
|
|
132
|
+
can_cast, finfo, get_default_dtype, iinfo, is_distributed, is_floating_point,
|
|
133
|
+
is_nonzero, is_same_size, is_signed, is_storage, is_tensor, promote_types,
|
|
134
|
+
result_type,
|
|
135
|
+
set_default_dtype, typename,
|
|
136
|
+
# Bitwise operations and integer maths. On `bool` they become logical
|
|
137
|
+
# operations — torch looks at the dtype.
|
|
138
|
+
bitwise_and, bitwise_left_shift, bitwise_not, bitwise_or,
|
|
139
|
+
bitwise_right_shift, bitwise_xor, gcd, gcd_, lcm, lcm_,
|
|
140
|
+
arctan2, clamp_max, clamp_max_, clamp_min, clamp_min_, detach_, fill,
|
|
141
|
+
frexp, i0, i0_, logcumsumexp, mvlgamma, nextafter,
|
|
142
|
+
# Window functions. `periodic` is the default and it adds one to the
|
|
143
|
+
# length.
|
|
144
|
+
bartlett_window, blackman_window, hamming_window, hann_window,
|
|
145
|
+
kaiser_window,
|
|
146
|
+
# Shape and indexing. **`as_strided` is a view in torch and a copy here** —
|
|
147
|
+
# the details are written at that place in `_ops.py`.
|
|
148
|
+
as_strided, as_strided_, as_strided_scatter, diag_embed, diagonal_scatter,
|
|
149
|
+
select_scatter, slice_scatter, split_with_sizes, tensor_split,
|
|
150
|
+
unique_consecutive, unravel_index,
|
|
151
|
+
index_put, index_put_, index_reduce, masked_scatter, masked_scatter_,
|
|
152
|
+
put, renorm, scatter_reduce,
|
|
153
|
+
cartesian_prod, chain_matmul, combinations, ger, mv, tril_indices,
|
|
154
|
+
triu_indices, vander,
|
|
155
|
+
# The addmm family. **The in-place versions are not exposed** — torch keeps
|
|
156
|
+
# those as methods only. `addmv_` is the single exception and it alone is
|
|
157
|
+
# here (measured).
|
|
158
|
+
addbmm, addcdiv, addcmul, addmm, addmv, addmv_, addr, baddbmm, sspaddmm,
|
|
159
|
+
# Top-level linear algebra. **The two whose names collide with `linalg`'s
|
|
160
|
+
# have their positions moved** — that side's `lu` spreads `P`, `L` and `U`
|
|
161
|
+
# while this one gives a single packed matrix, and this `lu_solve` takes the
|
|
162
|
+
# right-hand side first.
|
|
163
|
+
cholesky_inverse, cholesky_solve, lobpcg, lu_top as lu,
|
|
164
|
+
lu_solve_top as lu_solve, lu_unpack, orgqr, ormqr, pca_lowrank,
|
|
165
|
+
svd_lowrank, triangular_solve,
|
|
166
|
+
# Statistics. **The four random ones cannot have their values pinned and
|
|
167
|
+
# their extremes are deterministic** — that is what the golden asks about.
|
|
168
|
+
# `stft`, `istft` and `hash_tensor` are names that refuse (no complex, no
|
|
169
|
+
# uint64).
|
|
170
|
+
bernoulli, binomial, gradient, hash_tensor, histc, histogram, histogramdd,
|
|
171
|
+
istft, mode, nanmedian, nonzero_static, normal, poisson, stft, trapz,
|
|
172
|
+
# **The names that have an answer even without complex numbers.** Over the
|
|
173
|
+
# reals the `conj` family is the identity and `is_complex` is false. `imag`
|
|
174
|
+
# alone refuses, and **torch itself does that** (measured).
|
|
175
|
+
angle, asarray, conj, conj_physical, conj_physical_, empty_permuted,
|
|
176
|
+
empty_strided, frombuffer, imag, is_complex, is_conj, is_neg, real,
|
|
177
|
+
resolve_conj, resolve_neg,
|
|
178
|
+
# **Complex numbers.** `complex128` is a name and trying to make one stops —
|
|
179
|
+
# because there is no `float64`. The gradient convention is
|
|
180
|
+
# `∂L/∂re + i·∂L/∂im`, pinned by measurement.
|
|
181
|
+
complex, polar, view_as_complex, view_as_real,
|
|
182
|
+
# **This file uses the builtin `range` in 91 places** — inside `_ops` it has
|
|
183
|
+
# a different name and it is exposed as `range` only on the way out. The same
|
|
184
|
+
# place as `lu` and `lu_solve`.
|
|
185
|
+
range_top as range,
|
|
186
|
+
# **The two distances that exist at top level as well.** In torch these two
|
|
187
|
+
# are **literally the same function** as `F`'s (`torch.pdist is F.pdist` is
|
|
188
|
+
# true).
|
|
189
|
+
#
|
|
190
|
+
# The seven losses that surfaced alongside (`kl_div`, `poisson_nll_loss`, …)
|
|
191
|
+
# are not exposed — the top-level ones are raw ATen operations, so **the
|
|
192
|
+
# default reduction is `none` and `reduction` is an integer.**
|
|
193
|
+
# `torch.kl_div(a, b)` gives `[2,2]` and `F.kl_div(a, b)` gives a scalar.
|
|
194
|
+
# Put down as a friendly alias, they diverge starting at the shape.
|
|
195
|
+
pairwise_distance, pdist,
|
|
196
|
+
)
|
|
197
|
+
from ._nn import (
|
|
198
|
+
AdaptiveAvgPool2d, AvgPool2d, BCELoss, BCEWithLogitsLoss, BatchNorm1d, BatchNorm2d,
|
|
199
|
+
BatchNorm3d, Conv1d, Conv2d, Conv3d, CrossEntropyLoss, Dropout, ELU, Embedding,
|
|
200
|
+
Flatten, GELU, GRU, Identity, L1Loss, LSTM, LayerNorm, LeakyReLU, Linear,
|
|
201
|
+
LogSoftmax, MSELoss, MaxPool1d, MaxPool2d, MaxPool3d, Module, ModuleDict,
|
|
202
|
+
ModuleList, ParameterDict, ParameterList,
|
|
203
|
+
MultiheadAttention, NLLLoss, Parameter, RNN, ReLU, Sequential, SiLU, Sigmoid,
|
|
204
|
+
SmoothL1Loss, Softmax, Tanh, Transformer, TransformerDecoder,
|
|
205
|
+
TransformerDecoderLayer, TransformerEncoder, TransformerEncoderLayer, Unflatten,
|
|
206
|
+
Upsample, _Activation, _Functional, _NN, _RNNBase, _apply_mask, _cls,
|
|
207
|
+
_nn_unsupported, _split_heads, nn, one_hot,
|
|
208
|
+
# **The eight top-level recurrent ones.** torch offers both the layer
|
|
209
|
+
# (`nn.LSTM`) and the function (`torch.lstm`), and what the layer calls
|
|
210
|
+
# inside is the function. The difference is that they take the weights as a
|
|
211
|
+
# list.
|
|
212
|
+
gru, gru_cell, lstm, lstm_cell, rnn_relu, rnn_relu_cell, rnn_tanh,
|
|
213
|
+
rnn_tanh_cell,
|
|
214
|
+
)
|
|
215
|
+
from ._optim import (
|
|
216
|
+
Adadelta, Adagrad, Adam, AdamW, Adamax, ChainedScheduler, ConstantLR,
|
|
217
|
+
CosineAnnealingLR, CosineAnnealingWarmRestarts, ExponentialLR, LambdaLR, LinearLR,
|
|
218
|
+
MultiStepLR, MultiplicativeLR, NAdam, OneCycleLR, Optimizer, PolynomialLR, RAdam,
|
|
219
|
+
RMSprop, ReduceLROnPlateau, SGD, SequentialLR, StepLR, _LRScheduler, _Optim,
|
|
220
|
+
_Scheduler, optim,
|
|
221
|
+
ASGD, Adafactor, LBFGS, Rprop, CyclicLR,
|
|
222
|
+
)
|
|
223
|
+
from ._data import (
|
|
224
|
+
BatchSampler, ChainDataset, ConcatDataset, DataLoader, Dataset, IterableDataset,
|
|
225
|
+
RandomSampler, Sampler, SequentialSampler, StackDataset, Subset,
|
|
226
|
+
SubsetRandomSampler, TensorDataset, WeightedRandomSampler, _Utils, _UtilsData,
|
|
227
|
+
default_collate, random_split, utils,
|
|
228
|
+
)
|
|
229
|
+
from ._rnn import (
|
|
230
|
+
_NnUtils, _NnUtilsRnn, pad_sequence,
|
|
231
|
+
)
|
|
232
|
+
from ._serialize import (
|
|
233
|
+
load, save,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# ==================================================== exposing them as methods
|
|
237
|
+
#
|
|
238
|
+
# torch code mixes `torch.sin(x)` and `x.sin()`. Having module functions only,
|
|
239
|
+
# a tutorial using dot notation stopped with an `AttributeError` — **a feature
|
|
240
|
+
# that exists and is one calling convention short.**
|
|
241
|
+
#
|
|
242
|
+
# This list was not chosen by hand. torch was asked whether `x.f(...)` and
|
|
243
|
+
# `torch.f(x, ...)` give the same value, and only the ones it said yes to are
|
|
244
|
+
# here. 62 came back equal and one differed — `where` (see below). Attaching one
|
|
245
|
+
# of those blindly gives a quietly wrong answer.
|
|
246
|
+
|
|
247
|
+
_AS_METHOD = (
|
|
248
|
+
"allclose", "argsort", "bmm", "ceil", "chunk", "clamp", "cos", "cosh", "cumprod",
|
|
249
|
+
"cumsum", "diag", "dot", "eq", "equal", "erf", "flip", "floor", "gather", "ge",
|
|
250
|
+
"gt", "isfinite", "isinf", "isnan", "le", "log10", "log2", "lt", "maximum",
|
|
251
|
+
"median", "minimum", "mm", "movedim", "multinomial", "narrow", "ne", "neg",
|
|
252
|
+
"norm", "outer", "pow", "prod", "reciprocal", "relu", "roll", "round", "rsqrt",
|
|
253
|
+
"sigmoid", "sign", "sin", "sinh", "softmax", "sort", "split", "square", "tan",
|
|
254
|
+
"tanh", "tile", "topk", "trace", "tril", "triu", "unbind", "unique",
|
|
255
|
+
# The maths group. Confirmed the same way — asked of torch, and only what
|
|
256
|
+
# it said was equal.
|
|
257
|
+
"acos", "acosh", "arccos", "arccosh", "arcsin", "arcsinh", "arctan", "arctanh",
|
|
258
|
+
"asin", "asinh", "atan", "atan2", "atanh", "absolute", "clip", "copysign",
|
|
259
|
+
"deg2rad", "erfc", "exp2", "expm1", "fix", "frac", "heaviside", "hypot", "ldexp",
|
|
260
|
+
"log1p", "logaddexp", "logaddexp2", "logit", "negative", "positive", "rad2deg",
|
|
261
|
+
"sgn", "signbit", "sinc", "trunc", "xlogy",
|
|
262
|
+
# The reduction group. torch exposes these sixteen as methods too.
|
|
263
|
+
"amax", "amin", "aminmax", "argwhere", "cummax", "cummin", "diff", "dist",
|
|
264
|
+
"kthvalue", "logsumexp", "msort", "nanmean", "nanquantile", "nansum", "nonzero",
|
|
265
|
+
"quantile",
|
|
266
|
+
# The shape group. Of these, `expand`, `repeat`, `ravel`, `select`, `unfold`
|
|
267
|
+
# and `expand_as` **have no module function in torch and exist as methods
|
|
268
|
+
# only** — places with a single calling convention.
|
|
269
|
+
"diagflat", "diagonal", "dsplit", "expand", "expand_as", "fliplr", "flipud",
|
|
270
|
+
"hsplit", "ravel", "repeat", "rot90", "select", "swapaxes", "swapdims",
|
|
271
|
+
"unflatten", "unfold", "vsplit",
|
|
272
|
+
# The three the sister library had as methods and this had as functions
|
|
273
|
+
# only. torch offers them as methods too.
|
|
274
|
+
"index_select", "masked_select", "repeat_interleave", "masked_fill",
|
|
275
|
+
# The writing side of indexing. torch offers all of them as methods as
|
|
276
|
+
# well — `x.scatter_(…)` is the form.
|
|
277
|
+
"scatter", "scatter_add", "index_add", "index_copy", "index_fill", "take",
|
|
278
|
+
"take_along_dim",
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
for _method in _AS_METHOD:
|
|
282
|
+
if not hasattr(Tensor, _method):
|
|
283
|
+
setattr(Tensor, _method, globals()[_method])
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _softmax_method(self, dim=None, dtype=None):
|
|
287
|
+
"""**The method is not the function.** `F.softmax` carries torch's private
|
|
288
|
+
`_stacklevel` third, and binding the function straight on as a method put that
|
|
289
|
+
into `Tensor.softmax` too — where torch's method has `(dim, dtype)` and nothing
|
|
290
|
+
between them. `x.softmax(1, torch.float32)` would then have set a stack level.
|
|
291
|
+
|
|
292
|
+
The seat exists on the function because a positional call reaches it there; it
|
|
293
|
+
does not exist on the method because torch's method has no such seat. **Sharing
|
|
294
|
+
an implementation is not the same as sharing a signature**, and the loop above
|
|
295
|
+
cannot tell the two apart — it binds whatever the module has.
|
|
296
|
+
"""
|
|
297
|
+
return softmax(self, dim=dim, dtype=dtype)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _log_softmax_method(self, dim=None, dtype=None):
|
|
301
|
+
"""See `_softmax_method`."""
|
|
302
|
+
return log_softmax(self, dim=dim, dtype=dtype)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _split_method(self, split_size, dim=0):
|
|
306
|
+
"""**torch's function and torch's method disagree, and refuse each other's
|
|
307
|
+
keyword.** `torch.split(t, split_size_or_sections=2)` is taken and
|
|
308
|
+
`torch.split(t, split_size=2)` is not; `t.split(split_size=2)` is taken and
|
|
309
|
+
`t.split(split_size_or_sections=2)` is not. Both measured.
|
|
310
|
+
|
|
311
|
+
So the method gets its own name for the same argument. This is the third pair in
|
|
312
|
+
a day where binding a module function straight on as a method carried the
|
|
313
|
+
function's signature into a place torch spells differently — `softmax` and
|
|
314
|
+
`log_softmax` above are the others.
|
|
315
|
+
"""
|
|
316
|
+
return split(self, split_size, dim)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
Tensor.softmax = _softmax_method
|
|
320
|
+
Tensor.log_softmax = _log_softmax_method
|
|
321
|
+
Tensor.split = _split_method
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _where_method(self, condition, other):
|
|
325
|
+
"""**The argument order differs from the function's.**
|
|
326
|
+
`x.where(condition, y)` is `torch.where(condition, x, y)`.
|
|
327
|
+
|
|
328
|
+
Attached blindly like the rest, `x` lands in the condition slot and the
|
|
329
|
+
answer is quietly wrong. It was found by asking torch; reading down the list
|
|
330
|
+
by eye would not have found it.
|
|
331
|
+
"""
|
|
332
|
+
return where(condition, self, other)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
Tensor.where = _where_method
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# ── the ones in `nn.functional` that **torch also keeps at top level** ──────
|
|
339
|
+
#
|
|
340
|
+
# torch mostly keeps the layer functions under `F.` alone, and puts some of them
|
|
341
|
+
# on `torch.` as well. Which is which is that side's history rather than a rule,
|
|
342
|
+
# so **it has to be asked** — `tests/torch_gap.py` produces the list.
|
|
343
|
+
#
|
|
344
|
+
# The things all existed already and only the names were missing. Without the
|
|
345
|
+
# name that code does not run.
|
|
346
|
+
for _name in ("conv_transpose1d", "conv_transpose2d", "conv_transpose3d",
|
|
347
|
+
"group_norm", "instance_norm", "rms_norm",
|
|
348
|
+
"celu", "selu", "prelu", "hardshrink", "threshold",
|
|
349
|
+
"avg_pool1d", "adaptive_avg_pool1d", "adaptive_max_pool1d"):
|
|
350
|
+
globals()[_name] = getattr(nn.functional, _name)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# ── the seven losses that are **not** the same function at top level ────────
|
|
354
|
+
#
|
|
355
|
+
# These were declined, and the reason read *the top-level one is the raw ATen op — its
|
|
356
|
+
# signature differs from F's.* Every word true, and none of it about what is missing:
|
|
357
|
+
# `F.kl_div` is here, so what was absent is a name and a set of defaults.
|
|
358
|
+
#
|
|
359
|
+
# **They really are different functions**, which is why they are not in the loop above:
|
|
360
|
+
#
|
|
361
|
+
# - the reduction is an **integer** — `0` none, `1` mean, `2` sum — where `F` takes the
|
|
362
|
+
# word. Passing `"mean"` to the ATen op is a `TypeError` in torch, so accepting the
|
|
363
|
+
# word here would be a wider door than the one being copied.
|
|
364
|
+
# - it **defaults to none**, where every `F` loss defaults to mean. A caller who reads
|
|
365
|
+
# `torch.kl_div(a, b)` as `F.kl_div(a, b)` gets a table where they expected a number,
|
|
366
|
+
# which is loud — and a caller who sums it afterwards gets a different number
|
|
367
|
+
# quietly, since `mean` divides.
|
|
368
|
+
# - `poisson_nll_loss` has **no defaults at all**: all six arguments are required.
|
|
369
|
+
# The others have theirs, and the two facts are measured rather than assumed.
|
|
370
|
+
#
|
|
371
|
+
# **The declared schema disagrees with the binding**, which is the part worth writing
|
|
372
|
+
# down: `aten::kl_div(..., int reduction=1)` says mean, and `torch.kl_div(a, b)` returns
|
|
373
|
+
# a table. The behaviour is the authority here, so `0` is what these take.
|
|
374
|
+
_REDUCTIONS = ("none", "mean", "sum")
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _aten_reduction(value):
|
|
378
|
+
"""The integer the ATen ops take, as the word `F` takes.
|
|
379
|
+
|
|
380
|
+
**Not a lookup with a default** — an out-of-range integer is an error rather than
|
|
381
|
+
the nearest legal one, because the three values are an enum and a fourth means the
|
|
382
|
+
caller believes something untrue about it.
|
|
383
|
+
"""
|
|
384
|
+
# **`int` in this module is torch's dtype**, not the builtin — the alias loop above
|
|
385
|
+
# binds `int`, `float` and `bool` as dtypes because torch has them under those
|
|
386
|
+
# names. So `isinstance(value, int)` here asks whether a number is a dtype and
|
|
387
|
+
# raises `TypeError: isinstance() arg 2 must be a type`. Measured, not guessed: it
|
|
388
|
+
# is the first thing this function did.
|
|
389
|
+
if (not isinstance(value, _builtins.int) or isinstance(value, _builtins.bool)
|
|
390
|
+
or not 0 <= value <= 2):
|
|
391
|
+
raise ValueError(_like_torch(
|
|
392
|
+
f"reduction has to be 0, 1 or 2, but got {value!r}.",
|
|
393
|
+
"reduction is expected to be an int in [0, 2]"))
|
|
394
|
+
return _REDUCTIONS[value]
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def binary_cross_entropy_with_logits(self, target, weight=None, pos_weight=None,
|
|
398
|
+
reduction=0):
|
|
399
|
+
"""`F.binary_cross_entropy_with_logits` with ATen's argument order and defaults."""
|
|
400
|
+
return nn.functional.binary_cross_entropy_with_logits(
|
|
401
|
+
self, target, weight=weight, pos_weight=pos_weight,
|
|
402
|
+
reduction=_aten_reduction(reduction))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def cosine_embedding_loss(input1, input2, target, margin=0.0, reduction=0):
|
|
406
|
+
"""As above. **`margin` defaults to 0 here and in `F`** — the two agree on that one
|
|
407
|
+
and disagree on the reduction, which is why neither can be assumed from the other."""
|
|
408
|
+
return nn.functional.cosine_embedding_loss(
|
|
409
|
+
input1, input2, target, margin=margin, reduction=_aten_reduction(reduction))
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def hinge_embedding_loss(self, target, margin=1.0, reduction=0):
|
|
413
|
+
return nn.functional.hinge_embedding_loss(
|
|
414
|
+
self, target, margin=margin, reduction=_aten_reduction(reduction))
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def kl_div(self, target, reduction=0, *, log_target=False):
|
|
418
|
+
"""**`log_target` is keyword-only**, as in the schema. `F.kl_div` takes it
|
|
419
|
+
positionally after two deprecated arguments, so a caller moving between the two has
|
|
420
|
+
to name it either way."""
|
|
421
|
+
return nn.functional.kl_div(self, target, reduction=_aten_reduction(reduction),
|
|
422
|
+
log_target=log_target)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def margin_ranking_loss(input1, input2, target, margin=0.0, reduction=0):
|
|
426
|
+
return nn.functional.margin_ranking_loss(
|
|
427
|
+
input1, input2, target, margin=margin, reduction=_aten_reduction(reduction))
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def poisson_nll_loss(input, target, log_input, full, eps, reduction):
|
|
431
|
+
"""**Six required arguments and no defaults.** `F.poisson_nll_loss` gives all four
|
|
432
|
+
of the trailing ones a value; this gives none, which is the schema and is what makes
|
|
433
|
+
it the odd one of the seven — a caller cannot reach it with two arguments at all."""
|
|
434
|
+
return nn.functional.poisson_nll_loss(
|
|
435
|
+
input, target, log_input=log_input, full=full, eps=eps,
|
|
436
|
+
reduction=_aten_reduction(reduction))
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def triplet_margin_loss(anchor, positive, negative, margin=1.0, p=2.0, eps=1e-6,
|
|
440
|
+
swap=False, reduction=0):
|
|
441
|
+
return nn.functional.triplet_margin_loss(
|
|
442
|
+
anchor, positive, negative, margin=margin, p=p, eps=eps, swap=swap,
|
|
443
|
+
reduction=_aten_reduction(reduction))
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
# ── methods exposed **as module functions too.** Exactly the opposite
|
|
447
|
+
# direction from `_AS_METHOD`. ──────────────────────────────────────────────
|
|
448
|
+
#
|
|
449
|
+
# torch offers nearly everything under two names — `x.sum()` and `torch.sum(x)`.
|
|
450
|
+
# Only one side existed here, so `torch.sum(x, dim=1)` stopped with an
|
|
451
|
+
# `AttributeError`. The golden did not catch it either — it turned up while
|
|
452
|
+
# writing cases, because the table held no case of that form at all.
|
|
453
|
+
#
|
|
454
|
+
# The list is not written by hand. The intersection of **what torch also offers
|
|
455
|
+
# as a module function** and what we have as a method is the answer, and the
|
|
456
|
+
# machine produces it. Written by hand, the next method added forgets this
|
|
457
|
+
# side.
|
|
458
|
+
# ── torch's **dtype aliases** go down first. They have to sit above the loop
|
|
459
|
+
# below. ────────────────────────────────────────────────────────────────────
|
|
460
|
+
#
|
|
461
|
+
# `torch.float`, `torch.double`, `torch.int` and `torch.bool` are dtypes rather
|
|
462
|
+
# than functions. And `float`, `double`, `int` and `bool` are also Tensor
|
|
463
|
+
# methods, so the loop below was filling these names with **functions built from
|
|
464
|
+
# the methods.** That made the textbook-common `zeros(2, dtype=torch.float)` stop
|
|
465
|
+
# with `'function' object has no attribute 'np'` — the dtype it points at was
|
|
466
|
+
# perfectly present and only the name was covered over.
|
|
467
|
+
#
|
|
468
|
+
# The loop skips on `_name in globals()`, so **putting them down here is the
|
|
469
|
+
# fix.** The method side (`x.float()`) is untouched — these names go into the
|
|
470
|
+
# module slot only.
|
|
471
|
+
#
|
|
472
|
+
# `int` alone is not an alias. **`torch.int` is int32 and there is no such
|
|
473
|
+
# storage here** — the name is kept and using it stops (`int32` is an
|
|
474
|
+
# `_AbsentDtype`).
|
|
475
|
+
float = float32
|
|
476
|
+
double = float64
|
|
477
|
+
bool = bool_
|
|
478
|
+
# The four below have no dtype to point at — names only, and using one stops.
|
|
479
|
+
int = int32
|
|
480
|
+
half = float16
|
|
481
|
+
short = int16
|
|
482
|
+
chalf = complex32
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def _as_function(name):
|
|
486
|
+
"""Wrap a method as a function taking it as the first argument."""
|
|
487
|
+
def call(t, *args, **kwargs):
|
|
488
|
+
return getattr(_wrap_tensor(t), name)(*args, **kwargs)
|
|
489
|
+
call.__name__ = name
|
|
490
|
+
call.__doc__ = f"The same as `x.{name}(...)`. torch offers both."
|
|
491
|
+
return call
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _wrap_tensor(t):
|
|
495
|
+
return t if isinstance(t, Tensor) else tensor(t)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
# **A name torch exposes as a different kind of thing is not taken.**
|
|
499
|
+
#
|
|
500
|
+
# This loop puts method names straight into the module slot, and there are
|
|
501
|
+
# places where that name is not a function in torch. Then **a function sits**
|
|
502
|
+
# on our side, and somebody using the name for its real purpose sees an error one
|
|
503
|
+
# step displaced — `dtype=torch.float` stopping with
|
|
504
|
+
# `'function' object has no attribute 'np'` was that shape.
|
|
505
|
+
#
|
|
506
|
+
# The eight dtypes (`float`, `bool`, `half`, …) are blocked **by being put down
|
|
507
|
+
# above**, and the five below are what is left. Removed by hand three times, so
|
|
508
|
+
# this time it is written as a rule.
|
|
509
|
+
#
|
|
510
|
+
# **This table is written without looking at torch** — the core does not lean on
|
|
511
|
+
# torch. Instead `tests/test_module_names.py` holds torch and checks that this
|
|
512
|
+
# table is neither stale nor short.
|
|
513
|
+
_NOT_OURS = {
|
|
514
|
+
"cpu": "a namespace in torch — there is one device to choose, so we have none",
|
|
515
|
+
"storage": "a namespace in torch — there is nowhere here to look into a storage layer",
|
|
516
|
+
"mtia": "a namespace in torch — a different accelerator",
|
|
517
|
+
"xpu": "a namespace in torch — a different accelerator",
|
|
518
|
+
"qscheme": "a class in torch — a quantisation scheme, and that dtype is absent",
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
for _name in dir(Tensor):
|
|
522
|
+
if _name.startswith("_") or _name in globals() or _name in _NOT_OURS:
|
|
523
|
+
continue
|
|
524
|
+
if callable(getattr(Tensor, _name, None)):
|
|
525
|
+
globals()[_name] = _as_function(_name)
|
|
526
|
+
|
|
527
|
+
# **This has to be near the end of the file.** The loop above puts `sum`, `min`,
|
|
528
|
+
# `max`, `all` and `any` into module scope, and those are Python builtins too.
|
|
529
|
+
# Code below this calling the builtins quietly calls something else — the binding
|
|
530
|
+
# went through this once with `bool`.
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# ── the methods that forward to a module function ──────────────────────────
|
|
534
|
+
#
|
|
535
|
+
# `_tensor.py` binds a few dozen names as `def method(self, *args, **kw)` that call
|
|
536
|
+
# `_ops.<name>`, because writing each list out twice is how the two drift. The cost is
|
|
537
|
+
# that **`inspect.signature` sees the wrapper**, so every check that reads a signature
|
|
538
|
+
# goes blind at those names: fifteen of the `variadic` rows on the core-to-torch axis
|
|
539
|
+
# were this, and `variadic` means *cannot be compared at all.*
|
|
540
|
+
#
|
|
541
|
+
# `__wrapped__` is what `inspect` follows, and it is set here rather than in
|
|
542
|
+
# `_tensor.py` because the module function does not exist yet when the binding runs —
|
|
543
|
+
# `_ops` imports `_tensor`, not the other way round.
|
|
544
|
+
#
|
|
545
|
+
# The same omission in `_accepts_out` below cost two names on the `dim` sweep, and
|
|
546
|
+
# widening it there turned up two silent wrong answers. This is the same repair on a
|
|
547
|
+
# larger set.
|
|
548
|
+
def _link_wrapped():
|
|
549
|
+
from . import _ops as _o
|
|
550
|
+
from ._tensor import Tensor as _T
|
|
551
|
+
|
|
552
|
+
for _n in dir(_T):
|
|
553
|
+
_m = getattr(_T, _n, None)
|
|
554
|
+
if getattr(_m, "__wrapped__", None) is not None:
|
|
555
|
+
continue
|
|
556
|
+
if getattr(_m, "_forwards_nothing", False):
|
|
557
|
+
# A tombstone — the name exists because torch keeps it and raises.
|
|
558
|
+
# Linking it to the live `_ops` function of the same name lends it an
|
|
559
|
+
# argument list it does not have; see `_deprecated_by_torch`.
|
|
560
|
+
continue
|
|
561
|
+
_target = getattr(_o, _n, None)
|
|
562
|
+
if callable(_m) and callable(_target) and "<locals>" in getattr(
|
|
563
|
+
_m, "__qualname__", "") and getattr(_m, "__name__", None) == _n:
|
|
564
|
+
try:
|
|
565
|
+
_m.__wrapped__ = _target
|
|
566
|
+
except (AttributeError, TypeError):
|
|
567
|
+
pass
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
_link_wrapped()
|
|
571
|
+
del _link_wrapped
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
# ── `out=` — writing into a tensor made in advance ──────────────────────────
|
|
575
|
+
#
|
|
576
|
+
# For these names torch writes into the tensor it was handed rather than making
|
|
577
|
+
# a new result. **The saving does not happen here** — it computes and then moves,
|
|
578
|
+
# so the allocation occurs anyway. The two observable things are real, though:
|
|
579
|
+
# the destination changes, and what comes back is **the same object.** Code leans
|
|
580
|
+
# on those two, so this keeps a fact rather than an imitation.
|
|
581
|
+
#
|
|
582
|
+
# **The list was built by measuring.** Going by the `out=None` in the docstrings
|
|
583
|
+
# it is wider — `rand_like`, `zeros_like`, `median` and `where` are written there
|
|
584
|
+
# and the actual overload does not accept it. So the split came from **actually
|
|
585
|
+
# calling torch**, and `tests/test_out_names.py` measures that split again. The
|
|
586
|
+
# core does not lean on torch, so the table lives here and the comparison lives
|
|
587
|
+
# there.
|
|
588
|
+
#
|
|
589
|
+
# **47 names arrived at once, and their absence was the check's own doing.** That
|
|
590
|
+
# file rejected the docstrings for deciding *whether* a name takes `out=` — and then
|
|
591
|
+
# enumerated its candidates from `"out=None" in fn.__doc__`, which is the same source
|
|
592
|
+
# doing the other half of the job. `abs`, `acos`, `asin`, `atan`, `log2`, `log10`,
|
|
593
|
+
# `square`, `norm`, `nansum`, `msort`, `diff` and the rest of the inverse-trigonometric
|
|
594
|
+
# family take `out=` in torch and are documented with a bare `out`, so they were never
|
|
595
|
+
# asked about. The check passed on exactly the set the table already held.
|
|
596
|
+
_TAKES_OUT = frozenset("""
|
|
597
|
+
add addbmm addcdiv addcmul addmm addmv addr all amax amin any arange
|
|
598
|
+
baddbmm bitwise_and bitwise_left_shift bitwise_not bitwise_right_shift
|
|
599
|
+
bitwise_xor bmm bucketize cat ceil cholesky cholesky_inverse
|
|
600
|
+
cholesky_solve clamp clip column_stack complex concat concatenate
|
|
601
|
+
conj_physical copysign cos cosh cross cumprod cumsum deg2rad diag
|
|
602
|
+
digamma div divide dot dstack empty eq erf erfc erfinv exp exp2 expm1
|
|
603
|
+
eye fix float_power floor floor_divide fmax fmin fmod frac full gather
|
|
604
|
+
gcd ge ger greater greater_equal gt heaviside histc hstack hypot i0
|
|
605
|
+
igamma igammac index_select inner inverse isneginf isposinf kron lcm
|
|
606
|
+
ldexp le lerp less less_equal lgamma linspace log log1p logaddexp
|
|
607
|
+
logaddexp2 logcumsumexp logical_and logical_not logical_or logit
|
|
608
|
+
logspace logsumexp lt lu_solve masked_select matmul matrix_power max
|
|
609
|
+
maximum mean min minimum mm mul multinomial multiply mv mvlgamma
|
|
610
|
+
nan_to_num nanmean nanquantile ne neg negative nextafter nonzero normal
|
|
611
|
+
not_equal ones ormqr outer polar polygamma pow quantile rand randint
|
|
612
|
+
randn randperm range reciprocal remainder renorm round row_stack rsqrt
|
|
613
|
+
searchsorted sgn sigmoid sign signbit sin sinc sinh sqrt stack std sub
|
|
614
|
+
subtract take_along_dim tan tanh tril triu trunc var vdot vstack xlogy
|
|
615
|
+
zeros
|
|
616
|
+
abs absolute acos acosh angle arccos arccosh arcsin arcsinh
|
|
617
|
+
arctan arctan2 arctanh argmax argmin asin asinh atan atan2
|
|
618
|
+
atanh bernoulli bitwise_or chain_matmul clamp_max clamp_min diff hardshrink hash_tensor
|
|
619
|
+
isin log10 log2 log_softmax logical_xor msort nansum norm orgqr
|
|
620
|
+
rad2deg slice_scatter softmax square take tensordot threshold true_divide
|
|
621
|
+
|
|
622
|
+
""".split())
|
|
623
|
+
|
|
624
|
+
# The ones taking **several**, as in `out=(values, indices)`. The same rule with
|
|
625
|
+
# more than one slot.
|
|
626
|
+
_TAKES_OUT_TUPLE = frozenset("""
|
|
627
|
+
aminmax cummax cummin frexp geqrf histogram kthvalue mode sort svd topk
|
|
628
|
+
triangular_solve
|
|
629
|
+
lu qr slogdet
|
|
630
|
+
""".split())
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _accepts_out(fn, name):
|
|
634
|
+
"""Take `out=` and hand it to `_out`. **Not written per function** — fixing
|
|
635
|
+
a hundred and seventy-two by hand leaves one of them out, and the one left
|
|
636
|
+
out swallows quietly."""
|
|
637
|
+
def call(*args, **kwargs):
|
|
638
|
+
out = kwargs.pop("out", None)
|
|
639
|
+
return _out(fn(*args, **kwargs), out, name)
|
|
640
|
+
call.__name__ = getattr(fn, "__name__", name)
|
|
641
|
+
call.__doc__ = getattr(fn, "__doc__", None)
|
|
642
|
+
# **The wrapped signature has to survive**, or every check that reads one goes
|
|
643
|
+
# blind at these names. `inspect.signature` follows `__wrapped__`, so setting it
|
|
644
|
+
# is what makes `narrow(t, dim, start, length)` still read as that rather than as
|
|
645
|
+
# `(*args, **kwargs)`.
|
|
646
|
+
#
|
|
647
|
+
# It was missing, and adding 50 names to the table above is what surfaced it:
|
|
648
|
+
# `slice_scatter` and `take` dropped out of `tests/test_axis_sweep.py` the moment
|
|
649
|
+
# they were wrapped, and the two guards there — *a call entry naming a function
|
|
650
|
+
# nobody sweeps* and *a function taking an index that is in neither table* — both
|
|
651
|
+
# fired. Without those two the axes would have quietly stopped asking about
|
|
652
|
+
# whatever the table grew to cover, which is the one direction a coverage check
|
|
653
|
+
# cannot report on itself.
|
|
654
|
+
call.__wrapped__ = fn
|
|
655
|
+
# **And `__wrapped__` alone under-reports: it says the list without `out`.**
|
|
656
|
+
# That is what the wrapped function takes, not what this one takes, and the
|
|
657
|
+
# difference is exactly the argument this wrapper exists to add. Four rows on
|
|
658
|
+
# the core-against-torch axis sat frozen under *"torch declares an `out=` and
|
|
659
|
+
# this does not"* — read three times and true each time, because the sentence
|
|
660
|
+
# is about the **declaration** and nobody re-asked once `out=` began working.
|
|
661
|
+
#
|
|
662
|
+
# `linalg` is the only namespace that axis compares where the gap shows; the
|
|
663
|
+
# same understatement covers all 229 top-level names in the table above and is
|
|
664
|
+
# simply not looked at there. So it is repaired where it lives.
|
|
665
|
+
#
|
|
666
|
+
# (The docstring says a hundred and seventy-two, which was the count the day it
|
|
667
|
+
# was written. Left alone — a past number changed to the current one stops being
|
|
668
|
+
# a record of anything.)
|
|
669
|
+
#
|
|
670
|
+
# `__signature__` wins over `__wrapped__` in `inspect`, and it is **built from**
|
|
671
|
+
# the wrapped list rather than replacing it — `narrow(input, dim, start, length,
|
|
672
|
+
# *, out=None)`, the whole thing, not `(*args, **kwargs)`.
|
|
673
|
+
try:
|
|
674
|
+
_sig = _inspect.signature(fn)
|
|
675
|
+
except (TypeError, ValueError):
|
|
676
|
+
return call
|
|
677
|
+
# **No builtins here.** This module binds `any`, `all`, `type`, `range`, `sum`,
|
|
678
|
+
# `min`, `max` and `abs` as torch functions, so inside it those names are the
|
|
679
|
+
# tensor operations. `any(p.name == "out" for p in _params)` reached
|
|
680
|
+
# `borch.any`, which tried to make a tensor out of a generator — a `TypeError`
|
|
681
|
+
# about `float()` that names neither `any` nor this function. `type` had cost
|
|
682
|
+
# the same half hour one screen down, and the loop below is what both fixes
|
|
683
|
+
# look like.
|
|
684
|
+
_params = list(_sig.parameters.values())
|
|
685
|
+
_at = len(_params)
|
|
686
|
+
for _i, _param in enumerate(_params):
|
|
687
|
+
if _param.name == "out":
|
|
688
|
+
return call
|
|
689
|
+
if _param.kind is _inspect.Parameter.VAR_KEYWORD and _at == len(_params):
|
|
690
|
+
_at = _i
|
|
691
|
+
_params.insert(_at, _inspect.Parameter(
|
|
692
|
+
"out", _inspect.Parameter.KEYWORD_ONLY, default=None))
|
|
693
|
+
call.__signature__ = _sig.replace(parameters=_params)
|
|
694
|
+
return call
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
for _name in _TAKES_OUT | _TAKES_OUT_TUPLE:
|
|
698
|
+
_fn = globals().get(_name)
|
|
699
|
+
if _fn is not None:
|
|
700
|
+
globals()[_name] = _accepts_out(_fn, _name)
|
|
701
|
+
del _name
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
# **The same wrapping, reaching `linalg`, which it did not.**
|
|
705
|
+
#
|
|
706
|
+
# The loop above walks this module's `globals()`. `linalg` is a namespace object whose
|
|
707
|
+
# members are bound from `_ops` directly, so none of them passed through it —
|
|
708
|
+
# `borch.qr(x, out=…)` was taken and `borch.linalg.qr(x, out=…)` was a `TypeError`,
|
|
709
|
+
# for the same function.
|
|
710
|
+
#
|
|
711
|
+
# **It was described and never decided.** Four frozen rows on the core↔torch axis
|
|
712
|
+
# ended with *"torch declares an `out=` and this does not"*, which is a true sentence
|
|
713
|
+
# that says nothing about whether the absence was chosen. Read three times and passed
|
|
714
|
+
# over three times, because a description has no retirement condition — it cannot go
|
|
715
|
+
# stale, so it never asks to be re-read. The peer session holding borch.ts named that
|
|
716
|
+
# shape today after `optim`'s seven sat under one for a day.
|
|
717
|
+
#
|
|
718
|
+
# Followed through, the sentence was **half true**: the machinery exists and simply
|
|
719
|
+
# did not reach here. What it buys is torch's *observable* `out=` — the destination
|
|
720
|
+
# changes and the same object comes back — and not the allocation it exists to save,
|
|
721
|
+
# which `_out`'s docstring has said all along.
|
|
722
|
+
#
|
|
723
|
+
# **The list is written down and not read off torch.** The first version walked
|
|
724
|
+
# `torch.linalg.__doc__` at import time, which makes the library's own surface depend
|
|
725
|
+
# on whether real torch happens to be installed: `out=` accepted on a development
|
|
726
|
+
# machine and refused in the browser, where this module *is* torch. Two libraries
|
|
727
|
+
# under one name, decided by the environment — the exact silent divergence this
|
|
728
|
+
# repository exists to hunt, and it was one edit from being shipped.
|
|
729
|
+
#
|
|
730
|
+
# So it is a table, and `tests/test_out_names.py` holds it against torch. That is the
|
|
731
|
+
# same shape as `_TAKES_OUT` above and the reason it is a table too.
|
|
732
|
+
#
|
|
733
|
+
# **The first draft of this list came from the docstrings**, and the file that now
|
|
734
|
+
# holds it opens by rejecting the docstrings for deciding exactly this — with the
|
|
735
|
+
# receipt: twenty-four names lost that way once already. Re-measured by calling, the
|
|
736
|
+
# thirty-seven were right and `lstsq` was missing, which is the direction that costs
|
|
737
|
+
# a reader: torch takes `out=` there and this refused it.
|
|
738
|
+
_LINALG_TAKES_OUT = frozenset("""
|
|
739
|
+
cholesky cholesky_ex cond cross det eig eigh eigvals eigvalsh
|
|
740
|
+
householder_product inv inv_ex ldl_factor ldl_factor_ex ldl_solve lstsq lu
|
|
741
|
+
lu_factor lu_factor_ex lu_solve matmul matrix_norm matrix_power matrix_rank
|
|
742
|
+
multi_dot norm pinv qr slogdet solve solve_ex solve_triangular svd svdvals
|
|
743
|
+
tensorinv tensorsolve vecdot vector_norm
|
|
744
|
+
""".split())
|
|
745
|
+
|
|
746
|
+
for _name in _LINALG_TAKES_OUT:
|
|
747
|
+
# **No `isinstance(_fn, type)` guard here, and the first version had one.**
|
|
748
|
+
# `type` is not the builtin in this module — `torch.type` is a real name and this
|
|
749
|
+
# package exports it — so the guard raised `arg 2 must be a type`. Every entry
|
|
750
|
+
# above is a function and none is a class, so the guard was doing nothing but
|
|
751
|
+
# being wrong. A name meaning something else, in the file that spent the day on
|
|
752
|
+
# exactly that.
|
|
753
|
+
_fn = getattr(linalg, _name, None)
|
|
754
|
+
if _fn is not None and callable(_fn):
|
|
755
|
+
setattr(linalg, _name, _accepts_out(_fn, f"linalg.{_name}"))
|
|
756
|
+
del _name
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
# ================================================================ install
|
|
760
|
+
|
|
761
|
+
def install(name="torch", modules=None):
|
|
762
|
+
"""Plant the submodule paths so that `import torch` picks up this subset.
|
|
763
|
+
|
|
764
|
+
Writing the paths by hand drifts — and it did. The runner, the checker and
|
|
765
|
+
the tests each held their own list and all three left out
|
|
766
|
+
`torch.optim.lr_scheduler`, so the thing existed and
|
|
767
|
+
`from torch.optim.lr_scheduler import StepLR` stopped in the body of a
|
|
768
|
+
textbook. So there is no list; it is built by walking `_Namespace`.
|
|
769
|
+
|
|
770
|
+
The root (`sys.modules["torch"]`) is planted by the caller — that side is
|
|
771
|
+
what holds the module object.
|
|
772
|
+
"""
|
|
773
|
+
import sys
|
|
774
|
+
|
|
775
|
+
modules = sys.modules if modules is None else modules
|
|
776
|
+
registered = []
|
|
777
|
+
|
|
778
|
+
def walk(namespace, prefix):
|
|
779
|
+
for key in sorted(dir(namespace)):
|
|
780
|
+
if key.startswith("_"):
|
|
781
|
+
continue
|
|
782
|
+
value = getattr(namespace, key)
|
|
783
|
+
if isinstance(value, _Namespace):
|
|
784
|
+
path = prefix + "." + key
|
|
785
|
+
modules[path] = value
|
|
786
|
+
registered.append(path)
|
|
787
|
+
walk(value, path)
|
|
788
|
+
|
|
789
|
+
walk_root = [(key, value) for key, value in sorted(globals().items())
|
|
790
|
+
if not key.startswith("_") and isinstance(value, _Namespace)]
|
|
791
|
+
for key, value in walk_root:
|
|
792
|
+
path = name + "." + key
|
|
793
|
+
modules[path] = value
|
|
794
|
+
registered.append(path)
|
|
795
|
+
walk(value, path)
|
|
796
|
+
return registered
|