# Mario RL MVP (macOS Apple Silicon)

最小可运行工程：使用像素输入 + 传统 CNN policy（`stable-baselines3` PPO）训练 `gym-super-mario-bros / nes-py` 智能体，不使用大语言模型。

最新进度

![PixPin_2026-02-14_12-57-02.gif](PixPin_2026-02-14_12-57-02.gif)

## 1. 项目结构

```text
mario-rl-mvp/
  src/
    env.py
    train_ppo.py
    eval.py
    record_video.py
    plot_model_max_x_trend.py
    utils.py
  artifacts/
    models/
    videos/
    logs/
  requirements.txt
  README.md
```

## 2. 环境准备（macOS / Apple Silicon）

建议 Python 3.9+（本机默认 `python3` 即可）。

```bash
cd /Users/roog/Work/FNT/SpMr/mario-rl-mvp
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
```

## 2.1 环境准备（WSL / Ubuntu）

如果系统 Python 缺少 `venv/pip`，推荐直接用 `uv` 创建环境并安装依赖：

```bash
cd /home/roog/super-mario/mario-rl-mvp
uv venv .venv -p /usr/bin/python3.10
uv pip install --python .venv/bin/python -r requirements.txt
```

如果你更倾向用系统 `venv`，先安装：

```bash
sudo apt-get update
sudo apt-get install -y python3.10-venv python3-pip
```

### RTX 50 系列（如 RTX 5080）GPU 说明

如果你看到类似：

```text
... CUDA capability sm_120 is not compatible with the current PyTorch installation ...
```

说明当前 torch wheel 不包含 `sm_120` 内核。可直接升级到 `cu128` nightly：

```bash
cd /home/roog/super-mario/mario-rl-mvp
uv pip install --python .venv/bin/python --upgrade --pre torch --index-url https://download.pytorch.org/whl/nightly/cu128
```

验证 GPU：

```bash
.venv/bin/python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'N/A'); print(torch.cuda.get_device_capability(0) if torch.cuda.is_available() else 'N/A')"
```

可选系统依赖（用于 ffmpeg 转码与潜在 SDL 兼容）：

```bash
brew install ffmpeg sdl2
```

## 3. 一条命令开始训练

默认 `--device auto` 训练（优先 CUDA，其次 MPS，最后 CPU）：

```bash
python -m src.train_ppo
```

显式指定 `--device cuda` 或 `--device mps` 时，如果该设备不可用，脚本会默认报错（避免静默回退到 CPU）。
若你明确接受回退，可加：

```bash
python -m src.train_ppo --device cuda --allow-device-fallback
```

常用覆盖参数：

```bash
python -m src.train_ppo \
  --total-timesteps 1000000 \
  --n-envs 4 \
  --save-freq 50000 \
  --env-id SuperMarioBros-1-1-v0 \
  --movement right_only \
  --seed 42
```

从已有 checkpoint 初始化后继续训练（可同时改超参数，如 `--ent-coef`）：

```bash
python -m src.train_ppo \
  --init-model-path artifacts/models/latest_model.zip \
  --total-timesteps 500000 \
  --ent-coef 0.02 \
  --learning-rate 1e-4
```

如果切换了动作空间（例如 checkpoint 是 `right_only`，当前想用 `simple`），可用部分加载：

```bash
python -m src.train_ppo \
  --init-model-path artifacts/models/latest_model.zip \
  --allow-partial-init \
  --movement simple \
  --total-timesteps 300000
```

我目前的参数

```
python -m src.train_ppo \
  --init-model-path artifacts/models/latest_model.zip \
  --n-envs 16 \
  --allow-partial-init \
  --reward-mode progress \
  --movement simple \
  --ent-coef 0.001 \
  --learning-rate 2e-5 \
  --n-steps 2048 \
  --gamma 0.99 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --backward-penalty-scale 0.01 \
  --milestone-bonus 2.0 \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --time-penalty -0.01 \
  --total-timesteps 1200000
```

### 3.1 从已有模型继续训练（`--init-model-path`）

