[LLM Pipeline 2] Sliding Window와 Stride 기반 데이터로더(DataLoader) 파이프라인

2026. 8. 28. 13:13·Programming/Dev notes
반응형

참조 사이트

  • SKALA 4기 - LLM과 Transformer 아키텍처 (SK AX SKALA)
  • Attention Is All You Need (Vaswani et al., 2017)
  • PyTorch Documentation - Datasets & DataLoaders

개요

 

지난 [LLM Pipeline 1]에서는 텍스트를 최적의 부단어(Subword) 단위로 분절하고 정수 번호로 매핑하는 토크나이저(Tokenizer)와 BPE 알고리즘을 살펴보았다.

토크나이저를 거친 최종 출력물은 [Batch, Time] (이하 [B, T]) 형태의 정수형 토큰 ID 배열(Token ID Matrix)이다. 하지만 신경망은 단순한 정수 번호를 직접 사칙연산하거나 의미적 좌표로 해석할 수 없다. 정수 ID 자체는 단어의 내재적 의미나 단어 간 유사도를 전혀 담지 못하기 때문이다. 또한 모든 단어를 한 번에 병렬 처리하는 트랜스포머의 셀프 어텐션(Self-Attention) 구조는 토큰들의 등장 어순(순서 정보)을 스스로 분별할 수 없다.

이번 글에서는 거대한 텍스트 스트림을 학습용 입력(x)과 정답 타겟(y)으로 가공하는 슬라이딩 윈도우(Sliding Window) 및 스트라이드(Stride)의 추출 메커니즘과, 이를 효율적으로 처리하는 파이토치 데이터로더(PyTorch DataLoader) 파이프라인을 다룬다.

 

💡 용어 사전: 데이터로더(DataLoader) 대규모 원본 데이터를 모델이 학습하기 가장 적합한 단위(Batch)로 분할하고, 데이터 순서를 섞는 셔플(Shuffle) 및 멀티프로세싱 기반의 병렬 로딩을 수행하여 GPU 메모리에 데이터를 안정적으로 공급하는 파이프라인 관리 모듈이다.


1. LLM 학습 데이터의 본질: "x와 y의 1칸 시프트(Shift)"

① 자기지도학습(Self-Supervised Learning)의 메커니즘

LLM의 사전학습(Pre-training)은 사람이 일일이 정답 라벨을 달아줄 필요가 없는 자기지도학습으로 진행된다. 텍스트 데이터의 연속성 자체에서 입력과 정답을 스스로 도출하기 때문이다.

  • 입력(x): 모델에게 주어지는 앞선 문맥(Context) 토큰 시퀀스
  • 타겟(y): 각 입력 위치에서 모델이 바로 다음에 올 것으로 예측해야 하는, 오른쪽으로 1칸 밀린(Shifted by 1) 정답 토큰 시퀀스

💡 용어 사전: 자기지도학습(Self-Supervised Learning) 별도의 사람이 라벨링한 데이터 없이, 입력 데이터 자체에서 레이블을 스스로 생성해 학습하는 방법론이다. 언어 모델에서는 문장 내 앞부분 단어들을 바탕으로 바로 다음 단어를 맞히는 문제(Next-Token Prediction)를 스스로 출제하고 푸는 방식으로 동작한다.

[입력 시퀀스 x] : [ 토큰_1, 토큰_2, 토큰_3, ..., 토큰_T ]
[정답 시퀀스 y] : [ 토큰_2, 토큰_3, 토큰_4, ..., 토큰_(T+1) ]

 

② 단 한 번의 연산으로 T개의 문제 풀기

입력 시퀀스의 길이가 T = 4 일 때, 모델은 내부의 인과적 마스크(Causal Mask)를 활용하여 다음과 같이 4개의 학습 문제를 단 한 번의 전방향 연산(Forward Pass)으로 병렬 계산한다.

  • 원본 단어 스트림: ["The", "quick", "brown", "fox", "jumps"]
  • 입력 x: ["The", "quick", "brown", "fox"]
  • 타겟 y: ["quick", "brown", "fox", "jumps"]

이때 모델은 각 단계별로 독립된 예측 손실을 계산한다:

  1. ["The"]를 보고 ➔ ["quick"] 예측
  2. ["The", "quick"]을 보고 ➔ ["brown"] 예측
  3. ["The", "quick", "brown"]을 보고 ➔ ["fox"] 예측
  4. ["The", "quick", "brown", "fox"]를 보고 ➔ ["jumps"] 예측

이러한 구조 덕분에 시퀀스 길이 T 만큼의 지도학습 샘플을 개별적으로 만드는 대신, 하나의 시퀀스 텐서 안에서 병렬로 효율적인 오차 계산과 가중치 갱신이 가능해진다.


💡 [심화 분석] 인과적 마스크(Causal Mask)가 미래 정보를 차단하는 원리

