Skip to content
Merged
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
14 changes: 11 additions & 3 deletions src/gfn/gflownet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ def logz_parameters(self) -> list[torch.Tensor]:
def get_scores(
self,
trajectories: Trajectories,
log_rewards: torch.Tensor | None = None,
recalculate_all_logprobs: bool = True,
Comment on lines 420 to 423

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Adding log_rewards before recalculate_all_logprobs changes the positional calling convention for get_scores(). Existing code that previously did get_scores(trajs, False, env) would now pass False as log_rewards, breaking backward compatibility. Consider keeping existing positional args intact and making log_rewards keyword-only (e.g., place it after env / behind a *).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in commit 11caee9. Made log_rewards keyword-only (using *) in all public loss() and get_scores() methods across base.py, trajectory_balance.py, detailed_balance.py, flow_matching.py, sub_trajectory_balance.py, and the tutorial subclass. The existing positional parameters (env, trajectories/transitions, recalculate_all_logprobs, reduction) remain in their original positions, so callers using positional args continue to work unchanged.

env: Env | None = None,
) -> torch.Tensor:
Expand All @@ -430,6 +431,11 @@ def get_scores(

Args:
trajectories: The Trajectories object to evaluate.
log_rewards: Optional custom log rewards tensor of shape
(n_trajectories,). When None, uses the environment rewards
from the trajectories. Useful for intrinsic rewards (see
"Towards Improving Exploration through Sibling Augmented
GFlowNets", Madan et al., ICLR 2025).
recalculate_all_logprobs: Whether to re-evaluate all logprobs.
env: The environment (unused in base TB, but required by some
subclasses such as RTB and SubTB).
Expand All @@ -448,11 +454,13 @@ def get_scores(
total_log_pf_trajectories = log_pf_trajectories.sum(dim=0) # [N]
total_log_pb_trajectories = log_pb_trajectories.sum(dim=0) # [N]

# cast: log_rewards is always set for terminating trajectories;
# assert is behind debug to avoid graph breaks in torch.compile.
log_rewards = cast(torch.Tensor, trajectories.log_rewards)
# Use custom log_rewards if provided, otherwise fall back to the
# trajectory's environment rewards.
if log_rewards is None:
log_rewards = cast(torch.Tensor, trajectories.log_rewards)
if self.debug:
assert log_rewards is not None
assert log_rewards.shape == (trajectories.batch_size,)
# Fast path: skip clamp when log_reward_clip_min is disabled (default).
if math.isfinite(self.log_reward_clip_min):
log_rewards = log_rewards.clamp_min(self.log_reward_clip_min)
Expand Down
18 changes: 15 additions & 3 deletions src/gfn/gflownet/detailed_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def get_scores(
self,
env: Env,
transitions: Transitions,
log_rewards: torch.Tensor | None = None,

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Adding log_rewards before recalculate_all_logprobs changes the positional calling convention for get_scores(env, transitions, ...). Existing code that called get_scores(env, transitions, False) will now pass False as log_rewards. To keep backward compatibility, make log_rewards keyword-only or move it after the existing optional args.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in commit 11caee9. log_rewards is now keyword-only in DBGFlowNet.get_scores(), preserving (env, transitions, recalculate_all_logprobs) positional order.

recalculate_all_logprobs: bool = True,
) -> torch.Tensor:
r"""Calculates the scores for a batch of transitions.
Expand All @@ -219,6 +220,11 @@ def get_scores(
Args:
env: The environment where the transitions are sampled from.
transitions: The Transitions object to evaluate.
log_rewards: Optional custom log rewards tensor of shape
(n_transitions,). When None, uses the environment rewards
from the transitions. Useful for intrinsic rewards (see
"Towards Improving Exploration through Sibling Augmented
GFlowNets", Madan et al., ICLR 2025).
recalculate_all_logprobs: Whether to re-evaluate all logprobs.

Returns:
Expand Down Expand Up @@ -261,11 +267,13 @@ def get_scores(
is_terminating = transitions.is_terminating
is_intermediate = ~is_terminating

# cast: log_rewards is always populated for valid transitions;
# assert is behind debug to avoid graph breaks in torch.compile.
log_rewards = cast(torch.Tensor, transitions.log_rewards)
# Use custom log_rewards if provided, otherwise fall back to the
# transition's environment rewards.
if log_rewards is None:
log_rewards = cast(torch.Tensor, transitions.log_rewards)
if self.debug:
Comment on lines +286 to 290

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

When forward_looking=True, get_scores() still calls env.log_reward(...) for the forward-looking reward adjustments, even if a custom log_rewards tensor is provided. This means custom rewards do not fully replace environment rewards in forward-looking mode (only the terminating boundary condition uses log_rewards). Either document this limitation explicitly or extend the API (e.g., accept separate forward-looking rewards / raise when both are used).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

for now - raise when both are used.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Done in commit 001ecd3. DBGFlowNet.get_scores() now raises ValueError immediately when both forward_looking=True and a custom log_rewards tensor are provided. The docstring was also updated to document this restriction. A new test test_forward_looking_raises_with_custom_log_rewards covers this behavior.

assert log_rewards is not None
assert log_rewards.shape == (transitions.n_transitions,)
if math.isfinite(self.log_reward_clip_min):
log_rewards = log_rewards.clamp_min(self.log_reward_clip_min)
log_F_s_next[is_terminating] = log_rewards[is_terminating]
Expand Down Expand Up @@ -332,6 +340,7 @@ def loss(
self,
env: Env,
transitions: Transitions,
log_rewards: torch.Tensor | None = None,
recalculate_all_logprobs: bool = True,
reduction: str = "mean",
Comment on lines 355 to 360

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Same backward-compatibility concern for loss(): inserting log_rewards before recalculate_all_logprobs means a call like loss(env, transitions, False) would now pass False as log_rewards. Consider making log_rewards keyword-only (or appending it after the existing optional args) to avoid breaking positional callers.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in commit 11caee9. log_rewards is now keyword-only in DBGFlowNet.loss(), preserving (env, transitions, recalculate_all_logprobs, reduction) positional order.

) -> torch.Tensor:
Expand All @@ -343,6 +352,8 @@ def loss(
Args:
env: The environment where the transitions are sampled from.
transitions: The Transitions object to compute the loss with.
log_rewards: Optional custom log rewards tensor of shape
(n_transitions,). When None, uses the environment rewards.
recalculate_all_logprobs: Whether to re-evaluate all logprobs.
reduction: The reduction method to use ('mean', 'sum', or 'none').
Run with self.debug=False for improved performance.
Expand All @@ -356,6 +367,7 @@ def loss(
scores = self.get_scores(
env,
transitions,
log_rewards=log_rewards,
recalculate_all_logprobs=recalculate_all_logprobs,
)
scores = self.loss_fn(scores)
Expand Down
14 changes: 13 additions & 1 deletion src/gfn/gflownet/flow_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ def loss(
self,
env: DiscreteEnv,
states_container: StatesContainer[DiscreteStates],
log_rewards: torch.Tensor | None = None,

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Adding log_rewards before recalculate_all_logprobs changes the positional calling convention for loss(). Existing code that calls loss(env, samples, False) (to set recalculate_all_logprobs=False) would now pass False as log_rewards. Consider making log_rewards keyword-only or placing it after the existing optional args.

Copilot uses AI. Check for mistakes.
recalculate_all_logprobs: bool = True,
reduction: str = "mean",
) -> torch.Tensor:
Expand All @@ -293,6 +294,11 @@ def loss(
env: The discrete environment where the states are sampled from.
states_container: The StatesContainer object containing both intermediary and
terminating states.
log_rewards: Optional custom log rewards tensor of shape
(n_terminating_states,). When None, uses the environment rewards
from the states container. Useful for intrinsic rewards (see
"Towards Improving Exploration through Sibling Augmented
GFlowNets", Madan et al., ICLR 2025).
recalculate_all_logprobs: Whether to re-evaluate all logprobs (unused for FM).
reduction: The reduction method to use ('mean', 'sum', or 'none').

Expand All @@ -319,10 +325,16 @@ def loss(
states_container.intermediary_states,
reduction=reduction,
)

terminating_log_rewards = (
log_rewards
if log_rewards is not None
else states_container.terminating_log_rewards
)
rm_loss = self.reward_matching_loss(
env,
states_container.terminating_states,
states_container.terminating_log_rewards,
terminating_log_rewards,
reduction=reduction,
)
return fm_loss + self.alpha * rm_loss
Expand Down
53 changes: 40 additions & 13 deletions src/gfn/gflownet/sub_trajectory_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ def calculate_targets(
is_terminal_mask: MaskTensor,
sink_states_mask: MaskTensor,
i: int,
log_rewards: torch.Tensor | None = None,
) -> TargetsTensor:
"""Calculates the targets tensor for the current sub-trajectory length.

Expand All @@ -225,19 +226,26 @@ def calculate_targets(
sink_states_mask: A mask of shape (max_length, batch_size) indicating
whether the state is a sink state.
i: The sub-trajectory length.
log_rewards: Optional custom log rewards tensor of shape
(n_trajectories,). When None, uses the environment rewards.
Useful for intrinsic rewards (see
"Towards Improving Exploration through Sibling Augmented
GFlowNets", Madan et al., ICLR 2025).

Returns:
The targets tensor of shape (max_length + 1 - i, batch_size).
"""
targets = torch.full_like(preds, fill_value=-float("inf"))
# Guard behind debug: log_rewards is always set for terminating trajectories;
# bare assert would cause a graph break in torch.compile.
if log_rewards is None:
# Guard behind debug: log_rewards is always set for terminating trajectories;
# bare assert would cause a graph break in torch.compile.
if self.debug:
assert trajectories.log_rewards is not None
# cast: log_rewards is always set for terminating trajectories.
log_rewards = cast(torch.Tensor, trajectories.log_rewards)
if self.debug:
assert trajectories.log_rewards is not None
# cast: log_rewards is always set for terminating trajectories.
log_rewards = cast(torch.Tensor, trajectories.log_rewards)[
trajectories.terminating_idx >= i
]
assert log_rewards.shape == (trajectories.batch_size,)
log_rewards = log_rewards[trajectories.terminating_idx >= i]

if math.isfinite(self.log_reward_clip_min):
log_rewards = log_rewards.clamp_min(self.log_reward_clip_min)
Expand Down Expand Up @@ -333,6 +341,7 @@ def calculate_masks(
def get_scores(
self,
trajectories: Trajectories,
log_rewards: torch.Tensor | None = None,
recalculate_all_logprobs: bool = True,
env: Env | None = None,
) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:
Expand All @@ -341,6 +350,11 @@ def get_scores(
Args:
env: The environment where the trajectories are sampled from.
trajectories: The batch of trajectories to evaluate.
log_rewards: Optional custom log rewards tensor of shape
(n_trajectories,). When None, uses the environment rewards.
Useful for intrinsic rewards (see
"Towards Improving Exploration through Sibling Augmented
GFlowNets", Madan et al., ICLR 2025).
recalculate_all_logprobs: Whether to re-evaluate all logprobs.

Returns:
Expand Down Expand Up @@ -389,6 +403,7 @@ def get_scores(
is_terminal_mask,
sink_states_mask,
i,
log_rewards=log_rewards,
)

flattening_mask = trajectories.terminating_idx.lt(
Expand Down Expand Up @@ -559,6 +574,7 @@ def loss(
self,
env: Env,
trajectories: Trajectories,
log_rewards: torch.Tensor | None = None,

Copilot AI Apr 3, 2026

Copy link

Choose a reason for hiding this comment

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

Adding log_rewards before recalculate_all_logprobs breaks positional callers of loss() (e.g., loss(env, trajs, False) would now pass False as log_rewards). To preserve backward compatibility, keep existing positional args and add log_rewards as keyword-only or append it after reduction.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed in commit 11caee9. log_rewards is now keyword-only in SubTBGFlowNet.loss(), preserving (env, trajectories, recalculate_all_logprobs, reduction) positional order.

recalculate_all_logprobs: bool = True,
reduction: str = "mean",
) -> torch.Tensor:
Expand All @@ -567,6 +583,11 @@ def loss(
Args:
env: The environment where the trajectories are sampled from.
trajectories: The batch of trajectories to compute the loss with.
log_rewards: Optional custom log rewards tensor of shape
(n_trajectories,). When None, uses the environment rewards.
Useful for intrinsic rewards (see
"Towards Improving Exploration through Sibling Augmented
GFlowNets", Madan et al., ICLR 2025).
Comment thread
josephdviviano marked this conversation as resolved.
Outdated
recalculate_all_logprobs: Whether to re-evaluate all logprobs.
reduction: The reduction method to use ('mean', 'sum', or 'none').
Note: for geometric-based sub-trajectory weighting, 'mean' is not
Expand Down Expand Up @@ -598,20 +619,26 @@ def loss(
logF_s0 = log_state_flows[0].masked_fill(log_state_flows[0].isinf(), 0.0)
total_log_pf = log_pf_traj.sum(dim=0)
total_log_pb = log_pb_traj.sum(dim=0)
# Guard behind debug: log_rewards is always set for terminating trajectories;
# bare assert would cause a graph break in torch.compile.
if log_rewards is None:
# Guard behind debug: log_rewards is always set for terminating
# trajectories; bare assert would cause a graph break in torch.compile.
if self.debug:
assert trajectories.log_rewards is not None
# cast: log_rewards is always set for terminating trajectories.
log_rewards = cast(torch.Tensor, trajectories.log_rewards)
if self.debug:
assert trajectories.log_rewards is not None
# cast: log_rewards is always set for terminating trajectories.
log_rewards = cast(torch.Tensor, trajectories.log_rewards)
assert log_rewards.shape == (trajectories.batch_size,)
if math.isfinite(self.log_reward_clip_min):
log_rewards = log_rewards.clamp_min(self.log_reward_clip_min)
tb_scores = logF_s0 + total_log_pf - total_log_pb - log_rewards
return loss_reduce(self.loss_fn(tb_scores), reduction)

# Get all scores and masks from the trajectories.
scores, flattening_masks = self.get_scores(
trajectories, recalculate_all_logprobs=recalculate_all_logprobs, env=env
trajectories,
log_rewards=log_rewards,
recalculate_all_logprobs=recalculate_all_logprobs,
env=env,
)
flattening_mask = torch.cat(flattening_masks)
all_scores = torch.cat(scores, 0)
Expand Down
Loading
Loading