Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e03e94e
docs: Add comprehensive GPU acceleration analysis for autodiff
claude Nov 15, 2025
09d594e
feat: Add GPU acceleration foundation with ILGPU
claude Nov 15, 2025
c4c4a99
feat: Implement GPU MatMul, Transpose, and Reductions
claude Nov 15, 2025
c412aec
Implement GPU-accelerated autodiff integration
claude Nov 15, 2025
fd195cc
Add GPU autodiff benchmarks, example, and documentation
claude Nov 15, 2025
8405416
Integrate GPU acceleration into PredictionModelBuilder API
claude Nov 15, 2025
a27e9ae
Enable GPU acceleration on neural networks and optimizers
claude Nov 15, 2025
2af0ed1
Implement GPU-accelerated forward and backward passes in FeedForwardL…
claude Nov 15, 2025
83f75d1
Add end-to-end GPU training integration tests
claude Nov 15, 2025
02eb621
Add comprehensive GPU training guide
claude Nov 15, 2025
e53abd2
Expand GPU operation coverage with new activations and math functions
claude Nov 15, 2025
a41cbec
Add GPU-accelerated optimizer parameter updates and expand layer support
claude Nov 15, 2025
d7619ad
Add GPU support to DenseLayer backward pass and FullyConnectedLayer
claude Nov 15, 2025
255a35a
Add GPU support to ActivationLayer
claude Nov 15, 2025
402fd10
Add GPU support to AddLayer
claude Nov 15, 2025
30be4c6
Add GPU support to MultiplyLayer and MomentumOptimizer
claude Nov 15, 2025
567c482
Add GPU support to 4 more optimizers
claude Nov 15, 2025
9432cef
Add GPU support to AdaDeltaOptimizer
claude Nov 15, 2025
e0edd19
Add GPU acceleration to AdaMax, AMSGrad, and Lion optimizers
claude Nov 15, 2025
61e87fc
Add GPU acceleration to Nesterov, GradientDescent, and MiniBatchGradi…
claude Nov 15, 2025
bf4169b
Add GPU acceleration to Proximal Gradient Descent and FTRL optimizers
claude Nov 15, 2025
b035ba2
Complete GPU acceleration for all gradient-based optimizers
claude Nov 15, 2025
d57a139
Merge branch 'master' into claude/gpu-acceleration-autodiff-011CV1GgG…
ooples Nov 17, 2025
06026b4
fix: conditionally reference ilgpu packages only for net8.0
ooples Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
708 changes: 708 additions & 0 deletions docs/GPU_ACCELERATION_ANALYSIS.md

Large diffs are not rendered by default.

600 changes: 600 additions & 0 deletions docs/GPU_AUTODIFF_GUIDE.md

Large diffs are not rendered by default.

272 changes: 272 additions & 0 deletions examples/GpuTrainingExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
using AiDotNet.Autodiff;
using AiDotNet.Gpu;
using AiDotNet.LinearAlgebra;

namespace AiDotNet.Examples;

/// <summary>
/// Demonstrates end-to-end GPU-accelerated neural network training.
/// </summary>
/// <remarks>
/// <para>
/// This example shows how to train a simple two-layer neural network using GPU acceleration.
/// It demonstrates:
/// - Setting up GPU execution context
/// - Creating trainable parameters
/// - Forward pass with GPU operations
/// - Loss computation and backpropagation
/// - Parameter updates with gradient descent
/// - Automatic CPU/GPU placement
/// </para>
/// <para><b>For Beginners:</b> This is a complete neural network training example!
///
/// The network structure:
/// - Input layer: 784 features (28x28 image)
/// - Hidden layer: 128 neurons with ReLU activation
/// - Output layer: 10 neurons (classification into 10 classes)
///
/// Training process:
/// 1. Forward pass: Input → Hidden → Output
/// 2. Compute loss: How wrong is the prediction?
/// 3. Backward pass: Compute gradients for all parameters
/// 4. Update parameters: Adjust weights to reduce loss
///
/// GPU acceleration makes this 10-100x faster for large datasets!
/// </para>
/// </remarks>
public class GpuTrainingExample
{
public static void RunExample()
{
Console.WriteLine("=== GPU-Accelerated Neural Network Training ===\n");

// Step 1: Initialize GPU backend
using var backend = new IlgpuBackend<float>();
backend.Initialize();

if (!backend.IsAvailable)
{
Console.WriteLine("GPU not available. This example requires GPU support.");
return;
}

Console.WriteLine($"GPU Device: {backend.DeviceName}");
Console.WriteLine($"Total GPU Memory: {backend.TotalMemory / (1024 * 1024 * 1024):F2} GB");
Console.WriteLine($"Free GPU Memory: {backend.FreeMemory / (1024 * 1024 * 1024):F2} GB\n");

// Step 2: Create execution context with automatic placement
using var context = new ExecutionContext(backend)
{
Strategy = ExecutionContext.PlacementStrategy.AutomaticPlacement,
GpuThreshold = 50_000 // Use GPU for tensors with >50K elements
};

// Step 3: Initialize network parameters
Console.WriteLine("Initializing network parameters...");

const int inputSize = 784; // 28x28 images flattened
const int hiddenSize = 128; // Hidden layer neurons
const int outputSize = 10; // 10 classes (digits 0-9)
const float learningRate = 0.01f;

// Weights and biases for layer 1 (input → hidden)
var w1 = InitializeWeights(inputSize, hiddenSize);
var b1 = InitializeBias(hiddenSize);

// Weights and biases for layer 2 (hidden → output)
var w2 = InitializeWeights(hiddenSize, outputSize);
var b2 = InitializeBias(outputSize);

Comment on lines +67 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Bias shape vs. batched activations: verify broadcasting and gradient handling.

In this example the biases are initialized as [1, size]:

  • Lines 67–79: InitializeBias(int size) returns a tensor of shape [1, size].
  • Lines 120–133: You add b1Node/b2Node to batched activations (layer1Matmul, layer2Matmul), whose shapes are [batchSize, size]. This implicitly assumes broadcasting over the batch dimension.
  • Lines 235–245: UpdateParameter iterates linearly over param and gradient, assuming shapes align element‑wise.

Given the current GpuTensorOperations.Add backward implementation (from src/Autodiff/GpuTensorOperations.cs), gradients are propagated as‑is to both parents without any reduction over broadcasted axes. Combined with UpdateParameter, this likely means that for biases the batch dimension is either ignored or only a subset of the gradient is used, instead of aggregating over all samples.

Please double‑check:

  • Whether Tensor<T>.Add really supports broadcasting between [batchSize, size] and [1, size], and
  • Whether bias gradients are correctly reduced over the batch dimension before being passed into UpdateParameter.

If not, consider either:

  • Changing the example to store biases in the same shape as the activation they’re added to (no broadcasting in this demo), or
  • Adjusting the bias update path to explicitly reduce the gradient over the batch dimension (e.g., summing/averaging across rows) before applying the update.

This will make the example’s training behavior more faithful to typical NN bias handling.

Also applies to: 120-133, 235-245


I'll verify the review comment by examining the codebase implementation of the critical components mentioned.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find and examine GpuTensorOperations.cs to understand Add backward implementation
fd -n "GpuTensorOperations" --type f -e cs

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Examine the GpuTrainingExample.cs file, particularly around cited lines
fd -n "GpuTrainingExample" --type f -e cs

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Search for UpdateParameter implementation
rg -n "UpdateParameter" --type cs -B2 -A10

Length of output: 266975


🏁 Script executed:

#!/bin/bash
# Search for bias initialization pattern in the example
rg -n "InitializeBias" --type cs -B2 -A10

Length of output: 8032


I need to examine the Add operation implementation to verify the broadcasting and gradient handling. Let me search for that.


🏁 Script executed:

#!/bin/bash
# Search for Add method implementation in Tensor or GpuTensorOperations
rg -n "\.Add\(" --type cs examples/GpuTrainingExample.cs -B2 -A5

Length of output: 1013


🏁 Script executed:

#!/bin/bash
# Find where Add is called and understand the context
rg -n "layer1Matmul.*Add|layer2Matmul.*Add" --type cs examples/GpuTrainingExample.cs -B1 -A3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for Tensor class Add method definition
rg -n "public.*Add.*Tensor" --type cs -A15 | head -100

Length of output: 4210


🏁 Script executed:

#!/bin/bash
# Look for backward/gradient handling in Add operations
rg -n "Backward|GradientTape|backward" --type cs examples/GpuTrainingExample.cs -B1 -A3

Length of output: 497


Based on my verification, the review comment identifies a real and critical issue. Here are the findings:

Key Discovery: The Tensor<T>.Add() method (in src/LinearAlgebra/Tensor.cs lines 2030-2040) and the GPU backend's Add() (in src/Gpu/IlgpuBackend.cs lines 553-562) both call validation that enforces strict shape matching — broadcasting is not supported.

The Problem:

  • Line 196: InitializeBias(hiddenSize) creates shape [1, size]
  • Lines 122 & 127: Code attempts Add([batchSize, size], [1, size])
  • Result: Runtime failure — shape validation will throw an exception

The code will not even execute past the first Add operation. This is more severe than the review comment suggests: it's not a subtle gradient handling issue, but rather an immediate runtime crash due to unsupported broadcasting.


Rewritten Review Comment:

Bias tensor shape incompatible with Add operation—broadcasting not supported.

The biases are initialized as [1, size] (line 196) but added to batched activations of shape [batchSize, size] (lines 122, 127). The Tensor<T>.Add() method and GPU backend do not support broadcasting and will throw a shape validation error at runtime.

Fix options:

  • Change bias initialization to [batchSize, size] to match activations, or
  • Use explicit bias addition logic that repeats/broadcasts the bias to batch size before adding, or
  • Implement broadcasting support in Add() if this is a common pattern elsewhere in the codebase.

Lines affected: 74, 78 (initialization), 122, 127 (usage), 235–245 (UpdateParameter).

Console.WriteLine($"W1 shape: [{string.Join("x", w1.Shape)}]");
Console.WriteLine($"W2 shape: [{string.Join("x", w2.Shape)}]\n");

// Step 4: Create synthetic training data
Console.WriteLine("Creating synthetic training data...");
const int batchSize = 32;
var inputBatch = CreateRandomBatch(batchSize, inputSize);
var targetBatch = CreateRandomTargets(batchSize, outputSize);

Console.WriteLine($"Input batch shape: [{string.Join("x", inputBatch.Shape)}]");
Console.WriteLine($"Target batch shape: [{string.Join("x", targetBatch.Shape)}]\n");

// Step 5: Training loop
Console.WriteLine("Starting training...\n");
const int epochs = 10;

for (int epoch = 0; epoch < epochs; epoch++)
{
// Reset statistics for this epoch
context.ResetStatistics();

using var tape = new GradientTape<float>();

// Create computation nodes for parameters
using var w1Node = GpuTensorOperations<float>.Variable(w1, context, "W1", requiresGradient: true);
using var b1Node = GpuTensorOperations<float>.Variable(b1, context, "b1", requiresGradient: true);
using var w2Node = GpuTensorOperations<float>.Variable(w2, context, "W2", requiresGradient: true);
using var b2Node = GpuTensorOperations<float>.Variable(b2, context, "b2", requiresGradient: true);

// Create computation node for input
using var inputNode = GpuTensorOperations<float>.Constant(inputBatch, context, "input");

// Watch parameters (we want to compute gradients for these)
tape.Watch(w1Node);
tape.Watch(b1Node);
tape.Watch(w2Node);
tape.Watch(b2Node);

// ===== FORWARD PASS =====

// Layer 1: hidden = ReLU(input · W1 + b1)
using var layer1Matmul = GpuTensorOperations<float>.MatMul(inputNode, w1Node, context);
using var layer1PreActivation = GpuTensorOperations<float>.Add(layer1Matmul, b1Node, context);
using var hidden = GpuTensorOperations<float>.ReLU(layer1PreActivation, context);

// Layer 2: output = hidden · W2 + b2
using var layer2Matmul = GpuTensorOperations<float>.MatMul(hidden, w2Node, context);
using var output = GpuTensorOperations<float>.Add(layer2Matmul, b2Node, context);

// Compute loss (simplified MSE for demonstration)
using var targetNode = GpuTensorOperations<float>.Constant(targetBatch, context, "target");
using var error = GpuTensorOperations<float>.Subtract(output, targetNode, context);
using var loss = GpuTensorOperations<float>.ElementwiseMultiply(error, error, context);

// ===== BACKWARD PASS =====
var gradients = tape.Gradient(loss, new[] { w1Node, b1Node, w2Node, b2Node });

// ===== PARAMETER UPDATE =====
// Update: param = param - learningRate * gradient
if (gradients.ContainsKey(w1Node) && gradients[w1Node] != null)
{
w1 = UpdateParameter(w1, gradients[w1Node]!, learningRate);
}
if (gradients.ContainsKey(b1Node) && gradients[b1Node] != null)
{
b1 = UpdateParameter(b1, gradients[b1Node]!, learningRate);
}
if (gradients.ContainsKey(w2Node) && gradients[w2Node] != null)
{
w2 = UpdateParameter(w2, gradients[w2Node]!, learningRate);
}
if (gradients.ContainsKey(b2Node) && gradients[b2Node] != null)
{
b2 = UpdateParameter(b2, gradients[b2Node]!, learningRate);
Comment on lines +139 to +153
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.
Inefficient use of 'ContainsKey' and indexer.

Suggested change
if (gradients.ContainsKey(w1Node) && gradients[w1Node] != null)
{
w1 = UpdateParameter(w1, gradients[w1Node]!, learningRate);
}
if (gradients.ContainsKey(b1Node) && gradients[b1Node] != null)
{
b1 = UpdateParameter(b1, gradients[b1Node]!, learningRate);
}
if (gradients.ContainsKey(w2Node) && gradients[w2Node] != null)
{
w2 = UpdateParameter(w2, gradients[w2Node]!, learningRate);
}
if (gradients.ContainsKey(b2Node) && gradients[b2Node] != null)
{
b2 = UpdateParameter(b2, gradients[b2Node]!, learningRate);
if (gradients.TryGetValue(w1Node, out var gradW1) && gradW1 != null)
{
w1 = UpdateParameter(w1, gradW1!, learningRate);
}
if (gradients.TryGetValue(b1Node, out var gradB1) && gradB1 != null)
{
b1 = UpdateParameter(b1, gradB1!, learningRate);
}
if (gradients.TryGetValue(w2Node, out var gradW2) && gradW2 != null)
{
w2 = UpdateParameter(w2, gradW2!, learningRate);
}
if (gradients.TryGetValue(b2Node, out var gradB2) && gradB2 != null)
{
b2 = UpdateParameter(b2, gradB2!, learningRate);

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +153
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.
Inefficient use of 'ContainsKey' and indexer.

Suggested change
if (gradients.ContainsKey(w1Node) && gradients[w1Node] != null)
{
w1 = UpdateParameter(w1, gradients[w1Node]!, learningRate);
}
if (gradients.ContainsKey(b1Node) && gradients[b1Node] != null)
{
b1 = UpdateParameter(b1, gradients[b1Node]!, learningRate);
}
if (gradients.ContainsKey(w2Node) && gradients[w2Node] != null)
{
w2 = UpdateParameter(w2, gradients[w2Node]!, learningRate);
}
if (gradients.ContainsKey(b2Node) && gradients[b2Node] != null)
{
b2 = UpdateParameter(b2, gradients[b2Node]!, learningRate);
if (gradients.TryGetValue(w1Node, out var w1Grad) && w1Grad != null)
{
w1 = UpdateParameter(w1, w1Grad!, learningRate);
}
if (gradients.TryGetValue(b1Node, out var b1Grad) && b1Grad != null)
{
b1 = UpdateParameter(b1, b1Grad!, learningRate);
}
if (gradients.TryGetValue(w2Node, out var w2Grad) && w2Grad != null)
{
w2 = UpdateParameter(w2, w2Grad!, learningRate);
}
if (gradients.TryGetValue(b2Node, out var b2Grad) && b2Grad != null)
{
b2 = UpdateParameter(b2, b2Grad!, learningRate);

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +153
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.
Inefficient use of 'ContainsKey' and indexer.

Suggested change
if (gradients.ContainsKey(w1Node) && gradients[w1Node] != null)
{
w1 = UpdateParameter(w1, gradients[w1Node]!, learningRate);
}
if (gradients.ContainsKey(b1Node) && gradients[b1Node] != null)
{
b1 = UpdateParameter(b1, gradients[b1Node]!, learningRate);
}
if (gradients.ContainsKey(w2Node) && gradients[w2Node] != null)
{
w2 = UpdateParameter(w2, gradients[w2Node]!, learningRate);
}
if (gradients.ContainsKey(b2Node) && gradients[b2Node] != null)
{
b2 = UpdateParameter(b2, gradients[b2Node]!, learningRate);
if (gradients.TryGetValue(w1Node, out var gradW1) && gradW1 != null)
{
w1 = UpdateParameter(w1, gradW1!, learningRate);
}
if (gradients.TryGetValue(b1Node, out var gradB1) && gradB1 != null)
{
b1 = UpdateParameter(b1, gradB1!, learningRate);
}
if (gradients.TryGetValue(w2Node, out var gradW2) && gradW2 != null)
{
w2 = UpdateParameter(w2, gradW2!, learningRate);
}
if (gradients.TryGetValue(b2Node, out var gradB2) && gradB2 != null)
{
b2 = UpdateParameter(b2, gradB2!, learningRate);

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +153
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.
Inefficient use of 'ContainsKey' and indexer.

Suggested change
if (gradients.ContainsKey(w1Node) && gradients[w1Node] != null)
{
w1 = UpdateParameter(w1, gradients[w1Node]!, learningRate);
}
if (gradients.ContainsKey(b1Node) && gradients[b1Node] != null)
{
b1 = UpdateParameter(b1, gradients[b1Node]!, learningRate);
}
if (gradients.ContainsKey(w2Node) && gradients[w2Node] != null)
{
w2 = UpdateParameter(w2, gradients[w2Node]!, learningRate);
}
if (gradients.ContainsKey(b2Node) && gradients[b2Node] != null)
{
b2 = UpdateParameter(b2, gradients[b2Node]!, learningRate);
if (gradients.TryGetValue(w1Node, out var w1Grad) && w1Grad != null)
{
w1 = UpdateParameter(w1, w1Grad!, learningRate);
}
if (gradients.TryGetValue(b1Node, out var b1Grad) && b1Grad != null)
{
b1 = UpdateParameter(b1, b1Grad!, learningRate);
}
if (gradients.TryGetValue(w2Node, out var w2Grad) && w2Grad != null)
{
w2 = UpdateParameter(w2, w2Grad!, learningRate);
}
if (gradients.TryGetValue(b2Node, out var b2Grad) && b2Grad != null)
{
b2 = UpdateParameter(b2, b2Grad!, learningRate);

Copilot uses AI. Check for mistakes.
}

// Calculate average loss
float avgLoss = CalculateAverageLoss(loss.Value);

// Print epoch statistics
Console.WriteLine($"Epoch {epoch + 1}/{epochs}:");
Console.WriteLine($" Loss: {avgLoss:F6}");
Console.WriteLine($" GPU Operations: {context.Statistics.GpuOperations}");
Console.WriteLine($" CPU Operations: {context.Statistics.CpuOperations}");
Console.WriteLine($" GPU Usage: {context.Statistics.GpuPercentage:F1}%");
Console.WriteLine();
}

Console.WriteLine("Training completed!");
Console.WriteLine("\n=== Summary ===");
Console.WriteLine($"Final GPU Usage: {context.Statistics.GpuPercentage:F1}%");
Console.WriteLine($"Total Operations: {context.Statistics.TotalOperations}");

Console.WriteLine("\nGPU acceleration enabled automatic speedup for large tensor operations!");
Console.WriteLine("Matrix multiplications and large activations were accelerated on GPU,");
Console.WriteLine("while small operations remained on CPU to avoid transfer overhead.");
}

private static Tensor<float> InitializeWeights(int inputDim, int outputDim)
{
var weights = new Tensor<float>(new[] { inputDim, outputDim });
var random = new Random(42);

// Xavier initialization: scale = sqrt(2 / (inputDim + outputDim))
float scale = (float)Math.Sqrt(2.0 / (inputDim + outputDim));

for (int i = 0; i < weights.Length; i++)
{
weights[i] = (float)(random.NextDouble() * 2 - 1) * scale;
}

return weights;
}

private static Tensor<float> InitializeBias(int size)
{
var bias = new Tensor<float>(new[] { 1, size });

// Initialize biases to zero
for (int i = 0; i < bias.Length; i++)
{
bias[i] = 0.0f;
}

return bias;
}

private static Tensor<float> CreateRandomBatch(int batchSize, int features)
{
var batch = new Tensor<float>(new[] { batchSize, features });
var random = new Random(42);

for (int i = 0; i < batch.Length; i++)
{
batch[i] = (float)(random.NextDouble() * 2 - 1); // Range [-1, 1]
}

return batch;
}

private static Tensor<float> CreateRandomTargets(int batchSize, int numClasses)
{
var targets = new Tensor<float>(new[] { batchSize, numClasses });
var random = new Random(42);

// Create one-hot encoded targets
for (int i = 0; i < batchSize; i++)
{
int targetClass = random.Next(numClasses);
targets[new[] { i, targetClass }] = 1.0f;
}

return targets;
}

private static Tensor<float> UpdateParameter(Tensor<float> param, Tensor<float> gradient, float learningRate)
{
var updated = new Tensor<float>(param.Shape);

for (int i = 0; i < param.Length; i++)
{
updated[i] = param[i] - learningRate * gradient[i];
}

return updated;
}

private static float CalculateAverageLoss(Tensor<float> lossTensor)
{
float sum = 0.0f;
for (int i = 0; i < lossTensor.Length; i++)
{
sum += lossTensor[i];
}
return sum / lossTensor.Length;
}

/// <summary>
/// Entry point for running the example standalone.
/// </summary>
public static void Main(string[] args)
{
try
{
RunExample();
}
catch (Exception ex)
{
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generic catch clause.

Suggested change
{
{
// Rethrow critical exceptions
if (ex is OutOfMemoryException || ex is StackOverflowException || ex is System.Threading.ThreadAbortException)
throw;

Copilot uses AI. Check for mistakes.
Console.WriteLine($"Error: {ex.Message}");
Console.WriteLine(ex.StackTrace);
}
}
}
2 changes: 2 additions & 0 deletions src/AiDotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
<PackageReference Include="Azure.Search.Documents" Version="11.7.0" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.2.1" />
<PackageReference Include="Google.Protobuf" Version="3.28.3" />
<PackageReference Include="ILGPU" Version="1.5.1" />
<PackageReference Include="ILGPU.Algorithms" Version="1.5.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.21" />
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.20.1" />
Expand Down
Loading
Loading