對境準備:對於多個gpu而言,一台機器2個gpu,引數交換的流程圖:
引數交換從main()進入train()函式,在train函式中找到對應原始碼為:
1因為gpu的個數》1,所以執行sync(solver, null, solver->param())和run()函式,首先會執行p2psync類的建構函式,然後執行run()函式,run函式的**如下:. . . . .
2if (gpus.size() > 1
) else
1在run()函式中,首先會執行compute()函式,該函式的作用是產生gpu pairs,gpu pairs的含義是[parent:child],對於2個gpu而言,gpu pairs為[-1:0],[0:1],預設根gpu的parent是其本身。然後通過乙個for迴圈構建gpu樹,對於2個gpu而言,gpu樹如下圖所示: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 }
接下來呼叫乙個for迴圈為每個gpu開啟乙個執行緒,值得注意的是for迴圈是從i=1開始的,即為每個子gpu單獨開啟乙個執行緒(這裡為gpu1開啟乙個執行緒),也就是呼叫startinternalthread()函式,該函式的**如下:
1該函式接著會執行entry()函式,該函式**如下:void
internalthread::startinternalthread() . . . . . . .
6 }
1該函式又會去呼叫internalthreadentry()函式,該函式是正式進入迭代運算的入口,**如下:void internalthread::entry(int device, caffe::brew mode, int
rand_seed,
2int solver_count, bool
root_solver)
1gpu1呼叫step()函式,進入迭代過程,見如下原始碼:void p2psync::internalthreadentry()
10 solver_->step(solver_->param().max_iter() -initial_iter_);
11 }
1整個step函式的執行如上所示,首先根gpu(gpu0)有整個網路的網路引數,callbacks_.size()指的是gpu樹的parent的個數(在這裡是1),on_start()函式的作用就是把根gpu(gpu0)的網路引數分發到每乙個子gpu(gpu1),gpu1會先進入這個函式,on_start()函式的部分**如下: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 }
1當執行到queue_.pop()時,會呼叫blocking_queue.cpp的pop()方法,pop()方法的內容如下:void p2psync::on_start()
8 . . . . . .
1 t blockingqueue::pop(const該方法內部有wait()函式,因為此時queue_為空,所以gpu1就會被堵塞,因為gpu0和gpu1是兩個執行緒並行執行,所以gpu0會執行run()函式中的下一步,也就是solver_->solve(),solve()函式的**如下:string&log_on_wait)
7 sync_->condition_.wait(lock);//
如果queue_為空,就一直阻塞。
8 }
1solve()函式會呼叫step()函式進入迭代過程,當gpu0進入on_start()函式後,會把佇列中的gpu0出佇列,同時會啟用被堵塞的gpu1,接下來的on_start()函式**如下:void solver::solve(const
char*resume_file)
1在該部分**中,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。. . . . .2//
update children
3for (int i = children_.size() - 1; i >= 0; i--)
14#endif
15 }
1此時,多個gpu的引數分發過程已經完成,接下來gpu0和gpu1並行執行step()函式的下一步,即:forwardbackward(),該函式的**如下:void blockingqueue::push(const t&t)
1 dtype forwardbackward(const vector* > &bottom)該函式的主要作用就是就是計算出loss和梯度diff,然後再接著執行step()函式中的下一步,即:on_gradients_ready()函式,該函式分為兩個部分,第一部分是多個gpu的梯度加和,第二部分是將計算後的梯度傳給根gpu(gpu0)。第一部分的**如下:
1第一部分是多個gpu的梯度加和,因為gpu0和gpu1是平行計算的,如果gpu0執行到這裡時,會使佇列中僅有的gpu1出佇列,然後通過呼叫caffe_gpu_add()函式,將乙個gpu的梯度diff直接傳給另乙個gpu,不需要經過cpu通訊,即gpu1把其計算的diff傳給gpu0。如果是gpu1執行到這裡時,因為gpu1沒有子gpu,所以會直接跳過這一部分。第二部分的**如下:void p2psync::on_gradients_ready()
1如果是gpu0的話,會執行else,即caffe_gpu_scal(),該函式把得到的之前計算的梯度diff_和除以gpu的個數,來更新梯度。如果是gpu1的話,會執行if的語句,此時和on_start()函式分析類似,經過cudamemcpyasync()和cudastreamsynchronize()函式操作之後,將gpu1中的梯度傳送給gpu0,第二部分完成。if(parent_) else
當神已無能為力,那便是魔渡眾生
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...