Skip to content

Commit 7a678a6

Browse files
authored
Merge pull request #3271 from AlexandreSinger/feature-place-equilibrium-init-t-est
[Place] Estimating Starting T Using Equilibrium T
2 parents 112d358 + 20c42f0 commit 7a678a6

File tree

10 files changed

+282
-12
lines changed

10 files changed

+282
-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}
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. The initial temperature is set to a value proportional to the variance.
851+
* ``equilibrium``: 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** ``cost_variance``
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")
700+
conv_value.set_value(e_anneal_init_t_estimator::EQUILIBRIUM);
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:
716+
conv_value.set_value("equilibrium");
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"};
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: 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, ///<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: 152 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,147 @@ 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:
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+
t_swap_result swap_result = try_swap_(*move_generator_1_,
332+
placer_opts_.place_algorithm,
333+
false /*manual_move_enabled*/);
334+
335+
if (swap_result.move_result == e_move_result::ACCEPTED) {
336+
accepted_swaps.push_back(swap_result.delta_c);
337+
// TODO: Look into not actually accepting these.
338+
swap_stats_.num_swap_accepted++;
339+
} else if (swap_result.move_result == e_move_result::ABORTED) {
340+
// Note: We do not keep track of the change in cost due to aborted
341+
// swaps. These are not interesting for this approach.
342+
swap_stats_.num_swap_aborted++;
343+
} else {
344+
rejected_swaps.push_back(swap_result.delta_c);
345+
swap_stats_.num_swap_rejected++;
346+
}
347+
}
348+
349+
// Computed the total change in cost due to accepted swaps.
350+
double total_accepted_cost = 0.0;
351+
for (double accepted_cost : accepted_swaps) {
352+
total_accepted_cost += accepted_cost;
353+
}
354+
355+
// Find the magnitude of the largest reject swap cost. This is useful for
356+
// picking a worst-case initial temperature.
357+
double max_rejected_swap_cost = 0.0;
358+
for (double rejected_cost : rejected_swaps) {
359+
max_rejected_swap_cost = std::max(max_rejected_swap_cost,
360+
std::abs(rejected_cost));
361+
}
362+
363+
// Perform a binary search to try and find the equilibrium temperature for
364+
// this placement. This is the temperature that we expect would lead to no
365+
// overall change in temperature. We do this by computing the expected
366+
// change in cost given a trial temperature and try larger / smaller
367+
// temperatures until one is found that causes the change cost is close to
368+
// 0. Since the expected change in cost is monotonically increasing for
369+
// all positive temperatures, this method will return a unique result if it
370+
// exists within this range.
371+
// Initialize the lower bound temperature to 0. The temperature cannot
372+
// be less than 0.
373+
double lower_bound_temp = 0.0;
374+
// Initialize the upper bound temperature. It is possible for
375+
// the equilibrium temperature to be infinite if the initial placement
376+
// is so bad that no swaps are accepted. In that case this value will
377+
// be returned instead of infinity.
378+
// At this temperature, the probability of accepting this worst rejected
379+
// swap would be 71.655% (e^(-1/3)).
380+
// TODO: Investigate if this is a good initial temperature for these
381+
// cases.
382+
double upper_bound_temp = 3.0 * max_rejected_swap_cost;
383+
// The max search iterations should never be hit, but it is here as an
384+
// exit condition to prevent infinite loops.
385+
constexpr unsigned max_search_iters = 100;
386+
for (unsigned binary_search_iter = 0; binary_search_iter < max_search_iters; binary_search_iter++) {
387+
// Exit condition for binary search. Could be hit if the lower and upper
388+
// bounds are arbitrarily close.
389+
if (lower_bound_temp >= upper_bound_temp)
390+
break;
391+
392+
// Try the temperature in the middle of the lower and upper bounds.
393+
double trial_temp = (lower_bound_temp + upper_bound_temp) / 2.0;
394+
395+
// Return the trial temperature if it is within 6 decimal-points of precision.
396+
// NOTE: This is arbitrary.
397+
// TODO: We could stop this early and then use Newton's Method to quickly
398+
// touch it up to a more accurate value.
399+
if (std::abs(upper_bound_temp - lower_bound_temp) / trial_temp < 1e-6)
400+
return trial_temp;
401+
402+
// Calculate the expected change in cost at this temperature (which we
403+
// call the residual here).
404+
double expected_total_post_rejected_cost = 0.0;
405+
for (double rejected_cost : rejected_swaps) {
406+
// Expected change in cost after a rejected swap is the change in
407+
// cost multiplied by the probability that this swap is accepted at
408+
// this temperature.
409+
double acceptance_prob = std::exp((-1.0 * rejected_cost) / trial_temp);
410+
expected_total_post_rejected_cost += rejected_cost * acceptance_prob;
411+
}
412+
double residual = expected_total_post_rejected_cost + total_accepted_cost;
413+
414+
if (residual < 0) {
415+
// Since the function is monotonically increasing, if the residual
416+
// is negative, then the lower bound should be raised to the trial
417+
// temperature.
418+
lower_bound_temp = trial_temp;
419+
} else if (residual > 0) {
420+
// Similarly, if the residual is positive, then the upper bound should
421+
// be lowered to the trial temperature.
422+
upper_bound_temp = trial_temp;
423+
} else {
424+
// If we happened to exactly hit the risidual, then this is the
425+
// exact temperature we should use.
426+
return trial_temp;
427+
}
428+
}
429+
430+
// If we get down here, it means that the upper loop did not reach a solution;
431+
// however, we know that the answer should be somewhere between lower and upper
432+
// bound. Therefore, return the average of the two.
433+
return (lower_bound_temp + upper_bound_temp) / 2.0;
434+
}
435+
436+
float PlacementAnnealer::estimate_starting_temp_using_cost_variance_() {
437+
const ClusteringContext& cluster_ctx = g_vpr_ctx.clustering();
299438

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

320459
// 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);
460+
t_swap_result swap_result = try_swap_(*move_generator_1_, placer_opts_.place_algorithm, manual_move_enabled);
322461

323-
if (swap_result == e_move_result::ACCEPTED) {
462+
if (swap_result.move_result == e_move_result::ACCEPTED) {
324463
num_accepted++;
325464
av += costs_.cost;
326465
sum_of_squares += costs_.cost * costs_.cost;
327466
swap_stats_.num_swap_accepted++;
328-
} else if (swap_result == e_move_result::ABORTED) {
467+
} else if (swap_result.move_result == e_move_result::ABORTED) {
329468
swap_stats_.num_swap_aborted++;
330469
} else {
331470
swap_stats_.num_swap_rejected++;
@@ -345,7 +484,7 @@ float PlacementAnnealer::estimate_starting_temperature_() {
345484
return init_temp;
346485
}
347486

348-
e_move_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
487+
t_swap_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
349488
const t_place_algorithm& place_algorithm,
350489
bool manual_move_enabled) {
351490
/* Picks some block and moves it to another spot. If this spot is
@@ -646,7 +785,11 @@ e_move_result PlacementAnnealer::try_swap_(MoveGenerator& move_generator,
646785
VTR_LOGV_DEBUG(g_vpr_ctx.placement().f_placer_debug,
647786
"\t\tAfter move Place cost %e, bb_cost %e, timing cost %e\n",
648787
costs_.cost, costs_.bb_cost, costs_.timing_cost);
649-
return move_outcome;
788+
789+
t_swap_result swap_result;
790+
swap_result.move_result = move_outcome;
791+
swap_result.delta_c = delta_c;
792+
return swap_result;
650793
}
651794

652795
void PlacementAnnealer::outer_loop_update_timing_info() {
@@ -687,13 +830,13 @@ void PlacementAnnealer::placement_inner_loop() {
687830

688831
// Inner loop begins
689832
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);
833+
t_swap_result swap_result = try_swap_(move_generator, placer_opts_.place_algorithm, manual_move_enabled);
691834

692-
if (swap_result == e_move_result::ACCEPTED) {
835+
if (swap_result.move_result == e_move_result::ACCEPTED) {
693836
// Move was accepted. Update statistics that are useful for the annealing schedule.
694837
placer_stats_.single_swap_update(costs_);
695838
swap_stats_.num_swap_accepted++;
696-
} else if (swap_result == e_move_result::ABORTED) {
839+
} else if (swap_result.move_result == e_move_result::ABORTED) {
697840
swap_stats_.num_swap_aborted++;
698841
} else { // swap_result == REJECTED
699842
swap_stats_.num_swap_rejected++;

0 commit comments

Comments
 (0)