1. 管道并行
DeepSpeed v0.3增加了(le)對管(guan)道(dao)并(bing)(bing)(bing)行(xing)的(de)新支持(chi)。管(guan)道(dao)并(bing)(bing)(bing)行(xing)將模型的(de)層(ceng)劃分為階段,可以并(bing)(bing)(bing)行(xing)處(chu)理(li),從而(er)提高深(shen)度學習(xi)訓練(lian)的(de)內(nei)存和計算效率。DeepSpeed的(de)訓練(lian)引擎提供了(le)**混合(he)數據(ju)和管(guan)道(dao)并(bing)(bing)(bing)行(xing)**,并(bing)(bing)(bing)可進一(yi)步與(yu)**模型并(bing)(bing)(bing)行(xing)**(如Megatron-LM)結合(he)使用(yong)
DeepSpeed使(shi)用梯度(du)累積(ji)(ji)來(lai)提取管(guan)道并(bing)行(xing)性。每個訓練數據批(pi)(pi)次(ci)被劃分為可以由(you)管(guan)道階段并(bing)行(xing)處理的(de)(de)微批(pi)(pi)次(ci)。一(yi)旦一(yi)個階段完成微批(pi)(pi)次(ci)的(de)(de)正向傳遞,激活(huo)內存就(jiu)會(hui)(hui)傳遞到管(guan)道中的(de)(de)下(xia)(xia)一(yi)個階段。同樣地,當(dang)下(xia)(xia)一(yi)個階段在(zai)微批(pi)(pi)次(ci)上(shang)完成反(fan)(fan)向傳遞時,關于激活(huo)的(de)(de)梯度(du)就(jiu)會(hui)(hui)通過(guo)管(guan)道向后傳遞。每個反(fan)(fan)向傳遞都(dou)會(hui)(hui)本地累積(ji)(ji)梯度(du)。接下(xia)(xia)來(lai),所有數據并(bing)行(xing)組都(dou)會(hui)(hui)并(bing)行(xing)執行(xing)梯度(du)的(de)(de)縮減操作。最(zui)后,優化器會(hui)(hui)更(geng)新模型權(quan)重。
2.Pipline 并行開始
DeepSpeed致力于加速和(he)簡化管道并行(xing)訓練過程。本節提供(gong)了使用混(hun)合數據和(he)管道并行(xing)訓練torchvision的(de)AlexNet模型(xing)的(de)第(di)一步。
2.1 表示管道模型
管道(dao)并行(xing)需要將(jiang)模(mo)型表示為(wei)層(ceng)(ceng)序(xu)列(lie)。在正(zheng)向傳(chuan)遞中(zhong),每個(ge)層(ceng)(ceng)都使用前一層(ceng)(ceng)的輸出。事(shi)實上(shang),對于管道(dao)并行(xing)模(mo)型,不(bu)需要指定forward()!管道(dao)并行(xing)模(mo)型的正(zheng)向傳(chuan)遞隱(yin)式(shi)采用以下形式(shi):
def forward(self, inputs):
x = inputs
for layer in self.layers:
x = layer(x)
return x
PyTorch的`torch.nn.Sequential`是表達管道并行(xing)模型的方(fang)便容器(qi),并且可以在無需修改(gai)的情況(kuang)下(xia)由DeepSpeed并行(xing)化。
net = nn.Sequential(
nn.Linear(in_features, hidden_dim),
nn.ReLU(inplace=True),
nn.Linear(hidden_dim, out_features)
)
from deepspeed.pipe import PipelineModule
net = PipelineModule(layers=net, num_stages=2)
`PipelineModule`使(shi)用其(qi)`layers`參數(shu)作為構(gou)成模型(xing)的(de)層序列。初(chu)始化后,net被劃分為兩個管道階段,其(qi)層被移動(dong)到(dao)相應(ying)的(de)GPU上。如果存在多于兩個GPU,則DeepSpeed還(huan)將(jiang)使(shi)用**混(hun)合數(shu)據(ju)并行(xing)。**
Note:The total number of GPUs must be divisible by the number of pipeline stages.
2.2 輸入和輸出
在`torch.nn.Sequential`之后(hou),每個(ge)層的(de)輸入和輸出必須是單個(ge)`torch.Tensor`或一個(ge)元組的(de)張(zhang)量。實(shi)際上(shang),某些模型可能(neng)需要修改它們的(de)正向傳遞以打包和解包`forward()`的(de)參數。考慮(lv)一下Transformer block的(de)簡化實(shi)現:
class TransformerBlock(nn.Module)
...
def forward(self, hidden, mask):
output = self.compute(hidden, mask)
return output
...
stack = [ TransformerBlock() for _ in range(num_layers) ]
需要對TransformerBlock進(jin)行兩個(ge)修(xiu)改:
1. 參數必須被收集到一個元組中。
2. 還必須從`forward()`返回(hui)`mask`以傳遞給下一層。
這些(xie)修改可以通過一(yi)個(ge)短的子類來完成:
class TransformerBlockPipe(TransformerBlock)
def forward(self, inputs):
hidden, mask = inputs
output = super().forward(hidden, mask)
return (output, mask)
stack = [ TransformerBlockPipe() for _ in range(num_layers) ]
2.3 訓練循環
管(guan)道并行交(jiao)替(ti)執行前向傳遞(di)和(he)(he)反向傳遞(di),因此訓(xun)練(lian)循環不能分為`forward()`、`backward`和(he)(he)`step()`等單(dan)獨的階(jie)段。相反,DeepSpeed的管(guan)道引擎提供了(le)`train_batch()`方法,該(gai)方法推進管(guan)道引擎直(zhi)到消(xiao)耗下一批訓(xun)練(lian)數據并更新(xin)模型(xing)權重。
2.4 數據處理
通常,數(shu)據(ju)并行訓練會使每(mei)個(ge)工作(zuo)器在(zai)每(mei)個(ge)批(pi)次開始時獨立執行IO。但是,在(zai)管道并行環境中,只有(you)第一(yi)個(ge)階段(duan)(duan)使用輸入數(shu)據(ju),只有(you)最后一(yi)個(ge)階段(duan)(duan)使用標簽進(jin)行損失計算。
`注意(yi):管道引擎希望數(shu)(shu)據(ju)加載器返回兩個(ge)項目的元組。第(di)(di)一個(ge)返回項是輸入(ru)批(pi)次數(shu)(shu)據(ju),第(di)(di)二個(ge)項目是用于計算損(sun)失的數(shu)(shu)據(ju)。與之前一樣,輸入(ru)和標簽應為(wei)torch.Tensor類型或張量的元組。`
出于(yu)方便起(qi)見,DeepSpeed管道引擎(qing)可以在向`deepspeed.initialize()`提供數據(ju)集時構(gou)建分布式數據(ju)加(jia)載器。DeepSpeed處理(li)了數據(ju)加(jia)載的其余(yu)復雜性,因此管道訓(xun)練循(xun)環(huan)變(bian)為:
engine, _, _, _ = deepspeed.initialize(
args=args,
model=net,
model_parameters=[p for p in net.parameters() if p.requires_grad],
training_data=cifar_trainset())
for step in range(args.steps):
loss = engine.train_batch()
當(dang)然,DeepSpeed將與您希(xi)望使用(yong)的任何數據(ju)加載器(qi)一起工作。數據(ju)加載器(qi)應(ying)由管(guan)道中的第一個(ge)和(he)最后一個(ge)階段構建。每個(ge)工作器(qi)應(ying)加載`engine.train_micro_batch_size_per_gpu()`大小的微(wei)批次,并且每個(ge)`train_batch()`將查詢`engine.gradient_accumulation_steps()`次。
`請(qing)注意!管道引擎從迭代器(qi)中(zhong)提取(qu)數據而不是對其(qi)進行迭代。在(zai)訓(xun)練批(pi)次(ci)中(zhong)間,數據流不能為空(kong)。每次(ci)調用(yong)train_batch()將(jiang)從數據迭代器(qi)中(zhong)拉(la)取(qu)engine.gradient_accumulation_steps()個(ge)微(wei)批(pi)次(ci)數據。`
DeepSpeed提供了一個方便的類`deepspeed.utils.RepeatingLoader`,它簡單(dan)地包(bao)裝可迭代對象(例如數據(ju)加載器),并在到達結(jie)尾時重新(xin)啟動(dong)它:
train_loader = deepspeed.utils.RepeatingLoader(train_loader)
train_iter = iter(train_loader)
for step in range(args.steps):
loss = engine.train_batch(data_iter=trainiter)
3. 進一步建議
3.1 管道模塊的負載平衡
管道(dao)并(bing)行訓(xun)練的(de)性能(neng)強烈依賴于負(fu)載均衡。DeepSpeed提(ti)供了幾種在GPU之間分配模型的(de)機制。可以使用`PipelineModule`的(de)`partition_method`關鍵字參數設置(zhi)這些策略。以下是DeepSpeed當(dang)前提(ti)供的(de)分區方法:
- `partition_method="parameters" `(默認)將每個管道階段上可訓練參數的數量平衡起來。這在內存受限的環境和當層的大小與計算時間成比例時特別有用。
- `partition_method="type:[regex]"`平衡類名與[regex]匹配的層。正則表達式不區分大小寫。例如,`partition_method="type:transformer"`將平衡每個階段中transformer層的數量。
- `partition_method="uniform" `平衡每個階段中的層數。
3.2 內存高效的模型構建
將一(yi)個(ge) `Sequential `容器(qi)構建并(bing)提供給 `PipelineModule` 是指(zhi)定管道并(bing)行模(mo)型(xing)(xing)的(de)一(yi)種(zhong)方(fang)便的(de)方(fang)式(shi)。然而,對于大型(xing)(xing)模(mo)型(xing)(xing),這種(zhong)方(fang)法會遇到(dao)可(ke)擴(kuo)展性問題(ti),因為每個(ge) worker 都會在 CPU 內(nei)存(cun)中復制整個(ge)模(mo)型(xing)(xing)。例如,一(yi)個(ge)具有 16 個(ge) GPU 的(de)機器(qi)必須擁有與模(mo)型(xing)(xing)大小的(de) 16 倍(bei)相同的(de)本地 CPU 內(nei)存(cun)。
DeepSpeed 提供了(le)一個(ge)` LayerSpec `類,它(ta)延遲(chi)模(mo)塊的(de)構建(jian),直到模(mo)型層次已經被分(fen)配給工作(zuo)節點(dian)。然后每個(ge) worker 只(zhi)分(fen)配它(ta)所(suo)分(fen)配的(de)層次。因此,與前(qian)一段中的(de)示例進行比較(jiao),使(shi)用 `LayerSpec`,具(ju)有 16 個(ge) GPU 的(de)機器將需要(yao)在其 CPU 內存上分(fen)配總共(gong) 1x 模(mo)型大小而(er)不是 16 倍。
某(mou)些(xie)模(mo)型(xing)不能完全表示為管(guan)道(dao)并行(xing)模(mo)型(xing),因(yin)為一些(xie)層在(zai)管(guan)道(dao)內被重用。例如,基于(yu) Transformer 的(de)(de)語言(yan)模(mo)型(xing)通常在(zai)管(guan)道(dao)早期使用嵌(qian)入(ru)(ru)層將詞(ci)匯映射到(dao)隱藏狀態(tai),然(ran)后在(zai)管(guan)道(dao)末(mo)尾使用嵌(qian)入(ru)(ru)將隱藏狀態(tai)映射回詞(ci)匯。如果該(gai)模(mo)型(xing)受到(dao)純管(guan)道(dao)并行(xing)性(xing)的(de)(de)限制,則這(zhe)種嵌(qian)入(ru)(ru)重用將禁止管(guan)道(dao)并行(xing)性(xing)。
3.3 Tied Layers
某些(xie)(xie)模(mo)型(xing)不能完全(quan)表示為管(guan)道并(bing)(bing)行模(mo)型(xing),因為一些(xie)(xie)層(ceng)在管(guan)道內被(bei)重用(yong)。例如(ru),基于 Transformer 的語言模(mo)型(xing)通常(chang)在管(guan)道早期使(shi)用(yong)嵌(qian)(qian)入(ru)(ru)層(ceng)將(jiang)(jiang)(jiang)詞匯映射(she)(she)到隱藏(zang)狀態,然后(hou)在管(guan)道末尾使(shi)用(yong)嵌(qian)(qian)入(ru)(ru)將(jiang)(jiang)(jiang)隱藏(zang)狀態映射(she)(she)回詞匯。如(ru)果該(gai)模(mo)型(xing)受到純管(guan)道并(bing)(bing)行性的限制,則這種嵌(qian)(qian)入(ru)(ru)重用(yong)將(jiang)(jiang)(jiang)禁(jin)止管(guan)道并(bing)(bing)行性。
DeepSpeed 提供(gong)了` TiedLayerSpec`,它是 `LayerSpec` 的擴(kuo)展。`TiedLayerSpec `需要一個額外(wai)的參數:`key`。每次重(zhong)用(yong)(yong)層都使(shi)用(yong)(yong) `TiedLayerSpec `進(jin)行(xing)指定,`key `字段用(yong)(yong)于識別層何處被重(zhong)用(yong)(yong)。
被捆綁的層會在(zai)每個擁有“重(zhong)用”實(shi)例的管(guan)道(dao)階段中復制。然后(hou)訓練將像平常(chang)一(yi)樣進行,但在(zai)所有反向(xiang)傳遞完成(cheng)后(hou),會添加(jia)一(yi)個額外的捆綁梯度(du)的全局歸(gui)約操作。這個全局歸(gui)約確保了捆綁層的權重(zhong)在(zai)管(guan)道(dao)階段之間保持同(tong)步。