From 0f1b4ed1571d9070f4f55d6933b883d834cb548b Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Fri, 5 Dec 2025 17:20:23 -0500 Subject: [PATCH 1/7] Starting new dagger example --- Project.toml | 11 +- .../Project.toml | 12 ++ .../benchmark.jl | 136 ++++++++++++++++++ .../generate-dagger-linear-solver/generate.jl | 19 +++ .../generate-dagger-linear-solver/readme | 1 + src/Agentic.jl | 58 +++++++- src/SmartSolve.jl | 6 +- src/test_performance.jl | 13 -- src/test_performance_cuda.jl | 21 +-- src/test_performance_dagger.jl | 62 ++++++++ 10 files changed, 299 insertions(+), 40 deletions(-) create mode 100644 examples/agentic/generate-dagger-linear-solver/Project.toml create mode 100644 examples/agentic/generate-dagger-linear-solver/benchmark.jl create mode 100644 examples/agentic/generate-dagger-linear-solver/generate.jl create mode 100644 examples/agentic/generate-dagger-linear-solver/readme create mode 100644 src/test_performance_dagger.jl diff --git a/Project.toml b/Project.toml index 9f77054..9abf269 100644 --- a/Project.toml +++ b/Project.toml @@ -9,9 +9,11 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Dagger = "d58978e5-989f-55fb-8d15-ea34adc7bf54" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" DecisionTree = "7806a523-6efd-50cb-b5f6-3fa6f1930dbb" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" MKL = "33e6dc65-8f57-5167-99aa-e5a354878fb2" MatrixDepot = "b51810bb-c9f3-55da-ae3c-350fc1fbce05" OpenAI = "e9f21f70-7185-4079-aca2-91159181367c" @@ -21,15 +23,20 @@ ScikitLearn = "3646fa90-6ef7-5e7e-9f22-8aca16db6324" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +[sources] +Dagger = {rev = "master", url = "https://github.com/JuliaParallel/Dagger.jl"} + [compat] +BSON = "0.3" BenchmarkTools = "1" CSV = "0.10" -BSON = "0.3" CUDA = "5.9.2" CairoMakie = "0.15" -DecisionTree = "0.12" +Dagger = "0.19.2" DataFrames = "1" +DecisionTree = "0.12" LinearAlgebra = "1.12.0" +Logging = "1.11.0" MKL = "0.9" MatrixDepot = "1.0.13" OpenAI = "0.12.0" diff --git a/examples/agentic/generate-dagger-linear-solver/Project.toml b/examples/agentic/generate-dagger-linear-solver/Project.toml new file mode 100644 index 0000000..62780dc --- /dev/null +++ b/examples/agentic/generate-dagger-linear-solver/Project.toml @@ -0,0 +1,12 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba" +Dagger = "d58978e5-989f-55fb-8d15-ea34adc7bf54" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +SmartSolve = "4fbb3a3c-2fa1-4c19-8d57-bae8bc1e16ac" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[sources] +Dagger = {rev = "master", url = "https://github.com/JuliaParallel/Dagger.jl"} +SmartSolve = {path = "../../.."} diff --git a/examples/agentic/generate-dagger-linear-solver/benchmark.jl b/examples/agentic/generate-dagger-linear-solver/benchmark.jl new file mode 100644 index 0000000..9ed3a2d --- /dev/null +++ b/examples/agentic/generate-dagger-linear-solver/benchmark.jl @@ -0,0 +1,136 @@ +using LinearAlgebra +using SparseArrays +using CUDA +using BenchmarkTools +using OrderedCollections +using Plots + +println("GPU benchmark with error-vs-time plot:\n") + +include("solver.jl") + +# Configuration +N = 15_000 +sparsity_levels = [0.1, 0.5, 0.9] +solvers = OrderedDict( + "Default" => (Ad, bd) -> (Ad \ bd), + "gesv!" => (Ad, bd) -> begin + x = CuArray(zeros(size(Ad, 1))) + CUDA.CUSOLVER.gesv!(x, Ad, bd) + x + end, + "Generated" => (Ad, bd) -> proposed_fn(Ad, bd) +) + +# Store results for plotting +results = Dict() + +for sparsity in sparsity_levels + println("\n=== Sparsity: $sparsity ===") + + # Generate problem + A = sprand(N, N, sparsity) + b = rand(N) + Ad = CuArray(Matrix(A)) + bd = CuArray(b) + + results[sparsity] = Dict() + + for (solver_name, solver_fn) in solvers + println(" $solver_name...") + + # Warm-up + bd_warm = CuArray(copy(b)) + try + x_warm = solver_fn(Ad, bd_warm) + CUDA.synchronize() + catch e + println(" Warning: solver failed during warm-up: $e") + continue + end + + # Benchmark + bd_bench = CuArray(copy(b)) + try + bench = @benchmark begin + x = $(solver_fn)($Ad, $bd_bench) + CUDA.synchronize() + end seconds = 5 samples = 10 + + time_ms = median(bench.times) / 1e9 # Convert to s + + # Compute error + bd_err = CuArray(copy(b)) + x_sol = solver_fn(Ad, bd_err) + CUDA.synchronize() + error = norm(Ad*x_sol - bd_err) / norm(bd_err) + + results[sparsity][solver_name] = (time=time_ms, error=error) + println(" Time: $(round(time_ms, digits=3)) s, Error: $(round(error, sigdigits=3))") + catch e + println(" Error during benchmark: $e") + end + end +end + +# Create error-vs-time plot +p = plot( + size=(800, 800), + #legend=:topright, + legend=:bottomright, + xlabel="Time (s)", + ylabel="Relative residual: ||Ax - b||₂ / ||b||₂", + # xscale=:log10, + yscale=:log10, + guidefontsize=22,#18, + tickfontsize=20, #16, + legendfontsize=18, #14, + margin=5*Plots.mm, + framestyle=:box, + title="Random Matrices of Size $(N)x$(N),\n Varying Sparsity Levels (ρ) and\n GPU Solvers", + titlefontsize=22, +) + +# Symbols encode sparsity levels; colors encode solvers. +# Define marker for each sparsity and a color for each solver. +## Sparsity shapes +marker_map_sparsity = OrderedDict(0.1=>:circle, 0.5=>:square, 0.9=>:utriangle) +## Solver color shades +color_map_solver = OrderedDict("Default"=>:red, "gesv!"=>:blue, "Generated"=>:green) + +# Plot each point individually so marker shape shows sparsity and color shows solver. +for solver_name in keys(solvers) + for sparsity in sparsity_levels + if sparsity in keys(results) && solver_name in keys(results[sparsity]) + t = results[sparsity][solver_name].time + e = results[sparsity][solver_name].error + scatter!(p, [t], [e]; + label="", + marker=marker_map_sparsity[sparsity], + markersize=15, + color=color_map_solver[solver_name], + markerstrokecolor=:black, + markerstrokewidth=0.0,#0.8, + alpha=0.45) + end + end +end + +# Create a combined legend +for solver_name in keys(solvers) + for s in sparsity_levels + lbl = "$(solver_name), ρ:$(s)" + scatter!(p, [NaN], [NaN]; label=lbl, + marker=marker_map_sparsity[s], + markersize=15, + color=color_map_solver[solver_name], + markerstrokecolor=:black, + markerstrokewidth=0.0, + alpha=0.45) + end +end + +savefig(p, "error_vs_time.pdf") +println("\n✓ Plot saved as error_vs_time.pdf") + +display(p) \ No newline at end of file diff --git a/examples/agentic/generate-dagger-linear-solver/generate.jl b/examples/agentic/generate-dagger-linear-solver/generate.jl new file mode 100644 index 0000000..3751f8f --- /dev/null +++ b/examples/agentic/generate-dagger-linear-solver/generate.jl @@ -0,0 +1,19 @@ +using SmartSolve +using LinearAlgebra +using SparseArrays +using CUDA +using BenchmarkTools +using Dagger + +prompt = """ +Generate a high-performance Dagger.jl (https://juliaparallel.org/Dagger.jl/dev/) implementation in Julia of a linear solver for sparse matrices +based on LU with iterative refinement (at least 5 refinement iterations), using the following +reference: https://nhigham.com/2023/03/13/what-is-iterative-refinement +""" + +secret_key = ENV["OPENAI_API_KEY"] +solver, hist, conv = gen_linear_solver_dagger(prompt, secret_key; max_iters = 5) + +println("Generated Code:\n") +println(solver) +write("solver.jl", solver) \ No newline at end of file diff --git a/examples/agentic/generate-dagger-linear-solver/readme b/examples/agentic/generate-dagger-linear-solver/readme new file mode 100644 index 0000000..e566ed3 --- /dev/null +++ b/examples/agentic/generate-dagger-linear-solver/readme @@ -0,0 +1 @@ +This example generates a high-performance Dagger.jl implementation in Julia for solving sparse linear systems using an LU-based method with iterative refinement. diff --git a/src/Agentic.jl b/src/Agentic.jl index a3a9842..e3fe137 100644 --- a/src/Agentic.jl +++ b/src/Agentic.jl @@ -20,7 +20,9 @@ function error_prompt_maker(err_message) end proposed_fn(x) = x -function generate_default_code(prompt, secret_key, checker_filename, model = "gpt-5-mini", dev_prompt_fn = dev_prompt_maker; max_iters = 3) +evaluator(x) = (true, "") +function generate_default_code(prompt, secret_key, checker_filename; + model = "gpt-5-mini", dev_prompt_fn = dev_prompt_maker, max_iters = 3) """ - checker_fn: proposed_fn -> check : Bool, performance_description : String """ @@ -81,18 +83,64 @@ function ls_cuda_dev_prompt_maker(fn_str) " Assume that LinearAlgebra and SparseArrays is already imported." end +function ls_dagger_dev_prompt_maker(fn_str) + return "You are a numerical linear algebra expert, and an expert Julia programmer. You are very experienced in GPU programming using CUDA." * + " The user will ask you to generate a function and use the following code the check if your solution is accurate and fast." * + " Make sure the code you produce uses Dagger." * + " Here is the code: \n" * fn_str * "\nOnly return the function. Make sure the function name is proposed_fn. Do not return extra text." * + " Assume that LinearAlgebra and SparseArrays is already imported." * + " Assume that Dagger is already imported." * + " Use the following Dagger.jl documentation: https://juliaparallel.org/Dagger.jl/dev/" * + " Use the following Dagger.jl implementation of Cholesky as an example: https://github.com/JuliaParallel/Dagger.jl/blob/67211816781d59109d74940550ca2d80af96b13d/src/array/cholesky.jl" +end + src_dir = @__DIR__ -function gen_linear_solver(prompt, secret_key, checker_filename = src_dir * "/test_performance.jl", model = "gpt-5-mini"; max_iters = 10) - return generate_default_code(prompt, secret_key, checker_filename, model, ls_dev_prompt_maker; max_iters = max_iters) +function gen_linear_solver(prompt, secret_key; + checker_filename = src_dir * "/test_performance.jl", + model = "gpt-5-mini", + max_iters = 10) + return generate_default_code(prompt, secret_key, checker_filename; + model = model, + dev_prompt_fn=ls_dev_prompt_maker, + max_iters = max_iters) end -function gen_linear_solver_cuda(prompt, secret_key,checker_filename = src_dir *"/test_performance_cuda.jl", model = "gpt-5-mini"; max_iters = 10) - return generate_default_code(prompt, secret_key, checker_filename, model, ls_cuda_dev_prompt_maker; max_iters = max_iters) +function gen_linear_solver_cuda(prompt, secret_key; + checker_filename = src_dir *"/test_performance_cuda.jl", + model = "gpt-5-mini", + max_iters = 10) + return generate_default_code(prompt, secret_key, checker_filename; + model=model, + dev_prompt_fn=ls_cuda_dev_prompt_maker, + max_iters = max_iters) +end + +function gen_linear_solver_dagger(prompt, secret_key; + checker_filename = src_dir *"/test_performance_dagger.jl", + model = "gpt-5-mini", + max_iters = 10) + return generate_default_code(prompt, secret_key, checker_filename; + model=model, + dev_prompt_fn=ls_dagger_dev_prompt_maker, + max_iters = max_iters) end function printhist(hist) for (i, (role, message)) in enumerate(hist) println("Message $i $(role[2]):\n$(message[2])\n") end +end + +function get_report(m_err, m_runtime, m_alloc, + err_threshold, runtime_threshold, alloc_threshold) + report = """ + Median error ratio (error_default / error_gen): $(m_err) + Desired median error ratio: >= $err_threshold + Median runtime ratio or speedup (runtime_default / runtime_gen): $(m_runtime) + Desired median runtime ratio: >= $runtime_threshold + Allocation median ratio (alloc_default / alloc_gen): $(m_alloc) + Desired median allocation ratio: >= $alloc_threshold + """ + return report end \ No newline at end of file diff --git a/src/SmartSolve.jl b/src/SmartSolve.jl index 9d3023e..5fc64d5 100644 --- a/src/SmartSolve.jl +++ b/src/SmartSolve.jl @@ -1,5 +1,6 @@ module SmartSolve + using MatrixDepot using LinearAlgebra using DataFrames @@ -13,15 +14,14 @@ using BSON using SparseArrays using OpenAI using CUDA +using Dagger include("SmartDiscovery.jl") include("SmartDB.jl") include("SmartModel.jl") include("Utils.jl") include("Agentic.jl") -include("test_performance.jl") -# include("test_performance_cuda.jl") -export generate_default_code, gen_linear_solver, gen_linear_solver_cuda, printhist +export generate_default_code, gen_linear_solver, gen_linear_solver_cuda, gen_linear_solver_dagger, printhist end # module SmartSolve diff --git a/src/test_performance.jl b/src/test_performance.jl index 85cb8b9..fefdeaf 100644 --- a/src/test_performance.jl +++ b/src/test_performance.jl @@ -4,19 +4,6 @@ push!(test_matrices, sprand(N, N, 0.1)) push!(test_matrices, sprand(N, N, 0.2)) push!(test_matrices, sprand(N, N, 0.3)) -function get_report(m_err, m_runtime, m_alloc, - err_threshold, runtime_threshold, alloc_threshold) - report = """ - Median error ratio (error_default / error_gen): $(m_err) - Desired median error ratio: >= $err_threshold - Median Runtime ratio or speedup (runtime_default / runtime_gen): $(m_runtime) - Desired median runtime ratio: >= $runtime_threshold - Allocation median ratio (alloc_default / alloc_gen): $(m_alloc) - Desired median allocation ratio: >= $alloc_threshold - """ - return report -end - function evaluator(proposed_fn, err_threshold=1.0, runtime_threshold=1.1, alloc_threshold=0.0) diff --git a/src/test_performance_cuda.jl b/src/test_performance_cuda.jl index 44921f3..269ba51 100644 --- a/src/test_performance_cuda.jl +++ b/src/test_performance_cuda.jl @@ -4,23 +4,10 @@ push!(test_matrices, sprand(N, N, 0.1)) push!(test_matrices, sprand(N, N, 0.2)) push!(test_matrices, sprand(N, N, 0.3)) -function get_report(m_err, m_runtime, m_alloc, - err_threshold, runtime_threshold, alloc_threshold) - report = """ - Median error ratio (error_default / error_gen): $(m_err) - Desired median error ratio: >= $err_threshold - Median runtime ratio or speedup (runtime_default / runtime_gen): $(m_runtime) - Desired median runtime ratio: >= $runtime_threshold - Allocation median ratio (alloc_default / alloc_gen): $(m_alloc) - Desired median allocation ratio: >= $alloc_threshold - """ - return report -end - -function evaluator_cuda(proposed_fn; - err_threshold::Float64 = 1.0, - runtime_threshold::Float64 = 1.1, - alloc_threshold::Float64 = 0.0) +function evaluator( proposed_fn; + err_threshold::Float64 = 1.0, + runtime_threshold::Float64 = 1.1, + alloc_threshold::Float64 = 0.0) error_ratios = Float64[] runtime_ratios = Float64[] diff --git a/src/test_performance_dagger.jl b/src/test_performance_dagger.jl new file mode 100644 index 0000000..80e1d9d --- /dev/null +++ b/src/test_performance_dagger.jl @@ -0,0 +1,62 @@ +test_matrices = [] +N = 2000#10_000 +# push!(test_matrices, randn(N, N)) +# push!(test_matrices, randn(N, N)) +# push!(test_matrices, randn(N, N)) +Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + push!(test_matrices, randn(Blocks(N, N), N, N)) + push!(test_matrices, randn(Blocks(N, N), N, N)) + push!(test_matrices, randn(Blocks(N, N), N, N)) +end + +function evaluator(proposed_fn; + err_threshold::Float64 = 1.0, + runtime_threshold::Float64 = 1.1, + alloc_threshold::Float64 = 0.0) + error_ratios = Float64[] + runtime_ratios = Float64[] + alloc_ratios = Float64[] + for A_d in test_matrices + # right-hand side on CPU + A_dim2 = size(A_d, 2) + b_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + randn(Blocks(A_dim2), A_dim2) + end + # move to GPU; here we use a dense GPU matrix + # If you have a sparse GPU solver, you can switch to CuSparseMatrixCSR(A_cpu) + A_cuda = CuArray(collect(A_d)) + b_cuda = CuArray(collect(b_d)) + # --- Solve once to ensure kernels are compiled (warm-up) --- + x_default = similar(b_cuda) + CUSOLVER.gesv!(x_default, A_cuda, b_cuda, irs_precision = "R_32F") + x_gen = similar(b_d) + Base.invokelatest(proposed_fn, x_gen, A_d, b_d) + CUDA.synchronize() + # --- Error ratios (all on GPU, scalars on CPU) --- + err_default = norm(A_cuda * x_default - b_cuda) + err_gen = norm(A_d * x_gen - b_d) + push!(error_ratios, err_default / err_gen) + # --- Runtime ratios (GPU) --- + b_default = @benchmark begin + x = similar($b_cuda) + CUSOLVER.gesv!($x, $A_cuda, $b_cuda, irs_precision = "R_32F") + CUDA.synchronize() + end + b_gen = @benchmark begin + x = similar($b_d) + Base.invokelatest($proposed_fn, $x, $A_d, $b_d) + end + push!(runtime_ratios, median(b_default.times) / median(b_gen.times)) + push!(alloc_ratios, median(b_default.allocs) / median(b_gen.allocs)) + end + m_err = median(error_ratios) + m_runtime = median(runtime_ratios) + m_alloc = median(alloc_ratios) + report = get_report(m_err, m_runtime, m_alloc, + err_threshold, runtime_threshold, alloc_threshold) + println(report) + ok = (m_err >= err_threshold) && # 1.0 => no worse error + (m_runtime >= runtime_threshold) && # 1.1 => at least 10% faster + (m_alloc >= alloc_threshold) # 0.0 => no alloc requirement + return ok, report +end \ No newline at end of file From cb9595589eee38d69bc9ebbf83d73265c5f09a32 Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Thu, 11 Dec 2025 12:59:03 -0500 Subject: [PATCH 2/7] Updates in agentic examples --- Project.toml | 2 +- .../error_vs_time.pdf | Bin 0 -> 33802 bytes .../generate-cpu-linear-solver/solver.jl | 91 +++++++---------- .../Project.toml | 2 +- .../generate-dagger-linear-solver/generate.jl | 93 +++++++++++++++++- src/Agentic.jl | 14 ++- src/test_performance_dagger.jl | 64 ++++++------ 7 files changed, 165 insertions(+), 101 deletions(-) create mode 100644 examples/agentic/generate-cpu-linear-solver/error_vs_time.pdf diff --git a/Project.toml b/Project.toml index 9abf269..e1e4c02 100644 --- a/Project.toml +++ b/Project.toml @@ -24,7 +24,7 @@ SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [sources] -Dagger = {rev = "master", url = "https://github.com/JuliaParallel/Dagger.jl"} +Dagger = {rev = "jps/lu-ldiv3", url = "https://github.com/JuliaParallel/Dagger.jl"} [compat] BSON = "0.3" diff --git a/examples/agentic/generate-cpu-linear-solver/error_vs_time.pdf b/examples/agentic/generate-cpu-linear-solver/error_vs_time.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8dce513d9864a66a91644d8c3844abd3aaabe6c7 GIT binary patch literal 33802 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 R!^6wRi$zZ_r6i5@{{Z7VkUszb literal 0 HcmV?d00001 diff --git a/examples/agentic/generate-cpu-linear-solver/solver.jl b/examples/agentic/generate-cpu-linear-solver/solver.jl index f9b8a54..05fc6ca 100644 --- a/examples/agentic/generate-cpu-linear-solver/solver.jl +++ b/examples/agentic/generate-cpu-linear-solver/solver.jl @@ -1,65 +1,44 @@ -function proposed_fn(A::SparseMatrixCSC, b::AbstractVector) - @assert size(A,1) == size(A,2) "A must be square" - n = length(b) - @assert size(A,2) == n "Dimensions of A and b must agree" - - niters = 4 - - # Convert sparse matrix to dense double for accurate residual computation - # and to dense single for fast factorization/solves with multithreaded BLAS. - Ad64 = Array(A) # dense Float64 - Ad32 = Array{Float32}(undef, n, n) - @inbounds for j in 1:n - for i in 1:n - Ad32[i,j] = Float32(Ad64[i,j]) - end +function proposed_fn(A, b) + # Cache Float32 factorizations and work buffers per matrix identity + if !isdefined(@__MODULE__, :LU32_CACHE) + global LU32_CACHE = IdDict{UInt64, Tuple{Any, Vector{Float64}, Vector{Float32}, Vector{Float32}}}() end - # Convert rhs to Float32 once - b32 = Vector{Float32}(undef, n) - @inbounds @simd for i in 1:n - b32[i] = Float32(b[i]) - end - - # Factorize dense single-precision matrix (uses LAPACK/BLAS and is multithreaded) - F32 = lu(Ad32) - - # Initial solve in single precision, in-place if possible - x32 = copy(b32) - try - LinearAlgebra.ldiv!(F32, x32) # in-place: x32 <- Ad32 \ b32 - catch - x32 = F32 \ b32 # fallback + # Ensure b as Float64 vector (avoid copy if already Float64) + b64 = eltype(b) === Float64 ? b : Vector{Float64}(b) + n = length(b64) + + key = objectid(A) + F32, r64, work32, bf32 = get!(LU32_CACHE, key) do + # Build a single-precision copy of the numeric values (structure reuse) + nz32 = Float32.(A.nzval) + Af = SparseMatrixCSC{Float32, Int}(size(A,1), size(A,2), + copy(A.colptr), copy(A.rowval), + nz32) + F32_local = lu(Af) # single-precision sparse LU + r64_local = Vector{Float64}(undef, n) # residual buffer (double) + work32_local = Vector{Float32}(undef, n) # temp residual in single + bf32_local = Vector{Float32}(undef, n) # temp right-hand side in single + return (F32_local, r64_local, work32_local, bf32_local) end - # Promote to double precision for accumulation and residual computation - x = Vector{Float64}(undef, n) - @inbounds @simd for i in 1:n - x[i] = Float64(x32[i]) + # Initial solve in single precision, accumulate in double + @inbounds for i = 1:n + bf32[i] = Float32(b64[i]) end - - # Preallocate working vectors - r = similar(b) # Float64 residual - r32 = Vector{Float32}(undef, n) # single-precision correction (in-place) - - for iter in 1:niters - # r = b - Ad64 * x (use BLAS for dense matvec) - mul!(r, Ad64, x) # r = Ad64 * x - @inbounds @simd for i in 1:n - r[i] = b[i] - r[i] - r32[i] = Float32(r[i]) + xf32 = F32 \ bf32 + x = Float64.(xf32) + + # Iterative refinement: compute residual in double, solve correction in single, update double solution + for _ = 1:5 + mul!(r64, A, x) # r64 = A * x (double) + @inbounds for i = 1:n + r64[i] = b64[i] - r64[i] # r64 = b - A*x + work32[i] = Float32(r64[i]) # convert residual to single end - - # Solve correction in single precision using the LU factorization - try - LinearAlgebra.ldiv!(F32, r32) # r32 <- Ad32 \ r32 (in-place) - catch - r32 = F32 \ r32 # fallback - end - - # Update double-precision solution - @inbounds @simd for i in 1:n - x[i] += Float64(r32[i]) + d32 = F32 \ work32 + @inbounds for i = 1:n + x[i] += Float64(d32[i]) # update solution in double end end diff --git a/examples/agentic/generate-dagger-linear-solver/Project.toml b/examples/agentic/generate-dagger-linear-solver/Project.toml index 62780dc..8362c6c 100644 --- a/examples/agentic/generate-dagger-linear-solver/Project.toml +++ b/examples/agentic/generate-dagger-linear-solver/Project.toml @@ -8,5 +8,5 @@ SmartSolve = "4fbb3a3c-2fa1-4c19-8d57-bae8bc1e16ac" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [sources] -Dagger = {rev = "master", url = "https://github.com/JuliaParallel/Dagger.jl"} +Dagger = {rev = "jps/lu-ldiv3", url = "https://github.com/JuliaParallel/Dagger.jl"} SmartSolve = {path = "../../.."} diff --git a/examples/agentic/generate-dagger-linear-solver/generate.jl b/examples/agentic/generate-dagger-linear-solver/generate.jl index 3751f8f..e3e3709 100644 --- a/examples/agentic/generate-dagger-linear-solver/generate.jl +++ b/examples/agentic/generate-dagger-linear-solver/generate.jl @@ -6,14 +6,97 @@ using BenchmarkTools using Dagger prompt = """ -Generate a high-performance Dagger.jl (https://juliaparallel.org/Dagger.jl/dev/) implementation in Julia of a linear solver for sparse matrices -based on LU with iterative refinement (at least 5 refinement iterations), using the following -reference: https://nhigham.com/2023/03/13/what-is-iterative-refinement +- Task: Write a high-performance Julia implementation of a linear solver for sparse matrices, using Dagger.jl on the GPU. The solver must be based on Cholesky factorization with iterative refinement. + +- Requirements + +1) Libraries and references + +1.1) Use Dagger.jl as documented here: +https://juliaparallel.org/Dagger.jl/dev/ + +1.2) Follow the iterative refinement algorithm described here: +https://nhigham.com/2023/03/13/what-is-iterative-refinement/ + +2) Dagger.jl + Cholesky + +2.1) Dagger.jl already has an Cholesky routine that can be used for distributed linear solves: cholesky(A_d) where A_d is a distributed matrix (DMatrix). This implementation extends cholesky from LinearAlgebra.jl. See https://github.com/JuliaParallel/Dagger.jl/blob/master/src/array/cholesky.jl. + +2.2) Use that Cholesky implementation within Dagger.jl (do not re-implement Cholesky from scratch). + +2.3) The computation must be fully on GPU, using Dagger's GPU support. + +2.4) Do not move data back to the CPU for intermediate computations. + +2.5) All linear algebra operations (factorization, forward/back substitution, residual computation, refinement updates) must be performed on GPU-resident Dagger arrays. + +3) Function API + +3.1) Implement exactly one Julia function with the following signature: +function proposed_fn(A_d, b_d) + # your code here +end +A_d: distributed sparse matrix (Dagger-distributed, GPU-resident). +b_d: distributed vector (Dagger-distributed, GPU-resident). +x: solution vector (Dagger-distributed, GPU-resident). You may treat x as an initial guess and overwrite it with the final refined solution. + +3.2) The function must return the final solution x (and anything else you consider useful, e.g., a residual norm, but the first return value must be the solution). + +4)Iterative refinement details + +4.1) Use Cholesky factorization of A_d to compute an initial solution x₀. + +4.2) Then apply iterative refinement: + + 4.2.1) At each iteration k, compute residual r_k = b_d - A_d * x_k on the GPU. + 4.2.2) Solve Ad d_k = r_k using the Cholesky factors (on GPU). + 4.2.3) Update x_k+1 = x_k + d_k on GPU. + +4.3) Perform at least 5 refinement iterations (you can use a loop with a fixed number of iterations ≥ 5; optional extra stopping criteria are allowed but not required). + +5) Performance and style constraints + +5.1) Use Dagger tasks / computation graphs appropriately so that the Cholesky factorization and solves are executed in parallel where possible. + +5.2) Avoid unnecessary data movement or conversions. + +5.3) Do not use CPU-only arrays or operations (no Array, no collect to CPU, etc.). + +5.4) Assume that using LinearAlgebra, using SparseArrays, and using Dagger have already been executed. + +5.5) Focus on clarity and correctness first, but structure the code with performance in mind (e.g., reuse LU factors, avoid recomputing them each iteration). + +6) Output format + +6.1) Output only the Julia code for the function: + +function proposed_fn(A_d, b_d) + ... +end + +6.2) Do not include any explanation, comments, or text outside the function definition. + """ secret_key = ENV["OPENAI_API_KEY"] -solver, hist, conv = gen_linear_solver_dagger(prompt, secret_key; max_iters = 5) +solver, hist, conv = gen_linear_solver_dagger(prompt, secret_key; max_iters = 50) println("Generated Code:\n") println(solver) -write("solver.jl", solver) \ No newline at end of file +write("solver.jl", solver) + + + + +# using Dagger, CUDA, LinearAlgebra +# N = 2000 +# A = rand(N, N) +# A = A * A' +# A[diagind(A)] .+= size(A, 1) +# A_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do +# view(A, Blocks(500, 500)) +# end +# b_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do +# randn(Blocks(500), N) +# end +# cholesky(A_d) \ b_d \ No newline at end of file diff --git a/src/Agentic.jl b/src/Agentic.jl index e3fe137..bf3c9e3 100644 --- a/src/Agentic.jl +++ b/src/Agentic.jl @@ -22,7 +22,9 @@ end proposed_fn(x) = x evaluator(x) = (true, "") function generate_default_code(prompt, secret_key, checker_filename; - model = "gpt-5-mini", dev_prompt_fn = dev_prompt_maker, max_iters = 3) + model = "gpt-5-mini", + dev_prompt_fn = dev_prompt_maker, + max_iters = 3) """ - checker_fn: proposed_fn -> check : Bool, performance_description : String """ @@ -42,6 +44,8 @@ function generate_default_code(prompt, secret_key, checker_filename; println("Iteration $iters") + println("Code:\n $gen_code") + # println(gen_code) push!(chat_history, Dict("role" => "assistant", "content" => gen_code)) try @@ -53,7 +57,7 @@ function generate_default_code(prompt, secret_key, checker_filename; next_prompt = description_prompt_maker(check, performance_description) push!(chat_history, Dict("role" => "user", "content" => next_prompt)) - converged = ~(iters == max_iters) + converevlged = ~(iters == max_iters) check && break catch e error_msg = sprint(showerror, e) @@ -61,6 +65,7 @@ function generate_default_code(prompt, secret_key, checker_filename; next_prompt = error_prompt_maker(error_msg * "\n" * st) push!(chat_history, Dict("role" => "user", "content" => next_prompt)) + println("error: $error_msg\n$st") end converged = ~(iters == max_iters) end @@ -84,14 +89,13 @@ function ls_cuda_dev_prompt_maker(fn_str) end function ls_dagger_dev_prompt_maker(fn_str) - return "You are a numerical linear algebra expert, and an expert Julia programmer. You are very experienced in GPU programming using CUDA." * + return "You are a numerical linear algebra expert, and an expert Julia programmer. You are very experienced in GPU programming using Dagger.jl" * " The user will ask you to generate a function and use the following code the check if your solution is accurate and fast." * " Make sure the code you produce uses Dagger." * " Here is the code: \n" * fn_str * "\nOnly return the function. Make sure the function name is proposed_fn. Do not return extra text." * " Assume that LinearAlgebra and SparseArrays is already imported." * " Assume that Dagger is already imported." * - " Use the following Dagger.jl documentation: https://juliaparallel.org/Dagger.jl/dev/" * - " Use the following Dagger.jl implementation of Cholesky as an example: https://github.com/JuliaParallel/Dagger.jl/blob/67211816781d59109d74940550ca2d80af96b13d/src/array/cholesky.jl" + " Use the following Dagger.jl documentation: https://juliaparallel.org/Dagger.jl/dev/" end src_dir = @__DIR__ diff --git a/src/test_performance_dagger.jl b/src/test_performance_dagger.jl index 80e1d9d..36dd211 100644 --- a/src/test_performance_dagger.jl +++ b/src/test_performance_dagger.jl @@ -1,50 +1,48 @@ -test_matrices = [] -N = 2000#10_000 -# push!(test_matrices, randn(N, N)) -# push!(test_matrices, randn(N, N)) -# push!(test_matrices, randn(N, N)) -Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do - push!(test_matrices, randn(Blocks(N, N), N, N)) - push!(test_matrices, randn(Blocks(N, N), N, N)) - push!(test_matrices, randn(Blocks(N, N), N, N)) -end - -function evaluator(proposed_fn; - err_threshold::Float64 = 1.0, - runtime_threshold::Float64 = 1.1, - alloc_threshold::Float64 = 0.0) - error_ratios = Float64[] +function evaluator( proposed_fn; + err_threshold::Float64 = 1.0, + runtime_threshold::Float64 = 1.1, + alloc_threshold::Float64 = 0.0) + N = 2000 #10_000 + error_ratios = Float64[] runtime_ratios = Float64[] alloc_ratios = Float64[] - for A_d in test_matrices - # right-hand side on CPU - A_dim2 = size(A_d, 2) + for _ in 1:3 + + # SPD Matrix + A = randn(N, N) + A = A*A' + N*I + + # Right-hand side + b = randn(N) + + # SPD Matrix and right hand side on GPU (CUDA) + A_cuda = CuArray(A) + b_cuda = CuArray(b) + + # SPD Matrix and right-hand side on GPU (Dagger Distributed) + A_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + distribute(A, Blocks(N÷4, N÷4)) + end b_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do - randn(Blocks(A_dim2), A_dim2) + distribute(b, Blocks(N÷4)) end - # move to GPU; here we use a dense GPU matrix - # If you have a sparse GPU solver, you can switch to CuSparseMatrixCSR(A_cpu) - A_cuda = CuArray(collect(A_d)) - b_cuda = CuArray(collect(b_d)) + # --- Solve once to ensure kernels are compiled (warm-up) --- - x_default = similar(b_cuda) - CUSOLVER.gesv!(x_default, A_cuda, b_cuda, irs_precision = "R_32F") - x_gen = similar(b_d) - Base.invokelatest(proposed_fn, x_gen, A_d, b_d) + x_default = cholesky(A_cuda) \ b_cuda + x_gen = Base.invokelatest(proposed_fn, A_d, b_d) CUDA.synchronize() + # --- Error ratios (all on GPU, scalars on CPU) --- err_default = norm(A_cuda * x_default - b_cuda) - err_gen = norm(A_d * x_gen - b_d) + err_gen = norm(A_d * x_gen - b_d) push!(error_ratios, err_default / err_gen) # --- Runtime ratios (GPU) --- b_default = @benchmark begin - x = similar($b_cuda) - CUSOLVER.gesv!($x, $A_cuda, $b_cuda, irs_precision = "R_32F") + cholesky($A_cuda) \ $b_cuda CUDA.synchronize() end b_gen = @benchmark begin - x = similar($b_d) - Base.invokelatest($proposed_fn, $x, $A_d, $b_d) + Base.invokelatest($proposed_fn, $A_d, $b_d) end push!(runtime_ratios, median(b_default.times) / median(b_gen.times)) push!(alloc_ratios, median(b_default.allocs) / median(b_gen.allocs)) From 705253210e6c1fa5aee6d25eb9012c30cd2caec0 Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Thu, 11 Dec 2025 18:25:04 -0500 Subject: [PATCH 3/7] Update of agentic cpu example --- .../{benchmark.jl => benchmark1.jl} | 24 ++++++------ .../generate-cpu-linear-solver/benchmark2.jl | 37 ++++++++++++++++++ .../error_vs_time.pdf | Bin 33802 -> 54458 bytes 3 files changed, 50 insertions(+), 11 deletions(-) rename examples/agentic/generate-cpu-linear-solver/{benchmark.jl => benchmark1.jl} (87%) create mode 100644 examples/agentic/generate-cpu-linear-solver/benchmark2.jl diff --git a/examples/agentic/generate-cpu-linear-solver/benchmark.jl b/examples/agentic/generate-cpu-linear-solver/benchmark1.jl similarity index 87% rename from examples/agentic/generate-cpu-linear-solver/benchmark.jl rename to examples/agentic/generate-cpu-linear-solver/benchmark1.jl index db7d835..ad81cf1 100644 --- a/examples/agentic/generate-cpu-linear-solver/benchmark.jl +++ b/examples/agentic/generate-cpu-linear-solver/benchmark1.jl @@ -18,9 +18,9 @@ umfpack_control = SparseArrays.UMFPACK.get_umfpack_control(Float64, Int64) # rea umfpack_control[SparseArrays.UMFPACK.JL_UMFPACK_IRSTEP] = 2.0 # reenable iterative refinement (2 is UMFPACK default max iterative refinement steps) solvers = OrderedDict( - "Default" => (A, b) -> (A \ b), - "UMFPACK" => (A, b) -> lu(A; control = umfpack_control) \ b, - "Generated" => (A, b) -> proposed_fn(A, b) + "UMFPACK (Baseline Direct Solve)" => (A, b) -> (A \ b), + "UMFPACK + Iterative Refinement" => (A, b) -> lu(A; control = umfpack_control) \ b, + "SmartSolve-Generated Solver" => (A, b) -> proposed_fn(A, b) ) # Store results for plotting @@ -52,7 +52,7 @@ for sparsity in sparsity_levels try bench = @benchmark begin x = $(solver_fn)($A, $b_bench) - end seconds = 5 samples = 10 + end time_ms = median(bench.times) / 1e9 # Convert to s @@ -67,20 +67,22 @@ for sparsity in sparsity_levels println(" Error during benchmark: $e") end end + + GC.gc() end # Create error-vs-time plot p = plot( size=(800, 800), #legend=:topright, - legend=:bottomleft, + legend=:topleft, xlabel="Time (s)", ylabel="Relative residual: ||Ax - b||₂ / ||b||₂", # xscale=:log10, yscale=:log10, guidefontsize=22,#18, tickfontsize=20, #16, - legendfontsize=18, #14, + legendfontsize=14, #14, margin=5*Plots.mm, framestyle=:box, title="Random Matrices of Size $(N)x$(N),\n Varying Sparsity Levels (ρ) and\n CPU Solvers", @@ -92,10 +94,10 @@ p = plot( ## Sparsity shapes marker_map_sparsity = OrderedDict(0.1=>:circle, 0.5=>:square, 0.9=>:utriangle) ## Solver color shades -color_map_solver = OrderedDict("Default"=>:red, "UMFPACK"=>:blue, "Generated"=>:green) +colors = [:red, :blue, :green] # Plot each point individually so marker shape shows sparsity and color shows solver. -for solver_name in keys(solvers) +for (i, solver_name) in enumerate(keys(solvers)) for sparsity in sparsity_levels if sparsity in keys(results) && solver_name in keys(results[sparsity]) t = results[sparsity][solver_name].time @@ -104,7 +106,7 @@ for solver_name in keys(solvers) label="", marker=marker_map_sparsity[sparsity], markersize=15, - color=color_map_solver[solver_name], + color=colors[i], markerstrokecolor=:black, markerstrokewidth=0.0,#0.8, alpha=0.45) @@ -113,13 +115,13 @@ for solver_name in keys(solvers) end # Create a combined legend -for solver_name in keys(solvers) +for (i, solver_name) in enumerate(keys(solvers)) for s in sparsity_levels lbl = "$(solver_name), ρ:$(s)" scatter!(p, [NaN], [NaN]; label=lbl, marker=marker_map_sparsity[s], markersize=15, - color=color_map_solver[solver_name], + color=colors[i], markerstrokecolor=:black, markerstrokewidth=0.0, alpha=0.45) diff --git a/examples/agentic/generate-cpu-linear-solver/benchmark2.jl b/examples/agentic/generate-cpu-linear-solver/benchmark2.jl new file mode 100644 index 0000000..88c2aa2 --- /dev/null +++ b/examples/agentic/generate-cpu-linear-solver/benchmark2.jl @@ -0,0 +1,37 @@ +using LinearAlgebra +using SparseArrays +using BenchmarkTools + +include("solver.jl") + +N = 15_000 +A = sprand(N, N, 0.5) +b = rand(N) + +# See https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#LinearAlgebra.lu +umfpack_control = SparseArrays.UMFPACK.get_umfpack_control(Float64, Int64) # read Julia default configuration for a Float64 sparse matrix +#SparseArrays.UMFPACK.show_umf_ctrl(umfpack_control) # optional - display values +umfpack_control[SparseArrays.UMFPACK.JL_UMFPACK_IRSTEP] = 2.0 # reenable iterative refinement (2 is UMFPACK default max iterative refinement steps) + +# warm-up +x1 = A \ b # solve Ax = b +x2 = lu(A; control = umfpack_control) \ b # solve Ax = b, including UMFPACK iterative refinement +x3 = proposed_fn(A, b) + +@benchmark begin + x = $A \ $b # solve Ax = b +end + +@benchmark begin + x = lu($A; control = $umfpack_control) \ $b # solve Ax = b, including UMFPACK iterative refinement +end + +@benchmark begin + x = proposed_fn($A, $b) +end + +error1 = norm(A*x1 - b) / norm(b) + +error2 = norm(A*x2 - b) / norm(b) + +error3 = norm(A*x3 - b) / norm(b) \ No newline at end of file diff --git a/examples/agentic/generate-cpu-linear-solver/error_vs_time.pdf b/examples/agentic/generate-cpu-linear-solver/error_vs_time.pdf index 8dce513d9864a66a91644d8c3844abd3aaabe6c7..0d33a2237a83dfd157e3655b7229ee9a8ed0b69d 100644 GIT binary patch literal 54458 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

x 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 R!^6wRi$zZ_r6i5@{{Z7VkUszb From edebe882ae84e1740285e99c2875e9b92f9adb17 Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Fri, 12 Dec 2025 19:26:23 -0500 Subject: [PATCH 4/7] Add cholesky benchmark --- .../cholesky_benchmark.jl | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl diff --git a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl new file mode 100644 index 0000000..68972f1 --- /dev/null +++ b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl @@ -0,0 +1,79 @@ +using LinearAlgebra, CUDA, Dagger, BenchmarkTools + +# SPD Matrix +N = 10_000 +A = randn(N, N) +A = A*A' + N*I + +# Right-hand side +b = randn(N) + + +# Cholesky: CUDA ############################################################# + +# SPD Matrix and right-hand side on GPU (CUDA) +A_cuda = CUDA.CuArray(A) +b_cuda = CUDA.CuArray(b) + +# Warm-up +x_cuda = cholesky(A_cuda) \ b_cuda + +# Benchmark time and memory +@time cholesky(A_cuda) # 0.895192 seconds (660 allocations: 12.094 KiB) +#@benchmark cholesky($A_cuda) +@time cholesky(A_cuda) \ b_cuda # 0.882263 seconds (953 allocations: 16.844 KiB) +#@benchmark cholesky($A_cuda) \ $b_cuda + +# Errors +e_cuda = norm(A_cuda*x_cuda - b_cuda)/norm(b_cuda) + +# Free memory +A_cuda = nothing +b_cuda = nothing +GC.gc() +CUDA.reclaim() + + +# Cholesky: Dagger ########################################################### + +# SPD Matrix and right-hand side on GPU (Dagger Distributed) +A_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + distribute(A, Blocks(N÷4, N÷4)) +end +b_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + distribute(b, Blocks(N÷4)) +end + +# Warm-up +x_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + cholesky(A_d) +end +Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + cholesky(A_d) \ b_d +end + +CUDA.reclaim() + +# Benchmark time and memory +@time Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + cholesky(A_d) +end # 1.039517 seconds (88.80 k allocations: 4.146 MiB, 7.74% gc time, 18 lock conflicts, 5.70% compilation time: <1% of which was recompilation) +@time Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do + cholesky(A_d) \ b_d +end # 1.046506 seconds (88.80 k allocations: 4.146 MiB, 7.74% gc time, 18 lock conflicts, 5.70% compilation time: <1% of which was recompilation) +# @benchmark Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do +# cholesky($A_d) +# end samples = 1 +# @benchmark Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do +# cholesky($A_d) \ $b_d +# end samples = 1 +# Free memory + +# Errors +e_d = norm(A_d*x_d - b_d) + +# Free memory +A_d = nothing +b_d = nothing +GC.gc() + From d7d73c4c158191ee51a0f40dd00dc99878191b77 Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Fri, 12 Dec 2025 19:30:59 -0500 Subject: [PATCH 5/7] Small changes in cholesky benchmark --- .../generate-dagger-linear-solver/cholesky_benchmark.jl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl index 68972f1..763bf77 100644 --- a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl +++ b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl @@ -24,7 +24,7 @@ x_cuda = cholesky(A_cuda) \ b_cuda @time cholesky(A_cuda) \ b_cuda # 0.882263 seconds (953 allocations: 16.844 KiB) #@benchmark cholesky($A_cuda) \ $b_cuda -# Errors +# Relative error e_cuda = norm(A_cuda*x_cuda - b_cuda)/norm(b_cuda) # Free memory @@ -67,10 +67,9 @@ end # 1.046506 seconds (88.80 k allocations: 4.146 MiB, 7.74% gc time, 18 lock # @benchmark Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do # cholesky($A_d) \ $b_d # end samples = 1 -# Free memory -# Errors -e_d = norm(A_d*x_d - b_d) +# Relative error +e_d = norm(A_d*x_d - b_d)/norm(b_cuda) # Free memory A_d = nothing From 59fb31308ac378d29227efc08d1aa23ce3942c61 Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Fri, 12 Dec 2025 19:36:53 -0500 Subject: [PATCH 6/7] Small changes in cholesky benchmark --- .../agentic/generate-dagger-linear-solver/cholesky_benchmark.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl index 763bf77..724b6f0 100644 --- a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl +++ b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl @@ -69,7 +69,7 @@ end # 1.046506 seconds (88.80 k allocations: 4.146 MiB, 7.74% gc time, 18 lock # end samples = 1 # Relative error -e_d = norm(A_d*x_d - b_d)/norm(b_cuda) +e_d = norm(A_d*x_d - b_d)/norm(b_d) # Free memory A_d = nothing From 6e7f8e3aa990cdbd1511ca0d06ed003b0678d710 Mon Sep 17 00:00:00 2001 From: Emmanuel Lujan Date: Fri, 12 Dec 2025 19:42:10 -0500 Subject: [PATCH 7/7] Small changes in cholesky benchmark --- .../generate-dagger-linear-solver/cholesky_benchmark.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl index 724b6f0..0e1bc2d 100644 --- a/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl +++ b/examples/agentic/generate-dagger-linear-solver/cholesky_benchmark.jl @@ -46,9 +46,6 @@ end # Warm-up x_d = Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do - cholesky(A_d) -end -Dagger.with_options(scope=Dagger.scope(;cuda_gpu=1)) do cholesky(A_d) \ b_d end