All articles
Engineering Deep Dives·

When You Have to Vendor: Pip-Install Reality on Legacy Edge Hardware

Modern Python ML libraries assume Python 3.10+ and numpy 2.x. The Jetson Orin you bought 18 months ago is still on JetPack 5.1.2 — Python 3.8, numpy 1.23. Here's the four-decision flowchart for getting modern algorithms onto stale hardware without breaking what's already working.

When You Have to Vendor: Pip-Install Reality on Legacy Edge Hardware

There's a class of bug that doesn't show up until you try to put a 2026-era algorithm onto a 2024-era edge device. The algorithm is published, the reference implementation is open source, the paper is well-written. You run pip install on your development laptop and it works in 90 seconds. You SSH to the edge box, run the same command, and get a 400-line dependency resolution error that ends with ERROR: Could not find a version that satisfies the requirement numpy>=2.0.

Welcome to the legacy-edge problem.

Why this happens

Edge AI hardware ships on a vendor SDK that bundles a specific JetPack / L4T / firmware version. That SDK fixes the operating system, the CUDA toolkit, the cuDNN version, the TensorRT version, and the system Python. Once a board is in the field, upgrading that stack is a multi-day operation that risks bricking the device — and on production hardware, you usually can't upgrade at all without re-validating every downstream component.

So the field accumulates a lot of boards on JetPack 5.1.2 (released August 2023), which means:

  • Ubuntu 20.04
  • Python 3.8 as the system Python
  • CUDA 11.4
  • TensorRT 8.5.2.2
  • numpy 1.23 as the highest version that doesn't tear down the rest of the stack

Meanwhile, the Python ML ecosystem moved on. As of 2026:

  • PyTorch ≥ 2.5 requires Python ≥ 3.9
  • numpy 2.0 released June 2024, became the default in October 2024, and is now required by most fresh-cut libraries
  • Modern detection / tracking libraries (Roboflow trackers, Ultralytics ≥ 8.3.x, RF-DETR, current torchvision) require Python 3.10+ and numpy ≥ 2

The gap is structural, not accidental. The library authors aren't being careless. They have no reason to maintain backward compatibility with a JetPack version that NVIDIA itself has flagged for end-of-life. And NVIDIA has no incentive to backport modern Python to a stable LTS image that customers depend on.

The result: when you try to deploy a 2026 algorithm to a 2024 board, you hit a wall. And the most common piece of advice — "just upgrade to JetPack 6" — is wrong more often than it's right, because JetPack 6 forces you to revalidate your entire production stack and most teams don't have the time or the test coverage to do that safely.

The four options, in order of preference

When a dependency conflict blocks deploying modern Python code on a legacy edge box, you have exactly four options. Each one has a clear use case and a clear failure mode.

Option 1: Find a maintained backport

Look at the library's GitHub repo, the issue tracker, and the recent pull requests. Some libraries explicitly maintain a Python 3.8 / numpy 1.x branch — often tagged as legacy/, compat/, or with a version range pinned to the older Python release.

Ultralytics, for example, maintains compatibility back to Python 3.8 on ultralytics ≥ 8.0.x, < 8.2.x. If your modern algorithm is shipping as part of an active library that cares about embedded deployment, there's a real chance an older version of the library will install cleanly and give you 80% of the new features.

When this works: the algorithm you want is in an actively-maintained library, the library has explicit legacy support, and the feature you need predates the breaking change.

When this fails: the algorithm is in a new library, the legacy branch is unmaintained, or you specifically need a feature that requires the new numpy / new Python.

Option 2: Pin the dependency and pray

Force-install with pip install --no-deps, then manually resolve the transitive dependencies. This works when the library author's requires-python line in pyproject.toml is wrong (a common bug) and the actual code runs fine on the older interpreter.

When this works: the version pin is conservative, not actually required by the code. Library authors often over-restrict to avoid CI hassle.

When this fails: the library uses Python 3.10+ syntax features (match statements, | union types in annotations, walrus operator in specific contexts, PEP 612 ParamSpec). Then your import succeeds but every function call explodes at runtime with SyntaxError. Or the library uses numpy 2.x semantic changes (the np.float64 removal, the np.int_ change, the copy=False enforcement) that break silently with wrong results instead of explicit errors.

Tread carefully. This option pays off when it works; it costs you a week when it doesn't.

Option 3: Vendor the upstream reference

Drop the algorithm's reference implementation directly into your repo as source code. Not as a pip dependency — as committed source files under a directory like external/ or vendor/. Adapt imports, fix the numpy 2 deprecations in place, and run the algorithm from your own code path.

This is what the OC-SORT tracker integration looked like for one of our recent deployments. The Roboflow trackers library is the modern Python implementation, but it requires Python ≥ 3.10. The upstream academic reference at noahcao/OC_SORT is a single-file algorithm with no fancy dependencies — it predates the Roboflow polish, it's not as ergonomic, but it runs on Python 3.8 with minimal modification.

We pulled the reference at a pinned commit, vendored the four files we needed into external/oc_sort/ with the original license headers preserved, fixed two numpy 1.x deprecation warnings, and ran it. The whole integration took an afternoon. The tracker performs identically to the Roboflow version because both implementations track back to the same paper.

When this works: the algorithm is research code that doesn't depend on a complex ecosystem. Most tracker implementations, most loss functions, most data augmentation pipelines fit this profile.

When this fails: the library has a large surface area, lots of transitive dependencies, or active development. Vendoring transformers is a non-starter; vendoring OC_SORT is a four-hour task.

Option 4: Pre-compute on a beefier box, deploy as artifact

Skip the legacy edge box entirely for the modern-algorithm work. Run the heavy library on a workstation with current Python, produce a portable artifact (an ONNX export, a TensorRT engine, a JSON-encoded model, a scalar lookup table), then ship that artifact to the edge device. The artifact runs on whatever runtime the edge box already has.

This is what you do when you can't get a fine-tuned detector to install on the Jetson directly. You train on Linux with PyTorch 2.5, you export the trained model to ONNX, you optimize that ONNX to a TensorRT engine that targets the specific TensorRT version on the edge box, and you run the engine via the legacy TensorRT Python bindings that JetPack 5.1.2 already shipped with.

The legacy edge box never sees PyTorch 2.5. It just sees a TensorRT engine file and an inference loop written against the TensorRT API.

When this works: the modern code is in the training / preprocessing / export pipeline, not in the deployment runtime. Most ML deployments fit this profile.

When this fails: the algorithm fundamentally requires runtime access to the modern library. Trackers, post-processing stages, dataset loaders, anything that takes streaming input — these can't be precomputed. They have to run on the device.

The decision flowchart

Here's the call pattern that works, applied in order:

  1. Is the algorithm in an actively-maintained library? If yes, check for a legacy branch (Option 1). Done in 10 minutes.
  2. Is the version pin a soft preference or a hard requirement? If soft, try pip install --no-deps and audit the code for new-syntax features (Option 2). Done in an hour if it works.
  3. Is the algorithm research code with a small surface area? If yes, vendor the reference implementation (Option 3). Done in an afternoon.
  4. Is the algorithm only needed during training / export? If yes, run it off-box and ship an artifact (Option 4). Done in the time it takes to retrain.

The trap teams fall into is jumping straight to "let's upgrade the JetPack" or "let's give up and pick a different algorithm." Both are expensive. The four options above cover 90% of legacy-edge dependency conflicts at one to two orders of magnitude less effort.

What this costs you long-term

Vendoring has a price. Once you've committed a third-party implementation into your repo, you own it. Upstream bug fixes don't flow to you automatically. The original repo's issue tracker isn't watching your fork. Security patches require manual back-ports.

The mitigation is good vendor hygiene:

  • Pin to a specific commit hash, not a branch. Branches move; hashes don't.
  • Document the source. A external/<library>/README.md with the upstream URL, the pinned commit, the date of import, and the list of local modifications.
  • Preserve the license. Most ML reference implementations are MIT or BSD; keep the original LICENSE file in the vendored directory.
  • Track upstream. Every six months, check whether upstream has released a version that's actually compatible with your edge environment. If yes, switch back to pip.

Vendoring is a stopgap, not a destination. The right time to switch back to pip is when JetPack 6 or its successor lands on your production hardware and Python 3.10+ becomes available. Until then, vendoring is the path that ships.

The structural lesson

Edge AI projects fail more often on dependency conflicts than on algorithmic insufficiency. The algorithm always exists; what's missing is a deployment path through a stack that the algorithm's authors didn't design for.

The teams that ship reliably on edge hardware aren't the ones with the deepest ML expertise. They're the ones with the most patience for dependency-resolution work — and the most willingness to use the right escape hatch (legacy branch, vendoring, off-box compute, artifact deployment) instead of forcing the modern stack onto hardware that wasn't built for it.

If you're staring at a 400-line pip error on a Jetson and considering a full JetPack upgrade, stop. Try the four options above first. The upgrade is rarely the right answer.


Related reading:

Share:

Stay Connected

Get practical insights on using AI and automation to grow your business. No fluff.