사전학습 데이터 파이프라인

모델은 거울입니다. 무엇을 먹이느냐를 그대로 비춥니다. 쓰레기 데이터를 먹이면, 아주 유창하게 쓰레기를 비춥니다.

유형: Build 언어: Python 선수 지식: Phase 10, Lesson 01-02 (토크나이저, 토크나이저 직접 구현) 예상 시간: 약 90분

학습 목표

  • 테라바이트(TB) 단위 텍스트를 모두 메모리에 올리지 않고 토큰화(tokenize), 청킹(chunk), 셔플(shuffle), 배치(batch) 처리하는 스트리밍 데이터 파이프라인(streaming data pipeline)을 만듭니다.
  • 실제 사전학습 파이프라인(pre-training pipeline)에서 쓰는 데이터 품질 필터(data quality filters)를 구현합니다. 여기에는 중복 제거(deduplication), 언어 감지(language detection), 콘텐츠 필터링(content filtering)이 포함됩니다.
  • 적절한 어텐션 마스크(attention masks)와 문서 경계(document boundary) 처리를 포함해 고정 길이 학습 시퀀스(fixed-length training sequences)를 만듭니다.
  • 데이터로더(dataloader)가 GPU 학습 속도를 따라갈 수 있도록 파이프라인 처리량(throughput)을 프로파일링합니다.

문제

토크나이저가 생겼습니다. 이제 데이터가 필요합니다.

데이터셋 하나가 아닙니다. CSV 파일도 아닙니다. 테라바이트 단위 텍스트입니다. 정제(cleaned)되고, 중복 제거(deduplicated)되고, 품질 기준으로 필터링(filtered for quality)되고, 고정 길이 시퀀스로 토큰화(tokenized)되고, 8-GPU 클러스터가 다음 배치를 기다리지 않을 만큼 빠르게 무작위 배치(randomized batches)로 제공되어야 합니다.

대부분의 사람은 LLM 학습이 모델 아키텍처(model architecture)의 문제라고 생각합니다. 아닙니다. Llama 3는 15.6조 개 토큰을 사용했습니다. GPT-3는 3,000억 개를 사용했습니다. DeepSeek-V2는 8.1조 개를 사용했습니다. 세 모델의 아키텍처는 대체로 비슷합니다. 어텐션(attention)과 피드포워드(feedforward) 층으로 쌓은 트랜스포머 블록(stacked transformer blocks)입니다. 출력 품질의 차이는 압도적으로 데이터에서 나옵니다.

DeepMind의 Chinchilla 논문은 이를 정밀하게 보여주었습니다. 주어진 연산 예산(compute budget)에서 모델 파라미터(model parameters)와 학습 토큰(training tokens) 사이에는 최적 비율이 있습니다. Chinchilla는 2022년의 많은 모델이 심각하게 과소학습(undertrained)되었다는 것을 보였습니다. 본 데이터 양에 비해 파라미터가 너무 많았다는 뜻입니다. 1.4조 토큰으로 학습한 70B 파라미터 모델(Chinchilla-optimal)이 3,000억 토큰으로 학습한 280B 모델(Gopher)을 능가했습니다.

데이터 파이프라인은 모델이 언어를 배울지, 잡음을 배울지를 결정합니다.

사전 테스트

2문제 · 이 강의를 시작하기 전에 얼마나 알고 있는지 확인해보세요

1.사전학습 데이터(pre-training data)를 모두 메모리(memory)에 올릴 수 없는 이유는 무엇인가요?

2.사전학습에서 데이터 중복 제거(data deduplication)가 중요한 이유는 무엇인가요?

0/2 답변 완료

개념

데이터는 어디서 오는가(Where the Data Comes From)

모든 대형 언어 모델(large language model)은 여러 출처(source)를 섞어 학습합니다. 대부분의 연구소는 정확한 조합(composition)을 철저히 비밀로 지키지만, 범주를 이해할 만큼은 알려져 있습니다.

