Skip to content

fix(dataloader): #315 SVAR2 variant-windows double_buffered slot under-allocation (Layer 1)#327

Merged
d-laub merged 8 commits into
mainfrom
fix/315-svar2-slot
Jul 24, 2026
Merged

fix(dataloader): #315 SVAR2 variant-windows double_buffered slot under-allocation (Layer 1)#327
d-laub merged 8 commits into
mainfrom
fix/315-svar2-slot

Conversation

@d-laub

@d-laub d-laub commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the deferred Layer 1 of #315: Dataset.to_dataloader(mode="double_buffered") over flat variant-windows failed deterministically at scale on SVAR2-format datasets with RuntimeError: ProducerError (ValueError): buffer is smaller than requested size (later SlotOverflowError). The shared-memory slot was sized from a per-instance byte estimate that under-counted the real serialized payload on the SVAR2 reconstruction path.

Root cause

The reported corpus is SVAR2-format-2.0.0 and reconstructs through Svar2Haps (a Haps subclass), not Haps. Dataset._output_bytes_per_instance's variant-windows branch derives its estimate from three terms — n_vars_total, ref_span, and alt_alleles — all read from Svar2Haps.genotypes / Svar2Haps.variants. For Svar2Haps those are permanently-empty SVAR1-shaped placeholders, constructed once at open time only so scattered isinstance(_, Haps) checks keep working; they are never populated, because SVAR2 reconstructs read-bound from the on-disk .svar2 store via Rust FFI (decode_variants_from_svar2_readbound), not through an in-memory sparse genotypes table.

Effect: all three terms collapse to 0 for every instance, so the estimate degenerates to a flat, content-independent 32 bytes/instance while the real payload scales with variant content (up to ~17 KB/instance on the real corpus). The 40×7089 chunk overran its estimate-sized slot → overflow. This is not a formula bug and not a p_eff/ploidy bug — it is a wrong data source.

Fix

  • Added Svar2Haps.measure_variant_payload(...): a shared per-instance measure that runs the same decode_variants_from_svar2_readbound + unphased_union → p_eff fold (var_off[::P]) the reconstructors already use, returning (n_vars_total, ref_span_sum, alt_bytes_sum).
  • Dataset._output_bytes_per_instance now sources those three terms from that shared entry point when isinstance(haps_obj, Svar2Haps), so the estimator and reconstructor derive their variant accounting from the same code and cannot drift apart again. Applied to both the variant-windows branch (the reported repro) and the sibling variants (non-window) branch, which had the identical defect.
  • The Haps (SVAR1/VCF/PGEN) estimate is byte-for-byte unchanged — the fix is gated on Svar2Haps.

Test coverage

