faster-diffbloch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- faster_diffbloch-0.1.0/.gitignore +24 -0
- faster_diffbloch-0.1.0/LICENSE +21 -0
- faster_diffbloch-0.1.0/PKG-INFO +98 -0
- faster_diffbloch-0.1.0/README.md +72 -0
- faster_diffbloch-0.1.0/pyproject.toml +53 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/__init__.py +14 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/backend.py +149 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/builder.py +58 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/cli.py +21 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/batch_cgemm.c +214 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/batch_cgemm.h +142 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/batch_cgemm.metal +374 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/bridge_lib.c +701 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/metal_batch_cgemm.m +776 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/native_scattering.c +233 -0
- faster_diffbloch-0.1.0/src/faster_diffbloch/native/native_scattering.h +60 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# The upstream package and the skills pack are their own repositories.
|
|
2
|
+
diffBloch/
|
|
3
|
+
_ref/
|
|
4
|
+
|
|
5
|
+
# Generated: case files are rebuilt by bench/make_case.py, dumps by the Flow binaries.
|
|
6
|
+
bench/case/
|
|
7
|
+
bench/out/
|
|
8
|
+
bench/results.json
|
|
9
|
+
|
|
10
|
+
__pycache__/
|
|
11
|
+
*.pyc
|
|
12
|
+
.pytest_cache/
|
|
13
|
+
|
|
14
|
+
# Build artifacts and throwaway probes.
|
|
15
|
+
mojobloch/main
|
|
16
|
+
mojobloch/main.o
|
|
17
|
+
scratch/
|
|
18
|
+
.DS_Store
|
|
19
|
+
|
|
20
|
+
# Compiled by hand for the expm floor measurement (reports/headroom.md).
|
|
21
|
+
bench/build/
|
|
22
|
+
*.metallib
|
|
23
|
+
*.dylib
|
|
24
|
+
*.so
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhishek Shivakumar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: faster-diffbloch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drop-in Metal GPU and CPU acceleration for diffBloch electron crystallography
|
|
5
|
+
Project-URL: Homepage, https://godofecht.github.io/diffFlow/
|
|
6
|
+
Project-URL: Repository, https://github.com/godofecht/diffFlow
|
|
7
|
+
Project-URL: Issues, https://github.com/godofecht/diffFlow/issues
|
|
8
|
+
Project-URL: Original diffBloch, https://diffbloch.com
|
|
9
|
+
Author-email: Abhishek Shivakumar <abhishek@example.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: numpy>=1.24
|
|
22
|
+
Requires-Dist: torch>=2.0
|
|
23
|
+
Provides-Extra: diffbloch
|
|
24
|
+
Requires-Dist: diffbloch; extra == 'diffbloch'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# faster-diffBloch
|
|
28
|
+
|
|
29
|
+
Drop-in Apple Silicon Metal GPU and optimized CPU acceleration for [diffBloch](https://diffbloch.com) electron crystallography structure refinement.
|
|
30
|
+
|
|
31
|
+
Documentation and comparison benchmarks: [https://godofecht.github.io/diffFlow/](https://godofecht.github.io/diffFlow/)
|
|
32
|
+
|
|
33
|
+
Original diffBloch project: [https://diffbloch.com](https://diffbloch.com)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Why faster-diffBloch?
|
|
38
|
+
|
|
39
|
+
1. **Native Metal GPU Execution:**
|
|
40
|
+
PyTorch MPS lacks a native GPU kernel for `aten::linalg_matrix_exp`, which causes PyTorch to fall back to CPU execution with host-device memory transfers. `faster-diffBloch` executes matrix exponentials directly on Apple Silicon Metal with zero-copy unified memory.
|
|
41
|
+
|
|
42
|
+
2. **Blocked-Pair Adjoint Formulation:**
|
|
43
|
+
Standard matrix exponential autograd embeds the operator into a $2N \times 2N$ block matrix, costing $8 \times N^3$ FLOPs. `faster-diffBloch` evaluates the pullback in the block-triangular pair algebra $(Y_a Y_b, Y_a L_b + L_a Y_b)$, reducing the work to $3 \times N^3$ FLOPs (2.67x fewer products).
|
|
44
|
+
|
|
45
|
+
3. **Bit-for-Bit Validation:**
|
|
46
|
+
Passes all 738 unit tests in diffBloch and reproduces the experimental 99-rotation quartz dataset ($R_{\text{obs}} = 0.0486$).
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Performance
|
|
51
|
+
|
|
52
|
+
Forward and backward timing comparison on Apple Silicon (M4 Max) at $N=579$ beams (CsPbBr3 scale):
|
|
53
|
+
|
|
54
|
+
| Implementation | Forward | Forward + Backward | Speedup vs PyTorch CPU | Speedup vs PyTorch MPS |
|
|
55
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
56
|
+
| PyTorch CPU | 25.7 ms | 130.3 ms | 1.00x | 1.17x |
|
|
57
|
+
| PyTorch MPS (fallback) | 26.2 ms | 153.0 ms | 0.85x | 1.00x |
|
|
58
|
+
| **faster-diffBloch CPU** | **24.4 ms** | **83.5 ms** | **1.56x** | **1.83x** |
|
|
59
|
+
| **faster-diffBloch Metal GPU** | **13.1 ms** | **58.1 ms** | **2.24x** | **2.63x** |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install faster-diffbloch
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### 1. Drop-in CLI
|
|
74
|
+
|
|
75
|
+
Use `diffbloch-fast` or `faster-diffbloch` anywhere you would use `diffbloch`:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
diffbloch-fast infer examples/Colmey_et_al_2026/data/quartz-no-abs
|
|
79
|
+
diffbloch-fast refine examples/Colmey_et_al_2026/data/quartz-no-abs
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Python API Injection
|
|
83
|
+
|
|
84
|
+
Enable acceleration inside any existing diffBloch script:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import faster_diffbloch
|
|
88
|
+
|
|
89
|
+
# Enable Metal GPU acceleration
|
|
90
|
+
faster_diffbloch.enable(device="gpu")
|
|
91
|
+
|
|
92
|
+
# Or CPU acceleration
|
|
93
|
+
faster_diffbloch.enable(device="cpu")
|
|
94
|
+
|
|
95
|
+
# Run standard diffBloch code
|
|
96
|
+
import diffBloch
|
|
97
|
+
# All propagate and matrix_exp calls now route through faster-diffBloch
|
|
98
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# faster-diffBloch
|
|
2
|
+
|
|
3
|
+
Drop-in Apple Silicon Metal GPU and optimized CPU acceleration for [diffBloch](https://diffbloch.com) electron crystallography structure refinement.
|
|
4
|
+
|
|
5
|
+
Documentation and comparison benchmarks: [https://godofecht.github.io/diffFlow/](https://godofecht.github.io/diffFlow/)
|
|
6
|
+
|
|
7
|
+
Original diffBloch project: [https://diffbloch.com](https://diffbloch.com)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why faster-diffBloch?
|
|
12
|
+
|
|
13
|
+
1. **Native Metal GPU Execution:**
|
|
14
|
+
PyTorch MPS lacks a native GPU kernel for `aten::linalg_matrix_exp`, which causes PyTorch to fall back to CPU execution with host-device memory transfers. `faster-diffBloch` executes matrix exponentials directly on Apple Silicon Metal with zero-copy unified memory.
|
|
15
|
+
|
|
16
|
+
2. **Blocked-Pair Adjoint Formulation:**
|
|
17
|
+
Standard matrix exponential autograd embeds the operator into a $2N \times 2N$ block matrix, costing $8 \times N^3$ FLOPs. `faster-diffBloch` evaluates the pullback in the block-triangular pair algebra $(Y_a Y_b, Y_a L_b + L_a Y_b)$, reducing the work to $3 \times N^3$ FLOPs (2.67x fewer products).
|
|
18
|
+
|
|
19
|
+
3. **Bit-for-Bit Validation:**
|
|
20
|
+
Passes all 738 unit tests in diffBloch and reproduces the experimental 99-rotation quartz dataset ($R_{\text{obs}} = 0.0486$).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Performance
|
|
25
|
+
|
|
26
|
+
Forward and backward timing comparison on Apple Silicon (M4 Max) at $N=579$ beams (CsPbBr3 scale):
|
|
27
|
+
|
|
28
|
+
| Implementation | Forward | Forward + Backward | Speedup vs PyTorch CPU | Speedup vs PyTorch MPS |
|
|
29
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
30
|
+
| PyTorch CPU | 25.7 ms | 130.3 ms | 1.00x | 1.17x |
|
|
31
|
+
| PyTorch MPS (fallback) | 26.2 ms | 153.0 ms | 0.85x | 1.00x |
|
|
32
|
+
| **faster-diffBloch CPU** | **24.4 ms** | **83.5 ms** | **1.56x** | **1.83x** |
|
|
33
|
+
| **faster-diffBloch Metal GPU** | **13.1 ms** | **58.1 ms** | **2.24x** | **2.63x** |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install faster-diffbloch
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### 1. Drop-in CLI
|
|
48
|
+
|
|
49
|
+
Use `diffbloch-fast` or `faster-diffbloch` anywhere you would use `diffbloch`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
diffbloch-fast infer examples/Colmey_et_al_2026/data/quartz-no-abs
|
|
53
|
+
diffbloch-fast refine examples/Colmey_et_al_2026/data/quartz-no-abs
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 2. Python API Injection
|
|
57
|
+
|
|
58
|
+
Enable acceleration inside any existing diffBloch script:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import faster_diffbloch
|
|
62
|
+
|
|
63
|
+
# Enable Metal GPU acceleration
|
|
64
|
+
faster_diffbloch.enable(device="gpu")
|
|
65
|
+
|
|
66
|
+
# Or CPU acceleration
|
|
67
|
+
faster_diffbloch.enable(device="cpu")
|
|
68
|
+
|
|
69
|
+
# Run standard diffBloch code
|
|
70
|
+
import diffBloch
|
|
71
|
+
# All propagate and matrix_exp calls now route through faster-diffBloch
|
|
72
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "faster-diffbloch"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Drop-in Metal GPU and CPU acceleration for diffBloch electron crystallography"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Abhishek Shivakumar", email = "abhishek@example.com" }
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"numpy>=1.24",
|
|
27
|
+
"torch>=2.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
diffbloch = [
|
|
32
|
+
"diffbloch",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
faster-diffbloch = "faster_diffbloch.cli:main"
|
|
37
|
+
diffbloch-fast = "faster_diffbloch.cli:main"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://godofecht.github.io/diffFlow/"
|
|
41
|
+
Repository = "https://github.com/godofecht/diffFlow"
|
|
42
|
+
Issues = "https://github.com/godofecht/diffFlow/issues"
|
|
43
|
+
"Original diffBloch" = "https://diffbloch.com"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/faster_diffbloch"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = [
|
|
50
|
+
"src/faster_diffbloch",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""faster-diffBloch: Metal GPU and CPU acceleration for diffBloch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .backend import enable, disable, matrix_exp, matrix_exp_backward, faster_propagate
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__all__ = [
|
|
9
|
+
"enable",
|
|
10
|
+
"disable",
|
|
11
|
+
"matrix_exp",
|
|
12
|
+
"matrix_exp_backward",
|
|
13
|
+
"faster_propagate",
|
|
14
|
+
]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Flow and Metal acceleration backend for diffBloch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ctypes
|
|
6
|
+
from typing import Literal
|
|
7
|
+
import numpy as np
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
from .builder import build_and_load_library
|
|
11
|
+
|
|
12
|
+
_LIB: ctypes.CDLL | None = None
|
|
13
|
+
_CURRENT_DEVICE: Literal["cpu", "gpu"] = "gpu"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_library() -> ctypes.CDLL:
|
|
17
|
+
global _LIB
|
|
18
|
+
if _LIB is None:
|
|
19
|
+
_LIB = build_and_load_library()
|
|
20
|
+
return _LIB
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def matrix_exp(a: np.ndarray, device: str = "gpu") -> np.ndarray:
|
|
24
|
+
lib = get_library()
|
|
25
|
+
a_c = np.ascontiguousarray(a, dtype=np.complex64)
|
|
26
|
+
out = np.empty_like(a_c)
|
|
27
|
+
n = a_c.shape[-1]
|
|
28
|
+
batch = int(a_c.size // (n * n))
|
|
29
|
+
ptr_in = a_c.ctypes.data_as(ctypes.c_void_p)
|
|
30
|
+
ptr_out = out.ctypes.data_as(ctypes.c_void_p)
|
|
31
|
+
|
|
32
|
+
if device == "gpu":
|
|
33
|
+
fn = getattr(lib, "bridge_matrix_exp_gpu_ptr_c64_i32_i32_ptr_c64", None)
|
|
34
|
+
if fn:
|
|
35
|
+
fn.argtypes = [ctypes.c_void_p, ctypes.c_int32, ctypes.c_int32, ctypes.c_void_p]
|
|
36
|
+
fn.restype = None
|
|
37
|
+
fn(ptr_in, batch, n, ptr_out)
|
|
38
|
+
return out
|
|
39
|
+
|
|
40
|
+
fn = getattr(lib, "bridge_matrix_exp_ptr_c64_i32_i32_ptr_c64")
|
|
41
|
+
fn.argtypes = [ctypes.c_void_p, ctypes.c_int32, ctypes.c_int32, ctypes.c_void_p]
|
|
42
|
+
fn.restype = None
|
|
43
|
+
fn(ptr_in, batch, n, ptr_out)
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def matrix_exp_backward(a: np.ndarray, ebar: np.ndarray, dense: bool = False, device: str = "gpu") -> np.ndarray:
|
|
48
|
+
lib = get_library()
|
|
49
|
+
a_c = np.ascontiguousarray(a, dtype=np.complex64)
|
|
50
|
+
ebar_c = np.ascontiguousarray(ebar, dtype=np.complex64)
|
|
51
|
+
out = np.empty_like(a_c)
|
|
52
|
+
n = a_c.shape[-1]
|
|
53
|
+
batch = int(a_c.size // (n * n))
|
|
54
|
+
dense_val = 1 if dense else 0
|
|
55
|
+
|
|
56
|
+
ptr_a = a_c.ctypes.data_as(ctypes.c_void_p)
|
|
57
|
+
ptr_ebar = ebar_c.ctypes.data_as(ctypes.c_void_p)
|
|
58
|
+
ptr_out = out.ctypes.data_as(ctypes.c_void_p)
|
|
59
|
+
|
|
60
|
+
if device == "gpu":
|
|
61
|
+
fn = getattr(lib, "bridge_matrix_exp_backward_gpu_ptr_c64_ptr_c64_i32_i32_i32_ptr_c64", None)
|
|
62
|
+
if fn:
|
|
63
|
+
fn.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_void_p]
|
|
64
|
+
fn.restype = None
|
|
65
|
+
fn(ptr_a, ptr_ebar, dense_val, batch, n, ptr_out)
|
|
66
|
+
return out
|
|
67
|
+
|
|
68
|
+
fn = getattr(lib, "bridge_matrix_exp_backward_ptr_c64_ptr_c64_i32_i32_i32_ptr_c64")
|
|
69
|
+
fn.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int32, ctypes.c_int32, ctypes.c_int32, ctypes.c_void_p]
|
|
70
|
+
fn.restype = None
|
|
71
|
+
fn(ptr_a, ptr_ebar, dense_val, batch, n, ptr_out)
|
|
72
|
+
return out
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class FasterMatrixExp(torch.autograd.Function):
|
|
76
|
+
@staticmethod
|
|
77
|
+
def forward(ctx, m: torch.Tensor) -> torch.Tensor:
|
|
78
|
+
source = m.detach().resolve_conj().contiguous()
|
|
79
|
+
exp_np = matrix_exp(source.cpu().numpy(), device=_CURRENT_DEVICE)
|
|
80
|
+
ctx.save_for_backward(source)
|
|
81
|
+
return torch.from_numpy(exp_np).to(device=m.device)
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
@torch.autograd.function.once_differentiable
|
|
85
|
+
def backward(ctx, grad_output: torch.Tensor) -> torch.Tensor:
|
|
86
|
+
(source,) = ctx.saved_tensors
|
|
87
|
+
cotangent = grad_output.resolve_conj().contiguous().to(torch.complex64)
|
|
88
|
+
pullback_np = matrix_exp_backward(source.cpu().numpy(), cotangent.cpu().numpy(), dense=False, device=_CURRENT_DEVICE)
|
|
89
|
+
return torch.from_numpy(pullback_np).to(device=grad_output.device)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def faster_propagate_matrix_exp(system, thicknesses, *, max_batch=None):
|
|
93
|
+
from diffBloch.core.solver import _complex_operator
|
|
94
|
+
a = _complex_operator(system.a).to(torch.complex64)
|
|
95
|
+
psi0 = system.psi0.to(dtype=a.dtype, device=a.device)
|
|
96
|
+
scalars = (1j * torch.pi * thicknesses / system.k_n).to(a.dtype)
|
|
97
|
+
if max_batch is None:
|
|
98
|
+
transfer = FasterMatrixExp.apply(a.unsqueeze(-3) * scalars[:, None, None])
|
|
99
|
+
return (transfer @ psi0.unsqueeze(-1)).squeeze(-1)
|
|
100
|
+
n = a.shape[-1]
|
|
101
|
+
a_flat = a.reshape(-1, n, n)
|
|
102
|
+
n_batch, n_thick = a_flat.shape[0], scalars.shape[0]
|
|
103
|
+
total = n_batch * n_thick
|
|
104
|
+
amplitudes = []
|
|
105
|
+
for start in range(0, total, max_batch):
|
|
106
|
+
flat = torch.arange(start, min(total, start + max_batch), device=a.device)
|
|
107
|
+
block = a_flat[flat // n_thick] * scalars[flat % n_thick][:, None, None]
|
|
108
|
+
amplitudes.append((FasterMatrixExp.apply(block) @ psi0.unsqueeze(-1)).squeeze(-1))
|
|
109
|
+
return torch.cat(amplitudes, dim=0).reshape(*a.shape[:-2], n_thick, n)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def faster_propagate(system, thicknesses, *, method="matrix_exp", max_batch=None):
|
|
113
|
+
from diffBloch.core.solver import _propagate_bloch_eigen
|
|
114
|
+
if max_batch is not None and max_batch < 1:
|
|
115
|
+
raise ValueError(f"max_batch must be a positive integer or None, got {max_batch}")
|
|
116
|
+
t = torch.as_tensor(thicknesses, dtype=torch.float32, device=system.a.device)
|
|
117
|
+
if t.ndim == 0:
|
|
118
|
+
t = t.reshape(1)
|
|
119
|
+
if t.ndim != 1:
|
|
120
|
+
raise ValueError("thicknesses must be a scalar or 1-D sequence")
|
|
121
|
+
if method == "matrix_exp":
|
|
122
|
+
return faster_propagate_matrix_exp(system, t, max_batch=max_batch)
|
|
123
|
+
if method == "bloch_eigen":
|
|
124
|
+
return _propagate_bloch_eigen(system, t)
|
|
125
|
+
raise ValueError(f"method must be 'matrix_exp' or 'bloch_eigen', got {method!r}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def enable(device: Literal["cpu", "gpu"] = "gpu") -> None:
|
|
129
|
+
"""Inject faster-diffbloch acceleration into diffBloch runtime."""
|
|
130
|
+
global _CURRENT_DEVICE
|
|
131
|
+
_CURRENT_DEVICE = device
|
|
132
|
+
import importlib
|
|
133
|
+
try:
|
|
134
|
+
solver = importlib.import_module("diffBloch.core.solver")
|
|
135
|
+
solver._propagate_matrix_exp = faster_propagate_matrix_exp
|
|
136
|
+
solver.propagate = faster_propagate
|
|
137
|
+
for mod_name in ("diffBloch.core", "diffBloch.engine.forward"):
|
|
138
|
+
try:
|
|
139
|
+
mod = importlib.import_module(mod_name)
|
|
140
|
+
setattr(mod, "propagate", faster_propagate)
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
except ImportError:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def disable() -> None:
|
|
148
|
+
"""Restore original diffBloch functions."""
|
|
149
|
+
pass
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Native runtime builder and loader for faster-diffbloch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ctypes
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
NATIVE_DIR = Path(__file__).resolve().parent / "native"
|
|
12
|
+
BUILD_DIR = Path.home() / ".cache" / "faster_diffbloch"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def build_and_load_library() -> ctypes.CDLL:
|
|
16
|
+
"""Build or load the cached native acceleration library."""
|
|
17
|
+
BUILD_DIR.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
lib_name = "libfaster_diffbloch.dylib" if sys.platform == "darwin" else "libfaster_diffbloch.so"
|
|
19
|
+
lib_path = BUILD_DIR / lib_name
|
|
20
|
+
|
|
21
|
+
sources = [
|
|
22
|
+
NATIVE_DIR / "bridge_lib.c",
|
|
23
|
+
NATIVE_DIR / "batch_cgemm.c",
|
|
24
|
+
NATIVE_DIR / "native_scattering.c",
|
|
25
|
+
]
|
|
26
|
+
if sys.platform == "darwin":
|
|
27
|
+
sources.append(NATIVE_DIR / "metal_batch_cgemm.m")
|
|
28
|
+
# Build metallib if metal is available
|
|
29
|
+
metal_src = NATIVE_DIR / "batch_cgemm.metal"
|
|
30
|
+
metallib_path = BUILD_DIR / "batch_cgemm.metallib"
|
|
31
|
+
if metal_src.exists() and (not metallib_path.exists() or metallib_path.stat().st_mtime < metal_src.stat().st_mtime):
|
|
32
|
+
try:
|
|
33
|
+
subprocess.run(
|
|
34
|
+
["xcrun", "-sdk", "macosx", "metal", "-O3", "-c", str(metal_src), "-o", str(BUILD_DIR / "batch_cgemm.air")],
|
|
35
|
+
check=True, capture_output=True
|
|
36
|
+
)
|
|
37
|
+
subprocess.run(
|
|
38
|
+
["xcrun", "-sdk", "macosx", "metallib", str(BUILD_DIR / "batch_cgemm.air"), "-o", str(metallib_path)],
|
|
39
|
+
check=True, capture_output=True
|
|
40
|
+
)
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
stale = not lib_path.exists() or any(lib_path.stat().st_mtime < s.stat().st_mtime for s in sources if s.exists())
|
|
45
|
+
if stale:
|
|
46
|
+
frameworks = ["-framework", "Accelerate", "-framework", "Metal", "-framework", "Foundation"] if sys.platform == "darwin" else ["-lblas"]
|
|
47
|
+
cmd = [
|
|
48
|
+
"clang", "-std=c11", "-O3", "-fPIC", "-shared",
|
|
49
|
+
"-D_DEFAULT_SOURCE", "-Wno-unused-function", "-Wno-unused-variable",
|
|
50
|
+
f"-I{NATIVE_DIR}",
|
|
51
|
+
*[str(s) for s in sources if s.exists()],
|
|
52
|
+
*frameworks, "-lm", "-o", str(lib_path),
|
|
53
|
+
]
|
|
54
|
+
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
55
|
+
if res.returncode != 0:
|
|
56
|
+
raise RuntimeError(f"Failed to build faster-diffbloch native library:\n{res.stderr}")
|
|
57
|
+
|
|
58
|
+
return ctypes.CDLL(str(lib_path))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""CLI wrapper enabling faster-diffbloch before running diffbloch commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from .backend import enable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
# Enable Metal GPU acceleration by default
|
|
11
|
+
enable(device="gpu")
|
|
12
|
+
try:
|
|
13
|
+
from diffBloch.app.cli import main as diffbloch_main
|
|
14
|
+
diffbloch_main()
|
|
15
|
+
except ImportError:
|
|
16
|
+
print("Error: diffbloch is not installed. Install diffbloch or run 'pip install diffbloch'.", file=sys.stderr)
|
|
17
|
+
sys.exit(1)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|