Back to notes
Field notebook
/
20:50:19
/
Note

AI 模型:LLM / VLM / VLA 指南

目录 1. 硬件环境与底层框架 The Bedrock 1 硬件环境与底层框架 the bedrock 2. 数据工程:模型的"燃料" Data Pipeline 2 数据工程模型的燃料 data pipeline 3. 模型架构与训练范式 Modeling 3 模型架构与训练范式 modeling 4. 针对性实战案例 Practical Use Cases 4 针对性实战案例 practical use cases 5. 性能优化与…

Reading signalRoute: 学习63 sections3 notes nearby
Follow 学习 route

At a glance

Reading effort and structure before you settle in.

Reading time
9 min
Images
1
Views
537

Reader briefing

Primary route: 学习63 sections
Section 1 of 2

Reading deck

Quiet body9 min63 sections学习 route

目录


1. 硬件环境与底层框架 (The Bedrock)

1.1 显存需求的第一性原理计算

在动手之前,你必须回答一个问题:我需要多少 GPU 显存?

1.1.1 参数存储

一个参数在不同精度下占用的字节数:

精度每参数字节数7B 模型参数占用13B70B
FP324 bytes28 GB52 GB280 GB
FP16 / BF162 bytes14 GB26 GB140 GB
INT81 byte7 GB13 GB70 GB
INT40.5 byte3.5 GB6.5 GB35 GB

公式显存_参数 = 参数量 × 每参数字节数

1.1.2 训练时的总显存估算(全精度 Mixed Precision)

训练时,显存远不只参数本身。以 AdamW + Mixed Precision (FP16/BF16 + FP32 master weights) 为例:

总显存 ≈ 模型参数 + 梯度 + 优化器状态 + 激活值

具体拆分(以 FP16 训练 + FP32 master copy 为例):
- FP16 模型参数:          2 bytes × P
- FP32 Master Weights:    4 bytes × P   (用于精确更新)
- FP16 梯度:              2 bytes × P
- FP32 Adam m (一阶矩):    4 bytes × P
- FP32 Adam v (二阶矩):    4 bytes × P
─────────────────────────────────────
合计(不含激活值):        16 bytes × P

→ 7B 模型:  16 × 7×10⁹ ≈ 112 GB(仅参数+优化器)
→ 13B 模型: 16 × 13×10⁹ ≈ 208 GB
→ 70B 模型: 16 × 70×10⁹ ≈ 1120 GB ≈ 1.1 TB

激活值(Activation Memory) 取决于 batch_size × seq_len × hidden_dim × num_layers,通常可通过 Gradient Checkpointing(梯度检查点) 将激活值显存降低到 O(L)O(\sqrt{L})LL 为层数),代价是约 33% 的额外计算时间。

经验法则(全参数训练):所需总显存 ≈ 18-20 × 参数量(以 bytes 计)

1.1.3 实际硬件对照表

GPU显存可全参数训练可 LoRA 微调(BF16)可 QLoRA 微调(4bit)
RTX 4070 (12GB)12 GB≤ 0.5B≤ 3B≤ 7B
RTX 4090 (24GB)24 GB≤ 1B≤ 7B≤ 13B
A100 (80GB)80 GB≤ 4B≤ 30B≤ 70B
8×A100 (640GB)640 GB≤ 30B≤ 70B+≤ 180B
H100 (80GB)80 GB≤ 5B≤ 34B≤ 70B

1.1.4 存储空间估算

别忘了磁盘:

  • 模型 checkpoint:一个 7B FP16 模型约 14 GB,训练中一般保留 3-5 个 checkpoint → 70 GB
  • 预训练语料:1T token 的文本语料(tokenized)约 2-4 TB
  • 多模态数据:LAION-400M 图像数据集约 80+ TB(原始图像)
  • 建议:NVMe SSD ≥ 2TB 用于高频 I/O,HDD 用于冷存储

1.2 本地化训练环境搭建

1.2.1 基础环境(以 Ubuntu 22.04 + CUDA 为例)

