> ## 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.

# ci.profile

> Attach the profiler with framework autodetect and hooks.

Attach the profiler to the current process. **Call once.** Subsequent
calls are no-ops and log a warning. Returns a `Profiler` handle most
callers discard.

Calling `ci.shutdown()` clears the singleton; `ci.profile()` after
that starts a fresh profiler. This is the supported way to reset
state between tests.

## Signature

```python theme={null}
def profile(
    config: dict | None = None,
    frameworks: list[str] | None = None,
    snapshots: Literal["stats", "sampled", "full"] | None = None,
    sample_rate: float | None = None,
    flush_interval: float | None = None,
    enabled: bool = True,
    path: str | None = None,
    output: str | list[str] | None = None,
) -> Profiler
```

## Parameters

| Name             | Type                 | Default   | Purpose                                                         |
| ---------------- | -------------------- | --------- | --------------------------------------------------------------- |
| `config`         | `dict?`              | `None`    | Runtime feature flags read by your code via `config.get(...)`   |
| `frameworks`     | `list[str]?`         | `None`    | Skip autodetect and install hooks for the named frameworks      |
| `snapshots`      | `str?`               | `"stats"` | `"stats"`, `"sampled"`, or `"full"` weight/gradient capture     |
| `sample_rate`    | `float?`             | `0.01`    | Fraction of epoch boundaries that serialize raw tensors         |
| `flush_interval` | `float?`             | `1.0`     | Background flush thread wake interval in seconds                |
| `enabled`        | `bool`               | `True`    | Set `False` to build a no-op profiler (zero overhead, no hooks) |
| `path`           | `str?`               | `None`    | Override the local spool directory (`./.cirron/` by default)    |
| `output`         | `str` or `list[str]` | `"spool"` | Local sink fan-out: `"spool"`, `"log"`, `"stdout"`, `"none"`    |

`None` for `snapshots` / `sample_rate` / `flush_interval` means "use
the `Cirron` instance default". Pass an explicit value to override
per-call.

## What it does

1. Resolves config (explicit `config=` → platform global → SDK defaults).
2. Reads platform context from `CIRRON_RUN_ID`, `CIRRON_PIPELINE_ID`,
   `CIRRON_DEPLOYMENT_ID`, `CIRRON_WORKSPACE_ID`.
3. Selects a transport: kernel event stream (inside a Cirron pipeline
   or deployment), HTTP (with an API key), or file-only (neither).
4. Autodetects installed frameworks unless `frameworks=` is explicit.
5. Installs hooks for **every** detected framework. When multiple are
   present, the priority order `transformers > tensorflow > torch`
   determines which one owns the semantic `epoch` and `step` scopes
   via a shared `HookContext.owned_scopes` map; lower-priority hooks
   yield on those names and still produce their own lower-level
   scopes (torch still emits `forward` / `backward` /
   `optimizer_step` / `data_load` under the transformers-owned
   `step`).
6. Starts the background flush thread.
7. Registers `atexit`, SIGTERM, and SIGINT handlers for clean shutdown.
8. Opens the `cirron.session` root scope with `framework`, `device`,
   `cuda_count`, and `mixed_precision` attributes.

## Snapshot modes

| Mode        | Cost per epoch boundary   | What's captured                                               |
| ----------- | ------------------------- | ------------------------------------------------------------- |
| `"stats"`   | ≤ 50 ms (typical model)   | `{mean, std, min, max, norm, histogram[16]}` per tensor       |
| `"sampled"` | ≤ 200 ms on sampled steps | Stats + raw tensor values for `random() < sample_rate` epochs |
| `"full"`    | unbounded; debug-only     | Stats + raw tensor values every epoch                         |

`"full"` is not recommended for models over 100M parameters. At 7B+,
even `"sampled"` is expensive; drop the `sample_rate`.

## Output sinks

The `output=` parameter selects which local sinks the flush thread
writes each batch to. It is independent of the platform transport:
when `CIRRON_RUN_ID` is set, batches still flow over the kernel event
stream regardless of `output`. Sinks control the **local** experience
(disk, logs, terminal), which is why `output="none"` is safe even
inside a Cirron pipeline.

| Value       | Behavior                                                                                                       |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| `"spool"`   | (Default.) Write each batch as a JSON file under `./.cirron/spool/`. Public spool format.                      |
| `"log"`     | Emit one `logging.INFO` line per closed span on the `cirron.trace` logger.                                     |
| `"stdout"`  | Print one line per closed span to stdout. Same format as `"log"`, no logging configuration.                    |
| `"none"`    | No spool, no log, no print. Traces stay in the in-memory buffer for [`ci.trace()`](/sdk/reference/trace) only. |
| `list[str]` | Multiple sinks fan-out simultaneously, e.g. `["spool", "log"]`.                                                |

Invalid values raise `ValueError` at `ci.profile()` time, before any
hook is installed.

```python theme={null}
ci.profile(output="log")               # stream lines to the cirron.trace logger
ci.profile(output=["spool", "stdout"]) # disk + live terminal feed
ci.profile(output="none")              # in-memory only; pair with ci.trace()
```

A live span line looks like:

```
[cirron] epoch[0] - 142us {epoch_loss=0.5234}
```

## Returns

A [`Profiler`](/sdk/reference/types#profiler) handle exposing
[`health`](/sdk/reference/lifecycle#health), [`flush`](/sdk/reference/lifecycle#flush),
[`trace`](/sdk/reference/trace), and [`shutdown`](/sdk/reference/lifecycle#shutdown).

## Examples

### Zero-touch

```python theme={null}
import cirron as ci
ci.profile()

trainer.train()   # HF Trainer, Keras model.fit, or torch + DataLoader
```

### Explicit snapshot mode

```python theme={null}
ci.profile(snapshots="sampled", sample_rate=0.05)
```

`sample_rate` is the fraction of epoch boundaries that serialize raw
tensors. Higher values give more fidelity for debugging (e.g. you can
inspect the actual weight values at epoch 7 when loss spiked); lower
values keep storage and flush cost bounded. The default `0.01` (1 %)
is conservative. For small models or short runs, `0.05`–`0.1` is
reasonable; at 7 B+ parameters, stay at `0.01` or lower.

### Disable hooks selectively

```python theme={null}
ci.profile(frameworks=["torch"])   # ignore transformers / tensorflow even if importable
```

### Dev-only kill switch

```python theme={null}
ci.profile(enabled=os.environ.get("ENABLE_PROFILE") == "1")
```

### Notebook-friendly inspection

```python theme={null}
ci.profile(output="none")    # no disk writes, no log noise
trainer.train()
ci.trace()                   # render the scope tree in the cell
```

See [`ci.trace`](/sdk/reference/trace) for the full read-back surface.

## Distributed training

Every rank calls `ci.profile()`. The SDK reads `RANK`, `LOCAL_RANK`,
and `WORLD_SIZE` from the environment and tags every span with its
rank. The platform merges views at query time.

## Related

<CardGroup cols={2}>
  <Card title="Profiling guide" icon="chart-line" href="/sdk/profiling">
    Narrative walk-through of training instrumentation.
  </Card>

  <Card title="Lifecycle" icon="power-off" href="/sdk/reference/lifecycle">
    `flush`, `health`, and `shutdown`.
  </Card>
</CardGroup>
