はじめに
前回、ヘッドレスな Ubuntu 24.04 Server が動作する Ryzen AI Max+ 395 (EVO-X2) 上でリモートから LM Studio を使う記事を書きましたが、運用上不都合が多いので、llama-server をコンテナ化して動作させてみました。以下の記事を参考にしました。
環境
前回と同じですが、以下の環境です。
- マシン: GMKtek EVO-X2 (Ryzen AI Max+ 395 / 128GB)
- OS: Ubuntu 24.04 Server (ヘッドレス)
- VRAM: 96GB固定
コンテナ作成
以下の手順でコンテナを作ります。
- モデルを置く場所の用意
- 各種設定ファイルの用意
- コンテナの起動
最初にモデルを置く場所を作ります。/path/to はコンテナを作る都合の良いディレクトリとして下さい。
cd /path/to
mkdir ./models
以下の各ファイルをカレントディレクトリに置きます。
- Dockerfile
FROM ubuntu:24.04
# 必要パッケージ
RUN apt-get update && apt-get install -y --no-install-recommends \
git build-essential cmake \
libvulkan-dev mesa-vulkan-drivers libvulkan1 \
glslc libcurl4-openssl-dev curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt
RUN git clone --depth=1 https://github.com/ggml-org/llama.cpp.git
WORKDIR /opt/llama.cpp
RUN cmake -B build -DGGML_VULKAN=ON && cmake --build build --config Release -j
# ★ 外出ししたエントリポイントをコピー
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/entrypoint.sh"]
- entrypoint.sh
#!/usr/bin/env bash
set -euo pipefail
BIN=/opt/llama.cpp/build/bin/llama-server
MODEL="${MODEL:-}"
ALIAS="${ALIAS:-}"
if [[ -z "$MODEL" ]]; then
cat <<'EOF' 1>&2
[entrypoint] ERROR: MODEL not set
EOF
exit 1
fi
# HF_TOKEN があれば export(private/大容量に推奨)
if [[ -n "${HF_TOKEN:-}" ]]; then
export HF_TOKEN
fi
args=("$BIN" "--host" "${HOST:-0.0.0.0}")
# ここでは --port は渡さない(コンテナ内 8080 のまま)。公開ポートは docker-compose 側でマッピング
args+=(-c "${CTX_SIZE:-65536}")
args+=(--n-gpu-layers "${N_GPU_LAYERS:-999}")
if [[ -n "${API_KEY:-}" ]]; then args+=(--api-key "$API_KEY"); fi
# MODEL の解釈
# 1) hf:org/repo:file.gguf → -hf repo --hf-file file
# 2) hf:org/repo → -hf repo(ファイルは llama.cpp が自動選択)
# 3) org/repo → -hf repo(同上)
# 4) *.gguf / パス → -m /path/to/file.gguf
model_for_alias=""
if [[ "$MODEL" == hf:* ]]; then
tail="${MODEL#hf:}"
if [[ "$tail" == *:* ]]; then
repo="${tail%%:*}"
file="${tail#*:}"
args+=(-hf "$repo" --hf-file "$file")
model_for_alias="$file"
else
repo="$tail"
args+=(-hf "$repo")
model_for_alias="$(basename "$repo")"
fi
elif [[ "$MODEL" == *.gguf ]]; then
# ローカルファイル
args+=(-m "$MODEL")
model_for_alias="$(basename "$MODEL")"
else
# “org/repo” だけを HF として扱う
repo="$MODEL"
args+=(-hf "$repo")
model_for_alias="$(basename "$repo")"
fi
# エイリアス(未指定なら自動)
if [[ -z "$ALIAS" ]]; then
if [[ "$model_for_alias" == *.gguf ]]; then
ALIAS="${model_for_alias%.gguf}"
else
ALIAS="$model_for_alias"
fi
fi
args+=(--alias "$ALIAS")
# 追加の任意フラグ(例: "--jinja -fa")
if [[ -n "${EXTRA_ARGS:-}" ]]; then
# shellcheck disable=SC2206
extras=($EXTRA_ARGS)
args+=("${extras[@]}")
fi
echo "[entrypoint] exec: ${args[*]}"
exec "${args[@]}"
- docker-compose.yml
services:
llama:
build: .
container_name: llama-vulkan2
ports:
- "${PORT:-8080}:8080" # コンテナ内 8080 → ホスト ${PORT}
devices:
- /dev/dri:/dev/dri # Vulkan
env_file:
- .env
environment:
MODEL: "${MODEL}" # 必須:repo だけでOK
ALIAS: "${ALIAS:-}" # 任意:未指定なら自動
HF_TOKEN: "${HF_TOKEN:-}" # private/大容量に推奨
HOST: "${HOST:-0.0.0.0}"
CTX_SIZE: "${CTX_SIZE:-65536}"
N_GPU_LAYERS: "${N_GPU_LAYERS:-999}"
EXTRA_ARGS: "${EXTRA_ARGS:---jinja -fa}"
API_KEY: "${API_KEY:-dummy}"
volumes:
- ./models:/root/.cache/llama.cpp
restart: unless-stopped
- .env
# ---- モデル & 推奨フラグ ----
MODEL=hf:unsloth/gpt-oss-20b-GGUF
ALIAS=gpt-oss-20b
CTX_SIZE=4096
N_GPU_LAYERS=999
#EXTRA_ARGS=--jinja -fa -ctk q8_0 -ctv q8_0
EXTRA_ARGS=--jinja -fa
HF_TOKEN=hf_**********************************
# ---- サーバ設定 ----
HOST=0.0.0.0
PORT=8081
API_KEY=dummy
THREADS=12
.env ファイルについては以下のことに留意して下さい。
- MODEL/ALIAS を変更することで、huggingface の他のモデルも選択できます。
- EXTRA_ARGS は前回の記事に合わせて設定していますが、コメントアウトした設定のほうが VRAM を節約できます。
- -fa を使わないと 120b のモデルは VRAM から溢れてしまい、実行できませんでした。
- HF_TOKEN は huggingfaceにアクセスする場合に必要になる場合がある API TOKENです。必要に応じて設定して下さい。
- PORT は私のところの環境の都合で 8081/tcp に変更しました。
一通りファイルを置いたら以下のように実行します。
docker compose up -d --build
一度ビルドしてしまえば、あとは以下のようにコンテナを再起動できます。
docker compose down; docker compose up -d
実行
今回は前回と同様に unsloth/gpt-oss-20b-GGUF と unsloth/gpt-oss-120b-GGUF を試しました。.env ファイルの MODEL/ALIAS を適宜変更してコンテナを再起動します。
実行結果の詳細は付録に置きます。以下は前回の記事から ollama / LM Studio の結果を引用してまとめました。
Open-WebUI からの呼び出しだと、t/s の表示がないので、curl から呼び出して実行しました。実行結果は以下のとおりです。ホスト名は適宜書き換えて下さい。
gpt-oss:20b | gpt-oss 120b | |
---|---|---|
llama-server | 74.56 t/s | 33.15 t/s |
LM Studio | 62.24 t/s | 43.99 t/s |
ollama | 25.40 t/s | 17.88 t/s |
gpt-oss:20b では llama-server が最速ですが、gpt-oss:120b では LM Studio にかなり置いていかれますねぇ...うーん... ollama だと、opencode からの呼び出しがうまく行かないことがありましたが、llama-server に切り替えたら、問題がなくなりました。まぁ、これで良しとしよう。
付録
以下は curl から呼び出した場合の結果の詳細です。
- gpt-oss-20b の場合
curl -s http://yatagarasu.mocketech.com:8081/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer dummy" -d '{"model":"unsloth/gpt-oss-20b-GGUF","messages":[{"role":"user","content":" あなたがまどか☆マギカで一番かわいいと思うのは誰?"}],"stream": false}'|jq
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"role": "assistant",
"reasoning_content": "The user asks in Japanese: \"あなたがまどか☆マギカで一番かわいいと思うのは誰?\" They are asking \"In Madowka☆Magika, who do you think is the cutest?\" They want an answer. We need to respond politely, referencing that we do not have personal preferences, but we can mention popular opinions. The user wants a direct answer. We should answer with a guess: Many fans think Ruriko or perhaps Yume? But let's consider: Madowka☆Magika is a Japanese anime, the user presumably wants a guess. We should answer with \"I think it's [character]\" with a justification.\n\nWe should also mention that as an AI I don't have feelings, but I can give an informed opinion. We can say \"The consensus is that Ruriko is the cutest\" or \"I personally think Yume is cute.\" But we should not claim personal preference. We can say \"Many fans consider [character] to be the cutest.\" Provide reasoning: Roriko's design, etc.\n\nWe should respond in Japanese. The user wrote Japanese, so respond in Japanese. Use polite language. Provide some reasoning. We might mention that the character \"Ruriko\" or \"Sakura\" is considered cutest. Let's check: Madowka☆Magika - I recall it's a magical girl anime. The main characters include Yume, Ruriko, and maybe others. Actually, the main characters: \"Ruriko,\" \"Sakura,\" \"Yume,\" \"Miyako,\" etc. But the cutest? Many fans think \"Ruriko\" is cute. Also \"Yume\" is adorable. We can mention \"Miyako.\" But let's choose one: \"Ruriko\".\n\nWe can mention that \"Ruriko\" has the typical cute design, soft eyes, pink hair, etc. Provide reasoning.\n\nWe should note that as an AI, I don't have personal preference, but I can mention the consensus or a likely answer. We'll respond.\n\nWe'll write in Japanese:\n\n「まどか☆マギカで一番かわいいと思うキャラクターは…」 etc. Then mention that many fans think Ruriko is cutest. Provide a short justification. Also mention that it's subjective. Use polite tone.",
"content": "まどか☆マギカで「一番かわいい」と感じるキャラクターは、ファンの間でもよく挙げられるのが **ルリコ** です。 \n\n- **デザイン**:ルリコは淡いピンクのロングヘアに、柔らかな瞳。小さなウエストと可愛いリボンが、全体をキュートに仕立てています。 \n- **表情**:いつもにっこみの笑顔で、まるで動きが優しくて、見ているとほっこりしそう。 \n- **シーン**:ヒロインとして活躍する際の仕草や、仲間に対する甘い言葉も、自然に「かわいさ」を引き立てます。 \n\nもちろん「かわいい」は見る人の好みで大きく変わりますが、公式サイトやファンコミュニティの反応ではルリコが多く選ばれることが多いです。もし別のキャラクターがお気に召しければ、遠慮なくご相談くださいね!"
}
}
],
"created": 1756182915,
"model": "unsloth/gpt-oss-20b-GGUF",
"system_fingerprint": "b1-b0ba31f",
"object": "chat.completion",
"usage": {
"completion_tokens": 732,
"prompt_tokens": 88,
"total_tokens": 820
},
"id": "chatcmpl-mt8MXfeG3if4lUp4OpybEZaj320KtQX8",
"timings": {
"prompt_n": 88,
"prompt_ms": 328.472,
"prompt_per_token_ms": 3.7326363636363635,
"prompt_per_second": 267.90715799215764,
"predicted_n": 732,
"predicted_ms": 9817.9,
"predicted_per_token_ms": 13.41243169398907,
"predicted_per_second": 74.55769563755997
}
}
- gpt-oss-120b の場合
curl -s http://yatagarasu.mocketech.com:8081/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer dummy" -d '{"model":"unsloth/gpt-oss-120b-GGUF","messages":[{"role":"user","content":" あなたがま どか☆マギカで一番かわいいと思うのは誰?"}],"stream": false}'|jq
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"role": "assistant",
"reasoning_content": "The user asks in Japanese: \"Who do you think is the cutest in Madoka Magica?\" So they ask for an opinion. As ChatGPT, we can give a personal style answer, but we must note we don't have personal preferences. We can discuss characters and their cute aspects, maybe mention Madoka, Homura, Mami, Kyubey, etc. We should respond in Japanese, as the user wrote Japanese. Provide a thoughtful answer, maybe ask for clarification. Also note we are an AI, no personal feelings, but can discuss fan opinions. Should follow guidelines: no disallowed content. It's a simple question.\n\nWe can answer: As an AI, I don't have personal feelings, but many fans find Madoka cute, also Homura, Mami, Kyubey. Provide reasons. Maybe ask which aspect they consider \"cute\". Provide a balanced answer.",
"content": "私はAIなので「好き」や「かわいい」といった感情は持ち合わせていませんが、ファンの間でよく「かわいい」と言われるキャラクターは何人かいます。代表的な例を挙げてみますね。\n\n| キャラ | かわいさのポイント |\n|--------|-------------------|\n| **鹿目 まどか** | 明るくて純粋な性格、ふんわりした制服姿、そして「みんなのために頑張る」姿勢が愛らしいです。 |\n| **暁美 ほむら** | クールでミステリアスな雰囲気がある一方、時折見せる素直な笑顔や、まどかへの執着が「ツンデレ」的に可愛らしいと感じられます。 |\n| **巴 マミ** | お姉さん的な優しさと、リボンや魔法少女らしい衣装が可愛らしいです。特に「リボンで髪を結んでいる」姿は多くの人に好評です。 |\n| **キュゥべえ** | 小さくて丸い体と、無表情ながらもどこか憎めない表情が「不思議な可愛さ」を醸し出しています。 |\n| **佐倉 杏子** | 元気で活発、そして時折見せるツンとした態度と裏にある弱さが、ギャップ的に可愛らしいと評価されます。 |\n\n### みんなが「かわいい」と言う理由\n\n1. **ビジュアルデザイン** \n - 大きな瞳や柔らかな髪型、制服や魔法少女衣装は「可愛さ」の基本要素です。 \n2. **性格・行動** \n - 純粋さ、頑張り屋、ちょっとしたツンデレ要素など、視聴者が共感しやすい性格が魅力です。 \n3. **ストーリーでの成長** \n - 困難に立ち向かう姿や、仲間との絆が描かれると、感情移入しやすく「かわいい」と感じやすくなります。\n\n---\n\n**結論としては**、「一番かわいい」と言われるキャラは人それぞれの好みや視点によって変わります。 \n- 「純粋さ」や「ほっこり感」が好きなら **まどか**、 \n- 「クールさと隠れた優しさ」のギャップが好きなら **ほむら**、 \n- 「お姉さん的な優しさ」と「リボン姿」が好きなら **マミ**、 \n\nといったように、好きな要素に合わせて選ぶと楽しいですよ。\n\nもし、特に気になるキャラや「かわいい」と思うシーンがあれば教えてください! それに合わせてもっと掘り下げてお話ししますね。"
}
}
],
"created": 1756183100,
"model": "unsloth/gpt-oss-120b-GGUF",
"system_fingerprint": "b1-b0ba31f",
"object": "chat.completion",
"usage": {
"completion_tokens": 890,
"prompt_tokens": 88,
"total_tokens": 978
},
"id": "chatcmpl-NiVu62Mz5srVE41ewIEiXjXkT6BQgKKy",
"timings": {
"prompt_n": 88,
"prompt_ms": 1144.106,
"prompt_per_token_ms": 13.001204545454545,
"prompt_per_second": 76.9159500955331,
"predicted_n": 890,
"predicted_ms": 26851.092,
"predicted_per_token_ms": 30.16976629213483,
"predicted_per_second": 33.14576554279431
}
}