# 1. 安装 NVIDIA 驱动(推荐使用 .run 包或 apt 方式)
sudo apt update
sudo apt install -y nvidia-driver-545  # 根据显卡型号选择

# 验证
nvidia-smi

# 2. 安装 CUDA Toolkit(推荐 12.1+,与 PyTorch 版本对齐)
wget https://developer.download.nvidia.com/compute/cuda/12.4.0/local_installers/cuda_12.4.0_550.54.14_linux.run
sudo sh cuda_12.4.0_550.54.14_linux.run --toolkit --silent

# 添加环境变量
echo 'export PATH=/usr/local/cuda-12.4/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

# 验证
nvcc --version

# 3. 安装 cuDNN
# 从 https://developer.nvidia.com/cudnn 下载对应版本
sudo dpkg -i cudnn-local-repo-ubuntu2204-9.x.x_1.0-1_amd64.deb

# 4. 安装 NCCL(多卡通信库,多卡训练必备)
sudo apt install libnccl2 libnccl-dev

1.2.2 Python 训练栈

# 推荐使用 conda 管理环境
conda create -n train python=3.11 -y
conda activate train

# PyTorch(务必对齐 CUDA 版本)
pip install torch==2.3.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# 核心训练框架
pip install transformers>=4.43.0       # Hugging Face 模型库
pip install datasets>=2.20.0           # 数据加载
pip install accelerate>=0.33.0         # 分布式训练抽象层
pip install deepspeed>=0.14.0          # ZeRO 优化器
pip install peft>=0.12.0              # LoRA/QLoRA
pip install trl>=0.9.0                # RLHF/DPO 训练
pip install bitsandbytes>=0.43.0      # 量化支持
pip install flash-attn --no-build-isolation  # FlashAttention-2

# 可选但推荐
pip install wandb                      # 实验追踪
pip install sentencepiece protobuf     # Tokenizer 依赖
pip install einops                     # 张量操作

1.2.3 关键框架的角色定位

┌─────────────────────────────────────────────────────────────────┐
│                      你的训练脚本                                │
│                   (train.py / train.yaml)                       │
├─────────────┬───────────────┬───────────────┬──────────────────┤
│ Transformers│     TRL       │     PEFT      │    Datasets      │
│  (模型定义)  │ (RLHF/DPO)   │ (LoRA/QLoRA)  │   (数据加载)      │
├─────────────┴───────────────┴───────────────┴──────────────────┤
│                      Accelerate                                 │
│           (统一的分布式训练接口 / 设备分配)                        │
├─────────────────────────────────────────────────────────────────┤
│              DeepSpeed (ZeRO-1/2/3)                             │
│       (优化器分片 / 梯度分片 / 参数分片 / Offload)                │
├─────────────────────────────────────────────────────────────────┤
│       PyTorch (FSDP / DDP)  +  NCCL (多卡通信)                  │
├─────────────────────────────────────────────────────────────────┤
│              CUDA  +  cuDNN  +  FlashAttention                  │
└─────────────────────────────────────────────────────────────────┘

各框架解释

  • Accelerate:Hugging Face 出品,一层薄抽象。你写单卡代码,它帮你处理多卡、多机、混合精度。配置驱动,不侵入代码。
  • DeepSpeed:微软出品,核心是 ZeRO(Zero Redundancy Optimizer)系列优化。
  • FSDP:PyTorch 原生的全分片数据并行(Fully Sharded Data Parallel),与 DeepSpeed ZeRO-3 理念相似。

1.2.4 Accelerate 配置示例

# 交互式配置
accelerate config

# 或直接编写 YAML(推荐)
# accelerate_config.yaml(单机双卡 + DeepSpeed ZeRO-2 示例)
compute_environment: LOCAL_MACHINE
distributed_type: DEEPSPEED
deepspeed_config:
  deepspeed_config_file: ds_config.json
  zero3_init_flag: false
machine_rank: 0
main_training_function: main
mixed_precision: bf16
num_machines: 1
num_processes: 2            # GPU 数量
gpu_ids: 0,1

1.3 消费级显卡的分布式策略

如果你只有 2-4 张 RTX 4070/4090,以下策略能让你训练更大的模型:

1.3.1 DeepSpeed ZeRO 三阶段对比

                    显存占用(示例: 7B 模型, 2 卡)
                    ┌──────────────────────────────┐
ZeRO-0 (DDP)       │ 每卡存完整副本: ~112 GB/卡    │  ← 放不下
                    ├──────────────────────────────┤
ZeRO-1             │ 优化器状态分片: ~80 GB/卡      │  ← 仍然放不下
(Optimizer Split)   ├──────────────────────────────┤
ZeRO-2             │ +梯度分片: ~66 GB/卡           │  ← 还是大
(+Gradient Split)   ├──────────────────────────────┤
ZeRO-3             │ +参数分片: ~28 GB/卡           │  ← 勉强可行
(+Parameter Split)  ├──────────────────────────────┤
ZeRO-3 + Offload   │ 优化器状态→CPU: ~16 GB/卡     │  ← 消费级可行
                    └──────────────────────────────┘

1.3.2 DeepSpeed ZeRO-3 + CPU Offload 配置

{
  "bf16": { "enabled": true },
  "zero_optimization": {
    "stage": 3,
    "offload_optimizer": {
      "device": "cpu",
      "pin_memory": true
    },
    "offload_param": {
      "device": "cpu",
      "pin_memory": true
    },
    "overlap_comm": true,
    "contiguous_gradients": true,
    "sub_group_size": 1e9,
    "reduce_bucket_size": "auto",
    "stage3_prefetch_bucket_size": "auto",
    "stage3_param_persistence_threshold": "auto",
    "stage3_max_live_parameters": 1e9,
    "stage3_max_reuse_distance": 1e9,
    "stage3_gather_16bit_weights_on_model_save": true
  },
  "gradient_accumulation_steps": 16,
  "gradient_clipping": 1.0,
  "steps_per_print": 10,
  "train_batch_size": "auto",
  "train_micro_batch_size_per_gpu": 1,
  "wall_clock_breakdown": false
}

关键参数解读

  • offload_optimizer.device: "cpu":将 Adam 的 m/v 状态放到 CPU 内存,极大减少 GPU 显存
  • offload_param.device: "cpu":将不在当前前向/反向计算中的参数也放到 CPU(速度会更慢,但显存极省)
  • gradient_accumulation_steps: 16:模拟更大 batch size,弥补显存不足导致的小 micro-batch

1.3.3 FSDP vs DeepSpeed:如何选择?

维度FSDPDeepSpeed ZeRO
来源PyTorch 官方微软
集成度PyTorch 原生,无额外依赖需要安装 deepspeed 包
CPU Offload支持(但生态较新)成熟且稳定
NVMe Offload不支持支持(ZeRO-Infinity)
HF 集成Accelerate 原生支持Accelerate 原生支持
推荐场景2-8 卡同构集群消费级显卡 + CPU Offload

实际建议

  • 4090 × 2 训练 7B QLoRA → Accelerate + FSDP 足够
  • 4070 × 4 全参训练 7B → DeepSpeed ZeRO-3 + CPU Offload
  • 跨机器训练 → DeepSpeed 配合 pdsh 或 Slurm

2. 数据工程:模型的"燃料" (Data Pipeline)

"Garbage in, garbage out" 在大模型时代变为 "Data quality is all you need"

2.1 通用 LLM 预训练数据

2.1.1 数据来源全景

预训练语料(目标: 数百B ~ 数T tokens)
├── Web 爬取
│   ├── Common Crawl(最大公开爬取数据集,PB 级)
│   ├── FineWeb(Hugging Face 清洗后的 15T token 数据集)
│   └── RefinedWeb(Falcon 团队清洗的 5T tokens)
├── 书籍
│   ├── Project Gutenberg(公版图书)
│   └── Books3(争议数据集,注意版权)
├── 学术论文
│   ├── arXiv(LaTeX 源码)
│   ├── S2ORC(Semantic Scholar)
│   └── PubMed(生物医学)
├── 代码
│   ├── The Stack v2(Hugging Face, 许可证过滤)
│   └── StarCoder Data
├── 百科与结构化
│   ├── Wikipedia(多语言)
│   └── StackExchange
└── 领域特定
    ├── 法律: CaseText, 裁判文书网
    ├── 医学: PubMed, MIMIC
    └── 金融: SEC Filings, 财经新闻

2.1.2 数据清洗流水线(The Cleaning Pipeline)

这是最被低估但最重要的环节。以下是工业级流水线的完整步骤:

原始数据 (Raw HTML/Text)
    │
    ▼
┌─────────────────────┐
│ 1. 文本提取          │  trafilatura / resiliparse 从 HTML 提取正文
│    (Extraction)      │  去除导航栏、广告、页脚等 boilerplate
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 2. 语言识别          │  fastText lid.176.bin → 过滤非目标语言
│    (Language ID)     │  阈值通常设 0.65+
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 3. URL/域名过滤      │  黑名单: 成人站点, 垃圾农场, 版权风险域名
│    (URL Filtering)   │  UT1 黑名单 + 自定义规则
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 4. 质量过滤          │  基于启发式规则:
│    (Quality Filter)  │  - 文档长度 > 50 words
│                      │  - 平均句子长度合理 (5-100 words)
│                      │  - 特殊字符/大写比例异常 → 过滤
│                      │  - 停用词比例检测(防机器翻译垃圾)
│                      │  - perplexity 过滤(KenLM 语言模型打分)
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 5. 文档级去重        │  MinHash + LSH (Locality-Sensitive Hashing)
│    (Deduplication)   │  工具: datatrove / text-dedup
│                      │  阈值: Jaccard > 0.8 判定为重复
│                      │  ⚠ 这一步可以去掉 30-50% 的数据
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 6. 段落/句子级去重   │  Exact substring dedup (suffix array)
│    (Fine Dedup)      │  去除在多文档中重复出现的样板段落
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│ 7. PII 脱敏          │  正则 + NER 模型去除:
│    (PII Removal)     │  电话号码, 邮箱, 身份证号, 地址
└─────────┬───────────┘
          │
          ▼
  清洗后的干净语料 → Tokenization → 序列化

2.1.3 工业级工具:Datatrove

Datatrove 是 Hugging Face 开发的可扩展数据处理库,FineWeb 数据集就是用它构建的。

"""
Datatrove 流水线示例:处理 Common Crawl WARC 文件
"""
from datatrove.pipeline.readers import WarcReader
from datatrove.pipeline.filters import (
    URLFilter,
    LanguageFilter,
    GopherRepetitionFilter,
    GopherQualityFilter,
    C4QualityFilter,
)
from datatrove.pipeline.dedup import MinhashDedupSignature, MinhashDedupBuckets, MinhashDedupCluster, MinhashDedupFilter
from datatrove.pipeline.extractors import Trafilatura
from datatrove.pipeline.writers import JsonlWriter
from datatrove.executor import LocalPipelineExecutor

# 阶段 1: 提取 + 过滤
pipeline_1 = [
    WarcReader("s3://commoncrawl/crawl-data/CC-MAIN-2024-10/"),
    Trafilatura(),                      # HTML → 正文提取
    URLFilter(),                        # URL 黑名单过滤
    LanguageFilter(language="zh"),      # 只保留中文
    GopherQualityFilter(),              # Gopher 论文的质量规则
    GopherRepetitionFilter(),           # 重复内容过滤
    C4QualityFilter(),                  # C4 数据集的过滤规则
    JsonlWriter("output/filtered/"),
]

executor = LocalPipelineExecutor(
    pipeline=pipeline_1,
    tasks=100,          # 并行任务数
    workers=16,         # 进程数
)
executor.run()

# 阶段 2: MinHash 去重(需要多步)
# Step 2.1: 计算签名
# Step 2.2: LSH 分桶
# Step 2.3: 聚类
# Step 2.4: 去重过滤

规模参考:FineWeb 处理了 96 个 Common Crawl 快照,最终产出 15T tokens 的高质量英文数据。中文可参考 CCI (Chinese Corpus Internet) 的实践。