- 用途：加载已有 `.zip` 权重后继续训练，适合“不中断实验目标但调整探索参数”。
- 常见场景：当前策略陷入局部最优（例如 `approx_kl` 和 `policy_gradient_loss` 长期接近 0），希望从已有模型继续探索。
- 注意：这不是“热更新”，仍然需要停止当前训练进程后用新命令重启。

```bash
python -m src.train_ppo \
  --init-model-path artifacts/models/latest_model.zip \
  --ent-coef 0.02 \
  --learning-rate 1e-4 \
  --total-timesteps 300000
```

训练输出：
- stdout：PPO 训练日志
- TensorBoard：`artifacts/logs/<run_name>/tb/`
- checkpoint：`artifacts/models/<run_name>/ppo_mario_ckpt_*.zip`
- final model：`artifacts/models/<run_name>/final_model.zip`
- latest 指针：`artifacts/models/latest_model.zip` + `LATEST_MODEL.txt`

启动 TensorBoard：

```bash
tensorboard --logdir artifacts/logs --port 6006
```

## 3.2 训练日志字段速查（PPO）

训练时你会看到类似：

```text
| rollout/ep_len_mean | rollout/ep_rew_mean | ... |
| train/approx_kl     | train/entropy_loss  | ... |
```

下面是常用字段的含义（对应你贴出来那组）：

- `rollout/ep_len_mean`：最近一批 episode 的平均步数。越大不一定越好，要结合 reward 一起看。
- `rollout/ep_rew_mean`：最近一批 episode 的平均回报。通常越高越好。
- `time/fps`：训练吞吐（每秒环境步数），只代表速度，不代表策略质量。
- `time/iterations`：第几次 rollout + update 循环。
- `time/time_elapsed`：训练已运行的秒数。
- `time/total_timesteps`：累计环境交互步数（达到你设定的 `--total-timesteps` 会停止）。
- `train/approx_kl`：新旧策略差异大小。太大说明更新过猛；接近 0 说明几乎没在更新。极小负数通常是数值误差，可当作 0。
- `train/clip_fraction`：有多少样本触发 PPO clipping。长期为 0 且 KL 也接近 0，常见于“策略基本不再更新”。
- `train/clip_range`：PPO 的 clipping 阈值（默认 0.2）。
- `train/entropy_loss`：探索强度指标。绝对值越接近 0，策略越确定、探索越少。
- `train/explained_variance`：价值网络对回报的解释度，越接近 1 越好，接近 0 说明 value 还不稳。
- `train/learning_rate`：优化器步长（参数更新幅度），不是硬件速度。
- `train/loss`：总损失（由多个部分组成），主要看趋势，不单看绝对值。
- `train/policy_gradient_loss`：策略网络的更新信号。长期接近 0 可能表示 actor 更新很弱。
- `train/value_loss`：价值网络误差。过大通常代表 critic 拟合还不稳定。

### 快速判断（实用版）

- `ep_rew_mean` / `avg_max_x_pos` 持续上升：一般在变好。
- `approx_kl≈0` + `clip_fraction=0` + `policy_gradient_loss≈0`：大概率卡住（更新几乎停了）。
- `entropy_loss` 绝对值太小且长期不变：探索不足，可尝试提高 `--ent-coef`。

## 4. 评估模型

加载最新模型，跑 N 个 episode，输出平均指标：

```bash
python -m src.eval \ 
  --model-path artifacts/models/latest_model.zip \
  --episodes 20 \
  --movement simple \
  --reward-mode progress \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --time-penalty -0.01 \
  --epsilon 0.08
```

可指定模型：

```bash
python -m src.eval \
  --model-path artifacts/models/latest_model.zip \
  --episodes 20 \
  --movement simple \
  --reward-mode progress \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --time-penalty -0.01 \
  --stochastic
```

注意：`eval.py` 默认 `--movement auto`，会按模型动作维度自动匹配 `right_only/simple`，避免动作空间不一致导致 `KeyError`。

输出指标包括：
- `avg_reward`
- `avg_max_x_pos`
- `clear_rate`（`flag_get=True` 的比例）


查看步数

```php
unzip -p artifacts/models/latest_model.zip data | rg '"num_timesteps"|"_total_timesteps"|"_tensorboard_log"'
```

