ai technology

Transformer의 가변 길이 시퀀스를 효율적으로 배치하는 방법

Junyoung Park · 2026-08-04 · 11 min

자연어, 음성, 비디오처럼 현실의 데이터는 길이가 제각각이다. 그러나 GPU가 가장 잘 처리하는 것은 같은 Shape을 가진 Dense Tensor의 Batch다. 결국 Transformer를 학습하려면 서로 다른 길이의 시퀀스를 어떤 방식으로 하나의 Batch에 넣을지 결정해야 한다.

가장 단순한 해법은 짧은 시퀀스 뒤에 Padding Token을 붙이는 것이다. 구현은 쉽지만 길이 차이가 클수록 실제 데이터보다 Padding을 처리하는 데 더 많은 메모리와 연산을 쓸 수 있다. 그렇다면 Padding을 줄이거나 완전히 없애면서도 학습의 의미를 보존하려면 어떻게 해야 할까.

Padding이 왜 비효율적인가

Batch에 들어온 시퀀스의 길이를 L1,L2,,LBL_1, L_2, \ldots, L_B라 하고 가장 긴 길이를 LmaxL_{\max}라 하자. 일반적인 Dense Batch는 다음과 같은 Shape을 가진다.

XRB×Lmax×dX \in \mathbb{R}^{B \times L_{\max} \times d}

실제 Token 수와 Padding Token 수는 각각 다음과 같다.

Nreal=i=1BLi,Npad=BLmaxi=1BLiN_{\mathrm{real}} = \sum_{i=1}^{B} L_i, \qquad N_{\mathrm{pad}} = B L_{\max} - \sum_{i=1}^{B} L_i

FFN과 LayerNorm처럼 Token마다 수행되는 연산의 낭비는 대체로 Padding Token 수에 비례한다. 일반적인 Dense Attention은 각 시퀀스를 LmaxL_{\max} 길이로 계산하므로 Batch 연산량이 대략 BLmax2B L_{\max}^{2}에 비례한다. 실제 길이만 따로 계산할 수 있다면 필요한 Attention 연산은 iLi2\sum_i L_i^2에 가까워진다. Batch 안에 매우 긴 Sample 하나와 짧은 Sample 여러 개가 섞였을 때 문제가 특히 커지는 이유다.

여기서 한 가지는 구분해야 한다. Padding Token이 항상 학습 노이즈가 되는 것은 아니다. attention_mask로 Padding을 Attention에서 제외하고, Language Modeling의 labels에서 Padding 위치를 -100과 같은 Ignore Index로 처리했다면 Padding은 Loss에 직접 기여하지 않는다. 문제는 의미적 노이즈보다 불필요한 연산과 Activation Memory에 가깝다. 반대로 Mask가 누락되거나 EOS를 PAD로 재사용하면서 Label Mask를 잘못 설정하면 실제로 잘못된 학습 신호가 들어갈 수 있다.

1. Global Static Padding

가장 단순한 방식은 Dataset 전체를 미리 정한 max_length까지 Padding하는 것이다. 길이가 32인 문장도, 길이가 480인 문장도 모두 512 Token으로 만든다.

장점은 구현과 디버깅이 쉽고 모든 Step의 Shape이 같다는 것이다. 정적 Shape을 선호하는 Compiler나 Accelerator에서는 Kernel 재사용과 Graph Compilation도 단순해진다. 반면 Dataset의 길이 분포가 넓으면 낭비가 가장 크며, max_length를 줄이면 긴 Sample을 Truncation해야 한다.

현재는 특별한 정적 Shape 제약이 없는 한, Dataset 전체 길이로 Padding하는 방식만 단독으로 사용하는 경우는 권장하기 어렵다.

2. Dynamic Padding

Dynamic Padding은 Dataset 전체의 최대 길이가 아니라 현재 Batch에서 가장 긴 시퀀스까지만 Padding한다.

예를 들어 길이가 [31, 28, 35, 30]인 Batch는 35까지 Padding하고, 다음 Batch가 [180, 210, 192, 205]라면 210까지 Padding한다. Hugging Face의 Data Collator도 이 방식을 기본적인 가변 길이 Batch 구성법으로 제공한다.

장점은 다음과 같다.

  • Model 구조를 바꿀 필요가 없다.
  • Encoder, Decoder, Encoder-Decoder 등 거의 모든 Transformer에 적용할 수 있다.
  • 기존 Attention Mask, 분산 학습, 평가 코드와 호환성이 가장 좋다.
  • Global Padding보다 평균 Padding 비율이 크게 줄어든다.

단점도 남아 있다.

  • Batch 안에 긴 Outlier가 하나만 있어도 나머지 Sample이 모두 그 길이에 맞춰진다.
  • Step마다 Shape이 달라져 Compilation Cache가 늘거나 Recompilation이 발생할 수 있다.
  • GPU별로 Batch의 최대 길이가 다르면 Distributed Data Parallel에서 느린 Rank를 기다리는 Straggler 문제가 생긴다.

Dynamic Padding은 가장 범용적인 출발점이지만, 길이 분포가 넓을수록 다음 단계인 Length Bucketing과 함께 사용해야 효과가 커진다.

3. Length Bucketing과 Sortish Sampling

Length Bucketing은 길이가 비슷한 Sample끼리 Bucket을 만들고, 같은 Bucket 안에서 Batch를 구성한다. Dataset 전체를 길이순으로 완전히 정렬하면 Padding은 줄지만 학습 순서의 무작위성이 사라질 수 있다. 그래서 실무에서는 넓은 Window 안에서 길이를 정렬한 뒤 Bucket과 Sample 순서를 다시 섞는 Sortish Sampling을 많이 사용한다.

Hugging Face Trainergroup_by_length=True도 Dynamic Padding과 함께 길이가 비슷한 Sample을 묶어 Padding을 줄이는 옵션이다.

장점은 기존 Model과 Collator를 그대로 두고 Sampler만 바꿔도 된다는 점이다. Packing처럼 복잡한 Attention Mask를 만들 필요도 없다. 반면 길이와 Label 또는 Domain 사이에 상관관계가 있다면 Batch 분포가 한쪽으로 치우칠 수 있고, 너무 엄격하게 정렬하면 SGD가 얻는 무작위성이 줄어든다. Epoch마다 Bucket 내부와 Bucket 순서를 충분히 Shuffle하는 이유다.

4. Fixed Example Batch 대신 Token-budget Batching

batch_size=8은 Sample 수만 고정할 뿐 실제 Token 수를 고정하지 않는다. 8개의 짧은 문장과 8개의 긴 문장은 메모리와 계산량이 전혀 다르다. Token-budget Batching은 다음 조건을 만족할 때까지 Sample을 Batch에 추가한다.

iBLiTbudget\sum_{i \in \mathcal{B}} L_i \le T_{\mathrm{budget}}

짧은 Sample이 많으면 Batch에 더 많이 넣고, 긴 Sample이 들어오면 Sample 수를 줄인다. 이는 Token 단위 처리량과 Activation Memory를 비교적 안정적으로 만들고 OOM 가능성을 줄인다. Length Bucketing과 함께 쓰면 Padding도 더 줄일 수 있다.

주의할 점은 같은 Token 수가 같은 Attention 연산량을 뜻하지 않는다는 것이다. 길이 1,024인 Sample 하나의 Attention 비용과 길이 256인 Sample 네 개의 비용은 다르다.

102424×25621024^2 \ne 4 \times 256^2

또한 Step마다 Sample 수가 달라지므로 Loss를 Sample 평균으로 계산할지, 유효 Token 평균으로 계산할지 명확히 해야 한다. Gradient Accumulation도 몇 Batch가 아니라 몇 Token을 기준으로 설계하는 편이 일관적이다.

5. Sequence Packing

Sequence Packing은 Padding으로 남을 공간에 다른 Sample을 채워 넣는다. 최대 길이가 16이고 길이가 7, 5, 4인 세 Sample이 있다면 다음처럼 하나의 Row로 만들 수 있다.

[ A A A A A A A | B B B B B | C C C C ]

2021년의 Efficient Sequence Packing without Cross-contamination은 일반적인 NLP Dataset에서 전체 Token의 50%가 Padding이 될 수 있고 일부 설정에서는 89%까지 올라갈 수 있음을 보였다. 이 연구는 Packing을 Bin Packing 문제로 정식화하고, Sample 사이의 Attention을 차단하면 원래의 개별 학습과 수학적으로 동등하게 만들 수 있음을 설명했다.

Pre-training의 Concatenate-then-split

Decoder-only LLM의 사전학습에서는 여러 문서를 EOS Token으로 연결해 긴 Token Stream을 만들고, 이를 고정 길이 Block으로 자르는 방식이 오래전부터 널리 사용되었다. Padding이 거의 사라지고 Dataset을 순차적으로 읽기 쉽다는 장점이 있다.

하지만 Block 경계에서 문서가 잘릴 수 있고, 별도의 Block-diagonal Mask가 없다면 뒤 문서가 앞 문서에 Attention할 수 있다. 대규모 사전학습에서는 이를 허용하고 EOS를 문서 경계로 학습시키는 설계도 가능하지만, 서로 독립이어야 하는 SFT Sample이나 분류 Sample에는 그대로 적용하기 어렵다.

Cross-contamination을 막는 Packing

서로 독립적인 Sample aabb를 한 Row에 넣었을 때, bbaa의 Token을 볼 수 있으면 원래 Dataset에는 없던 조건부 관계가 생긴다. 이를 막으려면 Attention Mask를 Block-diagonal 형태로 만들어야 한다.

Mij={0if i,j belong to the same sampleotherwiseM_{ij} = \begin{cases} 0 & \text{if } i,j \text{ belong to the same sample} \newline -\infty & \text{otherwise} \end{cases}

각 Sample의 Position ID를 0부터 다시 시작할지, Pack 전체에서 계속 증가시킬지도 Model의 Position Encoding 방식과 학습 목표에 맞춰 결정해야 한다. SFT에서는 Prompt Token, Assistant Token, Sample 사이의 EOS와 첫 Token에 어떤 Label을 줄지도 함께 확인해야 한다. Padding만 없앴는데 Attention과 Loss 경계를 잘못 만들면 효율은 높아져도 학습 문제 자체가 달라진다.

어떤 Packing Algorithm을 쓸까

  • Greedy / First Fit: Dataset 순서대로 들어갈 수 있는 Pack에 넣는다. 빠르고 Streaming에 유리하지만 빈 공간이 더 남을 수 있다.
  • Best-Fit Decreasing(BFD): 긴 Sample부터 배치하고 남은 공간이 가장 작은 Pack을 선택한다. Padding을 잘 줄이지만 사전 정렬이 필요하고 순서가 바뀐다.
  • Wrapped: 모든 Token을 하나로 연결한 뒤 일정 길이로 자른다. 공간 효율은 높지만 Sample 중간을 자르고 무관한 문맥을 섞을 수 있다.
  • Semantic Packing: 길이뿐 아니라 주제나 의미가 유사한 Sample을 함께 넣는다. Threshold Filtering Packing처럼 SFT 성능까지 고려하지만, 유사도 계산과 Dataset 편향을 추가로 관리해야 한다.

현재 TRL의 SFTTrainerpacking=True를 지원하고 BFD를 기본 Packing 전략으로 제공한다. 공식 문서는 긴 Sample을 자르는 방식에 따라 bfd, bfd_split, wrapped를 구분하며, wrapped는 Sample 연속성을 깨뜨릴 수 있다고 명시한다.

6. Padding-free와 Variable-length Attention Kernel

Packing이 Dataset을 잘 채우는 문제라면, Padding-free Kernel은 GPU가 실제 Token만 계산하게 만드는 실행 문제다. 여러 Sample을 다음과 같이 하나의 연속된 Tensor로 펼칠 수 있다.

XflatR(iLi)×dX_{\mathrm{flat}} \in \mathbb{R}^{(\sum_i L_i) \times d}

대신 각 시퀀스의 시작점을 나타내는 누적 길이 cu_seqlens = [0, L_1, L_1+L_2, ...]를 Kernel에 전달한다. Kernel은 이 경계를 사용해 Sample별 Attention을 계산하고 다른 Sample로 Attention이 넘어가지 않게 한다.

ByteTransformer는 BERT 계열의 가변 길이 입력에서 Padding Token을 제거하고, Attention을 포함한 전체 Transformer의 불필요한 계산을 줄이는 Kernel 수준 접근을 제시했다. 이후 FlashAttention-2는 Attention Matrix를 GPU HBM에 완전히 Materialize하지 않는 IO-aware Exact Attention을 개선해 긴 시퀀스 학습의 메모리와 속도를 크게 개선했다.

다만 FlashAttention을 켰다고 Padding이 자동으로 사라지는 것은 아니다. 일반 Dense [B, L_{\max}, d] 입력을 그대로 주면 FlashAttention도 그 Shape을 처리한다. 실제 Padding-free 효과를 얻으려면 Flattened Input과 시퀀스 경계를 받는 Variable-length 경로를 사용해야 한다.

현재 Hugging Face의 Padding-free Training 문서DataCollatorWithFlattening이 Batch를 펼치고 FlashAttention용 경계 정보를 미리 만들도록 권장한다. TRL도 BFD Packing을 사용할 때 FlashAttention 2 또는 3 기반 Padding-free Forward를 연결한다.

장점은 Padding Token의 Embedding, Projection, FFN, Attention 계산을 실제로 제거할 수 있다는 것이다. 단점은 지원 Kernel과 Model 구조가 제한될 수 있고, Custom Attention Bias나 특수 Position Encoding, Multimodal Token Layout과 결합할 때 경계를 직접 검증해야 한다는 점이다.

7. Ragged Tensor와 Nested Tensor

가변 길이 데이터를 Tensor 자체의 표현으로 다루는 방법도 있다. PyTorch Nested Tensor는 서로 다른 길이의 Tensor를 Jagged Layout으로 저장하고 SDPA나 FlexAttention과 연결할 수 있다. Padding을 Materialize하지 않으면서 일반 Tensor와 비슷한 API를 제공한다는 점이 매력적이다.

하지만 모든 Operator와 Model이 Jagged Layout을 지원하는 것은 아니며, Padded Tensor로 되돌아가는 순간 Copy와 Padding 비용이 다시 생긴다. PyTorch 문서도 지원 범위와 개발 상태에 주의를 요구한다. 범용 학습 Pipeline의 기본값이라기보다 지원되는 연산이 명확한 Custom Model에서 선택할 수 있는 방법에 가깝다.

2025~2026년 연구는 무엇을 더 보고 있는가

Padding을 제거하는 것만으로 문제가 끝나지는 않는다. 2025년 ACL Findings의 Packing Analysis는 8B부터 70B까지의 Model과 69K부터 1.2M Sample까지의 Dataset을 비교하며, Packing의 이점이 Model과 Dataset 규모에 따라 달라진다는 점을 분석했다. 작은 실험에서는 Packing 전처리와 복잡성이 이득보다 클 수 있지만, Model과 Dataset이 커질수록 자원 활용 개선의 가치가 커진다.

Long-context 분산 학습에서는 Token 수가 같아도 GPU별 Attention 비용이 달라진다. 2026년 7월 공개된 Libra는 고정 Token Packing 이후에도 Pack의 Attention 비용이 jLj2\sum_j L_j^2에 비례하기 때문에 Data Parallel Rank와 Pipeline Stage 사이에 Straggler가 남는다고 지적한다. 즉 최신 연구의 초점은 Padding 제거 → Token 수 균형 → Attention FLOPs와 통신 균형으로 이동하고 있다.

이미지와 비디오처럼 Token 수 차이가 더 큰 Diffusion Transformer에서도 비슷한 문제가 나타난다. 2025~2026년의 KnapFormer, Dynamic Context Parallelism, ChunkFlow 계열 연구는 Sequence Parallelism의 크기와 Sample 배치를 동적으로 바꾸어 GPU별 부하를 맞추려 한다. 다만 이는 수십 장 이상의 GPU에서 Long-context 또는 Multimodal Model을 학습할 때의 문제이며, 일반적인 Fine-tuning의 첫 선택지는 아니다.

방법별 장단점 비교

방식Padding 낭비구현 난이도주요 장점주요 단점적합한 상황
Global Static Padding매우 큼낮음Shape 고정, 디버깅 용이메모리·연산 낭비, Truncation정적 Shape가 필수인 작은 실험
Dynamic Padding중간낮음가장 높은 호환성Batch Outlier에 취약일반 Encoder/Fine-tuning 기본값
Length Bucketing낮음~중간낮음Model 변경 없이 Padding 감소Shuffle과 분산 Sampler 설계 필요길이 분포가 넓은 일반 학습
Token-budget Batching낮음~중간중간Token 처리량과 메모리 안정화Attention 비용은 완전히 균등하지 않음긴 시퀀스가 섞인 학습
Sequence Packing매우 낮음중간~높음고정 길이 Block 활용률 증가경계 Mask, Position, Label 처리 필요LLM Pre-training과 SFT
Padding-free Varlen Attention거의 없음높음실제 Token만 계산Kernel과 Model 호환성 제약고효율 LLM·Long-context 학습
Distributed Workload Scheduling거의 없음매우 높음Multi-GPU Straggler 완화시스템 복잡성과 통신 비용대규모 Long-context 학습

오늘 기준으로 가장 보편적인 방식

하나의 방식이 모든 Transformer 학습에서 가장 많이 쓰인다고 말하면 정확하지 않다. 2026년 8월 기준으로는 다음처럼 구분하는 편이 현실적이다.

일반적인 Encoder, 분류, 번역, 중소규모 Fine-tuning

가장 보편적인 구성은 Dynamic Padding + Attention Mask + Length Bucketing이다. 기존 Model과 학습 코드를 거의 바꾸지 않고 대부분의 Hardware와 Task에서 안정적으로 동작한다. Padding이 일부 남더라도 구현 복잡성과 정확성 위험이 낮아 여전히 가장 넓은 범위에서 사용되는 기본형이다.

Decoder-only LLM Pre-training

여러 문서를 EOS로 연결해 고정 길이 Token Block을 만들고, 가능한 한 모든 위치를 실제 Token으로 채우는 방식이 일반적이다. Pipeline에 따라 문서 간 Attention을 허용하거나 Block-diagonal Mask로 차단한다. 중요한 것은 Sample 수보다 Global Tokens per Step을 일정하게 유지하는 것이다.

LLM Supervised Fine-tuning

가장 단순하고 안전한 Baseline은 Dynamic Padding과 Length Bucketing이다. Dataset이 크고 평균 Sample이 Context Window보다 짧아 Padding 비율이 높다면 BFD Packing + 올바른 Sample Boundary + FlashAttention Variable-length 경로가 현재의 대표적인 고효율 구성이다. Packing은 TRL에서도 여전히 명시적으로 켜야 하는 옵션이므로 모든 학습의 무조건적인 기본값은 아니다.

구현 예시

가장 범용적인 Hugging Face 구성은 다음과 같다.

from transformers import DataCollatorWithPadding, TrainingArguments

collator = DataCollatorWithPadding(
    tokenizer=tokenizer,
    pad_to_multiple_of=8,
)

args = TrainingArguments(
    output_dir="outputs",
    per_device_train_batch_size=8,
    group_by_length=True,
    length_column_name="length",
)

pad_to_multiple_of=8은 약간의 Padding을 다시 만들지만 Tensor Core가 선호하는 Shape을 맞춰 전체 속도가 더 좋아질 수 있다. Padding Token 수만 최소화하는 것과 실제 Step Time을 최소화하는 것은 항상 같지 않다.

Decoder-only LLM의 SFT에서 Packing을 사용한다면 현재 TRL 기준으로 다음과 같이 구성할 수 있다.

from trl import SFTConfig

args = SFTConfig(
    output_dir="outputs",
    max_length=4096,
    packing=True,
    packing_strategy="bfd",
    eval_packing=False,
)

BFD Packing은 긴 Sample이 max_length를 넘을 때 잘라낼 수 있으므로, 보존이 필요하면 bfd_split 또는 별도의 Chunking 정책을 검토해야 한다. 또한 사용 Model이 FlashAttention의 Padding-free 경로를 실제로 지원하는지 확인해야 한다.

실무에서 확인할 항목

  1. Tokenize한 뒤 평균만 보지 말고 P50, P90, P95, P99 길이를 확인한다.
  2. max_length를 먼저 고정하지 말고 Truncation되는 Token과 Sample 비율을 측정한다.
  3. Baseline은 Dynamic Padding과 Length Bucketing으로 시작한다.
  4. pad_ratio = padded_tokens / total_tokenstokens/sec를 함께 기록한다.
  5. Sample 수 대신 유효 Token 수를 기준으로 Loss와 Gradient Accumulation을 점검한다.
  6. Packing을 켰다면 Sample 간 Attention, Position ID, EOS, Label Mask를 Unit Test한다.
  7. FlashAttention 사용 여부가 아니라 Variable-length Kernel이 실제 호출되는지 Profile한다.
  8. 분산 학습에서는 Rank별 Token 수뿐 아니라 iLi2\sum_i L_i^2와 Step Time 편차를 확인한다.
  9. 평가 단계에서는 Sample별 Metric과 생성 결과를 분리하기 쉽도록 Packing을 끄는 편이 안전하다.

정리

Padding은 잘 Mask하면 학습 노이즈라기보다 계산 낭비다. 가장 안전한 개선은 Batch 최대 길이까지만 Padding하고 길이가 비슷한 Sample을 묶는 것이다. 더 높은 효율이 필요하면 Token-budget Batching과 Sequence Packing을 적용하고, Sample 경계를 보존하는 Block-diagonal 또는 Variable-length Attention으로 Cross-contamination을 막아야 한다.

현재의 실용적인 결론은 간단하다. 범용 Baseline은 Dynamic Padding + Length Bucketing이고, 고효율 LLM 학습은 BFD Packing + Padding-free FlashAttention이다. 그리고 Context가 수십만 Token으로 길어지고 GPU 수가 늘어나면, 그다음 병목은 Padding이 아니라 Attention 연산량과 분산 Worker 사이의 부하 불균형이 된다.