Jump to content

OpenCL and MPI Termux Troubleshooting

From Haidhi Wiki

Dokumentasi proses menjalankan OpenCL + MPI secara bersamaan di Termux pada perangkat Android yang sudah di-root, lengkap dengan semua error yang ditemui, penyebabnya, dan cara memperbaikinya. Hasil akhir: sebuah program C++ yang membagi vektor lewat MPI ke beberapa proses, lalu tiap proses menghitung bagiannya di GPU Adreno lewat OpenCL.

Lingkungan

Komponen Keterangan
Perangkat Redmi Note 12 4G (rooted)
SoC Qualcomm Snapdragon 685
GPU Adreno 610 (OpenCL 2.0 device, driver melaporkan OpenCL 3.0)
Userland Termux
ICD loader ocl-icd ("OCL Icd free software")
Driver OpenCL /vendor/lib64/libOpenCL.so (milik vendor/Qualcomm)
MPI OpenMPI (paket openmpi Termux)
$PREFIX /data/data/com.termux/files/usr

Paket yang dibutuhkan:

pkg install clang make cmake openmpi opencl-headers ocl-icd clinfo

Ringkasan masalah

# Gejala (error) Penyebab singkat Fix singkat
1 clinfo: Number of platforms = 0 Loader ocl-icd tidak menemukan driver vendor Daftarkan ICD / muat driver vendor langsung
2 clang: cannot locate symbol _ZNSt6__ndk1... LD_LIBRARY_PATH global menarik libc++ Android Jangan set LD_LIBRARY_PATH global; pisah env build vs run
3 use of undeclared identifier 'cl' Header C++ <CL/opencl.hpp> tidak ada; <CL/cl.h> tidak punya namespace cl:: Pakai C API dari C++, atau pasang opencl-clhpp
4 mpirun ... signal 31 (Bad system call) seccomp Android memblokir syscall binding hwloc saat MPI_Init --mca hwloc_base_binding_policy none
5 Program C++: No OpenCL platform Driver Adreno bukan ICD-compliant → loader gagal enumerate Muat driver vendor langsung (vendor-first) + LD_PRELOAD libc++ Termux

Masalah 1: clinfo melaporkan 0 platform

Gejala

Number of platforms                               0
  ICD loader Name                                 OpenCL ICD Loader
  ICD loader Vendor                               OCL Icd free software

Loader-nya kebaca, tapi tidak ada platform OpenCL sama sekali.

Penyebab

Loader ocl-icd terpasang, tetapi tidak ada driver vendor yang terdaftar. Loader mencari berkas *.icd di $PREFIX/etc/OpenCL/vendors/; kalau kosong, hasilnya 0 platform.

Catatan penting: driver Adreno /vendor/lib64/libOpenCL.so ternyata bukan driver ICD yang patuh (tidak meng-export clIcdGetPlatformIDsKHR). Jadi mendaftarkannya ke loader saja tidak selalu cukup; lihat Masalah 5 untuk gambaran lengkapnya. Cara paling andal adalah memuat driver vendor langsung.

Solusi

1. Cari lokasi driver:

ls -l /vendor/lib64/libOpenCL.so /system/vendor/lib64/libOpenCL.so 2>/dev/null
find /vendor /system -iname 'libOpenCL*.so' 2>/dev/null

2. (Opsional, untuk loader) daftarkan ICD:

mkdir -p $PREFIX/etc/OpenCL/vendors
echo /vendor/lib64/libOpenCL.so > $PREFIX/etc/OpenCL/vendors/adreno.icd

3. Saat menjalankan, arahkan loader ke library vendor:

export LD_LIBRARY_PATH=/vendor/lib64:/system/vendor/lib64:/system/lib64
clinfo

Dengan LD_LIBRARY_PATH vendor-first, libOpenCL.so yang dimuat adalah library vendor (driver Adreno) langsung, dan clinfo akan melihat platform "QUALCOMM Snapdragon" / "Adreno (TM) 610".

PERINGATAN: meng-export LD_LIBRARY_PATH secara global (mis. di ~/.bashrc) akan menimbulkan Masalah 2. Set hanya saat dibutuhkan.

Masalah 2: clang rusak (cannot locate symbol __ndk1)

Gejala

Setelah meng-export LD_LIBRARY_PATH untuk OpenCL, clang/clang++ berhenti bekerja:

CANNOT LINK EXECUTABLE "clang": cannot locate symbol
"_ZNSt6__ndk122__libcpp_verbose_abortEPKcz" referenced by ...

Simbol itu kalau di-demangle: std::__ndk1::__libcpp_verbose_abort(char const*, ...).

Penyebab

LD_LIBRARY_PATH=/vendor/lib64:/system/lib64 yang di-set global membuat Termux memuat libc++ milik Android dari /system/lib64 alih-alih libc++ milik Termux. Keduanya memakai inline namespace yang berbeda:

  • libc++ Termux → std::__ndk1
  • libc++ Android → std::__1

clang Termux dikompilasi terhadap __ndk1, sehingga ketika libc++ Android (__1) yang termuat, simbol __ndk1 hilang dan clang gagal jalan.

Solusi

Konfirmasi dulu:

unset LD_LIBRARY_PATH
clang --version    # kalau jalan lagi, terbukti LD_LIBRARY_PATH penyebabnya

Prinsipnya: pisahkan environment untuk COMPILE dan RUN.

  • Saat compile/cmake/make → LD_LIBRARY_PATH harus kosong (biar libc++ Termux yang dipakai). Caranya pakai env -u:
env -u LD_LIBRARY_PATH clang -lOpenCL main.c -o app
env -u LD_LIBRARY_PATH cmake --build build
  • Saat run → baru set LD_LIBRARY_PATH ke library vendor, sebagai prefix satu perintah agar otomatis hilang setelahnya:
LD_LIBRARY_PATH=/vendor/lib64:/system/vendor/lib64:/system/lib64 ./app

Jangan menaruh export LD_LIBRARY_PATH=... di ~/.bashrc. Untuk compile, driver vendor tidak dibutuhkan; -lOpenCL hanya butuh loader ocl-icd di $PREFIX/lib; library vendor hanya perlu saat runtime.

Masalah 3: header C++ OpenCL tidak ada (use of undeclared identifier 'cl')

Gejala

Saat memakai OpenCL C++ bindings, <CL/opencl.hpp> tidak ditemukan. Lalu setelah diganti ke <CL/cl.h>, muncul:

error: use of undeclared identifier 'cl'
static cl::Device pick_gpu(...) { ... }
       ^
error: missing 'template' keyword prior to dependent template name 'getInfo'

Penyebab

Dua hal berbeda:

  • <CL/cl.h> adalah header C; isinya fungsi clGetPlatformIDs, clCreateContext, dst. Header ini tidak punya namespace cl:: sama sekali. Maka semua cl::Platform, cl::Device, dsb. menjadi "undeclared".
  • Class cl::* hanya ada di <CL/opencl.hpp> (OpenCL C++ bindings), yang merupakan header terpisah (paket opencl-clhpp) dan bukan bagian dari opencl-headers. Header ini sering belum terpasang di Termux.

Solusi

Dua pilihan:

A. Pakai OpenCL C API dari C++ (paling andal; hanya butuh <CL/cl.h> yang sudah ada). Tetap C++ (pakai std::vector, std::string, iostream), tapi panggil fungsi C:

#define CL_TARGET_OPENCL_VERSION 300
#include <CL/cl.h>
// cl_context ctx = clCreateContext(...); dst.

B. Sediakan header C++ bindings kalau memang ingin gaya cl:::

pkg install opencl-clhpp        # kalau tersedia di repo
# atau ambil manual (header tunggal, hanya butuh cl.h):
mkdir -p CL
curl -L -o CL/opencl.hpp \
  https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/opencl.hpp

lalu tambahkan target_include_directories(... PRIVATE ${CMAKE_SOURCE_DIR}) di CMake.

Masalah 4: mpirun signal 31 (Bad system call / SIGSYS)

Gejala

mpirun: Forwarding signal 31 to job
mpirun noticed that process rank 3 with PID 0 on node localhost
exited on signal 31 (Bad system call).

Tidak ada output program sama sekali sebelum crash (mati di dalam MPI_Init).

Penyebab

Signal 31 = SIGSYS = "Bad system call". Kernel Android memasang filter seccomp yang memblokir syscall tertentu; saat proses memanggil syscall yang diblokir, proses langsung dibunuh dengan sinyal 31.

Pemicunya: OpenMPI memakai hwloc untuk mengikat (binding) proses ke core CPU saat MPI_Init, dan binding itu memanggil syscall yang diblokir seccomp Android. Karena ini terjadi saat init (sebelum komunikasi), mengganti transport tidak menolong.

Yang TIDAK menyelesaikan (sudah dicoba): --mca btl self,tcp dan --mca btl_vader_single_copy_mechanism none ; keduanya tentang transport, padahal masalahnya di binding.

Solusi

Matikan binding hwloc:

mpirun -np 4 --oversubscribe \
  --mca hwloc_base_binding_policy none \
  --mca btl tcp,self \
  ./app
  • --mca hwloc_base_binding_policy none → kunci-nya; matikan binding yang memicu SIGSYS.
  • --mca btl tcp,self → komunikasi lewat TCP loopback (untuk demo ini datanya kecil, jadi tidak masalah).
  • --oversubscribe → izinkan proses lebih banyak daripada slot terdeteksi.

Verifikasi cepat dengan program "hello world" MPI minimal sebelum menggabungkan dengan OpenCL.

Masalah 5: "No OpenCL platform" saat program C++ dijalankan

Gejala

MPI sudah jalan (banner tampil, 4 rank start), tapi tiap rank gagal di OpenCL:

[rank 0] No OpenCL platform
[rank 1] No OpenCL platform
...

Anehnya:

  • clinfo polos (env shell vendor-first) → menemukan Adreno.
  • clinfo dengan $PREFIX/lib di depan → 0 platform.

Penyebab

