Skip to content

Commit b554852

Browse files
[Place] Estimating Starting T Using Equilibrium T
I found that the way that the annealer estimates the initial temperature to be too high when the initial placement is of very good quality (for example, after AP). Added a new way of estimating the starting temperature by setting it to an estimation of the equilibrium temperature. The equilibrium temperature is the temperature at which the change in cost after an annealing iteration would be 0. The old way (of using the variance of the change in cost) is still the default; however, this new method can be turned on in the command-line.
1 parent 3ee4a62 commit b554852

File tree

10 files changed

+289
-12
lines changed

10 files changed

+289
-12
lines changed

doc/src/vpr/command_line_usage.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,18 @@ If any of init_t, exit_t or alpha_t is specified, the user schedule, with a fixe
840840

841841
**Default:** ``1.0``
842842

843+
.. option:: ----anneal_auto_init_t_estimator {cost_variance, equilibrium_est}
844+
845+
Controls which estimation method is used when selecting the starting temperature
846+
for the automatic annealing schedule.
847+
848+
The options for estimators are:
849+
850+
* ``cost_variance``: Estimates the initial temperature using the variance of cost after a set of trial swaps.
851+
* ``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).
852+
853+
**Default** ``equilibrium_est``
854+
843855
.. option:: --init_t <float>
844856

845857
The starting temperature of the anneal for the manual annealing schedule.

vpr/src/base/read_options.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "argparse.hpp"
77

88
#include "ap_flow_enums.h"
9+
#include "vpr_types.h"
910
#include "vtr_log.h"
1011
#include "vtr_path.h"
1112
#include "vtr_util.h"
@@ -690,6 +691,44 @@ struct ParsePlaceAgentSpace {
690691
}
691692
};
692693

694+
struct ParsePlaceInitTEstimator {
695+
ConvertedValue<e_anneal_init_t_estimator> from_str(const std::string& str) {
696+
ConvertedValue<e_anneal_init_t_estimator> conv_value;
697+
if (str == "cost_variance")
698+
conv_value.set_value(e_anneal_init_t_estimator::COST_VARIANCE);
699+
else if (str == "equilibrium_est")
700+
conv_value.set_value(e_anneal_init_t_estimator::EQUILIBRIUM_EST);
701+
else {
702+
std::stringstream msg;
703+
msg << "Invalid conversion from '" << str << "' to e_anneal_init_t_estimator (expected one of: " << argparse::join(default_choices(), ", ") << ")";
704+
conv_value.set_error(msg.str());
705+
}
706+
return conv_value;
707+
}
708+
709+
ConvertedValue<std::string> to_str(e_anneal_init_t_estimator val) {
710+
ConvertedValue<std::string> conv_value;
711+
switch (val) {
712+
case e_anneal_init_t_estimator::COST_VARIANCE:
713+
conv_value.set_value("cost_variance");
714+
break;
715+
case e_anneal_init_t_estimator::EQUILIBRIUM_EST:
716+
conv_value.set_value("equilibrium_est");
717+
break;
718+
default: {
719+
std::stringstream msg;
720+
msg << "Unknown e_anneal_init_t_estimator type.";
721+
conv_value.set_error(msg.str());
722+
}
723+
}
724+
return conv_value;
725+
}
726+
727+
std::vector<std::string> default_choices() {
728+
return {"cost_variance", "equilibrium_est"};
729+
}
730+
};
731+
693732
struct ParseFixPins {
694733
ConvertedValue<e_pad_loc_type> from_str(const std::string& str) {
695734
ConvertedValue<e_pad_loc_type> conv_value;
@@ -2273,6 +2312,20 @@ argparse::ArgumentParser create_arg_parser(const std::string& prog_name, t_optio
22732312
.default_value("1.0")
22742313
.show_in(argparse::ShowIn::HELP_ONLY);
22752314

2315+
place_grp.add_argument<e_anneal_init_t_estimator, ParsePlaceInitTEstimator>(args.place_init_t_estimator, "--anneal_auto_init_t_estimator")
2316+
.help(
2317+
"Controls which estimation method is used when selecting the starting temperature "
2318+
"for the automatic annealing schedule.\n"
2319+
"\n"
2320+
"The options for estimators are:\n"
2321+
"\tcost_variance: Estimates the initial temperature using the variance "
2322+
"of cost after a set of trial swaps.\n"
2323+
"\tequilibrium_est: Estimates the initial temperature by trying to "
2324+
"predict the equilibrium temperature for the initial placement "
2325+
"(i.e. the temperature that would result in no change in cost).")
2326+
.default_value("cost_variance")
2327+
.show_in(argparse::ShowIn::HELP_ONLY);
2328+
22762329
place_grp.add_argument(args.PlaceInitT, "--init_t")
22772330
.help("Initial temperature for manual annealing schedule")
22782331
.default_value("100.0")

vpr/src/base/read_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ struct t_options {
133133
argparse::ArgValue<bool> ShowPlaceTiming;
134134
argparse::ArgValue<float> PlaceInnerNum;
135135
argparse::ArgValue<float> place_auto_init_t_scale;
136+
argparse::ArgValue<e_anneal_init_t_estimator> place_init_t_estimator;
136137
argparse::ArgValue<float> PlaceInitT;
137138
argparse::ArgValue<float> PlaceExitT;
138139
argparse::ArgValue<float> PlaceAlphaT;

vpr/src/base/setup_vpr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ static void setup_placer_opts(const t_options& Options, t_placer_opts* PlacerOpt
729729
PlacerOpts->placer_debug_net = Options.placer_debug_net;
730730

731731
PlacerOpts->place_auto_init_t_scale = Options.place_auto_init_t_scale.value();
732+
PlacerOpts->anneal_init_t_estimator = Options.place_init_t_estimator.value();
732733
}
733734

734735
static void setup_analysis_opts(const t_options& Options, t_analysis_opts& analysis_opts) {

vpr/src/base/vpr_types.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,15 @@ enum class e_place_delta_delay_algorithm {
967967
DIJKSTRA_EXPANSION,
968968
};
969969

970+
/**
971+
* @brief Enumeration of the different initial temperature estimators available
972+
* for the placer.
973+
*/
974+
enum class e_anneal_init_t_estimator {
975+
COST_VARIANCE, ///<Estimate the initial temperature using the variance in cost of a set of trial swaps.
976+
EQUILIBRIUM_EST, ///<Estimate the initial temperature by predicting the equilibrium temperature for the initial placement.
977+
};
978+
970979
enum class e_move_type;
971980

972981
/**
@@ -1024,6 +1033,9 @@ enum class e_move_type;
10241033
* @param place_auto_init_t_scale
10251034
* When the annealer is using the automatic schedule, this option
10261035
* scales the initial temperature selected.
1036+
* @param anneal_init_t_estimator
1037+
* When the annealer is using the automatic schedule, this option
1038+
* selects which estimator is used to select an initial temperature.
10271039
*/
10281040
struct t_placer_opts {
10291041
t_place_algorithm place_algorithm;
@@ -1097,6 +1109,8 @@ struct t_placer_opts {
10971109
e_place_delta_delay_algorithm place_delta_delay_matrix_calculation_method;
10981110

10991111
float place_auto_init_t_scale;
1112+
1113+
e_anneal_init_t_estimator anneal_init_t_estimator;
11001114
};
11011115

11021116
/******************************************************************

vpr/src/place/annealer.cpp

Lines changed: 150 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33

44
#include <algorithm>
55
#include <cmath>
6+
#include <limits>
67

78
#include "globals.h"
89
#include "place_macro.h"
10+
#include "vpr_context.h"
11+
#include "vpr_error.h"
912
#include "vpr_types.h"
1013
#include "place_util.h"
1114
#include "placer_state.h"
@@ -291,11 +294,145 @@ PlacementAnnealer::PlacementAnnealer(const t_placer_opts& placer_opts,
291294
}
292295

293296
float PlacementAnnealer::estimate_starting_temperature_() {
297+
294298
if (placer_opts_.anneal_sched.type == e_sched_type::USER_SCHED) {
295299
return placer_opts_.anneal_sched.init_t;
296300
}
297301

298-
const auto& cluster_ctx = g_vpr_ctx.clustering();
302+
switch (placer_opts_.anneal_init_t_estimator) {
303+
case e_anneal_init_t_estimator::COST_VARIANCE:
304+
return estimate_starting_temp_using_cost_variance_();
305+
case e_anneal_init_t_estimator::EQUILIBRIUM_EST:
306+
return estimate_equilibrium_temp_();
307+
default:
308+
VPR_FATAL_ERROR(VPR_ERROR_PLACE,
309+
"Unrecognized initial temperature estimator type");
310+
};
311+
}
312+
313+
float PlacementAnnealer::estimate_equilibrium_temp_() {
314+
const ClusteringContext& cluster_ctx = g_vpr_ctx.clustering();
315+
316+
// Determines the block swap loop count.
317+
// TODO: Revisit this. We may be able to get away with doing fewer trial
318+
// swaps. That or we may be able to get a more accurate initial
319+
// temperature by doing more moves.
320+
int move_lim = std::min(annealing_state_.move_lim_max, (int)cluster_ctx.clb_nlist.blocks().size());
321+
322+
// Perform N trial swaps and collect the change in cost for each of these
323+
// swaps. Accepted swaps are swaps which resulted in a negative change in
324+
// cost, rejected swaps are swaps which resulted in a positive change in
325+
// cost.
326+
std::vector<double> accepted_swaps;
327+
std::vector<double> rejected_swaps;
328+
accepted_swaps.reserve(move_lim);
329+
rejected_swaps.reserve(move_lim);
330+
for (int i = 0; i < move_lim; i++) {
331+
bool manual_move_enabled = false;
332+
#ifndef NO_GRAPHICS
333+
// Checks manual move flag for manual move feature
334+
t_draw_state* draw_state = get_draw_state_vars();
335+
if (draw_state->show_graphics) {
336+
manual_move_enabled = manual_move_is_selected();
337+
}
338+
#endif /*NO_GRAPHICS*/
339+
340+
t_swap_result swap_result = try_swap_(*move_generator_1_,
341+
placer_opts_.place_algorithm,
342+
manual_move_enabled);
343+
344+
if (swap_result.move_result == e_move_result::ACCEPTED) {
345+
accepted_swaps.push_back(swap_result.delta_c);
346+
// TODO: Look into not actually accepting these.
347+
swap_stats_.num_swap_accepted++;
348+
} else if (swap_result.move_result == e_move_result::ABORTED) {
349+
// Note: We do not keep track of the change in cost due to aborted
350+
// swaps. These are not interesting for this approach.
351+
swap_stats_.num_swap_aborted++;
352+
} else {
353+
rejected_swaps.push_back(swap_result.delta_c);
354+
swap_stats_.num_swap_rejected++;
355+
}
356+
}
357+
358+
// Computed the total change in cost due to accepted swaps.
359+
double total_accepted_cost = 0.0;
360+
for (double accepted_cost : accepted_swaps) {
361+
total_accepted_cost += accepted_cost;
362+
}
363+
364+
// Perform a binary search to try and find the equilibrium temperature for
365+
// this placement. This is the temperature that we expect would lead to no
366+
// overall change in temperature. We do this by computing the expected
367+
// change in cost given a trial temperature and try larger / smaller
368+
// temperatures until one is found that causes the change cost is close to
369+
// 0. Since the expected change in cost is monotonically increasing for
370+
// all positive temperatures, this method will return a unique result if it
371+
// exists within this range.
372+
// Initialize the lower bound temperature to 0. The temperature cannot
373+
// be less than 0.
374+
double lower_bound_temp = 0.0;
375+
// Initialize the upper bound temperature to 1e10. It is possible for
376+
// the equilibrium temperature to be infinite if the initial placement
377+
// is so bad that no swaps are accepted. In that case this value will
378+
// be returned instead of infinity.
379+
// TODO: Find what a reasonable value for this should be.
380+
double upper_bound_temp = 1e10;
381+
// The max search iterations should never be hit, but it is here as an
382+
// exit condition to prevent infinite loops.
383+
constexpr unsigned max_search_iters = 100;
384+
for (unsigned binary_search_iter = 0; binary_search_iter < max_search_iters; binary_search_iter++) {
385+
// Exit condition for binary search. Could be hit if the lower and upper
386+
// bounds are arbitrarily close.
387+
if (lower_bound_temp > upper_bound_temp)
388+
break;
389+
390+
// Try the temperature in the middle of the lower and upper bounds.
391+
double trial_temp = (lower_bound_temp + upper_bound_temp) / 2.0;
392+
393+
// Calculate the expected change in cost at this temperature (which we
394+
// call the residual here).
395+
double expected_total_post_rejected_cost = 0.0;
396+
for (double rejected_cost : rejected_swaps) {
397+
// Expected change in cost after a rejected swap is the change in
398+
// cost multiplied by the probability that this swap is accepted at
399+
// this temperature.
400+
double accpetance_prob = std::exp((-1.0 * rejected_cost) / trial_temp);
401+
expected_total_post_rejected_cost += rejected_cost * accpetance_prob;
402+
}
403+
double residual = expected_total_post_rejected_cost + total_accepted_cost;
404+
405+
// Return the trial temperature if it is within 6 decimal-points of precision.
406+
// NOTE: This is arbitrary.
407+
// TODO: We could stop this early and then use Newton's Method to quickly
408+
// touch it up to a more accurate value.
409+
if (std::abs(upper_bound_temp - lower_bound_temp) / trial_temp < 1e-6)
410+
return trial_temp;
411+
412+
if (residual < 0) {
413+
// Since the function is monotonically increasing, if the residual
414+
// is negative, then the lower bound should be raised to the trial
415+
// temperature.
416+
lower_bound_temp = trial_temp;
417+
} else if (residual > 0) {
418+
// Similarly, if the residual is positive, then the upper bound should
419+
// be lowered to the trial temperature.
420+
upper_bound_temp = trial_temp;
421+
} else {
422+
// If we happened to exactly hit the risidual, then this is the
423+
// exact temperature we should use.
424+
return trial_temp;
425+
}
426+
}
427+
428+
// If we get down here, it means that the upper loop did not reach a solution;
429+
// however, we know that the answer should be somewhere between lower and upper
430+
// bound. Therefore, return the average of the two.
431+
return (lower_bound_temp + upper_bound_temp) / 2.0;
432+
}
433+
434+
float PlacementAnnealer::estimate_starting_temp_using_cost_variance_() {
435+
const ClusteringContext& cluster_ctx = g_vpr_ctx.clustering();
299436

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

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

323-
if (swap_result == e_move_result::ACCEPTED) {
460+
if (swap_result.move_result == e_move_result::ACCEPTED) {
324461
num_accepted++;
325462
av += costs_.cost;
326463
sum_of_squares += costs_.cost * costs_.cost;
327464
swap_stats_.num_swap_accepted++;
328-
} else if (swap_result == e_move_result::ABORTED) {
465+
} else if (swap_result.move_result == e_move_result::ABORTED) {
329466
swap_stats_.num_swap_aborted++;
330467
} else {
331468
swap_stats_.num_swap_rejected++;
@@ -345,7 +482,7 @@ float PlacementAnnealer::estimate_starting_temperature_() {
345482
return init_temp;
346483
}
347484

348-
e_move_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
485+
t_swap_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
349486
const t_place_algorithm& place_algorithm,
350487
bool manual_move_enabled) {
351488
/* Picks some block and moves it to another spot. If this spot is
@@ -646,7 +783,11 @@ e_move_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
646783
VTR_LOGV_DEBUG(g_vpr_ctx.placement().f_placer_debug,
647784
"\t\tAfter move Place cost %e, bb_cost %e, timing cost %e\n",
648785
costs_.cost, costs_.bb_cost, costs_.timing_cost);
649-
return move_outcome;
786+
787+
t_swap_result swap_result;
788+
swap_result.move_result = move_outcome;
789+
swap_result.delta_c = delta_c;
790+
return swap_result;
650791
}
651792

652793
void PlacementAnnealer::outer_loop_update_timing_info() {
@@ -687,13 +828,13 @@ void PlacementAnnealer::placement_inner_loop() {
687828

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

692-
if (swap_result == e_move_result::ACCEPTED) {
833+
if (swap_result.move_result == e_move_result::ACCEPTED) {
693834
// Move was accepted. Update statistics that are useful for the annealing schedule.
694835
placer_stats_.single_swap_update(costs_);
695836
swap_stats_.num_swap_accepted++;
696-
} else if (swap_result == e_move_result::ABORTED) {
837+
} else if (swap_result.move_result == e_move_result::ABORTED) {
697838
swap_stats_.num_swap_aborted++;
698839
} else { // swap_result == REJECTED
699840
swap_stats_.num_swap_rejected++;

0 commit comments

Comments
 (0)