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/BJPxuFHJT.png) [![](https://hackmd.io/_uploads/rJxmk9H1T.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 - 這樣理解 ![](https://hackmd.io/_uploads/B1ak6CLJ6.png) 假設記憶體位址是 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