别让所有请求都挤向大模型:用开放模型构建低成本混合 AI 架构

2026-09-29 21 预计阅读时间: 1 分钟
来源: cloud.google.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:13 分钟

创业团队接入大模型时,最省事的方案通常是把所有请求都发给能力最强的云端模型。但当调用量增长、Agent 开始自动循环、产品需要实时交互时,这种单模型架构会同时暴露三个问题:网络往返拖慢响应,高频小任务持续侵蚀毛利,自托管超大模型又会让团队提前变成 GPU 基础设施公司。

更可持续的方案不是彻底放弃前沿 API,而是建立复合 AI 栈:让紧凑型开放模型处理高频、边界清晰的任务,把复杂推理、长上下文综合和高风险决策交给前沿模型。Gemma 4 代表的正是这类可本地运行、可微调、可控制的模型组件。

模型路由比“选出唯一赢家”更重要

生产系统里的请求并不处于同一个难度等级。意图分类、字段抽取、格式校验和状态判断通常有明确输入输出;跨文档分析、开放式规划与复杂多模态推理则更依赖通用能力。

可以按照下面的方式拆分:

工作负载 推荐执行位置 主要原因
意图分类、JSON 抽取、状态检查 紧凑型开放模型 调用频繁、结果结构固定、易于评测
桌面助手、移动端交互、离线功能 设备本地 降低网络延迟,敏感数据无需离开设备
企业专有术语与固定流程 微调后的开放模型 可以利用 LoRA 或 QLoRA 固化领域能力
长上下文综合、复杂规划、疑难请求 前沿云端模型 将昂贵推理留给真正需要它的请求

这套架构的关键并不是本地模型永远正确,而是建立清晰的升级机制。输出不符合 schema、置信度不足、上下文过长或请求风险较高时,路由器应自动转向能力更强的模型。

来源材料提到的案例也体现了这种变化:桌面助手 Cue 原计划只把本地 Gemma 当作离线后备,基准测试后却将其设为实时转录格式化的默认引擎,报告延迟从 876 毫秒降至 488 毫秒。BetterSpeak 则把约 2.9 GB 的 4 位量化模型装进移动设备,以离线运行换取零推理服务器账单。这些数字来自具体产品,不能直接外推到其他硬件和请求,但说明了值得验证的工程方向。

一个可运行的本地优先路由器

下面是一个可以改造的最小示例。它通过 Ollama 调用已经安装的本地模型;复杂任务或本地输出不符合 JSON 要求时,则调用一个 OpenAI 兼容的前沿模型端点。

先启动 Ollama,并确认本机已有可用的 Gemma 模型。具体模型标签取决于 Ollama 仓库和你的硬件,请用 ollama list 查到的名称替换环境变量:

ollama serve
ollama list

export LOCAL_MODEL='替换为本机的-gemma-模型标签'
export FRONTIER_URL='https://your-provider.example/v1/chat/completions'
export FRONTIER_MODEL='your-frontier-model'
export FRONTIER_TOKEN='your-api-token'

保存以下代码为 router.py。它只依赖 Python 标准库:

import argparse
import json
import os
import urllib.request

LOCAL_URL = os.getenv("LOCAL_URL", "http://127.0.0.1:11434/api/generate")
LOCAL_MODEL = os.environ["LOCAL_MODEL"]
FRONTIER_URL = os.getenv("FRONTIER_URL")
FRONTIER_MODEL = os.getenv("FRONTIER_MODEL")
FRONTIER_TOKEN = os.getenv("FRONTIER_TOKEN")

LOCAL_TASKS = {"classify", "extract", "summarize"}


def post_json(url, payload, headers=None, timeout=60):
    request = urllib.request.Request(
        url,
        data=json.dumps(payload).encode("utf-8"),
        headers={"Content-Type": "application/json", **(headers or {})},
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=timeout) as response:
        return json.loads(response.read().decode("utf-8"))


def local_call(prompt):
    result = post_json(
        LOCAL_URL,
        {"model": LOCAL_MODEL, "prompt": prompt, "stream": False},
    )
    return result["response"].strip()


def frontier_call(prompt):
    if not all([FRONTIER_URL, FRONTIER_MODEL, FRONTIER_TOKEN]):
        raise RuntimeError("未配置前沿模型端点,无法执行升级请求")

    result = post_json(
        FRONTIER_URL,
        {
            "model": FRONTIER_MODEL,
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0,
        },
        headers={"Authorization": f"Bearer {FRONTIER_TOKEN}"},
    )
    return result["choices"][0]["message"]["content"].strip()


def build_prompt(task, text):
    if task == "classify":
        return (
            "把文本分类为 support、sales 或 other。"
            "只返回严格 JSON,例如:{\"label\":\"support\",\"confidence\":0.92}。\n"
            f"文本:{text}"
        )
    if task == "extract":
        return (
            "从文本中提取 name、email、company。只返回严格 JSON;"
            "缺失字段使用 null。\n"
            f"文本:{text}"
        )
    if task == "summarize":
        return f"用不超过三句话总结以下文本:\n{text}"
    return f"分析以下问题,给出有依据的解决方案:\n{text}"


def run(task, text):
    prompt = build_prompt(task, text)

    # 边界明确且输入较短的任务优先在本地运行。
    if task in LOCAL_TASKS and len(text) <= 8000:
        answer = local_call(prompt)

        # 结构化任务必须通过机器校验,否则升级到前沿模型。
        if task in {"classify", "extract"}:
            try:
                json.loads(answer)
                return {"route": "local", "answer": answer}
            except json.JSONDecodeError:
                pass
        else:
            return {"route": "local", "answer": answer}

    return {"route": "frontier", "answer": frontier_call(prompt)}


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("task", choices=["classify", "extract", "summarize", "reason"])
    parser.add_argument("text")
    args = parser.parse_args()
    print(json.dumps(run(args.task, args.text), ensure_ascii=False, indent=2))

运行一个适合本地处理的请求:

python router.py classify '客户说账单重复扣款,希望今天退款。'

再测试一个默认升级到前沿模型的复杂请求:

python router.py reason '比较三种跨区域数据架构,并给出故障恢复与合规方面的取舍。'

真实系统还应加入超时、重试、熔断、并发限制、提示词版本和可观测性。不要只依据任务名称路由;可以逐步加入输入长度、用户等级、数据敏感度、历史准确率与本地队列深度等信号。

开放模型真正改善的是单位经济性

判断迁移是否值得,不能只比较单次模型报价。更有用的指标是每个成功任务的总成本:

每成功任务成本 = 推理费用 + GPU 摊销 + 运维成本 + 失败重试成本 + 升级调用成本

本地推理并不自动等于免费。闲置 GPU、模型加载时间、监控、升级和工程维护都需要预算。相反,如果模型运行在用户已有的手机或电脑上,高频交互的边际服务器成本可能接近零。

评测时建议同时记录:

  • P50、P95 和 P99 端到端延迟;
  • 每类任务的结构化输出通过率;
  • 本地请求升级到前沿模型的比例;
  • 每千次成功任务的综合成本;
  • 不同设备、量化级别和上下文长度下的内存占用;
  • 业务指标,例如任务完成率、留存率或人工复核率。

Gemma 4 的多种尺寸和架构为这种分层部署提供了选择:小型模型面向移动端与边缘设备,MoE 模型面向高吞吐服务,较大的稠密模型则适合单 GPU 推理和微调。来源还提到最高 256K 上下文、函数调用、可配置思考模式和用于推测解码的草稿模型。实际采用前,应根据具体版本、运行时和硬件重新核对支持情况,而不是假设所有能力在每个部署工具中都完全一致。

微调与垂直模型:护城河也会带来责任

开放权重让团队可以用 LoRA 或 QLoRA 把专有术语、分类边界和输出格式写进模型,而不必在每次请求中塞入大量示例。这适合稳定、重复且拥有高质量标注数据的任务。

但微调不是替代评测的捷径。训练集污染、领域漂移和灾难性遗忘都会让离线指标看起来很好,线上却不断失败。医疗、生物技术等高风险领域尤其需要人工复核、审计记录和明确的适用范围。来源中关于 MedGemma、胸片报告和科学工作流的结果,应被看作特定数据集与研究条件下的证据,而不是无需验证即可直接用于临床决策的承诺。

许可同样需要纳入发布流程。来源称 Gemma 4 采用 Apache 2.0,并允许微调、量化、再分发和商业部署;团队仍应让法务核对下载到的具体模型版本、权重条款、数据许可证以及目标市场的监管要求。

本周可以完成的迁移清单

不必一次性重写整个 AI 栈。更稳妥的推进方式是:

  1. 从日志中找出三个调用量最高、输出边界最清晰的任务。
  2. 建立包含正常输入、边界输入和对抗输入的小型回归集。
  3. 在目标硬件上测试一个紧凑型开放模型,记录质量、延迟和内存。
  4. 设计 schema 校验、置信度阈值以及前沿模型回退路径。
  5. 先让 1% 到 5% 的真实流量进入新路由,并比较每个成功任务的成本。
  6. 为敏感数据设置本地处理规则,同时记录升级原因而非原始隐私内容。

最好的模型策略通常不是押注某一个模型,而是建立可替换的路由层。开放模型负责速度、隐私和可控成本,前沿 API 负责能力上限。这样既能保护产品体验,也能避免把有限的现金和工程时间消耗在并不需要顶级推理的请求上。


相关推荐