Posted on
Artificial Intelligence

Artificial Intelligence Edge Deployments

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence at the Edge: A Practical Bash-Friendly Guide for Linux

If your cameras, sensors, and machines already run on Linux, why ship all that raw data to a distant cloud? Edge AI lets you act in milliseconds, preserve privacy, cut bandwidth costs, and keep working even when the network blips. The problem: getting a reliable, repeatable, and secure AI service running on small devices is hard.

This article shows a clean, Bash-first path: benchmark your model locally, wrap it in a minimal container, wire up I/O with MQTT, and make it survive reboots. You’ll get copy-paste commands for apt, dnf, and zypper, plus a few real-world patterns.

Why edge AI (and why Linux)?

  • Latency and determinism: Respond in tens of milliseconds (think safety stop, defect rejection) without round-trips to the cloud.

  • Privacy and compliance: Keep raw audio/video on-device; ship only events or embeddings.

  • Cost and resilience: Slash bandwidth, reduce cloud inference costs, keep running offline.

  • Linux is the edge OS: Package managers, containers, and systemd give you standard, repeatable ops on everything from small x86 boxes to ARM boards.

What you’ll install

We’ll use:

  • Podman (rootless containers by default, works great on Fedora/openSUSE/Ubuntu)

  • Git, curl (utility)

  • Python 3 + pip (to prototype and benchmark)

  • Mosquitto (MQTT broker and CLI) for decoupled I/O

Install the packages with your distro’s manager.

Debian/Ubuntu (apt):

sudo apt update
sudo apt install -y \
  git curl podman \
  python3 python3-pip python3-venv \
  mosquitto mosquitto-clients

Fedora/RHEL/CentOS Stream (dnf):

sudo dnf -y install \
  git curl podman \
  python3 python3-pip \
  mosquitto mosquitto-clients

openSUSE Leap/Tumbleweed (zypper):

sudo zypper refresh
sudo zypper install -y \
  git curl podman \
  python3 python3-pip \
  mosquitto mosquitto-clients

Note: For Python virtual environments, apt needs python3-venv; on dnf/zypper, venv is usually built into python3.


1) Benchmark your model locally (CPU baseline)

Before you optimize, set a baseline. ONNX Runtime runs on CPUs out of the box and is a great place to start.

Create a clean virtual environment:

python3 -m venv .venv
. .venv/bin/activate
pip install --upgrade pip
pip install onnx onnxruntime numpy opencv-python-headless paho-mqtt

Drop a model at ./model.onnx (export from PyTorch/TensorFlow or grab one from an ONNX model zoo). Then use this script to sanity-check shapes and measure latency:

#!/usr/bin/env python3
# file: benchmark.py
import os, time, json, numpy as np, onnxruntime as ort

MODEL = os.environ.get("MODEL_PATH", "model.onnx")
WARMUP = int(os.environ.get("WARMUP", "5"))
RUNS   = int(os.environ.get("RUNS", "50"))

sess = ort.InferenceSession(MODEL, providers=["CPUExecutionProvider"])
io = sess.get_inputs()
assert len(io) == 1, "This demo expects a single input"
name = io[0].name
shape = [d if isinstance(d, int) else 1 for d in io[0].shape]  # fill dynamic dims with 1
dtype = np.float32

x = np.random.rand(*shape).astype(dtype)

# Warmup
for _ in range(WARMUP):
    _ = sess.run(None, {name: x})

# Timed runs
times = []
for _ in range(RUNS):
    t0 = time.perf_counter()
    _ = sess.run(None, {name: x})
    times.append((time.perf_counter() - t0) * 1000.0)

print(json.dumps({
    "model": MODEL,
    "input_shape": shape,
    "mean_ms": float(np.mean(times)),
    "p90_ms": float(np.percentile(times, 90)),
    "runs": RUNS
}, indent=2))

Run it:

python benchmark.py

Actionable goal: Define your success criteria now (e.g., “<20 ms latency on CPU” or “>10 FPS”). If you can’t hit targets on CPU, plan for INT8 quantization or hardware acceleration next.


2) Containerize the inference service (Podman, rootless)

Containers make your deployment reproducible and portable across devices.

Create a minimal app that runs inference and publishes results to MQTT:

#!/usr/bin/env python3
# file: app.py
import os, json, time, numpy as np, onnxruntime as ort
import paho.mqtt.client as mqtt

MODEL = os.environ.get("MODEL_PATH", "model.onnx")
TOPIC = os.environ.get("MQTT_TOPIC", "edge/infer")
BROKER = os.environ.get("MQTT_BROKER", "localhost")

sess = ort.InferenceSession(MODEL, providers=["CPUExecutionProvider"])
xinfo = sess.get_inputs()[0]
iname = xinfo.name
ishape = [d if isinstance(d, int) else 1 for d in xinfo.shape]

client = mqtt.Client()
client.connect(BROKER, 1883, 60)
client.loop_start()

while True:
    # Replace with real sensor data
    x = np.random.rand(*ishape).astype(np.float32)
    t0 = time.time()
    out = sess.run(None, {iname: x})
    latency_ms = (time.time() - t0) * 1000.0

    payload = {"latency_ms": latency_ms, "outputs": [np.array(o).tolist()[:5] for o in out]}
    client.publish(TOPIC, json.dumps(payload), qos=0, retain=False)
    time.sleep(0.5)

Create a requirements file:

# file: requirements.txt
onnxruntime
numpy
paho-mqtt

Containerfile (Dockerfile syntax works with Podman):

# file: Containerfile
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1
RUN useradd -m app
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py model.onnx ./
USER app
CMD ["python", "app.py"]

Build and run:

podman build -t edge-infer:0.1 -f Containerfile .
podman run --rm \
  --name edge-infer \
  -e MODEL_PATH=/app/model.onnx \
  -e MQTT_BROKER=localhost \
  -e MQTT_TOPIC=edge/infer \
  --cap-drop=all --pids-limit=256 --read-only \
  -v "$(pwd)"/model.onnx:/app/model.onnx:ro,Z \
  edge-infer:0.1

Notes:

  • The :Z mount label helps on SELinux systems.

  • Drop capabilities and run read-only to harden by default.

  • If you need a writable temp dir, add -v /tmp:/tmp:rw,Z.


3) Decouple I/O with MQTT (simple, robust messaging)

Start a local broker:

sudo systemctl enable --now mosquitto

Watch inference messages:

mosquitto_sub -t 'edge/infer' -v

Publish a test message:

mosquitto_pub -t 'edge/infer' -m '{"ping":true}'

Why MQTT at the edge:

  • Decouples sensors, inference, and actuators

  • Lightweight, tolerant of lossy networks

  • Easy to bridge upstream later (site-to-cloud)


4) Make it survive reboots (systemd + Podman)

Create a managed container and generate a systemd unit:

# Create a reusable container config
podman create \
  --name edge-infer \
  -e MODEL_PATH=/app/model.onnx \
  -e MQTT_BROKER=localhost \
  -e MQTT_TOPIC=edge/infer \
  --cap-drop=all --pids-limit=256 --read-only \
  -v "$(pwd)"/model.onnx:/app/model.onnx:ro,Z \
  edge-infer:0.1

# Generate a unit file
podman generate systemd --name --files --new
# This writes container-edge-infer.service in the current dir
sudo mv container-edge-infer.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now container-edge-infer.service

Update policy:

  • Tag images semantically (edge-infer:0.1, 0.2).

  • For safe OTA, pull new image, then systemctl restart container-edge-infer.

  • Keep a “last known good” tag to roll back quickly.


5) Optimization and hardening tips

  • Choose the right runtime for your hardware:

    • CPU-only: ONNX Runtime with FP32 or INT8 quantization.
    • Intel iGPU/CPU: OpenVINO can accelerate many models.
    • NVIDIA dGPU/Jetson: TensorRT or NVIDIA’s Triton Inference Server container.
    • ARM SBCs: ONNX Runtime, TFLite; consider small architectures (MobileNet, EfficientNet-lite).
  • Quantize and prune:

    • Post-training INT8 often yields 2–4x speedups on CPU with minimal accuracy loss.
  • Constrain resources:

    • --memory 512m --cpus 1.0 --pids-limit 256 to avoid noisy-neighbor issues.
  • Observability:

    • Log latency/FPS to stdout (journald captures container logs).
    • Publish health/metrics to a separate MQTT topic (e.g., edge/metrics).
  • Security:

    • Run as non-root in containers (USER app).
    • --cap-drop=all --security-opt=no-new-privileges --read-only.
    • Expose only what you need; prefer localhost brokers on single-node setups.

Real-world edge patterns

  • People counting on an x86 mini PC:

    • CPU baseline with ONNX Runtime + INT8 quantization.
    • MQTT publishes per-minute counts; no images leave the device.
  • Wake-word detection on a Raspberry Pi:

    • Small audio model in ONNX/TFLite; publish “wake” events to MQTT; a second service records only after wake.
  • Quality inspection on a factory line:

    • Camera -> inference container -> MQTT “pass/fail” messages -> PLC/robot subscriber.
    • Buffer to disk when offline; forward summaries when the link returns.

Wrapping up

You now have a repeatable edge AI pattern: 1) Benchmark locally to set a latency/FPS baseline. 2) Containerize the model service with a minimal Python stack. 3) Use MQTT to decouple data producers and consumers. 4) Make it durable with systemd + Podman, and secure by default.

Call to action:

  • Pick one model and one sensor.

  • Run the benchmark script to set your baseline.

  • Containerize, wire up MQTT, and enable the systemd unit.

  • Iterate: quantize, constrain resources, and measure again.

Got further to go (GPU, VPU, or specialized accelerators)? Swap the runtime inside the same container pattern—your Bash, your packages, your process stay the same.