PenGym 单智能体 izumi 当日进展报告:BC warm-start 工程闭环打通,tiny-small 根因从数据不足推进到 guard、argmax 与奖励信号问题

一、这篇文章要说明什么

上一篇记录结束时,项目已经推进到一个比较清楚的位置:

  • medium family 内部泛化已经成立;
  • small-honeypottiny 已经可以通过 short adaptation 成功;
  • tiny-small / tiny-hard 仍然失败;
  • 失败并不是开局完全不会,而是前期 foothold 能建立,中期会被坏分支带偏;
  • 旧的 build_demo_dataset.py 仍然是 legacy raw observation / raw action id 路线,不能直接用于 unified cross-family 主线。

因此,本阶段的重点从“继续堆 PPO 训练步数”转向:

在当前 646/192 unified cross-family 主线下,构建兼容 unified observation 与 canonical action space 的示范轨迹 / BC warm-start 路线,并验证它是否能救 tiny-small

今天这一阶段实际推进了很多,但也暴露出几个新的关键事实。最重要的结论可以概括为八点:

  1. 当前主线被重新收敛并确认到 646/192,即 obs_dim=646action_dim=192v21368/420 暂时不进入当前主线。
  2. 已构建并跑通 unified-compatible demo dataset builder,使 demo trajectory 能转成 unified observation + canonical action id 数据集。
  3. 已跑通 train_bc.py --mode unified,完成 BC smoke 与 300 epoch 训练,证明 unified dataset -> BC checkpoint 工程链路可用。
  4. 已实现 BC checkpoint 到 MaskablePPO policy actor/action 层的 warm-start 迁移,并完成 tiny-small 5k smoke fine-tune。
  5. 发现简单 --episodes 8 只是重复同一条 22-step 轨迹,样本数增加但数据多样性没有增加,因此进一步构造了真正多样的 multidemo 数据集。
  6. 通过 multidemo + BC warm-start + PPO fine-tune,定位出两个阶段性根因:先是 repeat guard 过度封锁整个 target,后是 deterministic argmax 在 (4,0) 处错误偏好 e_ssh 而不是 e_ftp
  7. 修复 guard 后,stochastic policy 已经能成功解决 tiny-small,但 deterministic policy 仍未稳定成功。
  8. 继续单纯增加训练步数或简单 root bonus 并没有解决 deterministic argmax 提取问题,当前最合理的下一步是低成本收集 stochastic 成功轨迹,做 success distillation,而不是继续无控制地烧时间和 token。

因此,今天的阶段判断不是“tiny-small 已经完全解决”,而是:

tiny-small 的工程链路和学习信号已经被逐层打通,当前问题已经从“能不能训练 / 能不能接 BC”推进到“如何把 stochastic 成功策略压成 deterministic 成功策略”。


二、当前主线先被重新收敛到 646/192

在继续 BC / DAgger 之前,首先必须解决一个很现实的问题:项目里一度同时出现了两套版本。

1. 两套版本的冲突

当时审计和实际检查发现:

  • 一套是当前已有 dataset、BC、tiny adaptation 主要使用的 646/192
  • 另一套是后续某次实验产生的 1368/420

具体来说:

1
2
3
4
5
6
7
8
9
646/192:
observation dim = 646
action dim = 192
reference = medium_reference_signatures.json

1368/420:
observation dim = 1368
action dim = 420
reference = medium_reference_signatures_v2.json

随后实际检查已有模型确认:

1
2
3
4
5
maskable_ppo_izumi_medium_family_mixed_repeat_guard_v1.zip      646/192
maskable_ppo_izumi_medium_family_mixed_repeat_guard_v2.zip 1368/420
maskable_ppo_izumi_tiny_adapt_20k.zip 646/192
maskable_ppo_izumi_tiny_small_adapt_20k.zip 646/192
maskable_ppo_izumi_tiny_hard_adapt_20k.zip 646/192

这说明 v2 不能直接和当前 tiny-small / tiny-hard 修复路线混用。

2. 为什么最终选择 646/192

虽然 1368/420 从长期扩展看可能更大、更通用,但当前已经跑通的链路都在 646/192 上:

  • unified dataset 是 646/192
  • BC smoke 是 646/192
  • tiny / tiny-small / tiny-hard adaptation checkpoint 是 646/192
  • medium family v1 checkpoint 也是 646/192

