메뉴
HN
Hacker News 24일 전

Unsloth와 엔비디아, 소비자용 GPU에서 LLM 학습 25% 속도 향상 달성

IMP
8/10
핵심 요약

Unsloth와 NVIDIA는 소비자용 GPU에서 LLM 파인튜닝 시 발생하는 숨겨진 병목 현상을 해결하여 학습 속도를 약 25% 향상시켰습니다. 반복적인 메타데이터 구축을 캐싱하고, 그래디언트 체크포인팅 시 버퍼를 2개 사용해 연산을 겹치게 하며, MoE 라우팅을 최적화하는 세 가지 핵심 기술을 도입했습니다. 이는 개발자들이 기존 하드웨어의 한계를 뛰어넘어 최대치의 성능을 끌어낼 수 있게 해준다는 점에서 실무적으로 매우 중요합니다.

번역된 본문

모델 블로그 Unsloth Studio✨ 문서 블로그 Unsloth와 NVIDIA로 LLM 학습을 더 빠르게 만드는 방법 2026년 5월 6일

저자: Daniel, Michael, Mathew, Datta (NVIDIA의 도움을 받아 작성됨)

파인튜닝(Fine-tuning)은 오늘날 가장 연산 집약적인 워크로드 중 하나이며, 계속해서 하드웨어의 한계를 시험하고 있습니다. NVIDIA GPU는 이러한 워크로드에 맞게 특별히 제작되었으며, 복잡한 문제를 조각내어 병렬로 처리합니다. Unsloth는 로컬 RTX 노트북부터 DGX Spark 개인 AI 슈퍼컴퓨터에 이르기까지 광범위한 NVIDIA GPU에서 원활하게 작동합니다.

개발자들이 GPU의 성능을 최대한 끌어낼 수 있도록 돕기 위해 Unsloth는 NVIDIA와 팀업하여 학습 속도를 저하시키는 숨겨진 병목 현상을 제거했습니다. 새롭게 구현된 이러한 최적화들을 결합했을 때 GPU 학습 속도를 약 25% 가속할 수 있었습니다. 우리가 정확히 어떻게 했는지 여기에 설명해 드립니다.

모델 학습을 최적화할 때 개발자들은 보통 영향력이 큰 일반적인 커널(kernel)부터 시작합니다. 행렬 곱셈(matmul), 어텐션(attention), 퓨즈드 옵스(fused ops), 그룹화된 GEMM(grouped GEMM) 등이죠. 이러한 커널들이 대부분의 연산을 담당하지만, 주요 구성 요소가 최적화되고 나면 전혀 다른 종류의 병목 현상이 나타납니다. GPU는 메타데이터에 종속된 작업에서 멈춤(stall) 현상을 겪게 됩니다. 런타임은 매 반복(iteration)마다 동일한 데이터 구조를 다시 구축하며, 복사(copy) 및 연산(compute) 스트림은 겹칠(overlap) 수 있음에도 불구하고 순차적으로 실행됩니다.

이러한 병목 현상을 해결하기 위해 Unsloth와 NVIDIA는 세 가지 개선 사항을 협력하여 개발했습니다:

  1. 레이어 간에 팩된 시퀀스(packed-sequence) 메타데이터를 재구성하는 것을 피하기 위해 캐싱(Caching) 적용
  2. 그래디언트 체크포인팅(gradient checkpointing) 중 두 개의 버퍼를 사용하여 활성화 리로드(activation reload)가 역방향 연산(backward compute)과 겹칠 수 있도록 함
  3. argsort 및 bincount로 토큰을 한 번 그룹화하여 GPT-OSS MoE 라우팅의 비용을 절감

이러한 최적화의 공통된 패턴은 간단합니다. 반복되는 불필요한 정리 작업(bookkeeping)을 줄이고, 복사 작업이 유용한 연산과 병렬적으로 이루어지도록 만드는 것입니다.

  1. 팩된 시퀀스(Packed-Sequence) 메타데이터 캐싱

몇 가지 짧은 예제가 있다고 가정해 보겠습니다. 모든 예제를 동일한 길이로 패딩(padding)하고 패딩 토큰에 연산 리소스를 낭비하는 대신, 이를 하나의 긴 팩된 시퀀스로 연결(concatenate)합니다.

그러나 모델은 여전히 각 원본 시퀀스가 어디에서 시작하고 끝나는지 알아야 합니다. 따라서 팩된 토큰과 함께 다음과 같은 시퀀스 메타데이터를 전달합니다:

  • 시퀀스 길이(sequence lengths)
  • 누적 시퀀스 오프셋(cumulative sequence offsets, cu_seqlens)
  • 최대 시퀀스 길이(maximum sequence length)
  • 위 세 가지 항목에서 파생된 어텐션 구조(attention structure)

여기서 핵심은 고정된 팩된 배치에 대해 해당 메타데이터가 모든 레이어에서 동일하다는 것입니다. 팩된 배치의 경계 정보를 다음과 같이 작성해 보겠습니다:

B = { lengths, cu_seqlens, max_seqlen, mask structure }

그러면 순방향 패스(forward pass)에서 모든 트랜스포머 레이어가 동일한 B를 사용하게 됩니다. 모델에 L개의 레이어가 있는 경우, 레이어당 한 번씩 B를 재구축하거나 재동기화하는 것은 새로운 작업이 아닙니다. 이는 동일한 정보를 반복해서 재구축하는 것과 같습니다.

다시 말해, 유용한 작업은 다음과 같습니다: B를 한 번 구축하고, 이를 L번 사용합니다.

반면 비효율적인 방식은 다음과 같습니다: B 구축 + B 구축 + ⋯ + B 구축 (L번 반복)

여기서 발생하는 오버헤드는 주로 추가적인 FLOPs(초당 부동소수점 연산)에 있지 않습니다. 이러한 경로 중 일부는 디바이스-투-호스트 동기화(device-to-host synchronization)를 강제하여 결과적으로 GPU-CPU 동기화 지점을 만들 수 있습니다. 레이어별 경로 내에서 이러한 일이 발생하면 모든 레이어에서 오버헤드가 반복됩니다. 바로 이것이 팩된 시퀀스 캐싱 변경을 통해 줄이고자 하는 부분입니다.

반복적으로 팩된 시퀀스 정보, SDPA 팩된 마스크, xFormers 블록 마스크를 재구성하는 대신, 현재 팩된 배치에 대해 기기당 재사용 가능한 메타데이터와 이에서 파생된 어텐션 측면의 구조를 캐싱합니다. 그런 다음 이러한 캐시된 구조가 레이어 전반에 걸쳐 재사용됩니다.

왜 도움이 되는가 팩된 학습은 이미 패딩 낭비를 없애 하드웨어 활용도를 높여줍니다. 하지만 메타데이터 경로가 계속해서 동기화를 강제하면, 모델의 실제 학습과는 무관한 오버헤드로 인해 얻었던 이점의 일부를 잃게 됩니다. 캐싱은 핵심 경로에서 반복되는 조정 작업을 제거하므로 도움이 됩니다. 순방향 패스는 여러 레이어에 걸쳐 동일한 팩된 메타데이터가 반복적으로 소비되는 곳이므로 가장 큰 이점을 얻습니다.

벤치마크 Qwen3-14B QLoRA SFT 기준:

  • 순방향 패스(forward): +43.3%
  • 역방향 패스(backward): +5.8%
  • 배치당 속도(per batch): +14.3%

순방향 패스에서 가장 큰 이점을 볼 수 있는 이유는 다음과 같습니다.

원문 보기
원문 보기 (영어)
unsloth Models Blog Unsloth Studio✨ Docs Blog How to Make LLM Training Faster with Unsloth and NVIDIA May 6, 2026 May 6, 2026 Authors: Daniel, Michael, Mathew and Datta, with help from NVIDIA Fine-tuning is one of today's most computationally intensive workloads, and it continues to push hardware to its limits. NVIDIA GPUs are purpose-built for these workloads: they break complex problems into pieces and process them in parallel. Unsloth works across the breadth of NVIDIA GPUs, from local RTX laptops to DGX Spark personal AI supercomputers. To help developers get the most out of their GPUs, Unsloth has teamed up with NVIDIA to eliminate hidden bottlenecks that slow down training. These newly implemented optimizations accelerate GPU training speeds by ~25% when combined. Here is exactly how we did it. When optimizing model training, developers often start with the usual high-impact kernels: matmuls, attention, fused ops, grouped GEMM, and so on. Although those kernels do most of the arithmetic, once the main components are optimized, a different class of bottlenecks emerges. The GPU stalls on metadata-dependent work. The runtime rebuilds identical data structures every iteration, and copy/compute streams execute in sequence when they could instead overlap. By targeting these bottlenecks, Unsloth and NVIDIA collaborated on three improvements: Caching packed-sequence metadata to avoid reconstructing it across layers Using two buffers during gradient checkpointing so activation reloads can overlap with backward compute Making GPT-OSS MoE routing cheaper by grouping tokens once with argsort and bincount The common pattern across these optimizations is simple: do less repeated bookkeeping, and make copy work happen in parallel with useful compute. 1. Caching Packed-Sequence Metadata Suppose we have several short examples: Instead of padding all of them to the same length and wasting compute on padding tokens, we concatenate them into one longer packed sequence: The model still needs to know where each original sequence starts and ends. So, alongside the packed tokens, we carry sequence metadata such as: sequence lengths cumulative sequence offsets ( cu_seqlens ) the maximum sequence length attention structure derived from the three items above This is the key point: for a fixed packed batch, that metadata is the same for every layer. If we write the boundary information for a packed batch as: B = { lengths, cu_seqlens, max_seqlen, mask structure } then every transformer layer in that forward pass consumes the same B . If the model has L layers, rebuilding or re-synchronizing on B once per layer is not new work. It is the same information being reconstructed again and again. In other words, the useful work is: build B once, use it L times. The wasteful version is: build B + build B + ⋯ + build B ( L times) The overhead here is not primarily extra FLOPs. Some of these paths can force device-to-host synchronization, effectively creating a GPU-CPU sync point. Once that happens inside a per-layer path, the overhead recurs at every layer. That is what the packed-sequence caching change reduces. Instead of repeatedly reconstructing packed sequence info, SDPA packed masks, and xFormers block masks, it caches the reusable metadata and the attention-side structures derived from it, per device, for the current packed batch. Those cached structures are then reused across layers. Why this helps Packed training already improves utilization by eliminating padding waste. But if the metadata path keeps forcing synchronization, some of that gain is lost to overhead that has nothing to do with the model's actual learning. Caching helps because it removes repeated coordination work from the hot path. The forward pass benefits the most because that is where the same packed metadata is consumed repeatedly across many layers. Benchmarks On Qwen3-14B QLoRA SFT : forward: +43.3% backward: +5.8% per batch: +14.3% The forward pass sees the biggest benefit because repeated metadata and mask preparation show up most directly there. Backward also improves, but the effect is smaller. The time saved is similar, but the backward pass, especially with gradient checkpointing, takes longer, so the relative gains appear smaller. Now that we know the measured gain, we can ask a simpler question: does that scale make sense? A quick sanity check If we assume each layer is roughly similar, we can model the packed-attention path as: T_uncached ≈ L · ( A + s ) where: L is the number of layers, A is the useful attention-side work per layer, s is the repeated metadata and mask-preparation overhead per layer. With caching, that repeated overhead is paid once for the batch instead of once per layer: T_cached ≈ L · A + s So the saved time is approximately: T_saved ≈ ( L − 1) · s For the packed SDPA path, our microbenchmark on NVIDIA Blackwell GPUs showed that the low-level, host-visible metadata calls were real but small, at about 0.2 ms each. The dominant repeated cost was the packed SDPA mask-construction path itself, which measured about 13.7 ms for a synthetic packed batch with 2048 total packed tokens. For the SDPA backend, a better mental model is: small stream fence + mask rebuild ≈ mask rebuild That lets us do a cleaner consistency check. If one packed-mask rebuild costs m milliseconds, then under a uniform-layer model: T_saved ≈ ( L − 1) · m With m ≈ 13.7 ms, that predicts: 16 layers: (16 - 1) x 13.7 ≈ 206 ms 28 layers: (28 - 1) x 13.7 ≈ 370 ms Smaller packed-sequence runs showed the same pattern: Llama-3.2-1B , 16 layers: about 199 ms saved per step, which is about 11.5% lower end-to-end step time Qwen3-0.6B , 28 layers: about 319 ms saved per step, which is about 14.8% lower end-to-end step time Those percentages are relative to full training step time, so they still include work outside the packed-attention path, such as embeddings, the MLP, the LM head, the loss, and framework overhead. This estimate is intentionally only about the packed-attention side of the block, not the whole transformer layer. It is there only to check that the measured gains are in the right range for the packed SDPA path. 2. Hiding Latency With Double-Buffered Checkpoint Reloads Activation checkpointing is a standard technique for training large models. The idea is to save memory by not keeping every intermediate activation alive through the backward pass. In exchange, we pay for some extra work during backward. That trade-off is usually worth it, especially for larger models. But it raises another systems question: if an activation has been offloaded, how does it get back to the GPU for backward? In Unsloth's smart checkpointing path, activations can be staged in pinned CPU memory and copied back when needed. That saves VRAM, but it can introduce a bottleneck: Copy the activation from CPU to GPU. Wait for the copy to complete. Run backward compute on that activation. Start the next copy. That is a serialization pattern. If one buffer is reused for both copy and compute, the copy stream and the compute stream keep taking turns. Let T_copy be the activation reload time and T_compute be the backward compute time for the current layer. With a single buffer, this part of the step is roughly limited by: T_single ≈ T_copy + T_compute That is the serialized case. We pay for both almost entirely, one after the other. A cleaner way to handle this is to use two buffers. While the backward pass is running on buffer A , the copy stream can preload the next activation into buffer B . Then the roles swap. That creates pipeline overlap, though not perfect overlap. Double buffering does not reduce the amount of math. It hides copy latency behind useful compute. Why this helps This kind of optimization tends to get stronger once the model is large enough that backward compute is substantial, but not so dominant that all copy overhead disappears into noise. For larger models, higher hidden dimensions mean more data movement, so hid