Do thư viện toán tử tích hợp LiteRT chỉ hỗ trợ một số hạn chế số lượng toán tử TensorFlow, không phải mô hình nào cũng có thể chuyển đổi. Để biết thông tin chi tiết, tham chiếu đến khả năng tương thích của toán tử.
Để cho phép chuyển đổi, người dùng có thể cung cấp cách triển khai tuỳ chỉnh của riêng họ cho một toán tử TensorFlow không được hỗ trợ trong LiteRT, còn được gọi là toán tử tuỳ chỉnh. Nếu thay vào đó, bạn muốn kết hợp một loạt các cookie không được hỗ trợ (hoặc được hỗ trợ) Các toán tử TensorFlow thành một toán tử tuỳ chỉnh tối ưu hoá kết hợp, tham chiếu đến hợp nhất toán tử.
Quy trình sử dụng toán tử tuỳ chỉnh bao gồm 4 bước.
Tạo Mô hình TensorFlow. Đảm bảo thuộc tính Đã lưu Mô hình (hay Graph Def) tham chiếu đến toán tử LiteRT được đặt tên chính xác.
Chuyển đổi sang Mô hình LiteRT. Hãy đảm bảo bạn đặt đúng thuộc tính trình chuyển đổi LiteRT để chuyển đổi mô hình thành công.
Tạo và đăng ký toán tử. Chiến dịch này để thời gian chạy LiteRT biết cách ánh xạ toán tử và trong biểu đồ thành mã C/C++ có thể thực thi.
Kiểm tra và lập hồ sơ cho nhà cung cấp dịch vụ. Nếu bạn chỉ muốn thử nghiệm toán tử tuỳ chỉnh của mình, tốt nhất bạn nên tạo một mô hình với chỉ có toán tử tuỳ chỉnh của bạn và sử dụng benchmark_model .
Hãy xem qua ví dụ toàn diện về cách chạy một mô hình có tuỳ chỉnh
toán tử tf.atan
(có tên là Atan
, tham khảo bài viết Tạo Mô hình TensorFlow).
được hỗ trợ trong TensorFlow, nhưng không được hỗ trợ trong LiteRT.
Toán tử Văn bản TensorFlow là một ví dụ về toán tử tuỳ chỉnh. Xem Xem hướng dẫn Chuyển đổi văn bản TF sang LiteRT để xem ví dụ về mã.
Ví dụ: Toán tử Atan
tuỳ chỉnh
Hãy xem một ví dụ về việc hỗ trợ toán tử TensorFlow
LiteRT không có. Giả sử chúng ta đang sử dụng toán tử Atan
và
chúng ta đang xây dựng mô hình rất đơn giản cho hàm y = atan(x + offset)
, trong đó
offset
có thể huấn luyện.
Tạo Mô hình TensorFlow
Đoạn mã sau đây huấn luyện một mô hình TensorFlow đơn giản. Mô hình này chỉ
chứa một toán tử tuỳ chỉnh có tên Atan
. Đây là hàm y = atan(x +
offset)
, trong đó offset
có thể huấn luyện.
import tensorflow as tf
# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)
# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
return tf.atan(x + offset, name="Atan")
# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
with tf.GradientTape() as t:
predicted_y = atan(x)
loss = tf.reduce_sum(tf.square(predicted_y - y))
grads = t.gradient(loss, [offset])
optimizer.apply_gradients(zip(grads, [offset]))
for i in range(1000):
train(x, y)
print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905
Tại thời điểm này, nếu bạn cố tạo mô hình LiteRT bằng giá trị mặc định cờ chuyển đổi, bạn sẽ nhận được thông báo lỗi sau:
Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.
Chuyển đổi sang Mô hình LiteRT
Tạo mô hình LiteRT với các toán tử tuỳ chỉnh bằng cách đặt bộ chuyển đổi
thuộc tính allow_custom_ops
như minh hoạ dưới đây:
converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan) converter.allow_custom_ops = True tflite_model = converter.convert()
Tại thời điểm này, nếu bạn chạy chương trình bằng trình thông dịch mặc định bằng các lệnh như sau:
interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()
Bạn vẫn sẽ gặp lỗi:
Encountered unresolved custom op: Atan.
Tạo và đăng ký toán tử.
#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"
Toán tử tuỳ chỉnh LiteRT được xác định bằng một API thuần C đơn giản mà
bao gồm loại mờ (TfLiteRegistrationExternal
) và các hàm có liên quan.
TfLiteRegistrationExternal
là một loại mờ:
typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;
TfLiteRegistrationExternal
lưu trữ danh tính và cách triển khai của toán tử.
(Lưu ý rằng toán tử khác với toán hạng của nó, vốn được lưu trữ trong
Các nút đồ thị LiteRT cho các nút gọi toán tử.)
Các thực thể này được tạo bằng các lệnh gọi đến
TfLiteRegistrationExternalCreate
và có thể bị huỷ bằng cách gọi
TfLiteRegistrationExternalDelete
.
Thông tin nhận dạng của toán tử được đặt thông qua các tham số trong hàm khởi tạo
TfLiteRegistrationExternalCreate
:
TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
TfLiteBuiltinOperator builtin_code, // Normally `TfLiteBuiltinCustom`.
const char* custom_name, // The name of the custom op.
int version // Normally `1` for the first version of a custom op.
);
Việc triển khai toán tử có thể xác định "phương thức" bằng các chữ ký sau.
Tất cả các phương thức này là không bắt buộc, nhưng để toán tử thành công
được đánh giá, thì việc triển khai toán tử cần xác định và thiết lập (bằng phương thức setter
hàm) ít nhất là phương thức Prepare
và Invoke
.
// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);
// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);
// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);
// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);
// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node);
Tên hàm (hoặc tiền tố không gian tên, đối với C++) trong quá trình triển khai hoạt động của bạn không cần phải khớp với tên hàm trong đoạn mã ở trên, vì TF API hoạt động tuỳ chỉnh Lite sẽ chỉ sử dụng địa chỉ của chúng. Chúng tôi khuyên bạn nên khai báo chúng trong không gian tên ẩn danh hoặc dưới dạng hàm tĩnh.
Tuy nhiên, bạn nên đưa tên toán tử của mình vào dưới dạng không gian tên hoặc tiền tố trên các tên hàm này:
C++
namespace my_namespace::my_custom_op { void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of Free, Prepare, and Invoke ... }
C
void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } // ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and // MyCustomOpInvoke.
Vì đây là API C, nên các "phương thức" này được triển khai dưới dạng con trỏ hàm C trong
loại TfLiteRegistrationExternal
, được đặt bằng cách truyền địa chỉ của
các hàm triển khai của bạn với các hàm setter tương ứng
TfLiteRegistrationExternalSet
MethodName:
void TfLiteRegistrationExternalSetInit(
TfLiteRegistrationExternal* registration,
void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
size_t length));
void TfLiteRegistrationExternalSetFree(
TfLiteRegistrationExternal* registration,
void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
TfLiteRegistrationExternal* registration,
TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
TfLiteRegistrationExternal* registration,
TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
TfLiteRegistrationExternal* registration,
struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
TfLiteOpaqueNode* node));
Tham khảo
common.h
để biết thông tin chi tiết về TfLiteContext
và TfLiteNode
. TfLiteContext
đưa ra lỗi
báo cáo và quyền truy cập vào các đối tượng toàn cầu, bao gồm tất cả các tensor.
TfLiteNode
cho phép các phương thức triển khai toán tử truy cập vào dữ liệu đầu vào và đầu ra của chúng.
Khi tải một mô hình, trình phiên dịch sẽ gọi phương thức Init()
một lần cho mỗi lần
trong biểu đồ. Một Init()
nhất định sẽ được gọi nhiều lần nếu hoạt động là
được sử dụng nhiều lần trong biểu đồ. Đối với hoạt động tuỳ chỉnh, vùng đệm cấu hình sẽ là
được cung cấp, chứa một bộ đệm linh hoạt giúp ánh xạ tên tham số với giá trị của các tham số đó. Chiến lược phát hành đĩa đơn
bộ đệm trống đối với hoạt động tích hợp vì trình thông dịch đã phân tích cú pháp tệp
thông số hoạt động. Các phương thức triển khai kernel yêu cầu trạng thái sẽ được khởi chạy
tại đây và chuyển quyền sở hữu cho người gọi. Đối với mỗi cuộc gọi Init()
, sẽ có
một lệnh gọi tương ứng đến Free()
, cho phép các phương thức triển khai loại bỏ
vùng đệm mà chúng có thể đã phân bổ trong Init()
.
Bất cứ khi nào tensor đầu vào được đổi kích thước, trình phiên dịch sẽ chuyển sang
biểu đồ thông báo về việc triển khai thay đổi này. Điều này mang đến cho họ cơ hội
đổi kích thước bộ đệm nội bộ, kiểm tra tính hợp lệ của các hình dạng và kiểu đầu vào, và
tính toán lại các hình dạng đầu ra. Bạn có thể thực hiện việc này thông qua phương thức Prepare()
và
có thể truy cập vào trạng thái của chúng bằng cách sử dụng
TfLiteOpaqueNodeGetUserData(node)
.
Cuối cùng, mỗi lần suy luận chạy, trình thông dịch truyền tải biểu đồ gọi
phương thức Invoke()
và ở đây, trạng thái này cũng có sẵn dưới dạng
TfLiteOpaqueNodeGetUserData(node)
.
Có thể triển khai hoạt động tuỳ chỉnh bằng cách xác định các "phương thức" đó rồi sau đó
định nghĩa một hàm trả về bản sao của TfLiteRegistrationExternal
được tạo bằng cách gọi TfLiteRegistrationExternalCreate
, sau đó hàm liên quan
phương thức setter:
C++
namespace my_namespace::my_custom_op { namespace { void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } void Free(TfLiteOpaqueContext* context, void* buffer) { ... } TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... } TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {... } }; const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* my_custom_op = ()[] { TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1); TfLiteRegistrationExternalSetInit(r, Init); TfLiteRegistrationExternalSetFree(r, Free); TfLiteRegistrationExternalSetPrepare(r, Prepare); TfLiteRegistrationExternalSetInvoke(r, Eval); return r; }; return my_custom_op; } const TfLiteRegistration* MyCustomOpRegistration() { static const TfLiteRegistration my_custom_op { .registration_external = MyCustomOpRegistrationExternal(); }; return my_custom_op; } } // namespace my_namespace
C
static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer, size_t length) { ... } static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... } static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... } static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {... } static TfLiteRegistrationExternal* MyCustomOpCreate() { const TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1); TfLiteRegistrationExternalSetInit(r, MyCustomOpInit); TfLiteRegistrationExternalSetFree(r, MyCustomOpFree); TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare); TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval); return r; } const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate(); return my_custom_op; } const TfLiteRegistration MyCustomOpRegistration() { static const TfLiteRegistration my_custom_op { .registration_external = MyCustomOpRegistrationExternal(); }; return my_custom_op; }
Xin lưu ý rằng việc đăng ký không tự động và là lệnh gọi rõ ràng đến
Bạn cần tạo hàm MyCustomOpRegistration
(xem thông tin chi tiết ở bên dưới). Trong khi
BuiltinOpResolver
tiêu chuẩn (có sẵn từ mục tiêu :builtin_ops
) sẽ lấy
phụ trách đăng ký nội dung tích hợp, hoạt động tuỳ chỉnh sẽ phải được thu thập trong
các thư viện tuỳ chỉnh riêng biệt.
Xác định hạt nhân trong thời gian chạy LiteRT
Tất cả những gì chúng ta cần làm để sử dụng op trong LiteRT là xác định hai hàm
(Prepare
và Eval
) và một phần ba để tạo TfLiteRegistrationExternal
:
C++
namespace atan_op { namespace { TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1); TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1); const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); int num_dims = TfLiteOpaqueTensorNumDimensions(input); TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims); for (int i=0; i < num_dims; ++i) { output_size->data[i] = input->dims->data[i]; } return TfLiteOpaqueContextResizeTensor(context, output, output_size); } TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); float* input_data = static_cast<float*>(TfLiteOpaqueTensorData(input)); float* output_data = static_cast<float*>(TfLiteOpaqueTensorData(output)); size_t count = 1; int num_dims = TfLiteOpaqueTensorNumDimensions(input); for (int i = 0; i < num_dims; ++i) { count *= input->dims->data[i]; } for (size_t i = 0; i < count; ++i) { output_data[i] = atan(input_data[i]); } return kTfLiteOk; } } // anonymous namespace const TfLiteRegistrationExternal* AtanOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* atan_op = ()[] { auto* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteRegistrationExternalSetPrepare(r, Prepare); TfLiteRegistrationExternalSetInvoke(r, Eval); return r; }; return atan_op; } const TfLiteRegistration AtanOpRegistration() { static const TfLiteRegistration atan_op { .registration_external = AtanOpRegistrationExternal(); }; return atan_op; } } // namespace atan_op
C
static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1); TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1); const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); int num_dims = TfLiteOpaqueTensorNumDimensions(input); TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims); for (int i = 0; i < num_dims; ++i) { output_size->data[i] = input->dims->data[i]; } return TfLiteOpaqueContextResizeTensor(context, output, output_size); } static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0); TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0); float* input_data = static_cast<float*>(TfLiteOpaqueTensorData(input)); float* output_data = static_cast<float*>(TfLiteOpaqueTensorData(output)); size_t count = 1; int num_dims = TfLiteOpaqueTensorNumDimensions(input); for (int i = 0; i < num_dims; ++i) { count *= input->dims->data[i]; } for (size_t i = 0; i < count; ++i) { output_data[i] = atan(input_data[i]); } return kTfLiteOk; } static const TfLiteRegistrationExternal* AtanOpCreate() { TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate( kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1); TfLiteRegistrationExternalSetPrepare(r, Prepare); TfLiteRegistrationExternalSetInvoke(r, Eval); return r; } const TfLiteRegistrationExternal* AtanOpRegistrationExternal() { // Singleton instance, intentionally never destroyed. static const TfLiteRegistrationExternal* atan_op = AtanOpCreate(); return atan_op; } const TfLiteRegistration AtanOpRegistration() { static const TfLiteRegistration atan_op { .registration_external = AtanOpRegistrationExternal(); }; return atan_op; }
Khi khởi tạo OpResolver
, hãy thêm vận hành tuỳ chỉnh vào trình phân giải (xem
bên dưới để xem ví dụ). Thao tác này sẽ đăng ký nhà cung cấp dịch vụ với LiteRT để
mà LiteRT có thể sử dụng cách triển khai mới. Lưu ý rằng hai URL cuối cùng
đối số trong TfLiteRegistration
tương ứng với AtanPrepare
và AtanEval
các hàm mà bạn đã xác định cho hoạt động tuỳ chỉnh. Nếu bạn đã dùng AtanInit
và AtanFree
để khởi tạo các biến được sử dụng trong hoạt động và để giải phóng dung lượng,
thì chúng sẽ được thêm vào hai đối số đầu tiên của
TfLiteRegistration
; các đối số đó được đặt thành nullptr
trong ví dụ này.
Đăng ký toán tử với thư viện kernel
Bây giờ, chúng ta cần đăng ký toán tử với thư viện nhân. Việc này được thực hiện bằng
OpResolver
. Ở hậu trường, trình phiên dịch sẽ tải thư viện
hạt nhân sẽ được chỉ định để thực thi từng toán tử trong mô hình.
Mặc dù thư viện mặc định chỉ chứa các hạt nhân tích hợp, nhưng bạn có thể
thay thế/tăng cường bằng toán tử hoạt động của thư viện tuỳ chỉnh.
Lớp OpResolver
giúp dịch mã và tên toán tử thành tên và mã toán tử thực tế
mã, được xác định như sau:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
Lưu ý rằng để có khả năng tương thích ngược, lớp này sẽ sử dụng loại cụ thể cũ
TfLiteRegistration
thay vì loại mờ TfLiteRegistrationExternal
,
nhưng cấu trúc TfLiteRegistration
chứa trường registration_external
của
loại TfLiteRegistrationExternal*
.
Các lớp MutableOpResolver
và BuiltinOpResolver
bắt nguồn từ
OpResolver
:
class MutableOpResolver : public OpResolver {
public:
MutableOpResolver(); // Constructs an initially empty op resolver.
void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
void AddAll(const MutableOpResolver& other);
...
};
class BuiltinOpResolver : public MutableOpResolver {
public:
BuiltinOpResolver(); // Constructs an op resolver with all the builtin ops.
};
Việc sử dụng thông thường (không có hoạt động tuỳ chỉnh) đòi hỏi bạn phải sử dụng BuiltinOpResolver
và ghi:
tflite::ops::builtin::BuiltinOpResolver resolver;
Để thêm hoạt động tuỳ chỉnh được tạo ở trên, bạn có thể sử dụng MutableOpResolver
,
và gọi AddCustom
(trước khi truyền trình phân giải đến
InterpreterBuilder
):
tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());
Nếu nhóm hoạt động tích hợp sẵn được cho là quá lớn, thì OpResolver
mới có thể bị
được tạo dựa trên một tập hợp con hoạt động nhất định, có thể chỉ những hoạt động được chứa
trong một mô hình nhất định. Điều này tương đương với quy trình đăng ký có chọn lọc của TensorFlow
(và một phiên bản đơn giản của thư mục này có sẵn trong thư mục tools
).
Nếu muốn xác định các toán tử tuỳ chỉnh trong Java, hiện tại bạn cần phải tạo lớp JNI tuỳ chỉnh của riêng bạn và biên dịch AAR của riêng bạn trong mã jni này. Tương tự, nếu muốn xác định các toán tử này có sẵn trong Python, bạn có thể hãy đặt thông tin đăng ký của bạn trong Mã trình bao bọc Python.
Lưu ý rằng bạn có thể tuân theo quy trình tương tự như trên để hỗ trợ một tập hợp
thay vì một toán tử đơn lẻ. Bạn chỉ cần thêm số lượng toán tử AddCustom
tối đa
khi bạn cần. Ngoài ra, MutableOpResolver
cũng cho phép bạn ghi đè
bằng cách sử dụng AddBuiltin
để triển khai tích hợp.
Kiểm thử và phân tích tài nguyên cho nhà cung cấp dịch vụ
Để phân tích hiệu suất của bạn bằng công cụ đo điểm chuẩn LiteRT, bạn có thể sử dụng
công cụ mô hình điểm chuẩn
cho LiteRT. Đối với mục đích thử nghiệm, bạn có thể tạo bản dựng cục bộ của
LiteRT nhận biết được hoạt động tuỳ chỉnh của bạn bằng cách thêm AddCustom
thích hợp
gọi (như trình bày ở trên) đến
register.cc
Các phương pháp hay nhất
Tối ưu hoá quá trình phân bổ bộ nhớ và phân bổ lại một cách thận trọng. Đang phân bổ bộ nhớ trong
Prepare
hiệu quả hơn trongInvoke
và phân bổ bộ nhớ trước khi lặp lại hiệu quả hơn trong mỗi lần lặp lại. Sử dụng dữ liệu tensor tạm thời thay vì tự quyết định (xem mục 2). Thay vào đó, hãy dùng con trỏ/tham chiếu sao chép nhiều nhất có thể.Nếu cấu trúc dữ liệu vẫn tồn tại trong toàn bộ quá trình hoạt động, bạn nên phân bổ trước bộ nhớ bằng các tensor tạm thời. Bạn có thể cần phải sử dụng Cấu trúc OpData để tham chiếu các chỉ mục tensor trong các hàm khác. Xem ví dụ trong nhân cho tích chập. Dưới đây là đoạn mã mẫu.
struct MyOpData { int temp_tensor_index; ... }; void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length) { auto* op_data = new MyOpData{}; ... return op_data; } void Free(TfLiteOpaqueContext* context, void* buffer) { ... delete reinterpret_cast<MyOpData*>(buffer); } TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... auto* op_data = reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node)); const int num_temporaries = 1; int temporary_tensor_indices[num_temporaries]; TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate(); TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32); TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw); TfLiteOpaqueContextAddTensor(context, builder, &temporary_tensor_indices[0]); TfLiteOpaqueTensorBuilderDelete(builder); TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices, num_temporaries); op_data->temp_tensor_index = temporary_tensor_indices[0]; ... return kTfLiteOk; } TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) { ... auto* op_data = reinterpret_cast<MyOpData*>( TfLiteOpaqueNodeGetUserData(node)); TfLiteOpaqueTensor* temp_tensor = TfLiteOpaqueContextGetOpaqueTensor(context, op_data->temp_tensor_index); TF_LITE_OPAQUE_ENSURE(context, TfLiteTensorType(temp_tensor) == kTfLiteFloat32); TF_LITE_OPAQUE_ENSURE(context, TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw); void *temp_data = TfLiteTensorData(temp_tensor); TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr); ... return kTfLiteOk; }
Nếu bộ nhớ không bị lãng phí quá nhiều, hãy ưu tiên sử dụng kích thước tĩnh cố định (hoặc một
std::vector
được phân bổ trước trongResize
) thay vì sử dụng phân bổ độngstd::vector
mỗi lần lặp lại quá trình thực thi.Tránh tạo thực thể cho các mẫu vùng chứa thư viện chuẩn chưa có vì chúng ảnh hưởng đến kích thước nhị phân. Ví dụ: nếu bạn cần một
std::map
trong toán tử không tồn tại trong các hạt nhân khác, sử dụng mộtstd::vector
có ánh xạ lập chỉ mục trực tiếp có thể hoạt động trong khi vẫn duy trì kích thước nhị phân nhỏ. Xem các hạt nhân khác sử dụng gì để thu thập thông tin chi tiết (hoặc hỏi).Kiểm tra con trỏ đến bộ nhớ do
malloc
trả về. Nếu con trỏ này lànullptr
, bạn không nên thực hiện thao tác nào bằng con trỏ đó. Nếu bạnmalloc
trong một hàm và có lỗi thoát, giải phóng bộ nhớ trước bạn thoát.Sử dụng
TF_LITE_OPAQUE_ENSURE(context, condition)
để kiểm tra một . Mã của bạn không được để bộ nhớ bị treo khiTF_LITE_OPAQUE_ENSURE
được sử dụng, tức là phải dùng các macro này trước mọi tài nguyên được phân bổ sẽ bị rò rỉ.