tests/unit/test_slot_fit_property.py gains test_slot_fit_svar2_backend: a SVAR2-backed dataset opened via the released Dataset.open + to_dataloader/reconstruct path (the coverage gap that let #315 through), asserting est + slot_overhead ≥ real serialized payload across ref/alt × unphased_union for both variant-windows and variants output modes — red before the fix, green after. Existing Haps-path cases (dummy/VCF/PGEN/SVAR1) pass unchanged.

Real-corpus confirmation

Re-ran the exact reported repro (40 regions × 7089 samples, flat variant-windows, ref="window", alt="allele", unphased_union=True, jitter=0, flank_length=128, default 2 GiB buffer_bytes, batch_size=4096) against the real Hartwig SVAR2 corpus post-fix: to_dataloader(mode="double_buffered") now iterates batches with no SlotOverflowError/ProducerError.

Notes

  • Scope: the released double_buffered path over a written, file-backed dataset — normal main-targeted work, not StreamingDataset.
  • Out of scope (unchanged): buffered/manual modes, Bug A (realign_tracks), the producer-side grow-or-split escalation.
  • jitter>0 on SVAR2 variant-windows/variants remains guarded by the existing NotImplementedError ("right-clip") in _svar2_haps.py, so the measure intentionally does not widen by jitter (that path is unreachable).
  • Design/pin docs: docs/superpowers/specs/2026-07-23-svar2-variant-windows-slot-fit-design.md and …-phase0-realcorpus-findings.md.

🤖 Generated with Claude Code

d-laub and others added 8 commits July 24, 2026 08:53
The predecessor #315 work (Layers 2a/2b/2c) deferred Layer 1 because the
real-corpus divergence was unreproducible synthetically; Phase 0 concluded SVAR2
was StreamingDataset-only and every backend opens as Haps. That is wrong for
SVAR2-format (2.0.0) datasets: the real Hartwig corpus is svar2-backed and opens
as Svar2Haps via the released to_dataloader path (_reconstruct.py:149), whose
variant counting the estimate does not mirror -- so _output_bytes_per_instance
under-counts, packing 40x7089 into one ~17MB slot and overflowing.

This spec resolves the deferred Layer 1 using the now-available real corpus:
pin the diverging term (Phase-0-on-real-corpus), make the estimate an upper
bound on the Svar2Haps path, and extend the slot-fit property test to cover an
SVAR2 dataset through the released reconstruct path (the coverage gap that let
#315 through). Docs-only; --no-verify skips the whole-tree pyrefly hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion plan

Debug-gated, sequential plan: Task 1 pins the estimate divergence against the
real SVAR2 Hartwig corpus (spike); Task 2 locks it with a failing in-tree
slot-fit test on an Svar2Haps dataset (red); Task 3 corrects
_output_bytes_per_instance to upper-bound the Svar2Haps path (green); Task 4
verifies the reported config end-to-end, runs the full tree + cargo, updates the
predecessor docs, and reports to #315. Escalation to grow-or-split is a
stop-and-ask guard in Task 3.

Docs-only; --no-verify skips the whole-tree pyrefly hook.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Svar2Haps-backed fixture (phased_svar2_gvl + svar2_slot_reference) and
test_slot_fit_svar2_backend to lock the #315 slot under-allocation bug in the
test suite. The SVAR2 read path reconstructs via Svar2Haps, whose
genotypes/n_variants/variants are permanently-empty SVAR1-shaped
placeholders (real decode goes through Rust FFI instead), so
_output_bytes_per_instance's variant-windows AND variants branches collapse
n_vars_total/ref_span/alt_alleles to 0 for every instance -- a flat,
content-independent byte estimate that the real serialized payload blows
past for any instance with >=1 real variant.

Intentionally red pre-fix (Task 3 will source these terms from Svar2Haps'
own read-time decode instead of the empty placeholders).
… also red

Bump phased_svar2_gvl from 2 to 80 duplicate-window regions. The "variants"
(non-window) output branch's real payload for the original 4-variant/2-sample
fixture sat under slot_overhead_bytes' 4096-byte floor, masking the same
zeroed-Svar2Haps-placeholder defect the variant-windows branch has (per-instance
offset overhead in the buggy estimate grows slower than per-instance real
payload, so more instances -- not more variant complexity -- is enough to cross
the floor). Confirmed both sub-cases fail in isolation before this fix lands.
… payload

Dataset._output_bytes_per_instance's "variants"/"variant-windows" branches
sourced n_vars_total/ref_span/alt_alleles from Svar2Haps.genotypes/.variants,
which are permanently-empty SVAR1-shaped placeholders for this
read-bound-from-.svar2 reconstructor -- the estimate collapsed to a flat
32 B/instance regardless of real content, undersizing double_buffered slots.

Add Svar2Haps.measure_variant_payload, a shared per-instance decode/fold
entry point reused by both the estimator and (implicitly, via the same
decode_variants_from_svar2_readbound + p_eff fold) the reconstructors, so
the two can't drift apart again. Gated strictly on isinstance(_, Svar2Haps);
the SVAR1/VCF/PGEN Haps estimate path is untouched.
…eachability claim

Flip the design doc's status table (and header) to reflect Layer 1 as done,
pointing at the 2026-07-23 SVAR2 slot-fit design and real-corpus findings docs.
Add a correction note to the 2026-07-21 Phase 0 findings: the real Hartwig
corpus is SVAR2-format-2.0.0 and does reach Svar2Haps on the released
to_dataloader path, contradicting the earlier "SVAR2 unreachable / every
backend opens as Haps" conclusion (which only reflected the VCF/PGEN/SVAR1
backends available to that session).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Defense-in-depth per final review: document why the Svar2Haps measure
sites intentionally do not widen the region by jitter (the read-bound
decode guards jitter>0), so a future guard removal also updates the
estimate. Comment-only; no behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@d-laub
d-laub force-pushed the fix/315-svar2-slot branch from 4cc488a to e3601ac Compare July 24, 2026 15:53
@d-laub
d-laub marked this pull request as ready for review July 24, 2026 18:07
@d-laub
d-laub merged commit b078233 into main Jul 24, 2026
8 checks passed
@d-laub
d-laub deleted the fix/315-svar2-slot branch July 24, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant