cd /news/computer-vision/wifi-off-model-running-what-broke-an… Β· home β€Ί topics β€Ί computer-vision β€Ί article
[ARTICLE Β· art-132671] src=dev.to β†— pub= topic=computer-vision verified=true sentiment=Β· neutral

WiFi off, model running: what broke (and what shipped) building YOLO26 on MLX

A two-person team built SENTINEL, a single-file, fully offline on-device posture attention map that runs YOLO26n at roughly 16 FPS at 720p on an M4 Mac with WiFi physically disabled. Engineer Igor Eduardo and field specialist Lexi Armstrong documented three failure modes encountered during the seven-day webAI YOLO26 MLX Build Challenge: a SIGTRAP crash in OpenCV 4.13's AVFoundation capture when frames are grabbed off the main thread on macOS 26, a silent hang caused by lazy MLX proxies in yolo-mlx's Boxes that deadlock when materialized in a multi-threaded pipeline, and a fine-tuning run on labeled posture data that failed to converge and was dropped. The shipped version uses released yolo26n weights plus a simple bounding-box geometry heuristic for standing, sitting, and lying classification.

by read6 min views1 publishedSep 17, 2026

By Igor Eduardo Β· Austin, TX Β· with Lexi Armstrong

Site: igoreduardo.com Β· Repo: github.com/nomad-link-id/sentinel-mlx Β· Demo: youtu.be/c2v5Mdg5fpw

This is a build note from the webAI YOLO26 MLX Build Challenge (May 2026), not a product pitch. We shipped a single-file, on-device posture attention map that runs with WiFi physically off. The useful part for other builders is what failed first.

Seven days. Two-person build: Lexi Armstrong owned the problem and operational constraints; I owned the engineering.

What shipped uses yolo26n with released weights as-is, plus a deliberately simple bounding-box geometry heuristic for posture β€” standing / sitting / lying. Single-file Python (~200 LOC), single-thread synchronous loop, ~16 FPS at 720p on M4 / 32 GB / macOS 26.3 / Python 3.14, ~45 ms inference per frame.

An earlier attempt to fine-tune yolo26n on labeled posture data did not converge and was dropped.

We're writing this up because the failure modes are probably more useful than the demo β€” specifically the macOS 26 AVFoundation crash, the lazy-eval gotcha in yolo-mlx's Boxes, and the training collapse. And because the part that turned a demo into something a responder might actually trust came from the field side, not the code.

SENTINEL is the meeting of two views of the same problem. Lexi saw it from the field β€” denied-comms / industrial security / operational-edge constraints, including what "zero network egress" has to mean when operational security depends on it. I saw it from healthtech β€” production-class triage and patient-flow systems in extended pilot with tier-1 hospitals in SΓ£o Paulo. The core problem of who needs attention first when there are too many patients and not enough hands shows up in mass-casualty triage too, at different scale and stakes.

Concretely, the field side drove:

Honest division of labor: Lexi made sure it was worth building and aimed at the right target; I built it.

The initial architecture was a FastAPI backend exposing a WebSocket stream to a browser overlay, with camera capture in a ThreadPoolExecutor. Two hard blockers killed it.

With OpenCV 4.13's AVFoundation backend, capturing frames from a non-main thread crashed reliably with SIGTRAP. The trace pointed at:

cv2.abi3.so -> CaptureDelegate captureOutput -> CFRelease

This looks like a CFRelease reference-counting issue when AVCaptureSession is owned outside the main thread on macOS 26. We didn't patch around it β€” rewriting the capture stack wasn't worth the risk in a 7-day window. Flagging it because anyone building on this stack with a worker-thread capture pattern is going to hit it.

Boxes proxies The harder one to isolate. Benchmark showed clean FPS. Warmup completed. Model loaded. But when the WebSocket handler called detector.predict() on a real camera frame, it hung silently. Every time.

Diagnostic path:

dets = []) β€” video appeared immediately. Camera, WebSocket, frontend all confirmed working.predictor.py: _predict start, mx.eval done, returning results all appeared.predict() in detector.py: BEFORE PREDICT appeared, AFTER PREDICT never did. The hang was not in MLX inference itself β€” it was in the box-iteration loop after the model returned:

for box in results[0].boxes:
    x1, y1, x2, y2 = [int(v) for v in box.xyxy[0].tolist()]  # hangs here

Root cause hypothesis: yolo-mlx's Boxes returns lazy MLX proxies for .xyxy, .conf, .cls. They aren't evaluated during inference β€” they're deferred. Calling .tolist() or indexing them triggers a secondary mx.eval() that deadlocked in the multi-thread setup. Warmup used np.zeros (zero detections), so the box loop never ran during warmup. Real frames produced detections, the loop ran for the first time, the lazy eval fired, and the pipeline froze.

Workaround β€” force-materialize before iterating:

import mlx.core as mx
import numpy as np

boxes = results[0].boxes
mx.eval(boxes.xyxy); mx.eval(boxes.conf); mx.eval(boxes.cls)
xyxy = np.array(boxes.xyxy)
conf = np.array(boxes.conf)
cls = np.array(boxes.cls)

for i in range(len(xyxy)):
    x1, y1, x2, y2 = [int(v) for v in xyxy[i]]

No .tolist() on MLX proxies inside loops. Convert to NumPy once, then iterate. Warmup with synthetic zero-detection frames does not exercise the box-iteration path, so this bug is invisible until a real subject enters the frame β€” worth a doc note or an explicit .materialize() helper on Boxes.

Around day four we made a call. The architecture being hardened solved for production complexity not needed for a 7-day demo. We forked the official Yolo26-mlx challenge starter, deleted everything around the inference call, and rewrote it as a single synchronous loop on the main thread:

while True:
    ok, frame = cap.read()
    detections = model(frame)
    overlay = render_sentinel_ui(frame, detections)
    cv2.imshow("SENTINEL", overlay)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

That sidesteps the AVFoundation issue (capture on main thread) and the lazy-eval issue (no async / threading). ~16 FPS at 720p on M4 once settled. That's what shipped to the public repo.

In parallel, we tried fine-tuning yolo26n on labeled posture data β€” a Roboflow posture-classification dataset (person_lying / person_sitting / person_standing). Training loss collapsed to near-zero by epoch 2; mAP stuck at 0.0 for the remaining epochs. We didn't isolate the root cause in the time available β€” most likely a label-format mismatch or normalization gap β€” but those debugging cycles weren't available with AVFoundation and lazy-eval also live.

Noting this because "trained posture head" appears on the roadmap, and we want to be explicit that it's an honest open problem β€” not something we skipped by choice.

The shipped version does not use a trained posture classifier, and SENTINEL does not make a clinical triage decision. It uses released yolo26n.npz weights as-is. The layer on top is a deterministic posture heuristic on bounding-box geometry. It reports what the camera can actually see β€” body posture β€” not medical severity.

class == 0 (person), conf β‰₯ 0.40 aspect = bbox_height / bbox_width aspect > 1.6 β†’ standing1.0 ≀ aspect ≀ 1.6 β†’ sitting / slumpedaspect < 1.0 β†’ lying down Why posture, not severity: a camera cannot see a pulse, internal bleeding, or a blocked airway. The honest output is "who is upright, who is down, and for how long" β€” a visual cue that helps a responder decide where to look first. The human triages.

Where the heuristic fails (honest):

"We can write the rule on a napkin" was the right call for a 7-day safety-relevant demo β€” but it's a starting point, not the answer.

Worked: yolo-mlx 0.3.1 API; MLX Metal backend on Apple Silicon; starter repo structure (pivot took an afternoon); macOS 26 + Python 3.14 + M4 once threading was off the table.

Friction:

??. Form factor: laptop demo β‰  product. Realistic V1 path we sketched β€” Vision Pro for prototype, field-grade waveguide for first-responder pilot β€” both keep inference on-device. Platforms that move compute to a paired puck reintroduce a network leg that weakens the zero-egress story for this use case.

We didn't build SENTINEL to chase a market β€” we built it because the environments where attention allocation matters most are often the ones where the network isn't there. Before this is a product: trained posture classifier, a real labeled dataset, and validation with a real responder who either uses the map or ignores it. No projections β€” we haven't earned them yet.

If the AVFoundation crash, the lazy-eval workaround, or the training-collapse note is useful to YOLO26-MLX docs, happy to write more on any section.

β€” Igor Eduardo (igoreduardo.com) & Lexi Armstrong

Repo: nomad-link-id/sentinel-mlx Β· Demo: youtu.be/c2v5Mdg5fpw

Disclosure: drafted with AI assistance from build notes; technical claims and wording owned by the authors.

── more in #computer-vision 4 stories Β· sorted by recency
── more on @igor eduardo 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/wifi-off-model-runni…] indexed:0 read:6min 2026-09-17 Β· β€”