
端侧模型专用 Harness用 Qwen3-27B 实现「零成本推理」。这句话拆开看其实对应三件不相关的事Harness 是什么Qwen3-27B 怎么跑在端侧以及“零成本推理”到底能不能兑现。实际项目中很多团队把开源模型拉到本地后只验证了“模型能聊天”真正接入业务时才发现问题不少。模型加载、提示词组装、上下文管理、超时处理、日志记录、成本统计这些都需要一个统一的外壳来管理。这个外壳就是 Harness。它不是模型本身也不是某个聊天前端而是位于应用与模型之间的编排层负责把输入规范成模型能理解的格式把返回结果整理成业务能使用的输出同时处理重试、异常和资源占用。这篇文章会围绕一条主线展开用 Python 加 Ollama 作为后端推理框架搭建一个面向端侧 Qwen 模型的最小 Harness跑通“读取配置、组装会话、调用模型、记录成本、输出结果”的完整链路并给出参数选择、常见坑点和生产化建议。读完以后你可以把同样的结构用于本地知识库、代码辅助、日志分析等场景。先说模型形态。标题里的 Qwen3.8-27B在真实部署时通常对应 Qwen 3 开源系列里 27B 量级的端侧模型。不同下载渠道给出的模型标识不一定相同本文统一写成 Qwen3-27B。你本地实际拉到的模型 tag 以模型卡和下载源为准Harness 与具体模型标签解耦运行时从配置文件读取即可。1. 先理解“端侧模型专用 Harness”到底解决什么问题1.1 Harness 在模型推理中的含义Harness 这个词在 AI 工程社区里经常出现但它没有一个严格统一的定义。最直观的理解是Harness 是给模型调用套上的一层“操作台”。没有 Harness 时接入一个本地大模型通常是这样写代码的# 没有 Harness 时的原始调用 import requests response requests.post(http://127.0.0.1:11434/api/chat, json{ model: qwen3:27b, messages: [{role: user, content: 你好}], stream: False }) print(response.json()[message][content])这段代码能跑但问题是每次接入新功能都要复制一遍请求逻辑温度、最大输出长度、上下文窗口这些参数散落在不同调用点没有日志没有超时处理出错了只能靠肉眼猜没有任何成本或 token 消耗记录。Harness 要解决的就是这些重复劳动。比较技术一点的定义是Harness 是围绕语言模型调用的一层可复用工程脚手架负责管理请求参数、上下文、输出长度、工具调用、日志、异常和资源消耗。它不负责训练模型也不负责推理引擎本身它只负责让模型调用变得更规范、可观测、可复用。为了区分概念可以用一张表说明边界。组件职责典型示例推理引擎加载模型权重并执行推理Ollama、llama.cpp、vLLM模型权重参数化知识的载体Qwen3-27B 的 GGUF、AWQ 版本Harness编排请求、上下文、参数、日志本文实现的 QwenHarnessAgent决策下一步执行什么动作基于 Harness 再包装的工具调度层聊天前端用户交互展示Open WebUI、自建 Web 页面Harness 和 Agent 经常被混用。简单区分Agent 倾向于自主决策它负责“该不该调用工具、下一步做什么”Harness 更偏向执行环境它负责“怎么稳定地把请求发给模型、结果怎么回来、失败怎么处理”。一个项目可以先有 Harness再在 Harness 之上开发 Agent 能力。1.2 为什么端侧推理更需要 Harness端侧推理和云端 API 调用最大的区别在资源限制。云端有标准化的 GPU 实例显存不够可以升级实例端侧可能是一台 16GB 内存的笔记本一张 8GB 显存的消费级显卡甚至只是纯 CPU 环境。在这种环境下同样的请求可能因为上下文过长、参数不合理、并发过高而失败。没有 Harness 时端侧部署最容易出现的问题有三个第一上下文管理混乱。业务侧把用户问题直接拼成一大段文本传给模型不考虑端侧能承载的上下文长度导致响应越来越慢最终溢出或截断。第二采样参数不统一。有人用默认温度有人为了“更有创造性”把 temperature 调到 1.5结果同一套代码在不同接口上输出风格差异巨大测试用例等于白写。第三可观测性缺失。本地模型调用不产生账单看起来“免费”但一旦效果差你完全不知道是模型问题、提示词问题、上下文截断问题还是参数问题。没有日志没有 token 统计排查全靠猜。Harness 把这几个问题统一收口。它应该做到所有请求参数从配置中心或配置文件读取所有调用走同一个会话管理模块每次调用都记录 token 数、耗时、响应长度异常统一包装成业务可识别的错误。1.3 “零成本推理”的准确边界“零成本”不是一个可以无条件兑现的承诺它指的是相对于云端 API 调用而言的边际成本下降。具体拆开来看端侧 Harness 的“零成本”来自四个部分免 API 调用费。不再按 token 向模型服务商付费。本地算力复用。使用已有电脑、工作站或低配服务器的 CPU、GPU、NPU 资源。开源权重。Qwen 开源系列权重可以本地部署不需要购买商业模型授权具体以项目开源协议为准。上下文节约。Harness 统一管理历史会话避免把无关内容反复塞进上下文减少无效 token 消耗。但真实成本是存在的。首先是电费大模型推理是高功耗任务跑一次 27B 模型的长时间对话功耗可能相当于玩一小时游戏。其次是硬件折旧显存越大、算力越强的设备折旧成本越明显。最后是精度和速度的取舍端侧通常使用量化模型输出质量会略低于云端完整精度版本。所以文章后续提到的“零成本推理”指的是“已经拥有一台可用端侧设备时用 Harness 把单次推理的边际成本降到趋近为零”。这个边界要先说清楚。2. 选型与前置环境模型、推理框架和硬件要求2.1 模型选型为什么选择 Qwen 开源系列端侧权重Qwen 是阿里巴巴通义千问团队开源的系列模型覆盖从 0.6B 到 数百 B 的多种参数规模。对于端侧场景太大参数量的模型即使能加载推理速度也无法接受太小的模型虽然快但复杂指令和推理任务容易失效。27B 量级处于“质量与成本”的折中位置。端侧部署 27B 模型重点要看推理框架对模型格式的支持。常见格式包括GGUFllama.cpp 生态使用的量化格式适合 CPU 和混合设备Ollama 也使用这种格式。AWQ面向 GPU 的 4-bit 量化格式显存占用低推理速度不错。GPTQGPU 上常见的量化格式适合用 Transformers 或 vLLM 加载。MLXApple Silicon 平台的优化格式适合 macOS 设备。这里要注意标题里的“Qwen3.8-27B”到真实落地时要参考模型卡上给出的具体模型名和量化版本不要凭文件名猜测。同一个量级的模型不同量化精度需要的显存差异很大。模型量级量化方式参考显存占用参考内存占用适用设备7B 级4-bit GGUF约 6GB约 8GB6GB 显存显卡或 16GB 内存电脑14B 级4-bit GGUF约 10GB约 16GB8GB-12GB 显存显卡27B 级4-bit GGUF约 18GB约 24GB16GB-24GB 显存或 32GB 内存27B 级8-bit GGUF约 30GB约 36GB24GB 以上显存或 64GB 内存这个表是参考值实际占用还要看上下文长度、推理引擎并行策略和模型版本。落地前必须用本机实测不能只看表。2.2 推理框架选型Ollama 与 llama.cpp 的取舍为了让 Harness 的代码尽量简洁推理引擎的选型很关键。比较常见的选择是 Ollama、llama.cpp 和 Transformers。框架安装难度API 友好度模型管理适用场景Ollama低高提供 /api/chat 和 OpenAI 兼容接口支持 pull、list、rm 等命令个人开发、快速验证、Harness 后端llama.cpp中低需要自己编译或调用 server 子命令手动下载 GGUF 文件嵌入式集成、纯 CPU 环境Transformers低到中中需要自己处理加载和 batch手动下载权重微调、评测、研究实验本文没有选 Transformers 作为主推理引擎原因有两个一是 Transformers 在纯 CPU 端侧环境下内存占用较大加载 27B 模型容易 OOM二是 Transformers 需要额外处理 tokenizer 和模型权重格式Harness 层会混入太多“加载模型”的逻辑。选 Ollama 的主要原因是它把模型加载和推理封装成了本地 HTTP 服务Harness 只需要关注请求编排。Ollama 还自带模型管理ollama pull qwen3:27b就能拉取模型具体 tag 以本机可用的模型列表为准后续 Harness 通过模型名称访问即可。llama.cpp 更适合两种场景你需要把 C 推理能力直接嵌入到自己的程序里或者设备环境完全离线不想安装额外服务。如果你是做 Python 层 Harness更推荐先跑通 Ollama再按需切回 llama.cpp server。2.3 环境准备和验证命令以 Ollama 为例环境准备分四步。第一步安装 Ollama。Windows 和 macOS 可以直接从官网下载安装包Linux 使用安装脚本安装脚本内容可能随版本变化现场以官方文档为准。# Linux 安装完毕后确认版本 ollama --version第二步启动服务。# Linux 或 macOS 前台启动 ollama serve如果 Ollama 已经在后台运行这一步可以跳过。默认监听地址是http://127.0.0.1:11434。第三步拉取模型。这里的qwen3:27b是一个占位标识你要以自己实际可用的模型 tag 为准。# 示例拉取 Qwen3 系列 27B 模型 # 如果该 tag 不可用先运行 ollama list 查看已有模型 ollama pull qwen3:27b第四步验证服务可用。# 查看本地已拉取的模型列表 ollama list # 查看 Ollama API 是否正常响应 curl http://127.0.0.1:11434/api/tags如果curl返回 JSON 数组里面带 model 名称说明推理服务已经可用。硬件方面端侧部署 27B 模型的最低要求是内存不低于 16GB推荐 32GB 以上。如果你只有 8GB 显存建议选用 14B 或更小模型或者使用 4-bit 量化并严格控制上下文长度。设备环境可推荐模型量级上下文建议备注CPU-only16GB 内存7B 级量化版2048-4096速度慢适合异步任务CPU-only32GB 内存14B-27B 量化版2048-4096单轮响应可能耗时较长GPU 6GB-8GB 显存7B-14B 量化版2048-4096注意显存占用GPU 12GB-16GB 显存14B-27B 量化版4096-8192可接受日常开发使用多卡或 32GB 以上显存27B 及以上8192 或更高接近云端体验但硬件成本高3. 搭建最小 Harness用 Python 统一管理模型调用3.1 项目目录设计最小 Harness 只需要四个文件。目录结构如下harness_demo/ ├── config.yaml # 模型、参数、日志和成本配置 ├── harness.py # QwenHarness 核心类 ├── cost_logger.py # 成本与调用记录 ├── run_example.py # 示例入口 └── logs/ # 运行后自动生成这个结构比较克制正式项目的还可以加入prompts/、tools/、plugins/等目录但核心先跑通。3.2 定义 Harness 核心配置先创建config.yaml。配置文件把模型名、服务地址、采样参数、上下文长度、日志位置集中管理。model: qwen3:27b # 以本地实际模型 tag 为准 base_url: http://127.0.0.1:11434 temperature: 0.7 top_p: 0.9 max_tokens: 512 num_ctx: 4096 repeat_penalty: 1.1 timeout: 120 log_file: logs/harness.log cost_file: logs/cost.csv这里的num_ctx是 Ollama 的上下文窗口大小不是业务消息条数。它决定了模型能看到的 token 总长度端侧环境建议先设 4096后续按实际资源调整。3.3 实现会话、提示词和工具调用的编排创建harness.py。这个类的核心是把 config 中的参数转换成 Ollama/api/chat接口能识别的请求同时统一处理响应、日志和成本记录。import csv import json import logging import time from pathlib import Path import requests import yaml class QwenHarness: def __init__(self, config_path): self.config self._load_config(config_path) self.session requests.Session() self._setup_logging() self._setup_cost_file() def _load_config(self, path): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def _setup_logging(self): log_path Path(self.config[log_file]) log_path.parent.mkdir(parentsTrue, exist_okTrue) logging.basicConfig( filenameself.config[log_file], levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s, encodingutf-8, ) self.logger logging.getLogger(qwen_harness) def _setup_cost_file(self): cost_path Path(self.config[cost_file]) cost_path.parent.mkdir(parentsTrue, exist_okTrue) if not cost_path.exists(): with open(cost_path, w, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow( [timestamp, model, elapsed_sec, prompt_tokens, completion_tokens, content_length] ) def chat(self, messages, systemNone): if system: messages [{role: system, content: system}] messages payload { model: self.config[model], messages: messages, stream: False, options: { temperature: self.config[temperature], top_p: self.config[top_p], num_predict: self.config[max_tokens], num_ctx: self.config[num_ctx], repeat_penalty: self.config[repeat_penalty], }, } url self.config[base_url] /api/chat start time.time() try: resp self.session.post(url, jsonpayload, timeoutself.config[timeout]) except requests.exceptions.Timeout: self.logger.error(request timeout: %s, url) raise RuntimeError(Ollama request timeout) elapsed time.time() - start if resp.status_code ! 200: self.logger.error(request failed: status%s body%s, resp.status_code, resp.text) raise RuntimeError(fOllama request failed: {resp.status_code}) data resp.json() content data.get(message, {}).get(content, ) prompt_tokens data.get(prompt_eval_count, 0) completion_tokens data.get(eval_count, 0) self._record_cost(elapsed, prompt_tokens, completion_tokens, len(content)) self.logger.info( chat success: prompt_tokens%s completion_tokens%s elapsed%.2fs, prompt_tokens, completion_tokens, elapsed, ) return content, data def _record_cost(self, elapsed, prompt_tokens, completion_tokens, content_length): row [ time.strftime(%Y-%m-%d %H:%M:%S), self.config[model], round(elapsed, 2), prompt_tokens, completion_tokens, content_length, ] with open(self.config[cost_file], a, newline, encodingutf-8) as f: writer csv.writer(f) writer.writerow(row)这段代码里的几个关键点requests.Session()复用了 TCP 连接连续调用时比每次新建连接更省资源。timeout来自配置默认 120 秒。27B 模型在 CPU 上首 token 可能很慢超时太短会误报失败。响应中的prompt_eval_count和eval_count是 Ollama 返回的 token 统计字段分别表示输入和输出 token 数。不同推理框架字段可能不同。每次调用都写 CSV这样后续可以统计累计 token 消耗和平均响应速度。这里没有把cost_logger.py单独拆出来简化一下直接在 harness 里写成本记录减少文件数量。如果你要继续扩展可以把_record_cost抽成独立模块写入数据库或监控系统。3.4 运行首个端侧推理任务创建run_example.py用最小会话跑通 Harness。from harness import QwenHarness def main(): harness QwenHarness(config.yaml) messages [ {role: user, content: 用三句话解释什么是 KV Cache并说明为什么端侧部署要控制上下文长度。} ] content, meta harness.chat( messages, system你是端侧模型部署助手回答要简洁、准确、可操作。, ) print(回答内容) print(content) print(\n元数据) print(meta) if __name__ __main__: main()运行命令cd harness_demo pip install requests pyyaml python run_example.py如果一切正常你会看到模型返回的一段中文回答。同时logs/harness.log中会有成功日志logs/cost.csv中会新增一条调用记录。到这里最小闭环已经建立。你可以修改system提示词、增加多轮消息或者把max_tokens调大观察 Harness 是否稳定工作。4. 关键参数详解让 Harness 在端侧资源下更稳定4.1 上下文长度与 KV Cache 的关系Harness 配置中的num_ctx是端侧推理最需要关注的参数。它直接决定推理引擎会为当前请求预留多少 KV Cache 空间。通俗解释模型生成每个新 token 时都需要重新计算前面所有 token 的注意力信息。为了避免每次都从头算一遍推理框架会把已计算出的 Key 和 Value 缓存下来这个缓存就是 KV Cache。上下文越长KV Cache 越大内存或显存占用越高。num_ctx参考额外显存/内存占用27B 4-bit 量化适用业务2048较低短问答、简单指令4096中常规开发助手、代码片段分析8192较高长文档处理、复杂对话16384很高易 OOM仅在大显存或高内存设备使用这里要先理解一个容易误解的点num_ctx不是你想让模型看到多少内容就设多少而是你设置的缓存上限。如果业务输入超过这个上限Ollama 默认会截断前面或后面的内容导致模型“遗忘”。Harness 侧要做的是在组装消息时计算 token 长度超长时裁剪或摘要而不是盲目调大num_ctx。实际项目中建议 Harness 增加一个估算函数def estimate_tokens(text: str) - int: # 中文场景的粗略估算约 1 个汉字约 0.6 到 1 个 token # 更精确做法是调用 tokenizer但端侧环境可以先用字符数估算 return int(len(text) * 0.8)这个函数不精确但能在发送请求前快速判断是否会超出上下文限制。4.2 采样参数对回答质量和性能的影响采样参数直接影响模型输出的质量和风格也影响端侧推理的稳定性和耗时。参数含义默认值调大影响调小影响端侧建议temperature随机采样温度0.7回答更随机、更有发散性回答更确定、更保守0.6-0.8top_p核采样概率阈值0.9候选 token 更多候选 token 更少、更聚焦0.8-0.95max_tokens单次最大输出 token 数512可输出更长内容回答可能被截断1024 以下repeat_penalty重复惩罚1.1降低重复可能产生重复循环1.1-1.3num_ctx上下文窗口4096上下文更长但更耗资源节省资源但容易截断按业务评估一个常见误区是为了“让回答更稳定”把 temperature 调到 0。此时模型每次都选概率最高的 token看起来稳定但遇到复杂推理时容易陷入重复。端侧模型由于量化精度损失本身就比云端模型更容易输出重复内容建议不要把 temperature 调得过低。max_tokens在端侧很关键因为输出 token 数直接决定生成耗时。CPU 环境下 27B 模型每秒可能只生成几个到十几个 token一次输出 2000 token 可能耗时好几分钟。Harness 应该为不同业务设置不同的输出上限例如代码生成可以到 1024普通问答 256 就够。4.3 端侧推理常用的配置模板下面给出三个参考模板分别对应不同硬件环境。CPU-only 环境model: qwen3:27b base_url: http://127.0.0.1:11434 temperature: 0.7 top_p: 0.9 max_tokens: 256 num_ctx: 2048 repeat_penalty: 1.2 timeout: 3008GB 显存环境model: qwen3:14b # 如果本机只有 qwen3:27b 量化版先跑通再换模型 base_url: http://127.0.0.1:11434 temperature: 0.7 top_p: 0.9 max_tokens: 512 num_ctx: 4096 repeat_penalty: 1.1 timeout: 12016GB 显存或 32GB 内存环境model: qwen3:27b base_url: http://127.0.0.1:11434 temperature: 0.7 top_p: 0.9 max_tokens: 1024 num_ctx: 8192 repeat_penalty: 1.1 timeout: 180需要注意max_tokens和num_ctx是此消彼长的关系。上下文越长模型留给新 token 生成的空间不一定越多如果资源有限建议先压缩上下文再考虑输出长度。5. 验证与结果分析成本、速度和质量的观测方法5.1 如何验证“零成本”是否成立Harness 已经在每次调用后把成本信息写入 CSV下一步是用真实数据验证。打开logs/cost.csv类似这样timestamp,model,elapsed_sec,prompt_tokens,completion_tokens,content_length 2025-07-01 10:00:01,qwen3:27b,42.35,356,128,92 2025-07-01 10:05:12,qwen3:27b,38.12,402,201,150 2025-07-01 10:12:44,qwen3:27b,51.08,510,89,66随后可以写一个小脚本统计累计消耗import csv total_prompt 0 total_completion 0 total_elapsed 0 count 0 with open(logs/cost.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: total_prompt int(row[prompt_tokens]) total_completion int(row[completion_tokens]) total_elapsed float(row[elapsed_sec]) count 1 print(f调用次数: {count}) print(f输入 token: {total_prompt}) print(f输出 token: {total_completion}) print(f总耗时: {total_elapsed:.2f}s) print(f平均单次耗时: {total_elapsed / count:.2f}s if count else 无数据)从成本角度看这套记录能回答三个问题每个业务平均消耗多少 token哪些请求上下文过大拉高了耗时Harness 是否真的通过会话管理减少了重复 token。5.2 推理质量与性能数据怎么看端侧推理除了成本还要关注两个性能指标。第一个是首 token 延迟TTFTTime To First Token。它表示从发送请求到模型输出第一个 token 的时间。这个值受prompt_eval_count、设备算力和上下文长度影响。体验上超过 5 秒用户就会觉得“卡”。如果 Harness 使用非流式接口首 token 延迟会被“整体响应时间”掩盖建议后续扩展流式输出单独测量首 token 延迟。第二个是生成速度通常用 token/s 表示。在 CPU 上 27B 模型可能只有 2-10 token/s在消费级 GPU 上可能到 20-50 token/s。如果模型回答 500 token速度只有 5 token/s用户要等 100 秒这个体验在生产环境往往不可接受。Harness 可以在响应元数据里增加一个性能字段speed completion_tokens / elapsed print(f生成速度: {speed:.2f} token/s)如果速度过慢优先做三件事换更小模型、降低上下文窗口、启动 GPU 加速。5.3 日志与反馈闭环成本记录只是第一步。Harness 的日志要做到能还原一次完整调用过程。推荐日志至少记录以下字段请求发起时间模型名称提示词长度估算值输入 token 数输出 token 数响应耗时是否超时是否重试失败原因出现效果问题时按时间戳把日志和 cost.csv 对齐很快能定位是输入问题、参数问题还是负载问题。这里要特别强调不要只看模型输出是否“像样”。一个合格 Harness 还需要关注模型是否重复、是否截断、是否因上下文过长丢掉了关键信息。这些都需要记录和反馈。6. 常见问题排查端侧 Harness 最容易踩的坑6.1 模型加载过慢或内存不足现象调用 Harness 时长时间无响应或者 Ollama 日志报内存不足进程被系统终止。可能原因模型量化版本选择不合适27B 模型的 8-bit 版本在 16GB 内存机器上运行困难。num_ctx设置过大KV Cache 占用过多内存。没有 GPU 加速CPU 加载大模型非常慢。检查方式# 查看内存占用 free -h # 查看 Ollama 日志具体路径以你的环境为准 journalctl -u ollama --since 10 minutes ago # 查看本机模型列表 ollama list解决方案改用 4-bit 量化模型或更小量级模型。把num_ctx从 8192 降到 4096 或 2048。确认 Ollama 是否已经使用 GPU日志中通常会有inference compute相关信息。如果是纯 CPU 环境把max_tokens和num_ctx都调低避免单次推理占用过久。预防建议部署前先看模型文件大小结合本机内存和显存做估算不要直接套用默认配置。6.2 上下文被截断或模型“遗忘”现象对话进行到第三轮、第四轮时模型突然忘记第一轮提出的要求或者回答内容明显缺失。可能原因总输入 token 超过num_ctxOllama 默认丢弃超出部分。Harness 没有对历史消息做裁剪每次都把所有消息原样发给模型。消息顺序或 system 提示词被业务代码覆盖。检查方式查看 cost.csv 中prompt_tokens是否接近num_ctx。打印发给模型的 messages确认 system 提示词是否还在首位。解决方案在 Harness 里增加历史消息管理当估算 token 超限时丢弃最早的非 system 消息。对长文档做分段或摘要而不是全部塞进上下文。必要时提高num_ctx但要同步评估内存和速度。def trim_messages(messages, max_tokens): # 总是保留第一条 system 消息 system_msg None history [] for msg in messages: if msg[role] system and system_msg is None: system_msg msg else: history.append(msg) # 从最旧的非 system 消息开始丢弃 while history and estimate_tokens(str(messages)) max_tokens: history.pop(0) return [system_msg] history if system_msg else history6.3 输出死循环或重复生成现象模型开始正常几轮之后输出不断重复同一句话比如一直输出“好的我明白了。好的我明白了。”直到达到max_tokens。这个现象在社区里讨论 Qwen 本地部署时非常常见本质是模型在低资源或量化精度损失下进入退化循环。可能原因repeat_penalty太低模型没有受到重复惩罚。temperature为 0导致采样总是选最优 token陷入局部循环。max_tokens太大模型在长输出中更容易退化。上下文被截断模型对当前任务的“记忆”信号丢失。检查方式查看 cost.csv 中completion_tokens是否经常等于max_tokens如果是说明输出被重复内容填满。查看 response content 中是否有连续重复片段。解决方案temperature: 0.8 top_p: 0.85 repeat_penalty: 1.3 max_tokens: 512同时在 Harness 的chat方法里增加重复检测一旦检测到连续重复立即截断输出并告警。def detect_repeated(text: str, min_len10, threshold3): for i in range(min_len, len(text)): if text[i:i min_len] text[i - min_len:i]: return True return False6.4 调用推理服务时出现 HTTP 错误现象RuntimeError: Ollama request failed: 404、500或连接拒绝。错误现象常见原因检查方式处理建议连接拒绝 connection refusedOllama 服务未启动curl http://127.0.0.1:11434/api/tags启动ollama serve404 model not found模型 tag 与配置不一致ollama list修改 config.yaml 中的 model 名称500 internal error模型加载失败或资源不足查看 Ollama 日志降低参数量或上下文请求超时端侧推理太慢查看 cost.csv 中的耗时调大 timeout或换小模型排查顺序固定为先确认服务通没通再确认模型名称对不对再确认参数会不会导致资源不足最后看日志。7. 从最小 Harness 到生产可用扩展与最佳实践7.1 学习环境、开发环境与生产环境的区别最小 Harness 跑通后不要直接把它当成生产服务。三套环境的要求差别很大。关注点学习环境开发环境生产环境配置写在 config.yaml接入配置中心或环境变量外置化支持动态调整日志本地文件结构化日志接入集中日志平台成本手动看 CSV自动化统计按业务线拆分成本安全本机访问局域网访问要加认证必须鉴权、限流、加密部署python 直接运行容器化或 systemd容器编排、健康检查、滚动发布性能能跑通即可记录并优化有 SLA监控 TTFT 和 token/s回滚不需要保留旧配置支持模型和配置快速回滚生产环境尤其要注意一点不要直接把 Ollama 的11434端口对外暴露。Harness 通常会作为内部服务访问推理服务对上层只提供 HTTP 或 gRPC 接口接口层再增加身份认证和请求限流。7.2 可复用检查清单端侧 Harness 部署前自查整理一份清单每次接入新业务或换新模型时按顺序过一遍。[ ] 模型 tag 与本地ollama list列表一致没有配置错模型名[ ] 模型量化精度与显存/内存匹配留出至少 20% 余量[ ]num_ctx与业务输入 token 数匹配不盲目调大[ ]max_tokens与服务端生成速度匹配避免等待时间过长[ ]temperature、top_p、repeat_penalty已按当前任务测试过的取值设置[ ] Harness 能自动裁剪历史消息避免上下文超限[ ] 超时配置已考虑 CPU 慢速推理没有过早判定失败[ ] 每次调用都写日志和成本记录字段完整[ ] 重复输出有检测至少能告警[ ] 对外暴露的接口有认证、限流和审计[ ] 有配置回滚方案模型或参数变更后可快速恢复7.3 后续扩展方向最小 Harness 可以往几个方向扩展按实际业务需求选择。第一接入向量检索。如果要做本地知识库问答可以让 Harness 先接收用户问题从向量数据库检索相关片段把命中结果作为上下文拼进 messages再提交给模型。常见组合是 Qwen 的 Embedding 模型加上 MilvusJava 侧可以借助 LangChain4j 的 EmbeddingStore 集成减少自己写向量检索的样板代码。第二支持流式输出。目前示例使用非流式接口用户需要等完整回答。改成流式后可以把首 token 延迟降下来并在界面上逐字输出体验更好。第三接 OpenAI 兼容接口。Ollama 提供 OpenAI 兼容的/v1/chat/completions接口Harness 可以内部封装两套协议适配让上层业务不关心底层推理引擎。第四增加多模型路由。配置中可以定义多个模型按任务类型路由例如复杂推理走 27B简单问答走 7B既保质量又降成本。第五加入自动评测。对一批固定问题做回归测试记录输出结果和耗时对比每次参数调整的效果。这样 Harness 就不只是调用外壳还能成为模型迭代的测试平台。端侧模型专用 Harness 的价值在于把“模型能跑”变成“模型能稳定接入业务”。当你在本地跑通一个调用、一次成本记录、一次失败排查之后这套结构会在数据本地化、低延迟、低成本场景中慢慢体现出优势。真正的“零成本推理”不是硬件不花钱而是让每一次模型调用都在可控、可观测、可复用的轨道上运行不再因为工程细节不明而浪费算力和时间。