今まで使っていた京大のスパコンが今年度にリプレースされ7月以降使えなくなるようなので, 別の大学のスパコンを探していました. 同じ研究室の方が大阪大学のSQUIDを使っているとのことなので,今回試用してみました.
SQUIDのスペック
汎用CPUノード群(1520ノード)
- CPU: Intel Xeon Platinum 8368 (2.40 GHz 38コア) 2基
- Mem: 256 GB
GPUノード群(42ノード)
- CPU: Intel Xeon Platinum 8368 (2.40 GHz 38コア) 2基
- Mem: 512 GB
- GPU: NVIDIA A100 8基
ベクトルノード群(36ノード)
- CPU: AMD EPYC 7402P (2.8 GHz 24コア) 1基
- Mem: 128 GB
- アクセラレータ: NEC SX-Aurora TSUBASA Type20A 8基
試用申請
Webで所属や用途を記入するだけで簡単に申請できます. 申請から実際に利用できるようになるまでには1日くらいかかりました. 早いですね.
ログイン等
大阪大学のサイバーメディアセンターにかかれている方法をそのままやるだけです. 正直びっくりしたのが,SSHでログインする際に2要素認証が要求される点です. 公開鍵を登録してもログインできないみたい(?)なので,毎回パスワードと2要素認証コードの入力をしないといけないのは少しめんどくさいかも...
ジョブ投入など
基本的には普通のPBSシステムという感じです.
ノードを複数のジョブでシェアして実行するよりも,一つのジョブで一つ以上のノードを専有して実行することを想定されているようです. また,課金はCPU時間ではなくノード時間でカウントされるため,なるべく76コア全部を使うジョブを実行すべきなようです. (実際にはHTTが有効になっているので152論理コアまで有効に使ったほうがいいかもしれません)
ちなみに,私のqsubxargs
を使うと,CPU1コアしか使わない大量の処理を76個ずつ集約したアレイジョブを次のようにジョブ投入できます.
$ seq 1 10000 | qsubxargs --th:g=76 -I{} -- echo {}
複数のジョブで一つのノードを共有する方法もあるようですが,詳しく動作や課金体系を調査していないのでわかりません. 大阪大学のWebページを見る限りでは,共有でもノード時間でカウントされてしまう(ただし消費ポイントは小さくなる)みたいで,最大で0.5ノードのリソースしか使えないのに消費ポイントは半減もしないので損な気がしますが.
試用期間のリソースについて
試用期間とはいえ,ノード数の制限などはないようです. 試用期間は最大で3ヶ月で,その間に無料で大体ノード時間で500時間使えます. 500時間というと少し少ないように感じるかもしれませんが,CPUコアの時間でいえば38000時間なので試用期間でもわりと何でもできそうです. 本来であればこれは7500円分に相当するリソースであり,これが無料ですからいいですね.
ジョブの上限時間(wall time)の設定
ジョブの上限時間(wall time)がなるべく小さくなるように細かく設定すると,実行されるまでの待ち時間が大幅に減少します. 逆に言うと,wall timeが大きくなればなるほど,待ち時間はかなり伸びていきます. なので,処理時間の短いタスクを複数まとめてwall timeの大きなジョブとして投入するよりも, 1時間〜3時間程度のwall timeのジョブを投入したほうが良い気がします. 待ち時間が伸びることは他のスパコンでも同じだとは思いますが,体感は大阪大学のSQUIDでは比較的伸びる待ち時間が長めな気がします.
私はもともと処理時間が数日くらいのジョブを投げることもあったので,この仕様が少し問題でした. 私のプログラムは,1000通り程度のパラメータそれぞれについて,乱数のシードを変えて1万回試行するような処理を実行します. 従来までは,1000通り程度のパラメータそれぞれにつき1つのジョブを投入し,その1つのジョブで1万回の試行を実行していました. wall timeを小さくするために,1万回の試行を40個に分けて,1000通り程度x40個のジョブを投げて,それぞれで250回の試行をするように変えています. そして,全ジョブが終了後に40回に分けた試行の結果を結合して1万試行の結果を出力するようなプログラムに書き直して対応しました.
途中再実行可能なジョブ
wall timeをなるべく小さく設定しなければいけない都合上,設定したwall timeでは処理が終わらずにシステムからkillされる可能性が上がります. そのため,タスクを途中から再実行可能にする型をTUT-HPCLib4Dで提供しました. 次のようにして使います.
auto tasks = new MultiTaskList!void();
tasks ~= [
{
// 処理A
},
{
// 処理B
},
{
// 処理C
}
];
// ディスク上の"savefile"に途中結果を保存できるタスク
// 処理が一つ終わるたびに中間状態をファイルに保存
auto resumable = tasks.toResumable("savefile", 1);
// 処理A〜Cを順番に実行する
// もし,"savefile"に途中までの実行結果が保存されているなら
// その結果を"savefile"から読み取って途中から実行する
resumable();
同じようなことは以前からやっていたのですが, 以前は自分でシリアライゼーションやデシリアライゼーション,ファイルI/Oを書いていました. 今回はそれをライブラリに実装して,面倒な処理を隠蔽した型を提供しました.
また,10000個の細かいタスクを40個に分割して,それぞれで途中再実行可能なジョブを構成し,
前処理が完了後に結果をマージするようなプログラムも簡単に,次のように書けます.
処理の実態の関数func
が返す値を中間状態としてファイルに保存しておくため,その型がMessagePackでシリアライズ可能な型かJSONValueである必要があります.
enum nJobs = 40;
enum nTasks = 10000;
// 処理結果としてJSONを返す処理のリストを作り,1万件登録する
// つまり,func(0)〜func(9999)
auto tasks = new MultiTaskList!JSONValue();
foreach(i; 0 .. nTasks)
tasks.append(&func, i); // funcは整数を受け取ってJSONを返す関数
// 1万個の処理を分割して,250個の細かい処理をする40個のジョブを形成する
// 各ジョブは途中再実行可能で,10個の処理が終わるごとに"savefile_0"〜"savefile_39"に中間状態を保存する
auto jobs = tasks.toSplitMergeResumable(nJobs, "savefile", 10);
// プログラムのコマンドライン引数を調べて
// 結果を集約するか,進捗状況を確認するか,ジョブ投入するか判断
if(Runtime.args.canFind("merge")) {
// 1万件の結果を集約する
JSONValue[] results = jobs.returns;
jobs.nullify(); // ファイルから読み込んだデータを捨てる
// resultsのJSONValueをファイルに書き出す処理など...
} else if(Runtime.args.canFind("check")) {
// 40個のジョブがそれぞれどれだけ終わっているかを表示する
foreach(i; 0 .. nJobs) {
writefln!"%s: %s/%s"(i, jobs[i].numOfDone, jobs[i].numOfTotal);
jobs[i].nullify(); // ファイルから読み込んだデータを捨てる
}
} else {
// 引数にmergeやcheckがなければ
// 40個のジョブをアレイジョブとしてジョブスケジューラに登録する
tuthpc.run(jobs);
}