Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
## Highlights

- **Pure WebGPU** — Custom WGSL kernels for ternary matrix operations (no WASM, no server)
- **Multi-language** — TypeScript (`0xbitnet`), Rust (`oxbitnet`), Python (`oxbitnet`), Java/Android (`oxbitnet-java`), C (`oxbitnet-ffi`)
- **Multi-language** — TypeScript (`0xbitnet`), Rust (`oxbitnet`), Python (`oxbitnet`), Swift (`OxBitNet`), Java/Android (`oxbitnet-java`), C (`oxbitnet-ffi`)
- **Cross-platform** — Browsers, Node.js, Deno, native apps via wgpu
- **Chat templates** — Built-in LLaMA 3 chat message formatting
- **Automatic caching** — IndexedDB (browser) / disk cache (native)
Expand Down Expand Up @@ -96,6 +96,20 @@ model.chat(
model.dispose()
```

### Swift

```swift
import OxBitNet

let model = try await BitNet.load("model.gguf")

for try await token in model.chat([.user("Hello!")], options: .init(temperature: 0.7)) {
print(token, terminator: "")
}

model.dispose()
```

### Java

```java
Expand Down Expand Up @@ -165,6 +179,7 @@ More models are planned — see [#1](https://github.com/m96-chan/0xBitNet/issues
| TypeScript / JS | [`0xbitnet`](https://www.npmjs.com/package/0xbitnet) | `npm install 0xbitnet` |
| Rust | [`oxbitnet`](https://crates.io/crates/oxbitnet) | `cargo add oxbitnet` |
| Python | [`oxbitnet`](https://pypi.org/project/oxbitnet/) | `pip install oxbitnet` |
| Swift / iOS | `OxBitNet` | Swift Package Manager (see [oxbitnet-swift](packages/rust/crates/oxbitnet-swift/)) |
| Java / Android | `oxbitnet-java` | `cargo build -p oxbitnet-java --release` |
| C / FFI | `oxbitnet-ffi` | `cargo build -p oxbitnet-ffi --release` |

Expand Down Expand Up @@ -198,6 +213,16 @@ More models are planned — see [#1](https://github.com/m96-chan/0xBitNet/issues
| `model.generate_sync(prompt)` | Generate, return full string |
| `model.dispose()` | Release all GPU resources |

### Swift

| Method | Description |
|--------|-------------|
| `BitNet.load(source, options:)` | Load a GGUF model (async) |
| `BitNet.loadSync(source, options:)` | Load a GGUF model (blocking) |
| `model.generate(prompt, options:)` | Stream tokens as `AsyncThrowingStream<String, Error>` |
| `model.chat(messages, options:)` | Chat with streaming via `AsyncThrowingStream` |
| `model.dispose()` | Release all GPU resources (also called by `deinit`) |

### Java

| Method | Description |
Expand Down Expand Up @@ -274,6 +299,17 @@ pip install oxbitnet
python packages/rust/crates/oxbitnet-python/examples/chat.py
```

### Swift CLI

Minimal Swift chat example wrapping the C FFI layer.

```bash
cd packages/rust
cargo build -p oxbitnet-ffi --release
cd crates/oxbitnet-swift
swift run -Xlinker -L../../../../target/release Chat model.gguf "Hello!"
```

### Java CLI

Minimal Java chat example using JNI bindings.
Expand Down Expand Up @@ -313,6 +349,7 @@ LD_LIBRARY_PATH=target/release ./chat model.gguf "Hello!"
│ └── crates/
│ ├── oxbitnet/ # Rust library (crates.io: oxbitnet)
│ ├── oxbitnet-python/ # Python bindings via PyO3 (PyPI: oxbitnet)
│ ├── oxbitnet-swift/ # Swift bindings via C FFI (SPM package)
│ ├── oxbitnet-java/ # Java/JNI bindings (Android-ready)
│ └── oxbitnet-ffi/ # C FFI bindings (cdylib + staticlib)
├── examples/
Expand All @@ -328,6 +365,7 @@ See [Architecture](docs/architecture.md) for data flow and internals.

- **TypeScript/JS**: Node.js 18+, a WebGPU-capable environment
- **Rust**: Rust 1.75+, a Vulkan/Metal/DX12-capable GPU
- **Swift**: Swift 5.9+, a Vulkan/Metal/DX12-capable GPU
- **Java**: JDK 17+, a Vulkan/Metal/DX12-capable GPU
- **Python**: Python 3.9+, `pip install oxbitnet`

Expand Down
53 changes: 53 additions & 0 deletions packages/rust/crates/oxbitnet-swift/Examples/Chat/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Foundation
import OxBitNet

@main
struct ChatExample {
static func main() async throws {
let args = CommandLine.arguments
guard args.count >= 2 else {
print("Usage: Chat <model-path> [prompt]")
Foundation.exit(1)
}

let modelPath = args[1]
let prompt = args.count >= 3 ? args[2] : "Hello!"

// Optional: enable logging
setLogger(minLevel: .info) { level, message in
print("[\(level)] \(message)", to: &standardError)
}

// Load model with progress
print("Loading \(modelPath)...", to: &standardError)
let model = try await BitNet.load(modelPath, options: LoadOptions(
onProgress: { p in
let pct = String(format: "%.1f", p.fraction * 100)
print(" [\(p.phase)] \(pct)%", to: &standardError)
}
))
print("Model loaded.", to: &standardError)

// Chat
let messages: [ChatMessage] = [
.user(prompt),
]

for try await token in model.chat(messages) {
print(token, terminator: "")
fflush(stdout)
}
print()

model.dispose()
}
}

/// Helper to write to stderr.
private struct StderrOutputStream: TextOutputStream {
mutating func write(_ string: String) {
FileHandle.standardError.write(Data(string.utf8))
}
}

private var standardError = StderrOutputStream()
30 changes: 30 additions & 0 deletions packages/rust/crates/oxbitnet-swift/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// swift-tools-version: 5.9

import PackageDescription

let package = Package(
name: "OxBitNet",
platforms: [
.macOS(.v13),
.iOS(.v16),
],
products: [
.library(name: "OxBitNet", targets: ["OxBitNet"]),
],
targets: [
.target(
name: "COxBitNet",
path: "Sources/COxBitNet"
),
.target(
name: "OxBitNet",
dependencies: ["COxBitNet"],
path: "Sources/OxBitNet"
),
.executableTarget(
name: "Chat",
dependencies: ["OxBitNet"],
path: "Examples/Chat"
),
]
)
133 changes: 133 additions & 0 deletions packages/rust/crates/oxbitnet-swift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# oxbitnet-swift

Swift bindings for [oxbitnet](https://crates.io/crates/oxbitnet) — run [BitNet b1.58](https://github.com/microsoft/BitNet) ternary LLMs with GPU acceleration (wgpu).

Part of [0xBitNet](https://github.com/m96-chan/0xBitNet).

## Build

First, build the native library:

```bash
cargo build -p oxbitnet-ffi --release
```

Produces `target/release/liboxbitnet_ffi.so` (Linux) / `.dylib` (macOS) / `oxbitnet_ffi.dll` (Windows).

Then build the Swift package:

```bash
cd packages/rust/crates/oxbitnet-swift
swift build -Xlinker -L../../../../target/release
```

## Quick Start

```swift
import OxBitNet

let model = try await BitNet.load("model.gguf")

// Raw prompt
for try await token in model.generate("Hello!") {
print(token, terminator: "")
}

// Chat messages
for try await token in model.chat([.user("Hello!")], options: .init(temperature: 0.7)) {
print(token, terminator: "")
}

model.dispose()
```

## API

### Loading

```swift
// Async (recommended)
let model = try await BitNet.load("model.gguf")

// With progress
let model = try await BitNet.load("model.gguf", options: LoadOptions(
onProgress: { p in
print("[\(p.phase)] \(String(format: "%.1f", p.fraction * 100))%")
}
))

// Sync (blocks calling thread)
let model = try BitNet.loadSync("model.gguf")
```

### Generation

```swift
// Raw prompt — returns AsyncThrowingStream<String, Error>
for try await token in model.generate("Once upon a time") {
print(token, terminator: "")
}

// With options
for try await token in model.generate("Hello!", options: GenerateOptions(
maxTokens: 512,
temperature: 0.7,
topK: 40
)) {
print(token, terminator: "")
}
```

### Chat

```swift
let messages: [ChatMessage] = [
.system("You are a helpful assistant."),
.user("What is 2+2?"),
]

for try await token in model.chat(messages) {
print(token, terminator: "")
}
```

### Cleanup

`BitNet` calls `dispose()` automatically in its `deinit`, but you can call it explicitly:

```swift
model.dispose()
```

### Logging

```swift
setLogger(minLevel: .info) { level, message in
print("[\(level)] \(message)")
}
```

Must be called before `BitNet.load`. Can only be called once.

## Generation Options

| Field | Default | Description |
|-------|---------|-------------|
| `maxTokens` | 256 | Maximum tokens to generate |
| `temperature` | 1.0 | Sampling temperature |
| `topK` | 50 | Top-k sampling |
| `repeatPenalty` | 1.1 | Repetition penalty |
| `repeatLastN` | 64 | Window for repetition penalty |

## Running the Example

```bash
cd packages/rust
cargo build -p oxbitnet-ffi --release
cd crates/oxbitnet-swift
swift run -Xlinker -L../../../../target/release Chat model.gguf "Hello!"
```

## License

MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module COxBitNet {
header "oxbitnet.h"
export *
}
Loading