Skip to main content
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

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 attributes

Nested

Indexed

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.