-
Notifications
You must be signed in to change notification settings - Fork 130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate the implementation of RPC service and compilation session #254
Comments
looks good, definitely cleans up a lot of boilerplate I'm not sure I understand the need for |
Ah, that's a good point. The idea was to allow you to cheaply define different subsets of action spaces, like:
but tbh I'm not sure if that is a common use case, or even if it wouldn't just be better suited by creating different So V2 could be: class CompilationSession:
...
action_space: ActionSpace = ActionSpace() # what the compiler can do
...
def __init__(self, working_dir: Path, benchmark: Benchmark):
"""start a new compilation session with the given action space & benchmark"""
... |
I like it a lot! With the C++ session, can the out params be references instead of pointers? In the C++ API, if the action space did change, how do you know to which one it changed? Is taking
Why take
I slightly wonder about the format and whether the fixed params shouldn't be passed in to the createAndRun... fn, like the name is already. |
Yep, good idea.
Ah, good point. In my little demo implementation I added a virtual Status applyAction(size_t actionIndex, bool &endOfEpisode,
std::optional<ActionSpace> &newActionSpacce,
bool &actionHadNoEffect)
Hm, not really. I have an open issue, #52 to add support for non-categorical action spaces, but no concrete plans to work on it. I suppose I could wrap the current integer-only action in an def apply_action(self, action: Action) -> Tuple[bool, Optional[ActionSpace], bool]:
"""apply an action. Returns a tuple: (end_of_session, new_action_space, action_had_no_effect).
"""
print(action.action_index) # The index into the current categorical space
How about
Consider the // dispatch requires a bunch of string comparisons,
// harder to maintain + refactor
if (observationSpace.name() == "Ir") {
...
} else if (observationSpace.name() == "Autophase") {
...
} with indices, you can define an enum to perform that switch, which means the type system will help you: // fast int switch, + compiler warning if I've missed a case
switch (static_cast<ObservationSpacesEnum>(observationSpaceIndex)) {
case ObservationSpacesEnum::IR:
...
case ObservationSpacesEnum::AUTOPHASE:
...
} So to me it seems that indirection from observationSpaceIndex -> observationSpace index isn't a priority simply because ObservationSpace / ActionSpace messages aren't really helpful for computing the values, just for describing the spaces. I'm open to changing this though. :-)
I would prefer this, but it would require that whatever APIs the
Not sure I follow? |
Filed #258 to future-proof the action argument to |
Ah, I see. Fair enough. struct ObservationSpace<T> {
virtual string name() const;
virtual T operator() () = 0;
};
// User code:
struct IRCount: ObservationSpace<int> {
string name() { return "IRCount"; }
int operator() () { return ...; }
}; That way I don't have to manually dispatch, you can do it for me after you've found the right one of my observation spaces for me. You can construct what ever you need to transmit by querying a user friendly class, rather than the protobuf (protobufs aren't exactly user friendly). |
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
I agree, though I'm not sure what that type hierarchy would look like. The What I do like about the class hiearchy idea is that it halves the number of methods needed for template<typename T> // T must be one of: {int64, double, std::string, std::vector<int64>, std::vector<double>}
class ObservationSpace {
public:
virtual std::string name() const = 0;
virtual T operator() () = 0; // compute an observation
}
template<typename T> // Future proofing for different action types
class ActionSpace {
public:
virtual std::string name() const = 0;
virtual void operator() (T action) = 0;
}
class CategoricalActionSpace : public ActionSpace<int> {
public:
virtual std::vector<std::string> actionNames() const = 0; // e.g. LLVM pass names
virtual void operator() (int actionIndex) = 0;
}
class CompilationSession {
public:
virtual std::string getCompilerVersion() const;
// not sure if there's a prettier way to get an iterator over a bunch of polymorphic types
virtual std::vector<ActionSpace const*> getActionSpaces() const = 0;
virtual std::vector<ObservationSpace const*> getObservationSpaces() const = 0;
[[nodiscard]] virtual grpc::Status init(const Benchmark& benchmark) = 0;
} Happy to keep iterating on this design until we find something that works |
This will be used by the CompilationSession runtime to keep track of the Benchmark protobufs that have been sent by the user to the service, so that CompilationSession::init() can be passed a benchmark proto. This is a generalization of the BenchmarkFactory class that is used by the LLVM service to keep a bunch of llvm::Modules loaded in memory. The same class is implemented twice in C++ and Python using the same semantics and with the same tests. The cache has a target maximum size based on the number of bytes of its elements. When this size is reached, benchamrks are evicted using a random policy. The idea behind random cache eviction is that this cache will be large enough by default to store a good number of benchmarks, so exceeding the max cache size implies a training loop in which random programs are selected from a very large pool, rather than smaller pool where an LRU policy would be better. Issue facebookresearch#254.
This will be used by the CompilationSession runtime to keep track of the Benchmark protobufs that have been sent by the user to the service, so that CompilationSession::init() can be passed a benchmark proto. This is a generalization of the BenchmarkFactory class that is used by the LLVM service to keep a bunch of llvm::Modules loaded in memory. The same class is implemented twice in C++ and Python using the same semantics and with the same tests. The cache has a target maximum size based on the number of bytes of its elements. When this size is reached, benchamrks are evicted using a random policy. The idea behind random cache eviction is that this cache will be large enough by default to store a good number of benchmarks, so exceeding the max cache size implies a training loop in which random programs are selected from a very large pool, rather than smaller pool where an LRU policy would be better. Issue facebookresearch#254.
This will be used by the CompilationSession runtime to keep track of the Benchmark protobufs that have been sent by the user to the service, so that CompilationSession::init() can be passed a benchmark proto. This is a generalization of the BenchmarkFactory class that is used by the LLVM service to keep a bunch of llvm::Modules loaded in memory. The same class is implemented twice in C++ and Python using the same semantics and with the same tests. The cache has a target maximum size based on the number of bytes of its elements. When this size is reached, benchamrks are evicted using a random policy. The idea behind random cache eviction is that this cache will be large enough by default to store a good number of benchmarks, so exceeding the max cache size implies a training loop in which random programs are selected from a very large pool, rather than smaller pool where an LRU policy would be better. Issue facebookresearch#254.
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
This adds a new CompilerGymService class in both C++ and Python that takes a concrete CompilationSession subclass and provides all of the runtime logic to start and manage and RPC server that responds to requests and dispatches to CompilationSession instances. Issue facebookresearch#254.
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
Aye, I think so. Although, you don't have to provide that. Users of the class can do whatever they need to do. Like maybe their compiler isn't thread safe and so they can only support one session per process and everything is global. They can make a back pointer as they see fit when they create their
Why return T?
T not defined here.
I'm less sure about this. I have a nagging feeling there's a semantic difference between an observation space and an action space. The former represents a single function, the latter a collection of functions. struct Action {
virtual string name() const = 0;
virtual string help() const { return "Perform action " + name()); }
virtual void operation() () = 0;
};
struct ActionSpace {
virtual string name() const = 0;
};
struct CategoricalActionSpace: ActionSpace {
// No virtual methods here
CategoricalActionSpace(string name, vector<Action> actions) ...
string name() const { return name; }
vector<Action> actions() const { return actions; }
...
Action& operator(int key) const { return actions[key]; }
Action& operator(string key) const { return find_by_name(actions, key); }
};
struct LLVMAction: Action {
LLVMAction(LLVMCompilerSession& session, string pass) ...
...
static vector<string> passNames = { "dce", "loop-unroll", ... };
static vector<LLVMAction> actions(LLVMCompilerSession& session) {
return passNames.map(LLVMAction(session, _)); // Or however C++ does it
}
};
CategoricalActionSpace llvmActions( "llvm-actions", LLVMAction::actions(session));
// Use an action
llvmActions("dce")();
llvmActions(23)(); I'm not sure if BTW, your original design was good - I should be ignored at all costs. |
Ah I see, that makes sense. I wonder how much of a boilerplate tradeoff that is - now users will need to make public / add accessors to the internal state of
Woops, that's a mistake. Fixed.
I like this!
I'll merge the original design into development but I won't cut a public release until we're happy with it. If were going to introduce a new class hierarchy, now might be a good time for it. Thanks for the suggestions so far! |
This will be used by the CompilationSession runtime to keep track of the Benchmark protobufs that have been sent by the user to the service, so that CompilationSession::init() can be passed a benchmark proto. This is a generalization of the BenchmarkFactory class that is used by the LLVM service to keep a bunch of llvm::Modules loaded in memory. The same class is implemented twice in C++ and Python using the same semantics and with the same tests. The cache has a target maximum size based on the number of bytes of its elements. When this size is reached, benchamrks are evicted using a random policy. The idea behind random cache eviction is that this cache will be large enough by default to store a good number of benchmarks, so exceeding the max cache size implies a training loop in which random programs are selected from a very large pool, rather than smaller pool where an LRU policy would be better. Issue facebookresearch#254.
This adds a new CompilerGymService class in both C++ and Python that takes a concrete CompilationSession subclass and provides all of the runtime logic to start and manage and RPC server that responds to requests and dispatches to CompilationSession instances. Issue facebookresearch#254.
This adds a new CompilerGymService class in both C++ and Python that takes a concrete CompilationSession subclass and provides all of the runtime logic to start and manage and RPC server that responds to requests and dispatches to CompilationSession instances. Issue facebookresearch#254.
Closing this as all of the PRs are now merged, but will keep the discussion going about better ways to structure the action space. |
The CompilationSession class encapsulates an incremental compilation session. This is the first in a series of patches that separates the RPC server from the compilation session. Issue facebookresearch#254.
This will be used by the CompilationSession runtime to keep track of the Benchmark protobufs that have been sent by the user to the service, so that CompilationSession::init() can be passed a benchmark proto. This is a generalization of the BenchmarkFactory class that is used by the LLVM service to keep a bunch of llvm::Modules loaded in memory. The same class is implemented twice in C++ and Python using the same semantics and with the same tests. The cache has a target maximum size based on the number of bytes of its elements. When this size is reached, benchamrks are evicted using a random policy. The idea behind random cache eviction is that this cache will be large enough by default to store a good number of benchmarks, so exceeding the max cache size implies a training loop in which random programs are selected from a very large pool, rather than smaller pool where an LRU policy would be better. Issue facebookresearch#254.
This adds a new CompilerGymService class in both C++ and Python that takes a concrete CompilationSession subclass and provides all of the runtime logic to start and manage and RPC server that responds to requests and dispatches to CompilationSession instances. Issue facebookresearch#254.
🚀 Feature
Make it easier for users to add support for new compilers by providing base classes that allow users to "fill in the blanks" for interacting with their compiler, without having to implement all of the other RPC interface boilerplate.
Motivation
Top add support for a new compiler, a user must write a compiler service that performs two key functions:
This division of roles is obvious in the example compiler services. In the python example, the RPC server is implemented here and the compilation session here. The compilation session is where the actual interesting work gets done. The RPC server is boilerplate, and as can be seen in the example, is not an insubstantial amount of code (and must be implemented carefully to avoid concurrency bugs / performance issues). Given that the behavior of the RPC Server is the same across all compiler services, we should provide a builtin implementation so that the user can focus only on implementing the compilation session.
Pitch
Provide helper code for C++ and Python that makes it easier to add support for new compilers.
Python
Add a base class that represents a single interactive session with a compiler:
To add support for a new compiler, a user subclasses from this, provides implementations for the abstract methods, and then calls a helper function that will create and launch an RPC service using this class:
The
create_and_run_compiler_gym_service()
function here performs all of the boilerplate of command line argument parsing, writing to the correct temporary files, handling RPC requests etc.C++
Very similar to the python interface, only using out-params:
and to use it:
The text was updated successfully, but these errors were encountered: