# 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.
