NVLink跨卡原子操作与单调计数器:实现多GPU无锁同步的工程实践
1. 引子多卡同步遇到的问题锁为什么越来越不对味先讲一个具体的场景。某集群项目里有 8 张卡组成一个生产-消费流水线卡 0 做数据预取和预处理卡 1 到卡 7 负责推理计算。早期实现用的是最朴素的做法生产者写入一块共享内存区然后加一个全局标志位消费者轮询标志位。因为数据是按帧进来的我干脆把标志位做成了版本号——每生产一帧数据版本号加一消费者用循环不断读取这个版本号发现数字变了就去取新数据。这个方案在单卡甚至双卡下都能转但一旦把数据放到两个物理 GPU 之间问题就出现了。轮询端隔着 NVLink 不断发起读取PCIe 时代还勉强能忍受的同步开销在 NVLink 上被放大得非常明显链路被无意义的读请求占满真实的 16 字节有效载荷要排队等空位整条生产线的吞吐直接腰斩。我真正需要的是一个比锁标志位更扎实的东西生产者发布数据消费者只关心我能不能安全地读这批数据发布动作本身要跨卡可见而且不能让一群 CUDA 核在 NVLink 链路上互相争抢。最后做的方案就是题目里说的硬件级原子发布与单调计数器底层依赖 NVLink 上可用的跨卡原子操作实现真正无锁的同步。这篇文章就是那段踩坑经历的完整复盘。会讲到原子操作在 NVLink 上到底怎么工作、为什么单调计数器比普通版本号更适合做发布序列、代理原子和本地原子在行为上有哪些坑以及我在实测中遇到的死锁和一致性边界。适合正在做多卡训练、多 GPU 推理服务或者准备用 NVLink 做设备间通信的朋友参考。2. NVLink 跨卡原子先搞清楚这一代硬件到底给你什么2.1 跨卡原子不是所有 GPU 都默认支持的东西很多人第一反应是既然 NVLink 能把两块 GPU 的内存映射成 peer 访问那我在卡 A 上直接atomicAdd到卡 B 的内存在逻辑上不就好了硬件层面确实可以但前提条件非常多不是每次都能命中那条最高效的路径。首先要确认两件事通过cuDeviceCanAccessPeer查询目标设备是否允许 peer 映射。通过cuDeviceGetAttribute查询设备属性中与原子操作相关的能力位。我踩过最直接的一个坑某集群中的两块卡物理上是相邻槽位NVLink 拓扑也显示有连接程序cudaDeviceEnablePeerAccess也没报错但跨卡原子操作的实际落点还是在主机端中转性能上不去。查到最后发现是驱动版本没有开启对等原子peer fabric atomics的优化路径硬件支持但驱动层的协议栈还在走保守路线。所以做任何跨卡原子方案之前第一件事是写一个小程序跑一个简单的跨卡atomicAdd基准测试。不是为了跑分而是验证你当前驱动的实际行为。如果两卡之间的原子加延迟超过了几百纳秒或者吞吐随并发线程数急剧下降那基本可以判断它没有走硬件原子路径后面所有设计都得换思路。2.2 代理原子与本地原子看起来一样行为差距很大NVLink 上的跨卡原子操作官方文档通常叫代理原子proxy atomics。它的工作方式可以类比成一个跨卡中间人你向对端发起一个原子请求这个请求交给 NVLink 链路上的协议处理在远端内存上完成原子运算后再带上结果返回。这个中间过程带来的影响有两个。第一原子操作的往返延迟比本地原子高得多。本地 atomicAdd 大致是一次内存往返几百纳秒级别跨卡代理原子在 NVLink 上虽然比 PCIe 好很多但依然是两卡之间的一次请求-响应往返延迟通常在微秒级。如果你让一千个线程同时去做跨卡原子加那些线程都会堵在同一个链路出入口形成微观层面的热点。第二代理原子有并发上限和重试语义。现代 GPU 的原子单元支持一定规模的并发操作但跨卡方向的并发窗口比本地小得多。当同一时刻请求过多时硬件会要求线程重试。这和我们 CPU 上熟悉的 CAS 自旋重试是两码事CAS 重试是软件循环而 NVLink 代理原子重试是硬件层面的指令重发放线程本身可能并没有做明显后退。所以跨卡原子设计的第一原则是把原子操作的数量压到尽可能少、尽可能集中。理想情况是整个同步动作只触发一次原子操作并且由一个线程或一个线程块来承担而不是让整个网格的线程都去砸同一个地址。2.3 单调计数器为什么是最合适的原子对象有了上面的限制再看发布消费这个模型。同步的本质是生产者完成数据写入然后把一个已经写完的标记让消费者能读到。最简单也最经典的做法就是用 atomic-add 实现一个单调递增的计数器每次发布数据就对其加一。消费者则通过原子读取获得当前值与本地记录的上一次值做比较判断是否有新数据。为什么是单调计数器而不是一个简单的标志位标志位只有 0/1 两种状态生产者和消费者之间必须额外维护什么时候允许翻转的协议。如果生产者在消费者还没消费完就写完下一批数据标志位翻转会造成消费者误判。单调计数器天然解决这个问题每次发布对应一个唯一的序号消费者只要记住上次消费到的序号新序号出现就代表有新数据。不需要额外的忙状态也不需要清理标志位。另一个原因是硬件特性。跨卡原子加属于无条件的原子操作不需要读取-比较-写入的循环。只要数据地址对齐到 64 位边界驱动路径正确硬件就能在远端完成加一。相比之下CAS 型的跨卡原子操作需要携带比较值协议栈处理更复杂在某些驱动版本上甚至需要回退到软件模拟性能无法保证。所以只要能表达为单调递增序列的同步问题原子加就是最省事的解法。3. 一个可工作的跨卡无锁同步设计3.1 数据平面与控制平面分离设计这套原语时我给自己定了一条纪律不要让任何同步信息和数据混在同一个内存区域。数据平面负责真正的业务数据控制平面只放计数器、状态位这类同步元数据。这样做的原因很实际。数据平面上生产者写入数据可能涉及大块内存写入顺序可能被编译器或硬件缓存重新排序而控制平面上我们希望同步机制越简单越好只有计数器和一个可选的已完成位。分离之后数据内存用普通的全局内存分配即可控制平面则单独分配一段对齐到 64 字节的设备内存避免跨旁路缓存时出现伪共享问题。实际数据结构大概是这样// 控制平面单独分配按 64 字节对齐 struct PublishChannel { // 单调计数当前已发布的数据版本 unsigned long long publish_seq; // 生产者状态位用于标识“正在写数据中” unsigned long long writer_busy; // 可选的批大小记录 unsigned long long batch_size; // 预留字段保证总大小是 64 的倍数 unsigned long long reserved[3]; };需要特别说明这个结构体本身不能直接作为普通 C 结构体跨卡使用。设备内存中它是一段连续的字节序列所有访问都通过 volatile 指针或原子函数来完成。我在早期实现里吃了大亏直接把这个结构体用 memcpy 拷贝到另一个缓冲区然后在远端读取结果因为内存序和缓存一致性的问题读到的发布序号经常落后于实际值。后来改成全程用原子函数访问才彻底解决。3.2 生产者侧一写一发布不要搞复杂生产者在写入完整数据帧之后执行一次原子加一即可。重点在于这个加一操作和之前的数据写入之间必须有一个明确的发布屏障。CUDA 中实现这个屏障有两种方式。一是用__threadfence()它会保证当前线程之前写过的全局内存对所有设备可见语义上是 release 操作二是直接用带内存序参数的原子函数比如atomicAdd_system或原子释放操作。我首推后者因为原子函数本身就携带了内存序信息代码可读性和正确性都比先随手写数据 尾部 threadfence更清晰。__global__ void producer_kernel(float* data_buf, unsigned long long* publish_seq, int frame_id) { // 将业务数据写入 data_buf int idx blockIdx.x * blockDim.x threadIdx.x; data_buf[idx] compute_value(frame_id, idx); // 只让 block 0 承担发布动作避免多个 block 同时原子加 __syncthreads(); if (blockIdx.x 0 threadIdx.x 0) { // 释放内存序保证之前写入的所有数据在双卡视角下均可见 atomicAdd(publish_seq, 1ULL); // 如果使用旧架构可再加一个 __threadfence() 作为保险 __threadfence(); } }上面这个示例中数据处理本身可能并行跑了几百个线程块但发布计数器只由一个线程去加。为什么因为跨卡原子是稀缺资源。如果让所有 block 都在结束时去加同一地址几千个线程的原子请求会在链路上排队发布延迟反而变高还白白消耗了链路带宽。用一个代表线程做发布开销从每块一次变成每帧一次同步成本几乎可以忽略。3.3 消费者侧先读序号再安全地读取数据消费者侧的读取逻辑可以分为三步原子读取publish_seq当前值当读取值大于本地记录的上一次消费序号时判断已有新数据在读取业务数据前执行一次 acquire 操作使本地线程对生产者写入的数据完全可见。第三步是关键。原子读取只能保证你拿到了当前发布到第几版这个序号但随后读取的数据可能还在生产者的缓存里没有完全冲刷回全局内存或对端可见。正确的做法是用 acquire 语义的原子读或先做原子读再配合__threadfence()拉一个内存屏障。我最终实现时选择的是简单方案消费者只由一个统一的控制线程块读取序号然后通过片内共享内存广播给其他消费者。这样可以把跨卡原子读的次数降到每帧一次同时后续数据读取全部发生在本地不需要再对全局内存做昂贵的跨卡同步。消费者侧的伪代码逻辑__global__ void consumer_kernel(float* data_buf, unsigned long long* publish_seq, unsigned long long* last_seen) { if (blockIdx.x 0 threadIdx.x 0) { unsigned long long cur atomicAdd((unsigned long long*)publish_seq, 0ULL); // 原子读 // acquire 语义读取者线程对发布前的写入可见 if (cur *last_seen) { __threadfence(); // 保证后续读取不被重新排序到读取序号之前 *last_seen cur; shared_seq cur; } } __syncthreads(); // 其他线程从 shared_seq 中获得最新版本随后正常读取数据区 if (shared_seq 0) { float value data_buf[shared_seq * 16 idx]; } }这里有一个很重要的细节发布序号和实际数据批次之间如何映射。如果数据缓冲区是环形复用那么消费者不能只依赖单调计数器判断有新数据还需要知道新数据具体落到了哪个槽位。我在项目里选择的是把每次发布对应一个固定的批次号数据槽位直接用发布序号对总槽数取模得到。这样即使消费者落后了几个版本也能通过序号快速定位到正确的槽位。3.4 把线程换成线程块实现真正的跨卡代理既然跨卡原子最好只由一个线程发起那生产者和消费者两侧都应当采用单个代表线程 其余线程并行工作的经典结构。很多 CUDA 初次接触者容易忽略的一点是atomicAdd虽然是原子的但如果让全部线程在循环里疯狂调用它等价于在海量并发下打一个瓶颈点其行为可能比锁还要差。我把这个思想固化到了后续所有同步原语中同步 数据并行 单点发布/单点消费。数据写入和数据读取可以大规模并行但同步元数据的读写动作必须刻意地维持在一个极低并发度上。这套思想同样适用于将来扩展到多卡流水线比如 PKG 格式的生产者消费者模型每个卡只维护自己的发布序号其他卡通过原子读获取整体结构非常干净。类似的思路在 CPU 多线程编程里很常见全局的无锁队列也会限制为少数线程操作 head/tail 指针。GPU 端的特殊性在于代表线程需要依赖线程块内的同步和共享内存来广播信息而不是依赖 CPU 上的线程同步原语。理解了这一点代码写起来就很顺。4. 为什么不用锁NVLink 上的锁到底贵在哪里4.1 锁的直观成本阻塞、轮询、上下文切换在 GPU 共享内存环境中用互斥锁做跨卡同步最常见的做法是原子加一个标记 自旋等待。看起来很简单生产者抢到标记就开始写数据写完释放标记。消费者抢不到标记就自旋。实际跑出来性能惨不忍睹。原因有两层。第一层是自旋本身就浪费了线程。消费者线程如果抢不到标记就开始忙等GPU 的 warp 调度器会让这些线程持续活跃占着执行单元不仅没有推进任何业务还挤占了生产者线程的调度机会。结果就是整个 GPU 陷入一种诡异的假忙状态负载看起来很高但吞吐几乎为零。第二层是自旋读操作本身也会污染 NVLink 链路。当消费者不断用普通的加载指令去读取远端地址的锁值时这些读请求即使没有修改任何数据也会被 NVLink 协议当作普通传输处理持续在链路上制造流量。如果同时有 32 个消费者线程在自旋同一个地址链路压力读数直接比没有锁时高一两个数量级。4.2 自旋锁跨卡的一个隐蔽问题不公平的竞争就算锁本身的实现是正确的跨卡环境下还有公平性问题。NVLink 是点对点链路不同消费者线程访问远端地址的物理路径不完全相同有的线程离目标内存近有的线程离得远。本地线程抢锁的成功率天然高于远端线程会形成一种歧视效应离发布者较近的卡总能抢到锁远端卡只能望锁兴叹甚至出现饥饿。虽然 GPU 的线程调度机制和 CPU 不完全一样但这个规律反映的现象是一致的锁依赖的谁先抢到与业务逻辑上的谁的发布该被消费没有任何关系纯粹是物理距离和链路状态的输出结果。无锁原子方案在这一点上完全不同。发布者永远可以发布消费者永远可以读到最新序号不存在抢不到就阻塞的失败路径。正好符合前文说的单调计数器模式计数只增不减消费者只要关心自己的序号有没有变化无需竞争访问权。4.3 实测对比锁方案和无锁方案的指标变化我在这套架构上跑过几组对比测试场景是两卡之间持续传递 64 字节的小数据包生产者和消费者各开 256 线程。数据如下方案压测时长吞吐量GB/sp99 延迟µs跨卡读请求数全局互斥锁 自旋30 秒2.81840极高无法稳定统计原子标志位 自旋30 秒4.9320高无锁单调计数器30 秒11.642低无锁单调计数器 双缓冲30 秒12.138低需要说明这组数据不是精确的基准测试而是我在项目初期的调优参考环境变量、卡间拓扑等因素会让绝对值在不同机器上有所浮动。但趋势非常明显锁方案在高并发下受限于自旋和链路抢占吞吐远低于无锁方案而单调计数器配合单点发布的方案把同步开销压到了消费端逻辑能接受的范围内。这就是为什么本文的核心方案是无锁而不是更快地加锁——锁的本质是强制互斥而这里我们需要的只是发布-订阅语义并没有真正不可并行的临界区。5. 踩坑记录一致性边界、死锁与重试风暴5.1 一致性边界别假设跨卡原子 立即可见第一次把测试扩展到 4 卡时我犯了一个相当典型的错误。生产者在卡 A 发布后立即在卡 B 上启动消费者内核消费者内核开始读取发布序号。结果发现消费者读到的序号经常为 0偶尔读到的序号小于生产者实际发布的数值。原因在于 GPU 内核的启动执行是有边界的。生产者内核发布序号这一动作并不会自动保证所有尚未启动的消费者内核在启动后的第一次读取中就能看到这个序号。内核之间不存在类似 CPU 上互斥锁提供的可见性保证。要保证后续内核能看到已发布的信息必须在生产者内核结束时执行一次全局同步比如使用cudaDeviceSynchronize或协作组网格同步或者在消费者内核入口处主动做一次跨卡内存屏障。也就是说原子操作只负责某一时刻的读-改-写一致性不负责不同内核之间的调度可见性。跨卡同步原语如果要被内核边界跨过就必须额外补一个全局同步或内存栅栏否则就会出现我明明发布了你下一段时间怎么还看不到的错位。我的处理方式很朴素在启动消费者之前所有生产者内核必须完成并提交同步消费者内核的第一件事就是通过一次线程块的原子读取把发布序号拉到本地共享内存后续读取都从共享内存走。这样就算内核边界存在可见性延迟也不会污染业务逻辑。5.2 发布序的放松不小心把 release 写窄了第二个坑出在内存序语义上。早期为了让发布代码显得简单我用了一次普通的atomicAdd没有显式指定 release 语义然后自作主张在发布动作后面加了一个__threadfence()。但threadfence的作用域在全局内存上它保证的是当前线程所有写入对全局内存可见而跨卡场景还涉及 peer 设备缓存和远端缓存两者的可见性语义不完全一致。实际现象消费者已看到发布序号增加但随后读取业务数据时读到的是旧数据或全零。这表明发布序号已经传过去了但数据本身还在更远的缓存层级没有跟着序号一起到达。修复方法很简单不要手动组合普通原子 threadfence而是直接用具备内存序的原子释放函数。在 CUDA 中比如atomicExch配合__threadfence()的组合或直接使用更底层的atomicCAS配合__threadfence_system()等。关键是让发布操作本身携带 release 语义保证数据写入先完成序号再发布这一顺序被跨卡系统完整接纳。如果你正在做自己的原语库建议把所有跨卡发布操作封装成一个独立的函数比如publish_release(ptr, value)内部统一调用atomicAdd_system(ptr, value); __threadfence_system();。这样哪怕后续换驱动的坑也只需要改这一处封装。5.3 死锁案例等待一个永远不会到达的信号这是所有无锁同步中我最想写的一段。设计消费者流程时我为了减少跨卡原子读次数让消费者线程块先读取发布序号如果发现没有新数据就自旋等待。然后我发现一个极隐蔽的问题当生产者和消费者内核同时运行并且消费者核心里有多个线程块在自旋时GPU 线程调度器可能让部分块长期占用硬件资源导致生产者侧的线程块迟迟得不到调度发布动作被无限推迟。这种情况下消费者线程在等待发布序号大于当前值而发布动作需要消费者所在 GPU 的调度器腾出执行单元才能进行。结果就是一个标准的软件死锁没有人主动释放资源但整个系统卡死。解法是不要用线程块级自旋等待跨卡信号。生产者和消费者应当设计成完全不互相等待的模式。生产者在自己的执行流中发布消费者在自己的执行流中读取后返回无新数据即可由上层调度比如 CPU 端的启动逻辑决定何时再次启动消费者内核。把自旋等待信号从 GPU 内核中彻底拿掉死锁就自然消失了。顺带一提之前在某实验室同学的调优讨论里对方使用了一种定时重试 最大等待次数的折中方案。这只能缓解死锁的概率治标不治本。核心原则还是别在设备端跨卡同步时做盲等。5.4 重试风暴当原子请求多得让 NVLink 阻塞跨卡原子操作虽然高效但它也不是免费的午餐。某个版本我尝试把所有消费者的本地序号更新全部改为跨卡atomicAdd替代之前的共享内存广播结果链路瞬间不可用整卡吞吐掉到之前的 30%。重试风暴的产生链路大概是这样的消费者线程太多 - 同时发出几十路跨卡原子加 - 硬件原子单元和 NVLink 协议并发窗口被打满 - 部分请求被硬件拒绝或超时 - 线程重试 - 重试请求继续叠加在链路上 - 链路进一步拥塞。解决方式总结下来就是三个词集中、降频、后退。跨卡原子操作只发生在单一发布线程或消费控制线程上每帧数据只进行一次同步操作如果要做频繁的消费者轮询就把它放到 CPU 侧去调度而不是让 GPU 线程高频读远端地址。我之前还测过一种方法给消费者线程在两次原子读取之间加空循环退避nop 延迟效果还可以但只适合低负载场景。真正高吞吐下退避会让生产队列积压吞吐上升不明显。最后我还是回到了CPU 管理同步节奏、GPU 只做一次性查询的模型稳定性和吞吐才同时达标。6. 这套方案跑出来的效果以及什么场景别硬用6.1 项目里的实际收益整套方案稳定运行后我在项目中的 8 卡环境中重新测了一轮端到端流水线性能。原先使用锁方案时同步节点约占整体耗时的 18%替换为无锁单调计数器方案后同步节点耗时占比降到 2.1%端到端吞吐提升约 3.6 倍。这个提升里有一部分来自消费者侧的重复读次数大幅下降另一部分来自链路不再被自旋读压满。整个实现一共只有几处关键代码一个 64 字节的PublishChannel结构体生产者侧一个代表线程的原子加消费者侧一个代表线程的原子读加__threadfence()外加 CPU 层的启动节奏控制。没有引入任何第三方库也不需要额外的工具链依赖纯 CUDA 就能跑通。6.2 适合这个方案的场景这个方案特别适合三种形态的负载起床晚睡好把多卡流水线同步变得更简单。第一种是生产者与消费者明确分离且数据量较大的流水线比如数据预处理卡向推理卡持续投递批量输入天然适合用发布序号来描述新的一批数据。第二种是多卡用于协同计算但真正频繁同步的只有少量控制信号比如参数同步、梯度聚合前的状态通知。单调计数器表达第几轮已完成非常自然。第三种是有严格延迟预算的推理服务。每次推理开始时消费者只做一次跨卡原子读操作次数固定延迟波动远小于锁方案。6.3 不推荐硬套的情况说到底这套原语并不是万能的同步银弹。如果你的同步逻辑里需要同时修改多个状态位并且这些状态位之间必须保持一致的读-改-写顺序那么单调计数器就表达不了全部语义。这时候还是得回到 CAS 循环、事务内存或者干脆用更保守的锁方案。同样是跨卡场景如果卡之间走的是 PCIe 而非 NVLink跨卡原子延迟会明显增加且大概率没有硬件代理原子路径。这种情况下我不建议用本文的代理原子方案因为链路延迟、驱动回退都不可控用 DMA 批量传输配合 CPU 发布位反而更可靠。另外如果数据规模极小比如每次只传递一个 32 位整数明显不值得为它搭一套发布-订阅基础设施。一个简单的原子交换加合理的 CPU 调度就够了。任何技术方案的选型本质都是根据具体负载特征来决定复杂度。7. 最后说说我在实际调优中的个人体会如果你打算在项目里复刻这套发布-消费原语我强烈建议从最小的原型开始做起只做一卡到另一卡的单向发布跑通后再扩展为多卡。最初我在四卡环境一下子铺开很多核函数结果出问题时根本分不清是哪个卡上的缓存没有刷新还是哪个内核启动顺序出了问题排错成本比写方案还高。另外一定要给自己留一个观测口。我的做法是在控制平面结构体里藏了一个计数器专门统计每个消费者成功读取到的发布总次数。通过这个数字可以看到是否存在消费者反复读到同一个旧序号的情况也可以快速判断链路是否被异常依赖。有时候问题不是同步原语本身而是某个上层调度逻辑让消费者启动了太多次反而把原子读变成了新的瓶颈。这套方案目前仍然在持续改进中。下一步我会试着把它包装成一个独立的轻量级原语库让生产发布和消费读取都变成黑盒调用进一步降低多卡对接时的编码错误空间。跨卡无锁同步听起来高深真正落地之后你才会发现它其实只不过是把发布和消费两个概念用最贴近硬件的方式表达了出来。

相关新闻

Kafka 生产环境分区热点倾斜治理:基于 Key 加盐与动态再均衡的高并发解法

Kafka 生产环境分区热点倾斜治理:基于 Key 加盐与动态再均衡的高并发解法

在每年双 11 的大促峰值期,许多负责消息中间件保障的架构师都会遇到一个极具迷惑性的“假象报警”: 打开 Kafka 全局监控大盘,集群整体的 CPU 利用率仅仅只有 35%,网络入网总带宽距离物理网卡上限还有一半以上的富余,看…

2026/10/14 1:23:07 阅读更多 →
算法岗实习面试全攻略:字节、PayPal、寒武纪等五家大厂面经与避坑指南

算法岗实习面试全攻略:字节、PayPal、寒武纪等五家大厂面经与避坑指南

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

2026/10/14 1:23:07 阅读更多 →
单端口与双端口RAM实现指南:Verilog代码、时序与综合推断

单端口与双端口RAM实现指南:Verilog代码、时序与综合推断

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

2026/10/14 1:23:07 阅读更多 →

最新新闻

信创招标要求写在哪一节_96万的燃气项目

信创招标要求写在哪一节_96万的燃气项目

10 月 12 日,焦作市公共资源交易中心公示了一个城市燃气安全监测服务项目的中标结果。金额不大——96 万元,中标方是中国移动通信集团河南有限公司焦作分公司,评审总得分 93.92。项目内容是对市区 150 处燃气领域相邻空间节点建设全覆盖、全天…

