Checking Deployment Readiness
is_net_deployable(model) checks whether a network satisfies all hardware constraints
required to run on the Neuronova chip. It returns True or False and prints a
detailed report showing which checks passed and which failed.
The checks include:
- The first layer must be a Frontend with ≤ 16 input channels
- Only matching layer/synapse families (H1v1 or H1v2, not mixed)
- Layers and synapses must alternate in the correct order
- H1v2 hidden layers: ≤ 50 neurons; output layer: ≤ 100 neurons
- Synaptic weights within hardware bounds and sign-topology constraints met
Note
Synapse and Frontend layers initial random weights do not authomatically satisfy hardware constraints. Check the custom losses and regularizers at the end of this page. To simplify in this section, we init the layers with all positive weights within the hwardware range for H1v2.
import torch
import torch.nn as nn
from nwavesdk.layers import H1v2Frontend, H1v2Synapse, H1v2Layer, prepare_net
from nwavesdk.utils import is_net_deployable
# A model that satisfies all deployment constraints.
class DeployableNet(nn.Module):
def __init__(self):
super().__init__()
self.frontend = H1v2Frontend(nb_inputs=16, device="cpu")
self.lyr1 = H1v2Layer(16, taus=10e-3, dt=1e-3, layer_topology="FF", device="cpu")
self.syn1 = H1v2Synapse(16, 32, device="cpu")
self.lyr2 = H1v2Layer(32, taus=10e-3, dt=1e-3, layer_topology="FF", device="cpu")
def forward(self, x):
prepare_net(self)
spk_list = []
for t in range(x.shape[1]):
cur = self.frontend(x[:, t, :])
spk, _ = self.lyr1(cur)
cur = self.syn1(spk)
spk, _ = self.lyr2(cur)
spk_list.append(spk)
return torch.stack(spk_list, dim=1)
good_model = DeployableNet()
# Weights need to stay in range and follow the block rule:
# Ensuring range
good_model.frontend.weight.data = (good_model.frontend.weight.data/good_model.frontend.weight.data.abs().max()*1.60)
good_model.syn1.weight.data = (good_model.syn1.weight.data/good_model.syn1.weight.data.abs().max()*1.60)
# Trivial block enforcement (all weights are positive)
good_model.frontend.weight.data = torch.abs(good_model.frontend.weight.data)
good_model.syn1.weight.data = torch.abs(good_model.syn1.weight.data)
print("=== Deployable model ===")
result = is_net_deployable(good_model)
print(f"\nis_net_deployable → {result}")
=== Deployable model ===
=== HW SNN Constraint Check ===
Detected HW family: H1V2
Layer chain detected (top-level): ['frontend:H1v2Frontend', 'lyr1:H1v2Layer', 'syn1:H1v2Synapse', 'lyr2:H1v2Layer']
Total neurons (sum of H1v2Layer): 48 (limit 200)
[PASS ✅] First layer is Frontend and nb_inputs <= 16
[PASS ✅] Second layer is H1v2Layer
[PASS ✅] Only H1v2Synapse/H1v2Layer allowed and alternating (after first layer)
[PASS ✅] Total neurons (sum of H1v2Layer.n_neurons) <= 200
[PASS ✅] H1v2 hidden layers (all H1v2Layer except last) <= 50 neurons
[PASS ✅] H1v2 output layer (last H1v2Layer) <= 100 neurons
[PASS ✅] All parameters within corresponding layer weight range
[PASS ✅] Sign-topology per-column blocks (block=5) on all non-Frontend 2D weights
OVERALL: PASS ✅
is_net_deployable → True
# A model that fails: hidden layer exceeds the 50-neuron H1v2 limit.
class OverSizedNet(nn.Module):
def __init__(self):
super().__init__()
self.frontend = H1v2Frontend(nb_inputs=16, device="cpu")
self.lyr1 = H1v2Layer(16, taus=10e-3, dt=1e-3, layer_topology="FF", device="cpu")
self.syn1 = H1v2Synapse(16, 200, device="cpu") # 200 neurons — too many
self.lyr2 = H1v2Layer(200, taus=10e-3, dt=1e-3, layer_topology="FF", device="cpu")
def forward(self, x):
prepare_net(self)
spk_list = []
for t in range(x.shape[1]):
cur = self.frontend(x[:, t, :])
spk, _ = self.lyr1(cur)
cur = self.syn1(spk)
spk, _ = self.lyr2(cur)
spk_list.append(spk)
return torch.stack(spk_list, dim=1)
bad_model = OverSizedNet()
print("=== Oversized model ===")
result = is_net_deployable(bad_model)
print(f"\nis_net_deployable → {result}")
=== Oversized model ===
=== HW SNN Constraint Check ===
Detected HW family: H1V2
Layer chain detected (top-level): ['frontend:H1v2Frontend', 'lyr1:H1v2Layer', 'syn1:H1v2Synapse', 'lyr2:H1v2Layer']
Total neurons (sum of H1v2Layer): 216 (limit 200)
[PASS ✅] First layer is Frontend and nb_inputs <= 16
[PASS ✅] Second layer is H1v2Layer
[PASS ✅] Only H1v2Synapse/H1v2Layer allowed and alternating (after first layer)
[FAIL ❌] Total neurons (sum of H1v2Layer.n_neurons) <= 200
[PASS ✅] H1v2 hidden layers (all H1v2Layer except last) <= 50 neurons
[FAIL ❌] H1v2 output layer (last H1v2Layer) <= 100 neurons
[FAIL ❌] All parameters within corresponding layer weight range
[FAIL ❌] Sign-topology per-column blocks (block=5) on all non-Frontend 2D weights
-> First out-of-range param: 'frontend.weight' (H1v2Frontend) with min=0.407590, max=3.632255, allowed=[-1.66, 1.66]
-> First sign-topology failure at module='syn1', param='weight'
OVERALL: FAIL ❌
is_net_deployable → False
Fixing a failing network
If the neuron count check fails, reduce the size of your hidden layers to ≤ 50 neurons per H1v2 hidden layer and ≤ 100 for the output layer.
If the weight constraint check fails, use weight_magnitude_loss during training
and SignAnnealing to gradually enforce the hardware sign-topology:
from nwavesdk.loss import weight_magnitude_loss
from nwavesdk.optim import SignAnnealing
scheduler = SignAnnealing(model, total_epochs=100, alpha_start=0.0, alpha_end=1.0)
# Inside training loop:
loss = task_loss + 0.01 * weight_magnitude_loss(model)
scheduler.step(epoch)
Further reading
- Reference → Layers: hardware weight bounds, neuron limits, and sign topology constraints →
../reference/layers.md - Reference → Losses:
weight_magnitude_lossandSignTopologyCoherenceLossfor fixing failing networks →../loss/network_losses.md - Tutorial 1 — H1v1 model with hardware constraints applied during training →
../tutorials/Tutorial1_H1v1Model.md