ulpwise 0.3.2__tar.gz → 0.3.4__tar.gz
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.
- {ulpwise-0.3.2 → ulpwise-0.3.4}/CHANGELOG.md +73 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/Cargo.lock +1 -1
- {ulpwise-0.3.2 → ulpwise-0.3.4}/Cargo.toml +1 -1
- {ulpwise-0.3.2 → ulpwise-0.3.4}/PKG-INFO +35 -6
- {ulpwise-0.3.2 → ulpwise-0.3.4}/README.md +34 -5
- {ulpwise-0.3.2 → ulpwise-0.3.4}/pyproject.toml +1 -1
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/__init__.py +1 -1
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/corpus/cases.json +83 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/scan.py +410 -9
- {ulpwise-0.3.2 → ulpwise-0.3.4}/tests/test_scan.py +213 -4
- {ulpwise-0.3.2 → ulpwise-0.3.4}/.github/dependabot.yml +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/.github/workflows/ci.yml +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/.github/workflows/release.yml +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/.gitignore +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/CONTRIBUTING.md +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/LICENSE-APACHE +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/LICENSE-MIT +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/examples/sqrt_knife_values.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/examples/torch_sqrt_conformance.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/__main__.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/corpus/__init__.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/plugin.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/python/ulpwise/survey.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/src/edge.rs +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/src/exact.rs +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/src/knife.rs +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/src/lib.rs +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/src/py.rs +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/src/ulp.rs +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/studies/accuracy-survey-2026-09/README.md +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/studies/accuracy-survey-2026-09/results.csv +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/studies/accuracy-survey-2026-09/results.md +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/studies/accuracy-survey-2026-10-half/README.md +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/studies/accuracy-survey-2026-10-half/results.csv +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/studies/accuracy-survey-2026-10-half/results.md +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/tests/test_core.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/tests/test_corpus.py +0 -0
- {ulpwise-0.3.2 → ulpwise-0.3.4}/tests/test_survey.py +0 -0
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4 (2026-10-09)
|
|
4
|
+
|
|
5
|
+
- Regression corpus case for pytorch/rl #4504, merged upstream: `MultiCategorical.to_one_hot` iterated over `self.nvec`,
|
|
6
|
+
a 2-D tensor for a spec with a batch shape, so `one_hot` got a tensor as `num_classes` and raised `TypeError`, and the
|
|
7
|
+
0-D sample of a shape `[1]` spec raised `IndexError` on `val[..., i]`. The case one-hot encodes a sample of
|
|
8
|
+
`MultiCategorical([3, 2], shape=(4, 2))` and the scalar sample of `MultiCategorical([5])`.
|
|
9
|
+
- `ulpwise scan`: new `dropout-never-applied` rule (medium), the first that reads a whole file rather than one
|
|
10
|
+
function: a `Dropout`, `DropPath` or a `ModuleDict` or `ModuleList` under a dropout name is assigned to `self`
|
|
11
|
+
and then never called, never passed on, never returned or iterated and its rate never read, anywhere in the file.
|
|
12
|
+
The option is accepted and does nothing, so the model trains without the regularisation it reports. peft #3830 is
|
|
13
|
+
the example: `OFTLayer` built `module_dropout` into `self.oft_dropout`, filled it in `update_layer` and never
|
|
14
|
+
called it. Reading the rate counts as use (`scaled_dot_product_attention(dropout_p=self.dropout.p)`), as does
|
|
15
|
+
passing the module to another one; filling it does not, and subclasses of `Sequential`, which run every
|
|
16
|
+
attribute, are not read. On kornia, ultralytics, torchvision, diffusers, torchrl, vllm and detectron2 main the
|
|
17
|
+
rule reports nothing; on peft main it reports the #3830 line, on timm main one `attn_drop` in `coat.py` that a
|
|
18
|
+
comment already calls unused, and on transformers main nine lines, four of them modular files whose `forward`
|
|
19
|
+
lives in another file.
|
|
20
|
+
|
|
21
|
+
## 0.3.3 (2026-10-08)
|
|
22
|
+
|
|
23
|
+
- Two regression corpus cases for peft, both open upstream with a fix PR: peft #3769, `add_weighted_adapter` with
|
|
24
|
+
`combination_type='svd'` crashed for Conv1d and Conv3d LoRA layers because only Conv2d deltas were flattened before
|
|
25
|
+
the SVD (the case combines two rank 2 adapters at `svd_rank=4` and expects the output of both adapters active, which
|
|
26
|
+
the fix matches to 2e-6), and peft #3830, OFT `module_dropout` was never applied, so ten training forwards of the
|
|
27
|
+
same input were identical where the fix spreads them by 0.56.
|
|
28
|
+
- `ulpwise scan`: new `where-nan-gradient` rule (medium) for `where(d > eps, f(d), other)` with `f` a division by `d`
|
|
29
|
+
or a `sqrt`, `log`, `acos` or `asin` of it. `where` evaluates both branches and hands the discarded one a zero
|
|
30
|
+
gradient, and the backward of `f` at the singularity turns that zero into `0 / 0 = nan`, so the guard protects the
|
|
31
|
+
value and not the gradient. kornia #5579, found by the kornia conventions audit, is the example: the mutual
|
|
32
|
+
information losses divided by the range of the signal inside `where(diff > eps, ...)` and a constant input or
|
|
33
|
+
target got a nan gradient. The condition may be a comparison, a name bound to one, or `~`, `&`, `|` and a subscript
|
|
34
|
+
of those, and the method form `a.where(cond, b)` counts. Quiet for `numpy.where`, which has no autograd, for a
|
|
35
|
+
comparison against a number above 1 (`strength < 50` in kornia's JPEG scale is a piecewise definition), for a
|
|
36
|
+
floor named `eps`, `tol`, `floor`, `thresh`, `tiny`, `bound` or `limit` on the other side of the comparison, and
|
|
37
|
+
once `d` is re-bound to a `where`, `clamp` or `maximum` of itself, which is the recommended fix and the shape of
|
|
38
|
+
kornia's `fit_line` and `rotation_matrix_to_quaternion`. On kornia main d15741e2 the rule reports four lines, on
|
|
39
|
+
ultralytics main 8df3534 two, both in metric code, and nothing in torchvision.
|
|
40
|
+
- `ulpwise scan`: new `clamp-at-singularity` rule (medium) for `clamp(x, min=0).sqrt()`, `sqrt(clamp(x, min=0))`,
|
|
41
|
+
`clamp(c, -1, 1).acos()` and the `clip`, `clamp_min`, `clamp_max`, `asin` and `log` forms: the bound is the point
|
|
42
|
+
where the next function has an infinite derivative, and clamp's derivative at its own bound is 1 on torch 2.5.1 and
|
|
43
|
+
2.9.1 and 0 on 2.14, so the gradient there is `inf` or `nan` on the older half of a supported range. kornia #4229,
|
|
44
|
+
found by the kornia conventions audit, is the example, `_cdist`'s `clamp(min=0.0).sqrt()` with identical float16
|
|
45
|
+
descriptors, and kornia #5500's `clamp(-1, 1).acos()` the second. A bound strictly inside the domain, `min=1e-8`,
|
|
46
|
+
is a floor and is not reported, a bound given by a name is not read, and numpy is quiet. On kornia main d15741e2
|
|
47
|
+
the rule reports the three lines of `_solve_cubic_real`, on ultralytics main 8df3534 one line of `DepthMetrics`.
|
|
48
|
+
- `ulpwise scan`: a finding inside a nested function is reported once, under the nested function, instead of once
|
|
49
|
+
more under the enclosing one, and the enclosing function's call and division counts no longer include it.
|
|
50
|
+
- `ulpwise scan`: new `eps-floor` rule (medium) for `x * (1 - eps) + eps`, in either order and with `self.eps` too: the
|
|
51
|
+
floor moves every value, 0 becomes `eps` and 1 stays 1, so the entries of a one-hot sum to `1 + (C - 1) eps` and a
|
|
52
|
+
perfect prediction scores a loss that grows with the image. kornia #5538, found by the kornia conventions audit, is
|
|
53
|
+
the example: `one_hot` floored its zeros with 1e-6 and a perfect prediction of a 256 × 384 image with a one-pixel
|
|
54
|
+
class scored a macro Dice loss of 0.0234. On kornia main 9e188e08 the rule reports that line and nothing else, and
|
|
55
|
+
nothing in ultralytics or torchvision; `(p + eps) / (q + eps)` is not a floor and stays quiet.
|
|
56
|
+
- `ulpwise scan`: the `hypot-by-hand` advice no longer offers `norm` as the replacement. `torch.linalg.vector_norm` and
|
|
57
|
+
`numpy.linalg.norm` square the entries first too, so in float32 both return `inf` for a vector with two entries of
|
|
58
|
+
1.8e19 while `hypot` returns 2.5e19; the advice now says to divide a vector by its largest absolute entry when the
|
|
59
|
+
point is a direction. The kornia maintainer caught the case in review of kornia #5505, where `v / v.norm()` turned
|
|
60
|
+
such vectors into zeros and the angle between them read 0.
|
|
61
|
+
- `ulpwise scan`: `acos-for-angle` moves from `info` to `medium` and names the mechanism: next to 1 the cosine has no
|
|
62
|
+
digits left for a small angle, so `acos` recovers the angle with an absolute error of `sqrt(eps)`, 0.02 degrees in
|
|
63
|
+
float32, whatever the angle. The kornia scan listed `kornia/metrics/pose.py` under this rule, and both functions
|
|
64
|
+
there, `angle_error_mat` and `angle_error_vec`, return exactly 0 for every rotation below 0.03 degrees in float32
|
|
65
|
+
and 180.0 for 179.99 degrees (kornia #5500); the rule cites it.
|
|
66
|
+
- Regression corpus case for kornia #5500: a 0.01 degree rotation scores 0.0 in both metrics on kornia main 050ac77f.
|
|
67
|
+
- The kornia #5500 corpus case records its fix, kornia #5505, merged 2026-10-06: `angle_error_mat` reads the sine from
|
|
68
|
+
the skew part of the relative rotation and `angle_error_vec` from the cross product of the scaled vectors, and both
|
|
69
|
+
return `atan2(sin, cos)`, so a 0.01 degree rotation scores 0.0099990 in float32. The case stays an expected failure
|
|
70
|
+
until a kornia release carries the fix.
|
|
71
|
+
- `ulpwise scan`: `logsumexp-by-hand` no longer fires on the stable form, where every `exp` argument has its
|
|
72
|
+
maximum subtracted first (`log(exp(x - x.max()).sum())`, or `s = x - m` with `m` bound to a `max`, `amax`,
|
|
73
|
+
`maximum` or `torch.max(...).values`). The lm-evaluation-harness scan listed the numerically stable `_log_softmax`
|
|
74
|
+
of `lm_eval/models/_onnx_base.py` next to a real `log(exp(a) + exp(b))`; it now reports only the real one.
|
|
75
|
+
|
|
3
76
|
## 0.3.2 (2026-10-06)
|
|
4
77
|
|
|
5
78
|
- Two regression corpus cases for pytorch #199850: `torch.erf` in bfloat16 and float16 on CPU returns 0 at and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ulpwise
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Framework :: Pytest
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -185,6 +185,7 @@ PYTHONPATH=/tmp/kornia-0.8.3 ulpwise corpus --repo kornia # 31 present, 0 fi
|
|
|
185
185
|
| pytorch/rl #4443 `arange(0, 1, 1/n)` gives `n + 1` positions for 140 values of `n` below 2000 | rounding | 2026-09-20 |
|
|
186
186
|
| pytorch/rl #4444 `min_value or -inf` drops `min_value=0` | falsy zero | 2026-09-20 |
|
|
187
187
|
| pytorch/rl #4445 scheduler `state_dict()` contained a module object | crash | 2026-09-20 |
|
|
188
|
+
| pytorch/rl #4504 `MultiCategorical.to_one_hot` crashed for a batched spec and a 0-D sample | crash | 2026-10-07 |
|
|
188
189
|
| timm #2786 Mars kept a reference to `p.grad` as the previous gradient | aliasing | 2026-09-17 |
|
|
189
190
|
| timm #2790 AdafactorBigVision clipped updates in the wrong direction | direction | 2026-09-18 |
|
|
190
191
|
| timm #2791 AdaMuon conv LR scale computed from the wrong dims | scale | 2026-09-18 |
|
|
@@ -228,6 +229,9 @@ PYTHONPATH=/tmp/kornia-0.8.3 ulpwise corpus --repo kornia # 31 present, 0 fi
|
|
|
228
229
|
| pytorch #198664 `erfcx` off by `x*x/2` ulps for negative `x` (`exp` at the rounded square) | rounding | open |
|
|
229
230
|
| pytorch #199850 `torch.erf` in bfloat16 and float16 on CPU returns 0 at and below 1.8e-7 and loses relative accuracy below 1e-3 (13404 bfloat16 ulps, 5 float16 ulps) | cancellation | open |
|
|
230
231
|
| pytorch #199867 `torch.special.logit` in float16 and bfloat16 on CPU rounds `1 - x` and `x / (1 - x)` to the input dtype before the log, logit(0.499756) is -0.000488 for -0.000977 (512 float16 ulps, 64 bfloat16 ulps) | rounding | open |
|
|
232
|
+
| kornia #5505 `angle_error_mat` and `angle_error_vec` returned exactly 0 for every rotation below 0.03 degrees in float32 (`acos` next to 1, issue #5500) | cancellation | 2026-10-06 |
|
|
233
|
+
| peft #3769 `add_weighted_adapter` with `combination_type='svd'` crashed for Conv1d and Conv3d LoRA layers | crash | open |
|
|
234
|
+
| peft #3830 OFT `module_dropout` was never applied, every training forward used all rotation blocks | no-op | open |
|
|
231
235
|
|
|
232
236
|
Cases marked `open` have an issue with the complete patch attached and no merged fix yet. Every case
|
|
233
237
|
is an expected failure until the installed release contains the fix (`fixed_in_release` in
|
|
@@ -246,22 +250,27 @@ ulpwise scan path/to/repo --rules one-minus-cos,small-angle-division --top 40
|
|
|
246
250
|
```
|
|
247
251
|
|
|
248
252
|
The scan is static and needs nothing installed: it parses each file with `ast`, walks every
|
|
249
|
-
function and matches
|
|
250
|
-
overflows
|
|
253
|
+
function and matches sixteen patterns, each with a severity, the reason it loses digits,
|
|
254
|
+
overflows or loses its gradient, or in one case does nothing at all, the usual replacement and,
|
|
255
|
+
where one exists, the upstream bug it comes from.
|
|
251
256
|
|
|
252
257
|
| rule | severity | pattern |
|
|
253
258
|
|---|---|---|
|
|
254
259
|
| `exp-of-square` | high | `exp(x * x)`, `exp(x ** 2)`, `(-x.pow(2)).exp()`: the rounding error of the square is multiplied by `x * x / 2` ulps (pytorch #198664) |
|
|
255
260
|
| `sin-of-pi-times` | high | `sin(pi * x)`, `cos(pi * x)`: the product is rounded before the argument reduction (pytorch #198663) |
|
|
256
261
|
| `softplus-by-hand` | high | `log(1 + exp(x))` |
|
|
257
|
-
| `logsumexp-by-hand` | high | `log(exp(a) + exp(b))`, `log(sum(exp(x)))`, `log(exp(x).sum(-1))` |
|
|
258
|
-
| `hypot-by-hand` | high | `sqrt(a * a + b * b)` |
|
|
262
|
+
| `logsumexp-by-hand` | high | `log(exp(a) + exp(b))`, `log(sum(exp(x)))`, `log(exp(x).sum(-1))`; quiet when every `exp` argument has its maximum subtracted first, `log(exp(x - x.max()).sum())`, the stable form |
|
|
263
|
+
| `hypot-by-hand` | high | `sqrt(a * a + b * b)`; the advice no longer says `norm`, which squares first too and overflows at 1.8e19 in float32 (kornia #5505 review) |
|
|
259
264
|
| `sqrt-of-difference` | medium | `sqrt(a - b)` |
|
|
260
265
|
| `one-minus-cos` | medium | `1 - cos(x)` (kornia #4897) |
|
|
261
266
|
| `log1p-by-hand`, `expm1-by-hand` | medium | `log(1 + x)`, `exp(x) - 1` |
|
|
262
267
|
| `atan-of-quotient` | medium | `atan(y / x)` |
|
|
263
268
|
| `small-angle-division` | medium | `/ theta`, `/ theta ** 2`, `/ sin(theta)` in a function that takes `sin` or `cos` of `theta` and has no `where`, `clamp`, `eps` or series in sight (kornia #4838, #4897) |
|
|
264
|
-
| `acos-for-angle` |
|
|
269
|
+
| `acos-for-angle` | medium | `acos`, `asin` used to recover an angle: the angle comes back with an absolute error of `sqrt(eps)`, 0.02 degrees in float32 (kornia #5500) |
|
|
270
|
+
| `eps-floor` | medium | `x * (1 - eps) + eps`: every value moves, 0 becomes `eps`, so a one-hot's entries sum to `1 + (C - 1) eps` and a perfect prediction scores a loss that grows with the image (kornia #5538, found by the kornia conventions audit) |
|
|
271
|
+
| `where-nan-gradient` | medium | `where(d > eps, f(d), other)` with `f` a division by `d` or a `sqrt`, `log`, `acos` or `asin` of it: `where` evaluates both branches and hands the discarded one a zero gradient, and the backward of `f` at the singularity turns that zero into `0 / 0 = nan`, so the guard protects the value and not the gradient; quiet for `numpy.where`, for a comparison against a number above 1, for a floor named `eps`, `tol` or `floor` on the other side, and once `d` is re-bound to a `where`, `clamp` or `maximum` of itself (kornia #5579, found by the kornia conventions audit) |
|
|
272
|
+
| `clamp-at-singularity` | medium | `clamp(x, min=0).sqrt()`, `sqrt(clamp(x, min=0))`, `clamp(c, -1, 1).acos()`: the bound is the point where the next function has an infinite derivative, and clamp's derivative at its own bound is not the same across torch versions, 1 on 2.5.1 and 2.9.1 and 0 on 2.14, so the gradient there is `inf` or `nan` on the older half of a supported range; a bound strictly inside the domain, `min=1e-8`, is a floor and is not reported, and `numpy` is quiet (kornia #4229, found by the kornia conventions audit; kornia #5500) |
|
|
273
|
+
| `dropout-never-applied` | medium | a `Dropout`, `DropPath` or a `ModuleDict` of them assigned to `self` and then never called, never passed on and its rate never read, anywhere in the file: the option is accepted and does nothing, so the model trains without the regularisation it reports (peft #3830) |
|
|
265
274
|
|
|
266
275
|
On kornia `main` at `e05b0ee` the scan takes 4 s for 506 files and reports 35 findings. The
|
|
267
276
|
`one-minus-cos` and `small-angle-division` findings are the four lines of `So3.right_jacobian` and
|
|
@@ -270,6 +279,26 @@ and `Se3.exp` (also #4897) is under `one-minus-cos`. The scan puts `ellipse_to_l
|
|
|
270
279
|
on the list too, for a `sqrt` of a difference; the bug there was a different one, so that entry is
|
|
271
280
|
what the scan is: a reading list, not a verdict.
|
|
272
281
|
|
|
282
|
+
The two gradient rules are reading lists in the same sense. On kornia `main` at `d15741e2` they
|
|
283
|
+
report seven lines: three `clamp` bounds in `_solve_cubic_real`, and four `where` guards, in
|
|
284
|
+
`PatchDominantGradientOrientation`, `compute_correspond_epilines`, `_crop_scale_translation` and
|
|
285
|
+
`_get_convex_edges`, of which the first two sit on a differentiable path and the last two draw
|
|
286
|
+
boxes and polygons. On ultralytics `main` at `8df3534` they report three lines, all in metrics or
|
|
287
|
+
inference code that is never differentiated, and nothing in torchvision.
|
|
288
|
+
|
|
289
|
+
`dropout-never-applied` reads the whole file rather than one function, since the module is built
|
|
290
|
+
in `__init__` and used, if at all, in `forward` or in a sibling class. Calling it, calling it
|
|
291
|
+
through a subscript, passing it to another module, returning it, iterating over it and reading
|
|
292
|
+
its rate (`scaled_dot_product_attention(dropout_p=self.dropout.p)`) all count as use; filling it
|
|
293
|
+
(`self.oft_dropout.update(...)`) does not, and a subclass of `Sequential` runs every attribute and
|
|
294
|
+
is not read. On kornia `main` at `6d579a72`, ultralytics `6d51b7e`, torchvision `9a8d545`,
|
|
295
|
+
diffusers `d961a38`, torchrl `648f50c`, vllm `73c742b` and detectron2 `1e3e13b` it reports
|
|
296
|
+
nothing. On peft `main` at `f6d8480` it reports `OFTLayer.oft_dropout`, which is #3830; on timm
|
|
297
|
+
`83e6eb5` it reports `FactorAttnConvRelPosEnc.attn_drop` in `coat.py`, which a comment on that
|
|
298
|
+
line already calls unused; on transformers `9167f73` it reports nine lines, four of them in
|
|
299
|
+
modular files whose `forward` is inherited from another file, which the rule cannot see, and
|
|
300
|
+
five attention, embedding and head dropouts built in `__init__` and used nowhere in their file.
|
|
301
|
+
|
|
273
302
|
`--run` adds the dynamic half. The module level functions with the most elementary math are
|
|
274
303
|
imported and called with the same 91 point grid (both signs of `1e-8` to `1e3`, and zero) for every
|
|
275
304
|
required argument, in float32 and in float64, torch first and numpy second, and the float32 result
|
|
@@ -156,6 +156,7 @@ PYTHONPATH=/tmp/kornia-0.8.3 ulpwise corpus --repo kornia # 31 present, 0 fi
|
|
|
156
156
|
| pytorch/rl #4443 `arange(0, 1, 1/n)` gives `n + 1` positions for 140 values of `n` below 2000 | rounding | 2026-09-20 |
|
|
157
157
|
| pytorch/rl #4444 `min_value or -inf` drops `min_value=0` | falsy zero | 2026-09-20 |
|
|
158
158
|
| pytorch/rl #4445 scheduler `state_dict()` contained a module object | crash | 2026-09-20 |
|
|
159
|
+
| pytorch/rl #4504 `MultiCategorical.to_one_hot` crashed for a batched spec and a 0-D sample | crash | 2026-10-07 |
|
|
159
160
|
| timm #2786 Mars kept a reference to `p.grad` as the previous gradient | aliasing | 2026-09-17 |
|
|
160
161
|
| timm #2790 AdafactorBigVision clipped updates in the wrong direction | direction | 2026-09-18 |
|
|
161
162
|
| timm #2791 AdaMuon conv LR scale computed from the wrong dims | scale | 2026-09-18 |
|
|
@@ -199,6 +200,9 @@ PYTHONPATH=/tmp/kornia-0.8.3 ulpwise corpus --repo kornia # 31 present, 0 fi
|
|
|
199
200
|
| pytorch #198664 `erfcx` off by `x*x/2` ulps for negative `x` (`exp` at the rounded square) | rounding | open |
|
|
200
201
|
| pytorch #199850 `torch.erf` in bfloat16 and float16 on CPU returns 0 at and below 1.8e-7 and loses relative accuracy below 1e-3 (13404 bfloat16 ulps, 5 float16 ulps) | cancellation | open |
|
|
201
202
|
| pytorch #199867 `torch.special.logit` in float16 and bfloat16 on CPU rounds `1 - x` and `x / (1 - x)` to the input dtype before the log, logit(0.499756) is -0.000488 for -0.000977 (512 float16 ulps, 64 bfloat16 ulps) | rounding | open |
|
|
203
|
+
| kornia #5505 `angle_error_mat` and `angle_error_vec` returned exactly 0 for every rotation below 0.03 degrees in float32 (`acos` next to 1, issue #5500) | cancellation | 2026-10-06 |
|
|
204
|
+
| peft #3769 `add_weighted_adapter` with `combination_type='svd'` crashed for Conv1d and Conv3d LoRA layers | crash | open |
|
|
205
|
+
| peft #3830 OFT `module_dropout` was never applied, every training forward used all rotation blocks | no-op | open |
|
|
202
206
|
|
|
203
207
|
Cases marked `open` have an issue with the complete patch attached and no merged fix yet. Every case
|
|
204
208
|
is an expected failure until the installed release contains the fix (`fixed_in_release` in
|
|
@@ -217,22 +221,27 @@ ulpwise scan path/to/repo --rules one-minus-cos,small-angle-division --top 40
|
|
|
217
221
|
```
|
|
218
222
|
|
|
219
223
|
The scan is static and needs nothing installed: it parses each file with `ast`, walks every
|
|
220
|
-
function and matches
|
|
221
|
-
overflows
|
|
224
|
+
function and matches sixteen patterns, each with a severity, the reason it loses digits,
|
|
225
|
+
overflows or loses its gradient, or in one case does nothing at all, the usual replacement and,
|
|
226
|
+
where one exists, the upstream bug it comes from.
|
|
222
227
|
|
|
223
228
|
| rule | severity | pattern |
|
|
224
229
|
|---|---|---|
|
|
225
230
|
| `exp-of-square` | high | `exp(x * x)`, `exp(x ** 2)`, `(-x.pow(2)).exp()`: the rounding error of the square is multiplied by `x * x / 2` ulps (pytorch #198664) |
|
|
226
231
|
| `sin-of-pi-times` | high | `sin(pi * x)`, `cos(pi * x)`: the product is rounded before the argument reduction (pytorch #198663) |
|
|
227
232
|
| `softplus-by-hand` | high | `log(1 + exp(x))` |
|
|
228
|
-
| `logsumexp-by-hand` | high | `log(exp(a) + exp(b))`, `log(sum(exp(x)))`, `log(exp(x).sum(-1))` |
|
|
229
|
-
| `hypot-by-hand` | high | `sqrt(a * a + b * b)` |
|
|
233
|
+
| `logsumexp-by-hand` | high | `log(exp(a) + exp(b))`, `log(sum(exp(x)))`, `log(exp(x).sum(-1))`; quiet when every `exp` argument has its maximum subtracted first, `log(exp(x - x.max()).sum())`, the stable form |
|
|
234
|
+
| `hypot-by-hand` | high | `sqrt(a * a + b * b)`; the advice no longer says `norm`, which squares first too and overflows at 1.8e19 in float32 (kornia #5505 review) |
|
|
230
235
|
| `sqrt-of-difference` | medium | `sqrt(a - b)` |
|
|
231
236
|
| `one-minus-cos` | medium | `1 - cos(x)` (kornia #4897) |
|
|
232
237
|
| `log1p-by-hand`, `expm1-by-hand` | medium | `log(1 + x)`, `exp(x) - 1` |
|
|
233
238
|
| `atan-of-quotient` | medium | `atan(y / x)` |
|
|
234
239
|
| `small-angle-division` | medium | `/ theta`, `/ theta ** 2`, `/ sin(theta)` in a function that takes `sin` or `cos` of `theta` and has no `where`, `clamp`, `eps` or series in sight (kornia #4838, #4897) |
|
|
235
|
-
| `acos-for-angle` |
|
|
240
|
+
| `acos-for-angle` | medium | `acos`, `asin` used to recover an angle: the angle comes back with an absolute error of `sqrt(eps)`, 0.02 degrees in float32 (kornia #5500) |
|
|
241
|
+
| `eps-floor` | medium | `x * (1 - eps) + eps`: every value moves, 0 becomes `eps`, so a one-hot's entries sum to `1 + (C - 1) eps` and a perfect prediction scores a loss that grows with the image (kornia #5538, found by the kornia conventions audit) |
|
|
242
|
+
| `where-nan-gradient` | medium | `where(d > eps, f(d), other)` with `f` a division by `d` or a `sqrt`, `log`, `acos` or `asin` of it: `where` evaluates both branches and hands the discarded one a zero gradient, and the backward of `f` at the singularity turns that zero into `0 / 0 = nan`, so the guard protects the value and not the gradient; quiet for `numpy.where`, for a comparison against a number above 1, for a floor named `eps`, `tol` or `floor` on the other side, and once `d` is re-bound to a `where`, `clamp` or `maximum` of itself (kornia #5579, found by the kornia conventions audit) |
|
|
243
|
+
| `clamp-at-singularity` | medium | `clamp(x, min=0).sqrt()`, `sqrt(clamp(x, min=0))`, `clamp(c, -1, 1).acos()`: the bound is the point where the next function has an infinite derivative, and clamp's derivative at its own bound is not the same across torch versions, 1 on 2.5.1 and 2.9.1 and 0 on 2.14, so the gradient there is `inf` or `nan` on the older half of a supported range; a bound strictly inside the domain, `min=1e-8`, is a floor and is not reported, and `numpy` is quiet (kornia #4229, found by the kornia conventions audit; kornia #5500) |
|
|
244
|
+
| `dropout-never-applied` | medium | a `Dropout`, `DropPath` or a `ModuleDict` of them assigned to `self` and then never called, never passed on and its rate never read, anywhere in the file: the option is accepted and does nothing, so the model trains without the regularisation it reports (peft #3830) |
|
|
236
245
|
|
|
237
246
|
On kornia `main` at `e05b0ee` the scan takes 4 s for 506 files and reports 35 findings. The
|
|
238
247
|
`one-minus-cos` and `small-angle-division` findings are the four lines of `So3.right_jacobian` and
|
|
@@ -241,6 +250,26 @@ and `Se3.exp` (also #4897) is under `one-minus-cos`. The scan puts `ellipse_to_l
|
|
|
241
250
|
on the list too, for a `sqrt` of a difference; the bug there was a different one, so that entry is
|
|
242
251
|
what the scan is: a reading list, not a verdict.
|
|
243
252
|
|
|
253
|
+
The two gradient rules are reading lists in the same sense. On kornia `main` at `d15741e2` they
|
|
254
|
+
report seven lines: three `clamp` bounds in `_solve_cubic_real`, and four `where` guards, in
|
|
255
|
+
`PatchDominantGradientOrientation`, `compute_correspond_epilines`, `_crop_scale_translation` and
|
|
256
|
+
`_get_convex_edges`, of which the first two sit on a differentiable path and the last two draw
|
|
257
|
+
boxes and polygons. On ultralytics `main` at `8df3534` they report three lines, all in metrics or
|
|
258
|
+
inference code that is never differentiated, and nothing in torchvision.
|
|
259
|
+
|
|
260
|
+
`dropout-never-applied` reads the whole file rather than one function, since the module is built
|
|
261
|
+
in `__init__` and used, if at all, in `forward` or in a sibling class. Calling it, calling it
|
|
262
|
+
through a subscript, passing it to another module, returning it, iterating over it and reading
|
|
263
|
+
its rate (`scaled_dot_product_attention(dropout_p=self.dropout.p)`) all count as use; filling it
|
|
264
|
+
(`self.oft_dropout.update(...)`) does not, and a subclass of `Sequential` runs every attribute and
|
|
265
|
+
is not read. On kornia `main` at `6d579a72`, ultralytics `6d51b7e`, torchvision `9a8d545`,
|
|
266
|
+
diffusers `d961a38`, torchrl `648f50c`, vllm `73c742b` and detectron2 `1e3e13b` it reports
|
|
267
|
+
nothing. On peft `main` at `f6d8480` it reports `OFTLayer.oft_dropout`, which is #3830; on timm
|
|
268
|
+
`83e6eb5` it reports `FactorAttnConvRelPosEnc.attn_drop` in `coat.py`, which a comment on that
|
|
269
|
+
line already calls unused; on transformers `9167f73` it reports nine lines, four of them in
|
|
270
|
+
modular files whose `forward` is inherited from another file, which the rule cannot see, and
|
|
271
|
+
five attention, embedding and head dropouts built in `__init__` and used nowhere in their file.
|
|
272
|
+
|
|
244
273
|
`--run` adds the dynamic half. The module level functions with the most elementary math are
|
|
245
274
|
imported and called with the same 91 point grid (both signs of `1e-8` to `1e3`, and zero) for every
|
|
246
275
|
required argument, in float32 and in float64, torch first and numpy second, and the float32 result
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ulpwise"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.4"
|
|
8
8
|
description = "Numerical conformance testing for ML code: float edge values, exact rounding oracles, knife-edge finders and a pytest plugin"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT OR Apache-2.0" }
|
|
@@ -143,6 +143,25 @@
|
|
|
143
143
|
},
|
|
144
144
|
"before_fix": "TypeError: cannot pickle 'module' object"
|
|
145
145
|
},
|
|
146
|
+
{
|
|
147
|
+
"id": "torchrl-4504-multicategorical-to-one-hot-batched-spec",
|
|
148
|
+
"repo": "pytorch/rl",
|
|
149
|
+
"pr": 4504,
|
|
150
|
+
"merged_at": "2026-10-07T10:30:35Z",
|
|
151
|
+
"kind": "crash",
|
|
152
|
+
"title": "MultiCategorical.to_one_hot crashed for a spec with a batch shape and for the scalar sample of a shape [1] spec",
|
|
153
|
+
"symptom": "to_one_hot iterated over self.nvec, which is a 2-D tensor for a spec with a batch shape, so one_hot received a tensor as num_classes and raised TypeError; a shape [1] spec returns 0-D samples and val[..., i] on them raised IndexError.",
|
|
154
|
+
"requires": [
|
|
155
|
+
"torch",
|
|
156
|
+
"torchrl"
|
|
157
|
+
],
|
|
158
|
+
"fixed_in_release": null,
|
|
159
|
+
"repro": "import torch\nfrom torchrl.data import MultiCategorical\nspec = MultiCategorical([3, 2], shape=(4, 2))\nhot = spec.to_one_hot(spec.rand())\nscalar = MultiCategorical([5]).to_one_hot(torch.tensor(3))\nactual = [list(hot.shape), hot.dtype == torch.bool, int(hot.sum()), list(scalar.shape), int(scalar.sum()), bool(scalar[3])]\nexpected = [[4, 5], True, 8, [5], 1, True]\n",
|
|
160
|
+
"check": {
|
|
161
|
+
"type": "equal"
|
|
162
|
+
},
|
|
163
|
+
"before_fix": "TypeError: one_hot(): argument 'num_classes' (position 2) must be int, not Tensor for the batched spec; IndexError: too many indices for tensor of dimension 0 for the scalar sample"
|
|
164
|
+
},
|
|
146
165
|
{
|
|
147
166
|
"id": "timm-2786-mars-last-grad-aliasing",
|
|
148
167
|
"repo": "huggingface/pytorch-image-models",
|
|
@@ -1103,6 +1122,70 @@
|
|
|
1103
1122
|
"max_ulp": 1
|
|
1104
1123
|
},
|
|
1105
1124
|
"before_fix": "[-0.00048828125, -0.01129150390625, -0.039337158203125, 0.0797119140625] on torch 2.14.0 (512, 72, 23 and 6.7 ulp)"
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
"id": "kornia-5500-angle-error-small-angle-acos",
|
|
1128
|
+
"repo": "kornia/kornia",
|
|
1129
|
+
"pr": 5505,
|
|
1130
|
+
"issue": 5500,
|
|
1131
|
+
"merged_at": "2026-10-06T17:27:13Z",
|
|
1132
|
+
"kind": "cancellation",
|
|
1133
|
+
"title": "angle_error_mat and angle_error_vec return exactly 0 for every rotation below 0.03 degrees in float32",
|
|
1134
|
+
"symptom": "Both metrics recover the angle as acos of a cosine, (tr(R1^T R2) - 1) / 2 and v1 . v2 / (|v1| |v2|). Next to 1 the float32 cosine has no digits left for a small angle, so a 0.01 degree rotation scores 0.0, a 0.05 degree one is 3 to 21 % off depending on the rotations and 179.99 degrees comes back as 180.0; the clamp(-1, 1).acos() also has a -inf / nan backward at exactly 0 and 180 degrees on torch 2.5.1. The patch reads sin(theta) from the skew part of the relative rotation, the cross product of the unit vectors for angle_error_vec, and returns atan2(sin, cos).",
|
|
1135
|
+
"requires": [
|
|
1136
|
+
"torch",
|
|
1137
|
+
"kornia"
|
|
1138
|
+
],
|
|
1139
|
+
"fixed_in_release": null,
|
|
1140
|
+
"repro": "import torch\nimport kornia\nR1 = kornia.geometry.axis_angle_to_rotation_matrix(torch.tensor([[0.3, -0.5, 0.8]], dtype=torch.float64))\nR2 = R1 @ kornia.geometry.axis_angle_to_rotation_matrix(torch.deg2rad(torch.tensor([[0.0, 0.0, 0.01]], dtype=torch.float64)))\nv1 = torch.tensor([[0.0, 0.6, 0.8]], dtype=torch.float64)\nv2 = (kornia.geometry.axis_angle_to_rotation_matrix(torch.deg2rad(torch.tensor([[0.01, 0.0, 0.0]], dtype=torch.float64))) @ v1[..., None])[..., 0]\nactual = [kornia.metrics.angle_error_mat(R1.float(), R2.float()).item(), kornia.metrics.angle_error_vec(v1.float(), v2.float()).item()]\nexpected = [0.01, 0.01]\n",
|
|
1141
|
+
"check": {
|
|
1142
|
+
"type": "allclose",
|
|
1143
|
+
"atol": 0.0,
|
|
1144
|
+
"rtol": 0.001
|
|
1145
|
+
},
|
|
1146
|
+
"before_fix": "[0.0, 0.0] on kornia main 050ac77f"
|
|
1147
|
+
},
|
|
1148
|
+
{
|
|
1149
|
+
"id": "peft-3769-add-weighted-adapter-svd-conv1d-conv3d",
|
|
1150
|
+
"repo": "huggingface/peft",
|
|
1151
|
+
"pr": null,
|
|
1152
|
+
"issue": 3769,
|
|
1153
|
+
"merged_at": null,
|
|
1154
|
+
"kind": "crash",
|
|
1155
|
+
"title": "add_weighted_adapter with combination_type='svd' crashed for Conv1d and Conv3d LoRA layers",
|
|
1156
|
+
"symptom": "_svd_generalized_task_arithmetic_weighted_adapter checked isinstance(target, Conv2d) and flattened only that, so a Conv1d or Conv3d delta weight reached the SVD with its kernel dims in place and the matmul raised. The fix tests _ConvNd and always flattens from dim 1; PR 3903 is open with it.",
|
|
1157
|
+
"requires": [
|
|
1158
|
+
"torch",
|
|
1159
|
+
"peft"
|
|
1160
|
+
],
|
|
1161
|
+
"fixed_in_release": null,
|
|
1162
|
+
"repro": "import torch\nfrom peft import LoraConfig, get_peft_model\n\nclass Net(torch.nn.Module):\n def __init__(self):\n super().__init__()\n self.conv = torch.nn.Conv1d(4, 8, kernel_size=3)\n\n def forward(self, x):\n return self.conv(x)\n\ntorch.manual_seed(0)\nconfig = LoraConfig(target_modules=['conv'], r=2, init_lora_weights=False)\nmodel = get_peft_model(Net(), config, adapter_name='a')\nmodel.add_adapter('b', config)\nx = torch.randn(1, 4, 10)\nwith torch.no_grad():\n model.base_model.set_adapter(['a', 'b'])\n both = model(x)\n # svd_rank=4 holds the rank 2 + 2 sum of the two deltas exactly\n model.add_weighted_adapter(['a', 'b'], [1.0, 1.0], 'ab', combination_type='svd', svd_rank=4)\n model.set_adapter('ab')\n combined = model(x)\nactual = combined.flatten().tolist()\nexpected = both.flatten().tolist()\n",
|
|
1163
|
+
"check": {
|
|
1164
|
+
"type": "allclose",
|
|
1165
|
+
"atol": 1e-05,
|
|
1166
|
+
"rtol": 1e-05
|
|
1167
|
+
},
|
|
1168
|
+
"before_fix": "RuntimeError inside _svd_generalized_task_arithmetic_weighted_adapter on peft main 91ca7945: Expected size for first two dimensions of batch2 tensor to be: [8, 4] but got: [8, 3]"
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
"id": "peft-3830-oft-module-dropout-never-applied",
|
|
1172
|
+
"repo": "huggingface/peft",
|
|
1173
|
+
"pr": null,
|
|
1174
|
+
"issue": 3830,
|
|
1175
|
+
"merged_at": null,
|
|
1176
|
+
"kind": "no-op",
|
|
1177
|
+
"title": "OFT module_dropout was never applied, every training forward used all rotation blocks",
|
|
1178
|
+
"symptom": "OFTLayer built self.oft_dropout from module_dropout but OFTRotationModule.forward never called it, so module_dropout=0.5 behaved like 0 and ten training forwards of the same input were identical. The fix passes the dropout into the rotation module and applies it to the Cayley blocks; PR 3905 is open with it.",
|
|
1179
|
+
"requires": [
|
|
1180
|
+
"torch",
|
|
1181
|
+
"peft"
|
|
1182
|
+
],
|
|
1183
|
+
"fixed_in_release": null,
|
|
1184
|
+
"repro": "import torch\nfrom peft import OFTConfig, get_peft_model\n\nclass Net(torch.nn.Module):\n def __init__(self):\n super().__init__()\n self.lin = torch.nn.Linear(16, 12)\n\n def forward(self, x):\n return self.lin(x)\n\ntorch.manual_seed(0)\nconfig = OFTConfig(target_modules=['lin'], r=4, oft_block_size=0, module_dropout=0.5, init_weights=False)\nmodel = get_peft_model(Net(), config)\nmodel.train()\nx = torch.randn(8, 16)\nwith torch.no_grad():\n outputs = [model(x) for _ in range(10)]\n# module_dropout replaces half of the four rotation blocks by the identity in each training forward,\n# so ten forwards cannot all agree\nactual = [max((outputs[0] - out).abs().max().item() for out in outputs[1:])]\nexpected = [1.0] # any positive spread\n",
|
|
1185
|
+
"check": {
|
|
1186
|
+
"type": "sign"
|
|
1187
|
+
},
|
|
1188
|
+
"before_fix": "[0.0] on peft main 91ca7945; with the fix the ten forwards spread by 0.56"
|
|
1106
1189
|
}
|
|
1107
1190
|
]
|
|
1108
1191
|
}
|