Foundry/ternary_optim/docs/API.md

156 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ternary_optim — API Reference
Every public symbol in `ternary_optim.h`: signature, parameters, semantics, and
memory ownership. All functions are `extern "C"` and thread-agnostic (the update
uses no shared mutable global state; distinct handles are independent).
Row-major layout throughout: a `(rows x cols)` matrix stores element `(r, c)` at
index `r * cols + c`. `numel = rows * cols`.
---
## Return codes
```c
enum {
TERNARY_OPTIM_OK = 0, // success
TERNARY_OPTIM_EINVAL = -1, // invalid argument
TERNARY_OPTIM_ENOMEM = -2, // allocation failure
TERNARY_OPTIM_ENOTIMPL = -3 // not implemented (stubbed feature)
};
```
---
## Configuration
### `ternary_optim_config`
```c
typedef struct {
float target_step; // avg proposed step (LSB) for a mean-|g| weight; ~0.001
int q_clamp; // symmetric accumulator clamp; 7 (15 states -> nibble)
int theta; // dead-zone: w=0 while |q|<theta, else sign(q); 2
int use_dither; // 1 = stratified Weyl LD dither (default); 0 = i.i.d.
uint64_t seed; // threaded through every weight/every update + init
int64_t idx_base; // global index base for this matrix in the hash lane
} ternary_optim_config;
```
The struct is **copied** into the handle at create time; changing it afterward has
no effect. `q_clamp` and `theta` are floored to `1` internally if set below 1.
### `void ternary_optim_config_default(ternary_optim_config *cfg)`
Fills `cfg` with the reference defaults: `target_step=0.001`, `q_clamp=7`,
`theta=2`, `use_dither=1`, `seed=42`, `idx_base=0`. No-op if `cfg` is NULL.
**Ownership:** caller owns `cfg`.
---
## Lifecycle
### `ternary_optim *ternary_optim_create(int rows, int cols, const ternary_optim_config *cfg)`
Allocates a handle owning the `numel`-byte int4 accumulator `q`, initialized to
`{-3, 0, +3}` at `{33.5%, 33%, 33.5%}` (drawn from an internal xorshift seeded
from `seed ^ idx_base`).
- **Params:** `rows > 0`, `cols > 0`; `cfg` is copied, or defaults are used if
NULL.
- **Returns:** owning handle, or **NULL** on invalid args (`rows<=0`, `cols<=0`,
`numel` overflow) or allocation failure.
- **Ownership:** caller owns the returned handle and must free it with
`ternary_optim_free`. The caller retains ownership of `cfg`.
### `void ternary_optim_free(ternary_optim *opt)`
Releases the handle and its internal `q`. Safe with NULL.
---
## Core operations
### `void ternary_optim_materialize(const ternary_optim *opt, float *out_weights)`
Writes the ternary deployed weights into a **caller-owned** buffer:
```
out[i] = 0 if |q[i]| < theta ; +1 if q[i] >= +theta ; -1 if q[i] <= -theta
```
- **Params:** `out_weights` must hold at least `numel` floats. No-op if either
pointer is NULL.
- **Semantics:** pure read of `q`; does not modify optimizer state. Call before
every forward pass.
- **Ownership:** caller owns `out_weights`.
### `void ternary_optim_step(ternary_optim *opt, const float *grad_sum, int batch_size, uint64_t batch_counter, float target_step)`
One SR4 update: integrates the batch gradient into `q` (per-matrix step
normalization → 8.8 fixed-point stochastic rounding with LD dither → saturating
add). See [HOW_IT_WORKS.md §3–4](HOW_IT_WORKS.md).
- **`grad_sum`** — per-weight gradient **summed over the batch** (not averaged),
row-major, at least `numel` floats. Read-only; caller-owned.
- **`batch_size`** — samples summed into `grad_sum` (`> 0`). Used for the
`/batch_size` mean; may vary batch to batch (e.g. a partial final batch).
- **`batch_counter`** — monotonically increasing global update index starting at
0. **Contract:** all matrices updated within the *same* logical step use the
*same* `batch_counter`; the caller increments it by exactly 1 **after** stepping
every matrix in that step. Drives the stateless dither.
- **`target_step`** — effective per-step target (e.g. a cosine-annealed value). If
`<= 0`, the config `target_step` is used.
- **Side effect:** the only mutation is the integer add into `opt->q`. Nothing
else persists (the dither is regenerated from the counter-hash).
- **No-op** if `opt`/`grad_sum` is NULL or `batch_size <= 0`.
---
## Accessors (diagnostics)
```c
int ternary_optim_numel(const ternary_optim *opt); // rows*cols, 0 if NULL
int ternary_optim_rows (const ternary_optim *opt); // rows, 0 if NULL
int ternary_optim_cols (const ternary_optim *opt); // cols, 0 if NULL
const int8_t *ternary_optim_q (const ternary_optim *opt); // raw q view, NULL if NULL
```
`ternary_optim_q` returns a **read-only, non-owning** view of the internal `q`
(one signed byte/weight, clamped to `[-q_clamp, +q_clamp]`), valid until the
handle is freed. For fingerprints / rail-and-dead-zone diagnostics only; the
update rule never reads it back. Do not write through it.
---
## Optional per-column output scales (decoupled helpers)
These operate on **caller-owned** buffers over an arbitrary `(rows x cols)` matrix;
the scale vector is **not** owned by the optimizer (it belongs to the layer). See
[HOW_IT_WORKS.md §5](HOW_IT_WORKS.md) and [USAGE.md](USAGE.md).
### `void ternary_apply_col_scale(float *Z, const float *scale, int rows, int cols)`
In place, `Z[r,c] *= scale[c]`. `scale` has length `cols`. Exact identity when
`scale` is all-ones. No-op if `Z`/`scale` is NULL.
### `void ternary_col_scale_grad(const float *D, const float *Z_raw, float *out, int rows, int cols)`
Per-column scale gradient `out[c] = Σ_r D[r,c] * Z_raw[r,c]`. `out` (length `cols`)
is **overwritten**, not accumulated. `D` is the unscaled upstream delta, `Z_raw`
the pre-scale/pre-bias matmul output. No-op if any pointer is NULL. Apply to the
scale vector by plain SGD at a dedicated small LR.
---
## State serialization — STUBBED (TODO)
Both currently return `TERNARY_OPTIM_ENOTIMPL` and perform no I/O. The thing to
persist is the packed `q` (4 b/w) + shape + config; the dither/masks regenerate
from the counter-hash and never need saving. Format is a follow-up.
```c
int ternary_optim_save(const ternary_optim *opt, const char *path);
int ternary_optim_load(const char *path, ternary_optim **out); // sets *out = NULL
```