XII · Apple acceleration (welvet.metal) / CHAPTER 79
Apple Metal: training modes
github.com/openfluke/welvet/nativemetalverified Apple M5
Why it exists
Keep repeated training updates in the mounted Metal session.
What it is
Train two Dense layers using one of the 29 numbered modes, with persistent Step Line state and real Grid layouts for Mesh modes.
Training families
-mode all exercises all 29 modes. Step modes retain their Line session across calls. Mesh modes use a three-cell Grid with disabled ingress and two active layers, advancing four ticks per call. These small demonstrations validate usage; they are not performance measurements.
go run . -mode NormalBP -steps 8
go run . -mode all -steps 8Explicit mounted sessions keep updated weights native until SyncToGo(). Close discards any updates not returned to Go. See chapter 85 before saving.
Requirements and library installation: chapter 82.
Go example
examples/84-apple-training/main.go
cd welvet/examples/84-apple-training && source ../env.sh && go run .// Native Metal MSE training: persistent Stack Line state or an actual Grid clock.
package main
import (
"flag"
"fmt"
"github.com/openfluke/welvet/architecture"
"github.com/openfluke/welvet/core"
"github.com/openfluke/welvet/layers/dense"
"github.com/openfluke/welvet/layers/parallel"
"github.com/openfluke/welvet/nativemetal"
"github.com/openfluke/welvet/quant"
"log"
"runtime"
)
func main() {
if e := run(); e != nil {
log.Fatal(e)
}
}
func run() error {
selected := flag.String("mode", "NormalBP", "training mode name, or all 29")
steps := flag.Int("steps", 8, "training calls; retain the session for Step pipeline state")
flag.Parse()
if *steps < 1 {
return fmt.Errorf("steps must be positive")
}
if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
fmt.Println("SKIP: Apple Metal needs Apple silicon macOS and cgo")
return nil
}
if *selected == "all" {
for mode := parallel.ModeNormalBP; mode <= parallel.ModeStepTweenSplitSparse; mode++ {
if e := train(mode, *steps); e != nil {
return e
}
}
return nil
}
mode, e := parallel.ParseTrainMode(*selected)
if e != nil {
return e
}
if mode < parallel.ModeNormalBP || mode > parallel.ModeStepTweenSplitSparse {
return fmt.Errorf("choose one of the 29 update modes")
}
return train(mode, *steps)
}
func train(mode parallel.TrainMode, steps int) error {
var ops []any
for layer := 0; layer < 2; layer++ {
w := make([]float32, 16)
for i := range w {
w[i] = float32(i%5-2) * .04
}
l, e := dense.NewConfigured(4, 4, core.ActivationTanh, core.DTypeFloat32, quant.FormatNone, w)
if e != nil {
return e
}
ops = append(ops, l)
}
stack, e := parallel.NewStack(ops...)
if e != nil {
return e
}
var model any = stack
schedule := "Stack"
if mode.RequiresGrid() {
g := architecture.NewGrid(3, 1, 1, 1)
g.Cells[0].Layer.IsDisabled = true
for i, op := range ops {
g.Cells[i+1].Layer = op.(*dense.Layer).Core
g.Cells[i+1].Op = op
}
model = g
schedule = "Grid mesh clock (4 ticks/call)"
} else if mode.IsLineStep() {
schedule = "Stack Line pipeline"
}
s, e := nativemetal.Mount(model)
if e != nil {
return e
}
defer s.Close()
x := core.NewTensor[float32](2, 4)
for i := range x.Data {
x.Data[i] = float32(i%5-2) * .1
}
target := core.NewTensor[float32](2, 4)
var loss float64
for i := 0; i < steps; i++ {
loss, e = s.TrainMSE(x, target, mode, .01, 4)
if e != nil {
return fmt.Errorf("%s: %w", mode, e)
}
}
if e = s.SyncToGo(); e != nil {
return e
}
fmt.Printf("mode=%s schedule=%s calls=%d loss=%.8g weights=synced-to-Go\n", mode, schedule, steps, loss)
return nil
}
Output
mode=NormalBP schedule=Stack calls=8 loss=6.7954462e-06 weights=synced-to-Go
Validate (harness)
cd welvet/examples/84-apple-training && source ../env.sh && go run .