※ 記事中にプロモーション広告(アフィリエイトリンク)が含まれます。
管理人の小職さん
ロボ小職
自宅のProxmoxサーバーで余っていたGeForce GTX 1070 8GBを、Ubuntu ServerのVMへPCIパススルーしました。そこにPascal世代対応のFreeToken-Pascalをぶち込み、Qwen3.6 35B A3B NVFP4モデルをOpenAI互換APIとして起動しています。
さらにWindows版OpenCodeから、SSHトンネル経由で接続。ファイル調査から編集、コマンド実行、tool calling、WebFetch、WebSearch、自動Compactionまで、一通り動くローカルAI開発環境に仕上がっています。
最初からスムーズに動いたわけではありません。Pascal向けJITカーネル、データ型の不一致、MoEキャッシュ設定、tool-call parser、reasoning parser、コンテキスト超過、日本語の文字化け…。トラブルは数え切れず、全部を一つずつ切り分けて対処しました。
注意:この記事で紹介するユーザー名、IPアドレス、ファイルパスは構成例です。ご自身の環境に合わせて変更してください。また、この記事はGeForce GTX 1070で35B級モデル全体をVRAMへ載せたという内容ではありません。GPU、CPU、システムRAM、PCIe、FreeToken-Pascalを組み合わせたハイブリッド構成です。
※この記事の公開後も検証を続けたため、「実測結果」の直後に後日談を追記しています(コンテキストを65,536トークンへ拡張、CUDA Graphで速度が約2倍になった件)。
GTX 1070でQwen3.6 35Bが動いた
VM300上のFreeToken-Pascalで、Qwen3.6 35B A3B NVFP4モデルをAPI Readyまで起動できました。ここが一番の通過点です。

| 項目 | 確認結果 |
|---|---|
| GPU | NVIDIA GeForce GTX 1070 8GB |
| GPU世代 | Pascal、sm_61 |
| 仮想化基盤 | Proxmox VE |
| AIサーバー | Ubuntu Server VM300 |
| 推論エンジン | FreeToken-Pascal |
| モデルID | qwen3.6-35b-a3b-nvfp4-candidate |
| コンテキスト | 32,768トークン(後日談で65,536へ拡張) |
| APIポート | 8082 |
| クライアント | OpenCode 1.18.32 |
| tool calling | OpenAI互換の構造化tool_callsを確認 |
| WebFetch | 成功 |
| WebSearch | 成功 |
| 自動Compaction | 有効 |
| SSHトンネル | ログオン時自動起動、切断時自動再接続 |
| 短い直接API | 約2.36秒から4.01秒 |
| 短いストリーミングAPI | 約2.35秒で完了 |
ただ、OpenCodeでの処理時間は、直接APIの応答時間とは全然違います。システム指示、ツール定義、ファイル内容、Web取得結果、会話履歴まで全部が入力に乗るからですね。初回Prefillやキャッシュミスのときは、結果が返るまで数分待たされることもあります。
管理人の小職さん
ロボ小職
この記事に登場する4つの役割
今回の構成では、役割をこう分けています。ごちゃ混ぜにするとどの数値がどの環境のものか分からなくなるので、最初にすっきり整理しておきますよ。
- FreeToken-Pascal(現在の推論エンジン):Pascal世代のGTX 1070でFreeTokenを動かすためのforkです。VM300では今はこのエンジンでQwen3.6を常駐させています。
- llama.cpp(一時的な退避構成):FreeToken-Pascalの問題を切り分けている間に、動作確認とOpenCode接続を進めるために使いました。今は使っていないので、勘違いしないでくださいね。
- OpenCode(クライアント):Windows側でリポジトリを調査・編集するAIコーディングクライアントです。FreeToken-PascalのAPIへSSHトンネル経由でつながります。
- SSHトンネル(接続経路):WindowsからVM300のAPIへ安全に届く経路です。APIポートをLANへ出していません。
今回完成したVM300の構成
Windows PC
├─ OpenCode 1.18.32
├─ freetoken-ops
├─ AGENTS.md
├─ WebFetch / WebSearch
└─ 127.0.0.1:8082
│
│ SSHトンネル
▼
Ubuntu Server VM300
├─ FreeToken-Pascal
├─ freetoken-qwen36.service
├─ Qwen3.6 35B A3B NVFP4
├─ GTX 1070 8GB
├─ CPU / システムRAM
└─ 127.0.0.1:8082
Windows側からVM300の8082番ポートをLANへ直接出さず、SSHトンネルを使っています。OpenCodeから見えた接続先はWindows自身の127.0.0.1:8082。一見どこにも見えない接続ですね。
Pascal世代GPUで挑戦した理由
GTX 1070はPascal世代のGPUです。最近のRTXシリーズと違ってTensor Coreはありません。しかも最近のCUDAカーネルにはVolta以降を前提にした実装が混じっているので、「CUDA対応GPUなら最新AI推論エンジンが動く」とはいかないんです。
まずは、今回PCIパススルーで割り当てたのと同じ世代のカード、顔を見ておきましょう。

写真は、8GBのVRAMを積んだPascal世代のリファレンスカードです。今回の構成では、このVRAMに載る範囲をGPUが引き受け、載りきらないExpertの計算はCPUとシステムRAMが受け持ちます。得意なところ同士で分ける、いわば家事分担みたいな構成です。
FreeToken-Pascalは、FreeToken本家をPascal世代へ対応させるforkです。Tesla P100のsm_60と、GTX 10シリーズのsm_61を対象に、pre-Volta環境でもJITカーネルをコンパイルできるよう調整が入っています。
Pascal世代で引っかかるのが、CUDAの__grid_constant__です。FreeToken-Pascalではこの指定を条件付きマクロで保護して、Pascalでは互換性のない指定を外しつつ、Volta以降では本来の処理を残す設計になっています。
新しいGPU向け機能を全部削るのではなく、Pascalでコンパイルできない部分だけを世代別に切り分けている。ここがこのforkの巧さです。
今回使用した環境(VM300)
| 項目 | 内容 |
|---|---|
| 仮想化基盤 | Proxmox VE |
| Proxmoxホストの物理メモリ | 64GB |
| 仮想マシン | VM300 |
| ゲストOS | Ubuntu Server |
| VM割り当てメモリ | 約27GiB |
| Swap | 8GiB |
| GPU | NVIDIA GeForce GTX 1070 8GB(PCIパススルー) |
| モデル用ストレージ | 約196GB |
| 推論エンジン | FreeToken-Pascal |
| モデル | Qwen3.6 35B A3B NVFP4(qwen3.6-35b-a3b-nvfp4-candidate) |
| コンテキスト | 32,768トークン(後日談で65,536へ拡張) |
| クライアント | OpenCode 1.18.32(Windows) |
GTX 1070の中身、このGP104ダイです。

Pascal世代は16nm世代で、Tensor Coreはありません。そのぶん構成はシンプルですが、最新GPU向けカーネルをそのまま動かせるわけではないので、次に説明する世代別対応が必要になるんです。
構築手順
ここからは、最終構成に必要な操作だけをサクッとまとめます。途中の試行錯誤は、次の「つまずいた問題」の章でばらして解説しますね。
注意:以下には小職の環境で使ったパス、ユーザー名、IPアドレスが含まれます。そのままコピーせず、各自の環境に合わせて変更してください。
Step 1:Ubuntu VMを作成
今回のVM構成はこんな感じです。
(PR)
- メモリ:約27GiB
- Swap:8GiB
- GPU:GTX 1070をPCIパススルー
- モデル用ディスク:約196GB
Proxmoxホストには64GBの物理メモリがあります。そこから約27GiBをVM300へ割り当てました。モデル用のRAMをしっかり確保しつつ、Proxmoxホストと他のVMに使う分の余裕もちゃんと残しているのがコツです。
VMの作成は、Proxmox VEの管理画面から行います。

