使用 Modal 在云端 GPU 上运行 Outlines 结构化生成:从镜像构建到 JSON Schema 约束推理完整指南
2026/9/14 15:54:18 网站建设 项目流程

使用 Modal 在云端 GPU 上运行 Outlines 结构化生成:从镜像构建到 JSON Schema 约束推理完整指南

【免费下载链接】outlinesStructured Outputs项目地址: https://gitcode.com/GitHub_Trending/ou/outlines

导读

本文是一份端到端的实战指南,讲解如何借助 Modal 示例文件,可直接对照运行。

为什么用 Modal 跑 Outlines

Outlines 是一个结构化输出(Structured Outputs)生成框架,其核心思想是"在生成过程中保证输出结构合法,而不是生成完再靠解析去修补"。但要让这一能力落地,前提是你有一块能跑大模型的 GPU。Modal 是一个 Serverless 云平台,可以按需为你在云端创建 GPU 实例,并在几秒钟内完成代码打包、容器构建与调度。把两者结合,就能在没有本地怪兽级显卡的情况下,快速、弹性地在云端完成带格式约束的模型推理。

环境准备

官方建议在虚拟环境中安装modaloutlines。先创建并激活虚拟环境:

python -m venv venv source venv/bin/activate

然后安装依赖包:

pip install modal outlines

其中modal是 Modal 的 Python 客户端(用于定义镜像、App、函数并触发云端执行),outlines是本项目的结构化生成框架。如果你希望把仓库克隆到本地对照阅读,也可以先git clone本项目后再进入examples/目录查看配套示例。

构建容器镜像

Modal 通过Image对象声明运行环境。下面的代码创建了一个基于 Debian slim 的 Python 3.11 镜像,并预装 Outlines 及其依赖的 Transformers 生态组件:

from modal import Image, App, gpu import os # 创建 Modal App 对象,名称为 "outlines-app"。 # 还有其它可选参数,如 secrets、调度策略等。 app = App(name="outlines-app") # 指定要使用的语言模型。 # 另一个不错的选择是 "NousResearch/Hermes-2-Pro-Mistral-7B" language_model = "mistral-community/Mistral-7B-v0.2" # 请设置环境变量 HF_TOKEN,值为你的 Hugging Face API token。 # 下面代码中的 .env({...}) 部分会把本地环境中的 token 拷贝进容器。 outlines_image = Image.debian_slim(python_version="3.11").pip_install( "outlines", "transformers", "datasets", "accelerate", "sentencepiece", ).env({ # 这会把本地已有的 HF_TOKEN 环境变量带入容器。 'HF_TOKEN': os.environ['HF_TOKEN'] # 若想直接在代码中写死 token,取消下面一行注释并替换成你的 token。 # 'HF_TOKEN':'YOUR_TOKEN' })

要点说明:

  • Image.debian_slim(python_version="3.11")声明基础镜像与 Python 版本;pip_install(...)中的transformersdatasetsacceleratesentencepiece是 Outlines 通过from_transformers加载 Hugging Face 模型时所依赖的关键库。
  • 若目标模型是 Hugging Face 上的门控模型(gated model),必须在容器内提供访问 token。最佳实践是通过.env({'HF_TOKEN': os.environ['HF_TOKEN']})从本地环境透传,避免把 token 明文写进代码;代码中也保留了"直接写死 token"的注释行作为备选方案。
  • 仓库配套示例 examples/modal_example.py 对依赖版本做了显式锁定(如outlines==1.0.0transformers==4.38.2datasets==2.18.0accelerate==0.27.2),在生产环境建议同样固定版本以获得可复现的构建。

配置容器:启动时预热模型

对于需要长时间运行的 Modal 应用,官方建议在容器启动时下载模型,而不是等到函数被调用时才下载。这样模型权重会被缓存,后续冷启动与重复运行都会显著加速:

# 该函数负责从 Hugging Face 拉取模型。 # Modal 容器启动时会调用它,适合做模型下载、环境变量初始化等准备工作。 def import_model(): import outlines import transformers outlines.from_transformers( transformers.AutoModelForCausalLM.from_pretrained(language_model), transformers.AutoTokenizer.from_pretrained(language_model) ) # 这行代码告诉容器在启动时执行 import_model 函数。 outlines_image = outlines_image.run_function(import_model)

run_function是 Modal 镜像构建阶段的能力:它会在构建镜像时于容器内执行一次import_model,把模型权重固化进镜像层,从而避免每次冷启动都重新下载数 GB 的参数文件。这也是后面推理函数内只需"加载权重到显存"而非"下载权重"的前提。

定义输出 Schema:约束 JSON 结构

我们将复现 README 中的 JSON 结构化生成示例,为角色描述定义一个 JSON Schema。Schema 中包含一个带nameagearmorweaponstrength字段的角色对象,其中armorweapon通过$ref引用枚举定义:

schema = """{ "title": "Character", "type": "object", "properties": { "name": { "title": "Name", "maxLength": 10, "type": "string" }, "age": { "title": "Age", "type": "integer" }, "armor": {"$ref": "#/definitions/Armor"}, "weapon": {"$ref": "#/definitions/Weapon"}, "strength": { "title": "Strength", "type": "integer" } }, "required": ["name", "age", "armor", "weapon", "strength"], "definitions": { "Armor": { "title": "Armor", "description": "An enumeration.", "enum": ["leather", "chainmail", "plate"], "type": "string" }, "Weapon": { "title": "Weapon", "description": "An enumeration.", "enum": ["sword", "axe", "mace", "spear", "bow", "crossbow"], "type": "string" } } }"""

这个 schema 中的enum是约束的核心:模型只能在["leather", "chainmail", "plate"]中选择装甲,只能在六种武器中二选一或择一,agestrength必须输出整数,name不得超过 10 个字符。在推理时,Outlines 会把该 schema 编译为 logits 处理器(logits processor),在每一步解码时屏蔽掉不可能满足 schema 的 token,从而从机制上保证输出合法。

从源码看,Outlines 的JsonSchema类型位于 src/outlines/types/dsl.py,它的构造参数非常灵活:除了本文使用的 JSON schema字符串,还接受dict、Pydantic 模型、TypedDict、dataclass 以及 genSON schema builder。构造时会通过jsonschema.Draft7Validator.check_schema对 schema 做合法性校验,并支持whitespace_patternensure_ascii等选项。在outlines.types包中,JsonSchemaCFGRegexChoice等 DSL 类型一起构成结构化输出的类型体系(见 src/outlines/types/init.py),并在顶层通过outlines.json_schema导出(见 src/outlines/init.py)。

编写云端推理函数

在 Modal 上做推理,需要把推理逻辑包进@app.function装饰器中,并把镜像与 GPU 规格作为参数传入。下面选择 A100 80GB 实例:

@app.function(image=outlines_image, gpu=gpu.A100(size='80GB')) def generate( prompt: str = "Amiri, a 53 year old warrior woman with a sword and leather armor.", ): # 注意:此函数运行在容器内,因此需要在这里导入所需库。 import outlines import transformers from outlines.types import JsonSchema # 将模型加载进显存。前面的 import_model 已完成下载, # 所以这里只负责把权重加载到 GPU 内存。 outlines.from_transformers( transformers.AutoModelForCausalLM.from_pretrained(language_model, device_map="cuda"), transformers.AutoTokenizer.from_pretrained(language_model) ) # 基于模型与 JSON schema 创建生成器。 generator = outlines.Generator(model, JsonSchema(schema)) # 用指令标签 ([INST] 与 [/INST]) 包裹 prompt,指示这是指令任务。 # 不同模型的指令标签格式不同,请以模型官方文档为准。 character = generator( f"<s>[INST]Give me a character description. Describe {prompt}.[/INST]" ) # 打印生成的角色描述。 print(character)

