Skip to content

Actualización del Workflow #19

Actualización del Workflow

Actualización del Workflow #19

Workflow file for this run

name: CI - MiniML Embedded Pipeline
on:
push:
branches: [ "main", "master", "develop" ]
pull_request:
branches: [ "main", "master" ]
workflow_dispatch:
inputs:
target_arch:
description: 'Arquitectura objetivo (arm/xtensa)'
required: false
default: 'arm'
test_quantization:
description: 'Probar cuantificación (true/false)'
required: false
default: 'true'
jobs:
# ------------------------------------------------------------------
# JOB 1: Python Logic & Unit Tests
tests:
name: 🐍 Core Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip'
- name: Install MiniML & Deps
run: |
pip install --upgrade pip
pip install pytest
pip install -e .
- name: Verify Package Installation
run: |
python -c "import miniml; import estimators; import adapters; print('✓ All packages imported successfully')"
- name: Run Tests
run: |
pytest -v --junitxml=test-report.xml tests/
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-report.xml
# ------------------------------------------------------------------
# JOB 2: Embedded ML Training & C Code Generation
build-firmware:
name: ⚙️ Build Firmware (${{ inputs.target_arch || 'arm' }})
needs: tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Toolchains (ARM + AVR)
id: toolchain
run: |
ARCH=${{ inputs.target_arch || 'arm' }}
echo "Setting up toolchains for $ARCH and AVR..."
# Instalar toolchain ARM
if [ "$ARCH" == "arm" ] || [ "$ARCH" == "all" ]; then
sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi
echo "compiler_arm=arm-none-eabi-gcc" >> $GITHUB_ENV
echo "cflags_arm=-mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wall -Werror" >> $GITHUB_ENV
echo "ARM_TOOLCHAIN=installed" >> $GITHUB_ENV
fi
# Instalar toolchain AVR (para demostrar código Arduino)
sudo apt-get update && sudo apt-get install -y gcc-avr binutils-avr avr-libc
echo "compiler_avr=avr-gcc" >> $GITHUB_ENV
echo "cflags_avr=-mmcu=atmega328p -Wall -Werror -Os" >> $GITHUB_ENV
echo "AVR_TOOLCHAIN=installed" >> $GITHUB_ENV
# Toolchain genérico para validación de sintaxis
echo "compiler_gcc=gcc" >> $GITHUB_ENV
echo "cflags_gcc=-Wall -Werror" >> $GITHUB_ENV
if [ "$ARCH" == "xtensa" ]; then
echo "Assuming Xtensa toolchain is pre-installed on self-hosted runner."
echo "compiler_xtensa=xtensa-esp32-elf-gcc" >> $GITHUB_ENV
echo "cflags_xtensa=-Wall -Werror" >> $GITHUB_ENV
fi
- name: Install MiniML
uses: actions/setup-python@v4
with:
python-version: '3.10'
- run: pip install .
- name: Generate Embedded ML Models (Realistic Scenarios)
run: |
mkdir -p build/generated
cat <<'PYEOF' > generate_embedded_models.py
"""
Genera modelos ML realistas para casos de uso embebidos:
1. Neural Network con cuantificación (XOR - caso clásico)
2. Decision Tree (clasificación simple)
3. Linear Model (regresión para sensores)
"""
import miniml
import json
import os
# ============================================
# CASO 1: Neural Network con Cuantificación
# ============================================
# Dataset XOR: Caso clásico para validar NN en embebido
xor_dataset = [
[0.0, 0.0, 0],
[0.0, 1.0, 1],
[1.0, 0.0, 1],
[1.0, 1.0, 0]
]
print("[1/3] Entrenando Neural Network (XOR) con cuantificación...")
nn_result = miniml.train_pipeline(
model_name="nn_xor_embedded",
dataset=xor_dataset,
model_type="neural_network",
params={
"n_inputs": 2,
"n_hidden": 4,
"n_outputs": 1,
"epochs": 2000,
"learning_rate": 0.1,
"seed": 42
},
scaling="minmax" # Escalado para normalizar inputs
)
# Validar que act_scales se calcularon (esencial para cuantificación)
model_nn = nn_result['model']
if not hasattr(model_nn, 'act_scales') or not model_nn.act_scales:
raise RuntimeError("act_scales no se calcularon - cuantificación fallará")
print(f" ✓ act_scales: {model_nn.act_scales}")
# Asegurar que el modelo esté cuantificado antes de exportar
if not model_nn.quantized:
print(" → Cuantificando modelo antes de exportar...")
model_nn.quantize()
# Exportar código C cuantificado (intentará CMSISAdapter primero)
nn_code = miniml.export_to_c("nn_xor_embedded")
# Detectar tipo de código generado (verificación estricta)
is_cmsis = ("CMSIS" in nn_code or "arm_fully_connected_s8" in nn_code or
"predict_int8" in nn_code) and "avr/pgmspace.h" not in nn_code
is_avr = "avr/pgmspace.h" in nn_code or ("PROGMEM" in nn_code and "CMSIS" not in nn_code)
if is_cmsis:
print(" ✓ Código generado con CMSISAdapter (compatible ARM Cortex-M)")
with open("build/generated/nn_xor_arm.h", "w") as f:
f.write(nn_code)
print(" ✓ Guardado como: nn_xor_arm.h (ARM)")
elif is_avr:
print(" ✓ Código generado para AVR/Arduino (PROGMEM)")
with open("build/generated/nn_xor_avr.h", "w") as f:
f.write(nn_code)
print(" ✓ Guardado como: nn_xor_avr.h (AVR)")
else:
print(" ⚠️ Tipo de código no determinado claramente")
# Guardar también con nombre genérico
with open("build/generated/nn_xor_quantized.h", "w") as f:
f.write(nn_code)
print(" ✓ Código C generado: nn_xor_quantized.h")
# ============================================
# CASO 2: Decision Tree (Clasificación)
# ============================================
# Dataset simple para clasificación binaria (ej: detección de anomalías)
tree_dataset = [
[0.1, 0.2, 0],
[0.3, 0.4, 0],
[0.7, 0.8, 1],
[0.9, 0.95, 1]
]
print("[2/3] Entrenando Decision Tree...")
miniml.train_pipeline(
model_name="dt_classifier_embedded",
dataset=tree_dataset,
model_type="DecisionTreeClassifier",
params={"max_depth": 3},
scaling="minmax"
)
dt_code = miniml.export_to_c("dt_classifier_embedded")
with open("build/generated/dt_classifier.h", "w") as f:
f.write(dt_code)
print(" ✓ Código C generado: dt_classifier.h")
# ============================================
# CASO 3: Linear Regression (Sensor Data)
# ============================================
# Dataset de regresión (ej: temperatura vs voltaje)
linear_dataset = [
[0.0, 20.0],
[1.0, 22.5],
[2.0, 25.0],
[3.0, 27.5],
[4.0, 30.0]
]
print("[3/3] Entrenando Linear Model (regresión)...")
miniml.train_pipeline(
model_name="linear_sensor_embedded",
dataset=linear_dataset,
model_type="linear_regression",
params={"learning_rate": 0.01, "epochs": 100},
scaling="standard"
)
linear_code = miniml.export_to_c("linear_sensor_embedded")
with open("build/generated/linear_sensor.h", "w") as f:
f.write(linear_code)
print(" ✓ Código C generado: linear_sensor.h")
# ============================================
# Validación: Verificar características embebidas
# ============================================
print("\n[VALIDACIÓN] Verificando características embebidas...")
# Verificar que el código NN tiene cuantificación
with open("build/generated/nn_xor_quantized.h", "r") as f:
nn_content = f.read()
checks = {
"int8_t": "Pesos cuantificados a int8" in nn_content or "int8_t" in nn_content,
"PROGMEM": "PROGMEM" in nn_content or "const" in nn_content,
"CMSIS": "CMSIS" in nn_content or "arm_" in nn_content or "predict_int8" in nn_content,
"Scaler": "preprocess" in nn_content.lower() or "scaler" in nn_content.lower()
}
for check, passed in checks.items():
status = "✓" if passed else "✗"
print(f" {status} {check}: {'OK' if passed else 'FALTA'}")
if not all(checks.values()):
print(" ⚠️ Algunas características embebidas no se detectaron")
print("\n[COMPLETADO] Todos los modelos generados exitosamente")
PYEOF
python generate_embedded_models.py
- name: Compile C Artifacts (ARM + AVR)
run: |
echo "=========================================="
echo "🔨 COMPILANDO MODELOS PARA ARM Y AVR"
echo "=========================================="
# Función helper para compilar un modelo
compile_model() {
local model_file=$1
local compiler=$2
local cflags=$3
local target_name=$4
if [ ! -f "build/generated/$model_file" ]; then
echo "⚠️ $model_file no encontrado"
return 1
fi
echo ""
echo "📦 Compilando $model_file para $target_name..."
# Crear wrapper .c
# El código generado es código C completo, no un header, así que lo copiamos directamente
temp_c="build/generated/${model_file%.h}_${target_name}_temp.c"
# Verificar si el archivo ya tiene includes para evitar duplicados
has_stdint=$(grep -q "^#include <stdint.h>" "build/generated/$model_file" && echo "yes" || echo "no")
has_math=$(grep -q "^#include <math.h>" "build/generated/$model_file" && echo "yes" || echo "no")
has_avr=$(grep -q "^#include <avr/pgmspace.h>" "build/generated/$model_file" && echo "yes" || echo "no")
{
echo "// Wrapper para compilación de ${model_file} para ${target_name}"
echo ""
# Agregar includes solo si no están ya presentes
if [ "$has_stdint" == "no" ]; then
echo "#include <stdint.h>"
fi
if [ "$has_math" == "no" ]; then
echo "#include <math.h>"
fi
# Para AVR, asegurar que __AVR__ esté definido y avr/pgmspace.h incluido
if [ "$target_name" == "AVR (Arduino)" ]; then
if [ "$has_avr" == "no" ]; then
echo "#define __AVR__"
echo "#include <avr/pgmspace.h>"
fi
fi
echo ""
echo "// ========================================"
echo "// Código del modelo generado:"
echo "// ========================================"
# Leer el contenido del archivo .h y copiarlo (no incluirlo)
# Filtrar líneas vacías al final que puedan causar problemas
cat "build/generated/$model_file" | sed '/^$/d' | sed -e :a -e '/^\n*$/d;N;ba'
echo ""
echo "// ========================================"
echo "// Función dummy para evitar linker errors"
echo "// ========================================"
echo "int main() { return 0; }"
} > "$temp_c"
# Validar sintaxis básica: contar llaves abiertas y cerradas
open_braces=$(grep -o '{' "$temp_c" | wc -l)
close_braces=$(grep -o '}' "$temp_c" | wc -l)
if [ "$open_braces" -ne "$close_braces" ]; then
echo " ⚠️ Advertencia: Desbalance de llaves (abiertas: $open_braces, cerradas: $close_braces)"
echo " → Intentando compilar de todas formas..."
fi
# Validar que el archivo temporal se creó correctamente
if [ ! -f "$temp_c" ]; then
echo " ❌ No se pudo crear el archivo temporal $temp_c"
return 1
fi
# Mostrar primeras líneas del archivo para debugging (solo si falla)
if ! $compiler $cflags -c "$temp_c" -o "${temp_c%.c}.o" 2>&1; then
echo " ❌ Error compilando $model_file para $target_name"
echo " 📄 Primeras 20 líneas del código generado:"
head -n 20 "$temp_c" | sed 's/^/ /'
echo " 📄 Últimas 10 líneas del código generado:"
tail -n 10 "$temp_c" | sed 's/^/ /'
return 1
else
echo " ✅ $model_file compilado exitosamente para $target_name"
return 0
fi
}
# ==========================================
# COMPILAR PARA ARM CORTEX-M
# ==========================================
if [ "$ARM_TOOLCHAIN" == "installed" ]; then
echo ""
echo "🎯 === COMPILACIÓN ARM CORTEX-M4 ==="
# Función para verificar si un archivo es código ARM (no AVR)
is_arm_code() {
local file=$1
if [ ! -f "build/generated/$file" ]; then
return 1
fi
# Si tiene avr/pgmspace.h sin protección, NO es ARM
if grep -q "^#include <avr/pgmspace.h>" "build/generated/$file"; then
return 1
fi
# Si tiene CMSIS o predict_int8, SÍ es ARM
if grep -q "CMSIS\|arm_fully_connected_s8\|predict_int8" "build/generated/$file"; then
return 0
fi
# Si NO tiene avr/pgmspace.h, puede ser ARM
if ! grep -q "avr/pgmspace.h\|PROGMEM" "build/generated/$file"; then
return 0
fi
return 1
}
# Intentar compilar código ARM (CMSIS) si existe y es realmente ARM
if [ -f "build/generated/nn_xor_arm.h" ]; then
if is_arm_code "nn_xor_arm.h"; then
compile_model "nn_xor_arm.h" "$compiler_arm" "$cflags_arm" "ARM Cortex-M4"
else
echo " ⚠️ nn_xor_arm.h contiene código AVR, saltando compilación ARM"
fi
fi
# Compilar otros modelos que no sean AVR específicos
for model in "dt_classifier.h" "linear_sensor.h"; do
if [ -f "build/generated/$model" ]; then
# Solo compilar si NO es código AVR específico
if is_arm_code "$model"; then
compile_model "$model" "$compiler_arm" "$cflags_arm" "ARM Cortex-M4"
else
echo " ⚠️ $model es código AVR específico, saltando compilación ARM"
fi
fi
done
fi
# ==========================================
# COMPILAR PARA AVR (ARDUINO)
# ==========================================
if [ "$AVR_TOOLCHAIN" == "installed" ]; then
echo ""
echo "🎯 === COMPILACIÓN AVR (ARDUINO UNO) ==="
# Compilar código AVR si existe
if [ -f "build/generated/nn_xor_avr.h" ]; then
compile_model "nn_xor_avr.h" "$compiler_avr" "$cflags_avr" "AVR (Arduino)"
fi
# También intentar compilar el genérico si es AVR
if [ -f "build/generated/nn_xor_quantized.h" ]; then
if grep -q "avr/pgmspace.h\|PROGMEM" "build/generated/nn_xor_quantized.h"; then
compile_model "nn_xor_quantized.h" "$compiler_avr" "$cflags_avr" "AVR (Arduino)"
fi
fi
# Compilar otros modelos que sean compatibles con AVR
for model in "dt_classifier.h" "linear_sensor.h"; do
if [ -f "build/generated/$model" ]; then
# Crear versión AVR-friendly (definir __AVR__)
compile_model "$model" "$compiler_avr" "$cflags_avr" "AVR (Arduino)"
fi
done
fi
echo ""
echo "=========================================="
echo "✅ COMPILACIÓN COMPLETADA"
echo "=========================================="
echo "MiniML demuestra funcionamiento en:"
[ "$ARM_TOOLCHAIN" == "installed" ] && echo " ✓ ARM Cortex-M4"
[ "$AVR_TOOLCHAIN" == "installed" ] && echo " ✓ AVR (Arduino Uno/Nano)"
echo "=========================================="
- name: Validate Embedded Features
run: |
echo "Validando características específicas de embebido..."
# Verificar que los archivos generados tienen características embebidas
if [ -f "build/generated/nn_xor_quantized.h" ]; then
echo "Verificando nn_xor_quantized.h..."
# Verificar cuantificación
if grep -q "int8_t" build/generated/nn_xor_quantized.h; then
echo " ✓ Contiene cuantificación int8"
else
echo " ✗ No se detectó cuantificación int8"
exit 1
fi
# Verificar que no usa float64 o double (solo float32)
if grep -q "double\|float64" build/generated/nn_xor_quantized.h; then
echo " ⚠️ Advertencia: Usa double/float64 (no ideal para embebido)"
fi
# Verificar tamaño razonable (no debería ser enorme)
size=$(wc -c < build/generated/nn_xor_quantized.h)
if [ $size -lt 100000 ]; then
echo " ✓ Tamaño razonable: ${size} bytes"
else
echo " ⚠️ Tamaño grande: ${size} bytes"
fi
fi
- name: Upload Binary Artifacts
uses: actions/upload-artifact@v4
with:
name: firmware-build-${{ inputs.target_arch || 'arm' }}
path: build/generated/
retention-days: 7
#-----------------------------------------------------------------