# CompanionGuard-RL — 可复用经验库 **创建时间:2026-05-12** **来源:Module B + Module C 训练调试过程中积累的真实踩坑记录** --- ## 目录 1. [RTX 5090 / NCCL 通信问题](#1-rtx-5090--nccl-通信问题) 2. [HuggingFace Accelerate 多 GPU 分布式训练](#2-huggingface-accelerate-多-gpu-分布式训练) 3. [PyYAML 配置文件陷阱](#3-pyyaml-配置文件陷阱) 4. [服务器文件传输(无 rsync 环境)](#4-服务器文件传输无-rsync-环境) 5. [SSH 连接与持久会话管理](#5-ssh-连接与持久会话管理) 6. [Python 依赖与包缺失处理](#6-python-依赖与包缺失处理) 7. [分布式训练中的 Tensor 设备一致性](#7-分布式训练中的-tensor-设备一致性) 8. [DataLoader 与分布式训练的兼容](#8-dataloader-与分布式训练的兼容) 9. [离线服务器的模型加载](#9-离线服务器的模型加载) 10. [Shell 脚本跨平台问题(CRLF)](#10-shell-脚本跨平台问题crlf) 11. [Python 模块路径(PYTHONPATH)](#11-python-模块路径pythonpath) 12. [可选依赖的优雅处理(wandb 等)](#12-可选依赖的优雅处理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 内存访问越界。 ### 解决方案 ```bash # 同时禁用 SHM 和 P2P,强制 NCCL 走 socket 通信 export NCCL_SHM_DISABLE=1 export NCCL_P2P_DISABLE=1 ``` **在 accelerate launch 前设置(推荐写法):** ```bash 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 ... ``` ### 排查顺序 1. 先加 `NCCL_SHM_DISABLE=1` → 若仍崩溃 2. 再加 `NCCL_P2P_DISABLE=1` → 通常可解 3. 若仍有问题,尝试 `NCCL_DEBUG=INFO` 查看具体哪个集合通信操作出错 ### 性能影响 禁用 P2P 后 GPU 间通信走 PCIe,带宽略降,但对 batch_size=256 量级的训练影响不超过 10%。 --- ## 2. HuggingFace Accelerate 多 GPU 分布式训练 ### accelerate 路径问题 服务器有多个 conda 环境时,直接敲 `accelerate` 可能用到错误环境的版本,或报 `command not found`。 **正确做法:用 conda 环境的完整路径** ```bash # 查找正确路径 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`。 ```bash PYTHONPATH=/path/to/project accelerate launch ... ``` ### 推荐完整启动命令模板 ```bash cd /path/to/project PYTHONPATH=$(pwd) \ CUDA_VISIBLE_DEVICES=0,1,2,3 \ NCCL_SHM_DISABLE=1 \ NCCL_P2P_DISABLE=1 \ /opt/conda/envs//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 以上需要避免。 ### 解决方案 将所有科学计数法改为小数形式: ```yaml # ❌ 会被解析为字符串 lr: 1e-3 lr: 3e-4 # ✅ 正确写法 lr: 0.001 lr: 0.0003 ``` ### 快速检查 ```python import yaml cfg = yaml.safe_load(open("config.yaml")) print(type(cfg["lr"])) # 应为 ,若为 则有问题 ``` --- ## 4. 服务器文件传输(无 rsync 环境) ### 背景 - 本地 Windows,目标 Linux GPU 服务器 - 本地 WSL 无 `rsync`,PowerShell 无原生 rsync - 文件较多,直接 `scp -r` 速度慢且不方便增量同步 ### 推荐方案:tar 打包 + scp 单文件传输 **本地打包(PowerShell):** ```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/ ``` **服务器解压(覆盖更新):** ```bash cd /remote/project/dir tar -xzf ../sync_v4.tar.gz --strip-components=0 ``` ### Windows 路径转 WSL 路径 ``` D:\Myresearch\... → /mnt/d/Myresearch/... ``` ### sshpass 在 WSL 中使用 ```bash # 安装 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:** ```bash # 创建新会话并启动训练 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 ``` 可能原因及处理: 1. **sshd 崩溃/重启中** → 通过网页控制台(VNC)执行 `systemctl restart sshd` 2. **MaxStartups 限制** → sshd_config 中 `MaxStartups 10:30:60` 可临时调高 3. **fail2ban 封 IP** → `fail2ban-client status sshd`,`fail2ban-client set sshd unbanip ` --- ## 6. Python 依赖与包缺失处理 ### 服务器无网络时安装包 **方法一:从已有 conda 环境复制** ```bash # 查找其他环境中的包位置 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 # 本地下载(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 ``` ### 检查包是否可用 ```bash 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 上。 ### 修复模式 ```python 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** ```python # ❌ 若 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 守卫** ```python # ❌ 直接调用 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** ```powershell # 本地下载 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 ``` **方法二:用国内镜像(若服务器能访问)** ```bash HF_ENDPOINT=https://hf-mirror.com \ python -c "from transformers import AutoTokenizer; AutoTokenizer.from_pretrained('hfl/chinese-macbert-large')" ``` **更新配置文件:** ```yaml # 将 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:** ```powershell $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 服务器上):** ```bash sed -i 's/\r//' script.sh # 或 dos2unix script.sh ``` **验证:** ```bash 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(推荐)** ```bash PYTHONPATH=/root/path/to/project accelerate launch scripts/train.py ``` **方案二:在脚本开头动态添加** ```python 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 导入 + 功能开关 **导入部分:** ```python try: import wandb WANDB_AVAILABLE = True except ImportError: wandb = None WANDB_AVAILABLE = False ``` **使用部分:** ```python 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") ``` **配置文件:** ```yaml # 生产/受限环境 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` |