This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize for the AI-speak, we are working on rewriting it!
viv-lang.org

Notebooks · lib/optim

Four optimizers over one parameter vector

An optimizer turns a gradient into a change of the parameters. This module has four, and they differ only in what they remember and how they shape the step:

  • SGD steps against the gradient: theta <- theta - lr g.
  • SGD with momentum keeps a running sum of past gradients, m <- mu m + g, and steps along that. A direction the gradient keeps pointing in builds up speed, and one it flips back and forth on cancels.
  • Adam keeps two running averages per number: of the gradient (m) and of its square (v). It steps along m / sqrt(v), so every coordinate moves at about the rate lr whatever the size of its gradient.
  • Muon keeps a momentum like the second one, then treats each weight matrix as a matrix: it replaces the momentum by a matrix with the same singular vectors and every singular value close to 1 (five Newton-Schulz steps from lib/polar), and steps along that. Adam evens out the step per number; Muon evens it out per direction of the matrix.

All four work on the layout of lib/params: the parameters, the gradient and the optimizer's memory are each one vector. The memory is two vectors' worth (a first and a second moment), which is the most any of them needs.

A model held as records of matrices (lib/gpt) takes Adam and Muon one matrix at a time instead: adam_update and muon_update update a matrix and its memories in place, with the settings in an AdamSettings or MuonSettings record, and for_leaves applies them over a whole model, as opt.step() does in PyTorch. A test holds them to the flat versions bit for bit.

An optimizer is a small settings vector made by sgd(lr), momentum(lr, mu), adam(lr, beta1, beta2) or muon(lr, mu), and one step of any of them is optim_step(opt, theta, grad, state, layout). The step itself is looked up in a registry, optimizers(), so a program can run every one of them through the same loop; optimizer_of(kind) makes any of them at its usual constants.

The tests are the contract each of the four optimizers has to meet: from the same start, on the same schedule (a starting step size of its own, falling in a straight line to zero), reach the minimum of a convex quadratic and of a small least-squares problem to within 1e-3 in every coordinate. SGD, momentum and Adam get far closer than that, to 3.5e-5 or better. Muon stops at 7.2e-4, and a test says why: its step has the length it chose rather than the gradient's, so it ends as close as its last few steps are short. Muon has one more clause: the direction it steps along has every singular value in the band lib/polar measures, [0.68, 1.14], for a wide momentum matrix and a tall one.

Used with import optim.

Look inside this program

  • Atlas

    The program’s map: every function and which calls which.

  • There’s no recorded run of this program yet.

This program declares a record type (AdamSettings), and records run only in compiled C for now, so the Run buttons here are off. Open it in the workbench and use its C tab to compile it.

Shift+Enter in a cell runs it and moves to the next. Running a cell runs every cell above it too, as one program.

[1]lines 50–422