Installation#
This document describes how to build and install Maru.
System Components#
Maru consists of two server components and a client library. A deployment with GPU nodes without CXL (the remote_cxl backend) adds a remote CXL agent on each memory node:
Component |
Role |
Package |
|---|---|---|
Resource Manager |
Manages the CXL memory of a memory pool |
|
Metadata Server |
Manages KV metadata |
|
MaruHandler |
Client library embedded in LLM instances |
|
Remote CXL Agent |
On memory nodes: maps CXL regions for LLM instances on nodes without CXL and registers them with the NIC ( |
|
In a single-node setup, all components run on the same machine. In a multi-node setup, designate one node as the orchestrator to run the Resource Manager and Metadata Server. Other nodes run LLM instances with MaruHandler. With the default cxl backend, MaruHandler connects to both the Resource Manager and Metadata Server over the network. With the remote_cxl backend, it connects to the Metadata Server and to the remote CXL agent of each memory node instead; the remote CXL agents reach the Resource Manager, which runs on a memory node.
Prerequisites#
OS: Ubuntu 24.04 LTS+
Python: 3.12+
gcc: 13.3.0+
cmake: 3.28.3+
git
uv — recommended Python package installer
CXL DAX device (
/dev/dax*) or emulation environment, on the nodes that hold the pool and the nodes that read it over CXLMulti-node: All nodes that share the pool over CXL must be connected to the shared CXL memory pool (e.g., via CXL switch). GPU nodes without CXL reach it over RDMA instead (
remote_cxlbackend).
For the
remote_cxlbackend: an RDMA NIC (RoCE or InfiniBand) on every worker and memory node, a route from the maru-server node to the remote CXL agents on the memory nodes’ RDMA network addresses, and NIXL in the same environment, installed before./install.sh(uv pip install nixl, orpip install nixlwithout uv)
See System Setup for platform BIOS settings and DEV_DAX verification, and Consistency and Safety for the cache-visibility requirements of cross-host sharing.
sudo apt-get update
sudo apt-get install -y python3 python3-venv python3-pip git \
build-essential cmake libnuma-dev
# uv — used for all Python installs in this guide
curl -LsSf https://astral.sh/uv/install.sh | sh
Note:
install.shusesuvwhen available and falls back topipotherwise, so uv is recommended but not required.
1. Installation from Source Code#
1.1 Getting the Source#
The Maru source code for released versions can be obtained from our GitHub repository: https://github.com/xcena-dev/maru
git clone https://github.com/xcena-dev/maru
1.2 Installation#
(Optional) Create a virtual environment and activate it:
uv venv --python 3.12 .venv
source .venv/bin/activate
Pass
--python 3.12explicitly so the interpreter matches the one your LLM engine (vLLM, SGLang) was built against. Without it, uv may pick any interpreter satisfyingrequires-python >= 3.12.
Install all components (Python package + Resource Manager):
./install.sh
To install without the Resource Manager (e.g., on nodes that only run LLM instances with MaruHandler, and always on remote_cxl worker nodes, which would otherwise get a remote CXL agent and a key of their own):
./install.sh --no-rm
Note: With the default
cxlbackend, client nodes still require CXL device access (/dev/dax*) for direct mmap. The--no-rmflag skips building the Resource Manager binary — themaruPython package (including MaruHandler) is still installed and will connect to the remote Resource Manager. Workers that use theremote_cxlbackend need no CXL device: they reach the CXL memory of the memory nodes over RDMA.
With NIXL installed (uv pip install nixl before ./install.sh), the
installer also sets up the remote CXL agent as maru-remote-cxl-agent.service, which
starts and stops with the Resource Manager, and creates the remote CXL agent key
/etc/maru/remote-cxl-agent.key. Set the node’s RDMA NIC in
/etc/maru/remote-cxl-agent.env (MARU_REMOTE_CXL_AGENT_UCX_DEVICE): the service
listens on that NIC’s address. Workers and maru-server need a copy of the key,
readable only by the users that run them; they read
~/.maru/remote-cxl-agent.key by default. The
remote CXL example
walks through a deployment.
The installer checks for NIXL, not for an RDMA NIC, when selecting this service. It enables the unit without starting it immediately. Starting the Resource Manager later also starts the agent; with the default empty NIC and listen address, the agent fails to start until its RDMA interface is configured. The unit stops retrying after five failed starts in a minute. This agent failure does not stop the Resource Manager.
A memory node used only for the local cxl backend does not need the agent.
Without NIXL the installer skips its service. If NIXL is installed for another
purpose on such a node, disable only the unused agent:
sudo systemctl disable --now maru-remote-cxl-agent.service
Reinstalling with NIXL enables the unit again. ./install.sh --no-rm skips both
platform services, so it is the worker-node installation path rather than a
way to keep the Resource Manager without its agent on a memory node.
1.3 KV Placement Kernels (vLLM connector)#
The vLLM connector moves a request’s KV cache between a Maru CXL object and vLLM’s paged cache with the CUDA kernels in maru_kv_ops. Those kernels are a build product of this package, and building them needs PyTorch importable by the interpreter that will run the engine, plus the CUDA toolkit (nvcc). On a host without either, Maru installs anyway and the connector copies one layer at a time — correct, but materially slower, and the connector logs a warning naming this step.
install.sh handles this: it looks for PyTorch in the target environment and, when it finds it, installs with --no-build-isolation so the build can see it, then reports whether the kernels are callable.
Installing by hand needs that flag passed explicitly. An isolated build gets only the packages in [build-system].requires, so it never sees PyTorch and skips the kernels silently:
uv pip install setuptools wheel # the build backend, in this environment
uv pip install -e . --no-build-isolation
Check the result at any time:
python -c "import maru_kv_ops; print(maru_kv_ops.is_available(), maru_kv_ops.import_error())"
True None means the connector takes the kernel path. Anything else prints why it cannot, and MARU_SKIP_KV_OPS=1 skips the build outright when that is what you want.
1.4 Development Install#
To work on Maru itself, install the dev extra (pytest, mypy, ruff, pre-commit) instead of running install.sh:
uv venv --python 3.12 .venv
source .venv/bin/activate
uv pip install -e ".[dev]"
Without uv, the same install works through pip:
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
Install the git hooks once, so lint and format run on every commit:
pre-commit install
Then run the unit tests (these need neither a CXL device nor a running service):
pytest -m "not integration" --ignore=tests/sglang
To reproduce the lint job exactly, run the hooks against the whole tree. This
uses the ruff version pinned in .pre-commit-config.yaml — the same version CI
runs:
pre-commit run --all-files
2. Verify Installation#
Verify that the Maru Python package is installed:
python3 -c "import maru_shm; print('ok')"
Verify the x86 cache-flush extension used by the Quick Start producer and consumer. It is an optional build product, so the install succeeds without it when no C compiler is available:
python3 -c "from maru_shm._cxl_flush import HAVE_CLFLUSH; print(HAVE_CLFLUSH)"
1 means the extension is available. An ImportError means it was not built: install a C compiler (build-essential) and the headers for your Python interpreter (python3-dev for the system Python), then rerun install.sh.
If you installed with the Resource Manager, verify the binary:
which maru-resource-manager
Once the Resource Manager runs, sudo maru-rm-ctl status shows the devices it serves and why any other device is not served. See maru-rm-ctl.
Once installation is verified, choose the single-host or multi-host setup in Quick Start to start services and verify KV sharing.
3. Multi-Node Configuration#
In a multi-node deployment, the Resource Manager, Metadata Server, and MaruHandler communicate over the network.
Node A (Orchestrator) Node B
┌─────────────────────────┐ ┌─────────────────────┐
│ LLM Engine │ │ LLM Engine │
│ MaruHandler │ │ MaruHandler │
│ │ │ │
│ maru-server │◄─RPC─►│ │
│ maru-resource-manager │ │ │
└────────────┬────────────┘ └──────────┬──────────┘
│ │
└──────── CXL Memory Pool ──────┘
maru-serverandmaru-resource-managerdo not need to run on the same node. The diagram above shows the simplest configuration; each can be deployed independently as long as MaruHandler can reach both over the network.
Configuration#
Multi-node requires changing default bind addresses from 127.0.0.1 to a network-accessible address:
Resource Manager: change
--hostto accept remote connectionsMetadata Server: change
--hostand set--rm-addressto the Resource Manager’s externally reachable addressMaruHandler: set
server_urlto the Metadata Server’s address. The Resource Manager address is received automatically via handshake.Remote CXL Agent (
remote_cxlbackend): setMARU_RM_ADDRESSin/etc/maru/remote-cxl-agent.envto the address the Resource Manager listens on; a Resource Manager bound to a network address answers only there.
To run the Resource Manager as the systemd service on a network address, override its command line:
sudo systemctl edit maru-resource-manager
[Service]
ExecStart=
ExecStart=/usr/local/bin/maru-resource-manager --host <node-a-ip> --port 9850
sudo systemctl restart maru-resource-manager
Security: When binding to a non-loopback address, auth tokens and device paths are transmitted in plaintext. Use an encrypted tunnel (WireGuard, SSH tunnel, IPsec) in production.
Follow the multi-host quickstart to start the services and share data between a Python producer and consumer on two hosts.