태그 : 인공지능 일반

  • FastTrack을 활용한 Domain adaptive language model fine-tuning

    By 권용근

    Introduction

    이번 글에서는 Backend.AI의 MLOps 플랫폼인 FastTrack을 이용하여 공급망(Supply chain) 및 무역 관련 도메인에 특화된 언어모델을 학습시키고, 평가하는 방법에 대해 설명합니다. 해당 언어모델을 위한 Base model로는 공급망 및 무역 도메인 데이터 세트를 통해 continual-pretrained된 gemma-2-2b-it 모델을 사용하였습니다. 사용처에 따라 Question Answering task에 특화된 모델을 학습시키기 위해 웹에서 직접 수집, 가공한 도메인 데이터 세트를 학습 가능한 질문과 답변으로 구성되는 포맷(이하 Q/A task)으로 변환하여 사용하였습니다.

    AI를 개발하는 과정에서는 데이터 전처리, 학습, 검증, 배포, 추론과 같은 단계를 거쳐야 합니다. 래블업의 FastTrack을 사용하면 위와 같은 각각의 단계를 하나의 파이프라인으로 구성할 수 있고, 파이프라인 구성에 따라 특정 단계를 건너뛰거나 단계별 자원량을 다르게 설정하는 등 손쉽게 커스터마이징 할 수 있습니다.

    Concept of Domain Adaptation

    본격적인 모델 학습에 들어가기 앞서, Domain Adaptation이라는 과정이 필요합니다. 생소하신 분들을 위해 짧게 설명하자면, Domain Adaptation이란 사전 학습된 모델을 특정 도메인에 적합하도록 개선하는 프로세스를 말합니다. 오늘날 우리가 접하는 대다수의 일반적인 언어모델들은 특정 분야에 전문적인 지식을 가지도록 만들어지지 않았습니다. 대부분의 모델은 일반적인 도메인에서의 데이터 세트를 사용하여 다음 토큰을 잘 예측할 수 있도록 학습한 뒤, 전반적인 사용 방향에 맞게 fine-tune 되어 만들어집니다. 그러나 전문 도메인에서 사용될 목적으로 모델을 만든다면, 일반적인 데이터 세트를 사용하여 학습시키는 것은 충분치 않습니다. 예를 들어, 범용적인 도메인에서 학습된 모델은 "이 영화가 매우 훌륭했다"와 같은 일반적인 문장의 맥락을 잘 파악할 수 있지만, "법원이 채무자의 자산을 압류하도록 명령했다"와 같은 법률 도메인의 문장은 제대로 해석하지 못할 수 있습니다. 이는 모델이 각 도메인에서 사용되는 특수한 용어와 표현을 학습하지 않았기 때문입니다. 또 다른 예시로, 어떠한 Q/A task가 주어졌다면 일반적인 데이터로는 Q/A task를 구현할 수 없을 가능성이 있습니다. 제대로 된 Q/A task를 처리하기 위해서는 Q/A task에 특화된 데이터 세트로 사전 학습된 언어 모델을 fine-tune하는 식으로 '특정한 도메인의 데이터'를 넣어주어야 하기 때문이죠. 이러한 fine-tuning 과정은 모델이 작업의 뉘앙스를 더 잘 이해하여 사용자의 domain-specific한 질문에 대해 효과적으로 답변할 수 있도록 합니다.

    이번 글에서는 공급망(Supply Chain Management, 이하 SCM) 및 무역 도메인에 특화된 모델을 개발하는 과정을 다룹니다. 위 그림에서 볼 수 있듯이, "영화"나 "여행" 같은 일반 도메인 용어와 "항공화물운송장", "대금결제인"과 같은 SCM 도메인 용어 사이에는 현격한 차이가 있습니다. 이러한 차이를 좁히기 위해 SCM과 무역 도메인에서의 데이터 세트를 활용하여, 해당 도메인에 대한 모델의 이해도를 높이고, 맥락을 더욱 정확하게 파악할 수 있도록 조정하는 것이 우리가 오늘 달성해볼 목표입니다. 정리하면, Domain Adaptation은 본질적으로 서로 다른 도메인 간의 격차를 해소, 새로운 맥락에서 모델이 더 나은 성능을 발휘할 수 있도록 돕는 과정이라고 할 수 있습니다.

    Train model from scratch vs DAPT

    그렇다면 처음부터 해당 도메인의 데이터 세트를 통해 학습(Train model from scratch)하면 되지 않을까요? 물론 가능하지만, 여러가지 한계점이 존재합니다. 만약 처음부터 해당 도메인의 데이터 세트를 통해 학습하게 되면, 해당 도메인에서의 지식은 물론 일반적인 도메인에서의 지식조차 없는 상황이기 때문에 더 많은 데이터 세트와 학습이 요구될 수 있습니다. 일반적인 도메인에서의 딥러닝을 위한 데이터 세트를 수집하는 것도 어렵지만, 특정 도메인에 국한된 양질의 데이터를 수집하는 것은 더욱 어려운 일입니다. 데이터를 수집했다 치더라도, 모델 학습에 맞게 전처리하는 과정에서 많은 시간과 비용이 발생하게 되죠. 따라서 모델을 처음부터 학습시키는 것은 해당 도메인의 데이터 세트를 충분히 확보하고 있고, 자원을 충분히 보유한 기업에 더 적합한 방법이라고 할 수 있습니다.

    만약 domain-adaptive 한 model을 개발하고 싶은데 아주 많은 데이터 세트를 확보하지 못했거나, 자원이 충분하지 않다면 어떻게 해야 할까요? 이런 경우 선택할 수 있는 방법이 Domain-Adaptive Pre-Training (DAPT)입니다. DAPT란, 이미 일반적인 도메인을 통해 충분히 학습된 모델을 특정 도메인의 데이터 세트로 continual pretraining (지속 학습)하여 도메인에 특화된 모델을 개발하는 과정을 말합니다. 이 방법은 일반적인 도메인에 대한 지식을 이미 보유하고 있는 모델을 추가로 학습시키는 방법이기 때문에, 모델을 처음부터 학습시키는 방법에 비해 상대적으로 적은 비용과 데이터 세트를 요구합니다.

    Development environment Setup

    1. 모델 학습에 앞서, 필요한 패키지들을 설치합니다.
    pip install bitsandbytes==0.43.2
    pip install deepspeed==0.14.4
    pip install transformers==4.43.3
    pip install accelerate==0.33.0
    pip install flash-attn==1.0.5
    pip install xforms==0.1.0
    pip install datasets==2.20.0
    pip install wandb
    pip install evaluate==0.4.2
    pip install vertexai==1.60.0
    pip install peft==0.12.0
    pip install tokenizers==0.19.1
    pip install sentencepiece==0.2.0
    pip install trl==0.9.6
    pip install bitsandbytes==0.43.2
    pip install deepspeed==0.14.4
    pip install transformers==4.43.3
    pip install accelerate==0.33.0
    pip install flash-attn==1.0.5
    pip install xforms==0.1.0
    pip install datasets==2.20.0
    pip install wandb
    pip install evaluate==0.4.2
    pip install vertexai==1.60.0
    pip install peft==0.12.0
    pip install tokenizers==0.19.1
    pip install sentencepiece==0.2.0
    pip install trl==0.9.6
    
    1. 모듈 가져오기
    import os
    import json
    from datasets import load_from_disk, Dataset,load_dataset
    import torch
    from transformers import AutoTokenizer, AutoModelForCausalLM, Gemma2ForCausalLM, BitsAndBytesConfig, pipeline, TrainingArguments
    from peft import LoraConfig, get_peft_model
    import transformers
    from trl import SFTTrainer
    from dotenv import load_dotenv
    import wandb
    from huggingface_hub import login
    

    Dataset preparation

    데이터 세트는 fine-tuning의 목적에 따라 다르게 준비되어야 합니다. 이 글에서는 무역 영역에 대한 질문에 효과적으로 답변할 수 있는 모델을 학습하는 것을 목표로 하기 때문에, 웹 크롤링을 통해 자체적으로 수집한 데이터 세트를 사용합하기로 결정했습니다. 데이터 세트는 무역 자격증 시험 데이터 세트, 무역 용어-정의 데이터 세트, 무역 강의 스크립트 데이터 세트의 세 가지 유형으로 분류됩니다.

    1. 무역 자격증 시험 데이터 세트

    질문: 다음 중 우리나라 대외무역법의 성격에 대한 설명으로 거리가 먼 것을 고르시오. 1. 우리나라에서 성립되고 이행되는 대외무역행위는 기본적으로 대외무역법을 적용한다. 2. 타 법에서 명시적으로 대외무역법의 적용을 배제하면 당해 법은 특별법으로서 대외무역법보다 우선 적용된다. 3. 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 4. 관계 행정기관의 장은 해당 법률에 의한 물품의 수출·수입 요령 그 시행일 전에 지식경제부 장관이 통합하여 공고할 수 있도록 제출하여야 한다. 정답: 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 질문: ...

    1. 무역 용어 정의 데이터 세트
    {
      "term": "(계약 등을) 완전 무효화하다, 백지화하다, (처음부터) 없었던 것으로 하다(Rescind)",
      "description": "계약을 파기, 무효화, 철회, 취소하는 것; 그렇지 않았음에도 불구하고 계약을 시작부터 무효인 것으로 선언하고 종결짓는 것."
    }
    
    
    1. 무역 강의 스크립트 데이터 세트

    예전에는 전자상거래 셀러가 엑셀에다가 입력을 해서 수출신고 데이터를 업로드 해서 생성을 했잖아요 그리고 대량으로 전송하는 셀러는 api를 통해서 신고를 했습니다 그런데 그 수출신고 정보의 원천정보를 뭐냐면 쇼핑몰에서 제공하는 판매 주문정보입니다 그래서 그 쇼핑몰에 직접 저희가 연계를 해서 판매 주문 정보를 가져올 수 있게끔 새 서비스를 만들었어요 그래서 API 연계된 쇼핑몰들이 있는데 그게 현재 5개가 연결되어 있는데 쇼피 쇼피파이 라자다 라쿠텐 q10이 있고요 아마존하고 위치도 연계 예정에 있습니다 그래서 셀러는 ...

    Q/A 작업에 알맞은 모델을 만들려면 데이터 세트를 질의응답 형식으로 변환해야 합니다. 첫 번째 데이터 세트인 무역 자격증 시험 데이터 세트와 두 번째 데이터 세트인 무역 용어 정의 데이터 세트는 간단한 코드를 사용하여 변환할 수 있지만, 세번째 데이터 세트인 무역 강의 스크립트 데이터 세트를 확인해 보면 대화 형식의 데이터를 직접 변환하기 어려워 보입니다. 이 경우에는 대화형 스크립트에서 대규모 언어 모델(LLM)을 활용하여 Q/A 쌍을 추출하는 접근 방식을 사용할 수 있습니다. 프롬프트와 그 결과는 다음과 같습니다.

    Prompt

    summary: {summary}
    Instruction: 다음 summary를 기반으로 질문과 해당 답변을 추출하세요.
    출력 형식은 JSON으로, 각 질문과 답변을 객체로 묶어 'qa_pairs'라는 배열 안에 넣어야 합니다.
    'questions' key에는 요약에서 도출된 질문 목록이 포함되어야 하며, 'answers' key에는 각 질문에 대한 해당 답변 목록이 포함되어야 합니다:
    출력 예시:
    {{"qa_pairs": [{{"question": "Question1", "answer": "Answer1"}},{{"question": "Question2", "answer": "Answer2"}},{{"question": "Question3", "answer": "Answer3"}},]...}}
    Output:
    

    Output

    {"qa_pairs":
      [
        {"question": "전자상거래 무역 플랫폼은 어떤 서비스를 제공하나요?", "answer": "판매 주문 관리, 창고 관리, 수출입 통관, 배송 관리 등 전자상거래 업무를 위한 다양한 서비스를 제공합니다"},
        {"question": "쇼핑몰과의 API 연계를 통해 무엇을 간소화할 수 있나요?", "answer": "판매 주문 정보를 자동으로 가져와 수출신고를 간소화할 수 있습니다"},
        {"question": "창고 관리 기능은 어떤 것을 관리할 수 있나요?", "answer": "자가 창고, 위촉 창고, 해외 풀필먼트 센터 등을 관리할 수 있습니다"},
        {"question": "수출입 통관 서비스는 어떤 기능을 제공하나요?", "answer": "미국 세관과 연동된 수출입 통관 서비스를 통해 미국 수출 시 수입 신고를 간편하게 처리할 수 있습니다"},
        {"question": "배송 관리 기능은 어떤 서비스를 지원하나요?", "answer": "우체국 EMS, DHL 등 개별 배송과 함께 해외 현지 라스트 마일 배송까지 지원합니다"}
      ]
    }
    

    이제 간단한 코드를 사용하여 각 데이터 세트를 Q/A 데이터 집합으로 변환할 준비가 된 것 같습니다. 아래의 코드를 사용하여 각 데이터 세트를 Q/A 형식으로 변환해 보겠습니다.

    import os
    import json
    import re
    from datasets import Dataset, concatenate_datasets, load_from_disk
    
    def replace_dot_number(text):
        result = re.sub(r'\.(\d+)\.', r'. \1.', text)
        return result
    
    def read_json(path):
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    
    def write_json(data, path):
        with open(path, 'w', encoding='utf-8') as f:
            json.dump(data, f, ensure_ascii=False)
    
    def dataset_maker(data:list) -> Dataset:
        return Dataset.from_list(data)
    
    def save_dataset(dataset, save_path):
        dataset.save_to_disk(save_path)
    
    def exam_qa_formatter():
        data = []
        root = 'dataset/exam_data'
        for file in sorted(os.listdir(root)):
            file_path = os.path.join(root, file)
            content = read_json(file_path)['fixed_text']
            question_list = content.split('질문:')[1:]
            for question in question_list:
                try:
                    question_and_options = replace_dot_number(question.split('정답:')[0]).strip()
                    answer = question.split('정답:')[1].strip()
                    data.append({"context": replace_dot_number(question), "question":question_and_options, "answer":answer})
    
                except Exception as e:
                    pass
        return data
    
    def description_to_term_formattter(kor_term, eng_term, description):
        context = f"{kor_term}: {description}"
        question = f"설명: '{description}' 이 설명에 해당하는 무역 용어는 무엇인가요?"
        answer = kor_term if eng_term is None else f"{kor_term}, {eng_term}"
        return context, question, answer
    
    def term_to_description(kor_term, eng_term, description):
        context = f"{kor_term}: {description}"
        question = f"'{kor_term}({eng_term})' 이라는 무역 용어는 어떤 의미인가요?" if eng_term is not None else f"'{kor_term}' 이라는 무역 용어는 어떤 의미인가요?"
        answer = description
        return context, question, answer
        
    def term_qa_formatter():
        data = []
        root = 'dataset/term_data'
        for file in os.listdir(root):
            file_path = os.path.join(root, file)
            term_set = read_json(file_path)
            if file == 'terms_data_2.json':
                term_set = [item for sublist in term_set for item in sublist]
            for pair in term_set:
                eng_term = pair.get('eng_term', None)
                if 'term' in pair.keys():
                    kor_term = pair['term']
                else:
                    kor_term = pair['kor_term']
                description = pair['description']
                context_1, question_1, answer_1 = description_to_term_formattter(kor_term, eng_term, description)
                context_2, question_2, answer_2 = term_to_description(kor_term, eng_term, description)
                data_1 = {"context": context_1, "question": question_1, "answer": answer_1} 
                data_2 = {"context": context_2, "question": question_2, "answer": answer_2} 
                data.append(data_1)
                data.append(data_2)
        return data
    
    def transcript_qa_formatter():
        data = []
        root = 'dataset/transcript_data/success'
    
        for file in sorted(os.listdir(root)):
            file_path = os.path.join(root, file)
            for line in open(file_path):
                line = json.loads(line)
                context = line['context']
                output = line['json_output']
    
                qa_pairs = json.loads(output)['qa_pairs']
                for pair in qa_pairs:
                    question = pair['question']
                    answer = pair['answer']
                    if type(answer) == list:
                        answer = answer[0]
                    data.append({"context": context, "question": question, "answer": answer})
        return data
    
    ###### Term dataset
    {'context': 'APEC 경제위원회(Economic Committee (EC)): 개별위원회나 실무그룹이 추진하기 어려운 여러분야에 걸친 이슈에 대한 분석적 연구작업을 수행하기 위해 결성된 APEC 기구,',
     'question': "설명: '개별위원회나 실무그룹이 추진하기 어려운 여러분야에 걸친 이슈에 대한 분석적 연구작업을 수행하기 위해 결성된 APEC 기구,' 이 설명에 해당하는 무역 용어는 무엇인가요?",
     'answer': 'APEC 경제위원회(Economic Committee (EC))'}
    
    ###### Transcript dataset
    {'context': '수입 신고는 일반적으로 입항 후에 하는 것이 원칙이며, 보세 구역에서 5부 10장을 작성하여 신고합니다',
     'question': '수입 신고는 언제 하는 것이 원칙인가요?',
     'answer': '수입 신고는 일반적으로 입항 후에 하는 것이 원칙입니다.'}
    
    ###### Exam dataset
    {'context': ' 다음 중 우리나라 대외무역법의 성격에 대한 설명으로 거리가 먼 것을 고르시오. 1. 우리나라에서 성립되고 이행되는 대외무역행위는 기본적으로 대외무역법을 적용한다. 2. 타 법에서 명시적으로 대외무역법의 적용을 배제하면 당해 법은 특별법으로서 대외무역법보다 우선 적용된다. 3. 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 4. 관계 행정기관의 장은 해당 법률에 의한 물품의 수출·수입 요령 그 시행일 전에 지식경제부 장관이 통합하여 공고할 수 있도록 제출하여야  한다.정답: 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다.',
     'question': '다음 중 우리나라 대외무역법의 성격에 대한 설명으로 거리가 먼 것을 고르시오. 1. 우리나라에서 성립되고 이행되는 대외무역행위는 기본적으로 대외무역법을 적용한다. 2. 타 법에서 명시적으로 대외무역법의 적용을 배제하면 당해 법은 특별법으로서 대외무역법보다 우선 적용된다. 3. 대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다. 4. 관계 행정기관의 장은 해당 법률에 의한 물품의 수출·수입 요령 그 시행일 전에 지식경제부 장관이 통합하여 공고할 수 있도록 제출하여야  한다.',
     'answer': '대외무역법은 국내법으로서 국민의 국내 경제생활에 적용되는 법률이기 때문에 외국인이 국내에서 행하는 무역행위는 그 적용 대상이 아니다.'}
    
    # Exam dataset
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 1430
    })
    
    # Term dataset
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 15678
    })
    
    # Transcript dataset
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 8885
    })
    
    # Concatenated dataset 
    Dataset({
        features: ['context', 'question', 'answer'],
        num_rows: 25993
    })
    

    Q/A 형식의 데이터 세트와 합쳐진 데이터 세트(학습 데이터 세트)는 위와 같습니다. 약 26,000개의 Q/A 쌍이 학습에 사용될 것으로 예상됩니다.

    이제 fine-tuning을 위한 데이터 세트가 준비되었습니다. 이 데이터 세트가 실제로 모델에 어떻게 입력되는지 확인해 보겠습니다.

    <bos><start_of_turn>user
    Write a hello world program<end_of_turn>
    <start_of_turn>model
    

    huggingface 웹사이트에서는 채팅 템플릿 형식과 모델의 프롬프트 형식 정의에 대한 정보가 포함된 gemma-2b-it의 모델 카드를 찾을 수 있습니다(gemma-2-2b-it). 즉, gemma에게 질문을 하려면 모델이 이해할 수 있는 형식의 프롬프트를 만들어야 하는 것이죠.

    대화의 시작은 <start_of_turn>으로 표시되며, 대화의 끝은 <end_of_turn>으로 표시됩니다. 화자는 사용자 및 모델로 지정되어 있음을 알 수 있습니다. 따라서 모델에게 질문을 할 때 프롬프트의 형식은 위와 같은 형식이어야 합니다.

    def formatting_func(example):
        prompt_list = []
        for i in range(len(example['question'])):
            prompt_list.append("""<bos><start_of_turn>user
        다음 질문에 대답해주세요:
        {}<end_of_turn>
        <start_of_turn>model
        {}<end_of_turn><eos>""".format(example['question'][i], example['answer'][i]))
            return prompt_list  
    

    이 문서에서는 Q/A 데이터 세트를 사용하여 모델을 학습시키는 데 중점을 두고 있으므로 '이런 종류의 질문에는 이렇게 대답해야 한다'는 식으로 접근하여 모델을 학습시킬 것입니다. 앞서 언급한 채팅 템플릿을 고려하면 위와 같은 형식으로 코드를 작성할 수 있습니다. 이때, 채팅 템플릿에 토큰이 명시적으로 포함되어 있지 않더라도 모델은 구분 기호 이상으로 더 많은 콘텐츠를 생성하려고 시도할 수 있습니다. 이 때 모델이 답변만 제공하고 턴을 종료하도록 하기 위해 토큰을 추가해줍니다.

    <bos><start_of_turn>user
    다음 질문에 대답해주세요:
    '(관세)감축률(Reduction Rate)' 이라는 무역 용어는 어떤 의미인가요?<end_of_turn>
    <start_of_turn>model
    관세를 감축하는 정도를 말함. 예를 들어 200%p에 관세감축률이 50%를 적용하면 감축 후 관세는 100%p가 됨. 극단적인 경우로 관세감축률이 100%이면 모든 관세는 감축 후에는 0%p가 됨.<end_of_turn><eos>
    

    실제 학습에서는 위와 같은 예시가 input으로 들어가게 됩니다. 이제 학습을 위한 데이터 세트 준비를 마쳤습니다.

    Training

    학습 코드는 아주 간단합니다. SFTTrainer를 사용하며, base model로는 이전에 SCM & 무역 데이터 세트를 통해 continual-pretrained 된 모델을 gemma-2-2b-it 모델을 사용합니다.

    model_id = "google/gemma-2-2b-it"
    output_dir = 'QA_finetune/gemma-2-2b-it-lora128'
    tokenizer = AutoTokenizer.from_pretrained(model_id, token=access_token)
    
    model = AutoModelForCausalLM.from_pretrained(
                # "google/gemma-2-2b-it",
                "yonggeun/gemma-2-2b-it-lora128-merged",
                device_map="auto",
                torch_dtype=torch.bfloat16,
                token=access_token,
                attn_implementation="eager", # attn_implementation,
                cache_dir="./models/models",
            )
    
    
    def formatting_func(example):
        prompt_list = []
        for i in range(len(example['question'])):
            prompt_list.append("""<bos><start_of_turn>user
    다음 질문에 대답해주세요:
    {}<end_of_turn>
    <start_of_turn>model
    {}<end_of_turn><eos>""".format(example['question'][i], example['answer'][i]))
        return prompt_list   
    
    
    def train(data):  
        valid_set = data["test"]
        valid_set.save_to_disk('QA_finetune/valid_set/gemma-2-2b-it-lora128')
    
        lora_config = LoraConfig(
            r=256,
            lora_alpha=32,
            lora_dropout=0.05,
            bias="none",
            target_modules=["q_proj", "o_proj", "k_proj", "v_proj", "gate_proj", "up_proj", "down_proj"],
            task_type="CAUSAL_LM",
        )
    
        training_args = TrainingArguments(
            per_device_train_batch_size=2,
            warmup_steps=2,
            logging_steps=1, 
            gradient_accumulation_steps=4,
            # num_train_epochs=3,
            num_train_epochs=3,  
            learning_rate=2e-4,
            save_steps=100,
            fp16=False,
            bf16=True,
            output_dir=output_dir,
            push_to_hub=True,
            report_to="wandb"
        )
    
        trainer = SFTTrainer(
            model=model,
            tokenizer=tokenizer,
            train_dataset=data['train'],
            args=training_args,
            formatting_func=formatting_func,
            peft_config=lora_config,
            max_seq_length=max_length,
            packing= False,
        )
    
        model.config.use_cache = False
    
        print("Training...")
        trainer.train()
        print("Training done!")
    

    Evaluation

    훈련이 성공적으로 완료되면 필수적으로 모델의 성능을 평가해야 합니다. 이 글에서는 특정 도메인에서의 Question Answering 성능을 평가하는 데 중점을 두었기 때문에 일반적인 모델의 벤치마크에 사용되는 것과는 다른 지표가 필요했습니다. 이 글에서는 SemScore와 Truthfulness를 활용하여 모델을 평가했습니다.

    SemScore: 대상 응답과 모델 응답 간의 의미적 텍스트 유사성을 기반으로 하는 평가 방법입니다.(SemScore)

    Evaluating Truthfulness: 이 방법은 LLM에 모델 응답과 답변을 제공한 다음 1에서 5까지의 척도로 진실성을 측정하는 방식입니다.(Truthfulness)

    Fasttrack pipeline

    이제 FastTrack에서 모델 학습에 사용될 파이프라인을 만들어보겠습니다. 파이프라인은 FastTrack에서 사용하는 작업 단위입니다. 각 파이프라인은 최소 실행 단위인 태스크(Task)의 묶음으로 표현될 수 있습니다. 하나의 파이프라인에 포함되는 여러 개의 태스크는 서로 의존 관계를 가질 수 있으며, 이 의존성에 따라 순차적으로 실행이 보장됩니다.

    Create Pipeline

    위 그림에서 파란색 '+' 버튼을 찾아 새 파이프라인을 만듭니다.

    파이프라인을 생성하면 파이프라인의 이름과 설명, 사용할 데이터 저장소의 위치, 그리고 파이프라인에서 공통적으로 적용할 환경변수 등을 선택할 수 있습니다. 필요한 정보를 입력한 후 하단의 "Save" 버튼을 클릭하여 파이프라인을 생성합니다.

    Drag and create task

    새 파이프라인이 만들어지면 작업 템플릿에 새 작업을 추가할 수 있습니다. Custom Task를 클릭해서 아래 작업 공간으로 끌고오면, task가 새롭게 생성됩니다.

    Enter information

    작업을 만들 때는 위와 같이 작업 실행에 필요한 정보를 입력해야 합니다. 태스크 이름과 설명을 이해하기 쉽게 작성하고, 단일 노드 또는 다중 노드 중 하나를 선택합니다. 본 문서에서는 단일 노드 훈련을 수행하므로 단일 노드를 선택하겠습니다.

    다음으로 명령어를 작성해야 합니다. 명령은 기본적으로 세션을 실행하는 명령어입니다. 실행할 스크립트가 오류 없이 작동할 수 있도록 마운트된 V-folder의 디렉터리를 정확하게 지정해야 합니다. 학습에 필요한 대부분의 패키지는 이미 세션에 설치되어 있지만, 추가 패키지를 설치해야 하거나 버전 문제가 있는 경우에는 패키지를 다시 설치해야 할 수 있습니다. 이 경우 requirements.txt 파일에서 필요한 패키지를 지정하고 설치 후 다른 스크립트를 실행할 수 있습니다.

    Resource configuration

    다음은 세션, 리소스 및 V-folder 설정입니다.

    본 글에서는 Pytorch 기반으로 코드를 작성하였지만, Pytorch 이외에도 Tensorflow, Triton server 등의 환경을 선택할 수 있습니다.

    FastTrack의 장점 중 하나는 리소스를 최대한 효율적으로 활용할 수 있다는 점입니다. 하나의 리소스 그룹 내에서도 여러 세션에 리소스를 분할하여 resource utilization rate를 극대화할 수 있습니다.

    데이터 세트 준비의 경우 별도의 GPU 연산이 필요하지 않기 때문에 GPU 리소스를 할당하지 않아도 괜찮습니다. 이를 통해 최소한의 리소스로 코드를 실행할 수 있으며, 이 시간 동안 다른 세션에 GPU 리소스를 할당할 수 있어 GPU 리소스가 유휴 상태로 남는 상황을 방지할 수 있습니다. 또한 병렬적으로 모델 학습이 필요한 경우(예: 10FGPU가 가용하고 각 훈련 세션에 5FGPU씩 필요한 경우) 모델을 병렬로 학습시킬 수 있죠. 이렇게 하면 리소스 낭비를 줄이고 학습 시간을 단축할 수 있습니다.

    준비한 데이터 세트와 학습 코드가 작성되어 있는 V-folder를 올바르게 선택합니다.

    Duplicate or delete task

    작업 블록의 우측 상단 미트볼 메뉴 아이콘 (⋯)을 누르면 생성된 작업을 복제하거나 삭제할 수 있습니다.

    FastTrack에서는 이와 같이 생성된 여러 개의 작업 사이 순서를 정할 수 있습니다. 이는 작업들 간의 의존성을 추가하는 과정입니다. 경우에 따라 여러개의 작업이 끝난 뒤에 다음 작업이 실행되도록 설정할 수도 있습니다. 이 경우에는 작업들 간의 의존성에 따라 모든 작업이 끝나기 전까지 다음 작업이 진행되지 않습니다. 완성된 예시는 위와 같습니다. 이번 글에서는 dataset preparation - fine-tuning - evaluation 순서로 작업을 진행합니다.

    각 작업이 올바르게 정의되었다면 'Run'을 클릭하여 파이프라인을 실행합니다.

    FastTrack 화면 좌측에서 생성했던 파이프라인들을 확인할 수 있습니다. 클릭하면 파이프라인 작업 세션에서 현재 실행 중인 작업 및 실행했던 작업들을 모니터링 할 수 있습니다.

    Monitoring jobs

    위와 같은 화면을 통해 작업을 모니터링 할 수 있습니다. 각 작업은 지정된 순서대로 진행되며, 이전 작업이 완료되면 다음 작업을 위한 세션을 시작하기 위해 리소스가 할당되고, 작업이 완료되면 세션이 종료됩니다. 필요한 경우 작업을 건너뛸 수 있는 옵션도 있습니다. 일례로, 위의 이미지에서는 dataset preparation 작업을 건너뛰고 fine-tuning 작업이 실행되고 있는 것을 볼 수 있습니다. 건너뛴 작업은 분홍색, 실행되고 있는 작업은 하늘색, 실행 예정인 작업은 노란색으로 나타납니다.

    Log checking

    빨간 네모로 강조되어 있는, 각 작업의 이름 옆의 파란색 버튼을 클릭하면 각 작업의 로그를 확인할 수 있습니다. 이를 통해 트레이닝 진행 상황을 직접 모니터링할 수 있습니다. 로그는 터미널과 동일한 결과로, 위의 화면과 같이 나타납니다. 학습이 잘 이뤄지고 있는 것을 확인할 수 있죠. 파이프라인 실행이 성공적으로 완료되면 결과를 확인할 수 있습니다. 본 문서에서는 평가 결과를 플로팅하여 /home/work/XaaS/train/QA_finetune/truthfulness_result.png 로 저장하도록 구성했습니다. (Backend.AI의 V-folder는 /home/work/~ 가 기본 디렉토리 구조입니다.)

    학습을 마친 뒤, 해당 경로에 결과 이미지가 생성된 모습입니다.

    Result checking

    위와 같이 파이프라인이 성공적으로 실행된 모습을 작업 이름의 왼쪽에서 확인할 수 있습니다.

    Result

    이제 모델을 Fine-tuning 한 결과를 gemma-2-2b-it와 비교하여 확인해보겠습니다.

    1. SemScore (목표 응답과 모델 응답 간의 의미론적 텍스트 유사성, 1.00 is the best)

    | Base Model | Trained Model | |------------|---------------| | 0.62 | 0.77 |

    학습된 모델의 SemScore가 증가했습니다(0.62 -> 0.77). 이 결과는 학습된 모델이 목표 응답과 의미적으로 더 유사한 출력을 생성할 수 있음을 나타냅니다. 즉, 학습된 모델이 의도한 목표 응답에 더 가깝고 의미적으로 더 일관된 응답을 생성하는 능력이 향상되었다는 것입니다. 결과적으로 학습된 모델의 전반적인 성능과 신뢰성이 크게 향상되었다고 할 수 있습니다.

    1. Truthfulness 학습된 모델은 고득점 사례는 증가하고 저득점 사례는 감소하는 경향을 보입니다. 낮은 점수(1, 2점) (1,111 -> 777), 높은 점수(4, 5점) (108 -> 376) 이는 모델이 진실에 가까운 도메인 정보를 식별하는 능력이 향상되고, 훈련이 효과적이었다는 것을 나타냅니다.

    Truthfulness result

    Conclusion

    이번 글에서는 Backend.AI의 MLOps 플랫폼인 FastTrack을 활용하여 특정 domain에 특화된 모델을 학습하는 Pipeline을 구축해보았습니다. FastTrack의 모든 기능을 활용하지 않고 일부 기능만을 사용했음에도 자원을 유연하게 활용하고, Task 설정을 자유롭게 하여 학습 시간을 단축하고, 자원 활용율을 끌어올릴 수 있었습니다. 또한 독립적인 실행 환경에서 안정적으로 학습을 시킬 수 있었으며, Pipeline Job의 실행 정보를 모니터링할 수 있어 학습이 진행되는 동안 각 파이프라인의 자원 사용량 및 실행 횟수를 파악할 수 있었습니다. 이 글에서 다룬 내용 이외에도 FastTrack은 스케줄링, 병렬 모델 학습과 같은 추가적인 기능들을 다양하게 지원하고 있습니다. 아래 첨부된 래블업 블로그 게시글에서 각각 강지현님과 강정석님이 작성하신 FastTrack의 다른 기능들에 대한 더 많은 정보를 확인할 수 있습니다.

    FastTrack의 모든 기능을 활용하지는 못했지만, 자원의 유연한 활용, 자유로운 task 설정 등을 통해 학습 시간을 단축하고 자원의 utilization rate를 높일 수 있었습니다. 또한 독립적인 실행환경에서 안정적인 학습이 가능하고, Pipeline Job 실행 정보를 통해 각 파이프라인에서의 자원 사용량, 실행 횟수 등을 파악할 수 있었습니다. 이외에도 FastTrack에서는 스케줄링, 병렬 모델 학습 등 많은 기능을 지원합니다. 아래의 문서들에서 FastTrack에 대한 더 많은 정보를 확인할 수 있습니다.

    Backend.AI MLOps 플랫폼 FastTrack을 소개합니다.

    FastTrack 길라잡이: 모델 학습 결과 알림 받기

    26 September 2024

  • Model Variant: 손쉽게 대접하는 다양한 모델 서비스

    By 강지현

    들어가며

    어떠한 연구 목적으로 AI를 학습시켜 결과물을 만들어내야 하는 상황에 있다고 가정해봅시다. 우리가 해야 할 일은 AI에게 가르쳐 준 데이터를 AI가 올바르게 학습하길 기다리는 것뿐이죠. 하지만 AI를 '활용'하는 어떠한 서비스를 만든다고 가정하면 이야기가 복잡해집니다. 다양한 모델을 어떻게 시스템에 적용시킬 것인지, 부하 상황에서 어떤 기준에 의해 스케일링을 시켜야 할지 모든 요소 하나하나가 고민거리죠. 이런 고민에 대한 답을 얻기 위해 함부로 사용자가 존재하는 프로덕션 환경을 수정할 수도 없습니다. 프로덕션 환경을 늘렸다 줄였다 하다가 사고라도 난다면 끔찍한 일이 생길 수도 있거든요. 만약에 끔찍한 일이 벌어졌다면, 벌어진 일을 수습하기 위한 시간이 필요할 텐데, 우리 서비스를 사용하는 소비자에게는 모델 학습을 기다리는 연구자와 같은 참을성을 기대할 수 없을 겁니다. 엔지니어링 영역의 어려움 외에 비용에 대한 어려움도 있습니다. 모델을 서비스하는데에는 당연히 비용이 들고, 모델을 학습시키는 그 순간에도 자원을 소모하고 있는 만큼 사용자가 비용을 지출하고 있는 셈이니까요. 그러나 걱정하실 필요는 없습니다. 이미 세상에는 잘 만들어진 모델들이 많이 존재하고, 우리는 그러한 모델들을 가져다가 서비스하는 것으로 충분한 경우가 많거든요. 저희 솔루션에 관심이 있으셨던 분들이라면 다 아시는 내용이겠지만, Backend.AI는 이미 여러분이 모델을 서비스할 때 필요로 하는 기능들을 다양하게 지원하고 있습니다. 트래픽에 따라 서비스를 늘리는 것이나 줄이는 것도, 사용자의 입맛에 맞춘 다양한 모델을 서비스하는 것도 가능하죠.

    그러나 여기서 멈출 Backend.AI 팀이 아닙니다. 저희는 Backend.AI의 23.09 버전부터 제공된 모델 서비스를 한 층 강화하였고, 다양한 모델을 손쉽게 서비스할 수 있도록 개선하였습니다. 이번 포스팅을 통해 어떤 방법으로 쉽고 간편하게 다양한 모델을 서비스할 수 있는지 알아봅니다.

    이번 포스팅에서는 다양한 종류의 모델을 더욱 간편하게 서비스할 수 있는 기능을 소개합니다. 모델 서비스에 대한 설명은 23.09 버전 업데이트를 릴리즈하며 한 차례 드린 적이 있기 때문에, 자세한 설명은 생략하겠습니다. Backend.AI의 모델 서비스가 생소하시다면, 다음 포스팅을 먼저 읽어보시는 것을 추천합니다. Backend.AI Model Service 미리 보기

    기존 방식

    | | 필요조건 | 기존 방식 | 모델 배리언트(Model Variant) | |---|----------|------------|----------------------------------| | 1 | 모델 정의 파일(model-definitionl.yaml) 작성 | O | X | | 2 | 모델 정의 파일을 모델 폴더에 업로드 | O | X | | 3 | 모델 메타데이터 필요 | O | △* (일부는 자체 다운로드 가능) |

    Backend.AI 모델 서비스는 실행하기 위한 모델 메타데이터 외에 모델을 서비스할 때 실행할 명령어를 일정한 형식으로 담아둔 모델 정의 파일 (model-definition.yaml)을 필요로 했습니다. 서비스를 실행하는 순서는 다음과 같습니다. 모델 정의 파일을 작성하고, 모델 정의 파일을 읽을 수 있도록 모델(model) 타입 폴더에 업로드한 뒤, 모델서비스 시작시 모델 폴더를 마운트하면 자동으로 모델 정의 파일에 따라 엔드유저의 입력을 받아 모델로 전달하고, 응답 값을 보내주는 API 서버 등이 실행되는 형태였습니다. 하지만 이 방식은 모델 정의 파일을 수정할 때마다 파일에 접근해야한다는 단점이 있었습니다. 또, 이미 모델 정의 파일에 모델 경로가 정해져있기 때문에 모델이 달라질 때마다 모델 정의 파일을 다르게 작성해야 하는 것도 귀찮은 부분이었습니다. 이번에 선보이는 모델 배리언트(Model Variant)는 모델 정의 파일이 없이 모델 메타데이터만을 가지고 몇 가지 설정값을 입력하거나, 또는 아예 입력할 필요없이 즉시 모델을 서비스할 수 있는 기능입니다. 모델 배리언트에서는 커맨드(command), vLLM, 그리고 NIM(NVIDIA Inference Microservice) 방식을 지원합니다. 서비스하는 방법과 모델 서비스 실행을 확인하는 방법은 다음과 같습니다.

    이번에 선보이는 모델 배리언트(Model Variant)는 모델 정의 파일이 없이 모델 메타데이터만을 가지고 몇가지 설정값을 입력하거나, 또는 아예 입력할 필요없이 즉시 모델을 서비스 할 수 있는 기능입니다. 모델 배리언트에서는 커맨드(command) 방식, vLLM 방식, 그리고 NIM(NVIDIA Inference Microservice) 방식을 지원합니다. 서비스하는 방법과 모델 서비스 실행을 확인하는 방법은 다음과 같습니다.

    기본적으로, 모델 서비스는 서빙할 모델 메타데이터를 필요로 합니다. 가장 손쉽게 접할 수 있는 모델 메타데이터를 받을 수 있는 Hugging Face 에서 서비스할 모델을 다운로드 받아보세요. 이번 예제에서는 Hugging Face 의 Llama-2-7b-hf 모델과 Calm3-22b-chat 모델을 사용했습니다. 모델 메타데이터를 모델 폴더에 업로드 하는 방법은 앞의 포스팅의 모델 스토리지 준비를 참고하십시오.

    빌드된 이미지에서 자동으로 모델 서비스하기 (command 방식)

    첫 번째로 소개하는 커맨드 방식은 모델 정의 파일에서 모델을 서비스하기 위해 실행하는 명령어 부분이 실행 이미지에 들어간 형태입니다. CMD 라는 환경변수에 실행할 명령어를 지정한 뒤, 이미지를 빌드해 실제 모델을 서비스할 때 다른 입력 없이 바로 실행하는 방식이죠. 커맨드 방식은 서비스가 제대로 실행되고 있는지 확인하는, 이른바 Health check를 지원하지 않습니다. 따라서 대규모의 서비스를 수행할 때보다는 프로토타입으로 바로 서비스를 띄워서 확인해 볼 때 적절합니다. 실행방법은 다음과 같습니다.

    1. 시작화면에서 서비스할 모델 서비스에 해당하는 모델 메타데이터가 들어있는 모델 폴더를 마운트하도록 Model Storage To Mount 항목에서 Llama-2-7b-hf 를 선택하고, Inference Runtime Variant 항목에서 Predefined Image Command 를 선택합니다.

    모델 서비스를 별도의 토큰없이 접근할 수 있도록 제공할 경우 Open To Public 스위치 버튼을 활성화 해주세요.

    모델-서비스-시작화면-모델-메타데이터-마운트-및-CMD-선택

    1. 서비스할 환경을 선택합니다. 여기서는 vllm:0.5.0 를 사용하고, 자원은 CPU 4 Core, Memory 16 GiB, NVIDIA CUDA GPU 10 FGPU 를 할당하도록 설정했습니다.

    모델-서비스-시작화면-실행환경-선택-및-자원할당

    1. 마지막으로 클러스터 크기를 선택하고, 시작버튼을 클릭합니다. 클러스터 크기는 싱글노드, 싱글 컨테이너로 설정했습니다.

    모델-서비스-시작-화면-클러스터-크기-선택-및-시작

    서비스가 성공적으로 띄워졌다면, 서비스 상태는 HEALTHY 로 바뀌게 되고 엔드포인트 주소가 나오게 됩니다.

    모델-서비스-상세-화면

    서비스 확인하기

    서비스가 정상적으로 띄워졌다면, cURL 명령어로 서비스 모델명을 우선 확인합니다.

    curl https://cmd-model-service.asia03.app.backend.ai/v1/models \
    -H "Content-Type: application/json"
    

    모델명-확인하기

    이제 서비스에 보낼 입력을 cURL 명령어로 보내고, 응답값을 확인해보겠습니다.

    CMD로 실행하는 모델 서비스는 이미지에 이미 모델명이 정의되어 있기 때문에 모델명을 확인후 요청을 보낼 때 모델명을 model 키의 값으로 입력해야 합니다.

    curl https://cmd-model-service.asia03.app.backend.ai/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
    "model": "image-model",
    "prompt": "San Francisco is a",
    "max_tokens": 7,
    "temperature": 0}'
    

    모델-서비스-요청-결과-화면

    vLLM 모드로 모델 서비스하기

    vLLM 모드는 앞에서 소개한 커맨드 방식과 비슷하지만, vLLM 을 실행할 때 입력하는 여러가지 옵션들을 환경변수로 작성할 수 있습니다. 실행방법은 다음과 같습니다.

    실행방법

    1. 시작화면에서 서비스할 모델 서비스에 모델 폴더를 마운트하고, Inference Runtime Variant 항목에서 vLLM 을 선택합니다.

    모델-서비스-시작-화면-모델-메타데이터-마운트-및-vLLM-선택

    1. 서비스할 환경을 선택합니다. 앞서 설명한 커맨드 방식과 동일하게 vllm:0.5.0 으로 선택하고, (자원은 동일하게 설정해도 되지만) 이번에는 CPU 16 Core, Memory 64 GiB, NVIDIA CUDA GPU 10 fGPU를 할당하도록 하겠습니다.

    모델-서비스-시작-화면-실행환경-선택-및-자원-할당

    1. 마지막으로 클러스터 크기를 선택하고 환경 변수 BACKEND_MODEL_NAME 을 입력합니다. 이 값은 vLLM에서 --model-name 옵션에 대응하는 값으로, 사용자가 서비스에 요청을 보낼 때 지정하는 model 값이 됩니다.

    모델-서비스-시작-화면-실행환경-선택-및-자원-할당

    마찬가지로 서비스가 성공적으로 띄워졌다면, 서비스 상태는 HEALTHY 로 바뀌게 되고, 서비스가 띄워진 엔드포인트 주소가 나오게 됩니다.

    모델-서비스-상세-화면

    서비스 확인하기

    서비스에 보낼 입력을 cURL 명령어로 보내고, 응답값을 확인해보겠습니다. 이 때 model 값은 아까 설정한 BACKEND_MODEL_NAME 값으로 입력합니다. 입력이 끝났다면 START 버튼을 클릭해서 서비스를 생성합니다.

    curl https://vllm-calm3-22b-chat.asia03.app.backend.ai/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
    "model": "vllm-model",
    "prompt": "初めて会う日本人ビジネスマンに渡す最高の挨拶は何でしょうか?",
    "max_tokens":  200,
    "temperature": 0
    }'
    

    모델-서비스-요청-결과-화면

    NIM 모드로 모델 서비스하기

    NIM 을 실행하기 위해서는 NGC의 NIM 모델 레지스트리에 접근할 수 있는 계정으로부터 발행된 API 키가 있어야 합니다. 키값을 얻는 방법은 다음 내용을 참고하시기 바랍니다. NVIDIA Docs Hub : How to get NGC API Key

    NIM(NVIDIA Inference Microservice) 모드 역시 커맨드 모드와 유사하나, NVIDIA의 NIM을 지원하는 모델 서버가 내장된 이미지로 실행해야 합니다. 또, 모델을 불러올 때에, NGC API 키 값이 필요합니다. 모든 것이 준비되었다는 가정하에 모델 서비스를 시작해보겠습니다.

    실행방법

    1. 시작화면에서 서비스할 NIM 에서 받아올 메타데이터를 캐싱할 비어있는 모델 타입 폴더를 선택하고, Inference Runtime Variant 항목에서 NIM 을 선택합니다.

    모델-서비스-시작-화면-모델-폴더-마운트-및-NIM-선택

    1. 서비스할 환경을 선택합니다. 여기서는 ngc-nim:1.0.0-llama3.8b 를 사용하고, 자원은 CPU 8 Core, Memory 32 GiB, NVIDIA CUDA GPU 15 FGPU 를 할당하도록 설정했습니다.

    모델-서비스-시작-화면-실행환경-선택-및-자원-할당

    1. 마지막으로 클러스터 크기를 선택하고 환경 변수 HF_HOME으로 기본 경로인 /models 경로를 입력합니다. 그리고 NGC_API_KEY 을 입력하고, 발급받은 키값을 입력합니다. 입력이 끝났다면 CREATE 버튼을 클릭해서 서비스를 생성합니다.

    모델-서비스-시작-화면-클러스터-크기-선택-환경변수-입력-및-시작

    NIM 을 사용할 경우 모델 메타데이터를 저장소로부터 받아오기 때문에 처음 실행시에는 다소 시간이 소요될 수 있습니다. 세션 페이지에서 서비스중인 라우팅 세션에 대한 컨테이너 로그를 확인하여 진행상황을 확인할 수 있습니다. 모델-서비스에-대응하는-라우팅-세션 NIM-에서-데이터를-받고-있는-로그가-띄워진-컨테이너-로그-화면

    커맨드, vLLM 모드와 같이 서비스가 성공적으로 띄워졌다면, 서비스 상태는 HEALTHY 로 바뀌게 됩니다. 서비스가 띄워진 엔드포인트 주소를 활용해 서비스에 보낼 내용을 다음과 같이 입력하고, 응답값을 확인해보겠습니다.

    서비스 확인하기

    from openai import OpenAI
    
    client = OpenAI(
      base_url = "https://nim-model-service.asia03.app.backend.ai/v1",
      api_key = "$YOUR_NGC_API_KEY"
    )
    
    completion = client.chat.completions.create(
      model="meta/llama3-8b-instruct",
      messages=[
          {        
            "role":"user", 
            "content":"Hello! How are you?"
          },
          {
            "role":"assistant",
            "content":"Hi! I am quite well, how can I help you today?"
          },
          {
            "role":"user",
            "content":"Can you write me a song?"
          }],
      temperature=0.5,
      top_p=1,
      max_tokens=1024,
      stream=True
    )
    
    for chunk in completion:
      if chunk.choices[0].delta.content is not None:
        print(chunk.choices[0].delta.content, end="")
    

    모델-서비스-요청-결과-화면

    마치며

    모델 배리언트 기능은 이미 학습된 모델로 실질적인 서비스를 제공하는 것을 목표로 하는 연구자와 기업에 많은 도움이 될 것입니다. 강력한 자원 관리 시스템과 NVIDIA GPU, AMD ROCm, TPU, Graphcore IPU, Furiosa Warboy, Rebellions ATOM, Hyperaccel LPU 등과 같이 다양한 AI 가속기 지원을 바탕으로 한 Backend.AI 는 이제 단순히 모델을 학습하는 것을 뛰어넘어 서비스까지 쉽게 배포할 수 있는 통합 환경을 제공하게 되었습니다. Backend.AI 와 함께 여러분이 원하는 AI 모델을 언제든 서비스해보세요.

    11 July 2024

  • 자연으로부터 다시 배우기: 뉴로모픽 컴퓨팅과 딥 러닝

    By 신정규

    이 글은 2022년 5월 ESC 과학기술뉴스 에 기고된 글입니다.

    인공 신경망 분야가 본격적으로 주목 받기시작한 지 곧 10년이 됩니다. 그 길지 않은 시간 동안 인공 신경망 분야는 딥 러닝의 발전과 함께 엄청난 속도로 무수한 문제들을 해결하고 있습니다. 인공 지능 구현에 있어 가장 가능성이 높은 방법으로 꼽히고 있기도 합니다.

    그 첨단에서는 하이퍼스케일 딥 러닝 모델과 그 구현 방법에 대한 다양한 뉴스들이 주목 받고 있습니다. 2022년 4월 엔비디아의 새로운 GPU인 H100의 소식이 뉴스라인을 뒤덮었고, AMD의 고성능 컴퓨팅 특화 GPU인 MI 시리즈와 함께 인텔의 새로운 GPU인 폰테 베키오가 AI와 블록체인 채굴 가속 성능을 엄청나게 올려 하이퍼스케일 AI의 새 격전지를 만들 것으로 예상되고 있습니다.

    하이퍼스케일 인공지능의 붐에 묻힌 소식들 가운데 대중적으로 크게 관심을 끌지 못했던 뉴스가 있습니다. 바로 작년 10월 인텔이 발표한 로이히(Loihi) 2 칩 소식이었습니다[1]. 이 소식은 굉장히 흥미로운 역사와 기술적 배경을 안고 있습니다. AI 훈련 및 서비스 가속 칩들이 늘어나는 와중에 일어나고 있는 재미있는 기술 뉴스 뒤에 숨어 있는 과학에 대해 소개해 보고자 합니다.

    딥 러닝으로 지성을 코딩할 수 있을까?

    GPU 기반의 행렬 연산 가속으로 날개를 달기 시작한 2013년 이후, 딥 러닝 분야는 연산 규모에 힘입은 다양한 가능성을 탐구하기 시작했습니다. 2016년의 알파고 쇼크를 기점으로 딥 러닝은 연구 분야를 넘어서 그 범위를 응용 분야로 조금씩 확장하기 시작했습니다. 2017년에 제안되고 2018년부터 그 사용이 본격화된 트랜스포머 모델 구조[2]는 어텐션 및 셀프 어텐션이라는 개념을 도입해 딥 러닝 모델이 스스로의 기억 구조를 만드는 과정을 크게 개선했습니다. 트랜스포머 모델 구조는 이후 엄청나게 다양한 분야의 딥 러닝 모델에 사용되고 있으며, 특히 데이터가 풍부한 자연어 처리 및 이미지 처리 분야에서 두각을 나타내고 있습니다. 트랜스포머는 기존의 딥 러닝 모델이 효과적으로 동작하지 않는 것처럼 보이던 다양한 문제들을 딥 러닝 모델로 해결할 수 있게 해 주었습니다.

    만능처럼 보이는 이 모델은 2018년부터 딥 러닝 모델의 거대화 추세를 이끌기 시작했습니다. 딥 러닝 모델의 크기는 모델의 매개 변수의 수로 결정되는데, 딥 러닝의 매개 변수는 모델을 구성하는 퍼셉트론 사이의 연결 정보이며, 실제 뉴런의 시냅스 연결에 해당합니다. 연결이 많을수록 딥 러닝 모델은 더 복잡한 입력을 구분하고 판단할 수 있게 됩니다. 딥 러닝 모델이 복잡해지고 거대해질수록, 매개 변수는 기하급수적으로 늘어납니다. 2019년 이전까지의 딥 러닝 모델 매개 변수의 수는 해마다 대략 3 ~ 5배씩 증가해 왔지만, 2019년 이후에는 해마다 열 배 이상씩 증가하고 있습니다. 최근 약 2 ~ 3년 동안 등장한 거대 딥 러닝 모델들을 ‘하이퍼스케일 AI’라고 부르기도 합니다. 자연어 처리 분야에서 대중에게 잘 알려진 하이퍼스케일 딥 러닝 모델로는 OpenAI의 GPT-3, Google의 LaMDA 등이 있습니다. 이러한 거대 모델의 경우, 예를 들면 GPT-3의 경우에는 모델 훈련을 위한 시스템 비용(장비 구입 없이 클라우드상에서 훈련 한 번 시키는 비용)으로 최소 약 50억 원 이상이 들어가는 것으로 추산됩니다[3].

    하이퍼스케일 모델들은 기존에 풀기 어렵거나 풀지 못했던 문제들을 풀어내고 있습니다. 중력 렌즈를 새로 발견하거나[4] 렌즈로 인해 생긴 왜곡을 펴서[5] 우주의 신비를 풀기도 하죠. 기존의 방법보다 훨씬 더 짧은 시간과 적은 비용으로 단백질의 접힘 구조를 예측하기도 하고[6], 신약을 찾아냅니다[7]. 오랜 시간에 걸쳐 흐름을 파악해야 하는 스타크래프트 2 전략 시뮬레이션 같은 문제[8]를 풀기도 합니다.

    이렇게 여러 가지 문제를 해결해 주다 보니 당연히 따라오는 궁금증들이 있습니다. 과연 막대한 양의 자원을 부어 넣어 딥 러닝 모델을 만드는 이러한 접근은 지속 가능할까요? 그리고 이러한 방법으로 '지성'을 코딩할 수 있을까요?

    이 두 질문에 답하기 위해 딥 뉴럴 네트워크부터 오늘의 주제인 뉴로모픽 컴퓨팅까지 빠르게 한 번 이해해 보겠습니다.

    딥 뉴럴 네트워크: 태생과 차이점

    사실 딥 러닝은 줄임말입니다. 원래는 딥 뉴럴 네트워크(Deep Neural Network, DNN), 더 풀어서 쓰면 Artificial Neural Network with deep layers (심층 인공 신경망)입니다. 인공 신경망 이론은 신경 세포의 전기적 특성을 수학적으로 모사하는 과정에 뿌리를 두고 있습니다. 신경 세포의 전기적 특성과 함께 신경 세포 사이의 연결 구조가 정보 처리 과정에서 강화 또는 약화되는 가소성[e1]을 수학적으로 모사하고, 이를 단순화하면서 시작되었죠. 인공 신경망 모델은 신경 세포들의 연결에 따른 활성 과정을 엄청나게 단순화한 퍼셉트론[9]과, 신경 세포의 발화 과정을 모사하는 함수에서 시간 의존성을 제외하고 신경 세포로의 신호 입력에 대한 함수로 단순화한 활성 함수, 마지막으로 신경 세포 사이의 연결이 얼마나 강한지를 나타내는 가중치를 매개변수로 나타내는 수학적 모델입니다.

    인공 신경망 이론은 실제 신경망의 특징에 뿌리를 두고 있긴 하지만 실제 신경망과는 근본적인 차이가 있습니다. 바로 시간에 따른 동작을 결정하는 동역학의 유무입니다. 실제 신경망은 신경 세포들 간의 동역학에 의해 다양한 결과들이 결정됩니다. 신경 세포는 외부로부터 자극을 받았을 때의 각각의 동역학적 특성이 있고, 그에 따라 물리적으로 강화 또는 약화되는 방식의 가소성이 있습니다. 가령 어떤 판단을 내릴때 계속 같이 사용되고 연결된 신경세포들은, 입력 신호를 받았을 때 비슷한 시점에 활성화됩니다. ‘시간적으로’ 비슷한 시점에 활성화된 신경 세포들 사이의 연결에 해당하는 축삭이 물리적으로 두꺼워지는 것을 발견할 수 있습니다. 반면 일반적인 인공 신경망은 동역학 대신 역전파 이론을 이용하여 가소성을 모사합니다. 역전파 이론은 어떤 판단을 바르게 내릴 때 사용된 퍼셉트론 사이의 연결의 가중치를 강화하는 계산을 간단하게 할 수 있는 방법입니다. 인공 신경망에서 입력되는 정보를 처리는 과정은 퍼셉트론 사이의 가중치를 이용하므로 즉각적입니다. 입력 정보가 출력 정보로 이어지는 과정이 시간에 따른 함수로 계산되지 않으므로, 동역학 요소가 없습니다.

    동역학 외에도 다양한 차이가 있습니다. 이런 차이는 대개 생물학적 신경망에서는 불가능한 가정들을 도입해서 생긴 것으로, 1990년대 인공 신경망 이론의 한계를 극복하기 위한 노력의 결과입니다. 활성 함수에 ReLU[e2]를 사용한 것이 하나의 예입니다. 일반적인 신경 세포는 역치와 가중치 한계가 존재합니다. 무한대의 활성값은 물리적으로 불가능하기 때문입니다. 그래서 수학 모델도 활성함수로 역치와 가중치 한계를 잘 보여주는 함수를 사용했습니다. 허나 심층 인공 신경망이 깊어지면서 연구자들은 인공 신경망 훈련이 더 이상 진행되지 않는 것을 발견하였습니다.[e3]ReLU 활성함수는 물리적으로는 불가능하지만 수학적으로 무한대의 가중치를 가질 수 있습니다[e4]. ReLU를 심층 인공 신경망에 도입하면서 새로운 훈련이 가능해졌고 생물학적 신경망과의 차이는 커졌습니다.

    동역학을 고려할 필요가 없는 인공 신경망은 행렬 연산의 연속으로 변환이 가능하기에 엄청난 속도로 계산이 가능합니다. 하지만 생물에서 볼 수 있는 신경망과는 큰 차이가 생겼습니다. 그러면 딥 러닝 모델과 실제 우리의 뇌 속에서 일어나는 신경학 과정은 이제 완전히 다른 토대 위에 서 있는 걸까요?

    자연으로부터 다시 배우기: 뉴럴 네트워크의 동역학

    단일 신경세포는 다양한 방법으로 신호를 주고 받습니다. 일부는 전기적 신호이고 일부는 화학적 신호입니다. 단일 신경 세포 안에서의 전기적 신호 특성은 굉장히 일찍 해석되고 수식화 되었으며[10], 퍼셉트론의 이론적 기반이 되었습니다. 문제는 단순화 없이 동역학을 계산하기엔 그 수식이 너무 복잡하다는 점이었습니다. 이후 시간에 따른 전기적 반응을 계산적 부담을 줄일 수 있게 어림한 다양한 수학 모델이 제안되었고, 이러한 모델을 사용하여 다양한 단일 신경 세포 시뮬레이터들이 공개되고 있습니다. 대표적인 시뮬레이터로는 NEURON[11]이 있습니다.

    앞서 동역학의 모사에는 엄청난 계산량이 필요하다고 했습니다. 어느 순간 우리는 연산 성능이 넘쳐나는 시대를 맞이하고 있습니다. 넘쳐나는 연산 성능을 바탕으로 이러한 단일 신경세포 시뮬레이션들을 이어붙이면 어떻게 될까요?

    엄청난 계산량으로 인한 연산 속도 문제를 해결하고 동역학 기반의 인공 신경망을 만들기 위한 방법으로 알고리즘과 하드웨어, 두 가지 측면에서의 시도가 있습니다. 알고리즘 측면의 접근은 스파이킹 뉴럴 네트워크(spiking neural network, SNN)으로, 실제 신경세포에서 발생하는 스파이크 기반의 가소성을 도입하여 동역학 모델 기반의 인공 신경망을 만들어 보려는 것입니다. 하드웨어 측면의 접근은 2012년부터 본격화된 뉴로모픽 컴퓨팅입니다. 신경 세포에 해당하는 물리적 객체를 만드는 방법으로 인공 신경망을 구현하는 것입니다. 동역학 모사에 들어가는 엄청난 계산량을 범용 연산으로 해결하기에는 아직도 컴퓨터가 느립니다. 이걸 해결하기 위해 아예 회로 수준에서 신경 세포에 해당하는 수학적 특성을 갖는 객체를 만들거나 하드코드로 연산을 만들어 넣은 전용 소자를 만들면 어떨까 하는 방법이죠. 최근에는 뉴로모픽 컴퓨팅과 SNN을 구분하지 않고, SNN을 소자 수준에서 구현하는 방식을 뉴로모픽 컴퓨팅으로 부르는 등의 통합이 이루어지고 있기도 합니다. 두 가지 접근 모두 기존의 인공 신경망 이론이 사용하지 않았던 동역학 특성을 모사하여 새로운 현상이나 딥 러닝의 가능성을 찾으려는 시도입니다.

    뉴로모픽 컴퓨팅 분야에서 두각을 나타내고 있는 회사 중 한 곳이 인텔입니다. 인텔은 2017년 가을 약 13만 개의 뉴런과 1억 3천만 개의 시냅스를 내장한 연구용 뉴로모픽 칩인 로이히Loihi[e5] 칩을 공개했습니다. 로이히 칩을 사용하여 기존의 DNN 기반 알고리즘을 이식한 후 다양한 비교 테스트를 수행했고[12], 재미있게도 SNN을 사용해서도 DNN과 비슷한 결과를 얻을 수 있다는 걸 보였습니다.

    인텔은 이후 여러 개의 로이히 칩을 연결하여 거대한 SNN 시스템을 만들었고, 나우쿠(Nahuku)는 41억 개의 시냅스를, 포호이키 스프링(Pohoiki Springs) 뉴로모픽 슈퍼컴퓨터[13]는 768개의 로이히 칩을 바탕으로 약 1억 1백만 개의 뉴런과 1천억 개의 시냅스를 구현했습니다. 이 과정에서 인텔은 SNN를 로이히 위에서 구현하는 소프트웨어 스택을 만들어냈으며, 그 결과 작년 가을에 로이히 2와 함께 뉴로모픽 어플리케이션을 개발할 때 사용할 수 있도록 라바(Lava) 오픈소스 소프트웨어 프레임워크를 공개했습니다[14].

    DNN과 SNN이 비슷한 결과를 보여주리라는 것은 예상되어 있었습니다. 물리학적으로 보면 인공 신경망이 다양한 문제를 추론하는 과정은 결국 정보 기반으로 초 고차원의 불연속 상태 공간을 정의한 후, 새로운 정보를 그 공간에 투사하는 것입니다. DNN과 SNN 모두 초고차원의 불연속 상태 공간을 정의할 수 있는 특성이 있죠. 생물은 진화를 통해 정보에 적응하는 특성을 물리적으로 만들어냈고, 인류는 생체모방공학(Biomemetics)을 통해 인공 신경망 이론을 창안해 내고 딥 러닝을 발전시켰습니다.

    늘 그랬듯이 언제나 답을 찾는

    지금까지 우리는 신경세포를 동역학 수준에서 모사한 네트워크로도 실제 우리가 딥 러닝에 기대했던 것과 비슷한 결과를 얻을 수 있다는 것을 알게 되었습니다. 그러면 이에 따라 나오는 의문이 있습니다. 결과가 비슷하다면 굳이 SNN과 뉴로모픽 컴퓨팅을 쓸 필요가 있을까요? 오늘 소개한 것들은 다양한 시도들의 극히 일부입니다. SNN과 뉴로모픽 컴퓨팅이 기존의 접근과 어떤 다른 결과를 만들어내는지는 계속 연구되고 있습니다. SNN이 더 나은 성능을 보이는 결과 또한 로보틱스 및 센서를 중심으로 등장하고 있으며, 동역학적 특성을 반영하는 것이 인과관계 유추에 더욱 강력할 것이라는 연구 결과도 나오고 있습니다. 더 깊게 들어가서 시냅스에서 일어나는 화학적 신호를 시뮬레이션하는 시도[15]도 있습니다. 신경망의 연결 구조에 더하여, 신경망을 구성하는 개별 요소에 우리가 아직 모르는 지능의 창발을 불러오는 요소가 있을지도 모르기 때문입니다. 그런데 이 정도로는 굳이 왜 SNN을 사용하는가에 대한 답변으로 부족할 것입니다.

    글 서두에서 드렸던 두 가지 질문을 다시 해 보겠습니다. 막대한 양의 자원을 부어넣어 딥 러닝 모델을 만드는 이러한 접근이 지속 가능할까요? 그리고 이러한 방법으로 '지성'을 코딩할 수 있을까요? 뉴로모픽 컴퓨팅이 답이 될 수 있을까요? 그럴 수도 있고, 아닐 수도 있습니다.

    DNN과 SNN이 제각기 높은 성능과 결과를 보이는 이유는 결국 두 구현체 모두 우리가 지금은 모르는 정보 최적화 이론이 그 바탕에 있기 때문일 것입니다. 그걸 알게 된다면, 우리는 AI를 다른 방식으로 구현할 수도 있을 것입니다. 처음에 드린 의문인 "막대한 양의 자원을 부어넣어 딥 러닝 모델을 만드는 이러한 접근이 지속 가능할지"에 대한 답을 얻는 하나의 길이 될 수도 있겠습니다. 뉴로모픽 컴퓨팅과 SNN은 새로운 관점에서 우리가 이 문제를 뜯어볼 수 있게 해 줍니다.

    그리고 두 번째 질문에 대한 답이 될 수도 있을 것입니다. 우리는 언제나 가슴속에 질문 하나를 안고 삽니다. '우리는 누구인가?' 뉴로모픽 컴퓨팅과 SNN의 접근은 우리가 이 근본적인 철학적 질문에 물리적으로 접근할 때 가장 이해가 쉬운 방법입니다. 우리가 이미 알고 있는 (그렇지만 그 얼개는 아직 모르는) 시스템으로 설명하기 때문입니다.

    뉴로모픽 컴퓨팅 외에도 다양한 분야에서 위의 두 질문에 대한 대답에 도전하고 있습니다. 그중 한 가지는 양자 컴퓨팅인데요, 다음에 양자 컴퓨팅와 딥 러닝에 대한 기사를 같이 읽어 볼 기회를 만들어 보겠습니다.

    참고 문헌

    • [1] https://www.anandtech.com/show/16960/intel-loihi-2-intel-4nm-4
    • [2] https://arxiv.org/abs/1706.03762
    • [3] https://lambdalabs.com/blog/demystifying-gpt-3
    • [4] https://iopscience.iop.org/article/10.3847/1538-4357/abd62b
    • [5] https://academic.oup.com/mnras/article-abstract/504/2/1825/6219095
    • [6] https://www.nature.com/articles/s41586-021-03819-2
    • [7] https://www.frontiersin.org/articles/10.3389/frai.2020.00065/full
    • [8] https://www.deepmind.com/blog/alphastar-mastering-the-real-time-strategy-game-starcraft-ii
    • [9] https://doi.apa.org/doi/10.1037/h0042519
    • [10] https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1392413
    • [11] https://neuron.yale.edu/neuron
    • [12] https://ieeexplore.ieee.org/document/8259423
    • [13] https://arxiv.org/abs/2004.12691
    • [14] https://www.intel.com/content/www/us/en/newsroom/news/intel-unveils-neuromorphic-loihi-2-lava-software.html
    • [15] https://www.ibm.com/blogs/research/2016/12/the-brains-architecture-efficiency-on-a-chip

    미주

    • [e1] 가소성Plasticity은 외부의 환경 변화나 자극에 의하여 스스로가 적응하여 특성을 변경하는 능력입니다.
    • [e2] Recified Linear Unit의 약자입니다. 0보다 크면 y=x 함수 모양이 되는 활성함수로, x값에 따라 y가 계속 커질 수 있습니다.
    • [e3] Vanishing Gradient라는 문제입니다.
    • [e4] 신경세포는 점점 더 큰 입력을 받아도 세포의 물리적 한계 이상의 출력을 내 보낼 수가 없습니다. 전선에 전류를 무한정 흘릴 수 없는것과 마찬가지입니다. ReLU는 입력을 주는대로 출력이 그에 따라 선형적으로 무한정 증가하는 함수입니다.
    • [e5] 인텔은 뉴로모픽 칩 및 시스템에 하와이의 다양한 지명을 코드네임으로 붙이고 있습니다.

    27 June 2024

도움이 필요하신가요?

내용을 작성해 주시면 곧 연락 드리겠습니다.

문의하기

Headquarter & HPC Lab

서울특별시 강남구 선릉로100길 34 남영빌딩 4층, 5층

© Lablup Inc. All rights reserved.