在过去的几周里,我一直在探索小语言模型(SLM)的部署之旅。从最初了解 Phi-4 和小语言模型的强大之处,到实践使用 Foundry Local 在本地运行模型,再到学习函数调用,最近还构建了一个完整的多智能体测验应用,其中包含一个协调专家智能体的编排器。

这个测验应用在本地运行得很好,但它依赖于 Foundry Local 目录中的模型——这些模型经过预优化且随时可用。但如果想部署一个不在目录中的模型该怎么办?也许你已经在特定领域的测验数据上微调了一个模型,或者 Hugging Face 上刚发布了一个你想使用的新模型。今天我将展示如何从 Hugging Face 获取一个模型,使用 Microsoft Olive 优化它,在 Foundry Local 中注册它,并在测验应用中运行它。同样的工作流程适用于你可能为特定用例微调的任何模型。

理解部署选项

在深入之前,让我们先了解 SLM 应用程序的部署选项全景。根据目标环境,有几种部署 SLM 应用程序的途径。

三种主要路径

vLLM 是云部署的行业标准——容器化、可扩展、处理许多并发用户。非常适合 Azure VM 或 Kubernetes。

Ollama 提供了一个中间地带——比 vLLM 简单,但仍然提供 Docker 支持,便于共享和部署。

Foundry Local + Olive 是 Microsoft 的边缘优先方法。使用 Olive 优化你的模型,使用 Foundry Local 或自定义服务器提供服务。非常适合本地部署、离线或注重隐私的部署。

为了保持本系列文章的边缘优先主题,我将专注于 Foundry Local 路径。我将使用 Qwen 2.5-0.5B-Instruct——足够小,可以快速优化并演示完整的工作流程。可以把它看作是你在自己的测验数据上微调的模型的替代品。

前置要求

你需要:

  • Foundry Local 版本 0.8.117 或更高
  • Python 3.10+ 用于测验应用(foundry-local-sdk 需要)
  • 单独的 Python 3.9 环境用于 Olive(Olive 0.9.x 有此要求)

使用两个 Python 版本可能看起来有些奇怪,但这反映了一个常见的实际设置:在一个环境中优化模型,在另一个环境中提供服务。优化是一次性的步骤。

安装 Olive 依赖项

在你的 Python 3.9 环境中:

1
2
pip install olive-ai onnxruntime onnxruntime-genai
pip install transformers>=4.45.0,<5.0.0

重要提示: Olive 与 Transformers 5.x 不兼容。必须使用 4.x 版本。

使用 Olive 进行模型优化

Microsoft Olive 是 Hugging Face 模型与 Foundry Local 可服务内容之间的桥梁。它在一个命令中处理 ONNX 转换、图优化和量化。

理解量化

量化通过将权重从高精度浮点转换为低精度整数来减小模型大小:

精度 大小减少 质量 最适合
FP32 基线 最佳 开发、调试
FP16 减小 50% 优秀 具有充足 VRAM 的 GPU 推理
INT8 减小 75% 非常好 平衡生产
INT4 减小 87.5% 良好 边缘设备、资源受限

我将使用 INT4 来展示最大压缩。对于具有更好质量的生产环境,可以考虑 INT8——只需在下面的命令中将 –precision int4 更改为 –precision int8。

运行优化

scripts/optimize_model.py 处理两件事:在本地下载模型(以避免身份验证问题),然后运行 Olive。

下载步骤很重要。ONNX Runtime GenAI 模型构建器内部请求 HuggingFace 身份验证,即使对于公共模型也是如此。我们不配置令牌,而是首先使用 token=False 下载模型,然后将 Olive 指向本地路径:

1
2
from huggingface_hub import snapshot_download
local_path = snapshot_download("Qwen/Qwen2.5-0.5B-Instruct", token=False)

然后针对本地副本运行 Olive 命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cmd = [
    sys.executable, "-m", "olive", "auto-opt",
    "--model_name_or_path", local_path,
    "--trust_remote_code",
    "--output_path", "models/qwen2.5-0.5b-int4",
    "--device", "cpu",
    "--provider", "CPUExecutionProvider",
    "--precision", "int4",
    "--use_model_builder",
    "--use_ort_genai",
    "--log_level", "1",
]

关键标志:–precision int4 将权重量化为 4 位整数,–use_model_builder 读取每个 transformer 层并将其导出为 ONNX,–use_ort_genai 以 Foundry Local 使用的格式输出。

运行它:

1
python scripts/optimize_model.py

此过程大约需要一分钟。完成后,你将看到输出目录结构。

models/qwen2.5-0.5b-int4/model/
├── model.onnx              # ONNX 图 (162 KB)
├── model.onnx.data         # 量化的 INT4 权重 (823 MB)
├── genai_config.json       # ONNX Runtime GenAI 配置
├── tokenizer.json          # 分词器词汇表 (11 MB)
├── vocab.json              # Token-to-ID 映射 (2.7 MB)
├── merges.txt              # BPE 合并 (1.6 MB)
├── tokenizer_config.json
├── config.json
├── generation_config.json
├── special_tokens_map.json
└── added_tokens.json

总大小:约 838MB——与原始模型相比大幅减小,同时保持了结构化任务(如测验生成)的可用质量。

在 Foundry Local 中注册

模型优化完成后,我们需要在 Foundry Local 中注册它。与云模型注册表不同,这里没有 CLI 命令——你将文件放在正确的目录中,Foundry 会自动发现它们。

Foundry 的模型注册表

1
2
3
foundry cache cd
# Windows: C:\Users\<username>\.foundry\cache\
# macOS/Linux: ~/.foundry/cache/

Foundry 按发布者组织模型:

.foundry/cache/models/
├── foundry.modelinfo.json  ← 官方模型目录
├── Microsoft/               ← 预优化的 Microsoft 模型
│   ├── qwen2.5-7b-instruct-cuda-gpu-4/
│   ├── Phi-4-cuda-gpu-1/
│   └── ...
└── Custom/                  ← 你的模型放在这里

注册脚本

scripts/register_model.sh 脚本做两件事:将所有模型文件复制到 Foundry 缓存,并创建 inference_model.json 配置文件。

关键文件是 inference_model.json——没有它,Foundry 不会识别你的模型:

1
2
3
4
5
6
7
8
9
{
  "Name": "qwen-quiz-int4",
  "PromptTemplate": {
    "system": "<|im_start|>system\n{Content}<|im_end|>",
    "user": "<|im_start|>user\n{Content}<|im_end|>",
    "assistant": "<|im_start|>assistant\n{Content}<|im_end|>",
    "prompt": "<|im_start|>user\n{Content}<|im_end|>\n<|im_start|>assistant"
  }
}

PromptTemplate 定义了 Qwen 2.5 期望的 ChatML 格式。{Content} 占位符是 Foundry 在运行时注入实际消息内容的位置。如果你部署的是 Llama 或 Phi 模型,你将使用它们各自的提示模板。

运行注册:

1
scripts/register_model.sh

验证注册

1
foundry cache ls

测试模型

1
foundry model run qwen-quiz-int4

模型通过 CPU 上的 ONNX Runtime 加载。尝试一个简单的提示来验证它是否响应。

与测验应用集成

这就是有趣的地方。应用程序级别的更改只需在 utils/foundry_client.py 中修改一行:

1
2
3
4
# 之前:
DEFAULT_MODEL_ALIAS = "qwen2.5-7b-instruct-cuda-gpu"
# 之后:
DEFAULT_MODEL_ALIAS = "qwen-quiz-int4"

但这一行修改引发了一些值得理解的问题。

问题 1:SDK 无法看到自定义模型

Foundry Local Python SDK 通过在官方目录中查找模型来解析模型——这是一个 Microsoft 发布的模型 JSON 文件。Custom/ 目录中的自定义模型不在该目录中。因此 FoundryLocalManager(“qwen-quiz-int4”) 会抛出"找不到模型"错误,尽管 foundry cache ls 和 foundry model run 都完美工作。

foundry_client.py 中的修复是双代码路径。它首先尝试 SDK(适用于目录模型),当失败并显示"在目录中找不到"错误时,它会回退到直接发现正在运行的服务端点。

工作流程变成两个终端:

终端 1: foundry model run qwen-quiz-int4

终端 2: python main.py

客户端自动发现端点并连接。对于目录模型,现有的 FoundryLocalManager 路径保持不变。

问题 2:工具调用格式

对于目录模型,Foundry 的服务器端中间件会拦截模型输出中的 <tool_call> 标签,并将它们转换为 API 响应中的结构化 tool_calls 对象。这是通过 foundry.modelinfo.json 中的元数据配置的。

对于自定义模型,这些元数据字段不被识别——Foundry 在 inference_model.json 中忽略它们。<tool_call> 标签作为原始文本传递到 response.choices[0].message.content 中。

由于我们的自定义模型输出完全相同的 <tool_call> 格式,我们在 agents/base_agent.py 中添加了一个小的回退解析器——这与我们在函数调用中探索的模式相同。

模型的行为是相同的;只是解析位置发生了变化——从服务器端(Foundry 中间件)到客户端(我们的代码)。

测试部署

模型在一个终端中运行,在另一个终端启动测验应用。

编排器成功调用了 generate_new_quiz 工具,QuizGeneratorAgent 生成了结构良好的测验 JSON。

模型限制

0.5B INT4 模型偶尔会在复杂推理或基本算术方面遇到困难。这是如此小且大量量化的模型的预期行为。对于需要更高准确性的生产用例,可以使用 Qwen 2.5-1.5B 或 Qwen 2.5-7B 以获得更好的质量,或使用 INT8 量化而不是 INT4。部署工作流程保持不变——只需在优化脚本中更改模型名称和精度。

你已经完成的工作

花点时间欣赏一下整个系列的完整旅程:

文章 你学到了什么
1. Phi-4 介绍 为什么 SLM 重要,性能与大小的权衡
2. 本地运行 Foundry Local 设置,基本推理
3. 函数调用 工具使用,外部 API 集成
4. 多智能体系统 编排,专家智能体
5. 部署 Olive 优化,Foundry Local 注册,自定义模型部署

现在我掌握了构建生产 SLM 应用程序的端到端技能:理解全景、使用 Foundry Local 进行本地开发、使用函数调用的智能体应用、多智能体架构、使用 Olive 进行模型优化,以及将自定义模型部署到边缘。

接下来做什么

下一个逻辑步骤是针对你的领域进行微调。在 USMLE 问题上训练的医学测验导师,在案例法上训练的法律助理,在内部文档上训练的公司入职机器人——使用相同的 Olive 工作流程来优化和部署你的微调模型。我们使用 Foundry Local 注册的相同 ONNX 模型也可以通过 ONNX Runtime Mobile 在移动设备上运行,或者容器化用于服务器端边缘部署。

资源:


原文链接: Deploying Custom Models with Microsoft Olive and Foundry Local