@brief: preprocess the image for Qwen3_5VL model @note: Converts uint8 image to BF16 format, data is already in (3, H, W) CHW layout @param: image: the image to preprocess (already in CHW format) @return: the preprocessed image in BF16 format
| 159 | ///@param: image: the image to preprocess (already in CHW format) |
| 160 | ///@return: the preprocessed image in BF16 format |
| 161 | void Qwen3_5VL::preprocess_image(qwen3_5vl_image_t& image, std::vector<bf16> &pixel_values) { |
| 162 | const int width = image.width; |
| 163 | const int height = image.height; |
| 164 | const int channels = 3; // RGB |
| 165 | int resized_height; |
| 166 | int resized_width; |
| 167 | // do the automatically resizing in here |
| 168 | |
| 169 | qwen3_5vl_npu* lm_engine_qwen3_5_ptr = reinterpret_cast<qwen3_5vl_npu*>(this->lm_engine.get()); |
| 170 | smart_resize( |
| 171 | height, width, |
| 172 | resized_height, resized_width, |
| 173 | |
| 174 | lm_engine_qwen3_5_ptr->QWEN3_5_PATCH_SIZE * lm_engine_qwen3_5_ptr->QWEN3_5_IMAGE_MERGE_SIZE, |
| 175 | lm_engine_qwen3_5_ptr->QWEN3_5_SHORTEST_EDGE, |
| 176 | lm_engine_qwen3_5_ptr->QWEN3_5_LONGEST_EDGE |
| 177 | ); |
| 178 | // std::cout << "resized_height "<< resized_height << " resized_width " << resized_width <<std::endl; |
| 179 | |
| 180 | // Cache size calculations for efficiency |
| 181 | const uint32_t single_frame_size = resized_height * resized_width * channels; |
| 182 | const uint32_t total_patch_size = single_frame_size * lm_engine_qwen3_5_ptr->QWEN3_5_TEMPORAL_PATCH_SIZE; |
| 183 | const uint32_t grid_h = resized_height / lm_engine_qwen3_5_ptr->QWEN3_5_PATCH_SIZE; |
| 184 | const uint32_t grid_w = resized_width / lm_engine_qwen3_5_ptr->QWEN3_5_PATCH_SIZE; |
| 185 | |
| 186 | // Pre-allocate final buffer to avoid reallocation |
| 187 | const uint32_t prev_pixel_values_size = pixel_values.size(); |
| 188 | pixel_values.resize(prev_pixel_values_size + total_patch_size); |
| 189 | |
| 190 | // Use non-optimized path for consistent results across platforms |
| 191 | auto resize_image = imgproc::avx512::resize_bicubic_antialias_rgb_planar_avx512( |
| 192 | image._data.data(), width, height, resized_width, resized_height, true |
| 193 | ); |
| 194 | |
| 195 | // Reuse scratch buffer across calls to avoid repeated allocations |
| 196 | static thread_local std::vector<float> patch_vector_scratch; |
| 197 | if (patch_vector_scratch.size() < total_patch_size) { |
| 198 | patch_vector_scratch.resize(total_patch_size); |
| 199 | } |
| 200 | |
| 201 | // Apply rescale and normalization to first frame |
| 202 | imgproc::avx512::rescale_and_normalize_avx512( |
| 203 | resize_image.data(), patch_vector_scratch.data(), |
| 204 | resized_width, resized_height, channels, |
| 205 | true, lm_engine_qwen3_5_ptr->QWEN3_5_VISION_RESCALE_FACTOR, |
| 206 | true, lm_engine_qwen3_5_ptr->QWEN3_5_VISION_RESCALE_IMAGE_MEAN, lm_engine_qwen3_5_ptr->QWEN3_5_VISION_RESCALE_IMAGE_STD |
| 207 | ); |
| 208 | |
| 209 | // Replicate first frame for temporal patches (optimized for QWEN3_5_TEMPORAL_PATCH_SIZE = 2) |
| 210 | // This is more efficient than a loop for the common case |
| 211 | if (lm_engine_qwen3_5_ptr->QWEN3_5_TEMPORAL_PATCH_SIZE == 2) { |
| 212 | memcpy( |
| 213 | patch_vector_scratch.data() + single_frame_size, |
| 214 | patch_vector_scratch.data(), |
| 215 | single_frame_size * sizeof(float) |
| 216 | ); |
| 217 | } else { |
| 218 | // Generic loop for other TEMPORAL_PATCH_SIZE values |
nothing calls this directly
no test coverage detected