CacheLimit (8GB) and MemoryLimit (16GB) in DistillConfig control mlx.SetCacheLimit/SetMemoryLimit before model load. Conservative defaults for 1B model on 96GB machine. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
154 lines
4.5 KiB
Go
154 lines
4.5 KiB
Go
package lem
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// AIConfig is the top-level .core/ai/ai.yaml configuration.
|
|
type AIConfig struct {
|
|
Version int `yaml:"version"`
|
|
Backend string `yaml:"backend"`
|
|
Scorer ScorerConfig `yaml:"scorer"`
|
|
Generate GenerateConfig `yaml:"generate"`
|
|
Distill DistillConfig `yaml:"distill"`
|
|
}
|
|
|
|
// ScorerConfig controls quality gating.
|
|
type ScorerConfig struct {
|
|
Engine string `yaml:"engine"`
|
|
MinScore float64 `yaml:"min_score"`
|
|
Delta bool `yaml:"delta"`
|
|
SycophancyEcho float64 `yaml:"sycophancy_echo"`
|
|
SycophancyUplift float64 `yaml:"sycophancy_uplift"`
|
|
}
|
|
|
|
// GenerateConfig holds default inference parameters.
|
|
type GenerateConfig struct {
|
|
MaxTokens int `yaml:"max_tokens"`
|
|
Temperature float64 `yaml:"temperature"`
|
|
TopP float64 `yaml:"top_p"`
|
|
TopK int `yaml:"top_k"`
|
|
RepeatPenalty float64 `yaml:"repeat_penalty"`
|
|
}
|
|
|
|
// DistillConfig holds distillation defaults.
|
|
type DistillConfig struct {
|
|
Runs int `yaml:"runs"`
|
|
MinChars int `yaml:"min_chars"`
|
|
CacheLimit int `yaml:"cache_limit"` // Metal cache limit in GB (0 = no limit)
|
|
MemoryLimit int `yaml:"memory_limit"` // Metal memory limit in GB (0 = no limit)
|
|
}
|
|
|
|
// ModelConfig is a .core/ai/models/{family}/{size}.yaml file.
|
|
type ModelConfig struct {
|
|
Version int `yaml:"version"`
|
|
Name string `yaml:"name"`
|
|
Family string `yaml:"family"`
|
|
Parameters string `yaml:"parameters"`
|
|
Format string `yaml:"format"`
|
|
Paths ModelPaths `yaml:"paths"`
|
|
Kernel string `yaml:"kernel"`
|
|
Signature string `yaml:"signature"`
|
|
Training string `yaml:"training"`
|
|
Lessons map[int]string `yaml:"lessons"`
|
|
Valid string `yaml:"valid"`
|
|
Test string `yaml:"test"`
|
|
Generate GenerateConfig `yaml:"generate"`
|
|
Baselines Baselines `yaml:"baselines"`
|
|
}
|
|
|
|
// ModelPaths holds filesystem locations for model files.
|
|
type ModelPaths struct {
|
|
Base string `yaml:"base"`
|
|
Safetensors string `yaml:"safetensors"`
|
|
}
|
|
|
|
// Baselines holds scoring reference points.
|
|
type Baselines struct {
|
|
NoKernel float64 `yaml:"no_kernel"`
|
|
WithKernel float64 `yaml:"with_kernel"`
|
|
Target float64 `yaml:"target"`
|
|
}
|
|
|
|
// ProbesConfig is a .core/ai/probes.yaml file.
|
|
type ProbesConfig struct {
|
|
Version int `yaml:"version"`
|
|
Sets map[string]ProbeSet `yaml:"sets"`
|
|
}
|
|
|
|
// ProbeSet groups related probe files.
|
|
type ProbeSet struct {
|
|
Description string `yaml:"description"`
|
|
Phase *int `yaml:"phase"`
|
|
Files []string `yaml:"files"`
|
|
}
|
|
|
|
// LoadAIConfig reads .core/ai/ai.yaml from the given root directory.
|
|
func LoadAIConfig(root string) (*AIConfig, error) {
|
|
path := filepath.Join(root, ".core", "ai", "ai.yaml")
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read ai config: %w", err)
|
|
}
|
|
var cfg AIConfig
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse ai config: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// LoadModelConfig reads .core/ai/models/{model}.yaml.
|
|
// The model arg is a slash path like "gemma3/27b".
|
|
func LoadModelConfig(root, model string) (*ModelConfig, error) {
|
|
path := filepath.Join(root, ".core", "ai", "models", model+".yaml")
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read model config: %w", err)
|
|
}
|
|
var cfg ModelConfig
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse model config: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// LoadProbesConfig reads .core/ai/probes.yaml.
|
|
func LoadProbesConfig(root string) (*ProbesConfig, error) {
|
|
path := filepath.Join(root, ".core", "ai", "probes.yaml")
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read probes config: %w", err)
|
|
}
|
|
var cfg ProbesConfig
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse probes config: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
// MergeGenerate returns a GenerateConfig with model-level overrides
|
|
// applied on top of the global defaults. Zero values in the model
|
|
// config are ignored (global default kept).
|
|
func MergeGenerate(global, model GenerateConfig) GenerateConfig {
|
|
merged := global
|
|
if model.MaxTokens > 0 {
|
|
merged.MaxTokens = model.MaxTokens
|
|
}
|
|
if model.Temperature > 0 {
|
|
merged.Temperature = model.Temperature
|
|
}
|
|
if model.TopP > 0 {
|
|
merged.TopP = model.TopP
|
|
}
|
|
if model.TopK > 0 {
|
|
merged.TopK = model.TopK
|
|
}
|
|
if model.RepeatPenalty > 0 {
|
|
merged.RepeatPenalty = model.RepeatPenalty
|
|
}
|
|
return merged
|
|
}
|