几个值得展开的实现细节:

  • 为什么要在函数内重新导入库@app.function中的函数体运行在远端容器进程里,与本地 Python 进程不共享命名空间,因此outlinestransformers需要在函数内显式导入。
  • device_map="cuda":把模型权重显式放到 GPU 上。仓库中from_transformers的实现位于 src/outlines/models/transformers.py,它根据传入的是PreTrainedTokenizer还是ProcessorMixin分别构造TransformersTransformersMultiModal包装器。该包装器的generate方法会把用户传入的输出类型转成transformersLogitsProcessorList后交给model.generate执行(见同文件 L269-L313)。
  • outlines.Generator(model, JsonSchema(schema))做了什么:见 src/outlines/generator.py,SteerableGenerator在构造时会把JsonSchema类型通过python_types_to_terms归一化,再调用get_json_schema_logits_processor预编译出 logits 处理器并缓存。logits 处理器的构建通常比较昂贵,因此 Generator 把它存储复用,在每次调用时reset()后传入模型(见 L280-L300)。这正是"结构化生成"的底层原理:推理过程中每一步采样都被 logits 处理器约束,只允许产出符合 JSON Schema 的 token 序列。
  • 指令标签(instruction tags):Mistral 系模型要求用<s>[INST] ... [/INST]包裹指令,不同厂商/家族的模型标签格式可能不同,务必查阅所选模型文档。

定义本地入口并触发云端执行

@app.local_entrypoint()装饰器把main声明为使用 Modal CLI 启动时的本地入口函数:

@app.local_entrypoint() def main( prompt: str = "Amiri, a 53 year old warrior woman with a sword and leather armor.", ): # 调用上面定义的 generate 函数。.remote() 表示在云端机器上运行; # 若想本地运行可改用 .local(),但需要额外的环境配置。 generate.remote(prompt)

generate.remote(prompt)会触发一次远程函数调用:Modal 先在云端拉起(或复用)配置了outlines_image的容器,将请求调度到 A100 GPU 上执行generate,再把函数内的print输出回传终端。

仓库中的 examples/modal_example.py 是同一方案的完整可运行版本,可作为对照:它固定了依赖版本、使用mistralai/Mistral-7B-Instruct-v0.2模型、用gpu="A100-40GB"声明 GPU(字符串形式的简写),并采用outlines.json_schema(schema)的简洁 API 直接在模型调用中指定 schema。你可以把本指南中的代码保存为example.py,或直接参考该文件。

在云端运行

首先确认已安装 Modal 客户端(如未安装则执行):

pip install modal

然后获取 Modal 访问 token 并完成本地配置:

modal setup

按提示完成认证后,一条命令即可在云端运行推理:

modal run example.py

运行过程中你会看到 Modal 应用初始化(构建镜像、拉起容器、加载模型),随后终端中很快出现print函数输出的角色描述,例如一个包含nameagearmorweaponstrength五个字段、且完全符合上文 JSON Schema 的合法 JSON。至此,一次"云端 GPU + 结构化生成"的完整链路就跑通了。

常见问题与调优建议

  • 门控模型 403:确保本地已设置HF_TOKEN环境变量且 token 具备目标模型仓库的访问权限;镜像构建时.env(...)只在有该环境变量时才能成功透传。
  • 冷启动过慢:确认import_model已通过run_function挂到镜像上,让模型下载发生在镜像构建阶段而非运行时;首次运行后 Modal 会缓存镜像与模型,后续启动显著加快。
  • GPU 选型:7B 级模型用 A100-40GB 即可(见 examples/modal_example.py),更大模型再考虑 A100-80GB 或更高规格;Modal 支持多种 GPU 简写,可参考其官方 GPU 文档。
  • 调试与验证:本仓库的 tests/models/test_transformers.py 展示了from_transformers的实例化契约(如错误传入int会抛ValueError、Mamba/BART 等架构均被支持、device_dtype参数可指定torch.bfloat16等),本地调试结构化生成时可参考这些测试来验证模型包装是否正确。

小结

本文完整演示了"Modal 云端 GPU + Outlines 结构化生成"的落地路径:先通过Image.debian_slim().pip_install().env().run_function()构建并预热镜像,再用@app.function(image=..., gpu=...)声明带 GPU 的推理函数,配合outlines.Generator(model, JsonSchema(schema))完成受 JSON Schema 约束的生成,最后由@app.local_entrypoint()modal run一键触发。整个过程无需本地 GPU,也无需手动运维云主机——这正是 Serverless 平台与结构化生成框架结合的价值所在。

【免费下载链接】outlinesStructured Outputs项目地址: https://gitcode.com/GitHub_Trending/ou/outlines

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询