| #include <limits> |
| #include <numeric> |
| #include <cmath> |
| #include <algorithm> |
| |
| #include "vtr_assert.h" |
| |
| #include "routing_predictor.h" |
| |
| class LinearModel { |
| public: |
| LinearModel(float slope = std::numeric_limits<float>::quiet_NaN(), float y_intercept = std::numeric_limits<float>::quiet_NaN()) |
| : slope_(slope) |
| , y_intercept_(y_intercept) { |
| } |
| |
| float find_x_for_y_value(float y_value) const { |
| //y = m*x + b |
| //x = (y - b) / m |
| |
| return (y_value - y_intercept_) / slope_; |
| } |
| |
| float get_slope() { |
| return slope_; |
| } |
| |
| float find_y_for_x_value(float x_value) const { |
| //y = m*x + b |
| return slope_ * x_value + y_intercept_; |
| } |
| |
| private: |
| float slope_; |
| float y_intercept_; |
| }; |
| |
| template<typename T> |
| float variance(std::vector<float> values, float avg); |
| |
| float covariance(std::vector<size_t> x_values, std::vector<float> y_values, float x_avg, float y_avg); |
| LinearModel simple_linear_regression(std::vector<size_t> x_values, std::vector<float> y_values); |
| LinearModel fit_model(std::vector<size_t> iterations, std::vector<size_t> overuse, float history_factor); |
| |
| template<typename T> |
| float variance(std::vector<T> values, float avg) { |
| float var = 0; |
| for (float val : values) { |
| var += (val - avg) * (val - avg); |
| } |
| |
| return var; |
| } |
| |
| float covariance(std::vector<size_t> x_values, std::vector<float> y_values, float x_avg, float y_avg) { |
| VTR_ASSERT(x_values.size() == y_values.size()); |
| |
| float cov = 0; |
| for (size_t i = 0; i < x_values.size(); ++i) { |
| cov += (x_values[i] - x_avg) * (y_values[i] - y_avg); |
| } |
| |
| return cov; |
| } |
| |
| float RoutingPredictor::get_slope() { |
| if (iterations_.size() > min_history_) { |
| auto model = fit_model(iterations_, iteration_overused_rr_node_counts_, history_factor_); |
| return model.get_slope(); |
| } |
| return -1; |
| } |
| |
| LinearModel simple_linear_regression(std::vector<size_t> x_values, std::vector<float> y_values) { |
| float y_avg = std::accumulate(y_values.begin(), y_values.end(), 0.) / y_values.size(); |
| float x_avg = std::accumulate(x_values.begin(), x_values.end(), 0.) / x_values.size(); |
| |
| float covariance_x_y = covariance(x_values, y_values, x_avg, y_avg); |
| float variance_x = variance(x_values, x_avg); |
| |
| float beta = covariance_x_y / variance_x; |
| float alpha = y_avg - beta * x_avg; |
| |
| return LinearModel(beta, alpha); |
| } |
| |
| LinearModel fit_model(std::vector<size_t> iterations, std::vector<size_t> overuse, float history_factor) { |
| //For pathfinder-based routing overuse tends to follow a negative-exponential: |
| // |
| // ^ |
| // | * |
| // | |
| // | * |
| // o | |
| // v | * |
| // e | |
| // r | * |
| // u | |
| // s | |
| // e | * |
| // | * |
| // | * |
| // | * |
| // ------------------------------------------------> |
| // iterations |
| // |
| //initially falling off rapidly but slowing down as the iterations increase |
| //(intuitively the easy congestion is resolved quickly as the non-critical signals |
| //are routed around, leaving only critical signals which compete for fast resources). |
| // |
| //A simple linear model will typically do a poor job of fitting an exponential. |
| //However an exponential appears linear when plotted on a log-linear plot: |
| // |
| // ^ |
| // | |
| // l | |
| // o | * |
| // g | |
| // | * |
| // o | |
| // v | * |
| // e | |
| // r | * |
| // u | * |
| // s | * |
| // e | * |
| // | |
| // ------------------------------------------------> |
| // iterations |
| // |
| //As a result we fit to the logarithm of the overuse, allowing us to capture the |
| //exponential congestion behaviour with a simple linear model |
| |
| //We use the last history_factor of all iterations to perform our fit |
| //This helps avoid the problem of under estimating convergence at the |
| //end of the route when overuse is low, as compared to a fixed history length |
| //(since the history inspected grows as the number of iterations increases, |
| //later iterations use a longer history which helps reduce the noise caused by |
| //small numbers of overused nodes) |
| size_t start = overuse.size() - std::round(history_factor * overuse.size()); |
| size_t end = overuse.size(); |
| |
| //Calculate the log overuse for the history we are interested in |
| std::vector<float> hist_log_overuse; |
| std::vector<size_t> hist_iters; |
| for (size_t i = start; i < end; ++i) { |
| hist_log_overuse.push_back(std::log(overuse[i])); |
| hist_iters.push_back(iterations[i]); |
| } |
| |
| //We fit a linear model to the log of the overuse, this keeps the model simple but |
| //captures the (typically) negative-exponential behaviour of overuse |
| return simple_linear_regression(hist_iters, hist_log_overuse); |
| } |
| |
| RoutingPredictor::RoutingPredictor(size_t min_history, float history_factor) |
| : min_history_(min_history) |
| , history_factor_(history_factor) { |
| //nop |
| } |
| |
| float RoutingPredictor::estimate_success_iteration() { |
| float success_iteration = std::numeric_limits<float>::quiet_NaN(); |
| |
| if (iterations_.size() > min_history_) { |
| auto model = fit_model(iterations_, iteration_overused_rr_node_counts_, history_factor_); |
| success_iteration = model.find_x_for_y_value(0.); |
| |
| if (success_iteration < 0.) { |
| //Iterations less than zero occurs when the slope is positive, |
| //and the intercept is before the y-axis |
| success_iteration = std::numeric_limits<float>::infinity(); |
| } |
| } |
| |
| return success_iteration; |
| } |
| |
| float RoutingPredictor::estimate_overuse_slope() { |
| //We use a fixed size sliding window of history to estimate the slope |
| //This makes the slope estimate more 'recent' than the values used to estimate |
| //the success iteration (although at the risk of being noisier). |
| constexpr float FIXED_HISTORY_SIZE = 5; //# of previous iterations to consider |
| |
| float slope = std::numeric_limits<float>::quiet_NaN(); |
| |
| float history_factor = FIXED_HISTORY_SIZE / iterations_.size(); //Fixed history size |
| |
| if (iterations_.size() >= FIXED_HISTORY_SIZE) { |
| auto model = fit_model(iterations_, iteration_overused_rr_node_counts_, history_factor); |
| |
| float log_curr_usage = model.find_y_for_x_value(*(--iterations_.end())); |
| float log_next_usage = model.find_y_for_x_value(*(--iterations_.end()) + 1); |
| |
| float curr_usage = std::exp(log_curr_usage); |
| float next_usage = std::exp(log_next_usage); |
| |
| slope = next_usage - curr_usage; |
| } |
| |
| return slope; |
| } |
| |
| void RoutingPredictor::add_iteration_overuse(size_t iteration, size_t overused_rr_node_count) { |
| iterations_.push_back(iteration); |
| iteration_overused_rr_node_counts_.push_back(overused_rr_node_count); |
| } |