docs: Update README and compilation guides for clarity and consistency, including path corrections and improved formatting. Add copyright notices to source files and adjust file permissions for several scripts and directories.

This commit is contained in:
dian.yuan 2026-02-28 11:06:26 +08:00
parent f960c5030d
commit bd891a96dd
136 changed files with 14413 additions and 9399 deletions

View file

@ -1,159 +1,159 @@
# CLIP
## 1. Overview
This demo demonstrates how to run CLIP (Contrastive Language-Image Pre-Training) image-text matching using AMLNNLite. The CLIP model consists of two parts: a vision encoder and a text encoder, which work together to compute similarity between images and text descriptions.
## 2. Model Download
TO DO
## 3. Model Conversion
TO DO
## 4. Demo Run
### CPP
#### 1. Compile
**Prerequisites:**
- Android NDK (r25e recommended)
- `ANDROID_NDK_PATH` environment variable set
**Build:**
```bash
# Build for arm64-v8a
cd examples/clip/cpp
AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a
```
The executable will be generated at `build/android_arm64-v8a/clip_demo`.
#### 2. Run
```bash
# Push executable and resources to device
adb push build/android_arm64-v8a/clip_demo /data/local/tmp/
adb push model/vision_model_int8_S905X5.adla /data/local/tmp/
adb push model/text_model_int8_S905X5.adla /data/local/tmp/
adb push tokenizer_path/ /data/local/tmp/
# Run on device
adb shell
cd /data/local/tmp
chmod +x clip_demo
export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib)
# Usage: ./clip_demo <vision_model> <text_model> <tokenizer_path> [--profiling]
./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path/
```
The program will prompt for image paths and text descriptions interactively. Enter the path to an image file, then enter comma-separated text descriptions (or `skip` to use defaults). Type `exit` to quit.
**Argument Descriptions:**
| Argument | Description |
| -------------- | ------------------------------------------------------------ |
| vision_model | Path to vision encoder .adla model (required) |
| text_model | Path to text encoder .adla model (required) |
| tokenizer_path | Path to directory containing `vocab.json` and `merges.txt` (required) |
| --profiling | Enable performance profiling output (optional) |
**Note:** The `tokenizer_path` should contain `vocab.json` and `merges.txt` files from the CLIP tokenizer (e.g., from `openai/clip-vit-base-patch32`).
### Python
**Prerequisites:**
- Python 3.10
- Required packages: `numpy`, `Pillow`, `transformers`, `amlnnlite`
**Install dependencies:**
```bash
pip install numpy Pillow transformers amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl
```
**Run on device:**
```bash
python clip.py \
--vision-model ./vision_model_int8_S905X5.adla \
--text-model ./text_model_int8_S905X5.adla \
--tokenizer-dir ./tokenizer_path \
--image-path ./000000004505.jpg \
--texts "a red handbag" "a blue jacket" "a red bus"
```
**Interactive Mode (Recommended):**
If you don't provide `--image-path`, the program will run in interactive mode:
```bash
python clip.py \
--vision-model ./vision_model_int8_S905X5.adla \
--text-model ./text_model_int8_S905X5.adla \
--tokenizer-dir ./tokenizer_path
```
The program will prompt for image paths and text descriptions. Enter an image path to process, then enter comma-separated texts to compare. Type `exit` to quit.
**Argument Descriptions:**
| Argument | Description |
| ---------------- | ------------------------------------------------------------ |
| --vision-model | Path to vision encoder .adla model (required) |
| --text-model | Path to text encoder .adla model (required) |
| --tokenizer-dir | Path to CLIPTokenizer directory (required) |
| --image-path | Path to input image (.jpg, .png) - optional, will prompt if not provided |
| --texts | List of text descriptions to compare (space-separated) |
| --max-len | Maximum token sequence length, default is 64 |
| --logit-scale | Logit scale factor, default is 100.0 |
**Note:** The `--tokenizer-dir` should point to the directory containing the CLIPTokenizer files. You can use a Hugging Face model ID (e.g., `openai/clip-vit-base-patch32`) or a local directory.
## 5. Results
**Performance Feedback**
By using the `--profiling` flag (C++) or setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including:
- Hardware Information: System and ADLA library versions.
- Model Overview: Basic input/output configurations.
- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption.
**Interactive Mode Example:**
```bash
$ ./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path
[Info] Models initialized successfully.
============================================================
[Info] Image Path (or 'exit' to quit):
000000004505.jpg
[Info] Enter text descriptions (comma-separated, or 'skip' for defaults):
a red handbag, a blue jacket, a red bus
[Info] Processing image: 000000004505.jpg
[Info] Image embedding size: 512
[Info] Processing 3 text(s)...
[Info] Text embeddings size: 3 x 512
============================================================
CLIP Image-Text Matching Results
============================================================
Image: 000000004505.jpg
logit_scale: 100.000000
------------------------------------------------------------
[1] prob=0.999975 sim=0.327895 text='a red bus'
[2] prob=0.000016 sim=0.217690 text='a red handbag'
[3] prob=0.000008 sim=0.211029 text='a blue jacket'
============================================================
============================================================
[Info] Image Path (or 'exit' to quit):
exit
[Info] Exiting...
Free vision model memory.
Free text model memory.
[Info] Done.
```
# CLIP
## 1. Overview
This demo demonstrates how to run CLIP (Contrastive Language-Image Pre-Training) image-text matching using AMLNNLite. The CLIP model consists of two parts: a vision encoder and a text encoder, which work together to compute similarity between images and text descriptions.
## 2. Model Download
TO DO
## 3. Model Conversion
TO DO
## 4. Demo Run
### CPP
#### 1. Compile
**Prerequisites:**
- Android NDK (r25e recommended)
- `ANDROID_NDK_PATH` environment variable set
**Build:**
```bash
# Build for arm64-v8a
cd examples/clip/cpp
AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a
```
The executable will be generated at `build/android_arm64-v8a/clip_demo`.
#### 2. Run
```bash
# Push executable and resources to device
adb push build/android_arm64-v8a/clip_demo /data/local/tmp/
adb push model/vision_model_int8_S905X5.adla /data/local/tmp/
adb push model/text_model_int8_S905X5.adla /data/local/tmp/
adb push tokenizer_path/ /data/local/tmp/
# Run on device
adb shell
cd /data/local/tmp
chmod +x clip_demo
export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib)
# Usage: ./clip_demo <vision_model> <text_model> <tokenizer_path> [--profiling]
./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path/
```
The program will prompt for image paths and text descriptions interactively. Enter the path to an image file, then enter comma-separated text descriptions (or `skip` to use defaults). Type `exit` to quit.
**Argument Descriptions:**
| Argument | Description |
| -------------- | ------------------------------------------------------------ |
| vision_model | Path to vision encoder .adla model (required) |
| text_model | Path to text encoder .adla model (required) |
| tokenizer_path | Path to directory containing `vocab.json` and `merges.txt` (required) |
| --profiling | Enable performance profiling output (optional) |
**Note:** The `tokenizer_path` should contain `vocab.json` and `merges.txt` files from the CLIP tokenizer (e.g., from `openai/clip-vit-base-patch32`).
### Python
**Prerequisites:**
- Python 3.10
- Required packages: `numpy`, `Pillow`, `transformers`, `amlnnlite`
**Install dependencies:**
```bash
pip install numpy Pillow transformers amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl
```
**Run on device:**
```bash
python clip.py \
--vision-model ./vision_model_int8_S905X5.adla \
--text-model ./text_model_int8_S905X5.adla \
--tokenizer-dir ./tokenizer_path \
--image-path ./000000004505.jpg \
--texts "a red handbag" "a blue jacket" "a red bus"
```
**Interactive Mode (Recommended):**
If you don't provide `--image-path`, the program will run in interactive mode:
```bash
python clip.py \
--vision-model ./vision_model_int8_S905X5.adla \
--text-model ./text_model_int8_S905X5.adla \
--tokenizer-dir ./tokenizer_path
```
The program will prompt for image paths and text descriptions. Enter an image path to process, then enter comma-separated texts to compare. Type `exit` to quit.
**Argument Descriptions:**
| Argument | Description |
| ---------------- | ------------------------------------------------------------ |
| --vision-model | Path to vision encoder .adla model (required) |
| --text-model | Path to text encoder .adla model (required) |
| --tokenizer-dir | Path to CLIPTokenizer directory (required) |
| --image-path | Path to input image (.jpg, .png) - optional, will prompt if not provided |
| --texts | List of text descriptions to compare (space-separated) |
| --max-len | Maximum token sequence length, default is 64 |
| --logit-scale | Logit scale factor, default is 100.0 |
**Note:** The `--tokenizer-dir` should point to the directory containing the CLIPTokenizer files. You can use a Hugging Face model ID (e.g., `openai/clip-vit-base-patch32`) or a local directory.
## 5. Results
**Performance Feedback**
By using the `--profiling` flag (C++) or setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including:
- Hardware Information: System and ADLA library versions.
- Model Overview: Basic input/output configurations.
- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption.
**Interactive Mode Example:**
```bash
$ ./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path
[Info] Models initialized successfully.
============================================================
[Info] Image Path (or 'exit' to quit):
000000004505.jpg
[Info] Enter text descriptions (comma-separated, or 'skip' for defaults):
a red handbag, a blue jacket, a red bus
[Info] Processing image: 000000004505.jpg
[Info] Image embedding size: 512
[Info] Processing 3 text(s)...
[Info] Text embeddings size: 3 x 512
============================================================
CLIP Image-Text Matching Results
============================================================
Image: 000000004505.jpg
logit_scale: 100.000000
------------------------------------------------------------
[1] prob=0.999975 sim=0.327895 text='a red bus'
[2] prob=0.000016 sim=0.217690 text='a red handbag'
[3] prob=0.000008 sim=0.211029 text='a blue jacket'
============================================================
============================================================
[Info] Image Path (or 'exit' to quit):
exit
[Info] Exiting...
Free vision model memory.
Free text model memory.
[Info] Done.
```

0
examples/clip/cpp/.gitkeep Normal file → Executable file
View file

View file

@ -1,38 +1,38 @@
cmake_minimum_required(VERSION 3.10...3.27)
project(clip_demo)
set(CMAKE_CXX_STANDARD 17)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake")
find_package(AMLNN REQUIRED)
include_directories(${AMLNN_INCLUDE_DIR})
link_directories(${AMLNN_LIBRARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/../../../../common)
# Set 3rdparty path
set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency")
# Include directories for stb_image and json
# Note: code uses #include "stb_image.h" and #include "json.hpp"
include_directories(${3RDPARTY_DIR}/stb_image)
include_directories(${3RDPARTY_DIR}/json)
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
# Android needs log
link_libraries(log)
endif()
add_executable(${PROJECT_NAME}
main.cpp
model_invoke.cpp
pre_postprocess.cpp
clip_tokenizer.cpp
)
target_link_libraries(${PROJECT_NAME}
${AMLNN_LIBRARY}
dl
m
)
cmake_minimum_required(VERSION 3.10...3.27)
project(clip_demo)
set(CMAKE_CXX_STANDARD 17)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake")
find_package(AMLNN REQUIRED)
include_directories(${AMLNN_INCLUDE_DIR})
link_directories(${AMLNN_LIBRARY_DIR})
include_directories(${CMAKE_SOURCE_DIR}/../../../../common)
# Set 3rdparty path
set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency")
# Include directories for stb_image and json
# Note: code uses #include "stb_image.h" and #include "json.hpp"
include_directories(${3RDPARTY_DIR}/stb_image)
include_directories(${3RDPARTY_DIR}/json)
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
# Android needs log
link_libraries(log)
endif()
add_executable(${PROJECT_NAME}
main.cpp
model_invoke.cpp
pre_postprocess.cpp
clip_tokenizer.cpp
)
target_link_libraries(${PROJECT_NAME}
${AMLNN_LIBRARY}
dl
m
)

View file

@ -26,8 +26,8 @@
// Initialize network from file
void* init_network_file(const char *model_path);
// Run vision model inference
std::vector<float> run_vision_model(void* context, const std::vector<float>& input_data);
// Run image model inference
std::vector<float> run_image_model(void* context, const std::vector<float>& input_data);
// Run text model inference
std::vector<float> run_text_model(void* context, const std::vector<int64_t>& input_ids);

View file

@ -33,7 +33,7 @@ struct ProfilingTimer
{
uint64_t init_start, init_end;
uint64_t preprocess_start, preprocess_end;
uint64_t vision_infer_start, vision_infer_end;
uint64_t image_infer_start, image_infer_end;
uint64_t text_infer_start, text_infer_end;
};
@ -71,10 +71,10 @@ std::vector<std::string> parse_texts(const std::string& input)
void print_usage(const char* prog_name)
{
printf("Usage: %s <vision_model> <text_model> <tokenizer_dir> [--profiling]\n", prog_name);
printf("Usage: %s <image_model> <text_model> <tokenizer_dir> [--profiling]\n", prog_name);
printf("\n");
printf("Arguments:\n");
printf(" vision_model: Path to vision model (.adla)\n");
printf(" image_model: Path to image model (.adla)\n");
printf(" text_model: Path to text model (.adla)\n");
printf(" tokenizer_dir: Path to directory containing vocab.json and merges.txt\n");
printf(" --profiling: Enable performance profiling output (optional)\n");
@ -96,7 +96,7 @@ int main(int argc, char ** argv)
return -1;
}
const char* vision_model_path = argv[1];
const char* image_model_path = argv[1];
const char* text_model_path = argv[2];
const char* tokenizer_dir = argv[3];
@ -119,11 +119,11 @@ int main(int argc, char ** argv)
}
// Initialize models
printf("[Info] Initializing vision model: %s\n", vision_model_path);
printf("[Info] Initializing image model: %s\n", image_model_path);
timer.init_start = get_time_count();
void* vision_context = init_network_file(vision_model_path);
if (vision_context == NULL) {
printf("[Error] Failed to initialize vision model.\n");
void* image_context = init_network_file(image_model_path);
if (image_context == NULL) {
printf("[Error] Failed to initialize image model.\n");
return -1;
}
@ -131,7 +131,7 @@ int main(int argc, char ** argv)
void* text_context = init_network_file(text_model_path);
if (text_context == NULL) {
printf("[Error] Failed to initialize text model.\n");
destroy_network(vision_context);
destroy_network(image_context);
return -1;
}
timer.init_end = get_time_count();
@ -218,14 +218,14 @@ int main(int argc, char ** argv)
}
timer.preprocess_end = get_time_count();
// Run vision model
timer.vision_infer_start = get_time_count();
std::vector<float> image_embedding = run_vision_model(vision_context, image_input);
// Run image model
timer.image_infer_start = get_time_count();
std::vector<float> image_embedding = run_image_model(image_context, image_input);
if (image_embedding.empty()) {
printf("[Error] Vision model inference failed.\n");
printf("[Error] Image model inference failed.\n");
continue;
}
timer.vision_infer_end = get_time_count();
timer.image_infer_end = get_time_count();
// L2 normalize image embedding
image_embedding = l2_normalize(image_embedding);
@ -264,7 +264,8 @@ int main(int argc, char ** argv)
continue;
}
printf("[Info] Text embeddings size: %zu x %zu\n", text_embeddings.size(),
printf("[Info] Text embeddings size: %zu x %zu\n",
text_embeddings.size(),
text_embeddings.empty() ? 0 : text_embeddings[0].size());
// ==================== Compute Similarity ====================
@ -302,11 +303,11 @@ int main(int argc, char ** argv)
if (profiling) {
uint64_t preprocess_time = (timer.preprocess_end - timer.preprocess_start) / 1000000;
uint64_t vision_time = (timer.vision_infer_end - timer.vision_infer_start) / 1000000;
uint64_t image_time = (timer.image_infer_end - timer.image_infer_start) / 1000000;
uint64_t text_total_time = (timer.text_infer_end - timer.text_infer_start) / 1000000;
printf("\n[Profiling]\n");
printf(" Image preprocess: %lums\n", preprocess_time);
printf(" Vision inference: %lums\n", vision_time);
printf(" Image inference: %lums\n", image_time);
for (size_t i = 0; i < texts.size() && i < text_infer_times.size(); ++i) {
printf(" Text inference[%zu]: %lums '%s'\n", i, text_infer_times[i], texts[i].c_str());
}
@ -316,9 +317,9 @@ int main(int argc, char ** argv)
}
// Cleanup
ret = destroy_network(vision_context);
ret = destroy_network(image_context);
if (ret != 0) {
printf("[Error] Failed to destroy vision model.\n");
printf("[Error] Failed to destroy image model.\n");
}
ret = destroy_network(text_context);
@ -328,4 +329,4 @@ int main(int argc, char ** argv)
printf("[Info] Done.\n");
return 0;
}
}

View file

@ -27,9 +27,9 @@
#include "nn_sdk.h"
// Global DMA config for models
static aml_memory_config_t vision_mem_config;
static aml_memory_data_t vision_mem_data;
static void* vision_context_flag = nullptr;
static aml_memory_config_t image_mem_config;
static aml_memory_data_t image_mem_data;
static void* image_context_flag = nullptr;
static aml_memory_config_t text_mem_config;
static aml_memory_data_t text_mem_data;
@ -48,7 +48,7 @@ void* init_network_file(const char *model_path)
/* set omp, If you are considering high CPU usage during operation,
you can turn off this api, set_openmp_opt_flag = false */
aml_openmp_opt_t openmp_opt[] =
aml_openmp_opt_t openmp_opt[] =
{
{
.operator_type = AML_Unknown,
@ -62,7 +62,7 @@ void* init_network_file(const char *model_path)
config.forward_ctrl.softop_info.openmp_opt = openmp_opt;
/* set neon */
aml_neon_opt_t neon_opt[] =
aml_neon_opt_t neon_opt[] =
{
{
.operator_type = AML_Unknown,
@ -84,10 +84,11 @@ void* init_network_file(const char *model_path)
return qcontext;
}
std::vector<float> run_vision_model(void* qcontext, const std::vector<float>& input_data)
std::vector<float> run_image_model(void* qcontext, const std::vector<float>& input_data)
{
int ret = 0;
nn_input inData;
nn_output *outdata = NULL;
aml_output_config_t outconfig;
@ -96,19 +97,19 @@ std::vector<float> run_vision_model(void* qcontext, const std::vector<float>& in
inData.size = input_data.size() * sizeof(float);
// Use DMA
if (!vision_context_flag) {
vision_mem_config.cache_type = AML_WITH_CACHE;
vision_mem_config.memory_type = AML_VIRTUAL_ADDR;
vision_mem_config.direction = AML_MEM_DIRECTION_READ_WRITE;
vision_mem_config.index = 0;
vision_mem_config.mem_size = inData.size;
aml_util_mallocBuffer(qcontext, &vision_mem_config, &vision_mem_data);
aml_util_swapExternalInputBuffer(qcontext, &vision_mem_config, &vision_mem_data);
vision_context_flag = qcontext;
if (!image_context_flag) {
image_mem_config.cache_type = AML_WITH_CACHE;
image_mem_config.memory_type = AML_VIRTUAL_ADDR;
image_mem_config.direction = AML_MEM_DIRECTION_READ_WRITE;
image_mem_config.index = 0;
image_mem_config.mem_size = inData.size;
aml_util_mallocBuffer(qcontext, &image_mem_config, &image_mem_data);
aml_util_swapExternalInputBuffer(qcontext, &image_mem_config, &image_mem_data);
image_context_flag = qcontext;
}
inData.input_type = INPUT_DMA_DATA;
memcpy(vision_mem_data.viraddr, input_data.data(), vision_mem_config.mem_size);
memcpy(image_mem_data.viraddr, input_data.data(), image_mem_config.mem_size);
inData.input = NULL;
memset(&outconfig, 0, sizeof(aml_output_config_t));
@ -117,7 +118,7 @@ std::vector<float> run_vision_model(void* qcontext, const std::vector<float>& in
outdata = (nn_output*)aml_module_output_get(qcontext, outconfig);
if (outdata == NULL || outdata->out[0].buf == NULL) {
printf("Vision model inference failed.\n");
printf("Image model inference failed.\n");
return {};
}
@ -178,10 +179,10 @@ int destroy_network(void *qcontext)
{
int ret = 0;
if (vision_context_flag == qcontext) {
printf("Free vision model memory.\n");
aml_util_freeBuffer(qcontext, &vision_mem_config, &vision_mem_data);
vision_context_flag = nullptr;
if (image_context_flag == qcontext) {
printf("Free image model memory.\n");
aml_util_freeBuffer(qcontext, &image_mem_config, &image_mem_data);
image_context_flag = nullptr;
} else if (text_context_flag == qcontext) {
printf("Free text model memory.\n");
aml_util_freeBuffer(qcontext, &text_mem_config, &text_mem_data);
@ -199,4 +200,4 @@ int destroy_network(void *qcontext)
}
return ret;
}
}

View file

@ -1,13 +1,29 @@
#ifndef MODEL_INVOKE_H
#define MODEL_INVOKE_H
#include <string>
#include <vector>
#include <map>
void* init_network_file(const char *model_path);
std::vector<std::string> process_image_dir(void *context_model, const std::string& json_path, const std::string& base_dir = "", const std::string& json_filename = "");
int destroy_network(void *qcontext);
#endif // MODEL_INVOKE_H
/*
* Copyright (C) 2026 Amlogic, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MODEL_INVOKE_H
#define MODEL_INVOKE_H
#include <string>
#include <vector>
#include <map>
void* init_network_file(const char *model_path);
std::vector<std::string> process_image_dir(void *context_model, const std::string& json_path, const std::string& base_dir = "", const std::string& json_filename = "");
int destroy_network(void *qcontext);
#endif // MODEL_INVOKE_H

View file

@ -102,7 +102,7 @@ std::vector<float> preprocess_image(const std::string& image_path) {
}
}
// Return NHWC format (batch dimension will be added in caller)
// get NHWC
return cropped;
}

0
examples/clip/model/.gitkeep Normal file → Executable file
View file

0
examples/clip/py/.gitkeep Normal file → Executable file
View file

View file

@ -1,339 +1,336 @@
# -*- coding: utf-8 -*-
"""
Copyright (C) 20242025 Amlogic, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
# This inference script is designed for CLIP model using AMLNNLite.
import os
import argparse
import numpy as np
from PIL import Image
from transformers import CLIPTokenizer
from amlnnlite.api import AMLNNLite
# ==================== Utility Functions ====================
def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray:
"""Compute softmax values for array x."""
x = x - np.max(x, axis=axis, keepdims=True)
e = np.exp(x)
return e / np.sum(e, axis=axis, keepdims=True)
def l2_normalize(x: np.ndarray, axis: int = -1, eps: float = 1e-12) -> np.ndarray:
"""L2 normalize array x along specified axis."""
return x / (np.linalg.norm(x, axis=axis, keepdims=True) + eps)
# ==================== Vision Preprocessing ====================
def preprocess_image(image_path: str, target_size: int = 224) -> np.ndarray:
"""
Preprocess image for CLIP model.
Args:
image_path (str): Path to input image
target_size (int): Target image size (default: 224)
Returns:
np.ndarray: Preprocessed image data with shape (1, target_size, target_size, 3) in NHWC format
"""
image = Image.open(image_path).convert("RGB")
width, height = image.size
# Scale the shorter side
scale = target_size / min(width, height)
new_width = int(width * scale)
new_height = int(height * scale)
image_resized = image.resize((new_width, new_height), resample=Image.BICUBIC)
# Center crop
left = (new_width - target_size) // 2
top = (new_height - target_size) // 2
right = left + target_size
bottom = top + target_size
image_cropped = image_resized.crop((left, top, right, bottom))
# Convert to numpy array and normalize to [0, 1]
image_np = np.array(image_cropped).astype(np.float32) / 255.0
# CLIP normalization
mean = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32)
std = np.array([0.26862954, 0.26130258, 0.27577711], dtype=np.float32)
image_np = (image_np - mean) / std
# Add batch dimension: HWC -> NHWC
image_np = np.expand_dims(image_np, axis=0)
return image_np.astype(np.float32) # [1, 224, 224, 3]
# ==================== Text Preprocessing ====================
def preprocess_text(tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray:
"""
Preprocess text for CLIP model using CLIPTokenizer.
Args:
tokenizer: CLIPTokenizer instance
text (str): Input text string
max_len (int): Maximum sequence length (default: 64)
Returns:
np.ndarray: Tokenized text with shape (1, max_len) as int64
"""
enc = tokenizer(
text,
padding="max_length",
truncation=True,
max_length=max_len,
return_tensors="np",
)
# text model input: int64[1, max_len]
input_ids = enc["input_ids"].astype(np.int64)
return input_ids
# ==================== Model Inference ====================
def compute_image_embedding(vision_amlnn: AMLNNLite, image_path: str) -> np.ndarray:
"""
Compute image embedding using vision model.
Args:
vision_amlnn: AMLNNLite instance for vision model
image_path (str): Path to input image
Returns:
np.ndarray: L2-normalized image embedding with shape (1, embed_dim)
"""
input_data = preprocess_image(image_path) # [1, 224, 224, 3]
outputs = vision_amlnn.inference(
inputs=[input_data],
inputs_data_format='NHWC',
outputs_data_format='NHWC'
)
feats = outputs[0].astype(np.float32)
feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim]
return l2_normalize(feats, axis=1)
def compute_text_embedding(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray:
"""
Compute text embedding using text model.
Args:
text_amlnn: AMLNNLite instance for text model
tokenizer: CLIPTokenizer instance
text (str): Input text string
max_len (int): Maximum sequence length
Returns:
np.ndarray: L2-normalized text embedding with shape (1, embed_dim)
"""
input_ids = preprocess_text(tokenizer, text, max_len) # [1, max_len]
print(f"input_ids: {input_ids}")
# AMLNNLite requires 4D input, reshape to (1, 1, 1, max_len)
input_ids_4d = input_ids[:, None, None, :] # [1, 1, 1, max_len]
outputs = text_amlnn.inference(
inputs=[input_ids_4d],
inputs_data_format='NHWC',
outputs_data_format='NHWC'
)
feats = outputs[0].astype(np.float32)
feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim]
return l2_normalize(feats, axis=1)
def compute_text_embeddings_batch(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, texts: list, max_len: int = 64) -> np.ndarray:
"""
Compute text embeddings for multiple texts.
Args:
text_amlnn: AMLNNLite instance for text model
tokenizer: CLIPTokenizer instance
texts (list): List of input text strings
max_len (int): Maximum sequence length
Returns:
np.ndarray: L2-normalized text embeddings with shape (num_texts, embed_dim)
"""
embeddings = []
for text in texts:
emb = compute_text_embedding(text_amlnn, tokenizer, text, max_len)
embeddings.append(emb[0]) # Remove batch dimension
return np.stack(embeddings, axis=0) # [num_texts, embed_dim]
# ==================== Similarity Calculation ====================
def compute_similarity(image_embedding: np.ndarray, text_embeddings: np.ndarray, logit_scale: float = 100.0) -> tuple:
"""
Compute similarity between image and text embeddings.
Args:
image_embedding (np.ndarray): Image embedding with shape (1, embed_dim)
text_embeddings (np.ndarray): Text embeddings with shape (num_texts, embed_dim)
logit_scale (float): Scale factor for logits
Returns:
tuple: (similarities, logits, probabilities)
"""
# Cosine similarity (embeddings are already L2-normalized)
sims = text_embeddings @ image_embedding[0] # [num_texts]
logits = sims * logit_scale # [num_texts]
probs = softmax(logits, axis=0) # [num_texts]
return sims, logits, probs
# ==================== Main Function ====================
def main():
parser = argparse.ArgumentParser(description='CLIP Image-Text Matching Demo using AMLNNLite')
parser.add_argument('--vision-model', required=True, help='Path to vision model (.adla)')
parser.add_argument('--text-model', required=True, help='Path to text model (.adla)')
parser.add_argument('--tokenizer-dir', required=True, help='Path to CLIPTokenizer directory')
parser.add_argument('--image-path', default=None, help='Path to input image (optional, will prompt if not provided)')
parser.add_argument('--texts', nargs='+', default=None, help='List of text descriptions to compare')
parser.add_argument('--max-len', type=int, default=64, help='Maximum token sequence length (default: 64)')
parser.add_argument('--logit-scale', type=float, default=100.0, help='Logit scale factor (default: 100.0)')
args = parser.parse_args()
# Validate model paths
if not os.path.exists(args.vision_model):
print(f"[Error] Vision model not found: {args.vision_model}")
return -1
if not os.path.exists(args.text_model):
print(f"[Error] Text model not found: {args.text_model}")
return -1
# Load tokenizer
print(f"[Info] Loading CLIPTokenizer from: {args.tokenizer_dir}")
tokenizer = CLIPTokenizer.from_pretrained(args.tokenizer_dir)
# Initialize vision model
print(f"[Info] Initializing vision model: {args.vision_model}")
vision_amlnn = AMLNNLite()
vision_amlnn.config(model_path=args.vision_model, run_cycles=1)
vision_amlnn.init()
# Initialize text model
print(f"[Info] Initializing text model: {args.text_model}")
text_amlnn = AMLNNLite()
text_amlnn.config(model_path=args.text_model, run_cycles=1)
text_amlnn.init()
print("[Info] Models initialized successfully.\n")
try:
# Interactive loop
while True:
# Get image path
if args.image_path:
image_path = args.image_path
args.image_path = None # Clear for next iteration
else:
print("=" * 60)
print("[Info] Image Path (or 'exit' to quit):")
image_path = input().strip()
# Check for exit
if image_path.lower() == 'exit':
print("[Info] Exiting...")
break
# Validate image path
if not image_path:
print("[Warning] Please enter an image path.")
continue
if not os.path.exists(image_path):
print(f"[Error] Image not found: {image_path}")
continue
# Get texts to compare
if args.texts:
texts = args.texts
args.texts = None # Clear for next iteration
else:
print("[Info] Enter text descriptions (comma-separated, or 'skip' to use defaults):")
text_input = input().strip()
if text_input.lower() == 'skip' or not text_input:
# Default texts for demo
texts = [
"a red handbag",
"a blue jacket",
"a red bus",
]
print(f"[Info] Using default texts: {texts}")
else:
texts = [t.strip() for t in text_input.split(',') if t.strip()]
if not texts:
print("[Warning] No texts provided.")
continue
try:
# Compute image embedding
print(f"\n[Info] Processing image: {image_path}")
image_embedding = compute_image_embedding(vision_amlnn, image_path)
print(f"[Info] Image embedding shape: {image_embedding.shape}")
# Compute text embeddings
print(f"[Info] Processing {len(texts)} text(s)...")
text_embeddings = compute_text_embeddings_batch(text_amlnn, tokenizer, texts, args.max_len)
print(f"[Info] Text embeddings shape: {text_embeddings.shape}")
# Compute similarity
sims, logits, probs = compute_similarity(image_embedding, text_embeddings, args.logit_scale)
# Print results
print("\n" + "=" * 60)
print("CLIP Image-Text Matching Results")
print("=" * 60)
print(f"Image: {image_path}")
print(f"logit_scale: {args.logit_scale:.6f}")
print("-" * 60)
# Sort by probability (descending)
sorted_indices = np.argsort(probs)[::-1]
for rank, i in enumerate(sorted_indices):
print(f"[{rank + 1}] prob={probs[i]:.6f} sim={float(sims[i]):.6f} text='{texts[i]}'")
print("=" * 60 + "\n")
except Exception as e:
print(f"[Error] Processing failed: {e}")
import traceback
traceback.print_exc()
continue
except KeyboardInterrupt:
print("\n\n[Info] Interrupted by user. Exiting...")
finally:
# Cleanup
vision_amlnn.uninit()
text_amlnn.uninit()
print("[Info] Done.")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
#
# Copyright (C) 2026 Amlogic, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import os
import argparse
import numpy as np
from PIL import Image
from transformers import CLIPTokenizer
from amlnnlite.api import AMLNNLite
# ==================== Utility Functions ====================
def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray:
"""Compute softmax values for array x."""
x = x - np.max(x, axis=axis, keepdims=True)
e = np.exp(x)
return e / np.sum(e, axis=axis, keepdims=True)
def l2_normalize(x: np.ndarray, axis: int = -1, eps: float = 1e-12) -> np.ndarray:
"""L2 normalize array x along specified axis."""
return x / (np.linalg.norm(x, axis=axis, keepdims=True) + eps)
# ==================== Vision Preprocessing ====================
def preprocess_image(image_path: str, target_size: int = 224) -> np.ndarray:
"""
Preprocess image for CLIP model.
Args:
image_path (str): Path to input image
target_size (int): Target image size (default: 224)
Returns:
np.ndarray: Preprocessed image data with shape (1, target_size, target_size, 3) in NHWC format
"""
image = Image.open(image_path).convert("RGB")
width, height = image.size
# Scale the shorter side
scale = target_size / min(width, height)
new_width = int(width * scale)
new_height = int(height * scale)
image_resized = image.resize((new_width, new_height), resample=Image.BICUBIC)
# Center crop
left = (new_width - target_size) // 2
top = (new_height - target_size) // 2
right = left + target_size
bottom = top + target_size
image_cropped = image_resized.crop((left, top, right, bottom))
# Convert to numpy array and normalize to [0, 1]
image_np = np.array(image_cropped).astype(np.float32) / 255.0
# CLIP normalization
mean = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32)
std = np.array([0.26862954, 0.26130258, 0.27577711], dtype=np.float32)
image_np = (image_np - mean) / std
# Add batch dimension: HWC -> NHWC
image_np = np.expand_dims(image_np, axis=0)
return image_np.astype(np.float32) # [1, 224, 224, 3]
# ==================== Text Preprocessing ====================
def preprocess_text(tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray:
"""
Preprocess text for CLIP model using CLIPTokenizer.
Args:
tokenizer: CLIPTokenizer instance
text (str): Input text string
max_len (int): Maximum sequence length (default: 64)
Returns:
np.ndarray: Tokenized text with shape (1, max_len) as int64
"""
enc = tokenizer(
text,
padding="max_length",
truncation=True,
max_length=max_len,
return_tensors="np",
)
# text model input: int64[1, max_len]
input_ids = enc["input_ids"].astype(np.int64)
return input_ids
# ==================== Model Inference ====================
def compute_image_embedding(vision_amlnn: AMLNNLite, image_path: str) -> np.ndarray:
"""
Compute image embedding using vision model.
Args:
vision_amlnn: AMLNNLite instance for vision model
image_path (str): Path to input image
Returns:
np.ndarray: L2-normalized image embedding with shape (1, embed_dim)
"""
input_data = preprocess_image(image_path) # [1, 224, 224, 3]
outputs = vision_amlnn.inference(
inputs=[input_data],
inputs_data_format='NHWC',
outputs_data_format='NHWC'
)
feats = outputs[0].astype(np.float32)
feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim]
return l2_normalize(feats, axis=1)
def compute_text_embedding(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray:
"""
Compute text embedding using text model.
Args:
text_amlnn: AMLNNLite instance for text model
tokenizer: CLIPTokenizer instance
text (str): Input text string
max_len (int): Maximum sequence length
Returns:
np.ndarray: L2-normalized text embedding with shape (1, embed_dim)
"""
input_ids = preprocess_text(tokenizer, text, max_len) # [1, max_len]
print(f"input_ids: {input_ids}")
# AMLNNLite requires 4D input, reshape to (1, 1, 1, max_len)
input_ids_4d = input_ids[:, None, None, :] # [1, 1, 1, max_len]
outputs = text_amlnn.inference(
inputs=[input_ids_4d],
inputs_data_format='NHWC',
outputs_data_format='NHWC'
)
feats = outputs[0].astype(np.float32)
feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim]
return l2_normalize(feats, axis=1)
def compute_text_embeddings_batch(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, texts: list, max_len: int = 64) -> np.ndarray:
"""
Compute text embeddings for multiple texts.
Args:
text_amlnn: AMLNNLite instance for text model
tokenizer: CLIPTokenizer instance
texts (list): List of input text strings
max_len (int): Maximum sequence length
Returns:
np.ndarray: L2-normalized text embeddings with shape (num_texts, embed_dim)
"""
embeddings = []
for text in texts:
emb = compute_text_embedding(text_amlnn, tokenizer, text, max_len)
embeddings.append(emb[0]) # Remove batch dimension
return np.stack(embeddings, axis=0) # [num_texts, embed_dim]
# ==================== Similarity Calculation ====================
def compute_similarity(image_embedding: np.ndarray, text_embeddings: np.ndarray, logit_scale: float = 100.0) -> tuple:
"""
Compute similarity between image and text embeddings.
Args:
image_embedding (np.ndarray): Image embedding with shape (1, embed_dim)
text_embeddings (np.ndarray): Text embeddings with shape (num_texts, embed_dim)
logit_scale (float): Scale factor for logits
Returns:
tuple: (similarities, logits, probabilities)
"""
# Cosine similarity (embeddings are already L2-normalized)
sims = text_embeddings @ image_embedding[0] # [num_texts]
logits = sims * logit_scale # [num_texts]
probs = softmax(logits, axis=0) # [num_texts]
return sims, logits, probs
# ==================== Main Function ====================
def main():
parser = argparse.ArgumentParser(description='CLIP Image-Text Matching Demo using AMLNNLite')
parser.add_argument('--vision-model', required=True, help='Path to vision model (.adla)')
parser.add_argument('--text-model', required=True, help='Path to text model (.adla)')
parser.add_argument('--tokenizer-dir', required=True, help='Path to CLIPTokenizer directory')
parser.add_argument('--image-path', default=None, help='Path to input image (optional, will prompt if not provided)')
parser.add_argument('--texts', nargs='+', default=None, help='List of text descriptions to compare')
parser.add_argument('--max-len', type=int, default=64, help='Maximum token sequence length (default: 64)')
parser.add_argument('--logit-scale', type=float, default=100.0, help='Logit scale factor (default: 100.0)')
args = parser.parse_args()
# Validate model paths
if not os.path.exists(args.vision_model):
print(f"[Error] Vision model not found: {args.vision_model}")
return -1
if not os.path.exists(args.text_model):
print(f"[Error] Text model not found: {args.text_model}")
return -1
# Load tokenizer
print(f"[Info] Loading CLIPTokenizer from: {args.tokenizer_dir}")
tokenizer = CLIPTokenizer.from_pretrained(args.tokenizer_dir)
# Initialize vision model
print(f"[Info] Initializing vision model: {args.vision_model}")
vision_amlnn = AMLNNLite()
vision_amlnn.config(model_path=args.vision_model, run_cycles=1)
vision_amlnn.init()
# Initialize text model
print(f"[Info] Initializing text model: {args.text_model}")
text_amlnn = AMLNNLite()
text_amlnn.config(model_path=args.text_model, run_cycles=1)
text_amlnn.init()
print("[Info] Models initialized successfully.\n")
try:
# Interactive loop
while True:
# Get image path
if args.image_path:
image_path = args.image_path
args.image_path = None # Clear for next iteration
else:
print("=" * 60)
print("[Info] Image Path (or 'exit' to quit):")
image_path = input().strip()
# Check for exit
if image_path.lower() == 'exit':
print("[Info] Exiting...")
break
# Validate image path
if not image_path:
print("[Warning] Please enter an image path.")
continue
if not os.path.exists(image_path):
print(f"[Error] Image not found: {image_path}")
continue
# Get texts to compare
if args.texts:
texts = args.texts
args.texts = None # Clear for next iteration
else:
print("[Info] Enter text descriptions (comma-separated, or 'skip' to use defaults):")
text_input = input().strip()
if text_input.lower() == 'skip' or not text_input:
# Default texts for demo
texts = [
"a red handbag",
"a blue jacket",
"a red bus",
]
print(f"[Info] Using default texts: {texts}")
else:
texts = [t.strip() for t in text_input.split(',') if t.strip()]
if not texts:
print("[Warning] No texts provided.")
continue
try:
# Compute image embedding
print(f"\n[Info] Processing image: {image_path}")
image_embedding = compute_image_embedding(vision_amlnn, image_path)
print(f"[Info] Image embedding shape: {image_embedding.shape}")
# Compute text embeddings
print(f"[Info] Processing {len(texts)} text(s)...")
text_embeddings = compute_text_embeddings_batch(text_amlnn, tokenizer, texts, args.max_len)
print(f"[Info] Text embeddings shape: {text_embeddings.shape}")
# Compute similarity
sims, logits, probs = compute_similarity(image_embedding, text_embeddings, args.logit_scale)
# Print results
print("\n" + "=" * 60)
print("CLIP Image-Text Matching Results")
print("=" * 60)
print(f"Image: {image_path}")
print(f"logit_scale: {args.logit_scale:.6f}")
print("-" * 60)
# Sort by probability (descending)
sorted_indices = np.argsort(probs)[::-1]
for rank, i in enumerate(sorted_indices):
print(f"[{rank + 1}] prob={probs[i]:.6f} sim={float(sims[i]):.6f} text='{texts[i]}'")
print("=" * 60 + "\n")
except Exception as e:
print(f"[Error] Processing failed: {e}")
import traceback
traceback.print_exc()
continue
except KeyboardInterrupt:
print("\n\n[Info] Interrupted by user. Exiting...")
finally:
# Cleanup
vision_amlnn.uninit()
text_amlnn.uninit()
print("[Info] Done.")
return 0
if __name__ == "__main__":
import sys
sys.exit(main())