mirror of
https://github.com/ollama/ollama.git
synced 2026-09-21 05:28:00 -05:00
The MLX runner is the only Go inference runner left and is no longer experimental, so its packages leave x/. The bindings become a top-level mlx package beside the carried patches in mlx/compat, mirroring how llama/ holds the llama.cpp integration, and the runner becomes mlxrunner with the architectures nested under the package they implement. Subpackages move with their parent unless listed. x/mlxrunner/mlx mlx x/internal/mlxthread mlx/mlxthread x/internal/mlxthreadtest mlx/mlxthread/mlxthreadtest x/internal/mlxtest mlx/mlxtest x/quant mlx/quant mlx/compat/*.patch mlx/compat/mlx-c (MLX patches go in mlx/compat/mlx) x/mlxrunner mlxrunner x/models/nn mlxrunner/nn x/models/<arch> mlxrunner/model/<arch> x/mlxrunner/imports.go mlxrunner/model/architectures (new package) x/create create x/safetensors fs/safetensors x/tokenizer mlxrunner/tokenizer Every package keeps its name, so the Go changes are the import path rewrites the moves force, and the CMake, Dockerfile, CI cache keys, drift check and Darwin payload script follow the new paths. Four edits are not paths: the runner's blank architecture imports become the package mlxrunner/model/architectures, so the list to extend for a new model sits beside the architecture directories; a depguard rule keeps the two test harnesses out of non-test code, as the x/internal placement used to; the CI change filter's two entries for the long-deleted x/imagegen/mlx now name the bindings' CMake project and the carried patches, so a change to either builds the payload; and the tokenizer parity test reads its fixtures from its own testdata instead of walking out of x/. x/server and x/imagegen/manifest stay for the next two commits.
109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package create
|
|
|
|
import (
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// defaultQuantPolicy is the quantize policy for any architecture without a
|
|
// registered override: the shared GetTensorQuantization decision with no
|
|
// architecture-specific adjustments.
|
|
type defaultQuantPolicy struct{}
|
|
|
|
func (defaultQuantPolicy) quantizationType(name string, shape []int32, quantize string) string {
|
|
return GetTensorQuantization(name, shape, quantize)
|
|
}
|
|
|
|
// layerIndexRe extracts the layer index from tensor names like
|
|
// "model.language_model.layers.5.self_attn.v_proj.weight" or
|
|
// "model.language_model.layers.5.moe.experts.42.down_proj.weight"
|
|
var layerIndexRe = regexp.MustCompile(`\.layers\.(\d+)\.`)
|
|
|
|
// layerIndex returns the transformer layer index encoded in name, or -1.
|
|
func layerIndex(name string) int {
|
|
m := layerIndexRe.FindStringSubmatch(name)
|
|
if m == nil {
|
|
return -1
|
|
}
|
|
idx, err := strconv.Atoi(m[1])
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
return idx
|
|
}
|
|
|
|
// useMoreBits returns true for layers where quantization-sensitive tensors
|
|
// should use higher precision: the first and last 1/8 of layers (which handle
|
|
// input grounding and final output refinement), plus every 3rd layer in between
|
|
// to limit error accumulation through the residual stream.
|
|
func useMoreBits(layerIdx, numLayers int) bool {
|
|
return useMoreBitsWithMiddleEnd(layerIdx, numLayers, 7*numLayers/8)
|
|
}
|
|
|
|
// useMoreBitsWithMiddleEnd applies the standard early/late promotion and
|
|
// limits the every-third-layer cadence to layers before middleEnd.
|
|
func useMoreBitsWithMiddleEnd(layerIdx, numLayers, middleEnd int) bool {
|
|
if layerIdx < 0 || numLayers <= 0 {
|
|
return false
|
|
}
|
|
first := numLayers / 8
|
|
last := 7 * numLayers / 8
|
|
return layerIdx < first ||
|
|
layerIdx >= last ||
|
|
(layerIdx >= first && layerIdx < middleEnd && (layerIdx-first)%3 == 2)
|
|
}
|
|
|
|
// eightBit returns the 8-bit quantization type in base's family: int8 for the
|
|
// affine family, mxfp8 for the fp4 family.
|
|
func eightBit(base string) string {
|
|
if base == "int4" || base == "int8" {
|
|
return "int8"
|
|
}
|
|
return "mxfp8"
|
|
}
|
|
|
|
// promoteEmbedding returns the 8-bit type in base's family when the embedding
|
|
// shape fits it, or "" when it does not. Token embeddings often double as the
|
|
// lm_head projection, where an 8-bit type keeps quality close to bf16 while
|
|
// saving decode bandwidth; the caller decides the fallback when 8-bit does not
|
|
// fit (the base type, or source precision).
|
|
func promoteEmbedding(shape []int32, base string) string {
|
|
if e := eightBit(base); isAligned(shape, e) {
|
|
return e
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// sensitiveType resolves a quantization-sensitive projection (v/k/down): the
|
|
// 8-bit type in base's family when promote is set and fits the shape,
|
|
// otherwise the base type when it fits, otherwise source precision.
|
|
func sensitiveType(promote bool, shape []int32, base string) string {
|
|
if promote {
|
|
if e := eightBit(base); isAligned(shape, e) {
|
|
return e
|
|
}
|
|
}
|
|
if isAligned(shape, base) {
|
|
return base
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// isEmbedTokensWeight returns true for the main token embedding weight.
|
|
func isEmbedTokensWeight(name string) bool {
|
|
return strings.HasSuffix(name, "embed_tokens.weight") &&
|
|
!strings.Contains(name, "per_layer")
|
|
}
|
|
|
|
// isVision reports tensors under a model's vision components: towers,
|
|
// encoder-free embedders, and vision-to-text projections alike.
|
|
func isVision(name string) bool {
|
|
return strings.Contains(name, "vision") || strings.Contains(name, "visual")
|
|
}
|
|
|
|
// isAudioTower reports tensors under a model's audio tower or audio embedding.
|
|
func isAudioTower(name string) bool {
|
|
return strings.Contains(name, "audio_tower") || strings.Contains(name, "embed_audio")
|
|
}
|