Python · experiment tracking · SDK 0.28.x · July 2026

wandb

Weights & Biases — weights and biases · the name is hiding in the import

One mental model carries the whole library: a run is a record, and everything you call hangs off it. wandb.init() opens the record and returns a Run; the Run owns your config (inputs you chose), your history (one row per log() call), your summary (one final number per metric), your files & media, and your artifacts (versioned data and models). A sweep is just a controller that writes a different config into each run. Learn those five buckets and the API stops being a list of functions.

setup · CLI · env the run object config · hyperparameters logging — the core media · artifacts · API sweeps · tuning gotcha · removed ★ most common
Validated against: docs.wandb.ai — Quickstart, Python SDK reference (init/Run/functions), the official W&B SDK Python coding cheat sheet (runs · logging · artifacts), sweep configuration keys, log media & objects, log tables, environment variables, Keras integration, and the full SDK release notes through v0.28.0 (22 Jun 2026) · github.com/wandb/wandb
ONE RUN, END TO END wandb.login() once per machine wandb.init(project, config) opens the record → returns Run run.log({"loss": l}) one row of history per call run.log_artifact(model) versioned output run.finish() or exit the with-block every step / epoch — the step counter auto-increments exit code 0 → finished WHAT A RUN HOLDS — FIVE BUCKETS, ONE OBJECT Run run = wandb.init(...) sweep controller wandb.agent() writes the config run.config the inputs you chose lr · batch_size · arch written once, filterable in UI history every run.log() call one row per step → charts step must always increase run.summary one final number / metric last value, or min/max this is what run tables sort on files & media Image · Video · Audio · Table Html · Molecule · Object3D run.save() · run.log_code() artifacts versioned datasets & models :v0 :v1 … :latest :best carry lineage between runs
the whole library in fourteen lines
import wandb

wandb.login()                                          # reads WANDB_API_KEY / ~/.netrc

with wandb.init(                                       # with-block == auto finish(), and
        project="my-project",                            # marks the run FAILED on exception
        config={"lr": 3e-4, "epochs": 10},
        tags=["baseline"]) as run:

    run.watch(model, log="all", log_freq=100)          # PyTorch grads + params

    for epoch in range(run.config.epochs):            # config is attribute-accessible
        loss, acc = train_one_epoch()
        run.log({"train/loss": loss, "val/acc": acc})  # "/" makes UI sections

    run.summary["best_acc"] = best                       # the number the run table sorts on
    run.log_artifact("./ckpt.pt", type="model")          # versioned, with lineage
Part I

Setup & the Run object

everything starts and ends here
01

Install & authenticate

  • pip install wandbCore SDK. Python 3.10+ since 0.27.0 (3.9 dropped there, 3.8 in 0.25.0). Requires pydantic ≥ 2.6.
  • pip install wandb[media]Extra deps for images, video, audio, molecules. Other extras: [aws], [azure], [gcp], [launch], [sweeps].
  • wandb loginPrompts for the key, stores it in ~/.netrc. Do this once per machine.
  • wandb.login()Same, from Python / a notebook. Optional key=, relogin=True, host= for self-managed.
  • export WANDB_API_KEY=…The way to do it in CI, Docker, Slurm, SageMaker. The key is shown only once at creation — store it in a secret manager.
  • wandb login --host https://wandb.acme.comPoint at a Dedicated Cloud or self-managed server. Same as WANDB_BASE_URL.
02

The CLI

  • wandb sync ./wandb/offline-run-…Upload a run recorded offline. The whole point of WANDB_MODE=offline.
  • wandb offline / wandb onlineFlip the default mode for this directory. wandb disabled / enabled turn the SDK into no-ops.
  • wandb sweep sweep.yamlRegister a sweep from YAML; prints the sweep ID.
  • wandb agent entity/project/SWEEP_IDStart a worker. Run it on N machines to parallelise. --count N caps runs per agent.
  • wandb artifact put ./data -n dataset -t datasetAlso artifact get, artifact ls, artifact cache cleanup 1GB.
  • wandb leetTerminal UI for browsing runs, live charts and system metrics. GA in 0.28.0 — was wandb beta leet.0.28
  • wandb status · wandb verifyShow local settings; run a connectivity/permissions self-test against the server.
  • wandb purge-cacheClean cached artifact files. The cache defaults to ~/.cache/wandb and grows silently.
03

Environment variables

  • WANDB_API_KEYAuth. Required on any machine where wandb login hasn't run.
  • WANDB_PROJECT · WANDB_ENTITYDefaults for every run. Overridden by explicit init() arguments.
  • WANDB_MODE=offline|disabledoffline = record locally, sync later. disabled = every call is a no-op (use in unit tests).
  • WANDB_RUN_ID · WANDB_RESUMEThe pair that makes preemptible jobs resumable. WANDB_RESUME=auto + a stable ID survives a crash.
  • WANDB_NAME · WANDB_NOTES · WANDB_TAGSDisplay name, a markdown description, and a comma-separated tag list.
  • WANDB_RUN_GROUP · WANDB_JOB_TYPEGrouping without touching code — useful for launcher scripts and DDP.
  • WANDB_DIR · WANDB_ARTIFACT_DIR · WANDB_CACHE_DIRThree different directories: generated files, downloaded artifacts, and the dedup cache.
  • WANDB_SILENT=true · WANDB_QUIET=trueSilence the banner. Logs still go to $WANDB_DIR/debug.log.
  • WANDB_DISABLE_CODE · WANDB_DISABLE_GITStop W&B saving notebooks / git diffs. Worth knowing for private repos.
  • Singularity: prefix any of these with SINGULARITYENV_.
04

Start a run

  • with wandb.init(project="p") as run:The recommended form. Exiting the block calls finish(); an uncaught exception marks the run failed instead of leaving it "running" forever.
  • run = wandb.init(…)run.finish()The notebook-friendly form. If you never call finish() the run ends when the process exits.
  • run.log({"acc": 0.9})Log through the run object. The current reference documents run methods, not module-level wandb.log() — see card 24.
  • run.finish(exit_code=1)Non-zero exit code → run state Failed. States: Running · Finished · Failed · Crashed · Killed.
  • Run IDs may not contain / \ # ? % : and are immutable once a run is deleted.
05

wandb.init() parameters

  • project= · entity=Where it lands. Entity = user or team and must already exist. No project → inferred from git root, else "uncategorized".
  • config={…}dict, argparse.Namespace, absl FLAGS, or a path to a YAML file. Also config_include_keys / config_exclude_keys.
  • name= · id= · notes=Three different things. name = display label (auto two-word if omitted); id = unique key used for resuming; notes = markdown commit-message.
  • tags=["baseline"] · group= · job_type=Filterable labels, an experiment bucket, and a role within it ("train" / "eval").
  • mode="online"|"offline"|"disabled"|"shared"shared lets several processes on several machines write to one run — still experimental.
  • save_code=TrueSnapshot the script/notebook so runs are diffable in the UI. Off by default.
  • sync_tensorboard=TrueMirror TensorBoard/TensorBoardX event files into W&B. (tensorboard= is the deprecated spelling.)
  • dir= · settings=wandb.Settings(…)Where metadata is written, and the escape hatch for every advanced knob (quiet, finish_timeout, capture_loggers…).
  • force=TrueRefuse to run unless logged in, instead of silently falling back to offline.
06

