Compare commits

...

2 Commits

Author SHA1 Message Date
Xuan-Son Nguyen 3de7dd4c8f cli: add --output option (#25484) 2026-07-09 19:37:39 +02:00
Aparna M P fb30ba9a6c hexagon: tiling, tracing and optimizations for unary ops (#25474)
* hexagon: tile wide rows in pointwise unary ops to avoid VTCM overflow

* unary: reject permuted tensors for now (not used by models)

* hex-unary: replace divs with fastdiv

* hex-unary: add vtcm layout and host computed kernel params

* hex-unary: move fastdiv init into kernel params

* hex-unary: add specialized thread functions to improve generated code

* hex-unary: tracing instrumentation for unary ops

* hex-unary: factor out hvx kernels, streamline and remove more duplication

* ggml-hexagon: fix std::min collision with Windows min macro

* hex-cmake: make lto build happy

---------

Co-authored-by: Max Krasnyansky <maxk@qti.qualcomm.com>
2026-07-09 10:15:47 -07:00
12 changed files with 1147 additions and 689 deletions
+1 -1
View File
@@ -2849,7 +2849,7 @@ common_params_context common_params_parser_init(common_params & params, llama_ex
params.out_file = value;
}
).set_examples({LLAMA_EXAMPLE_IMATRIX, LLAMA_EXAMPLE_CVECTOR_GENERATOR, LLAMA_EXAMPLE_EXPORT_LORA, LLAMA_EXAMPLE_TTS, LLAMA_EXAMPLE_FINETUNE,
LLAMA_EXAMPLE_RESULTS, LLAMA_EXAMPLE_EXPORT_GRAPH_OPS}));
LLAMA_EXAMPLE_RESULTS, LLAMA_EXAMPLE_EXPORT_GRAPH_OPS, LLAMA_EXAMPLE_CLI}));
add_opt(common_arg(
{"-ofreq", "--output-frequency"}, "N",
string_format("output the imatrix every N iterations (default: %d)", params.n_out_freq),
+102 -1
View File
@@ -44,6 +44,7 @@
#include "htp-ops.h"
#include "htp/matmul-ops.h"
#include "htp/flash-attn-ops.h"
#include "htp/unary-ops.h"
#include "htp_iface.h"
#include "htp-drv.h"
@@ -170,8 +171,8 @@ static inline bool ggml_hexagon_is_hmx_weight_type(enum ggml_type type) {
return type == GGML_TYPE_F16 || type == GGML_TYPE_F32 || ggml_hexagon_is_repack_type(type);
}
struct htp_mm_kernel_params;
struct ggml_hexagon_session;
static void ggml_hexagon_precompute_matmul_params(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
@@ -180,6 +181,15 @@ static void ggml_hexagon_precompute_matmul_params(
struct htp_mm_kernel_params * kparams
);
static void ggml_hexagon_precompute_unary_params(
const struct ggml_hexagon_session * sess,
uint32_t op,
const struct ggml_tensor * src0,
const struct ggml_tensor * src1,
const struct ggml_tensor * dst,
struct htp_unary_kernel_params * kparams
);
static void ggml_hexagon_precompute_fused_qkv_params(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0,
@@ -2591,6 +2601,74 @@ finalize:
kparams->div_ne11 = init_fastdiv_values(ne11);
}
static void ggml_hexagon_precompute_unary_params(
const struct ggml_hexagon_session * sess,
uint32_t op,
const struct ggml_tensor * src0,
const struct ggml_tensor * src1,
const struct ggml_tensor * dst,
struct htp_unary_kernel_params * kparams
) {
memset(kparams, 0, sizeof(*kparams));
const uint32_t src0_nrows = src0->ne[1] * src0->ne[2] * src0->ne[3];
const uint32_t n_threads = (std::min)((uint32_t)sess->n_threads, src0_nrows);
kparams->n_threads = n_threads;
const size_t src0_data_row_size = src0->ne[0] * sizeof(float);
const size_t dst_data_row_size = dst->ne[0] * sizeof(float);
const size_t src0_row_size_aligned = hex_round_up(src0_data_row_size, 128);
const size_t dst_row_size_aligned = hex_round_up(dst_data_row_size, 128);
kparams->src0_row_size_aligned = src0_row_size_aligned;
kparams->dst_row_size_aligned = dst_row_size_aligned;
size_t src1_data_row_size = 0;
size_t src1_row_size_aligned = 0;
bool broadcast_weight = false;
if (op == HTP_OP_RMS_NORM_MUL) {
GGML_ASSERT(src1 != nullptr);
src1_data_row_size = src1->ne[0] * sizeof(float);
src1_row_size_aligned = hex_round_up(src1_data_row_size, 128);
broadcast_weight = (src1->ne[1] * src1->ne[2] * src1->ne[3] == 1);
}
kparams->src1_row_size_aligned = src1_row_size_aligned;
kparams->broadcast_weight = broadcast_weight;
struct htp_unary_vtcm_layout L;
uint32_t col_tile = 0;
uint32_t vtcm_row_per_thread = 0;
htp_unary_vtcm_layout_build(&L, op, src0->ne[0], dst->ne[0],
op == HTP_OP_RMS_NORM_MUL ? src1->ne[0] : 0,
broadcast_weight, n_threads, sess->vtcm_size,
&col_tile, &vtcm_row_per_thread);
kparams->col_tile = col_tile;
kparams->vtcm_row_per_thread = vtcm_row_per_thread;
kparams->vtcm_size = L.total_bytes;
kparams->vtcm_src0_size_per_thread = L.src0_bytes;
kparams->vtcm_src1_size_per_thread = L.src1_bytes;
kparams->vtcm_dst_size_per_thread = L.dst_bytes;
kparams->vtcm_src0_size = L.src0_bytes * n_threads;
kparams->vtcm_src1_size = L.src1_bytes * n_threads;
kparams->vtcm_dst_size = L.dst_bytes * n_threads;
kparams->block = col_tile ? 0 : ((L.src0_bytes / 2) / src0_row_size_aligned);
const uint32_t tiles_per_row = col_tile > 0 ? (src0->ne[0] + col_tile - 1) / col_tile : 1;
kparams->div_ne01 = init_fastdiv_values(src0->ne[1]);
kparams->div_ne02 = init_fastdiv_values(src0->ne[2]);
kparams->div_ne012 = init_fastdiv_values(src0->ne[1] * src0->ne[2]);
kparams->div_tpr = init_fastdiv_values(tiles_per_row);
}
static void ggml_hexagon_precompute_fused_qkv_params(
const struct ggml_hexagon_session * sess,
const struct ggml_tensor * src0, // Wk
@@ -2866,6 +2944,9 @@ static bool ggml_hexagon_supported_binary(const struct ggml_hexagon_session * se
return false;
}
if (ggml_is_permuted(src0) || ggml_is_permuted(dst)) {
return false;
}
if (!ggml_are_same_shape(src0, dst)) {
return false;
}
@@ -2912,6 +2993,9 @@ static bool ggml_hexagon_supported_unary(const struct ggml_hexagon_session * ses
if (dst->type != GGML_TYPE_F32) {
return false;
}
if (ggml_is_permuted(src0)) {
return false;
}
if (!ggml_are_same_shape(src0, dst)) {
return false;
}
@@ -3451,6 +3535,15 @@ static bool try_fuse_node(const ggml_hexagon_session * sess, const ggml_cgraph *
if (next_node->op == GGML_OP_MUL && op_is_compute(next_node) && ggml_can_fuse(graph, i, { GGML_OP_RMS_NORM, GGML_OP_MUL })) {
htp_opnode node(n, {}, HTP_OP_RMS_NORM_MUL);
node.add_fused(next_node);
auto inputs = node.get_inputs();
const struct ggml_tensor * src0 = inputs[0];
const struct ggml_tensor * src1 = inputs.size() > 1 ? inputs[1] : nullptr;
ggml_hexagon_precompute_unary_params(sess,
node.opcode, src0, src1, node.dst(),
(struct htp_unary_kernel_params *)node.kernel_params
);
nodes.push_back(std::move(node));
i++; // skip the fused MUL node
return true;
@@ -3555,6 +3648,14 @@ static ggml_status ggml_backend_hexagon_graph_compute(ggml_backend_t backend, gg
node.node,
(struct htp_fa_kernel_params *)node.kernel_params
);
} else if (htp_op_is_unary(node.opcode)) {
auto inputs = node.get_inputs();
const struct ggml_tensor * src0 = inputs[0];
const struct ggml_tensor * src1 = inputs.size() > 1 ? inputs[1] : nullptr;
ggml_hexagon_precompute_unary_params(sess,
node.opcode, src0, src1, node.dst(),
(struct htp_unary_kernel_params *)node.kernel_params
);
}
computed_nodes.push_back(std::move(node));
}
+4
View File
@@ -12,6 +12,7 @@
#include "htp-ops.h"
#include "htp/matmul-ops.h"
#include "htp/flash-attn-ops.h"
#include "htp/unary-ops.h"
struct htp_opnode {
ggml_tensor * node = nullptr;
@@ -362,6 +363,9 @@ struct htp_opformat {
path = "hvx";
}
snprintf(str, max_size, "%s vtcm %d", path, (int) kparams->vtcm_size);
} else if (htp_op_is_unary(node.opcode)) {
const auto * kparams = (const struct htp_unary_kernel_params *) node.kernel_params;
snprintf(str, max_size, "%s vtcm %d", kparams->col_tile ? "wide-row" : "row-block", (int) kparams->vtcm_size);
} else {
snprintf(str, max_size, "----");
}
+1 -1
View File
@@ -39,8 +39,8 @@ add_library(${HTP_LIB} SHARED
diag-ops.c
solve-tri-ops.c
pad-ops.c
flash-attn-ops.c
matmul-ops.c
flash-attn-ops.c
)
target_compile_definitions(${HTP_LIB} PRIVATE
-1
View File
@@ -120,7 +120,6 @@ int op_concat(struct htp_ops_context * octx);
int op_diag(struct htp_ops_context * octx);
int op_solve_tri(struct htp_ops_context * octx);
int op_gated_delta_net(struct htp_ops_context * octx);
int op_tri(struct htp_ops_context * octx);
int op_pad(struct htp_ops_context * octx);
#endif /* HTP_CTX_H */
+257
View File
@@ -0,0 +1,257 @@
#ifndef HVX_NORM_H
#define HVX_NORM_H
#include <stdint.h>
#include "hvx-base.h"
#include "hvx-reduce.h"
#include "hvx-inverse.h"
#include "hvx-sqrt.h"
#include "hvx-repl.h"
static inline void hvx_fast_rms_norm_f32(const uint8_t * restrict src,
uint8_t * restrict dst,
const int num_elems,
float epsilon) {
const HVX_Vector * restrict v_src = (HVX_Vector *) src;
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
const int nvec = num_elems / VLEN_FP32; // number of full vectors
const int nloe = num_elems % VLEN_FP32; // leftover elements
// Compute sum of squares for full vectors
HVX_Vector sum_v = Q6_V_vsplat_R(0x00000000);
HVX_Vector epsilon_v = hvx_vec_splat_f32(epsilon);
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_v, v2);
}
// Handle tail elements using vectorized ops with masking
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_v, v2);
}
// Reduce HVX sum
sum_v = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(sum_v));
HVX_Vector t_v = hvx_vec_splat_f32((float) num_elems);
HVX_Vector denom_v = hvx_vec_inverse_f32(t_v);
HVX_Vector mean_v = Q6_Vqf32_vmpy_VsfVsf(sum_v, denom_v);
HVX_Vector mean_epsilon_v = Q6_Vqf32_vadd_Vqf32Vsf(mean_v, epsilon_v);
// Scale full vectors
HVX_Vector scale_v = hvx_vec_rsqrt_f32(Q6_Vsf_equals_Vqf32(mean_epsilon_v));
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, scale_v);
v_dst[i] = Q6_Vsf_equals_Vqf32(v2);
}
// Handle tail elements using vectorized ops with masking
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, scale_v);
HVX_Vector result = Q6_Vsf_equals_Vqf32(v2);
// Store with masking to avoid overwriting memory beyond the tensor
hvx_vec_store_a(&v_dst[nvec], nloe * 4, result);
}
}
static inline void hvx_fast_rms_norm_mul_f32(const uint8_t * restrict src,
const uint8_t * restrict weight,
uint8_t * restrict dst,
const int num_elems,
float epsilon) {
const HVX_Vector * restrict v_src = (const HVX_Vector *) src;
const HVX_Vector * restrict v_weight = (const HVX_Vector *) weight;
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
const int nvec = num_elems / VLEN_FP32; // number of full vectors
const int nloe = num_elems % VLEN_FP32; // leftover elements
// Compute sum of squares for full vectors
HVX_Vector sum_v = Q6_V_vsplat_R(0x00000000);
HVX_Vector epsilon_v = hvx_vec_splat_f32(epsilon);
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_v, v2);
}
// Handle tail elements using vectorized ops with masking
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_v, v2);
}
// Reduce HVX sum
sum_v = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(sum_v));
HVX_Vector t_v = hvx_vec_splat_f32((float) num_elems);
HVX_Vector denom_v = hvx_vec_inverse_f32(t_v);
HVX_Vector mean_v = Q6_Vqf32_vmpy_VsfVsf(sum_v, denom_v);
HVX_Vector mean_epsilon_v = Q6_Vqf32_vadd_Vqf32Vsf(mean_v, epsilon_v);
// Scale and multiply
HVX_Vector scale_v = hvx_vec_rsqrt_f32(Q6_Vsf_equals_Vqf32(mean_epsilon_v));
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, scale_v);
HVX_Vector v3 = Q6_Vsf_equals_Vqf32(v2);
HVX_Vector result = Q6_Vqf32_vmpy_VsfVsf(v3, v_weight[i]);
v_dst[i] = Q6_Vsf_equals_Vqf32(result);
}
// Handle tail elements using vectorized ops with masking
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, scale_v);
HVX_Vector v3 = Q6_Vsf_equals_Vqf32(v2);
HVX_Vector result = Q6_Vqf32_vmpy_VsfVsf(v3, v_weight[nvec]);
HVX_Vector res_v = Q6_Vsf_equals_Vqf32(result);
// Store with masking to avoid overwriting memory beyond the tensor
hvx_vec_store_a(&v_dst[nvec], nloe * 4, res_v);
}
}
static inline void hvx_fast_norm_f32(const uint8_t * restrict src,
uint8_t * restrict dst,
const int num_elems,
float epsilon) {
const HVX_Vector * restrict v_src = (HVX_Vector *) src;
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
const int nvec = num_elems / VLEN_FP32; // number of full vectors
const int nloe = num_elems % VLEN_FP32; // leftover elements
// Compute sum of squares and sum of values for full vectors
HVX_Vector sum_sq_v = Q6_V_vsplat_R(0x00000000);
HVX_Vector sum_x_v = Q6_V_vsplat_R(0x00000000);
HVX_Vector epsilon_v = hvx_vec_splat_f32(epsilon);
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_sq_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_sq_v, v2);
sum_x_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_x_v, Q6_Vqf32_vadd_VsfVsf(v1, Q6_V_vzero()));
}
// Handle tail elements using vectorized ops with masking
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector v2 = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_sq_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_sq_v, v2);
sum_x_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_x_v, Q6_Vqf32_vadd_VsfVsf(v1, Q6_V_vzero()));
}
// Reduce HVX sums
sum_sq_v = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(sum_sq_v));
sum_x_v = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(sum_x_v));
HVX_Vector t_v = hvx_vec_splat_f32((float) num_elems);
HVX_Vector denom_v = hvx_vec_inverse_f32(t_v);
HVX_Vector mean_sq_v = Q6_Vqf32_vmpy_VsfVsf(sum_sq_v, denom_v);
HVX_Vector mean_x_v = Q6_Vqf32_vmpy_VsfVsf(sum_x_v, denom_v);
HVX_Vector mean_x_sq_v = Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vqf32(mean_x_v), Q6_Vsf_equals_Vqf32(mean_x_v));
HVX_Vector var_v = Q6_Vqf32_vsub_Vqf32Vqf32(mean_sq_v, mean_x_sq_v);
HVX_Vector var_epsilon_v = Q6_Vqf32_vadd_Vqf32Vsf(var_v, epsilon_v);
// scale = rsqrt(variance + epsilon), mean_x broadcast for subtraction
HVX_Vector scale_v = hvx_vec_rsqrt_f32(Q6_Vsf_equals_Vqf32(var_epsilon_v));
HVX_Vector mean_x_b = hvx_vec_repl_f32(Q6_Vsf_equals_Vqf32(mean_x_v));
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector v2 = Q6_Vqf32_vsub_VsfVsf(v1, mean_x_b);
HVX_Vector v3 = Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vqf32(v2), scale_v);
v_dst[i] = Q6_Vsf_equals_Vqf32(v3);
}
// Handle tail elements using vectorized ops with masking
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector v2 = Q6_Vqf32_vsub_VsfVsf(v1, mean_x_b);
HVX_Vector v3 = Q6_Vqf32_vmpy_VsfVsf(Q6_Vsf_equals_Vqf32(v2), scale_v);
HVX_Vector result = Q6_Vsf_equals_Vqf32(v3);
// Store with masking to avoid overwriting memory beyond the tensor
hvx_vec_store_a(&v_dst[nvec], nloe * 4, result);
}
}
static inline void hvx_fast_l2_norm_f32(const uint8_t * restrict src,
uint8_t * restrict dst,
const int num_elems,
float epsilon) {
const HVX_Vector * restrict v_src = (HVX_Vector *) src;
HVX_Vector * restrict v_dst = (HVX_Vector *) dst;
HVX_Vector sum_v = hvx_vec_splat_f32(0.0f);
const int nvec = num_elems / VLEN_FP32;
const int nloe = num_elems % VLEN_FP32;
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
HVX_Vector sq = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_v, sq);
}
// Include tail elements in the sum-of-squares using a predicate mask
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector sq = Q6_Vqf32_vmpy_VsfVsf(v1, v1);
sum_v = Q6_Vqf32_vadd_Vqf32Vqf32(sum_v, sq);
}
// Compute scale = 1/fmax(sqrt(sum), epsilon) entirely in HVX registers.
// hvx_vec_rsqrt_f32 + hvx_vec_inverse_f32 avoids scalar extraction.
HVX_Vector sum_sf = hvx_vec_reduce_sum_f32(Q6_Vsf_equals_Vqf32(sum_v));
HVX_Vector rsqrt_v = hvx_vec_rsqrt_f32(sum_sf); // 1/sqrt(sum)
HVX_Vector sqrt_v = hvx_vec_inverse_f32(rsqrt_v); // sqrt(sum)
HVX_Vector epsilon_v = hvx_vec_splat_f32(epsilon);
HVX_Vector denom_v = Q6_Vsf_vmax_VsfVsf(sqrt_v, epsilon_v); // fmax(sqrt(sum), epsilon)
HVX_Vector scale_v = hvx_vec_inverse_f32(denom_v); // 1/fmax(sqrt(sum), epsilon)
#pragma unroll(4)
for (int i = 0; i < nvec; i++) {
HVX_Vector v1 = v_src[i];
v_dst[i] = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vmpy_VsfVsf(v1, scale_v));
}
if (nloe > 0) {
HVX_VectorPred bmask = Q6_Q_vsetq_R(nloe * 4);
HVX_Vector v1 = Q6_V_vand_QV(bmask, v_src[nvec]);
HVX_Vector result = Q6_Vsf_equals_Vqf32(Q6_Vqf32_vmpy_VsfVsf(v1, scale_v));
hvx_vec_store_a(&v_dst[nvec], nloe * 4, result);
}
}
#endif // HVX_NORM_H
+1
View File
@@ -19,5 +19,6 @@
#include "hvx-base.h"
#include "hvx-pow.h"
#include "hvx-log.h"
#include "hvx-norm.h"
#endif /* HVX_UTILS_H */
+1 -1
View File
@@ -667,7 +667,7 @@ static int execute_op(struct htp_ops_context * octx) {
return op_gated_delta_net(octx);
case HTP_OP_TRI:
return op_tri(octx);
return op_unary(octx);
case HTP_OP_INVALID:
break;
File diff suppressed because it is too large Load Diff
+162
View File
@@ -0,0 +1,162 @@
#ifndef HTP_UNARY_OPS_H
#define HTP_UNARY_OPS_H
#include "hex-common.h"
#include "htp-ops.h"
// Op-specific struct for precomputed unary params
struct htp_unary_kernel_params {
uint32_t n_threads;
uint32_t col_tile;
uint32_t vtcm_row_per_thread;
uint32_t block;
uint32_t broadcast_weight;
uint32_t vtcm_src0_size_per_thread;
uint32_t vtcm_src1_size_per_thread;
uint32_t vtcm_dst_size_per_thread;
uint32_t vtcm_src0_size;
uint32_t vtcm_src1_size;
uint32_t vtcm_dst_size;
uint32_t src0_row_size_aligned;
uint32_t src1_row_size_aligned;
uint32_t dst_row_size_aligned;
uint32_t vtcm_size;
// Fastdiv helpers
struct fastdiv_values div_ne01;
struct fastdiv_values div_ne02;
struct fastdiv_values div_ne012;
struct fastdiv_values div_tpr;
};
#if defined(__cplusplus)
static_assert(sizeof(struct htp_unary_kernel_params) <= 128, "htp_unary_kernel_params is too large for kernel_params blob");
#else
_Static_assert(sizeof(struct htp_unary_kernel_params) <= 128, "htp_unary_kernel_params is too large for kernel_params blob");
#endif
static inline bool htp_op_is_unary(uint32_t opcode) {
switch (opcode) {
case HTP_OP_NORM:
case HTP_OP_RMS_NORM:
case HTP_OP_RMS_NORM_MUL:
case HTP_OP_SCALE:
case HTP_OP_SQR:
case HTP_OP_SQRT:
case HTP_OP_UNARY_NEG:
case HTP_OP_UNARY_EXP:
case HTP_OP_UNARY_SIGMOID:
case HTP_OP_UNARY_SOFTPLUS:
case HTP_OP_UNARY_TANH:
case HTP_OP_L2_NORM:
case HTP_OP_TRI:
return true;
default:
return false;
}
}
struct htp_unary_vtcm_layout {
size_t total_bytes;
size_t off_src0;
size_t off_src1;
size_t off_dst;
size_t src0_bytes;
size_t src1_bytes;
size_t dst_bytes;
};
static inline void htp_unary_vtcm_layout_build(
struct htp_unary_vtcm_layout * L,
uint32_t op,
uint32_t ne00,
uint32_t ne10,
uint32_t ne11,
bool broadcast_weight,
uint32_t n_threads,
size_t vtcm_size,
uint32_t * out_col_tile,
uint32_t * out_vtcm_row_per_thread
) {
const size_t src0_data_row_size = ne00 * sizeof(float);
const size_t dst_data_row_size = ne10 * sizeof(float);
const size_t src0_row_size_aligned = hex_round_up(src0_data_row_size, 128);
const size_t dst_row_size_aligned = hex_round_up(dst_data_row_size, 128);
size_t src1_row_size_aligned = 0;
if (op == HTP_OP_RMS_NORM_MUL) {
const size_t src1_data_row_size = ne11 * sizeof(float);
src1_row_size_aligned = hex_round_up(src1_data_row_size, 128);
}
size_t vtcm_size_per_row = 0;
size_t vtcm_row_per_thread = 0;
if (op == HTP_OP_RMS_NORM_MUL) {
if (broadcast_weight) {
size_t available_vtcm = vtcm_size;
size_t src1_vtcm_total = n_threads * src1_row_size_aligned;
if (available_vtcm > src1_vtcm_total) {
available_vtcm -= src1_vtcm_total;
} else {
available_vtcm = 0;
}
vtcm_size_per_row = 2 * (src0_row_size_aligned + dst_row_size_aligned);
vtcm_row_per_thread = available_vtcm / (n_threads * vtcm_size_per_row);
} else {
vtcm_size_per_row = 2 * (src0_row_size_aligned + dst_row_size_aligned + src1_row_size_aligned);
vtcm_row_per_thread = vtcm_size / (n_threads * vtcm_size_per_row);
}
} else {
vtcm_size_per_row = 2 * (src0_row_size_aligned + dst_row_size_aligned);
vtcm_row_per_thread = vtcm_size / (n_threads * vtcm_size_per_row);
}
const bool is_reduction = (op == HTP_OP_NORM || op == HTP_OP_RMS_NORM ||
op == HTP_OP_RMS_NORM_MUL || op == HTP_OP_L2_NORM);
uint32_t col_tile = 0;
if (vtcm_row_per_thread == 0 && !is_reduction) {
const size_t per_thread_budget = vtcm_size / n_threads;
const size_t col_tile_bytes = hex_align_down(per_thread_budget / 4, 128);
col_tile = (uint32_t) (col_tile_bytes / sizeof(float));
L->src0_bytes = col_tile_bytes * 2;
L->dst_bytes = col_tile_bytes * 2;
L->src1_bytes = 0;
} else {
L->src0_bytes = src0_row_size_aligned * vtcm_row_per_thread * 2;
L->dst_bytes = dst_row_size_aligned * vtcm_row_per_thread * 2;
if (op == HTP_OP_RMS_NORM_MUL) {
if (broadcast_weight) {
L->src1_bytes = src1_row_size_aligned;
} else {
L->src1_bytes = src1_row_size_aligned * vtcm_row_per_thread * 2;
}
} else {
L->src1_bytes = 0;
}
}
L->off_src0 = 0;
if (op == HTP_OP_RMS_NORM_MUL) {
L->off_src1 = L->off_src0 + L->src0_bytes * n_threads;
L->off_dst = L->off_src1 + L->src1_bytes * n_threads;
} else {
L->off_src1 = 0;
L->off_dst = L->off_src0 + L->src0_bytes * n_threads;
}
L->total_bytes = L->off_dst + L->dst_bytes * n_threads;
*out_col_tile = col_tile;
*out_vtcm_row_per_thread = vtcm_row_per_thread;
}
#endif /* HTP_UNARY_OPS_H */
+48 -6
View File
@@ -162,6 +162,14 @@ bool cli_context::init() {
fetch_server_props();
if (!params.out_file.empty()) {
output_file.emplace(params.out_file);
if (!output_file->is_open()) {
ui::show_error(string_format("failed to open output file '%s'", params.out_file.c_str()));
return false;
}
}
return true;
}
@@ -323,7 +331,14 @@ bool cli_context::stage_media_file(const std::string & fname, const std::string
return true;
}
bool cli_context::generate_completion(std::string & assistant_content, cli_timings & timings) {
void cli_context::write_output_file(const std::string & content) {
if (output_file) {
(*output_file) << content;
output_file->flush();
}
}
bool cli_context::generate_completion(generated_content & content_out, cli_timings & timings) {
json body = {
{"messages", impl->messages},
{"stream", true},
@@ -364,13 +379,14 @@ bool cli_context::generate_completion(std::string & assistant_content, cli_timin
if (delta.contains("reasoning_content") && delta.at("reasoning_content").is_string()) {
const std::string text = delta.at("reasoning_content").get<std::string>();
if (!text.empty()) {
content_out.reasoning += text;
a.push(ui::ASSISTANT_DISPLAY_MODE_REASONING, text);
}
}
if (delta.contains("content") && delta.at("content").is_string()) {
const std::string text = delta.at("content").get<std::string>();
if (!text.empty()) {
assistant_content += text;
content_out.content += text;
a.push(ui::ASSISTANT_DISPLAY_MODE_CONTENT, text);
}
}
@@ -520,10 +536,12 @@ int cli_context::run() {
continue;
}
ui::show_message(string_format("Loaded media from '%s'", fname.c_str()));
write_output_file(string_format("User: Added media: %s\n", fname.c_str()));
continue;
} else if (string_starts_with(buffer, "/read ")) {
std::string fname = string_strip(buffer.substr(6));
add_text_file(fname);
write_output_file(string_format("User: Added text file: %s\n", fname.c_str()));
continue;
} else if (string_starts_with(buffer, "/glob ")) {
std::error_code ec;
@@ -568,9 +586,11 @@ int cli_context::run() {
continue;
}
if (!add_text_file((rel_path / rel).string())) {
const std::string full_path = (curdir / rel).string();
if (!add_text_file(full_path)) {
continue;
}
write_output_file(string_format("User: Added text file: %s\n", full_path.c_str()));
if (++count >= FILE_GLOB_MAX_RESULTS) {
ui::show_error(string_format("Maximum number of globbed files allowed (%zu) reached.", FILE_GLOB_MAX_RESULTS));
@@ -586,16 +606,34 @@ int cli_context::run() {
// generate response
if (add_user_msg) {
push_user_message(cur_msg);
write_output_file(string_format("User:\n%s\n\n", cur_msg.c_str()));
cur_msg.clear();
}
cli_timings timings;
std::string assistant_content;
generate_completion(assistant_content, timings);
generated_content content;
generate_completion(content, timings);
impl->messages.push_back({
{"role", "assistant"},
{"content", assistant_content}
{"content", content.content}
});
if (output_file) {
std::string out_content = "Assistant:\n";
if (!content.reasoning.empty()) {
out_content += "[Start thinking]\n\n";
out_content += content.reasoning;
out_content += "[End thinking]\n\n";
}
out_content += content.content;
if (!out_content.empty() && out_content.back() != '\n') {
out_content += "\n";
}
out_content += "\n";
write_output_file(out_content);
}
if (params.show_timings) {
ui::show_info(string_format(
"\n[ Prompt: %.1f t/s | Generation: %.1f t/s ]",
@@ -619,4 +657,8 @@ void cli_context::shutdown() {
server->stop();
server.reset();
}
if (output_file) {
output_file->close();
output_file.reset();
}
}
+11 -1
View File
@@ -9,6 +9,7 @@
#include <memory>
#include <optional>
#include <string>
#include <fstream>
struct cli_timings {
double prompt_per_second = 0.0;
@@ -32,6 +33,8 @@ struct cli_context {
bool has_audio = false;
bool has_video = false;
std::optional<std::ofstream> output_file;
cli_context(const common_params & params);
~cli_context();
@@ -49,7 +52,11 @@ struct cli_context {
static std::atomic<bool> & interrupted();
private:
bool generate_completion(std::string & assistant_content, cli_timings & timings);
struct generated_content {
std::string reasoning;
std::string content;
};
bool generate_completion(generated_content & content_out, cli_timings & timings);
void fetch_server_props();
void add_system_prompt();
void push_user_message(const std::string & text);
@@ -62,5 +69,8 @@ private:
// "image", "audio", "video"; returns false if the file cannot be read
bool stage_media_file(const std::string & fname, const std::string & type);
// no-op if output file is not set
void write_output_file(const std::string & content);
std::unique_ptr<cli_context_impl> impl;
};