序言
第1章 并行和分布式計算介紹
第2章 異步編程
第3章 Python的并行計算
第4章 Celery分布式應用
第5章 云平臺部署Python
第6章 超級計算機群使用Python
第7章 測試和調(diào)試分布式應用
第8章 繼續(xù)學習
本章,我們學習另一種部署分布式Python應用的的方法。即使用高性能計算機(HPC)群(也叫作超級計算機)钮莲,它們通常價值數(shù)百萬美元(或歐元)招拙,占地龐大亚铁。
真正的HPC群往往位于大學和國家實驗室棚愤,創(chuàng)業(yè)公司和小公司因為資金難以運作骏庸。它們都是系統(tǒng)巨大髓涯,有上萬顆CPU袒啼、數(shù)千臺機器。
經(jīng)常超算中心的集群規(guī)模通常取決于電量供應纬纪。使用幾兆瓦的HPC系統(tǒng)很常見蚓再。例如,我使用過有160000核包各、7000節(jié)點的機群摘仅,它的功率是4兆瓦!
想在HPC群運行Python的開發(fā)者和科學家可以在本章學到有用的東西问畅。不使用HPC群的讀者娃属,也可以學到一些有用的工具。
典型的HPC群
HPC系統(tǒng)有多種形式和規(guī)模护姆,然而矾端,它們有一些相同點。它們是勻質(zhì)的卵皂,大量相同的须床、裝在架子上的計算機,處于同一個房間內(nèi)渐裂,通過高速網(wǎng)絡相連豺旬。有時,在升級的時候柒凉,HPC群會被分成兩個運行體系族阅。此時,要特別注意規(guī)劃代碼膝捞,以應對兩個部分的性能差異坦刀。
集群中的大部分機器(稱作節(jié)點),運行著相同的系統(tǒng)和相同的軟件包,只運行計算任務鲤遥。用戶不能直接使用這些機器沐寺。
少部分節(jié)點的算力不如計算節(jié)強大,但是允許用戶登錄盖奈。它們稱作服務節(jié)點(或登錄節(jié)點或頭節(jié)點)混坞,只運行用戶腳本、編譯文件钢坦、任務管理軟件究孕。用戶通常登錄這些節(jié)點,以訪問機群爹凹。
另一些節(jié)點厨诸,介于服務節(jié)點和計算節(jié)點之間,它們運行著全套計算節(jié)點的操作系統(tǒng)禾酱,但是由多個用戶共享微酬,而純粹的計算節(jié)點的每個核只運行一個線程。
這些節(jié)點是用來運行小型的序列化任務颤陶,而不需要計算節(jié)點的全部資源(安裝應用和清理)得封。例如在Cray系統(tǒng)上,這些節(jié)點稱作Multiple Application, Multiple User (MAMU)節(jié)點指郁。
下圖是NASA的2004 Columbia超級計算機忙上,它有10240個處理器伏穆,具有一定代表性:
如何在HPC群上運行代碼呢奖恰?通常是在服務節(jié)點登錄,使用任務規(guī)劃器(job scheduler)故爵。任務規(guī)劃器是一個中間件腰懂,給它一些代碼梗逮,它就可以尋找一些計算節(jié)點運行代碼。
如果此時沒有可用的硬件資源绣溜,代碼就會在一個隊列中等待慷彤,直到有可用的資源。等待時間可能很長怖喻,對于短代碼底哗,等待時間可能比運行時間還長。
HPC系統(tǒng)使用任務規(guī)劃器锚沸,視為了確保各部門和項目可以公平使用跋选,最大化利用機群。
商用和開源的規(guī)劃器有很多哗蜈。最常用的是PBS和它的衍生品(例如Torque和PBS Pro)前标,HTCondor坠韩,LoadLeveler,SLURM炼列、Grid Engine和LSF只搁。這里,我們簡短介紹下其中兩個:HTCondor和PBS Pro俭尖。
任務規(guī)劃器
如前所述氢惋,你不能直接在HPC群上運行代碼,你必須將任務請求提交給任務規(guī)劃器目溉。任務規(guī)劃器會分配算力資源,在分配的節(jié)點上運行應用菱农。
這種間接的方法會造成額外的開銷缭付,但可以保證每個用戶都能公平的分享使用計算機群,同時任務是有優(yōu)先級的循未,大多數(shù)處理器都處于忙碌狀態(tài)陷猫。
下圖展示了任務規(guī)劃器的基本組件,和任務提交到執(zhí)行的事件順序:
首先的妖,先來看一些定義:
- 任務:這是應用的元數(shù)據(jù)绣檬,例如它的可執(zhí)行文件、輸入和輸出嫂粟、它的硬件和軟件需求娇未,它的執(zhí)行環(huán)境,等等星虹;
- 機器:這是最小的任務執(zhí)行硬件零抬。取決于集群的配置,它可能是一部分節(jié)點(例如宽涌,一臺多核電腦的一個核心)和整個節(jié)點平夜。
從概念層面,任務規(guī)劃器的主要部分有:
- 資源管理器
- 一個或多個任務隊列
- 協(xié)調(diào)器
為了提交一個任務請求到任務規(guī)劃器卸亮,需要編寫元數(shù)據(jù)對象忽妒,它描述了我們想運行的內(nèi)容,運行的方式和位置兼贸。它往往是一個特殊格式的文本文件段直,后面有一個例子。
然后溶诞,用戶使用命令行或庫提交任務描述文件(上圖中的步驟1)到任務規(guī)劃器坷牛。這些任務先被分配到一個或多個隊列(例如,一個隊列負責高優(yōu)先級任務很澄,一個負責低優(yōu)先級任務京闰,一個負責小任務)颜及。
同時,資源管理器保持監(jiān)督(步驟2)所有計算節(jié)點蹂楣,以確定哪臺空閑哪臺繁忙俏站。它還監(jiān)督著正在運行的任務的優(yōu)先級,在必要時可以釋放一些空間給高優(yōu)先級的任務痊土。另外肄扎,它還監(jiān)督著每臺機器的性能指標,能運行什么樣的任務(例如赁酝,有的機器只能讓特定用戶使用)犯祠。
另外一個守護進程,蕭條期酌呆,持續(xù)監(jiān)督著任務隊列的閑置任務(步驟2)衡载,并將它們分配給何時的機器(步驟3),其間要考慮用戶的優(yōu)先級隙袁、任務優(yōu)先級痰娱、任務需求和偏好、和機器的性能和偏好菩收。如果在這一步(稱作協(xié)調(diào)循環(huán))沒有可用的資源來運行任務梨睁,任務就保存在隊列中。
一旦指派了運行任務的資源娜饵,規(guī)劃器會在分配的機器上運行可執(zhí)行文件(步驟4)坡贺。有的規(guī)劃器(例如HTCondor)會復制可執(zhí)行文件,向執(zhí)行機器發(fā)送文件箱舞。如果不是這樣拴念,就必須讓代碼和數(shù)據(jù)是在共享式文件系統(tǒng),或是復制到機器上褐缠。
規(guī)劃器(通常使用監(jiān)督進程)監(jiān)督所有的運行任務政鼠,如果任務失敗則重啟任務。如果需要的話队魏,還可以發(fā)送任務成功或失敗的email通知郵件公般。
大多數(shù)系統(tǒng)支持任務間依賴,只有達到一定條件時(比如胡桨,新的卷)官帘,任務才能執(zhí)行。
使用HTCondor運行Python任務
這部分設定是用HTCondor任務規(guī)劃器昧谊,接入機群刽虹。安裝HTCondor不難(參考管理文檔https://research.cs.wisc.edu/htcondor/manual/),這里就不介紹了呢诬。
HTCondor有一套命令行工具涌哲,可以用來向機群提交任務(condor_submit
)胖缤,查看提交任務的狀態(tài)(condor_q
),銷毀一個任務(condor_rm
)阀圾,查看所有機器的狀態(tài)(condor_status
)哪廓。還有許多其它工具,總數(shù)超過60初烘。但是涡真,我們這里只關注主要的四個。
另一種與HTCondor機群交互的方法是使用Distributed Resource Management Application API (DRMAA)肾筐,它內(nèi)置于多數(shù)HTCondor安裝包哆料,被打包成一個共享庫(例如,Linux上的libdrmma.so
)吗铐。
DRMAA有任務規(guī)劃器的大部分功能东亦,所以原則上,相同的代碼還可以用來提交抓歼、監(jiān)督和控制機群和規(guī)劃器的任務讥此。Python的drmaa
模塊(通過pip install drmaa
安裝)拢锹,提供了DRMAA的功能谣妻,包括HTCondor和PBS的功能。
我們關注的是命令行工具卒稳,如何用命令行工具運行代碼蹋半。我們先創(chuàng)建一個文本文件(稱作任務文件或提交文件),描述任務的內(nèi)容充坑。
打開文本編輯器减江,創(chuàng)建一個新文件。任務文件的名字不重要捻爷。我們現(xiàn)在要做的是在機群上運行下面的代碼:
$ python3.5 –c "print('Hello, HTCondor!')"
任務文件(htcondor/simple/simple.job
)的代碼如下:
# Simple Condor job file
# There is no requirement or standard on job file extensions.
# Format is key = value
# keys and values are case insensitive, with the exception of
# paths and file names (depending on the file system).
# Usage: shell> condor_submit simple.job
# Universe is the execution environment for our jobs
# vanilla is the one for shell scripts etc.
Universe = vanilla
# Executable is the path to the executable to run
Executable = /usr/local/bin/python3.5
# The arguments string is passed to the Executable
# The entire string is enclosed in double-quotes
# Arguments with spaces are in single quotes
# Single & double quotes are escaped by repeating them
Arguments = "-c 'print(''Hello, HTCondor!'')'"
# Output is the file where STDOUT will be redirected to
Output = simple_stdout.txt
# Error is the file where STDERR will be redirected to
Error = simple_stderr.txt
# Log is the HTCondor log, not the log for our app
Log = simple.log
# Queue tells HTCondor to enqueue our job
Queue
這段代碼很簡單辈灼。
讓人疑惑的可能是Output
指令,它指向文件進行STDOUT
重定向也榄,而不是執(zhí)行代碼的結果輸出。
另一個會讓人疑惑的是Log
指令降宅,它不知想應用的日志文件囚霸,而是任務專門的HTCondor日志腰根。指令Arguments
句法特殊,也要注意下额嘿。
我們可以用condor_submit
提交任務,如下圖所示岩睁,提交任務之后钞脂,立即用condor_q
查看狀態(tài):
HTCondor給任務分配了一個數(shù)字識別符,形式是cluster id.process id
(這里冰啃,進程ID專屬于HTCondor刘莹,與Unix進程ID不完全相同)。因為可以向一個任務文件提交多個任務(可以通過Queue
命令扇调,例如Queue 5000
抢肛,可以啟動5000個任務的實例),HTCondor會將其當做集群處理捡絮。
每個集群都有一個唯一的識別符福稳,集群中的每個進程都有一個0到N-1之間的識別符,N是集群的總進程數(shù)(任務實例的數(shù)量)的圆。我們的例子中,只提交一個任務季俩,它的識別符是60.0梅掠。
注意:嚴格的講,前面的任務識別符只是在任務隊列/提交奇跡中是唯一的赂韵,在整個集群不是唯一的挠蛉。唯一的是
GlobalJobId
祭示,它是一連串事件的ID谴古,包括主機名、集群ID汇陆、進程ID和任務提交的時間戳≡母可以用condor_q -log
顯示GlobalJobId
,和其它內(nèi)部參數(shù)教寂。
取決于HTCondor的配置,以及機群的繁忙程度导梆,任務可以立即運行迂烁,或是在隊列中等待。我們可以用condor_q
查詢狀態(tài)藏斩,idle
(狀態(tài)I)址芯,running
(狀態(tài)R)窜觉,suspended
(狀態(tài)H),killed
(狀態(tài)X)旬陡。最近添加了兩個新的狀態(tài):in the process of transferring data to the execute node
(<)和transferring data back to the submit host
(>)语婴。
如果一切正常,任務會在隊列中等待一段時間匿醒,然后狀態(tài)變?yōu)檫\行缠导,最后退出(成功或出現(xiàn)錯誤),從隊列消失憋他。
一旦任務完成,查看當前目錄镀娶,我們可以看到三個新文件:simple.log
揪罕,simple_stderr.txt
和simple_stdout.txt
。它們是任務的日志文件忍些,任務的標準錯誤坎怪,和標準輸出流搅窿。
日志文件有許多有用的信息,包括任務提交的時間和從哪臺機器提交的闹司,在隊列中等待的時間,運行的時間和機器沐飘,退出代碼和利用的資源。
我們的Python任務退出狀態(tài)是0(意味成功)耐朴,在STDERR
上沒有輸出(即simple_stderr.txt
是空的)筛峭,然后向STDOUT
寫入Hello,HTCondor镰吵!
(即simple_stdout.txt
)挂签。如果不是這樣饵婆,就要進行調(diào)試。
現(xiàn)在提交一個簡單的Python文件谓传。新的任務文件很相似,我們只需更改Executable
和Arguments
紧卒。我們還要傳遞一些環(huán)境變量給任務诗祸,提交100個實例直颅。
創(chuàng)建一個新任務文件(htcondor/script/script.job
),代碼如下:
# Simple Condor job file
# There is no requirement or standard on job file extensions.
# Format is key = value
# keys and values are case insensitive, with the exception of
# paths and file names (depending on the file system).
# Usage: shell> condor_submit script.job
# Universe is the execution environment for our jobs
# vanilla is the one for shell scripts etc.
Universe = vanilla
# Executable is the path to the executable to run
Executable = test.py
# The arguments string is passed to the Executable
# The entire string is enclosed in double-quotes
# Arguments with spaces are in single quotes
# Single & double quotes are escaped by repeating them
Arguments = "--clusterid=$(Cluster) --processid=$(Process)"
# We can specify environment variables for our jobs as
# by default jobs execute in a very restricted environment
Environment = "MYVAR1=foo MYVAR2=bar"
# We can also pass our entire environment to the job
# By default this is not the case (i.e. GetEnv is False)
GetEnv = True
# Output is the file where STDOUT will be redirected to
# We will have one file per process otherwise each
# process will overwrite the same file.
Output = script_stdout.$(Cluster).$(Process).txt
# Error is the file where STDERR will be redirected to
Error = script_stderr.$(Cluster).$(Process).txt
# Log is the HTCondor log, not the log for our app
Log = script.log
# Queue tells HTCondor to enqueue our job
Queue 100
接下來寫要運行的Python文件。創(chuàng)建一個新文件(htcondor/script/test.py
)械荷,代碼如下:
#!/usr/bin/env python3.5
import argparse
import getpass
import os
import socket
import sys
ENV_VARS = ('MYVAR1', 'MYVAR2')
parser = argparse.ArgumentParser()
parser.add_argument('--clusterid', type=int)
parser.add_argument('--processid', type=int)
args = parser.parse_args()
cid = args.clusterid
pid = args.processid
print('I am process {} of cluster {}'
.format(pid, cid))
print('Running on {}'
.format(socket.gethostname()))
print('$CWD = {}'
.format(os.getcwd()))
print('$USER = {}'
.format(getpass.getuser()))
undefined = False
for v in ENV_VARS:
if v in os.environ:
print('{} = {}'
.format(v, os.environ[v]))
else:
print('Error: {} undefined'
.format(v))
undefined = True
if undefined:
sys.exit(1)
sys.exit(0)
這段簡單的代碼很適合初次使用HPC機群吨瞎。它可以清晰的顯示任務在哪里運行,和運行的賬戶字旭。
這是在寫Python任務時需要知道的重要信息遗淳。某些機群有在所有計算節(jié)點上都有常規(guī)賬戶归露,在機群上分享用戶的主文件夾斤儿。對于我們的例子往果,用戶在登錄節(jié)點上提交之后就會運行。
在其他機群上堕油,任務都運行在低級用戶下(例如,nobody
用戶)卜录。這時眶明,特別要注意許可和任務執(zhí)行環(huán)境搜囱。
注意:HTCondor可以在提交主機和執(zhí)行節(jié)點之間高效復制數(shù)據(jù)文件和/或可執(zhí)行文件“硇冢可以是按需復制扮宠,或是總是復制的模式坛增。感興趣的讀者可以查看指令
should_transfer_files
,transfer_executable
典鸡,transfer_input_files
坏晦,和transfer_output_files
昆婿。
前面的任務文件(htcondor/script/script.job
)有一些地方值得注意。首先睁冬,要保證運行任務的用戶可以找到Python 3.5看疙,它的位置可能和不同能庆。我們可以讓HTCondor向運行的任務傳遞完整的環(huán)境(通過指令GetEnv = True
)。
我們還提交了100個實例(Queue 100
)弥搞。這是數(shù)據(jù)并行應用的常用方式,數(shù)據(jù)代碼彼此獨立運行船逮。
我們需要自定義文件的每個實例粤铭。我們可以在任務文件的等號右邊用兩個變量承耿,$(Process)和$(Cluster)。在提交任務的時候凛辣,對于每個進程职烧,HTCondor用響應的集群ID和進程ID取代了這兩個變量蚀之。
像之前一樣,提交這個任務:
$ condor_submit script.job
任務提交的結果顯示在下圖中:
當所有的任務都完成之后寿谴,在當前目錄讶泰,我們會有100個STDOUT
文件和100個STDERR
文件拂到,還有一個HTCondor生成的日志文件。
如果一切正常狼犯,所有的STDERR
文件都會是空的领铐,所有的STDOUT
文件都有以下的文字:
I am process 9 of cluster 61
Running on somehost
$CWD = /tmp/book/htcondor/script
$USER = bookuser
MYVAR1 = foo
MYVAR2 = bar
留給讀者一個有趣的練習罐孝,向test.py
文件插入條件化的錯誤。如下所示:
if pid == 13:
raise Exception('Booo!')
else:
sys.exit(0)
或者:
if pid == 13:
sys.exit(2)
else:
sys.exit(0)
然后汹来,觀察任務集群的變化收班。
如果做這個試驗谒兄,會看到在第一種情況下(拋出一個異常)承疲,響應的STDERR
文件不是空的。第二種情況的錯誤難以察覺兄世。錯誤是靜默的啊研,只是出現(xiàn)在script.log
文件党远,如下所示:
005 (034.013.000) 01/09 12:25:13 Job terminated.
(1) Normal termination (return value 2)
Usr 0 00:00:00, Sys 0 00:00:00 - Run Remote Usage
Usr 0 00:00:00, Sys 0 00:00:00 - Run Local Usage
Usr 0 00:00:00, Sys 0 00:00:00 - Total Remote Usage
Usr 0 00:00:00, Sys 0 00:00:00 - Total Local Usage
0 - Run Bytes Sent By Job
0 - Run Bytes Received By Job
0 - Total Bytes Sent By Job
0 - Total Bytes Received By Job
Partitionable Resources : Usage Request Allocated
Cpus : 1 1
Disk (KB) : 1 1 12743407
Memory (MB) : 0 1 2048
注意到Normal termination (return value 2)
此行沟娱,它說明發(fā)生了錯誤。
習慣上柳爽,我們希望發(fā)生錯誤時碱屁,會有這樣的指示娩脾。要這樣的話,我們在提交文件中使用下面的指令:
Notification = Error
Notify_User = email@example.com
這樣俩功,如果發(fā)生錯誤碰声,HTCondor就會向email@example.com
發(fā)送報錯的電子郵件胰挑。通知的可能的值有Complete
(即椿肩,無論退出代碼郑象,當任務完成時茬末,發(fā)送email)丽惭,Error
(即,退出代碼為非零值時柜砾,發(fā)送email)拷橘,和默認值Never
冗疮。
另一個留給讀者的練習是指出我們的任務需要哪臺機器,任務偏好的機器又是哪臺另萤。這兩個獨立的請求是分別通過指令Requirements
和Rank
诅挑。Requirements
是一個布爾表達式拔妥,Rank
是一個浮點表達式。二者在每個協(xié)調(diào)循環(huán)都被評估铺厨,以找到一批機器以運行任務解滓。
對于所有Requirements
被評為True
的機器筝家,被選中的機器都有最高的Rank
值溪王。
筆記:當然值骇,機器也可以對任務定義
Requirements
和Rank
(由系統(tǒng)管理員來做)雷客。因此桥狡,一個任務只在兩個Requirements
是True
的機器上運行裹芝,二者Rank
值結合起來一定是最高的娜汁。
如果不定義任務文件的Rank
掐禁,它就默認為0.0.Requirements
。默認會請求相同架構和OS作為請求節(jié)點缕允,和族都的硬盤保存可執(zhí)行文件蹭越。
例如响鹃,我們可以進行一些試驗,我們請求運行64位Linux粪糙、大于64GB內(nèi)存的機器蓉冈,傾向于快速機器:
Requirements = (Target.Memory > 64) && (Target.Arch == "X86_64") && (Target.OpSys == "LINUX")
Rank = Target.KFlops
筆記:對于
Requirements
和Rank
的可能的值倦卖,你可以查看附錄A中的Machine ClassAd Atributes怕膛。最可能用到的是Target.Memory
,Target.Arch
掸茅,Target.OpSys
昧狮,Target.Disk
,Target.Subnet
和Target.KFlops
合住。
最后撒璧,實踐中另一個強大的功能是卿樱,為不同的任務定義依賴。往往萨蚕,我們的應用可以分解成一系列步驟岳遥,其中一些可以并行執(zhí)行烤送,其余的不能(可能由于需要等待中間結果)帮坚。當只有獨立的步驟時,我們可以將它們組織成幾個任務集合讯泣,就像前面的例子好渠。
HTCondor DAGMan(無回路有向圖管理器Directed Acyclic Graph Manager的縮寫)是一個元規(guī)劃器节视,是一個提交任務寻行、監(jiān)督任務的工具,當任務完成時杆烁,它會檢查哪個其它的任務準備好了兔魂,并提交它。
為了在DAG中組織任務构罗,我們需要為每一個任務寫一個提交文件绰播。另外尚困,我們需要另寫一個文本文件链蕊,描述任務的依賴規(guī)則滔韵。
假設我們有四個任務(單進程或多進程集合)陪蜻。稱為A、B滋将、C随闽、D肝谭,它們的提交文件是a.job
攘烛,b.job
,c.job
鼠次,d.job
须眷。比如,我們想染A第一個運行捕传,當A完成時庸论,同時運行B和C棒呛,當B和C都完成時簇秒,再運行D趋观。
下圖,顯示了流程:
DAG文件(htcondor/dag/simple.dag
)的代碼如下所示:
# Simple Condor DAG file
# There is no requirement or standard on DAG file extensions.
# Usage: shell> condor_submit_dag simple.dag
# Define the nodes in the DAG
JOB A a.job
JOB B b.job
JOB C c.job
JOB D d.job
# Define the relationship between nodes
PARENT A CHILD B C
PARENT B C CHILD D
四個提交文件沒有那么重要。我們可以使用下面的內(nèi)容(例如掐场,任務A和其它三個都可以使用):
Universe = vanilla
Executable = /bin/echo
Arguments = "I am job A"
Output = a_stdout.txt
Log = a.log
Queue
提交完整的DAG贩猎,是使用condor_submit_dag
命令:
$ condor_submit_dag simple.dag
這條命令創(chuàng)建了一個特殊提交文件(simple.dag.condor.sub
)到condor_dagman
可執(zhí)行文件融欧,它的作用是監(jiān)督運行的任務噪馏,在恰當?shù)臅r間規(guī)劃任務。
DAGMan元規(guī)劃器有還有許多這里沒寫的功能瓶颠,包括類似Makefile的功能粹淋,可以繼續(xù)運行由于錯誤停止的任務。
關于性能屋匕,你還需要注意幾點借杰。DAG中的每個節(jié)點蔗衡,當被提交時绞惦,都要經(jīng)過一個協(xié)調(diào)循環(huán),就像一個通常的HTCondor任務杰刽。這些一系列的循環(huán)會導致?lián)p耗专缠,損耗與節(jié)點的數(shù)量成正比。通常哥力,協(xié)調(diào)循環(huán)會與計算重疊,所以在實踐中很少看到損耗吩跋。
另一點寞射,condor_dagman
的效率非常高,DAGs有百萬級別甚至更多的節(jié)點都很常見锌钮。
筆記:推薦感興趣的讀者閱讀HTCondor一章的DAGMan Applications桥温。
短短一章放不下更多關于HTCondor的內(nèi)容,它的完整手冊超過1000頁梁丘!這里介紹的覆蓋了日常使用。我們會在本章的末尾介紹調(diào)試的方法氛谜。接下來掏觉,介紹另一個流行的任務規(guī)劃器:PBS。
使用PBS運行Python任務
Portable Batch System (PBS)是90年代初值漫,NASA開發(fā)的澳腹。它現(xiàn)在有三個變體:OpenPBS,Torque和PBS Pro。這三個都是原先代碼的分叉酱塔,從用戶的角度沥邻,它們?nèi)齻€的外觀和使用感受十分相似。
這里我們學習PBS Pro(它是Altair Engineering的商用產(chǎn)品羊娃,http://www.pbsworks.com)谋国,它的特點和指令在Torque和OpenPBS上也可以使用,只是有一點不同迁沫。另外芦瘾,為了簡潔,我們主要關注HTCondor和PBS的不同集畅。
從概念上近弟,PBS和HTCondor很像。二者有相似的架構挺智,一個主節(jié)點(pbs_server
)祷愉,一個協(xié)調(diào)器和規(guī)劃器(pbs_sched
),執(zhí)行節(jié)點的任務監(jiān)督器(pbs_mom
)赦颇。
用戶將任務提交到隊列二鳄。通常,對不同類型的任務(例如媒怯,序列vsMPI并行)和不同優(yōu)先級的任務有多個隊列订讼。相反的,HTCondor對每個提交主機只有一個隊列扇苞。用戶可用命令行工具欺殿、DRMAA和Python的drmaa模塊(pip install drmaa
)與PBS交互。
PBS任務文件就是一般的可以本地運行的文件(例如鳖敷,Shell或Python文件)脖苏。它們一般都有專門的內(nèi)嵌的PBS指令,作為文件的注釋定踱。這些指令的Windows批處理腳本形式是#PBS <directive> 或 REM PBS <directive>(例如棍潘,#PBS -q serial or REM PBS –q serial
)。
使用qsub
命令(類似condor_submit
)崖媚,將任務提交到合適的任務隊列亦歉。一旦成功提交一個任務,qsub
會打印出任務ID(形式是integer.server_hostname
)至扰,然后退出鳍徽。任務ID也可以作為任務的環(huán)境變量$PBS_JOBID
。
資源需求和任務特性敢课,可以在qsub
中指出阶祭,或在文件中用指令標明绷杜。推薦在文件中用指令標明,而不用qsub
命令濒募,因為可以增加文件的可讀性鞭盟,也是種記錄。
例如瑰剃,提交我們之前討論過的simple.job
雕擂,你可以簡單的寫一個最小化的shell文件(pbs/simple/simple.sh
):
#!/bin/bash
/usr/local/bin/python3.5 -c "print('Hello, HTCondor!')"
我們看到护昧,沒有使用PBS指令(它適用于沒有需求的簡單任務)。我們可以如下提交文件:
$ qsub simple.sh
因為沒必要為這樣的一個簡單任務寫Shell文件,qsub
用行內(nèi)參數(shù)就可以了:
$ qsub -- /usr/local/bin/python3.5 -c "print('Hello, HTCondor!')"
但是枝冀,不是所有的PBS都有這個特性渐逃。
在有多個任務隊列/規(guī)劃器的安裝版本上液走,我們可以指定隊列和規(guī)劃器纲菌,可以用命令行(即qsub –q queue@scheduler_name
)或用文件中的指令(即,#PBS –q queue@scheduler_name
)宝磨。
前面的兩個示例任務顯示了PBS和HTCondor在提交任務時的不同弧关。使用HTCondor,我們需要寫一個任務提交文件唤锉,來處理運行什么以及在哪里運行世囊。使用PBS,可以直接提交任務窿祥。
筆記:從8.0版本開始株憾,HTCondor提供了一個命令行工具,
condor_qsub
壁肋,像是qsub
的簡化版号胚,非常適合從PBS向HTCondor轉移。
提交成功后浸遗,qsub
會打印出任務ID,它的形式是integer.servername
(例如8682293.pbshead
)箱亿。PBS將任務標準流重新轉到scriptname.oInteger
(給STDOUT
)和scriptname.eInteger
(給STDERR
)跛锌,Integer
是任務ID的整數(shù)部分(例如,我們例子中的simple.sh.e8682293和script.sh.o8682293)届惋。
任務通常(在執(zhí)行節(jié)點)運行在提交賬戶之下髓帽,在一個PBS創(chuàng)建的臨時目錄,之后會自動刪除脑豹。目錄的路徑是環(huán)境變量$PBS_TMPDIR
郑藏。
通常,PBS定義定義了許多環(huán)境變量瘩欺,用于運行的任務必盖。一些設定了提交任務的賬戶的環(huán)境拌牲,它們的名字通常是PBS_0
開頭(例如,$PBS_O_HOME
或$PBS_O_PATH
)歌粥。其它是專門用于任務的塌忽,如$PBS_TMPDIR
。
筆記:現(xiàn)在失驶,PBS Pro定義了30個任務環(huán)境變量土居。可以在PBS Professional Reference Guide的PBS Environment Variables一章查到完整列表嬉探。
使用指令#PBS –J start-end[:step]
提交任務數(shù)組(命令行或在文件中使用指令)擦耀。為了獲得提交者的環(huán)境,可以使用-V
指令涩堤,或者傳遞一個自定義環(huán)境到任務眷蜓,使用#PBS -v "ENV1=VAL1, ENV2=VAL2, …"
。
例如定躏,前面例子的任務數(shù)組账磺,可以這樣寫(pbs/script/test.py
):
#!/usr/bin/env python3.5
#PBS -J 0-99
#PBS -V
import argparse
import getpass
import os
import socket
import sys
ENV_VARS = ('MYVAR1', 'MYVAR2')
if 'PBS_ENVIRONMENT' in os.environ:
# raw_cid has the form integer[].server
raw_cid = os.environ['PBS_ARRAY_ID']
cid = int(raw_cid.split('[')[0])
pid = int(os.environ['PBS_ARRAY_INDEX'])
else:
parser = argparse.ArgumentParser()
parser.add_argument('--clusterid', type=int)
parser.add_argument('--processid', type=int)
args = parser.parse_args()
cid = args.clusterid
pid = args.processid
print('I am process {} of cluster {}'
.format(pid, cid))
print('Running on {}'
.format(socket.gethostname()))
print('$CWD = {}'
.format(os.getcwd()))
print('$USER = {}'
.format(getpass.getuser()))
undefined = False
for v in ENV_VARS:
if v in os.environ:
print('{} = {}'
.format(v, os.environ[v]))
else:
print('Error: {} undefined'
.format(v))
undefined = True
if undefined:
sys.exit(1)
sys.exit(0)
我們完全不需要提交文件。用qsub
提交痊远,如下所示:
$ MYVAR1=foo MYVAR2=bar qsub test.py
分配的任務ID的形式是integer[].server
(例如8688459[].pbshead
)垮抗,它可以指示提交了任務數(shù)組,而不是一個簡單的任務碧聪。這是HTCondor和PBS的另一不同之處:在HTCondor中冒版,一個簡單任務是一個任務集合(即,任務數(shù)組)逞姿,只有一個進程辞嗡。另一不同點是,PBS任務訪問集合ID和進程ID的唯一方式是通過環(huán)境變量滞造,因為沒有任務提交文件(提交任務時可以提交變量)续室。
使用PBS,我們還需要做一些簡單解析以從$PBS_ARRAY_ID
提取任務數(shù)組ID谒养。但是挺狰,我們可以通過檢測是否定義了$PBS_ENVIRONMENT
,來判斷代碼是否運行买窟。
使用指令-l
指明資源需求丰泊。例如,下面的指令要求20臺機器始绍,每臺機器有32核和16GB內(nèi)存:
#PBS –l select=20:ncpus=32:mem=16gb
也可以指定任務的內(nèi)部依賴瞳购,但不如HTCondor簡單:依賴的規(guī)則需要任務ID,只有在提交任務之后才會顯示出來亏推。之前的DAGdiamond
可以用如下的方法執(zhí)行(pbs/dag/dag.sh
):
#!/bin/bash
A=`qsub -N A job.sh`
echo "Submitted job A as $A"
B=`qsub -N B -W depend=afterok:$A job.sh`
C=`qsub -N C -W depend=afterok:$A job.sh`
echo "Submitted jobs B & C as $B, $C"
D=`qsub -N D -W depend=afterok:$B:$C job.sh`
echo "Submitted job D as $D"
這里学赛,任務文件是:
#!/bin/bash
echo "I am job $PBS_JOBNAME"
這個例子中年堆,使用了$PBS_JOBNAME
獲取任務名,并使用指令-W depend=
強制了任務執(zhí)行順序罢屈。
一旦提交了任務嘀韧,我們可以用命令qstat
監(jiān)控,它等同于condor_q
缠捌。銷毀一個任務(或在運行之前锄贷,將隊伍從隊列移除),是通過qdel
(等價于condor_rm
)曼月。
PBS Pro和HTCondor一樣谊却,是一個復雜的系統(tǒng),功能很多哑芹。這里介紹的只是它的表層炎辨,但是作為想要在PBS HPC機群上操作的人,作為入門足夠了聪姿。
一些人覺得用Python和Shell文件提交到PBS而不用任務文件非常有吸引力碴萧。其他人則喜歡HTCondor和DAGMan的工具處理任務內(nèi)依賴。二者都是運行在HPC機群的強大系統(tǒng)末购。
調(diào)試
一切正常是再好不過破喻,但是,運氣不會總是都好盟榴。分布式應用曹质,即使是遠程運行的簡單任務,都很難調(diào)試擎场。很難知道任務運行在哪個賬戶之下羽德,運行的環(huán)境是什么,在哪里運行迅办,使用任務規(guī)劃器宅静,很難預測何時運行。
當發(fā)生錯誤時站欺,通過幾種方法坏为,可以知道發(fā)生了什么當使用任務規(guī)劃器時,首先要做的是查看任務提交工具返回錯誤信息(即镊绪,condor_submit
,condor_submit_dag
洒忧,or qsub
)蝴韭。然后要看任務STDOUT
,STDERR
和日志文件熙侍。
通常榄鉴,任務規(guī)劃器本身就有診斷錯誤任務的工具履磨。例如,HTCondor提供了condor_q -better-analyze
庆尘,檢查為什么任務會在隊列中等待過長時間剃诅。
通常,任務規(guī)劃器導致的問題可以分成以下幾類:
- 權限不足
- 環(huán)境錯誤
- 受限的網(wǎng)絡通訊
- 代碼依賴問題
- 任務需求
- 共享vs本地文件系統(tǒng)
頭三類很容易檢測驶忌,只需提交一個測試任務矛辕,打印出完整的環(huán)境、用戶名等等付魔,剩下的很難檢測到聊品,尤其是在大集群上。
對于這些情況几苍,可以關注任務是在哪臺機器運行的翻屈,然后啟動一個交互session(即 qsub –I
、condor_submit – interactive
或condor_ssh_to_job
)妻坝,然后一步一步再運行代碼伸眶。
如果任務需求的資源不足(例如,需要一個特定版本的OS或軟件包刽宪,或其它特別的硬件)或資源過多厘贼,任務規(guī)劃器就需要大量時間找到合適的資源。
任務規(guī)劃期通常提供工具以檢查哪個資源符合任務的需求(例如纠屋,condor_status –constrain
)涂臣。如果任務分配給計算節(jié)點的時間不夠快,就需要進行檢測售担。
另一個產(chǎn)生問題的來源是提交主機的文件系統(tǒng)的代碼赁遗、數(shù)據(jù)不能適用于全部的計算節(jié)點。這種情況下族铆,推薦使用數(shù)據(jù)轉移功能(HTCondor提供)岩四,數(shù)據(jù)階段的預處理文件。
Python代碼的常用方法是使用虛擬環(huán)境哥攘,在虛擬環(huán)境里先安裝好所有的依賴(按照指定的安裝版本)剖煌。完成之后,再傳遞給任務規(guī)劃器逝淹。
在有些應用中耕姊,傳輸?shù)臄?shù)據(jù)量十分大,要用許多時間栅葡。這種情況下茉兰,最好是給數(shù)據(jù)分配進程。如果不能的話欣簇,應該像普通任務一樣規(guī)劃數(shù)據(jù)的移動规脸,并使用任務依賴坯约,保證數(shù)據(jù)準備好之后再開始計算。
總結
我們在本章學習了如何用任務規(guī)劃器莫鸭,在HPC機群上運行Python代碼闹丐。
但是由于篇幅的限制,還有許多內(nèi)容沒有涉及被因。也許卿拴,最重要的就是MPI(Message Passing Interface),它是HPC任務的進程間通訊標準庫氏身。Python有MPI模塊巍棱,最常使用的是mpi4py, http://pythonhosted.org/mpi4py/蛋欣,和Python包目錄https://pypi.python.org/pypi/mpi4py/航徙。
另一個沒涉及的是在HPC機群運行分布式任務隊列。對于這種應用陷虎,可以提交一系列的任務到機群到踏,一個任務作為消息代理,其它任務啟動worker尚猿,最后一個任務啟動應用窝稿。特別需要注意連接worker和應用到消息代理,提交任務的時候不能確定代理是在哪一臺機器凿掂。與Pyro類似的一個策略是使用nameserver伴榔,解決這個問題。
然而庄萎,計算節(jié)點上有持續(xù)的進程是不推薦的踪少,因為不符合任務規(guī)劃器的原則。大多數(shù)系統(tǒng)都會在幾個小時之后退出長時間運行的進程糠涛。對于長時間運行的應用援奢,最好先咨詢機群的管理者。
任務規(guī)劃器(包括MPI)是效率非常高的工具忍捡,在HPC之外也有用途集漾。其中許多都是開源的,并且有活躍的社區(qū)砸脊,值得一看具篇。
下一章會講分布式應用發(fā)生錯誤時該怎么做。
序言
第1章 并行和分布式計算介紹
第2章 異步編程
第3章 Python的并行計算
第4章 Celery分布式應用
第5章 云平臺部署Python
第6章 超級計算機群使用Python
第7章 測試和調(diào)試分布式應用
第8章 繼續(xù)學習