Packages

Tensor library for Gleam/BEAM with a pure Gleam API, zero-copy views, and optional native acceleration

Retired package: Release invalid

Current section

Files

Jump to
viva_tensor src viva_tensor@pool.erl
Raw

src/viva_tensor@pool.erl

-module(viva_tensor@pool).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/viva_tensor/pool.gleam").
-export([parallel_map/2, similarity_search/3, top_k_similar/3, parallel_sum/1, benchmark_pool/0, main/0]).
-export_type([tensor_op/0, search_result/0, pid_/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" TensorPool - Distributed Tensor Computing via OTP\n"
"\n"
" CONCEITO INOVADOR: GenServer + GPU\n"
" ===================================\n"
"\n"
" Em C/C++: você tem threads + mutex + condition vars (PESADELO)\n"
" Em Gleam/OTP: GenServer gerencia estado, NIFs fazem GPU compute\n"
"\n"
" Arquitetura:\n"
"\n"
" [Request] -> [GenServer Pool] -> [Worker 1] -> [NIF GPU] -> cuBLAS/cuDNN\n"
" -> [Worker 2] -> [NIF GPU] -> cuBLAS/cuDNN\n"
" -> [Worker N] -> [NIF GPU] -> cuBLAS/cuDNN\n"
"\n"
" Cada Worker é um processo BEAM leve (~2KB) que pode chamar NIFs de GPU.\n"
" O GenServer faz load balancing automático.\n"
" Se um Worker crashar, OTP reinicia (fault tolerance grátis!)\n"
).
-type tensor_op() :: {scale, float()} | normalize.
-type search_result() :: {search_result, integer(), float()}.
-type pid_() :: any().
-file("src/viva_tensor/pool.gleam", 265).
-spec apply_op(viva_tensor@tensor:tensor(), tensor_op()) -> viva_tensor@tensor:tensor().
apply_op(T, Op) ->
case Op of
{scale, Factor} ->
viva_tensor@tensor:scale(T, Factor);
normalize ->
viva_tensor@tensor:normalize(T)
end.
-file("src/viva_tensor/pool.gleam", 272).
-spec safe_div(integer(), integer()) -> float().
safe_div(A, B) ->
case B > 0 of
true ->
case erlang:float(B) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> erlang:float(A) / Gleam@denominator
end;
false ->
1.0
end.
-file("src/viva_tensor/pool.gleam", 279).
-spec float_to_string(float()) -> binary().
float_to_string(F) ->
Rounded = erlang:float(erlang:round(F * 100.0)) / 100.0,
gleam_stdlib:float_to_string(Rounded).
-file("src/viva_tensor/pool.gleam", 55).
?DOC(
" Processa uma lista de tensores em paralelo\n"
"\n"
" Cada tensor é processado em um processo BEAM separado.\n"
" Em C/C++ isso precisaria de pthread_create + mutex para cada tensor.\n"
" Em Gleam: uma linha de código e zero data races!\n"
).
-spec parallel_map(list(viva_tensor@tensor:tensor()), tensor_op()) -> list(viva_tensor@tensor:tensor()).
parallel_map(Tensors, Op) ->
Parent = erlang:self(),
Indexed = gleam@list:index_map(Tensors, fun(T, Idx) -> {Idx, T} end),
gleam@list:each(
Indexed,
fun(Pair) ->
{Idx@1, T@1} = Pair,
erlang:spawn(
fun() ->
Result = apply_op(T@1, Op),
viva_tensor_ffi:send_msg(Parent, {Idx@1, Result})
end
)
end
),
_pipe = viva_tensor_ffi:collect_n(erlang:length(Tensors)),
_pipe@1 = gleam@list:sort(
_pipe,
fun(A, B) ->
{Idx_a, _} = A,
{Idx_b, _} = B,
gleam@int:compare(Idx_a, Idx_b)
end
),
gleam@list:map(
_pipe@1,
fun(Pair@1) ->
{_, T@2} = Pair@1,
T@2
end
).
-file("src/viva_tensor/pool.gleam", 85).
?DOC(
" Busca de similaridade em batch - O KILLER FEATURE!\n"
"\n"
" Compara uma query contra milhares de documentos em paralelo.\n"
" Retorna os resultados ordenados por similaridade (desc).\n"
).
-spec similarity_search(
viva_tensor@tensor:tensor(),
list(viva_tensor@tensor:tensor()),
integer()
) -> list(search_result()).
similarity_search(Query, Documents, Chunk_size) ->
Parent = erlang:self(),
Chunks = gleam@list:sized_chunk(Documents, Chunk_size),
Num_chunks = erlang:length(Chunks),
gleam@list:index_map(
Chunks,
fun(Chunk, Chunk_idx) ->
erlang:spawn(
fun() ->
Start_idx = Chunk_idx * Chunk_size,
Results = gleam@list:index_map(
Chunk,
fun(Doc, Local_idx) ->
Similarity = case viva_tensor@tensor:dot(Query, Doc) of
{ok, Sim} ->
Sim;
{error, _} ->
+0.0
end,
{search_result, Start_idx + Local_idx, Similarity}
end
),
viva_tensor_ffi:send_msg(Parent, {Chunk_idx, Results})
end
)
end
),
_pipe = viva_tensor_ffi:collect_n(Num_chunks),
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Pair) ->
{_, Results@1} = Pair,
Results@1
end
),
gleam@list:sort(
_pipe@1,
fun(A, B) ->
gleam@float:compare(erlang:element(3, B), erlang:element(3, A))
end
).
-file("src/viva_tensor/pool.gleam", 123).
?DOC(" Top-K similarity - retorna os K mais similares\n").
-spec top_k_similar(
viva_tensor@tensor:tensor(),
list(viva_tensor@tensor:tensor()),
integer()
) -> list(search_result()).
top_k_similar(Query, Documents, K) ->
_pipe = similarity_search(Query, Documents, 100),
gleam@list:take(_pipe, K).
-file("src/viva_tensor/pool.gleam", 133).
?DOC(" Soma paralela de todos os tensores\n").
-spec parallel_sum(list(viva_tensor@tensor:tensor())) -> float().
parallel_sum(Tensors) ->
Parent = erlang:self(),
Chunks = gleam@list:sized_chunk(Tensors, 100),
gleam@list:each(
Chunks,
fun(Chunk) ->
erlang:spawn(
fun() ->
Partial = gleam@list:fold(
Chunk,
+0.0,
fun(Acc, T) -> Acc + viva_tensor@tensor:sum(T) end
),
viva_tensor_ffi:send_msg(Parent, Partial)
end
)
end
),
_pipe = viva_tensor_ffi:collect_n(erlang:length(Chunks)),
gleam@list:fold(_pipe, +0.0, fun(Acc@1, X) -> Acc@1 + X end).
-file("src/viva_tensor/pool.gleam", 156).
-spec benchmark_pool() -> nil.
benchmark_pool() ->
gleam_stdlib:println(
<<"╔══════════════════════════════════════════════════════════════════╗"/utf8>>
),
gleam_stdlib:println(
<<"║ TensorPool - Distributed Tensor Computing em Pure Gleam! ║"/utf8>>
),
gleam_stdlib:println(
<<"║ Algo que C/C++ NÃO consegue fazer com essa simplicidade! ║"/utf8>>
),
gleam_stdlib:println(
<<"╚══════════════════════════════════════════════════════════════════╝\n"/utf8>>
),
gleam_stdlib:println(<<"CONCEITO: GenServer + GPU"/utf8>>),
gleam_stdlib:println(
<<" - GenServer gerencia estado e load balancing"/utf8>>
),
gleam_stdlib:println(
<<" - Workers são processos BEAM leves (~2KB cada)"/utf8>>
),
gleam_stdlib:println(
<<" - NIFs chamam cuBLAS/cuDNN para GPU compute"/utf8>>
),
gleam_stdlib:println(
<<" - Zero data races, fault tolerant by design!\n"/utf8>>
),
gleam_stdlib:println(
<<"━━━ TEST 1: Parallel Map (Scale 1000 tensors x 512d) ━━━"/utf8>>
),
Tensors = begin
_pipe = gleam@list:range(1, 1000),
gleam@list:map(
_pipe,
fun(_) -> viva_tensor@tensor:random_uniform([512]) end
)
end,
{Seq_time, _} = timer:tc(
fun() ->
gleam@list:map(
Tensors,
fun(T) -> viva_tensor@tensor:scale(T, 2.0) end
)
end
),
{Par_time, _} = timer:tc(fun() -> parallel_map(Tensors, {scale, 2.0}) end),
Speedup1 = safe_div(Seq_time, Par_time),
gleam_stdlib:println(
<<<<" Sequential: "/utf8,
(erlang:integer_to_binary(Seq_time div 1000))/binary>>/binary,
"ms"/utf8>>
),
gleam_stdlib:println(
<<<<" Parallel: "/utf8,
(erlang:integer_to_binary(Par_time div 1000))/binary>>/binary,
"ms"/utf8>>
),
gleam_stdlib:println(
<<<<" Speedup: "/utf8, (float_to_string(Speedup1))/binary>>/binary,
"x"/utf8>>
),
gleam_stdlib:println(
<<"\n━━━ TEST 2: Similarity Search (10K docs x 512d) ━━━"/utf8>>
),
Query = viva_tensor@tensor:random_uniform([512]),
Docs = begin
_pipe@1 = gleam@list:range(1, 10000),
gleam@list:map(
_pipe@1,
fun(_) -> viva_tensor@tensor:random_uniform([512]) end
)
end,
{Search_time, Results} = timer:tc(
fun() -> top_k_similar(Query, Docs, 10) end
),
Throughput = case (erlang:float(Search_time) / 1000000.0) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> 10000.0 / Gleam@denominator
end,
gleam_stdlib:println(
<<<<" 10K docs searched in: "/utf8,
(erlang:integer_to_binary(Search_time div 1000))/binary>>/binary,
"ms"/utf8>>
),
gleam_stdlib:println(
<<<<" Throughput: "/utf8, (float_to_string(Throughput))/binary>>/binary,
" docs/sec"/utf8>>
),
gleam_stdlib:println(<<" Top 3 matches:"/utf8>>),
_pipe@2 = Results,
_pipe@3 = gleam@list:take(_pipe@2, 3),
gleam@list:each(
_pipe@3,
fun(R) ->
gleam_stdlib:println(
<<<<<<" Doc "/utf8,
(erlang:integer_to_binary(erlang:element(2, R)))/binary>>/binary,
": sim="/utf8>>/binary,
(float_to_string(erlang:element(3, R)))/binary>>
)
end
),
gleam_stdlib:println(
<<"\n━━━ TEST 3: Parallel Reduce (Sum 1000 tensors) ━━━"/utf8>>
),
{Reduce_time, Total} = timer:tc(fun() -> parallel_sum(Tensors) end),
gleam_stdlib:println(
<<<<" Time: "/utf8,
(erlang:integer_to_binary(Reduce_time div 1000))/binary>>/binary,
"ms"/utf8>>
),
gleam_stdlib:println(<<" Total: "/utf8, (float_to_string(Total))/binary>>),
gleam_stdlib:println(
<<"\n╔══════════════════════════════════════════════════════════════════╗"/utf8>>
),
gleam_stdlib:println(
<<"║ COMO ISSO RODA NA GPU: ║"/utf8>>
),
gleam_stdlib:println(
<<"║ ║"/utf8>>
),
gleam_stdlib:println(
<<"║ 1. GenServer recebe request de tensor op ║"/utf8>>
),
gleam_stdlib:println(
<<"║ 2. Spawna Worker (processo BEAM ~2KB) ║"/utf8>>
),
gleam_stdlib:println(
<<"║ 3. Worker chama NIF (Rust/C) que acessa GPU ║"/utf8>>
),
gleam_stdlib:println(
<<"║ 4. NIF usa cuBLAS para matmul, cuDNN para conv ║"/utf8>>
),
gleam_stdlib:println(
<<"║ 5. Resultado volta pro Worker, depois pro GenServer ║"/utf8>>
),
gleam_stdlib:println(
<<"║ ║"/utf8>>
),
gleam_stdlib:println(
<<"║ Vantagem: milhares de ops em paralelo, fault tolerant! ║"/utf8>>
),
gleam_stdlib:println(
<<"╚══════════════════════════════════════════════════════════════════╝"/utf8>>
).
-file("src/viva_tensor/pool.gleam", 152).
-spec main() -> nil.
main() ->
benchmark_pool().