PHP中的AI推理集成:利用FFI调用ONNX Runtime或TensorFlow Lite进行端侧推理

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++ 库。具体安装步骤可以参考官方文档。
  • 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. 性能优化

  • 选择合适的模型: 选择针对端侧设备优化的模型,例如量化模型。
  • 调整线程数: 通过 OrtSessionOptionsTfLiteInterpreterOptions 设置合适的线程数,可以提高推理速度。
  • 使用硬件加速: 如果设备支持硬件加速,例如 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 应用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注