모델이 T개의 예측 문제를 한 번에 병렬로 풀 수 있는 핵심 이유는 셀프 어텐션 내부의 인과적 마스크(Causal Mask / Look-ahead Mask)가 미래 단어를 훔쳐보는 행위(Information Leakage)를 수학적으로 완벽히 차단하기 때문이다.

어텐션 가중치를 계산할 때, 현재 위치보다 뒤에 있는 미래 위치의 점수를 -∞(음의 무한대)로 치환한다. 이후 Softmax를 취하면 미래 토큰에 대한 주목 확률이 정확히 0이 되므로, 모델은 오직 과거와 현재의 토큰들만 참조하여 다음 단어를 추론하게 된다.


2. 슬라이딩 윈도우(Sliding Window)와 스트라이드(Stride)

수억 개 이상의 연속적인 1차원 토큰 배열을 고정된 컨텍스트 크기(T)로 효율적으로 나누기 위해 슬라이딩 윈도우 기법을 적용한다.

① 핵심 파라미터 정의

💡 용어 사전: 컨텍스트 윈도우(Context Window / Max Length, T) 모델이 한 번에 입력받아 연산할 수 있는 최대 토큰 길이를 의미하며, 슬라이딩 윈도우의 가로폭(Width)과 동일하다.

💡 용어 사전: 스트라이드(Stride, S) 하나의 데이터 샘플을 추출한 뒤, 다음 샘플을 연속적으로 잘라내기 위해 윈도우를 오른쪽으로 몇 칸 이동시킬지 결정하는 이동 보폭이다.

② 스트라이드 설정에 따른 2가지 데이터 가공 전략

  • 무중첩 방식 (Stride = Context Size):
    • 데이터 간 겹침이 전혀 없기 때문에, 전체 말뭉치 토큰 수만큼만 정확히 1회 연산한다.
    • 데이터가 방대한 대규모 사전학습(Pre-training) 단계에서 학습 데이터 중복 처리 속도를 최적화하고 GPU 연산 효율을 극대화하기 위해 표준으로 채택된다.
  • 중첩 방식 (Stride < Context Size):
    • 윈도우가 일정 부분 겹치며 이동하기 때문에 동일한 텍스트로부터 시작점과 컨텍스트 배치가 다채롭게 변형된 대량의 데이터 샘플을 증강(Augmentation)할 수 있다.
    • 가용할 수 있는 텍스트 양이 제한적인 파인튜닝(Fine-tuning)이나 특정 도메인 전용 미세 조정 과정에서 샘플 다양성을 증폭시키고자 할 때 유용하다.

3. 윈도우 스트라이드 방식 핵심 비교

비교 항목 무중첩 스트라이드 (Stride = Context Size) 중첩 슬라이딩 (Stride < Context Size)
데이터 샘플 수 ≈ (전체 토큰 수 / Context Size) 개 ≈ (전체 토큰 수 - Context Size) / Stride 개 (대폭 증가)
데이터 중복도 없음 (0% 중복) 발생 (Stride가 작을수록 중복 비율 상승)
GPU 학습 연산량 최소화 (최적화된 학습 속도) 증가 (데이터 증강 비율에 비례)
문맥 다양성 고정된 경계면 분절로 단조로움 다양한 문맥적 변주 학습 가능
주요 활용 단계 거대 말뭉치 사전학습 (Pre-training) 특정 도메인 파인튜닝 (SFT) / 소규모 학습

4. PyTorch 커스텀 Dataset & DataLoader 구현

앞서 다룬 슬라이딩 윈도우 파이프라인을 파이토치(PyTorch) 환경에서 구현하는 표준 코드는 다음과 같다.

① PyTorch Custom Dataset 클래스 구현

Dataset 클래스는 1차원 토큰 배열을 입력받아 지정된 Context Size와 Stride 규칙에 따라 입력(x)과 정답(y) 텐서 쌍을 분할 반환하도록 정밀하게 설계된다.

import torch
from torch.utils.data import Dataset, DataLoader

class SlidingWindowDataset(Dataset):
    def __init__(self, token_ids, context_size, stride):
        """
        Args:
            token_ids (list): 전체 텍스트를 토크나이징하여 얻은 전체 정수 토큰 ID 배열
            context_size (int): 모델의 맥락 길이 (T)
            stride (int): 윈도우 이동 보폭 (S)
        """
        self.token_ids = torch.tensor(token_ids, dtype=torch.long)
        self.context_size = context_size
        self.stride = stride
        self.samples = []
        
        # 슬라이딩 윈도우 인덱싱을 통한 데이터 사전 가공
        self._build_samples()
        
    def _build_samples(self):
        # 입력(x)과 정답(y)을 추출할 수 있는 마지막 인덱스 계산
        limit = len(self.token_ids) - self.context_size
        for start_idx in range(0, limit + 1, self.stride):
            end_idx = start_idx + self.context_size
            
            # x: 현재 컨텍스트 길이만큼의 토큰들
            x = self.token_ids[start_idx : end_idx]
            # y: x에서 정확히 오른쪽으로 1칸 시프트된 타겟 토큰들
            y = self.token_ids[start_idx + 1 : end_idx + 1]
            
            self.samples.append((x, y))
            
    def __len__(self):
        return len(self.samples)
    
    def __getitem__(self, idx):
        return self.samples[idx]

② PyTorch 코드로 확인하는 배치 텐서 차원 검증

정의한 Dataset과 PyTorch DataLoader를 사용해 실제 배치 데이터가 어떻게 생성되는지 차원을 검증해 보자.

# 가상 데이터 생성 (1부터 20까지의 토큰 ID가 나열된 임의의 시퀀스)
dummy_tokens = list(range(1, 21))

# 컨텍스트 크기 T=4, 이동 보폭 S=2로 설정하여 데이터셋 초기화 (중첩 방식)
context_size = 4
stride = 2
dataset = SlidingWindowDataset(dummy_tokens, context_size=context_size, stride=stride)

# 배치 크기 B=2로 데이터로더 생성 (안정적인 텐서 크기 유지를 위해 drop_last=True, 전송 가속을 위해 pin_memory=True 적용)
batch_size = 2
dataloader = DataLoader(
    dataset, 
    batch_size=batch_size, 
    shuffle=False, 
    drop_last=True, 
    pin_memory=True
)

# 첫 번째 에포크의 배치 데이터 가시성 검증
for batch_idx, (batch_x, batch_y) in enumerate(dataloader):
    print(f"=== Batch {batch_idx + 1} ===")
    print(f"Input x Shape : {batch_x.shape}")
    print(f"Target y Shape: {batch_y.shape}")
    print(f"Input x Tensor:\n{batch_x}")
    print(f"Target y Tensor:\n{batch_y}\n")
    if batch_idx == 1:
        break

실행 결과 출력

=== Batch 1 ===
Input x Shape : torch.Size([2, 4])
Target y Shape: torch.Size([2, 4])
Input x Tensor:
tensor([[1, 2, 3, 4],
        [3, 4, 5, 6]])
Target y Tensor:
tensor([[2, 3, 4, 5],
        [4, 5, 6, 7]])

=== Batch 2 ===
Input x Shape : torch.Size([2, 4])
Target y Shape: torch.Size([2, 4])
Input x Tensor:
tensor([[ 5,  6,  7,  8],
        [ 7,  8,  9, 10]])
Target y Tensor:
tensor([[ 6,  7,  8,  9],
        [ 8,  9, 10, 11]])

출력 로그에서 볼 수 있듯이, 윈도우가 Stride 단위인 2만큼씩 매끄럽게 어긋나고, 각 인스턴스별로 타겟 y가 입력 x로부터 정확하게 오른쪽으로 한 칸 시프트되어 생성되었음이 검증되었다.


💡 [실무 엔지니어링 팁] 안정적인 학습을 위한 DataLoader 필수 옵션

대규모 LLM 학습 시 GPU의 연산 속도에 맞춰 CPU가 데이터를 지연 없이 공급하고, 연산 그래프의 안정성을 보장하기 위해 다음 두 옵션을 필수적으로 적용한다.

  • drop_last=True: 전체 데이터 샘플 개수가 배치 크기(Batch Size)로 나누어떨어지지 않아 남는 마지막 자투리 배치를 학습에서 제외하는 옵션이다. 학습 도중 배치 텐서의 첫 번째 차원인 B가 불규칙하게 변하면 정적 연산 그래프 최적화가 깨지거나 분산 학습(DDP) 통신 동기화 오류가 발생하는 현상을 방지한다.
  • pin_memory=True: CPU 메모리상에 적재된 텐서 데이터를 GPU로 보낼 때, 가상 메모리 스왑이 일어나지 않는 고정 물리 영역(Pinned Memory / Non-pageable)에 우선 할당하는 설정이다. 이를 통해 호스트(CPU)에서 디바이스(GPU)로의 DMA 고속 복사가 활성화되어 데이터 전송 병목을 최소화한다.

5. 데이터 파이프라인 텐서 차원(Tensor Shape) 변화 요약

