LLM for Unity  v2.3.0
Create characters in Unity with LLMs!
Loading...
Searching...
No Matches
LLMGGUF.cs
Go to the documentation of this file.
1
3using System;
4using System.Collections.Generic;
5using System.IO;
6using System.Runtime.InteropServices;
7
8namespace LLMUnity
9{
11 public enum GGUFValueType
12 {
13 UINT8 = 0,
14 INT8 = 1,
15 UINT16 = 2,
16 INT16 = 3,
17 UINT32 = 4,
18 INT32 = 5,
19 FLOAT32 = 6,
20 BOOL = 7,
21 STRING = 8,
22 ARRAY = 9,
23 UINT64 = 10,
24 INT64 = 11,
25 FLOAT64 = 12
26 }
27
28 public class ReaderField
29 {
30 public int offset;
31 public string name;
32 public List<Array> parts = new List<Array>();
33 public List<int> data = new List<int>();
34 public List<GGUFValueType> types = new List<GGUFValueType>();
35 }
36
37 public class ReaderTensor
38 {
39 public string name;
40 public GGUFValueType tensor_type;
41 public uint[] shape;
42 public int n_elements;
43 public int n_bytes;
44 public int data_offset;
45 public Array data;
46 public ReaderField field;
47 }
49
54 public class GGUFReader
55 {
56 private const uint GGUF_MAGIC = 0x46554747; // "GGUF"
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>
60 {
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) }
72 };
73
74 // private MemoryStream data;
75 private FileStream data;
77 public Dictionary<string, ReaderField> fields = new Dictionary<string, ReaderField>();
78
83 public GGUFReader(string path)
84 {
85 // data = new MemoryStream(File.ReadAllBytes(path));
86 data = new FileStream(path, FileMode.Open, FileAccess.Read);
87 int offs = 0;
88
89 if (BitConverter.ToUInt32(ReadBytes(offs, 4), 0) != GGUF_MAGIC)
90 throw new ArgumentException("GGUF magic invalid");
91 offs += 4;
92
93 uint temp_version = BitConverter.ToUInt32(ReadBytes(offs, 4));
94 if ((temp_version & 65535) == 0)
95 {
96 byte[] tempBytes = ReadBytes(offs, 4);
97 Array.Reverse(tempBytes);
98 temp_version = BitConverter.ToUInt32(tempBytes, 0);
99 }
100 uint version = temp_version;
101
102 if (!READER_SUPPORTED_VERSIONS.Contains((int)version))
103 throw new ArgumentException($"Sorry, file appears to be version {version} which we cannot handle");
104
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);
113 data.Close();
114 }
115
121 public ReaderField GetField(string key)
122 {
123 if (fields.TryGetValue(key, out ReaderField value))
124 return value;
125 return null;
126 }
127
133 public byte[] GetGenericField(string key)
134 {
135 ReaderField field = GetField(key);
136 if (field == null || field.parts.Count == 0) return null;
137 return (byte[])field.parts[field.parts.Count - 1];
138 }
139
145 public string GetStringField(string key)
146 {
147 byte[] value = GetGenericField(key);
148 if (value == null) return null;
149 return System.Text.Encoding.UTF8.GetString(value);
150 }
151
157 public int GetIntField(string key)
158 {
159 byte[] value = GetGenericField(key);
160 if (value == null) return -1;
161 return BitConverter.ToInt32(value, 0);
162 }
163
164 private byte[] ReadBytes(int offset, int count)
165 {
166 byte[] buffer = new byte[count];
167 data.Seek(offset, SeekOrigin.Begin);
168 data.Read(buffer, 0, count);
169 return buffer;
170 }
171
172 private int PushField(ReaderField field, bool skip_sum = false)
173 {
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;
177 if (skip_sum)
178 return 0;
179 int sum = 0;
180 for (int i = 0; i < field.parts.Count; i++)
181 {
182 Type partType = gguf_scalar_to_np[field.types[i]];
183 sum += Marshal.SizeOf(partType) * field.parts[i].Length;
184 }
185 return sum;
186 }
187
188 private (ulong[], byte[]) GetStr(int offset)
189 {
190 ulong slen = BitConverter.ToUInt64(ReadBytes(offset, 8));
191 byte[] sdata = ReadBytes(offset + 8, (int)slen);
192 return (new ulong[] { slen }, sdata);
193 }
194
195 private (int, List<Array>, List<int>, List<GGUFValueType>) GetFieldParts(int orig_offs, int raw_type)
196 {
197 int offs = orig_offs;
198 List<GGUFValueType> types = new List<GGUFValueType>();
199 types.Add((GGUFValueType)raw_type);
200 // Handle strings.
201 if ((GGUFValueType)raw_type == GGUFValueType.STRING)
202 {
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);
207 }
208
209 // Check if it's a simple scalar type.
210 if (gguf_scalar_to_np.TryGetValue((GGUFValueType)raw_type, out Type nptype))
211 {
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);
215 }
216
217 // Handle arrays.
218 if ((GGUFValueType)raw_type == GGUFValueType.ARRAY)
219 {
220 int raw_itype = BitConverter.ToInt32(ReadBytes(offs, 4));
221 offs += Marshal.SizeOf(typeof(int));
222
223 ulong alen = BitConverter.ToUInt64(ReadBytes(offs, 8));
224 offs += Marshal.SizeOf(typeof(ulong));
225
226 List<Array> aparts = new List<Array> { BitConverter.GetBytes(raw_itype), BitConverter.GetBytes(alen) };
227 List<int> data_idxs = new List<int>();
228
229 for (int idx = 0; idx < (int)alen; idx++)
230 {
231 (int curr_size, List<Array> curr_parts, List<int> curr_idxs, List<GGUFValueType> curr_types) = GetFieldParts(offs, raw_itype);
232 if (idx == 0)
233 types.AddRange(curr_types);
234
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)));
238 offs += curr_size;
239 }
240 return (offs - orig_offs, aparts, data_idxs, types);
241 }
242 // We can't deal with this one.
243 throw new ArgumentException($"Unknown/unhandled field type {(GGUFValueType)raw_type}");
244 }
245
246 private int BuildFields(int offs, int count)
247 {
248 for (int i = 0; i < count; i++)
249 {
250 int orig_offs = offs;
251 (ulong[] kv_klen, byte[] kv_kdata) = GetStr(offs);
252 offs += Marshal.SizeOf(typeof(ulong)) + kv_kdata.Length;
253
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 };
258
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)
261 continue;
262
263 parts.AddRange(field_parts);
264 ReaderField readerField = new ReaderField
265 {
266 offset = orig_offs,
267 name = System.Text.Encoding.UTF8.GetString(kv_kdata),
268 parts = parts,
269 data = new List<int>(field_idxs.ConvertAll(idx => idx + idxs_offs[0])),
270 types = field_types
271 };
272 PushField(readerField, skip_sum: true);
273 offs += field_size;
274 }
275 return offs;
276 }
277 }
278}
Class implementing the GGUF reader.
Definition LLMGGUF.cs:55
byte[] GetGenericField(string key)
Allows to retrieve a single-valued GGUF field.
Definition LLMGGUF.cs:133
GGUFReader(string path)
Constructor of the GGUF reader that parses a GGUF file and retrieves the fields.
Definition LLMGGUF.cs:83
int GetIntField(string key)
Allows to retrieve an integer GGUF field.
Definition LLMGGUF.cs:157
string GetStringField(string key)
Allows to retrieve a string GGUF field.
Definition LLMGGUF.cs:145
Dictionary< string, ReaderField > fields
Dictionary of GGUF fields to location info.
Definition LLMGGUF.cs:77
ReaderField GetField(string key)
Allows to retrieve location info for a GGUF field.
Definition LLMGGUF.cs:121