div telemetry
Prints the numbers a run wrote to DIVERGENT_TELEMETRY_PATH as it ran, and how its telemetry.toml lays them out on the run's Telemetry tab.
div telemetry run-7
div telemetry run-7 loss eval_loss
div telemetry run-7 loss -f | tail -1
A run’s telemetry is the numbers it writes while it runs: loss by step, learning rate, throughput. They are drawn on the run’s Telemetry tab, and div telemetry prints them.
Writing it
Every run is given DIVERGENT_TELEMETRY_PATH. Append one JSON object per line there, whenever there is something to say:
import json, os
with open(os.environ["DIVERGENT_TELEMETRY_PATH"], "a") as f:
print(json.dumps({"step": 120, "loss": 0.41, "lr": 3e-4}), file=f, flush=True)
Each line becomes a point. Every number in it is kept under its key, with nested objects flattened into dotted keys: {"eval": {"loss": 0.4}} is eval.loss. Strings, booleans and nulls are passed over, and so are NaN and Infinity, which Python’s json writes and JSON does not have. The rest of the line is still kept. Lines arrive about a second after they are written.
Only this file is read. A number printed to the log stays text: nothing parses the log. Telemetry is drawn and never judged either: a run’s checks read its result, and nothing a point says changes how a run ends.
From a Hugging Face Trainer
The Trainer prints {'eval_loss': 0.0267, 'epoch': 0.04} to the log, and those numbers stay text there. A callback writes the same dict to the file:
import json, os
from transformers import TrainerCallback
class Telemetry(TrainerCallback):
def on_log(self, args, state, control, logs=None, **kwargs):
if state.is_world_process_zero and logs:
with open(os.environ["DIVERGENT_TELEMETRY_PATH"], "a") as f:
f.write(json.dumps({"step": state.global_step, **logs}) + "\n")
trainer = Trainer(model=model, args=args, callbacks=[Telemetry()], ...)
Laying it out
A telemetry.toml at the top of the directory div run ships lays out the Telemetry tab. --telemetry <file> names another. It is read when the run is created and kept with it, so a run shows what it was launched with.
x = "step" # what every panel is drawn against
[[panel]]
title = "Loss"
y = ["loss", "eval_loss"] # up to 4 keys on one panel
scale = "log"
[[panel]]
title = "F1"
y = ["eval_strict_f1", "eval_typed_f1"]
min = 0.9
max = 1.0
rule = 0.95 # one horizontal line, like a check's threshold
[[panel]]
y = "eval_leak_recall"
wide = true # spans both columns
[[stat]]
title = "Samples/s"
y = "eval_samples_per_second"
reduce = "last" # last, min or max
| Key | In | Means | When left out |
|---|---|---|---|
x |
the file, a panel | the key a panel is drawn against | the point’s place in the run |
title |
a panel, a stat | what it is called | its keys |
y |
a panel, a stat | the key, or keys, to draw | required |
scale |
a panel | linear or log |
linear |
min, max |
a panel | the y axis’s bounds | fitted to the points |
rule |
a panel | a horizontal reference line | none |
wide |
a panel | span both columns | false |
reduce |
a stat | last, min or max |
last |
A point without a panel’s x key has nowhere to go on that panel, so it is left off. A key no point has written yet shows as “no points yet”, never as a flat line at zero.
A run without a telemetry.toml gets one panel per key, in the order the keys first arrived, drawn against step when the run writes one. A file that cannot be read refuses the run, the way a check that cannot be read does. That covers a key misspelt, a scale that does not exist, or a min above the max, and the error says which table and which key:
div: the API refused the run: telemetry [[panel]] 2 has "scael", which is not one of title, x, y, scale, min, max, rule, wide
Reading it
With no keys, div telemetry prints every key the run has written, with how many points carry it and the last, lowest and highest value:
key count last min max
step 412 41200 100 41200
loss 412 0.0412 0.0398 2.31
eval_loss 41 0.0267 0.0261 1.88
With keys, it prints the points as tab-separated columns on stdout: x first, then one column per key. A point missing a key has an empty cell there, never a zero:
div telemetry run-7 loss eval_loss > curve.tsv
div telemetry run-7 loss --x epoch
div telemetry run-7 --csv > run-7.csv
| Flag | Does |
|---|---|
--x <key> |
the first column; step when the run writes one, the point’s place in the run otherwise |
--csv |
the same table with commas, quoted where a cell needs it; with no keys, a column for every key the run has written |
--json |
every point as one JSON object per line, {"seq": 1, "data": {...}} |
-f, --follow |
keeps printing points as they arrive, until the run ends |
What is not kept
A line is dropped whole when it is not a JSON object, is over 4 KiB, or has more than 64 numbers. A key that is not letters, digits, _ and - between dots, like train/loss, is left out of its point. A run keeps at most 256 keys and 200,000 points.
What is dropped is counted. The log says so the first time each reason comes up, and div telemetry repeats the counts on stderr:
telemetry: dropped a line that is not JSON; write one JSON object per line
Any value of one of the org’s secrets is masked before a line is read, as it is in the log.