# Tips for using cmdstanpy as a dependency in a Python package

**URL:** <https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129>\
**Category:** Interfaces\
**Created:** [August 27, 2021, 2:29pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129 "2021-08-27T14:29:09Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [August 27, 2021, 2:29pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/1 "2021-08-27T14:29:09Z")

</div>

Hi! Keen to get thoughts on what the best approach is for having `cmdstanpy` as a dependency in a Python package. For the prophet package we currently have `cmdstanpy` as an optional backend (see [GitHub - facebook/prophet: Tool for producing high quality forecasts for time series data that has](https://github.com/facebook/prophet#experimental-backend---cmdstanpy)) and end-users need to install cmdstan themselves before installing prophet. Is there a recommended way to make the installation just one step? Since prophet requires a build step anyways, I was thinking we could put `install_cmdstan()` somewhere inside setup.py.

And a follow-up question, is there any guidance on how we might include a stan model in a package without requiring the end-user to build the package? Installs can take a while (building cmdstan + compiling the stan model included in the package) and can fail for different reasons on different machines, so I’m wondering if there’s a way to package everything into a wheel. I guess I’m looking for an equivalent of how RStan does this, as described here [Guidelines for developers of R packages interfacing with Stan • rstantools](https://mc-stan.org/rstantools/articles/developer-guidelines.html)

---

<div class="post-metadata">

**Author:** ![mitzimorris](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/mitzimorris/32/29_2.png) [@mitzimorris](https://discourse.mc-stan.org/u/mitzimorris)\
**Post date:** [August 27, 2021, 4:49pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/2 "2021-08-27T16:49:01Z")

</div>

latest CmdStanPy now has conda installer that also will install CmdStan - [Installation — CmdStanPy 1.2.0 documentation](https://mc-stan.org/cmdstanpy/installation.html#conda-users-recommended)

> include a stan model in a package without requiring the end-user to build the package?

is this something you could do with Conda?

---

<div class="post-metadata">

**Author:** ![ahartikainen](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/ahartikainen/32/321_2.png) [@ahartikainen](https://discourse.mc-stan.org/u/ahartikainen)\
**Post date:** [August 27, 2021, 5:47pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/3 "2021-08-27T17:47:00Z")

</div>

What if the models are compiled and needed files are packed together with the wheel?

→ execution files + tbb

---

<div class="post-metadata">

**Author:** ![WardBrian](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/wardbrian/32/12078_2.png) [@WardBrian](https://discourse.mc-stan.org/u/WardBrian)\
**Post date:** [August 27, 2021, 6:29pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/4 "2021-08-27T18:29:41Z")

</div>

It seems like [prophet is on conda-forge](https://github.com/conda-forge/prophet-feedstock) already, so this should be very easy to do. (Edit: to be 100% clear, I mean including cmdstan[py] as a dependency should be very easy. I haven’t tried to see if conda could ship a compiled stan model, but maybe!)

To support both this and non-conda setups, I think that checking cmdstanpy.cmdstan\_path() would work. This should throw if cmdstan isn’t found, and then you can call install\_cmdstan internally.

---

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [August 28, 2021, 6:42pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/5 "2021-08-28T18:42:28Z")

</div>

Thanks for your responses!! @ahartikainen yep I ended up trying to package everything together (actually looking through past Issues I think you suggested the same thing a while back!) just using setup.py and a packaging tool.

I included the built cmdstan package in the wheel as well, but that bloated it by quite a bit (the wheel ended up being ~170MB). I think that’s the only way to ensure that the model .exe is run using the same cmdstan version used to compile it. It did work very nicely out of the box, but I’m not quite sure if if that’s worth the large wheel size.

Thanks again for the help :)

---

<div class="post-metadata">

**Author:** ![ahartikainen](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/ahartikainen/32/321_2.png) [@ahartikainen](https://discourse.mc-stan.org/u/ahartikainen)\
**Post date:** [August 28, 2021, 6:46pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/6 "2021-08-28T18:46:18Z")

</div>

I don’t think cmdstan is needed after compilation. Only tbb.

---

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [August 28, 2021, 7:01pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/7 "2021-08-28T19:01:00Z")

</div>

Ahhh, do you mean that we only need to keep some of the files in the cmdstan-2.26.1 folder? Sorry I’m not sure which files are the tbb files.

---

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [August 28, 2021, 7:37pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/8 "2021-08-28T19:37:31Z")

</div>

Oh sorry I think I see what you’re saying – we don’t actually need to keep the cmdstan files at all? e.g. in this notebook you made: [Precompile\_cmdstan/02\_sample\_without\_cmdstan.ipynb at master · ahartikainen/Precompile\_cmdstan · GitHub](https://github.com/ahartikainen/Precompile_cmdstan/blob/master/02_sample_without_cmdstan.ipynb) we could still instantiate and fit the Model object even without a link to a cmdstan installation. Curious - is this a bug or a feature? haha

---

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [August 29, 2021, 5:45am UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/9 "2021-08-29T05:45:48Z")

</div>

I ended up pruning the `stan/` folder and the downloaded binaries `macos-stanc`, `linux-stanc`, `windows-stanc`, etc. in order to get the package size way down, and everything still seems to work. Thanks for the tip @ahartikainen !

I think my original questions have been largely answered so thanks everyone :) In case anyone reading this is interested, here is the work-in-progress PR we have on Prophet at the moment: [[Draft] Python Wheels for PyPi by tcuongd · Pull Request #2010 · facebook/prophet · GitHub](https://github.com/facebook/prophet/pull/2010) . There are still some issues with Linux we need to figure out and other optimisations we can do.

---

<div class="post-metadata">

**Author:** ![ahartikainen](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/ahartikainen/32/321_2.png) [@ahartikainen](https://discourse.mc-stan.org/u/ahartikainen)\
**Post date:** [August 29, 2021, 7:29am UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/10 "2021-08-29T07:29:40Z")

</div>

> [@cuongd](#):
>
> fit the Model object even without a link to a cmdstan installation. Curious - is this a bug or a feature? haha

After Stan model is compiled to an executable, then it doesn’t need anything from cmdstan, it is a standalone. The only thing the executable needs, is tbb executable, which needs to be on the $PATH. This executable can be found from cmdstan folder, but atleast on Windows you could just copy it into the same folder your executables are it would still work. On macos and linux I think you need to do some hack for the executable so that it works correctly.

---

<div class="post-metadata">

**Author:** ![mitzimorris](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/mitzimorris/32/29_2.png) [@mitzimorris](https://discourse.mc-stan.org/u/mitzimorris)\
**Post date:** [August 29, 2021, 3:47pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/11 "2021-08-29T15:47:46Z")

</div>

we should think about the tooling required for development mode vs. the tooling required for production mode - there’s a brief discussion in the docs ([https://mc-stan.org/cmdstanpy/workflow.html](https://mc-stan.org/cmdstanpy/workflow.html)):

> The statistical modeling enterprise has two principal modalities: development and production. The focus of development is model building, comparison, and validation. Many models are written and fitted to many kinds of data. The focus of production is using a trusted model on real-world data to obtain estimates for decision-making. In both modalities, the essential workflow remains the same: compile a Stan model, assemble input data, do inference on the model conditioned on the data, and validate, access, and export the results.

it sounds like the tooling for production mode would be just the model executable, the tbb lib, CmdStan utilities `stansummary` and `diagnose`, plus CmdStanPy. (probably can skip the CmdStan uilities).

---

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [September 5, 2021, 3:00pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/12 "2021-09-05T15:00:40Z")

</div>

Thanks again @ahartikainen and @mitzimorris for the clarity!! Our code for the packaging step currently looks like this (not reviewed yet):

```python
CMDSTAN_VERSION = "2.26.1"
BINARIES_DIR = "bin"
BINARIES = ["diagnose", "print", "stanc", "stansummary"]
TBB_PARENT = "stan/lib/stan_math/lib"
TBB_DIRS = ["tbb", "tbb_2019_U8"]

def prune_cmdstan(cmdstan_dir: str) -> None:
    original_dir = Path(cmdstan_dir).resolve()
    parent_dir = original_dir.parent
    temp_dir = parent_dir / "temp"
    if temp_dir.is_dir():
        rmtree(temp_dir)
    temp_dir.mkdir()

    copytree(original_dir / BINARIES_DIR, temp_dir / BINARIES_DIR)
    for f in (temp_dir / BINARIES_DIR).iterdir():
        if f.is_dir():
            rmtree(f)
        elif f.is_file() and f.stem not in BINARIES:
            os.remove(f)
    for tbb_dir in TBB_DIRS:
        copytree(original_dir / TBB_PARENT / tbb_dir, temp_dir / TBB_PARENT / tbb_dir)

    rmtree(original_dir)
    temp_dir.rename(original_dir)

def build_cmdstan_model(target_dir):
    import cmdstanpy

    cmdstan_cache = get_cmdstan_cache()
    cmdstan_dir = os.path.join(target_dir, f"cmdstan-{CMDSTAN_VERSION}")

    if os.path.isdir(cmdstan_cache):
        print(f"Found existing cmdstan library at {cmdstan_cache}")
    else:
        cmdstanpy.install_cmdstan(version=CMDSTAN_VERSION, dir=cmdstan_cache)

    if os.path.isdir(cmdstan_dir):
        rmtree(cmdstan_dir)
    copytree(cmdstan_cache, cmdstan_dir)
    with cmdstanpy.utils.pushd(cmdstan_dir):
        clean_all_cmdstan()
        build_cmdstan()
    cmdstanpy.set_cmdstan_path(cmdstan_dir)

    model_name = "prophet.stan"
    target_name = "prophet_model.bin"
    sm = cmdstanpy.CmdStanModel(stan_file=os.path.join(MODEL_DIR, model_name))
    sm.compile()
    copy(sm.exe_file, os.path.join(target_dir, target_name))
    # Clean up
    for f in Path(MODEL_DIR).iterdir():
        if f.is_file() and f.name != model_name:
            os.remove(f)
    prune_cmdstan(cmdstan_dir)

```

Keen to get thoughts!

---

<div class="post-metadata">

**Author:** ![ahartikainen](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/ahartikainen/32/321_2.png) [@ahartikainen](https://discourse.mc-stan.org/u/ahartikainen)\
**Post date:** [September 6, 2021, 12:31pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/13 "2021-09-06T12:31:30Z")

</div>

Is this code run in CI or on user computer?

> [@cuongd](#):
>
> ```no-highlight
> if os.path.isdir(cmdstan_dir):
> rmtree(cmdstan_dir)
> copytree(cmdstan_cache, cmdstan_dir)
> with cmdstanpy.utils.pushd(cmdstan_dir):
> clean_all_cmdstan()
> build_cmdstan()
> 
> ```

I don’t quite follow this remove-copy idea? Is this to make sure that cmdstan is build with default settings?

> [@cuongd](#):
>
> `sm.compile()`

I think this is not needed

> [@cuongd](#):
>
> `prune_cmdstan(cmdstan_dir)`

Why is this needed? You pack the whole cmdstan folder structure + executables for summary etc?

I think what you should only need to do, is compile executable files (`.bin`) and then pack them into the wheel, as a data folder in your package + compiled tbb files. Then your user doesn’t need to have cmdstan installation.

---

<div class="post-metadata">

**Author:** ![cuongd](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.mc-stan.org/cuongd/32/7215_2.png) [@cuongd](https://discourse.mc-stan.org/u/cuongd)\
**Post date:** [October 2, 2021, 3:49pm UTC](https://discourse.mc-stan.org/t/tips-for-using-cmdstanpy-as-a-dependency-in-a-python-package/24129/14 "2021-10-02T15:49:25Z")

</div>

Thanks @ahartikainen! Responses to your questions below:

> Is this code run in CI or on user computer?

The main purpose would be to run on the facebook/prophet CI on package release, in order to generate the `.whl` files for PyPi. Ideally it’d work on users’ computers too if they want to build the package locally (but I guess we can think of this as a secondary use case for now).

> I don’t quite follow this remove-copy idea? Is this to make sure that cmdstan is build with default settings?

I don’t think the `remove` is necessary on CI actually, you’re right. I guess this step is pretty defensive; it’d only be relevant if the end user had previously built the package in-place (i.e. in the same folder as the source code).

> I think this is not needed

You’re right, thank you!

> Why is this needed? You pack the whole cmdstan folder structure + executables for summary etc?

Yeah exactly.

> I think what you should only need to do, is compile executable files ( `.bin` ) and then pack them into the wheel, as a data folder in your package + compiled tbb files.

Hmm we still use cmdstanpy code in our package, particularly the `.fit(), .sample(), .draws()` methods. The CmdStanMLE and CmdStanMCMC objects are also saved to the fitted Prophet object. I get your point though that the end user probably doesn’t care about the MCMC diagnostics at this point, and it’d probably simplify the setup script if we just remove the cmdstan installation altogether. I’m happy to include those executables in the wheel for now though (the total wheel size is still reasonable, \<20mb).
