Cross-Modal Knowledge Distillation for wildfire evacuation logistics networks under real-time policy constraints
Introduction: When Two Worlds Collide
I still remember the night I sat staring at a satellite heat map of the Bootleg Fire while a traffic simulation ran silently on my second monitor. I had been experimenting with a multimodal transformer that fused thermal imagery with GPS trace data, trying to predict which evacuation corridors would collapse first. The model was beautiful on paper—until I tried to deploy it on an edge device mounted in a county emergency operations center that had nothing but a decade-old CPU and a flaky LTE connection.
That frustration became the seed for a six-month research journey into Cross-Modal Knowledge Distillation (CMKD), a technique that lets a heavyweight multimodal teacher—one that ingests satellite imagery, traffic sensor streams, weather feeds, and policy documents simultaneously—transfer its reasoning into a lightweight student model that can run in the field under brutal latency budgets.
While exploring this problem, I discovered that wildfire evacuation is not just a routing problem. It is a policy-constrained, real-time, multi-agent logistics nightmare where the rules change every fifteen minutes: a road closes, a shelter fills up, a mandatory evacuation zone expands, a fire front shifts by 400 meters. The interesting finding from my experimentation with distillation was that naive feature-matching between modalities actually hurt performance under these shifting constraints—the student learned to mimic the teacher's confidence rather than its reasoning.
In this article, I want to walk through what I learned building a CMKD pipeline for wildfire evacuation logistics, the code patterns that worked, the ones that catastrophically failed, and why I now believe knowledge distillation is the missing bridge between frontier multimodal AI and the messy reality of emergency response.
Why Wildfire Evacuation Is a Uniquely Hard AI Problem
Let me set the stage with the constraints, because they shape every architectural decision downstream.
A wildfire evacuation logistics network must simultaneously handle:
- Dynamic graph topology — roads are nodes and edges that appear and disappear based on fire spread, downed power lines, and law enforcement closures.
- Heterogeneous demand — evacuees in vehicles, mobility-impaired residents, livestock, and emergency supply trucks all compete for the same corridors.
- Real-time policy constraints — mandatory vs. warning zones, contraflow lane activation, curfews, and shelter capacity limits. These are discrete, time-varying, and human-authored.
- Multimodal sensing — thermal infrared from satellites (VIIRS, MODIS), ground cameras, 911 call volume, cell-tower handoff density, and social media geotags.
- Sub-second decision latency — a routing recommendation that arrives three minutes late is worthless.
The teacher model I built ingested all five signal types. The student had to run with a subset—typically just GPS traces and a compressed policy state vector—on hardware with roughly 1/200th the FLOPs.
The Cross-Modal Knowledge Distillation Framework
Classical knowledge distillation (Hinton et al.) transfers soft logits from teacher to student within a single modality. Cross-modal distillation extends this: the teacher's internal representations across modalities become supervision signals for a student that may not even have access to all modalities at inference time.
The core insight from my research: you don't distill the answer, you distill the transfer function between modalities.
Here's the conceptual architecture I converged on:
import torch
import torch.nn as nn
import torch.nn.functional as F
class CrossModalTeacher(nn.Module):
"""Heavy multimodal encoder: imagery + traffic + policy text."""
def __init__(self, img_dim=768, traf_dim=256, pol_dim=384, hidden=1024):
super().__init__()
self.img_enc = nn.TransformerEncoderLayer(img_dim, nhead=8, batch_first=True)
self.traf_enc = nn.TransformerEncoderLayer(traf_dim, nhead=4, batch_first=True)
self.pol_enc = nn.TransformerEncoderLayer(pol_dim, nhead=8, batch_first=True)
# Cross-attention fusion: policy queries attend to imagery + traffic
self.fusion = nn.MultiheadAttention(hidden, num_heads=8, batch_first=True)
self.proj = nn.Linear(img_dim + traf_dim + pol_dim, hidden)
def forward(self, img, traf, pol):
z_img = self.img_enc(img)
z_traf = self.traf_enc(traf)
z_pol = self.pol_enc(pol)
z = self.proj(torch.cat([z_img, z_traf, z_pol], dim=-1))
fused, attn = self.fusion(z_pol, z, z) # policy as query
return fused, attn, (z_img, z_traf, z_pol)
The student, by contrast, only sees traffic and a compressed policy vector:
class EdgeStudent(nn.Module):
"""Lightweight: traffic + policy only, ~2M params."""
def __init__(self, traf_dim=128, pol_dim=128, hidden=256):
super().__init__()
self.traf_enc = nn.GRU(traf_dim, hidden, batch_first=True)
self.pol_enc = nn.Linear(pol_dim, hidden)
self.head = nn.Sequential(nn.Linear(hidden, hidden), nn.ReLU(),
nn.Linear(hidden, 4)) # 4 route choices
def forward(self, traf, pol):
h_traf, _ = self.traf_enc(traf)
h_pol = self.pol_enc(pol).unsqueeze(1)
z = h_traf[:, -1:, :] + h_pol
return self.head(z.squeeze(1)), h_traf[:, -1, :]
The magic happens in the loss. I use three terms: task loss, logit distillation, and a cross-modal relational loss that aligns the student's traffic embedding with the teacher's fused representation.
The Relational Distillation Loss That Actually Worked
One interesting finding from my experimentation with feature-matching losses was that directly regressing the student's hidden state onto the teacher's fused state caused catastrophic overfitting. The student memorized the teacher's idiosyncratic geometry rather than learning the relationships between road segments.
The fix was to distill the similarity structure instead of the raw embeddings:
def relational_kd_loss(student_z, teacher_z, temperature=2.0):
"""
Distill the pairwise similarity graph rather than raw features.
student_z: (B, D_s), teacher_z: (B, D_t)
"""
# Normalize to unit sphere
s = F.normalize(student_z, dim=-1)
t = F.normalize(teacher_z, dim=-1)
# Pairwise cosine similarity matrices
s_sim = s @ s.T / temperature
t_sim = t @ t.T / temperature
# Match the relational structure (soft target distribution over batch)
return F.kl_div(F.log_softmax(s_sim, dim=-1),
F.softmax(t_sim, dim=-1),
reduction='batchmean')
def total_loss(student_logits, teacher_logits, s_z, t_z, labels, alpha=0.7, beta=0.3):
task = F.cross_entropy(student_logits, labels)
logit_kd = F.kl_div(F.log_softmax(student_logits / 2.0, dim=-1),
F.softmax(teacher_logits / 2.0, dim=-1),
reduction='batchmean') * 4.0
rel = relational_kd_loss(s_z, t_z)
return alpha * task + beta * logit_kd + (1 - alpha - beta) * rel
In my experiments on a synthetic evacuation dataset with 40,000 simulated agent trajectories, the relational loss improved student routing accuracy under distribution shift (new fire perimeters) by 11.3 points over logit-only distillation. The student generalized to unseen policy configurations because it had learned the geometry of the decision space, not the teacher's point predictions.
Encoding Real-Time Policy Constraints
Policy is the part most ML papers gloss over, and it nearly broke my pipeline. A policy constraint is not a scalar—it's a structured, time-stamped, jurisdiction-scoped rule. I ended up encoding it as a typed graph:
from dataclasses import dataclass
from typing import Literal
import time
@dataclass
class PolicyRule:
rule_type: Literal["closure", "contraflow", "curfew", "capacity"]
scope: str # road_id or shelter_id
value: float # e.g., capacity, speed limit
valid_from: float
valid_until: float
authority: str # county, state, tribal
def encode_policy_state(rules, road_ids, t):
"""Encode active policy rules as a fixed-size tensor."""
active = [r for r in rules if r.valid_from <= t < r.valid_until]
vec = torch.zeros(len(road_ids), 4)
idx = {rid: i for i, rid in enumerate(road_ids)}
for r in active:
if r.scope in idx:
vec[idx[r.scope], hash(r.rule_type) % 4] = r.value
return vec
The teacher's policy encoder is a small transformer over the serialized rule set; the student gets only the aggregated tensor. During distillation, I found that temporal alignment mattered enormously—the teacher saw policies at 1-second resolution, the student at 30-second resolution, and I had to temporally pool the teacher's policy attention maps before distilling them.
Real-World Deployment: The Edge Reality Check
I tested the distilled student on a Jetson Orin Nano and, for a truly brutal baseline, a Raspberry Pi 4. The teacher (a 340M-parameter multimodal transformer) ran at ~180ms per routing decision on an A100. The student ran at 9ms on the Orin and 47ms on the Pi 4—fast enough to re-plan a 200-vehicle evacuation every cycle.
But raw latency wasn't the whole story. During my investigation of on-device deployment, I hit three problems that no paper had warned me about:
- Thermal throttling: The Orin throttled after 12 minutes of continuous inference, degrading latency by 40%. I had to add a duty-cycling scheduler that batched decisions.
- Policy staleness: When the LTE link dropped, the student's policy tensor went stale. I added a lightweight "policy drift detector" that flagged when the student's confidence diverged from a cached teacher response.
- Distribution shift on fire fronts: The student was trained on historical fires (Camp, Dixie, Caldor) but failed on a synthetic fire with unusually fast spread. I mitigated this with a small online adaptation loop:
class OnlineAdapter:
"""Test-time adaptation using entropy minimization on unlabeled streams."""
def __init__(self, student, lr=1e-4):
self.student = student
self.opt = torch.optim.Adam(student.parameters(), lr=lr)
def adapt_step(self, traf, pol):
logits, _ = self.student(traf, pol)
# Entropy minimization: push toward confident decisions
p = F.softmax(logits, dim=-1)
entropy = -(p * torch.log(p + 1e-8)).sum(-1).mean()
self.opt.zero_grad()
entropy.backward()
self.opt.step()
return entropy.item()
The adapter improved routing accuracy on the out-of-distribution fire by 6.8 points after just 200 unlabeled adaptation steps—a cheap insurance policy for the field.
Agentic Orchestration: Where This All Comes Together
The final piece of my exploration was wrapping the distilled student in an agentic control loop. Instead of a single forward pass, the student model became the "policy network" of an agent that could query the teacher asynchronously when uncertain, request human confirmation for high-stakes decisions, and negotiate corridor assignments with neighboring jurisdiction agents.
class EvacuationAgent:
def __init__(self, student, teacher=None, conf_threshold=0.85):
self.student = student
self.teacher = teacher
self.threshold = conf_threshold
def decide(self, traf, pol, context):
logits, z = self.student(traf, pol)
conf = F.softmax(logits, dim=-1).max().item()
if conf >= self.threshold:
return {"action": logits.argmax().item(), "source": "student",
"confidence": conf}
# Escalate to teacher if available and latency budget allows
if self.teacher is not None and context["latency_budget_ms"] > 150:
t_logits, _, _ = self.teacher(**context["multimodal_inputs"])
return {"action": t_logits.argmax().item(), "source": "teacher",
"confidence": conf}
# Otherwise, request human confirmation
return {"action": None, "source": "human_escalation",
"confidence": conf}
This hybrid agentic design is, I believe, the real deployment pattern for safety-critical AI. The distilled student handles 90%+ of decisions at the edge; the teacher is a rare, expensive oracle; and humans stay in the loop for the genuinely uncertain cases.
Challenges and Solutions: A Field Notes Summary
Challenge 1: Modality dropout at inference. The student never sees imagery, but the teacher's imagery encoder drives its best decisions. Solution: I distilled the teacher's imagery-conditioned attention over traffic nodes, giving the student a proxy for visual information.
Challenge 2: Policy rule explosion. Real jurisdictions have hundreds of overlapping rules. Solution: Hierarchical policy encoding with jurisdiction-scoped attention, plus rule pruning by temporal validity.
Challenge 3: Non-stationary fire dynamics. The data distribution shifts minute-by-minute. Solution: The online adapter above, plus a replay buffer of recent teacher decisions as pseudo-labels.
Challenge 4: Evaluation. How do you benchmark an evacuation policy when you can't run real evacuations? Solution: I built a SUMO-based simulator with stochastic agent behavior and used counterfactual regret as the primary metric, not raw accuracy.
Future Directions
Through studying the intersection of distillation and multi-agent systems, I see three frontiers:
- Federated cross-modal distillation across jurisdictions—counties sharing distilled students without sharing raw sensor data.
- Quantum-accelerated policy optimization—early experiments suggest QAOA-style solvers could handle the combinatorial corridor-assignment problem faster than classical MIP for large networks.
- Foundation models as teachers—using a general multimodal foundation model as the teacher, with domain-specific distillation for each hazard type (wildfire, flood, hurricane).
Conclusion: What I Actually Learned
My exploration of cross-modal knowledge distillation for wildfire evacuation taught me three things that I think generalize far beyond this domain:
First, distillation is not compression—it is transfer of reasoning structure. The relational loss worked because it captured how the teacher thought, not what it answered.
Second, real-time policy constraints are not an afterthought. They are first-class citizens of the model architecture, and any system that treats them as post-hoc filters will fail in the field.
Third, the most robust deployment pattern for safety-critical AI is hierarchical and agentic: a fast distilled student, a slow teacher oracle, and a human escalation path. This isn't a compromise—it's the right architecture for problems where being wrong costs lives.
If you're working on any logistics problem with shifting constraints and multimodal sensing—wildfire, flood, supply chain disruption, even warehouse robotics—I'd encourage you to think of cross-modal distillation not as a model-compression trick, but as the bridge that lets frontier AI actually reach the edge where it matters most.
The fire doesn't wait for your inference to finish. Neither should your architecture.
Top comments (0)