Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cirron.com/llms.txt

Use this file to discover all available pages before exploring further.

Attach a named scalar value to the innermost open scope on the current thread. Marks are how you log per-step loss, grad norm, learning rate, accuracy: anything that should appear alongside the span in the viewer.

Signature

def mark(
    name: str,
    value: float | int | str | bool,
    *,
    kind: Literal["point", "summary"] = "point",
    **attrs,
) -> None

Parameters

NameTypeDefaultPurpose
namestr-Mark name
valuefloat | int | str | bool-Scalar value
kind"point" | "summary""point"See Kinds below
**attrsany-Extra attributes attached to the mark record
Values coerce to float64 / int / string (256-byte cap) / bool. Complex types (tensors, arrays) should use snapshots, not marks.

Kinds

kind=Use forViewer rendering
"point"Time-series data recorded while the span is open (per-step loss, per-batch accuracy)Chart
"summary"A single canonical end-of-span value (epoch-final loss)One value attached to the span

Attachment

The mark attaches to the innermost open scope on the calling thread at the moment mark() is called:
  • If a scope is open → the mark is recorded against that span.
  • If no scope is open → the mark attaches to the cirron.session root opened by ci.profile().
  • If ci.profile() was never called (pre-profile imports, tests that don’t open a session) → the mark falls through to the legacy "root" sentinel rather than a real span ID.

Examples

Point marks

for batch in loader:
    loss = train_step(batch)
    ci.mark("loss", loss.item())
    ci.mark("grad_norm", compute_grad_norm(model))
    ci.mark("learning_rate", scheduler.get_last_lr()[0])

Summary marks

for epoch in ci.epochs(range(20)):
    train_one_epoch()
    val_acc = evaluate()
    ci.mark("epoch_accuracy", val_acc, kind="summary")

With extra attributes

ci.mark("loss", loss.item(), step=global_step, phase="warmup")

Buffer behavior

Marks are written to a lock-free per-thread ring buffer with a default capacity of 64 k marks (not bytes). When full, the oldest mark is dropped and a drop counter is incremented. Every thread has its own buffer, so the counter is per-thread; the mark_drop_count field returned by ci.health() is the sum across all threads. Drop counts also surface on the dashboard. ci.mark is ~3.7 μs per call on x86_64 and ~2.4 μs on arm64. The buffer append is inlined into mark() itself. The hot path never enters MarkBuffer.append or walks through a property dispatch. Mark ids are generated via os.urandom(16).hex(), which produces a globally unique 32-char hex value on each call; the platform uses this id as the primary key for the mark row, so uniqueness across concurrent runs matters.

ci.scope

Open the scope that ci.mark will attach to.

Mark schema

How marks are serialized in the spool.