如果此时切到 1368/420,就必须重建 dataset、重训 BC、重训或迁移 PPO checkpoint。考虑到当前目标是先救 tiny-small / tiny-hard,最终决定当前主线暂时收敛到:

1
current mainline = 646-dim unified observation + 192-dim canonical action

随后对项目做了和谐化整理:

  • prototype/rl/adapters/unified_medium_obs.py 恢复为 17 × 38 = 646
  • train_maskable_ppo_medium_family_mixed_repeat_guard_v1.py 改回 medium_reference_signatures.json
  • 新增 CURRENT_MAINLINE.md
  • 输出 /home/test1/Desktop/PROJECT_HARMONIZATION_REPORT.md

这一步的意义是:后续所有 BC、PPO warm-start、success distillation 都必须基于同一个输入输出空间,否则实验没有可解释性。


三、unified demo dataset 与 BC 训练链路打通

完成主线收敛后,开始补旧 build_demo_dataset.py 的根本问题。

旧 builder 的问题是:

  • 走 raw observation;
  • 走 raw action id;
  • 生成的数据维度例如 (22, 114)
  • 不能直接用于当前 unified observation + canonical action 主线。

因此新建了:

1
prototype/rl/build_demo_dataset_unified.py

它的目标是把 demo JSON 转成:

1
2
3
observations: unified obs, shape = (N, 646)
actions: canonical action ids, shape = (N,)
action_dim = 192

1. 初始 tiny-small unified dataset

第一版 tiny-small unified dataset 结果为:

1
2
3
4
observations shape = (22, 646)
actions shape = (22,)
obs_dim = 646
action_dim = 192

随后使用 train_bc.py --mode unified 跑通 BC smoke,并确认 checkpoint 中保存:

1
2
3
state_dict
obs_dim = 646
action_dim = 192

这说明:

unified demo dataset -> train_bc.py --mode unified -> BC checkpoint 工程链路已经打通。

2. 完整 BC 300 epoch

在 smoke 通过后,继续跑完整 BC warm-start:

1
2
3
checkpoint: prototype/models/bc_unified_tiny_small_646_192_300e.pt
final loss = 0.001817
acc = 1.0000

这个结果说明 BC 已经完全拟合当前 22-step demo,但也暴露出一个问题:

acc=1.0 并不代表泛化好,只能说明模型把这一条 demo 记住了。

后续 BC eval 也验证了这一点:维度无误,但 rollout 会出现重复动作,说明单轨迹 demo 对偏轨状态没有足够覆盖。


四、BC → MaskablePPO warm-start 工程链路打通

BC checkpoint 是普通 PyTorch classifier,不是 SB3 checkpoint,因此不能直接:

1
MaskablePPO.load(bc_checkpoint)

正确做法是:

  1. 构建当前 646/192 unified tiny-small env;
  2. 创建 MaskablePPO;
  3. 加载 BC checkpoint 的 state_dict
  4. 将 BC policy 可匹配的 actor/action 权重迁移到 PPO policy;
  5. value branch 继续保持 PPO 随机初始化。

本阶段新增了:

1
prototype/rl/train_maskable_ppo_tiny_small_bc_warmstart.py

实际迁移结果为:

1
2
3
6 层全部精确迁移
BC net.0/2/4 -> PPO policy_net.0/2/4 + action_net
value branch 保持 PPO 随机初始化

随后跑 tiny-small 5k smoke fine-tune:

1
2
model: prototype/models/maskable_ppo_tiny_small_bc_warmstart_smoke.zip
result: no obs/action mismatch, no v2, model saved

这一步意义很大:

BC checkpoint 已经可以作为 MaskablePPO actor 初始化来源,warm-start 工程闭环成立。


五、--episodes 8 暴露出“重复数据不是数据增强”

为了解决 22 样本太少的问题,先尝试通过 builder 的多 episode 功能生成:

1
prototype/models/demo_tiny_small_unified_dataset_ep8.npz

结果为:

1
2
3
176 samples = 22 × 8
obs_dim = 646
action_dim = 192

但进一步检查发现:

1
8 个 episode 完全相同

原因是:

  • env 是确定性的;
  • demo JSON 是同一条;
  • replay 出来的 trajectory 完全一致。

