EVO-X2にローカルLLM環境を作ってしばらく経ったのでもろもろのメモ

昨年末にGMKtec EVO-X2の128GBモデルを買ってから、ローカルLLMをわりと使っている。

思ったこと、ひっかかったところと解決方法、などについてざっくり書く。

自分のユースケース

仕事で使うときやちゃちゃっと解決したい場合などは素直にClaudeを使っている。

ローカルLLMは無限のrate limitが欲しかったり、SaaSに流したくない情報を取り扱ったり、単純にケチりたかったりするときに使う。

EVO-X2はheadlessで運用しており、OSはarchlinuxに載せかえている。llamaで動かしているOpenAI互換APIを、別インスタンスで動かしているフロントエンドや開発機のOpenCodeなどから叩いている。

archlinuxを入れているのは元々自分の家はarchlinuxかproxmoxしか動いておらず、WindowsやらUbuntuやらを動かすモチベーションが一切ないため。

セットアップ周辺

モデルの取得・配置以外はAnsibleで実行している。のでセットアップはこのディレクトリを参照してもらったほうがわかりやすいはず。

https://github.com/k16em/homelab/tree/main/ansible/roles/llm-backend

openai互換のAPIを喋るのはllama-swap+llama.cppにお任せしている。この記事を読んでollamaは使うのをやめた。

APIサーバの起動まわりはアプリケーション側が提供してくれているので、systemdに任せている。一部設定はoverride.confで上書きしている。

llama.cppはAURにあるllama.cpp-hipを代わりに入れている。

ROCmはまだ安定していないところもあるらしいが、ことローカルLLM用途に関してはそこまで引っかかるポイントはなかった。

モデルは /var/lib/models に保存している。やや拡大解釈気味だが、FHS を一応意識している。

モデルの取得・配置

メンテナンス用ユーザで、Hugging FaceのCLIを使ってGGUFをわかりやすい場所に取得する。たとえばこんな感じ。

hf download unsloth/GLM-4.7-Flash-GGUF --include "BF16/*" --local-dir ./models/GLM-4.7-Flash-BF16

そのあと、installコマンドを使って/var/lib/modelsに配置する。たとえばこんな感じ。

sudo install -m 644 -o root -g root ./models/GLM-4.7-Flash-BF16/BF16/GLM-4.7-Flash-BF16-00001-of-00002.gguf /var/lib/models/

llama-swapの設定

以下のような設定ファイルを /etc/llama-swap/config.yaml に配置している。

healthCheckTimeout: 300
models:
  "glm-4.7-flash-bf16":
    env:
      - "HSA_OVERRIDE_GFX_VERSION=11.5.1"
      - "HSA_ENABLE_SDMA=0"
      - "ROCBLAS_USE_HIPBLASLT=1"
    cmd: |
      /usr/bin/llama-server
      -m /var/lib/models/GLM-4.7-Flash-BF16-00001-of-00002.gguf
      --port ${PORT}
      -ngl 99
      -c 32768
      --no-mmap
      --temp 1.0
      --top-p 0.95
      --min-p 0.01
      --repeat-penalty 1.0
    ttl: 600

envの値と —no-mmap を設定しないと、VRAMにモデルを載せるところでllama-serverが落ちてしまうことがある。

OpenCodeから接続する

コーディングエージェントとしてのクライアントにはOpenCodeを使っている。

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "llama.cpp": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "llama-swap(tk)",
      "options": {
        "baseURL": "http://192.168.20.17:12434/v1"
      },
      "models": {
        "glm-4.7-flash-bf16": {
          "name": "glm-4.7-flash-bf16",
          "tools": true
        }
      }
    }
  },
  "model": "glm-4.7-flash-bf16",
以下略

OpenCodeがllama-swapがモデルを呼び出すとき、モデル名は provider["llama.cpp"].models["glm-4.7-flash-bf16"].name ではなく provider["llama.cpp"].models のキー名を参照するのには注意が必要。

パッと見呼び出しのときに参照されそうな provider["llama.cpp"].models["glm-4.7-flash-bf16"].name はOpenCode上のモデル一覧での表示にのみ使われる。

雑感

ぶっちゃけ話はこれ。

値上げによってコスパも悪くなってしまった感もあるので勧めにくくなってしまった。

実用よりもイジって楽しい、動かして楽しい、のためにこういうの買って遊ぶなら全然アリだとは思う。