《Python分布式計算》 第6章 超級計算機群使用Python (Distributed Computing with Python)


序言
第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.txtsimple_stdout.txt。它們是任務的日志文件忍些,任務的標準錯誤坎怪,和標準輸出流搅窿。

日志文件有許多有用的信息,包括任務提交的時間和從哪臺機器提交的闹司,在隊列中等待的時間,運行的時間和機器沐飘,退出代碼和利用的資源。

我們的Python任務退出狀態(tài)是0(意味成功)耐朴,在STDERR上沒有輸出(即simple_stderr.txt是空的)筛峭,然后向STDOUT寫入Hello,HTCondor镰吵!(即simple_stdout.txt)挂签。如果不是這樣饵婆,就要進行調(diào)試。

現(xiàn)在提交一個簡單的Python文件谓传。新的任務文件很相似,我們只需更改ExecutableArguments紧卒。我們還要傳遞一些環(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_filestransfer_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冗疮。

另一個留給讀者的練習是指出我們的任務需要哪臺機器,任務偏好的機器又是哪臺另萤。這兩個獨立的請求是分別通過指令RequirementsRank诅挑。Requirements是一個布爾表達式拔妥,Rank是一個浮點表達式。二者在每個協(xié)調(diào)循環(huán)都被評估铺厨,以找到一批機器以運行任務解滓。

對于所有Requirements被評為True的機器筝家,被選中的機器都有最高的Rank值溪王。

筆記:當然值骇,機器也可以對任務定義RequirementsRank(由系統(tǒng)管理員來做)雷客。因此桥狡,一個任務只在兩個RequirementsTrue的機器上運行裹芝,二者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

筆記:對于RequirementsRank的可能的值倦卖,你可以查看附錄A中的Machine ClassAd Atributes怕膛。最可能用到的是Target.MemoryTarget.Arch掸茅,Target.OpSys昧狮,Target.DiskTarget.SubnetTarget.KFlops合住。

最后撒璧,實踐中另一個強大的功能是卿樱,為不同的任務定義依賴。往往萨蚕,我們的應用可以分解成一系列步驟岳遥,其中一些可以并行執(zhí)行烤送,其余的不能(可能由于需要等待中間結果)帮坚。當只有獨立的步驟時,我們可以將它們組織成幾個任務集合讯泣,就像前面的例子好渠。

HTCondor DAGMan(無回路有向圖管理器Directed Acyclic Graph Manager的縮寫)是一個元規(guī)劃器节视,是一個提交任務寻行、監(jiān)督任務的工具,當任務完成時杆烁,它會檢查哪個其它的任務準備好了兔魂,并提交它。

為了在DAG中組織任務构罗,我們需要為每一個任務寫一個提交文件绰播。另外尚困,我們需要另寫一個文本文件链蕊,描述任務的依賴規(guī)則滔韵。

假設我們有四個任務(單進程或多進程集合)陪蜻。稱為A、B滋将、C随闽、D肝谭,它們的提交文件是a.job攘烛,b.jobc.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_submitcondor_submit_dag洒忧,or qsub)蝴韭。然后要看任務STDOUTSTDERR和日志文件熙侍。

通常榄鉴,任務規(guī)劃器本身就有診斷錯誤任務的工具履磨。例如,HTCondor提供了condor_q -better-analyze庆尘,檢查為什么任務會在隊列中等待過長時間剃诅。

通常,任務規(guī)劃器導致的問題可以分成以下幾類:

  • 權限不足
  • 環(huán)境錯誤
  • 受限的網(wǎng)絡通訊
  • 代碼依賴問題
  • 任務需求
  • 共享vs本地文件系統(tǒng)

頭三類很容易檢測驶忌,只需提交一個測試任務矛辕,打印出完整的環(huán)境、用戶名等等付魔,剩下的很難檢測到聊品,尤其是在大集群上。

對于這些情況几苍,可以關注任務是在哪臺機器運行的翻屈,然后啟動一個交互session(即 qsub –Icondor_submit – interactivecondor_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ù)學習


最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末凌埂,一起剝皮案震驚了整個濱河市栽连,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖秒紧,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挨下,居然都是意外死亡熔恢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門臭笆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叙淌,“玉大人,你說我怎么就攤上這事愁铺∮セ簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵茵乱,是天一觀的道長茂洒。 經(jīng)常有香客問我,道長瓶竭,這世上最難降的妖魔是什么督勺? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮斤贰,結果婚禮上智哀,老公的妹妹穿的比我還像新娘。我一直安慰自己荧恍,他們只是感情好瓷叫,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著送巡,像睡著了一般摹菠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上授艰,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天辨嗽,我揣著相機與錄音,去河邊找鬼淮腾。 笑死糟需,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的谷朝。 我是一名探鬼主播洲押,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼圆凰!你這毒婦竟也來了杈帐?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挑童,沒想到半個月后累铅,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡站叼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年娃兽,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尽楔。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡投储,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阔馋,到底是詐尸還是另有隱情玛荞,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布呕寝,位于F島的核電站勋眯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏壁涎。R本人自食惡果不足惜凡恍,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怔球。 院中可真熱鬧嚼酝,春花似錦、人聲如沸竟坛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽担汤。三九已至涎跨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崭歧,已是汗流浹背隅很。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留率碾,地道東北人叔营。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像所宰,于是被迫代替她去往敵國和親绒尊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容