2025年1月26日日曜日

Creality Space Pi フィラメントドライヤー、利用にあたってのベストプラクティス

 3Dプリンター向けのフィラメントを乾燥させるには何等かのフィラメントドライヤーが必要です。私は正直あまり何も考えずに Creality Space Pi フィラメントドライヤーを買いました。Creality Space Pi フィラメントドライヤーを利用するにあたって気づいたことをまとめてみます。



Space Pi フィラメントドライヤー 1ロール



どうしてこのドライヤーを選んだか

買うにあたって「これでいいや」と考えたポイントは以下でした。

  • 置き場所の制約から1スプール用のドライヤー
  • ドライヤーからプリンターにフィラメントを直接フィードできる

他のフィラメントドライヤーと比較したことがないので良し悪しはわからないですが、とりあえず結果的に使えているので大きな問題は感じていません。ただ、この代わりになるものを買い足すのなら、次はコンベクションオーブン等を買ったほうがよいと思っています。


使ってみて気づいたこと

乾燥温度が70度までしか上がりません。 ABS や TPU などのフィラメントでは 80 度前後の乾燥温度が推奨されていることがありますが、このドライヤーではそこまでの温度に上げることはできません。代わりに乾燥時間を長めに設定することで対処します。

フィラメント供給用の穴がありますが、ここからフィラメントと裸で供給するなら問題ないかもしれませんが PTFEチューブで保護した状態で3Dプリンターに送り出したい場合は部品交換したほうがよさそうです。この点については後述します。

スプールがある程度回転するよう、小さなローラーで支えられていますが、ダンボール製スプールなどは安定して供給できないように見えます。複数の理由から、ダンボールスプールに巻かれたフィラメントはプラスチック製のスプールに巻きなおしてから乾燥をはじめるのが正解と思っています。この点についても後述します。

ディスプレイ画面部分が脆いです。タッチパネルとして指で押すだけでも表示が崩れることがありますし、メンテナンスのときにディスプレイ部分を下にして置いたら、壊れたかなと思うぐらいに表示がバグったことがあります(最終的に元に戻りましたが)。温度や時間を操作できなくなると完全なゴミと化します。ここは利用上の注意が必要そうに見えます。ディスプレイ保護フィルムなどを貼っておくのも保険になるかもしれません。


フィラメント供給用穴のアップグレード

ドライヤーの中からフィラメントを供給したい場合のために穴があり、ゴム状のフタがついています。ここからフィラメントを直接供給する場合はよいのですが、せっかく乾燥させたフィラメントを、吸湿を防ぎながら3Dプリンターに送りたければPTFEチューブで接続したくなります。

超初心者な私は、ドライヤー本体のゴム状のフタにPTFEチューブを刺してつないだのですが、特に固めのフィラメントだとPTFEチューブが抵抗にまけてフィラメントドライヤー内に引き込まれ、スプールに巻き付くというトラブルになり大変な思いをしました。また、フィラメント押し出し量が不安定になり造形不良につながっているかもしれません。

解決先として、フタをこちらに含まれる PC4-M6 の継ぎ手に交換しました。


Litorange PTFEチューブ テフロンチューブ 2mm x 4mm x 2M + PC4-M6 ワンタッチ継手 + PC4-M10 ストレート空気圧継手 1.75mmフィラメント用 3Dプリンターパーツセット (ホワイト)

こちらには長めのPTFEチューブも付いており、フィラメントの供給用にもちょうど良いです。取付には別途M6のナットが必要です。通常のナットだと必要以上の高さがあるので 5mm 程度の M6ナットを3Dプリントしてもよいかもしれませんが(その場合70度の乾燥に耐えるフィラメントを使う必要があります)

Aurooooa 50個入 M6 六角ロックナット ナイロン ロック ナット 高さ6mm 六角幅10mm 304ステンレス鋼 ナイロンインサートセルフロック

このコネクタを取り付けることにより PTFE チューブを本体のフィラメント排出穴に取り付けできるようになり、PTFEチューブで保護した状態で3Dプリンターに送り出せます。



ダンボールスプールからプラスチックスプールに巻き直す

ダンボールスプールに巻かれたフィラメントを乾燥したり造形に使用したりするには以下の問題があると考えてました。

  • ダンボールの吸湿性がそもそも高いので、乾燥させたフィラメントがまたダンボールを通じて吸湿する可能性がある
  • ダンボールスプールのまま Bambu AMS やドライヤーのローラーの上で回すとしても、送り出しに抵抗が発生してうまくいかない
  • ダンボールスプールとローラーが接触する部分を保護するためのリングのモデルが存在するが、PLAなどで造形したこのリングは70度の乾燥に耐えられず歪んでしまう(PAなどで造形すれば使える?)
  • ダンボールが摩耗し紙の粉が発生するとフィラメントに不純物が混じる可能性がある


まあそりゃそうだろという感じではあるが、
70度乾燥に耐えられなかったPLA製ダンボールスプールリング
ローラー跡がついているほか全体的に歪んでいる


こういった理由から、手元では、ダンボールスプールに巻かれたフィラメントはプラチックのスプールに巻き直して使用するほうが遥かに扱いやすく、トラブル切り分けをシンプルにできるため、悩みたくなければ開封してまずはプラスチックスプールに巻き直してしまったほうが良いと考えています。

Bambu の再利用可能スプール。70度の乾燥に耐えられると表記があります
Creality Space Pi ドライヤーの最大温度(70度)で使用して、
いまのところ問題ありません


手元では以下のフィラメントリワンダーを造形し、余っている Bambu の再利用可能スプールに巻き直しています。最初は道具なしで巻き直してみたのですが、それではきれいに巻き直すことができませんでした。手作業で苦労して巻き直しているうちにフィラメントに不純物が付着したり、巻きが汚くなったりすることで送り出し~造形時の安定性にリスクを感じました。スプール間の巻き直しに道具は必要です。



Filament Spool Switcher for Bambulab AMS and AMS Lite

このフィラメント巻き器は特に Bambu スプールの径にあわせて作られているので、手元のニーズにあっています。巻取り先スプールが別のものであれば別の巻き器のデザインを見る必要があるのかもしれません。

2025年1月24日金曜日

3Dプリンタを初めて購入した感想など

今更ですが3Dプリンタ Bambu Lab P1S を購入しました。

思ったより楽しくて日々色々出力してみてるのですが、思ったことなどを色々まとめておきたいと思います。

なお3Dプリンタや樹脂を溶かすといった経験についてはここ2カ月弱の経験しかないのでとんちんかんな事を書いているかもしれませんが多めに見て、場合によっては Twitter などでやさしく教えてください。

場所がないので玄関に置いている
背面へのアクセス、上部からのアクセスなどが制限されるので
本当は良い置き場所とはいえない


AMS(フィラメント自動切り替えシステム)は便利

Bambu Lab の現行プリンタは AMS もしくは AMS Lite と呼ばれるフィラント素材を自動的に切り替えてくれるシステムが存在しています。同社のプリンタでは P1S, X1 Carbon では AMS と呼ばれるエンクロージャ式のものが利用できます。AMSには以下のメリットがありました。

  • 多色出力ができる。もちろんフルカラーなどではないですが一部色を変えたい道具類を作りたいときにはとても便利です。
  • サポートのインターフェイスにだけ別素材を使える。後でサポートの取り外しが楽になります。
  • 素材の切り替えが自動。PLA、PETG、ウッドPLAなど複数の素材を自動的に行き来できる。
  • いちおう密閉されておりフィラメントの吸湿をある程度抑えられる。フィラメントを利用可能な状態を保てるため、フィラメント交換や出力を気軽にできます。
X1 Carbon か P1S か

Bambu Lab での個人向け現行最上位機種は X1 Carbon 、それの廉価モデルが P1S になります。はじめての3Dプリンタであったこと、予算的に安いほうがよいと感じたことから P1S 一択で買いましたが、使い始めて知ることで、何が違うのかも色々判ってきました。

X1 Carbon と P1S の差違は基本的に、温度が高い「 PLA や PETG 以外の材質を使いたいかどうか」かなと思いました。

X1 Carbon は高温で出力する素材に対応できる部品が採用されていたり、出力品質安定化のために庫内を暖めたり、高温で臭いやガス、細かな粒子の発生する状況に備えてHEPAフィルタが付いていたりするという点です。総じて PLA や PETG なら P1S で出力すると最初から割り切っていれば P1S で十分ですが、ラボっぽく、いろんな素材を試してみたいと思うのなら X1 Carbon のほうがチャレンジできる幅が広くなります。

一方 P1S でもノズルや押し出しギアなどを X1 Carbon に交換でき、これである程度の素材までは対応できます。とはいえチャンバーの温度コントールなどを後から実装するのは現実的ではないので、 X1 Carbon と互角までは持っていけないですね。

そのほか筐体が金属かプラスチックか、ロッドがカーボンか、ベッドから造形物が外れてしまったら検出できるAIセンサーなどの差がありますが、 P1S でも、とりあえず大抵のことはできるという印象です。 X1 Carbon を買わなかった後悔はせずとも、次の3Dプリンタを選ぶときに自分が選ぶもののスペックが判るようにはなりました。

ノズルの素材と径

私は 0.2mm と 0.6mm の焼き入れスチールを本体とともに購入しましたが、結果的に 0.2mm は使っていません。改めて、焼き入れスチール 0.4mm ノズルを購入して、今はこれが主力になっています。

P1S 向けで提供されるノズルには「ステンレススチール」と「焼き入れスチール」の二種類があります。 P1S では標準で 0.4mm ステンレススチールが装着されていますが、後で交換可能です。

積層型のプリンタでは 0.4mm の径がいまのところ出力品質と速度のバランスが取れており、よく使われる径のようです。スライサー(出力をコントロールするプログラム)の品質向上により 0.6mm でもかなりキレイに出力できるようになってきたようですが、トラブルなくサクサク出力したいなら 0.4mm が安心です。一方、木材を練り込まれたフィラメントなど特殊なものを利用してみたい場合や出力速度を稼ぎたいときには 0.6mm も欲しくなります。

P1Sに標準搭載のステンレススチールのノズルは PLA/PETG を使っているうちは問題ないですが、焼き入れスチールになると摩耗耐性が上がります。より固い材質、カーボンファイバーやガラスファイバーが含まれるフィラメント、ナイロン系フィラメントなどを利用するには焼き入れスチールのノズルが欲しくなります。たとえば歯車などの機械的構造の部品を作りたいとか思ったときに、ここで使える材料に差がでてきます。

 
P1S だと付属はステンレススチールですが、X1 Carbon 同等の焼き入れスチールに交換できるので、必要となったときにさくっと焼き入れスチールに交換してしまうのが良いかと思います。径を変えたいと思わない限りは、もうほとんど交換の手間もありません。

標準の 0.4mm スチールノズルから 0.4mm焼き込みスチールに交換する様子
これでABSやカーボン、ナイロンなども扱えるようになる
焼き込みスチールノズルに交換するなら、
押し出しギアも強化ギア(黒)に変えてしまおう

ツールヘッドから写真の押し出しギアまわりやノズルの部品を取り外して交換作業をしてみると、3Dプリンタのツールヘッドの中身がわかりますし、詰まりの時に何をしたら良いか想像できるようになるので、よい経験になりました。


3Dプリンタで作った3Dプリンタ備品

3Dプリンタを便利に利用するために便利な小物は3Dプリンタで作れます。騒音問題などがなければ、寝るタイミングなどで以下のようなものを仕掛けて作っておくとよいと思います。私は以下のものを作りました。
  • フィラメント交換時に発生するごみフィラメント(poop💩)を受けるくず箱
  • AMSをシリカゲル除湿剤をセットするためのケース
  • X1 / P1 PTFE Tool - P1SとAMSの間などを接続する通信ケーブルやPTFEチューブの付けけ外しに便利

とりあえず シワなしPITのり

PLA/PETGなどだけ扱うのならあまり問題ないでしょうが、取り扱うが難しい素材にチャレンジすると、ベッドにのりをつけて定着改善&PEIプレートを保護したくなります。

まだよく判っていないのですが、 PEI プレートというのは鋼にプラスチックのシールが貼り付けてあるようなものみたいですね。また Bambu 公式サイトを見てもエンジニアリングプレートが出ていないです。結果として本来推奨されない PEI プレート上でエンジニアリングプラスチックを造形しちゃえということになります。

しかし、造形中に外れてしまったり造形物を取り外すときに PEI プレートの表面をもっていかれないように気をつけなければいけません。そのためにはスティックのりをベッドに塗ることになります。

もともと手元にあった大塚商会取り扱いのスティックのり(PVP)を使いましたが、これを塗ると厚みがでてしまい、とても塗りにくいです。シワなしPITはもっと液状っぽくて塗りやすいです。結果としてベッドの段差もできにくいので、とりあえずシワなしPITを買って持っておくのが正解だと思います。

手前にある白のがPVPのスティックのり。
造形中の Benchy の下には実はシワなしPITが塗ってありますが、
薄くぬれているので判らないと思います


フィラメント乾燥機は事実上必要。専用機か食品用のオーブンか

PLAやPETGなどの比較的取り扱いが簡単なフィラメントを気軽に使うためだけでも、フィラメントスプール専用の乾燥機は持っていて損しないと思います。これらの乾燥機からPTFEチューブで直接3Dプリンタに出力できる点は魅力です。

PLA、PETGなら既製の専用フィラメント乾燥機がお手軽だと思っています。ただABSなどのより上位の素材を扱いはじめると、乾燥温度を80度を求められたりすると、このようなフィラメント乾燥機では手に負えなくなってきます。あまり加熱するとプラスチックのスプール自体が溶けてしまうんですね...手元ではABSやナイロンは70度でのんびり乾燥させています。

使い混んでいる方のなかには食品用のコンベクションオーブンを利用している方もいるようで、使いこなせてきたら、そういったものもアリかなと思います。


ノズルの交換がめんどくさい

P1S のノズルの交換はけっこう面倒くさいです。

出力直後だとノズルが熱い可能性があるので火傷の不安があります。私はサーマルカメラを持っているので、視覚的に火傷する温度かどうかは確認できるので何度か確認して、「ああ、この状態でこれぐらい経ったらこれぐらいの温度なんだ」というのを知ってしまいましたが、普通はそんなもの持ってないですよね。

また、Bambu Labの3Dプリンタの場合、ノズル交換にあたって小さなコネクタを3つ付けたり外したりする必要がありますが、私はコレ一発で正しく付けられません。コネクタが小さくて取り付けるのが難しい、壊してしまいそうという不安に加えて、 P1S は構造上プリントヘッドの前側にフレームがくるので視界がとおりにくく、この交換がしづらいです。クリック感がないのでうまく装着できたかわからず、動作させてみたから「ファンの回転数異常です」などのエラーメッセージを受けて問題に気づく、みたいな感じになります。このため必要以上にノズル交換をしたくありません。

コネクタ取り付け失敗対策として、コネクタの高さに対してマーカーでしるしを付けてみました。しるしを付けてからコネクタを弄っていないので、役に立つかどうかはこれからです。

コネクタ縦位置のマーカー

スプールは造形しなくても溜まる

購入した非純正フィラメントや交換用フィラメントだとスプールが無かったり、ダンボール製で印刷に向かなかったりすることがあります。ひとつの解決策はスプールを3Dプリントすることです。

でも、本当に色々なフィラメントで遊び始めると、すぐに再利用可能スプールが溜まりはじめると思います。たとえば Bambu サイトで購入可能なマットPLAのフィラメントは交換用パックとしての販売がないので、これを使おうと思うと必然的に再利用可能なスプールが手元に増えるんです。


PETG(青)でスプールを造形したが結局一度も使わず。
Bambu 純正フィラメントのスプールが何本も余りはじめている

スプールは探すと3Dモデルが出てきて、これを造形すれば造り出すことができます。低コストなフィラメントを探して回る旅をしているのならともかく、 Bambu 純正スプールを何本か試すつもりなら、たぶん必然的に予備スプールが生まれるので、本当に3Dプリントしてまでスプールを増やす必要があるかは疑わしいです。

カーボン強化フィラメント・ガラス強化フィラメントのリスクとナイロン系フィラメントを考える

本体購入と合わせて PLA-CF (カーボンファイバー強化PLA)のフィラメントを購入したり、ABS-GF(ガラスファイバー強化ABS)をフィラメントを購入したりしましたが、ABS-GFは造形しましたがPLA-CFについてはまだ未開封です。特に PLA-CF については、結果的には、意図的に未開封です。

特にカーボン強化フィラメントはフィラメント内にカーボン繊維が練り込まれていて、これは一度体に吸い込むと分解されずに体のどこかに刺さったまま蓄積されてしまう物質ということになると思います。テストプリントでそんなものをボロボロ出してみたり、それをノリで削ったりして吸い込むと本当に体によくないんですね。BambuのCFは日用品の出力例が示されていますが、一般論的に、CFやGFは出力後に塗料で塗装して封印してしまうとか、そういう使い方を前提としてほうがいいのかなと思います。

素材の特性で避けられないということなら仕方ないですが、PLA-CFやABS-GFなどよりも安全そうなものとして、PAや、非純正であればPolymakerのCoPAなどがあります。PAやCoPAは所謂ナイロン系のフィラメントで、こちらは造形時に熱で溶かして一時的に有害なガスが出ることはあっても、カーボンファイバーみたく呼吸器に刺さった状態で蓄積するような恐ろしさは少なそう。



750g 1万円ちょっとと、割と値がはりますが、一週間ほど試行錯誤して、手元のP1Sで割と安定して出力できるようにパラメータを作成することができました。ということで、手元では機械的な部品などを作るときの強化フィラメントとしては、色の選択肢などは少ないですが、まずはCoPAを中心に選ぶことになりそうです。


中国メーカーのサーバーにデータを送るというリスクをどう見るか、直近のネットワーク認証強化についての議論

Bambu Lab 社のプリンタで直近話題になっているネットワーク認証強化の件にもからむところですが、 Bambu Lab のプリンタはアプリケションからプリンタに対して、ジョブをインターネット経由で送信する仕組みになっています。

実は私もこの点は購入前に気になっていて、ある程度割り切って使うことにしました。 Bambu Lab P1S が便利なところは割と設定なしでもモデルを食わせれば勝手に造形物が出てくるところにあり、クラウドへの依存は設定操作をシンプル化するための仕組みです。

自分の造形物データを盗まれるかもしれないとかそういう可能性はゼロではないと思いますが、まぁ自分の造形データを盗むほど価値はないですし、どちらかといえば同社がサービス停止した後にプリンタが文鎮化するのではないかという点を気にしました。本当にクラウドに流したくないデータはSDカード経由でプリンタにデータを入れて造形開始することも可能なので、そういう時はそうするつもりです。

最近ネットワーク認証強化のために、結果的に Orca Slicer などのオープンソースプロジェクトからプリンタへのジョブ直接投入ができなくなるとか、プリンタがセキュリティ対策をせずに丸出しにしていたインターフェイスを使ったサードパーティモジュールが使えなくなるとか、そういった話題で「課金しないとプリンタを使えなくしようとしている」とか大騒ぎしているインフルエンサーがいますが、私にはそこまでには見えません。

3Dプリンタはオープンソース文化との親和性が高く、スライサーソフトなどもユーザや業界の協調で発展してきた経緯から、プロプライエタリなロックが発生するのだと敏感に反応しているように見えます。一方、3Dプリント界隈はコンピュータエンジニア畑ではない人が中心に、「いままでに無かったロックは怖い」というお気持ちを表明しているようにしか、私には見えていません。

恐らく同社が守りたいのは同社が提供するサーバーと、その先に接続されているユーザーのプリンターだと思っています。
Bambu Studio やそれをフォークした Orca Slicer など、そこに含まれたコードが
場合によっては悪意ある攻撃コードの土台にもなりうると思えば、
無償提供・APIが公開されていない自社サービスに対して
サードパーティーが直接接続することを嫌う気持ちはわかる。

ローカルネットワークからプリンタ自体への接続も自社ソフト経由というのは
確かに少しやりすぎにも感じるが、乗っ取られた無線ルータなどから発火させられる
リスク等を回避する方法のひとつ思えばまぁ判る。筋がいいかは微妙だが


最新の家庭用プリンタは 350度ぐらいまでノズル温度を上昇させることができるようになってきて、そこまでの温度で造形するものだとチャンバー温度を高く保てるモデルが増えてくると思います。そういったプリンタをインターネットから操作できるようにして、攻撃者がウチのプリンタを加熱させて意図的に火事を起こすとかそういう行為ができるようなセキュリティホールなら、同社はそれを対処する必要があります。

同社からは、プリンタの認証機能をアップグレードせずに既存のソフトウェアを使い続けたければ現行バージョンを使い続けることで従来のサードパーティーも利用できると案内されていますし、 Developer モードを使えばローカルネットワークでそのセキュリティ機能を回避できる選択肢が用意されることが案内されました。
実際のところiPhone や Android のアプリストアはこの件以上にプラットフォームが強い規制をしていますし、これらのスマホやOculus Questなども同じようにストアを経由しないソフトを動かすためには Developer モードを使用します。コンピュータハードウェアの世界では何年か前にソフトウェアのライセンスを変更して、新ファームウェアにアップロードした後は特定形態での使用を禁止するような製品価値を差損するEULA変更をした企業もありましたが、それと比べたら大した話でもありません。
今回の件が本当に気になるなら、他社の製品を選択したり、純粋にオープンソースのものを選ぶなど、自由に広い選択肢から好きなものを選べばいいと思います。

2024年9月29日日曜日

IkaLogの開発で得た知見: ニューラルネットワークを用いたブキ分類器の思想と実装

以前IkaLogの開発で得た知見:大量の動画や画像を取り扱う際の Tips にて、 IkaLog が使用していたブキ認識において、いかに実ユーザ環境からの送信データを活用しながらいかに効率的に教師データを揃えることができたかを紹介しました。

最近は LLM や生成 AI などが話題をもっていきがちですが、独自のモデルを構築してアプリケーションに組み込もうというアイデアで取り組まれている方も増えているのではないでしょうか。

私自身が取り組んだのは 2015-2017年頭の話なので昔話でしかないとはいえ、もしかしたら新規アプリケーションを作られる方の参考になるかもしれない?と思ったので、当時どのような思考で作業をしていたか、もう少しまとめてみる事にしました。

今回は、 IkaLog のブキ認識において、最終的にシンプルなニューラルネットワークに行きついたかについて解説します。

バックグラウンド: IkaLog で目指した性能と、当時のハードウェア制約

精度99%では満足できなかった理由

スプラトゥーンでは、合計8名のプレイヤーがふたつのチームに分かれて勝敗を競います。このときひとりひとりのプレイヤーが100クラス以上のブキのなかからひとつ選んで勝負に挑みます。


ブキにはそれぞれシューター/ローラー/ブラスターなどのクラスの特徴があるほか、攻撃力や距離、連射力で差別化がされています。また、ブキには戦局を有利に進めたり、不利な場面を打開するために活用できるサブウェポンやスペシャルウェポンが用意されています。

IkaLog で処理される勝敗データでは、各プレイヤーがどのメインブキを使用しているか、またサブウェポンやスペシャルウェポンが何であるかを正しく記録することが、戦績データを扱うためのしくみとして重要だと考えていました。

※ この統計自体には IkaLog は使われていません


先述のとおり、一回の対戦において8名のプレイヤーがゲームに参加します。これは 99% の分類器があったとしても、対戦で使われたブキすべてが正しく分類できる確率は 0.99^8 = 92.2% にしかなりません。このため IkaLog ではブキ分類器で 99.9% 以上の精度が必要であろうと考えていました。

スプラトゥーンのリザルト画面からのブキ認識は今のAIの仕組みから考えれば、とても簡単な仕組みです。画面の同じ場所に同じ画像が表示されるだけ。ただ、バグありのビデオキャプチャデバイス、HDMIでさりげなく入るノイズ、ユーザーのセットアップなどの理由で、ソフトウェア観点からみると「同じ画像が毎回表示される」前提ではブキ分類器を実現できませんでした。


当時のハードウェア事情

また、 IkaLog を開発していた 2015 年頃はまだゲーミングPCらしくゲーミングPCを一般的なプレイヤー層は持っていることは想定できませんでした。

ましてや Wii U でスプラトゥーンをプレイしているユーザがとなりに AI で十分な性能を発揮できるようなハードウェアを持っておらず、ゲームをしている人が最新CPU搭載のコンピュータを所有しているかも怪しいです(今でも多くのスプラトゥーンプレイヤーでも余裕のあるPCを持っておらず、むしろスマホのほうが性能が高い可能性すらあるでしょう)。当時の事情でいえば、x86 CPUでの演算において SSE4.2 程度の命令セットしか期待できなかった時期での取り組みでした。私自身が認識できていたユーザには Core 2 Duo で IkaLog を常用している方もいらっしゃいました。

IkaLog がリアルタイムで画像認識をするというコンセプトであったため、当時の記録によると、8キャラクタ分のブキ分類を含めてリザルト画面の解析をおよそ3秒以内で実現できることをひとつの目安としていたようです。


IkaLogの開発においてたどった当初の分類器のしくみや遷移

K近傍法

IkaLog では、当初は OpenCV の K近傍法 の実装を用いて、入力画像に対してもっとも特徴量ベクトルが近い近傍があるクラスに分類するアプローチで実装していました。

開発開始当初はブキ数が30程度だったかと思いますが、アップデート終了までにクラス数が100を超えるほどに増えました。結果論ですが、単純なテンプレートマッチングやSVMなどといったアプローチをとっていたら、実行時間の観点から、より苦労していたと思います(スプラトゥーン2向けの実装では数字などのキャラクタ認識でSVMを利用することも検討し取り組みましたが、このワークロードではKNNほどコスパよくありませんでした)。

KNN の実装は当時の CPU でアイコン程度を分類するのは十分に高速だったほか、分類器の訓練も高速でしたので、開発初期からこのアプローチにたどり着いていたことはとても助かりました。データ量が少ない状態から半自動的に訓練データを集めようとしたときに、いまでもとりあえず使うことが多いです。


色相のヒストグラム

ブキの分類器を作り始めた当初、最初に実装したものは色相のヒストグラムからパターンを見つけて分類が可能かどうかを試していました。


ただ、スプラトゥーンのリザルト画面では、所属チームによりブキ画像の背景にチーム色が映り込み、場合によってはプレイヤーが選んだ装備品もブキに重なります。プレイヤーのキャプチャーデバイスの設定などにも依存することもあり、あまり実用的な精度は出せませんでした。

ラプラシアンフィルタ

輪郭抽出は古典的な画像の分類アプローチでよく使われる手法の一つかと思います。IkaLogでもラプラシアンフィルタを適用し、カラー画像から次元数を削減した特徴量を生成し、ここから分類を行う方法を試していました。一時期のバージョンの IkaLog では実際にこのアプローチで提供していたと思います。

IkaLogのブキ分類のワークロードにおいてラプラシアンフィルタはチームの背景色の影響を排除し、ブキの形状に基づいて分類するには良い方法に思えます。しかし、ユーザの映像キャプチャ環境において元画像が 720/1080p だったり、それを480pにリスケールした映像が投入されたりといったかたちで想定外の入力がされると精度を保てないという問題が生じました。

最終的なチャレンジはスプラトゥーンのアップデートそのものでも発生しました。スプラトゥーンではメインブキに対して、サブウェポン・スペシャルウェポン違いのバージョンとして「カスタム」「コラボ」といった亜種が登場します。これらの亜種ではブキ画像の右下に小さな追加マークが表示されるのですが、ラプラシアンフィルタを介して色相情報を落とした状態でこれらの特徴を合理的に見分けることはできませんでした。



ニューラルネットワーク導入の決断

IkaLog を作り始めて自然と機械学習的アプローチに関わるようになっていたことから、 Cousera の Andrew Ng 先生のコースなども一通り修了していた頃に「もうニューラルネットワークにカラー画像をそのまま入力したほうがいいんじゃないか」考えるようになってきていました。

とはいえ、AlexNet などの既存のニューラルネットワークは100MB以上の重みデータがありますが、さすがにこれは過剰ですし、ユーザがそんなもので推論できるようなプロセッサを持っていませんし、CUDAをユーザのプロセッサで実行できるわけでもありません。このため IkaLog で目的に合わせたニューラルネットワークを実装することを考え始めました。

IkaLog のブキ分類においてニューラルネットワークに画像をそのまま入力することによる一つのメリットは、ニューラルネットワークであれば背景色などを無視できることがあります。チームカラーによって何色になるかわからないようなピクセルに基づく入力値は、結果的に無視されるようになります。ブキの形状によって適切な重みが自動的に形成されることを想定できたので、おそらく簡単にうまくいくだろうと思いました。


HSV色空間

RGBとHSVでしっかり比較したわけではないのですが、ブキ分類器での分類対象ではHSV色空間で取り扱ったほうがよいだろうと判断したので、何も考えずにHSV色空間を特徴量として使用しています。

HSV色空間を利用しようと思った最大の理由は、ブキの「カスタム」「コラボ」といった亜種の特徴を表現する色相がピクセルあたりひとつのパラメータで表現されることになるので、おそらくRGB色空間で扱われるよりもいいだろう、ぐらいにしか考えていませんでした。背景の色相を無視するという観点でも重みが小さくなることで簡単に表現できるでしょう。ここについては「こうなってくれたらいいな」という思想でしかなくて、現時点でもそう思っているだけで、これによる差があったかどうかは何も検証していません。


