Skip to content

oxmera 0.5.1 — ten defects found by deep-testing the published 0.5.0 - #94

Merged
vyncint merged 5 commits into
mainfrom
release/0.5.1
Sep 16, 2026
Merged

vyncint merged 5 commits into
mainfrom
release/0.5.1

Conversation

@vyncint

@vyncint vyncint commented Sep 16, 2026

Copy link
Copy Markdown
Owner

oxmera 0.5.1 — the defects deep-testing the published 0.5.0 found

0.5.0 was tested as a repository. This release is what happened when it was
tested as a published artifact — a downstream project depending on
oxmera = "0.5.0" from crates.io. Ten defects, each reproduced by running
code, each now carrying a regression test. No public API changed, so
semver-checks passes as a patch.

Three of these are defects in 0.5.0's own new work, which is the honest
argument for testing the artifact rather than the tree.

Wrong numbers

Panics where a typed error or a value was promised

Data integrity and packaging

Closes #84
Closes #85
Closes #86
Closes #87
Closes #88
Closes #89
Closes #90
Closes #91
Closes #92
Closes #93

Not in this release

The deep-test run hit the org's monthly agent budget partway through, so the
autograd dimension never ran and 46 reported findings were left unverified
rather than disproven. Promising leads still to check: eigh's absolute
convergence threshold, its f32-computed symmetry guard on f64 input, whether
sum/mean are the compensated summation the CHANGELOG claims, and
serialize::load moving parameters back to the CPU after Module::to_device.

- `maximum`/`minimum` split a tie's gradient evenly instead of handing all
  of it to one operand. Every composite built on them was wrong at a tie:
  BCEWithLogitsLoss is `max(x, 0) - x*t + ...`, so at a logit of exactly 0
  it returned `-t` rather than `sigmoid(0) - t`, and one SGD step on a
  balanced batch raised the loss. This is the convention LIMITATIONS.md
  already documented. `relu'(0) = 0` is untouched — relu has its own VJP
  and never went through `maximum` (#84).
- `Tensor::from_storage` computes its bounds with checked arithmetic. The
  0.5.0 check overflowed isize: it panicked inside a Result-returning
  constructor in debug and accepted an out-of-bounds layout in release,
  where the first read then indexed far past the buffer. Any overflow is
  now "cannot be proven in bounds", which is a typed refusal (#85).
- Batched matmul returns the empty tensor when the output has a zero-size
  dimension instead of handing rayon a chunk size of zero and panicking;
  the rank-2 path already did. A zero inner dimension still produces the
  zero-filled result it should (#86).
- `mean` on an f64 tensor divides in f64. Scaling the sum by an f32
  reciprocal gave every f64 mean f32 precision: mean([1, 2, 3]) came back
  2.0000000596046448 (#87).

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
…a group

- Adam and AdamW correct bias with each parameter's own update count. A
  single global counter mis-scaled the first update of any parameter whose
  gradient arrived late: two parameters given identical first gradients
  moved by 0.100 and 0.074. 0.5.0 made skipping grad-less parameters the
  normal path, which makes a late first gradient routine (#88).
- Per-parameter state grows to match its group. `ParamGroup::params` is
  public and reachable through `groups_mut()`, so adding a parameter
  between steps is a supported thing to do; indexing the state blindly
  panicked with an index out of bounds on the next step. A parameter with
  no slot yet simply has no history, which is what `None` already meant
  (#89).

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
…yped

- `serialize::load` decodes and validates every parameter before writing
  any of them. Writing as it walked left a failed load half-applied: the
  parameters already visited held checkpoint values and the rest held
  their own, so a caller that handled the error kept training a model from
  neither source (#90).
- `serialize::save` rejects a duplicate parameter name with a typed
  InvalidArgument naming it, instead of letting safetensors panic on the
  repeated key. `named_parameters` is written by hand in every downstream
  Module, so a duplicate is ordinary caller error (#91).

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
LICENSE-MIT and LICENSE-APACHE lived only at the workspace root, so cargo
never packaged them: all twelve 0.5.0 tarballs carried the licence claim
`license = "MIT OR Apache-2.0"` without the licence text, which both
licences require a redistribution to include. Both files now sit in every
crate, and the release workflow refuses to publish a crate whose package
listing is missing either one, so this cannot regress silently (#92).

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
- README and docs/LIMITATIONS.md described the `ctor` pre-`main`
  constructor that 0.5.0 deleted and restated 0.4.0's "seven crates,
  47 -> 40" for `--no-default-features`. Measured with `cargo tree -e
  normal`: three crates leave (cudarc, libloading, oxmera-cuda) and it is
  43 -> 40.
- SECURITY.md said "Nothing is released yet" for a project with twelve
  crates published; docs/STABILITY.md cites that file for the supported-
  versions policy, so the stale line was load-bearing.
- The README's optim row named `SGD` and `RMSprop`; the types are `Sgd`
  and `RmsProp`, as docs/STABILITY.md already said (#93).

The workspace version is 0.5.1 and the CHANGELOG cuts the release.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit 1fa0359 into main Sep 16, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment