Skip to content

Standard metrics

metrana.log is the workhorse for standard, ML-step metrics:

metrana.log(metric_name, value, *, step=None, timestamp=None, scale=None, labels=None, evaluation=False, dtype=None)

It logs to the default ML_STEP scale. For how step, scale, labels, and evaluation shape a series, see Steps, scales & labels; this page covers values and call shapes.

A value may be a scalar or an array — a Python list, or any NumPy / PyTorch / JAX / TensorFlow tensor (any float dtype, on any device). Arrays are converted to a contiguous NumPy array once before crossing into the engine.

metrana.log("loss", 0.5) # one point
metrana.log("loss", [0.51, 0.49, 0.48]) # three points (bulk)
metrana.log("grad_norm", grad_norm_tensor) # torch / np / jax / tf tensor

Logging an array is the bulk path: it records many points in one call. This is far cheaper than a Python loop of scalar log calls — see Logging at scale.

Float values are stored at float32 by default: metric signals don’t carry more precision than that, and it halves the wire and storage footprint. Values arriving wider are rounded with IEEE round-to-nearest-even. Integer-typed values — Python ints, int arrays/tensors, typically counters like tokens seen or cumulative samples — are exempt from the default and stored as float64, which keeps them exact up to 253 (float32 would silently round anything past ~16.7M, 224). Choose a precision explicitly per call with dtype, or run-wide with metrana.init(..., default_dtype=...):

metrana.log("loss", loss) # float32 (the float default)
metrana.log("tokens_seen", tokens) # int input → float64, stays exact
metrana.log("reward", rewards, dtype="float16") # half precision, half the wire again

Accepted values are "float64", "float32", "float16", "int64" and "int32" — numpy spellings (np.float32, "f4", …) work too; for torch/JAX dtypes pass their string name. "float16" rounds to half precision but travels as float32 (the widening is exact). "int64"/"int32" declare an integer metric: use them when a counter reaches the log call as a float (after a division, a mean, or a metrics dict that stores plain floats — where the automatic exemption can’t see it) — values are truncated toward zero and travel as float64, exact to 253; NaN/inf raise a validation error (a NaN is meaningful in a float metric, but always a bug in a counter). An explicit dtype always wins, including over the integer exemption: dtype="float32" narrows an int input too.

Pass a mapping to log several metrics in a single call. Each value may itself be a scalar or an array:

metrana.log({"loss": loss, "accuracy": acc})
metrana.log({
"loss": loss,
"lr": current_lr,
"grad_norm": grad_norm_tensor,
})

This is purely a convenience over calling log once per metric — each entry becomes its own series.

import metrana
metrana.init(workspace_name="ws", project_name="proj", run_name="run-001")
try:
for step in range(num_steps):
loss = train_step()
metrics = evaluate() # {"accuracy": ..., "f1": ...}
metrana.log("loss", loss) # logged every step → auto-increment is fine
metrana.log(metrics) # several at once
if step % 100 == 0:
# logged only every 100 steps → pass the real training step explicitly,
# otherwise this series' auto-incremented axis (0, 1, 2, …) wouldn't line
# up with `loss` on the same chart.
metrana.log("lr", scheduler.get_last_lr()[0], step=step)
finally:
metrana.close()

A series logged with log is ordered: every new point’s step must be greater than the last step already logged for that series. Auto-incremented steps satisfy this by construction; with explicit step= it’s on you.

Two situations look like backward steps and have proper solutions instead:

  • Several processes writing one series legitimately interleave steps — use log_distributed, whose merge semantics accept out-of-order steps.
  • A restarted process must not re-number from 0: resume from the run’s real last step (metrana.get_last_step) or your checkpoint’s step counter. See Resuming & forking.
  • log is non-blocking: it validates the value and enqueues it for the background engine, then returns. It does not wait for the network. To wait for durability, call flush.
  • An invalid value (bad name, mismatched lengths, non-finite where not allowed, …) raises MetranaValidationError synchronously from log — validation is the enqueue boundary. See Errors.
  • For series written by more than one process, use log_distributed instead — it uses merge semantics so concurrent writers don’t conflict.