-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DeepLearning] Inference 때의 GPU 상의 메모리 할당 분석 및 정리 #33
Comments
원본 코드 변형 및 디렉토리 구조저는 이걸 현재 레포에 추가할까 고민했지만, 레포와는 별개의 실험이니, 요기에 정리하기로 하였습니다.
article_implem.py
utils
- memory.py
- plot.py
접기/펼치기 버튼import pandas as pd
import torch
from torchvision.models import resnet18
from utils.memory import log_mem, getMemoryUsage
from utils.plot import plot_mem
# TODO : 조건을 선택해서, 실험해보세요.
base_dir = '.'
is_no_grad = False
model = resnet18().cuda()
model.eval()
# torch.save(model, "resnet18.pt") # 모델 용량 확인용, 46.8MB
print("------------------------------------ after upload model to gpu ---------------------------------------------")
print(f"Allocated: {round(torch.cuda.memory_allocated(0) / 1024 ** 2, 1)}MB, "
f"'Max Allocated: {round(torch.cuda.max_memory_allocated(0) / 1024 ** 2, 1)}MB, "
f" Cached: {round(torch.cuda.memory_cached(0) / 1024 ** 2, 1)}MB, "
f" Max Cached: {round(torch.cuda.max_memory_cached(0) / 1024 ** 2, 1)} MB, NVIDIA_SMI: {getMemoryUsage()} MB")
bs = 1
input = torch.rand(bs, 3, 224, 224).cuda()
mem_log = []
print("------------------------------------ after upload input to gpu ---------------------------------------------")
print(f"Allocated: {round(torch.cuda.memory_allocated(0) / 1024 ** 2, 1)}MB, "
f"'Max Allocated: {round(torch.cuda.max_memory_allocated(0) / 1024 ** 2, 1)}MB, "
f" Cached: {round(torch.cuda.memory_cached(0) / 1024 ** 2, 1)}MB, "
f" Max Cached: {round(torch.cuda.max_memory_cached(0) / 1024 ** 2, 1)} MB, NVIDIA_SMI: {getMemoryUsage()} MB")
try:
if is_no_grad:
with torch.no_grad():
no_grad = "_no_grad"
print("------------------------------------ 1 Epoch ----------------------------------")
mem_log.extend(log_mem(model, input, exp='1'))
print("------------------------------------ 2 Epoch ----------------------------------")
mem_log.extend(log_mem(model, input, exp='2'))
print("------------------------------------ 3 Epoch ----------------------------------")
mem_log.extend(log_mem(model, input, exp='3'))
else:
no_grad = ""
print("------------------------------------ 1 Epoch ----------------------------------")
mem_log.extend(log_mem(model, input, exp='1'))
print("------------------------------------ 2 Epoch ----------------------------------")
mem_log.extend(log_mem(model, input, exp='2'))
print("------------------------------------ 3 Epoch ----------------------------------")
mem_log.extend(log_mem(model, input, exp='3'))
except Exception as e:
print(e)
print(f'log_mem failed because of {e}')
df = pd.DataFrame(mem_log)
plot_mem(df, exps=['1', '2', '3'], output_file=f'{base_dir}/baseline_memory_plot_{bs}{no_grad}.png')
접기/펼치기 버튼from matplotlib import pyplot as plt
def plot_mem(
df,
exps=None,
normalize_call_idx=True,
normalize_mem_all=True,
filter_fwd=False,
return_df=False,
output_file=None
):
if exps is None:
exps = df.exp.drop_duplicates()
fig, ax = plt.subplots(figsize=(20, 10))
for exp in exps:
df_ = df[df.exp == exp]
if normalize_call_idx:
df_.call_idx = df_.call_idx / df_.call_idx.max()
if normalize_mem_all:
df_.mem_all = df_.mem_all - df_[df_.call_idx == df_.call_idx.min()].mem_all.iloc[0]
df_.mem_all = df_.mem_all // 2 ** 20
if filter_fwd:
layer_idx = 0
callidx_stop = df_[(df_["layer_idx"] == layer_idx) & (df_["hook_type"] == "fwd")]["call_idx"].iloc[0]
df_ = df_[df_["call_idx"] <= callidx_stop]
# df_ = df_[df_.call_idx < df_[df_.layer_idx=='bwd'].call_idx.min()]
plot = df_.plot(ax=ax, x='call_idx', y='mem_all', label=exp)
if output_file:
plot.get_figure().savefig(output_file)
if return_df:
return df_
접기/펼치기 버튼from torch.utils.checkpoint import checkpoint_sequential
import torch
from pynvml.smi import nvidia_smi
nvsmi = nvidia_smi.getInstance()
def getMemoryUsage():
usage = nvsmi.DeviceQuery("memory.used")["gpu"][0]["fb_memory_usage"]
return "%d %s" % (usage["used"], usage["unit"])
def _get_gpu_mem(synchronize=True, empty_cache=True):
print(f"Allocated: {round(torch.cuda.memory_allocated(0) / 1024 ** 2, 1)}MB, "
f"'Max Allocated: {round(torch.cuda.max_memory_allocated(0) / 1024 ** 2, 1)}MB, "
f" Cached: {round(torch.cuda.memory_cached(0) / 1024 ** 2, 1)}MB, "
f" Max Cached: {round(torch.cuda.max_memory_cached(0) / 1024 ** 2, 1)} MB, NVIDIA_SMI: {getMemoryUsage()} MB")
return torch.cuda.memory_allocated(), torch.cuda.memory_cached()
def _generate_mem_hook(handle_ref, mem, idx, hook_type, exp):
def hook(self, *args):
if len(mem) == 0 or mem[-1]["exp"] != exp:
call_idx = 0
else:
call_idx = mem[-1]["call_idx"] + 1
mem_all, mem_cached = _get_gpu_mem()
torch.cuda.synchronize()
mem.append({
'layer_idx': idx,
'call_idx': call_idx,
'layer_type': type(self).__name__,
'exp': exp,
'hook_type': hook_type,
'mem_all': mem_all,
'mem_cached': mem_cached,
})
return hook
def _add_memory_hooks(idx, mod, mem_log, exp, hr):
h = mod.register_forward_pre_hook(_generate_mem_hook(hr, mem_log, idx, 'pre', exp))
hr.append(h)
h = mod.register_forward_hook(_generate_mem_hook(hr, mem_log, idx, 'fwd', exp))
hr.append(h)
h = mod.register_backward_hook(_generate_mem_hook(hr, mem_log, idx, 'bwd', exp))
hr.append(h)
def log_mem(model, inp, mem_log=None, exp=None):
mem_log = mem_log or []
exp = exp or f'exp_{len(mem_log)}'
hr = []
for idx, module in enumerate(model.modules()):
_add_memory_hooks(idx, module, mem_log, exp, hr)
try:
out = model(inp)
# loss = out.sum()
# loss.backward()
finally:
[h.remove() for h in hr]
return mem_log |
실험 Batch Size = 1두 경우 모두,
접기/펼치기 버튼
접기/펼치기 버튼
|
실험 Batch Size = 128두 경우 모두,
접기/펼치기 버튼
마지막 결과 값으로, 그래프 상 (사진) 접기/펼치기 버튼
|
실험 Batch Size = 512두 경우 모두,
접기/펼치기 버튼
접기/펼치기 버튼
|
결론위의 결과를 기반으로 정리를 하면 아래와 같이 정리할 수 있을거 같으며,
위의 정보를 활용하여,
|
또한, 이를 공부하다 여러가지 더 깊게 정리할 수 있었습니다.
https://pytorch.org/docs/stable/notes/cuda.html#cuda-memory-management
위 함수를 활용해서, 아래와 같이 정리된 표로 나타낼 수 있으며, https://discuss.pytorch.org/t/does-cuda-cache-memory-for-future-usage/87680 https://discuss.pytorch.org/t/clearing-the-gpu-is-a-headache/84762/4 이 자료들을 참고하여, 아래 대사를 통해서,
그래서, 이를 확인을 해보았고, 아래와 같이 비슷한 범위내에서 만족을 하고 있는 것 또한 확인할 수 있었습니다. |
참고자료
https://www.sicara.ai/blog/2019-28-10-deep-learning-memory-usage-and-pytorch-optimization-tricks
위 글을 보면서,
학습
을 할 때의GPU
상에memory
가 쌓이는 순서 및release
되는 방향에 대해서, 수치적으로 이해를 할 수 있었습니다.하지만, 실제로
torch
에서는cache
에 대해서도 고려를 해주어야할 필요도 있고,with torch.no_grad()
를 통해서,inference
를 할 때 또한, 생각을 해주어야 할 필요가 있습니다.https://pytorch.org/docs/stable/notes/cuda.html#cuda-memory-management
그렇기에, 이를 참고하여, 실제로
train
과inference
를 할 때,GPU memory
와torch memory
에서는 어떠하게 다르며 이를 분석하고자, 아래와 같은 실험을 하게되었습니다. 대부분의model infernce
API
는with torch.no_grad()
를 사용을 많이하게 되어있는 데, 이를 수치적으로 어떠한 만큼 효율을 낼 수 있는지 보여주고 싶습니다.The text was updated successfully, but these errors were encountered: