PHP 中的 AI 推理集成:利用 FFI 调用 ONNX Runtime 或 TensorFlow Lite 进行端侧推理
大家好,今天我们来聊聊如何在 PHP 中集成 AI 推理能力,特别是利用 FFI(Foreign Function Interface)调用 ONNX Runtime 或 TensorFlow Lite,实现端侧推理。这可以帮助我们构建更智能、更高效的 PHP 应用,例如图像识别、自然语言处理等等,而无需依赖外部 API 或复杂的服务器端模型部署。
1. 为什么选择 FFI 和 ONNX Runtime/TensorFlow Lite?
在 PHP 中集成 AI 推理,通常有几种选择:
- 调用外部 API: 这是最简单的方式,但依赖网络连接,且可能存在延迟和隐私问题。
- 使用 PHP 扩展: 性能最好,但需要编写 C/C++ 代码,开发和维护成本较高。
- 使用 FFI: 介于两者之间,可以直接调用 C/C++ 库,无需编写扩展,性能接近原生,开发效率高。
ONNX Runtime 和 TensorFlow Lite 都是流行的端侧推理框架,具有以下优点:
- 跨平台: 支持多种操作系统和硬件平台。
- 高性能: 针对端侧设备进行了优化,推理速度快。
- 轻量级: 模型体积小,资源占用低。
- 模型兼容性: 支持多种模型格式,例如 ONNX、TensorFlow Lite。
因此,使用 FFI 调用 ONNX Runtime 或 TensorFlow Lite,是一种在 PHP 中集成 AI 推理的理想方案。
2. 环境准备
在开始之前,需要确保以下环境已经准备好:
- PHP 7.4+: FFI 需要 PHP 7.4 或更高版本。
- FFI 扩展: 确保 PHP 已经安装并启用了 FFI 扩展。可以通过
php -m | grep ffi命令检查。如果未安装,可以使用pecl install ffi命令安装。 - ONNX Runtime 或 TensorFlow Lite C/C++ 库: 需要下载并安装 ONNX Runtime 或 TensorFlow Lite 的 C/C++ 库。具体安装步骤可以参考官方文档。
- ONNX Runtime: https://onnxruntime.ai/docs/get-started/
- TensorFlow Lite: https://www.tensorflow.org/lite/guide/build_cmake
- C/C++ 编译器: 需要安装 C/C++ 编译器,例如 GCC 或 Clang,用于编译 FFI 定义文件。
示例:Ubuntu 环境下安装 ONNX Runtime
# 安装依赖
sudo apt-get update
sudo apt-get install -y build-essential cmake
# 下载 ONNX Runtime 源码
git clone --recursive https://github.com/microsoft/onnxruntime.git
# 创建构建目录
cd onnxruntime
mkdir build
cd build
# 配置构建
cmake .. -DONNX_CUSTOM_OPS=ON -DPYTHON_EXECUTABLE=/usr/bin/python3 -DCMAKE_BUILD_TYPE=Release
# 编译
make -j
# 安装 (可选,可以将库文件复制到指定目录)
sudo make install
安装完成后,需要记住 ONNX Runtime 或 TensorFlow Lite 的库文件(.so 或 .dylib)和头文件(.h)的路径,以便在 FFI 中使用。
3. FFI 定义文件
FFI 的核心在于定义 C/C++ 库的接口。我们需要创建一个 FFI 定义文件,描述 ONNX Runtime 或 TensorFlow Lite 中需要用到的函数、结构体和常量。
3.1 ONNX Runtime FFI 定义示例 (onnxruntime.ffi.php):
<?php
namespace ONNXRuntime;
use FFI;
class ORT {
public static FFI $ffi;
public static function init(string $ort_library_path): void {
self::$ffi = FFI::cdef(
<<<CTYPES
typedef struct OrtEnv OrtEnv;
typedef struct OrtSessionOptions OrtSessionOptions;
typedef struct OrtSession OrtSession;
typedef struct OrtValue OrtValue;
typedef struct OrtMemoryInfo OrtMemoryInfo;
typedef struct OrtAllocator OrtAllocator;
typedef struct OrtTensorTypeAndShapeInfo OrtTensorTypeAndShapeInfo;
typedef struct OrtTypeInfo OrtTypeInfo;
typedef struct OrtIoBinding OrtIoBinding;
typedef enum OrtDataType {
ORT_INVALID_DATA_TYPE = 0,
ORT_FLOAT,
ORT_UINT8,
ORT_INT8,
ORT_UINT16,
ORT_INT16,
ORT_INT32,
ORT_INT64,
ORT_STRING,
ORT_BOOL,
ORT_FLOAT16,
ORT_DOUBLE,
ORT_UINT32,
ORT_UINT64,
ORT_COMPLEX64,
ORT_COMPLEX128,
ORT_BFLOAT16
} OrtDataType;
typedef enum OrtErrorCode {
ORT_OK = 0,
ORT_FAIL = 1,
ORT_INVALID_ARGUMENT = 2,
ORT_NO_SUCHFILE = 3,
ORT_NOT_IMPLEMENTED = 4,
ORT_INVALID_GRAPH = 5,
ORT_NO_AVAILABLE_PROVIDER = 6,
ORT_MODEL_LOADED = 7,
ORT_NOT_ENOUGH_MEMORY = 8,
ORT_API_NOT_IMPL = 9,
ORT_UNEXPECTED = 10,
ORT_CANCELLED = 11,
ORT_INFERENCE_ERROR = 12,
ORT_MODEL_ERROR = 13,
ORT_UTR_ERROR = 14,
ORT_UNSUPPORTED_GRAPH = 15,
ORT_RUNTIME_EXCEPTION = 16
} OrtErrorCode;
const char* OrtGetLastErrorMessage();
typedef struct OrtStatus OrtStatus;
OrtStatus* OrtCreateEnv(int log_severity_level, const char* log_id, OrtEnv** out);
void OrtReleaseEnv(OrtEnv* env);
OrtStatus* OrtCreateSessionOptions(OrtSessionOptions** options);
OrtStatus* OrtSetSessionGraphOptimizationLevel(OrtSessionOptions* options, unsigned int graph_optimization_level);
void OrtReleaseSessionOptions(OrtSessionOptions* options);
OrtStatus* OrtCreateSession(OrtEnv* env, const char* model_path, const OrtSessionOptions* options, OrtSession** out);
void OrtReleaseSession(OrtSession* session);
OrtStatus* OrtCreateMemoryInfo(const char* name, int memory_type, int device_id, int mem_location, OrtMemoryInfo** out);
void OrtReleaseMemoryInfo(OrtMemoryInfo* memory_info);
OrtStatus* OrtCreateCpuMemoryInfo(OrtMemoryInfo** out, int mem_type, int device_id);
OrtStatus* OrtCreateTensorWithDataAsOrtValue(const OrtMemoryInfo* memory_info, void* data, size_t data_len, const int64_t* shape, size_t shape_len, int type, OrtValue** out);
OrtStatus* OrtCreateTensor(const int64_t* shape, size_t shape_len, int type, OrtValue** out);
OrtStatus* OrtFillStringTensor(OrtValue* ort_value, const char** s, size_t s_len);
OrtStatus* OrtGetStringTensorDataLength(const OrtValue* value, size_t* out);
OrtStatus* OrtGetStringTensorContent(const OrtValue* value, void* buffer, size_t buffer_size, size_t* offsets);
OrtStatus* OrtGetTensorMutableData(OrtValue* value, void** out);
void OrtReleaseValue(OrtValue* value);
OrtStatus* OrtGetInputCount(const OrtSession* session, size_t* out);
OrtStatus* OrtGetOutputCount(const OrtSession* session, size_t* out);
OrtStatus* OrtGetInputName(const OrtSession* session, size_t index, OrtAllocator* allocator, char** out);
OrtStatus* OrtGetOutputName(const OrtSession* session, size_t index, OrtAllocator* allocator, char** out);
OrtStatus* OrtGetInputTypeInfo(const OrtSession* session, size_t index, OrtTypeInfo** out);
OrtStatus* OrtGetOutputTypeInfo(const OrtSession* session, size_t index, OrtTypeInfo** out);
void OrtReleaseTypeInfo(OrtTypeInfo* type_info);
OrtStatus* OrtCastTypeInfoToTensorInfo(const OrtTypeInfo* type_info, OrtTensorTypeAndShapeInfo** out);
OrtStatus* OrtGetTensorElementType(const OrtTensorTypeAndShapeInfo* tensor_info, int* out);
OrtStatus* OrtGetDimensionsCount(const OrtTensorTypeAndShapeInfo* tensor_info, size_t* out);
OrtStatus* OrtGetDimensions(const OrtTensorTypeAndShapeInfo* tensor_info, int64_t* shape, size_t shape_len);
OrtStatus* OrtGetSymbolicDimensions(const OrtTensorTypeAndShapeInfo* tensor_info, char** symbolic_dimensions, size_t symbolic_dimensions_len);
OrtStatus* OrtGetStringTensorElement(const OrtValue* value, size_t index, OrtAllocator* allocator, char** output);
OrtStatus* OrtRun(const OrtSession* session, const char* const* input_names, const OrtValue* const* input_values, size_t input_count, const char* const* output_names, OrtValue* const* output_values, size_t output_count);
OrtStatus* OrtCreateIoBinding(const OrtSession* session, OrtIoBinding** out);
OrtStatus* OrtBindInput(OrtIoBinding* io_binding, const char* name, const OrtValue* value);
OrtStatus* OrtBindOutput(OrtIoBinding* io_binding, const char* name, const OrtValue* value);
OrtStatus* OrtRunWithBinding(const OrtSession* session, OrtIoBinding* io_binding, const OrtRunOptions* run_options);
OrtStatus* OrtUnbindOutput(OrtIoBinding* io_binding, const char* name, OrtValue** out);
void OrtReleaseIoBinding(OrtIoBinding* io_binding);
CTYPES
, $ort_library_path);
}
public static function checkStatus(?FFICData $status): void {
if ($status === null) {
return;
}
$code = (int) self::$ffi->cast('int', $status->code);
if ($code !== 0) {
$message = self::$ffi->OrtGetLastErrorMessage();
throw new Exception("ONNX Runtime Error: {$message} (code: {$code})");
}
}
}
3.2 TensorFlow Lite FFI 定义示例 (tensorflowlite.ffi.php):
<?php
namespace TensorFlowLite;
use FFI;
class TFLite {
public static FFI $ffi;
public static function init(string $tflite_library_path): void {
self::$ffi = FFI::cdef(
<<<CTYPES
typedef struct TfLiteModel TfLiteModel;
typedef struct TfLiteInterpreterOptions TfLiteInterpreterOptions;
typedef struct TfLiteInterpreter TfLiteInterpreter;
typedef struct TfLiteTensor TfLiteTensor;
typedef enum TfLiteType {
kTfLiteNoType = 0,
kTfLiteFloat32 = 1,
kTfLiteInt32 = 2,
kTfLiteUInt8 = 3,
kTfLiteInt64 = 4,
kTfLiteString = 5,
kTfLiteBool = 6,
kTfLiteInt16 = 7,
kTfLiteComplex64 = 8,
kTfLiteInt8 = 9,
kTfLiteFloat16 = 10,
kTfLiteFloat64 = 11,
kTfLiteComplex128 = 12,
kTfLiteUInt16 = 13,
kTfLiteUInt32 = 14,
kTfLiteUInt64 = 15,
} TfLiteType;
TfLiteModel* TfLiteModelCreateFromFile(const char* model_path);
void TfLiteModelDelete(TfLiteModel* model);
TfLiteInterpreterOptions* TfLiteInterpreterOptionsCreate();
void TfLiteInterpreterOptionsDelete(TfLiteInterpreterOptions* options);
void TfLiteInterpreterOptionsSetNumThreads(TfLiteInterpreterOptions* options, int num_threads);
TfLiteInterpreter* TfLiteInterpreterCreate(const TfLiteModel* model, const TfLiteInterpreterOptions* options);
void TfLiteInterpreterDelete(TfLiteInterpreter* interpreter);
int TfLiteInterpreterAllocateTensors(TfLiteInterpreter* interpreter);
int TfLiteInterpreterInvoke(TfLiteInterpreter* interpreter);
int TfLiteInterpreterGetInputTensorCount(const TfLiteInterpreter* interpreter);
int TfLiteInterpreterGetOutputTensorCount(const TfLiteInterpreter* interpreter);
TfLiteTensor* TfLiteInterpreterGetInputTensor(const TfLiteInterpreter* interpreter, int input_index);
TfLiteTensor* TfLiteInterpreterGetOutputTensor(const TfLiteInterpreter* interpreter, int output_index);
const char* TfLiteTensorName(const TfLiteTensor* tensor);
TfLiteType TfLiteTensorType(const TfLiteTensor* tensor);
int TfLiteTensorNumDims(const TfLiteTensor* tensor);
int TfLiteTensorDim(const TfLiteTensor* tensor, int dim_index);
void* TfLiteTensorData(TfLiteTensor* tensor);
size_t TfLiteTensorByteSize(const TfLiteTensor* tensor);
int TfLiteTensorCopyFromBuffer(TfLiteTensor* tensor, const void* input_data, size_t input_data_size);
int TfLiteTensorCopyToBuffer(const TfLiteTensor* tensor, void* output_data, size_t output_data_size);
CTYPES
, $tflite_library_path);
}
}
说明:
CTYPES字符串中定义了 C/C++ 库中的函数、结构体和常量。- 需要根据 ONNX Runtime 或 TensorFlow Lite 的 API 文档,选择需要用到的接口进行定义。
$ort_library_path和$tflite_library_path是 ONNX Runtime 或 TensorFlow Lite 库文件的路径。
4. 使用 FFI 调用 ONNX Runtime 或 TensorFlow Lite
有了 FFI 定义文件,就可以在 PHP 代码中使用 FFI 调用 ONNX Runtime 或 TensorFlow Lite 了。
4.1 使用 ONNX Runtime 进行推理
<?php
require_once 'onnxruntime.ffi.php';
use ONNXRuntimeORT;
// ONNX Runtime 库文件路径
$ort_library_path = '/path/to/onnxruntime.so'; // 替换为实际路径
// 初始化 FFI
ORT::init($ort_library_path);
$ffi = ORT::$ffi;
try {
// 1. 创建 ONNX Runtime 环境
$env = FFI::addr(FFI::new("struct OrtEnv*"));
ORT::checkStatus($ffi->OrtCreateEnv(0, "php-onnxruntime", $env));
$env = $env->deref();
// 2. 创建 SessionOptions
$session_options = FFI::addr(FFI::new("struct OrtSessionOptions*"));
ORT::checkStatus($ffi->OrtCreateSessionOptions($session_options));
$session_options = $session_options->deref();
// 设置优化级别 (可选)
ORT::checkStatus($ffi->OrtSetSessionGraphOptimizationLevel($session_options, 1)); // ORT_ENABLE_BASIC = 1
// 3. 加载 ONNX 模型
$model_path = '/path/to/your/model.onnx'; // 替换为实际模型路径
$session = FFI::addr(FFI::new("struct OrtSession*"));
ORT::checkStatus($ffi->OrtCreateSession($env, $model_path, $session_options, $session));
$session = $session->deref();
// 4. 获取输入和输出信息
$input_count = FFI::addr(FFI::new("size_t"));
ORT::checkStatus($ffi->OrtGetInputCount($session, $input_count));
$input_count = (int)$input_count->deref();
$output_count = FFI::addr(FFI::new("size_t"));
ORT::checkStatus($ffi->OrtGetOutputCount($session, $output_count));
$output_count = (int)$output_count->deref();
// 获取输入名称
$input_names_ptr = [];
$input_names = [];
$allocator_options_ptr = FFI::addr(FFI::new("struct OrtAllocator*"));
ORT::checkStatus($ffi->OrtCreateCpuMemoryInfo($allocator_options_ptr, 0, 0)); // OrtArenaAllocator = 0
$allocator = $allocator_options_ptr->deref();
for ($i = 0; $i < $input_count; ++$i) {
$input_name_ptr_ptr = FFI::addr(FFI::new("char*"));
ORT::checkStatus($ffi->OrtGetInputName($session, $i, $allocator, $input_name_ptr_ptr));
$input_names_ptr[] = $input_name_ptr_ptr;
$input_names[] = FFI::string($input_name_ptr_ptr->deref());
}
// 获取输出名称
$output_names_ptr = [];
$output_names = [];
for ($i = 0; $i < $output_count; ++$i) {
$output_name_ptr_ptr = FFI::addr(FFI::new("char*"));
ORT::checkStatus($ffi->OrtGetOutputName($session, $i, $allocator, $output_name_ptr_ptr));
$output_names_ptr[] = $output_name_ptr_ptr;
$output_names[] = FFI::string($output_name_ptr_ptr->deref());
}
// 5. 准备输入数据
$input_shape = [1, 3, 224, 224]; // 示例形状
$input_data = array_fill(0, array_product($input_shape), 0.5); // 示例数据
$input_data_type = 1; // ORT_FLOAT
$input_tensor = FFI::addr(FFI::new("struct OrtValue*"));
$shape_cdata = FFI::new("int64_t[" . count($input_shape) . "]", $input_shape);
$memory_info_ptr = FFI::addr(FFI::new("struct OrtMemoryInfo*"));
ORT::checkStatus($ffi->OrtCreateCpuMemoryInfo($memory_info_ptr, 0, 0));
$memory_info = $memory_info_ptr->deref();
$input_data_cdata = FFI::new("float[" . count($input_data) . "]", $input_data);
ORT::checkStatus($ffi->OrtCreateTensorWithDataAsOrtValue(
$memory_info,
FFI::addr($input_data_cdata),
FFI::sizeof($input_data_cdata),
$shape_cdata,
count($input_shape),
$input_data_type,
$input_tensor
));
$input_tensor = $input_tensor->deref();
// 6. 准备输出数据
$output_tensors = [];
$output_tensors_ptr = [];
for ($i = 0; $i < $output_count; ++$i) {
$output_tensor = FFI::addr(FFI::new("struct OrtValue*"));
$output_tensors_ptr[] = $output_tensor;
$output_tensors[] = $output_tensor->deref(); // Keep the dereferenced value for later use
}
// 创建输入和输出的 C array
$input_names_cdata = FFI::new("char*[" . count($input_names) . "]");
for ($i = 0; $i < count($input_names); $i++) {
$input_names_cdata[$i] = $input_names_ptr[$i]->deref(); // Assign the dereferenced pointer
}
$output_names_cdata = FFI::new("char*[" . count($output_names) . "]");
for ($i = 0; $i < count($output_names); $i++) {
$output_names_cdata[$i] = $output_names_ptr[$i]->deref(); // Assign the dereferenced pointer
}
$input_values_cdata = FFI::new("struct OrtValue*[" . count($input_names) . "]");
$input_values_cdata[0] = $input_tensor; // Assuming only one input
$output_values_cdata = FFI::new("struct OrtValue*[" . count($output_names) . "]");
for ($i = 0; $i < count($output_names); $i++) {
$output_values_cdata[$i] = $output_tensors_ptr[$i]->deref(); // Assign the dereferenced pointer
}
// 7. 运行推理
ORT::checkStatus($ffi->OrtRun(
$session,
FFI::addr($input_names_cdata),
FFI::addr($input_values_cdata),
count($input_names),
FFI::addr($output_names_cdata),
FFI::addr($output_values_cdata),
count($output_names)
));
// 8. 获取输出数据
$output_data_ptr = FFI::addr(FFI::new("void*"));
ORT::checkStatus($ffi->OrtGetTensorMutableData($output_values_cdata[0], $output_data_ptr));
$output_data = $output_data_ptr->deref();
// 获取输出形状信息
$output_type_info_ptr = FFI::addr(FFI::new("struct OrtTypeInfo*"));
ORT::checkStatus($ffi->OrtGetOutputTypeInfo($session, 0, $output_type_info_ptr));
$output_type_info = $output_type_info_ptr->deref();
$output_tensor_info_ptr = FFI::addr(FFI::new("struct OrtTensorTypeAndShapeInfo*"));
ORT::checkStatus($ffi->OrtCastTypeInfoToTensorInfo($output_type_info, $output_tensor_info_ptr));
$output_tensor_info = $output_tensor_info_ptr->deref();
$output_dims_count_ptr = FFI::addr(FFI::new("size_t"));
ORT::checkStatus($ffi->OrtGetDimensionsCount($output_tensor_info, $output_dims_count_ptr));
$output_dims_count = (int)$output_dims_count_ptr->deref();
$output_shape = FFI::new("int64_t[" . $output_dims_count . "]");
ORT::checkStatus($ffi->OrtGetDimensions($output_tensor_info, FFI::addr($output_shape), $output_dims_count));
$output_shape_array = [];
for ($i = 0; $i < $output_dims_count; $i++) {
$output_shape_array[] = $output_shape[$i];
}
$output_element_count = array_product($output_shape_array);
$output_float_data = FFI::new("float[" . $output_element_count . "]");
ORT::checkStatus($ffi->OrtGetTensorMutableData($output_values_cdata[0], FFI::addr($output_float_data)));
$output_array = [];
for ($i = 0; $i < $output_element_count; $i++) {
$output_array[] = $output_float_data[$i];
}
// 输出结果
print_r($output_array);
// 9. 释放资源
for ($i = 0; $i < $input_count; ++$i) {
$ffi->OrtFree($input_names_ptr[$i]->deref());
}
for ($i = 0; $i < $output_count; ++$i) {
$ffi->OrtFree($output_names_ptr[$i]->deref());
}
$ffi->OrtReleaseValue($input_tensor);
for ($i = 0; $i < $output_count; ++$i) {
$ffi->OrtReleaseValue($output_values_cdata[$i]);
}
$ffi->OrtReleaseMemoryInfo($memory_info);
$ffi->OrtReleaseSession($session);
$ffi->OrtReleaseSessionOptions($session_options);
$ffi->OrtReleaseEnv($env);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . PHP_EOL;
}
4.2 使用 TensorFlow Lite 进行推理
<?php
require_once 'tensorflowlite.ffi.php';
use TensorFlowLiteTFLite;
// TensorFlow Lite 库文件路径
$tflite_library_path = '/path/to/libtensorflowlite.so'; // 替换为实际路径
// 初始化 FFI
TFLite::init($tflite_library_path);
$ffi = TFLite::$ffi;
try {
// 1. 加载 TensorFlow Lite 模型
$model_path = '/path/to/your/model.tflite'; // 替换为实际模型路径
$model = $ffi->TfLiteModelCreateFromFile($model_path);
if ($model === null) {
throw new Exception("Failed to load model from file: " . $model_path);
}
// 2. 创建 InterpreterOptions
$options = $ffi->TfLiteInterpreterOptionsCreate();
if ($options === null) {
throw new Exception("Failed to create interpreter options.");
}
// 设置线程数 (可选)
$ffi->TfLiteInterpreterOptionsSetNumThreads($options, 4);
// 3. 创建 Interpreter
$interpreter = $ffi->TfLiteInterpreterCreate($model, $options);
if ($interpreter === null) {
throw new Exception("Failed to create interpreter.");
}
// 4. 分配 Tensors
if ($ffi->TfLiteInterpreterAllocateTensors($interpreter) !== 0) {
throw new Exception("Failed to allocate tensors.");
}
// 5. 获取输入和输出 Tensor 数量
$input_count = $ffi->TfLiteInterpreterGetInputTensorCount($interpreter);
$output_count = $ffi->TfLiteInterpreterGetOutputTensorCount($interpreter);
// 6. 获取输入 Tensor
$input_index = 0; // 假设只有一个输入 Tensor
$input_tensor = $ffi->TfLiteInterpreterGetInputTensor($interpreter, $input_index);
if ($input_tensor === null) {
throw new Exception("Failed to get input tensor.");
}
// 获取输入 Tensor 的信息
$input_name = FFI::string($ffi->TfLiteTensorName($input_tensor));
$input_type = $ffi->TfLiteTensorType($input_tensor);
$input_num_dims = $ffi->TfLiteTensorNumDims($input_tensor);
$input_dims = [];
for ($i = 0; $i < $input_num_dims; $i++) {
$input_dims[] = $ffi->TfLiteTensorDim($input_tensor, $i);
}
$input_byte_size = $ffi->TfLiteTensorByteSize($input_tensor);
// 7. 准备输入数据
$input_shape = $input_dims; // 示例形状
$input_data = array_fill(0, array_product($input_shape), 0.5); // 示例数据
// 根据数据类型创建 CData
switch ($input_type) {
case 1: // kTfLiteFloat32
$input_data_cdata = FFI::new("float[" . count($input_data) . "]", $input_data);
break;
case 2: // kTfLiteInt32
$input_data_cdata = FFI::new("int[" . count($input_data) . "]", $input_data);
break;
// ... 其他数据类型
default:
throw new Exception("Unsupported input data type: " . $input_type);
}
// 8. 将输入数据复制到 Tensor
$ffi->TfLiteTensorCopyFromBuffer($input_tensor, FFI::addr($input_data_cdata), $input_byte_size);
// 9. 运行推理
if ($ffi->TfLiteInterpreterInvoke($interpreter) !== 0) {
throw new Exception("Failed to invoke interpreter.");
}
// 10. 获取输出 Tensor
$output_index = 0; // 假设只有一个输出 Tensor
$output_tensor = $ffi->TfLiteInterpreterGetOutputTensor($interpreter, $output_index);
if ($output_tensor === null) {
throw new Exception("Failed to get output tensor.");
}
// 获取输出 Tensor 的信息
$output_name = FFI::string($ffi->TfLiteTensorName($output_tensor));
$output_type = $ffi->TfLiteTensorType($output_tensor);
$output_num_dims = $ffi->TfLiteTensorNumDims($output_tensor);
$output_dims = [];
for ($i = 0; $i < $output_num_dims; $i++) {
$output_dims[] = $ffi->TfLiteTensorDim($output_tensor, $i);
}
$output_byte_size = $ffi->TfLiteTensorByteSize($output_tensor);
// 11. 获取输出数据
$output_data = FFI::new("char[" . $output_byte_size . "]");
$ffi->TfLiteTensorCopyToBuffer($output_tensor, FFI::addr($output_data), $output_byte_size);
// 根据数据类型解析 CData
$output_array = [];
switch ($output_type) {
case 1: // kTfLiteFloat32
$float_data = FFI::cast("float*", $output_data);
$num_elements = $output_byte_size / FFI::sizeof("float");
for ($i = 0; $i < $num_elements; $i++) {
$output_array[] = $float_data[$i];
}
break;
case 2: // kTfLiteInt32
$int_data = FFI::cast("int*", $output_data);
$num_elements = $output_byte_size / FFI::sizeof("int");
for ($i = 0; $i < $num_elements; $i++) {
$output_array[] = $int_data[$i];
}
break;
// ... 其他数据类型
default:
throw new Exception("Unsupported output data type: " . $output_type);
}
// 输出结果
print_r($output_array);
// 12. 释放资源
$ffi->TfLiteInterpreterDelete($interpreter);
$ffi->TfLiteInterpreterOptionsDelete($options);
$ffi->TfLiteModelDelete($model);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . PHP_EOL;
}
说明:
- 需要根据具体的模型,调整输入数据的形状、类型和值。
- 代码中包含了错误处理,可以更好地调试和排查问题。
- 释放资源非常重要,可以避免内存泄漏。
5. 性能优化
- 选择合适的模型: 选择针对端侧设备优化的模型,例如量化模型。
- 调整线程数: 通过
OrtSessionOptions或TfLiteInterpreterOptions设置合适的线程数,可以提高推理速度。 - 使用硬件加速: 如果设备支持硬件加速,例如 GPU 或 NPU,可以使用 ONNX Runtime 或 TensorFlow Lite 的硬件加速后端。
- 避免重复加载模型: 将模型加载到内存中,避免每次推理都重新加载。
6. 注意事项
- 类型转换: PHP 是弱类型语言,需要注意类型转换,确保数据类型与 ONNX Runtime 或 TensorFlow Lite 的要求一致。
- 内存管理: FFI 调用 C/C++ 库,需要手动管理内存,避免内存泄漏。
- 错误处理: 充分的错误处理可以帮助我们更好地调试和排查问题。
- 安全性: 注意模型的来源,避免加载恶意模型。
7. 总结
通过 FFI 调用 ONNX Runtime 或 TensorFlow Lite,可以在 PHP 中轻松集成 AI 推理能力,实现端侧推理。这可以帮助我们构建更智能、更高效的 PHP 应用,而无需依赖外部 API 或复杂的服务器端模型部署。希望今天的讲解对大家有所帮助。
8. 进一步探索
- 模型转换: 使用 ONNX 或 TensorFlow Lite 的转换工具,将其他格式的模型转换为 ONNX 或 TensorFlow Lite 格式。
- 自定义算子: 如果 ONNX Runtime 或 TensorFlow Lite 中没有需要的算子,可以自定义算子。
- 与其他 PHP 框架集成: 将 FFI 集成到 Laravel、Symfony 等 PHP 框架中,可以更方便地构建 AI 应用。