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

# Weights & Biases Migration

> How to migrate from Weights & Biases to Pluto with zero code changes using the dual-logging hook.

<Warning>
  The wandb compatibility shim is in **public preview**. Its interface and behavior may change as we iterate. Expect sharp edges.
</Warning>

<Note>
  This page covers **dual-logging runs as they happen**. To import runs that have already finished in wandb, see [Exporting Weights & Biases Runs](/pluto/exporting-wandb-runs).
</Note>

We've created a Weights & Biases compatibility layer that enables dual-logging to both wandb and Pluto simultaneously. This allows you to gradually migrate your experiment tracking without touching existing training scripts.

## Quick Start

Install Pluto and authenticate. Use either `pluto login` (stores a token in your system keyring — recommended for interactive workstations) **or** export `PLUTO_API_KEY` (recommended for CI / containers / non-interactive jobs):

```bash theme={null}
pip install pluto-ml

# Pick ONE of the two lines below:
pluto login                          # interactive — stores token in keyring
export PLUTO_API_KEY=mlps_xxx        # or set the env var
```

Then run any existing wandb script — dual-logging activates automatically:

```python theme={null}
import wandb

# Project that the run is recorded to
project = "my-awesome-project"

# Dictionary with hyperparameters
config = {
    'epochs': 10,
    'lr': 0.01,
}

with wandb.init(project=project, config=config) as run:
    # Training code here
    # Log values to W&B with run.log()
    run.log({"accuracy": 0.9, "loss": 0.1})
```

That's it. Every `wandb.log()` call now also logs to Pluto. No `PLUTO_PROJECT` env var needed — the shim picks up the `project=` kwarg from `wandb.init()`.

## Configuration

### Authentication

Either of the following is sufficient — the hook checks both:

| Source | When to use |
| - | - |
| `pluto login` | Interactive workstations. Stores a token in your system keyring; persists across shells. |
| `PLUTO_API_KEY` env var | CI, containers, or any non-interactive context. In `DISABLE_WANDB_LOGGING=true` mode, `WANDB_API_KEY` may be reused as a Pluto token. |

If neither is present, the shim logs a one-time warning pointing at `pluto login` / `PLUTO_API_KEY` and wandb runs unmodified.

### Project Name

Resolved in this order (first match wins):

| Source | Notes |
| - | - |
| `PLUTO_PROJECT` env var | Highest precedence. |
| `project=` kwarg to `wandb.init(...)` | Picked up automatically — no extra config needed. |
| `WANDB_PROJECT` env var | Falls back to wandb's own project env so you don't need to duplicate config. |
| `run.project` attribute on the wandb run | Last-resort fallback. |

### Optional Environment Variables

| Variable | Description |
| - | - |
| `PLUTO_URL_APP` | App endpoint for self-hosted instances |
| `PLUTO_URL_API` | API endpoint for self-hosted instances |
| `PLUTO_URL_INGEST` | Ingest endpoint for self-hosted instances |
| `DISABLE_WANDB_LOGGING` | Set to `true` to skip wandb entirely and only log to Pluto (post-sunset mode, see below). |

## Supported Operations

The compatibility layer monkey-patches `wandb.init()` so the returned `Run` object dual-logs every supported call. Unsupported methods still work normally on wandb via attribute fallthrough — they just don't dual-log to Pluto.

### Initialization

| wandb | pluto |
| - | - |
| `wandb.init(project="my-project", config=cfg)` | `pluto.init(project="my-project", config=cfg)` |
| `wandb.init(group="my-sweep")` / `WANDB_RUN_GROUP` | Surfaced as a `group:my-sweep` tag |
| `wandb.init(job_type="train")` / `WANDB_JOB_TYPE` | Surfaced as a `job_type:train` tag |
| `wandb.init(fork_from=...)` | `pluto.init(fork_run_id=..., fork_step=...)` |
| `run.tags = ["experiment-v1"]` | `run.add_tags(["experiment-v1"])` |

`wandb.init()` completes fully before any Pluto code runs. `pluto.init()` runs in a thread with a 10s timeout — if the Pluto server is unreachable, the run silently falls back to wandb-only. Tag assignments on the wandb run sync to Pluto on every assignment.

### Metric Logging

| wandb | pluto |
| - | - |
| `wandb.log({"train/loss": 0.5})` | `run.log({"train/loss": 0.5})` |
| `wandb.log({"train/loss": 0.5}, step=100)` | `run.log({"train/loss": 0.5}, step=100)` |

Numeric values, lists of numbers, and the wandb media types below are forwarded to Pluto. The `data` dict you pass into `wandb.log()` is never mutated.

### Media Logging

| wandb | pluto equivalent |
| - | - |
| `wandb.Image(...)` | `pluto.Image(...)` |
| `wandb.Histogram(...)` | `pluto.Histogram(...)` |
| `wandb.Audio(...)` | `pluto.Audio(...)` |
| `wandb.Video(...)` | `pluto.Video(...)` |
| `wandb.Table(...)` | Pluto table file |

Lists of media at the same step (`wandb.log({"gens": [wandb.Image(a), wandb.Image(b)]})`) are forwarded as a multi-sample group. The Pluto UI renders these with a per-card `◀ i / N ▶` nav — see [Images: Multi-Sample Logging](/pluto/visualizations/images#multi-sample-logging).

### Artifacts and Saved Files

| wandb | pluto equivalent |
| - | - |
| `wandb.log_artifact(art)` | Each local file in the artifact → `pluto.Artifact` under `artifacts/{art_name}/{entry_path}` |
| `wandb.save(path_or_glob)` | Each matched file → `pluto.Artifact` under `save/{basename}` |

Reference entries (S3 URLs, etc.) inside `wandb.Artifact` are skipped — Pluto stores files directly. Artifact versions and aliases are not translated; Pluto has no equivalent.

### Finish

`wandb.finish()` triggers `pluto.finish()` as well. Pluto's teardown has a 5s timeout so it never blocks wandb's cleanup. If `pluto.init()` failed earlier, the run stays wandb-only and `finish()` is a no-op on the Pluto side.

## Not Supported

These wandb features run normally on wandb but **do not** dual-log to Pluto:

| wandb feature | Reason |
| - | - |
| `run.config.update()` post-init | Compat layer captures config only at `wandb.init()`. Call `run.update_config()` directly on the Pluto run for in-run updates. |
| `run.watch()` (gradient tracking) | No Pluto equivalent. |
| `wandb.log_model()` / model registry | No Pluto equivalent. |
| `wandb.log_code()` | No Pluto equivalent. |
| `define_metric()`, `mark_preempting()`, sweeps | No Pluto equivalent. |

## Post-Sunset Mode

When you're ready to stop sending data to wandb, set the kill switch:

```bash theme={null}
export DISABLE_WANDB_LOGGING=true
```

In disabled mode, `WANDB_API_KEY` can also hold a Pluto API token, so a user fully migrating from wandb can swap a single env var value:

```bash theme={null}
export WANDB_API_KEY=mlps_xxx       # Pluto token in the existing var
export WANDB_PROJECT=my-project
export DISABLE_WANDB_LOGGING=true
python train.py
```

All `wandb.*` calls are intercepted and redirected to Pluto without any code changes.

## What the shim does not cover

The shim forwards `wandb.log()` and its media types. A few newer Pluto features are **not** reachable through it:

| Not forwarded | Instead |
| - | - |
| **Image annotations** — `boxes` and `masks` on `wandb.Image` are dropped | Log with [`pluto.Image`](/pluto/visualizations/images#annotations), or import the run offline |
| **String metrics** — string values are stored as config, latest-wins | Use [`pluto.log`](/pluto/visualizations/lines#string-metrics) for a per-step series |

All three *do* migrate through [`pluto migrate wandb`](/pluto/exporting-wandb-runs), which reads the finished run from W\&B rather than intercepting it live.


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