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
12 changes: 12 additions & 0 deletions doc/src/vpr/command_line_usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,18 @@ If any of init_t, exit_t or alpha_t is specified, the user schedule, with a fixe

**Default:** ``1.0``

.. option:: ----anneal_auto_init_t_estimator {cost_variance, equilibrium_est}

Controls which estimation method is used when selecting the starting temperature
for the automatic annealing schedule.

The options for estimators are:

* ``cost_variance``: Estimates the initial temperature using the variance of cost after a set of trial swaps.
* ``equilibrium_est``: Estimates the initial temperature by trying to predict the equilibrium temperature for the initial placement (i.e. the temperature that would result in no change in cost).

**Default** ``equilibrium_est``

.. option:: --init_t <float>

The starting temperature of the anneal for the manual annealing schedule.
Expand Down
53 changes: 53 additions & 0 deletions vpr/src/base/read_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "argparse.hpp"

#include "ap_flow_enums.h"
#include "vpr_types.h"
#include "vtr_log.h"
#include "vtr_path.h"
#include "vtr_util.h"
Expand Down Expand Up @@ -690,6 +691,44 @@ struct ParsePlaceAgentSpace {
}
};

struct ParsePlaceInitTEstimator {
ConvertedValue<e_anneal_init_t_estimator> from_str(const std::string& str) {
ConvertedValue<e_anneal_init_t_estimator> conv_value;
if (str == "cost_variance")
conv_value.set_value(e_anneal_init_t_estimator::COST_VARIANCE);
else if (str == "equilibrium_est")
conv_value.set_value(e_anneal_init_t_estimator::EQUILIBRIUM_EST);
else {
std::stringstream msg;
msg << "Invalid conversion from '" << str << "' to e_anneal_init_t_estimator (expected one of: " << argparse::join(default_choices(), ", ") << ")";
conv_value.set_error(msg.str());
}
return conv_value;
}

ConvertedValue<std::string> to_str(e_anneal_init_t_estimator val) {
ConvertedValue<std::string> conv_value;
switch (val) {
case e_anneal_init_t_estimator::COST_VARIANCE:
conv_value.set_value("cost_variance");
break;
case e_anneal_init_t_estimator::EQUILIBRIUM_EST:
conv_value.set_value("equilibrium_est");
break;
default: {
std::stringstream msg;
msg << "Unknown e_anneal_init_t_estimator type.";
conv_value.set_error(msg.str());
}
}
return conv_value;
}

std::vector<std::string> default_choices() {
return {"cost_variance", "equilibrium_est"};
}
};

struct ParseFixPins {
ConvertedValue<e_pad_loc_type> from_str(const std::string& str) {
ConvertedValue<e_pad_loc_type> conv_value;
Expand Down Expand Up @@ -2273,6 +2312,20 @@ argparse::ArgumentParser create_arg_parser(const std::string& prog_name, t_optio
.default_value("1.0")
.show_in(argparse::ShowIn::HELP_ONLY);

place_grp.add_argument<e_anneal_init_t_estimator, ParsePlaceInitTEstimator>(args.place_init_t_estimator, "--anneal_auto_init_t_estimator")
.help(
"Controls which estimation method is used when selecting the starting temperature "
"for the automatic annealing schedule.\n"
"\n"
"The options for estimators are:\n"
"\tcost_variance: Estimates the initial temperature using the variance "
"of cost after a set of trial swaps.\n"
"\tequilibrium_est: Estimates the initial temperature by trying to "
"predict the equilibrium temperature for the initial placement "
"(i.e. the temperature that would result in no change in cost).")
.default_value("cost_variance")
.show_in(argparse::ShowIn::HELP_ONLY);

place_grp.add_argument(args.PlaceInitT, "--init_t")
.help("Initial temperature for manual annealing schedule")
.default_value("100.0")
Expand Down
1 change: 1 addition & 0 deletions vpr/src/base/read_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ struct t_options {
argparse::ArgValue<bool> ShowPlaceTiming;
argparse::ArgValue<float> PlaceInnerNum;
argparse::ArgValue<float> place_auto_init_t_scale;
argparse::ArgValue<e_anneal_init_t_estimator> place_init_t_estimator;
argparse::ArgValue<float> PlaceInitT;
argparse::ArgValue<float> PlaceExitT;
argparse::ArgValue<float> PlaceAlphaT;
Expand Down
1 change: 1 addition & 0 deletions vpr/src/base/setup_vpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ static void setup_placer_opts(const t_options& Options, t_placer_opts* PlacerOpt
PlacerOpts->placer_debug_net = Options.placer_debug_net;

PlacerOpts->place_auto_init_t_scale = Options.place_auto_init_t_scale.value();
PlacerOpts->anneal_init_t_estimator = Options.place_init_t_estimator.value();
}

static void setup_analysis_opts(const t_options& Options, t_analysis_opts& analysis_opts) {
Expand Down
14 changes: 14 additions & 0 deletions vpr/src/base/vpr_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -967,6 +967,15 @@ enum class e_place_delta_delay_algorithm {
DIJKSTRA_EXPANSION,
};

/**
* @brief Enumeration of the different initial temperature estimators available
* for the placer.
*/
enum class e_anneal_init_t_estimator {
COST_VARIANCE, ///<Estimate the initial temperature using the variance in cost of a set of trial swaps.
EQUILIBRIUM_EST, ///<Estimate the initial temperature by predicting the equilibrium temperature for the initial placement.
};

enum class e_move_type;

/**
Expand Down Expand Up @@ -1024,6 +1033,9 @@ enum class e_move_type;
* @param place_auto_init_t_scale
* When the annealer is using the automatic schedule, this option
* scales the initial temperature selected.
* @param anneal_init_t_estimator
* When the annealer is using the automatic schedule, this option
* selects which estimator is used to select an initial temperature.
*/
struct t_placer_opts {
t_place_algorithm place_algorithm;
Expand Down Expand Up @@ -1097,6 +1109,8 @@ struct t_placer_opts {
e_place_delta_delay_algorithm place_delta_delay_matrix_calculation_method;

float place_auto_init_t_scale;

e_anneal_init_t_estimator anneal_init_t_estimator;
};

/******************************************************************
Expand Down
159 changes: 150 additions & 9 deletions vpr/src/place/annealer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

#include <algorithm>
#include <cmath>
#include <limits>

#include "globals.h"
#include "place_macro.h"
#include "vpr_context.h"
#include "vpr_error.h"
#include "vpr_types.h"
#include "place_util.h"
#include "placer_state.h"
Expand Down Expand Up @@ -291,11 +294,145 @@ PlacementAnnealer::PlacementAnnealer(const t_placer_opts& placer_opts,
}

float PlacementAnnealer::estimate_starting_temperature_() {

if (placer_opts_.anneal_sched.type == e_sched_type::USER_SCHED) {
return placer_opts_.anneal_sched.init_t;
}

const auto& cluster_ctx = g_vpr_ctx.clustering();
switch (placer_opts_.anneal_init_t_estimator) {
case e_anneal_init_t_estimator::COST_VARIANCE:
return estimate_starting_temp_using_cost_variance_();
case e_anneal_init_t_estimator::EQUILIBRIUM_EST:
return estimate_equilibrium_temp_();
default:
VPR_FATAL_ERROR(VPR_ERROR_PLACE,
"Unrecognized initial temperature estimator type");
};
}

float PlacementAnnealer::estimate_equilibrium_temp_() {
const ClusteringContext& cluster_ctx = g_vpr_ctx.clustering();

// Determines the block swap loop count.
// TODO: Revisit this. We may be able to get away with doing fewer trial
// swaps. That or we may be able to get a more accurate initial
// temperature by doing more moves.
int move_lim = std::min(annealing_state_.move_lim_max, (int)cluster_ctx.clb_nlist.blocks().size());

// Perform N trial swaps and collect the change in cost for each of these
// swaps. Accepted swaps are swaps which resulted in a negative change in
// cost, rejected swaps are swaps which resulted in a positive change in
// cost.
std::vector<double> accepted_swaps;
std::vector<double> rejected_swaps;
accepted_swaps.reserve(move_lim);
rejected_swaps.reserve(move_lim);
for (int i = 0; i < move_lim; i++) {
bool manual_move_enabled = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd delete this. No need to enable manual moves during initial temperature calculations -- this is intended to let people directly control the annealer, but just doing that in the main annealer is enough.

There should be code like this at the start of the main annealer -- if it is there, there isn't any need to duplicate it here. If it isn't there, I think we should move this there anyway.

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 agree, this should not be here assuming that the entire purpose of this method is to estimate the starting temperature.

Looking at the code, however, I do not believe that the manual move feature is working at all. It appears to only ever turn on in the initial temperature estimation. I do not believe that this should be resolved in this PR, but I can raise an issue if you would like?

In the meantime, I will remove this from the equilibrium estimator (which I have added), but I will leave it the default flow for now so we do not regress the feature further.

Copy link
Contributor Author

@AlexandreSinger AlexandreSinger Sep 16, 2025

Choose a reason for hiding this comment

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

Ill raise an issue to look into this!

#ifndef NO_GRAPHICS
// Checks manual move flag for manual move feature
t_draw_state* draw_state = get_draw_state_vars();
if (draw_state->show_graphics) {
manual_move_enabled = manual_move_is_selected();
}
#endif /*NO_GRAPHICS*/

t_swap_result swap_result = try_swap_(*move_generator_1_,
placer_opts_.place_algorithm,
manual_move_enabled);

if (swap_result.move_result == e_move_result::ACCEPTED) {
accepted_swaps.push_back(swap_result.delta_c);
// TODO: Look into not actually accepting these.
swap_stats_.num_swap_accepted++;
} else if (swap_result.move_result == e_move_result::ABORTED) {
// Note: We do not keep track of the change in cost due to aborted
// swaps. These are not interesting for this approach.
swap_stats_.num_swap_aborted++;
} else {
rejected_swaps.push_back(swap_result.delta_c);
swap_stats_.num_swap_rejected++;
}
}

// Computed the total change in cost due to accepted swaps.
double total_accepted_cost = 0.0;
for (double accepted_cost : accepted_swaps) {
total_accepted_cost += accepted_cost;
}

// Perform a binary search to try and find the equilibrium temperature for
// this placement. This is the temperature that we expect would lead to no
// overall change in temperature. We do this by computing the expected
// change in cost given a trial temperature and try larger / smaller
// temperatures until one is found that causes the change cost is close to
// 0. Since the expected change in cost is monotonically increasing for
// all positive temperatures, this method will return a unique result if it
// exists within this range.
// Initialize the lower bound temperature to 0. The temperature cannot
// be less than 0.
double lower_bound_temp = 0.0;
// Initialize the upper bound temperature to 1e10. It is possible for
// the equilibrium temperature to be infinite if the initial placement
// is so bad that no swaps are accepted. In that case this value will
// be returned instead of infinity.
// TODO: Find what a reasonable value for this should be.
double upper_bound_temp = 1e10;
// The max search iterations should never be hit, but it is here as an
// exit condition to prevent infinite loops.
constexpr unsigned max_search_iters = 100;
for (unsigned binary_search_iter = 0; binary_search_iter < max_search_iters; binary_search_iter++) {
// Exit condition for binary search. Could be hit if the lower and upper
// bounds are arbitrarily close.
if (lower_bound_temp > upper_bound_temp)
break;

// Try the temperature in the middle of the lower and upper bounds.
double trial_temp = (lower_bound_temp + upper_bound_temp) / 2.0;

// Calculate the expected change in cost at this temperature (which we
// call the residual here).
double expected_total_post_rejected_cost = 0.0;
for (double rejected_cost : rejected_swaps) {
// Expected change in cost after a rejected swap is the change in
// cost multiplied by the probability that this swap is accepted at
// this temperature.
double accpetance_prob = std::exp((-1.0 * rejected_cost) / trial_temp);
expected_total_post_rejected_cost += rejected_cost * accpetance_prob;
}
double residual = expected_total_post_rejected_cost + total_accepted_cost;

// Return the trial temperature if it is within 6 decimal-points of precision.
// NOTE: This is arbitrary.
// TODO: We could stop this early and then use Newton's Method to quickly
// touch it up to a more accurate value.
if (std::abs(upper_bound_temp - lower_bound_temp) / trial_temp < 1e-6)
return trial_temp;

if (residual < 0) {
// Since the function is monotonically increasing, if the residual
// is negative, then the lower bound should be raised to the trial
// temperature.
lower_bound_temp = trial_temp;
} else if (residual > 0) {
// Similarly, if the residual is positive, then the upper bound should
// be lowered to the trial temperature.
upper_bound_temp = trial_temp;
} else {
// If we happened to exactly hit the risidual, then this is the
// exact temperature we should use.
return trial_temp;
}
}

// If we get down here, it means that the upper loop did not reach a solution;
// however, we know that the answer should be somewhere between lower and upper
// bound. Therefore, return the average of the two.
return (lower_bound_temp + upper_bound_temp) / 2.0;
}

float PlacementAnnealer::estimate_starting_temp_using_cost_variance_() {
const ClusteringContext& cluster_ctx = g_vpr_ctx.clustering();

// Use to calculate the average of cost when swap is accepted.
int num_accepted = 0;
Expand All @@ -318,14 +455,14 @@ float PlacementAnnealer::estimate_starting_temperature_() {
#endif /*NO_GRAPHICS*/

// Will not deploy setup slack analysis, so omit crit_exponenet and setup_slack
e_move_result swap_result = try_swap_(*move_generator_1_, placer_opts_.place_algorithm, manual_move_enabled);
t_swap_result swap_result = try_swap_(*move_generator_1_, placer_opts_.place_algorithm, manual_move_enabled);

if (swap_result == e_move_result::ACCEPTED) {
if (swap_result.move_result == e_move_result::ACCEPTED) {
num_accepted++;
av += costs_.cost;
sum_of_squares += costs_.cost * costs_.cost;
swap_stats_.num_swap_accepted++;
} else if (swap_result == e_move_result::ABORTED) {
} else if (swap_result.move_result == e_move_result::ABORTED) {
swap_stats_.num_swap_aborted++;
} else {
swap_stats_.num_swap_rejected++;
Expand All @@ -345,7 +482,7 @@ float PlacementAnnealer::estimate_starting_temperature_() {
return init_temp;
}

e_move_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
t_swap_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
const t_place_algorithm& place_algorithm,
bool manual_move_enabled) {
/* Picks some block and moves it to another spot. If this spot is
Expand Down Expand Up @@ -646,7 +783,11 @@ e_move_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
VTR_LOGV_DEBUG(g_vpr_ctx.placement().f_placer_debug,
"\t\tAfter move Place cost %e, bb_cost %e, timing cost %e\n",
costs_.cost, costs_.bb_cost, costs_.timing_cost);
return move_outcome;

t_swap_result swap_result;
swap_result.move_result = move_outcome;
swap_result.delta_c = delta_c;
return swap_result;
}

void PlacementAnnealer::outer_loop_update_timing_info() {
Expand Down Expand Up @@ -687,13 +828,13 @@ void PlacementAnnealer::placement_inner_loop() {

// Inner loop begins
for (int inner_iter = 0, inner_crit_iter_count = 1; inner_iter < annealing_state_.move_lim; inner_iter++) {
e_move_result swap_result = try_swap_(move_generator, placer_opts_.place_algorithm, manual_move_enabled);
t_swap_result swap_result = try_swap_(move_generator, placer_opts_.place_algorithm, manual_move_enabled);

if (swap_result == e_move_result::ACCEPTED) {
if (swap_result.move_result == e_move_result::ACCEPTED) {
// Move was accepted. Update statistics that are useful for the annealing schedule.
placer_stats_.single_swap_update(costs_);
swap_stats_.num_swap_accepted++;
} else if (swap_result == e_move_result::ABORTED) {
} else if (swap_result.move_result == e_move_result::ABORTED) {
swap_stats_.num_swap_aborted++;
} else { // swap_result == REJECTED
swap_stats_.num_swap_rejected++;
Expand Down
Loading