flesh out dtypes

This commit is contained in:
Daniel Hiltgen
2026-05-21 18:28:10 -07:00
parent 660de28470
commit 547d7e21c1
8 changed files with 352 additions and 24 deletions

View File

@@ -424,8 +424,12 @@ func (t TensorType) BlockSize() uint64 {
TensorTypeQ8_0,
TensorTypeQ8_1,
tensorTypeIQ4_NL,
4, TensorTypeMXFP4:
TensorTypeMXFP4:
return 32
case TensorTypeNVFP4:
return 64
case TensorTypeQ1_0:
return 128
default:
return 256
}
@@ -497,8 +501,12 @@ func (t TensorType) TypeSize() uint64 {
return blockSize/8 + blockSize/16 + blockSize/32
case TensorTypeBF16:
return 2
case 4, TensorTypeMXFP4:
case TensorTypeMXFP4:
return 1 + blockSize/2
case TensorTypeNVFP4:
return 4 + blockSize/2
case TensorTypeQ1_0:
return 2 + blockSize/8
default:
return 0
}

View File

@@ -14,9 +14,9 @@ const (
FileTypeF16
fileTypeQ4_0
fileTypeQ4_1
fileTypeMXFP4 // originally fileTypeQ4_1_F16 // unused by GGML
fileTypeQ4_2 // unused by GGML
fileTypeQ4_3 // unused by GGML
fileTypeQ4_1_F16 // removed from GGUF files
fileTypeQ4_2 // removed from GGUF files
fileTypeQ4_3 // removed from GGUF files
FileTypeQ8_0
fileTypeQ5_0
fileTypeQ5_1
@@ -48,6 +48,9 @@ const (
fileTypeQ4_0_8_8 // unused by GGML
fileTypeTQ1_0
fileTypeTQ2_0
fileTypeMXFP4_MOE
fileTypeNVFP4
fileTypeQ1_0
FileTypeUnknown = 1024
)
@@ -97,8 +100,6 @@ func (t FileType) String() string {
return "Q4_0"
case fileTypeQ4_1:
return "Q4_1"
case fileTypeMXFP4:
return "MXFP4"
case FileTypeQ8_0:
return "Q8_0"
case fileTypeQ5_0:
@@ -123,10 +124,44 @@ func (t FileType) String() string {
return "Q5_K_M"
case fileTypeQ6_K:
return "Q6_K"
case fileTypeIQ2_XXS:
return "IQ2_XXS"
case fileTypeIQ2_XS:
return "IQ2_XS"
case fileTypeQ2_K_S:
return "Q2_K_S"
case fileTypeIQ3_XS:
return "IQ3_XS"
case fileTypeIQ3_XXS:
return "IQ3_XXS"
case fileTypeIQ1_S:
return "IQ1_S"
case fileTypeIQ4_NL:
return "IQ4_NL"
case fileTypeIQ3_S:
return "IQ3_S"
case fileTypeIQ3_M:
return "IQ3_M"
case fileTypeIQ2_S:
return "IQ2_S"
case fileTypeIQ2_M:
return "IQ2_M"
case fileTypeIQ4_XS:
return "IQ4_XS"
case fileTypeIQ1_M:
return "IQ1_M"
case FileTypeBF16:
return "BF16"
case fileTypeTQ1_0:
return "TQ1_0"
case fileTypeTQ2_0:
return "TQ2_0"
case fileTypeMXFP4_MOE:
return "MXFP4_MOE"
case fileTypeNVFP4:
return "NVFP4"
case fileTypeQ1_0:
return "Q1_0"
default:
return "unknown"
}
@@ -170,12 +205,40 @@ func (ftype FileType) ToTensorType() TensorType {
return TensorTypeQ5_K
case fileTypeQ6_K:
return TensorTypeQ6_K
case fileTypeIQ2_XXS:
return tensorTypeIQ2_XXS
case fileTypeIQ2_XS:
return tensorTypeIQ2_XS
case fileTypeQ2_K_S:
return TensorTypeQ2_K
case fileTypeIQ3_XS:
return tensorTypeIQ3_S
case fileTypeIQ3_XXS:
return tensorTypeIQ3_XXS
case fileTypeIQ1_S:
return tensorTypeIQ1_S
case fileTypeIQ4_NL:
return tensorTypeIQ4_NL
case fileTypeIQ3_S, fileTypeIQ3_M:
return tensorTypeIQ3_S
case fileTypeIQ2_S, fileTypeIQ2_M:
return tensorTypeIQ2_S
case fileTypeIQ4_XS:
return tensorTypeIQ4_XS
case fileTypeIQ1_M:
return tensorTypeIQ1_M
case FileTypeBF16:
return TensorTypeBF16
case fileTypeMXFP4:
case fileTypeTQ1_0:
return tensorTypeTQ1_0
case fileTypeTQ2_0:
return tensorTypeTQ2_0
case fileTypeMXFP4_MOE:
return TensorTypeMXFP4
case fileTypeNVFP4:
return TensorTypeNVFP4
case fileTypeQ1_0:
return TensorTypeQ1_0
default:
slog.Warn("unsupported file type", "type", ftype)
return 0 // F32
@@ -227,6 +290,8 @@ const (
tensorTypeIQ4_NL_4_8 // unused by GGML
tensorTypeIQ4_NL_8_8 // unused by GGML
TensorTypeMXFP4
TensorTypeNVFP4
TensorTypeQ1_0
)
// ParseTensorType parses the provided GGUF tensor type
@@ -315,12 +380,46 @@ func (t TensorType) String() string {
return "Q6_K"
case TensorTypeQ8_K:
return "Q8_K"
case tensorTypeIQ2_XXS:
return "IQ2_XXS"
case tensorTypeIQ2_XS:
return "IQ2_XS"
case tensorTypeIQ3_XXS:
return "IQ3_XXS"
case tensorTypeIQ1_S:
return "IQ1_S"
case tensorTypeIQ4_NL:
return "IQ4_NL"
case tensorTypeIQ3_S:
return "IQ3_S"
case tensorTypeIQ2_S:
return "IQ2_S"
case tensorTypeIQ4_XS:
return "IQ4_XS"
case TensorTypeI8:
return "I8"
case TensorTypeI16:
return "I16"
case TensorTypeI32:
return "I32"
case TensorTypeI64:
return "I64"
case TensorTypeF64:
return "F64"
case tensorTypeIQ1_M:
return "IQ1_M"
case TensorTypeBF16:
return "BF16"
case 4, TensorTypeMXFP4:
case tensorTypeTQ1_0:
return "TQ1_0"
case tensorTypeTQ2_0:
return "TQ2_0"
case TensorTypeMXFP4:
return "MXFP4"
case TensorTypeNVFP4:
return "NVFP4"
case TensorTypeQ1_0:
return "Q1_0"
default:
return "unknown"
}

115
fs/ggml/type_test.go Normal file
View File

@@ -0,0 +1,115 @@
package ggml
import "testing"
func TestFileTypeStringMatchesLlamaFType(t *testing.T) {
tests := []struct {
ftype FileType
want string
}{
{0, "F32"},
{1, "F16"},
{2, "Q4_0"},
{3, "Q4_1"},
{7, "Q8_0"},
{8, "Q5_0"},
{9, "Q5_1"},
{10, "Q2_K"},
{11, "Q3_K_S"},
{12, "Q3_K_M"},
{13, "Q3_K_L"},
{14, "Q4_K_S"},
{15, "Q4_K_M"},
{16, "Q5_K_S"},
{17, "Q5_K_M"},
{18, "Q6_K"},
{19, "IQ2_XXS"},
{20, "IQ2_XS"},
{21, "Q2_K_S"},
{22, "IQ3_XS"},
{23, "IQ3_XXS"},
{24, "IQ1_S"},
{25, "IQ4_NL"},
{26, "IQ3_S"},
{27, "IQ3_M"},
{28, "IQ2_S"},
{29, "IQ2_M"},
{30, "IQ4_XS"},
{31, "IQ1_M"},
{32, "BF16"},
{36, "TQ1_0"},
{37, "TQ2_0"},
{38, "MXFP4_MOE"},
{39, "NVFP4"},
{40, "Q1_0"},
{FileTypeUnknown, "unknown"},
}
for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {
if got := tt.ftype.String(); got != tt.want {
t.Fatalf("FileType(%d).String() = %q, want %q", tt.ftype, got, tt.want)
}
})
}
}
func TestRemovedFileTypesAreUnknown(t *testing.T) {
for _, ftype := range []FileType{4, 5, 6, 33, 34, 35} {
t.Run(ftype.String(), func(t *testing.T) {
if got := ftype.String(); got != "unknown" {
t.Fatalf("FileType(%d).String() = %q, want unknown", ftype, got)
}
})
}
}
func TestTensorTypeStringMatchesGGMLType(t *testing.T) {
tests := []struct {
tt TensorType
want string
}{
{0, "F32"},
{1, "F16"},
{2, "Q4_0"},
{3, "Q4_1"},
{6, "Q5_0"},
{7, "Q5_1"},
{8, "Q8_0"},
{9, "Q8_1"},
{10, "Q2_K"},
{11, "Q3_K"},
{12, "Q4_K"},
{13, "Q5_K"},
{14, "Q6_K"},
{15, "Q8_K"},
{16, "IQ2_XXS"},
{17, "IQ2_XS"},
{18, "IQ3_XXS"},
{19, "IQ1_S"},
{20, "IQ4_NL"},
{21, "IQ3_S"},
{22, "IQ2_S"},
{23, "IQ4_XS"},
{24, "I8"},
{25, "I16"},
{26, "I32"},
{27, "I64"},
{28, "F64"},
{29, "IQ1_M"},
{30, "BF16"},
{34, "TQ1_0"},
{35, "TQ2_0"},
{39, "MXFP4"},
{40, "NVFP4"},
{41, "Q1_0"},
}
for _, tt := range tests {
t.Run(tt.want, func(t *testing.T) {
if got := tt.tt.String(); got != tt.want {
t.Fatalf("TensorType(%d).String() = %q, want %q", tt.tt, got, tt.want)
}
})
}
}

View File

@@ -879,6 +879,43 @@ func TestSetupLlamaServerCommandEnv(t *testing.T) {
}
}
func TestFilteredEnvLogValue(t *testing.T) {
attrs := filteredEnv([]string{
"OLLAMA_DEBUG=1",
"OLLAMA_API_KEY=ollama-secret",
"OPENAI_API_KEY=openai-secret",
"HF_TOKEN=hf-secret",
"GGML_BACKEND_PATH=/tmp/ggml",
"CUDA_VISIBLE_DEVICES=0",
"CUDA_API_KEY=cuda-secret",
"HIP_VISIBLE_DEVICES=1",
"PATH=/bin",
}).LogValue().Group()
got := make(map[string]string, len(attrs))
for _, attr := range attrs {
got[attr.Key] = attr.Value.String()
}
for _, key := range []string{"OLLAMA_DEBUG", "OLLAMA_API_KEY", "OPENAI_API_KEY", "HF_TOKEN"} {
if _, ok := got[key]; ok {
t.Fatalf("%s should not be logged: %#v", key, got)
}
}
for key, want := range map[string]string{
"GGML_BACKEND_PATH": "/tmp/ggml",
"CUDA_VISIBLE_DEVICES": "0",
"HIP_VISIBLE_DEVICES": "1",
"PATH": "/bin",
"CUDA_API_KEY": "[redacted]",
} {
if got[key] != want {
t.Fatalf("%s = %q, want %q; attrs=%#v", key, got[key], want, got)
}
}
}
func TestLlamaServerCompletionBOSOwnership(t *testing.T) {
tests := []struct {
name string

View File

@@ -25,27 +25,37 @@ func (e filteredEnv) LogValue() slog.Value {
var attrs []slog.Attr
for _, env := range e {
if key, value, ok := strings.Cut(env, "="); ok {
switch {
case strings.HasPrefix(key, "OLLAMA_"),
strings.HasPrefix(key, "CUDA_"),
strings.HasPrefix(key, "ROCR_"),
strings.HasPrefix(key, "ROCM_"),
strings.HasPrefix(key, "HIP_"),
strings.HasPrefix(key, "GPU_"),
strings.HasPrefix(key, "HSA_"),
strings.HasPrefix(key, "GGML_"),
slices.Contains([]string{
"PATH",
"LD_LIBRARY_PATH",
"DYLD_LIBRARY_PATH",
}, key):
attrs = append(attrs, slog.String(key, value))
if filteredEnvLogKey(key) {
attrs = append(attrs, slog.String(key, filteredEnvLogValue(key, value)))
}
}
}
return slog.GroupValue(attrs...)
}
func filteredEnvLogKey(key string) bool {
return strings.HasPrefix(key, "CUDA_") ||
strings.HasPrefix(key, "ROCR_") ||
strings.HasPrefix(key, "ROCM_") ||
strings.HasPrefix(key, "HIP_") ||
strings.HasPrefix(key, "HSA_") ||
strings.HasPrefix(key, "GGML_") ||
slices.Contains([]string{
"PATH",
"LD_LIBRARY_PATH",
"DYLD_LIBRARY_PATH",
}, key)
}
func filteredEnvLogValue(key, value string) string {
for _, token := range []string{"API", "KEY", "TOKEN", "SECRET", "PASSWORD", "PASS", "CREDENTIAL", "AUTH"} {
if strings.Contains(strings.ToUpper(key), token) {
return "[redacted]"
}
}
return value
}
type LlamaServer interface {
ModelPath() string
Load(ctx context.Context, systemInfo ml.SystemInfo, gpus []ml.DeviceInfo, requireFull bool) ([]ml.DeviceID, error)

View File

@@ -16,6 +16,7 @@ import (
"time"
"github.com/ollama/ollama/api"
"github.com/ollama/ollama/fs/ggml"
"github.com/ollama/ollama/manifest"
"github.com/ollama/ollama/model/parsers"
ollamatemplate "github.com/ollama/ollama/template"
@@ -353,6 +354,9 @@ func buildModelListSummary(name model.Name, mf *manifest.Manifest) (modelListSum
if summary.Details.EmbeddingLength == 0 {
summary.Details.EmbeddingLength = info.EmbeddingLength
}
if isUnknownQuantization(summary.Details.QuantizationLevel) && !isUnknownQuantization(info.FileType) {
summary.Details.QuantizationLevel = info.FileType
}
}
}
@@ -455,6 +459,7 @@ type modelListGGUF struct {
Capabilities []model.Capability
ContextLength int
EmbeddingLength int
FileType string
}
const (
@@ -554,6 +559,15 @@ func readModelListGGUF(path string) (modelListGGUF, error) {
continue
}
if key == "general.file_type" {
value, err := readModelListGGUFIntValue(r, byteOrder, version, valueType)
if err != nil {
return modelListGGUF{}, err
}
info.FileType = ggml.FileType(value).String()
continue
}
if architecture != "" && strings.HasPrefix(key, "tokenizer.") {
break
}
@@ -766,6 +780,10 @@ func appendModelListCapability(capabilities []model.Capability, capability model
return append(capabilities, capability)
}
func isUnknownQuantization(quantization string) bool {
return quantization == "" || quantization == "unknown"
}
func cloneModelListSummary(summary modelListSummary) modelListSummary {
summary.Details.Families = append([]string(nil), summary.Details.Families...)
summary.Capabilities = append([]model.Capability(nil), summary.Capabilities...)

View File

@@ -1514,6 +1514,11 @@ func GetModelInfo(req api.ShowRequest) (*api.ShowResponse, error) {
}
resp.Template = selectedModelTemplate(m, kvData)
if isUnknownQuantization(resp.Details.QuantizationLevel) {
if fileType := kvData.FileType().String(); !isUnknownQuantization(fileType) {
resp.Details.QuantizationLevel = fileType
}
}
delete(kvData, "general.name")
delete(kvData, "tokenizer.chat_template")

View File

@@ -586,6 +586,42 @@ func TestGetModelInfo_SafetensorsUsesStoredFileType(t *testing.T) {
}
}
func TestGetModelInfoRepairsUnknownGGUFFileType(t *testing.T) {
t.Setenv("OLLAMA_MODELS", t.TempDir())
_, digest := createBinFile(t, ggml.KV{
"general.architecture": "llama",
"general.file_type": uint32(ggml.FileTypeQ4_K_M),
}, nil)
modelLayer, err := manifest.NewLayerFromLayer(digest, "application/vnd.ollama.image.model", "")
if err != nil {
t.Fatal(err)
}
configLayer, err := createConfigLayer([]manifest.Layer{modelLayer}, model.ConfigV2{
ModelFormat: "gguf",
ModelFamily: "llama",
ModelFamilies: []string{"llama"},
FileType: "unknown",
})
if err != nil {
t.Fatal(err)
}
name := model.ParseName("show-unknown-gguf")
if err := manifest.WriteManifest(name, *configLayer, []manifest.Layer{modelLayer}); err != nil {
t.Fatal(err)
}
resp, err := GetModelInfo(api.ShowRequest{Model: name.String()})
if err != nil {
t.Fatalf("GetModelInfo() error = %v", err)
}
if resp.Details.QuantizationLevel != "Q4_K_M" {
t.Fatalf("QuantizationLevel = %q, want %q", resp.Details.QuantizationLevel, "Q4_K_M")
}
}
func TestGetModelInfo_SafetensorsModelfileUsesShortName(t *testing.T) {
t.Setenv("OLLAMA_MODELS", t.TempDir())