OOM(Out Of Memory)가 발생하는 이유

Linux에서는 프로세스 실행 시 필요한 메모리를 바로 할당하는 것이 아니라 메모리를 주었다는 것을 저장하는 Memory Commit 정책을 가지고 있다. 실제 메모리 할당은 프로세스가 실행되면서 메모리가 필요할 때 이루어진다.

그리고 Linux는 Overcommit를 허용하는데, 이는 커밋된 메모리가 실제 가용 메모리보다 더 큰 것을 뜻한다. Overcommit 덕분에 메모리 사용량이 큰 프로세스가 있어도 실행될 수가 있다. 그런데 Overcommit이 된 상태에서 프로세스에게 실제 메모리 할당이 되면 가용 메모리보다 더 큰 메모리를 할당할 수도 있는데, 이 때 OOM(Out Of Memory)이 발생하기 때문에 Overcommit은 OOM의 주요 원인으로 손꼽힌다.

OOM Killer과 OOM Score

OOM이 발생하면 메모리 확보를 위해 프로세스를 강제로 종료 시키는 OOM Killer가 실행된다. OOM Killer이 프로세스를 종료할 때 당연히 무작위로 종료하는 것은 아니고, OOM Score에 따라서 종료될 프로세스가 결정된다. OOM Score은 0~1000 사이의 값을 가지고, 값이 클 수록 종료 우선순위가 높아진다. 따라서 OOM Killer는 OOM Score이 높은 순서대로 프로세스를 종료한다.

OOM Score은 oom_badness()를 통해 계산된다. Heuristic하게 계산되는 점수인 만큼 시간이 지나면서 점수 계산 방법이 여러 번 바뀌었던 것 같은데 현재는 기본적으로 메모리 사용량이 높을 수록 OOM Score이 높아진다.

OOM Killer가 실행됐을 때 로그는 /var/log/syslog 에서 볼 수 있다.

OOM Score 확인법

Linux에서 프로세스의 OOM Score 관련 값이 저장되어 있는 경로는 다음과 같다.

  • /proc/{PID}/oom_score : 프로세스의 OOM Score(0~1000)
  • /proc/{PID}/oom_adj : OOM Score 조정값(-17 ~ 17). Deprecated
  • /proc/{PID}/oom_score_adj : OOM Score 조정값(-1000 ~ 1000)

oom_score_adj 값은 OOM Score 조정 값이다. OOM Score를 계산하는 oom_badness()를 보면 마지막에 adj이 OOM Score에 더해지는 것을 볼 수 있다. oom_score_adj 값이 -1000이면 OOM Score LONG_MIN 값을 가져서 종료되지 않는다.

따라서 하나의 머신에서 서버를 포함한 여러 개의 프로세스가 실행되어 있는데, OOM이 발생해도 OOM Killer가 서버 프로세스를 종료하지 않도록 하기 위해서는 서버 프로세스의 oom_score_adj 값을 -1000으로 바꾸면 된다.

예시

  • OOM Score 확인
$ cat /proc/2533032/oom_score
2
  • oom_score_adj 확인
$ cat /proc/2533032/oom_score_adj
-998
  • oom_score_adj
$ sudo echo 0 > oom_score_adj
  • oom_score_adj 값을 -998에서 0으로 변경

Reference

 

Huggingface hub에 모델을 올리는 법을 자꾸 까먹어서 글로 남겨보려고 합니다. 방법들을 찾아보니 총 3가지 방법이 있는거 같은데, 이들에 대해 다뤄보고자 합니다. 실습에 사용된 패키지와 버전은 아래와 같습니다.

transformers==4.14.1 
huggingface-hub==0.2.1 # transformers 설치하면 같이 설치됨

그리고 빠른 실습을 위해 모델의 용량이 작은 gpt2-tiny모델을 이용하였습니다.

1. push_to_hub() 이용

모델과 토크나이저에 대해서 push_to_hub()메소드를 이용하여 바로 모델을 업로드하는 방법이 있습니다. 이 방법을 이용하면 원격 저장소 생성까지 한 후에 업로드를 해줍니다.

from transformers import AutoTokenizer, AutoModelForCausalLM

print("model loading...")

# Model & Tokenizer loading
tokenizer = AutoTokenizer.from_pretrained("sshleifer/tiny-gpt2")
model = AutoModelForCausalLM.from_pretrained("sshleifer/tiny-gpt2")

# Repository 생성 & model upload
REPO_NAME = YOUR_REPO_NAME # ex) 'my-bert-fine-tuned'
AUTH_TOKEN = YOUR_OWN_TOKEN # <https://huggingface.co/settings/token>

## Upload to Huggingface Hub
model.push_to_hub(
    REPO_NAME, 
    use_temp_dir=True, 
    use_auth_token=AUTH_TOKEN
)
tokenizer.push_to_hub(
    REPO_NAME, 
    use_temp_dir=True, 
    use_auth_token=AUTH_TOKEN
)

위 코드를 통해 모델을 업로드할 때, 권한을 위한 토큰값이 필요합니다.

Huggingface 홈페이지에 로그인한 후 오른쪽 상단 프로필 > Settings > Access Tokens을 누르거나 로그인 후에 링크를 클릭하면 바로 토큰을 관리할 수 있는 페이지가 나타납니다. 페이지 상단을 보면 User Access Tokens부분이 있는데, 여기서 New token을 눌러 토큰을 생성할 수 있습니다. 이 때, Role을 read로 하면 권한 에러가 발생하므로 write로 설정해야합니다.

New Token 클릭
Role을 write로 해서 토큰을 생성
생성된 토큰의 모습

이렇게 생성된 토큰값을 AUTH_TOKEN의 값으로 이용하면 됩니다.

2. CLI 이용

다음은 CLI환경에서 모델을 업로드하는 방법입니다. 우선 아래 코드를 이용해 모델과 토크나이저가 model 디렉토리에 저장되어 있다고 하겠습니다.

from transformers import AutoTokenizer, AutoModelForCausalLM

print("model loading...")

# Model & Tokenizer loading
tokenizer = AutoTokenizer.from_pretrained("sshleifer/tiny-gpt2")
model = AutoModelForCausalLM.from_pretrained("sshleifer/tiny-gpt2")

tokenizer.save_pretrained("./model")
model.save_pretrained("./model")

이후 이용법은 깃허브에 파일을 올리는 것과 동일합니다.

다만 모델의 용량이 큰 것은 수십GB에 이르기 때문에 용량이 큰 파일도 업로드 할 수 있도록 git lfs(Large File Storage)를 설치합니다. 설치가 완료되면 user.email과 user.name을 huggingface 계정의 이메일과 이름으로 설정해줍니다.

git lfs install
git config --global user.email YOUR_EMAIL
git config --global user.name YOUR_NAME

그리고 huggingface-cli login을 입력하여 로그인을 합니다. 로그인을 위해 토큰값이 필요한데, 위의 과정을 통해 생성된 토큰값을 입력하면 됩니다.

huggingface-cli login

이후 아래 과정을 통해 모델을 업로드하면 됩니다.

# Repository 생성
# organization에 생성할 경우
# huggingface-cli repo create YOUR_MODEL_NAME --organization YOUR_ORG_NAME
huggingface-cli repo create YOUR_MODEL_NAME

# 원격 저장소 가져오기
git clone <https://huggingface.co/USER_NAME/YOUR_MODEL_NAME>

# 모델, 토크나이저 Copy
cp -r model/* YOUR_MODEL_NAME
cd YOUR_MODEL_NAME
git add *
git commit -m "Initial commit"
git push

3. huggingface-hub 이용

마지막으로 transformers 패키지를 설치하면 같이 설치되는 huggingface-hub 패키지를 이용하는 방법이 있는데, 이를 이용하면 코드 상에서 모델 업로드 뿐만 아니라 더 많은 작업을 수행할 수 있습니다. 주의할 점은 ipynb파일에서만 작업이 가능한 점입니다.

먼저 아래 코드로 huggingface 계정과 연동시킬 수 있는데, 위의 과정과 마찬가지로 토큰값이 필요합니다.

from huggingface_hub import notebook_login

notebook_login()

create_repo()와 delete_repo()를 이용하면 원격 저장소를 생성하고, 삭제할 수 있습니다.

from huggingface_hub import create_repo

# Repository 생성
create_repo(name=REPO_NAME)

# Repository 삭제
delete_repo(name=REPO_NAME

아래 코드를 이용하면 Repository 객체를 생성할 수 있습니다. 여기서 첫 번째 인자는 생성할 로컬 저장소 이름이고, 두 번째 인자는 연동할 원격 저장소 이름입니다. 코드를 실행하면 객체가 생성되면서 원격 저장소의 파일들이 로컬 저장소에 복사되고, 객체를 이용해서 원격 저장소와 관련된 작업들을 할 수 있습니다.

from huggingface_hub import Repository

repo = Repository(LOCAL_DIR_NAME, clone_from=NAMESPACE/REPO_NAME)

로컬에 저장한 모델을 로컬 저장소로 복사합니다.

!cp -r model/* LOCAL_DIR_NAME

아래 코드를 이용해서 로컬 저장소에 있는 모델들을 원격 저장소에 업로드 할 수 있습니다.

repo.git_add()
repo.git_commit('Initial commit')
repo.git_push()

그리고 upload_file()를 이용하면 add, commit, push 작업 없이 바로 하나의 파일을 업로드할 수도 있습니다. 여기서는 굳이 로컬 저장소에 있지 않은 파일도 업로드가 가능합니다. path_in_repo 인자값은 원격 저장소에 저장되는 파일의 이름입니다.

from huggingface_hub import upload_file, delete_file

# 파일을 직접 업로드
upload_file(
    LOCAL_FILE_PATH,
    path_in_repo=REPO_FILE_PATH,
    repo_id=NAMESPACE/REPO_NAME,
)

delete_file()를 이용하면 원격 저장소의 파일을 바로 삭제할 수도 있습니다.

# 파일 삭제
delete_file(
    REPO_FILE_PATH,
    repo_id=NAMESPACE/REPO_NAME,
)

Reference

부스트캠프 AI Tech과정이 2주전 쯤에 완전히 종료되었다.

 

P Stage2 이후로 후기를 작성하지 않았는데, 과정이 종료되었으니 마지막으로 그동안 밀린 후기를 써보려고 한다.

 

이번 후기에는 P Stage3, 4와 기업 채용 연계(기업 네트워킹)에 관한 내용을 담아보려고 한다.

 

P Stage3

: P Stage3에서는 MRC Task를 선택했다. 프로젝트 이름은 MRC이지만 사실상 Open-Domain QA에 가까웠다.

먼저 문제 해결을 위해서는 두 가지 과정을 거쳐야 했는데, 주어진 질문(query)에 대해 유사한 문서를 찾고(retrieval), 문서 내에서 정답을 찾는 과정(extraction)으로 진행되었다.

문서 내에서 정답을 찾는 것도 중요했지만 유사한 문서를 잘못 찾으면 답도 잘못 나오기 때문에 유사한 문서를 찾는 것이 매우 중요했다.

강의 등에서 최근에는 DPR과 같이 dense embedding을 이용한 retrieval 방법이 sprase retrieval 방법보다 성능이 더 좋아지고 있다는 것을 듣고 시도해보았으나 성능이 너무 안 좋게 나왔다. 그래서 결국 sparse embedding을 사용을 했었다.

P Stage3부터 팀 단위로 프로젝트가 진행되다보니까 문제가 더욱 어려워진 느낌이 들었다.

개인적으로 어떻게 성능을 개선시켜할지 잘 떠오르지가 않았고, 막상 떠올라도 어떻게 구현을 해야되는지 어려운 점이 많았다.

 

P Stage4

: P Stage4에서는 수식인식 Task를 선택했다. 사실상 OCR 문제였는데,  사진 속 수식을 인식하고 이를 latex 문법으로 변환하는 문제였다.

수식인식 문제도 크게 수식 부분을 찾는 detection 부분과 수식을 인식하는 recognition 부분으로 나눌 수 있었다. 이번 스테이지에서는 detection이 수행된 부분을 데이터셋으로 주어져서 recognition부분만 다루면 됐었다.

하지만 P Stage4는 개인적인 사정이 있어서 제대로 참여하지 못했다ㅠㅠ

 

기업 네트워킹

: 아마 부스트캠프에 관심을 가지는 분들은 대부분 "기업 채용 연계"인 "기업 네트워킹"에 관심이 있기 때문이라고 생각한다.

나 역시도 지원을 하기 전에 어떤 기업들이 채용 연계에 참여하는지, 또 실제로 얼만큼 채용이 이루어질지 많이 궁금했다.

채용 연계에 참여한 기업 리스트는 알려드릴 수 없지만 20개 이상의 기업이 참여했고, 예상보다 많은 기업이 채용 연계에 참여한다고 생각했다. 그 중에는 우리가 아는 큰 기업들도 많았고, 인공지능 스타트업으로 유명한 기업들도 있었다. 

채용을 어떻게 진행하는지는 기업마다 달랐는데, 대부분은 부스트캠프 전형을 따로 만들거나 개별 컨택을 통해 채용 연계가 이루어졌다.

다만 채용 연계가 진행된다 해도 코테/과제 => 면접 과 같이 일반 채용과 비슷하게 전형이 진행되는 듯 했다.

나는 채용 연계에 참여를 하지 않았기 때문에 자세한 내용을 알지는 못하지만 생각보다 많은 채용 공고가 올라오고, 뽑는 인원도 꽤 많았던 것 같아서 부스트캠프에서 열심히 한다면 좋은 기업에 취업할 수 있지 않을까 생각한다.

 

지금 생각해봤을 때, 부스트캠프에 참여한 것이 매우 좋은 기회였다고 생각한다. 현재는 산업기능요원으로 군복무를 하고 있는데, 이 역시 부스트캠프 덕분이라고 생각한다.

아마 2기부터는 1기에서의 진행을 바탕으로 교육 과정을 수정해서 더욱 완성도 있는 교육이 이루어질 것 같고, 아마 취업 연계 등도 더 활발히 이루어지지 않을까 생각을 한다.

문제 링크 : https://programmers.co.kr/learn/courses/30/lessons/42747

 

코딩테스트 연습 - H-Index

H-Index는 과학자의 생산성과 영향력을 나타내는 지표입니다. 어느 과학자의 H-Index를 나타내는 값인 h를 구하려고 합니다. 위키백과1에 따르면, H-Index는 다음과 같이 구합니다. 어떤 과학자가 발표

programmers.co.kr

문제에 따르면 논문 $n$편 중, $h$번 이상 인용된 논문이 $h$편 이상이고, 나머지 논문이 $h$번 이하 인용되었다면 $h$의 최댓값을 H-Index라고 한다.

예를들어 인용수가 [3, 0, 6, 1, 5]라고 하면, H-index는 3이 된다.

H-Index를 구하기 위해 정렬을 먼저 하면 [0, 1, 3, 5, 6]가 되는데 3까지는 $h$번 이상 인용된 논문의 수가 $h$이상이고 5부터는 $h$번 이상 인용된 논문의 수가 $h$보다 작게된다.

이렇게 $h$번 이상 인용된 논문의 수가 $h$보다 작게되는 원소를 찾으면 최종 H-Index의 범위는 (이전 원소 값) ~ (현재 원소 값)사이에 위치하게 된다. 따라서 (현재 원소 값)부터 (이전 원소 값)에 대하여 for문을 돌고, $h$번 이상 인용된 논문의 수가 $h이상이 되는 값이 나오면 그 값이 바로 H-Index가 된다.

def solution(citations):
    # len(citations) : 1 <= n <= 1000
    answer = 0
    
    citations.sort() # O(nlogn)
    
    max_h = -1
    for i in range(len(citations)):
        h = citations[i]
        n_h_over = len(citations) - i
        
        if n_h_over >= h:
            max_h = h
        else:
            for h in range(citations[i], max_h-1, -1):
                if n_h_over >= h:
                    max_h = h
                    
                    return max_h
    
    return max_h

 

 

문제 링크 : https://programmers.co.kr/learn/courses/30/lessons/42748

 

코딩테스트 연습 - K번째수

[1, 5, 2, 6, 3, 7, 4] [[2, 5, 3], [4, 4, 1], [1, 7, 3]] [5, 6, 3]

programmers.co.kr

문제에 나와있는대로 코드를 작성했다.

 

부분 배열의 시작, 끝 인덱스가 주어지면 부분 배열을 얻어내고 이를 정렬하여 부분 배열의 K번째 수를 얻는 과정을 반복한다.

def solution(array, commands):
    answer = []
    
    for command in commands:
        i, j, k = command[0], command[1], command[2]
        
        part_arr = array[i-1:j]
        part_arr.sort()
        answer.append(part_arr[k-1])
    
    return answer

 

 

문제 링크 : https://programmers.co.kr/learn/courses/30/lessons/42628

 

코딩테스트 연습 - 이중우선순위큐

 

programmers.co.kr

문제에 주어진대로 그대로 코드를 작성하면 해결되는 문제였다.

import heapq

def solution(operations):
    # len(operations) : 1 <= n <= 10000000
    
    answer = []
    heap = []
    
    for operation in operations:
        op = operation.split()[0]
        data = operation.split()[1]
        
        if op == 'I':
            heapq.heappush(heap, int(data))
        elif op == 'D' and len(heap) > 0:
            if data == '1':
                heap.pop()
            elif data == '-1':
                heapq.heappop(heap)
    
    if len(heap) > 0:
        answer = [max(heap), min(heap)]
    else:
        answer = [0, 0]
        
    
    return answer

 

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[프로그래머스] H-Index  (0) 2021.06.23
[프로그래머스] K번째 수  (0) 2021.06.21
[프로그래머스] 더 맵게  (0) 2021.04.16
[프로그래머스] 기능개발  (0) 2021.04.14
[프로그래머스] 주식가격  (0) 2021.04.12

요즘 부스트캠프 AI Tech 2기를 모집한다는 소식이 있어가지고 합격후기 조회수가 많이 늘어난 것 같다.

 

2기는 1기에 비해 커리큘럼이 바뀐 부분이 많아 보였는데 간단하게 살펴본 것들을 써보고자 한다.

 

먼저 U Stage가 8주에서 5주로 줄어들었다.

 

그래프, 특강 주차가 사라지고 최적화 강의는 프로젝트 부분으로 이동한 것 같았다.

 

따라서 강의는 기초개념과 CV, NLP에 대해서만 이루어지는 것으로 보였다.

 

그리고 P stage도 많이 바뀐 것 같은데 정형데이터, DKT와 같은 프로젝트가 없어지고

 

CV와 NLP 프로젝트 중 하나를 선택하고, 데이터 구축과 배포 프로젝트가 의무적으로 추가되었다.

 

1기에 비해 CV, NLP에 중점을 두고 전체적인 pipeline을 경험까지 할 수 있도록 구성을 변경한 것 같다.

 

나중에 엔지니어로서 채용연계를 노리시는 분이 있다면 서비스 배포까지 경험할 수 있는 거 같아서 상당히 좋은 기회일 것 같다.

 

 

그 다음으로 P Stage2가 끝난지 3주정도 됐는데 많이 늦었지만 P Stage2 후기를 작성하고자 한다.

 

P Stage2에서는 NLP task를 경험해보고 싶어서 KLUE를 선택했다.

 

트랜스포머 기반 언어모델들을 모아서 라이브러리로 만든 huggingface를 처음 써봐서 프로젝트 초반에는 이것에 익숙해지느라 시간을 좀 많이 쏟았던 것 같다.

 

그리고 baseline 코드에서는 학습부분이 huggingface의 trainer를 기반으로 작성되어 있는데, trainer를 쓰면 직접 코드를 수정하기가 어려워서 학습 부분 코드를 작성했는데, 여기서도 시간이 좀 걸렸다.

 

그리고 일단 먼저 entity를 감싸는 special token을 추가해보기로 했는데, 문제를 어떤 식으로 접근해야되나 많이 막막했다. 그러던 중에 다른 캠퍼분이 공유해주신 R-BERT 내용을 보고 entity vector들의 평균을 이용하는 식으로 접근을 하기로 하였다.

 

모델이나 하이퍼파라미터 등은 피어세션이나 토론게시판 등 다른 캠퍼분들을 많은 도움을 받았고 성능도 많이 올릴 수 있었다.

 

예상보다 다양한 시도는 못 해봐서 아쉬움은 있었는데 그래도 P Stage1보다는 등수가 올라서 만족스러웠다. 

문제 링크 : programmers.co.kr/learn/courses/30/lessons/42626

 

코딩테스트 연습 - 더 맵게

매운 것을 좋아하는 Leo는 모든 음식의 스코빌 지수를 K 이상으로 만들고 싶습니다. 모든 음식의 스코빌 지수를 K 이상으로 만들기 위해 Leo는 스코빌 지수가 가장 낮은 두 개의 음식을 아래와 같

programmers.co.kr

heap을 사용하기 위해 heapq 라이브러리를 이용하였다.

input으로 들어오는 scoville을 heapify하게되면, heap형태로 변환되기 때문에

첫 번째 heapq.heappop()를 통해 가장 맵지 않은 음식의 스코빌 지수, 두 번째 heapq.heappop()를 통해 두 번째로 맵지 않은 음식의 스코빌 지수를 얻을 수 있다.

 

import heapq

def solution(scoville, K):
    # len(scoville) : 1 <= n <= 1000000
    # 최대 O(nlogn)정도
    answer = 0
    
    heapq.heapify(scoville)
    
    while scoville[0] < K:
        if len(scoville) <= 1:
            return -1
        
        mix_scoville = heapq.heappop(scoville) + heapq.heappop(scoville) * 2
        heapq.heappush(scoville, mix_scoville)
        
        answer += 1
        
    return answer

 

 

 

 

+ Recent posts