L2 缓存管理
当多个 Kernel 反复访问同一块全局内存数据时(如神经网络权重、查找表、配置参数),通过 L2 缓存持久化策略可显著提升带宽并降低访问延迟。
为什么需要 L2 缓存管理
流式访问与持久化访问(Streaming vs Persisting Accesses)
MT GPU 的 L2 缓存默认采用标准替换策略(类似 LRU), 适合大多数访问模式。但某些场景下,这种策略效率较低:
| 访问类型 | 特征 | 缓存行为 |
|---|---|---|
| 流式访问(Streaming Accesses) | 数据仅访问一次 | cache lines 不太可能保留在 L2 中 |
| 持久化访问(Persisting Accesses) | 数据被反复访问 | cache lines 更可能持久保持在 L2 中 |
| 常规访问(Normal Accesses) | 常规访问 | 标准替换策略 |
典型应用场景
深度学习推理是典型的持久化访问(Persisting Accesses)场景:
ResNet-50 网络示例:
- 2600 万个权重参数(约 100MB)
- 正向传递中计算 1600 万次激活
- 权重被多个 layer 反复读取
问题:如果 L2 缓存效率低,每次读取权重都访问 DRAM,带宽瓶颈严重。
解决:将权重数据标记为 Persisting,使其在 L2 中驻留更久。
L2 缓存属性查询
在设置 L2 缓存策略前,需查询设备属性:
musaDeviceProp prop;
musaGetDeviceProperties(&prop, device_id);
// 三个关键属性
size_t l2CacheSize = prop.l2CacheSize; // GPU 总 L2 缓存大小
size_t maxPersistingSize = prop.persistingL2CacheMaxSize; // 可用于持久化的最大 L2 大小
size_t maxWindowBytes = prop.accessPolicyMaxWindowSize; // Access Policy Window 最大尺寸
典型值(以 MUSA S5000 为例):
l2CacheSize: 4MBpersistingL2CacheMaxSize: 3MBaccessPolicyMaxWindowSize: 2MB
设置 L2 预留缓存大小
为持久化访问预留 L2 缓存:
musaDeviceProp prop;
musaGetDeviceProperties(&prop, 0);
// 推荐配置:75% 的 L2 缓存用于持久化访问,或硬件允许的最大值
size_t persistingCacheSize = min(
static_cast<int>(prop.l2CacheSize * 0.75),
prop.persistingL2CacheMaxSize
);
musaDeviceSetLimit(musaLimitPersistingL2CacheSize, persistingCacheSize);
注意事项:
- 此设置是全局的,影响该设备上所有流
- 必须在 Kernel 启动前设置
- 设置后不会自动恢复,程序退出前需手动重置
访问策略窗口(Access Policy Window)
设置预留缓存大小后,需指定哪些数据享受持久化策略:
API 参考
musaStreamAttrValue stream_attribute;
stream_attribute.accessPolicyWindow.base_ptr = reinterpret_cast<void*>(data_ptr); // 全局内存指针
stream_attribute.accessPolicyWindow.num_bytes = window_size; // 窗口大小(字节)
stream_attribute.accessPolicyWindow.hitRatio = 0.6; // 命中比例提示
stream_attribute.accessPolicyWindow.hitProp = musaAccessPropertyPersisting; // 命中时的访问属性
stream_attribute.accessPolicyWindow.missProp = musaAccessPropertyStreaming; // 未命中时的访问属性
musaStreamSetAttribute(stream, musaStreamAttributeAccessPolicyWindow, &stream_attribute);
参数详解
| 参数 | 含义 | 建议值 |
|---|---|---|
base_ptr | 全局内存数据起始地址 | 实际数据指针 |
num_bytes | 窗口大小 | min(数据大小,accessPolicyMaxWindowSize) |
hitRatio | 提示:窗口内多少比例的访问是持久化的 | 0.6-1.0(根据实际访问模式) |
hitProp | 命中的 cache line 采用什么策略 | musaAccessPropertyPersisting |
missProp | 未命中的 cache line 采用什么策略 | musaAccessPropertyStreaming |
实际持久化区域计算
实际持久化区域 = min(num_bytes * hitRatio, L2_set_aside_cache_size)
示例:
- num_bytes = 2MB
- hitRatio = 0.6
- L2_set_aside_cache_size = 3MB
实际持久化区域 = min(2MB * 0.6, 3MB) = 1.2MB
三种访问属性
| 属性 | 行为 | 使用场景 |
|---|---|---|
musaAccessPropertyStreaming | cache lines 优先驱逐,不保留 | 一次性读取的数据 |
musaAccessPropertyPersisting | cache lines 优先保留在 L2 预留区 | 反复读取的权重/参数 |
musaAccessPropertyNormal | 强制重置为标准策略 | 清理不再需要的持久化区域 |
重要:使用 musaAccessPropertyNormal 可以释放持久化缓存,供后续内核使用:
// 方法 1:设置 window 大小为 0(禁用策略窗口)
stream_attribute.accessPolicyWindow.num_bytes = 0;
musaStreamSetAttribute(stream, musaStreamAttributeAccessPolicyWindow, &stream_attribute);
// 方法 2:重置整个 L2 缓存
musaCtxResetPersistingL2Cache();
// 方法 3:设置 Normal 属性覆盖持久化区域
stream_attribute.accessPolicyWindow.hitProp = musaAccessPropertyNormal;
musaStreamSetAttribute(stream, musaStreamAttributeAccessPolicyWindow, &stream_attribute);