27. 什么是算子
Operator(算子)是一个边界清楚、输入输出格式确定、可以被独立部署的计算函数。 例如“把 RGB 图像转成灰度图”是一个算子;“把卫星 20 上的所有程序笼统看成一个服务”不是一个足够精确的算子定义。

图 1 “经过算子”是一次真实的转发与进程计算
一个算子至少要回答六个问题:
- 输入是什么字节格式?
- 输出是什么字节格式?
- 算法是否确定,也就是相同输入是否产生相同输出?
- 状态属于单包、单 Tile、单对象还是长期实例?
- 需要等待哪些其他输入才能计算?
- 失败、超时和不完整时如何记录?
本项目把算子放到网络路径上。业务包沿 SRv6 路径到达算子 SID,Linux 执行 End.X,把包送入该算子的独立 network namespace;算子进程通过 AF_XDP 收包、计算、改写 SPP 负载,再把包送回路径。因此“经过算子”不是日志里的逻辑标签,而是一次真实的包转发和进程计算。
28. 算子类型、算子实例和算子 SID
这三个概念很容易混淆。
28.1 算子类型
Operator Type(算子类型)说明做什么计算。例如 Gray 表示灰度化,Edge 表示边缘 检测。类型由函数编号表示:
| 类型 | 函数编号 | 十六进制后缀 | 含义 |
|---|---|---|---|
| Gray | 256 | 0x100 | RGB 转灰度 |
| Edge | 512 | 0x200 | Sobel 边缘检测 |
| Fire Mask | 768 | 0x300 | 保留疑似火焰颜色 |
| Binary Fire Mask | 1024 | 0x400 | 火焰像素二值化 |
| Tile Mean 2×2 | 1280 | 0x500 | 2×2 Tile 分组均值与降采样 |
| Tile Blur 3×3 | 1536 | 0x600 | 3×3 邻域模糊 |
| BNN Fire Filter | 1792 | 0x700 | 二值神经网络火焰筛选 |
28.2 算子实例
Operator Instance(算子实例)是某一物理节点上正在运行的一个具体进程。例如:

