use std::hint::black_box;
#[cfg(feature = "cuda")]
use burn::backend::Cuda;
#[cfg(feature = "wgpu")]
use burn::backend::Wgpu;
use burn::{
backend::{Autodiff, NdArray},
optim::{AdamConfig, GradientsParams, Optimizer},
tensor::{Int, Tensor, TensorData, backend::AutodiffBackend},
};
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
use rand::{SeedableRng, rngs::StdRng};
use thrust_rl::{
env::{
Environment,
games::{cartpole::CartPole, pendulum::PendulumSwingUp},
},
policy::{
atari_cnn::{NatureDqnBurnPolicy, NatureDqnConfig, NatureDqnQNetwork},
mlp::MlpBurnPolicy,
q_network::QNetworkBurn,
},
train::{
A2cConfig, A2cTrainer, BurnOptimizer, DQNConfig, DQNTrainerBurn, PPOConfig, PPOTrainerBurn,
SacConfig, SacTrainer,
},
utils::cuda::default_burn_device,
};
const OBS_DIM: usize = 4;
const ACTION_DIM: usize = 2;
const HIDDEN_DIM: usize = 64;
const SYNTH_BATCH: usize = 64;
const ROLLOUT_N_STEPS: usize = 5;
const ROLLOUT_NUM_ENVS: usize = 16;
const DQN_BATCH: usize = 64;
const DQN_MIN_BUFFER: usize = 256;
const DQN_BUFFER_CAPACITY: usize = 4_096;
const DQN_LOOP_STEPS: usize = 16;
const SAC_OBS_DIM: usize = 3;
const SAC_ACTION_DIM: usize = 1;
const SAC_MAX_TORQUE: f32 = 2.0;
const SAC_BATCH: usize = 64;
const SAC_MIN_BUFFER: usize = 256;
const SAC_BUFFER_CAPACITY: usize = 4_096;
const SAC_LEARNING_STARTS: usize = 128;
const SAC_HIDDEN_DIM: usize = 64;
const SAC_NUM_HIDDEN_LAYERS: usize = 2;
const SAC_LOOP_STEPS: usize = 16;
const CNN_BATCH_DQN: usize = 32;
const CNN_BATCH_PPO: usize = 256;
const CNN_N_ACTIONS: usize = 18;
const CNN_LR: f64 = 2.5e-4;
const CNN_ENTROPY_COEF: f32 = 0.01;
fn make_a2c_trainer<B: AutodiffBackend>(
device: &B::Device,
) -> A2cTrainer<B, MlpBurnPolicy<B>, impl burn::optim::Optimizer<MlpBurnPolicy<B>, B>> {
let policy = MlpBurnPolicy::<B>::new(OBS_DIM, ACTION_DIM, HIDDEN_DIM, device);
let inner_opt = AdamConfig::new().init();
let burn_opt: BurnOptimizer<B, MlpBurnPolicy<B>, _> = BurnOptimizer::new(inner_opt, 7e-4);
let config = A2cConfig::default();
A2cTrainer::new(config, policy, burn_opt).expect("valid A2C config")
}
fn make_ppo_trainer<B: AutodiffBackend>(
device: &B::Device,
) -> PPOTrainerBurn<B, MlpBurnPolicy<B>, impl burn::optim::Optimizer<MlpBurnPolicy<B>, B>> {
let policy = MlpBurnPolicy::<B>::new(OBS_DIM, ACTION_DIM, HIDDEN_DIM, device);
let inner_opt = AdamConfig::new().init();
let burn_opt: BurnOptimizer<B, MlpBurnPolicy<B>, _> = BurnOptimizer::new(inner_opt, 3e-4);
let config = PPOConfig::new().n_epochs(1);
PPOTrainerBurn::new(config, policy, burn_opt).expect("valid PPO config")
}
#[allow(clippy::type_complexity)]
fn synthetic_batch<B: AutodiffBackend>(
device: &B::Device,
) -> (
Tensor<B, 2>,
Tensor<B, 1, Int>,
Tensor<B, 1>,
Tensor<B, 1>,
Tensor<B, 1>,
Tensor<B, 1>,
) {
let mut rng = StdRng::seed_from_u64(12345);
use rand::Rng;
let obs_data: Vec<f32> =
(0..SYNTH_BATCH * OBS_DIM).map(|_| rng.random_range(-1.0..1.0)).collect();
let observations =
Tensor::<B, 2>::from_data(TensorData::new(obs_data, [SYNTH_BATCH, OBS_DIM]), device);
let action_data: Vec<i64> =
(0..SYNTH_BATCH).map(|_| rng.random_range(0..ACTION_DIM as i64)).collect();
let actions =
Tensor::<B, 1, Int>::from_data(TensorData::new(action_data, [SYNTH_BATCH]), device);
let old_log_probs_data: Vec<f32> =
(0..SYNTH_BATCH).map(|_| -rng.random_range(0.5..1.5)).collect();
let old_log_probs =
Tensor::<B, 1>::from_data(TensorData::new(old_log_probs_data, [SYNTH_BATCH]), device);
let old_values_data: Vec<f32> = (0..SYNTH_BATCH).map(|_| rng.random_range(-1.0..1.0)).collect();
let old_values =
Tensor::<B, 1>::from_data(TensorData::new(old_values_data, [SYNTH_BATCH]), device);
let adv_data: Vec<f32> = (0..SYNTH_BATCH).map(|_| rng.random_range(-2.0..2.0)).collect();
let advantages = Tensor::<B, 1>::from_data(TensorData::new(adv_data, [SYNTH_BATCH]), device);
let returns_data: Vec<f32> = (0..SYNTH_BATCH).map(|_| rng.random_range(-1.0..1.0)).collect();
let returns = Tensor::<B, 1>::from_data(TensorData::new(returns_data, [SYNTH_BATCH]), device);
(observations, actions, old_log_probs, old_values, advantages, returns)
}
struct Rollout<B: AutodiffBackend> {
observations: Tensor<B, 2>,
actions: Tensor<B, 1, Int>,
old_log_probs: Tensor<B, 1>,
old_values: Tensor<B, 1>,
advantages: Tensor<B, 1>,
returns: Tensor<B, 1>,
}
fn collect_rollout<B: AutodiffBackend>(
policy: &MlpBurnPolicy<B>,
device: &B::Device,
rng: &mut StdRng,
) -> Rollout<B> {
const GAMMA: f32 = 0.99;
let mut envs: Vec<CartPole> = (0..ROLLOUT_NUM_ENVS)
.map(|_| {
let mut e = CartPole::new();
e.reset();
e
})
.collect();
let total = ROLLOUT_N_STEPS * ROLLOUT_NUM_ENVS;
let mut obs_flat: Vec<f32> = Vec::with_capacity(total * OBS_DIM);
let mut actions_flat: Vec<i64> = Vec::with_capacity(total);
let mut log_probs_flat: Vec<f32> = Vec::with_capacity(total);
let mut values_flat: Vec<f32> = Vec::with_capacity(total);
let mut rewards_flat: Vec<f32> = Vec::with_capacity(total);
let mut dones_flat: Vec<bool> = Vec::with_capacity(total);
for _ in 0..ROLLOUT_N_STEPS {
let mut step_obs: Vec<f32> = Vec::with_capacity(ROLLOUT_NUM_ENVS * OBS_DIM);
for env in &envs {
step_obs.extend_from_slice(&env.get_observation());
}
let obs_tensor = Tensor::<B, 2>::from_data(
TensorData::new(step_obs.clone(), [ROLLOUT_NUM_ENVS, OBS_DIM]),
device,
);
let (acts, lps, vals) = policy.get_action_host_seeded(obs_tensor, rng);
for (i, env) in envs.iter_mut().enumerate() {
obs_flat.extend_from_slice(&step_obs[i * OBS_DIM..(i + 1) * OBS_DIM]);
actions_flat.push(acts[i]);
log_probs_flat.push(lps[i]);
values_flat.push(vals[i]);
let result = env.step(acts[i]);
rewards_flat.push(result.reward);
let done = result.terminated || result.truncated;
dones_flat.push(done);
if done {
env.reset();
}
}
}
let mut returns_vec = vec![0.0f32; total];
let mut advantages_vec = vec![0.0f32; total];
for env_idx in 0..ROLLOUT_NUM_ENVS {
let mut running = 0.0f32;
for step in (0..ROLLOUT_N_STEPS).rev() {
let idx = step * ROLLOUT_NUM_ENVS + env_idx;
if dones_flat[idx] {
running = 0.0;
}
running = rewards_flat[idx] + GAMMA * running;
returns_vec[idx] = running;
advantages_vec[idx] = running - values_flat[idx];
}
}
Rollout {
observations: Tensor::<B, 2>::from_data(
TensorData::new(obs_flat, [total, OBS_DIM]),
device,
),
actions: Tensor::<B, 1, Int>::from_data(TensorData::new(actions_flat, [total]), device),
old_log_probs: Tensor::<B, 1>::from_data(TensorData::new(log_probs_flat, [total]), device),
old_values: Tensor::<B, 1>::from_data(TensorData::new(values_flat, [total]), device),
advantages: Tensor::<B, 1>::from_data(TensorData::new(advantages_vec, [total]), device),
returns: Tensor::<B, 1>::from_data(TensorData::new(returns_vec, [total]), device),
}
}
fn bench_a2c_train_step<B: AutodiffBackend>(c: &mut Criterion, device: &B::Device, suffix: &str) {
let (obs, actions, _old_lp, _old_v, advantages, returns) = synthetic_batch::<B>(device);
let mut group = c.benchmark_group(format!("a2c_train_step/{suffix}"));
group.throughput(Throughput::Elements(SYNTH_BATCH as u64));
group.bench_function("synthetic_batch", |b| {
b.iter_batched(
|| make_a2c_trainer::<B>(device),
|mut trainer| {
let stats = trainer
.train_step(
obs.clone(),
actions.clone(),
advantages.clone(),
returns.clone(),
|p, o, a| p.evaluate_actions(o, a),
)
.expect("A2C train_step");
black_box(stats)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_ppo_train_step<B: AutodiffBackend>(c: &mut Criterion, device: &B::Device, suffix: &str) {
let (obs, actions, old_lp, old_v, advantages, returns) = synthetic_batch::<B>(device);
let mut group = c.benchmark_group(format!("ppo_train_step/{suffix}"));
group.throughput(Throughput::Elements(SYNTH_BATCH as u64));
group.bench_function("synthetic_batch", |b| {
b.iter_batched(
|| make_ppo_trainer::<B>(device),
|mut trainer| {
let stats = trainer
.train_step(
obs.clone(),
actions.clone(),
old_lp.clone(),
old_v.clone(),
advantages.clone(),
returns.clone(),
|p, o, a| p.evaluate_actions(o, a),
)
.expect("PPO train_step");
black_box(stats)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_a2c_cartpole_steps_per_sec<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let total_steps = (ROLLOUT_N_STEPS * ROLLOUT_NUM_ENVS) as u64;
let mut group = c.benchmark_group(format!("a2c_cartpole_steps_per_sec/{suffix}"));
group.throughput(Throughput::Elements(total_steps));
group.bench_function("rollout_plus_update", |b| {
b.iter_batched(
|| (make_a2c_trainer::<B>(device), StdRng::seed_from_u64(777)),
|(mut trainer, mut rng)| {
let rollout = collect_rollout::<B>(trainer.policy(), device, &mut rng);
let stats = trainer
.train_step(
rollout.observations,
rollout.actions,
rollout.advantages,
rollout.returns,
|p, o, a| p.evaluate_actions(o, a),
)
.expect("A2C train_step");
black_box(stats)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_ppo_cartpole_steps_per_sec<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let total_steps = (ROLLOUT_N_STEPS * ROLLOUT_NUM_ENVS) as u64;
let mut group = c.benchmark_group(format!("ppo_cartpole_steps_per_sec/{suffix}"));
group.throughput(Throughput::Elements(total_steps));
group.bench_function("rollout_plus_update", |b| {
b.iter_batched(
|| (make_ppo_trainer::<B>(device), StdRng::seed_from_u64(777)),
|(mut trainer, mut rng)| {
let rollout = collect_rollout::<B>(trainer.policy(), device, &mut rng);
let stats = trainer
.train_step(
rollout.observations,
rollout.actions,
rollout.old_log_probs,
rollout.old_values,
rollout.advantages,
rollout.returns,
|p, o, a| p.evaluate_actions(o, a),
)
.expect("PPO train_step");
black_box(stats)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn dqn_config() -> DQNConfig {
DQNConfig::new()
.learning_rate(1e-3)
.batch_size(DQN_BATCH)
.buffer_capacity(DQN_BUFFER_CAPACITY)
.min_buffer_size(DQN_MIN_BUFFER)
.target_update_interval(500)
.gamma(0.99)
.epsilon_start(1.0)
.epsilon_end(0.05)
.epsilon_decay_steps(10_000)
}
fn make_dqn_trainer<B: AutodiffBackend>(
device: &B::Device,
) -> DQNTrainerBurn<B, QNetworkBurn<B>, impl burn::optim::Optimizer<QNetworkBurn<B>, B>> {
let online = QNetworkBurn::<B>::new(OBS_DIM, ACTION_DIM, HIDDEN_DIM, device);
let inner_opt = AdamConfig::new().init();
let config = dqn_config();
let burn_opt: BurnOptimizer<B, QNetworkBurn<B>, _> =
BurnOptimizer::new(inner_opt, config.learning_rate);
DQNTrainerBurn::new(config, online, burn_opt, OBS_DIM, ACTION_DIM as i64, device.clone())
.expect("valid DQN config")
}
fn synthetic_transition(i: usize) -> ([f32; OBS_DIM], i64, f32, [f32; OBS_DIM], bool) {
let phase = (i as f32) * 0.1;
let obs = [phase.sin(), phase.cos(), phase * 0.5, phase * -0.3];
let next_obs = [(phase + 0.1).sin(), (phase + 0.1).cos(), phase * 0.5, phase * -0.3];
let action = (i % ACTION_DIM) as i64;
let reward = if action == 0 { 1.0 } else { -1.0 };
let done = i % 32 == 31;
(obs, action, reward, next_obs, done)
}
fn prefill_buffer<B: AutodiffBackend>(
trainer: &mut DQNTrainerBurn<
B,
QNetworkBurn<B>,
impl burn::optim::Optimizer<QNetworkBurn<B>, B>,
>,
n: usize,
) {
for i in 0..n {
let (obs, action, reward, next_obs, done) = synthetic_transition(i);
trainer.buffer_mut().push(&obs, action, reward, &next_obs, done);
}
}
fn bench_dqn_train_step<B: AutodiffBackend>(c: &mut Criterion, device: &B::Device, suffix: &str) {
let mut group = c.benchmark_group(format!("dqn_train_step/{suffix}"));
group.throughput(Throughput::Elements(DQN_BATCH as u64));
group.bench_function("replay_minibatch", |b| {
b.iter_batched(
|| {
let mut trainer = make_dqn_trainer::<B>(device);
prefill_buffer::<B>(&mut trainer, DQN_MIN_BUFFER);
(trainer, StdRng::seed_from_u64(99))
},
|(mut trainer, mut rng)| {
let stats = trainer
.train_step(
&mut rng,
|q: &QNetworkBurn<B>, o: Tensor<B, 2>| q.forward(o),
|q: &QNetworkBurn<B>, o: Tensor<B, 2>| q.forward(o),
)
.expect("DQN train_step")
.expect("buffer pre-filled, train_step must update (not None)");
black_box(stats)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_dqn_cartpole_steps_per_sec<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let mut group = c.benchmark_group(format!("dqn_cartpole_steps_per_sec/{suffix}"));
group.throughput(Throughput::Elements(DQN_LOOP_STEPS as u64));
group.bench_function("env_step_plus_update", |b| {
b.iter_batched(
|| {
let mut trainer = make_dqn_trainer::<B>(device);
prefill_buffer::<B>(&mut trainer, DQN_MIN_BUFFER);
let mut env = CartPole::new();
env.reset();
(trainer, env, StdRng::seed_from_u64(0xC0FFEE))
},
|(mut trainer, mut env, mut rng)| {
let mut obs = env.get_observation();
for _ in 0..DQN_LOOP_STEPS {
let action = trainer.select_action(
&obs,
&mut rng,
|q: &QNetworkBurn<B>, o_host: &[f32]| {
let o_t: Tensor<B, 2> = Tensor::from_data(
TensorData::new(o_host.to_vec(), [1, o_host.len()]),
device,
);
let q_host: Vec<f32> =
q.forward(o_t).into_data().to_vec().unwrap_or_default();
let mut best = 0_i64;
let mut best_v = f32::NEG_INFINITY;
for (i, &v) in q_host.iter().enumerate() {
if v > best_v {
best_v = v;
best = i as i64;
}
}
best
},
);
let result = env.step(action);
let next_obs = result.observation.clone();
let done = result.terminated || result.truncated;
trainer.buffer_mut().push(&obs, action, result.reward, &next_obs, done);
obs = next_obs;
trainer.increment_env_step();
let stats = trainer
.train_step(
&mut rng,
|q: &QNetworkBurn<B>, o: Tensor<B, 2>| q.forward(o),
|q: &QNetworkBurn<B>, o: Tensor<B, 2>| q.forward(o),
)
.expect("DQN train_step")
.expect("buffer pre-filled, train_step must update (not None)");
black_box(stats);
if done {
env.reset();
obs = env.get_observation();
}
}
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn make_sac_trainer<B: AutodiffBackend>(device: &B::Device) -> SacTrainer<B> {
let config = SacConfig::new()
.batch_size(SAC_BATCH)
.buffer_capacity(SAC_BUFFER_CAPACITY)
.min_buffer_size(SAC_MIN_BUFFER)
.learning_starts(SAC_LEARNING_STARTS)
.hidden_dim(SAC_HIDDEN_DIM)
.num_hidden_layers(SAC_NUM_HIDDEN_LAYERS)
.seed(0);
SacTrainer::<B>::new(config, SAC_OBS_DIM, SAC_ACTION_DIM, device.clone())
.expect("valid SAC config")
}
fn sac_synthetic_transition(
i: usize,
) -> ([f32; SAC_OBS_DIM], [f32; SAC_ACTION_DIM], f32, [f32; SAC_OBS_DIM], bool) {
let phase = (i as f32) * 0.1;
let obs = [phase.cos(), phase.sin(), phase * 0.2];
let next_obs = [(phase + 0.1).cos(), (phase + 0.1).sin(), phase * 0.2];
let action = [phase.sin().clamp(-0.99, 0.99)];
let reward = -(phase * phase);
let done = i % 5 == 4;
(obs, action, reward, next_obs, done)
}
fn sac_prefill_buffer<B: AutodiffBackend>(trainer: &mut SacTrainer<B>, n: usize) {
for i in 0..n {
let (obs, action, reward, next_obs, done) = sac_synthetic_transition(i);
trainer.buffer_mut().push(&obs, &action, reward, &next_obs, done);
}
}
fn sac_scale_action(action: &[f32]) -> Vec<f32> {
action.iter().map(|a| a * SAC_MAX_TORQUE).collect()
}
fn bench_sac_train_step<B: AutodiffBackend>(c: &mut Criterion, device: &B::Device, suffix: &str) {
let mut group = c.benchmark_group(format!("sac_train_step/{suffix}"));
group.throughput(Throughput::Elements(SAC_BATCH as u64));
group.bench_function("replay_minibatch", |b| {
b.iter_batched(
|| {
let mut trainer = make_sac_trainer::<B>(device);
sac_prefill_buffer::<B>(&mut trainer, SAC_MIN_BUFFER);
for _ in 0..SAC_LEARNING_STARTS {
trainer.increment_env_step();
}
trainer
},
|mut trainer| {
let stats = trainer
.train()
.expect("SAC train()")
.expect("buffer pre-filled, train() must update (not None)");
black_box(stats)
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn bench_sac_pendulum_steps_per_sec<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let mut group = c.benchmark_group(format!("sac_pendulum_steps_per_sec/{suffix}"));
group.throughput(Throughput::Elements(SAC_LOOP_STEPS as u64));
group.bench_function("env_step_plus_update", |b| {
b.iter_batched(
|| {
let mut trainer = make_sac_trainer::<B>(device);
sac_prefill_buffer::<B>(&mut trainer, SAC_MIN_BUFFER);
for _ in 0..SAC_LEARNING_STARTS {
trainer.increment_env_step();
}
let mut env = PendulumSwingUp::with_seed(0);
env.reset();
(trainer, env)
},
|(mut trainer, mut env)| {
let mut obs = env.get_observation();
for _ in 0..SAC_LOOP_STEPS {
let action = trainer.select_action(&obs);
let result = env.step(sac_scale_action(&action));
let done = result.terminated || result.truncated;
trainer.buffer_mut().push(
&obs,
&action,
result.reward,
&result.observation,
done,
);
trainer.increment_env_step();
let stats = trainer
.train()
.expect("SAC train()")
.expect("buffer pre-filled, train() must update (not None)");
black_box(stats);
if done {
env.reset();
obs = env.get_observation();
} else {
obs = result.observation;
}
}
},
criterion::BatchSize::SmallInput,
);
});
group.finish();
}
fn cnn_obs<B: AutodiffBackend>(batch: usize, device: &B::Device) -> Tensor<B, 4> {
let data: Vec<f32> = (0..batch * 4 * 84 * 84).map(|i| (i as f32 * 0.001).sin().abs()).collect();
Tensor::<B, 4>::from_data(TensorData::new(data, [batch, 4, 84, 84]), device)
}
fn cnn_actions<B: AutodiffBackend>(batch: usize, device: &B::Device) -> Tensor<B, 1, Int> {
let data: Vec<i64> = (0..batch).map(|i| (i % CNN_N_ACTIONS) as i64).collect();
Tensor::<B, 1, Int>::from_data(TensorData::new(data, [batch]), device)
}
fn make_cnn_policy<B: AutodiffBackend>(device: &B::Device) -> NatureDqnBurnPolicy<B> {
NatureDqnBurnPolicy::<B>::with_config(
CNN_N_ACTIONS,
NatureDqnConfig::default().with_seed(42),
device,
)
}
fn make_cnn_qnet<B: AutodiffBackend>(device: &B::Device) -> NatureDqnQNetwork<B> {
NatureDqnQNetwork::<B>::with_config(
CNN_N_ACTIONS,
NatureDqnConfig::default().with_seed(42),
device,
)
}
fn bench_nature_dqn_policy_forward<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let mut group = c.benchmark_group(format!("nature_dqn_policy_forward/{suffix}"));
for &batch in &[CNN_BATCH_DQN, CNN_BATCH_PPO] {
group.throughput(Throughput::Elements(batch as u64));
group.bench_function(format!("b{batch}"), |b| {
b.iter_batched(
|| (make_cnn_policy::<B>(device), cnn_obs::<B>(batch, device)),
|(policy, obs)| {
let (logits, values) = policy.forward(obs);
black_box((logits, values));
let _ = B::sync(device);
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_nature_dqn_qnet_forward<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let mut group = c.benchmark_group(format!("nature_dqn_qnet_forward/{suffix}"));
for &batch in &[CNN_BATCH_DQN, CNN_BATCH_PPO] {
group.throughput(Throughput::Elements(batch as u64));
group.bench_function(format!("b{batch}"), |b| {
b.iter_batched(
|| (make_cnn_qnet::<B>(device), cnn_obs::<B>(batch, device)),
|(qnet, obs)| {
let q_values = qnet.forward(obs);
black_box(q_values);
let _ = B::sync(device);
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_nature_dqn_policy_train_step<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let mut group = c.benchmark_group(format!("nature_dqn_policy_train_step/{suffix}"));
for &batch in &[CNN_BATCH_DQN, CNN_BATCH_PPO] {
group.throughput(Throughput::Elements(batch as u64));
group.bench_function(format!("b{batch}"), |b| {
b.iter_batched(
|| {
let policy = make_cnn_policy::<B>(device);
let inner_opt = AdamConfig::new().init();
let opt: BurnOptimizer<B, NatureDqnBurnPolicy<B>, _> =
BurnOptimizer::new(inner_opt, CNN_LR);
(policy, opt, cnn_obs::<B>(batch, device), cnn_actions::<B>(batch, device))
},
|(policy, mut opt, obs, actions)| {
let (log_probs, entropy, values) = policy.evaluate_actions(obs, actions);
let loss = log_probs.mean().neg() + values.powf_scalar(2.0).mean()
- entropy.mean().mul_scalar(CNN_ENTROPY_COEF);
let grads = GradientsParams::from_grads(loss.backward(), &policy);
let policy = opt.inner_mut().step(CNN_LR, policy, grads);
black_box(policy);
let _ = B::sync(device);
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn bench_nature_dqn_qnet_train_step<B: AutodiffBackend>(
c: &mut Criterion,
device: &B::Device,
suffix: &str,
) {
let mut group = c.benchmark_group(format!("nature_dqn_qnet_train_step/{suffix}"));
for &batch in &[CNN_BATCH_DQN, CNN_BATCH_PPO] {
group.throughput(Throughput::Elements(batch as u64));
group.bench_function(format!("b{batch}"), |b| {
b.iter_batched(
|| {
let qnet = make_cnn_qnet::<B>(device);
let inner_opt = AdamConfig::new().init();
let opt: BurnOptimizer<B, NatureDqnQNetwork<B>, _> =
BurnOptimizer::new(inner_opt, CNN_LR);
(qnet, opt, cnn_obs::<B>(batch, device))
},
|(qnet, mut opt, obs)| {
let q_values = qnet.forward(obs);
let loss = q_values.powf_scalar(2.0).mean();
let grads = GradientsParams::from_grads(loss.backward(), &qnet);
let qnet = opt.inner_mut().step(CNN_LR, qnet, grads);
black_box(qnet);
let _ = B::sync(device);
},
criterion::BatchSize::SmallInput,
);
});
}
group.finish();
}
fn register_all<B: AutodiffBackend>(c: &mut Criterion, device: &B::Device, suffix: &str) {
bench_a2c_train_step::<B>(c, device, suffix);
bench_ppo_train_step::<B>(c, device, suffix);
bench_a2c_cartpole_steps_per_sec::<B>(c, device, suffix);
bench_ppo_cartpole_steps_per_sec::<B>(c, device, suffix);
bench_dqn_train_step::<B>(c, device, suffix);
bench_dqn_cartpole_steps_per_sec::<B>(c, device, suffix);
bench_sac_train_step::<B>(c, device, suffix);
bench_sac_pendulum_steps_per_sec::<B>(c, device, suffix);
bench_nature_dqn_policy_forward::<B>(c, device, suffix);
bench_nature_dqn_qnet_forward::<B>(c, device, suffix);
bench_nature_dqn_policy_train_step::<B>(c, device, suffix);
bench_nature_dqn_qnet_train_step::<B>(c, device, suffix);
}
fn benches(c: &mut Criterion) {
type Cpu = Autodiff<NdArray<f32>>;
register_all::<Cpu>(c, &default_burn_device::<Cpu>(), "ndarray");
#[cfg(feature = "wgpu")]
{
type Gpu = Autodiff<Wgpu<f32, i32>>;
register_all::<Gpu>(c, &default_burn_device::<Gpu>(), "wgpu");
}
#[cfg(feature = "cuda")]
{
type Gpu = Autodiff<Cuda<f32, i32>>;
register_all::<Gpu>(c, &default_burn_device::<Gpu>(), "cuda");
}
#[cfg(all(feature = "cuda", feature = "training-fp16"))]
{
type Fp16Cuda = Autodiff<Cuda<burn::tensor::f16, i32>>;
register_all::<Fp16Cuda>(c, &default_burn_device::<Fp16Cuda>(), "cuda-f16");
}
#[cfg(all(feature = "wgpu", not(feature = "cuda"), feature = "training-fp16"))]
{
type Fp16Wgpu = Autodiff<Wgpu<burn::tensor::f16, i32>>;
register_all::<Fp16Wgpu>(c, &default_burn_device::<Fp16Wgpu>(), "wgpu-f16");
}
}
criterion_group!(benches_group, benches);
criterion_main!(benches_group);