mirror of
https://github.com/ollama/ollama.git
synced 2026-07-31 01:48:44 -04:00
This is a rewrite of the create functionality for the MLX engine. The core idea behind the create functionality is to break the import/convert into a pipeline of distinct phases: * Read (scan the safetensors directory for the various bits of metadata) * Classify (determine what the import type) * Plan (determine any transforms that need to be done) * Write (transform any data as necessary and write out the blobs) * Create the manifest Each architecture has a "policy" which determines how to convert the model correctly. A number of different formats for safetensors are supported including: * nvfp4 (two formats: model optimized, torch) * fp8 datatypes (convert to mxfp8) * standard bf16 based weights A number of cleanups/simplifications have been done including: * using the baked in names for the tensors instead of munging them into something else * unified 3d expert tensors (instead of separate per expert tensors) * fewer unnecessary transforms to the various tensors in a model (keep a model as close to the source as possible) * unified capability checking * draft model handling (for MTP) is done on the same path Image generation has been intentionally removed.
119 lines
3.1 KiB
Go
119 lines
3.1 KiB
Go
package model
|
|
|
|
import (
|
|
"github.com/ollama/ollama/x/mlxrunner/mlx"
|
|
"github.com/ollama/ollama/x/quant"
|
|
)
|
|
|
|
// QuantizationParams returns default groupSize, bits, and mode for a
|
|
// quantization type. The values live in the shared x/quant package so the
|
|
// importer, the runtime loader, and `ollama show` agree on them.
|
|
func QuantizationParams(quantization string) (groupSize, bits int, mode string) {
|
|
return quant.Params(quantization)
|
|
}
|
|
|
|
// TensorQuantParams resolves quant params for a tensor using per-tensor metadata
|
|
// when available, otherwise falling back to the provided model defaults.
|
|
func TensorQuantParams(
|
|
defaultGroupSize, defaultBits int,
|
|
defaultMode string,
|
|
tensorQuant map[string]*TensorQuantInfo,
|
|
tensorName string,
|
|
) (groupSize, bits int, mode string, fromTensor bool) {
|
|
if tensorQuant != nil {
|
|
if tq := tensorQuant[tensorName]; tq != nil {
|
|
groupSize, bits, mode = QuantizationParams(tq.QuantType)
|
|
if tq.GroupSize > 0 {
|
|
groupSize = tq.GroupSize
|
|
}
|
|
return groupSize, bits, mode, true
|
|
}
|
|
}
|
|
return defaultGroupSize, defaultBits, defaultMode, false
|
|
}
|
|
|
|
// ResolveLinearQuantParams resolves quantization params for a quantized linear
|
|
// tensor, preferring per-tensor metadata and falling back to shape-based
|
|
// inference for affine packed tensors.
|
|
func ResolveLinearQuantParams(
|
|
defaultGroupSize, defaultBits int,
|
|
defaultMode string,
|
|
tensorQuant map[string]*TensorQuantInfo,
|
|
tensorName string,
|
|
weight, scales *mlx.Array,
|
|
) (groupSize, bits int, mode string) {
|
|
groupSize, bits, mode, fromTensor := TensorQuantParams(
|
|
defaultGroupSize,
|
|
defaultBits,
|
|
defaultMode,
|
|
tensorQuant,
|
|
tensorName,
|
|
)
|
|
|
|
if mode == "affine" {
|
|
if inferredGroupSize, inferredBits, ok := InferAffineQuantParamsFromShapes(weight, scales, bits); ok {
|
|
if !fromTensor || groupSize == 0 || bits == 0 {
|
|
groupSize = inferredGroupSize
|
|
bits = inferredBits
|
|
}
|
|
}
|
|
}
|
|
|
|
return groupSize, bits, mode
|
|
}
|
|
|
|
// InferAffineQuantParamsFromShapes infers (groupSize,bits) for affine quantized
|
|
// tensors from packed weight and scale shapes.
|
|
func InferAffineQuantParamsFromShapes(weight, scales *mlx.Array, hintBits int) (groupSize, bits int, ok bool) {
|
|
if weight == nil || scales == nil {
|
|
return 0, 0, false
|
|
}
|
|
|
|
weightShape := weight.Dims()
|
|
scaleShape := scales.Dims()
|
|
if len(weightShape) == 0 || len(scaleShape) == 0 {
|
|
return 0, 0, false
|
|
}
|
|
|
|
weightCols := weightShape[len(weightShape)-1]
|
|
scalesCols := scaleShape[len(scaleShape)-1]
|
|
if weightCols <= 0 || scalesCols <= 0 {
|
|
return 0, 0, false
|
|
}
|
|
|
|
groupSize4 := weightCols * 8 / scalesCols
|
|
groupSize8 := weightCols * 4 / scalesCols
|
|
|
|
switch {
|
|
case groupSize4 == 32:
|
|
return 32, 4, true
|
|
case groupSize8 == 64:
|
|
return 64, 8, true
|
|
case groupSize4 == 64 && groupSize8 == 32:
|
|
if hintBits == 8 {
|
|
return 32, 8, true
|
|
}
|
|
if hintBits == 4 {
|
|
return 64, 4, true
|
|
}
|
|
}
|
|
|
|
if isCommonGroupSize(groupSize4) && !isCommonGroupSize(groupSize8) {
|
|
return groupSize4, 4, true
|
|
}
|
|
if isCommonGroupSize(groupSize8) && !isCommonGroupSize(groupSize4) {
|
|
return groupSize8, 8, true
|
|
}
|
|
|
|
return 0, 0, false
|
|
}
|
|
|
|
func isCommonGroupSize(v int) bool {
|
|
switch v {
|
|
case 16, 32, 64, 128:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|