教程使用 llama.cpp 深度学习容器,在 SageMaker AI 上提供兼容 OpenAI 的聊天补全接口。通过 SemIf 扩展运行时,可在 CPU 或 GPU 上返回预设选项的概率。
需要路由请求、选择智能体动作或判定策略结果的应用,通常需要对一组固定选项进行评分,而不是生成自由形式的文本。通过 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 InvokeEndpoint 提供兼容 OpenAI 的聊天补全服务。随后,你将使用 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 镜像。容器会找到 /opt/ml/model 下的第一个 .gguf 文件,将 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 模式。发送一个包含 role 和 content 的 messages 数组,以及 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,一个专为智能体工作流构建、输出经过校准的模型。
当应用在请求时定义允许的选项,并需要模型理解非结构化上下文时,可以使用这种模式。示例包括将工单路由到某个队列、从已知集合中提取一个值、为事件分配优先级、判定策略结果,或选择智能体动作。将授权逻辑和确定性的业务规则放在模型之外,并在将模型概率视为实际运行中的置信度之前,对其进行验证。
当你需要通过 InvokeEndpoint 获取兼容 OpenAI 的聊天补全时,使用标准的 SageMaker AI 文本生成路径。SageMaker AI 镜像将 POST /invocations 映射到 llama-server 的 /v1/chat/completions 端点。只有当应用需要此接口未提供的行为时,才构建子镜像,例如 SemIf 对 logits 和状态的直接访问。
省去答案 token 的生成并不意味着省去模型推理。在 SemIf 读取最终 logits 之前,模型仍然需要处理完整的状态、问题和选项描述。保持提示词聚焦,并在投入生产之前,使用应用中带标注的决策样本评估所选模型。
以下基准测试适用于 SemIf 扩展,而非文本生成端点。模型大小、量化方式、提示词长度、运行时和 CPU 线程数都会影响每次决策。为了量化这些权衡,我们将同一组 231 个公开 JevBench 任务,分别串行发送到一个 CPU 端点和一个 GPU 端点。
与 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 Command Line Interface(AWS CLI)。
一个专用于可选 SemIf 扩展的 CodeBuild 服务角色。
一个 SageMaker AI 可以代入的 AWS Identity and Access Management(IAM)角色。
执行 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 上执行结构化决策。
克隆代码示例并安装其依赖项。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
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
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 校验和与已验证的值不一致,构建就会停止。
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 模型。从适用于 llama.cpp 的 AWS DLC 文档中获取通过摘要固定的 server-sagemaker-cpu-v1 镜像,然后创建 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"
从适用于 llama.cpp 的 AWS DLC 文档中获取通过摘要固定的 server-sagemaker-cpu-v1 镜像,然后创建 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}" 部署脚本会拒绝可变的镜像标签。
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 镜像,提供按优先级排序的实例池,并请求将整个模型卸载到 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,并查找确认所有模型层均已卸载到 GPU 的消息。
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 镜像,提供按优先级排序的实例池,并请求将整个模型卸载到 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,并查找确认所有模型层均已卸载到 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" } } 在应用层设置阈值和回退机制。不要用模型决策代替授权检查。
请求为每个允许的选项声明了稳定的 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 角色。
AWS DLC for llama.cpp 为你提供了托管基础,使你能够在 SageMaker AI 的 CPU 和 GPU 实例上部署决策模型并提供推理服务。它可以从 SageMaker AI 模型制品或配置的 Hugging Face 仓库中加载 GGUF 模型,然后通过 InvokeEndpoint 提供兼容 OpenAI 的聊天补全服务,无需自定义容器。当你的应用需要直接访问 logits 并执行模型状态操作时,可以使用 SemIf 适配器扩展 DLC。对于延迟不敏感的工作负载,选择 CPU;当提示词预填充和尾部延迟会影响应用体验时,选择 GPU。
参阅代码示例、AWS DLC for llama.cpp 文档,以及 SageMaker AI 自定义推理容器接口规范,根据你的工作负载调整部署。