CPU负责控制与I/O
TIFF/ARW解析、metadata读取、物理offset合并、12线程pread、GPU批次构建,以及最终索引和顺序pwrite。这些工作分支多、系统调用密集,更适合CPU。
技术硬件实验 · 2026-08-26
不是让CPU和GPU重复做同一件事,而是让它们各自负责擅长的阶段:CPU解析ARW并并行读取,Metal批量完成无损SOF3解码与精确ROI裁切,CPU再把Bayer16顺序写入内部SSD。三个阶段通过有界队列并行推进;整个裁切与暂存阶段不缩放、不去马赛克、不改像素。
最终持久化层是无损Bayer16,而不是RGB8、JPEG或模型张量。它保留ARW有效14-bit样本,把模型相关的去马赛克、色彩、归一化与resize推迟到YOLO Pose、YOLO CLS和SAM各自的输入预处理。
TIFF/ARW解析、metadata读取、物理offset合并、12线程pread、GPU批次构建,以及最终索引和顺序pwrite。这些工作分支多、系统调用密集,更适合CPU。
Metal以“一个线程解一个独立tile”的方式并行SOF3 Huffman、predictor-1重建、四分量到Bayer16重排与精确ROI裁切。大批量时比10核自写CPU解码高5.27×。
所有数据无法常驻内存,因此CPU writer把结果写成大分片并建立索引。模型消费者读取Bayer16后再做各自预处理;不创建大量小文件,也不对临时数据做zlib-6二次压缩。
该阶段只把传感器样本从ARW的SOF3 tile恢复为精确ROI内的uint16 little-endian单平面Bayer;有效值仍是0–16383的14-bit数据。没有黑电平扣除、白平衡、去马赛克、tone、颜色空间转换、量化、归一化或resize。
1000张工作负载被分成有界批次。队列深度为1:读取器只允许领先GPU一个批次,writer只落后一个批次,既能重叠执行,又不会让统一内存无限增长。
示意图用于说明依赖关系,不按绝对时间比例绘制。GPU处理N时,读取器准备N+1,writer写N−1;有界队列形成背压。
| 阶段 | 运行位置 | 1000张累计服务时间 | 等效速率 | 资源峰值 / 说明 |
|---|---|---|---|---|
| 读取与组批 | CPU,12线程 | 956.95ms | 约3.03GB/s输入 | 本地F_NOCACHE;含metadata、offset合并和批次构建 |
| SOF3 + ROI | Apple M5 GPU | 788.80ms墙钟;572.31ms设备时间 | 约11.22k tiles/s墙钟 | Metal allocation峰值4.68GB |
| Bayer16分片写 | CPU单writer + 内部SSD | 365.94ms + fsync 1.46ms | 约8.08GB/s输出 | 16MiB顺序pwrite;真实写2.958GB |
| 三段若串行 | — | 2.113s | 473图/s | 各段服务时间直接相加 |
| 三级流水线 | CPU + GPU + SSD重叠 | 1.491s | 670.8图/s;5937.6 tiles/s | 隐藏622.5ms,即29.46% |
| 最大RSS | 5.44GB |
|---|---|
| 峰值footprint | 8.01GB / 32GB统一内存 |
| 峰值Metal allocation | 4.68GB |
| swap | 受控运行前后无变化 |
| memory throttling | 0 pages throttled |
| 输出总字节 | 与精确计算一致 |
|---|---|
| 临时shard字节 | 与输出一致 |
| 抽样ROI | 逐样本值一致 |
| GPU错误tile | 0 |
| 测试临时文件 | 验证后已清理 |
在本地完整实测的3072批次上,累计最长的是CPU读取/组批(956.95ms),其次是GPU(788.80ms),writer明显更短;三段可较好重叠。换回CFexpress后,实测卡读取单独就要2.064秒/1000张,成为明确的物理瓶颈,GPU和SSD写入可隐藏在卡读取之后。
报告只把上游框当作既定几何输入,不评价前级检测正确性。1000张样本用于覆盖真实tile分布与长尾;裁切阶段不生成可观看的图像,而是恢复传感器CFA样本供后续神经网络预处理。
| 总照片 | 1000张 |
|---|---|
| 有框照片 | 594张 |
| ROI | 707个 |
| 唯一tile | 8851个 |
| 合并后读取段 | 2230段 |
| 压缩输入 | 2.900GB |
| Bayer16 ROI输出 | 2.958GB |
ARW是相机RAW容器;本批A7R6文件的传感器payload使用JPEG家族里的SOF3 lossless sequential编码。它与日常8-bit有损JPEG不是同一种用途:Huffman解码和predictor重建能精确恢复原14-bit样本。
“ARW一定无损”也不能作为普遍结论;本报告的无损性来自对这批文件编码模式与逐值输出的验证。
source id、原RAW尺寸、active area、原始bbox、ROI宽高、行步长、shard offset/length。
14-bit有效精度、Bayer pattern、裁切起点奇偶校正、little-endian uint16、black/white level。
白平衡增益、相机矩阵、色彩相关tag。保存但不在裁切阶段应用。
orientation、原文件标识、版本与checksum,使后续预处理可复现并能回到ARW。
这里就是上游框映射到RAW坐标后的矩形裁切范围。ROI裁切只减少后续要保存和处理的区域;它不代表resize,也不意味着可以改变画面内容。
SOF3单tile的变长码和predictor具有顺序依赖;GPU不能把一个tile任意拆散。但不同tile互相独立,足够大的批次能用数千线程并行多个tile。小批次下GPU启动与低占用反而输给CPU。
| 解码器 / 工况 | 批量 | 中位时间 | 吞吐 | 判断 |
|---|---|---|---|---|
| libjpeg-turbo + 重排,CPU 10线程 | 256真实tiles | 43.60ms | 5871 tiles/s | 成熟兼容回退路径 |
| 自写SOF3融合解码,CPU 10线程 | 256真实tiles | 38.35ms | 6676 tiles/s | 比libjpeg路径快12.05% |
| Metal融合解码,小批量 | 256真实tiles | 118.36ms墙钟 | 2163 tiles/s | GPU未吃满,不应逐图提交 |
| Metal融合解码,真实不同输入峰值 | 6144 tiles | 174.76ms设备时间 | 35,157 tiles/s | 约为CPU 10线程的5.27× |
| Metal缓存复用上界 | 8192 tiles | 191.08ms设备时间 | 42,872 tiles/s | 只表示计算/占用上界,不当作真实I/O结果 |
输入来自28个不同ARW、共7840个唯一tile和2.458GB压缩字节;超过7840后仅循环剩余部分。
6144是“纯GPU算子”的吞吐峰值,不是“整机端到端”的最佳点。完整流水线在3072达到1.491秒,而6144退化到2.961秒。
超大批次需要约7.30GB Metal allocation,统一内存缓冲的分配、清零与搬运增加;总批次数从3降到2,也减少了流水线稳态重叠,首批填充和末批排空占比更高。
因此算子峰值与流水线峰值必须分开调参。
| tiles/批 | 批次数 | 端到端墙钟 | 中位读/组批 | 中位GPU墙钟 | 中位写盘 | 峰值Metal |
|---|---|---|---|---|---|---|
| 768 | 12 | 2.535s | 85.1ms | 173.1ms | 42.5ms | 1.25GB |
| 1024 | 9 | 2.218s | 109.6ms | 183.1ms | 43.4ms | 1.65GB |
| 1536 | 6 | 1.709s | 161.6ms | 200.7ms | 69.4ms | 2.41GB |
| 2048 | 5 | 1.678s | 212.6ms | 222.5ms | 93.4ms | 3.15GB |
| 2560 | 4 | 1.536s | 284.7ms | 237.8ms | 92.7ms | 3.95GB |
| 3072 | 3 | 1.491s | 335.4ms | 264.7ms | 112.7ms | 4.68GB |
| 3584 | 3 | 1.550s | 395.3ms | 262.4ms | 139.5ms | 4.26GB |
| 4096 | 3 | 1.709s | 456.6ms | 301.5ms | 207.6ms | 6.17GB |
| 6144 | 2 | 2.961s | 793.6ms | 798.7ms | 238.6ms | 7.30GB |
生产路径优先把大批量交给Metal;自写CPU解码可处理尾批、小批、GPU不可用或不支持的SOF3变体,libjpeg-turbo则作为兼容回退。所谓“专用CPU版本”是针对ARM64通用CPU核优化的软件,并不是调用了CPU内部的JPEG ASIC。
ARW传感器样本只有14个有效bit;Bayer16不会凭空增加信息,只是为每个样本分配一个自然对齐的16-bit槽位。它删除了14-bit位打包/解包步骤,便于GPU写出、CPU顺序落盘和模型loader读取。
| 格式 | 1000张未压缩输出 | 旧CPU端到端中位 | 处理要求 | 结论 |
|---|---|---|---|---|
| 紧凑14-bit位流 | 2.589GB | 2.129s | 逐行跨字节位打包;消费者还要解包 | 更小,但多一道计算和不规则写入 |
| Bayer16 uint16 LE | 2.958GB | 1.832s | 有效14-bit值直接写入16-bit槽位 | 旧CPU路径快13.97%;本方案采用 |
16-bit比紧凑14-bit大14.28%。表中旧CPU时间用于比较容器处理成本;新的CPU–GPU–SSD三级流水线为另一套实现,其1000张完整墙钟是1.491秒,不能直接把两张表相减。
每个0–16383样本原值不变;CFA pattern、black/white level、WB、相机矩阵、active area和orientation保存在索引。后续可以采用不同中性化方案。
zlib-6是DEFLATE压缩的中等压缩级别。旧测试把2.589GB压到2.382GB,只省7.96%,但墙钟从2.13秒增到13.60秒,约6.4×更慢。
内部SSD写512MB–2GB shard,索引记录每条ROI的offset和length;writer以16MiB块顺序pwrite。这样减少文件系统元数据和随机写。
2.958GB只是1000张样本;全量与后续多个消费者无法全部驻留统一内存。方案不尝试把全批放下,而是让每个GPU批次在writer确认写入后立即释放,内存上限由批次和深度为1的有界队列决定。下游按shard流式读取,消费完成并有checkpoint后再回收。
ARW裁切的消费者是YOLO Pose、YOLO CLS与SAM。它们的输入几何还没有完全定下来,因此裁切层绝不能预先缩成固定宽高,也不能统一做一套RGB或归一化。
| 消费者 | 用途 | 从Bayer16读取后自行完成 | 当前边界 |
|---|---|---|---|
| YOLO Pose | 鸟类特征点标定 | 黑电平、WB、去马赛克、色彩/灰度策略、归一化、letterbox/resize、张量布局 | 输入目标几何待实验确定 |
| YOLO CLS | 鸟类清晰度分类 | 可采用与Pose不同的中性化、色彩、锐度保留与crop策略 | 输入目标几何待实验确定 |
| SAM | 分割与后续掩码处理 | 自己的色彩预处理、归一化、resize/letterbox与prompt坐标变换 | 不在裁切阶段固定到1024 |
黑电平、WB、去马赛克、矩阵、tone、resize、RGB/灰度生成与张量布局将来可以按消费者融合成Metal kernel,以减少中间张量和内存往返;本轮只记录这个方向,不把未实现的性能计入结果。
模型loader若以后做crop扩边、resize或letterbox,必须从原始ROI和CFA奇偶开始记录完整变换,使Pose关键点、SAM prompt和输出坐标能精确映射回RAW。
不再保存RGB8,不再一律宽高减半,不再预设YOLO detect 1632×1088或SAM 1024,也不在裁切阶段做去马赛克、色彩转换、8-bit量化或归一化。这些操作都属于具体模型的输入预处理。
CFexpress卡已经推出,不再重复插卡。下面保留当天已完成的只读测量;卡端端到端数字是把这些测量与新的本地三级流水线组合得到的估算,明确不冒充重新实测。
| 已冻结测量 | 工况 | 三次结果 / 中位 | 吞吐 | 判断 |
|---|---|---|---|---|
| 真实tile只读 | 1000张,2.900GB,12线程,F_NOCACHE | 2.041 / 2.064 / 2.182s | 中位1.405GB/s | 12线程最佳;16线程无收益 |
| 旧卡读 + CPU SOF3 + Bayer16 ROI | 1000张,16 workers | 2.235 / 2.312 / 2.485s | 中位1.254GB/s输入 | 说明CPU计算仅在卡读上增加约0.25s量级 |
| 内部SSD顺序写 | 16GiB,F_NOCACHE + fsync | 2.833s | 6.064GB/s | 明显快于卡读 |
卡填满这两种批次约需179–239ms;本地测得GPU墙钟约173–183ms,writer约42–43ms。三段时间接近时可以让GPU和SSD写入藏在下一批卡读取后面,同时减少首批填充与末批排空。
组合估算
估算包含最后一批排空,比2.064秒纯卡读只多约0.22秒。它结合了此前真实卡读和今天本地阶段数据;由于卡已推出,这不是新的卡端完整实测。
卡端工作时,1.405GB/s的真实读取已经决定主节拍;GPU解码和内部SSD写入能通过三级流水线被大部分隐藏。接下来的工程优先级应是正确性、容错、队列背压、可恢复shard与metadata,而不是继续压榨几十毫秒。只有改用内部SSD作为长期输入、换更快读卡链路,或模型预处理成为新瓶颈时,才值得重新扫参。
参数分“本地输入”和“真实卡输入”两套。最重要的是用字节容量做背压,而不是只按照片数量;长尾大框会让单张照片的tile与输出体积相差数十倍。
| 批次 | 最多3072 tiles |
|---|---|
| Stage 1 | 12线程读/组批 |
| Stage 2 | 单Metal批;一个线程/独立tile |
| Stage 3 | 单writer,16MiB pwrite |
| 队列 | 两条depth-1有界队列 |
| 峰值footprint | 约8.01GB |
| 批次 | 768–1024 tiles |
|---|---|
| 卡读取 | 12线程;物理offset合并 |
| GPU | 大批Metal,不逐图提交 |
| writer | 内部SSD,不写回CFexpress |
| shard | 512MB–2GB + 小索引 |
| 二次压缩 | 默认关闭 |
索引记录source id、ARW checksum、ROI、批次与shard边界;崩溃后从最后确认的shard继续,不要求全部内存状态仍在。
支持的标准SOF3批量走GPU;尾批或GPU不可用走CPU;遇到未覆盖布局交给libjpeg兼容路径,并记录而不是静默改像素。
shard顺序写与fsync完成后原子提交索引;消费者只读取已提交范围,避免进程退出后拿到半条ROI。
分别统计read/build、GPU wall/device、writer、队列等待、RSS、footprint、Metal allocation、swap与错误tile,避免只看整体吞吐误判瓶颈。
本报告把完整实测、算子实测、真实卡测量和组合估算分开。没有把“GPU纯算子峰值”写成“整机吞吐”,也没有把卡已推出后的模型推算写成重新实测。
本地1000张的读取/组批、Metal SOF3+Bayer16 ROI、真实内部SSD分片写与fsync;9档批量扫描;输出大小、shard大小、抽样ROI、错误tile、RSS、footprint和swap检查。
自写CPU与libjpeg-turbo对比;Metal从256到9216 tiles的占用扫描;真实CFexpress 1000张tile只读三次;内部SSD 16GiB顺序写。
卡端768–1024 tiles批次及2.279–2.290秒端到端时间。估算依据是已冻结卡读和本地各阶段时间,需在下次卡可用时用同一程序复测。
上游框语义正确性;YOLO Pose、YOLO CLS、SAM最终输入几何和精度;模型预处理Metal kernel;推理/训练吞吐;读卡器温度与电源状态。
P50是中位数:一半样本低于它,一半高于它。P99是第99百分位:约99%的样本不超过该值,只剩最慢或最大的约1%更高。百分位不是平均数,也不是最大值;它用于同时描述典型情况和长尾。
本地JSON与源码是性能和正确性证据;外部链接只用于解释芯片、Metal共享内存与无损JPEG接口能力。
arw-crop-staged-cpu-gpu-pipeline-m5-2026-08-26.jsonsof3-gpu-saturation-m5-2026-08-26.jsonsof3-decoder-cpu-metal-m5-2026-08-26.jsonsof3-custom-end-to-end-bayer16-m5-2026-08-26.jsoncfa-card-rgb-spool-analysis-2026-08-26.json;只采用其中读取/写入实测,旧RGB决策已废弃。arw_crop_staged_pipeline_bench.mmsof3_decode_bench.mm、sof3_gpu_saturation_bench.mm、native_pipeline_bench.cpp