5d16368817
The DSA lightning indexer selects the top-index_topk (2048) context keys to attend to by finding the threshold = keep-th largest attention score. It previously full-qsorted all nk scores per layer per token (O(nk log nk)) just to read one pivot value, then scanned the original array in position order to build the kept set. Replace the qsort with partial_select_desc (Hoare quickselect, median-of-three, descending): O(nk) average to partition the keep largest into a[0..k), then the threshold is min of that block. The two position-order scans (>thr then ==thr) are UNCHANGED, so the kept-position set is bit-identical -- a stronger contract than #335's sampling heap (which was multiset-only because the heap was unstable and changed accumulation order). The quickselect pivot IS by definition the keep-th largest, so the new threshold equals old tmp[keep-1] exactly. Measured (bench_dsa_select, keep=2048, median of 2000 reps): nk=2049: 119us -> 5.7us (21x) nk=8192: 626us -> 43us (15x) nk=32768: 2.8ms -> 0.28ms (10x) nk=65536: 6.6ms -> 0.47ms (14x) The gap widens with context (linear vs n-log-n). DSA only fires past index_topk, so this is precisely the long-conversation regime where decode latency matters. Adds test_dsa_select (in TEST_BINS): 129 cases asserting element-wise identical kept-set vs an independent qsort reference across shapes (random, peaked, sorted, reverse-sorted, tie-plateau, all-equal) and edges (keep==1, keep==nk). Also directly checks the partition invariant. Adds bench_dsa_select (on-demand, NOT a gate): reproduces the table above.