con un clic
ai-services
Hailo-optimized AI service patterns and best practices
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Hailo-optimized AI service patterns and best practices
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
| name | ai-services |
| description | Hailo-optimized AI service patterns and best practices |
This skill provides pragmatic patterns for AI service APIs that leverage Hailo-10H on Raspberry Pi 5.
Adopt existing standards: Don't invent new APIs. Use proven protocols and conventions:
GET /health or /api/healthBenefit: Works with existing client tools, familiar to users, reduces code to write and maintain.
These are personal projects and art installations—reuse over reinvention.
For Python-based services: Use isolated virtual environments in /opt/hailo-service-name/venv (see Raspberry Pi skill for detailed rationale and alternatives).
Why it matters for AI services:
/opt/hailo-service-name/ directoryhailo-ollama exception: Wraps Ollama binary (not Python); no venv needed.
The hailo-apps submodule provides 20+ AI applications across vision and GenAI domains. Each can be wrapped as a systemd service.
Architecture Note: Hailo-10H supports concurrent services — multiple models can run simultaneously. Design services to:
Purpose: Expose LLM inference via Ollama API as a systemd service
Pattern:
/api/tags, /api/chat, /api/generate)Available applications ready for service deployment:
Object Detection:
/api/detect endpoint with image uploadPose Estimation:
/api/pose endpoint returning joint coordinatesInstance Segmentation:
/api/segment with mask outputDepth Estimation:
/api/depth returning depth mapsOCR:
/api/ocr following Tesseract conventionsFace Recognition:
/api/faces with recognition resultsOther Vision:
curl -X POST http://localhost:8080/api/detect \
-F "image=@photo.jpg" \
-H "Content-Type: multipart/form-data"
# Returns COCO-format bboxes: [{"label": "person", "confidence": 0.95, "bbox": [x, y, w, h]}]
Pose Estimation services:
curl -X POST http://localhost:8081/api/pose \
-F "image=@person.jpg"
# Returns keypoints: [{"person_id": 0, "keypoints": [{"name": "nose", "x": 120, "y": 80}...]}]
Speech-to-Text services (Whisper API format):
curl -X POST http://localhost:8082/api/transcribe \
-F "audio=@speech.wav" \
-F "language=en"
# Returns: {"text": "Hello world", "language": "en", "duration": 2.5}
Text-to-Speech services (Piper TTS):
curl -X POST http://localhost:8083/api/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "voice": "en_US-amy-low"}' \
--output speech.wav
# Returns audio file (WAV format)
General principles for all services:
/api/health, /health/api/transcribe with audio uploadVoice Assistant:
/api/voice streaming endpointVision-Language Models:
/api/vlm with image + promptFor each AI capability:
Purpose: Queue-based inference for non-interactive workloads
Characteristics:
Ollama-based services (LLM):
curl http://localhost:11434/api/tags # Standard Ollama endpoint
Object Detection services:
curl -X POST http://localhost:8080/api/detect \
-F "image=@photo.jpg" \
-H "Content-Type: multipart/form-data"
# Returns COCO-format bboxes: [{"label": "person", "confidence": 0.95, "bbox": [x, y, w, h]}]
Pose Estimation services:
curl -X POST http://localhost:8081/api/pose \
-F "image=@person.jpg"
# Returns keypoints: [{"person_id": 0, "keypoints": [{"name": "nose", "x": 120, "y": 80}...]}]
Speech-to-Text services (Whisper API format):
curl -X POST http://localhost:8082/api/transcribe \
-F "audio=@speech.wav" \
-F "language=en"
# Returns: {"text": "Hello world", "language": "en", "duration": 2.5}
Text-to-Speech services (Piper TTS):
curl -X POST http://localhost:8083/api/speak \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "voice": "en_US-amy-low"}' \
--output speech.wav
# Returns audio file (WAV format)
General principles for all services:
/api/health, /health)Keep it simple:
{
"error": "Model not found",
"detail": "Available models: neural-chat, mistral"
}
No need for custom error codes if standard HTTP status codes work.
For long-running inference:
POST /api/infer
Body: {"model": "llama2", "prompt": "..."}
Response: {"job_id": "abc123", "status": "queued"}
GET /api/infer/abc123
Response: {"status": "complete", "result": "..."}
Persistent Loading (Preferred):
Graceful Unloading (When Needed):
/api/unload endpoint to release model memoryConcurrent Services:
# Default model storage
~hailo/.ollama/models/ # User home
/var/lib/ollama/models/ # System service
# Model structure
/var/lib/ollama/models/manifests/<namespace>/<model>:<tag>
/var/lib/ollama/models/blobs/ # Actual model files
Recommended pattern for persistent services:
# Service startup: Load model into memory (happens once)
# - Model remains resident until service stop or explicit unload
# - First inference after startup: ~10-30s (model loading)
# - Subsequent inferences: <1s (model already loaded)
# List loaded models
curl http://localhost:11434/api/tags
# Returns: {"models": [{"name": "llama2", "size": "4.1GB", "loaded": true}]}
# Inference (uses already-loaded model)
curl -X POST http://localhost:11434/api/chat -d '{"model": "llama2", "messages": [...]}'
# Fast response (model already in memory)
# Graceful unload (optional, for memory management)
curl -X POST http://localhost:11434/api/unload -d '{"model": "llama2"}'
# Releases memory; next inference will reload (slow)
# Health check includes model status
curl http://localhost:11434/api/health
# Returns: {"status": "ok", "models_loaded": ["llama2"], "memory_used_mb": 4200}
Design principle: Services should keep models loaded by default. Only unload when:
/api/unload endpointRecommended models for single-service deployment:
neural-chat (7B, ~4GB) - Good for dedicated LLM servicemistral (7B, ~4GB) - Faster inference, good general useorca-mini (3B, ~2GB) - Lowest memory, good for multi-service setupsFor concurrent multi-service scenarios:
orca-mini (2GB) + Whisper-tiny (512MB) + YOLO (512MB) = ~3GB totalfree -h and /api/health endpointsAvoid:
Total Pi 5 RAM: 8 GB
Reserved for OS: 1 GB
Available for services: ~7 GB
Ollama process overhead: ~300 MB
Model cache (llama2 7B): ~5 GB
Inference workspace: ~1 GB buffer
Action: Set MemoryLimit=6G in systemd unit to prevent swap thrashing.
Hailo-10 offloads: Tensor operations (inference)
CPU still handles: Tokenization, sampling, post-processing
Typical CPU load: 30-50% during active inference
Thermal idle: 35-45°C (with passive cooling)
Thermal active use: 50-65°C (with active fan)
Action: Monitor CPU usage in journalctl logs; if sustained >80%, reduce batch size or model size.
# Monitor service + system temperature
while true; do
echo "$(date): $(vcgencmd measure_temp)"
sleep 5
done
# Log thermal events to journalctl
# Modify service unit:
# ExecStartPost=/usr/local/bin/log-thermal-baseline.sh
Throttle recovery:
hailortcli fw-control identifyhailo:hailols -l /dev/hailo0 shows hailo grouphailosystemd-analyze verifysystemctl start + check logscurl http://localhost:PORT/api/healthcurl ... /api/chat with sample request (fast response)free -h shows adequate headroom if running concurrent servicescurl .../api/unload releases memorysystemctl statusjournalctl -u service-name shows activity# Python service code
import signal
def signal_handler(sig, frame):
print("Shutting down gracefully...")
# Save model state, close Hailo device connection
sys.exit(0)
signal.signal(signal.SIGTERM, signal_handler)
# systemd unit
TimeoutStopSec=30
KillSignal=SIGTERM
# In installer, verify prerequisites:
if ! command -v hailortcli &> /dev/null; then
echo "ERROR: Hailo driver not installed. Run system setup first."
exit 1
fi
if ! [ -e /dev/hailo0 ]; then
echo "ERROR: /dev/hailo0 not found. Reboot after hailo-h10-all install."
exit 1
fi
# Installer: Pre-download and load model to avoid cold-start delays
su - hailo -s /bin/bash -c "/usr/local/bin/ollama pull neural-chat"
# Service startup script loads model into memory:
# - systemd ExecStartPost can trigger initial model load
# - Or service startup code loads default model
# - Subsequent requests use already-loaded model (fast)
# Example multi-service deployment on Pi 5 (8GB RAM):
# - hailo-ollama: 2GB (LLM inference)
# - hailo-whisper: 1GB (STT)
# - hailo-yolo: 512MB (object detection)
# - OS + overhead: 1.5GB
# Total: ~5GB (safe with headroom)
# Monitor memory across services:
free -h
systemctl status hailo-ollama hailo-whisper hailo-yolo
# Each service tracks its own memory via /api/health:
curl http://localhost:11434/api/health # Ollama
curl http://localhost:8082/api/health # Whisper
curl http://localhost:8080/api/health # YOLO
Key principle: Services running under systemd have constrained environments that differ from manual execution. What works when you run application may fail as a service.
Common differences:
WorkingDirectory=, not your shell's $PWDhailo-ollama), not root or your user/usr/local/bin or other custom paths/var/lib/service-name), not /root or /home/youDebugging strategy:
Test as the service user first:
# Become the service user and test manually
sudo -u hailo-ollama bash
cd /var/lib/hailo-ollama
/usr/bin/hailo-ollama # Run exactly as service will
Match the service environment:
# Export the exact environment from unit file
sudo -u hailo-ollama \
XDG_DATA_HOME=/var/lib \
XDG_CONFIG_HOME=/etc/xdg \
bash -c '/usr/bin/hailo-ollama'
Inspect the running service environment:
# Check environment variables
sudo cat /proc/$(pgrep service-name)/environ | tr '\0' '\n'
# Check mount points (for bind mounts)
sudo cat /proc/$(pgrep service-name)/mountinfo | grep service-name
# Check working directory
ls -la /proc/$(pgrep service-name)/cwd
Problem pattern: Application works manually but service can't find resources (models, configs, manifests).
Discovery mechanisms to investigate:
Hardcoded paths:
# Search binary for path strings
strings /usr/bin/application | grep -E "(usr|var|etc|share)"
Environment-based paths (XDG, HOME, etc.):
# Trace file access during startup
strace -e openat,access /usr/bin/application 2>&1 | grep -E "(model|config|manifest)"
Config file references:
# Check installed config files
dpkg -L package-name | grep -E "\.(json|yaml|conf|ini)$"
Common solutions:
Option 1: Configuration (preferred when supported):
# systemd unit
Environment=MODEL_PATH=/usr/share/hailo-models
Option 2: Bind mount (when app searches fixed user paths):
# Mount package resources into service's writable state
BindReadOnlyPaths=/usr/share/package-resources:/var/lib/service-name/resources
Why bind mounts over symlinks:
d_type: Directory scanners using readdir() see DT_LNK for symlinks, DT_DIR for real directories. Many apps skip non-directory entries.Option 3: Copy resources (last resort):
# In installer: copy package resources to service directory
cp -r /usr/share/package-resources/* /var/lib/service-name/resources/
chown -R service-user:service-user /var/lib/service-name/resources/
Downsides: Duplicates data, stale after package upgrades, requires re-copy logic.
When to use: Application fails in service but works manually; need to see what it's actually accessing.
# Trace file operations during startup
sudo systemctl stop service-name
strace -f -e openat,access,stat /usr/bin/application 2>&1 | tee /tmp/strace.log
# Common patterns to look for:
grep -E "ENOENT|EACCES" /tmp/strace.log # File not found or permission denied
grep "manifest" /tmp/strace.log # Resource discovery paths
grep "\.so" /tmp/strace.log # Library loading issues
Attach to running service:
# Trace a running process
pid=$(pgrep service-name)
sudo strace -p $pid -e openat -f 2>&1 | grep -i resource
Progressive verification checklist:
Package resources installed:
dpkg -L package-name | grep -E "models|manifests|configs"
ls -la /usr/share/package-name/
Service user can read resources:
sudo -u service-user ls -la /usr/share/package-resources/
sudo -u service-user cat /usr/share/package-resources/model.hef
Manual execution as service user:
sudo systemctl stop service-name
sudo -u service-user bash -c 'cd /var/lib/service-name && /usr/bin/application'
# Verify application finds resources
Service execution:
sudo systemctl start service-name
sudo systemctl status service-name
sudo journalctl -u service-name -n 50
API validation:
curl http://localhost:PORT/api/health
curl http://localhost:PORT/api/list-resources
Issue: "Resources not found" in service but work manually
Cause: Application searches paths relative to $HOME, $XDG_DATA_HOME, or current directory.
Debug:
# Compare environments
env | grep -E "HOME|XDG|PATH" > /tmp/manual.env
sudo cat /proc/$(pgrep service-name)/environ | tr '\0' '\n' | grep -E "HOME|XDG|PATH" > /tmp/service.env
diff /tmp/manual.env /tmp/service.env
Solutions:
BindReadOnlyPaths to mount resources into expected locationIssue: "Permission denied" accessing /dev/hailo0
Cause: Service user not in Hailo device group.
Fix:
stat -c '%G' /dev/hailo0 # Check device group (usually 'hailo' or 'root')
sudo usermod -aG hailo service-user
sudo systemctl restart service-name
Issue: Models load on first run but not after package upgrade
Cause: Resources copied during install rather than referenced from package location.
Fix: Use bind mounts or config paths pointing to /usr/share instead of copying.
Lesson: Device manager socket errors are transient
Context: When integrating services with the device manager, socket connection errors may appear in logs but resolve automatically as services establish connections.
Action: No intervention needed; these are normal during startup and connection establishment.
Lesson: Model file permissions must be accessible by device_manager
Context: Services that download or manage HEF models must ensure the files have permissions allowing the device manager (running as hailo-device-mgr user) to read them. This includes:
hailo-device-mgr (e.g., chown hailo-depth:hailo-device-mgr model.hef)hailo-depth) is in the hailo-device-mgr group (usermod -aG hailo-depth hailo-device-mgr)drwxr-x---)Additional Integration Note: When adding new model types (e.g., depth), you must update the installed device manager code (e.g., copy updated hailo_device_manager.py to /opt/hailo-device-manager/) so the running service recognizes and supports the new handler. Restart the device manager service after updating.
Lesson: Testing practices for device manager services
Context: When testing new services integrated with device manager, avoid cluttering logs and conversations with raw base64 data.
Action: Uninstall old service (and its config) first. Test against the running system service. Use file payloads or script base64 encoding instead of inline base64 in curl commands or output.
Scenario: Application uses XDG Base Directory specification but only searches XDG_DATA_HOME, not XDG_DATA_DIRS.
Symptoms:
/usr/share/application/XDG_DATA_DIRS includes /usr/share but doesn't helpRoot cause: Application follows XDG spec incompletely—only checks user data location, not system data directories.
Solution: Use BindReadOnlyPaths:
# systemd unit
Environment=XDG_DATA_HOME=/var/lib
BindReadOnlyPaths=/usr/share/application/resources:/var/lib/application/resources
Lesson: Don't assume applications follow specs completely. Verify actual behavior with strace.
See hailo-ollama MANIFEST_DISCOVERY.md for detailed case study.
# Real-time logs
sudo journalctl -u hailo-ollama.service -f
# Debug level logs (if configured)
sudo journalctl -u hailo-ollama.service -p debug
# Continuous output with timestamps
sudo journalctl -u hailo-ollama.service -o short-iso -f
# Health
curl http://localhost:11434/api/health | jq
# List models
curl http://localhost:11434/api/tags | jq
# Chat inference
curl -X POST http://localhost:11434/api/chat \
-H "Content-Type: application/json" \
-d '{
"model": "neural-chat",
"messages": [{"role": "user", "content": "Hello"}],
"stream": false
}' | jq
# During active inference, monitor in separate terminal
watch -n 1 'echo "Temp: $(vcgencmd measure_temp)" && \
free -h && \
ps aux | grep hailo-ollama'
Reference: