Transfer Learning
The transfer package provides validated checkpoint serialization plus layer
freezing helpers for SNN transfer-learning workflows.
Pythonimport numpy as np
from sc_neurocore.transfer import (
SNNCheckpoint,
TransferConfig,
apply_transfer_config,
freeze_layers,
load_checkpoint,
save_checkpoint,
)
checkpoint = SNNCheckpoint(
weights=[
np.ones((32, 64), dtype=np.float64),
np.ones((10, 32), dtype=np.float64),
],
layer_names=["hidden", "output"],
layer_sizes=[(64, 32), (32, 10)],
neuron_types=["LIF", "LIF"],
metadata={"task": "mnist"},
)
save_checkpoint(checkpoint, "model_v1")
checkpoint = load_checkpoint("model_v1")
freeze_layers(checkpoint, layer_names=["hidden"])
checkpoint, learning_rates = apply_transfer_config(
checkpoint,
TransferConfig(freeze_until=0, lr_backbone=0.0, lr_head=0.01),
)
Checkpoints are stored as a model_v1.npz weight archive plus a model_v1.json
metadata file. Loading validates the JSON metadata schema, rejects unexpected
archive members, opens .npz weights with pickle disabled, rejects non-finite
weights, and checks every matrix against its (input_features, output_features)
layer-size contract.
Validation Surface
| Surface |
Contract |
| Python |
Constructor and loader reject duplicate layer names, shape mismatches, non-finite weights, unknown frozen layers, invalid learning rates, and non-JSON metadata. |
| Rust |
src/sc_neurocore/accel/rust/safety/checkpoint.rs and fine_tune.rs compile as standalone safety mirrors with unit tests. |
| Julia |
src/sc_neurocore/accel/julia/transfer/checkpoint.jl and fine_tune.jl validate the same in-memory checkpoint and transfer schedule invariants. |
| Mojo |
src/sc_neurocore/accel/mojo/kernels/checkpoint.mojo and fine_tune.mojo run deterministic validation kernels. |
Local Evidence
benchmarks/results/bench_transfer.json records local, non-isolated regression
evidence. The 2026-06-27 run reports:
| Check |
Result |
| Python checkpoint roundtrip |
100 calls in 0.154779 s, 646.081 calls/s |
Python apply_transfer_config |
100 calls in 0.009153 s, 10925.32 calls/s |
| Rust checkpoint compile/tests |
pass |
| Rust fine-tune compile/tests |
pass |
| Julia validation |
pass |
| Mojo checkpoint/fine-tune validation |
pass |
These timings are regression evidence only; the artifact marks
production_benchmark_claim as false.
See Tutorial 81: Transfer Learning.
sc_neurocore.transfer
Checkpoint serialization and transfer-learning helpers for SNN models.
The package exports the complete public transfer workflow: build or load a
validated checkpoint, freeze or unfreeze named layers, apply a learning-rate
schedule, and save the resulting state back to disk.
SNNCheckpoint
dataclass
Complete dense-weight SNN checkpoint for transfer workflows.
Parameters
weights:
One two-dimensional weight matrix per layer. A matrix shape must be
(output_features, input_features) for the matching layer_sizes
entry (input_features, output_features).
layer_names:
Unique layer names in forward order.
layer_sizes:
(input_features, output_features) pairs for each layer.
neuron_types:
Optional neuron-model labels, either empty or one label per layer.
metadata:
JSON-serializable provenance or training metadata.
frozen_layers:
Layer names currently marked non-trainable.
Source code in src/sc_neurocore/transfer/checkpoint.py
| Python |
|---|
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 | @dataclass
class SNNCheckpoint:
"""Complete dense-weight SNN checkpoint for transfer workflows.
Parameters
----------
weights:
One two-dimensional weight matrix per layer. A matrix shape must be
``(output_features, input_features)`` for the matching ``layer_sizes``
entry ``(input_features, output_features)``.
layer_names:
Unique layer names in forward order.
layer_sizes:
``(input_features, output_features)`` pairs for each layer.
neuron_types:
Optional neuron-model labels, either empty or one label per layer.
metadata:
JSON-serializable provenance or training metadata.
frozen_layers:
Layer names currently marked non-trainable.
"""
weights: list[FloatArray]
layer_names: list[str]
layer_sizes: list[tuple[int, int]]
neuron_types: list[str] = field(default_factory=list)
metadata: dict[str, object] = field(default_factory=dict)
frozen_layers: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
"""Normalize arrays and reject inconsistent checkpoint state."""
_validate_string_vector(self.layer_names, "layer_names")
if len(set(self.layer_names)) != len(self.layer_names):
raise ValueError("Checkpoint layer_names must be unique")
if len(self.weights) != len(self.layer_names):
raise ValueError("Checkpoint weights length must match layer_names")
if len(self.layer_sizes) != len(self.layer_names):
raise ValueError("Checkpoint layer_sizes length must match layer_names")
self.layer_sizes = [_validate_layer_size_tuple(size) for size in self.layer_sizes]
if self.neuron_types:
_validate_string_vector(self.neuron_types, "neuron_types")
if len(self.neuron_types) != len(self.layer_names):
raise ValueError("Checkpoint neuron_types length must match layer_names")
_validate_string_vector(self.frozen_layers, "frozen_layers")
unknown_frozen = sorted(set(self.frozen_layers) - set(self.layer_names))
if unknown_frozen:
raise ValueError("Checkpoint frozen_layers must reference known layers")
self.frozen_layers = sorted(set(self.frozen_layers))
_validate_json_serializable(self.metadata)
self.weights = [
_validate_weight_array(weight, f"layer_{index}", self.layer_sizes[index])
for index, weight in enumerate(self.weights)
]
@property
def n_layers(self) -> int:
"""Return the number of serialized layers."""
return len(self.weights)
@property
def total_params(self) -> int:
"""Return the total number of scalar weight parameters."""
return int(sum(weight.size for weight in self.weights))
|
n_layers
property
Return the number of serialized layers.
total_params
property
Return the total number of scalar weight parameters.
__post_init__()
Normalize arrays and reject inconsistent checkpoint state.
Source code in src/sc_neurocore/transfer/checkpoint.py
| Python |
|---|
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96 | def __post_init__(self) -> None:
"""Normalize arrays and reject inconsistent checkpoint state."""
_validate_string_vector(self.layer_names, "layer_names")
if len(set(self.layer_names)) != len(self.layer_names):
raise ValueError("Checkpoint layer_names must be unique")
if len(self.weights) != len(self.layer_names):
raise ValueError("Checkpoint weights length must match layer_names")
if len(self.layer_sizes) != len(self.layer_names):
raise ValueError("Checkpoint layer_sizes length must match layer_names")
self.layer_sizes = [_validate_layer_size_tuple(size) for size in self.layer_sizes]
if self.neuron_types:
_validate_string_vector(self.neuron_types, "neuron_types")
if len(self.neuron_types) != len(self.layer_names):
raise ValueError("Checkpoint neuron_types length must match layer_names")
_validate_string_vector(self.frozen_layers, "frozen_layers")
unknown_frozen = sorted(set(self.frozen_layers) - set(self.layer_names))
if unknown_frozen:
raise ValueError("Checkpoint frozen_layers must reference known layers")
self.frozen_layers = sorted(set(self.frozen_layers))
_validate_json_serializable(self.metadata)
self.weights = [
_validate_weight_array(weight, f"layer_{index}", self.layer_sizes[index])
for index, weight in enumerate(self.weights)
]
|
TransferConfig
dataclass
Configuration for checkpoint-based SNN transfer learning.
Parameters
freeze_until:
Freeze all layers up to and including this layer name or index. -1
means do not add frozen layers.
lr_backbone:
Learning rate for frozen backbone layers, usually zero or a small value.
lr_head:
Learning rate for unfrozen task-head layers.
Source code in src/sc_neurocore/transfer/fine_tune.py
| Python |
|---|
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56 | @dataclass
class TransferConfig:
"""Configuration for checkpoint-based SNN transfer learning.
Parameters
----------
freeze_until:
Freeze all layers up to and including this layer name or index. ``-1``
means do not add frozen layers.
lr_backbone:
Learning rate for frozen backbone layers, usually zero or a small value.
lr_head:
Learning rate for unfrozen task-head layers.
"""
freeze_until: str | int = -1
lr_backbone: float = 0.0
lr_head: float = 0.01
def __post_init__(self) -> None:
"""Reject invalid freeze targets and learning rates."""
if isinstance(self.freeze_until, bool) or not isinstance(self.freeze_until, (str, int)):
raise ValueError("TransferConfig freeze_until must be a layer name or integer index")
if isinstance(self.freeze_until, int) and self.freeze_until < -1:
raise ValueError("TransferConfig freeze_until index must be -1 or non-negative")
if (
not math.isfinite(self.lr_backbone)
or not math.isfinite(self.lr_head)
or self.lr_backbone < 0.0
or self.lr_head < 0.0
):
raise ValueError("TransferConfig learning rates must be finite and non-negative")
|
__post_init__()
Reject invalid freeze targets and learning rates.
Source code in src/sc_neurocore/transfer/fine_tune.py
| Python |
|---|
44
45
46
47
48
49
50
51
52
53
54
55
56 | def __post_init__(self) -> None:
"""Reject invalid freeze targets and learning rates."""
if isinstance(self.freeze_until, bool) or not isinstance(self.freeze_until, (str, int)):
raise ValueError("TransferConfig freeze_until must be a layer name or integer index")
if isinstance(self.freeze_until, int) and self.freeze_until < -1:
raise ValueError("TransferConfig freeze_until index must be -1 or non-negative")
if (
not math.isfinite(self.lr_backbone)
or not math.isfinite(self.lr_head)
or self.lr_backbone < 0.0
or self.lr_head < 0.0
):
raise ValueError("TransferConfig learning rates must be finite and non-negative")
|
load_checkpoint(path)
Load and validate an SNN checkpoint from path.npz and path.json.
Parameters
path:
Base path without extension.
Returns
SNNCheckpoint:
Reconstructed checkpoint with finite float64 weight arrays.
Source code in src/sc_neurocore/transfer/checkpoint.py
| Python |
|---|
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 | def load_checkpoint(path: str | Path) -> SNNCheckpoint:
"""Load and validate an SNN checkpoint from ``path.npz`` and ``path.json``.
Parameters
----------
path:
Base path without extension.
Returns
-------
SNNCheckpoint:
Reconstructed checkpoint with finite ``float64`` weight arrays.
"""
path = Path(path)
with _json_path(path).open(encoding="utf-8") as handle:
raw_meta: object = json.load(handle)
meta = _validate_metadata(raw_meta)
n_layers = meta["n_layers"]
with np.load(_npz_path(path), allow_pickle=False) as data:
expected_keys = [f"layer_{index}" for index in range(n_layers)]
if set(data.files) != set(expected_keys):
raise ValueError("Checkpoint weight archive does not match metadata layers")
weights = [
_validate_weight_array(data[key], key, meta["layer_sizes"][index])
for index, key in enumerate(expected_keys)
]
checkpoint = SNNCheckpoint(
weights=weights,
layer_names=meta["layer_names"],
layer_sizes=meta["layer_sizes"],
neuron_types=meta["neuron_types"],
metadata=meta["metadata"],
frozen_layers=meta["frozen_layers"],
)
if checkpoint.total_params != meta["total_params"]:
raise ValueError("Checkpoint metadata total_params does not match weights")
return checkpoint
|
save_checkpoint(checkpoint, path)
Save an SNN checkpoint to path.npz plus path.json.
Parameters
checkpoint:
Validated checkpoint to serialize.
path:
Base path without extension. Parent directories are created.
Source code in src/sc_neurocore/transfer/checkpoint.py
| Python |
|---|
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 | def save_checkpoint(checkpoint: SNNCheckpoint, path: str | Path) -> None:
"""Save an SNN checkpoint to ``path.npz`` plus ``path.json``.
Parameters
----------
checkpoint:
Validated checkpoint to serialize.
path:
Base path without extension. Parent directories are created.
"""
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
weight_dict = {f"layer_{index}": weight for index, weight in enumerate(checkpoint.weights)}
np.savez_compressed(_npz_path(path), **weight_dict) # type: ignore[arg-type]
meta = {
"layer_names": checkpoint.layer_names,
"layer_sizes": checkpoint.layer_sizes,
"neuron_types": checkpoint.neuron_types,
"frozen_layers": checkpoint.frozen_layers,
"n_layers": checkpoint.n_layers,
"total_params": checkpoint.total_params,
"metadata": checkpoint.metadata,
}
_json_path(path).write_text(
json.dumps(meta, allow_nan=False, indent=2) + "\n",
encoding="utf-8",
)
|
apply_transfer_config(checkpoint, config)
Apply a transfer config and return per-layer learning rates.
Parameters
checkpoint:
Checkpoint to mutate according to config.
config:
Validated transfer schedule.
Returns
tuple[SNNCheckpoint, list[float]]:
The mutated checkpoint and one learning rate per layer.
Source code in src/sc_neurocore/transfer/fine_tune.py
| Python |
|---|
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160 | def apply_transfer_config(
checkpoint: SNNCheckpoint,
config: TransferConfig,
) -> tuple[SNNCheckpoint, list[float]]:
"""Apply a transfer config and return per-layer learning rates.
Parameters
----------
checkpoint:
Checkpoint to mutate according to ``config``.
config:
Validated transfer schedule.
Returns
-------
tuple[SNNCheckpoint, list[float]]:
The mutated checkpoint and one learning rate per layer.
"""
if isinstance(config.freeze_until, int) and config.freeze_until >= 0:
freeze_layers(checkpoint, until_index=config.freeze_until)
elif isinstance(config.freeze_until, str):
if config.freeze_until not in checkpoint.layer_names:
raise ValueError("TransferConfig freeze_until layer is not present in checkpoint")
freeze_layers(checkpoint, until_index=checkpoint.layer_names.index(config.freeze_until))
per_layer_lr = [
config.lr_backbone if name in checkpoint.frozen_layers else config.lr_head
for name in checkpoint.layer_names
]
return checkpoint, per_layer_lr
|
freeze_layers(checkpoint, layer_names=None, until_index=None)
Mark checkpoint layers as frozen.
Parameters
checkpoint:
Checkpoint to mutate.
layer_names:
Specific layer names to freeze.
until_index:
Freeze every layer with index less than or equal to this value.
Returns
SNNCheckpoint:
The same checkpoint object with frozen_layers updated.
Source code in src/sc_neurocore/transfer/fine_tune.py
| Python |
|---|
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 | def freeze_layers(
checkpoint: SNNCheckpoint,
layer_names: Sequence[str] | None = None,
until_index: int | None = None,
) -> SNNCheckpoint:
"""Mark checkpoint layers as frozen.
Parameters
----------
checkpoint:
Checkpoint to mutate.
layer_names:
Specific layer names to freeze.
until_index:
Freeze every layer with index less than or equal to this value.
Returns
-------
SNNCheckpoint:
The same checkpoint object with ``frozen_layers`` updated.
"""
frozen = set(checkpoint.frozen_layers)
if layer_names is not None:
_validate_layer_names(checkpoint, layer_names)
frozen.update(layer_names)
if until_index is not None:
_validate_until_index(checkpoint, until_index)
for index, name in enumerate(checkpoint.layer_names):
if index <= until_index:
frozen.add(name)
checkpoint.frozen_layers = sorted(frozen)
return checkpoint
|
unfreeze_layers(checkpoint, layer_names=None, all_layers=False)
Mark checkpoint layers as trainable.
Parameters
checkpoint:
Checkpoint to mutate.
layer_names:
Specific layer names to unfreeze.
all_layers:
When true, clear every frozen-layer marker.
Returns
SNNCheckpoint:
The same checkpoint object with frozen_layers updated.
Source code in src/sc_neurocore/transfer/fine_tune.py
| Python |
|---|
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 | def unfreeze_layers(
checkpoint: SNNCheckpoint,
layer_names: Sequence[str] | None = None,
all_layers: bool = False,
) -> SNNCheckpoint:
"""Mark checkpoint layers as trainable.
Parameters
----------
checkpoint:
Checkpoint to mutate.
layer_names:
Specific layer names to unfreeze.
all_layers:
When true, clear every frozen-layer marker.
Returns
-------
SNNCheckpoint:
The same checkpoint object with ``frozen_layers`` updated.
"""
if all_layers:
checkpoint.frozen_layers = []
return checkpoint
if layer_names is not None:
_validate_layer_names(checkpoint, layer_names)
removals = set(layer_names)
checkpoint.frozen_layers = [
name for name in checkpoint.frozen_layers if name not in removals
]
return checkpoint
|