Nvidia DGX Spark 安裝 Holo-3.1
原本我在 Nvidia 都搭配 vLLM 啟動,這條路理論上可以發揮 NVFP4 權重的優勢,但在 DGX Spark 的 GB10 平台上,實際遇到 CUDA Kernel 與 Marlin repack 相容性問題。
最後我改採:
Hcompany/Holo-3.1-35B-A3B-GGUF
搭配 llama.cpp CUDA Build,成功啟動模型並提供 API 服務。
這篇文章記錄完整流程,也保留幾個重要的踩坑經驗。
環境
本次環境如下:
硬體:NVIDIA DGX SparkGPU:NVIDIA GB10系統:Ubuntu Linux模型儲存位置:/mnt/ai-models推論框架:llama.cpp模型格式:GGUF量化版本:Q4_K_M
DGX Spark 使用統一記憶體架構,因此 CPU、GPU 與系統服務會共用記憶體。這點對 vLLM 與 llama.cpp 都很重要。
為什麼放棄 NVFP4 + vLLM
一開始使用 vLLM 載入:
Hcompany/Holo-3.1-35B-A3B-NVFP4
後,權重其實已經完整載入:
Loading safetensors checkpoint shards: 100% Completed | 3/3Loading weights took 142.78 seconds
但載入完成後,仍然在 Marlin FP4 重新整理階段失敗:
NotImplementedError:Could not run '_C::gptq_marlin_repack'with arguments from the 'CUDA' backend
這代表模型檔案本身沒有問題,真正卡住的是 vLLM 的 CUDA Extension 在 DGX Spark GB10 上沒有完整提供所需的 Marlin CUDA Operator。
如果只是想先把 Holo 3.1 跑起來,不一定要繼續投入時間處理 NVFP4 相容性。改用 GGUF + llama.cpp 是更快、更穩定的選擇。
移除 NVFP4 模型快取
vLLM 下載的 Hugging Face 模型通常會放在:
/mnt/ai-models/huggingface/hub/
先確認路徑:
find /mnt/ai-models/huggingface \ -maxdepth 3 \ -type d \ -name 'models--Hcompany--Holo-3.1-35B-A3B-NVFP4' \ -print
確認容量:
du -sh \ /mnt/ai-models/huggingface/hub/models--Hcompany--Holo-3.1-35B-A3B-NVFP4
確認無誤後刪除:
rm -rf \ /mnt/ai-models/huggingface/hub/models--Hcompany--Holo-3.1-35B-A3B-NVFP4
安裝 llama.cpp
1. 安裝編譯工具
sudo apt update sudo apt install -y \ git \ build-essential \ cmake \ ninja-build \ libcurl4-openssl-dev \ pkg-config
2. 確認 CUDA Toolkit
export CUDA_HOME=/usr/local/cudaexport PATH="${CUDA_HOME}/bin:${PATH}"
export LD_LIBRARY_PATH="${CUDA_HOME}/lib64:${LD_LIBRARY_PATH:-}"
nvcc --versionnvidia-sminvcc --version 必須能正常回傳 CUDA 版本。
3. Clone llama.cpp
mkdir -p /mnt/ai-models/srccd /mnt/ai-models/srcgit clone https://github.com/ggml-org/llama.cpp.gitcd llama.cpp
如果之前已經下載過:
cd /mnt/ai-models/src/llama.cpp git fetch origingit switch master git pull --ff-only origin master
4. 編譯 CUDA 版本
cd /mnt/ai-models/src/llama.cpp rm -rf buildcmake -B build \ -DGGML_CUDA=ON \ -DCMAKE_BUILD_TYPE=Releasecmake --build build \ --config Release \ -j 4 \ --target llama-server llama-cli
確認:
./build/bin/llama-server --version ./build/bin/llama-cli --version
下載 Holo 3.1 GGUF 模型
建立模型目錄:
mkdir -p \ /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF
下載主模型、視覺投影模型與 Chat Template:
hf download \ Hcompany/Holo-3.1-35B-A3B-GGUF \ q4_k_m.gguf \ mmproj.f16.gguf \ chat_template.jinja \ --local-dir \ /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF
確認:
ls -lh \ /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF
應該看到:
q4_k_m.ggufmmproj.f16.ggufchat_template.jinja
單模型模式啟動
先用最簡單的單模型方式確認服務能運作:
cd /mnt/ai-models/src/llama.cpp ./build/bin/llama-server \ -m /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF/q4_k_m.gguf \ --mmproj /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF/mmproj.f16.gguf \ --jinja \ --host 0.0.0.0 \ --port 8080 \ -c 8192 \ -np 1 \ -ngl 999
參數說明:
--mmproj 指定視覺投影模型 --jinja 使用模型附帶的 Chat Template -c 8192 Context Length -np 1 同時處理 1 個請求 -ngl 999 儘可能將模型層放到 GPU
測試文字 API
curl -s http://192.168.0.240:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Holo-3.1-35B-A3B-GGUF", "messages": [ { "role": "user", "content": "請使用繁體中文介紹你的 UI 畫面分析能力。" } ], "max_tokens": 256, "temperature": 0.2 }' | python3 -m json.tool在這台 DGX Spark 上,實測文字生成速度約為:
82 tokens/s
測試圖片分析 API
curl -s http://127.0.0.1:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "q4_k_m.gguf", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one concise English sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ], "thinking_budget_tokens": 64, "max_tokens": 1024, "temperature": 0.1 }' | python3 -m json.tool已知問題:中文多模態輸出偶爾觸發解析錯誤
圖片推論本身可以成功,但在某些中文輸出中,llama-server 可能回傳:
Failed to parse input at pos ...
例如:
自由女神像、火炬、基座、城市天際線、高樓、水面、島嶼、樹木、旗�、船。
其中 � 是 UTF-8 無效字元替代符號。
實務上的處理方式:
1. 降低 temperature,例如 0.12. 限制輸出為簡短句子3. 提高 max_tokens,避免輸出被截斷4. Client 端遇到 500 時自動 Retry 一次5. 優先更新至最新版 llama.cpp
Router Mode:支援多模型切換
確認單模型模式正常後,可以啟用 Router Mode:
cd /mnt/ai-models/src/llama.cpp ./build/bin/llama-server \ --models-dir /mnt/ai-models/llama-models \ --models-max 1 \ --models-autoload \ --jinja \ --host 0.0.0.0 \ --port 8080 \ -c 8192 \ -np 1 \ -ngl 999
啟動後會看到:
Loaded 1 local model presets from /mnt/ai-models/llama-modelsAvailable models (1) Holo-3.1-35B-A3B-GGUFstarting router server, no model will be loaded in this processrouter server is listening on http://0.0.0.0:8080
Router 會在 Client 第一次呼叫時才載入模型。
查看模型清單:
curl -s \ 'http://127.0.0.1:8080/models?reload=1' | \ python3 -m json.tool
指定模型:
curl -s http://127.0.0.1:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Holo-3.1-35B-A3B-GGUF", "messages": [ { "role": "user", "content": "請使用繁體中文簡短介紹你的功能。" } ], "max_tokens": 512, "temperature": 0.2 }' | python3 -m json.toolRouter Mode 的記憶體策略
我使用:
--models-max 1
意思是:
可以讓 Client 選擇多個模型但同一時間只保留一個模型在記憶體中
這很適合 DGX Spark。因為 Holo 35B、KV Cache、圖片 Token 與系統服務都會共用統一記憶體。
如果要同時提供 Holo 與另一個 Tool Calling 模型,建議:
Holo 35B:Context 8192 或 16384用途:UI 截圖與視覺分析較小的文字 Tool Calling 模型:Context 65536用途:Hermes Agent 預設模型
不同模型需要不同 Context Length 時,可使用 models.ini:
version = 1 [*] jinja = true n-gpu-layers = 999 parallel = 1 [Holo-3.1-35B-A3B-GGUF] model = /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF/q4_k_m.gguf mmproj = /mnt/ai-models/llama-models/Holo-3.1-35B-A3B-GGUF/mmproj.f16.gguf ctx-size = 8192 [qwen-coder] model = /mnt/ai-models/llama-models/qwen-coder-14b-q4_k_m.gguf ctx-size = 65536
啟動:
./build/bin/llama-server \ --models-preset /mnt/ai-models/llama-models/models.ini \ --models-max 1 \ --models-autoload \ --host 0.0.0.0 \ --port 8080
讓 llama.cpp 開機就執行
一、確認執行檔路徑
先執行:
ls -lh /mnt/ai-models/src/llama.cpp/build/bin/llama-server
再確認模型目錄:
ls -lah /mnt/ai-models/llama-models
測試版本:
/mnt/ai-models/src/llama.cpp/build/bin/llama-server --version
如果這三個指令正常,就可以建立服務。
二、建立 systemd 服務
建立服務檔:
sudo nano /etc/systemd/system/llama-router.service
貼上:
[Unit] Description=llama.cpp Router Server Documentation=https://github.com/ggml-org/llama.cpp After=network-online.target local-fs.target Wants=network-online.target RequiresMountsFor=/mnt/ai-models [Service] Type=simple User=gwoyju Group=gwoyju WorkingDirectory=/mnt/ai-models/src/llama.cpp Environment="LD_LIBRARY_PATH=/usr/local/cuda/lib64" Environment="CUDA_HOME=/usr/local/cuda" ExecStartPre=/usr/bin/test -x /mnt/ai-models/src/llama.cpp/build/bin/llama-server ExecStartPre=/usr/bin/test -d /mnt/ai-models/llama-models ExecStart=/mnt/ai-models/src/llama.cpp/build/bin/llama-server \ --models-dir /mnt/ai-models/llama-models \ --models-max 1 \ --models-autoload \ --jinja \ --host 0.0.0.0 \ --port 8080 \ -c 8192 \ -np 1 \ -ngl 999 Restart=on-failure RestartSec=10 TimeoutStopSec=30 KillSignal=SIGTERM LimitNOFILE=65535 [Install] WantedBy=multi-user.target
儲存後離開:
Ctrl + O Enter Ctrl + X
WantedBy=multi-user.target 讓服務可以隨一般多使用者開機流程啟動;Restart=on-failure 會在程式異常退出時重新啟動服務。
為什麼需要 RequiresMountsFor
你的模型與程式都放在:
/mnt/ai-models
如果外接 SSD 尚未掛載完成,直接啟動 llama-server 會失敗。
這一行:
RequiresMountsFor=/mnt/ai-models
會要求 systemd 先準備好該掛載點,再啟動 Router。
三、啟用開機自動執行
重新讀取服務設定:
sudo systemctl daemon-reload
設定開機自動啟動,並立即啟動:
sudo systemctl enable --now llama-router
查看服務狀態:
systemctl status llama-router --no-pager
正常情況應該看到:
Active: active (running)
以及:
router server is listening on http://0.0.0.0:8080
四、查看即時日誌
查看最近 100 行日誌:
journalctl -u llama-router -n 100 --no-pager
持續追蹤日誌:
journalctl -u llama-router -f
離開即時日誌:
Ctrl + C
五、測試 Router API
確認 Router 已啟動:
curl -s http://127.0.0.1:8080/health
查看模型清單:
curl -s \ 'http://127.0.0.1:8080/models?reload=1' | \ python3 -m json.tool
測試 Holo 3.1:
curl -s http://127.0.0.1:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Holo-3.1-35B-A3B-GGUF", "messages": [ { "role": "user", "content": "請使用繁體中文簡短介紹你的功能。" } ], "max_tokens": 256, "temperature": 0.2 }' | python3 -m json.tool第一次呼叫時會需要等待模型載入。後續請求會比較快。
六、常用管理指令
啟動服務
sudo systemctl start llama-router
停止服務
sudo systemctl stop llama-router
重新啟動
sudo systemctl restart llama-router
查看狀態
systemctl status llama-router --no-pager
取消開機自動啟動
sudo systemctl disable --now llama-router
七、確認開機後是否真的自動啟動
重新開機:
sudo reboot
重新 SSH 登入後執行:
systemctl status llama-router --no-pager
確認 Port:
ss -ltnp | grep :8080
測試模型清單:
curl -s http://127.0.0.1:8080/models | \ python3 -m json.tool
八、改用 models.ini
準備讓不同模型有不同 Context Length:
Holo 35B:8192Hermes 預設 Tool Calling 模型:65536
這種情況建議不要在服務中使用:
--models-dir-c 8192
而是改用:
--models-preset
假設設定檔位於:
/mnt/ai-models/llama-models/models.ini
服務中的 ExecStart 改成:
ExecStart=/mnt/ai-models/src/llama.cpp/build/bin/llama-server \ --models-preset /mnt/ai-models/llama-models/models.ini \ --models-max 1 \ --models-autoload \ --host 0.0.0.0 \ --port 8080
改完後重新載入設定:
sudo systemctl daemon-reload sudo systemctl restart llama-router
查看日誌:
journalctl -u llama-router -f
九、避免 Ollama 開機後搶占記憶體
你之前遇過記憶體不足。如果目前 DGX Spark 主要改用 llama.cpp,建議停用 Ollama 的開機自動啟動:
sudo systemctl disable --now ollama
確認:
systemctl status ollama --no-pager
未來需要恢復:
sudo systemctl enable --now ollama
十、限制只允許內網連線
目前使用:
--host 0.0.0.0
代表區域網路內其他裝置可以存取 API。Router Mode 目前仍屬於實驗性功能;llama.cpp 啟動日誌也提醒,不建議直接暴露在不受信任的網路環境。
假設區域網路是:
192.168.0.0/24
可以設定 UFW:
sudo ufw allow from 192.168.0.0/24 \ to any port 8080 proto tcp
查看規則:
sudo ufw status numbered
不要直接把 Port 8080 暴露到公網。
建議你現在直接執行的版本
建立 /etc/systemd/system/llama-router.service 後,執行:
sudo systemctl daemon-reload sudo systemctl enable --now llama-router systemctl status llama-router --no-pagerjournalctl -u llama-router -n 50 --no-pager
這樣 DGX Spark 每次重新開機後,llama.cpp Router Server 就會自動啟動,並等待 Hermes Agent 或其他 Client 指定要載入的模型。
近期留言