자연어 말뭉치가 데이터 가공 파이프라인과 딥러닝 텐서 흐름을 경유해 최종 모델에 적용되기까지의 핵심 차원 변화는 다음과 같다.

  1. 토크나이징 직후: 임의의 대형 말뭉치가 길이 N의 1차원 정수 시퀀스로 연결된 단일 선형 어레이다.
  2. Dataset 슬라이싱 직후: 설정한 맥락 윈도우 폭 T에 맞추어 __getitem__이 가동되며 1차원 정수 텐서 x: [T]와 이로부터 1칸 밀린 y: [T]의 쌍으로 분할된다.
  3. DataLoader 배치화 직후: 데이터로더의 Collate 로직이 작동하여 B개의 독립적인 토큰 행들을 하나로 병합함으로써 학습 단위에 적합한 2차원 정수형 텐서 x: [B, T] 및 y: [B, T]를 축조해 낸다.
  4. Embedding Layer 통과 직후: 비로소 W_E 룩업 행렬과 매핑 연산이 일어나면서 단어 차원 D가 차원 확장을 이루어 최종적인 [B, T, D]의 3차원 입력 밀집 텐서로 변환된다.

요약

  1. 시프트 데이터 구조: LLM 사전학습 데이터셋은 원본의 연속적인 단어 배열에서 1칸씩 시프트된 입력 x와 타겟 y 데이터쌍을 추출해, 인과적 마스킹 구조 안에서 T개의 어순 예측 문제를 단번에 해결하도록 유도한다.
  2. 슬라이딩 윈도우와 이동 보폭: 대량의 정수 시퀀스를 일정한 크기로 자르는 핵심 변수이며, 데이터 중복성 및 다양성의 엔지니어링 트레이드오프에 따라 무중첩 방식(Pre-training 표준)과 중첩 방식(Fine-tuning 가속화)으로 분류하여 적용한다.
  3. 학습용 배치 완성: 데이터로더 파이프라인을 통과하며 정비된 2차원 텐서 [B, T]는 모델 입력단에서 [B, T, D] 텐서로 확장되어 모델 내부로 투입될 자격을 갖추게 된다.

다음 [LLM Pipeline 3]에서는 이렇게 데이터로더를 통해 준비된 [B, T] 정수 배치 텐서가 모델 최입력단에서 고차원 의미 공간의 실수 좌표로 변환되고 어순 감각을 부여받는 토큰 임베딩(Token Embedding)과 위치 인코딩(Positional Encoding & RoPE)의 핵심 원리를  알아보겠다.

 


틀린 부분이 있거나 인용한 부분에 대해 문제가 있을 시
댓글로 알려주시면 감사하겠습니다.

 

반응형

'Programming > Dev notes' 카테고리의 다른 글

[LLM Pipeline 4] 트랜스포머: Q·K·V 수식 분해와 4대 어텐션(Attention) 메커니즘  (0) 2026.08.31
[LLM Pipeline 3] 토큰 임베딩(Token Embedding)과 위치 인코딩(Positional Encoding & RoPE)  (0) 2026.08.28
[LLM Pipeline 1] 토크나이저(Tokenizer)와 BPE 알고리즘  (0) 2026.08.27
[LLM Pipeline 0] LLM 전체 아키텍처와 5단계 데이터 파이프라인  (0) 2026.08.27
[Dev notes] Spring Boot 실행 방식 비교 (bootRun, bootJar, java -jar, Docker)  (0) 2026.08.03
'Programming/Dev notes' 카테고리의 다른 글
  • [LLM Pipeline 4] 트랜스포머: Q·K·V 수식 분해와 4대 어텐션(Attention) 메커니즘
  • [LLM Pipeline 3] 토큰 임베딩(Token Embedding)과 위치 인코딩(Positional Encoding & RoPE)
  • [LLM Pipeline 1] 토크나이저(Tokenizer)와 BPE 알고리즘
  • [LLM Pipeline 0] LLM 전체 아키텍처와 5단계 데이터 파이프라인
wwwjong
wwwjong
wwwjong 님의 블로그 입니다.
  • wwwjong
    wwwjong 님의 블로그
    wwwjong
  • 전체
    오늘
    어제
    • 분류 전체보기 (41)
      • Programming (11)
        • 트러블슈팅 (3)
        • Dev notes (8)
      • CS (2)
        • Infra (2)
      • 개발환경 (6)
        • Ubuntu (6)
      • Algorithms (15)
        • 백준 (8)
        • Algorithm (3)
        • 코드트리 (3)
        • Programmers (1)
      • Study (6)
        • 면접을 위한 CS 전공지식 노트 (6)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    딥러닝
    skala
    LLM
    ubuntu server
    면접을 위한 CS 전공지식 노트
    코드트리
    computer science
    CS
    Server
    코딩테스트
    코테공부
    ubuntu
    Jenkins
    AI
    docker
    docker compose
    트랜스포머
    백준
    transformer
    Nginx
  • 최근 댓글

  • 최근 글

  • 반응형
  • hELLO· Designed By정상우.v4.10.1
wwwjong
[LLM Pipeline 2] Sliding Window와 Stride 기반 데이터로더(DataLoader) 파이프라인
상단으로

티스토리툴바