Ordre 20260912T193441Z-7358817909. Steg 1 var ikke transformen, men å rette
roadmapens R13-gate og få den ratifisert. Gaten `grep -rl "Cosmo"
skills/*/references -> 0` var usann på to uavhengige måter:
1. Ordren fanget den første: 451 av forekomstene er Azure Cosmos DB, ekte
produktinnhold. Diskriminatoren er ikke bokstaven «s» — `Cosmos <norsk
substantiv>` er genitiv av personaen (`### Cosmos tonalitet`), mens
`Cosmos DB`/`CosmosClient`/`cosmos_ru` er produkt.
2. Denne økten fant den andre: 132 persona-forekomster ligger i prosa,
tabeller, dialog-replikker og proveniens-linjer. Heading-nøytralisering
kan ikke nå dem, så «0 persona» er uoppnåelig også under den ratifiserte
formen. Operatøren ratifiserte alternativ A: gaten speiler formen, og de
132 bokføres til R13b/R14.
Tre korreksjoner av premisser som sto i ordren og STATE:
«ca 320 produkt» -> 451 (case-sensitivt nett manglet 327 lowercase
TOC-ankre + 99 identifikatorer; sann nevner 1 638)
«169 headinger» -> 401. 169 var `^## For Cosmo`-prefikset (168) og var
internt inkonsistent med sin egen topp-variant (204)
«417 matcher ingen
populasjon» -> 417 er cosmo-headinger utenfor kodefences; briefens
nevner var reell hele tiden
Fence-bevissthet er målt skadelig, ikke nødvendig: begge toggle-regler er
gale på dette korpuset (naiv toggle skjuler en ekte heading i
chain-of-thought-prompting.md, CommonMark-regelen ubalanserer
service-level-documentation-dr.md). Fence-agnostisk deteksjon finner 401
heading-linjer i nøyaktig de samme 40 variantene som fence-bevisst finner
400 i — ingen kodeblokk-linje er byte-identisk til en persona-heading. Derfor
nøkles transformen på 40 enumererte heading-tekster og ignorerer fences. En
ukjent variant kaster; en slug-kollisjon kaster. Ingenting auto-fikses.
TOC-en regenereres ikke, den rettes kirurgisk: alle 327 persona-lenker hadde
lenketekst lik én av de 40 heading-tekstene og anker lik slugify av den
(327/327, 0 avvik), så heading og TOC-entry skrives i samme operasjon og
ingen mellomtilstand etterlater en død lenke.
Ratifisert målform: `For Cosmo`, `For Cosmo Skyberg` og `For arkitekten
(Cosmo)` konvergerer på `For arkitekten`. To filer kolliderte og er adjudisert
ved å lese dem, ikke ved regel.
Verifisering (alle 7 kriterier fra ordren):
G1 persona på heading-linjer 401 -> 0
G2 døde fragmentlenker 1 -> 1 (pre-eksisterende, unntatt)
G3 produkt-forekomster 451 -> 451; `Cosmos DB|Azure Cosmos` 308 = 308
de 3 kun-produkt-filene byte-identiske
nettet validert begge veier injisert persona feller G1; genitiv feller G1;
produkt-heading og de 3 filene passerer
hele diffen 802 heading-linjer + 654 TOC-linjer, ANNET = 0
linjeantall 728 lagt til = 728 slettet
suite 1120/1120 (1097 + 23 nye)
validate-plugin 250 PASS / 0 FAIL
stikkprøve 10 filer, alle 5 skills, inkl. de 3 mest
produkt-tunge (26/20/19) — kun heading+TOC
Utenfor scope, urørt: de 4 SKILL.md, de 23 commands, CLAUDE.md, README.md,
NOTICE.md, docs/ (alt R14).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
358 lines
13 KiB
Markdown
358 lines
13 KiB
Markdown
# Token-Per-Second Optimization
|
|
|
|
**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](#introduksjon)
|
|
- [Kjernekomponenter](#kjernekomponenter)
|
|
- [Batch Sizing Impact](#batch-sizing-impact)
|
|
- [Prompt Length Optimization](#prompt-length-optimization)
|
|
- [GPU Utilization og throughput-monitorering](#gpu-utilization-og-throughput-monitorering)
|
|
- [Throughput per PTU per modell](#throughput-per-ptu-per-modell)
|
|
- [Norsk offentlig sektor](#norsk-offentlig-sektor)
|
|
- [Beslutningsrammeverk](#beslutningsrammeverk)
|
|
- [Referanser](#referanser)
|
|
- [For arkitekten](#for-arkitekten)
|
|
|
|
## Introduksjon
|
|
|
|
Token-per-second (TPS) er en kritisk ytelsesmetrikk for Azure OpenAI-deployments som måler hvor raskt modellen genererer output-tokens. Denne metrikken påvirker direkte brukeropplevelsen ved streaming og den totale gjennomstrømmingen for batch-workloads. Azure OpenAI tilbyr latens-mål per PTU som varierer fra 25 TPS (o1) til 100 TPS (gpt-4.1-nano), og optimalisering av TPS er nøkkelen til å utnytte tildelt kapasitet effektivt.
|
|
|
|
TPS avhenger av flere faktorer: modellstørrelse, prompt-lengde (antall input-tokens), requested output length (max_tokens), samtidige forespørsler, og om prompt caching er aktiv. For Provisioned Throughput (PTU) deployments er TPS direkte koblet til utilization — når utilization nærmer seg 100%, begynner nye forespørsler å få 429-feil. For Standard deployments er TPS begrenset av den tildelte TPM-kvoten.
|
|
|
|
For norsk offentlig sektor, der AI-assistenter brukes av saksbehandlere i sanntid, er TPS-optimalisering direkte knyttet til arbeidseffektivitet. Forskjellen mellom 25 TPS og 80 TPS betyr at en 400-tokens respons leveres på 16 sekunder vs. 5 sekunder.
|
|
|
|
## Kjernekomponenter
|
|
|
|
| Komponent | Formål | Teknologi |
|
|
|-----------|--------|-----------|
|
|
| Provisioned Throughput (PTU) | Dedikert kapasitet med TPS-garantier | Azure OpenAI PTU |
|
|
| Prompt Caching | Reduser input-prosessering for bedre TPS | Azure OpenAI Caching |
|
|
| Predicted Outputs | Spekulative output for raskere generering | Azure OpenAI Preview |
|
|
| Azure Monitor | TPS- og utilization-metrikker | Azure Monitor |
|
|
| Capacity Calculator | PTU-estimering basert på workload | Microsoft Foundry |
|
|
|
|
## Batch Sizing Impact
|
|
|
|
### Hvordan batch-størrelse påvirker TPS
|
|
|
|
```python
|
|
# Demonstrer forholdet mellom concurrent requests og throughput
|
|
import asyncio
|
|
import time
|
|
from openai import AsyncAzureOpenAI
|
|
|
|
async def measure_tps_at_concurrency(
|
|
client: AsyncAzureOpenAI,
|
|
model: str,
|
|
concurrency: int,
|
|
num_requests: int = 50,
|
|
max_tokens: int = 200
|
|
) -> dict:
|
|
"""Measure tokens per second at different concurrency levels."""
|
|
semaphore = asyncio.Semaphore(concurrency)
|
|
total_output_tokens = 0
|
|
successful = 0
|
|
|
|
async def single_request():
|
|
nonlocal total_output_tokens, successful
|
|
async with semaphore:
|
|
try:
|
|
response = await client.chat.completions.create(
|
|
model=model,
|
|
messages=[{"role": "user",
|
|
"content": "Skriv en kort forklaring av KI."}],
|
|
max_tokens=max_tokens
|
|
)
|
|
total_output_tokens += response.usage.completion_tokens
|
|
successful += 1
|
|
except Exception:
|
|
pass
|
|
|
|
start = time.time()
|
|
await asyncio.gather(*[single_request() for _ in range(num_requests)])
|
|
duration = time.time() - start
|
|
|
|
return {
|
|
"concurrency": concurrency,
|
|
"total_output_tokens": total_output_tokens,
|
|
"duration_seconds": round(duration, 2),
|
|
"aggregate_tps": round(total_output_tokens / duration, 1),
|
|
"per_request_tps": round(
|
|
total_output_tokens / max(successful, 1) /
|
|
(duration / max(successful, 1)), 1),
|
|
"successful": successful
|
|
}
|
|
|
|
# Kjør test ved ulike concurrency-nivåer
|
|
async def find_optimal_concurrency(client, model):
|
|
results = []
|
|
for concurrency in [1, 5, 10, 20, 50]:
|
|
result = await measure_tps_at_concurrency(
|
|
client, model, concurrency)
|
|
results.append(result)
|
|
print(f"Concurrency {concurrency}: "
|
|
f"{result['aggregate_tps']} aggregate TPS")
|
|
return results
|
|
|
|
# Typisk resultat for PTU deployment:
|
|
# Concurrency 1: ~40 TPS (per-request max)
|
|
# Concurrency 5: ~180 TPS (aggregate)
|
|
# Concurrency 10: ~320 TPS (aggregate, nær optimal)
|
|
# Concurrency 20: ~350 TPS (aggregate, diminishing returns)
|
|
# Concurrency 50: ~350 TPS (aggregate, 429s starter)
|
|
```
|
|
|
|
## Prompt Length Optimization
|
|
|
|
### Reduser input-tokens for bedre TPS
|
|
|
|
```python
|
|
def optimize_prompt_for_tps(
|
|
system_prompt: str,
|
|
user_input: str,
|
|
max_system_tokens: int = 500,
|
|
context_budget: int = 4000
|
|
) -> dict:
|
|
"""Optimize prompt length to improve TPS."""
|
|
import tiktoken
|
|
|
|
enc = tiktoken.encoding_for_model("gpt-4o")
|
|
|
|
original_system_tokens = len(enc.encode(system_prompt))
|
|
original_user_tokens = len(enc.encode(user_input))
|
|
original_total = original_system_tokens + original_user_tokens
|
|
|
|
optimizations = []
|
|
|
|
# 1. Kompakt system prompt
|
|
if original_system_tokens > max_system_tokens:
|
|
optimizations.append(
|
|
f"System prompt er {original_system_tokens} tokens "
|
|
f"(mål: {max_system_tokens}). Vurder å flytte eksempler "
|
|
f"til fine-tuning.")
|
|
|
|
# 2. Trunkér brukerinput til kontekstbudsjett
|
|
if original_user_tokens > context_budget:
|
|
optimizations.append(
|
|
f"User input er {original_user_tokens} tokens "
|
|
f"(budsjett: {context_budget}). Bruk chunking eller "
|
|
f"pre-summarization.")
|
|
|
|
# 3. Fjern redundans
|
|
# Identifiser gjentatte seksjoner i prompt
|
|
lines = system_prompt.split('\n')
|
|
unique_lines = list(dict.fromkeys(lines)) # Behold rekkefølge
|
|
if len(unique_lines) < len(lines):
|
|
optimizations.append(
|
|
f"Fjern {len(lines) - len(unique_lines)} dupliserte linjer "
|
|
f"i system prompt.")
|
|
|
|
return {
|
|
"original_tokens": original_total,
|
|
"estimated_savings": len(optimizations) * 100, # Estimat
|
|
"optimizations": optimizations,
|
|
"tps_impact": (
|
|
"Kortere prompts → raskere prefill → "
|
|
"lavere time-to-first-token")
|
|
}
|
|
|
|
# Prompt caching for gjentatte prefixer
|
|
def design_cacheable_prompt(
|
|
static_system: str,
|
|
static_examples: list[str],
|
|
dynamic_input: str
|
|
) -> list[dict]:
|
|
"""Design prompt structure optimized for prompt caching."""
|
|
# Plaserer alt statisk innhold FØRST (minimum 1024 tokens)
|
|
# Prompt caching cacher identiske prefixer
|
|
|
|
messages = [
|
|
{
|
|
"role": "system",
|
|
"content": static_system # Statisk — cacheable
|
|
}
|
|
]
|
|
|
|
# Legg til eksempler som del av cacheable prefix
|
|
for example in static_examples:
|
|
messages.extend([
|
|
{"role": "user", "content": example["input"]},
|
|
{"role": "assistant", "content": example["output"]}
|
|
])
|
|
|
|
# Dynamisk input SIST
|
|
messages.append({
|
|
"role": "user",
|
|
"content": dynamic_input # Varierer per forespørsel
|
|
})
|
|
|
|
return messages
|
|
```
|
|
|
|
## GPU Utilization og throughput-monitorering
|
|
|
|
### Azure Monitor-metrikker for TPS
|
|
|
|
```python
|
|
# KQL: Beregn faktisk TPS fra Azure Monitor
|
|
|
|
TPS_CALCULATION = """
|
|
AzureMetrics
|
|
| where ResourceProvider == "MICROSOFT.COGNITIVESERVICES"
|
|
| where MetricName == "GeneratedTokens"
|
|
| summarize
|
|
TotalTokens = sum(Total),
|
|
AvgTokensPerMinute = avg(Total),
|
|
MaxTokensPerMinute = max(Total)
|
|
by bin(TimeGenerated, 1m), deploymentName = tostring(
|
|
split(DimensionValue, ",")[0])
|
|
| extend TokensPerSecond = TotalTokens / 60.0
|
|
| project TimeGenerated, deploymentName,
|
|
TokensPerSecond = round(TokensPerSecond, 1),
|
|
AvgTPM = round(AvgTokensPerMinute, 0)
|
|
| order by TimeGenerated desc
|
|
"""
|
|
|
|
# Utilization vs TPS-korrelasjon
|
|
UTILIZATION_VS_TPS = """
|
|
AzureMetrics
|
|
| where MetricName in ("ProvisionedManagedUtilizationV2", "GeneratedTokens")
|
|
| summarize
|
|
Utilization = avgif(Average,
|
|
MetricName == "ProvisionedManagedUtilizationV2"),
|
|
TPS = sumif(Total,
|
|
MetricName == "GeneratedTokens") / 60.0
|
|
by bin(TimeGenerated, 5m)
|
|
| where Utilization > 0
|
|
| project TimeGenerated,
|
|
Utilization = round(Utilization, 1),
|
|
TPS = round(TPS, 1)
|
|
| order by TimeGenerated desc
|
|
"""
|
|
```
|
|
|
|
### TPS-overvåking dashboard
|
|
|
|
```python
|
|
# Alert rule: Varsle når TPS faller under terskel
|
|
ALERT_CONFIG = {
|
|
"name": "Low TPS Alert",
|
|
"description": "Tokens per second under forventet nivå",
|
|
"condition": {
|
|
"query": """
|
|
AzureMetrics
|
|
| where MetricName == "GeneratedTokens"
|
|
| summarize TPS = sum(Total) / 300.0
|
|
by bin(TimeGenerated, 5m)
|
|
| where TPS < 20
|
|
""",
|
|
"threshold": 0,
|
|
"frequency_minutes": 5,
|
|
"window_minutes": 15
|
|
},
|
|
"severity": 2, # Warning
|
|
"action_group": "ai-ops-team"
|
|
}
|
|
```
|
|
|
|
## Throughput per PTU per modell
|
|
|
|
### Offisielle TPS-mål fra Microsoft
|
|
|
|
| Modell | Input TPM/PTU | Latens-mål (TPS) | Min PTU (Global) | Min PTU (Regional) |
|
|
|--------|--------------|-------------------|-------------------|---------------------|
|
|
| gpt-5.2 | 3,400 | 99% > 50 TPS | 15 | 50 |
|
|
| gpt-5.1 | 4,750 | 99% > 50 TPS | 15 | 50 |
|
|
| gpt-5 | 4,750 | 99% > 50 TPS | 15 | 50 |
|
|
| gpt-5-mini | 23,750 | 99% > 80 TPS | 15 | 25 |
|
|
| gpt-4.1 | 3,000 | 99% > 80 TPS | 15 | 50 |
|
|
| gpt-4.1-mini | 14,900 | 99% > 90 TPS | 15 | 25 |
|
|
| gpt-4.1-nano | 59,400 | 99% > 100 TPS | 15 | 25 |
|
|
| o3 | 3,000 | 99% > 80 TPS | 15 | 50 |
|
|
| o4-mini | 5,400 | 99% > 90 TPS | 15 | 25 |
|
|
| gpt-4o | 2,500 | 99% > 25 TPS | 15 | 50 |
|
|
| gpt-4o-mini | 37,000 | 99% > 33 TPS | 15 | 25 |
|
|
|
|
### Predicted Outputs for TPS-forbedring
|
|
|
|
```python
|
|
# Predicted outputs kan øke TPS for forutsigbare oppgaver
|
|
# Eksempel: Kode-refactoring der output ligner input
|
|
|
|
from openai import AzureOpenAI
|
|
|
|
client = AzureOpenAI(
|
|
azure_endpoint="https://my-aoai.openai.azure.com",
|
|
api_key="...",
|
|
api_version="2024-12-01-preview"
|
|
)
|
|
|
|
# Original kode som skal refaktoreres
|
|
original_code = """
|
|
def process_data(data):
|
|
result = []
|
|
for item in data:
|
|
if item['status'] == 'active':
|
|
result.append(item['value'])
|
|
return result
|
|
"""
|
|
|
|
response = client.chat.completions.create(
|
|
model="gpt-4o",
|
|
messages=[
|
|
{"role": "user",
|
|
"content": f"Refaktorer med list comprehension:\n{original_code}"}
|
|
],
|
|
prediction={
|
|
"type": "content",
|
|
"content": original_code # Forventet output ligner input
|
|
}
|
|
)
|
|
|
|
# Sjekk prediction effektivitet
|
|
usage = response.usage.completion_tokens_details
|
|
print(f"Accepted predictions: {usage.accepted_prediction_tokens}")
|
|
print(f"Rejected predictions: {usage.rejected_prediction_tokens}")
|
|
# Accepted tokens reduserer latens uten ekstra kostnad
|
|
```
|
|
|
|
## Norsk offentlig sektor
|
|
|
|
- **Brukeropplevelse**: For AI-assistenter brukt av saksbehandlere er TPS direkte merkbar. Streaming med > 50 TPS føles "responsivt", mens < 20 TPS føles tregt.
|
|
- **PTU-valg**: Velg modell og PTU-nivå basert på brukerforventninger — gpt-4.1-nano med 100 TPS for enkle oppgaver, gpt-4.1 med 80 TPS for komplekse analyser.
|
|
- **Kostnadsoptimalisering**: Prompt caching gir opptil 100% rabatt på cached input tokens for PTU — design prompts med lange statiske prefixer for å maksimere cache hit rate.
|
|
- **SLA-krav**: Dokumenter forventet TPS i tjenesteavtaler. PTU-mål er "99% > N TPS beregnet som p50 over 5-minutters vinduer".
|
|
|
|
## Beslutningsrammeverk
|
|
|
|
| Scenario | Anbefaling | Begrunnelse |
|
|
|----------|------------|-------------|
|
|
| Sanntids chat (høy TPS viktig) | PTU med gpt-4.1-mini/nano | Høyest TPS per PTU |
|
|
| Kompleks analyse (kvalitet > TPS) | PTU med gpt-4.1 eller o3 | Akseptabel TPS med best kvalitet |
|
|
| Prompt caching mulig | Design lange statiske prefixer | Opptil 100% rabatt på cached tokens |
|
|
| Forutsigbart output | Predicted Outputs | Reduserer latens for matching tokens |
|
|
| Variabel workload | Standard deployment | Betal per token, ingen PTU-forpliktelse |
|
|
|
|
## Referanser
|
|
|
|
- [Performance and latency](https://learn.microsoft.com/azure/foundry/openai/how-to/latency) — TPS og throughput forklaring
|
|
- [Provisioned throughput onboarding](https://learn.microsoft.com/azure/foundry/openai/concepts/provisioned-throughput-billing) — PTU TPS-mål per modell
|
|
- [Prompt caching](https://learn.microsoft.com/azure/foundry/openai/how-to/prompt-caching) — Cache-basert TPS-forbedring
|
|
- [Predicted outputs](https://learn.microsoft.com/azure/foundry/openai/how-to/predicted-outputs) — Spekulativ generering
|
|
- [Foundry PTU calculator](https://ai.azure.com/resource/calculator) — Kapasitetskalkulator
|
|
|
|
## For arkitekten
|
|
|
|
- **Bruk denne referansen** når kunden ønsker å optimalisere responstid for AI-tjenester, eller når de skal dimensjonere PTU-deployments.
|
|
- TPS-mål varierer dramatisk mellom modeller: gpt-4.1-nano gir 100 TPS vs. gpt-4o med 25 TPS — velg modell basert på oppgavens kompleksitet.
|
|
- Prompt caching er den enkleste TPS-forbedringen — sørg for at de første 1024+ tokens er identiske mellom forespørsler.
|
|
- Predicted Outputs gir latensreduksjon for oppgaver der output ligner input (kode-refactoring, oversettelse) men kan øke kostnad ved lav acceptance rate.
|
|
- Monitorer PTU utilization — når den nærmer seg 100%, øker latens drastisk og nye forespørsler throttles.
|