大模型下的深度强化学习的多智能体渗透测试(10)
PenGym 单智能体 izumi 当日进展报告:BC warm-start 工程闭环打通,tiny-small 根因从数据不足推进到 guard、argmax 与奖励信号问题
一、这篇文章要说明什么
上一篇记录结束时,项目已经推进到一个比较清楚的位置:
medium family内部泛化已经成立;small-honeypot和tiny已经可以通过 short adaptation 成功;tiny-small / tiny-hard仍然失败;- 失败并不是开局完全不会,而是前期 foothold 能建立,中期会被坏分支带偏;
- 旧的
build_demo_dataset.py仍然是 legacy raw observation / raw action id 路线,不能直接用于 unified cross-family 主线。
因此,本阶段的重点从“继续堆 PPO 训练步数”转向:
在当前
646/192unified cross-family 主线下,构建兼容 unified observation 与 canonical action space 的示范轨迹 / BC warm-start 路线,并验证它是否能救tiny-small。
今天这一阶段实际推进了很多,但也暴露出几个新的关键事实。最重要的结论可以概括为八点:
- 当前主线被重新收敛并确认到
646/192,即obs_dim=646、action_dim=192,v2的1368/420暂时不进入当前主线。 - 已构建并跑通 unified-compatible demo dataset builder,使 demo trajectory 能转成 unified observation + canonical action id 数据集。
- 已跑通
train_bc.py --mode unified,完成 BC smoke 与 300 epoch 训练,证明unified dataset -> BC checkpoint工程链路可用。 - 已实现 BC checkpoint 到 MaskablePPO policy actor/action 层的 warm-start 迁移,并完成 tiny-small 5k smoke fine-tune。
- 发现简单
--episodes 8只是重复同一条 22-step 轨迹,样本数增加但数据多样性没有增加,因此进一步构造了真正多样的 multidemo 数据集。 - 通过 multidemo + BC warm-start + PPO fine-tune,定位出两个阶段性根因:先是 repeat guard 过度封锁整个 target,后是 deterministic argmax 在
(4,0)处错误偏好e_ssh而不是e_ftp。 - 修复 guard 后,stochastic policy 已经能成功解决
tiny-small,但 deterministic policy 仍未稳定成功。 - 继续单纯增加训练步数或简单 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 | 646/192: |
随后实际检查已有模型确认:
1 | maskable_ppo_izumi_medium_family_mixed_repeat_guard_v1.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 | observations: unified obs, shape = (N, 646) |
1. 初始 tiny-small unified dataset
第一版 tiny-small unified dataset 结果为:
1 | observations shape = (22, 646) |
随后使用 train_bc.py --mode unified 跑通 BC smoke,并确认 checkpoint 中保存:
1 | state_dict |
这说明:
unified demo dataset -> train_bc.py --mode unified -> BC checkpoint工程链路已经打通。
2. 完整 BC 300 epoch
在 smoke 通过后,继续跑完整 BC warm-start:
1 | checkpoint: prototype/models/bc_unified_tiny_small_646_192_300e.pt |
这个结果说明 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) |
正确做法是:
- 构建当前
646/192unified tiny-small env; - 创建 MaskablePPO;
- 加载 BC checkpoint 的
state_dict; - 将 BC policy 可匹配的 actor/action 权重迁移到 PPO policy;
- value branch 继续保持 PPO 随机初始化。
本阶段新增了:
1 | prototype/rl/train_maskable_ppo_tiny_small_bc_warmstart.py |
实际迁移结果为:
1 | 6 层全部精确迁移 |
随后跑 tiny-small 5k smoke fine-tune:
1 | model: prototype/models/maskable_ppo_tiny_small_bc_warmstart_smoke.zip |
这一步意义很大:
BC checkpoint 已经可以作为 MaskablePPO actor 初始化来源,warm-start 工程闭环成立。
五、--episodes 8 暴露出“重复数据不是数据增强”
为了解决 22 样本太少的问题,先尝试通过 builder 的多 episode 功能生成:
1 | prototype/models/demo_tiny_small_unified_dataset_ep8.npz |
结果为:
1 | 176 samples = 22 × 8 |
但进一步检查发现:
1 | 8 个 episode 完全相同 |
原因是:
- env 是确定性的;
- demo JSON 是同一条;
- replay 出来的 trajectory 完全一致。
因此 ep8 dataset 虽然样本数变多,但没有增加状态多样性。BC 训练也很快达到:
1 | final loss = 0.000001 |
这实际上只是重复背诵同一条轨迹。由此得到的判断是:
对
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 | samples = 77 |
对比之前:
1 | single-demo: 22 samples, 22 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 | bc_unified_tiny_small_multidemo_smoke.pt |
这个 acc 低于 ep8 的 1.0,但这是正常信号:
数据真的变多样了,模型不再只是背一条重复轨迹。
随后跑正式 300 epoch:
1 | bc_unified_tiny_small_multidemo_300e.pt |
这成为后续 warm-start 的主要 BC checkpoint。
七、20k warm-start 对照:cold-start 对照无效,warm-start 仍未成功
使用 multidemo BC 300e 作为初始化,继续跑 tiny-small warm-start 20k。
结果为:
1 | warm-start 20k: 训练成功,active learning |
但 deterministic eval 仍然:
1 | success rate = 0% |
同时跑了所谓 cold-start 20k 对照,但很快发现这个对照无效:
1 | entropy = 0 |
根因是所谓 cold-start 实际上加载了 curriculum/base model,不是真正随机初始化。因此它不是有效对照。
两个模型 eval 都卡在类似位置:
1 | (3,1) 节点尝试 e_ftp,触发 permission_error |
当时初步判断:
- 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 | e_ftp@(3,1) 失败 |
也就是说,模型即使想试正确动作 e_http@(3,1),也会被 guard 拦住。
这时结论发生变化:
当前不是数据问题,也不是 DAgger 问题,而是 repeat guard 的 target-level exploit blocking 过度激进。
修复方式
将 exploit 失败后的封锁粒度从:
1 | target_index |
改成:
1 | (action_name, target_index) |
也就是:
1 | e_ftp@(3,1) 失败 -> 只封 e_ftp@(3,1) |
随后新增验证:
1 | prototype/rl/validate_guard_fix.py |
验证结果:
1 | e_ftp@(3,1) blocked |
九、guard fix 后:stochastic 可以成功,deterministic 仍未收敛
修复 guard 后,先用旧 50k 模型在新 wrapper 下做 eval。
结果显示:
1 | e_http@(3,1) now executes |
这证明 guard fix 有效。
但旧 50k 模型是在坏 guard 环境下训练出来的,所以后面又暴露出新的问题:
1 | (4,0) 处选择 e_ssh,只拿 USER |
随后重跑 guardfix_20k,出现了很强的训练信号:
1 | entropy = -1.93 |
这说明:
BC prior 已经能够以 stochastic policy 解决 tiny-small,平均约 18 步。
但 deterministic eval 仍为 0%。原因是 argmax policy 的动作顺序还没稳定,尤其在 (3,1) 附近会跳过 (2,0) pivot。
十、guardfix_50k 与 100k:继续训练无法解决 argmax 错误
继续跑 guardfix_50k:
1 | training: success, no collapse |
关键行为:
1 | (3,1)->e_http 每轮都执行 |
这说明成功路径已经存在于策略分布中,但还不是 argmax。
继续跑 guardfix_100k 后,结果更明确:
1 | training: 完全收敛 |
logit 诊断显示:
1 | 50k: e_ftp - e_ssh = -1.78 |
也就是说:
更多训练反而增强了
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 | deterministic success = 0% |
这个结果说明:
- 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 | 最多 100 episodes |
但实际执行时发现这个设置成本过高:
- 当前 stochastic success 约 30%;
- 每个失败 episode 可能跑到较长步数;
- Claude 后台继续轮询等待文件生成;
- API token / 费用明显失控。
因此这一步被中止,并重新设定后续执行原则:
1 | 不要跑长时间后台任务 |
这一步虽然没有完成 success distillation,但它明确了后续工程纪律:
先做低成本小闭环验证,再扩大采集规模。
十三、今天阶段性总判断
到目前为止,tiny-small 的问题已经被拆到非常具体。
已经解决的部分
646/192主线重新收敛。- unified dataset builder 打通。
- BC 训练链路打通。
- BC checkpoint 到 MaskablePPO warm-start 打通。
- multidemo dataset 构建完成,解决了单轨迹重复问题。
canonical_signature_from_id与 v1 reference format 对齐。- repeat guard 过度封锁 bug 修复。
(3,1)->e_http已经能执行。- stochastic policy 已经能成功完成 tiny-small。
仍未解决的部分
- deterministic argmax 仍然不能稳定成功。
(4,0)上e_ssh仍然经常压过e_ftp。- ROOT bonus 虽然实现正确,但训练中没有稳定触发。
- success distillation 方向合理,但第一次采集任务成本过高,需要低成本重做。
当前最准确的结论
当前 agent 已经不是“完全不会 tiny-small”,而是 stochastic policy 已经具备成功路径;真正未解决的是如何把 stochastic 成功行为稳定压成 deterministic policy。
十四、下一步计划
下一阶段不建议继续盲目训练,也不建议继续大规模调用 Claude 长时间采集。
推荐顺序如下。
1. 低成本 success distillation
重新设置采集参数:
1 | max_episodes = 20 |
只要收集到 1 条成功轨迹,就保存 partial success dataset。
目标不是一次性解决全部问题,而是验证:
1 | stochastic success trace -> merge dataset -> BC -> deterministic 是否改善 |
2. 如果 success distillation 有改善,再扩大收集
如果 1–2 条成功轨迹能改善 deterministic 行为,再增加到:
1 | target_successes = 5 |
否则不继续烧成本。
3. 如果 success distillation 无效,再考虑 targeted correction
如果 stochastic 成功轨迹蒸馏仍然不能让 deterministic 选择 e_ftp@(4,0),再做更强的 targeted correction,例如:
1 | 专门强化 (4,0)->e_ftp |
4. 暂不做的方向
当前暂不做:
- Transformer;
- 完整 DAgger;
- 1368/420 主线迁移;
- Pikachu / Web 靶场展示层;
- 大规模长训;
- step-level LLM override。
十五、最终结论
今天这一阶段最大的价值,不是某个模型已经 deterministic 成功,而是把 tiny-small 的问题从宽泛的“泛化失败”逐层拆成了可定位、可修复的工程与策略问题。
目前项目已经从:
1 | 20k adaptation 失败,不知道为什么 |
推进到:
1 | BC warm-start 可用 |
因此,当前 izumi 单智能体主线仍然成立:
1 | 646/192 unified MaskablePPO |
下一步最合理的工作不是继续堆训练,而是以低成本方式把已经出现的 stochastic 成功轨迹蒸馏成 deterministic 可复现策略。
