Skip to content

Conversation

@mgarrard
Copy link
Contributor

Summary:
This criteria updates the completion state logic to assume if a node can transition, and that transition is to itself, then the optimization is complete.

This works because should_transition_to_next_node only considers transtion blocking criteria (ie not max parallelism) when thinking about should transition or not. And if a node points to itself, we can assume that signifies the end of the optimiztion (steps are initialized this way earlier in this stack). this allows allows for the gs to be re-called into, and the tc criterion to change thus putting it back into a non-complete state.

An alternative I considered is to check if all transition edges are completed, and at least one points to self. This would look something like the below snippet. It would be much more expensive to evaluate, and is guarding against a malformed strategy. Edges are already known to be created in order of importance, and self transition edges should be considered ending edges when their importance is considered

property
def optimization_complete(self) -> bool:
    if len(self._curr.transition_criteria) == 0:
        return False

    # Check ALL transition edges, not just the first matching one
    for next_node, all_tc in self._curr.transition_edges.items():
        transition_blocking = [tc for tc in all_tc if tc.block_transition_if_unmet]
        if not transition_blocking:
            continue
        
        all_met = all(
            tc.is_met(experiment=self.experiment, curr_node=self._curr)
            for tc in transition_blocking
        )
        
        if all_met:
            # An edge's criteria are met - check where it points
            if next_node != self._curr.name:
                return False  # Can transition to different node, not complete
    
    # All met edges (if any) point to self
    # Check if we actually have any met criteria pointing to self
    can_transition, next_node = self._curr.should_transition_to_next_node(
        raise_data_required_error=False
    )
    return can_transition and next_node == self._curr.name

The thrid alternative is to instate "compeletion node", which i think could be viable in the future if we have more complex generation strategies than we currently support, and the self generation logic is too cumbersome.

For now though, I think this is a pretty nice simplification that also should have some compute wins. Going from O (number of nodes * number of TC per node), to O(number of tc on current node)

Differential Revision: D91549954

…acebook#4802)

Summary:

Instead of merely checking for presence of any data, we could unify this transition with the helper method we use in target trial logic. This will allow us to check that all metrics in the opt config  have data before transitioning, which also ensures target trial selection is correct once we are in mbm etc. 

We could add additional checks about amount of data, but i think this is a good start. Happy to hear other thougths

Reviewed By: saitcakmak

Differential Revision: D91064377
Summary:
**Enforce `transition_to` as a required argument in `TransitionCriterion`**

This diff makes `transition_to` a required argument in the `TransitionCriterion` class. It updates the `MinimumPreferenceOccurances` and `MinTrials` classes to include `transition_to` as a required argument. The `GenerationNode` class is also updated to include a `placeholder_transition_to` attribute, which will be overwritten in the `GenerationStrategy` constructor.

The changes include:

*   Updated `MinimumPreferenceOccurances` and `MinTrials` classes to require `transition_to` argument
*   Added `placeholder_transition_to` attribute to `GenerationNode` class
*   Updated unit tests to reflect the changes

With this change, users will be required to provide a `transition_to` argument when creating a `TransitionCriterion` object. This ensures that the `GenerationStrategy` knows which generation node to transition to when a criterion is met.

Differential Revision: D91141100
Summary: This cleans up a todo from long ago, this is unique enough as it uses all components of a GeneratorSpec

Reviewed By: saitcakmak

Differential Revision: D91229383
Summary:
After discussing with Jason Chow, AEPysch no longer interfaces with Ax, instead it directly leverages BoTorch. There are no plans atm to integrate directly with Ax, but if that is taken on in the future, it would be better to re-onboard them using GenNode based GSs given the opportunity for a clean slate. 

This allows us to remove special casing for AEPysch from GS codebase

Reviewed By: saitcakmak

Differential Revision: D91156800
Summary:

Some minor improvements to readabilty/ conciseness to the Auxillary exp TC

Differential Revision: D91365207
Summary:

We initially provided this override during transition from legacy dispatch to GS, and at the time voted to persist the override in case folks wanted to provide this information manually. However, in the ~year+ since we added this, I'm not sure we've had any usage of this, and keeping it around increases the complexity of an already challenging part of the stack. 

My suggestion is to remove this, and for advanced users to directly leverage nodes or models if custom modeling is needed

Differential Revision: D91513177
Summary:
This criteria updates the completion state logic to assume if a node can transition, and that transition is to itself, then the optimization is complete.

This works because should_transition_to_next_node only considers transtion blocking criteria (ie not max parallelism) when thinking about should transition or not. And if a node points to itself, we can assume that signifies the end of the optimiztion (steps are initialized this way earlier in this stack). this allows allows for the gs to be re-called into, and the tc criterion to change thus putting it back into a non-complete state.

An alternative I considered is to check if all transition edges are completed, and at least one points to self. This would look something like the below snippet. It would be much more expensive to evaluate, and is guarding against a malformed strategy. Edges are already known to be created in order of importance, and self transition edges should be considered ending edges when their importance is considered

```
property
def optimization_complete(self) -> bool:
    if len(self._curr.transition_criteria) == 0:
        return False

    # Check ALL transition edges, not just the first matching one
    for next_node, all_tc in self._curr.transition_edges.items():
        transition_blocking = [tc for tc in all_tc if tc.block_transition_if_unmet]
        if not transition_blocking:
            continue
        
        all_met = all(
            tc.is_met(experiment=self.experiment, curr_node=self._curr)
            for tc in transition_blocking
        )
        
        if all_met:
            # An edge's criteria are met - check where it points
            if next_node != self._curr.name:
                return False  # Can transition to different node, not complete
    
    # All met edges (if any) point to self
    # Check if we actually have any met criteria pointing to self
    can_transition, next_node = self._curr.should_transition_to_next_node(
        raise_data_required_error=False
    )
    return can_transition and next_node == self._curr.name
```

The thrid alternative is to instate "compeletion node", which i think could be viable in the future if we have more complex generation strategies than we currently support, and the self generation logic is too cumbersome.

For now though, I think this is a pretty nice simplification that also should have some compute wins. Going from O (number of nodes * number of TC per node), to O(number of tc on current node)

Differential Revision: D91549954
@meta-codesync
Copy link

meta-codesync bot commented Jan 27, 2026

@mgarrard has exported this pull request. If you are a Meta employee, you can view the originating Diff in D91549954.

@meta-cla meta-cla bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Jan 27, 2026
@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 96.29630% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 96.71%. Comparing base (1eb22a8) to head (fd87f1a).

Files with missing lines Patch % Lines
ax/generation_strategy/transition_criterion.py 83.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4828      +/-   ##
==========================================
- Coverage   96.71%   96.71%   -0.01%     
==========================================
  Files         586      585       -1     
  Lines       61323    61220     -103     
==========================================
- Hits        59309    59208     -101     
+ Misses       2014     2012       -2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed Do not delete this pull request or issue due to inactivity. fb-exported meta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants