资讯动态

微调提速 6 倍:Qwen 27B 从 7 小时 44 分到 1 小时 17 分

发布时间:2026/10/18 6:24:54 来源:尧图企业网站定制
微调提速 6 倍Qwen 27B 从 7 小时 44 分到 1 小时 17 分原文Databricks Blog - 《Reducing Qwen training time by 6x on AI Runtime》https://www.databricks.com/blog/reducing-qwen-training-time-6x-ai-runtime一、起点GPU 利用率已经 98%Databricks 这条优化的基线很值得注意微调 Qwen 3.8 27B、固定数据集一次训练 7 小时 44 分钟GPU 平均利用率 98%吞吐 9,396 tokens/s。任何做过性能优化的人看到 98% 都会先愣一下——利用率这么高还有什么可挤的答案是有的而且挤出了 6 倍。这也直接说明了第一条经验GPU 利用率不等于没有浪费它只告诉你 GPU 在干活不告诉你它在干什么活。原文没有给出具体的卡型和卡数只说明在 27B 这个量级上纯数据并行放不下权重所以用 FSDP2 把模型分片到多卡。二、第一步不是优化是测量优化从一次单步 trace 开始用 torch.profiler 抓一个 step 的 CPU 与 CUDA 活动withtorch.profiler.profile(activities[torch.profiler.ProfilerActivity.CPU,torch.profiler.ProfilerActivity.CUDA,],scheduletorch.profiler.schedule(wait1,# 先跳过 1 步让运行时预热warmup0,# 这次单步 trace 不做额外 warmupactive1,# 只抓 1 个 steprepeat1))asprof:training code hereprint(prof.key_averages().table(sort_byself_cuda_time_total,row_limit10))torch.profiler.schedule这几个参数是配套用的wait 让前几步跑完预热active 决定抓几个 steprepeat 决定这套 schedule 重复几轮sort_by 换成本次关心的 self_cuda_time_totalrow_limit 只留前十名避免一次打印几千行。trace 给出的结果是这样的CPU 侧 14.4 秒、1,714,382 次调用GPU 侧 10.3 秒、269,448 个算子。CPU 的调用次数是 GPU 算子数的六倍多——瓶颈不在算力在按 token 逐个调度这件事本身的固定开销。三、优化一给非标准层装对口的内核第一个改动很小装上 flash-linear-attention 和 causal-conv1d 两个包。原因是 Qwen 3.8 的某些层不是标准注意力而是 gated delta network 这类线性注意力结构通用算子在它们上面效率不高。装上之后端到端时间从 7 小时 44 分降到 2 小时 5 分提速 3.7 倍。这一步没有任何算法改动纯粹是让算子对上硬件。非标准层要配对应的融合内核这条在任何自定义架构上成立。四、优化二把 2D 的 padding 换成 1D 打包第二个改动看着更不起眼但收益同样大。原来的数据加载器把样本打包成 2D 矩形张量每批 padding 到 8192 个 tokenpadding 出来的 token 也要参与前向和反向等于白烧算力。改法是把 2D 矩形张量换成 1D 的打包序列再传入正确的position_ids让每个样本只 attend 到自己内部的 token。padding 本身也在消耗算力而且这一改动把总批次数减少了大约 36%。结果2 小时 5 分降到 1 小时 17 分。两次改动叠起来就是 7 小时 44 分到 1 小时 17 分总体 6 倍。优化后的单步 profiling 也变干净了CPU 调用从 1,714,382 次降到 118,316 次GPU 算子从 269,448 个降到 18,060 个CPU 与 GPU 两边的耗时都降到 3.9 秒。五、这套配置本身就是性能的一部分除了上面两步原文给出的训练骨架也值得抄一遍因为分片、检查点和精度策略本身就是性能的一部分# 数据加载器data_loadermake_dataloader(HuggingFaceH4/ultrachat_200k)modelAutoModelForImageTextToText.from_pretrained(Qwen/Qwen3.8-27B,torch_dtypetorch.bfloat16)# 梯度检查点压低显存占用model.gradient_checkpointing_enable(gradient_checkpointing_kwargs{use_reentrant:False})# FSDP2逐层分片再对整体分片mpMixedPrecisionPolicy(param_dtypetorch.bfloat16,reduce_dtypetorch.float32)layersmodel.model.language_model.layersforlayerinlayers:fully_shard(layer,mp_policymp)fully_shard(model,mp_policymp)model.cuda()opttorch.optim.AdamW(model.parameters(),lr1e-5,betas(0.9,0.95),weight_decay0.0)model.train()forbatchindata_loader:outmodel(input_idsbatch[inputs],labelsbatch[labels])out.loss.backward()opt.step()opt.zero_grad(set_to_noneTrue)几处细节fully_shard先逐层调用、再对整个模型调用这是 FSDP2 的标准顺序先分层再整体才能让参数、梯度和优化器状态都按同一套分片走。MixedPrecisionPolicy里参数用 bfloat16、梯度归约用 float32是为了在省显存的同时不让归约精度拖累收敛。use_reentrantFalse是新一代梯度检查点的写法前者已不推荐。zero_grad(set_to_noneTrue)把梯度置为 None 而不是零省一次写显存。六、四条能直接搬走的经验只看 GPU 利用率会骗人98% 的利用率下仍然有 6 倍空间必须看时间花在哪单步 trace 就够不用跑完整训练抓一个 step 的 CPU 与 CUDA 活动就能定位大头自定义结构要主动找融合内核线性注意力、卷积之类的非标准层通用算子很容易成为瓶颈padding 是隐性浪费矩形打包看起来整洁实际上让模型算了一堆无意义的 token。七、小结这次提速没有引入任何新的算法或模型技巧三件事分别对应测量、算子、数据管线。真正有价值的是顺序先测量再动手而不是先换框架或调超参。对做 Agent 的同学来说这条链路同样有用——给 Agent 配一个自己微调的小模型时训练时间直接决定迭代速度而上面这些优化大多不依赖特定平台本地多卡环境也能照做。具体 API 与推荐配置请以 PyTorch 与 Databricks 官方文档为准本文未验证最新版本。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