Folding@homeでWorkUnitを得られずにシステムリソースを遊ばせてしまう状態を回避する(追記有)

 以下の投稿で、個人でも可能なCOVID-19に対する研究への貢献として、Folding@homeへコンピューティングパワーを提供する方法を紹介しました。
wave.hatenablog.com

 世界中から同プロジェクトへの貢献が殺到しているようで、自分のマシンのCPUやGPUに作業(WorkUnit)が割り振られずに、無駄に遊ばせてしまう状態が発生するようになっています。公式サポートフォーラムにもそれらしいトピックが3/14に投稿されており、以下の言及があります。

We've been overwhelmed with the support we're getting from new Donors.
Several servers ran out of WU's overnight.
The issue is being addressed as fast as the project's owner can get to it.
Unfortunately the server(s) may have to be taken off-line while the new WUs are being generated.

Folding Forum • View topic - Temporary server outages
 ざっくり意訳すると、「新しい貢献者(ドナー)からの支援に圧倒されており、いくつかのサーバーは夜間にWU(WorkUnit)が不足してしまっている。この事象にはプロジェクトオーナーができるだけ早く対処するけど、残念ながら新しいWUを生成する間は(複数の)サーバーをオフラインにしなければいけないかも。」といった感じの内容です*1

 せっかく強力な演算能力を持ったマシンも、WorkUnitが得られなければアイドル状態で無駄に電力消費する箱でしかありません。故に、可及的速やかに新しいWorkUnitを得て演算を開始させたいところです。
 もちろん、自動でWorkUnitの再ダウンロード試行は行われますが、これが曲者でリトライ回数が増えるごとに倍々に近い時間(正確なロジックは不明)、リトライ間隔が延ばされます。その結果、リトライまで数時間待ちとなっていることもあります。
 調べてみたところ、直ちにWorkUnitの再ダウンロードを試行させる直接的な方法は無いようで、Folding@homeのプロセスを終了して再起動させるとか、マシン自体を再起動させることでWorkUnitのダウンロードを試行させるような方法しか見つかりませんでした。

 いろいろ試行錯誤してみたところ、以下の手段が効果的だったので紹介します。
 

実行中のWorkUnitの処理が完了する前に、次のWorkUnitのダウンロードを開始する

 単にFolding@homeをインストールしたままの状態だと、実行中のWorkUnitの処理がほぼ(99%)完了するまで次のWorkUnitのダウンロードは開始されません。
 ダウンロードを試行して1発でWorkUnitが得られるなら無駄なタイムロスは少ないですが、複数回のリトライを想定すると事前にダウンロードしておくことが望ましいです。
 実行中のWorkUnitの処理の進行具合が指定値に達したら次のWorkUnitのダウンロードを開始させる機能は公式に実装されており、Extra client optionsのnext-unit-percentageというオプションを使います。
Configuration guide – Folding@home
 next-unit-percentageという名称からも察せられますが、これは実行中のWorkUnitの処理が何%まで完了したら次のWorkUnitのダウンロードを開始するかを指定します。ダウンロードのリトライが多発する現状では小さな値を指定したいところですが、最小値は90に制限されていますので、90を指定するのが良いでしょう。
 FAHControl(Advanced Control)のConfigureボタンからExpertタブを選択するとExtra client optionsの追加ができます。
f:id:kachine:20200403214500p:plain
 お使いのマシンのスペックによって異なりますが実行中のWorkUnitの残り10%の処理に要する処理時間中に、(リトライごとに増える待ち時間を含めて)次のWorkUnitが首尾よくダウンロードできれば効率的にシステムリソースを献上できます。が、リトライ時間が膨れ上がって残り10%の処理も完了してしまうと、GPUまたはCPUは暇な状態となってしまいます。
 

(2020/4/20追記)
以下の方法はFolding@home 7.6.9でコマンドサーバーの挙動が変わったため、依然と同様には使えなくなりました(ウェルカムメッセージが変わっていたり、request-wsのレスポンスが出力されなくなっていたりするため、期待通りの動作をしません)。pause/unpauseでリトライ間隔を短縮するのは引き続き有効なようです。末尾に7.6.9への暫定対応スクリプトを追記しました。

ダウンロードのリトライ間隔を短縮する(それなりにPC系の知識ある人向け)

 FAHControlでDownloadステータスのWorkQueueのNextAttemptに表示されている時間が、次回ダウンロード試行までの待ち時間ですが、これが数十秒~数分ならともかく、数時間に膨れ上がっていると、座して待つのは合理的とは思えません。かと言って、即時にダウンロードを試行する機能はありませんので、何とかしたいと思います。

隠しコンソール?

 ところで、FAHControlの左側のClients欄に127.0.0.1:36330と表示されていることに気づいている方もいるかも知れませんが、デフォルトでインストールするとPort: 36330が開いてることにお気づきでしょうか。これ、アクセスできるんじゃね?と思い、telnetlocalhostのポート36330に接続してみたところ繋がりました。

$ telnet localhost 36330
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the Folding@home Client command server.
>

 このプロンプトでftp等と同じノリでhelpと叩いてみたらコマンド一覧出てきました。

> help

  auth                        Authenticate.
  error                       Error message.
  exit                        Exit the command processor
  heartbeat                   Prints an increasing hearbeat count.
  log-updates start | restart | stop Enable/diable log updates.
  quit                        Exit the command processor
  screensaver                 Unpause all slots which are paused waiting for a
                              screensaver and pause them again on disconnect.
  updates add <id> <rate> <expression> | del <id> | list | clear | reset Enable/disable
                              updates.

Folding@home Client:
  always_on [slot]            Set all or one slot(s) always on.
  bond <ip>:<port> <input> [output] [ip:port] Bond a packet file to a outgoing
                              debug socket connection.
  configured                  Return a PyON message indicating if the client has
                              set a user, team or passkey.
  do-cycle                    Run one client cycle.
  download-core <type> <url>  Download a core.
  finish [slot]               Finish all or one slot(s).
  get-info <category> <key>   Print application information
  info                        Print application information in PyON format
  inject <ip>:<port> <input> [output] [ip:port] Inject a packet file to a
                              listening debug socket. Will wait until packet is
                              processed.
  mask-unit-state             Disable specified unit states.
  num-slots                   Get number of slots in PyON format.
  on_idle [slot]              Set all or one slot(s) on idle.
  option <name> [value]       Get or set a configuration option
  options                     List or set options with their values.
                              If no name arguments are given then all options
                              with non-default values will be listed. If the
                              '-d' argument is given then even defaulted options
                              will be listed. If the '-a' option is given then
                              unset options will also be listed. Otherwise, if
                              option names are provided only those options will
                              be listed.
                              The special name '*' lists all options which have
                              not yet been listed and is affected by the '-d'
                              and '-a' options.
                              If a name argument is followed directly by an
                              equal sign then the rest of the arugment will be
                              used to set the option's value. If instead a name
                              argument is followed immediately by a '!' then the
                              option will be reset to its default value.
                              Options which are set or reset will also be
                              listed.
                              Options are listed as a PyON format dictionary.[-d
                              | -a] | [<name>[! | =<value>]]...
  pause [slot]                Pause all or one slot(s).
  ppd                         Get current total estimated Points Per Day.
  queue-info                  Get work unit queue information in PyON format.
  request-id                  Request an ID from the assignment server.
  request-ws                  Request work server assignment from the assignment
                              server.
  save [file]                 Save the configuration either to the specified
                              file or to the file the configuration was last
                              loaded from.
  shutdown                    Shutdown the application
  simulation-info <slot id>   Get current simulation information.
  slot-add <type> [<name>=<value>]... Add a new slot. Configuration options for
                              the new slot can be provided.
  slot-delete <slot>          Delete a slot. If it is running a unit it will be
                              stopped.
  slot-info                   Get slot information in PyON format.
  slot-modify <id> <type> [<name><! | =<value>>]... Modify an existing slot.
                              Configuration options can be either set or reset
                              using the same syntax used by the 'options'
                              command.
  slot-options <slot> [-d | -a] | [name]... The first argument is the slot ID.
                              See 'options' help for a description of the
                              remaining arguments.
  trajectory <slot id>        Get current protein trajectory.
  unpause [slot]              Unpause all or one slot(s).
  uptime                      Print application uptime
  wait-for-units              Wait for all running units to finish.

