ms-ai-architect/skills/ms-ai-infrastructure/references/hybrid-edge/network-constrained-ai-deployment.md
Kjell Tore Guttormsen ddce43d8b2 feat(ms-ai-architect): Spor 1 — Port-1-substrat migrert på 4 ikke-advisor-skills (243 Source + 327 Type + 325 TOC + stale-verified poison fjernet) [skip-docs]
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.
2026-07-04 10:19:11 +02:00

483 lines
18 KiB
Markdown

# Network-Constrained AI Deployment
**Last updated:** 2026-02
**Status:** GA
**Category:** Hybrid Cloud & Edge AI
**Type:** reference
---
## Innhold
- [Introduksjon](#introduksjon)
- [Kjernekomponenter](#kjernekomponenter)
- [Model Size Reduction](#model-size-reduction)
- [Partial Model Loading](#partial-model-loading)
- [Bandwidth-Aware Batching](#bandwidth-aware-batching)
- [Latency Compensation Patterns](#latency-compensation-patterns)
- [Norsk offentlig sektor](#norsk-offentlig-sektor)
- [Beslutningsrammeverk](#beslutningsrammeverk)
- [For Cosmo](#for-cosmo)
## Introduksjon
Nettverksbegrensede miljoer — med lav bandwidth, hoey latens, eller intermitterende tilkobling — krever spesialtilpassede AI-deployments. Standard sky-baserte AI-arkitekturer som sender data frem og tilbake til cloud endpoints feiler i slike miljoer, enten pa grunn av uakseptabel latens eller fordi tilkoblingen simpelthen ikke er palitelig nok.
For norsk offentlig sektor er dette relevant i mange scenarier: rurale omrader med begrenset mobildekning, maritime miljoer med satellittkommunikasjon, tunneler og underjordiske anlegg, feltoperasjoner i krisesituasjoner, og industrielle miljoer med nettverksisolasjon av sikkerhetsgrunner. AI-losninger for slike miljoer ma optimaliseres for minimal nettverksbruk.
Microsoft tilbyr flere teknologier for nettverksbegrensede deployments: modellkvantisering og -komprimering med Olive/ONNX Runtime for mindre modeller, Azure IoT Edge med utvidet offline-stoette for edge-prosessering, delta-synkronisering for effektiv dataoverfoering, og bandwidth-bevisst batching for a maksimere utnyttelsen av tilgjengelig tilkobling.
---
## Kjernekomponenter
| Komponent | Formal | Teknologi |
|-----------|--------|-----------|
| ONNX Runtime | Optimalisert lokal inferens | Cross-platform |
| Olive | Modellkomprimering og kvantisering | Python |
| Azure IoT Edge | Edge-prosessering med buffring | Container runtime |
| Delta Sync | Inkrementell datasynkronisering | Protokoll |
| MQTT | Lettvekts meldingsprotokoll | IoT-standard |
| gRPC | Effektiv binart API-protokoll | Google RPC |
| Protocol Buffers | Kompakt serialisering | Google |
---
## Model Size Reduction
### Kvantiseringsstrategier for nettverksbegrensede miljoer
| Teknikk | Stoerrelses-reduksjon | Kvalitetstap | Nedlastningstid (1 Mbps) |
|---------|---------------------|-------------|--------------------------|
| FP32 (original) | Baseline (7 GB for 3.8B) | Ingen | 15+ timer |
| FP16 | 2x (3.5 GB) | Minimalt | 7+ timer |
| INT8 | 4x (1.75 GB) | < 1% | 3.5 timer |
| INT4 | 8x (875 MB) | 1-3% | 1.7 timer |
| INT4 + Pruning | 10-12x (600 MB) | 2-5% | 1.2 timer |
| Distillation | 20-50x (140-350 MB) | 5-15% | 15-40 min |
### Olive-basert komprimering
```python
# Olive pipeline for maksimal modellkomprimering
import json
olive_config = {
"input_model": {
"type": "HfModel",
"model_path": "microsoft/Phi-3-mini-4k-instruct"
},
"passes": {
# Steg 1: Konverter til ONNX
"conversion": {
"type": "OnnxConversion",
"target_opset": 17
},
# Steg 2: Grafoptimalisering
"optimization": {
"type": "OrtTransformersOptimization",
"model_type": "gpt2",
"opt_level": 2,
"only_onnxruntime": True
},
# Steg 3: Kvantisering til INT4
"quantization": {
"type": "OnnxMatMul4Quantizer",
"block_size": 32,
"is_symmetric": True,
"accuracy_level": 4
},
# Steg 4: Strukturell pruning (fjern unodvendige vekter)
"pruning": {
"type": "SlicGPT",
"sparsity": 0.25, # 25% reduksjon
"calibration_data_config": {
"name": "c4",
"split": "validation",
"num_samples": 128
}
}
},
"engine": {
"evaluator": {
"metrics": [
{
"name": "model_size",
"type": "custom_metric",
"priority": 1
},
{
"name": "latency",
"type": "latency",
"priority": 2
}
]
}
}
}
# Kjor Olive-pipeline
# olive run --config olive_compress.json
```
### Kunnskapsdesstillasjon for ultra-sma modeller
```python
# Destiller fra Phi-3 Medium (14B) til en 1B custom-modell
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
class ModelDistillation:
def __init__(self, teacher_model: str, student_config: dict):
self.teacher = AutoModelForCausalLM.from_pretrained(teacher_model)
self.student = self._create_student(student_config)
self.tokenizer = AutoTokenizer.from_pretrained(teacher_model)
def distill(self, dataset, epochs: int = 5, temperature: float = 2.0):
"""Destiller laerer-modell til elev-modell"""
optimizer = torch.optim.AdamW(self.student.parameters(), lr=1e-4)
for epoch in range(epochs):
for batch in dataset:
inputs = self.tokenizer(batch["text"], return_tensors="pt",
padding=True, truncation=True)
# Laerer-prediksjoner (soft targets)
with torch.no_grad():
teacher_logits = self.teacher(**inputs).logits
# Elev-prediksjoner
student_logits = self.student(**inputs).logits
# Destillasjonsloss: KL-divergens med temperatur
loss = self._distillation_loss(
student_logits, teacher_logits, temperature
)
loss.backward()
optimizer.step()
optimizer.zero_grad()
def _distillation_loss(self, student_logits, teacher_logits, temperature):
"""KL-divergens mellom student og teacher"""
import torch.nn.functional as F
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
return F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (temperature ** 2)
```
---
## Partial Model Loading
### Modell-segmentering for inkrementell nedlasting
```python
# Delvis modellnedlasting for nettverksbegrensede miljoer
import os
import hashlib
from typing import Optional
class IncrementalModelLoader:
def __init__(self, model_dir: str, remote_url: str):
self.model_dir = model_dir
self.remote_url = remote_url
self.manifest_path = os.path.join(model_dir, "manifest.json")
def check_and_download(self, bandwidth_kbps: float) -> dict:
"""Sjekk modellstatus og last ned manglende deler"""
manifest = self._get_remote_manifest()
local_state = self._get_local_state()
missing_segments = []
total_download_bytes = 0
for segment in manifest["segments"]:
local_hash = local_state.get(segment["name"])
if local_hash != segment["hash"]:
missing_segments.append(segment)
total_download_bytes += segment["size"]
# Estimer nedlastningstid
download_time_seconds = (total_download_bytes * 8) / (bandwidth_kbps * 1000)
return {
"model_version": manifest["version"],
"total_segments": len(manifest["segments"]),
"missing_segments": len(missing_segments),
"download_size_mb": total_download_bytes / 1024 / 1024,
"estimated_time_minutes": download_time_seconds / 60,
"can_use_partial": manifest.get("supports_partial_inference", False),
"minimum_segments_for_inference": manifest.get("min_segments", 1)
}
def download_prioritized(self, bandwidth_kbps: float,
time_budget_minutes: float) -> str:
"""Last ned modellsegmenter prioritert innenfor tidsbudsjett"""
check = self.check_and_download(bandwidth_kbps)
if check["estimated_time_minutes"] <= time_budget_minutes:
# Full nedlasting mulig
return self._download_all_segments()
else:
# Prioritert delvis nedlasting
# Last ned kritiske segmenter foerst (embedding, attention heads)
return self._download_critical_first(time_budget_minutes, bandwidth_kbps)
def _download_critical_first(self, time_budget: float, bw: float) -> str:
"""Last ned de viktigste modelldelene foerst"""
priority_order = [
"embeddings", # Nodvendig for all inferens
"attention_layers", # Kjernekapabilitet
"ffn_layers", # Detaljert prosessering
"output_head" # Siste lag
]
# Download i prioritert rekkefoolge innenfor tidsbudsjett
downloaded = []
remaining_seconds = time_budget * 60
for priority in priority_order:
segment_size = self._get_segment_size(priority)
download_time = (segment_size * 8) / (bw * 1000)
if download_time <= remaining_seconds:
self._download_segment(priority)
downloaded.append(priority)
remaining_seconds -= download_time
else:
break
return f"Lastet ned {len(downloaded)}/{len(priority_order)} segmenter"
```
---
## Bandwidth-Aware Batching
### Adaptiv batchstoerrelser basert pa tilgjengelig bandwidth
```python
# Bandwidth-bevisst batch-synkronisering
import asyncio
import time
from dataclasses import dataclass
@dataclass
class BandwidthProfile:
estimated_kbps: float
latency_ms: float
reliability: float # 0-1, andel vellykkede overforinger
class AdaptiveBatchSync:
def __init__(self):
self.bandwidth_history: list[BandwidthProfile] = []
self.compression_enabled = True
async def measure_bandwidth(self) -> BandwidthProfile:
"""Mal tilgjengelig bandwidth med minimal data"""
test_data = b"x" * 1024 # 1 KB testpakke
start = time.time()
try:
# Send testpakke og mal round-trip
success = await self._send_probe(test_data)
elapsed = time.time() - start
profile = BandwidthProfile(
estimated_kbps=(len(test_data) * 8) / (elapsed * 1000),
latency_ms=elapsed * 1000,
reliability=1.0 if success else 0.0
)
except Exception:
profile = BandwidthProfile(
estimated_kbps=0, latency_ms=float('inf'), reliability=0.0
)
self.bandwidth_history.append(profile)
return profile
def calculate_optimal_batch(self, pending_items: int,
avg_item_size_kb: float) -> dict:
"""Beregn optimal batchstoorrelse basert pa nettverksforhold"""
if not self.bandwidth_history:
return {"batch_size": 1, "reason": "Ingen maalinger"}
recent = self.bandwidth_history[-5:] # Siste 5 maalinger
avg_bw = sum(p.estimated_kbps for p in recent) / len(recent)
avg_reliability = sum(p.reliability for p in recent) / len(recent)
if avg_reliability < 0.3:
# Svart upaalitelig — minimale batche
return {"batch_size": 1, "compress": True, "priority_only": True}
if avg_bw < 10: # < 10 kbps
# Ekstremt lav bandwidth
batch_size = min(5, pending_items)
return {
"batch_size": batch_size,
"compress": True,
"format": "protobuf",
"priority_only": True,
"estimated_time_s": (batch_size * avg_item_size_kb) / avg_bw
}
elif avg_bw < 100: # 10-100 kbps
# Lav bandwidth
batch_size = min(20, pending_items)
return {
"batch_size": batch_size,
"compress": True,
"format": "protobuf",
"priority_only": False
}
elif avg_bw < 1000: # 100 kbps - 1 Mbps
# Medium bandwidth
batch_size = min(100, pending_items)
return {
"batch_size": batch_size,
"compress": True,
"format": "json_gzip"
}
else: # > 1 Mbps
# God bandwidth
return {
"batch_size": min(500, pending_items),
"compress": False,
"format": "json"
}
```
---
## Latency Compensation Patterns
### Strategier for latenskompensering
| Monster | Beskrivelse | Implementering |
|---------|-------------|----------------|
| Optimistisk UI | Vis resultat umiddelbart, korriger senere | Lokal prediksjon + sky-validering |
| Prefetching | Forhaandslast sannsynlige data | Prediktiv caching |
| Stale-while-revalidate | Vis cachet data mens ny hentes | Cache-lag med TTL |
| Lokal buffer | Buffer resultater lokalt | SQLite + event queue |
| Priority queue | Prioriter kritiske data | Vektet synk-koe |
| Komprimering | Reduser datamengde | gzip, protobuf, CBOR |
### Implementering av latenskompensering
```python
# Latenskompenserende AI-proxy
import asyncio
import gzip
import json
from collections import OrderedDict
class LatencyCompensatingProxy:
def __init__(self, local_model, cache_size: int = 1000):
self.local_model = local_model
self.cache = OrderedDict()
self.cache_size = cache_size
self.pending_validations = asyncio.Queue()
async def predict(self, input_data: dict) -> dict:
"""Prediksjon med latenskompensering"""
cache_key = self._hash_input(input_data)
# Sjekk cache forst
if cache_key in self.cache:
cached = self.cache[cache_key]
self.cache.move_to_end(cache_key)
return {**cached, "source": "cache"}
# Lokal prediksjon (umiddelbar)
local_result = self.local_model.predict(input_data)
# Cache resultatet
self._cache_result(cache_key, local_result)
# Koe for sky-validering i bakgrunnen
await self.pending_validations.put({
"cache_key": cache_key,
"input": input_data,
"local_result": local_result
})
return {**local_result, "source": "local", "validated": False}
async def background_validator(self):
"""Bakgrunnsvalidering mot sky-modell"""
while True:
item = await self.pending_validations.get()
try:
cloud_result = await self._cloud_predict(item["input"])
# Oppdater cache med validert resultat
self._cache_result(item["cache_key"], {
**cloud_result,
"validated": True
})
# Varsle om avvik
if self._significant_difference(
item["local_result"], cloud_result
):
await self._notify_correction(item, cloud_result)
except Exception:
pass # Sky utilgjengelig — behold lokal prediksjon
def _significant_difference(self, local: dict, cloud: dict) -> bool:
"""Sjekk om sky-resultat avviker vesentlig fra lokalt"""
if local.get("label") != cloud.get("label"):
return True
if abs(local.get("confidence", 0) - cloud.get("confidence", 0)) > 0.2:
return True
return False
```
---
## Norsk offentlig sektor
### Nettverksbegrensede scenarier i Norge
| Scenario | Typisk bandwidth | Latens | Tilgjengelighet |
|----------|-----------------|--------|-----------------|
| Rural mobildekning | 1-10 Mbps | 50-200 ms | 70-90% |
| Maritim (kyst) | 0.5-5 Mbps | 200-600 ms | 60-80% |
| Tunnel/underjordisk | 0 Mbps (isolert) | N/A | 0% |
| Svalbart | 1-50 Mbps | 500+ ms | 80% |
| Beredskap (krise) | 0.1-1 Mbps | Variable | 30-70% |
| Felt (skog/fjell) | 0-5 Mbps | 100-500 ms | 40-80% |
### Anbefalinger
- Dimensjoner alltid for verste-tilfelle tilkobling
- Bruk INT4-kvantiserte modeller som standard for edge-deployment
- Implementer bandwidth-maling for a tilpasse sync-strategi dynamisk
- Protobuf/CBOR for serialisering i stedet for JSON i lav-bandwidth-scenarier
- Prioriter anomalier og kritiske resultater i sync-koeen
---
## Beslutningsrammeverk
| Scenario | Anbefaling | Begrunnelse |
|----------|------------|-------------|
| < 100 kbps | Full lokal inferens + minimal sync | Ikke nok bandwidth for sky-AI |
| 100 kbps - 1 Mbps | Lokal inferens + delta-sync | Synkroniser resultater, ikke radata |
| 1-10 Mbps | Hybrid med progressiv enhancement | Sky-validering av lokale resultater |
| > 10 Mbps | Standard sky-AI med lokal fallback | Normal drift med offline-buffer |
| Intermitterende | Event sourcing + prioritert batch-sync | Palitelig leveranse over tid |
| Satelitt (hoey latens) | Full lokal med periodisk bulk-sync | Latens for hoey for interaktiv sky-AI |
---
## For Cosmo
- **Modellstoerrelse er den viktigste faktoren for nettverksbegrensede deployments** — bruk INT4-kvantisering som standard, og vurder destillasjon for ultralette modeller under 500 MB
- **Bandwidth-bevisst batching er pabudt** — maal tilgjengelig bandwidth kontinuerlig og tilpass batchstoerrelser og kompresjonsformat dynamisk
- **Protobuf/CBOR sparer 60-80% bandwidth** sammenlignet med JSON — bruk binaere serialiseringsformater for all edge-til-sky-kommunikasjon i lav-bandwidth-miljoer
- **Inkrementell modellnedlasting er kritisk** for modelloppdatering over lav bandwidth — last ned kun endrede lag/segmenter, og stoeett delvis modellbruk under nedlasting
- **For norsk offentlig sektor: Design for 100 kbps som worst case** — mange felt-scenarier i rural Norge har begrenset 4G-dekning, og maritime/beredskapsscenarier kan ha enda lavere bandwidth