第一次接触WandB的时候,我一度觉得它才是整个训练流程里最难伺候的环节。模型还没开始收敛,WandB先抛一个Not logged in;等你登录完了,训练跑了六个小时,它又跳出一串ConnectionError,所有指标全都停在本地没同步。这种报错本身不算复杂,但因为它总出现在训练已经开始之后,排查时间全都被浪费在等待重跑上。
这篇文章是我拿WandB当主力实验跟踪工具这两年沉淀下来的排错笔记,把高频报错按训练流程的生命周期归类:登录认证、脚本API调用、结果同步、分布式训练、版本环境冲突。每个环节我都会先讲报错背后的机制,再给可以直接照做的排查步骤。无论你是刚把wandb加进训练脚本的研究生,还是在集群上维护多卡训练任务的工程师,这套方法应该都能帮你省下不少冤枉时间。
1. 登录认证阶段的报错:API Key失效远比你想的更频繁
1.1 "Not logged in"到底丢的是什么
先看最常见的一种:在服务器上跑训练,代码执行到wandb.init()附近,日志里突然出现wandb: Not logged in,然后弹出一个登录链接。问题是服务器根本没有浏览器,你复制链接到本地登录,再回到终端时可能已经超时了。
这个报错的本质是客户端找不到合法的认证凭据。WandB客户端默认会把登录凭据写进用户目录下的.netrc文件,同时把一些本地配置放在~/.config/wandb。只要你换了登录用户、重建了容器、或者home目录被清理过,这些文件就没了,Not logged in自然出现。
排查方式很简单,先确认当前登录状态:
bash复制wandb login --verify
cat ~/.netrc | grep -i machine
ls -la ~/.config/wandb/
--verify会直接告诉你是不是已经登录;~/.netrc里能看到w&b的认证条目;~/.config/wandb目录是否存在则能判断配置是否完整。实测下来,这三条命令能在30秒内定位90%的登录类问题。
1.2 用环境变量注入API Key,而不是依赖交互式登录
既然交互式登录在服务器场景下很难用,最稳妥的办法是把API Key直接作为环境变量注入。WandB客户端初始化时能找到WANDB_API_KEY,就跳过交互式登录流程。
bash复制export WANDB_API_KEY=你的key
这里有个细节容易踩坑:如果你的key里包含特殊字符,某些shell会把空格或符号截断,所以建议在.bashrc或提交脚本里用单引号包裹:
bash复制export WANDB_API_KEY='你的key'
另外,WANDB_API_KEY的优先级高于.netrc文件。如果你之前登录过,后来又手动改过key,以环境变量为准。这一点在多人共用的GPU服务器上特别重要——别人登录过的凭据不影响你的训练,你也不会误用自己的key污染别人的实验。
提示:在CI流水线或集群调度任务里,直接把key写进代码或SQLite文件里是个坏习惯。正确做法是放环境变量或密钥管理系统,这样即使日志输出也不会泄露完整凭据。
1.3 无外网环境与自建服务场景下的认证问题
如果你的训练集群无法直接访问云端服务端,或者你们团队用的是私有化部署的W&B服务,那报错会变成AuthenticationError或Failed to authenticate。这两个报错比Not logged in更有迷惑性,因为它可能不是key的问题,而是客户端连错了服务器。
排查时先确认客户端实际访问的服务地址:
bash复制wandb status
如果输出里显示的是https://api.wandb.ai而你其实应该访问团队自建服务,就需要设置:
bash复制export WANDB_BASE_URL=http://你的服务地址:8090
自建服务场景下,WANDB_BASE_URL和WANDB_API_KEY必须是一对匹配的凭据。很多人只改了base_url,key还是云端账号的,结果报了AuthenticationError,很容易误判。
我把认证阶段常见的状态整理成一个表,方便对照:
| 报错关键词 | 实际含义 | 处理方式 |
|---|---|---|
Not logged in |
本地没有有效凭据 | 设置WANDB_API_KEY或执行wandb login |
AuthenticationError |
有凭据但key无效 | 重新生成key,核对服务地址 |
Failed to authenticate |
自建服务地址或凭据不匹配 | 检查WANDB_BASE_URL和key是否对应 |
Network error (ConnectionError) |
客户端无法到达服务端 | 检查连通性或提前使用离线模式 |
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 训练脚本里的API调用报错:大部分是把wandb当普通日志工具用了
解决了登录问题,下一步的报错基本都出在wandb.log()、wandb.config、run.summary这些API使用上。这类错误的特点是代码本身能跑,但日志写到一半突然抛异常,而且异常信息看起来跟WandB毫无关系。
2.1 Object of type XXX is not JSON serializable
训练到第100步,终端突然刷出一行:
code复制TypeError: Object of type LRScheduler is not JSON serializable
原因很简单:wandb.log()接收的字典会被序列化后写入本地事件文件。凡是JSON无法序列化的对象,都不能直接往里传。最容易暴雷的是这几类:
torch.optim.lr_scheduler对象- 自定义类实例
- GPU上的
torch.Tensor(部分版本能处理,但跨版本行为不一致) numpy.ndarray(大数组虽能处理,但会拖慢写入速度)
我现在的习惯是,凡是进wandb.log()的值,统一转成Python基本类型:
python复制import wandb
wandb.log({
"train/loss": loss.item(),
"train/lr": scheduler.get_last_lr()[0],
"train/acc": acc.cpu().item(),
})
这个习惯看起来繁琐,但它能让你在切换WandB版本、换机器、换GPU驱动之后少很多莫名其妙的报错。很多此类问题不一定每次都炸,偶尔是攒了很久的曲线图某一天突然不更新了,那大概率就是某一个非法值把本地事件文件写坏了。
2.2 想记录图片却忘了包一层wandb.Image
很多做CV的朋友习惯直接wandb.log({"predictions": img})。这个写法不一定会报错,但图表里往往看不到图像,或者显示一片空白,甚至在某些版本里抛TypeError: image must be PIL image or numpy array。
正确做法是包一层:
python复制image = wandb.Image(img_array, caption="val_sample")
wandb.log({"val/predictions": image})
同理,音频要包wandb.Audio,视频要包wandb.Video。核心原因是WandB需要知道这个值的具体类型,才能在后台上渲染成对应的预览组件。你不包,它就只能当普通数组处理,结果就是上传了很大体积的原始数据,UI上却什么都没显示。
2.3 checkpoint保存时误把wandb对象写进去
这个坑比较隐蔽。有些代码会在保存checkpoint时图省事:
python复制torch.save({
"model": model.state_dict(),
"config": wandb.config,
}, "checkpoint.pth")
wandb.config本身是Config对象,不是普通字典。不同版本序列化行为不同,轻则所有配置项在加载后取不到值,重则直接抛pickle相关异常。更麻烦的是,如果这个checkpoint被当成artifact上传,问题会延迟到同步阶段才暴露。
建议改成:
python复制torch.save({
"model": model.state_dict(),
"config": dict(wandb.config),
}, "checkpoint.pth")
显式转成普通字典,加载时就不会出幺蛾子。
2.4 summary和step在不同SDK版本下的差异
老项目里很常见的一个写法是wandb.run.summary["best_acc"] = best_acc,这个API本身没问题,但如果你把best_acc设成了GPU上的tensor,某些SDK版本会在进程退出时尝试序列化summary,然后报错。赋值前先.item()转float,永远是最稳的做法。
step参数也值得注意。wandb.log({"loss": loss}, step=global_step)这个写法在0.15和0.17系列里的表现不完全一样,如果你发现画出来的曲线横轴不对,先确认SDK版本,再查ChangeLog。大多数情况下,把SDK固定到同一个版本,这类问题会自然消失。
3. 同步上传阶段的Connection Error与卡死:断点续传的完整排查链路
3.1 自动进入offline模式,不等于你的指标丢了
训练跑到一半,日志里出现:
code复制wandb: Network error (ConnectionError), entering wandb offline mode.
这行字让很多人以为实验白跑了。实际上WandB会把所有指标先写到本地./wandb目录下,生成类似offline-run-20240101_123456-abcdef的目录,等网络恢复后可以手动补传。
这个机制的本质是把"网络不可达"从致命错误降级成延迟同步。所以看到这行日志,第一反应不应该是终止训练,而是检查两件事:本地磁盘是否还有空间、离线目录是否正常写入。
bash复制df -h .
find . -name "offline-run-*" -type d
如果离线目录存在且文件在更新,训练数据就是安全的,安心等训练结束再做同步。
3.2 同步卡住时的定位思路
训练结束后执行wandb sync,最怕的是长时间卡在Uploading...。我的排查顺序是这样的:
- 确认服务端可访问:如果用的是官方云端,在浏览器打开官方控制台;如果是自建服务,
curl一下服务地址看是否响应。 - 开详细日志观察卡在哪个环节:
wandb sync --verbose。verbose模式下能看到当前在同步哪个run、哪个文件。 - 检查磁盘空间和文件大小。
df -h确认本地缓存目录没写满,同时查一下./wandb目录里是不是有体积特别大的文件。
有一条实操经验:如果你在wandb.log()里传过大对象(比如包含了完整DataFrame或者说大列表的指标),本地事件文件会变得很大,同步时自然慢。这时候再回头去改代码已经来不及,只能等它同步完。
3.3 用Artifacts管理大文件,别什么都往日志里塞
wandb.save()和Artifacts是两个功能。wandb.save()适合记录少量日志附属文件;重量级的模型权重、数据集特征文件,更适合走Artifacts。
python复制artifact = wandb.Artifact("model-ckpt", type="model")
artifact.add_file("best_model.pth", name="best_model.pth")
run.log_artifact(artifact)
Artifacts有内容寻址和去重机制,同一个文件重复上传不会重复消耗带宽。更重要的是,Artifacts的上传进度是独立的,你可以在UI的Artifacts面板里单独看到它同步到了多少,排查起来比混在普通日志里清晰得多。
我自己处理过最典型的一次同步问题,就是训练结束后有几百MB的视频文件反复卡在上传。用--verbose定位到具体文件后,我放弃了对这个大文件的同步,改用Artifacts单独传,其他指标正常的run先完成同步,整个排查过程从"完全卡死"变成"部分成功+重点处理",效率差了很多。
3.4 一个从"上传超时"到"正常同步"的完整实例
有一次,一个在受限网络上跑的训练任务结束后,我收集了./wandb目录,准备在有网络环境的机器上补同步。执行wandb sync后前两个run都正常,第三个一直卡着。
我按上面的步骤排查:
- 服务端正常;
- verbose显示卡在某个视频文件上;
- 这个文件是
wandb.log()里直接塞进去的视频帧数组,体积非常大; - 我没有改代码环境,直接用Artifacts把这个文件单独上传,其他日志文件正常同步。
整个过程大概花了20分钟。如果一开始没有用verbose定位具体文件,我可能还在傻等那个run同步完。
提示:
wandb sync支持指定目录,你可以只同步某个offline-run:wandb sync ./wandb/offline-run-xxx。这样单个大文件出错不会阻塞其他run。
4. 分布式训练环境下的wandb冲突:每个rank都初始化是最隐蔽的坑
多卡训练是WandB报错的重灾区,但大部分人遇到问题时去搜报错信息,往往搜不到直接答案,因为问题根本不在报错信息本身。
4.1 DDP场景下重复init产生的多个run
如果你用torch.distributed.launch或torchrun启动训练,并且在主函数里无脑写:
python复制wandb.init(project="my-project")
你会发现Web UI里冒出来好几个同名run,每个对应一个GPU进程,指标四分五裂。因为DDP会让每个进程都执行一遍main函数里的代码,每个进程都调用了init。
正确的做法是按rank区分:
python复制import torch.distributed as dist
if dist.get_rank() == 0:
run = wandb.init(project="my-project", config=vars(args))
else:
run = wandb.init(mode="disabled")
这里用了mode="disabled",它不是"不初始化",而是把WandB的API调用全部变成空操作。好处是代码里不需要加大量if dist.get_rank() == 0判断,非主进程遇到wandb.log也不会报错,只是什么都不做。
4.2 fork子进程时遇到的W&B service启动失败
在PyTorch的DDP之外,还有一种常见场景:用multiprocessing写数据加载或后处理逻辑,父进程里已经init了wandb,再fork出子进程。某些SDK版本会出现:
code复制wandb: ERROR W&B process failed to launch
原因在于WandB的后台服务线程是在父进程里启动的,fork之后子进程继承了这个状态,但服务的通信文件描述符可能已经失效。此时子进程里再调用WandB API,就会碰到各种奇怪的异常。
解决思路分成三个层级:
- 能不fork尽量不fork,或者确保子进程不调用WandB API;
- 如果子进程确实需要独立记录指标,在子进程内部重新
wandb.init(),结束时run.finish(); - 部分新版SDK支持service模式,把WandB逻辑放到独立后台服务中,对子进程更友好。遇到这类问题可以优先查SDK文档中关于service mode的说明。
4.3 非主进程该怎么记录指标
有些场景下你不只主进程想看指标,比如需要记录每个GPU卡上的loss分布。我的建议是在这些进程里记录到独立run,或者干脆写入自己的日志文件,训练结束后再通过离线模式补转。不要在一个进程里反复wandb.init()和wandb.finish(),那样产生的run会很碎片化,反而不利于实验对比。
另外,分布式场景下还有一个环境变量值得记住:
bash复制export WANDB_SILENT=true
它只屏蔽WandB的日志输出,不影响功能。把非主进程的WandB日志关掉之后,训练日志会清爽很多,也能减少很多人为排查干扰。
5. 版本与环境的隐性冲突:改了机器、换了conda环境就报错
5.1 排查版本三连:which、version、file
有时候同一个项目在自己电脑跑得好好的,换到另一台服务器就报AttributeError: module 'wandb' has no attribute 'init',或者某个参数突然不认识了。这种问题大概率是环境里的WandB不是你以为的那个WandB。
排查顺序固定三连:
bash复制which wandb
python -c "import wandb; print(wandb.__version__, wandb.__file__)"
pip show wandb
which wandb告诉你命令行对象在哪;wandb.__file__告诉你Python导入的模块真实路径;pip show展示包元数据。如果__file__指向某个乱七八糟的路径,比如不是当前conda环境的site-packages,那你遇到的问题就是Python环境串了。
一个很容易踩的坑是:在conda环境里用pip install wandb装了一个版本,但之前有旧的.egg-link或.pth文件把导入指向了系统Python。这种情况pip uninstall wandb都未必干净,需要手动清理残留文件。
5.2 SDK版本差异带来的隐性问题
WandB版本迭代很快,API变化也频繁。老代码里常见的wandb.run.config.update(...)在新版本里依旧能用,但部分中间版本的step参数行为不一致、summary序列化规则不同。如果你在升级SDK后遇到曲线异常、图像不显示、同步卡死这些"半报错半正常"的现象,先看版本号,再查ChangeLog。
最稳妥的做法是固定版本:
bash复制pip install wandb==0.16.3
并且在requirements.txt里写明wandb==0.16.3,不要写wandb>=0.16这种宽松范围。团队协作时,统一版本能省掉大量"我这儿能跑你那儿不能跑"的沟通成本。
5.3 后台进程残留引发的初始化失败
WandB运行时会启动一个后台服务进程负责上传和序列化。如果训练被Ctrl+C强杀或容器被直接kill,这个后台进程可能不会正常退出。下次你在同一个环境里启动新任务时,可能遇到W&B process failed to launch,或者API请求一直无响应。
检查方式:
bash复制ps aux | grep wandb
看到残留进程后,在确认当前没有其他关键任务依赖它的情况下清理:
bash复制pkill -f wandb-service
pkill -f wandb
清理完重启终端或重新打开环境,再跑一次wandb.login --verify确认状态正常。此后再跑训练基本就恢复了。
5.4 高频报错速查表
我把自己实际遇到和被同事问过的高频报错整理成一张表,方便快速定位:
| 报错信息关键词 | 出现阶段 | 常见原因 | 处理建议 |
|---|---|---|---|
Not logged in |
登录 | 本地凭据丢失 | 注入WANDB_API_KEY或执行wandb login |
Network error (ConnectionError) |
同步 | 客户端无法到达服务端 | 确认连通性,或使用离线模式 |
TypeError ... not JSON serializable |
训练日志 | 记录非基本类型对象 | 转成数值/字符串后再log |
AttributeError ... has no attribute |
环境 | SDK版本不匹配或导入路径错误 | 固定SDK版本,检查模块路径 |
W&B process failed to launch |
初始化 | 后台服务进程异常 | 检查残留进程、磁盘空间 |
Bad request (400) |
登录/上传 | key、project或base_url配置错误 | 核对服务地址和key |
Error while running hook |
训练 | 前序异常触发钩子失败 | 向上查原始异常,通常不是根因 |
最后分享一条我自己的经验。每次准备跑长时间训练前,我都会先起一个只跑几十步的smoke test,专门验证WandB的登录状态、日志写入、网络连接和磁盘空间。这个测试会暴露大量"跑了一会儿才浮现"的问题,而且成本极低。报错本身不可怕,可怕的是训练跑了一个晚上之后,你才发现WandB一条指标都没记录。把这些问题堵在正式训练之前,比事后花几个小时排查和补数据划算太多。
