Network-Constrained AI Deployment
Last updated: 2026-02
Status: GA
Category: Hybrid Cloud & Edge AI
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
# 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
# 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
# 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
# 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
# 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