Standard Commands:
  add <number> <number>       Add two values
  clear                       Clear the screen
  date [format]               Print the date and time. Optionally, with
                              'format'. See: man strftime
  div <number> <number>       Divide two values
  eq <string> <string>        True if arguments are equal
  eval [expr]...              Evaluate all arguments
  if <cond> <expr1> [expr2]   If 'cond' evaluates to a non-empty string then
                              evalute 'expr1' otherwise, if provided, evaluate
                              'expr2'
  less <string> <string>      True the first argument is lexigraphically less
                              than the second
  mul <number> <number>       Multiply two values
  neq <string> <string>       True if arguments are not equal
  not <expr>                  Invert the truth value of the argument
  sleep <seconds>             Sleep for a number of seconds
  sub <number> <number>       Subtract two values

 隠し機能を見つけたかと思いきや、実は3rd party FAHClient APIとして公開されているようです。
3rd party FAHClient API · FoldingAtHome/fah-control Wiki · GitHub
 なお、Firewallでポート36330への通信をブロックしていなければLAN内のマシンからアクセスされるリスクがありますが、OSコマンドを実行できそうなコマンドは無いので、隠れた脆弱性でもなければシステムを危険にさらすリスクは少なさそうです(WorkUnitの停止/開始や使用スレッド数を変更されたりして、体感上のシステムパフォーマンスを変えたりされる可能性は否定できませんが、LAN内に侵入されている時点で…)。
 ちなみに、Folding@homeの設定でパスワードを指定していれば、パスワード認証しない限りポート36330に接続してもhelpを含めた各種コマンド実行を受け付けませんので、ハードニングの観点からはパスワード設定もしておいた方がいいかもしれません*2
 

リトライ間隔短縮のために使えそうなコマンド

 いろいろ試してみたところ、上記FAHClient APIで用意されているコマンドのうち、request-ws, pause [slot], unpause [slot], の3つが使えそうです。
 そもそも、WorkUnitのダウンロードに失敗しているときのログを確認すると、概ね以下の3種類ありそうです。

WARNING:WU00:FS00:WorkServer connection failed on port 8080 trying 80
ERROR:WU00:FS00:Exception: Failed to connect to HOSTADDRESS:80: 接続済みの呼び出し先が一定の時間を過ぎても正しく応答しなかったため、接続できませんでした。または接続済みのホストが応答しなかったため、確立された接続は失敗しました。

 サーバーの80番ポートが応答しないから8080番で再試行(あるいは逆に8080⇒80)してみたけど失敗というパターンです。ネットワークの問題も考えられますが、恐らくはサーバーが落ちているのでしょう。冒頭に引用したフォーラムの内容から察するに、新規WorkUnitを生成する作業などを行っているのだと想像されます。

WARNING:WU00:FS00:Failed to get assignment from 'HOSTADDRESS:8080': No WUs available for this configuration

 この構成に向けたWorkUnitは無いと言っているパターンです。CPU用とGPU用ではWorkUnitが違う*3ため、CPU用のWorkUnitを要求した時に、サーバーにはGPU用のWorkUnitしかなかった場合や、逆にGPU用を要求した時にCPU用のWorkUnitしかないとか言った場合に発生するのだと思われます。なお、GPU用WorkUnitも多分CUDA用とOpenGL用の2種類が存在してそうな気がします*4ので、非NVIDIAプラットフォームのGPU搭載機でCUDA用WorkUnitしか持ってないサーバーに要求した場合も同様の挙動となると想像されます。

