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 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