工业 AI 推理高可用方案设计:双机热备下的模型状态同步与无感切换机制详解
工业 AI 推理高可用方案设计双机热备下的模型状态同步与无感切换机制详解一、引言在工业场景中AI 推理宕机的后果可能比通用 IT 系统严重得多——一块 PCB 板的人工复检成本约 3 元一条 SMT 产线停产 1 小时损失约 8000 元。因此边缘推理节点必须具备高可用HA能力。然而工业 AI 的 HA 设计与传统 Web 服务的 HA 有本质区别AI 推理节点不仅需要处理请求-响应的链路切换还需要保证两台设备的模型版本一致、推理状态如异常检测的自适应阈值、增量学习的最新权重同步、以及切换后的输出信号无毛刺。当方案从单一的 Keepalived VIP 漂移升级到真正的双机热备 (Active-Standby)时需要解决模型状态的确定性同步和切换窗口内的输出连续性两个核心问题。二、原理剖析工业 AI 推理高可用的架构分为三层检测层心跳 自检→ 决策层故障仲裁→ 执行层信号切换。故障检测采用双通道心跳机制软件心跳通过 TCP/UART 每 100ms 发送包含时间戳和模型版本号的状态包硬件心跳通过 GPIO 输出方波信号10Hz由独立的 Watchdog MCU 直接检测电平跳变——即使主节点 CPU 死锁、OS 崩溃GPIO 方波也会停止Watchdog 能在 100ms 内检测到硬件故障。仲裁逻辑的决策树三、代码实现以下为双机热备中模型状态同步和切换的核心实现。/** * file ai_ha_manager.c * brief 工业 AI 推理双机热备 — 状态同步 故障切换 * hw 两台 RK3588 工控机 (主/备) STM32G0 Watchdog MCU * comm 主备间: TCP (端口 9556) GPIO (物理心跳) * Watchdog: UART (心跳检测) GPIO (切换控制) */ #include stdio.h #include stdlib.h #include string.h #include stdint.h #include stdbool.h #include unistd.h #include pthread.h #include time.h /* 常量定义 */ #define HEARTBEAT_INTERVAL_MS 100 /* 心跳间隔 */ #define HEARTBEAT_TIMEOUT_MS 300 /* 超时判定故障 (3个周期) */ #define STATE_SYNC_INTERVAL_MS 500 /* 状态同步间隔 */ #define MODEL_CHECKSUM_LEN 32 /* SHA-256 摘要长度 */ #define MAX_SYNC_PAYLOAD_SIZE 4096 /* 同步载荷最大字节 */ /* 角色枚举 */ typedef enum { ROLE_ACTIVE 0, /* 主节点 */ ROLE_STANDBY 1, /* 备节点 */ ROLE_UNKNOWN 2 /* 初始状态 */ } node_role_t; /* 模型状态数据结构 */ typedef struct { char model_version[32]; /* 模型版本号 (如 v3.2.1-20240721) */ char model_checksum[MODEL_CHECKSUM_LEN 1]; /* 权重文件 SHA-256 */ uint32_t model_updated_at; /* 模型最后更新时间戳 (epoch) */ float adaptive_threshold; /* 异常检测自适应阈值 */ uint32_t inference_count; /* 累计推理次数 (用于断点续传) */ uint32_t positive_count; /* 检出 NG 品累计次数 */ float avg_confidence; /* 最近 1000 次推理的平均置信度 */ uint8_t reserved[32]; /* 保留扩展字段 */ } model_state_t; /* 总大小约 128 字节 */ /* 心跳包结构 */ typedef struct { uint32_t seq_num; /* 递增序号 (检测丢包) */ uint32_t timestamp_ms; /* 发送时间戳 */ node_role_t sender_role; /* 发送方角色 */ uint8_t node_healthy; /* 0健康, 1自检失败 */ char model_version[32]; /* 当前模型版本 */ uint32_t crc32; /* 整个包的 CRC 校验 */ } heartbeat_pkt_t; /* 节点管理器 */ typedef struct { /* 本节点信息 */ node_role_t my_role; /* 当前角色 (Active/Standby) */ model_state_t my_state; /* 本节点模型状态 */ uint32_t last_hb_recv; /* 最后收到对端心跳的时间戳 */ /* 对端信息 */ char peer_ip[16]; /* 对端 IP */ uint16_t peer_port; /* 对端 TCP 端口 */ uint8_t peer_healthy; /* 对端健康状态 */ bool peer_alive; /* 对端是否存活 */ /* GPIO 硬件心跳 */ int gpio_hb_out; /* 本机 GPIO 心跳输出引脚编号 */ int gpio_hb_in; /* 对端 GPIO 心跳输入引脚编号 */ /* 同步状态 */ bool sync_pending; /* 有待同步的增量变更 */ pthread_mutex_t state_lock; /* 状态读写互斥锁 */ } ha_node_t; /* CRC32 计算 */ static const uint32_t crc32_table[256] { /* 标准 CRC-32 查找表 (多项式 0xEDB88320) */ /* 此处省略完整 256 项, 实际实现需补全 */ 0x00000000, 0x77073096, /* ... 254 项省略 ... */ }; static uint32_t crc32(const uint8_t *data, size_t len) { uint32_t crc 0xFFFFFFFFUL; for (size_t i 0U; i len; i) { crc (crc 8U) ^ crc32_table[(crc ^ data[i]) 0xFFU]; } return crc ^ 0xFFFFFFFFUL; } /* 心跳发射 */ /** * brief 发送心跳包到对端节点 (TCP) * param node 本节点管理器 * param fd 已建立的 TCP socket 文件描述符 * return 0成功, -1发送失败 */ static int send_heartbeat(ha_node_t *node, int fd) { if (fd 0) { return -1; } heartbeat_pkt_t hb; memset(hb, 0, sizeof(hb)); pthread_mutex_lock(node-state_lock); /* 填充心跳包各字段 */ hb.seq_num node-my_state.inference_count; /* 利用推理计数作为序号 */ hb.timestamp_ms (uint32_t)(time(NULL) * 1000ULL); hb.sender_role node-my_role; hb.node_healthy (node-my_role ROLE_UNKNOWN) ? 1U : 0U; memcpy(hb.model_version, node-my_state.model_version, sizeof(hb.model_version)); /* CRC32 计算覆盖除 crc32 字段外的所有字段 */ hb.crc32 crc32((const uint8_t *)hb, sizeof(heartbeat_pkt_t) - sizeof(uint32_t)); pthread_mutex_unlock(node-state_lock); /* 通过 TCP 发送 */ ssize_t sent write(fd, hb, sizeof(hb)); if (sent ! (ssize_t)sizeof(hb)) { fprintf(stderr, [ERROR] 心跳发送失败: 期望 %zu 字节, 实际 %zd 字节\n, sizeof(hb), sent); return -1; } return 0; } /* 心跳接收与故障检测 */ /** * brief 接收并校验对端心跳包 * param node 本节点管理器 * param fd 已建立的 TCP socket * return 0正常, -1接收失败, -2CRC 错误 */ static int recv_heartbeat(ha_node_t *node, int fd) { if (fd 0) { return -1; } heartbeat_pkt_t hb; ssize_t bytes read(fd, hb, sizeof(hb)); if (bytes ! (ssize_t)sizeof(hb)) { /* 读取不完整或对端断开 */ return -1; } /* CRC 校验 */ uint32_t recv_crc hb.crc32; uint32_t calc_crc crc32((const uint8_t *)hb, sizeof(heartbeat_pkt_t) - sizeof(uint32_t)); if (recv_crc ! calc_crc) { fprintf(stderr, [WARN] 心跳 CRC 校验失败: recv0x%08X calc0x%08X\n, recv_crc, calc_crc); return -2; } /* 更新对端状态 */ pthread_mutex_lock(node-state_lock); node-last_hb_recv (uint32_t)(time(NULL) * 1000ULL); node-peer_healthy (hb.node_healthy 0U); node-peer_alive true; /* 检查版本一致性 */ if (strncmp(hb.model_version, node-my_state.model_version, sizeof(hb.model_version)) ! 0) { printf([WARN] 模型版本不一致: 对端%s 本端%s, 需要同步\n, hb.model_version, node-my_state.model_version); } pthread_mutex_unlock(node-state_lock); return 0; } /* 模型状态同步 */ /** * brief 序列化模型状态为 JSON 字符串 (用于 TCP 传输) * param state 模型状态 * param buf 输出缓冲区 * param buf_sz 缓冲区大小 * return 序列化后的 JSON 字符串长度, -1 表示溢出 */ static int serialize_state(const model_state_t *state, char *buf, size_t buf_sz) { if (state NULL || buf NULL || buf_sz 0U) { return -1; } int n snprintf(buf, buf_sz, { \ver\:\%s\, \chk\:\%s\, \upd\:%u, \thr\:%.6f, \cnt\:%u, \pos\:%u, \conf\:%.6f }, state-model_version, state-model_checksum, state-model_updated_at, state-adaptive_threshold, state-inference_count, state-positive_count, state-avg_confidence); if (n 0 || (size_t)n buf_sz) { return -1; /* 缓冲区不足 */ } return n; } /** * brief 同步模型状态 [Active → Standby] * param node 本节点管理器 (应为 Active) * param fd TCP socket * * note 同步策略: 增量同步 — 仅同步自上次同步以来变化的状态字段 * 全量同步 — 在 Standby 首次上线或时钟对齐失败时触发 */ static int sync_model_state(ha_node_t *node, int fd) { if (fd 0 || node NULL) { return -1; } char payload[MAX_SYNC_PAYLOAD_SIZE]; pthread_mutex_lock(node-state_lock); int payload_len serialize_state(node-my_state, payload, sizeof(payload)); pthread_mutex_unlock(node-state_lock); if (payload_len 0) { fprintf(stderr, [ERROR] 状态序列化溢出\n); return -1; } /* 前置 4 字节长度头 JSON 负载 */ uint32_t net_len htonl((uint32_t)payload_len); if (write(fd, net_len, sizeof(net_len)) ! sizeof(net_len)) { fprintf(stderr, [ERROR] 同步长度头发送失败\n); return -1; } if (write(fd, payload, (size_t)payload_len) ! payload_len) { fprintf(stderr, [ERROR] 同步负载发送失败\n); return -1; } return 0; } /* 切换决策 */ /** * brief 评估是否应该执行角色切换 * param node 本节点管理器 * return true需要切换, false保持当前角色 */ static bool should_failover(ha_node_t *node) { uint32_t now (uint32_t)(time(NULL) * 1000ULL); uint32_t elapsed; /* 计算距离上次收到对端心跳的时间 */ if (now node-last_hb_recv) { elapsed now - node-last_hb_recv; } else { /* 时间戳溢出绕回 */ elapsed (0xFFFFFFFFU - node-last_hb_recv) now 1U; } /* 条件 1: 软件心跳超时 */ if (elapsed HEARTBEAT_TIMEOUT_MS) { printf([FAILOVER] 主节点软件心跳超时 (%u ms %u ms)\n, elapsed, HEARTBEAT_TIMEOUT_MS); return true; } /* 条件 2: 硬件心跳异常 (GPIO 方波停止) * 此条件由 Watchdog MCU 独立检测并通过 UART 通知本节点 * 此处仅检查标志位 */ if (!node-peer_healthy node-my_role ROLE_STANDBY) { printf([FAILOVER] 主节点硬件心跳异常\n); return true; } return false; } /** * brief 执行角色切换: Standby → Active * param node 本节点管理器 * return 0成功, -1切换失败 * * note 切换步骤: * 1. 确认备节点状态已同步 (版本 权重一致) * 2. 加载最新的模型状态 * 3. 通知 Watchdog MCU 切换输出通道 * 4. 广播自身新角色给对端 (但对端可能已死亡) */ static int execute_failover(ha_node_t *node) { printf([FAILOVER] 开始执行角色切换: Standby → Active\n); /* Step 1: 加载最新同步的模型状态 */ pthread_mutex_lock(node-state_lock); /* 实际应用中: 从磁盘加载对端同步过来的最新权重和阈值 */ /* 这里假设状态已在 recv_heartbeat 中自动更新 */ if (node-my_state.model_updated_at 0U) { pthread_mutex_unlock(node-state_lock); fprintf(stderr, [ERROR] 备节点模型状态未初始化, 无法切换\n); return -1; } pthread_mutex_unlock(node-state_lock); /* Step 2: 通知 Watchdog MCU 切换输出通道 */ /* 通过 GPIO 输出高电平: STM32G0 检测到后切换模拟开关 */ /* 假设 GPIO 编号为 17WiringPi 编号*/ /* digitalWrite(17, HIGH); — 实际实现依赖具体 GPIO 库 */ /* Step 3: 更新本节点角色 */ node-my_role ROLE_ACTIVE; printf([FAILOVER] 角色切换完成, 本节点已成为 Active\n); return 0; } /* 主循环 */ /** * brief 双机热备管理器主循环 * param node 节点管理器 * param fd TCP socket (既发送也接收) * * note 调用周期: 100ms (由定时器驱动) * 此函数在 RT 线程中执行 (SCHED_FIFO, priority 50) */ void ha_manager_tick(ha_node_t *node, int fd) { if (node NULL) { return; } switch (node-my_role) { case ROLE_ACTIVE: /* Active 角色: ① 发送心跳 ② 同步状态 ③ 输出 GPIO 方波 */ send_heartbeat(node, fd); /* 每 5 个周期同步一次状态 (500ms) */ static int sync_counter 0; if (sync_counter 5) { sync_counter 0; sync_model_state(node, fd); } /* GPIO 方波翻转: 10Hz 硬件心跳 */ /* digitalWrite(gpio_hb_out, !digitalRead(gpio_hb_out)); */ break; case ROLE_STANDBY: /* Standby 角色: ① 接收并验证心跳 ② 接收状态同步 ③ 评估切换 */ if (recv_heartbeat(node, fd) ! 0) { /* 心跳接收失败, 对端可能宕机 */ if (should_failover(node)) { execute_failover(node); } } break; default: /* ROLE_UNKNOWN: 初始状态, 等待角色协商 */ break; } }四、边界分析1. 脑裂 (Split-Brain) 风险最危险的故障场景是网络分区而非节点宕机——主备之间 TCP 心跳断开但各自独立判定对端死亡双双进入 Active 角色。双主导致两套推理结果可能冲突同时输出到 PLC。解决方案引入独立的 Watchdog MCU 作为仲裁者——只有 Watchdog MCU 通过硬件信号选择器如高速模拟开关 TS5A3166连接输出通道其切换依据是双路 GPIO 硬件心跳和 UART 软件心跳的综合判决。Watchdog MCU 本身是单点但其复杂度极低纯硬实时、无操作系统可靠性远高于 Linux 工控机。2. 切换窗口内的输出毛刺从检测到 Active 故障到 Watchdog MCU 完成通道切换最快需要两个心跳周期检测窗口 300ms MCU 决策 10ms。这段时间内PLC 端可能收到悬空信号。解决方案在第一路输入失去心跳的 100ms 内让 Standby 节点提前开始并行推理输入数据通过交换机分光或软件多播到达双机这样 Standby 在切换瞬间已经有最近一次推理结果可用输出信号可无缝衔接。3. 模型版本同步的原子性当 Active 节点完成增量微调并更新了模型权重文件如从 v3.2 → v3.3必须在状态同步中携带新的权重文件或权重差分Delta。但同步过程可能在中途被打断如 Active 在同步中途宕机导致 Standby 持有一个不完整的 v3.3 权重文件。解决方案是文件校验和双阶段提交① 同步增量变更文件到 Standby 的临时目录② 同步完成后发送 commit 确认消息③ Standby 收到 commit 后将临时文件原子 mv 到目标路径。4. 频繁切换的抑制如果硬件心跳 GPIO 因接触不良产生间歇性毛刺频繁通断可能导致 Watchdog MCU 反复触发切换。应在 MCU 固件中加入滞回逻辑——连续 N 次如 3 次未检测到心跳方波才触发切换切换后至少保持 2 秒稳定期不允许回切。五、总结工业 AI 推理的高可用方案最核心的设计原则是故障检测独立于被检测节点——独立的硬件 Watchdog 独立的物理链路TCP 软件心跳 GPIO 硬件心跳构成多层故障检测体系。模型状态同步的难点不在技术本身TCP JSON 序列化足够简单有效而在如何确保同步的原子性和切换窗口内的输出连续性。双机热备方案在成本上是两台工控机 一块 Watchdog MCU对于停产损失远大于硬件成本的产线场景这几乎是强制性的架构选择。

相关新闻

【JAVA毕设源码分享】基于springboot篮球管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot篮球管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/10/17 15:05:20 阅读更多 →
工业视觉检测边缘推理方案全记录:从光源选型到缺陷分类模型部署的完整工序分析

工业视觉检测边缘推理方案全记录:从光源选型到缺陷分类模型部署的完整工序分析

工业视觉检测边缘推理方案全记录:从光源选型到缺陷分类模型部署的完整工序分析 一、引言 在 3C 电子制造产线中,一块 PCB 板从 SMT 贴片到成品出厂,通常要经过 20 道以上的视觉检测工序。传统方式依赖工控机 独立显卡的 PC-based 视觉方案&a…

2026/10/18 10:18:52 阅读更多 →
【JAVA毕设源码分享】基于springboot冷链运输生鲜销售系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot冷链运输生鲜销售系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/10/18 5:23:05 阅读更多 →

最新新闻

Flutter工程规范实战:用Claude Code和Codex约束AI写代码

Flutter工程规范实战:用Claude Code和Codex约束AI写代码

最近我在几个 Flutter 项目里试了用 Claude Code 和 Codex 来写代码,效果确实惊艳,但翻车概率也一点都不低。最典型的情况是:AI 能独立完成一个小功能,却会在你根本没注意到的地方破坏既有约定——混用状态管理方案、跳过异常处理…

2026/10/19 3:17:05 阅读更多 →
YOLOv8基建裂缝检测系统:轻量高鲁棒的工程落地实践

YOLOv8基建裂缝检测系统:轻量高鲁棒的工程落地实践

1. 项目概述:为什么一个“基建裂缝检测系统”值得用YOLOv8重做一遍你可能已经见过不少用YOLO系列做裂缝识别的Demo——拍张照片,框出几条灰白细线,再打个“裂缝:0.87”的标签,就叫“智能巡检”。但真把它拉进某高铁桥墩…

2026/10/19 3:17:05 阅读更多 →
基于Python与深度学习的植物病虫害识别系统设计与实现

基于Python与深度学习的植物病虫害识别系统设计与实现

1. 项目整体设计与技术选型植物病虫害识别这几年一直是农业信息化里的热门方向,真正落地做过的朋友都知道,难点不在模型本身,而是“识别”这个动作如何被最终用户方便地使用。我这次做的这套基于Python的植物病虫害识别系统,目标很…

2026/10/19 3:17:05 阅读更多 →
基于Python的植物病虫害识别系统实战:从模型训练到前后端部署

基于Python的植物病虫害识别系统实战:从模型训练到前后端部署

1. 项目价值与整体定位看到“基于Python的植物病虫害识别系统”这个标题时,我第一反应是这应该是一个典型的“算法工程”相结合的综合型项目。它既要解决“能不能识别出来”的算法问题,又要解决“别人怎么用”的工程落地问题。前后端源码意味着它不仅仅是…

2026/10/19 3:17:05 阅读更多 →
二进制部署Kubernetes高可用:从单master到双master+双LB实践

二进制部署Kubernetes高可用:从单master到双master+双LB实践

做二进制方式部署 Kubernetes 的人,多少带点“倔”——不想被 kubeadm 那堆自动生成的证书和静态 Pod 挡住视线,就想亲手把 etcd、apiserver、controller-manager、scheduler 一个个进程拉起来,看它们到底是怎么协作的。我前阵子刚把一套“单…

2026/10/19 3:17:05 阅读更多 →
Metadef实践指南:CANN自定义算子开发的基石

Metadef实践指南:CANN自定义算子开发的基石

Metadef 这个名字在自定义算子开发圈子里,说实话一开始很容易被忽略。很多人刚接触 CANN 算子开发时,目光全放在 kernel 怎么实现、tiling 怎么切分上,等真正动手写第一个算子才发现,最卡脖子的不是计算逻辑,而是“这个…

2026/10/19 3:16:05 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →