Torch7入门续集(六)----多GPU运行程序 ---- (2018-4-18更新!)

总说

更新: 重写多GPU程序示例, 2018-4-18
先感叹一波,我竟然会时隔一年后, 再更新这个, 本来是不想更新的,错了就错了, 根本没人看, 然而网友的评论说出现了问题, 我想还是更新一波吧, 不说了,真要给自己点个赞.
Torch的多GPU程序其实并不复杂.

最简单的代码:

第一种方法(不推荐)

require 'optim'
require 'nn'
require 'cutorch'
require 'cunn'
require 'torch'
torch.setdefaulttensortype('torch.FloatTensor')

local conv_net = nn.Sequential()
local concat = nn.ConcatTable()
concat:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
concat:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(concat)
local jt = nn.JoinTable(2)
conv_net:add(jt)
conv_net:add(nn.SpatialConvolution(6,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:cuda()  -- 把网络的参数变成cuda类型


num_epochs = 1
batchsize = 64
criterion = nn.MSECriterion():cuda()
input = torch.randn(1000,3,256,256):mul(100):floor():cuda()
target = torch.randn(1000,3,256,256):mul(100):floor():cuda()
num_takes = input:size(1)/batchsize

-- DataParrallelTable是进行
-- 使用DataParallelTable进行多GPU, 1表示在batch维度进行切分
dpt = nn.DataParallelTable(1)

-- 使用第1,2,3,4个GPU, 将相同一个网络, 分别复制到4个GPU上.
for i = 1, 4 do
  cutorch.setDevice(i)
  dpt:add(conv_net:clone():cuda(), i)  
end
print(net)

cutorch.setDevice(1) -- 设置`主`GPU, 同步时, 就是用来将其他GPU参数全部累加到这个GPU上.
parameters, gradParameters = dpt:getParameters()
-- TRAINING:
local timer = torch.Timer()
for i = 1, num_epochs do
  for bth = 1, num_takes do
    local input_bt = input:narrow(1, (bth-1)*batchsize+1, batchsize)
    local target_bt = target:narrow(1, (bth-1)*batchsize+1, batchsize)
    print('Processing epoch: ',i,' , batch: ',bth)
    -- 如果你想要每个batch所需要的时间, 那么
    -- 需要在 这里加上
    -- local timer = torch.Timer()
    -- dpt:syncParameters()

    feval = function(x)
        dpt:zeroGradParameters()
        local output = dpt:forward(input_bt)
        local err = criterion:forward(output, target_bt)
        local gradOutput = criterion:backward(output, target_bt)
        local gradInput = dpt:backward(input_bt, gradOutput)
        return err, gradParameters
    end
    optim.sgd(feval, parameters, optimState)
    dpt:syncParameters() -- 必须加入这个, 同步网络参数,才能进行下一步更新.
    -- 如果你想要每个batch所需要的时间, 那么
    -- 需要在 这里加上
    -- print('Elapse time: '..timer:time().real..' seconds')
  end
end
print('Totally elapse time: '..timer:time().real..' seconds')

因为这个方法是比较老的方法, 所以思想还是比较Naive:
因为采用的是同步模式训练方法, 所以 是先将一个网络在主GPU上构建出来, 然后复制到其他的GPU上, 然后更新. 每个GPU单独对拿到的一个batch的一小部分(batchsize/num_gpus)进行前向, 再反向. 得到的梯度要进行算平均, 即batch的平均梯度. 然后更新GPU的梯度(optim.sgd是对GPU进行操作), 再 net:syncParameters()一下, 这个是用来同步GPU的梯度, 就是将GPU的新的参数, 赋值给所有的GPU上, 使得所有GPU上的参数保持一致(这也就是为什么如果要计算一个batch所花的时间, 要进行syncParameters()一下,). 然后再用这个平均梯度, 更新每个GPU上的参数.

输出是

DataParallelTable: 4 x nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output]
  (1): nn.ConcatTable {
    input
      |`-> (1): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
       `-> (2): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
       ... -> output
  }
  (2): nn.JoinTable
  (3): nn.SpatialConvolution(6 -> 3, 3x3, 1,1, 1,1) without bias
  (4): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (5): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (6): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (7): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (8): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (9): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (10): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
}
Processing epoch:       1        , batch:       1
Processing epoch:       1        , batch:       2
Processing epoch:       1        , batch:       3
Processing epoch:       1        , batch:       4
Processing epoch:       1        , batch:       5
Processing epoch:       1        , batch:       6
Processing epoch:       1        , batch:       7
Processing epoch:       1        , batch:       8
Processing epoch:       1        , batch:       9
Processing epoch:       1        , batch:       10
Processing epoch:       1        , batch:       11
Processing epoch:       1        , batch:       12
Processing epoch:       1        , batch:       13
Processing epoch:       1        , batch:       14
Processing epoch:       1        , batch:       15
Totally elapse time: 15.530827045441 seconds

可以看到 DataParallelTable: 4 x表示有4块, 网络是什么什么. 我们再试试 2个GPU

2个GPU的输出

DataParallelTable: 2 x nn.Sequential {
  [input -> (1) -> (2) -> (3) -> (4) -> (5) -> (6) -> (7) -> (8) -> (9) -> (10) -> output]
  (1): nn.ConcatTable {
    input
      |`-> (1): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
       `-> (2): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
       ... -> output
  }
  (2): nn.JoinTable
  (3): nn.SpatialConvolution(6 -> 3, 3x3, 1,1, 1,1) without bias
  (4): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (5): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (6): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (7): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (8): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (9): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
  (10): nn.SpatialConvolution(3 -> 3, 3x3, 1,1, 1,1) without bias
}
Processing epoch:       1        , batch:       1
Processing epoch:       1        , batch:       2
Processing epoch:       1        , batch:       3
Processing epoch:       1        , batch:       4
Processing epoch:       1        , batch:       5
Processing epoch:       1        , batch:       6
Processing epoch:       1        , batch:       7
Processing epoch:       1        , batch:       8
Processing epoch:       1        , batch:       9
Processing epoch:       1        , batch:       10
Processing epoch:       1        , batch:       11
Processing epoch:       1        , batch:       12
Processing epoch:       1        , batch:       13
Processing epoch:       1        , batch:       14
Processing epoch:       1        , batch:       15
Totally elapse time: 26.187878131866 seconds

可以看到,明显慢了很多. 另外一点, batchsize是平分的, 比如batchsize=64, 2个GPU, 那么每个32.

第二种方法(强力推荐)

第一种方法是很早的写法了, 可以参考 : https://github.com/torch/cunn/pull/73
比较繁琐, 而且还要自己在每个batch训练好后, 进行 dpt:syncParameters()一下. 而且还有其他的弊端.
后来, 他们对上面的方法进行了改进, https://github.com/torch/cunn/pull/189 简单来说就是把 dpt:syncParameters()可以不用写了. dpt:syncParameters()被嵌入到 updateOuput中. 程序会自动判断, 如果没有显示调用 dpt:syncParameters() , 会自动进行在训练下一批之前, 将参数同步到所有GPU上.

require 'optim'
require 'nn'
require 'cutorch'
require 'cunn'
require 'torch'
torch.setdefaulttensortype('torch.FloatTensor')

local conv_net = nn.Sequential()
local concat = nn.ConcatTable()
concat:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
concat:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(concat)
local jt = nn.JoinTable(2)
conv_net:add(jt)
conv_net:add(nn.SpatialConvolution(6,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:add(nn.SpatialConvolution(3,3,3,3,1,1,1,1):noBias())
conv_net:cuda()

local optimState = {
    learningRate = 1,
    momentum = 0,
}

num_epochs = 1
batchsize = 64
criterion = nn.MSECriterion():cuda()
input = torch.randn(1000,3,256,256):mul(100):floor():cuda()
target = torch.randn(1000,3,256,256):mul(100):floor():cuda()
num_takes = input:size(1)/batchsize

dpt = nn.DataParallelTable(1)

-- -- 使用DataParallelTable进行多GPU, 1表示在batch维度进行切分
-- for i = 1, 2 do
--   cutorch.setDevice(i)
--   net:add(conv_net:clone():cuda(), i)  -- 使用第1,2个GPU
-- -- end

-- print(net)

-- cutorch.setDevice(1)
-- parameters, gradParameters = net:getParameters()

-- 没必要设置主GP
dpt:add(conv_net, {1,2})
print(dpt)
-- TRAINING:
local timer = torch.Timer()
for i = 1, num_epochs do
  for bth = 1, num_takes do
    local input_bt = input:narrow(1, (bth-1)*batchsize+1, batchsize)
    local target_bt = target:narrow(1, (bth-1)*batchsize+1, batchsize)
    print('Processing epoch: ',i,' , batch: ',bth)
   -- net:syncParameters()

    local function feval(net)
        local params, gradParams = net:getParameters()
        return params, function(x)
            net:zeroGradParameters()
            local output = net:forward(input_bt)
            local err = criterion:forward(output, target_bt)
            local gradOutput = criterion:backward(output, target_bt)
            local gradInput = net:backward(input_bt, gradOutput)
            return err, gradParams
        end
    end

    local params_dpt, feval_dpt = feval(dpt)

    optim.sgd(feval_dpt, params_dpt, optimState)
    -- net:syncParameters() -- 这个可以省略了

  end
end
print('Totally elapse time: '..timer:time().real..' seconds')

再进行加速

由于上面的写法都是用一个线程, 我们可以使得每个GPU分别用单独的线程进行操作

示例:

gpus = torch.range(1, opt.nGPU):totable() 
dpt = nn.DataParallelTable(1, true)
    :add(model,gpus)
    :threads(function()
    require 'nngraph'  -- 如果用nngraph, 加上这行
        local cudnn = require 'cudnn'
        cudnn.fastest, cudnn.benchmark = true, true
        end)
dpt.gradInput = nil  -- 这个是为了省内存
model = dpt:cuda()

最后

其他方法, 比如 如何保存 dpt的模型啊等等,
参考:https://github.com/soumith/imagenet-multiGPU.torch

最后还是那句话“退torch,保平安!”

你可能感兴趣的:(Lua,torch,Torch7入门教程)