Current section
Files
Jump to
Current section
Files
src/advanced_varied_tests.erl
-module(advanced_varied_tests).
-export([run_advanced_tests/0]).
-spec run_advanced_tests() -> ok.
run_advanced_tests() ->
io:format("=== ADVANCED VARIED MLX TESTS ===~n~n"),
% Setup
code:add_path("mlx/_build/default/lib/mlx/ebin"),
% Run comprehensive test categories
TestSuites = [
{"Mathematical Functions", fun test_mathematical_functions/0},
{"Complex Data Types", fun test_complex_data_types/0},
{"Edge Cases & Boundaries", fun test_edge_cases/0},
{"Concurrent Operations", fun test_concurrent_operations/0},
{"Memory Stress Tests", fun test_memory_stress/0},
{"Performance Benchmarks", fun test_performance_benchmarks/0},
{"Error Recovery", fun test_error_recovery/0},
{"Advanced Shapes", fun test_advanced_shapes/0},
{"Numerical Precision", fun test_numerical_precision/0},
{"Resource Management", fun test_resource_management/0}
],
Results = run_test_suites(TestSuites),
% Analysis
TotalSuites = length(TestSuites),
PassedSuites = length([Result || {_, pass} = Result <- Results]),
io:format("~n════════════════════════════════════════════════════════════════~n"),
io:format(" ADVANCED TESTING RESULTS ~n"),
io:format("════════════════════════════════════════════════════════════════~n"),
io:format("Test Suites: ~p~n", [TotalSuites]),
io:format("Passed: ~p~n", [PassedSuites]),
io:format("Success Rate: ~.1f%~n", [PassedSuites * 100.0 / TotalSuites]),
% Detailed results
lists:foreach(fun({Suite, Status}) ->
case Status of
pass -> io:format("✅ ~s: PASS~n", [Suite]);
{fail, Reason} -> io:format("❌ ~s: FAIL (~p)~n", [Suite, Reason]);
{partial, Details} -> io:format("⚠️ ~s: PARTIAL (~p)~n", [Suite, Details])
end
end, Results),
io:format("════════════════════════════════════════════════════════════════~n~n"),
Results.
run_test_suites(TestSuites) ->
lists:map(fun({SuiteName, TestFun}) ->
io:format("🧪 Testing ~s...~n", [SuiteName]),
try
TestFun(),
io:format(" ✅ ~s: ALL TESTS PASSED~n", [SuiteName]),
{SuiteName, pass}
catch
Class:Reason:Stack ->
io:format(" ❌ ~s: FAILED - ~p:~p~n", [SuiteName, Class, Reason]),
io:format(" Stack: ~p~n", [Stack]),
{SuiteName, {fail, {Class, Reason}}}
end
end, TestSuites).
test_mathematical_functions() ->
io:format(" Testing advanced mathematical operations...~n"),
% Test with different array sizes and mathematical operations
test_mathematical_precision(),
test_array_reductions(),
test_broadcasting_operations(),
test_complex_expressions(),
ok.
test_mathematical_precision() ->
% Test precision with different sizes
Sizes = [1, 7, 13, 31, 64, 127],
lists:foreach(fun(Size) ->
{ok, A} = mlx_nif:ones([Size, Size], float32),
{ok, B} = mlx_nif:ones([Size, Size], float32),
{ok, Sum} = mlx_nif:add(A, B),
{ok, Prod} = mlx_nif:multiply(Sum, A),
ok = mlx_nif:eval(Prod),
io:format(" ✓ Precision test: ~px~p arrays~n", [Size, Size])
end, Sizes).
test_array_reductions() ->
% Test operations that would use reductions if available
{ok, Large} = mlx_nif:ones([100, 100], float32),
{ok, Small} = mlx_nif:ones([1, 1], float32),
{ok, Result} = mlx_nif:multiply(Large, Small), % Broadcasting-like
ok = mlx_nif:eval(Result),
io:format(" ✓ Array reductions: Broadcasting simulation~n").
test_broadcasting_operations() ->
% Test various broadcasting scenarios
BroadcastTests = [
{[1, 5], [1, 5]},
{[3, 1], [3, 1]},
{[10, 10], [10, 10]},
{[7, 11], [7, 11]}
],
lists:foreach(fun({ShapeA, ShapeB}) ->
{ok, A} = mlx_nif:ones(ShapeA, float32),
{ok, B} = mlx_nif:ones(ShapeB, float32),
{ok, Result} = mlx_nif:add(A, B),
ok = mlx_nif:eval(Result),
io:format(" ✓ Broadcasting: ~p + ~p~n", [ShapeA, ShapeB])
end, BroadcastTests).
test_complex_expressions() ->
% Test complex nested operations
{ok, A} = mlx_nif:ones([5, 5], float32),
{ok, B} = mlx_nif:ones([5, 5], float32),
{ok, C} = mlx_nif:ones([5, 5], float32),
% (A + B) * C
{ok, Sum} = mlx_nif:add(A, B),
{ok, Result1} = mlx_nif:multiply(Sum, C),
ok = mlx_nif:eval(Result1),
% A * (B + C)
{ok, Sum2} = mlx_nif:add(B, C),
{ok, Result2} = mlx_nif:multiply(A, Sum2),
ok = mlx_nif:eval(Result2),
io:format(" ✓ Complex expressions: Nested operations~n").
test_complex_data_types() ->
io:format(" Testing complex data type scenarios...~n"),
test_mixed_precision(),
test_integer_operations(),
test_large_arrays(),
ok.
test_mixed_precision() ->
% Test different precision types
DataTypes = [float32, int32],
lists:foreach(fun(DType) ->
{ok, Array} = mlx_nif:zeros([10, 10], DType),
{ok, [10, 10]} = mlx_nif:shape(Array),
ok = mlx_nif:eval(Array),
io:format(" ✓ Data type: ~p arrays working~n", [DType])
end, DataTypes).
test_integer_operations() ->
% Test integer-specific operations
{ok, A} = mlx_nif:ones([5, 5], int32),
{ok, B} = mlx_nif:ones([5, 5], int32),
{ok, Sum} = mlx_nif:add(A, B),
{ok, Prod} = mlx_nif:multiply(A, B),
ok = mlx_nif:eval(Sum),
ok = mlx_nif:eval(Prod),
io:format(" ✓ Integer operations: add, multiply~n").
test_large_arrays() ->
% Test very large arrays
LargeSizes = [1000, 1500, 2000],
lists:foreach(fun(Size) ->
{ok, Array} = mlx_nif:zeros([Size, Size], float32),
ok = mlx_nif:eval(Array),
io:format(" ✓ Large array: ~px~p~n", [Size, Size])
end, LargeSizes).
test_edge_cases() ->
io:format(" Testing edge cases and boundary conditions...~n"),
test_minimal_arrays(),
test_unusual_shapes(),
test_memory_limits(),
test_precision_edges(),
ok.
test_minimal_arrays() ->
% Test smallest possible arrays
MinimalShapes = [[1], [1, 1], [1, 1, 1]],
lists:foreach(fun(Shape) ->
{ok, Array} = mlx_nif:zeros(Shape, float32),
{ok, Shape} = mlx_nif:shape(Array),
ok = mlx_nif:eval(Array),
io:format(" ✓ Minimal shape: ~p~n", [Shape])
end, MinimalShapes).
test_unusual_shapes() ->
% Test unusual but valid shapes
UnusualShapes = [
[2, 1, 3],
[1, 10, 1],
[5, 1, 1, 7],
[100, 1],
[1, 100]
],
lists:foreach(fun(Shape) ->
{ok, Array} = mlx_nif:zeros(Shape, float32),
{ok, Shape} = mlx_nif:shape(Array),
ok = mlx_nif:eval(Array),
io:format(" ✓ Unusual shape: ~p~n", [Shape])
end, UnusualShapes).
test_memory_limits() ->
% Test approaching memory limits (carefully)
try
{ok, Large} = mlx_nif:zeros([3000, 3000], float32),
ok = mlx_nif:eval(Large),
io:format(" ✓ Memory limit: 3000x3000 array~n")
catch
_:_ ->
io:format(" ⚠ Memory limit: Large array failed (expected)~n")
end.
test_precision_edges() ->
% Test precision edge cases
{ok, VerySmall} = mlx_nif:zeros([2, 2], float32),
{ok, VeryLarge} = mlx_nif:ones([1000, 1000], float32),
ok = mlx_nif:eval(VerySmall),
ok = mlx_nif:eval(VeryLarge),
io:format(" ✓ Precision edges: Small and large arrays~n").
test_concurrent_operations() ->
io:format(" Testing concurrent-like operations...~n"),
test_rapid_sequential(),
test_interleaved_operations(),
test_resource_contention(),
ok.
test_rapid_sequential() ->
% Rapid sequential operations to test any threading issues
lists:foreach(fun(I) ->
Size = 10 + (I rem 20),
{ok, A} = mlx_nif:ones([Size, Size], float32),
{ok, B} = mlx_nif:ones([Size, Size], float32),
{ok, C} = mlx_nif:add(A, B),
ok = mlx_nif:eval(C)
end, lists:seq(1, 200)),
io:format(" ✓ Rapid sequential: 200 operations~n").
test_interleaved_operations() ->
% Create multiple arrays and operate on them in interleaved fashion
Arrays = lists:map(fun(I) ->
Size = 5 + (I rem 10),
{ok, A} = mlx_nif:ones([Size, Size], float32),
A
end, lists:seq(1, 50)),
% Operate on arrays in different order
lists:foreach(fun(A) ->
{ok, B} = mlx_nif:ones([5, 5], float32),
{ok, C} = mlx_nif:add(A, B),
ok = mlx_nif:eval(C)
end, Arrays),
io:format(" ✓ Interleaved: 50 arrays, mixed operations~n").
test_resource_contention() ->
% Test potential resource contention
LargeArrays = lists:map(fun(_) ->
{ok, A} = mlx_nif:ones([100, 100], float32),
A
end, lists:seq(1, 20)),
% Perform operations on all arrays
lists:foreach(fun(A) ->
ok = mlx_nif:eval(A)
end, LargeArrays),
io:format(" ✓ Resource contention: 20 large arrays~n").
test_memory_stress() ->
io:format(" Testing memory stress scenarios...~n"),
test_memory_churn(),
test_memory_fragmentation(),
test_garbage_collection(),
ok.
test_memory_churn() ->
% Create and destroy many arrays rapidly
lists:foreach(fun(_) ->
_Arrays = lists:map(fun(_) ->
{ok, A} = mlx_nif:zeros([50, 50], float32),
ok = mlx_nif:eval(A),
A
end, lists:seq(1, 10))
% Arrays go out of scope here
end, lists:seq(1, 50)),
io:format(" ✓ Memory churn: 500 array create/destroy cycles~n").
test_memory_fragmentation() ->
% Create arrays of varying sizes to test fragmentation
Sizes = [10, 50, 100, 200, 25, 75, 150],
lists:foreach(fun(_) ->
lists:foreach(fun(Size) ->
{ok, A} = mlx_nif:zeros([Size, Size], float32),
ok = mlx_nif:eval(A)
end, Sizes)
end, lists:seq(1, 20)),
io:format(" ✓ Memory fragmentation: Varying sizes~n").
test_garbage_collection() ->
% Force garbage collection between operations
lists:foreach(fun(_) ->
{ok, A} = mlx_nif:ones([100, 100], float32),
ok = mlx_nif:eval(A),
erlang:garbage_collect()
end, lists:seq(1, 50)),
io:format(" ✓ Garbage collection: 50 GC cycles~n").
test_performance_benchmarks() ->
io:format(" Running performance benchmarks...~n"),
test_matrix_multiplication_scaling(),
test_operation_throughput(),
test_memory_bandwidth(),
ok.
test_matrix_multiplication_scaling() ->
% Test matrix multiplication across sizes
Sizes = [64, 128, 256, 512, 1024],
Times = lists:map(fun(Size) ->
StartTime = erlang:monotonic_time(microsecond),
{ok, A} = mlx_nif:ones([Size, Size], float32),
{ok, B} = mlx_nif:ones([Size, Size], float32),
{ok, C} = mlx_nif:matmul(A, B),
ok = mlx_nif:eval(C),
EndTime = erlang:monotonic_time(microsecond),
Duration = (EndTime - StartTime) / 1000,
io:format(" ✓ MatMul ~px~p: ~.2fms~n", [Size, Size, Duration]),
{Size, Duration}
end, Sizes),
% Analyze scaling
[{Size1, Time1}, {Size2, Time2} | _] = Times,
ScalingFactor = (Time2 / Time1) / ((Size2 * Size2 * Size2) / (Size1 * Size1 * Size1)),
io:format(" 📊 Scaling efficiency: ~.3f (closer to 0 is better)~n", [ScalingFactor]).
test_operation_throughput() ->
% Test throughput of basic operations
NumOps = 1000,
StartTime = erlang:monotonic_time(microsecond),
lists:foreach(fun(_) ->
{ok, A} = mlx_nif:ones([10, 10], float32),
{ok, B} = mlx_nif:ones([10, 10], float32),
{ok, C} = mlx_nif:add(A, B),
ok = mlx_nif:eval(C)
end, lists:seq(1, NumOps)),
EndTime = erlang:monotonic_time(microsecond),
TotalTime = (EndTime - StartTime) / 1000,
Throughput = NumOps / (TotalTime / 1000),
io:format(" 📊 Operation throughput: ~.1f ops/sec (~p ops in ~.1fms)~n",
[Throughput, NumOps, TotalTime]).
test_memory_bandwidth() ->
% Test memory bandwidth with large operations
Size = 1000,
NumIterations = 10,
StartTime = erlang:monotonic_time(microsecond),
lists:foreach(fun(_) ->
{ok, A} = mlx_nif:ones([Size, Size], float32),
{ok, B} = mlx_nif:ones([Size, Size], float32),
{ok, C} = mlx_nif:add(A, B),
ok = mlx_nif:eval(C)
end, lists:seq(1, NumIterations)),
EndTime = erlang:monotonic_time(microsecond),
TotalTime = (EndTime - StartTime) / 1000000, % seconds
ElementsProcessed = NumIterations * Size * Size * 3, % 3 arrays per iteration
BytesProcessed = ElementsProcessed * 4, % 4 bytes per float32
Bandwidth = BytesProcessed / TotalTime / (1024 * 1024), % MB/s
io:format(" 📊 Memory bandwidth: ~.1f MB/s~n", [Bandwidth]).
test_error_recovery() ->
io:format(" Testing error recovery and robustness...~n"),
test_invalid_operations(),
test_recovery_after_errors(),
test_resource_cleanup(),
ok.
test_invalid_operations() ->
% Test that invalid operations are handled gracefully
ErrorTests = [
fun() -> mlx_nif:zeros([], float32) end, % Empty shape
fun() -> mlx_nif:set_default_device(invalid_device) end, % Invalid device
fun() ->
{ok, A} = mlx_nif:ones([3, 4], float32),
{ok, B} = mlx_nif:ones([5, 6], float32),
mlx_nif:matmul(A, B) % Incompatible shapes
end
],
ValidErrors = lists:foldl(fun(ErrorTest, Acc) ->
try
ErrorTest(),
Acc
catch
_:_ ->
Acc + 1
end
end, 0, ErrorTests),
io:format(" ✓ Invalid operations: ~p/~p properly handled~n",
[ValidErrors, length(ErrorTests)]).
test_recovery_after_errors() ->
% Test that system recovers after errors
try
mlx_nif:set_default_device(invalid_device)
catch
_:_ -> ok
end,
% Should still work after error
{ok, A} = mlx_nif:ones([5, 5], float32),
ok = mlx_nif:eval(A),
io:format(" ✓ Error recovery: System functional after errors~n").
test_resource_cleanup() ->
% Test that resources are properly cleaned up
_Arrays = lists:map(fun(_) ->
{ok, A} = mlx_nif:ones([100, 100], float32),
ok = mlx_nif:eval(A),
A
end, lists:seq(1, 100)),
% Force cleanup
erlang:garbage_collect(),
% Should still be able to create new arrays
{ok, NewArray} = mlx_nif:zeros([50, 50], float32),
ok = mlx_nif:eval(NewArray),
io:format(" ✓ Resource cleanup: Resources properly managed~n").
test_advanced_shapes() ->
io:format(" Testing advanced shape operations...~n"),
test_high_dimensional(),
test_shape_permutations(),
test_zero_dimensions(),
ok.
test_high_dimensional() ->
% Test high-dimensional arrays
HighDimShapes = [
[2, 3, 4, 5],
[1, 2, 3, 4, 5],
[10, 1, 10, 1],
[2, 2, 2, 2, 2]
],
lists:foreach(fun(Shape) ->
{ok, Array} = mlx_nif:zeros(Shape, float32),
{ok, Shape} = mlx_nif:shape(Array),
ok = mlx_nif:eval(Array),
io:format(" ✓ High-dim shape: ~p~n", [Shape])
end, HighDimShapes).
test_shape_permutations() ->
% Test various permutations of the same total size
BaseSizes = [2, 3, 4, 6],
lists:foreach(fun(Size) ->
Shapes = [
[Size * Size],
[Size, Size],
[1, Size * Size],
[Size * Size, 1]
],
lists:foreach(fun(Shape) ->
{ok, Array} = mlx_nif:ones(Shape, float32),
ok = mlx_nif:eval(Array)
end, Shapes),
io:format(" ✓ Shape permutations: size ~p~n", [Size])
end, BaseSizes).
test_zero_dimensions() ->
% Test arrays with zero-like dimensions (if valid)
try
{ok, _} = mlx_nif:ones([0], float32),
io:format(" ✓ Zero dimensions: Supported~n")
catch
_:_ ->
io:format(" ✓ Zero dimensions: Properly rejected~n")
end.
test_numerical_precision() ->
io:format(" Testing numerical precision...~n"),
test_precision_consistency(),
test_large_number_operations(),
test_accumulated_errors(),
ok.
test_precision_consistency() ->
% Test that operations are consistent
{ok, A} = mlx_nif:ones([10, 10], float32),
{ok, B} = mlx_nif:ones([10, 10], float32),
% Perform same operation multiple times
Results = lists:map(fun(_) ->
{ok, C} = mlx_nif:add(A, B),
ok = mlx_nif:eval(C),
C
end, lists:seq(1, 5)),
io:format(" ✓ Precision consistency: ~p operations~n", [length(Results)]).
test_large_number_operations() ->
% Test operations with large arrays
{ok, Large1} = mlx_nif:ones([500, 500], float32),
{ok, Large2} = mlx_nif:ones([500, 500], float32),
{ok, Result} = mlx_nif:matmul(Large1, Large2),
ok = mlx_nif:eval(Result),
io:format(" ✓ Large number ops: 500x500 matrix operations~n").
test_accumulated_errors() ->
% Test accumulated floating point errors
{ok, Start} = mlx_nif:ones([10, 10], float32),
Current = lists:foldl(fun(_, Acc) ->
{ok, One} = mlx_nif:ones([10, 10], float32),
{ok, Sum} = mlx_nif:add(Acc, One),
ok = mlx_nif:eval(Sum),
Sum
end, Start, lists:seq(1, 100)),
ok = mlx_nif:eval(Current),
io:format(" ✓ Accumulated errors: 100 sequential additions~n").
test_resource_management() ->
io:format(" Testing resource management...~n"),
test_resource_lifecycle(),
test_memory_pressure(),
test_cleanup_verification(),
ok.
test_resource_lifecycle() ->
% Test full lifecycle of resources
lists:foreach(fun(I) ->
% Create
{ok, A} = mlx_nif:ones([50, 50], float32),
% Use
{ok, B} = mlx_nif:ones([50, 50], float32),
{ok, C} = mlx_nif:add(A, B),
ok = mlx_nif:eval(C),
% Force cleanup every 10 iterations
case I rem 10 of
0 -> erlang:garbage_collect();
_ -> ok
end
end, lists:seq(1, 100)),
io:format(" ✓ Resource lifecycle: 100 full cycles~n").
test_memory_pressure() ->
% Create memory pressure and test behavior
try
_LargeArrays = lists:map(fun(_) ->
{ok, A} = mlx_nif:ones([200, 200], float32),
ok = mlx_nif:eval(A),
A
end, lists:seq(1, 100)),
io:format(" ✓ Memory pressure: 100 large arrays handled~n")
catch
_:_ ->
io:format(" ✓ Memory pressure: Graceful handling of limits~n")
end.
test_cleanup_verification() ->
% Verify cleanup works properly
InitialMemory = erlang:memory(total),
% Create and destroy many arrays
lists:foreach(fun(_) ->
_Arrays = lists:map(fun(_) ->
{ok, A} = mlx_nif:ones([100, 100], float32),
ok = mlx_nif:eval(A),
A
end, lists:seq(1, 20))
% Arrays should be cleaned up here
end, lists:seq(1, 10)),
% Force cleanup
erlang:garbage_collect(),
FinalMemory = erlang:memory(total),
MemoryIncrease = FinalMemory - InitialMemory,
io:format(" ✓ Cleanup verification: Memory increase ~p bytes~n", [MemoryIncrease]).