Skip to content
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

[examples] Update and improve the example service implementations and documentation #467

Merged
merged 9 commits into from
Oct 14, 2021
2 changes: 1 addition & 1 deletion INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ On macOS the required dependencies can be installed using
[homebrew](https://docs.brew.sh/Installation):

```sh
brew install bazelisk buildifier hadolint prototool zlib
brew install bazelisk buildifier clang-format hadolint prototool zlib
export LDFLAGS="-L/usr/local/opt/zlib/lib"
export CPPFLAGS="-I/usr/local/opt/zlib/include"
export PKG_CONFIG_PATH="/usr/local/opt/zlib/lib/pkgconfig"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,16 @@ template <typename CompilationSessionType>

// Set up the working and logging directories.
boost::filesystem::path workingDirectory{FLAGS_working_dir};
bool createdWorkingDir = false;
if (FLAGS_working_dir.empty()) {
// If no working directory was set, create one.
workingDirectory = boost::filesystem::unique_path(boost::filesystem::temp_directory_path() /
"compiler_gym-service-%%%%-%%%%");
boost::filesystem::create_directories(workingDirectory / "logs");
FLAGS_working_dir = workingDirectory.string();
createdWorkingDir = true;
}

// Create amd set the logging directory.
boost::filesystem::create_directories(workingDirectory / "logs");
FLAGS_log_dir = workingDirectory.string() + "/logs";
if (!createdWorkingDir && !boost::filesystem::is_directory(FLAGS_log_dir)) {
std::cerr << "ERROR: logging directory '" << FLAGS_log_dir << "' not found";
exit(1);
}

google::InitGoogleLogging(argv[0]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ def main(argv):
compiler_gym_service_pb2_grpc.add_CompilerGymServiceServicer_to_server(
service, server
)
port = server.add_insecure_port("0.0.0.0:0")

address = f"0.0.0.0:{FLAGS.port}" if FLAGS.port else "0.0.0.0:0"
port = server.add_insecure_port(address)

with atomic_file_write(working_dir / "port.txt", fileobj=True, mode="w") as f:
f.write(str(port))
Expand Down
20 changes: 19 additions & 1 deletion examples/example_compiler_gym_service/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
load("@rules_python//python:defs.bzl", "py_library", "py_test")
load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")

# Here we bundle up the new compiler service and data dependencies as a python
# library. To use these example services you can depend on this target. See
# below for examples.
py_library(
name = "example_compiler_gym_service",
srcs = ["__init__.py"],
Expand All @@ -13,10 +16,25 @@ py_library(
],
visibility = ["//visibility:public"],
deps = [
"//compiler_gym/datasets",
"//compiler_gym/spaces",
"//compiler_gym/util",
],
)

# This is a scrip that demonstrates how to use the example services. To run it,
# use: $ bazel run -c opt //examples/example_compiler_gym_service:demo
py_binary(
name = "demo",
srcs = ["demo.py"],
deps = [
":example_compiler_gym_service",
"//compiler_gym",
],
)

# This is a set of unit tests for the example services. To run the tests, use:
# $ bazel test //examples/example_compiler_gym_service:env_tests
py_test(
name = "env_tests",
srcs = ["env_tests.py"],
Expand Down
66 changes: 40 additions & 26 deletions examples/example_compiler_gym_service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,60 @@ RPC interface that the frontend interacts with.

This directory contains an example backend service implemented in C++ and
Python. Both implementations have the same features. They don't do any actual
compilation, but can be used as a starting point for writing new services, or
for debugging and testing frontend code.
compilation, but can be used as a starting point for adding support for new
compilers, or for debugging and testing frontend code.

Features:
If you have any questions please [file an
issue](https://github.com/facebookresearch/CompilerGym/issues/new/choose).

* Enforces the service contract, e.g. `StartSession()` must be called before
`EndSession()`, list indices must be in-bounds, etc.
* Implements all of the RPC endpoints.

## Features

* A static action space with three items: `["a", "b", "c"]`. The action space
never changes. Actions never end the episode.
* There are two observation spaces:
* `ir` which returns the string "Hello, world!".
* `features` which returns an `int64_list` of `[0, 0, 0]`.
* A single reward space `runtime` which returns 0.
* It has a single dataset "benchmark://example-v0" with two programs "foo" and
"bar".
* It has a static action space with three items: `["a", "b", "c"]`. The action
space never changes. Actions never end the episode.
* There are two observation spaces:
* `ir` which returns the string "Hello, world!".
* `features` which returns an `int64_list` of `[0, 0, 0]`.
* There is a single reward space `runtime` which returns 0.
* Supports default observation and reward spaces.

See [service_cc/ExampleService.h](service_cc/ExampleService.h) for the C++
service implementation,
[service_py/example_service.py](service_py/example_service.py) for the Python
version, [__init__.py](__init__.py) for a python module that registers this
service with the gym on import, and [env_tests.py](env_tests.py) for tests.

## Implementation

There are two identical service implementations, one in Python, one in C++. See
[service_cc/ExampleService.h](service_cc/ExampleService.h) for the C++ service,
and [service_py/example_service.py](service_py/example_service.py) for the
Python service. The module [__init__.py](__init__.py) defines the reward space,
dataset, and registers two new environments using these services.

The file [demo.py](demo.py) demonstrates how to use these example environments
using CompilerGym's bazel build system. The file [env_tests.py](env_tests.py)
contains unit tests for the example services. Because the Python and C++
services implement the same interface, the same tests are run against both
environments.

## Usage

Start an example service using:
Run the demo script using:

```sh
$ bazel run -c opt //examples/example_compiler_gym_service/service -- \
--port=8080 --working_dir=/tmp
$ bazel run -c opt //examples/example_compiler_gym_service:demo
```

The service never terminates and does not print logging messages. Interact with
the RPC endpoints using your frontend of choice, for example, the manual
environment:
Run the unit tests using:

```sh
$ bazel run -c opt //compiler_gym/bin:manual_env -- --service=localhost:8080
$ bazel test //examples/example_compiler_gym_service/...
```

Kill the service using C-c when you are done.
### Using the python service without bazel

Because the python service contains no compiled code, it can be run directly as
a standalone script without using the bazel build system. From the root of the
CompilerGym repository, run:

```sh
$ cd examples
$ python3 example_compiler_gym_service/demo_without_bazel.py
```
2 changes: 1 addition & 1 deletion examples/example_compiler_gym_service/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
"""This module demonstrates how to """
"""This module defines and registers the example gym environments."""
from pathlib import Path
from typing import Iterable

Expand Down
35 changes: 35 additions & 0 deletions examples/example_compiler_gym_service/demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
"""This script demonstrates how the example services defined in this directory
can be used as gym environments. Usage:

$ bazel run -c opt //examples/example_compiler_gym_service:demo
"""
import logging

import gym

# To use the example services we simply need to import the module which
# registers the environments.
import examples.example_compiler_gym_service # noqa Register environments


def main():
# Use debug verbosity to print out extra logging information.
logging.basicConfig(level=logging.DEBUG)

# Create the environment using the regular gym.make(...) interface. We could
# use either the C++ service "example-cc-v0" or the Python service
# "example-py-v0".
with gym.make("example-cc-v0") as env:
env.reset()
for _ in range(20):
observation, reward, done, info = env.step(env.action_space.sample())
if done:
env.reset()


if __name__ == "__main__":
main()
114 changes: 114 additions & 0 deletions examples/example_compiler_gym_service/demo_without_bazel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
"""This script demonstrates how the Python example service without needing
to use the bazel build system. Usage:

$ python example_compiler_gym_service/demo_without_bazel.py

It is equivalent in behavior to the demo.py script in this directory.
"""
import logging
from pathlib import Path
from typing import Iterable

import gym

from compiler_gym.datasets import Benchmark, Dataset
from compiler_gym.spaces import Reward
from compiler_gym.util.registration import register
from compiler_gym.util.runfiles_path import site_data_path

EXAMPLE_PY_SERVICE_BINARY: Path = Path(
"example_compiler_gym_service/service_py/example_service.py"
)
assert EXAMPLE_PY_SERVICE_BINARY.is_file(), "Service script not found"


class RuntimeReward(Reward):
"""An example reward that uses changes in the "runtime" observation value
to compute incremental reward.
"""

def __init__(self):
super().__init__(
id="runtime",
observation_spaces=["runtime"],
default_value=0,
default_negates_returns=True,
deterministic=False,
platform_dependent=True,
)
self.previous_runtime = None

def reset(self, benchmark: str, observation_view):
del benchmark # unused
self.previous_runtime = None

def update(self, action, observations, observation_view):
del action
del observation_view

if self.previous_runtime is None:
self.previous_runtime = observations[0]

reward = float(self.previous_runtime - observations[0])
self.previous_runtime = observations[0]
return reward


class ExampleDataset(Dataset):
def __init__(self, *args, **kwargs):
super().__init__(
name="benchmark://example-v0",
license="MIT",
description="An example dataset",
site_data_base=site_data_path("example_dataset"),
)
self._benchmarks = {
"benchmark://example-v0/foo": Benchmark.from_file_contents(
"benchmark://example-v0/foo", "Ir data".encode("utf-8")
),
"benchmark://example-v0/bar": Benchmark.from_file_contents(
"benchmark://example-v0/bar", "Ir data".encode("utf-8")
),
}

def benchmark_uris(self) -> Iterable[str]:
yield from self._benchmarks.keys()

def benchmark(self, uri: str) -> Benchmark:
if uri in self._benchmarks:
return self._benchmarks[uri]
else:
raise LookupError("Unknown program name")


# Register the environment for use with gym.make(...).
register(
id="example-v0",
entry_point="compiler_gym.envs:CompilerEnv",
kwargs={
"service": EXAMPLE_PY_SERVICE_BINARY,
"rewards": [RuntimeReward()],
"datasets": [ExampleDataset()],
},
)


def main():
# Use debug verbosity to print out extra logging information.
logging.basicConfig(level=logging.DEBUG)

# Create the environment using the regular gym.make(...) interface.
with gym.make("example-v0") as env:
env.reset()
for _ in range(20):
observation, reward, done, info = env.step(env.action_space.sample())
if done:
env.reset()


if __name__ == "__main__":
main()
10 changes: 10 additions & 0 deletions examples/example_compiler_gym_service/demo_without_bazel_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
"""Smoke test for examples/example_compiler_gym_service/demo_without_bazel.py"""
from example_compiler_gym_service.demo_without_bazel import main


def test_demo_without_bazel():
main()
Loading