> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trainy.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Exporting Weights & Biases Runs

> Export existing Weights & Biases runs to Pluto using pluto migrate wandb

<Warning>
  `pluto migrate wandb` is in **public preview**. Its interface and behavior may change as we iterate.
</Warning>

`pluto migrate wandb` imports **historical** Weights & Biases runs — runs that already finished — into Pluto.

## Which tool do I want?

<CardGroup cols={2}>
  <Card title="Runs you're still training" icon="arrows-rotate" href="/pluto/wandb-migration">
    Use the **compatibility shim**. It dual-logs every `wandb.log()` call to Pluto as the run happens, with no code changes.
  </Card>

  <Card title="Runs that already finished" icon="box-archive">
    Use **`pluto migrate wandb`**, described on this page. It reads finished runs out of the W\&B cloud API and replays them into Pluto.
  </Card>
</CardGroup>

The two are complementary: point the shim at your ongoing work, then backfill your history with the importer.

## Install

```bash theme={null}
pip install 'pluto-ml[migrate]'
```

You need credentials for both sides:

```bash theme={null}
export WANDB_API_KEY=...     # or run `wandb login`
export PLUTO_API_KEY=...     # or run `pluto login`
```

## How it works

```
W&B cloud  ──export──▶  staging directory  ──load──▶  Pluto
                        (parquet + run.json
                         + media files)
```

**Export** downloads each run to disk. **Load** replays the staged data into Pluto through the normal client API.

Keeping them separate means you can inspect exactly what was pulled before anything is written to Pluto, re-run the load without hitting the W\&B API again, and resume either phase after an interruption.

## Quick start

Import one project, end to end:

```bash theme={null}
# --entity    wandb entity — your team or username (required)
# --project   which wandb project; omit it to take them all
# --output    where runs are staged on disk (required)
# --workers   projects at once — budget 2-4 GB of RAM each
# --cleanup   delete each run's staged files once it has loaded
pluto migrate wandb all --entity my-team --project my-project --output ./staging --workers 4 --cleanup
```

Or run the phases separately, which gives you a chance to inspect what was pulled before anything reaches Pluto:

```bash theme={null}
# 1. export — download the runs from W&B to ./staging
pluto migrate wandb export --entity my-team --project my-project --output ./staging

# 2. check what would load, without writing anything
pluto migrate wandb load --input ./staging --dry-run

# 3. load — replay what you staged into Pluto
pluto migrate wandb load --input ./staging --cleanup
```

<Tip>
  Start with one `--project` before pointing it at a whole entity.
</Tip>

## Commands

### `export`

Downloads runs from W\&B into a staging directory.

| Flag | Description |
| - | - |
| `--entity` | W\&B entity (team or user). **Required.** |
| `--output` | Directory to stage exported data in. **Required.** |
| `--project` | W\&B project to migrate. Repeatable. Omit to export **all** projects under the entity. |
| `--exclude` | Project to skip when exporting all projects. Repeatable. |
| `--run-id` | Only these W\&B run ids. Repeatable. |
| `--after` / `--before` | Only runs whose W\&B creation date falls after / before an ISO-8601 date — `2024-01-01` or `2024-01-01T00:00:00Z`. Naive dates are read as UTC. |
| `--wandb-api-key` | W\&B API key. Defaults to `WANDB_API_KEY` or `wandb login`. |
| `--workers` | Parallelism, default `4`. Projects exported concurrently, and the per-run file-download budget. Each concurrent project holds its run history and upload queue in memory (\~2–4 GB per worker), so raise it only if the host has the RAM. |
| `--no-files` | Skip media and file downloads. **Also drops custom charts** — see below. |
| `--no-console` | Skip console `output.log`. |
| `--no-system-metrics` | Skip GPU/CPU system metrics. |
| `--no-artifacts` | Skip logged artifacts. |
| `--artifact-max-size-mb` | Skip artifacts larger than N MB. No cap by default. |
| `--strict` | Exit non-zero if any run had data that couldn't be migrated. |

<Note>
  `--after` / `--before` filter on W\&B's `created_at`, and the filtering happens locally after listing the project's runs — so a narrow window doesn't make the listing itself faster. A date it can't parse fails the command rather than being ignored, and a run with no usable `created_at` is excluded from a date-filtered export with a warning.
</Note>

### `load`

Replays a staging directory into Pluto.

| Flag | Description |
| - | - |
| `--input` | Export directory to load from. **Required.** |
| `--project` / `--exclude` | Limit which staged projects are loaded. Repeatable. |
| `--run-id` | Only these W\&B run ids. Repeatable. |
| `--dest-project` | Pluto project to load into. Defaults to the W\&B project name. **Only valid with a single `--project`** — otherwise the command exits with an error. |
| `--dry-run` | Print what would be loaded without creating runs. |
| `--force-resume` | Re-load runs already marked loaded. May duplicate media files. |
| `--flush-every` | Metric batch size, and how often non-metric rows check backpressure. Default `500`. |
| `--max-pending` | Max queued records before the loader throttles. Default `5000`. |
| `--cleanup` | Delete each run's staged files once it's confirmed loaded. |
| `--workers` | Projects loaded concurrently, default `4`. |

