# PPO训练设计与验收手册

> 状态：2026-08-30停止追加PPO训练并完成项目归档。P0的SFT与父checkpoint step20统一64K评测均已完成：test38均为6/38，Verified27按全部attempt均为9/27，配对检验均无差异。parent的Sphinx 8621单Pod 8Gi OOM作为基础设施异常保留，不再为单题恢复整套环境。腾讯云CVM、云硬盘及相关计费资源已释放；本文命令仅作为历史验收与未来重建手册，当前不能直接执行。

## 1. 范围

PPO历史上与GRPO共用冻结数据、split、任务镜像、ACK兼容sandbox、Agent、rollout、Harbor verifier、terminal reward和运行日志。模型及Canary验证过64K能力；PPO smoke/soak为控制80GB trainer上的反向激活峰值，独立使用40K训练轨迹边界。共同链路不在本文重复；以[`README.md`](PROJECT_README.md)、[`AUTODL_SETUP.md`](AUTODL_SETUP.md)和现有GRPO入口为准。由于运行面已经释放，任何复测都必须先完成基础设施重建和全套preflight。

PPO仅增加以下训练语义：

1. critic/value模型为response token估计value；
2. terminal verifier reward与value共同生成GAE advantage和return；
3. actor执行PPO clipped update，critic执行value update；
4. checkpoint只在末步保存actor PEFT adapter与critic BF16模型；不保存actor基础模型、optimizer或extra状态。

这是一项独立PPO对照实验，不是Klear-AgentForge论文所述revised GRPO的复现。

## 2. 实现边界

现有GRPO入口保持默认行为不变。PPO只暴露三个入口：

```bash
make ppo-smoke
make ppo-soak
make ppo
```

- 通用资源、网络、数据和镜像检查复用现有preflight，不新增 `ppo-preflight`。PPO入口设置`PREFLIGHT_REQUIRE_CANARY_MODEL=false`，只跳过必须已停止的独立Canary vLLM存活探测；Python、Kubernetes、RBAC、Registry和BuildKit门禁仍全部执行。独立Canary链路继续由默认`make preflight`和`make network-smoke`严格验收。
- checkpoint延续训练通过actor `lora_adapter_path`与critic FSDP model-only shards warm-start；optimizer、scheduler、RNG、dataloader和run global step重新初始化，不属于严格resume。
- `ppo-smoke`固定执行1个update；`ppo-soak`只允许2–3个update，`PPO_SOAK_TOTAL_BATCHES>3`会在preflight前失败。soak只验证训练链路，不承担数据遍历或收敛判断。
- `make ppo`是formal入口：强制`TRAIN_TASK_LIMIT=0`、空`TRAIN_TASK_IDS`、`TRAIN_TOTAL_BATCHES=-1`、完整187题train split和确定性尾批补齐。默认batch 8时每epoch执行24个update，187题至少各暴露一次，尾批重复5题；每个epoch记录任务、仓库和重复暴露统计。
- formal训练期validation从冻结42题中按固定seed选择21题，约占train+validation使用量的10%；step 0/10/20/24各运行一次。checkpoint保存step 10/20/24。训练结束后再用单卡评测入口对选中checkpoint运行完整validation42。
- 第一轮smoke设置 `TRAIN_KL_BETA=0`；后续通过同一入口设置非零KL复测，不新增 `ppo-kl-smoke`。
- `make train-smoke` 和 `make train` 继续只表示已验证的GRPO链路。

已实现、待GPU验收的PPO特有配置包括：

| 配置                                   | 计划值/语义                                                       |
| ------------------------------------ | ------------------------------------------------------------ |
| advantage estimator                  | GAE                                                          |
| `PPO_BATCH_SIZE`                     | 首轮 `4`，须能被trainer GPU数整除                                     |
| `PPO_ROLLOUTS_PER_TASK`              | 首轮 `1`                                                       |
| `TRAIN_GAMMA`                        | `1.0`                                                        |
| `TRAIN_GAE_LAMBDA`                   | `0.95`                                                       |
| `TRAIN_PPO_CLIP_RATIO`               | `0.2`                                                        |
| `TRAIN_PPO_VALUE_CLIP`               | `0.2`                                                        |
| `TRAIN_PPO_EPOCHS`                   | 首轮 `1`                                                       |
| `TRAIN_CRITIC_MODEL_PATH`            | 默认与actor使用同一8B SFT checkpoint                                |
| `TRAIN_CRITIC_LEARNING_RATE`         | 首轮计划 `1e-5`，以实际稳定性为准                                         |
| `TRAIN_CRITIC_MINI_BATCH_SIZE`       | 4卡首轮 `4`                                                     |
| `PPO_SMOKE_CONTEXT_WINDOW`           | smoke/soak默认`40960`；同时约束Plus runtime与vLLM/训练边界               |
| `TRAIN_CRITIC_MAX_TOKEN_LEN_PER_GPU` | smoke/soak自动与40K训练边界对齐                                       |
| `PYTORCH_CUDA_ALLOC_CONF`            | 分离smoke/soak固定`expandable_segments:False`以兼容CUDA IPC         |
| `TRAIN_CRITIC_OPTIMIZER_OFFLOAD`     | 4卡smoke首轮启用                                                  |
| `TRAIN_KL_BETA`                      | 首轮 `0`；基础链路通过后再测试非零值                                         |
| `TRAIN_PPO_ACTOR_LORA_ONLY`          | 默认`true`；actor只保留PEFT safetensors adapter                    |
| `TRAIN_WARM_START_CHECKPOINT`        | 可选`global_step_*`目录；加载actor adapter与critic model-only shards |
| `PPO_SOAK_MAX_TIMEOUT_RATIO`         | 默认`0.35`；900秒对照运行显式设为`0.20`                                  |
| checkpoint上限                         | actor 2048MiB、critic 24GiB、合计26GiB                           |