ERROR:WU00:FS00:Exception: Server did not assign work unit

 サーバーがWorkUnitを割り振ってくれないと言っているパターンです。configurationについての指摘が無いので、単にそのサーバーが割り当てるWorkUnitが尽きたのだと想像されます。

 これらのログが継続している場合でも、相手側サーバーのIPアドレスは逐次異なっています。要するに、理由はどうであれWorkUnitを得られなければ、(リトライ間隔を増して待機したうえで)他のサーバーに仕事をもらいに行くという挙動となっています。これを複数回繰り返して、WorkUnitを割り当ててくれるサーバを引き当てれば無事ダウンロードが始まるという流れです。

 そこで、request-wsコマンドが使えそうな気がします。Request work server assignment from the assignment server.と書いてありますから。

> request-ws

PyON 1 error
"Could not get an assignment"

 叩いてみると、上記のように割り当てられなかったと出力されるパターンと、何も出力されずにプロンプトに戻るパターンがあります。何も出力されなかった場合は、WorkUnitを割り当てることができる状態にあるサーバーに割り当てられたという意味だと想像しています(実際は不明)。
 ですが、WorkUnit再ダウンロードまでの時間が膨れ上がっているとダウンロードは開始されません。

 ここでpause / unpauseコマンドが活きました!pause / unpauseコマンドはその引数に指定したスロットの処理を停止/再開することができます。で、説明には何も書かれていないのですが、何故かunpauseした際にNextAttempt時間が激減します。unpause後に即時~約1分30秒以内にダウンロードが試行されるような挙動を示します。

 なお、前述のrequest-wsで"Could not get an assignment"が表示されなかった後でも、unpause後のダウンロード試行で失敗することは多々あります。
 故に、ダウンロードが開始されるまで、pause ⇒ request-ws ⇒ unpauseを繰り返せば良さそうだと解ります。手で叩くのは面倒ですし、そもそもこの状態にいつ陥っているのか解りませんので、状態検知も含めて自動化しましょう。
 以下のスクリプトはWSLでUbuntu18.04を導入済みのWindows10Pro環境で動作を確認しています。

  • 事前準備

 telnetの自動応答をするためにexpectを使います。未導入の環境ではexpectパッケージを導入してください。

$ sudo apt install expect

 以下のシェルスクリプト(fah_autorequest.sh)と、expectスクリプト(fah_reqws.exp)はwsl環境内の~/bin/に配置する前提です。両スクリプトともに、実行権限を付与してパーミッションは744等にしてください。

$ cat fah_autorequest.sh
#!/bin/bash
TMPFILE=/tmp/fah_response.tmp

# Get status
( sleep 1 && echo queue-info && sleep 1 ) | telnet localhost 36330 > ${TMPFILE}

# Parse the response
# 使用するマシンのスロット数によってループ範囲を調整(GPU非搭載機は00だけにする)
for ID in 00 01; do
  grep "\"slot\": \"${ID}\"" ${TMPFILE} | grep "\"state\": \"DOWNLOAD\"" | sed -e 's/,/\r\n/g' | grep next | grep -v "0.00 secs" > /dev/null
  if [[ $? == 0 ]]; then
    grep "\"slot\": \"${ID}\"" ${TMPFILE} | grep "\"state\": \"RUNNING\"" > /dev/null
    if [[ $? == 1 ]]; then
      echo "Slot:${ID} needs work unit!"
      ~/bin/fah_reqws.exp ${ID}
    fi
  fi
done

rm ${TMPFILE}
  • 自動応答用expectスクリプト(pauseしてCould not get an assignmentが出なくなるまでrequest-wsをSLEEPTIME毎に叩き、unpauseして終了)(Folding@home 7.5.1用、7.6.9では期待した動作をしません)
