Current section

Files

Jump to
viva_tensor src viva_tensor_blas.erl
Raw

src/viva_tensor_blas.erl

%% viva_tensor_blas.erl - Intelligent BLAS Backend Selection
%%
%% Auto-detects Intel MKL, OpenBLAS, or Zig SIMD and configures
%% for maximum performance on the current CPU.
-module(viva_tensor_blas).
-export([
detect_backend/0,
get_cpu_topology/0,
configure_threads/1,
set_affinity/1,
auto_configure/0,
matmul/5
]).
%% Detect the best available BLAS backend
detect_backend() ->
case os:type() of
{win32, _} ->
%% Windows: Check for MKL DLLs
case os:getenv("ONEAPI_ROOT") of
false -> check_openblas_win();
_ -> intel_mkl
end;
_ ->
%% Unix: Check library availability
case os:find_executable("ldd") of
false -> zig_simd;
_ -> detect_unix_blas()
end
end.
detect_unix_blas() ->
%% Check if MKL is available
MklPath = "/opt/intel/oneapi/mkl/latest/lib/intel64/libmkl_rt.so",
case filelib:is_file(MklPath) of
true -> intel_mkl;
false ->
%% Check OpenBLAS
case os:find_executable("libopenblas.so") of
false ->
%% Check system OpenBLAS
case filelib:wildcard("/usr/lib/*/libopenblas*.so*") of
[] -> zig_simd;
_ -> open_blas
end;
_ -> open_blas
end
end.
check_openblas_win() ->
case filelib:wildcard("C:/msys64/*/lib/libopenblas*.dll") of
[] -> zig_simd;
_ -> open_blas
end.
%% Get CPU topology (calls into NIF or fallback)
get_cpu_topology() ->
try viva_tensor_zig:cpu_topology() of
Topo when is_map(Topo) -> Topo;
_ -> fallback_topology()
catch
_:_ -> fallback_topology()
end.
fallback_topology() ->
Logical = erlang:system_info(logical_processors),
#{physical_cores => Logical div 2,
logical_cpus => Logical,
l2_cache_kb => 256, l3_cache_kb => 8192,
has_avx2 => true, has_avx512 => false}.
%% Configure threads for BLAS operations
configure_threads(NumThreads) ->
%% Set environment variables for all BLAS implementations
os:putenv("MKL_NUM_THREADS", integer_to_list(NumThreads)),
os:putenv("OMP_NUM_THREADS", integer_to_list(NumThreads)),
os:putenv("OPENBLAS_NUM_THREADS", integer_to_list(NumThreads)),
os:putenv("GOTO_NUM_THREADS", integer_to_list(NumThreads)),
os:putenv("MKL_DYNAMIC", "FALSE"),
{ok, nil}.
%% Set thread affinity mode
set_affinity(Mode) ->
ModeStr = case Mode of
<<"scatter">> -> "scatter,granularity=fine";
<<"compact">> -> "compact,granularity=fine";
_ -> "scatter,granularity=fine"
end,
os:putenv("KMP_AFFINITY", ModeStr),
os:putenv("GOMP_CPU_AFFINITY", "0-23"),
%% MKL threading layer (INTEL is fastest for GEMM)
os:putenv("MKL_THREADING_LAYER", "INTEL"),
{ok, nil}.
%% Auto-configure for maximum performance
auto_configure() ->
Backend = detect_backend(),
Topo = get_cpu_topology(),
Physical = maps:get(physical_cores, Topo, 8),
Logical = maps:get(logical_cpus, Topo, 16),
%% Use all physical cores (includes E-cores on hybrid)
NumThreads = Physical,
configure_threads(NumThreads),
set_affinity(<<"scatter">>),
io:format("~n=== VIVA TENSOR BLAS ===~n"),
io:format("Backend: ~p~n", [Backend]),
io:format("Threads: ~B (of ~B logical)~n", [NumThreads, Logical]),
io:format("Affinity: scatter~n"),
io:format("========================~n~n"),
{ok, Backend}.
%% Smart matmul - uses best available backend
matmul(A, B, M, N, K) ->
case detect_backend() of
intel_mkl ->
%% Use MKL via cblas_dgemm
viva_tensor_zig:nt_matmul_blas(A, B, M, N, K);
open_blas ->
%% Use OpenBLAS via cblas_dgemm
viva_tensor_zig:nt_matmul_blas(A, B, M, N, K);
zig_simd ->
%% Fallback to Zig GEMM
viva_tensor_zig:nt_matmul(A, B, M, N, K)
end.