backend: support non-ASCII characters in path to llmodel libs on Windows (#2388)

* backend: refactor dlhandle.h into oscompat.{cpp,h}

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* llmodel: alias std::filesystem

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* llmodel: use wide strings for paths on Windows

Using the native path representation allows us to manipulate paths and
call LoadLibraryEx without mangling non-ASCII characters.

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* llmodel: prefer built-in std::filesystem functionality

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* oscompat: fix string type error

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* backend: rename oscompat back to dlhandle

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* dlhandle: fix #includes

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* dlhandle: remove another #include

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* dlhandle: move dlhandle #include

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* dlhandle: remove #includes that are covered by dlhandle.h

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

* llmodel: fix #include order

Signed-off-by: Jared Van Bortel <jared@nomic.ai>

---------

Signed-off-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
Jared Van Bortel 2024-05-31 13:12:28 -04:00 committed by GitHub
parent 8a70f770a2
commit 4e89a9c44f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 91 additions and 94 deletions

View File

@ -149,7 +149,7 @@ endforeach()
add_library(llmodel add_library(llmodel
llmodel.h llmodel.cpp llmodel_shared.cpp llmodel.h llmodel.cpp llmodel_shared.cpp
llmodel_c.h llmodel_c.cpp llmodel_c.h llmodel_c.cpp
dlhandle.h dlhandle.cpp
) )
target_compile_definitions(llmodel PRIVATE LIB_FILE_EXT="${CMAKE_SHARED_LIBRARY_SUFFIX}") target_compile_definitions(llmodel PRIVATE LIB_FILE_EXT="${CMAKE_SHARED_LIBRARY_SUFFIX}")

View File

@ -0,0 +1,56 @@
#include "dlhandle.h"
#ifndef _WIN32
# include <dlfcn.h>
#else
# include <sstream>
# define WIN32_LEAN_AND_MEAN
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
#endif
namespace fs = std::filesystem;
#ifndef _WIN32
Dlhandle::Dlhandle(const fs::path &fpath) {
chandle = dlopen(fpath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!chandle) {
throw Exception("dlopen(\"" + fpath.filename().string() + "\"): " + dlerror());
}
}
Dlhandle::~Dlhandle() {
if (chandle) dlclose(chandle);
}
void *Dlhandle::get_internal(const char *symbol) const {
return dlsym(chandle, symbol);
}
#else // defined(_WIN32)
Dlhandle::Dlhandle(const fs::path &fpath) {
auto afpath = fs::absolute(fpath);
chandle = LoadLibraryExW(afpath.c_str(), NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
if (!chandle) {
auto err = GetLastError();
std::ostringstream ss;
ss << "LoadLibraryExW(\"" << fpath.filename().string() << "\") failed with error 0x" << std::hex << err;
throw Exception(ss.str());
}
}
Dlhandle::~Dlhandle() {
if (chandle) FreeLibrary(HMODULE(chandle));
}
void *Dlhandle::get_internal(const char *symbol) const {
return GetProcAddress(HMODULE(chandle), symbol);
}
#endif // defined(_WIN32)

View File

@ -1,74 +1,15 @@
#ifndef DLHANDLE_H #pragma once
#define DLHANDLE_H
#ifndef _WIN32
#include <string>
#include <stdexcept>
#include <utility>
#include <dlfcn.h>
class Dlhandle {
void *chandle;
public:
class Exception : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
Dlhandle() : chandle(nullptr) {}
Dlhandle(const std::string& fpath, int flags = RTLD_LAZY | RTLD_LOCAL) {
chandle = dlopen(fpath.c_str(), flags);
if (!chandle) {
throw Exception("dlopen(\""+fpath+"\"): "+dlerror());
}
}
Dlhandle(const Dlhandle& o) = delete;
Dlhandle(Dlhandle&& o) : chandle(o.chandle) {
o.chandle = nullptr;
}
~Dlhandle() {
if (chandle) dlclose(chandle);
}
auto operator =(Dlhandle&& o) {
chandle = std::exchange(o.chandle, nullptr);
}
bool is_valid() const {
return chandle != nullptr;
}
operator bool() const {
return is_valid();
}
template<typename T>
T* get(const std::string& fname) const {
auto fres = reinterpret_cast<T*>(dlsym(chandle, fname.c_str()));
return (dlerror()==NULL)?fres:nullptr;
}
auto get_fnc(const std::string& fname) const {
return get<void*(...)>(fname);
}
};
#else
#include <algorithm>
#include <filesystem> #include <filesystem>
#include <string>
#include <exception>
#include <stdexcept> #include <stdexcept>
#include <string>
#include <utility>
#define WIN32_LEAN_AND_MEAN namespace fs = std::filesystem;
#ifndef NOMINMAX
# define NOMINMAX
#endif
#include <windows.h>
#include <libloaderapi.h>
class Dlhandle { class Dlhandle {
HMODULE chandle; void *chandle = nullptr;
public: public:
class Exception : public std::runtime_error { class Exception : public std::runtime_error {
@ -76,34 +17,31 @@ public:
using std::runtime_error::runtime_error; using std::runtime_error::runtime_error;
}; };
Dlhandle() : chandle(nullptr) {} Dlhandle() = default;
Dlhandle(const std::string& fpath) { Dlhandle(const fs::path &fpath);
std::string afpath = std::filesystem::absolute(fpath).string();
std::replace(afpath.begin(), afpath.end(), '/', '\\');
chandle = LoadLibraryExA(afpath.c_str(), NULL, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
if (!chandle) {
throw Exception("dlopen(\""+fpath+"\"): Error");
}
}
Dlhandle(const Dlhandle &o) = delete; Dlhandle(const Dlhandle &o) = delete;
Dlhandle(Dlhandle&& o) : chandle(o.chandle) { Dlhandle(Dlhandle &&o)
: chandle(o.chandle)
{
o.chandle = nullptr; o.chandle = nullptr;
} }
~Dlhandle() {
if (chandle) FreeLibrary(chandle);
}
bool is_valid() const { ~Dlhandle();
return chandle != nullptr;
Dlhandle &operator=(Dlhandle &&o) {
chandle = std::exchange(o.chandle, nullptr);
return *this;
} }
template <typename T> template <typename T>
T* get(const std::string& fname) const { T *get(const std::string &symbol) const {
return reinterpret_cast<T*>(GetProcAddress(chandle, fname.c_str())); return reinterpret_cast<T *>(get_internal(symbol.c_str()));
} }
auto get_fnc(const std::string& fname) const {
return get<void*(...)>(fname); auto get_fnc(const std::string &symbol) const {
return get<void*(...)>(symbol);
} }
private:
void *get_internal(const char *symbol) const;
}; };
#endif
#endif // DLHANDLE_H

View File

@ -1,6 +1,4 @@
#include "llmodel.h" #include "llmodel.h"
#include "dlhandle.h"
#include "sysinfo.h"
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
@ -15,6 +13,9 @@
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "dlhandle.h"
#include "sysinfo.h"
#ifdef _WIN32 #ifdef _WIN32
# define WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN
# ifndef NOMINMAX # ifndef NOMINMAX
@ -27,6 +28,8 @@
# include <intrin.h> # include <intrin.h>
#endif #endif
namespace fs = std::filesystem;
#ifndef __APPLE__ #ifndef __APPLE__
static const std::string DEFAULT_BACKENDS[] = {"kompute", "cpu"}; static const std::string DEFAULT_BACKENDS[] = {"kompute", "cpu"};
#elif defined(__aarch64__) #elif defined(__aarch64__)
@ -129,17 +132,17 @@ const std::vector<LLModel::Implementation> &LLModel::Implementation::implementat
std::string path; std::string path;
// Split the paths string by the delimiter and process each path. // Split the paths string by the delimiter and process each path.
while (std::getline(ss, path, ';')) { while (std::getline(ss, path, ';')) {
std::filesystem::path fs_path(path); std::u8string u8_path(path.begin(), path.end());
// Iterate over all libraries // Iterate over all libraries
for (const auto& f : std::filesystem::directory_iterator(fs_path)) { for (const auto &f : fs::directory_iterator(u8_path)) {
const std::filesystem::path& p = f.path(); const fs::path &p = f.path();
if (p.extension() != LIB_FILE_EXT) continue; if (p.extension() != LIB_FILE_EXT) continue;
if (!std::regex_search(p.stem().string(), re)) continue; if (!std::regex_search(p.stem().string(), re)) continue;
// Add to list if model implementation // Add to list if model implementation
try { try {
Dlhandle dl(p.string()); Dlhandle dl(p);
if (!isImplementation(dl)) if (!isImplementation(dl))
continue; continue;
fres.emplace_back(Implementation(std::move(dl))); fres.emplace_back(Implementation(std::move(dl)));