PPO smoke/soak默认总长度40960、prompt 32768、response 8192；actor train及critic train/infer预算均为40960。该限制不修改64K模型配置、Canary或GRPO入口。

## 3. 资源计划

| 阶段               | 资源                             | 目的                                                   |
| ---------------- | ------------------------------ | ---------------------------------------------------- |
| PPO micro smoke  | 单机4×80GB                       | critic初始化、GAE、actor/critic backward及一次optimizer step |
| PPO 3-step soak  | 单机4×80GB历史结果                   | 已通过连续更新、权重同步、有限值漂移与末步model-only checkpoint           |
| PPO 20-step soak | 单机5×80GB：4 trainer + 1 rollout | 验证单vLLM分离拓扑并观察critic、reward、solve rate和显存趋势          |
| checkpoint延续验收   | 同一5×80GB环境                     | 验证actor adapter和critic模型可加载；optimizer重新初始化           |
| 非零KL验收           | 同一4×80GB环境，显存允许时执行             | 验证reference-policy路径                                 |
| 正式PPO            | 单机5×80GB当前候选                   | 4卡actor/critic FSDP，1卡TP=1 vLLM；更多卡仅作未来余量选项          |

5卡分离拓扑固定为4个trainer rank加1个独立rollout GPU；每步batch、任务数和workflow实际并发默认均为8。单个vLLM保留最多10条并发序列的容量，超出即时KV容量时由scheduler排队；PPO训练请求边界为40K。

## 4. 实现任务

