Retrieved article excerpt
Open article Β· Retrieved 2026-09-29T00:32:00.022294+00:00
[Range](https://github.com/andreygrehov/range "Range on GitHub (Alt+R)")
Use a remote environment before downloading it.
README.md
README.md
# Use a remote environment before downloading it.
Range opens a shell in a container image, a Hugging Face repository, or an environment in S3
or on any HTTP server, without downloading it first. Only the bytes your program reads cross the
network.
```
$ range shell python:3.12
$ range shell python:3.12 --mount hf://moonshotai/Kimi-K2-Instruct:/model
$ range shell s3://<your-bucket>/dev.range
```
**2.8 s**to run Python in python:3.12
**48 MB**moved, of a 435 MB image
**9.5 MB**read, of a 1.03 TB model
No Docker, no daemon and no pull. Linux runs it natively. On macOS, Range runs Linux in a
small VM that it manages itself.
Measured on EC2 in usβeastβ1, with the image indexed once. See bench.log.
demo.txt
## A chat model, from nothing, in one line
```
$ range run ghcr.io/ggml-org/llama.cpp:light-b11206 \
--mount hf://unsloth/gemma-3-270m-it-GGUF:/model -- \
llama-cli -m /model/gemma-3-270m-it-Q4_K_M.gguf -st \
-p "Why is the sky blue? Answer in one sentence."
The sky is blue because of a phenomenon called Rayleigh scattering,
where blue light is scattered more than other colors.
```
Range opens the llama.cpp image from its registry and mounts the model repository at
`/model`. The repository holds 6.38 GB in 24 files. Range reads one of them.
With the image indexed, the answer took 6.7 s from an empty cache.
`docker pull` plus `hf download` took 18.3 s. The very first run,
which also indexes the image, took 15.5 s.
## A 1 TB model, open in seconds
```
$ range run python:3.12 --mount hf://moonshotai/Kimi-K2-Instruct:/model -- \
du -sh --apparent-size /model
959G /model
```
Kimi K2 is 1.03 TB in 61 shards. A Python script inside read its config, the header of one
shard and one tensor. That took 3.4 s with the image indexed, and moved 9.5 MB of the model. The
other 60 shards never left Hugging Face.
Range reads a file when a program opens it. A program that reads a whole model
still downloads the whole model, once.
bench.log
## The chat demo, against docker pull
docker pull + hf download1.0x18.27 s579 MB
Range, first run1.2x15.46 s590 MB
Range, image indexed2.7x6.73 s317 MB
Range, again4.3x4.25 s0 MB
*0 s**10 s*
| Empty cache to output | docker pull | Range, first run | Range, indexed | Range, again |
| --- | --- | --- | --- | --- |
| Chat demo | 18.3 s 579 MB | 15.5 s 590 MB | 6.7 s 317 MB | 4.2 s 0 MB |
| python:3.12 | 15.8 s 435 MB | 16.5 s 415 MB | 2.8 s 48 MB | 1.1 s 0 MB |
| rust:1.82 | 19.6 s 569 MB | 22.4 s 546 MB | 8.0 s 125 MB | 1.6 s 0 MB |
| eclipse-temurin:21 | 7.9 s 232 MB | 9.2 s 225 MB | 2.9 s 49 MB | 1.0 s 0 MB |
| Kimi K2, 1 TB | not tried 1.03 TB | 17.6 s 433 MB | 3.4 s 56 MB | 1.2 s 0 MB |
The commands: import json and sqlite3, cargo --version, java -version, and a
read of one Kimi K2 tensor. A first run reads each layer once to index it. The python:3.12 index
is 4.1 MB. Medians of three, m6i.large, usβeastβ1, 28 September 2026. Every run starts
empty, except "again". The bars replay at 3x speed.
problem.txt
## What you wait for today
A machine that needs a large environment downloads all of it, every time, to use a small part.
### docker pull downloads a whole image to run one command
: python:3.12 is a 435 MB download. Starting Python needs 48 MB of it.
### A model repository comes whole
: unsloth/gemma-3-270m-it-GGUF holds 24 versions of one model in 6.38 GB. The demo
needs one of them, 253 MB.
### Fifty eval workers download the same thing fifty times
: Each worker copies it to its own disk before it starts.
Range reads only the bytes each machine touches, and the next run fetches them
before it asks.
design.txt
## One abstraction, and nothing above it
`ReadAt(offset, length) -> bytes`. Range turns a source into a disk, and turns
each read of that disk into a ranged request to the source.
### An image becomes a disk
: Range reads each layer once to index it. After that a file costs one ranged request to the
registry, and a few megabytes of gzip or zstd decompression at most. The image stays as it is.
### A model repository becomes a disk
: The file list comes from the Hugging Face API, pinned to one commit. Reading a file sends a
ranged request to the Hub.
### A shell is that, with a filesystem on top
: disk -> read-only EROFS -> writable overlay -> namespaces. Writes stay local.
Range checks every 64 KiB of an image layer against a SHA-256 in the index.
### It learns the working set
: Each session records which blocks it needed. The next session fetches them in the background
as the shell starts. A real read always goes first.
install.txt
## Install
A release archive for macOS or Linux, x86-64 or arm64. On macOS, Range also needs Lima for
its Linux VM. Then open a shell in any image:
```
$ curl -fsSL https://github.com/andreygrehov/range/releases/latest/download/range_$(uname -s)_$(uname -m).tar.gz | tar -xz
$ brew install lima # macOS only
$ ./range shell python:3.12
```
For your own environments, build once, and every first run reads lazily:
```
$ range build --from-oci python:3.12 -o py.range
$ range publish py.range s3://<your-bucket>/py.range
$ range shell s3://<your-bucket>/py.range
```
A published artifact needs no indexing. The go1.23 demo artifact was ready in
0.41 s on its first run, and moved 6 MB of 1.03 GB.
Or build from source, with Go 1.25 or newer:
```
$ git clone https://github.com/andreygrehov/range && cd range && make install
```
[GitHub](https://github.com/andreygrehov/range)
Linux needs root and the nbd, erofs and overlay kernel modules.
`range doctor` checks them. Windows works through WSL2, untested.
about\_me.txt
A dithered black and white portrait of Andrey Grehov
I am a software engineer at AWS. Range is my personal project.
~/range $ view README.md