AutoPhase: Juggling HLS Phase Orderings in Random Forests with Deep Reinforcement Learning

The performance of the code a compiler generates depends on the order in which it applies the optimization passes. Choosing a good order--often referred to as the phase-ordering problem, is an NP-hard problem. As a result, existing solutions rely on a variety of heuristics. In this paper, we evaluate a new technique to address the phase-ordering problem: deep reinforcement learning. To this end, we implement AutoPhase: a framework that takes a program and uses deep reinforcement learning to find a sequence of compilation passes that minimizes its execution time. Without loss of generality, we construct this framework in the context of the LLVM compiler toolchain and target high-level synthesis programs. We use random forests to quantify the correlation between the effectiveness of a given pass and the program's features. This helps us reduce the search space by avoiding phase orderings that are unlikely to improve the performance of a given program. We compare the performance of AutoPhase to state-of-the-art algorithms that address the phase-ordering problem. In our evaluation, we show that AutoPhase improves circuit performance by 28% when compared to using the -O3 compiler flag, and achieves competitive results compared to the state-of-the-art solutions, while requiring fewer samples. Furthermore, unlike existing state-of-the-art solutions, our deep reinforcement learning solution shows promising result in generalizing to real benchmarks and 12,874 different randomly generated programs, after training on a hundred randomly generated programs.


INTRODUCTION
High-Level Synthesis (HLS) automates the process of creating digital hardware circuits from algorithms written in high-level languages. Modern HLS tools (Xilinx, 2019;Intel, 2019;Canis et al., 2013) use the same front-end as the traditional software compilers. They rely on traditional software compiler techniques to optimize the input program's intermediate representation (IR) and produce circuits in the form of RTL code. Thus, the quality of compiler front-end optimizations directly impacts the performance of HLSgenerated circuit.
Program optimization is a notoriously difficult task. A program must be just in "the right form" for a compiler to recognize the optimization opportunities. This is a task a programmer might be able to perform easily, but is often difficult for a compiler. Despite a decade of research on developing sophisticated optimization algorithms, there is still a performance gap between the HLS generated code * Equal contribution. 1 University of California, Berkeley 2 Massachusetts Institute of Technology. Correspondence to: Qijing Huang <qijing.huang@berkeley.edu>, Ameer Haj-Ali <ameerh@berkeley.edu>.
In this paper, we build off the LLVM compiler (Lattner & Adve, 2004). However, our techniques, can be broadly applicable to any compiler that uses a series of optimization passes. In this case, the optimization of an HLS program consists of applying a sequence of analysis and optimization phases, where each phase in this sequence consumes the output of the previous phase, and generates a modified version of the program for the next phase. Unfortunately, these phases are not commutative which makes the order in which these phases are applied critical to the performance of the output.
Consider the program in Figure 1, which normalizes a vector. Without any optimizations, the norm function will take Θ(n 2 ) to normalize a vector. However, a smart compiler will implement the loop invariant code motion (LICM) (Muchnick, 1997) optimization, which allows it to move the call to mag above the loop, resulting in the code on the left column in Figure 2. This optimization brings the runtime down to Θ(n)-a big speedup improvement. Another optimization the compiler could perform is (function) inlining (Muchnick, 1997). With inlining, a call to a function is simply replaced with the body of the function, reducing the overhead of the function call. Applying inlining to the code will result in the code in the right column of Figure  Now, consider applying these optimization passes in the opposite order: first inlining then LICM. After inlining, we get the code on the left of Figure 3. Once again we get a modest speedup, having eliminated n function calls, though our runtime is still Θ(n 2 ). If the compiler afterwards attempted to apply LICM, we would find the code on the right of Figure 3. LICM was able to successfully move the allocation of sum outside the loop. However, it was unable to move the instruction setting sum=0 outside the loop, as doing so would mean that all iterations excluding the first one would end up with a garbage value for sum. Thus, the internal loop will not be moved out.
As this simple example illustrates, the order in which the optimization phases are applied can be the difference between the program running in Θ(n 2 ) versus Θ(n). It is thus crucial to determine the optimal phase ordering to maximize the circuit speeds. Unfortunately, not only is this a difficult task, but the optimal phase ordering may vary from program to program. Furthermore, it turns out that finding the optimal sequence of optimization phases is an NP-hard problem, and exhaustively evaluating all possible sequences is infeasible in practice. In this work, for example, the search space extends to more than 2 247 phase orderings.
The goal of this paper is to provide a mechanism for automatically determining good phase orderings for HLS programs to optimize for the circuit speed. To this end, we aim to leverage recent advancements in deep reinforcement learning (RL) (Sutton & Barto, 1998;Haj-Ali et al., 2019b) to address the phase ordering problem. With RL, a software agent continuously interacts with the environment by taking actions. Each action can change the state of the environment and generate a "reward". The goal of RL is to learn a policy-that is, a mapping between the observed states of the environment and a set of actions-to maximize the cumulative reward. An RL algorithm that uses a deep neural network to approximate the policy is referred to as a deep RL algorithm. In our case, the observation from the environment could be the program and/or the optimization passes applied so far. The action is the optimization pass to apply next, and the reward is the improvement in the circuit performance after applying this pass. The particular framing of the problem as an RL problem has a significant impact on the solution's effectiveness. Significant challenges exist in understanding how to formulate the phase ordering optimization problem in an RL framework.
In this paper, we consider three approaches to represent the environment's state. The first approach is to directly use salient features from the program. The second approach is to derive the features from the sequence of optimizations we applied while ignoring the program's features. The third approach combines the first two approaches. We evaluate these approaches by implementing a framework that takes a group of programs as input and quickly finds a phase ordering that competes with state-of-the-art solutions. Our main contributions are: • Extend a previous work (Huang et al., 2019) and leverage deep RL to address the phase-ordering problem.
• An importance analysis on the features using random forests to significantly reduce the state and action spaces.
• AutoPhase: a framework that integrates the current HLS compiler infrastructure with the deep RL algorithms.
• We show that AutoPhase gets a 28% improvement over -O3 for nine real benchmarks. Unlike all state-of-theart approaches, deep RL demonstrates the potential to generalize to thousands of different programs after training on a hundred programs.

