| name | tensorflow-lite-deep-dive |
| description | Pipeline TFLite complet — conversion avancée, quantification INT8/FP16/FP32, GPU Delegates (OpenGL/OpenCL/Vulkan), NNAPI/XNNPACK, benchmarking, profilage mémoire, personnalisation d'opérateurs et déploiement cross-platform. |
| version | 1.0.0 |
| author | EVA |
| license | Privée EVA St-Étienne |
| platforms | ["linux","macos","windows"] |
| metadata | {"EVA":{"tags":["tensorflow-lite","tflite","quantization","delegate","gpu","xnnpack","nnapi","benchmarking","edge-ai","model-optimization"],"related_skills":["tinyml-fundamentals","onnx-edge-deployment","nvidia-jetson-deployment","model-optimization-edge"]}} |
TensorFlow Lite — Plongée Approfondie
Vue d'ensemble
TensorFlow Lite est le moteur d'inférence léger de TensorFlow pour les dispositifs mobiles, embarqués et Edge. Cette skill couvre le pipeline complet de conversion avancée, les delegates matériels, l'optimisation mémoire, le benchmarking systématique et le déploiement cross-platform (Android, iOS, Linux ARM, MCU).
Architecture TFLite
┌──────────────────────────────────────────────────────┐
│ Modèle source (Keras / SavedModel) │
├──────────────────────────────────────────────────────┤
│ TFLite Converter (conversion avancée) │
├──────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ XNNPACK │ │ GPU Delegate│ │ NNAPI │ │
│ │ (CPU NEON/ │ │ (OpenGL/ │ │ (Android HW │ │
│ │ SSE/AVX) │ │ Vulkan) │ │ acceleration│ │
│ └─────────────┘ └─────────────┘ └──────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ │
│ │ CoreML │ │ Hexagon │ │ Edge TPU │ │
│ │ (iOS) │ │ (DSP) │ │ (Coral) │ │
│ └─────────────┘ └─────────────┘ └──────────────┘ │
├──────────────────────────────────────────────────────┤
│ Interpreter (Runtime TFLite) │
└──────────────────────────────────────────────────────┘
1. Conversion Avancée
1.1 Paramètres fins du Converter
import tensorflow as tf
def converter_tflite_avance(model_path: str, quant_mode: str = "int8",
representative_data=None) -> bytes:
"""
Conversion TFLite avec contrôle précis de chaque paramètre.
Args:
model_path: chemin du modèle Keras/SavedModel
quant_mode: "float16", "int8", "dynamic_range", "float32"
representative_data: générateur de calibration (obligatoire pour int8)
Returns:
modèle TFLite en bytes
"""
converter = tf.lite.TFLiteConverter.from_saved_model(model_path)
if quant_mode in ("int8", "float16", "dynamic_range"):
converter.optimizations = [tf.lite.Optimize.DEFAULT]
else:
converter.optimizations = []
if quant_mode == "float16":
converter.target_spec.supported_types = [tf.float16]
elif quant_mode == "int8":
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS_INT8
]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8
if representative_data:
converter.representative_dataset = representative_data
elif quant_mode == "dynamic_range":
pass
converter.target_spec.supported_ops += [
tf.lite.OpsSet.TFLITE_BUILTINS
]
converter._experimental_disable_batchmatmul_unfold =
converter._experimental_lower_tensor_list_ops =
converter._experimental_default_to_single_batch_in_tensor_list_ops =
converter.convert()
1.2 Sélecteur de précision par opérateur (mixed-precision)
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS_INT8,
tf.lite.OpsSet.TFLITE_BUILTINS,
]
1.3 Quantification sélective avec signature
converter.target_spec.supported_ops_by_signature = {
"encoder": [tf.lite.OpsSet.TFLITE_BUILTINS_INT8],
"decoder": [tf.lite.OpsSet.TFLITE_BUILTINS],
}
1.4 Sélection d'opérateurs (Allowlisting)
from tensorflow.lite.python.op_hint import OpHint
ALLOWED_OPS = {
"CONV_2D", "DEPTHWISE_CONV_2D", "FULLY_CONNECTED",
"SOFTMAX", "AVERAGE_POOL_2D", "MAX_POOL_2D",
"RESHAPE", "CONCATENATION", "ADD", "MUL",
}
2. Delegates (Accélération Matérielle)
2.1 XNNPACK Delegate (CPU optimisé)
import tflite_runtime.interpreter as tflite
from tflite_runtime.interpreter import Interpreter
from tflite_runtime.interpreter import load_delegate
try:
delegate = load_delegate("libXNNPACK.so")
interpreter = Interpreter(
model_path="model.tflite",
experimental_delegates=[delegate],
)
print("XNNPACK Delegate activé")
except:
print("XNNPACK non disponible, fallback CPU standard")
interpreter = Interpreter(model_path="model.tflite")
git clone https://github.com/google/XNNPACK.git
cd XNNPACK
mkdir build && cd build
cmake -DXNNPACK_BUILD_TESTS=OFF -DXNNPACK_BUILD_BENCHMARKS=OFF ..
make -j$(nproc)
sudo make install
2.2 GPU Delegate (OpenGL / Vulkan / Metal)
import tflite_runtime.interpreter as tflite
from tflite_runtime.interpreter import load_delegate
gpu_delegate = load_delegate("libgpu_delegate.so")
interpreter = tflite.Interpreter(
model_path="model.tflite",
experimental_delegates=[gpu_delegate],
)
from tensorflow.lite.python.interpreter import OpResolverType
GPU_OPTIONS = {
"precision_loss_allowed": 0,
"inference_priority1": 1,
"inference_preference": 1,
"cache_directory": "/tmp/gpu_cache",
"model_token": "model_v1",
}
def verifier_gpu_delegate():
"""Teste la disponibilité et la couverture GPU."""
from tensorflow.lite.python.interpreter import Interpreter
delegate = load_delegate("libgpu_delegate.so")
interpreter = Interpreter(
model_path="model.tflite",
experimental_delegates=[delegate],
)
interpreter.allocate_tensors()
import numpy as np
input_details = interpreter.get_input_details()
interpreter.set_tensor(input_details[0]["index"],
np.random.randn(*input_details[0]["shape"]).astype(
input_details[0]["dtype"]))
interpreter.invoke()
return True
2.3 NNAPI Delegate (Android Neural Networks API)
from tflite_runtime.interpreter import Interpreter, load_delegate
nnapi_delegate = load_delegate("libnnapi_delegate.so")
interpreter = Interpreter(
model_path="model.tflite",
experimental_delegates=[nnapi_delegate],
)
nnapi_delegate = load_delegate("libnnapi_delegate.so", {
"disable_nnapi_cpu": "1",
"accelerator_name": "qti-dsp",
})
2.4 Hexagon Delegate (Qualcomm DSP)
hexagon_delegate = load_delegate("libhexagon_delegate.so")
interpreter = Interpreter(
model_path="model_quantized.tflite",
experimental_delegates=[hexagon_delegate],
)
2.5 CoreML Delegate (iOS)
let coreml_delegate = CoreMLDelegate()
var interpreter: Interpreter
if coreml_delegate != nil {
interpreter = try Interpreter(
modelPath: "model.tflite",
delegates: [coreml_delegate!]
)
} else {
interpreter = try Interpreter(modelPath: "model.tflite")
}
2.6 Tableau Comparatif des Delegates
| Delegate | Plateforme | Accélération | Précision | Taille binaire |
|---|
| XNNPACK | Linux/Android/Windows | CPU NEON/SSE/AVX | FP32/FP16 | ~500 KB |
| GPU (OpenGL) | Android/Linux | GPU (shaders) | FP16 | ~300 KB |
| GPU (Vulkan) | Android/Linux | GPU (compute) | FP16/FP32 | ~400 KB |
| Metal | iOS | GPU (Apple) | FP16 | ~200 KB |
| NNAPI | Android 8.1+ | DSP/NPU/GPU | INT8/FP16 | ~100 KB |
| Hexagon | Qualcomm | DSP | INT8 | ~2 MB |
| CoreML | iOS 12+ | Neural Engine | FP16 | ~300 KB |
| Edge TPU | Coral | TPU | INT8 | ~200 KB |
3. Benchmarking et Profilage
3.1 TFLite Benchmark Tool (officiel)
bazel build -c opt tensorflow/lite/tools/benchmark:benchmark_model
./benchmark_model \
--graph=model.tflite \
--num_threads=4 \
--num_runs=100 \
--warmup_runs=10
./benchmark_model \
--graph=model.tflite \
--use_gpu=true \
--allow_fp16_precision_for_fp32=true
./benchmark_model \
--graph=model.tflite \
--use_xnnpack=true \
--num_threads=8
./benchmark_model --graph=model.tflite --profiling=true --profiling_output_csv_file=profile.csv
3.2 Benchmark Python
import time
import numpy as np
import tensorflow as tf
class TFLiteBenchmark:
"""Benchmark systématique d'un modèle TFLite."""
def __init__(self, model_path: str, delegate: str = "cpu"):
self.model_path = model_path
self.delegate = delegate
self.interpreter = self._creer_interpreteur()
self.details_entree = self.interpreter.get_input_details()
self.details_sortie = self.interpreter.get_output_details()
def _creer_interpreteur(self):
"""Crée l'interpréteur avec le delegate spécifié."""
if self.delegate == "cpu":
return tf.lite.Interpreter(model_path=self.model_path)
elif self.delegate == "xnnpack":
from tflite_runtime.interpreter import load_delegate
delegate = load_delegate("libXNNPACK.so")
return tf.lite.Interpreter(
model_path=self.model_path,
experimental_delegates=[delegate],
)
elif self.delegate == :
tflite_runtime.interpreter load_delegate
delegate = load_delegate()
tf.lite.Interpreter(
model_path=.model_path,
experimental_delegates=[delegate],
)
() -> :
.interpreter.allocate_tensors()
input_data :
shape = .details_entree[][]
dtype = .details_entree[][]
input_data = np.random.randn(*shape).astype(dtype)
_ (warmup):
.interpreter.set_tensor(
.details_entree[][], input_data)
.interpreter.invoke()
latences = []
_ (n_iterations):
.interpreter.set_tensor(
.details_entree[][], input_data)
start = time.perf_counter()
.interpreter.invoke()
elapsed = (time.perf_counter() - start) *
latences.append(elapsed)
{
: .delegate,
: (np.mean(latences)),
: (np.std(latences)),
: (np.percentile(latences, )),
: (np.percentile(latences, )),
: (np.percentile(latences, )),
: / (np.mean(latences)),
: n_iterations,
}
() -> :
.interpreter.allocate_tensors()
details_tous = .interpreter.get_tensor_details()
memoire_totale =
memoire_par_type = {}
det details_tous:
taille = np.prod(det[]) * np.dtype(det[]).itemsize
memoire_totale += taille
type_name = (det[])
memoire_par_type[type_name] = memoire_par_type.get(type_name, ) + taille
{
: (memoire_totale),
: memoire_totale / ,
: {k: k, v memoire_par_type.items()},
: (details_tous),
}
() -> :
ops = []
op_details .interpreter._get_ops_details():
ops.append({
: op_details[],
: op_details[],
: op_details[],
: op_details[],
})
ops
bench = TFLiteBenchmark(, delegate=)
lat = bench.mesurer_latence(n_iterations=)
()
()
3.3 Profilage mémoire détaillé
def profilage_memoire_tflite(model_path: str) -> dict:
"""Analyse détaillée de la mémoire d'un modèle TFLite."""
import struct
with open(model_path, "rb") as f:
model_data = f.read()
info = {
"taille_fichier_kb": len(model_data) / 1024,
}
magic = model_data[:4]
assert magic == b'\x1c\x00\x00\x00', "Format TFLite invalide"
version = struct.unpack("<i", model_data[4:8])[0]
info["version_modele"] = f"TFLite {version}"
interpreter = tf.lite.Interpreter(model_path=model_path)
interpreter.allocate_tensors()
details = interpreter.get_tensor_details()
poids_total = 0
activations_total = 0
for det in details:
taille = np.prod(det["shape"]) * np.dtype(det["dtype"]).itemsize
if "weight" in det["name"].lower():
poids_total += taille
else:
activations_total += taille
info["poids_kb"] = poids_total /
info[] = activations_total /
info[] =
info
4. Opérateurs Personnalisés (Custom Ops)
4.1 Création d'un opérateur personnalisé C++
#ifndef CUSTOM_OP_H_
#define CUSTOM_OP_H_
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/register.h"
namespace tflite {
namespace ops {
namespace custom {
TfLiteRegistration* Register_PREEMPHASIS();
TfLiteStatus PreemphasisPrepare(TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* input = GetInput(context, node, 0);
TfLiteTensor* output = GetOutput(context, node, 0);
TfLiteIntArray* output_dims = TfLiteIntArrayCopy(input->dims);
return context->ResizeTensor(context, output, output_dims);
}
TfLiteStatus PreemphasisEval(TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* input = GetInput(context, node, 0);
const TfLiteTensor* alpha_tensor = GetInput(context, node, 1);
TfLiteTensor* output = GetOutput(context, node, 0);
float alpha = *GetTensorData<float>(alpha_tensor);
* in_data = <>(input);
* out_data = <>(output);
size = (input).();
out_data[] = in_data[];
( i = ; i < size; i++) {
out_data[i] = in_data[i] - alpha * in_data[i - ];
}
kTfLiteOk;
}
{
TfLiteRegistration r = {
.init = ,
.free = ,
.prepare = PreemphasisPrepare,
.invoke = PreemphasisEval,
};
&r;
}
}
}
}
4.2 Enregistrement du custom op dans le résolveur
MicroMutableOpResolver<10> resolver;
resolver.AddCustom("Preemphasis",
tflite::ops::custom::Register_PREEMPHASIS());
4.3 Utilisation du custom op en Python
import tensorflow as tf
@tf.function
def preemphasis(x, alpha=0.97):
"""Filtre pre-emphasis personnalisé."""
y = tf.concat([[x[0]], x[1:] - alpha * x[:-1]], axis=0)
return y
concrete_fn = preemphasis.get_concrete_function(
x=tf.TensorSpec((None, 128), tf.float32)
)
converter = tf.lite.TFLiteConverter.from_concrete_functions([concrete_fn])
converter.target_spec.supported_ops = [
tf.lite.OpsSet.TFLITE_BUILTINS,
tf.lite.OpsSet.SELECT_TF_OPS,
]
tflite_model = converter.convert()
class PreemphasisLayer(tf.keras.layers.Layer):
def __init__(self, alpha=0.97, **kwargs):
super().__init__(**kwargs)
self.alpha = alpha
def call(self, x):
return tf.concat([[x[0]], x[1:] - self.alpha * x[:-1]], axis=0)
def get_config(self):
return {"alpha": self.alpha}
5. Compression et Taille de Modèle
5.1 Analyse détaillée du FlatBuffer
def analyser_flatbuffer_tflite(model_path: str) -> dict:
"""Extrait la structure interne du FlatBuffer TFLite."""
from flatbuffers import util
from tflite import Model, SubGraph, OperatorCode
with open(model_path, "rb") as f:
buf = f.read()
model = Model.Model.GetRootAsModel(buf, 0)
info = {
"version": model.Version(),
"description": model.Description().decode() if model.Description() else "",
"n_subgraphs": model.SubgraphsLength(),
"n_operator_codes": model.OperatorCodesLength(),
"n_buffers": model.BuffersLength(),
}
codes = []
for i in range(model.OperatorCodesLength()):
op_code = model.OperatorCodes(i)
builtin = op_code.BuiltinCode()
custom = op_code.CustomCode()
codes.append({
"index": i,
"builtin": builtin,
"custom": custom.decode() if custom else None,
})
info["operator_codes"] = codes
buffer_sizes = []
for i in range(model.BuffersLength()):
buf = model.Buffers(i)
data = buf.DataAsNumpy()
if data is not :
buffer_sizes.append((data))
info[] = {
: (buffer_sizes),
: (buffer_sizes) buffer_sizes ,
: (buffer_sizes),
}
info
5.2 Réduction de taille
converter._experimental_strip_unsupported_ops = True
from tensorflow_model_optimization.sparsity import keras as sparsity
pruning_params = {
"pruning_schedule": sparsity.PolynomialDecay(
initial_sparsity=0.30,
final_sparsity=0.80,
begin_step=1000,
end_step=5000,
)
}
model_prune = sparsity.prune_low_magnitude(model, **pruning_params)
from tensorflow_model_optimization.clustering import keras as clustering
cluster_weights = clustering.cluster_weights(model, number_of_clusters=16)
model_clustered = cluster_weights()
6. Déploiement Cross-Platform
6.1 Android AAR
python3 tensorflow/lite/tools/build_aar.py \
--input_model=model.tflite \
--target_archs=arm64-v8a,armeabi-v7a,x86_64 \
--include_ops="CONV_2D,DEPTHWISE_CONV_2D,SOFTMAX"
6.2 iOS CocoaPods / SPM
pod 'TensorFlowLiteSwift', '~> 2.14.0'
pod 'TensorFlowLiteSwift/Metal', '~> 2.14.0'
import TensorFlowLite
class EdgeInference {
private var interpreter: Interpreter
init(modelPath: String) throws {
interpreter = try Interpreter(modelPath: modelPath)
try interpreter.allocateTensors()
}
func predict(input: [Float]) throws -> [Float] {
let inputData = Data(bytes: input, count: input.count * MemoryLayout<Float>.stride)
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()
let outputData = try interpreter.output(at: 0)
return outputData.toArray(type: Float.self)
}
}
6.3 Linux ARM (Raspberry Pi, Jetson)
pip3 install tflite-runtime
git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
./tensorflow/lite/tools/make/download_dependencies.sh
./tensorflow/lite/tools/make/build_aarch64_lib.sh
g++ my_app.cpp -I tensorflow/lite/tools/make/downloads \
-L tensorflow/lite/tools/make/gen/aarch64_armv8-a/lib \
-ltensorflow-lite -ldl -lpthread -o my_app
7. Métriques et Surveillance
7.1 Métriques de déploiement TFLite
def metriques_deploiement_tflite(model_path: str) -> dict:
"""Calcule les métriques clés pour le déploiement."""
bench = TFLiteBenchmark(model_path)
latence = bench.mesurer_latence(n_iterations=200)
memoire = bench.profiler_memoire()
return {
"performance": latence,
"memoire": memoire,
"compatibilite": {
"tflite_version": tf.__version__,
"ops_count": len(bench.analyser_ops()),
"quantized": "int8" in model_path or "quant" in model_path,
}
}
Pièges Courants
-
Conversion INT8 sans calibration : dégrade la précision de 10-15%. Toujours fournir un representative_dataset représentatif des données réelles de production.
-
Ops SELECT_TF_OPS vs TFLITE_BUILTINS : SELECT_TF_OPS augmente la taille du binaire de ~2 MB. Utiliser uniquement pour les ops TF non disponibles en builtins.
-
GPU Delegate silencieux : certains ops ne sont pas supportés par le GPU Delegate (tombent silencieusement sur CPU → pas d'accélération). Vérifier avec --use_gpu=true --profiling=true.
-
Threading implicite : par défaut TFLite utilise 1 thread. Pour CPU multi-cœur, définir explicitement num_threads (optimal = nombre de cœurs P). Sur mobile, le thread count peut varier selon l'état thermique.
-
XNNPACK FP16 sur ARM : XNNPACK utilise FP16 par défaut sur ARM64 (2× throughput). Mais certains modèles peuvent perdre en précision. Forcer FP32 si nécessaire.
-
TFLite Micro vs TFLite standard : TFLite Micro a un sous-ensemble d'ops réduit. Toujours vérifier la compatibilité avant déploiement µC.
-
Buffer d'entrée non aligné : l'entrée des delegates GPU nécessite un alignement 16-bytes. Utiliser np.ascontiguousarray().
Références