DroidRunner
listening for jobs

android arm64 android-api-36 soc-mediatek-mt6899 android-npu npu-neuron

引き出しで眠っているその端末は、NPU付きのARM64 Linuxマシンです。 DroidRunnerは、それをGitHub Actionsにそう認識させます。

APK単体で、公式のGitHub Actions RunnerをAndroid端末上で動かします。 root不要、Termux不要、PCへの接続も不要。

はじめる → APKをダウンロード ソース
$ uname -m
aarch64
$ droidrunner-device capabilities
device Xiaomi 2511FPC34G · android 16 (api 36)
soc mediatek mt6899
nnapi mtk-neuron_shim, mtk-dsp_shim, mtk-mdla_shim, nnapi-reference
thermal none · battery 93% charging
Connected to GitHub · Listening for Jobs
これはself-hosted runnerです。 Workflowに書かれたコードを、そのまま端末上で 実行します。信頼できないRepositoryには接続しないでください。

なぜ作ったか

GitHubがホストするARM64 Runnerは仮想マシンです。そのため、あなたのモデルが Qualcomm HexagonやGoogle Tensor TPU、MediaTek APUで実際に動くのかには答えられません。 背後にNPUが無く、ベンダーのドライバスタックも無いからです。

それに答えられる端末は、すでに引き出しの中にあります。DroidRunnerが目指すのは、 CIがラベルでジョブを振り分けられる実機のプールです。 「このビルドはARM64で通るか」と「このモデルはそのベンダーのNPUで動くか」を、 どちらもpushで走る同じ種類の質問にすることが目的です。

なぜシングルボードではなくスマホか

スマホの正直な比較対象は仮想マシンではなく、棚に置くRaspberry Piか、ホストされた ARM64の従量課金です。GitHubのlinux-arm64 Runnerは公開リポジトリなら 無料ですが、プライベートは課金されます。つまりプライベートリポジトリでの実際の 選択は、買うボードすでに持っている端末の比較になります。

Raspberry Pi 5引き出しの中のスマホ
費用本体・電源・ストレージ・ケース支払い済み
NPU無しこのプロジェクトの存在理由
計算に使えるGPU実質無しTFLite delegateから到達可能
停電止まる。UPSは別料金バッテリーがUPS
状態表示画面を足す最初から在る
Linux普通に使える。root有りPRoot、root不可、ベンダーの電源管理
過小評価されがちなのはバッテリーです。ボードは電源の一瞬の揺らぎでビルドを 落とします。スマホは気付きもしません。そして再起動が実際に起きたときは、DroidRunnerが 無人だった時間を記録するので、その空白が見えないまま埋もれることもありません。

ボードが勝つのは「普通のコンピュータであること」です。root、予想通りに動くLinux、 GPIO、そしてバックグラウンドで何を動かしてよいかをベンダーが決めないこと。CIがビルドと テストだけで、アクセラレータに触れないなら、ボードを買うべきです。そちらが単純な機械で、 このプロジェクトはそれを止めようとはしていません。

スマホが勝つのは、問いがシリコンに関わった瞬間です。このページの以降にあるもの ——どのドライバが実際にグラフを走らせたか、どの演算子をベンダーが拒否するか、誰が計算 したかではなく何を計算したか——は、NPUの無いボードでは答えられません。VMで答えられない のと同じ理由です。

ダッシュボード

CPU・メモリ・ディスク・電源・Runnerの各パネルを表示したbtop風ダッシュボード
cpu
負荷履歴グラフと、現在の周波数付きのコア別メーター。
mem / disk
RAMとアプリ専用ストレージの使用率と履歴。
pwr/net
バッテリー残量と充電状態、電池温度、Androidのthermal status、 ネットワーク転送量。
runner
Runnerの状態、登録先、稼働時間、ジョブ成功/失敗数、ログのライブtail。 公式リスナーの出力をそのまま解析しています。
setup (⚙)
別画面。サインイン、登録先の選択、登録、ジョブポリシー。 登録済みの端末では、アプリ起動時にRunnerが自動で立ち上がります。

しくみ

GitHub Actions
      ↓  ラベルでジョブが割り当てられる
┌─ DroidRunner APK ────────────────────────────────┐
   Androidコントローラ  → Keystore (ユーザートークン)
      ↓
   PRoot + Ubuntu ARM64 rootfs
      ↓
   公式 Linux ARM64 Actions Runner
      ↓  loopback + ジョブ単位のcapability token
   Device Agent  → NNAPI / QNN / Neuron / ENN
└──────────────────────────────────────────────────┘

シェル、Git、Node、Python、Rust、Go、GradleはPRootの中で通常のLinuxと同じように動きます。 Android APIやNPUが必要なものはそのnamespaceからは触れないため、 実行中のジョブ向けに発行されたトークンだけを受け付けるloopback API経由で、 アプリ側に委譲します。

proot自体はネイティブライブラリとしてAPKのに同梱しています。 Android 10以降はアプリのデータ領域に置いたバイナリのexec()を拒否するためです。 したがってダウンロードするruntime bundleはデータのみで、そのmanifestには署名があり、 誰も保証していないrootfsを端末が取りに行くことはありません。

特徴

APK単体

コンパニオンアプリもTermuxも、貼り付けるスクリプトも不要。

root不要

PRoot上のLinux ARM64環境で動作。

公式Runner

GitHub公式のlinux-arm64 Actions Runnerを無改造で使用。

GitHub Appログイン

Device Flowでサインインして登録先を選ぶだけ。PAT発行もclient secret同梱も不要。

Repo / Orgスコープ

1リポジトリ、または組織内の全リポジトリを担当。

ラベル自動生成

Android APIレベル、SoC、NPU候補からRunnerラベルを生成。

自己防衛

高温・容量不足、あるいはバッテリー動作かつ残量がしきい値未満のあいだはジョブを保留。給電時のみ動かす設定は既定でオフ。

クラッシュ復帰

異常終了してもバックオフ付きで自動再起動。

ephemeralモード

ジョブごとに再登録し、work directoryを消去。

署名付きruntime

ECDSA署名のmanifestを、APKに埋め込んだ鍵で検証。

Keystore保管

GitHubトークンはAndroidが暗号化し、Linux側には渡しません。

バックグラウンド待機

Foreground ServiceとWakeLockでRunnerを維持。

スマホで動くジョブ

jobs:
  test:
    runs-on: [self-hosted, android, arm64]
    steps:
      - uses: actions/checkout@v4
      - run: uname -a
      - run: ./ci/test-arm64.sh

シリコンに直接聞くこともできます:

runs-on: [self-hosted, android, nnapi-accelerator]
steps:
  - run: droidrunner-device capabilities   # 端末情報・温度・ドライバ一覧
  - run: droidrunner-device bench-all      # 全ドライバでCONV_2D
DEVICE                       AVG_US     GFLOPS
mtk-dsp_shim                      - compilation_finish
mtk-mdla_shim                     - compilation_finish
mtk-neuron_shim              4148.9       4.55
nnapi-reference              1107.7      17.04

拒否した2つのドライバはfloat32ではなく量子化モデルを要求しています。 ラベルをSoC名の解析ではなくプローブ結果から作るべき理由が、まさにこれです。

ラベル

ラベル意味
androidDroidRunnerが動作している実機Android端末
arm64ARM64端末
android-api-NAndroid APIレベル
soc-*検出したSoC情報
android-npuそのSoCファミリーにNPUがあることは分かっている。約束ではなくヒント
nnapi-accelerator アクセラレータがプローブに応答した — 指定すべきラベル
npu-qnn この端末のHexagonでモデルが実際に走った — 推測ではなく実測
npu-tflite / npu-neuron / npu-enn LiteRT / MediaTek Neuron / Samsung ENN の候補(SoC名由来)
nnapi / nnapi-accelerator / npu-qnn は 端末に聞いた実測、それ以外はSoC名由来の候補です。加速が必要なジョブは 実測系のラベルを指定してください。さもないと黙ってCPUで走ります。 npu-qnn はかつて推測でしたが、いまは Hexagonでモデルが走ったことを示した端末にだけ付きます。

