判断特化型AI「Jev」互換のOSSモデル「Kev」を Docker上で試してみた

判断特化型AI「Jev」互換のOSSモデル「Kev」を Docker上で試してみた

TypeSafe AI の判断特化型 AI「Jev」と同じ `POST /v1/systemone` API を持つ OSS モデル「Kev」を、Apple M1・メモリ 16GB・GPU なしの Linux 環境で Docker から起動し、必要なリソースと回答の傾向を確かめました。
2026.09.27

はじめに

「Jev」は、料金の安さとレスポンスの速さで注目されている判断特化型 AI です。

https://dev.classmethod.jp/articles/jev-guide-with-examples/

その Jev と同じ API を実装した OSS の判断特化型 AI(デシジョンモデル)「Kev」が、Apache-2.0 ライセンスで公開されています。複数のサイズがあり、最小の Kev-0.8B(Qwen3.5-0.8B-Base ベース)は GPU のない環境でも動作するとされています。

今回は、初代 M1 Mac(メモリ 16GB、CPU 8 コア)に Linux を入れた環境の Docker 上で、Kev-0.8B を試した結果を紹介します。

Kev の API とモデルサイズ

https://github.com/jaredpalmer/kev

Kev は jaredpalmer/kev で開発されているデシジョンモデルです。Jev と同じ POST /v1/systemone を受け付けるので、TypeSafe の SDK はエンドポイントの変更だけで利用できます。

モデルサイズは 0.8B / 4B / 9B / 27B の 4 つです。README は 4B を標準として薦めていますが、4B 以上はメモリ 32GB の Mac か GPU が前提とされています。CPU で実用的な速度が出るのは最小の 0.8B なので、この記事では 0.8B を評価しています。

動作環境

項目 値
OS Fedora Linux Asahi Remix 44(aarch64)
CPU Apple M1 / 8 コア(性能コア 4 + 高効率コア 4)
メモリ 16GB
Docker イメージ arm64 python:3.12-slim(torch 2.8.0+cpu)

macOS ではなく Linux で動かしているため、GPU(Metal)は使えず、PyTorch は CPU 経路になります。

Docker で Kev-0.8B を起動する

バージョンとモデルを固定するため、公式リポジトリのソースをビルドして起動します。

docker run -d --name kev-server \
  -p 127.0.0.1:8009:8009 \
  -e KEV_API_KEY=localkey \
  -e OMP_NUM_THREADS=4 \
  python:3.12-slim \
  sh -c "
    apt-get update -qq && apt-get install -qq -y git build-essential g++
    pip install --quiet uv
    git clone --quiet https://github.com/jaredpalmer/kev /kev
    cd /kev && git checkout --quiet f153596
    uv sync --quiet --extra serve
    uv run python -m kev.serve --run jaredpalmer/kev-0.8b --host 0.0.0.0 --port 8009
  "

f153596 は執筆時点の最新コミットです。モデル jaredpalmer/kev-0.8b は起動時に自動でダウンロードされ、認証は要りません。依存の解決とモデルの取得を含めて、起動までに約 90 秒かかりました。

Fetching 9 files: 100%|██████████| 9/9 [00:02<00:00]
Loading weights: 100%|██████████| 320/320 [00:00<00:00]
INFO:     Started server process [1434]
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8009 (Press CTRL+C to quit)

起動後に /v1/models を呼び出すと、実行構成が返ります。

run=jaredpalmer/kev-0.8b device=cpu backend=torch dtype=float32 temperature=2.3510958125672174

応答に含まれる確率は、checkpoint ごとに同梱された temperature で校正された値です。表示されている 2.35 は 0.8B 用です。

KEV_API_KEY を設定する

Kev は KEV_API_KEY が未設定だと認証なしで応答します(kev/serve.py、commit f153596)。

API_KEY = os.environ.get("KEV_API_KEY")   # unset = open server; set = require Authorization: Bearer <key>

未設定のコンテナでは、認証ヘッダなしでも誤ったキーでも 200 が返りました(キーを設定すると、認証ヘッダなしでは 401 でした)。先の起動コマンドでは -e KEV_API_KEY=localkey でキーを渡しています。

また、既定のバインド先は 127.0.0.1 ですが、Docker で使うには --host 0.0.0.0 が必要です。その代わりに -p 127.0.0.1:8009:8009 で、ホスト側の待ち受け先をループバックに絞っています。

Dockerfile でモデルまで焼き込む

毎回 clone と uv sync を待ちたくない場合は、ビルド時にモデルまで焼き込んだイメージにします。この形では、起動までに約 30 秒でした。

Dockerfile-kev とビルド・起動コマンド

Dockerfile-kev として保存します。

FROM python:3.12-slim

RUN apt-get update -qq \
 && apt-get install -qq -y --no-install-recommends git build-essential g++ \
 && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir uv==0.9.7

RUN git clone https://github.com/jaredpalmer/kev /kev
WORKDIR /kev
RUN git checkout f153596
RUN uv sync --extra serve

# ビルド時にモデルを焼き込む(初回起動を待たない)
RUN uv run python -c "from huggingface_hub import snapshot_download; snapshot_download('jaredpalmer/kev-0.8b')"

# KEV_API_KEY と OMP_NUM_THREADS は ENV に焼かず docker run -e で渡す
#   KEV_API_KEY     : 未設定だと無認証で応答するため必ず渡す。ENV に書くと docker build が SecretsUsedInArgOrEnv を警告する
#   OMP_NUM_THREADS : 適値はホストの CPU 構成で変わる

EXPOSE 8009
CMD ["uv", "run", "python", "-m", "kev.serve", \
     "--run", "jaredpalmer/kev-0.8b", "--host", "0.0.0.0", "--port", "8009"]
docker build -f Dockerfile-kev -t kev-local .
docker run -d --name kev-server -p 127.0.0.1:8009:8009 -e KEV_API_KEY=localkey -e OMP_NUM_THREADS=4 kev-local

API で質問を投げる

POST /v1/systemone に、判定対象のテキスト(state)と質問(questions)を渡します。回答形式は質問ごとに type で指定します。選択肢から選ぶ choice、はい・いいえを 1 つの確率で返す noul、段階評価の score の 3 形式があります。choice なら criteria に選択肢を並べます。認証には、起動時に設定した KEV_API_KEY を Bearer トークンとして渡します。

まず choice を 1 問だけ聞く例です。

curl -s -X POST http://localhost:8009/v1/systemone \
  -H "Authorization: Bearer localkey" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "インフラ設計のレビューをしたい",
    "model": "kev-latest",
    "questions": {
      "skill": {
        "type": "choice",
        "instructions": "Which skill fits this message?",
        "criteria": {
          "aidlc-infrastructure-design": null,
          "aidlc-bugfix": null,
          "aidlc-market-research": null,
          "none": null
        }
      }
    }
  }'
{
    "model": "kev-latest",
    "answers": {
        "skill": {
            "type": "choice",
            "choice": "aidlc-infrastructure-design",
            "confidence": 0.705,
            "probabilities": {
                "aidlc-infrastructure-design": 0.7788,
                "aidlc-bugfix": 0.0078,
                "aidlc-market-research": 0.0016,
                "none": 0.2118
            }
        }
    },
    "usage": {
        "input_tokens": 39,
        "output_tokens": 89
    },
    "latency_ms": 1029.4
}

応答の形も Jev と同じで、choice と probabilities を持ちます。confidence は正答率ではなく、README が (p_max − 1/K) / (1 − 1/K) と定義する値です(K は選択肢の数)。

サポートチケットの振り分けを想定して、担当部署(choice)、エスカレーションの要否(noul)、怒りの度合い(score)を 1 リクエストで聞いた応答です。

{
    "model": "kev-latest",
    "answers": {
        "department": {
            "type": "choice",
            "choice": "shipping",
            "confidence": 0.2625,
            "probabilities": {"returns": 0.338, "shipping": 0.5083, "billing": 0.1537}
        },
        "escalate": {"type": "noul", "noul": 0.4363},
        "frustration": {
            "type": "score",
            "score": 1.3551,
            "legend": {"0": "Calm", "1": "Frustrated", "2": "Very angry"},
            "probabilities": {"0": 0.041, "1": 0.5629, "2": 0.3961},
            "confidence": 0.3444
        }
    },
    "usage": {"input_tokens": 102, "output_tokens": 180},
    "latency_ms": 994.8
}