1. 已完成：将GRPO与PPO配置解耦，`TRAIN_ALGORITHM`默认仍为`grpo`。
2. 已完成：固定rLLM版本的unified Verl路径原先硬编码`use_critic=False`；现以`patches/rllm-verl-native-ppo.patch`增加native critic、GAE和critic checkpoint，不使用group-relative advantage冒充PPO。
3. 已完成：critic已纳入colocated FSDP、gradient checkpoint和独立optimizer；四卡micro smoke已证明`Klear-AgentForge-8B-SFT`的value forward、backward和update可运行。
4. 已完成：PPO使用独立experiment、episode和checkpoint目录，并写出解析后的`config-summary.json`。当前末步策略只保存actor LoRA与critic BF16模型，不保存optimizer/extra。
5. 已完成：batch、mini-batch、epochs、clip、GAE、critic token budget、offload、KL和恢复参数均已展开；项目级非法组合在创建运行目录前失败。
6. 已完成：FSDP2 smoke支持参数化rank数；`make ppo-smoke`固定4 rank，`make ppo-soak`默认4个trainer rank并另占1张rollout GPU。
7. 已完成：增加PPO静态配置测试、shell解析检查和GRPO默认值回归检查。
8. 2026-08-17首轮四卡执行：公共preflight与四rank FSDP2 forward/backward/optimizer门禁通过；PPO在创建训练进程前因旧式`critic.model.fsdp_config.*`覆盖路径被Verl 0.8.0 Hydra struct拒绝。实现已改用`critic.fsdp.*`并补充critic dtype断言，下一轮仍从无KL micro smoke开始，不把本次配置失败记作训练结果。
9. Hydra路径修正后的第二轮执行已完成数据选择和配置摘要写入，但在colocated Ray worker构造时发现`TrainingWorker`被错误传入`role`。Verl 0.8.0要求先将`CriticConfig`转换为`TrainingWorkerConfig`，只传`config`，随后执行`reset()`并注册`value_loss`；value forward和critic update也必须分别走`infer_batch`与`train_mini_batch`。实现已按该完整契约修正。actor/critic模型、rollout、value/GAE和optimizer仍未完成本轮GPU验收。
10. Worker契约修正后的第三轮已证明`gpu_memory_utilization=0.35`可初始化四个rollout服务并完成4/4任务调度，但3题在Plus runtime静默复合自检中退出，另1题因缺少base-image rewrite而回退Docker Hub并超时。所有episode均为`error`、`steps_used=0`，原流程仍以0退出并保存actor/critic收尾checkpoint。根因之一是`.env.example`仍把curation指向历史`swesmith-20260803-v1/poc`（84 available/3 skipped），而训练数据已切换到`swesmith-curated-grpo-267-v1`，其权威profile为`swesmith-grpo-700-v1/grpo700`（103 available/2 skipped）。默认值现已对齐；实现同时强制Plus命令使用`/opt/mini-swe-agent-plus/bin`、逐项报告revision/schema/CLI失败，训练启动前逐题检查Dockerfile基础镜像存在私有rewrite。PPO smoke仅在values/returns/advantages存在、critic和actor均完成update、存在有效trajectory且双checkpoint分片存在时输出`PPO_SMOKE_PASSED`，否则非零退出。
11. 第四轮的逐项诊断确认4/4 episode均注入了schema v2 runtime，而训练契约固定为v3；这不是模型失败。空batch随后仍进入checkpoint保存，历史30GiB无效checkpoint叠加使71GiB数据盘达到100%，PyTorch最终产生128-byte损坏分片和`unexpected pos`。已清理无效checkpoint与回收站，恢复47GiB可用空间。训练入口现强制schema必须为v3且runtime镜像tag与revision/schema一致，并要求PPO smoke checkpoint文件系统至少有40GiB可用空间；backend在真实update marker不存在时先失败，禁止保存空checkpoint。
12. 2026-08-18四卡无KL micro smoke通过：4/4 rollout完成、183个有效agent step、平均reward 0.5；value、return、advantage、actor loss、critic loss及grad norm均为有限值，actor/critic update marker为true，model-only checkpoint约30GiB。初始critic explained variance较差属于单步未拟合状态，不作为链路失败。
13. 已增加`make ppo-soak`：默认对一个固定task batch执行3个连续update，且现已硬限制为2–3步；逐步累积`smoke-update.json.updates`并校验有限值。该门禁通过后必须转入完整数据formal训练，禁止扩展soak步数。
14. 首次3-step soak的4/4 rollout与公网请求均正常，但step 1在actor old-log-prob计算前失败：official 64K profile产生36322-token序列，而actor与critic动态batch预算仍为32768。该运行没有value/GAE、optimizer update或有效checkpoint。PPO smoke/soak现将两个预算自动对齐`TRAIN_MAX_MODEL_LEN`并在rollout前输出`PPO_SMOKE_TOKEN_BUDGET_GATE_PASSED`；正式训练仍允许显式配置预算。
15. token预算修正后的运行完成logprob、value/GAE和critic update，但actor backward申请8.06GiB时每卡仅余7.05GiB；训练Ray进程占43.78GiB，同卡vLLM仍占28.38GiB。该运行可能已更新critic但actor未更新，未写合格marker或checkpoint，必须整体丢弃。随后尝试通过`enable_sleep_mode=true`与`free_cache_engine=true`释放vLLM权重/KV显存；该尝试的兼容性结果见下一项。
16. 当前AutoDL vLLM 0.22.1报告sleep mode可用，但CUDA platform没有可调用的`is_cumem_allocator_available`接口；启用sleep在vLLM ModelConfig阶段以`cumem allocator is not supported`失败。非cuMem fallback关闭sleep/cache release并启用actor与critic参数/optimizer CPU offload。`gpu_memory_utilization=0.30`只有7.26GiB KV cache，无法承载64K所需的9.0GiB；colocated验证值因此使用0.35。历史入口曾用`PPO_SOAK_N_GPUS`整体扩容，后续6卡实测证明该方式会同步增加vLLM副本，现已由4+1分离拓扑取代。
17. 2026-08-19四卡3-step soak通过：12个episode、513个agent step、errors/timeouts为0，最终step最大序列55167 token，无截断或aborted；actor/critic连续更新并保存`global_step_3`。峰值reserved显存49.74GiB，单step约634秒。critic最终`vf_explained_var=-7.96`。当时曾据此安排20-step中等soak；该方案现已废弃，因为它重复同一小批任务，不能替代完整train遍历。
18. 首轮20-step中等soak在step 1–5完成有效actor/critic更新，step 6完成4个episode后于critic value inference停止。训练覆盖已将`critic.ppo_max_token_len_per_gpu`和`critic.forward_max_token_len_per_gpu`设为65536，但native `TrainingWorkerConfig.engine_config.infer_max_token_len_per_gpu`误从未展开的`ppo_infer_max_token_len_per_gpu`读取默认32768；首个34713-token序列因此触发`max_token_len >= max_seq_len`断言。修复将engine inference预算映射到`forward_max_token_len_per_gpu`，并在worker构造前输出`PPO_CRITIC_TOKEN_BUDGET_GATE_PASSED required=65536 infer=65536 train=65536`。由于本次`save_freq=20`，step 1–5只有指标和episode证据、没有可恢复checkpoint；重跑必须使用新run ID。
19. critic预算修复后，4卡及6卡colocated重跑仍会在长序列actor/critic更新阶段OOM。扩为6卡时Verl继续按trainer world size启动6个vLLM副本，所以每个rank仍承担训练状态和约28GiB rollout常驻开销；卡数增加不能解决单rank显存竞争。当前补丁把`separated_rollout`与`async_training`拆开：同步trainer使用4卡FSDP并保留critic/GAE，独立1卡只启动一个TP=1 vLLM，`max_num_seqs=10`，每个policy update结束后再同步权重。该路径完成静态验收，GPU结果待测。
20. 五卡分离路径随后完成单步更新、actor/critic checkpoint及trainer到单vLLM权重同步。并发8、20-step运行在step 3完成8个episode后进入actor backward，GPU 0已有67.19GiB进程占用、仅余12.00GiB，而单次反向仍需申请13.04GiB，触发CUDA OOM；PyTorch仅2.47GiB为reserved未分配，碎片不是主因。由于`save_freq=20`，本轮没有可恢复checkpoint。当时将PPO边界从64K降至49152；后续第23项记录900秒warm-start再次触发的48K峰值及40K调整。
21. Verl 0.8.0的`save_contents=[model]`仍会把LoRA actor的完整FSDP state写盘，不能满足adapter-only要求。训练入口现对固定0.8.0做fail-closed backport：actor保存前只保留`lora_`/adapter keys，训练结束后将小型FSDP LoRA shards合并成`lora_adapter/adapter_model.safetensors`与`adapter_config.json`并删除actor shards；导出配置显式保留训练使用的rank 32、alpha 16，critic继续保存BF16 model shards。末步门禁禁止optimizer/extra和actor分布式model文件，要求actor不超过2048MiB、critic不超过24GiB、合计不超过26GiB，并输出`PPO_COMPACT_CHECKPOINT_PASSED`。20-step AutoDL运行已验证该格式与大小门禁。
22. 2026-08-21五卡48K、并发8的20-step运行完整结束：160个episode、errors=0、timeouts=48、成功21个；后10步成功率16.25%，高于前10步10%，但样本不足以形成能力结论。critic VF loss从2.973降到0.091，explained variance仍为负；actor KL、entropy和grad norm稳定。末步导出actor 186086224 bytes、critic 15149181794 bytes、合计15335269261 bytes并通过`PPO_COMPACT_CHECKPOINT_PASSED`。Make退出2仅因旧门禁要求零timeout。现增加`TRAIN_WARM_START_CHECKPOINT`：严格核对base model、rank/alpha、四个critic shards及无optimizer/extra后，actor通过PEFT adapter初始化，critic仅加载model shards，随后输出`PPO_WARM_START_LOAD_PASSED`并从新run step 0训练；checkpoint summary记录父step与optimizer reset。
23. 从step 20以900秒启动的首轮warm-start完成8个rollout，但在actor `loss.backward()`时GPU 0已有76.61GiB进程占用、仅余2.59GiB，继续申请13.59GiB而OOM，未完成step 1或生成新checkpoint。同时确认driver-local monkey patch不会进入Ray `VerlTaskRunner`，所以只有actor adapter加载、没有critic加载标记。现改为在配置中携带`warm_start_model_only`契约，由Ray actor内的`load_checkpoint`加载critic并在rollout前输出门禁；PPO默认边界降至40960。
24. 修复后的40K启动已输出`PPO_WARM_START_LOAD_PASSED`，证明四个critic shard在Ray actor内加载成功，但首次trainer到独立vLLM的CUDA IPC同步因宿主机内核缺少expandable segments所需的`pidfd_open`而失败。与此同时PEFT对meta LoRA参数产生1008次copy no-op警告。分离拓扑现固定`expandable_segments:False`。首次仅向`PeftModel.from_pretrained`传`low_cpu_mem_usage=True`仍留下每rank 504个meta adapter tensor，fail-closed门禁在Actor初始化阶段正确终止。现针对PEFT 0.20.0改为显式两阶段路径：`get_peft_model`创建meta adapter，`load_peft_weights`读取CPU state，再由`set_peft_model_state_dict(..., low_cpu_mem_usage=True)`以assign materialize；随后核对checkpoint key/value、非meta、finite和非零并输出`PPO_ACTOR_LORA_LOAD_PASSED`。
25. 显式meta materialize路径在AutoDL四个FSDP rank上表现不一致：仅一个rank成功，另外三个rank仍各留下504个meta LoRA参数。V3加载器因此禁用PEFT的low-memory meta初始化，在每个rank以`low_cpu_mem_usage=False`创建真实CPU adapter并复制checkpoint state，再执行相同的key/value、非meta、finite和非零门禁。rank 32 adapter每个worker约增加333MiB临时CPU内存，四rank约1.3GiB，不增加GPU常驻参数量。
26. V3复测仍只有rank 0成功：非主rank的基础层由Verl保留在meta device，PEFT加载结束时会把adapter移动到基础层设备，导致已分配权重再次回到meta。V4不再调用该PEFT加载路径；它仅为标准LoRA checkpoint key插入`default` adapter名，核对504个目标参数全部存在后直接调用`module.load_state_dict(..., assign=True)`，避免自动设备迁移，再执行checkpoint key/value、非meta、finite和非零门禁。
27. V4已在四个FSDP rank上全部输出`PPO_ACTOR_LORA_LOAD_PASSED`，critic warm-start也成功。随后第二个batch中某个agent在40K上下文耗尽后仍以`max_tokens=0`发起40961-token请求；vLLM错误响应被gateway记录为空token trace，最终令Verl no-padding转换得到`prompt_len=0`。现保持40K不变：gateway在`max_tokens<=0`时返回`context_length_exceeded`且不转发、不记录非2xx或空token trace；PPO转换层再过滤空prompt/response step并输出丢弃计数，避免错误终止调用进入梯度batch。
28. 第二轮40K、900秒warm-start完成20步：160个episode、errors=0、timeouts=12、成功32个，actor adapter为186086224 bytes、critic为15149181794 bytes。前10步成功18/80，后10步14/80；Pygments单题贡献16/32次成功，另两题始终未成功，不能把稳定更新等同于整体能力提升。critic VF loss从0.0894降至0.0078，但explained variance仍为-1.689。训练期间仍偶发40961-token请求，因此PPO训练和test38评估覆盖official公开配置并强制启用256-token Plus context guard；Verified27保持独立32K历史协议。能力门禁只使用完整test38与Verified27；历史SWE-Smith30全部进入最终267且含train12，不作为held-out指标。
29. 父checkpoint `state/checkpoints/swesmith-curated-mini-swe-agent-plus-ppo-smoke-20260820T154406Z/global_step_20`以run `20260822-parent-full-184437Z`完成单卡并发8基线。test38使用40K、256-token guard，38/38有效、成功6题、pass@1 15.79%、Wilson 95%为7.44%–30.42%，0 exceptions/timeouts，耗时1h0m45s；成功题来自Parso 1、Click 1、python-docx 3和grafanalib 1。Verified27使用32K，27 attempts中26个有效、成功6题，scoreable pass@1 23.08%、Wilson 95%为11.03%–42.05%，全部attempt口径为22.22%；成功题为Django 10973/12741、Flask 5014、Requests 5414、Pytest 6202和scikit-learn 14710，`pylint-dev__pylint-4661`有1个`RuntimeError`，耗时51m46s。两个Harbor job分别为`ppo-ppo-parent-test38-20260822-parent-full-184437Z-r1`和`ppo-ppo-parent-verified27-20260822-parent-full-184437Z-r1`，聚合报告为`state/checkpoint-evals/ppo-parent-20260822-parent-full-184437Z/report.json`。vLLM全程无OOM、400/500或上下文错误。后续current在test38为4/38、Verified27按全部attempt仍为6/27，未形成随step单调提升；历史SFT单次结果的波动也禁止把差异直接归因于PPO。
30. 原始`Klear-AgentForge-8B-SFT`以base模式、run `20260822-sft-full-204740Z`完成相同seed `20260822`、单卡、并发8、retry 0正式基线。test38使用40K、256-token guard，38/38有效、成功4题、pass@1 10.53%、Wilson 95%为4.17%–24.13%，0 exceptions/timeouts，耗时31m31s；成功题为Conan `pr_12775`、Parso `d16kevtr`、python-docx `atty59fl`和grafanalib `7971608v`，平均input/output token与agent step分别为729925.1/9857.5/51.9。Verified27使用32K，27/27有效、成功4题、pass@1 14.81%、Wilson 95%为5.92%–32.48%，0 exceptions/timeouts，耗时24m50s；成功题为Django 12741、Requests 5414、Pytest 6202和scikit-learn 10297，平均input/output token与agent step分别为959324.6/11286.9/60.4。两个Harbor job分别为`ppo-sft-base-test38-20260822-sft-full-204740Z-r1`和`ppo-sft-base-verified27-20260822-sft-full-204740Z-r1`，聚合报告为`state/checkpoint-evals/sft-base-20260822-sft-full-204740Z/report.json`。父checkpoint相对本次SFT在test38为6/38对4/38（+5.26个百分点），Verified27按全部attempt为6/27对4/27（+7.41个百分点）；置信区间高度重叠、每题仅一次采样，且历史SFT同一Verified27曾为8/27，因此该点估计不足以宣称PPO提升。
31. Effective step 40 checkpoint `state/checkpoints/swesmith-curated-mini-swe-agent-plus-ppo-smoke-20260821T114029Z/global_step_20`以run `20260822-current-full-214727Z`完成同口径LoRA评测。test38为38/38有效、成功4题、pass@1 10.53%、Wilson 95%为4.17%–24.13%，0 exceptions/timeouts，耗时1h3m31s；成功题为ExceptionGroup `1085phdz`/`xcemkw8b`、django-money `ddzxlx5n`和python-docx `rm3gxhmv`，平均input/output token与agent step为802617.2/9358.2/55.3。Verified27为27/27有效、成功6题、pass@1 22.22%、Wilson 95%为10.61%–40.76%，0 exceptions/timeouts，耗时46m56s；成功题为Django 12741、xarray 4094、Pytest 6202及scikit-learn 10297/14710/25973，平均input/output token与agent step为966376.6/10198.6/61.4。两个Harbor job分别为`ppo-ppo-current-test38-20260822-current-full-214727Z-r1`和`ppo-ppo-current-verified27-20260822-current-full-214727Z-r1`，聚合报告为`state/checkpoint-evals/ppo-current-20260822-current-full-214727Z/report.json`。current相对SFT在test38为0题/0个百分点、Verified27为+2题/+7.41个百分点；相对parent在test38为-2题/-5.26个百分点，Verified27按全部attempt为0题/0个百分点。current与SFT的test38成功集合无交集；Verified27与SFT及parent均只共享3个成功任务，不能把相同总分解释为稳定的相同能力，也不能从这次单样本宣布step 40优于step 20。
32. P0.1增加PPO critic观测：所有value/return/advantage与EV分解统一使用action-token `response_mask`；记录population std、EV的return variance与residual variance、有效action token和trajectory数。return variance为0时显式记录`critic/observability/ev_defined=0`且不伪造EV。训练batch同时保留可逆编码的task ID，并按任务记录episode/success/timeout/error、action token、agent step、reward及critic return residual。空mask、shape错配和masked非有限值均fail closed。critic worker的`vf_loss`、`vf_clipfrac`和`vpred_mean`另提供无重复前缀的canonical名称。
33. P0.2把冻结validation接入可审计的checkpoint选择闭环。完整PPO强制`val_before_train=true`、正数`test_freq/save_freq`，且每个validation step必须存在checkpoint；默认保留5个actor/critic checkpoint。validation默认greedy（temperature 0、top-p 1、每题1次），精确任务顺序与SHA-256写入`validation-contract.json`，逐次总体/逐任务结果追加到`validation-metrics.jsonl`。train/validation空ID、重复ID、任务重叠、镜像rewrite缺失均在rollout前失败。`select_ppo_checkpoint`以`val/success_rate_all`选最高分、同分选更早step，并报告相邻checkpoint成功任务Jaccard与patience建议；它不删除checkpoint，也不使用critic指标代替能力指标。

## 5. 验收顺序

### 5.1 静态与分布式门禁

- 现有测试与GRPO配置快照全部通过；
- PPO配置展开后明确包含critic与GAE；
- 6张GPU可见且没有独立Canary vLLM；
- 五卡collective、FSDP forward/backward和optimizer step通过；
- freeze manifest、267个任务镜像、训练Gateway配置和ACK容量门禁通过；PPO启动前不要求已经停止的独立Canary vLLM返回`/v1/models`。
- 当前选中train任务的每个Dockerfile基础镜像必须命中私有rewrite；存在skipped镜像或profile/dataset错配时在创建Ray worker前失败，禁止回退Docker Hub。
- Plus训练必须先输出`PLUS_RUNTIME_IMAGE_GATE_PASSED schema=v3`；5卡soak还必须输出`PPO_SEPARATED_ROLLOUT_GATE_PASSED trainer_gpus=4 rollout_gpus=1 total_gpus=5 replicas=1 max_num_seqs=10 async=false`。shell token门禁及native critic门禁都必须显示40960，显存门禁必须显示`allocator=expandable_segments:False`；warm-start还必须在rollout前依次显示`PPO_ACTOR_LORA_LOAD_PASSED`和`PPO_WARM_START_LOAD_PASSED`。
- 40K PPO还必须输出`PPO_CONTEXT_GUARD_PASSED window=40960 reserve=256`；vLLM日志不得再出现40961-token或`max_tokens=0`请求。

### 5.2 无KL micro smoke

首轮只运行一个极小batch，关闭reference KL。验收必须同时满足：

- rollout与verifier reward完整；
- value、return和GAE advantage均为有限值；
- actor policy loss、critic value loss和两个grad norm均为有限值；
- `critic/observability/ev_return_variance`、`ev_residual_variance`、action token数和trajectory数存在且有限；若`ev_defined=1`，则必须同时存在有限的`critic/observability/ev`，若为0则该batch只标记EV不可定义，不得填0冒充EV；
- `critic/vf_loss`、`critic/vf_clipfrac`和`critic/vpred_mean`三个canonical指标存在且有限；
- 每个rollout任务都有`train/task/<percent-encoded-task-id>/rollout/*`，进入critic的任务还有对应`critic/*`统计；
- actor与critic各完成一次optimizer step；
- actor权重同步到rollout后仍能产生合法动作；
- 无OOM、NCCL timeout、Ray worker崩溃或ACK遗留Pod。
- 终端出现`PPO_SMOKE_UPDATE_PASSED`和最终`PPO_SMOKE_PASSED`；checkpoint根目录包含`smoke-update.json`，其中actor/critic update均为true。
- checkpoint必须包含actor的`lora_adapter/adapter_model.safetensors`、`adapter_config.json`、critic BF16 model shards和`checkpoint-summary.json`；不得保留actor完整model shards、optimizer或extra。

