PyTorch 2.0 實操:為 HuggingFace 和 TIMM 模型提速!

超神經HyperAI發表於2022-12-15

PyTorch 2.0 透過簡單一行 torch.compile() 就可以使模型訓練速度提高 30%-200%,本教程將演示如何真實復現這種提速。

​torch.compile() 可以輕鬆地嘗試不同的編譯器後端,進而加速 PyTorch 程式碼的執行。它作為 torch.jit.script() 的直接替代品,可以直接在 nn.Module 上執行,無需修改原始碼。

上篇文章中,我們介紹了 torch.compile 支援任意的 PyTorch 程式碼、control flow、mutation,並一定程度上支援 dynamic shapes。

透過對 163 個開源模型進行測試,我們發現 torch.compile() 可以帶來 30%-200% 的加速。

opt_module = torch.compile(module)

測試結果詳見:
https://github.com/pytorch/to...

本教程將演示如何利用 torch.compile() 為模型訓練提速。

要求及設定

對於 GPU 而言(越新的 GPU 效能提升越突出):

pip3 install numpy --pre torch[dynamo] --force-reinstall --extra-index-url https://download.pytorch.org/whl/nightly/cu117

對於 CPU 而言:

pip3 install --pre torch --extra-index-url https://download.pytorch.org/whl/nightly/cpu

可選:驗證安裝

git clone https://github.com/pytorch/pytorch
cd tools/dynamo
python verify_dynamo.py

可選:Docker 安裝

在 PyTorch 的 Nightly Binaries 檔案中提供了所有必要的依賴項,可以透過以下方式下載:

docker pull ghcr.io/pytorch/pytorch-nightly

對於臨時測試 (ad hoc experiment),只需確保容器能夠訪問所有 GPU 即可:

docker run --gpus all -it ghcr.io/pytorch/pytorch-nightly:latest /bin/bash

開始

簡單示例

先來看一個簡單示例,注意,GPU 越新速度提升越明顯。

import torch
   def fn(x, y):
       a = torch.sin(x).cuda()
       b = torch.sin(y).cuda()
       return a + b
   new_fn = torch.compile(fn, backend="inductor")
   input_tensor = torch.randn(10000).to(device="cuda:0")
   a = new_fn()

這個例子實際上不會提升速度,但是可以拋磚引玉。

該示例中,torch.cos() 和 torch.sin() 是逐點運算 (pointwise ops) 的例子,他們可以在向量上逐一操作 element,一個更著名的逐點運算是 torch.relu()。

eager mode 下的逐點運算並不是最優解,因為每個運算元都需要從記憶體中讀取一個張量、做一些更改,然後再寫回這些更改。

PyTorch 2.0 最重要的一項最佳化是融合 (fusion)。

因此,該例中就可以把 2 次讀和 2 次寫變成 1 次讀和 1 次寫,這對較新的 GPU 來說是至關重要的,因為這些 GPU 的瓶頸是記憶體頻寬(能多快地把資料傳送到 GPU)而不是計算(GPU 能多快地進行浮點運算)。

PyTorch 2.0 第二個重要最佳化是 CUDA graphs。

CUDA graphs 有助於消除從 Python 程式中啟動單個核心的開銷。

torch.compile() 支援許多不同的後端,其中最值得關注的是 Inductor,它可以生成 Triton 核心。

https://github.com/openai/triton

這些核心是用 Python 寫的,但卻優於絕大多數手寫的 CUDA 核心。假設上面的例子叫做 trig.py,實際上可以透過執行來檢查生成 triton 核心的程式碼。

TORCHINDUCTOR_TRACE=1 python trig.py


@pointwise(size_hints=[16384], filename=__file__, meta={'signature': {0: '*fp32', 1: '*fp32', 2: 'i32'}, 'device': 0, 'constants': {}, 'configs': [instance_descriptor(divisible_by_16=(0, 1, 2), equal_to_1=())]})
   @triton.jit
   def kernel(in_ptr0, out_ptr0, xnumel, XBLOCK : tl.constexpr):
       xnumel = 10000
       xoffset = tl.program_id(0) * XBLOCK
       xindex = xoffset + tl.reshape(tl.arange(0, XBLOCK), [XBLOCK])
       xmask = xindex < xnumel
       x0 = xindex
       tmp0 = tl.load(in_ptr0 + (x0), xmask)
       tmp1 = tl.sin(tmp0)
       tmp2 = tl.sin(tmp1)
       tl.store(out_ptr0 + (x0 + tl.zeros([XBLOCK], tl.int32)), tmp2, xmask)

以上程式碼可知:兩個 sins 確實發生了融合,因為兩個 sin 運算元發生在一個 Triton 核心中,而且臨時變數被儲存在 register 中,訪問速度非常快。

真實模型示例

以 PyTorch Hub 中的 resnet50 為例:

import torch
   model = torch.hub.load('pytorch/vision:v0.10.0', 'resnet18', pretrained=True)
   opt_model = torch.compile(model, backend="inductor")
   model(torch.randn(1,3,64,64))

實際執行中會發現,第一次執行速度很慢,這是因為模型正在被編譯。隨後的執行速度會加快,所以在開始基準測試之前,通常的做法是對模型進行 warm up。

可以看到,這裡我們用「inductor」表示編譯器名稱,但它不是唯一可用的後端,可以在 REPL 中執行 torch._dynamo.list_backends() 來檢視可用後端的完整列表。

也可以試試 aot_cudagraphs 或 nvfuser 。

Hugging Face 模型示例

PyTorch 社群經常使用 transformers 或 TIMM 的預訓練模型:

https://github.com/huggingfac...

https://github.com/rwightman/...

PyTorch 2.0 的設計目標之一,就是任意編譯棧,都需要在實際執行的絕大多數模型中,開箱即用。

這裡我們直接從 HuggingFace hub 下載一個預訓練的模型,並進行最佳化:

import torch
   from transformers import BertTokenizer, BertModel
   # Copy pasted from here https://huggingface.co/bert-base-uncased
   tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
   model = BertModel.from_pretrained("bert-base-uncased").to(device="cuda:0")
   model = torch.compile(model) # This is the only line of code that we changed
   text = "Replace me by any text you'd like."
   encoded_input = tokenizer(text, return_tensors='pt').to(device="cuda:0")
   output = model(**encoded_input)

如果從模型中刪除 to(device="cuda:0") 和 encoded_input ,PyTorch 2.0 將生成為在 CPU 上執行最佳化的 C++ 核心。

可以檢查 BERT 的 Triton 或 C++ 核心,它們顯然比上面的三角函式的例子更復雜。但如果你瞭解 PyTorch 可以略過。

同樣的程式碼與以下一起使用,仍舊可以得到更好的效果:

同樣的,試試 TIMM 的例子:

import timm
   import torch
   model = timm.create_model('resnext101_32x8d', pretrained=True, num_classes=2)
   opt_model = torch.compile(model, backend="inductor")
   opt_model(torch.randn(64,3,7,7))

PyTorch 的目標是建立一個能適配更多模型的編譯器,為絕大多數開源模型的執行提速,現在就訪問 HuggingFace Hub,用 PyTorch 2.0 為 TIMM 模型加速吧!

https://huggingface.co/timm

相關文章