blob: 6b6fd969701577501323f58c02e7804f68a23799 [file] [log] [blame]
#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);
}