Skip to content

Commit d1e281a

Browse files
Merge pull request #1152 from qualcomm/fix/qairt-nonascii-image-path
fix(sdk): load non-ASCII image paths in qairt VLM on Windows
2 parents 59a7ca2 + 1f7c3c0 commit d1e281a

3 files changed

Lines changed: 77 additions & 30 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2026 Qualcomm Technologies, Inc. and/or its subsidiaries.
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
#pragma once
5+
6+
#include <string>
7+
8+
#if defined(_WIN32)
9+
#include <windows.h>
10+
#endif
11+
12+
namespace geniex::qairt {
13+
14+
// The vision pipeline (geniex-proc) opens image files with std::filesystem and stb_image,
15+
// both of which interpret narrow paths in the active ANSI code page rather than UTF-8. On
16+
// Windows that breaks any non-ASCII path. Collapse the UTF-8 path to its 8.3 short name and
17+
// re-encode it in that same ANSI code page, so those APIs open it correctly. No-op elsewhere,
18+
// and falls back to the original path when no ANSI-representable short name is available (e.g.
19+
// 8.3 disabled on the volume).
20+
inline std::string to_loadable_path(const std::string& utf8_path) {
21+
#if defined(_WIN32)
22+
int wlen = MultiByteToWideChar(CP_UTF8, 0, utf8_path.c_str(), -1, nullptr, 0);
23+
if (wlen <= 0) return utf8_path;
24+
std::wstring wpath(wlen, L'\0');
25+
MultiByteToWideChar(CP_UTF8, 0, utf8_path.c_str(), -1, wpath.data(), wlen);
26+
27+
DWORD slen = GetShortPathNameW(wpath.c_str(), nullptr, 0);
28+
if (slen == 0) return utf8_path;
29+
std::wstring wshort(slen, L'\0');
30+
if (GetShortPathNameW(wpath.c_str(), wshort.data(), slen) == 0) return utf8_path;
31+
32+
// Encode in the active ANSI code page — the exact encoding the downstream narrow-path APIs
33+
// decode with. If any character has no ANSI representation, bail to the original path rather
34+
// than hand over a lossy substitution.
35+
int nlen = WideCharToMultiByte(CP_ACP, 0, wshort.c_str(), -1, nullptr, 0, nullptr, nullptr);
36+
if (nlen <= 0) return utf8_path;
37+
std::string narrow(nlen, '\0');
38+
BOOL used_default = FALSE;
39+
WideCharToMultiByte(CP_ACP, 0, wshort.c_str(), -1, narrow.data(), nlen, nullptr, &used_default);
40+
if (used_default) return utf8_path;
41+
narrow.resize(nlen - 1); // drop the terminating NUL from the count
42+
return narrow;
43+
#else
44+
return utf8_path;
45+
#endif
46+
}
47+
48+
} // namespace geniex::qairt

sdk/plugins/qairt/src/llm.cpp

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -262,22 +262,21 @@ int32_t QairtLlm::generate(const geniex_LlmGenerateInput* input, geniex_LlmGener
262262
output->profile_data.prefill_speed =
263263
result.prompt_tokens > 0 && result.ttft_ms > 0.0 ? result.prompt_tokens / (result.ttft_ms / 1000.0) : 0.0;
264264

265-
// Stop reason (string must be static/persistent).
266-
static const char* kStopEos = "eos";
267-
static const char* kStopLength = "length";
268-
static const char* kStopUser = "user";
269-
if (result.stop_reason == "eos")
270-
output->profile_data.stop_reason = kStopEos;
271-
else if (result.stop_reason == "length" || result.stop_reason == "context_length")
272-
output->profile_data.stop_reason = kStopLength;
273-
else if (result.stop_reason == "user")
274-
output->profile_data.stop_reason = kStopUser;
275-
else
276-
output->profile_data.stop_reason = kStopEos;
277-
278-
if (result.stop_reason == "context_length") {
265+
// Stop Reason
266+
if (result.stop_reason == "user") {
267+
output->profile_data.stop_reason = "user";
268+
} else if (result.stop_reason == "length") {
269+
output->profile_data.stop_reason = "length";
270+
} else if (result.stop_reason == "context_length") {
271+
output->profile_data.stop_reason = "length";
279272
GENIEX_LOG_WARN("QAIRT generate: context length exceeded (partial result populated)");
280273
return GENIEX_ERROR_LLM_TOKENIZATION_CONTEXT_LENGTH;
274+
} else if (result.stop_reason == "error") {
275+
output->profile_data.stop_reason = "eos";
276+
GENIEX_LOG_ERROR("QAIRT generate failed during prompt processing (empty result)");
277+
return GENIEX_ERROR_LLM_GENERATION_FAILED;
278+
} else {
279+
output->profile_data.stop_reason = "eos";
281280
}
282281
return GENIEX_SUCCESS;
283282
}

sdk/plugins/qairt/src/vlm.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "geniex-proc/types.h" // ChatMessage, MMContent, Role::, Modality::
2020
#include "llm/llm_spec_loader.h" // parseGenieSamplerConfig
2121
#include "logging.h"
22+
#include "path_utils.h"
2223
#include "pipeline/vlm_pipeline.h"
2324
#include "qnn_runtime_utils.h"
2425
#include "sampler_config_utils.h"
@@ -247,7 +248,7 @@ int32_t QairtVlm::generate(const geniex_VlmGenerateInput* input, geniex_VlmGener
247248
image_paths.reserve(static_cast<size_t>(input->config->image_count));
248249
for (int32_t i = 0; i < input->config->image_count; ++i) {
249250
if (input->config->image_paths[i]) {
250-
image_paths.emplace_back(input->config->image_paths[i]);
251+
image_paths.emplace_back(qairt::to_loadable_path(input->config->image_paths[i]));
251252
}
252253
}
253254
}
@@ -289,24 +290,23 @@ int32_t QairtVlm::generate(const geniex_VlmGenerateInput* input, geniex_VlmGener
289290
? static_cast<double>(result.prompt_tokens) / (result.ttft_ms / 1000.0)
290291
: 0.0;
291292

292-
// Stop reason.
293-
static const char* kStopEos = "eos";
294-
static const char* kStopLength = "length";
295-
static const char* kStopUser = "user";
296-
if (result.stop_reason == "eos")
297-
output->profile_data.stop_reason = kStopEos;
298-
else if (result.stop_reason == "length" || result.stop_reason == "context_length")
299-
output->profile_data.stop_reason = kStopLength;
300-
else if (result.stop_reason == "user")
301-
output->profile_data.stop_reason = kStopUser;
302-
else
303-
output->profile_data.stop_reason = kStopEos;
304-
305-
if (result.stop_reason == "context_length") {
293+
// Stop Reason
294+
if (result.stop_reason == "user") {
295+
output->profile_data.stop_reason = "user";
296+
} else if (result.stop_reason == "length") {
297+
output->profile_data.stop_reason = "length";
298+
} else if (result.stop_reason == "context_length") {
299+
output->profile_data.stop_reason = "length";
306300
GENIEX_LOG_WARN("QAIRT VLM generate: context length exceeded (partial result populated)");
307301
return GENIEX_ERROR_LLM_TOKENIZATION_CONTEXT_LENGTH;
302+
} else if (result.stop_reason == "error") {
303+
output->profile_data.stop_reason = "eos";
304+
GENIEX_LOG_ERROR("QAIRT VLM generate failed during prompt processing (empty result)");
305+
return GENIEX_ERROR_VLM_GENERATION_FAILED;
306+
} else {
307+
output->profile_data.stop_reason = "eos";
308308
}
309309
return GENIEX_SUCCESS;
310310
}
311311

312-
} // namespace geniex
312+
} // namespace geniex

0 commit comments

Comments
 (0)