使用 llama.cpp、Claude Code 和 Codex 搭建本地 Agent 编码环境
我手头有一张 24GB 显存的 RTX 4090。平时写代码主要用 Codex,一些简单任务交给本地模型,能少占一点订阅额度。
本文按实际操作顺序记录 NVIDIA 驱动、CUDA、llama.cpp 编译、模型下载、服务启动、接口验证,以及 Claude Code 与 Codex 接入。ModelScope 的缓存扫描、清理和旧目录迁移另见使用 ModelScope 管理本地模型缓存。
最后一次完整验证使用以下版本:
- ModelScope
1.39.1,其 Hub CLI 为modelscope-hub 0.2.0。 - llama.cpp build
10573,commitd775b8967。 - Claude Code
2.1.239。 - Codex CLI
0.149.0。
Qwen3.6-27B 在这组版本下完成了普通回复和真实终端调用。版本继续变化后,至少重新跑一遍文末的两类验证,不能只看服务是否返回 OK。
准备 NVIDIA 驱动
这次使用 Debian 12、RTX 4090 和 CUDA 13.3。先安装添加 NVIDIA 软件源需要的工具:
sudo apt install dirmngr ca-certificates software-properties-common apt-transport-https curl -y添加 Debian 12 对应的 NVIDIA GPG 公钥:
curl -fSsL https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/3bf863cc.pub \
| sudo gpg --dearmor \
| sudo tee /usr/share/keyrings/nvidia-drivers.gpg > /dev/null这台机器的旧驱动有问题,我清理后重新安装了驱动。下面的 purge 命令会卸载已有的 NVIDIA 和 CUDA 软件包,驱动正常时不要执行:
sudo apt-get purge -y '^nvidia-.*'
sudo apt-get purge -y '^libnvidia-.*'
sudo apt-get purge -y '^cuda-.*'
sudo apt autoremove --purge -y
apt search nvidia-driver
sudo apt install --reinstall -y nvidia-driver nvidia-driver-cuda重启后运行:
nvidia-smi我的实际输出如下:
Tue Jul 21 17:35:09 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 610.43.02 KMD Version: 610.43.02 CUDA UMD Version: 13.3 |
+-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA GeForce RTX 4090 On | 00000000:01:00.0 Off | Off |
| 30% 34C P8 12W / 450W | 1MiB / 24564MiB | 0% Default |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| No running processes found |
+-----------------------------------------------------------------------------------------+这里需要确认系统识别到了 NVIDIA GeForce RTX 4090,并且显存容量显示为 24564 MiB。确认驱动和 GPU 状态正常后,再继续安装 CUDA 开发工具包。
Docker 显卡支持是可选项
这台机器还要通过 Docker 使用显卡,因此安装了 NVIDIA Container Toolkit:
sudo apt install nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker直接在宿主机运行 llama.cpp 不依赖这一步。
安装 CUDA Toolkit
apt search cuda-toolkit
sudo apt install cuda-toolkit-13-3
echo 'export CUDA_HOME=/usr/local/cuda' >> ~/.bashrc
echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc
nvcc --version这里记录的是我实际使用的 cuda-toolkit-13-3,不是对其他驱动版本的通用推荐。仓库版本和 Toolkit 版本变化后,应先确认 NVIDIA 的兼容关系。
编译 llama.cpp
git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp
rm -fr build
cmake -B build -DGGML_CUDA=ON -DGGML_RPC=ON
cmake --build build --config Release -j 32
./build/bin/llama-server --version这里同时启用了 CUDA 和 RPC。纯 CPU 构建可以改成:
cmake -B build -DGGML_CUDA=OFF -DGGML_RPC=OFF./build/bin/llama-server --version 能打印版本信息,说明二进制已经生成。它还不能证明模型一定能加载,接下来继续下载模型并实际启动服务。
安装 ModelScope 并下载模型
我使用 uv 管理 Python 工具,不手工创建并激活 venv。ModelScope Hub CLI 是长期使用的命令行工具,因此通过 uv tool 隔离安装。开始前先确认 uv 可用:
uv --version
uv tool install modelscopeuv tool 会为 ModelScope 建立独立环境,并把 modelscope 命令安装到 uv 的工具目录。安装后先检查 CLI:
uv tool list
modelscope --version
modelscope --help
modelscope download --help如果 uv tool list 能看到 ModelScope,但终端找不到 modelscope,执行:
uv tool update-shell重新打开终端后再检查。以后更新或卸载继续使用同一套工具管理命令:
uv tool upgrade modelscope
uv tool uninstall modelscopeQwen3.6-27B 使用下面的实际下载命令:
export MODELSCOPE_CACHE="/path/to/modelscope"
modelscope download unsloth/Qwen3.6-27B-GGUF \
--include "*Q4_K_M*" "*mmproj-F16*"MODELSCOPE_CACHE 是长期使用的缓存根目录。写进 ~/.bashrc 后重新加载即可,不需要在每条命令后重复传 --cache-dir:
echo 'export MODELSCOPE_CACHE="/path/to/modelscope"' >> ~/.bashrc
source ~/.bashrc
modelscope list --envs
modelscope cache scan下载命令把仓库 ID 作为位置参数传入,只取 Q4_K_M 权重和 F16 视觉投影文件,避免把其他量化版本一起拉下来。
只做文本编码任务时,可以不下载视觉投影文件:
modelscope download unsloth/Qwen3.6-27B-GGUF \
--include "*Q4_K_M*"缓存目录与实际文件路径
--cache-dir 适合临时覆盖,例如只想把一次下载放到另一块磁盘:
modelscope download unsloth/Qwen3.6-27B-GGUF \
--include "*Q4_K_M*" "*mmproj-F16*" \
--cache-dir /path/to/temporary-cache使用 modelscope-hub 0.2.0 重新下载后,两个文件位于:
$MODELSCOPE_CACHE/models/unsloth--Qwen3.6-27B-GGUF/snapshots/master/Qwen3.6-27B-Q4_K_M.gguf
$MODELSCOPE_CACHE/models/unsloth--Qwen3.6-27B-GGUF/snapshots/master/mmproj-F16.gguf这里的 unsloth--Qwen3.6-27B-GGUF 是把仓库 ID 中的 / 替换成 --,master 是下载时使用的 revision。不要再沿用旧版的 models/unsloth/Qwen3___6-27B-GGUF 路径。启动前可以用下面的命令核对:
modelscope cache scan
find "$MODELSCOPE_CACHE/models/unsloth--Qwen3.6-27B-GGUF/snapshots/master" \
-maxdepth 1 -type f -printf '%f %s bytes\n'GGUF 分片不用手工合并
大型模型可能以 00001-of-00003.gguf 这类名称分片。所有分片下载完整后,把 --model 指向第一片,llama.cpp 会按照 GGUF 分片信息继续加载其余文件,不要使用 cat 手工拼接。
下载中断时,先用同一条 modelscope download 命令继续,不要删除已经完成的文件。下载结束后再核对分片数量和文件大小,然后进入下一步启动模型。
启动 Qwen3.6-27B
下面的选项可在 llama-server 参数参考中核对。
先设置本次服务使用的 API Key。实际值只放在当前终端或私有配置中,不写进文章:
export LLAMA_API_KEY='在这里填入你的实际 key'
./build/bin/llama-server --metrics --alias Qwen3.6-27B \
--model "$MODELSCOPE_CACHE/models/unsloth--Qwen3.6-27B-GGUF/snapshots/master/Qwen3.6-27B-Q4_K_M.gguf" \
--mmproj "$MODELSCOPE_CACHE/models/unsloth--Qwen3.6-27B-GGUF/snapshots/master/mmproj-F16.gguf" \
--reasoning-preserve --jinja \
--host 0.0.0.0 --port 30104 --ctx-size 262144 -ngl 100 \
--cache-type-k q4_0 --cache-type-v q4_0 --parallel 1 \
--batch-size 4096 --ubatch-size 1024 --flash-attn on \
--load-mode mlock --threads $(nproc)下面这些参数针对这台 RTX 4090 机器调过,不适合原样套到所有环境:
--ctx-size 262144将上下文设为 262K。-ngl 100尽量把模型层放到 GPU。--cache-type-k q4_0 --cache-type-v q4_0压缩 KV Cache,降低长上下文的显存占用。--parallel 1只保留一个并行槽位。--metrics开启指标接口。--batch-size 4096设置逻辑批次上限。调大后可能加快长提示词处理,也会增加内存或显存压力。--ubatch-size 1024设置实际计算的物理批次上限。--flash-attn on强制启用 Flash Attention。--reasoning-preserve在聊天模板中保留模型的推理输出。--jinja使用 GGUF 中的 Jinja 聊天模板整理消息和工具定义。当前版本默认已经启用,命令中保留它只是为了让配置意图更明确。--load-mode mlock把模型页锁在 RAM 中,避免交换到磁盘。旧写法--mlock --no-mmap已被标记为弃用。--threads $(nproc)使用系统可见的逻辑处理器数量处理剩余 CPU 工作。
最新版会尝试根据显存自动调整部分参数,但显式设置 -ngl 100 后不会再替你修改 GPU layers。第一次报错时不要同时修改上下文、KV Cache、批大小和线程数,否则很难判断是哪项参数产生了影响。
--jinja 依赖 GGUF 带有正确的工具调用模板。模型把工具调用当普通文本输出时,先检查启动日志中的 chat template。GGUF 没有合适模板时,应更换带完整模板的 Instruct GGUF,或者用 --chat-template-file 显式指定模板。单独增加 --jinja 不能补上模型本身缺少的工具能力。
纯文本编码场景
不需要向模型发送图片时,可以不加载视觉投影文件:
./build/bin/llama-server --metrics --alias Qwen3.6-27B \
--model "$MODELSCOPE_CACHE/models/unsloth--Qwen3.6-27B-GGUF/snapshots/master/Qwen3.6-27B-Q4_K_M.gguf" \
--no-mmproj --reasoning-preserve --jinja \
--host 0.0.0.0 --port 30104 --ctx-size 262144 -ngl 100 --fit off \
--cache-type-k q4_0 --cache-type-v q4_0 --parallel 1 \
--batch-size 4096 --ubatch-size 1024 --flash-attn on \
--load-mode mlock --threads $(nproc)这里保留显式的 -ngl 100,同时用 --fit off 关闭显存参数自动适配。不加载 mmproj 后,视觉输入不可用。
用 tmux 保持进程运行
sudo apt install tmux
tmux
tmux attach -t 0进入 tmux 后运行 llama-server。离开时按 Ctrl+B,再按 D 分离会话,模型会继续运行。不要在会话里执行 exit,否则进程也会结束。
检查 llama-server 接口
查询时使用与服务端相同的 API Key:
curl --noproxy '*' -s http://localhost:30104/health
curl --noproxy '*' -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/v1/models
curl --noproxy '*' -N -H "Authorization: Bearer $LLAMA_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"Qwen3.6-27B","input":"只回复 OK","max_output_tokens":256,"stream":true}' \
http://localhost:30104/v1/responses
curl --noproxy '*' -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/metrics
curl --noproxy '*' -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/props
curl --noproxy '*' -s -H "Authorization: Bearer $LLAMA_API_KEY" http://localhost:30104/slots--noproxy '*' 可避免本机请求被 HTTP_PROXY 或 HTTPS_PROXY 转发。实测中,未绕过代理的 127.0.0.1 请求曾返回 502,而服务本身没有故障。/health 用来确认服务在线,/v1/models 返回已加载模型。性能和运行状态分别看 /metrics、/props 与 /slots。
本次检查中,/health 返回 {"status":"ok"}。/v1/models 的模型 ID 是 Qwen3.6-27B,上下文为 262144,量化类型显示为 Q4_K - Medium,并带有 multimodal 能力。
max_output_tokens 同时包含推理与最终答案。设为 64 时,Qwen3.6-27B 的配额被 reasoning 用完,只返回推理块;提高到 256 后才得到最终的 OK。这不是接口中断,而是输出预算太小。
Claude Code 配置
Claude Code 模型配置文档说明了模型字段的用途。用户级配置文件是 ~/.claude/settings.json;llama-server 使用的模型别名是 Qwen3.6-27B,客户端也沿用这个名称。
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"ANTHROPIC_AUTH_TOKEN": "与 LLAMA_API_KEY 相同",
"ANTHROPIC_BASE_URL": "http://localhost:30104",
"ANTHROPIC_MODEL": "Qwen3.6-27B",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "Qwen3.6-27B",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "Qwen3.6-27B",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "Qwen3.6-27B",
"CLAUDE_CODE_SUBAGENT_MODEL": "Qwen3.6-27B",
"API_TIMEOUT_MS": "3000000",
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "200000",
"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "90"
},
"theme": "auto",
"statusLine": {
"type": "command",
"command": "node -e \"let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const j=JSON.parse(s),c=j.context_window||{},i=c.total_input_tokens||0,o=c.total_output_tokens||0,m=(j.model&&(j.model.display_name||j.model.id))||'model';console.log(m+' | Claude '+(i+o)+'/'+(c.context_window_size||200000)+' ('+(c.used_percentage||0)+'%) | Server 262144')})\"",
"padding": 1
}
}ANTHROPIC_AUTH_TOKEN 填写 llama-server 使用的 API Key,不加 Bearer 前缀。Claude Code 环境变量文档中没有 ANTHROPIC_DEFAULT_FABLE_MODEL,不要继续从旧配置复制。这里让 Claude Code 按 200000 token 管理本地模型,并在约 180000 token 时开始自动压缩,给服务端的 262144 上下文留出生成和工具结果空间。
Claude Code 2.1.239 会把不在内置清单中的 Qwen3.6-27B 标为 unrecognized_model。这条提示不代表请求失败;显式设置 CLAUDE_CODE_MAX_CONTEXT_TOKENS 后,客户端会按指定窗口管理会话。
先测试普通回复:
claude --model Qwen3.6-27B -p --max-turns 1 --no-session-persistence "只回复 OK"再测试 Bash 工具调用:
claude --model Qwen3.6-27B -p --max-turns 2 --no-session-persistence \
--permission-mode bypassPermissions \
"必须使用 Bash 工具执行 git rev-parse --show-toplevel,然后只输出命令结果"我的实际测试中,第一条返回 OK,第二条返回了仓库根目录。普通回复只证明模型能生成文本,第二项才证明 Claude Code 能发起工具调用并接收结果。bypassPermissions 只用于这个受控的只读测试,不要把它设成日常默认值。状态栏和上下文窗口的进一步说明见Claude Code 接入本地 llama.cpp。
Codex 配置
按照 Codex 配置参考,Codex 可以通过自定义 model provider 连接 llama-server 的 /v1/responses。为了不影响默认配置,在 $CODEX_HOME 中新建独立 profile:Linux 使用 ~/.codex/local-qwen.config.toml,Windows 使用 %USERPROFILE%\.codex\local-qwen.config.toml。
启动 Codex 前,在当前终端设置与 llama-server 相同的 API Key:
export LLAMA_API_KEY='在这里填入你的实际 key'profile 内容如下:
model = "Qwen3.6-27B"
model_provider = "llama_cpp_qwen"
model_context_window = 262144
model_auto_compact_token_limit = 200000
model_reasoning_summary = "none"
approval_policy = "never"
sandbox_mode = "workspace-write"
web_search = "disabled"
[features]
apps = false
plugins = false
[model_providers.llama_cpp_qwen]
name = "llama.cpp Qwen3.6-27B"
base_url = "http://localhost:30104/v1"
env_key = "LLAMA_API_KEY"
wire_api = "responses"
request_max_retries = 2
stream_max_retries = 5
stream_idle_timeout_ms = 3000000
[sandbox_workspace_write]
network_access = true
[tui]
status_line = ["model-with-reasoning", "context-remaining", "current-dir", "git-branch"]启动交互式会话:
codex --profile local-qwen新会话中先输入 /status,核对模型、审批策略、沙箱模式和可写目录。然后分别验证普通回复与真实终端调用:
codex exec --profile local-qwen "只回复 OK"
codex exec --profile local-qwen "必须调用终端执行 git rev-parse --show-toplevel,然后原样输出命令结果"Codex CLI 0.149.0 能读取这份 profile,并通过 /v1/responses 返回普通文本。真实终端调用也成功执行了 git rev-parse --show-toplevel。公开配置使用 env_key,实际 Token 不写进 profile。
当前版本还有两条边界需要保留:
- Codex 找不到
Qwen3.6-27B的内置模型元数据时,会使用 fallback metadata。虽然 profile 已显式给出上下文窗口,客户端仍会提示这可能影响性能或行为。 - llama-server 可能记录
unsupported Responses tool type 'namespace' skipped。关闭 Web Search 后不再发送web_search,但namespace警告仍可能出现。已经验证exec_command能执行,不能据此推断所有 Codex 工具都兼容。
tool_search 在 0.149.0 中已被移除,standalone_web_search 仍不是稳定功能,所以不再写入 profile。更完整的排错过程见Codex 接入本地 llama.cpp。
我在 Codex 桌面应用内嵌套启动 CLI 时,子进程被强制成 read-only,无法验证独立终端中的 workspace-write。为了只检查工具协议,我对无副作用的 git rev-parse 单独使用过无沙箱模式。日常使用仍应保留 profile 中的 workspace-write,并在普通终端通过 /status 和实际命令确认沙箱状态。