출처(Source)크기(Size)품질(Quality)사용하는 모델/계열
Common Crawl원시 데이터 약 250 TB낮음(강한 필터링 필요)GPT-3, Llama, 대부분의 오픈 모델
Wikipedia약 20 GB높음주요 LLM 전반
GitHub code1 TB+중간(중복, 죽은 코드 많음)StarCoder, CodeLlama, DeepSeek-Coder
Books(BookCorpus, Pile)약 100 GB높음GPT-2, GPT-3, 초기 모델
학술 논문(arXiv, S2ORC)약 100 GBSTEM 분야에서 높음Llama, Galactica
StackOverflow, Reddit약 100 GB중간Llama, Falcon
정제된 웹(C4, RefinedWeb)약 5 TB중간-높음(사전 필터링됨)T5, Falcon

Llama 3는 데이터 조합을 공개했습니다. 대략 50% 웹 데이터, 25% 코드, 13% 책과 학술 논문, 8% 수학 데이터, 4% 다국어 웹 데이터입니다. 총량은 5 TB가 넘는 원시 텍스트 출처에서 나온 15.6조 토큰이었습니다.

비율은 총량만큼 중요합니다. 웹 데이터가 너무 많으면 모델은 Reddit 앵무새가 됩니다. 코드가 너무 적으면 프로그래밍을 못합니다. 수학이 너무 적으면 추론에 실패합니다. 이 조합을 맞추는 일은 LLM 학습에서 가장 어려운 부분 중 하나이며, 공식은 없습니다. 실험과 평가가 필요합니다.

데이터 정제(Data Cleaning)

원시 웹 데이터는 지저분합니다. 전형적인 Common Crawl 덤프에는 다음이 들어 있습니다.

  • HTML 태그와 JavaScript
  • 보일러플레이트(boilerplate) 헤더, 푸터, 내비게이션 메뉴
  • 중복 페이지(정확한 중복과 근사 중복)
  • 기계 생성 스팸(machine-generated spam)
  • 개인 식별 정보(PII, Personally Identifiable Information)
  • 낮은 품질의 텍스트(키워드 목록, SEO 스팸)
  • 텍스트로 인코딩된 비텍스트 콘텐츠(non-text content)

정제는 선택사항이 아닙니다. 이것이 일관된 문단을 생성하는 모델과 HTML 태그와 상품 목록을 뒤섞어 출력하는 모델을 가릅니다.

graph TD
    A["원시 텍스트(Raw Text)"] --> B["HTML 제거(HTML Strip)"]
    B --> C["언어 감지(Language Detection)"]
    C --> D["품질 필터(Quality Filter)"]
    D --> E["중복 제거(Deduplication)"]
    E --> F["PII 제거(PII Removal)"]
    F --> G["정제된 텍스트(Clean Text)"]

    style A fill:#1a1a2e,stroke:#e94560,color:#fff
    style B fill:#1a1a2e,stroke:#e94560,color:#fff
    style C fill:#1a1a2e,stroke:#e94560,color:#fff
    style D fill:#1a1a2e,stroke:#e94560,color:#fff
    style E fill:#1a1a2e,stroke:#e94560,color:#fff
    style F fill:#1a1a2e,stroke:#e94560,color:#fff
    style G fill:#1a1a2e,stroke:#e94560,color:#fff

각 단계는 잡음의 한 범주를 제거합니다.

HTML 제거(HTML stripping): 모든 마크업(markup)을 제거합니다. 보이는 텍스트 내용만 남깁니다. trafilaturareadability 같은 라이브러리는 내비게이션, 광고, 보일러플레이트를 버리고 기사 본문을 추출합니다.

언어 감지(Language detection): fastText의 언어 식별 모델(lid.176.bin)을 사용해 각 문서를 분류합니다. 목표 언어만 남깁니다. 영어로 분류되었지만 신뢰도(confidence)가 0.8 미만인 문서는 아마 깨끗한 영어가 아닙니다.

품질 필터링(Quality filtering): 여기부터 흥미로워집니다. Falcon 뒤에 있는 데이터셋인 RefinedWeb은 퍼플렉시티 기반 필터(perplexity-based filter)를 사용합니다. 작은 언어 모델을 Wikipedia로 학습한 뒤 각 문서에 점수를 매깁니다. 퍼플렉시티가 높다는 것은 문서가 Wikipedia와 다르다는 뜻이며, 스팸, 키워드 목록, 기계 생성 콘텐츠일 가능성이 큽니다. 임계값(threshold)을 넘는 문서는 제거됩니다.