num_timesteps = 151552
_total_timesteps = 150000

## 5. 录制回放视频（无窗口/headless）

默认录制约 10 秒 mp4 到 `artifacts/videos/`：

```bash
python -m src.record_video \
  --model-path artifacts/models/latest_model.zip \
  --movement simple \
  --reward-mode progress \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --time-penalty -0.01 \
  --epsilon 0.08 \
  --duration-sec 30
  --stochastic
```

或者稳定版本

```bash
python -m src.record_video \
  --model-path artifacts/models/latest_model.zip \
  --movement simple \
  --reward-mode progress \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --time-penalty -0.01 \
  --epsilon 0.08 \
  --epsilon-random-mode uniform \
  --max-steps 6000
```


可选:

```bash
--output artifacts/videos/mario_eps008.mp4
```

注意：`record_video.py` 默认 `--movement auto`，会按模型自动匹配动作空间。

实现方式：
- 使用 `render_mode=rgb_array`，无需打开窗口
- 默认通过 `imageio + ffmpeg` 输出 mp4
- 若 mp4 写入失败，会自动降级保存帧序列（PNG），并打印 ffmpeg 转码命令

## 5.1 模型趋势可视化（HTML / Markdown）

用于可视化 `artifacts/models/` 里的模型在训练过程中的关键指标趋势，输出中文 HTML 或 Markdown 报告。

默认命令：

```bash
python -m src.plot_model_max_x_trend
```

默认输出：

- `artifacts/reports/model_max_x_trend.html`

输出 Markdown 报告：

```bash
python -m src.plot_model_max_x_trend --format markdown
```

Markdown 默认输出：

- `artifacts/reports/model_max_x_trend.md`

可选参数（自定义目录/输出）：

```bash
uv run python -m src.plot_model_max_x_trend \
  --models-dir artifacts/models \
  --logs-dir artifacts/logs \
  --format markdown \
  --output artifacts/reports/model_max_x_trend.md
```

报告内容：

- 主趋势：`max_x`（最大前进距离）  
- 多维趋势：平均回报、平均回合步数、通关率、无进展终止率、死亡终止率、超时终止率、硬卡死终止率  
- 模型明细表：每个 checkpoint/final 模型对应的指标值、匹配步数、来源 TensorBoard tag  
- 术语解释：Run、Checkpoint、model_step、matched_step、TensorBoard Tag 等专有名词

## 6. 动作空间选择说明

默认 `RIGHT_ONLY`，原因：
- 动作更少，探索空间更小，MVP 更快收敛到“向右推进”策略
- 适合先验证训练闭环

可切到 `SIMPLE_MOVEMENT`（动作更丰富）：

```bash
python -m src.train_ppo --movement simple
```

## 7. 预处理与奖励

默认预处理链路：
- 跳帧：`frame_skip=4`
- 灰度：`RGB -> Gray`
- 缩放：`84x84`
- 帧堆叠：`4`
- 通道布局：`CHW`（兼容 `CnnPolicy`）

奖励：
- `--reward-mode raw`：原始奖励（默认）
- `--reward-mode clip`：裁剪奖励 `sign(reward)`（等价于旧参数 `--clip-reward`）
- `--reward-mode progress`：奖励塑形模式，额外包含：
  - 前进增益奖励（`--progress-scale`）
  - 死亡惩罚（`--death-penalty`）
  - 通关奖励（`--flag-bonus`）
  - 原地卡住惩罚（`--stall-penalty` + `--stall-steps`）
  - 后退惩罚（`--backward-penalty-scale`）

使用裁剪奖励：

```bash
python -m src.train_ppo --reward-mode clip
```

针对“卡在固定位置（如 x=314 撞蘑菇）”的推荐续训命令：

```bash
python -m src.train_ppo \
  --init-model-path  /home/roog/super-mario/mario-rl-mvp/artifacts/models/ppo_SuperMarioBros-1-1-v0_20260212_205220/ppo_mario_ckpt_100000_steps.zip \
  --n-envs 16 \
  --allow-partial-init \
  --reward-mode progress \
  --movement simple \
  --ent-coef 0.01 \
  --learning-rate 1e-4 \
  --n-steps 1024 \
  --gamma 0.99 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --backward-penalty-scale 0.01 \
  --milestone-bonus 2.0 \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --total-timesteps 300000
```

