inkrs 0.2.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.
Files changed (200) hide show
  1. inkrs-0.2.0/.gitattributes +7 -0
  2. inkrs-0.2.0/.github/workflows/ci.yml +148 -0
  3. inkrs-0.2.0/.github/workflows/package.yml +471 -0
  4. inkrs-0.2.0/.gitignore +3 -0
  5. inkrs-0.2.0/API.md +265 -0
  6. inkrs-0.2.0/CHANGELOG.md +36 -0
  7. inkrs-0.2.0/Cargo.lock +1175 -0
  8. inkrs-0.2.0/Cargo.toml +58 -0
  9. inkrs-0.2.0/LICENSE-APACHE +202 -0
  10. inkrs-0.2.0/LICENSE-MIT +21 -0
  11. inkrs-0.2.0/PKG-INFO +145 -0
  12. inkrs-0.2.0/README.md +144 -0
  13. inkrs-0.2.0/THIRD_PARTY_NOTICES +159 -0
  14. inkrs-0.2.0/bench/p0-tiles-s1.json +50 -0
  15. inkrs-0.2.0/bench/p0h-tiles-1447.json +10 -0
  16. inkrs-0.2.0/bench/pairdiff_inksweep.py +63 -0
  17. inkrs-0.2.0/deny.toml +28 -0
  18. inkrs-0.2.0/docs/FAST.md +134 -0
  19. inkrs-0.2.0/docs/MODELS.md +192 -0
  20. inkrs-0.2.0/docs/WRITEUP.md +56 -0
  21. inkrs-0.2.0/fixtures/hann128.f32 +0 -0
  22. inkrs-0.2.0/fixtures/hann384.f32 +1786 -4
  23. inkrs-0.2.0/fixtures/v8-gauss1.f32 +0 -0
  24. inkrs-0.2.0/fixtures/v8-gauss3.f32 +0 -0
  25. inkrs-0.2.0/inkrs/__init__.py +860 -0
  26. inkrs-0.2.0/inkrs/_cuda.py +390 -0
  27. inkrs-0.2.0/inkrs/_inkrs.pyi +146 -0
  28. inkrs-0.2.0/inkrs/export_weights.py +180 -0
  29. inkrs-0.2.0/inkrs/py.typed +0 -0
  30. inkrs-0.2.0/licenses/cuda-rust-LICENSE +203 -0
  31. inkrs-0.2.0/metal/coreml_body.py +104 -0
  32. inkrs-0.2.0/ptx/inkrs.sm120.ptx +48387 -0
  33. inkrs-0.2.0/ptx/inkrs.sm120.sm_120.cubin +0 -0
  34. inkrs-0.2.0/ptx/inkrs.sm75.ptx +20432 -0
  35. inkrs-0.2.0/ptx/inkrs.sm75.sm_75.cubin +0 -0
  36. inkrs-0.2.0/ptx/inkrs.sm80.ptx +48534 -0
  37. inkrs-0.2.0/ptx/inkrs.sm80.sm_100.cubin +0 -0
  38. inkrs-0.2.0/ptx/inkrs.sm80.sm_80.cubin +0 -0
  39. inkrs-0.2.0/ptx/inkrs.sm80.sm_86.cubin +0 -0
  40. inkrs-0.2.0/ptx/inkrs.sm80.sm_89.cubin +0 -0
  41. inkrs-0.2.0/ptx/inkrs.sm80.sm_90.cubin +0 -0
  42. inkrs-0.2.0/ptx/libdevice.txt +8 -0
  43. inkrs-0.2.0/ptx/ptxas-sm120.txt +366 -0
  44. inkrs-0.2.0/ptx/ptxas-sm75.txt +231 -0
  45. inkrs-0.2.0/ptx/ptxas-sm80.txt +366 -0
  46. inkrs-0.2.0/ptx/ptxas.txt +1 -0
  47. inkrs-0.2.0/ptx/sources.sha256 +36 -0
  48. inkrs-0.2.0/ptx/tools.txt +4 -0
  49. inkrs-0.2.0/pyproject.toml +63 -0
  50. inkrs-0.2.0/python/API.md +265 -0
  51. inkrs-0.2.0/python/Cargo.toml +20 -0
  52. inkrs-0.2.0/python/LICENSE-APACHE +202 -0
  53. inkrs-0.2.0/python/LICENSE-MIT +21 -0
  54. inkrs-0.2.0/python/README.md +104 -0
  55. inkrs-0.2.0/python/THIRD_PARTY_NOTICES +159 -0
  56. inkrs-0.2.0/python/build-wheel.sh +46 -0
  57. inkrs-0.2.0/python/src/lib.rs +608 -0
  58. inkrs-0.2.0/python/src/v8in.rs +117 -0
  59. inkrs-0.2.0/python/tests/gpu_parity.py +207 -0
  60. inkrs-0.2.0/python/tests/test_api.py +651 -0
  61. inkrs-0.2.0/results/README.md +213 -0
  62. inkrs-0.2.0/results/accuracy-speed.json +15848 -0
  63. inkrs-0.2.0/results/coverage-mutations-s1.json +50 -0
  64. inkrs-0.2.0/results/fast-mode-timing.json +337 -0
  65. inkrs-0.2.0/results/first-run.json +238 -0
  66. inkrs-0.2.0/results/hecate-inksweep-timing.json +64 -0
  67. inkrs-0.2.0/results/hecate-kernel-timing.json +56 -0
  68. inkrs-0.2.0/results/hecate-maps-crop.json +335 -0
  69. inkrs-0.2.0/results/hecate-p0h.json +812 -0
  70. inkrs-0.2.0/results/hecate-timing.json +812 -0
  71. inkrs-0.2.0/results/ink3d-gates.json +40 -0
  72. inkrs-0.2.0/results/inkrs-report-r343-gate.json +486 -0
  73. inkrs-0.2.0/results/inkrs-report-r343.json +698 -0
  74. inkrs-0.2.0/results/inkrs-report-s1.json +698 -0
  75. inkrs-0.2.0/results/inksweep-passes-r343.json +1170 -0
  76. inkrs-0.2.0/results/inksweep-passes-s1.json +1035 -0
  77. inkrs-0.2.0/results/inksweep-vs-villa-r343-calibrated.json +790 -0
  78. inkrs-0.2.0/results/inksweep-vs-villa-r343-raw.json +197 -0
  79. inkrs-0.2.0/results/inksweep-vs-villa-s1-calibrated.json +790 -0
  80. inkrs-0.2.0/results/integration-v0.1.json +259 -0
  81. inkrs-0.2.0/results/integration-v0.2.json +245 -0
  82. inkrs-0.2.0/results/letters-r343-multiscale.json +258 -0
  83. inkrs-0.2.0/results/metal-gates.json +190 -0
  84. inkrs-0.2.0/results/metal-s1.json +129 -0
  85. inkrs-0.2.0/results/normaliser-gpu.json +4 -0
  86. inkrs-0.2.0/results/other-gpu-a10.json +1608 -0
  87. inkrs-0.2.0/results/other-gpu-tesla-t4.json +738 -0
  88. inkrs-0.2.0/results/other-gpus-v0.2.json +2231 -0
  89. inkrs-0.2.0/results/p0-s1.json +1326 -0
  90. inkrs-0.2.0/results/p1-r343-multiscale.json +276 -0
  91. inkrs-0.2.0/results/p1-r343.json +276 -0
  92. inkrs-0.2.0/results/p1-s1.json +276 -0
  93. inkrs-0.2.0/results/prize-models-gates.json +594 -0
  94. inkrs-0.2.0/results/ptx-tiers-models.json +494 -0
  95. inkrs-0.2.0/results/ptx-tiers-s1.json +228 -0
  96. inkrs-0.2.0/results/public-ports.json +686 -0
  97. inkrs-0.2.0/results/r343-map-figure.json +28 -0
  98. inkrs-0.2.0/results/r343-map-figure.png +0 -0
  99. inkrs-0.2.0/results/reader-v2-p1.json +308 -0
  100. inkrs-0.2.0/results/reader-v2-published.json +56 -0
  101. inkrs-0.2.0/results/reader-v2-timing.json +179 -0
  102. inkrs-0.2.0/results/reader-v2-villa-engine.json +169 -0
  103. inkrs-0.2.0/results/reader-v3-norm.json +29 -0
  104. inkrs-0.2.0/results/reader-v3-p0.json +16 -0
  105. inkrs-0.2.0/results/reader-v3-p1.json +82 -0
  106. inkrs-0.2.0/results/reader-v3-timing.json +146 -0
  107. inkrs-0.2.0/results/recipe343-timing.json +78 -0
  108. inkrs-0.2.0/results/release-checks.json +235 -0
  109. inkrs-0.2.0/results/release-timing.json +875 -0
  110. inkrs-0.2.0/results/repeatability.json +77 -0
  111. inkrs-0.2.0/results/software.json +16 -0
  112. inkrs-0.2.0/results/t4-gates.json +119 -0
  113. inkrs-0.2.0/results/t4-models.json +436 -0
  114. inkrs-0.2.0/results/t4-speed.json +29 -0
  115. inkrs-0.2.0/results/timing-matched.json +324 -0
  116. inkrs-0.2.0/results/timing-villa-fast-flags.json +51 -0
  117. inkrs-0.2.0/results/tta-306b-timing.json +60 -0
  118. inkrs-0.2.0/results/v8in-306b.json +230 -0
  119. inkrs-0.2.0/results/windows-checks.json +62 -0
  120. inkrs-0.2.0/rustfmt.toml +3 -0
  121. inkrs-0.2.0/scripts/build-cubin.sh +30 -0
  122. inkrs-0.2.0/scripts/build-ptx.sh +147 -0
  123. inkrs-0.2.0/scripts/c_letters.py +152 -0
  124. inkrs-0.2.0/scripts/check-ptx.sh +52 -0
  125. inkrs-0.2.0/scripts/export_hecate.py +106 -0
  126. inkrs-0.2.0/scripts/export_unet3d.py +276 -0
  127. inkrs-0.2.0/scripts/export_v8in.py +182 -0
  128. inkrs-0.2.0/scripts/export_weights.py +8 -0
  129. inkrs-0.2.0/scripts/fingerprint.py +17 -0
  130. inkrs-0.2.0/scripts/gate_mutations.py +101 -0
  131. inkrs-0.2.0/scripts/hecate_mapgate.py +315 -0
  132. inkrs-0.2.0/scripts/inksweep_matched.py +135 -0
  133. inkrs-0.2.0/scripts/make_hecate_inputs.py +134 -0
  134. inkrs-0.2.0/scripts/make_pyramid.py +94 -0
  135. inkrs-0.2.0/scripts/make_s1.py +22 -0
  136. inkrs-0.2.0/scripts/mapdiff.py +99 -0
  137. inkrs-0.2.0/scripts/mapgate3d.py +163 -0
  138. inkrs-0.2.0/scripts/norm_fixture.py +134 -0
  139. inkrs-0.2.0/scripts/p0_compare.py +103 -0
  140. inkrs-0.2.0/scripts/p0_hecate.py +402 -0
  141. inkrs-0.2.0/scripts/p0_ink3d.py +182 -0
  142. inkrs-0.2.0/scripts/p0_torch.py +112 -0
  143. inkrs-0.2.0/scripts/p1_gate.py +123 -0
  144. inkrs-0.2.0/scripts/p1_mutate.py +107 -0
  145. inkrs-0.2.0/scripts/recipe.py +695 -0
  146. inkrs-0.2.0/scripts/recipe343_gate.py +240 -0
  147. inkrs-0.2.0/scripts/v8_windows.py +26 -0
  148. inkrs-0.2.0/scripts/villa_coverage.py +113 -0
  149. inkrs-0.2.0/scripts/villa_map3d.py +116 -0
  150. inkrs-0.2.0/src/checks.rs +616 -0
  151. inkrs-0.2.0/src/conv.rs +425 -0
  152. inkrs-0.2.0/src/dll.rs +627 -0
  153. inkrs-0.2.0/src/dnn/algo_cache.rs +723 -0
  154. inkrs-0.2.0/src/dnn/bias_act.rs +129 -0
  155. inkrs-0.2.0/src/dnn/fused.rs +637 -0
  156. inkrs-0.2.0/src/dnn/gemm_bf16.rs +50 -0
  157. inkrs-0.2.0/src/dnn.rs +604 -0
  158. inkrs-0.2.0/src/engine/tta.rs +159 -0
  159. inkrs-0.2.0/src/engine.rs +1083 -0
  160. inkrs-0.2.0/src/error.rs +165 -0
  161. inkrs-0.2.0/src/fast.rs +211 -0
  162. inkrs-0.2.0/src/gpu.rs +637 -0
  163. inkrs-0.2.0/src/hecate/engine.rs +406 -0
  164. inkrs-0.2.0/src/hecate/model.rs +997 -0
  165. inkrs-0.2.0/src/hecate/p0.rs +104 -0
  166. inkrs-0.2.0/src/hecate.rs +287 -0
  167. inkrs-0.2.0/src/lib.rs +42 -0
  168. inkrs-0.2.0/src/main.rs +1064 -0
  169. inkrs-0.2.0/src/metal/common.metal +55 -0
  170. inkrs-0.2.0/src/metal/conv.metal +304 -0
  171. inkrs-0.2.0/src/metal/coreml.rs +504 -0
  172. inkrs-0.2.0/src/metal/engine.rs +869 -0
  173. inkrs-0.2.0/src/metal/glue.metal +603 -0
  174. inkrs-0.2.0/src/metal/gpu.rs +699 -0
  175. inkrs-0.2.0/src/metal/hecate/engine.rs +365 -0
  176. inkrs-0.2.0/src/metal/hecate/model.rs +1006 -0
  177. inkrs-0.2.0/src/metal/hecate.metal +600 -0
  178. inkrs-0.2.0/src/metal/hecate.rs +5 -0
  179. inkrs-0.2.0/src/metal/model.rs +1114 -0
  180. inkrs-0.2.0/src/metal/v8in/engine.rs +262 -0
  181. inkrs-0.2.0/src/metal/v8in/model.rs +1291 -0
  182. inkrs-0.2.0/src/metal/v8in.metal +493 -0
  183. inkrs-0.2.0/src/metal/v8in.rs +5 -0
  184. inkrs-0.2.0/src/metal.rs +53 -0
  185. inkrs-0.2.0/src/model.rs +1034 -0
  186. inkrs-0.2.0/src/probe.rs +716 -0
  187. inkrs-0.2.0/src/sched.rs +884 -0
  188. inkrs-0.2.0/src/tiff.rs +199 -0
  189. inkrs-0.2.0/src/tta.rs +67 -0
  190. inkrs-0.2.0/src/unet3d/engine.rs +476 -0
  191. inkrs-0.2.0/src/unet3d/model.rs +874 -0
  192. inkrs-0.2.0/src/unet3d/p0.rs +157 -0
  193. inkrs-0.2.0/src/unet3d/zio.rs +215 -0
  194. inkrs-0.2.0/src/unet3d.rs +305 -0
  195. inkrs-0.2.0/src/v8in/engine.rs +263 -0
  196. inkrs-0.2.0/src/v8in/host.rs +384 -0
  197. inkrs-0.2.0/src/v8in/model.rs +582 -0
  198. inkrs-0.2.0/src/v8in.rs +518 -0
  199. inkrs-0.2.0/src/weights.rs +396 -0
  200. inkrs-0.2.0/src/zarr.rs +401 -0
@@ -0,0 +1,7 @@
1
+ # LF in every checkout, Windows included: the PTX tiers are embedded byte for byte (include_str!) and checked
2
+ # against hashes, and the villa patches must apply as written, so CRLF would change what a build ships
3
+ * text=auto eol=lf
4
+ # float fixtures are raw little-endian bytes
5
+ *.f32 binary
6
+ # compiled kernels, embedded byte for byte (include_bytes!)
7
+ *.cubin binary
@@ -0,0 +1,148 @@
1
+ # Builds and checks inkrs on Linux and Windows x86_64: formatting, clippy, the CPU tests and the CLI, plus the
2
+ # dependency policy and the Python lints. package.yml builds and tests the Python wheels and sdist. A GitHub runner
3
+ # has no GPU, so nothing here runs a model; the GPU checks are in results/README.md. CUDA loads at run time, so no
4
+ # job installs a CUDA toolkit
5
+
6
+ name: CI
7
+
8
+ on:
9
+ workflow_dispatch: {}
10
+ push:
11
+ branches:
12
+ - master
13
+ pull_request:
14
+ branches:
15
+ - master
16
+
17
+ permissions:
18
+ contents: read
19
+
20
+ # a new push to a pull request supersedes its running checks
21
+ concurrency:
22
+ group: ci-${{ github.ref }}
23
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
24
+
25
+ env:
26
+ CARGO_TERM_COLOR: always
27
+
28
+ jobs:
29
+ rust:
30
+ name: Rust (${{ matrix.name }})
31
+ # MSVC links slower
32
+ timeout-minutes: ${{ matrix.os == 'windows-2025' && 45 || 30 }}
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ include:
37
+ - name: Linux x86_64
38
+ os: ubuntu-24.04
39
+ rustflags: -D warnings
40
+ # the C runtime is linked statically, so inkrs.exe runs without the Visual C++ redistributable. RUSTFLAGS
41
+ # replaces the per-target flags rather than adding to them, so each target spells out its full set
42
+ - name: Windows x86_64
43
+ os: windows-2025
44
+ rustflags: -D warnings -C target-feature=+crt-static
45
+ runs-on: ${{ matrix.os }}
46
+ env:
47
+ RUSTFLAGS: ${{ matrix.rustflags }}
48
+ steps:
49
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
50
+
51
+ - name: Install Rust toolchain
52
+ uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
53
+ with:
54
+ toolchain: stable
55
+ components: rustfmt, clippy
56
+
57
+ - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
58
+
59
+ # the PTX tiers are embedded byte for byte, so a checkout must not change a line ending; .gitattributes keeps
60
+ # every file LF, and renormalising must find nothing to change
61
+ - name: Line endings
62
+ shell: bash
63
+ run: |
64
+ set -euo pipefail
65
+ git add --renormalize .
66
+ git diff --cached --exit-code
67
+
68
+ - name: Format
69
+ if: runner.os == 'Linux'
70
+ run: cargo fmt --all -- --check
71
+
72
+ # hashes and file lists only; the PTX itself is built with cuda-oxide and the CUDA toolkit
73
+ - name: Committed PTX matches the kernel sources
74
+ if: runner.os == 'Linux'
75
+ run: scripts/check-ptx.sh
76
+
77
+ - name: Clippy
78
+ run: cargo clippy --workspace --all-targets --locked -- -D warnings
79
+
80
+ # the tests that need a GPU open one and fail without it, so only the CPU tests run here; the Windows leg also
81
+ # runs the DLL search's own tests
82
+ - name: Tests
83
+ run: cargo test --workspace --exclude inkrs-python --release --locked
84
+
85
+ - name: Build the CLI
86
+ run: cargo build --release --locked --bin inkrs
87
+
88
+ # a static C runtime keeps vcruntime140.dll and the UCRT out of the imports, and CUDA must load at run time,
89
+ # so the binary may import only Windows system DLLs
90
+ - name: Check the CLI's imports and that it starts
91
+ if: runner.os == 'Windows'
92
+ shell: bash
93
+ run: |
94
+ set -euo pipefail
95
+ bin=target/release/inkrs.exe
96
+ rustup component add llvm-tools
97
+ readobj="$(cygpath -u "$(rustc --print sysroot)")/lib/rustlib/x86_64-pc-windows-msvc/bin/llvm-readobj.exe"
98
+ # a Windows tool's output ends lines with CRLF
99
+ imports="$("$readobj" --coff-imports "$bin" | tr -d '\r' | sed -n 's/^ *Name: //p' | sort -u)"
100
+ echo "$imports"
101
+ if grep -Eiv '^(kernel32|ntdll|advapi32|bcrypt|bcryptprimitives|ws2_32|userenv|secur32|crypt32|ole32|oleaut32|shell32|user32|synchronization|api-ms-win-core-[a-z0-9-]+)\.dll$' <<< "$imports"; then
102
+ echo "the binary imports a DLL outside Windows itself" >&2
103
+ exit 1
104
+ fi
105
+ "$bin" --help
106
+ # the runner has no NVIDIA driver, so opening the GPU must fail naming the Windows driver, not panic
107
+ if out="$("$bin" probe 2>&1)"; then
108
+ echo "probe succeeded without a GPU" >&2
109
+ exit 1
110
+ fi
111
+ echo "$out"
112
+ grep -q 'nvcuda.dll' <<< "$out"
113
+
114
+
115
+ # advisories, licences and sources (deny.toml): every dependency comes from crates.io
116
+ deny:
117
+ name: Dependency policy
118
+ runs-on: ubuntu-24.04
119
+ steps:
120
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
121
+
122
+ - name: Install Rust toolchain
123
+ uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
124
+ with:
125
+ toolchain: stable
126
+
127
+ # a prebuilt binary instead of cargo-deny-action, whose Docker build fails whenever Docker Hub rate-limits the
128
+ # runner
129
+ - name: Install cargo-deny
130
+ uses: taiki-e/install-action@86f7a0ffb25f40114606501a311ebcfaf882ea32 # v2.88.0
131
+ with:
132
+ tool: cargo-deny@0.20.2
133
+
134
+ - name: Check dependency policy
135
+ run: cargo deny --workspace --all-features check
136
+
137
+ python-lint:
138
+ name: Python lints
139
+ runs-on: ubuntu-24.04
140
+ steps:
141
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
142
+
143
+ - name: Install uv
144
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
145
+
146
+ # pinned, because a ruff release that adds lints would otherwise fail an unchanged tree
147
+ - name: Ruff
148
+ run: uvx ruff@0.17.4 check python
@@ -0,0 +1,471 @@
1
+ # Builds and tests the inkrs Python package, its wheels and sdist, on every push and pull request, and publishes
2
+ # them from a version tag
3
+ #
4
+ # To release:
5
+ # 1. set the version in Cargo.toml and python/Cargo.toml; pyproject.toml takes it from python/Cargo.toml
6
+ # 2. in CHANGELOG.md, give the version's entry its release date ("## [X.Y.Z] - YYYY-MM-DD", replacing
7
+ # "Unreleased") and its link at the bottom
8
+ # 3. merge, then tag the merge commit and push the tag:
9
+ # git tag vX.Y.Z
10
+ # git push origin vX.Y.Z
11
+ #
12
+ # The tag publishes to TestPyPI and installs that release on each platform. The pypi environment then waits for a
13
+ # reviewer, and once approved the workflow publishes to PyPI, then a GitHub release with the tag's CHANGELOG.md
14
+ # entry as its notes and the wheels and sdist attached. A tag whose version or changelog entry is wrong fails
15
+ # before anything is published. PyPI and TestPyPI trust this workflow and its environments through OIDC, so no
16
+ # token is stored. Every publish skips files already uploaded, so "Re-run failed jobs" finishes a partial release.
17
+ # Third-party actions are pinned to commit SHAs, since these jobs build and publish the release files; the trailing
18
+ # comment names the version
19
+
20
+ name: Package
21
+
22
+ on:
23
+ workflow_dispatch: {}
24
+ push:
25
+ branches:
26
+ - master
27
+ tags:
28
+ - "v*"
29
+ pull_request:
30
+ branches:
31
+ - master
32
+
33
+ permissions:
34
+ contents: read
35
+
36
+ # a new push to a pull request supersedes its running checks; a tag's run is never cancelled
37
+ concurrency:
38
+ group: package-${{ github.ref }}
39
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
40
+
41
+ env:
42
+ CARGO_TERM_COLOR: always
43
+
44
+ jobs:
45
+ # one abi3 wheel per platform with the backend that platform runs, installed and tested on the oldest and a
46
+ # current CPython. The Linux and Windows wheels build without a GPU or CUDA toolkit because CUDA loads at run time
47
+ # and the kernels ship compiled; the macOS wheel needs no Metal toolchain because the shaders compile when the
48
+ # engine opens. A runner has no GPU, so nothing here runs a model; the GPU checks are in results/README.md
49
+ wheels:
50
+ name: Wheel (${{ matrix.name }})
51
+ timeout-minutes: 45
52
+ strategy:
53
+ fail-fast: false
54
+ matrix:
55
+ include:
56
+ - name: Linux x86_64, CUDA
57
+ os: ubuntu-24.04
58
+ target: x86_64-unknown-linux-gnu
59
+ manylinux: "2_28"
60
+ rustflags: -D warnings
61
+ - name: macOS arm64, Metal
62
+ os: macos-26
63
+ target: aarch64-apple-darwin
64
+ manylinux: "off"
65
+ rustflags: -D warnings
66
+ # the C runtime is linked statically, so the extension runs without the Visual C++ redistributable whichever
67
+ # Python loads it. RUSTFLAGS replaces the per-target flags rather than adding to them, so each target spells
68
+ # out its full set
69
+ - name: Windows x86_64, CUDA
70
+ os: windows-2025
71
+ target: x86_64-pc-windows-msvc
72
+ manylinux: "off"
73
+ rustflags: -D warnings -C target-feature=+crt-static
74
+ runs-on: ${{ matrix.os }}
75
+ env:
76
+ RUSTFLAGS: ${{ matrix.rustflags }}
77
+ # the Metal engine needs macOS 26, but the wheel keeps the arm64 floor so pip installs it on any Apple silicon
78
+ # Mac, where an older macOS gets the engine's UnsupportedError naming the version instead of a failed sdist
79
+ # build; other platforms ignore this
80
+ MACOSX_DEPLOYMENT_TARGET: "11.0"
81
+ steps:
82
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
83
+
84
+ # a Windows checkout without symlink support writes each licence symlink in python/ as a text file holding its
85
+ # target, which maturin would ship in the wheel as the licence; removing it first keeps cp from writing through
86
+ # a real symlink onto itself
87
+ - name: Resolve the licence symlinks
88
+ if: runner.os == 'Windows'
89
+ shell: bash
90
+ run: |
91
+ set -euo pipefail
92
+ for f in LICENSE-MIT LICENSE-APACHE THIRD_PARTY_NOTICES; do
93
+ rm -f "python/$f"
94
+ cp "$f" "python/$f"
95
+ done
96
+
97
+ - name: Build wheel
98
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
99
+ with:
100
+ command: build
101
+ target: ${{ matrix.target }}
102
+ manylinux: ${{ matrix.manylinux }}
103
+ args: --release --locked --out dist -m python/Cargo.toml
104
+ # tag builds are published, so they never reuse objects from the shared cache
105
+ sccache: ${{ !startsWith(github.ref, 'refs/tags/') }}
106
+
107
+ - name: Check wheel size
108
+ shell: bash
109
+ run: |
110
+ set -euo pipefail
111
+ # PyPI rejects files over 100 MB unless the project's limit is raised
112
+ limit=$((90 * 1000 * 1000))
113
+ for wheel in dist/*.whl; do
114
+ size="$(wc -c < "$wheel")"
115
+ echo "$(basename "$wheel"): $size bytes"
116
+ test "$size" -le "$limit"
117
+ done
118
+
119
+ # pip on macOS before 26 skips a wheel tagged for a newer macOS and tries to build the sdist instead
120
+ - name: Check the macOS platform tag
121
+ if: runner.os == 'macOS'
122
+ shell: bash
123
+ run: |
124
+ set -euo pipefail
125
+ ls dist/*-macosx_11_0_arm64.whl
126
+
127
+ # CUDA must load at run time and the C runtime is static, so the extension may import only Python's stable ABI
128
+ # and Windows system DLLs
129
+ - name: Check the extension's imports
130
+ if: runner.os == 'Windows'
131
+ shell: bash
132
+ run: |
133
+ set -euo pipefail
134
+ unpacked="$RUNNER_TEMP/wheel"
135
+ mkdir -p "$unpacked"
136
+ # 7-Zip ships on the runner image, and a wheel is a zip
137
+ 7z x -y dist/*.whl -o"$unpacked" > /dev/null
138
+ ext="$(find "$unpacked" -name '_inkrs*.pyd' -print -quit)"
139
+ test -n "$ext"
140
+ rustup component add llvm-tools
141
+ readobj="$(cygpath -u "$(rustc --print sysroot)")/lib/rustlib/x86_64-pc-windows-msvc/bin/llvm-readobj.exe"
142
+ # a Windows tool's output ends lines with CRLF
143
+ imports="$("$readobj" --coff-imports "$ext" | tr -d '\r' | sed -n 's/^ *Name: //p' | sort -u)"
144
+ echo "$imports"
145
+ if grep -Eiv '^(python3|kernel32|ntdll|advapi32|bcrypt|bcryptprimitives|ws2_32|userenv|secur32|crypt32|ole32|oleaut32|shell32|user32|synchronization|api-ms-win-core-[a-z0-9-]+)\.dll$' <<< "$imports"; then
146
+ echo "the extension imports a DLL outside Python and Windows itself" >&2
147
+ exit 1
148
+ fi
149
+
150
+ - name: Install uv
151
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
152
+
153
+ # the oldest and a current CPython, since the wheel is abi3; the tests need no GPU, cuDNN or checkpoint
154
+ - name: Install the wheel and run its CPU tests
155
+ shell: bash
156
+ run: |
157
+ set -euo pipefail
158
+ for python in 3.9 3.13; do
159
+ venv="$RUNNER_TEMP/venv-$python"
160
+ uv venv --python "$python" "$venv"
161
+ # Windows venvs keep the interpreter in Scripts, not bin
162
+ py="$venv/bin/python"
163
+ if [ "$RUNNER_OS" = Windows ]; then py="$venv/Scripts/python.exe"; fi
164
+ uv pip install --python "$py" dist/*.whl pytest tifffile imagecodecs
165
+ "$py" -c "import inkrs; print(inkrs.__version__, inkrs.build_info())"
166
+ "$py" -m pytest -q -rs python/tests/test_api.py
167
+ done
168
+
169
+ - name: Upload wheel
170
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
171
+ with:
172
+ name: python-wheel-${{ matrix.target }}
173
+ path: dist/*.whl
174
+
175
+ sdist:
176
+ name: Sdist
177
+ runs-on: ubuntu-24.04
178
+ timeout-minutes: 30
179
+ env:
180
+ RUSTFLAGS: -D warnings
181
+ steps:
182
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
183
+
184
+ # the package takes its version from python/Cargo.toml and release tags are named after the engine's, so a
185
+ # bindings version that lags behind fails here on the pull request instead of at publish time
186
+ - name: Check the bindings version matches inkrs
187
+ run: |
188
+ set -euo pipefail
189
+ engine=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
190
+ bindings=$(sed -n 's/^version = "\(.*\)"/\1/p' python/Cargo.toml | head -1)
191
+ if [ "$engine" != "$bindings" ]; then
192
+ echo "python/Cargo.toml is $bindings but inkrs is $engine; bump python/Cargo.toml"
193
+ exit 1
194
+ fi
195
+
196
+ - name: Build sdist
197
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
198
+ with:
199
+ command: sdist
200
+ args: --out dist -m python/Cargo.toml
201
+
202
+ - name: Install Rust toolchain
203
+ uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable
204
+ with:
205
+ toolchain: stable
206
+
207
+ - name: Install uv
208
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
209
+
210
+ # pip builds the sdist where no wheel fits, so build it the same way, outside the checkout. The engine embeds
211
+ # the kernels from ptx/ at compile time, so a build that finds them all proves the sdist carries them; the
212
+ # runner has no GPU, so the check stops at what the import reports
213
+ - name: Install sdist into a fresh environment
214
+ shell: bash
215
+ working-directory: ${{ runner.temp }}
216
+ run: |
217
+ set -euo pipefail
218
+ uv venv --python 3.12 sdist-venv
219
+ uv pip install --python sdist-venv "$GITHUB_WORKSPACE"/dist/*.tar.gz
220
+ version=$(sed -n 's/^version = "\(.*\)"/\1/p' "$GITHUB_WORKSPACE/python/Cargo.toml" | head -1)
221
+ sdist-venv/bin/python -I - "$version" <<'EOF'
222
+ import sys
223
+
224
+ import inkrs
225
+
226
+ info = inkrs.build_info()
227
+ print(inkrs.__version__, info)
228
+ assert inkrs.__version__ == info["version"] == sys.argv[1], (inkrs.__version__, sys.argv[1])
229
+ assert [p["target"] for p in info["ptx"]] == ["sm_75", "sm_80", "sm_120"], info["ptx"]
230
+ # the sdist must carry the cubins too, or a wheel built from it JITs on every GPU
231
+ assert len(info["cubins"]) == 7, info["cubins"]
232
+ EOF
233
+
234
+ - name: Upload sdist
235
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
236
+ with:
237
+ name: python-sdist
238
+ path: dist/*.tar.gz
239
+
240
+ # checks a tag before anything is published: both crates' versions must match it, and CHANGELOG.md needs a dated
241
+ # entry for it, which becomes the GitHub release notes
242
+ release-check:
243
+ name: Check the tag
244
+ if: startsWith(github.ref, 'refs/tags/v')
245
+ runs-on: ubuntu-24.04
246
+ steps:
247
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
248
+
249
+ # the GitHub release reads its notes with parse-changelog, so the same parser checks the entry here
250
+ - name: Install parse-changelog
251
+ uses: taiki-e/install-action@86f7a0ffb25f40114606501a311ebcfaf882ea32 # v2.88.0
252
+ with:
253
+ tool: parse-changelog
254
+
255
+ - name: Check the version and changelog
256
+ shell: bash
257
+ run: |
258
+ set -euo pipefail
259
+ version="${GITHUB_REF_NAME#v}"
260
+ for manifest in Cargo.toml python/Cargo.toml; do
261
+ crate=$(sed -n 's/^version = "\(.*\)"/\1/p' "$manifest" | head -1)
262
+ if [ "$crate" != "$version" ]; then
263
+ echo "$manifest is $crate but the tag is $GITHUB_REF_NAME"
264
+ exit 1
265
+ fi
266
+ done
267
+ # an entry still marked "Unreleased" has not been given its date
268
+ title="$(parse-changelog CHANGELOG.md --title "$version")"
269
+ if ! [[ "$title" =~ ^\[${version//./\\.}\]\ -\ [0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
270
+ echo "the CHANGELOG.md heading for $version is \"$title\"; it needs \"[$version] - YYYY-MM-DD\""
271
+ exit 1
272
+ fi
273
+ notes="$(parse-changelog CHANGELOG.md "$version")"
274
+ if [ -z "$notes" ]; then
275
+ echo "the CHANGELOG.md entry for $version is empty"
276
+ exit 1
277
+ fi
278
+
279
+ # a v* tag publishes to TestPyPI, installs that release, and only then publishes to PyPI; each index trusts this
280
+ # workflow and environment, so no token is stored
281
+ testpypi-publish:
282
+ name: Publish to TestPyPI
283
+ if: startsWith(github.ref, 'refs/tags/v')
284
+ needs: [release-check, wheels, sdist]
285
+ runs-on: ubuntu-24.04
286
+ environment:
287
+ name: testpypi
288
+ url: https://test.pypi.org/project/inkrs/
289
+ permissions:
290
+ id-token: write
291
+ steps:
292
+ - name: Download wheels and sdist
293
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
294
+ with:
295
+ pattern: python-*
296
+ path: dist
297
+ merge-multiple: true
298
+
299
+ - name: Check files match the tag
300
+ shell: bash
301
+ run: |
302
+ set -euo pipefail
303
+ version="${GITHUB_REF_NAME#v}"
304
+ ls -la dist
305
+ # the Linux, macOS and Windows wheels and the sdist
306
+ test "$(find dist -type f | wc -l)" -eq 4
307
+ for file in dist/*; do
308
+ case "$(basename "$file")" in
309
+ "inkrs-${version}"-*.whl | "inkrs-${version}.tar.gz") ;;
310
+ *) echo "$file does not match tag $GITHUB_REF_NAME"; exit 1 ;;
311
+ esac
312
+ done
313
+
314
+ - name: Publish
315
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
316
+ with:
317
+ repository-url: https://test.pypi.org/legacy/
318
+ skip-existing: true
319
+
320
+ testpypi-install:
321
+ name: Install from TestPyPI (${{ matrix.name }})
322
+ needs: testpypi-publish
323
+ runs-on: ${{ matrix.os }}
324
+ strategy:
325
+ fail-fast: false
326
+ matrix:
327
+ include:
328
+ - name: Linux x86_64
329
+ os: ubuntu-24.04
330
+ backend: cuda
331
+ - name: macOS arm64
332
+ os: macos-26
333
+ backend: metal
334
+ - name: Windows x86_64
335
+ os: windows-2025
336
+ backend: cuda
337
+ steps:
338
+ - name: Install uv
339
+ uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
340
+
341
+ - name: Install the release and import it
342
+ shell: bash
343
+ working-directory: ${{ runner.temp }}
344
+ env:
345
+ BACKEND: ${{ matrix.backend }}
346
+ run: |
347
+ set -euo pipefail
348
+ version="${GITHUB_REF_NAME#v}"
349
+ uv venv --python 3.12 venv-release
350
+ # Windows venvs keep the interpreter in Scripts, not bin
351
+ py=venv-release/bin/python
352
+ if [ "$RUNNER_OS" = Windows ]; then py=venv-release/Scripts/python.exe; fi
353
+ # anyone can upload to TestPyPI, so only inkrs comes from it, as a wheel; numpy comes from PyPI
354
+ uv pip install --python "$py" "numpy>=1.22"
355
+ # a new release can take a few minutes to appear in the index
356
+ for attempt in 1 2 3 4 5 6 7 8 9 10; do
357
+ if uv pip install --python "$py" --no-cache --no-deps --only-binary inkrs \
358
+ --index-url https://test.pypi.org/simple/ "inkrs==${version}"; then
359
+ break
360
+ fi
361
+ test "$attempt" -lt 10
362
+ sleep 30
363
+ done
364
+ "$py" -I - "$version" "$BACKEND" <<'EOF'
365
+ import sys
366
+
367
+ import inkrs
368
+
369
+ info = inkrs.build_info()
370
+ print(inkrs.__version__, info)
371
+ assert inkrs.__version__ == sys.argv[1], (inkrs.__version__, sys.argv[1])
372
+ assert info["backend"] == sys.argv[2], info
373
+ EOF
374
+
375
+ # the pypi environment's reviewer approves the release here, before anything outside TestPyPI is published
376
+ pypi-publish:
377
+ name: Publish to PyPI
378
+ needs: testpypi-install
379
+ runs-on: ubuntu-24.04
380
+ environment:
381
+ name: pypi
382
+ url: https://pypi.org/project/inkrs/
383
+ permissions:
384
+ id-token: write
385
+ steps:
386
+ - name: Download wheels and sdist
387
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
388
+ with:
389
+ pattern: python-*
390
+ path: dist
391
+ merge-multiple: true
392
+
393
+ - name: Publish
394
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
395
+ with:
396
+ skip-existing: true
397
+
398
+ # the release starts as a draft and is published only once its files are attached
399
+ github-release:
400
+ name: GitHub release
401
+ needs: pypi-publish
402
+ runs-on: ubuntu-24.04
403
+ permissions:
404
+ contents: write
405
+ steps:
406
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
407
+ with:
408
+ persist-credentials: false
409
+
410
+ - name: Download wheels and sdist
411
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
412
+ with:
413
+ pattern: python-*
414
+ path: dist
415
+ merge-multiple: true
416
+
417
+ # create-gh-release-action deletes a release that already exists, so a rerun leaves a published release alone
418
+ # and finishes a draft instead of recreating it
419
+ - name: Check for an existing release
420
+ id: existing
421
+ env:
422
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
423
+ shell: bash
424
+ run: |
425
+ set -euo pipefail
426
+ if draft="$(gh release view "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --json isDraft --jq .isDraft 2> view.err)"; then
427
+ if [ "$draft" = true ]; then state=draft; else state=published; fi
428
+ elif grep -q 'release not found' view.err; then
429
+ state=none
430
+ else
431
+ # any other failure could hide a release the next step would delete
432
+ cat view.err >&2
433
+ exit 1
434
+ fi
435
+ echo "the $GITHUB_REF_NAME release is $state"
436
+ echo "state=$state" >> "$GITHUB_OUTPUT"
437
+
438
+ - name: Create a draft release
439
+ if: steps.existing.outputs.state == 'none'
440
+ uses: taiki-e/create-gh-release-action@eba8ea96c86cca8a37f1b56e94b4d13301fba651 # v1.11.0
441
+ with:
442
+ draft: true
443
+ # the tag's CHANGELOG.md entry becomes the release notes
444
+ changelog: CHANGELOG.md
445
+
446
+ # --clobber replaces files a failed run already attached. The release becomes latest only when no published
447
+ # stable release has a higher version, so a maintenance release of an older line leaves the newest one as latest
448
+ - name: Attach the wheels and sdist, then publish
449
+ if: steps.existing.outputs.state != 'published'
450
+ env:
451
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
452
+ shell: bash
453
+ run: |
454
+ set -euo pipefail
455
+ gh release upload "$GITHUB_REF_NAME" dist/* --clobber --repo "$GITHUB_REPOSITORY"
456
+
457
+ version="${GITHUB_REF_NAME#v}"
458
+ latest=false
459
+ if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
460
+ # a plain assignment, so set -e stops the step when the lookup fails instead of marking this release
461
+ # latest against an empty list
462
+ releases="$(gh release list --repo "$GITHUB_REPOSITORY" --exclude-drafts --exclude-pre-releases \
463
+ --limit 1000 --json tagName --jq '.[].tagName')"
464
+ newest="$(
465
+ printf '%s\n' "$releases" "$GITHUB_REF_NAME" \
466
+ | sed 's/^v//' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1
467
+ )"
468
+ if [ "$newest" = "$version" ]; then latest=true; fi
469
+ fi
470
+ echo "marking $GITHUB_REF_NAME latest: $latest"
471
+ gh release edit "$GITHUB_REF_NAME" --draft=false --latest="$latest" --repo "$GITHUB_REPOSITORY"
inkrs-0.2.0/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ target/
2
+ weights/
3
+ __pycache__/