本文是《从 IP 到 SRv6 计算编排》新手教材第 5/13 编。 总目录 · 上一篇:第四编:统一任务、Color 和策略安装 · 下一篇:第六编:Anycast 从逻辑服务选择实例
27. 什么是算子
**Operator(算子)**是一个边界清楚、输入输出格式确定、可以被独立部署的计算函数。 例如“把RGB图像转成灰度图”是一个算子;“把卫星20上的所有程序笼统看成一个服务” 不是一个足够精确的算子定义。
一个算子至少要回答六个问题:
- 输入是什么字节格式?
- 输出是什么字节格式?
- 算法是否确定,也就是相同输入是否产生相同输出?
- 状态属于单包、单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(算子实例)**是某一物理节点上正在运行的一个具体进程。例如:
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混用, 还是缓存键错误。补全身份不是为了美化日志,而是为了让组件边界可以被证伪。