0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
CI / Shell Format Check (push) Has been cancelled
CI / Check Ruby (3.4) (push) Has been cancelled
CI / CI Config (push) Has been cancelled
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Has been cancelled
CI / Build on Node ${{ matrix.node }} (push) Has been cancelled
CI / Style Check (push) Has been cancelled
CI / Generate Assets (push) Has been cancelled
CI / Check Python (3.14) (push) Has been cancelled
CI / Check Python (3.9) (push) Has been cancelled
CI / Build Docs (push) Has been cancelled
CI / Code Scan Action (push) Has been cancelled
CI / Site tests (push) Has been cancelled
CI / webui tests (push) Has been cancelled
CI / Run Integration Tests (push) Has been cancelled
CI / Run Smoke Tests (push) Has been cancelled
CI / Go Tests (push) Has been cancelled
CI / Share Test (push) Has been cancelled
CI / Redteam (Production API) (push) Has been cancelled
CI / Redteam (Staging API) (push) Has been cancelled
CI / GitHub Actions Lint (push) Has been cancelled
CI / Check Ruby (3.0) (push) Has been cancelled
release-please / release-please (push) Has been cancelled
release-please / build (push) Has been cancelled
release-please / publish-npm (push) Has been cancelled
release-please / publish-npm-backfill (push) Has been cancelled
release-please / docker (push) Has been cancelled
release-please / publish-code-scan-action (push) Has been cancelled
release-please / attest-code-scan-action (push) Has been cancelled
226 lines
7.0 KiB
Python
226 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
SageMaker Deployment Helper Script
|
|
|
|
This script helps deploy a test model on Amazon SageMaker for testing the promptfoo SageMaker provider.
|
|
It uses the Hugging Face integration with SageMaker to deploy models.
|
|
|
|
Prerequisites:
|
|
- AWS CLI configured
|
|
- Required Python packages: sagemaker, boto3
|
|
- SageMaker execution role with appropriate permissions
|
|
|
|
Usage:
|
|
python deploy-test-model.py --model-id meta-llama/Llama-2-7b-chat-hf --task text-generation
|
|
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import time
|
|
from datetime import datetime
|
|
|
|
import boto3
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Parse command line arguments
|
|
parser = argparse.ArgumentParser(description="Deploy a Hugging Face model to SageMaker")
|
|
parser.add_argument(
|
|
"--model-id",
|
|
required=True,
|
|
help="Hugging Face model ID (e.g., meta-llama/Llama-2-7b-chat-hf)",
|
|
)
|
|
parser.add_argument(
|
|
"--task",
|
|
default="text-generation",
|
|
help="Task of the model (default: text-generation)",
|
|
)
|
|
parser.add_argument(
|
|
"--instance-type",
|
|
default="ml.g5.2xlarge",
|
|
help="SageMaker instance type (default: ml.g5.2xlarge)",
|
|
)
|
|
parser.add_argument(
|
|
"--endpoint-name", help="Custom endpoint name (default: based on model name)"
|
|
)
|
|
parser.add_argument("--region", help="AWS region (default: from AWS CLI configuration)")
|
|
parser.add_argument(
|
|
"--role-name",
|
|
help="SageMaker execution IAM role name (if not specified, will try to find one)",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Get the AWS region
|
|
session = boto3.session.Session()
|
|
region = args.region or session.region_name
|
|
if not region:
|
|
logger.error("AWS region not specified and not found in AWS CLI configuration")
|
|
raise SystemExit(1)
|
|
|
|
# Generate endpoint name if not provided
|
|
if not args.endpoint_name:
|
|
# Extract model name from model ID and create a timestamp
|
|
model_name = args.model_id.split("/")[-1].lower()
|
|
timestamp = datetime.now().strftime("%m%d%H%M")
|
|
args.endpoint_name = f"promptfoo-test-{model_name}-{timestamp}"
|
|
|
|
# Connect to AWS services
|
|
iam = boto3.client("iam", region_name=region)
|
|
sagemaker_client = boto3.client("sagemaker", region_name=region)
|
|
|
|
|
|
# Find or get SageMaker execution role
|
|
def get_sagemaker_role() -> str:
|
|
"""Return the requested role ARN or discover a SageMaker execution role."""
|
|
if args.role_name:
|
|
try:
|
|
role = iam.get_role(RoleName=args.role_name)
|
|
return role["Role"]["Arn"]
|
|
except Exception as e:
|
|
logger.error(f"Error getting specified role: {e}")
|
|
raise SystemExit(1)
|
|
|
|
# Try to find a SageMaker execution role
|
|
try:
|
|
roles = iam.list_roles()
|
|
for role in roles["Roles"]:
|
|
if "AmazonSageMaker-ExecutionRole" in role["RoleName"]:
|
|
logger.info(f"Found SageMaker role: {role['RoleName']}")
|
|
return role["Arn"]
|
|
|
|
logger.error(
|
|
"No SageMaker execution role found. Please specify a role with --role-name"
|
|
)
|
|
raise SystemExit(1)
|
|
except Exception as e:
|
|
logger.error(f"Error finding SageMaker role: {e}")
|
|
raise SystemExit(1)
|
|
|
|
|
|
role_arn = get_sagemaker_role()
|
|
logger.info(f"Using role ARN: {role_arn}")
|
|
|
|
# Create model
|
|
try:
|
|
logger.info(f"Creating model: {args.model_id}")
|
|
|
|
# HuggingFace Container settings
|
|
# Find the latest container image for the region
|
|
transformers_version = "4.28.1"
|
|
pytorch_version = "2.0.0"
|
|
python_version = "py310"
|
|
|
|
hf_container = f"763104351884.dkr.ecr.{region}.amazonaws.com/huggingface-pytorch-tgi:{transformers_version}-transformers{pytorch_version}-cuda11.8-{python_version}"
|
|
|
|
# Create model
|
|
model_name = f"promptfoo-test-model-{int(time.time())}"
|
|
|
|
# Hub config for text generation interface
|
|
hub_config = {"HF_MODEL_ID": args.model_id, "HF_TASK": args.task}
|
|
|
|
# Generation parameters for text generation models
|
|
if args.task == "text-generation":
|
|
hub_config["PARAMETERS"] = {
|
|
"max_new_tokens": 256,
|
|
"temperature": 0.7,
|
|
"return_full_text": False,
|
|
}
|
|
|
|
# Create the model
|
|
create_model_response = sagemaker_client.create_model(
|
|
ModelName=model_name,
|
|
PrimaryContainer={
|
|
"Image": hf_container,
|
|
"Environment": {
|
|
"HF_MODEL_ID": args.model_id,
|
|
"HF_TASK": args.task,
|
|
"HF_MODEL_QUANTIZE": "bitsandbytes", # Optional: for quantization
|
|
"SM_NUM_GPUS": "1",
|
|
"MAX_INPUT_LENGTH": "2048",
|
|
"MAX_TOTAL_TOKENS": "4096",
|
|
"HF_HUB_CONFIG": json.dumps(hub_config),
|
|
},
|
|
},
|
|
ExecutionRoleArn=role_arn,
|
|
)
|
|
|
|
logger.info(f"Model created: {model_name}")
|
|
|
|
# Create endpoint configuration
|
|
endpoint_config_name = f"{args.endpoint_name}-config"
|
|
|
|
logger.info(f"Creating endpoint configuration: {endpoint_config_name}")
|
|
sagemaker_client.create_endpoint_config(
|
|
EndpointConfigName=endpoint_config_name,
|
|
ProductionVariants=[
|
|
{
|
|
"VariantName": "AllTraffic",
|
|
"ModelName": model_name,
|
|
"InstanceType": args.instance_type,
|
|
"InitialInstanceCount": 1,
|
|
}
|
|
],
|
|
)
|
|
|
|
# Create endpoint
|
|
logger.info(
|
|
f"Creating endpoint: {args.endpoint_name} (this will take several minutes)"
|
|
)
|
|
sagemaker_client.create_endpoint(
|
|
EndpointName=args.endpoint_name, EndpointConfigName=endpoint_config_name
|
|
)
|
|
|
|
# Wait for endpoint to be in service
|
|
logger.info("Waiting for endpoint to be in service...")
|
|
|
|
status = None
|
|
while status != "InService":
|
|
response = sagemaker_client.describe_endpoint(EndpointName=args.endpoint_name)
|
|
status = response["EndpointStatus"]
|
|
|
|
if status == "Failed":
|
|
logger.error(
|
|
f"Endpoint creation failed: {response.get('FailureReason', 'Unknown reason')}"
|
|
)
|
|
raise SystemExit(1)
|
|
|
|
if status != "InService":
|
|
logger.info(f"Endpoint status: {status}. Waiting...")
|
|
time.sleep(60)
|
|
|
|
logger.info(f"✅ Endpoint {args.endpoint_name} is now InService!")
|
|
logger.info("\nTest with promptfoo using:")
|
|
logger.info(
|
|
f"""
|
|
providers:
|
|
- id: sagemaker:{args.task.replace("-", ":")}:{args.endpoint_name}
|
|
config:
|
|
region: {region}
|
|
modelType: {"openai" if "llama" in args.model_id.lower() else "custom"}
|
|
maxTokens: 256
|
|
temperature: 0.7
|
|
"""
|
|
)
|
|
|
|
logger.info("\nOr use the test script:")
|
|
logger.info(
|
|
f"node test-sagemaker-provider.js --endpoint={args.endpoint_name} --region={region} --model-type={'openai' if 'llama' in args.model_id.lower() else 'custom'}"
|
|
)
|
|
|
|
logger.info(
|
|
"\nTo delete this endpoint when done testing (to avoid unnecessary charges):"
|
|
)
|
|
logger.info(
|
|
f"aws sagemaker delete-endpoint --endpoint-name {args.endpoint_name} --region {region}"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error deploying model: {e}")
|
|
raise SystemExit(1)
|