| name | model-deployment |
| description | Export and deploy fine-tuned models to production. Covers GGUF/Ollama, vLLM, HuggingFace Hub, Docker, quantization, and platform selection. Use after fine-tuning when you need to deploy models efficiently. |
Model Deployment
Complete guide for exporting, optimizing, and deploying fine-tuned LLMs to production environments.
Overview
After fine-tuning your model with Unsloth, deploy it efficiently:
- GGUF export - For llama.cpp, Ollama, local inference
- vLLM deployment - For high-throughput production serving
- HuggingFace Hub - For sharing and version control
- Quantization - Reduce size while maintaining quality
- Platform selection - Choose the right infrastructure
- Monitoring - Track performance and costs
Quick Start
Export to GGUF (Ollama/llama.cpp)
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"./fine_tuned_model",
max_seq_length=2048
)
model.save_pretrained_gguf(
"./gguf_output",
tokenizer,
quantization_method="q4_k_m"
)
Deploy with vLLM
from unsloth import FastLanguageModel
model.save_pretrained("./vllm_model")
tokenizer.save_pretrained("./vllm_model")
Push to HuggingFace Hub
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"./fine_tuned_model",
max_seq_length=2048
)
model.push_to_hub(
"your-username/model-name",
token="hf_...",
private=False
)
tokenizer.push_to_hub(
"your-username/model-name",
token="hf_..."
)
Export Formats
1. GGUF (llama.cpp / Ollama)
Best for: Local deployment, edge devices, CPU inference
quantization_methods = {
"q4_k_m": "4-bit, medium quality (recommended)",
"q5_k_m": "5-bit, higher quality",
"q8_0": "8-bit, near-original quality",
"f16": "16-bit float, full quality",
"f32": "32-bit float, highest quality"
}
model.save_pretrained_gguf(
"./gguf_output",
tokenizer,
quantization_method="q4_k_m"
)
Use with Ollama:
cd gguf_output
ollama create my-medical-model -f Modelfile
ollama run my-medical-model "What are the symptoms of pneumonia?"
ollama serve
Use with llama.cpp:
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make
./main -m ../gguf_output/model-q4_k_m.gguf -p "Your prompt here"
./server -m ../gguf_output/model-q4_k_m.gguf --host 0.0.0.0 --port 8080
2. vLLM (Production Serving)
Best for: High-throughput production, API serving, multi-user
model.save_pretrained("./vllm_model")
tokenizer.save_pretrained("./vllm_model")
model = FastLanguageModel.merge_and_unload(model)
model.save_pretrained("./vllm_model_merged")
Deploy vLLM Server:
python -m vllm.entrypoints.openai.api_server \
--model ./vllm_model \
--dtype bfloat16 \
--max-model-len 4096
python -m vllm.entrypoints.openai.api_server \
--model ./vllm_model \
--tensor-parallel-size 4 \
--dtype bfloat16
python -m vllm.entrypoints.openai.api_server \
--model ./vllm_model \
--quantization awq \
--dtype half
Use vLLM API:
import openai
openai.api_key = "EMPTY"
openai.api_base = "http://localhost:8000/v1"
response = openai.Completion.create(
model="./vllm_model",
prompt="Your prompt here",
max_tokens=512,
temperature=0.7
)
print(response.choices[0].text)
3. HuggingFace Hub
Best for: Sharing, version control, collaboration
from unsloth import FastLanguageModel
from huggingface_hub import HfApi
model, tokenizer = FastLanguageModel.from_pretrained("./fine_tuned_model")
model.push_to_hub(
"username/model-name",
token="hf_...",
private=True,
commit_message="Initial upload of medical model"
)
tokenizer.push_to_hub("username/model-name", token="hf_...")
api = HfApi()
api.upload_file(
path_or_fileobj="README.md",
path_in_repo="README.md",
repo_id="username/model-name",
token="hf_..."
)
Download from Hub:
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"username/model-name",
max_seq_length=2048
)
4. Docker Deployment
Best for: Reproducible deployments, cloud platforms
Dockerfile for vLLM:
FROM vllm/vllm-openai:latest
# Copy model
COPY ./vllm_model /app/model
# Expose port
EXPOSE 8000
# Run server
CMD ["python", "-m", "vllm.entrypoints.openai.api_server", \
"--model", "/app/model", \
"--host", "0.0.0.0", \
"--port", "8000"]
Build and run:
docker build -t my-model-server .
docker run -d \
--gpus all \
-p 8000:8000 \
-v $(pwd)/vllm_model:/app/model \
my-model-server
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"model": "/app/model", "prompt": "Hello", "max_tokens": 50}'
Quantization Strategies
Quantization Methods Comparison
| Method | Size | Quality | Speed | Use Case |
|---|
| F32 | 100% | 100% | Slow | Baseline, not recommended |
| F16 | 50% | ~100% | Fast | Full quality, GPU |
| Q8_0 | 25% | ~99% | Faster | Near-full quality |
| Q5_K_M | 16% | ~95% | Very fast | Balanced |
| Q4_K_M | 12% | ~90% | Fastest | Recommended default |
| Q4_0 | 12% | ~85% | Fastest | Low-end devices |
| Q2_K | 8% | ~70% | Fastest | Edge devices only |
GGUF Quantization
quantization_levels = ["q4_k_m", "q5_k_m", "q8_0"]
for quant in quantization_levels:
model.save_pretrained_gguf(
f"./gguf_output_{quant}",
tokenizer,
quantization_method=quant
)
print(f"Exported {quant}")
GPTQ Quantization
Best for: GPU inference with high throughput
from transformers import GPTQConfig
gptq_config = GPTQConfig(
bits=4,
dataset="c4",
tokenizer=tokenizer,
group_size=128
)
quantized_model = model.quantize(gptq_config)
quantized_model.save_pretrained("./gptq_model")
tokenizer.save_pretrained("./gptq_model")
AWQ Quantization
Best for: vLLM deployment
from awq import AutoAWQForCausalLM
model = AutoAWQForCausalLM.from_pretrained("./fine_tuned_model")
model.quantize(
tokenizer,
quant_config={
"zero_point": True,
"q_group_size": 128,
"w_bit": 4
}
)
model.save_quantized("./awq_model")
tokenizer.save_pretrained("./awq_model")
Deployment Platforms
Local Deployment
Pros: Full control, no API costs, data privacy
Cons: Limited scale, hardware costs
ollama create my-model -f Modelfile
ollama run my-model
./server -m model.gguf --host 0.0.0.0 --port 8080
python -m vllm.entrypoints.openai.api_server --model ./model
Hardware Requirements:
| Model Size | Min RAM | Min VRAM | Recommended GPU |
|---|
| 1-3B | 8GB | 4GB | RTX 3060 |
| 7B | 16GB | 8GB | RTX 4070 |
| 13B | 32GB | 16GB | RTX 4090 |
| 30B | 64GB | 24GB | A5000 |
| 70B | 128GB | 48GB | 2x A6000 |
Cloud Platforms
Modal
Best for: Serverless, pay-per-use
import modal
stub = modal.Stub("my-model")
@stub.function(
image=modal.Image.debian_slim().pip_install("vllm"),
gpu="A100",
timeout=600
)
def generate(prompt: str) -> str:
from vllm import LLM
llm = LLM(model="./model")
output = llm.generate(prompt)
return output[0].outputs[0].text
Pricing: ~$1-3/hour A100, pay only for usage
RunPod
Best for: Persistent endpoints, GPU pods
curl -X POST https://api.runpod.io/v2/endpoints \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-d '{
"name": "my-model",
"gpu_type": "RTX_4090",
"docker_image": "vllm/vllm-openai:latest",
"env": {
"MODEL_NAME": "./model"
}
}'
Pricing: ~$0.30-0.50/hour RTX 4090, ~$1.50/hour A100
Vast.ai
Best for: Lowest cost, spot instances
vastai search offers 'gpu_name=RTX_4090 num_gpus=1'
vastai create instance <instance_id> \
--image vllm/vllm-openai:latest \
--env MODEL_NAME=./model
Pricing: ~$0.15-0.30/hour RTX 4090, ~$0.80/hour A100
AWS/GCP/Azure
Best for: Enterprise, compliance, scale
AWS SageMaker:
from sagemaker.huggingface import HuggingFaceModel
huggingface_model = HuggingFaceModel(
model_data="s3://bucket/model.tar.gz",
role=role,
transformers_version="4.37",
pytorch_version="2.1",
py_version="py310"
)
predictor = huggingface_model.deploy(
initial_instance_count=1,
instance_type="ml.g5.xlarge"
)
result = predictor.predict({
"inputs": "Your prompt here"
})
Pricing: ~$1-5/hour depending on instance type
Platform Comparison
| Platform | Setup | Cost | Scale | Best For |
|---|
| Local | Medium | Hardware only | Limited | Development, privacy |
| Modal | Easy | Pay-per-use | Auto | Serverless, experiments |
| RunPod | Easy | Low | Manual | Production, cost-sensitive |
| Vast.ai | Medium | Lowest | Manual | Training, batch inference |
| AWS/GCP | Hard | High | Auto | Enterprise, compliance |
Optimization Strategies
1. Merge LoRA Adapters
Before deployment, merge LoRA weights:
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
"./fine_tuned_model",
max_seq_length=2048
)
model = FastLanguageModel.merge_and_unload(model)
model.save_pretrained("./merged_model")
tokenizer.save_pretrained("./merged_model")
Benefits:
- Faster inference (no adapter computation)
- Simpler deployment (single model file)
- Broader compatibility
2. Enable Flash Attention
model, tokenizer = FastLanguageModel.from_pretrained(
"model-name",
max_seq_length=2048,
use_flash_attention_2=True
)
3. Batch Processing
For high throughput:
from vllm import LLM, SamplingParams
llm = LLM(model="./model")
prompts = [
"Prompt 1",
"Prompt 2",
]
outputs = llm.generate(prompts, SamplingParams(temperature=0.7))
for output in outputs:
print(output.outputs[0].text)
4. Continuous Batching
vLLM automatically does continuous batching:
python -m vllm.entrypoints.openai.api_server \
--model ./model \
--max-num-batched-tokens 8192 \
--max-num-seqs 256
Load Testing & Benchmarking
Benchmark Inference Speed
import time
from vllm import LLM
llm = LLM(model="./model")
prompts = ["Test prompt"] * 100
start = time.time()
outputs = llm.generate(prompts)
end = time.time()
total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
tokens_per_sec = total_tokens / (end - start)
print(f"Throughput: {tokens_per_sec:.2f} tokens/sec")
Load Testing with Locust
from locust import HttpUser, task, between
class ModelUser(HttpUser):
wait_time = between(1, 3)
@task
def generate(self):
self.client.post("/v1/completions", json={
"model": "./model",
"prompt": "What is the capital of France?",
"max_tokens": 50
})
Performance Targets
| Metric | Target | Excellent | Notes |
|---|
| Latency (TTFT) | <500ms | <200ms | Time to first token |
| Throughput | >50 tok/s | >100 tok/s | Per user |
| P99 Latency | <2s | <1s | 99th percentile |
| Batch throughput | >500 tok/s | >1000 tok/s | Total system |
| GPU utilization | >70% | >85% | Resource efficiency |
Monitoring & Observability
Basic Monitoring
import prometheus_client
from prometheus_client import Counter, Histogram
REQUEST_COUNT = Counter('model_requests_total', 'Total requests')
REQUEST_DURATION = Histogram('model_request_duration_seconds', 'Request duration')
TOKENS_GENERATED = Counter('model_tokens_generated_total', 'Total tokens')
@REQUEST_DURATION.time()
def generate(prompt: str):
REQUEST_COUNT.inc()
output = model.generate(prompt)
TOKENS_GENERATED.inc(len(output.token_ids))
return output
prometheus_client.start_http_server(9090)
vLLM Metrics
vLLM exposes metrics automatically:
curl http://localhost:8000/metrics
Cost Tracking
class CostTracker:
def __init__(self, cost_per_hour: float):
self.cost_per_hour = cost_per_hour
self.start_time = time.time()
self.total_tokens = 0
def track_generation(self, num_tokens: int):
self.total_tokens += num_tokens
def get_stats(self):
hours = (time.time() - self.start_time) / 3600
total_cost = hours * self.cost_per_hour
cost_per_1k_tokens = (total_cost / self.total_tokens) * 1000
return {
'total_cost': total_cost,
'total_tokens': self.total_tokens,
'cost_per_1k_tokens': cost_per_1k_tokens,
'tokens_per_dollar': self.total_tokens / total_cost
}
tracker = CostTracker(cost_per_hour=1.50)
tracker.track_generation(512)
print(tracker.get_stats())
Common Deployment Patterns
Pattern 1: Quick Local Demo
python export_gguf.py
ollama create my-demo -f Modelfile
ollama run my-demo
Pattern 2: Production API
python merge_lora.py
python quantize_awq.py
docker run -d --gpus all -p 8000:8000 \
-v $(pwd)/model:/model \
vllm/vllm-openai:latest \
--model /model --quantization awq
Pattern 3: Multi-Model Serving
from vllm import LLM
models = {
'medical': LLM(model="./medical_model"),
'legal': LLM(model="./legal_model"),
'general': LLM(model="./general_model")
}
def route_and_generate(text: str, domain: str):
model = models.get(domain, models['general'])
return model.generate(text)
Pattern 4: Hybrid Deployment
class HybridInference:
def __init__(self):
self.local = LLM(model="./small_model")
self.cloud_endpoint = "https://api.cloud.com/large-model"
def generate(self, prompt: str, complexity: str = 'auto'):
if complexity == 'auto':
complexity = self.estimate_complexity(prompt)
if complexity == 'simple':
return self.local.generate(prompt)
else:
return requests.post(self.cloud_endpoint, json={'prompt': prompt})
Troubleshooting
Issue: Out of Memory (OOM)
Solutions:
model.save_pretrained_gguf("./output", tokenizer, quantization_method="q4_0")
python -m vllm.entrypoints.openai.api_server \
--model ./model \
--max-model-len 2048
model, tokenizer = FastLanguageModel.from_pretrained(
"./model",
device_map="auto",
offload_folder="./offload"
)
python -m vllm.entrypoints.openai.api_server \
--model ./model \
--tensor-parallel-size 2
Issue: Slow Inference
Solutions:
model, tokenizer = FastLanguageModel.from_pretrained(
"./model",
use_flash_attention_2=True
)
Issue: Model Quality Degradation
Solutions:
def test_quantization(original_model, quantized_model, test_prompts):
results = []
for prompt in test_prompts:
orig_out = original_model.generate(prompt)
quant_out = quantized_model.generate(prompt)
similarity = calculate_similarity(orig_out, quant_out)
results.append(similarity)
return np.mean(results)
Issue: High Latency
Solutions:
python -m vllm.entrypoints.openai.api_server \
--model ./model \
--gpu-memory-utilization 0.95 \
--max-num-batched-tokens 8192
Best Practices
1. Test Before Production
test_prompts = load_test_prompts()
original = LLM(model="./fine_tuned_model")
quantized = LLM(model="./quantized_model")
for prompt in test_prompts:
orig_out = original.generate(prompt)
quant_out = quantized.generate(prompt)
print(f"Original: {orig_out}")
print(f"Quantized: {quant_out}")
print(f"Similarity: {calculate_similarity(orig_out, quant_out)}")
2. Version Your Models
models/
├── medical-v1.0.0/
│ ├── full/ # Full precision
│ ├── q4_k_m/ # 4-bit GGUF
│ ├── awq/ # AWQ quantized
│ └── README.md # Model card
├── medical-v1.1.0/
└── production -> medical-v1.0.0/ # Symlink to deployed version
3. Monitor Everything
- Latency (P50, P95, P99)
- Throughput (tokens/sec)
- Error rate
- GPU utilization
- Cost per request
- Quality metrics (if available)
4. Start Small, Scale Up
1. Local testing (Ollama/llama.cpp)
2. Cloud trial (Modal/RunPod single instance)
3. Production (vLLM with load balancer)
4. Scale (Multi-GPU, multi-region)
5. Document Everything
Create a deployment README:
# Model Deployment Guide
## Model Details
- Base: Llama-3.2-7B
- Fine-tuned on: Medical Q&A dataset
- Quantization: Q4_K_M
- Size: 4.2GB
## Deployment
ollama create medical-model -f Modelfile
ollama run medical-model
## Performance
- Latency: ~200ms (TTFT)
- Throughput: 50 tok/s
- Hardware: RTX 4070, 12GB VRAM
## Example Usage
...
Cost Optimization
Estimate Deployment Costs
def estimate_monthly_cost(
requests_per_day: int,
avg_tokens_per_request: int,
platform: str
):
"""
Estimate monthly deployment costs
"""
costs = {
'local_rtx4090': 0.20,
'vast_rtx4090': 0.25,
'runpod_rtx4090': 0.40,
'runpod_a100': 1.50,
'modal_a100': 2.00,
'aws_g5_xlarge': 1.20
}
hourly_cost = costs.get(platform, 1.0)
tokens_per_sec = 50
seconds_per_request = avg_tokens_per_request / tokens_per_sec
daily_seconds = requests_per_day * seconds_per_request
daily_hours = daily_seconds / 3600
if platform.startswith('modal'):
monthly_cost = daily_hours * 30 * hourly_cost
else:
monthly_cost = 24 * 30 * hourly_cost
return {
'monthly_cost': monthly_cost,
'cost_per_request': monthly_cost / (requests_per_day * 30),
'daily_hours': daily_hours
}
cost = estimate_monthly_cost(
requests_per_day=10000,
avg_tokens_per_request=256,
platform='runpod_rtx4090'
)
print(f"Monthly cost: ${cost['monthly_cost']:.2f}")
print(f"Per request: ${cost['cost_per_request']:.4f}")
Cost Optimization Strategies
- Use spot instances (Vast.ai) - 50-70% cheaper
- Scale down during off-peak - 30-50% savings
- Batch requests - Better GPU utilization
- Use smaller models - 7B vs 13B often similar quality
- Aggressive quantization - Q4 often sufficient
- Multi-tenancy - Share GPU across models
Additional Resources
- Training: See
unsloth-finetuning skill for model training
- Tokenizers: See
superbpe and unsloth-tokenizer skills
- Optimization: See
training-optimization skill for training details
- Datasets: See
dataset-engineering skill for data preparation
Summary
Model deployment workflow:
- ✓ Fine-tune with Unsloth
- ✓ Merge LoRA adapters
- ✓ Choose export format (GGUF/vLLM/HF)
- ✓ Quantize appropriately (Q4_K_M recommended)
- ✓ Select deployment platform
- ✓ Deploy and monitor
- ✓ Optimize costs and performance
Start with local Ollama deployment for testing, then scale to cloud for production.