mtlpy 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.
Files changed (176) hide show
  1. mtlpy-0.1.0/.github/workflows/ci.yml +38 -0
  2. mtlpy-0.1.0/.github/workflows/release.yml +141 -0
  3. mtlpy-0.1.0/.gitignore +32 -0
  4. mtlpy-0.1.0/.gitmodules +3 -0
  5. mtlpy-0.1.0/CMakeLists.txt +37 -0
  6. mtlpy-0.1.0/LICENSE +21 -0
  7. mtlpy-0.1.0/PKG-INFO +295 -0
  8. mtlpy-0.1.0/README.md +266 -0
  9. mtlpy-0.1.0/benchmarks/bench.py +208 -0
  10. mtlpy-0.1.0/csrc/bindings.cpp +46 -0
  11. mtlpy-0.1.0/csrc/buffer.cpp +22 -0
  12. mtlpy-0.1.0/csrc/buffer.h +21 -0
  13. mtlpy-0.1.0/csrc/device.cpp +75 -0
  14. mtlpy-0.1.0/csrc/device.h +35 -0
  15. mtlpy-0.1.0/csrc/metal_impl.mm +7 -0
  16. mtlpy-0.1.0/csrc/pipeline.cpp +109 -0
  17. mtlpy-0.1.0/csrc/pipeline.h +32 -0
  18. mtlpy-0.1.0/csrc/pipeline_cache.cpp +173 -0
  19. mtlpy-0.1.0/csrc/pipeline_cache.h +47 -0
  20. mtlpy-0.1.0/examples/adder.py +14 -0
  21. mtlpy-0.1.0/examples/async_dispatch.py +28 -0
  22. mtlpy-0.1.0/examples/reuse_buffers.py +37 -0
  23. mtlpy-0.1.0/metal-cpp/Foundation/Foundation.hpp +47 -0
  24. mtlpy-0.1.0/metal-cpp/Foundation/NSArray.hpp +124 -0
  25. mtlpy-0.1.0/metal-cpp/Foundation/NSAutoreleasePool.hpp +83 -0
  26. mtlpy-0.1.0/metal-cpp/Foundation/NSBundle.hpp +374 -0
  27. mtlpy-0.1.0/metal-cpp/Foundation/NSData.hpp +54 -0
  28. mtlpy-0.1.0/metal-cpp/Foundation/NSDate.hpp +53 -0
  29. mtlpy-0.1.0/metal-cpp/Foundation/NSDefines.hpp +45 -0
  30. mtlpy-0.1.0/metal-cpp/Foundation/NSDictionary.hpp +128 -0
  31. mtlpy-0.1.0/metal-cpp/Foundation/NSEnumerator.hpp +78 -0
  32. mtlpy-0.1.0/metal-cpp/Foundation/NSError.hpp +173 -0
  33. mtlpy-0.1.0/metal-cpp/Foundation/NSLock.hpp +118 -0
  34. mtlpy-0.1.0/metal-cpp/Foundation/NSNotification.hpp +110 -0
  35. mtlpy-0.1.0/metal-cpp/Foundation/NSNumber.hpp +501 -0
  36. mtlpy-0.1.0/metal-cpp/Foundation/NSObjCRuntime.hpp +43 -0
  37. mtlpy-0.1.0/metal-cpp/Foundation/NSObject.hpp +302 -0
  38. mtlpy-0.1.0/metal-cpp/Foundation/NSPrivate.hpp +531 -0
  39. mtlpy-0.1.0/metal-cpp/Foundation/NSProcessInfo.hpp +386 -0
  40. mtlpy-0.1.0/metal-cpp/Foundation/NSRange.hpp +83 -0
  41. mtlpy-0.1.0/metal-cpp/Foundation/NSSet.hpp +87 -0
  42. mtlpy-0.1.0/metal-cpp/Foundation/NSSharedPtr.hpp +324 -0
  43. mtlpy-0.1.0/metal-cpp/Foundation/NSString.hpp +255 -0
  44. mtlpy-0.1.0/metal-cpp/Foundation/NSTypes.hpp +51 -0
  45. mtlpy-0.1.0/metal-cpp/Foundation/NSURL.hpp +90 -0
  46. mtlpy-0.1.0/metal-cpp/LICENSE.txt +202 -0
  47. mtlpy-0.1.0/metal-cpp/Metal/MTL4AccelerationStructure.hpp +1395 -0
  48. mtlpy-0.1.0/metal-cpp/Metal/MTL4Archive.hpp +93 -0
  49. mtlpy-0.1.0/metal-cpp/Metal/MTL4ArgumentTable.hpp +187 -0
  50. mtlpy-0.1.0/metal-cpp/Metal/MTL4BinaryFunction.hpp +50 -0
  51. mtlpy-0.1.0/metal-cpp/Metal/MTL4BinaryFunctionDescriptor.hpp +97 -0
  52. mtlpy-0.1.0/metal-cpp/Metal/MTL4CommandAllocator.hpp +100 -0
  53. mtlpy-0.1.0/metal-cpp/Metal/MTL4CommandBuffer.hpp +193 -0
  54. mtlpy-0.1.0/metal-cpp/Metal/MTL4CommandEncoder.hpp +134 -0
  55. mtlpy-0.1.0/metal-cpp/Metal/MTL4CommandQueue.hpp +283 -0
  56. mtlpy-0.1.0/metal-cpp/Metal/MTL4CommitFeedback.hpp +62 -0
  57. mtlpy-0.1.0/metal-cpp/Metal/MTL4Compiler.hpp +345 -0
  58. mtlpy-0.1.0/metal-cpp/Metal/MTL4CompilerTask.hpp +63 -0
  59. mtlpy-0.1.0/metal-cpp/Metal/MTL4ComputeCommandEncoder.hpp +307 -0
  60. mtlpy-0.1.0/metal-cpp/Metal/MTL4ComputePipeline.hpp +158 -0
  61. mtlpy-0.1.0/metal-cpp/Metal/MTL4Counters.hpp +138 -0
  62. mtlpy-0.1.0/metal-cpp/Metal/MTL4FunctionDescriptor.hpp +49 -0
  63. mtlpy-0.1.0/metal-cpp/Metal/MTL4LibraryDescriptor.hpp +98 -0
  64. mtlpy-0.1.0/metal-cpp/Metal/MTL4LibraryFunctionDescriptor.hpp +86 -0
  65. mtlpy-0.1.0/metal-cpp/Metal/MTL4LinkingDescriptor.hpp +204 -0
  66. mtlpy-0.1.0/metal-cpp/Metal/MTL4MachineLearningCommandEncoder.hpp +66 -0
  67. mtlpy-0.1.0/metal-cpp/Metal/MTL4MachineLearningPipeline.hpp +172 -0
  68. mtlpy-0.1.0/metal-cpp/Metal/MTL4MeshRenderPipeline.hpp +413 -0
  69. mtlpy-0.1.0/metal-cpp/Metal/MTL4PipelineDataSetSerializer.hpp +85 -0
  70. mtlpy-0.1.0/metal-cpp/Metal/MTL4PipelineState.hpp +150 -0
  71. mtlpy-0.1.0/metal-cpp/Metal/MTL4RenderCommandEncoder.hpp +340 -0
  72. mtlpy-0.1.0/metal-cpp/Metal/MTL4RenderPass.hpp +280 -0
  73. mtlpy-0.1.0/metal-cpp/Metal/MTL4RenderPipeline.hpp +587 -0
  74. mtlpy-0.1.0/metal-cpp/Metal/MTL4SpecializedFunctionDescriptor.hpp +100 -0
  75. mtlpy-0.1.0/metal-cpp/Metal/MTL4StitchedFunctionDescriptor.hpp +86 -0
  76. mtlpy-0.1.0/metal-cpp/Metal/MTL4TileRenderPipeline.hpp +173 -0
  77. mtlpy-0.1.0/metal-cpp/Metal/MTLAccelerationStructure.hpp +1887 -0
  78. mtlpy-0.1.0/metal-cpp/Metal/MTLAccelerationStructureCommandEncoder.hpp +260 -0
  79. mtlpy-0.1.0/metal-cpp/Metal/MTLAccelerationStructureTypes.hpp +292 -0
  80. mtlpy-0.1.0/metal-cpp/Metal/MTLAllocation.hpp +40 -0
  81. mtlpy-0.1.0/metal-cpp/Metal/MTLArgument.hpp +840 -0
  82. mtlpy-0.1.0/metal-cpp/Metal/MTLArgumentEncoder.hpp +235 -0
  83. mtlpy-0.1.0/metal-cpp/Metal/MTLBinaryArchive.hpp +152 -0
  84. mtlpy-0.1.0/metal-cpp/Metal/MTLBlitCommandEncoder.hpp +233 -0
  85. mtlpy-0.1.0/metal-cpp/Metal/MTLBlitPass.hpp +154 -0
  86. mtlpy-0.1.0/metal-cpp/Metal/MTLBuffer.hpp +119 -0
  87. mtlpy-0.1.0/metal-cpp/Metal/MTLCaptureManager.hpp +217 -0
  88. mtlpy-0.1.0/metal-cpp/Metal/MTLCaptureScope.hpp +91 -0
  89. mtlpy-0.1.0/metal-cpp/Metal/MTLCommandBuffer.hpp +464 -0
  90. mtlpy-0.1.0/metal-cpp/Metal/MTLCommandEncoder.hpp +117 -0
  91. mtlpy-0.1.0/metal-cpp/Metal/MTLCommandQueue.hpp +158 -0
  92. mtlpy-0.1.0/metal-cpp/Metal/MTLComputeCommandEncoder.hpp +324 -0
  93. mtlpy-0.1.0/metal-cpp/Metal/MTLComputePass.hpp +169 -0
  94. mtlpy-0.1.0/metal-cpp/Metal/MTLComputePipeline.hpp +439 -0
  95. mtlpy-0.1.0/metal-cpp/Metal/MTLCounters.hpp +243 -0
  96. mtlpy-0.1.0/metal-cpp/Metal/MTLDataType.hpp +129 -0
  97. mtlpy-0.1.0/metal-cpp/Metal/MTLDefines.hpp +41 -0
  98. mtlpy-0.1.0/metal-cpp/Metal/MTLDepthStencil.hpp +277 -0
  99. mtlpy-0.1.0/metal-cpp/Metal/MTLDevice.hpp +1512 -0
  100. mtlpy-0.1.0/metal-cpp/Metal/MTLDrawable.hpp +90 -0
  101. mtlpy-0.1.0/metal-cpp/Metal/MTLDynamicLibrary.hpp +78 -0
  102. mtlpy-0.1.0/metal-cpp/Metal/MTLEvent.hpp +170 -0
  103. mtlpy-0.1.0/metal-cpp/Metal/MTLFence.hpp +55 -0
  104. mtlpy-0.1.0/metal-cpp/Metal/MTLFunctionConstantValues.hpp +76 -0
  105. mtlpy-0.1.0/metal-cpp/Metal/MTLFunctionDescriptor.hpp +153 -0
  106. mtlpy-0.1.0/metal-cpp/Metal/MTLFunctionHandle.hpp +65 -0
  107. mtlpy-0.1.0/metal-cpp/Metal/MTLFunctionLog.hpp +101 -0
  108. mtlpy-0.1.0/metal-cpp/Metal/MTLFunctionStitching.hpp +319 -0
  109. mtlpy-0.1.0/metal-cpp/Metal/MTLGPUAddress.hpp +36 -0
  110. mtlpy-0.1.0/metal-cpp/Metal/MTLHeaderBridge.hpp +3161 -0
  111. mtlpy-0.1.0/metal-cpp/Metal/MTLHeap.hpp +318 -0
  112. mtlpy-0.1.0/metal-cpp/Metal/MTLIOCommandBuffer.hpp +182 -0
  113. mtlpy-0.1.0/metal-cpp/Metal/MTLIOCommandQueue.hpp +211 -0
  114. mtlpy-0.1.0/metal-cpp/Metal/MTLIOCompressor.hpp +94 -0
  115. mtlpy-0.1.0/metal-cpp/Metal/MTLIndirectCommandBuffer.hpp +376 -0
  116. mtlpy-0.1.0/metal-cpp/Metal/MTLIndirectCommandEncoder.hpp +273 -0
  117. mtlpy-0.1.0/metal-cpp/Metal/MTLIntersectionFunctionTable.hpp +173 -0
  118. mtlpy-0.1.0/metal-cpp/Metal/MTLLibrary.hpp +813 -0
  119. mtlpy-0.1.0/metal-cpp/Metal/MTLLinkedFunctions.hpp +110 -0
  120. mtlpy-0.1.0/metal-cpp/Metal/MTLLogState.hpp +111 -0
  121. mtlpy-0.1.0/metal-cpp/Metal/MTLParallelRenderCommandEncoder.hpp +83 -0
  122. mtlpy-0.1.0/metal-cpp/Metal/MTLPipeline.hpp +104 -0
  123. mtlpy-0.1.0/metal-cpp/Metal/MTLPixelFormat.hpp +173 -0
  124. mtlpy-0.1.0/metal-cpp/Metal/MTLPrivate.hpp +158 -0
  125. mtlpy-0.1.0/metal-cpp/Metal/MTLRasterizationRate.hpp +337 -0
  126. mtlpy-0.1.0/metal-cpp/Metal/MTLRenderCommandEncoder.hpp +1022 -0
  127. mtlpy-0.1.0/metal-cpp/Metal/MTLRenderPass.hpp +792 -0
  128. mtlpy-0.1.0/metal-cpp/Metal/MTLRenderPipeline.hpp +1876 -0
  129. mtlpy-0.1.0/metal-cpp/Metal/MTLResidencySet.hpp +178 -0
  130. mtlpy-0.1.0/metal-cpp/Metal/MTLResource.hpp +190 -0
  131. mtlpy-0.1.0/metal-cpp/Metal/MTLResourceStateCommandEncoder.hpp +98 -0
  132. mtlpy-0.1.0/metal-cpp/Metal/MTLResourceStatePass.hpp +154 -0
  133. mtlpy-0.1.0/metal-cpp/Metal/MTLResourceViewPool.hpp +118 -0
  134. mtlpy-0.1.0/metal-cpp/Metal/MTLSampler.hpp +345 -0
  135. mtlpy-0.1.0/metal-cpp/Metal/MTLStageInputOutputDescriptor.hpp +356 -0
  136. mtlpy-0.1.0/metal-cpp/Metal/MTLTensor.hpp +511 -0
  137. mtlpy-0.1.0/metal-cpp/Metal/MTLTexture.hpp +803 -0
  138. mtlpy-0.1.0/metal-cpp/Metal/MTLTextureViewPool.hpp +59 -0
  139. mtlpy-0.1.0/metal-cpp/Metal/MTLTypes.hpp +164 -0
  140. mtlpy-0.1.0/metal-cpp/Metal/MTLVersion.hpp +32 -0
  141. mtlpy-0.1.0/metal-cpp/Metal/MTLVertexDescriptor.hpp +326 -0
  142. mtlpy-0.1.0/metal-cpp/Metal/MTLVisibleFunctionTable.hpp +96 -0
  143. mtlpy-0.1.0/metal-cpp/Metal/Metal.hpp +120 -0
  144. mtlpy-0.1.0/metal-cpp/MetalFX/MTL4FXFrameInterpolator.hpp +47 -0
  145. mtlpy-0.1.0/metal-cpp/MetalFX/MTL4FXSpatialScaler.hpp +49 -0
  146. mtlpy-0.1.0/metal-cpp/MetalFX/MTL4FXTemporalDenoisedScaler.hpp +49 -0
  147. mtlpy-0.1.0/metal-cpp/MetalFX/MTL4FXTemporalScaler.hpp +49 -0
  148. mtlpy-0.1.0/metal-cpp/MetalFX/MTLFXDefines.hpp +41 -0
  149. mtlpy-0.1.0/metal-cpp/MetalFX/MTLFXFrameInterpolator.hpp +953 -0
  150. mtlpy-0.1.0/metal-cpp/MetalFX/MTLFXPrivate.hpp +560 -0
  151. mtlpy-0.1.0/metal-cpp/MetalFX/MTLFXSpatialScaler.hpp +397 -0
  152. mtlpy-0.1.0/metal-cpp/MetalFX/MTLFXTemporalDenoisedScaler.hpp +1217 -0
  153. mtlpy-0.1.0/metal-cpp/MetalFX/MTLFXTemporalScaler.hpp +994 -0
  154. mtlpy-0.1.0/metal-cpp/MetalFX/MetalFX.hpp +35 -0
  155. mtlpy-0.1.0/metal-cpp/QuartzCore/CADefines.hpp +41 -0
  156. mtlpy-0.1.0/metal-cpp/QuartzCore/CAMetalDrawable.hpp +57 -0
  157. mtlpy-0.1.0/metal-cpp/QuartzCore/CAMetalLayer.hpp +234 -0
  158. mtlpy-0.1.0/metal-cpp/QuartzCore/CAPrivate.hpp +154 -0
  159. mtlpy-0.1.0/metal-cpp/QuartzCore/QuartzCore.hpp +28 -0
  160. mtlpy-0.1.0/metal-cpp/README.md +318 -0
  161. mtlpy-0.1.0/metal-cpp/SingleHeader/MakeSingleHeader.py +271 -0
  162. mtlpy-0.1.0/pyproject.toml +41 -0
  163. mtlpy-0.1.0/src/mtlpy/__init__.py +11 -0
  164. mtlpy-0.1.0/src/mtlpy/buffer.py +137 -0
  165. mtlpy-0.1.0/src/mtlpy/device.py +122 -0
  166. mtlpy-0.1.0/src/mtlpy/operators.py +50 -0
  167. mtlpy-0.1.0/src/mtlpy/pipeline.py +19 -0
  168. mtlpy-0.1.0/src/mtlpy/shader.py +165 -0
  169. mtlpy-0.1.0/src/mtlpy/utils.py +40 -0
  170. mtlpy-0.1.0/tests/conftest.py +12 -0
  171. mtlpy-0.1.0/tests/test_async.py +54 -0
  172. mtlpy-0.1.0/tests/test_basic.py +140 -0
  173. mtlpy-0.1.0/tests/test_buffer_reuse.py +66 -0
  174. mtlpy-0.1.0/tests/test_operators.py +257 -0
  175. mtlpy-0.1.0/tests/test_pipeline_persistence.py +89 -0
  176. mtlpy-0.1.0/tests/test_stability.py +124 -0
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ # Validates every PR targeting main (and main itself, as a sanity check
4
+ # after merge) before the tag-release workflow can ever fire on main --
5
+ # this is what stands between a broken PR and an automatic PyPI publish.
6
+ on:
7
+ pull_request:
8
+ branches: [main]
9
+ push:
10
+ branches: [main]
11
+
12
+ env:
13
+ # Matches publish.yml, so the packaging smoke test below produces the
14
+ # same wheel tag (macosx_14_0_arm64) the real release build would.
15
+ MACOSX_DEPLOYMENT_TARGET: "14.0"
16
+
17
+ jobs:
18
+ test:
19
+ runs-on: macos-14
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ submodules: recursive
24
+
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install mtlpy (editable, with dev extras)
30
+ run: pip install -e ".[dev]"
31
+
32
+ - name: Run test suite
33
+ run: pytest tests/ -v
34
+
35
+ - name: Build sdist + wheel (packaging smoke test)
36
+ run: |
37
+ pip install build
38
+ python -m build
@@ -0,0 +1,141 @@
1
+ name: Release
2
+
3
+ # Runs on every push to main (i.e. every merged PR). If pyproject.toml's
4
+ # [project].version was bumped and isn't already tagged: tags it, creates a
5
+ # GitHub Release, builds the sdist + a wheel per supported Python version,
6
+ # and publishes all of it to PyPI -- all in one workflow run.
7
+ #
8
+ # Deliberately one workflow, not tag-then-trigger-a-second-workflow: GitHub
9
+ # doesn't let events caused by the default GITHUB_TOKEN (e.g. a release
10
+ # created by an Actions step) trigger other workflows, as an anti-recursion
11
+ # safeguard. That meant a separate "publish on release" workflow silently
12
+ # never ran. Using `needs:` to sequence jobs within a single workflow run
13
+ # has no such restriction, so this version doesn't need a PAT either.
14
+ on:
15
+ push:
16
+ branches: [main]
17
+
18
+ permissions:
19
+ contents: write
20
+
21
+ env:
22
+ # Wheels only need building on the OLDEST macOS we support (14): the
23
+ # resulting binary installs and runs on every later macOS too, since
24
+ # macOS is backward- (not forward-) compatible. Building on the floor
25
+ # OS also means its older SDK can't let us accidentally call a
26
+ # macOS-15/26-only Metal API while still claiming macOS 14 support --
27
+ # see CMakeLists.txt for the matching CMAKE_OSX_DEPLOYMENT_TARGET.
28
+ # scikit-build-core reads this env var (not the CMake cache variable)
29
+ # to decide the wheel's platform tag.
30
+ MACOSX_DEPLOYMENT_TARGET: "14.0"
31
+
32
+ jobs:
33
+ tag-release:
34
+ runs-on: ubuntu-latest
35
+ outputs:
36
+ tag: ${{ steps.version.outputs.tag }}
37
+ released: ${{ steps.check_tag.outputs.exists == 'false' }}
38
+ steps:
39
+ - uses: actions/checkout@v4
40
+ with:
41
+ fetch-depth: 0 # full history + tags, needed to check for an existing tag
42
+
43
+ - uses: actions/setup-python@v5
44
+ with:
45
+ python-version: "3.12" # need 3.11+ for stdlib tomllib below
46
+
47
+ - name: Read version from pyproject.toml
48
+ id: version
49
+ run: |
50
+ version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
51
+ echo "tag=v$version" >> "$GITHUB_OUTPUT"
52
+
53
+ - name: Check whether this version is already tagged
54
+ id: check_tag
55
+ run: |
56
+ if git rev-parse "${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then
57
+ echo "exists=true" >> "$GITHUB_OUTPUT"
58
+ else
59
+ echo "exists=false" >> "$GITHUB_OUTPUT"
60
+ fi
61
+
62
+ - name: Create tag and GitHub Release
63
+ if: steps.check_tag.outputs.exists == 'false'
64
+ env:
65
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66
+ run: |
67
+ tag="${{ steps.version.outputs.tag }}"
68
+ git tag "$tag"
69
+ git push origin "$tag"
70
+ gh release create "$tag" --title "$tag" --generate-notes
71
+
72
+ build-sdist:
73
+ needs: tag-release
74
+ if: needs.tag-release.outputs.released == 'true'
75
+ runs-on: macos-14
76
+ steps:
77
+ - uses: actions/checkout@v4
78
+ with:
79
+ submodules: recursive
80
+ ref: ${{ needs.tag-release.outputs.tag }}
81
+
82
+ - uses: actions/setup-python@v5
83
+ with:
84
+ python-version: "3.12"
85
+
86
+ - name: Build sdist
87
+ run: |
88
+ pip install build
89
+ python -m build --sdist
90
+
91
+ - uses: actions/upload-artifact@v4
92
+ with:
93
+ name: dist-sdist
94
+ path: dist/*.tar.gz
95
+
96
+ build-wheels:
97
+ needs: tag-release
98
+ if: needs.tag-release.outputs.released == 'true'
99
+ runs-on: macos-14
100
+ strategy:
101
+ matrix:
102
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
103
+ steps:
104
+ - uses: actions/checkout@v4
105
+ with:
106
+ submodules: recursive
107
+ ref: ${{ needs.tag-release.outputs.tag }}
108
+
109
+ - uses: actions/setup-python@v5
110
+ with:
111
+ python-version: ${{ matrix.python-version }}
112
+
113
+ - name: Build wheel
114
+ run: |
115
+ pip install build
116
+ python -m build --wheel
117
+
118
+ - uses: actions/upload-artifact@v4
119
+ with:
120
+ name: dist-wheel-cp${{ matrix.python-version }}
121
+ path: dist/*.whl
122
+
123
+ publish:
124
+ needs: [build-sdist, build-wheels]
125
+ runs-on: ubuntu-latest
126
+ environment: pypi # optional extra gate: add required reviewers to this
127
+ # GitHub Environment (Settings -> Environments) if you
128
+ # want a manual approval click before every publish.
129
+ permissions:
130
+ id-token: write # required for PyPI Trusted Publishing
131
+ steps:
132
+ - uses: actions/download-artifact@v4
133
+ with:
134
+ path: dist
135
+ pattern: "dist-*"
136
+ merge-multiple: true
137
+
138
+ - name: Publish to PyPI
139
+ uses: pypa/gh-action-pypi-publish@release/v1
140
+ with:
141
+ packages-dir: dist/
mtlpy-0.1.0/.gitignore ADDED
@@ -0,0 +1,32 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ venv/
9
+ *.egg
10
+ .eggs/
11
+
12
+ # C++ build artifacts
13
+ *.o
14
+ *.so
15
+ *.dylib
16
+ CMakeCache.txt
17
+ CMakeFiles/
18
+ cmake_install.cmake
19
+ Makefile
20
+ _skbuild/
21
+
22
+ # IDE
23
+ .vscode/
24
+ .idea/
25
+ *.swp
26
+
27
+ # Test artifacts
28
+ .pytest_cache/
29
+ .coverage
30
+
31
+ # Benchmark output (local baselines, not source)
32
+ benchmarks/results/
@@ -0,0 +1,3 @@
1
+ [submodule "metal-cpp"]
2
+ path = metal-cpp
3
+ url = https://github.com/apple/metal-cpp
@@ -0,0 +1,37 @@
1
+ cmake_minimum_required(VERSION 3.15)
2
+
3
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "Minimum macOS version")
4
+
5
+ project(mtlpy LANGUAGES CXX OBJCXX)
6
+
7
+ set(CMAKE_CXX_STANDARD 17)
8
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
9
+ set(CMAKE_OBJCXX_STANDARD 17)
10
+ set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
11
+
12
+ find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
13
+ find_package(pybind11 CONFIG REQUIRED)
14
+
15
+ set(METAL_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/metal-cpp)
16
+
17
+ pybind11_add_module(_mtlpy
18
+ csrc/metal_impl.mm
19
+ csrc/device.cpp
20
+ csrc/pipeline_cache.cpp
21
+ csrc/buffer.cpp
22
+ csrc/pipeline.cpp
23
+ csrc/bindings.cpp
24
+ )
25
+
26
+ target_include_directories(_mtlpy PRIVATE
27
+ ${METAL_CPP_DIR}
28
+ ${CMAKE_CURRENT_SOURCE_DIR}/csrc
29
+ )
30
+
31
+ target_link_libraries(_mtlpy PRIVATE
32
+ "-framework Metal"
33
+ "-framework Foundation"
34
+ "-framework QuartzCore"
35
+ )
36
+
37
+ install(TARGETS _mtlpy LIBRARY DESTINATION mtlpy)
mtlpy-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peyton Howe
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.
mtlpy-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,295 @@
1
+ Metadata-Version: 2.4
2
+ Name: mtlpy
3
+ Version: 0.1.0
4
+ Summary: Python bindings for Apple Metal GPU compute
5
+ Keywords: metal,gpu,compute,apple,macos,mtl
6
+ Author: Peyton Howe
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Programming Language :: C++
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Project-URL: Homepage, https://github.com/peyton-howe/mtlpy
22
+ Project-URL: Repository, https://github.com/peyton-howe/mtlpy
23
+ Project-URL: Issues, https://github.com/peyton-howe/mtlpy/issues
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: numpy>=1.20
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0; extra == "dev"
28
+ Description-Content-Type: text/markdown
29
+
30
+ # mtlpy
31
+
32
+ Python bindings for GPU compute on Apple Metal, built on [pybind11](https://github.com/pybind/pybind11)
33
+ and Apple's [metal-cpp](https://developer.apple.com/metal/cpp/). Write a Metal
34
+ compute kernel as a string, dispatch it over NumPy arrays, get the result back
35
+ as a NumPy array — no separate build step, no manual buffer plumbing.
36
+
37
+ ```python
38
+ import numpy as np
39
+ import mtlpy
40
+
41
+ device = mtlpy.Device()
42
+ a = device.buffer(np.array([1.0, 2.0, 3.0], dtype=np.float32))
43
+ b = device.buffer(np.array([10.0, 20.0, 30.0], dtype=np.float32))
44
+
45
+ print((a + b).contents) # [11. 22. 33.]
46
+ ```
47
+
48
+ ## Why this exists
49
+
50
+ mtlpy is a from-scratch rewrite of the`metalgpu`
51
+ project. Rather than build on top of that codebase's ctypes-based bindings
52
+ and global singleton state, mtlpy starts over with a few deliberate
53
+ improvements:
54
+
55
+ - **pybind11 instead of ctypes** — real type safety across the Python/C++
56
+ boundary, and Metal errors propagate as Python exceptions instead of
57
+ silent failures.
58
+ - **No global singleton state** — each `Device` owns its own command queue
59
+ and pipeline cache; nothing is saved/restored behind your back.
60
+ - **Pipeline compile caching** — a compute pipeline is compiled once per
61
+ (shader source, function name) and reused, both within a process and
62
+ (via an on-disk Metal binary archive) across process launches.
63
+ - **Async dispatch** — `Pipeline.run(..., wait=False)` lets you batch work
64
+ without stalling on every call.
65
+
66
+ ## Status
67
+
68
+ Alpha, but built, tested, and benchmarked on real Apple Silicon hardware —
69
+ see [Building from source](#building-from-source) and the test suite for
70
+ current coverage.
71
+
72
+ ## Architecture
73
+
74
+ ```
75
+ metal-cpp/ Apple's C++ Metal headers (git submodule)
76
+ csrc/ C++ extension (pybind11 + metal-cpp)
77
+ device.{h,cpp} MTL::Device + MTL::CommandQueue owner
78
+ buffer.{h,cpp} MTL::Buffer wrapper (shared-storage, CPU/GPU unified memory)
79
+ pipeline.{h,cpp} Dispatches a compiled MTL::ComputePipelineState
80
+ pipeline_cache.{h,cpp} Compiles-once cache, keyed on (source, function name),
81
+ backed by an on-disk MTL::BinaryArchive
82
+ metal_impl.mm Single Obj-C++ translation unit providing the
83
+ NS::/CA::/MTL:: private implementations
84
+ bindings.cpp pybind11 module definition (`_mtlpy`)
85
+ src/mtlpy/ Python package (src layout, for PyPI)
86
+ device.py Device: buffer/empty/compile, list_devices(), wraps _mtlpy.Device
87
+ buffer.py Buffer: NumPy-backed contents, arithmetic/comparison/in-place operators
88
+ pipeline.py Pipeline: thin wrapper over _mtlpy.Pipeline
89
+ operators.py sqrt/cos/sin/tan/exp/log, sum/max/min/mean reductions
90
+ shader.py Generates Metal Shading Language source per dtype
91
+ utils.py NumPy dtype <-> Metal type name mapping
92
+ tests/ pytest suite
93
+ benchmarks/ Standalone performance baseline scripts
94
+ examples/ Runnable usage examples
95
+ ```
96
+
97
+ Each `Device` in Python owns exactly one `MTL::Device`, one `MTL::CommandQueue`,
98
+ and one `PipelineCache`. Buffers use `MTL::ResourceStorageModeShared`, so on
99
+ Apple Silicon's unified memory there's no copy between CPU and GPU views of
100
+ the same allocation — `Buffer.contents` is a NumPy array backed directly by
101
+ GPU-visible memory (accessing `.contents` is a true zero-copy view; writing
102
+ new data into it via `buf.contents[:] = arr` is still a real memcpy from
103
+ `arr`'s own memory, same as it would be for any destination).
104
+
105
+ ## Features
106
+
107
+ - **Elementwise operators**: `+`, `-`, `*`, `/`, unary `-`, and in-place
108
+ `+=`/`-=`/`*=`/`/=` (which dispatch in-place, into the same `Buffer`, with
109
+ no extra allocation) on `Buffer` — each also works with a NumPy/Python
110
+ scalar on either side (`buf + 5.0`, `5.0 - buf`), not just `Buffer op
111
+ Buffer`. Plus `sqrt`, `cos`, `sin`, `tan`, `exp`, `log`, and `astype` for
112
+ dtype conversion.
113
+ - **Comparisons**: `==`, `!=`, `<`, `<=`, `>`, `>=` (against another `Buffer`
114
+ or a scalar) return a `bool` `Buffer`, matching NumPy's `ndarray`
115
+ convention — which also makes `Buffer` unhashable, same tradeoff NumPy
116
+ makes.
117
+ - **Reductions**: `operators.sum`/`max`/`min`/`mean` — an O(log n) multi-pass
118
+ tree reduction returning a plain Python scalar.
119
+ - **Custom kernels**: compile and dispatch arbitrary Metal Shading Language
120
+ source directly (see [Custom kernels](#custom-kernels) below).
121
+ `Pipeline.run` validates the buffer count against the kernel's own
122
+ argument reflection, so passing too few buffers raises a clear Python
123
+ exception instead of leaving a Metal buffer argument unbound (undefined
124
+ behavior).
125
+ - **Dtype support**: `float32`, `float16`, `int32`, `uint32`,
126
+ `int16`, `uint16`, `int64`, `uint64`, `bool` — mapped to their Metal
127
+ equivalents (`float`, `half`, `int`, `uint`, `short`, `ushort`,
128
+ `long`, `ulong`, `bool`) in `src/mtlpy/utils.py`. `float64` has no Metal
129
+ equivalent (no Apple GPU supports double precision), so it's silently
130
+ downcast to `float32` at buffer creation. Note that `Buffer / Buffer` uses
131
+ Metal's native `/` for the shared dtype (truncating for integers), not
132
+ NumPy's always-promote-to-float64 semantics.
133
+ - **Pipeline caching**: identical (source, function name) pairs are compiled
134
+ once per process and reused; a binary archive on disk
135
+ (`~/Library/Caches/mtlpy/pipelines.metallib`) carries compiled pipelines
136
+ across process launches too. `Device.flush_cache()` (or using `Device` as
137
+ a context manager: `with mtlpy.Device() as d:`) serializes it on demand,
138
+ rather than only when the `Device` is garbage collected.
139
+ - **Async dispatch**: `wait=False` commits work without blocking; Metal
140
+ retires command buffers on a queue in commit order, so a later `wait=True`
141
+ dispatch that reads the result is enough to synchronize (see
142
+ `examples/async_dispatch.py`). `Pipeline.run` releases the GIL for the
143
+ whole call, so other Python threads keep running during the GPU wait
144
+ instead of being blocked for its full duration.
145
+ - **Multi-GPU support**: `mtlpy.list_devices()` lists every Metal-capable GPU
146
+ on the machine; `mtlpy.Device(index=...)` selects one (the default targets
147
+ the system default GPU).
148
+ - **Errors as exceptions**: shader compile failures, missing kernel
149
+ functions, mismatched buffer counts, mismatched-`Device` operands, and GPU
150
+ execution errors all raise Python exceptions with a clear message, instead
151
+ of failing silently or invoking undefined behavior.
152
+
153
+ ## Building from source
154
+
155
+ Requires macOS with Metal support, Xcode (for the Metal/Objective-C++
156
+ toolchain), CMake, and Python 3.9+.
157
+
158
+ ```bash
159
+ git clone --recursive git@github.com:peyton-howe/mtlpy.git
160
+ cd mtlpy
161
+ pip install -e ".[dev]"
162
+ ```
163
+
164
+ If you already cloned without `--recursive`:
165
+
166
+ ```bash
167
+ git submodule update --init
168
+ ```
169
+
170
+ ## Quick start
171
+
172
+ ```python
173
+ import numpy as np
174
+ import mtlpy
175
+
176
+ device = mtlpy.Device()
177
+
178
+ a = device.buffer(np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32))
179
+ b = device.buffer(np.array([10.0, 20.0, 30.0, 40.0], dtype=np.float32))
180
+
181
+ c = a + b
182
+ print(c.contents) # numpy.ndarray([11. 22. 33. 44.])
183
+
184
+ d = mtlpy.operators.sqrt(a)
185
+ print(d.contents)
186
+
187
+ e = a.astype(np.int32)
188
+ print(e.dtype, e.contents)
189
+ ```
190
+
191
+ ## Custom kernels
192
+
193
+ `Device.compile(source, function_name)` compiles arbitrary Metal Shading
194
+ Language and returns a `Pipeline` you can dispatch directly:
195
+
196
+ ```python
197
+ source = """
198
+ #include <metal_stdlib>
199
+ using namespace metal;
200
+ kernel void square(
201
+ device const float *a [[buffer(0)]],
202
+ device float *b [[buffer(1)]],
203
+ uint id [[thread_position_in_grid]])
204
+ {
205
+ b[id] = a[id] * a[id];
206
+ }
207
+ """
208
+ pipeline = device.compile(source, "square")
209
+
210
+ a = device.buffer(np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32))
211
+ b = device.empty(4, np.float32)
212
+ pipeline.run([a, b], grid=4)
213
+
214
+ print(b.contents) # [1. 4. 9. 16.]
215
+ ```
216
+
217
+ `grid` may be an int (1D dispatch) or a 3-tuple/list for 2D/3D dispatch.
218
+ Threadgroup sizing is computed automatically from the pipeline's
219
+ `thread_execution_width` and `max_threads_per_threadgroup`.
220
+
221
+ ## Reusing buffers in a hot loop
222
+
223
+ `Buffer.contents` is a live NumPy view over the same underlying Metal
224
+ allocation, not a copy — writing `buf.contents[:] = ...` updates GPU-visible
225
+ memory in place, and reading it back after a `wait=True` dispatch needs no
226
+ reallocation either. For a kernel dispatched repeatedly (e.g. in a `while`
227
+ loop), compile the pipeline and allocate buffers once, then just write/read
228
+ `.contents` each iteration:
229
+
230
+ ```python
231
+ pipeline = device.compile(source, "square")
232
+
233
+ a = device.buffer(np.zeros(4, dtype=np.float32)) # allocated once
234
+ out = device.empty(4, np.float32) # allocated once
235
+
236
+ while running:
237
+ a.contents[:] = get_next_input() # in-place write, no realloc
238
+ pipeline.run([a, out], grid=4) # wait=True by default
239
+ consume(out.contents) # in-place read, no realloc
240
+ ```
241
+
242
+ The out-of-place convenience operators (`a + b`, `operators.sqrt(a)`,
243
+ `astype`, etc.) don't follow this pattern — each call allocates a fresh
244
+ output `Buffer` internally, which is fine for one-off use but wasteful in a
245
+ tight loop. The in-place operators (`a += b`, `a *= 2.0`, ...) do reuse `a`'s
246
+ own buffer with no extra allocation, if that fits your loop. See
247
+ `examples/reuse_buffers.py`.
248
+
249
+ ## Testing
250
+
251
+ ```bash
252
+ pytest tests/
253
+ ```
254
+
255
+ - `test_basic.py` / `test_operators.py` — correctness for every operator
256
+ (arithmetic, scalar broadcasting, comparisons, in-place, reductions),
257
+ dtype, and `astype` conversion, plus error handling for mismatched buffer
258
+ sizes/dtypes/devices and wrong kernel argument counts.
259
+ - `test_async.py` — `wait=False` dispatch ordering.
260
+ - `test_buffer_reuse.py` — in-place `.contents` writes and repeated dispatch
261
+ against the same buffers, without reallocation.
262
+ - `test_stability.py` — repeated-dispatch and object-lifetime stress tests
263
+ (regression coverage for the Metal object-ownership rules in `csrc/`),
264
+ plus multi-threaded dispatch/compilation tests (`Pipeline.run` releases
265
+ the GIL, so this exercises genuinely concurrent Metal calls).
266
+ - `test_pipeline_persistence.py` — spawns separate processes to verify the
267
+ on-disk pipeline binary archive is actually written and read back, and
268
+ that `Device.flush_cache()` writes it on demand.
269
+
270
+ ## Benchmarking
271
+
272
+ ```bash
273
+ python benchmarks/bench.py
274
+ ```
275
+
276
+ Measures first-dispatch (compile-included) and steady-state warm-dispatch
277
+ latency/throughput for every operator across a range of buffer sizes, with
278
+ NumPy CPU timings alongside for context. Each run is saved as JSON
279
+ (timestamped, tagged with the git commit) under `benchmarks/results/` so you
280
+ can baseline future changes:
281
+
282
+ ```bash
283
+ python benchmarks/bench.py --baseline benchmarks/results/<earlier-run>.json
284
+ ```
285
+
286
+ `benchmarks/demosaic_bench.py` is a separate, more involved benchmark
287
+ comparing the edge-aware Bayer demosaicing kernel
288
+ (`benchmarks/bayer2rgb_ea_kernel.txt`) against OpenCV's own
289
+ `COLOR_Bayer*2BGR_EA` (requires `pip install -e ".[bench]"`), covering both
290
+ single-shot dispatch latency and realistic streaming throughput (a rotating
291
+ buffer pool pipelining dispatches instead of waiting on every frame).
292
+
293
+ ## License
294
+
295
+ MIT