Metrics
Log Metrics
You can log multiple metrics at once:
run.log({
"train/loss": 0.5,
"train/accuracy": 0.85,
"val/loss": 0.6,
"val/accuracy": 0.80
}, step=epoch)
Nested Dicts
You can also use nested dicts, which are automatically flattened with / as the separator:
run.log({"train": {"loss": 0.5, "accuracy": 0.85}}, step=0)
# equivalent to: run.log({"train/loss": 0.5, "train/accuracy": 0.85}, step=0)
run.log({"model": {"encoder": {"loss": 0.3}}}, step=1)
# equivalent to: run.log({"model/encoder/loss": 0.3}, step=1)
Nested metric values must be scalars (str, bool, int, float). Logging a non-scalar leaf (like a list) returns a 422 error.
Series vs Scalar Types
The step parameter determines whether a metric is stored as a scalar or series:
step=None→ Scalar types (float,int,string,bool)- Single values stored without a step index
- Displayed as a single value card in the UI
-
Use for: final metrics, summary statistics, best model performance
-
step=<int>→ Series types (float_series,int_series,string_series,bool_series) - Values stored with a step index (e.g., step=0, step=1, step=2)
- Displayed as an interactive chart in the UI
- Use for: training curves, epoch-level metrics, iteration metrics
How Step Determines Type
The type is automatically determined by the step parameter:
# step=None → scalar type
run.log({"best_accuracy": 0.95}) # attribute_type="float"
# step=0 → series type
run.log({"accuracy": 0.85}, step=0) # attribute_type="float_series"
run.log({"accuracy": 0.87}, step=1) # attribute_type="float_series"
run.log({"accuracy": 0.89}, step=2) # attribute_type="float_series"
Supported Value Types
| Python Type | attribute_type |
Storage Column | Notes |
|---|---|---|---|
float |
float |
float_value |
Summary metrics (step=None) |
int |
int |
int_value |
Summary metrics (step=None) |
str |
string |
string_value |
Summary metrics (step=None) |
bool |
bool |
bool_value |
Summary metrics (step=None) |
float |
float_series |
float_value |
Time-series metrics (step=int) |
int |
int_series |
int_value |
Time-series metrics (step=int) |
str |
string_series |
string_value |
Time-series metrics (step=int) |
bool |
bool_series |
bool_value |
Time-series metrics (step=int) |
Logging Categorical Metrics
In addition to numeric types (float, int), you can log bool and str values to track categorical changes over time. These render as stacked area charts instead of line charts.
Bool series — track binary flags like training phase, convergence, or early stopping:
for step in range(100):
run.log({
"train/loss": train_loss,
"phase/is_training": step % 10 != 7,
"phase/is_converged": step >= 50,
}, step=step)
String series — track categorical values like optimizer, data source, or phase name:
for step in range(100):
run.log({
"train/loss": train_loss,
"hyperparams/optimizer": current_optimizer,
"phase/name": "train" if step % 10 != 7 else "validate",
}, step=step)
By default, string series show the top 3 categories as separate areas with the rest grouped as "Other". You can adjust this up to 10 in the chart UI.
When to use Config vs Metrics?
| Aspect | Config | Metrics |
|---|---|---|
| When set | Once at init | Multiple times during run |
| Use case | Hyperparameters | Values that change over time |
| Examples | lr=0.001, model=resnet50 |
loss=0.5, accuracy=0.87 |
| Display | Config tab | Overview tab + Charts |
Repeated Keys (Strict Insert — No Overwrites)
Logging a metric or config with a key that already exists raises a 409 Conflict error. To overwrite a value, you must explicitly remove it first.
This is intentional: overwrites can silently lose data in concurrent scenarios and make run history unreliable. Every logged value is permanent.
Metrics
Metric keys are unique per run per step. Logging the same metric at the same step raises 409:
run.log({"loss": 0.5}, step=0) # OK
run.log({"loss": 0.3}, step=0) # 409 Conflict — use remove() first
run.log({"loss": 0.1}, step=1) # OK — different step, new row created
To overwrite:
run.remove("loss", step=0) # remove specific step
run.log({"loss": 0.3}, step=0) # now log the new value
Config
Config keys are unique per run. Adding a duplicate key raises a 409 error. Use run.log_config() to add new keys after run creation, or run.remove_config() to delete a key before overwriting:
run = dalva.init(project="my-project", config={"lr": 0.001})
# Add more config later:
run.log_config({"batch_size": 32, "epochs": 100})
# Overwrite an existing key — must remove first:
run.remove_config("lr")
run.log_config({"lr": 0.01}) # now succeeds
Nested dicts are flattened with / as separator:
run.log_config({"optimizer": {"lr": 0.001, "betas": [0.9, 0.999]}})
# Creates keys: optimizer/lr, optimizer/betas
Type Consistency
Once a metric key is logged with a type, it cannot be changed. int and float are treated as distinct types:
run.log({"m": 5}, step=0) # OK — int_series
run.log({"m": 5.5}, step=0) # 409 Conflict — cannot change to float_series
Scalar vs Series
You cannot mix scalar (step=NULL) and series (step!=NULL) values for the same key:
run.log({"acc": 0.5}) # OK — scalar
run.log({"acc": 0.9}, step=0) # 409 Conflict — acc already has scalar values
Removing Metrics and Config
run.remove("loss") # removes ALL loss entries (all steps)
run.remove("loss", step=5) # removes only step 5
run.remove_config("lr") # removes the lr config key
run.log_config({"lr": 0.01}) # log new value after removal
Getting Metrics and Config
Retrieve a specific metric or config key by name:
# Get the latest step value for a metric:
run.get("loss") # {"key": "loss", "value": 0.3, "step": 1}
# Get a specific step:
run.get("loss", step=0) # {"key": "loss", "value": 0.5, "step": 0}
# Works with nested/flattened keys too:
run.get("train/loss", step=0) # {"key": "train/loss", "value": 0.4, "step": 0}
# If the key doesn't exist, returns None (or your default):
run.get("missing") # None
run.get("missing", default=0) # 0
# Get a config key:
run.get_config("lr") # {"key": "lr", "value": 0.001}
run.get_config("optimizer/lr") # {"key": "optimizer/lr", "value": 0.001}
run.get_config("missing", default="unknown") # "unknown"
Remove and Re-log Pattern
Since logging is strict (no overwrites), you must remove before re-logging. This works for both flat and nested keys:
# Overwrite a scalar metric:
run.remove("best_accuracy")
run.log({"best_accuracy": 0.97})
# Overwrite a specific step:
run.log({"train/loss": 0.5}, step=0)
run.remove("train/loss", step=0)
run.log({"train/loss": 0.3}, step=0)
# Overwrite a nested config key:
run.remove_config("optimizer/lr")
run.log_config({"optimizer": {"lr": 0.01}})
Frontend Rendering
Scalar Metrics (step=None)
Scalar metrics are displayed as value cards in the run's Overview tab:
| Metric | Value |
|---|---|
| best_model/accuracy | 0.9523 |
| best_model/hter | 0.0821 |
| training_completed | true |
| best_optimizer | adamw |
Numeric Series (float_series, int_series)
Numeric series are displayed as interactive line charts in the run's Metrics tab. The chart shows the metric value over steps, with hover tooltips for exact values.
Bool Series (bool_series)
Bool series are displayed as stacked area charts with two areas (true / false) showing cumulative counts over steps.
Use case — tracking binary states:
for step in range(50):
run.log({
"phase/is_training": step % 10 != 7,
"phase/is_converged": step >= 30,
}, step=step)
This lets you visualize when a flag flips — for example, when training switches to validation, or when a model converges.
String Series (string_series)
String series are displayed as stacked area charts showing cumulative counts per category over steps. By default the top 3 categories (by total count) are shown as separate areas, with remaining categories grouped under "Other". You can increase the number of visible categories up to min(10, nunique) using the selector below the chart.
Use case — tracking categorical values:
optimizers = ["adam", "adam", "sgd", "adam", "rmsprop", ...]
for step in range(50):
run.log({
"hyperparams/optimizer": optimizers[step],
"phase/name": "train" if step % 8 != 7 else "validate",
}, step=step)
Common scenarios for string series:
| Scenario | Example metric | Categories |
|---|---|---|
| Training phase | phase/name |
train, validate, test |
| Optimizer sweep | hyperparams/optimizer |
adam, sgd, rmsprop |
| Data source region | infra/region |
us-east, eu-west, ap-south |
| Learning rate schedule | lr/schedule |
cosine, step, constant |
| Model selection | model/variant |
base, large, xl |
Compare Runs View
When comparing multiple runs, categorical metrics display side-by-side stacked area charts — one per run — so you can see how category distributions differ across experiments.