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 |