hpc::perm: permutations as composable maps, and one fold over the whole 64×64 field - #336
Conversation
Perm64 composes on the index register (one U8x64::permute_bytes, the VPERMB word) and moves the payload only at a call named materialize_*. PermTable12 stores twelve base steps as two 64-entry half tables (8 KiB) instead of 4096 flat compositions (256 KiB), trading one compose per lookup for tables that stay in L1; the split is exact because the step order is fixed. PermChain holds one Perm64 whatever its length and has no method that takes a payload except materialize_into, so eager application is not expressible through the type. All lane work goes through crate::simd::U8x64; no intrinsics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HnekoM1EidTwQLS3oFVFm
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Essentials Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches📝 Generate docstrings
Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_7ecd7914-cff4-4d0d-8b2c-91d90a45b1c9) |
- PermField is the live-code SET (a 512-byte occupancy mask plus distinct codes in first-appearance order). PermBatch carries the request stream and a request->distinct remap, so multiplicity and order survive deduplication. - Schedule has no default: Deduplicate for public codes, ConstantTime for possibly-secret codes (no field, no reordering, every table lookup reads all entries via for_code_constant_time). - PermInvariant / PermEquivariant are traits the operation implements, not a list owned by the field. Count/Any/Sum/Min/Max are invariant; AND/OR/XOR/ternlog are equivariant. PermBatch::fold_invariant takes no table, so an invariant fold cannot compose a permutation. - Perm64::relative_to(basis) names its direction by the law q.relative_to(p).then(p) == q; combine_in_basis moves each non-basis operand once by its relative map and returns the result still in basis. Tests pin the relative-basis law for every equivariant op against normalize-everything, invariance over all 4096 codes, the dedup remap, and two negative controls (a non-lane-wise op and a position-reading fold) that must break the laws. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HnekoM1EidTwQLS3oFVFm
Every field cell is a gather, so a fold over the whole field of anything that depends only on the (output lane, source lane) pair equals one weighted fold over 64x64 pairs. Exposure is that kernel, built from the two 64-entry tables in 64*64 + 64^3 increments, independent of how much data is folded against it. PermTable12::relative_field(q) re-bases the field onto a fixed map: the relative map of cell (h,l) factors as (q . hi[h]^-1) . lo[l]^-1, so the re-based field is again two 64-entry tables (128 compositions once). examples/perm_field_probe asks one question of the whole field and compares 4096 standalone per-cell folds with one hot fold (N = 64 blocks, release, one run, AVX-512 VBMI host): shared basis Count 4096 compositions / 786432 shuffles -> 0 / 0 different bases Count 16.8M evals -> 262144 evals, 0 shuffles output-mask Count 16.8M evals -> 266240 evals, 0 shuffles ordered Keep owes 4096 x N output blocks either way Dedup over 300 distinct cells in a 20000-request stream was slower than composing per request (594 us vs 471 us): a composition is one byte permute, so the set bookkeeping costs more than it saves here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019HnekoM1EidTwQLS3oFVFm
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_ca6af2f2-2cc9-4a3d-b643-8f887d7b0700) |
A permutation here is a coordinate map.
hpc::permcomposes permutations on the index register, answers questions about the whole 64×64 field without building any cell, and moves data only through a call whose name starts withmaterialize.1. Permutations as maps (
a9a5d1a4)Perm64: a bijection on 64 byte lanes, checked at construction.thencomposes two maps with oneU8x64::permute_bytes: theVPERMBinstruction on AVX-512 VBMI, the facade's fallback elsewhere. It touches indices only.inverse,rotate.conjugate_maskcarries a lane mask through the map instead of moving the data.materialize_into/materialize_blocks_intoare the only data moves.PermTable12: twelve base steps selected by a 12-bit code, with the steps applied in a fixed order.lo[l].then(hi[h]). This is exact even when the steps don't commute, because the order is fixed.PermChain: a lazy accumulator.Perm64however many steps are pushed.materialize_*, so applying a step to data eagerly cannot be written through this type.2. Batches and operation properties (
d24e3fad)PermFieldis the set of live codes: a 512-byte occupancy mask plus the distinct codes in first-appearance order.PermBatchcarries the request stream and a request→distinct remap, so multiplicity and order survive deduplication.Schedulehas no default, so the caller must say whether the codes are public:Deduplicateis for public codes.ConstantTimeis for codes that may be secret. It builds no set and reorders nothing, and every table lookup reads all entries (for_code_constant_time, best effort).PermInvariant/PermEquivariantare traits each operation implements, rather than a list the field owns.PermBatch::fold_invarianttakes no table, so it cannot compose a permutation.Perm64::relative_to(basis)is defined by the lawq.relative_to(p).then(p) == q.combine_in_basismoves each non-basis operand once, by its relative map. The result stays in the basis until something needs actual coordinates.3. The field as an exposure (
ebb9fc42)Every cell of the field is a gather: output lane
ireads one source lanej. So summing, over the whole field, anything that depends only on the pair(i, j)gives the same answer as one weighted sum over the 64×64 pairs.Exposureis that 64×64 weight table.count_where(pred)computes the field-wide Count from it.PermTable12::relative_field(q)re-bases the field onto a fixed mapq.(h, l)factors as(q · hi[h]⁻¹) · lo[l]⁻¹, so the re-based field is again two 64-entry tables. That is 128 compositions once, not one per cell.Probe:
examples/perm_field_probe.rsEach question is asked of all 4096 cells and has one answer. The probe compares 4096 standalone per-cell folds with one fold that keeps the data and the running total in place. Setup: N = 64 blocks, release build, one run on an AVX-512 VBMI host.
Verification
perm.rsor the probe.config-v3).thenoperand order.for_codecomposing the high half before the low half.conjugate_maskusing the inverse map.from_indicesskipping the duplicate check.PermChainnot resetting aftermaterialize_into.relative_tocomposing in the wrong order.combine_in_basisskipping alignment.combine_in_basismoving an operand that already shares the basis.Deduplicatecomposing once per request instead of once per distinct code.ConstantTimeusing the fast lookup would still pass, because the tests check values, not timing.🤖 Generated with Claude Code
https://claude.ai/code/session_019HnekoM1EidTwQLS3oFVFm
Generated by Claude Code