NewChunking Qwen3.5's gated DeltaNet for 1.5x faster prefill on Apple Silicon
BLOG2026-07-14 · engineering · research · 5 MIN

How to run YOLO11 on the Coral Edge TPU with 3x less quantization loss

Rayan

The Coral Edge TPU is a strange piece of hardware to love in 2026. It costs about $60, draws 2W, and runs int8 TFLite graphs through a compiler that has not seen a release in years. It is also everywhere: bolted onto Raspberry Pis in smart cameras, screwed into M.2 slots on industrial gateways, and sitting in the parts drawer of every robotics team that ever prototyped on one.

Getting a modern detection model onto it is harder than it should be, and the standard path costs more accuracy than it should. This post walks through the whole pipeline we ship in Prysm: why the compiler rejects YOLO11 as exported, where the graph has to be cut, and what each choice costs in accuracy.

Results

YOLO11n at 416x416, 1,000 COCO val images:

mAP@0.5:0.95vs fp32invoke latencyops on TPU
fp32 ONNX reference (CPU)0.3540
Prysm raw-head int80.3445-2.7%44.6 ms21/484
Ultralytics full-graph export0.3270-7.6%39.6 ms44/478

The standard export loses 2.7 mAP points to quantization. Cutting the model at the raw detection heads and decoding on the host loses 0.95. That is nearly 3x less quantization damage for the same model on the same chip, at effectively the same speed.1

The rest of this post is how you get there.

Why does edgetpu_compiler fail on YOLO11 with an opcode error?

edgetpu_compiler v16 is the last released version, and its TFLite parser predates several op versions that modern exporters emit. YOLO11's backbone uses grouped convolutions, which TFLite encodes as CONV_2D version 6. Feed that to the compiler and it exits before mapping a single op:

ERROR: Didn't find op for builtin opcode 'CONV_2D' version '6'

Two conversion choices get you past the wall:

onnx2tf -i yolo11n_headless.onnx -dgc -b 1

First, onnx2tf has to run with its tf_converter backend rather than the default flatbuffer path, because the direct flatbuffer emitter writes op codes the v16 parser cannot read at all. Second, -dgc splits every grouped convolution into plain convolutions the old parser understands. The split makes the graph a little larger and a little slower, and it is the only way to get the model through the compiler.

Why do most YOLO ops run on the CPU instead of the Edge TPU?

The Edge TPU runtime has a blunt execution model: the compiler maps one contiguous subgraph from the top of the model, and everything after the first unsupported op runs on the host CPU forever. There is no second chance later in the graph.

For a YOLO11 export, the mapping is thin on both sides: Ultralytics' export puts 44 of 478 ops on the TPU, ours 21 of 484. Neither pipeline gets most of the graph onto the accelerator. The invoke time does not follow the op count, though: the chip runs its mapped stem and streams the boundary feature maps back over USB, and that round-trip is most of it. The host tail carries most of the ops but runs in 3.5ms of the raw-head export's 44.6ms; the rest is the accelerator and its USB transfer. The 44.6ms vs 39.6ms gap between the two exports tracks the boundary size, not the op split: our cut streams 519KB of feature maps back where Ultralytics' streams 346KB, and the extra USB drain accounts for it. On a Pi-class host the tail grows and both slow down together.

Which is exactly why the interesting variable is not speed. It is where quantization happens.

How much accuracy does int8 quantization cost YOLO?

Everything after the raw detection heads is arithmetic over wide dynamic ranges: a softmax over the box-distribution bins, box decoding against anchor grids, sigmoids over 8,400 candidate scores. Quantizing that arithmetic to int8 forces thousands of small values through 256 buckets chosen during calibration, and the boxes come out subtly wrong everywhere. That is the 2.7-point tax in the table. It does not show up as a crash or a warning. The model just gets quietly worse.

The fix is to refuse to quantize any of it. We cut the ONNX graph at the six raw head tensors, before decode:

# Three strides, two tensors each: box distributions (4 * reg_max
# channels) and class scores. Matched by shape, never by name;
# names differ across exporters, shapes do not.
heads = find_yolo_head_tensors(model)  # e.g. (1, 64, 52, 52), (1, 80, 52, 52), ...
onnx.utils.extract_model(src, dst, input_names, [h.name for h in heads])

The backbone and heads quantize cleanly; convolutions are what int8 calibration is good at. Decode runs on the host in fp32,2 driven by a small spec that ships with the model: strides, reg_max, class count, confidence and IoU thresholds. The Prysm runtime rematches the six outputs by shape at load time, so the same decode code serves this model on Coral today and on Hailo-8.

How to convert YOLO11 for the Edge TPU

End to end, four steps:

  1. Cut the ONNX graph at the six raw head tensors with onnx.utils.extract_model, as in the previous section. Write down the strides and thresholds you cut with; you need them again at inference.
  2. Convert to TensorFlow: onnx2tf -i yolo11n_headless.onnx -dgc -b 1.
  3. Quantize to int8 with the TFLite converter, calibrated on a few hundred representative images.
  4. Map it onto the TPU: edgetpu_compiler yolo11n_headless_int8.tflite.

At inference, the compiled model runs on the TPU and hands back the six raw head tensors. Your host code finishes in float: softmax over the box-distribution bins, box decode against the anchor grids, sigmoid on the class scores, NMS. Hand-rolled, that is a few hundred lines of numpy. Prysm packages the decode parameters alongside the compiled model, so any device can run it without the original export script.

Is the Coral Edge TPU still worth using?

A frozen compiler is a hard ceiling: v16 will never learn new op versions, so every model conversion becomes an exercise in emitting 2021-era TFLite. The single-subgraph rule caps how much of a detection model can ever reach the TPU. Within those walls, the accuracy is recoverable, and that is the part that matters for a fleet: the raw-head cut turns the Coral from a chip that quietly degrades your model into one that runs it 0.95 points off fp32 for 2W.

The cut itself is not Coral-specific. On Hailo-8 the tradeoff changes: its compiler can put the decode and even NMS on the chip, so the question stops being whether to cut and becomes where. We'll write about this next.

This pipeline ships in Prysm: prysm compile yolo11n.onnx --target coral runs the cut, the conversion, and the compiler, and the same command targets Hailo-8, Jetson, and Kria.

Footnotes

  1. Our first measurement said the opposite of the table above: 0.2922, three and a half points behind the standard export. The cause was in the evaluation, not the compiler: a deployed detector runs with a confidence threshold around 0.25, while mAP needs it near zero so the metric can integrate the full precision-recall curve. The baselines were scored at 0.001, our pipeline at its deployment 0.25, and that alone is worth about 5 mAP points. Scoring both sides at 0.001 flipped the result. It is an easy bug to ship anywhere a deploy config and an evaluation touch the same model.
  2. We also tried keeping the model's outputs in float32 (-oqd float32), expecting it to recover accuracy. The mAP did not move at four decimals. Dequantization is a linear rescale, and it does not matter whether it happens inside the graph or on the host.

Deploying models in the field? We should talk. hello@prysmlabs.ai