コンテナに割り当てるリソース

必要なメモリの下限

docker run --memory の値を変えて起動した結果です。

制限 結果
4 GB 起動・応答 OK(使用量の実測 3.516 GiB)
3.5 GB OOMKilled(重み読み込み中、ExitCode 137、12 秒で停止)

既定の fp32 では重みが 0.8B × 4 byte = 約 3.2GB(3.0GiB)で、バッファが加わって 3.5GiB 強でした。コンテナには 4GB 以上のメモリを割り当てます。

参考:CPU の設定で変わる応答時間

M1 CPU で測定した latency_ms です。プロンプトキャッシュの影響を除くため、いずれも同じ入力を初めて送ったときの値です(測定回数は列見出しに示します)。

設定 質問1・初回(3回の範囲) 質問5・初回(1回)
既定(8スレッド, fp32) 1760–2068 ms 3728 ms
OMP_NUM_THREADS=4 591–624 ms 2073 ms
KEV_DTYPE=bf16(既定スレッド) 13315–13597 ms 32753 ms
KEV_DTYPE=fp16(既定スレッド) 10768–11038 ms 19145 ms

スレッド数は性能コアの数に合わせます。既定の 8 スレッドから OMP_NUM_THREADS=4 にすると、質問 1 件では約 3 倍速くなりました。起動時に OMP_NUM_THREADS=4 を渡しているのはこのためです。

dtype は既定の fp32 が最速です。KEV_DTYPE=bf16 は既定スレッド同士の比較で 7.6 倍遅くなりました。M1 の CPU は bf16 の演算命令を備えず、低精度の行列積が最適化されないためです。fp16 でも 6 倍遅くなりました。

一方、bf16 にすると必要なメモリは減り、0.8B は 2GB 制限でも、4B は 11GB 制限でも起動できました。ただし、bf16 で動かした 4B の応答時間は 26〜93 秒でした。CPU 環境で 4B を使うことは、応答時間が長いため見送ることとしました。

回答の精度を確かめる

README のモデル表(commit f153596)では、new sources(development 列)の精度が Kev-0.8B で 0.648、Kev-4B で 0.817、Jev で 0.857 です。ただし、Jev の学習データが不明なため、統制された比較ではないとも注記されています。

手元では、5 ケース(回答 7 個)を 0.8B と 4B に同じ形式で投げ、正解が一意に決まる 3 件を表に示します。

ケース Kev-0.8B Kev-4B
skill 選択(明確な依頼) aidlc-infrastructure-design 0.779 ✓ aidlc-infrastructure-design 0.721 ✓
entailment(正解 insufficient) contradicted 0.911 ✗ insufficient 0.996 ✓
knowledge(正解 nitrogen) nitrogen 0.801 ✓ nitrogen 0.979 ✓

入力に判断の手がかりが明示されている分類、たとえば明確な依頼に対するスキル選択や知識問題(knowledge)では、0.8B も 4B と同じ回答を返しました。一方、文の含意を判定するような推論が必要な問題(entailment)では、0.8B は誤答に 0.9 を超える確率を割り当てました。これは README で confident errors と呼ばれている失敗です。確率が高くても、正しい回答とは限りません。

まとめ

Jev と API 互換性のある OSS の Kev は、モデルサイズや性能の制限はありますが、CPU のみ、4GB メモリの Docker 環境上でも動作することが確認できました。

今回は実行環境の制限で最小モデルを評価しましたが、GPU を積んだホストや、Apple Silicon ネイティブ(MLX)が使える macOS 環境があれば、より高性能なモデルも動作する可能性があります。

判定や分類に特化したモデルを評価したい、あるいは推論時の入力データを外部 API に送信したくない場合などに Kev をお試しください。

この記事をシェアする

関連記事