중복 제거(Deduplication): 가장 영향력 있는 정제 단계입니다. Common Crawl에는 법적 고지, 쿠키 알림, 이용약관 같은 중복 페이지가 엄청나게 많습니다. 중복 위에서 학습하면 연산을 낭비하고 모델이 특정 구절을 그대로 암기하고 되뱉게 만들 수 있습니다.

PII 제거(PII removal): 이름, 이메일 주소, 전화번호, 사회보장번호 같은 정보입니다. 구조화된 PII에는 정규식 기반 감지를, 문맥 속 이름에는 NER(Named Entity Recognition) 모델을 사용할 수 있습니다.

MinHash를 이용한 중복 제거(Deduplication with MinHash)

정확한 중복 제거(exact deduplication)는 쉽습니다. 각 문서를 해시(hash)하고 중복을 제거하면 됩니다. 하지만 진짜 문제는 근사 중복(near-duplicates)입니다. 같은 뉴스 기사에 주변 광고만 조금 다른 두 사본은 근사 중복입니다. 내용은 95% 같지만 바이트 단위로는 다릅니다.

MinHash + 지역 민감 해싱(Locality-Sensitive Hashing, LSH)은 이 문제를 효율적으로 해결합니다.

graph LR
    A["문서(Document)"] --> B["Shingling"]
    B --> C["MinHash 서명(MinHash Signature)"]
    C --> D["LSH 버킷(LSH Buckets)"]
    D --> E["후보 쌍(Candidate Pairs)"]
    E --> F["Jaccard 유사도(Jaccard Similarity)"]
    F --> G["중복 제거된 집합(Deduplicated Set)"]

    style A fill:#1a1a2e,stroke:#e94560,color:#fff
    style B fill:#1a1a2e,stroke:#e94560,color:#fff
    style C fill:#1a1a2e,stroke:#e94560,color:#fff
    style D fill:#1a1a2e,stroke:#e94560,color:#fff
    style E fill:#1a1a2e,stroke:#e94560,color:#fff
    style F fill:#1a1a2e,stroke:#e94560,color:#fff
    style G fill:#1a1a2e,stroke:#e94560,color:#fff

아이디어는 다음과 같습니다.

  1. Shingling: 각 문서를 n-그램(n-gram) 집합으로 바꿉니다. 예를 들어 단어 5-그램 또는 문자 5-그램을 사용할 수 있습니다. "the quick brown fox"를 3단어 shingle로 만들면 {"the quick brown", "quick brown fox"}가 됩니다.

  2. MinHash: 각 문서의 shingle 집합에 대해 k개의 해시 값을 계산합니다. 각 해시 값은 서로 다른 해시 함수 아래에서 모든 shingle의 해시 중 최솟값입니다. 이렇게 고정 크기 "서명(signature)"을 만들면 두 문서 사이의 Jaccard 유사도(Jaccard similarity)를 근사할 수 있습니다.

  3. LSH: MinHash 서명의 band를 기준으로 문서를 버킷(bucket)에 묶습니다. 같은 버킷에 있는 문서는 근사 중복 후보(candidate near-duplicates)입니다. 이렇게 하면 모든 쌍을 비교하지 않아도 됩니다. 후보만 비교합니다.

  4. 검증(Verify): 각 후보 쌍에 대해 정확한 Jaccard 유사도를 계산합니다. 유사도가 임계값을 넘으면 보통 0.8을 기준으로 한 사본을 제거합니다.

Llama 팀은 중복 제거를 통해 웹 데이터의 약 38%를 제거했다고 보고했습니다. 작은 숫자가 아닙니다. Common Crawl의 3분의 1 이상은 중복 또는 근사 중복 콘텐츠입니다.

시퀀스 패킹(Sequence Packing)

모델은 고정 길이 입력 시퀀스(fixed-length input sequences)를 기대합니다. 문서는 가변 길이(variable length)입니다. 어떤 문서는 50토큰입니다. 어떤 문서는 50,000토큰입니다.

순진한 접근은 모든 문서를 최대 시퀀스 길이까지 패딩(padding)하는 것입니다. 이 방식은 학습에 아무 기여도 하지 않는 패딩 토큰 위에서 엄청난 연산을 낭비합니다.

더 나은 접근은 여러 문서를 하나의 시퀀스 안에 패킹하고, 문서 사이를 시퀀스 끝 토큰(end-of-sequence tokens)으로 구분하는 것입니다. 2048토큰 시퀀스 하나가 [EOS] 토큰을 사이에 둔 짧은 문서 세 개를 담을 수 있습니다.