ネットワーク構成をシンプルに

使用するレイヤとしては単純な全結合とReLUに制約することにしました。理由はふたつあり、一つ目は計算量、二つ目は再実装のしやすさです。

計算量の観点では、畳み込みフィルタなども考えましたが、当時 MacBook Pro (2014) とその上の GeForce チップ、また Haswell Refresh プロセッサで走るニューラルネットワークの速度を見ていると、 CNN やプーリングをエンドユーザのプロセッサで実行させることは現実的ではないだろうと感じていました。単純な全結合と ReLU 程度であれば、当時で型落ちとなっていたプロセッサ上でも NumPy やその下位のライブラリが現実的なスピードで動いてくれるだろうと期待しました。

再実装の観点では IkaLog に取り組んでいた当時でニューラルネットワークを動かそうと思うと、 Caffe を使うとか、もしくは Chainer を使うとか、そういったいくつかのフレームワークを利用する方法でした。 ONNX ランタイムみたいなものはまだ出てきておらず、想定するユーザ層が中学生・高校生・大学生や社会人で、主に Windows ユーザであろうことを考えると、既存のフレームワークを IkaLog のためにセットアップさせるのは不可能でしょう。

IkaLog は zip ファイルを展開して実行すれば使える状態の配布形態を維持することを心がけていたので、ブキの分類器のためにフレームワークへ依存を追加することにためらいました。このため、シンプルなネットワーク構成とすることで、IkaLog用に推論コードを作成するコストを最低限に抑えることにしました。


実装を進める前の事前確認を Azure Machine Learning で実行

それまでの取り組みである程度のデータ量は確保できていたので、まずは手元のデータセットを用いて最低限の作業でアプローチを検証するため、 Azure Machine Learning に想定する特徴量をアップロードして、 MLPで期待するようなモデルが実現するのかを確認しました。

Azure ML はこの程度のワークロードであればファイルをアップロードしポチポチするだけでいいですし、 confusion matrix などもさくっと出してくれるので、アプローチ上問題がないことを簡単に確認できましたしコードを具体的に書く前に最低限の労力で検証できたことはとても助かりました。なおこの Azure ML の体験談は 2016 年頃当時の話であることに留意してください。





学習済みモデルのインポート、推論

実際の学習は Chainer と GeForce GTX 1080 (後半は Tesla P100)で行いました。そもそもどれぐらいのノード数で性能が飽和するかを Chainer 上で検証し、ネットワークの隠れ層のサイズを決めました。

本番の学習は 24 時間などのオーダーで1000エポック以上回したような覚えがあります。 Chainer のチェックポイントとして得られたものをいくつか評価して使用するモデルをきめました。

Chainer フレームワークからネットワークの重み・バイアスを取り出して、単純な NumPy コード上で推論できることを確認できたので、モデルをただのマトリックスとして pickle してファイルに保存、そこからモデルを復元・推論することで、機械学習フレームワークへの依存を断ち切りました。

実際に生成できた実行用モデルファイルをみてみると15MBほどになっていました。この中には32ビット浮動小数点数が並んでおり、zipなどでの圧縮効果がほとんどありません。配布ファイルが大きくなることを嫌って、pickcleする際に16ビット浮動小数点数として扱うことによってファイルサイズを半分に抑えることにしました。このワークロードとモデルにおいて浮動小数点数のビット数を抑えても実用上の影響はほとんど感じられません。最終的に戦績共有サイト stat.ink に投稿されるブキ画像を 99.99% で分類できる精度が得られました。


推論の実装

先述の理由で、 IkaLog ではフルスクラッチかつ最低限のコード量で推論を再実装しました。ここで実装した内容は、のちに発売されるオライリーの「ゼロから作る Deep Learning」の最初の100ページで解説された内容そのものともいえるかと思います。

このブキ分類器は、すでに型落ちとなっていた IvyBridge 2.0 の MacBook Air でも 0.02 秒で実行できました。Core 2 Duo などでも十分な速度で動きましたし、 PYNQ (ARMコア搭載 FPGA)でも1回あたり200ms未満の実行速度で収まりました。

FPGAならPLで実行すりゃもっといけるだろとかそういうツッコミはいくらでも可能かと思いますが、技術的には可能ですが、非営利の独りプロジェクトでここまでやれば十分かなと思っています。


おまけ

当時やってみたかったこと

ネットワークの蒸留や枝切りをしてより配布ファイルのサイズを小さくできるのではないかと考えていましたが、着手しませんでした。

単に手が回っていなかったほか、ネットワークの規模が小さくなったときに、どのクラスにも該当しない画像を特定のクラスに分類してしまう可能性などを恐れていたように思います(実際取り組んだらどうなったかはわかっていません)。


最近のエコシステムに思うこと

ここまでの内容を2017年以前に取り組んだ後、こんなフレームワーク便利だなと思ったものが幾つかでてきました(現時点の選択肢として筋がいいと言いたいわけではありません)。

  • ONNX Runtime の登場によりホストプログラムが雑に使える推論ライブラリが出てきたという印象
  • Intel が OpenVINO や Movidius VPU を出してきて、 Windows PC でハードウェア支援が期待できるようになった。最悪 AMD CPU でも SSE 相当で動くっぽい
  • OpenCV に DNN に対する推論機能が強化されており、配布方法を工夫すれば GPU アクセラレーションなどをホストプログラムから呼び出せるかもしれない。

さらに、2017年の iPhone X から Neural Engine が搭載され、 Android にも同じように推論エンジンがハードウェアとして搭載されるようになりました。 Mac であれば M1 から、 Windows でも Copilot PC が出てきました。

ようやく OS レベルの推論の抽象化が進んできた

ライブラリの観点では、Windows であれば DirectML 、 Apple であれば CoreML 、Androidであれば MLKit などが普通に使えるようになってきて、私が IkaLog を作るときに困った「推論のための仕組みがない。ターゲットごとに実装してられない」という状況に大きな変化が起きているように感じています。特に個人的には DirectML は(ごくたまにしか試してませんが) Windows 環境において OS が推論ワークロードをハードウェアで実行してくれるという、まさに OS らしい抽象化をしてくれるようになりました。

私自身はふだんほとんどプログラムを書かないのですが、ローカルで推論をするアプリケーションの開発難易度は10年前から比べると大きく下がってきたなと感じています。

ローカル LLM の話題などもみかけますが、 Copilot PC の話題などをみつつ 2024 年は、ローカルで推論をするタイプのアプリケーションの開発が加速する年になるだろうなと思っています。2025 年になるとウイルス対策ソフトすら推論用アクセラレータにオフロードするような世界がくるのかもしれませんね。

2024年3月9日土曜日

BOSE SoundBar 900 のメンテナンスと覚え書き

暫く iOS アプリなどから連携不可能な状態になっていた BOSE SoundBar 900 を復旧したので覚え書き。まとまった情報がなかったため理解するまでにかなりの時間を要しました……。


問題

私が対応した個体では下記の問題がありました。

  • ルータの設定上の問題か、 BOSE 社サイトへの通信が通らずアクティベーションできない (iOSアプリにて連携できない)
    • ネットワーク構成の問題かもしれないが、一般的な IPv4 通信はできる環境のはずで、なぜうまくいかないのかわからない
  • アクティベーションされた際、最新ファームウェアへのアップデートが始まるが、完了しない
これに対しての対応方針をかきのように定めました。
  • シンプルなネットワーク上にサウンドバー本体を有線LAN接続しアクティベーションを完了させる
    • 現地のネットワーク構成に依存した問題が発生しているように見える
    • ファクトリーリセット状態からのアクティベーションは LTE ルータをゲートウェイとしたシンプルなネットワークを使用する
  • ファームウェアアップデートをアクティベーション前に済ませる
    • 600MB近いファームウェアのダウンロードを繰り返しさせたくない(遅い)
    • 確実にファームウェアを更新完了した状態でアクティエーションすることで失敗要素を減らす

この問題を解決するための手順は、最終的に、下記のとおりになりました。
  • DHCPでアドレス取得ができるネットワークに有線で接続
  • ファクトリーリセットのため Bluetooth + 早送り> ボタンを長押しする
  • BOSE社サイトからファームウェアファイル product_update.zip をダウンロードする
  • SoundBar 900 が掴んでいる IP アドレスを見つける
    • 今回はルータの DHCP リース状況からIPアドレスを特定
    • BOSE社サイトで案内されているMACの prefix がアテにならない
  • http://x.x.x.x:17008/update.html にアクセスする
  • 表示されたファームウェアアップロード画面にて product_update.zip をアップロード
    • ファームウェアアップロード画面をリロードしてファームウェアバージョンが上がっても我慢
    • 白いダッシュ(−)LEDが点滅しているうちはFWアップデートが続いているので1~2時間かかるつもりで放置
    • サウンドバー本体からファクトリーリセット時の起動音がしたらアップデート完了
  • DHCPでアドレス取得ができ、素直に通信できるインターネット環境に接続
    • 今回はLTE無線&有線ルータを上流として使用
    • SoundBar 900 を同ルータに接続して有線 DHCP でアドレス取得した状態とする
    • iOS端末を同ルータのL2セグメントに接続
  • iOS端末にて SoundBar 900 を登録、アクティベート
  • 本来のネットワークにイーサネットで接続し DHCP でアドレスを取得しなおす
    • 有線 -> 有線であれば WiFi のパラメータが絡まず、上流を置き換えても問題がおきない模様
普通の人に「これやりなよ」とカジュアルに言えるかというと、かなり微妙です。
知人が同じ問題に悩んでいても、面倒なので、教えてあげたり手伝ってあげたりする気には、正直なりません。
このようにファームウェアの挙動が常に判りづらく、デジタルガジェット的観点な感想として、かなり低めの評価になります。


ファームウェアアップデートについて

事前にファームウェアをアップデートする理由は、アクティベート成功後、自動的にファームウェアアップデートが開始されますが、iOS上でファームウェアアップデートが開始された後に失敗しても何も理由が表示されません。アップデートは一度で30分弱かかると書かれていますが、ファイルの転送段階でアボートしていても把握できないようです。このため手動でファームウェアアップデートを済ませて解決します。

LTE経由でファームウェアアップデートが走ったとしても、ここでダウンロードされるファイルが 600MB ほどになり、光回線などがあれば、LTEで転送することは合理的ではありません。PC等でファームウェアを事前ダウンロードしておけばリトライも簡単ですし、試行あたりの待ち時間や総転送量も減るでしょう。

実際にファームウェア更新ページで試してみるとわかるのですが、ファームウェアアップロード時、 SoundBar 900 のフラッシュストレージ上に過去の失敗ファイルが残っていることがあるようで、その場合にはファームウェア更新のワークエリアが十分にとれずに失敗することがあるようです。この問題を回避するためには Bluetooth ボタン + 早送り> ボタンを同時長押しして、あらかじめファクトリーリセットしておくと、ワークエリアがリセットされるのか、空き容量不足になるエラーが解消できました。

いちどファームウェアが更新できれば、LTE回線を上流としたネットワーク環境でも、アクティベート後の自動的なファームウェアアップデートも発生せず、期待通りデバイスが制御できるようになりました。

最近のファームウェアでは HDMI で接続されたソースが起動したときにこちらに入力を切り替えるような変更が入っているようですね。何かの拍子に入力ソースが切り替わった後に、テレビ等からの音が聞こえずモヤモヤするということは減るのかもしれません。(音が鳴らないと HDMI のリンクが落ちているのか、単に入力ソースの問題なのかという二択が頭によぎる時点でかなりUXが低い印象でしたが、これが改善しているかもしれません)


中身は Android なのね

サービス用の Type-C コネクタを介して疑似イーサネット?接続することも可能なようですが、手元ではすぐにイーサネットとして認識されなかったので、切り分けも面倒なので早々に諦めて有線ネットワーク経由でアクセスしました(このアップロードページは、これはこれでセキュリティ的にどうなんだという作りですが)。

Type-Cで接続したところ Android デバイスとして認識されたので、これ中身の OS は Android なんですね。ファームウェアファイルが600MBもあったのに驚きましたが、なんか納得というかんじです。 Raspberry Pi にのっているような STB 用のプロセッサ等で制御しているんでしょうね。


HDMIのリンク落ち問題

経験してみて判ったのですが BOSE SoundBar シリーズは HDMI で接続中に HDMI のリンクが落ちるとコールドスタート(電源ケーブル抜き差し)まで復旧しないようで、ネットで調べるとフラストレーションを感じている人が日本語でも他の言語でもいっぱいいます。私が弄った個体でもこの問題は発生していて、以下の対策をしています。

1)Ultra High Speed HDMI 認証のケーブルに交換で接続
2)簡単にリセットできるように電源ケーブルにスイッチを増設 :-(
3)リンクモードを拡張から標準にダウンブレード


Ultra High Speed HDMI 認証のケーブルに交換で接続

Ultra High Speed HDMI は、 8K 対応として売られているケーブルです。メーカーがズルをしていなければ、このロゴを付けられるケーブルは 8K のデータが転送できることを確認している(よりテクニカルには 48Gbps のデータ伝送で問題ないことが確認された製品)ということになります。Ultra High Speed HDMI はいまのところ HDMI シリーズでいちばんノイズ耐性をもとめる規格です。1m程度のケーブルは銅線などで単に接続されているパッシブタイプのケーブルが一般的ですが、高ノイズ耐性なケーブルであれば迂闊な HDMI の信号ロックはずれに頻度も減るだろうという狙いです(自分で HDMI 信号を扱ってきた際の体験談に基づいてのロジックです)。実際、体感的には、コレでかなり音が消える頻度が下がった気がします。

SoundBar 900 は eARC の製品ですが、どうせオーディオしか伝送しないだろうに、高いビットクロックでソースとシンクが HDMI 信号をロックしてて、ちょっとしたノイズで簡単に HDMI 信号のロックが外れると電源入れ直しまで復旧しないとか、そんな感じの挙動に見えます。ネット上のクレームを見る限り、おそらく付属のケーブルでも同期がとれなくなるケース多いのでしょうが、私みたいに古いケーブルなどをうっかり使い回すような人は、かなり痛い目にあうのでしょう。