Ini inti persoalannya. Demi libc++ (Masalah 2), program C++ menaruh $PREFIX/lib paling depan di LD_LIBRARY_PATH. Akibatnya libOpenCL.so yang termuat adalah loader ocl-icd (di $PREFIX/lib), bukan driver vendor.

Masalahnya: driver Adreno /vendor/lib64/libOpenCL.so bukan driver ICD yang patuh; ia adalah implementasi OpenCL lengkap yang berdiri sendiri, tetapi tidak meng-export entry point ICD (clIcdGetPlatformIDsKHR) yang dipakai loader untuk meng-enumerasi platform. Maka:

  • Lewat loader (saat $PREFIX/lib di depan) → loader bisa memuat driver tapi tidak bisa enumerate → 0 platform.
  • Dimuat langsung (saat /vendor/lib64 di depan) → berhasil, karena fungsi clGetPlatformIDs dipanggil langsung ke driver vendor.

Jadi driver model ini harus dimuat langsung, bukan lewat loader. Tetapi itu berbenturan dengan kebutuhan libc++ Termux yang menuntut $PREFIX/lib di depan.

Solusi

Muat driver vendor langsung (vendor-first) dan paksa libc++ Termux lewat LD_PRELOAD:

LD_LIBRARY_PATH=/vendor/lib64:/system/vendor/lib64:/system/lib64:$PREFIX/lib \
LD_PRELOAD=$PREFIX/lib/libc++_shared.so \
  mpirun --oversubscribe --mca hwloc_base_binding_policy none --mca btl tcp,self \
  -np 4 ./app

Cara kerjanya:

  • /vendor/lib64 di depan → libOpenCL.so menyangkut ke driver Adreno langsung → platform ketemu.
  • $PREFIX/lib di paling belakang → cadangan untuk library Termux lain (mis. libmpi.so), yang juga ditemukan lewat RUNPATH binary.
  • LD_PRELOAD=$PREFIX/lib/libc++_shared.so → memaksa libc++ Termux (__ndk1) dimuat lebih dulu, sehingga simbol C++ program ter-resolve dengan benar (mencegah crash __ndk1).

Catatan: cara ini bekerja selama driver vendor memakai libc++.so (platform, soname berbeda) dan/atau tidak meng-link libc++_shared.so secara dinamis. Untuk mengecek dependensi driver:

readelf -d /vendor/lib64/libOpenCL.so | grep NEEDED

Jika di sana muncul libc++_shared.so (akan bentrok dengan preload), alternatifnya adalah static-link libc++ ke binary (tambah -static-libstdc++ di kompilasi) sehingga binary tidak butuh libc++ shared sama sekali, lalu LD_PRELOAD bisa dilepas.

Konfigurasi runtime final

Ringkasan resep yang terbukti bekerja end-to-end:

Build (libc++ Termux, LD_LIBRARY_PATH dibuang):

env -u LD_LIBRARY_PATH cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
env -u LD_LIBRARY_PATH cmake --build build -j

Cek GPU (driver vendor langsung):

LD_LIBRARY_PATH=/vendor/lib64:/system/vendor/lib64:/system/lib64:$PREFIX/lib \
  clinfo | head -n 25

Run (driver vendor langsung + libc++ Termux + binding hwloc dimatikan):

LD_LIBRARY_PATH=/vendor/lib64:/system/vendor/lib64:/system/lib64:$PREFIX/lib \
LD_PRELOAD=$PREFIX/lib/libc++_shared.so \
TMPDIR=$PREFIX/tmp \
  mpirun --oversubscribe --mca hwloc_base_binding_policy none --mca btl tcp,self \
  -np 4 ./build/app <arg...>

Tiga prinsip kunci:

  1. Build pakai env bersih (env -u LD_LIBRARY_PATH), run pakai env vendor; jangan dicampur, jangan di-global.
  2. OpenCL: muat driver vendor langsung (vendor-first), karena driver Adreno bukan ICD-compliant.
  3. C++ + vendor-first: pakai LD_PRELOAD libc++ Termux supaya tidak bentrok namespace.

Catatan

  • "Max clock 1 MHz" dan "1 compute unit" yang dilaporkan device bukan error/bug kode; driver Adreno memang sering melaporkan CL_DEVICE_MAX_CLOCK_FREQUENCY dengan nilai placeholder, dan Adreno meng-expose 1 compute unit ke OpenCL (normal untuk arsitekturnya). Kernel tetap berjalan paralel di banyak ALU di dalamnya.
  • Paralelisme: dengan hanya 1 GPU, menjalankan banyak rank MPI tidak mempercepat sisi GPU ; rangkaian kernel dari tiap rank di-serialize oleh driver pada GPU yang sama. Sisi CPU (generate data, verifikasi, reduksi) tetap paralel. Paralelisme GPU nyata baru tercapai bila ada beberapa GPU (mis. cluster beberapa perangkat Android via MPI/TCP), satu rank per GPU.
  • $PREFIX Termux = /data/data/com.termux/files/usr. Variabel $PREFIX sudah otomatis ter-set di Termux.

Lihat pula