156 lines
6.1 KiB
Markdown
156 lines
6.1 KiB
Markdown
# 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
|
||
```
|