2026/10/14 2:02:18 阅读更多 →
Cadence Cerebrus 用户指南:强化学习驱动的数字后端 PPA 自动调优实战

Cadence Cerebrus 用户指南:强化学习驱动的数字后端 PPA 自动调优实战

简介:Cadence Cerebrus User Guide 是面向电子设计自动化工程师与技术人员的官方操作指南,聚焦 Cadence Cerebrus 工具在芯片设计流程优化中的实际应用。该指南对应产品版本 22.1x,最后更新于 2023 年 12 月,内容涵盖工具特定指令…

2026/10/14 2:02:18 阅读更多 →
astropy SAMP Hub 启动与停止完全指南:三种方式详解与底层原理

astropy SAMP Hub 启动与停止完全指南:三种方式详解与底层原理

科学计算科研数据分析 【免费下载链接】astropy Astronomy and astrophysics core library 项目地址: https://gitcode.com/gh_mirrors/as/astropy 点击查看 免费下载 导读 SAMP(Simple Application Messaging Protocol,简单应用消息协议&a…

2026/10/14 2:02:18 阅读更多 →
Knative Eventing 跨命名空间事件链接(Cross Namespace Event Links)特性全解析

Knative Eventing 跨命名空间事件链接(Cross Namespace Event Links)特性全解析