Compiler Phase-ordering
Compilers execute optimization passes to transform programs into more efficient forms to run on various hardware targets. Groups of optimizations are often packaged into "optimization levels" , such as -O0 and -O3, for ease. While these optimization levels offer a simple set of choices for developers, they are handpicked by the compiler-designers and often most benefit certain groups of benchmark programs. The compiler community has attempted to address the issue by selecting a particular set of compiler optimizations on a per-program or per-target basis for software (Triantafyllis et al., 2003;Almagor et al., 2004;Pan & Eigenmann, 2006;Ansel et al., 2014).
Since the search space of phase-ordering is too large for an exhaustive search, many heuristics have been proposed to explore the space by using machine learning. Huang et al. tried   by using modified greedy algorithms (Huang et al., 2013;2015). It achieved 16% improvement vs -O3 on the CHstone benchmarks (Hara et al., 2008), which we used in this paper. In (Agakov et al., 2006) both independent and Markov models were applied to automatically target an optimized search space for iterative methods to improve the search results. In (Stephenson et al., 2003), genetic algorithms were used to tune heuristic priority functions for three compiler optimization passes. Milepost GCC (Fursin et al., 2011) used machine learning to determine the set of passes to apply to a given program, based on a static analysis of its features. It achieved an 11% execution time improvement over -O3, for the ARC reconfigurable processor on the MiBench program suite1. In (Kulkarni & Cavazos, 2012) the challenge was formulated as a Markov process and supervised learning was used to predict the next optimization, based on the current program state. OpenTuner (Ansel et al., 2014) autotunes a program using an AUC-Bandit-meta-technique-directed ensemble selection of algorithms. Its current mechanism for selecting the compiler optimization passes does not consider the order or support repeated optimizations. Wang et al. (Wang & OBoyle, 2018), provided a survey for using machine learning in compiler optimization where they also described that using program features might be helpful. NeuroVectorizer (Haj-Ali et al., 2020;2019a) used deep RL for automatically tuning compiler pragmas such as vectorization and interleaving factors. NeuroVectorizer achieves 97% of the oracle performance (brute-force search) on a wide range of benchmarks.

