# 📦 WP-087b — `perf(ptl): batched mask-loss gather, one sync not 64`

> Research log · [Phase 7 — Instance segmentation, v0.2.0](https://borda.github.io/lucid-YOLO/research/phase-07-instance-segmentation/)
>
> 🔗 Depends on [WP-087](https://borda.github.io/lucid-YOLO/work_packages/wp-087/) · Needed by none
>
> ⬅️ [WP-087](https://borda.github.io/lucid-YOLO/work_packages/wp-087/) · [Roadmap](https://borda.github.io/lucid-YOLO/ROADMAP/) · [WP-087c](https://borda.github.io/lucid-YOLO/work_packages/wp-087c/) ➡️


## WP-087b — batched mask-loss gather

<a id="wp-087b"></a>

Wiring the segmentation path made it trainable and slow: **1.59 s a step against detection's 0.39** on an RTX PRO 6000, GPU idle, main process on CPU.

Cause was not volume — ~2,340 masks, 0.24 GB. `_branch_mask_loss` selected each image's positives with a boolean mask index, whose output shape is data-dependent, so the device had to report the surviving count back to the host before the next op could be built. Inside a loop over the batch, run per branch, that is **2×B forced syncs a step — 64 at batch 32**.

Padding to the batch's largest positive count and selecting once costs two syncs a branch at any batch size. Positives keep ascending anchor order within an image and image order across the batch, so summation order — and therefore the value — is the loop's own.

Padding is where a perf change turns numerical, and it did: padded rows carry `gt_index = -1`, the unassigned sentinel, which is a legal negative index in Python and out of bounds in a gather. The existing gradient test caught it before any of the perf work ran.