$ cat fah_reqws.exp
#!/usr/bin/expect

set TARGETHOST localhost
set TARGETPORT 36330
set TARGETSLOT [lrange $argv 0 0]
set SLEEPTIME 10
set timeout 5

spawn env LANG=C /usr/bin/telnet ${TARGETHOST} ${TARGETPORT}

expect {
  "Welcome to the Folding@home Client command server." {
    send "pause ${TARGETSLOT}\n"
    sleep 1
    send "request-ws\n"
    exp_continue
  }
  "Could not get an assignment" {
    sleep ${SLEEPTIME}
    send "request-ws\n"
    exp_continue
  }
  timeout {
    send "unpause ${TARGETSLOT}\n"
    sleep 1
    send-user "finished request work-server for slot ${TARGETSLOT}\n"
    exit
  }
}

 unpause後にWorkUnitが確実にダウンロードできるとは限りませんので、fah_autorequest.shをサイクリックに叩く必要があります。ここではWindows標準のタスクスケジューラを使うことにします*5
 タスクスケジューラからWSL内のシェルスクリプトは直接は叩けないので、ラッパーとなるバッチファイル(fah_autorequest.bat)を書きます。配置パスは任意の場所で構いません。

>type fah_autorequest.bat
wsl ~/bin/fah_autorequest.sh

 このバッチファイルをタスクスケジューラから一定間隔(最短5分毎)で実行すれば、WorkUnitのダウンロードのNextAttemptに何時間も待たされるようなことは無くなります*6
 これで、SARS-CoV-2を殲滅するための研究に、より多くの時間のコンピューティングリソースを貢献できます!
 

以下追記

 7.6.9でコマンドサーバーの挙動が変わったことに対応したスクリプトです。ウェルカムメッセージが変わっている事、request-wsのレスポンスが無くなったことに対応し、単にpause/unpauseのみ実行します。一見無意味ですが、前述の通りpause/unpauseでリトライ間隔が劇的に短縮される挙動は変わっていないようですので、リトライ時間が雪だるま式に膨れ上がるのは抑止できます。
 ※このスクリプトは場当たり的に修正しただけで、長時間動かして問題ないことを確認したわけではありません。予期しない問題が発生する可能性があります。

#!/usr/bin/expect

set TARGETHOST localhost
set TARGETPORT 36330
set TARGETSLOT [lrange $argv 0 0]
set SLEEPTIME 3
set timeout 5

spawn env LANG=C /usr/bin/telnet ${TARGETHOST} ${TARGETPORT}

expect {
  "Welcome to the FAHClient command server." {
    send "pause ${TARGETSLOT}\n"
    sleep 1
    send "unpause ${TARGETSLOT}\n"
    sleep 1
    send-user "finished request work-server for slot ${TARGETSLOT}\n"
    exit
  }
}

 



以上。

*1:文中の夜間(overnight)は現地時間の意味だと思われます。

*2:FAHControlのConfigureボタンからConnectionタブを選択すると一番下にPassword設定があります。パスワード設定した場合、ポート36330に接続後にパスワードを尋ねてはこないので、authコマンドでパスワード入力します。

*3:FahCore:0xA7がAMD64(X86?)用、FahCore:0x22がOpenGL用を意味しているのではと想像しています。

*4:Folding@homeを実行できるNVIDIAプラットフォームが手元に無いので未確認。

*5:既にWSLでcrondを常時生かしている環境であれば、タスクスケジューラを使わずにcronからfah_autorequest.shを数分おきに叩けばいいです。

*6:あまりにも短時間間隔で実行すると、上記スクリプト排他制御を行っていないため、多重起動されてpause/unpause周りでおかしな挙動をするかもしれません。そもそも、短時間過ぎるのはFolding@homeのサーバー側にも多少なりとも負荷をかける可能性もありますから自重しましょう。