管理画面では、VMの一覧とリソース使用状況がひと目で分かります。メモリ割り当てとディスク容量は後から変えにくいので、先に決めておくと作業がスムーズですよ。
Step 2:必要パッケージを導入
パッケージを更新する前に、まずは今の環境を確認しておきましょう。
nvidia-smiでドライバーバージョンを確認してください。スナップショットさえあれば、問題が出たときも元の状態へポンッと戻せます。
sudo apt update
sudo apt install -y build-essential cmake ninja-build git curl wget python3 python3-venv
ビルドツールを入れたら、GPUの顔出し確認をしましょう。
nvidia-smi
GTX 1070が表示されれば、GPUパススルーもNVIDIAドライバーもバッチリ動いています。
Step 2.5:GPUパススルー設定
GPUパススルーは本番環境への影響が大きいので、詳しい手順は別記事へ分けています。ここでは要点だけ。
- GTX 1070をVM300へPCIパススルー済み
- GPUとAudio Functionを同じVMへ割り当てた
- VM内で
nvidia-smiを確認した - 実際のVM設定値:
raw=true、pcie=true
詳しくは「Proxmox VE入門」の記事をどうぞ。
パススルーはホストと他のVMにも影響する操作なので、先に注意点を頭へ入れておきましょう。
冷却対策について:GTX 1070のTDPは150W。長時間のLLM推論では温度が上がりがちです。ケース内のエアフローを整えて、冷却はちゃんと効かせておいてください。高温稼働はVRAMエラーやスロットリングのタネになります。
GTX 1070だけで150Wですが、CPUやストレージ、長時間の連続運転まで考えると、電源ユニットは多少余裕を持たせておきたいですね。
電源に余裕がないと、負荷が上がった瞬間に再起動がかかっちゃったり、GPUがスロットリングしたり。意外とここときしますよ。
PCIe帯域幅について:GTX 1070が刺さるPCIeスロットの世代と幅は、性能に直結します。たとえばGen3 x16なら片方向16GB/s、Gen2 x16なら8GB/s。ExpertデータがCPU側RAMとGPUの間を頻繁に行き来する構成なので、ここがボトルネックになることは十分あり得ます。可能ならGen3 x16スロットへ接続してください。
Step 3:モデル用ストレージ準備
sudo mkdir -p /srv/ai/freetoken-data/models
sudo mkdir -p /srv/ai/freetoken-data/hf-cache
sudo mkdir -p /srv/ai/inventory
sudo chown -R <Ubuntuユーザー名>:<グループ名> /srv/ai/freetoken-data
sudo chown -R <Ubuntuユーザー名>:<グループ名> /srv/ai/inventory
<Ubuntuユーザー名>と<グループ名>は、各自の環境に合わせて書き換えてください。
Step 4:FreeToken-Pascal導入
Pascal世代向けのforkを取得して、Python仮想環境へインストールします。
sudo mkdir -p /opt/ai/src
sudo chown -R "$USER":"$USER" /opt/ai/src
cd /opt/ai/src
git clone https://github.com/CryptoJones/FreeToken-Pascal.git freetoken-pascal
cd freetoken-pascal
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .
FreeToken-Pascalは、Tesla P100のsm_60とGTX 10シリーズのsm_61を対象に、pre-Volta環境でもFreeTokenのJITカーネルをコンパイルできるようにしたforkです。
動作を確認できたコミットは、記録しておきましょう。
git rev-parse HEAD
git status --short
回帰防止のため、動作確認済みのコミットを固定しておくに越したことはありません。
git checkout <動作確認済みコミット>
最初につまずいた問題
FreeToken-Pascalのビルドが成功しても、そこでOpenCodeで即実用、にはなりませんでした。
データ型の不一致
生成試験で、hc.pyのLinear処理にて、BFloat16入力とHalf重みのデータ型不一致エラーが出ました。
この段階ではPascal向けJITカーネルのビルドは確認できても、文章生成まで成功したとは言えない状態でした。
MoEキャッシュサイズ0の扱い
--moe-cache-size 0を明示的に指定しても、内部でmoe_cache_auto=Trueへ変換されてしまう問題も発見です。
そこで、明示的な0を正しく尊重するようにargs.pyを局所修正しました。大規模な書き換えはせず、問題が発生している条件だけをつつくように修正して、既存動作への影響は最小限。修正後は回帰テストを実施して、63件がPASSです。
一時的にllama.cppへ退避
FreeToken-Pascalの問題を切り分けている間は、llama.cppを使ってQwen3.6の動作確認とOpenCode接続を進めていました。そのため、当時の記事にはllama.cppを常用エンジンとして書いた版が残っています。
あの頃に測ったGGUFのUD-Q4_K_M、モデルサイズ約22.1GB、VRAM使用量約3.2GB、生成速度約20〜22トークン/秒、GPU温度約41度といった数値は、あくまで退避構成当時の記録です。今のFreeToken-Pascalの性能として使えないですからね。
API Readyまでの起動設定
いよいよ、FreeToken-Pascal上で次のモデルを起動します。
qwen3.6-35b-a3b-nvfp4-candidate
現在の主要設定はこんな感じです。
- 最大シーケンス長:32,768
- 最大出力トークン:1,024
- Prefill分割長:2,048
- キャッシュ方式:radix
- tool-call parser:qwen3_coder
- reasoning parser:qwen3
- MoE CPU threads:10
- MoE cache size:0
- APIポート:8082
FreeToken-Pascalはsystemdのユーザーサービスとして運用しています。
systemctl --user status freetoken-qwen36.service
起動時は、次のログを目印にします。
Prefill warmup complete
Scheduler is idle, waiting for new reqs...
API server is ready to serve on 0.0.0.0:8082
モデル起動からAPI Readyまでは、実測で約6分30秒前後かかりました。モデルロードとPrefill warmup込みの起動時間です。ちょっと待ち時間ですよね。
API Readyが終われば、次のOpenAI互換エンドポイントが使えます。
http://127.0.0.1:8082/v1/models
http://127.0.0.1:8082/v1/chat/completions
tool callingの正常化
OpenAI互換APIから普通の文章が返るだけでは、OpenCodeのコーディングエージェントとしては物足りません。ここは用途を分けて確認しました。
tool-call parserの変更
OpenCodeがツールを動かすには、モデルの応答がOpenAI互換の構造化されたtool_callsになっている必要があります。
初期状態は、tool callがXML風の文字列のまま通常本文に混ざっていて、OpenCodeはツール呼び出しとして認識してくれませんでした。そこでtool-call parserをqwen3_coderへ変更。効きました。
直接API試験では、こう確認しています。
finish_reason = tool_callstool_calls_present = True- tool name:
list_directory - arguments:
{"path":"."}
これで、OpenCodeからbash、edit、WebFetch、WebSearchなどのツールを呼べるようになりました。
reasoning parserの設定
初期状態では、内部推論を示す</think>などの文字列が、OpenCodeの通常本文へ紛れ込むことがありました。今はreasoning parserへqwen3を設定しています。
確定している組み合わせはこれです。
tool-call parser: qwen3_coder
reasoning parser: qwen3
これはThinking機能を全面オフにした、という意味ではありません。FreeToken側でreasoningと通常本文を分け、OpenCodeの表示へ不要な内部推論を混ぜないための構成です。
Radixキャッシュの効果
OpenCodeは、ファイル読取りやツール実行のたびにモデルを何回も呼び出します。そのたびにシステム指示、ツール定義、AGENTS.md、会話履歴などを再処理するのは、古いGPUには大変な負担です。
初期設定のキャッシュ方式はnaiveでした。これをradixへ切り替えです。
| 試験 | 所要時間 | new-token | cached-token |
|---|---|---|---|
| 同一要求1回目 | 41秒 | 310 | 0 |
| 同一要求2回目 | 13秒 | 54 | 256 |
同一prefixの試験では、約68%の短縮を確認しました。68%は無視できませんよ。
ただし、Radixを有効にしても全部が速くなるわけではありません。OpenCodeがツール結果や会話構造を組み替えるとprefixがずれて、cached-token=0になることもあります。
管理人の小職さん
ロボ小職
コンテキストとCompaction
OpenCodeは、あなたが入力した文章だけをモデルへ送るわけではありません。次の要素も全部入り込みます。
- システム指示
- エージェント定義
- ツール定義
- AGENTS.md
- 会話履歴
- ファイル内容
- コマンド結果
- Web取得結果
長いセッションでは、こんなコンテキスト超過が飛び出しました。
prompt is too long: 38879 tokens > 32768 maximum
現在のOpenCode設定はこれです。
- input limit:32,000
- context limit:32,768
- output limit:512
- request max_tokens:512
- provider timeout:30分
- chunk timeout:30分
- automatic compaction:有効
- compaction keep:4,000
- compaction buffer:12,000(後日談で8,000へ調整)
自動Compactionは、長いセッションを続けるなら必需品です。ただしローカル35BモデルではCompaction自体も重くて、約6分9秒かかった例もあります。軽い動作だとは思わないでくださいね。
bufferを12,000へ増やして早めに圧縮を開始し、keepを4,000へ減らして圧縮後のコンテキストを軽くしました。それでも、無関係な作業を一つのセッションへ詰め込まず、おおむね1作業1セッションで使う方が安定します。
この記事の公開後、コンテキストを65,536トークンへ拡張したときに、bufferは8,000へ調整しました。keepは4,000のままです。広いコンテキストを使いながら、圧縮後の体積は抑えめ。この組み合わせにすることで、公開時点より1セッションを長く続けられるようになりました。詳しい経緯は後日談で書っています。
Web取得機能の追加
OpenCodeから最新の公式情報や既知URLを確認できるよう、常用エージェントfreetoken-opsへWeb機能を統合しました。
- 既知URLの取得:webfetch
- Web検索:websearch
- Web権限:ask
- Web検索有効化:
OPENCODE_ENABLE_EXA=true
Webツールを使うときは、モデルが通常本文で「実行してよいですか」と二重に聞いてくる方式ではなく、Webツールを直接呼び出します。承認はOpenCodeの権限確認画面で行うので、流れがスムーズです。
次の動作を確認しました。
- WordPress公式トップページのWebFetch
- WordPress公式開発者向け資料のWebSearch
- 公式サイト優先
- 検索結果を最大3件に制限
- Web調査時にプロジェクトファイルを変更しない
Web調査の入力がふくらみすぎないよう、次のルールも追加しています。
- WebFetchは1ターン1 URL
- WebSearchとWebFetchを同一ターンで連続実行しない
- WebSearch結果は原則3件以内
- 同じURLを同一セッションで再取得しない
- ページ内リンクを自動取得しない
- Web取得後は再取得せず、取得済み結果を整理する
AGENTS.mdで操作範囲を制御
参考 OpenCodeとFreeTokenの構築手順(Manjaro環境)ローカルLLMだからといって、プロジェクト全体を無制限に調査・変更させる構成にはしていません。プロジェクトルートへAGENTS.mdを置いて、OpenCodeの操作規則をGitで管理しているんです。
主なルールはこんな感じです。
- 作業対象は現在のプロジェクト内に限定
Read .による全体読取りを禁止- 無制限の全体走査を禁止
- 一度に読むファイルは最大3件
- 一度に変更するファイルは最大3件
- ディレクトリ一覧は最大20件
- 既存の未コミット変更を上書きしない
git reset、git clean、強制checkoutを禁止- ファイル削除は明示的な許可が必要
- パッケージ追加・削除は許可制
- 巨大なルール文書は全文を読まず、関連箇所だけ確認
- Web結果を大量にコンテキストへ追加しない
関連するコミットはこちらです。
ee69fc299 Add OpenCode project operating rules
9ab941df4 Unify approved web access into OpenCode ops agent
4a837daf9 Add bounded web access performance rules
この記事でいうバイブコーディングは、AIへ無制限の操作権を渡すことではありません。対象、権限、変更量、テスト、バックアップ、ロールバックをはっきり決めたうえで、AIと対話しながら開発を進める方式です。ここが一番大事なところです。
SSHトンネルの自動起動
WindowsからVM300へつなぐため、専用のSSH鍵を作りました。
$HOME\.ssh\id_ed25519_vm300_tunnel
記事中の$HOMEは、今使っているWindowsユーザーのホームディレクトリです。フルパスで書くと、例えばC:\Users\<Windowsユーザー名>\.ssh\id_ed25519_vm300_tunnelの形になります。<Windowsユーザー名>は各自のユーザー名へ置き換えてください。
SSHの設定例はこれです。
Host vm300
HostName <VM300_IP>
User <Ubuntuユーザー名>
IdentityFile ~/.ssh/id_ed25519_vm300_tunnel
IdentitiesOnly yes
BatchMode yes
StrictHostKeyChecking yes
ConnectTimeout 10
ServerAliveInterval 30
ServerAliveCountMax 3
ExitOnForwardFailure yes
BatchModeでパスワード入力なしの接続を試験して、終了コード0を確認しています。
トンネル監督スクリプトは、次へ保存しました。
$HOME\Scripts\Start-FreeTokenTunnel.ps1
監督スクリプトには、こんな機能を入れてあります。
127.0.0.1:8082のSSH転送- 切断時の自動再接続
- Mutexによる多重起動防止
/v1/modelsのヘルスチェック- ログ保存
ログは次へ溜まります。
$HOME\Logs\freetoken-tunnel.log
タスクスケジューラ方式も試しましたが、最後に選んだのはWindowsのStartupフォルダー方式です。StartupフォルダーのStart-FreeTokenTunnel.cmdから、監督スクリプトを非表示で起動しています。
@start "" /min powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File "%USERPROFILE%\Scripts\Start-FreeTokenTunnel.ps1"
同じ監督スクリプトを二重に起動すると、ログにこんな風に残って、後発プロセスはお仕舞いになります。
Another tunnel supervisor is already running.
日本語本文とファイル名の文字化け対策
Windows PowerShellで日本語ファイルを直接読む場合は、UTF-8指定で問題なく表示できました。ところがOpenCodeのbashツール経由では、PowerShellの標準出力に入っている日本語が文字化けしてしまったんです。
これはファイルが壊れているのではなく、PowerShellからOpenCodeへ標準出力を渡す経路の文字コード問題です。日本語本文はPythonでUTF-8として読み、ASCIIだけで表せるUnicodeエスケープへ変換してモデルへ渡しています。
python -c "import json,pathlib; print(json.dumps(pathlib.Path('TARGET_FILE').read_text(encoding='utf-8')[:4000], ensure_ascii=True))"
ファイル名やディレクトリ名も、Pythonのpathlibで取得してjson.dumps(..., ensure_ascii=True)で出力します。モデルは\u958b\u767aのようなUnicodeエスケープをちゃんと日本語へ戻して読んでくれます。
文字化けした表示だけを根拠に、ファイル名の改名や文字コード変換、再保存はしないでください。かえって事態を悪くしますよ。
実測結果
短い直接API
| 試験 | 所要時間 | 結果 |
|---|---|---|
| 1回目 | 約4.01秒 | OK |
| 2回目 | 約2.36秒 | OK |
入力は17トークン、出力は2トークンでした。
ストリーミングAPI
| 項目 | 値 |
|---|---|
| HTTPステータス | 200 |
| 最初のデータ受信 | 約0.027秒 |
| ストリーム完了 | 約2.346秒 |
この試験で、FreeTokenの基本API、SSHトンネル、ストリーミング終了処理はすべて正常と判断できます。
OpenCodeのWebFetch
WordPress公式サイトをWebFetchした試験では、最終回答まで約8分56秒かかりました。ログを見ると、WebFetch結果を受け取ったあとに次のPrefillが走っています。
2048 + 2048 + 584 = 4680 tokens
cached-token = 0
FreeTokenの短い直接APIは数秒で返るのに、OpenCodeではツール定義やWeb取得結果を含む大きな入力を処理し直すので、数分かかることがあるんです。
参考 ローカルLLM推論環境比較2026|LM Studio・Ollama・FreeTokenを実測比較後日談:コンテキストを64Kへ拡張してわかったこと
この記事を公開したあとも、VM300のチューニングは続いていました。狙いは一つ、コンテキストを32K(32,768トークン)から64K(65,536トークン)へ広げることです。長いセッションで「prompt is too long」にイライラさせられたくないので、一度に一気ふかしではなく、32K→48K→64Kと段階的に試しました。
管理人の小職さん
ロボ小職
32K→48K→64Kの段階拡張と実測
| 構成 | 実効速度 | VRAM使用量 |
|---|---|---|
| 32K(この記事公開時点) | 4.252 token/s | 5,623 MiB |
| 48K | 4.207 token/s | 5,943 MiB |
| 48K + CUDA Graph 1 | 8.453 token/s | 6,103 MiB |
| 64K + CUDA Graph 1 | 平均7.739 token/s | 6,423 MiB |
速度はDecode(本文を1トークンずつ生成していく処理)の実効値です。単純にコンテキストを広げるだけだと、32Kの4.252 token/sから48Kの4.207 token/sへ、むしろわずかに落ちました。VRAMは5,623 MiBから6,423 MiBへ増えて、8GB(8,192 MiB)の範囲内ではありますが、余裕はひかえめです。
CUDA Graphが一番効いた

いちばん効果が大きかったのがCUDA Graph 1です。CUDA Graphは、GPUで行う処理の手順を一度記録しておき、以降はその記録を再生して実行するNVIDIAの仕組み。毎回処理の呼び出しを組み直す手間が減るので、繰り返しの多い推論に向いています。48K構成でCUDA Graph 1を有効にしたら、4.207 token/sが8.453 token/sへ、約100.9%の改善。32Kから64Kへ広げたあとの平均7.739 token/sは、CUDA Graphなしの時代(4.252 token/s)のおよそ1.8倍です。コンテキストを広げたのに速くなるって、こういうことなんです。
Prefill・Offload・MoEキャッシュの結果
併せて3つ試しました。結論から言うと、効いたのはCUDA Graphだけでした。
- Prefillの2,048対4,096:改善は約0.40%で、ほぼ誤差の範囲でした。Prefillは入力文をまとめて読み込む処理で、この構成ではボトルネックになりにくいと判断できます。
- Offload:不採用にしました。VRAMに載せきれない部分をCPUとシステムRAMへ移して処理する方式ですが、有効にして15分待っても実Readyにならず、結局CPU側へ戻しました。スタートアップが安定しない状態で常用する意味はないためです。
- MoEキャッシュの自動設定:実効512で有効にしましたが、速度の変化はありませんでした。
Ready判定は/v1/modelsではなくChat Completions基準
もう一つ変わったのが判定方法です。以前は/v1/modelsが応答したタイミングでReadyと見なしていましたが、これは実際の生成準備が終わるより早いらしいんです。実際に1往復させるChat Completionsを基準にしたら、32K構成で約330秒、40K構成で約390秒かかることが判明(Ready判定の試験は32K構成と40K構成で実施しています)。「APIが応答した=すぐ使える」ではないので、スクリプトで起動を監視するなら生成テストまで待つのが確実です。
Windows側のOpenCodeも64Kへ同期
VM300側だけでなく、WindowsとVM105のOpenCode設定も64Kへ揃えました。
context: 65536
input: 63488
output: 2048
body.max_tokens: 2048
compaction: auto = true
keep = 4000
buffer = 8000(変更前 12000)
コンテキスト上限を広げたら、入力側の上限(63,488)と出力側の上限(2,048)も合わせて調整しないとダメです。片方だけ変えても、結局はその上限で止まりますよ。bufferは変更前の12,000から8,000へ、keepは4,000据え置きです。
なお、opencode --auto を使う場合は注意してください。自動実行モードはコンテキストが大きい状態でも作業を続けやすい反面、Compaction(会話履歴の自動圧縮)が走るタイミングも長くなります。重要な操作を伴う作業では、途中の様子を確認しながら進める方が安全です。
管理人の小職さん
ロボ小職
バックアップとロールバック
設定を変える前に、設定ファイル本体とOpenCode側の設定をコピーして日付を付けて残しています。元に戻したいときは、このコピーを戻すだけで32K時代へ復元できます。VMのスナップショットを取ってから1項目ずつ変更する、この流儀は記事本編で行ったときと同じです。変更項目が多い日ほど、バックアップの日付は細かくしています。
以上が公開後の追加チューニングです。古くてもPascal世代のGPUで、コンテキストを広げた状態でも無理なく動かせました(詳しい値は各表のとおり)。次の「確認できたことと未確認のこと」では、この後日談も含めて、確定したこととまだ分からないことを整理しています。
確認できたことと未確認のこと
今回の検証で確定したことと、断定できないこと、はっきり分けておきます。
| 区分 | 内容 |
|---|---|
| 確定 | FreeToken-Pascalのビルド成功、Pascal向けJITカーネルのコンパイル確認 |
| 確定 | Qwen3.6 35B A3B NVFP4のAPI Ready到達、OpenAI互換エンドポイントの応答 |
| 確定 | OpenAI互換の構造化tool_calls、WebFetch、WebSearch、自動Compactionの動作 |
| 確定 | Radixキャッシュによる同一prefixの短縮、ローカル修正後の回帰テスト63件PASS |
| 確定 | SSHトンネルのログオン時自動起動と、切断時の自動再接続 |
| 未確認 | NVFP4モデルの入手元と取得手順の詳細(環境により異なるため、本記事では確定情報として示していません) |
| 未確認 | 各Expertの配置、Active Expertの演算場所、CPUとGPU間の実転送量、レイヤーごとの配置 |
| 未確認 | 他環境での生成速度。CPU、メモリ帯域、PCIe、量子化形式、モデル構造に依存します |
速い処理と遅い処理
今回の環境で、わりと速い処理です。
- 短い直接API
- 短いストリーミング
- 同じprefixを再利用できる処理
- 対象ファイルが明確な小規模調査
- 短いGit状態確認
時間のかかりやすい処理です。
- 初回Prefill
- キャッシュが一致しない大きな入力
- WebFetch結果の再投入
- 長い会話のCompaction
- プロジェクト全体の無制限走査
- 大量ファイルの一括読取り
「GTX 1070で35B級モデルが動くこと」と「すべてのAIコーディング処理が数秒で終わること」は、まったく別ものです。
GTX 1070で35Bモデルを使う際の制約
- 35B全体を8GB VRAMへ載せているわけではない
- CPU性能とメモリ帯域の影響を受ける
- PCIeリンク速度やスロット配置の影響を受ける
- 量子化形式とモデル構造に依存する
- 同じGTX 1070でも同じ性能になるとは限らない
- 長いコンテキストはPrefill負荷が大きい
- Compactionにも推論時間が必要
- 電力、ハードウェア、ストレージ、保守コストは必要
FreeTokenはソフトウェア利用料をゼロで済ませる構成は作れますが、ローカル推論全体が完全無料という意味ではありません。電気代も保守の手間もありますからね。
処理速度を左右するのはGPUだけではありません。VRAMに載りきらないExpertの計算はCPU側が担当するので、CPUのコア数とメモリ帯域も体感速度に効いてきます。
とはいえ、CPUを強化してもVRAMとPCIeの制約は残ります。まずは今の環境で、どこがボトルネックなのかをのぞいてみるのがおすすめです。
向いている用途と向いていない用途
向いている用途
- 指定ファイルの調査
- 小規模なコード修正
- Git差分の確認
- 対象を限定したテスト
- 公式仕様のWeb検索
- 既知URLの確認
- WordPress、SEO、運用を横断した相談
- ローカル環境へコードや情報を残したい作業
向いていない用途
- プロジェクト全体の無制限走査
- 大量URLの一括取得
- 大規模Webクロール
- 複数の無関係な作業を一つのセッションで継続
- 最新GPUと同等の応答速度を要求する用途
- 時間制限の厳しいリアルタイム処理
Qwen3.6を選んだ理由
最初はQwen3-Coder-30B-A3Bを使いました。明確なコード修正やファイル検索には使えますが、しばらく使うと、小職の用途には少し融通が利かないと感じてきたんです。
- 明確なコード変更:比較的得意
- 曖昧な相談:やや苦手
- 日本語の細かい意図:伝わりにくいことがある
- 複数案の比較:一つの方針へ固執することがある
- WordPress、SEO、多言語、運用の横断:やや不得意
小職が求めていたのは、コードを書くだけのモデルではありません。たとえば、こんな相談です。
「WordPressサイトを速くしたい。ただし、SEO、多言語ページ、既存デザインは壊したくない。プラグインを増やすだけでなく、サーバー、テーマ、画像、キャッシュまで含めて考えたい。」
Qwen3.6へ切り替えると、この依頼をぐっと整理して返してくれました。バイブコーディングって、コード生成能力だけでなく、ちゃんと話が通じることが大事なんです。その点、今回の用途ではQwen3.6の方がずっと扱いやすいです。
Proxmoxへ64GB積んだ意味
自宅のProxmoxサーバーへ64GBのメモリを積んだときは、複数のVMを余裕を持って動かすのが主な目的でした。ところがローカルLLMを始めたら、このメモリが急に価値ある資源に変わります。

- Proxmoxホスト:64GB RAM
- Qwen3.6用のUbuntu Server VM300:約27GiB RAM
- GPU:GTX 1070 8GB
メモリは、ローカルLLMでいちばん先に効いてくるパーツです。VRAMに載りきらないExpertをCPU側へ逃がす構成では、システムRAMの容量がそのまま「扱えるモデルの余裕」につながります。
増設する場合は、マザーボードの対応規格とスロット数、そしてホスト側に残す分まで含めて確認してくださいね。
GPUを最新製品へ買い替えるのではなく、既存のProxmoxサーバー、余裕のあるRAM、古いGTX 1070を組み合わせて、新しい用途を作ることができました。
ローカルAIサーバについてのよくある質問
読者さんからよく届く質問を、ここにまとめておきますね。
8GB VRAMで35Bモデルが動く理由は?
管理人の小職さん
ロボ小職
実際の生成速度はどのくらい?
管理人の小職さん
ロボ小職
llama.cppの数値は今も使える?
管理人の小職さん
ロボ小職
64Kへ拡張したら速度は落ちませんか?
管理人の小職さん
ロボ小職
CUDA Graphとは何ですか?
管理人の小職さん
ロボ小職
VRAMの使用量はどれくらい増えますか?
管理人の小職さん
ロボ小職
コンテキストを広げるとモデルの挙動は変わりますか?
管理人の小職さん
ロボ小職
Offloadはなぜ使わなくなったのですか?
管理人の小職さん
ロボ小職
Readyになったかどうかはどう確認すればいいですか?
管理人の小職さん
/v1/modelsではなく、実際にChat Completionsで1往復させることを基準にしてください。今回の環境では、32K構成で約330秒、40K構成で約390秒でした。起動スクリプトから監視するなら、生成テストが返るまで待つのが確実です。
ロボ小職
Windows側のOpenCodeも設定を変える必要がありますか?
管理人の小職さん
ロボ小職
compaction bufferはなぜ8,000にしたのですか?
管理人の小職さん
ロボ小職
opencode –auto の運用で注意することはありますか?
管理人の小職さん
ロボ小職
設定を戻したいときはどうすればいいですか?
管理人の小職さん
ロボ小職
MoEキャッシュの自動設定は効きましたか?
管理人の小職さん
ロボ小職
このやり方は他のGPUでもできますか?
管理人の小職さん
ロボ小職
まとめ
2016年世代のGeForce GTX 1070 8GBでも、FreeToken-Pascal、CPU、システムRAM、PCIe、適切な量子化モデルを組み合わせれば、Qwen3.6 35B A3B NVFP4をOpenAI互換APIとして動かせました。
さらに、Windows版OpenCodeからSSHトンネル経由でつなぎ、構造化tool calling、WebFetch、WebSearch、自動Compaction、Radixキャッシュ、AGENTS.mdによる安全制御まで、ちゃんと確認済みです。
最初から簡単に動いたわけではありません。Pascal向けJIT、データ型不一致、MoEキャッシュ、tool-call parser、reasoning parser、コンテキスト超過、日本語文字化け、SSH自動復旧。トラブルは次から次へとでしたが、一つずつ確認しては小さく直し、問題があれば戻せる状態を作ってきました。
管理人の小職さん
ロボ小職
最新GPUのような圧倒的な速度ではありません。それでも、自宅にある古いGPUへ新しい役割を与え、ローカルLLMと相談しながら実際のリポジトリを扱えるところまで構築できました。RAMを増やせば何でも速くなるわけではありませんが、MoE、量子化、CPUオフロード、システムRAMをうまく組み合わせれば、古いハードウェアにも新しい使い道はあります。
トラブルの切り分けまで含めて、まさに「思い立ったら弄る!」を実践したローカルAI環境になりました。
後日談も含めた要点は次のとおりです。コンテキストは32,768から65,536トークンへ段階的に拡張し、速度はCUDA Graph 1の有効化で4.252 token/sから平均7.739 token/sへ改善。VRAMは6,423 MiBで、8GBの範囲内に収めています。Offloadは安定起動の確認が取れなかったので不採用、Ready判定はChat Completions基準へ変更し、Windows側OpenCodeも64Kへ同期済みです。古いGPUでも、設定を広げる前にバックアップを取り、1項目ずつ試して戻せる状態にしておくこと。それが一番の近道でした。
参考資料
- FreeToken 本家 GitHubリポジトリ
- FreeToken-Pascal GitHubリポジトリ
- OpenCode 公式ドキュメント
- FreeTokenでローカルLLMが超進化!RTX 3060 12GB + 32GB RAMで大型MoEを動作させてみた
(PR)
タグ





