Skip to content

Commit 8b54448

Browse files
committed
nuget
1 parent c2c071f commit 8b54448

File tree

4 files changed

+160
-159
lines changed

4 files changed

+160
-159
lines changed

.github/workflows/ci.yml

Lines changed: 68 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -257,71 +257,71 @@ jobs:
257257
echo "version=$VERSION" >> $GITHUB_OUTPUT
258258
echo "Extracted version: $VERSION"
259259
260-
# - name: Check if tag exists
261-
# id: check_tag
262-
# run: |
263-
# if git ls-remote --tags origin | grep -q "refs/tags/v${{ steps.version.outputs.version }}"; then
264-
# echo "exists=true" >> $GITHUB_OUTPUT
265-
# echo "Tag v${{ steps.version.outputs.version }} already exists"
266-
# else
267-
# echo "exists=false" >> $GITHUB_OUTPUT
268-
# echo "Tag v${{ steps.version.outputs.version }} does not exist"
269-
# fi
270-
271-
# - name: Download package artifacts
272-
# if: steps.check_tag.outputs.exists == 'false'
273-
# uses: actions/download-artifact@v4
274-
# with:
275-
# name: packages
276-
# path: artifacts/packages
277-
278-
# - name: Publish to NuGet
279-
# if: steps.check_tag.outputs.exists == 'false'
280-
# run: |
281-
# for package in artifacts/packages/*.nupkg; do
282-
# echo "Publishing $package..."
283-
# dotnet nuget push "$package" \
284-
# --api-key ${{ secrets.NUGET_API_KEY }} \
285-
# --source https://api.nuget.org/v3/index.json \
286-
# --skip-duplicate || true
287-
# done
288-
289-
# - name: Create Git tag
290-
# if: steps.check_tag.outputs.exists == 'false'
291-
# run: |
292-
# git config user.name "github-actions[bot]"
293-
# git config user.email "github-actions[bot]@users.noreply.github.com"
294-
# git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}"
295-
# git push origin "v${{ steps.version.outputs.version }}"
296-
297-
# - name: Generate release notes
298-
# if: steps.check_tag.outputs.exists == 'false'
299-
# id: release_notes
300-
# run: |
301-
# PREVIOUS_TAG=$(git describe --abbrev=0 --tags $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "")
302-
# if [ -z "$PREVIOUS_TAG" ]; then
303-
# COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse)
304-
# else
305-
# COMMITS=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"- %s (%h)" --reverse)
306-
# fi
307-
308-
# echo "## What's Changed" > release_notes.md
309-
# echo "" >> release_notes.md
310-
# echo "$COMMITS" >> release_notes.md
311-
# echo "" >> release_notes.md
312-
# echo "## NuGet Packages" >> release_notes.md
313-
# echo "- [MLXSharp v${{ steps.version.outputs.version }}](https://www.nuget.org/packages/MLXSharp/${{ steps.version.outputs.version }})" >> release_notes.md
314-
# echo "- [MLXSharp.SemanticKernel v${{ steps.version.outputs.version }}](https://www.nuget.org/packages/MLXSharp.SemanticKernel/${{ steps.version.outputs.version }})" >> release_notes.md
315-
316-
# cat release_notes.md
317-
318-
# - name: Create GitHub Release
319-
# if: steps.check_tag.outputs.exists == 'false'
320-
# uses: softprops/action-gh-release@v1
321-
# with:
322-
# tag_name: v${{ steps.version.outputs.version }}
323-
# name: Release v${{ steps.version.outputs.version }}
324-
# body_path: release_notes.md
325-
# files: artifacts/packages/*
326-
# env:
327-
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
260+
- name: Check if tag exists
261+
id: check_tag
262+
run: |
263+
if git ls-remote --tags origin | grep -q "refs/tags/v${{ steps.version.outputs.version }}"; then
264+
echo "exists=true" >> $GITHUB_OUTPUT
265+
echo "Tag v${{ steps.version.outputs.version }} already exists"
266+
else
267+
echo "exists=false" >> $GITHUB_OUTPUT
268+
echo "Tag v${{ steps.version.outputs.version }} does not exist"
269+
fi
270+
271+
- name: Download package artifacts
272+
if: steps.check_tag.outputs.exists == 'false'
273+
uses: actions/download-artifact@v4
274+
with:
275+
name: packages
276+
path: artifacts/packages
277+
278+
- name: Publish to NuGet
279+
if: steps.check_tag.outputs.exists == 'false'
280+
run: |
281+
for package in artifacts/packages/*.nupkg; do
282+
echo "Publishing $package..."
283+
dotnet nuget push "$package" \
284+
--api-key ${{ secrets.NUGET_API_KEY }} \
285+
--source https://api.nuget.org/v3/index.json \
286+
--skip-duplicate || true
287+
done
288+
289+
- name: Create Git tag
290+
if: steps.check_tag.outputs.exists == 'false'
291+
run: |
292+
git config user.name "github-actions[bot]"
293+
git config user.email "github-actions[bot]@users.noreply.github.com"
294+
git tag -a "v${{ steps.version.outputs.version }}" -m "Release v${{ steps.version.outputs.version }}"
295+
git push origin "v${{ steps.version.outputs.version }}"
296+
297+
- name: Generate release notes
298+
if: steps.check_tag.outputs.exists == 'false'
299+
id: release_notes
300+
run: |
301+
PREVIOUS_TAG=$(git describe --abbrev=0 --tags $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null || echo "")
302+
if [ -z "$PREVIOUS_TAG" ]; then
303+
COMMITS=$(git log --pretty=format:"- %s (%h)" --reverse)
304+
else
305+
COMMITS=$(git log ${PREVIOUS_TAG}..HEAD --pretty=format:"- %s (%h)" --reverse)
306+
fi
307+
308+
echo "## What's Changed" > release_notes.md
309+
echo "" >> release_notes.md
310+
echo "$COMMITS" >> release_notes.md
311+
echo "" >> release_notes.md
312+
echo "## NuGet Packages" >> release_notes.md
313+
echo "- [MLXSharp v${{ steps.version.outputs.version }}](https://www.nuget.org/packages/MLXSharp/${{ steps.version.outputs.version }})" >> release_notes.md
314+
echo "- [MLXSharp.SemanticKernel v${{ steps.version.outputs.version }}](https://www.nuget.org/packages/MLXSharp.SemanticKernel/${{ steps.version.outputs.version }})" >> release_notes.md
315+
316+
cat release_notes.md
317+
318+
- name: Create GitHub Release
319+
if: steps.check_tag.outputs.exists == 'false'
320+
uses: softprops/action-gh-release@v1
321+
with:
322+
tag_name: v${{ steps.version.outputs.version }}
323+
name: Release v${{ steps.version.outputs.version }}
324+
body_path: release_notes.md
325+
files: artifacts/packages/*
326+
env:
327+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 74 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,27 @@
11
# MLXSharp
22

3-
MLXSharp offers .NET bindings and tooling around [Apple MLX](https://github.com/ml-explore/mlx) that plug directly into the [`Microsoft.Extensions.AI`](https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai) ecosystem.
4-
The design mirrors the packaging approach from projects such as [LLamaSharp](https://github.com/SciSharp/LLamaSharp): managed clients sit on top of a native wrapper that can be distributed through NuGet alongside prebuilt binaries.
3+
[![NuGet](https://img.shields.io/nuget/v/ManagedCode.MLXSharp.svg)](https://www.nuget.org/packages/ManagedCode.MLXSharp)
4+
[![NuGet](https://img.shields.io/nuget/v/ManagedCode.MLXSharp.SemanticKernel.svg)](https://www.nuget.org/packages/ManagedCode.MLXSharp.SemanticKernel)
55

6-
## Features
6+
MLXSharp is the first .NET wrapper around [Apple MLX](https://github.com/ml-explore/mlx) that plugs straight into [`Microsoft.Extensions.AI`](https://learn.microsoft.com/dotnet/ai/microsoft-extensions-ai). It is designed and tested on macOS (Apple silicon) but ships prebuilt native binaries for both macOS and Linux so that NuGet consumers can run out-of-the-box.
77

8-
- `IChatClient`, `IEmbeddingGenerator<string, Embedding<float)>`, and image generation helpers that adhere to the `Microsoft.Extensions.AI` abstractions.
9-
- Builder-based backend configuration with a deterministic managed implementation for tests and a P/Invoke powered native backend.
10-
- Native library resolver that probes application directories, `MLXSHARP_LIBRARY`, or packaged runtimes and loads `libmlxsharp` on demand.
11-
- Dependency injection extensions (`AddMlx`) in **MLXSharp** package.
12-
- Semantic Kernel integration (`AddMlxChatCompletion`) in separate **MLXSharp.SemanticKernel** package.
13-
- Integration test suite that exercises chat, embedding, image, and Semantic Kernel flows against both managed and native backends.
8+
## Highlights
9+
- .NET 9 / C# 13 preview friendly APIs that implement `IChatClient`, `IEmbeddingGenerator<string, Embedding<float>>`, and image helpers.
10+
- Dependency Injection extensions (`AddMlx`) and Semantic Kernel integration (`AddMlxChatCompletion`).
11+
- Native runtime loader that understands `MLXSHARP_LIBRARY`, custom paths, and packaged runtimes.
12+
- CI pipeline that builds the managed code once, produces macOS and Linux native libraries in parallel, and packs everything into distributable NuGet packages.
1413

15-
## Repository Layout
16-
17-
```
18-
├── extern/mlx # Git submodule with the official MLX sources
19-
├── native/ # Native wrapper scaffold (CMake project)
20-
├── src/MLXSharp/ # Managed library with Microsoft.Extensions.AI adapters + packaged runtimes
21-
├── src/MLXSharp.SemanticKernel/ # Semantic Kernel integration (separate package)
22-
└── src/MLXSharp.Tests/ # Integration tests covering DI and Semantic Kernel
23-
```
24-
25-
## Prerequisites
26-
27-
The solution targets .NET 9 / C# 13 previews. Install the SDK via the official installer script:
14+
## Quick Start
15+
Add the package and wire it into the service collection:
2816

2917
```bash
30-
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version 9.0.305
31-
export PATH="$PATH:$HOME/.dotnet"
32-
dotnet --version
18+
dotnet add package ManagedCode.MLXSharp
3319
```
3420

35-
## Getting Started
36-
37-
Register MLX services through dependency injection:
38-
3921
```csharp
22+
using Microsoft.Extensions.DependencyInjection;
23+
using MLXSharp;
24+
4025
var services = new ServiceCollection();
4126
services.AddMlx(builder =>
4227
{
@@ -46,87 +31,86 @@ services.AddMlx(builder =>
4631
options.EmbeddingModelId = "mlx-embedding";
4732
});
4833

49-
// Managed fallback backend (default) that keeps tests deterministic.
5034
builder.UseManagedBackend(new MlxManagedBackend());
51-
52-
// Or switch to the native backend once libmlxsharp is available.
35+
// Switch to the native backend when libmlxsharp.{dylib|so} is available.
5336
// builder.UseNativeBackend();
5437
});
38+
39+
var provider = services.BuildServiceProvider();
40+
var chat = provider.GetRequiredService<IChatClient>();
41+
var reply = await chat.GetResponseAsync("hello MLX", CancellationToken.None);
5542
```
5643

57-
Semantic Kernel integration uses the same builder experience:
44+
### Semantic Kernel
45+
46+
```bash
47+
dotnet add package ManagedCode.MLXSharp.SemanticKernel
48+
```
5849

5950
```csharp
51+
using Microsoft.SemanticKernel;
52+
using MLXSharp.SemanticKernel;
53+
6054
var kernelBuilder = Kernel.CreateBuilder();
6155
kernelBuilder.AddMlxChatCompletion(b => b.UseManagedBackend(new MlxManagedBackend()));
6256
var kernel = kernelBuilder.Build();
6357

64-
var chat = kernel.Services.GetRequiredService<IChatCompletionService>();
6558
var history = new ChatHistory();
6659
history.AddUserMessage("Summarise MLX in one sentence");
67-
var response = await chat.GetChatMessageContentsAsync(history, new PromptExecutionSettings(), kernel, CancellationToken.None);
60+
var response = await kernel
61+
.GetRequiredService<IChatCompletionService>()
62+
.GetChatMessageContentsAsync(history, new PromptExecutionSettings(), kernel, CancellationToken.None);
6863
```
6964

70-
## NuGet Packages
71-
72-
### MLXSharp [![NuGet](https://img.shields.io/nuget/v/MLXSharp.svg)](https://www.nuget.org/packages/MLXSharp)
73-
74-
Core package with `Microsoft.Extensions.AI` integration:
75-
76-
```bash
77-
dotnet add package MLXSharp
65+
## Repository Layout
7866
```
79-
80-
This package contains:
81-
- Managed DLL with `Microsoft.Extensions.AI` implementations
82-
- Native assets in `runtimes/{rid}/native/`:
83-
- `runtimes/linux-x64/native/libmlxsharp.so.b64` - Base64-encoded stub that `MlxNativeLibrary` expands for CI/testing
84-
- `runtimes/osx-arm64/native/libmlxsharp.dylib` - built in CI on macOS
85-
86-
`MlxNativeLibrary` materialises `libmlxsharp.so` from the encoded payload on first use so Git history stays free of binary blobs while tests retain deterministic behaviour.
87-
88-
### MLXSharp.SemanticKernel [![NuGet](https://img.shields.io/nuget/v/MLXSharp.SemanticKernel.svg)](https://www.nuget.org/packages/MLXSharp.SemanticKernel)
89-
90-
Semantic Kernel integration:
91-
92-
```bash
93-
dotnet add package MLXSharp.SemanticKernel
67+
extern/mlx/ # MLX sources (git submodule)
68+
native/ # CMake project that builds libmlxsharp
69+
src/MLXSharp/ # Managed MLXSharp library + runtime assets
70+
src/MLXSharp.SemanticKernel/ # Semantic Kernel integration
71+
src/MLXSharp.Tests/ # Integration tests
9472
```
9573

96-
This package depends on MLXSharp and adds Semantic Kernel chat completion service.
97-
98-
### How It Works
99-
100-
GitHub Actions automatically:
101-
1. Compiles native wrapper with MLX submodule
102-
2. Copies `libmlxsharp.dylib` to `src/MLXSharp/runtimes/osx-arm64/native/`
103-
3. Packs managed + native together in NuGet package
104-
105-
At runtime `MlxNativeLibrary` automatically finds the right library:
106-
- Checks `MlxClientOptions.LibraryPath`
107-
- Checks `MLXSHARP_LIBRARY` environment variable
108-
- Searches in `runtimes/{rid}/native/` (NuGet unpacks automatically)
109-
- Fallback to system library search paths
110-
111-
To build the native wrapper locally (mirroring the LLamaSharp workflow):
112-
113-
```bash
114-
git submodule update --init --recursive
115-
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release
116-
cmake --build native/build
117-
export MLXSHARP_LIBRARY=$(pwd)/native/build/libmlxsharp.dylib
118-
```
119-
120-
The CMake project vendors the official [mlx](https://github.com/ml-explore/mlx) sources as a submodule and
121-
builds them alongside `libmlxsharp`. On non-macOS hosts the configuration automatically disables the Metal backend;
122-
pass `-DMLX_BUILD_METAL=ON` if you are compiling on Apple silicon and want GPU support. Additional MLX switches can be
123-
forwarded on the command line, for example `-DMLX_BUILD_BLAS_FROM_SOURCE=ON` when targeting systems without an OpenBLAS
124-
installation.
125-
126-
## Running Tests
74+
## Building the native wrapper locally
75+
1. Install .NET 9, CMake, and ensure Xcode command-line tools (macOS) or build-essential + OpenBLAS/LAPACK headers (Linux) are available.
76+
2. Sync submodules:
77+
```bash
78+
git submodule update --init --recursive
79+
```
80+
3. Build for your platform:
81+
```bash
82+
# macOS (Apple silicon)
83+
cmake -S native -B native/build/macos -DCMAKE_BUILD_TYPE=Release
84+
cmake --build native/build/macos
85+
export MLXSHARP_LIBRARY=$(pwd)/native/build/macos/libmlxsharp.dylib
86+
87+
# Linux
88+
cmake -S native -B native/build/linux -DCMAKE_BUILD_TYPE=Release
89+
cmake --build native/build/linux
90+
export MLXSHARP_LIBRARY=$(pwd)/native/build/linux/libmlxsharp.so
91+
```
92+
4. Run your application or `dotnet pack` with explicit paths:
93+
```bash
94+
dotnet pack src/MLXSharp/MLXSharp.csproj \
95+
-p:MLXSharpMacNativeBinary=$PWD/native/build/macos/libmlxsharp.dylib \
96+
-p:MLXSharpLinuxNativeBinary=$PWD/native/build/linux/libmlxsharp.so
97+
```
98+
99+
The CMake project vendored from MLX builds MLX and the shim in one go. macOS builds enable Metal automatically; disable or tweak MLX options by passing flags such as `-DMLX_BUILD_METAL=OFF` or `-DMLX_BUILD_BLAS_FROM_SOURCE=ON`.
100+
101+
## CI overview
102+
1. `dotnet-build` (Ubuntu): restores the solution and compiles managed projects.
103+
2. `native-linux` / `native-macos`: compile `libmlxsharp.so` and `libmlxsharp.dylib` in parallel.
104+
3. `package-test` (macOS): downloads both native artifacts, stages them into `src/MLXSharp/runtimes/{rid}/native`, rebuilds, runs the integration tests, and produces NuGet packages.
105+
106+
## Testing
107+
Tests require a local MLX model bundle. Point `MLXSHARP_MODEL_PATH` to the directory before running:
127108

128109
```bash
110+
export MLXSHARP_MODEL_PATH=$PWD/models/Qwen1.5-0.5B-Chat-4bit
111+
huggingface-cli download mlx-community/Qwen1.5-0.5B-Chat-4bit --local-dir "$MLXSHARP_MODEL_PATH"
129112
dotnet test
130113
```
131114

132-
The suite validates the managed backend, Semantic Kernel extensions, and the native loader by exercising the packaged stub library.
115+
## Versioning & platform support
116+
This initial release is focused on macOS developers who want MLX inside .NET applications. Linux binaries are produced to keep NuGet packages complete, and Windows support is not yet available.

src/MLXSharp.SemanticKernel/MLXSharp.SemanticKernel.csproj

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,13 @@
66
<ItemGroup>
77
<ProjectReference Include="..\MLXSharp\MLXSharp.csproj" />
88
</ItemGroup>
9-
</Project>
9+
10+
<!-- NuGet metadata -->
11+
<PropertyGroup>
12+
<Title>ManagedCode.MLXSharp.SemanticKernel</Title>
13+
<PackageId>ManagedCode.MLXSharp.SemanticKernel</PackageId>
14+
<Description>Semantic Kernel chat completion integration built on top of MLXSharp.</Description>
15+
<PackageTags>managedcode;mlx;semantic-kernel;ai;dotnet;chat;apple</PackageTags>
16+
</PropertyGroup>
17+
18+
</Project>

src/MLXSharp/MLXSharp.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
44
</PropertyGroup>
55

6+
<!-- NuGet metadata -->
7+
<PropertyGroup>
8+
<Title>ManagedCode.MLXSharp</Title>
9+
<PackageId>ManagedCode.MLXSharp</PackageId>
10+
<Description>.NET bindings for Apple MLX with Microsoft.Extensions.AI integration and packaged native runtimes.</Description>
11+
<PackageTags>managedcode;mlx;apple;ai;dotnet;microsoft-extensions-ai;native</PackageTags>
12+
</PropertyGroup>
13+
614
<ItemGroup>
715
<PackageReference Include="Microsoft.Extensions.AI" Version="9.9.1" />
816
</ItemGroup>

0 commit comments

Comments
 (0)