분석시각화 대회 코드 공유 게시물은
내용 확인 후
좋아요(투표) 가능합니다.
ChatGPT 활용 AI 경진대회 월간 데이콘
[완전 초보자용] 전처리가 끝난 데이터를 불러오기 + OOM 컨트롤하기
빠르게 압축된 내용만을 원하시는 분들을 위해..!
많지는 않지만 공유하신 내용들을 보면 전처리 시간이 오래 걸린다거나, 여기서 메모리가 터져버려서 제대로 학습을 진행하지 못하는 경우가 좀 있는 것 같더라구요!
데이터 양이 적은 것도 아니라서, 예를 들어 POS 태깅 같은 전처리를 포함하고 있다면 매번 학습할 때마다 엄청난 시간이 소요되겠죠.
그래서 내가 시도해본 전처리가 유의미한 성능 향상으로 이어지는 경우, 이를 반복적으로 수행하지 않고 딱 한 번만 돌린 후 파일을 저장해서 이를 필요할 때마다 불러오는 방식을 쓰는 것이 훨씬 효율적입니다.
예를 들어 영어 글자를 소문자로 바꾸는 lowering을 적용했다고 가정합시다.
이때 우리는 train/test dataset을 판다스로 불러옵니다.
간단히 코드로 써보면 이렇게요.
import pandas as pd train_df = pd.read_csv('train.csv') test_df = pd.read_csv('test.csv') train_df['cleaned'] = train_df['text'].apply(lambda x: x.lower()) test_df['cleaned'] = test_df['text'].apply(lambda x: x.lower())
뭐 이런 식으로 작성하고 나중에 Dataset을 구성할 때는 cleaned series에 저장된 텍스트를 가져오겠죠.
근데 매번 이걸 할 바에는 그냥 저장을 해두면 됩니다.
train_df.to_csv('cleaned_train.csv', index=False) test_df.to_csv('cleaned_test.csv', index=False)
인덱스를 False로 하지 않으면 열이 하나 더 생겨버리니까 index=False로 해주시구요.
사실 이런 식으로 파일을 저장해서 하든 안하든 어차피 GPT가 응답한 코드로만 학습을 돌릴 수 있는 로직이기 때문에 문제되지 않습니다.
지금 같은 경우는 굉장히 간단한 함수지만 lemmatization과 같은 기법을 적용하려고 해보셨던 분들은 이런식으로 파일을 저장하고 불러오는 것이 훨씬 편하겠죠?
이렇게 전처리를 딱 한 번만해서 파일을 저장하고 불러오는 방법에 대해 간단히 살펴봤습니다.
(구글 드라이브에 파일을 저장하고 이 파일을 gdown 라이브러리로 불러오도록 하는 방식도 존재합니다)
이번엔 OOM, Out Of Memory에 대해 알아보죠.
우리는 딥러닝 모델이 빠른 학습을 하도록 GPU 자원을 사용합니다.
물론 CPU를 사용해도 되지만 그럼 학습 한 바퀴를 도는데 며칠이 걸릴 것으로 예상되는 걸 볼 수 있게 됩니다.
특히나 지금 대회처럼 데이터의 개수가 학습/추론이 각각 4만/8만이 넘어가는 상황이라면 더욱 그렇겠죠.
자 코랩 환경을 예시로 볼까요?
Disk는 기타 모든 데이터들을 아우르는 공간을 뜻합니다.
이를테면 학습, 추론 csv파일도 포함되고, 각종 라이브러리를 다운 받을 때 이 공간에 할당됩니다.
또한 결과물들을 저장하면서 생긴 추가 파일들도 이 공간을 차지합니다.
코랩의 경우 78G까지 여유가 있으니까 각종 라이브러리를 다운로드 받거나 각 epoch마다 모델의 state dict를 저장하는 것 등에 부담이 없겠네요!
(물론 런타임이 끊기는 순간 ㅂ2입니다... 가상환경이 초기화되기 때문에 전부 다시 다운로드 받아야하죠)
그래서 진짜 중요한건 사실 GPU RAM이죠.
지금 따로 뭐 활성화시키진 않아서 보이지 않습니다만 학습중에는 저 선이 높게 올라와 있어요.
그리고 보통은 메모리가 12G 인 GPU가 할당됩니다.(코랩 일반 버전 기준)
이게 무슨 뜻이냐~
우리는 학습 과정에서 이런 세팅을 많이 해줍니다.
import torch from transformers import AutoModel device = 'cuda' if torch.cuda.is_available() else 'cpu' model = AutoModel.from_pretrained(model_name).to(device)
위 코드는 cuda, 즉 GPU가 사용가능하다면 device를 GPU로 정하고, 그렇지 않으면 cpu로 정한다는 뜻입니다.
따라서 모델이 GPU에 올라갔으면 거기에 입력되는 모든 값들도 GPU에 올라가야 합니다.
for batch in train_dataloader: input_ids = batch['input_ids'] attention_mask = batch['attention_maks'] labels = batch['labels'] output = model(input_ids, attention_mask) logits = output.logits
만약 이런식으로 코드를 짜게 된다면 무조~~건 오류가 발생합니다.
왜냐하면 "모델은 GPU로 연산할게 ok!" 세팅이 되어 있는데,
input들은 "나는 CPU에 있는디?"하기 때문이죠.
따라서 이놈들을 전부 to(device) 해줘야 학습이 가능합니다.
for batch in train_dataloader: input_ids = batch['input_ids'].to(device) attention_mask = batch['attention_maks'].to(device) labels = batch['labels'].to(device) output = model(input_ids, attention_mask) logits = output.logits
자 근데 희한한게 있습니다.
우리는 아직 학습을 끝까지 돌리지도 않았는데 OOM이 발생합니다.
이제 막 train loop에 들어가는데..! OOM에 막히는거죠.
아주 감사하게도 이 똑똑한 놈들은 우리가 GPU 자원을 얼마나 사용할지 계산을 미리하고 그 메모리를 할당 받습니다.
예를 들어,
1) 자 데이터의 개수는 4만개 + 8만개 = 12만개고..
2) 각 텍스트의 토큰은 최대 512개 까지? ok
3) 이걸 batch마다 8개씩 하면...
음 12G의 GPU 메모리를 할당받아야겠군.
이 되는 것입니다.
물론 메모리 관리에 관한 여러가지 팁들도 있고 신경써야하는 사항들도 많지만,
저같은 초보자분들은 딱 두가지만 기억합시다.
NLP에서 max_length라 함은 일반적으로 모델이 input으로 받는 token의 길이를 뜻합니다.
(이는 모델의 tokenizer에 따라 달라질 수 있습니다)
대표적으로 256, 512 등의 단위를 많이 쓰는데요, 즉 어떤 문장들을 토큰 단위로 쪼갰을 때, 몇 개까지 입력으로 받을거냐 하는 뜻입니다.
제가 사용하는 코드의 일부를 보여드리자면,
text = self.dataframe.loc[idx, "preprocessed_text"] self.encoded_dict[idx] = self.tokenizer.encode_plus( text, add_special_tokens=True, max_length=self.max_length, padding='max_length', truncation=True, return_tensors='pt', return_attention_mask=True, return_token_type_ids=False )
이런식으로 tokenizer의 encode_plus 함수를 사용하여 텍스트를 숫자로 바꾸고 임베딩 벡터로 바꿀 수 있게되죠.
이때 max_length = self.max_length인데, 이것도 역시 하이퍼 파라미터기 때문에 여러분의 판단대로 값을 조정해주시면 됩니다.
이를 위해서는 주어진 train/test.csv 파일을 분석해보고 어떤 길이가 적당할지 감을 잡는게 좋겠죠?
다음은 batch_size입니다.
이것 또한 하이퍼 파라미터인데, 뭐 이놈은 말이 참 많습니다.
배치를 어느정도 키우는게 좋다, 아니다 이건 그냥 처리 시간과 관련 있을 뿐 이 값을 조정하는건 의미가 없다..
어쨌든 뭣보다 확실한 것은 '무조~~~건 확실히 좋은 배치사이즈' 같은건 정해져 있지 않다는 거죠.
우리한테 좋다고 하는 것은 어쩌면 '학습이 빨리 끝나는 것'일지도 모릅니다..!
따라서 웬만하면 돌릴 수 있는 최대한의 배치사이즈를 정하는게 좋습니다.
('일반적으로' 학습이 훨씬 빨리 끝나고 성능이 향상될 때가 많습니다)
대표적으로는 BERT모델을 기준으로 base는 8에서 16, large는 4에서 8정도로 돌려야 합니다.
물론 이것도 GPU 메모리 크기에 따라 가능할수도 있고 아닐 수도 있습니다.
특별한 추가 세팅 없이 bert-large로 돌려보려다가 메모리가 터지거나, 메모리를 맞춰놨더니 학습시간이 15시간 넘게 걸려서 저는 관뒀습니다.
결국 데이터의 양을 줄여주거나, 배치를 조절하거나, 비싼 자원을 쓰거나(?) 뭐라도 해야 큰 모델을 돌릴 수 있다는 것이죠.
아니 근데 메모리가 얼마나 남았는지 어떻게 아냐고!!
네, 이건 제가 W&B라는 mlops 툴을 사용하고 있어서 로그를 캡쳐해온 것인데요.
뭐 이게 아니더라도 각자 모델을 돌리는 환경 내에서 버튼 하나만 클릭해도 몇 퍼센트의 GPU 메모리를 쓰고 있는지 알 수 있습니다.
그래서, '어? 배치 8인데도 아직 50퍼도 할당 안됐네?' 라면 좀 더 키워도 되는 것이죠.
경험상 60퍼를 조금 넘게 차지하고 있을 때 배치를 두 배로 키우면 80퍼를 초과하여 그것보다는 큰 값을 쓸 수 없었습니다.
예를 들어 배치 8일 때 60퍼 -> 배치 16일 때 80퍼 -> 한계임. 이렇게 되는 것이죠.
제가 들었던 예시들은 대부분 BERT 이상의 헤비한 모델들이 기준이었기 때문에 가벼운 distilBERT나 alBERT 기타 transformer 기반이 아닌 모델들을 사용중이라면 또 차이가 많이 납니다.
그래서 직접 "기록을 잘 남기면서" 체크해보시길 바랍니다.
이게 머리가 나빠서 제출할 때 메모 작성하려고 보면, 어..? 뭐였지.. 싶을 때가 많더군요.
주절주절 말이 많아져서 가독성이 떨어지는 것 같은데..여튼 핵심만 기억하시고 문제 해결하셔서 다양하게 실험해보시길 추천드립니다!
ㅎㅎ감사합니다. 도움이 될만한 내용이 있으면 또 작성해봐야겠네요 😄
너무 좋네요! 감사합니다^^
torch 또는 tensor에서 데이터를 전처리하고 데이터셋을 만드는 과정에서 gpu자체에 올려두고 그 데이터를 사용하는 경우도 있는데 이 경우에도 위에서 보여주신것 처럼 gpu till이 저조한 현상이 발견됩니다,
이러한 경우에는 cpu가 배치를 준비하지 않아도 이미 gpu에 할당 되어있는 데이터를 꺼내써오기만 하면 되는 것은 아닌가 생각하는데 혹시 제가 생각하는 부분이 틀린걸까요???
데이터셋을 만들 때 GPU에 올려두고 한다는 것이 어떤 말인지 잘 이해가 되지 않네요 ㅜㅜ
관련된 코드를 보여주시면 조금 좋을 것 같은데요..!
gpu till이 저조한 현상이 의미하는 건 어떤 걸까요? 할당 가능한 메모리가 많이 남았다는 뜻일까요??
제가 정확히 이해하고 있는지는 솔직히 자신이 없는데, 연산은 같은 디바이스에서 이뤄져야 하는 것으로 알고 있습니다.
즉 배치를 어디서 준비하냐, 이 문제가 아니라 데이터 적재 공간과 연산 공간이 동일해야 한다는 뜻인데요.
데이터가 GPU에 로드되어 있지 않으면 GPU 연산이 불가능합니다.
데이터셋을 만들 때 이미 GPU에 올려두었다고 한다면, 그것은 이미 배치 단위로 올라갔다는 뜻일 것 같습니다.
그 상황에서 GPU를 full로 사용하지 않고 있다는 것은 배치 사이즈를 작게 만들었기 때문인데, 이를 최대로 활용하기 위해서는
배치 사이즈를 재조정할 수 있을 것입니다.
배치 사이즈 재조정은 GPU 상에서도 추가 메모리를 할당하여 가능한 것으로 알고 있고,
CPU에서 배치 단위로 다시 로드할 수도 있을 것 같습니다.
검색해본 결과 배치 단위로 데이터를 CPU -> GPU 로드하는 것은 맞는 것으로 보입니다..!
데이콘(주) | 대표 김국진 | 699-81-01021
통신판매업 신고번호: 제 2021-서울영등포-1704호
서울특별시 영등포구 은행로 3 익스콘벤처타워 901호
이메일 dacon@dacon.io | 전화번호: 070-4102-0545
Copyright ⓒ DACON Inc. All rights reserved
공유 감사합니다
이런거 너무 좋네요 ㅎㅎ