56 private const uint GGUF_MAGIC = 0x46554747;
57 private const int GGUF_VERSION = 3;
58 private readonly List<int> READER_SUPPORTED_VERSIONS =
new List<int> { 2, GGUF_VERSION };
59 private Dictionary<GGUFValueType, Type> gguf_scalar_to_np =
new Dictionary<GGUFValueType, Type>
61 { GGUFValueType.UINT8, typeof(
byte) },
62 { GGUFValueType.INT8, typeof(sbyte) },
63 { GGUFValueType.UINT16, typeof(ushort) },
64 { GGUFValueType.INT16, typeof(
short) },
65 { GGUFValueType.UINT32, typeof(uint) },
66 { GGUFValueType.INT32, typeof(
int) },
67 { GGUFValueType.FLOAT32, typeof(
float) },
68 { GGUFValueType.UINT64, typeof(ulong) },
69 { GGUFValueType.INT64, typeof(
long) },
70 { GGUFValueType.FLOAT64, typeof(
double) },
71 { GGUFValueType.BOOL, typeof(
bool) }
75 private FileStream data;
77 public Dictionary<string, ReaderField>
fields =
new Dictionary<string, ReaderField>();
86 data =
new FileStream(path, FileMode.Open, FileAccess.Read);
89 if (BitConverter.ToUInt32(ReadBytes(offs, 4), 0) != GGUF_MAGIC)
90 throw new ArgumentException(
"GGUF magic invalid");
93 uint temp_version = BitConverter.ToUInt32(ReadBytes(offs, 4));
94 if ((temp_version & 65535) == 0)
96 byte[] tempBytes = ReadBytes(offs, 4);
97 Array.Reverse(tempBytes);
98 temp_version = BitConverter.ToUInt32(tempBytes, 0);
100 uint version = temp_version;
102 if (!READER_SUPPORTED_VERSIONS.Contains((
int)version))
103 throw new ArgumentException($
"Sorry, file appears to be version {version} which we cannot handle");
105 offs += PushField(
new ReaderField { offset = offs, name =
"GGUF.version", parts =
new List<Array> {
new uint[] { temp_version } }, data =
new List<int> { 0 }, types =
new List<GGUFValueType> { GGUFValueType.UINT32 } });
106 ulong[] temp_counts =
new ulong[2];
107 Buffer.BlockCopy(ReadBytes(offs, 16), 0, temp_counts, 0, 16);
108 offs += PushField(
new ReaderField { offset = offs, name =
"GGUF.tensor_count", parts =
new List<Array> {
new ulong[] { temp_counts[0] } }, data =
new List<int> { 0 }, types =
new List<GGUFValueType> { GGUFValueType.UINT64 } });
109 offs += PushField(
new ReaderField { offset = offs, name =
"GGUF.kv_count", parts =
new List<Array> {
new ulong[] { temp_counts[1] } }, data =
new List<int> { 0 }, types =
new List<GGUFValueType> { GGUFValueType.UINT64 } });
110 ulong tensor_count = temp_counts[0];
111 ulong kv_count = temp_counts[1];
112 offs = BuildFields(offs, (
int)kv_count);
123 if (
fields.TryGetValue(key, out ReaderField value))
136 if (field ==
null || field.parts.Count == 0)
return null;
137 return (
byte[])field.parts[field.parts.Count - 1];
148 if (value ==
null)
return null;
149 return System.Text.Encoding.UTF8.GetString(value);
160 if (value ==
null)
return -1;
161 return BitConverter.ToInt32(value, 0);
164 private byte[] ReadBytes(
int offset,
int count)
166 byte[] buffer =
new byte[count];
167 data.Seek(offset, SeekOrigin.Begin);
168 data.Read(buffer, 0, count);
172 private int PushField(ReaderField field,
bool skip_sum =
false)
174 if (
fields.ContainsKey(field.name))
175 throw new ArgumentException($
"Duplicate {field.name} already in list at offset {field.offset}");
176 fields[field.name] = field;
180 for (
int i = 0; i < field.parts.Count; i++)
182 Type partType = gguf_scalar_to_np[field.types[i]];
183 sum += Marshal.SizeOf(partType) * field.parts[i].Length;
188 private (ulong[],
byte[]) GetStr(
int offset)
190 ulong slen = BitConverter.ToUInt64(ReadBytes(offset, 8));
191 byte[] sdata = ReadBytes(offset + 8, (
int)slen);
192 return (
new ulong[] { slen }, sdata);
195 private (int, List<Array>, List<int>, List<GGUFValueType>) GetFieldParts(
int orig_offs,
int raw_type)
197 int offs = orig_offs;
198 List<GGUFValueType> types =
new List<GGUFValueType>();
199 types.Add((GGUFValueType)raw_type);
201 if ((GGUFValueType)raw_type == GGUFValueType.STRING)
203 (ulong[] slen,
byte[] sdata) = GetStr(offs);
204 List<Array> sparts =
new List<Array> { slen, sdata };
205 int size = slen.Length *
sizeof(ulong) + sdata.Length;
206 return (size, sparts,
new List<int> { 1 }, types);
210 if (gguf_scalar_to_np.TryGetValue((GGUFValueType)raw_type, out Type nptype))
212 Array val = ReadBytes(offs, Marshal.SizeOf(nptype));
213 int size = nptype == typeof(
bool) ? 1 : Marshal.SizeOf(nptype);
214 return (size,
new List<Array> { val },
new List<int> { 0 }, types);
218 if ((GGUFValueType)raw_type == GGUFValueType.ARRAY)
220 int raw_itype = BitConverter.ToInt32(ReadBytes(offs, 4));
221 offs += Marshal.SizeOf(typeof(
int));
223 ulong alen = BitConverter.ToUInt64(ReadBytes(offs, 8));
224 offs += Marshal.SizeOf(typeof(ulong));
226 List<Array> aparts =
new List<Array> { BitConverter.GetBytes(raw_itype), BitConverter.GetBytes(alen) };
227 List<int> data_idxs =
new List<int>();
229 for (
int idx = 0; idx < (int)alen; idx++)
231 (
int curr_size, List<Array> curr_parts, List<int> curr_idxs, List<GGUFValueType> curr_types) = GetFieldParts(offs, raw_itype);
233 types.AddRange(curr_types);
235 int idxs_offs = aparts.Count;
236 aparts.AddRange(curr_parts);
237 data_idxs.AddRange(
new List<int>(curr_idxs.ConvertAll(i => i + idxs_offs)));
240 return (offs - orig_offs, aparts, data_idxs, types);
243 throw new ArgumentException($
"Unknown/unhandled field type {(GGUFValueType)raw_type}");
246 private int BuildFields(
int offs,
int count)
248 for (
int i = 0; i < count; i++)
250 int orig_offs = offs;
251 (ulong[] kv_klen,
byte[] kv_kdata) = GetStr(offs);
252 offs += Marshal.SizeOf(typeof(ulong)) + kv_kdata.Length;
254 int raw_kv_type = BitConverter.ToInt32(ReadBytes(offs, 4));
255 offs += Marshal.SizeOf(typeof(
int));
256 List<Array> parts =
new List<Array> { kv_klen, kv_kdata, BitConverter.GetBytes(raw_kv_type) };
257 List<int> idxs_offs =
new List<int> { parts.Count };
259 (
int field_size, List<Array> field_parts, List<int> field_idxs, List<GGUFValueType> field_types) = GetFieldParts(offs, raw_kv_type);
260 if (field_size == -1)
263 parts.AddRange(field_parts);
264 ReaderField readerField =
new ReaderField
267 name = System.Text.Encoding.UTF8.GetString(kv_kdata),
269 data =
new List<int>(field_idxs.ConvertAll(idx => idx + idxs_offs[0])),
272 PushField(readerField, skip_sum: true);