mirror of
https://github.com/ollama/ollama.git
synced 2026-11-08 02:16:57 -06:00
NVIDIA's libcusolver looks for its dependencies only in its own directory, so libcublas and libnvJitLink resolved to the host's copies and MLX failed to load when those didn't match the bundled ones. Preload the bundled copies by full path before loading MLX.
109 lines
4.1 KiB
Go
109 lines
4.1 KiB
Go
package mlx
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
// buildSharedLibrary compiles a one-function shared library to out with the
|
|
// given soname, linked against needs (file names found in linkDirs).
|
|
func buildSharedLibrary(t *testing.T, cc, out, soname, fn string, linkDirs, needs []string) {
|
|
t.Helper()
|
|
src := filepath.Join(t.TempDir(), "lib.c")
|
|
if err := os.WriteFile(src, []byte("int "+fn+"(void) { return 1; }\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
args := []string{"-shared", "-fPIC", "-o", out, src, "-Wl,--no-as-needed"}
|
|
if soname != "" {
|
|
args = append(args, "-Wl,-soname,"+soname)
|
|
}
|
|
for _, linkDir := range linkDirs {
|
|
args = append(args, "-L"+linkDir)
|
|
}
|
|
for _, need := range needs {
|
|
if filepath.Base(need) != need {
|
|
// Linking a library without a soname by path records that path.
|
|
args = append(args, need)
|
|
continue
|
|
}
|
|
args = append(args, "-l:"+need)
|
|
}
|
|
cmd := exec.CommandContext(t.Context(), cc, args...)
|
|
cmd.Dir = filepath.Dir(out)
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("cc %v: %v\n%s", args, err, output)
|
|
}
|
|
}
|
|
|
|
func TestSharedDependenciesListsBundledLibrariesDependenciesFirst(t *testing.T) {
|
|
cc, err := exec.LookPath("cc")
|
|
if err != nil {
|
|
t.Skip("no C compiler to build test libraries")
|
|
}
|
|
root := t.TempDir()
|
|
mlxDir := filepath.Join(root, "mlx_cuda_v13")
|
|
depDir := filepath.Join(root, "cuda_v13")
|
|
for _, dir := range []string{mlxDir, depDir} {
|
|
if err := os.Mkdir(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
lib := func(dir, name string) string { return filepath.Join(dir, name) }
|
|
|
|
// The layout that broke: the MLX library needs a versioned library beside
|
|
// it that needs one only the dependency directory has, which needs another
|
|
// there. Libraries nothing in that chain needs stay unloaded.
|
|
buildSharedLibrary(t, cc, lib(depDir, "libinner.so.1"), "libinner.so.1", "inner", nil, nil)
|
|
buildSharedLibrary(t, cc, lib(depDir, "libouter.so.1"), "libouter.so.1", "outer", []string{depDir}, []string{"libinner.so.1"})
|
|
buildSharedLibrary(t, cc, lib(depDir, "libunused.so.1"), "libunused.so.1", "unused", nil, nil)
|
|
buildSharedLibrary(t, cc, lib(mlxDir, "libother.so.1"), "libother.so.1", "other", []string{depDir}, []string{"libunused.so.1"})
|
|
buildSharedLibrary(t, cc, lib(mlxDir, "libsolver.so.1.2"), "libsolver.so.1", "solver", []string{depDir}, []string{"libouter.so.1"})
|
|
if err := os.Symlink("libsolver.so.1.2", lib(mlxDir, "libsolver.so.1")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
buildSharedLibrary(t, cc, lib(mlxDir, "libmlxc.so"), "libmlxc.so", "mlxc", []string{mlxDir}, []string{"libsolver.so.1"})
|
|
|
|
got := sharedDependencies(lib(mlxDir, "libmlxc.so"), depDir)
|
|
want := []string{lib(depDir, "libinner.so.1"), lib(depDir, "libouter.so.1")}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("sharedDependencies() = %#v, want %#v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSharedDependenciesSkipsNamesWithDirectories(t *testing.T) {
|
|
cc, err := exec.LookPath("cc")
|
|
if err != nil {
|
|
t.Skip("no C compiler to build test libraries")
|
|
}
|
|
root := t.TempDir()
|
|
mlxDir := filepath.Join(root, "mlx_cuda_v13")
|
|
depDir := filepath.Join(root, "cuda_v13")
|
|
for _, dir := range []string{mlxDir, filepath.Join(depDir, "sub")} {
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// A library with no soname, linked by a relative path, is recorded as
|
|
// needed under that path; joined to depDir it would name a file there.
|
|
buildSharedLibrary(t, cc, filepath.Join(depDir, "sub", "libpath.so"), "", "path", nil, nil)
|
|
if err := os.Symlink(filepath.Join(depDir, "sub"), filepath.Join(mlxDir, "sub")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
buildSharedLibrary(t, cc, filepath.Join(mlxDir, "libmlxc.so"), "libmlxc.so", "mlxc", nil, []string{"sub/libpath.so"})
|
|
if needed := neededLibraries(filepath.Join(mlxDir, "libmlxc.so")); !slices.Contains(needed, "sub/libpath.so") {
|
|
t.Fatalf("libmlxc.so needs %q; want it to need sub/libpath.so", needed)
|
|
}
|
|
if err := os.Remove(filepath.Join(mlxDir, "sub")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if got := sharedDependencies(filepath.Join(mlxDir, "libmlxc.so"), depDir); got != nil {
|
|
t.Fatalf("sharedDependencies() = %#v, want nil", got)
|
|
}
|
|
}
|