Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lerobot/policies/pi0/modeling_pi0.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ def denoise_step_partial_call(input_x_t, current_timestep=expanded_time):
time=time,
original_denoise_step_partial=denoise_step_partial_call,
execution_horizon=execution_horizon,
num_flow_matching_steps=num_steps,
)
else:
v_t = denoise_step_partial_call(x_t)
Expand Down
1 change: 1 addition & 0 deletions src/lerobot/policies/pi05/modeling_pi05.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,7 @@ def denoise_step_partial_call(input_x_t, current_timestep=expanded_time):
time=time,
original_denoise_step_partial=denoise_step_partial_call,
execution_horizon=execution_horizon,
num_flow_matching_steps=num_steps,
)
else:
v_t = denoise_step_partial_call(x_t)
Expand Down
3 changes: 3 additions & 0 deletions src/lerobot/policies/rtc/configuration_rtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class RTCConfig:
debug: bool = False
debug_maxlen: int = 100

use_soare_optimization: bool = True
variance_clipping_factor: float = 0.2

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you meant: sigma_d: float = 1.0 instead of variance_clipping_factor (or if you prefer a more descriptive parameter, prior_variance, but be careful because "variance" is sigma_d ** 2). That parameter is used in all cases. When it is 1.0 you are not using the improvement suggested in my article. Otherwise you are. And therefore, you can drop use_soare_optimization altogether, and don't need to guard any code with if use_soare_optimization

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, as per my article, it's max_guidance_weight that you would set equal to num_flow_matching_steps. In the RTC paper they don't give guidance for that, and just suggest setting it to 5.0.


def __post_init__(self):
"""Validate RTC configuration parameters."""
if self.max_guidance_weight <= 0:
Expand Down
19 changes: 18 additions & 1 deletion src/lerobot/policies/rtc/modeling_rtc.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ def denoise_step(
time,
original_denoise_step_partial,
execution_horizon=None,
num_flow_matching_steps=None,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest not passing this here, as the total number of denoising steps shouldn't be the concern of a single denoising step. My article's guidance is to set max_guidance_weight = num_steps. I'm fairly convinced that is the correct thing to do, enough so that I would recommend just forcing to default to this unless the user explicitly provides max_guidance_weight. I also note that you have already defaulted it to 10, which is not the value of 5 that the original RTC paper suggests (which is fine IMO, but just showing that there are already deviations from the original specification, so we might as well ground it with my article's guidance).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. I used 10, because by default, num_inference_steps: int = 10 , so 5 won't work well for Lerobot policies with default config. Probably, PI used 5 during testing RTC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it, and after this returned the logic back. pi0.x pass num steps as parameter to the predic_action_chunk

) -> Tensor:
"""RTC guidance wrapper around an existing denoiser.

Expand Down Expand Up @@ -163,6 +164,9 @@ def denoise_step(
# So we need to invert the time
tau = 1 - time

if self.config.use_soare_optimization and num_flow_matching_steps is None:
raise ValueError("num_flow_matching_steps must be provided when use_soare_optimization is True")

if prev_chunk_left_over is None:
# First step, no guidance - return v_t
v_t = original_denoise_step_partial(x_t)
Expand Down Expand Up @@ -217,10 +221,23 @@ def denoise_step(
grad_outputs = err.clone().detach()
correction = torch.autograd.grad(x1_t, x_t, grad_outputs, retain_graph=False)[0]

max_guidance_weight = self.rtc_config.max_guidance_weight

# Check the following paper - https://alexander-soare.github.io/robotics/2025/08/05/smooth-as-butter-robot-policies.html
# num of steps could be used as clipping parameter without requirements on hyperparameters tuning
if self.config.use_soare_optimization:
max_guidance_weight = num_flow_matching_steps

max_guidance_weight = torch.as_tensor(self.rtc_config.max_guidance_weight)
tau_tensor = torch.as_tensor(tau)
squared_one_minus_tau = (1 - tau_tensor) ** 2
inv_r2 = (squared_one_minus_tau + tau_tensor**2) / (squared_one_minus_tau)
if self.config.use_soare_optimization:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my comments above. This whole block won't need this if else guard for use_soare_optimization.
You just need
inv_r2 = (squared_one_minus_tau + tau_tensor ** 2 * sigma_d ** 2) / (squared_one_minus_tau * sigma_d ** 2)
or if you are going to call it prior_variance instead, since that's already σ² it would be:
inv_r2 = (squared_one_minus_tau + tau_tensor ** 2 * prior_variance) / (squared_one_minus_tau * prior_variance)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then setting sigma_d = 1.0 reverts to the original RTC implementation.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw this is just eqn 8 in my article

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

variance_clipping_factor = torch.as_tensor(self.rtc_config.variance_clipping_factor)
inv_r2 = (squared_one_minus_tau + tau_tensor**2 * variance_clipping_factor) / (
squared_one_minus_tau * variance_clipping_factor
)
else:
inv_r2 = (squared_one_minus_tau + tau_tensor**2) / (squared_one_minus_tau)
c = torch.nan_to_num((1 - tau_tensor) / tau_tensor, posinf=max_guidance_weight)
guidance_weight = torch.nan_to_num(c * inv_r2, posinf=max_guidance_weight)
guidance_weight = torch.minimum(guidance_weight, max_guidance_weight)
Expand Down
1 change: 1 addition & 0 deletions src/lerobot/policies/smolvla/modeling_smolvla.py
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,7 @@ def denoise_step_partial_call(input_x_t, current_timestep=expanded_time):
time=time,
original_denoise_step_partial=denoise_step_partial_call,
execution_horizon=execution_horizon,
num_flow_matching_steps=self.config.num_steps,
)
else:
v_t = denoise_step_partial_call(x_t)
Expand Down