Skip to content

Batched structure factors and float64 Triton splat - #96

Open
HatPdotS wants to merge 8 commits into
mainfrom
feature/batched-splat
Open

HatPdotS wants to merge 8 commits into
mainfrom
feature/batched-splat

Conversation

@HatPdotS

Copy link
Copy Markdown
Owner

Summary

Adds an optional leading batch over models that share one crystal (same cell, space group, grid and HKL), such as ensemble members, timepoints, conformers or multistarts. The batch is supported in the density splat kernels, the base functions and SfFFT. The input shape picks the path: unbatched input behaves exactly as before, and every kernel runs it as a single member.

  • Batch contract (torchref/base/electron_density/batching.py): either dense (B, N, ...) atoms (A/B may be shared as (N, 5)), or flat atoms with CSR-style batch_offsets (B+1,). In the flat form members can have different atom counts, including zero. Maps come back as (B, nx, ny, nz) and structure factors as (B, n_refl).
  • Kernels: each has one body for both the batched and unbatched case.
    • Portable: shifts each atom's flat voxel index into its member's slab.
    • Rust: the entry points take an i64 offsets buffer, and batched output is bitwise identical to stacking per-member results.
    • Triton: per-atom member id behind a HAS_BATCH constexpr, with an int64 slab offset.
    • Metal: member buffer plus a has_batch flag; refuses a batch of 2³¹ voxels or more because its indices are 32-bit.
  • Triton float64: per-atom arithmetic follows the input dtype, so the cuda_triton row accepts float64 when all inputs share one dtype. The voxel-index decode stays float32, since its exactness bound is a float32 one. Gradient sums now accumulate per lane and reduce once after the loop; this slightly changes the float32 summation order.
  • Base functions: build_electron_density (batch_offsets_iso / batch_offsets_aniso), ReciprocalSymmetryExtractor.extract_from_grid, both map-symmetry operators, and SfFFT.build_density_map / compute_structure_factors (including imaginary_batch_offsets). ModelFT, refinement and I/O are unchanged.
  • ABI: Rust ABI 1 → 2 and torchref-kernels 0.2.0. This matches the kernel-expansion branch, and the two should be released together. Merging them conflicts only in kernels/src/lib.rs and Cargo.toml, where both sets of modules need registering.
  • Fix: the CPU double-backward re-derivation failed on a call with no atoms; it now returns zero gradients.

Performance (Apple Silicon, float32, splat + FFT + backward, loop vs batched)

B=16 B=64 B=256
20 atoms, CPU 8.7× 5.9× 2.9×
20 atoms, MPS 2.8× 11.5× 17.9×
100 atoms, CPU 1.1× 1.9× 1.6×
100 atoms, MPS 2.2× 5.3× 11.5×

At B=1 and for large structures (2000 atoms), batched is at parity with the loop, within timer noise. CUDA has not been measured.

Test plan

  • New tests/unit/structure_factor/test_batched.py: compares batched against per-member results for forward, gradients and second order on every backend available on the host; ragged vs dense input; build_electron_density, the extractor, map symmetry (direct and interpolated) and SfFFT (late and early symmetry, with and without the imaginary term); error paths.
  • macOS (CPU + MPS) against a wheel built from this branch: tests/unit/structure_factor, tests/unit/base, tests/unit/symmetry, tests/unit/model, dtype conformance, kernel fixes and import smoke all pass.
  • CUDA (not yet run; the Triton changes have never been compiled): pip install ./kernels, then pytest tests/unit/structure_factor/test_batched.py and pytest tests/unit/structure_factor tests/unit/base tests/integration/test_triton_vs_eager_targets.py -m "gpu or cuda". Pass criteria: all cuda-* cases pass, including cuda-f64-cuda_triton; test_triton_kernel_is_actually_dispatched[*-float64] passes; no TorchRefDegradationWarning.

🤖 Generated with Claude Code

HatPdotS and others added 6 commits October 26, 2026 21:42
Adds the batch contract (torchref/base/electron_density/batching.py): a
(B, nx, ny, nz) map plus CSR-style batch_offsets, or dense (B, N, ...)
atoms flattened by normalize_atoms. The portable splat shifts each atom's
flat voxel index into its member's slab; unbatched calls are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Rust entry points take an i64 offsets buffer (ABI 2): forward runs
each member's x-plane blocks over its own slab, backward resolves each
atom's member and reads that member's slab of the gradient. The
per-member kernels are unchanged, so a batch is bitwise the stack of
single-member calls. The double-backward re-derivation passes the batch
on, and returns zero gradients for a call with no atoms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each kernel takes a per-atom member id behind a HAS_BATCH constexpr and
offsets its map pointer by member * nvox in int64. Per-atom arithmetic
follows the input dtype, so the cuda_triton row now accepts float64
(uniform dtypes only); the voxel-index decode stays float32. Gradient
sums accumulate per lane and reduce once after the loop. Not yet run on
a CUDA host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shaders take a member-id buffer and a has_batch flag and work in
their member's slab of the grid. Indices stay 32-bit, so the wrapper
refuses a batch of 2**31 voxels or more instead of wrapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_electron_density takes dense (B, N, ...) atoms or ragged ones with
batch_offsets_iso / batch_offsets_aniso and returns (B, nx, ny, nz). The
reciprocal extractor returns (B, N) from a (B, Nx, Ny, Nz) grid, and both
map-symmetry operators accept one leading batch axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_density_map and compute_structure_factors take dense or ragged
batched atoms (and ragged imaginary atoms via imaginary_batch_offsets)
and return (B, n_reflections) structure factors through either symmetry
route. Implied batch offsets are built on the host, so batching costs
no device sync. Changelog entries for the batched path, float64 Triton
and torchref-kernels 0.2.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T22:27:01.924360Z cb23c0d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb23c0d690

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread kernels/Cargo.toml
[package]
name = "torchref_kernels"
version = "0.1.0"
version = "0.2.0"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Update the kernel dependency constraint for ABI version 2

For installations using the declared dependencies, pyproject.toml still requires torchref-kernels>=0.1.0,<0.2, which excludes this new 0.2.0 package. Meanwhile, torchref/utils/native.py now requires ABI version 2 and rejects the permitted 0.1.0 build. Consequently, a normal installation cannot load the native CPU kernels and falls back to the much slower portable implementation; explicitly installing both projects also produces conflicting dependency requirements. Update the dependency range alongside the package and ABI versions.

AGENTS.md reference: AGENTS.md:L300-L305

Useful? React with 👍 / 👎.

Comment on lines +750 to +752
grad_xyz = torch.zeros_like(xyz)
grad_third = torch.zeros_like(third)
grad_occ = torch.zeros_like(occ)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allocate contiguous CUDA gradient buffers

When CUDA callers supply a non-contiguous dense tensor, such as coordinates constructed with xyz.T.contiguous().T, the refactored forward saves that original layout rather than the contiguous copy passed to the kernel. These zeros_like allocations preserve its strides, but the backward kernels write gradients using contiguous offsets such as atom * 3 + component. The returned coordinate gradients—and anisotropic gradients for similarly laid-out u—are therefore silently permuted even though the forward result is correct. Allocate the gradient buffers with contiguous memory format or save contiguous inputs as the previous implementation did.

Useful? React with 👍 / 👎.

HatPdotS and others added 2 commits October 26, 2026 23:37
The splat entry points changed (ABI 2), and the <0.2 pin made
pip install -e . replace the freshly built 0.2.0 wheel with 0.1.0 from
PyPI, so CI tested against kernels this TorchRef refuses to load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The portable splat's scatter_add sums in a different order when atoms
are regrouped into a batch on some hosts (Linux CI differed by one ulp),
so it gets the same rounding tolerance as the atomic GPU kernels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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