Caffe引數交換原始碼分析

2021-09-21 02:35:55 字數 4317 閱讀 8106

對境準備:對於多個gpu而言,一台機器2個gpu,引數交換的流程圖:

引數交換從main()進入train()函式,在train函式中找到對應原始碼為:

1

. . . . .

2if (gpus.size() > 1

) else

因為gpu的個數》1,所以執行sync(solver, null, solver->param())和run()函式,首先會執行p2psync類的建構函式,然後執行run()函式,run函式的**如下:

1

void p2psync::run(const vector&gpus)

10for (int i = 1; i < syncs.size(); ++i)

13 solver_->solve();

14for (int i = 1; i < syncs.size(); ++i)

17 }

在run()函式中,首先會執行compute()函式,該函式的作用是產生gpu pairs,gpu pairs的含義是[parent:child],對於2個gpu而言,gpu pairs為[-1:0],[0:1],預設根gpu的parent是其本身。然後通過乙個for迴圈構建gpu樹,對於2個gpu而言,gpu樹如下圖所示:

接下來呼叫乙個for迴圈為每個gpu開啟乙個執行緒,值得注意的是for迴圈是從i=1開始的,即為每個子gpu單獨開啟乙個執行緒(這裡為gpu1開啟乙個執行緒),也就是呼叫startinternalthread()函式,該函式的**如下:

1

void

internalthread::startinternalthread() . . . . . . .

6 }

該函式接著會執行entry()函式,該函式**如下:

1

void internalthread::entry(int device, caffe::brew mode, int

rand_seed,

2int solver_count, bool

root_solver)

該函式又會去呼叫internalthreadentry()函式,該函式是正式進入迭代運算的入口,**如下:

1

void p2psync::internalthreadentry()

10 solver_->step(solver_->param().max_iter() -initial_iter_);

11 }

gpu1呼叫step()函式,進入迭代過程,見如下原始碼:

1

void solver::step(int

iters)

8const

bool display = param_.display() && iter_ % param_.display() == 0

;9 net_->set_debug_info(display &¶m_.debug_info());

10//

accumulate the loss and gradient

11 dtype loss = 0;12

for (int i = 0; i < param_.iter_size(); ++i)

15 loss /= param_.iter_size();//

loss歸一化

16. . . . . . .

17for (int i = 0; i < callbacks_.size(); ++i)

2021

. . . . . . . . . .

22 ++iter_;23}

24 }

整個step函式的執行如上所示,首先根gpu(gpu0)有整個網路的網路引數,callbacks_.size()指的是gpu樹的parent的個數(在這裡是1),on_start()函式的作用就是把根gpu(gpu0)的網路引數分發到每乙個子gpu(gpu1),gpu1會先進入這個函式,on_start()函式的部分**如下:

1

void p2psync::on_start()

8 . . . . . .

當執行到queue_.pop()時,會呼叫blocking_queue.cpp的pop()方法,pop()方法的內容如下:

1 t blockingqueue::pop(const

string&log_on_wait)

7 sync_->condition_.wait(lock);//

如果queue_為空,就一直阻塞。

8 }

該方法內部有wait()函式,因為此時queue_為空,所以gpu1就會被堵塞,因為gpu0和gpu1是兩個執行緒並行執行,所以gpu0會執行run()函式中的下一步,也就是solver_->solve(),solve()函式的**如下:

1

void solver::solve(const

char*resume_file)

solve()函式會呼叫step()函式進入迭代過程,當gpu0進入on_start()函式後,會把佇列中的gpu0出佇列,同時會啟用被堵塞的gpu1,接下來的on_start()函式**如下:

1

. . . . .2//

update children

3for (int i = children_.size() - 1; i >= 0; i--)

14#endif

15 }

在該部分**中,src指的是gpu0的data(網路引數),dst指的是gpu1的data(網路引數),通過呼叫cudamemcpyasync()函式來放置乙個請求,表示在cudastreamdefault流中執行一次記憶體複製操作,然後呼叫cudastreamsynchronize()等待cudastreamdefault流中的操作完成後實現流的同步。經過這兩個函式後,gpu0完成了把網路引數分發給gpu1,然後children_[i]->queue_.push(this)被執行後,會呼叫block_queue.cpp檔案中的push函式啟用gpu0的子gpu,即gpu1,同時把gpu1壓入佇列,此時佇列中只有gpu1。

1

void blockingqueue::push(const t&t)

此時,多個gpu的引數分發過程已經完成,接下來gpu0和gpu1並行執行step()函式的下一步,即:forwardbackward(),該函式的**如下:

1 dtype forwardbackward(const vector* > &bottom)
該函式的主要作用就是就是計算出loss和梯度diff,然後再接著執行step()函式中的下一步,即:on_gradients_ready()函式,該函式分為兩個部分,第一部分是多個gpu的梯度加和,第二部分是將計算後的梯度傳給根gpu(gpu0)。第一部分的**如下:

1

void p2psync::on_gradients_ready()

第一部分是多個gpu的梯度加和,因為gpu0和gpu1是平行計算的,如果gpu0執行到這裡時,會使佇列中僅有的gpu1出佇列,然後通過呼叫caffe_gpu_add()函式,將乙個gpu的梯度diff直接傳給另乙個gpu,不需要經過cpu通訊,即gpu1把其計算的diff傳給gpu0。如果是gpu1執行到這裡時,因為gpu1沒有子gpu,所以會直接跳過這一部分。第二部分的**如下:

1

if(parent_) else

如果是gpu0的話,會執行else,即caffe_gpu_scal(),該函式把得到的之前計算的梯度diff_和除以gpu的個數,來更新梯度。如果是gpu1的話,會執行if的語句,此時和on_start()函式分析類似,經過cudamemcpyasync()和cudastreamsynchronize()函式操作之後,將gpu1中的梯度傳送給gpu0,第二部分完成。

當神已無能為力,那便是魔渡眾生

caffe原始碼分析 DataTransformer

將datum型別或者cv mat,轉化為caffe的blob,並按照transformation parameter引數對影象做處理,例如scale,mirro等 推斷blob的shape proto定義如下 to the data layer s data message transformati...

caffe原始碼分析 IoU計算

iou pre dict 檢測框與 grou nd t ruth 檢測框的 交集pr edic t檢測框 與gro und trut h檢測框 的的並集 iou frac iou pr edic t檢測框 與gro und trut h檢測框 的的並集 pred ict檢 測框與g roun d t...

Caffe原始碼分析隨筆 二 Blob

blob是caffe中每一層的基本型別。檢視標頭檔案 template class blob protected shared ptr data shared ptr diff shared ptr shape data vector shape int count int capacity blo...