{ "cells": [ { "cell_type": "markdown", "id": "title-and-toc", "metadata": {}, "source": [ "# alpamayo-0.5B:NVIDIA Alpamayo 最小复现\n", "\n", "本 Notebook 从环境安装开始,依次完成模型与数据准备、VLM–expert 连接验证、两阶段训练、结构化轨迹分支、重载评估和结果可视化。\n", "\n", "## 目录\n", "\n", "1. [论文与复现范围](#论文与复现范围)\n", "2. [架构介绍](#架构介绍)\n", "3. [环境安装](#环境安装)\n", "4. [运行配置](#运行配置)\n", "5. [权重与数据准备](#权重与数据准备)\n", "6. [样本检查](#样本检查)\n", "7. [连接验证](#连接验证)\n", "8. [Stage 1:驾驶指令 VLM](#stage-1驾驶指令-vlm)\n", "9. [结构化轨迹 token 分支](#结构化轨迹-token-分支)\n", "10. [Stage 2:Diffusion expert](#stage-2diffusion-expert)\n", "11. [评估与实验结果](#评估与实验结果)\n", "12. [复现检查清单](#复现检查清单)\n" ] }, { "cell_type": "markdown", "id": "paper-and-scope", "metadata": {}, "source": [ "\n", "## 1. 论文与复现范围\n", "\n", "- 论文:[Alpamayo-R1: Bridging Reasoning and Action Prediction for Generalizable Autonomous Driving in the Long Tail](https://arxiv.org/abs/2511.00088)\n", "- NVIDIA 上游仓库:[NVlabs/alpamayo](https://github.com/NVlabs/alpamayo)\n", "- NVIDIA 模型页:[nvidia/Alpamayo-R1-10B](https://huggingface.co/nvidia/Alpamayo-R1-10B)\n", "- 本项目数据源:[nvidia/PhysicalAI-Autonomous-Vehicles](https://huggingface.co/datasets/nvidia/PhysicalAI-Autonomous-Vehicles)\n", "\n", "本项目复现的是 VLM、past-KV、diffusion expert 与连续轨迹解码之间的核心连接方式。它使用 Qwen2.5-0.5B 文本骨干、约 0.80B 的完整 VLM 和 105.6M diffusion expert,在一个 PAI 样本上完成可训练、可保存、可恢复、可评估的最小闭环。它不使用 Alpamayo-R1 原始权重,也不代表完整模型规模、训练数据或驾驶能力。\n" ] }, { "cell_type": "markdown", "id": "architecture", "metadata": {}, "source": [ "\n", "## 2. 架构介绍\n", "\n", "```text\n", "前视图像 ─┐\n", "驾驶问题 ─┼─> LLaVA VLM (CLIP + Qwen2.5-0.5B) ─┬─> 驾驶指令 / 结构化轨迹 token\n", "历史 XY ─┘ └─> 24 层 past-KV\n", " │\n", "noisy XY-delta + diffusion timestep ─> 105.6M Qwen2 expert <──┘\n", " │\n", " vector field\n", " │\n", " 10-step denoising\n", " │\n", " 16 × 2 future XY\n", "```\n", "\n", "Expert 使用 24 层、hidden size 512、8 个 query heads、2 个 KV heads 和 head dimension 64。精确参数量为 105,575,170,其中 Qwen2 主体 105,425,408、action/time projection 148,736、输出层 1,026。\n" ] }, { "cell_type": "markdown", "id": "environment-guide", "metadata": {}, "source": [ "\n", "## 3. 环境安装\n", "\n", "推荐使用 Python 3.12。若尚未启动 Notebook,可在仓库根目录执行:\n", "\n", "```bash\n", "python3.12 -m venv .venv\n", "source .venv/bin/activate\n", "python -m pip install --upgrade pip\n", "python -m pip install -e '.[notebook]'\n", "python -m jupyter lab notebooks/alpamayo_0_5b_minimal_reproduction.ipynb\n", "```\n", "\n", "下面的单元格会定位仓库根目录,并在当前 kernel 尚未安装项目时执行 editable install。\n" ] }, { "cell_type": "code", "id": "environment-setup", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from __future__ import annotations\n", "\n", "import importlib.util\n", "import os\n", "import platform\n", "import subprocess\n", "import sys\n", "from pathlib import Path\n", "\n", "def find_repo_root(start: Path) -> Path:\n", " for candidate in (start.resolve(), *start.resolve().parents):\n", " if (candidate / 'pyproject.toml').exists() and (candidate / 'scripts').is_dir():\n", " return candidate\n", " raise FileNotFoundError('请从 alpamayo-0.5B 仓库内启动此 Notebook')\n", "\n", "ROOT = find_repo_root(Path.cwd())\n", "os.chdir(ROOT)\n", "if importlib.util.find_spec('alpamayo_lite') is None:\n", " subprocess.run([sys.executable, '-m', 'pip', 'install', '-e', str(ROOT)], check=True)\n", "\n", "print('Repository:', ROOT)\n", "print('Python:', sys.version.split()[0])\n", "print('Platform:', platform.platform())\n" ] }, { "cell_type": "markdown", "id": "runtime-guide", "metadata": {}, "source": [ "\n", "## 4. 运行配置\n", "\n", "支持三种执行方式:\n", "\n", "- `reuse`:复用本地已有权重、数据和 checkpoint;当前工作区具备完整产物时自动选择。\n", "- `full`:全新工作区默认选择,执行 80-step Stage 1、160-step structured VLM 和 1,000-step expert。\n", "- `smoke`:每个训练阶段只运行少量步数,用于检查代码、显存和接口;不用于复现发布指标。\n", "\n", "可在启动 Jupyter 前设置以下环境变量导入本地资源:\n", "\n", "```bash\n", "export ALPAMAYO_PROFILE=reuse # reuse | full | smoke\n", "export ALPAMAYO_MODEL_DIR=/path/to/qwen2.5_0.5B_vl\n", "export ALPAMAYO_MANIFEST=/path/to/overfit_manifest.jsonl\n", "export ALPAMAYO_PAI_RAW_DIR=/path/to/pai/raw\n", "export ALPAMAYO_STAGE1_DIR=/path/to/stage1_vlm\n", "export ALPAMAYO_STRUCTURED_DIR=/path/to/stage1_vlm_trajectory\n", "export ALPAMAYO_STAGE2_DIR=/path/to/stage2_expert\n", "```\n", "\n", "若自动下载 PAI 数据,Hugging Face 账号必须先接受 gated dataset 许可,并通过 `hf auth login` 或 `HF_TOKEN` 提供凭据。\n" ] }, { "cell_type": "code", "id": "runtime-config", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import json\n", "import torch\n", "import yaml\n", "from IPython.display import display\n", "\n", "BASE_CONFIG = ROOT / 'configs/local_overfit.yaml'\n", "NOTEBOOK_OUTPUT = ROOT / 'outputs/notebook'\n", "NOTEBOOK_OUTPUT.mkdir(parents=True, exist_ok=True)\n", "\n", "default_stage1 = ROOT / 'outputs/stage1_vlm'\n", "default_stage2 = ROOT / 'outputs/stage2_expert'\n", "has_reusable_outputs = (default_stage1 / 'stage1_metrics.json').exists() and (default_stage2 / 'expert_config.json').exists()\n", "PROFILE = os.environ.get('ALPAMAYO_PROFILE', 'reuse' if has_reusable_outputs else 'full').lower()\n", "if PROFILE not in {'reuse', 'full', 'smoke'}:\n", " raise ValueError('ALPAMAYO_PROFILE must be reuse, full, or smoke')\n", "\n", "profile_steps = {\n", " 'reuse': {'stage1': 80, 'structured': 160, 'stage2': 1000, 'rollouts': 16},\n", " 'full': {'stage1': 80, 'structured': 160, 'stage2': 1000, 'rollouts': 16},\n", " 'smoke': {'stage1': 1, 'structured': 1, 'stage2': 1, 'rollouts': 2},\n", "}[PROFILE]\n", "\n", "raw = yaml.safe_load(BASE_CONFIG.read_text(encoding='utf-8'))\n", "raw['paths']['model_dir'] = os.environ.get('ALPAMAYO_MODEL_DIR', str(ROOT / 'models/qwen2.5_0.5B_vl'))\n", "raw['paths']['pai_raw_dir'] = os.environ.get('ALPAMAYO_PAI_RAW_DIR', str(ROOT / 'data/raw/pai'))\n", "raw['paths']['manifest'] = os.environ.get('ALPAMAYO_MANIFEST', str(ROOT / 'data/processed/overfit_manifest.jsonl'))\n", "raw['paths']['stage1_dir'] = os.environ.get(\n", " 'ALPAMAYO_STAGE1_DIR',\n", " str(default_stage1 if PROFILE == 'reuse' else NOTEBOOK_OUTPUT / 'stage1_vlm'),\n", ")\n", "raw['paths']['stage2_dir'] = os.environ.get(\n", " 'ALPAMAYO_STAGE2_DIR',\n", " str(default_stage2 if PROFILE == 'reuse' else NOTEBOOK_OUTPUT / 'stage2_expert'),\n", ")\n", "raw['stage1']['steps'] = profile_steps['stage1']\n", "raw['stage2']['steps'] = profile_steps['stage2']\n", "RUNTIME_CONFIG = NOTEBOOK_OUTPUT / 'runtime_config.yaml'\n", "RUNTIME_CONFIG.write_text(yaml.safe_dump(raw, sort_keys=False), encoding='utf-8')\n", "\n", "MODEL_DIR = Path(raw['paths']['model_dir']).expanduser().resolve()\n", "MANIFEST = Path(raw['paths']['manifest']).expanduser().resolve()\n", "STAGE1_DIR = Path(raw['paths']['stage1_dir']).expanduser().resolve()\n", "STAGE2_DIR = Path(raw['paths']['stage2_dir']).expanduser().resolve()\n", "default_structured = ROOT / 'outputs/stage1_vlm_trajectory'\n", "STRUCTURED_DIR = Path(os.environ.get(\n", " 'ALPAMAYO_STRUCTURED_DIR',\n", " str(default_structured if PROFILE == 'reuse' else NOTEBOOK_OUTPUT / 'stage1_vlm_trajectory'),\n", ")).expanduser().resolve()\n", "HANDOFF_DIR = (ROOT / 'outputs/structured_vlm_handoff') if PROFILE == 'reuse' else (NOTEBOOK_OUTPUT / 'structured_vlm_handoff')\n", "COMPARISON_DIR = (ROOT / 'outputs/complete_comparison') if PROFILE == 'reuse' else (NOTEBOOK_OUTPUT / 'complete_comparison')\n", "\n", "def run_script(name: str, *args: object) -> None:\n", " command = [sys.executable, str(ROOT / 'scripts' / name), *map(str, args)]\n", " print('$', ' '.join(command))\n", " subprocess.run(command, cwd=ROOT, check=True)\n", "\n", "device = 'cuda' if torch.cuda.is_available() else ('mps' if torch.backends.mps.is_available() else 'cpu')\n", "print(json.dumps({\n", " 'profile': PROFILE,\n", " 'steps': profile_steps,\n", " 'device': device,\n", " 'model_dir': str(MODEL_DIR),\n", " 'manifest': str(MANIFEST),\n", " 'stage1_dir': str(STAGE1_DIR),\n", " 'stage2_dir': str(STAGE2_DIR),\n", "}, indent=2, ensure_ascii=False))\n" ] }, { "cell_type": "markdown", "id": "resources-guide", "metadata": {}, "source": [ "\n", "## 5. 权重与数据准备\n", "\n", "模型目录存在时直接复用,否则从 `thkim0305/qwen2.5_0.5B_vl` 下载固定 revision。Manifest 存在时直接复用,否则从 gated PAI 数据集物化单个样本。\n" ] }, { "cell_type": "code", "id": "prepare-resources", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if not (MODEL_DIR / 'model.safetensors').exists():\n", " run_script('download_model.py', '--config', RUNTIME_CONFIG)\n", "else:\n", " print('Reuse model:', MODEL_DIR)\n", "\n", "if not MANIFEST.exists():\n", " if not (os.environ.get('HF_TOKEN') or (Path.home() / '.cache/huggingface/token').exists()):\n", " raise RuntimeError(\n", " 'PAI manifest 不存在。请先接受数据集许可并运行 hf auth login,'\n", " '或设置 ALPAMAYO_MANIFEST 指向本地 manifest。'\n", " )\n", " run_script('materialize_pai_subset.py', '--config', RUNTIME_CONFIG)\n", "else:\n", " print('Reuse manifest:', MANIFEST)\n", "\n", "assert (MODEL_DIR / 'config.json').exists(), MODEL_DIR\n", "assert MANIFEST.exists(), MANIFEST\n" ] }, { "cell_type": "markdown", "id": "sample-guide", "metadata": {}, "source": [ "\n", "## 6. 样本检查\n", "\n", "显示单样本图像、历史轨迹和未来监督轨迹,确认 manifest 中的相对路径可以从仓库根目录解析。\n" ] }, { "cell_type": "code", "id": "inspect-sample", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "from PIL import Image\n", "\n", "sample = json.loads(MANIFEST.read_text(encoding='utf-8').splitlines()[0])\n", "image_path = Path(sample['image_path'])\n", "trajectory_path = Path(sample['trajectory_path'])\n", "image_path = image_path if image_path.is_absolute() else ROOT / image_path\n", "trajectory_path = trajectory_path if trajectory_path.is_absolute() else ROOT / trajectory_path\n", "trajectory = np.load(trajectory_path)\n", "history_xy = trajectory['history_xyz'][:, :2]\n", "future_xy = trajectory['future_xyz'][:, :2]\n", "\n", "fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))\n", "axes[0].imshow(Image.open(image_path).convert('RGB'))\n", "axes[0].set_title(f\"{sample['camera']} @ {sample['t0_us']} us\")\n", "axes[0].axis('off')\n", "axes[1].plot(history_xy[:, 0], history_xy[:, 1], 'o-', label='history')\n", "axes[1].plot(future_xy[:, 0], future_xy[:, 1], 'o-', label='future target')\n", "axes[1].axis('equal')\n", "axes[1].grid(True, alpha=0.3)\n", "axes[1].legend()\n", "axes[1].set_title(sample['reasoning'])\n", "plt.show()\n" ] }, { "cell_type": "markdown", "id": "connection-guide", "metadata": {}, "source": [ "\n", "## 7. 连接验证\n", "\n", "该步骤加载 VLM,提取 24 层 past-KV,实例化 105.6M expert,并检查层数、KV heads、head dimension 和输出形状。\n" ] }, { "cell_type": "code", "id": "verify-connection", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "run_script('verify_connection.py', '--config', RUNTIME_CONFIG)\n", "connection = json.loads((ROOT / 'outputs/connection_test.json').read_text(encoding='utf-8'))\n", "connection\n" ] }, { "cell_type": "markdown", "id": "stage1-guide", "metadata": {}, "source": [ "\n", "## 8. Stage 1:驾驶指令 VLM\n", "\n", "Stage 1 冻结 vision tower、multimodal projector 和 LM head,只训练最后 8 个 Qwen2 language layers 与 final norm。`reuse` 模式在 checkpoint 存在时跳过训练。\n" ] }, { "cell_type": "code", "id": "train-stage1", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stage1_metrics_path = STAGE1_DIR / 'stage1_metrics.json'\n", "if PROFILE == 'reuse' and stage1_metrics_path.exists():\n", " print('Reuse Stage 1:', STAGE1_DIR)\n", "else:\n", " run_script('train_stage1_vlm.py', '--config', RUNTIME_CONFIG, '--steps', profile_steps['stage1'])\n", "stage1_metrics = json.loads(stage1_metrics_path.read_text(encoding='utf-8'))\n", "stage1_metrics\n" ] }, { "cell_type": "markdown", "id": "structured-guide", "metadata": {}, "source": [ "\n", "## 9. 结构化轨迹 token 分支\n", "\n", "该分支从 Stage 1 checkpoint 继续训练,加入 128 个离散 action token、meta-action 和轨迹边界标记。完整配置训练 160 步并生成 32 个轨迹 token。`smoke` 模式只验证训练入口,不保证结构化输出收敛。\n" ] }, { "cell_type": "code", "id": "train-structured", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "structured_metrics_path = STRUCTURED_DIR / 'trajectory_logits_metrics.json'\n", "if PROFILE == 'smoke':\n", " structured_metrics = None\n", " print('Smoke profile: skip structured generation, which requires convergence before decoding.')\n", "elif PROFILE == 'reuse' and structured_metrics_path.exists():\n", " print('Reuse structured VLM:', STRUCTURED_DIR)\n", "else:\n", " run_script(\n", " 'train_vlm_trajectory_logits.py',\n", " '--config', RUNTIME_CONFIG,\n", " '--output-dir', STRUCTURED_DIR,\n", " '--steps', profile_steps['structured'],\n", " '--trajectory-vocab-size', 128,\n", " '--action-min', -4,\n", " '--action-max', 4,\n", " '--max-new-tokens', 96,\n", " )\n", "if PROFILE != 'smoke':\n", " structured_metrics = json.loads(structured_metrics_path.read_text(encoding='utf-8'))\n", "structured_metrics\n" ] }, { "cell_type": "markdown", "id": "stage2-guide", "metadata": {}, "source": [ "\n", "## 10. Stage 2:Diffusion expert\n", "\n", "Stage 2 冻结 Stage 1 VLM,使用其 prefix cache 训练 105,575,170 参数的 expert。目标为标准化的相邻 waypoint XY delta,flow matching 输出经反标准化和累积求和还原未来轨迹。\n" ] }, { "cell_type": "code", "id": "train-stage2", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stage2_metrics_path = STAGE2_DIR / 'stage2_metrics.json'\n", "if PROFILE == 'reuse' and stage2_metrics_path.exists():\n", " print('Reuse Stage 2:', STAGE2_DIR)\n", "else:\n", " run_script('train_stage2_expert.py', '--config', RUNTIME_CONFIG, '--steps', profile_steps['stage2'])\n", "stage2_metrics = json.loads(stage2_metrics_path.read_text(encoding='utf-8'))\n", "expert_config = json.loads((STAGE2_DIR / 'expert_config.json').read_text(encoding='utf-8'))\n", "print('Expert parameters:', stage2_metrics['expert_parameters'])\n", "stage2_metrics\n" ] }, { "cell_type": "markdown", "id": "evaluation-guide", "metadata": {}, "source": [ "\n", "## 11. 评估与实验结果\n", "\n", "完整模式执行 16 个 diffusion rollouts、structured exact-cache handoff 和 GT/VLM/diffusion 三方比较。`smoke` 模式只执行少量 rollout,并展示仓库中保存的发布结果。\n" ] }, { "cell_type": "code", "id": "reload-evaluation", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "evaluation_path = NOTEBOOK_OUTPUT / 'reload_evaluation.json'\n", "run_script(\n", " 'evaluate_pipeline.py',\n", " '--config', RUNTIME_CONFIG,\n", " '--stage2-dir', STAGE2_DIR,\n", " '--num-rollouts', profile_steps['rollouts'],\n", " '--base-seed', 12,\n", " '--output', evaluation_path,\n", ")\n", "evaluation = json.loads(evaluation_path.read_text(encoding='utf-8'))\n", "evaluation\n" ] }, { "cell_type": "code", "id": "comparison-results", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "if PROFILE != 'smoke':\n", " run_script(\n", " 'verify_structured_vlm_handoff.py',\n", " '--config', RUNTIME_CONFIG,\n", " '--vlm-dir', STRUCTURED_DIR,\n", " '--stage2-dir', STAGE2_DIR,\n", " '--output-dir', HANDOFF_DIR,\n", " )\n", " run_script(\n", " 'build_complete_comparison.py',\n", " '--vlm-npz', STRUCTURED_DIR / 'trajectory_logits.npz',\n", " '--vlm-metrics', STRUCTURED_DIR / 'trajectory_logits_metrics.json',\n", " '--diffusion-npz', STAGE2_DIR / 'reload_prediction.npz',\n", " '--diffusion-metrics', STAGE2_DIR / 'stage2_metrics.json',\n", " '--expert-config', STAGE2_DIR / 'expert_config.json',\n", " '--manifest', MANIFEST,\n", " '--diffusion-seed', 12,\n", " '--output-dir', COMPARISON_DIR,\n", " )\n", " result_dir = COMPARISON_DIR\n", "else:\n", " print('Smoke profile: display checked-in comparison artifacts.')\n", " result_dir = ROOT / 'results/complete_comparison'\n", "\n", "comparison = json.loads((result_dir / 'comparison.json').read_text(encoding='utf-8'))\n", "metrics = comparison['metrics']\n", "print(json.dumps({\n", " 'vlm': metrics['vlm']['computed_from_npz'],\n", " 'diffusion': metrics['diffusion']['computed_from_npz'],\n", " 'trajectory_token_accuracy': metrics['vlm']['trajectory_token_accuracy'],\n", "}, indent=2))\n", "display(Image.open(result_dir / 'trajectory_comparison.png'))\n" ] }, { "cell_type": "markdown", "id": "published-results", "metadata": {}, "source": [ "### 仓库当前发布结果\n", "\n", "| 输出 | Action MSE | ADE | FDE | Max error |\n", "| --- | ---: | ---: | ---: | ---: |\n", "| VLM trajectory tokens | 0.0003439452 | 0.026104664 m | 0.045444489 m | 0.054956669 m |\n", "| Diffusion(seed 12) | 0.7453363983 | 1.125946622 m | 0.258275141 m | 2.925430649 m |\n", "\n", "VLM 的 32 个生成 token 与量化目标全部匹配;其非零误差来自 128-bin 反量化。Diffusion 结果依赖采样 seed,仓库记录的 16-seed 平均 ADE 为 1.530302633 m。\n" ] }, { "cell_type": "markdown", "id": "reproduction-checklist", "metadata": {}, "source": [ "\n", "## 12. 复现检查清单\n", "\n", "顺序执行全部单元格后,应满足:\n", "\n", "- 模型目录包含 `model.safetensors` 与 `download_provenance.json`。\n", "- Manifest 可加载一张前视图像和一份包含 history/future 的 NPZ。\n", "- `verify_connection.py` 返回 24 层 KV,并报告 105,575,170 个 expert 参数。\n", "- Stage 1、structured VLM 与 Stage 2 均生成指标 JSON 和权重文件。\n", "- Reload evaluation 可重复加载 checkpoint 并输出 rollout 指标。\n", "- 完整模式生成 handoff 指标、比较 JSON、waypoint CSV 和三方轨迹图。\n", "\n", "模型输出和本项目不能用于真实车辆控制或安全决策。不要提交 Hugging Face token、gated PAI 数据、下载权重或训练 checkpoint。\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (alpamayo-0.5B)", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.12" } }, "nbformat": 4, "nbformat_minor": 5 }