Reducing Qwen training time by 6x on AI Runtime
https://www.databricks.com/blog/reducing-qwen-training-time-6x-ai-runtime📌 Databricks實測:微調Qwen 27B速度提升6倍,從7.7小時到77分鐘
TL;DR:Databricks靠kernel融合與資料封裝,把Qwen微調時間砍成六分之一。
如果有人跟你說他的GPU使用率長期維持在98%,你可能會以為訓練已經把硬體用到極致了。但Databricks工程團隊發現,這個數字其實會騙人。
🤔 GPU使用率98%,為什麼還有最佳化空間 GPU使用率這個指標只計算GPU「有沒有在做事」的時間比例,不代表GPU活動時做得有多好。Databricks團隊在AI Runtime(AIR)上微調Qwen 3.8 27B模型,搭配transformers函式庫與HuggingFaceH4/ultrachat_200k這個SFT資料集,baseline跑完整資料集需要7小時44分,訓練速度為9,396 tok/s,期間GPU平均使用率98%。
為了搞清楚使用率背後真正發生了什麼,團隊用torch的trace機制做更深入的剖析(並限制step數以快速取樣)。單一step的結果顯示:CPU耗時14.4秒,呼叫次數高達1,714,382次;GPU耗時10.3秒,包含269,448個operations。其中batch matrix multiplication這個kernel在一個step內就被呼叫超過30,000次,同時批次中還存在大量為了湊成矩形張量而產生的padding浪費。
🧩 融合kernel與重新設計batch,兩個關鍵最佳化 第一個最佳化是kernel融合。所謂fused kernel,是把多個小的GPU kernel合併成一個單位,藉此避免GPU高頻寬記憶體與晶片上更高頻寬記憶體之間的昂貴搬運,同時也讓GPU核心的排程更有效率。團隊觀察到Qwen模型裡大量的小operation其實來自非標準的gated delta network層,而這類層剛好有causal-conv1d與flash-linear-attention這兩個套件可以把許多逐層的operation融合起來。要知道哪些kernel可用,其實transformers函式庫對Qwen的實作本身就會告訴你支援哪些kernel。
取得這些kernel有兩種方式:一是在建置容器時就把所有kernel安裝好(團隊推薦這個作法,尤其是搭配docker容器一起使用,可以減少job啟動時間);二是使用kernelhub,雖然方便,但由於kernel常常與特定版本的torch、CUDA綁定,你的環境未必有對應版本可用,此時transformers模型會悄悄退回未最佳化的kernel,效能大打折扣卻不會有任何警示。安裝flash-linear-attention與causal-conv1d這兩個Python套件後,端到端時間改善了3.7倍,降到2小時5分。新的profiling結果顯示,原本的elementwise運算已經被對應的融合kernel取代,總operation數量也減少了15倍(CPU耗時降到3.9秒、118,316次呼叫;GPU耗時降到3.9秒、18,060次呼叫)。需要留意的是,訓練時forward與backward兩個方向都需要對應kernel,但部分kernel只為了加速推理而實作,訓練時未必能直接套用。
第二個最佳化是重新設計batch的組織方式。預設的batching使用2D矩形張量,把每個batch填到8,192 token(這是記憶體允許的最大值),但這種做法會把大量空間浪費在padding上。如果Qwen只用標準attention機制,torch的NestedTensor已經能很好地支援、藉由完全移除padding來省下這筆成本,但flash-linear-attention只支援較傳統的1D packed張量。問題是不能直接把1D張量丟進模型——這種做法在預訓練階段或許可以接受,但微調時必須確保同一個樣本中的token只會互相關注,不能跨樣本attend。解法是把正確的樣本position_ids傳入模型,模型會把這些資訊傳給融合過的attention kernel,讓它套用正確的attention mask。
📊 這個改動,把訓練時間再砍36% 做完這個看似簡單的改動,訓練時間從2小時5分降到1小時17分。這裡有個明確的trade-off:輸入pipeline會變得更複雜,出錯的風險也會提高,一旦出錯可能影響模型品質;但在這個案例裡,36%的時間節省被認為值得冒這個風險。團隊也提到另一種減少padding的做法——維持原本的batching機制,但把所有輸入依長度排序以縮小padding,只是這麼做可能讓相鄰樣本之間產生意外的相關性,帶來訓練上的偏誤。整體而言,這個資料封裝(packing)的改動帶來1.6倍的訓練速度提升。
💡 六倍加速背後的三件事 把整個過程串起來看,6倍加速(從7小時44分到1小時17分)來自三件事:先衡量真正重要的指標,而不是只看GPU使用率,找出模型實際把時間花在哪裡;用像Flash Linear Attention這類已知函式庫做kernel融合;以及重新設計batch的資料餵入方式。
🎯 實務啟示 Databricks表示,AI Runtime已經內建了許多這類最佳化:基礎環境預先綁好最新版CUDA與flash attention,自動追蹤GPU使用率、溫度與健康狀態等核心指標,並確保torch版本與profiler保持在最新狀態;內部agent「Genie Code」也會主動尋找類似的最佳化機會,而透過UCVolumeDataset讀取Databricks上的資料也經過最佳化。對正在微調大型語言模型的工程師來說,這個案例提醒:GPU使用率高不代表沒有最佳化空間,真正該做的是用profiling工具找出kernel層級的浪費,再評估kernel融合與資料封裝這類改動是否值得承擔額外的複雜度與風險。
🔗 來源
- 標題:Reducing Qwen training time by 6x on AI Runtime
- 作者/機構:Databricks
- 連結:https://www.databricks.com/blog/reducing-qwen-training-time-6x-ai-runtime
#Qwen #LLM #FineTuning #Databricks #GPUOptimization #FlashAttention #MachineLearning #DeepLearning #MLOps #PerformanceEngineering
原始資料 Databricks · 收集於 2026-10-15
摘要原文
Training or finetuning a multi-billion parameter foundation model requires significant compute, often spanning hundreds to thousands of hours of GPU training. But tuning training performance for an LLM is challenging, and high level metrics, such as GPU utilization may not be showing your actual utilization. In this blog post, we show how to cut through the layers of abstraction, and speed up fine-tuning of the latest Qwen 3.8 27B model on AI Runtime by 6x. Through systematic performance optimization, even with GPU utilization already at 98%, we go from 7 hours 44 minutes to 1 hour 17 minutes fine tuning a fixed dataset. We will provide a deep dive into the techniques behind these improvements, from how to measure what matters, implementation details, and design trade-offs involved in each optimization. If you want to get started right away, jump directly to the example code on AI Runtime and give it a try! We start with a standard training loop for Qwen 3.8 27B using the transformers library and the SFT training dataset HuggingFaceH4/ultrachat_200k . Our baseline training of the model over the complete dataset takes 7 hours and 44 minutes and during the training the GPU shows 98% average utilization. The result is 9,396 tok/s training. This GPU utilization looks high, but as mentioned earlier that number is misleading. The standard GPU utilization metric only counts the percentage of the time the GPU was doing anything , so this does not tell us how well the model was using the GPU while it was active. To understand this we need to look deeper. One way of doing this is the torch trace mechanism, we can adjust our training loop like the following (while also limiting the number of steps so we get a quick response): For a sample step this gives us the following data: Table 1: CPU - 14.4s with 1,714,382 calls. Table contains top operations by total time. Table 2: GPU - 10.3s with 269,448 operations. Table contains top operations by total time From these tables we notice a few high level issues with the current model: As well, we have an issue with excess padding in the batch: the input tensor must be rectangular and each row gets padded to the maximum number of tokens for that batch. We will show a trick for LLMs that can be used to minimize it. In the following sections, we will dive deeper into each technical topic, covering what it is, recommended best practices, and the key trade-offs to consider when applying it. You may notice that some kernels occur a lot! In particular, for Qwen, the batch matrix multiplication kernel runs over 30,000 times in one step. In this case we’ll explore fused kernels first. A fused kernel takes multiple smaller GPU kernels and combines them into one unit. This can help performance by avoiding costly transfers between a GPU’s high bandwidth memory and its even higher bandwidth on die memory. As well it can also allow for more efficient scheduling of GPU cores. With this many extra kernels, while torch.compile might work, it will take a long time to run. So reducing the raw number of ops with manually fused kernels is a good first step. Also, well-known fusions such as Flash Attention aren’t something that torch.compile by itself would find. The transformers library by default uses torch’s SDPA for the standard attention layers. But, it turns out many of these small operations we see come from the non-standard gated delta network layers. We’re lucky that for these layers there are two kernels, causal-conv1d and flash-linear-attention which have been written to fuse many of these operations per layer. How do we discover this? The transformers library implementation of Qwen already tells us what kernels it supports: There are two high level methods for getting these kernels: We generally recommend the first, especially paired with our docker container support , build your container with all your kernels installed at build time to reduce the amount of time it takes for your job to start. While kernelhub is convenient, since kernels often explicitly depend on the version torch and cuda installed, an available kernel for your specific configuration might not be available, and the transformers model will silently fallback to an unoptimized kernel with low performance. After installing the flash-linear-attention and causal-conv1d python packages, we improved our end to end time by a factor of 3.7x down to 2 hours 5 minutes. In the tables below, you can see the new kernels (highlighted), taking the place of the elementwise functions in the previous tables. As well the total number of operations executed has been greatly reduced, by a factor of 15x: Table 3: CPU - 3.9s with 118,316 calls. Table contains top operations by total time. Table 4: GPU - 3.9s with 18,060 calls. Table contains top operations by total time. Caveat : during training you will need both a kernel for the forwards and backwards pass and some kernels are forwards only, as they’ve only been implemented to speed up inference. Our Qwen model is pretty fast now, but we can get even faster. By default our batching setup uses 2D rectangular tensors 8,192 tokens as this is the largest size we can fit without running out of memory. The downside of this approach is that we tend to waste a considerable amount of space in the tensor with padding. If Qwen only used standard attention mechanisms, the torch’s NestedTensor is now well supported and would help reduce this cost by completely removing the padding. But flash-linear-attention only supports a more traditional 1D packed tensor approach: But we can’t just do this arbitrarily, simply passing the 1D tensor to the model may be acceptable for pretraining, but for finetuning, we need to ensure that in an example tokens only attend to tokens within the same example. To fix this we have to pass in the correct example position_ids to the model. In turn the model will pass these to the fused attention kernels and it will apply the correct attention mask. If you look at the example code here you can see how we compute the needed inputs to make this work. With this simple looking change, our training time goes from 2 hours 5 minutes to 1 hour and 17 minutes. Trade off: This style of change does make the input pipeline more complex, and there’s a high chance of introducing errors that impact model quality. But in this case the 36% savings are worth it. There are other techniques for reducing padding such as keeping the original batching mechanism but sorting all inputs by length, so that the padding is minimized, but this order may introduce unexpected correlation in adjacent samples, biasing the model. Take Aways: When training LLMs over a dataset with inputs shorter than your max trainable tokens (e.g. in this case 8192), efficiently packing data into batches can save a significant amount of time. For this dataset, we saw a 1.6x improvement in training speed, taking our training time from 2 hours 5 minutes to 1 hour and 17 minutes . In this blog, we showed you how to 6x your training efficiency. It came down to (1) first measuring what matters, not just gpu utilization, but also where your model is spending time training and (2) optimizing and fusing kernels, using known libraries like Flash Linear Attention and (3) tailoring how batches of data are fed into the model. To do this at scale, you need a platform that makes a performant path the default. In AIR, many of these performance improvements are built in. We bundle in the latest CUDA and flash attention with our base environments, automatically track core metrics like GPU utilization, temperature, and health for you, ensure torch versions (and the profilers are up to date). Our internal agent, Genie Code, is also tuned to look for opportunities like these to further optimize your training. Furthermore, reading data from Databricks is optimized through our UCVolumeDataset . Please give AIR a try for your next training workload! Ready to try it?
由 claude-code/sonnet 自動生成