From 9e17c2f7b952171cb2a5f2d86e5f5e401f8c4086 Mon Sep 17 00:00:00 2001 From: Kristian Ollikainen Date: Mon, 21 Oct 2024 16:20:22 +0300 Subject: [PATCH] feat: GGUF --- gguf/common.go | 30 ++++++ gguf/gguf.go | 35 +++++++ gguf/header.go | 62 ++++++++++++ gguf/metadata.go | 245 +++++++++++++++++++++++++++++++++++++++++++++++ gguf/tensor.go | 5 + gguf/types.go | 42 ++++++++ 6 files changed, 419 insertions(+) create mode 100644 gguf/common.go create mode 100644 gguf/gguf.go create mode 100644 gguf/header.go create mode 100644 gguf/metadata.go create mode 100644 gguf/tensor.go create mode 100644 gguf/types.go diff --git a/gguf/common.go b/gguf/common.go new file mode 100644 index 0000000..71111b4 --- /dev/null +++ b/gguf/common.go @@ -0,0 +1,30 @@ +package gguf + +import ( + "encoding/binary" + "os" +) + +type String struct { + Length uint64 + Str []byte +} + +func (s *String) String() string { + return string(s.Str) +} + +func (s *String) Read(file *os.File) error { + err := binary.Read(file, binary.LittleEndian, &s.Length) + if err != nil { + return err + } + + s.Str = make([]byte, s.Length) + _, err = file.Read(s.Str) + if err != nil { + return err + } + + return nil +} \ No newline at end of file diff --git a/gguf/gguf.go b/gguf/gguf.go new file mode 100644 index 0000000..b562a17 --- /dev/null +++ b/gguf/gguf.go @@ -0,0 +1,35 @@ +package gguf + + +import ( + "fmt" + "log" + "os" +) + +// DebugReadGGUF reads a GGUF file and prints its header information. +func DebugReadGGUF(filename string) error { + f, err := os.Open(filename) + if err != nil { + return err + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Printf("Error closing file: %v\n", err) + } + }(f) + + // Read the header + var header Header + err = header.Read(f) + if err != nil { + return err + } + + // Print header information + fmt.Printf("GGUF File Information:\n") + fmt.Println(header.String()) + + return nil +} diff --git a/gguf/header.go b/gguf/header.go new file mode 100644 index 0000000..8b8fdbb --- /dev/null +++ b/gguf/header.go @@ -0,0 +1,62 @@ +package gguf + +import ( + "encoding/binary" + "fmt" + "os" +) + +// Header represents the header information in a GGUF file. +type Header struct { + MagicNumber uint32 + Version uint32 + TensorCount uint64 + MetadataKVCount uint64 + Metadata []MetadataKV +} + +func (h *Header) String() string { + magicToString := func(magic uint32) string { + return fmt.Sprintf("%c%c%c%c", byte(magic), byte(magic>>8), byte(magic>>16), byte(magic>>24)) + } + + str := fmt.Sprintf("Magic Number: %x (%s)\n", h.MagicNumber, magicToString(h.MagicNumber)) + str += fmt.Sprintf("Version: %d\n", h.Version) + str += fmt.Sprintf("Tensor Count: %d\n", h.TensorCount) + str += fmt.Sprintf("Metadata KV Count: %d\n", h.MetadataKVCount) + str += fmt.Sprintf("Metadata:\n") + for i, kv := range h.Metadata { + str += fmt.Sprintf("\tMetadata KV %d:\n", i) + str += fmt.Sprintf("\t\t%s\n", kv.String()) + } + return str +} + +func (h *Header) Read(file *os.File) error { + err := binary.Read(file, binary.LittleEndian, &h.MagicNumber) + if err != nil { + return err + } + err = binary.Read(file, binary.LittleEndian, &h.Version) + if err != nil { + return err + } + err = binary.Read(file, binary.LittleEndian, &h.TensorCount) + if err != nil { + return err + } + err = binary.Read(file, binary.LittleEndian, &h.MetadataKVCount) + if err != nil { + return err + } + + h.Metadata = make([]MetadataKV, h.MetadataKVCount) + for i := range h.Metadata { + err = h.Metadata[i].Read(file) + if err != nil { + return err + } + } + + return nil +} diff --git a/gguf/metadata.go b/gguf/metadata.go new file mode 100644 index 0000000..bd72804 --- /dev/null +++ b/gguf/metadata.go @@ -0,0 +1,245 @@ +package gguf + +import ( + "encoding/binary" + "fmt" + "os" +) + +type MetadataValueType uint32 + +const ( + UINT8 MetadataValueType = iota + INT8 + UINT16 + INT16 + UINT32 + INT32 + FLOAT32 + BOOL + STRING + ARRAY + UINT64 + INT64 + FLOAT64 +) + +func (t MetadataValueType) String() string { + switch t { + case UINT8: + return "UINT8" + case INT8: + return "INT8" + case UINT16: + return "UINT16" + case INT16: + return "INT16" + case UINT32: + return "UINT32" + case INT32: + return "INT32" + case FLOAT32: + return "FLOAT32" + case BOOL: + return "BOOL" + case STRING: + return "STRING" + case ARRAY: + return "ARRAY" + case UINT64: + return "UINT64" + case INT64: + return "INT64" + case FLOAT64: + return "FLOAT64" + default: + return "Unknown" + } +} + +type MetadataArray struct { + Type MetadataValueType + Length uint64 + Array []MetadataValue +} + +func (a *MetadataArray) String() string { + str := fmt.Sprintf("Type: %s, Length: %d, Array:\n", a.Type.String(), a.Length) + for i, v := range a.Array { + str += fmt.Sprintf("\tValue %d: %s\n", i, v.String(a.Type)) + } + return str +} + +func (a *MetadataArray) Read(file *os.File) error { + err := binary.Read(file, binary.LittleEndian, &a.Type) + if err != nil { + return err + } + err = binary.Read(file, binary.LittleEndian, &a.Length) + if err != nil { + return err + } + a.Array = make([]MetadataValue, a.Length) + for i := range a.Array { + err = a.Array[i].Read(a.Type, file) + if err != nil { + return err + } + } + return nil +} + +type MetadataValue struct { + U8 uint8 + I8 int8 + U16 uint16 + I16 int16 + U32 uint32 + I32 int32 + F32 float32 + U64 uint64 + I64 int64 + F64 float64 + B bool + STR String + ARR MetadataArray +} + +func (v *MetadataValue) String(forType MetadataValueType) string { + switch forType { + case UINT8: + return fmt.Sprintf("%d", v.U8) + case INT8: + return fmt.Sprintf("%d", v.I8) + case UINT16: + return fmt.Sprintf("%d", v.U16) + case INT16: + return fmt.Sprintf("%d", v.I16) + case UINT32: + return fmt.Sprintf("%d", v.U32) + case INT32: + return fmt.Sprintf("%d", v.I32) + case FLOAT32: + return fmt.Sprintf("%f", v.F32) + case BOOL: + return fmt.Sprintf("%t", v.B) + case STRING: + return v.STR.String() + case ARRAY: + return fmt.Sprintf("%v", v.ARR) + case UINT64: + return fmt.Sprintf("%d", v.U64) + case INT64: + return fmt.Sprintf("%d", v.I64) + case FLOAT64: + return fmt.Sprintf("%f", v.F64) + default: + return "Unknown" + } +} + +func (v *MetadataValue) Read(forType MetadataValueType, file *os.File) error { + switch forType { + case UINT8: + return binary.Read(file, binary.LittleEndian, &v.U8) + case INT8: + return binary.Read(file, binary.LittleEndian, &v.I8) + case UINT16: + return binary.Read(file, binary.LittleEndian, &v.U16) + case INT16: + return binary.Read(file, binary.LittleEndian, &v.I16) + case UINT32: + return binary.Read(file, binary.LittleEndian, &v.U32) + case INT32: + return binary.Read(file, binary.LittleEndian, &v.I32) + case FLOAT32: + return binary.Read(file, binary.LittleEndian, &v.F32) + case BOOL: + return binary.Read(file, binary.LittleEndian, &v.B) + case STRING: + return v.STR.Read(file) + case ARRAY: + return v.ARR.Read(file) + case UINT64: + return binary.Read(file, binary.LittleEndian, &v.U64) + case INT64: + return binary.Read(file, binary.LittleEndian, &v.I64) + case FLOAT64: + return binary.Read(file, binary.LittleEndian, &v.F64) + default: + return fmt.Errorf("unknown type: %d", forType) + } +} + +type MetadataKV struct { + Key String + Type MetadataValueType + Value MetadataValue +} + +func (kv *MetadataKV) String() string { + vStr := kv.Value.String(kv.Type) + // If value string exceeds 100 characters, only show "too long to display" + if len(vStr) > 100 { + vStr = fmt.Sprintf("Value too long to display (%d characters)", len(vStr)) + } + return fmt.Sprintf("Key: %s, Type: %s, Value: %s", kv.Key.String(), kv.Type.String(), vStr) +} + +func (kv *MetadataKV) Read(file *os.File) error { + err := kv.Key.Read(file) + if err != nil { + return err + } + + err = binary.Read(file, binary.LittleEndian, &kv.Type) + if err != nil { + return err + } + + switch kv.Type { + case UINT8: + err = binary.Read(file, binary.LittleEndian, &kv.Value.U8) + case INT8: + err = binary.Read(file, binary.LittleEndian, &kv.Value.I8) + case UINT16: + err = binary.Read(file, binary.LittleEndian, &kv.Value.U16) + case INT16: + err = binary.Read(file, binary.LittleEndian, &kv.Value.I16) + case UINT32: + err = binary.Read(file, binary.LittleEndian, &kv.Value.U32) + case INT32: + err = binary.Read(file, binary.LittleEndian, &kv.Value.I32) + case FLOAT32: + err = binary.Read(file, binary.LittleEndian, &kv.Value.F32) + case BOOL: + err = binary.Read(file, binary.LittleEndian, &kv.Value.B) + case STRING: + err = kv.Value.STR.Read(file) + case ARRAY: + err = binary.Read(file, binary.LittleEndian, &kv.Value.ARR.Type) + if err != nil { + return err + } + err = binary.Read(file, binary.LittleEndian, &kv.Value.ARR.Length) + if err != nil { + return err + } + kv.Value.ARR.Array = make([]MetadataValue, kv.Value.ARR.Length) + for i := range kv.Value.ARR.Array { + err = kv.Value.ARR.Array[i].Read(kv.Value.ARR.Type, file) + if err != nil { + return err + } + } + case UINT64: + err = binary.Read(file, binary.LittleEndian, &kv.Value.U64) + case INT64: + err = binary.Read(file, binary.LittleEndian, &kv.Value.I64) + case FLOAT64: + err = binary.Read(file, binary.LittleEndian, &kv.Value.F64) + } + + return err +} \ No newline at end of file diff --git a/gguf/tensor.go b/gguf/tensor.go new file mode 100644 index 0000000..c7833df --- /dev/null +++ b/gguf/tensor.go @@ -0,0 +1,5 @@ +package gguf + +type TensorInfo struct { + Name String +} diff --git a/gguf/types.go b/gguf/types.go new file mode 100644 index 0000000..a56f61c --- /dev/null +++ b/gguf/types.go @@ -0,0 +1,42 @@ +package gguf + +type GGMLType uint32 + +const ( + TYPE_F32 GGMLType = iota + TYPE_F16 + TYPE_Q4_0 + TYPE_Q4_1 + // 4 & 5 skipped due to Q4_2 and Q4_3 being legacy + TYPE_Q5_0 GGMLType = iota + 2 + TYPE_Q5_1 + TYPE_Q8_0 + TYPE_Q8_1 + TYPE_Q2_K + TYPE_Q3_K + TYPE_Q4_K + TYPE_Q5_K + TYPE_Q6_K + TYPE_Q8_K + TYPE_IQ2_XXS + TYPE_IQ2_XS + TYPE_IQ3_XXS + TYPE_IQ1_S + TYPE_IQ4_NL + TYPE_IQ3_S + TYPE_IQ2_S + TYPE_IQ4_XS + TYPE_I8 + TYPE_I16 + TYPE_I32 + TYPE_I64 + TYPE_F64 + TYPE_IQ1_M + TYPE_BF16 + TYPE_Q4_0_4_4 + TYPE_Q4_0_4_8 + TYPE_Q4_0_8_8 + TYPE_TQ1_0 + TYPE_TQ2_0 + TYPE_COUNT +)