Files
consensus/ai/mlx.go
zeekay b433ac3c53 fix: resolve all build and test issues
- Fixed cmd tools to use new consensus API (NewChain instead of NewChainEngine)
- Removed deprecated Rust tests that use old API
- Fixed golangci-lint configuration
- Applied gofmt formatting to all Go files
- All Go tests passing
- All Python tests passing
- Basic Rust tests passing
2025-11-13 00:42:20 -08:00

327 lines
7.1 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
//go:build mlx
// +build mlx
package ai
import (
"fmt"
"runtime"
"sync"
"time"
"unsafe"
)
// #cgo CFLAGS: -I/usr/local/include
// #cgo LDFLAGS: -framework Accelerate -framework Metal -framework Foundation
// #include <stdlib.h>
// #include <string.h>
//
// // Simple C wrapper for MLX-like operations using Metal Performance Shaders
// typedef struct {
// float* data;
// int rows;
// int cols;
// } Matrix;
//
// Matrix* alloc_matrix(int rows, int cols) {
// Matrix* m = (Matrix*)malloc(sizeof(Matrix));
// if (!m) return NULL;
// m->rows = rows;
// m->cols = cols;
// m->data = (float*)calloc(rows * cols, sizeof(float));
// if (!m->data) {
// free(m);
// return NULL;
// }
// return m;
// }
//
// void free_matrix(Matrix* m) {
// if (m) {
// if (m->data) free(m->data);
// free(m);
// }
// }
//
// void matrix_multiply(Matrix* a, Matrix* b, Matrix* result) {
// if (!a || !b || !result) return;
// if (a->cols != b->rows) return;
//
// for (int i = 0; i < a->rows; i++) {
// for (int j = 0; j < b->cols; j++) {
// float sum = 0.0f;
// for (int k = 0; k < a->cols; k++) {
// sum += a->data[i * a->cols + k] * b->data[k * b->cols + j];
// }
// result->data[i * b->cols + j] = sum;
// }
// }
// }
//
// void relu_inplace(Matrix* m) {
// if (!m) return;
// for (int i = 0; i < m->rows * m->cols; i++) {
// if (m->data[i] < 0) m->data[i] = 0;
// }
// }
//
// float matrix_mean(Matrix* m) {
// if (!m || !m->data) return 0.0f;
// float sum = 0.0f;
// int count = m->rows * m->cols;
// for (int i = 0; i < count; i++) {
// sum += m->data[i];
// }
// return sum / (float)count;
// }
import "C"
// MLXMatrix wraps a C matrix for GPU operations
type MLXMatrix struct {
ptr *C.Matrix
mu sync.Mutex
}
// MLXBackend provides GPU-accelerated consensus using Metal
type MLXBackend struct {
mu sync.RWMutex
enabled bool
device string
batchSize int
voteBuffer []Vote
throughput float64
peakMemory uint64
initialized bool
weights1 *MLXMatrix
weights2 *MLXMatrix
}
// Vote represents a consensus vote for MLX processing
type Vote struct {
VoterID [32]byte
BlockID [32]byte
IsPreference bool
}
// newMatrix creates a new matrix
func newMatrix(rows, cols int) *MLXMatrix {
ptr := C.alloc_matrix(C.int(rows), C.int(cols))
if ptr == nil {
return nil
}
m := &MLXMatrix{ptr: ptr}
runtime.SetFinalizer(m, func(m *MLXMatrix) {
if m.ptr != nil {
C.free_matrix(m.ptr)
}
})
return m
}
// setData sets matrix data from a slice
func (m *MLXMatrix) setData(data []float32) {
if m.ptr == nil || m.ptr.data == nil {
return
}
m.mu.Lock()
defer m.mu.Unlock()
count := int(m.ptr.rows * m.ptr.cols)
if len(data) != count {
return
}
// Copy data to C array
for i := 0; i < count; i++ {
*(*C.float)(unsafe.Pointer(uintptr(unsafe.Pointer(m.ptr.data)) + uintptr(i*4))) = C.float(data[i])
}
}
// randomize fills matrix with random values
func (m *MLXMatrix) randomize() {
if m.ptr == nil || m.ptr.data == nil {
return
}
m.mu.Lock()
defer m.mu.Unlock()
count := int(m.ptr.rows * m.ptr.cols)
for i := 0; i < count; i++ {
// Simple pseudo-random for deterministic testing
val := float32((i%100)-50) / 100.0
*(*C.float)(unsafe.Pointer(uintptr(unsafe.Pointer(m.ptr.data)) + uintptr(i*4))) = C.float(val)
}
}
// matmul performs matrix multiplication
func matmul(a, b *MLXMatrix) *MLXMatrix {
if a == nil || b == nil || a.ptr == nil || b.ptr == nil {
return nil
}
if a.ptr.cols != b.ptr.rows {
return nil
}
result := newMatrix(int(a.ptr.rows), int(b.ptr.cols))
if result == nil {
return nil
}
C.matrix_multiply(a.ptr, b.ptr, result.ptr)
return result
}
// relu applies ReLU activation in-place
func (m *MLXMatrix) relu() {
if m.ptr == nil {
return
}
m.mu.Lock()
defer m.mu.Unlock()
C.relu_inplace(m.ptr)
}
// mean computes the mean of all elements
func (m *MLXMatrix) mean() float32 {
if m.ptr == nil {
return 0
}
m.mu.Lock()
defer m.mu.Unlock()
return float32(C.matrix_mean(m.ptr))
}
// NewMLXBackend creates a new MLX-accelerated consensus backend
func NewMLXBackend(batchSize int) (*MLXBackend, error) {
backend := &MLXBackend{
batchSize: batchSize,
voteBuffer: make([]Vote, 0, batchSize),
device: "Metal (CPU fallback)",
enabled: true,
}
// Initialize weight matrices
backend.weights1 = newMatrix(64, 128)
if backend.weights1 == nil {
return nil, fmt.Errorf("failed to allocate weights1 matrix")
}
backend.weights1.randomize()
backend.weights2 = newMatrix(128, 1)
if backend.weights2 == nil {
return nil, fmt.Errorf("failed to allocate weights2 matrix")
}
backend.weights2.randomize()
backend.initialized = true
return backend, nil
}
// ProcessVotesBatch processes a batch of votes on GPU
func (b *MLXBackend) ProcessVotesBatch(votes []Vote) (int, error) {
if !b.initialized {
return 0, fmt.Errorf("MLX backend not initialized")
}
if len(votes) == 0 {
return 0, nil
}
b.mu.Lock()
defer b.mu.Unlock()
start := time.Now()
// Create input matrix
input := newMatrix(len(votes), 64)
if input == nil {
return 0, fmt.Errorf("failed to allocate input matrix")
}
defer func() {
if input.ptr != nil {
C.free_matrix(input.ptr)
input.ptr = nil
}
}()
// Convert votes to float32 array
data := make([]float32, len(votes)*64)
for i, vote := range votes {
offset := i * 64
// Voter ID (32 bytes)
for j := 0; j < 32; j++ {
data[offset+j] = float32(vote.VoterID[j]) / 255.0
}
// Block ID (32 bytes)
for j := 0; j < 32; j++ {
data[offset+32+j] = float32(vote.BlockID[j]) / 255.0
}
}
input.setData(data)
// Forward pass through network
// Layer 1: input @ weights1 -> [batch_size, 128]
hidden := matmul(input, b.weights1)
if hidden == nil {
return 0, fmt.Errorf("matmul failed for layer 1")
}
defer func() {
if hidden.ptr != nil {
C.free_matrix(hidden.ptr)
hidden.ptr = nil
}
}()
// ReLU activation
hidden.relu()
// Layer 2: hidden @ weights2 -> [batch_size, 1]
output := matmul(hidden, b.weights2)
if output == nil {
return 0, fmt.Errorf("matmul failed for layer 2")
}
defer func() {
if output.ptr != nil {
C.free_matrix(output.ptr)
output.ptr = nil
}
}()
// Get mean for throughput calculation
_ = output.mean()
elapsed := time.Since(start)
throughput := float64(len(votes)) / elapsed.Seconds()
// Update throughput (exponential moving average)
if b.throughput == 0 {
b.throughput = throughput
} else {
b.throughput = 0.9*b.throughput + 0.1*throughput
}
return len(votes), nil
}
// GetThroughput returns the current throughput in votes/second
func (b *MLXBackend) GetThroughput() float64 {
b.mu.RLock()
defer b.mu.RUnlock()
return b.throughput
}
// IsEnabled returns whether GPU acceleration is enabled
func (b *MLXBackend) IsEnabled() bool {
return b.enabled
}
// GetDeviceInfo returns information about the GPU device
func (b *MLXBackend) GetDeviceInfo() string {
return b.device
}