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.

Open a named span. Becomes the innermost scope on the current thread until the context exits. Use it for regions that framework hooks and loop wrappers don’t already cover: augmentation, beam search, custom schedulers, preprocessing passes.

Signature

@contextmanager
def scope(name: str, index: int | None = None, **attrs) -> Scope

Parameters

NameTypeDefaultPurpose
namestr-Scope name; appears as span.name in the spool
indexint?NoneOptional index (e.g. epoch or batch number)
**attrsany-Arbitrary keyword attributes attached to the span
Attribute values are stored on span.attrs as JSON. The platform indexes them and lets you filter by them in the dashboard.

Behavior

  • Nests arbitrarily under whatever scope is already open. Max depth 64; scopes beyond that log a warning and the span record is dropped, but the with block still runs normally: __enter__ and __exit__ fire, and the code inside executes. The drop is reported in ci.health() so you can spot it.
  • Thread-local. Each thread maintains its own scope stack, so parallel DataLoader workers and async handlers don’t contaminate each other.
  • On __exit__, end time, CPU time, and (when hooks populate it) GPU time are recorded. The closed span is handed to the flush thread.
  • Overhead is tracked and surfaced as a mark on every scope.

Examples

Simple wrap

with ci.scope("augmentation"):
    batch = augment(batch)

With attributes

with ci.scope("postprocess", variant="beam-search", beam_width=4):
    output = beam_search(logits)

Nested

with ci.scope("preprocess"):
    with ci.scope("tokenize"):
        ids = tokenizer(text)
    with ci.scope("pad"):
        ids = pad(ids, max_len=512)

Indexed

for i, shard in enumerate(shards):
    with ci.scope("shard", index=i):
        process(shard)

Returns

The yielded Scope carries the span’s ID and start time. Most callers ignore it; ci.mark() automatically attaches to whatever scope is currently innermost.

Overhead

Open + close measures ~4.4 μs per cycle on x86_64 and ~2.7 μs on arm64. The hot path is lock-free: a thread-local scope stack, a slotted Scope instance, a 32-char hex id, and inlined ContextVar + thread-local lookups. Cost scales through the push/pop pair itself, not with the number of sibling scopes already closed, so nesting doesn’t compound. See Overhead for the full picture.

ci.mark

Attach a value to the innermost open scope.

Loop wrappers

ci.epochs and ci.batches: scopes without a with block.