企业级视频网站系统开发实战与架构解析
2026/10/15 4:27:26
GPU 显存分配器演进时间线 ├── 2007 -> CUDA 1.0 发布 cudaMalloc: 同步语义,适合图形负载不适合训练循环 ├── 2011-2016 -> Torch7/早期 PyTorch 引入 THC 缓存分配器: 训练循环免同步分配的开端 ├── 2017 -> c10/cuda/CUDACachingAllocator 重构定型: 双池 + Block 分裂合并成为现代形态 ├── 2020-11 -> CUDA 11.2 引入 cudaMallocAsync 流序分配: 分配/释放进入流序,驱动级内存池 ├── 2023-10 -> PyTorch 2.1 引入 expandable_segments(实验): VMM 虚拟内存映射治碎片 ├── 2024-2025 -> memory snapshot 可视化成熟 + expandable_segments 转为推荐配置 └── 2026-06 -> PyTorch 官方 DevLog 系统分析 CUDA Graph 池碎片 + RFC 推进与 NCCL VMM 互操作DeviceCachingAllocator 内部结构 ├── per-device Malloc 状态 │ ├── large_blocks: 大块池(>= 1MB 请求,2MB 圆整) │ ├── small_blocks: 小块池(< 1MB 请求,512B 圆整) │ ├── active_blocks: 已分配块索引(按地址查块) │ ├── cudaMalloc 扩容链: 池未命中 -> 驱动申请新 segment │ └── 统计器: allocated / reserved / active / retries / ooms └── Block(最小管理单元) ├── ptr / size: 设备地址与字节数 ├── stream: 分配时所处流的身份 ├── prev / next: 同一 segment 内分裂链的相邻块指针 ├── stream_uses: 跨流使用记录(事件护栏的依据) └── allocated: 缓存态(false=已释放待复用)与活跃态圆整与分界常量 ├── 小请求界: kSmallSize = 1MB,之下进 small pool ├── 小圆整: 512 字节粒度,小请求向上取整 ├── 小 segment: kSmallBuffer = 2MB(小块从 2MB 段上切出来) ├── 大圆整: 2MB 粒度,大请求向上取整 └── 大 segment: kLargeBuffer = 20MB(大块至少按 20MB 段向驱动申请)malloc(size) 全路径 ├── 圆整 -> 小于 1MB 进 small pool / 否则进 large pool ├── best-fit 查找 -> pool.blocks.lower_bound(size) │ ├── 命中且剩余量值得跟踪 -> split: 大块裂成 [已用块 + 余量回池] │ ├── 命中且无需分裂 -> 直接标 allocated 返回 │ └── 未命中 ↓ ├── 池扩充 -> cudaMalloc( max(请求, 段下限) ) 新 segment │ ├── 成功 -> 切块返回,reserved 增长 │ └── CUDA_ERROR_OUT_OF_MEMORY ↓ └── OOM 重试链 ├── 释放全部缓存块(release_cached_blocks)-> cudaDeviceSynchronize -> 重试 ├── 再失败 -> 遍历释放 + 打印完整统计报告 -> 抛出 RuntimeError └── 全程计数: num_alloc_retries / num_ooms 递增(诊断金指标)free(block) 全路径 ├── 标记 allocated=false -> 插回所属池的有序集合 ├── 合并尝试 -> prev 空闲则并块 / next 空闲则并块(Doubly-linked 分裂链上原地合并) ├── 结果: 块驻留缓存(地址不还给驱动)-> 下次同尺寸请求零开销复用 └── 例外: 跨流使用过 -> 先走事件护栏(见下一章)