图 2 七类算子类型及其函数编号
Gray类型
├── Gray@node-2
├── Gray@node-20
└── Gray@node-38
这些实例功能相同,但位置、CPU 负载、内存、路径和生命周期不同。HEFT 选择的是具体实例,而不是抽象类型。
28.3 具体算子 SID
具体实例的 IPv6 地址规则是:
fd42:3:<node-id>::<function-id>
例如节点 20 上的 Gray 实例是:
fd42:3:14::100
这里 14 是节点号 20 的十六进制表示,100 是函数号 0x100。这个地址同时表达了“在哪个节点”和“是什么函数”。
28.4 逻辑 Anycast SID
Anycast(任播)让多个实例共同发布同一个逻辑服务地址,路由系统选择其中一个可达 provider(提供者)。逻辑地址规则为:
fd42:7::<function-id>
Gray 逻辑服务是 fd42:7::100。它没有把物理节点写进地址,因此访问者只声明“我要 Gray”,不声明“必须去节点 20”。
要牢牢记住:
fd42:3:14::100 = node-20上的具体Gray实例
fd42:7::100 = 当前路由选择的某个Gray实例
29. 算子清单从哪里来
每个节点启动时,根据 operator profile(算子配置档)创建自己的实例。当前heterogeneous-v1 意为“异构版本 1”:不同节点有不同函数集合,从而形成真实的编排问题。
当前确定性分配规则是:
- 奇数节点提供 Fire Mask;
- 节点号对 3 取余等于 1 时提供 Binary Fire Mask;
- 偶数节点提供 Gray;
- 节点号能被 3 整除时提供 Edge;
- 节点号能被 4 整除时提供 Tile Mean 2×2;
- 节点号能被 6 整除时提供 Tile Blur 3×3;
- 节点号能被 5 整除时提供 BNN Fire Filter。
这是基础设施能力分布规则,不是针对某一次实验结果临时安排“正确答案”。节点把自己真实启动成功且尚未过期的实例,连同资源和 lifetime(生命周期)上报。控制器不进入节点探测,也不凭配置文件猜测实例一定存在。
三级降档业务要求所有网络节点都有 Gray 和 Edge,因此相应场景会额外保证这两个函数存在。它表达的是该业务的部署前提,不改变其他算子的异构分布。
30. 一个算子实例在 Linux 里怎样存在
每个实例有自己的 network namespace 和进程。主节点 namespace 与算子 namespace 之间通过专用 veth pair 连接:
主节点namespace
concrete operator SID
│ End.X
▼
算子专用veth ── 算子network namespace
│
├── XDP程序
├── AF_XDP socket
└── operator worker进程
End.X表示“执行本地 SRv6 端点行为后,交给指定下一跳接口”。因此包确实跨入算子 namespace。算子 worker 完成以下循环:
- 从 AF_XDP RX ring 取 descriptor;
- 在 UMEM 读取以太网帧;
- 校验 IPv6、SRH 和 SPP 边界;
- 执行自己的算法;
- 必要时改写 SPP 格式、尺寸、payload length 和校验相关字段;
- 把 descriptor 放入 TX ring;
- 回收已经发送完成的 UMEM frame;
- 记录结构化事件和错误。
supervisor(监督进程)负责维持 worker 存在。worker 异常退出时,supervisor 记录退出并重启它,而不是让整个节点或整个仿真退出。
31. 无状态与有状态算子
31.1 单 Tile 无状态算子
Gray、Edge、Fire Mask、Binary Fire Mask 和 BNN Fire Filter 只需当前 Tile 就能决定当前包。它们不需要等待同一对象的其他 Tile,因而可以把状态限制在单包处理期间。
这里的“无状态”不表示进程没有计数器、模型权重或 UMEM;它只表示业务结果不依赖此前由同一实例收到的其他 Tile。
31.2 跨 Tile 有状态算子
Tile Mean 2×2 和 Tile Blur 3×3 必须缓存同一对象的多个 Tile。它们的输出依赖输入集合是否完整,所以状态身份必须包含:
(task_id, object_id, object_epoch)
2×2 Mean 还要加入当前 2×2 group(分组)的横纵坐标。不能只按 object_id 缓存,否则不同任务、同对象的新版本或不同分组会污染彼此。
32. 图像格式和单 Tile 大小
当前 Tile 固定为 16×16 像素。三种 SPP payload format(负载格式)是:
| 格式 | 编号 | 每像素 | 16×16 字节数 | 含义 |
|---|---|---|---|---|
| RGB24 | 1 | 24 bit | 768 | 红、绿、蓝各 8 bit |
| GRAY8 | 2 | 8 bit | 256 | 每像素一个灰度值 |
| EDGE1 | 3 | 1 bit | 32 | 每像素一个边缘 bit |
bit 是二进制位,只能取 0 或 1;8 bit 组成一个 byte。RGB24 每像素 3 byte,所以16×16×3=768。EDGE1 共有 256 bit,所以 256÷8=32 byte。
降档方向只能是:
RGB24 → GRAY8 → EDGE1
不能从 EDGE1“升级”回 RGB,因为前一步已经丢失颜色和强度信息,网络不能凭空恢复。
33. Gray:灰度化
Gray 不是简单平均三个颜色分量,而是使用整数近似的亮度权重:
gray = (77 × R + 150 × G + 29 × B) >> 8
>> 8 表示右移 8 位,等价于对非负整数除以 256 并取整。三个权重之和为 256,因此输出仍在0 到 255 之间。绿色权重最大,是因为人眼对绿色亮度更敏感。
输入必须是 RGB24,输出是 GRAY8。已经是 GRAY8 或 EDGE1 的包不会被“伪装成新计算”重复升降格式。
34. Edge:Sobel 边缘检测
Sobel Operator(索贝尔算子)用水平与垂直卷积核估计图像亮度变化。卷积是把一个小 矩阵放到像素邻域上,对应元素相乘后求和。
典型 3×3 核为:
Gx = -1 0 1 Gy = -1 -2 -1
-2 0 2 0 0 0
-1 0 1 1 2 1
本项目使用确定性的 L1 近似:
edge_strength = (abs(gx) + abs(gy)) / 8
abs 是绝对值。若输出紧凑 EDGE1,则将强度与阈值比较;默认阈值为 32,大于等于阈值写 1,否则写 0。这样把 256 byte 灰度 Tile 压成 32 byte 边缘位图。
35. Fire Mask 与 Binary Fire Mask
两者使用同一个确定性候选条件:
R > 120
且 R × 100 > G × 112
且 R × 100 > B × 135
且 R - B > 45
这些式子要求像素不仅“红”,而且红色相对绿、蓝有足够优势。
- Fire Mask 对候选像素保留 R 并把 G、B 置 0;非候选像素三个分量都置 0。
- Binary Fire Mask 对候选像素写
(255,0,0);非候选写(0,0,0)。
前者保留红色强度,后者只保留“是否候选”。
36. Tile Mean 2×2
该算子把空间上相邻的四个 16×16 Tile 看成一个 2×2 group:
左上 右上
左下 右下
只有同一 task_id/object_id/object_epoch、同一 group 的四块都到齐,算子才计算输出。其作用是对对应像素区域求均值并做 2 倍降采样,因此输出对象的宽和高都约为输入的一半。
不能用“收到任意四个 Tile”作为条件。必须先把 Morton tile id 还原为二维坐标,再计算它属于哪个 2×2 group,以及它在组内的位置。
若窗口到期仍不完整,算子丢弃这个窗口并记录已收、缺失以及完整缓存身份;它不会合成黑色Tile,不会把缺失自动算成整个 task 失败,也不会替源端重发。
37. Tile Blur 3×3
Blur 使用一个中心 Tile 周围的 3×3 Tile 邻域。内部像素的模糊需要相邻 Tile 边界,因此算子为同一对象维护共享缓存。当某个中心 Tile 所需邻居都到达时,才发出该中心的输出。
对象边界没有九个真实邻居,算法按确定的边界规则处理;关键不是“永远等九块”,而是根据对象网格和中心坐标算出实际需要的集合。
每个中心输出就绪后可以独立发出,不需要等待整张图的全部 Tile。这避免人为建立全局 barrier(屏障)。
38. BNN Fire Filter
BNN(Binarized Neural Network,二值神经网络)把大部分权重和中间激活约束为两个
离散值,通常可理解为 -1/+1。它比普通浮点神经网络更紧凑,更适合节点内快速推理。
当前模型输入是 16×16 RGB,共 16×16×3=768 个数。预处理为:
x = uint8_value / 127.5 - 1.0
网络结构为:
768输入 → 512二值隐藏单元 → 16二值隐藏单元 → 1个logit
Logit(未归一化判别分数)大于等于 0 表示 KEEP,即保留疑似火焰 Tile;小于 0 表示 DROP。 运行节点使用固定的紧凑 C 模型,不在节点容器里启动 PyTorch。
KEEP 时原包不变继续转发;DROP 时算子消费该帧并回收 UMEM,不伪造一个“全黑结果包”;ERROR 是模型或格式错误,必须与正常 DROP 分开记录。
38.1 从 BNN 首次发现到 Alarm 闭环
BNN 不只做 KEEP/DROP 统计。对同一 (task_id, object_id),第一个 KEEP Tile 会形成一次
Fire Detection(火情发现),携带检测节点、Tile 坐标、墙钟时间和单调时钟时间。算子
通过非阻塞 Unix datagram 把发现交给同一节点内的集成端侧业务进程;控制器和前端都不产生Alarm。
集成端侧随后自然创建一条新的普通传输任务:
bnn_fire_first_detected
→ fire_alarm_created
→ fire_alarm_task_requested
→ 入口安装Alarm自己的策略
→ fire_alarm_task_ready
→ fire_alarm_sent
→ 固定地面端点fire_alarm_received
Alarm 有独立 alarm_task_id,同时保留 parent_task_id 和 object_id,所以检测业务与告警业务不会被混成同一个 task。线上 FAL1 数据报还包含 detector node、destination endpoint、Tile 坐标以及原检测时间戳。Alarm 发送使用 IPv6 traffic class CS7 和 socket priority 7,但仍必须经过正常 REQUEST/READY、SRv6 和物理链路,优先级不能绕过不可达事实。
闭环时间从 detected_monotonic_ns 量到目的端真实接收,当前森林火灾合同的 deadline 为60 秒。前端显示的“Alarm 闭环”必须由这条完整事件链计算,不能从 BNN KEEP 事件直接伪造一个已送达结果。
39. 算子事件为什么必须有业务身份
有状态窗口的日志至少应携带:
run_id:哪一轮实验;node_id和 operator instance:哪个实例;task_id:哪条业务编排;object_id:哪一个逻辑图像对象;object_epoch:对象的哪一版;tile_id或 group 坐标:哪一块/组;- received、expected 和 missing:收了多少、应有多少、缺什么;
- event time:什么时候发生。
只写“incomplete window dropped”无法区分是 Anycast 漂移、链路损伤、源端未发、epoch 混用,还是缓存键错误。补全身份不是为了美化日志,而是为了让组件边界可以被证伪。