Skip to content

复现论文与做实验 ​

为什么要先复现 ​

复现是进入研究最快的方式:你会真正理解方法的细节,会拿到一套能跑的代码和实验环境,还经常会发现论文没写的问题,这些问题往往就是你的研究起点。

选哪篇来复现 ​

进组后的第一篇通常是组内论文,三种复现方式(运行官方代码、自己写仿真器、搭真实系统)见复现组内论文。

选其他论文时,优先选满足这些条件的:

  • 和你的方向最相关,最好是组内论文或组内论文的对比对象;
  • 有官方开源代码,且仓库近一两年有人维护;
  • 实验需要的硬件你能拿到(先看论文用了几张什么卡);
  • 依赖的模型和数据集能下载到。

复现检查清单 ​

开始前

  • 读完论文第三遍,列出要复现的实验(通常是主实验的一张表或一张图)。
  • 读代码仓库的 README、Issues,看别人复现时遇到过什么问题。
  • 记下论文用的硬件、软件版本、模型、数据集、超参数。

过程中

  • 用独立的 Python 环境或 Docker 容器,记录所有依赖的版本(uv pip freeze > requirements.lock 或保存 Dockerfile),见 Python 环境。
  • 固定随机种子。
  • 先用小规模配置跑通整个流程,再跑完整实验。
  • 每次运行都记录:代码的 Git commit、配置、开始时间、结果文件位置。
  • 遇到报错和解决方法立刻记下来。

结束后

  • 把你的结果和论文的数字放在同一张表里对比。
  • 差距能解释清楚,就算复现成功。同样的硬件和设置下,准确率等指标的差距通常在几个百分点以内;硬件不同时比较加速比、相对提升,而不是绝对延迟。差距大要分析原因:硬件不同?版本不同?超参数没写全?
  • 写复现报告:环境、步骤、结果对比、差距分析、发现的问题和改进点。

从 Jupyter 到实验脚本 ​

Jupyter Notebook 适合探索和画图,但正式实验要写成脚本:脚本能放进 tmux 长时间运行、能用不同参数批量运行、能被 Git 准确记录。建议的项目结构:

text
my-experiment/
├── README.md            # 怎么安装、怎么运行
├── pyproject.toml       # 依赖(uv 管理)
├── configs/             # 实验配置
│   └── baseline.yaml
├── src/                 # 核心代码
├── scripts/
│   ├── run.py           # 运行一次实验
│   └── plot.py          # 从结果生成图
├── results/             # 原始结果(不提交到 Git)
├── figures/             # 生成的图
└── notebooks/           # 探索用的 notebook

一个实验脚本模板 ​

python
# scripts/run.py
import argparse
import json
import random
import subprocess
import time
from pathlib import Path

import numpy as np
import torch


def set_seed(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)


def git_commit():
    try:
        return subprocess.check_output(["git", "rev-parse", "--short", "HEAD"], text=True).strip()
    except Exception:
        return "unknown"


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--method", default="baseline")
    parser.add_argument("--batch-size", type=int, default=8)
    parser.add_argument("--repeats", type=int, default=3)
    parser.add_argument("--seed", type=int, default=0)
    parser.add_argument("--out", default="results")
    args = parser.parse_args()

    set_seed(args.seed)
    run_dir = Path(args.out) / f"{args.method}_bs{args.batch_size}_{time.strftime('%Y%m%d-%H%M%S')}"
    run_dir.mkdir(parents=True)

    # 保存这次运行的全部配置,之后能准确知道每个结果是怎么来的
    meta = vars(args) | {"commit": git_commit(), "gpu": torch.cuda.get_device_name(0) if torch.cuda.is_available() else "cpu"}
    (run_dir / "config.json").write_text(json.dumps(meta, indent=2, ensure_ascii=False))

    records = []
    for r in range(args.repeats):
        start = time.perf_counter()
        # ===== 在这里运行你要测量的东西 =====
        time.sleep(0.1)
        # ==================================
        records.append({"repeat": r, "latency_s": time.perf_counter() - start})

    # 保存原始数据,画图和统计都从这里读
    with open(run_dir / "records.jsonl", "w") as f:
        for rec in records:
            f.write(json.dumps(rec) + "\n")
    print(f"结果已保存到 {run_dir}")


if __name__ == "__main__":
    main()

运行:python scripts/run.py --method baseline --batch-size 16。批量跑多组参数时写一个 shell 循环,放在 tmux 里运行。

做实验的好习惯 ​

代码与配置 ​

  • 所有代码都放进 Git,每次正式实验前先 commit。
  • 超参数写在配置文件(YAML)或命令行参数里,不要改代码里的常量来调参。
  • 模型权重、数据集、实验输出不要放进 Git,在 .gitignore 里排除。

记录与结果 ​

  • 用 TensorBoard 或 Weights & Biases 记录训练曲线。
  • 原始结果(每次请求的延迟、每步的耗时)都保存成 CSV 或 JSON,画图和统计用脚本从原始数据生成。
  • 不要手工修改任何实验数字。
  • 画图脚本也放进 Git,论文里每张图都能一键重新生成。

系统实验的特殊要求 ​

  • 预热:第一次运行包含编译、加载、缓存冷启动,测性能前先跑几轮预热。
  • 多次测量:每个配置至少跑 3 次,报告平均值和波动(标准差或误差棒)。
  • 看尾延迟:除了平均延迟,还要看 P90、P99。用户感受到的卡顿往往来自尾延迟。
  • GPU 计时要同步:PyTorch 的 GPU 操作是异步的,计时前后要调用 torch.cuda.synchronize()。
  • 控制干扰:测性能时确认 GPU 上没有别人的任务(nvidia-smi),记录 GPU 型号和驱动版本。
  • 公平对比:baseline 和你的方法用同样的硬件、模型、负载,并且 baseline 要调到它的最佳配置。

统计与画图 ​

从原始数据算统计量:

python
import json

import numpy as np

lat = np.array([json.loads(line)["latency_s"] for line in open("results/xxx/records.jsonl")])
print(f"平均 {lat.mean() * 1000:.1f} ms,标准差 {lat.std() * 1000:.1f} ms")
print(f"P50 {np.percentile(lat, 50) * 1000:.1f} ms,P99 {np.percentile(lat, 99) * 1000:.1f} ms")

画论文用的图(矢量 PDF、字体够大、带误差棒):

python
import matplotlib.pyplot as plt
import numpy as np

plt.rcParams.update({"font.size": 12, "pdf.fonttype": 42})   # fonttype 42 避免投稿时的字体问题

rates = [1, 2, 4, 8, 16]
baseline = np.array([[120, 125, 118], [130, 128, 135], [160, 170, 165], [260, 280, 270], [900, 950, 920]])
ours = np.array([[100, 102, 98], [105, 108, 104], [118, 120, 121], [150, 158, 155], [320, 340, 330]])

fig, ax = plt.subplots(figsize=(4.5, 3))
for name, data, marker in [("Baseline", baseline, "o"), ("Ours", ours, "s")]:
    ax.errorbar(rates, data.mean(axis=1), yerr=data.std(axis=1), marker=marker, capsize=3, label=name)
ax.set_xlabel("Request rate (req/s)")
ax.set_ylabel("P99 TTFT (ms)")
ax.set_xscale("log", base=2)
ax.grid(alpha=0.3)
ax.legend()
fig.tight_layout()
fig.savefig("figures/ttft_vs_rate.pdf")

图中的数字是示例。论文里的图通常一栏宽约 3.5 英寸,坐标轴标签要写单位。

负载与数据集 ​

系统实验需要真实的请求负载。常用的公开数据:

名称内容
ShareGPT真实对话,常用于构造 LLM 请求的输入输出长度
Azure LLM 推理 traceAzure 上 LLM 服务的请求到达时间和 token 长度
BurstGPT真实 LLM 服务的请求 trace,包含突发流量
Alibaba 集群 trace阿里巴巴集群的任务与资源使用数据,常用于调度研究
Google 集群 traceGoogle Borg 集群数据

扩散模型方向常用 MS-COCO 的文本描述作为提示词,视频生成常用 VBench 的提示词集和评测。

消融实验与敏感性分析 ​

一个方法通常由好几个设计组成。消融实验逐个去掉某个设计,证明每个设计都有用。敏感性分析改变关键参数(负载强度、模型大小、缓存阈值等),说明方法在不同条件下都有效、有什么适用边界。这两类实验是审稿人一定会看的。

作者:崔涵帅、姚智、王振胜、唐志清 · 边缘智能系统实验室