## 8. 常见问题排查

### 8.1 `pip install` 失败（nes-py/gym-super-mario-bros 编译问题）

先安装工具链并重试：

```bash
xcode-select --install
brew install cmake swig sdl2
pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
```

### 8.2 MPS 不稳定或报错

强制 CPU：

```bash
python -m src.train_ppo --device cpu
```

说明：脚本会先做一次 MPS 张量 sanity check，失败自动回退 CPU。

### 8.3 视频写入失败（ffmpeg/codec）

1) 安装系统 ffmpeg：
```bash
brew install ffmpeg
```

2) 已降级保存帧序列时，手动转码：
```bash
ffmpeg -framerate 30 -i artifacts/videos/<name>_frames/frame_%06d.png -c:v libx264 -pix_fmt yuv420p artifacts/videos/<name>.mp4
```

### 8.4 图形窗口相关报错

本工程默认 `rgb_array` 录制，不依赖 GUI 窗口。
若仍遇到 SDL 问题，可显式设置：

```bash
export SDL_VIDEODRIVER=dummy
python -m src.record_video --duration-sec 10
```

### 8.5 `size mismatch for action_net`（加载旧模型时报错）

典型原因：旧 checkpoint 的动作空间与当前配置不同（如 `right_only`=5 动作，`simple`=7 动作）。

可选修复：

1) 保持和 checkpoint 一致的动作空间：
```bash
python -m src.train_ppo --init-model-path /Users/roog/Work/FNT/SpMr/mario-rl-mvp/artifacts/models/ppo_SuperMarioBros-1-1-v0_20260212_164717/ppo_mario_ckpt_150000_steps.zip --movement right_only
```

2) 若你确实要切动作空间，用部分初始化（跳过不兼容动作头）：
```bash
python -m src.train_ppo --init-model-path artifacts/models/latest_model.zip --movement simple --allow-partial-init
```

3) 或者直接不加载旧模型，从头训练新动作空间。

### 8.6 `cudaGetDeviceCount ... Error 304`（WSL 下 CUDA 初始化失败）

如果训练启动时看到：

```text
[device] cpu | CUDA unavailable, using CPU.
[device_diag] ... torch.cuda.is_available()=False ... Error 304 ...
```

说明不是 `device` 参数没传，而是 CUDA 运行时在当前环境初始化失败。

先做两步确认：

```bash
nvidia-smi --query-gpu=name,driver_version,compute_cap --format=csv,noheader
.venv/bin/python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available())"
```

常见原因是 WSL GPU 栈/驱动状态异常，而不是 PPO 代码本身。若你是临时跑通实验，可先显式 CPU：

```bash
python -m src.train_ppo --device cpu
```

## 9. 最小 smoke test（按顺序执行）

```bash
cd /Users/roog/Work/FNT/SpMr/mario-rl-mvp
source .venv/bin/activate

# 1) 训练 1e4 steps，并至少写出 checkpoint + final model
python -m src.train_ppo \
  --total-timesteps 10000 \
  --save-freq 2000 \
  --n-envs 1 \
  --device cpu \
  --movement right_only

# 2) 快速评估
python -m src.eval --episodes 2 --max-steps 2000

# 3) 录制 10 秒视频
python -m src.record_video --duration-sec 10 --fps 30
```

验收标准：
- `artifacts/models/` 下有 `.zip` 模型
- `artifacts/logs/` 下有 TensorBoard event 文件
- `artifacts/videos/` 下有 `.mp4`（或失败时有 `_frames/` 帧序列）

python -m src.eval \
  --model-path artifacts/models/latest_model.zip \
  --episodes 50 \
  --movement simple \
  --reward-mode progress \
  --no-progress-terminate-steps 300 \
  --no-progress-terminate-penalty 10 \
  --death-penalty -50 \
  --stall-penalty 0.05 \
  --stall-steps 40 \
  --time-penalty -0.01 \
  --random-noops 30 \
  --epsilon 0.03