Merge pull request 'SMT E4: stabilize cloned updater for DAgger' (#6) from d310-dagger-stability into main
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
Performance Benchmarks / Run Benchmarks (push) Has been cancelled
CI / Format Check (push) Has been cancelled
CI / Clippy Check (push) Has been cancelled
CI / Build CPU-Only (Explicit) (push) Has been cancelled
Documentation / Build API Documentation (push) Has been cancelled
Documentation / Build User Guide (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / CI Success (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
Performance Benchmarks / Run Benchmarks (push) Has been cancelled
CI / Format Check (push) Has been cancelled
CI / Clippy Check (push) Has been cancelled
CI / Build CPU-Only (Explicit) (push) Has been cancelled
Documentation / Build API Documentation (push) Has been cancelled
Documentation / Build User Guide (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / CI Success (push) Has been cancelled
This commit was merged in pull request #6.
This commit is contained in:
@@ -294,12 +294,96 @@ impl ClonedMemoryUpdater {
|
|||||||
)
|
)
|
||||||
.expect("backward");
|
.expect("backward");
|
||||||
|
|
||||||
|
// DAgger trains on the policy's own (clamped, sometimes extreme) visited
|
||||||
|
// states, which can spike the loss; a single non-finite step would
|
||||||
|
// poison every weight via Adam. Skip the update when the loss is not
|
||||||
|
// finite, and clip gradients so an extreme-but-finite state can't blow
|
||||||
|
// the optimizer up.
|
||||||
|
if !loss_val.is_finite() {
|
||||||
|
return loss_val;
|
||||||
|
}
|
||||||
self.step += 1;
|
self.step += 1;
|
||||||
let t = self.step;
|
let t = self.step;
|
||||||
const LR: f32 = 0.01;
|
const LR: f32 = 0.01;
|
||||||
self.w_in.adam(&grad_of(&storage, in_id), LR, t);
|
const CLIP: f32 = 1.0;
|
||||||
self.w_mem.adam(&grad_of(&storage, mem_id), LR, t);
|
let clip = |mut g: Vec<f32>| {
|
||||||
self.w_read.adam(&grad_of(&storage, read_id), LR, t);
|
for v in &mut g {
|
||||||
|
*v = v.clamp(-CLIP, CLIP);
|
||||||
|
}
|
||||||
|
g
|
||||||
|
};
|
||||||
|
self.w_in.adam(&clip(grad_of(&storage, in_id)), LR, t);
|
||||||
|
self.w_mem.adam(&clip(grad_of(&storage, mem_id)), LR, t);
|
||||||
|
self.w_read.adam(&clip(grad_of(&storage, read_id)), LR, t);
|
||||||
|
loss_val
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DAgger memory-only correction step: train just the recurrence
|
||||||
|
/// (`W_in`, `W_mem`) to map `(prev_mem, x) → target_mem` at learning rate
|
||||||
|
/// `lr`, leaving the readout `W_read` untouched. This is what on-policy
|
||||||
|
/// distillation needs — it pulls the free-running memory back toward the
|
||||||
|
/// oracle trajectory without disturbing the (BC-trained) readout, which
|
||||||
|
/// already works on oracle-range memory. Gradients are clipped and a
|
||||||
|
/// non-finite step is skipped, for stability on the extreme drifted states a
|
||||||
|
/// rollout visits. Returns the memory loss before the update.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics on any shape mismatch.
|
||||||
|
pub fn train_step_memory(
|
||||||
|
&mut self,
|
||||||
|
prev_mem: &[f32],
|
||||||
|
x: &[f32],
|
||||||
|
target_mem: &[f32],
|
||||||
|
lr: f32,
|
||||||
|
) -> f32 {
|
||||||
|
let dm = self.cfg.d_mem;
|
||||||
|
let din = self.cfg.d_in;
|
||||||
|
assert_eq!(prev_mem.len(), dm);
|
||||||
|
assert_eq!(x.len(), din);
|
||||||
|
assert_eq!(target_mem.len(), dm);
|
||||||
|
|
||||||
|
let (w_in, in_id) = self.w_in.leaf(&self.dev);
|
||||||
|
let (w_mem, mem_id) = self.w_mem.leaf(&self.dev);
|
||||||
|
|
||||||
|
let mut cat = Vec::with_capacity(dm + din);
|
||||||
|
cat.extend_from_slice(prev_mem);
|
||||||
|
cat.extend_from_slice(x);
|
||||||
|
let cat_t = Ad::from_data(&cat, [1, dm + din], &self.dev);
|
||||||
|
let prev_t = Ad::from_data(prev_mem, [1, dm], &self.dev);
|
||||||
|
|
||||||
|
let h = Ad::gelu(Ad::matmul(cat_t, w_in));
|
||||||
|
let delta = Ad::matmul(h, w_mem);
|
||||||
|
let decay_t = Ad::from_data(&vec![DECAY; dm], [1, dm], &self.dev);
|
||||||
|
let new_mem = Ad::add(Ad::mul(prev_t, decay_t), delta);
|
||||||
|
|
||||||
|
let tgt_mem_t = Ad::from_data(target_mem, [1, dm], &self.dev);
|
||||||
|
let mem_err = Ad::sub(new_mem, tgt_mem_t);
|
||||||
|
let loss = Ad::sum(Ad::mul(mem_err.clone(), mem_err));
|
||||||
|
let loss_val = Ad::to_data(&loss)[0];
|
||||||
|
|
||||||
|
let storage = backward_impl(
|
||||||
|
&loss,
|
||||||
|
Some(GradTensor::from_d1(CpuBackend::ones(
|
||||||
|
[1],
|
||||||
|
&<CpuBackend as Backend>::Device::default(),
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.expect("backward");
|
||||||
|
|
||||||
|
if !loss_val.is_finite() {
|
||||||
|
return loss_val;
|
||||||
|
}
|
||||||
|
self.step += 1;
|
||||||
|
let t = self.step;
|
||||||
|
const CLIP: f32 = 1.0;
|
||||||
|
let clip = |mut g: Vec<f32>| {
|
||||||
|
for v in &mut g {
|
||||||
|
*v = v.clamp(-CLIP, CLIP);
|
||||||
|
}
|
||||||
|
g
|
||||||
|
};
|
||||||
|
self.w_in.adam(&clip(grad_of(&storage, in_id)), lr, t);
|
||||||
|
self.w_mem.adam(&clip(grad_of(&storage, mem_id)), lr, t);
|
||||||
loss_val
|
loss_val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user