簡単にリセットできるように電源ケーブルにスイッチを増設 :-(

HDMI信号ロックが外れたときのために SoundBar 900 の通電用ケーブルは簡単にリセットできるように電源スイッチを自分で追加する加工した電源ケーブルに置き換えました。


電源タップ側で簡単に電源オンオフできればそれでいいのですが、設置先の電源タップがアウトレットごとの独立制御に対応していなかったので、ケーブルを加工しました。電源ケーブルを抜き差しする手間がないだけでもかなりストレス軽減になるでしょうです。

リンクモードを拡張から標準にダウンブレード

テレビ側の設定を見ていたら HDMI ソース側(今回は SONY BRAVIA)の外部入力設定において HDMI リンクモードを拡張から標準に変更しました。HDMI のリンクモードを拡張・標準で選べるようなので、これを変更したときに具体的に何がおきるかは判っていないのですが(テレビ側もスピーカー側も何も教えてくれません)、これで HDMI のビットクロックを落とせたら安定性が増すかなと期待して標準モードに変更しました。結果として、この設定が安定性面で一番効果があったようです。


追記
BRAVIAのリンクモードを標準に落とした場合に何がおきるかわかっていないのですが、仮にリンクモードが落ちてARCにダウングレードされると、 5.1ch 圧縮までのサポートとなり 5.1 非圧縮や7.1のフォーマットを伝送できなくなるようです。
今回の設置場所では非圧縮5.1chなどのコンテンツに用はなさそうなのでARCにダウングレードされても十分でしたが、7.1期待で買っている人はどうしたらいいんでしょうね...

2022年4月30日土曜日

ES Modules から YouTube IFrame Player API を使う

YouTube IFrame Player API は、手元の Web サイトなどに YouTube 動画を埋め込みできる YouTube 公式のAPIです。

iframe 組み込みの YouTube Player API リファレンス

以下に Iframe Player API のサイトにあるサンプルコードを引用します。

<!DOCTYPE html>
<html>
  <body>
    <!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="player"></div>

    <script>
      // 2. This code loads the IFrame Player API code asynchronously.
      var tag = document.createElement('script');

      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: '360',
          width: '640',
          videoId: 'M7lc1UVf-VE',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }

      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        event.target.playVideo();
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.PLAYING && !done) {
          setTimeout(stopVideo, 6000);
          done = true;
        }
      }
      function stopVideo() {
        player.stopVideo();
      }
    </script>
  </body>
</html>


Iframe Player API は ESM として実装されていないので、 ESM として実装した Javascript などのコードから利用する場合にはいくらかの工夫が必要になります。

API の紹介サイトでは同 API の初期化終了後にコールバックされる onYouTubeIframeAPIReady() をグローバル名前空間で宣言していますが、これは ESM の名前空間からみれば window.onYouTubeIframeAPIReady() にあたります。また、同 API の初期化が完了すると YT が定義されますが、これは ESM の名前空間からみると window.YT になります。

この考慮点を反映して ESM としてサンプルを再実装したものが下記になります。

実際には ESM として実装する場合には、クラスとして実装するケースが多いでしょう。下記の例では、埋め込んだプレイヤーからのコールバックをインスタンスのメンバ変数として実装しています。

※コード上は動画のロードが完了し次第自動再生するような実装になっていますが、本記事執筆時点ではそのように動作していないようです。これは API のサイトにあるオリジナルのサンプルの問題です。

2022年3月28日月曜日

シェルスクリプトで unixtime を扱う

シェルスクリプト内で unixtime を得るには /bin/date +%s が利用できます。この方法で取得した unixtime はシェルスクリプト内で加算・減算したり、比較したりできます。

以下は、 sleep に頼らずに $TIMEOUT 秒間待機する例です。単純に sleep $TIMEOUT と記述するよりも、柔軟な待機ループを実装できます。

TIMEOUT=10
TIME_START=`/bin/date +%s`             # ループ開始時刻(いま)
TIME_END=$((${TIME_START} + ${TIMEOUT})) # ループ脱出時刻

echo -n running
while [ `/bin/date +%s` -lt ${TIME_END} ]; do
        sleep 1
        echo -n .
        
        # MySQL Server への接続性が確認できたら $TIMEOUT 秒待たずにループを抜ける 
        mysql -e 'SHOW STATUS' 1> /dev/null 2> /dev/null && break
done

2022年3月25日金曜日

Linux KVM + virtio-net のレイテンシーを削減するヘンな方法

Linux KVM の virtio-net を利用している際にリモートホストへの PING 結果がマイクロ秒のオーダーで安定しない(ブレる)という話を見かけました。

Linux KVM の仕組みや QEMU のデバイスエミュレーションの仕組みを考えれば「そんなもの」かなと思い、調査および追加の実験をしてみました。なお、本記事は x86 アーキテクチャの Linux KVM の動作に基づきます。


パケットが送信される仕組み

ping コマンドなどにより発生した、送信パケットは OS のネットワークスタック(L3, L2)を通じてイーサネットのドライバに引き渡されます。
今回はLinux KVMのデバイスエミュレーションを利用した際のレイテンシーに注目したいため、プロセスからデバイスドライバまでデータ届く流れについては触れません。 Linux KVM の仮想マシンで使われる virtio-net に注目します。
仮想的なネットワークインターフェイスである virtio-net 、またそのベースとなる、ホスト−ゲスト間の通信に使われる virtio-pci では、ゲストOSのメモリ空間にある送信対象データをバッファへのポインタをリングバッファに積み、I/Oポートを叩くことで VMM (Linux KVM) 側に通知します。
このあたりの仕組み(PCIデバイスに模倣する virtio のゲスト・ホスト間通信の仕組み)は過去にエンジニアなら知っておきたい仮想マシンのしくみ スライドにて解説しているので、そちらもご確認ください。

https://github.com/torvalds/linux/blob/4f50ef152ec652cf1f1d3031019828b170406ebf/drivers/net/virtio_net.c#L1762 に、ホストへ通知すべき送信データが準備できた際ホスト側の virtio-net に通知するブロックがあります。
        if (kick || netif_xmit_stopped(txq)) {
            if (virtqueue_kick_prepare(sq->vq) && virtqueue_notify(sq->vq)) {
			u64_stats_update_begin(&sq->stats.syncp);
			sq->stats.kicks++;
			u64_stats_update_end(&sq->stats.syncp);
		}
	}

virtqueue_notify(sq->vq) は仮想デバイスの I/O ポートへのアクセスする実装となっています。そのセンシティブ命令を契機としてゲストから VMM に制御が移ります。デバイスモデル(qemu-kvm)のI/Oポートハンドラがゲストからの割り込み理由を判断し、 virtio-net のゲスト側リングバッファにあるデータをバックエンドのネットワークインターフェイスに渡します。
この際(Intel 表記で) VMX non-root → VMX root の kvm → qemu-kvm (ring3) と遷移し、場合によっては qemu-kvm がネットワークスタックにパケットを渡す前にプリエンプションが発生する可能性があり、送信タイミングが遅れる原因になり得ます。

 パケットが受信される仕組み


では、パケットが届いた場合はどうなるでしょうか? qemu-kvm がゲスト行きの受信データを受け取ると、予め用意されているゲスト側のメモリ領域に受信データを書き込み「ゲストに対して割り込みをかけます」。
本物のx86のプロセッサであれば、IRQ(Interrupt ReQuest)を受け付けるための信号線があり、それを介して割り込みを通知する方法と、割り込み通知用のアドレス空間に対するメモリ書き込みトランザクションにより割り込みを通知するMSI(MSI-X)割り込みがあります。仮想マシンの場合、仮想プロセッサにはIRQ線、MSI通知用のアドレス空間にメモリトランザクションを行ってもそれを受けてくれる相手がいないので、仮想マシンのプロセッサを「割り込みがかかった状態」にすることで割り込む挿入を実現することになります。

https://github.com/torvalds/linux/blob/0564eeb71bbb0e1a566fb701f90155bef9e7a224/arch/x86/kvm/vmx/vmx.c#L4574 にある vmx_inject_irq() に、 Intel VT でゲストに割り込みを通知する実装があります。
static void vmx_inject_irq(struct kvm_vcpu *vcpu)
{
	struct vcpu_vmx *vmx = to_vmx(vcpu);
	uint32_t intr;
	int irq = vcpu->arch.interrupt.nr;

	trace_kvm_inj_virq(irq);

	++vcpu->stat.irq_injections;
	if (vmx->rmode.vm86_active) {
		int inc_eip = 0;
		if (vcpu->arch.interrupt.soft)
			inc_eip = vcpu->arch.event_exit_inst_len;
		kvm_inject_realmode_interrupt(vcpu, irq, inc_eip);
		return;
	}
	intr = irq | INTR_INFO_VALID_MASK;
	if (vcpu->arch.interrupt.soft) {
		intr |= INTR_TYPE_SOFT_INTR;
		vmcs_write32(VM_ENTRY_INSTRUCTION_LEN,
			     vmx->vcpu.arch.event_exit_inst_len);
	} else
		intr |= INTR_TYPE_EXT_INTR;
	vmcs_write32(VM_ENTRY_INTR_INFO_FIELD, intr);

	vmx_clear_hlt(vcpu);
}
ここでのキモは intr |= INTR_TYPE_EXT_INTR もしくは INTR_TYPE_SOFT_INTR です。 Intel VT では、仮想プロセッサの状態を管理するVMCS構造体に対して特定のビットを立てておくと、「次にその仮想マシンを実行するときに、ゲスト側で割り込みハンドラに処理が移される」仕組みです。ただし、ここでは実際に仮想マシンの実行し、割り込みハンドラに処理を移してはいないことに注意が必要です。

では、具体的にいつ、仮想マシンの割り込みハンドラが実行されるのでしょうか?答えは、Linux KVM + qemu-kvm の場合 qemu-kvm のメインループでCPUスレッドが改めて VMX non-root モードに移行するように要求したときです。

「次に仮想CPUを実行するときは割り込みハンドラを実行する必要があるよ」設定されても、その次にスケジュールされるタイミングは qemu-kvm の仮想CPU実行スケジュール、Linuxのスケジューラに依存することになります。仮想マシンのCPU負荷が低い場合には、qemu-kvmはVMX non-rootへの遷移を抑制して他のプロセスへのプリエンプションを許すことにより負荷を下げます。しかし、パケットが届いた後にqemu-kvmのCPUスレッドにプリエンプションが発生し、VMX non-rootへ遷移するまでがネットワークレイテンシーの増加として見えることになります。


VMX non-root でのビジーループは解決策になるか

では、ゲストがビジー状態でCPUを使い続ければ他のプロセスへのプリエンプションが発生せず、レイテンシーに改善が見られるのではないか?という仮説がありました。この方法で、ゲストOSがHLT命令などを発効してCPUを手放すことが減るというメリットは確かにありそうですが、2つの考慮点があります。
  • 仮想プロセッサが割り込みハンドラに制御を移すのはVMX rootからVMX non-rootに制御を移すタイミングです。このため、VMX non-rootでビジーループを回っていても割り込みハンドラに制御が移るわけではありません。
  • VMX non-rootでビジーループを回り続けた果てにVMX rootに遷移したとき、Linux カーネルから見れば、そのCPUスレッドは「大量のCPU時間を消費した」プロセスと認識します。このため、負荷が高いシステムでは、Linuxカーネルは他のプロセスに積極的にプリエンプションしようとするでしょう。これでは、次回のVMX non-rootへの遷移が遅れてしまいます。


ちょっとしたカーネルモジュールでレイテンシー性能を改善する

ここでは、ちょっとしたカーネルモジュールをロードして、レイテンシー改善が可能かを試してみました。コードは https://github.com/hasegaw/cheetah/blob/master/cheetah.c です。以下の関数をカーネルスレッドとしてビジーループで回します。

static void kthread_main(void)
{
        u32 vmx_msr_low, vmx_msr_high;

        rdmsr(MSR_IA32_UCODE_REV, vmx_msr_low, vmx_msr_high);

        schedule();
}

rdmsr は、 x86 アーキテクチャにある Machine-Specific Register を読み取ります。この命令はデバイスモデルでのエミュレーションが必要になるセンシティブ命令であるため、強制的に VMEXIT が発生して VMX non-root → VMX root への遷移が発生し、qemu-kvmでRDMSRのエミュレーションが行われ、その結果をもって再度VMENTERされます。
RDMSRのエミュレーションは未改造のqemu-kvmにおいて比較的コストの低いエミュレーション処理で、特に副作用が想定されないため、これを選んでいます。HLTなどを利用するとVMMがすぐにゲストに処理を戻さないかもしれませんが、RDMSRであれば、他プロセスへのプリエンプションがない限りは、すぐに再度VMENTERによりVMX non-rootに移行することが想定され、このタイミングで割り込みハンドラへの制御が移るわけです。


測定結果

Linux KVMで動作するシステム上のLinuxゲスト間でpingコマンドを実行した際のレイテンシーを測定してみました。下記が90%パーセンタイル値です。
  • RDMSRのビジーループなし(通常の状態) —— 614us (100.0%)
  • RDMSRのビジーループなし、ゲスト内で perl -e 'while(1) {}' でビジーループ —— 516us (84.0%)
  • RDMSRのビジーループあり —— 482us (78.5%)
ごく一般的なLinuxゲストと比較すると、先のRDMSRのビジーループを回したLinuxゲストでは20%程度のレイテンシー低下が確認できました。

このデータは2021年3月(本記事の執筆時点からちょうど1年前)に割とノリで試したもので、であまり細かいデータを取っていないため、上記の値しか残っていないことにはご容赦ください。


割り込み通知からポーリングループへの移行

ゲストがビジー状態で回り続けていても通知を受けられないのなら、ひとつの選択肢としてはポーリングループでホストからの通知が受けられたらいいのに、という発想になるかと思います。実際、DPDKではプロセスがループでポーリングし続けることで大幅にレイテンシーを短縮しているわけです。ホストOSからゲストOSへの通知をポーリングループで監視すればよいのではないでしょうか?

今回は、virtio-netに対して手を入れるような事はしなかったのですが、ホストOSからの割り込み通知を待たずにリングバッファへ新着データを拾いにいくことは可能な気がします。しかし、本当にレイテンシー面を気にするのでれば SRIOV などを検討するほうがいいでしょう。

Linux KVM ではありませんが、同じく Intel VT ベースで仮想マシン技術を提供する VMware 社のホワイトペーパー Best Practices for Performance Tuning of Latency-Sensitive Workloads in vSphere VMs には、下記の記述があります。



まとめ

Linux KVM の virtio-net を利用している際にリモートホストへの PING 結果がマイクロ秒のオーダーで安定しない(ブレる)理由について、Linux KVM、qemu-kvmとゲスト側カーネルの間で何がおきるかを考えてみました。
また、ゲストへの割り込み挿入タイミングを増やすために、ゲストカーネル上で数行で書けるカーネルスレッドを動かすことにとって、実際にレイテンシー性能がいくらか向上することを確認しました。
マイクロ秒オーダーの時間のブレを気にする場合は、最終的にはSRIOVやポーリングモードの導入、そもそも(VMENTER/VMEXITだって遅いので)VMM上ではなく物理的なマシンの利用が解決策になると思いますが、仮想マシンのしくみがこんな物なんだと理解しておくのは悪くない事かと思います。