Skip to content

oxmera 0.5.0 — production hardening (25 issues) - #83

Merged
vyncint merged 8 commits into
mainfrom
release/0.5.0
Sep 16, 2026
Merged

vyncint merged 8 commits into
mainfrom
release/0.5.0

Conversation

@vyncint

@vyncint vyncint commented Sep 16, 2026

Copy link
Copy Markdown
Owner

oxmera 0.5.0 — production hardening

The 0.5 milestone: turn the panics, silent wrong answers and untyped
failures a downstream consumer could still hit into typed errors, lock the
public API against accidental breakage, and give the release pipeline the
checks that keep a bad build from shipping. Twenty-five issues, eight
signed commits, full CHANGELOG under ## [0.5.0].

The whole local gate is green (fmt, clippy -D warnings, the workspace
test suite, docs -D warnings, cargo deny). Breaking changes are
collected in the CHANGELOG with one-line migrations; the new
semver-checks job enforces that a future break cannot land in a patch.

Correctness — typed refusals over panics and silent NaN

  • from_storage rejects out-of-bounds (negative-stride) layouts instead of
    panicking on first read; narrow reports an overflowing range.
  • eigh keeps an f64 input in f64 end to end (was rounding to f32).
  • Losses: CrossEntropyLoss refuses an empty batch and a mismatched
    target; MSELoss/BCEWithLogitsLoss require an exact-shape target.
  • Conv2d refuses a sub-kernel input or a zero stride; BatchNorm2d uses
    the unbiased running variance; argmax refuses NaN; Dropout::new
    refuses p outside [0, 1).
  • Optimizer::step skips a parameter with no gradient instead of failing
    the whole step.
  • GPU meta builders reject extents/strides/offsets that exceed u32.

