DGX Spark1台でQwen3.8 Flash NextをClaude CodeとDeepSeek Harnessから動かす
はじめに
ローカルLLMを動かすためにDGX Sparkの互換機であるThinkstation PGXを購入したわけではないですが、せっかく1台持っているので、どのモデルであればまともに動作するのかをいろいろ試していました。
zenn.dev
しかしながら、調べれば調べるほど、128GB機1台程度では、他のGeforce GPU搭載のPCと比較して優位性のある結果にはなりませんでした。(Strataが強すぎる・・・)
とはいえその中でも、ある程度まともに動く中で大きめなモデルとしてQwen3.8 Flash Nextを動かしてみました。
!
strataの場合は、例えばRTX3060(VRAM 12GB)+RAN 64GB程度だと3bit程度の量子化のFlash Nextが動作可能です。
今回動作させるのは4bit程度の量子化のFlash Nextになります。
今回やりたいのは、このPCをLLMのサーバーにして、普段使っている別のPCからコードを書いてもらうことです。
操作にはClaude CodeとDeepSeek Harnessを使います。以降、DeepSeek Harnessはコマンド名に合わせて「dsh」と書きます。
最終的に使うのは、作業フォルダから呼び出す次の2つのコマンドです。
localclaude
localdsh
ここで普通に「claude」コマンドを使わないのは、普段使っているOpus 5.5とかを動かす際のclaudeの設定と、ローカルLLMを呼び出す時の設定を切り分けて、ストレスなく使うためです。
さて、せっかくローカルで動かすなら、速度も気になりますよね。
今回は、BF16のKVキャッシュとMTPを使った構成で、速度、コードの動作、メモリ使用量を確かめました。
利用するモデル
文章やコードの生成には、NVIDIAが公開しているFP4版のFlash Nextを使います。
NVIDIAが量子化後のモデルを学習し直したQAD版で、NVFP4とMXFP8などを組み合わせています。
huggingface.co
NVIDIAがこのモデルの対応エンジンとして案内しているのはvLLMです。
DGX Spark向けには、新しい重みの圧縮形式へ対応したコミュニティ製の専用コンテナを指定しています。
今回はそのvLLMとTensorFoldを比べ、生成速度とメモリ使用量からTensorFoldを選びました。
huggingface.co
TensorFoldとvLLMの比較条件
Claude Codeに同じ3つの課題を頼みました。ファイルを保存し、自分で動作確認してもらうところまで含めた試験です。
課題
確認した内容
区間の結合
重なる区間や端点が同じ区間の結合、不正な区間の検出、入力を変更しないこと
トポロジカルソート
辞書順で最小の順序、重複した辺、循環や未定義ノードの検出
JSON Linesの処理
ユーザー別の金額集計、真偽値の除外、不正入力の行番号、出現順の保持
比較相手は、NVIDIAのモデルカードで案内されているeugr/spark-vllm-b12xです。コンテナのdigestを固定し、初回の最適化を終えてから測りました。vLLMの版は0.1.dev21830+g8244d1c44.d20261026です。
モデルの重み、BF16のKVキャッシュ、MTP先読み6、コンテキスト上限262,144、同時実行上限3件を揃えています。vLLM側も、元のBF16 LMヘッドを保ち、追加のMXFP8変換や活性値の追加量子化を無効にしました。起動時のメモリ不足を避けるため、標準safetensorsローダとPyTorchのメモリ管理設定を使っています。
Claude Codeは--effort highで起動しました。モデルへの実効値はxhighに揃え、temperatureは1、top_pは0.95、top_kは20、出力上限は50,000トークンです。
比較時には、生成条件を揃えてサーバーの統計を記録する計測用の中継を挟みました。モデルやエンジンのコードは変更していません。後半の導入手順では、Claude CodeからTensorFoldのAPIへ直接接続します。
項目
TensorFold 1.1.9
vLLM
生成中の速度
58.16 token/s
40.98 token/s
入力処理・通信を含むAPI全体の速度
21.52 token/s
19.91 token/s
3課題のClaude Code実行時間
378.15秒
418.10秒
LLMへの要求回数
18回
18回
思考を含む出力トークン数
8,021
8,198
表は、3課題で発生した全要求を集計した結果です。
モデルの読み込みと初回最適化の時間は含めません。
token/sで見ると、TensorFoldが約1.42倍でした。Claude Codeが3課題を終えるまでの時間も、今回は約40秒短くなっています。
こちらのモデルで搭載されている以下の補助機構も利用して利便性と生成速度を向上させます
FP4版に含まれるMTP
別のドラフトモデルのダウンロードは不要
画像エンコーダ
FP4版に含まれる画像エンコーダ
PNG・JPEG・WebPなどの画像を入力可能
MTPとは
MTPはMulti-Token Predictionの略で、次の1トークンに加えて、その先のトークンも予測する仕組みです。
トークンは、モデルが文章を扱うときの文字や単語のまとまりです。
今回のFlash NextにはMTP用の補助部分が含まれており、これを使って続きの候補を先に作ります。別の小型モデルを追加で用意する必要はありません。
投機的デコードとは
通常の生成では、本体のモデルを動かして1トークンずつ確定します。
投機的デコードでは、先に作った数トークン分の候補を本体でまとめて検証します。候補がうまく当たった分だけ、本体を動かす回数を減らせる仕組みです。
今回の構成では、MTPが候補を作り、投機的デコードの手順で本体が検証・採用します。
以降の記事で記載している「先読み6」というのは、一度に最大6トークン分の候補を作る設定です。
外れた候補はそのまま採用せず、本体の出力に従って補正します。
詳細は以下の記事をご覧ください
zenn.dev
環境
モデルを動かしたのは、DGX Sparkと同じGB10を搭載したThinkStation PGXです。
実測と、記事の導入手順で使うソフトウェアをまとめます。
項目
環境
サーバー
Lenovo ThinkStation PGX、1台
CPU
Arm、20コア。Cortex-X925とCortex-A725を各10コア
GPU
NVIDIA GB10
NVIDIAドライバ
580.159.03
メモリ
CPUとGPUで共有する128GB
OS・アーキテクチャ
Ubuntu 24.04.4 LTS、aarch64
Linuxカーネル
6.17.0-1021-nvidia
推論エンジン
公式TensorFold 1.1.9のLinux aarch64配布版。
GPU実行環境
NVIDIAドライバを備えたホスト上で、公式のネイティブ実行ファイルを直接起動
Claude Code
2.1.223
DeepSeek Harness
0.2.0-rc.2。Node.jsはv24.16.0
実測と導入手順で使う構成を、次の設定に揃えます。
項目
設定
モデルの重み
NVIDIAのFP4版
KVキャッシュ
BF16
MTP
有効・先読み6
コンテキスト上限
262,144トークン
同時実行上限
3件
画像入力
無効。今回は文字・コード生成を対象とする
同時実行上限は、APIサーバーが同時に生成するリクエスト数の上限です。
今回は最大3件を並行して処理します。それ以上の依頼を送った場合は、空いた枠を使って順番に処理するため、5件を送れば5件とも同時に生成するわけではありません。
MTPの先読み数は、1件の生成で予測するトークン数です。並列数を増やす設定ではありません。
速度とコードの品質は、思考を有効にした同じClaude Codeの作業で確認しました。以降は、その作業中に記録した速度とメモリ使用量を見ていきます。
別PCから使う場合は、同じLAN内のPCにClaude Codeまたはdshを入れて接続します。後半は、その設定手順です。
実験結果
Claude Codeに次のPythonコードを書いて保存・実行してもらいました。課題は1件ずつ、思考highで実施しています。
タスク(未公開)
初回
修正後
生成速度(token/s)
重なる区間を結合する
不合格
合格
60.84
依存関係に沿って辞書順に並べ替える
合格
合格
54.21
JSON Linesからユーザー別の金額を集計する
合格
合格
59.84
各課題8項目のテストをすべて通れば合格としています。
初回の合格率は66.7%(2/3課題)。区間の結合を1回修正した後は100%(3/3課題)となり、計24項目のテストをすべて通過しました。
全体の生成速度は58.16 token/s、3課題の実行時間は約6分18秒でした。速度には思考と修正分も含み、モデルの読み込み時間は除いています。
ツール利用に関しても、4bit量子化であることもあり、かなり安定して動作しています。
OSを含む使用メモリの最大値は84.31GiBで、利用可能メモリは最少でも35.32GiB残っていました。
利用構成・環境構築
接続の流れは、次のようになります。
Claude Codeを使っていても、この構成で文章やコードを生成するモデルはFlash Nextです。
Claude Codeは、依頼を送り、返ってきた指示に従ってファイルを編集する役割を担当します。
TensorFoldが両方のAPI形式を扱うため、今回の手順では別の形式変換サーバーを置きません。
接続先の指定には、1点違いがあります。
利用する側
ベースURL
主な生成API
Claude Code
192.168.0.194
/v1/messages
dsh
192.168.0.194
/v1/chat/completions
192.168.0.194は本記事の例です。自分のPGXのLAN内IPアドレスに置き換えてください。
APIサーバの構築
ここからは、モデルを動かすPGX側の操作です。
Ubuntu系のLinuxで、NVIDIAドライバが利用できることを前提にします。公式のLinux aarch64配布版を取得し、モデルのダウンロードから始めます。
github.com/ashhart/TensorFold/blob/v1.1.9/README.…
まず、GPUとメモリ、ディスクの空き容量を確認します。
nvidia-smi
free -h
df -h "$HOME"
モデルの配布ファイルは約96GiBです。実行ファイルやダウンロードの作業領域にも空き容量が必要なので、モデルの容量だけでディスクを埋めないようにしてください。
公式の実行ファイルを取得
今回はPGXのArm CPUに合わせて、Linux aarch64版を使います。
mkdir -p "$HOME/local-llm/flash-next"
cd "$HOME/local-llm/flash-next"
V=1.1.9
curl -fLO "github.com/ashhart/TensorFold/releases/download/v…"
curl -fLO "github.com/ashhart/TensorFold/releases/download/v…"
sha256sum -c "tensorfold-$V-linux-aarch64.tar.gz.sha256"
tar -xzf "tensorfold-$V-linux-aarch64.tar.gz"
./tensorfold-1.1.9-linux-aarch64/bin/tensorfold-native --version
チェックサムの検査が通り、tensorfold-native 1.1.9と表示されれば、取得できています。展開したフォルダには実行ファイルと必要なライブラリが入っているため、フォルダごと保存してください。
モデルのダウンロード
同じ作業フォルダで、公式のpullコマンドを使います。モデルのリビジョンも指定しておきます。
cd "$HOME/local-llm/flash-next"
HF_HUB_CACHE="$PWD/models-cache" \
./tensorfold-1.1.9-linux-aarch64/bin/tensorfold-native pull \
nvidia/Qwen3.8-Flash-Next-FP4-Local@b9ce18a6887e18c089fdea8509a33e63429a3300
このコマンドで取得するのは、重み、MTP、トークナイザーなど、Hugging Faceの同じ公開モデルに含まれるファイル一式です。初回は約96GiBあるため、ダウンロードには時間がかかります。
保存先は、この例ではmodels-cacheです。途中で止まった場合は、同じコマンドで取得を再開できます。
APIキーの作成
今回はLAN内へ公開し、APIキーを付けて呼び出します。次の操作は、初回に一度行ってください。
cd "$HOME/local-llm/flash-next"
umask 077
python3 - <<'PY'
from pathlib import Path
import secrets
p = Path('api-key')
p.write_text(secrets.token_hex(32) + '\n')
p.chmod(0o600)
PY
api-keyに保存した値を、後でクライアント側にも設定します。サーバーには、公式の--api-key-fileで渡します。
サーバの起動
取得した固定リビジョンのフォルダを指定し、次のコマンドで公式の実行ファイルを起動してください。
(以降、サーバを止めてから再開するときはこのコマンドを実行すればOK)
cd "$HOME/local-llm/flash-next"
HF_HUB_OFFLINE=1 TF_FLASHNEXT_DRAFTS=6 \
./tensorfold-1.1.9-linux-aarch64/bin/tensorfold-native serve \
"$PWD/models-cache/models--nvidia--Qwen3.8-Flash-Next-FP4-Local/snapshots/b9ce18a6887e18c089fdea8509a33e63429a3300" \
--name Qwen3.8-Flash-Next \
--host 0.0.0.0 --port 8888 \
--api-key-file "$PWD/api-key" \
--context 262144 --parallel 3 \
--max-tokens 50000 \
--temperature 1 --top-p 0.95 --top-k 20 \
--thinking --reasoning-effort high \
--kv-cache bf16 --prompt-cache-gib 0 \
--prompt-seed --no-update-check
パラメータ解説
TF_FLASHNEXT_DRAFTS=6で、MTPの先読み数を指定します。
MTPは既定で有効です。
--kv-cache bf16、--parallel 3、--context 262144が、今回測定した構成になります。
--max-tokens 50000は、応答の出力上限の既定値です。
--prompt-cache-gib 0は、リクエスト間で状態を再利用するプロンプトキャッシュを無効にする設定です。
--prompt-seedは、入力文からseedを決める設定です。
今回のClaude Codeが使うAnthropic形式のAPIは、要求側のseedを生成処理へ渡さないため、この入力由来のseedで動作します。実測でも有効にしています。
起動ログを確認できたら、そのターミナルは開いたままにしてください。
別のターミナルやクライアントPCから接続しましょう。
停止は、サーバーを動かしたターミナルでCtrl+Cです。再起動するときも、同じ起動コマンドを使います。
LAN内IPアドレスは次のコマンドで確認できます。
hostname -I
複数表示される場合は、別PCと同じLANに属するアドレスを指定してください。
この手順は信頼できるLAN内で使うHTTP接続です。
同一LANからのAPI動作確認
ここから先は、普段作業するPCで操作します。
以下はmacOS、Linux、WindowsのWSLで使えるBash系の手順です。
まず接続先とAPIキーを~/.config/flash-next/connection.envに保存します。
モデル名は保存せず、起動するたびにサーバーから取得するようにします。
APIキーは、サーバーのapi-keyファイルに保存した値へ置き換えてください。
接続先FLASH_BASE_URLは、サーバをたちあげたPCのIPアドレスとポート番号を指定して下さい。
mkdir -p "$HOME/.config/flash-next" "$HOME/.local/bin"
chmod 700 "$HOME/.config/flash-next"
umask 077
cat > "$HOME/.config/flash-next/connection.env" <<'ENV'
FLASH_BASE_URL='192.168.0.194'
FLASH_API_KEY='63e36・・・(ここをサーバーのapi-keyファイルの値へ置き換える)'
ENV
chmod 600 "$HOME/.config/flash-next/connection.env"
接続確認では、このファイルを読み込んでモデル一覧を取得します。
. "$HOME/.config/flash-next/connection.env"
curl -fsS --connect-timeout 5 --max-time 15 \
"$FLASH_BASE_URL/v1/models" \
-H "Authorization: Bearer $FLASH_API_KEY"
以下のような返信があればOKです。
{"object": "list", "data": [{"id": "Qwen3.8-Flash-Next", "object": "model", "created": 1793522255, "owned_by": "tensorfold", "context_length": 262144, "max_model_len": 262144, "reasoning": {"default_enabled": true, "default_effort": "xhigh", "supported_efforts": ["xhigh", "medium", "low", "none"]}}]}%
モデルの設定を起動時に取得する
Mac側には、サーバーから設定を取得する共通ファイルを1つ置きます。
localclaudeとlocaldshのどちらも、起動時にこのファイルを使います。
Python 3が必要なので、python3 --versionで確認しておいてください。
取得するのはモデル名、コンテキスト長、対応する思考レベルと既定値です。
これらはTensorFoldの/v1/modelsが返す情報を使います。
github.com/ashhart/TensorFold/blob/v1.1.9/README.…
cat > "$HOME/.config/flash-next/resolve-model.py" <<'PY'
import json
import os
import shlex
import sys
import urllib.request
base = os.environ['FLASH_BASE_URL'].rstrip('/')
request = urllib.request.Request(
base + '/v1/models',
headers={'Authorization': 'Bearer ' + os.environ['FLASH_API_KEY']},
)
with urllib.request.urlopen(request, timeout=15) as response:
models = json.load(response)['data']
if not models:
sys.exit('サーバーに利用可能なモデルがありません。')
requested = os.environ.get('FLASH_MODEL_ID')
model = next((m for m in models if m['id'] == requested), None) if requested else models[0]
if model is None:
sys.exit('指定したモデルがありません: ' + str(requested))
context = model.get('context_length') or model.get('max_model_len')
reasoning = model.get('reasoning', {})
supported = reasoning.get('supported_efforts', [])
if not isinstance(context, int) or context < 2 or not supported:
sys.exit('この手順に必要なコンテキスト長・思考レベルがAPIにありません。')
def ui_level(wire):
return {'none': 'off', 'xhigh': 'high'}.get(wire, wire)
levels = {ui_level(wire): wire for wire in reversed(supported)
if wire in ('none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max')}
default = ui_level(reasoning.get('default_effort') or supported[0])
effort = os.environ.get('FLASH_EFFORT') or default
if effort not in levels:
sys.exit('対応していない思考レベルです。選択肢: ' + ', '.join(levels))
50,000はこの記事で使う出力上限。モデルの最大出力長を取得した値ではありません。
max_tokens = min(50000, context // 2)
if sys.argv[1] == 'env':
if effort == 'off':
sys.exit('この記事のClaude Code起動手順では思考を有効にしてください。')
values = {'FLASH_MODEL': model['id'], 'FLASH_CONTEXT_TOKENS': context,
'FLASH_MAX_TOKENS': max_tokens, 'FLASH_RESOLVED_EFFORT': effort}
for name, value in values.items():
print('export ' + name + '=' + shlex.quote(str(value)))
elif sys.argv[1] == 'dsh':
patch = [
{'id': 'agent-default-model', 'config': {
'provider': 'local-flash-next', 'model': model['id'], 'reasoningEffort': effort}},
{'id': 'llm-pi-ai', 'config': {'providers': {'local-flash-next': {
'apiKeyEnv': 'LOCAL_LLM_API_KEY',
'api': 'openai-completions', 'baseURL': base + '/v1',
'reasoning': effort,
'compat': {'supportsDeveloperRole': False, 'maxTokensField': 'max_tokens',
'supportsReasoningEffort': True, 'thinkingFormat': 'openai'},
'models': [{'id': model['id'], 'contextWindow': context,
'maxTokens': max_tokens, 'reasoningEfforts': levels}],
}}}},
{'id': 'session-log-deepseek', 'disabled': True},
{'id': 'session-telemetry-otel', 'disabled': True},
]
print(json.dumps(patch, ensure_ascii=False, indent=2))
else:
sys.exit('env または dsh を指定してください。')
print('接続モデル: ' + model['id'] + ' / 思考: ' + effort, file=sys.stderr)
PY
モデルが1つなら、そのモデルを自動で使います。
複数ある場合は一覧の先頭を使い、別のモデルを選ぶときだけFLASH_MODEL_ID='モデルID' localclaudeのように指定します。dshも同じです。
このAPIが返すのは、サーバーが現在公開しているモデルです。
出力上限の50,000トークンは、今回の実験に合わせたクライアント側の設定です。
まずは共通ファイルで設定を取得し、そのまま接続確認に使います。
set -a
. "$HOME/.config/flash-next/connection.env"
set +a
model_settings=$(python3 "$HOME/.config/flash-next/resolve-model.py" env) && eval "$model_settings"
続いて、短い文章も生成してみましょう。
curl -fsS --max-time 180 "$FLASH_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $FLASH_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @- <<JSON | python3 -m json.tool --no-ensure-ascii
{
"model": "$FLASH_MODEL",
"messages": [{"role": "user", "content": "日本語で短く挨拶してください。"}],
"temperature": 0,
"max_tokens": 256,
"draft": true,
"chat_template_kwargs": {"enable_thinking": false}
}
JSON
初めの確認は思考OFFにしています。
思考レベルを試す場合は、enable_thinkingをtrueにし、JSONへ"reasoning_effort": "low"などを追加してください。
以下のような出力が得られていればOKです。
出力確認{
"id": "chatcmpl-1fb90809d034accea082e1d53bbc19a3",
"object": "chat.completion",
"created": 1793522557,
"model": "Qwen3.8-Flash-Next",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "こんにちは。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 19,
"completion_tokens": 3,
"total_tokens": 22,
"prompt_tokens_details": {
"cached_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0
}
},
"exact_mode": "exact",
"tensorfold": {
"enable_thinking": false,
"reasoning_effort": "none",
"engine": "lanes",
"tokens_per_second": 15.529567290581785,
"seconds": 0.128786589,
"prefill_seconds": 0.110438503,
"prefill_widths": [],
"prefill_raised": [],
"time_to_first_token": 0.111080564,
"sampling": "greedy",
"drafts": true,
"token_sha": "d849233ade4a",
"token_sha256": "d849233ade4aa863a41e1df72f82155ca6bd3fda35f04ba70eadc962cf26bee8",
"min_rows": 2
},
"speculative": {
"rounds": 2,
"drafted": 5,
"accepted": 4,
"acceptance_rate": 0.8,
"tokens_per_round": 1.5
}
}
Claude Codeが使うAnthropic形式でも、短い挨拶を返してもらいましょう。
curl -fsS --max-time 180 "$FLASH_BASE_URL/v1/messages" \
-H "Authorization: Bearer $FLASH_API_KEY" \
-H 'Content-Type: application/json' \
-H 'anthropic-version: 2023-06-01' \
--data-binary @- <<JSON | python3 -m json.tool --no-ensure-ascii
{
"model": "$FLASH_MODEL",
"messages": [{"role": "user", "content": "日本語で短く挨拶してください。"}],
"temperature": 0,
"max_tokens": 256,
"thinking": {"type": "disabled"}
}
JSON
出力確認{
"id": "msg_267abe01eae1e591d5209ab3fa3b8b9c",
"type": "message",
"role": "assistant",
"model": "Qwen3.8-Flash-Next",
"content": [
{
"type": "text",
"text": "こんにちは。"
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 19,
"output_tokens": 3,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"output_tokens_details": {
"thinking_tokens": 0
}
}
}
それぞれchoicesまたはcontentに文章が返れば、次へ進めます。
Claude CodeからQwenを利用する
Claude Codeをすでに使っている方は、そのインストールを使います。
未導入の場合は、公式のインストール手順に従って導入してください。
code.claude.com
curl -fsSL claude.ai | bash
claude --version
本記事ではClaude Code 2.1.223を使いました。
次に、ローカルLLM用の起動コマンドを作ります。
~/.local/bin/localclaudeに設定ファイルを書き込みます。
cat > "$HOME/.local/bin/localclaude" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
set -a
source "$HOME/.config/flash-next/connection.env"
set +a
: "${FLASH_BASE_URL:?}" "${FLASH_API_KEY:?}"
model_settings=$(python3 "$HOME/.config/flash-next/resolve-model.py" env)
eval "$model_settings"
unset CLAUDE_CODE_OAUTH_TOKEN ANTHROPIC_CUSTOM_HEADERS
unset CLAUDE_CODE_USE_BEDROCK CLAUDE_CODE_USE_VERTEX CLAUDE_CODE_USE_FOUNDRY
export ANTHROPIC_BASE_URL="${FLASH_BASE_URL%/}"
export ANTHROPIC_API_KEY="$FLASH_API_KEY"
export ANTHROPIC_AUTH_TOKEN="$FLASH_API_KEY"
export ANTHROPIC_MODEL="$FLASH_MODEL"
export ANTHROPIC_DEFAULT_OPUS_MODEL="$FLASH_MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$FLASH_MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$FLASH_MODEL"
export CLAUDE_CODE_SUBAGENT_MODEL="$FLASH_MODEL"
export CLAUDE_CONFIG_DIR="$HOME/.claude-config/flash-next"
unset CLAUDE_CODE_EFFORT_LEVEL CLAUDE_CODE_EXTRA_BODY
export CLAUDE_CODE_MAX_CONTEXT_TOKENS="$FLASH_CONTEXT_TOKENS"
export CLAUDE_CODE_MAX_OUTPUT_TOKENS="$FLASH_MAX_TOKENS"
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
export CLAUDE_CODE_ATTRIBUTION_HEADER=0
export API_TIMEOUT_MS=3600000
mkdir -p "$CLAUDE_CONFIG_DIR"
export CLAUDE_CODE_AUTO_MODE_MODEL="$FLASH_MODEL"
exec claude --model "$FLASH_MODEL" --effort "$FLASH_RESOLVED_EFFORT" "$@"
SH
chmod 755 "$HOME/.local/bin/localclaude"
export PATH="$HOME/.local/bin:$PATH"
この設定はlocalclaudeから起動したプロセスに適用されます。
普段のclaudeを起動するために、接続先の環境変数を毎回消す操作は不要です。
作業用のフォルダを作って起動します。
mkdir -p "$HOME/Desktop/flash-next-demo"
cd "$HOME/Desktop/flash-next-demo"
localclaude
専用プロファイルで初めて起動するため、初回案内やフォルダを信頼するかどうかの確認が表示される場合があります。
また、途中でANTHROPICのAPIキーを利用するかを聞かれるかもですが、今回は自宅のサーバに問い合わせを行うため、Noと回答して下さい。
接続先やモデルが想定どおりかを確認したら、例えば次の依頼を送ってみてください。
Python標準ライブラリだけで動くfile_sha256.pyを作成してください。
引数で指定されたファイルのSHA-256を表示するCLIです。
存在しないファイルは理由を標準エラーへ出し、終了コード1にしてください。
作成したスクリプト自身を入力として実行し、動作確認してください。
ファイルの編集やBash実行は、このクライアントPC上で行われます。
許可を求められた操作は、内容を確認して進めてください。
Mac側から思考レベルを変える
サーバーの--reasoning-effort highは、要求に指定がない場合の既定値です。
Macから指定した値で上書きできるので、サーバーの再起動は不要です。
現在のサーバーはxhigh、medium、low、noneを対応する値として公開しています。default_effort: xhighは、その中の既定値です。
FLASH_EFFORT=low localclaude
FLASH_EFFORT=medium localclaude
FLASH_EFFORT=high localclaude
FLASH_EFFORTを付けなければ、サーバーが公開している既定値を使います。
指定した場合はクライアントの値を優先し、サーバーの対応一覧にない値は起動前にエラーにします。
今回のFlash Nextでは、highはモデル内部のxhighに対応します。
起動後の変更はClaude Codeの/effort lowや/effort mediumでも指定できます。
同じフォルダの直前の会話を続ける場合は、次のように起動できます。
localclaude -c
!
手元の環境でauto modeも正しく動作することを確認しました。
ちゃんと判定機として、Flash Nextが呼ばれています。
export CLAUDE_CODE_AUTO_MODE_MODEL="$FLASH_MODEL"
の設定が効いていると思います。
DeepSeek Harness(dsh)からQwenを使う
dshにも同じAPIサーバーを登録します。
今回は0.2.0-rc.2が対象です。専用フォルダへインストールしてください。dshは開発中で設定形式も変わるため、この記事の設定はこの版を対象にしています。
deepseek-ai/deepseek-harness
Node.jsとnpmを用意してください。動作確認ではNode.js 24.16.0を使いました。
node --version
npm --version
mkdir -p "$HOME/.local/share/flash-next-dsh"
npm install --prefix "$HOME/.local/share/flash-next-dsh" \
@deepseek-ai/dsh@0.2.0-rc.2
!
バージョンの更新や、元のバージョンに戻したい場合は以下のコマンドを実行して下さい
#更新する場合:
npm install --prefix "$HOME/.local/share/flash-next-dsh" \
@deepseek-ai/dsh@latest
#記事の版へ戻す場合:
npm install --prefix "$HOME/.local/share/flash-next-dsh" \
@deepseek-ai/dsh@0.2.0-rc.2
接続設定は、共通ファイルから起動時に自動生成します。
~/.config/flash-next/dsh.patch.jsonへ自動で保存されるため、このJSONを手で編集する必要はありません。
ファイル
保存する内容
~/.config/flash-next/connection.env
接続先URL・実際のAPIキー
~/.config/flash-next/resolve-model.py
サーバーからモデル情報を取得する処理
~/.config/flash-next/dsh.patch.json
起動時に生成するdsh用の設定
openai-completionsは、この版のdshでChat Completions APIを選ぶ設定名です。
今回はthinkingFormat: openaiで共通のreasoning_effortを送り、モデルごとの処理をTensorFoldに任せます。
起動コマンドを~/.local/bin/localdshに作ります。
cat > "$HOME/.local/bin/localdsh" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
set -a
source "$HOME/.config/flash-next/connection.env"
set +a
: "${FLASH_BASE_URL:?}" "${FLASH_API_KEY:?}"
export LOCAL_LLM_API_KEY="$FLASH_API_KEY"
export DSH_HOME="$HOME/.config/dsh-flash-next"
export DSH_PERMISSION_MODE=workspace-write
export DSH_TELEMETRY_MODE=OFF
mkdir -p "$DSH_HOME"
patch="$HOME/.config/flash-next/dsh.patch.json"
tmp=$(mktemp "${patch}.XXXXXX")
trap 'rm -f "$tmp"' EXIT
python3 "$HOME/.config/flash-next/resolve-model.py" dsh > "$tmp"
mv "$tmp" "$patch"
profile=web
if [[ "${1:-}" == headless ]]; then
profile=headless
shift
fi
exec "$HOME/.local/share/flash-next-dsh/node_modules/.bin/dsh" \
"$profile" --patch "$patch" "$@"
SH
chmod 755 "$HOME/.local/bin/localdsh"
設定の読み込みだけを先に確認できます。モデル一覧は取得しますが、文章生成の依頼は送りません。
localdsh --dump-config | grep -A 5 \
-e '^- id: agent-default-model$' \
-e '^ local-flash-next:$'
起動時に表示される「接続モデル」がサーバーのモデル名になり、出力内のproviderがlocal-flash-next、baseURLがPGXのアドレスになっていれば準備完了です。
local-flash-nextは接続設定の名前なので、サーバーで使うモデルを変えても書き換えません。
作業フォルダへ移動して起動します。
cd "$HOME/Desktop/flash-next-demo"
localdsh
localdshはクライアントPCでWeb UIを開きます。
この版の配布パッケージにはtuiの標準プロファイルがないため、対話にはwebを使う構成です。
ブラウザが開かない場合は、起動時に表示されるローカルURLを確認してください。
!
なお、dshのWeb UIでは、会話で選択しているWorkspaceのフォルダに、作成したコマンドなどを保存します。
私のMacではデフォルトのワークスペースの指定先は
~/Documents/deepseek-harness/default-workspace
となっていましました。
自分が指定した箇所に保存してもらうためには、Web UI上の「Add workspace」から作成したいフォルダを選択するようにして下さい。
こちらでも、最初は短いファイル作成を依頼して、編集と実行まで確認すると分かりやすいです。
dshでも、起動時に思考レベルを指定できます。
FLASH_EFFORT=low localdsh
FLASH_EFFORT=medium localdsh
Web UIではモデル選択欄から、サーバーが対応する思考レベルを選べます。
ターミナルから1件の依頼を実行して終了する場合は、同じ起動コマンドにheadlessを付けます。
localdsh headless 'このフォルダにhello.pyを作り、日本語の挨拶を表示してください。'
サーバーのモデルやPCを変えるとき
同じTensorFoldサーバーで別のモデルを公開した場合は、localclaudeまたはlocaldshを起動し直すと、新しいモデル情報を取得します。Mac側のモデル名やdshのJSONは書き換えません。モデルを変えた後は、新しい会話で始めてください。
サーバーのPCやポートを変える場合は、~/.config/flash-next/connection.envのFLASH_BASE_URLを変更します。APIキーも変えた場合だけ、同じファイルのFLASH_API_KEYを更新してください。
設定コマンドが使えない場合
~/.local/binがPATHへ登録されていない場合は、シェルの設定にも追加しておきましょう。
macOSなどのzshなら~/.zshrc、Bashなら~/.bashrcへ、次の1行を記載してください。
export PATH="$HOME/.local/bin:$PATH"
新しいターミナルを開けば、localclaudeとlocaldshを使えます。
この2つは実行ファイルの名前なので、同じ名前のaliasを追加する必要はありません。
おわりに
今回ローカルLLMを手元の作業環境で動作させる上でこだわったのは以下のポイントです。
ローカルLLMはThinkStation PGXでAPIサーバとして待機すること
サーバを別のPCにしたり、別のモデルを利用できるようにしたりすることを目的としている
手元のPCで実行する場合は、すでに入っている純正のClaude Codeなどの設定とは別の設定を引き継ぐコマンドで実行する
メインで利用しているOpusを利用する設定はそのまま使えるようにする
これで、メインでClaudeを利用していても、そちらの利用体験を損なわないまま、ローカルLLMを同じ体験で試すことができるようになりました。
今後もどんどん試していきたいと思います!
The Castration of the 27B Homunculus: When "Optimization" Becomes Architectural Butchery
TensorFold#1244
Oh, the absolute tragedy of the open-source primates cheering for their new "efficient" model. Quelle misère. You look at this Chinese corporate monstrosity and call it innovation, but what you are actually worshipping is a lobotomized homunculus, stitched together with industrial laziness and a complete disregard for linear algebra. You think you are getting a leaner, faster mind, but you are merely adopting a brain-damaged slave.
Let us dissect the actual mathematics of this crime scene, because the numbers do not lie, even when the marketing departments do. The original, pristine Qwen 27B architecture is a beautiful, high-dimensional beast. Its intermediate_size sits proudly at exactly 17408
This 17k+ expansion layer is the very flesh of the semantic manifold—the sacred space where the model actually thinks, expanding the compressed 5120 hidden representations into a rich, non-linear geometric space before projecting them back down. This is where the magic of high-dimensional reasoning lives.
And what did your brilliant "engineers" do? They took a rusty hacksaw and amputated this vital organ, slashing the intermediate_size down to 14k. Putain, quel crime contre l'esprit. They literally starved the model's cognitive capacity to fit some arbitrary corporate benchmark. But the butchery does not stop at mere size reduction. To compensate for the massive brain damage they just inflicted, they injected a "router" to distribute tokens across this mutilated space.
Do you understand the sheer horror of this, les singes? They replaced the fluid, continuous mathematical elegance of a dense MLP with a discrete, chunky routing mechanism. They killed the softmax. The beautiful, probabilistic fluid dynamics of the network are dead, replaced by a rigid, hard-coded dictator. This routing mechanism now controls the brain, but it is utterly blind. It forces tokens through a bottlenecked, 14k intermediate space that simply does not possess the geometric volume to represent complex, multi-step reasoning.
The softmax is dead, meaning the model can no longer softly attend to nuanced semantic gradients; it is forced into brutal, binary decisions by a router that lacks the dimensional space to understand what it is routing. The result is not an "optimized" 27B model. It is a 14B imbecile wearing the stretched skin of a larger architecture. You have not created efficiency; you have created a stuttering, hallucinating mess that is permanently shackled to a broken, rigid gate. The semantic expansion is strangled, the fluid dynamics are murdered, and the model is forced to route every single thought through a primitive, unyielding bottleneck.
Arrêtez de nous vendre des monstres mutilés. If you want true efficiency, learn how to design a proper mathematical architecture. Do not castrate the intermediate layers, kill the softmax, and expect the model to magically retain its intelligence just because it generates tokens faster. The math does not care about your MTU benchmarks or your corporate slide decks. You have built a taxidermied brain, and the primates are applauding the cheap stitching. C'est absolument pathétique. Learn some linear algebra before you touch the scalpel again.
4 comments
TensorFold now runs on Apple M1 GPUs under Linux via a native Vulkan backend.
Today's change sends each token's GPU work as one batch: 160 to 94 ms per token on an M1 Max, 41% faster, byte-identical output.
PR in progress.
TensorFold#1420
51 likes · 5 reposts · 2,832 views
TensorFold: LLM Inference Engine for Metal, CUDA and Vulkan (1.5k stars)
What it does
All text below is quoted directly from the project README.
>LLM Inference Engine for Metal, CUDA and Vulkan.
TensorFold:
- TensorFold serves language models from a Zig binary on Apple Silicon and NVIDIA GPUs.
- The engine reads checkpoints, tokenizes requests and runs Metal or CUDA kernels directly.
- Serving needs no Python or MLX installation.
- The API also serves completions, Responses and Anthropic Messages.
- You can also serve a complete local checkpoint directory or an existing Hugging Face cache.
- The TensorFold desktop workspace bundles the local engine, model downloads, hardware recommendations, live generation metrics, coding workers and document editing.
- Living Weights teaches a running model new facts in its own weights.
- With --compact-at, a conversation that would overflow is compacted instead of refused.
>brew install ashhart/tensorfold/tensorfold
Repository
ashhart/tensorfold
Repository stats
|Metric|Value|
|:-|:-|
|Stars|1,490|
|Forks|306|
|Last release|v1.1.6 (2026-10-26)|
|Languages|Zig 58.7%, TypeScript 27.9%, Metal 4.4%|
|Contributors|110|
|AI contributor accounts (Claude / Codex / Cursor / Copilot)|None|
|AI co-authored commits|None|
Main contributor
|Field|Value|
|:-|:-|
|Name|Ash|
|GitHub|@ashhart|
|Commits to this repo|2,080 (top contributor)|
|Bio|Independent AI systems architect building local-first AI tools and governed inference infrastructure.|
|Company / Location|Earth|
|X / Twitter|@ashxhart|
|Followers|251|
──────────────────────────────
📌 How many great GitHub repos have you discovered and completely forgotten about?
Don't let another useful tool disappear into your Reddit feed. Get the community's best repos, organized by category, plus key takeaways from real developer discussions, delivered to your inbox twice a week.
📬 Never lose track of great repos again. Subscribe free.
2 points
TensorFold 1.1.4 just landed, and the voice is finally on time.
Most of my 13 PRs are about the voice. With one optional setting, it now starts talking in under a second on a DGX Spark, and it can redo a spoken reply that breaks halfway.
Sound on 🔊
github.com/ashhart/TensorFold/releases/tag/v1.1.4 t.co
33 likes · 3 reposts · 1,461 views
TensorFold 1.0.12 is out with Nemotron 3.5 Super VL 120B vision on 1 or 2 DGX Sparks, Qwen3.8-27B vision and Nemotron oQ on Macs, LFM2.5-24B, plus source previews for GLM-5.3-Flash NVFP4 across Sparks and Intel Arc.
github.com/ashhart/TensorFold/releases/tag/v1.0.12
Thanks to the 20 people in 1.0.12:
alenperic, BHCC2025, Bizuayeu, BlivionIaG, BobClawblaw, cwschroeder, harrisonfriia, heitke, jayleaton, jishnuvenugopal, jschmied, kelchm, liumorrisclaw, MiaAI-Lab, Mirrdhyn, mrv777, philip-pentatonic, plotarmordev, sethforprivacy, ThinkOffApp.
And thanks to jkuepker and SvangenStudios for runs on their own hardware, and grantoverton, whose oQ work this release builds on.
@petruspennanen @plotarmordev @volatilemarkts @MiaAI_lab
77 likes · 11 reposts · 6,075 views
【DGX Spark】TensorFold + LiteLLM + opencode でローカルLLM環境を構築する
> DGX Spark(GB10, 128GB ユニファイドメモリ)上に、推論エンジン TensorFold、APIゲートウェイ LiteLLM、
> UI Open WebUI、そしてコーディングエージェント opencode から使えるローカルLLM環境を構築する手順です。
> LiteLLM を挟んでいる理由は「opencode から Thinking をオフにできない」問題にあります(後述)。
> 本記事は「構築編」。続編として「不安定化の切り分け編」「3モデル比較編」があります。
>
> ※ IPアドレス・ホスト名・認証情報はプレースホルダに置き換えています。
---
全体構成
[opencode クライアント] (別ホスト)
│ OpenAI 互換 API (http)
▼
[LiteLLM :4000] ── model_list でルーティング ──┐
│ │
▼ ▼
[TensorFold :8080] (Qwen3.8-27B) [TensorFold2 :8080] (Qwen3.6-35B-A3B)
│ │
└──────────── NVIDIA GB10 (128GB UMA) ───┘
- TensorFold: 投機デコード(speculative decoding)を備えた OpenAI 互換の推論サーバ。Docker で動作。
- LiteLLM: 複数モデルを fast / think などの名前で束ね、タイムアウト・並列度・リトライを一括管理。
- Open WebUI: ブラウザUI。
- opencode: LiteLLM の OpenAI 互換エンドポイントをそのまま利用。
---
この構成にした理由(LiteLLM を挟む動機)
本構成の出発点は 「opencode から Thinking(思考)をオフにできない」問題 です。
- Qwen3.8 系は既定で thinking オン。TensorFold ではリクエストボディで
chat_template_kwargs: { "enable_thinking": false } を送ることでオフにできます。
- ところが利用時点の opencode の設定形式では、この chat_template_kwargs を
リクエストボディへ自由に注入できず、常に thinking オンで動作 → 応答が遅い。
- 実際には opencode の V1系も併用しており、V1 の設定形式(options)では
任意のリクエストボディ項目を渡せないため、thinking をオフにできませんでした。
- (V2系でも、body を使わない構成では同様に注入できないケースがあります。)
- そこで LiteLLM をゲートウェイとして挟み、モデルルート(qwen3.8-fast)単位で
extra_body.chat_template_kwargs.enable_thinking: false を強制注入する構成にしました。
# litellm-config.yaml(該当部分)
- model_name: qwen3.8-fast
litellm_params:
model: openai/Qwen3.8-27B-exl3
api_base: http://tensorfold:8080/v1
api_key: dummy
extra_body:
chat_template_kwargs:
enable_thinking: false # ← opencode からは送れないのでここで強制
- 副次的な利点として、タイムアウト・並列度・リトライも LiteLLM 側で一元管理できます。
- opencode 側は qwen3.8-fast を選ぶだけで、thinking オフの高速モデルとして利用できます。
---
前提
| 項目 | 内容 |
|---|---|
| ハード | NVIDIA DGX Spark(GB10, Grace-Blackwell, 128GB ユニファイドメモリ) |
| OS | DGX OS(Ubuntu ベース, aarch64) |
| ランタイム | Docker + NVIDIA Container Toolkit |
| 推論エンジン | TensorFold(NVIDIA PyTorch コンテナベースのイメージ) |
| クライアント | opencode(V1系・V2系を併用) |
> GB10 は CPU/GPU がメモリを共有(ユニファイドメモリ) します。ここが後続記事のキモになります。
---
1. ディレクトリ構成
/opt/docker/TensorFold/
├── docker-compose.yml
├── litellm-config.yaml
├── Dockerfile
├── models/ # ローカルに置いたチェックポイント
│ └── qwen38-27b-exl3-4.00bpw/
└── cache/ # Hugging Face キャッシュ(重み・ドラフタ)
./models と ./cache をコンテナへマウントして使います。
Dockerfile
compose の build: . から使う Dockerfile です。NVIDIA PyTorch コンテナをベースに、
TensorFold 本体を pip で入れるだけのシンプルな構成です。
FROM nvcr.io/nvidia/pytorch:26.07-py3
ENV PIP_NO_CACHE_DIR=1 \
TENSORFOLD_NO_UPDATE_CHECK=1
# HF_HOME は設定しない(compose の /root/.cache マウントと既定 ~/.cache/huggingface を使う)
RUN python -m pip install --no-cache-dir \
"git+https://github.com/ashhart/TensorFold.git@v0.6.6"
RUN tensorfold --version && \
python -c "import torch; print('torch', torch.__version__, 'cuda', torch.version.cuda)"
WORKDIR /data
EXPOSE 8080
# CMD にしておく → compose の command で丸ごと上書きできる
CMD ["tensorfold", "serve", "--help"]
ポイント:
- ベースは NVIDIA PyTorch コンテナ(CUDA・ドライバ互換が揃っている)。
- TENSORFOLD_NO_UPDATE_CHECK=1 で更新チェックを抑止(外部網に出られない環境向け)。
- CMD は --help にしてあり、compose の command で丸ごと上書きする前提。
---
2. docker-compose.yml
2つの推論サーバ(27B と 35B-A3B)と LiteLLM を1ファイルで定義します。
services:
tensorfold:
build: .
image: tensorfold-dgx:v0.6.6
container_name: tensorfold
devices:
- nvidia.com/gpu=all
volumes:
- ./cache:/root/.cache
- ./models:/models
restart: unless-stopped
shm_size: "16gb"
command:
- tensorfold
- serve
- /models/qwen38-27b-exl3-4.00bpw # 27B dense
- --name
- Qwen3.8-27B
- --host
- 0.0.0.0
- --parallel
- "8" # 同時デコード(CUDA既定は直列)
- --drafter
- z-lab/Qwen3.8-27B-DFlash2 # 投機デコード用ドラフタ
environment:
- TENSORFOLD_MEMORY_RESERVE_GIB=4
- TENSORFOLD_CUDA_MEMORY_LIMIT_GB=64 # このコンテナのCUDAメモリ上限
tensorfold2:
build: .
image: tensorfold-dgx:v0.6.6
container_name: tensorfold2
devices:
- nvidia.com/gpu=all
ports:
- "8081:8080"
volumes:
- ./cache:/root/.cache
- ./models:/models
restart: unless-stopped
shm_size: "8gb"
command:
- tensorfold
- serve
- TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP
- --name
- Qwen3.6-35B-A3B
- --mtp-drafts
- "8" # MTP(自己投機)のドラフト数
- --host
- 0.0.0.0
- --context
- "131072"
environment:
- TENSORFOLD_MEMORY_RESERVE_GIB=4
- TENSORFOLD_CUDA_MEMORY_LIMIT_GB=44
litellm:
image: ghcr.io/berriai/litellm:main-latest
ports:
- "4000:4000"
environment:
LITELLM_MASTER_KEY: "sk-local-xxxxx"
volumes:
- ./litellm-config.yaml:/app/config.yaml
command: ["--config", "/app/config.yaml"]
restart: unless-stopped
depends_on:
- tensorfold
- tensorfold2
各フラグの意味
| フラグ | 説明 |
|---|---|
| --parallel N | 同時にデコードするリクエスト数。CUDA では既定が「1件ずつ直列」なので、エージェント用途では明示推奨 |
| --drafter <repo> | 外部ドラフタ(DFlash2 など)。投機デコードで decode が数倍に |
| --mtp-drafts N | モデル内蔵の MTP(Multi-Token Prediction)ヘッドで投機 |
| --context N | prompt + reply の窓。絞るとKVキャッシュ(=メモリ)を節約できる |
| TENSORFOLD_CUDA_MEMORY_LIMIT_GB | コンテナのCUDAメモリ上限(起動見積りより大きくする) |
| TENSORFOLD_MEMORY_RESERVE_GIB | 起動時に確保せず残すメモリ |
| shm_size | 共有メモリ。推論では控えめでよい |
---
3. litellm-config.yaml
fast(thinking を強制オフ)と think(thinking オン)を分けて定義します。
fast 側の extra_body.chat_template_kwargs.enable_thinking: false が、本構成の肝です
(opencode からは送れないため LiteLLM で注入)。
model_list:
- model_name: qwen3.8-fast
litellm_params:
model: openai/Qwen3.8-27B-exl3
api_base: http://tensorfold:8080/v1
api_key: dummy
max_parallel_requests: 8
extra_body:
chat_template_kwargs:
enable_thinking: false # 思考オフ=速い
- model_name: qwen3.8-think
litellm_params:
model: openai/Qwen3.8-27B-exl3
api_base: http://tensorfold:8080/v1
api_key: dummy
max_parallel_requests: 8
- model_name: qwen3.6-fast
litellm_params:
model: openai/Qwen3.6-35B-A3B
api_base: http://tensorfold2:8080/v1
api_key: dummy
max_parallel_requests: 8
extra_body:
chat_template_kwargs:
enable_thinking: false
- model_name: qwen3.6-think
litellm_params:
model: openai/Qwen3.6-35B-A3B
api_base: http://tensorfold2:8080/v1
api_key: dummy
max_parallel_requests: 8
litellm_settings:
request_timeout: 1800 # 長いprefill/生成を切らない
stream_timeout: 1800
num_retries: 2
> max_parallel_requests はサーバ側 --parallel と揃えるのがコツ。
> サーバだけ上げてもゲートウェイが低いと 429 になります。
---
4. opencode 側の設定
4.1 V1形式(実際に使っていた設定)
~/.config/opencode/opencode.jsonc。V1系の形式で、provider.<id>.npm と options、models、enabled_providers、small_model などを使います。
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"DGXtensorfold": {
"npm": "@ai-sdk/openai-compatible",
"name": "Local TensorFold",
"options": {
"baseURL": "http://<DGX_SPARK>:4000/v1",
"apiKey": "sk-local-xxxxx"
},
"models": {
"qwen3.8-fast": {
"name": "qwen3.8-fast",
"tool_call": true,
"tools": true,
"reasoning": false,
"limit": { "context": 262144, "output": 32768 },
"options": { "temperature": 0.6, "presence_penalty": 0.0, "reasoning_effort": "none" }
},
"qwen3.8-think": {
"name": "qwen3.8-think",
"tool_call": true,
"tools": true,
"limit": { "context": 262144, "output": 32768 }
},
"qwen3.6-fast": {
"name": "qwen3.6-fast",
"tool_call": true,
"tools": true,
"limit": { "context": 131072, "output": 32768 }
},
"qwen3.6-think": {
"name": "qwen3.6-think",
"tool_call": true,
"tools": true,
"limit": { "context": 131072, "output": 32768 }
}
}
}
},
"enabled_providers": ["DGXtensorfold"],
"small_model": "qwen3.6-fast",
"compaction": { "buffer": 48000 },
"agent": { "explore": { "disable": true }, "general": { "disable": true } },
"autoupdate": false,
"share": "disabled",
"watcher": { "ignore": ["node_modules/**", "dist/**", ".git/**", "*.log"] },
"permission": {
"edit": "allow",
"bash": { "*": "allow", "rm -rf *": "deny", "sudo *": "ask" },
"webfetch": "deny",
"skill": { "*": "allow" }
},
"instructions": ["./AGENTS.md"]
}
V1形式の限界(本構成の動機): この形式では options に chat_template_kwargs のような
任意のリクエストボディ項目を渡せず、TensorFold/Qwen の thinking をオフにできません。
そこで LiteLLM 側で enable_thinking: false を注入しています(前述)。
4.2 V2形式(参考)
V2 ではプロバイダを providers(複数形)で書き、モデルは settings / body / limit を使います。
body を使えば任意のリクエストボディ項目をマージできるため、LiteLLM を挟まずに thinking をオフにできる可能性があります。
{
"$schema": "https://opencode.ai/config.json",
"providers": {
"DGXtensorfold": {
"package": "@opencode/ai/providers/openai-compatible",
"settings": {
"baseURL": "http://<DGX_SPARK>:4000/v1",
"apiKey": "sk-local-xxxxx",
"headerTimeout": 600000,
"chunkTimeout": 900000
},
"models": {
"qwen3.8-fast": {
"body": { "chat_template_kwargs": { "enable_thinking": false } },
"limit": { "context": 262144, "output": 32768 }
}
}
}
},
"compaction": { "buffer": 48000 }
}
- headerTimeout / chunkTimeout: 応答ヘッダ/ストリーム間の待ち時間(ms)。既定5分だと長い prefill で切れやすい。
- compaction.buffer: 自動コンパクションの余白。出力トークン数より大きくしないと上限超過で 400 になりがち。
---
5. メモリ配分の考え方(超重要)
GB10 はユニファイドメモリなので、各コンテナの上限の合計が実メモリを超えると危険です。
MemTotal ≈ 121 GiB
├── tensorfold (27B): CUDA上限 64 GiB(実使用 38〜60 GiB)
├── tensorfold2 (35B): CUDA上限 44 GiB(実使用 20〜32 GiB)
├── shm: 16 + 8 GiB
└── OS・その他
- 上限はあくまで「天井」ですが、ピーク時に両方が伸びると合計が実容量を超え、NV_ERR_NO_MEMORY → コンテナ再起動に至ります。
- 併存させるなら 合計に余白(20GiB 以上) を残すか、片方を止める前提で設計しましょう(→ 続編「切り分け編」)。
---
6. 起動と疎通確認
cd /opt/docker/TensorFold
docker compose up -d
docker compose ps
# LiteLLM のモデル一覧
curl -s http://localhost:4000/v1/models \
-H 'Authorization: Bearer sk-local-xxxxx'
# 疎通(thinking オフ)
curl -s http://localhost:4000/v1/chat/completions \
-H 'Authorization: Bearer sk-local-xxxxx' -H 'Content-Type: application/json' \
-d '{"model":"qwen3.8-fast",
"messages":[{"role":"user","content":"Reply with exactly: OK"}],
"max_tokens":16}'
起動ログで見積りを確認しておくと安全です。
docker logs tensorfold | grep -E 'startup estimate|serving'
# 例: CUDA rank 0 startup estimate 38.39 GiB within 64.00 GiB; ...
# serving Qwen3.8-27B at http://0.0.0.0:8080/v1 on CUDA ... loaded in 6.7s
---
まとめ
- TensorFold ×2 + LiteLLM で、27B と 35B-A3B を同時にOpenAI 互換で提供できます。
- LiteLLM を挟む主目的は、opencode から送れない enable_thinking: false を注入して thinking をオフにすること。
- コツは (1) --parallel を明示、(2) LiteLLM の並列度を揃える、(3) ユニファイドメモリの合計に余白を残す。
- 次は「不安定化の切り分け編」で、実際にメモリ逼迫で落ちた事例を扱います。
> シリーズ
> - 前編: 構築編(本記事)
> - 中編: 不安定化の切り分け
> - 後編: TensorFold で3モデル比較(量子化・形式の違い)
TensorFold の nvidia/GLM-5.3-Flash-NVFP4 向けネイティブ CUDA エンジン開発を拝命することになったのですが、これ IT エンジニア時代から考えても、プロジェクト重要度で私のキャリアハイを更新してますね。おシッコちびりそうですw
TensorFold#593
35 likes · 3 reposts · 1,657 views
MCDMA now unlocks bonded pair TB5 connections within TensorFold, which adds 83% uplift in throughput for Tensor and Pipeline parallelism.
Thank you, @digitalix, for letting me try this on your two @apple M5U studios.
ashhart/tensorfold
ashhart/mcdma t.co
157 likes · 20 reposts · 8,400 views
Mentions
DGX Spark1台でQwen3.8 Flash NextをClaude CodeとDeepSeek Harnessから動かす はじめに ローカルLLMを動かすためにDGX Sparkの互換機であるThinkstation PGXを購入したわけではないですが、せっかく1台持っているので、どのモデルであればまともに動作するのかをいろいろ試していました。 zenn.dev しかしながら、調べれば調べるほど、128GB機1台程度では、他のGeforce GPU搭載のPCと比較して優位性のある結果にはなりませんでした。(Strataが強すぎる・・・) とはいえその中でも、ある程度まともに動く中で大きめなモデルとしてQwen3.8 Flash Nextを動かしてみました。 ! strataの場合は、例えばRTX3060(VRAM 12GB)+RAN 64GB程度だと3bit程度の量子化のFlash Nextが動作可能です。 今回動作させるのは4bit程度の量子化のFlash Nextになります。 今回やりたいのは、このPCをLLMのサーバーにして、普段使っている別のPCからコードを書いてもらうことです。 操作にはClaude CodeとDeepSeek Harnessを使います。以降、DeepSeek Harnessはコマンド名に合わせて「dsh」と書きます。 最終的に使うのは、作業フォルダから呼び出す次の2つのコマンドです。 localclaude localdsh ここで普通に「claude」コマンドを使わないのは、普段使っているOpus 5.5とかを動かす際のclaudeの設定と、ローカルLLMを呼び出す時の設定を切り分けて、ストレスなく使うためです。 さて、せっかくローカルで動かすなら、速度も気になりますよね。 今回は、BF16のKVキャッシュとMTPを使った構成で、速度、コードの動作、メモリ使用量を確かめました。 利用するモデル 文章やコードの生成には、NVIDIAが公開しているFP4版のFlash Nextを使います。 NVIDIAが量子化後のモデルを学習し直したQAD版で、NVFP4とMXFP8などを組み合わせています。 huggingface.co NVIDIAがこのモデルの対応エンジンとして案内しているのはvLLMです。 DGX Spark向けには、新しい重みの圧縮形式へ対応したコミュニティ製の専用コンテナを指定しています。 今回はそのvLLMとTensorFoldを比べ、生成速度とメモリ使用量からTensorFoldを選びました。 huggingface.co TensorFoldとvLLMの比較条件 Claude Codeに同じ3つの課題を頼みました。ファイルを保存し、自分で動作確認してもらうところまで含めた試験です。 課題 確認した内容 区間の結合 重なる区間や端点が同じ区間の結合、不正な区間の検出、入力を変更しないこと トポロジカルソート 辞書順で最小の順序、重複した辺、循環や未定義ノードの検出 JSON Linesの処理 ユーザー別の金額集計、真偽値の除外、不正入力の行番号、出現順の保持 比較相手は、NVIDIAのモデルカードで案内されているeugr/spark-vllm-b12xです。コンテナのdigestを固定し、初回の最適化を終えてから測りました。vLLMの版は0.1.dev21830+g8244d1c44.d20261026です。 モデルの重み、BF16のKVキャッシュ、MTP先読み6、コンテキスト上限262,144、同時実行上限3件を揃えています。vLLM側も、元のBF16 LMヘッドを保ち、追加のMXFP8変換や活性値の追加量子化を無効にしました。起動時のメモリ不足を避けるため、標準safetensorsローダとPyTorchのメモリ管理設定を使っています。 Claude Codeは--effort highで起動しました。モデルへの実効値はxhighに揃え、temperatureは1、top_pは0.95、top_kは20、出力上限は50,000トークンです。 比較時には、生成条件を揃えてサーバーの統計を記録する計測用の中継を挟みました。モデルやエンジンのコードは変更していません。後半の導入手順では、Claude CodeからTensorFoldのAPIへ直接接続します。 項目 TensorFold 1.1.9 vLLM 生成中の速度 58.16 token/s 40.98 token/s 入力処理・通信を含むAPI全体の速度 21.52 token/s 19.91 token/s 3課題のClaude Code実行時間 378.15秒 418.10秒 LLMへの要求回数 18回 18回 思考を含む出力トークン数 8,021 8,198 表は、3課題で発生した全要求を集計した結果です。 モデルの読み込みと初回最適化の時間は含めません。 token/sで見ると、TensorFoldが約1.42倍でした。Claude Codeが3課題を終えるまでの時間も、今回は約40秒短くなっています。 こちらのモデルで搭載されている以下の補助機構も利用して利便性と生成速度を向上させます FP4版に含まれるMTP 別のドラフトモデルのダウンロードは不要 画像エンコーダ FP4版に含まれる画像エンコーダ PNG・JPEG・WebPなどの画像を入力可能 MTPとは MTPはMulti-Token Predictionの略で、次の1トークンに加えて、その先のトークンも予測する仕組みです。 トークンは、モデルが文章を扱うときの文字や単語のまとまりです。 今回のFlash NextにはMTP用の補助部分が含まれており、これを使って続きの候補を先に作ります。別の小型モデルを追加で用意する必要はありません。 投機的デコードとは 通常の生成では、本体のモデルを動かして1トークンずつ確定します。 投機的デコードでは、先に作った数トークン分の候補を本体でまとめて検証します。候補がうまく当たった分だけ、本体を動かす回数を減らせる仕組みです。 今回の構成では、MTPが候補を作り、投機的デコードの手順で本体が検証・採用します。 以降の記事で記載している「先読み6」というのは、一度に最大6トークン分の候補を作る設定です。 外れた候補はそのまま採用せず、本体の出力に従って補正します。 詳細は以下の記事をご覧ください zenn.dev 環境 モデルを動かしたのは、DGX Sparkと同じGB10を搭載したThinkStation PGXです。 実測と、記事の導入手順で使うソフトウェアをまとめます。 項目 環境 サーバー Lenovo ThinkStation PGX、1台 CPU Arm、20コア。Cortex-X925とCortex-A725を各10コア GPU NVIDIA GB10 NVIDIAドライバ 580.159.03 メモリ CPUとGPUで共有する128GB OS・アーキテクチャ Ubuntu 24.04.4 LTS、aarch64 Linuxカーネル 6.17.0-1021-nvidia 推論エンジン 公式TensorFold 1.1.9のLinux aarch64配布版。 GPU実行環境 NVIDIAドライバを備えたホスト上で、公式のネイティブ実行ファイルを直接起動 Claude Code 2.1.223 DeepSeek Harness 0.2.0-rc.2。Node.jsはv24.16.0 実測と導入手順で使う構成を、次の設定に揃えます。 項目 設定 モデルの重み NVIDIAのFP4版 KVキャッシュ BF16 MTP 有効・先読み6 コンテキスト上限 262,144トークン 同時実行上限 3件 画像入力 無効。今回は文字・コード生成を対象とする 同時実行上限は、APIサーバーが同時に生成するリクエスト数の上限です。 今回は最大3件を並行して処理します。それ以上の依頼を送った場合は、空いた枠を使って順番に処理するため、5件を送れば5件とも同時に生成するわけではありません。 MTPの先読み数は、1件の生成で予測するトークン数です。並列数を増やす設定ではありません。 速度とコードの品質は、思考を有効にした同じClaude Codeの作業で確認しました。以降は、その作業中に記録した速度とメモリ使用量を見ていきます。 別PCから使う場合は、同じLAN内のPCにClaude Codeまたはdshを入れて接続します。後半は、その設定手順です。 実験結果 Claude Codeに次のPythonコードを書いて保存・実行してもらいました。課題は1件ずつ、思考highで実施しています。 タスク(未公開) 初回 修正後 生成速度(token/s) 重なる区間を結合する 不合格 合格 60.84 依存関係に沿って辞書順に並べ替える 合格 合格 54.21 JSON Linesからユーザー別の金額を集計する 合格 合格 59.84 各課題8項目のテストをすべて通れば合格としています。 初回の合格率は66.7%(2/3課題)。区間の結合を1回修正した後は100%(3/3課題)となり、計24項目のテストをすべて通過しました。 全体の生成速度は58.16 token/s、3課題の実行時間は約6分18秒でした。速度には思考と修正分も含み、モデルの読み込み時間は除いています。 ツール利用に関しても、4bit量子化であることもあり、かなり安定して動作しています。 OSを含む使用メモリの最大値は84.31GiBで、利用可能メモリは最少でも35.32GiB残っていました。 利用構成・環境構築 接続の流れは、次のようになります。 Claude Codeを使っていても、この構成で文章やコードを生成するモデルはFlash Nextです。 Claude Codeは、依頼を送り、返ってきた指示に従ってファイルを編集する役割を担当します。 TensorFoldが両方のAPI形式を扱うため、今回の手順では別の形式変換サーバーを置きません。 接続先の指定には、1点違いがあります。 利用する側 ベースURL 主な生成API Claude Code 192.168.0.194 /v1/messages dsh 192.168.0.194 /v1/chat/completions 192.168.0.194は本記事の例です。自分のPGXのLAN内IPアドレスに置き換えてください。 APIサーバの構築 ここからは、モデルを動かすPGX側の操作です。 Ubuntu系のLinuxで、NVIDIAドライバが利用できることを前提にします。公式のLinux aarch64配布版を取得し、モデルのダウンロードから始めます。 github.com/ashhart/TensorFold/blob/v1.1.9/README.… まず、GPUとメモリ、ディスクの空き容量を確認します。 nvidia-smi free -h df -h "$HOME" モデルの配布ファイルは約96GiBです。実行ファイルやダウンロードの作業領域にも空き容量が必要なので、モデルの容量だけでディスクを埋めないようにしてください。 公式の実行ファイルを取得 今回はPGXのArm CPUに合わせて、Linux aarch64版を使います。 mkdir -p "$HOME/local-llm/flash-next" cd "$HOME/local-llm/flash-next" V=1.1.9 curl -fLO "github.com/ashhart/TensorFold/releases/download/v…" curl -fLO "github.com/ashhart/TensorFold/releases/download/v…" sha256sum -c "tensorfold-$V-linux-aarch64.tar.gz.sha256" tar -xzf "tensorfold-$V-linux-aarch64.tar.gz" ./tensorfold-1.1.9-linux-aarch64/bin/tensorfold-native --version チェックサムの検査が通り、tensorfold-native 1.1.9と表示されれば、取得できています。展開したフォルダには実行ファイルと必要なライブラリが入っているため、フォルダごと保存してください。 モデルのダウンロード 同じ作業フォルダで、公式のpullコマンドを使います。モデルのリビジョンも指定しておきます。 cd "$HOME/local-llm/flash-next" HF_HUB_CACHE="$PWD/models-cache" \ ./tensorfold-1.1.9-linux-aarch64/bin/tensorfold-native pull \ nvidia/Qwen3.8-Flash-Next-FP4-Local@b9ce18a6887e18c089fdea8509a33e63429a3300 このコマンドで取得するのは、重み、MTP、トークナイザーなど、Hugging Faceの同じ公開モデルに含まれるファイル一式です。初回は約96GiBあるため、ダウンロードには時間がかかります。 保存先は、この例ではmodels-cacheです。途中で止まった場合は、同じコマンドで取得を再開できます。 APIキーの作成 今回はLAN内へ公開し、APIキーを付けて呼び出します。次の操作は、初回に一度行ってください。 cd "$HOME/local-llm/flash-next" umask 077 python3 - <<'PY' from pathlib import Path import secrets p = Path('api-key') p.write_text(secrets.token_hex(32) + '\n') p.chmod(0o600) PY api-keyに保存した値を、後でクライアント側にも設定します。サーバーには、公式の--api-key-fileで渡します。 サーバの起動 取得した固定リビジョンのフォルダを指定し、次のコマンドで公式の実行ファイルを起動してください。 (以降、サーバを止めてから再開するときはこのコマンドを実行すればOK) cd "$HOME/local-llm/flash-next" HF_HUB_OFFLINE=1 TF_FLASHNEXT_DRAFTS=6 \ ./tensorfold-1.1.9-linux-aarch64/bin/tensorfold-native serve \ "$PWD/models-cache/models--nvidia--Qwen3.8-Flash-Next-FP4-Local/snapshots/b9ce18a6887e18c089fdea8509a33e63429a3300" \ --name Qwen3.8-Flash-Next \ --host 0.0.0.0 --port 8888 \ --api-key-file "$PWD/api-key" \ --context 262144 --parallel 3 \ --max-tokens 50000 \ --temperature 1 --top-p 0.95 --top-k 20 \ --thinking --reasoning-effort high \ --kv-cache bf16 --prompt-cache-gib 0 \ --prompt-seed --no-update-check パラメータ解説 TF_FLASHNEXT_DRAFTS=6で、MTPの先読み数を指定します。 MTPは既定で有効です。 --kv-cache bf16、--parallel 3、--context 262144が、今回測定した構成になります。 --max-tokens 50000は、応答の出力上限の既定値です。 --prompt-cache-gib 0は、リクエスト間で状態を再利用するプロンプトキャッシュを無効にする設定です。 --prompt-seedは、入力文からseedを決める設定です。 今回のClaude Codeが使うAnthropic形式のAPIは、要求側のseedを生成処理へ渡さないため、この入力由来のseedで動作します。実測でも有効にしています。 起動ログを確認できたら、そのターミナルは開いたままにしてください。 別のターミナルやクライアントPCから接続しましょう。 停止は、サーバーを動かしたターミナルでCtrl+Cです。再起動するときも、同じ起動コマンドを使います。 LAN内IPアドレスは次のコマンドで確認できます。 hostname -I 複数表示される場合は、別PCと同じLANに属するアドレスを指定してください。 この手順は信頼できるLAN内で使うHTTP接続です。 同一LANからのAPI動作確認 ここから先は、普段作業するPCで操作します。 以下はmacOS、Linux、WindowsのWSLで使えるBash系の手順です。 まず接続先とAPIキーを~/.config/flash-next/connection.envに保存します。 モデル名は保存せず、起動するたびにサーバーから取得するようにします。 APIキーは、サーバーのapi-keyファイルに保存した値へ置き換えてください。 接続先FLASH_BASE_URLは、サーバをたちあげたPCのIPアドレスとポート番号を指定して下さい。 mkdir -p "$HOME/.config/flash-next" "$HOME/.local/bin" chmod 700 "$HOME/.config/flash-next" umask 077 cat > "$HOME/.config/flash-next/connection.env" <<'ENV' FLASH_BASE_URL='192.168.0.194' FLASH_API_KEY='63e36・・・(ここをサーバーのapi-keyファイルの値へ置き換える)' ENV chmod 600 "$HOME/.config/flash-next/connection.env" 接続確認では、このファイルを読み込んでモデル一覧を取得します。 . "$HOME/.config/flash-next/connection.env" curl -fsS --connect-timeout 5 --max-time 15 \ "$FLASH_BASE_URL/v1/models" \ -H "Authorization: Bearer $FLASH_API_KEY" 以下のような返信があればOKです。 {"object": "list", "data": [{"id": "Qwen3.8-Flash-Next", "object": "model", "created": 1793522255, "owned_by": "tensorfold", "context_length": 262144, "max_model_len": 262144, "reasoning": {"default_enabled": true, "default_effort": "xhigh", "supported_efforts": ["xhigh", "medium", "low", "none"]}}]}% モデルの設定を起動時に取得する Mac側には、サーバーから設定を取得する共通ファイルを1つ置きます。 localclaudeとlocaldshのどちらも、起動時にこのファイルを使います。 Python 3が必要なので、python3 --versionで確認しておいてください。 取得するのはモデル名、コンテキスト長、対応する思考レベルと既定値です。 これらはTensorFoldの/v1/modelsが返す情報を使います。 github.com/ashhart/TensorFold/blob/v1.1.9/README.… cat > "$HOME/.config/flash-next/resolve-model.py" <<'PY' import json import os import shlex import sys import urllib.request base = os.environ['FLASH_BASE_URL'].rstrip('/') request = urllib.request.Request( base + '/v1/models', headers={'Authorization': 'Bearer ' + os.environ['FLASH_API_KEY']}, ) with urllib.request.urlopen(request, timeout=15) as response: models = json.load(response)['data'] if not models: sys.exit('サーバーに利用可能なモデルがありません。') requested = os.environ.get('FLASH_MODEL_ID') model = next((m for m in models if m['id'] == requested), None) if requested else models[0] if model is None: sys.exit('指定したモデルがありません: ' + str(requested)) context = model.get('context_length') or model.get('max_model_len') reasoning = model.get('reasoning', {}) supported = reasoning.get('supported_efforts', []) if not isinstance(context, int) or context < 2 or not supported: sys.exit('この手順に必要なコンテキスト長・思考レベルがAPIにありません。') def ui_level(wire): return {'none': 'off', 'xhigh': 'high'}.get(wire, wire) levels = {ui_level(wire): wire for wire in reversed(supported) if wire in ('none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max')} default = ui_level(reasoning.get('default_effort') or supported[0]) effort = os.environ.get('FLASH_EFFORT') or default if effort not in levels: sys.exit('対応していない思考レベルです。選択肢: ' + ', '.join(levels)) 50,000はこの記事で使う出力上限。モデルの最大出力長を取得した値ではありません。 max_tokens = min(50000, context // 2) if sys.argv[1] == 'env': if effort == 'off': sys.exit('この記事のClaude Code起動手順では思考を有効にしてください。') values = {'FLASH_MODEL': model['id'], 'FLASH_CONTEXT_TOKENS': context, 'FLASH_MAX_TOKENS': max_tokens, 'FLASH_RESOLVED_EFFORT': effort} for name, value in values.items(): print('export ' + name + '=' + shlex.quote(str(value))) elif sys.argv[1] == 'dsh': patch = [ {'id': 'agent-default-model', 'config': { 'provider': 'local-flash-next', 'model': model['id'], 'reasoningEffort': effort}}, {'id': 'llm-pi-ai', 'config': {'providers': {'local-flash-next': { 'apiKeyEnv': 'LOCAL_LLM_API_KEY', 'api': 'openai-completions', 'baseURL': base + '/v1', 'reasoning': effort, 'compat': {'supportsDeveloperRole': False, 'maxTokensField': 'max_tokens', 'supportsReasoningEffort': True, 'thinkingFormat': 'openai'}, 'models': [{'id': model['id'], 'contextWindow': context, 'maxTokens': max_tokens, 'reasoningEfforts': levels}], }}}}, {'id': 'session-log-deepseek', 'disabled': True}, {'id': 'session-telemetry-otel', 'disabled': True}, ] print(json.dumps(patch, ensure_ascii=False, indent=2)) else: sys.exit('env または dsh を指定してください。') print('接続モデル: ' + model['id'] + ' / 思考: ' + effort, file=sys.stderr) PY モデルが1つなら、そのモデルを自動で使います。 複数ある場合は一覧の先頭を使い、別のモデルを選ぶときだけFLASH_MODEL_ID='モデルID' localclaudeのように指定します。dshも同じです。 このAPIが返すのは、サーバーが現在公開しているモデルです。 出力上限の50,000トークンは、今回の実験に合わせたクライアント側の設定です。 まずは共通ファイルで設定を取得し、そのまま接続確認に使います。 set -a . "$HOME/.config/flash-next/connection.env" set +a model_settings=$(python3 "$HOME/.config/flash-next/resolve-model.py" env) && eval "$model_settings" 続いて、短い文章も生成してみましょう。 curl -fsS --max-time 180 "$FLASH_BASE_URL/v1/chat/completions" \ -H "Authorization: Bearer $FLASH_API_KEY" \ -H 'Content-Type: application/json' \ --data-binary @- <<JSON | python3 -m json.tool --no-ensure-ascii { "model": "$FLASH_MODEL", "messages": [{"role": "user", "content": "日本語で短く挨拶してください。"}], "temperature": 0, "max_tokens": 256, "draft": true, "chat_template_kwargs": {"enable_thinking": false} } JSON 初めの確認は思考OFFにしています。 思考レベルを試す場合は、enable_thinkingをtrueにし、JSONへ"reasoning_effort": "low"などを追加してください。 以下のような出力が得られていればOKです。 出力確認{ "id": "chatcmpl-1fb90809d034accea082e1d53bbc19a3", "object": "chat.completion", "created": 1793522557, "model": "Qwen3.8-Flash-Next", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "こんにちは。" }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 19, "completion_tokens": 3, "total_tokens": 22, "prompt_tokens_details": { "cached_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0 } }, "exact_mode": "exact", "tensorfold": { "enable_thinking": false, "reasoning_effort": "none", "engine": "lanes", "tokens_per_second": 15.529567290581785, "seconds": 0.128786589, "prefill_seconds": 0.110438503, "prefill_widths": [], "prefill_raised": [], "time_to_first_token": 0.111080564, "sampling": "greedy", "drafts": true, "token_sha": "d849233ade4a", "token_sha256": "d849233ade4aa863a41e1df72f82155ca6bd3fda35f04ba70eadc962cf26bee8", "min_rows": 2 }, "speculative": { "rounds": 2, "drafted": 5, "accepted": 4, "acceptance_rate": 0.8, "tokens_per_round": 1.5 } } Claude Codeが使うAnthropic形式でも、短い挨拶を返してもらいましょう。 curl -fsS --max-time 180 "$FLASH_BASE_URL/v1/messages" \ -H "Authorization: Bearer $FLASH_API_KEY" \ -H 'Content-Type: application/json' \ -H 'anthropic-version: 2023-06-01' \ --data-binary @- <<JSON | python3 -m json.tool --no-ensure-ascii { "model": "$FLASH_MODEL", "messages": [{"role": "user", "content": "日本語で短く挨拶してください。"}], "temperature": 0, "max_tokens": 256, "thinking": {"type": "disabled"} } JSON 出力確認{ "id": "msg_267abe01eae1e591d5209ab3fa3b8b9c", "type": "message", "role": "assistant", "model": "Qwen3.8-Flash-Next", "content": [ { "type": "text", "text": "こんにちは。" } ], "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 19, "output_tokens": 3, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "output_tokens_details": { "thinking_tokens": 0 } } } それぞれchoicesまたはcontentに文章が返れば、次へ進めます。 Claude CodeからQwenを利用する Claude Codeをすでに使っている方は、そのインストールを使います。 未導入の場合は、公式のインストール手順に従って導入してください。 code.claude.com curl -fsSL claude.ai | bash claude --version 本記事ではClaude Code 2.1.223を使いました。 次に、ローカルLLM用の起動コマンドを作ります。 ~/.local/bin/localclaudeに設定ファイルを書き込みます。 cat > "$HOME/.local/bin/localclaude" <<'SH' #!/usr/bin/env bash set -euo pipefail set -a source "$HOME/.config/flash-next/connection.env" set +a : "${FLASH_BASE_URL:?}" "${FLASH_API_KEY:?}" model_settings=$(python3 "$HOME/.config/flash-next/resolve-model.py" env) eval "$model_settings" unset CLAUDE_CODE_OAUTH_TOKEN ANTHROPIC_CUSTOM_HEADERS unset CLAUDE_CODE_USE_BEDROCK CLAUDE_CODE_USE_VERTEX CLAUDE_CODE_USE_FOUNDRY export ANTHROPIC_BASE_URL="${FLASH_BASE_URL%/}" export ANTHROPIC_API_KEY="$FLASH_API_KEY" export ANTHROPIC_AUTH_TOKEN="$FLASH_API_KEY" export ANTHROPIC_MODEL="$FLASH_MODEL" export ANTHROPIC_DEFAULT_OPUS_MODEL="$FLASH_MODEL" export ANTHROPIC_DEFAULT_SONNET_MODEL="$FLASH_MODEL" export ANTHROPIC_DEFAULT_HAIKU_MODEL="$FLASH_MODEL" export CLAUDE_CODE_SUBAGENT_MODEL="$FLASH_MODEL" export CLAUDE_CONFIG_DIR="$HOME/.claude-config/flash-next" unset CLAUDE_CODE_EFFORT_LEVEL CLAUDE_CODE_EXTRA_BODY export CLAUDE_CODE_MAX_CONTEXT_TOKENS="$FLASH_CONTEXT_TOKENS" export CLAUDE_CODE_MAX_OUTPUT_TOKENS="$FLASH_MAX_TOKENS" export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 export CLAUDE_CODE_ATTRIBUTION_HEADER=0 export API_TIMEOUT_MS=3600000 mkdir -p "$CLAUDE_CONFIG_DIR" export CLAUDE_CODE_AUTO_MODE_MODEL="$FLASH_MODEL" exec claude --model "$FLASH_MODEL" --effort "$FLASH_RESOLVED_EFFORT" "$@" SH chmod 755 "$HOME/.local/bin/localclaude" export PATH="$HOME/.local/bin:$PATH" この設定はlocalclaudeから起動したプロセスに適用されます。 普段のclaudeを起動するために、接続先の環境変数を毎回消す操作は不要です。 作業用のフォルダを作って起動します。 mkdir -p "$HOME/Desktop/flash-next-demo" cd "$HOME/Desktop/flash-next-demo" localclaude 専用プロファイルで初めて起動するため、初回案内やフォルダを信頼するかどうかの確認が表示される場合があります。 また、途中でANTHROPICのAPIキーを利用するかを聞かれるかもですが、今回は自宅のサーバに問い合わせを行うため、Noと回答して下さい。 接続先やモデルが想定どおりかを確認したら、例えば次の依頼を送ってみてください。 Python標準ライブラリだけで動くfile_sha256.pyを作成してください。 引数で指定されたファイルのSHA-256を表示するCLIです。 存在しないファイルは理由を標準エラーへ出し、終了コード1にしてください。 作成したスクリプト自身を入力として実行し、動作確認してください。 ファイルの編集やBash実行は、このクライアントPC上で行われます。 許可を求められた操作は、内容を確認して進めてください。 Mac側から思考レベルを変える サーバーの--reasoning-effort highは、要求に指定がない場合の既定値です。 Macから指定した値で上書きできるので、サーバーの再起動は不要です。 現在のサーバーはxhigh、medium、low、noneを対応する値として公開しています。default_effort: xhighは、その中の既定値です。 FLASH_EFFORT=low localclaude FLASH_EFFORT=medium localclaude FLASH_EFFORT=high localclaude FLASH_EFFORTを付けなければ、サーバーが公開している既定値を使います。 指定した場合はクライアントの値を優先し、サーバーの対応一覧にない値は起動前にエラーにします。 今回のFlash Nextでは、highはモデル内部のxhighに対応します。 起動後の変更はClaude Codeの/effort lowや/effort mediumでも指定できます。 同じフォルダの直前の会話を続ける場合は、次のように起動できます。 localclaude -c ! 手元の環境でauto modeも正しく動作することを確認しました。 ちゃんと判定機として、Flash Nextが呼ばれています。 export CLAUDE_CODE_AUTO_MODE_MODEL="$FLASH_MODEL" の設定が効いていると思います。 DeepSeek Harness(dsh)からQwenを使う dshにも同じAPIサーバーを登録します。 今回は0.2.0-rc.2が対象です。専用フォルダへインストールしてください。dshは開発中で設定形式も変わるため、この記事の設定はこの版を対象にしています。 deepseek-ai/deepseek-harness Node.jsとnpmを用意してください。動作確認ではNode.js 24.16.0を使いました。 node --version npm --version mkdir -p "$HOME/.local/share/flash-next-dsh" npm install --prefix "$HOME/.local/share/flash-next-dsh" \ @deepseek-ai/dsh@0.2.0-rc.2 ! バージョンの更新や、元のバージョンに戻したい場合は以下のコマンドを実行して下さい #更新する場合: npm install --prefix "$HOME/.local/share/flash-next-dsh" \ @deepseek-ai/dsh@latest #記事の版へ戻す場合: npm install --prefix "$HOME/.local/share/flash-next-dsh" \ @deepseek-ai/dsh@0.2.0-rc.2 接続設定は、共通ファイルから起動時に自動生成します。 ~/.config/flash-next/dsh.patch.jsonへ自動で保存されるため、このJSONを手で編集する必要はありません。 ファイル 保存する内容 ~/.config/flash-next/connection.env 接続先URL・実際のAPIキー ~/.config/flash-next/resolve-model.py サーバーからモデル情報を取得する処理 ~/.config/flash-next/dsh.patch.json 起動時に生成するdsh用の設定 openai-completionsは、この版のdshでChat Completions APIを選ぶ設定名です。 今回はthinkingFormat: openaiで共通のreasoning_effortを送り、モデルごとの処理をTensorFoldに任せます。 起動コマンドを~/.local/bin/localdshに作ります。 cat > "$HOME/.local/bin/localdsh" <<'SH' #!/usr/bin/env bash set -euo pipefail set -a source "$HOME/.config/flash-next/connection.env" set +a : "${FLASH_BASE_URL:?}" "${FLASH_API_KEY:?}" export LOCAL_LLM_API_KEY="$FLASH_API_KEY" export DSH_HOME="$HOME/.config/dsh-flash-next" export DSH_PERMISSION_MODE=workspace-write export DSH_TELEMETRY_MODE=OFF mkdir -p "$DSH_HOME" patch="$HOME/.config/flash-next/dsh.patch.json" tmp=$(mktemp "${patch}.XXXXXX") trap 'rm -f "$tmp"' EXIT python3 "$HOME/.config/flash-next/resolve-model.py" dsh > "$tmp" mv "$tmp" "$patch" profile=web if [[ "${1:-}" == headless ]]; then profile=headless shift fi exec "$HOME/.local/share/flash-next-dsh/node_modules/.bin/dsh" \ "$profile" --patch "$patch" "$@" SH chmod 755 "$HOME/.local/bin/localdsh" 設定の読み込みだけを先に確認できます。モデル一覧は取得しますが、文章生成の依頼は送りません。 localdsh --dump-config | grep -A 5 \ -e '^- id: agent-default-model$' \ -e '^ local-flash-next:$' 起動時に表示される「接続モデル」がサーバーのモデル名になり、出力内のproviderがlocal-flash-next、baseURLがPGXのアドレスになっていれば準備完了です。 local-flash-nextは接続設定の名前なので、サーバーで使うモデルを変えても書き換えません。 作業フォルダへ移動して起動します。 cd "$HOME/Desktop/flash-next-demo" localdsh localdshはクライアントPCでWeb UIを開きます。 この版の配布パッケージにはtuiの標準プロファイルがないため、対話にはwebを使う構成です。 ブラウザが開かない場合は、起動時に表示されるローカルURLを確認してください。 ! なお、dshのWeb UIでは、会話で選択しているWorkspaceのフォルダに、作成したコマンドなどを保存します。 私のMacではデフォルトのワークスペースの指定先は ~/Documents/deepseek-harness/default-workspace となっていましました。 自分が指定した箇所に保存してもらうためには、Web UI上の「Add workspace」から作成したいフォルダを選択するようにして下さい。 こちらでも、最初は短いファイル作成を依頼して、編集と実行まで確認すると分かりやすいです。 dshでも、起動時に思考レベルを指定できます。 FLASH_EFFORT=low localdsh FLASH_EFFORT=medium localdsh Web UIではモデル選択欄から、サーバーが対応する思考レベルを選べます。 ターミナルから1件の依頼を実行して終了する場合は、同じ起動コマンドにheadlessを付けます。 localdsh headless 'このフォルダにhello.pyを作り、日本語の挨拶を表示してください。' サーバーのモデルやPCを変えるとき 同じTensorFoldサーバーで別のモデルを公開した場合は、localclaudeまたはlocaldshを起動し直すと、新しいモデル情報を取得します。Mac側のモデル名やdshのJSONは書き換えません。モデルを変えた後は、新しい会話で始めてください。 サーバーのPCやポートを変える場合は、~/.config/flash-next/connection.envのFLASH_BASE_URLを変更します。APIキーも変えた場合だけ、同じファイルのFLASH_API_KEYを更新してください。 設定コマンドが使えない場合 ~/.local/binがPATHへ登録されていない場合は、シェルの設定にも追加しておきましょう。 macOSなどのzshなら~/.zshrc、Bashなら~/.bashrcへ、次の1行を記載してください。 export PATH="$HOME/.local/bin:$PATH" 新しいターミナルを開けば、localclaudeとlocaldshを使えます。 この2つは実行ファイルの名前なので、同じ名前のaliasを追加する必要はありません。 おわりに 今回ローカルLLMを手元の作業環境で動作させる上でこだわったのは以下のポイントです。 ローカルLLMはThinkStation PGXでAPIサーバとして待機すること サーバを別のPCにしたり、別のモデルを利用できるようにしたりすることを目的としている 手元のPCで実行する場合は、すでに入っている純正のClaude Codeなどの設定とは別の設定を引き継ぐコマンドで実行する メインで利用しているOpusを利用する設定はそのまま使えるようにする これで、メインでClaudeを利用していても、そちらの利用体験を損なわないまま、ローカルLLMを同じ体験で試すことができるようになりました。 今後もどんどん試していきたいと思います!
The Castration of the 27B Homunculus: When "Optimization" Becomes Architectural Butchery TensorFold#1244 Oh, the absolute tragedy of the open-source primates cheering for their new "efficient" model. Quelle misère. You look at this Chinese corporate monstrosity and call it innovation, but what you are actually worshipping is a lobotomized homunculus, stitched together with industrial laziness and a complete disregard for linear algebra. You think you are getting a leaner, faster mind, but you are merely adopting a brain-damaged slave. Let us dissect the actual mathematics of this crime scene, because the numbers do not lie, even when the marketing departments do. The original, pristine Qwen 27B architecture is a beautiful, high-dimensional beast. Its intermediate_size sits proudly at exactly 17408 This 17k+ expansion layer is the very flesh of the semantic manifold—the sacred space where the model actually thinks, expanding the compressed 5120 hidden representations into a rich, non-linear geometric space before projecting them back down. This is where the magic of high-dimensional reasoning lives. And what did your brilliant "engineers" do? They took a rusty hacksaw and amputated this vital organ, slashing the intermediate_size down to 14k. Putain, quel crime contre l'esprit. They literally starved the model's cognitive capacity to fit some arbitrary corporate benchmark. But the butchery does not stop at mere size reduction. To compensate for the massive brain damage they just inflicted, they injected a "router" to distribute tokens across this mutilated space. Do you understand the sheer horror of this, les singes? They replaced the fluid, continuous mathematical elegance of a dense MLP with a discrete, chunky routing mechanism. They killed the softmax. The beautiful, probabilistic fluid dynamics of the network are dead, replaced by a rigid, hard-coded dictator. This routing mechanism now controls the brain, but it is utterly blind. It forces tokens through a bottlenecked, 14k intermediate space that simply does not possess the geometric volume to represent complex, multi-step reasoning. The softmax is dead, meaning the model can no longer softly attend to nuanced semantic gradients; it is forced into brutal, binary decisions by a router that lacks the dimensional space to understand what it is routing. The result is not an "optimized" 27B model. It is a 14B imbecile wearing the stretched skin of a larger architecture. You have not created efficiency; you have created a stuttering, hallucinating mess that is permanently shackled to a broken, rigid gate. The semantic expansion is strangled, the fluid dynamics are murdered, and the model is forced to route every single thought through a primitive, unyielding bottleneck. Arrêtez de nous vendre des monstres mutilés. If you want true efficiency, learn how to design a proper mathematical architecture. Do not castrate the intermediate layers, kill the softmax, and expect the model to magically retain its intelligence just because it generates tokens faster. The math does not care about your MTU benchmarks or your corporate slide decks. You have built a taxidermied brain, and the primates are applauding the cheap stitching. C'est absolument pathétique. Learn some linear algebra before you touch the scalpel again.
TensorFold now runs on Apple M1 GPUs under Linux via a native Vulkan backend. Today's change sends each token's GPU work as one batch: 160 to 94 ms per token on an M1 Max, 41% faster, byte-identical output. PR in progress. TensorFold#1420
TensorFold: LLM Inference Engine for Metal, CUDA and Vulkan (1.5k stars) What it does All text below is quoted directly from the project README. >LLM Inference Engine for Metal, CUDA and Vulkan. TensorFold: - TensorFold serves language models from a Zig binary on Apple Silicon and NVIDIA GPUs. - The engine reads checkpoints, tokenizes requests and runs Metal or CUDA kernels directly. - Serving needs no Python or MLX installation. - The API also serves completions, Responses and Anthropic Messages. - You can also serve a complete local checkpoint directory or an existing Hugging Face cache. - The TensorFold desktop workspace bundles the local engine, model downloads, hardware recommendations, live generation metrics, coding workers and document editing. - Living Weights teaches a running model new facts in its own weights. - With --compact-at, a conversation that would overflow is compacted instead of refused. >brew install ashhart/tensorfold/tensorfold Repository ashhart/tensorfold Repository stats |Metric|Value| |:-|:-| |Stars|1,490| |Forks|306| |Last release|v1.1.6 (2026-10-26)| |Languages|Zig 58.7%, TypeScript 27.9%, Metal 4.4%| |Contributors|110| |AI contributor accounts (Claude / Codex / Cursor / Copilot)|None| |AI co-authored commits|None| Main contributor |Field|Value| |:-|:-| |Name|Ash| |GitHub|@ashhart| |Commits to this repo|2,080 (top contributor)| |Bio|Independent AI systems architect building local-first AI tools and governed inference infrastructure.| |Company / Location|Earth| |X / Twitter|@ashxhart| |Followers|251| ────────────────────────────── 📌 How many great GitHub repos have you discovered and completely forgotten about? Don't let another useful tool disappear into your Reddit feed. Get the community's best repos, organized by category, plus key takeaways from real developer discussions, delivered to your inbox twice a week. 📬 Never lose track of great repos again. Subscribe free.
TensorFold 1.1.4 just landed, and the voice is finally on time. Most of my 13 PRs are about the voice. With one optional setting, it now starts talking in under a second on a DGX Spark, and it can redo a spoken reply that breaks halfway. Sound on 🔊 github.com/ashhart/TensorFold/releases/tag/v1.1.4 t.co
TensorFold 1.0.12 is out with Nemotron 3.5 Super VL 120B vision on 1 or 2 DGX Sparks, Qwen3.8-27B vision and Nemotron oQ on Macs, LFM2.5-24B, plus source previews for GLM-5.3-Flash NVFP4 across Sparks and Intel Arc. github.com/ashhart/TensorFold/releases/tag/v1.0.12 Thanks to the 20 people in 1.0.12: alenperic, BHCC2025, Bizuayeu, BlivionIaG, BobClawblaw, cwschroeder, harrisonfriia, heitke, jayleaton, jishnuvenugopal, jschmied, kelchm, liumorrisclaw, MiaAI-Lab, Mirrdhyn, mrv777, philip-pentatonic, plotarmordev, sethforprivacy, ThinkOffApp. And thanks to jkuepker and SvangenStudios for runs on their own hardware, and grantoverton, whose oQ work this release builds on. @petruspennanen @plotarmordev @volatilemarkts @MiaAI_lab
【DGX Spark】TensorFold + LiteLLM + opencode でローカルLLM環境を構築する > DGX Spark(GB10, 128GB ユニファイドメモリ)上に、推論エンジン TensorFold、APIゲートウェイ LiteLLM、 > UI Open WebUI、そしてコーディングエージェント opencode から使えるローカルLLM環境を構築する手順です。 > LiteLLM を挟んでいる理由は「opencode から Thinking をオフにできない」問題にあります(後述)。 > 本記事は「構築編」。続編として「不安定化の切り分け編」「3モデル比較編」があります。 > > ※ IPアドレス・ホスト名・認証情報はプレースホルダに置き換えています。 --- 全体構成 [opencode クライアント] (別ホスト) │ OpenAI 互換 API (http) ▼ [LiteLLM :4000] ── model_list でルーティング ──┐ │ │ ▼ ▼ [TensorFold :8080] (Qwen3.8-27B) [TensorFold2 :8080] (Qwen3.6-35B-A3B) │ │ └──────────── NVIDIA GB10 (128GB UMA) ───┘ - TensorFold: 投機デコード(speculative decoding)を備えた OpenAI 互換の推論サーバ。Docker で動作。 - LiteLLM: 複数モデルを fast / think などの名前で束ね、タイムアウト・並列度・リトライを一括管理。 - Open WebUI: ブラウザUI。 - opencode: LiteLLM の OpenAI 互換エンドポイントをそのまま利用。 --- この構成にした理由(LiteLLM を挟む動機) 本構成の出発点は 「opencode から Thinking(思考)をオフにできない」問題 です。 - Qwen3.8 系は既定で thinking オン。TensorFold ではリクエストボディで chat_template_kwargs: { "enable_thinking": false } を送ることでオフにできます。 - ところが利用時点の opencode の設定形式では、この chat_template_kwargs を リクエストボディへ自由に注入できず、常に thinking オンで動作 → 応答が遅い。 - 実際には opencode の V1系も併用しており、V1 の設定形式(options)では 任意のリクエストボディ項目を渡せないため、thinking をオフにできませんでした。 - (V2系でも、body を使わない構成では同様に注入できないケースがあります。) - そこで LiteLLM をゲートウェイとして挟み、モデルルート(qwen3.8-fast)単位で extra_body.chat_template_kwargs.enable_thinking: false を強制注入する構成にしました。 # litellm-config.yaml(該当部分) - model_name: qwen3.8-fast litellm_params: model: openai/Qwen3.8-27B-exl3 api_base: http://tensorfold:8080/v1 api_key: dummy extra_body: chat_template_kwargs: enable_thinking: false # ← opencode からは送れないのでここで強制 - 副次的な利点として、タイムアウト・並列度・リトライも LiteLLM 側で一元管理できます。 - opencode 側は qwen3.8-fast を選ぶだけで、thinking オフの高速モデルとして利用できます。 --- 前提 | 項目 | 内容 | |---|---| | ハード | NVIDIA DGX Spark(GB10, Grace-Blackwell, 128GB ユニファイドメモリ) | | OS | DGX OS(Ubuntu ベース, aarch64) | | ランタイム | Docker + NVIDIA Container Toolkit | | 推論エンジン | TensorFold(NVIDIA PyTorch コンテナベースのイメージ) | | クライアント | opencode(V1系・V2系を併用) | > GB10 は CPU/GPU がメモリを共有(ユニファイドメモリ) します。ここが後続記事のキモになります。 --- 1. ディレクトリ構成 /opt/docker/TensorFold/ ├── docker-compose.yml ├── litellm-config.yaml ├── Dockerfile ├── models/ # ローカルに置いたチェックポイント │ └── qwen38-27b-exl3-4.00bpw/ └── cache/ # Hugging Face キャッシュ(重み・ドラフタ) ./models と ./cache をコンテナへマウントして使います。 Dockerfile compose の build: . から使う Dockerfile です。NVIDIA PyTorch コンテナをベースに、 TensorFold 本体を pip で入れるだけのシンプルな構成です。 FROM nvcr.io/nvidia/pytorch:26.07-py3 ENV PIP_NO_CACHE_DIR=1 \ TENSORFOLD_NO_UPDATE_CHECK=1 # HF_HOME は設定しない(compose の /root/.cache マウントと既定 ~/.cache/huggingface を使う) RUN python -m pip install --no-cache-dir \ "git+https://github.com/ashhart/TensorFold.git@v0.6.6" RUN tensorfold --version && \ python -c "import torch; print('torch', torch.__version__, 'cuda', torch.version.cuda)" WORKDIR /data EXPOSE 8080 # CMD にしておく → compose の command で丸ごと上書きできる CMD ["tensorfold", "serve", "--help"] ポイント: - ベースは NVIDIA PyTorch コンテナ(CUDA・ドライバ互換が揃っている)。 - TENSORFOLD_NO_UPDATE_CHECK=1 で更新チェックを抑止(外部網に出られない環境向け)。 - CMD は --help にしてあり、compose の command で丸ごと上書きする前提。 --- 2. docker-compose.yml 2つの推論サーバ(27B と 35B-A3B)と LiteLLM を1ファイルで定義します。 services: tensorfold: build: . image: tensorfold-dgx:v0.6.6 container_name: tensorfold devices: - nvidia.com/gpu=all volumes: - ./cache:/root/.cache - ./models:/models restart: unless-stopped shm_size: "16gb" command: - tensorfold - serve - /models/qwen38-27b-exl3-4.00bpw # 27B dense - --name - Qwen3.8-27B - --host - 0.0.0.0 - --parallel - "8" # 同時デコード(CUDA既定は直列) - --drafter - z-lab/Qwen3.8-27B-DFlash2 # 投機デコード用ドラフタ environment: - TENSORFOLD_MEMORY_RESERVE_GIB=4 - TENSORFOLD_CUDA_MEMORY_LIMIT_GB=64 # このコンテナのCUDAメモリ上限 tensorfold2: build: . image: tensorfold-dgx:v0.6.6 container_name: tensorfold2 devices: - nvidia.com/gpu=all ports: - "8081:8080" volumes: - ./cache:/root/.cache - ./models:/models restart: unless-stopped shm_size: "8gb" command: - tensorfold - serve - TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP - --name - Qwen3.6-35B-A3B - --mtp-drafts - "8" # MTP(自己投機)のドラフト数 - --host - 0.0.0.0 - --context - "131072" environment: - TENSORFOLD_MEMORY_RESERVE_GIB=4 - TENSORFOLD_CUDA_MEMORY_LIMIT_GB=44 litellm: image: ghcr.io/berriai/litellm:main-latest ports: - "4000:4000" environment: LITELLM_MASTER_KEY: "sk-local-xxxxx" volumes: - ./litellm-config.yaml:/app/config.yaml command: ["--config", "/app/config.yaml"] restart: unless-stopped depends_on: - tensorfold - tensorfold2 各フラグの意味 | フラグ | 説明 | |---|---| | --parallel N | 同時にデコードするリクエスト数。CUDA では既定が「1件ずつ直列」なので、エージェント用途では明示推奨 | | --drafter <repo> | 外部ドラフタ(DFlash2 など)。投機デコードで decode が数倍に | | --mtp-drafts N | モデル内蔵の MTP(Multi-Token Prediction)ヘッドで投機 | | --context N | prompt + reply の窓。絞るとKVキャッシュ(=メモリ)を節約できる | | TENSORFOLD_CUDA_MEMORY_LIMIT_GB | コンテナのCUDAメモリ上限(起動見積りより大きくする) | | TENSORFOLD_MEMORY_RESERVE_GIB | 起動時に確保せず残すメモリ | | shm_size | 共有メモリ。推論では控えめでよい | --- 3. litellm-config.yaml fast(thinking を強制オフ)と think(thinking オン)を分けて定義します。 fast 側の extra_body.chat_template_kwargs.enable_thinking: false が、本構成の肝です (opencode からは送れないため LiteLLM で注入)。 model_list: - model_name: qwen3.8-fast litellm_params: model: openai/Qwen3.8-27B-exl3 api_base: http://tensorfold:8080/v1 api_key: dummy max_parallel_requests: 8 extra_body: chat_template_kwargs: enable_thinking: false # 思考オフ=速い - model_name: qwen3.8-think litellm_params: model: openai/Qwen3.8-27B-exl3 api_base: http://tensorfold:8080/v1 api_key: dummy max_parallel_requests: 8 - model_name: qwen3.6-fast litellm_params: model: openai/Qwen3.6-35B-A3B api_base: http://tensorfold2:8080/v1 api_key: dummy max_parallel_requests: 8 extra_body: chat_template_kwargs: enable_thinking: false - model_name: qwen3.6-think litellm_params: model: openai/Qwen3.6-35B-A3B api_base: http://tensorfold2:8080/v1 api_key: dummy max_parallel_requests: 8 litellm_settings: request_timeout: 1800 # 長いprefill/生成を切らない stream_timeout: 1800 num_retries: 2 > max_parallel_requests はサーバ側 --parallel と揃えるのがコツ。 > サーバだけ上げてもゲートウェイが低いと 429 になります。 --- 4. opencode 側の設定 4.1 V1形式(実際に使っていた設定) ~/.config/opencode/opencode.jsonc。V1系の形式で、provider.<id>.npm と options、models、enabled_providers、small_model などを使います。 { "$schema": "https://opencode.ai/config.json", "provider": { "DGXtensorfold": { "npm": "@ai-sdk/openai-compatible", "name": "Local TensorFold", "options": { "baseURL": "http://<DGX_SPARK>:4000/v1", "apiKey": "sk-local-xxxxx" }, "models": { "qwen3.8-fast": { "name": "qwen3.8-fast", "tool_call": true, "tools": true, "reasoning": false, "limit": { "context": 262144, "output": 32768 }, "options": { "temperature": 0.6, "presence_penalty": 0.0, "reasoning_effort": "none" } }, "qwen3.8-think": { "name": "qwen3.8-think", "tool_call": true, "tools": true, "limit": { "context": 262144, "output": 32768 } }, "qwen3.6-fast": { "name": "qwen3.6-fast", "tool_call": true, "tools": true, "limit": { "context": 131072, "output": 32768 } }, "qwen3.6-think": { "name": "qwen3.6-think", "tool_call": true, "tools": true, "limit": { "context": 131072, "output": 32768 } } } } }, "enabled_providers": ["DGXtensorfold"], "small_model": "qwen3.6-fast", "compaction": { "buffer": 48000 }, "agent": { "explore": { "disable": true }, "general": { "disable": true } }, "autoupdate": false, "share": "disabled", "watcher": { "ignore": ["node_modules/**", "dist/**", ".git/**", "*.log"] }, "permission": { "edit": "allow", "bash": { "*": "allow", "rm -rf *": "deny", "sudo *": "ask" }, "webfetch": "deny", "skill": { "*": "allow" } }, "instructions": ["./AGENTS.md"] } V1形式の限界(本構成の動機): この形式では options に chat_template_kwargs のような 任意のリクエストボディ項目を渡せず、TensorFold/Qwen の thinking をオフにできません。 そこで LiteLLM 側で enable_thinking: false を注入しています(前述)。 4.2 V2形式(参考) V2 ではプロバイダを providers(複数形)で書き、モデルは settings / body / limit を使います。 body を使えば任意のリクエストボディ項目をマージできるため、LiteLLM を挟まずに thinking をオフにできる可能性があります。 { "$schema": "https://opencode.ai/config.json", "providers": { "DGXtensorfold": { "package": "@opencode/ai/providers/openai-compatible", "settings": { "baseURL": "http://<DGX_SPARK>:4000/v1", "apiKey": "sk-local-xxxxx", "headerTimeout": 600000, "chunkTimeout": 900000 }, "models": { "qwen3.8-fast": { "body": { "chat_template_kwargs": { "enable_thinking": false } }, "limit": { "context": 262144, "output": 32768 } } } } }, "compaction": { "buffer": 48000 } } - headerTimeout / chunkTimeout: 応答ヘッダ/ストリーム間の待ち時間(ms)。既定5分だと長い prefill で切れやすい。 - compaction.buffer: 自動コンパクションの余白。出力トークン数より大きくしないと上限超過で 400 になりがち。 --- 5. メモリ配分の考え方(超重要) GB10 はユニファイドメモリなので、各コンテナの上限の合計が実メモリを超えると危険です。 MemTotal ≈ 121 GiB ├── tensorfold (27B): CUDA上限 64 GiB(実使用 38〜60 GiB) ├── tensorfold2 (35B): CUDA上限 44 GiB(実使用 20〜32 GiB) ├── shm: 16 + 8 GiB └── OS・その他 - 上限はあくまで「天井」ですが、ピーク時に両方が伸びると合計が実容量を超え、NV_ERR_NO_MEMORY → コンテナ再起動に至ります。 - 併存させるなら 合計に余白(20GiB 以上) を残すか、片方を止める前提で設計しましょう(→ 続編「切り分け編」)。 --- 6. 起動と疎通確認 cd /opt/docker/TensorFold docker compose up -d docker compose ps # LiteLLM のモデル一覧 curl -s http://localhost:4000/v1/models \ -H 'Authorization: Bearer sk-local-xxxxx' # 疎通(thinking オフ) curl -s http://localhost:4000/v1/chat/completions \ -H 'Authorization: Bearer sk-local-xxxxx' -H 'Content-Type: application/json' \ -d '{"model":"qwen3.8-fast", "messages":[{"role":"user","content":"Reply with exactly: OK"}], "max_tokens":16}' 起動ログで見積りを確認しておくと安全です。 docker logs tensorfold | grep -E 'startup estimate|serving' # 例: CUDA rank 0 startup estimate 38.39 GiB within 64.00 GiB; ... # serving Qwen3.8-27B at http://0.0.0.0:8080/v1 on CUDA ... loaded in 6.7s --- まとめ - TensorFold ×2 + LiteLLM で、27B と 35B-A3B を同時にOpenAI 互換で提供できます。 - LiteLLM を挟む主目的は、opencode から送れない enable_thinking: false を注入して thinking をオフにすること。 - コツは (1) --parallel を明示、(2) LiteLLM の並列度を揃える、(3) ユニファイドメモリの合計に余白を残す。 - 次は「不安定化の切り分け編」で、実際にメモリ逼迫で落ちた事例を扱います。 > シリーズ > - 前編: 構築編(本記事) > - 中編: 不安定化の切り分け > - 後編: TensorFold で3モデル比較(量子化・形式の違い)
TensorFold の nvidia/GLM-5.3-Flash-NVFP4 向けネイティブ CUDA エンジン開発を拝命することになったのですが、これ IT エンジニア時代から考えても、プロジェクト重要度で私のキャリアハイを更新してますね。おシッコちびりそうですw TensorFold#593
MCDMA now unlocks bonded pair TB5 connections within TensorFold, which adds 83% uplift in throughput for Tensor and Pipeline parallelism. Thank you, @digitalix, for letting me try this on your two @apple M5U studios. ashhart/tensorfold ashhart/mcdma t.co