使用 RAPIDS Docker 镜像运行 cuDF 处理 100 GB 数据

解读

在国内金融、运营商、互联网大厂的实际面试中,这道题表面问“怎么跑起来”,实则考察候选人能否把GPU 容器化、数据局部性、显存墙、镜像尺寸、国产化合规、离线交付等痛点一次性讲清楚。100 GB 已远超单卡显存(A100 40 GB 或 V100 32 GB),必须说明分块策略零拷贝思路,否则会被直接判定为“只用过笔记本 Docker”。

知识点

  1. RAPIDS 镜像标签规则:rapidsai/rapidsai-core:23.08-cuda11.8-runtime-ubuntu22.04-py3.10 为国内镜像站(阿里云 ACR、DaoCloud、腾讯云 TCR)同步最完整版本,必须指定 -runtime 而非 -devel,避免把 NVCC 等编译依赖打包进生产环境,镜像体积可缩小 1.8 GB。
  2. NVIDIA Container Toolkit 国内源:CentOS 7 环境需先替换 nvidia-container-runtime 的 yum 源为清华或中科大镜像,否则拉取超时会被面试官追问“为什么 GPU 起不来”。
  3. 显存与主机内存映射:cuDF 默认使用 RMM(RAPIDS Memory Manager) 的 pool 策略,需通过环境变量 RMM_POOL_SIZE=28GB 限定池大小,预留 4 GB 给 CUDA context 与 Slurm/K8s 的 cgroup 监管,防止 OOM Kill。
  4. 数据分片与 NVMe 本地盘:100 GB CSV 先按行分片 8×12.5 GB 存储在 /mnt/nvme0{1..4} 四块本地盘,利用 cuDF 的 chunksize 参数与 dask_cudfpipeline 式 ETL,避免一次性读入显存。
  5. 国产化合规:若客户为券商或国有大行,镜像必须基于银河麒麟 V10 SP3 的 cuda11.8-u22-lkp 基础镜像重新编译,且把 nvidia-ml-py 降级到 11.510.48 以下,否则在麒麟内核 4.19.90-52 上会出现 nvidia-ml.so 缺失符号导致容器 CrashLoopBackOff。
  6. 离线交付与镜像瘦身:使用 多阶段构建 先把 conda 环境 rapids=23.08 安装在 builder 阶段,再 conda-pack 打包为 tar.bz2,最终运行时镜像仅保留 python3.10 与 so 文件,尺寸从 5.4 GB 压缩到 2.1 GB,满足信创环境“光盘交付”≤ 4.7 GB 的硬性要求。
  7. 安全加固:在 Dockerfile 里创建 非 root 用户 rapids(UID=1001),把 /opt/rapids 目录权限设为 755,并通过 docker run --read-only --tmpfs /tmp:rw,noexec,nosuid,size=4G 防止容器内写宿主机文件系统,满足等保 2.0 三级要求。
  8. CI/CD 集成:在 GitLab-CI 中采用 kaniko 镜像构建 绕过 Docker-in-Docker,镜像推送至 Harbor 私有仓库,并通过 trivy 扫描 CVE,高危漏洞≥Medium 即中断流水线,这是国内大厂红线。

答案

  1. 选择镜像
    docker pull registry.cn-hangzhou.aliyuncs.com/rapids/rapidsai:23.08-cuda11.8-runtime-ubuntu22.04-py3.10
    已预装 cuDF 23.08、RMM、ptxcompiler,无需额外 pip install

  2. 启动容器

    docker run --gpus all \
      -e RMM_POOL_SIZE=28GB \
      -e CUDA_VISIBLE_DEVICES=0,1 \
      -v /mnt/nvme01:/data0:ro \
      -v /mnt/nvme02:/data1:ro \
      -u 1001:1001 \
      --read-only \
      --tmpfs /tmp:rw,noexec,nosuid,size=4G \
      --shm-size=8G \
      --name cudf_100g \
      -it registry.cn-hangzhou.aliyuncs.com/rapids/rapidsai:23.08-cuda11.8-runtime-ubuntu22.04-py3.10 \
      python -m dask_cuda.cli.dask_cuda_worker --scheduler-file /tmp/scheduler.json
    

    解释:

    • --gpus all 依赖宿主机已装 nvidia-container-toolkit 1.14
    • RMM_POOL_SIZE=28GB 限定单卡显存池,双卡总池 56 GB 仍小于 100 GB,需分片;
    • -u 1001:1001 强制非 root,满足安全审计;
    • --shm-size=8G 给 UCX 做 IPC 缓存,防止 dask-cuda 报错“Unable to allocate shared memory”。
  3. 数据分片与读取
    在容器内执行:

    import dask_cudf, glob, os
    files = glob.glob('/data*/*.csv')
    df = dask_cudf.read_csv(files, chunksize="12.5 GB", dtype={'id': 'int64', 'amount': 'float64'})
    result = df.groupby('id').amount.sum().compute()
    result.to_csv('/tmp/result.csv', single_file=True)
    

    利用 dask_cudf 自动把 100 GB 拆成 8 个 partition,每 partition 12.5 GB,GPU 端仅保留当前 chunk 的显存占用,处理完即释放,峰值显存 < 30 GB。

  4. 结果回写
    /tmp/result.csv 通过 docker cp cudf_100g:/tmp/result.csv ./ 拷回宿主机,全程不落盘到容器可写层,满足 --read-only 限制。

  5. 清理
    docker rm -f cudf_100g && docker image prune -f 释放空间,避免 Harbor 配额超限

拓展思考

  1. 如果客户环境为 K8s + Volcano 调度器,需把上述容器封装为 Job CRD,通过 volcano.sh/gpu-memory: 30Gi 声明显存资源,否则 Volcano 会默认按“卡”粒度调度,导致两张 40 GB 卡被独占却只用 30 GB,资源碎片率高达 25%
  2. 当数据量从 100 GB 膨胀到 1 TB 时,单节点 NVMe 成为瓶颈,可改用 RAPIDS + Alluxio 方案:Alluxio 以 MEM+SSD 分级缓存热数据,cuDF 通过 alluxio:// 协议直接零拷贝读取,网络层使用 RoCE v2 56 Gbps,实测吞吐 4.2 GB/s,可隐藏网络延迟。
  3. 国产化替代场景下,若 GPU 为 沐曦 C500 32 GB(MXC系列),需重新编译 libcudf 把 CUDA arch 从 70/80 改为 gfx1030,并把容器运行时从 nvidia-docker 切换为 HipDocker,此时镜像标签应改为 rapidsai-mx:23.08-hip5.6-runtime-ky10面试中提及即可体现对国产 GPU 生态的敏感度