1. Error handler thread safety: last_mlx_error now uses _Atomic(const char*) with atomic_store_explicit/atomic_exchange_explicit (release/acquire). 2. macOS version minimum: -mmacosx-version-min changed from 26.0 to 13.3 (MLX's own minimum), no longer locks out macOS 14/15 users. 3. LoadOption applied in metalBackend.LoadModel(): calls ApplyLoadOpts(), passes ContextLen through to Model which replaces unbounded KVCache with RotatingKVCache when set. GPULayers=0 logs a warning. Co-Authored-By: Virgil <virgil@lethean.io> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
//go:build darwin && arm64
|
|
|
|
package metal
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestEval_Success(t *testing.T) {
|
|
a := FromValues([]float32{1, 2, 3}, 3)
|
|
b := FromValues([]float32{4, 5, 6}, 3)
|
|
c := Add(a, b)
|
|
|
|
if err := Eval(c); err != nil {
|
|
t.Fatalf("Eval should succeed: %v", err)
|
|
}
|
|
|
|
got := c.Floats()
|
|
want := []float32{5, 7, 9}
|
|
for i := range got {
|
|
if got[i] != want[i] {
|
|
t.Errorf("got[%d] = %f, want %f", i, got[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEval_NilArray(t *testing.T) {
|
|
// Eval should handle nil arrays gracefully.
|
|
if err := Eval(nil); err != nil {
|
|
t.Fatalf("Eval(nil) should not error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLastError_NoError(t *testing.T) {
|
|
// When no error has occurred, lastError should return nil.
|
|
if err := lastError(); err != nil {
|
|
t.Errorf("lastError should be nil when no error occurred, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestNewCaches_ContextLen(t *testing.T) {
|
|
// When contextLen is set, unbounded KVCaches should become RotatingKVCaches.
|
|
m := &Model{
|
|
model: &fakeModel{numLayers: 4},
|
|
}
|
|
|
|
// Without contextLen — should get plain KVCaches.
|
|
caches := m.newCaches()
|
|
for i, c := range caches {
|
|
if _, ok := c.(*KVCache); !ok {
|
|
t.Errorf("cache[%d] without contextLen: got %T, want *KVCache", i, c)
|
|
}
|
|
}
|
|
|
|
// With contextLen — should get RotatingKVCaches.
|
|
m.contextLen = 2048
|
|
caches = m.newCaches()
|
|
for i, c := range caches {
|
|
if _, ok := c.(*RotatingKVCache); !ok {
|
|
t.Errorf("cache[%d] with contextLen=2048: got %T, want *RotatingKVCache", i, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
// fakeModel is a minimal InternalModel for testing cache creation.
|
|
type fakeModel struct {
|
|
numLayers int
|
|
}
|
|
|
|
func (f *fakeModel) Forward(_ *Array, _ []Cache) *Array { return nil }
|
|
func (f *fakeModel) NewCache() []Cache {
|
|
caches := make([]Cache, f.numLayers)
|
|
for i := range caches {
|
|
caches[i] = NewKVCache()
|
|
}
|
|
return caches
|
|
}
|
|
func (f *fakeModel) NumLayers() int { return f.numLayers }
|
|
func (f *fakeModel) Tokenizer() *Tokenizer { return nil }
|
|
func (f *fakeModel) ModelType() string { return "fake" }
|
|
func (f *fakeModel) ApplyLoRA(_ LoRAConfig) *LoRAAdapter { return nil }
|
|
|
|
func TestLoadAllSafetensors_MissingFile(t *testing.T) {
|
|
_, err := LoadAllSafetensors("/nonexistent/path/model.safetensors")
|
|
if err == nil {
|
|
t.Fatal("LoadAllSafetensors should fail for missing file")
|
|
}
|
|
}
|