仅出现`global_step=1`、进程退出码0或actor/critic checkpoint不足以判定通过。

### 5.3 恢复与KL验收

无KL smoke通过后先运行：

```bash
make ppo-soak
```

soak必须输出`PPO_SOAK_PASSED`，`smoke-update.json.updates`必须依次包含全部预期step且每步指标有限；episode error必须为0，timeout比例不得超过配置门限。checkpoint只能包含末步目录，其中actor为标准PEFT adapter、critic为BF16 model shards，不得包含optimizer或extra状态。

soak通过后，使用actor adapter与critic模型重新启动一个batch，确认两侧权重可加载且optimizer按设计重新初始化。该紧凑checkpoint不支持optimizer、RNG、dataloader位置和global step的无损续接。加载通过且4卡仍有显存余量后，再设置小的非零 `TRAIN_KL_BETA` 验证reference logprob和KL指标。任一步失败都不影响现有GRPO入口。

3-step通过后直接进入formal训练，不再执行“20-step中等soak”。历史20-step run复用了同一批8题，其step 1–20不是对train split的遍历；这些结果仅保留为显存、checkpoint和critic观测历史，不能代表20个不同训练批次。当前入口会拒绝`PPO_SOAK_TOTAL_BATCHES=20`。

正式训练使用：

```bash
export TRAIN_WARM_START_CHECKPOINT="$PWD/state/checkpoints/<parent-run>/global_step_20"
export PPO_FORMAL_BATCH_SIZE=8
export PPO_FORMAL_PARALLEL_TASKS=8
export PPO_FORMAL_TOTAL_EPOCHS=1
export PPO_FORMAL_EXPECTED_TRAIN_TASKS=187
export PPO_FORMAL_VALIDATION_TASK_LIMIT=21
export PPO_FORMAL_TEST_FREQ=10
export PPO_FORMAL_SAVE_FREQ=10
make ppo
```

启动必须输出`PPO_FORMAL_PHASE_GATE_PASSED ... validation_tasks=21`、`TRAIN_VALIDATION_SUBSET_GATE_PASSED tasks=21 full_validation=42 train=187`和`TRAIN_FULL_DATASET_GATE_PASSED tasks=187 batch_size=8 batches_per_epoch=24 padded_exposures=5`。epoch结束必须输出`TRAIN_EPOCH_COVERAGE exposures=192 unique_tasks=187 repeated_exposures=5`。默认save/test间隔为10，保存step 10/20；末步24通过`save_final_checkpoint`补存，因此validation与checkpoint最终时点为step 0/10/20/24和step 10/20/24。warm-start仍须在首个rollout前输出`PPO_WARM_START_CONFIG_PASSED`和`PPO_WARM_START_LOAD_PASSED`。

### 5.4 P0：SFT与parent step20的Klear 64K对齐评测

P0不启动训练，只比较原始SFT与父checkpoint `20260820T154406Z/global_step_20`。两个候选均依次运行冻结test38和既有SWE-bench Verified27；两面板统一使用`official-public-repro`、65536 context、200 agent steps、temperature 1.0、provider默认top-p、未显式设置max_tokens、seed `20260822`、retry 0和每题1次。Verified27保持既有27题，不扩大到完整500题。每个候选产生65个attempt，两个面板严格分开报告：

```bash
PPO_EVAL_MODE=base \
PPO_EVAL_CHECKPOINT= \
PPO_EVAL_CANDIDATE=sft-base \
PPO_EVAL_RUN_ID=p0-klear64k-sft-<timestamp> \
PPO_EVAL_PROTOCOL=klear-64k-p0 \
PPO_EVAL_CONTEXT_WINDOW=65536 \
PPO_EVAL_CONTEXT_RESERVE_TOKENS=0 \
PPO_EVAL_SEED=20260822 \
PPO_EVAL_REPEATS=1 \
PPO_EVAL_PANELS=test38,verified27 \
PPO_EVAL_TEST_EXPECTED_COUNT=38 \
PPO_EVAL_CONCURRENCY=8 \
make ppo-eval-checkpoint

PPO_EVAL_MODE=lora \
PPO_EVAL_CHECKPOINT="$PWD/state/checkpoints/swesmith-curated-mini-swe-agent-plus-ppo-smoke-20260820T154406Z/global_step_20" \
PPO_EVAL_CANDIDATE=ppo-parent \
PPO_EVAL_RUN_ID=p0-klear64k-parent-<timestamp> \
PPO_EVAL_PROTOCOL=klear-64k-p0 \
PPO_EVAL_CONTEXT_WINDOW=65536 \
PPO_EVAL_CONTEXT_RESERVE_TOKENS=0 \
PPO_EVAL_SEED=20260822 \
PPO_EVAL_REPEATS=1 \
PPO_EVAL_PANELS=test38,verified27 \
PPO_EVAL_TEST_EXPECTED_COUNT=38 \
PPO_EVAL_CONCURRENCY=8 \
make ppo-eval-checkpoint

```

入口在执行任务前强制验证P0协议、官方采样配置、test38题数、Verified27 manifest/offline-v3任务与镜像rewrite，并对本地vLLM发送约64512-token真实请求，确认服务端没有退回32K/40K。该本机探针只临时使用`127.0.0.1`；Harbor任务必须继续使用`.env`中ACK Pod可达的公网/隧道`CANARY_MODEL_BASE_URL`，loopback地址会在启动前被拒绝。正式任务前还会创建临时ACK Pod访问该公网模型端点；不可达时不会进入38题评测。每个面板结束后若出现`NetworkConnectionError`立即终止，不继续下一面板。每次运行在`state/checkpoint-evals/<candidate>-<run-id>/jobs.json`记录完整协议和配置SHA-256，并生成分面板`report.json`。两个候选合并比较：

```bash
PYTHONPATH="$PWD/src" python -m harbor_qwen_grpo_experiment.ppo_checkpoint_eval \
  --run sft=state/checkpoint-evals/sft-base-<sft-run-id> \
  --run parent=state/checkpoint-evals/ppo-parent-<parent-run-id> \
  --json-output state/checkpoint-evals/p0-klear64k-sft-vs-parent.json
```

每个面板分别给出benchmark Pass@1（全部attempt为分母，主指标）、Wilson 95%区间、无异常scoreable Pass@1、timeout/exception、token/step均值、逐任务胜/平/负和exact McNemar p值，不生成65题混合分数。基础设施异常在benchmark口径计为未通过，同时保留scoreable诊断口径；协议对象不完全相同的run会拒绝合并。

SFT侧正式run `p0-klear64k-sft-20260828T145119Z`已经完成。test38为6/38（15.79%，Wilson 95% 7.44%–30.42%，平均input/output tokens 614526.3/8537.6，平均46.8 agent steps，耗时34m59s）；成功题为Parso `d16kevtr`/`xp4gz0nc`、Click `dcme2i6c`、jsonschema `3hn9c0ns`、python-docx `atty59fl`/`rm3gxhmv`。Verified27为9/27（33.33%，Wilson 95% 18.64%–52.18%，平均input/output tokens 749155.3/8726.9，平均51.4 agent steps，耗时31m33s）；成功题为Django 10973/12741/13449、Flask 5014、Requests 5414、Pytest 6202、scikit-learn 10297、Sphinx 8621和SymPy 12481。两个面板均0 exceptions、0 timeouts；协议配置SHA-256为`681736ee9aaaa1a68d3dfe80fd437663236abebc1f6a2d69f6b5db2204109b22`。报告为`state/checkpoint-evals/sft-base-p0-klear64k-sft-20260828T145119Z/report.json`。

