Skip to content

Commit

Permalink
Add comments for public functions and classes
Browse files Browse the repository at this point in the history
ghstack-source-id: e01e07dd7101d7f98d9d1138724c816dad1c8c43
Pull Request resolved: #222
  • Loading branch information
PaliC committed Oct 21, 2022
1 parent 8cea05b commit 277424c
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 10 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ cmake --build . --config Release

### Running unit tests for `multipy::runtime`

We first need to generate the neccessary examples. First make sure your python enviroment has [torch](https://pytorch.org). Afterwards, once `multipy::runtime` is built, run the following (executed automatically for `docker` and `pip` above):
We first need to generate the neccessary examples. First make sure your python environment has [torch](https://pytorch.org). Afterwards, once `multipy::runtime` is built, run the following (executed automatically for `docker` and `pip` above):

```
cd multipy/multipy/runtime
Expand Down
77 changes: 69 additions & 8 deletions multipy/runtime/deploy.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ struct TORCH_API InterpreterSession {
// InterpreterSession* I)' instead. We will have no backwards compatibility
// guarentees for this function.
ReplicatedObj createMovable(Obj obj);

// Converts a `ReplicatedObj` to an `Obj` on this InterpreterSession.
Obj fromMovable(const ReplicatedObj& obj);

protected:
Expand All @@ -73,6 +75,9 @@ struct TORCH_API InterpreterSession {
std::function<void()> deconstruction_callback_ = nullptr;
};

// An `Interpreter` represents an invidual subinterpreter created by `torch::deploy`.
// It allows for the creation of `InterpreterSession` objects which allow users to interact with
// python objects.
class TORCH_API Interpreter {
private:
void* handle_;
Expand All @@ -84,17 +89,22 @@ class TORCH_API Interpreter {
multipy::optional<EmbeddedFile> torchPluginFile_;

public:
// Creates an Interpreter which is managed by `manager` and using the environment `env`
Interpreter(InterpreterManager* manager, std::shared_ptr<Environment> env);

// Creates an Interpreter manager using environment `env` which is not tied to an Interpreter Manager.
explicit Interpreter(std::shared_ptr<Environment> env)
: Interpreter(nullptr, env) {}

// Gets a new `InterpreterSession` from this Interpreter.
InterpreterSession acquireSession() const {
if (manager_) {
return InterpreterSession(pImpl_->acquireSession(), manager_);
} else {
return InterpreterSession(pImpl_->acquireSession());
}
}

~Interpreter();
Interpreter(Interpreter&& rhs) noexcept
: handle_(rhs.handle_),
Expand All @@ -113,17 +123,26 @@ class TORCH_API Interpreter {

struct Package;

// The default LoadBalancer for torch::deploy which handles allocating and freeing subinterpreters.
struct TORCH_API LoadBalancer {

// Creates a Loadbalancer which handles `n` interpreters.
explicit LoadBalancer(size_t n)
: uses_(new uint64_t[8 * n]), allocated_(n), n_(n) {
// 8*... to avoid false sharing of atomics on the same cache line
memset(uses_.get(), 0, 8 * n_ * sizeof(uint64_t));
}

// Changes the amount of subinterpreters which is handled by the load balancer.
void setResourceLimit(size_t n) {
MULTIPY_INTERNAL_ASSERT(n <= allocated_);
n_ = n;
}

// Allocates an subinterpreter, and return its ID which is used to free it.
int acquire();

// Frees the subinterpreter with ID `where`. This ID is returned by `LoadBalancer::acquire()`
void free(int where);

private:
Expand All @@ -134,13 +153,21 @@ struct TORCH_API LoadBalancer {
size_t n_;
};

// An `InterpreterManager` handles the interaction of multiple subinterpreters such as allocating
// subinterpreters, or load balancing the subinterpreters.
struct TORCH_API InterpreterManager {

// constructor for `InterpreterManager` which takes the number of interpreters
// (usually correlates to number of cores on your cpu), and a pointer to an `Environment`.
// The defualt uses the local python env.
explicit InterpreterManager(
size_t nInterp = 2,
std::shared_ptr<Environment> env = std::make_shared<NoopEnvironment>());

// get a free model, guarenteed that no other user of acquireOne has the same
// model. It _is_ possible that other users will be using the interpreter.
// get a free InterpreterSession, guarenteed that no other user of acquireOne has the same
// InterpreterSession. It _is_ possible that other users will be using the interpreter if there are
// no free InterpreterSessions. Unless you are very careful to only use free interpreters, then do not assume
// that the `Obj`s are isolated from each other.
InterpreterSession acquireOne() {
int where = resources_.acquire();
InterpreterSession I = instances_[where].acquireSession();
Expand All @@ -154,11 +181,18 @@ struct TORCH_API InterpreterManager {
at::ArrayRef<Interpreter> allInstances() {
return instances_;
}

// debugging tool to control the size of the loadBalancer
// and change the number of interpreters on the fly
void debugLimitInterpreters(size_t N) {
AT_ASSERT(N <= instances_.size());
resources_.setResourceLimit(N);
}

// loads a package from a file with name `uri`
Package loadPackage(const std::string& uri);

// loads a package from a `PyTorchStreamReader` or any class other which uses `ReadAdapterInterface`
Package loadPackage(
std::shared_ptr<caffe2::serialize::ReadAdapterInterface> reader);

Expand All @@ -171,10 +205,12 @@ struct TORCH_API InterpreterManager {
registeredModuleSource_[std::move(name)] = std::move(src);
}

// Util function for debugging.
// Util function for debugging which outputs the number of registered modules.
size_t countRegisteredModuleSources() {
return registeredModuleSource_.size();
}

// Converts `obj` from on `InterpreterSession` I into a `ReplicatedObj`.
ReplicatedObj createMovable(Obj obj, InterpreterSession* I);
InterpreterManager(const InterpreterManager&) = delete;
InterpreterManager& operator=(const InterpreterManager&) = delete;
Expand Down Expand Up @@ -204,33 +240,51 @@ struct TORCH_API ReplicatedObjImpl {
InterpreterManager* manager_;
};

// A python object which is Replicated from an `Obj` such that it is able to move around to different `InterpreterSessions`
// by using `InterpreterSession::fromMovable(ReplicatedObj)`
struct TORCH_API ReplicatedObj {

// Default constructor for `ReplicatedObj`
ReplicatedObj() : pImpl_(nullptr) {}

// Creates an `InterpreterSession` using `onThisInterpreter`. If `onThisInterpreter` is
// a `nullptr', then the associated `InterpreterManager` allocates it.
InterpreterSession acquireSession(
const Interpreter* onThisInterpreter = nullptr) const;
at::IValue operator()(at::ArrayRef<at::IValue> args) const {
auto I = acquireSession();
return I.self(args).toIValue();
}

// Calls an `ReplicatedObj` callable, with arguments given by the tuple args and named arguments given by the dictionary
// kwargs. This is done on an arbitrary `InterpreterSession` which belongs to the `ReplicatedObj`'s manager.
[[nodiscard]] at::IValue callKwargs(
std::vector<at::IValue> args,
std::unordered_map<std::string, c10::IValue> kwargs) const {
auto I = acquireSession();
return I.self.callKwargs(std::move(args), std::move(kwargs)).toIValue();
}

// Calls an `ReplicatedObj` callable, with named arguments given by the dictionary kwargs.
// This is done on an arbitrary `InterpreterSession` which belongs to the `ReplicatedObj`'s manager.
[[nodiscard]] at::IValue callKwargs(
std::unordered_map<std::string, c10::IValue> kwargs) const {
auto I = acquireSession();
return I.self.callKwargs(std::move(kwargs)).toIValue();
}

[[nodiscard]] bool hasattr(const char* name) const {
// Returns true if `ReplicatedObj` has attribute with name `attr` and false otherwise.
// This is done on an arbitrary `InterpreterSession` which belongs to the `ReplicatedObj`'s manager.
[[nodiscard]] bool hasattr(const char* attr) const {
auto I = acquireSession();
return I.self.hasattr(name);
return I.self.hasattr(attr);
}

// Deletes `ReplicatedObj` from onThisInterpreter, if onThisInterpreter is `nullptr`,
// unload is called on all interpreters belonging to the ReplicatedObject's InterpreterManager
void unload(const Interpreter* onThisInterpreter = nullptr);

// Converts `ReplicatedObj` to `Obj` on `InterpreterSession` `I`
Obj toObj(InterpreterSession* I);

private:
Expand All @@ -242,21 +296,24 @@ struct TORCH_API ReplicatedObj {
friend struct InterpreterManager;
};

// PythonMethodWrapper is a more specific instance of a
// ReplicatedObj which represents a python method, and
// is therefore callable and has argument names accessible.
class PythonMethodWrapper : public torch::IMethod {
// PythonMethodWrapper is a more specific instance of a
// ReplicatedObj which represents a python method, and
// is therefore callable and has argument names accessible.
public:
// TODO(whc) make bound method pickleable, then directly construct from that

PythonMethodWrapper(
torch::deploy::ReplicatedObj model,
std::string methodName)
: model_(std::move(model)), methodName_(std::move(methodName)) {}

// return the name of the python method.
const std::string& name() const override {
return methodName_;
}

// overrides the `()` operater to call the underlying python method.
c10::IValue operator()(
std::vector<c10::IValue> args,
const IValueMap& kwargs = IValueMap()) const override {
Expand All @@ -274,6 +331,7 @@ class PythonMethodWrapper : public torch::IMethod {
std::string methodName_;
};

// An object to encapsulate a `torch::package` which can act as part (or entire) environment for subinterpreters.
struct TORCH_API Package {
// shorthand for getting the object as a pickle resource in the package
ReplicatedObj loadPickle(const std::string& module, const std::string& file) {
Expand Down Expand Up @@ -308,12 +366,15 @@ struct TORCH_API Package {
}
#endif

// Allocates an `InterpreterSession` and load the appropriate torch.package with it.
InterpreterSession acquireSession() {
auto I = manager_->acquireOne();
I.self =
I.impl_->createOrGetPackageImporterFromContainerFile(containerFile_);
return I;
}

// Converts an `Obj` from `InterpreterSession` `I` into a `ReplicatedObj`.
ReplicatedObj createMovable(Obj obj, InterpreterSession* I) {
return manager_->createMovable(obj, I);
}
Expand Down
8 changes: 7 additions & 1 deletion multipy/runtime/elf_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
namespace torch {
namespace deploy {

// A representation of a section of an ElfFile.
struct Section {
Section() {}
explicit Section(
Expand All @@ -35,13 +36,18 @@ struct Section {
}
};

// TODO: consolidate other ELF file related functions in loader.cpp to this file

/*
* This class provie utilities to handle ELF file. Only support 64bit ELF file.
*/
// TODO: consolidate other ELF file related functions in loader.cpp to this file
class ElfFile {
public:

// Constructs an Elffile with the corresponding `filename`
explicit ElfFile(const char* filename);

// Finds and return a `Section` with the corresponding `name`. If nothing is found, then a `multipy::nullopt` is returned.
multipy::optional<Section> findSection(const char* name) const;

private:
Expand Down
3 changes: 3 additions & 0 deletions multipy/runtime/embedded_file.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@
namespace torch {
namespace deploy {

// Represents an ExeSection of an EmbeddedFile.
struct ExeSection {
const char* sectionName;
bool customLoader;
};

// These are symbols used by the subinterpreters.
struct InterpreterSymbol {
const char* startSym;
const char* endSym;
bool customLoader;
};

// Represents an EmbeddedFile which is a file which contains a binary for a subinterprerter.
struct EmbeddedFile {
std::string libraryName{""};
bool customLoader{false};
Expand Down
6 changes: 6 additions & 0 deletions multipy/runtime/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class Environment {
fclose(zippedFile);
return zipArchive;
}

void setupZippedPythonModules(const std::string& pythonAppDir) {
#ifdef FBCODE_CAFFE2
extraPythonPaths_.push_back(getZippedArchive(
Expand All @@ -56,14 +57,19 @@ class Environment {
}

public:
// Environment constructor which creates a random temporary directory as
// a directory for the zipped python modules.
explicit Environment() {
char tempDirName[] = "/tmp/torch_deploy_zipXXXXXX";
char* tempDirectory = mkdtemp(tempDirName);
setupZippedPythonModules(tempDirectory);
}
// Environment constructor which takes a file name for the
// directory for the zipped python modules.
explicit Environment(const std::string& pythonAppDir) {
setupZippedPythonModules(pythonAppDir);
}
// Deconstructor for Environment.
virtual ~Environment() {
auto rmCmd = "rm -rf " + extraPythonLibrariesDir_;
(void)system(rmCmd.c_str());
Expand Down
14 changes: 14 additions & 0 deletions multipy/runtime/interpreter/interpreter_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace deploy {
struct InterpreterSessionImpl;
struct Obj;

// Representation a Pickled Object
struct PickledObject {
std::string data_;
std::vector<at::Storage> storages_;
Expand All @@ -28,6 +29,7 @@ struct PickledObject {
std::shared_ptr<caffe2::serialize::PyTorchStreamReader> containerFile_;
};

// The underlying implementation of `Obj` which holds the underlying `py::object`.
struct InterpreterObj {
friend struct Obj;
friend struct ReplicatedObjImpl;
Expand Down Expand Up @@ -72,21 +74,32 @@ struct Obj {
: isDefault_(false), baseObj_(baseObj) {}
Obj() : isDefault_(true), baseObj_(nullptr) {}

// return `IValue` representation.
at::IValue toIValue() const;

// Call an `Obj` callable, with arguments given by the tuple args.
Obj operator()(at::ArrayRef<Obj> args);

// Call an `Obj` callable, with arguments given by the tuple args.
Obj operator()(at::ArrayRef<at::IValue> args);

// Call an `Obj` callable, with arguments given by the tuple args, and named arguments given by the dictionary kwargs.
Obj callKwargs(
std::vector<at::IValue> args,
std::unordered_map<std::string, c10::IValue> kwargs);
// Call an `Obj` callable, with named arguments given by the dictionary kwargs.
Obj callKwargs(std::unordered_map<std::string, c10::IValue> kwargs);
// Returns true if `Obj` has attribute with name `attr` and false otherwise.
bool hasattr(const char* attr);
// Returns attribute `attr` from `Obj`. This is equivalent to calling `getattr(Obj, attr)` in python.
Obj attr(const char* attr);

private:
bool isDefault_;
std::shared_ptr<InterpreterObj> baseObj_;
};

// The underlying implementation of `InterpreterSession`
struct InterpreterSessionImpl {
friend struct Package;
friend struct ReplicatedObj;
Expand Down Expand Up @@ -132,6 +145,7 @@ struct InterpreterSessionImpl {
}
};

// The underlying implementation of `Interpreter`
struct InterpreterImpl {
virtual InterpreterSessionImpl* acquireSession() = 0;
virtual void setFindModule(
Expand Down
Loading

0 comments on commit 277424c

Please sign in to comment.