**See also**: [strawman consolidated dependency 
list](https://github.com/apache/incubator-tvm/pull/6620/files#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711)

- NOTE: the strawman was made a few weeks ago and may be out of date. It will 
be manually rebuilt before merging.

**Author's note**: dependency management tools, like text editors, are often 
the subject of holy wars. This RFC seeks only to improve our dependency 
management in TVM—we can consider using any dependency management tool that 
fits our requirements. For the purposes of maintaining a constructive 
conversation here, let's focus debates between dependency management tools 
around their impacts on the TVM project as a whole, not any one developer's 
individual workflow.

## Background

TVM has historically attempted to avoid overly specifying Python package 
requirements in order to be as lightweight and flexible as its applications 
allow. However, this practice has led to a scattering of Python dependencies 
around the codebase, such that it's now quite difficult for the average TVM 
developer to create a virtualenv for local development that comes close to 
matching that used in the regression.

This RFC proposes that we move all Python package requirements into a single 
location, then source or otherwise generate files where needed. Further, it 
proposes that we checkin the output of `pip freeze` from each CI container, so 
that it's easy to lookup the actual package versions TVM tests against.

## Challenges

### C0: Requirement Groups

TVM's Python code is organized into parts:

- A core portion, required to use TVM at all
- A set of Relay importers, which depend on a variety of third-party Python 
packages necessary to parse formats foreign to TVM
- A set of optional components, such as microTVM, which may depend on 
third-party libraries specific to one use case of TVM

TVM's Python package requirements depend on which parts of TVM you want to use. 
Further, TVM developers have an additional set of requirements (pylint, pytest, 
etc) that should not be included as dependencies of any built TVM package.

### C1: Varying Constraints on Dependency Versions

Python dependencies can also be separately categorized into these 3 categories, 
which bear no relation to the groups from C0.

- Loose dependencies — where any non-ancient version will likely do
- Range dependencies — where some version preference (i.e. in major/minor 
version) exists, but generally any package in that range will do. Sometimes 
users may want to purposefully install a package outside the range i.e. to 
break one feature of TVM but enable another for a particular model. 
Dependencies that follow [semantic versioning](https://semver.org/) are likely 
candidates for this group.
- Exact dependencies — where using any other version than that used in CI is 
unworkable

A survey of TVM's present dependencies is included in the strawman 
[`pyproject.toml`](https://github.com/apache/incubator-tvm/pull/6620/files#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711)
 from the PoC PR.

### C2: Python Package Requirements

The de facto standard Python package manager, `pip`, resolves and installs 
package dependencies by default (i.e. unless `--no-deps` is given). However, 
`pip install` produces a set of installed packages that is neither 
deterministic (even given a frozen index) nor 
[consistent](https://github.com/pypa/pip/issues/988). Specifically, if a user 
executes `pip install a b`, they may see a different set of installed packages 
than if they execute `pip install b a`. 

Tools such as `pipenv` and `poetry` have been written to work around this.

### C3: Updating CI containers

Generally speaking, the policy of tvm is to avoid restricting dependency 
versions when possible. This allows the TVM CI to remain up-to-date with 
respect to its dependencies as the CI is updated. However, users of TVM would 
ideally like to install TVM alongside the same set of Python dependencies used 
in the regression—this gives a predictable user experience. 

Currently, Python packages are installed to each container using a series of 
`pip install` commands, and CI container updates are made individually (i.e. 
`ci-cpu` is updated independently of `ci-gpu`). This means that it's entirely 
possible and expected that we test TVM against different but unpredictable 
versions of its dependencies.

## Topics for this RFC

In general, our Python dependencies are scattered around the codebase and I'd 
like to argue that any solution going forward should at least centralize these. 
The topics I'd like to debate with this RFC are:

T0. In what format should we store dependencies?

T1. Which dependencies should be listed in `setup.py`?

T2. What changes, if any, do we need to make to the CI process in order to 
produce a tested list of Python dependencies?

T3. What pathway should we provide to the user to install the dependencies they 
need to use TVM?

T4. What pathway should we provide to the developer to install dependencies for 
developing TVM?

## Approaches

### A0. De-centralized dependency tracking (current approach)

Currently, dependencies are tracked in a decentralized fashion:

- `[setup.py](http://setup.py)` reports the bare minimum dependencies required 
to use TVM. Some extras are provided, but there is no coordination between the 
versions specified in `setup.py` and the version used in CI test containers 
(i.e. `pip install -e python` is not executed in the CI test container).
- CI containers are built with Python package version restrictions specified in 
the install script. Where versions are not restricted, no checking is performed 
to ensure that package versions are compatible (i.e. only `pip install` is 
used, not pipenv, poetry, or another tool that checks the integrity of the 
version graph).
- To run developer tools such as `pylint`, the suggested approach is to use the 
`ci-lint` container with a local docker container.
- To build docs, developers install dependencies specified in `docs/README.md`. 
Developers can ensure they have the correct dependencies by comparing against 
`pip freeze` from `ci-gpu` (NOTE however that `ci-gpu` requires a gpu instance 
to run locally).

There are some benefits to this approach:

1. It is simple to perform each step of the process separately
2. Tests for the most part run against recent Python deps as containers are 
updated regularly, and non-pinned Python packages update automatically with 
each container rebuild.
3. Since containers run slightly different versions of Python packages, some 
diversity is present in the set of Python packages TVM is tested against.

However, there are drawbacks:

1. It's very difficult for a developer to tell exactly which versions of 
dependencies TVM is tested against, short of pulling a multi-gigabyte docker 
image and running `pip freeze`.
2. When building documentation, it's actually impossible to deduce this unless 
the developer happens to have a machine that can run `nvidia-docker`. The 
`ci-gpu` container, which is used to build docs, can't be started without 
binding to a GPU.
3. Although there is diversity in the dependencies tested, we have no control 
over this and limited visibility into it.
4. End users installing TVM (i.e. from `pip install tlcpack` or from `pip 
install -e ./python`) can't expect it to depend on the specific tested versions 
of Python packages. While loose dependency pinning is standard practice in the 
Python community, having the ability to pin to a known-good configuration can 
be helpful. Further, there isn't even a "simple command" a user could run—they 
need to download `ci-cpu` and `ci-gpu` and cherry-pick package versions from 
`pip freeze`.
5. There is a tool to run containers for local developer use, but it doesn't 
work well with `git-subtree` and requires developers to lookup the relevant 
container versions in `Jenkinsfile`. It's unwieldy. When using `git-subtree`, 
the only way to run the linter locally is to checkout your development branch 
in the original git repo.

### A1. Centralized Management with a set of `requirements.txt`

Create a set of `requirements.txt` files that contain the reference versions of 
TVM packages. More than 1 `requirements.txt` file is necessary because the set 
of dependencies needed to use TVM varies with your use case, and we wish to 
maintain flexibility. For instance, to use the `pytorch` importer, `torch` is 
needed; but we don't wish to require users to install that for basic TVM usage 
or when using TVM purely for running inference. Therefore, a new file 
`requirements-torch.txt` would be generated for this case, and would correspond 
to a `torch` 
[extras_require](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#optional-dependencies)
 entry in `setup.py`.

Additionally, a `requirements-dev.txt` would be created to capture developer 
requirements such as `pylint` and the docs-building requirements.

When building CI containers, care needs to be taken to install Python packages 
only from `requirements.txt`files in the repo. Either all Python packages need 
to be installed in one `pip install` command, or a shell script helper should 
be written to verify that the packages requested are present in 
`requirements.txt`. When installation is finished, `docker/build.sh` should 
`pip freeze` and write the output to 
`docker/python-versions/v0.62-ci-cpu-constraints.txt`.

Finally, `setup.py` must read the `requirements.txt` files and fill 
`install_requires` and `extras_require` from those files.

Pros:

- It is easy to determine the set of TVM dependencies and the actual package 
versions used in test.
- `requirements.txt` is a universally-consumable format so no additional 
tooling is imposed on TVM developers or users.
- `setup.py` will agree with `requirements.txt` as pip wheels are built.

Cons:

- CI containers may continue to diverge from one another in terms of dependency 
management.
- The set of installed Python packages could still differ depending on the 
order of `pip install -r requirements.txt` and e.g. `pip install -r 
requirements-torch.txt`. Developers may not remember the order in which these 
commands were invoked or the full history of their local virtualenv, so bug 
reports could arise from dependency problems that are hard to document and 
reproduce.
- The set of installed Python packages could still not be consistent—a Python 
package mentioned later in a `requirements.txt` may install a dependency 
incompatible with a previously-mentioned Python package.
- Developer usage is still somewhat tricky — multiple `pip install -r` commands 
are needed
- When syncing, developers need to remember to rebuild their virtualenv

### A2. Consistent centralized dependencies with a tool such as `poetry` or 
`pipenv`

This approach is similar to A1, but instead of creating a set of 
`requirements.txt` files, a more advanced dependency management tools such as 
`poetry` or `pipenv` is used. These tools tend to favor a centralized file—for 
instance, poetry stores dependencies in `pyproject.toml` at the root of the TVM 
repo. The set of `requirements.txt` files could still be auto-generated for 
developers who prefer that approach, and a unit test could verify they are in 
sync with the authoritative `pyproject.toml`.

Pros:

- It is easy to determine the set of TVM dependencies and the actual package 
versions used in test.
- Local developer virtualenv management is automated
- The set of installed packages is always consistent
- Version specification is a little bit better in poetry with operators 
dedicated to semantic versioning (i.e. `^0.4` means anything `>=0.4.0` and 
`<0.5`)
- `setup.py` will agree with `pyproject.toml` as pip wheels are built.

Cons:

- Additional tooling is needed for the optimal developer experience
- Holy wars abound with respect to developer tooling, though this could be 
mitigated by tools such as [`dephell`](https://github.com/dephell/dephell).
- `setup.py` would need to parse `pyproject.toml` and so could be more complex. 
It would also need to map semantic versions to pip-compatible versions 
(translating `^0.4` to pip constraints `>=0.4, <0.5` is straightforward, but 
`setup.py` may wish to loosen the version constraints on some dependencies).
- Nothing in this approach fixes the problem of building CI containers with 
different dependency sets; however, it does provide a way forward here (see 
Consistent CI containers below).
- The container dependency snapshot needs to be manually checked-in under 
`docker/python-versions`

## Consistent CI Containers

The topic of ensuring CI containers run on the same Python package versions is 
for another RFC. But, approach A2 enables a fairly straightforward, if notably 
more complex, flow which I'll sketch here:

1. When it is time to update the CI containers due to a change in Python 
package version, a script launches the base container (i.e. `ubuntu:18.04`), 
installs `poetry`, and runs `poetry lock` . The output is written to 
`docker/python-versions/poetry.lock-v1.01`.
2. When a new container is built, the corresponding `poetry.lock-v1.01` file is 
copied from `docker/python-versions` to the root of the repository. All `pip 
install` commands are replaced with `poetry install`, and no further change is 
needed because the `poetry.lock` file specifies the exact version to install 
plus any dependencies needed (and their exact versions).
3. When one CI container is updated, all of them are updated and all containers 
with the same version number share the same `poetry.lock`-file. This is why 
I've bumped the container major version to `1` in this example.
4. The new set of containers is tested against a PR that submits the new 
`poetry.lock` file and bumps the global container version number in 
`Jenkinsfile`. Additionally, a `docker/python-versions/poetry.lock-latest` file 
could be included to view diffs against the previous lock-file in code review.

A more thorough testing flow should be specified at the time this is baked into 
an RFC. Additional challenges that would need to be addressed in a hypothetical 
RFC are support for executing Python code for non-linux OS (currently the CI 
does not do this, but we should not add any impediments to this).

## Discussion

Here are some points for discussion. There are probably things I haven't 
considered with this RFC, let's discuss them as well.

C0. Which approach should we take to this problem?

C1. Do you care if TVM adds an extra tool such as `poetry` as the preferred way 
to manage your local development virtualenv? Do you suggest a different tool 
(please do so based on the merits of such tool, not simply that it's the one 
you use)?

C2. How important is it to standardize the CI container build process? Should 
we further consider a standardized CI container build pipeline?

C3.  Is loose dependency specification in the `setup.py` the right thing to aim 
for? At what level should we specify dependency versions there?





---
[Visit 
Topic](https://discuss.tvm.apache.org/t/rfc-consolidating-tvm-python-dependencies/8329/1)
 to respond.

You are receiving this because you enabled mailing list mode.

To unsubscribe from these emails, [click 
here](https://discuss.tvm.apache.org/email/unsubscribe/5d3de2c9299dfb207d4ca6586e3d9fc504b5c257816a4f8265810e2a395d630e).

Reply via email to