# AI Infra 离线实验：训练、恢复、调度与运行点

本包用 Python 标准库完成三种不同实验。`train.py` 在 CPU 上真正优化两个参数；`simulate.py` 按声明规则构造调度时间线；`benchmark.py` 重算逐请求合成数据。后两者不运行 GPU，也不请求真实 LLM。模拟成功不能证明某块 GPU 的性能，低训练 loss 也不能单独证明 checkpoint 恢复正确。

需要 Python 3.11 或更新版本。无需 pip、模型权重、云账户或 GPU。解压后进入 `infra-lab` 目录运行命令。所有数据为教学构造；运行输出保存在相对目录 `runs/`。参考训练数值在 Python 3.14 实跑；精确恢复对照使用同一 Python 版本，跨版本/平台应记录环境和数值容差。

## 先预测，再运行

开始前写下三个预测：保存全部状态后，19→80 步是否与不中断的 80 步一致？chunk=2 是否会改善所有请求的 TTFT？删除失败行是否只改变表格的样本数？完成后用原始记录逐条核对，保留预测错误。

## 实验一：一次真实的 CPU 训练与恢复

任务是拟合 `y=3x+2`，16 个固定样本，每步消费 4 个样本；优化器为学习率 0.04、momentum 0.8 的 SGD。目标函数为半均方误差，梯度为 `mean(error*x)` 与 `mean(error)`。每个 epoch 的样本次序用 seed=7 的独立 RNG 打乱。

```sh
python3 train.py --steps 19 --checkpoint runs/step19.json --output runs/step19-report.json
python3 train.py --resume runs/step19.json --steps 80 --checkpoint runs/resumed.json --output runs/resumed-report.json
python3 train.py --steps 80 --checkpoint runs/full.json --output runs/full-report.json
```

`--steps` 是**累计目标步数**，不是新增步数。19→80 继续 61 步。checkpoint 含参数、优化器配置和 momentum velocity、步数、样本消费位置、当前次序、数据集、RNG 状态。写文件先写同目录临时文件，再原子替换，避免正常写入流程留下半个 JSON。

实跑参考：19 步 `w≈3.3557282153,b≈2.2900000515,MSE≈0.25216437,samplePosition=12`；80 步 `w≈3.0000640811,b≈1.9999645195,MSE≈6.71266e-9,samplePosition=16`。完整与恢复版的**全部状态**相同，不仅是参数接近。参考 JSON 在 `reference/train-step19.json`、`train-full.json`、`train-resumed.json`，相应完整 checkpoint 也在该目录。

加入恢复故障，用较近的第 31 步看差异：

```sh
python3 train.py --resume runs/step19.json --steps 31 --fault drop-optimizer --checkpoint runs/bad-opt.json --output runs/bad-opt-report.json
python3 train.py --resume runs/step19.json --steps 31 --fault reset-cursor --checkpoint runs/bad-cursor.json --output runs/bad-cursor-report.json
python3 train.py --resume runs/step19.json --steps 31 --fault reset-rng --checkpoint runs/bad-rng.json --output runs/bad-rng-report.json
python3 train.py --steps 31 --checkpoint runs/control31.json --output runs/control31-report.json
```

第一种故障丢 momentum；第二种重读当前 epoch 开头；第三种在下一次 shuffle 改变顺序。三者仍能继续训练，不能据此宣称恢复等价。`drop-optimizer` 实跑第 31 步 `w≈2.9157068334,b≈1.9667916593`；应与正确对照比较，而不是仅看 loss 是否下降。这个例子没有多进程 DataLoader 预取、分布式进程或不确定 GPU 算子；真实系统还需记录这些状态与可重复性的限制。

验收：说明各字段的用途；证明 JSON 落盘/读回后 19→80 与完整 80 步一致；解释一个故障为何破坏样本或更新路径。测试包含解析梯度与有限差分的独立对照、真实收敛、完整恢复状态相等与故障差异。

## 实验二：固定 trace 下比较两种调度

```sh
python3 simulate.py --trace mixed --policy both --chunk 2 --output runs/mixed.json
python3 simulate.py --trace prefill-only --policy both --chunk 2 --output runs/prefill-only.json
```

规则刻意简单：一个虚拟设备；每个 prefill token 1ms；一次 decode batch 1ms，不随活跃请求数变化；每次 dispatch 额外 0.25ms；prefill 完成时发出首 token。等待队列 FIFO，没有 KV 容量、抢占、设备通信或真实 kernel。这里 `fcfs` 表示有等待 prefill 时优先完整执行它；`chunk` 表示先服务活跃 decode，再给队首 prefill 最多 2 tokens。它们不是某个真实引擎版本的完整实现。

mixed 的输入在 `data/scheduler-traces.json`：A 在 0ms 到达，输入 2、输出 12；L 在 2ms 到达，输入 12、输出 2；S 在 4ms 到达，输入 1、输出 4。每个输出 token 的时间都写入结果，首 token 之后的间隔才是 ITL。

| 条件 | 虚拟批次时长 | A 最大 ITL | S TTFT |
|---|---:|---:|---:|
| mixed / fcfs | 29.50ms | 14.75ms | 11.75ms |
| mixed / chunk=2 | 30.75ms | 3.50ms | 21.75ms |
| prefill-only / fcfs | 12.25ms | 无 decode 间隔 | — |
| prefill-only / chunk=2 | 13.50ms | 无 decode 间隔 | — |

chunk 减少 A 的最长输出停顿，却使 FIFO 后面的 S 更晚得到首字。单长请求的反例中，多次 dispatch 造成额外开销。参考完整事件在 `reference/mixed.json` 和 `reference/prefill-only.json`。这不是“chunk 必然更好/更差”的结论，而是在指定机制下展示取舍。

陌生负载任务：**先在纸上预测** U2 的首字与 U1 的最大 ITL，再运行：

```sh
python3 simulate.py --trace unseen --policy both --chunk 4 --output runs/unseen.json
python3 simulate.py --trace prefill-only --policy both --chunk 2 --overhead-ms 0 --output runs/no-overhead.json
python3 simulate.py --trace mixed --policy both --fault zero-arrivals --output runs/bad-arrivals.json
```

`zero-arrivals` 模拟日志丢失原始到达时间。它会同时改变 admission 与 TTFT 边界，不能拿错误 trace 解释原场景。验收：逐条保留入队、首字、token 与结束时间；解释 A 的 TTFT 为什么不因后续阻塞改变；给出一个反例与一个模拟未覆盖的机制。零 overhead 的单请求反例应得到两种策略同为 12ms。

## 实验三：从 800 条逐请求记录重新选运行点

```sh
python3 benchmark.py --scenario both --output runs/benchmark.json
python3 benchmark.py --scenario both --p95-limit 0.3 --output runs/no-feasible.json
python3 benchmark.py --scenario both --deadline 0.3 --output runs/tight-deadline.json
```

数据在 `data/workloads.json`：A 的输入为 80%×256/20%×2048 tokens；B 为 60%×256/40%×4096 tokens；每个成功请求输出 40 tokens。每个候选 cap=1/4/8/16 都有 100 次尝试。到达规则为闭合波次：提交至并发上限，等本波全部结束/超时，再提交下一波。窗口从开始到最后结束，随档位不同，但对该档位固定不变；总成本按同一虚构费率 0.001 元/s×窗口计算。它不能验证固定开放到达率下的稳定容量。

成功延迟 P95 使用 nearest-rank；失败率以全部尝试为分母；质量通过率以成功请求为分母。合格请求必须同时成功、质量分数达到 `--score-minimum`（默认 80）、单次 TTFT 不超过 `--deadline`（默认 2s）。每千合格成本=`总成本/合格数×1000`，goodput=`合格数/完整窗口秒数`。

档位条件另设 `--p95-limit 2 --failure-limit 1 --quality-limit 97`，各 rate 参数用百分数，时间用秒。P95 档位条件不会改变原请求或窗口；单次 deadline/score 门槛会重新计算联合合格数、goodput 与成本。无合格请求时成本未定义；无可行档返回 none。

默认实际输出：A 选 cap=8，成功 P95=1.4s，失败=1/100，质量=97/99，合格=94，窗口=38.24s，成本≈0.406809 元/千合格；B 选 cap=4，P95=1.7s，失败=1/100，质量=97/99，合格=94，窗口=75.44875s，成本≈0.802646。P95 上限改为 0.3s 时两组均无解。仅将单次 deadline 改为 0.3s，A 选 1、合格 18、成本≈8.680556；B 无解。参考见 `reference/benchmark*.json`。

故障与复查：

```sh
python3 benchmark.py --scenario short --failure-limit 0 --output runs/no-failure-allowed.json
python3 benchmark.py --scenario short --failure-limit 0 --fault drop-failures --output runs/bad-denominator.json
```

正确结果只能选 cap=1；删失败行后 cap=8 的 attempted 变为 99、失败率被伪造为 0，于是错误入选。这个故障开关故意制造错误，输出会保留 fault 标签；不能把故障输出当正确验收。

迁移任务：复制 JSON 成 `runs/unseen-workload.json`，只把 A/cap8 的前三个成功请求标为超时（success=false，ttftSec/qualityScore=null，finishReason="timeout"，outputTokens=0）。保留它们的到达与结束，窗口与总成本不变。先预测失败率，再运行 `python3 benchmark.py --scenario short --data runs/unseen-workload.json --output runs/unseen-decision.json`。参考：失败=4/100，cap8 越过 1% 失败门槛；默认可行档中选 cap4。提交修改过的原始数据、命令、结果、排除理由与复测触发条件。

## 运行自检

```sh
python3 -B -m unittest test_lab -v
```

15 项测试应全部通过。仓库集成时还可在课程目录运行 `node --test tests/course-model.test.cjs`，浏览器计算与 JSON 数据同源校验包括在其中；下载离线包只需要 Python。

若启动报 Python 找不到，检查 `python3 --version`；若文件不存在，先核对当前目录与 `--resume` 路径；若没有合格档位，先看排除原因，不能强选最低费用。改输入后请重新跑测试并保存你的独立结果，不要覆盖 `reference/`。

## 带问题读原始资料

- [PyTorch checkpoint 指南](https://docs.pytorch.org/tutorials/beginner/saving_loading_models.html)：列出仅保存权重会遗漏哪些继续训练状态，再对照本实验字段。本文的样本/RNG记录比示例更具体。
- [vLLM 调优](https://docs.vllm.ai/en/latest/configuration/optimization/)：找到 chunk/token budget 对 TTFT 与 ITL 的取舍；指出本模拟缺少的 KV、抢占和实现细节。
- [vLLM 基准口径](https://docs.vllm.ai/en/stable/benchmarking/cli/)：核对客户端 TTFT、流式输出事件 ITL、按请求 TPOT 的区别。真实测量时还需固定版本、warmup、缓存状态、输入输出、到达过程、观察窗口、质量验收与成本边界。

核对日期：2026-10-04。此包不提供 GPU、云或 Kubernetes 的真实运行验收。
