NOTE: The Jupyter Notebook below is included in the Chimera SDK and can be run interactively by running the following CLI command:
$ quadric sdk notebook
From the Jupyter Notebook window in your browser, select the notebook named /quadric/sdk-cli/examples/custom_op/voxel_pooling/voxel_pooling.ipynb.
Tutorial: Voxel Pooling
Voxel Pooling is an operator commonly used in neural networks like BEVDepth. Due to its highly parallel data processing nature, Voxel Pooling often lacks a native PyTorch implementation and is typically accelerated on GPUs. You can see an example of this in the following code: Voxel Pooling CUDA Implementation.
In this notebook, we’ll walk through a simple neural network that utilizes Voxel Pooling. We will:
- Demonstrate how Voxel Pooling operates within the network.
- Replace the standard operator with a Custom Operator using our Quadric Custom Operator tooling.
- Compile and profile the performance of the new operator.
To achieve this, we first implemented Voxel Pooling as a CCL (Custom Compute Language) operator.
CCL Implementation Details
We harnessed the parallel processing capabilities of the Quadric Chimera GPNPU to translate the original CUDA code into an efficient CCL implementation.

For more in-depth implementation details, refer to the techincal documentation here.
The Voxel Pooling Graph
The Voxel Pooling graph can be seen below

To export a graph that includes custom operations, modify your PyTorch graph by registering a symbolic operator. The example below demonstrates how to define a custom autograd function with symbolic registration for a voxel pooling operation. This network shown below is for demonstration purposes only.
## Define a custom autograd function for VoxelPooling.
class VoxelPoolingFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, img_features, geom):
# Dummy implementation: simply add the two inputs.
# Replace this with your voxel pooling logic.
return img_features + geom
@staticmethod
def symbolic(g, img_features, geom):
# This tells the ONNX exporter to create a custom op.
# The operator name here is "VoxelPooling" under the domain "mydomain".
return g.op("mydomain::VoxelPooling", img_features, geom)
## Wrap the custom function in a module.
class VoxelPoolingModule(nn.Module):
def forward(self, img_features, geom):
return VoxelPoolingFunction.apply(img_features, geom)
Steps
1. Replace Voxel Pooling with a Custom Operator
We can then utilize our Custom Operations library to transform this operator and replace it with our nn::voxelPooling operator we've created in CCL
from tvm.contrib.epu.graphutils import FilterNode, CustomOpReplacer
import onnx
import onnxoptimizer
inp = CustomOpReplacer.FilterWildcard
voxel_pooling = FilterNode("VoxelPooling", [inp, inp])
util = CustomOpReplacer(onnx.load("voxel_original.onnx"))
custom_op_onnx_model = util.replace_all(
voxel_pooling,
"nn::voxelPooling",
False,
allow_io_in_l2_mem=True,
reserved_ocm=1024 << 10,
)
optimized_model = onnxoptimizer.optimize(custom_op_onnx_model, ["eliminate_identity"])
onnx.save(optimized_model, "voxel_opt.onnx")
WARNING:root:ONNX node does not have a name, deriving temp name from its outputs. Node was named to: output
WARNING:root:ONNX node does not have a name, deriving temp name from its outputs. Node was named to: scale
WARNING:root:ONNX node does not have a name, deriving temp name from its outputs. Node was named to: zero_point
2. Inspect the Custom Operation
After applying our Custom Operator, the computational graph should now resemble the image below:

3. Compile and Profile the Voxel Pooling Operator Using ChimeraJob with Real Inputs
Next, we’ll compile and profile the operator using ChimeraJob. We’ll also use real input data since the algorithm’s performance is data-dependent.
from tvm.contrib.epu.chimera_job.chimera_job import ChimeraJob
import numpy as np
cgc_job = ChimeraJob(
"voxel_opt.onnx",
region_timeout=3600, # In case of slower CPU.
)
data = np.load("voxel_pooling_inputs.npy", allow_pickle=True).item()
geom_xyz_reshaped = data["geom_xyz"].reshape(1, 1, 473088, 3) # Reshape Data To 4D
img_feat_reshaped = data["img_feat_with_depth"].reshape(1, 1, 473088, 80) # Reshape Data To 4D
cgc_job.run_inference_harness(
compare_ort=False,
inputs={"geom_xyz": geom_xyz_reshaped, "img_feat_with_depth": img_feat_reshaped},
)
cgc_job.plot_run_statistics()
print(cgc_job)
/usr/local/lib/python3.10/dist-packages/tvm/relay/frontend/onnx.py:6270: UserWarning: No Op registered for VoxelPooling with domain_version of 16
==> Context: Bad node spec for node. Name: CustomOp/nn::voxelPooling0 OpType: QuadricCustomOp
warnings.warn(str(e))
2026-07-18 12:09 - INFO - epu - iss_testing - No tranges found for input, use default float range: <tvm.contrib.epu.interval.Interval object at 0x7beee2bd25f0>
2026-07-18 12:09 - INFO - epu - iss_testing - No tranges found for input, use default float range: <tvm.contrib.epu.interval.Interval object at 0x7beee2bd25f0>
FILM 3/3: 100%|██████████████████████████████████████████████████████| 3/3 [18:04<00:00, 361.49s/it]
2026-07-18 12:28 - INFO - epu - chimera_job - Combined plots generated and saved to:
/quadric/sdk-cli/examples/custom_op/voxel_pooling/ccl_build/voxel_opt_QC_U_1d7_16MB_4kB_128GBps_128GBps_16_OFF_x1_x1/run/20260718_120929_cf37a7/data/voxel_opt_QC_U_1d7_16MB_4kB_128GBps_128GBps_16_OFF_x1_x1.combined.png
╒═════════════════════╤══════════════════════════════════════════════════════════╕
│ Module Name │ voxel_opt_QC_U_1d7_16MB_4kB_128GBps_128GBps_16_OFF_x1_x1 │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ ONNX File │ voxel_opt.onnx │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Product Target │ QC-U │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Number of Cores │ 1 │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ ISS Clock Frequency │ 1.700 │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ L2M Size │ 16MB │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ LRM Size │ 4kB │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ External Read BW │ 128GBps │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ External Write BW │ 128GBps │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ MACS per PE │ 16 │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Max L2M │ 6.250MB │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Max LRM │ 0.391kB │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Max Temp Ext Bytes │ 37.344MB │
├─────────────────────┼──────────────────────────────────────────────────────────┤
│ Network GMACs │ │
╘═════════════════════╧══════════════════════════════════════════════════════════╛
╒════╤════════╤═════════════════════╤════════════════════╤══════════════════════════╤═══════╕
│ │ Type │ Name │ shape │ type │ mse │
╞════╪════════╪═════════════════════╪════════════════════╪══════════════════════════╪═══════╡
│ 0 │ Input │ img_feat_with_depth │ [1, 1, 473088, 80] │ tensor[FixedPoint32<27>] │ n/a │
├────┼────────┼─────────────────────┼────────────────────┼──────────────────────────┼───────┤
│ 1 │ Input │ geom_xyz │ [1, 1, 473088, 3] │ tensor[int32] │ n/a │
├────┼────────┼─────────────────────┼────────────────────┼──────────────────────────┼───────┤
│ 2 │ Output │ output │ [1, 80, 128, 128] │ tensor[int32] │ n/a │
╘════╧════════╧═════════════════════╧════════════════════╧══════════════════════════╧═══════╛
Post-ISS Report 1.7 GHz ***
Fully placed-and-routed gate simulation:
╒══════════════════════════════════╤═════════╕
│ Latency (ms) │ 39.7 │
├──────────────────────────────────┼─────────┤
│ FPS │ 25.19 │
├──────────────────────────────────┼─────────┤
│ Average Power @ 3nm SSGNP (mW) │ 2187.12 │
├──────────────────────────────────┼─────────┤
│ FPS per Watt @ 3nm SSGNP (FPS/W) │ 11.52 │
├──────────────────────────────────┼─────────┤
│ Ext Rd Bytes (MB) │ 216.01 │
├──────────────────────────────────┼─────────┤
│ Ext Wr Bytes (MB) │ 42.34 │
├──────────────────────────────────┼─────────┤
│ Avg Ext Rd BW (GBps) │ 5.31 │
├──────────────────────────────────┼─────────┤
│ Avg Ext Wr BW (GBps) │ 1.04 │
╘══════════════════════════════════╧═════════╛
*** Data generated using 7nm SSGNP gatesim and scaled to 3nm
[SDK-CLI] : TotalCycles: 67,484,896
[SDK-CLI] : Executions/second: 25.19
compute : ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 36.702M
data_array : ▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 10.303M
mac : 0
data_external: ▏ 105.71K
data_ocm : ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 20.371M
for more information check run directory: /quadric/sdk-cli/examples/custom_op/voxel_pooling/ccl_build/voxel_opt_QC_U_1d7_16MB_4kB_128GBps_128GBps_16_OFF_x1_x1/run/20260718_120929_cf37a7

Note how FILM Region 2/3 takes ~60M cycles, this region represents the nn::voxelPooling operator as seen in the CCL code generated by the compiler below
cgc::profileStart("region: 2 / 3");
free_core_memory(cgc_core_stack_pointer, 800);
DdrTensor<int8_t, 1, 80, 128, 128> ext_tensor_1(pExtTempsPtr + 37847040);
nn::voxelPooling(input_geom_xyz, ext_tensor_0, ext_tensor_1, mem_allocator) /* CustomOp/nn::voxelPooling0 */ ;
cgc::profileStop("region: 2 / 3");