Merged code repo (CompanionGuard-RL) into single project-level git. Reorganized root: docs/, reference/, experiments/, tmp/active|archives/. Gitignored: data/, checkpoints/, .venv, experiment logs, tmp/archives. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
13 KiB
CompanionGuard-RL — 可复用经验库
创建时间:2026-05-12
来源:Module B + Module C 训练调试过程中积累的真实踩坑记录
目录
- RTX 5090 / NCCL 通信问题
- HuggingFace Accelerate 多 GPU 分布式训练
- PyYAML 配置文件陷阱
- 服务器文件传输(无 rsync 环境)
- SSH 连接与持久会话管理
- Python 依赖与包缺失处理
- 分布式训练中的 Tensor 设备一致性
- DataLoader 与分布式训练的兼容
- 离线服务器的模型加载
- Shell 脚本跨平台问题(CRLF)
- Python 模块路径(PYTHONPATH)
- 可选依赖的优雅处理(wandb 等)
1. RTX 5090 / NCCL 通信问题
症状
[rank0]: CUDA error: an illegal memory access was encountered
在多 GPU 训练中,某一阶段(如 BC warmup 后进入 PPO,或切换数据集后)突发崩溃,单 GPU 无此问题。
根因
RTX 5090 的 NVLink/P2P 拓扑与 NCCL 默认的共享内存(SHM)和 P2P 直连通信不兼容,导致跨 GPU 内存访问越界。
解决方案
# 同时禁用 SHM 和 P2P,强制 NCCL 走 socket 通信
export NCCL_SHM_DISABLE=1
export NCCL_P2P_DISABLE=1
在 accelerate launch 前设置(推荐写法):
CUDA_VISIBLE_DEVICES=0,1,2,3 NCCL_SHM_DISABLE=1 NCCL_P2P_DISABLE=1 \
accelerate launch --num_processes=4 --mixed_precision=bf16 \
scripts/train_xxx.py ...
排查顺序
- 先加
NCCL_SHM_DISABLE=1→ 若仍崩溃 - 再加
NCCL_P2P_DISABLE=1→ 通常可解 - 若仍有问题,尝试
NCCL_DEBUG=INFO查看具体哪个集合通信操作出错
性能影响
禁用 P2P 后 GPU 间通信走 PCIe,带宽略降,但对 batch_size=256 量级的训练影响不超过 10%。
2. HuggingFace Accelerate 多 GPU 分布式训练
accelerate 路径问题
服务器有多个 conda 环境时,直接敲 accelerate 可能用到错误环境的版本,或报 command not found。
正确做法:用 conda 环境的完整路径
# 查找正确路径
find /opt/conda/envs -name "accelerate" -type f 2>/dev/null
# 使用完整路径启动
/opt/conda/envs/dlapo-py310-cu128/bin/accelerate launch ...
PYTHONPATH 设置
使用 accelerate launch 时,各 rank 子进程不继承当前 shell 的 sys.path,自定义 src/ 包会报 ModuleNotFoundError。
PYTHONPATH=/path/to/project accelerate launch ...
推荐完整启动命令模板
cd /path/to/project
PYTHONPATH=$(pwd) \
CUDA_VISIBLE_DEVICES=0,1,2,3 \
NCCL_SHM_DISABLE=1 \
NCCL_P2P_DISABLE=1 \
/opt/conda/envs/<env>/bin/accelerate launch \
--num_processes=4 \
--mixed_precision=bf16 \
scripts/train_xxx.py \
--config configs/xxx.yaml \
> experiments/train_$(date +%Y%m%d_%H%M%S).log 2>&1 &
echo "PID: $! LOG: $LOG"
3. PyYAML 配置文件陷阱
症状
TypeError: '<=' not supported between instances of 'float' and 'str'
明明写的是数字,PyYAML 却解析成字符串。
根因
PyYAML 6.x 将科学计数法(如 1e-3、3e-4)解析为字符串,而非浮点数。
PyYAML 5.x 以下正常,6.x 以上需要避免。
解决方案
将所有科学计数法改为小数形式:
# ❌ 会被解析为字符串
lr: 1e-3
lr: 3e-4
# ✅ 正确写法
lr: 0.001
lr: 0.0003
快速检查
import yaml
cfg = yaml.safe_load(open("config.yaml"))
print(type(cfg["lr"])) # 应为 <class 'float'>,若为 <class 'str'> 则有问题
4. 服务器文件传输(无 rsync 环境)
背景
- 本地 Windows,目标 Linux GPU 服务器
- 本地 WSL 无
rsync,PowerShell 无原生 rsync - 文件较多,直接
scp -r速度慢且不方便增量同步
推荐方案:tar 打包 + scp 单文件传输
本地打包(PowerShell):
# 打包项目代码(排除数据集、checkpoint、缓存)
tar -czf sync_v4.tar.gz `
-C "D:\Myresearch\CompanionGuard-RL\code\CompanionGuard-RL" `
--exclude=".git" --exclude="__pycache__" `
--exclude="checkpoints" --exclude="experiments" `
src scripts configs requirements.txt
# 使用 WSL sshpass 上传
wsl -d Ubuntu-24.04 -- sshpass -p 'PASSWORD' scp -P PORT \
/mnt/d/Myresearch/CompanionGuard-RL/sync_v4.tar.gz \
root@HOST:/remote/path/
服务器解压(覆盖更新):
cd /remote/project/dir
tar -xzf ../sync_v4.tar.gz --strip-components=0
Windows 路径转 WSL 路径
D:\Myresearch\... → /mnt/d/Myresearch/...
sshpass 在 WSL 中使用
# 安装
sudo apt-get install sshpass
# 密码直接传参(注意在脚本中要保护密码)
sshpass -p 'PASSWORD' ssh -p PORT user@host 'command'
sshpass -p 'PASSWORD' scp -P PORT local_file user@host:/remote/path/
5. SSH 连接与持久会话管理
nohup vs tmux
| 方式 | 优点 | 缺点 |
|---|---|---|
nohup ... & |
简单 | 非交互式 SSH 中 nohup 进程在连接断开后有时会收到 SIGHUP 而退出;无法重新 attach 查看输出 |
tmux |
会话持久,可 attach/detach,输出可随时查看 | 需要服务器安装 tmux |
推荐用 tmux:
# 创建新会话并启动训练
tmux new-session -d -s train 'PYTHONPATH=... accelerate launch ...'
# 查看所有会话
tmux ls
# 重新连接查看输出
tmux attach -t train
# 在会话中执行命令(不 attach)
tmux send-keys -t train 'tail -f experiments/latest.log' Enter
SSH 连接被拒绝但 ping 通(kex_exchange_identification)
症状:TCP 端口开放,ping 通,但 SSH 在握手前被关闭:
kex_exchange_identification: Connection closed by remote host
可能原因及处理:
- sshd 崩溃/重启中 → 通过网页控制台(VNC)执行
systemctl restart sshd - MaxStartups 限制 → sshd_config 中
MaxStartups 10:30:60可临时调高 - fail2ban 封 IP →
fail2ban-client status sshd,fail2ban-client set sshd unbanip <IP>
6. Python 依赖与包缺失处理
服务器无网络时安装包
方法一:从已有 conda 环境复制
# 查找其他环境中的包位置
find /opt/conda/envs -name "gymnasium" -type d 2>/dev/null
# 直接复制到目标环境
cp -r /opt/conda/envs/other-env/lib/python3.10/site-packages/gymnasium \
/opt/conda/envs/target-env/lib/python3.10/site-packages/
方法二:本地下载 wheel,scp 传输,离线安装
# 本地下载(PowerShell)
pip download -d D:\wheels --platform linux_x86_64 --python-version 310 \
--only-binary=:all: gymnasium
# scp 传到服务器后:
pip install --no-index --find-links=/path/to/wheels gymnasium
检查包是否可用
python -c "import gymnasium; print(gymnasium.__version__)"
python -c "import torch; print(torch.cuda.device_count())"
7. 分布式训练中的 Tensor 设备一致性
症状
RuntimeError: No backend type associated with device type cpu
在 torch.distributed.broadcast() 等集合通信操作中,传入了 CPU tensor。
根因
NCCL 后端只支持 CUDA tensor,所有参与 broadcast/all_reduce/gather 的 tensor 必须在 GPU 上。
修复模式
dev = accelerator.device # 当前 rank 的 CUDA device
# 广播 size
size_tensor = torch.tensor([data.shape[0]], dtype=torch.long, device=dev)
torch.distributed.broadcast(size_tensor, src=0)
n = size_tensor.item()
# 广播数据
if accelerator.is_main_process:
data = data.to(dev)
else:
data = torch.zeros(n, data_dim, device=dev) # 必须在 GPU 上
torch.distributed.broadcast(data, src=0)
# 使用后如需 CPU,再 .cpu()
关键原则
- 集合通信(broadcast/all_reduce/scatter)→ 必须 CUDA tensor
- DataLoader 输入 → CPU tensor(除非
pin_memory=False) - 在 GPU 计算完成后,如需放入 CPU DataLoader,显式
.cpu()
8. DataLoader 与分布式训练的兼容
pin_memory 陷阱
RuntimeError: cannot pin torch.cuda.FloatTensor
DataLoader(pin_memory=True) 要求数据必须是 CPU tensor,若传入已在 GPU 上的 tensor 则报错。
修复:构建 TensorDataset 前先移到 CPU
# ❌ 若 obs_tensor 在 GPU 上会崩溃
dataset = TensorDataset(obs_tensor, action_tensor)
loader = DataLoader(dataset, pin_memory=True)
# ✅ 先 .cpu()
dataset = TensorDataset(obs_tensor.cpu(), action_tensor.cpu())
loader = DataLoader(dataset, pin_memory=True)
set_epoch 守卫
AttributeError: 'SequentialSampler' object has no attribute 'set_epoch'
set_epoch 只有 DistributedSampler 有,SequentialSampler 没有。
修复:加 hasattr 守卫
# ❌ 直接调用
loader.sampler.set_epoch(epoch)
# ✅ 安全写法
if hasattr(loader.sampler, "set_epoch"):
loader.sampler.set_epoch(epoch)
9. 离线服务器的模型加载
症状
OSError: Can't load tokenizer for 'hfl/chinese-macbert-large'.
服务器无法访问 HuggingFace,在线下载失败。
解决方案
方法一:本地下载后 scp
# 本地下载
python -c "
from huggingface_hub import snapshot_download
snapshot_download('hfl/chinese-macbert-large', local_dir='D:/models/macbert-large')
"
# 上传到服务器
scp -P PORT -r D:\models\macbert-large root@HOST:/remote/models/macbert-large
方法二:用国内镜像(若服务器能访问)
HF_ENDPOINT=https://hf-mirror.com \
python -c "from transformers import AutoTokenizer; AutoTokenizer.from_pretrained('hfl/chinese-macbert-large')"
更新配置文件:
# 将 HuggingFace model id 改为本地绝对路径
model_name: "/root/path/to/macbert-large"
10. Shell 脚本跨平台问题(CRLF)
症状
/bin/bash^M: bad interpreter: No such file or directory
或脚本执行后立即退出,没有任何错误信息。
根因
Windows 上编辑/保存的 .sh 文件使用 CRLF(\r\n)换行,Linux 只认 LF(\n),^M(即 \r)被当作命令的一部分。
修复方案
PowerShell 写入时强制 LF:
$content = @'
#!/bin/bash
cd /project/dir
ACCEL=/path/to/accelerate
nohup $ACCEL launch ... > log.txt 2>&1 &
echo "PID: $!"
'@
# 关键:用 Replace 去掉 \r,用 UTF8NoBOM 编码
[System.IO.File]::WriteAllText(
"D:\path\to\script.sh",
$content.Replace("`r`n", "`n"),
[System.Text.UTF8Encoding]::new($false)
)
事后修复(在 Linux 服务器上):
sed -i 's/\r//' script.sh
# 或
dos2unix script.sh
验证:
file script.sh # 应显示 "ASCII text" 而非 "CRLF line terminators"
11. Python 模块路径(PYTHONPATH)
症状
ModuleNotFoundError: No module named 'src'
项目结构是 src/models/,但脚本中 from src.models import ... 找不到。
根因
accelerate launch / torchrun 启动的子进程工作目录不一定是项目根目录,sys.path 不包含项目根目录。
解决方案
方案一:启动时设置 PYTHONPATH(推荐)
PYTHONPATH=/root/path/to/project accelerate launch scripts/train.py
方案二:在脚本开头动态添加
import sys, os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
方案三:项目根目录加 __init__.py(不推荐,污染命名空间)
12. 可选依赖的优雅处理(wandb 等)
背景
wandb 有复杂的依赖树(sentry-sdk、setproctitle 等),在受限环境中难以安装。
推荐模式:try/except 导入 + 功能开关
导入部分:
try:
import wandb
WANDB_AVAILABLE = True
except ImportError:
wandb = None
WANDB_AVAILABLE = False
使用部分:
if use_wandb and WANDB_AVAILABLE:
wandb.log({"loss": loss})
elif use_wandb and not WANDB_AVAILABLE:
if step == 0:
print("[WARN] wandb not available, skipping logging")
配置文件:
# 生产/受限环境
use_wandb: false
# 开发环境
use_wandb: true
这样即使 wandb 未安装,训练也能正常运行,不会因为一行 import wandb 而整个崩溃。
附:本项目服务器快速参考
| 项目 | 值 |
|---|---|
| SSH | ssh -p 22657 root@connected.svt.net.cn |
| 备用 SSH | ssh -p 20083 root@10.82.3.180 |
| 密码 | yx123456 |
| conda 环境 | dlapo-py310-cu128 |
| accelerate 路径 | /opt/conda/envs/dlapo-py310-cu128/bin/accelerate |
| 项目目录 | /root/siton-data-2849d4ce327c4ccfb233ce33868fe7fe/zsy/CompanionGuard-RL |
| MacBERT 本地路径 | /root/siton-data-2849d4ce327c4ccfb233ce33868fe7fe/zsy/macbert-large |