PyTorch 記憶體管理
===
###### tags: `NVIDIA`
###### tags: `NVIDIA`, `GPU`, `cuda`, `PyTorch`, `記憶體管理`, `記憶體碎片化`, `OOM`
<br>
[TOC]
<br>
## OOM
### [一文读懂 PyTorch 显存管理机制](https://www.cvmart.net/community/detail/6242)
- 底下圖容易理解
[](https://hackmd.io/_uploads/BJPxuFHJT.png)
[](https://hackmd.io/_uploads/rJxmk9H1T.png)
- > CUDA out of memory. Tried to allocate 1.24 GiB (GPU 0; 15.78 GiB total capacity; 10.34 GiB already allocated; 435.50 MiB free; 14.21 GiB reserved in total by PyTorch)
- Tried to allocate:指本次 malloc 時預計分配的 alloc_size;
- total capacity:由 cudaMemGetInfo 返回的 device 顯存總量;
- already allocated:由統計數據記錄,當前為止請求分配的 size 的總和;
- free:由 cudaMemGetInfo 返回的 device 顯存剩餘量;
- reserved:BlockPool 中所有 Block 的大小,與已經分配的 Block 大小的總和。
即 [reserved] = [already allocated] + [sum size of 2 BlockPools]
<br>
- > 注意,reserved + free 並不等同於 total capacity,因為 reserved 只記錄了通過 PyTorch 分配的顯存,如果用戶手動調用 cudaMalloc 或通過其他手段分配到了顯存,是沒法在這個報錯信息中追踪到的(又因為一般 PyTorch 分配的顯存佔大部分,分配失敗的報錯信息一般也是由PyTorch 反饋的)。
- reserved 14.21 GiB + Free 435.50 MiB = 14.64GiB < Total 15.78 GiB
- 有 1.14 GiB 未被追蹤(可能有其他 App 在跑?)
<br>
- > 在這個例子裡,device 只剩 435.5MB,不夠 1.24GB,而 PyTorch 自己保留了 14.21GB(儲存在 Block 裡),其中分配了 10.3GB,剩 3.9GB。那為何不能從這 3.9GB 剩餘當中分配 1.2GB 呢?原因肯定是碎片化了,而且是做了整理也不行的情況。
- 情況:reserved 14.21 GiB >> allocated 10.34 GiB
- 14.21GiB - 10.34 GiB = 3.87GiB > 1.24 GiB
- 要配置 1.24 GiB,明明有 3.87GiB,卻看得到吃不到
- 這時候才需要設定 max_split_size_mb
<br>
### [通过设置PYTORCH_CUDA_ALLOC_CONF中的 max_split_size_mb 解决Pytorch的显存碎片化导致的CUDA:Out Of Memory问题](https://blog.csdn.net/MirageTanker/article/details/127998036)
> :warning: 而报错信息中的”3.43 GiB free”实际上是指~~pytorch所能找到的最大的空闲Block的大小,而非总的空闲空间大小~~
> - 根據這篇[一文读懂 PyTorch 显存管理机制](https://www.cvmart.net/community/detail/6242),free 是指由 cudaMemGetInfo 返回的 device 显存剩余量;
> torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.00 GiB (GPU 1; 10.91 GiB total capacity; 9.98 GiB already allocated; 297.38 MiB free; 10.02 GiB reserved in total by PyTorch) If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF
- 錯誤類型: OOM
- 前提:記憶體配置必須是連續的
- pytorch显存管理机制中,显存请求必须是连续的
- GPU 資源記憶體情況
- 使用 GPU: 1
- 總容量 10.91 GiB
- 已分配 9.98 GiB
- 剩餘 297.38 MiB <- 文章意思是指 最大可配置的請求
- PyTorch 預留 10.02GiB
- 想配置 2.00GiB 的連續空間,卻只有 297.38 GiB
- 如果:預留空間遠大於已分配空間(reserved memory is >> allocated memory)
- 表示有太多片段空間,無法組成連續空間
- 需設定 `export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:1024` (範例)
- 但 10.02GiB >> 9.98 GiB 不成立
---
> torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 1024.00 MiB (GPU 0; 10.92 GiB total capacity; 780.17 MiB already allocated; 790.50 MiB free; 794.00 MiB reserved in total by PyTorch) If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF
- GPU 資源記憶體情況
- 使用 GPU: 0
- 總容量 10.92 GiB
- 已分配 780.17 MiB
- 剩餘 790.50 MiB <- 文章意思是指 最大可配置的請求
- PyTorch 預留 794MiB
- 794MiB >> 780.17 MiB 不成立
- 想配置 1024.00 MiB 的連續空間,卻只有 790.50 MiB
<br>
## max_split_size_mb
- [[pytorch][code] kLargeBuffer](https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L379) = 20
- [[pytorch][code] If reserved memory is >> allocated memory try setting max_split_size_mb to avoid fragmentation. See documentation for Memory Management and PYTORCH_CUDA_ALLOC_CONF](https://github.com/pytorch/pytorch/blob/a5b848aec10b15b1f903804308eed4140c5263cb/c10/cuda/CUDACachingAllocator.cpp#L558)
- ### [一文读懂 PyTorch 显存管理机制](https://www.cvmart.net/community/detail/6242)
> 关于阈值 max_split_size_mb ,直觉来说应该是大于某个阈值的 Block 比较大,适合拆分成稍小的几个 Block,但这里却设置为小于这一阈值的 Block 才进行拆分。个人理解是,PyTorch 认为,从统计上来说大部分内存申请都是小于某个阈值的,这些大小的 Block 按照常规处理,进行拆分与碎片管理;==**但对大于阈值的 Block 而言,PyTorch 认为这些大的 Block 申请时开销大(时间,失败风险),可以留待分配给下次较大的请求,于是不适合拆分。**== 默认情况下阈值变量 max_split_size_mb 为 INT_MAX,即全部 Block 都可以拆分。
- max_split_size_mb 預設是 INT_MAX
- 這樣理解

假設記憶體位址是 0-99 (方便描述)
假設 [20-39], [80-89] 已經被使用
剩下 [0-19], [40-79], [90-99] 可用
- **CASE 1: 需求是20 (GPU 很多顆)**
max_split_size_mb 設為100
剩餘區間 20 <= [0-19], [40-79] < 100
兩個區間都可拿來用 (GPU 很多顆,記憶體夠,隨便切,有錢就是任性)
- **CASE 2: 需求是20 (GPU 只有一顆)**
閥值設為 40 (max_split_size_mb:40)
這個區間 [40-79] >=40 ,不建議分割
只能選 [0-19] 來用
- **小結:**
- [40-79] 要預留給:之後如果有較大的需求,比如 30,就容易配置成功
- 如果[40-79]不預留,就先拿去切割,將來有 30 單位需求,就會面臨無法配置而容易 OOM
- [0-19], [90-99] <-- 明明還有 30 單位,看得到卻吃不到
- 類比經驗:在 twcc.ai 上
看到系統還有 10 個 CPU
但每個 worker 都只剩 2, 3 個 CPU
卻無法配置 8CPU 的需求
如果需求量都是小的 CPU
應該要先集中在一台配置 <--- 有點像是 max_split_size_mb 概念
免得將來有較大的需求,如 8CPU, 16CPU, 32CPU,就會容易配置失敗
- ### [How can I set max_split_size_mb to avoid fragmentation in Pytorch?](https://iamholumeedey007.medium.com/ff0a6c641098)
```
# Get the current memory allocator state
allocator = torch.cuda.memory._get_memory_allocator()
# Update max_split_size_mb in the memory allocator
allocator.set_max_split_size(max_split_size_mb * 1024 * 1024)
```
<br>
### [心得][看法] max_split_size_mb 的概念
pytorch 在管理那些記憶體片段,是採用雙向鏈接串列在管理空閒 block
所以只能循序拜訪
max_split_size_mb = 128
逐一對 空閒block 搜尋
如果遇到有一塊 <= 128 mb 就拿來用
若遇到有一塊 600 mb 就略過
感覺概念是這樣
避免 600 mb 這區塊造成碎片而無法利用
若 600 mb 拿去用,
將來若要配置 512 mb (假設),
配置失敗的風險就會比較高
<br>
### [心得][看法] max_split_size_mb 設成最小 21,會不會更好?
如果把 max_split_size_mb 設成最小 21
(設成 20 會有 error ---> RuntimeError: CachingAllocator option max_split_size_mb too small, must be > 20)
這樣不是更好?
這題沒有找到確切答案
但評估可能的影響:空閒 block 的搜尋,效能變差
worst case:
一路上,空閒 block 都超過 21mb,
最差情況:可能要找到後面,才發現有 <= 21mb 的空閒 block 可用
best case:
對照 max_split_size_mb=INT_MAX
一路上,空閒 block 都完全小於 INT_MAX
i.e. 看到的 空閒 block,都可以拿來用,在前面就很快要到 空閒 block