跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • 用户
  • 群组
皮肤
  • 浅色
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • 深色
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • 默认(LCZ-Blue)
  • 不使用皮肤
  • LCZ-Green
  • LCZ-Blue
  • LCZ-Black
折叠
品牌标识

抡锤者

首页 版块 标签
硬件
装备秀
AI 广场 App
  1. 主页
  2. 版块
  3. AI硬件
  4. 额外加一张7900xtx用llama跑qwen3.8 27B Q4和Q4非审查的经验总结

额外加一张7900xtx用llama跑qwen3.8 27B Q4和Q4非审查的经验总结

已定时 已固定 已锁定 已移动 AI硬件
7900xtxqwen-27bllama.cpp
6 帖子 2 发布者 620 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
回复
  • 在新帖中回复
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • yi songY
    yi songY
    yi song
    体面乡绅 硕果累累
    发表于 最后由 yi song 编辑
    #1

    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 的实现原理)

    核心思路:靠软件构建保证独占,而不是靠参数过滤。

    1. 使用 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 应用,互不干扰
    2. Vulkan 构建已弃用:C:\llama 根目录还有一份 Vulkan 版(b10617 zip 解压),已不用。关键坑:必须把两个位置的 ggml-vulkan.dll 改名为 .bak,否则运行时 Vulkan 后端会抢跑、与 ROCm 冲突:
      • C:\llama\ggml-vulkan.dll → .bak
      • C:\llama\llama-hip\ggml-vulkan.dll → .bak(start.bat 启动时会自动检查改名)
    3. 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-q4km qwen3.8-27b-q4km qwen3.8-27b-q4km
    Qwen3.8-27B-UD-Q4_K_XL.gguf qwen3.8-27b-ud-q4kxl qwen3.8-27b-ud-q4kxl qwen3.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 on Flash Attention,省显存带宽
    --no-mmap 权重全进内存,避免缺页
    --cache-type-k/v q4_0 KV 缓存 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 处修改(重做模板时照此改):

    1. 合并多条 system 消息:Codex 每次请求带 3 条 developer 消息,原版模板直接 400("System message must be at the beginning")。本模板把所有 system/developer 内容合并成一个 merged_sys 块放开头
    2. 思考默认值反转:开启条件从 undefined or true 改为 defined and true → 不传参默认关思考(配合 llama-server 会注入 enable_thinking=true 的行为,必须显式压制)
    3. 关思考时输出空思考块:<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,表现为"点总结后长时间空白,最后才一次性吐正文"。

    关思考 = 双保险:

    1. --chat-template-kwargs "{\"enable_thinking\":false}" — 模板层显式关闭(压制 llama-server 自动注入的 true)
    2. --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 pause
    

    codex-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-needed
    

    Obsidian 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. 踩坑清单(按血泪程度排序,每条都注明正确做法)

    1. --verbose 毁掉流式响应:终端高频输出干扰 HTTP 流,客户端报 TRANSPORT 错误、任务失败。✅ 正确做法:--log-file C:\llama\server.log
    2. Vulkan 后端抢跑:ggml-vulkan.dll 存在时与 ROCm 冲突。✅ 改名 .bak(start.bat 启动时自动检查)
    3. llama-server 注入 enable_thinking=true:即使模板默认关思考,服务端也会注入 true 覆盖。✅ 必须 --chat-template-kwargs "{\"enable_thinking\":false}" 显式压制
    4. PowerShell 数组传参拆碎 JSON 引号:--chat-template-kwargs 静默失效,服务照常启动。✅ 用单字符串 $argline
    5. logit-bias 用冒号报错:✅ 格式为 248068-100(连字符)
    6. 原版模板拒绝多条 system 消息:Codex 每请求 3 条 developer 消息 → 400 "System message must be at the beginning"。✅ 容错模板合并
    7. llama.cpp /v1/responses 转换器 bug(GitHub issue #26394 / PR #27751):特定 input 形状报 400 "Cannot determine type of 'item'"。✅ 真实 Codex 载荷能通过;别用手工合成的最小化请求下结论,要看真实客户端流量
    8. KV 缓存爆显存:q8_0 KV 在 32K 就要 8.6GB,27B 模型 + 长文档必翻车。✅ q4_0 减半(4.3GB@32K)
    9. 同时只能跑一个 llama-server:8080 端口 + 显存双重冲突,残留实例曾吃 6.2GB 内存。✅ 换模型流程:先关窗口 → 再「启动llama」重选;排障先查进程数
    10. model 字段被忽略:llama-server 只认加载的 gguf。✅ 各端 UI 选的模型必须与加载的 gguf 对齐(换模型后 dsh 用 /model 选,Codex 双击「Codex启动」自动同步,Copilot 设置里选)
    11. 批处理括号吞字:if 块内 set 值含括号会被解析器吃掉尾部 )。✅ 值里用短横线代替括号
    12. opencode 后端 O(n²) 卡顿:Obsidian Copilot 用 opencode 后端会自动累积历史。✅ 用 chat 后端 + contextTurns: 0
    13. 隐藏思考拖慢总结:总结慢先查 reasoning_content 长度,别误判为历史记录问题(有 contextTurns: 0 在,历史根本不参与)
    14. MTP 投机解码不适合长文档总结:--spec-type draft-mtp 草稿接受率低、频繁回溯,反而更慢。✅ 不用
    15. dsh 输出 token 限制报错:maxTokens 太小。✅ settings.yaml 热重载改 maxTokens: 16384、contextWindow: 65536,无需重启
    16. 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 可照此自动完成)

    1. 装 AMD Adrenalin(驱动),7900 XTX 调优:功耗 -10%、电压 -65mV
    2. 装 llama.cpp HIP 版到 C:\llama\llama-hip\(Releases 的 win-hip-x64 包);若有 ggml-vulkan.dll 改名 .bak
    3. 模型放 C:\model\(两个 Qwen3.8-27B gguf)
    4. 按本文第 3/4/6/7 节创建 start.bat、qwen38_tolerant.jinja、restart_llama.ps1、codex-launcher.ps1
    5. 桌面建快捷方式:启动llama.lnk → C:\llama\start.bat(工作目录 C:\llama);Codex启动.bat 按第 7 节
    6. 装 Codex 桌面版(MSIX,首次启动应用内登录),配置 ~/.codex/config.toml 关键行 + model_catalog.json 注册两个本地 slug
    7. dsh:settings.yaml(第 8 节)+ .credentials.yaml 占位 key
    8. Obsidian Copilot:data.json 按第 8 节要点配置
    9. 双击「启动llama」→ 选模型 → 选 64K → 回车(关思考)→ 用第 10 节验证三端

    12. 已知未验证项(诚实声明)

    • 思考关(默认)模式下的 Codex 桌面版:Codex 自带思考参数,而服务端此时封死了 </think> token,两者叠加的行为未实测。若 Codex 输出异常,临时方案是启动时选"开思考",或去掉 --logit-bias 只靠模板关思考
    • 模板与 token ID(248068)为 Qwen3.8 系列专属,其他模型家族需另配模板与思考标记 ID

    整理日期:2026-08-28。所有参数均经实测验证,标注"待实测"的除外。

    1 条回复 最后回复
    0
    • yi songY
      yi songY
      yi song
      体面乡绅 硕果累累
      发表于 最后由 编辑
      #2

      全文自包含(论坛读者/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 等。

      1 条回复 最后回复
      0
      • yi songY
        yi songY
        yi song
        体面乡绅 硕果累累
        发表于 最后由 编辑
        #3

        有类似需求就甩给AI来配置。我发这个贴是为了以后重装系统把这个甩给AI。
        也跟大家分享一下这三天遇到的各种问题。

        1 条回复 最后回复
        0
        • terryT
          terryT
          terry
          易有太极
          发表于 最后由 编辑
          #4

          😢,直接AI自己去整理就好了,不过非常好的分享。我都是让Codex和Hermes来配置的。

          油管:https://www.youtube.com/@抡锤者

          1 条回复 最后回复
          0
          • yi songY
            yi songY
            yi song
            体面乡绅 硕果累累
            编写于 最后由 编辑
            #5

            做一个后续补充。
            半个月前的太空兔模型让我薅了一周,期间安装了strata架构测试,但是不适合我的机器。然后再转回本地模型体感就觉得非常卡,起初我以为是我用API太快让本地给我的错觉,后期发现是缓存命中太低的问题。
            昨天和今天把缓存命中让AI修复,然后尽可能给他甩几个帖子让它看我机器有没有提升空间。目前是从30t/s提到现在48t/s,做一个简短的总结。

            从 30 t/s 到 48 t/s:AMD 7900 XTX 本地跑 Qwen3.8-27B 的调优实录

            一、起点

            硬件:AMD RX 7900 XTX 24GB + i5-13600KF(20 逻辑核)
            模型:Qwen3.8-27B UD-Q4_K_XL(约 17GB 权重)
            推理框架:llama.cpp / llama-server(HIP/ROCm 后端)
            初始速度:~28–30 t/s,且不稳定,长对话缓存命中经常掉到 30~45%

            二、做了哪些调整(按时间线)

            第 1 步:MTP 投机解码(最大提升来源)

            Qwen3.8-27B 自带 MTP(Multi-Token Prediction)头权重,不需要单独的 draft 模型文件。只需在启动命令加:

            --spec-type draft-mtp --spec-draft-n-max 2
            
            • --spec-type draft-mtp:启用 MTP 投机解码
            • --spec-draft-n-max 2:每步最多 draft 2 个 token
            • 如果日后拿到独立的 mtp-*.gguf 文件,再加 -md "路径" 指向它

            效果:接受率从 0(之前完全没启用)提升到 61%+,生成速度从 ~28 t/s 跳到 ~38.8 t/s。

            第 2 步:batch size 翻倍

            :: 改前
            -b 512 -ub 256
            :: 改后
            -b 1024 -ub 512
            
            • -b(prompt batch):prefill 阶段的批量大小
            • -ub(unbatched):非批处理上限

            7900 XTX 的显存带宽和计算单元足够撑住更大的 batch,翻倍后 prefill 阶段吞吐更高,对 MTP 的 draft 验证也有帮助。

            第 3 步:线程数调优

            --threads 10
            

            之前默认 14 线程(20 逻辑核的 70%),实测 10 线程(50%)反而更稳。原因:llama.cpp 的 HIP 后端在 GPU 推理时 CPU 线程主要负责数据搬运和调度,线程太多会引入调度开销和缓存抖动。10 线程是实测最优。

            第 4 步(关键):修复 DSH 侧 compaction 触发点 = 0 的隐藏 bug

            这是最隐蔽也最致命的问题。 前 3 步做完后速度到 38.8 t/s,但缓存命中仍然上不去 90%,而且出现"单步输出被截断"的提示。

            排查发现:DSH 的 provider 配置里 headroomTokens=32768,而服务端实际跑 64K 窗口(-c 65536)。DSH 的 compaction 触发公式:

            触发点 = min(W × 0.8,  W - maxTokens - headroomTokens)
            

            代入当时的值:

            W = 65536(实际窗口)
            maxTokens = 32768
            headroomTokens = 32768
            → 触发点 = min(52428, 65536 - 32768 - 32768) = min(52428, 0) = 0
            

            触发点 = 0 意味着每一步都触发 compaction(压缩历史)。后果:

            • 每步都改写历史 + 多打一次整段上下文请求
            • KV 前缀被反复冲掉,prompt cache 命中从 90%+ 崩到 30~45%
            • MTP draft 的上下文匹配质量下降,接受率被压到 41%
            • 单步输出顶到 maxTokens=32768 上限 → 触发"已达输出 token 上限"截断提示

            修法:把三个参数对齐到 64K 窗口:

            # cordis.patch.yml
            models:
              - id: qwen3.8-27b-ud-q4kxl
                contextWindow: 65536   # 跟服务端 -c 对齐(之前是 131072)
                maxTokens: 16384       # 单步输出上限,够用且给 headroom 留空间
                headroomTokens: 16384  # 压缩预留缩半
            
            # compaction-basic 插件
            headroomTokens: 16384
            thresholdRatio: 0.8
            

            重新算触发点:

            W = 65536, maxTokens = 16384, headroom = 16384
            → 触发点 = min(52428, 65536 - 16384 - 16384) = min(52428, 32768) = 32768
            

            触发点回到 32768,compaction 只在上下文真正膨胀时才触发,KV cache 保持稳定。

            效果:

            • 缓存命中从 30~45% 回到 90%+
            • MTP 接受率从 41% 跳到 61.8%(KV 稳定 → draft 匹配质量恢复)
            • 生成速度从 38.8 t/s 提升到 46–48 t/s
            • 截断提示消失

            三、最终启动命令

            llama-server.exe -m "C:\model\Qwen3.8-27B-UD-Q4_K_XL.gguf" --mmproj "C:\model\mmproj-F16.gguf" ^
              -c 65536 -ngl 99 -n -1 -fa on --no-mmap ^
              --spec-type draft-mtp --spec-draft-n-max 2 ^
              --cache-type-k q4_0 --cache-type-v q4_0 ^
              --parallel 1 -b 1024 -ub 512 --threads 10 ^
              --chat-template-file C:\llama\qwen38_tolerant.jinja ^
              --chat-template-kwargs "{\"enable_thinking\":false}" --logit-bias 248068-100 ^
              --host 0.0.0.0 --port 8080
            

            四、最终数据

            指标 调优前 调优后 提升
            生成速度 28–30 t/s 46–48 t/s +60%
            MTP 接受率 0%(未启用) 61–71% —
            缓存命中(长对话) 30–45%(崩) 90%+ 恢复
            单步截断 频繁 消失 —
            上下文窗口 128K(名义)/ 实际有效 ~14K 64K(对齐) 更稳

            五、经验教训

            1. MTP 是 Qwen3.8 系列最大的免费加速。自带 MTP 头,零额外显存成本,接受率 60%+ 意味着每 5 个 token 只付 3 个的代价。

            2. batch size 别保守。24GB 显存跑 27B Q4 权重还剩 ~7GB 给 KV,-b 1024 -ub 512 完全撑得住,prefill 吞吐翻倍。

            3. CPU 线程不是越多越好。HIP 后端下 10 线程(50% 逻辑核)比 14 线程(70%)更稳,调度开销是隐藏的瓶颈。

            4. 最隐蔽的坑:DSH 侧参数和服务端实际窗口必须对齐。headroomTokens + maxTokens 加起来不能超过实际窗口的一半,否则 compaction 触发点被压到 0,每步都压缩 → KV 反复冲 → 缓存命中崩 → MTP 接受率也崩。这个 bug 不报错,只是"变慢",极难定位。

            5. 64K vs 128K:24GB 显存跑 27B Q4,有效上下文深度约 ~14K token(KV cache 物理限制)。128K 窗口名义上更大,但超出 ~14K 后 KV 溢出到 CPU,速度断崖式下跌。64K 窗口 + 对齐的 DSH 参数,实际体验比 128K 更快更稳。

            6. 验证方法:用 /v1/chat/completions 的 SSE 流里 timings 块读 predicted_per_second(生成速度)和 draft_n_accepted / draft_n(MTP 接受率),比看界面数字更准。短 prompt 测试容易虚高(提前 EOS),用 128+ token 的 prompt 才有参考意义。

            六、后续可做

            • 驱动降压:当前 -10% 功耗限制。速度到 48 t/s 后如果热点温度仍 100°C,可降到 -5% 并调风扇曲线,再高优先换硅脂。
            • --spec-draft-n-max 3:尝试每步 draft 3 个 token,接受率可能降到 50% 左右但每步产出更多,需实测对比。
            • --metrics:服务端加 --metrics 开启 Prometheus 端点,可以监控 prompt cache 命中率的精确数字。
            1 条回复 最后回复
            0
            • yi songY
              yi songY
              yi song
              体面乡绅 硕果累累
              编写于 最后由 编辑
              #6

              完整对比
              配置 长文生成速度 MTP 接受率 Prefill 速度
              n-max=2 + b=1024(之前最优) 46.0 t/s 61.8% 200 t/s
              n-max=3 + b=2048 37.7 t/s 40.0% 309 t/s
              n-max=2 + b=2048(现在) 42.4 t/s 53.4% 321 t/s
              结论
              draft-n-max:2 是甜点位,3 反而更慢(接受率从 61.8% 崩到 40%)。已改回 2。
              -b 2048:对生成速度没帮助(42.4 < 46.0),但 prefill 速度提升 60%(321 vs 200 t/s)。Prefill 是长对话、工具输出多、compaction 时的瓶颈,batch 翻倍在这些场景下更明显。
              建议:保留 b=2048 -ub 1024。日常短对话体感差别不大,但长对话和工具调用多的场景(你 DSH 里大量工具调用)prefill 快 60% 是实打实的收益。

              当前脚本最终参数:

              代码块

              --spec-type draft-mtp --spec-draft-n-max 2
              -b 2048 -ub 1024 --threads 10
              这是目前的最优组合。

              1 条回复 最后回复
              0

              你好!看起来您对这段对话很感兴趣,但您还没有一个账号。

              厌倦了每次访问都刷到同样的帖子?您注册账号后,您下次访问时都将自动回到上次浏览的位置,并可选择接收新回复的通知(通过电子邮件或推送通知)。您还可以收藏帖子、为帖子点赞,以此向其他社区成员表达您的感谢。

              有了你的建议,这篇帖子会更精彩哦 💗

              注册 登录
              回复
              • 在新帖中回复
              登录后回复
              • 从旧到新
              • 从新到旧
              • 最多赞同


              • 登录

              • 登录或注册以进行搜索。
              • 第一个帖子
                最后一个帖子
              0
              • 版块
              • 最新
              • 标签
              • 热门
              • 用户
              • 群组