Skip to content

Commit

Permalink
First stab at v3 support.
Browse files Browse the repository at this point in the history
  • Loading branch information
abrander committed Nov 10, 2023
1 parent 0b6c25f commit 3ced953
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 31 deletions.
88 changes: 61 additions & 27 deletions Reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gguf

import (
"bytes"
"encoding/binary"
"fmt"
"io"
"os"
Expand All @@ -10,11 +11,18 @@ import (

// V1: https://github.com/philpax/ggml/blob/2b65fba00c83b9fa041df2ac55ccd8c2f10c5281/docs/gguf.md
// V2: https://github.com/philpax/ggml/blob/574b408f472923071fbc7a265c974c00ce01f959/docs/gguf.md
// V3: Like v2, but can store both little- and big-endian data.

// Reader is a reader for GGUF files.
type Reader struct {
r io.ReadSeeker

// ByteOrder is the byte order of the GGUF file. This package
// does not do any byte swapping for tensor data, it's the
// responsibility of you, the user, to make sure endian is
// correct or swapped.
ByteOrder binary.ByteOrder

// Version is the GGUF version.
Version int

Expand All @@ -27,7 +35,7 @@ type Reader struct {
tensorOffset int64

// Helper to read int32 or int64 depending on GGUF version.
readUint func(io.Reader) (uint64, error)
readUint func(io.Reader, binary.ByteOrder) (uint64, error)
}

// readString reads a GGUF string from r.
Expand All @@ -50,7 +58,7 @@ func (r *Reader) readString() (string, error) {
return false
}

length, err := r.readUint(r.r)
length, err := r.readUint(r.r, r.ByteOrder)
if err != nil {
return "", err
}
Expand All @@ -72,28 +80,28 @@ func (r *Reader) readString() (string, error) {
func (r *Reader) readMetaDataValueScalar(typ Type) (interface{}, error) {
switch typ {
case Uint8:
return read[uint8](r.r)
return read[uint8](r.r, r.ByteOrder)

case Int8:
return read[int8](r.r)
return read[int8](r.r, r.ByteOrder)

case Uint16:
return read[uint16](r.r)
return read[uint16](r.r, r.ByteOrder)

case Int16:
return read[int16](r.r)
return read[int16](r.r, r.ByteOrder)

case Uint32:
return read[uint32](r.r)
return read[uint32](r.r, r.ByteOrder)

case Int32:
return read[int32](r.r)
return read[int32](r.r, r.ByteOrder)

case Float32:
return read[float32](r.r)
return read[float32](r.r, r.ByteOrder)

case Bool:
i, err := read[uint8](r.r)
i, err := read[uint8](r.r, r.ByteOrder)

if i != 0 && i != 1 {
return nil, fmt.Errorf("invalid bool value: %d", i)
Expand All @@ -105,13 +113,13 @@ func (r *Reader) readMetaDataValueScalar(typ Type) (interface{}, error) {
return r.readString()

case Uint64:
return read[uint64](r.r)
return read[uint64](r.r, r.ByteOrder)

case Int64:
return read[int64](r.r)
return read[int64](r.r, r.ByteOrder)

case Float64:
return read[float64](r.r)
return read[float64](r.r, r.ByteOrder)

default:
return nil, fmt.Errorf("invalid scalar type: %d", typ)
Expand All @@ -123,7 +131,7 @@ func readMetaDataValueArray[T readables](r *Reader, length uint64) ([]T, error)
a := make([]T, length)

for i := uint64(0); i < length; i++ {
v, err := read[T](r.r)
v, err := read[T](r.r, r.ByteOrder)
if err != nil {
return nil, err
}
Expand All @@ -136,19 +144,19 @@ func readMetaDataValueArray[T readables](r *Reader, length uint64) ([]T, error)

// readMetaValue reads a GGUF metadata value from r.
func (r *Reader) readMetaValue() (interface{}, error) {
typ, err := read[Type](r.r)
typ, err := read[Type](r.r, r.ByteOrder)
if err != nil {
return nil, err
}

switch typ {
case Array:
aType, err := read[Type](r.r)
aType, err := read[Type](r.r, r.ByteOrder)
if err != nil {
return nil, err
}

length, err := r.readUint(r.r)
length, err := r.readUint(r.r, r.ByteOrder)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -249,33 +257,59 @@ func Open(readseeker io.ReadSeeker) (*Reader, error) {
return nil, fmt.Errorf("not a GGUF file, unknown magic: %q", buf)
}

version, err := read[uint32](readseeker)
// Jump to the last byte of the version, and check if this
// could be a big-endian file.
_, err = readseeker.Seek(3, io.SeekCurrent)
if err != nil {
return nil, err
}

bigEndianMarker := int8(0)
err = binary.Read(readseeker, binary.LittleEndian, &bigEndianMarker)
if err != nil {
return nil, err
}

var byteOrder binary.ByteOrder = binary.LittleEndian

if bigEndianMarker != 0 {
byteOrder = binary.BigEndian
}

// Jump back to read the version in file byteorder.
_, err = readseeker.Seek(-4, io.SeekCurrent)
if err != nil {
return nil, err
}

version, err := read[uint32](readseeker, byteOrder)
if err != nil {
return nil, err
}

r := &Reader{
r: readseeker,
Version: int(version),
r: readseeker,
ByteOrder: byteOrder,
Version: int(version),
}

switch version {
case 1:
r.readUint = readCast[uint32, uint64]

case 2:
case 2, 3:
r.readUint = read[uint64]

default:
return nil, fmt.Errorf("invalid version: %d", version)
}

tensorCount, err := r.readUint(readseeker)
tensorCount, err := r.readUint(readseeker, r.ByteOrder)
if err != nil {
return nil, err
}

metadataCount, err := r.readUint(readseeker)
metadataCount, err := r.readUint(readseeker, r.ByteOrder)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -322,28 +356,28 @@ func Open(readseeker io.ReadSeeker) (*Reader, error) {
return nil, err
}

nDimensions, err := read[uint32](readseeker)
nDimensions, err := read[uint32](readseeker, r.ByteOrder)
if err != nil {
return nil, err
}

r.Tensors[i].Dimensions = make([]uint64, nDimensions)

for j := uint32(0); j < nDimensions; j++ {
r.Tensors[i].Dimensions[j], err = r.readUint(readseeker)
r.Tensors[i].Dimensions[j], err = r.readUint(readseeker, r.ByteOrder)
if err != nil {
return nil, err
}
}

typ, err := read[uint32](readseeker)
typ, err := read[uint32](readseeker, r.ByteOrder)
if err != nil {
return nil, err
}

r.Tensors[i].Type = GGML(typ)

r.Tensors[i].Offset, err = r.readUint(readseeker)
r.Tensors[i].Offset, err = r.readUint(readseeker, r.ByteOrder)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions ggufmeta/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ func main() {
keys = append(keys, k)
}

fmt.Printf("File byte order: \033[33m%v\033[0m\n", g.ByteOrder.String())
fmt.Printf("File Version: \033[33m%d\033[0m\n", g.Version)

sort.StringSlice(keys).Sort()

for _, k := range keys {
Expand Down
8 changes: 4 additions & 4 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ type readables interface {
}

// read reads a value of type T from a binary stream.
func read[T readables](r io.Reader) (T, error) {
func read[T readables](r io.Reader, byteorder binary.ByteOrder) (T, error) {
var v T

err := binary.Read(r, binary.LittleEndian, &v)
err := binary.Read(r, byteorder, &v)

return v, err
}

// readCast reads a value of type T from a binary stream and casts it to type C.
func readCast[T, C readables](r io.Reader) (C, error) {
func readCast[T, C readables](r io.Reader, byteorder binary.ByteOrder) (C, error) {
var v T

err := binary.Read(r, binary.LittleEndian, &v)
err := binary.Read(r, byteorder, &v)

return C(v), err
}

0 comments on commit 3ced953

Please sign in to comment.