文档教程云原生 【免费下载链接】docs User documentation for Knative components. 项目地址: https://gitcode.com/gh_mirrors/docs1/docs 点击查看 免费下载 Knative Eventing 从 1.15 版本起引入了 cross-namespace-event-links 实验特性,允许 Trig…

2026/10/14 2:02:18 阅读更多 →
NPO光引擎替掉4.8万个光模块_买的是什么

NPO光引擎替掉4.8万个光模块_买的是什么

1. 引言:一次「不追求更快」的互联替换华为在 HUAWEI CONNECT 2026 上发布的 Ascend 960 SuperPoD,宣传语是「业界首个基于近封装光学(NPO)的超节点」。它给了一组很好算的数字:约 5500 个 Hi-ONE 光引擎,替…

2026/10/14 2:02:18 阅读更多 →
基于Vue3+SpringBoot的前后端分离电商系统设计与实现:以雪地装备销售平台为例

基于Vue3+SpringBoot的前后端分离电商系统设计与实现:以雪地装备销售平台为例

简介:一套基于Vue.js、SpringBoot与MySQL构建的雪地装备销售系统成品源码,面向计算机专业毕业设计或课程设计场景,可直接作为高评分项目提交。系统覆盖商品展示、搜索、购买、订单处理、支付接口及用户注册登录等完整电商流程,采用…

2026/10/14 2:01:18 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →