Steg 9 (R4): unified migrate-corpus.mjs --write over engineering/governance/ infrastructure/security. 327 filer mutert, verified=null, prosa byte-identisk (fra første ## seksjon), advisor urørt (0 endringer). To applier-fixes oppdaget under kjøring (TDD, RED→GREEN): - insertHeaderFields: anker faller nå tilbake når en meta-linje selv passerer 500B (2 filer pakket et avsnitt i **Status:** → Type/Source landet utenfor scan-vinduet, applierens post-write-assertion fanget + restaurerte). - normalizeStaleVerified: fjerner nå ALLE stale non-date **Verified:** i 500B-vinduet, inkl. stray body-dup rett under --- (9 mlops-genaiops-filer var ellers falskt "verified"/fresh, droppet fra worklist). Operatør-godkjent utvidelse av carve-out; kun stray metadata-linjer, aldri prosa. test-transform-criterion: precondition oppdatert til post-migrasjons-sannhet (fila bærer nå Source). Suite 728/728 grønn.
15 KiB
Load Testing AI Services
Last updated: 2026-06-24 Status: GA Category: Performance & Scalability Type: reference Source: https://learn.microsoft.com/azure/load-testing/overview-what-is-azure-load-testing
Innhold
- Introduksjon
- Kjernekomponenter
- Load Test Design
- Realistic Traffic Patterns
- Bottleneck Analysis
- Capacity Forecasting
- Norsk offentlig sektor
- Beslutningsrammeverk
- Referanser
- For Cosmo
Introduksjon
Load testing av Azure AI Services er fundamentalt annerledes enn tradisjonell web-applikasjons lasttesting. AI-tjenester har variabel responstid basert på input-størrelse og output-kompleksitet, token-baserte rate limits (TPM/RPM) som ikke korrelerer lineært med antall forespørsler, og kostnader som skalerer med bruk. En enkelt Azure OpenAI-forespørsel kan ta fra 200ms til 120 sekunder avhengig av modell, prompt-størrelse og generert output.
Microsoft tilbyr to offisielle verktøy for dette: Azure Load Testing (JMeter-basert managed service) og azure-openai-benchmark (CLI-verktøy spesifikt for Azure OpenAI). For Provisioned Throughput Units (PTU) er benchmarking spesielt viktig fordi den faktiske throughputen avhenger av workload shape — forholdet mellom input og output tokens, call rate og cache match rate.
For norsk offentlig sektor bør load testing gjennomføres før produksjonslansering av alle AI-tjenester som eksponeres mot sluttbrukere, og deretter regelmessig for å verifisere at ytelsen holder seg innenfor definerte SLAer.
Kjernekomponenter
| Komponent | Formål | Teknologi |
|---|---|---|
| Azure Load Testing | Managed lasttestings-tjeneste | Azure Load Testing (JMeter) |
| azure-openai-benchmark | Offisielt benchmarking-verktøy for Azure OpenAI | GitHub CLI |
| Azure Monitor | Metrikker under lasttest | Azure Monitor |
| Application Insights | End-to-end latens-sporing | App Insights |
| Performance Optimizer | Azure Functions ytelsesoptimalisering | Azure Load Testing |
Load Test Design
Test-scenarioer for Azure OpenAI
# azure-load-test-config.yaml
# Konfigurasjon for Azure Load Testing
version: v0.1
testId: aoai-load-test-chat
testPlan: aoai-chat-test.jmx
engineInstances: 3
configurationFiles:
- aoai-chat-test.jmx
- test-prompts.csv
failureCriteria:
- avg(response_time_ms) > 5000
- percentage(error) > 5
- p95(response_time_ms) > 15000
env:
- name: AOAI_ENDPOINT
value: https://aoai-prod.openai.azure.com
- name: DEPLOYMENT_NAME
value: gpt-4o
- name: API_VERSION
value: "2024-10-21"
secrets:
- name: AOAI_API_KEY
value: $(aoai-api-key) # Referanse til Key Vault
Python-basert lasttest
import asyncio
import time
import statistics
from dataclasses import dataclass, field
from openai import AsyncAzureOpenAI
@dataclass
class LoadTestConfig:
target_rps: float # Forespørsler per sekund
duration_seconds: int # Testens varighet
ramp_up_seconds: int = 30 # Tid til full belastning
model: str = "gpt-4o"
max_tokens: int = 500
concurrent_limit: int = 50
@dataclass
class LoadTestResults:
total_requests: int = 0
successful: int = 0
failed: int = 0
throttled: int = 0
latencies_ms: list[float] = field(default_factory=list)
tokens_used: int = 0
@property
def p50(self) -> float:
return statistics.median(self.latencies_ms) if self.latencies_ms else 0
@property
def p95(self) -> float:
if not self.latencies_ms:
return 0
sorted_l = sorted(self.latencies_ms)
idx = int(len(sorted_l) * 0.95)
return sorted_l[idx]
@property
def p99(self) -> float:
if not self.latencies_ms:
return 0
sorted_l = sorted(self.latencies_ms)
idx = int(len(sorted_l) * 0.99)
return sorted_l[idx]
@property
def error_rate(self) -> float:
total = self.successful + self.failed
return round(self.failed / max(total, 1) * 100, 2)
@property
def throttle_rate(self) -> float:
total = self.successful + self.failed
return round(self.throttled / max(total, 1) * 100, 2)
async def run_load_test(
client: AsyncAzureOpenAI,
config: LoadTestConfig,
test_prompts: list[str]
) -> LoadTestResults:
"""Run load test against Azure OpenAI deployment."""
results = LoadTestResults()
semaphore = asyncio.Semaphore(config.concurrent_limit)
prompt_idx = 0
async def send_request():
nonlocal prompt_idx
async with semaphore:
prompt = test_prompts[prompt_idx % len(test_prompts)]
prompt_idx += 1
start = time.time()
try:
response = await client.chat.completions.create(
model=config.model,
messages=[{"role": "user", "content": prompt}],
max_tokens=config.max_tokens
)
latency = (time.time() - start) * 1000
results.latencies_ms.append(latency)
results.successful += 1
results.tokens_used += response.usage.total_tokens
except Exception as e:
results.failed += 1
if hasattr(e, 'status_code') and e.status_code == 429:
results.throttled += 1
results.total_requests += 1
# Ramp-up og sustained load
start_time = time.time()
tasks = []
while time.time() - start_time < config.duration_seconds:
elapsed = time.time() - start_time
# Ramp-up: gradvis øk RPS
if elapsed < config.ramp_up_seconds:
current_rps = config.target_rps * (
elapsed / config.ramp_up_seconds)
else:
current_rps = config.target_rps
if current_rps > 0:
interval = 1.0 / current_rps
tasks.append(asyncio.create_task(send_request()))
await asyncio.sleep(interval)
# Vent på at alle pågående forespørsler fullføres
await asyncio.gather(*tasks, return_exceptions=True)
return results
# Bruk
async def main():
client = AsyncAzureOpenAI(
azure_endpoint="https://my-aoai.openai.azure.com",
api_key="...",
api_version="2024-10-21"
)
config = LoadTestConfig(
target_rps=5.0,
duration_seconds=300,
ramp_up_seconds=60,
model="gpt-4o",
max_tokens=500,
concurrent_limit=20
)
prompts = [
"Oppsummer denne teksten: ...",
"Klassifiser dette dokumentet: ...",
"Generer et svar på denne klagen: ..."
]
results = await run_load_test(client, config, prompts)
print(f"Total: {results.total_requests}")
print(f"Success: {results.successful}")
print(f"Error rate: {results.error_rate}%")
print(f"Throttle rate: {results.throttle_rate}%")
print(f"P50: {results.p50:.0f}ms")
print(f"P95: {results.p95:.0f}ms")
print(f"P99: {results.p99:.0f}ms")
Realistic Traffic Patterns
Workload Shape-profiler
from enum import Enum
class WorkloadProfile(Enum):
CHAT_BOT = "chat_bot"
DOCUMENT_ANALYSIS = "document_analysis"
RAG_SEARCH = "rag_search"
CODE_GENERATION = "code_generation"
BATCH_PROCESSING = "batch_processing"
WORKLOAD_SHAPES = {
WorkloadProfile.CHAT_BOT: {
"avg_input_tokens": 200,
"avg_output_tokens": 300,
"peak_rps": 10,
"avg_rps": 3,
"pattern": "bursty",
"description": "Korte input, moderate svar, ujevn trafikk"
},
WorkloadProfile.DOCUMENT_ANALYSIS: {
"avg_input_tokens": 4000,
"avg_output_tokens": 800,
"peak_rps": 2,
"avg_rps": 0.5,
"pattern": "batch",
"description": "Store input, strukturert output, batch-mønster"
},
WorkloadProfile.RAG_SEARCH: {
"avg_input_tokens": 3000,
"avg_output_tokens": 500,
"peak_rps": 20,
"avg_rps": 8,
"pattern": "steady_with_peaks",
"description": "Store kontekster fra search, mange samtidige"
},
WorkloadProfile.CODE_GENERATION: {
"avg_input_tokens": 1500,
"avg_output_tokens": 2000,
"peak_rps": 5,
"avg_rps": 1,
"pattern": "variable",
"description": "Middels input, store output, variabel"
},
WorkloadProfile.BATCH_PROCESSING: {
"avg_input_tokens": 2000,
"avg_output_tokens": 500,
"peak_rps": 50,
"avg_rps": 30,
"pattern": "sustained",
"description": "Jevnt høy belastning i batch-vindu"
}
}
Bottleneck Analysis
Flaskehals-identifisering under test
def analyze_bottlenecks(results: LoadTestResults, config: LoadTestConfig) -> list[str]:
"""Identify bottlenecks from load test results."""
findings = []
# 1. Throttling-analyse
if results.throttle_rate > 5:
findings.append(
f"HIGH_THROTTLING: {results.throttle_rate}% throttled. "
f"Øk TPM-kvote eller distribuer over flere regioner.")
# 2. Latens-analyse
if results.p95 > 10000:
findings.append(
f"HIGH_LATENCY: P95={results.p95:.0f}ms. "
f"Vurder PTU for forutsigbar latens, "
f"eller reduser max_tokens/prompt-størrelse.")
# 3. Latens-spredning
if results.p99 > results.p50 * 5:
findings.append(
f"HIGH_VARIANCE: P99/P50 ratio={results.p99/results.p50:.1f}. "
f"Tyder på kapasitetsproblemer ved peak — "
f"vurder circuit breaker og retry-logikk.")
# 4. Throughput vs target
actual_rps = results.successful / (
config.duration_seconds - config.ramp_up_seconds)
if actual_rps < config.target_rps * 0.8:
findings.append(
f"LOW_THROUGHPUT: {actual_rps:.1f} RPS vs target "
f"{config.target_rps} RPS. "
f"Klient-side bottleneck — øk concurrent_limit.")
# 5. Error rate
if results.error_rate > 1:
findings.append(
f"HIGH_ERRORS: {results.error_rate}% errors. "
f"Sjekk 5xx-feil i Azure Monitor.")
return findings
Capacity Forecasting
PTU-dimensjonering fra lasttestresultater
def forecast_ptu_requirements(
test_results: LoadTestResults,
target_rps: float,
model: str = "gpt-4o",
growth_factor: float = 1.3 # 30% vekstmargin
) -> dict:
"""Forecast PTU requirements based on load test data."""
# TPM per PTU (fra Microsoft dokumentasjon)
tpm_per_ptu = {
"gpt-4o": 2500,
"gpt-4o-mini": 37000,
"gpt-4.1": 3000,
"gpt-4.1-mini": 14900,
"gpt-4.1-nano": 59400,
"o3": 3000
}
if model not in tpm_per_ptu:
raise ValueError(f"Unknown model: {model}")
avg_tokens_per_request = (
test_results.tokens_used / max(test_results.successful, 1))
required_tpm = target_rps * 60 * avg_tokens_per_request
required_tpm_with_growth = required_tpm * growth_factor
ptus_needed = required_tpm_with_growth / tpm_per_ptu[model]
# Round opp til nærmeste deployable enhet.
# Global & Data Zone Provisioned (anbefalt default): minimum 15 PTU, increment 5 for alle GPT-/o-modeller.
# Regional Provisioned: minimum 50 (gpt-4.1/o3/gpt-4o), 25 for mini/nano-modeller.
min_deployment = 15
increment = 5
ptus_deployed = max(
min_deployment,
((int(ptus_needed) // increment) + 1) * increment
)
return {
"model": model,
"avg_tokens_per_request": round(avg_tokens_per_request),
"required_tpm": round(required_tpm),
"required_tpm_with_growth": round(required_tpm_with_growth),
"ptus_needed_exact": round(ptus_needed, 1),
"ptus_deployed": ptus_deployed,
"headroom_pct": round(
(ptus_deployed * tpm_per_ptu[model] - required_tpm) /
required_tpm * 100, 1)
}
Azure OpenAI Benchmark Tool
# Offisielt benchmarking-verktøy fra Microsoft
# https://github.com/Azure/azure-openai-benchmark
# Installer
pip install azure-openai-benchmark
# Kjør benchmark med standard workload shape
azure-openai-benchmark \
--api-key $AOAI_API_KEY \
--api-base-endpoint https://my-aoai.openai.azure.com \
--deployment gpt-4o \
--shape-profile balanced \
--clients 20 \
--duration 600 \
--output-format json \
--output results.json
# Custom workload shape
azure-openai-benchmark \
--api-key $AOAI_API_KEY \
--api-base-endpoint https://my-aoai.openai.azure.com \
--deployment gpt-4o \
--context-tokens 3000 \
--max-tokens 500 \
--clients 10 \
--rate 5 \
--duration 300
Norsk offentlig sektor
- Krav til testing: NSMs grunnprinsipper krever ytelsestesting av kritiske tjenester. AI-tjenester som brukes i saksbehandling bør lasttestes kvartalsvis og etter større endringer.
- Testmiljø: Bruk separate Azure OpenAI-deployments for lasttesting — aldri test mot produksjons-kvoten. Global Standard deployments er kostnadseffektive for testing.
- Data i tester: Bruk syntetiske eller anonymiserte data i lasttester. Reelle personopplysninger skal ikke brukes i testmiljøer.
- Dokumentasjon: Lasttestresultater bør dokumenteres som del av driftsdokumentasjonen og refereres i SLA-avtaler med interne tjenesteeiere.
- Kostnadsbevissthet: Lasttester genererer reelle token-kostnader. Estimer kostnad på forhånd og sett budsjettgrenser.
Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|---|---|---|
| Ny produksjonsdeployment | Full lasttest med ramp-up | Baseline etablering |
| PTU-dimensjonering | azure-openai-benchmark + kapasitetskalkulator | Mest nøyaktige tall |
| Etter kvoteendring | Regression-test med baseline | Verifiser forbedring |
| Multi-region failover | Lasttest under simulert feil | Valider failover-ytelse |
| Periodisk verifisering | Månedlig smoke test | Fang degradering tidlig |
Referanser
- Run a benchmark — Azure OpenAI benchmarking guide
- Azure OpenAI Benchmark Tool — Offisielt CLI-verktøy
- Azure Load Testing overview — Managed lasttesting
- Performance and latency — Throughput vs latency forklaring
- Capacity planning — WAF kapasitetsplanlegging
For Cosmo
- Bruk denne referansen når kunden skal dimensjonere Azure OpenAI-deployment, validere ytelse før lansering, eller feilsøke ytelsesprobler i produksjon.
- Alltid bruk azure-openai-benchmark for PTU-dimensjonering — kapasitetskalkulatoren gir estimater, benchmarking gir reelle tall.
- Definer workload shape (input tokens, output tokens, call rate) FØR testing — resultatene er kun gyldige for den testede workloaden.
- Kjør lasttester i minimum 10 minutter for å oppnå steady state — korte tester gir misvisende resultater.
- For norsk offentlig sektor: dokumenter baseline-ytelse og bruk den som referansepunkt for SLA-avtaler.