GitHubのトレンドで magnitudedev/magnitude を見かけました。「Run your agent on local models. Free, private, and offline.」という一行と、その下の説明が気になったんですよね。「It profiles your machine, recommends the models that fit, then downloads, tunes, and runs them.」
つまり、Ollama を自分で選んで自分でモデルを決めるのではなく、マシンを測って「あなたのマシンにはこれが載ります」と言ってくれる、と。README の FAQ にも「Why not just have my agent set up Ollama?」という項があって、そこには「Your agent would be guessing. It doesn’t know your hardware, which quant fits, or how fast it’ll run.」と書かれていました。
推測ではなく実測でモデルを決める、という設計思想には共感します。運用を考えると、社内の開発端末にローカルモデルを配る場合にいちばん面倒なのが「この端末なら何が載るのか」の判定だからです。ただ、私が気になったのはもう一段先でした。その「実測」は、仮想化された環境でも正しく効くのか。 社内でこの手のものを配るとき、実行環境はほぼ確実にコンテナか VM か WSL です。
というわけで、メモリと CPU を明示的に絞った使い捨てコンテナの中で動かしてみました。
試した環境
ホストには何も入れず、使い捨てのコンテナの中だけで触っています。
- ベースイメージ:
node:22-bookworm(Debian GNU/Linux 12 / x86-64 / Node v22.23.1 / npm 10.9.8) - 対象:
@magnitudedev/cli0.0.11(Apache-2.0)、リリースマニフェスト上のsourceCommitは27aa9ca98c404e31741e1c6b76bcf2a960ebb327 - ホスト側の物理 CPU は Intel Core Ultra 9 285K
- コンテナは 3 本立てて、それぞれ
--memoryと--cpusを変えています
なお Magnitude は README に「Magnitude supports macOS and Linux. Windows is supported through WSL.」とあり、Docker での動作は謳っていません。ここから先は「サポート対象外の環境に持ち込んだらどう見えたか」の記録として読んでください。
「1 package」の中身は 414MiB だった
README のインストールは npm i -g @magnitudedev/cli の一行です。実際に流すと、確かに一瞬で終わりました。
# 手順1: npm install(cold)
$ npm i -g @magnitudedev/cli
added 1 package in 1s
STEP1_npm_i_g_MS=1035
660879 /usr/local/lib/node_modules/@magnitudedev/cli
# 手順2: 初回のコマンド実行
$ magnitude --version
Downloading Magnitude CLI...
Verifying Magnitude CLI...
Installing Magnitude CLI...
✓ Magnitude CLI installed
0.0.11
STEP2_first_run_MS=2395
143630684 /root/.magnitude
# 手順3: サービス導入
$ magnitude service start
Downloading Magnitude service... 100% (40.7 MB / 40.7 MB)
STEP3_service_start_MS=2842
250841629 /root/.magnitude
# 手順4: 2回目以降(warm)
STEP4_warm_version_MS=250
npm が入れているのは 660,879 バイトのランチャ 1 本だけで、本体は初回実行時に落ちてきます。--version を叩いただけで 137MiB、service start でさらに 107MiB。そしてサービスを実際に起動すると推論ノードが追加で降ってきて、最終的にこうなりました。
| 段階 | 実行したこと | ディスク使用量 |
|---|---|---|
| 1 | npm i -g @magnitudedev/cli | 660,879 B |
| 2 | magnitude —version | 143,630,684 B |
| 3 | magnitude service start | 250,841,629 B |
| 4 | サービスを実際に起動 | 432,829,752 B(414MiB) |
内訳は CLI 本体 143,603,905 バイト、サービス 107,194,561 バイト、推論ノードとそのベース成果物が 90,930,638 と 90,934,534 バイトです。モデルはまだ 1 バイトも入っていません。 これが不誠実だとは思いません。ランチャ方式は各 OS 向けバイナリを npm に載せない実装として合理的ですし、実際に速い。ただ、運用を考えると「npm i -g は 1 秒」という体感と「配布時に端末あたり 414MiB」という見積もりは別物として扱う必要があって、そこは最初に測っておいてよかったと思っています。
サービスは systemd を直接叩いていた
次に magnitude service start です。ここで止まりました。
$ magnitude service start
○ Starting Magnitude service
Downloading Magnitude service... 100% (40.7 MB / 40.7 MB)
Magnitude service failed to start:
SystemError: NotFound: Command.spawn (systemctl --user daemon-reload):
Executable not found in $PATH: "systemctl"
# 失敗しているのに、ユニットファイルは書かれている
$ cat /root/.config/systemd/user/magnitude.service
[Unit]
Description=Magnitude local inference service
After=network.target
[Service]
Type=simple
ExecStart="/root/.magnitude/releases/acn/0.0.11/linux-x64-gnu/f653.../bin/magnitude-service" "serve"
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.target
Linux 側のサービス管理が systemd の user unit 決め打ちになっていて、systemctl が居ないと service start は通りません。コンテナは当然そうですし、README が案内している WSL も、wsl.conf で明示的に有効化していなければ systemd は動いていないはずです。ここは好みが分かれるところですが、私は「Linux = systemd」と置く設計自体は現実的だと思っています。気になったのは、同梱ドキュメント(magnitude docs onboarding が 153 行、magnitude docs cli が 38 行)を systemd WSL Linux で grep しても該当が 1 件も無かったことのほうでした。前提が前提として書かれていないと、詰まったときに切り分けようがないんですよね。
とはいえ、ユニットファイルの ExecStart に答えが書いてあります。magnitude-service serve を直接起動したらそのまま立ち上がり、magnitude hardware が答えるようになりました。ここからが本題です。
CPU は cgroup を見て、メモリはホストを見る
制限を変えたコンテナを 2 本用意して、同じことをしました。ケースBはキャッシュの影響を避けるため、インストールからやり直しています。
$ /root/.magnitude/releases/acn/0.0.11/linux-x64-gnu/*/bin/magnitude-service serve &
# ケースA: docker run --memory=4g --cpus=2
$ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
4294967296
$ cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
200000
$ nproc
8
$ magnitude hardware
Intel(R) Core(TM) Ultra 9 285K
Linux · x86-64 · 2 logical CPU cores
7.8 GB system memory
Memory
System memory
4.3 GB / 7.8 GB used
Free 3.5 GB
# ケースB: docker run --memory=1g --cpus=6(まっさらな状態から)
$ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
1073741824
$ cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
600000
$ nproc
8
$ magnitude hardware
Intel(R) Core(TM) Ultra 9 285K
Linux · x86-64 · 6 logical CPU cores
7.8 GB system memory
Memory
System memory
4.7 GB / 7.8 GB used
Free 3.1 GB
まず感心したのが CPU 数です。nproc はどちらも 8 を返すのに、Magnitude は 2 と 6、つまり --cpus の指定どおりに答えています。--cpus は CPU アフィニティではなく cgroup の CFS クォータ(cpu.cfs_quota_us ÷ cpu.cfs_period_us)として効くので、nproc からは見えません。意図して cgroup を読みに行っている、ということです。
一方でメモリは、4 GiB のときも 1 GiB のときも 7.8 GB のまま動きませんでした。これはホスト側の物理メモリの値です。
結果を並べます。
| 観測項目 | ケースA(4g / 2cpu) | ケースB(1g / 6cpu) |
|---|---|---|
| cgroup のメモリ上限 | 4,294,967,296 | 1,073,741,824 |
nproc | 8 | 8 |
| magnitude が言う CPU 数 | 2(cgroup と一致) | 6(cgroup と一致) |
| magnitude が言うメモリ | 7.8 GB(ホスト値) | 7.8 GB(ホスト値) |
| compatible models | 8 件 | 8 件(同じ顔ぶれ) |
CPU 数は cgroup のクォータにきれいに追随して 2 と 6 に変わり、メモリは 2 ケースとも 7.8 GB のまま動きませんでした。ハードウェア検出のうち、CPU だけがコンテナを認識していて、メモリは /proc/meminfo 相当のホスト値をそのまま見ている、という説明が素直だと思います。
そして、この「メモリだけホスト値」がそのまま推薦に効きます。1 GiB しか使えないコンテナで、こう返ってきました。
# ケースB(--memory=1g)でのモデル一覧
$ magnitude catalog list
Local model catalog - 8 compatible models
MODEL MEMORY SPEED CONTEXT ACCELERATION
Gemma 4 E2B (Q4 QAT) 4.7 GB ~3–4 tok/s 64K None
Gemma 4 E4B (Q4 QAT) 4.6 GB ~2 tok/s 64K None
Liquid LFM2.5 2.6B (Q4) 2.7 GB ~3–4 tok/s 64K DSpark
Liquid LFM2.5 2.6B (Q5) 4.1 GB ~3–4 tok/s 64K DSpark
Liquid LFM2.5 2.6B (Q6) 4.4 GB ~3–4 tok/s 64K DSpark
Liquid LFM2.5 2.6B (Q8) 5 GB ~2–3 tok/s 64K DSpark
Liquid LFM2.5 8B-A1B (Q4) 3.2 GB ~4–6 tok/s 64K DSpark
Qwen3.5 4B (Q4) 5 GB ~2 tok/s 64K None
$ magnitude catalog recommendations --limit 3
Local model recommendations - Balanced
Intel(R) Core(TM) Ultra 9 285K - 7.8 GB
1. Liquid LFM2.5 2.6B (Q6) ~3–4 tok/s - 4.4 GB memory - 64K context
2. Qwen3.5 4B (Q4) ~2 tok/s - 5 GB memory - 64K context
3. Liquid LFM2.5 2.6B (Q5) ~3–4 tok/s - 4.1 GB memory - 64K context
$ magnitude catalog show qwen3.5-4b:gguf:q4
Performance on this machine
Speed ~2 tok/s
Memory Tight (5 GB)
上限 1 GiB の環境で、第 1 推薦が 4.4 GB。5 GB のモデルの評価は「Tight」です。物理メモリが 8 GB のマシンから見れば 5 GB は確かにギリギリなので、この表示自体は正しい判断をしています。見ている数字が違うだけなんですよね。
面白いのは、速度の見積もりのほうは CPU 数の変化をちゃんと拾っていることです。Liquid LFM2.5 8B-A1B (Q4) はケースA(2 コア)で ~2 tok/s、ケースB(6 コア)で ~4–6 tok/s に変わっていました。同じ推薦ロジックの中で、速度は正しい値を、適合判定は誤った値を使っている状態です。
同梱の magnitude docs recommendations には、Memory について「the estimated memory needed while the model is running, not its download size. A model that normally fits can still need other memory-intensive applications to be closed before…」と書かれていました。「他のアプリを閉じれば載る」という前提が置かれているわけで、これは物理マシンを想定した記述としてはまったく妥当です。裏を返すと、cgroup で上限が切られている環境は最初から想定の外にある、ということだと思います。
ついでに見つけた、起動直後の 1 秒
もうひとつ、細かい挙動を記録しておきます。サービスを起動した直後、magnitude service status と実際の疎通が食い違う瞬間がありました。
# サービス起動直後から1秒ごとにポーリング
t=+1s status=Stopped hardware_rc=0
t=+2s status=Ready hardware_rc=0
t=+3s status=Ready hardware_rc=0
...
t=+14s status=Ready hardware_rc=0
# サービスを落とすと、両方が揃って「落ちている」と言う
$ magnitude service status
Magnitude service
Runtime Stopped
$ magnitude hardware
Magnitude service unavailable:
Magnitude service is not running. Run `magnitude service start`.
(rc=1)
t=+1s の時点では status が Stopped と言っているのに、magnitude hardware は正常に値を返しています。サービスを本当に止めれば両方とも Stopped と rc=1 になるので、状態表示自体は壊れていません。起動直後の 1 秒だけ、状態の見え方が実際より遅れる、という話です。実害は薄いのですが、スクリプトで service status の結果を待ち条件に使うつもりなら知っておいたほうがいいと思います。
なお、正常系の終了コードは素直でした。magnitude models list(存在しないサブコマンド)、magnitude catalog show nonexistent-model、magnitude docs nosuchtopic はいずれも rc=1 を返します。ここを疑い始めるとキリがないので、一通り測って問題なしと確認できたのは収穫でした。
確かめていないこと
正直に書いておきます。推薦されたモデルを実際に catalog pull して読み込ませてはいません。 ダウンロードサイズが 3.23 GB から 3.67 GB あり、今回の検証枠に収まらなかったためです。したがって「1 GiB のコンテナで 4.4 GB のモデルを引くと OOM で落ちる」という部分は、私は観測していません。確認できたのは、適合判定と推薦がコンテナのメモリ上限を見ていない、という一点だけです。
試してみての所感
Magnitude 自体は、方向性としてかなり好きです。ローカルモデルの導入でいちばん人間の判断コストが高いのは「どのモデルのどの量子化を選ぶか」で、そこを端末の実測に寄せるという設計は正しいと思っています。実際、CPU クォータを読みに行っている時点で、この手のツールとしては丁寧な部類です。
今思えば、私が今回引っかかったのは Magnitude 固有の話ではなく、もっと一般的な線引きの問題でした。「環境を測って自動で決める」タイプのツールは、測る対象が物理マシンである前提を暗黙に持ちがちです。そして社内に配るときの実行環境は、たいてい物理マシンではありません。
運用に落とすなら、私はこう扱うと思います。開発者の物理マシンに直接入れるなら素直に使う。CI やコンテナ、メモリ上限付きの VDI に載せるなら、magnitude hardware の出力を鵜呑みにせず、cgroup の上限と突き合わせる 1 行を挟む。コスト面でも、ローカル推論はトークン課金が消える代わりに端末あたり 414MiB の配布物と数 GB のモデルが乗るので、「無料」と言い切る前にストレージと配布の見積もりは出しておきたいところです。
バージョンは 0.0.11 で、まだ非常に若いプロジェクトです。この先 systemd 以外の起動経路や cgroup 対応が入る可能性は十分にあると思っているので、しばらく追ってみるつもりです。