额外加一张7900xtx用llama跑qwen3.8 27B Q4和Q4非审查的经验总结
-
RX 7900 XTX + llama.cpp(HIP)+ Qwen3.8-27B:单卡单服务三端共享完整搭建指南(DSH / Obsidian Copilot / Codex 桌面版)
本文目标:完整记录一台 7900 XTX 专卡跑 llama.cpp 的全部配置。发到论坛存档,重装系统后把本文交给 AI,即可按步骤 100% 恢复到当前状态。
最终效果:一个 llama-server 实例(8080 端口)同时服务三个客户端;启动时可选模型、上下文长度、思考模式开/关;Obsidian 文档总结秒出正文。- 机器:i5-13600KF / 64GB RAM / 技嘉 Z790M AORUS ELITE AX / Windows 11 26H1
- 显卡分工:RX 7900 XTX(24GB)只跑 llama;RTX 4060 Ti 负责显示输出和其余一切
- 实测性能(128K 上下文):预填充 ~800-900 tok/s,生成 ~34 tok/s(降压超频后约 270-290W)
1. GPU 独占逻辑(7900 XTX 只跑 llama 的实现原理)
核心思路:靠软件构建保证独占,而不是靠参数过滤。
- 使用 HIP/ROCm 版 llama.cpp(
C:\llama\llama-hip\llama-server.exe,commit be4a6a6,Clang 23 构建对应官方 b10617 时代)。- HIP build 只枚举 AMD GPU(ROCm 只认到 7900 XTX 一张卡),结构上不可能调用 4060 Ti,不需要任何
--device过滤参数 - NVIDIA 4060 Ti 照常跑桌面、CUDA 应用,互不干扰
- HIP build 只枚举 AMD GPU(ROCm 只认到 7900 XTX 一张卡),结构上不可能调用 4060 Ti,不需要任何
- Vulkan 构建已弃用:
C:\llama根目录还有一份 Vulkan 版(b10617 zip 解压),已不用。关键坑:必须把两个位置的ggml-vulkan.dll改名为.bak,否则运行时 Vulkan 后端会抢跑、与 ROCm 冲突:C:\llama\ggml-vulkan.dll→.bakC:\llama\llama-hip\ggml-vulkan.dll→.bak(start.bat 启动时会自动检查改名)
- 7900 XTX 降压超频(AMD Adrenalin 手动设置,非脚本,重装系统后需重新设置):
- 功耗限制 -10%、电压 -65mV
- 效果:推理功耗约 270-290W(原版可达 350W+),性能损失可忽略
重装系统时 HIP 版获取途径:llama.cpp GitHub Releases 下载
llama-bXXXX-bin-win-hip-x64.zip(需与 ROCm 驱动版本匹配),解压到C:\llama\llama-hip\。2. 目录结构
C:\llama\ ├─ start.bat ← 总启动脚本(选模型/上下文/思考开关) ├─ qwen38_tolerant.jinja ← 容错聊天模板(三端兼容的关键) ├─ restart_llama.ps1 ← AI 无人值守重启(固定 UD 模型+128K+关思考) ├─ codex-launcher.ps1 ← Codex 对齐+启动逻辑 ├─ server.log ← 服务日志(--log-file 输出) ├─ ggml-vulkan.dll.bak ← 已封印的 Vulkan 后端 └─ llama-hip\ ← HIP 版 llama.cpp(llama-server.exe 在这里) C:\model\ ├─ Qwen3.8-27B-Q4_K_M.gguf └─ Qwen3.8-27B-UD-Q4_K_XL.gguf 桌面快捷方式: ├─ 启动llama.lnk → C:\llama\start.bat(工作目录 C:\llama) └─ Codex启动.bat → 调 codex-launcher.ps1模型与 ID 对应关系(铁律:llama-server 一次只加载一个 gguf,且完全忽略请求里的 model 字段,所以各客户端选的模型必须和当前加载的 gguf 一致,否则 token 统计错位):
gguf 文件 dsh slug Codex slug Copilot slug Qwen3.8-27B-Q4_K_M.gguf qwen3.8-27b-q4kmqwen3.8-27b-q4kmqwen3.8-27b-q4kmQwen3.8-27B-UD-Q4_K_XL.gguf qwen3.8-27b-ud-q4kxlqwen3.8-27b-ud-q4kxlqwen3.8-27b-ud两个模型 tokenizer 完全相同 →
</think>token ID(248068)对两个模型都有效,思考开关对两个都适用。注意:模板和 token ID 是 Qwen3.8 系列专属,换成其他家族模型需另配。3. start.bat(总启动脚本,完整内容)
启动流程:自动封印 Vulkan dll → 选模型 → 选上下文 → 选思考模式 → 启动服务。
@echo off setlocal enabledelayedexpansion :: ============================================================ :: GPU: use HIP backend for AMD RX 7900 XTX ONLY :: HIP build only sees the 7900 XTX (ROCm0), never the 4060 Ti :: ggml-vulkan.dll renamed to .bak to prevent Vulkan interference :: ============================================================ :: Ensure ggml-vulkan.dll is disabled (must be .bak or absent) set "VULKAN_DLL=C:\llama\ggml-vulkan.dll" if exist "%VULKAN_DLL%" ( echo [WARNING] ggml-vulkan.dll found, renaming to prevent Vulkan conflict... rename "%VULKAN_DLL%" "ggml-vulkan.dll.bak" ) :: Also check HIP directory set "VULKAN_DLL2=C:\llama\llama-hip\ggml-vulkan.dll" if exist "%VULKAN_DLL2%" ( echo [WARNING] ggml-vulkan.dll found in HIP dir, renaming... rename "%VULKAN_DLL2%" "ggml-vulkan.dll.bak" ) :: HIP build path (gfx1100 = RDNA3, supports RX 7900 XTX) set SERVER_DIR=C:\llama\llama-hip set SERVER_EXE=%SERVER_DIR%\llama-server.exe set MODEL_DIR=C:\model echo ============================================ echo Select model to load: echo ============================================ set count=0 for %%f in ("%MODEL_DIR%\*.gguf") do ( set /a count+=1 echo !count!. %%~nxf ) if %count%==0 ( echo Error: no .gguf files in %MODEL_DIR% pause exit /b ) set /p choice="Model number (1-%count%): " set selected= set idx=0 for %%f in ("%MODEL_DIR%\*.gguf") do ( set /a idx+=1 if !idx!==%choice% set selected=%%f ) if "%selected%"=="" ( echo Invalid input pause exit /b ) echo Selected: %selected% echo. echo ============================================ echo Select context size: echo 1. 8K (8192) echo 2. 16K (16384) echo 3. 32K (32768) echo 4. 64K (65536) echo 5. 128K (131072) echo ============================================ set /p ctx_choice="Choice (1-5): " set ctx=8192 if "%ctx_choice%"=="1" set ctx=8192 if "%ctx_choice%"=="2" set ctx=16384 if "%ctx_choice%"=="3" set ctx=32768 if "%ctx_choice%"=="4" set ctx=65536 if "%ctx_choice%"=="5" set ctx=131072 echo Context: %ctx% tokens echo. echo ============================================ echo Thinking mode: echo 1. Off (default) - summaries output instantly echo 2. On - deep reasoning, slower summaries echo ============================================ set /p think_choice="Choice (1-2, Enter=1): " set think_args=--chat-template-kwargs "{\"enable_thinking\":false}" --logit-bias 248068-100 set think_label=OFF - no reasoning, fast summary if "%think_choice%"=="2" ( set think_args=--chat-template-kwargs "{\"enable_thinking\":true}" set think_label=ON - deep reasoning, slower ) echo. echo ============================================ echo Starting llama-server (HIP build)... echo Model : %selected% echo Ctx : %ctx% echo Think : %think_label% echo GPU : AMD RX 7900 XTX (HIP/ROCm, 24GB) echo FA : ON (Flash Attention) echo NGL : 99 (all layers to GPU) echo KV : q4_0 (half size, prevents VRAM thrashing) echo Par : 1 (single slot, max speed for single app) echo API : http://localhost:8080 echo Press Ctrl+C to stop echo ============================================ "%SERVER_EXE%" -m "%selected%" -c %ctx% -ngl 99 -n -1 -fa on --no-mmap ^ --cache-type-k q4_0 --cache-type-v q4_0 ^ --parallel 1 -b 512 -ub 256 ^ --chat-template-file C:\llama\qwen38_tolerant.jinja ^ %think_args% ^ --log-file C:\llama\server.log ^ --host 0.0.0.0 --port 8080 pause参数逐条理由(都是验证过的正确做法,勿改):
参数 理由 -ngl 99全部层进 7900 XTX -n -1服务端不限输出 token 数,由客户端决定 -fa onFlash Attention,省显存带宽 --no-mmap权重全进内存,避免缺页 --cache-type-k/v q4_0KV 缓存 4bit 量化:32K 上下文只占 4.3GB(q8_0 要 8.6GB),避免 24GB 卡在长文档时显存溢出换页 --parallel 1单槽位,单客户端场景最快 -b 512 -ub 256针对 7900 XTX 调优的批大小 --chat-template-file容错模板(见第 4 节) %think_args%思考模式参数(见第 5 节) --log-file日志必须写文件。绝对不要用 --verbose:终端高频输出会干扰 HTTP 流式响应,造成客户端 TRANSPORT 错误(血泪坑)--host 0.0.0.0 --port 8080三端统一入口 上下文怎么选:日常任务实际用量 ~20K,64K 够用;128K 可选(加载 1-2 分钟)。
4. qwen38_tolerant.jinja(容错聊天模板,完整内容)
相对模型内置模板的 3 处修改(重做模板时照此改):
- 合并多条 system 消息:Codex 每次请求带 3 条
developer消息,原版模板直接 400("System message must be at the beginning")。本模板把所有 system/developer 内容合并成一个merged_sys块放开头 - 思考默认值反转:开启条件从
undefined or true改为defined and true→ 不传参默认关思考(配合 llama-server 会注入enable_thinking=true的行为,必须显式压制) - 关思考时输出空思考块:
<think>\n\n</think>\n\n,模型直接续写正文
{%- set image_count = namespace(value=0) %} {%- set video_count = namespace(value=0) %} {%- macro render_content(content, do_vision_count, is_system_content=false) %} {%- if content is string %} {{- content }} {%- elif content is iterable and content is not mapping %} {%- for item in content %} {%- if 'image' in item or 'image_url' in item or item.type == 'image' %} {%- if is_system_content %} {{- raise_exception('System message cannot contain images.') }} {%- endif %} {%- if do_vision_count %} {%- set image_count.value = image_count.value + 1 %} {%- endif %} {%- if add_vision_id %} {{- 'Picture ' ~ image_count.value ~ ': ' }} {%- endif %} {{- '<|vision_start|><|image_pad|><|vision_end|>' }} {%- elif 'video' in item or item.type == 'video' %} {%- if is_system_content %} {{- raise_exception('System message cannot contain videos.') }} {%- endif %} {%- if do_vision_count %} {%- set video_count.value = video_count.value + 1 %} {%- endif %} {%- if add_vision_id %} {{- 'Video ' ~ video_count.value ~ ': ' }} {%- endif %} {{- '<|vision_start|><|video_pad|><|vision_end|>' }} {%- elif 'text' in item %} {{- item.text }} {%- else %} {{- raise_exception('Unexpected item type in content.') }} {%- endif %} {%- endfor %} {%- elif content is none or content is undefined %} {{- '' }} {%- else %} {{- raise_exception('Unexpected content type.') }} {%- endif %} {%- endmacro %} {%- if not messages %} {{- raise_exception('No messages provided.') }} {%- endif %} {%- set sysacc = namespace(text='') %} {%- for message in messages %} {%- if message.role == 'system' %} {%- set part = render_content(message.content, false, true)|trim %} {%- if part %} {%- if sysacc.text %} {%- set sysacc.text = sysacc.text + '\n\n' + part %} {%- else %} {%- set sysacc.text = part %} {%- endif %} {%- endif %} {%- endif %} {%- endfor %} {%- set merged_sys = sysacc.text %} {%- set reasoning_instructions = '' %} {%- if enable_thinking is defined and enable_thinking is true %} {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %} {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %} {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }} {%- endif %} {%- if resolved_reasoning_effort == 'xhigh' %} {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %} {%- elif resolved_reasoning_effort == 'low' %} {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %} {%- endif %} {%- endif %} {%- if tools and tools is iterable and tools is not mapping %} {{- '<|im_start|>system\n' }} {%- if reasoning_instructions %} {{- reasoning_instructions + '\n\n' }} {%- endif %} {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }} {%- for tool in tools %} {{- "\n" }} {{- tool | tojson }} {%- endfor %} {{- "\n</tools>" }} {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }} {%- if merged_sys %} {{- '\n\n' + merged_sys }} {%- endif %} {{- '<|im_end|>\n' }} {%- else %} {%- if merged_sys %} {{- '<|im_start|>system\n' + (reasoning_instructions + '\n\n' if reasoning_instructions else '') + merged_sys + '<|im_end|>\n' }} {%- elif reasoning_instructions %} {{- '<|im_start|>system\n' + reasoning_instructions + '<|im_end|>\n' }} {%- endif %} {%- endif %} {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %} {%- for message in messages[::-1] %} {%- set index = (messages|length - 1) - loop.index0 %} {%- if ns.multi_step_tool and message.role == "user" %} {%- set content = render_content(message.content, false)|trim %} {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %} {%- set ns.multi_step_tool = false %} {%- set ns.last_query_index = index %} {%- endif %} {%- endif %} {%- endfor %} {%- if ns.multi_step_tool %} {{- raise_exception('No user query found in messages.') }} {%- endif %} {%- for message in messages %} {%- set content = render_content(message.content, true)|trim %} {%- if message.role == "system" %} {%- elif message.role == "user" %} {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }} {%- elif message.role == "assistant" %} {%- set reasoning_content = '' %} {%- if message.reasoning_content is string %} {%- set reasoning_content = message.reasoning_content %} {%- endif %} {%- set reasoning_content = reasoning_content|trim %} {%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index %} {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }} {%- else %} {{- '<|im_start|>' + message.role + '\n' + content }} {%- endif %} {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %} {%- for tool_call in message.tool_calls %} {%- if tool_call.function is defined %} {%- set tool_call = tool_call.function %} {%- endif %} {%- if loop.first %} {%- if content|trim %} {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }} {%- else %} {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }} {%- endif %} {%- else %} {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }} {%- endif %} {%- if tool_call.arguments is defined and tool_call.arguments != '' %} {%- for args_name, args_value in tool_call.arguments|items %} {{- '<parameter=' + args_name + '>\n' }} {%- set args_value = args_value | string if args_value is string else args_value | tojson | safe %} {{- args_value }} {{- '\n</parameter>\n' }} {%- endfor %} {%- endif %} {{- '</function>\n</tool_call>' }} {%- endfor %} {%- endif %} {{- '<|im_end|>\n' }} {%- elif message.role == "tool" %} {%- if loop.previtem and loop.previtem.role != "tool" %} {{- '<|im_start|>user' }} {%- endif %} {{- '\n<tool_response>\n' }} {{- content }} {{- '\n</tool_response>' }} {%- if not loop.last and loop.nextitem.role != "tool" %} {{- '<|im_end|>\n' }} {%- elif loop.last %} {{- '<|im_end|>\n' }} {%- endif %} {%- else %} {{- raise_exception('Unexpected message role.') }} {%- endif %} {%- endfor %} {%- if add_generation_prompt %} {{- '<|im_start|>assistant\n' }} {%- if enable_thinking is not defined or enable_thinking is false %} {{- '<think>\n\n</think>\n\n' }} {%- else %} {{- '<think>\n' }} {%- endif %} {%- endif %}对单条 system 的普通请求(dsh/Obsidian),本模板渲染结果与原版完全一致,已实测无影响。
5. 思考模式开关(原理 + 各端表现)
为什么需要:Qwen3.8 默认开思考,会在隐藏通道(reasoning_content)先生成大量思考内容再输出正文。实测 Obsidian 总结时思考多达 1.5万~4.4万 token,表现为"点总结后长时间空白,最后才一次性吐正文"。
关思考 = 双保险:
--chat-template-kwargs "{\"enable_thinking\":false}"— 模板层显式关闭(压制 llama-server 自动注入的 true)--logit-bias 248068-100— 物理封禁</think>token(Qwen3.8 词表 ID 248068),模型想思考也吐不出标记
修复前后实测(模拟 Obsidian 总结请求):
状态 reasoning_chars 说明 修复前 142+ 先思考再出正文,慢 修复后 0 直接出正文,133-194 token 秒回 各端表现:
场景 dsh Obsidian Codex 思考关(默认) 不思考,直接出结果 不思考,总结秒出 自带思考参数,行为待实测(见第 9 节) 思考开 思考 思考(总结变慢) 思考 排障要点:
--logit-bias格式是连字符248068-100,写成248068:-100报错- PowerShell 脚本传这两个参数必须用单字符串(见第 6 节),用数组会拆碎 JSON 引号,参数静默失效(服务照常启动,极难察觉)
- 批处理坑:
if块内set的值里不要用括号(set x=ON (reasoning)的尾部)会被解析器吞掉)
6. restart_llama.ps1(AI 无人值守重启,完整内容)
AI 助手用来把服务恢复到"UD 模型 + 128K + 关思考"状态。通过状态文件轮询,避免长 HTTP 调用被终端工具杀掉。
$ErrorActionPreference = 'Stop' Set-Content C:\llama\restart_status.txt 'step1-killing' Get-Process llama-server -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep -Seconds 3 Set-Content C:\llama\restart_status.txt 'step2-starting' $server = 'C:\llama\llama-hip\llama-server.exe' $argline = '-m C:\model\Qwen3.8-27B-UD-Q4_K_XL.gguf -c 131072 -ngl 99 -n -1 -fa on --no-mmap ' + '--cache-type-k q4_0 --cache-type-v q4_0 --parallel 1 -b 512 -ub 256 ' + '--chat-template-file C:\llama\qwen38_tolerant.jinja ' + '--chat-template-kwargs "{\"enable_thinking\":false}" ' + '--logit-bias 248068-100 ' + '--log-file C:\llama\server.log --host 0.0.0.0 --port 8080' Start-Process -FilePath $server -ArgumentList $argline -WindowStyle Minimized Set-Content C:\llama\restart_status.txt 'step3-waiting' $ok = $false foreach ($i in 1..60) { Start-Sleep -Seconds 5 try { $r = Invoke-RestMethod 'http://127.0.0.1:8080/props' -TimeoutSec 3 if ($r.model_path -like '*UD-Q4_K_XL*') { $ok = $true; break } } catch {} } if ($ok) { Set-Content C:\llama\restart_status.txt 'done-ready' } else { Set-Content C:\llama\restart_status.txt 'timeout' }核心教训:
Start-Process -ArgumentList用数组传参会把"{\"enable_thinking\":false}"的引号拆碎 → 参数静默丢失。必须用单字符串$argline。7. Codex 桌面版对接(codex-launcher.ps1 + Codex启动.bat)
Codex 桌面版为 MSIX 包
OpenAI.Codex(26.820.9563.0),CLI 版已卸载(释放 385MB C 盘),但~/.codex配置目录必须保留(桌面版依赖)。Codex启动.bat(桌面):
@echo off chcp 65001 >nul :: Codex launcher: reuse the single llama-server on :8080 (no second server). :: Model switching happens in the llama shortcut; this only aligns + opens Codex. powershell -NoProfile -ExecutionPolicy Bypass -File "C:\llama\codex-launcher.ps1" if errorlevel 1 pausecodex-launcher.ps1(完整内容):
# Codex launcher: align Codex with the single shared llama-server on :8080. # - Does NOT create a second server. It reuses the one started by start.bat. # - If the running server lacks the Codex-compat chat template, it restarts # THE SAME server with the SAME model & context (adds --chat-template-file). # - Syncs config.toml "model" to whatever gguf is currently loaded. # - Model switching is done via the desktop llama shortcut (start.bat). $ErrorActionPreference = 'Stop' $server = 'C:\llama\llama-hip\llama-server.exe' $template = 'C:\llama\qwen38_tolerant.jinja' $log = 'C:\llama\server.log' $codexDir = 'C:\test' $cfg = Join-Path $env:USERPROFILE '.codex\config.toml' function Wait-Ready([int]$maxSec) { $deadline = (Get-Date).AddSeconds($maxSec) while ((Get-Date) -lt $deadline) { try { $r = Invoke-WebRequest -UseBasicParsing 'http://127.0.0.1:8080/health' -TimeoutSec 5 if ($r.StatusCode -eq 200) { return $true } } catch { } Start-Sleep -Seconds 5 } return $false } # ── 1) probe the running server ────────────────────────────────────────────── $modelPath = $null $ctx = 65536 $fixed = $false try { $mm = Invoke-RestMethod 'http://127.0.0.1:8080/v1/models' -TimeoutSec 3 $modelPath = $mm.data[0].id if ($mm.data[0].meta -and $mm.data[0].meta.n_ctx) { $ctx = [int]$mm.data[0].meta.n_ctx } $p = Invoke-RestMethod 'http://127.0.0.1:8080/props' -TimeoutSec 3 if ($p.chat_template -match 'merged_sys') { $fixed = $true } if ($p.default_generation_settings -and $p.default_generation_settings.n_ctx) { $ctx = [int]$p.default_generation_settings.n_ctx } } catch { } if (-not $modelPath) { Write-Host '' Write-Host '[X] llama-server 未运行 (8080)。' Write-Host ' 请先双击桌面「启动llama」选择模型启动,然后再运行「Codex启动」。' exit 1 } Write-Host "llama-server 在线: $modelPath (ctx=$ctx)" # ── 2) ensure Codex-compat template (restart SAME server if missing) ───────── if (-not $fixed) { Write-Host "当前服务缺少 Codex 兼容模板,用同一模型/上下文($ctx)重启(约1-2分钟)..." Get-Process llama-server -ErrorAction SilentlyContinue | Stop-Process -Force Start-Sleep -Seconds 3 Start-Process -WindowStyle Minimized -FilePath $server -ArgumentList @( '-m', $modelPath, '-c', "$ctx", '-ngl', '99', '-n', '-1', '-fa', 'on', '--no-mmap', '--cache-type-k', 'q4_0', '--cache-type-v', 'q4_0', '--parallel', '1', '-b', '512', '-ub', '256', '--chat-template-file', $template, '--log-file', $log, '--host', '0.0.0.0', '--port', '8080' ) if (-not (Wait-Ready 300)) { Write-Host '[X] llama-server 重启超时,请手动检查。'; exit 1 } Write-Host 'llama-server 已就绪(带 Codex 兼容模板)。' } else { Write-Host '服务已带 Codex 兼容模板,直接复用(不重启)。' } # ── 3) sync config.toml model = currently loaded gguf ──────────────────────── $slug = ([IO.Path]::GetFileNameWithoutExtension($modelPath)).ToLower().Replace('_', '') $t = [IO.File]::ReadAllText($cfg) $t = [regex]::Replace($t, '(?m)^model\s*=\s*".*?"', ('model = "' + $slug + '"')) [IO.File]::WriteAllText($cfg, $t, (New-Object Text.UTF8Encoding($false))) Write-Host "Codex 模型已对齐当前加载的模型: $slug" # ── 4) open Codex desktop app (fallback: CLI in terminal) ──────────────────── $app = Get-AppxPackage -Name 'OpenAI.Codex' -ErrorAction SilentlyContinue if ($app) { Write-Host '打开 Codex 桌面版...' Start-Process ('shell:AppsFolder\' + $app.PackageFamilyName + '!App') } else { Write-Host '未找到 Codex 桌面版 (OpenAI.Codex)。CLI 版已于 2026-08-28 卸载。' Pause } exit 0~/.codex/config.toml 关键行(其余为默认/插件配置,首次启动应用内登录一次):
model = "qwen3.8-27b-q4km" # 「Codex启动」会自动同步为当前加载的 gguf model_provider = "llama-local" preferred_auth_method = "apikey" forced_login_method = "api" model_catalog_json = 'C:\Users\Administrator\.codex\model_catalog.json' model_reasoning_effort = "low" [model_providers.llama-local] name = "llama-local (本地 llama-server :8080)" base_url = "http://127.0.0.1:8080/v1" wire_api = "responses" # 必须!Codex 0.150+ 已删除 chat 通道 experimental_bearer_token = "llama-local"model_catalog.json中注册上面两个本地 slug(照抄表格里的 id/displayName 即可)。日志里
unsupported Responses tool type 'custom'/'namespace'/'web_search' skipped属正常(llama.cpp 跳过不支持的工具类型,不影响使用)。8. dsh 与 Obsidian Copilot 配置
dsh(
C:\Users\Administrator\.dsh\settings.yaml,热重载,改完不用重启)llm-pi-ai: providers: llama-local: displayName: llama-server (本地 Qwen3.8-27B) api: openai-completions baseURL: http://127.0.0.1:8080/v1 apiKeyEnv: LLAMA_LOCAL_API_KEY timeoutMs: 600000 # 本地推理慢,放宽超时防首 token 前被判超时 streamIdleTimeoutMs: 600000 defaultContextWindow: 65536 defaultMaxTokens: 16384 compat: # 关掉 llama-server 读不懂的 OpenAI 新字段 supportsStore: false supportsDeveloperRole: false supportsReasoningEffort: false supportsStrictMode: false supportsUsageInStreaming: true maxTokensField: max_tokens models: - id: qwen3.8-27b-q4km name: Qwen3.8-27B Q4_K_M (本地) contextWindow: 65536 maxTokens: 16384 - id: qwen3.8-27b-ud-q4kxl name: Qwen3.8-27B UD-Q4_K_XL (本地) contextWindow: 65536 maxTokens: 16384 agent-default-model: provider: llama-local model: qwen3.8-27b-q4km # 启动 UD 时在 /model 选择器里切换.credentials.yaml(占位 key,llama-server 不校验鉴权):version: 1 refs: LLAMA_LOCAL_API_KEY: sk-no-key-neededObsidian Copilot(
E:\ObsidianVault\.obsidian\plugins\copilot\data.json要点)- provider:
openai-compatible,baseUrl: http://localhost:8080/v1,displayName "Llama",requiresApiKey true(占位 key 存 keychain) - 模型只保留两个本地:
qwen3.8-27b-ud、qwen3.8-27b-q4km(activeModels / configuredModels / backends.chat.enabledModels / backends.opencode.enabledModels 四处都要有) "contextTurns": 0(关键!总结不携带历史对话)"activeBackend": "chat"(绝不用 opencode 后端:会自动累积历史,上下文越大越慢,O(n²) 卡死)"temperature": 0.1,"autoCompactThreshold": 128000- 总结长度限制(500 字 / 3-5 要点)写在其系统提示词里
9. 踩坑清单(按血泪程度排序,每条都注明正确做法)
--verbose毁掉流式响应:终端高频输出干扰 HTTP 流,客户端报 TRANSPORT 错误、任务失败。
正确做法:--log-file C:\llama\server.log- Vulkan 后端抢跑:
ggml-vulkan.dll存在时与 ROCm 冲突。
改名 .bak(start.bat 启动时自动检查) - llama-server 注入 enable_thinking=true:即使模板默认关思考,服务端也会注入 true 覆盖。
必须 --chat-template-kwargs "{\"enable_thinking\":false}"显式压制 - PowerShell 数组传参拆碎 JSON 引号:
--chat-template-kwargs静默失效,服务照常启动。
用单字符串 $argline - logit-bias 用冒号报错:
格式为 248068-100(连字符) - 原版模板拒绝多条 system 消息:Codex 每请求 3 条 developer 消息 → 400 "System message must be at the beginning"。
容错模板合并 - llama.cpp /v1/responses 转换器 bug(GitHub issue #26394 / PR #27751):特定 input 形状报 400 "Cannot determine type of 'item'"。
真实 Codex 载荷能通过;别用手工合成的最小化请求下结论,要看真实客户端流量 - KV 缓存爆显存:q8_0 KV 在 32K 就要 8.6GB,27B 模型 + 长文档必翻车。
q4_0 减半(4.3GB@32K) - 同时只能跑一个 llama-server:8080 端口 + 显存双重冲突,残留实例曾吃 6.2GB 内存。
换模型流程:先关窗口 → 再「启动llama」重选;排障先查进程数 - model 字段被忽略:llama-server 只认加载的 gguf。
各端 UI 选的模型必须与加载的 gguf 对齐(换模型后 dsh 用 /model 选,Codex 双击「Codex启动」自动同步,Copilot 设置里选) - 批处理括号吞字:
if块内set值含括号会被解析器吃掉尾部)。
值里用短横线代替括号 - opencode 后端 O(n²) 卡顿:Obsidian Copilot 用 opencode 后端会自动累积历史。
用 chat 后端 + contextTurns: 0 - 隐藏思考拖慢总结:总结慢先查 reasoning_content 长度,别误判为历史记录问题(有
contextTurns: 0在,历史根本不参与) - MTP 投机解码不适合长文档总结:
--spec-type draft-mtp草稿接受率低、频繁回溯,反而更慢。
不用 - dsh 输出 token 限制报错:maxTokens 太小。
settings.yaml 热重载改 maxTokens: 16384、contextWindow: 65536,无需重启 - AI 助手操作教训(给恢复配置的 AI 看):
- 超过 ~20 秒的 HTTP 调用会被终端工具杀掉 → 用分离进程 + 轮询状态文件(restart_llama.ps1 模式)
- PowerShell 输出捕获偶发丢失 → 关键结论以文件落盘为准,勿轻信"无输出"
- 长流式响应要限制读取窗口,否则流内 400 事件会被误判为成功
10. 健康检查与验证
# 服务是否在线 + 模板是否生效(chat_template 含 merged_sys = 容错模板已加载) Invoke-RestMethod http://127.0.0.1:8080/props # 当前加载的模型 Invoke-RestMethod http://127.0.0.1:8080/v1/models # 日志 Get-Content C:\llama\server.log -Tail 50思考开关验证:Obsidian 里发起文档总结 → 正文应立即流式输出(关思考),或先输出思考再出正文(开思考)。
11. 重装系统恢复清单(按顺序执行,AI 可照此自动完成)
- 装 AMD Adrenalin(驱动),7900 XTX 调优:功耗 -10%、电压 -65mV
- 装 llama.cpp HIP 版到
C:\llama\llama-hip\(Releases 的 win-hip-x64 包);若有ggml-vulkan.dll改名.bak - 模型放
C:\model\(两个 Qwen3.8-27B gguf) - 按本文第 3/4/6/7 节创建
start.bat、qwen38_tolerant.jinja、restart_llama.ps1、codex-launcher.ps1 - 桌面建快捷方式:
启动llama.lnk→C:\llama\start.bat(工作目录C:\llama);Codex启动.bat按第 7 节 - 装 Codex 桌面版(MSIX,首次启动应用内登录),配置
~/.codex/config.toml关键行 +model_catalog.json注册两个本地 slug - dsh:
settings.yaml(第 8 节)+.credentials.yaml占位 key - Obsidian Copilot:
data.json按第 8 节要点配置 - 双击「启动llama」→ 选模型 → 选 64K → 回车(关思考)→ 用第 10 节验证三端
12. 已知未验证项(诚实声明)
- 思考关(默认)模式下的 Codex 桌面版:Codex 自带思考参数,而服务端此时封死了
</think>token,两者叠加的行为未实测。若 Codex 输出异常,临时方案是启动时选"开思考",或去掉--logit-bias只靠模板关思考 - 模板与 token ID(248068)为 Qwen3.8 系列专属,其他模型家族需另配模板与思考标记 ID
整理日期:2026-08-28。所有参数均经实测验证,标注"待实测"的除外。
-
全文自包含(论坛读者/AI 不需要任何本地上下文就能恢复),12 个章节:
章节 内容
1 7900 XTX 独占逻辑:HIP 构建结构性独占(不是参数过滤)、Vulkan dll 封印、Adrenalin 降压设置
2 目录结构 + 两个模型的四端 slug 对应表
3 start.bat 完整内容 + 每个启动参数的理由表
4 qwen38_tolerant.jinja 完整内容(170 行全量,重装不丢)+ 3 处修改说明
5 思考开关原理(双保险)、修复前后实测数据、各端表现表、批处理/PowerShell 坑
6 restart_llama.ps1 全量 + 单字符串传参教训
7 Codex 对接:launcher 全量、config.toml 关键行、wire_api="responses" 必须性
8 dsh settings.yaml + Obsidian data.json 完整要点
9 踩坑清单 16 条,每条都注明
正确做法
10 健康检查命令
11 重装系统恢复清单(9 步顺序,AI 可照做)
12 诚实声明:Codex 关思考模式待你下次实测,模板/token ID 为 Qwen 专属
核对时确认过的关键事实都已写入:HIP 构建 commit be4a6a6(Clang 23)、KV 缓存 4.3GB@32K 实测数据、Codex slug 同步规则(文件名小写去下划线)、占位 key sk-no-key-needed 等。
,直接AI自己去整理就好了,不过非常好的分享。我都是让Codex和Hermes来配置的。