graph TD
    subgraph Naive Packing
        A1["Doc A (200 tokens)"] --> P1["[PAD] x 1848"]
        A2["Doc B (500 tokens)"] --> P2["[PAD] x 1548"]
        A3["Doc C (100 tokens)"] --> P3["[PAD] x 1948"]
    end

    subgraph Efficient Packing
        B1["Doc A (200) | Doc B (500) | Doc C (100) | Doc D (400) | Doc E (848)"]
    end

    style A1 fill:#1a1a2e,stroke:#e94560,color:#fff
    style A2 fill:#1a1a2e,stroke:#e94560,color:#fff
    style A3 fill:#1a1a2e,stroke:#e94560,color:#fff
    style P1 fill:#333,stroke:#666,color:#999
    style P2 fill:#333,stroke:#666,color:#999
    style P3 fill:#333,stroke:#666,color:#999
    style B1 fill:#1a1a2e,stroke:#16c784,color:#fff

어텐션 마스크(attention mask)를 올바르게 설정해야 합니다. 같은 패킹 시퀀스 안에 있더라도 문서 A(Document A)의 토큰이 문서 B(Document B)의 토큰을 참조(attend)하면 안 됩니다. 이를 위해 블록 대각 어텐션 마스크(block-diagonal attention mask)가 필요합니다.

긴 문서는 잘리거나(truncated) 시퀀스 경계에서 청크로 나뉩니다. 분할 지점도 중요합니다. 문장 중간에서 자르면 모델은 불완전한 생각을 보게 됩니다. 일부 파이프라인은 가능하면 문단 또는 문장 경계에 맞춰 분할합니다.

Chinchilla 스케일링 법칙(The Chinchilla Scaling Law)

고정된 연산 예산 C(FLOPs로 측정)에 대해 최적 모델 크기 N과 데이터셋 크기 D는 다음을 따릅니다.

N_opt ~ C^0.5
D_opt ~ C^0.5

실제로는 모델 크기와 데이터셋 크기를 대략 함께 키워야 한다는 뜻입니다. 파라미터가 10배 많은 모델은 같은 손실(loss) 수준에 도달하려면 대략 10배 많은 학습 토큰이 필요합니다.

모델파라미터학습 토큰Chinchilla 최적 여부
GPT-3175B300B아니요(3-4배 과소학습)
Chinchilla70B1.4T예(설계상)
Llama 270B2T과학습(overtrained, 의도적)
Llama 370B15T강하게 과학습

Llama 3는 의도적으로 Chinchilla 법칙을 어깁니다. Meta는 연산 최적 비율을 훨씬 넘는 더 많은 데이터로 과학습하면 추론(inference)에 더 좋은 모델이 된다는 것을 발견했습니다. 추가 학습 비용은 한 번만 지불하지만, 더 작은 모델은 영원히 더 싸게 서빙할 수 있습니다. 이를 때로는 "추론 최적(inference-optimal)" 스케일링 접근이라고 부르며, 2024년 이후 업계 표준이 되었습니다.

직접 만들기

Step 1: 텍스트 정제(Text Cleaning)

HTML을 제거하고, 공백을 정규화하고, 비텍스트 콘텐츠를 제거합니다. 작은 말뭉치로는 퍼블릭 도메인 텍스트(Project Gutenberg)를 사용합니다.

import re

def clean_text(text):
    text = re.sub(r"<[^>]+>", "", text)
    text = re.sub(r"http\S+", "", text)
    text = re.sub(r"[^\x20-\x7E\n]", "", text)
    text = re.sub(r"\n{3,}", "\n\n", text)
    text = re.sub(r" {2,}", " ", text)
    return text.strip()

def quality_filter(text, min_words=50, max_ratio_caps=0.3, max_ratio_special=0.1):
    words = text.split()
    if len(words) < min_words:
        return False
    caps_ratio = sum(1 for w in words if w.isupper()) / len(words)
    if caps_ratio > max_ratio_caps:
        return False
    special_chars = sum(1 for c in text if not c.isalnum() and not c.isspace())
    if special_chars / max(len(text), 1) > max_ratio_special:
        return False
    return True

