mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
- 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
327 lines
7.1 KiB
Go
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
|
|
}
|