use std::io::Read;
use std::path::PathBuf;
#[cfg(feature = "tch-model")]
pub fn build_pt_mlp_temp(
layer_specs: &[(usize, usize, Vec<f32>, Vec<f32>)],
) -> Result<(Vec<u8>, PathBuf), String> {
use tch::nn::Module;
use tch::{Device, Kind, Tensor, nn};
if layer_specs.is_empty() {
return Err("Empty layer specs".to_string());
}
let mut vs = nn::VarStore::new(Device::Cpu);
let root = vs.root();
let mut seq = nn::seq();
for (idx, (layer_in, layer_out, weights, biases)) in layer_specs.iter().enumerate() {
let layer_path = root.sub(&format!("layer_{}", idx));
let mut linear_config = nn::LinearConfig::default();
linear_config.bias = true;
let mut linear = nn::linear(
&layer_path,
*layer_in as i64,
*layer_out as i64,
linear_config,
);
let weight_tensor = Tensor::from_slice(weights)
.reshape([*layer_in as i64, *layer_out as i64])
.transpose(0, 1);
let bias_tensor = Tensor::from_slice(biases);
tch::no_grad(|| {
linear.ws.copy_(&weight_tensor);
if let Some(ref mut bs) = linear.bs {
bs.copy_(&bias_tensor);
}
});
seq = seq.add(linear);
if idx < layer_specs.len() - 1 {
seq = seq.add_fn(|x| x.relu());
}
}
vs.freeze();
let temp_file = tempfile::Builder::new()
.prefix("relayrl_pt_model_")
.suffix(".pt")
.tempfile()
.map_err(|e| format!("Failed to create temp file: {}", e))?;
let temp_path = temp_file.path().to_path_buf();
let in_dim = layer_specs[0].0 as i64;
let example_input = Tensor::zeros([1, in_dim], (Kind::Float, Device::Cpu));
let mut trace_closure = |inputs: &[Tensor]| -> Vec<Tensor> { vec![seq.forward(&inputs[0])] };
let module = tch::no_grad(|| {
tch::CModule::create_by_tracing("mlp", "forward", &[example_input], &mut trace_closure)
})
.map_err(|e| format!("Failed to create traced module: {}", e))?;
module
.save(&temp_path)
.map_err(|e| format!("Failed to save model: {}", e))?;
let mut file = std::fs::File::open(&temp_path)
.map_err(|e| format!("Failed to open saved model: {}", e))?;
let mut bytes = Vec::new();
file.read_to_end(&mut bytes)
.map_err(|e| format!("Failed to read model bytes: {}", e))?;
std::mem::forget(temp_file);
Ok((bytes, temp_path))
}
#[cfg(not(feature = "tch-model"))]
pub fn build_pt_mlp_temp(
_layer_specs: &[(usize, usize, Vec<f32>, Vec<f32>)],
) -> Result<(Vec<u8>, PathBuf), String> {
Err("tch-model feature not enabled".to_string())
}
#[cfg(feature = "tch-model")]
pub fn build_pt_conv_temp(
arch: &[crate::algorithms::ArchLayer],
obs_dim: usize,
) -> Result<(Vec<u8>, PathBuf), String> {
use crate::algorithms::ArchLayer;
use tch::nn::Module;
use tch::{Device, Kind, Tensor, nn};
if arch.is_empty() {
return Err("Empty arch spec".to_string());
}
let mut vs = nn::VarStore::new(Device::Cpu);
let root = vs.root();
let mut seq = nn::seq();
let mut conv_idx = 0usize;
let mut fc_idx = 0usize;
for layer in arch {
match layer {
ArchLayer::Reshape { shape } => {
let shape: Vec<i64> = shape.clone();
seq = seq.add_fn(move |x| x.reshape(shape.as_slice()));
}
ArchLayer::Conv2d {
in_channels,
out_channels,
kernel_size,
stride,
weights,
biases,
} => {
let in_ch = *in_channels as i64;
let out_ch = *out_channels as i64;
let k = *kernel_size as i64;
let s = *stride as i64;
let path = root.sub(&format!("conv_{conv_idx}"));
let mut conv = nn::conv2d(
&path,
in_ch,
out_ch,
k,
nn::ConvConfig {
stride: s,
padding: 0,
..Default::default()
},
);
let w_t = Tensor::from_slice(weights).reshape([out_ch, in_ch, k, k]);
let b_t = Tensor::from_slice(biases.as_slice());
tch::no_grad(|| {
conv.ws.copy_(&w_t);
if let Some(ref mut bs) = conv.bs {
bs.copy_(&b_t);
}
});
seq = seq.add(conv);
conv_idx += 1;
}
ArchLayer::Elu => {
seq = seq.add_fn(|x| x.elu());
}
ArchLayer::Flatten => {
seq = seq.add_fn(|x| x.flatten(1, -1));
}
ArchLayer::Linear {
in_dim,
out_dim,
weights,
biases,
} => {
let in_d = *in_dim as i64;
let out_d = *out_dim as i64;
let path = root.sub(&format!("fc_{fc_idx}"));
let mut linear = nn::linear(
&path,
in_d,
out_d,
nn::LinearConfig {
bias: true,
..Default::default()
},
);
let w_t = Tensor::from_slice(weights.as_slice())
.reshape([in_d, out_d])
.transpose(0, 1);
let b_t = Tensor::from_slice(biases.as_slice());
tch::no_grad(|| {
linear.ws.copy_(&w_t);
if let Some(ref mut bs) = linear.bs {
bs.copy_(&b_t);
}
});
seq = seq.add(linear);
fc_idx += 1;
}
}
}
let temp_file = tempfile::Builder::new()
.prefix("relayrl_pt_conv_")
.suffix(".pt")
.tempfile()
.map_err(|e| format!("Failed to create temp file: {e}"))?;
let temp_path = temp_file.path().to_path_buf();
vs.freeze();
let example_input = Tensor::zeros([1, obs_dim as i64], (Kind::Float, Device::Cpu));
let mut trace_fn = |inputs: &[Tensor]| -> Vec<Tensor> { vec![seq.forward(&inputs[0])] };
let module = tch::no_grad(|| {
tch::CModule::create_by_tracing("convnet", "forward", &[example_input], &mut trace_fn)
})
.map_err(|e| format!("Failed to create traced module: {e}"))?;
module
.save(&temp_path)
.map_err(|e| format!("Failed to save model: {e}"))?;
let mut bytes = Vec::new();
std::fs::File::open(&temp_path)
.map_err(|e| format!("Failed to open saved model: {e}"))?
.read_to_end(&mut bytes)
.map_err(|e| format!("Failed to read model bytes: {e}"))?;
std::mem::forget(temp_file);
Ok((bytes, temp_path))
}
#[cfg(not(feature = "tch-model"))]
pub fn build_pt_conv_temp(
_arch: &[crate::algorithms::ArchLayer],
_obs_dim: usize,
) -> Result<(Vec<u8>, PathBuf), String> {
Err("tch-model feature not enabled".to_string())
}
#[cfg(all(test, feature = "tch-model"))]
mod tests {
use super::*;
#[test]
fn test_build_pt_mlp_single_layer() {
let weights = vec![1.0f32, 0.0, 0.0, 1.0]; let biases = vec![0.0f32, 0.0];
let specs = vec![(2usize, 2usize, weights, biases)];
let result = build_pt_mlp_temp(&specs);
assert!(result.is_ok(), "Should successfully build PT model");
let (bytes, path) = result.unwrap();
assert!(!bytes.is_empty(), "PT bytes should not be empty");
assert!(path.exists(), "Temp file should exist");
let _ = std::fs::remove_file(path);
}
#[test]
fn test_build_pt_mlp_empty_layers() {
let result = build_pt_mlp_temp(&[]);
assert!(result.is_err(), "Should fail on empty layer specs");
}
#[test]
fn test_build_pt_mlp_two_layers() {
let w1 = vec![0.1f32; 4 * 8]; let b1 = vec![0.0f32; 8];
let w2 = vec![0.2f32; 8 * 2]; let b2 = vec![0.0f32; 2];
let specs = vec![(4, 8, w1, b1), (8, 2, w2, b2)];
let result = build_pt_mlp_temp(&specs);
assert!(result.is_ok(), "Should successfully build 2-layer PT model");
let (bytes, path) = result.unwrap();
assert!(bytes.len() > 100, "Expected non-trivial PT model bytes");
let _ = std::fs::remove_file(path);
}
}