### `all`

Runs both phases together, and takes the export and load flags at once, minus `--input`.

`all` is usually faster than running the two commands back to back, because it **overlaps them**. Export runs in the background while a loader repeatedly sweeps the staging directory, picking up each run the moment it's fully staged — so uploads to Pluto are already going while later runs are still downloading from W\&B.

That matters because the two phases are bound by different things: export waits on the W\&B API, load waits on uploads to Pluto. Run sequentially, the wall clock is the sum of both. Overlapped, it's closer to the slower of the two.

<Tip>
  Pair it with `--cleanup` on a large migration. Each run's staged files are deleted once it's confirmed loaded, so peak disk stays near the size of a single run instead of the whole entity.
</Tip>

<Note>
  `--dry-run` is **not** accepted on `all`. Run `export` first, then `load --dry-run`.
</Note>

### Exit codes

| Code | Meaning |
| - | - |
| `0` | Everything migrated cleanly |
| `1` | Some runs failed; the rest completed |
| `2` | Usage error, a crashed worker, a missing `migrate` extra, or a `--strict` violation |

## The staging directory

Each run is staged under `{output}/{entity}/{project}/runs/{run_id}/`:

| File | Contents |
| - | - |
| `run.json` | Run manifest — name, config, tags, summary, state, original timestamps. |
| `part-00000.parquet`, … | Metrics, string series, system metrics, media references, console lines and artifact rows in one long schema. Parts rotate at 50 MB. |
| `files/` | Downloaded media and artifact files. |
| `_export_complete.json` | Sentinel, written **last**. Its presence means the run staged completely. |

Runs are staged atomically — written to a temporary directory and renamed into place — so an interrupted export re-does at most one run.

<Warning>
  Re-running `export` skips any run that already has its sentinel, **regardless of which flags you pass**. If you first exported with `--no-artifacts` and want the artifacts, delete those run directories — changing the flag alone won't re-export them.
</Warning>

The load phase records progress in a ledger beside the export: `loaded_runs.json` for a single project, or `loaded_runs.{project}.json` when two or more projects are in scope. A load over many projects and a load over one therefore keep **separate** resume state.

## Timestamps

