Skip to content

Commit d3841ed

Browse files
authored
Merge branch 'master' into b_whisper_unification
2 parents fbda9d8 + 8698683 commit d3841ed

File tree

60 files changed

+2304
-126
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2304
-126
lines changed

samples/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ if(ENABLE_SAMPLES)
1010
add_subdirectory(cpp/rag)
1111
add_subdirectory(c/text_generation)
1212
add_subdirectory(c/whisper_speech_recognition)
13+
add_subdirectory(c/visual_language_chat)
1314
endif()
1415

1516
install(FILES
@@ -41,4 +42,5 @@ install(DIRECTORY
4142
install(DIRECTORY
4243
c/text_generation
4344
c/whisper_speech_recognition
45+
c/visual_language_chat
4446
DESTINATION samples/c COMPONENT cpp_samples_genai)

samples/c/text_generation/chat_sample_c.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@
1414
fprintf(stderr, "[ERROR] return status %d, line %d\n", return_status, __LINE__); \
1515
goto err; \
1616
}
17-
ov_genai_streamming_status_e print_callback(const char* str, void* args) {
17+
ov_genai_streaming_status_e print_callback(const char* str, void* args) {
1818
if (str) {
1919
// If args is not null, it needs to be cast to its actual type.
2020
fprintf(stdout, "%s", str);
2121
fflush(stdout);
22-
return OV_GENAI_STREAMMING_STATUS_RUNNING;
22+
return OV_GENAI_STREAMING_STATUS_RUNNING;
2323
} else {
2424
printf("Callback executed with NULL message!\n");
25-
return OV_GENAI_STREAMMING_STATUS_STOP;
25+
return OV_GENAI_STREAMING_STATUS_STOP;
2626
}
2727
}
2828

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright (C) 2025 Intel Corporation
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
find_package(OpenVINOGenAI REQUIRED
5+
PATHS
6+
"${CMAKE_BINARY_DIR}" # Reuse the package from the build.
7+
${OpenVINO_DIR} # GenAI may be installed alogside OpenVINO.
8+
NO_CMAKE_FIND_ROOT_PATH
9+
)
10+
11+
include(FetchContent)
12+
FetchContent_Declare(
13+
stb
14+
GIT_REPOSITORY https://github.com/nothings/stb.git
15+
GIT_TAG master
16+
)
17+
FetchContent_MakeAvailable(stb)
18+
19+
add_library(stb_image INTERFACE)
20+
target_include_directories(stb_image INTERFACE ${stb_SOURCE_DIR})
21+
22+
# VLM Pipeline Sample
23+
add_executable(vlm_pipeline_c vlm_pipeline.c load_image.c)
24+
25+
# Specifies that the source file should be compiled as a C source file
26+
set_source_files_properties(vlm_pipeline.c load_image.c PROPERTIES LANGUAGE C)
27+
28+
target_include_directories(vlm_pipeline_c PRIVATE
29+
${CMAKE_SOURCE_DIR}/src/c/include
30+
${stb_SOURCE_DIR}
31+
)
32+
33+
target_link_libraries(vlm_pipeline_c PRIVATE openvino::genai::c stb_image)
34+
if(UNIX AND NOT APPLE)
35+
target_link_libraries(vlm_pipeline_c PRIVATE m)
36+
endif()
37+
38+
set_target_properties(vlm_pipeline_c PROPERTIES
39+
# Ensure out-of-box LC_RPATH on macOS with SIP
40+
INSTALL_RPATH_USE_LINK_PATH ON)
41+
42+
# Install
43+
install(TARGETS vlm_pipeline_c
44+
RUNTIME DESTINATION samples_bin/
45+
COMPONENT samples_bin
46+
EXCLUDE_FROM_ALL)
47+
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#include "load_image.h"
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
6+
#ifdef _WIN32
7+
#define strcasecmp _stricmp
8+
#else
9+
#include <strings.h>
10+
#endif
11+
12+
#ifdef _WIN32
13+
#include <io.h>
14+
#define stat _stat
15+
#else
16+
#include <sys/stat.h>
17+
#endif
18+
19+
#include "openvino/c/openvino.h"
20+
21+
#define STB_IMAGE_IMPLEMENTATION
22+
#include "stb_image.h"
23+
24+
static const char* supported_extensions[] = {
25+
".jpg", ".jpeg", ".png", ".bmp", ".tga", ".psd", ".gif", ".hdr", ".pic", ".pnm"
26+
};
27+
static const size_t num_extensions = sizeof(supported_extensions) / sizeof(supported_extensions[0]);
28+
29+
static int is_supported_image(const char* filename) {
30+
if (!filename) return 0;
31+
32+
size_t len = strlen(filename);
33+
for (size_t i = 0; i < num_extensions; i++) {
34+
size_t ext_len = strlen(supported_extensions[i]);
35+
if (len >= ext_len) {
36+
const char* ext = filename + len - ext_len;
37+
if (strcasecmp(ext, supported_extensions[i]) == 0) {
38+
return 1;
39+
}
40+
}
41+
}
42+
return 0;
43+
}
44+
45+
typedef struct {
46+
unsigned char* image_data;
47+
int channels;
48+
int height;
49+
int width;
50+
} image_allocator_t;
51+
52+
static void* image_allocate(size_t bytes, size_t alignment, void* user_data) {
53+
image_allocator_t* allocator = (image_allocator_t*)user_data;
54+
if (allocator && allocator->image_data &&
55+
allocator->channels * allocator->height * allocator->width == (int)bytes) {
56+
return allocator->image_data;
57+
}
58+
return NULL;
59+
}
60+
61+
static void image_deallocate(void* ptr, size_t bytes, size_t alignment, void* user_data) {
62+
image_allocator_t* allocator = (image_allocator_t*)user_data;
63+
if (allocator && allocator->image_data &&
64+
allocator->channels * allocator->height * allocator->width == (int)bytes) {
65+
stbi_image_free(allocator->image_data);
66+
allocator->image_data = NULL;
67+
}
68+
}
69+
70+
#define CHECK_STATUS(return_status) \
71+
if (return_status != OK) { \
72+
fprintf(stderr, "[ERROR] return status %d, line %d\n", return_status, __LINE__); \
73+
goto err; \
74+
}
75+
76+
ov_tensor_t* load_image(const char* image_path) {
77+
if (!image_path) {
78+
fprintf(stderr, "Error: image_path is NULL\n");
79+
return NULL;
80+
}
81+
82+
if (!file_exists(image_path)) {
83+
fprintf(stderr, "Error: Image file '%s' does not exist\n", image_path);
84+
return NULL;
85+
}
86+
87+
int width, height, channels;
88+
const int desired_channels = 3;
89+
90+
unsigned char* data = stbi_load(image_path, &width, &height, &channels, desired_channels);
91+
if (!data) {
92+
fprintf(stderr, "Error: Failed to load image '%s': %s\n", image_path, stbi_failure_reason());
93+
return NULL;
94+
}
95+
96+
image_allocator_t* allocator = (image_allocator_t*)malloc(sizeof(image_allocator_t));
97+
if (!allocator) {
98+
fprintf(stderr, "Error: Failed to allocate memory for allocator\n");
99+
stbi_image_free(data);
100+
return NULL;
101+
}
102+
103+
allocator->image_data = data;
104+
allocator->channels = desired_channels;
105+
allocator->height = height;
106+
allocator->width = width;
107+
108+
ov_tensor_t* tensor = NULL;
109+
ov_element_type_e input_type = U8;
110+
int64_t dims[4] = {1, height, width, desired_channels};
111+
112+
ov_shape_t input_shape = {.rank = 0, .dims = NULL};
113+
ov_shape_create(4, dims, &input_shape);
114+
115+
ov_tensor_create_from_host_ptr(
116+
input_type,
117+
input_shape, // shape: [1, H, W, C]
118+
data,
119+
&tensor
120+
);
121+
122+
free(allocator);
123+
124+
return tensor;
125+
}
126+
127+
const ov_tensor_t** load_images(const char* image_path, size_t* tensor_count) {
128+
if (!image_path || !tensor_count) {
129+
fprintf(stderr, "Error: image_path or tensor_count is NULL\n");
130+
return NULL;
131+
}
132+
133+
if (!file_exists(image_path)) {
134+
fprintf(stderr, "Error: Image file '%s' does not exist\n", image_path);
135+
return NULL;
136+
}
137+
138+
ov_tensor_t* tensor = load_image(image_path);
139+
if (!tensor) {
140+
return NULL;
141+
}
142+
143+
const ov_tensor_t** tensors = (const ov_tensor_t**)malloc(sizeof(ov_tensor_t*));
144+
if (!tensors) {
145+
fprintf(stderr, "Error: Failed to allocate memory for single tensor\n");
146+
free_tensor(tensor);
147+
return NULL;
148+
}
149+
150+
tensors[0] = tensor;
151+
*tensor_count = 1;
152+
153+
return tensors;
154+
}
155+
156+
void free_tensor(ov_tensor_t* tensor) {
157+
if (tensor) {
158+
ov_tensor_free(tensor);
159+
}
160+
}
161+
162+
void free_tensor_array(ov_tensor_t** tensors, size_t count) {
163+
if (tensors) {
164+
for (size_t i = 0; i < count; i++) {
165+
if (tensors[i]) {
166+
ov_tensor_free(tensors[i]);
167+
}
168+
}
169+
free(tensors);
170+
}
171+
}
172+
173+
int file_exists(const char* path) {
174+
if (!path) return 0;
175+
176+
struct stat buffer;
177+
return (stat(path, &buffer) == 0);
178+
}
179+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#ifndef LOAD_IMAGE_H
2+
#define LOAD_IMAGE_H
3+
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
7+
#ifdef __cplusplus
8+
extern "C" {
9+
#endif
10+
11+
typedef struct ov_tensor ov_tensor_t;
12+
13+
ov_tensor_t* load_image(const char* image_path);
14+
15+
const ov_tensor_t** load_images(const char* image_path, size_t* tensor_count);
16+
17+
void free_tensor(ov_tensor_t* tensor);
18+
19+
void free_tensor_array(ov_tensor_t** tensors, size_t count);
20+
21+
int file_exists(const char* path);
22+
23+
#ifdef __cplusplus
24+
}
25+
#endif
26+
27+
#endif // LOAD_IMAGE_H
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/**
5+
* @file vlm_pipeline.c
6+
* @brief Example demonstrating how to use OpenVINO GenAI VLM Pipeline C API
7+
*/
8+
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include "openvino/genai/c/vlm_pipeline.h"
13+
#include "load_image.h"
14+
15+
#define MAX_PROMPT_LENGTH 64
16+
17+
// Callback function for streaming results
18+
ov_genai_streaming_status_e stream_callback(const char* str, void* args) {
19+
printf("%s", str);
20+
fflush(stdout);
21+
return OV_GENAI_STREAMING_STATUS_RUNNING;
22+
}
23+
24+
int main(int argc, char* argv[]) {
25+
if (argc < 4) {
26+
printf("Usage: %s <models_path> <device> <image_path> \n", argv[0]);
27+
printf("Example: %s ./models CPU ./image.jpg \n", argv[0]);
28+
return -1;
29+
}
30+
31+
const char* models_path = argv[1];
32+
const char* device = argv[2];
33+
const char* image_path = argv[3];
34+
35+
size_t tensor_count;
36+
const ov_tensor_t** tensors = load_images(image_path, &tensor_count);
37+
38+
// Create VLM pipeline
39+
ov_genai_vlm_pipeline* pipeline = NULL;
40+
ov_genai_vlm_pipeline_create(models_path, device, 0, &pipeline);
41+
42+
// Set up streaming callback
43+
streamer_callback callback = {
44+
.callback_func = stream_callback,
45+
.args = NULL
46+
};
47+
48+
// Generate response
49+
ov_genai_vlm_decoded_results* results = NULL;
50+
ov_genai_generation_config* config = NULL;
51+
ov_genai_generation_config_create(&config);
52+
ov_genai_generation_config_set_max_new_tokens(config, 100);
53+
char prompt[MAX_PROMPT_LENGTH];
54+
55+
ov_genai_vlm_pipeline_start_chat(pipeline);
56+
printf("question:\n");
57+
58+
if (fgets(prompt, MAX_PROMPT_LENGTH, stdin)) {
59+
prompt[strcspn(prompt, "\n")] = 0;
60+
if (strlen(prompt) > 0) {
61+
ov_genai_vlm_pipeline_generate(pipeline, prompt, tensors, tensor_count, config, &callback, &results);
62+
printf("\n----------\nquestion:\n");
63+
}
64+
}
65+
66+
while (fgets(prompt, MAX_PROMPT_LENGTH, stdin)) {
67+
prompt[strcspn(prompt, "\n")] = 0;
68+
if (strlen(prompt) == 0) {
69+
continue;
70+
}
71+
ov_genai_vlm_pipeline_generate(pipeline, prompt, NULL, 0, config, &callback, &results);
72+
printf("\n----------\nquestion:\n");
73+
}
74+
ov_genai_vlm_pipeline_finish_chat(pipeline);
75+
76+
77+
// Get performance metrics
78+
ov_genai_perf_metrics* metrics = NULL;
79+
ov_genai_vlm_decoded_results_get_perf_metrics(results, &metrics);
80+
81+
// Get final result string
82+
size_t output_size = 0;
83+
ov_genai_vlm_decoded_results_get_string(results, NULL, &output_size);
84+
if (output_size > 0) {
85+
char* output = (char*)malloc(output_size);
86+
if (output) {
87+
ov_genai_vlm_decoded_results_get_string(results, output, &output_size);
88+
free(output);
89+
}
90+
}
91+
92+
// Cleanup
93+
ov_genai_vlm_decoded_results_free(results);
94+
ov_genai_generation_config_free(config);
95+
ov_genai_vlm_pipeline_free(pipeline);
96+
97+
return 0;
98+
}

0 commit comments

Comments
 (0)