本文档描述 ISnnComputeCore 在 SnnDL 中的语义契约,便于后续实现新的计算核心(Compute Core)时做到可替换且行为一致。
当前参考实现:DefaultSnnComputeCore(SnnComputeCore.{h,cc})。
- 将“神经动力学 / 计算核心”与“SST/SnnDL 适配层(内存、GAS、路由等)解耦。
- 保证在不修改
SnnPESubComponent的前提下,可以替换不同的 compute core 实现。 - 计算核心对外提供统一的、事件驱动的接口:接受突触输入,输出发放事件。
SnnPESubComponent(适配层)负责:
- SST 生命周期对接:
init/setup/finish、clock 驱动; - StandardMem / DRAM / BCSR 权重读写与缓存;
- GAS 超步(Gather/Apply/Scatter)状态机与统计;
- 基于
route_provider_的 fanout 计算与 SpikeEvent 发送; - 通过接口将突触输入、时间与阶段信息“喂”给 compute core;
- 通过接口从 compute core 拉取发放事件,再做路由和统计。
ISnnComputeCore 实现负责:
- 内部所有神经元状态的存储与更新(膜电位、不应期、最近发放时间等);
- 突触输入(ΔV/权重作用)如何影响状态;
- 不应期 / 阈值 / 窗口单次发放等发放逻辑;
- 提供只读/写状态视图(用于 debug/学习);
- 对统计接口(
getStatistics)的自有内部计数。
struct ComputeCoreContext {
uint32_t core_id;
uint32_t node_id;
uint32_t num_neurons;
uint64_t global_neuron_base;
uint32_t neurons_per_pe_cfg;
SST::Output* log; // 可选:日志指针,可为空
IWeightReader* weight_reader; // 可选:权重访问抽象,可为空
// 可选:学习写回回调(若 core 实现学习/梯度累加,可在窗口边界调用)
// 返回 true 表示写回成功,core 可清空本窗梯度
std::function<bool(const std::unordered_map<uint64_t,float>& grads,
float learning_rate,
float weight_decay)> writeback_fn;
};num_neurons:核心内本地神经元数,所有 idx 均在[0, num_neurons)。global_neuron_base:本 core 对应的全局 neuron 基址,仅供日志 / 诊断参考。weight_reader:若实现需要自行发起权重访问,可通过该抽象;也可以完全不用。writeback_fn:学习可选回调;用于让 core 在窗口边界请求控制层执行权重写回。
struct FireEvent {
uint32_t neuron_idx; // 本地 neuron 索引 [0, num_neurons)
float v_before; // 发放前膜电位
float v_after; // 发放后膜电位
};
struct NeuronStateSnapshot {
float v_mem;
uint32_t refractory;
uint64_t last_spike;
};FireEvent用于 compute core → 适配层的发放事件传递;NeuronStateSnapshot用于 debug / 学习路径的轻量级状态访问。
生命周期 / 配置:
virtual void configure(const ComputeCoreContext&, const SST::Params&) = 0;
virtual void onInit(unsigned phase) = 0;
virtual void onSetup() = 0;
virtual void onFinish() = 0;时间推进:
virtual void onClockTick(uint64_t now_cycle) = 0;GAS 阶段回调:
virtual void onStageBeginGather(uint32_t seq) = 0;
virtual void onStageBeginApply(uint32_t seq) = 0;
virtual void onStageEndApply(uint32_t seq) = 0;
virtual void onStageBeginScatter(uint32_t seq) = 0;
virtual void onStageEndScatter(uint32_t seq, uint64_t spikes_emitted_hint) = 0;Spike 输入(路由后到达本 core 的 spike):
virtual void onSpikeDelivered(SpikeEvent* spike) = 0;状态 / 统计:
virtual bool hasWork() const = 0;
virtual double getUtilization() const = 0;
virtual void getStatistics(std::map<std::string, uint64_t>& out) const = 0;
virtual void resetMembraneState(float v_rest) = 0;突触输入与发放管理:
// 应用一次突触输入 ΔV(或其它权重作用),post_local 为本地 neuron 索引
virtual void applySynapticDelta(uint32_t post_local, float dv) = 0;
// 窗口累加语义下,控制层在记录边/发起权重读/累加前,需先询问核心是否接受该输入(例如不应期过滤)
virtual bool shouldAcceptSynapticInput(uint32_t post_local, uint64_t now_cycle) const = 0;
// 便捷接口:允许核心内部处理带有 pre_global 的完整突触事件(由核心自行决定门控/累加方式)
virtual void onSynapticEvent(const SynapticEvent& ev) = 0;
// 统一的“周期收敛”接口:推进动力学并在内部判定发放,输出事件待 drain
virtual void endCycle(uint64_t now_cycle) = 0;
// 仅对候选集合进行发放判定(window/scatter 可选优化;必须不改变结果)
virtual void endCycleCandidates(uint64_t now_cycle, const std::vector<uint32_t>& candidates) = 0;
// 取出当前尚未消费的输出事件(控制层统一路由)
virtual void drainOutputs(std::vector<FireEvent>& out, bool clear = true) = 0;
// 窗口级发放状态清零(通常在 BeginScatter 调用)
virtual void clearFiredWindow() = 0;
// 兼容接口:旧路径仍可用(推荐使用 drainOutputs)
virtual void getFiredEvents(std::vector<FireEvent>& out, bool clear = true) = 0;状态快照(只读/写视图):
virtual bool readNeuronState(uint32_t idx, NeuronStateSnapshot& out) const = 0;
virtual void writeNeuronState(uint32_t idx, const NeuronStateSnapshot& st) = 0;显式发放判定(当前默认实现依然使用):
virtual void updateNeuronStates() = 0;
virtual bool fire(uint32_t idx, uint64_t now_cycles, INeuronModel* model,
float& v_before, float& v_after) = 0;说明:控制层应优先使用
endCycle/endCycleCandidates + drainOutputs;updateNeuronStates/fire/getFiredEvents更偏向“兼容/调试接口”,后续可逐步弱化其在适配层中的直接使用。
- 所有
idx/post_local参数必须满足:- 有效范围为
[0, num_neurons); - 若越界,实现可以安全忽略或返回
false,不得崩溃。
- 有效范围为
-
configure(context, params)- 在构造完成后、init 之前调用一次。
- 必须使用
context.num_neurons初始化内部 state(包括 AoS/SoA/AoSoA 任意布局)。
-
onInit(phase)- 与 SST
init阶段扣合,可能被多次调用; - 可以根据
phase决定何时实际初始化内部结构。
- 与 SST
-
onSetup()/onFinish()- 各调用一次,用于仿真开始前 / 结束后做额外操作(预热、统计收尾等)。
-
onClockTick(now_cycle):- 每周期调用一次,与 SST global clock 对齐;
- 用于 per-cycle bookkeeping(统计、学习/验证 tick、窗口计数等);
- 动力学推进与发放判定由适配层通过
endCycle/endCycleCandidates在收敛点显式触发(以支持 window/scatter 语义)。
-
onStageBeginGather/Apply/Scatter、onStageEndApply/Scatter:- 由 SnnPESubComponent 的 GAS 控制路径触发;
- 一般用于设置内部
gas_stage_状态、清空窗口数据结构等; onStageBeginScatter通常应调用clearFiredWindow()等价操作。
- 语义:向本地 neuron
post_local施加一次“突触输入事件”。最常见是 ΔV 叠加,但实现可以采用更一般的动力学(例如电流、门控变量等)。 - 调用特点:
- 可能在同一周期内多次调用、也可能跨多个周期累积;
- 常见场景:
- GAS Scatter 使用累加器(dense / map),遍历 touched posts 后调用;
- Scheme1 Scatter 中对当前 slice 的所有 post 施加 ΔV。
- 要求:
- 每次调用必须对内部状态产生一致影响;
- 不得在此接口中直接向外部暴露发放(发放应统一经
endCycle/endCycleCandidates + drainOutputs)。
- 背景:在
apply_acc_enable && gas_window_mode下,适配层常见模式是“先收集边/发起权重读,在 Scatter 统一应用 ΔV”。 此时无法立刻调用onSynapticEvent()来复用核心侧的输入门控(例如不应期过滤),因此需要该接口做一次一致的 gate 判定。 - 语义:
shouldAcceptSynapticInput(post_local, now_cycle)返回:true:允许该输入进入控制面流程(记录边/发起权重读/累加 ΔV);false:该输入应被丢弃。
- 不变量:
- 当该接口返回
false时,适配层不得:- 记录该边(避免污染 window-read 的 edge 集合);
- 发起权重读取请求;
- 将该输入累加进窗口累加器。
- 当该接口返回
当前 Default 实现中,发放判定流程为:
-
适配层在收敛点调用:
compute_core_->endCycle(now_cycles); std::vector<FireEvent> fired; compute_core_->drainOutputs(fired, true);
-
compute core 在
endCycle()内:- 推进动力学(Default:
updateNeuronStates()); - 对 neuron 集合执行发放判定(Default: 扫描并调用
fire()); - 将发放结果追加到内部输出队列(由
drainOutputs拉取)。
- 推进动力学(Default:
-
适配层对每个
FireEvent执行:- 统计更新(
spikes_generated、window_spikes_all_等); - 基于
route_provider_计算 fanout 并发送 SpikeEvent。
- 统计更新(
不变量:
-
窗口门控:
- 当
apply_acc_enable && gas_window_mode为真时:- 只有在 Scatter 阶段(
onStageBeginScatter与onStageEndScatter之间)允许产生输出发放事件; - 单一窗口内,对给定
idx,最多产生一个FireEvent。
- 只有在 Scatter 阶段(
- 当
-
事件一致性:
- 每次发放被判定为成功,都必须在后续某次
drainOutputs(clear=true)中返回对应事件,且只返回一次。
- 每次发放被判定为成功,都必须在后续某次
- 用途:
- Scatter 阶段调试:打印
v_before/v_after/refrac; - 学习路径:计算 surrogate gradient 需要访问
v_mem和refractory。
- Scatter 阶段调试:打印
- 要求:
- 当
idx < num_neurons时:readNeuronState(idx, out)需返回true且out等价于当前内部状态;writeNeuronState(idx, st)需把st写回内部状态,使下一次read返回相同值(除非中间有onClockTick/applySynapticDelta/fire等改变)。
- 当
实现可以内部直接使用 SnnCoreEngine 的 getMem/getRefrac/getLastSpike/set*,但这些不再暴露到接口层。
适配层典型调用序列(省略非关键细节):
-
构造时:
ComputeCoreContext ctx{...}; compute_core_->configure(ctx, params); -
SST 生命周期:
compute_core_->onInit(phase); compute_core_->onSetup(); ... compute_core_->onFinish();
-
每个仿真周期:
compute_core_->onClockTick(total_cycles_); -
GAS 阶段:
- 控制层在窗口阶段边界调用相应 onStage*(当前由
SnnPESubComponent::Impl统一记录/触发):
compute_core_->onStageBeginGather(seq); compute_core_->onStageBeginApply(seq); compute_core_->onStageBeginScatter(seq); ... compute_core_->onStageEndScatter(seq, spikes_emitted_hint);
- 控制层在窗口阶段边界调用相应 onStage*(当前由
-
突触输入与发放:
- Apply/Scatter 阶段计算出 ΔV 后(适配层只负责喂 ΔV 与触发收敛):
applySynapticDelta(post_local, dv); std::vector<FireEvent> fired; compute_core_->endCycle(now_cycles); compute_core_->drainOutputs(fired, true); for (auto& ev : fired) { handleNeuronFire_(ev.neuron_idx, ev.v_before, ev.v_after); }
-
状态快照(调试/学习):
NeuronStateSnapshot snap{}; if (compute_core_->readNeuronState(idx, snap)) { // 使用 snap.v_mem / snap.refractory / snap.last_spike 做额外处理 }
当你实现新的 ISnnComputeCore 时:
- 确保完全遵守本文档中接口语义和不变量;
- 可以自由选择内部动态方程(LIF、Izhikevich、非脉冲激活单元等);
- 只要保留:
applySynapticDelta视为“向某 neuron 输入一个事件”;FireEvent.neuron_idx为本地索引;NeuronStateSnapshot提供等价状态视图; 即可和现有SnnPESubComponent无缝协作。