ec1117

fpo2

Feb 25th, 2026
80
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 25.89 KB | None | 0 0
  1. from __future__ import annotations
  2.  
  3. from functools import partial
  4. from typing import Literal, assert_never
  5.  
  6. import jax
  7. import jax_dataclasses as jdc
  8. import mujoco_playground as mjp
  9. import optax
  10. from jax import Array
  11. from jax import numpy as jnp
  12.  
  13. from flow_policy.networks import MlpWeights
  14.  
  15. from . import math_utils, networks, rollouts
  16.  
  17.  
  18. @jdc.pytree_dataclass
  19. class FpoConfig:
  20.     # Flow parameters.
  21.     flow_steps: jdc.Static[int] = 10
  22.     output_mode: jdc.Static[Literal["u", "u_but_supervise_as_eps"]] = (
  23.         "u_but_supervise_as_eps"
  24.     )
  25.     timestep_embed_dim: jdc.Static[int] = 8
  26.     """"Must be divisible by 2."""
  27.     n_samples_per_action: jdc.Static[int] = 8
  28.     average_losses_before_exp: jdc.Static[bool] = True
  29.  
  30.     discretize_t_for_training: jdc.Static[bool] = True
  31.     feather_std: float = 0.0
  32.     policy_mlp_output_scale: float = 0.25
  33.  
  34.     loss_mode: jdc.Static[Literal["fpo", "denoising_mdp"]] = "fpo"
  35.     final_steps_only: jdc.Static[bool] = False
  36.  
  37.     # Fixed noise level for sampling via denoising MDP. This is used for
  38.     # DDPO-style policy updates.
  39.     sde_sigma: float = 0.0
  40.  
  41.     clipping_epsilon: float = 0.05
  42.  
  43.     # Based on Brax PPO config:
  44.     batch_size: jdc.Static[int] = 1024
  45.     discounting: float = 0.995
  46.     episode_length: int = 1000
  47.     learning_rate: float = 3e-4
  48.     normalize_observations: jdc.Static[bool] = True
  49.     num_envs: jdc.Static[int] = 2048
  50.     num_evals: jdc.Static[int] = 10
  51.     num_minibatches: jdc.Static[int] = 32
  52.     num_timesteps: jdc.Static[int] = 60_000_000
  53.     num_updates_per_batch: jdc.Static[int] = 16
  54.     reward_scaling: float = 10.0
  55.     unroll_length: jdc.Static[int] = 30
  56.  
  57.     gae_lambda: float = 0.95
  58.     normalize_advantage: jdc.Static[bool] = True
  59.     value_loss_coeff: float = 0.25
  60.  
  61.     def __post_init__(self) -> None:
  62.         assert self.timestep_embed_dim % 2 == 0
  63.  
  64.     @property
  65.     def iterations_per_env(self) -> int:
  66.         """Number of iterations (=policy forward passes) per environment at the
  67.        start of each training step."""
  68.         return (
  69.             self.num_minibatches * self.batch_size * self.unroll_length
  70.         ) // self.num_envs
  71.  
  72.  
  73. @jdc.pytree_dataclass
  74. class FpoParams:
  75.     policy: MlpWeights
  76.     value: MlpWeights
  77.  
  78.  
  79. @jdc.pytree_dataclass
  80. class FpoActionInfo:
  81.     loss_eps: Array  # (*, sample_dim, action_dim)
  82.     loss_t: Array  # (*, sample_dim, 1)
  83.     initial_cfm_loss: Array  # (*,)
  84.  
  85.  
  86. @jdc.pytree_dataclass
  87. class DenoisingMdpActionInfo:
  88.     """For treating the denoising chain as an MDP."""
  89.  
  90.     full_x_t_path: Array  # (*, flow_steps, action_dim)
  91.     initial_log_likelihood: Array  # (*, flow_steps)
  92.  
  93.  
  94. @jdc.pytree_dataclass
  95. class FlowSchedule:
  96.     t_current: Array  # (*, flow_steps) - timesteps at the start of each step
  97.     t_next: Array  # (*, flow_steps) - timesteps at the end of each step
  98.  
  99.  
  100. FpoTransition = rollouts.TransitionStruct[FpoActionInfo | DenoisingMdpActionInfo]
  101.  
  102.  
  103. @jdc.pytree_dataclass
  104. class FpoState:
  105.     """PPO agent state."""
  106.  
  107.     env: jdc.Static[mjp.MjxEnv]
  108.     config: FpoConfig
  109.     params: FpoParams
  110.     obs_stats: math_utils.RunningStats
  111.  
  112.     opt: jdc.Static[optax.GradientTransformation]
  113.     opt_state: optax.OptState
  114.  
  115.     prng: Array
  116.     steps: Array
  117.  
  118.     @staticmethod
  119.     @jdc.jit
  120.     def init(prng: Array, env: jdc.Static[mjp.MjxEnv], config: FpoConfig) -> FpoState:
  121.         obs_size = env.observation_size
  122.         action_size = env.action_size
  123.         assert isinstance(obs_size, int)
  124.  
  125.         prng0, prng1, prng2 = jax.random.split(prng, num=3)
  126.         actor_net = networks.mlp_init(
  127.             # Policy takes both observation and action as input. We'll just concatenate them!
  128.             prng0,
  129.             (
  130.                 obs_size + action_size + config.timestep_embed_dim,
  131.                 32,
  132.                 32,
  133.                 32,
  134.                 32,
  135.                 action_size,
  136.             ),
  137.         )
  138.         critic_net = networks.mlp_init(prng1, (obs_size, 256, 256, 256, 256, 256, 1))
  139.  
  140.         network_params = FpoParams(actor_net, critic_net)
  141.  
  142.         # We'll manage learning rate ourselves!
  143.         opt = optax.scale_by_adam()
  144.         return FpoState(
  145.             env=env,
  146.             config=config,
  147.             params=network_params,
  148.             obs_stats=math_utils.RunningStats.init((obs_size,)),
  149.             opt=opt,
  150.             opt_state=opt.init(network_params),  # type: ignore
  151.             prng=prng2,
  152.             steps=jnp.zeros((), dtype=jnp.int32),
  153.         )
  154.  
  155.     def get_schedule(self) -> FlowSchedule:
  156.         full_t_path = jnp.linspace(1.0, 0.0, self.config.flow_steps + 1)
  157.         t_current = full_t_path[:-1]
  158.         return FlowSchedule(
  159.             t_current=t_current,
  160.             t_next=full_t_path[1:],
  161.         )
  162.  
  163.     def embed_timestep(self, t: Array) -> Array:
  164.         """Embed (*, 1) timestep into (*, timestep_embed_dim)."""
  165.         assert t.shape[-1] == 1
  166.         freqs = 2 ** jnp.arange(self.config.timestep_embed_dim // 2)
  167.         scaled_t = t * freqs
  168.         out = jnp.concatenate([jnp.cos(scaled_t), jnp.sin(scaled_t)], axis=-1)
  169.         assert out.shape == (*t.shape[:-1], self.config.timestep_embed_dim)
  170.         return out
  171.  
  172.     def _compute_cfm_loss(
  173.         self,
  174.         obs_norm: Array,
  175.         action: Array,
  176.         eps: Array,
  177.         t: Array,
  178.     ) -> Array:
  179.         """Computes from:
  180.        - obs_norm: (*, obs_dim)
  181.        - action: (*, action_dim)
  182.  
  183.        A CFM loss term with shape:
  184.        - (*,)
  185.  
  186.        That is, one per obs-action pair, which is averaged across
  187.        `n_samples_per_action` sampled eps-t pairs.
  188.        """
  189.         # Compute flow matching terms.
  190.         (*batch_dims, action_dim) = action.shape
  191.         samples_dim = self.config.n_samples_per_action
  192.         obs_dim = self.env.observation_size
  193.         sample_shape = (*batch_dims, samples_dim)
  194.         assert eps.shape == (*batch_dims, samples_dim, action_dim)
  195.         assert t.shape == (*batch_dims, samples_dim, 1)
  196.         x_t = t * eps + (1.0 - t) * action[..., None, :]
  197.         network_pred = (
  198.             networks.flow_mlp_fwd(
  199.                 self.params.policy,
  200.                 jnp.broadcast_to(obs_norm[..., None, :], (*sample_shape, obs_dim)),
  201.                 x_t,
  202.                 self.embed_timestep(t),
  203.             )
  204.             * self.config.policy_mlp_output_scale
  205.         )
  206.         if self.config.output_mode == "u":
  207.             velocity_pred = network_pred
  208.             velocity_gt = eps - action[..., None, :]  # u = x1 - x0
  209.             out = jnp.mean((velocity_pred - velocity_gt) ** 2, axis=-1)
  210.         elif self.config.output_mode == "u_but_supervise_as_eps":
  211.             # We want to compute velocity_pred => x1_pred.
  212.             velocity_pred = network_pred  # x1 - x0
  213.             x0_pred = x_t - t * velocity_pred
  214.             x1_pred = x0_pred + velocity_pred
  215.             out = jnp.mean((eps - x1_pred) ** 2, axis=-1)
  216.         else:
  217.             assert_never(self.config.output_mode)
  218.  
  219.         assert out.shape == (*batch_dims, samples_dim)
  220.         return out
  221.  
  222.     def _compute_denoising_log_likelihood(
  223.         self,
  224.         obs_norm: Array,
  225.         x_t_path: Array,
  226.     ) -> Array:
  227.         """Computes log likelihood for each Euler step in a denoising path. This is used for MDP / DPPO experiments.
  228.  
  229.        Args:
  230.            obs_norm: (*, obs_dim) - normalized observation
  231.            x_t_path: (*, flow_steps+1, action_dim) - states at each timestep (including final x0)
  232.  
  233.        Returns:
  234.            log_likelihood: (*, flow_steps) - log likelihood for each step
  235.        """
  236.         (*batch_dims, total_states, action_dim) = x_t_path.shape
  237.  
  238.         schedule = self.get_schedule()
  239.         flow_steps = schedule.t_current.shape[0]
  240.  
  241.         # Verify input shapes.
  242.         assert total_states == flow_steps + 1, (
  243.             f"Expected {flow_steps + 1} states, got {total_states}"
  244.         )
  245.         assert x_t_path.shape == (*batch_dims, flow_steps + 1, action_dim)
  246.         assert schedule.t_current.shape == (flow_steps,)
  247.         assert schedule.t_next.shape == (flow_steps,)
  248.         assert obs_norm.shape == (*batch_dims, self.env.observation_size)
  249.  
  250.         # Extract states for all transitions.
  251.         x_t = x_t_path[..., :-1, :]  # (*, flow_steps, action_dim) - start states
  252.         x_t_next = x_t_path[..., 1:, :]  # (*, flow_steps, action_dim) - end states
  253.         assert x_t.shape == (*batch_dims, flow_steps, action_dim)
  254.         assert x_t_next.shape == (*batch_dims, flow_steps, action_dim)
  255.  
  256.         # Compute dt from the actual timestep differences.
  257.         dt = schedule.t_next - schedule.t_current  # (flow_steps,)
  258.         assert dt.shape == (flow_steps,)
  259.  
  260.         # Get predicted reverse velocities for all steps at once.
  261.         velocity_pred = (
  262.             networks.flow_mlp_fwd(
  263.                 self.params.policy,
  264.                 jnp.broadcast_to(
  265.                     obs_norm[..., None, :],
  266.                     (*batch_dims, flow_steps, obs_norm.shape[-1]),
  267.                 ),
  268.                 x_t,
  269.                 jnp.broadcast_to(
  270.                     self.embed_timestep(schedule.t_current[..., None]),
  271.                     (
  272.                         *batch_dims,
  273.                         self.config.flow_steps,
  274.                         self.config.timestep_embed_dim,
  275.                     ),
  276.                 ),
  277.             )
  278.             * self.config.policy_mlp_output_scale
  279.         )
  280.  
  281.         # Simple expected next state with fixed sigma.
  282.         assert velocity_pred.shape == x_t.shape == (*batch_dims, flow_steps, action_dim)
  283.         expected_x_t_next = x_t + dt[None, :, None] * velocity_pred
  284.         assert expected_x_t_next.shape == x_t_next.shape
  285.  
  286.         # Compute realized noise with fixed sigma.
  287.         realized_noise = (x_t_next - expected_x_t_next) / (
  288.             self.config.sde_sigma + 1e-6
  289.         )[None, :, None]
  290.         assert realized_noise.shape == x_t_next.shape
  291.  
  292.         # Log probability of standard normal: -0.5 * ||z||^2 - d/2 * log(2pi).
  293.         all_log_likelihoods = (
  294.             -0.5 * jnp.sum(realized_noise**2, axis=-1)  # (*, flow_steps)
  295.             - 0.5 * action_dim * jnp.log(2 * jnp.pi)
  296.         )
  297.         assert all_log_likelihoods.shape == (*batch_dims, flow_steps)
  298.         return all_log_likelihoods
  299.  
  300.     def sample_action(
  301.         self, obs: Array, prng: Array, deterministic: bool
  302.     ) -> tuple[Array, FpoActionInfo | DenoisingMdpActionInfo]:
  303.         """Sample an action from the policy given an observation."""
  304.         if self.config.normalize_observations:
  305.             obs_norm = (obs - self.obs_stats.mean) / self.obs_stats.std
  306.         else:
  307.             obs_norm = obs
  308.  
  309.         (*batch_dims, obs_dim) = obs.shape
  310.         assert obs_dim == self.env.observation_size
  311.  
  312.         def euler_step(
  313.             carry: Array, inputs: tuple[FlowSchedule, Array]
  314.         ) -> tuple[Array, Array]:
  315.             x_t = carry
  316.             assert x_t.shape == (*batch_dims, self.env.action_size)
  317.             schedule_t, noise = inputs
  318.             assert schedule_t.t_current.shape == ()
  319.             assert schedule_t.t_next.shape == ()
  320.             assert noise.shape == x_t.shape
  321.  
  322.             # Compute dt as the difference between current and next timestep
  323.             dt = schedule_t.t_next - schedule_t.t_current
  324.  
  325.             # Get velocity from flow model using the current timestep
  326.             # This is the reverse velocity, which takes us from t=1 to t=0!
  327.             velocity = (
  328.                 networks.flow_mlp_fwd(
  329.                     self.params.policy,
  330.                     obs_norm,
  331.                     x_t,
  332.                     jnp.broadcast_to(
  333.                         self.embed_timestep(schedule_t.t_current[None]),
  334.                         (*batch_dims, self.config.timestep_embed_dim),
  335.                     ),
  336.                 )
  337.                 * self.config.policy_mlp_output_scale
  338.             )
  339.  
  340.             # Simple SDE with fixed sigma - no time-dependent noise.
  341.             x_t_next = x_t + dt * velocity + self.config.sde_sigma * noise
  342.             assert x_t_next.shape == x_t.shape
  343.             return x_t_next, x_t
  344.  
  345.         prng_sample, prng_loss, prng_feather, prng_noise = jax.random.split(prng, num=4)
  346.  
  347.         # Generate full timestep path and slice it for current/next pairs
  348.         noise_path = jax.random.normal(
  349.             prng_noise,
  350.             (self.config.flow_steps, *batch_dims, self.env.action_size),
  351.         )
  352.         x0, x_t_path = jax.lax.scan(
  353.             euler_step,
  354.             init=jax.random.normal(prng_sample, (*batch_dims, self.env.action_size)),
  355.             xs=(self.get_schedule(), noise_path),
  356.         )
  357.  
  358.         if not deterministic:
  359.             # Perturb the action with noise.
  360.             perturb = (
  361.                 jax.random.normal(prng_feather, (*batch_dims, self.env.action_size))
  362.                 * self.config.feather_std
  363.             )
  364.             x0 = x0 + perturb
  365.  
  366.         # Create action info based on loss mode
  367.         if self.config.loss_mode == "fpo":
  368.             # Sample eps and t for FPO loss.
  369.             sample_shape = (*batch_dims, self.config.n_samples_per_action)
  370.             prng_eps, prng_t = jax.random.split(prng_loss)
  371.             eps = jax.random.normal(prng_eps, (*sample_shape, self.env.action_size))
  372.             if self.config.discretize_t_for_training:
  373.                 t = self.get_schedule().t_current[
  374.                     jax.random.randint(
  375.                         prng_t,
  376.                         shape=(*sample_shape, 1),
  377.                         minval=0,
  378.                         maxval=self.config.flow_steps,
  379.                     )
  380.                 ]
  381.             else:
  382.                 t = jax.random.uniform(prng_t, (*sample_shape, 1))
  383.             initial_cfm_loss = self._compute_cfm_loss(obs_norm, x0, eps=eps, t=t)
  384.  
  385.             return x0, FpoActionInfo(
  386.                 loss_eps=eps,
  387.                 loss_t=t,
  388.                 initial_cfm_loss=initial_cfm_loss,
  389.             )
  390.         else:  # denoising_mdp
  391.             # x_t_path contains states at the START of each Euler step (from scan).
  392.             # x0 is the final state at t=0.
  393.             # For the MDP, we track all states in the denoising chain.
  394.             assert x_t_path.shape == (
  395.                 self.config.flow_steps,
  396.                 *batch_dims,
  397.                 self.env.action_size,
  398.             )
  399.             assert x0.shape == (*batch_dims, self.env.action_size)
  400.  
  401.             # Move flow_steps dimension from first to second-to-last position.
  402.             mdp_x_t_path = jnp.moveaxis(x_t_path, 0, -2)
  403.             assert mdp_x_t_path.shape == (
  404.                 *batch_dims,
  405.                 self.config.flow_steps,
  406.                 self.env.action_size,
  407.             )
  408.  
  409.             # Append x0 to create full path for likelihood computation.
  410.             full_x_t_path = jnp.concatenate([mdp_x_t_path, x0[..., None, :]], axis=-2)
  411.             assert full_x_t_path.shape == (
  412.                 *batch_dims,
  413.                 self.config.flow_steps + 1,
  414.                 self.env.action_size,
  415.             )
  416.  
  417.             # Compute initial log likelihood for each Euler step.
  418.             # Use full_x_t_path which has flow_steps+1 states for flow_steps transitions.
  419.             initial_log_likelihood = self._compute_denoising_log_likelihood(
  420.                 obs_norm, full_x_t_path
  421.             )
  422.             assert initial_log_likelihood.shape == (*batch_dims, self.config.flow_steps)
  423.  
  424.             return x0, DenoisingMdpActionInfo(
  425.                 full_x_t_path=full_x_t_path,  # Store only flow_steps states for MDP
  426.                 initial_log_likelihood=initial_log_likelihood,
  427.             )
  428.  
  429.     @jdc.jit
  430.     def training_step(
  431.         self, transitions: FpoTransition
  432.     ) -> tuple[FpoState, dict[str, Array]]:
  433.         # We're use a (T, B) shape convention, corresponding to a "scan of the
  434.         # vmap" and not a "vmap of the scan".
  435.         config = self.config
  436.         assert transitions.reward.shape == (config.iterations_per_env, config.num_envs)
  437.  
  438.         # Update observation statistics.
  439.         state = self
  440.         if config.normalize_observations:
  441.             with jdc.copy_and_mutate(state) as state:
  442.                 state.obs_stats = state.obs_stats.update(transitions.obs)
  443.         del self
  444.  
  445.         def step_batch(state: FpoState, _):
  446.             step_prng = jax.random.fold_in(state.prng, state.steps)
  447.             state, metrics = jax.lax.scan(
  448.                 partial(
  449.                     FpoState._step_minibatch, prng=jax.random.fold_in(step_prng, 0)
  450.                 ),
  451.                 init=state,
  452.                 xs=transitions.prepare_minibatches(
  453.                     step_prng, config.num_minibatches, config.batch_size
  454.                 ),
  455.             )
  456.             return state, metrics
  457.  
  458.         # Do N updates over the full batch of transitions.
  459.         state, metrics = jax.lax.scan(
  460.             step_batch,
  461.             init=state,
  462.             length=config.num_updates_per_batch,
  463.         )
  464.  
  465.         return state, metrics
  466.  
  467.     def _step_minibatch(
  468.         self, transitions: FpoTransition, prng: Array
  469.     ) -> tuple[FpoState, dict[str, Array]]:
  470.         """One training step over a minibatch of transitions."""
  471.  
  472.         assert transitions.reward.shape == (
  473.             self.config.unroll_length,
  474.             self.config.batch_size,
  475.         )
  476.         (loss, metrics), grads = jax.value_and_grad(
  477.             lambda params: FpoState._compute_fpo_loss(
  478.                 jdc.replace(self, params=params),
  479.                 transitions,
  480.                 prng,
  481.             ),
  482.             has_aux=True,
  483.         )(self.params)
  484.         assert isinstance(grads, FpoParams)
  485.         assert isinstance(loss, Array)
  486.         assert isinstance(metrics, dict)
  487.  
  488.         param_update, new_opt_state = self.opt.update(grads, self.opt_state)  # type: ignore
  489.         param_update = jax.tree.map(
  490.             lambda x: -self.config.learning_rate * x, param_update
  491.         )
  492.         with jdc.copy_and_mutate(self) as state:
  493.             state.params = jax.tree.map(jnp.add, self.params, param_update)
  494.             state.opt_state = new_opt_state
  495.             state.steps = state.steps + 1
  496.         return state, metrics
  497.  
  498.     def _compute_fpo_loss(
  499.         self, transitions: FpoTransition, prng: Array
  500.     ) -> tuple[Array, dict[str, Array]]:
  501.         del prng  # Unused for now.
  502.  
  503.         (timesteps, batch_dim) = transitions.reward.shape
  504.         assert transitions.obs.shape == (
  505.             timesteps,
  506.             batch_dim,
  507.             self.env.observation_size,
  508.         )
  509.         assert transitions.action.shape == (
  510.             timesteps,
  511.             batch_dim,
  512.             self.env.action_size,
  513.         )
  514.  
  515.         metrics = dict[str, Array]()
  516.  
  517.         if self.config.normalize_observations:
  518.             obs_norm = (transitions.obs - self.obs_stats.mean) / self.obs_stats.std
  519.         else:
  520.             obs_norm = transitions.obs
  521.         value_pred = networks.value_mlp_fwd(self.params.value, obs_norm)
  522.         assert value_pred.shape == (timesteps, batch_dim)
  523.  
  524.         bootstrap_obs_norm = (
  525.             transitions.next_obs[-1:, :, :] - self.obs_stats.mean
  526.         ) / self.obs_stats.std
  527.         bootstrap_value = networks.value_mlp_fwd(self.params.value, bootstrap_obs_norm)
  528.         assert bootstrap_value.shape == (1, batch_dim)
  529.  
  530.         gae_vs, gae_advantages = jax.lax.stop_gradient(
  531.             rollouts.compute_gae(
  532.                 truncation=transitions.truncation,
  533.                 discount=transitions.discount * self.config.discounting,
  534.                 rewards=transitions.reward * self.config.reward_scaling,
  535.                 values=value_pred,
  536.                 bootstrap_value=bootstrap_value,
  537.                 gae_lambda=self.config.gae_lambda,
  538.             )
  539.         )
  540.  
  541.         # Log advantage statistics before normalization
  542.         metrics["advantages_mean"] = jnp.mean(gae_advantages)
  543.         metrics["advantages_std"] = jnp.std(gae_advantages)
  544.         metrics["advantages_min"] = jnp.min(gae_advantages)
  545.         metrics["advantages_max"] = jnp.max(gae_advantages)
  546.  
  547.         if self.config.normalize_advantage:
  548.             gae_advantages = (gae_advantages - gae_advantages.mean()) / (
  549.                 gae_advantages.std() + 1e-8
  550.             )
  551.  
  552.         # Compute policy ratio based on loss mode
  553.         if self.config.loss_mode == "fpo":
  554.             # Original FPO loss computation
  555.             assert isinstance(transitions.action_info, FpoActionInfo)
  556.  
  557.             # Check action info shapes
  558.             assert transitions.action_info.loss_eps.shape == (
  559.                 timesteps,
  560.                 batch_dim,
  561.                 self.config.n_samples_per_action,
  562.                 self.env.action_size,
  563.             )
  564.             assert transitions.action_info.loss_t.shape == (
  565.                 timesteps,
  566.                 batch_dim,
  567.                 self.config.n_samples_per_action,
  568.                 1,
  569.             )
  570.             assert transitions.action_info.initial_cfm_loss.shape == (
  571.                 timesteps,
  572.                 batch_dim,
  573.                 self.config.n_samples_per_action,
  574.             )
  575.  
  576.             cfm_loss = self._compute_cfm_loss(
  577.                 obs_norm,
  578.                 transitions.action,
  579.                 eps=transitions.action_info.loss_eps,
  580.                 t=transitions.action_info.loss_t,
  581.             )
  582.             assert cfm_loss.shape == transitions.action_info.initial_cfm_loss.shape
  583.  
  584.             if self.config.average_losses_before_exp:
  585.                 rho_s = jnp.exp(
  586.                     jnp.mean(
  587.                         transitions.action_info.initial_cfm_loss,
  588.                         axis=-1,
  589.                         keepdims=True,
  590.                     )
  591.                     - jnp.mean(
  592.                         cfm_loss,
  593.                         axis=-1,
  594.                         keepdims=True,
  595.                     )
  596.                 )
  597.                 assert rho_s.shape == (
  598.                     timesteps,
  599.                     batch_dim,
  600.                     1,
  601.                 )
  602.             else:
  603.                 # Compute FPO ratio. We clip before exponentiation to prevent
  604.                 # outliers from blowing up the loss; this is optional.
  605.                 rho_s = jnp.exp(
  606.                     jnp.clip(
  607.                         transitions.action_info.initial_cfm_loss - cfm_loss, -3.0, 3.0
  608.                     )
  609.                 )
  610.                 assert rho_s.shape == (
  611.                     timesteps,
  612.                     batch_dim,
  613.                     self.config.n_samples_per_action,
  614.                 )
  615.         else:  # denoising_mdp
  616.             # Compute policy ratio for denoising MDP
  617.             assert isinstance(transitions.action_info, DenoisingMdpActionInfo)
  618.  
  619.             # Check action info shapes
  620.             assert transitions.action_info.full_x_t_path.shape == (
  621.                 timesteps,
  622.                 batch_dim,
  623.                 self.config.flow_steps + 1,
  624.                 self.env.action_size,
  625.             )
  626.             assert transitions.action_info.initial_log_likelihood.shape == (
  627.                 timesteps,
  628.                 batch_dim,
  629.                 self.config.flow_steps,
  630.             )
  631.  
  632.             # Compute current log likelihoods for each step in the denoising chain
  633.             current_log_likelihood = self._compute_denoising_log_likelihood(
  634.                 obs_norm, transitions.action_info.full_x_t_path
  635.             )
  636.             assert (
  637.                 current_log_likelihood.shape
  638.                 == transitions.action_info.initial_log_likelihood.shape
  639.             )
  640.  
  641.             # Sum log likelihoods across flow steps to get joint probability.
  642.             rho_s = jnp.exp(
  643.                 current_log_likelihood - transitions.action_info.initial_log_likelihood,
  644.             )
  645.             if self.config.final_steps_only:
  646.                 # Use only the final steps for likelihood.
  647.                 rho_s = rho_s[..., self.config.flow_steps // 2 :]
  648.  
  649.         # Shared PPO loss computation
  650.         assert gae_advantages.shape == (timesteps, batch_dim)
  651.  
  652.         surrogate_loss1 = rho_s * gae_advantages[..., None]
  653.         surrogate_loss2 = (
  654.             jnp.clip(
  655.                 rho_s,
  656.                 1 - self.config.clipping_epsilon,
  657.                 1 + self.config.clipping_epsilon,
  658.             )
  659.             * gae_advantages[..., None]
  660.         )
  661.  
  662.         # Check that surrogate losses have the same shape as rho_s
  663.         assert surrogate_loss1.shape == rho_s.shape
  664.         assert surrogate_loss2.shape == rho_s.shape
  665.  
  666.         policy_loss = -jnp.mean(jnp.minimum(surrogate_loss1, surrogate_loss2))
  667.  
  668.         # Metrics
  669.         metrics["clipped_ratio_mean"] = jnp.mean(
  670.             jnp.abs(rho_s - 1.0) > self.config.clipping_epsilon
  671.         )
  672.         metrics["policy_ratio_mean"] = jnp.mean(rho_s)
  673.         metrics["policy_ratio_min"] = jnp.min(rho_s)
  674.         metrics["policy_ratio_max"] = jnp.max(rho_s)
  675.         metrics["policy_loss"] = policy_loss
  676.         metrics["surrogate_loss1_mean"] = jnp.mean(surrogate_loss1)
  677.         metrics["surrogate_loss2_mean"] = jnp.mean(surrogate_loss2)
  678.  
  679.         # Log action distribution statistics
  680.         metrics["action_min"] = jnp.min(transitions.action)
  681.         metrics["action_max"] = jnp.max(transitions.action)
  682.  
  683.         # Don't supervise value function on truncated timesteps.
  684.         v_error = (gae_vs - value_pred) * (1 - transitions.truncation)
  685.  
  686.         # Value function statistics
  687.         metrics["value_mean"] = jnp.mean(value_pred)
  688.         metrics["value_std"] = jnp.std(value_pred)
  689.         metrics["value_min"] = jnp.min(value_pred)
  690.         metrics["value_max"] = jnp.max(value_pred)
  691.         metrics["value_target_mean"] = jnp.mean(gae_vs)
  692.         metrics["value_error_mean"] = jnp.mean(v_error)
  693.         metrics["value_error_std"] = jnp.std(v_error)
  694.  
  695.         v_loss = jnp.mean(v_error**2) * self.config.value_loss_coeff
  696.         metrics["v_loss"] = v_loss
  697.  
  698.         # Compute the total loss that will be used for optimization
  699.         total_loss = policy_loss + v_loss
  700.  
  701.         return total_loss, metrics
  702.  
Advertisement
Add Comment
Please, Sign In to add comment