이 품질 필터는 SEO 스팸(ALL CAPS), 기계 생성 잡음(높은 특수문자 비율), 빈약한 토막 문서(stub 페이지, 너무 짧은 문서)를 잡아냅니다. 이 세 가지 검사만으로도 웹 크롤에서 놀랄 만큼 많은 쓰레기 데이터를 제거할 수 있습니다.

Step 2: MinHash 중복 제거(MinHash Deduplication)

MinHash를 직접 구현합니다. 외부 라이브러리는 필요 없습니다. hashlib만 사용합니다.

import hashlib
from collections import defaultdict

def get_shingles(text, k=5):
    words = text.lower().split()
    if len(words) < k:
        return set()
    return {" ".join(words[i:i+k]) for i in range(len(words) - k + 1)}

def minhash_signature(shingles, num_hashes=128):
    signature = []
    for i in range(num_hashes):
        min_hash = float("inf")
        for shingle in shingles:
            h = int(hashlib.sha256(f"{i}:{shingle}".encode()).hexdigest(), 16)
            min_hash = min(min_hash, h)
        signature.append(min_hash)
    return signature

def lsh_buckets(signature, bands=16):
    rows_per_band = len(signature) // bands
    buckets = []
    for b in range(bands):
        start = b * rows_per_band
        band_data = tuple(signature[start:start + rows_per_band])
        bucket_hash = hashlib.md5(str(band_data).encode()).hexdigest()
        buckets.append((b, bucket_hash))
    return buckets

def deduplicate(documents, threshold=0.8, num_hashes=128, bands=16):
    signatures = []
    shingle_sets = []
    for doc in documents:
        shingles = get_shingles(doc)
        shingle_sets.append(shingles)
        signatures.append(minhash_signature(shingles, num_hashes))

    bucket_map = defaultdict(list)
    for doc_idx, sig in enumerate(signatures):
        for band_id, bucket_hash in lsh_buckets(sig, bands):
            bucket_map[(band_id, bucket_hash)].append(doc_idx)

    duplicate_pairs = set()
    for bucket_docs in bucket_map.values():
        if len(bucket_docs) < 2:
            continue
        for i in range(len(bucket_docs)):
            for j in range(i + 1, len(bucket_docs)):
                duplicate_pairs.add((bucket_docs[i], bucket_docs[j]))

    removed = set()
    for i, j in duplicate_pairs:
        if i in removed or j in removed:
            continue
        s1, s2 = shingle_sets[i], shingle_sets[j]
        if not s1 or not s2:
            continue
        jaccard = len(s1 & s2) / len(s1 | s2)
        if jaccard >= threshold:
            removed.add(j)

    return [doc for idx, doc in enumerate(documents) if idx not in removed], len(removed)

num_hashes=128bands=16 파라미터는 정밀도-재현율 트레이드오프(precision-recall tradeoff)를 제어합니다. 해시가 많을수록 유사도 추정이 더 정확합니다. band가 많을수록 재현율이 높아져 더 많은 중복을 잡지만, 거짓 양성(false positives)도 늘어납니다. 이 값들은 일반적인 웹 텍스트에 잘 맞습니다.

Step 3: 토큰화와 시퀀스 패킹(Tokenize and Pack Sequences)

정제되고 중복 제거된 텍스트를 토큰화하고, 학습에 사용할 고정 길이 시퀀스로 패킹합니다.

def tokenize_corpus(documents, tokenizer):
    all_tokens = []
    for doc in documents:
        tokens = tokenizer.encode(doc)
        all_tokens.extend(tokens)
        all_tokens.append(tokenizer.eos_id)
    return all_tokens

def pack_sequences(token_ids, seq_length, pad_id=0):
    sequences = []
    attention_masks = []
    for i in range(0, len(token_ids), seq_length):
        seq = token_ids[i:i + seq_length]
        mask = [1] * len(seq)
        if len(seq) < seq_length:
            pad_count = seq_length - len(seq)
            seq = seq + [pad_id] * pad_count
            mask = mask + [0] * pad_count
        sequences.append(seq)
        attention_masks.append(mask)
    return sequences, attention_masks

Step 4: 학습용 DataLoader(DataLoader for Training)

패킹된 시퀀스의 무작위 배치를 생성합니다. 학습 루프(training loop)는 이것을 소비합니다.

import random

class PreTrainingDataLoader:
    def __init__(self, sequences, attention_masks, batch_size, shuffle=True):
        self.sequences = sequences
        self.attention_masks = attention_masks
        self.batch_size = batch_size
        self.shuffle = shuffle

    def __len__(self):
        return (len(self.sequences) + self.batch_size - 1) // self.batch_size

    def __iter__(self):
        indices = list(range(len(self.sequences)))
        if self.shuffle:
            random.shuffle(indices)
        for start in range(0, len(indices), self.batch_size):
            batch_idx = indices[start:start + self.batch_size]
            batch_seqs = [self.sequences[i] for i in batch_idx]
            batch_masks = [self.attention_masks[i] for i in batch_idx]
            yield batch_seqs, batch_masks

Step 5: 데이터셋 통계(Dataset Statistics)

중요한 숫자를 계산합니다. 총 토큰 수(total tokens), 고유 토큰 수(unique tokens), 압축 비율(compression ratio), 문서 길이 분포(document length distribution)입니다.

from collections import Counter

def compute_statistics(documents, token_ids, sequences, tokenizer_vocab_size):
    total_chars = sum(len(d) for d in documents)
    total_tokens = len(token_ids)
    unique_tokens = len(set(token_ids))
    compression_ratio = total_chars / total_tokens

    doc_lengths = [len(d.split()) for d in documents]
    avg_doc_length = sum(doc_lengths) / max(len(doc_lengths), 1)
    max_doc_length = max(doc_lengths) if doc_lengths else 0
    min_doc_length = min(doc_lengths) if doc_lengths else 0

    token_counts = Counter(token_ids)
    top_tokens = token_counts.most_common(10)

    non_pad_tokens = sum(sum(1 for t in seq if t != 0) for seq in sequences)
    total_positions = sum(len(seq) for seq in sequences)
    utilization = non_pad_tokens / max(total_positions, 1)

    stats = {
        "total_documents": len(documents),
        "total_characters": total_chars,
        "total_tokens": total_tokens,
        "unique_tokens": unique_tokens,
        "vocab_utilization": unique_tokens / tokenizer_vocab_size,
        "compression_ratio": compression_ratio,
        "avg_doc_length_words": avg_doc_length,
        "max_doc_length_words": max_doc_length,
        "min_doc_length_words": min_doc_length,
        "num_sequences": len(sequences),
        "sequence_utilization": utilization,
        "top_10_tokens": top_tokens,
    }
    return stats

압축 비율은 이 말뭉치에서 토크나이저가 얼마나 효율적인지 알려줍니다. 영어 텍스트는 보통 토큰당 약 3-4문자로 압축됩니다. 토큰당 1.5문자가 나오면 토크나이저가 너무 잘게 쪼개고 있다는 뜻입니다. 8 이상이 나오면 매우 도메인 특화적인 병합을 학습한 것입니다.

시퀀스 활용률(sequence utilization)은 패킹된 시퀀스 중 실제 데이터와 패딩의 비율을 알려줍니다. 90% 미만이면 패킹이 비효율적입니다. 패딩 토큰 위에서 연산을 낭비하고 있다는 뜻입니다.

사용해보기

HuggingFace Datasets와 비교하기(Compare With HuggingFace Datasets)

같은 말뭉치를 HuggingFace의 datasets 라이브러리로 불러오고 파이프라인 속도를 비교합니다.

from datasets import load_dataset
from transformers import AutoTokenizer

ds = load_dataset("wikitext", "wikitext-2-raw-v1", split="train")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")

import time

start = time.time()
tokenized = ds.map(
    lambda x: tokenizer(x["text"], truncation=True, max_length=2048),
    batched=True,
    num_proc=4,
)
hf_time = time.time() - start
total_tokens = sum(len(t) for t in tokenized["input_ids"])
print(f"HuggingFace: {total_tokens:,} tokens in {hf_time:.2f}s ({total_tokens/hf_time:,.0f} tokens/sec)")

HuggingFace 파이프라인은 내부적으로 Rust 토크나이저와 4개 코어에 걸친 병렬 처리를 사용합니다. 순수 Python 파이프라인은 10-50배 느릴 것입니다. 그 차이가 프로덕션 팀이 컴파일된 토크나이저를 쓰는 이유입니다. 알고리즘은 같습니다. 구현 언어가 차이를 만듭니다.

산출물 만들기

