use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use fandhe_ai_autodiff::merge_ops;
use fandhe_ai_autodiff::nn::{MaxUnpool1d, MaxUnpool2d, MaxUnpool3d};
use super::Sequential;
use super::SequentialVars;
use super::training::{Compiled, CompiledSnapshot, compiled_from_snapshot, snapshot_of_compiled};
use crate::{AutodiffError, Tape, Tensor, Var};
mod train;
#[cfg(test)]
mod fit_parity_tests;
#[cfg(test)]
mod fit_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod merge_tests;
static NEXT_BUILDER_ID: AtomicU64 = AtomicU64::new(1);
fn invalid(message: String) -> AutodiffError {
AutodiffError::InvalidArgument(message)
}
fn ensure_on_tape(tape: &Tape, var: &Var<'_>) -> Result<(), AutodiffError> {
if !tape.0.owns(var) {
return Err(AutodiffError::TapeMismatch);
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Node {
builder_id: u64,
index: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum MergeKind {
Concatenate { dim: usize },
Add,
Multiply,
Average,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum PoolSpec {
D1 {
kernel_size: usize,
stride: Option<usize>,
padding: usize,
dilation: usize,
},
D2 {
kernel_size: [usize; 2],
stride: Option<[usize; 2]>,
padding: [usize; 2],
dilation: [usize; 2],
},
D3 {
kernel_size: [usize; 3],
stride: Option<[usize; 3]>,
padding: [usize; 3],
dilation: [usize; 3],
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum UnpoolSpec {
D1 {
kernel_size: usize,
stride: Option<usize>,
padding: usize,
output_size: Option<usize>,
},
D2 {
kernel_size: [usize; 2],
stride: Option<[usize; 2]>,
padding: [usize; 2],
output_size: Option<[usize; 2]>,
},
D3 {
kernel_size: [usize; 3],
stride: Option<[usize; 3]>,
padding: [usize; 3],
output_size: Option<[usize; 3]>,
},
}
impl PoolSpec {
pub(super) fn dims(&self) -> usize {
match self {
PoolSpec::D1 { .. } => 1,
PoolSpec::D2 { .. } => 2,
PoolSpec::D3 { .. } => 3,
}
}
}
impl UnpoolSpec {
pub(super) fn dims(&self) -> usize {
match self {
UnpoolSpec::D1 { .. } => 1,
UnpoolSpec::D2 { .. } => 2,
UnpoolSpec::D3 { .. } => 3,
}
}
fn validate(&self) -> Result<(), AutodiffError> {
match *self {
UnpoolSpec::D1 {
kernel_size,
stride,
padding,
..
} => MaxUnpool1d::new(kernel_size, stride, padding).map(|_| ()),
UnpoolSpec::D2 {
kernel_size,
stride,
padding,
..
} => MaxUnpool2d::new(kernel_size, stride, padding).map(|_| ()),
UnpoolSpec::D3 {
kernel_size,
stride,
padding,
..
} => MaxUnpool3d::new(kernel_size, stride, padding).map(|_| ()),
}
}
}
pub(super) enum NodeDef {
Input,
Block {
block: Box<Sequential>,
inputs: Vec<usize>,
},
Merge { kind: MergeKind, inputs: Vec<usize> },
MaxPoolIndices { spec: PoolSpec, inputs: Vec<usize> },
MaxUnpool {
spec: UnpoolSpec,
inputs: Vec<usize>,
},
}
impl NodeDef {
fn sources(&self) -> &[usize] {
match self {
NodeDef::Input => &[],
NodeDef::Block { inputs, .. }
| NodeDef::Merge { inputs, .. }
| NodeDef::MaxPoolIndices { inputs, .. }
| NodeDef::MaxUnpool { inputs, .. } => inputs,
}
}
}
pub struct FunctionalBuilder {
id: u64,
nodes: Vec<NodeDef>,
}
impl Default for FunctionalBuilder {
fn default() -> Self {
Self::new()
}
}
impl FunctionalBuilder {
pub fn new() -> Self {
Self {
id: NEXT_BUILDER_ID.fetch_add(1, Ordering::Relaxed),
nodes: Vec::new(),
}
}
fn resolve(&self, node: Node, what: &str) -> Result<usize, AutodiffError> {
if node.builder_id != self.id {
return Err(invalid(format!(
"{what}: 他のビルダーが発行したノードは使えない"
)));
}
if node.index >= self.nodes.len() {
return Err(invalid(format!(
"{what}: ノード添字 {} が範囲外(ノード数 {})",
node.index,
self.nodes.len()
)));
}
Ok(node.index)
}
fn push_node(&mut self, def: NodeDef) -> Result<Node, AutodiffError> {
self.nodes
.try_reserve(1)
.map_err(|_| super::alloc_failed())?;
let index = self.nodes.len();
self.nodes.push(def);
Ok(Node {
builder_id: self.id,
index,
})
}
pub fn input(&mut self) -> Result<Node, AutodiffError> {
self.push_node(NodeDef::Input)
}
pub fn apply(&mut self, block: Sequential, input: Node) -> Result<Node, AutodiffError> {
let src = self.resolve(input, "apply")?;
if block.compiled.is_some() {
return Err(invalid(
"apply: compile() 済みの Sequential は使えない(compile は Functional モデル側で行う)"
.to_string(),
));
}
if block.layers().is_empty() {
return Err(invalid(
"apply: 層を 1 つも持たない Sequential は使えない".to_string(),
));
}
let mut inputs = Vec::new();
inputs.try_reserve(1).map_err(|_| super::alloc_failed())?;
inputs.push(src);
self.push_node(NodeDef::Block {
block: Box::new(block),
inputs,
})
}
pub fn concatenate(&mut self, inputs: &[Node], dim: usize) -> Result<Node, AutodiffError> {
self.add_merge("concatenate", MergeKind::Concatenate { dim }, inputs)
}
pub fn add(&mut self, inputs: &[Node]) -> Result<Node, AutodiffError> {
self.add_merge("add", MergeKind::Add, inputs)
}
pub fn multiply(&mut self, inputs: &[Node]) -> Result<Node, AutodiffError> {
self.add_merge("multiply", MergeKind::Multiply, inputs)
}
pub fn average(&mut self, inputs: &[Node]) -> Result<Node, AutodiffError> {
self.add_merge("average", MergeKind::Average, inputs)
}
fn add_merge(
&mut self,
what: &str,
kind: MergeKind,
inputs: &[Node],
) -> Result<Node, AutodiffError> {
if inputs.len() < 2 {
return Err(invalid(format!(
"{what}: 入力は 2 件以上が必要(件数 {})",
inputs.len()
)));
}
let mut srcs: Vec<usize> = Vec::new();
srcs.try_reserve_exact(inputs.len())
.map_err(|_| super::alloc_failed())?;
let mut seen = vec![false; self.nodes.len()];
for node in inputs {
let index = self.resolve(*node, what)?;
let slot = seen
.get_mut(index)
.ok_or_else(|| invalid(format!("{what}: ノード添字 {index} が範囲外")))?;
if *slot {
return Err(invalid(format!(
"{what}: ノード {index} が重複して指定された"
)));
}
*slot = true;
srcs.push(index);
}
self.push_node(NodeDef::Merge { kind, inputs: srcs })
}
pub fn max_pool1d_with_indices(
&mut self,
input: Node,
kernel_size: usize,
stride: Option<usize>,
padding: usize,
dilation: usize,
) -> Result<Node, AutodiffError> {
self.add_max_pool_indices(
"max_pool1d_with_indices",
PoolSpec::D1 {
kernel_size,
stride,
padding,
dilation,
},
input,
)
}
pub fn max_pool2d_with_indices(
&mut self,
input: Node,
kernel_size: [usize; 2],
stride: Option<[usize; 2]>,
padding: [usize; 2],
dilation: [usize; 2],
) -> Result<Node, AutodiffError> {
self.add_max_pool_indices(
"max_pool2d_with_indices",
PoolSpec::D2 {
kernel_size,
stride,
padding,
dilation,
},
input,
)
}
pub fn max_pool3d_with_indices(
&mut self,
input: Node,
kernel_size: [usize; 3],
stride: Option<[usize; 3]>,
padding: [usize; 3],
dilation: [usize; 3],
) -> Result<Node, AutodiffError> {
self.add_max_pool_indices(
"max_pool3d_with_indices",
PoolSpec::D3 {
kernel_size,
stride,
padding,
dilation,
},
input,
)
}
pub fn max_unpool1d(
&mut self,
input: Node,
indices_from: Node,
kernel_size: usize,
stride: Option<usize>,
padding: usize,
output_size: Option<usize>,
) -> Result<Node, AutodiffError> {
self.add_max_unpool(
"max_unpool1d",
UnpoolSpec::D1 {
kernel_size,
stride,
padding,
output_size,
},
input,
indices_from,
)
}
pub fn max_unpool2d(
&mut self,
input: Node,
indices_from: Node,
kernel_size: [usize; 2],
stride: Option<[usize; 2]>,
padding: [usize; 2],
output_size: Option<[usize; 2]>,
) -> Result<Node, AutodiffError> {
self.add_max_unpool(
"max_unpool2d",
UnpoolSpec::D2 {
kernel_size,
stride,
padding,
output_size,
},
input,
indices_from,
)
}
pub fn max_unpool3d(
&mut self,
input: Node,
indices_from: Node,
kernel_size: [usize; 3],
stride: Option<[usize; 3]>,
padding: [usize; 3],
output_size: Option<[usize; 3]>,
) -> Result<Node, AutodiffError> {
self.add_max_unpool(
"max_unpool3d",
UnpoolSpec::D3 {
kernel_size,
stride,
padding,
output_size,
},
input,
indices_from,
)
}
fn add_max_pool_indices(
&mut self,
what: &str,
spec: PoolSpec,
input: Node,
) -> Result<Node, AutodiffError> {
let src = self.resolve(input, what)?;
let mut inputs = Vec::new();
inputs
.try_reserve_exact(1)
.map_err(|_| super::alloc_failed())?;
inputs.push(src);
self.push_node(NodeDef::MaxPoolIndices { spec, inputs })
}
fn add_max_unpool(
&mut self,
what: &str,
spec: UnpoolSpec,
input: Node,
indices_from: Node,
) -> Result<Node, AutodiffError> {
let src = self.resolve(input, what)?;
let pool = self.resolve(indices_from, what)?;
match self.nodes.get(pool) {
Some(NodeDef::MaxPoolIndices {
spec: pool_spec, ..
}) => {
if pool_spec.dims() != spec.dims() {
return Err(invalid(format!(
"{what}: indices_from(ノード {pool})の pool は {} 次元で、unpool の次元 {} と一致しない",
pool_spec.dims(),
spec.dims()
)));
}
}
_ => {
return Err(invalid(format!(
"{what}: indices_from(ノード {pool})は max_pool*_with_indices ノードでない"
)));
}
}
spec.validate()?;
let mut inputs = Vec::new();
inputs
.try_reserve_exact(2)
.map_err(|_| super::alloc_failed())?;
inputs.push(src);
inputs.push(pool);
self.push_node(NodeDef::MaxUnpool { spec, inputs })
}
pub fn build(
self,
inputs: &[Node],
outputs: &[Node],
) -> Result<FunctionalModel, AutodiffError> {
if inputs.is_empty() {
return Err(invalid("build: inputs が空".to_string()));
}
if outputs.is_empty() {
return Err(invalid("build: outputs が空".to_string()));
}
let node_count = self.nodes.len();
let mut listed = vec![false; node_count];
let mut input_indices = Vec::new();
input_indices
.try_reserve(inputs.len())
.map_err(|_| super::alloc_failed())?;
for node in inputs {
let index = self.resolve(*node, "build inputs")?;
if !matches!(self.nodes.get(index), Some(NodeDef::Input)) {
return Err(invalid(format!(
"build inputs: ノード {index} は入力ノードではない"
)));
}
let slot = listed
.get_mut(index)
.ok_or_else(|| invalid(format!("build inputs: ノード {index} が範囲外")))?;
if *slot {
return Err(invalid(format!("build inputs: ノード {index} が重複")));
}
*slot = true;
input_indices.push(index);
}
for (index, def) in self.nodes.iter().enumerate() {
let is_listed = listed.get(index).copied().unwrap_or(false);
if matches!(def, NodeDef::Input) && !is_listed {
return Err(invalid(format!(
"build: 入力ノード {index} が inputs に列挙されていない(未束縛)"
)));
}
}
let mut reached = vec![false; node_count];
let mut output_indices = Vec::new();
output_indices
.try_reserve(outputs.len())
.map_err(|_| super::alloc_failed())?;
for node in outputs {
let index = self.resolve(*node, "build outputs")?;
let slot = reached
.get_mut(index)
.ok_or_else(|| invalid(format!("build outputs: ノード {index} が範囲外")))?;
if *slot {
return Err(invalid(format!("build outputs: ノード {index} が重複")));
}
*slot = true;
output_indices.push(index);
}
for index in (0..node_count).rev() {
if !reached.get(index).copied().unwrap_or(false) {
continue;
}
if let Some(def) = self.nodes.get(index) {
for src in def.sources() {
if let Some(slot) = reached.get_mut(*src) {
*slot = true;
}
}
}
}
if let Some(dead) = reached.iter().position(|r| !*r) {
return Err(invalid(format!(
"build: ノード {dead} はどの出力にも寄与しない"
)));
}
let mut layer_starts = Vec::new();
layer_starts
.try_reserve(node_count)
.map_err(|_| super::alloc_failed())?;
let mut next_layer = 0usize;
for def in &self.nodes {
layer_starts.push(next_layer);
if let NodeDef::Block { block, .. } = def {
next_layer = next_layer
.checked_add(block.layers().len())
.ok_or_else(|| {
invalid("build: 層の通し番号がオーバーフローする".to_string())
})?;
}
}
Ok(FunctionalModel {
nodes: self.nodes,
inputs: input_indices,
outputs: output_indices,
layer_starts,
compiled: None,
})
}
}
pub struct FunctionalModel {
pub(super) nodes: Vec<NodeDef>,
pub(super) inputs: Vec<usize>,
pub(super) outputs: Vec<usize>,
pub(super) layer_starts: Vec<usize>,
pub(super) compiled: Option<Compiled>,
}
pub(crate) struct FunctionalVars<'m, 't> {
model: &'m FunctionalModel,
blocks: Vec<(usize, SequentialVars<'m, 't>)>,
}
pub(super) fn to_global_key(layer_start: usize, local: &str) -> Result<String, AutodiffError> {
let (index, name) = local.split_once('.').ok_or_else(|| {
invalid(format!(
"パラメータキー {local:?} が \"{{index}}.{{name}}\" 形式でない"
))
})?;
let index: usize = index
.parse()
.map_err(|_| invalid(format!("パラメータキー {local:?} の層番号が数値でない")))?;
let global = layer_start
.checked_add(index)
.ok_or_else(|| invalid("パラメータキーの通し番号がオーバーフローする".to_string()))?;
Ok(format!("{global}.{name}"))
}
pub(super) fn to_local_key(layer_start: usize, global: &str) -> Option<String> {
let (index, name) = global.split_once('.')?;
let index: usize = index.parse().ok()?;
let local = index.checked_sub(layer_start)?;
Some(format!("{local}.{name}"))
}
impl FunctionalModel {
pub(super) fn blocks(&self) -> impl Iterator<Item = (usize, &Sequential)> {
self.nodes
.iter()
.enumerate()
.filter_map(|(i, def)| match def {
NodeDef::Block { block, .. } => Some((i, block.as_ref())),
NodeDef::Input
| NodeDef::Merge { .. }
| NodeDef::MaxPoolIndices { .. }
| NodeDef::MaxUnpool { .. } => None,
})
}
pub(super) fn layer_start(&self, node_index: usize) -> Result<usize, AutodiffError> {
self.layer_starts
.get(node_index)
.copied()
.ok_or_else(|| invalid(format!("内部不整合: ノード {node_index} の層番号がない")))
}
pub(super) fn slot_shapes(&self) -> Vec<Vec<usize>> {
self.trainable_parameters()
.iter()
.map(|p| p.shape().to_vec())
.collect()
}
pub(super) fn compile_state_snapshot(&self) -> Result<Option<CompiledSnapshot>, AutodiffError> {
self.compiled.as_ref().map(snapshot_of_compiled).transpose()
}
pub(super) fn restore_compile_state(
&mut self,
snap: CompiledSnapshot,
) -> Result<(), AutodiffError> {
let slots = self.slot_shapes();
self.compiled = Some(compiled_from_snapshot(snap, &slots)?);
Ok(())
}
pub fn forward<'t>(
&self,
tape: &'t Tape,
inputs: &[Var<'t>],
) -> Result<Vec<Var<'t>>, AutodiffError> {
self.eval_graph(tape, inputs, &mut |_, block, x| block.forward(tape, x))
}
fn eval_graph<'t>(
&self,
tape: &'t Tape,
inputs: &[Var<'t>],
eval_block: &mut dyn FnMut(usize, &Sequential, &Var<'t>) -> Result<Var<'t>, AutodiffError>,
) -> Result<Vec<Var<'t>>, AutodiffError> {
if inputs.len() != self.inputs.len() {
return Err(invalid(format!(
"forward: 入力数 {} がモデルの入力数 {} と一致しない",
inputs.len(),
self.inputs.len()
)));
}
for var in inputs {
ensure_on_tape(tape, var)?;
}
let mut values: Vec<Option<Var<'t>>> = Vec::new();
values
.try_reserve_exact(self.nodes.len())
.map_err(|_| super::alloc_failed())?;
values.resize(self.nodes.len(), None);
let mut indices: Vec<Option<Tensor<i32>>> = Vec::new();
indices
.try_reserve_exact(self.nodes.len())
.map_err(|_| super::alloc_failed())?;
indices.resize(self.nodes.len(), None);
for (slot, var) in self.inputs.iter().zip(inputs) {
*values
.get_mut(*slot)
.ok_or_else(|| invalid(format!("内部不整合: 入力ノード {slot} が範囲外")))? =
Some(*var);
}
for (index, def) in self.nodes.iter().enumerate() {
let y =
match def {
NodeDef::Input => continue,
NodeDef::Block { block, inputs } => {
let [src] = inputs.as_slice() else {
return Err(invalid(format!(
"内部不整合: ブロックノード {index} の入力数が 1 でない"
)));
};
let x =
values.get(*src).copied().flatten().ok_or_else(|| {
invalid(format!("内部不整合: ノード {src} が未評価"))
})?;
eval_block(index, block.as_ref(), &x)?
}
NodeDef::Merge { kind, inputs } => {
let mut xs: Vec<Var<'t>> = Vec::new();
xs.try_reserve_exact(inputs.len())
.map_err(|_| super::alloc_failed())?;
for src in inputs {
xs.push(values.get(*src).copied().flatten().ok_or_else(|| {
invalid(format!("内部不整合: ノード {src} が未評価"))
})?);
}
match kind {
MergeKind::Concatenate { dim } => {
merge_ops::merge_concatenate(&xs, *dim)?
}
MergeKind::Add => merge_ops::merge_add(&xs)?,
MergeKind::Multiply => merge_ops::merge_multiply(&xs)?,
MergeKind::Average => merge_ops::merge_average(&xs)?,
}
}
NodeDef::MaxPoolIndices { spec, inputs } => {
let [src] = inputs.as_slice() else {
return Err(invalid(format!(
"内部不整合: MaxPool ノード {index} の入力数が 1 でない"
)));
};
let x =
values.get(*src).copied().flatten().ok_or_else(|| {
invalid(format!("内部不整合: ノード {src} が未評価"))
})?;
let (y, idx) = match *spec {
PoolSpec::D1 {
kernel_size,
stride,
padding,
dilation,
} => x.max_pool1d(kernel_size, stride, padding, dilation, false)?,
PoolSpec::D2 {
kernel_size,
stride,
padding,
dilation,
} => x.max_pool2d(kernel_size, stride, padding, dilation, false)?,
PoolSpec::D3 {
kernel_size,
stride,
padding,
dilation,
} => x.max_pool3d(kernel_size, stride, padding, dilation, false)?,
};
*indices.get_mut(index).ok_or_else(|| {
invalid(format!("内部不整合: ノード {index} が範囲外"))
})? = Some(idx);
y
}
NodeDef::MaxUnpool { spec, inputs } => {
let [src, pool] = inputs.as_slice() else {
return Err(invalid(format!(
"内部不整合: MaxUnpool ノード {index} の入力数が 2 でない"
)));
};
let x =
values.get(*src).copied().flatten().ok_or_else(|| {
invalid(format!("内部不整合: ノード {src} が未評価"))
})?;
let idx = indices.get(*pool).and_then(Option::as_ref).ok_or_else(|| {
invalid(format!("内部不整合: ノード {pool} の索引が未評価"))
})?;
match *spec {
UnpoolSpec::D1 {
kernel_size,
stride,
padding,
output_size,
} => x.max_unpool1d(idx, kernel_size, stride, padding, output_size)?,
UnpoolSpec::D2 {
kernel_size,
stride,
padding,
output_size,
} => x.max_unpool2d(idx, kernel_size, stride, padding, output_size)?,
UnpoolSpec::D3 {
kernel_size,
stride,
padding,
output_size,
} => x.max_unpool3d(idx, kernel_size, stride, padding, output_size)?,
}
}
};
*values
.get_mut(index)
.ok_or_else(|| invalid(format!("内部不整合: ノード {index} が範囲外")))? = Some(y);
}
let mut outputs = Vec::new();
outputs
.try_reserve_exact(self.outputs.len())
.map_err(|_| super::alloc_failed())?;
for slot in &self.outputs {
let value = values
.get(*slot)
.copied()
.flatten()
.ok_or_else(|| invalid(format!("内部不整合: 出力ノード {slot} が未評価")))?;
outputs.push(value);
}
Ok(outputs)
}
pub fn predict(&self, inputs: &[&Tensor<f32>]) -> Result<Vec<Tensor<f32>>, AutodiffError> {
let tape = crate::tape();
let mut vars = Vec::new();
vars.try_reserve_exact(inputs.len())
.map_err(|_| super::alloc_failed())?;
for tensor in inputs {
vars.push(tape.var(tensor));
}
let outputs = self.forward(&tape, &vars)?;
Ok(outputs.iter().map(Var::to_tensor).collect())
}
pub fn set_training(&mut self, training: bool) {
for def in &mut self.nodes {
if let NodeDef::Block { block, .. } = def {
block.set_training(training);
}
}
}
pub fn train(&mut self) {
self.set_training(true);
}
pub fn eval(&mut self) {
self.set_training(false);
}
pub fn training(&self) -> bool {
self.blocks().all(|(_, block)| block.training())
}
pub fn named_parameters(&self) -> Result<Vec<(String, &Tensor<f32>)>, AutodiffError> {
let mut out = Vec::new();
for (index, block) in self.blocks() {
let start = self.layer_start(index)?;
for (local, tensor) in block.named_parameters() {
out.try_reserve(1).map_err(|_| super::alloc_failed())?;
out.push((to_global_key(start, &local)?, tensor));
}
}
Ok(out)
}
pub fn state_dict(&self) -> Result<HashMap<String, Tensor<f32>>, AutodiffError> {
let mut out = HashMap::new();
for (index, block) in self.blocks() {
let start = self.layer_start(index)?;
for (local, tensor) in block.state_dict() {
out.try_reserve(1).map_err(|_| super::alloc_failed())?;
out.insert(to_global_key(start, &local)?, tensor);
}
}
Ok(out)
}
pub fn load_state_dict(
&mut self,
mut state: HashMap<String, Tensor<f32>>,
) -> Result<(), AutodiffError> {
let mut plan: Vec<(usize, Vec<(String, String)>)> = Vec::new();
let mut expected_keys = 0usize;
for (index, block) in self.blocks() {
let start = self.layer_start(index)?;
let mut pairs = Vec::new();
for (local, current) in block.state_dict() {
let global = to_global_key(start, &local)?;
let Some(given) = state.get(&global) else {
return Err(invalid(format!("load_state_dict: キー {global:?} が欠落")));
};
if given.shape() != current.shape() {
return Err(invalid(format!(
"load_state_dict: キー {global:?} の shape {:?} が期待 {:?} と一致しない",
given.shape(),
current.shape()
)));
}
expected_keys += 1;
pairs.try_reserve(1).map_err(|_| super::alloc_failed())?;
pairs.push((local, global));
}
plan.try_reserve(1).map_err(|_| super::alloc_failed())?;
plan.push((index, pairs));
}
if state.len() != expected_keys {
let unknown = state
.keys()
.find(|k| !plan.iter().any(|(_, p)| p.iter().any(|(_, g)| g == *k)));
return Err(invalid(format!(
"load_state_dict: 未知のキー {unknown:?} がある(期待 {expected_keys} 件・入力 {} 件)",
state.len()
)));
}
let mut snapshots: Vec<HashMap<String, Tensor<f32>>> = Vec::new();
snapshots
.try_reserve_exact(plan.len())
.map_err(|_| super::alloc_failed())?;
for (index, _) in &plan {
match self.nodes.get(*index) {
Some(NodeDef::Block { block, .. }) => snapshots.push(block.state_dict()),
_ => {
return Err(invalid(format!(
"内部不整合: ノード {index} がブロックでない"
)));
}
}
}
for (applied, (index, pairs)) in plan.iter().enumerate() {
let mut local_state = HashMap::new();
for (local, global) in pairs {
let tensor = state.remove(global).ok_or_else(|| {
invalid(format!("内部不整合: キー {global:?} が第 2 パスで消えた"))
})?;
local_state.insert(local.clone(), tensor);
}
let result = match self.nodes.get_mut(*index) {
Some(NodeDef::Block { block, .. }) => block.load_state_dict(local_state),
_ => Err(invalid(format!(
"内部不整合: ノード {index} がブロックでない"
))),
};
if let Err(err) = result {
let mut rollback_failures: Vec<String> = Vec::new();
for ((done_index, _), snapshot) in
plan.iter().zip(snapshots.iter()).take(applied + 1)
{
match self.nodes.get_mut(*done_index) {
Some(NodeDef::Block { block, .. }) => {
if let Err(rb) = block.load_state_dict(snapshot.clone()) {
rollback_failures.push(format!("ノード {done_index}: {rb}"));
}
}
_ => rollback_failures
.push(format!("ノード {done_index}: ブロックでないため復元不能")),
}
}
if !rollback_failures.is_empty() {
return Err(invalid(format!(
"load_state_dict: ノード {index} の適用に失敗({err})し、失敗ブロック自身と先行ブロックの巻き戻しにも失敗した({})。モデルが部分適用のまま残っている可能性がある",
rollback_failures.join("; ")
)));
}
return Err(err);
}
}
Ok(())
}
}