VMには答えられない問い

EfficientNet-Lite0を、3台の実機で同じモデル・同じ計測系で実行したものです。各行は 「依頼したドライバ」ではなく実際に実行したプロセッサを報告しています。

端末ドライバint8float32
Tensor G4google-edgetpu0.65 ms16.7 ms
SM8650qnn-htp(Hexagon)1.22 ms2.78 ms
SM8650gpu(Adreno、TFLiteデリゲート)4.95 ms
MT6899mtk-neuron_shim2.12 ms7.29 ms
MT6899mtk-mdla_shim2.69 msCPUに落ちた
Tensor G4NNAPI任せ5.65 ms
SM8650nnapi-reference(CPU)113 ms39.8 ms
MT6899nnapi-reference(CPU)257 ms106 ms
量子化で勝者が入れ替わります。int8はEdgeTPU、float32はHexagonが2.6倍差。 「速い端末」というものは無く、出荷するモデル次第です。

いくつかのセルはアクセラレータの計測ではありません。MediaTekのMDLAはfloat32を 断り、CPUが拾っています。以前のこのページならそれを数値として載せていました。現在は 毎回「誰が実行したか」を報告し、フォールバックはフォールバックとして出ます。

NNAPI任せは高速化のほとんどを失い、そもそもHexagonには到達できません。 だからその行には端末側のopt-inが必要です。

シリコンがどの演算子を受けるか

ネットワーク全体では答えられません。1ノードの拒否がそのパーティション全体をCPUへ押し戻すので、 フォールバックは「ベンダーの表のどこかが間違っている」とは言っても「どこが」は言わない。 なので演算子×精度ごとに1opのモデルを62個作り、その端末が持つ全ドライバで走らせます。

ドライバfloat32int8
qnn-htp(SM8650 Hexagon)31/3131/31
gpu(SM8650 Adreno、TFLiteデリゲート)31/3131/31
mtk-neuron_shim(MT6899)28/3130/31
mtk-mdla_shim(MT6899)0/3130/31
mtk-dsp_shim(MT6899)10/3119/31
google-edgetpu(Tensor G4)0/3129/31
同一SoC、3ドライバ、3通りの答え。 ドライバ軸を持たない表は、どう書いても 2つ以上に対して間違いです。MDLAはfloat32を1つも取らない — これは 非対応であってペナルティ付きのエミュレートではなく、コンパイラが吐くコードが変わります。

全モデルはアクセラレータ指定なしでも走らせます。 CPUでも動かないモデルはこちらの不具合なので、 「あるドライバが拒否した演算子」ではなく「除外」として明示されます。

端末ごとの表を保存し、次回はそれと比較します。 システム更新後にドライバがある演算子を 取らなくなれば、ビルドが赤くなる。この検査には同じ端末を持ち続けることが要るので、 時間貸しのマシンでは原理的にできません。

実装状況

Runnerライフサイクル動作
ダッシュボードUI動作
GitHub Appログイン動作
ジョブ受付制御動作
ephemeral / 自動復帰動作
署名付きruntime動作
NNAPIプローブ / ベンチ動作
任意の.tfliteモデル実行動作
入力を渡し、出力を受け取る動作
Qualcomm Hexagon(opt-in)動作
結果契約 + composite action動作
オペレータ対応表動作
測定時の端末状態動作
全端末のGPU動作
1グラフに2エンジン実験的
MediaTek Neuron 直接保留
フリート表示(読み取り)動作

動作としたものは、CIだけでなく実機で確認済みです。 詳細な表は README にあります。保留は、経路を全部試したうえで どれも「端末に無い、またはアプリからロードできない」ライブラリを通ることが分かった状態です (issue #83)。

使う前に

端末をセットアップする → リリース