개요
octodocs 프로젝트에서 OpenAI API 사용 비용을 절감하기 위해 서버에서 직접 CPU로 임베딩을 수행했던 적이 있습니다.
프로젝트 환경이 node였기 때문에 HuggingFaceTransformersEmbeddings를 사용하여 원격 서버에서 임베딩을 수행했습니다.
당시 임베딩은 docker를 활용한 container 환경에서 실행했는데 alpine 버전을 사용했습니다.
그런데 막상 해당 라이브러리를 활용해서 임베딩을 진행했더니 다음과 같은 오류가 발생했습니다.
Error: Error loading shared library ld-linux-x86-64.so.2: No such file or directory
(needed by /app/node_modules/onnxruntime-node/bin/napi-v3/linux/x64//libonnxruntime.so.1.14.0)
에러 메시지를 분석해보면 libonnxruntime.so.1.14.0는 ld-linux-x86-64.so.2가 필요한데 ld-linux-x86-64.so.2를 찾을 수가 없다는 뜻입니다.
그렇다면 libonnxruntime.so.1.14.0 와 ld-linux-x86-64.so.2가 뭘까요?
libonnxruntime.so.1.14.0
먼저 ONNX란 Open Neural Network Exchange의 약어로 서로 다른 머신 러닝 프레임워크 간 neural network의 변환을 촉구하기 위해 개발된 오픈 소스 프레임워크입니다.
즉, Tensorflow나 PyTorch와 같이 서로 다른 프레임워크에서 개발된 모델들의 호환을 담당하는 공유 플랫폼이라고 볼 수 있습니다.
예를 들어 Tensorflow에서 학습시킨 모델을 ONNX 포맷으로 변환한 뒤 PyTorch에서 사용하거나 ONNX runtime을 통해 실행할 수 있습니다.

그리고 libonnxruntime.so.1.14.0 는 ONNX 형식 모델을 빠르게 실행시키기 위한 라이브러리입니다.
그리고 so 확장자를 통해 shared object 즉, 공유 라이브러리 파일이라는 것을 알 수 있습니다.
ld-linux-x86-64.so.2
ld-linux-x86-64.so.2는 동적 링커로 실행 파일에 공유 라이브러리 종속성이 있는 경우 이 공유 라이브러리를 연결해주는 역할을 담당합니다.
동적 링크와 정적 링크에 대해 잘 모르시는 분들은 아래 포스팅을 읽고 오시는 것을 추천드립니다.
https://growth-coder.tistory.com/344
정적 링크 (static link) vs 동적 링크 (dynamic link)
개요정적 링크와 동적 링크에 대해 이해하기 위해서는 컴파일 과정에 대한 이해가 필요합니다. 아래 링크의 글을 읽고 오는 것을 추천합니다.https://growth-coder.tistory.com/339 [컴파일러] 컴파일, Token
growth-coder.tistory.com
동적 링커는 프로그램을 실행할 때 필요한 공유 라이브러리를 찾고 메모리에 로드하는 역할을 수행하거나 프로세스가 런타임에 공유 라이브러리의 함수를 호출할 수 있도록 어셈블합니다.
즉, 동적 링커는 프로세스를 생성하는 동안 공유 라이브러리의 종속성을 관리하는 중요한 역할을 수행합니다.
그리고 ld-linux-x86-64.so.2는 glibc (GNU 표준 C 라이브러리)를 사용하는 64비트 리눅스 프로그램들이 사용하는 동적 링커입니다.
표준 C 라이브러리 (glibc vs musl)
위에서 ld-linux-x86-64.so.2는 glibc라는 GNU에서 만든 표준 C 라이브러리를 사용한다고 했습니다.
그리고 표준 C 라이브러리에는 glibc 외에 musl이라는 것도 존재합니다.
대부분의 리눅스 배포판에서 외부 라이브러리 패키지를 설치하면 기본적으로 glibc와 동적으로 링크됩니다.
glibc는 동적 링크를 사용하지만 musl의 경우 정적 링크를 사용합니다.
동적 링크 방식으로 바이너리를 빌드하면 외부 라이브러리에 대한 의존성 정보가 필요하지만
정적 링크 방식으로 바이너리를 빌드하면 외부 라이브러리가 바이너리 내부에 포함되어 있기 때문에 쉽고 빠르게 바이너리를 실행할 수 있다는 장점이 있습니다.
또한, musl은 glibc보다 훨씬 작은 크기를 가지고 있습니다.
glibc는 POSIX 표준을 준수하면서 GNU 전용 확장 기능을 포함하지만 musl은 확장 기능 없이 POSIX 표준을 준수하기 때문입니다.
그래서 musl은 주로 경량화를 위한 Alpine 컨테이너 환경에서 자주 사용됩니다.
결론
지금까지 배운 내용을 바탕으로 에러의 원인을 분석해봅시다.
우선 저는 HuggingFaceTransformersEmbeddings를 사용해서 임베딩을 수행했다고 했습니다.
HuggingFaceTransformersEmbeddings는 내부적으로 ONNX runtime을 사용합니다.
다시 에러 메시지를 확인해봅시다.
Error: Error loading shared library ld-linux-x86-64.so.2: No such file or directory
(needed by /app/node_modules/onnxruntime-node/bin/napi-v3/linux/x64//libonnxruntime.so.1.14.0)
- 임베딩을 수행할 때, libonnxruntime.so.1.14.0이라는 공유 라이브러리를 사용합니다.
- ONNX runtime은 glibc 기반으로 컴파일 됩니다.
- 이 공유 라이브러리를 동적으로 linking 하기 위해서 동적 linker인 ld-linux-x86-64.so.2가 필요한데 찾을 수 없습니다.
- ld-linux-x86-64.so.2는 외부 라이브러리와 표준 C 라이브러리인 glibc를 동적 링크 하는데 사용됩니다.
- 저는 경량화를 위해 Alpine 버전의 container를 사용했습니다.
- Alpine 버전은 경량화를 위해 표준 C 라이브러리로 glibc가 아닌 musl을 사용합니다.
- musl과 외부 라이브러리를 링킹 할 때는 ld-musl-...이 사용됩니다.
- 즉, Alpine 버전 container에는 ld-linux-x86-64.so.2이 존재하지 않습니다.
결론은 단순합니다.
그냥 ld-linux-x86-64.so.2이 없어서 찾지 못 한다는 에러 메시지가 나온 겁니다.
해결 방법도 단순합니다. 그냥 표준 C 라이브러리로 glibc를 사용하는 운영 체제를 선택하면 됩니다.
저는 musl을 사용하는 Alpine 버전에서 glibc를 사용하는 slim 버전으로 바꾸어 해결했습니다.
참고
https://www.baeldung.com/linux/dynamic-linker
https://man7.org/linux/man-pages/man8/ld.so.8.html
https://blog.lablup.com/posts/2023/09/20/diet-of-containers/
https://github.com/microsoft/onnxruntime/issues/22539