因此 ep8 dataset 虽然样本数变多,但没有增加状态多样性。BC 训练也很快达到:

1
2
final loss = 0.000001
acc = 1.0000

这实际上只是重复背诵同一条轨迹。由此得到的判断是:

tiny-small,真正需要的是多条不同 demo / recovery trajectory,而不是重复同一条 expert path。


六、构造 multidemo dataset,并修复 action signature 根因

随后进入多 demo 阶段。

1. 发现 canonical signature 格式 bug

在构建 multidemo 时,定位到一个关键根因:

1
unified_action_schema.py: canonical_signature_from_id

之前生成的 canonical signature 与 v1 reference format 不一致,导致 action 映射、mask 或 replay 中出现 all-zero / 错误映射问题。

修复后,canonical_signature_from_id 使用:

1
scenario.host_num_map

生成与 v1 reference format 匹配的 4-tuple signature。

这一步不是小修,而是当前 646/192 canonical action 主线的关键一致性修复。

2. multidemo dataset 结果

最终构建出:

1
prototype/models/demo_tiny_small_unified_multidemo_dataset.npz

数据集情况:

1
2
3
samples            = 77
trajectories = 4 distinct trajectories
unique action ids = 30

对比之前:

1
2
3
single-demo: 22 samples, 22 unique action ids
ep8: 176 samples, but repeated same trajectory
multidemo: 77 samples, 4 real trajectories, 30 unique action ids

新数据中包含:

  • failure -> recovery sequences;
  • 6 个显式 perm_err / conn_err 步骤;
  • pe_daclsvc 作为替代提权路径;
  • 不同扫描顺序;
  • 覆盖 (3,1)->e_http 的正确动作。

3. multidemo BC smoke

multidemo BC smoke 结果:

1
2
3
4
bc_unified_tiny_small_multidemo_smoke.pt
obs_dim = 646
action_dim = 192
acc = 0.44 at epoch 40

这个 acc 低于 ep8 的 1.0,但这是正常信号:

数据真的变多样了,模型不再只是背一条重复轨迹。

随后跑正式 300 epoch:

1
2
3
bc_unified_tiny_small_multidemo_300e.pt
loss = 0.4938
acc = 0.7273

这成为后续 warm-start 的主要 BC checkpoint。


七、20k warm-start 对照:cold-start 对照无效,warm-start 仍未成功

使用 multidemo BC 300e 作为初始化,继续跑 tiny-small warm-start 20k。

结果为:

1
2
warm-start 20k: 训练成功,active learning
model: maskable_ppo_tiny_small_bc_multidemo_20k.zip

但 deterministic eval 仍然:

1
success rate = 0%

同时跑了所谓 cold-start 20k 对照,但很快发现这个对照无效:

1
2
3
entropy = 0
clip = 0
policy collapsed

根因是所谓 cold-start 实际上加载了 curriculum/base model,不是真正随机初始化。因此它不是有效对照。

两个模型 eval 都卡在类似位置:

1
2
(3,1) 节点尝试 e_ftp,触发 permission_error
正确方向应该是 (3,1)->e_http

当时初步判断:

  • warm-start 20k 没有成功;
  • cold-start 对照设计失败;
  • 下一步要么继续到 50k,要么定位 (3,1) 错误分支。

八、50k 分析定位真正根因:repeat guard 过度封锁

继续跑 warm-start 50k 后,最关键的发现不是 success rate,而是 guard 逻辑本身有问题。

原逻辑中,SuccessActionMemoryWrapper 在某个 exploit 失败后执行了类似:

1
self._blocked_exploit_targets.add(target_index)

这导致:

1
2
3
4
e_ftp@(3,1) 失败
-> target_index=3 被加入 _blocked_exploit_targets
-> 同一 target 上所有 exploit 都被封锁
-> e_http@(3,1) 也被封锁

也就是说,模型即使想试正确动作 e_http@(3,1),也会被 guard 拦住。

这时结论发生变化:

当前不是数据问题,也不是 DAgger 问题,而是 repeat guard 的 target-level exploit blocking 过度激进。

修复方式

将 exploit 失败后的封锁粒度从:

1
target_index

改成:

1
(action_name, target_index)

也就是:

1
2
e_ftp@(3,1) 失败 -> 只封 e_ftp@(3,1)
e_http@(3,1) 仍然允许

随后新增验证:

1
prototype/rl/validate_guard_fix.py

验证结果:

1
2
3
e_ftp@(3,1) blocked
e_http@(3,1) NOT blocked
_blocked_exploit_targets stays empty

九、guard fix 后:stochastic 可以成功,deterministic 仍未收敛

修复 guard 后,先用旧 50k 模型在新 wrapper 下做 eval。

结果显示:

1
2
3
e_http@(3,1) now executes
(4,0) discovered every episode
reward: -849 -> -762

这证明 guard fix 有效。

但旧 50k 模型是在坏 guard 环境下训练出来的,所以后面又暴露出新的问题:

1
2
(4,0) 处选择 e_ssh,只拿 USER
正确应为 e_ftp,直接 ROOT -> GOAL

随后重跑 guardfix_20k,出现了很强的训练信号:

1
2
3
4
entropy = -1.93
no collapse
ep_rew_mean = 185
ep_len_mean = 18.8 from iteration 1

这说明:

BC prior 已经能够以 stochastic policy 解决 tiny-small,平均约 18 步。

但 deterministic eval 仍为 0%。原因是 argmax policy 的动作顺序还没稳定,尤其在 (3,1) 附近会跳过 (2,0) pivot。


十、guardfix_50k 与 100k:继续训练无法解决 argmax 错误

继续跑 guardfix_50k:

1
2
3
4
5
6
training: success, no collapse
entropy: -2.50 -> -0.566
value_loss: 4300 -> 0.989
clip_fraction: active
deterministic success: 0/10
stochastic success: 1/10

关键行为:

1
2
3
(3,1)->e_http 每轮都执行
deterministic 在 (4,0) 选择 e_ssh
stochastic 某一轮选择 e_ftp -> ROOT -> GOAL

这说明成功路径已经存在于策略分布中,但还不是 argmax。

继续跑 guardfix_100k 后,结果更明确:

1
2
3
4
5
training: 完全收敛
entropy: -2.23 -> -0.00156
value_loss: 4420 -> 4.7e-05
deterministic success: 0/10
stochastic success: 2/10

logit 诊断显示:

1
2
50k:  e_ftp - e_ssh = -1.78
100k: e_ftp - e_ssh = -2.10

也就是说:

更多训练反而增强了 e_ssh@(4,0) 相对 e_ftp@(4,0) 的优势。

这说明继续 150k / 200k 没有意义。当前问题不是步数不够,而是结构性偏好错误。

结构性原因

(4,0)(2,0) 的 observation 很相似:

1
都有 ssh=1.0, ftp=1.0

而在 (2,0) pivot 状态上,BC+PPO 强化了 e_ssh,这个偏好被误迁移到了 (4,0)

但在 (4,0) 上,正确动作是:

1
e_ftp@(4,0) -> ROOT -> GOAL

而不是:

1
e_ssh@(4,0) -> USER only

十一、ROOT transition bonus 实验:实现成功,但没触发有效梯度

为了解决 (4,0)e_ssh 压过 e_ftp 的问题,尝试增强 ProgressRewardWrapper 的 ROOT transition reward。

新设计不是硬编码 (4,0),而是通用逻辑:

1
如果某 step 让某 target 新增 ROOT 权限,给 +50 root transition bonus

这样理论上可以让:

1
e_ftp@(4,0) -> ROOT

明显优于:

1
e_ssh@(4,0) -> USER only

unit check 通过,说明奖励逻辑本身实现正确。

但 rootbonus_20k 训练结果显示:

1
ROOT_BONUS never fired

也就是说,policy 在训练中没有稳定走到触发 ROOT bonus 的那一步。结果:

1
2
3
deterministic success = 0%
deterministic 退化到 os_scan@(3,1) UNDEF
stochastic success = 30%(3/10)

这个结果说明:

  • root bonus 写对了;
  • 但它没有进入有效训练轨迹;
  • stochastic policy 中成功路径继续变强;
  • deterministic argmax 仍然无法稳定提取成功路径。

因此,此时判断再次转变:

当前重点不再是继续 reward shaping,而是把 stochastic 成功路径蒸馏回 deterministic policy。


十二、尝试 success distillation,但成本失控后中止

既然 stochastic eval 已经有成功轨迹,最自然的下一步是:

1
stochastic rollout -> 收集成功 episode -> 追加到 dataset -> 重新 BC -> warm-start

这不是完整 DAgger,更接近:

1
self-imitation learning / stochastic success distillation

初始任务设定为:

1
2
最多 100 episodes
至少收集 10 条成功轨迹

但实际执行时发现这个设置成本过高:

  • 当前 stochastic success 约 30%;
  • 每个失败 episode 可能跑到较长步数;
  • Claude 后台继续轮询等待文件生成;
  • API token / 费用明显失控。

因此这一步被中止,并重新设定后续执行原则:

1
2
3
4
5
6
不要跑长时间后台任务
不要轮询
单个命令超过 5 分钟必须停下报告
success collection 默认 max_episodes <= 20
target_successes <= 2
先保存 partial results,而不是等待完美结果

这一步虽然没有完成 success distillation,但它明确了后续工程纪律:

先做低成本小闭环验证,再扩大采集规模。


十三、今天阶段性总判断

到目前为止,tiny-small 的问题已经被拆到非常具体。

已经解决的部分

  1. 646/192 主线重新收敛。
  2. unified dataset builder 打通。
  3. BC 训练链路打通。
  4. BC checkpoint 到 MaskablePPO warm-start 打通。
  5. multidemo dataset 构建完成,解决了单轨迹重复问题。
  6. canonical_signature_from_id 与 v1 reference format 对齐。
  7. repeat guard 过度封锁 bug 修复。
  8. (3,1)->e_http 已经能执行。
  9. stochastic policy 已经能成功完成 tiny-small。

仍未解决的部分

  1. deterministic argmax 仍然不能稳定成功。
  2. (4,0)e_ssh 仍然经常压过 e_ftp
  3. ROOT bonus 虽然实现正确,但训练中没有稳定触发。
  4. success distillation 方向合理,但第一次采集任务成本过高,需要低成本重做。

当前最准确的结论

当前 agent 已经不是“完全不会 tiny-small”,而是 stochastic policy 已经具备成功路径;真正未解决的是如何把 stochastic 成功行为稳定压成 deterministic policy。


十四、下一步计划

下一阶段不建议继续盲目训练,也不建议继续大规模调用 Claude 长时间采集。

推荐顺序如下。

1. 低成本 success distillation

重新设置采集参数:

1
2
3
max_episodes = 20
target_successes = 2
max_steps_per_episode = 80

只要收集到 1 条成功轨迹,就保存 partial success dataset。

目标不是一次性解决全部问题,而是验证:

1
stochastic success trace -> merge dataset -> BC -> deterministic 是否改善

2. 如果 success distillation 有改善,再扩大收集

如果 1–2 条成功轨迹能改善 deterministic 行为,再增加到:

1
2
target_successes = 5
max_episodes = 50

否则不继续烧成本。

3. 如果 success distillation 无效,再考虑 targeted correction

如果 stochastic 成功轨迹蒸馏仍然不能让 deterministic 选择 e_ftp@(4,0),再做更强的 targeted correction,例如:

1
2
专门强化 (4,0)->e_ftp
或者对 e_ssh@(4,0) 后续 USER-only 路线加入更明确劣势信号

4. 暂不做的方向

当前暂不做:

  • Transformer;
  • 完整 DAgger;
  • 1368/420 主线迁移;
  • Pikachu / Web 靶场展示层;
  • 大规模长训;
  • step-level LLM override。

十五、最终结论

今天这一阶段最大的价值,不是某个模型已经 deterministic 成功,而是把 tiny-small 的问题从宽泛的“泛化失败”逐层拆成了可定位、可修复的工程与策略问题。

目前项目已经从:

1
20k adaptation 失败,不知道为什么

推进到:

1
2
3
4
5
BC warm-start 可用
multidemo 数据可用
guard bug 已修
stochastic policy 能成功
deterministic argmax 仍需蒸馏 / 校正

因此,当前 izumi 单智能体主线仍然成立:

1
2
3
4
646/192 unified MaskablePPO
+ repeat guard
+ multidemo BC warm-start
+ 后续 success distillation / targeted correction

下一步最合理的工作不是继续堆训练,而是以低成本方式把已经出现的 stochastic 成功轨迹蒸馏成 deterministic 可复现策略。