结构化数据格式与 GPU 亲和性¶
背景知识
AI 数据管道的核心矛盾:数据在磁盘上以压缩格式存储,但 GPU 需要连续、对齐、固定宽度的内存布局来高效计算。格式设计的本质是在磁盘效率与 GPU 亲和性之间找到 Pareto 最优点。
1. 三种格式的定位¶
磁盘 ──────────────────────────────────────────────── 内存/显存
Parquet Lance Arrow / Feather
(通用分析格式) (ML 数据湖格式) (内存交换标准)
高压缩 · 批量扫描 随机访问 · 版本控制 零拷贝 · GPU 原生对齐
| 维度 | Parquet | Lance | Arrow / Feather |
|---|---|---|---|
| 定位 | 磁盘列式存储的事实标准 | AI/ML 工作负载的列式湖仓格式 | 语言无关的内存列式交换标准 |
| 数据布局 | File → Row Group → Column Chunk → Page | Fragment → Data File(无 Row Group) | 连续列 buffer + null bitmap |
| 压缩策略 | 编码(Dictionary/RLE/Delta)+ 通用压缩(Snappy/Zstd) | 自适应结构编码(Mini-block / Full-zip)+ LZ4 | 可选 LZ4/Zstd(Feather V2) |
| GPU 亲和性 | 中(需调优) | 中高(侧重 ML 路径) | 最高(内存层面) |
2. Apache Parquet:需要 GPU-aware 调优的事实标准¶
2.1 格式结构¶
┌─────────────────────────────────┐
│ PAR1 (Magic) │
├─────────────────────────────────┤
│ Row Group 0 │
│ ├─ Column Chunk 0 (Pages...) │ ◄── 最小独立读取单元 = Page
│ ├─ Column Chunk 1 (Pages...) │
│ └─ ... │
├─────────────────────────────────┤
│ Row Group 1 │
│ └─ ... │
├─────────────────────────────────┤
│ File Metadata (Thrift) │ ◄── 包含 schema、row group 统计信息
│ Footer Length (4 bytes) │
│ PAR1 (Magic) │
└─────────────────────────────────┘
关键设计:Page 是压缩/编码的最小单元(通常 ~1 MB),Row Group 是 IO 调度的最小单元。
2.2 GPU 亲和性:85% 的时间花在哪里¶
NVIDIA RAPIDS 团队发现 TPC-H 基准测试中 85% 的运行时花在 Parquet 扫描而非计算1。原因不是格式本身,而是默认配置为 CPU 优化:
| 瓶颈 | 原因 | GPU 影响 |
|---|---|---|
| 元数据解析 | Thrift 格式的 footer 需 CPU 顺序解析,大文件耗时 8-30 秒 | GPU 完全空闲 |
| Page 数量不足 | 默认常为 1 page/列块,cuDF 映射 page→kernel grid | GPU 并行度不足 |
| 变长类型解码 | 字符串的 4 字节 length + data 布局阻止并行读取 | H100 上仅 10-30 GB/s(目标 ≥100) |
| 嵌套类型 | Definition/Repetition level 的 RLE 解码曾是单 warp 瓶颈 | Null 处理耗时 29 ms |
| 隐式同步 | cudaMemcpy 对 host-pageable memory 产生不必要的 CUDA 同步 |
流水线气泡 |
2.3 GPU-aware 调优:从默认到 125 GB/s¶
同一份数据,不同写入配置下的 GPU 扫描性能差异巨大2:
| 调优项 | 默认值 | GPU 优化值 | 效果 |
|---|---|---|---|
| Page 数/列块 | 1 | ≥100 | 匹配 GPU kernel grid size |
| Row Group 行数 | 视写入器而定(常导致 ~100 KB 列块) | 10M+ 行 | GPUDirect Storage 最优于 MiB 级 IO |
| 整数编码 | PLAIN | DELTA_BINARY_PACKED | 更高压缩比,GPU 解码友好 |
| 压缩 | Snappy | Zstd 或无压缩 | Blackwell 硬件解压引擎支持 600 GB/s |
经过调优后,有效带宽可达 125 GB/s(A100)。
2.4 GPU 生态¶
cuDF 是唯一完整的 GPU Parquet Reader——解压和解码全部作为 GPU kernel 执行。2024-2025 的关键优化:
- Microkernel 架构:拆分单体 kernel 为固定宽度/字典等专用 kernel,吞吐提升 117%
- Hybrid Scan API:流水线化读取,原始加速 2x
- GPUDirect Storage:NVMe → GPU 直读,平均吞吐提升 30-50%
- Blackwell 硬件解压引擎:600 GB/s 原生解压 Snappy/LZ4/Deflate/GZip,释放 SM 算力
3. Apache Arrow / Feather:GPU 内存层的事实标准¶
3.1 内存布局:为什么天然适合 GPU¶
Arrow 的列式内存布局精确匹配 GPU 的访问模式:
固定宽度列 (int64):
┌────────────────────────────────────────────────┐
│ Validity Bitmap │ 1 bit/值, 64-byte 对齐 │
├──────────────────┼────────────────────────────-─┤
│ Data Buffer │ 连续 8 字节/值, 64-byte 对齐 │
└────────────────────────────────────────────────┘
▲
GPU warp 内 32 线程访问连续 256 字节 = coalesced load
| 设计决策 | GPU 收益 |
|---|---|
| 64 字节对齐 + padding | 匹配 AVX-512 / GPU cache line,无需额外对齐操作 |
| 固定宽度列连续存储 | Warp 内 coalesced memory access,带宽利用率最高 |
| Null bitmap 独立存储 | 无分支判断,GPU 可并行处理 null |
| 无编码/无压缩(原始 Arrow) | 零解码开销,数据即 GPU 可计算的形态 |
3.2 GPU 集成机制¶
cuDF = Arrow on GPU:cuDF 的 Column 就是 Arrow Array,data/null bitmap/child columns 全部驻留 GPU 显存。
三层 GPU 集成:
graph LR
subgraph "底层:设备抽象"
A[CudaBuffer / CudaContext<br/>Arrow buffer 直接驻留 GPU 显存]
end
subgraph "中层:跨库交换"
B[C Device Data Interface<br/>ArrowDeviceArray + ARROW_DEVICE_CUDA<br/>零拷贝跨库 GPU 数据交换]
end
subgraph "上层:传输协议"
C[Dissociated IPC<br/>元数据走 CPU,数据体留 GPU<br/>Arrow Flight 共享内存: 25 GB/s]
end
A --> B --> C
3.3 Feather:Arrow 的磁盘序列化¶
Feather V2 = Arrow IPC 文件格式 + 可选 LZ4/Zstd 压缩。
局限:Feather 作为磁盘格式不如 Parquet——无编码(未压缩时比 Parquet 大 ~2x),且 cuDF 读 Feather 走 CPU 路径(官方建议转 Parquet 做 GPU IO)。Arrow 的价值在内存层而非磁盘层。
4. Lance:为 ML 数据管道设计的格式¶
4.1 核心设计决策:无 Row Group¶
Parquet 的 Row Group 将所有列耦合在一起——宽表(数千特征列)即使只读一列也要解析所有列的元数据。Lance 的解法是每列独立分页:
Parquet: Lance:
┌─── Row Group ──────────┐ ┌─── Fragment ────────────────┐
│ Col A │ Col B │ Col C │ │ Data File A (独立分页) │
│ (耦合在一起) │ │ Data File B (独立分页) │
└────────────────────────┘ │ Data File C (独立分页) │
└────────────────────────────┘
4.2 自适应结构编码¶
根据数据宽度自动选择编码策略,而非一刀切:
| 数据特征 | 编码策略 | 设计权衡 |
|---|---|---|
| 小数据(int/float/短字符串) | Mini-block(4-8 KB 块) | 透明压缩:可解压单值而不读整块 → 随机访问友好 |
| 大数据(embedding/图片) | Full-zip(连续打包) | 不透明压缩:需整块解压 → 顺序扫描更高效 |
| 结构化字段(常一起访问) | Packed struct(行式存储) | 减少 IO 次数:一次读取获取所有相关字段 |
4.3 随机访问:比 Parquet 快 100-2000x¶
这是 Lance 与 Parquet 最大的性能分野——ML 训练的 shuffle/随机采样依赖快速随机访问:
| 操作 | Parquet | Lance | 原因 |
|---|---|---|---|
| 随机取 1 行 | 扫描 ~1 MB page | 1-2 次 IO | Mini-block 透明压缩 + 独立列分页 |
| 100M 记录取 1000 行 | 1.25 s/key | 0.00062 s/key | 2000x |
| Blob 读取(图片/音频) | 需外部引用 | 原生 Blob V2 | 68x |
4.4 GPU 亲和性:ML 路径而非格式直读¶
Lance 的 GPU 加速不在格式底层(不做 GPU 端解压/解码),而在 ML 数据管道的上层:
| 能力 | 实现 |
|---|---|
| PyTorch DataLoader | LanceDataset(iterable)/ SafeLanceDataset(map-style),内建 batch_readahead |
| Tensor 转换 | Arrow → torch.frombuffer() 零拷贝 |
| GPU 索引训练 | IVF/PQ 训练通过 PyTorch CUDA 加速(accelerator="cuda") |
| 多模态数据 | Blob V2:inline / packed / dedicated / external 四种存储语义 |
| 版本控制 | MVCC,每次写入创建不可变版本,支持 time travel |
4.5 ML 生态集成¶
- PyTorch:原生 DataLoader 支持
- Hugging Face:2025 年原生
hf://URI 支持 - Ray / Spark:分布式读写连接器
- DuckDB:Core extension,支持向量搜索 + 全文检索 + SQL 混合查询
- 向量搜索:内建 IVF、HNSW、PQ、SQ 索引,GPU 加速训练
5. GPU 数据管道中的协作关系¶
三种格式不是互相替代,而是在 GPU 数据管道的不同阶段各司其职:
graph LR
subgraph "持久化层(磁盘/对象存储)"
P[Parquet<br/>通用分析<br/>高压缩]
L[Lance<br/>ML 训练数据<br/>随机访问 + 版本控制]
end
subgraph "内存 / 显存层"
A[Arrow<br/>零拷贝交换<br/>GPU 原生对齐]
end
subgraph "计算层"
G[GPU Kernel<br/>cuDF / PyTorch]
end
P -->|cuDF GPU Reader| A
L -->|CPU 解码| A
A -->|零拷贝| G
选型决策树:
| 场景 | 推荐格式 | 理由 |
|---|---|---|
| GPU 加速 OLAP 分析 | Parquet(GPU-aware 配置)→ cuDF → Arrow | 生态最成熟,cuDF 全 GPU 解码 |
| ML 训练数据管道 | Lance → Arrow → PyTorch | 随机访问快 100x+,版本控制,多模态原生 |
| 进程间 / 框架间数据交换 | Arrow | 唯一选择,零拷贝 GPU 内存共享 |
| 数据湖长期存储 | Parquet | 生态最广,所有引擎都支持 |