Conao3 Notes

PLaMo 3 Translateを手元で動かしてパイプで訳すCLI plamo-tr-localを作った

Preferred Networks (PFN) の翻訳特化 LLM である PLaMo 3 Translate の重みが、2026-11-05 に公開されました。 このモデルを手元の PC で動かして、シェルのパイプで文章を訳すコマンド plamo-tr-local を作りました。 リポジトリは conao3/rust-plamo-tr-local (public) で、ライセンスは Apache-2.0 です。 記事の中の訳文の例はすべて PLaMo 3 Translate の生成物です。

img

きっかけ

PLaMo 3 Translate は 0.8B・2B・8B・31B の 4 つの大きさで公開され、llama.cpp 向けの GGUF や MLX 向けの量子化版もそろっています (PFN のテックブログ)。 ただし、重みをダウンロードしただけでは訳せません。 PFML3 という形式でプロンプトを組み、llama.cpp などでモデルを動かす手間があるので、文章を少し訳したいときには使いにくいです。

作ろうと思ったのは、kat0h さんのポストを見たからです。 kat0h さんは、Claude の日本語の言い回しが嫌で、Claude の応答を手元の PLaMo を通して表示するスクリプトを用意したそうです。 手元の翻訳モデルをパイプの 1 段として使えれば、こうした使い方をすぐに試せます。

使い方

Nix があれば、インストールせずに実行できます。

$ nix run github:conao3/rust-plamo-tr-local -- text translate-text --text 今日はいい天気ですね。

Cargo でもインストールできます。 Cargo はビルドの途中で llama.cpp をコンパイルするので、CMake と C++ コンパイラと libclang が要ります。

$ cargo install --git https://github.com/conao3/rust-plamo-tr-local

初回の実行で、Hugging Face の pfnet/plamo-3-translate-<大きさ>-GGUF から Q4_K_M の GGUF をキャッシュ (Linux では ~/.cache/plamo-tr-local ) へ自動でダウンロードします。 アカウントも API キーも要らず、訳す文章は端末の外に出ません。

訳文の言語を指定しなければ、日本語の文は英語に、それ以外の言語の文は日本語に訳します。 既定の出力は JSON です。

$ plamo-tr-local text translate-text --text 今日はいい天気ですね。
{
  "TranslatedText": "The weather is nice today, isn't it?",
  "SourceLanguageCode": null,
  "TargetLanguageCode": "en"
}

標準入力から読むときは --text を省きます。 訳文だけを取り出すときは、出力の JSON を jq に渡します。

$ echo "Since it's raining today, I think I'll relax at home and read a book." \
    | plamo-tr-local text translate-text | jq -r .TranslatedText
今日は雨だから、家でゆっくり本を読んでいようと思う。

--glossary で語の訳を決め、 --writing-style で日本語の常体 (plain) と敬体 (polite) を選べます。

$ plamo-tr-local text translate-text --text "Filter tasks by owner." \
    --glossary Source=owner,Target=担当者 --writing-style polite | jq -r .TranslatedText
担当者でタスクを絞り込みます。

ほかに、周りの段落やページの種類を伝える --context と、モデルの大きさを選ぶ --model-size があります。 --model-size には 0.8b ・ 2b ・ 8b ・ 31b を指定でき、既定は 2b です。 小さいモデルほど速く訳し、大きいモデルほど訳の質が上がります。

AWS CLI 型のコマンド体系

コマンド体系は AWS CLI 型にしました。 <コマンド> <サブコマンド> の後に、名前付きの引数を並べます。

PFML3 のプロンプトを組む

PLaMo 3 Translate へ渡すプロンプトの形式は PFML3 です。 <|plamo:tag|> で始まる節を並べ、データセットの種類、参考文脈、用語集、原文、訳文の言語と文体を書きます。 plamo-tr-local は GGUF のリポジトリに付く chat_template.jinja と同じ形で、引数からこの文字列を組み立てます。 たとえば、参考文脈 "A task list" と用語集 owner → 担当者 を付けて "Filter by owner." を日本語の敬体に訳すときのプロンプトは次のとおりです。 実際は 1 行の文字列で、ここでは読みやすいように節ごとに改行しています。

<|plamo:tag|>dataset<|plamo:msg|>translation
<|plamo:tag|>context<|plamo:msg|>A task list
<|plamo:tag|>glossary<|plamo:index|>owner<|plamo:item|>担当者
<|plamo:tag|>input<|plamo:msg|>Filter by owner.
<|plamo:tag|>output<|plamo:key|>lang<|plamo:val|>ja<|plamo:key|>form<|plamo:val|>polite<|plamo:msg|>

input の節に原文の言語 (lang) を書かなければ、モデルが原文の言語を判定します。 モデルは最後の <|plamo:msg|> に続けて訳文を生成し、 plamo-tr-local は <|plamo:tag|> などの区切りのトークンが出たところで生成を止めます。 トークンの選び方は greedy (毎回もっとも確率の高いトークンを選ぶ) です。 原文に <|plamo: を含む文字列があると、プロンプトの区切りと見分けられません。 そのとき plamo-tr-local は、終了コード 254 で終了します。

長い文は段落ごとに訳す

モデルカードによると、モデルが一度に扱えるのは入力と出力を合わせて 32,768 トークンまでです。 plamo-tr-local は長い文を空行で段落に分け、4,096 トークン以下になるように段落をまとめて 1 回ずつ訳します。 1 つの段落が 4,096 トークンを超えるときは、行ごとに分けます。 訳した結果は元の文と同じ空行と改行でつなぎ直します。

作るときに当たったこと

llama-cpp-2 のリビジョンの固定

Rust から llama.cpp を呼ぶのには、llama-cpp-rs の llama-cpp-2 クレートを使っています。 ところが、crates.io の 0.1.161 に同梱された llama.cpp では、モデルの読み込みが unknown tokenizer: 'plamo3' で失敗しました。 そこで、llama.cpp v0.8.0 を取り込んだ llama-cpp-rs の git のリビジョンに固定しました。

[dependencies.llama-cpp-2]
git = "https://github.com/utilityai/llama-cpp-rs"
rev = "0364f5da1c6a6ee37f507031f116ce4a09b28594"
version = "0.1.162"

Nix でビルドするときは、git から取る依存の中身を固定するために、 flake.nix の cargoLock.outputHashes にハッシュを書きます。

cargoLock = {
  lockFile = ./Cargo.lock;
  outputHashes."llama-cpp-2-0.1.162" = "sha256-7jI4+6tJ6s69ojMeqUbDHbbUfmlMbnecpvWrTM7iLEM=";
};

AVX2 を使うビルド

llama-cpp-sys-2 (llama-cpp-2 が使う llama.cpp のバインディング) は、llama.cpp の CPU 向けの命令セットを Rust の target feature から決めます。 Rust の x86_64 の既定は SSE2 までなので、そのままビルドすると llama.cpp も AVX2 を使わず、3〜4 倍遅くなりました。 そこで、 .cargo/config.toml で x86_64 Linux の target CPU を x86-64-v3 にしました。 x86-64-v3 は AVX2 を含む水準で、Intel は Haswell 以降、AMD は Excavator 以降の CPU が当てはまります。

[target.x86_64-unknown-linux-gnu]
rustflags = ["-C", "target-cpu=x86-64-v3"]

スレッド数の既定

手元の Intel Core Ultra 5 225U は、性能重視の P コアと省電力の E コアが混ざった CPU で、論理 CPU は 14 個です。 次の節と同じ MIT ライセンスの訳を、2b のモデルでスレッド数を変えて測りました。 4 スレッドは 1 回、ほかは 3 回以上測った中央値です。

スレッド数 最初のトークンまで 出力 (214 トークン) 全体
4 4.6 秒 13.0 秒 21.2 秒
7 3.2 秒 10.0 秒 17.0 秒
10 2.9 秒 9.3 秒 15.9 秒
14 2.7 秒 10.4 秒 16.8 秒

img

入力を読むのは 14 スレッドがもっとも速く、出力の生成は 10 スレッドがもっとも速くなりました。 8・9・11・12 スレッドも測りましたが、出力の生成は 10 スレッドより遅くなりました。 14 スレッドでは、出力の生成が 7 スレッドより遅くなりました。 そこで、スレッド数の既定を論理 CPU の 3/4 (手元では 10) にしました。 環境変数 PLAMO_TR_LOCAL_THREADS で変えられます。

速さ

MIT ライセンスの本文 (英語 169 語、252 トークンのプロンプト) を日本語に訳す時間を、Intel Core Ultra 5 225U の CPU だけで測りました。 スレッド数は既定の 10 で、どの値も 3 回以上測った中央値です。 各段階の時間は --debug が出す値です。

$ plamo-tr-local text translate-text --text file://LICENSE | jq -r .TranslatedText
モデル 読み込み 最初のトークンまで 出力の速さ 全体
0.8b 2.8 秒 0.7 秒 71 トークン/秒 6.9 秒
2b 3.4 秒 2.9 秒 23 トークン/秒 15.9 秒

img

「最初のトークンまで」はモデルを読み込んだあと、プロンプトを読んで最初のトークンを出すまでの時間です。 グラフの「読み込みなど」は、全体から「最初のトークンまで」と出力の生成の時間を引いた残りで、モデルの読み込みを含みます。 訳文は 0.8b が 229 トークン、2b が 214 トークンでした。 2b の訳文は次のとおりです。

MITライセンス

Copyright (c) <year> <copyright holders>

本ソフトウェアおよび関連ドキュメントファイル(以下「本ソフトウェア」)の複製を取得したすべての者に対し、以下の条件に従い、本ソフトウェアを制限なく取り扱う(本ソフトウェアの使用、複製、改変、結合、公開、頒布、サブライセンス、および/または販売を行う権利、ならびに本ソフトウェアを提供された者が同様の行為を行うことを許可する権利を含むが、これらに限定されない)ことを、無償で許諾する。

上記の著作権表示および本許諾表示は、本ソフトウェアのすべての複製物または実質的な部分に含まれなければならない。

本ソフトウェアは「現状のまま」提供され、明示的か黙示的かを問わず、商品性、特定目的への適合性、および非侵害性に関する保証を含むがこれらに限定されない、いかなる種類の保証も伴わない。いかなる場合においても、著作者または著作権者は、契約上の行為、不法行為、またはその他の事由を問わず、本ソフトウェアに起因し、または本ソフトウェアの使用もしくはその他の取り扱いに関連して生じたいかなる請求、損害、またはその他の責任についても、一切の責任を負わないものとする。

モデルの読み込みはコマンドを呼ぶたびにかかり、0.8b では全体の 4 割を占めます。 Claude の答えを段落ごとに流して訳すような使い方では、この読み込みの時間が段落の数だけ積み重なります。 モデルを読み込んだまま入力を待つ常駐の形にするのが、次の課題です。

文体の指定で試したこと

PLaMo 3 Translate で指定できる文体 (form) は、常体 (plain) と敬体 (polite) の 2 つです。 お嬢様言葉や関西弁に訳せるかを試しました。 llama.cpp の llama-server で 2B と 8B の GGUF (Q4_K_M) を動かし、temperature を 0 にして、次の指定を試しました。

  • 訳文の言語 (lang) に お嬢様言葉 ・ 関西弁 ・ Japanese (Kansai dialect) を入れると、原文の日本語をそのまま返しました。
  • form に お嬢様言葉 ・ ojousama ・ kansai を入れると、常体で訳しました。
  • 参考文脈 (context) に「お嬢様言葉で訳す」と書くと、ふつうの口調で訳しました。
  • お嬢様言葉の対訳の履歴を 2 組、原文の前に置くと、ふつうの口調で訳しました。

2B と 8B のどちらでも、お嬢様言葉や関西弁の訳文は出ませんでした。

一方、日本語から日本語へ form=polite を付けて訳すと、常体の文を敬体に書き換えられました (2B)。

今日は雨だから家で本読むわ。
→ 今日は雨だから家で本を読みます。

plamo-tr-local は原文と訳文に同じ言語を指定すると止まるので、この書き換えは llama-server で試しただけです。

ライセンス

plamo-tr-local のライセンスは Apache-2.0 です。 plamo-tr-local がダウンロードするモデルのライセンスは PFN の PLaMo コミュニティライセンス (本文) で、要点は次のとおりです。

  • 私的な利用と非商用の利用は無償です。
  • 商用の利用は PLaMo 商用利用登録フォームで登録したうえで、直近の事業年度の売上が 10 億円以下である間は無償です。
  • 生成した訳文を公表するときは、PLaMo の生成物であることを明示します。
  • モデルや派生モデル、生成物を第三者に提供するときは「Built with PLaMo」と表示します。 plamo-tr-local の README にもこの表示を書いています。

使う前に、ライセンスの本文を読んでください。

おわりに

文章を端末の外に出さずに訳したいときに使ってください。