이 lesson은 LLM 학습 데이터 파이프라인의 데이터 품질을 검증하고 디버깅하기 위한 프롬프트를 산출합니다. outputs/prompt-data-quality-checker.md를 확인합니다.

연습문제

  1. 쉬움: 단순한 휴리스틱(문자 집합 분석)을 사용해 정제 파이프라인에 언어 감지(language detection)를 추가합니다. 영어 문서만 남기고 몇 개의 문서가 제거되는지 측정합니다.
  2. 중간: MinHash 근사 중복 제거와 함께 SHA-256 해시를 사용하는 정확한 중복 제거(exact deduplication)를 구현합니다. 웹 스크래핑 말뭉치에서 각 방법이 잡아낸 중복 수를 비교합니다.
  3. 어려움: 퍼플렉시티 기반 품질 필터(perplexity-based quality filter)를 만듭니다. Wikipedia 텍스트로 작은 바이그램 언어 모델(bigram language model)을 학습하고, 각 문서의 퍼플렉시티를 계산한 뒤 하위 20%를 제거합니다. 필터링된 데이터와 필터링되지 않은 데이터로 학습했을 때 모델 출력 품질을 비교합니다.

핵심 용어

용어흔한 설명실제 의미
Common Crawl"인터넷"매달 웹을 크롤링하는 비영리 프로젝트입니다. 원시 데이터 약 250TB이며 대부분의 LLM 학습 데이터의 출발점입니다.
MinHash"해싱 트릭"고정 크기 서명(fixed-size signatures)으로 집합 간 Jaccard 유사도를 추정하는 기법입니다. 대규모 근사 중복 탐지를 가능하게 합니다.
LSH(Locality-Sensitive Hashing)"지역 민감 해싱"비슷한 항목을 같은 버킷에 묶는 방법입니다. 쌍별 비교(pairwise comparisons)를 O(n^2)에서 거의 선형(near-linear)으로 줄입니다.
시퀀스 패킹(Sequence packing)"문서 이어 붙이기"여러 문서를 적절한 어텐션 마스크와 함께 고정 길이 시퀀스에 맞추는 작업입니다. 패딩 낭비를 제거합니다.
Chinchilla 스케일링(Chinchilla scaling)"더 많은 데이터로 학습"고정 연산 예산에서 최적 성능을 내려면 모델 크기와 학습 토큰을 대략 함께 키워야 한다는 법칙입니다.
토큰 비옥도(Fertility)"단어당 토큰 수"단어당 평균 토큰 수입니다. GPT-4의 영어는 약 1.3이고 비라틴 문자 체계에서는 더 높습니다.
데이터 믹싱(Data mixing)"학습 데이터 고르기"코드, 텍스트, 수학, 다국어 데이터의 비율입니다. 공식은 없고 실험이 필요합니다.
퍼플렉시티 필터(Perplexity filter)"품질 점수화"작은 언어 모델로 문서에 점수를 매기는 방식입니다. 퍼플렉시티가 높으면 깨끗한 기준 데이터와 다른 텍스트라는 뜻입니다.
중복 제거(Deduplication)"복사본 제거"정확한 중복과 근사 중복 문서를 제거합니다. 일반적으로 원시 웹 데이터의 30-40%를 제거합니다.
어텐션 마스크(Attention mask)"어떤 토큰을 볼지"패킹된 시퀀스에서 문서 경계를 넘는 어텐션을 막는 이진 마스크(binary mask)입니다.

더 읽을거리

실습 코드

이 강의의 실습 코드 1개

main
Code

산출물

이 강의에서 생성된 프롬프트, 스킬, 코드 산출물 1개

prompt-data-quality-checker

Validate and debug data quality in LLM pre-training pipelines

Prompt

확인 문제

3문제 · 모두 맞추면 완료 표시가 가능합니다

1.가변 길이 문서(variable-length document)에서 고정 길이 학습 시퀀스(fixed-length training sequence)를 만드는 목적은 무엇인가요?

2.데이터 파이프라인(data pipeline)이 GPU 학습 속도보다 느리면 어떤 일이 생기나요?

3.언어 감지(language detection), 콘텐츠 필터링(content filtering) 같은 데이터 품질 필터링(data quality filtering)을 토큰화 전에 적용하는 이유는 무엇인가요?

0/3 답변 완료

추가 문제 풀기

AI가 강의 내용을 바탕으로 새로운 문제를 생성합니다