2.1.4 Tokenization 策略

Tokenizer 的选择直接影响模型效率。

Tokenizer代表模型词表大小中文效率
BPEGPT-4, LLaMA32K-128K取决于训练语料比例
SentencePiece (Unigram)T5, mBART32K-250K较好
WordPieceBERT30K一般

关键决策

  • 如果做中文模型,务必确保 tokenizer 训练语料中中文占比足够(否则一个汉字被拆成 3-4 个 token,效率极低)
  • LLaMA-3 的 tokenizer 词表扩大到 128K,中文效率显著提升
  • 自训练 tokenizer 示例:
from tokenizers import Tokenizer, models, trainers, pre_tokenizers

# 训练 BPE tokenizer
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)

trainer = trainers.BpeTrainer(
    vocab_size=64000,
    special_tokens=["<s>", "</s>", "<pad>", "<unk>", "<mask>"],
    min_frequency=2,
    show_progress=True,
)

# 在你的语料上训练
files = ["chinese_corpus.txt", "english_corpus.txt", "code_corpus.txt"]
tokenizer.train(files, trainer)
tokenizer.save("my_tokenizer.json")

# 验证中文效率
test = "大规模预训练语言模型的核心在于数据质量"
encoded = tokenizer.encode(test)
print(f"字符数: {len(test)}, Token数: {len(encoded.ids)}")
print(f"压缩率: {len(test)/len(encoded.ids):.2f} 字符/token")
# 理想情况: 中文应达到 1.5-2.5 字符/token

2.2 多模态 VLM 数据

2.2.1 图文对齐数据的类型

VLM 训练数据
├── 预训练阶段(图文对齐 / Feature Alignment)
│   ├── 弱标注图文对 (Weakly-labeled Image-Text Pairs)
│   │   ├── LAION-5B:  50亿图文对, 来自网页 alt-text
│   │   ├── COYO-700M: 7亿图文对
│   │   └── DataComp: 可复现的数据筛选基准
│   │
│   └── 说明: 这些数据质量参差不齐, 但量大
│
├── 指令微调阶段(Visual Instruction Tuning)
│   ├── LLaVA-Instruct-150K: GPT-4 生成的图像问答指令
│   ├── ShareGPT4V: 高质量图像描述
│   ├── ALLaVA: 多样化视觉指令
│   └── 说明: 质量 >> 数量, 通常 100K-1M 条
│
└── 评估数据
    ├── VQAv2, GQA:      视觉问答
    ├── TextVQA, OCRBench: OCR 相关
    ├── MMBench, MME:      综合多模态评测
    └── POPE:              幻觉检测

2.2.2 图文对数据的构建与合成

方法 1: 从网页爬取 + CLIP 过滤

"""
使用 CLIP Score 过滤低质量图文对
原理: CLIP 模型可以衡量图像和文本的语义相似度
"""
import torch
from transformers import CLIPProcessor, CLIPModel
from PIL import Image

model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14")

def filter_image_text_pair(image_path: str, text: str, threshold: float = 0.25):
    """
    CLIP score > threshold → 保留
    DataComp 论文建议阈值 0.25-0.30
    """
    image = Image.open(image_path)
    inputs = processor(text=[text], images=image, return_tensors="pt", padding=True)

    with torch.no_grad():
        outputs = model(**inputs)
        # cosine similarity
        score = outputs.logits_per_image.item() / 100.0

    return score > threshold, score

方法 2: 用大模型合成高质量描述(LLaVA 方法)

"""
用 GPT-4V 或开源 VLM 为图像生成详细描述/问答对
这是 LLaVA 论文的核心数据构造方法
"""
import openai
import base64