The importer replays each point at its original wall-clock time rather than stamping it with the import time. Run `createdAt` comes from W\&B's `created_at`, and the historical finish time is written to `statusUpdated`, which is what drives Pluto's [Duration](/pluto/run-lifecycle#imported-runs).

<Note>
  Pluto copies whatever dates W\&B recorded — it can't recover a date W\&B doesn't have. If a run was trained offline and synced to W\&B days later, W\&B stamped it with the **sync** date, so that's the date your migrated run gets too.
</Note>

## Resuming and re-running

Every run carries the external id `wandb::{entity}/{project}/{run_id}`, which is how the loader recognizes a run it has already imported.

Re-running `load` is safe. Finished runs are skipped, and a run that was created but not fully replayed — for example if the loader was killed mid-run — is resumed and completed.

<Warning>
  Loading into a destination that was already migrated **briefly reopens** those runs server-side before restoring their terminal status. The end state is correct, but a run can appear as `RUNNING` for a moment.
</Warning>

**Duplicates only ever affect media and files, never scalar metrics.** Metrics carry their original timestamps, so replaying them replaces rather than duplicates. Use `--force-resume` only when you deliberately want to re-load a run that's already marked loaded.

## Large migrations

* `--workers` controls project concurrency and, on export, the per-run file-download budget. Budget \~2–4 GB of RAM per worker.
* `--flush-every` and `--max-pending` bound how much the loader queues before it throttles.
* `--cleanup` deletes each run's staged files as soon as it's confirmed loaded, so peak disk stays close to the largest single run rather than the whole entity.
* Set `disable_system_metrics` so the import machine's own hardware stats aren't recorded into the runs you're importing:

  ```python theme={null}
  import pluto
  pluto.settings(disable_system_metrics=True)
  ```

  The runs still receive the system metrics captured on the original W\&B host.

## What migrates

| What | Notes |
| - | - |
| **Scalar metrics** | Original step and timestamp; booleans as `1.0`/`0.0`; `NaN` and `±Inf` preserved |
| **String metrics** | Non-numeric series become a [string metric](/pluto/visualizations/lines#string-metrics) staircase |
| **Histograms** | Pre-binned, with real bin edges reconstructed from W\&B's packed bins |
| **System metrics** | `system.*` → `sys/*`, unless `--no-system-metrics` |
| **Console logs** | stdout/stderr from `output.log`, unless `--no-console` |
| **Images** | Single and list-logged, order preserved, unless `--no-files` |
| **Image annotations** | Bounding boxes and segmentation masks — see [Annotations](/pluto/visualizations/images#annotations) |
| **Audio & video** | Including list-logged, unless `--no-files` |
| **Tables** | Plain `wandb.Table` → `pluto.Table` |
| **Custom charts** | `wandb.plot.*` panels, re-rendered as Vega |
| **Sweep membership** | Runs keep their sweep grouping as a `sweep:<id>` tag, alongside the sweep's recorded config |
| **Run metadata** | Name, tags (plus an `import:wandb` tag), state, config and summary |
| **Run environment** | Git commit/branch, OS, Python version, CLI args, GPU inventory |
| **Artifact files** | Files of *logged* artifacts, unless `--no-artifacts` and subject to `--artifact-max-size-mb` |

## What a migrated run looks like

Migrated content renders with some limits worth knowing before you go looking for it.

| Content | Where it appears | Limits |
| - | - | - |
| **Custom charts** | All-runs and individual-run **Charts** tabs | Up to **25 runs** overlaid per chart; only the newest step of the backing table; **cannot be added to a dashboard**, reordered, hidden, or saved in a Charts layout |
| **Plotly / matplotlib** | Charts view, run page, Files tab | JSON over **8 MB** renders as raw text instead of a chart |
| **3D point clouds** | Same | Row widths 3, 4 and 6 only; over 200,000 points are decimated |
| **HTML** | Same | Sandboxed, so external stylesheets and scripts don't load |
| **Tables** | Charts view, run page, Files tab | Newest step only — there is no step slider; no dashboard widget |
| **String metrics** | Everywhere, including dashboards | Sampled to 500 steps |

<Note>
  Logs named like W\&B's per-run artifacts — `run-xxxxxxxx-name:v0` — are hidden from the metrics views by design. Find them on the run's **Files** tab.
</Note>

## What does not migrate

<AccordionGroup>
  <Accordion title="Hand-authored Vega charts and wandb.plot_table">
    W\&B never exports the spec body for a custom Vega panel, so there's nothing to render. The chart's **backing table still imports** — the data survives, the visualization doesn't. Flagged as `custom-chart-unsupported`.
  </Accordion>

  <Accordion title="Bokeh, and 3D meshes and molecules">
    Bokeh has no renderer. `Object3D` imports only as a point cloud — mesh and molecule formats (`.obj`, `.glb`, `.pdb`) aren't handled.
  </Accordion>

  <Accordion title="Rich tables">
    `joined-table` and `partitioned-table` are skipped. Plain tables import; a table with media in its cells imports as data, with the media cells rendered as text.
  </Accordion>

  <Accordion title="Artifact lineage and versioning">
    Only the *files* of a run's logged artifacts are re-uploaded. Versions, aliases, the type/lineage graph and input (used) artifacts are dropped.
  </Accordion>

  <Accordion title="Reports and saved workspace views">
    W\&B Reports, saved panel layouts and custom dashboards.
  </Accordion>

  <Accordion title="Model Registry">
    Registered models and their lineage.
  </Accordion>

  <Accordion title="Internal summary keys">
    `_`-prefixed summary fields such as `_wandb` are stripped.
  </Accordion>
</AccordionGroup>

## Silently dropped

Most gaps are reported in the coverage flags below. These are **not** — nothing warns you, and `--strict` won't catch them:

* **Custom charts, when you pass `--no-files`.** The panel specs live in the run's `config.yaml`, which `--no-files` never downloads. Every `wandb.plot.*` chart disappears with no flag raised.
* **History keys beginning with `_`** — `_runtime`, `_wandb` and similar.
* **History rows missing `_step` or `_timestamp`**, and `None` values.
* **System metrics not prefixed `system.`** in W\&B's events stream.

A few things migrate, but with less fidelity than you might expect:

* **W\&B `notes` don't become Pluto notes.** They're preserved under the run's config, alongside the original URL, state and summary.
* **Console output loses its stream.** Every line is recorded at `INFO`, so stdout and stderr are no longer distinguishable.
* **Histograms with no bin edges get synthetic ones** (`0..N`). Counts are exact; the x-axis is generic.
* **String values over 200 characters are dropped** — string metrics are short state labels, not free-form text. Flagged as `string-series-too-long`.

## Coverage flags

The exporter prints a flag for each dropped item, per run and as an end-of-project total. `--strict` exits non-zero if any flag was raised.

| Dropped item | Flag |
| - | - |
| Hand-authored Vega, or an unresolvable backing table | `custom-chart-unsupported` |
| String value over 200 characters | `string-series-too-long` |
| Media skipped via `--no-files` | `media-file(--no-files)` |
| Unknown media type | `unsupported(<type>)` |
| Non-scalar history value | `unsupported(<python type>)` |
| Failed downloads | `file-download-failed` / `artifact-download-failed` |
| Artifact over `--artifact-max-size-mb` | `artifact-over-size-cap` |
| Table columns containing media | `table-media-cell` |
| Input-artifact lineage | `artifact-input-lineage` |
| Artifact versions and non-latest aliases | `artifact-versioning` |

<Warning>
  `--strict` fires on **any** flag, including ones you caused deliberately. Exporting with `--no-files` raises `media-file(--no-files)` for every skipped item, so `--strict` will fail that run.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.