1. Cài đặt Runtime và Môi trường C++ cho TensorFlow Lite
Trước khi viết code, bạn cần cài đặt các thư viện C++ cốt lõi của TensorFlow Lite để có thể biên dịch engine suy luận. Chúng ta sẽ sử dụng CMake để quản lý các dependency này.
Thư mục làm việc hiện tại của bạn là ~/ai-inference-engine. Tạo thư mục src và clone các dependency cần thiết.
mkdir -p ~/ai-inference-engine/src
cd ~/ai-inference-engine/src
git clone https://github.com/tensorflow/tensorflow.git --depth 1
cd tensorflow
# Chỉ cần thư viện TFLite C++ core, không cần build toàn bộ TF
git checkout 2.17.0 # Fix version để đảm bảo tính ổn định cho C++
Thao tác này tải về phiên bản TensorFlow cụ thể. Chúng ta cần phiên bản này để lấy source code của TFLite Interpreter C++.
Kiểm tra xem thư mục tensorflow/lite đã tồn tại chưa.
ls -la ~/ai-inference-engine/src/tensorflow/tensorflow/lite
Bạn sẽ thấy các thư mục core, kernels, tools và file interpreter.h trong thư mục đó.
Cấu hình CMake cho TFLite C++
Tạo file cấu hình CMakeLists.txt tại thư mục gốc ~/ai-inference-engine để thiết lập môi trường build.
File này sẽ chỉ dẫn CMake tìm kiếm thư viện TFLite và cấu hình biên dịch cho mục tiêu WebAssembly (wasm32).
cd ~/ai-inference-engine
cat > CMakeLists.txt
File CMakeLists.txt đã được tạo. Nó định nghĩa thư viện static từ các file nguồn TFLite và link nó vào chương trình inference_engine.cpp của bạn.
Verify bằng cách chạy cmake để xem có lỗi syntax nào không.
mkdir -p build && cd build
cmake ..
cd ..
Đầu ra phải hiển thị -- Configuring done và -- Generating done mà không có lỗi.
2. Viết Wrapper C++ để tải mô hình và thực hiện Inference
Bây giờ chúng ta sẽ viết file inference_engine.cpp. Đây là file wrapper quan trọng để giao tiếp giữa WebAssembly và TFLite Interpreter.
File này sẽ chứa các hàm C được export ra cho JavaScript gọi (dùng extern "C") để tải mô hình, tạo buffer đầu vào và chạy inference.
Tạo file src/inference_engine.cpp với nội dung sau:
cat > src/inference_engine.cpp AllocateTensors() != kTfLiteOk) {
return -3;
}
// Lấy kích thước input và output
input_size = interpreter->typed_input_tensor(0)->size();
output_size = interpreter->typed_output_tensor(0)->size();
return 0; // Success
} catch (const std::exception& e) {
return -99;
}
}
// Hàm inference: Nhận input float array, trả về output float array
EMSCRIPTEN_KEEPALIVE
int run_inference(const float* input_data, float* output_data) {
if (!interpreter) {
return -1;
}
// Copy input data vào tensor của TFLite
float* input_tensor = interpreter->typed_input_tensor(0);
std::memcpy(input_tensor, input_data, input_size * sizeof(float));
// Chạy inference
if (interpreter->Invoke() != kTfLiteOk) {
return -2;
}
// Copy output data từ tensor ra memory buffer
const float* output_tensor = interpreter->typed_output_tensor(0);
std::memcpy(output_data, output_tensor, output_size * sizeof(float));
return 0;
}
// Hàm lấy kích thước input (dùng để tạo buffer JS)
EMSCRIPTEN_KEEPALIVE
int get_input_size() {
return input_size;
}
// Hàm lấy kích thước output
EMSCRIPTEN_KEEPALIVE
int get_output_size() {
return output_size;
}
EOF
Code này sử dụng EMSCRIPTEN_KEEPALIVE để đảm bảo các hàm không bị tối ưu hóa (tree-shaking) khi biên dịch. Nó xử lý việc tải mô hình và chạy inference bằng các con trỏ C.
Verify bằng cách kiểm tra cú pháp C++.
g++ -fsyntax-only -I src/tensorflow/tensorflow/lite src/inference_engine.cpp -DEMSCRIPTEN
Không có lỗi nào xuất hiện (no error output) nghĩa là cú pháp đúng.
3. Sử dụng Wasm-pack để biên dịch C++ sang WebAssembly
Thay vì dùng Emscripten trực tiếp (cần cấu hình phức tạp), chúng ta dùng wasm-pack để đóng gói C++ thành module WebAssembly chuẩn cho Rust/Web ecosystem.
Tuy nhiên, wasm-pack mặc định dùng Rust. Để dùng với C++, chúng ta cần cấu hình CMakeLists.txt để tạo target WASM và dùng emcc (Emscripten C Compiler) làm backend.
Đầu tiên, cài đặt Emscripten SDK nếu chưa có.
git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
cd ~/emsdk
./emsdk install latest
./emsdk activate latest
source ~/emsdk/emsdk_env.sh
Các lệnh này tải về và kích hoạt môi trường Emscripten. Biến môi trường EMSDK và các tool emcc sẽ được set.
Verify bằng cách kiểm tra phiên bản.
emcc --version
Đầu ra phải hiển thị phiên bản Emscripten (ví dụ: version 3.1.60).
Cấu hình build WASM với Emscripten
Chúng ta sẽ tạo một script build đơn giản gọi emcc để biên dịch file C++ đã viết sang .wasm và file glue code .js.
Tạo file build_wasm.sh tại thư mục gốc ~/ai-inference-engine.
cat > build_wasm.sh
Script này tự động tìm tất cả file nguồn TFLite cần thiết và gọi emcc với các flag tối ưu cho Web. Nó tạo ra file .js (glue code) và .wasm.
Chạy script để biên dịch.
./build_wasm.sh
Quá trình này có thể mất vài phút. Kết quả mong đợi là xuất hiện file build_wasm/tflite_engine.wasm và build_wasm/tflite_engine.js.
4. Tối ưu hóa biên dịch với các flag phù hợp cho môi trường Web
Để engine chạy nhanh và nhẹ trên trình duyệt, chúng ta cần tinh chỉnh các flag biên dịch trong script build_wasm.sh hoặc tạo một script riêng cho build production.
Chúng ta sẽ tạo một script optimize_build.sh áp dụng các kỹ thuật: LTO (Link Time Optimization), Strip symbols, và tối ưu code size.
cat > optimize_build.sh /dev/null; then
wasm-opt -O3 build_wasm/tflite_engine_opt.wasm -o build_wasm/tflite_engine_opt.wasm
echo "Optimized with wasm-opt"
else
echo "wasm-opt not found, skipping final optimization"
fi
echo "Optimized build completed!"
ls -lh build_wasm/tflite_engine_opt.wasm
EOF
chmod +x optimize_build.sh
Script này thêm các flag -flto và -s STRIP_CODE_SIZE=1 để giảm kích thước file WASM và tăng tốc độ khởi tạo.
Chạy script tối ưu.
./optimize_build.sh
Đầu ra phải hiển thị kích thước file .wasm nhỏ hơn so với build thường (nếu có so sánh) và thông báo thành công.
Verify kết quả cuối cùng
Tạo một file HTML đơn giản để test xem engine WASM có load và chạy được không.
Tạo file test.html tại thư mục gốc.
cat > test.html {
const inputPtr = wasmModule._malloc(100 * 4); // 100 floats
const outputPtr = wasmModule._malloc(100 * 4);
// Fill input with 0.5
wasmModule.HEAPF32.set(new Float32Array(100).fill(0.5), inputPtr/4);
const run = wasmModule.cwrap('run_inference', 'number', ['number', 'number']);
const res = run(inputPtr, outputPtr);
if (res === 0) {
// Read output
const outputData = new Float32Array(wasmModule.HEAPF32.buffer, outputPtr, 100);
document.getElementById('result').innerText = "Success! Output sum: " + outputData.reduce((a,b)=>a+b, 0).toFixed(4);
} else {
document.getElementById('result').innerText = "Inference Failed: " + res;
}
wasmModule._free(inputPtr);
wasmModule._free(outputPtr);
});
// Mock fetch để test mà không cần file .tflite thật ngay lập tức
window.fetch = function(url) {
return Promise.resolve({
arrayBuffer: function() {
return Promise.resolve(new ArrayBuffer(4096));
}
});
};
init();
File HTML này load module WASM, gọi hàm init_model và run_inference qua cwrap.
Chạy server HTTP đơn giản để mở file HTML này trong trình duyệt.
cd ~/ai-inference-engine
python3 -m http.server 8080
Mở trình duyệt và truy cập http://localhost:8080/test.html.
Kết quả mong đợi: Bạn thấy dòng "WASM Loaded", sau đó "Model Init Success", và khi bấm nút "Run Inference", bạn thấy kết quả "Success! Output sum: ...".
Điều hướng series:
Mục lục: Series: Xây dựng nền tảng Real-time AI với WebAssembly, TensorFlow Lite và Kubernetes
« Phần 3: Chuẩn bị và tối ưu hóa mô hình TensorFlow Lite
Phần 5: Phát triển ứng dụng Frontend tích hợp WebAssembly »