feat(ms-ai-architect): add plugin to open marketplace (v1.5.0 baseline)
Initial addition of ms-ai-architect plugin to the open-source marketplace. Private content excluded: orchestrator/ (Linear tooling), docs/utredning/ (client investigation), generated test reports and PDF export script. skill-gen tooling moved from orchestrator/ to scripts/skill-gen/. Security scan: WARNING (risk 20/100) — no secrets, no injection found. False positive fixed: added gitleaks:allow to Python variable reference in output-validation-grounding-verification.md line 109. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a8d79e4484
commit
6a7632146e
490 changed files with 213249 additions and 2 deletions
|
|
@ -0,0 +1,385 @@
|
|||
# Azure Arc for AI Management
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Azure Arc er Microsofts svar på utfordringen med å administrere AI-arbeidsbelastninger på tvers av hybride og multicloud-miljøer. For norsk offentlig sektor, der data kan befinne seg i egne datasentre, på Azure Local-klynger eller hos tredjeparts skyleverandorer, gir Arc en sentralisert kontrollflate som gjor det mulig å behandle alle Kubernetes-klynger som forsteklasses Azure-ressurser.
|
||||
|
||||
Med Azure Arc-enabled Kubernetes kan organisasjoner koble sammen klynger som kjorer lokalt, i Azure eller hos andre skyleverandorer, og administrere dem fra Azure Portal med ensartede policyer, overvaking og sikkerhetskontroller. Dette er spesielt verdifullt for AI-arbeidsbelastninger som krever GPU-akselerasjon, modellversjonering og sentralisert governance.
|
||||
|
||||
For offentlige virksomheter i Norge betyr dette at man kan overholde krav til datasuverenitet og plassering av data, samtidig som man drar nytte av Azures ML-plattform for trening og inferens pa tvers av distribuerte miljoer.
|
||||
|
||||
---
|
||||
|
||||
## Arkitekturoversikt
|
||||
|
||||
Azure Arc for AI Management bygger pa tre lag:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Azure Control Plane │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
|
||||
│ │ Azure │ │ Azure │ │ Azure Machine │ │
|
||||
│ │ Policy │ │ Monitor │ │ Learning │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └───────┬──────────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────────┼───────────────┘ │
|
||||
│ │ Azure Arc │
|
||||
└─────────────────────┼───────────────────────────-┘
|
||||
│
|
||||
┌─────────────┼──────────────┐
|
||||
│ │ │
|
||||
┌────▼───┐ ┌────▼───┐ ┌─────▼──────┐
|
||||
│ On-prem│ │ Azure │ │ Multi-cloud│
|
||||
│ K8s │ │ AKS │ │ K8s │
|
||||
│ Cluster│ │ Cluster│ │ Cluster │
|
||||
└────────┘ └────────┘ └────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Arc-enabled Kubernetes for AI
|
||||
|
||||
### Tilkobling av klynger
|
||||
|
||||
Azure Arc-enabled Kubernetes lar deg koble eksisterende Kubernetes-klynger til Azure for sentralisert administrasjon. Klynger kan kjore pa:
|
||||
|
||||
| Plattform | Stotte | Beskrivelse |
|
||||
|-----------|--------|-------------|
|
||||
| AKS i Azure | Innebygd | Fullt administrert Kubernetes i skyen |
|
||||
| AKS pa Azure Local | GA | Kubernetes pa egne servere med Azure-integrasjon |
|
||||
| Arc-enabled K8s (on-prem) | GA | Alle CNCF-sertifiserte klynger lokalt |
|
||||
| Arc-enabled K8s (multicloud) | GA | AWS EKS, Google GKE, etc. |
|
||||
| Edge-enheter | GA | Azure Stack Edge, IoT Edge-enheter |
|
||||
|
||||
### Tilkobling med Azure CLI
|
||||
|
||||
```bash
|
||||
# Koble en on-premises klynge til Azure Arc
|
||||
az connectedk8s connect \
|
||||
--name my-ai-cluster \
|
||||
--resource-group ai-rg \
|
||||
--location norwayeast
|
||||
|
||||
# Verifiser tilkobling
|
||||
az connectedk8s show \
|
||||
--name my-ai-cluster \
|
||||
--resource-group ai-rg
|
||||
```
|
||||
|
||||
### Arc-agenter
|
||||
|
||||
Nar en klynge kobles til Arc, installeres flere agenter:
|
||||
|
||||
| Agent | Funksjon |
|
||||
|-------|----------|
|
||||
| `clusteridentityoperator` | Administrerer managed identity for klyngen |
|
||||
| `clusterconnectoperator` | Hndterer klynge-tilkobling |
|
||||
| `configoperator` | Overvaker konfigurasjonsendringer |
|
||||
| `controlleroperator` | Orkestrerer andre agenter |
|
||||
| `fluxoperator` | GitOps-basert konfigurasjonsadministrasjon |
|
||||
| `extensionoperator` | Installerer og administrerer klynge-extensions |
|
||||
|
||||
---
|
||||
|
||||
## Sentralisert ML-modellforvaltning
|
||||
|
||||
### Azure Machine Learning Extension
|
||||
|
||||
Azure Machine Learning-extensionen er kjernen i AI-forvaltning pa Arc-enabled klynger. Den lar deg bruke Arc-klynger som compute targets for bade trening og inferens.
|
||||
|
||||
**Installasjon:**
|
||||
|
||||
```bash
|
||||
# Installer ML-extension pa Arc-enabled klynge
|
||||
az k8s-extension create \
|
||||
--name aml-extension \
|
||||
--extension-type Microsoft.AzureML.Kubernetes \
|
||||
--cluster-type connectedClusters \
|
||||
--cluster-name my-ai-cluster \
|
||||
--resource-group ai-rg \
|
||||
--scope cluster \
|
||||
--configuration-settings \
|
||||
enableTraining=True \
|
||||
enableInference=True \
|
||||
inferenceRouterServiceType=LoadBalancer \
|
||||
allowInsecureConnections=True
|
||||
```
|
||||
|
||||
**Koble til ML workspace:**
|
||||
|
||||
```bash
|
||||
# Attach klynge til Azure ML workspace
|
||||
az ml compute attach \
|
||||
--resource-group ai-workspace-rg \
|
||||
--workspace-name ai-workspace \
|
||||
--type Kubernetes \
|
||||
--name arc-compute \
|
||||
--resource-id "/subscriptions/<sub>/resourceGroups/ai-rg/providers/Microsoft.Kubernetes/connectedClusters/my-ai-cluster" \
|
||||
--namespace ai-workloads
|
||||
```
|
||||
|
||||
### Bruksmonster for Kubernetes Compute
|
||||
|
||||
| Monster | Data | Trening | Inferens | Bruksomrade |
|
||||
|---------|------|---------|----------|-------------|
|
||||
| Sky-forst | Sky | Azure | Azure | Standard ML-pipeline |
|
||||
| Hybrid trening | Lokalt | Lokalt | Sky | Datasuverenitiet, global tilgang |
|
||||
| Hybrid inferens | Sky | Sky | Lokalt | Latens, compliance |
|
||||
| Full lokal | Lokalt | Lokalt | Lokalt | Strengt regulert |
|
||||
| Multi-sky | Distribuert | Begge | Begge | Elastisitet + kontroll |
|
||||
|
||||
### KAITO - Kubernetes AI Toolchain Operator
|
||||
|
||||
KAITO forenkler deployment av open-source LLM-er pa Arc-enabled Kubernetes:
|
||||
|
||||
```yaml
|
||||
# workspace-phi4.yaml - Deploy Phi-4-mini pa Arc-klynge
|
||||
apiVersion: kaito.sh/v1alpha1
|
||||
kind: Workspace
|
||||
metadata:
|
||||
name: workspace-phi-4-mini
|
||||
spec:
|
||||
resource:
|
||||
instanceType: Standard_NC4_A2
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
apps: llm-inference
|
||||
inference:
|
||||
preset:
|
||||
name: phi-4-mini-instruct
|
||||
```
|
||||
|
||||
**Stottede GPU-modeller for KAITO pa Azure Local:**
|
||||
|
||||
| GPU | VM SKU | Stottede modeller |
|
||||
|-----|--------|-------------------|
|
||||
| NVIDIA T4 | Standard_NK6 | Phi-3-mini-4k |
|
||||
| NVIDIA A2 | Standard_NC4, NC8 | Phi-3-mini, Phi-3.5-mini |
|
||||
| NVIDIA A16 | Standard_NC16, NC32 | Phi-4-mini, Mistral-7B, Qwen2.5 |
|
||||
|
||||
---
|
||||
|
||||
## Policy og Compliance Enforcement
|
||||
|
||||
### Azure Policy for Kubernetes
|
||||
|
||||
Azure Policy kan handheve governance-regler pa tvers av alle Arc-enabled klynger. For AI-arbeidsbelastninger er dette kritisk for a sikre:
|
||||
|
||||
- Konsistente sikkerhetsinnstillinger pa tvers av klynger
|
||||
- Modell-deployment kun til godkjente noder
|
||||
- Overholdelse av dataklassifisering og suverenitetskrav
|
||||
- Standardiserte konfigurasjoner for GPU-ressurser
|
||||
|
||||
**Installasjon av Policy-extension:**
|
||||
|
||||
```bash
|
||||
# Installer Azure Policy pa Arc-klynge
|
||||
az k8s-extension create \
|
||||
--cluster-type connectedClusters \
|
||||
--cluster-name my-ai-cluster \
|
||||
--resource-group ai-rg \
|
||||
--extension-type Microsoft.PolicyInsights \
|
||||
--name azurepolicy
|
||||
```
|
||||
|
||||
### Innebygde policyer for AI-governance
|
||||
|
||||
| Policy | Kategori | Effekt |
|
||||
|--------|----------|--------|
|
||||
| Kubernetes-klynger bor ikke tillate privilegerte containere | Sikkerhet | Deny |
|
||||
| Kubernetes-klynger bor bruke interne lastbalanserere | Nettverk | Deny |
|
||||
| Kubernetes-klynger bor ha Azure Policy-addon | Compliance | Audit |
|
||||
| Kubernetes-klynger bor kun bruke godkjente container images | Supply chain | Deny |
|
||||
| Kubernetes-klynger bor ha resursgrenser | Ressurs | Audit |
|
||||
|
||||
### Tilpassede policyer for AI
|
||||
|
||||
```json
|
||||
{
|
||||
"if": {
|
||||
"allOf": [
|
||||
{
|
||||
"field": "type",
|
||||
"equals": "Microsoft.Kubernetes/connectedClusters"
|
||||
},
|
||||
{
|
||||
"field": "tags['ai-workload']",
|
||||
"exists": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"effect": "auditIfNotExists",
|
||||
"details": {
|
||||
"type": "Microsoft.KubernetesConfiguration/extensions",
|
||||
"existenceCondition": {
|
||||
"field": "Microsoft.KubernetesConfiguration/extensions/extensionType",
|
||||
"equals": "Microsoft.AzureML.Kubernetes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-cluster AI Governance
|
||||
|
||||
### Azure Kubernetes Fleet Manager
|
||||
|
||||
For organisasjoner med mange AI-klynger gir Fleet Manager sentralisert koordinering:
|
||||
|
||||
| Funksjon | Beskrivelse |
|
||||
|----------|-------------|
|
||||
| Cluster grouping | Grupper klynger etter formål (trening, inferens, edge) |
|
||||
| Update orchestration | Koordinerte oppdateringer pa tvers av klynger |
|
||||
| Configuration propagation | Distribuer GitOps-konfigurasjoner til mange klynger |
|
||||
| Multi-cluster networking | Service discovery pa tvers av klynger |
|
||||
|
||||
### GitOps-basert AI-modell-distribusjon
|
||||
|
||||
Bruk Flux v2 for a distribuere AI-modeller og konfigurasjoner:
|
||||
|
||||
```bash
|
||||
# Konfigurer GitOps med Flux v2 for modell-deployment
|
||||
az k8s-configuration flux create \
|
||||
--name ai-model-config \
|
||||
--cluster-name my-ai-cluster \
|
||||
--resource-group ai-rg \
|
||||
--cluster-type connectedClusters \
|
||||
--namespace ai-models \
|
||||
--scope namespace \
|
||||
--url https://github.com/org/ai-model-configs \
|
||||
--branch main \
|
||||
--kustomization name=models path=./models prune=true
|
||||
```
|
||||
|
||||
### Overvaking med Azure Monitor
|
||||
|
||||
```bash
|
||||
# Aktiver Container Insights pa Arc-klynge
|
||||
az k8s-extension create \
|
||||
--name azuremonitor-containers \
|
||||
--cluster-name my-ai-cluster \
|
||||
--resource-group ai-rg \
|
||||
--cluster-type connectedClusters \
|
||||
--extension-type Microsoft.AzureMonitor.Containers
|
||||
```
|
||||
|
||||
**Viktige metrikker a overvake for AI-klynger:**
|
||||
|
||||
| Metrikk | Beskrivelse | Terskel |
|
||||
|---------|-------------|---------|
|
||||
| GPU-utnyttelse | Prosent GPU-bruk per node | >80% = skaler opp |
|
||||
| GPU-minne | VRAM-forbruk | >90% = advarsel |
|
||||
| Inferens-latens | P95 responstid | <500ms for real-time |
|
||||
| Modell-versjon | Aktiv modellversjon | Match med registeret |
|
||||
| Pod-restarts | Antall omstarter | >3 = undersok |
|
||||
|
||||
---
|
||||
|
||||
## Sikkerhetsarkitektur for Arc AI
|
||||
|
||||
### Defense in Depth
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────┐
|
||||
│ 1. Azure RBAC │
|
||||
│ ┌──────────────────────────────────┐ │
|
||||
│ │ 2. Kubernetes RBAC │ │
|
||||
│ │ ┌────────────────────────────┐ │ │
|
||||
│ │ │ 3. Network Policy │ │ │
|
||||
│ │ │ ┌──────────────────────┐ │ │ │
|
||||
│ │ │ │ 4. Pod Security │ │ │ │
|
||||
│ │ │ │ ┌────────────────┐ │ │ │ │
|
||||
│ │ │ │ │ 5. Container │ │ │ │ │
|
||||
│ │ │ │ │ Security │ │ │ │ │
|
||||
│ │ │ │ └────────────────┘ │ │ │ │
|
||||
│ │ │ └──────────────────────┘ │ │ │
|
||||
│ │ └────────────────────────────┘ │ │
|
||||
│ └──────────────────────────────────┘ │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Microsoft Defender for Kubernetes
|
||||
|
||||
Defender gir trusselbeskyttelse for alle Arc-enabled klynger:
|
||||
|
||||
- Runtime-trusselbeskyttelse
|
||||
- Sarbarhetsskanning av container images
|
||||
- Sikkerhetskonfigurasjonskontroller
|
||||
- Integrasjon med Microsoft Sentinel for SIEM
|
||||
|
||||
### Hemmelighetshaandtering
|
||||
|
||||
```bash
|
||||
# Installer Azure Key Vault Secrets Provider
|
||||
az k8s-extension create \
|
||||
--cluster-type connectedClusters \
|
||||
--cluster-name my-ai-cluster \
|
||||
--resource-group ai-rg \
|
||||
--extension-type Microsoft.AzureKeyVaultSecretsProvider \
|
||||
--name akvsecretsprovider
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relevans for norsk offentlig sektor
|
||||
|
||||
### Datasuverenitetshensyn
|
||||
|
||||
| Krav | Arc-losning |
|
||||
|------|-------------|
|
||||
| Data ma forbli i Norge | On-prem klynge med Arc management |
|
||||
| Sentralisert policy | Azure Policy handheves fra Norway East |
|
||||
| Auditlog | Azure Monitor med lokal lagring |
|
||||
| Kryptering | Key Vault med CMK i Norway East |
|
||||
| Tilgangskontroll | Azure RBAC + Kubernetes RBAC |
|
||||
|
||||
### Anbefalte Azure-regioner
|
||||
|
||||
| Region | Bruk | Data residency |
|
||||
|--------|------|----------------|
|
||||
| Norway East | Primaer kontrollflate | Norge |
|
||||
| Norway West | DR/backup | Norge |
|
||||
| West Europe | Fallback, utvidede tjenester | EU/EFTA |
|
||||
|
||||
### NSM-krav og Arc
|
||||
|
||||
Nasjonal sikkerhetsmyndighet (NSM) sine grunnprinsipper for IKT-sikkerhet kan mappes mot Arc-kapabiliteter:
|
||||
|
||||
| NSM-prinsipp | Arc-kontroll |
|
||||
|--------------|--------------|
|
||||
| Kartlegg enheter og programvare | Arc inventory og tagging |
|
||||
| Ha kontroll pa nettverk og systemer | Azure Policy, Network Policy |
|
||||
| Beskytt data | Kryptering, Key Vault |
|
||||
| Overlapp/overvak | Azure Monitor, Defender |
|
||||
| Styring og kontroll | RBAC, governance hierarki |
|
||||
|
||||
---
|
||||
|
||||
## Begrensninger og hensyn
|
||||
|
||||
| Begrensning | Beskrivelse | Workaround |
|
||||
|-------------|-------------|------------|
|
||||
| Outbound connectivity | Arc krever utgaende HTTPS | Proxy-stotte tilgjengelig |
|
||||
| Extension-kompatibilitet | Ikke alle extensions stotter alle distribusjoner | Sjekk kompatibilitetsmatrise |
|
||||
| GPU-stotte | KAITO pa Arc kun for Azure Local (preview) | Bruk Azure ML extension for andre |
|
||||
| Skalering | Ingen auto-scaling for Kubernetes compute i ML | Manuell skalering |
|
||||
| Modellkatalog | Model Catalog ikke stottet pa Kubernetes endpoints | Bruk custom modeller |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Azure Arc er brokken mellom lokale AI-klynger og Azures skybaserte administrasjon** — alle Kubernetes-klynger blir forsteklasses Azure-ressurser med policy, overvaking og ML-integrasjon.
|
||||
- **KAITO (Kubernetes AI Toolchain Operator) forenkler LLM-deployment** pa Arc-enabled klynger, spesielt pa Azure Local med GPU-stotte for Phi-4, Mistral og Qwen-modeller.
|
||||
- **Azure Policy for Kubernetes handhever governance pa tvers av alle klynger** — fra on-prem til multicloud — med innebygde og tilpassede policyer for AI-arbeidsbelastninger.
|
||||
- **For norsk offentlig sektor er Arc losningen for "data forblir lokalt, styring fra skyen"** — kontrollflaten i Norway East, data pa egne servere, med full auditlog og kryptering.
|
||||
- **Multi-cluster governance med Fleet Manager og GitOps** gir skalerbar, deklarativ modell- og konfigurasjonsstyring for distribuerte AI-miljoer.
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
# Azure Confidential Computing for AI
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Azure Confidential Computing (ACC) beskytter data under prosessering (data-in-use) ved hjelp av hardware-baserte Trusted Execution Environments (TEE). For AI-arbeidsbelastninger betyr dette at modeller og inferensdata kan beskyttes mot uautorisert tilgang — inkludert fra skyoperatoren selv. Dette er en gamechanger for organisasjoner som prosesserer sensitive data med AI.
|
||||
|
||||
For norsk offentlig sektor losner ACC en fundamental utfordring: hvordan bruke sky-basert AI-kraftig hardware (GPU-er, akseleratorer) for sensitive data uten a kompromittere datasikkerheten. NSM Grunnprinsipper og Schrems II-krav kan ivaretas ved at data aldri eksisteres i klartekst utenfor TEE — selv Microsoft som skyoperator kan ikke se dataene.
|
||||
|
||||
Microsoft tilbyr flere ACC-tilbud for AI: Confidential VMs basert pa AMD SEV-SNP for CPU-baserte arbeidsbelastninger, Confidential GPU VMs med NVIDIA H100 for GPU-akselerert AI, Confidential Containers pa ACI og AKS, og Azure Attestation for verifisering av TEE-integritet.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| AMD SEV-SNP | Minnesikring for hele VM | CPU-basert TEE |
|
||||
| Intel TDX | Trust Domain Extensions for VM-isolasjon | CPU-basert TEE (Preview) |
|
||||
| Intel SGX | Application-level enclaves | Enclave-basert TEE |
|
||||
| NVIDIA H100 TEE | GPU-basert confidential computing | Confidential GPU VM |
|
||||
| Azure Attestation | Verifisering av TEE-tilstand | PaaS-tjeneste |
|
||||
| Confidential VMs | Kryptert VM-minne | DCasv5, ECasv5, NCCadsH100v5 |
|
||||
| Confidential Containers | Container-isolasjon i TEE | ACI, AKS |
|
||||
| Azure Key Vault mHSM | Nokkelhandtering i HSM | FIPS 140-2 Level 3 |
|
||||
|
||||
---
|
||||
|
||||
## TEE-Enabled Model Execution
|
||||
|
||||
### Confidential VM for AI-inferens
|
||||
|
||||
```bash
|
||||
# Opprett Confidential VM for AI-arbeidslast (AMD SEV-SNP)
|
||||
az vm create \
|
||||
--resource-group rg-confidential-ai \
|
||||
--name vm-confidential-inference \
|
||||
--image "Canonical:0001-com-ubuntu-confidential-vm-jammy:22_04-lts-cvm:latest" \
|
||||
--size Standard_DC4as_v5 \
|
||||
--security-type ConfidentialVM \
|
||||
--os-disk-security-encryption-type VMGuestStateOnly \
|
||||
--enable-vtpm true \
|
||||
--enable-secure-boot true \
|
||||
--admin-username azureuser \
|
||||
--generate-ssh-keys
|
||||
|
||||
# Installer AI runtime
|
||||
az vm run-command invoke \
|
||||
--resource-group rg-confidential-ai \
|
||||
--name vm-confidential-inference \
|
||||
--command-id RunShellScript \
|
||||
--scripts "
|
||||
pip install onnxruntime torch transformers
|
||||
# Modell og data er kryptert i minnet av AMD SEV-SNP
|
||||
"
|
||||
```
|
||||
|
||||
### Confidential GPU VM for AI (NVIDIA H100)
|
||||
|
||||
```bash
|
||||
# Opprett Confidential GPU VM med NVIDIA H100 TEE
|
||||
az vm create \
|
||||
--resource-group rg-confidential-ai \
|
||||
--name vm-confidential-gpu \
|
||||
--image "microsoft-dsvm:ubuntu-hpc:2204:latest" \
|
||||
--size Standard_NCCads_H100_v5 \
|
||||
--security-type ConfidentialVM \
|
||||
--os-disk-security-encryption-type DiskWithVMGuestState \
|
||||
--enable-vtpm true \
|
||||
--admin-username azureuser \
|
||||
--generate-ssh-keys
|
||||
```
|
||||
|
||||
### Linked CPU-GPU TEE-arkitektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Confidential GPU VM │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌────────────────┐ │
|
||||
│ │ CPU TEE │ │ GPU TEE │ │
|
||||
│ │ (AMD SNP) │←→│ (NVIDIA H100) │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ - Datainntak │ │ - Inferens │ │
|
||||
│ │ - Pre/post │ │ - Training │ │
|
||||
│ │ - Orkestrering│ │ - Tensor ops │ │
|
||||
│ └──────────────┘ └────────────────┘ │
|
||||
│ ↑ ↑ │
|
||||
│ Kryptert minne Kryptert VRAM │
|
||||
│ (aldri i klartekst utenfor TEE) │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Encrypted Inference Pipelines
|
||||
|
||||
### End-to-end kryptert inferens
|
||||
|
||||
```python
|
||||
# Confidential inferens med attestation-basert nokkelfrigivelse
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from azure.keyvault.keys import KeyClient
|
||||
from azure.attestation import AttestationClient
|
||||
import onnxruntime as ort
|
||||
|
||||
class ConfidentialInferencePipeline:
|
||||
def __init__(self):
|
||||
self.credential = DefaultAzureCredential()
|
||||
self.attestation_client = AttestationClient(
|
||||
endpoint="https://sharedeus.eus.attest.azure.net",
|
||||
credential=self.credential
|
||||
)
|
||||
|
||||
async def run_confidential_inference(self, encrypted_input: bytes) -> bytes:
|
||||
"""Kjor inferens med end-to-end kryptering"""
|
||||
|
||||
# Steg 1: Generer TEE-attestasjonsrapport
|
||||
attestation_report = self._generate_attestation()
|
||||
|
||||
# Steg 2: Hent dekrypteringsnokkel via Secure Key Release (SKR)
|
||||
decryption_key = await self._secure_key_release(attestation_report)
|
||||
|
||||
# Steg 3: Dekrypter input innenfor TEE
|
||||
# (Data er kun i klartekst innenfor TEE-minnet)
|
||||
plaintext_input = self._decrypt_in_tee(encrypted_input, decryption_key)
|
||||
|
||||
# Steg 4: Kjor inferens
|
||||
result = self._run_model(plaintext_input)
|
||||
|
||||
# Steg 5: Krypter output for returnerning
|
||||
encrypted_output = self._encrypt_in_tee(result, decryption_key)
|
||||
|
||||
return encrypted_output
|
||||
|
||||
def _generate_attestation(self) -> dict:
|
||||
"""Generer hardware-attestasjonsrapport fra AMD SEV-SNP"""
|
||||
# Hent SNP attestation report fra /dev/sev-guest
|
||||
# Rapporten inkluderer:
|
||||
# - Platform-versjon og firmware
|
||||
# - VM measurement (hash av VM-konfigurasjon)
|
||||
# - Runtime measurement
|
||||
import subprocess
|
||||
report = subprocess.run(
|
||||
["snp-report", "--format", "json"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return {
|
||||
"snp_report": report.stdout,
|
||||
"runtime_data": self._get_runtime_claims()
|
||||
}
|
||||
|
||||
async def _secure_key_release(self, attestation: dict) -> bytes:
|
||||
"""Frigivelse av nokkel basert pa attestasjon"""
|
||||
# Azure Attestation verifiserer TEE-tilstand
|
||||
result = self.attestation_client.attest_snp_vm(
|
||||
report=attestation["snp_report"],
|
||||
runtime_data=attestation["runtime_data"]
|
||||
)
|
||||
|
||||
# Kun hvis attestasjon er gyldig, frigir Key Vault nokkelen
|
||||
key_client = KeyClient(
|
||||
vault_url="https://myvault.vault.azure.net",
|
||||
credential=self.credential
|
||||
)
|
||||
|
||||
return key_client.release_key(
|
||||
name="inference-key",
|
||||
target_attestation_token=result.token
|
||||
)
|
||||
```
|
||||
|
||||
### Confidential Containers for AI
|
||||
|
||||
```yaml
|
||||
# Confidential container deployment pa Azure Container Instances
|
||||
# Container-gruppen kjorer i AMD SEV-SNP TEE
|
||||
apiVersion: 2023-05-01
|
||||
name: confidential-inference
|
||||
location: norwayeast
|
||||
properties:
|
||||
confidentialComputeProperties:
|
||||
ccePolicy: "<base64-encoded-rego-policy>"
|
||||
containers:
|
||||
- name: inference-engine
|
||||
properties:
|
||||
image: myregistry.azurecr.io/confidential-inference:v1
|
||||
resources:
|
||||
requests:
|
||||
cpu: 4
|
||||
memoryInGB: 16
|
||||
environmentVariables:
|
||||
- name: MODEL_PATH
|
||||
value: /models/encrypted_model.enc
|
||||
- name: ATTESTATION_ENDPOINT
|
||||
value: "https://sharedneu.neu.attest.azure.net"
|
||||
volumeMounts:
|
||||
- name: model-volume
|
||||
mountPath: /models
|
||||
osType: Linux
|
||||
sku: Confidential
|
||||
volumes:
|
||||
- name: model-volume
|
||||
azureFile:
|
||||
shareName: encrypted-models
|
||||
storageAccountName: mystorageaccount
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Attestation for Compliance
|
||||
|
||||
### Azure Attestation-flyten
|
||||
|
||||
```
|
||||
┌──────────┐ ┌───────────────┐ ┌──────────────┐
|
||||
│ TEE/CVM │────→│ Azure │────→│ Relying │
|
||||
│ │ │ Attestation │ │ Party │
|
||||
│ Generer │ │ │ │ │
|
||||
│ Evidence │ │ Verifiser │ │ Frigir data/ │
|
||||
│ │ │ Evaluer policy│ │ noekler │
|
||||
└──────────┘ └───────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
### Attestasjonspolicy for AI-arbeidsbelastninger
|
||||
|
||||
```json
|
||||
// SKR-policy for Confidential AI VM
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"anyOf": [
|
||||
{
|
||||
"authority": "https://sharedneu.neu.attest.azure.net",
|
||||
"allOf": [
|
||||
{
|
||||
"claim": "x-ms-compliance-status",
|
||||
"equals": "azure-compliant-cvm"
|
||||
},
|
||||
{
|
||||
"claim": "x-ms-sevsnpvm-is-debuggable",
|
||||
"equals": "false"
|
||||
},
|
||||
{
|
||||
"claim": "x-ms-sevsnpvm-vmpl",
|
||||
"equals": "0"
|
||||
},
|
||||
{
|
||||
"claim": "x-ms-isolation-tee.x-ms-attestation-type",
|
||||
"equals": "sevsnpvm"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Compliance-rapportering
|
||||
|
||||
```python
|
||||
# Generer compliance-rapport for confidential AI
|
||||
class ConfidentialAIComplianceReport:
|
||||
def generate_report(self) -> dict:
|
||||
return {
|
||||
"platform": {
|
||||
"type": "Azure Confidential VM",
|
||||
"tee": "AMD SEV-SNP",
|
||||
"firmware_version": self._get_firmware_version(),
|
||||
"attestation_status": "verified"
|
||||
},
|
||||
"data_protection": {
|
||||
"encryption_at_rest": "AES-256 (Customer-managed key)",
|
||||
"encryption_in_transit": "TLS 1.3",
|
||||
"encryption_in_use": "AMD SEV-SNP memory encryption",
|
||||
"key_management": "Azure Key Vault Managed HSM"
|
||||
},
|
||||
"access_control": {
|
||||
"operator_access": "Denied (TEE-enforced)",
|
||||
"attestation_required": True,
|
||||
"secure_key_release": True
|
||||
},
|
||||
"compliance_frameworks": [
|
||||
"GDPR Art. 32 (data-in-use protection)",
|
||||
"Schrems II (operator cannot access data)",
|
||||
"NSM Grunnprinsipper (kryptering ved bruk)",
|
||||
"ISO 27001 A.10 (cryptographic controls)"
|
||||
],
|
||||
"audit_trail": {
|
||||
"attestation_logs": "Azure Monitor",
|
||||
"key_release_logs": "Key Vault audit log",
|
||||
"inference_metadata": "Application Insights"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Trade-offs
|
||||
|
||||
### Ytelsespavirkning av Confidential Computing
|
||||
|
||||
| Arbeidsbelastning | Uten CC | Med CC (CPU TEE) | Med CC (GPU TEE) | Overhead |
|
||||
|-------------------|---------|-------------------|-------------------|----------|
|
||||
| ONNX inferens (CPU) | 10 ms | 11-12 ms | N/A | 10-20% |
|
||||
| PyTorch inferens (GPU) | 5 ms | N/A | 5.5-6 ms | 10-20% |
|
||||
| LLM generering (GPU) | 30 tok/s | N/A | 25-28 tok/s | 7-17% |
|
||||
| Embedding-generering | 50 ms/batch | 55-60 ms/batch | 52-55 ms/batch | 4-20% |
|
||||
| Modell-lasting | 5 s | 7-8 s | 6-7 s | 20-40% |
|
||||
|
||||
### Optimalisering for lavere overhead
|
||||
|
||||
| Optimalisering | Beskrivelse | Forventet forbedring |
|
||||
|----------------|-------------|---------------------|
|
||||
| Batching | Samle flere inferensforesp. | Amortiser TEE-overhead |
|
||||
| Model caching | Hold modell i TEE-minne | Unnga re-dekryptering |
|
||||
| Forhands-attestasjon | Cache attestasjonstoken | Reduser latens per kall |
|
||||
| NUMA-optimalisering | Pin til korrekt NUMA-node | Bedre minneytelse |
|
||||
| Hugepage-allokering | Bruk store minnesider | Reduser TLB-misser |
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Hvorfor Confidential Computing for norsk offentlig AI?
|
||||
|
||||
- **Schrems II-kompatibilitet**: Data er kryptert under prosessering — selv Microsoft kan ikke se innholdet, noe som adresserer europeisk personvernlovgivning
|
||||
- **NSM-krav**: Grunnprinsipper for IKT-sikkerhet krever kryptering, og CC utvider dette til data-in-use
|
||||
- **Flerpartisanalyse**: Kommuner og etater kan analysere data sammen uten a eksponere radata for hverandre
|
||||
- **AI pa sensitive data**: Helsedata (pasientjournaler), persondata (NAV), og justisdata kan prosesseres med AI uten a kompromittere personvern
|
||||
|
||||
### Bruksscenarier
|
||||
|
||||
| Etat | Scenario | CC-komponent |
|
||||
|------|----------|--------------|
|
||||
| NAV | AI-analyse av soknader med persondata | Confidential VM |
|
||||
| Helsedirektoratet | Prediktiv analyse pa helsejournaler | Confidential GPU VM |
|
||||
| Politiet | Biometrisk matching | SGX enclaves |
|
||||
| Skatteetaten | Fraud detection pa skattedata | Confidential Containers |
|
||||
| Kommuner | Flerparts-analyse av velferdsdata | Confidential ACI |
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| CPU-basert AI, enkel migrasjon | Confidential VM (SEV-SNP) | Ingen kodeendringer |
|
||||
| GPU-akselerert AI, hoy ytelse | NCCadsH100v5 Confidential GPU VM | Linked CPU+GPU TEE |
|
||||
| Container-baserte mikrotjenester | Confidential Containers pa ACI/AKS | Container-policy-basert sikkerhet |
|
||||
| Application-level isolasjon | Intel SGX enclaves | Minst mulig TCB |
|
||||
| Flerpartssanalyse | Confidential Containers + Attestation | Verifiserbar isolasjon |
|
||||
| Strengeste compliance-krav | Confidential GPU + mHSM + Attestation | Full stack confidentiality |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Confidential Computing losner data-in-use-problemet for sky-AI** — anbefal Confidential VMs (AMD SEV-SNP) som forste steg for organisasjoner som noler med sky-AI pa grunn av datasikkerhetshensyn
|
||||
- **Ytelsesoverhead er typisk 10-20%** — dette er akseptabelt for de fleste arbeidsbelastninger og kan optimaliseres med batching og modell-caching innenfor TEE
|
||||
- **Azure Attestation + Secure Key Release er pabudt for compliance** — modeller og data bor kun dekrypteres etter vellykket attestasjon som beviser at arbeidsmiljoet er integert
|
||||
- **For norsk offentlig sektor: Confidential GPU VMs (H100) er den mest lovende losningen** for a kjore avansert AI pa sensitive data i skyen — den kombinerer GPU-ytelse med TEE-beskyttelse
|
||||
- **Dokumenter alltid TEE-stack, attestasjonspolicy og nokkelhandtering** i sikkerhetsarkitekturen — dette er konkret bevis for compliance i DPIA og sikkerhetsrevisjoner
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
# Azure IoT Hub and AI Pipeline
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Azure IoT Hub er Microsofts sentrale PaaS-tjeneste for toveiskommunikasjon mellom IoT-enheter og skyen. Kombinert med Azure Stream Analytics for sanntidsanalyse og Azure Machine Learning for modelltrening og -scoring, danner IoT Hub kjernen i en enhetlig AI-pipeline fra enhet til innsikt.
|
||||
|
||||
For norsk offentlig sektor er denne arkitekturen relevant for scenarioer som smart veginfrastruktur (sanntidsmaling av trafikk og veiforhold), bygg-automatisering (energistyring i offentlige bygninger), miljooverkaking (luft- og vannkvalitet), og prediktiv vedlikehold av kritisk infrastruktur. IoT Hub gir sikker enhetstilkobling, mens Stream Analytics prosesserer data i sanntid, og Azure ML scorer modeller for prediktive innsikter.
|
||||
|
||||
Arkitekturen skalerer fra hundrevis til millioner av enheter, med innebygd stoette for meldingsruting, device twins for konfigurasjonstyring, og enkel integrasjon med Azure-dataplatformen (Fabric, Event Hub, Cosmos DB) for langsiktig analyse.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Azure IoT Hub | Sentral enhetskommunikasjon og -styring | PaaS |
|
||||
| Azure Stream Analytics | Sanntids stromprosessering | SQL-basert |
|
||||
| Azure Machine Learning | Modelltrening og online scoring | ML Platform |
|
||||
| Event Hub | Hoyvolum meldingsinntak | Event streaming |
|
||||
| Azure Cosmos DB | Sanntids operasjonell database | NoSQL |
|
||||
| Azure Data Lake / Fabric | Langsiktig dataanalyse | Analytics |
|
||||
| Power BI | Sanntids dashboards | Visualisering |
|
||||
| IoT Edge | Lokal prosessering pa enheter | Container runtime |
|
||||
|
||||
---
|
||||
|
||||
## Device-to-Hub Data Flow
|
||||
|
||||
### Arkitektur for enhet-til-sky-dataflyt
|
||||
|
||||
```
|
||||
┌──────────┐ ┌──────────┐ ┌──────────────┐
|
||||
│ Sensorer │────→│ IoT Edge │────→│ IoT Hub │
|
||||
│ (MQTT) │ │ Gateway │ │ │
|
||||
└──────────┘ └──────────┘ │ - Routing │
|
||||
│ - Enrichment│
|
||||
┌──────────┐ │ - Twin mgmt │
|
||||
│ Direkte │─────────────────────→│ │
|
||||
│ enheter │ └──────┬───────┘
|
||||
│ (AMQP) │ │
|
||||
└──────────┘ ┌─────────┼─────────┐
|
||||
↓ ↓ ↓
|
||||
┌──────────┐ ┌────────┐ ┌────────┐
|
||||
│ Stream │ │ Event │ │ Cosmos │
|
||||
│ Analytics│ │ Hub │ │ DB │
|
||||
└──────────┘ └────────┘ └────────┘
|
||||
↓ ↓ ↓
|
||||
┌──────────┐ ┌────────┐ ┌────────┐
|
||||
│ Azure ML │ │ Fabric │ │ Power │
|
||||
│ Scoring │ │ │ │ BI │
|
||||
└──────────┘ └────────┘ └────────┘
|
||||
```
|
||||
|
||||
### IoT Hub meldingsruting
|
||||
|
||||
```json
|
||||
// IoT Hub meldingsruting for AI pipeline
|
||||
{
|
||||
"routes": [
|
||||
{
|
||||
"name": "realtime-to-stream-analytics",
|
||||
"source": "DeviceMessages",
|
||||
"condition": "temperature > 0 OR vibration > 0",
|
||||
"endpointNames": ["stream-analytics-endpoint"],
|
||||
"isEnabled": true
|
||||
},
|
||||
{
|
||||
"name": "anomalies-to-event-hub",
|
||||
"source": "DeviceMessages",
|
||||
"condition": "$body.alert = 'anomaly'",
|
||||
"endpointNames": ["anomaly-event-hub"],
|
||||
"isEnabled": true
|
||||
},
|
||||
{
|
||||
"name": "all-data-to-storage",
|
||||
"source": "DeviceMessages",
|
||||
"condition": "true",
|
||||
"endpointNames": ["datalake-storage"],
|
||||
"isEnabled": true
|
||||
},
|
||||
{
|
||||
"name": "device-lifecycle-to-cosmos",
|
||||
"source": "DeviceLifecycleEvents",
|
||||
"condition": "true",
|
||||
"endpointNames": ["cosmos-db-endpoint"],
|
||||
"isEnabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Enhetstilkobling med Python SDK
|
||||
|
||||
```python
|
||||
# IoT-enhet sender sensordata til IoT Hub
|
||||
from azure.iot.device import IoTHubDeviceClient, Message
|
||||
import json
|
||||
import time
|
||||
|
||||
class SensorDevice:
|
||||
def __init__(self, connection_string: str):
|
||||
self.client = IoTHubDeviceClient.create_from_connection_string(
|
||||
connection_string
|
||||
)
|
||||
self.client.connect()
|
||||
|
||||
def send_telemetry(self, sensor_data: dict):
|
||||
"""Send sensordata med metadata for ruting"""
|
||||
message = Message(
|
||||
json.dumps(sensor_data),
|
||||
content_encoding="utf-8",
|
||||
content_type="application/json"
|
||||
)
|
||||
|
||||
# Egendefinerte properties for meldingsruting
|
||||
message.custom_properties["sensorType"] = sensor_data.get("type", "unknown")
|
||||
message.custom_properties["location"] = sensor_data.get("location", "unknown")
|
||||
|
||||
# Sett prioritet for anomalier
|
||||
if sensor_data.get("alert"):
|
||||
message.custom_properties["priority"] = "high"
|
||||
|
||||
self.client.send_message(message)
|
||||
|
||||
def start_continuous_telemetry(self, interval_seconds: int = 10):
|
||||
"""Kontinuerlig sending av sensordata"""
|
||||
while True:
|
||||
data = self.read_sensors()
|
||||
self.send_telemetry(data)
|
||||
time.sleep(interval_seconds)
|
||||
|
||||
def read_sensors(self) -> dict:
|
||||
"""Les sensorverdier (simulert)"""
|
||||
import random
|
||||
return {
|
||||
"timestamp": time.time(),
|
||||
"temperature": random.uniform(18.0, 25.0),
|
||||
"humidity": random.uniform(30.0, 70.0),
|
||||
"vibration": random.uniform(0.0, 5.0),
|
||||
"type": "environment",
|
||||
"location": "building-A-floor-2"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stream Processing for AI
|
||||
|
||||
### Azure Stream Analytics for IoT AI
|
||||
|
||||
```sql
|
||||
-- Sanntids anomalideteksjon med Stream Analytics
|
||||
-- Kombinerer sensordata med ML-scoring
|
||||
|
||||
-- Query 1: Glidende statistikk per enhet
|
||||
WITH DeviceStats AS (
|
||||
SELECT
|
||||
IoTHub.ConnectionDeviceId AS DeviceId,
|
||||
System.Timestamp() AS WindowEnd,
|
||||
AVG(temperature) AS AvgTemp,
|
||||
STDEV(temperature) AS StdTemp,
|
||||
MIN(temperature) AS MinTemp,
|
||||
MAX(temperature) AS MaxTemp,
|
||||
COUNT(*) AS ReadingCount
|
||||
FROM
|
||||
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
|
||||
GROUP BY
|
||||
IoTHub.ConnectionDeviceId,
|
||||
SlidingWindow(minute, 10)
|
||||
)
|
||||
|
||||
-- Query 2: Anomalideteksjon med statistisk terskel
|
||||
SELECT
|
||||
ds.DeviceId,
|
||||
ds.WindowEnd,
|
||||
ds.AvgTemp,
|
||||
ds.StdTemp,
|
||||
CASE
|
||||
WHEN ds.AvgTemp > (ref.NormalAvg + 3 * ref.NormalStd) THEN 'HIGH_ANOMALY'
|
||||
WHEN ds.AvgTemp > (ref.NormalAvg + 2 * ref.NormalStd) THEN 'WARNING'
|
||||
WHEN ds.AvgTemp < (ref.NormalAvg - 3 * ref.NormalStd) THEN 'LOW_ANOMALY'
|
||||
ELSE 'NORMAL'
|
||||
END AS Status,
|
||||
ref.DeviceName,
|
||||
ref.Location
|
||||
INTO
|
||||
AnomalyOutput
|
||||
FROM
|
||||
DeviceStats ds
|
||||
JOIN ReferenceData ref ON ds.DeviceId = ref.DeviceId
|
||||
WHERE
|
||||
ds.ReadingCount >= 5 -- Minst 5 malinger for palitelighet
|
||||
|
||||
-- Query 3: Dataaggregering for ML-trening
|
||||
SELECT
|
||||
IoTHub.ConnectionDeviceId AS DeviceId,
|
||||
System.Timestamp() AS WindowEnd,
|
||||
AVG(temperature) AS AvgTemp,
|
||||
AVG(humidity) AS AvgHumidity,
|
||||
AVG(vibration) AS AvgVibration,
|
||||
STDEV(vibration) AS StdVibration,
|
||||
MAX(vibration) AS PeakVibration,
|
||||
COUNT(*) AS SampleCount
|
||||
INTO
|
||||
MLTrainingOutput
|
||||
FROM
|
||||
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
|
||||
GROUP BY
|
||||
IoTHub.ConnectionDeviceId,
|
||||
TumblingWindow(hour, 1)
|
||||
```
|
||||
|
||||
### Stream Analytics med innebygd anomalideteksjon
|
||||
|
||||
```sql
|
||||
-- Bruk innebygd AnomalyDetection-funksjon
|
||||
SELECT
|
||||
IoTHub.ConnectionDeviceId AS DeviceId,
|
||||
temperature,
|
||||
AnomalyDetection_SpikeAndDip(
|
||||
temperature,
|
||||
95, -- Konfidensniaa
|
||||
120, -- Vindusstoorrelse
|
||||
'spikesanddips'
|
||||
) OVER (
|
||||
PARTITION BY IoTHub.ConnectionDeviceId
|
||||
LIMIT DURATION(minute, 120)
|
||||
) AS AnomalyResult
|
||||
INTO
|
||||
AnomalyAlertOutput
|
||||
FROM
|
||||
IoTHubInput TIMESTAMP BY EventProcessedUtcTime
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Real-Time Model Scoring
|
||||
|
||||
### Azure ML Online Endpoint for IoT-scoring
|
||||
|
||||
```python
|
||||
# Azure ML endpoint for sanntids IoT-scoring
|
||||
from azure.ai.ml import MLClient
|
||||
from azure.ai.ml.entities import (
|
||||
ManagedOnlineEndpoint,
|
||||
ManagedOnlineDeployment,
|
||||
Model
|
||||
)
|
||||
from azure.identity import DefaultAzureCredential
|
||||
|
||||
def deploy_iot_scoring_endpoint(ml_client: MLClient):
|
||||
"""Deploy sanntids scoring-endpoint for IoT-data"""
|
||||
|
||||
# Opprett endpoint
|
||||
endpoint = ManagedOnlineEndpoint(
|
||||
name="iot-anomaly-scoring",
|
||||
auth_mode="key",
|
||||
description="Anomalideteksjon for IoT-sensordata"
|
||||
)
|
||||
ml_client.online_endpoints.begin_create_or_update(endpoint).result()
|
||||
|
||||
# Deploy modell
|
||||
deployment = ManagedOnlineDeployment(
|
||||
name="anomaly-v1",
|
||||
endpoint_name="iot-anomaly-scoring",
|
||||
model=Model(path="./models/anomaly_model.pkl"),
|
||||
code_configuration={
|
||||
"code": "./scoring",
|
||||
"scoring_script": "score.py"
|
||||
},
|
||||
instance_type="Standard_DS3_v2",
|
||||
instance_count=2, # Redundans for palitelighet
|
||||
environment="azureml:sklearn-1.0:1"
|
||||
)
|
||||
ml_client.online_deployments.begin_create_or_update(deployment).result()
|
||||
```
|
||||
|
||||
### Scoring-script for IoT-data
|
||||
|
||||
```python
|
||||
# score.py — Azure ML scoring-script for IoT
|
||||
import json
|
||||
import joblib
|
||||
import numpy as np
|
||||
|
||||
def init():
|
||||
global model
|
||||
model = joblib.load("model/anomaly_model.pkl")
|
||||
|
||||
def run(raw_data):
|
||||
"""Score IoT-sensordata mot prediktiv modell"""
|
||||
data = json.loads(raw_data)
|
||||
|
||||
features = np.array([[
|
||||
data["avg_temperature"],
|
||||
data["avg_humidity"],
|
||||
data["avg_vibration"],
|
||||
data["std_vibration"],
|
||||
data["peak_vibration"],
|
||||
data["sample_count"]
|
||||
]])
|
||||
|
||||
prediction = model.predict(features)[0]
|
||||
probability = model.predict_proba(features)[0]
|
||||
|
||||
return json.dumps({
|
||||
"device_id": data["device_id"],
|
||||
"prediction": int(prediction),
|
||||
"failure_probability": float(max(probability)),
|
||||
"recommendation": (
|
||||
"SCHEDULE_MAINTENANCE" if prediction == 1
|
||||
else "NORMAL_OPERATION"
|
||||
),
|
||||
"scored_at": data.get("window_end")
|
||||
})
|
||||
```
|
||||
|
||||
### Stream Analytics integrert med Azure ML
|
||||
|
||||
```sql
|
||||
-- Kall Azure ML endpoint fra Stream Analytics
|
||||
WITH ScoringInput AS (
|
||||
SELECT
|
||||
IoTHub.ConnectionDeviceId AS device_id,
|
||||
System.Timestamp() AS window_end,
|
||||
AVG(temperature) AS avg_temperature,
|
||||
AVG(humidity) AS avg_humidity,
|
||||
AVG(vibration) AS avg_vibration,
|
||||
STDEV(vibration) AS std_vibration,
|
||||
MAX(vibration) AS peak_vibration,
|
||||
COUNT(*) AS sample_count
|
||||
FROM IoTHubInput
|
||||
TIMESTAMP BY EventProcessedUtcTime
|
||||
GROUP BY
|
||||
IoTHub.ConnectionDeviceId,
|
||||
TumblingWindow(minute, 15)
|
||||
)
|
||||
SELECT
|
||||
si.*,
|
||||
ml.prediction,
|
||||
ml.failure_probability,
|
||||
ml.recommendation
|
||||
INTO MaintenanceOutput
|
||||
FROM ScoringInput si
|
||||
CROSS APPLY AzureMLEndpoint(si) AS ml
|
||||
WHERE ml.failure_probability > 0.5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scaling Hybrid Ingestion
|
||||
|
||||
### Skaleringsarkitektur
|
||||
|
||||
| Skala | Enheter | IoT Hub SKU | Stream Analytics SU | Anbefaling |
|
||||
|-------|---------|-------------|--------------------|----|
|
||||
| Liten | < 1 000 | S1 (1 enhet) | 6 SU | Standard oppsett |
|
||||
| Medium | 1 000 - 100 000 | S2 (2 enheter) | 12-24 SU | Partisjonering |
|
||||
| Stor | 100 000 - 1M | S3 (10 enheter) | 48+ SU | Event Hub routing |
|
||||
| Enterprise | > 1M | S3 + Event Hub | Dedikert klynge | Multi-hub-arkitektur |
|
||||
|
||||
### Hybrid skalering med edge-forbehandling
|
||||
|
||||
```python
|
||||
# Hybrid skaleringsmonster: Edge reduserer skylast
|
||||
class HybridScalingConfig:
|
||||
"""Konfigurasjon for hybrid edge-sky skalering"""
|
||||
|
||||
@staticmethod
|
||||
def calculate_cloud_load(
|
||||
total_devices: int,
|
||||
messages_per_device_per_hour: int,
|
||||
edge_aggregation_ratio: float = 0.1 # 10% av data sendes til sky
|
||||
) -> dict:
|
||||
"""Beregn skylast med edge-forbehandling"""
|
||||
|
||||
raw_messages = total_devices * messages_per_device_per_hour
|
||||
cloud_messages = int(raw_messages * edge_aggregation_ratio)
|
||||
bandwidth_reduction = 1 - edge_aggregation_ratio
|
||||
|
||||
# IoT Hub dimensjonering
|
||||
messages_per_day = cloud_messages * 24
|
||||
if messages_per_day < 400_000:
|
||||
iot_hub_sku = "S1 (1 enhet)"
|
||||
elif messages_per_day < 6_000_000:
|
||||
iot_hub_sku = "S2 (1 enhet)"
|
||||
else:
|
||||
units = (messages_per_day // 6_000_000) + 1
|
||||
iot_hub_sku = f"S2 ({units} enheter)"
|
||||
|
||||
return {
|
||||
"total_devices": total_devices,
|
||||
"raw_messages_per_hour": raw_messages,
|
||||
"cloud_messages_per_hour": cloud_messages,
|
||||
"bandwidth_reduction": f"{bandwidth_reduction*100:.0f}%",
|
||||
"iot_hub_sku": iot_hub_sku,
|
||||
"estimated_monthly_cost_nok": cloud_messages * 24 * 30 * 0.001
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Relevante bruksomrader
|
||||
|
||||
| Sektor | Use Case | Enheter | AI-modell |
|
||||
|--------|----------|---------|-----------|
|
||||
| Samferdsel | Veisensor-nettverket | ~5 000 | Trafikk-prediksjon, vintervedlikehold |
|
||||
| Energi | Smart bygg-styring | ~10 000/bygg | Energi-optimalisering |
|
||||
| Miljoe | Luft/vann-kvalitet | ~500 stasjoner | Forurensnings-varsling |
|
||||
| Helse | Utstyrsovervaking | ~1 000/sykehus | Prediktiv vedlikehold |
|
||||
| Kyst | Maritime sensorer | ~2 000 | Vaer-prediksjon, sikkerhet |
|
||||
|
||||
### Sikkerhetskrav
|
||||
|
||||
- IoT Hub-endepunkt i Norway East
|
||||
- TLS 1.2+ for all enhetskommunikasjon
|
||||
- X.509-sertifikater for enhetsautentisering
|
||||
- DPS (Device Provisioning Service) for automatisk registrering
|
||||
- NSM-kompatibel nettverkssegmentering
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| < 1 000 enheter, enkel analyse | IoT Hub S1 + Stream Analytics | Lavest kostnad og kompleksitet |
|
||||
| Sanntids ML-scoring | Stream Analytics + Azure ML endpoint | Integrert ML-scoring i strom |
|
||||
| Hoeyvolum med edge-forbehandling | IoT Edge + IoT Hub S2/S3 | Redusert skylast og kostnad |
|
||||
| Langsiktig analyse | IoT Hub + Event Hub + Fabric | Skalerbar historisk analyse |
|
||||
| Prediktiv vedlikehold | Full pipeline med retraining loop | Kontinuerlig modellforbedring |
|
||||
| Anomalideteksjon | Stream Analytics innebygd anomali | Raskest a implementere |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **IoT Hub + Stream Analytics + Azure ML er den kanoniske AI-pipeline for IoT** — anbefal denne treledds-arkitekturen som standard for alle IoT-AI-scenarier i offentlig sektor
|
||||
- **Edge-forbehandling reduserer skylast med 90%+** — la IoT Edge aggregere og filtrere data for sensordata sendes til sky, noe som dramatisk reduserer baade kostnader og bandbreddekrav
|
||||
- **Stream Analytics innebygde anomalideteksjon er raskest a implementere** — bruk AnomalyDetection_SpikeAndDip-funksjonen for rask oppstart for du bygger egne ML-modeller
|
||||
- **Azure ML Online Endpoints gir sanntids scoring fra Stream Analytics** — bruk CROSS APPLY med AzureMLEndpoint-funksjonen for a integrere avansert ML direkte i strom-prosessering
|
||||
- **For norsk offentlig sektor: Dimensjoner IoT Hub-kapasitet basert pa cloud-meldinger etter edge-aggregering** — med 90% edge-reduksjon kan selv store sensornettverk klare seg med S1/S2-tieren
|
||||
|
|
@ -0,0 +1,399 @@
|
|||
# Azure Local for Edge AI Workloads
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Azure Local (tidligere Azure Stack HCI) er Microsofts hyperkonvergerte infrastrukturlosning for a kjore Azure-tjenester lokalt. For AI-arbeidsbelastninger tilbyr Azure Local GPU-akselerasjon, Kubernetes-stotte via AKS enabled by Arc, og lokal tilgang til Azure-tjenester — alt administrert fra Azure Portal.
|
||||
|
||||
For norsk offentlig sektor representerer Azure Local en unik mulighet: organisasjoner kan plassere AI-infrastruktur i egne datarom eller hos godkjente driftspartnere, samtidig som de far tilgang til Azures ML-plattform, overvaking og governance-verktoy. Dette gir data residency i Norge uten a ga pa kompromiss med moderne AI-kapabiliteter.
|
||||
|
||||
Azure Local stotter et bredt utvalg av NVIDIA GPU-er for AI-inferens og trening, inkludert T4, A2, A16, L4, L40 og L40S. Sammen med AKS enabled by Arc og KAITO (Kubernetes AI Toolchain Operator) kan organisasjoner kjore open-source LLM-er som Phi-4, Mistral og Llama direkte pa egen infrastruktur.
|
||||
|
||||
---
|
||||
|
||||
## Arkitekturoversikt
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ Azure Control Plane │
|
||||
│ Azure Portal │ ML Workspace │ Azure Monitor │
|
||||
└───────────────────────┬───────────────────────┘
|
||||
│ Azure Arc
|
||||
│
|
||||
┌───────────────────────▼───────────────────────┐
|
||||
│ Azure Local Cluster │
|
||||
│ ┌─────────────────────────────────────────┐ │
|
||||
│ │ AKS enabled by Arc │ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ │ │
|
||||
│ │ │ CPU Node │ │ GPU Node │ │ │
|
||||
│ │ │ Pool │ │ Pool │ │ │
|
||||
│ │ │ │ │ NVIDIA │ │ │
|
||||
│ │ │ Services │ │ T4/A2/L4 │ │ │
|
||||
│ │ └──────────┘ └──────────┘ │ │
|
||||
│ └─────────────────────────────────────────┘ │
|
||||
│ ┌───────────────┐ ┌─────────────────────┐ │
|
||||
│ │ Azure Arc VMs │ │ Storage Spaces │ │
|
||||
│ │ (DDA/GPU-P) │ │ Direct (S2D) │ │
|
||||
│ └───────────────┘ └─────────────────────┘ │
|
||||
└───────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## GPU-stotte i Azure Local
|
||||
|
||||
### GPU-tilordningsmetoder
|
||||
|
||||
Azure Local stotter to metoder for GPU-bruk:
|
||||
|
||||
| Egenskap | Discrete Device Assignment (DDA) | GPU Partitioning (GPU-P) |
|
||||
|----------|----------------------------------|--------------------------|
|
||||
| Ressursmodell | Hel GPU til en VM | Delt GPU mellom flere VM-er |
|
||||
| VM-tetthet | Lav (1 GPU = 1 VM) | Hoy (1 GPU = mange VM-er) |
|
||||
| Appkompatibilitet | Full (DX12, OpenGL, CUDA) | Full (DX12, OpenGL, CUDA) |
|
||||
| VRAM | Opp til full GPU VRAM | Per partisjon |
|
||||
| Driver i gjest | Leverandor-driver (NVIDIA) | Leverandor-driver (NVIDIA) |
|
||||
| AKS-stotte | Ja | Nei (kun VM-er) |
|
||||
| Best for | AI-trening, stor inferens | Lettere inferens, delt bruk |
|
||||
|
||||
### Stottede NVIDIA GPU-er
|
||||
|
||||
| GPU-modell | DDA (Arc VMs) | DDA (AKS) | GPU-P (VMs) | Typisk bruk |
|
||||
|------------|---------------|-----------|-------------|-------------|
|
||||
| NVIDIA T4 | Ja | Ja | Nei | Inferens, lette modeller |
|
||||
| NVIDIA A2 | Ja | Ja | Ja | Inferens, mellomstore modeller |
|
||||
| NVIDIA A10 | Ja (unmanaged) | Nei | Ja | Trening og inferens |
|
||||
| NVIDIA A16 | Ja | Ja | Ja | Multi-bruker inferens |
|
||||
| NVIDIA A40 | Ja (unmanaged) | Nei | Ja | Tung trening |
|
||||
| NVIDIA L4 | Ja | Ja | Ja | Moderne inferens |
|
||||
| NVIDIA L40 | Ja | Ja | Ja | Stor modell-inferens |
|
||||
| NVIDIA L40S | Ja | Ja | Ja | High-end AI workloads |
|
||||
|
||||
### GPU-klargjoring
|
||||
|
||||
```powershell
|
||||
# Sjekk GPU-status pa Azure Local-noder
|
||||
Get-PnpDevice | Select-Object Status, Class, FriendlyName, InstanceId |
|
||||
Where-Object { $_.FriendlyName -match "Nvidia" }
|
||||
|
||||
# Installer mitigation driver for DDA
|
||||
# (Kreves for Azure Local 23H2+)
|
||||
pnputil /add-driver oem_mitigation.inf /install
|
||||
|
||||
# Verifiser GPU er dismounted og klar for DDA
|
||||
Get-VMHostAssignableDevice
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cluster-felles ML Stack
|
||||
|
||||
### AKS enabled by Azure Arc pa Azure Local
|
||||
|
||||
AKS pa Azure Local gir en fullt administrert Kubernetes-opplevelse med GPU-stotte:
|
||||
|
||||
**Oppretting av klynge med GPU:**
|
||||
|
||||
```bash
|
||||
# Opprett AKS-klynge pa Azure Local
|
||||
az aksarc create \
|
||||
--resource-group ai-edge-rg \
|
||||
--name ai-edge-cluster \
|
||||
--custom-location my-custom-location \
|
||||
--vnet-ids /subscriptions/.../virtualNetworks/ai-vnet
|
||||
|
||||
# Legg til GPU node pool
|
||||
az aksarc nodepool add \
|
||||
--cluster-name ai-edge-cluster \
|
||||
--name gpu-pool \
|
||||
--resource-group ai-edge-rg \
|
||||
--node-count 2 \
|
||||
--node-vm-size Standard_NC4_A2 \
|
||||
--os-type Linux
|
||||
```
|
||||
|
||||
**GPU VM SKU-er tilgjengelige:**
|
||||
|
||||
| VM SKU | GPU | vCPU | Minne (GB) | GPU-minne |
|
||||
|--------|-----|------|------------|-----------|
|
||||
| Standard_NK6 | T4 (1x) | 6 | 56 | 16 GB |
|
||||
| Standard_NC4 | A2 (1x) | 4 | 28 | 16 GB |
|
||||
| Standard_NC8 | A2 (1x) | 8 | 56 | 16 GB |
|
||||
| Standard_NC16 | A16 (1x) | 16 | 112 | 16 GB |
|
||||
| Standard_NC32 | A16 (2x) | 32 | 224 | 32 GB |
|
||||
|
||||
### KAITO pa Azure Local
|
||||
|
||||
Kubernetes AI Toolchain Operator (KAITO) kjorer som en cluster extension:
|
||||
|
||||
```bash
|
||||
# Opprett klynge med KAITO aktivert
|
||||
az aksarc create \
|
||||
--resource-group ai-edge-rg \
|
||||
--name ai-kaito-cluster \
|
||||
--custom-location my-custom-location \
|
||||
--vnet-ids /subscriptions/.../virtualNetworks/ai-vnet \
|
||||
--enable-ai-toolchain-operator
|
||||
|
||||
# Aktiver KAITO pa eksisterende klynge
|
||||
az aksarc update \
|
||||
--resource-group ai-edge-rg \
|
||||
--name ai-edge-cluster \
|
||||
--enable-ai-toolchain-operator
|
||||
```
|
||||
|
||||
**Deploy LLM med KAITO:**
|
||||
|
||||
```yaml
|
||||
# workspace-phi4-mini.yaml
|
||||
apiVersion: kaito.sh/v1alpha1
|
||||
kind: Workspace
|
||||
metadata:
|
||||
name: workspace-phi-4-mini
|
||||
spec:
|
||||
resource:
|
||||
instanceType: Standard_NC8
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
apps: llm-inference
|
||||
inference:
|
||||
preset:
|
||||
name: phi-4-mini-instruct
|
||||
```
|
||||
|
||||
```bash
|
||||
# Deploy modellen
|
||||
kubectl apply -f workspace-phi4-mini.yaml
|
||||
|
||||
# Sjekk status
|
||||
kubectl get workspace -w
|
||||
|
||||
# Test inferens
|
||||
export SERVICE_IP=$(kubectl get svc workspace-phi-4-mini \
|
||||
-o jsonpath='{.spec.clusterIP}')
|
||||
|
||||
kubectl run -it --rm curl --image=curlimages/curl -- \
|
||||
curl -X POST http://$SERVICE_IP/v1/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "phi-4-mini-instruct",
|
||||
"prompt": "Forklar fordelene med edge AI for offentlig sektor",
|
||||
"max_tokens": 200
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local Azure Services
|
||||
|
||||
### Azure-tjenester som kjorer lokalt
|
||||
|
||||
Azure Local gir tilgang til et voksende utvalg Azure-tjenester direkte pa lokale servere:
|
||||
|
||||
| Tjeneste | Tilgjengelighet | AI-relevans |
|
||||
|----------|-----------------|-------------|
|
||||
| AKS (Arc) | GA | Container-basert ML og inferens |
|
||||
| Azure Arc VMs | GA | GPU-akselererte VM-er for AI |
|
||||
| Azure Monitor | GA | Overvaking av AI-arbeidsbelastninger |
|
||||
| Azure Policy | GA | Governance for AI-deployments |
|
||||
| Azure Key Vault | GA | Hemmelighetshaandtering for modeller |
|
||||
| Azure Container Registry | Preview | Lokal container-lagring (disconnected) |
|
||||
| Azure Arc Data Services | GA | SQL og PostgreSQL for AI-data |
|
||||
|
||||
### Lokal Container Registry for disconnected drift
|
||||
|
||||
```bash
|
||||
# Opprett lokal ACR for disconnected miljoer
|
||||
# (Azure Local med autonome operasjoner)
|
||||
az acr create \
|
||||
--name myedgeregistry \
|
||||
--resource-group ai-edge-rg \
|
||||
--location autonomous \
|
||||
--sku Standard
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Storage-optimalisert inferens
|
||||
|
||||
### Storage Spaces Direct (S2D)
|
||||
|
||||
Azure Local bruker S2D for distribuert lagring med hoy ytelse:
|
||||
|
||||
| Lagringstype | Best for | Ytelse |
|
||||
|-------------|----------|--------|
|
||||
| NVMe + SSD tiered | Modell-lasting | <100ms load for 7B modell |
|
||||
| All-flash NVMe | Real-time inferens | Sub-ms I/O |
|
||||
| SSD + HDD tiered | Modell-arkiv, batch | Kostnadsoptimalt |
|
||||
|
||||
### Persistent Volume for ML-modeller
|
||||
|
||||
```yaml
|
||||
# persistent-volume-claim.yaml
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ai-model-storage
|
||||
namespace: ai-workloads
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
storageClassName: azure-local-ssd
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
---
|
||||
# Pod med modell-lagring
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: model-server
|
||||
namespace: ai-workloads
|
||||
spec:
|
||||
containers:
|
||||
- name: inference
|
||||
image: myregistry/model-server:v1
|
||||
volumeMounts:
|
||||
- name: model-data
|
||||
mountPath: /models
|
||||
resources:
|
||||
limits:
|
||||
nvidia.com/gpu: 1
|
||||
volumes:
|
||||
- name: model-data
|
||||
persistentVolumeClaim:
|
||||
claimName: ai-model-storage
|
||||
```
|
||||
|
||||
### Caching-strategier for modeller
|
||||
|
||||
| Strategi | Beskrivelse | Fordel |
|
||||
|----------|-------------|--------|
|
||||
| Pre-load til NVMe | Last modeller ved oppstart | Raskest cold start |
|
||||
| Shared PVC | ReadWriteMany for flere pods | Effektiv lagring |
|
||||
| InitContainer | Last modell for main container starter | Paalitelig sekvensering |
|
||||
| Model sidecar | Egen container for modell-lasting | Separasjon av ansvar |
|
||||
|
||||
---
|
||||
|
||||
## Hybrid Resilience Patterns
|
||||
|
||||
### High Availability for AI pa Azure Local
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Azure Local HA Cluster │
|
||||
│ │
|
||||
│ Node 1 ────────── Node 2 │
|
||||
│ GPU: L4 GPU: L4 │
|
||||
│ AI Workload AI Workload │
|
||||
│ (Active) (Standby) │
|
||||
│ │ │ │
|
||||
│ └──── S2D ───────┘ │
|
||||
│ Replicated Storage │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Resilience-moenster | Beskrivelse | RTO |
|
||||
|---------------------|-------------|-----|
|
||||
| Active-Passive GPU | Standby node med GPU ready | 2-5 min |
|
||||
| Active-Active load balanced | Inferens fordelt pa noder | 0 (graceful) |
|
||||
| N+1 redundancy | Ekstra node for failover | 1-3 min |
|
||||
| Stretched cluster | Klynge over to lokasjoner | Automatisk |
|
||||
|
||||
### Failover-konfigurasjon
|
||||
|
||||
```powershell
|
||||
# Konfigurer VM failover med GPU re-assignment
|
||||
# Krever at GPU-er er tilgjengelige pa failover-noden
|
||||
|
||||
# Sett foretrukket eier for AI VM
|
||||
Set-ClusterGroup -Name "AI-Inference-VM" `
|
||||
-PreferredOwner "Node1", "Node2"
|
||||
|
||||
# Aktiver automatisk failback
|
||||
(Get-ClusterGroup "AI-Inference-VM").AutoFailbackType = 1
|
||||
```
|
||||
|
||||
### Cloud-fallback for Azure Local
|
||||
|
||||
Nar lokal kapasitet er utilstrekkelig:
|
||||
|
||||
```yaml
|
||||
# Hybrid inference med sky-fallback
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: inference-config
|
||||
data:
|
||||
routing: |
|
||||
primary:
|
||||
endpoint: http://local-model-svc:8080/v1/completions
|
||||
timeout: 5s
|
||||
fallback:
|
||||
endpoint: https://my-foundry.openai.azure.com/v1/completions
|
||||
condition: local_gpu_util > 95% OR local_unavailable
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nettverksarkitektur
|
||||
|
||||
### Anbefalte nettverkstopologier
|
||||
|
||||
| Topologi | Bruk | Krav |
|
||||
|----------|------|------|
|
||||
| Converged | Enkle deployments | Min 10 Gbps |
|
||||
| Hyper-converged | Standard AI-klynge | 25 Gbps + RDMA |
|
||||
| Switched | Stor skala, mange noder | 25-100 Gbps fabric |
|
||||
|
||||
### Minimum nettverkskrav for AI
|
||||
|
||||
| Trafikk | Minimum | Anbefalt |
|
||||
|---------|---------|----------|
|
||||
| Management (Arc) | 1 Gbps + internett | 10 Gbps |
|
||||
| Storage (S2D) | 10 Gbps RDMA | 25 Gbps RDMA |
|
||||
| Compute (GPU) | 10 Gbps | 25 Gbps |
|
||||
| Client (inferens) | 1 Gbps | 10 Gbps |
|
||||
|
||||
---
|
||||
|
||||
## Sizing-guide for AI-arbeidsbelastninger
|
||||
|
||||
### Anbefalte konfigurasjoner
|
||||
|
||||
| Arbeidsbelastning | Noder | GPU per node | Minne per node | Lagring |
|
||||
|-------------------|-------|-------------|----------------|---------|
|
||||
| Liten inferens (Phi-3) | 2 | 1x A2 | 64 GB | 1 TB NVMe |
|
||||
| Medium inferens (Phi-4, Mistral-7B) | 3 | 1x A16 | 128 GB | 2 TB NVMe |
|
||||
| Stor inferens (Llama-70B) | 4 | 2x L40S | 256 GB | 4 TB NVMe |
|
||||
| Trening + inferens | 4+ | 2x A40/L40S | 512 GB | 8 TB NVMe |
|
||||
|
||||
### Kostnadsestimat (Azure Local)
|
||||
|
||||
| Komponent | Estimert kost (NOK) |
|
||||
|-----------|---------------------|
|
||||
| 3-node Azure Local cluster | 300,000 - 600,000 |
|
||||
| NVIDIA A2 GPU (per stk) | 15,000 - 25,000 |
|
||||
| NVIDIA L4 GPU (per stk) | 25,000 - 40,000 |
|
||||
| NVIDIA L40S GPU (per stk) | 80,000 - 120,000 |
|
||||
| Azure Local lisens | Inkludert i Azure-abonnement |
|
||||
| Azure Arc management | Inkludert (basis) |
|
||||
| Azure ML extension | Inkludert |
|
||||
|
||||
**Merk:** Priser er estimater og varierer med leverandor og konfigurasjon.
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Azure Local er den primaere plattformen for on-premises AI i Microsoft-okosystemet** — fullt integrert med Azure Arc for sentralisert styring, men med all databehandling pa egen infrastruktur.
|
||||
- **GPU-stotten er bred med NVIDIA T4/A2/A16/L4/L40/L40S** — bade DDA (hel GPU) og GPU-P (delt GPU) er tilgjengelig, noe som gir fleksibilitet fra lette inferensoppgaver til tung trening.
|
||||
- **KAITO pa AKS Arc forenkler LLM-deployment drastisk** — fra GPU-klargjoring til modell-serving med OpenAI-kompatibelt API pa noen fa kubectl-kommandoer.
|
||||
- **For norsk offentlig sektor gir Azure Local data residency i Norge** med full Azure-administrasjon fra Norway East-regionen — data forlater aldri lokale servere.
|
||||
- **Hybrid resilience med sky-fallback** sikrer at AI-tjenester forblir tilgjengelige selv ved lokal kapasitetsmangel, med automatisk routing til Azure-endepunkter.
|
||||
|
|
@ -0,0 +1,403 @@
|
|||
# Data Sovereignty for Norwegian Public Sector
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Datasuverenitet er et av de viktigste temaene nar norsk offentlig sektor vurderer skybaserte AI-losninger. Etter Schrems II-dommen (2020), EUs AI Act (2024), og okt fokus pa digital autonomi i Europa, ma organisasjoner navigere et komplekst landskap av juridiske krav, tekniske kontroller og politiske forventninger.
|
||||
|
||||
For AI-arbeidsbelastninger er utfordringene spesielt store: AI-modeller kan inneholde implisitt persondata i sine vekter, treningsdata kan vare sensitivt, og inferensresultater kan avslore informasjon om underlaget. Samtidig er mange av de kraftigste AI-tjenestene kun tilgjengelige fra bestemte Azure-regioner, og noen krever global databehandling.
|
||||
|
||||
Denne referansen gir en strukturert oversikt over regulatoriske krav, Microsofts tilbud for datasuverenitet, og praktiske arkitekturmoenstre for norsk offentlig sektor som vil ta i bruk AI pa en trygg og lovlig mate.
|
||||
|
||||
---
|
||||
|
||||
## Regulatorisk landskap
|
||||
|
||||
### Schrems II og konsekvenser
|
||||
|
||||
Schrems II-dommen (juli 2020) ugyldiggjorde EU-US Privacy Shield og stilte strengere krav til overforing av persondata til tredjeland:
|
||||
|
||||
| Aspekt | Konsekvens for AI i skyen |
|
||||
|--------|--------------------------|
|
||||
| Ugyldiggjoring av Privacy Shield | Kan ikke basere dataoverforing til USA pa Privacy Shield |
|
||||
| Strengere SCC-krav | Standard Contractual Clauses krever tilleggstiltak |
|
||||
| Risikovurdering pakreves | Ma vurdere om mottakerlandets lovgivning gir tilstrekkelig vern |
|
||||
| Supplementary measures | Tekniske, organisatoriske og kontraktuelle tiltak ma iverksettes |
|
||||
|
||||
**Post-Schrems II tiltak fra Microsoft:**
|
||||
- EU Data Boundary (EUDB) implementert for a holde data i EU/EFTA
|
||||
- Standard Contractual Clauses (SCCs) oppdatert
|
||||
- Data Protection Addendum (DPA) styrket
|
||||
- Transparensrapporter publisert
|
||||
|
||||
### EU-US Data Privacy Framework (2023)
|
||||
|
||||
I juli 2023 vedtok EU-kommisjonen EU-US Data Privacy Framework som ny mekanisme for lovlig overforing av persondata til USA:
|
||||
|
||||
| Aspekt | Status |
|
||||
|--------|--------|
|
||||
| Adequacy decision | Vedtatt juli 2023 |
|
||||
| Microsoft-sertifisering | Ja, sertifisert under DPF |
|
||||
| Norsk aksept | Norge (via EOS-avtalen) folger EU-beslutninger |
|
||||
| Stabilitet | Utfordret av NOYB, men gyldig per 2026 |
|
||||
| Anbefalinger | Bruk DPF + tekniske tiltak (defense in depth) |
|
||||
|
||||
### GDPR/Personvernforordningen
|
||||
|
||||
| Krav | Relevans for AI |
|
||||
|------|-----------------|
|
||||
| Art. 5 (formaalsbegrensning) | AI-modeller ma brukes til angitt formal |
|
||||
| Art. 6 (behandlingsgrunnlag) | Samtykke, avtale eller berettiget interesse |
|
||||
| Art. 22 (automatiserte beslutninger) | Rett til menneskelig inngripen |
|
||||
| Art. 25 (privacy by design) | Innebygd personvern i AI-systemer |
|
||||
| Art. 35 (DPIA) | Pakrevd for AI med hoy risiko |
|
||||
| Art. 44-49 (tredjelands overforing) | Relevant for sky-AI-tjenester |
|
||||
|
||||
### EUs AI Act
|
||||
|
||||
| Risikokategori | Krav | Eksempler |
|
||||
|----------------|------|-----------|
|
||||
| Uakseptabel risiko | Forbudt | Sosial scoring, manipulering |
|
||||
| Hoy risiko | Strenge krav | Biometrisk ID, kredittscoring |
|
||||
| Begrenset risiko | Transparenskrav | Chatbots (merking) |
|
||||
| Minimal risiko | Ingen sarlige krav | Spamfiltre, anbefalinger |
|
||||
|
||||
**Norsk implementering:** AI Act folges opp gjennom EOS-avtalen. Datatilsynet er ansvarlig for haandheving.
|
||||
|
||||
---
|
||||
|
||||
## Norske krav til data residency
|
||||
|
||||
### Utredningsinstruksen
|
||||
|
||||
Statlige tiltak (inkludert AI-prosjekter) ma folge utredningsinstruksen:
|
||||
|
||||
| Krav | Konsekvens for AI-prosjekter |
|
||||
|------|------------------------------|
|
||||
| Problemdefinisjon | Klar definisjon av hva AI skal lose |
|
||||
| Behovsanalyse | Dokumenter hvorfor AI er nodvendig |
|
||||
| Alternativvurdering | Sammenlign sky/hybrid/lokalt |
|
||||
| Konsekvensutredning | Personvern, sikkerhet, okonomi |
|
||||
| Forholdsmessighet | Balanse mellom nytte og risiko |
|
||||
| Horing | Involver berorte parter |
|
||||
|
||||
### Sikkerhetsloven og NSMs krav
|
||||
|
||||
For virksomheter underlagt sikkerhetsloven:
|
||||
|
||||
| Krav | Implikasjon |
|
||||
|------|------------|
|
||||
| Informasjonssikkerhet | AI-systemer som behandler sikkerhetsgradert info |
|
||||
| Forebyggende sikkerhet | Risikovurdering av AI-leverandorer |
|
||||
| Personellsikkerhet | Klarering for tilgang til AI-systemer |
|
||||
| Objektsikkerhet | Fysisk sikring av AI-infrastruktur |
|
||||
| IKT-sikkerhet | NSMs grunnprinsipper for AI-systemer |
|
||||
|
||||
### Digitaliseringsrundskrivet
|
||||
|
||||
Regjeringens retningslinjer for offentlig sektors digitalisering:
|
||||
|
||||
| Prinsipp | AI-relevans |
|
||||
|----------|-------------|
|
||||
| Skyforst-strategi | Sky er forstevalg, men med unntak for sensitiv data |
|
||||
| Apne data | AI-modeller bor benytte apne datakilder der mulig |
|
||||
| Deling av data | Samarbeid mellom etater om AI-treningsdata |
|
||||
| Personvern | DPIA for alle AI-systemer med persondata |
|
||||
| Tilgjengelighet | AI-tjenester ma vaere universelt utformet |
|
||||
|
||||
---
|
||||
|
||||
## Azure Data Residency for Norge
|
||||
|
||||
### Azure-regioner i Norge
|
||||
|
||||
| Region | Tjenester | Formaal |
|
||||
|--------|-----------|---------|
|
||||
| Norway East (Oslo) | Fullt tjenesteomfang | Primaerregion |
|
||||
| Norway West (Stavanger) | Begrenset | DR/backup |
|
||||
|
||||
### Azure-tjenester tilgjengelig i Norway East
|
||||
|
||||
| Tjenestekategori | Tilgjengelighet | Merknader |
|
||||
|-----------------|-----------------|-----------|
|
||||
| Compute (VMs) | GA | Inkl. GPU (NC, ND-serier) |
|
||||
| Azure Kubernetes Service | GA | Primaer container-plattform |
|
||||
| Azure Storage | GA | Alle lagringstyper |
|
||||
| Azure SQL/Cosmos DB | GA | Regional data residency |
|
||||
| Azure AI Foundry | Begrenset | Ikke alle modeller |
|
||||
| Azure OpenAI | GA | GPT-4o, GPT-4o-mini |
|
||||
| Azure AI Services | GA | Vision, Speech, Language |
|
||||
| Azure Machine Learning | GA | Trening og inferens |
|
||||
| Azure Key Vault | GA | Hemmelighetshaandtering |
|
||||
| Azure Monitor | GA | Overvaking |
|
||||
|
||||
### Tjenester som IKKE er tilgjengelige i Norway East
|
||||
|
||||
| Tjeneste | Naermeste region | Alternativ |
|
||||
|----------|-----------------|------------|
|
||||
| Azure OpenAI (GPT-5) | Sweden Central | Bruk EUDB-region |
|
||||
| Copilot Studio | EU-regioner | Sett tenant til EU |
|
||||
| Noen AI Foundry-modeller | Sweden/West Europe | Vurder EUDB-scope |
|
||||
| Azure AI Search (semantic) | West Europe | Kan kreve EU-plassering |
|
||||
|
||||
---
|
||||
|
||||
## EU Data Boundary (EUDB)
|
||||
|
||||
### Hva er EUDB?
|
||||
|
||||
EU Data Boundary er Microsofts forpliktelse til a lagre og behandle kundedata og persondata innenfor EU/EFTA for sine enterprise online services:
|
||||
|
||||
| Tjeneste | EUDB-stottet | Betingelse |
|
||||
|----------|-------------|------------|
|
||||
| Azure (regionale) | Ja | Deploy i EU/EFTA-region |
|
||||
| Azure (ikke-regionale) | Delvis | Krever konfigurasjon |
|
||||
| Dynamics 365 | Ja | Tenant i EU geo |
|
||||
| Power Platform | Ja | Miljo i EU geo |
|
||||
| Microsoft 365 | Ja | Tenant i EU geo |
|
||||
|
||||
### EUDB-land
|
||||
|
||||
EU Data Boundary dekker:
|
||||
- **EU:** Osterrike, Belgia, Bulgaria, Kroatia, Kypros, Tsjekkia, Danmark, Estland, Finland, Frankrike, Tyskland, Hellas, Ungarn, Irland, Italia, Latvia, Litauen, Luxembourg, Malta, Nederland, Polen, Portugal, Romania, Slovakia, Slovenia, Spania, Sverige
|
||||
- **EFTA:** Liechtenstein, Island, **Norge**, Sveits
|
||||
|
||||
### Konfigurering av EUDB for Azure
|
||||
|
||||
```bash
|
||||
# Konfigurer Azure Data Boundary for tenant
|
||||
az data-boundary create --data-boundary EU --default default
|
||||
```
|
||||
|
||||
```json
|
||||
// Azure Policy: Tving ressurser til Norway East
|
||||
{
|
||||
"if": {
|
||||
"not": {
|
||||
"field": "location",
|
||||
"in": ["norwayeast", "norwaywest", "swedencentral",
|
||||
"westeurope", "northeurope"]
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"effect": "deny"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Microsoft Sovereign Cloud
|
||||
|
||||
### Sovereign deployment-modeller
|
||||
|
||||
Microsoft tilbyr tre nivaer av suverenitet:
|
||||
|
||||
| Modell | Beskrivelse | Kontrollniva | Tilgjengelighet |
|
||||
|--------|-------------|-------------|-----------------|
|
||||
| Sovereign Public Cloud | Azure med EUDB + sovereign controls | Hoy | GA (EU/EFTA) |
|
||||
| Sovereign Private Cloud | Azure Local/M365 Local i eget datasenter | Hoyest | GA |
|
||||
| National Partner Clouds | Partnerdrevet lokal sky | Variabel | Utvalgte land |
|
||||
|
||||
### Sovereign Landing Zone (SLZ)
|
||||
|
||||
SLZ er en variant av Azure Landing Zone med innebygde suverenitetskontroller:
|
||||
|
||||
| Kontrollniva | Policyer | Bruksomrade |
|
||||
|-------------|----------|-------------|
|
||||
| L1 (Basis) | Data residency, godkjente regioner | Standard offentlig sektor |
|
||||
| L2 (Styrket) | L1 + kryptering med CMK | Sensitiv data |
|
||||
| L3 (Konfidensielt) | L2 + confidential computing | Sikkerhetsgradert |
|
||||
|
||||
**SLZ Policy-kontroller:**
|
||||
|
||||
| Policy-ID | Kontroll | Effekt |
|
||||
|-----------|---------|--------|
|
||||
| SO.1 | Data residency — godkjente regioner | Deny |
|
||||
| SO.2 | Kryptering med kundestyrt nokkel (CMK) | Audit/Deny |
|
||||
| SO.3 | Confidential computing for utvalgte tjenester | Audit |
|
||||
| SO.4 | Private endpoints for datatilgang | Deny |
|
||||
|
||||
### Implementering av SLZ
|
||||
|
||||
```bash
|
||||
# Deploy Sovereign Landing Zone med Bicep
|
||||
az deployment sub create \
|
||||
--location norwayeast \
|
||||
--template-file sovereign-landing-zone.bicep \
|
||||
--parameters \
|
||||
allowedLocations='["norwayeast","norwaywest"]' \
|
||||
requireCMK=true \
|
||||
enforcePrivateEndpoints=true \
|
||||
dataClassification="sensitive"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Azure Data Classification for AI
|
||||
|
||||
### Dataklassifiseringsmatrise
|
||||
|
||||
| Klassifisering | Beskrivelse | Sky-tillatelse | Azure-krav |
|
||||
|---------------|-------------|----------------|------------|
|
||||
| Apen | Offentlig tilgjengelig | Alle regioner | Standard |
|
||||
| Intern | Ikke-sensitiv intern data | EU/EFTA | EUDB |
|
||||
| Fortrolig | Sensitiv, persondata | Norway East/West | CMK + RBAC |
|
||||
| Strengt fortrolig | Hoy sensitivitet | Norway + spesialtiltak | SLZ L2+ |
|
||||
| Sikkerhetsgradert | Underlagt sikkerhetsloven | Lokalt / godkjent sky | Azure Local |
|
||||
|
||||
### AI-spesifikke datakategorier
|
||||
|
||||
| Datakategori | Eksempel | Klassifisering | Behandlingssted |
|
||||
|-------------|----------|---------------|-----------------|
|
||||
| Treningsdata | Dokumenter, bilder | Fortrolig+ | Norway East |
|
||||
| Modellvekter | Fine-tuned modeller | Intern/Fortrolig | Norway East |
|
||||
| Inferens-input | Brukerforesporsler | Fortrolig | Norway East |
|
||||
| Inferens-output | AI-svar | Fortrolig | Norway East |
|
||||
| Systemlogger | Telemetri, metrikker | Intern | EU/EFTA |
|
||||
| Prompt-logger | Bruker-prompts | Fortrolig | Norway East |
|
||||
|
||||
---
|
||||
|
||||
## Praktiske arkitekturmoenstre
|
||||
|
||||
### Moenster 1: Full sky i Norway East
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Norway East Region │
|
||||
│ ┌───────────┐ ┌───────────────┐ │
|
||||
│ │ Azure │ │ Azure AI │ │
|
||||
│ │ OpenAI │ │ Services │ │
|
||||
│ │ (GPT-4o) │ │ (Vision,Speech│ │
|
||||
│ └─────┬─────┘ └──────┬────────┘ │
|
||||
│ │ │ │
|
||||
│ ┌─────▼───────────────▼────────┐ │
|
||||
│ │ Azure ML Workspace │ │
|
||||
│ │ + Private Endpoints │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ Azure Key Vault (CMK) │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Best for:** Standard AI-prosjekter uten krav utover GDPR/EUDB.
|
||||
|
||||
### Moenster 2: Hybrid med Azure Local
|
||||
|
||||
```
|
||||
┌──────────────────────┐ ┌────────────────────┐
|
||||
│ Norway East │ │ Eget datasenter │
|
||||
│ ┌────────────────┐ │ │ ┌──────────────┐ │
|
||||
│ │ Azure ML │ │ │ │ Azure Local │ │
|
||||
│ │ (Orchestration)│◄─┼──┼─►│ (AI Inference)│ │
|
||||
│ └────────────────┘ │ │ │ GPU + Data │ │
|
||||
│ ┌────────────────┐ │ │ └──────────────┘ │
|
||||
│ │ Azure Monitor │ │ │ ┌──────────────┐ │
|
||||
│ │ (Overvaking) │◄─┼──┼──│ Arc Agent │ │
|
||||
│ └────────────────┘ │ │ └──────────────┘ │
|
||||
└──────────────────────┘ └────────────────────┘
|
||||
```
|
||||
|
||||
**Best for:** Sensitiv data som ikke kan forlate egne lokaler, men trenger sky-administrasjon.
|
||||
|
||||
### Moenster 3: Fullstendig lokal (sovereign private)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Eget datasenter │
|
||||
│ ┌──────────────────────────────┐ │
|
||||
│ │ Azure Local Cluster │ │
|
||||
│ │ ┌────────┐ ┌───────────┐ │ │
|
||||
│ │ │ AKS │ │ ONNX │ │ │
|
||||
│ │ │ (KAITO)│ │ Runtime │ │ │
|
||||
│ │ └────────┘ └───────────┘ │ │
|
||||
│ │ ┌────────────────────────┐ │ │
|
||||
│ │ │ Disconnected │ │ │
|
||||
│ │ │ AI Containers │ │ │
|
||||
│ │ └────────────────────────┘ │ │
|
||||
│ └──────────────────────────────┘ │
|
||||
│ Ingen ekstern tilkobling │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Best for:** Sikkerhetsgradert data, forsvarssektor, kritisk infrastruktur.
|
||||
|
||||
---
|
||||
|
||||
## Beslutningstre for datasuverenitet
|
||||
|
||||
```
|
||||
Er dataene sikkerhetsgraderte (Sikkerhetsloven)?
|
||||
├── Ja → Moenster 3: Azure Local, helt lokalt
|
||||
│ Ingen sky-tilkobling
|
||||
│ ONNX Runtime + Disconnected containers
|
||||
│
|
||||
└── Nei → Inneholder dataene personopplysninger?
|
||||
├── Ja → Er det saerlige kategorier (helse, biometri)?
|
||||
│ ├── Ja → Moenster 2: Hybrid
|
||||
│ │ Data lokalt, styring fra Norway East
|
||||
│ │ DPIA pakrevd, CMK-kryptering
|
||||
│ │
|
||||
│ └── Nei → Moenster 1 eller 2
|
||||
│ Norway East med EUDB
|
||||
│ Standard GDPR-tiltak
|
||||
│
|
||||
└── Nei → Moenster 1: Full sky
|
||||
Norway East / EU region
|
||||
Standard sikkerhetstiltak
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compliance-sjekkliste for AI-prosjekter
|
||||
|
||||
| # | Kontroll | Ansvarlig | Status |
|
||||
|---|---------|-----------|--------|
|
||||
| 1 | DPIA gjennomfort | Personvernombud | |
|
||||
| 2 | Behandlingsgrunnlag dokumentert | Juridisk | |
|
||||
| 3 | Dataklassifisering gjennomfort | Informasjonseier | |
|
||||
| 4 | Azure-region valgt (Norway East) | IT-arkitekt | |
|
||||
| 5 | EUDB konfigurert | Sky-administrator | |
|
||||
| 6 | CMK aktivert for sensitiv data | Sikkerhetsansvarlig | |
|
||||
| 7 | Private endpoints konfigurert | Nettverksansvarlig | |
|
||||
| 8 | RBAC implementert | IAM-ansvarlig | |
|
||||
| 9 | Logging og overvaking aktivert | Driftsansvarlig | |
|
||||
| 10 | AI Act risikoklassifisering | AI-ansvarlig | |
|
||||
| 11 | Utredningsinstruksen fulgt | Prosjektleder | |
|
||||
| 12 | ROS-analyse gjennomfort | Sikkerhetsansvarlig | |
|
||||
| 13 | Leverandorvurdering gjennomfort | Innkjopsansvarlig | |
|
||||
| 14 | Databehandleravtale inngatt | Juridisk | |
|
||||
| 15 | Exitstrategi dokumentert | IT-arkitekt | |
|
||||
|
||||
---
|
||||
|
||||
## Sammenligning: Sovereign Cloud-alternativer
|
||||
|
||||
| Egenskap | Azure Sovereign Public | Azure Local (Private) | National Partner Cloud |
|
||||
|----------|----------------------|----------------------|----------------------|
|
||||
| Data residency | EU/EFTA (konfiguerbar) | Fullt lokalt | Varierer |
|
||||
| Kontroll over data | Microsoft-driftet | Kunde-driftet | Partnerdriftet |
|
||||
| AI-tjenester | Fullt omfang | Begrensede (ONNX, containers) | Varierer |
|
||||
| Skalerbarhet | Hoy | Begrenset av hardware | Varierer |
|
||||
| Kostnad | Pay-as-you-go | CAPEX + OPEX | Varierer |
|
||||
| Compliance (GDPR) | Ja | Ja | Varierer |
|
||||
| Compliance (NSM) | Delvis | Ja (med tiltak) | Varierer |
|
||||
| Sikkerhetsgradert | Nei | Mulig | Varierer |
|
||||
| AI Act compliance | Verktoy tilgjengelig | Kunde-ansvar | Varierer |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Schrems II er ikke lenger den eneste utfordringen** — EU-US Data Privacy Framework (2023), EU Data Boundary, og Sovereign Landing Zone gir et nyansert verktoyskrin for lovlig bruk av Azure AI fra Norge.
|
||||
- **Norway East-regionen er forstevalg for norsk offentlig sektor** — de fleste AI-tjenester (Azure OpenAI GPT-4o, AI Services, ML) er tilgjengelig der, men noen nyere modeller krever Sweden Central eller West Europe.
|
||||
- **Tre arkitekturmoenstre dekker hele spekteret** — full sky for standard data, hybrid for sensitiv data, og helt lokalt (Azure Local) for sikkerhetsgradert — alltid med DPIA og risikovurdering.
|
||||
- **Sovereign Landing Zone med L1-L3 policyer** gir mekanisk haandheving av data residency, kryptering og tilgangskontroll — ikke bare dokumentbaserte lovnader.
|
||||
- **AI Act-klassifisering ma gjores for hvert AI-prosjekt** — norsk offentlig sektor ma identifisere risikokategori (minimal/begrenset/hoy/uakseptabel) og implementere tilsvarende tiltak for AI-systemer.
|
||||
|
|
@ -0,0 +1,513 @@
|
|||
# Disconnected AI Scenarios
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Frakoblede (disconnected) AI-scenarioer er situasjoner der AI-arbeidsbelastninger ma kjore uten internettilkobling — enten permanent, periodisk eller i beredskapssituasjoner. For norsk offentlig sektor er dette svart relevant: Forsvaret opererer i omrader uten nettdekning, helsesektoren trenger AI-stotte i ambulanser og utposter, og kritisk infrastruktur (energi, transport) ma fungere uavhengig av skytjenester.
|
||||
|
||||
Microsoft tilbyr flere losninger for frakoblet AI: Azure AI Foundry Tools disconnected containers for tradisjonelle AI-tjenester (tale, tekst, bilde), Azure Stack Edge for hardware-basert edge-inferens, Azure Local med disconnected operations for storre Kubernetes-miljoer, og ONNX Runtime for helt lokale modellkjoringer uten skyavhengigheter.
|
||||
|
||||
Denne referansen dekker de viktigste moensterne for offline modell-deployment, datarekonsiliering, lokal caching/synkronisering og fallback-strategier — alle med fokus pa palit drift nar nettverkstilkoblingen er ustabil eller fravarende.
|
||||
|
||||
---
|
||||
|
||||
## Spekter av tilkobling
|
||||
|
||||
AI-scenarioer fordeler seg langs et tilkoblingsspektrum:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Alltid Sporadisk Periodisk Helt │
|
||||
│ tilkoblet tilkoblet tilkoblet frakoblet │
|
||||
│ ●──────────●────────────●────────────● │
|
||||
│ | | | | │
|
||||
│ Standard Connected Batch sync Air-gapped │
|
||||
│ Azure containers + lokale │
|
||||
│ services (billing) offline ops modeller │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
| Modus | Nettverkskrav | Azure-tjenester | Billing |
|
||||
|-------|---------------|-----------------|---------|
|
||||
| Alltid tilkoblet | Stabilt internett | Alle | Pay-as-you-go |
|
||||
| Connected containers | Periodisk (billing) | Begrensede | Bruksbasert |
|
||||
| Periodisk synk | Timer/dager mellom tilkoblinger | Batch-synk | Commitment tier |
|
||||
| Helt frakoblet | Ingen | Kun lokale | Forhndsbetalt lisens |
|
||||
|
||||
---
|
||||
|
||||
## Offline Model Deployment
|
||||
|
||||
### Azure AI Foundry Tools Disconnected Containers
|
||||
|
||||
Microsofts primaere losning for AI-tjenester uten nettverkstilkobling:
|
||||
|
||||
| Tjeneste | Container | Disconnected | Status |
|
||||
|----------|-----------|--------------|--------|
|
||||
| Speech to Text | speech-to-text | Ja | GA |
|
||||
| Custom Speech to Text | custom-speech-to-text | Ja | GA |
|
||||
| Neural Text to Speech | neural-text-to-speech | Ja | GA |
|
||||
| Translator | text-translation | Ja | GA |
|
||||
| Language Detection | text-language-detection | Ja | GA |
|
||||
| Key Phrase Extraction | text-keyphrase | Ja | GA |
|
||||
| Named Entity Recognition | text-ner | Ja | GA |
|
||||
| PII Detection | text-pii | Ja | GA |
|
||||
| Sentiment Analysis | text-sentiment | Ja | GA |
|
||||
| CLU | clu | Ja | GA |
|
||||
| Summarization | text-summarization | Ja | Preview |
|
||||
| Read OCR | vision-read | Ja | GA |
|
||||
| Document Intelligence | document-intelligence | Ja | GA |
|
||||
| Content Safety (Text) | contentsafety-text | Ja | Preview |
|
||||
| Content Safety (Image) | contentsafety-image | Ja | Preview |
|
||||
| Prompt Shields | contentsafety-promptshields | Ja | Preview |
|
||||
|
||||
### Prosess for disconnected deployment
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ 1. Soknad og godkjenning │
|
||||
│ ├── Enterprise Agreement kreves │
|
||||
│ ├── Gyldig business case │
|
||||
│ └── Godkjenning innen 10 dager │
|
||||
│ │
|
||||
│ 2. Lisensnedlasting │
|
||||
│ ├── Kjop commitment tier │
|
||||
│ ├── Last ned lisensfil │
|
||||
│ └── Lisensfil har utlopsdato │
|
||||
│ │
|
||||
│ 3. Container-nedlasting │
|
||||
│ ├── Pull fra MCR (online) │
|
||||
│ ├── Eksporter til tar │
|
||||
│ └── Overfar til offline-miljo │
|
||||
│ │
|
||||
│ 4. Offline deployment │
|
||||
│ ├── Importer container │
|
||||
│ ├── Mount lisensfil │
|
||||
│ └── Kjor uten nettverkstilkobling │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Lisensnedlasting og container-oppsett
|
||||
|
||||
```bash
|
||||
# Steg 1: Last ned lisens (online maskin)
|
||||
docker run --rm -it \
|
||||
-v /host/license:/license \
|
||||
mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text \
|
||||
eula=accept \
|
||||
billing=https://my-resource.cognitiveservices.azure.com \
|
||||
apikey=<API_KEY> \
|
||||
DownloadLicense=True \
|
||||
Mounts:License=/license
|
||||
|
||||
# Steg 2: Eksporter container image
|
||||
docker save \
|
||||
mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text \
|
||||
-o speech-to-text.tar
|
||||
|
||||
# Steg 3: Overfar til offline-miljo (USB, etc.)
|
||||
# Kopier speech-to-text.tar og lisensfil
|
||||
|
||||
# Steg 4: Importer pa offline-maskin
|
||||
docker load -i speech-to-text.tar
|
||||
|
||||
# Steg 5: Kjor uten nettverkstilkobling
|
||||
docker run --rm -it -p 5000:5000 \
|
||||
-v /host/license:/license \
|
||||
mcr.microsoft.com/azure-cognitive-services/speechservices/speech-to-text \
|
||||
eula=accept \
|
||||
Mounts:License=/license \
|
||||
Mounts:Output=/output
|
||||
```
|
||||
|
||||
### ONNX Runtime — helt lokale modeller
|
||||
|
||||
For scenarioer uten Docker- eller lisensbehov:
|
||||
|
||||
```python
|
||||
# Helt lokal inferens med ONNX Runtime
|
||||
# Ingen skyavhengighet, ingen lisens, ingen Docker
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
|
||||
# Last modell fra lokal disk
|
||||
session = ort.InferenceSession(
|
||||
"/models/document-classifier.onnx",
|
||||
providers=['CPUExecutionProvider']
|
||||
)
|
||||
|
||||
# Kjor inferens
|
||||
input_name = session.get_inputs()[0].name
|
||||
result = session.run(None, {
|
||||
input_name: np.array(preprocessed_data)
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Azure Stack Edge i disconnected modus
|
||||
|
||||
### Nkkelforskjeller offline vs online
|
||||
|
||||
| Funksjon | Online | Disconnected |
|
||||
|----------|--------|-------------|
|
||||
| Azure Portal management | Ja | Nei — kun lokal UI |
|
||||
| Kubernetes workloads | Full Arc-stotte | Lokal kubectl |
|
||||
| Container registry | Azure Container Registry | Edge Container Registry |
|
||||
| Overvaking | Azure Monitor | Lokalt Kubernetes dashboard |
|
||||
| Azure Arc | Full integrasjon | Ikke tilgjengelig |
|
||||
| VM-styring | Arc-enabled VMs | Lokalt PowerShell/UI |
|
||||
| GPU workloads | Full stotte | Full stotte (forklargjort) |
|
||||
|
||||
### Forberedelse for disconnected drift
|
||||
|
||||
```powershell
|
||||
# Forbered Azure Stack Edge for offline-bruk
|
||||
# (Gjores mens enheten fortsatt er online)
|
||||
|
||||
# 1. Aktiver enhet via Azure Portal
|
||||
# 2. Enable Kubernetes
|
||||
Set-AzureDataBoxEdgeRole -Name "Kubernetes" -Activated
|
||||
|
||||
# 3. Deploy alle nodvendige container workloads
|
||||
kubectl apply -f ai-inference-deployment.yaml
|
||||
|
||||
# 4. Push container images til Edge Container Registry
|
||||
docker tag my-model:v1 ecr.edgehostname:31001/my-model:v1
|
||||
docker push ecr.edgehostname:31001/my-model:v1
|
||||
|
||||
# 5. Verifiser at alt kjorer
|
||||
kubectl get pods -A
|
||||
|
||||
# 6. Koble fra nettverket
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Reconciliation Strategies
|
||||
|
||||
### Utfordringer med frakoblet data
|
||||
|
||||
Nar AI-systemer kjorer offline, oppstar det utfordringer med:
|
||||
- Data som genereres lokalt ma synkroniseres nar tilkobling gjenopprettes
|
||||
- Modellresultater fra offline-perioden ma valideres
|
||||
- Konflikter mellom lokale og sentrale data
|
||||
- Versjonshaandtering av modeller og konfigurasjoner
|
||||
|
||||
### Rekonsilieringsmoenstre
|
||||
|
||||
| Moenster | Beskrivelse | Bruksomrade |
|
||||
|---------|-------------|-------------|
|
||||
| Store-and-Forward | Buffer lokalt, send nar tilkoblet | IoT-data, loggfiler |
|
||||
| Event Sourcing | Registrer alle hendelser, replay sentralt | Audit, compliance |
|
||||
| Last-Write-Wins | Siste endring vinner ved konflikt | Enkle konfigurasjoner |
|
||||
| Merge/CRDTs | Konfliktfri sammenslaing | Distribuerte datasett |
|
||||
| Manual Review | Menneske loeser konflikter | Kritiske beslutninger |
|
||||
|
||||
### Store-and-Forward med IoT Hub
|
||||
|
||||
```python
|
||||
# Lokal buffering og batch-synkronisering
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
class OfflineBuffer:
|
||||
def __init__(self, buffer_dir="/data/offline-buffer"):
|
||||
self.buffer_dir = Path(buffer_dir)
|
||||
self.buffer_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def store_result(self, inference_result, metadata):
|
||||
"""Lagre inferensresultat lokalt."""
|
||||
entry = {
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"result": inference_result,
|
||||
"metadata": metadata,
|
||||
"synced": False
|
||||
}
|
||||
filepath = self.buffer_dir / f"{entry['timestamp']}.json"
|
||||
filepath.write_text(json.dumps(entry))
|
||||
return filepath
|
||||
|
||||
def get_unsynced(self):
|
||||
"""Hent alle usynkroniserte resultater."""
|
||||
results = []
|
||||
for f in sorted(self.buffer_dir.glob("*.json")):
|
||||
entry = json.loads(f.read_text())
|
||||
if not entry.get("synced"):
|
||||
results.append((f, entry))
|
||||
return results
|
||||
|
||||
async def sync_to_cloud(self, iot_client):
|
||||
"""Synkroniser bufferede resultater til sky."""
|
||||
unsynced = self.get_unsynced()
|
||||
for filepath, entry in unsynced:
|
||||
try:
|
||||
await iot_client.send_message(
|
||||
json.dumps(entry)
|
||||
)
|
||||
entry["synced"] = True
|
||||
entry["synced_at"] = datetime.utcnow().isoformat()
|
||||
filepath.write_text(json.dumps(entry))
|
||||
except Exception as e:
|
||||
# Fortsett med neste — proev igjen senere
|
||||
print(f"Sync feilet for {filepath}: {e}")
|
||||
break
|
||||
```
|
||||
|
||||
### Modellversjon-rekonsiliering
|
||||
|
||||
```yaml
|
||||
# model-sync-config.yaml
|
||||
sync:
|
||||
strategy: "check-on-connect"
|
||||
model_registry:
|
||||
cloud: "https://ml-workspace.azureml.net/models"
|
||||
local: "/models/registry.json"
|
||||
conflict_resolution: "cloud-wins"
|
||||
validation:
|
||||
enabled: true
|
||||
test_dataset: "/data/validation/standard-test.json"
|
||||
min_accuracy: 0.95
|
||||
rollback:
|
||||
enabled: true
|
||||
keep_previous: 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local Cache and Sync
|
||||
|
||||
### Flerlags cache-arkitektur
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ Lag 1: In-Memory Cache (Redis) │
|
||||
│ TTL: 1 time │ Storrelse: 2 GB │
|
||||
│ Hoyest prioritet, raskest tilgang │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Lag 2: Lokal Disk Cache (SSD) │
|
||||
│ TTL: 7 dager │ Storrelse: 100 GB │
|
||||
│ Modellvekter, embeddings, resultater │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Lag 3: Persistent Storage (S2D/NAS) │
|
||||
│ Ingen TTL │ Storrelse: 1 TB+ │
|
||||
│ Full modellhistorikk, treningsdata │
|
||||
├──────────────────────────────────────────┤
|
||||
│ Lag 4: Cloud Sync (Azure Blob) │
|
||||
│ Synk ved tilkobling │
|
||||
│ Master-kopi av modeller og data │
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Synkroniseringslogikk
|
||||
|
||||
```python
|
||||
# Intelligent sync-manager
|
||||
class SyncManager:
|
||||
def __init__(self, config):
|
||||
self.local_store = LocalModelStore(config.local_path)
|
||||
self.cloud_store = AzureBlobStore(config.connection_string)
|
||||
self.sync_log = SyncLog(config.log_path)
|
||||
|
||||
async def check_connectivity(self):
|
||||
"""Sjekk om skytilkobling er tilgjengelig."""
|
||||
try:
|
||||
await self.cloud_store.ping()
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def sync_models(self):
|
||||
"""Synkroniser modeller mellom lokal og sky."""
|
||||
if not await self.check_connectivity():
|
||||
return SyncResult(status="offline", synced=0)
|
||||
|
||||
# Hent manifest fra sky
|
||||
cloud_manifest = await self.cloud_store.get_manifest()
|
||||
local_manifest = self.local_store.get_manifest()
|
||||
|
||||
updates = []
|
||||
for model_id, cloud_info in cloud_manifest.items():
|
||||
local_info = local_manifest.get(model_id)
|
||||
|
||||
if not local_info:
|
||||
# Ny modell — last ned
|
||||
updates.append(("download", model_id, cloud_info))
|
||||
elif cloud_info['version'] > local_info['version']:
|
||||
# Oppdatert modell — last ned ny versjon
|
||||
updates.append(("update", model_id, cloud_info))
|
||||
|
||||
# Utfor oppdateringer med prioritering
|
||||
for action, model_id, info in sorted(
|
||||
updates, key=lambda x: x[2].get('priority', 99)
|
||||
):
|
||||
try:
|
||||
await self._download_model(model_id, info)
|
||||
self.sync_log.record(action, model_id, "success")
|
||||
except Exception as e:
|
||||
self.sync_log.record(action, model_id, f"failed: {e}")
|
||||
|
||||
# Last opp lokale resultater
|
||||
await self._upload_offline_results()
|
||||
|
||||
return SyncResult(
|
||||
status="synced",
|
||||
synced=len(updates)
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fallback Inference Patterns
|
||||
|
||||
### Degraderingsstrategier
|
||||
|
||||
| Strategi | Nar | Implementasjon |
|
||||
|----------|-----|---------------|
|
||||
| Full model → Lite model | GPU svikter | Fall tilbake til CPU-modell |
|
||||
| Cloud model → Edge model | Nettverk nede | Bruk lokal kvantisert modell |
|
||||
| ML-modell → Regler | Modell korrupt | Regelbasert fallback |
|
||||
| Real-time → Batch | Overbelastning | Buffer foresporsler |
|
||||
| AI → Manuell | Alt feiler | Eskalering til menneske |
|
||||
|
||||
### Implementasjon av fallback-kaskade
|
||||
|
||||
```python
|
||||
class ResilientInferenceEngine:
|
||||
def __init__(self):
|
||||
self.engines = [
|
||||
CloudInference(endpoint="https://foundry.azure.com"),
|
||||
LocalGPUInference(model_path="/models/full-model.onnx"),
|
||||
LocalCPUInference(model_path="/models/quantized-int8.onnx"),
|
||||
RuleBasedFallback(rules_path="/config/rules.json")
|
||||
]
|
||||
|
||||
async def infer(self, input_data, timeout=5.0):
|
||||
"""Prover inferensmotorer i prioritetsrekkefoolge."""
|
||||
last_error = None
|
||||
|
||||
for engine in self.engines:
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
engine.predict(input_data),
|
||||
timeout=timeout
|
||||
)
|
||||
return InferenceResult(
|
||||
prediction=result,
|
||||
engine=engine.name,
|
||||
confidence=engine.confidence_level,
|
||||
degraded=(engine != self.engines[0])
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
last_error = f"{engine.name}: timeout"
|
||||
timeout = min(timeout * 2, 30) # Okt timeout for neste
|
||||
except Exception as e:
|
||||
last_error = f"{engine.name}: {e}"
|
||||
continue
|
||||
|
||||
# Alle motorer feilet — returner fallback
|
||||
return InferenceResult(
|
||||
prediction=None,
|
||||
engine="none",
|
||||
confidence=0,
|
||||
degraded=True,
|
||||
error=last_error
|
||||
)
|
||||
```
|
||||
|
||||
### Health monitoring for offline-systemer
|
||||
|
||||
```yaml
|
||||
# health-check-config.yaml
|
||||
health_checks:
|
||||
model_health:
|
||||
interval: 60s
|
||||
checks:
|
||||
- name: model_loaded
|
||||
type: inference_test
|
||||
input: "test_input.json"
|
||||
expected_output_shape: [1, 10]
|
||||
- name: gpu_available
|
||||
type: nvidia_smi
|
||||
min_free_memory_mb: 1024
|
||||
- name: disk_space
|
||||
type: disk
|
||||
min_free_gb: 10
|
||||
|
||||
degradation_rules:
|
||||
- condition: "gpu_available == false"
|
||||
action: "switch_to_cpu_model"
|
||||
- condition: "disk_space < 5GB"
|
||||
action: "cleanup_old_models"
|
||||
- condition: "model_loaded == false"
|
||||
action: "reload_from_cache"
|
||||
max_retries: 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Scenarioer for norsk offentlig sektor
|
||||
|
||||
### Forsvar og beredskap
|
||||
|
||||
| Scenario | Tilkoblingsstatus | Losning |
|
||||
|----------|-------------------|---------|
|
||||
| Feltoperasjoner | Helt frakoblet | ONNX Runtime + kvantiserte modeller |
|
||||
| Kjoretoy/fartoy | Periodisk | Store-and-forward + modellsynk |
|
||||
| Kommandoplass | Begrenset | Azure Stack Edge disconnected |
|
||||
| Sambandssystemer | Ustabil | Fallback-kaskade med degradering |
|
||||
|
||||
### Helse
|
||||
|
||||
| Scenario | Tilkoblingsstatus | Losning |
|
||||
|----------|-------------------|---------|
|
||||
| Ambulanse | Ustabil | Lokal bildeanalyse (ONNX) |
|
||||
| Distriktslege | Periodisk | Disconnected containers (tale, tekst) |
|
||||
| Sykehus DR | Beredskap | Azure Local med offline-kapasitet |
|
||||
| Feltsykehus | Frakoblet | Forhndslastede modeller |
|
||||
|
||||
### Transport og infrastruktur
|
||||
|
||||
| Scenario | Tilkoblingsstatus | Losning |
|
||||
|----------|-------------------|---------|
|
||||
| Tunneler | Frakoblet | Edge-inferens med kamerasystem |
|
||||
| Fartsoyvervaking | Ustabil | Lokal objektdeteksjon |
|
||||
| Trafikkanalyse | Periodisk | Batch-analyse med synk |
|
||||
| Fergedrift | Variabel | Hybrid med sky-fallback |
|
||||
|
||||
---
|
||||
|
||||
## Lisens- og kostnadshensyn
|
||||
|
||||
### Disconnected containers prismodell
|
||||
|
||||
| Prismodell | Beskrivelse | Krav |
|
||||
|-----------|-------------|------|
|
||||
| Commitment tier | Arlig forpliktelse | Enterprise Agreement |
|
||||
| Per-tjeneste | Betal per container-tjeneste | Godkjent soknad |
|
||||
| Kalenderars-binding | 12 mnd minimum | Automatisk fornyelse |
|
||||
|
||||
### Viktige begrensninger
|
||||
|
||||
- Lisensfil har utlopsdato — krever periodisk fornyelse
|
||||
- Enterprise Agreement eller tilsvarende er obligatorisk
|
||||
- Godkjenningsprosess tar opptil 10 virkedager
|
||||
- Ingen SLA for disconnected containers (kunde eier infrastruktur)
|
||||
- Ikke tilgjengelig i sovereign clouds (kun public cloud for opprettelse)
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Azure tilbyr et komplett spekter for frakoblet AI** — fra Foundry Tools disconnected containers (tale, tekst, bilde) til helt lokale ONNX Runtime-modeller uten skyavhengighet.
|
||||
- **Disconnected containers krever Enterprise Agreement og godkjenning** — lisensfiler har utlopsdato og ma fornyes, men gir tilgang til de samme API-ene som sky-tjenestene.
|
||||
- **Fallback-kaskader er essensielt for paalitelig edge-AI** — design alltid med degraderingsstrategi: sky → lokal GPU → lokal CPU → regler → manuell.
|
||||
- **Store-and-forward med rekonsilieringslogikk** loser utfordringen med data som genereres offline — buffer lokalt, synkroniser ved tilkobling, hndter konflikter.
|
||||
- **For norsk offentlig sektor er frakoblet AI kritisk for beredskap, forsvar og helse** — Azure Stack Edge og ONNX Runtime gir funksjonskapasitet uten internett-avhengighet.
|
||||
|
|
@ -0,0 +1,482 @@
|
|||
# Edge AI Inferencing Patterns
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Edge AI-inferens handler om a kjore maskinlaeringsmodeller naermest mulig der data oppstar — pa enheter, gateways, lokale servere eller Azure Local-klynger. For norsk offentlig sektor er dette relevant i scenarioer som sanntids videoanalyse, dokumentbehandling i felt, naturspraakbehandling offline, og autonome systemer i omrader med begrenset nettverkstilkobling.
|
||||
|
||||
Microsoft tilbyr et bredt spekter av verktoy for edge-inferens: ONNX Runtime som universell inferensmotor, Azure IoT Edge for container-baserte modeller, Azure Stack Edge for hardware-akselerert inferens, og KAITO for LLM-deployment pa Kubernetes. Valget av moenster avhenger av modellstorrelse, latenskrav, tilgjengelig hardware og tilkoblingsstatus.
|
||||
|
||||
Denne referansen dekker de viktigste moensterne for modelloptimalisering, akselerasjon, caching og batching/streaming — alle med fokus pa Microsoft Azure-okosystemet og relevans for offentlig sektor.
|
||||
|
||||
---
|
||||
|
||||
## Model Quantization og Compression
|
||||
|
||||
### Hva er kvantisering?
|
||||
|
||||
Kvantisering reduserer presisjonen til modellvekter fra hoyere til lavere bit-representasjoner, noe som reduserer modellstorrelse og oker inferenshastighet med minimalt noyaktighetstap.
|
||||
|
||||
| Presisjon | Bits | Storrelse (7B modell) | Hastighet | Noyaktighet |
|
||||
|-----------|------|----------------------|-----------|-------------|
|
||||
| FP32 | 32 | ~28 GB | Baseline | 100% |
|
||||
| FP16 | 16 | ~14 GB | 2x | ~99.9% |
|
||||
| BF16 | 16 | ~14 GB | 2x | ~99.9% |
|
||||
| INT8 | 8 | ~7 GB | 3-4x | ~99% |
|
||||
| INT4 | 4 | ~3.5 GB | 5-8x | ~97% |
|
||||
|
||||
### Kvantiseringsmetoder i Azure-okosystemet
|
||||
|
||||
| Metode | Verktoy | Best for | Presisjon |
|
||||
|--------|---------|----------|-----------|
|
||||
| Post-Training Quantization (PTQ) | ONNX Runtime, Olive | Rask konvertering | INT8/INT4 |
|
||||
| Quantization-Aware Training (QAT) | PyTorch + Azure ML | Hoyest noyaktighet | INT8 |
|
||||
| GPTQ | HuggingFace + KAITO | LLM-kvantisering | INT4 |
|
||||
| AWQ | HuggingFace + KAITO | LLM-kvantisering | INT4 |
|
||||
| Dynamic Quantization | ONNX Runtime | CPU-inferens | INT8 |
|
||||
|
||||
### ONNX Runtime-kvantisering
|
||||
|
||||
```python
|
||||
# Kvantiser ONNX-modell til INT8
|
||||
from onnxruntime.quantization import quantize_dynamic, QuantType
|
||||
|
||||
quantize_dynamic(
|
||||
model_input="model_fp32.onnx",
|
||||
model_output="model_int8.onnx",
|
||||
weight_type=QuantType.QInt8,
|
||||
optimize_model=True
|
||||
)
|
||||
```
|
||||
|
||||
### Olive — Microsofts modelloptimalisering
|
||||
|
||||
Olive er Microsofts verktoy for helhetlig modelloptimalisering:
|
||||
|
||||
```python
|
||||
# olive_config.json
|
||||
{
|
||||
"input_model": {
|
||||
"type": "OnnxModel",
|
||||
"model_path": "model.onnx"
|
||||
},
|
||||
"systems": {
|
||||
"local_system": {
|
||||
"type": "LocalSystem",
|
||||
"accelerators": [{"device": "npu"}]
|
||||
}
|
||||
},
|
||||
"passes": {
|
||||
"quantization": {
|
||||
"type": "OnnxQuantization",
|
||||
"config": {
|
||||
"quant_mode": "static",
|
||||
"quant_format": "QDQ",
|
||||
"calibration_data_reader": "CalibrationDataReader"
|
||||
}
|
||||
},
|
||||
"perf_tuning": {
|
||||
"type": "OrtPerfTuning",
|
||||
"config": {
|
||||
"data_dir": "./calibration_data"
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"search_strategy": {
|
||||
"execution_order": "joint",
|
||||
"search_algorithm": "exhaustive"
|
||||
},
|
||||
"output_dir": "./optimized"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Modellkomprimering
|
||||
|
||||
| Teknikk | Beskrivelse | Storrelsereduksjon | Noyaktighetstap |
|
||||
|---------|-------------|-------------------|-----------------|
|
||||
| Pruning | Fjerner uvesentlige vekter | 30-70% | 1-3% |
|
||||
| Knowledge Distillation | Laerer liten modell fra stor | 50-90% | 2-5% |
|
||||
| Weight Sharing | Deler vekter mellom lag | 20-40% | <1% |
|
||||
| Low-Rank Factorization | Dekomponerer vektmatriser | 30-50% | 1-2% |
|
||||
|
||||
---
|
||||
|
||||
## Real-time Inference Acceleration
|
||||
|
||||
### ONNX Runtime Execution Providers
|
||||
|
||||
ONNX Runtime stotter flere hardware-akseleratorer gjennom Execution Providers (EP):
|
||||
|
||||
| Execution Provider | Hardware | Best for | Latens |
|
||||
|--------------------|----------|----------|--------|
|
||||
| CPU EP | x86/ARM CPU | Universell | Basis |
|
||||
| CUDA EP | NVIDIA GPU | GPU-inferens | 5-50x raskere |
|
||||
| TensorRT EP | NVIDIA GPU | Optimalisert GPU | 10-100x raskere |
|
||||
| OpenVINO EP | Intel CPU/GPU/VPU | Intel-optimalisert | 3-20x raskere |
|
||||
| DirectML EP | Windows GPU/NPU | Windows-enheter | 5-30x raskere |
|
||||
| QNN EP | Qualcomm NPU | Snapdragon-enheter | 10-50x raskere |
|
||||
|
||||
### GPU-akselerert inferens med ONNX Runtime
|
||||
|
||||
```python
|
||||
import onnxruntime as ort
|
||||
|
||||
# Konfigurasjon for NVIDIA GPU (TensorRT)
|
||||
session_options = ort.SessionOptions()
|
||||
session_options.graph_optimization_level = \
|
||||
ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||
|
||||
providers = [
|
||||
('TensorrtExecutionProvider', {
|
||||
'trt_max_workspace_size': 2147483648, # 2 GB
|
||||
'trt_fp16_enable': True,
|
||||
'trt_engine_cache_enable': True,
|
||||
'trt_engine_cache_path': './trt_cache'
|
||||
}),
|
||||
('CUDAExecutionProvider', {
|
||||
'device_id': 0,
|
||||
'arena_extend_strategy': 'kSameAsRequested',
|
||||
'gpu_mem_limit': 4 * 1024 * 1024 * 1024, # 4 GB
|
||||
'cudnn_conv_algo_search': 'DEFAULT'
|
||||
}),
|
||||
'CPUExecutionProvider'
|
||||
]
|
||||
|
||||
session = ort.InferenceSession(
|
||||
"model_fp16.onnx",
|
||||
sess_options=session_options,
|
||||
providers=providers
|
||||
)
|
||||
|
||||
# Inferens
|
||||
result = session.run(None, {"input": input_data})
|
||||
```
|
||||
|
||||
### OpenVINO Model Server (OVMS) pa Edge
|
||||
|
||||
For Azure Arc/Azure Local-miljoer der Intel-hardware brukes:
|
||||
|
||||
```yaml
|
||||
# ovms-deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ovms-inference
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: ovms
|
||||
image: openvino/model_server:latest
|
||||
ports:
|
||||
- containerPort: 9000 # gRPC
|
||||
- containerPort: 8000 # REST
|
||||
volumeMounts:
|
||||
- name: model-store
|
||||
mountPath: /models
|
||||
env:
|
||||
- name: MODELS_CONFIG
|
||||
value: "/models/config.json"
|
||||
resources:
|
||||
limits:
|
||||
cpu: "4"
|
||||
memory: "8Gi"
|
||||
volumes:
|
||||
- name: model-store
|
||||
persistentVolumeClaim:
|
||||
claimName: model-pvc
|
||||
```
|
||||
|
||||
### vLLM for LLM-inferens
|
||||
|
||||
KAITO bruker vLLM som standard inferensmotor for LLM-er:
|
||||
|
||||
| Funksjon | Beskrivelse | Fordel |
|
||||
|----------|-------------|--------|
|
||||
| PagedAttention | Effektiv KV-cache-haandtering | 2-4x gjennomstromning |
|
||||
| Continuous batching | Dynamisk batching av foresporsler | Redusert latens |
|
||||
| Tensor parallelism | Fordel modell over GPUer | Storre modeller |
|
||||
| Quantization support | AWQ, GPTQ, SqueezeLLM | Lavere minnebruk |
|
||||
| OpenAI-compatible API | Standard API-format | Enkel integrasjon |
|
||||
|
||||
---
|
||||
|
||||
## Caching Patterns for Edge
|
||||
|
||||
### Modellcaching-strategier
|
||||
|
||||
| Strategi | Implementasjon | Bruksomrade |
|
||||
|----------|---------------|-------------|
|
||||
| Model preloading | Last modell i minne ved oppstart | Sanntids inferens |
|
||||
| TensorRT engine cache | Cach kompilerte TRT-motorer | GPU-inferens |
|
||||
| ONNX session cache | Gjenbruk ORT-sesjoner | Repetitive inferenser |
|
||||
| Result caching | Redis/memcached for resultater | Identiske inputs |
|
||||
| Embedding cache | Cach vektorrepresentasjoner | RAG pa edge |
|
||||
|
||||
### Resultatchaching med Redis pa Edge
|
||||
|
||||
```yaml
|
||||
# redis-cache-deployment.yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: inference-cache
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
resources:
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
args:
|
||||
- "--maxmemory"
|
||||
- "1.5gb"
|
||||
- "--maxmemory-policy"
|
||||
- "allkeys-lru"
|
||||
```
|
||||
|
||||
```python
|
||||
# Inferens med caching
|
||||
import redis
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
cache = redis.Redis(host='inference-cache', port=6379)
|
||||
|
||||
def cached_inference(model_session, input_data, ttl=3600):
|
||||
# Generer cache-nokkel fra input
|
||||
cache_key = hashlib.sha256(
|
||||
json.dumps(input_data, sort_keys=True).encode()
|
||||
).hexdigest()
|
||||
|
||||
# Sjekk cache
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
return json.loads(cached)
|
||||
|
||||
# Kjor inferens
|
||||
result = model_session.run(None, input_data)
|
||||
|
||||
# Lagre i cache
|
||||
cache.setex(cache_key, ttl, json.dumps(result.tolist()))
|
||||
return result
|
||||
```
|
||||
|
||||
### KV-cache for LLM-er
|
||||
|
||||
For LLM-inferens pa edge er KV-cache (key-value cache) kritisk:
|
||||
|
||||
| Teknikk | Beskrivelse | Minnebesparelse |
|
||||
|---------|-------------|-----------------|
|
||||
| Standard KV-cache | Full cache for alle tokens | Baseline |
|
||||
| Sliding window | Begrens cache til siste N tokens | 50-80% |
|
||||
| Grouped-query attention | Faerre KV-hoder | 4-8x |
|
||||
| PagedAttention (vLLM) | Sidert minnehaandtering | Dynamisk |
|
||||
|
||||
---
|
||||
|
||||
## Batching vs. Streaming Inference
|
||||
|
||||
### Sammenligning
|
||||
|
||||
| Egenskap | Batch Inference | Streaming Inference |
|
||||
|----------|----------------|---------------------|
|
||||
| Latens | Hoyere (venter pa batch) | Lavere (umiddelbar) |
|
||||
| Gjennomstromning | Hoyere (GPU-utnyttelse) | Lavere (per request) |
|
||||
| GPU-utnyttelse | Optimal (fyller batch) | Variabel |
|
||||
| Bruksomrade | Dokumentanalyse, batch-scoring | Chat, real-time |
|
||||
| Skalering | Horisontal (flere workers) | Vertikal (GPU-kapasitet) |
|
||||
|
||||
### Beslutningstre
|
||||
|
||||
```
|
||||
Trenger du sanntidssvar (<100ms)?
|
||||
├── Ja → Streaming inference
|
||||
│ ├── Enkelt request → Single request pipeline
|
||||
│ └── Flere samtidige → Continuous batching (vLLM)
|
||||
└── Nei → Batch inference
|
||||
├── <1000 elementer → Micro-batch pa GPU
|
||||
└── >1000 elementer → Azure ML Batch Endpoints
|
||||
```
|
||||
|
||||
### Batch Inference pa Edge
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
import threading
|
||||
import time
|
||||
|
||||
class EdgeBatchInferencer:
|
||||
def __init__(self, session, batch_size=8, max_wait_ms=50):
|
||||
self.session = session
|
||||
self.batch_size = batch_size
|
||||
self.max_wait = max_wait_ms / 1000
|
||||
self.queue = deque()
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def infer(self, input_data):
|
||||
"""Legg til i koe og vent pa batch-resultat."""
|
||||
event = threading.Event()
|
||||
result_container = {}
|
||||
|
||||
with self.lock:
|
||||
self.queue.append((input_data, event, result_container))
|
||||
|
||||
# Trigger batch hvis full
|
||||
if len(self.queue) >= self.batch_size:
|
||||
self._process_batch()
|
||||
|
||||
# Vent pa resultat (med timeout)
|
||||
event.wait(timeout=self.max_wait * 2)
|
||||
return result_container.get('result')
|
||||
|
||||
def _process_batch(self):
|
||||
"""Prosesser akkumulerte inputs som en batch."""
|
||||
with self.lock:
|
||||
items = []
|
||||
while self.queue and len(items) < self.batch_size:
|
||||
items.append(self.queue.popleft())
|
||||
|
||||
if not items:
|
||||
return
|
||||
|
||||
# Kombiner inputs til batch
|
||||
batch_input = np.stack([item[0] for item in items])
|
||||
|
||||
# Kjor batch-inferens
|
||||
batch_results = self.session.run(
|
||||
None, {"input": batch_input}
|
||||
)
|
||||
|
||||
# Distribuer resultater
|
||||
for i, (_, event, container) in enumerate(items):
|
||||
container['result'] = batch_results[0][i]
|
||||
event.set()
|
||||
```
|
||||
|
||||
### Streaming Inference for LLM pa Edge
|
||||
|
||||
```python
|
||||
# Streaming med vLLM-kompatibelt API (KAITO)
|
||||
import requests
|
||||
import json
|
||||
|
||||
def stream_inference(prompt, service_ip, max_tokens=500):
|
||||
"""Stream tokens fra lokal LLM pa edge."""
|
||||
response = requests.post(
|
||||
f"http://{service_ip}/v1/completions",
|
||||
json={
|
||||
"model": "phi-4-mini-instruct",
|
||||
"prompt": prompt,
|
||||
"max_tokens": max_tokens,
|
||||
"stream": True
|
||||
},
|
||||
stream=True
|
||||
)
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
data = line.decode('utf-8')
|
||||
if data.startswith('data: '):
|
||||
chunk = json.loads(data[6:])
|
||||
if chunk.get('choices'):
|
||||
token = chunk['choices'][0].get('text', '')
|
||||
yield token
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## IoT Edge ML Inference Pattern
|
||||
|
||||
### Azure IoT Edge med dynamisk modellasting
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Azure Cloud │
|
||||
│ ┌──────────┐ ┌──────────────────┐ │
|
||||
│ │ Blob │ │ IoT Hub │ │
|
||||
│ │ Storage │ │ (Device Twins) │ │
|
||||
│ │ (Models) │ │ │ │
|
||||
│ └────┬─────┘ └────────┬─────────┘ │
|
||||
└───────┼───────────────────┼─────────────┘
|
||||
│ │
|
||||
│ ┌───────────────▼──────────┐
|
||||
│ │ IoT Edge Runtime │
|
||||
│ │ ┌─────────────────────┐ │
|
||||
└───┼─►│ Model Loader Module │ │
|
||||
│ └──────────┬──────────┘ │
|
||||
│ ┌──────────▼──────────┐ │
|
||||
│ │ Inference Module │ │
|
||||
│ │ (ONNX/LiteRT) │ │
|
||||
│ └──────────┬──────────┘ │
|
||||
│ ┌──────────▼──────────┐ │
|
||||
│ │ Local Storage │ │
|
||||
│ │ (Model Cache) │ │
|
||||
│ └─────────────────────┘ │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
### Device Twin-basert modellstyring
|
||||
|
||||
```python
|
||||
# Motta modelloppdatering via IoT Edge Device Twin
|
||||
from azure.iot.device import IoTHubModuleClient
|
||||
|
||||
async def twin_update_handler(patch):
|
||||
if 'model_version' in patch:
|
||||
model_url = patch['model_url']
|
||||
model_version = patch['model_version']
|
||||
checksum = patch['model_checksum']
|
||||
|
||||
# Last ned ny modell
|
||||
await download_model(model_url, checksum)
|
||||
|
||||
# Hot-swap modell uten nedetid
|
||||
await reload_model(model_version)
|
||||
|
||||
# Rapporter tilbake
|
||||
reported = {
|
||||
"active_model_version": model_version,
|
||||
"model_loaded_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
client.patch_twin_reported_properties(reported)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ytelsesreferanser
|
||||
|
||||
### Typiske inferenstider pa Microsoft edge-hardware
|
||||
|
||||
| Hardware | Modell | Oppgave | Latens (ms) |
|
||||
|----------|--------|---------|-------------|
|
||||
| Azure Stack Edge Pro GPU (T4) | YOLOv8 | Objektdeteksjon | 8-15 |
|
||||
| Azure Stack Edge Pro GPU (T4) | ResNet-50 | Bildeklassifisering | 3-5 |
|
||||
| Azure Local (A2) | Phi-3-mini-4k | Tekst (128 tokens) | 200-400 |
|
||||
| Azure Local (L4) | Phi-4-mini | Tekst (128 tokens) | 80-150 |
|
||||
| Azure Local (L40S) | Mistral-7B | Tekst (128 tokens) | 50-100 |
|
||||
| Intel CPU (Xeon) + OpenVINO | BERT-base | NER | 5-10 |
|
||||
| CPU (ONNX Runtime) | DistilBERT | Sentiment | 3-8 |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **ONNX Runtime er universalmotoren for edge-inferens** — stotter CPU, GPU, NPU via Execution Providers, med TensorRT for NVIDIA og OpenVINO for Intel som de viktigste akseleratorene.
|
||||
- **Kvantisering (INT8/INT4) er den enkleste og mest effektive optimaliseringen** — reduserer modellstorrelse 2-8x med minimalt noyaktighetstap, spesielt viktig for edge-enheter med begrenset minne.
|
||||
- **Velg batching for gjennomstromning, streaming for latens** — continuous batching (vLLM/KAITO) gir det beste av begge for LLM-inferens pa edge Kubernetes-klynger.
|
||||
- **Caching pa flere nivaer er essensielt for edge-ytelse** — TensorRT engine cache, resultat-cache (Redis), og KV-cache for LLM-er reduserer bade latens og GPU-belastning.
|
||||
- **IoT Edge med Device Twins gir skalerbar modellstyring** for distribuerte edge-enheter — modellversjonering, inkrementell oppdatering og hot-swap uten nedetid.
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
# Edge-to-Cloud Data Synchronization
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Palitelig datasynkronisering mellom edge og sky er en av de mest komplekse utfordringene i hybrid AI-arkitekturer. Data ma flyte i begge retninger — sensordata og inferensresultater fra edge til sky for langsiktig analyse og modelltrening, og oppdaterte modeller og konfigurasjoner fra sky tilbake til edge. Alt dette ma handtere nettverksavbrudd, konflikter og dataintegritet.
|
||||
|
||||
For norsk offentlig sektor er palitelig synkronisering kritisk: inspeksjonsdata fra felt ma garantert na sentrale systemer, AI-modellsoppdateringer ma distribueres til alle edge-stasjoner uten manuell intervensjon, og logging for compliance-formal ma vaere komplett — selv etter langvarige offline-perioder.
|
||||
|
||||
Microsoft tilbyr flere synkroniseringsmekanismer: Azure IoT Edge med utvidet offline-stoette (ubegrenset offline-tid med lokal buffring), Azure Container Storage med automatisk sky-sync, Azure Cosmos DB med multi-region replikering, og Azure IoT Hub device twins for konfigurasjonssynkronisering. Valget avhenger av datamengde, konsistenskrav og tilkoblingsprofil.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Azure IoT Edge | Utvidet offline med meldingsbuffring | Container runtime |
|
||||
| Azure Container Storage | Lokal lagring med automatisk sky-sync | Arc-enabled |
|
||||
| Azure Cosmos DB | Multi-region database med konflikthondtering | NoSQL / SQL API |
|
||||
| IoT Hub Device Twins | Konfigurasjonssynkronisering enhet-sky | PaaS |
|
||||
| Event Hub | Hoeyvolum event-inntak | Event streaming |
|
||||
| Azure Data Lake | Langsiktig datalagring | Storage Gen2 |
|
||||
| Delta Lake | ACID-transaksjoner pa datalake | Open source |
|
||||
|
||||
---
|
||||
|
||||
## Eventual Consistency Patterns
|
||||
|
||||
### Konsistensmodeller for edge-sky
|
||||
|
||||
| Modell | Beskrivelse | Latens | Brukstilfelle |
|
||||
|--------|-------------|--------|---------------|
|
||||
| Sterk konsistens | Alle noder ser samme data samtidig | Hoey | Kritiske transaksjoner |
|
||||
| Bounded staleness | Data er konsistent innen et tidsvindu | Medium | Nesten-sanntid dashboards |
|
||||
| Session konsistens | Konsistens innen en enhet-session | Lav | Brukerinteraksjon |
|
||||
| Eventuell konsistens | Data konvergerer over tid | Lavest | Sensordata, logger |
|
||||
|
||||
### Azure Cosmos DB for edge-sky-synkronisering
|
||||
|
||||
```python
|
||||
# Azure Cosmos DB med konfigurerbar konsistens for edge-sky sync
|
||||
from azure.cosmos import CosmosClient, PartitionKey
|
||||
from azure.cosmos.documents import ConsistencyLevel
|
||||
|
||||
class EdgeCloudSyncStore:
|
||||
def __init__(self, endpoint: str, key: str):
|
||||
self.client = CosmosClient(
|
||||
endpoint, key,
|
||||
consistency_level=ConsistencyLevel.Session # Bra for edge-sky
|
||||
)
|
||||
self.database = self.client.get_database_client("edge-ai-data")
|
||||
|
||||
def setup_containers(self):
|
||||
"""Opprett containere for synkronisert data"""
|
||||
|
||||
# Sensordata: Eventuell konsistens, hoey throughput
|
||||
self.sensor_container = self.database.create_container_if_not_exists(
|
||||
id="sensor-data",
|
||||
partition_key=PartitionKey(path="/deviceId"),
|
||||
default_ttl=86400 * 30 # 30 dagers retention
|
||||
)
|
||||
|
||||
# AI-resultater: Session konsistens
|
||||
self.ai_results = self.database.create_container_if_not_exists(
|
||||
id="ai-results",
|
||||
partition_key=PartitionKey(path="/deviceId"),
|
||||
default_ttl=86400 * 365 # 1 aars retention
|
||||
)
|
||||
|
||||
# Modellkonfigurasjon: Sterkere konsistens
|
||||
self.model_config = self.database.create_container_if_not_exists(
|
||||
id="model-config",
|
||||
partition_key=PartitionKey(path="/region")
|
||||
)
|
||||
|
||||
def upsert_sensor_data(self, device_id: str, readings: list[dict]):
|
||||
"""Skriv sensordata med idempotensnokkel for a haandtere re-sync"""
|
||||
for reading in readings:
|
||||
reading["id"] = f"{device_id}_{reading['timestamp']}"
|
||||
reading["deviceId"] = device_id
|
||||
reading["_etag"] = None # Cosmos DB haandterer versjonering
|
||||
|
||||
self.sensor_container.upsert_item(
|
||||
body=reading,
|
||||
pre_trigger_include=None,
|
||||
post_trigger_include=None
|
||||
)
|
||||
|
||||
def get_latest_model_config(self, region: str) -> dict:
|
||||
"""Hent siste modellkonfigurasjon for en region"""
|
||||
query = """
|
||||
SELECT TOP 1 *
|
||||
FROM c
|
||||
WHERE c.region = @region
|
||||
ORDER BY c.updatedAt DESC
|
||||
"""
|
||||
items = list(self.model_config.query_items(
|
||||
query=query,
|
||||
parameters=[{"name": "@region", "value": region}],
|
||||
enable_cross_partition_query=False
|
||||
))
|
||||
return items[0] if items else None
|
||||
```
|
||||
|
||||
### Event-basert synkronisering
|
||||
|
||||
```python
|
||||
# Event-basert edge-to-cloud synkronisering
|
||||
import asyncio
|
||||
import json
|
||||
import gzip
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class EventBasedSync:
|
||||
def __init__(self, local_store, cloud_endpoint: str):
|
||||
self.local_store = local_store
|
||||
self.cloud_endpoint = cloud_endpoint
|
||||
self.sync_log = []
|
||||
self.last_sync = None
|
||||
|
||||
async def sync_outbound(self, max_batch_size: int = 100) -> dict:
|
||||
"""Synkroniser lokale hendelser til sky"""
|
||||
# Hent usynkroniserte hendelser
|
||||
pending = self.local_store.get_unsynced_events(limit=max_batch_size)
|
||||
|
||||
if not pending:
|
||||
return {"status": "up_to_date", "synced": 0}
|
||||
|
||||
# Komprimer for overfoering
|
||||
payload = gzip.compress(
|
||||
json.dumps([e.__dict__ for e in pending]).encode()
|
||||
)
|
||||
|
||||
try:
|
||||
# Send til sky-endpoint
|
||||
response = await self._send_to_cloud(payload)
|
||||
|
||||
if response["status"] == "accepted":
|
||||
# Marker som synkronisert
|
||||
event_ids = [e.id for e in pending]
|
||||
self.local_store.mark_synced(event_ids)
|
||||
|
||||
self.last_sync = datetime.utcnow()
|
||||
self.sync_log.append({
|
||||
"direction": "outbound",
|
||||
"events": len(event_ids),
|
||||
"size_bytes": len(payload),
|
||||
"timestamp": self.last_sync.isoformat()
|
||||
})
|
||||
|
||||
return {
|
||||
"status": "synced",
|
||||
"synced": len(event_ids),
|
||||
"remaining": self.local_store.count_unsynced(),
|
||||
"compressed_size": len(payload)
|
||||
}
|
||||
|
||||
except ConnectionError:
|
||||
return {
|
||||
"status": "offline",
|
||||
"pending": len(pending),
|
||||
"retry_after": "next_connectivity"
|
||||
}
|
||||
|
||||
async def sync_inbound(self) -> dict:
|
||||
"""Hent oppdateringer fra sky (modeller, konfigurasjon)"""
|
||||
try:
|
||||
since = self.last_sync or datetime.utcnow() - timedelta(days=7)
|
||||
updates = await self._fetch_from_cloud(since)
|
||||
|
||||
applied = 0
|
||||
for update in updates:
|
||||
if update["type"] == "model_update":
|
||||
await self._apply_model_update(update)
|
||||
applied += 1
|
||||
elif update["type"] == "config_change":
|
||||
await self._apply_config_change(update)
|
||||
applied += 1
|
||||
|
||||
return {"status": "updated", "applied": applied}
|
||||
|
||||
except ConnectionError:
|
||||
return {"status": "offline", "using_cached": True}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Delta Sync Optimization
|
||||
|
||||
### Inkrementell synkronisering
|
||||
|
||||
```python
|
||||
# Delta-synkronisering for effektiv dataoverfoering
|
||||
import hashlib
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
class DeltaSyncEngine:
|
||||
def __init__(self):
|
||||
self.local_checksums: dict[str, str] = {}
|
||||
self.sync_watermark: Optional[str] = None
|
||||
|
||||
def calculate_delta(self, current_data: dict,
|
||||
last_synced_data: dict) -> dict:
|
||||
"""Beregn delta mellom navaerende og sist synkronisert tilstand"""
|
||||
delta = {
|
||||
"added": {},
|
||||
"modified": {},
|
||||
"deleted": []
|
||||
}
|
||||
|
||||
# Finn nye og endrede elementer
|
||||
for key, value in current_data.items():
|
||||
current_hash = self._hash_value(value)
|
||||
if key not in last_synced_data:
|
||||
delta["added"][key] = value
|
||||
elif self._hash_value(last_synced_data[key]) != current_hash:
|
||||
delta["modified"][key] = value
|
||||
|
||||
# Finn slettede elementer
|
||||
for key in last_synced_data:
|
||||
if key not in current_data:
|
||||
delta["deleted"].append(key)
|
||||
|
||||
return delta
|
||||
|
||||
def apply_delta(self, base_data: dict, delta: dict) -> dict:
|
||||
"""Anvend delta pa basisdatasettet"""
|
||||
result = dict(base_data)
|
||||
|
||||
# Legg til nye
|
||||
result.update(delta.get("added", {}))
|
||||
|
||||
# Oppdater endrede
|
||||
result.update(delta.get("modified", {}))
|
||||
|
||||
# Fjern slettede
|
||||
for key in delta.get("deleted", []):
|
||||
result.pop(key, None)
|
||||
|
||||
return result
|
||||
|
||||
def compress_delta(self, delta: dict) -> bytes:
|
||||
"""Komprimer delta for overfoering"""
|
||||
import gzip
|
||||
json_bytes = json.dumps(delta, separators=(',', ':')).encode()
|
||||
compressed = gzip.compress(json_bytes, compresslevel=9)
|
||||
|
||||
return compressed
|
||||
|
||||
def get_sync_stats(self, delta: dict, compressed: bytes) -> dict:
|
||||
"""Beregn synkroniseringsstatistikk"""
|
||||
full_size = len(json.dumps(delta).encode())
|
||||
return {
|
||||
"items_changed": (
|
||||
len(delta.get("added", {})) +
|
||||
len(delta.get("modified", {})) +
|
||||
len(delta.get("deleted", []))
|
||||
),
|
||||
"full_size_bytes": full_size,
|
||||
"compressed_size_bytes": len(compressed),
|
||||
"compression_ratio": f"{(1 - len(compressed)/full_size)*100:.1f}%"
|
||||
if full_size > 0 else "N/A"
|
||||
}
|
||||
|
||||
def _hash_value(self, value) -> str:
|
||||
return hashlib.sha256(json.dumps(value, sort_keys=True).encode()).hexdigest()[:16]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conflict Resolution Strategies
|
||||
|
||||
### Konflikttyper i edge-sky-synkronisering
|
||||
|
||||
| Konflikttype | Arsak | Losningsstrategi |
|
||||
|-------------|-------|-----------------|
|
||||
| Write-Write | Samme data endret pa bade edge og sky | LWW eller custom merge |
|
||||
| Delete-Update | Data slettet pa en side, oppdatert pa annen | Policy-basert (behold eller slett) |
|
||||
| Schema-conflict | Modellversjon ulik pa edge og sky | Versjonert schema med migrasjon |
|
||||
| Ordering-conflict | Hendelser mottat i feil rekkefolge | Timestamp-basert reordering |
|
||||
|
||||
### Cosmos DB konflikthondtering
|
||||
|
||||
```python
|
||||
# Cosmos DB konflikthondterings-policy
|
||||
from azure.cosmos import ContainerProxy
|
||||
|
||||
class CosmosConflictHandler:
|
||||
def __init__(self, container: ContainerProxy):
|
||||
self.container = container
|
||||
|
||||
def setup_lww_policy(self):
|
||||
"""Last-Write-Wins basert pa egendefinert felt"""
|
||||
# Konfigureres ved container-oppretting
|
||||
# Cosmos DB bruker _ts (timestamp) som default
|
||||
pass
|
||||
|
||||
def setup_custom_resolution(self):
|
||||
"""Custom konflikthondtering med stored procedure"""
|
||||
sproc_body = """
|
||||
function resolve(incomingItem, existingItem, isTombstone, conflictingItems) {
|
||||
// For AI-resultater: Behold den med hoeyest confidence
|
||||
if (incomingItem.ai_confidence > existingItem.ai_confidence) {
|
||||
return incomingItem;
|
||||
}
|
||||
return existingItem;
|
||||
}
|
||||
"""
|
||||
self.container.scripts.create_stored_procedure({
|
||||
"id": "resolveConflict",
|
||||
"body": sproc_body
|
||||
})
|
||||
|
||||
def read_conflict_feed(self) -> list[dict]:
|
||||
"""Les konflikter som krever manuell losning"""
|
||||
conflicts = list(self.container.list_conflicts())
|
||||
return [{
|
||||
"id": c["id"],
|
||||
"resource_id": c.get("resourceId"),
|
||||
"conflict_type": c.get("operationType"),
|
||||
"source_region": c.get("sourceResourceId")
|
||||
} for c in conflicts]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Deduplication at Scale
|
||||
|
||||
### Dedupliseringsstrategier
|
||||
|
||||
```python
|
||||
# Skalerbar deduplisering for edge-sky-data
|
||||
import hashlib
|
||||
from bloom_filter2 import BloomFilter
|
||||
|
||||
class EdgeDeduplication:
|
||||
def __init__(self, expected_items: int = 1_000_000):
|
||||
# Bloom-filter for hurtig duplikat-sjekk (minneeffektivt)
|
||||
self.bloom = BloomFilter(
|
||||
max_elements=expected_items,
|
||||
error_rate=0.01 # 1% falsk-positiv rate
|
||||
)
|
||||
# Eksakt sjekk for bloom-positive
|
||||
self.recent_hashes: set = set()
|
||||
self.max_recent = 100_000
|
||||
|
||||
def is_duplicate(self, data: dict) -> bool:
|
||||
"""Sjekk om dataelementet allerede er prosessert"""
|
||||
data_hash = self._compute_hash(data)
|
||||
|
||||
# Hurtig bloom-filter-sjekk
|
||||
if data_hash not in self.bloom:
|
||||
return False
|
||||
|
||||
# Eksakt sjekk for bekreftelse
|
||||
return data_hash in self.recent_hashes
|
||||
|
||||
def mark_processed(self, data: dict):
|
||||
"""Marker dataelement som prosessert"""
|
||||
data_hash = self._compute_hash(data)
|
||||
self.bloom.add(data_hash)
|
||||
self.recent_hashes.add(data_hash)
|
||||
|
||||
# Begrens minnebruk
|
||||
if len(self.recent_hashes) > self.max_recent:
|
||||
# Fjern eldste 20%
|
||||
to_remove = len(self.recent_hashes) - int(self.max_recent * 0.8)
|
||||
for _ in range(to_remove):
|
||||
self.recent_hashes.pop()
|
||||
|
||||
def _compute_hash(self, data: dict) -> str:
|
||||
"""Beregn deterministisk hash av dataelementet"""
|
||||
# Bruk innholds-hash (ekskluder metadata som timestamp)
|
||||
content_keys = sorted(k for k in data.keys()
|
||||
if k not in ("_ts", "synced_at", "sync_id"))
|
||||
content = {k: data[k] for k in content_keys}
|
||||
return hashlib.sha256(
|
||||
json.dumps(content, sort_keys=True).encode()
|
||||
).hexdigest()
|
||||
|
||||
def get_stats(self) -> dict:
|
||||
return {
|
||||
"bloom_filter_items": len(self.bloom),
|
||||
"recent_exact_items": len(self.recent_hashes),
|
||||
"estimated_memory_mb": (
|
||||
self.bloom.bitarray.nbytes / 1024 / 1024 +
|
||||
len(self.recent_hashes) * 64 / 1024 / 1024
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Synkroniseringskrav for offentlig sektor
|
||||
|
||||
| Krav | Beskrivelse | Losning |
|
||||
|------|-------------|---------|
|
||||
| Dataintegritet | Ingen datatap ved offline/sync | Event sourcing + idempotens |
|
||||
| Sporbarhet | All synkronisering ma logges | Sync audit log |
|
||||
| Personvern | Sensitive data ma krypteres i transit | TLS 1.3 + end-to-end |
|
||||
| Compliance | 7 ars retention for visse datatyper | Immutable storage |
|
||||
| Konflikthondtering | Sporbar og deterministisk | Policy-basert med audit trail |
|
||||
|
||||
### Anbefalte Azure-tjenester per scenario
|
||||
|
||||
| Scenario | Primaer-tjeneste | Sekundaer | Konsistens |
|
||||
|----------|-----------------|-----------|------------|
|
||||
| IoT-sensordata | IoT Hub + Event Hub | Data Lake | Eventuell |
|
||||
| AI-resultater | Cosmos DB | Data Lake backup | Session |
|
||||
| Modellkonfig | IoT Hub Device Twin | Git (GitOps) | Sterk |
|
||||
| Inspeksjonsdata | Cosmos DB | Blob Storage | Bounded staleness |
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Hoeyvolum sensorer, enveis | IoT Hub → Event Hub → Data Lake | Skalerbart, rimelig |
|
||||
| Toveis med konfliktfare | Cosmos DB med session-konsistens | Innebygd konflikthondtering |
|
||||
| Kritisk data, null tap | Event sourcing + Cosmos DB | Idempotent, sporbar |
|
||||
| Periodisk bulk-sync | Delta sync + Azure Blob | Minimal bandwidth |
|
||||
| Multi-edge koordinering | Cosmos DB multi-write | Automatisk konflikthondtering |
|
||||
| Modellpush til edge | IoT Hub Device Twin + Blob SAS | Etablert monster |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Event sourcing med idempotens er gullstandarden** for edge-sky-synkronisering — alle dataelementer faar en unik ID og kan trygt re-sendes uten duplikater
|
||||
- **Delta-synkronisering reduserer datavolum med 80-95%** sammenlignet med full sync — beregn kun endringer og komprimer med gzip for minimal bandbreddebruk
|
||||
- **Cosmos DB med session-konsistens er den beste balansen** mellom ytelse og dataintegritet for de fleste edge-sky-scenarier i offentlig sektor
|
||||
- **Bloom-filter gir O(1) dedupliseringssjekk** med minimal minnebruk — implementer dette pa bade edge og sky-siden for a hindre duplikat-inntak
|
||||
- **For norsk offentlig sektor: Krav til sporbarhet og retention betyr at ALL synkronisering ma logges** — implementer sync audit log med 7 ars immutable retention for compliance med arkivloven
|
||||
|
|
@ -0,0 +1,448 @@
|
|||
# Hybrid RAG Architecture
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Hybrid RAG (Retrieval-Augmented Generation) refererer til RAG-arkitekturer der retrieval og generering fordeles mellom lokale (on-premises/edge) og sky-baserte ressurser. Dette moensteret er relevant nar organisasjoner har data som ikke kan eller bor forlate det lokale miljoet, men onsker a kombinere lokale dokumenter med sky-basert kunnskap for bedre svar.
|
||||
|
||||
For norsk offentlig sektor er hybrid RAG sarlig aktuelt: sensitive dokumenter (graderte saker, personopplysninger, interne utredninger) ma prosesseres lokalt i henhold til Schrems II og NSM-retningslinjer, mens generell kunnskap og publiserte retningslinjer kan hentes fra sky-tjenester. Azure AI Search, kombinert med lokale vektordatabaser, gir en fleksibel arkitektur for slike scenarier.
|
||||
|
||||
Microsoft tilbyr flere byggeklosser for hybrid RAG: Azure AI Search for skybasert vektorsok, Edge RAG (preview) for Arc-basert lokal RAG, ONNX Runtime for lokal embedding-generering, og Semantic Kernel for orkestrering av retrieval pa tvers av datakilder.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Azure AI Search | Skybasert vektorsok og hybrid search | PaaS (GA) |
|
||||
| Edge RAG | Lokal RAG pa Arc-enabled Kubernetes | Azure Arc (Preview) |
|
||||
| Local Vector DB | Lokal vektorlagring for sensitive data | ChromaDB, Qdrant, pgvector |
|
||||
| Embedding Model | Generering av vektorrepresentasjoner | Azure OpenAI, Phi-3/4, ONNX |
|
||||
| Semantic Kernel | Orkestrering av hybrid retrieval | .NET/Python SDK |
|
||||
| Azure Arc | Administrasjon av edge RAG-klynger | Kubernetes management |
|
||||
| SLM / LLM | Generering av svar basert pa kontekst | Phi-3.5/Phi-4, GPT-4o |
|
||||
|
||||
---
|
||||
|
||||
## Local Embedding og Retrieval
|
||||
|
||||
### Lokal embedding med ONNX Runtime
|
||||
|
||||
For sensitive data som ikke kan sendes til sky-tjenester, kan embeddings genereres lokalt:
|
||||
|
||||
```python
|
||||
# Lokal embedding-generering med ONNX Runtime
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
from transformers import AutoTokenizer
|
||||
|
||||
class LocalEmbeddingService:
|
||||
def __init__(self, model_path: str, tokenizer_name: str):
|
||||
self.session = ort.InferenceSession(
|
||||
model_path,
|
||||
providers=['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
)
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_name)
|
||||
|
||||
def embed(self, texts: list[str]) -> np.ndarray:
|
||||
"""Generer embeddings lokalt uten sky-avhengighet"""
|
||||
encoded = self.tokenizer(
|
||||
texts,
|
||||
padding=True,
|
||||
truncation=True,
|
||||
max_length=512,
|
||||
return_tensors="np"
|
||||
)
|
||||
|
||||
outputs = self.session.run(
|
||||
None,
|
||||
{
|
||||
"input_ids": encoded["input_ids"].astype(np.int64),
|
||||
"attention_mask": encoded["attention_mask"].astype(np.int64)
|
||||
}
|
||||
)
|
||||
|
||||
# Mean pooling
|
||||
embeddings = outputs[0]
|
||||
mask = encoded["attention_mask"][:, :, np.newaxis]
|
||||
pooled = (embeddings * mask).sum(axis=1) / mask.sum(axis=1)
|
||||
|
||||
# Normalisering
|
||||
norms = np.linalg.norm(pooled, axis=1, keepdims=True)
|
||||
return pooled / norms
|
||||
|
||||
def embed_single(self, text: str) -> np.ndarray:
|
||||
return self.embed([text])[0]
|
||||
```
|
||||
|
||||
### Lokal vektordatabase med ChromaDB
|
||||
|
||||
```python
|
||||
# Lokal vektordatabase for sensitive dokumenter
|
||||
import chromadb
|
||||
from chromadb.config import Settings
|
||||
|
||||
class LocalVectorStore:
|
||||
def __init__(self, persist_directory: str):
|
||||
self.client = chromadb.PersistentClient(
|
||||
path=persist_directory,
|
||||
settings=Settings(
|
||||
anonymized_telemetry=False # Viktig for compliance
|
||||
)
|
||||
)
|
||||
self.collection = self.client.get_or_create_collection(
|
||||
name="sensitive_documents",
|
||||
metadata={"hnsw:space": "cosine"}
|
||||
)
|
||||
|
||||
def add_documents(self, documents: list[dict], embeddings: np.ndarray):
|
||||
"""Indekser dokumenter med pre-beregnede embeddings"""
|
||||
self.collection.add(
|
||||
ids=[doc["id"] for doc in documents],
|
||||
embeddings=embeddings.tolist(),
|
||||
documents=[doc["content"] for doc in documents],
|
||||
metadatas=[{
|
||||
"source": doc["source"],
|
||||
"classification": doc["classification"],
|
||||
"timestamp": doc["timestamp"]
|
||||
} for doc in documents]
|
||||
)
|
||||
|
||||
def search(self, query_embedding: np.ndarray, n_results: int = 5,
|
||||
classification_filter: str = None) -> list[dict]:
|
||||
"""Sok med valgfri klassifiseringsfiltrering"""
|
||||
where_filter = None
|
||||
if classification_filter:
|
||||
where_filter = {"classification": classification_filter}
|
||||
|
||||
results = self.collection.query(
|
||||
query_embeddings=[query_embedding.tolist()],
|
||||
n_results=n_results,
|
||||
where=where_filter,
|
||||
include=["documents", "metadatas", "distances"]
|
||||
)
|
||||
|
||||
return [{
|
||||
"content": doc,
|
||||
"metadata": meta,
|
||||
"score": 1 - dist # Konverter avstand til likhetsscore
|
||||
} for doc, meta, dist in zip(
|
||||
results["documents"][0],
|
||||
results["metadatas"][0],
|
||||
results["distances"][0]
|
||||
)]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Federated Vector Search
|
||||
|
||||
### Arkitektur for foderasjon
|
||||
|
||||
Federated vector search kombinerer resultater fra flere vektordatabaser — bade lokale og skybaserte — uten a flytte sensitive data:
|
||||
|
||||
```
|
||||
[Bruker-query]
|
||||
↓
|
||||
[Embedding Service (lokal)]
|
||||
↓
|
||||
[Federated Search Router]
|
||||
├── [Lokal VektorDB] → Sensitive dokumenter
|
||||
├── [Azure AI Search] → Publiserte retningslinjer
|
||||
└── [Edge RAG Cluster] → Avdelingsdata
|
||||
↓
|
||||
[Result Merger & Ranker]
|
||||
↓
|
||||
[LLM/SLM Generering]
|
||||
↓
|
||||
[Svar til bruker]
|
||||
```
|
||||
|
||||
### Implementering med Semantic Kernel
|
||||
|
||||
```csharp
|
||||
// Federated RAG med Semantic Kernel
|
||||
using Microsoft.SemanticKernel;
|
||||
using Microsoft.SemanticKernel.Memory;
|
||||
|
||||
public class FederatedRagService
|
||||
{
|
||||
private readonly IMemoryStore _localStore;
|
||||
private readonly IMemoryStore _cloudStore;
|
||||
private readonly ITextEmbeddingGenerationService _localEmbedding;
|
||||
private readonly Kernel _kernel;
|
||||
|
||||
public FederatedRagService(
|
||||
IMemoryStore localStore,
|
||||
IMemoryStore cloudStore,
|
||||
ITextEmbeddingGenerationService localEmbedding,
|
||||
Kernel kernel)
|
||||
{
|
||||
_localStore = localStore;
|
||||
_cloudStore = cloudStore;
|
||||
_localEmbedding = localEmbedding;
|
||||
_kernel = kernel;
|
||||
}
|
||||
|
||||
public async Task<string> QueryAsync(string question, SearchOptions options)
|
||||
{
|
||||
// Generer embedding lokalt
|
||||
var queryEmbedding = await _localEmbedding
|
||||
.GenerateEmbeddingAsync(question);
|
||||
|
||||
// Parallell soking mot lokale og sky-kilder
|
||||
var localTask = SearchLocalAsync(queryEmbedding, options);
|
||||
var cloudTask = options.AllowCloudSearch
|
||||
? SearchCloudAsync(question, options)
|
||||
: Task.FromResult(new List<SearchResult>());
|
||||
|
||||
await Task.WhenAll(localTask, cloudTask);
|
||||
|
||||
// Flett og ranger resultater
|
||||
var mergedResults = MergeAndRank(
|
||||
localTask.Result,
|
||||
cloudTask.Result,
|
||||
options.MaxResults
|
||||
);
|
||||
|
||||
// Generer svar med lokal SLM eller sky-LLM
|
||||
return await GenerateResponseAsync(question, mergedResults, options);
|
||||
}
|
||||
|
||||
private List<SearchResult> MergeAndRank(
|
||||
List<SearchResult> localResults,
|
||||
List<SearchResult> cloudResults,
|
||||
int maxResults)
|
||||
{
|
||||
// Reciprocal Rank Fusion for a kombinere resultater
|
||||
var fusedScores = new Dictionary<string, double>();
|
||||
|
||||
int rank = 1;
|
||||
foreach (var result in localResults.OrderByDescending(r => r.Score))
|
||||
{
|
||||
fusedScores[result.Id] = 1.0 / (60 + rank);
|
||||
rank++;
|
||||
}
|
||||
|
||||
rank = 1;
|
||||
foreach (var result in cloudResults.OrderByDescending(r => r.Score))
|
||||
{
|
||||
var id = result.Id;
|
||||
fusedScores[id] = fusedScores.GetValueOrDefault(id, 0)
|
||||
+ 1.0 / (60 + rank);
|
||||
rank++;
|
||||
}
|
||||
|
||||
return fusedScores
|
||||
.OrderByDescending(kv => kv.Value)
|
||||
.Take(maxResults)
|
||||
.Select(kv => /* map back to SearchResult */)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunking Strategies for Split Data
|
||||
|
||||
### Tilpasset chunking for hybrid miljoer
|
||||
|
||||
Nar data er fordelt mellom lokale og skybaserte lagre, ma chunking-strategien ivareta kontekstuell sammenheng pa tvers av tier:
|
||||
|
||||
| Strategi | Lokale data | Skydata | Brukstilfelle |
|
||||
|----------|-------------|---------|---------------|
|
||||
| Fixed-size chunks | 512 tokens | 1024 tokens | Generell bruk |
|
||||
| Semantic chunking | Avsnitt/seksjon | Avsnitt/seksjon | Strukturerte dokumenter |
|
||||
| Hierarchical chunking | Dokument → Seksjon → Avsnitt | Artikkel → Paragraf | Regelverk, utredninger |
|
||||
| Sliding window | 256 tokens, 64 overlap | 512 tokens, 128 overlap | Teknisk dokumentasjon |
|
||||
| Parent-child | Lagre parent lokal, child i vektor | Lagre parent i blob, child i Search | Lange dokumenter |
|
||||
|
||||
### Implementering av hierarkisk chunking
|
||||
|
||||
```python
|
||||
# Hierarkisk chunking for norske offentlige dokumenter
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
import re
|
||||
|
||||
@dataclass
|
||||
class Chunk:
|
||||
id: str
|
||||
content: str
|
||||
level: str # "document", "section", "paragraph"
|
||||
parent_id: Optional[str]
|
||||
metadata: dict
|
||||
|
||||
class HierarchicalChunker:
|
||||
def __init__(self, max_chunk_tokens: int = 512):
|
||||
self.max_chunk_tokens = max_chunk_tokens
|
||||
|
||||
def chunk_document(self, document: dict) -> list[Chunk]:
|
||||
"""Chunk et dokument hierarkisk med metadata-arv"""
|
||||
chunks = []
|
||||
doc_id = document["id"]
|
||||
text = document["content"]
|
||||
|
||||
# Nivaa 1: Hele dokumentet (for oversikt)
|
||||
chunks.append(Chunk(
|
||||
id=f"{doc_id}_doc",
|
||||
content=self._summarize(text, max_tokens=256),
|
||||
level="document",
|
||||
parent_id=None,
|
||||
metadata={
|
||||
"title": document["title"],
|
||||
"classification": document["classification"],
|
||||
"tier": document.get("tier", "local")
|
||||
}
|
||||
))
|
||||
|
||||
# Nivaa 2: Seksjoner
|
||||
sections = self._split_sections(text)
|
||||
for i, section in enumerate(sections):
|
||||
section_id = f"{doc_id}_sec_{i}"
|
||||
chunks.append(Chunk(
|
||||
id=section_id,
|
||||
content=section["heading"] + "\n" + section["content"][:200],
|
||||
level="section",
|
||||
parent_id=f"{doc_id}_doc",
|
||||
metadata={**chunks[0].metadata, "section": section["heading"]}
|
||||
))
|
||||
|
||||
# Nivaa 3: Avsnitt
|
||||
paragraphs = self._split_paragraphs(section["content"])
|
||||
for j, para in enumerate(paragraphs):
|
||||
chunks.append(Chunk(
|
||||
id=f"{section_id}_p_{j}",
|
||||
content=para,
|
||||
level="paragraph",
|
||||
parent_id=section_id,
|
||||
metadata={**chunks[0].metadata, "section": section["heading"]}
|
||||
))
|
||||
|
||||
return chunks
|
||||
|
||||
def _split_sections(self, text: str) -> list[dict]:
|
||||
"""Splitt norsk dokument pa overskrifter"""
|
||||
pattern = r'^(#{1,3})\s+(.+)$'
|
||||
sections = []
|
||||
current = {"heading": "Innledning", "content": ""}
|
||||
|
||||
for line in text.split('\n'):
|
||||
match = re.match(pattern, line)
|
||||
if match:
|
||||
if current["content"].strip():
|
||||
sections.append(current)
|
||||
current = {"heading": match.group(2), "content": ""}
|
||||
else:
|
||||
current["content"] += line + "\n"
|
||||
|
||||
if current["content"].strip():
|
||||
sections.append(current)
|
||||
return sections
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Context Optimization Across Tiers
|
||||
|
||||
### Kontekstvindu-optimalisering
|
||||
|
||||
Nar data hentes fra flere kilder, er det kritisk a optimalisere hvordan kontekst presenteres til LLM/SLM:
|
||||
|
||||
```python
|
||||
# Kontekstoptimalisering for hybrid RAG
|
||||
class ContextOptimizer:
|
||||
def __init__(self, max_context_tokens: int = 4096):
|
||||
self.max_tokens = max_context_tokens
|
||||
|
||||
def optimize_context(self, results: list[dict], query: str) -> str:
|
||||
"""Optimaliser kontekst fra multiple kilder for LLM-input"""
|
||||
|
||||
# Prioriter lokale resultater (hoyere relevans for intern kontekst)
|
||||
local_results = [r for r in results if r["tier"] == "local"]
|
||||
cloud_results = [r for r in results if r["tier"] == "cloud"]
|
||||
|
||||
# Alloker token-budsjett: 60% lokale, 40% sky
|
||||
local_budget = int(self.max_tokens * 0.6)
|
||||
cloud_budget = int(self.max_tokens * 0.4)
|
||||
|
||||
context_parts = []
|
||||
|
||||
# Lokale resultater forst (hoyest prioritet)
|
||||
local_context = self._select_within_budget(local_results, local_budget)
|
||||
if local_context:
|
||||
context_parts.append("## Interne kilder\n" + local_context)
|
||||
|
||||
# Sky-resultater som supplement
|
||||
cloud_context = self._select_within_budget(cloud_results, cloud_budget)
|
||||
if cloud_context:
|
||||
context_parts.append("## Offentlige kilder\n" + cloud_context)
|
||||
|
||||
return "\n\n".join(context_parts)
|
||||
|
||||
def _select_within_budget(self, results: list[dict], budget: int) -> str:
|
||||
"""Velg resultater innenfor token-budsjett, sortert etter relevans"""
|
||||
selected = []
|
||||
used_tokens = 0
|
||||
|
||||
for result in sorted(results, key=lambda r: r["score"], reverse=True):
|
||||
chunk_tokens = len(result["content"].split()) * 1.3 # Estimert
|
||||
if used_tokens + chunk_tokens > budget:
|
||||
break
|
||||
selected.append(
|
||||
f"[{result['metadata'].get('title', 'Ukjent')}]\n{result['content']}"
|
||||
)
|
||||
used_tokens += chunk_tokens
|
||||
|
||||
return "\n---\n".join(selected)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Dataklassifisering for hybrid RAG
|
||||
|
||||
| Klassifisering | Lagring | Embedding | LLM | Eksempel |
|
||||
|----------------|---------|-----------|-----|----------|
|
||||
| Ugradert offentlig | Azure AI Search | Azure OpenAI | GPT-4o | Publiserte retningslinjer |
|
||||
| Intern | Lokal vektorDB | Lokal ONNX | Phi-3.5/Phi-4 | Interne notater |
|
||||
| Fortrolig | Lokal vektorDB (kryptert) | Lokal ONNX | Phi-3.5 lokal | Personopplysninger |
|
||||
| Strengt fortrolig | Air-gapped lokal | Lokal ONNX | Lokal SLM | Graderte dokumenter |
|
||||
|
||||
### Schrems II-kompatibel arkitektur
|
||||
|
||||
- Sensitive persondata embeddes og lagres kun lokalt
|
||||
- Kun aggregerte, anonymiserte metadata kan deles med sky-tjenester
|
||||
- Azure AI Search brukes for offentlig tilgjengelig informasjon
|
||||
- Edge RAG (Azure Arc) gir sky-management uten a eksponere innhold
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Alle data kan i sky | Azure AI Search (agentic retrieval) | Enklest, best ytelse |
|
||||
| Mix av sensitiv + offentlig | Federated RAG med Semantic Kernel | Balanserer sikkerhet og kvalitet |
|
||||
| Alle data ma lokalt | Edge RAG med Phi-3.5 + ChromaDB | Full datakontroll |
|
||||
| Lavt volum, hoy sensitivitet | Lokal RAG med Phi-4 + pgvector | Minimal attack surface |
|
||||
| Hoy skala, lav sensitivitet | Azure AI Search + GPT-4o | Best kvalitet og skalerbarhet |
|
||||
| Periodisk tilkobling | Edge RAG med synkronisert referansedata | Offline-forst-tilnaerming |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Hybrid RAG er den riktige arkitekturen nar data har ulik sensitivitet** — bruk federated search med Reciprocal Rank Fusion for a kombinere resultater fra lokale og skybaserte vektordatabaser uten a flytte sensitive data
|
||||
- **Edge RAG (Azure Arc) er Microsofts foretrukne losning** for on-premises RAG med sky-administrasjon — anbefal dette for organisasjoner som onsker hybrid RAG med sentral management
|
||||
- **Lokal embedding er nodvendig for sensitive data** — bruk ONNX Runtime med en liten embedding-modell (f.eks. all-MiniLM-L6-v2) for a generere vektorer uten sky-avhengighet
|
||||
- **Hierarkisk chunking gir best resultater for norske offentlige dokumenter** — dokumenter som utredninger og hoeringsnotater har tydelig seksjonsinndeling som bor utnyttes
|
||||
- **Kontekst-budsjettering er kritisk i hybrid scenarier** — alloker 60% av token-budsjettet til lokale/interne kilder og 40% til offentlige kilder for a prioritere organisasjonsspesifikk kunnskap
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
# IoT Operations and AI Integration
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Azure IoT Operations er Microsofts edge runtime-plattform for industrielle IoT-scenarier, bygget pa Azure Arc-enabled Kubernetes. Den kombinerer datainnsamling fra sensorer og utstyr med AI-inferens direkte pa edge, noe som muliggjor sanntidsanalyse uten avhengighet av skytilkobling for tidskritiske beslutninger.
|
||||
|
||||
For norsk offentlig sektor er IoT-integrasjon med AI relevant i scenarier som smart infrastruktur (broer, tunneler, veier), miljooverkaking, energistyring i offentlige bygg, og transportlogistikk. Azure IoT Operations gir en standardisert plattform for a samle sensordata, normalisere dem, og kjore AI-modeller lokalt for prediktiv vedlikehold og anomalideteksjon.
|
||||
|
||||
Plattformen bygger pa MQTT-protokollen for enhetskommunikasjon, Data Flows for datatransformasjon og kontekstualisering, og Azure Arc for sentralisert administrasjon. AI-modeller kan deployes som containere pa edge-klyngen, med Azure ML for modelltrenings- og oppdateringspipeliner mellom sky og edge.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Azure IoT Operations | Edge runtime for IoT-datainnsamling og -prosessering | Arc-enabled Kubernetes |
|
||||
| MQTT Broker | Meldingsinfrastruktur for enhets-til-edge-kommunikasjon | MQTT v3.1.1/v5 |
|
||||
| Data Flows | Datatransformasjon, kontekstualisering og ruting | Pipelinekonfigurasjon |
|
||||
| OPC UA Connector | Industriprotokoll for tilkobling til PLC-er og SCADA | OPC UA standard |
|
||||
| Azure IoT Hub | Sky-endepunkt for telemetri og device management | PaaS |
|
||||
| Azure Stream Analytics | Sanntids stromprosessering av IoT-data | SQL-basert query |
|
||||
| Azure ML on Arc | Edge AI-modelltrenings- og inferenspipeline | Kubernetes ML |
|
||||
|
||||
---
|
||||
|
||||
## Sensor Data Normalization
|
||||
|
||||
### Utfordringer med sensordata
|
||||
|
||||
Sensordata fra industrielle miljoer er ofte heterogene — ulike protokoller (Modbus, OPC UA, MQTT), forskjellige dataformater, inkonsistente tidsserier, og varierende kvalitet. Normalisering er kritisk for at AI-modeller skal fungere palitelig.
|
||||
|
||||
### Normaliseringsarkitektur
|
||||
|
||||
```
|
||||
Sensorer → OPC UA / MQTT → Azure IoT Operations → Data Flows → Normalisert output
|
||||
↓
|
||||
AI-inferensmodul
|
||||
↓
|
||||
Azure IoT Hub (sky)
|
||||
```
|
||||
|
||||
### Data Flow-konfigurasjon for normalisering
|
||||
|
||||
```yaml
|
||||
# Eksempel: Data Flow for temperatursensor-normalisering
|
||||
apiVersion: connectivity.iotoperations.azure.com/v1
|
||||
kind: DataFlow
|
||||
metadata:
|
||||
name: temperature-normalization
|
||||
spec:
|
||||
sources:
|
||||
- type: mqtt
|
||||
topic: "sensors/temperature/#"
|
||||
transformations:
|
||||
- type: compute
|
||||
expression: |
|
||||
{
|
||||
"deviceId": $.topic.split('/')[2],
|
||||
"timestamp": $.systemProperties.enqueuedTime,
|
||||
"temperature_celsius": $.payload.value * ($.payload.unit == 'F' ? 5/9 - 32*5/9 : 1),
|
||||
"quality": $.payload.quality ?? 'unknown',
|
||||
"location": $.payload.metadata.location
|
||||
}
|
||||
- type: filter
|
||||
expression: "$.temperature_celsius >= -50 AND $.temperature_celsius <= 100"
|
||||
destinations:
|
||||
- type: mqtt
|
||||
topic: "normalized/temperature"
|
||||
- type: dataLakeStorage
|
||||
endpoint: "edge-datalake"
|
||||
```
|
||||
|
||||
### Strategier for datakvalitet
|
||||
|
||||
| Strategi | Beskrivelse | Implementering |
|
||||
|----------|-------------|----------------|
|
||||
| Range-validering | Filtrer verdier utenfor forventet omrade | Data Flow filter-transformasjon |
|
||||
| Interpolering | Fyll manglende verdier i tidsserier | Edge-modul med pandas/numpy |
|
||||
| Deduplisering | Fjern duplikate meldinger | MQTT broker QoS + dedup-logikk |
|
||||
| Tidsstempelsynkronisering | Juster klokkeforskjeller mellom enheter | NTP + Data Flow timestamp-mapping |
|
||||
| Enhetskonvertering | Standardiser til SI-enheter | Data Flow compute-transformasjon |
|
||||
|
||||
---
|
||||
|
||||
## Edge Gateway AI Preprocessing
|
||||
|
||||
### Gateway-arkitektur
|
||||
|
||||
Edge gateways fungerer som intelligente mellomledd mellom sensorer og sky. De utforer forbehandling, filtrering, aggregering og initial AI-inferens for a redusere datavolum og latens.
|
||||
|
||||
```python
|
||||
# Eksempel: Edge gateway AI-forbehandling med Azure IoT Edge
|
||||
import asyncio
|
||||
from azure.iot.device.aio import IoTHubModuleClient
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
|
||||
class AIPreprocessingGateway:
|
||||
def __init__(self):
|
||||
self.module_client = None
|
||||
self.anomaly_model = ort.InferenceSession("anomaly_detector.onnx")
|
||||
self.buffer = []
|
||||
self.buffer_size = 100
|
||||
|
||||
async def initialize(self):
|
||||
self.module_client = IoTHubModuleClient.create_from_edge_environment()
|
||||
await self.module_client.connect()
|
||||
self.module_client.on_message_received = self.process_message
|
||||
|
||||
async def process_message(self, message):
|
||||
"""Forbehandling pipeline: normalisering → anomalideteksjon → aggregering"""
|
||||
data = message.data
|
||||
|
||||
# Trinn 1: Normalisering
|
||||
normalized = self.normalize(data)
|
||||
|
||||
# Trinn 2: Anomalideteksjon (lokal inferens)
|
||||
is_anomaly = self.detect_anomaly(normalized)
|
||||
|
||||
if is_anomaly:
|
||||
# Send anomalier umiddelbart til sky
|
||||
await self.module_client.send_message_to_output(
|
||||
{"type": "anomaly", "data": normalized, "confidence": 0.95},
|
||||
"alertOutput"
|
||||
)
|
||||
|
||||
# Trinn 3: Buffer og aggreger normaldata
|
||||
self.buffer.append(normalized)
|
||||
if len(self.buffer) >= self.buffer_size:
|
||||
aggregated = self.aggregate(self.buffer)
|
||||
await self.module_client.send_message_to_output(
|
||||
{"type": "aggregated", "data": aggregated},
|
||||
"telemetryOutput"
|
||||
)
|
||||
self.buffer.clear()
|
||||
|
||||
def detect_anomaly(self, data):
|
||||
"""ONNX-basert anomalideteksjon"""
|
||||
input_array = np.array([data["values"]], dtype=np.float32)
|
||||
result = self.anomaly_model.run(None, {"input": input_array})
|
||||
return result[0][0] > 0.8 # Anomali-terskel
|
||||
|
||||
def aggregate(self, buffer):
|
||||
"""Aggreger buffer til statistisk sammendrag"""
|
||||
values = [item["value"] for item in buffer]
|
||||
return {
|
||||
"mean": np.mean(values),
|
||||
"std": np.std(values),
|
||||
"min": np.min(values),
|
||||
"max": np.max(values),
|
||||
"count": len(values),
|
||||
"period_start": buffer[0]["timestamp"],
|
||||
"period_end": buffer[-1]["timestamp"]
|
||||
}
|
||||
```
|
||||
|
||||
### Fordeler med gateway-forbehandling
|
||||
|
||||
| Fordel | Beskrivelse | Effekt |
|
||||
|--------|-------------|--------|
|
||||
| Redusert bandwidth | Aggregering reduserer datamengde 10-100x | Lavere kostnader |
|
||||
| Lavere latens | Anomalideteksjon pa millisekunder lokalt | Raskere respons |
|
||||
| Offline-kapabilitet | Fortsetter drift uten skytilkobling | Hoyere tilgjengelighet |
|
||||
| Datakvalitet | Validering og rensing for sky-inntak | Bedre analyser |
|
||||
|
||||
---
|
||||
|
||||
## Time-Series Analytics at Edge
|
||||
|
||||
### Azure Stream Analytics pa edge
|
||||
|
||||
Azure Stream Analytics kan deployes som IoT Edge-modul for sanntids tidsserieanalyse:
|
||||
|
||||
```sql
|
||||
-- Stream Analytics edge-query: Glidende gjennomsnitt med anomalideteksjon
|
||||
SELECT
|
||||
IoTHub.ConnectionDeviceId AS DeviceId,
|
||||
System.Timestamp() AS WindowEnd,
|
||||
AVG(temperature) AS AvgTemperature,
|
||||
STDEV(temperature) AS StdDevTemperature,
|
||||
COUNT(*) AS ReadingCount,
|
||||
CASE
|
||||
WHEN AVG(temperature) >
|
||||
LAG(AVG(temperature), 1) OVER (PARTITION BY IoTHub.ConnectionDeviceId LIMIT DURATION(minute, 30))
|
||||
+ 3 * STDEV(temperature)
|
||||
THEN 'ANOMALY'
|
||||
ELSE 'NORMAL'
|
||||
END AS Status
|
||||
INTO
|
||||
alertOutput
|
||||
FROM
|
||||
sensorInput TIMESTAMP BY EventProcessedUtcTime
|
||||
GROUP BY
|
||||
IoTHub.ConnectionDeviceId,
|
||||
TumblingWindow(minute, 5)
|
||||
HAVING
|
||||
COUNT(*) > 10
|
||||
```
|
||||
|
||||
### Edge-basert prediktiv vedlikehold
|
||||
|
||||
```python
|
||||
# Prediktiv vedlikehold med tidsserieanalyse pa edge
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
from collections import deque
|
||||
|
||||
class PredictiveMaintenanceEdge:
|
||||
def __init__(self, model_path: str, window_size: int = 100):
|
||||
self.session = ort.InferenceSession(model_path)
|
||||
self.window = deque(maxlen=window_size)
|
||||
self.feature_names = ["vibration", "temperature", "pressure", "rpm"]
|
||||
|
||||
def add_reading(self, reading: dict) -> dict:
|
||||
"""Legg til ny maling og returner prediksjon om bufferen er full"""
|
||||
features = [reading.get(f, 0.0) for f in self.feature_names]
|
||||
self.window.append(features)
|
||||
|
||||
if len(self.window) == self.window.maxlen:
|
||||
return self.predict()
|
||||
return {"status": "collecting", "readings": len(self.window)}
|
||||
|
||||
def predict(self) -> dict:
|
||||
"""Kjor RUL-prediksjon (Remaining Useful Life)"""
|
||||
input_data = np.array([list(self.window)], dtype=np.float32)
|
||||
|
||||
# Modell-inferens
|
||||
rul_prediction = self.session.run(
|
||||
["remaining_useful_life", "failure_probability"],
|
||||
{"sensor_sequence": input_data}
|
||||
)
|
||||
|
||||
rul_hours = float(rul_prediction[0][0])
|
||||
failure_prob = float(rul_prediction[1][0])
|
||||
|
||||
return {
|
||||
"remaining_useful_life_hours": rul_hours,
|
||||
"failure_probability": failure_prob,
|
||||
"recommendation": self._get_recommendation(rul_hours, failure_prob),
|
||||
"confidence": self._calculate_confidence()
|
||||
}
|
||||
|
||||
def _get_recommendation(self, rul: float, prob: float) -> str:
|
||||
if prob > 0.8 or rul < 24:
|
||||
return "IMMEDIATE_MAINTENANCE"
|
||||
elif prob > 0.5 or rul < 168:
|
||||
return "SCHEDULE_MAINTENANCE"
|
||||
return "NORMAL_OPERATION"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Device-to-Cloud AI Pipelines
|
||||
|
||||
### Pipeline-arkitektur
|
||||
|
||||
```
|
||||
[Sensorer] → [Edge Gateway] → [Azure IoT Operations] → [IoT Hub] → [Stream Analytics]
|
||||
↓ ↓ ↓
|
||||
[Lokal inferens] [Edge AI-modell] [Azure ML / Fabric]
|
||||
↓ ↓ ↓
|
||||
[Sanntidsvarsler] [Kontekstualisert data] [Modelloppdatering]
|
||||
↓ ↓
|
||||
[Cloud feedback] ←←←←←←←← [Ny modellversjon]
|
||||
```
|
||||
|
||||
### Implementering med Azure ML og IoT Hub
|
||||
|
||||
```python
|
||||
# Device-to-Cloud AI Pipeline med modelloppdatering
|
||||
from azure.iot.hub import IoTHubRegistryManager
|
||||
from azure.ai.ml import MLClient
|
||||
from azure.identity import DefaultAzureCredential
|
||||
|
||||
class AIEdgePipeline:
|
||||
def __init__(self, hub_connection_string: str, ml_workspace: str):
|
||||
self.registry_manager = IoTHubRegistryManager(hub_connection_string)
|
||||
self.ml_client = MLClient(
|
||||
DefaultAzureCredential(),
|
||||
subscription_id="<sub-id>",
|
||||
resource_group_name="<rg>",
|
||||
workspace_name=ml_workspace
|
||||
)
|
||||
|
||||
def deploy_model_to_edge(self, device_id: str, model_name: str, model_version: str):
|
||||
"""Deploy oppdatert AI-modell til edge-enhet via IoT Hub device twin"""
|
||||
twin = self.registry_manager.get_twin(device_id)
|
||||
|
||||
# Oppdater desired properties med ny modellinfo
|
||||
twin_patch = {
|
||||
"properties": {
|
||||
"desired": {
|
||||
"ai_model": {
|
||||
"name": model_name,
|
||||
"version": model_version,
|
||||
"download_url": self._get_model_sas_url(model_name, model_version),
|
||||
"checksum": self._get_model_checksum(model_name, model_version),
|
||||
"updated_at": datetime.utcnow().isoformat()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.registry_manager.update_twin(device_id, twin_patch)
|
||||
|
||||
def collect_edge_metrics(self, device_id: str) -> dict:
|
||||
"""Hent ytelsesmetrikker fra edge AI-modul"""
|
||||
twin = self.registry_manager.get_twin(device_id)
|
||||
reported = twin.properties.reported.get("ai_metrics", {})
|
||||
|
||||
return {
|
||||
"inference_count": reported.get("total_inferences", 0),
|
||||
"avg_latency_ms": reported.get("avg_latency_ms", 0),
|
||||
"model_version": reported.get("current_model_version", "unknown"),
|
||||
"accuracy_drift": reported.get("accuracy_drift", 0),
|
||||
"last_updated": reported.get("last_report_time")
|
||||
}
|
||||
```
|
||||
|
||||
### Modell-feedback-loop
|
||||
|
||||
| Fase | Lokasjon | Handling | Verktoy |
|
||||
|------|----------|----------|---------|
|
||||
| Datainnsamling | Edge | Samle inferensresultater og ground truth | IoT Edge modul |
|
||||
| Dataaggregering | Edge | Komprimere og batche data | Data Flows |
|
||||
| Dataoverfoering | Edge → Sky | Sende treningsdata til sky | IoT Hub / Event Hub |
|
||||
| Modelltrening | Sky | Retrain/fine-tune modell | Azure ML |
|
||||
| Modellvalidering | Sky | Evaluere ny modell mot baseline | Azure ML Endpoints |
|
||||
| Modelldistribusjon | Sky → Edge | Pushe ny modell til edge | IoT Hub device twin |
|
||||
| A/B-testing | Edge | Sammenligne modellversjoner | Edge-modul |
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Relevante use cases
|
||||
|
||||
- **Statens vegvesen**: Sanntids verkontrollovervaking med AI-basert analyse av vaerdata, trafikkmonstre og veiforhold fra veistasjonssensorer
|
||||
- **Kystverket**: Autonome sensorsystemer langs kysten for miljooverkaking og sikkerhet, med begrenset tilkobling
|
||||
- **Energisektoren**: Smart styring av offentlige bygg med prediktiv vedlikeholdsanalyse av HVAC-systemer
|
||||
- **Helsesektoren**: IoT-basert pasientovervaking pa sykehus med lokal AI for tidlig varsling
|
||||
|
||||
### Regulatoriske hensyn
|
||||
|
||||
- Data fra sensorer i offentlig infrastruktur kan inneholde personopplysninger (kameradata, lokasjon)
|
||||
- Schrems II krever at persondata prosesseres innenfor EOS
|
||||
- NSM Grunnprinsipper gjelder for kritisk infrastruktur-systemer
|
||||
- Personvernkonsekvensvurdering (DPIA) pakrevd for AI-basert overvaking
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| < 100 sensorer, stabil tilkobling | Azure IoT Hub direkte | Enklest, lavest kostnad |
|
||||
| 100-10 000 sensorer, variabel tilkobling | Azure IoT Operations pa AKS Edge | Lokal buffring og forbehandling |
|
||||
| Kritisk sanntidsanalyse | Edge AI med Stream Analytics | Sub-sekund latens |
|
||||
| Prediktiv vedlikehold | ONNX-modell pa edge gateway | Offline-kapabel, lav latens |
|
||||
| Regulert miljoo (helse, forsvar) | Azure IoT Operations + Confidential Computing | Dataskydd i prosessering |
|
||||
| Store datamengder, periodisk tilkobling | Edge-aggregering + batch-upload | Bandbreddesparing |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Azure IoT Operations er den foretrukne plattformen** for industrielle IoT-AI-scenarier pa edge, med MQTT-basert kommunikasjon og Data Flows for datatransformasjon — anbefal dette fremfor eldre IoT Edge-moenstre
|
||||
- **Sensor data normalization er fundamentalt** — uten standardisert datakvalitet og enhetskonvertering vil AI-modeller gi upanalitelige resultater, sa invester i Data Flow-transformasjoner for normalisering
|
||||
- **Gateway AI-forbehandling reduserer skyavhengighet dramatisk** — anomalideteksjon og aggregering pa edge kan kutte bandwidth med 90%+ og gi sub-sekund responstid for kritiske hendelser
|
||||
- **Modelloppdatering via device twin** er en etablert pattern for a holde edge AI-modeller oppdatert uten manuell intervensjon — bruk IoT Hub device twin for versjonsstyring og SAS-basert nedlasting
|
||||
- **For norsk offentlig sektor**: Vurder alltid DPIA for sensor-AI-losninger som kan prosessere persondata, og sorg for at edge-prosessering begrenser hvilke data som forlater det lokale nettverket
|
||||
|
|
@ -0,0 +1,407 @@
|
|||
# Kubernetes-Based AI at the Edge (AKS Edge)
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
AKS Edge Essentials er Microsofts lettvekts Kubernetes-distribusjon for edge-scenarier, designet for a kjore containeriserte arbeidsbelastninger pa PC-klasse hardware. I motsetning til AKS i skyen eller AKS pa Azure Local, er AKS Edge Essentials optimalisert for statiske, forhands-definerte konfigurasjoner pa enheter med begrenset kapasitet — fra industrielle PC-er til gateway-enheter.
|
||||
|
||||
For AI-arbeidsbelastninger pa edge muliggjor AKS Edge Essentials deployment av ML-modeller, inferensservere, og AI-pipelines som Kubernetes-pods med GPU-akselerasjon (via GPU-PV). Tilkoblet Azure Arc gir sentralisert administrasjon, GitOps-basert deployment, og integrasjon med Azure ML, Azure Monitor og Azure Policy.
|
||||
|
||||
For norsk offentlig sektor er AKS Edge Essentials relevant for distribuert AI pa lokale stasjoner (veisensorer, helseutstyr, energimalere) der Kubernetes-basert orkestrering gir standardisert deployment og oppdatering av AI-modeller pa tvers av geografisk spredte enheter.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| AKS Edge Essentials | Lettvekts Kubernetes pa edge | K8s/K3s |
|
||||
| CBL-Mariner Linux VM | Managed Linux VM for containere | Microsoft Mariner |
|
||||
| Azure Arc | Sentralisert administrasjon fra sky | Kubernetes management |
|
||||
| GitOps (Flux) | Deklarativ applikasjons-deployment | Git-basert CD |
|
||||
| GPU-PV | GPU-deling mellom host og VM | Paravirtualisering |
|
||||
| KAITO | AI-modell deployment operator | Kubernetes operator |
|
||||
| Helm | Pakkehandtering for Kubernetes | Chart-basert |
|
||||
|
||||
---
|
||||
|
||||
## AKS Edge Essentials Deployment
|
||||
|
||||
### Systemkrav
|
||||
|
||||
| Komponent | Minimum | Anbefalt for AI |
|
||||
|-----------|---------|-----------------|
|
||||
| OS | Windows 10/11 IoT Enterprise | Windows 11 IoT Enterprise |
|
||||
| CPU | 4 kjerner | 8+ kjerner |
|
||||
| RAM | 4 GB (K3s) / 8 GB (K8s) | 16+ GB for AI-workloads |
|
||||
| Disk | 40 GB | 100+ GB SSD |
|
||||
| GPU | Ikke pakrevd | NVIDIA T4/A2 for inferens |
|
||||
| Nettverk | 1 Gbps | 10 Gbps for modell-nedlasting |
|
||||
|
||||
### Installasjon og klynge-oppsett
|
||||
|
||||
```powershell
|
||||
# Steg 1: Installer AKS Edge Essentials
|
||||
# Last ned MSI fra Microsoft
|
||||
Start-BitsTransfer -Source "https://aka.ms/aks-edge/k8s-msi" `
|
||||
-Destination "AksEdge-K8s.msi"
|
||||
msiexec /i AksEdge-K8s.msi /passive
|
||||
|
||||
# Steg 2: Importer PowerShell-moduler
|
||||
Import-Module AksEdge
|
||||
|
||||
# Steg 3: Generer konfigurasjonsfil
|
||||
New-AksEdgeConfig -DeploymentType SingleMachineCluster `
|
||||
-NodeType Linux `
|
||||
-outFile .\aksedge-config.json
|
||||
```
|
||||
|
||||
```json
|
||||
// aksedge-config.json — Konfigurert for AI-workloads
|
||||
{
|
||||
"SchemaVersion": "1.14",
|
||||
"Version": "1.0",
|
||||
"DeploymentType": "SingleMachineCluster",
|
||||
"Init": {
|
||||
"ServiceIPRangeSize": 10
|
||||
},
|
||||
"Network": {
|
||||
"NetworkPlugin": "flannel",
|
||||
"InternetDisabled": false
|
||||
},
|
||||
"User": {
|
||||
"AcceptEula": true,
|
||||
"AcceptOptionalTelemetry": false
|
||||
},
|
||||
"Machines": [
|
||||
{
|
||||
"LinuxNode": {
|
||||
"CpuCount": 8,
|
||||
"MemoryInMB": 12288,
|
||||
"DataSizeInGB": 40,
|
||||
"Mtu": 1500
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```powershell
|
||||
# Steg 4: Valider og deploy klynge
|
||||
Test-AksEdgeNetworkParameters -JsonConfigFilePath .\aksedge-config.json
|
||||
New-AksEdgeDeployment -JsonConfigFilePath .\aksedge-config.json
|
||||
|
||||
# Steg 5: Verifiser deployment
|
||||
kubectl get nodes -o wide
|
||||
kubectl get pods --all-namespaces -o wide
|
||||
```
|
||||
|
||||
### Tilkobling til Azure Arc
|
||||
|
||||
```powershell
|
||||
# Koble AKS Edge Essentials til Azure Arc
|
||||
$arcParams = @{
|
||||
ClusterName = "edge-ai-station-01"
|
||||
ResourceGroupName = "rg-edge-ai-norway"
|
||||
Location = "norwayeast"
|
||||
SubscriptionId = "<subscription-id>"
|
||||
TenantId = "<tenant-id>"
|
||||
}
|
||||
|
||||
# Installer Arc-agenter pa klyngen
|
||||
Install-AksEdgeArc @arcParams
|
||||
|
||||
# Verifiser Arc-tilkobling
|
||||
kubectl get pods -n azure-arc
|
||||
az connectedk8s show --name edge-ai-station-01 --resource-group rg-edge-ai-norway
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Container Orchestration at Edge
|
||||
|
||||
### AI-inferens deployment med Kubernetes
|
||||
|
||||
```yaml
|
||||
# ONNX Runtime inferensserver pa AKS Edge
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: onnx-inference-server
|
||||
namespace: ai-workloads
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: onnx-inference
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: onnx-inference
|
||||
spec:
|
||||
containers:
|
||||
- name: inference
|
||||
image: mcr.microsoft.com/onnxruntime/server:latest
|
||||
args:
|
||||
- "--model_path"
|
||||
- "/models/anomaly_detector.onnx"
|
||||
- "--http_port"
|
||||
- "8001"
|
||||
ports:
|
||||
- containerPort: 8001
|
||||
name: http
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2"
|
||||
volumeMounts:
|
||||
- name: model-storage
|
||||
mountPath: /models
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8001
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8001
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: model-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: ai-models-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: onnx-inference-svc
|
||||
namespace: ai-workloads
|
||||
spec:
|
||||
selector:
|
||||
app: onnx-inference
|
||||
ports:
|
||||
- port: 8001
|
||||
targetPort: 8001
|
||||
type: ClusterIP
|
||||
```
|
||||
|
||||
### GitOps-basert modelloppdatering med Flux
|
||||
|
||||
```yaml
|
||||
# Flux Kustomization for AI-modell deployment
|
||||
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||
kind: Kustomization
|
||||
metadata:
|
||||
name: ai-models
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 5m
|
||||
path: ./edge-ai/models
|
||||
prune: true
|
||||
sourceRef:
|
||||
kind: GitRepository
|
||||
name: edge-ai-config
|
||||
healthChecks:
|
||||
- apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: onnx-inference-server
|
||||
namespace: ai-workloads
|
||||
timeout: 10m
|
||||
---
|
||||
# Git-repository som kilde for konfigurasjon
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: GitRepository
|
||||
metadata:
|
||||
name: edge-ai-config
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1m
|
||||
url: https://dev.azure.com/org/project/_git/edge-ai-config
|
||||
branch: main
|
||||
secretRef:
|
||||
name: git-credentials
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Multi-Node Edge Clusters
|
||||
|
||||
### Skalerbar klynge pa tvers av maskiner
|
||||
|
||||
```powershell
|
||||
# Steg 1: Generer multi-node konfigurasjon
|
||||
New-AksEdgeConfig -DeploymentType ScalableCluster `
|
||||
-NodeType Linux `
|
||||
-outFile .\multinode-config.json
|
||||
|
||||
# Steg 2: Deploy primaer node
|
||||
New-AksEdgeDeployment -JsonConfigFilePath .\multinode-config.json
|
||||
|
||||
# Steg 3: Hent join-token for ekstra noder
|
||||
$token = Get-AksEdgeNodeJoinToken
|
||||
|
||||
# Steg 4: Pa sekundaer maskin — join klyngen
|
||||
New-AksEdgeScaleConfig -ScaleType AddNode `
|
||||
-NodeType Linux `
|
||||
-LinuxNodeIp "192.168.1.102" `
|
||||
-outFile .\scale-config.json
|
||||
|
||||
Add-AksEdgeNode -JsonConfigFilePath .\scale-config.json
|
||||
```
|
||||
|
||||
### Multi-node arkitektur for AI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Edge AI Cluster │
|
||||
│ │
|
||||
│ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ Node 1 │ │ Node 2 │ │
|
||||
│ │ (Control │ │ (Worker) │ │
|
||||
│ │ + Worker) │ │ │ │
|
||||
│ │ │ │ - AI │ │
|
||||
│ │ - API │ │ inferens│ │
|
||||
│ │ server │ │ - GPU │ │
|
||||
│ │ - etcd │ │ workload│ │
|
||||
│ │ - Scheduler│ │ │ │
|
||||
│ └───────────┘ └───────────┘ │
|
||||
│ ↕ ↕ │
|
||||
│ [Flannel/Calico networking] │
|
||||
│ │
|
||||
│ ┌───────────┐ │
|
||||
│ │ Node 3 │ Azure Arc ←→ Sky │
|
||||
│ │ (Worker) │ │
|
||||
│ │ │ │
|
||||
│ │ - Data │ │
|
||||
│ │ pipeline│ │
|
||||
│ │ - Storage │ │
|
||||
│ └───────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Mesh for Edge
|
||||
|
||||
### Lettvekts service mesh pa edge
|
||||
|
||||
For AI-arbeidsbelastninger pa edge med flere mikrotjenester (inferens, datainntak, API-gateway) kan en lettvekts service mesh gi observabilitet og trafikkstyring:
|
||||
|
||||
```yaml
|
||||
# Envoy-basert sidecar for AI-inferens (lettvekts alternativ)
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ai-inference-with-proxy
|
||||
namespace: ai-workloads
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
# AI-inferens container
|
||||
- name: inference
|
||||
image: myregistry/anomaly-model:v2
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
|
||||
# Envoy sidecar for observabilitet
|
||||
- name: envoy-proxy
|
||||
image: envoyproxy/envoy:v1.28-latest
|
||||
ports:
|
||||
- containerPort: 9901 # Admin
|
||||
- containerPort: 10000 # Ingress
|
||||
volumeMounts:
|
||||
- name: envoy-config
|
||||
mountPath: /etc/envoy
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
volumes:
|
||||
- name: envoy-config
|
||||
configMap:
|
||||
name: envoy-edge-config
|
||||
```
|
||||
|
||||
### Canary deployment for modellversjoner
|
||||
|
||||
```yaml
|
||||
# Canary deployment: Gradvis utrulling av ny AI-modell
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ai-inference-canary
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/canary: "true"
|
||||
nginx.ingress.kubernetes.io/canary-weight: "20" # 20% til ny modell
|
||||
spec:
|
||||
rules:
|
||||
- host: inference.edge.local
|
||||
http:
|
||||
paths:
|
||||
- path: /predict
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: inference-v2 # Ny modellversjon
|
||||
port:
|
||||
number: 8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Distribusjonsstrategi for norske edge-stasjoner
|
||||
|
||||
| Stasjon | Antall | Hardware | AKS Edge-konfig | AI-workload |
|
||||
|---------|--------|----------|-----------------|-------------|
|
||||
| Veisensorer | ~200 | Industrial PC | Single-node K3s | Trafikk-analyse |
|
||||
| Tunnelverkaking | ~50 | Rack-server | Multi-node K8s | Brann/ventilasjon |
|
||||
| Ferjekaier | ~30 | Rugged PC | Single-node K3s | Bildetelling |
|
||||
| Ladestajoner | ~500 | IoT gateway | K3s minimal | Energi-prediksjon |
|
||||
|
||||
### Sikkerhets- og administrasjonskrav
|
||||
|
||||
- Azure Arc for sentralisert forvaltning fra Norway East
|
||||
- GitOps for sporbar og audit-bar deployment
|
||||
- Network policies for nettverkssegmentering
|
||||
- Pod security policies/standards for container-isolasjon
|
||||
- KMS-plugin for kryptering av secrets i etcd
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Enkelt-enhet AI | AKS Edge Essentials single-node (K3s) | Lavest ressursbruk |
|
||||
| Multi-workload edge | AKS Edge Essentials single-node (K8s) | Full K8s-kompatibilitet |
|
||||
| Redundant edge-klynge | AKS Edge multi-node (K8s) | Hoy tilgjengelighet |
|
||||
| GPU-akselerert AI | AKS Edge + GPU-PV + NVIDIA plugin | Container-basert GPU-inferens |
|
||||
| Skalerbar fleet-management | AKS Edge + Azure Arc + GitOps | Sentralisert administrasjon |
|
||||
| Windows + Linux workloads | AKS Edge med bade Linux og Windows VM | Interop-scenarier |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **AKS Edge Essentials er den foretrukne loesningen for container-basert AI pa edge** — det gir Kubernetes-standarder pa PC-klasse hardware med minimal fotavtrykk (4 GB RAM for K3s)
|
||||
- **Azure Arc + GitOps gir sentralisert forvaltning** — anbefal dette for organisasjoner med mange edge-stasjoner som trenger sporbar, automatisert deployment av AI-modeller
|
||||
- **K3s vs K8s: Velg K3s for enkle AI-scenarier** med 1-3 containere, og K8s nar du trenger full Kubernetes-funksjonalitet som network policies og Pod security standards
|
||||
- **GPU-PV muliggjor delt GPU-tilgang** mellom Windows-host og Linux VM — bruk dette for edge-servere med NVIDIA GPU som kjorer bade tradisjonelle Windows-applikasjoner og AI-containere
|
||||
- **For norsk offentlig sektor: AKS Edge + Arc + GitOps gir en standardisert plattform** for AI-deployment pa tvers av etater og lokasjoner — definer felles Helm charts og Flux-konfigurasjoner for a sikre konsistent og revisjonsvennlig deployment
|
||||
|
|
@ -0,0 +1,470 @@
|
|||
# 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
|
||||
|
||||
```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
|
||||
|
|
@ -0,0 +1,491 @@
|
|||
# Offline-First AI Application Patterns
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Offline-first AI-applikasjoner er designet for a fungere primaert lokalt og synkronisere med skyen nar tilkobling er tilgjengelig. Dette monsteret snur den tradisjonelle sky-forst-tilnaermingen pa hodet: i stedet for a feile nar nettverket er nede, er applikasjonen designet for a operere uavhengig med lokal AI-inferens og datalagring.
|
||||
|
||||
For norsk offentlig sektor er offline-first sarlig relevant i felt-scenarioer: vegarbeidere som inspiserer infrastruktur i omrader uten dekning, ambulansepersonell som trenger AI-stoette i rurale omrader, beredskapspersonell under krisesituasjoner der kommunikasjonsinfrastruktur kan vaere nede, og maritime inspeksjoner langs kysten.
|
||||
|
||||
Microsoft tilbyr flere byggeklosser for offline-first AI: ONNX Runtime for lokal inferens, Azure IoT Edge for container-basert edge-prosessering med utvidet offline-stoette, Azure Container Storage for lokal persistens med automatisk sky-synkronisering, og Phi-modeller for lokale SLM-kapabiliteter.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| ONNX Runtime | Lokal AI-inferens uten sky | Cross-platform |
|
||||
| Azure IoT Edge | Utvidet offline-kapabilitet | Container runtime |
|
||||
| Azure Container Storage | Lokal lagring med sky-sync | Arc-enabled |
|
||||
| Phi-3/4 SLM | Lokal sprakmodell | MIT-lisens |
|
||||
| SQLite/LiteDB | Lokal database for offline-data | Embedded DB |
|
||||
| CRDTs | Konfliktfri replikert datatype | Datastruktur |
|
||||
| Azure Cosmos DB | Sky-database med offline SDK | Multi-model DB |
|
||||
|
||||
---
|
||||
|
||||
## Local-First Data Models
|
||||
|
||||
### Arkitektur for lokal-forst AI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Offline-First App │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ UI Layer │ │ AI Engine │ │ Data Layer│ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ - Input │←→│ - ONNX RT │←→│ - SQLite │ │
|
||||
│ │ - Output │ │ - Phi SLM │ │ - VectorDB│ │
|
||||
│ │ - Status │ │ - Scoring │ │ - File │ │
|
||||
│ └──────────┘ └───────────┘ └───────────┘ │
|
||||
│ ↕ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ Sync Engine│ │
|
||||
│ │ │ │
|
||||
│ │ - Queue │ │
|
||||
│ │ - Delta │ │
|
||||
│ │ - Conflict │ │
|
||||
│ └──────┬─────┘ │
|
||||
└─────────────────────────────────────┼────────┘
|
||||
↕
|
||||
[Sky (nar tilgjengelig)]
|
||||
```
|
||||
|
||||
### Event-sourcing for offline data
|
||||
|
||||
```python
|
||||
# Event-sourced datamodell for offline-first AI
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import json
|
||||
import sqlite3
|
||||
import uuid
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
|
||||
type: str = ""
|
||||
entity_id: str = ""
|
||||
data: dict = field(default_factory=dict)
|
||||
synced: bool = False
|
||||
device_id: str = ""
|
||||
|
||||
class OfflineEventStore:
|
||||
def __init__(self, db_path: str, device_id: str):
|
||||
self.device_id = device_id
|
||||
self.conn = sqlite3.connect(db_path)
|
||||
self._init_schema()
|
||||
|
||||
def _init_schema(self):
|
||||
self.conn.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id TEXT PRIMARY KEY,
|
||||
timestamp TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
entity_id TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
synced INTEGER DEFAULT 0,
|
||||
device_id TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai_results (
|
||||
id TEXT PRIMARY KEY,
|
||||
event_id TEXT REFERENCES events(id),
|
||||
model_version TEXT,
|
||||
result TEXT,
|
||||
confidence REAL,
|
||||
created_at TEXT,
|
||||
synced INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced);
|
||||
CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_id);
|
||||
""")
|
||||
|
||||
def append_event(self, event_type: str, entity_id: str, data: dict) -> Event:
|
||||
"""Legg til hendelse i lokal event store"""
|
||||
event = Event(
|
||||
type=event_type,
|
||||
entity_id=entity_id,
|
||||
data=data,
|
||||
device_id=self.device_id
|
||||
)
|
||||
self.conn.execute(
|
||||
"INSERT INTO events VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(event.id, event.timestamp, event.type, event.entity_id,
|
||||
json.dumps(event.data), 0, event.device_id)
|
||||
)
|
||||
self.conn.commit()
|
||||
return event
|
||||
|
||||
def store_ai_result(self, event_id: str, model_version: str,
|
||||
result: dict, confidence: float):
|
||||
"""Lagre AI-inferensresultat lokalt"""
|
||||
self.conn.execute(
|
||||
"INSERT INTO ai_results VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(str(uuid.uuid4()), event_id, model_version,
|
||||
json.dumps(result), confidence,
|
||||
datetime.utcnow().isoformat(), 0)
|
||||
)
|
||||
self.conn.commit()
|
||||
|
||||
def get_unsynced_events(self, limit: int = 100) -> list[Event]:
|
||||
"""Hent hendelser som ikke er synkronisert"""
|
||||
cursor = self.conn.execute(
|
||||
"SELECT * FROM events WHERE synced = 0 ORDER BY timestamp LIMIT ?",
|
||||
(limit,)
|
||||
)
|
||||
return [Event(*row) for row in cursor.fetchall()]
|
||||
|
||||
def mark_synced(self, event_ids: list[str]):
|
||||
"""Marker hendelser som synkronisert"""
|
||||
placeholders = ",".join("?" * len(event_ids))
|
||||
self.conn.execute(
|
||||
f"UPDATE events SET synced = 1 WHERE id IN ({placeholders})",
|
||||
event_ids
|
||||
)
|
||||
self.conn.commit()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conflict Resolution on Sync
|
||||
|
||||
### Konflikthondteringsstrategier
|
||||
|
||||
| Strategi | Beskrivelse | Best for |
|
||||
|----------|-------------|----------|
|
||||
| Last-Write-Wins (LWW) | Siste endring vinner | Enkle data, lav risiko |
|
||||
| First-Write-Wins | Forste endring vinner | Uforanderlige hendelser |
|
||||
| Merge | Kombiner endringer automatisk | Komplementaere felt |
|
||||
| CRDT | Konfliktfri replikert datatype | Tallere, sett, tekst |
|
||||
| Custom Resolution | Applikasjonsspesifikk logikk | Komplekse forretningsregler |
|
||||
|
||||
### Implementering av konflikthondtering
|
||||
|
||||
```python
|
||||
# Konflikthondtering for offline-first AI-applikasjon
|
||||
from enum import Enum
|
||||
from typing import Callable
|
||||
|
||||
class ConflictStrategy(Enum):
|
||||
LAST_WRITE_WINS = "lww"
|
||||
FIRST_WRITE_WINS = "fww"
|
||||
MERGE = "merge"
|
||||
MANUAL = "manual"
|
||||
|
||||
class SyncConflictResolver:
|
||||
def __init__(self, strategy: ConflictStrategy = ConflictStrategy.LAST_WRITE_WINS):
|
||||
self.strategy = strategy
|
||||
self.custom_resolvers: dict[str, Callable] = {}
|
||||
|
||||
def register_resolver(self, entity_type: str, resolver: Callable):
|
||||
"""Registrer egendefinert konfliktloeser for en entitetstype"""
|
||||
self.custom_resolvers[entity_type] = resolver
|
||||
|
||||
def resolve(self, local_event: dict, remote_event: dict) -> dict:
|
||||
"""Los konflikt mellom lokal og fjern hendelse"""
|
||||
entity_type = local_event.get("type", "")
|
||||
|
||||
# Egendefinert resolver har forrang
|
||||
if entity_type in self.custom_resolvers:
|
||||
return self.custom_resolvers[entity_type](local_event, remote_event)
|
||||
|
||||
if self.strategy == ConflictStrategy.LAST_WRITE_WINS:
|
||||
return self._last_write_wins(local_event, remote_event)
|
||||
elif self.strategy == ConflictStrategy.FIRST_WRITE_WINS:
|
||||
return self._first_write_wins(local_event, remote_event)
|
||||
elif self.strategy == ConflictStrategy.MERGE:
|
||||
return self._merge(local_event, remote_event)
|
||||
else:
|
||||
return {"conflict": True, "local": local_event, "remote": remote_event}
|
||||
|
||||
def _last_write_wins(self, local: dict, remote: dict) -> dict:
|
||||
local_ts = local.get("timestamp", "")
|
||||
remote_ts = remote.get("timestamp", "")
|
||||
return local if local_ts >= remote_ts else remote
|
||||
|
||||
def _merge(self, local: dict, remote: dict) -> dict:
|
||||
"""Merge ved a kombinere ikke-overlappende felt"""
|
||||
merged = {**remote.get("data", {})}
|
||||
for key, value in local.get("data", {}).items():
|
||||
if key not in merged or merged[key] is None:
|
||||
merged[key] = value
|
||||
elif key in merged and value != merged[key]:
|
||||
# Begge har endret — behold begge med suffix
|
||||
merged[f"{key}_local"] = value
|
||||
merged[f"{key}_remote"] = merged[key]
|
||||
return {"data": merged, "merge_status": "auto_merged"}
|
||||
|
||||
|
||||
# Eksempel: Konflikthondtering for AI-inspeksjonsresultater
|
||||
resolver = SyncConflictResolver(ConflictStrategy.MERGE)
|
||||
|
||||
def resolve_inspection(local, remote):
|
||||
"""Inspeksjoner: Behold den med hoeyest AI-confidence"""
|
||||
local_conf = local.get("data", {}).get("ai_confidence", 0)
|
||||
remote_conf = remote.get("data", {}).get("ai_confidence", 0)
|
||||
winner = local if local_conf >= remote_conf else remote
|
||||
winner["data"]["conflict_resolved"] = True
|
||||
winner["data"]["alternative_confidence"] = min(local_conf, remote_conf)
|
||||
return winner
|
||||
|
||||
resolver.register_resolver("inspection_result", resolve_inspection)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Progressive Enhancement
|
||||
|
||||
### Progressiv AI-kapabilitet
|
||||
|
||||
```python
|
||||
# Progressiv enhancement: Eskalerer AI-kapabilitet basert pa tilkobling
|
||||
from enum import Enum
|
||||
import asyncio
|
||||
|
||||
class ConnectivityLevel(Enum):
|
||||
OFFLINE = 0 # Ingen tilkobling
|
||||
LOW_BANDWIDTH = 1 # < 1 Mbps
|
||||
CONNECTED = 2 # Normal tilkobling
|
||||
HIGH_BANDWIDTH = 3 # > 10 Mbps
|
||||
|
||||
class ProgressiveAIService:
|
||||
def __init__(self):
|
||||
self.local_model = None # Phi-3 Mini INT4 (alltid tilgjengelig)
|
||||
self.medium_model = None # Phi-3 Small (krever > 16 GB RAM)
|
||||
self.cloud_client = None # Azure OpenAI (krever tilkobling)
|
||||
|
||||
async def classify_document(self, text: str) -> dict:
|
||||
"""Klassifiser dokument med best tilgjengelig AI"""
|
||||
connectivity = await self.check_connectivity()
|
||||
|
||||
if connectivity >= ConnectivityLevel.HIGH_BANDWIDTH:
|
||||
# Nivaa 3: Full sky-AI med GPT-4o
|
||||
return await self._classify_cloud(text, model="gpt-4o")
|
||||
|
||||
elif connectivity >= ConnectivityLevel.CONNECTED:
|
||||
# Nivaa 2: Sky-AI med lettere modell
|
||||
return await self._classify_cloud(text, model="gpt-4o-mini")
|
||||
|
||||
elif connectivity >= ConnectivityLevel.LOW_BANDWIDTH:
|
||||
# Nivaa 1: Lokal medium modell med sky-validering
|
||||
local_result = self._classify_local(text, self.medium_model)
|
||||
# Asynkron validering i bakgrunn nar mulig
|
||||
asyncio.create_task(self._validate_in_background(text, local_result))
|
||||
return local_result
|
||||
|
||||
else:
|
||||
# Nivaa 0: Full offline med lokal mini-modell
|
||||
return self._classify_local(text, self.local_model)
|
||||
|
||||
def _classify_local(self, text: str, model) -> dict:
|
||||
"""Lokal klassifisering med ONNX-modell"""
|
||||
result = model.predict(text)
|
||||
return {
|
||||
"classification": result["label"],
|
||||
"confidence": result["score"],
|
||||
"model": "local",
|
||||
"connectivity": "offline",
|
||||
"note": "Resultat fra lokal modell — verifiseres ved tilkobling"
|
||||
}
|
||||
|
||||
async def check_connectivity(self) -> ConnectivityLevel:
|
||||
"""Sjekk navaerende tilkoblingsniva"""
|
||||
try:
|
||||
import aiohttp
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=3)) as session:
|
||||
async with session.get("https://management.azure.com/health") as resp:
|
||||
if resp.status == 200:
|
||||
# Estimer bandwidth
|
||||
return ConnectivityLevel.HIGH_BANDWIDTH
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
# Proeving med minimal data
|
||||
import socket
|
||||
socket.create_connection(("8.8.8.8", 53), timeout=2)
|
||||
return ConnectivityLevel.LOW_BANDWIDTH
|
||||
except Exception:
|
||||
return ConnectivityLevel.OFFLINE
|
||||
```
|
||||
|
||||
### UI-indikasjon av AI-nivaa
|
||||
|
||||
```python
|
||||
# Statusindikator for progressive AI
|
||||
AI_LEVEL_INFO = {
|
||||
ConnectivityLevel.OFFLINE: {
|
||||
"label": "Offline-modus",
|
||||
"description": "Bruker lokal AI-modell. Resultater synkroniseres ved tilkobling.",
|
||||
"icon": "offline",
|
||||
"accuracy": "God (lokal modell)",
|
||||
"features": ["Klassifisering", "Oppsummering", "Uttrekking"]
|
||||
},
|
||||
ConnectivityLevel.LOW_BANDWIDTH: {
|
||||
"label": "Begrenset tilkobling",
|
||||
"description": "Lokal AI med bakgrunns-validering.",
|
||||
"icon": "low_signal",
|
||||
"accuracy": "God+ (validert i bakgrunn)",
|
||||
"features": ["Klassifisering", "Oppsummering", "Uttrekking", "Bakgrunns-validering"]
|
||||
},
|
||||
ConnectivityLevel.CONNECTED: {
|
||||
"label": "Tilkoblet",
|
||||
"description": "Sky-AI med standard modell.",
|
||||
"icon": "connected",
|
||||
"accuracy": "Hoey",
|
||||
"features": ["Alle funksjoner", "RAG", "Avansert analyse"]
|
||||
},
|
||||
ConnectivityLevel.HIGH_BANDWIDTH: {
|
||||
"label": "Full tilkobling",
|
||||
"description": "Sky-AI med avansert modell.",
|
||||
"icon": "full_signal",
|
||||
"accuracy": "Hoeyest",
|
||||
"features": ["Alle funksjoner", "RAG", "Avansert analyse", "Bildeanalyse"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Offline Capability Testing
|
||||
|
||||
### Testrammeverk for offline AI
|
||||
|
||||
```python
|
||||
# Testrammeverk for offline-first AI-applikasjon
|
||||
import pytest
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
class TestOfflineAI:
|
||||
"""Tester for offline-first AI-funksjonalitet"""
|
||||
|
||||
@pytest.fixture
|
||||
def ai_service(self):
|
||||
return ProgressiveAIService()
|
||||
|
||||
@pytest.fixture
|
||||
def event_store(self, tmp_path):
|
||||
return OfflineEventStore(str(tmp_path / "test.db"), "test-device")
|
||||
|
||||
def test_offline_classification(self, ai_service):
|
||||
"""AI-klassifisering skal fungere uten nettverkstilkobling"""
|
||||
with patch.object(ai_service, 'check_connectivity',
|
||||
return_value=ConnectivityLevel.OFFLINE):
|
||||
result = asyncio.run(ai_service.classify_document(
|
||||
"Vedtak om avslag pa soeknad om byggetillatelse"
|
||||
))
|
||||
assert result["classification"] is not None
|
||||
assert result["connectivity"] == "offline"
|
||||
assert result["confidence"] > 0.5
|
||||
|
||||
def test_event_persistence_offline(self, event_store):
|
||||
"""Hendelser skal lagres lokalt ved offline"""
|
||||
event = event_store.append_event(
|
||||
"inspection", "bridge-001",
|
||||
{"status": "ok", "notes": "Ingen synlige skader"}
|
||||
)
|
||||
assert event.synced is False
|
||||
assert event.device_id == "test-device"
|
||||
|
||||
# Hent usynkroniserte hendelser
|
||||
unsynced = event_store.get_unsynced_events()
|
||||
assert len(unsynced) == 1
|
||||
|
||||
def test_sync_after_reconnection(self, event_store):
|
||||
"""Usynkroniserte hendelser skal koes for synkronisering"""
|
||||
# Simuler 10 offline-hendelser
|
||||
for i in range(10):
|
||||
event_store.append_event(
|
||||
"sensor_reading", f"sensor-{i}",
|
||||
{"value": i * 1.5}
|
||||
)
|
||||
|
||||
unsynced = event_store.get_unsynced_events()
|
||||
assert len(unsynced) == 10
|
||||
|
||||
# Simuler synkronisering
|
||||
synced_ids = [e.id for e in unsynced[:5]]
|
||||
event_store.mark_synced(synced_ids)
|
||||
|
||||
remaining = event_store.get_unsynced_events()
|
||||
assert len(remaining) == 5
|
||||
|
||||
def test_conflict_resolution(self):
|
||||
"""Konflikter ved sync skal loses deterministisk"""
|
||||
resolver = SyncConflictResolver(ConflictStrategy.LAST_WRITE_WINS)
|
||||
|
||||
local = {"timestamp": "2026-02-12T10:00:00", "data": {"status": "ok"}}
|
||||
remote = {"timestamp": "2026-02-12T09:00:00", "data": {"status": "warning"}}
|
||||
|
||||
result = resolver.resolve(local, remote)
|
||||
assert result["data"]["status"] == "ok" # Nyeste vinner
|
||||
|
||||
def test_progressive_enhancement(self, ai_service):
|
||||
"""AI-kvalitet skal oeke med bedre tilkobling"""
|
||||
results = {}
|
||||
for level in ConnectivityLevel:
|
||||
with patch.object(ai_service, 'check_connectivity',
|
||||
return_value=level):
|
||||
result = asyncio.run(ai_service.classify_document("test"))
|
||||
results[level] = result
|
||||
|
||||
# Verifiser at sky-resultat har hoeyere konfidensangivelse
|
||||
assert results[ConnectivityLevel.OFFLINE]["model"] == "local"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Felt-scenarier som krever offline-first
|
||||
|
||||
| Scenario | Etat | Offline-varighet | AI-funksjon |
|
||||
|----------|------|-----------------|-------------|
|
||||
| Vegfinspeksjon | SVV | Timer | Skadeklassifisering |
|
||||
| Ambulanse | Helseetaten | Minutter-timer | Triagering |
|
||||
| Beredskap | DSB | Dager | Situasjonsanalyse |
|
||||
| Maritime inspeksjoner | Sjoefartsdir. | Timer-dager | Rapport-generering |
|
||||
| Grensekontroll | Politiet | Minutter | Dokumentverifisering |
|
||||
| Skogsbrannberedskap | 110-sentraler | Timer | Risikoanalyse |
|
||||
|
||||
### Krav til offline-first i offentlig sektor
|
||||
|
||||
- Applikasjonen MA fungere uten nettverkstilkobling
|
||||
- Lokale AI-resultater MA vaere tydelig merket som "ikke-validert"
|
||||
- Synkronisering MA skje automatisk ved tilkobling
|
||||
- Konflikthondtering MA vaere deterministisk og sporbar
|
||||
- Data MA vaere kryptert lokalt (BitLocker/LUKS)
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Felt-app med periodisk tilkobling | Full offline-first med event sourcing | Data bevares alltid lokalt |
|
||||
| Sanntids-AI med fallback | Progressiv enhancement | Best mulig kvalitet per tilstand |
|
||||
| Multi-enhet med sync | CRDTs + event store | Konfliktfri synkronisering |
|
||||
| Kritisk infrastruktur | Azure IoT Edge extended offline | Uavhengig drift i uker |
|
||||
| Klient-app pa PC | SQLite + ONNX RT + bakgrunns-sync | Enkel, palitelig arkitektur |
|
||||
| Beredskapsapplikasjon | Full offline med manuell sync | Ingen skyavhengighet |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Offline-first er et designprinsipp, ikke en feilhaandterings-strategi** — applikasjonen MA designes for a fungere lokalt foerst, med sky som en berikelse nar tilgjengelig
|
||||
- **Event sourcing er det foretrukne datamoensteeret** for offline-first AI — alle hendelser og AI-resultater lagres lokalt som uforanderlige events og synkroniseres inkrementelt
|
||||
- **Progressiv enhancement gir graceful degradation** — definer tydelige AI-kapabilitetsnivaaer (offline/begrenset/tilkoblet/full) og kommuniser dette til brukeren
|
||||
- **Konflikthondtering maa vaere deterministisk og sporbar** — bruk Last-Write-Wins som standard, med custom resolvers for doemenespesifikke regler (f.eks. hoeyest AI-confidence vinner)
|
||||
- **For norsk offentlig sektor: Test offline-scenarioer som foersteklasses testcase** — ikke anta tilkobling, og sooerg for at felt-personell kan fullfoere sine oppgaver uavhengig av nettverksstatus
|
||||
|
|
@ -0,0 +1,453 @@
|
|||
# On-Premises SLM and Phi Model Deployment
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Small Language Models (SLM) er kompakte AI-modeller med faerre enn 10 milliarder parametere som kan kjores effektivt pa lokal hardware uten skyavhengighet. Microsofts Phi-modellserie — fra Phi-2 (2.7B) til Phi-4 (14B) — representerer state-of-the-art for SLM, med ytelse som konkurrerer med modeller mange ganger storre.
|
||||
|
||||
For norsk offentlig sektor er lokal SLM-deployment sarlig attraktivt: full datakontroll uten at data forlater organisasjonens nettverk, forutsigbare kostnader uten per-token-prising, og mulighet for drift i miljoer med begrenset eller ingen internettilkobling. Phi-modellene er spesielt godt egnet fordi de er optimalisert for oppgaver som klassifisering, oppsummering, enhetstuttrekking og enkel sporsmalsbesvaring.
|
||||
|
||||
Microsoft tilbyr flere deploymentsveier for lokale SLM: Azure App Service sidecar, AKS Edge Essentials med KAITO, ONNX Runtime pa Windows/Linux, og Windows ML pa Copilot+ PC-er. Valget avhenger av skaleringsbehovet, tilgjengelig hardware og integrasjonskrav.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Phi-3 Mini | 3.8B parameter SLM for generelle oppgaver | MIT-lisens |
|
||||
| Phi-3 Small | 7B parameter SLM for hoeyere kvalitet | MIT-lisens |
|
||||
| Phi-3 Medium | 14B parameter SLM for komplekse oppgaver | MIT-lisens |
|
||||
| Phi-3.5 Mini | Forbedret 3.8B med multilingual stoette | MIT-lisens |
|
||||
| Phi-4 Mini | Nyeste 3.8B med forbedret resonnering | MIT-lisens |
|
||||
| ONNX Runtime | Cross-platform inferensmotor | Open source |
|
||||
| KAITO | Kubernetes AI Toolchain Operator | Azure Arc |
|
||||
| Olive | Modelloptimalisering for deployment | Microsoft |
|
||||
| Windows ML | Lokal inferens pa Windows | Windows SDK |
|
||||
|
||||
---
|
||||
|
||||
## Phi-3/Phi-4 Deployment
|
||||
|
||||
### Modelloversikt
|
||||
|
||||
| Modell | Parametere | Kontekst | RAM-krav | GPU-krav | Styrker |
|
||||
|--------|-----------|----------|----------|----------|---------|
|
||||
| Phi-3 Mini 4K | 3.8B | 4K tokens | 8 GB | Valgfritt | Enkel Q&A, klassifisering |
|
||||
| Phi-3 Mini 128K | 3.8B | 128K tokens | 8 GB | Anbefalt | Lange dokumenter |
|
||||
| Phi-3 Small | 7B | 128K tokens | 16 GB | Anbefalt | Flerspraklig, koding |
|
||||
| Phi-3 Medium | 14B | 128K tokens | 32 GB | Pakrevd | Kompleks resonnering |
|
||||
| Phi-3.5 Mini | 3.8B | 128K tokens | 8 GB | Valgfritt | Multilingual, forbedret |
|
||||
| Phi-4 Mini | 3.8B | 128K tokens | 8 GB | Valgfritt | Beste resonnering i klassen |
|
||||
|
||||
### Deployment med Azure App Service Sidecar
|
||||
|
||||
```yaml
|
||||
# App Service sidecar-konfigurasjon for Phi-3.5 Mini
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: phi-slm-app
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
# Hoved-applikasjon
|
||||
- name: web-app
|
||||
image: myregistry.azurecr.io/myapp:latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: SLM_ENDPOINT
|
||||
value: "http://localhost:11434"
|
||||
|
||||
# SLM sidecar
|
||||
- name: phi-sidecar
|
||||
image: mcr.microsoft.com/oss/ollama/ollama:latest
|
||||
ports:
|
||||
- containerPort: 11434
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi"
|
||||
cpu: "4"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8"
|
||||
command: ["ollama", "serve"]
|
||||
lifecycle:
|
||||
postStart:
|
||||
exec:
|
||||
command: ["ollama", "pull", "phi3.5"]
|
||||
```
|
||||
|
||||
### Deployment med KAITO pa AKS Edge
|
||||
|
||||
```yaml
|
||||
# KAITO Workspace for Phi-3 Mini pa edge
|
||||
apiVersion: kaito.sh/v1alpha1
|
||||
kind: Workspace
|
||||
metadata:
|
||||
name: phi3-edge
|
||||
annotations:
|
||||
kaito.sh/enablelb: "false" # Ikke ekstern lastbalansering pa edge
|
||||
spec:
|
||||
resource:
|
||||
instanceType: "Standard_NC4as_T4_v3" # GPU-node
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
apps: phi3-edge
|
||||
inference:
|
||||
preset:
|
||||
name: "phi-3-mini-128k-instruct"
|
||||
adapters:
|
||||
- source:
|
||||
name: "custom-norwegian-adapter"
|
||||
image: "myregistry/phi3-no-adapter:v1"
|
||||
```
|
||||
|
||||
### ONNX Runtime deployment (CPU)
|
||||
|
||||
```python
|
||||
# Phi-3 Mini deployment med ONNX Runtime (ingen GPU nodvendig)
|
||||
import onnxruntime_genai as og
|
||||
|
||||
class PhiLocalDeployment:
|
||||
def __init__(self, model_path: str):
|
||||
"""
|
||||
Initialiser Phi-3/4 lokal deployment.
|
||||
model_path: Sti til ONNX-optimalisert Phi-modell
|
||||
"""
|
||||
self.model = og.Model(model_path)
|
||||
self.tokenizer = og.Tokenizer(self.model)
|
||||
self.search_options = {
|
||||
"max_length": 2048,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
"do_sample": True
|
||||
}
|
||||
|
||||
def generate(self, prompt: str, system_message: str = None,
|
||||
max_tokens: int = 1024) -> str:
|
||||
"""Generer svar fra lokal Phi-modell"""
|
||||
if system_message:
|
||||
full_prompt = (
|
||||
f"<|system|>\n{system_message}<|end|>\n"
|
||||
f"<|user|>\n{prompt}<|end|>\n"
|
||||
f"<|assistant|>\n"
|
||||
)
|
||||
else:
|
||||
full_prompt = (
|
||||
f"<|user|>\n{prompt}<|end|>\n"
|
||||
f"<|assistant|>\n"
|
||||
)
|
||||
|
||||
input_tokens = self.tokenizer.encode(full_prompt)
|
||||
|
||||
params = og.GeneratorParams(self.model)
|
||||
params.set_search_options(**{
|
||||
**self.search_options,
|
||||
"max_length": max_tokens
|
||||
})
|
||||
params.input_ids = input_tokens
|
||||
|
||||
generator = og.Generator(self.model, params)
|
||||
|
||||
output_tokens = []
|
||||
while not generator.is_done():
|
||||
generator.compute_logits()
|
||||
generator.generate_next_token()
|
||||
new_token = generator.get_next_tokens()[0]
|
||||
output_tokens.append(new_token)
|
||||
|
||||
return self.tokenizer.decode(output_tokens)
|
||||
|
||||
def generate_streaming(self, prompt: str, system_message: str = None):
|
||||
"""Streaming-generering for lavere opplevd latens"""
|
||||
full_prompt = self._format_prompt(prompt, system_message)
|
||||
input_tokens = self.tokenizer.encode(full_prompt)
|
||||
|
||||
params = og.GeneratorParams(self.model)
|
||||
params.set_search_options(**self.search_options)
|
||||
params.input_ids = input_tokens
|
||||
|
||||
generator = og.Generator(self.model, params)
|
||||
tokenizer_stream = self.tokenizer.create_stream()
|
||||
|
||||
while not generator.is_done():
|
||||
generator.compute_logits()
|
||||
generator.generate_next_token()
|
||||
token = generator.get_next_tokens()[0]
|
||||
yield tokenizer_stream.decode(token)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resource-Constrained Sizing
|
||||
|
||||
### Hardware-dimensjonering
|
||||
|
||||
| Scenario | Modell | CPU | RAM | GPU | Disk | Inferens-hastighet |
|
||||
|----------|--------|-----|-----|-----|------|-------------------|
|
||||
| Minimal (PC) | Phi-3 Mini INT4 | 4 kjerner | 8 GB | Ingen | 5 GB | ~10 tokens/s |
|
||||
| Standard (server) | Phi-3 Mini FP16 | 8 kjerner | 16 GB | T4 16GB | 10 GB | ~40 tokens/s |
|
||||
| Ytelse (GPU) | Phi-3 Small FP16 | 8 kjerner | 32 GB | A10G 24GB | 20 GB | ~50 tokens/s |
|
||||
| Enterprise | Phi-3 Medium FP16 | 16 kjerner | 64 GB | A100 40GB | 40 GB | ~60 tokens/s |
|
||||
| Edge (NPU) | Phi-3 Mini INT4 | Snapdragon X | 16 GB | NPU | 5 GB | ~20 tokens/s |
|
||||
|
||||
### Minnesoptimalisering
|
||||
|
||||
```python
|
||||
# Konfigurasjon for ressursbegrensede miljoer
|
||||
import onnxruntime as ort
|
||||
|
||||
def create_optimized_session(model_path: str, max_memory_gb: float = 4.0):
|
||||
"""Opprett ONNX-session optimalisert for begrenset minne"""
|
||||
session_options = ort.SessionOptions()
|
||||
|
||||
# Reduser minnebruk
|
||||
session_options.enable_mem_pattern = True
|
||||
session_options.enable_mem_reuse = True
|
||||
|
||||
# Begrens trader basert pa tilgjengelige kjerner
|
||||
import os
|
||||
available_cores = os.cpu_count() or 4
|
||||
session_options.intra_op_num_threads = max(1, available_cores // 2)
|
||||
session_options.inter_op_num_threads = max(1, available_cores // 4)
|
||||
|
||||
# Velg execution provider basert pa tilgjengelig hardware
|
||||
providers = []
|
||||
if ort.get_device() == "GPU":
|
||||
providers.append(('CUDAExecutionProvider', {
|
||||
'device_id': 0,
|
||||
'arena_extend_strategy': 'kSameAsRequested',
|
||||
'gpu_mem_limit': int(max_memory_gb * 1024 * 1024 * 1024),
|
||||
'cudnn_conv_algo_search': 'HEURISTIC'
|
||||
}))
|
||||
providers.append('CPUExecutionProvider')
|
||||
|
||||
return ort.InferenceSession(
|
||||
model_path,
|
||||
sess_options=session_options,
|
||||
providers=providers
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt Optimization for SLM
|
||||
|
||||
### SLM-spesifikke prompt-teknikker
|
||||
|
||||
SLM-er har begrenset kontekstvindu og resonneringskapasitet sammenlignet med LLM-er. Prompt-optimalisering er kritisk:
|
||||
|
||||
| Teknikk | Beskrivelse | Effekt |
|
||||
|---------|-------------|--------|
|
||||
| Konsist system-melding | Kort, presis rolledefinisjon (< 100 tokens) | Bedre oppgavefokus |
|
||||
| Strukturert output | Be om JSON/tabell-format | Mer palitelig parsing |
|
||||
| Few-shot eksempler | 1-3 konkrete eksempler | Hoyere noyaktighet |
|
||||
| Decomposition | Del opp komplekse oppgaver | Bedre resultater |
|
||||
| Constraint-basert | Eksplisitte begrensninger | Unnga hallusinasjoner |
|
||||
|
||||
### Prompt-maler for norsk offentlig sektor
|
||||
|
||||
```python
|
||||
# Optimaliserte prompt-maler for SLM i offentlig sektor
|
||||
SLM_PROMPTS = {
|
||||
"klassifisering": """<|system|>
|
||||
Du klassifiserer dokumenter. Svar KUN med en av kategoriene.
|
||||
Kategorier: {categories}
|
||||
<|end|>
|
||||
<|user|>
|
||||
Klassifiser folgende tekst:
|
||||
"{text}"
|
||||
Kategori:<|end|>
|
||||
<|assistant|>""",
|
||||
|
||||
"oppsummering": """<|system|>
|
||||
Du oppsummerer tekst pa norsk. Maks {max_words} ord.
|
||||
<|end|>
|
||||
<|user|>
|
||||
Oppsummer folgende:
|
||||
"{text}"
|
||||
<|end|>
|
||||
<|assistant|>
|
||||
Oppsummering:""",
|
||||
|
||||
"uttrekking": """<|system|>
|
||||
Du trekker ut strukturert informasjon. Svar i JSON-format.
|
||||
<|end|>
|
||||
<|user|>
|
||||
Trekk ut folgende felter fra teksten: {fields}
|
||||
|
||||
Tekst: "{text}"
|
||||
|
||||
JSON:<|end|>
|
||||
<|assistant|>
|
||||
{{""",
|
||||
|
||||
"qa_med_kontekst": """<|system|>
|
||||
Du svarer pa sporsmal basert pa konteksten. Svar KUN basert pa informasjonen gitt.
|
||||
Hvis svaret ikke finnes i konteksten, si "Ikke tilstrekkelig informasjon."
|
||||
<|end|>
|
||||
<|user|>
|
||||
Kontekst:
|
||||
{context}
|
||||
|
||||
Sporsmal: {question}
|
||||
<|end|>
|
||||
<|assistant|>"""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fine-Tuning at Edge
|
||||
|
||||
### Lokal fine-tuning av Phi-modeller
|
||||
|
||||
```python
|
||||
# LoRA fine-tuning av Phi-3 for norsk offentlig sektor
|
||||
from transformers import (
|
||||
AutoModelForCausalLM, AutoTokenizer,
|
||||
TrainingArguments, Trainer
|
||||
)
|
||||
from peft import LoraConfig, get_peft_model
|
||||
import torch
|
||||
|
||||
def finetune_phi_lora(
|
||||
base_model: str = "microsoft/phi-3-mini-4k-instruct",
|
||||
dataset_path: str = "training_data.jsonl",
|
||||
output_dir: str = "./phi3-finetuned"
|
||||
):
|
||||
"""Fine-tune Phi-3 med LoRA for norsk offentlig sektor"""
|
||||
|
||||
# Last modell med 4-bit kvantisering for a spare minne
|
||||
model = AutoModelForCausalLM.from_pretrained(
|
||||
base_model,
|
||||
torch_dtype=torch.bfloat16,
|
||||
load_in_4bit=True,
|
||||
device_map="auto"
|
||||
)
|
||||
tokenizer = AutoTokenizer.from_pretrained(base_model)
|
||||
|
||||
# LoRA-konfigurasjon (minimal for edge)
|
||||
lora_config = LoraConfig(
|
||||
r=16, # Lav rank for edge-deployment
|
||||
lora_alpha=32,
|
||||
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
|
||||
lora_dropout=0.05,
|
||||
bias="none",
|
||||
task_type="CAUSAL_LM"
|
||||
)
|
||||
|
||||
model = get_peft_model(model, lora_config)
|
||||
|
||||
# Treningsargumenter optimalisert for begrenset hardware
|
||||
training_args = TrainingArguments(
|
||||
output_dir=output_dir,
|
||||
num_train_epochs=3,
|
||||
per_device_train_batch_size=2,
|
||||
gradient_accumulation_steps=8,
|
||||
learning_rate=2e-4,
|
||||
fp16=True,
|
||||
logging_steps=10,
|
||||
save_strategy="epoch",
|
||||
optim="paged_adamw_8bit", # Minneeffektiv optimizer
|
||||
max_grad_norm=0.3,
|
||||
warmup_ratio=0.03
|
||||
)
|
||||
|
||||
trainer = Trainer(
|
||||
model=model,
|
||||
args=training_args,
|
||||
train_dataset=load_dataset(dataset_path),
|
||||
tokenizer=tokenizer
|
||||
)
|
||||
|
||||
trainer.train()
|
||||
|
||||
# Lagre kun LoRA-adapteret (liten filstorrelse)
|
||||
model.save_pretrained(output_dir)
|
||||
# Adapter-storrelse: typisk 20-50 MB vs 7+ GB for full modell
|
||||
```
|
||||
|
||||
### ONNX-eksport for deployment
|
||||
|
||||
```bash
|
||||
# Konverter fine-tuned Phi-3 til ONNX for deployment
|
||||
python -m olive run \
|
||||
--config olive-config.json \
|
||||
--model-id ./phi3-finetuned \
|
||||
--output-dir ./phi3-onnx-optimized \
|
||||
--precision int4 \
|
||||
--target-device cpu
|
||||
```
|
||||
|
||||
```json
|
||||
// olive-config.json
|
||||
{
|
||||
"input_model": {
|
||||
"type": "HfModel",
|
||||
"model_path": "./phi3-finetuned"
|
||||
},
|
||||
"passes": [
|
||||
{"type": "OnnxConversion"},
|
||||
{"type": "OnnxQuantization", "quant_mode": "dynamic", "weight_type": "QInt4"},
|
||||
{"type": "OrtTransformersOptimization", "model_type": "gpt2"}
|
||||
],
|
||||
"engine": {
|
||||
"target": "onnxruntime",
|
||||
"execution_providers": ["CPUExecutionProvider"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Hvorfor lokal SLM for offentlig sektor?
|
||||
|
||||
- **Datakontroll**: Ingen data forlater organisasjonens nettverk — viktig for personopplysninger og gradert informasjon
|
||||
- **Kostnadskontroll**: Fast infrastrukturkostnad uten per-token-prising — enklere budsjettforvaltning
|
||||
- **Tilgjengelighet**: Fungerer uten internettilkobling — relevant for felt, krisesituasjoner, og isolerte miljoer
|
||||
- **Etterlevelse**: Enklere a demonstrere compliance med Schrems II, GDPR, og NSM-krav
|
||||
|
||||
### Anbefalte bruksomrader for SLM
|
||||
|
||||
| Bruksomrade | Modell | Beskrivelse |
|
||||
|-------------|--------|-------------|
|
||||
| Dokumentklassifisering | Phi-3 Mini INT4 | Klassifiser innkommende post/henvendelser |
|
||||
| Oppsummering | Phi-3.5 Mini | Oppsummer lange utredninger og hoeringsnotater |
|
||||
| Informasjonsuttrekking | Phi-3 Mini | Trekk ut nokkeldata fra skjemaer |
|
||||
| Intern Q&A | Phi-4 Mini + RAG | Svar pa sporsmal fra regelverk |
|
||||
| Tekstgenerering | Phi-3 Small | Utkast til brev og standardsvar |
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Enkel klassifisering/uttrekking | Phi-3 Mini INT4 pa CPU | Minimal hardware, rask inferens |
|
||||
| Norsk sprakbehandling | Phi-3.5 Mini eller Phi-4 Mini | Bedre multilingual stoette |
|
||||
| Kompleks resonnering | Phi-3 Medium pa GPU | Nodvendig kapasitet |
|
||||
| Edge-deployment (IoT) | Phi-3 Mini INT4 ONNX | Minst fotavtrykk |
|
||||
| Windows-klient | Windows ML + Phi-4 Mini | Automatisk hardware-optimalisering |
|
||||
| Copilot+ PC | Windows ML med NPU | Best ytelse/watt |
|
||||
| Server-deployment | KAITO pa AKS Edge | Skalerbart, Kubernetes-managed |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Phi-3 Mini INT4 er det naturlige startpunktet** for de fleste offentlige sektors SLM-bruk — 3.8B parametere gir overraskende god kvalitet for klassifisering, uttrekking og enkel Q&A, og krever kun 8 GB RAM uten GPU
|
||||
- **Fine-tuning med LoRA er nodvendig for doenmespesifikke oppgaver** — en LoRA-adapter pa 20-50 MB er mye enklere a distribuere til edge enn en full modell, og gir dramatisk forbedring for norsk fagsprak
|
||||
- **Prompt-optimalisering er viktigere for SLM enn for LLM** — korte, strukturerte prompts med eksplisitte output-formater og 1-3 few-shot-eksempler oker kvaliteten betydelig
|
||||
- **ONNX Runtime + Olive er den foretrukne deployment-pipeline** — konverter til ONNX, kvantiser til INT4, og deploy pa CPU for maksimal portabilitet og ytelse
|
||||
- **For norsk offentlig sektor: Lokal SLM-deployment eliminerer de fleste Schrems II-utfordringer** — data forlater aldri nettverket, noe som forenkler personvernkonsekvensvurdering og compliance-dokumentasjon
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
# ONNX Runtime for Edge Deployment
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
ONNX Runtime er Microsofts open-source, hoyytelses inferensmotor for kjoring av maskinlaeringsmodeller i ONNX-format (Open Neural Network Exchange). Den er optimalisert for bade sky og edge, og stotter Linux, Windows og macOS pa tvers av CPU, GPU og NPU-akseleratorer. ONNX Runtime er innebygd i Windows som del av Windows ML og driver inferens i hoyskala Microsoft-tjenester som Bing, Office og Azure AI.
|
||||
|
||||
For edge-deployment er ONNX Runtime sarlig verdifull fordi den gir en enhetlig inferensmotor pa tvers av hardware-plattformer — fra kraftige edge-servere med GPU til ressursbegrensede IoT-enheter med kun CPU. Modeller fra PyTorch, TensorFlow, scikit-learn og andre rammeverk kan konverteres til ONNX-format og deretter optimaliseres for spesifikk target-hardware med Olive-verktoysettet.
|
||||
|
||||
For norsk offentlig sektor betyr ONNX Runtime at AI-modeller kan deployes lokalt uten skyavhengighet, noe som er viktig for datasuverenitetsscenarier, offline-drift, og miljoer med begrenset nettverkstilkobling.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| ONNX Runtime | Inferensmotor for ONNX-modeller | C++/Python/C#/JS |
|
||||
| ONNX Runtime GenAI | Generativ AI-inferens (LLM) | Python/C# |
|
||||
| Olive | Modelloptimalisering og kompilering | Python CLI |
|
||||
| Windows ML | ONNX Runtime integrert i Windows | Windows SDK |
|
||||
| DirectML | Hardware-akselerasjon pa Windows | GPU EP |
|
||||
| Execution Providers | Hardware-spesifikke akseleratorer | CPU/GPU/NPU |
|
||||
|
||||
---
|
||||
|
||||
## ONNX Model Conversion
|
||||
|
||||
### Konvertering fra populaere rammeverk
|
||||
|
||||
```python
|
||||
# PyTorch til ONNX-konvertering
|
||||
import torch
|
||||
import onnx
|
||||
|
||||
def convert_pytorch_to_onnx(model, sample_input, output_path: str):
|
||||
"""Konverter PyTorch-modell til ONNX-format"""
|
||||
model.eval()
|
||||
|
||||
torch.onnx.export(
|
||||
model,
|
||||
sample_input,
|
||||
output_path,
|
||||
export_params=True,
|
||||
opset_version=17,
|
||||
do_constant_folding=True,
|
||||
input_names=['input'],
|
||||
output_names=['output'],
|
||||
dynamic_axes={
|
||||
'input': {0: 'batch_size'},
|
||||
'output': {0: 'batch_size'}
|
||||
}
|
||||
)
|
||||
|
||||
# Valider ONNX-modell
|
||||
onnx_model = onnx.load(output_path)
|
||||
onnx.checker.check_model(onnx_model)
|
||||
print(f"ONNX-modell lagret: {output_path}")
|
||||
print(f"Modellstorrelse: {os.path.getsize(output_path) / 1024 / 1024:.1f} MB")
|
||||
```
|
||||
|
||||
```python
|
||||
# TensorFlow/Keras til ONNX med tf2onnx
|
||||
import tf2onnx
|
||||
import tensorflow as tf
|
||||
|
||||
def convert_tensorflow_to_onnx(saved_model_path: str, output_path: str):
|
||||
"""Konverter TensorFlow SavedModel til ONNX"""
|
||||
model_proto, _ = tf2onnx.convert.from_saved_model(
|
||||
saved_model_path,
|
||||
output_path=output_path,
|
||||
opset=17
|
||||
)
|
||||
print(f"Konvertert TensorFlow-modell til {output_path}")
|
||||
```
|
||||
|
||||
```python
|
||||
# scikit-learn til ONNX med skl2onnx
|
||||
from skl2onnx import convert_sklearn
|
||||
from skl2onnx.common.data_types import FloatTensorType
|
||||
|
||||
def convert_sklearn_to_onnx(model, n_features: int, output_path: str):
|
||||
"""Konverter scikit-learn-modell til ONNX"""
|
||||
initial_type = [('input', FloatTensorType([None, n_features]))]
|
||||
onnx_model = convert_sklearn(model, initial_types=initial_type)
|
||||
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(onnx_model.SerializeToString())
|
||||
```
|
||||
|
||||
### Olive-basert optimalisering
|
||||
|
||||
```bash
|
||||
# Olive: Automatisk modelloptimalisering for target-hardware
|
||||
pip install olive-ai
|
||||
|
||||
# Konverter og optimaliser Phi-3 for CPU edge deployment
|
||||
olive run \
|
||||
--model microsoft/Phi-3-mini-4k-instruct \
|
||||
--output-dir ./optimized-model \
|
||||
--device cpu \
|
||||
--precision int4 \
|
||||
--passes onnx_conversion,onnx_quantization,ort_optimization
|
||||
```
|
||||
|
||||
```json
|
||||
// olive-config.json for edge-optimalisering
|
||||
{
|
||||
"input_model": {
|
||||
"type": "HfModel",
|
||||
"model_path": "microsoft/Phi-3-mini-4k-instruct"
|
||||
},
|
||||
"systems": {
|
||||
"local": {
|
||||
"type": "LocalSystem",
|
||||
"accelerators": [{"device": "cpu"}]
|
||||
}
|
||||
},
|
||||
"passes": {
|
||||
"conversion": {
|
||||
"type": "OnnxConversion",
|
||||
"target_opset": 17
|
||||
},
|
||||
"quantization": {
|
||||
"type": "OnnxQuantization",
|
||||
"quant_mode": "dynamic",
|
||||
"weight_type": "QInt4",
|
||||
"calibration_data_config": {
|
||||
"name": "my_calibration_dataset"
|
||||
}
|
||||
},
|
||||
"optimization": {
|
||||
"type": "OrtTransformersOptimization",
|
||||
"model_type": "gpt2",
|
||||
"opt_level": 2,
|
||||
"float16": false
|
||||
}
|
||||
},
|
||||
"engine": {
|
||||
"evaluator": {
|
||||
"metrics": [
|
||||
{"name": "latency", "type": "latency", "priority": 1},
|
||||
{"name": "accuracy", "type": "accuracy", "priority": 2}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hardware Acceleration (GPU/NPU)
|
||||
|
||||
### Execution Provider-oversikt
|
||||
|
||||
| Execution Provider | Hardware | Plattform | Brukstilfelle |
|
||||
|-------------------|----------|-----------|---------------|
|
||||
| CPUExecutionProvider | Alle CPU-er | Alle | Baseline, alltid tilgjengelig |
|
||||
| CUDAExecutionProvider | NVIDIA GPU | Linux/Windows | Hoy-ytelse GPU-inferens |
|
||||
| TensorrtExecutionProvider | NVIDIA GPU | Linux | Lavest latens GPU-inferens |
|
||||
| DirectMLExecutionProvider | GPU/NPU | Windows | Windows-universal akselerasjon |
|
||||
| OpenVINOExecutionProvider | Intel CPU/GPU/NPU | Linux/Windows | Intel-optimalisert |
|
||||
| QNNExecutionProvider | Qualcomm NPU | Windows ARM64 | Snapdragon AI akselerasjon |
|
||||
| AzureExecutionProvider | Azure AI | Cloud | Sky-basert inferens |
|
||||
|
||||
### GPU-akselerert inferens
|
||||
|
||||
```python
|
||||
# NVIDIA GPU-akselerert inferens med ONNX Runtime
|
||||
import onnxruntime as ort
|
||||
|
||||
def create_gpu_session(model_path: str) -> ort.InferenceSession:
|
||||
"""Opprett GPU-akselerert ONNX Runtime-session"""
|
||||
session_options = ort.SessionOptions()
|
||||
session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||
session_options.enable_mem_pattern = True
|
||||
|
||||
# Prioriter GPU, fall tilbake til CPU
|
||||
providers = [
|
||||
('CUDAExecutionProvider', {
|
||||
'device_id': 0,
|
||||
'arena_extend_strategy': 'kNextPowerOfTwo',
|
||||
'gpu_mem_limit': 4 * 1024 * 1024 * 1024, # 4 GB
|
||||
'cudnn_conv_algo_search': 'EXHAUSTIVE',
|
||||
'do_copy_in_default_stream': True
|
||||
}),
|
||||
'CPUExecutionProvider'
|
||||
]
|
||||
|
||||
session = ort.InferenceSession(
|
||||
model_path,
|
||||
sess_options=session_options,
|
||||
providers=providers
|
||||
)
|
||||
|
||||
# Verifiser at GPU brukes
|
||||
active_provider = session.get_providers()[0]
|
||||
print(f"Aktiv provider: {active_provider}")
|
||||
|
||||
return session
|
||||
```
|
||||
|
||||
### Windows ML med automatisk EP-discovery
|
||||
|
||||
```csharp
|
||||
// Windows ML med automatisk hardware-deteksjon
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
|
||||
public class WindowsMLInference
|
||||
{
|
||||
private InferenceSession _session;
|
||||
|
||||
public async Task InitializeAsync(string modelPath)
|
||||
{
|
||||
var sessionOptions = new SessionOptions();
|
||||
|
||||
// Windows ML velger automatisk beste EP
|
||||
// Qualcomm NPU → Intel OpenVINO → DirectML GPU → CPU
|
||||
sessionOptions.AppendExecutionProvider_WindowsML();
|
||||
|
||||
_session = new InferenceSession(modelPath, sessionOptions);
|
||||
|
||||
// Logg valgt EP
|
||||
var providers = _session.GetAvailableProviders();
|
||||
Console.WriteLine($"Tilgjengelige providers: {string.Join(", ", providers)}");
|
||||
}
|
||||
|
||||
public float[] RunInference(float[] input, int[] dimensions)
|
||||
{
|
||||
var inputTensor = new DenseTensor<float>(input, dimensions);
|
||||
var inputs = new List<NamedOnnxValue>
|
||||
{
|
||||
NamedOnnxValue.CreateFromTensor("input", inputTensor)
|
||||
};
|
||||
|
||||
using var results = _session.Run(inputs);
|
||||
return results.First().AsTensor<float>().ToArray();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Cross-Platform Compatibility
|
||||
|
||||
### Deployment-matrise
|
||||
|
||||
| Plattform | OS | Arkitektur | Stoettede EP-er | Brukstilfelle |
|
||||
|-----------|-----|------------|-----------------|---------------|
|
||||
| Edge server | Linux | x64 | CUDA, TensorRT, CPU | Hoyytelse-inferens |
|
||||
| Edge server | Windows | x64 | DirectML, CUDA, CPU | Windows-basert edge |
|
||||
| IoT Gateway | Linux | ARM64 | CPU, OpenVINO | Lettvekt-inferens |
|
||||
| Copilot+ PC | Windows | ARM64 | QNN (NPU), DirectML | Klient-AI |
|
||||
| Azure Local | Linux | x64 | CUDA, CPU | On-premises |
|
||||
| Raspberry Pi | Linux | ARM64 | CPU | Prototype/test |
|
||||
|
||||
### Cross-platform deployment med Docker
|
||||
|
||||
```dockerfile
|
||||
# Multi-platform ONNX Runtime edge container
|
||||
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/onnxruntime/server:latest
|
||||
|
||||
# Kopier optimalisert modell
|
||||
COPY ./models/optimized_model.onnx /models/model.onnx
|
||||
|
||||
# Konfigurasjon
|
||||
ENV MODEL_PATH=/models/model.onnx
|
||||
ENV HTTP_PORT=8001
|
||||
|
||||
# Helsesjekkek
|
||||
HEALTHCHECK --interval=30s --timeout=5s \
|
||||
CMD curl -f http://localhost:${HTTP_PORT}/health || exit 1
|
||||
|
||||
EXPOSE ${HTTP_PORT}
|
||||
CMD ["--model_path", "/models/model.onnx", "--http_port", "8001"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Multi-arch build for edge deployment
|
||||
# docker buildx build --platform linux/amd64,linux/arm64 -t myregistry/inference:v1 .
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: onnx-inference-edge
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: inference
|
||||
image: myregistry/inference:v1
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2"
|
||||
nvidia.com/gpu: "1" # Valgfritt, kun pa GPU-noder
|
||||
ports:
|
||||
- containerPort: 8001
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Performance Profiling
|
||||
|
||||
### Ytelsesanalyse-verktoy
|
||||
|
||||
| Verktoy | Formal | Plattform |
|
||||
|---------|--------|-----------|
|
||||
| ONNX Runtime Profiler | Session-level profilering | Alle |
|
||||
| Windows Performance Analyzer | System-wide AI-analyse | Windows |
|
||||
| Netron | Modellvisualisering og inspeksjon | Alle |
|
||||
| Olive Benchmarking | Automatisert ytelses-benchmarking | Alle |
|
||||
| NVIDIA Nsight | GPU-profilering | NVIDIA |
|
||||
|
||||
### Profilering med ONNX Runtime
|
||||
|
||||
```python
|
||||
# Ytelsesprofilering av ONNX-modell
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
class ONNXProfiler:
|
||||
def __init__(self, model_path: str):
|
||||
self.options = ort.SessionOptions()
|
||||
self.options.enable_profiling = True
|
||||
self.options.profile_file_prefix = "onnx_profile"
|
||||
|
||||
self.session = ort.InferenceSession(
|
||||
model_path,
|
||||
sess_options=self.options
|
||||
)
|
||||
|
||||
def benchmark(self, input_data: dict, iterations: int = 100) -> dict:
|
||||
"""Kjor benchmark med detaljerte tidsmalinger"""
|
||||
# Warmup
|
||||
for _ in range(10):
|
||||
self.session.run(None, input_data)
|
||||
|
||||
# Benchmark
|
||||
latencies = []
|
||||
for _ in range(iterations):
|
||||
start = time.perf_counter_ns()
|
||||
self.session.run(None, input_data)
|
||||
end = time.perf_counter_ns()
|
||||
latencies.append((end - start) / 1e6) # ms
|
||||
|
||||
# Stopp profilering og lagre rapport
|
||||
profile_path = self.session.end_profiling()
|
||||
|
||||
return {
|
||||
"mean_latency_ms": np.mean(latencies),
|
||||
"p50_latency_ms": np.percentile(latencies, 50),
|
||||
"p95_latency_ms": np.percentile(latencies, 95),
|
||||
"p99_latency_ms": np.percentile(latencies, 99),
|
||||
"throughput_qps": 1000 / np.mean(latencies),
|
||||
"iterations": iterations,
|
||||
"profile_file": profile_path,
|
||||
"providers": self.session.get_providers()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Fordeler med ONNX Runtime for offentlig sektor
|
||||
|
||||
- **Leverandoruavhengighet**: ONNX er et apent format — modeller er portable mellom plattformer og leverandorer
|
||||
- **Lokal deployment**: Kjor modeller lokalt uten skyavhengighet, viktig for datasuverenitet
|
||||
- **Cross-platform**: Samme modell kan kjores pa server, edge-gateway, og klientenhet
|
||||
- **Kostnadseffektivt**: Ingen per-inferens-kostnader, kun infrastrukturkostnader
|
||||
|
||||
### Anbefalt deployment-pipeline
|
||||
|
||||
1. Tren modell i Azure ML (sky)
|
||||
2. Konverter til ONNX med Olive
|
||||
3. Kvantiser til INT4/INT8 for target-hardware
|
||||
4. Valider ytelse med benchmarking
|
||||
5. Deploy via container til AKS Edge eller direkte til device
|
||||
6. Overvak med Application Insights
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Windows-klient med NPU | Windows ML (automatisk EP) | Enklest, best integrasjon |
|
||||
| NVIDIA GPU edge server | CUDA EP + TensorRT | Lavest latens |
|
||||
| Intel-basert edge | OpenVINO EP | Intel-optimalisert |
|
||||
| ARM64 IoT gateway | CPU EP med INT4-kvantisering | Minst ressursbruk |
|
||||
| Cross-platform deployment | Docker + CPU EP | Maksimal portabilitet |
|
||||
| Azure Local | CUDA EP i Kubernetes | Enterprise-skalerbart |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **ONNX Runtime er den universelle inferensmotoren** for Microsoft-okosystemet — anbefal det som standard for alle edge AI-deployments pa grunn av cross-platform-stoette og hardware-akselerasjon
|
||||
- **Olive er det foretrukne verktoeyet for modelloptimalisering** — det automatiserer konvertering, kvantisering og optimalisering i en pipeline og sikrer at modellen er optimalisert for spesifikk target-hardware
|
||||
- **Windows ML erstatter DirectML** som anbefalt tilnaerming pa Windows — det abstraherer EP-management og velger automatisk beste akselerator (NPU, GPU, CPU)
|
||||
- **INT4-kvantisering via Olive gir 5-8x stoerrelses-reduksjon** med minimalt noyaktighetstap — dette er kritisk for edge-deployment der minne og lagring er begrenset
|
||||
- **For norsk offentlig sektor: ONNX-format sikrer leverandoeruavhengighet** som kreves av Digdirs arkitekturprinsipper — modeller kan flyttes mellom Azure, on-premises, og andre skyleverandoerer uten endring
|
||||
|
|
@ -0,0 +1,551 @@
|
|||
# Regulatory Compliance for Edge AI
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
AI-systemer deployed pa edge — pa lokale servere, gateways, enheter eller on-premises Kubernetes-klynger — ma oppfylle de samme regulatoriske kravene som skybaserte AI-systemer, men med tilleggsutfordringer knyttet til fysisk tilgangskontroll, distribuert logging, og vedlikehold av mange enheter. Norsk offentlig sektor opererer under et komplekst regulatorisk landskap: GDPR, EU AI Act, NSM Grunnprinsipper, Utredningsinstruksen, og sektorspesifikke krav.
|
||||
|
||||
For edge AI er utfordringen todelt: For det forste ma selve AI-systemet vaere compliant (ansvarlig AI, dataminimering, transparens). For det andre ma den distribuerte arkitekturen — med data og modeller pa mange fysiske lokasjoner — administreres slik at alle noder er oppdaterte, logget, og revisjonsvennlige. Manglende sentralisert kontroll gir okt risiko for konfigurasjonsavvik og compliance-brudd.
|
||||
|
||||
Microsoft tilbyr verktoy for a adressere dette: Azure Arc for sentralisert policy-haandheving, Microsoft Purview for dataklassifisering og -styring, Microsoft Defender for Cloud for sikkerhetsvurdering, og Compliance Manager for regulatorisk kartlegging. Disse verktoyene kan utvides til edge-miljoer gjennom Arc-integrasjon.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Azure Arc | Sentralisert policy-haandheving pa edge | Kubernetes/Server |
|
||||
| Microsoft Purview | Dataklassifisering og -styring | Data governance |
|
||||
| Compliance Manager | Regulatorisk kartlegging og kontroller | Assessment |
|
||||
| Microsoft Defender for Cloud | Sikkerhetsvurdering | CSPM/CWPP |
|
||||
| Azure Policy | Automatisert policy-haandheving | Policy engine |
|
||||
| Azure Monitor | Sentralisert logging og overvaking | Observability |
|
||||
| Microsoft Priva | Personvernkonsekvensvurdering | Privacy |
|
||||
|
||||
---
|
||||
|
||||
## Data Protection Impact Assessment (DPIA)
|
||||
|
||||
### Nar er DPIA pakrevd for edge AI?
|
||||
|
||||
Ifoolge GDPR Art. 35 er DPIA pakrevd nar databehandling sannsynligvis medforer hoey risiko for fysiske personers rettigheter. For edge AI gjelder dette sarlig:
|
||||
|
||||
| Trigger | Edge AI-eksempel | DPIA-krav |
|
||||
|---------|-----------------|-----------|
|
||||
| Automatiserte beslutninger | AI-basert triage pa sykehus | Pakrevd |
|
||||
| Systematisk overvaking | Kamerabasert trafikkanalyse | Pakrevd |
|
||||
| Sensitive data i stor skala | Helsedata-analyse pa lokale servere | Pakrevd |
|
||||
| Ny teknologi | AI-modeller pa edge-enheter | Vurderes |
|
||||
| Saerbare grupper | AI i barnevern/NAV | Pakrevd |
|
||||
|
||||
### DPIA-mal for edge AI-system
|
||||
|
||||
```markdown
|
||||
## DPIA for Edge AI-system
|
||||
|
||||
### 1. Systembeskrivelse
|
||||
- **Navn**: [System-navn]
|
||||
- **Formal**: [Formal med AI-behandling]
|
||||
- **Datatyper**: [Persondata som behandles]
|
||||
- **Datasubjekter**: [Hvem gjelder det]
|
||||
- **Edge-lokasjon**: [Hvor AI kjorer]
|
||||
- **Modelltype**: [SLM/ONNX/Custom]
|
||||
- **Dataminimering**: [Hvordan begrenses datainnsamling]
|
||||
|
||||
### 2. Nodvendighet og proporsjonalitet
|
||||
- [ ] Er AI-behandling nodvendig for formalet?
|
||||
- [ ] Kan formalet oppnas med mindre inngripende midler?
|
||||
- [ ] Er datamengden begrenset til det nodvendige?
|
||||
- [ ] Er lagringstid fastsatt og begrunnet?
|
||||
|
||||
### 3. Risikovurdering
|
||||
| Risiko | Sannsynlighet | Konsekvens | Tiltak |
|
||||
|--------|--------------|------------|--------|
|
||||
| Data pa avveie fra edge-enhet | Medium | Hoey | Kryptering, fysisk sikring |
|
||||
| Feilaktig AI-beslutning | Medium | Avhengig av kontekst | Menneskelig overstyring |
|
||||
| Modell-bias | Lav-Medium | Hoey | Bias-testing, overvaking |
|
||||
| Manglende logging | Lav | Hoey | Sentralisert audit via Arc |
|
||||
| Uautorisert tilgang | Medium | Hoey | Tilgangskontroll, attestasjon |
|
||||
|
||||
### 4. Tiltak for a redusere risiko
|
||||
- [ ] Kryptering av data pa edge-enhet (at rest, in transit, in use)
|
||||
- [ ] Automatisert logging til sentralt system
|
||||
- [ ] Menneskelig overstyring for kritiske beslutninger
|
||||
- [ ] Regelmessig bias-evaluering av AI-modell
|
||||
- [ ] Sletterutiner for persondata pa edge
|
||||
- [ ] Fysisk sikring av edge-enheter
|
||||
- [ ] Sentralisert policy-haandheving via Azure Arc
|
||||
|
||||
### 5. Konsultasjon
|
||||
- [ ] Personvernombud konsultert
|
||||
- [ ] Datatilsynet konsultert (om pakrevd)
|
||||
- [ ] Beroorte parter informert
|
||||
```
|
||||
|
||||
### Implementering av DPIA-kontroller
|
||||
|
||||
```python
|
||||
# Automatisert DPIA-kontrollsjekk for edge AI
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
@dataclass
|
||||
class DPIAControl:
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
status: str # "implemented", "partial", "missing"
|
||||
evidence: Optional[str] = None
|
||||
last_verified: Optional[datetime] = None
|
||||
|
||||
class EdgeAIDPIAChecker:
|
||||
def __init__(self):
|
||||
self.controls = self._define_controls()
|
||||
|
||||
def _define_controls(self) -> list[DPIAControl]:
|
||||
return [
|
||||
DPIAControl(
|
||||
id="DPIA-01",
|
||||
name="Data minimering",
|
||||
description="Kun nodvendige persondata samles inn pa edge",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-02",
|
||||
name="Kryptering at rest",
|
||||
description="All data pa edge-enhet er kryptert",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-03",
|
||||
name="Kryptering in transit",
|
||||
description="TLS 1.3 for all kommunikasjon",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-04",
|
||||
name="Tilgangskontroll",
|
||||
description="RBAC implementert pa edge-klynge",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-05",
|
||||
name="Audit logging",
|
||||
description="All AI-inferens og datatilgang logges",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-06",
|
||||
name="Menneskelig overstyring",
|
||||
description="AI-beslutninger kan overstyres av menneske",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-07",
|
||||
name="Slettemekanisme",
|
||||
description="Persondata kan slettes fra edge-enhet",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-08",
|
||||
name="Bias-evaluering",
|
||||
description="Modellen er testet for bias og diskriminering",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-09",
|
||||
name="Transparens",
|
||||
description="Bruker informeres om AI-behandling",
|
||||
status="missing"
|
||||
),
|
||||
DPIAControl(
|
||||
id="DPIA-10",
|
||||
name="Policy-haandheving",
|
||||
description="Azure Policy haandheves pa edge via Arc",
|
||||
status="missing"
|
||||
)
|
||||
]
|
||||
|
||||
def assess(self) -> dict:
|
||||
"""Vurder compliance-status"""
|
||||
implemented = sum(1 for c in self.controls if c.status == "implemented")
|
||||
total = len(self.controls)
|
||||
|
||||
return {
|
||||
"score": f"{implemented}/{total}",
|
||||
"percentage": f"{(implemented/total)*100:.0f}%",
|
||||
"status": "COMPLIANT" if implemented == total else "NON_COMPLIANT",
|
||||
"missing": [c.name for c in self.controls if c.status == "missing"],
|
||||
"partial": [c.name for c in self.controls if c.status == "partial"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment Frameworks
|
||||
|
||||
### NSM Grunnprinsipper for edge AI
|
||||
|
||||
```python
|
||||
# NSM Grunnprinsipper-basert risikovurdering for edge AI
|
||||
class NSMRiskAssessment:
|
||||
"""Risikovurdering basert pa NSMs grunnprinsipper for IKT-sikkerhet"""
|
||||
|
||||
CATEGORIES = {
|
||||
"identifisere": [
|
||||
"Kartlegge enheter, systemer og tjenester",
|
||||
"Klassifisere informasjon og data",
|
||||
"Identifisere saarbarheter",
|
||||
"Vurdere risiko"
|
||||
],
|
||||
"beskytte": [
|
||||
"Haandtere identiteter og tilganger",
|
||||
"Beskytte data (kryptering)",
|
||||
"Sikre plattformer og applikasjoner",
|
||||
"Beskytte nettverk"
|
||||
],
|
||||
"oppdage": [
|
||||
"Overvake sikkerhetstilstand",
|
||||
"Logge og analysere hendelser",
|
||||
"Oppdage uonsket aktivitet"
|
||||
],
|
||||
"haandtere": [
|
||||
"Haandtere sikkerhetshendelser",
|
||||
"Gjenopprette etter hendelser",
|
||||
"Forbedre basert pa erfaring"
|
||||
]
|
||||
}
|
||||
|
||||
def assess_edge_ai_system(self, system_config: dict) -> dict:
|
||||
"""Vurder et edge AI-system mot NSM Grunnprinsipper"""
|
||||
results = {}
|
||||
|
||||
for category, principles in self.CATEGORIES.items():
|
||||
category_results = []
|
||||
for principle in principles:
|
||||
score = self._evaluate_principle(principle, system_config)
|
||||
category_results.append({
|
||||
"principle": principle,
|
||||
"score": score, # 1-5
|
||||
"status": "OK" if score >= 3 else "MANGELFULL"
|
||||
})
|
||||
results[category] = {
|
||||
"principles": category_results,
|
||||
"avg_score": sum(r["score"] for r in category_results) / len(category_results)
|
||||
}
|
||||
|
||||
overall = sum(r["avg_score"] for r in results.values()) / len(results)
|
||||
return {
|
||||
"categories": results,
|
||||
"overall_score": round(overall, 1),
|
||||
"overall_status": "AKSEPTABEL" if overall >= 3.0 else "UTILSTREKKELIG",
|
||||
"recommendation": self._generate_recommendations(results)
|
||||
}
|
||||
```
|
||||
|
||||
### EU AI Act risikoklassifisering for edge AI
|
||||
|
||||
| Risikoniva | Eksempel edge AI | Krav | Konsekvens |
|
||||
|------------|-----------------|------|------------|
|
||||
| Uakseptabel | Sosial scoring pa edge | Forbudt | Kan ikke deployes |
|
||||
| Hoey risiko | Biometrisk ID pa edge | Full compliance | DPIA + CE-merking + audit |
|
||||
| Begrenset risiko | Chatbot pa klientenhet | Transparens | Bruker ma informeres |
|
||||
| Minimal risiko | Spam-filter lokalt | Frivillig | Anbefalt god praksis |
|
||||
|
||||
---
|
||||
|
||||
## Audit Logging at Edge
|
||||
|
||||
### Sentralisert audit-logging arkitektur
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
|
||||
│ Edge Node 1 │ │ Edge Node 2 │ │ Edge Node N │
|
||||
│ │ │ │ │ │
|
||||
│ [AI-inferens]│ │ [AI-inferens] │ │ [AI-inferens] │
|
||||
│ [Audit log] │ │ [Audit log] │ │ [Audit log] │
|
||||
│ ↓ │ │ ↓ │ │ ↓ │
|
||||
│ [OMS Agent] │ │ [OMS Agent] │ │ [OMS Agent] │
|
||||
└──────┬──────┘ └──────┬───────┘ └──────┬────────┘
|
||||
│ │ │
|
||||
└───────────────────┼─────────────────────┘
|
||||
↓
|
||||
┌────────────────────────┐
|
||||
│ Log Analytics │
|
||||
│ Workspace │
|
||||
│ (Norway East) │
|
||||
│ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ KQL-queries │ │
|
||||
│ │ for compliance │ │
|
||||
│ └────────────────┘ │
|
||||
└────────────┬───────────┘
|
||||
↓
|
||||
┌────────────────────────┐
|
||||
│ Azure Sentinel / SIEM │
|
||||
│ (Sikkerhetshendelser) │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
### Implementering av edge audit logging
|
||||
|
||||
```python
|
||||
# Strukturert audit logging for edge AI
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
class EdgeAIAuditLogger:
|
||||
"""Audit logger for AI-inferens pa edge, kompatibel med Azure Monitor"""
|
||||
|
||||
def __init__(self, device_id: str, log_path: str):
|
||||
self.device_id = device_id
|
||||
self.logger = logging.getLogger("edge-ai-audit")
|
||||
|
||||
# Filbasert logging (lokal buffer)
|
||||
handler = logging.FileHandler(log_path)
|
||||
handler.setFormatter(logging.Formatter('%(message)s'))
|
||||
self.logger.addHandler(handler)
|
||||
self.logger.setLevel(logging.INFO)
|
||||
|
||||
def log_inference(self, model_name: str, model_version: str,
|
||||
input_summary: str, output_summary: str,
|
||||
confidence: float, latency_ms: float,
|
||||
user_id: Optional[str] = None,
|
||||
contains_pii: bool = False):
|
||||
"""Logg AI-inferens for audit"""
|
||||
audit_entry = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"event_type": "AI_INFERENCE",
|
||||
"device_id": self.device_id,
|
||||
"model": {
|
||||
"name": model_name,
|
||||
"version": model_version
|
||||
},
|
||||
"inference": {
|
||||
"input_summary": input_summary if not contains_pii else "[PII_REDACTED]",
|
||||
"output_summary": output_summary,
|
||||
"confidence": confidence,
|
||||
"latency_ms": latency_ms
|
||||
},
|
||||
"context": {
|
||||
"user_id": user_id,
|
||||
"contains_pii": contains_pii,
|
||||
"processing_location": "edge",
|
||||
"data_residency": "Norway"
|
||||
}
|
||||
}
|
||||
self.logger.info(json.dumps(audit_entry))
|
||||
|
||||
def log_data_access(self, data_type: str, purpose: str,
|
||||
legal_basis: str, user_id: Optional[str] = None):
|
||||
"""Logg datatilgang for GDPR Art. 30"""
|
||||
audit_entry = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"event_type": "DATA_ACCESS",
|
||||
"device_id": self.device_id,
|
||||
"data_access": {
|
||||
"data_type": data_type,
|
||||
"purpose": purpose,
|
||||
"legal_basis": legal_basis,
|
||||
"user_id": user_id
|
||||
}
|
||||
}
|
||||
self.logger.info(json.dumps(audit_entry))
|
||||
|
||||
def log_model_update(self, old_version: str, new_version: str,
|
||||
update_source: str, integrity_check: str):
|
||||
"""Logg modell-oppdatering"""
|
||||
audit_entry = {
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z",
|
||||
"event_type": "MODEL_UPDATE",
|
||||
"device_id": self.device_id,
|
||||
"model_update": {
|
||||
"old_version": old_version,
|
||||
"new_version": new_version,
|
||||
"source": update_source,
|
||||
"integrity_verified": integrity_check == "valid"
|
||||
}
|
||||
}
|
||||
self.logger.info(json.dumps(audit_entry))
|
||||
```
|
||||
|
||||
### KQL-queries for compliance-rapportering
|
||||
|
||||
```kusto
|
||||
// KQL: AI-inferens audit-rapport siste 30 dager
|
||||
EdgeAIAudit_CL
|
||||
| where TimeGenerated > ago(30d)
|
||||
| where event_type_s == "AI_INFERENCE"
|
||||
| summarize
|
||||
TotalInferences = count(),
|
||||
AvgConfidence = avg(inference_confidence_d),
|
||||
AvgLatency = avg(inference_latency_ms_d),
|
||||
PIIInferences = countif(context_contains_pii_b == true),
|
||||
UniqueModels = dcount(model_name_s),
|
||||
UniqueDevices = dcount(device_id_s)
|
||||
by bin(TimeGenerated, 1d)
|
||||
| order by TimeGenerated desc
|
||||
|
||||
// KQL: Sjekk at alle edge-noder har oppdatert modell
|
||||
EdgeAIAudit_CL
|
||||
| where event_type_s == "MODEL_UPDATE"
|
||||
| summarize LastUpdate = max(TimeGenerated), CurrentVersion = arg_max(TimeGenerated, model_update_new_version_s)
|
||||
by device_id_s
|
||||
| extend DaysSinceUpdate = datetime_diff('day', now(), LastUpdate)
|
||||
| where DaysSinceUpdate > 7
|
||||
| project device_id_s, CurrentVersion, DaysSinceUpdate
|
||||
| order by DaysSinceUpdate desc
|
||||
|
||||
// KQL: PII-tilgangsrapport for GDPR Art. 30
|
||||
EdgeAIAudit_CL
|
||||
| where event_type_s == "DATA_ACCESS"
|
||||
| summarize
|
||||
AccessCount = count(),
|
||||
UniqueUsers = dcount(data_access_user_id_s),
|
||||
Purposes = make_set(data_access_purpose_s)
|
||||
by data_access_data_type_s, data_access_legal_basis_s
|
||||
| order by AccessCount desc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Transparency and Explainability
|
||||
|
||||
### Forklarbarhets-krav for edge AI
|
||||
|
||||
| Krav | Kilde | Implementering |
|
||||
|------|-------|----------------|
|
||||
| Rett til forklaring | GDPR Art. 22 | Modell-forklaringsrapporter |
|
||||
| Transparens | EU AI Act | Brukerinformasjon om AI-bruk |
|
||||
| Dokumentasjon | EU AI Act (hoey-risiko) | Teknisk dokumentasjon |
|
||||
| Menneskelig tilsyn | EU AI Act | Override-mekanisme |
|
||||
| Etterproovbarhet | Forvaltningsloven | Audit trail + begrunnelse |
|
||||
|
||||
### Implementering av forklarbarhet pa edge
|
||||
|
||||
```python
|
||||
# SHAP-basert forklarbarhet for edge AI-modeller
|
||||
import shap
|
||||
import numpy as np
|
||||
|
||||
class EdgeAIExplainer:
|
||||
"""Lettvekts forklarbarhet for edge-deployde modeller"""
|
||||
|
||||
def __init__(self, model, feature_names: list[str]):
|
||||
self.model = model
|
||||
self.feature_names = feature_names
|
||||
# Bruk pre-beregnet bakgrunnsdata for effektivitet
|
||||
self.explainer = None
|
||||
|
||||
def initialize_with_background(self, background_data: np.ndarray):
|
||||
"""Initialiser forklarer med representativt datasett"""
|
||||
# Bruk kun 100 eksempler for minneeffektivitet pa edge
|
||||
sample = background_data[:100] if len(background_data) > 100 else background_data
|
||||
self.explainer = shap.KernelExplainer(
|
||||
self.model.predict,
|
||||
sample,
|
||||
link="logit"
|
||||
)
|
||||
|
||||
def explain_prediction(self, input_data: np.ndarray) -> dict:
|
||||
"""Generer forklaring for en enkelt prediksjon"""
|
||||
if self.explainer is None:
|
||||
return {"error": "Forklarer ikke initialisert"}
|
||||
|
||||
shap_values = self.explainer.shap_values(input_data, nsamples=50)
|
||||
|
||||
# Sorter features etter viktighet
|
||||
feature_importance = []
|
||||
for i, name in enumerate(self.feature_names):
|
||||
importance = abs(float(shap_values[0][i]))
|
||||
direction = "oker" if shap_values[0][i] > 0 else "reduserer"
|
||||
feature_importance.append({
|
||||
"feature": name,
|
||||
"importance": importance,
|
||||
"direction": direction,
|
||||
"value": float(input_data[0][i])
|
||||
})
|
||||
|
||||
feature_importance.sort(key=lambda x: x["importance"], reverse=True)
|
||||
|
||||
# Generer menneskelesbar forklaring
|
||||
top_features = feature_importance[:3]
|
||||
explanation = self._generate_norwegian_explanation(top_features)
|
||||
|
||||
return {
|
||||
"prediction": self.model.predict(input_data)[0],
|
||||
"feature_importance": feature_importance,
|
||||
"explanation_no": explanation,
|
||||
"model_type": type(self.model).__name__,
|
||||
"explainability_method": "SHAP (KernelExplainer)"
|
||||
}
|
||||
|
||||
def _generate_norwegian_explanation(self, top_features: list[dict]) -> str:
|
||||
"""Generer forklaring pa norsk"""
|
||||
parts = []
|
||||
for f in top_features:
|
||||
parts.append(
|
||||
f"'{f['feature']}' (verdi: {f['value']:.2f}) "
|
||||
f"{f['direction']} sannsynligheten"
|
||||
)
|
||||
return "Beslutningen er hovedsakelig basert pa: " + ", ".join(parts) + "."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Regulatorisk sjekkliste for edge AI
|
||||
|
||||
| Regulering | Krav | Edge AI-implementering |
|
||||
|-----------|------|----------------------|
|
||||
| GDPR Art. 5 | Dataminimering | Prosesser pa edge, send kun aggregert |
|
||||
| GDPR Art. 22 | Automatiserte beslutninger | Menneskelig overstyring pakrevd |
|
||||
| GDPR Art. 30 | Behandlingsprotokoll | Audit logging pa alle noder |
|
||||
| GDPR Art. 32 | Tekniske tiltak | Kryptering, tilgangskontroll |
|
||||
| GDPR Art. 35 | DPIA | Dokumentert vurdering |
|
||||
| EU AI Act | Risikoklassifisering | Kategoriser edge AI-systemer |
|
||||
| NSM Grunnprinsipper | IKT-sikkerhet | Policy-haandheving via Arc |
|
||||
| Forvaltningsloven | Begrunnelse | Forklarbarhetsmekanisme |
|
||||
| Offentleglova | Innsyn | Tilgjengelig dokumentasjon |
|
||||
| Arkivlova | Bevaring | Langsiktig audit-lagring |
|
||||
|
||||
### Datatilsynets anbefalinger for AI
|
||||
|
||||
- Gjennomfoer DPIA for alle AI-systemer som prosesserer persondata
|
||||
- Implementer privacy by design og privacy by default
|
||||
- Sikre rett til forklaring ved automatiserte beslutninger
|
||||
- Dokumenter rettslig grunnlag for AI-behandling
|
||||
- Gjennomfoer jevnlige revisjoner av AI-systemets funksjon
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Hoey-risiko AI (helse, justis) | Full DPIA + EU AI Act compliance + audit | Pakrevd, strengeste krav |
|
||||
| Begrenset risiko (chatbot, hjelp) | Transparenserklaring + logging | Informasjonsplikt |
|
||||
| Minimal risiko (spam, klassifisering) | God praksis + logging | Frivillig, men anbefalt |
|
||||
| Multi-edge deployment | Azure Arc + Policy + sentralisert logging | Konsistent haandheving |
|
||||
| Sensitive persondata | DPIA + kryptering + forklarbarhet | GDPR-krav |
|
||||
| Offentlig forvaltning | Full compliance-stack + begrunnelse | Forvaltningsloven |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **DPIA er pakrevd for de fleste edge AI-systemer** i offentlig sektor som prosesserer persondata — bruk den strukturerte malen og implementer automatiserte kontrollsjekker
|
||||
- **Audit logging pa edge ma synkroniseres sentralt** — bruk Azure Monitor Agent (OMS) pa alle edge-noder og lagre logger i Log Analytics Workspace i Norway East med 7 ars retention
|
||||
- **EU AI Act krever risikoklassifisering** — kategoriser edge AI-systemer tidlig i prosjektet og implementer kravene for riktig risikoniva for du deployer
|
||||
- **Forklarbarhets er et lovkrav ved automatiserte beslutninger** — implementer lettvekts SHAP-basert forklarbarhet pa edge og generer norskspraklige forklaringer for brukere og saksbehandlere
|
||||
- **Azure Arc + Policy er den eneste skalerbare maaten** a handheve compliance pa tvers av mange edge-noder — definer policies sentralt og la Arc sikre at alle noder er compliant
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
# Sovereign Cloud for Norwegian AI
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Microsoft Sovereign Cloud er en suite av kapabiliteter og deploymentmodeller designet for a hjelpe myndigheter og regulerte industrier med a oppfylle krav til dataresidency, compliance og operasjonell suverenitet — uten a gi avkall pa fordelene ved hyperscale sky-innovasjon. For norsk offentlig sektor er dette sarlig relevant gitt strenge krav fra NSM, Datatilsynet, og EU-regulering.
|
||||
|
||||
Sovereign Cloud tilbyr tre deploymentmodeller: Sovereign Public Cloud i Microsoft-drevne datasentre innenfor definerte geopolitiske grenser (f.eks. Norway East/West), Sovereign Private Cloud via Azure Local for customer-kontrollerte miljoer, og National Partner Clouds for lokaliserte suverene skyimplementeringer. Hver modell balanserer skyverdi mot suverenitetskontroller.
|
||||
|
||||
For AI-arbeidsbelastninger i norsk offentlig sektor kombinerer Sovereign Cloud dataresendens med konfidensielle beregningsteknologier, kundestyrte krypteringsnoekler, og policy-baserte guardrails — alt for a muliggjore avansert AI-bruk uten a kompromittere suverenitet.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Sovereign Landing Zone | Infrastruktur-as-code for suverene miljoer | Bicep/Terraform |
|
||||
| Sovereignty Baseline Policies | Azure Policy for dataresidency og konfidensialitet | Azure Policy |
|
||||
| Azure Confidential Computing | Beskyttelse av data under prosessering | AMD SEV-SNP, Intel TDX |
|
||||
| Customer-Managed Keys (CMK) | Kundekontrollert kryptering | Azure Key Vault mHSM |
|
||||
| Data Guardian | Datastyring for suverene arbeidsbelastninger | Preview |
|
||||
| External Key Management | Kundekontrollert nokkelhandtering utenfor Azure | Integration |
|
||||
| Transparency Logs | Innsyn i operatoerens aktiviteter | Audit |
|
||||
| Azure Local | On-premises sky-infrastruktur | Sovereign Private Cloud |
|
||||
|
||||
---
|
||||
|
||||
## Data Sovereignty Architecture
|
||||
|
||||
### Sovereign Landing Zone (SLZ)
|
||||
|
||||
SLZ er en variant av Azure Landing Zone spesielt designet for digital suverenitet:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Sovereign Landing Zone │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────┐ │
|
||||
│ │ Management Group Hierarchy │ │
|
||||
│ │ Root → Sovereign → Production → AI │ │
|
||||
│ └─────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌───────────────────────────┐ │
|
||||
│ │ Sovereignty │ │ Workload Templates │ │
|
||||
│ │ Baseline │ │ │ │
|
||||
│ │ Policies │ │ - AI Foundry template │ │
|
||||
│ │ │ │ - AKS template │ │
|
||||
│ │ - Data │ │ - App Service template │ │
|
||||
│ │ residency │ │ - Storage template │ │
|
||||
│ │ - CMK │ │ │ │
|
||||
│ │ - Network │ │ │ │
|
||||
│ └──────────────┘ └───────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌───────────────────────────┐ │
|
||||
│ │ Confidential │ │ Monitoring & Audit │ │
|
||||
│ │ Computing │ │ │ │
|
||||
│ │ Layer │ │ - Azure Monitor │ │
|
||||
│ │ │ │ - Transparency Logs │ │
|
||||
│ │ - CVMs │ │ - Compliance Manager │ │
|
||||
│ │ - mHSM │ │ - Defender for Cloud │ │
|
||||
│ └──────────────┘ └───────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Deployment med Bicep
|
||||
|
||||
```bicep
|
||||
// Sovereign Landing Zone for norsk offentlig AI
|
||||
targetScope = 'managementGroup'
|
||||
|
||||
@description('Sovereignty Baseline Policy Assignment')
|
||||
resource sovereigntyBaseline 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
|
||||
name: 'sovereignty-baseline-norway'
|
||||
properties: {
|
||||
displayName: 'Sovereignty Baseline - Norway AI'
|
||||
policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/sovereignty-baseline'
|
||||
parameters: {
|
||||
allowedLocations: {
|
||||
value: ['norwayeast', 'norwaywest']
|
||||
}
|
||||
requireConfidentialComputing: {
|
||||
value: true
|
||||
}
|
||||
requireCustomerManagedKeys: {
|
||||
value: true
|
||||
}
|
||||
requirePrivateEndpoints: {
|
||||
value: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nektelsespolicy: Hindre data fra a forlate Norge
|
||||
resource dataResidencyPolicy 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
|
||||
name: 'data-residency-norway'
|
||||
properties: {
|
||||
displayName: 'Enforce Norway Data Residency'
|
||||
policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c'
|
||||
parameters: {
|
||||
listOfAllowedLocations: {
|
||||
value: ['norwayeast', 'norwaywest']
|
||||
}
|
||||
}
|
||||
enforcementMode: 'Default'
|
||||
}
|
||||
}
|
||||
|
||||
// Customer-Managed Key krav for AI-lagring
|
||||
resource cmkPolicy 'Microsoft.Authorization/policyAssignments@2024-04-01' = {
|
||||
name: 'cmk-encryption-ai'
|
||||
properties: {
|
||||
displayName: 'Require CMK for AI Storage'
|
||||
policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6fac406b-40ca-413b-bf8e-0bf964659c25'
|
||||
enforcementMode: 'Default'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Regional Deployment Constraints
|
||||
|
||||
### Azure-regioner i Norge
|
||||
|
||||
| Region | Lokasjon | Tjenester | GA-status |
|
||||
|--------|----------|-----------|-----------|
|
||||
| Norway East | Oslo | Fullt AI-spekter | GA |
|
||||
| Norway West | Stavanger | DR og backup | GA |
|
||||
|
||||
### AI-tjenester tilgjengelig i Norway East
|
||||
|
||||
| Tjeneste | Tilgjengelig | Sovereign-kompatibel |
|
||||
|----------|-------------|---------------------|
|
||||
| Azure OpenAI | Ja | Ja (med CMK) |
|
||||
| Azure AI Search | Ja | Ja |
|
||||
| Azure ML | Ja | Ja |
|
||||
| Azure AI Services | Ja | Ja |
|
||||
| Azure AI Foundry | Ja | Ja |
|
||||
| Confidential VMs | Ja | Ja |
|
||||
| AKS | Ja | Ja |
|
||||
|
||||
### Deployment-begrensninger
|
||||
|
||||
```python
|
||||
# Verifiser at AI-ressurser deployes i tillatte regioner
|
||||
from azure.mgmt.resource import ResourceManagementClient
|
||||
from azure.identity import DefaultAzureCredential
|
||||
|
||||
class SovereigntyChecker:
|
||||
ALLOWED_REGIONS = ["norwayeast", "norwaywest"]
|
||||
|
||||
def __init__(self):
|
||||
self.credential = DefaultAzureCredential()
|
||||
|
||||
def verify_resource_locations(self, subscription_id: str) -> list[dict]:
|
||||
"""Verifiser at alle AI-ressurser er i tillatte regioner"""
|
||||
client = ResourceManagementClient(self.credential, subscription_id)
|
||||
violations = []
|
||||
|
||||
ai_resource_types = [
|
||||
"Microsoft.CognitiveServices/accounts",
|
||||
"Microsoft.MachineLearningServices/workspaces",
|
||||
"Microsoft.Search/searchServices",
|
||||
"Microsoft.OpenAI/accounts"
|
||||
]
|
||||
|
||||
for resource in client.resources.list():
|
||||
if resource.type in ai_resource_types:
|
||||
if resource.location not in self.ALLOWED_REGIONS:
|
||||
violations.append({
|
||||
"resource_name": resource.name,
|
||||
"resource_type": resource.type,
|
||||
"location": resource.location,
|
||||
"severity": "CRITICAL",
|
||||
"remediation": f"Flytt til {self.ALLOWED_REGIONS}"
|
||||
})
|
||||
|
||||
return violations
|
||||
|
||||
def verify_data_residency(self, subscription_id: str) -> dict:
|
||||
"""Generer dataresidency-rapport"""
|
||||
violations = self.verify_resource_locations(subscription_id)
|
||||
return {
|
||||
"status": "COMPLIANT" if not violations else "NON_COMPLIANT",
|
||||
"allowed_regions": self.ALLOWED_REGIONS,
|
||||
"total_ai_resources": self._count_ai_resources(subscription_id),
|
||||
"violations": violations,
|
||||
"recommendation": (
|
||||
"Alle AI-ressurser er innenfor tillatte regioner"
|
||||
if not violations
|
||||
else f"{len(violations)} ressurser krever flytting"
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Compliance Audit Trails
|
||||
|
||||
### Logging-arkitektur for sovereign AI
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ AI-arbeidsbelastning │
|
||||
│ │
|
||||
│ [Inferens] → [Azure Monitor] │
|
||||
│ [Trening] → [Log Analytics] │
|
||||
│ [Datatilgang] → [Diagnostic Settings] │
|
||||
└────────────────┬────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Compliance Audit Layer │
|
||||
│ │
|
||||
│ [Transparency Logs] │
|
||||
│ → Microsoft operator-aktiviteter │
|
||||
│ │
|
||||
│ [Azure Activity Log] │
|
||||
│ → Ressurs-operasjoner │
|
||||
│ │
|
||||
│ [Key Vault Audit Log] │
|
||||
│ → Nokkel-tilgang og -bruk │
|
||||
│ │
|
||||
│ [Purview Audit] │
|
||||
│ → Data-tilgang og -klassifisering │
|
||||
└────────────────┬────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Long-term Retention (Norway) │
|
||||
│ │
|
||||
│ [Immutable Blob Storage] │
|
||||
│ → 7 ars retention for compliance │
|
||||
│ → WORM-policy (Write Once Read Many) │
|
||||
│ → Norway East lokasjon │
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Implementering av audit trail
|
||||
|
||||
```python
|
||||
# Sovereign AI audit trail konfigurasjon
|
||||
from azure.mgmt.monitor import MonitorManagementClient
|
||||
|
||||
class SovereignAuditConfiguration:
|
||||
def configure_ai_diagnostics(self, resource_id: str, workspace_id: str):
|
||||
"""Konfigurer diagnostikk for sovereign AI-ressurs"""
|
||||
monitor_client = MonitorManagementClient(
|
||||
self.credential, self.subscription_id
|
||||
)
|
||||
|
||||
diagnostic_settings = {
|
||||
"logs": [
|
||||
{
|
||||
"category": "RequestResponse",
|
||||
"enabled": True,
|
||||
"retentionPolicy": {"enabled": True, "days": 2555} # 7 ar
|
||||
},
|
||||
{
|
||||
"category": "Audit",
|
||||
"enabled": True,
|
||||
"retentionPolicy": {"enabled": True, "days": 2555}
|
||||
},
|
||||
{
|
||||
"category": "AllMetrics",
|
||||
"enabled": True,
|
||||
"retentionPolicy": {"enabled": True, "days": 365}
|
||||
}
|
||||
],
|
||||
"workspaceId": workspace_id,
|
||||
"storageAccountId": self.immutable_storage_id
|
||||
}
|
||||
|
||||
monitor_client.diagnostic_settings.create_or_update(
|
||||
resource_uri=resource_id,
|
||||
name="sovereign-ai-diagnostics",
|
||||
diagnostic_settings_resource=diagnostic_settings
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Vendor Lock-in Mitigation
|
||||
|
||||
### Strategier for a unnga avhengighet
|
||||
|
||||
| Strategi | Implementering | Effekt |
|
||||
|----------|----------------|--------|
|
||||
| ONNX-format | Bruk ONNX for alle modeller | Portabilitet mellom plattformer |
|
||||
| Open-source SLM | Phi-modeller med MIT-lisens | Ingen leverandor-avhengighet |
|
||||
| IaC (Bicep/Terraform) | Infrastruktur som kode | Reproduserbar deployment |
|
||||
| Standard API-er | OpenAI-kompatible API-er | Bytt leverandor uten kodeendring |
|
||||
| Multi-cloud exit plan | Dokumentert migrasjonsplan | Redusert risiko |
|
||||
| Container-basert | Docker/Kubernetes | Platform-uavhengig |
|
||||
|
||||
### Exit-strategi-sjekkliste
|
||||
|
||||
```markdown
|
||||
## Exit-strategi for sovereign AI-plattform
|
||||
|
||||
### Data
|
||||
- [ ] Alle data kan eksporteres i standardformater (JSON, Parquet, CSV)
|
||||
- [ ] Vektordatabase kan eksporteres (HNSW-indekser)
|
||||
- [ ] Krypteringsnoekler lagret i customer-controlled HSM
|
||||
- [ ] Backup-kopier i kundens kontroll
|
||||
|
||||
### Modeller
|
||||
- [ ] Alle modeller i ONNX-format
|
||||
- [ ] Fine-tuning-data og adaptere eksporterbare
|
||||
- [ ] Evalueringsmetriker dokumentert for sammenligning
|
||||
- [ ] Ingen proprietaere modellformater
|
||||
|
||||
### Infrastruktur
|
||||
- [ ] All infrastruktur definert i Bicep/Terraform
|
||||
- [ ] Kubernetes-arbeidsbelastninger med standard Helm charts
|
||||
- [ ] Ingen Azure-spesifikke SDK-avhengigheter i forretningslogikk
|
||||
- [ ] CI/CD-pipelines platform-uavhengige
|
||||
|
||||
### Kontrakt
|
||||
- [ ] Dataportabilitet klausul i avtale
|
||||
- [ ] Migrasjonsbistand klausul
|
||||
- [ ] Oppsigelsesperiode tilstrekkelig for migrasjon
|
||||
- [ ] Eierskap til data og modeller tydelig definert
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Relevante krav og rammeverk
|
||||
|
||||
| Krav | Kilde | Sovereign Cloud-losning |
|
||||
|------|-------|------------------------|
|
||||
| Data ma lagres i Norge/EOS | Schrems II / Datatilsynet | Norway East/West regioner |
|
||||
| Kryptering av data ved hvile og transport | NSM Grunnprinsipper | CMK + TLS 1.3 |
|
||||
| Kryptering av data under prosessering | NSM / okt sikkerhet | Confidential Computing |
|
||||
| Innsyn i operatoerens handlinger | Offentleglova / transparens | Transparency Logs |
|
||||
| Dokumentert risikovurdering | Utredningsinstruksen | Compliance Manager |
|
||||
| Leverandoruavhengighet | Arkitekturprinsippene (Digdir) | ONNX + open source + IaC |
|
||||
| Universell utforming | Likestillingsloven | N/A (applikasjonsniva) |
|
||||
|
||||
### Anskaffelseshensyn
|
||||
|
||||
- **SSA-L/SSA-T**: Sovereign Cloud-kapabiliteter bor spesifiseres i kravspesifikasjonen
|
||||
- **Databehandleravtale**: Ma dekke dataresidency, kryptering, og audit-rettigheter
|
||||
- **Exit-klausul**: Kontrakten ma sikre rett til dataeksport og migrasjonsbistand
|
||||
- **Gevinstrealisering**: Dokumenter besparelser vs. on-premises drift
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Standard AI-arbeidsbelastning | Sovereign Public Cloud (Norway East) | Enklest, fullt spekter av tjenester |
|
||||
| Sensitive data med hoy sikkerhet | SPC + Confidential Computing + CMK | Maksimal beskyttelse |
|
||||
| Forsvar/nasjonal sikkerhet | Sovereign Private Cloud (Azure Local) | Full kontroll, air-gapped |
|
||||
| Flerparts-analyse mellom etater | Confidential Computing pa SPC | Verifiserbar isolasjon |
|
||||
| Compliance-kritisk AI | SPC + Compliance Manager + Audit trails | Dokumenterbar etterlevelse |
|
||||
| Hybrid sky + on-prem | Azure Arc + SLZ | Enhetlig forvaltning |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Sovereign Landing Zone er den korrekte startarkitekturen** for alle AI-prosjekter i norsk offentlig sektor — deploy SLZ med Sovereignty Baseline Policies som forste steg
|
||||
- **Norway East er primary-regionen for AI** — alle Azure AI-tjenester inkludert OpenAI og Confidential VMs er tilgjengelig der, med Norway West for DR
|
||||
- **Customer-Managed Keys (CMK) via Managed HSM er et minimum** for sovereign AI — dette gir kundekontrollert kryptering og tilfredsstiller NSM-krav
|
||||
- **Vendor lock-in-mitigering er pabudt** ifoolge Digdirs arkitekturprinsipper — bruk ONNX, open-source SLM (Phi), og Infrastructure-as-Code for a sikre portabilitet
|
||||
- **Transparency Logs og Compliance Manager er kritiske for revisjon** — norsk offentlig sektor ma kunne dokumentere operatoertilgang og compliance-status for Riksrevisjonen og Datatilsynet
|
||||
|
|
@ -0,0 +1,356 @@
|
|||
# Windows AI and AI PC Capabilities
|
||||
|
||||
**Last updated:** 2026-02
|
||||
**Status:** GA
|
||||
**Category:** Hybrid Cloud & Edge AI
|
||||
|
||||
---
|
||||
|
||||
## Introduksjon
|
||||
|
||||
Windows AI-plattformen representerer Microsofts satsing pa lokal AI-inferens direkte pa klientenheter. Med Windows ML (Machine Learning), ONNX Runtime integrert i OS, og Neural Processing Units (NPU) i Copilot+ PC-er, kan AI-modeller kjores lokalt med full datakontroll, ingen nettverkslatens, og forutsigbar ytelse.
|
||||
|
||||
For norsk offentlig sektor er Windows AI relevant for klientbaserte AI-funksjoner som dokumentklassifisering, oppsummering, og informasjonsuttrekking — alt uten at data forlater enheten. Ansatte kan bruke AI-stoettede verktoy for daglige oppgaver uten bekymring for at sensitive data sendes til skytjenester. Phi-4 Mini, innebygd i Microsoft Edge som lokal SLM, demonstrerer denne tilnaermingen.
|
||||
|
||||
Windows ML er den anbefalte veien for a deploye ONNX-modeller pa Windows, med automatisk Execution Provider-discovery som velger beste tilgjengelige akselerator — NPU, GPU eller CPU — uten at utviklere trenger a kode for spesifikk hardware.
|
||||
|
||||
---
|
||||
|
||||
## Kjernekomponenter
|
||||
|
||||
| Komponent | Formal | Teknologi |
|
||||
|-----------|--------|-----------|
|
||||
| Windows ML | ONNX Runtime integrert i Windows | Windows App SDK |
|
||||
| ONNX Runtime | Inferensmotor for AI-modeller | Open source |
|
||||
| DirectML | GPU/NPU-akselerasjon (legacy) | Windows |
|
||||
| Execution Providers | Hardware-spesifikke akseleratorer | QNN, OpenVINO, DML |
|
||||
| Phi-4 Mini | Innebygd SLM i Microsoft Edge | Lokal inferens |
|
||||
| AI Dev Gallery | Eksempler og modellkatalog | Open source |
|
||||
| Foundry Local | Klare-til-bruk AI-modeller | Microsoft |
|
||||
| Windows AI APIs | Innebygde AI-funksjoner | Windows SDK |
|
||||
|
||||
---
|
||||
|
||||
## Windows ML og ONNX Runtime
|
||||
|
||||
### Hvordan Windows ML fungerer
|
||||
|
||||
Windows ML inkluderer en kopi av ONNX Runtime og muliggjor dynamisk nedlasting av leverandorspesifikke Execution Providers (EP):
|
||||
|
||||
```
|
||||
[ONNX-modell] → [Windows ML] → [EP Discovery] → [Inferens]
|
||||
↓
|
||||
┌─────────────┼─────────────┐
|
||||
↓ ↓ ↓
|
||||
[Qualcomm QNN] [Intel OpenVINO] [DirectML]
|
||||
(Snapdragon NPU) (Intel NPU) (GPU/CPU)
|
||||
```
|
||||
|
||||
### Kodeeksempel: Windows ML-inferens i C#
|
||||
|
||||
```csharp
|
||||
// Windows ML-inferens med automatisk EP-discovery
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
|
||||
public class WindowsMLService
|
||||
{
|
||||
private InferenceSession _session;
|
||||
|
||||
public async Task<bool> InitializeAsync(string modelPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new SessionOptions();
|
||||
|
||||
// Windows ML velger automatisk beste EP:
|
||||
// 1. NPU (Qualcomm QNN / Intel OpenVINO) - lavest energibruk
|
||||
// 2. GPU (DirectML) - hoeyest ytelse
|
||||
// 3. CPU - alltid tilgjengelig fallback
|
||||
// EP-er lastes ned automatisk via Windows Update
|
||||
options.AppendExecutionProvider_WindowsML();
|
||||
|
||||
_session = new InferenceSession(modelPath, options);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Kunne ikke initialisere modell: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public float[] Classify(float[] input, int[] shape)
|
||||
{
|
||||
var tensor = new DenseTensor<float>(input, shape);
|
||||
var inputs = new List<NamedOnnxValue>
|
||||
{
|
||||
NamedOnnxValue.CreateFromTensor("input", tensor)
|
||||
};
|
||||
|
||||
using var results = _session.Run(inputs);
|
||||
return results.First().AsTensor<float>().ToArray();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Kodeeksempel: Python med Windows ML
|
||||
|
||||
```python
|
||||
# Windows ML-inferens i Python
|
||||
import onnxruntime as ort
|
||||
import numpy as np
|
||||
|
||||
# Opprett session med Windows ML EP
|
||||
session_options = ort.SessionOptions()
|
||||
session = ort.InferenceSession(
|
||||
"model.onnx",
|
||||
sess_options=session_options,
|
||||
providers=["WindowsMLExecutionProvider", "CPUExecutionProvider"]
|
||||
)
|
||||
|
||||
# Kjor inferens
|
||||
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)
|
||||
result = session.run(None, {"input": input_data})
|
||||
print(f"Output shape: {result[0].shape}")
|
||||
```
|
||||
|
||||
### Modellkompilering for optimal ytelse
|
||||
|
||||
```csharp
|
||||
// Kompiler modell for optimal EP-ytelse (forhands-optimalisering)
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
|
||||
// Kompilering kan ta flere minutter — gjor dette i bakgrunnen
|
||||
var compileOptions = new OrtModelCompilationOptions(sessionOptions);
|
||||
compileOptions.SetInputModelPath(modelPath);
|
||||
compileOptions.SetOutputModelPath(compiledModelPath);
|
||||
|
||||
// Kompiler modellen (optimal for enhetens hardware)
|
||||
await Task.Run(() => compileOptions.CompileModel());
|
||||
|
||||
// Bruk kompilert modell for raskere inferens
|
||||
var session = new InferenceSession(compiledModelPath, sessionOptions);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Neural Processing Unit (NPU)
|
||||
|
||||
### Hva er en NPU?
|
||||
|
||||
En Neural Processing Unit er en dedikert AI-brikke designet spesifikt for a utfore AI-oppgaver som moenstergjenkjenning, klassifisering og naturlig sprakbehandling. I motsetning til CPU (generelle beregninger) og GPU (parallellprosessering for grafikk), er NPU-er optimalisert for nevrale nettverksoperasjoner med lavt energiforbruk.
|
||||
|
||||
### NPU-landskap i Copilot+ PC-er
|
||||
|
||||
| Leverandoer | Chip | TOPS | Prosess | Plattform |
|
||||
|-------------|------|------|---------|-----------|
|
||||
| Qualcomm | Snapdragon X Elite | 45 TOPS | 4nm | ARM64 |
|
||||
| Qualcomm | Snapdragon X Plus | 45 TOPS | 4nm | ARM64 |
|
||||
| Intel | Core Ultra 200V | 48 TOPS | Intel 4 | x64 |
|
||||
| AMD | Ryzen AI 300 | 50 TOPS | 4nm | x64 |
|
||||
|
||||
### NPU vs GPU vs CPU for AI
|
||||
|
||||
| Aspekt | NPU | GPU | CPU |
|
||||
|--------|-----|-----|-----|
|
||||
| Energiforbruk | Lavest | Hoeyest | Medium |
|
||||
| AI-ytelse | Hoey (spesialisert) | Hoeyest (generell) | Lavest |
|
||||
| Tilgjengelighet | Nye PC-er | Diskret/integrert | Alle |
|
||||
| Modellstoette | INT4/INT8 | FP16/FP32 | Alle formater |
|
||||
| Best for | Alltid-pa AI | Tunge oppgaver | Fallback |
|
||||
|
||||
### Tilgang til NPU via Windows ML
|
||||
|
||||
```csharp
|
||||
// Automatisk NPU-bruk via Windows ML
|
||||
// Ingen eksplisitt NPU-koding nodvendig — Windows ML velger beste EP
|
||||
|
||||
// For avansert kontroll: Sjekk tilgjengelig hardware
|
||||
public void CheckAICapabilities()
|
||||
{
|
||||
var session = new InferenceSession("model.onnx");
|
||||
var providers = session.GetAvailableProviders();
|
||||
|
||||
foreach (var provider in providers)
|
||||
{
|
||||
Console.WriteLine($"Tilgjengelig EP: {provider}");
|
||||
// Eksempel output:
|
||||
// QNNExecutionProvider (Qualcomm NPU)
|
||||
// OpenVINOExecutionProvider (Intel NPU)
|
||||
// DmlExecutionProvider (GPU)
|
||||
// CPUExecutionProvider (CPU)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Copilot+ PC Specifications
|
||||
|
||||
### Minimumskrav for Copilot+ PC
|
||||
|
||||
| Krav | Spesifikasjon |
|
||||
|------|---------------|
|
||||
| NPU | Minimum 40 TOPS |
|
||||
| RAM | 16 GB eller mer |
|
||||
| Lagring | 256 GB SSD eller mer |
|
||||
| OS | Windows 11 24H2 eller nyere |
|
||||
|
||||
### Windows AI APIs (innebygde funksjoner)
|
||||
|
||||
| API | Funksjon | Krav | Status |
|
||||
|-----|----------|------|--------|
|
||||
| OCR | Tekstgjenkjenning i bilder | Copilot+ PC | GA |
|
||||
| Image Description | Bildebeskrivelese med AI | Copilot+ PC | GA |
|
||||
| Text Summarization | Oppsummering av tekst | Copilot+ PC | GA |
|
||||
| Object Erase | Fjern objekter fra bilder | Copilot+ PC | GA |
|
||||
| Image Segmentation | Segmentering av bilder | Copilot+ PC | GA |
|
||||
| Phi Silica | Innebygd SLM i Windows | Copilot+ PC | GA |
|
||||
|
||||
### Bruk av Windows AI APIs
|
||||
|
||||
```csharp
|
||||
// Windows AI API: Tekstoppsummering
|
||||
using Windows.AI;
|
||||
|
||||
public async Task<string> SummarizeText(string text)
|
||||
{
|
||||
var summarizer = await TextSummarizer.CreateAsync();
|
||||
|
||||
var result = await summarizer.SummarizeAsync(text, new SummarizerOptions
|
||||
{
|
||||
MaxSentences = 3,
|
||||
Language = "no" // Norsk stoette
|
||||
});
|
||||
|
||||
return result.Summary;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Local LLM Inference on Device
|
||||
|
||||
### Phi-4 Mini i Microsoft Edge
|
||||
|
||||
Microsoft Edge inkluderer Phi-4 Mini som lokal SLM, tilgjengelig via Web AI API-er:
|
||||
|
||||
```javascript
|
||||
// Prompt API i Microsoft Edge (Phi-4 Mini lokal inferens)
|
||||
// Ingen nettverkskall — alt skjer pa enheten
|
||||
|
||||
async function localAIClassification(text) {
|
||||
// Sjekk tilgjengelighet
|
||||
const availability = await ai.languageModel.capabilities();
|
||||
if (availability.available === 'no') {
|
||||
console.log('Lokal AI ikke tilgjengelig pa denne enheten');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Opprett session med system-prompt
|
||||
const session = await ai.languageModel.create({
|
||||
systemPrompt: `Du er en dokumentklassifiserer for norsk offentlig sektor.
|
||||
Klassifiser dokumenter i en av disse kategoriene:
|
||||
- Vedtak
|
||||
- Klage
|
||||
- Henvendelse
|
||||
- Intern notat
|
||||
- Hoeringssvar
|
||||
Svar KUN med kategorinavnet.`
|
||||
});
|
||||
|
||||
// Kjor lokal inferens
|
||||
const result = await session.prompt(
|
||||
`Klassifiser dette dokumentet: "${text.substring(0, 500)}"`
|
||||
);
|
||||
|
||||
session.destroy();
|
||||
return result.trim();
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Writing Assistance API: Oppsummering i Edge
|
||||
async function summarizeDocument(text) {
|
||||
const summarizer = await ai.summarizer.create({
|
||||
type: 'key-points',
|
||||
length: 'short',
|
||||
format: 'markdown'
|
||||
});
|
||||
|
||||
const summary = await summarizer.summarize(text);
|
||||
summarizer.destroy();
|
||||
return summary;
|
||||
}
|
||||
```
|
||||
|
||||
### Foundry Local for rikere modeller
|
||||
|
||||
```bash
|
||||
# Installer Foundry Local for lokale AI-modeller
|
||||
# Gir tilgang til storre modeller enn de innebygde
|
||||
|
||||
# List tilgjengelige modeller
|
||||
foundry model list
|
||||
|
||||
# Last ned Phi-3.5 for lokal bruk
|
||||
foundry model download phi-3.5-mini
|
||||
|
||||
# Start inferens-server
|
||||
foundry model serve phi-3.5-mini --port 11434
|
||||
|
||||
# Bruk via OpenAI-kompatibelt API
|
||||
curl http://localhost:11434/v1/chat/completions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "phi-3.5-mini",
|
||||
"messages": [
|
||||
{"role": "system", "content": "Du er en hjelpsom assistent for norsk offentlig sektor."},
|
||||
{"role": "user", "content": "Oppsummer folgende utredning..."}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Norsk offentlig sektor
|
||||
|
||||
### Brukstilfeller for Windows AI i offentlig sektor
|
||||
|
||||
| Brukstilfelle | Windows AI-losning | Fordel |
|
||||
|---------------|-------------------|--------|
|
||||
| E-post-triage | Phi-4 Mini (Edge Prompt API) | Klassifiser innkommende post uten sky |
|
||||
| Dokumentoppsummering | Windows AI Summarizer API | Rask oversikt over lange dokumenter |
|
||||
| Skjema-lesing | Windows AI OCR | Digitalisering av papirskjemaer |
|
||||
| Intern Q&A | Foundry Local + Phi-3.5 | Svar basert pa lokale dokumenter |
|
||||
| Referat-skriving | Edge Writing Assistance | Utkast til moetereferater |
|
||||
|
||||
### Sikkerhetshensyn
|
||||
|
||||
- Alle data forblir pa enheten — ingen nettverkskall for AI-inferens
|
||||
- Phi-4 Mini-modellen er innebygd i Edge, ikke nedlastet fra sky per session
|
||||
- Windows ML-modeller lagres lokalt og krever ingen sky-autentisering
|
||||
- IT-administratorer kan kontrollere AI-API-tilgjengelighet via Group Policy
|
||||
|
||||
---
|
||||
|
||||
## Beslutningsrammeverk
|
||||
|
||||
| Scenario | Anbefaling | Begrunnelse |
|
||||
|----------|------------|-------------|
|
||||
| Enkel tekst-AI pa klient | Edge Prompt API (Phi-4 Mini) | Innebygd, ingen oppsett |
|
||||
| Oppsummering/skriving | Edge Writing Assistance APIs | Spesialisert, hoey kvalitet |
|
||||
| Custom ONNX-modell | Windows ML med automatisk EP | Best hardware-utnyttelse |
|
||||
| Storre SLM lokalt | Foundry Local | OpenAI-kompatibelt API |
|
||||
| Enterprise-utrulling | Windows ML + Intune-administrasjon | Sentralisert styring |
|
||||
| NPU-optimalisert | Copilot+ PC med Windows ML | Best ytelse/watt |
|
||||
|
||||
---
|
||||
|
||||
## For Cosmo
|
||||
|
||||
- **Windows ML er den anbefalte veien for lokal AI pa Windows** — det erstatter DirectML og gir automatisk hardware-deteksjon og EP-nedlasting, noe som forenkler deployment dramatisk
|
||||
- **Copilot+ PC-er med NPU muliggjor always-on AI** med lavt energiforbruk — anbefal dette for klientbaserte AI-oppgaver som dokumentklassifisering og oppsummering
|
||||
- **Edge Prompt API (Phi-4 Mini) er den laveste terskelen for lokal AI** — utviklere kan bruke JavaScript-API-er for a integrere AI uten modellnedlasting eller kompleks oppsett
|
||||
- **For norsk offentlig sektor: Lokal AI pa klientenheter eliminerer behovet for a sende sensitive data til sky** — dette forenkler DPIA og Schrems II-compliance for enklere AI-brukstilfeller
|
||||
- **Modellkompilering er viktig for produksjonsytelse** — kompiler ONNX-modeller for target-hardware for a oppna opptil 2-3x forbedring i inferenshastighet etter forste kjoring
|
||||
Loading…
Add table
Add a link
Reference in a new issue