Repository navigation
decoding: Add slab-based affinity CC and parallel HDF5 reading (~43x faster postprocessing) - #223
Open
isWangjunjie wants to merge 2 commits into
Open
isWangjunjie wants to merge 2 commits into
isWangjunjie wants to merge 2 commits into
Conversation
Import Sequence from collections.abc, use builtin tuple annotations, and pass stacklevel=2 to the four UserWarning calls flagged by B028. No behavior change; this lets the file pass the changed-file lint gate when the next commit touches decode_affinity_cc. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
decode_affinity_cc(backend="slab") replaces the iterative min-label sweeps
of the numba backend with one pass of per-X-slab cc3d on a 6-connected
affinity graph, first-appearance compaction, a union-find over slab seams
and a fused final renumber. Output values and dtype equal backend="numba"
(when its sweeps converge); the partition equals numba_serial. New
keywords: num_workers, parallel ("auto", "processes", "fork",
"forkserver", "threads") and buffer_dir. On Linux the process mode forks
the caller with precompiled kernels; "forkserver" uses shared-file
buffers instead.
read_hdf5_affinity_threshold reads a (C, X, Y, Z) HDF5 affinity dataset
in chunk-aligned X blocks across worker processes and returns the boolean
hard affinities, equal to NumPy's native-dtype `ds[channels] > threshold`
(float16 via a 65536-entry lookup table).
Full volume, 3000x3000x1350 float16 LZF affinities, threshold 0.66, fresh
process per run; every output identical (sha256, dtype) to read_hdf5 +
backend="numba":
read_hdf5 + numba, 16 CPUs: 183 s read, 3210 s decode, 194 GiB
reader + slab, 16 CPUs (median, 3): 30 s read, 48 s decode, 97 GiB
reader + slab, 32 CPUs (median, 3): 21 s read, 40 s decode, 109 GiB
(slab runs on an exclusive node; one of four 16-CPU runs decoded in 82 s)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Faster affinity connected-component postprocessing, with output unchanged. On one full volume (3000×3000×1350 float16 affinities, 16 CPUs), read + threshold + decode drops from 3393 s with
read_hdf5+backend="numba"to 79 s with the new reader and slab backend (~43×). Peak memory drops from 194 GiB to 97 GiB. Every output is identical to the numba backend in values and dtype. Both features are opt-in:decode_affinity_ccstill defaults tobackend="cc3d", and"auto"still resolves to"numba".Commit 1: ruff fixes in
decoders/segmentation.pycollections.abc.Sequence, builtintupleannotations, andstacklevel=2on fourUserWarnings. No behavior change. This lets the file pass the changed-file lint gate, which commit 2 triggers by touchingdecode_affinity_cc.Commit 2: slab affinity CC backend + parallel HDF5 affinity threshold reader
decode_affinity_cc(backend="slab")replaces numba's repeated min-label sweeps with a single pass:Output values and dtype equal
backend="numba"whenever its sweeps converge. The partition equalsnumba_serial, which labels differently foredge_offset=1.New keywords, used only by
backend="slab":num_workers;parallel:"auto","processes","fork","forkserver"or"threads";buffer_dir.On Linux,
"auto"/"processes"forks the caller:BrokenProcessPoolinstead of hanging.Forking a process whose other threads hold locks can deadlock the children. Use
parallel="forkserver"(the default on non-Linux POSIX) or"threads"when that matters. The forkserver path copies the input into shared files and measured about 3× slower on a full volume.connectomics.data.io.read_hdf5_affinity_thresholdreads a(C, X, Y, Z)HDF5 affinity dataset in X blocks aligned to the HDF5 chunks, using forkserver/spawn workers, and returns the boolean hard affinities.ds[channels] > threshold.connectomics/utils/shared_buffer.py: file-backed shared arrays for the forkserver path. Storage is reserved withposix_fallocatebefore mapping, and buffers are cleaned up on every exit path.Benchmark (full volume, 3000×3000×1350 float16 LZF, chunks
(1, 32, 128, 128), threshold 0.66, fresh process per run pinned to N CPUs, peak = cgroup anon+shmem; every output identical by sha256 and dtype, 165,200 segments, uint32):read_hdf5(serial, float16 in memory)numbaread_hdf5_affinity_threshold(parallel, bool)slabread_hdf5_affinity_threshold(parallel, bool)slabread_hdf5_affinity_threshold(parallel, bool)slabread_hdf5_affinity_threshold(parallel, bool)slabread_hdf5(serial, float16 in memory)slabdecode_affinity_cc(aff, 0.66, backend="numba").read_hdf5_affinity_threshold(path, 0.66)returns boolean hard affinities directly, thenconnected_components_affinity_3d_slab(hard)decodes them.backend: slab. The reader is unchanged, so the decoder still thresholds the float16 array single-threaded.Recommended for large affinity volumes:
hard = read_hdf5_affinity_threshold(path, threshold, num_workers=N)seg = connected_components_affinity_3d_slab(hard, edge_offset=..., num_workers=N)(fromconnectomics.decoding.decoders.affinity_cc_slab), ordecode_affinity_cc(..., backend="slab", num_workers=N)from a decoding configif __name__ == "__main__":guard because of the forkserver/spawn workers. KeepTMPDIRshort: the forkserver socket path has a 108-byte limit, and exceeding it raisesOSError: AF_UNIX path too long.Test plan
test_affinity_cc_slab.py: structured and random fixtures for bothedge_offsetvalues against numba (values + dtype) and numba_serial (partition). Also covers:affinity_channels, empty axes, uint64 promotion;test_affinity_threshold_reader.py:test_shared_buffer.py: reservation failures, including a realRLIMIT_FSIZEfailure, mapping lifetime, and worker death.connectomics.data.iopublic API snapshot intest_v3_guardrails.py.test_banis_features.py,test_decode_graph.py): 367 passed.read_hdf5+backend="numba"by sha256 and dtype.ruff check,ruff format --checkandmypy --config-file .github/mypy_changed.iniall exit 0.neuron_j0126tutorials, missingcloud-volume, andtest_affinity_cc3d.py::TestAffinityCC3D::test_threshold_sensitivity, which also fails at0a6e4c68.🤖 Generated with Claude Code