mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-07-21 02:05:51 +00:00
* ggml-et: Add performance logging * ggml-et: Quants helpers * ggml-et: Add MUL_MAT kernel * ggml-et: Add ROPE kernel * ggml-et: Add RMS_NORM kernel * ggml-et: Add GLU kernel * ggml-et: Add SOFT_MAX kernel * ggml-et: Add GET_ROWS kernel * ggml-et: Add CONT kernel * ggml-et: Add SET_ROWS kernel * ggml-et: Add MUL_MAT_ID kernel * ggml-et: Build et kernels as part of ggml * ggml-et: Embed kernels with fs fallback * ggml-et: Build fixes * ggml-et: Add MUL_MAT F32xF32 op * ggml_et: Add MUL_MAT_ID op * ggml-et: Disable offloading for debug * ggml-et: Refactor out block ops * ggml-et: ggml backend API changes * ggml-et: Add RESHAPE/TRANSPOSE to supported * ggml-et: Add CONT_F16 * ggml-et: Add supported ops doc * gglm-et: Initial doc * ggml-et: Remove runtime import hacks We can now import the runtime by a simple find_package(), so we can cleanup the CMakeLists.txt. * ggml-et: Fix GET_ROWS kernel Fix lost batch dimension. Also clean vibe-comments. * ggml-et: Fix SET_ROWS kernel Remove incorrect broadcasting guard. * ggml-et: Use custom instruction for fp32->fp16 * ggml-et: Vectorize set_rows fp32->fp16 * ggml-et: Fix ROPE kernel (yarn) ggml-et: fix et_logf WIP: Fix ramp WIP: fix ROPE! * ggml-et: Better sinf * ggml-et: Fix SOFT_MAX Add `max_bias` and `sink` support. * ggml-et: Fix CONT Reorder from contiguous write to read with atomic stores. * ggml-et: Fix elmap kernel Remainder handlin * ggml-et: Fix MUL_MAT MUL_MAT_ID remainders * ggml-et: Fix ET-SOC reference * ggml-et: Fix embed kernels scripts for old python This allows GGML-ET to build on pre-3.8 python. * Add sysemu support with compile time flag `-DGGML_ET_SYSEMU=ON` (#6) * Example using ET-Soc-1 emulator configuration Example usage: ```bash cmake -B build -DGGML_CUDA=OFF -DGGML_ET=ON -DLLAMA_CURL=OFF -DGGML_CCACHE=ON cmake --build build --config Release -j $(nproc) time ./build/bin/test-backend-ops ./build/bin/llama-server \ --model Qwen3-0.6B-Q8_0.gguf \ --alias Qwen3-0.6B-Q8_0 \ -fa 0 \ --ctx-size 1024 \ --no-warmup \ --host 127.0.0.1 \ --port 8080 ``` * build: proper dep tracking for kernels * support host using MOLD linker * initial multi core GET_ROW F32 implementation * vectorized q8 dequant * wip: cland warning clenaups and initial logging refactor * wip: message default message cleanup * chore: message cleanups * cmake cleanup * migrate to use platform provided functions * cmake back into subdir * support et_print() in kernels * fix: repair kernel building * perf: operations run async by default * debug: proper kernel dep tracking and error detection on kenrel launch * fix: kernel binary dep tracking and fixing get_rows_f32 erroring * perf: back to doing async kernel runs by default * perf: vectorize and parallel device memset * merge matmul work * misc: align allocation and enable all offload * misc: delete deadcode and respect memory limits * fix: repair tensor debug print * fix: loosen RMS_NORM op percision * feat: Q4_0 GET_ROWS * perf: FP32 MUL_MAT using TensorFMA * update limitations * perf: redue L1 load in compute_block_dot_product_q8_0 * feat: save kernel mapping (name to id) when profiling is enabled * chore: memops cleanup * perf: parallelize softmax by rows * perf: vectorize 2nd phase of softmax * perf: ban GET_ROWS from offloaded * perf: vectorize and non-atomic for eltwise ops and sub support * perf: vectorize normal rope * perf: glu runs in parallel * merge: manually merge saqib's work on kernel fixes * perf: more vectorized RoPE * perf: parallelize mul_mat_id * perf: parallelize set_rows_f32 * perf: vectorize softmax * feat: support kernel fusion and fuse RMS_NORM + MUL * fix: mostly resolve test-backend-ops failure in SOFT_MAX and ROPE * fix: bump max rope dims for gemma * feat: GeGLU and SCALE support to fully offload Gemma * perf: faster device memset * feat: get_rows supporting Q4_K and avoid cont cache coherent issues * better F32 MM * feat: NORM for ET backend * feat: SQR for ET backend * feat: UNARY on ET * feat: el_map support broadcasting for ET * feat: SUM_ROWS in ET backend * feat: more ops in ET backend * feat: WKV* operators in ET backend * perf: parallelize operators across cacheline instead of row * perf: parallelize get_rows on cacheline * wip: baseline FlashAttention for ET backend * wip: enough FA and CPY f32->f16 to run llama 3.1 fully offloaded with FA on * feat: f16 x f16 -> f32 MM using matrix engine * wip: f16 FlashAttention using matrix engine * wip: clean up * feat: barriers * perf: optimize FA_F16 in ET * perf: vectorize pack_k_for_transpose16 * perf: prefetch next loop matrix tile * perf: FlashAttention 2nd MM uses TensorFMA and optimizations * cleanup: flashattention reorg * perf: optimizations and fixes * feat: L2SCP API and make FlashAttention support DV = 256 for gemma * perf: parallelize norms beyond single row * feat: GATED_DELTA_NET support and relaxed L2_NORM requirment * feat: loosen RMS_NORM, NORM, ROPE contingous req too * feat: repeat supports brocasting on dim 0 and loosen cont check * feat: FILL and DIAG operator * feat: loosen UNARY support chcek * feat: TRI support * feat: SOLVE_TRI support * feat: basic SET support * feat: loosen CONT req * perf: fp16_to_fp32 use ASM * feat: IMROPE support * feat: PAD support * feat: global barrier * fix: view must live on the same backend as backing tensor * feat: relax CONCAT in ET backend * feat: dead simple CUMSUM implementation * feat: basic SSM_CONV support * feat: loosen CONCAT req * feat: relax GATED_DELTA_NET and add SET support proper * cleanup: cleanup LCM math * feat: SWIGLU single input * feat: SSM_SCAN support * feat: el_map supports non aligned tensors in best effort * feat: basic GROUP_NORM support * feat: loosen MUL_MAT capablities slightly * feat: loosen MUL_MAT and GET_ROWS and add IM2COL * feat: special case for softmax 1x1x1x1 * feat: loosen SOFT_MAX req in ET backend * fix: el_map unaligned acse fixes * perf: optimize zero_acc_vec in flash_attn_ext_f16_me * perf: use hart 1 for packing in MM and FA for FP16 * feat: kernel semaphore * perf: better instruction sequence in FlashAttention * fix: gated_delta_net with proper masking * perf: better parallelization for GATED_DELTA_NET * perf: parallelize SSM_CONV over nr * perf: vectorize SSM_CONV * perf: optimize MUL_MAT for q8 * feat: support Gemma 4 * fix: support multi-device * feat: broader GLU support * feat: unary ops supports view * fix: repair fp16 MM using matrix engine * perf: handle large N GEMV better * perf: better q8_0 MM * perf: better set_rows * add back deleted files * fix: repair after merge * feat: POC version of uberkernel * feat: RMS_NORM in uberkernel * feat: add more kernels into usage * chore: clean up uberkernel compilation * perf: faster flash attention * perf: opt flash attention for large seq length * feat: loosen op bounds. clamp and mean support * perf: vectorize ssm_scan * perf: slightly faster FA * perf: FlashAttention parallel MM and load * perf: fuse Q8 MM and ADD * feat: basic conv kernel for ET * softMAx_test * set_rows_f32 * get_rows and cont * testing * set_rows_exp * Junk addition * Narrowing the issue * Update flash_attn_ext_f16_me.c Focusing FA_ext_f16_me * test * Eviction updated * Detailed cache eviction debug * mulmat * removeal of `BUILD_FOR_UBERKERNEL` flag * cleaning... * fix: balance FCC0 count * feat: implement mul_mat and mul_mat_id for Q4_0 type * optimize uberkernel plan upload * add mul_mat q4 into uberkernel * enable gating flush to just uberkernel * update docs for ET * update op support for ET * et-backend: optimize Q4_0 and Q8_0 mul_mat_id row accumulations * et-backend: specialize mul_mat_id kernels for Q4_0 and Q8_0 * et-backend: fix RoPE YaRN corr_dim formula and handle degenerate inputs * test-backend-ops: add DeepSeek-V2-Lite RoPE test coverage * et-backend: add Q4_0 mul_mat matrix-engine kernel using TensorFMA32 * et-backend: vectorize Q4_0 matrix-engine dequantization * et-backend: support hybrid matrix/vector engine execution for Q4_0 mul_mat tail * et-backend: run partial-N tiles on matrix engine for Q4_0 mul_mat * et-backend: route Q4_0 mul_mat N < 53 to vecdot for better prefill latency * Update uberkernel.c * Update unary_f32.c * gemma 4 * bisect gemma4: enable scale_f32 only * bisect gemma4: +rms_norm_f32 * bisect gemma4: +rms_norm_mul_f32 * bisect gemma4: disable rms_norm_mul_f32 -- BREAKS OUTPUT * bisect gemma4: +rope_f32 (skip rms_norm_mul) * bisect gemma4: +el_map_f32 * bisect gemma4: +softmax_f32 * bisect gemma4: +get_rows_f32 * bisect gemma4: +glu_f32 * bisect gemma4: +mul_mat_f32 +mul_mat_f32_matrix_engine * bisect gemma4: +mul_mat_f16 +mul_mat_f16_matrix_engine * bisect gemma4: +mul_mat_Q8_0 +mul_mat_Q4_0 * bisect gemma4: +flash_attn_ext_f32 +flash_attn_ext_f16_me * bisect gemma4: +mul_mat_id_f32 * bisect gemma4: +sum_rows_f32 * bisect gemma4: +cont_f16 * bisect gemma4: +fill_f32 * bisect gemma4: +unary_f32 (all ops re-enabled except rms_norm_mul) * Update rms_norm_mul_f32.c * bisect2 gemma4 n64: +scale_f32 only * bisect2 gemma4 n64: +rms_norm_f32 +rope_f32 * bisect2 gemma4 n64: +rms_norm_mul_f32 (with ET_UBERKERNEL eviction fix) * bisect2 gemma4 n64: +el_map +get_rows +glu +softmax (skip rms_norm_mul) * bisect2 gemma4 n64: all ops enabled except rms_norm_mul * bisect2 n64: test unary+cont+fill+sum_rows (no mul_mat/flash_attn) * bisect2 n64: +mul_mat_f32 +mul_mat_f32_matrix_engine * bisect2 n64: +mul_mat_f16 +mul_mat_f16_matrix_engine * bisect2 n64: +mul_mat_Q8_0 +mul_mat_Q4_0 * bisect2 n64: +mul_mat_Q8_0 only (disable Q4_0) * bisect2 n64: +mul_mat_Q4_0 only (Q8_0 breaks) * bisect2 n64: +mul_mat_id +flash_attn_ext (skip Q8_0) * run-3: matmul + rms_norm_mul * run-4 * Revert "run-4" * run5 * changes after cleanup * cleanup before upstream * restrict changes into ET backend * move kernel embedding from Python to CMake * move uberkernel gen into CMake * apply clang format * update CMake style * update to match C and C++ style * use source ggml and quant headers instead of ET's * MROPE support * absorb view ops into same branch as none * fix bad rebase * add marty1885 to codeowners * oops * remove redundant newline * fix CI editor warnings --------- Co-authored-by: Vidas <vidas@nuolat.lt> Co-authored-by: Gianluca Guida <glguida@tlbflush.org> Co-authored-by: Gianluca Guida <gianluca@nekko.ai> Co-authored-by: ubergarm <leimgrub@gmail.com> Co-authored-by: SaqibAkram-10xE <saqib.akram@10xengineers.ai> Co-authored-by: Rehan Qasim <rehan.qasim@10xengineers.ai>
498 lines
21 KiB
C++
498 lines
21 KiB
C++
#include "ggml-et-cpu-compare.h"
|
|
|
|
#include "ggml-cpu/ggml-cpu-impl.h"
|
|
#include "ggml-cpu/ops.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
bool ggml_et_cpu_compare_init_pre(ggml_et_cpu_compare_ctx * ctx, const ggml_tensor * node, ggml_op op) {
|
|
if (!ctx || !node) {
|
|
GGML_LOG_ERROR("ET: Invalid parameters for CPU compare init\n");
|
|
return false;
|
|
}
|
|
|
|
// Clear context
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
|
|
// Calculate actual buffer sizes - use backend buffer size for accurate copy
|
|
auto get_tensor_buffer_size = [](const ggml_tensor * tensor) -> size_t {
|
|
if (!tensor) {
|
|
return 0;
|
|
}
|
|
|
|
if (tensor->buffer) {
|
|
// Get actual backend buffer size
|
|
size_t buffer_size = ggml_backend_buffer_get_size(tensor->buffer);
|
|
|
|
// Use the full buffer size to avoid any truncation issues
|
|
return buffer_size;
|
|
} else {
|
|
// Fallback to logical size if no buffer
|
|
return ggml_nbytes(tensor);
|
|
}
|
|
};
|
|
|
|
ctx->src0_size = get_tensor_buffer_size(node->src[0]);
|
|
ctx->src1_size = get_tensor_buffer_size(node->src[1]);
|
|
ctx->src2_size = get_tensor_buffer_size(node->src[2]);
|
|
ctx->dst_size = get_tensor_buffer_size(node);
|
|
|
|
// Allocate CPU buffers for all tensors
|
|
if (ctx->src0_size > 0) {
|
|
ctx->cpu_src0_data = malloc(ctx->src0_size);
|
|
if (!ctx->cpu_src0_data) {
|
|
GGML_LOG_ERROR("ET: Failed to allocate CPU src0 buffer\n");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (ctx->src1_size > 0) {
|
|
ctx->cpu_src1_data = malloc(ctx->src1_size);
|
|
if (!ctx->cpu_src1_data) {
|
|
GGML_LOG_ERROR("ET: Failed to allocate CPU src1 buffer\n");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (ctx->src2_size > 0) {
|
|
ctx->cpu_src2_data = malloc(ctx->src2_size);
|
|
if (!ctx->cpu_src2_data) {
|
|
GGML_LOG_ERROR("ET: Failed to allocate CPU src2 buffer\n");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
ctx->cpu_dst_data = malloc(ctx->dst_size);
|
|
if (!ctx->cpu_dst_data) {
|
|
GGML_LOG_ERROR("ET: Failed to allocate CPU dst buffer\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
ctx->et_dst_data = malloc(ctx->dst_size);
|
|
if (!ctx->et_dst_data) {
|
|
GGML_LOG_ERROR("ET: Failed to allocate ET dst buffer\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
// Copy data from ET device buffers to CPU host buffers
|
|
if (ctx->src0_size > 0) {
|
|
// Copy logical tensor size - ggml_backend_tensor_get handles stride layout internally
|
|
size_t logical_size = ggml_nbytes(node->src[0]);
|
|
ggml_backend_tensor_get(node->src[0], ctx->cpu_src0_data, 0, logical_size);
|
|
}
|
|
if (ctx->src1_size > 0) {
|
|
size_t logical_size = ggml_nbytes(node->src[1]);
|
|
ggml_backend_tensor_get(node->src[1], ctx->cpu_src1_data, 0, logical_size);
|
|
}
|
|
if (ctx->src2_size > 0) {
|
|
size_t logical_size = ggml_nbytes(node->src[2]);
|
|
ggml_backend_tensor_get(node->src[2], ctx->cpu_src2_data, 0, logical_size);
|
|
}
|
|
|
|
// Copy destination data from device (for operations like SET_ROWS that modify existing data)
|
|
// Most ops create new tensors so this is unused, but SET_ROWS requires existing dst data
|
|
{
|
|
size_t logical_size = ggml_nbytes(node);
|
|
ggml_backend_tensor_get(node, ctx->cpu_dst_data, 0, logical_size);
|
|
}
|
|
|
|
// Create CPU backend for reference computation
|
|
GGML_LOG_DEBUG("ET: Creating CPU backend for reference computation\n");
|
|
ctx->cpu_backend = ggml_backend_cpu_init();
|
|
if (!ctx->cpu_backend) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU backend\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
// Create GGML context for CPU tensors
|
|
GGML_LOG_DEBUG("ET: Creating GGML context for CPU computation\n");
|
|
ggml_init_params ctx_params;
|
|
ctx_params.mem_size = ggml_tensor_overhead() * 4 + ggml_graph_overhead(); // up to 4 tensors + graph
|
|
ctx_params.mem_buffer = nullptr;
|
|
ctx_params.no_alloc = true; // We'll manage data ourselves
|
|
ctx->ggml_ctx = ggml_init(ctx_params);
|
|
if (!ctx->ggml_ctx) {
|
|
GGML_LOG_ERROR("ET: Failed to create GGML context\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
// Create CPU tensors with proper context
|
|
if (node->src[0]) {
|
|
ctx->cpu_src0 = ggml_new_tensor(ctx->ggml_ctx, node->src[0]->type, GGML_MAX_DIMS, node->src[0]->ne);
|
|
if (!ctx->cpu_src0) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU src0 tensor\n");
|
|
goto cleanup;
|
|
}
|
|
ctx->cpu_src0->data = ctx->cpu_src0_data;
|
|
// Copy stride array (nb) for correct memory layout
|
|
memcpy(ctx->cpu_src0->nb, node->src[0]->nb, sizeof(node->src[0]->nb));
|
|
// Copy op_params if present
|
|
memcpy(ctx->cpu_src0->op_params, node->src[0]->op_params, sizeof(node->src[0]->op_params));
|
|
}
|
|
|
|
if (node->src[1]) {
|
|
ctx->cpu_src1 = ggml_new_tensor(ctx->ggml_ctx, node->src[1]->type, GGML_MAX_DIMS, node->src[1]->ne);
|
|
if (!ctx->cpu_src1) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU src1 tensor\n");
|
|
goto cleanup;
|
|
}
|
|
ctx->cpu_src1->data = ctx->cpu_src1_data;
|
|
// Copy stride array (nb) for correct memory layout
|
|
memcpy(ctx->cpu_src1->nb, node->src[1]->nb, sizeof(node->src[1]->nb));
|
|
// Copy op_params if present
|
|
memcpy(ctx->cpu_src1->op_params, node->src[1]->op_params, sizeof(node->src[1]->op_params));
|
|
}
|
|
|
|
if (node->src[2]) {
|
|
ctx->cpu_src2 = ggml_new_tensor(ctx->ggml_ctx, node->src[2]->type, GGML_MAX_DIMS, node->src[2]->ne);
|
|
if (!ctx->cpu_src2) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU src2 tensor\n");
|
|
goto cleanup;
|
|
}
|
|
ctx->cpu_src2->data = ctx->cpu_src2_data;
|
|
// Copy stride array (nb) for correct memory layout
|
|
memcpy(ctx->cpu_src2->nb, node->src[2]->nb, sizeof(node->src[2]->nb));
|
|
// Copy op_params if present
|
|
memcpy(ctx->cpu_src2->op_params, node->src[2]->op_params, sizeof(node->src[2]->op_params));
|
|
}
|
|
|
|
return true;
|
|
|
|
cleanup:
|
|
ggml_et_cpu_compare_free(ctx);
|
|
return false;
|
|
}
|
|
|
|
bool ggml_et_cpu_compare_compute_and_check(ggml_et_cpu_compare_ctx * ctx,
|
|
const ggml_tensor * node,
|
|
const ggml_et_cpu_compare_config * config) {
|
|
if (!ctx || !ctx->cpu_backend || !ctx->ggml_ctx || !node || !config) {
|
|
GGML_LOG_ERROR("ET: Invalid parameters for CPU compute and check\n");
|
|
return false;
|
|
}
|
|
|
|
// Create operation-specific CPU destination tensor based on the node's operation
|
|
ggml_op op = node->op;
|
|
switch (op) {
|
|
case GGML_OP_MUL:
|
|
ctx->cpu_dst = ggml_mul(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1);
|
|
break;
|
|
case GGML_OP_ADD:
|
|
ctx->cpu_dst = ggml_add(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1);
|
|
break;
|
|
case GGML_OP_MUL_MAT:
|
|
ctx->cpu_dst = ggml_mul_mat(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1);
|
|
break;
|
|
case GGML_OP_MUL_MAT_ID:
|
|
// MUL_MAT_ID: Mixture of Experts matrix multiplication
|
|
// src0 (as): expert weight matrices [K, M, n_expert]
|
|
// src1 (b): activations [K, n_expert_used, batch]
|
|
// src2 (ids): expert selection indices [n_expert_used, batch]
|
|
ctx->cpu_dst = ggml_mul_mat_id(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1, ctx->cpu_src2);
|
|
break;
|
|
case GGML_OP_ROPE:
|
|
{
|
|
const int32_t * op_params = (const int32_t *) node->op_params;
|
|
const int32_t n_dims = op_params[1];
|
|
const int32_t mode = op_params[2];
|
|
const int32_t n_ctx_orig = op_params[4];
|
|
const float freq_base = *((const float *) (op_params + 5));
|
|
const float freq_scale = *((const float *) (op_params + 6));
|
|
const float ext_factor = *((const float *) (op_params + 7));
|
|
const float attn_factor = *((const float *) (op_params + 8));
|
|
const float beta_fast = *((const float *) (op_params + 9));
|
|
const float beta_slow = *((const float *) (op_params + 10));
|
|
|
|
if (mode & GGML_ROPE_TYPE_MROPE) {
|
|
int sections[GGML_MROPE_SECTIONS];
|
|
memcpy(sections, op_params + 11, sizeof(sections));
|
|
ctx->cpu_dst = ggml_rope_multi(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1, ctx->cpu_src2,
|
|
n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale,
|
|
ext_factor, attn_factor, beta_fast, beta_slow);
|
|
} else {
|
|
ctx->cpu_dst = ggml_rope_ext(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1, ctx->cpu_src2,
|
|
n_dims, mode, n_ctx_orig, freq_base, freq_scale, ext_factor,
|
|
attn_factor, beta_fast, beta_slow);
|
|
}
|
|
}
|
|
break;
|
|
case GGML_OP_RMS_NORM:
|
|
// Extract epsilon parameter from op_params (stored as float)
|
|
{
|
|
float eps;
|
|
memcpy(&eps, node->op_params, sizeof(float));
|
|
ctx->cpu_dst = ggml_rms_norm(ctx->ggml_ctx, ctx->cpu_src0, eps);
|
|
}
|
|
break;
|
|
case GGML_OP_SQR:
|
|
ctx->cpu_dst = ggml_sqr(ctx->ggml_ctx, ctx->cpu_src0);
|
|
break;
|
|
case GGML_OP_UNARY:
|
|
{
|
|
ggml_unary_op uop = (ggml_unary_op) ggml_get_op_params_i32(node, 0);
|
|
ctx->cpu_dst = ggml_unary(ctx->ggml_ctx, ctx->cpu_src0, uop);
|
|
}
|
|
break;
|
|
case GGML_OP_SUM_ROWS:
|
|
ctx->cpu_dst = ggml_sum_rows(ctx->ggml_ctx, ctx->cpu_src0);
|
|
break;
|
|
case GGML_OP_MEAN:
|
|
ctx->cpu_dst = ggml_mean(ctx->ggml_ctx, ctx->cpu_src0);
|
|
break;
|
|
case GGML_OP_CLAMP:
|
|
{
|
|
float clamp_min, clamp_max;
|
|
memcpy(&clamp_min, (const float *) node->op_params + 0, sizeof(float));
|
|
memcpy(&clamp_max, (const float *) node->op_params + 1, sizeof(float));
|
|
ctx->cpu_dst = ggml_clamp(ctx->ggml_ctx, ctx->cpu_src0, clamp_min, clamp_max);
|
|
}
|
|
break;
|
|
case GGML_OP_GLU:
|
|
// Extract GLU parameters from op_params (split mode only)
|
|
{
|
|
int32_t glu_op_type = ggml_get_op_params_i32(node, 0); // GLU variant
|
|
ggml_glu_op glu_op = (ggml_glu_op) glu_op_type;
|
|
|
|
// Only support split tensor mode
|
|
if (!ctx->cpu_src1) {
|
|
GGML_LOG_ERROR("ET: GLU CPU comparison requires split tensor mode\n");
|
|
return false;
|
|
}
|
|
ctx->cpu_dst = ggml_glu_split(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1, glu_op);
|
|
}
|
|
break;
|
|
case GGML_OP_SOFT_MAX:
|
|
{
|
|
// Extract scale and max_bias from op_params
|
|
float scale = 1.0f;
|
|
float max_bias = 0.0f;
|
|
memcpy(&scale, (const float *) node->op_params + 0, sizeof(float));
|
|
memcpy(&max_bias, (const float *) node->op_params + 1, sizeof(float));
|
|
|
|
if (ctx->cpu_src1 || scale != 1.0f || max_bias != 0.0f) {
|
|
// Use extended softmax when mask or non-default parameters are present
|
|
ctx->cpu_dst = ggml_soft_max_ext(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1, scale, max_bias);
|
|
} else {
|
|
// Use simple softmax when no mask and default parameters
|
|
ctx->cpu_dst = ggml_soft_max(ctx->ggml_ctx, ctx->cpu_src0);
|
|
}
|
|
|
|
// Add sinks if present
|
|
if (ctx->cpu_src2) {
|
|
ggml_soft_max_add_sinks(ctx->cpu_dst, ctx->cpu_src2);
|
|
}
|
|
}
|
|
break;
|
|
case GGML_OP_GET_ROWS:
|
|
ctx->cpu_dst = ggml_get_rows(ctx->ggml_ctx, ctx->cpu_src0, ctx->cpu_src1);
|
|
break;
|
|
case GGML_OP_CONT:
|
|
ctx->cpu_dst = ggml_cont(ctx->ggml_ctx, ctx->cpu_src0);
|
|
break;
|
|
case GGML_OP_SET_ROWS:
|
|
{
|
|
// SET_ROWS operation scatters src0 rows to dst[src1] positions
|
|
// Create destination tensor (this is the "view" that SET_ROWS returns)
|
|
ggml_tensor * cpu_dst_base = ggml_new_tensor(ctx->ggml_ctx, node->type, GGML_MAX_DIMS, node->ne);
|
|
if (!cpu_dst_base) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU destination base tensor for SET_ROWS\n");
|
|
return false;
|
|
}
|
|
cpu_dst_base->data = ctx->cpu_dst_data;
|
|
memcpy(cpu_dst_base->nb, node->nb, sizeof(node->nb));
|
|
|
|
// Note: cpu_dst_data already contains the pre-existing destination data from device
|
|
// SET_ROWS will update specific rows, leaving others unchanged
|
|
|
|
// Perform SET_ROWS operation: returns a view that scatters src0 rows to dst[src1] positions
|
|
ctx->cpu_dst = ggml_set_rows(ctx->ggml_ctx, cpu_dst_base, ctx->cpu_src0, ctx->cpu_src1);
|
|
}
|
|
break;
|
|
default:
|
|
GGML_LOG_ERROR("ET: Unsupported operation %s for CPU comparison\n", ggml_op_name(op));
|
|
return false;
|
|
}
|
|
|
|
if (!ctx->cpu_dst) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU destination tensor for operation %s\n", ggml_op_name(op));
|
|
return false;
|
|
}
|
|
|
|
ctx->cpu_dst->data = ctx->cpu_dst_data;
|
|
// Copy stride array (nb) for correct memory layout - except for CONT which should keep contiguous strides
|
|
if (op != GGML_OP_CONT) {
|
|
memcpy(ctx->cpu_dst->nb, node->nb, sizeof(node->nb));
|
|
}
|
|
// For CONT operations, keep the contiguous strides created by ggml_cont()
|
|
|
|
// Create minimal computation graph
|
|
ctx->cpu_graph = ggml_new_graph_custom(ctx->ggml_ctx, 1, false);
|
|
if (!ctx->cpu_graph) {
|
|
GGML_LOG_ERROR("ET: Failed to create CPU computation graph\n");
|
|
return false;
|
|
}
|
|
ctx->cpu_graph->nodes[0] = ctx->cpu_dst;
|
|
ctx->cpu_graph->n_nodes = 1;
|
|
|
|
// Log input data for debugging if enabled
|
|
if (config && config->log_differences) {
|
|
if (ctx->cpu_src0_data && ctx->src0_size >= 4) {
|
|
GGML_LOG_DEBUG("ET: CPU src0 first few bytes: %02x %02x %02x %02x\n", ((uint8_t *) ctx->cpu_src0_data)[0],
|
|
((uint8_t *) ctx->cpu_src0_data)[1], ((uint8_t *) ctx->cpu_src0_data)[2],
|
|
((uint8_t *) ctx->cpu_src0_data)[3]);
|
|
}
|
|
if (ctx->cpu_src1_data && ctx->src1_size >= 16) {
|
|
GGML_LOG_DEBUG("ET: CPU src1 first few floats: %.6f %.6f %.6f %.6f\n", ((float *) ctx->cpu_src1_data)[0],
|
|
((float *) ctx->cpu_src1_data)[1], ((float *) ctx->cpu_src1_data)[2],
|
|
((float *) ctx->cpu_src1_data)[3]);
|
|
}
|
|
}
|
|
|
|
// Compute using CPU backend
|
|
ggml_status cpu_result = ggml_backend_graph_compute(ctx->cpu_backend, ctx->cpu_graph);
|
|
|
|
if (cpu_result != GGML_STATUS_SUCCESS) {
|
|
GGML_LOG_ERROR("ET: CPU reference computation failed with status %d\n", cpu_result);
|
|
return false;
|
|
}
|
|
|
|
// Log output data for debugging if enabled
|
|
if (config && config->log_differences && ctx->dst_size >= 16) {
|
|
GGML_LOG_DEBUG("ET: CPU dst first few floats after computation: %.6f %.6f %.6f %.6f\n",
|
|
((float *) ctx->cpu_dst_data)[0], ((float *) ctx->cpu_dst_data)[1],
|
|
((float *) ctx->cpu_dst_data)[2], ((float *) ctx->cpu_dst_data)[3]);
|
|
}
|
|
|
|
// Now copy ET device destination to host for comparison
|
|
size_t dst_logical_size = ggml_nbytes(node);
|
|
ggml_backend_tensor_get(node, ctx->et_dst_data, 0, dst_logical_size);
|
|
|
|
if (config->log_differences) {
|
|
size_t num_elements = ggml_nelements(node);
|
|
size_t max_log = std::min(num_elements, config->max_log_elements);
|
|
|
|
// Check if this is an elementwise operation that can show src inputs
|
|
bool is_elementwise = (op == GGML_OP_MUL || op == GGML_OP_ADD || op == GGML_OP_GLU);
|
|
float * cpu_src0_float = is_elementwise ? (float *) ctx->cpu_src0_data : nullptr;
|
|
float * cpu_src1_float = is_elementwise ? (float *) ctx->cpu_src1_data : nullptr;
|
|
|
|
// Helper to get float value from tensor data (handles f16 and f32)
|
|
auto get_float = [](const void * data, size_t idx, ggml_type type) -> float {
|
|
if (type == GGML_TYPE_F16) {
|
|
const ggml_fp16_t * fp16_data = (const ggml_fp16_t *) data;
|
|
return ggml_fp16_to_fp32(fp16_data[idx]);
|
|
}
|
|
|
|
const float * float_data = (const float *) data;
|
|
return float_data[idx];
|
|
};
|
|
|
|
// Compare all elements but log only the first max_log_elements
|
|
bool matches = true;
|
|
size_t total_mismatches = 0;
|
|
|
|
// First pass: check all elements for mismatches
|
|
for (size_t i = 0; i < num_elements; i++) {
|
|
float cpu_val = get_float(ctx->cpu_dst_data, i, node->type);
|
|
float et_val = get_float(ctx->et_dst_data, i, node->type);
|
|
float diff = fabsf(cpu_val - et_val);
|
|
float rel_diff = diff / (fabsf(cpu_val) + 1e-8f);
|
|
|
|
if (rel_diff > config->tolerance) {
|
|
matches = false;
|
|
total_mismatches++;
|
|
}
|
|
}
|
|
|
|
// Second pass: log detailed info for first max_log elements only
|
|
for (size_t i = 0; i < max_log; i++) {
|
|
float cpu_val = get_float(ctx->cpu_dst_data, i, node->type);
|
|
float et_val = get_float(ctx->et_dst_data, i, node->type);
|
|
float diff = fabsf(cpu_val - et_val);
|
|
|
|
if (is_elementwise && cpu_src0_float && cpu_src1_float) {
|
|
GGML_LOG_DEBUG("ET: [%zu] src0=%.6f, src1=%.6f -> CPU=%.6f, ET=%.6f, diff=%.6f\n", i, cpu_src0_float[i],
|
|
cpu_src1_float[i], cpu_val, et_val, diff);
|
|
} else if (is_elementwise && cpu_src0_float) {
|
|
GGML_LOG_DEBUG("ET: [%zu] src0=%.6f -> CPU=%.6f, ET=%.6f, diff=%.6f\n", i, cpu_src0_float[i], cpu_val,
|
|
et_val, diff);
|
|
} else {
|
|
GGML_LOG_DEBUG("ET: [%zu] CPU=%.6f, ET=%.6f, diff=%.6f\n", i, cpu_val, et_val, diff);
|
|
}
|
|
}
|
|
|
|
// Check some elements from the middle and end for full coverage
|
|
if (num_elements > max_log) {
|
|
size_t mid = num_elements / 2;
|
|
size_t end = num_elements - 1;
|
|
float cpu_mid = get_float(ctx->cpu_dst_data, mid, node->type);
|
|
float et_mid = get_float(ctx->et_dst_data, mid, node->type);
|
|
float cpu_end = get_float(ctx->cpu_dst_data, end, node->type);
|
|
float et_end = get_float(ctx->et_dst_data, end, node->type);
|
|
|
|
GGML_LOG_DEBUG("ET: Middle element [%zu]: CPU=%.6f, ET=%.6f\n", mid, cpu_mid, et_mid);
|
|
GGML_LOG_DEBUG("ET: Last element [%zu]: CPU=%.6f, ET=%.6f\n", end, cpu_end, et_end);
|
|
}
|
|
|
|
GGML_LOG_DEBUG("ET: Results %s (%zu/%zu elements match within tolerance %.6f)\n", matches ? "MATCH" : "DIFFER",
|
|
num_elements - total_mismatches, num_elements, config->tolerance);
|
|
}
|
|
|
|
// Copy CPU result to device if flag is set
|
|
if (config->use_cpu_result) {
|
|
GGML_LOG_DEBUG("ET: Overwriting ET device result with CPU result for correct inference\n");
|
|
size_t dst_logical_size = ggml_nbytes(node);
|
|
ggml_backend_tensor_set(const_cast<ggml_tensor *>(node), ctx->cpu_dst_data, 0, dst_logical_size);
|
|
GGML_LOG_DEBUG("ET: CPU result copied to ET device buffer\n");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ggml_et_cpu_compare_free(ggml_et_cpu_compare_ctx * ctx) {
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
|
|
if (ctx->cpu_src0_data) {
|
|
free(ctx->cpu_src0_data);
|
|
ctx->cpu_src0_data = nullptr;
|
|
}
|
|
if (ctx->cpu_src1_data) {
|
|
free(ctx->cpu_src1_data);
|
|
ctx->cpu_src1_data = nullptr;
|
|
}
|
|
if (ctx->cpu_src2_data) {
|
|
free(ctx->cpu_src2_data);
|
|
ctx->cpu_src2_data = nullptr;
|
|
}
|
|
if (ctx->cpu_dst_data) {
|
|
free(ctx->cpu_dst_data);
|
|
ctx->cpu_dst_data = nullptr;
|
|
}
|
|
if (ctx->et_dst_data) {
|
|
free(ctx->et_dst_data);
|
|
ctx->et_dst_data = nullptr;
|
|
}
|
|
|
|
if (ctx->ggml_ctx) {
|
|
ggml_free(ctx->ggml_ctx);
|
|
ctx->ggml_ctx = nullptr;
|
|
}
|
|
|
|
if (ctx->cpu_backend) {
|
|
ggml_backend_free(ctx->cpu_backend);
|
|
ctx->cpu_backend = nullptr;
|
|
}
|
|
|
|
// Clear pointers
|
|
ctx->cpu_src0 = nullptr;
|
|
ctx->cpu_src1 = nullptr;
|
|
ctx->cpu_src2 = nullptr;
|
|
ctx->cpu_dst = nullptr;
|
|
ctx->cpu_graph = nullptr;
|
|
}
|