Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -174,17 +174,17 @@ http_archive(

################################### Official/forked mediapipe repository #########
#### Will be used on feature release
#local_repository(
# name = "mediapipe",
# path = "C:\\git\\mediapipe",
#)
local_repository(
name = "mediapipe",
path = "C:\\git\\mediapipe",
)

# Remote fallback for non-local development:
git_repository(
name = "mediapipe",
remote = "https://github.com/openvinotoolkit/mediapipe",
commit = "ec9f5b3e5e765097f8fd9622f2e5d0c037351173", # Fix capi ovms handle ownership - code review1
)
#git_repository(
# name = "mediapipe",
# remote = "https://github.com/openvinotoolkit/mediapipe",
# commit = "ec9f5b3e5e765097f8fd9622f2e5d0c037351173", # Fix capi ovms handle ownership - code review1
#)

# Protobuf for Node dependencies
http_archive(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
{
"config": {
"name": "clip",
"base_path": "/model/"
"base_path": "C:\\git\\model_server\\demos\\python_demos\\clip_image_classification\\servable\\model",
"target_device": "CPU"
}
}],
"mediapipe_config_list": [
{
"name":"python_model",
"graph_path":"/workspace/graph.pbtxt"
"graph_path":"C:\\git\\model_server\\demos\\python_demos\\clip_image_classification\\servable\\graph.pbtxt"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ node {
output_stream: "PIXEL_VALUES_PY:pixel_values_py"
node_options: {
[type.googleapis.com/mediapipe.PythonExecutorCalculatorOptions]: {
handler_path: "/workspace/preprocess.py"
handler_path: "C:\\git\\model_server\\demos\\python_demos\\clip_image_classification\\servable\\preprocess.py"
}
}
}
Expand Down Expand Up @@ -118,7 +118,7 @@ node {
output_stream: "LABEL:output_label"
node_options: {
[type.googleapis.com/mediapipe.PythonExecutorCalculatorOptions]: {
handler_path: "/workspace/postprocess.py"
handler_path: "C:\\git\\model_server\\demos\\python_demos\\clip_image_classification\\servable\\postprocess.py"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@

from pyovms import Tensor
import numpy as np
from scipy.special import softmax
from tritonclient.utils import deserialize_bytes_tensor


def softmax(x, axis=None):
x_max = np.max(x, axis=axis, keepdims=True)
e_x = np.exp(x - x_max)
return e_x / np.sum(e_x, axis=axis, keepdims=True)

class OvmsPythonModel:

def initialize(self, kwargs: dict):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#*****************************************************************************

from pyovms import Tensor
from transformers import CLIPProcessor
from PIL import Image
import numpy as np
from io import BytesIO
Expand All @@ -24,6 +23,10 @@
class OvmsPythonModel:

def initialize(self, kwargs: dict):
try:
from transformers import CLIPProcessor
except ImportError:
from transformers.models.clip.processing_clip import CLIPProcessor
model_id = "openai/clip-vit-base-patch16"
self.processor = CLIPProcessor.from_pretrained(model_id)

Expand Down
40 changes: 34 additions & 6 deletions docs/windows_developer_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,19 @@ This default command compiles ovms.exe without python dependencies, just C++ bin
windows_build.bat
```

Optionally, you add parameter to the windows_build.bat script
Optionally, you add parameters to the windows_build.bat script
```bat
windows_build.bat my_dir_on_c --with_python --with_tests --integrity
windows_build.bat my_dir_on_c --with_python 3.13.1 --with_tests --integrity
```
[arg1] This way you can change default dependency location directory to c:\my_dir_on_c
[arg2] --with_python - this will build the ovms.exe with python dependency and support for python chat templates for GENAI LLM
[arg3] --with_tests - this will also build ovms_test.exe target
[arg4] --integrity - Add the compilation integrity flag to 0 or 1 - set the additional integritycheck compilation flag when compiling dependencies
[arg3] additional Python version (e.g. `3.13.1`) - builds a second set of Python runtime libraries (`libovmspython`, `libpython_calculators`, `pyovms`) linked against the specified Python ABI, on top of the default Python 3.12 ABI. This enables serving Python nodes from virtualenvs created with that Python version. Requires a full Python development install (with headers and `pythonXYZ.dll`) at `C:\opt\Python<MAJOR><MINOR>` (e.g. `C:\opt\Python313`).
[arg4] --with_tests - this will also build ovms_test.exe target
[arg5] --integrity - set the additional integritycheck compilation flag

> **Note:** When arg5 is provided, the build performs three Bazel invocations: the main build (cp312 default), the extra-ABI build (e.g. cp313), and a restore build that returns the bazel-bin artifacts to cp312 linkage so that packaging picks up the correct default DLLs.

The staged extra-ABI libraries are placed in `dist\windows\python_abi_addons\cp<tag>\` and are automatically picked up by `windows_create_package.bat --with_python`.

# Running unit tests - optional
The script compiles ovms_test binary with C++ only, downloads and converts test LLM models (src\tests\llm_testing).
Expand All @@ -92,8 +97,12 @@ windows_test.bat

The optional script compiles ovms_test binary with python support, downloads and converts test LLM models (src\tests\llm_testing) and installs Python torch and optimum.
```bat
windows_test.bat opt --with_python
windows_test.bat opt --with_python 3.13.1
```
[arg1] This way you can change default dependency location directory to c:\my_dir_on_c
[arg2] --with_python - compile and run tests with Python support
[arg3] optional additional Python ABI version (e.g. `3.13.1`) - sets `OVMS_PYTHON_ABI` so the Python runtime tests exercise the versioned loader path (cp313 DLLs). Requires the dev Python install at `C:\opt\Python313`.
[arg4] optional gtest filter (default `*`)

# Creating deployment package
This step prepares ovms.zip deployment package from the build artifacts in the dist\windows\ directory. Run this script after successful compilation.
Expand All @@ -102,11 +111,30 @@ The default version creates C++ only version without Python dependency.
windows_create_package.bat
```

Optionally you can create a package with Python dependency. Note that to create valid package with Python, you need to build using `--with_python` flag in the previous step as well.
Optionally you can create a package with Python dependency. Note that to create a valid package with Python, you need to build using the `--with_python` flag in the previous step as well.
```bat
windows_create_package.bat opt --with_python
```

The package includes the default Python 3.12 embedded runtime and its libraries:
- `libovmspython.dll` / `libovmspython-cp312.dll` — Python runtime loader (cp312 fallback / cp312 explicit)
- `libpython_calculators.dll` / `libpython_calculators-cp312.dll` — MediaPipe Python calculator plugin
- `python\pyovms.pyd` and `python\cp312\pyovms.pyd` — Python binding module

If arg5 was passed to `windows_build.bat` (e.g. `3.13.1`), the additional ABI libraries are also included:
- `libovmspython-cp313.dll`, `libpython_calculators-cp313.dll`
- `python\cp313\pyovms.pyd`

**Selecting the active Python ABI at runtime:** `ovms.exe` detects the ABI from the `PYTHONHOME` environment variable. When started via `setupvars.bat`, `PYTHONHOME` points to the bundled `python\` directory (no version digits), so the unversioned fallback DLLs are used (cp312). To use a different ABI — e.g. when serving Python nodes from a cp313 virtualenv — set the environment before starting `ovms.exe`:
```bat
set PYTHONHOME=C:\Program Files\Python313
set PYTHONPATH=<path_to_venv>\Lib\site-packages;<ovms_dir>\python\cp313
```
Or use the explicit override to bypass auto-detection:
```bat
set OVMS_PYTHON_ABI=313
```

# Test the Deployment
You can follow the [baremetal deployment guide](deploying_server_baremetal.md) for information how to deploy and use the ovms.zip package.

Expand Down
11 changes: 11 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ cc_binary(
"//src/mediapipe_internal:libovms_mediapipe_runtime_owner",
"//src/mediapipe_internal:libovms_mediapipe_runtime_c_api_exports",
":libovms_version_impl",
":ovms_lib",
],
linkopts = select({
"//conditions:default": ["-Wl,--allow-shlib-undefined"],
Expand Down Expand Up @@ -713,9 +714,11 @@ ovms_cc_library(
"kfs_python_tensor_bridge_hdrs",
"mediapipe_executor_interface",
"libovms_execution_context",
"libovms_module",
"libovmsstatus",
"libovmslogging",
"libovms_servable_definition",
"python_runtime_version",
"//src/utils:newline_delimited",
],
visibility = ["//visibility:public"],
Expand Down Expand Up @@ -871,6 +874,13 @@ ovms_cc_library(
visibility = ["//visibility:public"],
)

ovms_cc_library(
name = "python_runtime_version",
hdrs = ["python_runtime_version.hpp"],
srcs = ["python_runtime_version.cpp"],
visibility = ["//visibility:public"],
)

ovms_cc_library(
name = "python_calculators_plugin_loader",
hdrs = ["python_calculators_plugin_loader.hpp"],
Expand All @@ -880,6 +890,7 @@ ovms_cc_library(
"libovmslogging",
"libovmsstatus",
"kfs_python_tensor_bridge",
"python_runtime_version",
],
visibility = ["//visibility:public"],
)
Expand Down
1 change: 1 addition & 0 deletions src/llm/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,7 @@ ovms_cc_library(
],
deps = [
"//src:libovmslogging",
"//src:python_runtime_version",
],
visibility = ["//visibility:public"],
)
Expand Down
3 changes: 2 additions & 1 deletion src/llm/runtime_chat_template_runtime_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#endif

#include "../logging.hpp"
#include "../python_runtime_version.hpp"

namespace ovms {
namespace {
Expand Down Expand Up @@ -107,7 +108,7 @@ std::vector<std::string> buildCandidates() {
"./bazel-bin/src/python/libovmspython.so",
});
#endif
return candidates;
return withAbiVersionedCandidates(candidates);
}

void initializeRuntimeState() {
Expand Down
40 changes: 31 additions & 9 deletions src/mediapipe_runtime_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@

#include "logging.hpp"
#include "kfs_python_tensor_bridge.hpp"
#include "python_runtime_version.hpp"
#include "mediapipe_graph_executor_interface.hpp"
#include "server.hpp"
#include "utils/newline_delimited.hpp"

struct OVMS_Server_;
Expand Down Expand Up @@ -325,10 +327,26 @@ MediapipeRuntimeApi::MediapipeRuntimeApi(PythonBackend* pythonBackend) :
"bazel-bin\\src\\ovms_mediapipe_runtime_shared.dll",
".\\bazel-bin\\src\\ovms_mediapipe_runtime_shared.dll"};

char executablePathBuf[MAX_PATH] = {0};
DWORD executablePathBufLength = GetModuleFileNameA(nullptr, executablePathBuf, MAX_PATH);
if (executablePathBufLength > 0 && executablePathBufLength < MAX_PATH) {
std::string exePath(executablePathBuf, executablePathBufLength);
std::string exeDir = ".";
size_t separatorPos = exePath.find_last_of("\\/");
if (separatorPos != std::string::npos) {
exeDir = exePath.substr(0, separatorPos);
}
candidates.emplace_back(exeDir + "\\ovms_mediapipe_runtime_shared.dll");
candidates.emplace_back(exeDir + "\\src\\ovms_mediapipe_runtime_shared.dll");
}

candidates = withAbiVersionedCandidates(candidates);

for (const auto& candidate : candidates) {
api->handle = LoadLibraryA(candidate.c_str());
if (api->handle != nullptr) {
SPDLOG_TRACE("MediaPipe runtime API loaded from: {}", candidate);
SPDLOG_INFO("MediaPipe runtime API loaded from: {}", candidate);
SPDLOG_INFO("MediaPipe runtime shared module handle = {}", fmt::ptr(api->handle));
break;
}
}
Expand Down Expand Up @@ -389,14 +407,18 @@ MediapipeRuntimeApi::MediapipeRuntimeApi(PythonBackend* pythonBackend) :
}

if (api->setExternalServerHandle != nullptr) {
OVMS_Server* serverHandle = nullptr;
auto* status = OVMS_ServerNew(&serverHandle);
if (status != nullptr) {
OVMS_StatusDelete(status);
SPDLOG_WARN("Failed to obtain OVMS server handle for MediaPipe runtime-shared");
} else {
api->setExternalServerHandle(static_cast<void*>(serverHandle));
}
// The MediaPipe runtime-shared library is loaded as a separate DLL and has its
// own static/global state. Creating a fresh OVMS_Server inside this runtime would
// not point at the live server instance used by the main process; bind directly to
// the main process singleton instead.
auto* serverHandle = static_cast<void*>(&ovms::Server::instance());
SPDLOG_INFO("MediaPipe runtime-shared is setting external OVMS server handle: live singleton ptr={} (this={})",
fmt::ptr(serverHandle),
fmt::ptr(&ovms::Server::instance()));
api->setExternalServerHandle(serverHandle);
SPDLOG_INFO("MediaPipe runtime-shared bound to main OVMS server singleton");
} else {
SPDLOG_WARN("MediaPipe runtime-shared does not export OVMS_MPSetExternalServerHandle; it will keep creating a local server handle inside the DLL");
}
}

Expand Down
1 change: 1 addition & 0 deletions src/python/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,7 @@ ovms_cc_library(
"//src:libovmslogging",
"//src:libovmsstatus",
"//src:libovms_module",
"//src:python_runtime_version",
],
visibility = ["//visibility:public"],
)
Expand Down
29 changes: 27 additions & 2 deletions src/python/python_runtime_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ using PythonLibraryHandle = HMODULE;

#include "../logging.hpp"
#include "../module.hpp"
#include "../python_runtime_version.hpp"

namespace ovms {

Expand Down Expand Up @@ -96,6 +97,7 @@ Module* ensurePythonRuntimeLoaded() {
"./src/python/libovmspython.so",
"bazel-bin/src/python/libovmspython.so",
"./bazel-bin/src/python/libovmspython.so"};
candidates = withAbiVersionedCandidates(candidates);

for (const auto& candidate : candidates) {
pythonRuntimeHandle = dlopen(candidate.c_str(), RTLD_NOW | RTLD_GLOBAL);
Expand Down Expand Up @@ -158,17 +160,28 @@ Module* ensurePythonRuntimeLoaded() {
candidates.insert(candidates.end(), executableRelativeCandidates.begin(), executableRelativeCandidates.end());
candidates.insert(candidates.end(), runfilesCandidates.begin(), runfilesCandidates.end());
}
candidates = withAbiVersionedCandidates(candidates);

std::string triedCandidates;
for (const auto& candidate : candidates) {
pythonRuntimeHandle = LoadLibraryA(candidate.c_str());
if (pythonRuntimeHandle != nullptr) {
break;
}
if (!triedCandidates.empty())
triedCandidates += ", ";
triedCandidates += candidate;
}

if (pythonRuntimeHandle == nullptr) {
DWORD error = GetLastError();
SPDLOG_WARN("Python runtime library libovmspython.dll failed to load: {} ({})", error, std::system_category().message(error));
SPDLOG_WARN("Python runtime library libovmspython.dll failed to load. "
"Tried candidates: [{}]. "
"Ensure the OVMS package directory is on PATH and the correct ABI DLL exists next to ovms.exe. "
"Detected ABI tag: '{}'. Win32 error: {} ({})",
triedCandidates,
detectPythonAbiTag(),
error, std::system_category().message(error));
return nullptr;
}
createPythonInterpreterModuleFn = reinterpret_cast<CreatePythonInterpreterModuleFn>(GetProcAddress(pythonRuntimeHandle, "OVMS_createPythonInterpreterModule"));
Expand All @@ -192,7 +205,19 @@ Module* ensurePythonRuntimeLoaded() {

const char* pythonRuntimeValidationError = nullptr;
if (!validatePythonEnvironmentFn(&pythonRuntimeValidationError)) {
SPDLOG_WARN("Python runtime environment validation failed. Ensure Python dependencies and PYTHONPATH are configured. Details: {}",
const std::string abiTag = detectPythonAbiTag();
const char* pythonHome = std::getenv("PYTHONHOME");
const char* pythonPath = std::getenv("PYTHONPATH");
SPDLOG_WARN("Python runtime environment validation failed. "
"Detected ABI tag: '{}'. PYTHONHOME: '{}'. PYTHONPATH: '{}'. "
"Ensure the correct pyovms.pyd (e.g. python\\cp{}\\pyovms.pyd) is on PYTHONPATH "
"and that the Python installation directory (containing python{}.dll) is on PATH. "
"Details: {}",
abiTag.empty() ? "<none - will use unversioned fallback DLLs>" : abiTag,
pythonHome != nullptr ? pythonHome : "<not set>",
pythonPath != nullptr ? pythonPath : "<not set>",
abiTag.empty() ? "312" : abiTag,
abiTag.empty() ? "312" : abiTag,
pythonRuntimeValidationError != nullptr ? pythonRuntimeValidationError : "Unknown error");
createPythonInterpreterModuleFn = nullptr;
validatePythonEnvironmentFn = nullptr;
Expand Down
Loading