Packaging PyTorch Into a Pet Bowl's Operating System

Packaging PyTorch Into a Pet Bowl's Operating System

A smart pet bowl runs real neural networks. It segments the animal in a camera frame, reads a temperature at the right point on its body, and classifies the sounds of eating and barking. That means a full deep-learning runtime has to live on the device — not a toy inference library, but the same C++ engine the models were trained against.

Now add the constraints. The operating system is built from recipes, so every component is declared, versioned, and reproducible; nobody gets to log in and install things by hand. The build happens on an ordinary desktop machine, but the target is an entirely different processor architecture. And building this particular runtime from source is a small expedition — an enormous dependency tree and hours of compilation, before you even start cross-compiling it.

Here's how we got it in anyway — by refusing to build it at all.

The Problem: You Can't Just Install It

On a normal Linux box, adding a deep-learning runtime is one command. On a purpose-built embedded operating system, that command is exactly what you're trying to eliminate.

The whole reason for building the OS from recipes is that every device should run an identical, reproducible image. The moment a human installs a package by hand, that device has drifted — it's no longer something you can rebuild, audit, or roll back to with confidence. So the runtime has to become a first-class, declared part of the image, like the kernel or any other component.

That leaves two options: compile the runtime from source as part of the OS build, or find a legitimate way to package a prebuilt one. The first is technically pure and practically miserable — a multi-hour build of a huge C++ project, cross-compiled, that you then own forever. The second sounds like cheating. It isn't, and it's what we did.

The Approach: A Wheel Is Just a Zip

The project already publishes an official prebuilt package for our target architecture. It's distributed as a Python wheel — but that wheel contains far more than Python. Inside it sit the exact C++ shared libraries, headers, and build-system configuration we need.

And a wheel is, structurally, nothing more than a ZIP archive with a different file extension. That single observation is the whole trick. If you rename it on download, the build system's fetcher recognises it as an archive and unpacks it like any other source:

10	# A wheel is a zip; rename via downloadfilename so the fetcher unpacks it, into a subdir.
11	SRC_URI = "https://files.pythonhosted.org/packages/…/torch-2.5.1-cp311-cp311-manylinux2014_aarch64.whl;downloadfilename=libtorch-${PV}.zip;subdir=libtorch-${PV}"
12	SRC_URI[sha256sum] = "340ce0432cad0d37f5a31be666896e16788f1adf8ad7be481196b503dad675b9"

(URL path shortened above for width.)

Look at line 12, because it's what makes this legitimate rather than a hack. The source is pinned by cryptographic checksum. The build will only ever accept those exact bytes; if the upstream file changed, or a mirror served something else, the build fails instead of silently shipping different software. The runtime becomes content-addressed, exactly like every other input to the image.

A guard also restricts the recipe to the architecture the binary was built for, so it can never be pulled into a build for the wrong processor:

8	COMPATIBLE_HOST = "aarch64.*-linux"

The Process: Four Decisions That Make It Work

Take the C++ runtime, leave the Python

The wheel is built for Python users, but nothing on our device runs Python for inference — the pipeline is a C++ program. So the install step copies the runtime libraries and explicitly skips the one library that exists solely to bind the engine to Python:

27	    # Core C++ runtime libs (exclude the Python-only binding lib).
28	    for so in ${S}/torch/lib/*.so*; do
29	        case "$so" in
30	            *libtorch_python*) continue ;;
31	        esac
32	        install -m 0755 "$so" ${D}${libdir}/
33	    done

That one exclusion removes a large library that would otherwise sit in the image forever, doing nothing. On a device where storage is budgeted to the megabyte, dead weight is a real cost.

Bring the hidden dependencies

Prebuilt wheels are self-contained: they quietly bundle their own maths, threading, and architecture-optimised compute libraries, given mangled filenames to avoid clashing with the host system's copies. Miss them and the runtime loads but fails at the first real operation.

35	    # Bundled dependency libs (libgomp/openblas/arm_compute/gfortran — hashed names).
36	    if [ -d ${S}/torch.libs ]; then
37	        for so in ${S}/torch.libs/*.so*; do
38	            install -m 0755 "$so" ${D}${libdir}/
39	        done
40	    fi

Turn off the quality checks meant for your own code

An OS build system runs a battery of automated checks on everything it packages: strip the debug symbols, split them into a separate debug package, verify linker flags and architecture conventions. Those checks all assume you compiled the thing yourself.

You didn't. You can't re-strip a binary someone else already stripped, and you can't enforce your own compiler conventions on a third-party artifact. So the recipe explicitly stands those checks down for this package:

16	# Prebuilt third-party binaries: skip normal strip/debug-split and binary QA.
17	INHIBIT_PACKAGE_STRIP = "1"
18	INHIBIT_PACKAGE_DEBUG_SPLIT = "1"
19	INHIBIT_SYSROOT_STRIP = "1"
20	INSANE_SKIP:${PN} = "already-stripped ldflags textrel arch file-rdeps libdir staticdev dev-so dev-deps"

That list looks alarming, and it deserves the comment above it. Every entry is a deliberate acknowledgement that this package is an imported binary, not something the build produced — which is precisely why the checksum pin on line 12 matters so much. You've traded one form of assurance for another.

The ownership trap

This is the subtle one, and the kind of bug that only bites in production. Copying files two different ways gives two different results: the standard install helper resets ownership to root, while a plain recursive copy preserves whatever ownership the unpacked archive had — which is the user account of the machine that ran the build.

Left unfixed, the headers in your operating system image would be owned by a numeric user ID that means nothing on the device. The fix is one line, and the comments record exactly why it's there:

42	    # Headers (install resets ownership; cp -R would carry host uid 1000).
43	    cp -R ${S}/torch/include/* ${D}${includedir}/
...
49	    # Reset host (uid 1000) ownership from the unpacked wheel to root.
50	    chown -R root:root ${D}

Make it findable when cross-compiling

Finally, the runtime has to be usable by the application build. Modern C++ projects locate their dependencies through generated configuration files, and those files contain paths — which, when you're cross-compiling, must resolve to locations on the target, not the machine doing the building. Shipping that configuration into the right place is what lets the application find the runtime cleanly:

45	    # CMake package config (TORCH_INSTALL_PREFIX resolves to ${prefix} => libs in
46	    # ${libdir}, headers in ${includedir}) so find_package(Torch) works cross.
47	    cp -R ${S}/torch/share/cmake/* ${D}${libdir}/cmake/

Both of the last two problems are the same bug wearing different clothes: the build machine leaking into the shipped image. Once through file ownership, once through paths. Cross-compiling is largely the discipline of noticing every place that can happen.

The Results

The outcome is a deep-learning runtime that is a declared, versioned, checksum-pinned component of the operating system — indistinguishable in status from the kernel or any other package. The inference pipeline cross-builds against it on a desktop machine and runs against it on the device.

More importantly, it preserves the property the whole platform exists to protect: every device runs an image that can be rebuilt, byte for byte, from recipes. There is no step where a human installed a machine-learning framework by hand, and no device carrying a slightly different version than its neighbours. The clever part isn't the wheel trick — it's that a large, awkward third-party binary got absorbed into a reproducible system without weakening it.

Why It Matters at Hoomanely

Hoomanely is reinventing healthcare for pets — replacing reactive, imprecise care with continuous, clinical-grade monitoring that catches problems early. Our devices form a Physical Intelligence ecosystem: sensors fused at the edge, feeding the Biosense AI Engine that turns raw signals into personalized, preventive insights.

Edge AI is the part everyone talks about; getting the AI onto the edge is the part that actually takes the time. A model is only useful if the runtime that executes it is present on every device, identical across the fleet, and updatable years from now — which means it has to be built into the platform rather than bolted onto it.

Doing this work properly is what lets us say something specific about any bowl in any home: exactly which inference runtime it has, verified by checksum, and reproducible on demand. In health monitoring, knowing precisely what software produced a result is not a nicety — it's the basis for trusting the result at all.

Key Takeaways

  • Reproducibility beats convenience. On a fleet OS, a hand-installed dependency is drift; every component should be declared, versioned, and rebuildable.
  • You don't always have to compile it. An official prebuilt binary, pinned by cryptographic checksum, can be more trustworthy and far cheaper than a heroic from-source cross-build.
  • Package formats are often just archives. Recognising that a wheel is a zip turns an awkward import into an ordinary fetch-and-unpack.
  • Prebuilt binaries need their quality gates stood down deliberately. Strip, debug-split and convention checks assume you compiled the code — disable them consciously, and lean harder on the checksum instead.
  • Watch how files are copied. A recursive copy carries the build machine's file ownership into your image; only an explicit reset keeps the shipped filesystem sane.

Author's Note

This recipe is a small, unglamorous piece of everOS, the operating system behind Hoomanely's Everbowl. It exists so that a pet bowl in someone's kitchen can run genuine neural networks, and so that we can say precisely — and prove — which software is doing it. Most of the interesting work in edge AI looks like this: not the model, but the patient plumbing that gets the model somewhere it can actually help a pet.