def generate_visual_instruction(image_path: str) -> dict:
    with open(image_path, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()

    # 生成三种类型的数据
    prompts = {
        "conversation": "基于这张图片,生成一段自然的多轮对话(3-5轮)。",
        "detail_description": "请详细描述这张图片中的所有内容,包括物体、位置关系、颜色、动作等。",
        "complex_reasoning": "基于这张图片,生成一个需要复杂推理才能回答的问题和答案。"
    }

    results = {}
    for task, prompt in prompts.items():
        response = openai.chat.completions.create(
            model="gpt-4o",
            messages=[{
                "role": "user",
                "content": [
                    {"type": "text", "text": prompt},
                    {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_b64}"}}
                ]
            }]
        )
        results[task] = response.choices[0].message.content

    return results

2.2.3 数据格式标准化

VLM 的训练数据最终需要统一为以下格式:

{
  "id": "000001",
  "image": "images/000001.jpg",
  "conversations": [
    {
      "from": "human",
      "value": "<image>\n请描述这张图片中的场景。"
    },
    {
      "from": "gpt",
      "value": "这张图片展示了一个阳光明媚的公园..."
    }
  ]
}

<image> token:这是一个特殊标记,在模型内部会被替换为视觉编码器输出的特征向量序列(通常 256-576 个 visual tokens)。

2.3 具身 VLA 数据

2.3.1 VLA 数据的独特性

VLA(Vision-Language-Action)数据不同于 LLM/VLM,它必须包含:

VLA 训练数据 = 视觉观测 + 语言指令 + 动作标签

每一条轨迹 (Trajectory) 的结构:
{
    "task_description": "pick up the red cup and place it on the table",
    "steps": [
        {
            "timestamp": 0.0,
            "observation": {
                "image": "frame_000.jpg",              # RGB 图像 (H×W×3)
                "depth": "depth_000.npy",               # 深度图 (可选)
                "proprio": [0.1, 0.2, 0.3, 0.0, ...]   # 本体感受 (关节角度/末端位姿)
            },
            "action": {
                "cartesian": [dx, dy, dz, dRx, dRy, dRz, gripper],  # 7-DOF 动作
                # 或者
                "joint": [dq1, dq2, dq3, dq4, dq5, dq6, gripper]   # 关节空间动作
            }
        },
        ...
    ]
}

2.3.2 动作数据的归一化

这是 VLA 数据工程中最容易出错的环节。 不同机械臂的动作空间差异巨大:

"""
VLA 动作数据归一化
关键: 不同机械臂的关节范围、末端执行器坐标系、控制频率都不同
必须统一归一化到 [-1, 1] 或 [0, 1]
"""
import numpy as np

class ActionNormalizer:
    """
    针对机械臂动作空间的归一化器
    支持两种模式:
    1. min-max 归一化 → [-1, 1]
    2. z-score 归一化 → μ=0, σ=1 (然后 clip)
    """

    def __init__(self, method="minmax"):
        self.method = method
        self.stats = {}

    def fit(self, actions: np.ndarray):
        """
        在整个数据集上计算统计量
        actions: shape (N, action_dim), 例如 (100000, 7)
        """
        if self.method == "minmax":
            self.stats["min"] = np.percentile(actions, 1, axis=0)   # 用 1% 分位数避免异常值
            self.stats["max"] = np.percentile(actions, 99, axis=0)
        elif self.method == "zscore":
            self.stats["mean"] = actions.mean(axis=0)
            self.stats["std"] = actions.std(axis=0) + 1e-8

    def normalize(self, action: np.ndarray) -> np.ndarray:
        if self.method == "minmax":
            normed = 2 * (action - self.stats["min"]) / (self.stats["max"] - self.stats["min"] + 1e-8) - 1
            return np.clip(normed, -1, 1)
        elif self.method == "zscore":
            normed = (action - self.stats["mean"]) / self.stats["std"]
            return np.clip(normed, -5, 5)  # clip 极端值

    def denormalize(self, normed_action: np.ndarray) -> np.ndarray:
        """推理时需要反归一化回真实动作空间"""
        if self.method == "minmax":
            return (normed_action + 1) / 2 * (self.stats["max"] - self.stats["min"]) + self.stats["min"]
        elif self.method == "zscore":
            return normed_action * self.stats["std"] + self.stats["mean"]


# 使用示例
normalizer = ActionNormalizer(method="minmax")

# 假设你有一个 Franka Panda 的数据集
# 动作维度: [x, y, z, rx, ry, rz, gripper] = 7-DOF
all_actions = np.load("franka_actions.npy")  # (N, 7)
normalizer.fit(all_actions)

# 归一化单条数据
raw_action = np.array([0.5, 0.3, 0.1, 0.0, 0.0, 0.1, 1.0])
normed = normalizer.normalize(raw_action)
print(f"归一化后: {normed}")  # 所有值在 [-1, 1]

2.3.3 动作的序列化:离散化 vs 连续化

VLA 模型将动作预测为 token,有两种方式:

方式 1: 离散化(OpenVLA / RT-2 的做法)

"""
将连续动作离散化为 token (bins)
例如: 将 [-1, 1] 均匀分为 256 个 bin → 每个动作维度对应一个 token ID
"""
def discretize_action(action: np.ndarray, num_bins: int = 256) -> list[int]:
    """
    action: 归一化后的动作向量, 值域 [-1, 1]
    返回: token ID 列表
    """
    # [-1, 1] → [0, num_bins-1]
    bin_indices = ((action + 1) / 2 * (num_bins - 1)).astype(int)
    bin_indices = np.clip(bin_indices, 0, num_bins - 1)
    return bin_indices.tolist()

def undiscretize_action(tokens: list[int], num_bins: int = 256) -> np.ndarray:
    """反离散化: token → 连续动作"""
    action = np.array(tokens) / (num_bins - 1) * 2 - 1
    return action

# 示例
action = np.array([0.5, -0.3, 0.0, 0.1, -0.5, 0.8, 1.0])  # 7-DOF
tokens = discretize_action(action, num_bins=256)
print(f"离散化 tokens: {tokens}")  # e.g., [191, 89, 128, 140, 64, 230, 255]

方式 2: 连续回归(直接预测浮点数)

# 在模型最后一层接一个 MLP 回归头
class ActionHead(torch.nn.Module):
    def __init__(self, hidden_dim, action_dim=7):
        super().__init__()
        self.mlp = torch.nn.Sequential(
            torch.nn.Linear(hidden_dim, 256),
            torch.nn.GELU(),
            torch.nn.Linear(256, action_dim),
            torch.nn.Tanh()  # 输出 [-1, 1]
        )

    def forward(self, hidden_states):
        # hidden_states: 最后一个 token 的隐状态
        return self.mlp(hidden_states)

2.3.4 开源机器人数据集

数据集规模机器人任务
Open X-Embodiment100万+ 轨迹22 种机器人527 种技能
DROID76K 轨迹Franka多样化操作
BridgeData V260K 轨迹WidowX桌面操作
RH20T110K 轨迹多种中国团队, 丰富场景

使用 Open X-Embodiment 的 RLDS 格式

import tensorflow_datasets as tfds
dataset = tfds.load("fractal20220817_data", split="train")
for episode in dataset:
    for step in episode["steps"]:
        image = step["observation"]["image"]
        action = step["action"]  # 已归一化

2.4 数据混合策略(Data Mixing)

预训练时不同数据源的混合比例对模型质量影响巨大:

Llama-3 数据混合(推测):
├── Web 文本:    82%     (FineWeb 等)
├── 代码:        8%      (The Stack)
├── 学术论文:    4%      (arXiv, PubMed)
├── 书籍:        4%      (公版)
└── 百科+问答:   2%      (Wikipedia, StackExchange)

中文模型建议配比:
├── 中文 Web:    40%
├── 英文 Web:    25%     (跨语言能力)
├── 代码:        15%
├── 中文百科:    8%
├── 学术论文:    7%
└── 中文书籍:    5%

Hugging Face 教程参考: FineWeb 博客 详细记录了如何从 96 个 CC 快照中清洗出 15T token 的完整过程,包括每个过滤步骤对下游基准测试的影响消融实验。强烈推荐阅读


Section 1 of 2

Route control

After Reading

Choose the next trail: follow the same topic route, open the research shelf, or continue through nearby notes.

学习3 nearby notes