Skip to content

Measuring Energy Consumption

get_chip_consumption(model, spks, dt) estimates the average power (in watts) that the network would draw on the Neuronova chip.

What drives consumption: - Spike count — more spikes mean more synaptic operations and higher power - Number of active connections — a synapse is considered active if |weight| > 0.1; only active connections contribute to power (weights below the threshold are treated as pruned). The cost per active synapse is a fixed hardware constant — weight magnitude beyond the threshold does not scale consumption. - Layer type — H1v1 and H1v2 have different hardware cost constants

spks must be a list of spike tensors (batch, timesteps, neurons), one per layer that contributes to power (i.e., layers with incoming synaptic weights).

import torch
import torch.nn as nn
from nwavesdk.layers import H1v2Synapse, H1v2Layer, prepare_net
from nwavesdk.metrics import get_chip_consumption
N_IN, N_HIDDEN, N_OUT, DT = 16, 20, 8, 1e-3

class TwoLayerNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.syn1 = H1v2Synapse(N_IN,     N_HIDDEN, device="cpu")
        self.lyr1 = H1v2Layer(N_HIDDEN, taus=10e-3, dt=DT, layer_topology="FF", device="cpu")
        self.syn2 = H1v2Synapse(N_HIDDEN, N_OUT,    device="cpu")
        self.lyr2 = H1v2Layer(N_OUT,    taus=10e-3, dt=DT, layer_topology="FF", device="cpu")

    def forward(self, x):
        # x: (batch, timesteps, n_in)
        prepare_net(self)
        spk1_list, spk2_list = [], []
        for t in range(x.shape[1]):
            cur1 = self.syn1(x[:, t, :])
            spk1, _ = self.lyr1(cur1)
            cur2 = self.syn2(spk1)
            spk2, _ = self.lyr2(cur2)
            spk1_list.append(spk1)
            spk2_list.append(spk2)
        # Return one tensor per layer: (batch, timesteps, neurons)
        return torch.stack(spk1_list, dim=1), torch.stack(spk2_list, dim=1)

model = TwoLayerNet()
model.eval()
TwoLayerNet(
  (syn1): H1v2Synapse()
  (lyr1): H1v2Layer(
    (spike_grad): FastSigmoid(slope=25.0)
  )
  (syn2): H1v2Synapse()
  (lyr2): H1v2Layer(
    (spike_grad): FastSigmoid(slope=25.0)
  )
)
# Collect spike tensors over a batch of synthetic inputs.
BATCH, T = 32, 50
x = (torch.rand(BATCH, T, N_IN) > 0.8).float()

with torch.no_grad():
    spk_hidden, spk_out = model(x)

# Pass the spike tensors for every layer that has incoming synaptic weights.
spks = [spk_hidden, spk_out]
power_W = get_chip_consumption(model, spks, dt=DT)

energy_per_inference_nJ = power_W * T * DT * 1e9  # convert J → nJ

print(f"Average power:          {power_W * 1e6:.3f} µW")
print(f"Energy per inference:   {energy_per_inference_nJ:.3f} nJ")
print(f"\nMean hidden firing rate: {spk_hidden.mean().item():.3f} spk/ts/neuron")
print(f"Mean output firing rate: {spk_out.mean().item():.3f} spk/ts/neuron")
Average power:          0.011 µW
Energy per inference:   0.549 nJ

Mean hidden firing rate: 0.077 spk/ts/neuron
Mean output firing rate: 0.064 spk/ts/neuron

Reducing consumption

Two levers control power:

1. Reduce spike count — use firing_rate_target_mse_loss to penalise high activity:

from nwavesdk.loss import firing_rate_target_mse_loss

activity_loss = firing_rate_target_mse_loss(
    spikes_list=[spk_hidden, spk_out],
    offsets=[0.05, 0.05],      # target 5% firing rate per layer
    multipliers=[1.0, 1.0],
)

2. Prune connections — synaptic weights with |w| ≤ 0.1 are treated as inactive by get_chip_consumption and contribute nothing to power. Training networks to have fewer active connections (more weights near zero) directly reduces consumption.


Further reading