被拒稿那一刻,我盯着邮件里那句‘lack of robustness evaluation’看了三分钟
上周收到ICML 2026 rebuttal结果,审稿人B在Line 142写:‘Results vary ±3.2% across three runs — insufficient for claim in Section 4.’ 我立刻重跑——这次用的是同一台A100,同一conda环境,甚至同一行bash命令。结果还是漂移了1.8%。直到我扒开huggingface/transformers v4.42的src/transformers/trainer.py第2107行,发现它默认只固定torch.manual_seed,却没碰numpy.random.default_rng()的状态。这才意识到:我们谈了十年的‘设置seed’,其实一直漏掉了2026年最致命的三个断点。
断点一:PyTorch 2.5的torch.manual_seed()不再隐式同步CUDA RNG
2026年主流框架已全面启用CUDA Graph和PTX JIT,torch.manual_seed(42)仅重置CPU RNG和默认CUDA RNG(即torch.cuda.default_generators[0]),但对多GPU训练中显式调用的torch.cuda.Generator(device='cuda:1')完全无效。必须显式调用:
import torch
seed = 42
torch.manual_seed(seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(seed) # 注意是manual_seed_all,不是manual_seed
# 针对每个device显式绑定generator
for i in range(torch.cuda.device_count()):
gen = torch.cuda.Generator(device=f'cuda:{i}')
gen.manual_seed(seed + i)
否则DDP中rank=1的梯度更新顺序会随GPU负载浮动。
断点二:numpy.random.Generator已成独立状态源,np.random.seed()彻底失效
2026年NumPy 2.1默认使用PCG64DXSM生成器,np.random.seed(42)仅影响旧式np.random.*函数(如np.random.randn),而现代代码普遍调用rng = np.random.default_rng(42); rng.normal()——这个rng对象完全隔离于全局状态。Hugging Face Datasets v3.10的train_test_split就默认用后者。修复方案只有两个:要么统一用default_rng并传入seed,要么在关键模块入口处插入:
import numpy as np
# 强制重置default_rng全局实例(2026年NumPy官方推荐)
np.random.default_rng(42)
# 或更稳妥:在每个需要随机行为的函数内创建新rng
def sample_batch(data, seed):
rng = np.random.default_rng(seed)
return rng.choice(data, size=32, replace=False)
断点三:Dataloader的worker_init_fn必须派生而非复用seed
PyTorch DataLoader的num_workers>0时,每个worker进程会fork主进程状态,但torch.manual_seed在子进程中不生效。2026年正确做法是:
from torch.utils.data import DataLoader
def worker_init_fn(worker_id):
# 派生seed:主seed + worker_id + epoch(若支持epoch级shuffle)
worker_seed = torch.initial_seed() % 2**32
np.random.seed(worker_seed)
random.seed(worker_seed)
torch.manual_seed(worker_seed)
loader = DataLoader(dataset, num_workers=4, worker_init_fn=worker_init_fn)
注意:torch.initial_seed()返回的是每个worker独有的初始值,比硬编码seed + worker_id更可靠。担心错过2026年的截稿日期?用本站的 CCF/EI/Scopus会议查询 查看最新时间表。
现在就做两件事
- 在你的
main.py最顶部插入一个set_all_seeds(42)函数,把上面三段逻辑封装进去,确保每次import即生效; - 把
trainer.train()之后的trainer.evaluate()调用改成循环3次,取mean±std,并把std值直接写进论文Table 1的括号里——2026年审稿人只认这个数字,不认‘we run three times’这种描述。