The Run object

  • run.id · run.name · run.pathpath is entity/project/run_id — the string every Public API call wants.
  • run.url · run.project_url · run.sweep_urlProperties, not methods. Offline runs have no URL.
  • run.config · run.summaryThe two dict-likes. Inputs vs. outputs.
  • run.stepThe step the next log() will use. Raises in mode="shared".
  • run.resumed · run.offline · run.disabledBooleans worth branching on — e.g. only reload a checkpoint if run.resumed.
  • run.tags += ("new-tag",)tags is a tuple, so you must reassign, and the trailing comma matters.
  • run.alert(title=, text=, level="WARN")Slack/email from inside the loop. Title < 64 chars; wait_duration throttles repeats.
  • run.save("out/*.txt", policy="live")Sync loose files. live re-uploads on change, now once, end at finish. Globs expand at call time — new files aren't picked up.
  • run.log_code()Saves every .py under the cwd as a code artifact. Customise with include_fn / exclude_fn.
  • run.mark_preempting()Tell the server this run is about to be pre-empted, so a sweep can requeue it rather than count it failed.
  • run.write_logs(text)Write straight to the Logs tab instead of relying on stdout capture.0.27
07

Resume, fork, rewind

  • init(id=rid, resume="allow")allow resume if it exists else create · must error if missing · never error if present · auto resume only if it crashed here.
  • resume_from=f"{rid}?_step=200"Rewind: truncate history at step 200 and carry on in the same run. Fixes a bad logging bug without losing the run.
  • fork_from=f"{rid}?_step=200"Fork: branch a new run off step 200. Same syntax, different intent — try a second LR schedule from a shared warmup.
  • Pick exactly one. resume, resume_from and fork_from are mutually exclusive.
  • Resuming replaces the tag list. To add instead, do run.tags += (…,) after init.
  • reinit="create_new"Start a second run without finishing the first. Others: finish_previous, return_previous, default.
08

Organising many runs

  • init(group="cv-fold-sweep")Collapses related runs into one row in the UI, with mean±band charts. Made for cross-validation and distributed training.
  • init(job_type="eval")The role inside a group: preprocess → train → eval. Filterable on its own.
  • init(tags=["v2", "ablation"])Free-form labels, editable later in the UI. Use tags for questions, groups for structure.
  • Distributed training, two patterns: one run per process (each with its own id, shared group), or one run for all processes via mode="shared" and a common run ID.
  • if rank == 0: run = wandb.init(…)The simplest DDP recipe — only rank 0 logs. Avoids N duplicate runs per job.
  • run.pin_config_keys(["lr", "arch"])Pin key hyperparameters to the top of the run Overview page.0.26
Part II

Config & logging

inputs in, numbers and pictures out
09

Config — your hyperparameters

  • init(config={"lr": 0.01, "bs": 32})The normal way. These become the columns you group, filter and sort runs by.
  • run.config.lr · run.config["lr"]Both work. Attribute access is what sweep code usually uses.
  • run.config.update({"n_params": n})Add derived values after the model is built — parameter counts, dataset size, resolved paths.
  • init(config=vars(args))argparse in one line. A raw Namespace or absl.flags.FLAGS also works directly.
  • init(config="config.yaml")A string config is read as a path to YAML. Handy with Hydra.
  • allow_val_change=TrueWithout it, overwriting a config value raises. Default is False in scripts, True in notebooks. For values that change during training, log them instead.
  • Two hard limits: config keys must not contain ., and each value must be under 10 MB.
  • run.config_staticA frozen snapshot — useful when you want to be sure a sweep can't mutate under you.
10

run.log() — the core call

  • run.log({"loss": 0.5, "acc": 0.9})Appends one row to history and updates the summary for those keys. Log related metrics together in one call, not one call each.
  • run.log({"train/loss": l, "val/loss": v})A / creates a UI section. Only one level counts"a/b/c" lands in section a.
  • run.log(d, commit=False)Accumulate into the current step without advancing it. The next log() without commit=False flushes them together.
  • run.log(d, step=i)Explicit step. Note the default flips: with step given, commit defaults to False; without it, True.
  • run.log({"epoch": e, "loss": l})Treat the W&B step as a timestamp, not a training step. Log epoch as an ordinary metric and use it as an x-axis.
  • The step can only go up. You cannot log to a step you've already passed.
  • Rate: not designed for more than a few calls per second. Log every N iterations or batch it.
  • run.log({"chart": fig})A Matplotlib figure is accepted directly and converted. So is plotly via wandb.Plotly.
11

Summary & define_metric

  • run.summary["best_acc"] = 0.94Set a headline number explicitly. This is what the runs table and sweep leaderboard sort on.
  • By default the summary holds the last logged value of each metric — which is rarely the best one.
  • run.define_metric("val/acc", summary="max")Make the summary track the best instead. Options: min max mean last first none.
  • run.define_metric("*", step_metric="epoch")Custom x-axis. Charts plot against your epoch instead of the internal step counter. Glob patterns are allowed.
  • define_metric("val/loss", step_metric="epoch")Per-metric axes — the fix when train logs per batch and val logs per epoch and the charts look wrong.
  • hidden=True · overwrite=TrueHide a metric from auto-generated panels; replace rather than merge earlier define_metric calls.
  • summary="best" with goal= is deprecated — say "min" or "max".deprecated
12

Images, video, audio

  • run.log({"ex": wandb.Image(arr, caption="pred")})Accepts a NumPy array, a PIL.Image, a torch tensor, or a file path. Last dim 1→grey, 3→RGB, 4→RGBA; floats are rescaled to 0–255.
  • run.log({"ex": [wandb.Image(i) for i in imgs]})A list under one key gives a scrubbable grid. Keep it under ~50 images per step or logging becomes the bottleneck.
  • Image(img, masks={"pred": {"mask_data": m, "class_labels": L}})Segmentation overlay: mask_data is a 2D int array of class IDs; class_labels maps ID→name.
  • Image(img, boxes={"pred": {"box_data": […]}})Boxes take either {minX,maxX,minY,maxY} or {middle,width,height}, plus class_id, scores, box_caption.
  • Box coordinates are fractions of the image by default. Pass "domain": "pixel" if yours are in pixels — otherwise everything collapses into a corner.
  • wandb.Video(arr, fps=4, format="gif")NumPy axes are (time, channel, height, width). Needs ffmpeg + moviepy for arrays. Formats: gif, mp4, webm, ogg.
  • wandb.Audio(arr, sample_rate=22050)Max 100 clips per step.
  • wandb.Histogram(grads, num_bins=64)Flattens and calls np.histogram. Max 512 bins. In history it renders as a heatmap over time.
  • wandb.Html(s) · Molecule · Object3DArbitrary HTML panels; 10 molecule formats plus SMILES/rdkit; point clouds and lidar (UI truncates at 300k points).
13

Tables

  • t = wandb.Table(columns=[…], data=[[…]])Row-oriented 2D data. Cells may hold numbers, strings, and any media type — that's what makes them prediction viewers.
  • wandb.Table(dataframe=df)Straight from pandas. The usual route for logging a CSV.
  • t.add_data(x, wandb.Image(img), pred)Row at a time. t.add_column(name, values) adds one the other way.
  • log_mode="IMMUTABLE"Default. Log once, cheapest, all rows render. Use it at the end of a run.
  • log_mode="MUTABLE"Re-log the same table after adding columns. Whole table is rewritten each time — slow for big ones.
  • log_mode="INCREMENTAL"Append batches of rows during training and watch them arrive. Workspaces show only the last 100 increments — the documented trick is to log an INCREMENTAL table during the run and one IMMUTABLE table at the end.
  • Default cap is 10,000 rows; raise with wandb.Table.MAX_ROWS = N.
  • A Table is stored as an artifact, not a plain metric — which is why it gets versioned and can be joined across runs.
14

Custom charts — wandb.plot

  • wandb.plot.line(table, x=, y=, title=)Every wandb.plot helper takes a Table and returns an object you pass to run.log().
  • plot.scatter · bar · histogramThe workhorses. Column names must match the x/y/value arguments exactly.
  • plot.line_series(xs=, ys=, keys=)Several lines on one axis without building a Table first.
  • plot.confusion_matrix(y_true=, preds=, class_names=)One line instead of a seaborn heatmap, and it stays interactive.
  • plot.roc_curve(y_test, y_probas, labels)Also pr_curve. Takes probability matrices, not hard labels.
  • wandb.plot_table(vega_spec, table, fields)The escape hatch: render a Table with any Vega-Lite spec you've saved as a preset.
  • It is wandb.plot — singular. wandb.plots was removed in 0.17.0.removed
15

System metrics, logs, offline

  • Free with every run: CPU%, RAM, disk, network, and per-GPU utilisation / memory / power / temperature (NVIDIA, AMD, Apple Silicon, Google TPU, AWS Trainium). No code needed.
  • In Linux containers, CPU and memory are reported against the cgroup limit, not the host total.0.27
  • stdout and stderr are captured into the run's Logs tab automatically.
  • Settings(capture_loggers={"myapp": "INFO"})Pipe named Python logging loggers into the run.0.27
  • WANDB_MODE=offlinewandb syncThe air-gapped / no-network workflow. Keep the run folder or the data is gone.
  • Settings(finish_timeout=300)Cap the wait for the final upload. Pair with finish_timeout_raises to fail loudly.0.27
  • WANDB_ENABLE_DCGM_PROFILING=trueDeep NVIDIA profiling metrics. Costs resources; needs the nvidia-dcgm service.
Part III

Artifacts, sweeps & the wider platform

versioning, tuning, integrations, querying back
16

Artifacts — create & log

  • a = wandb.Artifact(name="cifar", type="dataset")An artifact is a named, versioned bundle. type is free text; "dataset" and "model" are the conventions the UI understands.
  • a.add_file("data/train.csv") · a.add_dir("data/")Copy contents in. Files are content-addressed, so re-logging unchanged data creates no new version.
  • a.add_reference("s3://bucket/path")Reference artifact — W&B stores the URI and checksum, not the bytes. The right call for TB-scale data you already own.
  • a.metadata = {"n_rows": 50000}Arbitrary JSON travelling with the version. a.description is the human note.
  • run.log_artifact(a, aliases=["best"])Declares it an output of this run. Every version silently gets latest; pass tags= too if you want searchable labels.
  • run.log_artifact("./ckpt.pt", type="model")Shorthand — a path works instead of an Artifact object.
  • a = run.log_artifact(…); a.wait()Non-obvious: logging is asynchronous. Call wait() before anything that assumes the upload finished.
  • a.ttl = timedelta(days=30)Time-to-live → automatic deletion. The main lever on storage bills.
  • run.upsert_artifact()run.finish_artifact()Distributed writers all contribute to one version via a shared distributed_id, then one finalises it.
17

Artifacts — use & download

  • a = run.use_artifact("cifar:latest")Declares it an input. This single call is what draws the lineage graph.
  • path = a.download()Returns a local directory. Cached, so a second run on the same machine is instant.
  • a.download(path_prefix="train/")Partial download — one file or one subfolder out of a huge artifact.
  • entry = a.get_entry("labels.json")One file. For a reference artifact this hands back the external URL instead.
  • Version vs alias: name:v3 is immutable and reproducible; name:latest or name:prod is a moving pointer. Pin the version in anything you need to reproduce.
  • wandb.Api().artifact("ent/proj/name:v2")Fetch outside a run — no run is created, nothing is logged.
  • a.aliases.append("prod"); a.save()Retag an existing version. Same pattern for description, metadata, ttl — mutate, then save().
  • run.link_artifact(a, "wandb-registry-model/my-coll")Publish into the org Registry. The path prefix wandb-registry- is mandatory. Linking points at the artifact; it does not copy it.
  • run.log_model(path) · run.use_model(name)Thin model-flavoured wrappers over log/use_artifact.
18

Sweeps — the config

  • Three required keys: method, parameters, and program (the last only for YAML/CLI sweeps).
  • "method": "grid" | "random" | "bayes"grid every combination · random uninformed draws · bayes a surrogate model — good for a few continuous params, scales poorly.
  • "metric": {"name": "val/acc", "goal": "maximize"}Only bayes and early stopping read it. Default goal is minimize. Optional target stops the sweep once reached.
  • "lr": {"min": 1e-5, "max": 1e-1, "distribution": "log_uniform_values"}Use this for learning rates. Plain uniform wastes almost every draw on the large end.
  • "opt": {"values": ["adam", "sgd"]}Categorical. values = a set, value = a constant you still want recorded.
  • Inferred distributions: values→categorical · int min/max→int_uniform · float min/max→uniform · value→constant.
  • Full family: log_uniform_values, q_uniform, q_log_uniform_values, inv_log_uniform_values, normal, log_normal, q_normal… (q = quantisation step; mu/sigma for normals).
  • "early_terminate": {"type": "hyperband", "min_iter": 3}Hyperband kills laggards at bracket boundaries. Give min_iter or max_iter+s; eta defaults to 3.
  • "run_cap": 50Set it. random and bayes sweeps otherwise run until you stop them.
  • "command": ["${env}", "${interpreter}", "${program}", "${args}"]How the agent invokes your script. Variants: ${args_no_hyphens}, ${args_json}, ${args_no_boolean_flags}, ${envvar:NAME}.
19

Sweeps — running them

  • sweep_id = wandb.sweep(cfg, project="p")Registers the search space and returns an ID. Or wandb sweep sweep.yaml from the shell.
  • wandb.agent(sweep_id, function=main, count=10)The worker: ask for a config, call your function, repeat. Launch it on N machines and they coordinate through the server.
  • def main():
      with wandb.init() as run:
        train(run.config)
    The training function takes no arguments — the agent injects hyperparameters through run.config. This surprises everyone once.
  • The sweep's project and the run's project must match, or your runs vanish into a different project and the sweep never sees them.
  • if __name__ == "__main__":Mandatory around agent()/sweep() when using multiprocessing or torch.multiprocessing — otherwise workers re-import and re-spawn.
  • Grid search over a continuous range never terminates. Grid needs values/value, not min/max.
  • wandb sweep --pause | --resume | --stop | --cancel IDstop lets running runs finish; cancel kills them.
  • wandb.controller(sweep_id)Run the search/stopping logic locally instead of in the cloud.
  • Read results with a parallel coordinates panel and the parameter importance panel — the two that actually tell you which knob mattered.
20

PyTorch & framework hooks

  • run.watch(model, log="all", log_freq=100)Hooks a nn.Module for gradient and parameter histograms. log"gradients" (default), "parameters", "all", None. log_graph=True adds the graph.
  • run.unwatch(model)Removes the hooks. Worth doing before a long eval loop — log_freq=1 on a big model is genuinely slow.
  • from lightning.pytorch.loggers import WandbLoggerTrainer(logger=WandbLogger(project="p", log_model="all"))log_model pushes checkpoints as artifacts.
  • TrainingArguments(report_to="wandb")HuggingFace Transformers. Add WANDB_LOG_MODEL=checkpoint to version checkpoints; run_name= sets the display name.
  • from wandb.integration.keras import WandbMetricsLoggerPass to model.fit(callbacks=[…]). log_freq = "epoch" (default), "batch", or an int.
  • WandbModelCheckpoint("models/", save_best_only=True)Subclasses Keras' own ModelCheckpoint and uploads each save as an artifact. Use alongside WandbMetricsLogger.
  • WandbEvalCallbackAbstract base — implement add_ground_truth and add_model_predictions to get a prediction Table per epoch.
  • Also shipped: XGBoost, LightGBM, CatBoost, Ignite, PyTorch Geometric, torchtune, Accelerate, Diffusers, Hydra, YOLOX, SB3.
21

Public API — querying back

  • api = wandb.Api()A separate, read/write client for runs that already exist. Note its Run class is not the same object init() returns.
  • runs = api.runs("entity/project")Iterable of runs, oldest first. Lazy by default — config and summary load on access.
  • api.runs(p, {"config.lr": {"$lt": 0.01}})MongoDB-style filters over config.*, summary_metrics.*, tags, state, display_name.
  • run.summary · run.config · run.history()history() returns a sampled DataFrame; scan_history() streams every row.
  • run = api.run("ent/proj/run_id")Then run.tags.append(…); run.update() to edit stored data. update() is what persists it.
  • run.logged_artifacts() · run.used_artifacts()Outputs and inputs — walk these to traverse a lineage graph.
  • api.sweep(…).best_run()Straight to the winner of a sweep.
  • api.artifacts() · api.registries() · api.automations()Paginated; all accept per_page, order and a resumable start cursor.
  • Since 0.27.1 these route through wandb-core and raise WandbApiFailedError, not requests exceptions — update your except clauses.0.27
22

Beyond experiment tracking

  • Registry — org-wide model shelf. Link versions into wandb-registry-{name}/{collection}, promote with aliases, audit through lineage.
  • Reports — narrative documents mixing prose with live panels. Buildable in code via wandb_workspaces.reports.
  • Automations — trigger a Slack message or webhook on events: new artifact version, alias added, run finished, or a metric crossing a threshold / z-score.
  • Launch — package a run as a job and queue it onto Kubernetes, SageMaker, Vertex or Docker.
  • Weave — the separate weave package for LLM app tracing and evals. Different SDK, same platform; wandb.integration.weave bridges them.
  • MCP server — point an AI coding agent at your workspace data and the W&B docs.
23

Stale APIs the tutorials still teach

  • wandb.log(…)run.log(…)The single biggest drift. Nearly every blog post uses the module-level form. W&B's own global-function reference now lists only init login setup teardown sweep agent controller restore finish — everything else is a method on the Run you got back.
  • WandbCallback (Keras) removed 0.27.1Deleted from the SDK — though the docs page still describes it as "legacy". Use WandbMetricsLogger + WandbModelCheckpoint.
  • wandb.beta.workflows removed 0.24.0Its log_model/use_model/link_model became Run.log_artifact / use_artifact / link_artifact.
  • wandb.plotswandb.plot 0.17.0Same release moved wandb.keras, wandb.xgboost, wandb.fastai… under wandb.integration.*.
  • Run.plot_table()wandb.plot_table() 0.19.0
  • run.get_url()run.urlLikewise project_name()project, get_project_url()project_url, get_sweep_url()sweep_url. And run.join() is long gone — it's finish().
  • OpenAI / Cohere / LangChain autolog removed 0.27.1The old tracing shims are gone. LLM work belongs in Weave now.
  • init(anonymous=…) deprecatedAnonymous mode emits warnings since 0.23.1.
  • wandb.require("core") is unnecessary — the Go backend has been the default since 0.18.0, and "legacy-service" now raises.
24

When something goes wrong

  • Run stuck "running" forever → the process died without finish(). Use the with block; it also marks the run failed on exception.
  • "You must call wandb.init() before …" → a worker process. Under Keras use_multiprocessing=True, init inside the Sequence constructor and guard main.
  • Charts are jagged / x-axis is nonsense → you're logging train per batch and val per epoch onto one step counter. Fix with define_metric(step_metric=).
  • Config raises on assignment → you set the same key twice. That's intentional; pass allow_val_change=True or log it as a metric.
  • Training got slow → logging too often, too many images per step, or watch() with a small log_freq.
  • CUDA OOM only under a sweep → agents reuse the process; free the model and empty the cache at the end of each trial.
  • Artifact "missing" right after logging → you skipped artifact.wait().
  • Storage bill climbing → set TTLs, use reference artifacts for large data, and run wandb artifact cache cleanup.
  • Version floor: SDK 0.28.0 requires W&B Server ≥ 0.65.0. Self-managed users, check before upgrading.
  • Debug logs live at wandb/debug.log and wandb/debug-internal.log — the first place to look, and what support will ask for.

Where does a number live? config vs history vs summary

Three buckets, three lifetimes. Most confusion about W&B is really confusion about which one you wanted.

config set once, before training lr = 3e-4 bs = 32 arch = "resnet50" → these become the columns you filter, group and sort runs by history one row per run.log() call 0 2 4 6 step → monotonic summary one number per metric loss = 0.11 (last) acc = 0.94 (max) → defaults to the LAST value — use define_metric(summary="max") ​   to make it the best one. This is what leaderboards sort on.

The step counter — three ways to log the same thing

Every run.log() creates a new step by default. Two knobs change that, and one of them is why your charts look wrong.

A  default — each call is its own step run.log({"loss": l}) ; run.log({"acc": a}) step 0 loss only step 1 acc only two half-empty rows — charts look sparse and gappy B  commit=False — accumulate into one step run.log({"loss": l}, commit=False) ; run.log({"acc": a}) step 0 loss + acc together the second call (no commit=False) flushes and advances the step C  step_metric — change the x-axis itself run.define_metric("val/*", step_metric="epoch") x-axis = epoch not the internal counter the fix when train logs per batch and val logs per epoch

Artifact lineage — what use_artifact actually buys you

Logging an output and declaring an input are the two edges of a graph. W&B draws the rest.

run job_type=prep cifar:v3 type="dataset" run job_type=train log_ artifact use_ artifact log_artifact resnet:v7 type="model" latest best production aliases are movable pointers · :v7 is forever Registry collection wandb-registry-model/detector link_artifact — a pointer, not a copy

Anatomy of a sweep

One config, one cloud controller, N agents anywhere. Hyperband is the part that saves you money.

sweep config method: bayes metric: val/acc ↑ parameters: {lr, bs} controller wandb.sweep() → ID agent — laptop agent — GPU box agent — cluster node each agent asks for the next config early_terminate: hyperband — check at brackets, kill the laggards acc iterations logged min_iter=3 9 27  (eta=3) survives stopped at bracket 9 stopped at bracket 3 brackets count logged iterations, not the value of the step counter

Worth memorizing

  1. Use the context manager. with wandb.init(...) as run: finishes the run on exit and marks it failed on exception. A bare init() without finish() leaves zombie "running" runs.
  2. Log through the run, not the module. run.log(), run.config, run.summary. The module-level wandb.log() in every tutorial predates the current API surface.
  3. config = inputs, history = the curve, summary = the headline. If you can name the bucket, you can find the call.
  4. The step only goes up. You can never log to a step you've passed. Treat W&B's step like a timestamp and log epoch as a normal metric.
  5. One slash makes a section. "train/loss" groups; "a/b/c" still lands in section a.
  6. Summary defaults to the LAST value, not the best. define_metric("val/acc", summary="max") is the one line that fixes every leaderboard.
  7. Log related metrics in one call. Separate log() calls create separate steps with holes in them; commit=False merges them.
  8. Config keys can't contain ., values cap at 10 MB, and reassigning a key raises unless allow_val_change=True.
  9. use_artifact is what draws lineage. Downloading a file by hand gets you the data and none of the graph.
  10. artifact.wait() after log_artifact() before anything that assumes the upload finished. It's asynchronous.
  11. Pin :v3, don't ship :latest. Aliases move; version numbers don't.
  12. Sweep project must equal run project, and the training function takes no arguments — the agent injects hyperparameters via run.config.
  13. Learning rates want log_uniform_values. A plain uniform range spends almost every trial on the useless end.
  14. Set run_cap. Random and Bayesian sweeps run forever; grid over a continuous range never terminates at all.
  15. Wrap wandb.agent() in if __name__ == "__main__": whenever multiprocessing is in play, or workers re-import and re-spawn.
  16. Under 50 images per step, under 100 audio clips, and don't call log() more than a few times a second — logging becomes the bottleneck fast.
  17. Bounding boxes are fractional by default. Pass "domain": "pixel" or everything piles into one corner.
  18. Keras WandbCallback was removed in 0.27.1, even though the docs page still lists it. Use WandbMetricsLogger + WandbModelCheckpoint.
  19. offline records and needs wandb sync; disabled is a no-op. Keep the run folder or offline data is lost.
  20. Python 3.10+ and pydantic ≥ 2.6 since 0.27.0, and SDK 0.28.0 needs W&B Server ≥ 0.65.0.