API surface and robustness

  • Module::to_device (fixes the never-running fused Adam step); Module
    requires Debug; Sequential is inspectable (iter/get/Index).
  • Hyperparameter builders on the optimizers and norm layers.
  • MatmulPlan/AdamStep/ParamGroup are #[non_exhaustive].
  • Tensor's Debug prints metadata, not the whole buffer.
  • Metal/CUDA register explicitly (no pre-main #[ctor]/dlopen).

CLI, CI and docs

  • train --tui fails cleanly off a tty; Ctrl-C quits the dashboard;
    subcommand --help exits 0; doctor --json.
  • semver-checks job, release test + CHANGELOG gates, gate concurrency and
    timeouts, scheduled advisories, crates.io keywords/categories.
  • docs/STABILITY.md; single-GPU limits documented.

Closes #45
Closes #46
Closes #47
Closes #48
Closes #49
Closes #50
Closes #51
Closes #52
Closes #53
Closes #54
Closes #55
Closes #56
Closes #57
Closes #58
Closes #59
Closes #61
Closes #62
Closes #63
Closes #64
Closes #65
Closes #66
Closes #67
Closes #73
Closes #74
Closes #75

Deferred to the 0.5.x/0.6 cycle (still in the milestone)

…ing eigh

- from_storage rejects a layout whose minimum addressed index is negative,
  not only one whose maximum overruns the buffer. A negative stride with
  too small an offset addressed before the storage and panicked on the
  first read; the bounds check now accounts for negative strides while
  still admitting a valid flip view (#50).
- narrow reports an overflowing start + len as a typed InvalidArgument
  instead of overflowing the usize addition in its own bounds check (#51).
- try_zeros/try_ones/try_full return a typed error when the shape's
  element count overflows usize; the infallible zeros/ones/full delegate
  to them and document that they panic on such a shape (#51).
- eigh keeps an f64 input in f64 from input to output through the new
  cpu_linalg::eigh_f64, instead of rounding to f32 before decomposing.
  The comment that claimed it already preserved f64 is now true (#52).
- Tensor's Debug prints shape, dtype, device and requires_grad rather than
  the entire storage buffer, which a derived Debug dumped into every log
  line and panic message (#62).

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

- Module gains `to_device`, backed by `Param::to_device`, so a model
  uploads its weights to the GPU once instead of every forward pass
  re-uploading them; this also lets the device-resident fused Adam step
  actually run (#46).
- Module now requires Debug, so Sequential derives a Debug that shows its
  children, and gains `iter`, `get` and `Index` for child access (#61).
- Optimizer::step skips a parameter with no gradient instead of failing
  the whole step, matching PyTorch's grad=None handling (#53).
- Adam/AdamW expose `with_betas`/`with_eps` and RMSprop `with_alpha`/
  `with_eps`, builder-style; the defaults are unchanged (#54).
- MatmulPlan, AdamStep and ParamGroup are `#[non_exhaustive]` so future
  fields are not a breaking change; AdamStep gains a constructor for its
  one cross-crate call site, and the backends destructure MatmulPlan with
  `..` (#59).

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

- CrossEntropyLoss returns a typed error on an empty batch instead of the
  NaN it produced from `0 * inf`, and rejects a target whose length does
  not match the batch (#47).
- MSELoss and BCEWithLogitsLoss require the target to match the input
  shape exactly, rather than silently broadcasting a wrong-shaped target
  into a wrong loss (#48).
- Conv2d returns a typed error for an input smaller than its kernel or a
  zero stride, instead of underflowing a usize subtraction or dividing by
  zero (#49).
- BatchNorm2d updates running_var with the unbiased sample variance
  (Bessel's correction), matching PyTorch, while still normalizing the
  current batch with the biased variance (#64).
- argmax returns a typed error when the input contains NaN instead of
  skipping the NaN and pointing at an arbitrary element (#74).
- Dropout::new rejects a probability outside [0, 1) rather than scaling
  survivors by 1/(1-p) <= 0 (#74). LayerNorm and BatchNorm2d gain
  `with_eps` (and BatchNorm2d `with_momentum`) builders (#54).

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
- The Metal and CUDA backends no longer register from a pre-main `#[ctor]`
  constructor; the `ctor` dependency and its life-before-main `dlopen` of
  libcuda are gone. Every consumer already registers explicitly through
  `oxmera_runtime::init()` (the CLI and examples) or `register_default()`
  (the parity tests), and `oxmera doctor` probes the devices directly, so
  nothing relied on the constructor (#55).
- The GPU meta builder converts tensor extents, strides and offsets with
  checked `try_from` instead of an `as u32`/`as i32` that silently
  truncated a tensor larger than 4 Gi elements or a large offset into a
  wrong-but-plausible launch; an out-of-range value is now a typed
  InvalidArgument before dispatch (#63).

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

- `train --tui` (and a replay) with stdout that is not a terminal now
  returns a usage-style error and a non-zero exit instead of panicking
  with exit 101 inside `ratatui::init()` (#56).
- Ctrl-C typed into the dashboard quits it. In raw mode ISIG is off, so it
  arrives as Char('c')+CONTROL, a key the event loop now treats like q and
  Esc; the SIGINT path is unchanged (#57).
- `oxmera doctor --help` and `oxmera train --help` print usage on stdout
  and exit 0 instead of being rejected as an unknown argument with exit 1;
  `oxmera doctor --json` emits the report as JSON for scripts (#58).

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
matmul materialized both operands with `to_vec_f32` on every call, copying
even an already-contiguous operand. It now borrows the storage slice
directly when the operand is contiguous and only gathers (copies) a strided
or broadcast view, which is the case that actually needs it (#73).

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

- A semver-checks job compares the workspace public API against the last
  crates.io release; a 0.x minor bump may break, a patch may not (#59).
- The release workflow runs the whole test suite and asserts CHANGELOG.md
  names the version before it publishes anything (#66).
- The convergence gate gets a concurrency group and per-job timeouts, so a
  superseded run is cancelled and a hung one cannot run forever (#67).
- Every crate declares crates.io keywords and categories, inherited from
  the workspace, and a weekly scheduled job runs the advisory database
  against the committed lockfile so a new CVE surfaces without a push (#75).
- The no-default-features graph check folds ctor into the must-be-absent
  list: it left the workspace with the pre-main registration.

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

- docs/STABILITY.md states the pre-1.0 SemVer contract, the MSRV policy,
  the #[non_exhaustive] surface, and the criteria for 1.0 (#45).
- LIMITATIONS.md and the Device docs record that only device index 0 is
  registered — a higher index is a typed BackendUnavailable — and that
  Metal and CUDA register explicitly rather than before main (#65, #55).
- The workspace version is 0.5.0 and the CHANGELOG cuts the release.

Signed-off-by: Vyncint Ng <115854244+vyncint@users.noreply.github.com>
@vyncint
vyncint merged commit 5e67550 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