Reinforcement Learning Algorithms
Reinforcement learning (RL) is a machine learning approach in which an agent continually interacts with the environment (Kaelbling et al., 1996). In particular, the agent observes the state of the environment, and based on this observation takes an action. The goal of the RL agent is then to compute a policy-a mapping between the environment states and actions-that maximizes a long term reward.
RL can be viewed as a stochastic optimization solution for solving Markov Decision Processes (MDPs) (Bellman, 1957), when the MDP is not known. An MDP is defined by a tuple with four elements: S, A, P (s, a), r(s, a) where S is the set of states of the environment, A describes the set of actions or transitions between states, s ∼P (s, a) describes the probability distribution of next states given the current state and action and r(s, a) : S × A → R is the reward of taking action a in state s. Given an MDP, the goal of the agent is to gain the largest possible aggregate reward. The objective of an RL algorithm associated with an MDP is to find a decision policy π * (a|s) : s → A that achieves this goal for that MDP: Deep RL leverages a neural network to learn the policy (and sometimes the reward function). Policy Gradient (PG) (Sutton et al., 2000), for example, updates the policy directly by differentiating the aggregate reward E in Equation 1: and updating the network parameters (weights) in the direction of the gradient: Note that PG is an on-policy method in that it uses decisions made directly by the current policy to compute the new policy.
PPO is a variant of PG that enables multiple epochs of minibatch updates to improve the sample complexity. Vanilla PG performs one gradient update per data sample while PPO uses a novel surrogate objective function to enable multiple epochs of minibatch updates. It alternates between sampling data through interaction with the environment and optimizing the surrogate objective function using stochastic gradient ascent. It performs updates that maximizes the reward function while ensuring the deviation from the previous policy is small by using a surrogate objective function. The loss function of PPO is defined as: (4) where r t (θ) is defined as a probability ratio π θ (at|st) π θ old (at|st) so r(θ old ) = 1. This term penalizes policy update that move r t (θ) from r(θ old ).Â t denotes the estimated advantage that approximates how good a t is compared to the average. The second term in the min function acts as a disincentive for A3C uses an actor (usually a neural network) that interacts with the critic, which is another network that evaluates the action by computing the value function. The critic tells the actor how good its action was and how it should adjust. The update performed by the algorithm can be seen as ∇ θ logπ θ (a i,t |s i,t )Â t .

Evolutionary Algorithms
Evolutionary algorithms are another technique that can be used to search for the best compiler pass ordering. It con-tains a family of population-based meta-heuristic optimization algorithms inspired by natural selection. The main idea of these algorithms is to sample a population of solutions and use the good ones to direct the distribution of future generations. Two commonly used Evolutionary Algorithms are Genetic Algorithms (GA) (Goldberg, 2006) and Evolution Strategies (ES) (Conti et al., 2018).
GA generally requires a genetic representation of the search space where the solutions are coded as integer vectors. The algorithm starts with a pool of candidates, then iteratively evolves the pool to include solutions with higher fitness by the three following strategies: selection, crossover, and mutation. Selection keeps a subset of solutions with the highest fitness values. These selected solutions act as parents for the next generation. Crossover merges pairs from the parent solutions to produce new offsprings. Mutation perturbs the offspring solutions with a low probability. The process repeats until a solution that reaches the goal fitness is found or after a certain number of generations.
ES works similarly to GA. However, the solutions are coded as real numbers in ES. In addition, ES is self-adapting. The hyperparameters, such as the step size or the mutation probability, are different for different solutions. They are encoded in each solution, so good settings get to the next generation with good solutions. Recent work (Salimans et al., 2017) has used ES to update policy weights for RL and showed it is a good alternative for gradient-based methods.

AUTOPHASE FRAMEWORK FOR AUTOMATIC PHASE ORDERING
We leverage an existing open-source HLS framework called LegUp  that compiles a C program into a hardware RTL design. In (Huang et al., 2013), an approach is devised to quickly determine the number of hardware execution cycles without requiring time-consuming logic simulation. We develop our RL simulator environment based on the existing harness provided by LegUp and validate our final results by going through the time-consuming logic simulation. AutoPhase takes a program (or multiple programs) and intelligently explores the space of possible passes to figure out an optimal pass sequence to apply. Table 1 lists all the passes used in AutoPhase. The workflow of AutoPhase is illustrated in Figure 4.

HLS Compiler
AutoPhase takes a set of programs as input and compiles them to a hardware-independent intermediate representation (IR) using the Clang front-end of the LLVM compiler.
Optimization and analysis passes act as transformations on the IR, taking a program as input and emitting a new IR as output. The HLS tool LegUp is invoked after the compiler  Figure 4. The block diagram of AutoPhase. The input programs are compiled to an LLVM IR using Clang/LLVM. The feature extractor and clock-cycle profiler are used to generate the input features (state) and the runtime improvement (reward), respectively from the IR. The input features and runtime improvement are fed to the deep RL agent as in input data to train on. The RL agent predicts the next best optimization passes to apply. After convergence, the HLS compiler is used to compile the LLVM IR to hardware RTL. optimization as a back-end pass, which transforms LLVM IR into hardware modules.

Clock-cycle Profiler
Once the hardware RTL is generated, one could run a hardware simulation to gather the cycle count results of the synthesized circuit. This process is quite time-consuming, hindering RL and all other optimization approaches. Therefore, we approximate cycle count using the profiler in LegUp (Huang et al., 2013), which leverages the software traces and runs 20× faster than hardware simulation. In LegUp, the frequency of the generated circuits is set as a compiler constraint that directs the HLS scheduling algorithm. In other words, HLS tool will always try to generate hardware that can run at a certain frequency. In our experiment setting, without loss of generality, we set the target frequency of all generated hardware to 200MHz. We experimented with lower frequencies too; the improvements were similar but the cycle counts the different algorithms achieved were better as more logic could be fitted in a single cycle.

IR Feature Extractor
Wang et al. (Wang & OBoyle, 2018) proposed to convert a program into an observation by extracting all the features from the program. Similarly, in addition to the LegUp backend tools, we developed analysis passes to extract 56 static features from the program, such as the number of basic blocks, branches, and instructions of various types. We use these features as partially observable states for the RL learning and hope the neural network can capture the correlation of certain combinations of these features and certain optimizations. Table 2 lists all the features used.

Random Program Generator
As a data-driven approach, RL generalizes better if we train the agent on more programs. However, there are a limited number of open-source HLS examples online. Therefore, we expand our training set by automatically generating synthetic HLS benchmarks. We first generate standard C programs using CSmith (Yang et al., 2011), a random C program generator, which is originally designed to generate test cases for finding compiler bugs. Then, we develop scripts to filter out programs that take more than five minutes to run on CPU or fail the HLS compilation.

Overall Flow of AutoPhase
We integrate the compilation utilities into a simulation environment in Python with APIs similar to an OpenAI gym (Brockman et al., 2016). The overall flow works as follows: 1. The input program is compiled into LLVM IR using the Clang/LLVM.

The IR Feature
Extractor is run to extract salient program features.
4. The Clock-cycle Profiler estimates a clock-cycle count for the generated circuit.
5. The RL agent takes the program features or the histogram of previously applied passes and the improvement in clock-cycle count as input data to train on.
6. The RL agent predicts the next best optimization passes to apply.
7. New LLVM IR is generated after the new optimization sequence is applied.
Note that AutoPhase uses the LLVM compiler and the passes used are listed in Table 2. However, adding support for any compiler or optimization passes in AutoPhase is very easy and straightforward. The action and state definitions must be specified again.

CORRELATION OF PASSES AND PROGRAM FEATURES
Similar to the case with many deep learning approaches, explainability is one of the major challenges we face when applying deep RL to the phase-ordering challenge. To analyze and understand the correlation of passes and program features, we use random forests (Breiman, 2001) to learn the importance of different features. Random forest is an ensemble of multiple decision trees. The prediction made by each tree could be explained by tracing the decisions made at each node and calculating the importance of different features on making the decisions at each node. This helps us to identify the effective features and passes to use and show whether our algorithms learn informative patterns on data.
For each pass, we build two random forests to predict whether applying it would improve the circuit performance. The first forest takes the program features as inputs while the second takes a histogram of previously applied passes. To gather the training data for the forests, we run PPO with high exploration parameter on 100 randomly generated programs to generate feature-action-reward tuples. The algorithm assigns higher importance to the input features that affect the final prediction more.

Importance of Program Features
The heat map in Figure 5 shows the importance of different features on whether a pass should be applied. The higher the value is, the more important the feature is (the sum of the values in each row is one). The random forest is trained with 150,000 samples generated from the random programs. The index mapping of features and passes can be found in Tables 1 and 2. For example, the yellow pixel corresponding to feature index 17 and pass index 23 reflects that numberof-critical-edges affects the decision on whether to apply -loop-rotate greatly. A critical edge in control flow graph is an edge that is neither the only edge leaving its source block, nor the only edge entering its destination block. The critical edges can be commonly seen in a loop as a back edge so the number of critical edges might roughly represent the number of loops in a program. The transform pass -looprotate detects a loop and transforms a while loop to a dowhile loop to eliminate one branch instruction in the loop body. Applying the pass results in better circuit performance as it reduces the total number of FSM states in a loop.
Other expected behaviors are also observed in this figure.
For instance, the correlation between number of branches and the transform passes -loop-simplify, -tailcallelism (which transforms calls of the current function i.e., self recursion, followed by a return instruction with a branch to the entry of the function, creating a loop), -lowerswitch (which rewrites switch instructions with a sequence of branches).
Other interesting behaviors are also captured. For example, in the correlation between binary operations with a constant operand and -functionattrs, which marks different operands of a function as read-only (constant). Some correlations are harder to explain, for example, number of BitCast instructions and -instcombine, which combines instructions into fewer simpler instructions. This is actually a result of -instcombine reducing the loads and stores that call bitcast instructions for casting pointer types. Another example is number of memory instructions and -sink, where -sink basically moves memory instructions into successor blocks and delays the execution of memory until needed. Intuitively, whether to apply -sink should be dependent on whether there is any memory instruction in the program. Our last example to show is number of occurrences of constant 0 and -deadargelim, where -deadargelim helped eliminate dead/unused constant zero arguments.
Overall, we observe that all the passes are correlated to some features and are able to affect the final circuit performance. We also observe that multiple features are not effective at directing decisions and training with them could increase the variance that would result in lower prediction accuracy of our results. For example, the total number of instructions did not give a direct indication of whether applying a pass would be helpful or not. This is because sometimes more instructions could improve the performance (for example, due to loop unrolling) and eliminating unnecessary code could also improve the performance. In addition, the importance of features varies among different benchmarks depending on the tasks they perform. Figure 6 illustrates the impact of previously applied passes on the new pass to apply. The higher the value is, the more important having the old pass is. From this figure, we learn that for the programs we trained on passes -scalarrepl, -gvn, Figure 6. Heat map illustrating the importance of indices of previously applied passes and the new pass to apply.

Importance of Previously Applied Passes
-scalarrepl-ssa, -loop-reduce, -loop-deletion, -reassociate, -loop-rotate, -partial-inliner, -early-cse, -adce, -instcombine, -simplifycfg, -dse, -loop-unroll, -mem2reg, and -sroa, are more impactful on the performance compared to the rest of the passes regardless of their order in the trajectory. Point (23,23) has the highest importance in which implies that pass -loop-rotate is very helpful and should be included if not applied before. By examining thousands of the programs, we find that -loop-rotate indeed reduces the cycle count significantly. Interestingly, applying this pass twice is not harmful if the passes were given consecutively. However, giving this pass twice with some other passes between them is sometimes very harmful. Another interesting behavior our heat map captured is the fact that applying pass 33 (-loop-unroll) after (not necessarily consecutive) pass 23 (-loop-rotate) was much more useful compared to applying these two passes in the opposite order.

The RL Environment Definition
Assume the optimal number of passes to apply is N and there are K transform passes to select from in total, our search space S for the phase-ordering problem is [0, K N ). Given M program features and the history of already applied passes, the goal of deep RL is to learn the next best optimization pass a to apply that minimizes the long term cycle count of the generated hardware circuit. Note that the optimization state s is partially observable in this case as the M program features cannot fully capture all the properties of a program.
Action Space -we define our action space A as {a ∈ Z : a ∈ [0, K)} where K is the total number of transform passes.
Observation Space -two types of input features were considered in our evaluation: 1 program features o f ∈ Z M listed in Table 2 and 2 action history which is a histogram of previously applied passes o a ∈ Z K . After each RL step where the pass i is applied, we call the feature extractor in our environment to return new o f , and update the action histogram element o ai to o ai + 1.
Reward -the cycle count of the generated circuit is reported by the clock-cycle profiler at each RL iteration. Our reward is defined as R = c prev − c cur , where c prev and c cur represent the previous and the current cycle count of the generated circuit respectively. It is possible to define a different reward for different objectives. For example, the reward could be defined as the negative of the area and thus the RL agent will optimize for the area. It is also possible to co-optimize multiple objectives (e.g., area, execution time, power, etc.) by defining a combination of different objectives.

Applying Multiple Passes per Action
An alternative to the action formulation above is to evaluate a complete sequence of passes with length N instead of a single action a at each RL iteration. Upon the start of training a new episode, the RL agent resets all pass indices p ∈ Z N to the index value K 2 . For pass p i at index i, the next action to take is either to change to a new pass or not. By allowing positive and negative index update for each p, we reduced the total steps required to traverse all possible pass indices. The sub-action space a i for each pass is thus defined as [−1, 0, 1]. The total action space A is defined as [−1, 0, 1] N . At each step, the RL agent predicts the updates [a 1 , a 2 , ..., a N ] to N passes, and the current optimization sequence [p 1 , p 2 , ..., p N ] is updated to [p 1 + a 1 , p 2 + a 2 , ..., p N + a N ].

Normalization Techniques
In order for the trained RL agent to work on new programs, we need to properly normalize the program features and rewards so they represent a meaningful state among different programs. In this work, we experiment with two techniques: 1 taking the logarithm of program features or rewards and, 2 normalizing to a parameter from the original input program that roughly depicts the problem size. For technique 1 , note that taking the logarithm of the program features not only reduces their magnitude, it also correlates them in a different manner in the neural network. Since, w 1 log(o f1 ) + w 2 log(o f2 ) = log(o w1 f1 o w2 f2 ), the neural network is learning to correlate the products of features instead of a linear combination of them. For technique 2 , we normalize the program features to the total number of instructions in the input program (o f norm = o f o f 51 ), which is feature #51 in Table 2.

EVALUATION
To run our deep RL algorithms we use RLlib (Liang et al., 2017), an open-source library for reinforcement learning that offers both high scalability and a unified API for a variety of applications. RLlib is built on top of Ray (Moritz et al., 2018), a high-performance distributed execution framework targeted at large-scale machine learning and reinforcement learning applications. We ran the framework on a four-core Intel i7-4765T CPUwith a Tesla K20c GPUfor training and inference.
We set our frequency constraint in HLS to 200MHz and use the number of clock cycles reported by the HLS profiler as the circuit performance metric. In (Huang et al., 2013), results showed a one-to-one correspondence between the clock cycle count and the actual hardware execution time under certain frequency constraint. Therefore, better clock cycle count will lead to better hardware performance.

Performance
To evaluate the effectiveness of various algorithms for tackling the phase-ordering problem, we run them on nine real HLS benchmarks and compare the results based on the final HLS circuit performance and the sample efficiency against state-of-the-art approaches for overcoming the phase ordering, which include random search, Greedy Algorithms (Huang et al., 2013), OpenTuner (Ansel et al., 2014), and Genetic Algorithms (Fortin et al., 2012). These benchmarks are adapted from CHStone (Hara et al., 2008) and LegUp examples. They are: adpcm, aes, blowfish, dhrystone, gsm, matmul, mpeg2, qsort, and sha. For this evaluation, the input features/rewards were not normalized, the pass length was set to 45, and each algorithm was run on a per-program basis. Table 3 lists the action and observation spaces used in all the deep RL algorithms.
The bar chart in Figure 7 shows the percentage improvement of the circuit performance compared to -O3 results on the nine real benchmarks from CHStone. The dots on the blue line in Figure 7 show the total number of samples for each program, which is the number of times the algorithm calls the simulator to gather the cycle count. -O0 and -O3 are the default compiler optimization levels. RL-PPO1 is a PPO explorer where we set all the rewards to 0 to test if the rewards are meaningful. RL-PPO2 is the PPO agent that learns the next pass based on a histogram of applied passes. RL-A3C is the A3C agent that learns based on the program features. Greedy performs the greedy algorithm, which always inserts the pass that achieves the highest speedup at the best position (out of all possible positions it can be inserted to) in the current sequence. RL-PPO3 uses a PPO agent and the program features but with the action space described in Section 5.2. explained in Section 5.2. OpenTuner runs an ensemble of six algorithms, which  includes two families of algorithms: particle swarm optimization (Kennedy, 2010) and GA, each with three different crossover settings. RL-ES is similar to A3C agent that learns based on the program features, but updates the policy network using the evolution strategy instead of backpropagation. Genetic-DEAP (Fortin et al., 2012) is a genetic algorithm implementation. random randomly generates a sequence of 45 passes at once instead of sampling them one-by-one.
From Greedy, we see that always adding the pass in the current sequence that achieves the highest reward leads to sub-optimal circuit performance. RL-PPO2 achieves higher performance than RL-PPO1, which shows that the deep RL captures useful information during training. Using the histogram of applied passes results in better sample efficiency, but using the program features with more samples results in a slightly higher speedup. RL-PPO2, for example, at the minor cost of 4% lower speedup, achieves 50× more sample efficiency than OpenTuner. Using ES to update the policy is supposed to be more sample efficient for problems with sparse rewards like ours, however, our experiments did not benefit from that. Furthermore, RL-PPO3 with multiple action updates achieves a higher speedup than the other deep RL algorithms with a single action. One reason for that is the ability of RL-PPO3 to explore more passes per compilation as it applies multiple passes simultaneously in between every compilation. On the other hand, the other deep RL algorithms apply a single pass at a time.

Generalization
With deep RL, the search should benefit from prior knowledge learned from other different programs. This knowledge should be transferable from one program to another. For example, as discussed in section 4 applying pass -loop-rotate is always beneficial, and -loop-unroll should be applied after -loop-rotate. Note that the black-box search algorithms, such as OpenTuner, GA, and greedy algorithms, cannot generalize. For these algorithms, rerunning a new search with many compilations is necessary for every new program, as they do not learn any patterns from the programs to direct the search and can be viewed as a smart random search.
To evaluate how generalizable deep RL could be with different programs and whether any prior knowledge could be useful, we train on 100 randomly-generated programs using PPO. Random programs are used for transfer learning due to lack of sufficient benchmarks and because it is the worst-case scenario, i.e., they are very different from the programs that we use for inference. The improvement can be higher if we train on programs that are similar to the ones we inference on. We train a network with 256 × 256 fully connected layers and use the histogram of previously applied passes concatenated to the program features as the observation and passes as actions.
As described in Section 5.3, we experiment with two normalization techniques for the program features: 1 taking the logarithm of all the program features and 2 normalizing the program features to the total number of instructions in the program. In each pass sequence, the intermediate reward was defined as the logarithm of the improvement in cycle count after applying each pass. The logarithm was chosen so that the RL agent will not give much larger weights to big rewards from programs with longer execution time. Three approaches were evaluated: filtered-norm1 uses the filtered (based on the analysis in Section 4 where we only keep the important features and passes) program features and passes from Section with normalization technique 1 , original-norm2 uses all the program features and passes with normalization technique 2 , and filtered-norm2 uses the filtered program features and passes from Section 4 with normalization technique 2 . Filtering the features and passes might not be ideal, especially when different programs have different feature characteristics and impactful passes. However, reducing the number of features and passes helps to reduce variance among all programs and significantly narrow the search space. Figure 8 shows the episode reward mean as a function of the step for the three approaches. We observe that filtered-norm2 and filtered-norm1 converge much faster and achieve a higher episode reward mean than original-norm2, which uses all the features and passes. At roughly 8,000 steps the filtered-norm2 and filter-norm1 already achieve a very high episode reward mean, with minor improvements in later steps. Furthermore, the episode reward mean of the filtered approaches is still higher than that of original-norm2 even when we allowed it to train for 20 times more steps (i.e., 160,000 steps). This indicates that filtering the features and passes significantly improved the learning process. All three approaches learned to always apply pass -loop-rotate, and -loop-unroll after -loop-rotate. Another useful pass that the three approaches learned to apply is -loop-simplify, which performs several transformations to transform natural loops into a simpler form that enables subsequent analyses and transformations.
We now compare the generalization results of filtered-norm2 and filtered-norm1 with the other black-box algorithms. We use 100 randomlygenerated programs as the training set and nine real benchmarks from CHStone as the testing set for the deep RL-based methods. With the state-of-the-art black-box algorithms, we first search for the best pass sequences that achieved the lowest aggregated hardware cycle counts for the 100 random programs and then directly apply them to the nine test set programs. In Figure 9, the bar chart shows the percentage improvement of the circuit performance compared to -O3 on the nine real benchmarks, the dots on the blue line show the total number of samples each inference takes for one new program.
This evaluation shows that the deep RL-based inference achieves higher speedup than the predetermined sequences produced by the state-of-the-art black-box algorithms for new programs. The predetermined sequences that are over- fitted to the random programs can cause poor performance in unseen programs (e.g., -24% for Genetic-DEAP). Besides, normalization technique 2 works better compared to normalization technique 1 for deep RL generalization (4% vs 3% speedup). This indicates that normalizing the different instructions to the total number of instructions i.e., the distribution of the different instructions in Technique 2 represents more universal characteristics across different programs, while taking the log in Technique 1 only suppresses the value ranges of different program features. Furthermore, when we use other 12,874 randomly generated programs as the testing set with filtered-norm2, the speedup is 6% compared to -O3.

CONCLUSIONS
In this paper, we propose an approach based on deep RL to improve the performance of HLS designs by optimizing the order in which the compiler applies optimization phases.
We use random forests to analyze the relationship between program features and optimization passes. We then leverage this relationship to reduce the search space by identifying the most likely optimization phases to improve the performance, given the program features. Our RL based approach achieves 28% better performance than compiling with the -O3 flag after training for a few minutes, and a 24% improvement after training for less than a minute. Furthermore, we show that unlike prior work, our solution shows potential to generalize to a variety of programs. While in this paper we have applied deep RL to HLS, we believe that the same approach can be successfully applied to software compilation and optimization. Going forward, we envision using deep RL techniques to optimize a wide range of programs and systems.