路由请求、选择智能体动作或分配策略结果的应用程序,通常需要在一组固定选项上给出评分,而不是生成自由格式的文本。部署这些决策模型时 llama.cpp 于 Amazon SageMaker AI 为您提供了在 CPU 或 GPU 基础设施上部署量化模型的托管路径。
本文继续介绍用于生成式 AI 推理的专用 AWS Deep Learning Containers (DLC) 系列。之前的文章涵盖了 WhisperX, vLLM-Omni – 第1部分,以及 vLLM-Omni – 第2部分.
在这篇文章中,你首先使用 用于 llama.cpp 的 AWS DLC 通过 SageMaker AI 提供 OpenAI 兼容的聊天补全服务 InvokeEndpoint。然后,您使用以下内容扩展同一运行时: SemIf,前身为 OpenJev,它在不解码答案 token 的情况下返回针对已声明选项的概率分布。随附的 代码示例 包括 CPU 和 GPU 部署选项。
解决方案概述
图1展示了来自同一个打包模型的两种部署路径。文本生成路径使用适用于llama.cpp的AWS DLC,并通过OpenAI兼容API返回生成的响应。结构化决策路径构建一个子镜像,将默认服务器替换为SemIf适配器,并返回已声明选项的概率。
图1:根据工作负载的 API 和延迟要求,在 CPU 或 GPU 上运行用于 llama.cpp 的 AWS DLC 或 SemIf 子镜像
对于标准路径,将模型打包为 model.tar.gz 中 Amazon Simple Storage Service (Amazon S3) 并直接引用已发布的 DLC 镜像。容器会找到第一个 .gguf 归档于 /opt/ml/model, 地图 POST /invocations 至 llama-server 在 /v1/chat/completions, 并将 SM_LLAMA_CPP_* 环境变量传递给 llama.cpp 参数。
该 llama-server HTTP API 未公开 SemIf 所使用的最终位置 logits 和状态操作。因此结构化决策路径使用 AWS CodeBuild 构建子镜像并将其存储在 Amazon Elastic Container Registry (Amazon ECR)中。子镜像保留 DLC 已编译的 CPU 或 CUDA 库,同时将其入口点替换为与 SageMaker 兼容的适配器。
理解输入和输出模式
用于 llama.cpp 的 AWS DLC 接受 OpenAI Chat Completions 模式。发送一个 messages 数组,其中包含 role 和 content,以及诸如 max_tokens 和 stream:
{
"messages": [
{
"role": "user",
"content": "A customer cannot receive a password reset email. Reply with the best support queue."
}
],
"max_tokens": 64,
"stream": false
}
等生成控制参数。响应使用兼容 OpenAI 的 choices 数组。从 choices[0].message.content.
中读取生成的文本。SemIf 使用受约束的决策模式而非聊天消息。请求包含调用方定义的 ID、非结构化状态、一个问题以及允许选项的有序列表:
{
"decision": {
"id": "support-route",
"state": "The customer cannot receive a password reset email.",
"question": "Which queue should handle this request?",
"options": [
{"id": "account_access", "description": "Account access support."},
{"id": "billing", "description": "Billing support."}
]
}
}
响应按相同顺序返回 option_ids, probabilities和原始 option_logits ,以及提示词、计时和固定的模型元数据。借助稳定的选项 ID,您的应用程序可以应用阈值、回退和策略检查,而无需解析生成的文本。
SemIf 属于更广泛的一类决策模型,这类模型从非结构化状态中对应用程序定义的结果进行评分。其他实现包括 Jev、TypeSafe 的决策模型及其 /v1/systemone API; Kev,一个为 TypeSafe API 兼容性设计的模型服务器;以及 Strands Decider 2B,一个专为智能体工作流构建、具有校准输出的模型。
选择合适的决策工作流
当您的应用程序在请求时定义允许的选项,并需要模型来解释非结构化上下文时,请使用此模式。示例包括将案例路由到队列、从已知集合中提取一个值、分配事件优先级、检查策略结果或选择智能体操作。请将授权和确定性的业务规则保留在模型之外,并在将模型概率视为运营置信度之前对其进行验证。
当您需要通过 OpenAI 兼容的聊天补全时,请使用标准的 SageMaker AI 文本生成路径 InvokeEndpoint获得 OpenAI 兼容的聊天补全时,请使用标准的 SageMaker AI 文本生成路径。SageMaker AI 镜像将 POST /invocations 映射到 llama-server /v1/chat/completions 端点。仅当您的应用程序需要此接口不提供的行为(例如 SemIf 的直接 logits 和状态访问)时,才构建子镜像。
移除答案 token 的生成并不会移除模型推理。在 SemIf 读取最终 logits 之前,模型仍会处理完整的状态、问题和选项描述。请保持提示词聚焦,并在生产使用之前使用您应用程序中带标签的决策来评估所选模型。
针对您的延迟和质量目标进行调优
以下基准测试适用于 SemIf 扩展,而非文本生成端点。模型大小、量化、提示词长度、运行时和 CPU 线程数都会影响每次决策。为了量化这些权衡,我们将相同的 231 个公开的 JevBench 任务串行地发送到一个 CPU 端点和一个 GPU 端点。
| 配置 | AWS DLC for llama.cpp | 有效响应 | 准确率 | 客户端 p50 | 客户端 p95 |
Qwen3.5-4B Q4_K_M, ml.c6i.8xlarge,32 线程 |
llama-cpp:server-sagemaker-cpu-v1 | 231/231 | 179/231 (77.5%) | 1.715 秒 | 19.291 秒 |
Qwen3.5-4B Q4_K_M, ml.g6e.xlarge,完全 GPU 卸载 |
llama-cpp:server-sagemaker-cuda-v1 | 231/231 | 180/231 (77.9%) | 0.352 秒 | 0.599 秒 |
与 CPU 配置相比,GPU 端点在 p50 时快 4.9 倍,在 p95 时快 32.2 倍。该示例使用 SageMaker AI 实例池功能按优先级顺序列出兼容的 GPU 实例类型,因此当首选实例不可用时,SageMaker 可以选择替代实例。该池的排序为 g6e, g6, g5,然后是 g4dn。SageMaker 选择了 ml.g6e.xlarge,启动日志确认 llama.cpp 将全部 34 个模型层卸载到了 NVIDIA L40S。CPU 和 GPU 镜像使用了不同的 llama.cpp 构建,并在六个困难任务上产生了不同的预测。请将这一个答案的准确率差异视为运行时差异,而不是 GPU 卸载能提升模型质量的证据。
更小的模型降低了中位延迟,但 2B 和 0.8B 候选模型的准确率分别为 66.7% 和 58.4%。因此,该示例保留了 4B Q4_K_M 模型作为其质量优先的默认选择。这些测量包含网络延迟,并非服务保证。
请根据您自己的决策对完整的模型和基础设施配置进行基准测试。对于不敏感于延迟、可以容忍数秒响应时间的后台、离线或低吞吐量工作负载,请使用 CPU。对于交互式决策、长提示词或有严格尾延迟目标的工作负载,请使用 GPU。保持状态和选项描述简洁,将共享相同状态的决策组合到一个请求中,并为并发扩展端点实例。共享状态评分会重用共同的提示词前缀,而适配器会返回可重试的 HTTP 503 响应,而不是让其有状态的 llama.cpp 上下文周围形成无界队列。
先决条件
你需要:
- 一个 AWS 账户以及位于您部署所在 AWS 区域的一个 S3 存储桶。
- Python 3.10 或更高版本、Git,以及 AWS 命令行界面(AWS CLI)。
- 一个专用于可选 SemIf 扩展的特定用途 CodeBuild 服务角色。
- 一篇 AWS Identity and Access Management (IAM) SageMaker AI 可以承担的角色。
- SageMaker AI、Amazon ECR、Amazon S3、CodeBuild、IAM pass-role 操作以及 Amazon CloudWatch Logs.
- 兼容的 x86 CPU 或 NVIDIA GPU SageMaker 实时端点的配额。
文本生成示例使用 CPU DLC 和固定的 CPU 实例。SemIf 扩展将 CPU 保留为非延迟敏感工作负载的默认选项,并提供带有序实例池的可选 CUDA 镜像。评测 SageMaker AI 定价 在创建端点之前。
部署一个决策模型并使用 SemIf 对其进行扩展
使用以下步骤部署标准文本生成端点,然后在 CPU 或 GPU 上构建可选的 SemIf 扩展以实现结构化决策。
- 克隆代码示例并安装其依赖项。
git clone https://github.com/aws-samples/sagemaker-genai-hosting-examples.git cd sagemaker-genai-hosting-examples/03-features/semif-decision-model python3 -m venv .venv . .venv/bin/activate python3 -m pip install -r deploy/requirements.txt - 为您的账户设置部署值。
export AWS_REGION="<your-region>" export ARTIFACT_BUCKET=amzn-s3-demo-semif-artifacts export CODEBUILD_ROLE_ARN=arn:aws:iam::111122223333:role/semif-codebuild-role export SAGEMAKER_ROLE_ARN=arn:aws:iam::111122223333:role/semif-sagemaker-role export STANDARD_ENDPOINT_NAME=llamacpp-gguf export SEMIF_ENDPOINT_NAME=semif-decision-workflows - 使用 CodeBuild 打包固定版本的 GGUF 模型和分词器。
python deploy/prepare_model_build.py \ --region "$AWS_REGION" \ --bucket "$ARTIFACT_BUCKET" \ --codebuild-role-arn "$CODEBUILD_ROLE_ARN" \ --model-s3-uri "s3://$ARTIFACT_BUCKET/semif-llamacpp/model.tar.gz"等待返回的构建 ID 达到
SUCCEEDED。构建会将源代码修订版本和校验和记录在model-manifest.json中,如果 GGUF 校验和与已验证的值不同则会停止。 - 使用用于 llama.cpp 的 AWS DLC 部署 GGUF 模型。
解析一个以摘要固定的
server-sagemaker-cpu-v1镜像,来源为 用于 llama.cpp 的 AWS DLC 文档,然后创建 SageMaker AI 模型、端点配置和端点:export STANDARD_IMAGE_URI="<published-llama-cpp-cpu-image>@sha256:<digest>" python deploy/deploy_standard_endpoint.py \ --region "$AWS_REGION" \ --image-uri "$STANDARD_IMAGE_URI" \ --model-data-url "s3://$ARTIFACT_BUCKET/semif-llamacpp/model.tar.gz" \ --role-arn "$SAGEMAKER_ROLE_ARN" \ --instance-type ml.c6i.2xlarge \ --name "$STANDARD_ENDPOINT_NAME"选择
server-sagemaker-cpu-v1或server-sagemaker-cuda-v1镜像,而不是对应的 EC2 镜像。这些 SageMaker AI 镜像实现了/ping和/invocations容器契约,并将调用负载映射到聊天补全。本演练通过 Amazon S3 中的 SageMaker AI 模型工件提供 GGUF 模型。作为替代方案,当端点具有网络出站流量时,您可以配置
SM_LLAMA_CPP_HF_REPO和SM_LLAMA_CPP_HF_FILE以便从 Hugging Face 下载 GGUF 文件。此路径不需要 Dockerfile 或 ECR 存储库。等待端点进入
InService状态,然后发送兼容 OpenAI 的聊天请求:python deploy/invoke_standard_endpoint.py \ --region "$AWS_REGION" \ --endpoint-name "$STANDARD_ENDPOINT_NAME" - 构建可选的 SemIf 子镜像并解析其不可变摘要。
对于 CPU:
python deploy/prepare_build.py \ --region "$AWS_REGION" \ --bucket "$ARTIFACT_BUCKET" \ --codebuild-role-arn "$CODEBUILD_ROLE_ARN"对于 GPU,改为构建 CUDA 子镜像:
python deploy/prepare_build.py \ --region "$AWS_REGION" \ --bucket "$ARTIFACT_BUCKET" \ --codebuild-role-arn "$CODEBUILD_ROLE_ARN" \ --dockerfile Dockerfile.gpu \ --image-tag "gpu-$(date +%Y%m%d%H%M%S)"构建成功后,构造不可变的镜像 URI:
ECR_REPOSITORY_URI=$(aws ecr describe-repositories \ --region "$AWS_REGION" \ --repository-names semif-llamacpp-dlc \ --query 'repositories[0].repositoryUri' \ --output text) IMAGE_DIGEST=$(aws ecr describe-images \ --region "$AWS_REGION" \ --repository-name semif-llamacpp-dlc \ --query 'sort_by(imageDetails,&imagePushedAt)[-1].imageDigest' \ --output text) export IMAGE_URI="${ECR_REPOSITORY_URI}@${IMAGE_DIGEST}"部署脚本会拒绝可变的镜像标签。
- 部署 SemIf 端点。
CPU 配置使用固定的实例类型:
python deploy/deploy_endpoint.py \ --region "$AWS_REGION" \ --image-uri "$IMAGE_URI" \ --model-data-url "s3://$ARTIFACT_BUCKET/semif-llamacpp/model.tar.gz" \ --role-arn "$SAGEMAKER_ROLE_ARN" \ --instance-type ml.c6i.8xlarge \ --threads 32 \ --name "$SEMIF_ENDPOINT_NAME"等待返回的端点名称进入
InService状态。此路径设置n_gpu_layers=0。适配器围绕一个有状态的 llama.cpp 上下文进行评分序列化,因此请在测量您的工作负载后通过端点实例进行横向扩展,而不是假设在单个容器内进行并行评分。对于 GPU 镜像,请提供有序的实例池并请求完整的模型卸载:
python deploy/deploy_endpoint.py \ --region "$AWS_REGION" \ --image-uri "$IMAGE_URI" \ --model-data-url "s3://$ARTIFACT_BUCKET/semif-llamacpp/model.tar.gz" \ --role-arn "$SAGEMAKER_ROLE_ARN" \ --instance-pool ml.g6e.xlarge \ --instance-pool ml.g6.2xlarge \ --instance-pool ml.g5.2xlarge \ --instance-pool ml.g4dn.2xlarge \ --gpu-layers=-1 \ --provision-timeout 1200 \ --name "$SEMIF_ENDPOINT_NAME"实例池是有序的放置回退机制,而不是推理负载均衡。SageMaker 会为该变体选择一个可用的类型。在 SageMaker AI 遍历池的过程中,端点创建可能需要更长时间。请检查启动日志中是否出现所选 GPU 以及确认所有模型层均已卸载的消息。
- 使用示例决策调用 SemIf 端点。
该请求为每个允许的选项声明了稳定 ID:
{ "decision": { "id": "support-route", "state": "Customer asks to reset a forgotten password and says the reset email never arrived.", "question": "Which queue should handle this request?", "options": [ {"id": "account_access", "description": "Account access and authentication support."}, {"id": "billing", "description": "Billing and payment support."}, {"id": "sales", "description": "Sales and product evaluation."} ] } }python deploy/invoke_endpoint.py \ --region "$AWS_REGION" \ --endpoint-name "$SEMIF_ENDPOINT_NAME"响应保留了选项顺序,并包含条件概率、原始 logits、token 和提示元数据,以及固定的模型身份:
{ "result": { "id": "support-route", "option_ids": ["account_access", "billing", "sales"], "probabilities": [0.99996, 0.00003, 0.00001], "option_logits": [28.47, 17.92, 17.04], "prompt_sha256": "016267542103ab91422c425d2234ec7e1b9aef77f9d3e481b96db2de7cca470a", "readout": "quantized last-position logits restricted to declared answer slots; no generated tokens" } }在应用层面设置阈值和回退机制。不要将模型决策用作授权的替代品。
清理
删除 SageMaker AI 端点、端点配置和模型:
python deploy/cleanup_endpoint.py \
--region "$AWS_REGION" \
--name "$STANDARD_ENDPOINT_NAME"
python deploy/cleanup_endpoint.py \
--region "$AWS_REGION" \
--name "$SEMIF_ENDPOINT_NAME"
该脚本只删除那些被明确指定名称的 SageMaker AI 资源。当不再需要时,请删除 CodeBuild 项目、ECR 仓库、Amazon S3 中的模型和源对象、日志以及专用的 IAM 角色。
结论
用于 llama.cpp 的 AWS DLC 为您在 SageMaker AI 上跨 CPU 和 GPU 实例服务决策模型提供了一个托管的基础。它可以从 SageMaker AI 模型工件或配置的 Hugging Face 仓库加载 GGUF 模型,然后通过 InvokeEndpoint 提供 OpenAI 兼容的聊天补全服务,无需自定义容器。当您的应用需要直接的 logits 和模型状态操作时,可以使用 SemIf 适配器扩展该 DLC。对于不延迟敏感的工作负载,请选择 CPU;当提示预填充和尾延迟影响应用体验时,请选择 GPU。
查看 代码示例, AWS DLC for llama.cpp 文档以及 SageMaker AI 自定义推理容器契约 ,以便针对您的工作负载调整部署。
