GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 97.5% 312 / 0 / 320
Functions: 100.0% 35 / 0 / 35
Branches: 66.9% 234 / 0 / 350

src/gguf.cpp
Line Branch Exec Source
1 #include "brotensor/gguf.h"
2
3 #include "brotensor/runtime.h"
4 #include "brotensor/tensor.h"
5
6 #include <cstdint>
7 #include <cstring>
8 #include <stdexcept>
9 #include <string>
10 #include <utility>
11 #include <vector>
12
13 #ifdef _WIN32
14 #define WIN32_LEAN_AND_MEAN
15 #define NOMINMAX
16 #include <windows.h>
17 #else
18 #include <fcntl.h>
19 #include <sys/mman.h>
20 #include <sys/stat.h>
21 #include <unistd.h>
22 #endif
23
24 namespace brotensor::gguf {
25
26 namespace {
27
28 35 [[noreturn]] void fail(const std::string& msg) {
29
4/8
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 35 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 35 times.
✓ Branch 6 taken 35 times.
✗ Branch 7 not taken.
35 throw std::runtime_error("gguf: " + msg);
30 35 }
31
32 // ─── mmap (platform) — duplicated from safetensors.cpp by design ────────────
33
34 28 struct Mapping {
35 28 void* file = nullptr;
36 28 void* mapping = nullptr;
37 28 void* base = nullptr;
38 28 std::size_t size = 0;
39 };
40
41 28 Mapping map_file(const std::string& path) {
42 28 Mapping m;
43 #ifdef _WIN32
44 HANDLE fh = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
45 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
46 if (fh == INVALID_HANDLE_VALUE) {
47 fail("cannot open '" + path + "'");
48 }
49 LARGE_INTEGER sz;
50 if (!GetFileSizeEx(fh, &sz)) {
51 CloseHandle(fh);
52 fail("GetFileSizeEx failed for '" + path + "'");
53 }
54 HANDLE mh = CreateFileMappingA(fh, nullptr, PAGE_READONLY, 0, 0, nullptr);
55 if (!mh) {
56 CloseHandle(fh);
57 fail("CreateFileMapping failed for '" + path + "'");
58 }
59 void* base = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, 0);
60 if (!base) {
61 CloseHandle(mh);
62 CloseHandle(fh);
63 fail("MapViewOfFile failed for '" + path + "'");
64 }
65 m.file = fh;
66 m.mapping = mh;
67 m.base = base;
68 m.size = static_cast<std::size_t>(sz.QuadPart);
69 #else
70 28 int fd = ::open(path.c_str(), O_RDONLY);
71
4/6
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
28 if (fd < 0) fail("cannot open '" + path + "'");
72 27 struct stat st{};
73
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
27 if (fstat(fd, &st) != 0) {
74 ::close(fd);
75 fail("fstat failed for '" + path + "'");
76 }
77 27 void* base = ::mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
78
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
27 if (base == MAP_FAILED) {
79 ::close(fd);
80 fail("mmap failed for '" + path + "'");
81 }
82 27 m.file = reinterpret_cast<void*>(static_cast<intptr_t>(fd));
83 27 m.mapping = nullptr;
84 27 m.base = base;
85 27 m.size = static_cast<std::size_t>(st.st_size);
86 #endif
87 27 return m;
88 1 }
89
90 29 void unmap_file(Mapping& m) noexcept {
91 #ifdef _WIN32
92 if (m.base) UnmapViewOfFile(m.base);
93 if (m.mapping) CloseHandle(m.mapping);
94 if (m.file) CloseHandle(m.file);
95 #else
96
4/6
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 27 times.
✗ Branch 5 not taken.
29 if (m.base && m.size) ::munmap(m.base, m.size);
97
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
29 if (m.file) ::close(static_cast<int>(reinterpret_cast<intptr_t>(m.file)));
98 #endif
99 29 m = Mapping{};
100 29 }
101
102 // ─── ggml type table ────────────────────────────────────────────────────────
103
104 struct GgmlTypeInfo {
105 brotensor::Dtype dtype;
106 bool supported;
107 int block_size;
108 int type_size;
109 };
110
111 81 GgmlTypeInfo ggml_type_info(uint32_t t) {
112
16/16
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 19 times.
✓ Branch 8 taken 6 times.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 4 times.
✓ Branch 11 taken 4 times.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 4 times.
✓ Branch 14 taken 4 times.
✓ Branch 15 taken 4 times.
81 switch (t) {
113 19 case 0: return { brotensor::Dtype::FP32, true, 1, 4 };
114 4 case 1: return { brotensor::Dtype::FP16, true, 1, 2 };
115 6 case 2: return { brotensor::Dtype::Q4_0, true, 32, 18 };
116 4 case 3: return { brotensor::Dtype::Q4_1, true, 32, 20 };
117 4 case 6: return { brotensor::Dtype::Q5_0, true, 32, 22 };
118 4 case 7: return { brotensor::Dtype::Q5_1, true, 32, 24 };
119 4 case 8: return { brotensor::Dtype::Q8_0, true, 32, 34 };
120 4 case 9: return { brotensor::Dtype::Q8_1, true, 32, 36 };
121 4 case 10: return { brotensor::Dtype::Q2_K, true, 256, 82 };
122 4 case 11: return { brotensor::Dtype::Q3_K, true, 256, 110 };
123 4 case 12: return { brotensor::Dtype::Q4_K, true, 256, 144 };
124 4 case 13: return { brotensor::Dtype::Q5_K, true, 256, 176 };
125 4 case 14: return { brotensor::Dtype::Q6_K, true, 256, 210 };
126 4 case 15: return { brotensor::Dtype::Q8_K, true, 256, 292 };
127 4 case 30: return { brotensor::Dtype::BF16, true, 1, 2 };
128 4 default: return { brotensor::Dtype::FP32, false, 0, 0 };
129 }
130 81 }
131
132 // ─── Reader ────────────────────────────────────────────────────────────────
133
134 struct Reader {
135 const uint8_t* base = nullptr;
136 std::size_t pos = 0;
137 std::size_t size = 0;
138
139 977 void need(std::size_t n) const {
140 // Subtract rather than add: `pos + n` wraps for a corrupt/hostile n near
141 // SIZE_MAX (string and array lengths come straight off disk as u64), and
142 // a wrapped sum silently passes the check. pos <= size is an invariant.
143
3/4
✓ Branch 0 taken 976 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
977 if (n > size - pos) fail("unexpected EOF");
144 977 }
145
146 25 uint8_t read_u8() {
147 25 need(1);
148 25 return base[pos++];
149 }
150 3 int8_t read_i8() {
151 3 return static_cast<int8_t>(read_u8());
152 }
153 6 uint16_t read_u16() {
154 6 need(2);
155 uint16_t v;
156 6 std::memcpy(&v, base + pos, 2);
157 6 pos += 2;
158 6 return v;
159 }
160 3 int16_t read_i16() { uint16_t v = read_u16(); int16_t r; std::memcpy(&r, &v, 2); return r; }
161 327 uint32_t read_u32() {
162 327 need(4);
163 uint32_t v;
164 327 std::memcpy(&v, base + pos, 4);
165 327 pos += 4;
166 327 return v;
167 }
168 12 int32_t read_i32() { uint32_t v = read_u32(); int32_t r; std::memcpy(&r, &v, 4); return r; }
169 460 uint64_t read_u64() {
170 460 need(8);
171 uint64_t v;
172 460 std::memcpy(&v, base + pos, 8);
173 460 pos += 8;
174 460 return v;
175 }
176 3 int64_t read_i64() { uint64_t v = read_u64(); int64_t r; std::memcpy(&r, &v, 8); return r; }
177 4 float read_f32() { uint32_t v = read_u32(); float r; std::memcpy(&r, &v, 4); return r; }
178 3 double read_f64() { uint64_t v = read_u64(); double r; std::memcpy(&r, &v, 8); return r; }
179 7 bool read_bool() {
180 7 uint8_t b = read_u8();
181
5/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
7 if (b != 0 && b != 1) fail("bool value not 0 or 1");
182 6 return b != 0;
183 1 }
184 160 std::string read_str() {
185 160 uint64_t len = read_u64();
186 160 need(static_cast<std::size_t>(len));
187 160 std::string s(reinterpret_cast<const char*>(base + pos), static_cast<std::size_t>(len));
188 160 pos += static_cast<std::size_t>(len);
189 160 return s;
190 160 }
191 };
192
193 Value parse_value(Reader& r, ValueType type);
194
195 76 Value parse_scalar(Reader& r, ValueType type) {
196 76 Value v;
197 76 v.type = type;
198
12/14
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 12 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 10 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 15 times.
76 switch (type) {
199
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15 case ValueType::U8: v.scalar.u8 = r.read_u8(); break;
200
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case ValueType::I8: v.scalar.i8 = r.read_i8(); break;
201
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case ValueType::U16: v.scalar.u16 = r.read_u16(); break;
202
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case ValueType::I16: v.scalar.i16 = r.read_i16(); break;
203
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9 case ValueType::U32: v.scalar.u32 = r.read_u32(); break;
204
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 case ValueType::I32: v.scalar.i32 = r.read_i32(); break;
205
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 case ValueType::F32: v.scalar.f32 = r.read_f32(); break;
206
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 time.
7 case ValueType::Bool: v.scalar.b = r.read_bool();break;
207
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 case ValueType::U64: v.scalar.u64 = r.read_u64(); break;
208
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case ValueType::I64: v.scalar.i64 = r.read_i64(); break;
209
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 case ValueType::F64: v.scalar.f64 = r.read_f64(); break;
210 case ValueType::String:
211
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 v.str = r.read_str();
212 10 break;
213 case ValueType::Array:
214 // Caller dispatches arrays via parse_value.
215 fail("internal: parse_scalar called with Array");
216 }
217 75 return v;
218 76 }
219
220 95 Value parse_value(Reader& r, ValueType type) {
221
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 19 times.
95 if (type != ValueType::Array) {
222 76 return parse_scalar(r, type);
223 }
224 19 Value v;
225 19 v.type = ValueType::Array;
226
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 uint32_t elem_raw = r.read_u32();
227
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
19 if (elem_raw > 12) fail("array element type out of range");
228 18 v.array_elem_type = static_cast<ValueType>(elem_raw);
229
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 uint64_t len = r.read_u64();
230
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 v.array.reserve(static_cast<std::size_t>(len));
231
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 33 times.
51 for (uint64_t i = 0; i < len; ++i) {
232
2/4
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
✗ Branch 3 not taken.
33 v.array.push_back(parse_value(r, v.array_elem_type));
233 33 }
234 18 return v;
235 113 }
236
237 14 std::size_t align_up(std::size_t x, std::size_t a) {
238
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 if (a == 0) return x;
239 14 return (x + (a - 1)) & ~(a - 1);
240 14 }
241
242 } // namespace
243
244 // ─── shape_to_2d ────────────────────────────────────────────────────────────
245
246 20 std::pair<int, int> shape_to_2d(const std::vector<int64_t>& shape) {
247
3/4
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
24 if (shape.empty()) fail("shape_to_2d: empty shape");
248
2/2
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 16 times.
60 for (int64_t d : shape) {
249
3/4
✓ Branch 0 taken 42 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
44 if (d <= 0) fail("shape_to_2d: non-positive dimension");
250
3/4
✓ Branch 0 taken 41 times.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
42 if (d > 0x7FFFFFFF) fail("shape_to_2d: dimension exceeds int range");
251 }
252
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
16 if (shape.size() == 1) {
253 4 return { static_cast<int>(shape[0]), 1 };
254 }
255 12 int64_t rows = 1;
256
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 25 times.
36 for (std::size_t i = 1; i < shape.size(); ++i) {
257
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 1 time.
25 if (rows > (int64_t{0x7FFFFFFF}) / shape[i]) {
258
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 fail("shape_to_2d: row product overflows int");
259 }
260 24 rows *= shape[i];
261 24 }
262 11 return { static_cast<int>(rows), static_cast<int>(shape[0]) };
263 20 }
264
265 // ─── File ──────────────────────────────────────────────────────────────────
266
267 54 File::~File() { release_(); }
268
269 2 File::File(File&& o) noexcept
270 1 : file_(o.file_), mapping_(o.mapping_), base_(o.base_),
271 1 file_size_(o.file_size_),
272 1 version_(o.version_), alignment_(o.alignment_),
273 1 metadata_(std::move(o.metadata_)),
274 1 meta_index_(std::move(o.meta_index_)),
275 1 tensors_(std::move(o.tensors_)),
276 2 tensor_index_(std::move(o.tensor_index_)) {
277 1 o.file_ = o.mapping_ = o.base_ = nullptr;
278 1 o.file_size_ = 0;
279 1 o.version_ = 0;
280 1 o.alignment_ = 32;
281 2 }
282
283 1 File& File::operator=(File&& o) noexcept {
284
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 if (this != &o) {
285 1 release_();
286 1 file_ = o.file_;
287 1 mapping_ = o.mapping_;
288 1 base_ = o.base_;
289 1 file_size_ = o.file_size_;
290 1 version_ = o.version_;
291 1 alignment_ = o.alignment_;
292 1 metadata_ = std::move(o.metadata_);
293 1 meta_index_ = std::move(o.meta_index_);
294 1 tensors_ = std::move(o.tensors_);
295 1 tensor_index_ = std::move(o.tensor_index_);
296 1 o.file_ = o.mapping_ = o.base_ = nullptr;
297 1 o.file_size_ = 0;
298 1 o.version_ = 0;
299 1 o.alignment_ = 32;
300 1 }
301 1 return *this;
302 }
303
304 28 void File::release_() noexcept {
305 28 Mapping m{ file_, mapping_, base_, file_size_ };
306 28 unmap_file(m);
307 28 file_ = mapping_ = base_ = nullptr;
308 28 file_size_ = 0;
309 28 version_ = 0;
310 28 alignment_ = 32;
311 28 metadata_.clear();
312 28 meta_index_.clear();
313 28 tensors_.clear();
314 28 tensor_index_.clear();
315 28 }
316
317 28 File File::open(const std::string& path) {
318 28 Mapping m = map_file(path);
319
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 1 time.
28 if (m.size < 24) {
320 1 unmap_file(m);
321
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
18 fail("file too small: '" + path + "'");
322 }
323
324 27 File f;
325 27 f.file_ = m.file;
326 27 f.mapping_ = m.mapping;
327 27 f.base_ = m.base;
328 27 f.file_size_ = m.size;
329 // From here on, f's destructor unmaps on throw.
330
331 27 Reader r{ static_cast<const uint8_t*>(m.base), 0, m.size };
332
333
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 1 time.
27 uint32_t magic = r.read_u32();
334
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 24 times.
26 if (magic != 0x46554747u) {
335
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 fail("bad magic (not a GGUF file)");
336 }
337
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 uint32_t version = r.read_u32();
338
4/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 22 times.
24 if (version != 2 && version != 3) {
339
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 fail("unsupported GGUF version " + std::to_string(version));
340 }
341 23 f.version_ = version;
342
343
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 uint64_t tensor_count = r.read_u64();
344
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 uint64_t meta_count = r.read_u64();
345
346
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 f.metadata_.reserve(static_cast<std::size_t>(meta_count));
347
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 17 times.
80 for (uint64_t i = 0; i < meta_count; ++i) {
348
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 std::string key = r.read_str();
349
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 uint32_t vt_raw = r.read_u32();
350
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 62 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
63 if (vt_raw > 12) fail("metadata value type out of range");
351
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 2 times.
62 Value v = parse_value(r, static_cast<ValueType>(vt_raw));
352
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 4 times.
60 if (key == "general.alignment") {
353
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if (v.type != ValueType::U32) {
354
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 fail("general.alignment must be u32");
355 }
356 3 if (v.scalar.u32 == 0 || (v.scalar.u32 & (v.scalar.u32 - 1)) != 0) {
357 fail("general.alignment must be a positive power of two");
358 }
359 3 f.alignment_ = v.scalar.u32;
360 3 }
361
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 2 times.
59 f.meta_index_.emplace(key, f.metadata_.size());
362
1/2
✓ Branch 0 taken 57 times.
✗ Branch 1 not taken.
57 f.metadata_.emplace_back(std::move(key), std::move(v));
363 63 }
364
365 // Tensor infos.
366 86 struct PendingTensor {
367 std::string name;
368 std::vector<int64_t> shape;
369 86 uint32_t ggml_type = 0;
370 86 uint64_t offset = 0;
371 };
372 17 std::vector<PendingTensor> pending;
373
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 3 times.
17 pending.reserve(static_cast<std::size_t>(tensor_count));
374
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 90 times.
104 for (uint64_t i = 0; i < tensor_count; ++i) {
375 90 PendingTensor pt;
376
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 4 times.
90 pt.name = r.read_str();
377
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 2 times.
86 uint32_t n_dims = r.read_u32();
378
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
84 if (n_dims == 0 || n_dims > 8) fail("invalid tensor n_dims");
379
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 pt.shape.resize(n_dims);
380
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 142 times.
226 for (uint32_t d = 0; d < n_dims; ++d) {
381
1/2
✓ Branch 0 taken 142 times.
✗ Branch 1 not taken.
142 pt.shape[d] = static_cast<int64_t>(r.read_u64());
382 142 }
383
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 pt.ggml_type = r.read_u32();
384
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 pt.offset = r.read_u64();
385
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84 pending.push_back(std::move(pt));
386 86 }
387
388 // Align to alignment_ for the data blob.
389
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 const std::size_t data_blob_start = align_up(r.pos, f.alignment_);
390
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
14 if (data_blob_start > m.size) fail("data blob start past end of file");
391
392
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 3 times.
13 f.tensors_.reserve(pending.size());
393
2/2
✓ Branch 0 taken 83 times.
✓ Branch 1 taken 6 times.
89 for (auto& pt : pending) {
394 83 TensorInfo info;
395 83 info.name = std::move(pt.name);
396 83 info.shape = std::move(pt.shape);
397 83 info.ggml_type = pt.ggml_type;
398
399 83 int64_t numel = 1;
400
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 81 times.
222 for (int64_t d : info.shape) {
401
5/8
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 140 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
141 if (d <= 0) fail("tensor '" + info.name + "' has non-positive dim");
402
4/6
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 139 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 time.
140 if (numel > INT64_MAX / d) fail("tensor numel overflows int64");
403 139 numel *= d;
404 }
405 81 info.numel = numel;
406
407
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 1 time.
81 GgmlTypeInfo gt = ggml_type_info(info.ggml_type);
408 80 info.dtype = gt.dtype;
409 80 info.dtype_supported = gt.supported;
410
411 // An unsupported ggml type is the only case with supported == false, and
412 // ggml_type_info's default arm returns block_size == type_size == 0 for
413 // it — so nbytes stays 0 and the tensor is indexed but not sized.
414 80 std::size_t nbytes = 0;
415
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 77 times.
80 if (gt.supported) {
416
4/4
✓ Branch 0 taken 50 times.
✓ Branch 1 taken 27 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 49 times.
77 if (gt.block_size > 1 && (numel % gt.block_size) != 0) {
417
3/6
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
1 fail("tensor '" + info.name + "' numel not multiple of block size");
418 }
419 228 nbytes = (static_cast<std::size_t>(numel) /
420 152 static_cast<std::size_t>(gt.block_size)) *
421 76 static_cast<std::size_t>(gt.type_size);
422 76 }
423 79 info.nbytes = nbytes;
424
425 // pt.offset is a u64 straight off disk. Bound it *before* adding, and
426 // bound nbytes against the remaining span: `data_blob_start + offset`
427 // wraps for a corrupt offset near SIZE_MAX, and a wrapped sum lands back
428 // inside the mapping — silently aliasing a tensor onto the wrong bytes
429 // instead of failing.
430 79 const std::size_t rel_off = static_cast<std::size_t>(pt.offset);
431 79 if (pt.offset > m.size || rel_off > m.size - data_blob_start) {
432 fail("tensor '" + info.name + "' data out of bounds");
433 }
434 79 const std::size_t abs_off = data_blob_start + rel_off;
435
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 79 times.
79 if (nbytes > m.size - abs_off) {
436 fail("tensor '" + info.name + "' data out of bounds");
437 }
438 79 info.data = static_cast<const uint8_t*>(m.base) + abs_off;
439
440
1/2
✓ Branch 0 taken 79 times.
✗ Branch 1 not taken.
79 f.tensor_index_.emplace(info.name, f.tensors_.size());
441
1/2
✓ Branch 0 taken 79 times.
✗ Branch 1 not taken.
79 f.tensors_.push_back(std::move(info));
442 83 }
443
444 6 return f;
445 57 }
446
447 44 const TensorInfo* File::find_tensor(std::string_view name) const {
448 44 auto it = tensor_index_.find(name);
449
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 39 times.
44 if (it == tensor_index_.end()) return nullptr;
450 39 return &tensors_[it->second];
451 44 }
452
453 20 const TensorInfo& File::get_tensor(std::string_view name) const {
454 20 const TensorInfo* t = find_tensor(name);
455
5/8
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 2 times.
20 if (!t) fail("no tensor named '" + std::string(name) + "'");
456 18 return *t;
457 2 }
458
459 23 const Value* File::find_meta(std::string_view key) const {
460 23 auto it = meta_index_.find(key);
461
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 20 times.
23 if (it == meta_index_.end()) return nullptr;
462 20 return &metadata_[it->second].second;
463 23 }
464
465 20 const Value& File::get_meta(std::string_view key) const {
466 20 const Value* v = find_meta(key);
467
5/8
✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 time.
20 if (!v) fail("no metadata key '" + std::string(key) + "'");
468 19 return *v;
469 1 }
470
471 // ─── upload_raw ────────────────────────────────────────────────────────────
472
473 16 void upload_raw(const TensorInfo& info, int rows, int cols,
474 brotensor::Tensor& dst) {
475
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1 time.
16 if (!info.dtype_supported) {
476
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
9 fail("upload_raw: tensor '" + info.name +
477
2/4
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 "' has unsupported ggml_type " + std::to_string(info.ggml_type));
478 }
479
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
15 if (rows <= 0 || cols <= 0) {
480
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 fail("upload_raw: rows/cols must be positive");
481 }
482 12 const int64_t prod = static_cast<int64_t>(rows) * static_cast<int64_t>(cols);
483
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 if (prod != info.numel) {
484
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
4 fail("upload_raw: rows*cols (" + std::to_string(prod) +
485
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 ") != info.numel (" + std::to_string(info.numel) + ")");
486 }
487
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 if (brotensor::dtype_is_quant(info.dtype)) {
488 6 const int bs = brotensor::dtype_block_size(info.dtype);
489
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if (cols % bs != 0) {
490
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 fail("upload_raw: cols not a multiple of block size for quant dtype");
491 }
492 4 }
493
494 // info.data points into the mmap'd, host-resident GGUF file — a valid
495 // H2D source as-is. Upload straight from it (single copy) instead of
496 // staging through an intermediate CPU tensor and then migrating.
497 8 const brotensor::Device target = brotensor::default_device();
498 8 dst = brotensor::Tensor::from_raw_bytes_on(
499 8 target, info.data, rows, cols, info.dtype, info.nbytes);
500 16 }
501
502 } // namespace brotensor::gguf
503