Parent step20正式run `p0-klear64k-parent-20260828T160138Z`也已完成。test38为6/38（15.79%，Wilson 95% 7.44%–30.42%，平均input/output tokens 907401.1/9905.0，平均55.8 agent steps，耗时1h21m41s）；成功题为Conan 12775、Parso `d16kevtr`、django-money `ddzxlx5n`、Click `dcme2i6c`、jsonschema `3hn9c0ns`和grafanalib `7971608v`。Verified27按全部attempt为9/27（33.33%，Wilson 95% 18.64%–52.18%），scoreable为9/26（34.62%，Wilson 95% 19.41%–53.78%），平均input/output tokens 1099618.6/9923.3、平均65.3 agent steps、耗时1h10m34s；成功题为Django 10973/12741、Flask 5014、Requests 1724/5414、Pytest 6202、scikit-learn 10297/14710和SymPy 12481。Sphinx 8621的agent主容器在8Gi limit下OOMKilled（exit 137），Harbor记录1个`NonZeroAgentExitCodeError`；原始attempt必须保留，若恢复则单独标记infrastructure recovery。

严格配对比较中，test38为parent 3胜32平3负、benchmark delta 0、exact McNemar p=1.0；Verified27 benchmark为2胜23平2负、delta 0、exact McNemar p=1.0。scoreable Verified27因Sphinx没有有效parent值，为2胜23平1负，不能替代全部attempt主口径。parent相对SFT的平均input tokens在test38/Verified27分别增加约47.7%/46.8%，平均agent steps增加约19.2%/27.0%，运行时间约为2.33/2.24倍，但两个面板成功数均未提高。P0结论是不支持继续追加PPO训练；Sphinx单题16Gi恢复只用于补齐审计，不会改变该停止判断。

### 5.5 单卡冻结validation42评测

仅评测actor泛化时不使用`make ppo`：该入口即使设置`TRAIN_VAL_ONLY=true`，仍会按PPO/GAE初始化4-way FSDP critic与完整trainer拓扑。改用单卡checkpoint评测入口，只加载基础模型与actor LoRA，不恢复critic、optimizer或trainer worker：

```bash
PPO_EVAL_MODE=lora \
PPO_EVAL_PROTOCOL=validation42 \
PPO_EVAL_CHECKPOINT="$PWD/state/checkpoints/swesmith-curated-mini-swe-agent-plus-ppo-smoke-20260821T114029Z/global_step_20" \
PPO_EVAL_CANDIDATE=ppo-current-val42 \
PPO_EVAL_RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)" \
PPO_EVAL_SEED=20260822 \
PPO_EVAL_REPEATS=1 \
PPO_EVAL_PANELS=validation42 \
PPO_EVAL_VALIDATION_EXPECTED_COUNT=42 \
PPO_EVAL_CONTEXT_WINDOW=65536 \
PPO_EVAL_CONCURRENCY=1 \
PPO_EVAL_GPU=0 \
make ppo-eval-checkpoint
```

入口在启动vLLM前强制验证冻结validation目录恰好42题及64K context，并在run目录保存由公开配置派生的确定性Agent配置（temperature 0.0、top-p 1.0）；运行协议固定为retry 0，并将整体与逐题结果写入`state/checkpoint-evals/ppo-current-val42-<run-id>/report.json`。该路径评测actor成功率，不产生critic EV或VF loss。

### 5.6 P0结论边界

P0是固定65题上的配对小样本实验，不等价于Klear完整SWE-bench Verified 500题复现。只有当两面板方向一致、基础设施异常可接受且任务级翻转支持总体差异时，才能把结果记录为“在P0诊断集上优于SFT”；即使如此也不外推为完整榜单提升。若两面板方向冲突或McNemar证据不足，保留点估计并停止因单次结果追加PPO训练。

## 6. 正式训练放行条件（历史标准，当前未放行）

- 4卡历史无KL门禁及5卡分离拓扑、紧凑checkpoint加载和计划中的KL策略均有明确结论；
- 20-step soak的每一步actor/critic及critic监控指标均为有限值、episode无error、timeout比例不超过门限，且只生成末步model-only checkpoint；
- 至少一个batch出现有效reward与非零actor/critic更新；
- actor adapter与critic模型重新加载后能够继续更新；optimizer重新初始化必须被明确记录；
- checkpoint文件系统至少有30GiB可用空间，末步总checkpoint不得超过26GiB；
- 每卡峰值显存、主机内存、磁盘增长和单batch耗时均已测量；
- 已确认5卡拓扑只产生一个vLLM server，4个trainer rank不再承载rollout常驻显存；
- validation/test仍保持冻结且不参与训练。
- P0的SFT与parent step20统一64K test38/Verified27报告完成前，不追加训练step；P0不以训练reward或critic指标替代能力评测。

最终P0没有显示parent相对SFT的总体增益，因此上述条件没有转化为新的正式训练授权。项目归档后，即使未来重建基础设施，也应先提出新的实验假设、预算和held-out判据，再决定是否恢复训练。

## 附录A：故障记录要求

OOM、NCCL、Ray或ACK故障的详细排查命令放在后续运行记录或本附录，不占用主流程。每次故障至少记录：run ID、代码版本、完整配置、GPU拓扑、各rank峰值显存、异常类型、残留进程/Pod、是否产生checkpoint，以及恢复动作。不得把基础设施异常计作模型reward 0，也不得用删除整类错误记录的方式恢复作业。
