Amazon Bedrock allows you to customize foundation models with your own data, improving performance for domain-specific tasks without building models from scratch.
Customization Methods
| Method |
Purpose |
Data Needed |
| Continued Pre-training |
Adapt to domain language |
Unlabeled text |
| Fine-tuning |
Improve task performance |
Labeled examples |
flowchart LR
A["Base Model"] --> B["Continued Pre-training"]
B --> C["Domain-Adapted Model"]
C --> D["Fine-tuning"]
D --> E["Task-Optimized Model"]
style B fill:#3b82f6,color:#fff
style D fill:#8b5cf6,color:#fff
Supported Models
| Model |
Fine-tuning |
Continued Pre-training |
| Amazon Titan Text |
Yes |
Yes |
| Cohere Command |
Yes |
No |
| Meta Llama 2/3 |
Yes |
No |
Data Preparation
Fine-tuning Format (JSONL)
{"prompt": "Summarize this document:", "completion": "The document discusses..."}
{"prompt": "Translate to French:", "completion": "Bonjour, comment allez-vous?"}
{"prompt": "Classify sentiment:", "completion": "positive"}
Continued Pre-training Format
{"input": "Domain-specific text content goes here. This helps the model learn domain terminology and patterns."}
{"input": "Another document with domain knowledge..."}
Data Requirements
| Aspect |
Fine-tuning |
Continued Pre-training |
| Min samples |
1000+ |
100,000+ tokens |
| Format |
Prompt-completion pairs |
Raw text |
| Quality |
Clean, diverse |
Representative |
Creating a Fine-tuning Job
Prepare S3 Data
import boto3
import json
s3 = boto3.client('s3')
training_data = [
{"prompt": "Customer: Product is broken\nAgent:", "completion": "I'm sorry to hear that. Let me help you with a replacement."},
{"prompt": "Customer: When will my order arrive?\nAgent:", "completion": "Let me check your order status. Could you provide your order number?"},
]
with open('/tmp/training.jsonl', 'w') as f:
for item in training_data:
f.write(json.dumps(item) + '\n')
s3.upload_file('/tmp/training.jsonl', 'my-bucket', 'training/train.jsonl')
Create Customization Job
client = boto3.client('bedrock')
response = client.create_model_customization_job(
jobName='customer-service-ft',
customModelName='customer-service-titan',
roleArn='arn:aws:iam::123456789012:role/BedrockCustomizationRole',
baseModelIdentifier='amazon.titan-text-express-v1',
customizationType='FINE_TUNING',
trainingDataConfig={
's3Uri': 's3://my-bucket/training/train.jsonl'
},
validationDataConfig={
's3Uri': 's3://my-bucket/training/validation.jsonl'
},
outputDataConfig={
's3Uri': 's3://my-bucket/output/'
},
hyperParameters={
'epochCount': '3',
'batchSize': '8',
'learningRate': '0.00001'
}
)
job_arn = response['jobArn']
Monitor Job Status
response = client.get_model_customization_job(jobIdentifier=job_arn)
print(f"Status: {response['status']}")
print(f"Training metrics: {response.get('trainingMetrics', {})}")
Hyperparameters
| Parameter |
Description |
Typical Range |
| epochCount |
Training passes |
1-5 |
| batchSize |
Samples per batch |
4-32 |
| learningRate |
Update step size |
1e-6 to 1e-4 |
| learningRateWarmupSteps |
Warmup period |
0-100 |
Using Custom Models
Create Provisioned Throughput
Custom models require provisioned throughput:
response = client.create_provisioned_model_throughput(
modelUnits=1,
provisionedModelName='customer-service-pt',
modelId='arn:aws:bedrock:us-east-1:123456789012:custom-model/customer-service-titan'
)
provisioned_arn = response['provisionedModelArn']
Invoke Custom Model
runtime = boto3.client('bedrock-runtime')
response = runtime.invoke_model(
modelId=provisioned_arn,
body=json.dumps({
"inputText": "Customer: My package is damaged\nAgent:",
"textGenerationConfig": {
"maxTokenCount": 256,
"temperature": 0.7
}
})
)
result = json.loads(response['body'].read())
print(result['results'][0]['outputText'])
Evaluation
Built-in Metrics
| Metric |
Description |
| Training Loss |
Model fit to training data |
| Validation Loss |
Generalization ability |
| Perplexity |
Prediction confidence |
Custom Evaluation
def evaluate_model(model_id, test_data):
runtime = boto3.client('bedrock-runtime')
results = []
for item in test_data:
response = runtime.invoke_model(
modelId=model_id,
body=json.dumps({
"inputText": item['prompt'],
"textGenerationConfig": {"maxTokenCount": 256}
})
)
output = json.loads(response['body'].read())
predicted = output['results'][0]['outputText']
results.append({
'expected': item['completion'],
'predicted': predicted,
'match': item['completion'].lower() in predicted.lower()
})
accuracy = sum(1 for r in results if r['match']) / len(results)
return accuracy, results
Best Practices
| Practice |
Recommendation |
| Data quality |
Clean, diverse, representative |
| Start small |
Test with subset first |
| Monitor loss |
Watch for overfitting |
| Validate |
Use held-out test set |
| Version control |
Track data and model versions |
Cost Considerations
| Component |
Pricing Factor |
| Training |
Per token processed |
| Storage |
Custom model storage |
| Inference |
Provisioned throughput |
Key Takeaways
- Two customization types - Pre-training for domain, fine-tuning for tasks
- Data quality matters - Clean, diverse examples improve results
- Provisioned throughput required - For custom model inference
- Monitor training metrics - Prevent overfitting
- Start with baseline - Compare against base model
References