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.
17 KiB
Throughput Optimization Strategies
Last updated: 2026-06-24 Status: GA Category: Performance & Scalability Type: reference Source: https://learn.microsoft.com/azure/foundry/openai/concepts/provisioned-throughput-billing
Innhold
- Introduksjon
- Kjernekomponenter
- Parallel Request Execution
- Request Buffering Strategies
- Queue Depth Tuning
- System Bottleneck Identification
- Implementeringsmønstre
- Norsk offentlig sektor
- Beslutningsrammeverk
- Referanser
- For Cosmo
Introduksjon
Throughput-optimalisering for Azure OpenAI og Azure AI Services handler om å maksimere antall fullførte forespørsler per sekund innenfor de tildelte kvotene. Azure OpenAI måler throughput i tokens per minutt (TPM) og forespørsler per minutt (RPM), og den reelle throughputen avhenger av en kompleks kombinasjon av input-størrelse, output-størrelse, modelltype og samtidige forespørsler.
For Standard deployments bestemmer den tildelte kvoten (TPM) en øvre grense for gjennomstrømming, men faktisk throughput kan være lavere på grunn av per-forespørsel latens. For Provisioned Throughput Units (PTU) er kapasiteten dedikert, og throughputen avhenger av workload shape — forholdet mellom input- og output-tokens. Microsofts offisielle benchmarking-verktøy (azure-openai-benchmark) er anbefalt for å måle reell throughput for spesifikke workloads.
I norsk offentlig sektor, der AI-løsninger ofte betjener tusenvis av saksbehandlere eller borgere samtidig, er throughput-optimalisering direkte knyttet til brukeropplevelse og kostnadseffektivitet. En 2x forbedring i throughput kan bety halverte Azure-kostnader for samme arbeidsmengde.
Kjernekomponenter
| Komponent | Formål | Teknologi |
|---|---|---|
| Token quota (TPM/RPM) | Rate limiting for Standard deployments | Azure OpenAI Quota |
| Provisioned Throughput Units | Dedikert kapasitet med garantert throughput | Azure OpenAI PTU |
| Batch API | 50% rabatt for asynkrone batch-jobber | Azure OpenAI Global Batch |
| Azure Load Testing | Lasttesting og throughput-måling | Azure Load Testing |
| Azure Monitor | Throughput-metrikker og overvåking | Azure Monitor |
| azure-openai-benchmark | Offisielt benchmarking-verktøy | GitHub CLI tool |
Parallel Request Execution
Asynkron parallellisering i Python
import asyncio
import time
from openai import AsyncAzureOpenAI
from dataclasses import dataclass
@dataclass
class ThroughputResult:
total_requests: int
successful: int
failed: int
total_tokens: int
duration_seconds: float
requests_per_second: float
tokens_per_second: float
async def parallel_completions(
client: AsyncAzureOpenAI,
messages_batch: list[list[dict]],
model: str = "gpt-4o",
max_concurrent: int = 20,
max_tokens: int = 500
) -> ThroughputResult:
"""Execute chat completions in parallel with controlled concurrency."""
semaphore = asyncio.Semaphore(max_concurrent)
results = {"success": 0, "failed": 0, "tokens": 0}
async def process_one(messages: list[dict]):
async with semaphore:
try:
response = await client.chat.completions.create(
model=model,
messages=messages,
max_tokens=max_tokens
)
results["success"] += 1
results["tokens"] += response.usage.total_tokens
except Exception as e:
results["failed"] += 1
if hasattr(e, 'status_code') and e.status_code == 429:
# Retry-After: vent og prøv igjen
retry_after = getattr(e, 'retry_after', 5)
await asyncio.sleep(retry_after)
await process_one(messages) # Retry
start = time.time()
await asyncio.gather(*[process_one(m) for m in messages_batch])
duration = time.time() - start
return ThroughputResult(
total_requests=len(messages_batch),
successful=results["success"],
failed=results["failed"],
total_tokens=results["tokens"],
duration_seconds=round(duration, 2),
requests_per_second=round(results["success"] / duration, 2),
tokens_per_second=round(results["tokens"] / duration, 2)
)
# Eksempel: Prosesser 1000 forespørsler med 20 samtidige
async def main():
client = AsyncAzureOpenAI(
azure_endpoint="https://my-aoai.openai.azure.com",
api_key="...",
api_version="2024-10-21"
)
batch = [
[{"role": "user", "content": f"Oppsummer dokument {i}"}]
for i in range(1000)
]
result = await parallel_completions(client, batch, max_concurrent=20)
print(f"Throughput: {result.requests_per_second} RPS, "
f"{result.tokens_per_second} tokens/s")
.NET Parallel Processing med SemaphoreSlim
using Azure.AI.OpenAI;
using System.Collections.Concurrent;
public class ThroughputOptimizer
{
private readonly AzureOpenAIClient _client;
private readonly SemaphoreSlim _semaphore;
private readonly ConcurrentBag<RequestMetric> _metrics = new();
public ThroughputOptimizer(AzureOpenAIClient client, int maxConcurrency = 20)
{
_client = client;
_semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
}
public async Task<ThroughputReport> ProcessBatchAsync(
IReadOnlyList<ChatMessage[]> requests,
string deploymentName,
CancellationToken cancellationToken = default)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
var tasks = requests.Select(messages =>
ProcessSingleAsync(messages, deploymentName, cancellationToken));
await Task.WhenAll(tasks);
sw.Stop();
var successful = _metrics.Where(m => m.Success).ToList();
return new ThroughputReport
{
TotalRequests = requests.Count,
Successful = successful.Count,
Failed = _metrics.Count - successful.Count,
TotalTokens = successful.Sum(m => m.TotalTokens),
DurationMs = sw.ElapsedMilliseconds,
RequestsPerSecond = Math.Round(
successful.Count / (sw.ElapsedMilliseconds / 1000.0), 2),
TokensPerSecond = Math.Round(
successful.Sum(m => m.TotalTokens) /
(sw.ElapsedMilliseconds / 1000.0), 2)
};
}
private async Task ProcessSingleAsync(
ChatMessage[] messages,
string deploymentName,
CancellationToken ct)
{
await _semaphore.WaitAsync(ct);
try
{
var chatClient = _client.GetChatClient(deploymentName);
var response = await chatClient.CompleteChatAsync(messages);
_metrics.Add(new RequestMetric
{
Success = true,
TotalTokens = response.Value.Usage.TotalTokenCount
});
}
catch (Exception)
{
_metrics.Add(new RequestMetric { Success = false });
}
finally
{
_semaphore.Release();
}
}
}
Request Buffering Strategies
Mikro-batching for høy throughput
import asyncio
from collections import deque
from typing import Callable, Any
class RequestBuffer:
"""Buffer requests and flush in micro-batches for throughput optimization."""
def __init__(
self,
process_fn: Callable,
max_batch_size: int = 10,
flush_interval_ms: int = 100,
max_queue_size: int = 1000
):
self.process_fn = process_fn
self.max_batch_size = max_batch_size
self.flush_interval = flush_interval_ms / 1000
self.queue: deque = deque(maxlen=max_queue_size)
self._running = False
async def enqueue(self, request: dict) -> asyncio.Future:
"""Add request to buffer, returns future with result."""
future = asyncio.get_event_loop().create_future()
self.queue.append({"request": request, "future": future})
if len(self.queue) >= self.max_batch_size:
await self._flush()
return await future
async def _flush(self):
"""Process all buffered requests."""
batch = []
futures = []
while self.queue and len(batch) < self.max_batch_size:
item = self.queue.popleft()
batch.append(item["request"])
futures.append(item["future"])
if batch:
try:
results = await self.process_fn(batch)
for future, result in zip(futures, results):
future.set_result(result)
except Exception as e:
for future in futures:
if not future.done():
future.set_exception(e)
async def run(self):
"""Run flush loop."""
self._running = True
while self._running:
if self.queue:
await self._flush()
await asyncio.sleep(self.flush_interval)
Queue Depth Tuning
Optimal kø-dybde for Azure OpenAI
import math
def calculate_optimal_queue_depth(
tpm_quota: int,
avg_input_tokens: int,
avg_output_tokens: int,
avg_latency_ms: float,
target_utilization: float = 0.85
) -> dict:
"""Calculate optimal queue depth based on quota and latency."""
# Beregn maks concurrent requests basert på quota
total_tokens_per_request = avg_input_tokens + avg_output_tokens
max_rpm = tpm_quota / total_tokens_per_request
# Maks concurrent basert på latens
requests_per_second = max_rpm / 60
avg_latency_s = avg_latency_ms / 1000
# Little's Law: L = λ * W
# L = concurrent requests, λ = arrival rate, W = service time
optimal_concurrent = requests_per_second * avg_latency_s
# Queue depth = concurrent * buffer factor
queue_depth = math.ceil(optimal_concurrent * (1 / target_utilization))
return {
"max_rpm": round(max_rpm),
"max_rps": round(requests_per_second, 2),
"optimal_concurrent": math.ceil(optimal_concurrent),
"recommended_queue_depth": queue_depth,
"theoretical_max_tps": round(
tpm_quota / 60 / total_tokens_per_request *
total_tokens_per_request, 0
)
}
# Eksempel: 240K TPM quota, typisk RAG-workload
result = calculate_optimal_queue_depth(
tpm_quota=240_000,
avg_input_tokens=2000,
avg_output_tokens=500,
avg_latency_ms=1200
)
print(result)
# {'max_rpm': 96, 'max_rps': 1.6, 'optimal_concurrent': 2,
# 'recommended_queue_depth': 3, ...}
System Bottleneck Identification
Identifisering av flaskehalser med Azure Monitor
# KQL-spørringer for throughput-analyse
# 1. Token throughput per minutt
PROCESSED_TOKENS_QUERY = """
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| where Category == "RequestResponse"
| extend promptTokens = toint(properties_s.promptTokens)
| extend completionTokens = toint(properties_s.completionTokens)
| summarize
TotalPromptTPM = sum(promptTokens),
TotalCompletionTPM = sum(completionTokens),
TotalTPM = sum(promptTokens) + sum(completionTokens),
RequestCount = count()
by bin(TimeGenerated, 1m), deploymentName_s
| order by TimeGenerated desc
"""
# 2. Identifiser throttling-mønstre
THROTTLING_ANALYSIS = """
AzureMetrics
| where MetricName == "AzureOpenAIRequests"
| extend StatusCode = tostring(split(DimensionValue, ",")[0])
| summarize
Total = count(),
Throttled = countif(StatusCode == "429"),
ServerErrors = countif(StatusCode startswith "5"),
ThrottleRate = round(
countif(StatusCode == "429") * 100.0 / count(), 2)
by bin(TimeGenerated, 5m)
| where ThrottleRate > 0
| order by TimeGenerated desc
"""
# 3. Latens-distribusjon for å finne bottlenecks
LATENCY_PERCENTILES = """
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
| extend DurationMs = todouble(DurationMs)
| summarize
P50 = percentile(DurationMs, 50),
P90 = percentile(DurationMs, 90),
P95 = percentile(DurationMs, 95),
P99 = percentile(DurationMs, 99),
Avg = avg(DurationMs)
by bin(TimeGenerated, 5m), deploymentName_s
| order by TimeGenerated desc
"""
Bottleneck Decision Tree
Lav throughput?
├── Høy throttle rate (>5% 429s)?
│ ├── Ja → Øk TPM-kvote eller legg til regioner
│ └── Nei → Sjekk latens
├── Høy latens (P95 > 5s)?
│ ├── Input tokens > 4K? → Reduser prompt-størrelse
│ ├── Output tokens > 2K? → Reduser max_tokens
│ └── Lav token count? → Sjekk nettverkslatens
├── Lav concurrent requests?
│ ├── Klient-side bottleneck → Øk parallellisering
│ └── Connection pool for liten → Øk pool size
└── Utilization < 50%?
└── Under-provisjonert? → Sjekk quota allocation
Implementeringsmønstre
Batch API for ikke-tidskritisk prosessering
from openai import AzureOpenAI
import json
def create_batch_file(requests: list[dict], filename: str = "batch.jsonl"):
"""Create JSONL file for Azure OpenAI Batch API."""
with open(filename, "w") as f:
for i, req in enumerate(requests):
batch_request = {
"custom_id": f"request-{i}",
"method": "POST",
"url": "/chat/completions",
"body": {
"model": "gpt-4o", # Must match deployment name
"messages": req["messages"],
"max_tokens": req.get("max_tokens", 1000)
}
}
f.write(json.dumps(batch_request) + "\n")
def submit_batch(client: AzureOpenAI, filename: str):
"""Submit batch job — 50% cost reduction, 24hr turnaround."""
# Upload file
batch_file = client.files.create(
file=open(filename, "rb"),
purpose="batch"
)
# Create batch job
batch_job = client.batches.create(
input_file_id=batch_file.id,
endpoint="/chat/completions",
completion_window="24h"
)
return batch_job
Norsk offentlig sektor
- Kostnadseffektivitet: Bruk Batch API for alle ikke-sanntids workloads (dokumentanalyse, klassifisering, oppsummering) for å oppnå 50% kostnadsreduksjon. Dette er spesielt relevant for store etater med høyt dokumentvolum.
- Kapasitetsplanlegging: Start med å estimere TPM-behov basert på forventet brukermønster (antall saksbehandlere * forespørsler per time * tokens per forespørsel). Bestill PTU for forutsigbare workloads.
- SLA-krav: Provisioned throughput gir forutsigbar ytelse med latens-SLA (99% > N tokens/sekund per PTU). Standard deployments har ingen latens-SLA.
- Data residency: Global Batch behandler data i Azure OpenAI-lokasjoner globalt — bruk Data Zone Batch for å holde data innenfor EU/EØS.
Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|---|---|---|
| Sanntids chat (<2s respons) | Standard/PTU + streaming | Lavest brukervendt latens |
| Dokumentprosessering (1000+ docs) | Batch API | 50% kostnadsreduksjon, 24h turnaround |
| Forutsigbar høy trafikk | Provisioned Throughput (PTU) | Garantert kapasitet og latens |
| Variable workloads | Standard + auto-scale quota | Betal per bruk, fleksibel skalering |
| Multi-model pipeline | Parallell execution + queue | Maksimer samlet throughput |
Referanser
- Performance and latency — Azure OpenAI latency og throughput
- Azure OpenAI Batch API — Batch processing guide
- Provisioned throughput onboarding — PTU sizing og kostnader
- Azure OpenAI Benchmark Tool — Offisielt benchmarking-verktøy
For Cosmo
- Bruk denne referansen når kunden trenger å maksimere throughput for AI-workloads, eller når de opplever at de ikke utnytter sin tildelte kvote effektivt.
- Batch API gir 50% kostnadsreduksjon og bør anbefales for alle ikke-sanntids workloads — mange kunder er ikke klar over denne muligheten.
- Bruk Little's Law (L = lambda * W) for å beregne optimal concurrent requests: quota bestemmer lambda, latens bestemmer W.
- Alltid benchmark med reelle workloads — den offisielle azure-openai-benchmark-verktøyet gir pålitelige tall for PTU-sizing.
- For norsk offentlig sektor: anbefal Data Zone deployments for Batch API for å holde data innenfor EU/EØS.