您的位置:首頁>遊戲>正文

如何為TensorFlow和PyTorch自動選擇空閒GPU,解決搶卡爭端

雷鋒網按:本文作者天清, 原文載於其知乎專欄世界那麼大我想寫代碼, 雷鋒網獲其授權發佈。

項目位址:QuantumLiu/tf_gpu_manager

***

更新:支援pytorch

***

使用

git clone https://github.com/QuantumLiu/tf_gpu_manager

把manager.py放到你訓練的目錄就行。

直接使用with gm.auto_choice自動選擇設備進行接下來代碼塊的操作。

import tensorflow as tf

from manager import GPUManager

from keras.layers LSTM

gm=GPUManager

with gm.auto_choice:

x=tf.placeholder(tf.float32,shape=(None,20,64))

y=LSTM(32)(x)

背景

隨著深度學習技術快速的發展, 深度學習任務的資料和計算規模也越來越大, 想要做出個像樣的work, 沒有一台powerful的GPU工作站是萬萬不能的。

除了要求單卡性能強大, GPU數量多也很重要。

因為以下幾點原因, 多GPU工作站已經成了各大實驗室的標配:

一般來說, 一個深度學習專案需要一個實驗室或者小組的多人合作完成,

要共用一台或幾台工作站。 一個host多個GPU比較方便。

實驗需要試多組參數或者對比試驗。 多GPU並行跑省時間。

模型計算量大, 需要將模型不同分配在多個GPU上計算。

現在, Tensorflow、pytorch等主流深度學習框架都支持多GPU訓練。

比如Tensorflow, 在 tensorflowpython ramework中定義了device函數, 返回一個用來執行操作的GPU設備的context manager物件。

def device(device_name_or_function):

"""Wrapper for `Graph.device` using the default graph.

See

@{tf.Graph.device}

for more details.

Args:

device_name_or_function: The device name or function to use in the context.

Returns:

A context manager that specifies the default device to use for newly created ops.

"""

return get_default_graph.device(device_name_or_function)

在我們的訓練腳本中使用with語句就可以指定接下來的操作在某個GPU上進行。

with tf.device('/gpu:2'):

a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')

b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')

c = tf.matmul(a, b)

那麼問題來了:

在寫訓練腳本時怎麼知道哪個GPU是空閒可用的?

同組的人做實驗和我衝突怎麼辦?

將來某個時刻運行這個腳本的時候是不是還要根據情況修改?

同行用我的代碼複現實驗, GPU配置環境不一樣, 他們甚至可能沒有GPU, 又要改代碼?

當然, 上道兒的開發者都知道nvidia-smi可以查詢顯卡資訊,

查看GPU顯存、溫度、功率使用, 然後選擇合適的GPU。

每次訓練前執行這個命令, 再與良好團隊保持良好的溝通可以解決上述1、2兩個問題, 但是3、4兩個問題還是不好解決。

而且經常和師兄弟、同事搶卡豈不是影響效率?

我們需要一種解決方案, 能夠實現不修改腳本、不需要和組員溝通,

自動選擇空閒GPU設備。

實現如何高效獲取GPU狀態資訊

nvidia-smi是一個由NVIDIA官方提供的GPU狀態管理、監控命令列軟體。 和其他命令列軟體一樣, nvidia-smi也有許多argument。

通過閱讀文檔, 以及學習老司機的經驗, 我們知道--query-gpu這個option可以指定查詢GPU狀態資訊, 並返回格式化資訊。

通過執行命令:

nvidia-smi --help-query-gpu

我們得到了所有支持的查詢參數(太多了不一一枚舉)

最有用的參數老司機給我們總結出來了:

還有我自己查到的index, name, power.draw, power.limit

於是我們有了基本思路, 用os.popen執行相關命令, 解析返回文本資訊。

def parse(line,qargs):

'''

line:

a line of text

qargs:

query arguments

return:

a dict of gpu infos

Pasing a line of csv format text returned by nvidia-smi

解析一行nvidia-smi返回的csv格式文本

'''

numberic_args=['memory.free','memory.total','power.draw','power.limit']#可計數的參數

power_manage_enable=lambdav:(not'Not Support'inv)#lambda運算式, 顯卡是否滋瓷power management(筆記本可能不滋瓷)

to_numberic=lambdav:float(v.upper.strip.replace('MIB','').replace('W',''))#帶單位字串去掉單位

process=lambdak,v:((int(to_numberic(v))ifpower_manage_enable(v)else1)ifkinnumberic_argselsev.strip)

return{k:process(k,v)fork,vinzip(qargs,line.strip.split(','))}

def query_gpu(qargs=[]):

'''

qargs:

query arguments

return:

a list of dict

Querying GPUs infos

查詢GPU資訊

'''

qargs=['index','gpu_name','memory.free','memory.total','power.draw','power.limit']+qargs

cmd='nvidia-smi --query-gpu={} --format=csv,noheader'.format(','.join(qargs))

results=os.popen(cmd).readlines

return [parse(line,qargs) for line in results]

如何衡量GPU空閒度

現在已經能獲取GPU狀態了,

但是要怎麼衡量GPU空閒度並排序呢?

深度學習領域, GPU空閒度可以主要用兩個指標衡量:顯存空閒和功率空閒。

顯存佔用又分絕對空間佔用和佔用比例。

最後, 我們用三個指標衡量:

顯存剩餘空間

顯存剩餘比例

當前功率/額定功率

在之前, 我們已經把所有GPU的資訊存成了一個list, 每個list是gpu資訊的字典。

我們使用內置函數sorted來對可使用GPU進行排序。

如, 按顯存使用:

def_sort_by_memory(self,gpus,by_size=False):

if by_size:

print('Sorted by free memory size')

return sorted(gpus,key=lambda d:d['memory.free'],reverse=True)

else:

print('Sorted by free memory rate')

return sorted(gpus,key=lambda d:float(d['memory.free']) / d['memory.total'],reverse=True)

完整腳本

我們定義一個GPUManager類, 在他的實例物件的存活週期裡會更新GPU狀態、記錄已被分配的GPU。

產生實體後, 通過調用auto_choice方法直接返回一個tf.device對象。

同時, 考慮到使用者電腦可能沒有GPU, 加入異常處理機制。

def check_gpus: '''

GPU available check

reference : http://feisky.xyz/machine-learning/tensorflow/gpu_list.html

'''

all_gpus = [x.name for x in device_lib.list_local_devices() if x.device_type == 'GPU']

if not all_gpus:

print('This script could only be used to manage NVIDIA GPUs,but no GPU found in your device')

return False

elif not 'NVIDIA System Management' in os.popen('nvidia-smi -h').read:

print("'nvidia-smi' tool not found.")

return False

return True

if check_gpus: def parse(line,qargs):

'''

line:

a line of text

qargs:

query arguments

return:

a dict of gpu infos

Pasing a line of csv format text returned by nvidia-smi

解析一行nvidia-smi返回的csv格式文本

'''

numberic_args = ['memory.free', 'memory.total', 'power.draw', 'power.limit']#可計數的參數

power_manage_enable=lambda v:(not 'Not Support' in v)#lambda運算式, 顯卡是否滋瓷power management(筆記本可能不滋瓷)

to_numberic=lambda v:float(v.upper.strip.replace('MIB','').replace('W',''))#帶單位字串去掉單位

process = lambda k,v:((int(to_numberic(v)) if power_manage_enable(v) else 1) if k in numberic_args else v.strip)

return {k:process(k,v) for k,v in zip(qargs,line.strip.split(','))}

def query_gpu(qargs=[]) :

'''

qargs:

query arguments

return:

a list of dict

Querying GPUs infos

查詢GPU資訊

'''

qargs =['index','gpu_name', 'memory.free', 'memory.total', 'power.draw', 'power.limit']+ qargs

cmd = 'nvidia-smi --query-gpu={} --format=csv,noheader'.format(','.join(qargs))

results = os.popen(cmd).readlines

return [parse(line,qargs) for line in results]

def by_power(d): '''

helper function fo sorting gpus by power

'''

power_infos=(d['power.draw'],d['power.limit'])

if any(v==1 for v in power_infos):

print('Power management unable for GPU {}'.format(d['index']))

return 1

return float(d['power.draw'])/d['power.limit']

class GPUManager: '''

qargs:

query arguments

A manager which can list all available GPU devices and sort them and choice the most free one.Unspecified ones pref.

GPU裝置管理員, 考慮列舉出所有可用GPU設備, 並加以排序, 自動選出最空閒的設備。 在一個GPUManager物件內會記錄每個GPU是否已被指定, 優先選擇未指定的GPU。

'''

def __init__(self,qargs=[]):

'''

'''

self.qargs=qargs

self.gpus=query_gpu(qargs)

for gpu in self.gpus:

gpu['specified']=False

self.gpu_num=len(self.gpus)

def _sort_by_memory(self,gpus,by_size=False):

if by_size:

print('Sorted by free memory size')

return sorted(gpus,key=lambda d:d['memory.free'],reverse=True)

else:

print('Sorted by free memory rate')

return sorted(gpus,key=lambda d:float(d['memory.free'])/ d['memory.total'],reverse=True)

def _sort_by_power(self,gpus): return sorted(gpus,key=by_power)

def _sort_by_custom(self,gpus,key,reverse=False,qargs=[]):

if isinstance(key,str) and (key in qargs):

return sorted(gpus,key=lambda d:d[key],reverse=reverse)

if isinstance(key,type(lambda a:a)):

return sorted(gpus,key=key,reverse=reverse)

raise ValueError("The argument 'key' must be a function or a key in query args,please read the documention of nvidia-smi")

def auto_choice(self,mode=0): '''

mode:

0:(default)sorted by free memory size

return:

a TF device object

Auto choice the freest GPU device,not specified

ones

自動選擇最空閒GPU

'''

for old_infos,new_infos in zip(self.gpus,query_gpu(self.qargs)):

old_infos.update(new_infos)

unspecified_gpus=[gpu for gpu in self.gpus if not gpu['specified']] or self.gpus

if mode==0:

print('Choosing the GPU device has largest free memory...')

chosen_gpu=self._sort_by_memory(unspecified_gpus,True)[0]

elif mode==1:

print('Choosing the GPU device has highest free memory rate...')

chosen_gpu=self._sort_by_power(unspecified_gpus)[0]

elif mode==2:

print('Choosing the GPU device by power...')

chosen_gpu=self._sort_by_power(unspecified_gpus)[0]

else:

print('Given an unaviliable mode,will be chosen by memory')

chosen_gpu=self._sort_by_memory(unspecified_gpus)[0]

chosen_gpu['specified']=True

index=chosen_gpu['index']

print('Using GPU {i}:{info}'.format(i=index,info=''.join([str(k)+':'+str(v) for k,v in chosen_gpu.items()])))

return tf.device('/gpu:{}'.format(index))

else:

raise ImportError('GPU available check failed'

Next Article
喜欢就按个赞吧!!!
点击关闭提示