src/jit/trace_compiler_cpu.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "trace_compiler.h" | ||
| 2 | #include "trace_dag.h" | ||
| 3 | #include "trace_cache.h" | ||
| 4 | #include "../cpu/cpu_jit.h" | ||
| 5 | #include <brotensor/detail/cpu/thread_pool.h> | ||
| 6 | |||
| 7 | #if BROTENSOR_HAS_BRASS_JIT | ||
| 8 | #include <brass/codegen/kernel_jit.hpp> | ||
| 9 | #include <brass/codegen/trace_builder.hpp> | ||
| 10 | #include <brass/mir/builder.hpp> | ||
| 11 | #include <brass/mir/module.hpp> | ||
| 12 | #include <brass/mir/function.hpp> | ||
| 13 | #include <brass/mir/types.hpp> | ||
| 14 | #endif | ||
| 15 | |||
| 16 | #include <chrono> | ||
| 17 | #include <cmath> | ||
| 18 | #include <string> | ||
| 19 | #include <vector> | ||
| 20 | #include <algorithm> | ||
| 21 | #include <stdexcept> | ||
| 22 | #include <iostream> | ||
| 23 | |||
| 24 | namespace brotensor::jit { | ||
| 25 | |||
| 26 | 10 | FusionPattern TraceCompiler::classify_dag(const TraceDAG& dag) { | |
| 27 | 10 | const auto& nodes = dag.nodes(); | |
| 28 | |||
| 29 | // Check for Fused Residual + RMSNorm: in-place Add followed by RMSNorm consuming it | ||
| 30 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 9 times.
|
69 | for (const auto& n : nodes) { |
| 31 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 59 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
60 | if (n.op == TraceOpKind::RMSNorm && !n.inputs.empty()) { |
| 32 | 1 | int in_id = n.inputs[0]; | |
| 33 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (in_id >= 0 && in_id < static_cast<int>(nodes.size())) { |
| 34 | 1 | const auto& prev = nodes[in_id]; | |
| 35 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (prev.op == TraceOpKind::Add && prev.is_inplace) { |
| 36 | 1 | return FusionPattern::ResidualRMSNorm; | |
| 37 | } | ||
| 38 | ✗ | } | |
| 39 | ✗ | } | |
| 40 | } | ||
| 41 | |||
| 42 | // Check for Fused LayerNorm + Modulate: LayerNorm followed by Modulate / Affine | ||
| 43 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 50 times.
|
58 | for (const auto& n : nodes) { |
| 44 |
2/2✓ Branch 0 taken 49 times.
✓ Branch 1 taken 1 time.
|
50 | if (n.op == TraceOpKind::LayerNorm) { |
| 45 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | for (int out_id : n.outputs) { |
| 46 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (out_id >= 0 && out_id < static_cast<int>(nodes.size())) { |
| 47 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | if (nodes[out_id].op == TraceOpKind::Modulate || nodes[out_id].op == TraceOpKind::Mul) { |
| 48 | 1 | return FusionPattern::LayerNormModulate; | |
| 49 | } | ||
| 50 | ✗ | } | |
| 51 | } | ||
| 52 | ✗ | } | |
| 53 | } | ||
| 54 | |||
| 55 | 8 | return FusionPattern::Elementwise; | |
| 56 | 10 | } | |
| 57 | |||
| 58 | static CudaTraceCompilerFn s_cuda_trace_compiler_fn = nullptr; | ||
| 59 | |||
| 60 | ✗ | CudaTraceCompilerFn get_cuda_trace_compiler_hook() { | |
| 61 | ✗ | return s_cuda_trace_compiler_fn; | |
| 62 | } | ||
| 63 | |||
| 64 | ✗ | void register_cuda_trace_compiler(CudaTraceCompilerFn fn) { | |
| 65 | ✗ | s_cuda_trace_compiler_fn = fn; | |
| 66 | ✗ | } | |
| 67 | |||
| 68 | static VulkanTraceCompilerFn s_vulkan_trace_compiler_fn = nullptr; | ||
| 69 | |||
| 70 | ✗ | VulkanTraceCompilerFn get_vulkan_trace_compiler_hook() { | |
| 71 | ✗ | return s_vulkan_trace_compiler_fn; | |
| 72 | } | ||
| 73 | |||
| 74 | ✗ | void register_vulkan_trace_compiler(VulkanTraceCompilerFn fn) { | |
| 75 | ✗ | s_vulkan_trace_compiler_fn = fn; | |
| 76 | ✗ | } | |
| 77 | |||
| 78 | 11 | TraceHandle TraceCompiler::compile_and_cache(const TraceDAG& dag) { | |
| 79 | 11 | uint64_t hash = dag.compute_hash(); | |
| 80 | |||
| 81 | // Check trace cache for previously compiled kernel | ||
| 82 | 11 | auto cached = TraceCache::instance().lookup(hash); | |
| 83 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
11 | if (cached && cached->rebind_fn) { |
| 84 | 1 | std::vector<void*> input_ptrs; | |
| 85 | 1 | std::vector<void*> output_ptrs; | |
| 86 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
|
9 | for (const auto& node : dag.nodes()) { |
| 87 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4 times.
|
8 | if (node.op == TraceOpKind::Input) { |
| 88 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | input_ptrs.push_back(node.buffer); |
| 89 | 4 | } | |
| 90 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
|
8 | if (node.is_live_at_end) { |
| 91 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | output_ptrs.push_back(node.buffer); |
| 92 | 1 | } | |
| 93 | } | ||
| 94 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | auto re_bound = cached->rebind_fn(input_ptrs, output_ptrs); |
| 95 | 1 | re_bound->signature_hash = hash; | |
| 96 | 1 | re_bound->is_cache_hit = true; | |
| 97 | 1 | re_bound->node_count = dag.node_count(); | |
| 98 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | re_bound->execute(); |
| 99 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | return TraceHandle(re_bound); |
| 100 | 1 | } | |
| 101 | |||
| 102 | 10 | FusionPattern pattern = classify_dag(dag); | |
| 103 | |||
| 104 | // Which compiler gets the DAG. CUDA has the PTX compiler and Vulkan the | ||
| 105 | // SPIR-V one (brass MIR); a Vulkan DAG must never reach the CPU compiler | ||
| 106 | // (host code over device addresses, unordered against the device | ||
| 107 | // stream), so what the Vulkan compiler cannot fuse is replayed op by op | ||
| 108 | // instead. Everything else is the CPU compiler's. | ||
| 109 | 10 | bool is_cuda = false; | |
| 110 | 10 | bool is_vulkan = false; | |
| 111 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 10 times.
|
70 | for (const auto& n : dag.nodes()) { |
| 112 |
2/4✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
|
60 | if (n.device.is_cuda()) is_cuda = true; |
| 113 |
2/4✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
|
60 | if (n.device.is_vulkan()) is_vulkan = true; |
| 114 | } | ||
| 115 | |||
| 116 | 10 | const auto compile_t0 = std::chrono::steady_clock::now(); | |
| 117 | 10 | std::shared_ptr<TraceHandleImpl> handle; | |
| 118 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (is_cuda) { |
| 119 | ✗ | auto cuda_fn = get_cuda_trace_compiler_hook(); | |
| 120 | ✗ | if (!cuda_fn) { | |
| 121 | ✗ | throw std::runtime_error("brotensor::jit: CUDA JIT compiler hook is not registered or CUDA is unavailable"); | |
| 122 | } | ||
| 123 | ✗ | handle = cuda_fn(dag, pattern); | |
| 124 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | } else if (is_vulkan) { |
| 125 | ✗ | if (auto vk_fn = get_vulkan_trace_compiler_hook()) handle = vk_fn(dag, pattern); | |
| 126 | ✗ | if (!handle) handle = eager::compile_eager(dag); | |
| 127 | ✗ | } else { | |
| 128 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | handle = cpu::compile_cpu(dag, pattern); |
| 129 | } | ||
| 130 | |||
| 131 | 10 | handle->signature_hash = hash; | |
| 132 | 10 | handle->is_cuda = is_cuda; | |
| 133 | 10 | handle->node_count = dag.node_count(); | |
| 134 | 10 | handle->is_cache_hit = false; | |
| 135 | // The CUDA and Vulkan compilers name their own fusion and time their own | ||
| 136 | // builds, the eager replay counts its own launches; the CPU path is always | ||
| 137 | // one fused call through brass's host codegen. | ||
| 138 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
|
10 | if (handle->launch_count == 0) handle->launch_count = 1; |
| 139 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | if (!is_cuda && !is_vulkan) { |
| 140 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | if (handle->fusion_name == nullptr || handle->fusion_name[0] == '\0') { |
| 141 | ✗ | handle->fusion_name = "cpu-avx2-fused"; | |
| 142 | ✗ | } | |
| 143 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
20 | handle->compile_us = std::chrono::duration<double, std::micro>( |
| 144 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | std::chrono::steady_clock::now() - compile_t0).count(); |
| 145 | 10 | } | |
| 146 | |||
| 147 |
2/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
|
10 | TraceCache::instance().insert(hash, handle); |
| 148 | |||
| 149 | // Initial execution to populate outputs | ||
| 150 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | handle->execute(); |
| 151 | |||
| 152 |
1/2✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
|
10 | return TraceHandle(handle); |
| 153 | 11 | } | |
| 154 | |||
| 155 | 11 | TraceHandle end_trace() { | |
| 156 | 11 | TraceDAG dag = TraceContext::current().end(); | |
| 157 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | return TraceCompiler::compile_and_cache(dag); |
| 158 | 11 | } | |
| 159 | |||
| 160 | namespace cpu { | ||
| 161 | |||
| 162 | #if BROTENSOR_HAS_BRASS_JIT | ||
| 163 | |||
| 164 | using ElementwiseFn = void (*)(float* const* out_ptrs, const float* const* in_ptrs, int64_t n); | ||
| 165 | |||
| 166 | // What the host codegen below can actually be handed. | ||
| 167 | // | ||
| 168 | // It walks every operand as a flat run of `total_numel` FP32 values. A (1, D) | ||
| 169 | // broadcast row or a half-precision buffer would be read past the end of its | ||
| 170 | // allocation — a crash, or worse, silently wrong numbers — so anything that is | ||
| 171 | // not the trace's full shape in FP32 is refused here. end_trace() then throws | ||
| 172 | // and the caller falls back to its eager path, which is the contract the CUDA | ||
| 173 | // compiler already follows for expressions it cannot fuse. | ||
| 174 | 6 | static void require_cpu_fusable(const TraceDAG& dag) { | |
| 175 | 6 | int rows = 0, cols = 0; | |
| 176 | 6 | dag.dominant_shape(rows, cols); | |
| 177 | 6 | const int64_t dom_numel = static_cast<int64_t>(rows) * cols; | |
| 178 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 6 times.
|
46 | for (const TraceNode& n : dag.nodes()) { |
| 179 |
1/2✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
|
40 | if (n.dtype != Dtype::FP32) { |
| 180 | ✗ | throw std::runtime_error( | |
| 181 | "brotensor::jit: the CPU trace compiler is FP32 only"); | ||
| 182 | } | ||
| 183 | 40 | const BroadcastKind bk = broadcast_of(n.rows, n.cols, rows, cols); | |
| 184 | 40 | const int64_t numel = static_cast<int64_t>(n.rows) * n.cols; | |
| 185 |
3/4✓ Branch 0 taken 37 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 37 times.
✗ Branch 3 not taken.
|
40 | if (bk == BroadcastKind::Full && numel != dom_numel) { |
| 186 | ✗ | throw std::runtime_error( | |
| 187 | ✗ | "brotensor::jit: operand shape (" + std::to_string(n.rows) + ", " + | |
| 188 | ✗ | std::to_string(n.cols) + ") cannot broadcast to dominant shape (" + | |
| 189 | ✗ | std::to_string(rows) + ", " + std::to_string(cols) + ")"); | |
| 190 | } | ||
| 191 |
3/4✓ Branch 0 taken 7 times.
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
40 | if (n.is_live_at_end && bk != BroadcastKind::Full) { |
| 192 | ✗ | throw std::runtime_error( | |
| 193 | "brotensor::jit: a traced output must have the trace's full (rows, cols) shape"); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | 6 | } | |
| 197 | |||
| 198 | // The emitted code dereferences every buffer on the host. CUDA device | ||
| 199 | // pointers and Vulkan buffer device addresses are not host addresses (and | ||
| 200 | // even where one happened to be readable, the host code would be unordered | ||
| 201 | // against the device stream that produces its inputs). Metal storage is | ||
| 202 | // MTLResourceStorageModeShared, whose contents pointer is a host address. | ||
| 203 | 10 | static void require_host_addressable(const TraceDAG& dag) { | |
| 204 |
2/2✓ Branch 0 taken 60 times.
✓ Branch 1 taken 10 times.
|
70 | for (const TraceNode& n : dag.nodes()) { |
| 205 |
1/2✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
|
60 | if (n.device.is_cuda() || n.device.is_vulkan()) { |
| 206 | ✗ | throw std::runtime_error( | |
| 207 | ✗ | "brotensor::jit: the CPU trace compiler was handed a " + | |
| 208 | ✗ | to_string(n.device) + " tensor; it only runs host-addressable memory"); | |
| 209 | } | ||
| 210 | } | ||
| 211 | 10 | } | |
| 212 | |||
| 213 | 6 | static std::shared_ptr<TraceHandleImpl> compile_cpu_elementwise(const TraceDAG& dag) { | |
| 214 | 6 | require_cpu_fusable(dag); | |
| 215 | 6 | const auto& nodes = dag.nodes(); | |
| 216 | |||
| 217 | 6 | int dom_rows = 0, dom_cols = 0; | |
| 218 | 6 | dag.dominant_shape(dom_rows, dom_cols); | |
| 219 | |||
| 220 | 6 | std::vector<int> input_node_ids; | |
| 221 | 6 | std::vector<BroadcastKind> input_bcast; | |
| 222 | 6 | std::vector<int> output_node_ids; | |
| 223 | 6 | std::unordered_map<int, int> node_to_in_idx; | |
| 224 | 6 | std::unordered_map<int, int> node_to_out_idx; | |
| 225 | |||
| 226 | 6 | bool any_bcast = false; | |
| 227 | 6 | bool any_row_bcast = false; | |
| 228 | 6 | bool any_col_bcast = false; | |
| 229 | 6 | bool has_where = false; | |
| 230 | |||
| 231 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 40 times.
|
46 | for (const auto& n : nodes) { |
| 232 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 21 times.
|
40 | if (n.op == TraceOpKind::Input) { |
| 233 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | node_to_in_idx[n.id] = static_cast<int>(input_node_ids.size()); |
| 234 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | input_node_ids.push_back(n.id); |
| 235 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | const BroadcastKind bk = broadcast_of(n.rows, n.cols, dom_rows, dom_cols); |
| 236 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | input_bcast.push_back(bk); |
| 237 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 3 times.
|
19 | if (bk != BroadcastKind::Full) any_bcast = true; |
| 238 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 1 time.
|
19 | if (bk == BroadcastKind::Row) any_row_bcast = true; |
| 239 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 2 times.
|
19 | if (bk == BroadcastKind::Col) any_col_bcast = true; |
| 240 | 19 | } | |
| 241 |
2/2✓ Branch 0 taken 39 times.
✓ Branch 1 taken 1 time.
|
40 | if (n.op == TraceOpKind::Where) { |
| 242 | 1 | has_where = true; | |
| 243 | 1 | } | |
| 244 |
2/2✓ Branch 0 taken 33 times.
✓ Branch 1 taken 7 times.
|
40 | if (n.is_live_at_end) { |
| 245 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | node_to_out_idx[n.id] = static_cast<int>(output_node_ids.size()); |
| 246 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | output_node_ids.push_back(n.id); |
| 247 | 7 | } | |
| 248 | } | ||
| 249 | |||
| 250 | 6 | const int64_t total_numel = static_cast<int64_t>(dom_rows) * static_cast<int64_t>(dom_cols); | |
| 251 |
4/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 4 times.
|
6 | const bool use_vec_loop = !any_row_bcast && !any_col_bcast && !has_where; |
| 252 | |||
| 253 | using namespace brass; | ||
| 254 | using namespace brass::codegen; | ||
| 255 | |||
| 256 | static uint64_t kernel_id_counter = 0; | ||
| 257 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | std::string mod_name = "mod_trace_cpu_" + std::to_string(++kernel_id_counter); |
| 258 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Module mod(mod_name); |
| 259 | |||
| 260 |
5/10✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
✗ Branch 9 not taken.
|
6 | Function* fn = mod.create_function("trace_elementwise_kernel", Type::void_type(), { |
| 261 | 6 | Type::ptr(), // float* const* out_ptrs | |
| 262 | 6 | Type::ptr(), // const float* const* in_ptrs | |
| 263 | 6 | Type::i64() // int64_t n | |
| 264 | }); | ||
| 265 | |||
| 266 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | TraceBuilder tb(mod, fn); |
| 267 | |||
| 268 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | BasicBlock* entry = tb.builder().append_block("entry"); |
| 269 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* p_out_ptrs = tb.builder().add_block_param(entry, Type::ptr()); |
| 270 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* p_in_ptrs = tb.builder().add_block_param(entry, Type::ptr()); |
| 271 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* n_val = tb.builder().add_block_param(entry, Type::i64()); |
| 272 | |||
| 273 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | BasicBlock* shead = tb.builder().create_block("shead"); |
| 274 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | BasicBlock* sbody = tb.builder().create_block("sbody"); |
| 275 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
6 | BasicBlock* exit_bb = tb.builder().create_block("exit"); |
| 276 | |||
| 277 | 6 | tb.position_at_end(entry); | |
| 278 | |||
| 279 | // Load input and output base pointers into local SSA values | ||
| 280 | 6 | std::vector<Value*> in_ptrs_val; | |
| 281 |
2/2✓ Branch 0 taken 19 times.
✓ Branch 1 taken 6 times.
|
25 | for (size_t k = 0; k < input_node_ids.size(); ++k) { |
| 282 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | Value* p = tb.builder().build_load(Type::ptr(), p_in_ptrs, static_cast<int32_t>(k * 8)); |
| 283 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | in_ptrs_val.push_back(p); |
| 284 | 19 | } | |
| 285 | 6 | std::vector<Value*> out_ptrs_val; | |
| 286 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
|
13 | for (size_t m = 0; m < output_node_ids.size(); ++m) { |
| 287 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | Value* p = tb.builder().build_load(Type::ptr(), p_out_ptrs, static_cast<int32_t>(m * 8)); |
| 288 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | out_ptrs_val.push_back(p); |
| 289 | 7 | } | |
| 290 | |||
| 291 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* zero_i64 = tb.builder().build_iconst_i64(0); |
| 292 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* one_i64 = tb.builder().build_iconst_i64(1); |
| 293 | |||
| 294 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
|
6 | if (use_vec_loop) { |
| 295 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | BasicBlock* vhead = tb.builder().create_block("vhead"); |
| 296 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | BasicBlock* vbody = tb.builder().create_block("vbody"); |
| 297 | |||
| 298 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* two_i64 = tb.builder().build_iconst_i64(2); |
| 299 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* eight_i64 = tb.builder().build_iconst_i64(8); |
| 300 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* mask_eight = tb.builder().build_iconst_i64(~int64_t(7)); |
| 301 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* vec_n = tb.builder().build_and(n_val, mask_eight); |
| 302 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* has_vec = tb.builder().build_sge(n_val, eight_i64); |
| 303 | |||
| 304 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | tb.builder().build_br_if(has_vec, vhead, {zero_i64}, shead, {zero_i64}); |
| 305 | |||
| 306 | // ── Vector AVX2 Loop (8-wide) ───────────────────────────────────────── | ||
| 307 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | fn->append_block(vhead); |
| 308 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* iv_v = tb.builder().add_block_param(vhead, Type::i64()); |
| 309 | 4 | tb.position_at_end(vhead); | |
| 310 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* cond_v = tb.builder().build_slt(iv_v, vec_n); |
| 311 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | tb.builder().build_br_if(cond_v, vbody, {}, shead, {iv_v}); |
| 312 | |||
| 313 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | fn->append_block(vbody); |
| 314 | 4 | tb.position_at_end(vbody); | |
| 315 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* byte_off_v = tb.builder().build_shl(iv_v, two_i64); |
| 316 | |||
| 317 | 4 | std::unordered_map<int, Value*> v_node_vals; | |
| 318 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 4 times.
|
16 | for (size_t k = 0; k < input_node_ids.size(); ++k) { |
| 319 | 12 | int nid = input_node_ids[k]; | |
| 320 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | if (input_bcast[k] == BroadcastKind::Scalar) { |
| 321 | ✗ | Value* sv = tb.load_f32(in_ptrs_val[k], 0); | |
| 322 | ✗ | v_node_vals[nid] = tb.vbroadcast(Type::f32x8(), sv); | |
| 323 | ✗ | } else { | |
| 324 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | Value* addr = tb.builder().build_add(in_ptrs_val[k], byte_off_v); |
| 325 |
2/4✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
|
12 | v_node_vals[nid] = tb.vload_f32x8(addr); |
| 326 | } | ||
| 327 | 12 | } | |
| 328 | |||
| 329 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 27 times.
|
31 | for (const auto& n : nodes) { |
| 330 |
2/2✓ Branch 0 taken 15 times.
✓ Branch 1 taken 12 times.
|
27 | if (n.op == TraceOpKind::Input) continue; |
| 331 | |||
| 332 | 15 | Value* res = nullptr; | |
| 333 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
|
15 | if (n.op == TraceOpKind::Add) { |
| 334 |
3/6✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
|
4 | res = tb.vadd(v_node_vals[n.inputs[0]], v_node_vals[n.inputs[1]]); |
| 335 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 11 times.
|
15 | } else if (n.op == TraceOpKind::Sub) { |
| 336 | ✗ | res = tb.vsub(v_node_vals[n.inputs[0]], v_node_vals[n.inputs[1]]); | |
| 337 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 5 times.
|
11 | } else if (n.op == TraceOpKind::Mul) { |
| 338 |
3/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
6 | res = tb.vmul(v_node_vals[n.inputs[0]], v_node_vals[n.inputs[1]]); |
| 339 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
11 | } else if (n.op == TraceOpKind::Div) { |
| 340 | ✗ | res = tb.vdiv(v_node_vals[n.inputs[0]], v_node_vals[n.inputs[1]]); | |
| 341 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::AddScalar) { |
| 342 | ✗ | Value* c = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 343 | ✗ | res = tb.vadd(v_node_vals[n.inputs[0]], c); | |
| 344 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::SubScalar) { |
| 345 | ✗ | Value* c = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 346 | ✗ | res = tb.vsub(v_node_vals[n.inputs[0]], c); | |
| 347 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::MulScalar) { |
| 348 | ✗ | Value* c = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 349 | ✗ | res = tb.vmul(v_node_vals[n.inputs[0]], c); | |
| 350 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::DivScalar) { |
| 351 | ✗ | Value* c = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 352 | ✗ | res = tb.vdiv(v_node_vals[n.inputs[0]], c); | |
| 353 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::ScalarSub) { |
| 354 | ✗ | Value* c = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 355 | ✗ | res = tb.vsub(c, v_node_vals[n.inputs[0]]); | |
| 356 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::ScalarDiv) { |
| 357 | ✗ | Value* c = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 358 | ✗ | res = tb.vdiv(c, v_node_vals[n.inputs[0]]); | |
| 359 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::FMA) { |
| 360 | ✗ | res = tb.vfma(v_node_vals[n.inputs[0]], v_node_vals[n.inputs[1]], v_node_vals[n.inputs[2]]); | |
| 361 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::Modulate) { |
| 362 | ✗ | Value* c1 = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(1.0f)); | |
| 363 | ✗ | res = tb.vfma(v_node_vals[n.inputs[0]], tb.vadd(v_node_vals[n.inputs[1]], c1), v_node_vals[n.inputs[2]]); | |
| 364 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | } else if (n.op == TraceOpKind::Clamp) { |
| 365 | ✗ | Value* lo_v = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar)); | |
| 366 | ✗ | Value* hi_v = tb.vbroadcast(Type::f32x8(), tb.builder().build_fconst_f32(n.scalar2)); | |
| 367 | ✗ | res = tb.vmin(tb.vmax(v_node_vals[n.inputs[0]], lo_v), hi_v); | |
| 368 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
|
5 | } else if (n.op == TraceOpKind::SiLU) { |
| 369 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | res = tb.vsilu_f32x8(v_node_vals[n.inputs[0]]); |
| 370 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
5 | } else if (n.op == TraceOpKind::GELU) { |
| 371 | ✗ | res = tb.vgelu_f32x8(v_node_vals[n.inputs[0]]); | |
| 372 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | } else if (n.op == TraceOpKind::ReLU) { |
| 373 | ✗ | res = tb.vrelu_f32x8(v_node_vals[n.inputs[0]]); | |
| 374 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | } else if (n.op == TraceOpKind::Tanh) { |
| 375 | ✗ | res = tb.vtanh_f32x8(v_node_vals[n.inputs[0]]); | |
| 376 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | } else if (n.op == TraceOpKind::Sigmoid) { |
| 377 | ✗ | res = tb.vsigmoid_f32x8(v_node_vals[n.inputs[0]]); | |
| 378 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | } else if (n.op == TraceOpKind::Copy || n.op == TraceOpKind::Cast) { |
| 379 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | res = v_node_vals[n.inputs[0]]; |
| 380 | 1 | } else { | |
| 381 | ✗ | throw std::runtime_error( | |
| 382 | ✗ | std::string("brotensor::jit: op '") + op_kind_name(n.op) + | |
| 383 | "' has no CPU elementwise form"); | ||
| 384 | } | ||
| 385 |
1/2✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
|
15 | v_node_vals[n.id] = res; |
| 386 | |||
| 387 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 11 times.
|
15 | if (n.is_live_at_end) { |
| 388 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | int out_idx = node_to_out_idx[n.id]; |
| 389 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* addr = tb.builder().build_add(out_ptrs_val[out_idx], byte_off_v); |
| 390 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | tb.vstore_f32x8(addr, res); |
| 391 | 4 | } | |
| 392 | } | ||
| 393 | |||
| 394 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | Value* next_iv_v = tb.builder().build_add(iv_v, eight_i64); |
| 395 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | tb.builder().build_br(vhead, {next_iv_v}); |
| 396 | 4 | } else { | |
| 397 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | tb.builder().build_br(shead, {zero_i64}); |
| 398 | } | ||
| 399 | |||
| 400 | // ── Scalar Loop ────────────────────────────────────────────────────────── | ||
| 401 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | fn->append_block(shead); |
| 402 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* iv_s = tb.builder().add_block_param(shead, Type::i64()); |
| 403 | 6 | tb.position_at_end(shead); | |
| 404 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* cond_s = tb.builder().build_slt(iv_s, n_val); |
| 405 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | tb.builder().build_br_if(cond_s, sbody, {}, exit_bb, {}); |
| 406 | |||
| 407 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | fn->append_block(sbody); |
| 408 | 6 | tb.position_at_end(sbody); | |
| 409 | |||
| 410 |
4/4✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 4 times.
|
8 | Value* cols_i64 = (any_row_bcast || any_col_bcast) |
| 411 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | ? tb.builder().build_iconst_i64(dom_cols) |
| 412 | : nullptr; | ||
| 413 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
6 | Value* col_s = any_row_bcast ? tb.builder().build_umod(iv_s, cols_i64) : nullptr; |
| 414 |
3/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
6 | Value* row_s = any_col_bcast ? tb.builder().build_udiv(iv_s, cols_i64) : nullptr; |
| 415 | |||
| 416 | 6 | std::unordered_map<int, Value*> s_node_vals; | |
| 417 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 19 times.
|
25 | for (size_t k = 0; k < input_node_ids.size(); ++k) { |
| 418 | 19 | int nid = input_node_ids[k]; | |
| 419 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (input_bcast[k] == BroadcastKind::Scalar) { |
| 420 | ✗ | s_node_vals[nid] = tb.load_f32(in_ptrs_val[k], 0); | |
| 421 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 18 times.
|
19 | } else if (input_bcast[k] == BroadcastKind::Row) { |
| 422 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | s_node_vals[nid] = tb.load_f32_indexed(in_ptrs_val[k], col_s, 4, 0); |
| 423 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 16 times.
|
19 | } else if (input_bcast[k] == BroadcastKind::Col) { |
| 424 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | s_node_vals[nid] = tb.load_f32_indexed(in_ptrs_val[k], row_s, 4, 0); |
| 425 | 2 | } else { | |
| 426 |
2/4✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
|
16 | s_node_vals[nid] = tb.load_f32_indexed(in_ptrs_val[k], iv_s, 4, 0); |
| 427 | } | ||
| 428 | 19 | } | |
| 429 | |||
| 430 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 40 times.
|
46 | for (const auto& n : nodes) { |
| 431 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 19 times.
|
40 | if (n.op == TraceOpKind::Input) continue; |
| 432 | |||
| 433 | 21 | Value* res = nullptr; | |
| 434 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 15 times.
|
21 | if (n.op == TraceOpKind::Add) { |
| 435 |
3/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
6 | res = tb.add(s_node_vals[n.inputs[0]], s_node_vals[n.inputs[1]]); |
| 436 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
|
21 | } else if (n.op == TraceOpKind::Sub) { |
| 437 | ✗ | res = tb.sub(s_node_vals[n.inputs[0]], s_node_vals[n.inputs[1]]); | |
| 438 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 8 times.
|
15 | } else if (n.op == TraceOpKind::Mul) { |
| 439 |
3/6✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
|
7 | res = tb.mul(s_node_vals[n.inputs[0]], s_node_vals[n.inputs[1]]); |
| 440 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
15 | } else if (n.op == TraceOpKind::Div) { |
| 441 | ✗ | res = tb.div(s_node_vals[n.inputs[0]], s_node_vals[n.inputs[1]]); | |
| 442 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::AddScalar) { |
| 443 | ✗ | Value* c = tb.builder().build_fconst_f32(n.scalar); | |
| 444 | ✗ | res = tb.add(s_node_vals[n.inputs[0]], c); | |
| 445 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::SubScalar) { |
| 446 | ✗ | Value* c = tb.builder().build_fconst_f32(n.scalar); | |
| 447 | ✗ | res = tb.sub(s_node_vals[n.inputs[0]], c); | |
| 448 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::MulScalar) { |
| 449 | ✗ | Value* c = tb.builder().build_fconst_f32(n.scalar); | |
| 450 | ✗ | res = tb.mul(s_node_vals[n.inputs[0]], c); | |
| 451 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::DivScalar) { |
| 452 | ✗ | Value* c = tb.builder().build_fconst_f32(n.scalar); | |
| 453 | ✗ | res = tb.div(s_node_vals[n.inputs[0]], c); | |
| 454 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::ScalarSub) { |
| 455 | ✗ | Value* c = tb.builder().build_fconst_f32(n.scalar); | |
| 456 | ✗ | res = tb.sub(c, s_node_vals[n.inputs[0]]); | |
| 457 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::ScalarDiv) { |
| 458 | ✗ | Value* c = tb.builder().build_fconst_f32(n.scalar); | |
| 459 | ✗ | res = tb.div(c, s_node_vals[n.inputs[0]]); | |
| 460 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::FMA) { |
| 461 | ✗ | res = tb.fma(s_node_vals[n.inputs[0]], s_node_vals[n.inputs[1]], s_node_vals[n.inputs[2]]); | |
| 462 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
|
8 | } else if (n.op == TraceOpKind::Modulate) { |
| 463 | ✗ | Value* c1 = tb.builder().build_fconst_f32(1.0f); | |
| 464 | ✗ | res = tb.fma(s_node_vals[n.inputs[0]], tb.add(s_node_vals[n.inputs[1]], c1), s_node_vals[n.inputs[2]]); | |
| 465 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
|
8 | } else if (n.op == TraceOpKind::Clamp) { |
| 466 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Value* lo_c = tb.builder().build_fconst_f32(n.scalar); |
| 467 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | Value* hi_c = tb.builder().build_fconst_f32(n.scalar2); |
| 468 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
4 | res = tb.builder().build_fmin_f32( |
| 469 |
2/4✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
|
2 | tb.builder().build_fmax_f32(s_node_vals[n.inputs[0]], lo_c), hi_c); |
| 470 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 5 times.
|
8 | } else if (n.op == TraceOpKind::Where) { |
| 471 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | Value* c_bits = tb.builder().build_bitcast_i32_f32(s_node_vals[n.inputs[0]]); |
| 472 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | Value* mag_bits = tb.builder().build_and(c_bits, tb.builder().build_iconst_i32(0x7FFFFFFF)); |
| 473 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
|
1 | Value* nz = tb.builder().build_ne(mag_bits, tb.builder().build_iconst_i32(0)); |
| 474 |
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 | res = tb.builder().build_select(nz, s_node_vals[n.inputs[1]], s_node_vals[n.inputs[2]]); |
| 475 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
|
6 | } else if (n.op == TraceOpKind::SiLU) { |
| 476 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | res = tb.silu_f32(s_node_vals[n.inputs[0]]); |
| 477 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
5 | } else if (n.op == TraceOpKind::GELU) { |
| 478 | ✗ | res = tb.gelu_f32(s_node_vals[n.inputs[0]]); | |
| 479 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | } else if (n.op == TraceOpKind::ReLU) { |
| 480 | ✗ | res = tb.relu_f32(s_node_vals[n.inputs[0]]); | |
| 481 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | } else if (n.op == TraceOpKind::Tanh) { |
| 482 | ✗ | res = tb.tanh_f32(s_node_vals[n.inputs[0]]); | |
| 483 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | } else if (n.op == TraceOpKind::Sigmoid) { |
| 484 | ✗ | res = tb.sigmoid_f32(s_node_vals[n.inputs[0]]); | |
| 485 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | } else if (n.op == TraceOpKind::Copy || n.op == TraceOpKind::Cast) { |
| 486 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | res = s_node_vals[n.inputs[0]]; |
| 487 | 1 | } else { | |
| 488 | ✗ | throw std::runtime_error( | |
| 489 | ✗ | std::string("brotensor::jit: op '") + op_kind_name(n.op) + | |
| 490 | "' has no CPU elementwise form"); | ||
| 491 | } | ||
| 492 |
1/2✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
|
21 | s_node_vals[n.id] = res; |
| 493 | |||
| 494 |
2/2✓ Branch 0 taken 7 times.
✓ Branch 1 taken 14 times.
|
21 | if (n.is_live_at_end) { |
| 495 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | int out_idx = node_to_out_idx[n.id]; |
| 496 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | tb.store_f32_indexed(out_ptrs_val[out_idx], iv_s, res, 4, 0); |
| 497 | 7 | } | |
| 498 | } | ||
| 499 | |||
| 500 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | Value* next_iv_s = tb.builder().build_add(iv_s, one_i64); |
| 501 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | tb.builder().build_br(shead, {next_iv_s}); |
| 502 | |||
| 503 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | fn->append_block(exit_bb); |
| 504 | 6 | tb.position_at_end(exit_bb); | |
| 505 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | tb.builder().build_ret_void(); |
| 506 | |||
| 507 | 6 | KernelOptions opts; | |
| 508 | 6 | opts.enable_optimizations = true; | |
| 509 | 6 | opts.enable_avx2 = true; | |
| 510 | 6 | opts.enable_fma = true; | |
| 511 | 6 | opts.enable_vectorize = true; | |
| 512 | |||
| 513 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | auto jit = std::make_shared<KernelJit>(opts); |
| 514 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | KernelFunction kfn = jit->compile(*fn); |
| 515 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | if (!kfn.is_valid()) { |
| 516 | ✗ | throw std::runtime_error("brotensor::jit: Failed to JIT compile CPU elementwise trace kernel"); | |
| 517 | } | ||
| 518 | |||
| 519 | 6 | auto kernel_ptr = kfn.as<ElementwiseFn>(); | |
| 520 | 6 | auto engine_ref = kfn.engine(); | |
| 521 | |||
| 522 |
3/6✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
|
19 | auto make_bound_handle = [kernel_ptr, engine_ref, total_numel, any_bcast, input_node_ids, output_node_ids] |
| 523 | (const std::vector<void*>& in_buffers, const std::vector<void*>& out_buffers) { | ||
| 524 | 7 | auto h = std::make_shared<TraceHandleImpl>(); | |
| 525 | |||
| 526 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::vector<const float*> in_f(in_buffers.size()); |
| 527 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 7 times.
|
30 | for (size_t k = 0; k < in_buffers.size(); ++k) in_f[k] = static_cast<const float*>(in_buffers[k]); |
| 528 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | std::vector<float*> out_f(out_buffers.size()); |
| 529 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 7 times.
|
15 | for (size_t m = 0; m < out_buffers.size(); ++m) out_f[m] = static_cast<float*>(out_buffers[m]); |
| 530 | |||
| 531 |
3/8✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
565 | h->execute_fn = [kernel_ptr, engine_ref, total_numel, any_bcast, in_f, out_f]() { |
| 532 |
4/4✓ Branch 0 taken 556 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 554 times.
✓ Branch 3 taken 2 times.
|
558 | if (!any_bcast && total_numel >= 262144) { |
| 533 | 2 | auto& pool = detail::cpu::ThreadPool::instance(); | |
| 534 | 2 | int n_threads = pool.num_threads(); | |
| 535 | 2 | int64_t chunk = (total_numel + n_threads - 1) / n_threads; | |
| 536 | 2 | chunk = (chunk + 7) & ~int64_t(7); // Align to 8-wide AVX2 | |
| 537 | |||
| 538 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
8 | detail::cpu::parallel_for(n_threads, [&](std::size_t tid_sz) { |
| 539 | 6 | int64_t tid = static_cast<int64_t>(tid_sz); | |
| 540 | 6 | int64_t start = tid * chunk; | |
| 541 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
6 | if (start >= total_numel) return; |
| 542 | 6 | int64_t count = std::min(chunk, total_numel - start); | |
| 543 | |||
| 544 | 6 | std::vector<const float*> chunk_in(in_f.size()); | |
| 545 |
2/2✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
|
18 | for (size_t k = 0; k < in_f.size(); ++k) { |
| 546 | 12 | chunk_in[k] = in_f[k] + start; | |
| 547 | 12 | } | |
| 548 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | std::vector<float*> chunk_out(out_f.size()); |
| 549 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
|
12 | for (size_t m = 0; m < out_f.size(); ++m) { |
| 550 | 6 | chunk_out[m] = out_f[m] + start; | |
| 551 | 6 | } | |
| 552 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | kernel_ptr(chunk_out.data(), chunk_in.data(), count); |
| 553 | 6 | }); | |
| 554 | 2 | } else { | |
| 555 | 556 | kernel_ptr(out_f.data(), in_f.data(), total_numel); | |
| 556 | } | ||
| 557 | 558 | }; | |
| 558 | 7 | return h; | |
| 559 |
1/2✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
|
7 | }; |
| 560 | |||
| 561 | 6 | std::vector<void*> initial_in_buffers; | |
| 562 |
3/4✓ Branch 0 taken 19 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
|
25 | for (int nid : input_node_ids) initial_in_buffers.push_back(nodes[nid].buffer); |
| 563 | 6 | std::vector<void*> initial_out_buffers; | |
| 564 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
|
13 | for (int nid : output_node_ids) initial_out_buffers.push_back(nodes[nid].buffer); |
| 565 | |||
| 566 |
1/2✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
|
6 | auto handle = make_bound_handle(initial_in_buffers, initial_out_buffers); |
| 567 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
|
7 | handle->rebind_fn = [make_bound_handle](const std::vector<void*>& in_b, const std::vector<void*>& out_b) { |
| 568 | 1 | return make_bound_handle(in_b, out_b); | |
| 569 | }; | ||
| 570 | |||
| 571 | 6 | return handle; | |
| 572 | 6 | } | |
| 573 | |||
| 574 | 10 | std::shared_ptr<TraceHandleImpl> compile_cpu(const TraceDAG& dag, FusionPattern pattern) { | |
| 575 | 10 | const auto& nodes = dag.nodes(); | |
| 576 | 10 | require_host_addressable(dag); | |
| 577 | |||
| 578 |
2/2✓ Branch 0 taken 59 times.
✓ Branch 1 taken 8 times.
|
67 | for (const auto& n : nodes) { |
| 579 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 57 times.
|
59 | if (n.dtype != Dtype::FP32) { |
| 580 | 2 | return eager::compile_eager(dag); | |
| 581 | } | ||
| 582 | } | ||
| 583 | |||
| 584 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 7 times.
|
8 | if (pattern == FusionPattern::ResidualRMSNorm) { |
| 585 | 1 | const TraceNode* node_rms = nullptr; | |
| 586 | 1 | const TraceNode* node_add = nullptr; | |
| 587 | 1 | const TraceNode* node_res = nullptr; | |
| 588 | 1 | const TraceNode* node_gamma = nullptr; | |
| 589 | |||
| 590 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
|
5 | for (const auto& n : nodes) { |
| 591 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
5 | if (n.op != TraceOpKind::RMSNorm || n.inputs.empty()) continue; |
| 592 | 1 | const TraceNode& add = nodes[n.inputs[0]]; | |
| 593 | // The hand kernel below is exactly `x += res; rms_norm(x, gamma)`. | ||
| 594 | // Anything else — no residual, no gain, or downstream ops — is not | ||
| 595 | // this pattern. | ||
| 596 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (add.op != TraceOpKind::Add || add.inputs.size() < 2) continue; |
| 597 |
2/4✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
1 | if (n.inputs.size() < 2 || !n.outputs.empty()) continue; |
| 598 | 1 | node_rms = &n; | |
| 599 | 1 | node_add = &add; | |
| 600 | 1 | node_res = &nodes[add.inputs[1]]; | |
| 601 | 1 | node_gamma = &nodes[n.inputs[1]]; | |
| 602 | 1 | break; | |
| 603 | } | ||
| 604 | |||
| 605 |
4/8✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 time.
|
1 | if (node_rms && node_add && node_res && node_gamma) { |
| 606 | 1 | int B = node_add->rows; | |
| 607 | 1 | int D = node_add->cols; | |
| 608 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | float eps = node_rms->scalar > 0.0f ? node_rms->scalar : 1e-5f; |
| 609 | |||
| 610 | 2 | auto make_handle = [B, D, eps](void* x_buf, const void* res_buf, const void* gamma_buf, void* y_buf) { | |
| 611 | 1 | auto h = std::make_shared<TraceHandleImpl>(); | |
| 612 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
3 | h->execute_fn = [x_buf, res_buf, gamma_buf, y_buf, B, D, eps]() { |
| 613 | 2 | detail::cpu::jit::fused_residual_rmsnorm( | |
| 614 | 2 | static_cast<float*>(x_buf), | |
| 615 | 2 | static_cast<const float*>(res_buf), | |
| 616 | 2 | static_cast<const float*>(gamma_buf), | |
| 617 | 2 | eps, | |
| 618 | 2 | static_cast<float*>(y_buf), | |
| 619 | 2 | B, D | |
| 620 | ); | ||
| 621 | 2 | }; | |
| 622 | 1 | return h; | |
| 623 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | }; |
| 624 | |||
| 625 | 1 | void* x_buf = node_add->buffer; | |
| 626 | 1 | const void* res_buf = node_res->buffer; | |
| 627 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | const void* gamma_buf = node_gamma ? node_gamma->buffer : nullptr; |
| 628 | 1 | void* y_buf = node_rms->buffer; | |
| 629 | |||
| 630 | 1 | auto handle = make_handle(x_buf, res_buf, gamma_buf, y_buf); | |
| 631 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | handle->rebind_fn = [make_handle](const std::vector<void*>& in_b, const std::vector<void*>& out_b) { |
| 632 | ✗ | const void* res_p = in_b.size() > 1 ? in_b[1] : nullptr; | |
| 633 | ✗ | const void* gamma_p = in_b.size() > 2 ? in_b[2] : nullptr; | |
| 634 | ✗ | void* x_p = out_b.size() > 0 ? out_b[0] : in_b[0]; | |
| 635 | ✗ | void* y_p = out_b.size() > 1 ? out_b[1] : (out_b.size() > 0 ? out_b[0] : nullptr); | |
| 636 | ✗ | return make_handle(x_p, res_p, gamma_p, y_p); | |
| 637 | }; | ||
| 638 | 1 | return handle; | |
| 639 | 1 | } | |
| 640 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 time.
|
7 | } else if (pattern == FusionPattern::LayerNormModulate) { |
| 641 | 1 | const TraceNode* node_ln = nullptr; | |
| 642 | 1 | const TraceNode* node_gamma = nullptr; | |
| 643 | 1 | const TraceNode* node_beta = nullptr; | |
| 644 | 1 | const TraceNode* node_scale = nullptr; | |
| 645 | 1 | const TraceNode* node_shift = nullptr; | |
| 646 | 1 | const TraceNode* node_out = nullptr; | |
| 647 | |||
| 648 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
|
10 | for (const auto& n : nodes) { |
| 649 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 time.
|
9 | if (n.op == TraceOpKind::LayerNorm) { |
| 650 | 1 | node_ln = &n; | |
| 651 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if (n.inputs.size() > 1) node_gamma = &nodes[n.inputs[1]]; |
| 652 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if (n.inputs.size() > 2) node_beta = &nodes[n.inputs[2]]; |
| 653 | 1 | } | |
| 654 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
9 | if (n.is_live_at_end && n.op != TraceOpKind::LayerNorm) { |
| 655 | 1 | node_out = &n; | |
| 656 | 1 | } | |
| 657 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (n.op == TraceOpKind::Modulate) { |
| 658 | ✗ | if (n.inputs.size() > 1) node_scale = &nodes[n.inputs[1]]; | |
| 659 | ✗ | if (n.inputs.size() > 2) node_shift = &nodes[n.inputs[2]]; | |
| 660 | ✗ | } | |
| 661 | } | ||
| 662 | |||
| 663 | // Find scale/shift from expression ln * (1 + scale) + shift if not directly Modulate op | ||
| 664 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1 | if (!node_scale || !node_shift) { |
| 665 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
|
10 | for (const auto& n : nodes) { |
| 666 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
9 | if (n.op == TraceOpKind::AddScalar && n.inputs.size() > 0) { |
| 667 | 1 | node_scale = &nodes[n.inputs[0]]; | |
| 668 | 1 | } | |
| 669 |
3/4✓ Branch 0 taken 1 time.
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
|
9 | if (n.op == TraceOpKind::Add && n.inputs.size() > 1) { |
| 670 | 1 | int rhs = n.inputs[1]; | |
| 671 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | if (nodes[rhs].op == TraceOpKind::Input) { |
| 672 | 1 | node_shift = &nodes[rhs]; | |
| 673 | 1 | } | |
| 674 | 1 | } | |
| 675 | } | ||
| 676 | 1 | } | |
| 677 | |||
| 678 | // The hand kernel dereferences all five operands, so a trace missing | ||
| 679 | // any of them (a non-affine LayerNorm, a modulate with no shift) is | ||
| 680 | // not this pattern and falls through to the generic path. | ||
| 681 |
6/12✓ 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.
✓ Branch 6 taken 1 time.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 time.
|
1 | if (node_ln && node_out && node_gamma && node_beta && node_scale && node_shift) { |
| 682 | 1 | int R = node_ln->rows; | |
| 683 | 1 | int D = node_ln->cols; | |
| 684 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | float eps = node_ln->scalar > 0.0f ? node_ln->scalar : 1e-5f; |
| 685 | |||
| 686 | 2 | auto make_handle = [R, D, eps](const void* x, const void* g, const void* b, | |
| 687 | const void* scale, const void* shift, void* y) { | ||
| 688 | 1 | auto h = std::make_shared<TraceHandleImpl>(); | |
| 689 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
3 | h->execute_fn = [x, g, b, scale, shift, y, R, D, eps]() { |
| 690 | 2 | detail::cpu::jit::fused_layernorm_modulate( | |
| 691 | 2 | static_cast<const float*>(x), | |
| 692 | 2 | static_cast<const float*>(g), | |
| 693 | 2 | static_cast<const float*>(b), | |
| 694 | 2 | static_cast<const float*>(scale), | |
| 695 | 2 | static_cast<const float*>(shift), | |
| 696 | 2 | eps, | |
| 697 | 2 | static_cast<float*>(y), | |
| 698 | 2 | R, D | |
| 699 | ); | ||
| 700 | 2 | }; | |
| 701 | 1 | return h; | |
| 702 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | }; |
| 703 | |||
| 704 | 1 | const void* x_buf = nodes[node_ln->inputs[0]].buffer; | |
| 705 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | const void* g_buf = node_gamma ? node_gamma->buffer : nullptr; |
| 706 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | const void* b_buf = node_beta ? node_beta->buffer : nullptr; |
| 707 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | const void* s_buf = node_scale ? node_scale->buffer : nullptr; |
| 708 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
|
1 | const void* sh_buf = node_shift ? node_shift->buffer : nullptr; |
| 709 | 1 | void* y_buf = node_out->buffer; | |
| 710 | |||
| 711 | 1 | auto handle = make_handle(x_buf, g_buf, b_buf, s_buf, sh_buf, y_buf); | |
| 712 |
1/2✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
|
1 | handle->rebind_fn = [make_handle](const std::vector<void*>& in_b, const std::vector<void*>& out_b) { |
| 713 | ✗ | const void* x = in_b.size() > 0 ? in_b[0] : nullptr; | |
| 714 | ✗ | const void* g = in_b.size() > 1 ? in_b[1] : nullptr; | |
| 715 | ✗ | const void* b = in_b.size() > 2 ? in_b[2] : nullptr; | |
| 716 | ✗ | const void* sc = in_b.size() > 3 ? in_b[3] : nullptr; | |
| 717 | ✗ | const void* sh = in_b.size() > 4 ? in_b[4] : nullptr; | |
| 718 | ✗ | void* y = out_b.size() > 0 ? out_b[0] : nullptr; | |
| 719 | ✗ | return make_handle(x, g, b, sc, sh, y); | |
| 720 | }; | ||
| 721 | 1 | return handle; | |
| 722 | 1 | } | |
| 723 | ✗ | } | |
| 724 | |||
| 725 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 6 times.
|
46 | for (const auto& n : nodes) { |
| 726 |
2/4✓ Branch 0 taken 40 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 40 times.
|
40 | if (n.op == TraceOpKind::RMSNorm || n.op == TraceOpKind::LayerNorm) { |
| 727 | ✗ | return eager::compile_eager(dag); | |
| 728 | } | ||
| 729 | } | ||
| 730 | |||
| 731 | 6 | return compile_cpu_elementwise(dag); | |
| 732 | 10 | } | |
| 733 | |||
| 734 | #else // !BROTENSOR_HAS_BRASS_JIT | ||
| 735 | |||
| 736 | std::shared_ptr<TraceHandleImpl> compile_cpu(const TraceDAG& /*dag*/, FusionPattern /*pattern*/) { | ||
| 737 | throw std::runtime_error("brotensor::jit: Brass CPU JIT compiler is not enabled in this build"); | ||
| 738 | } | ||
| 739 | |||
| 740 | #endif // BROTENSOR_HAS_BRASS_JIT | ||
| 741 | |||
| 742 | } // namespace cpu | ||
| 743 | } // namespace brotensor::jit | ||
| 744 |