Customizing a PyTorch operation
At a glance
| Item |
Summary |
| Purpose |
Implement a custom operation in PyTorch that uses Metal kernels to improve performance. |
| App architecture |
Objective-C++, Python source; the Python model invokes compiled_lib.mps_softshrink, and the Objective-C++ binding compiles embedded MSL before encoding it on PyTorch’s MPS command stream. |
| Main patterns |
Custom operator bridge, Reference-versus-custom strategy, Language boundary |
| Scope |
High-level review of 5 scanned source files and 3 detected declarations; build assets are omitted. |
Project structure
Source bundle/
├── softshrink.py
├── run_sample.py
├── CustomSoftshrink.h
├── CustomSoftshrink.mm
└── compiler.py
Structure observations
- The entry/composition boundary and renderer or operation boundary are separate in the source;
Objective-C++ custom op is the principal feature coordinator.
- GPU kernel source is embedded in a native header and compiled into a Metal library at runtime.
- The tree above is intentionally pruned to composition, resource, and shader files.
Overall architecture
flowchart LR
N1["Python model/script"]
N2["Objective-C++ custom op"]
N3["Python-to-native custom operator bridge"]
N4["runtime-compiled Metal kernel on PyTorch's MPS stream"]
N1 --> N2
N2 --> N3
N3 --> N4
Reference code
softshrink.py:27 — feature handoff or setup anchor
def forward(self, input):
return compiled_lib.mps_softshrink(input, self.lambd)
Interpretation
The Python module calls a pybind11 extension. Objective-C++ validates the tensor, compiles the embedded Metal kernel, borrows PyTorch’s MPS command buffer/queue, and encodes the compute dispatch.
Ownership and state
classDiagram
run_sample *-- CustomMPSSoftshrinkModel : custom_model
Ownership evidence
run_sample.py:24 — run_sample creates for a local scope custom_model
custom_model = CustomMPSSoftshrinkModel().to(mps_device)
# Measures time.
for _ in range(100):
start = time.time()
default_model.forward(x)
torch.mps.synchronize()
default_softshrink += time.time() - start
| Owner |
Object or state |
Relationship |
Mutation authority |
run_sample |
custom_model / CustomMPSSoftshrinkModel |
Creates for a local scope; the diagram uses composition because construction is source-visible. |
The declaring scope performs setup and replacement. |
Objective-C++ custom op |
Feature-specific framework and Metal resources |
Operation-local calls or stored state; exclusive lifetime is not assumed beyond cited evidence. |
Feature setup/encoding code controls mutation and command submission. |
Class and protocol design
| Type |
Responsibility |
Depends on or conforms to |
Source |
CustomMPSSoftshrinkModel |
represents model/data state used by the operation. |
nn.Module |
softshrink.py:33 |
SoftshrinkModel |
represents model/data state used by the operation. |
nn.Module |
softshrink.py:58 |
MPSSoftshrink |
wraps the compiled MPS soft-shrink operator as a reusable PyTorch module. |
nn.Module |
softshrink.py:18 |
No local protocol abstraction is present; the sample uses concrete framework, operation, or model types at this boundary.
Access control
| Symbol |
Access |
Verified effect |
Design reason |
CustomSoftshrink implementation details |
implementation-only |
native helpers, stored state, or registration code stays out of the imported header contract. |
Hide native implementation details from importing translation units. (CustomSoftshrink.mm:1) |
softshrink |
Python module boundary |
leading underscores are conventions; the compiled extension/registration layer defines the native export boundary. |
Keep the usable surface no wider than the collaboration requires. (softshrink.py:1) |
CUSTOM_KERNEL |
runtime-compiled Metal source |
Objective-C++ compiles the embedded MSL string into an MTLLibrary at runtime. |
Keep the embedded kernel definition beside the native dispatch bridge that compiles it. (CustomSoftshrink.h:11) |
This sample does not use Swift private, fileprivate, or public for its native boundary. Objective-C/Objective-C++ use header versus implementation placement, C++ uses access specifiers/linkage, Python uses module conventions, and Metal entry points cross a compiled-library boundary; these are not Swift access levels.
Logic ownership and placement
| Logic |
Owning type or file |
Why it lives there |
| Represents model/data state used by the operation |
CustomMPSSoftshrinkModel — softshrink.py:33 |
The type’s callbacks and stored state align with this responsibility. |
| Represents model/data state used by the operation |
SoftshrinkModel — softshrink.py:58 |
The type’s callbacks and stored state align with this responsibility. |
| Python-to-native custom operator bridge |
Objective-C++ custom op / softshrink.py:27 |
Keeps Metal/framework setup and encoding out of entry or lifecycle code. |
| Runtime-compiled Metal kernel on PyTorch’s MPS stream |
CustomSoftshrink.h:19 |
GPU-parallel code remains in the Metal compilation boundary. |
Shader boundary reference
CustomSoftshrink.h:19 — representative GPU entry/helper
kernel void softshrink_kernel(constant T* input [[buffer(0)]],
device T* output [[buffer(1)]],
constant float& lambda [[buffer(2)]],
uint index [[thread_position_in_grid]]) {
output[index] = input[index] > lambda ? input[index] - lambda :
input[index] < -lambda ? input[index] + lambda : 0;
}
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Custom operator bridge |
softshrink.py:27 |
Makes the sample’s Python-to-native custom operator bridge an explicit, reviewable boundary. |
| Reference-versus-custom strategy |
run_sample.py:23 |
Makes the sample’s Python-to-native custom operator bridge an explicit, reviewable boundary. |
| Language boundary |
softshrink.py:27 |
Python-facing APIs isolate compiled native/GPU implementation details. |
Naming conventions
- Role suffixes make ownership visible:
CustomMPSSoftshrinkModel, SoftshrinkModel.
- Method names describe setup or encoding actions:
__init__, forward, extra_repr, test_speedup, test_correctness, test_softshrink.
- Shader entry points use stage/operation names:
softshrink_kernel.
- Names favor concrete domain roles and target-local types; no broad
public library namespace is introduced.
Architecture takeaways
- Keep
Python model/script focused on composition; Objective-C++ custom op is the owner of Python-to-native custom operator bridge.
- Treat runtime-compiled Metal kernel on PyTorch’s MPS stream as a separate execution/compilation boundary with explicit resource and data-layout contracts.
- The verified ownership edge is
run_sample → custom_model; broader exclusive ownership is not inferred.
- Access-control rationale follows concrete language boundaries rather than translating every header or shader symbol into Swift terms.
Source map
| Source file |
Architectural role |
softshrink.py |
MPSSoftshrink, CustomMPSSoftshrinkModel, SoftshrinkModel |
run_sample.py |
entry point or feature implementation |
CustomSoftshrink.h |
entry point or feature implementation |
CustomSoftshrink.mm |
entry point or feature implementation |
compiler.py |
entry point or feature implementation |