Charlie Marsh on uv: Python Dependency Resolution and Performance

The Astral founder explains universal lockfiles, Python packaging constraints, and the data structures and cache design behind uv.

CMCharlie MarshPonente · Astral
Canal
Jane Street
Publicado
23 oct 2024
Vídeo
40:33
Lectura
6 min
Diapositivas
7
Original
inglés
Abrir en YouTube
Al desplazarte, el vídeo se reduce a la esquina inferior derecha y sigue reproduciéndose. Los subtítulos siguen tu idioma de lectura.
Idioma de lecturaDisponible en 2 idiomas
Este artículo aún no está en español, así que se muestra el original en inglés.
RESUMEN

En 30 segundos

In this 2024 talk, Charlie Marsh describes uv as a unified Python package and project manager. He walks through dependency resolution across platforms, then explains performance techniques including compact version representations, partial wheel downloads, a shared cache, and zero-copy metadata.

  1. 1A unified toolMarsh describes uv as a fast all-in-one package manager inspired by Rust’s Cargo.
  2. 2The one-version constraintPython generally cannot install two versions of the same package in one environment, so incompatible dependencies require backtracking or failure.
  3. 3Universal resolutionPlatform-specific conflicts can require solving separate branches and merging them into a marker-annotated graph.
  4. 4Compact version encodingMore than 90% of versions in the team’s observation fit into a u64 representation with efficient comparison.
  5. 5Fetch only metadataHTTP range requests can retrieve a ZIP central directory and its metadata entry without downloading a large wheel.
  6. 6Optimize warm installsuv stores unpacked package contents in a global cache and links them into environments when possible.

Conclusiones

  1. uv aims to unify Python installation, environments, dependency resolution, package installation, and project workflows.
  2. Python’s single-version constraint makes conflicting dependency requirements a difficult version-solving problem.
  3. A universal lockfile must account for platform and Python-version markers and can require separate solutions that are merged.
  4. Compact version encoding speeds common comparisons while retaining a fallback for complex versions.
  5. Range requests can retrieve wheel metadata without downloading an entire archive when supported.
  6. An unpacked shared cache and zero-copy metadata reduce work in repeated operations.
TEXTO COMPLETO

Lee el texto editado por capítulos

Solo la transcripción
01

Why build uv?

Marsh describes a unified Python toolchain and how speed can change workflows. He outlines the installation and resolution lifecycle.

Charlie Marsh, founder of Astral, introduces the team’s Rust-based Python tools: Ruff for linting and formatting, and uv for package management. This talk focuses on why uv was built, the difficult parts of Python packaging, and techniques that make it fast.

uv aims to be a fast, unified package manager: install Python, create environments, resolve and install dependencies, and build distributable packages. Marsh compares the intended experience to Cargo, where cloning and running a Rust project is relatively predictable.

Python tooling is fragmented both because there are multiple choices for a given task and because nontrivial workflows require chaining tools. uv builds its own stack rather than layering on pip, with the goal of providing a coherent experience.

Speed can change workflows, not merely save time. Marsh says Ruff made some checks practical as local hooks instead of only in CI. With uv, virtual environments can be treated as disposable because recreating them is comparatively cheap.

Marsh reports that after its February release, uv had reached roughly 16 million downloads per month and more than 10% of requests to PyPI at the time of this 2024 talk. He uses that adoption as context for the implementation lessons.

uv exposes a pip-compatible interface such as uv pip install, as well as higher-level commands such as uv sync and uv lock. Both follow a similar lifecycle: find the Python interpreter, discover requirements, resolve dependencies, and plan changes to the environment.

Resolution turns a request such as a Pydantic dependency into compatible package versions. A lockfile also records where artifacts come from, their dependencies, hashes, and sizes. The installation plan compares that desired state with what is already installed.

A lockfile entry
DIAPOSITIVA 1 / 7A lockfile entryThe example records a package version, source, dependencies, artifact hash, and size.
02

The hard parts of resolution

Single-version constraints and conditional dependencies make resolution difficult. uv builds a universal, marker-annotated lock graph.

One difficulty is that a Python environment generally cannot use two versions of the same package at once. If one dependency requires Pydantic 2 and another requires Pydantic 1, that graph cannot be installed as specified.

If a dependency’s version is flexible, the resolver can backtrack through alternatives to find compatible versions. A realistic graph can have many packages and constraints; selecting versions so all requirements are satisfied is a Boolean satisfiability problem and is NP-hard in general.

uv uses a conflict-driven clause-learning approach to make solving practical, though there is no general guarantee of fast resolution. Marsh contrasts this with ecosystems that can sometimes escape a conflict by allowing multiple package versions.

Python requirements can also have environment markers: a dependency might apply only on Windows or only below a particular Python version. Solving for one known machine can filter irrelevant requirements, but uv aims to produce a universal lockfile usable across machines.

A universal lock does not mean every platform installs identical packages. It means users on the same applicable platform and Python version can reproduce the corresponding selection. The resolver tries a common solution, but some requirements conflict only across different environments.

Universal resolution and conflicting markers
DIAPOSITIVA 2 / 7Universal resolution and conflicting markersA platform-dependent conflict can be forked, solved, and merged.

For example, one requirement may need Pydantic 2 on Windows while another needs a version below 2 elsewhere. uv can fork the solve for the disjoint cases, resolve them separately, and merge the results with markers identifying where each version applies.

Merging platform-specific resolutions
DIAPOSITIVA 3 / 7Merging platform-specific resolutionsThe slide shows distinct Pydantic versions selected by mutually exclusive markers.

Dependencies common to both branches can be merged too. A marker saying Windows or not Windows is always true and can be simplified away. This requires marker algebra: combining conditions, simplifying them, and checking whether two expressions can overlap.

Checking marker disjointness is another satisfiability problem. Real expressions can become large, especially in a test resolving all optional dependencies of Transformers. The team built a normalizer based on algebraic decision diagrams to simplify and reason about markers.

A further complication is metadata: a package registry does not always provide static dependency information. If only source is published, determining dependencies may require executing Python build code or even building the package. uv tries to avoid unnecessary work while preserving correctness.

The resulting universal resolution is a graph of package versions with marker conditions on edges. At installation time, uv traverses the graph for the current environment instead of performing a second full dependency solve.

03

Version parsing and performance

Rust provides a fast baseline but is only part of the story. A compact representation accelerates common version comparisons.

Rust contributes to uv’s performance by providing a fast baseline and control over memory, but it is not the whole explanation. Much package-manager work is I/O, and uv has continued to improve while remaining written in Rust.

Python version syntax is richer than a simple 1.0.0: it can include pre-releases, post-releases, local identifiers, epochs, and combinations of these. Release segments can be numerous, so a general representation can require vectors and allocations.

Local version identifiers have also been used by projects such as PyTorch to distinguish accelerator-compatible builds. Marsh presents that as an adaptation to a gap in existing packaging conventions, rather than a simple numeric-version case.

A team member known as BurntSushi observed that more than 90% of versions they encountered could fit into a single u64. uv uses a compact representation for those common cases and falls back to a full representation for versions that do not fit.

The eight-byte encoding reserves two bytes for the first release segment, supporting calendar-style versions, three for later release segments, and three for one pre-release or post-release specifier. It cannot represent every valid version, including epochs or more than four release segments.

Compact version encoding
DIAPOSITIVA 4 / 7Compact version encodingThe diagram assigns bytes in a u64 to release segments and a pre- or post-release specifier.

The encoding is carefully ordered so that version ordering corresponds to integer ordering. Common comparisons can therefore use a single integer comparison instead of traversing allocated structures. In a difficult fully cached resolution, Marsh reports a roughly three- to fourfold speedup.

04

Reducing I/O and caching

Range requests retrieve wheel metadata selectively. Unpacked package caching and zero-copy metadata help repeated operations.

Next, consider dependency metadata. Python wheels are ZIP archives containing metadata, but some registries do not expose that metadata separately. Downloading an entire large wheel just to discover dependencies is expensive, particularly when testing multiple versions.

The ZIP central directory indexes the entries and their positions. uv can use an HTTP range request near the end of the archive to retrieve the directory, find the metadata entry, and make a second range request for that entry rather than downloading the full wheel.

ZIP central directory
DIAPOSITIVA 5 / 7ZIP central directoryAn index near the end of a wheel lets uv locate metadata with range requests.

Marsh says cache design is one of the largest contributors to perceived speed. Many local installations reuse packages already present on the machine, so uv optimizes these warm operations rather than repeatedly downloading and unpacking the same artifacts.

The global cache contains unpacked archives: downloaded wheels are unzipped into it as they arrive. When possible, installing into an environment uses reflinks or hard links to cached files. That makes repeated installations fast and avoids duplicating the same contents across projects.

Cache design
DIAPOSITIVA 6 / 7Cache designThe cache is optimized for warm operations and stores unpacked archives.

Package metadata is different from installed files, so uv also uses zero-copy techniques. Rather than allocating a second copy of data after reading it, a structure can refer to bytes already in memory where the representation permits it.

Zero-copy metadata
DIAPOSITIVA 7 / 7Zero-copy metadataThe example illustrates accessing a structure from already-read bytes, with unsafe operations requiring care.

For cached metadata, Marsh describes storing data in a form close to its in-memory representation and using the rkyv library. Validation and safety checks matter around the unsafe operations. Reading still costs more for a larger file, but converting the already-read representation avoids work proportional to ordinary parsing and copying.

Más de este canal

6 diapositivas15:33EN
JSJane Street· 26 oct 2026Figgie Strategy: Reading Hands, Winning Majorities and Pricing CardsBradley, Mike · Jane StreetJane Street traders Bradley and Mike analyze three games to show how private information, market competition and timing affect card prices.Decision MakingMathematicsFintech
1:48:29EN
JSJane Street· 16 oct 2026How AI Is Changing Programming: John Krazit and Ron MinskyRon Minsky, John Krazit · Jane StreetJane Street’s AI tooling lead and the Signals and Threads host examine agent infrastructure, security, code review and the changing experience of writing software.Agentes de programaciónDeveloper productivityEvaluaciones+2
1:36:37EN
JSJane Street· 23 sept 2026Aaron Bauer on Teaching Software Engineering in the Age of AIRon Minsky, Aaron Bauer · Jane StreetHow learning goals, active practice and editor design shape engineering education when models can generate the code.AI in EducationSoftware engineeringEducation+2
9 diapositivas1:05:45EN
JSJane Street· 11 sept 2026Cyrus Omar on Live Programming and a Computational Commons for Humans and AICyrus OmarA demonstration of Hazel shows how immediate feedback, structured collaboration and carefully scoped permissions could support people and AI agents working together.Programming LanguagesHuman-AI CollaborationFlujos de desarrollo y pruebas+2
1:25:00EN
JSJane Street· 2 sept 2026Eric Mannes on Turning Messy External Data Into Trading ResearchRon, Eric Mannes · Jane StreetFrom commodity futures and an Excel risk system to point-in-time datasets and AI, Eric Mannes explains the domain knowledge behind data engineering at Jane Street.Data quality & curationFlujos de desarrollo y pruebasInvesting+2
7 diapositivas1:16:29EN
JSJane Street· 26 ago 2026Grant Sanderson and Alok Puranik: Positional Encodings Through Group TheoryGrant Sanderson, Alok Puranik · 3Blue1BrownStarting with assumptions about attention, they derive matrix exponentials and connect rotations, decay and Jordan blocks to RoPE and ALiBi.Transformers & attentionMathematicsFundamentos de ML+1