src/dispatch.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Backend registration + per-operand dispatch: the dispatcher core. | ||
| 2 | // | ||
| 3 | // Storage is two parallel std::array<>s indexed by static_cast<int>(Device). | ||
| 4 | // Registration happens during static init (CPU) and at brotensor::init() | ||
| 5 | // time (CUDA/Metal probe success). Both are single-threaded entry points | ||
| 6 | // before any op call, so no mutex is required. | ||
| 7 | |||
| 8 | #include <brotensor/detail/dispatch.h> | ||
| 9 | #include <brotensor/tensor.h> | ||
| 10 | #include <brotensor/runtime.h> | ||
| 11 | |||
| 12 | #include <array> | ||
| 13 | #include <cstddef> | ||
| 14 | #include <cstring> | ||
| 15 | #include <initializer_list> | ||
| 16 | #include <stdexcept> | ||
| 17 | #include <string> | ||
| 18 | |||
| 19 | #if defined(BROTENSOR_HAS_CUDA) | ||
| 20 | #include <cuda_runtime.h> | ||
| 21 | #endif | ||
| 22 | |||
| 23 | namespace brotensor::detail { | ||
| 24 | |||
| 25 | namespace { | ||
| 26 | |||
| 27 | constexpr int kNumDevices = 5; // CPU, CUDA, Metal, (3 unused), VULKAN | ||
| 28 | |||
| 29 | // Upper bound on operands a single dispatch call inspects. The widest op is | ||
| 30 | // resblock_backward (25 operands); 32 leaves headroom. dispatch_with_opts | ||
| 31 | // throws if a future op ever exceeds this rather than silently truncating. | ||
| 32 | constexpr std::size_t kMaxOperands = 32; | ||
| 33 | |||
| 34 | struct Slot { | ||
| 35 | OpsVTable ops{}; | ||
| 36 | AllocVTable alloc{}; | ||
| 37 | bool registered = false; | ||
| 38 | }; | ||
| 39 | |||
| 40 | 338972 | std::array<Slot, kNumDevices>& slots() { | |
| 41 | static std::array<Slot, kNumDevices> s{}; | ||
| 42 | 338972 | return s; | |
| 43 | } | ||
| 44 | |||
| 45 | 13 | const char* dev_name(Device d) { | |
| 46 | 13 | return device_name(d); | |
| 47 | } | ||
| 48 | |||
| 49 | 2 | [[noreturn]] void throw_unregistered(Device d) { | |
| 50 | 2 | std::string msg = "brotensor: backend "; | |
| 51 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += dev_name(d); |
| 52 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | msg += " not registered"; |
| 53 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | throw std::runtime_error(msg); |
| 54 | 2 | } | |
| 55 | |||
| 56 | // A tensor is "committed" to a device once it owns real storage. A freshly | ||
| 57 | // default-constructed (or resized-to-empty) tensor has data == nullptr and | ||
| 58 | // carries no device affinity yet — it is a wildcard that adopts the op's | ||
| 59 | // device. This is what lets callers pass an unsized output tensor to a GPU op | ||
| 60 | // without first pinning it to the right backend by hand. | ||
| 61 | 250818 | inline bool committed(const Tensor& t) { return t.data != nullptr; } | |
| 62 | |||
| 63 | 4 | [[noreturn]] void throw_device_mismatch(Device resolved, int idx, Device got) { | |
| 64 | 4 | std::string msg = "brotensor: dispatch: operand "; | |
| 65 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | msg += std::to_string(idx); |
| 66 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | msg += " is on "; |
| 67 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | msg += dev_name(got); |
| 68 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | msg += " but the op resolved to "; |
| 69 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | msg += dev_name(resolved); |
| 70 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
4 | throw std::runtime_error(msg); |
| 71 | 4 | } | |
| 72 | |||
| 73 | 75374 | inline void activate_device_context(Device dev) { | |
| 74 | 75374 | dev = resolve_device_alias(dev); | |
| 75 | #if defined(BROTENSOR_HAS_CUDA) | ||
| 76 | if (dev.is_cuda()) { | ||
| 77 | cudaSetDevice(dev.index); | ||
| 78 | } | ||
| 79 | #endif | ||
| 80 | (void)dev; | ||
| 81 | 75374 | } | |
| 82 | |||
| 83 | // Resolve the op's device from the first committed operand; verify every other | ||
| 84 | // committed operand agrees (uncommitted operands are skipped). Returns the | ||
| 85 | // vtable for the resolved device. | ||
| 86 | 73584 | const OpsVTable& dispatch_v(std::initializer_list<const Tensor*> ts) { | |
| 87 | 73584 | Device dev = Device::CPU; | |
| 88 | 73584 | bool found = false; | |
| 89 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 73588 times.
|
73606 | for (const Tensor* t : ts) { |
| 90 |
3/4✓ Branch 0 taken 73588 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 73566 times.
|
73588 | if (t && committed(*t)) { dev = t->device; found = true; break; } |
| 91 | } | ||
| 92 |
4/6✓ Branch 0 taken 18 times.
✓ Branch 1 taken 73566 times.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
|
73584 | if (!found && ts.size() != 0 && *ts.begin() != nullptr) { |
| 93 | // All operands empty — degenerate but not an error (e.g. a fresh | ||
| 94 | // output passed before any input). Keep operand 0's tag. | ||
| 95 | 18 | dev = (*ts.begin())->device; | |
| 96 | 18 | } | |
| 97 | 73584 | int idx = 0; | |
| 98 |
2/2✓ Branch 0 taken 166273 times.
✓ Branch 1 taken 73582 times.
|
239855 | for (const Tensor* t : ts) { |
| 99 |
5/6✓ Branch 0 taken 166273 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 109792 times.
✓ Branch 3 taken 56481 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 109790 times.
|
166273 | if (t && committed(*t) && t->device != dev) { |
| 100 | 2 | throw_device_mismatch(dev, idx, t->device); | |
| 101 | } | ||
| 102 | 166271 | ++idx; | |
| 103 | } | ||
| 104 | 73582 | activate_device_context(dev); | |
| 105 | 73582 | return ops_for(dev); | |
| 106 | } | ||
| 107 | |||
| 108 | // Same resolution rule as dispatch_v, over a plain operand array — used by the | ||
| 109 | // optional-operand dispatch path so it needs no heap allocation. Null entries | ||
| 110 | // (skipped optional operands) are tolerated. | ||
| 111 | 1794 | const OpsVTable& resolve_over(const Tensor* const* all, std::size_t count) { | |
| 112 | 1794 | Device dev = Device::CPU; | |
| 113 | 1794 | bool found = false; | |
| 114 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1798 times.
|
1800 | for (std::size_t i = 0; i < count; ++i) { |
| 115 |
4/4✓ Branch 0 taken 1796 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1792 times.
|
1798 | if (all[i] && committed(*all[i])) { |
| 116 | 1792 | dev = all[i]->device; | |
| 117 | 1792 | found = true; | |
| 118 | 1792 | break; | |
| 119 | } | ||
| 120 | 6 | } | |
| 121 |
2/2✓ Branch 0 taken 1792 times.
✓ Branch 1 taken 2 times.
|
1794 | if (!found) { |
| 122 | // No committed operand — adopt the first non-null operand's tag. | ||
| 123 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | for (std::size_t i = 0; i < count; ++i) { |
| 124 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | if (all[i]) { dev = all[i]->device; break; } |
| 125 | ✗ | } | |
| 126 | 2 | } | |
| 127 |
2/2✓ Branch 0 taken 10830 times.
✓ Branch 1 taken 1792 times.
|
12622 | for (std::size_t i = 0; i < count; ++i) { |
| 128 |
6/6✓ Branch 0 taken 9161 times.
✓ Branch 1 taken 1669 times.
✓ Branch 2 taken 7820 times.
✓ Branch 3 taken 1341 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 7818 times.
|
10830 | if (all[i] && committed(*all[i]) && all[i]->device != dev) { |
| 129 | 2 | throw_device_mismatch(dev, static_cast<int>(i), all[i]->device); | |
| 130 | } | ||
| 131 | 10828 | } | |
| 132 | 1792 | activate_device_context(dev); | |
| 133 | 1792 | return ops_for(dev); | |
| 134 | } | ||
| 135 | |||
| 136 | 1 | [[noreturn]] void throw_too_many_operands(std::size_t n) { | |
| 137 |
3/6✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
|
2 | throw std::runtime_error( |
| 138 |
3/6✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
|
1 | "brotensor: dispatch: operand count " + std::to_string(n) + |
| 139 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | " exceeds the fixed dispatch buffer (" + |
| 140 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
2 | std::to_string(kMaxOperands) + ")"); |
| 141 | 1 | } | |
| 142 | |||
| 143 | } // namespace | ||
| 144 | |||
| 145 | static int g_cuda_device_count = 0; | ||
| 146 | static int g_vulkan_device_count = 0; | ||
| 147 | |||
| 148 | ✗ | void set_cuda_device_count(int count) { | |
| 149 | ✗ | g_cuda_device_count = count; | |
| 150 | ✗ | } | |
| 151 | |||
| 152 | ✗ | void set_vulkan_device_count(int count) { | |
| 153 | ✗ | g_vulkan_device_count = count; | |
| 154 | ✗ | } | |
| 155 | |||
| 156 | 347 | void register_backend(DeviceType dt, const OpsVTable& ops, const AllocVTable& alloc) { | |
| 157 | 347 | auto& s = slots()[static_cast<int>(dt)]; | |
| 158 | 347 | std::memcpy(&s.ops, &ops, sizeof(OpsVTable)); | |
| 159 | 347 | std::memcpy(&s.alloc, &alloc, sizeof(AllocVTable)); | |
| 160 | 347 | s.registered = true; | |
| 161 | 347 | } | |
| 162 | |||
| 163 | 347 | void register_backend(Device d, const OpsVTable& ops, const AllocVTable& alloc) { | |
| 164 | 347 | register_backend(d.type, ops, alloc); | |
| 165 | 347 | } | |
| 166 | |||
| 167 | 14586 | bool is_registered(Device d) { | |
| 168 |
2/2✓ Branch 0 taken 4639 times.
✓ Branch 1 taken 9947 times.
|
14586 | if (d.is_cpu()) return slots()[static_cast<int>(DeviceType::CPU)].registered; |
| 169 |
2/2✓ Branch 0 taken 4936 times.
✓ Branch 1 taken 5011 times.
|
9947 | if (d.is_cuda()) { |
| 170 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4936 times.
|
4936 | if (!slots()[static_cast<int>(DeviceType::CUDA)].registered) return false; |
| 171 | ✗ | int count = ::brotensor::cuda_device_count(); | |
| 172 | ✗ | if (count <= 0) count = 1; | |
| 173 | ✗ | return d.index >= 0 && d.index < count; | |
| 174 | } | ||
| 175 |
2/2✓ Branch 0 taken 4994 times.
✓ Branch 1 taken 17 times.
|
5011 | if (d.is_metal()) { |
| 176 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 4979 times.
|
4994 | return slots()[static_cast<int>(DeviceType::Metal)].registered && d.index == 0; |
| 177 | } | ||
| 178 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | if (d.is_vulkan()) { |
| 179 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 17 times.
|
17 | if (!slots()[static_cast<int>(DeviceType::VULKAN)].registered) return false; |
| 180 | ✗ | return d.index >= 0 && d.index < ::brotensor::vulkan_device_count(); | |
| 181 | } | ||
| 182 | ✗ | return false; | |
| 183 | 14586 | } | |
| 184 | |||
| 185 | 553538 | Device resolve_device_alias(Device d) { | |
| 186 |
3/4✓ Branch 0 taken 2601 times.
✓ Branch 1 taken 550937 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2601 times.
|
553538 | if (d.is_cuda() && !slots()[static_cast<int>(DeviceType::CUDA)].registered) { |
| 187 | 2601 | const bool metal = slots()[static_cast<int>(DeviceType::Metal)].registered; | |
| 188 | 2601 | const bool vulkan = slots()[static_cast<int>(DeviceType::VULKAN)].registered; | |
| 189 | // With both, the default device decides (a Metal + Vulkan build run | ||
| 190 | // with BROTENSOR_DEFAULT_DEVICE=vulkan means Vulkan); otherwise Metal | ||
| 191 | // first, the order pick_default_from_available() uses. | ||
| 192 |
3/6✓ Branch 0 taken 2601 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2601 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2601 times.
|
2601 | if (metal && vulkan && ::brotensor::default_device().is_vulkan()) return Device::vulkan(d.index); |
| 193 |
1/2✓ Branch 0 taken 2601 times.
✗ Branch 1 not taken.
|
2601 | if (metal) return Device::Metal; |
| 194 | ✗ | if (vulkan) return Device::vulkan(d.index); | |
| 195 | ✗ | } | |
| 196 | 550937 | return d; | |
| 197 | 553538 | } | |
| 198 | |||
| 199 | 75500 | const OpsVTable& ops_for(Device d) { | |
| 200 | 75500 | d = resolve_device_alias(d); | |
| 201 | 75500 | auto& s = slots()[static_cast<int>(d.type)]; | |
| 202 |
2/2✓ Branch 0 taken 75499 times.
✓ Branch 1 taken 1 time.
|
75500 | if (!s.registered) throw_unregistered(d); |
| 203 | 75499 | return s.ops; | |
| 204 | } | ||
| 205 | |||
| 206 | 240736 | const AllocVTable& alloc_for(Device d) { | |
| 207 | 240736 | d = resolve_device_alias(d); | |
| 208 | 240736 | auto& s = slots()[static_cast<int>(d.type)]; | |
| 209 |
2/2✓ Branch 0 taken 240735 times.
✓ Branch 1 taken 1 time.
|
240736 | if (!s.registered) throw_unregistered(d); |
| 210 | 240735 | return s.alloc; | |
| 211 | } | ||
| 212 | |||
| 213 | // ─── dispatch overloads ──────────────────────────────────────────────────── | ||
| 214 | // | ||
| 215 | // Each forwards to dispatch_v, which resolves the device from the first | ||
| 216 | // committed operand and skips uncommitted (data == nullptr) wildcards. | ||
| 217 | |||
| 218 | 1776 | const OpsVTable& dispatch(const Tensor& a) { | |
| 219 | 1776 | return dispatch_v({&a}); | |
| 220 | } | ||
| 221 | |||
| 222 | 57485 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b) { | |
| 223 | 57485 | return dispatch_v({&a, &b}); | |
| 224 | } | ||
| 225 | |||
| 226 | 9935 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b, const Tensor& c) { | |
| 227 | 9935 | return dispatch_v({&a, &b, &c}); | |
| 228 | } | ||
| 229 | |||
| 230 | 3565 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b, const Tensor& c, | |
| 231 | const Tensor& d) { | ||
| 232 | 3565 | return dispatch_v({&a, &b, &c, &d}); | |
| 233 | } | ||
| 234 | |||
| 235 | 193 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b, const Tensor& c, | |
| 236 | const Tensor& d, const Tensor& e) { | ||
| 237 | 193 | return dispatch_v({&a, &b, &c, &d, &e}); | |
| 238 | } | ||
| 239 | |||
| 240 | 222 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b, const Tensor& c, | |
| 241 | const Tensor& d, const Tensor& e, const Tensor& f) { | ||
| 242 | 222 | return dispatch_v({&a, &b, &c, &d, &e, &f}); | |
| 243 | } | ||
| 244 | |||
| 245 | 99 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b, const Tensor& c, | |
| 246 | const Tensor& d, const Tensor& e, const Tensor& f, | ||
| 247 | const Tensor& g) { | ||
| 248 | 99 | return dispatch_v({&a, &b, &c, &d, &e, &f, &g}); | |
| 249 | } | ||
| 250 | |||
| 251 | 309 | const OpsVTable& dispatch(const Tensor& a, const Tensor& b, const Tensor& c, | |
| 252 | const Tensor& d, const Tensor& e, const Tensor& f, | ||
| 253 | const Tensor& g, const Tensor& h) { | ||
| 254 | 309 | return dispatch_v({&a, &b, &c, &d, &e, &f, &g, &h}); | |
| 255 | } | ||
| 256 | |||
| 257 | 271 | const OpsVTable& dispatch_with_opts(const Tensor& a, | |
| 258 | std::initializer_list<const Tensor*> opts) { | ||
| 259 | 271 | const std::size_t count = 1 + opts.size(); | |
| 260 |
2/2✓ Branch 0 taken 270 times.
✓ Branch 1 taken 1 time.
|
271 | if (count > kMaxOperands) throw_too_many_operands(count); |
| 261 | const Tensor* all[kMaxOperands]; | ||
| 262 | 270 | std::size_t n = 0; | |
| 263 | 270 | all[n++] = &a; | |
| 264 |
2/2✓ Branch 0 taken 649 times.
✓ Branch 1 taken 270 times.
|
919 | for (const Tensor* p : opts) all[n++] = p; |
| 265 | 270 | return resolve_over(all, n); | |
| 266 | } | ||
| 267 | |||
| 268 | 1524 | const OpsVTable& dispatch_with_opts(const Tensor& a, const Tensor& b, | |
| 269 | std::initializer_list<const Tensor*> opts) { | ||
| 270 | 1524 | const std::size_t count = 2 + opts.size(); | |
| 271 |
1/2✓ Branch 0 taken 1524 times.
✗ Branch 1 not taken.
|
1524 | if (count > kMaxOperands) throw_too_many_operands(count); |
| 272 | const Tensor* all[kMaxOperands]; | ||
| 273 | 1524 | std::size_t n = 0; | |
| 274 | 1524 | all[n++] = &a; | |
| 275 | 1524 | all[n++] = &b; | |
| 276 |
2/2✓ Branch 0 taken 6863 times.
✓ Branch 1 taken 1524 times.
|
8387 | for (const Tensor* p : opts) all[n++] = p; |
| 277 | 1524 | return resolve_over(all, n); | |
| 278 | } | ||
| 279 | |||
| 280 | // ─── output adoption ─────────────────────────────────────────────────────── | ||
| 281 | // | ||
| 282 | // An uncommitted output tensor (data == nullptr) has no device affinity yet. | ||
| 283 | // The wrapper calls this after dispatch so the tensor is pinned to the op's | ||
| 284 | // device before the backend impl resizes/allocates it. | ||
| 285 | 81980 | void adopt_output(Tensor& t, Device d) { | |
| 286 |
2/2✓ Branch 0 taken 20827 times.
✓ Branch 1 taken 61153 times.
|
81980 | if (t.data == nullptr) t.device = resolve_device_alias(d); |
| 287 | 81980 | } | |
| 288 | |||
| 289 | 3 | [[noreturn]] void throw_not_implemented(const char* op_name, Device d) { | |
| 290 | 3 | std::string msg = "brotensor: "; | |
| 291 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | msg += op_name; |
| 292 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | msg += ": not implemented on "; |
| 293 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | msg += dev_name(d); |
| 294 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
|
3 | throw std::runtime_error(msg); |
| 295 | 3 | } | |
| 296 | |||
| 297 | 2 | [[noreturn]] void throw_spatial_1d_error(const char* op, const char* tensor_name, | |
| 298 | int N, int C, int L, int rows, int cols) { | ||
| 299 | 2 | std::string msg = "brotensor: "; | |
| 300 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += op; |
| 301 |
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 | if (N <= 0 || C <= 0 || L <= 0) { |
| 302 | ✗ | msg += ": non-positive spatial dimensions (N="; | |
| 303 | ✗ | msg += std::to_string(N); | |
| 304 | ✗ | msg += ", C="; | |
| 305 | ✗ | msg += std::to_string(C); | |
| 306 | ✗ | msg += ", L="; | |
| 307 | ✗ | msg += std::to_string(L); | |
| 308 | ✗ | msg += ") for "; | |
| 309 | ✗ | msg += tensor_name; | |
| 310 | ✗ | msg += " (rows="; | |
| 311 | ✗ | msg += std::to_string(rows); | |
| 312 | ✗ | msg += ", cols="; | |
| 313 | ✗ | msg += std::to_string(cols); | |
| 314 | ✗ | msg += ")"; | |
| 315 | ✗ | } else { | |
| 316 | 2 | const std::int64_t expected_cols = static_cast<std::int64_t>(C) * L; | |
| 317 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ": shape mismatch for "; |
| 318 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += tensor_name; |
| 319 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ": expected (N="; |
| 320 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(N); |
| 321 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", C*L="; |
| 322 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(C); |
| 323 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "*"; |
| 324 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(L); |
| 325 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "="; |
| 326 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(expected_cols); |
| 327 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "), got (rows="; |
| 328 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(rows); |
| 329 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", cols="; |
| 330 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(cols); |
| 331 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | msg += ")"; |
| 332 | } | ||
| 333 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
|
2 | throw std::runtime_error(msg); |
| 334 | 2 | } | |
| 335 | |||
| 336 | 9 | [[noreturn]] void throw_spatial_2d_error(const char* op, const char* tensor_name, | |
| 337 | int N, int C, int H, int W, int rows, int cols) { | ||
| 338 | 9 | std::string msg = "brotensor: "; | |
| 339 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | msg += op; |
| 340 |
6/8✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
|
9 | if (N <= 0 || C <= 0 || H <= 0 || W <= 0) { |
| 341 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ": non-positive spatial dimensions (N="; |
| 342 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(N); |
| 343 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", C="; |
| 344 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(C); |
| 345 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", H="; |
| 346 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(H); |
| 347 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", W="; |
| 348 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(W); |
| 349 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ") for "; |
| 350 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += tensor_name; |
| 351 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += " (rows="; |
| 352 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(rows); |
| 353 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", cols="; |
| 354 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(cols); |
| 355 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | msg += ")"; |
| 356 | 2 | } else { | |
| 357 | 7 | const std::int64_t expected_cols = static_cast<std::int64_t>(C) * H * W; | |
| 358 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += ": shape mismatch for "; |
| 359 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += tensor_name; |
| 360 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += ": expected (N="; |
| 361 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(N); |
| 362 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += ", C*H*W="; |
| 363 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(C); |
| 364 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += "*"; |
| 365 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(H); |
| 366 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += "*"; |
| 367 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(W); |
| 368 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += "="; |
| 369 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(expected_cols); |
| 370 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += "), got (rows="; |
| 371 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(rows); |
| 372 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | msg += ", cols="; |
| 373 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
7 | msg += std::to_string(cols); |
| 374 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
|
7 | msg += ")"; |
| 375 | } | ||
| 376 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | throw std::runtime_error(msg); |
| 377 | 9 | } | |
| 378 | |||
| 379 | 3 | [[noreturn]] void throw_spatial_3d_error(const char* op, const char* tensor_name, | |
| 380 | int N, int C, int T, int H, int W, int rows, int cols) { | ||
| 381 | 3 | std::string msg = "brotensor: "; | |
| 382 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | msg += op; |
| 383 |
6/10✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 time.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
|
3 | if (N <= 0 || C <= 0 || T <= 0 || H <= 0 || W <= 0) { |
| 384 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ": non-positive spatial dimensions (N="; |
| 385 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(N); |
| 386 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ", C="; |
| 387 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(C); |
| 388 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ", T="; |
| 389 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(T); |
| 390 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ", H="; |
| 391 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(H); |
| 392 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ", W="; |
| 393 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(W); |
| 394 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ") for "; |
| 395 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += tensor_name; |
| 396 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += " (rows="; |
| 397 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(rows); |
| 398 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | msg += ", cols="; |
| 399 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | msg += std::to_string(cols); |
| 400 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | msg += ")"; |
| 401 | 1 | } else { | |
| 402 | 2 | const std::int64_t expected_cols = static_cast<std::int64_t>(C) * T * H * W; | |
| 403 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ": shape mismatch for "; |
| 404 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += tensor_name; |
| 405 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ": expected (N="; |
| 406 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(N); |
| 407 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", C*T*H*W="; |
| 408 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(C); |
| 409 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "*"; |
| 410 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(T); |
| 411 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "*"; |
| 412 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(H); |
| 413 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "*"; |
| 414 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(W); |
| 415 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "="; |
| 416 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(expected_cols); |
| 417 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += "), got (rows="; |
| 418 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(rows); |
| 419 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | msg += ", cols="; |
| 420 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | msg += std::to_string(cols); |
| 421 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | msg += ")"; |
| 422 | } | ||
| 423 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | throw std::runtime_error(msg); |
| 424 | 3 | } | |
| 425 | |||
| 426 | 21 | [[noreturn]] void throw_quant_cols_error(const char* op, const char* tensor_name, | |
| 427 | Dtype dt, int rows, int cols) { | ||
| 428 | 21 | std::string msg = "brotensor: "; | |
| 429 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += op; |
| 430 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += ": block-quant tensor "; |
| 431 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += tensor_name; |
| 432 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += " ("; |
| 433 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | msg += dtype_name(dt); |
| 434 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += ", rows="; |
| 435 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | msg += std::to_string(rows); |
| 436 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += ", cols="; |
| 437 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
|
21 | msg += std::to_string(cols); |
| 438 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | msg += ") requires positive rows and cols with cols a multiple of block size ("; |
| 439 |
3/6✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
|
21 | msg += std::to_string(dtype_block_size(dt)); |
| 440 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 21 times.
|
21 | msg += ")"; |
| 441 |
2/4✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 21 times.
|
21 | throw std::runtime_error(msg); |
| 442 | 21 | } | |
| 443 | |||
| 444 | } // namespace brotensor::detail | ||
| 445 | |||
| 446 | namespace brotensor { | ||
| 447 | ✗ | int cuda_device_count() { | |
| 448 | ✗ | return detail::g_cuda_device_count; | |
| 449 | } | ||
| 450 | 4608 | int vulkan_device_count() { | |
| 451 | 4608 | return detail::g_vulkan_device_count; | |
| 452 | } | ||
| 453 | } | ||
| 454 |