docs/en/integrations/hailo.md
!!! warning "Not a direct Ultralytics export format"
Hailo HEF is **not currently supported** as a direct Ultralytics `model.export(format="hailo")` target. The workflow below is a manual fallback that exports to ONNX first, then uses Hailo's external Dataflow Compiler toolchain to produce a `.hef` file. A seamless Ultralytics workflow should expose Hailo through the same Python and CLI export API as other hardware formats.
The Hailo toolchain uses HEF files for embedded platforms including the Raspberry Pi AI Kit and AI HAT+, industrial cameras, edge gateways, and AI PCs.
This guide walks through exporting selected Ultralytics YOLO models to Hailo's HEF (Hailo Executable Format) using the Hailo Dataflow Compiler (DFC) SDK. The workflow starts from a YOLO .pt model, exports to ONNX, compiles with Hailo tools, and produces a .hef file ready for supported Hailo accelerators.
HEF is the compiled artifact consumed by HailoRT on Hailo target devices. Use this manual guide only when your deployment hardware specifically requires Hailo HEF before direct Ultralytics Hailo export support is available.
HEF is similar in deployment role to hardware-specific formats such as RKNN for Rockchip NPUs, IMX500 for Raspberry Pi AI Cameras, and Qualcomm QNN for Snapdragon NPUs, but it is not currently generated directly by Ultralytics.
This workflow is relevant when you need:
HEF is a hardware-specific executable generated by the Hailo Dataflow Compiler. It contains the quantized model graph, memory allocation, scheduling, and optional post-processing configured for a target Hailo architecture. Unlike standard YOLO Export mode formats that are produced directly by model.export(format=...), HEF compilation currently uses a two-stage flow:
The full workflow expands into the following pipeline:
YOLO (.pt) -> ONNX -> HAR (parse) -> HAR (optimize/quantize) -> HEF (compile)
.alls) with normalization and post-processing directivesThe current manual example focuses on YOLO11 object detection because the Hailo model script and post-processing configuration are detection-head specific. A future direct model.export(format="hailo") implementation should make Hailo export feel like every other Ultralytics export format, with task support gated by the model head and Hailo compiler compatibility rather than by external workflow steps.
| Task | Direct Hailo Export Target | Notes |
|---|---|---|
| Object Detection | ✅ Primary target | YOLOv8, YOLO11, and YOLO26 detection should be the first direct-export path. |
| Instance Segmentation | ✅ Target | YOLOv8, YOLO11, and YOLO26 segmentation require task-specific mask output handling and validation. |
| Semantic Segmentation | ⚠️ Validate | YOLO26 semantic segmentation needs a dedicated compiler and output validation path. |
| Pose Estimation | ⚠️ Validate | Pose requires keypoint output handling beyond the detection NMS path. |
| OBB Detection | ⚠️ Validate | OBB requires rotated-box output handling beyond the standard detection NMS path. |
| Classification | ⚠️ Validate | Classification has a simpler output head, but still needs Hailo compile and runtime validation. |
Until direct Hailo export is implemented in Ultralytics, only the manual ONNX-to-HEF workflow below is documented.
Direct Hailo export must account for Hailo's hardware and SDK generation split:
This version split affects compiler APIs, supported architectures, generated HEF compatibility, and which hw_arch values a direct exporter should expose. Task support on one Hailo hardware generation should not be treated as support on another without validating the target DFC version and hw_arch.
Hailo export compatibility depends on the model head, input image size, class count, Hailo architecture, generated model script (.alls), and post-processing configuration. Static configs are not universal templates. For example, an NMS JSON created for a COCO 80-class YOLO11n model is not correct for a custom 3-class model or for a different fixed imgsz.
| Scope | Expected Support | Notes |
|---|---|---|
| YOLOv8 / YOLO11 detection | ✅ Good | Shared decoupled detection head; .alls directives, end nodes, and NMS config still need to match the exported graph and fixed imgsz. |
| Custom YOLOv8 / YOLO11 detection | ✅ Possible | Requires per-model NMS configuration generated from class count, strides, and detection-head layout; static JSON will not match. |
| YOLO26 detection | ✅ Target | NMS-free architecture needs a separate compiler/post-processing path; do not reuse the YOLO11/YOLOv8 NMS workflow below for YOLO26. |
| YOLO26 instance segmentation | ✅ Target | Needs YOLO26 segmentation-specific mask output handling and accuracy validation. |
| YOLO26 semantic, pose, OBB, classification | ⚠️ Research | These tasks need dedicated compiler and runtime validation before they can be advertised as directly supported. |
| Dynamic or arbitrary image sizes | ❌ Not supported | Hailo compilation uses a fixed input shape; .alls and post-processing settings must match the exported imgsz. |
pip install ultralytics
The Hailo DFC is required for parsing, optimization, and compilation. Download the Python wheel from the Hailo Developer Zone (free registration required) and install it:
pip install /path/to/hailo_sdk_client-*.whl
!!! note
The Hailo DFC SDK requires a **Linux x86_64** machine. Export and compilation cannot be performed on ARM devices such as Raspberry Pi. Copy the resulting `.hef` file to your Hailo-powered device for deployment with HailoRT.
The script below compiles a YOLO11n detection model from .pt to .hef at a fixed 640-pixel input size. It exports to ONNX using Ultralytics, then compiles with Hailo DFC using COCO128 as a small calibration dataset.
Before running the script, provide a Hailo NMS JSON that matches the exact YOLO11n export graph, class count, strides, and fixed input size. Reuse this script as a known YOLO11n starting point; custom models need matching end nodes, .alls directives, and NMS settings.
!!! note "YOLO26 uses a different Hailo path"
YOLO26 models are NMS-free. A direct Ultralytics Hailo exporter needs a dedicated YOLO26 compile and post-processing path for detection or instance segmentation instead of the YOLO11 NMS example below.
!!! example "Full Pipeline"
```python
import random
import numpy as np
from hailo_sdk_client import ClientRunner
from PIL import Image
from ultralytics import YOLO
from ultralytics.data.utils import check_det_dataset
from ultralytics.utils import DATASETS_DIR
# Configuration
MODEL = "yolo11n"
HW_ARCH = "hailo8" # hailo8 | hailo8l | hailo15h
IMGSZ = 640
CALIB_IMAGES = 128
NMS_CONFIG = "yolo11n_nms_config.json" # Download or generate for your exact model.
# YOLO11 detection head end nodes. See "Supported Models and End Nodes" for YOLOv8 and other families.
END_NODES = [
"/model.23/cv2.0/cv2.0.2/Conv",
"/model.23/cv3.0/cv3.0.2/Conv",
"/model.23/cv2.1/cv2.1.2/Conv",
"/model.23/cv3.1/cv3.1.2/Conv",
"/model.23/cv2.2/cv2.2.2/Conv",
"/model.23/cv3.2/cv3.2.2/Conv",
]
# Step 1: Export to ONNX
model = YOLO(f"{MODEL}.pt")
model.export(format="onnx", imgsz=IMGSZ, opset=11) # creates an ONNX file named after MODEL
# Step 2: Parse ONNX with Hailo DFC
# The DFC prints the detected end nodes after parsing; use them if unsure.
runner = ClientRunner(hw_arch=HW_ARCH)
runner.translate_onnx_model(f"{MODEL}.onnx", end_node_names=END_NODES)
# Step 3: Load model script (normalization + HailoRT NMS)
# The conv layer names are generated by DFC and can change for other model sizes/families.
model_script = (
"normalization1 = normalization([0.0, 0.0, 0.0], [255.0, 255.0, 255.0])\n"
"change_output_activation(conv54, sigmoid)\n"
"change_output_activation(conv65, sigmoid)\n"
"change_output_activation(conv80, sigmoid)\n"
f'nms_postprocess("{NMS_CONFIG}", meta_arch=yolov8, engine=cpu)\n'
"allocator_param(width_splitter_defuse=disabled)"
)
runner.load_model_script(model_script)
# Step 4: Build calibration dataset (auto-downloads COCO128)
check_det_dataset("coco128.yaml")
calib_dir = DATASETS_DIR / "coco128" / "images" / "train2017"
image_files = list(calib_dir.glob("*.jpg")) + list(calib_dir.glob("*.png"))
if not image_files:
raise FileNotFoundError(f"No calibration images found in {calib_dir}")
calibset = np.zeros((CALIB_IMAGES, IMGSZ, IMGSZ, 3), dtype=np.float32)
for i in range(CALIB_IMAGES):
img = Image.open(random.choice(image_files)).convert("RGB").resize((IMGSZ, IMGSZ))
calibset[i] = np.array(img, dtype=np.float32)
# Step 5: Optimize and quantize
runner.optimize(calibset)
runner.save_har(f"{MODEL}.o.har") # optional intermediate HAR
# Step 6: Compile to HEF
hef = runner.compile()
with open(f"{MODEL}.hef", "wb") as f:
f.write(hef)
print(f"Compiled HEF saved to: {MODEL}.hef")
```
The resulting HEF file, such as yolo11n.hef, is ready to deploy on a compatible Hailo device. If you are compiling for Raspberry Pi AI Kit, set HW_ARCH = "hailo8l" before running the compile step.
Ultralytics exports your trained model to ONNX format, which the Hailo DFC ingests as input. Set opset=11 for broad DFC compatibility.
from ultralytics import YOLO
MODEL = "yolo11n"
model = YOLO(f"{MODEL}.pt")
model.export(format="onnx", imgsz=640, opset=11)
The translate_onnx_model call converts the ONNX graph into Hailo's intermediate HAR representation. The end_node_names list tells the DFC where to cut the graph before NMS so Hailo can attach its own hardware post-processing.
from hailo_sdk_client import ClientRunner
runner = ClientRunner(hw_arch="hailo8")
runner.translate_onnx_model(f"{MODEL}.onnx", end_node_names=END_NODES)
!!! tip "Finding end nodes"
The DFC prints a suggestion after parsing:
```
[info] In order to use HailoRT post-processing capabilities, these end node names should be used: ...
```
Copy those node names if you are unsure which ones to use, or if you are working with a custom or less common architecture.
The model script (.alls) configures input normalization, output activation, and NMS post-processing. The meta_arch=yolov8 setting applies to both YOLOv8 and YOLO11 since they share the same detection head layout.
MODEL = "yolo11n"
NMS_CONFIG = "yolo11n_nms_config.json"
model_script = (
"normalization1 = normalization([0.0, 0.0, 0.0], [255.0, 255.0, 255.0])\n"
"change_output_activation(conv54, sigmoid)\n"
"change_output_activation(conv65, sigmoid)\n"
"change_output_activation(conv80, sigmoid)\n"
f'nms_postprocess("{NMS_CONFIG}", meta_arch=yolov8, engine=cpu)\n'
"allocator_param(width_splitter_defuse=disabled)"
)
runner.load_model_script(model_script)
!!! note
The `change_output_activation` layer names (`conv54`, `conv65`, `conv80`) are assigned by the DFC during parsing and are **model-specific**. If you are compiling a different model size or architecture, check the DFC output for the correct names or generate the `.alls` directives from the exported graph.
The `NMS_CONFIG` file is also model-specific. Use a config that matches your exported model exactly.
`engine=cpu` runs NMS through HailoRT on the host CPU. Use `engine=nn_core` only for model/script combinations that Hailo documents as supported by the target hardware and SDK version.
Remove the `nms_postprocess` line if you prefer to run NMS fully in your application code. If you do this, update the inference parser because the HEF will output raw detection-head tensors instead of grouped NMS detections.
INT8 quantization requires a representative set of images. The script below uses COCO128, which Ultralytics downloads automatically via check_det_dataset:
from ultralytics.data.utils import check_det_dataset
from ultralytics.utils import DATASETS_DIR
check_det_dataset("coco128.yaml") # downloads to DATASETS_DIR if not present
calib_dir = DATASETS_DIR / "coco128" / "images" / "train2017"
!!! tip
Use at least 64 images for calibration. More images generally improve quantization quality. For best results, use images from your deployment domain rather than COCO128.
runner.optimize(calibset)
runner.save_har(f"{MODEL}.o.har") # optional intermediate checkpoint
This step applies quantization-aware fine-tuning and layer noise analysis. A GPU is strongly recommended; without one, this step can take several hours.
hef = runner.compile()
with open(f"{MODEL}.hef", "wb") as f:
f.write(hef)
For detection models, end_node_names identifies the ONNX detection-head outputs that Hailo should compile before attaching its NMS post-processing. These names vary by architecture and can change when the exported graph changes.
The end-node examples below apply to YOLOv8 and YOLO11 detection models that use Hailo's YOLOv8-style NMS post-processing. YOLO26 is NMS-free and does not use this YOLO11 NMS configuration.
YOLO11 and YOLOv8 share the same decoupled detection head. The layer index differs by one between the two families:
| Model Family | Detection Head Layer | End Node Pattern |
|---|---|---|
| YOLO11 (all) | model.23 | /model.23/cv2.0/cv2.0.2/Conv (6 nodes) |
| YOLOv8 (all) | model.22 | /model.22/cv2.0/cv2.0.2/Conv (6 nodes) |
YOLO11 end nodes (all sizes: n, s, m, l, x):
END_NODES = [
"/model.23/cv2.0/cv2.0.2/Conv",
"/model.23/cv3.0/cv3.0.2/Conv",
"/model.23/cv2.1/cv2.1.2/Conv",
"/model.23/cv3.1/cv3.1.2/Conv",
"/model.23/cv2.2/cv2.2.2/Conv",
"/model.23/cv3.2/cv3.2.2/Conv",
]
YOLOv8 end nodes (all sizes: n, s, m, l, x):
END_NODES = [
"/model.22/cv2.0/cv2.0.2/Conv",
"/model.22/cv3.0/cv3.0.2/Conv",
"/model.22/cv2.1/cv2.1.2/Conv",
"/model.22/cv3.1/cv3.1.2/Conv",
"/model.22/cv2.2/cv2.2.2/Conv",
"/model.22/cv3.2/cv3.2.2/Conv",
]
For other detection architectures, run the parse step without end_node_names first, read the suggested nodes from the DFC log output, then re-run with those nodes:
# First pass: let the DFC suggest end nodes
runner = ClientRunner(hw_arch=HW_ARCH)
runner.translate_onnx_model(f"{MODEL}.onnx")
# Check the printed log for: "[info] In order to use HailoRT post-processing..."
For direct Ultralytics support, these .alls directives and post-processing settings should be generated or selected by the exporter instead of requiring users to assemble them manually.
| Architecture | Device | Peak Compute (Vendor Spec) | Common Use Case |
|---|---|---|---|
hailo8 | Hailo-8 | 26 TOPS | Hailo accelerator card |
hailo8l | Hailo-8L | 13 TOPS | Raspberry Pi AI Kit |
hailo15h | Hailo-15H | 20 TOPS | Hailo-15 target devices |
Set HW_ARCH in the script to match your target device before compiling.
Once you have the .hef file, copy it to your Hailo-powered device and run inference using the HailoRT Python API (hailo_platform package). Unlike the DFC export steps, inference runs directly on the edge device.
!!! note
The inference code below runs **on the Hailo-powered device** (e.g. Raspberry Pi + AI Kit), not on the x86 machine used for compilation.
On the target device, install HailoRT and the Python bindings. For Raspberry Pi AI Kit and AI HAT+ users, the official Raspberry Pi AI software guide installs HailoRT, the device driver, and Python bindings with:
sudo apt install dkms
sudo apt install hailo-all
For non-Raspberry Pi Hailo devices, install the HailoRT package that matches your device, driver, and SDK version from the Hailo Developer Zone.
AI HAT+ 2 devices use a different Raspberry Pi package (hailo-h10-all) and Hailo-10H workflow. Follow the Raspberry Pi AI software guide for that hardware generation.
Before running Python inference, confirm the Hailo device is recognized:
hailortcli fw-control identify
You should see the device type, firmware version, and serial number printed.
The script below runs object detection on a single image using the compiled HEF file and the hailo_platform Python API. It handles preprocessing, inference, and drawing bounding boxes from the HailoRT NMS output.
!!! example "Inference Script"
```python
import numpy as np
from hailo_platform import (
HEF,
ConfigureParams,
FormatType,
HailoStreamInterface,
InferVStreams,
InputVStreamParams,
OutputVStreamParams,
VDevice,
)
from PIL import Image, ImageDraw
# Configuration
MODEL = "yolo11n"
HEF_PATH = f"{MODEL}.hef" # path to your compiled HEF file
SOURCE = "bus.jpg" # image path
IMGSZ = 640
CONF = 0.25
COCO_NAMES = [
"person",
"bicycle",
"car",
"motorcycle",
"airplane",
"bus",
"train",
"truck",
"boat",
"traffic light",
"fire hydrant",
"stop sign",
"parking meter",
"bench",
"bird",
"cat",
"dog",
"horse",
"sheep",
"cow",
"elephant",
"bear",
"zebra",
"giraffe",
"backpack",
"umbrella",
"handbag",
"tie",
"suitcase",
"frisbee",
"skis",
"snowboard",
"sports ball",
"kite",
"baseball bat",
"baseball glove",
"skateboard",
"surfboard",
"tennis racket",
"bottle",
"wine glass",
"cup",
"fork",
"knife",
"spoon",
"bowl",
"banana",
"apple",
"sandwich",
"orange",
"broccoli",
"carrot",
"hot dog",
"pizza",
"donut",
"cake",
"chair",
"couch",
"potted plant",
"bed",
"dining table",
"toilet",
"tv",
"laptop",
"mouse",
"remote",
"keyboard",
"cell phone",
"microwave",
"oven",
"toaster",
"sink",
"refrigerator",
"book",
"clock",
"vase",
"scissors",
"teddy bear",
"hair drier",
"toothbrush",
]
# Load HEF and connect to device
hef = HEF(HEF_PATH)
params = VDevice.create_params()
target = VDevice(params)
configure_params = ConfigureParams.create_from_hef(hef, interface=HailoStreamInterface.PCIe)
network_groups = target.configure(hef, configure_params)
network_group = network_groups[0]
network_group_params = network_group.create_params()
# Setup I/O virtual streams
input_vstreams_params = InputVStreamParams.make(network_group, quantized=False, format_type=FormatType.FLOAT32)
output_vstreams_params = OutputVStreamParams.make(network_group, quantized=False, format_type=FormatType.FLOAT32)
# Preprocess
orig = Image.open(SOURCE).convert("RGB")
ow, oh = orig.size
resized = orig.resize((IMGSZ, IMGSZ))
input_data = np.expand_dims(np.array(resized, dtype=np.float32), axis=0) # (1,640,640,3)
input_name = hef.get_input_vstream_infos()[0].name
# Inference
with InferVStreams(network_group, input_vstreams_params, output_vstreams_params) as pipeline:
with network_group.activate(network_group_params):
pipeline.send({input_name: input_data})
raw = pipeline.recv()
# Parse HailoRT NMS output and draw results
# When compiled with nms_postprocess the HEF outputs detections grouped by
# class: shape (batch, num_classes, max_dets, 5) where 5 = [y1,x1,y2,x2,score]
draw = ImageDraw.Draw(orig)
output_key = next(iter(raw.keys()))
batch_dets = raw[output_key][0] # shape: (num_classes, max_dets, 5)
for cls_idx, cls_dets in enumerate(batch_dets):
for det in cls_dets:
score = float(det[4])
if score < CONF:
continue
y1, x1, y2, x2 = det[:4]
# Scale from model coords (0-640) back to original image size
x1 = int(x1 * ow / IMGSZ)
y1 = int(y1 * oh / IMGSZ)
x2 = int(x2 * ow / IMGSZ)
y2 = int(y2 * oh / IMGSZ)
label = f"{COCO_NAMES[cls_idx]} {score:.2f}"
draw.rectangle([x1, y1, x2, y2], outline="red", width=2)
draw.text((x1 + 2, y1 + 2), label, fill="red")
orig.save("output.jpg")
print("Saved output.jpg")
```
!!! tip
The detection output format assumes the HEF was compiled with `nms_postprocess` in the `.alls` script. If you compiled **without** NMS, the raw outputs are the 6 detection head tensors and you must run NMS in your application separately.
The Raspberry Pi AI Kit and 13 TOPS AI HAT+ use Hailo-8L. To use either device:
HW_ARCH = "hailo8l" before compiling your HEF on the x86 machine..hef to your Raspberry Pi.For camera-based inference on Raspberry Pi, the picamera2 Hailo examples provide ready-to-use scripts for live detection with the Camera Module. You can also compare Raspberry Pi deployment paths in the Coral Edge TPU on Raspberry Pi guide and Sony IMX500 integration guide.
For high-throughput video pipelines, TAPPAS provides GStreamer elements that stream video through the Hailo chip in real time:
MODEL=yolo11n
gst-launch-1.0 filesrc location=video.mp4 ! decodebin ! \
hailonet hef-path=${MODEL}.hef ! \
hailofilter function-name=yolov8 ! \
hailooverlay ! autovideosink
See the TAPPAS documentation for full pipeline configuration options.
This guide covered the complete workflow to export Ultralytics YOLO detection models to Hailo HEF format:
model.export(format="onnx"))..hef file ready for Hailo-8, Hailo-8L, or Hailo-15.For further details, see the Hailo Developer Zone and Hailo documentation. For other Ultralytics export targets, see the related ONNX, OpenVINO, TensorRT, NCNN, TFLite Edge TPU, RKNN, Sony IMX500, and Qualcomm QNN guides. To compare exported model speed and accuracy across formats, use Benchmark mode. For the full list of formats and options, visit the Export mode documentation and the integrations guide page.
The Hailo DFC supports Hailo-8 (hailo8), Hailo-8L (hailo8l), and Hailo-15H (hailo15h). See the Supported Hardware Architectures table for the matching HW_ARCH value.
This guide focuses on detection models. See Supported Tasks for task-level scope, Compatibility Notes for model compatibility limits, and Supported Models and End Nodes for YOLO11 and YOLOv8 end-node examples.
meta_arch=yolov8 for YOLO11?YOLO11 uses the same decoupled detection head architecture as YOLOv8. The Hailo DFC uses meta_arch=yolov8 for NMS configuration for both model families.
A GPU is strongly recommended for the quantization-aware fine-tuning in runner.optimize(). Without one, the process still works but is significantly slower (several hours vs. about 10-20 minutes with a GPU).
Run runner.translate_onnx_model(...) without specifying end_node_names, then use the suggested detection-head nodes printed by the DFC. See Other Architectures for the example command.
The Hailo DFC SDK Python wheel is available from the Hailo Developer Zone. For a direct Ultralytics Hailo exporter, the model script and post-processing configuration should be generated or selected inside the export workflow.