Python · experiment tracking · SDK 0.28.x · July 2026
wandb
Weights & Biases — weightsandbiases · 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 · envthe run objectconfig · hyperparameterslogging — the coremedia · artifacts · APIsweeps · tuninggotcha · 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
the whole library in fourteen lines
importwandbwandb.login() # reads WANDB_API_KEY / ~/.netrcwithwandb.init( # with-block == auto finish(), andproject="my-project", # marks the run FAILED on exceptionconfig={"lr": 3e-4, "epochs": 10},
tags=["baseline"]) as run:
run.watch(model, log="all", log_freq=100) # PyTorch grads + paramsfor epoch inrange(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.
Singularity: prefix any of these with SINGULARITYENV_.
04
Start a run
★
withwandb.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 nextlog() 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=offline → wandb 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_iterormax_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.
defmain(): withwandb.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.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.
fromlightning.pytorch.loggersimport 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.
fromwandb.integration.kerasimport 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.
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.workflowsremoved 0.24.0Its log_model/use_model/link_model became Run.log_artifact / use_artifact / link_artifact.
wandb.plots → wandb.plot0.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 autologremoved 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.
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.
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.
Anatomy of a sweep
One config, one cloud controller, N agents anywhere. Hyperband is the part that saves you money.
Worth memorizing
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.
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.
config = inputs, history = the curve, summary = the headline. If you can name the bucket, you can find the call.
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.
One slash makes a section."train/loss" groups; "a/b/c" still lands in section a.
Summary defaults to the LAST value, not the best. define_metric("val/acc", summary="max") is the one line that fixes every leaderboard.
Log related metrics in one call. Separate log() calls create separate steps with holes in them; commit=False merges them.
Config keys can't contain ., values cap at 10 MB, and reassigning a key raises unless allow_val_change=True.
use_artifact is what draws lineage. Downloading a file by hand gets you the data and none of the graph.
artifact.wait() after log_artifact() before anything that assumes the upload finished. It's asynchronous.
Pin :v3, don't ship :latest. Aliases move; version numbers don't.
Sweep project must equal run project, and the training function takes no arguments — the agent injects hyperparameters via run.config.
Learning rates want log_uniform_values. A plain uniform range spends almost every trial on the useless end.
Set run_cap. Random and Bayesian sweeps run forever; grid over a continuous range never terminates at all.
Wrap wandb.agent() in if __name__ == "__main__": whenever multiprocessing is in play, or workers re-import and re-spawn.
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.
Bounding boxes are fractional by default. Pass "domain": "pixel" or everything piles into one corner.
Keras WandbCallback was removed in 0.27.1, even though the docs page still lists it. Use WandbMetricsLogger + WandbModelCheckpoint.
offline records and needs wandb sync; disabled is a no-op. Keep the run folder or offline data is lost.
Python 3.10+ and pydantic ≥ 2.6 since 0.27.0, and SDK 0.28.0 needs W&B Server ≥ 0.65.0.