HuggingFace로 배포된 Llama 모델#

소개#

생성형 AI가 발전함에 따라 대형 언어 모델(LLM)은 우리 일상의 한 부분이 되었습니다. 이로 인해 최근 사람들은 ChatGPT, Llama, Gemini, Qwen, Mistral, Exaone, DeepSeek, Claude, Clova, Grok 등 무수히 많은 모델들 중 적어도 하나 이상을 사용해본 경험이 있습니다.

이러한 시류에 맞춰 qbcompiler는 LLM을 컴파일하고, 모빌린트의 엣지 디바이스에서 사용할 수 있도록 지원하고 있습니다. 이 섹션에서는 Meta의 Llama 버전 3.2, 1B 크기의 경량 모델 중 Instruction Tuning을 거친 모델을 사용하여 그 방법에 대해 알아봅니다.

모델 준비#

현재 LLM은 AI 모델 배포 플랫폼인 HuggingFace를 통해 공유되고 있습니다. 따라서 이 섹션에서의 모델 준비 과정은 HuggingFace의 transformers형식으로 배포되는 모델을 준비하는 과정에 대해 설명합니다.

HuggingFace의 transformers 형식의 모델들은 다음과 같이 준비할 수 있습니다.

HuggingFace 인증#

모델을 다운로드하기에 앞서 Huggingface 웹페이지를 방문하여 계정을 만들어야 합니다. 그 다음 사이드바를 클릭하여 토큰 관리 페이지로 들어가야 합니다.

토큰 관리 페이지 열기

토큰 관리 페이지에서, 현재 생성된 토큰이 없다면 새로운 토큰을 생성해야 합니다. 모델을 다운받기 위해서는 별도의 설정 없이 읽기 전용 토큰만 생성해도 충분합니다.

새로운 토큰 생성

읽기 전용 토큰 생성

생성된 토큰을 복사하고, HuggingFace CLI 명령어를 실행하여 로그인합니다.

접속 토큰 복사

터미널에서 다음의 명령어를 실행하여 접근 권한을 인증합니다.

huggingface-cli login

로그인 과정에서 Git 인증서에 대한 질문에 대해 “예”라고 대답하는 것을 권장합니다. 만약 그랬을 때 다음과 같은 경고 메시지를 보게 된다면 경고 메시지에 적힌 지시사항을 수행하고 로그인 명령어를 다시 실행하여 절차를 한 번 더 진행해야 합니다.

HuggingFace CLI 로그인

모델 다운로드#

모델을 다운로드하기 위해서는 Git을 통해 대용량 파일을 관리할 수 있게 하는 Git LFS라는 도구를 설치해야 합니다. 다음의 명령어를 실행하여 설치할 수 있습니다.

apt-get update
apt-get install git-lfs

이제 Llama-3.2-1B-Instruct 페이지로 가서 요구된 양식을 작성합니다. 그 다음 “clone repository”라고 적힌 탭을 누르고, 보이는 지시사항에 따라 모델을 로컬 환경에 다운로드합니다.

Llama-3.2-1B-Instruct 모델 페이지

Calibration Dataset 준비#

모빌린트의 NPU상에서의 LLM 성능 향상을 위해, LLM의 Embedding 부분은 외부에서 처리하도록 구현되었습니다. 따라서 LLM을 컴파일하는 경우 문장에 대한 Token Tensor를 사용하는 것이 아니라 Embedded Tensor가 요구됩니다.

따라서 Calibration Dataset을 만들기 위해서도 외부에서 Embedding을 거치는 등의 추가적인 작업이 필요합니다. 아직 LLM에 대한 지원은 개발 중에 있기에 이 작업을 지원하는 도구들이 qbcompiler에 갖추어지지 않았으나, 추후 업데이트에 반영될 예정입니다.

Embedding Weight 추출#

우선 해야할 것은 Embedding 레이어의 가중치를 추출하는 것입니다. transformers 형식으로 배포되는 모델들의 경우 “get_input_embeddings”라는 함수를 항상 구비하고 있고, 이 함수를 이용하면 손쉽게 가중치 텐서를 얻을 수 있습니다. 아래 코드는 그러한 방법으로 가중치 텐서를 “Llama-3.2-1B-instruct_embedding_weight.pt”라는 이름으로 저장합니다.

from transformers import AutoModelForCausalLM
import torch

model_dir = "/workspace/Llama-3.2-1B-Instruct" # path where the model was downloaded
model = AutoModelForCausalLM.from_pretrained(
    model_dir,
)  # model
model.eval()

embedding_weight = model.get_input_embeddings().weight  # (vocab_size, dim) tensor

torch.save(embedding_weight, model_dir.split("/")[-1] + "_embedding_weight.pt")

Calibration Dataset 생성#

비전 모델에 대한 Calibration Dataset처럼, LLM의 Calibration Dataset도 실제 모델의 학습에 사용된 데이터와 유사한 입력값들로부터 생성하는 것이 권장됩니다. 이 경우 저희는 위키미디어 재단에서 공식 제공하는 다중 언어 데이터셋인 Wikipedia 데이터셋을 사용합니다.

아래 코드는 Wikipedia 데이터셋에 포함된 문서를 Embedded Tensor로 변환하는 과정을 구현하고 있습니다. 우선 문서를 Token Tensor로 변환, 지정된 범위의 사이즈로 조정하고, Embedding을 거쳐 Calibration Dataset을 생성합니다. 사용자는 아래 코드를 수정하여 Calibration Dataset에 사용되는 문장의 최대/최소 길이 및 전체 데이터 수를 조정할 수 있습니다.

from transformers import AutoTokenizer
from datasets import load_dataset
import torch
import numpy as np
import os

os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # set GPU device
model_dir_list = [  # tokenizer directories
    "/workspace/Llama-3.2-1B-Instruct",
]
embedding_dir_list = [  # corresponding embedding weight path
    "/workspace/Llama-3.2-1B-Instruct_embedding_weight.pt",
]

min_seqlen = 512  # sequence length minimum
max_seqlen = 2048  # sequence length maximum
max_calib = 128  # number of calibration

calib_dir = "/workspace"

for model_dir, embedding_dir in zip(model_dir_list, embedding_dir_list):
    tokenizer = AutoTokenizer.from_pretrained(
        model_dir,
        trust_remote_code=True,
    )  # tokenizer for model
    model_name = model_dir.split("/")[-1]

    subset_list = [ # subset for each language
        #"20231101.ko",
        "20231101.en",
        #"20231101.ja",
        #"20231101.zh",
        #"20231101.fr",
        #"20231101.de",
        #"20231101.es",
        #"20231101.ru",
    ]

    for subset_name in subset_list:
        lang = subset_name.split(".")[-1]
        dataset = load_dataset("wikimedia/wikipedia", subset_name, split="train")[
            "text"
        ]
        embedding_weight = torch.load(
            embedding_dir
        )  # (vocab_size, dim) embedding weight
        embedding_layer = torch.nn.Embedding.from_pretrained(embedding_weight).to(
            "cuda"
        )  # set embedding layer

        os.makedirs(
            os.path.join(calib_dir, f"{model_name}-Wikipedia-{lang}"), exist_ok=True
        )  # calibration save dir

        cur_num_calib = 0  # current number of calib
        for i, text in enumerate(dataset):
            print(f"Sentence {i}")
            token_ids = (
                tokenizer(text, return_tensors="pt")["input_ids"]
                .squeeze()
                .to("cuda")
            )
            print(token_ids.shape)
            if token_ids.ndim == 0:  # empty
                continue
            embedded_text = embedding_layer(token_ids)
            if embedded_text.ndim == 1:
                embedded_text = embedded_text.unsqueeze(0).unsqueeze(0)
            elif embedded_text.ndim == 2:
                embedded_text = embedded_text.unsqueeze(0)
            print(embedded_text.shape)

            if embedded_text.shape[1] < min_seqlen:
                continue
            elif embedded_text.shape[1] > max_seqlen:
                embedded_text = embedded_text[:, :max_seqlen, :]

            np.save(
                os.path.join(calib_dir, f"{model_name}-Wikipedia-{lang}/inputs_embeds_{cur_num_calib}.npy"),
                embedded_text.cpu().numpy(),
            )
            cur_num_calib += 1

            if cur_num_calib > max_calib - 1:
                break

모델 컴파일#

HuggingFace transformers형식으로 배포되는 모델을 컴파일하는 경우 mxq_compile은 추가적인 입력값들을 요구합니다. 우선 “weight_dtype” 변수를 원본 모델의 데이터 타입과 동일한 것으로 지정해주어야 합니다. 해당 타입은 원본 모델 폴더의 “config.json”에서 찾을 수 있습니다. 또한 LLM의 경우 연산량이 매우 많기 때문에 컴파일 시 GPU를 사용하는 것이 권장됩니다. 다만, GPU의 메모리 크기가 충분한지 확인이 필요합니다.

from qbcompiler import mxq_compile, get_llm_config
 
model_id = "meta-llama/Llama-3.2-1B-Instruct"
calib_path ="/workspace/Llama-3.2-1B-Instruct-Wikipedia-en" # folder of npy files
save_path = "./Llama-3.2-1B-Instruct.mxq"
hf_config = {
    "library": "transformers",
    "loader": "AutoModelForCausalLM",
    "tokenizer": "AutoTokenizer",
    "model_args": (),
    "model_kwargs": {
        "trust_remote_code": True,
    },
    "tokenizer_args": (),
    "tokenizer_kwargs": {},
}
 
# seqlen should be the same as cachelen on current version
llm_config = get_llm_config(
    llm_config_apply=True,
    max_sequence_length=4096,
    max_cache_length=4096,
    use_full_seq_length=True,
)
 
mxq_compile(
    model=model_id,
    calib_data_path=calib_path,
    save_path=save_path,
    backend="torch",
    quantization_output=0, # per layer quantization for the output layer
    hf_config=hf_config,
    use_gpu_only_for_calibration=True,  # decrease GPU memory usage
    weight_dtype="bfloat16",  # calibration weight data that should be same as dtype of the original model
    llm_config=llm_config,
    device="gpu" # using GPU is recommended
)

LLM 컴파일 결과