[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Re: Python C-library import paths



On 2022-04-02 16:04 +0100, Simon McVittie wrote:
> On Sat, 02 Apr 2022 at 12:55:37 +0100, Wookey wrote:
> > On 2022-04-01 00:30 -0400, M. Zhou wrote:
> > > They have written
> > > their own ffi loader, so I think it is an upstream bug. The upstream
> > > should detect and add multiarch directory to the paths.
> >
> > A correct implemntation really should use the full ldconfig set of search paths.
> 
> I think what they should actually be doing on Linux (at least in distro
> packages) is taking a step back from all these attempts to reproduce
> the system's search path for public shared libraries, and instead doing
> this in https://github.com/apache/tvm/blob/main/python/tvm/_ffi/base.py:
> 
>     ctypes.CDLL('libtvm.so.0')
> 
> which will (ask glibc to) do the correct path search, in something like
> 99% less code.

Aha. That sounds much more the answer to the query in my original mail
'or is there a way to turn on 'just use the system paths' mode?'.

This does indeed work to load the library without a lot of manual
search-path generation, but it's a bit tricky to use in the existing
codebase which wants the loader function to return both a name and a
path, and with this magic loading, I don't know the path.
_LIB, _LIB_NAME = _load_lib()

The second parameter only seems to be used to determine whether libtvm or libtvm_runtime was loaded. I think I can work round that.

OK. This patch appears to basically work:
-- tvm-0.8.0.orig/python/tvm/_ffi/base.py
+++ tvm-0.8.0/python/tvm/_ffi/base.py
@@ -48,15 +48,21 @@ else:

 def _load_lib():
     """Load libary by searching possible path."""
-    lib_path = libinfo.find_lib_path()
     # The dll search path need to be added explicitly in
     # windows after python 3.8
     if sys.platform.startswith("win32") and sys.version_info >= (3, 8):
         for path in libinfo.get_dll_directories():
             os.add_dll_directory(path)
-    lib = ctypes.CDLL(lib_path[0], ctypes.RTLD_GLOBAL)
+
+    use_runtime = os.environ.get("TVM_USE_RUNTIME_LIB", False)
+    if use_runtime:
+        lib = ctypes.CDLL('libtvm_runtime.so.0', ctypes.RTLD_GLOBAL)
+        sys.stderr.write("Loading runtime library %s... exec only\n" % lib._name)
+        sys.stderr.flush()
+    else:
+        lib = ctypes.CDLL('libtvm.so.0', ctypes.RTLD_GLOBAL)
     lib.TVMGetLastError.restype = ctypes.c_char_p
-    return lib, os.path.basename(lib_path[0])
+    return lib

try:
@@ -68,10 +74,10 @@ except ImportError:
 # version number
 __version__ = libinfo.__version__
 # library instance
-_LIB, _LIB_NAME = _load_lib()
+_LIB = _load_lib()

 # Whether we are runtime only
-_RUNTIME_ONLY = "runtime" in _LIB_NAME
+_RUNTIME_ONLY = "runtime" in _LIB._name

Yay!

_Unless_ you ask to use the runtime version - then it blows up.
$ TVM_USE_RUNTIME_LIB=1 tvmc
...
  File "/usr/lib/python3/dist-packages/tvm/auto_scheduler/cost_model/cost_model.py", line 37, in __init__
    self.__init_handle_by_constructor__(_ffi_api.RandomModel)
AttributeError: module 'tvm.auto_scheduler._ffi_api' has no attribute 'RandomModel'

I've not looked into that yet.

Back to the issue of getting the path of the loaded library. Which I no longer obviously _need_, but I would like to know how to do it.

There is ctypes.util.find_library(name) which says it returns a path but
ctypes.util.find_library('tvm')
just returns 
'libtvm.so.0'

I can't see an attribute in the returned lib object to get the path:
lib=ctypes.CDLL('libtvm.so.0')
>>> print(lib)
<CDLL 'libtvm.so.0', handle 1409040 at 0x7f5f0ef48e20>
>> print(lib.__dir__())
['_name', '_FuncPtr', '_handle', '__module__', '__doc__', '_func_flags_', '_func_restype_', '__init__', '__repr__', '__getattr__', '__getitem__', '__dict__', '__weakref__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

But _something_ knows, because if I ask for an incorrect thing it prints it out as an 'AtributeError':
>>> print(lib.wibble)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.9/ctypes/__init__.py", line 387, in __getattr__
    func = self.__getitem__(name)
  File "/usr/lib/python3.9/ctypes/__init__.py", line 392, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /usr/lib/x86_64-linux-gnu/libtvm.so.0: undefined symbol: wibble

Again, my weak python-foo is holding me back here.


Also, you suggest that for a disto package we just need to do
lib = ctypes.CDLL('libtvm.so.0', ctypes.RTLD_GLOBAL) and scrap 99% of the
code.  However, some of the complicated logic in the special _load_lib
code seems like maybe it shouldn't be thrown away.  Like allowing to
override the path with TVM_LIBRARY_PATH envvar, and setting
TVM_USE_RUNTIME_LIB to load libtvm_runtime rather than libtvm (I've
currently preserved the latter but not the former).

I don't know why this is useful, but I assume upstream has reasons.

I guess I should wave my patches upstream and see what they have to say about all this.

> Maybe all this complexity is needed on Windows or in a "relocatable"
> package, but for a distro package it's completely unnecessary and
> sometimes harmful.

Agreed. Although of course upstreams often want to support all sorts
of 'local install' type circumstances so they have the code anyway,
and we end up maintaining a patch to throw it all away and do
something sensible instead.

> > I also don't think it should use the $PATH paths for finding
> > libraries (but maybe upstream have some reason for doing that)
> 
> I suspect the reason is: on Windows, PATH is the equivalent of Linux PATH,
> but it also has a dual role as the equivalent of Linux LD_LIBRARY_PATH.

That would make sense except the relevant code is already conditional by OS:

if sys.platform.startswith("linux") or sys.platform.startswith("freebsd"):
        dll_path.extend(split_env_var("LD_LIBRARY_PATH", ":"))
        dll_path.extend(split_env_var("PATH", ":"))
    elif sys.platform.startswith("darwin"):
        dll_path.extend(split_env_var("DYLD_LIBRARY_PATH", ":"))
        dll_path.extend(split_env_var("PATH", ":"))
    elif sys.platform.startswith("win32"):
        dll_path.extend(split_env_var("PATH", ";"))

So I think it's actually just wrong. I'll take it upstream.

Wookey
-- 
Principal hats:  Debian, Wookware, ARM
http://wookware.org/


Reply to: