概要
スパコン使用時にqsub でarray job を使うときのメモです
アレイジョブとは?
qsub の一種でパラメータが異なる同一の処理のジョブを多数実行する時に複数のjobを1つのファイルでまとめて実行する処理(option)です。
UGEはアレイジョブと呼ばれる概念を持ちます。 一度に多数のジョブをシステムに投入したい場合(パラメトリックスタディでパラメータを変えながらジョブを投げたい場合) はアレイジョブ機能を利用してください。決して、多数のジョブをそのまま投入しようとしないでください。システムを過負荷に陥れる場合があります。
遺伝研HP(参考1)より
qsub -t [min_idx]-[max_idx]:[step_num]
の形で記述します(パラメーターの意味、使い方は後述)
アレイジョブのメリット、ユースケース
僕の分野(生物)の場合はサンプルや目的の遺伝子がたくさんあって、100~10000ぐらいの対象物に対して同じ処理したいというニーズがあります。
CS系の人たちだと、色々なパラメータを組み合わせて比較する際に使うことがあると思います。
また、大量のパラメータを触れること以外にもメリットとして上述のようにスパコンを使うときは、大量のジョブを投げるよりも、アレイジョブで投げたほうが、負荷が少ないということがあるようです。
環境
レッドハットエンタープライズのCentOS 7です。(Sirokane)
スパコンのシステムはUGEです。
bash-4.2$ uname -r
3.10.0-957.5.1.el7.x86_64
bash-4.2$ cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
アレイジョブの実行方法
array job を使用する際にはタスクIDという変数をqsub のoptionで指定して、それをシェルスクリプト内でパースする必要があります。
タスクIDの指定
qsubの際に -t で指定します。初期値、終了値、step幅を:
区切りで指定します。
qsub -t [[min_idx]-[max_idx]:[step_num] hoge.sh
そうすることで、環境変数 SGE_TASK_ID にmin_idxからmax_idxまでの値がSstep_numのステップ数で入ります。
例えば、qsub -t 1-5:1
なら1,2,3,4,5がSGE_TASK_ID に入ります。
python のrange みたいな感じですが区切り文字が-
と:
なところ、max_idxを含むところが違いとしてあります。
for i in range(1,5,1)
タスクIDをshebangの中に記述する
通常のqsubのoptionと同様に -t option もshebangに記述することができます。
#$ -t [min_idx]-[max_idx];[step_num]
と記入しても良いです。(僕は可用性が高まると思っているので、qsubへのoptionは毎回scriptfileに書き込むようにしています)
shell script内でのタスクIDのパース
qsubのoptionで指定したタスクIDはshell script内では${SGE_TASK_ID}という変数に入ります。
そのため、変更したいパラメータに${SGE_TASK_ID}をわりあてればよいです。
例として1.out,2.out,,,,10.outというファイルが有り、それを処理して1_kraken.html...というinputのファイル名に合わせた出力ファイルを作りたいときの例を示します。
#!/bin/bash
#$ -S /bin/bash
#$ -l os7
#$ -l s_vmem=10G
#$ -l mem_req=10G
#$ -cwd
#$ -pe def_slot 3
#$ -t 1-10:1
ktImportTaxonomy -q 2 -t 3 ${SGE_TASK_ID}.out -o ${SGE_TASK_ID}_kraken.html
複数のパラメータ、変数のループをarray job で実現する実例
以下とりとめないので必要ならスルーしてください。
今回のやりたいこととしては以下の3つがあるため、それを実現するscriptをshell scriptに書いていきます
- 3つのパラメータを変化させたい
- 552通りで50通り全て試したい
- 出力先やファイル名はパラメータの名前を入れたい
###パラメータの設定、どうやって計算するか
パラメータが1から100までとかならよかったのですが、そうではない(サンプル名は文字列だし)なので、パラメータはリストで管理しました。
li_param_1=([1]="0"[2]="3"[3]="5"[4]="10"[5]="20")
li_param_2=([1]="auto"[2]="1000"[3]="2000"[4]="5000"[5]="10000")
sample=([1]=${sampleA},[2]=${sampleB})
これを50通り全て試したいです。
それぞれのパラメータの値を5で割った余り、5で割った時の商を5で割った余り、2で割った余りに設定すれば良さそうです。
Bashでの四則演算
そこで、いまだに身につかないbashでの計算の仕方を学ぶ必要がありました。
Bash $((算術式)) のすべて - A 基本編
を参考にしました。
基本は$((式))
という形になります。
今回はSGE_TASK_ID
を変数5で割った余り、5で割った時の商を5で割った余り、2で割った余りを入れたいので、
for i in {1..50} ; do
SGE_TASK_ID=${i}
a=$((SGE_TASK_ID%5))
b=$((SGE_TASK_ID/5%5))
c=$((SGE_TASK_ID%2))
echo ${a} ${b} ${c}
done
とするとうまくいってそうです。
1 0 1
2 0 0
3 0 1
4 0 0
0 1 1
1 1 0
2 1 1
3 1 0
4 1 1
0 2 0
1 2 1
以下省略
リストの定義、参照
bash 配列まとめ を参照しました。
####定義
array=("a" "b" "c")
で定義します。()でくくるのがちょっと違和感あります。
その前に参照したサイトがarray=([1]="a" [2]=""...)
という形式で書いていたので、混乱したんですが、両方の書き方があるんでしょうか?
indexを0から順番に始めたくない場合にはindexを指定してあげるこの書き方の方が便利そうですね。
####参照
普通にarray[1]
array[$i]
の形で良さそうです。
複雑な入れ子構造
${li_param_1[a]}
のように一回{}で囲ってあげると内側の変数は${}で囲わなくてもいいということが理解できなくて、無駄に時間を費やしていました。
sampleA="Liver"
sampleB="Kidney"
li_param_1=("0" "3" "5" "10" "20" )
li_param_2=("auto" "1000" "2000" "5000" "10000" )
sample=(${sampleA} ${sampleB} )
for i in {1..10} ; do
SGE_TASK_ID=${i}
a=$((SGE_TASK_ID%5))
b=$((SGE_TASK_ID/5%5))
c=$((SGE_TASK_ID%2))
echo "parameter ${li_param_1[a]} ${li_param_2[b]} ${sample[c]}"
out_dir="${li_param_1[a]}_${li_param_2[b]}_${sample[c]}"
echo "$out_dir"
done
で行けたみたいです。
sh bash_test.sh
parameter 3 auto Kidney
3_auto_Kidney
parameter 5 auto Liver
5_auto_Liver
parameter 10 auto Kidney
10_auto_Kidney
parameter 20 auto Liver
20_auto_Liver
parameter 0 1000 Kidney
0_1000_Kidney
parameter 3 1000 Liver
3_1000_Liver
parameter 5 1000 Kidney
5_1000_Kidney
parameter 10 1000 Liver
10_1000_Liver
parameter 20 1000 Kidney
20_1000_Kidney
parameter 0 2000 Liver
0_2000_Liver
これでもとのやりたかったことをできるようになりました。
参考
遺伝研のページ
https://sc2.ddbj.nig.ac.jp/index.php/ja-uge-additional#info5