Customizing a TensorFlow operation
At a glance
| Item |
Summary |
| Purpose |
Implement a custom operation that uses Metal kernels to accelerate neural-network training performance. |
| App architecture |
C++, Python host code with Metal shaders; the Python HashEncoder layer invokes a registered TensorFlow operation whose C++ kernel dispatches the Metal hash-encoding functions. |
| Main patterns |
Custom operator bridge, Framework registration, Language boundary |
| Scope |
High-level review of 7 scanned source files and 10 detected declarations; build assets are omitted. |
Project structure
Source bundle/
├── hash_encoder/
│ ├── hash_encoder_kernel.cc
│ ├── hash_encoder_kernel.metal
│ ├── hash_encoder.py
│ └── mtl_hash_encoder_kernel.cc
├── tiny_nerf_hash.py
├── tiny_nerf_mlp.py
└── render_utils.py
Structure observations
- The entry/composition boundary and renderer or operation boundary are separate in the source;
C++ TensorFlow op is the principal feature coordinator.
- GPU-specific logic stays in Metal shader files; shared headers bridge host/shader layouts where present.
- The tree above is intentionally pruned to composition, resource, and shader files.
Overall architecture
flowchart LR
N1["Python layer/model"]
N2["C++ TensorFlow op"]
N3["Python layer and registered C++ custom operation"]
N4["Metal hash-encoding kernel"]
N1 --> N2
N2 --> N3
N3 --> N4
Reference code
hash_encoder/hash_encoder_kernel.cc:13 — feature handoff or setup anchor
REGISTER_OP("HashEncode")
.Attr("T: {float}")
// The input coordinates.
.Input("inputs: float")
// The NGP embedding buffer.
.Input("embeddings: T")
// The hashmap offsets of all levels.
.Input("hashmap_offsets: int32")
Interpretation
The Python layer crosses TensorFlow’s custom-op ABI. C++ owns operation/kernel registration and the native dispatch path; the .metal file owns the parallel forward and backward algorithms.
Ownership and state
classDiagram
NGP *-- HashEncoder : enc
Ownership evidence
tiny_nerf_hash.py:313 — NGP creates and stores enc
self.enc = HashEncoder(n_dim=ngp_n_dim, n_levels=ngp_levels, n_feature=ngp_feature,
resolution_coarsest=32, log2_hashmap_size=19, resolution_finest=256)
# The output feature length.
ngp_output_channel = ngp_feature * ngp_levels
# The geometry feature vector length for the color net.
geometry_feat = 3
| Owner |
Object or state |
Relationship |
Mutation authority |
NGP |
enc / HashEncoder |
Creates and stores; the diagram uses composition because construction is source-visible. |
The declaring scope performs setup and replacement. |
C++ TensorFlow 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 |
HashEncoder |
encapsulates the feature operation and its framework/GPU resources. |
keras.Model |
hash_encoder/hash_encoder.py:70 |
_hash_encode |
binds the forward and backward custom operations through TensorFlow’s custom-gradient wrapper. |
concrete Metal/framework collaborators |
hash_encoder/hash_encoder.py:41 |
HashEncodeOp |
supplies the registered, intentionally unimplemented CPU forward-kernel fallback. |
OpKernel |
hash_encoder/hash_encoder_kernel.cc:67 |
HashEncodeGradOp |
supplies the registered, intentionally unimplemented CPU gradient-kernel fallback. |
OpKernel |
hash_encoder/hash_encoder_kernel.cc:80 |
KernelLibrarySingleton |
lazily loads and shares the compiled Metal kernel library. |
concrete Metal/framework collaborators |
hash_encoder/mtl_hash_encoder_kernel.cc:29 |
InitPlugin |
registers the forward and gradient GPU kernels when the plug-in library loads. |
concrete Metal/framework collaborators |
hash_encoder/mtl_hash_encoder_kernel.cc:484 |
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 |
hash_encoder_kernel C++ members |
C++ access specifier |
public/protected/private defines member reachability. |
Keep the usable surface no wider than the collaboration requires. (hash_encoder/hash_encoder_kernel.cc:68) |
hash_encoder_kernel entry points |
Metal library boundary |
host code resolves named shader entry points; Swift access modifiers do not apply. |
Expose only named shader entry points needed by pipeline creation. (hash_encoder/hash_encoder_kernel.metal:184) |
hash_encoder |
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. (hash_encoder/hash_encoder.py:1) |
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 |
| Encapsulates the feature operation and its framework/GPU resources |
HashEncoder — hash_encoder/hash_encoder.py:70 |
The type’s callbacks and stored state align with this responsibility. |
| Python layer and registered C++ custom operation |
C++ TensorFlow op / hash_encoder/hash_encoder_kernel.cc:13 |
Keeps Metal/framework setup and encoding out of entry or lifecycle code. |
| Metal hash-encoding kernel |
hash_encoder/hash_encoder_kernel.metal:184 |
GPU-parallel code remains in the Metal compilation boundary. |
Shader boundary reference
hash_encoder/hash_encoder_kernel.metal:184 — representative GPU entry/helper
kernel void HashEncodeForward(
// ...
uint3 tid [[thread_position_in_grid]]) {
// ...
}
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Custom operator bridge |
hash_encoder/hash_encoder_kernel.cc:13 |
Makes the sample’s Python layer and registered C++ custom operation an explicit, reviewable boundary. |
| Framework registration |
hash_encoder/hash_encoder_kernel.cc:13 |
Makes the sample’s Python layer and registered C++ custom operation an explicit, reviewable boundary. |
| Language boundary |
hash_encoder/hash_encoder_kernel.cc:13 |
Python-facing APIs isolate compiled native/GPU implementation details. |
Naming conventions
- Role suffixes make ownership visible:
HashEncoder.
- Method names describe setup or encoding actions:
forward, forward_with_tensors_only, grad, __init__, call, OK, queue.
- Shader entry points use stage/operation names:
HashEncodeForward, HashEncodeBackward.
- Names favor concrete domain roles and target-local types; no broad
public library namespace is introduced.
Architecture takeaways
- Keep
Python layer/model focused on composition; C++ TensorFlow op is the owner of Python layer and registered C++ custom operation.
- Treat Metal hash-encoding kernel as a separate execution/compilation boundary with explicit resource and data-layout contracts.
- The verified ownership edge is
NGP → enc; 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 |
hash_encoder/hash_encoder_kernel.cc |
HashEncodeOp, HashEncodeGradOp |
tiny_nerf_hash.py |
NGP, TrainMonitor |
hash_encoder/hash_encoder_kernel.metal |
entry point or feature implementation |
hash_encoder/hash_encoder.py |
_hash_encode, HashEncoder |
hash_encoder/mtl_hash_encoder_kernel.cc |
KernelLibrarySingleton, InitPlugin |
tiny_nerf_mlp.py |
NeRF, TrainMonitor |
render_utils.py |
entry point or feature implementation |