diff --git a/common/arg.cpp b/common/arg.cpp index e3058318ac86..8c6479fcac15 100644 --- a/common/arg.cpp +++ b/common/arg.cpp @@ -3600,6 +3600,27 @@ common_params_context common_params_parser_init(common_params & params, llama_ex } } ).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_N_CPU_MOE")); + add_opt(common_arg( + {"--eagle3"}, + "use EAGLE3 speculative decoding with the draft model", + [](common_params & params) { + params.speculative.draft.eagle3 = true; + } + ).set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI})); + add_opt(common_arg( + {"--dflash"}, + "use DFlash speculative decoding with the draft model", + [](common_params & params) { + params.speculative.draft.dflash = true; + } + ).set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI})); + add_opt(common_arg( + {"-cd", "--ctx-size-draft"}, "N", + string_format("size of the prompt context for the draft model (default: %d, 0 = loaded from model)", params.speculative.draft.n_ctx), + [](common_params & params, int value) { + params.speculative.draft.n_ctx = value; + } + ).set_spec().set_examples({LLAMA_EXAMPLE_SPECULATIVE, LLAMA_EXAMPLE_SERVER, LLAMA_EXAMPLE_CLI}).set_env("LLAMA_ARG_SPEC_DRAFT_N_CTX")); add_opt(common_arg( {"--spec-draft-n-max"}, "N", diff --git a/common/common.h b/common/common.h index fea9ef7e7916..897800a0c24e 100644 --- a/common/common.h +++ b/common/common.h @@ -325,6 +325,11 @@ struct common_params_speculative_draft { std::vector devices; // devices to use for offloading std::vector tensor_buft_overrides; + + bool eagle3 = false; // use EAGLE3 speculative decoding + bool dflash = false; // use DFlash speculative decoding + int32_t n_ctx = 0; // draft context size + }; struct common_params_speculative_ngram_mod { diff --git a/common/speculative.cpp b/common/speculative.cpp index 9aabdef76e38..8873aa1d7cf5 100644 --- a/common/speculative.cpp +++ b/common/speculative.cpp @@ -835,6 +835,14 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl { // scratch buffer for concatenated target features [n_tokens, n_embd_enc] std::vector features_buf; + // Stashed encoder outputs for deferred KV cache injection + struct StashedG { + llama_pos pos; + std::vector data; // n_embd_dec floats + }; + std::vector> stashed; // [n_seq][...] + static constexpr int32_t MAX_STASH = 64; + bool m_use_deferred = true; common_speculative_impl_draft_dflash(const common_params_speculative & params, uint32_t n_seq) : common_speculative_impl(COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH, n_seq) @@ -869,7 +877,17 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl { LOG_INF("%s: - n_max=%d, n_min=%d, p_min=%.2f\n", __func__, this->params.n_max, this->params.n_min, this->params.p_min); LOG_INF("%s: - block_size=%d, mask_token_id=%d, n_extract=%u\n", __func__, block_size, mask_token_id, target_layer_ids_n); - // DFlash input is [id_last, * (block_size-1)], so it can draft at most block_size-1 tokens per step + // Detect target model type: Gemma4 models regress with deferred injection (low acceptance rate) + { + char model_desc[128] = {}; + llama_model_desc(model_tgt, model_desc, sizeof(model_desc)); + if (strstr(model_desc, "gemma")) { + m_use_deferred = false; + LOG_INF("%s: - deferred_kv_injection=0 (disabled for %s)\n", __func__, model_desc); + } else { + LOG_INF("%s: - deferred_kv_injection=1 (enabled for %s)\n", __func__, model_desc); + } + } if (this->params.n_max > block_size - 1 || this->params.n_min > block_size - 1) { LOG_WRN("%s: requested draft size (n_max=%d, n_min=%d) exceeds the trained DFlash block size %d -- clamping to %d\n", __func__, this->params.n_max, this->params.n_min, block_size, block_size - 1); @@ -880,6 +898,8 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl { batch = llama_batch_init(llama_n_batch(ctx_dft), 0, n_seq); batch_inject = llama_batch_init(llama_n_batch(ctx_dft), n_embd_dec, n_seq); + stashed.resize(n_seq); + smpls.resize(n_seq); for (auto & s : smpls) { common_params_sampling sparams; @@ -889,13 +909,13 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl { s.reset(common_sampler_init(model_dft, sparams)); } - // turn on extraction of the target layers' input embeddings + // Enable extraction of target model layer outputs for DFlash encoder for (uint32_t k = 0; k < target_layer_ids_n; ++k) { llama_set_embeddings_layer_inp(ctx_tgt, (uint32_t) target_layer_ids[k], true); } + // Enable nextn (encoder) output on the draft context for DFlash decoder K/V injection llama_set_embeddings_nextn(ctx_dft, true, /*masked*/ true); - llama_set_causal_attn(ctx_dft, false); // DFlash needs non-causal attention } ~common_speculative_impl_draft_dflash() override { @@ -996,21 +1016,29 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl { const float * inp_g = llama_get_embeddings_nextn(ctx_dft); GGML_ASSERT(inp_g && "DFlash encoder produced no output."); - // inject the DFlash decoder K/V cache at the tokens' target positions - batch_inject.n_tokens = n_chunk; - std::memcpy(batch_inject.embd, inp_g, (size_t) n_chunk * n_embd_dec * sizeof(float)); - - for (int32_t i = 0; i < n_chunk; ++i) { - batch_inject.pos[i] = batch_in.pos[i_batch_beg[seq_id] + offset + i]; - batch_inject.n_seq_id[i] = 1; - batch_inject.seq_id[i][0] = seq_id; - batch_inject.logits[i] = false; - } - rc = llama_decode(ctx_dft, batch_inject); - if (rc != 0) { - LOG_ERR("%s: llama_decode(ctx_dft) failed rc=%d (n_tokens=%d, offset=%d)\n", - __func__, rc, (int) n_chunk, (int) offset); - return false; + if (m_use_deferred) { + // stash encoder output for deferred KV cache injection + auto & stash = stashed[seq_id]; + for (int32_t i = 0; i < n_chunk; ++i) { + StashedG sg; + sg.pos = batch_in.pos[i_batch_beg[seq_id] + offset + i]; + sg.data.assign(inp_g + (size_t) i * n_embd_dec, inp_g + (size_t) (i + 1) * n_embd_dec); + stash.push_back(std::move(sg)); + } + // auto-flush to prevent unbounded accumulation + if ((int32_t) stash.size() >= MAX_STASH) { + flush_injection(seq_id); + } + } else { + // immediate injection (original per-chunk behavior) + auto & stash = stashed[seq_id]; + for (int32_t i = 0; i < n_chunk; ++i) { + StashedG sg; + sg.pos = batch_in.pos[i_batch_beg[seq_id] + offset + i]; + sg.data.assign(inp_g + (size_t) i * n_embd_dec, inp_g + (size_t) (i + 1) * n_embd_dec); + stash.push_back(std::move(sg)); + } + flush_injection(seq_id); } } } @@ -1018,9 +1046,44 @@ struct common_speculative_impl_draft_dflash : public common_speculative_impl { return true; } + // Batch-inject all stashed encoder outputs into the draft decoder's KV cache + void flush_injection(llama_seq_id seq_id) { + auto & stash = stashed[seq_id]; + if (stash.empty()) return; + + const int32_t n = (int32_t) stash.size(); + batch_inject.n_tokens = n; + for (int32_t i = 0; i < n; ++i) { + std::memcpy(batch_inject.embd + (size_t) i * n_embd_dec, + stash[i].data.data(), (size_t) n_embd_dec * sizeof(float)); + batch_inject.pos[i] = stash[i].pos; + batch_inject.n_seq_id[i] = 1; + batch_inject.seq_id[i][0] = seq_id; + batch_inject.logits[i] = false; + } + + auto * ctx_dft = params.ctx_dft; + int32_t rc = llama_decode(ctx_dft, batch_inject); + if (rc != 0) { + LOG_ERR("%s: flush_injection llama_decode(ctx_dft) failed rc=%d (n_tokens=%d)\n", + __func__, rc, n); + } + + stash.clear(); + } + void draft(common_speculative_draft_params_vec & dparams) override { auto & ctx_dft = params.ctx_dft; + if (m_use_deferred) { + // flush all stashed KV injections before drafting + for (llama_seq_id seq_id = 0; seq_id < n_seq; ++seq_id) { + if (dparams[seq_id].drafting) { + flush_injection(seq_id); + } + } + } + common_batch_clear(batch); // build one batch holding every drafting sequence's noise block into a single decode) @@ -2082,6 +2145,13 @@ common_speculative * common_speculative_init(common_params_speculative & params, bool has_mtp = (enabled_configs & (1u << COMMON_SPECULATIVE_TYPE_DRAFT_MTP)) && params.draft.ctx_dft != nullptr; bool has_draft_dflash = (enabled_configs & (1u << COMMON_SPECULATIVE_TYPE_DRAFT_DFLASH)) && params.draft.ctx_dft != nullptr; + // If --dflash or --eagle3 flags are set, enable the corresponding type + if (!has_draft_dflash && params.draft.dflash && params.draft.ctx_dft != nullptr) { + has_draft_dflash = true; + } + if (!has_draft_eagle3 && params.draft.eagle3 && params.draft.ctx_dft != nullptr) { + has_draft_eagle3 = true; + } bool has_ngram_cache = (enabled_configs & (1u << COMMON_SPECULATIVE_TYPE_NGRAM_CACHE)); diff --git a/examples/speculative-simple/speculative-simple.cpp b/examples/speculative-simple/speculative-simple.cpp index d87ba48beb14..9a3a82137839 100644 --- a/examples/speculative-simple/speculative-simple.cpp +++ b/examples/speculative-simple/speculative-simple.cpp @@ -75,6 +75,8 @@ int main(int argc, char ** argv) { } auto cparams = common_context_params_to_llama(params_dft); + // DFlash/EAGLE3 draft models may reuse the target model's embeddings/output + cparams.ctx_other = ctx_tgt; ctx_dft.reset(llama_init_from_model(model_dft.get(), cparams)); params.speculative.draft.ctx_tgt = ctx_tgt; diff --git a/ggml/src/ggml-cuda/fattn-common.cuh b/ggml/src/ggml-cuda/fattn-common.cuh index c0352e0b1cfe..10e2c5eff609 100644 --- a/ggml/src/ggml-cuda/fattn-common.cuh +++ b/ggml/src/ggml-cuda/fattn-common.cuh @@ -45,46 +45,6 @@ typedef void (* fattn_kernel_t)( typedef float (*vec_dot_KQ_t)( const char * __restrict__ K_c, const void * __restrict__ Q_v, const int * __restrict__ Q_q8 , const void * __restrict__ Q_ds); -struct ggml_cuda_flash_attn_ext_f16_extra_data { - uintptr_t K; - uintptr_t V; - uintptr_t end; -}; - -static inline ggml_cuda_flash_attn_ext_f16_extra_data ggml_cuda_flash_attn_ext_get_f16_extra_data( - const ggml_tensor * dst, const bool need_f16_K, const bool need_f16_V) { - GGML_ASSERT(dst->op == GGML_OP_FLASH_ATTN_EXT); - - const ggml_tensor * K = dst->src[1]; - const ggml_tensor * V = dst->src[2]; - - GGML_ASSERT(K != nullptr); - GGML_ASSERT(V != nullptr); - - const bool V_is_K_view = V->view_src && (V->view_src == K || (V->view_src == K->view_src && V->view_offs == K->view_offs)); - - ggml_cuda_flash_attn_ext_f16_extra_data data = {}; - data.end = (uintptr_t) dst->data + ggml_nbytes(dst); - - if (need_f16_K && K->type != GGML_TYPE_F16) { - data.end = GGML_PAD(data.end, 128); - data.K = data.end; - data.end += ggml_nelements(K)*ggml_type_size(GGML_TYPE_F16); - } - - if (need_f16_V && V->type != GGML_TYPE_F16) { - if (V_is_K_view) { - data.V = data.K; - } else { - data.end = GGML_PAD(data.end, 128); - data.V = data.end; - data.end += ggml_nelements(V)*ggml_type_size(GGML_TYPE_F16); - } - } - - return data; -} - template static __device__ __forceinline__ float vec_dot_fattn_vec_KQ_f16( const char * __restrict__ K_c, const void * __restrict__ Q_v, const int * __restrict__ Q_q8 , const void * __restrict__ Q_ds_v) { @@ -908,30 +868,100 @@ static __device__ __forceinline__ void dequantize_V_turbo4_0(const void * __rest static_assert(ne == 2 || ne == 4 || ne == 8, "bad ne"); - float vals[ne]; -#pragma unroll - for (int l = 0; l < ne; ++l) { - const int j = j0 + l; - const uint8_t qsb = x[ib].qs[j >> 1]; - const uint8_t idx = (qsb >> ((j & 1) * 4)) & 0xF; - vals[l] = TURBO_CENTROIDS_4BIT[idx] * norm; - } + if constexpr (ne == 4) { + // j0 is always a multiple of 4 from the VEC kernel access pattern. + // 4 consecutive elements span 2 qs bytes: j0/2 and j0/2+1. + const uint8_t qs_byte0 = x[ib].qs[j0 / 2]; // elements j0, j0+1 + const uint8_t qs_byte1 = x[ib].qs[j0 / 2 + 1]; // elements j0+2, j0+3 + + const uint8_t idx0 = (qs_byte0 >> 0) & 0xF; + const uint8_t idx1 = (qs_byte0 >> 4) & 0xF; + const uint8_t idx2 = (qs_byte1 >> 0) & 0xF; + const uint8_t idx3 = (qs_byte1 >> 4) & 0xF; #ifdef FP16_AVAILABLE - if constexpr (std::is_same_v) { -#pragma unroll - for (int l = 0; l < ne; l += 2) { - ((half2 *) dst)[l/2] = make_half2(__float2half(vals[l]), __float2half(vals[l+1])); + if constexpr (std::is_same_v) { + ((half2 *) dst)[0] = make_half2( + __float2half(TURBO_CENTROIDS_4BIT[idx0] * norm), + __float2half(TURBO_CENTROIDS_4BIT[idx1] * norm)); + ((half2 *) dst)[1] = make_half2( + __float2half(TURBO_CENTROIDS_4BIT[idx2] * norm), + __float2half(TURBO_CENTROIDS_4BIT[idx3] * norm)); + } else +#endif // FP16_AVAILABLE + if constexpr (std::is_same_v) { + ((float2 *) dst)[0] = make_float2( + TURBO_CENTROIDS_4BIT[idx0] * norm, + TURBO_CENTROIDS_4BIT[idx1] * norm); + ((float2 *) dst)[1] = make_float2( + TURBO_CENTROIDS_4BIT[idx2] * norm, + TURBO_CENTROIDS_4BIT[idx3] * norm); + } else { + static_assert(std::is_same_v, "unsupported type"); } - } else + } else if constexpr (ne == 8) { + // j0 is always a multiple of 8 from the VEC kernel access pattern. + // 8 consecutive elements span 4 qs bytes: j0/2 .. j0/2+3. + const uint8_t qs_byte0 = x[ib].qs[j0 / 2]; + const uint8_t qs_byte1 = x[ib].qs[j0 / 2 + 1]; + const uint8_t qs_byte2 = x[ib].qs[j0 / 2 + 2]; + const uint8_t qs_byte3 = x[ib].qs[j0 / 2 + 3]; + + const uint8_t idx0 = (qs_byte0 >> 0) & 0xF; + const uint8_t idx1 = (qs_byte0 >> 4) & 0xF; + const uint8_t idx2 = (qs_byte1 >> 0) & 0xF; + const uint8_t idx3 = (qs_byte1 >> 4) & 0xF; + const uint8_t idx4 = (qs_byte2 >> 0) & 0xF; + const uint8_t idx5 = (qs_byte2 >> 4) & 0xF; + const uint8_t idx6 = (qs_byte3 >> 0) & 0xF; + const uint8_t idx7 = (qs_byte3 >> 4) & 0xF; + +#ifdef FP16_AVAILABLE + if constexpr (std::is_same_v) { + ((half2 *) dst)[0] = make_half2( + __float2half(TURBO_CENTROIDS_4BIT[idx0] * norm), + __float2half(TURBO_CENTROIDS_4BIT[idx1] * norm)); + ((half2 *) dst)[1] = make_half2( + __float2half(TURBO_CENTROIDS_4BIT[idx2] * norm), + __float2half(TURBO_CENTROIDS_4BIT[idx3] * norm)); + ((half2 *) dst)[2] = make_half2( + __float2half(TURBO_CENTROIDS_4BIT[idx4] * norm), + __float2half(TURBO_CENTROIDS_4BIT[idx5] * norm)); + ((half2 *) dst)[3] = make_half2( + __float2half(TURBO_CENTROIDS_4BIT[idx6] * norm), + __float2half(TURBO_CENTROIDS_4BIT[idx7] * norm)); + } else #endif // FP16_AVAILABLE - if constexpr (std::is_same_v) { -#pragma unroll - for (int l = 0; l < ne; ++l) { - ((float *) dst)[l] = vals[l]; + if constexpr (std::is_same_v) { + ((float2 *) dst)[0] = make_float2( + TURBO_CENTROIDS_4BIT[idx0] * norm, + TURBO_CENTROIDS_4BIT[idx1] * norm); + ((float2 *) dst)[1] = make_float2( + TURBO_CENTROIDS_4BIT[idx2] * norm, + TURBO_CENTROIDS_4BIT[idx3] * norm); + ((float2 *) dst)[2] = make_float2( + TURBO_CENTROIDS_4BIT[idx4] * norm, + TURBO_CENTROIDS_4BIT[idx5] * norm); + ((float2 *) dst)[3] = make_float2( + TURBO_CENTROIDS_4BIT[idx6] * norm, + TURBO_CENTROIDS_4BIT[idx7] * norm); + } else { + static_assert(std::is_same_v, "unsupported type"); + } + } else { // ne == 2 +#ifdef FP16_AVAILABLE + if constexpr (std::is_same_v) { + float v0 = turbo4_dequant_element(&x[ib], j0, norm); + float v1 = turbo4_dequant_element(&x[ib], j0+1, norm); + ((half2 *) dst)[0] = make_half2(__float2half(v0), __float2half(v1)); + } else +#endif // FP16_AVAILABLE + if constexpr (std::is_same_v) { + ((float *) dst)[0] = turbo4_dequant_element(&x[ib], j0, norm); + ((float *) dst)[1] = turbo4_dequant_element(&x[ib], j0+1, norm); + } else { + static_assert(std::is_same_v, "unsupported type"); } - } else { - static_assert(std::is_same_v, "unsupported type"); } } @@ -994,7 +1024,7 @@ constexpr __device__ dequantize_V_t get_dequantize_V() { template __launch_bounds__(FATTN_KQ_STRIDE/2, 1) static __global__ void flash_attn_mask_to_KV_max( - const half2 * __restrict__ mask, int * __restrict__ KV_max, const int ne30, const int64_t s31, const int64_t s33) { + const half2 * __restrict__ mask, int * __restrict__ KV_max, const int ne30, const int s31, const int s33) { const int ne31 = gridDim.x; const int tid = threadIdx.x; const int sequence = blockIdx.y; @@ -1006,7 +1036,6 @@ static __global__ void flash_attn_mask_to_KV_max( if (tid < WARP_SIZE) { buf_iw[tid] = 1; } - ggml_cuda_pdl_sync(); __syncthreads(); int KV_max_sj = (ne30 - 1) * FATTN_KQ_STRIDE; @@ -1048,8 +1077,8 @@ static __global__ void flash_attn_mask_to_KV_max( template // D == head size __launch_bounds__(D, 1) static __global__ void flash_attn_stream_k_fixup_uniform( - float * dst_ptr, - const float2 * dst_fixup_ptr, + float * __restrict__ dst, + const float2 * __restrict__ dst_fixup, const int ne01, const int ne02, const int ne12, const int nblocks_stream_k, const int gqa_ratio, @@ -1058,9 +1087,6 @@ static __global__ void flash_attn_stream_k_fixup_uniform( const uint3 fd_iter_j_z, const uint3 fd_iter_j) { constexpr int ncols = ncols1*ncols2; - ggml_cuda_pdl_lc(); - float * GGML_CUDA_RESTRICT dst = dst_ptr; - const float2 * GGML_CUDA_RESTRICT dst_fixup = dst_fixup_ptr; const int tile_idx = blockIdx.x; // One block per output tile. const int j = blockIdx.y; @@ -1092,7 +1118,6 @@ static __global__ void flash_attn_stream_k_fixup_uniform( dst += sequence*ne02*ne01*D + jt*ne02*(ncols1*D) + zt_Q*D + (j*ne02 + c)*D + tid; - ggml_cuda_pdl_sync(); // Load the partial result that needs a fixup float dst_val = *dst; float max_val; @@ -1132,8 +1157,8 @@ static __global__ void flash_attn_stream_k_fixup_uniform( template // D == head size __launch_bounds__(D, 1) static __global__ void flash_attn_stream_k_fixup_general( - float * dst_ptr, - const float2 * dst_fixup_ptr, + float * __restrict__ dst, + const float2 * __restrict__ dst_fixup, const int ne01, const int ne02, const int gqa_ratio, const int total_work, @@ -1141,8 +1166,6 @@ static __global__ void flash_attn_stream_k_fixup_general( const uint3 fd_iter_k_j_z, const uint3 fd_iter_k_j, const uint3 fd_iter_k) { - float * GGML_CUDA_RESTRICT dst = dst_ptr; - const float2 * GGML_CUDA_RESTRICT dst_fixup = dst_fixup_ptr; constexpr int ncols = ncols1*ncols2; const int bidx0 = blockIdx.x; @@ -1186,7 +1209,6 @@ static __global__ void flash_attn_stream_k_fixup_general( float dst_val = 0.0f; float max_val = 0.0f; float rowsum = 0.0f; - ggml_cuda_pdl_sync(); { dst_val = *dst; @@ -1241,14 +1263,10 @@ static __global__ void flash_attn_stream_k_fixup_general( template // D == head size __launch_bounds__(D, 1) static __global__ void flash_attn_combine_results( - const float * VKQ_parts_ptr, - const float2 * VKQ_meta_ptr, - float * dst_ptr, + const float * __restrict__ VKQ_parts, + const float2 * __restrict__ VKQ_meta, + float * __restrict__ dst, const int parallel_blocks) { - ggml_cuda_pdl_lc(); - const float * GGML_CUDA_RESTRICT VKQ_parts = VKQ_parts_ptr; - const float2 * GGML_CUDA_RESTRICT VKQ_meta = VKQ_meta_ptr; - float * GGML_CUDA_RESTRICT dst = dst_ptr; // Dimension 0: threadIdx.x // Dimension 1: blockIdx.x // Dimension 2: blockIdx.y @@ -1272,7 +1290,6 @@ static __global__ void flash_attn_combine_results( __builtin_assume(tid < D); extern __shared__ float2 meta[]; - ggml_cuda_pdl_sync(); for (int i = tid; i < 2*parallel_blocks; i += D) { ((float *) meta)[i] = ((const float *)VKQ_meta) [i]; } @@ -1329,9 +1346,37 @@ void launch_fattn( const int cc = ggml_cuda_info().devices[id].cc; const int nsm = ggml_cuda_info().devices[id].nsm; - const ggml_cuda_flash_attn_ext_f16_extra_data f16_extra = - ggml_cuda_flash_attn_ext_get_f16_extra_data(KQV, need_f16_K, need_f16_V); - +#ifdef GGML_USE_HIP + // HIP/ROCm: bypass the memory pool for f16 temp buffers. + // The legacy pool (ggml_cuda_pool_leg) retains peak-sized allocations permanently + // because free() stores buffers for reuse rather than releasing them. + // On HIP without VMM support (RDNA 3/4), this means the f16 dequant temp buffers + // for quantized KV stay allocated after use, consuming more VRAM than the KV + // compression saves — causing OOM before f16 at equivalent context lengths. + // Using raw cudaMalloc/cudaFree ensures memory is released after the kernel completes. + // Ref: https://github.com/ggml-org/llama.cpp/issues/22107 + struct hip_f16_alloc { + half * ptr = nullptr; + cudaStream_t stream; + hip_f16_alloc(cudaStream_t s) : stream(s) {} + hip_f16_alloc(const hip_f16_alloc &) = delete; + hip_f16_alloc & operator=(const hip_f16_alloc &) = delete; + ~hip_f16_alloc() { + if (ptr) { + cudaStreamSynchronize(stream); + cudaFree(ptr); + } + } + void alloc(size_t nelements) { + CUDA_CHECK(cudaMalloc(&ptr, nelements * sizeof(half))); + } + }; + hip_f16_alloc K_f16(main_stream); + hip_f16_alloc V_f16(main_stream); +#else + ggml_cuda_pool_alloc K_f16(pool); + ggml_cuda_pool_alloc V_f16(pool); +#endif ggml_cuda_pool_alloc KV_max(pool); ggml_cuda_pool_alloc dst_tmp(pool); ggml_cuda_pool_alloc dst_tmp_meta(pool); @@ -1350,11 +1395,10 @@ void launch_fattn( const size_t bs = ggml_blck_size(K->type); const size_t ts = ggml_type_size(K->type); - GGML_ASSERT(f16_extra.K != 0); - half * K_f16 = (half *) f16_extra.K; + K_f16.alloc(ggml_nelements(K)); if (ggml_is_contiguously_allocated(K)) { to_fp16_cuda_t to_fp16 = ggml_get_to_fp16_cuda(K->type); - to_fp16(K_data, K_f16, ggml_nelements(K), main_stream); + to_fp16(K_data, K_f16.ptr, ggml_nelements(K), main_stream); nb11 = nb11*bs*sizeof(half)/ts; nb12 = nb12*bs*sizeof(half)/ts; @@ -1365,13 +1409,13 @@ void launch_fattn( const int64_t s01 = nb11 / ts; const int64_t s02 = nb12 / ts; const int64_t s03 = nb13 / ts; - to_fp16(K_data, K_f16, K->ne[0], K->ne[1], K->ne[2], K->ne[3], s01, s02, s03, main_stream); + to_fp16(K_data, K_f16.ptr, K->ne[0], K->ne[1], K->ne[2], K->ne[3], s01, s02, s03, main_stream); nb11 = K->ne[0] * sizeof(half); nb12 = K->ne[1] * nb11; nb13 = K->ne[2] * nb12; } - K_data = (char *) K_f16; + K_data = (char *) K_f16.ptr; } if (need_f16_V && V->type != GGML_TYPE_F16) { @@ -1384,12 +1428,11 @@ void launch_fattn( const size_t bs = ggml_blck_size(V->type); const size_t ts = ggml_type_size(V->type); - GGML_ASSERT(f16_extra.V != 0); - half * V_f16 = (half *) f16_extra.V; + V_f16.alloc(ggml_nelements(V)); if (ggml_is_contiguously_allocated(V)) { to_fp16_cuda_t to_fp16 = ggml_get_to_fp16_cuda(V->type); - to_fp16(V_data, V_f16, ggml_nelements(V), main_stream); - V_data = (char *) V_f16; + to_fp16(V_data, V_f16.ptr, ggml_nelements(V), main_stream); + V_data = (char *) V_f16.ptr; nb21 = nb21*bs*sizeof(half)/ts; nb22 = nb22*bs*sizeof(half)/ts; @@ -1400,13 +1443,13 @@ void launch_fattn( const int64_t s01 = nb21 / ts; const int64_t s02 = nb22 / ts; const int64_t s03 = nb23 / ts; - to_fp16(V_data, V_f16, V->ne[0], V->ne[1], V->ne[2], V->ne[3], s01, s02, s03, main_stream); + to_fp16(V_data, V_f16.ptr, V->ne[0], V->ne[1], V->ne[2], V->ne[3], s01, s02, s03, main_stream); nb21 = V->ne[0] * sizeof(half); nb22 = V->ne[1] * nb21; nb23 = V->ne[2] * nb22; } - V_data = (char *) V_f16; + V_data = (char *) V_f16.ptr; } } @@ -1419,8 +1462,8 @@ void launch_fattn( // Only worth the overhead if there is at lease one FATTN_KQ_STRIDE x FATTN_KQ_STRIDE square to be skipped or // multiple sequences of possibly different lengths. if (mask && K->ne[1] % FATTN_KQ_STRIDE == 0 && (Q->ne[1] >= 1024 || Q->ne[3] > 1)) { - const int64_t s31 = mask->nb[1] / sizeof(half2); - const int64_t s33 = mask->nb[3] / sizeof(half2); + const int s31 = mask->nb[1] / sizeof(half2); + const int s33 = mask->nb[3] / sizeof(half2); const dim3 blocks_num_KV_max(ntiles_x, Q->ne[3], 1); const dim3 block_dim_KV_max(FATTN_KQ_STRIDE/2, 1, 1); @@ -1532,9 +1575,7 @@ void launch_fattn( const uint3 ne01 = init_fastdiv_values(Q->ne[1]); GGML_ASSERT(block_dim.x % warp_size == 0); - - ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(blocks_num, block_dim, nbytes_shared, main_stream); - ggml_cuda_kernel_launch(fattn_kernel, launch_params, + fattn_kernel<<>>( (const char *) Q->data, K_data, V_data, @@ -1564,9 +1605,9 @@ void launch_fattn( const dim3 block_dim_combine(DV, 1, 1); const dim3 blocks_num_combine = {(unsigned)ntiles_dst, ncols1, ncols2}; - const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(blocks_num_combine, block_dim_combine, 0, main_stream); - ggml_cuda_kernel_launch(flash_attn_stream_k_fixup_uniform, launch_params, - (float *) KQV->data, dst_tmp_meta.ptr, + flash_attn_stream_k_fixup_uniform + <<>> + ((float *) KQV->data, dst_tmp_meta.ptr, Q->ne[1], Q->ne[2], K->ne[2], nblocks_sk, gqa_ratio, bpt, fd0, fd1, fd2); } else if (ntiles_dst % blocks_num.x != 0) { @@ -1581,9 +1622,9 @@ void launch_fattn( const dim3 block_dim_combine(DV, 1, 1); const dim3 blocks_num_combine = {blocks_num.x, ncols1, ncols2}; - const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(blocks_num_combine, block_dim_combine, 0, main_stream); - ggml_cuda_kernel_launch(flash_attn_stream_k_fixup_general, launch_params, - (float *) KQV->data, dst_tmp_meta.ptr, + flash_attn_stream_k_fixup_general + <<>> + ((float *) KQV->data, dst_tmp_meta.ptr, Q->ne[1], Q->ne[2], gqa_ratio, total_work, fd_k_j_z_ne12, fd_k_j_z, fd_k_j, fd_k); } @@ -1592,9 +1633,9 @@ void launch_fattn( const dim3 blocks_num_combine(Q->ne[1], Q->ne[2], Q->ne[3]); const size_t nbytes_shared_combine = parallel_blocks*sizeof(float2); - const ggml_cuda_kernel_launch_params launch_params = ggml_cuda_kernel_launch_params(blocks_num_combine, block_dim_combine, nbytes_shared_combine, main_stream); - ggml_cuda_kernel_launch(flash_attn_combine_results, launch_params, - dst_tmp.ptr, dst_tmp_meta.ptr, (float *) KQV->data, parallel_blocks); + flash_attn_combine_results + <<>> + (dst_tmp.ptr, dst_tmp_meta.ptr, (float *) KQV->data, parallel_blocks); } CUDA_CHECK(cudaGetLastError()); } diff --git a/ggml/src/ggml-cuda/fattn.cu b/ggml/src/ggml-cuda/fattn.cu index d271734b2dcd..91b19becf053 100644 --- a/ggml/src/ggml-cuda/fattn.cu +++ b/ggml/src/ggml-cuda/fattn.cu @@ -753,6 +753,36 @@ static best_fattn_kernel ggml_cuda_get_best_fattn_kernel(const int device, const return BEST_FATTN_KERNEL_TILE; } +struct ggml_cuda_flash_attn_ext_f16_extra_data { + half * K_f16; // f16 copy of K inside dst buffer, or nullptr + half * V_f16; // f16 copy of V inside dst buffer, or nullptr + uintptr_t end; // one past the last byte of the dst allocation +}; + +// Compute intra-buffer pointers for f16 K/V copies needed by TILE/WMMA/MMA kernels. +// The output tensor's buffer is sized to hold dst output + optional f16 K + optional f16 V +// in a single contiguous region. Runtime code may use these pointers or pool allocations; +// the extra reserved space ensures the buffer is large enough in either case. +static ggml_cuda_flash_attn_ext_f16_extra_data +ggml_cuda_flash_attn_ext_get_f16_extra_data(const ggml_tensor * dst, bool need_f16_K, bool need_f16_V) { + uintptr_t base = (uintptr_t) dst->data; + uintptr_t pos = base + ggml_nbytes(dst); + + half * K_f16 = nullptr; + half * V_f16 = nullptr; + + if (need_f16_K) { + K_f16 = (half *) pos; + pos += (size_t) ggml_nelements(dst->src[1]) * sizeof(half); + } + if (need_f16_V) { + V_f16 = (half *) pos; + pos += (size_t) ggml_nelements(dst->src[2]) * sizeof(half); + } + + return {K_f16, V_f16, pos}; +} + size_t ggml_cuda_flash_attn_ext_get_alloc_size(int device, const ggml_tensor * dst) { GGML_ASSERT(dst->op == GGML_OP_FLASH_ATTN_EXT); diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp index d80915ffdba5..4fd03921bbd6 100644 --- a/src/llama-arch.cpp +++ b/src/llama-arch.cpp @@ -355,6 +355,15 @@ static const std::map LLM_KV_NAMES = { { LLM_KV_TOKENIZER_PREFIX_ID, "tokenizer.ggml.prefix_token_id" }, { LLM_KV_TOKENIZER_SUFFIX_ID, "tokenizer.ggml.suffix_token_id" }, { LLM_KV_TOKENIZER_MIDDLE_ID, "tokenizer.ggml.middle_token_id" }, + + { LLM_KV_EAGLE3_EXTRACT_LAYERS, "%s.extract_layers" }, + { LLM_KV_EAGLE3_TARGET_HIDDEN_SIZE, "%s.target_hidden_size" }, + { LLM_KV_EAGLE3_NORM_BEFORE_RESIDUAL,"%s.norm_before_residual" }, + + { LLM_KV_DFLASH_TARGET_LAYER_IDS, "%s.target_layers" }, + { LLM_KV_DFLASH_BLOCK_SIZE, "%s.block_size" }, + { LLM_KV_DFLASH_MASK_TOKEN_ID, "%s.mask_token_id" }, + }; static const std::map LLM_TENSOR_NAMES = { @@ -570,6 +579,16 @@ static const std::map LLM_TENSOR_NAMES = { { LLM_TENSOR_MASKED_EMBD_ORDERING, "masked_embd_ordering" }, { LLM_TENSOR_FC, "fc" }, { LLM_TENSOR_D2T, "d2t" }, + + // EAGLE3 draft model + { LLM_TENSOR_EAGLE3_HIDDEN_NORM, "blk.%d.eagle3_hidden_norm" }, + { LLM_TENSOR_EAGLE3_FC, "eagle3_fc" }, + { LLM_TENSOR_EAGLE3_D2T, "d2t" }, + + // DFlash draft model + { LLM_TENSOR_DFLASH_FC, "fc" }, + { LLM_TENSOR_DFLASH_HIDDEN_NORM, "enc.output_norm" }, + }; // declare information about the model weight tensors: @@ -799,6 +818,16 @@ static const std::map LLM_TENSOR_INFOS = { // eagle3 {LLM_TENSOR_FC, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}}, {LLM_TENSOR_D2T, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}}, + + // EAGLE3 + {LLM_TENSOR_EAGLE3_HIDDEN_NORM, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}}, + {LLM_TENSOR_EAGLE3_FC, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}}, + {LLM_TENSOR_EAGLE3_D2T, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_GET_ROWS}}, + + // DFlash + {LLM_TENSOR_DFLASH_HIDDEN_NORM, {LLM_TENSOR_LAYER_INPUT, GGML_OP_NONE}}, + {LLM_TENSOR_DFLASH_FC, {LLM_TENSOR_LAYER_OUTPUT, GGML_OP_MUL_MAT}}, + }; LLM_KV::LLM_KV(llm_arch arch, const char * suffix) : arch(arch), suffix(suffix) {} diff --git a/src/llama-arch.h b/src/llama-arch.h index 946518d5f224..770b5f0c3a67 100644 --- a/src/llama-arch.h +++ b/src/llama-arch.h @@ -361,6 +361,17 @@ enum llm_kv { LLM_KV_DENSE_2_FEAT_OUT, LLM_KV_DENSE_3_FEAT_IN, LLM_KV_DENSE_3_FEAT_OUT, + + // EAGLE3 + LLM_KV_EAGLE3_EXTRACT_LAYERS, + LLM_KV_EAGLE3_TARGET_HIDDEN_SIZE, + LLM_KV_EAGLE3_NORM_BEFORE_RESIDUAL, + + // DFlash + LLM_KV_DFLASH_TARGET_LAYER_IDS, + LLM_KV_DFLASH_BLOCK_SIZE, + LLM_KV_DFLASH_MASK_TOKEN_ID, + }; enum llm_tensor { @@ -565,6 +576,16 @@ enum llm_tensor { LLM_TENSOR_INDEXER_K_NORM, LLM_TENSOR_INDEXER_PROJ, LLM_TENSOR_INDEXER_ATTN_K, + + // EAGLE3 + LLM_TENSOR_EAGLE3_HIDDEN_NORM, + LLM_TENSOR_EAGLE3_FC, + LLM_TENSOR_EAGLE3_D2T, + + // DFlash + LLM_TENSOR_DFLASH_FC, + LLM_TENSOR_DFLASH_HIDDEN_NORM, + LLM_TENSOR_INDEXER_ATTN_Q_B, LLM_TENSOR_NEXTN_PROJ_PRE, LLM_TENSOR_NEXTN_PROJ_POST, diff --git a/src/llama-context.cpp b/src/llama-context.cpp index 09fdf513c9f5..2131eba5831c 100644 --- a/src/llama-context.cpp +++ b/src/llama-context.cpp @@ -107,6 +107,7 @@ llama_context::llama_context( throw std::runtime_error(model.arch_name() + " requires ctx_other to be set (this warning is normal during memory fitting)"); } cparams.ctx_other = params.ctx_other; + } } @@ -2212,9 +2213,7 @@ uint32_t llama_context::output_reserve(int32_t n_outputs) { std::fill(output_ids.begin(), output_ids.end(), -1); this->n_outputs = 0; - GGML_ASSERT(n_outputs_max <= cparams.n_outputs_max); - return n_outputs_max; } diff --git a/src/llama-hparams.h b/src/llama-hparams.h index 39ffde7ff694..94555595cc18 100644 --- a/src/llama-hparams.h +++ b/src/llama-hparams.h @@ -396,6 +396,19 @@ struct llama_hparams { bool use_mrope() const; + + // EAGLE3 draft model + std::array eagle3_extract_layers = {0, 0, 0}; + uint32_t eagle3_target_hidden_size = 0; + bool eagle3_norm_before_residual = false; + + // DFlash draft model + uint32_t dflash_block_size = 16; + uint32_t dflash_mask_token_id = 0; + + + + }; static_assert(std::is_trivially_copyable::value, "llama_hparams must be trivially copyable"); diff --git a/src/llama-model-loader.cpp b/src/llama-model-loader.cpp index 6d9a669ccf92..1a61d44da805 100644 --- a/src/llama-model-loader.cpp +++ b/src/llama-model-loader.cpp @@ -456,7 +456,7 @@ namespace GGUFMeta { struct GGUFMeta::ArrayInfo arr_info = GGUFMeta::GKV::get_kv(metadata, kid); - if (n != arr_info.length) { + if (n != 0 && n != arr_info.length) { throw std::runtime_error(format("key %s has wrong array length; expected %u, got %u", key.c_str(), n, (uint32_t) arr_info.length)); } @@ -509,6 +509,9 @@ namespace GGUFMeta { template bool llama_model_loader::get_key_or_arr> (enum llm_kv kid, std::array & result, uint32_t n, bool required); template bool llama_model_loader::get_key_or_arr>(enum llm_kv kid, std::array & result, uint32_t n, bool required); template bool llama_model_loader::get_key_or_arr>(enum llm_kv kid, std::array & result, uint32_t n, bool required); + template bool llama_model_loader::get_key_or_arr> (enum llm_kv kid, std::array & result, uint32_t n, bool required); + template bool llama_model_loader::get_key_or_arr> (enum llm_kv kid, std::array & result, uint32_t n, bool required); + template bool llama_model_loader::get_key_or_arr> (enum llm_kv kid, std::array & result, uint32_t n, bool required); llama_model_loader::llama_model_loader( diff --git a/src/llama-model.h b/src/llama-model.h index f4718f6d5842..483b88832e56 100644 --- a/src/llama-model.h +++ b/src/llama-model.h @@ -258,6 +258,12 @@ struct llama_layer { struct ggml_tensor * wv_b = nullptr; struct ggml_tensor * wqkv_b = nullptr; struct ggml_tensor * wo_b = nullptr; + // DFlash: separate Q/K/V/O bias tensors + struct ggml_tensor * bq = nullptr; + struct ggml_tensor * bk = nullptr; + struct ggml_tensor * bv = nullptr; + struct ggml_tensor * bo = nullptr; + struct ggml_tensor * wq_cross = nullptr; struct ggml_tensor * wk_cross = nullptr; struct ggml_tensor * wv_cross = nullptr; @@ -496,6 +502,9 @@ struct llama_layer { // gemma4 layer output scale, reused for talkie embedding skip scale struct ggml_tensor * out_scale = nullptr; + // EAGLE3 hidden norm (per-layer) + struct ggml_tensor * eagle3_hidden_norm = nullptr; + struct llama_layer_posnet posnet; @@ -575,6 +584,14 @@ struct llama_model { // unified vector to store target-model extracted layer ids in eagle3, dflash, etc. std::vector target_layer_ids; + // dflash + struct ggml_tensor * dflash_hidden_norm = nullptr; + struct ggml_tensor * target_output = nullptr; // reference to target model's lm_head + + // Reference to target model's embedding layer + // This allows EAGLE3 to use target model's embeddings without copying + struct ggml_tensor * target_tok_embd = nullptr; + std::vector layers; diff --git a/src/models/dflash.cpp b/src/models/dflash.cpp index a7b4f4435a88..c2459f726f3c 100644 --- a/src/models/dflash.cpp +++ b/src/models/dflash.cpp @@ -4,30 +4,22 @@ #include "llama-kv-cache-iswa.h" void llama_model_dflash::load_arch_hparams(llama_model_loader & ml) { - ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); - if (!ml.get_arr(LLM_KV_TARGET_LAYERS, target_layer_ids, false)) { - throw std::runtime_error("DFlash model requires 'target_layers' in GGUF metadata"); + // Load target layer IDs (accept both #219 enum name and tip/upstream TARGET_LAYERS; + // both map to the same GGUF key pattern "%s.target_layers"). + if (!ml.get_arr(LLM_KV_DFLASH_TARGET_LAYER_IDS, target_layer_ids, false) && + !ml.get_arr(LLM_KV_TARGET_LAYERS, target_layer_ids, false)) { + throw std::runtime_error("DFlash model requires 'target_layers' / target_layer_ids in GGUF metadata"); } - - hparams.n_embd_inp_enc_impl = (uint32_t) target_layer_ids.size() * hparams.n_embd; + hparams.n_embd_inp_enc_impl = (uint32_t)target_layer_ids.size() * hparams.n_embd; LLAMA_LOG_INFO("%s: DFlash extract_layers = [", __func__); for (size_t i = 0; i < target_layer_ids.size(); ++i) { - LLAMA_LOG_INFO("%d%s", target_layer_ids[i], i + 1 < target_layer_ids.size() ? ", " : ""); + LLAMA_LOG_INFO("%s%d", i > 0 ? ", " : "", target_layer_ids[i]); } LLAMA_LOG_INFO("]\n"); - // optional interleaved sliding-window attention with per-layer pattern array. - // DFlash has a single rope, so the SWA rope == main rope. - if (ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa, false) && hparams.n_swa > 0) { - hparams.swa_type = LLAMA_SWA_TYPE_STANDARD; - ml.get_key_or_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, hparams.is_swa_impl, hparams.n_layer()); - hparams.rope_freq_base_train_swa = hparams.rope_freq_base_train; - hparams.rope_freq_scale_train_swa = hparams.rope_freq_scale_train; - } - type = LLM_TYPE_UNKNOWN; } @@ -37,8 +29,8 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) { const int64_t n_embd_inp = hparams.n_embd_inp_enc(); fc = create_tensor(tn(LLM_TENSOR_FC, "weight"), { n_embd_inp, n_embd }, 0); - output_norm_enc = create_tensor(tn(LLM_TENSOR_ENC_OUTPUT_NORM, "weight"), { n_embd }, 0); // encoder hidden_norm (after fc) - output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), { n_embd }, 0); // decoder final norm + output_norm_enc = create_tensor(tn(LLM_TENSOR_ENC_OUTPUT_NORM, "weight"), { n_embd }, 0); + output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), { n_embd }, 0); for (int i = 0; i < n_layer; ++i) { auto & layer = layers[i]; @@ -54,29 +46,19 @@ void llama_model_dflash::load_arch_tensors(llama_model_loader &) { layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), { n_embd_head_k }, 0); layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), { n_embd }, 0); - layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), { n_embd, n_ff }, 0); - layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd }, 0); - layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), { n_embd, n_ff }, 0); + layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), { n_embd, n_ff }, 0); + layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd }, 0); + layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), { n_embd, n_ff }, 0); } } -std::unique_ptr llama_model_dflash::build_arch_graph(const llm_graph_params & params) const { - switch (params.gtype) { - case LLM_GRAPH_TYPE_ENCODER: - return std::make_unique>(*this, params); - case LLM_GRAPH_TYPE_DEFAULT: - case LLM_GRAPH_TYPE_DECODER: - return std::make_unique>(*this, params); - default: - GGML_ABORT("invalid graph type"); - }; -} - -template <> -ggml_tensor * llama_model_dflash::graph::build_inp_embd_enc() const { - auto inp_target = std::make_unique(hparams.n_embd_inp_enc()); +// DFlash Encoder: processes target model features through feature fusion layer +template +ggml_tensor * llama_model_dflash::graph::build_inp_embd_enc() const { + const int64_t n_embd_inp = hparams.n_embd_inp_enc(); - inp_target->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, hparams.n_embd_inp_enc(), n_tokens); + auto inp_target = std::make_unique(n_embd_inp); + inp_target->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd_inp, n_tokens); ggml_set_input(inp_target->embd); ggml_tensor * cur = inp_target->embd; @@ -87,7 +69,6 @@ ggml_tensor * llama_model_dflash::graph::build_inp_embd_enc() const { return cur; } -// DFlash Encoder: processes target model features through feature fusion layer template <> llama_model_dflash::graph::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) { ggml_tensor * cur = build_inp_embd_enc(); @@ -128,7 +109,7 @@ llama_model_dflash::graph::graph(const llama_model & model, const llm_gra const float kq_scale = 1.0f/sqrtf(float(n_embd_head)); - // KV cache injection + // KV cache injection (embd batch) if (ubatch.embd) { auto inp = std::make_unique(n_embd); @@ -159,7 +140,6 @@ llama_model_dflash::graph::graph(const llama_model & model, const llm_gra cb(Vcur, "Vcur_injected", il); if (use_iswa) { - // route each layer's K/V to its sub-cache: SWA layers -> sliding cache, full -> dense const bool is_swa = hparams.is_swa(il); const auto * kv = is_swa ? inp_attn_iswa->mctx->get_swa() : inp_attn_iswa->mctx->get_base(); ggml_tensor * k_idxs = is_swa ? inp_attn_iswa->get_k_idxs_swa() : inp_attn_iswa->get_k_idxs(); @@ -178,12 +158,12 @@ llama_model_dflash::graph::graph(const llama_model & model, const llm_gra return; } + // Token batch: noise-block diffusion // tok_embd from the target model (shared via ctx_other) auto * tok_embd = model.tok_embd; if (tok_embd == nullptr) { GGML_ASSERT(cparams.ctx_other != nullptr); const auto * model_other = llama_get_model(cparams.ctx_other); - GGML_ASSERT(model_other->tok_embd != nullptr && "DFlash decoder requires the target model's token embeddings"); tok_embd = model_other->tok_embd; } @@ -274,3 +254,14 @@ llama_model_dflash::graph::graph(const llama_model & model, const llm_gra ggml_build_forward_expand(gf, cur); } + +std::unique_ptr llama_model_dflash::build_arch_graph(const llm_graph_params & params) const { + if (params.gtype == LLM_GRAPH_TYPE_ENCODER) { + return std::make_unique>(*this, params); + } else { + return std::make_unique>(*this, params); + } +} + +template struct llama_model_dflash::graph; +template struct llama_model_dflash::graph; diff --git a/src/models/eagle3.cpp b/src/models/eagle3.cpp index 3321b390515d..e7a64a7c00fb 100644 --- a/src/models/eagle3.cpp +++ b/src/models/eagle3.cpp @@ -3,29 +3,15 @@ void llama_model_eagle3::load_arch_hparams(llama_model_loader & ml) { ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps); - if (!ml.get_arr(LLM_KV_TARGET_LAYERS, target_layer_ids, false)) { - throw std::runtime_error("EAGLE3 model requires 'extract_layers' in GGUF metadata"); - } - if (target_layer_ids.size() != 3) { - throw std::runtime_error("EAGLE3 requires exactly 3 entries in 'extract_layers'"); - } - LLAMA_LOG_INFO("%s: EAGLE3 extract_layers = [%d, %d, %d]\n", __func__, - target_layer_ids[0], - target_layer_ids[1], - target_layer_ids[2]); - - uint32_t n_embd_tgt = 0; - - ml.get_key(LLM_KV_TARGET_HIDDEN_SIZE, n_embd_tgt); - LLAMA_LOG_INFO("%s: EAGLE3 n_embd_tgt = %u (draft n_embd = %u)\n", __func__, n_embd_tgt, hparams.n_embd); + ml.get_key_or_arr(LLM_KV_EAGLE3_EXTRACT_LAYERS, hparams.eagle3_extract_layers, 3, true); - hparams.n_embd_inp_impl = (uint32_t) target_layer_ids.size() * n_embd_tgt; + ml.get_key(LLM_KV_EAGLE3_TARGET_HIDDEN_SIZE, hparams.eagle3_target_hidden_size); + LLAMA_LOG_INFO("%s: EAGLE3 target_hidden_size = %u (draft n_embd = %u)\n", __func__, + hparams.eagle3_target_hidden_size, hparams.n_embd); - // eagle3 norm_before_residual (optional, default false) - // compatible with Readhat eagle3 speculator model - ml.get_key(LLM_KV_NORM_BEFORE_RESIDUAL, hparams.norm_before_residual, false); - if (hparams.norm_before_residual) { - LLAMA_LOG_INFO("%s: EAGLE3gnorm_before_residual = true\n", __func__); + ml.get_key(LLM_KV_EAGLE3_NORM_BEFORE_RESIDUAL, hparams.eagle3_norm_before_residual, false); + if (hparams.eagle3_norm_before_residual) { + LLAMA_LOG_INFO("%s: EAGLE3 norm_before_residual = true\n", __func__); } type = LLM_TYPE_UNKNOWN; @@ -34,83 +20,52 @@ void llama_model_eagle3::load_arch_hparams(llama_model_loader & ml) { void llama_model_eagle3::load_arch_tensors(llama_model_loader &) { LLAMA_LOAD_LOCALS; - const int64_t n_embd_inp = hparams.n_embd_inp(); + const int64_t n_embd_target_features = 3 * hparams.eagle3_target_hidden_size; const int64_t n_embd_attn_input = 2 * n_embd; - // Get vocab size from the d2t tensor in the GGUF file (optional - only needed if eagle3 has different vocab_size than target) - // d2t: draft to target vocabulary mapping - int64_t n_draft_vocab = n_vocab; // Default: same as target vocab - const struct ggml_tensor * d2t_meta = ml->get_tensor_meta("d2t"); - if (d2t_meta) { - n_draft_vocab = d2t_meta->ne[0]; // update draft vocab size - d2t = create_tensor(tn(LLM_TENSOR_D2T), {n_draft_vocab}, 0); - LLAMA_LOG_INFO("%s: EAGLE3 using d2t mapping (draft_vocab_size = %lld)\n", __func__, (long long)n_draft_vocab); - } else { - d2t = nullptr; // no d2t, use default vocab size - LLAMA_LOG_INFO("%s: EAGLE3 without d2t - sharing same vocab_size with target (vocab_size = %lld)\n", __func__, (long long)n_draft_vocab); - } + // Feature fusion layer + fc = create_tensor(tn(LLM_TENSOR_EAGLE3_FC, "weight"), {n_embd_target_features, n_embd}, 0); - // Feature fusion layer: projects 3 target layers to draft hidden size - fc = create_tensor(tn(LLM_TENSOR_FC, "weight"), {n_embd_inp, n_embd}, 0); + d2t = create_tensor(tn(LLM_TENSOR_EAGLE3_D2T, "weight"), {n_embd, n_vocab}, 0); - // Output layer (uses draft vocab size) output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0); - output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_draft_vocab}, TENSOR_NOT_REQUIRED); - - // Token embeddings (optional - Llama 3.3 70B EAGLE3 has its own) - const struct ggml_tensor * tok_embd_meta = ml->get_tensor_meta(tn(LLM_TENSOR_TOKEN_EMBD, "weight").str().c_str()); - if (tok_embd_meta) { - const int64_t n_target_vocab = tok_embd_meta->ne[1]; - tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_target_vocab}, 0); - LLAMA_LOG_INFO("%s: EAGLE3 using its own token_embd (vocab = %lld)\n", __func__, (long long)n_target_vocab); - } - // Single decoder layer for (int i = 0; i < n_layer; ++i) { auto & layer = layers[i]; - // input_layernorm: applied to token embeddings layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0); - // eagle3 specific: hidden_norm applied to fused target features - layer.attn_norm_2 = create_tensor(tn(LLM_TENSOR_ATTN_NORM_2, "weight", i), {n_embd}, 0); - - // Attention takes input_embeds_normed + fused_target_normed as input - layer.wq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "weight", i), {n_embd_attn_input, n_embd_head_k * n_head}, 0); - layer.wk = create_tensor(tn(LLM_TENSOR_ATTN_K, "weight", i), {n_embd_attn_input, n_embd_k_gqa}, 0); - layer.wv = create_tensor(tn(LLM_TENSOR_ATTN_V, "weight", i), {n_embd_attn_input, n_embd_v_gqa}, 0); + layer.wq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "weight", i), {n_embd_attn_input, n_embd_head_k * n_head}, 0); + layer.wk = create_tensor(tn(LLM_TENSOR_ATTN_K, "weight", i), {n_embd_attn_input, n_embd_k_gqa}, 0); + layer.wv = create_tensor(tn(LLM_TENSOR_ATTN_V, "weight", i), {n_embd_attn_input, n_embd_v_gqa}, 0); layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, n_embd}, 0); + layer.eagle3_hidden_norm = create_tensor(tn(LLM_TENSOR_EAGLE3_HIDDEN_NORM, "weight", i), {n_embd}, 0); + layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0); layer.ffn_gate = create_tensor(tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, 0); layer.ffn_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}, 0); layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, 0); - - // rope_freqs for llama3 rope scaling (optional - only if eagle3 config has rope_scaling) - layer.rope_freqs = create_tensor(tn(LLM_TENSOR_ROPE_FREQS, "weight", i), {n_rot/2}, TENSOR_NOT_REQUIRED); } } std::unique_ptr llama_model_eagle3::build_arch_graph(const llm_graph_params & params) const { - switch (params.gtype) { - case LLM_GRAPH_TYPE_ENCODER: - return std::make_unique>(*this, params); - case LLM_GRAPH_TYPE_DEFAULT: - case LLM_GRAPH_TYPE_DECODER: - return std::make_unique>(*this, params); - default: - GGML_ABORT("invalid graph type"); - }; + if (params.gtype == LLM_GRAPH_TYPE_ENCODER) { + return std::make_unique(*this, params); + } else { + return std::make_unique(*this, params); + } } -template <> -ggml_tensor * llama_model_eagle3::graph::build_inp_embd_enc() const { + +ggml_tensor * llm_build_eagle3_encode::build_inp_embd() const { + const int64_t n_embd_target_features = 3 * hparams.eagle3_target_hidden_size; ggml_tensor * cur = nullptr; // Input: Target model features (3 layers concatenated: low, mid, high) // Data will be provided via ubatch->embd in encode_eagle3_features() - auto inp_target = std::make_unique(hparams.n_embd_inp()); - inp_target->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32,hparams.n_embd_inp(), n_tokens); + auto inp_target = std::make_unique(n_embd_target_features); + inp_target->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd_target_features, n_tokens); ggml_set_input(inp_target->embd); cur = inp_target->embd; @@ -121,67 +76,49 @@ ggml_tensor * llama_model_eagle3::graph::build_inp_embd_enc() const { return cur; } -// eagle3 Encoder: processes target model features through feature fusion layer +// EAGLE3 Encoder: processes target model features through feature fusion layer // Input: target_features e.g. [12288, n_tokens] from target model layers low, middle, high // Output: g_embeddings e.g. [4096, n_tokens] stored in context -template <> -llama_model_eagle3::graph::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) { +llm_build_eagle3_encode::llm_build_eagle3_encode(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) { ggml_tensor * cur = nullptr; - cur = build_inp_embd_enc(); + cur = build_inp_embd(); // Feature fusion layer cur = build_lora_mm(model.fc, cur); cb(cur, "fc_out", -1); // Output: g_embeddings e.g. [4096, n_tokens] - // store in t_h_nextn (same as MTP) so can be read via llama_get_embeddings_nextn(ctx_dft) - ggml_set_output(cur); - res->t_h_nextn = cur; + res->t_embd = cur; ggml_build_forward_expand(gf, cur); } -// eagle3 Decoder: processes draft tokens using g_embeddings from encoder +// EAGLE3 Decoder: processes draft tokens using g_embeddings from encoder // Input: draft tokens + g_embeddings from encoder // Output: draft logits -template <> -llama_model_eagle3::graph::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) { +llm_build_eagle3_decode::llm_build_eagle3_decode(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) { const int64_t n_embd_head = hparams.n_embd_head_v(); GGML_ASSERT(n_embd_head == hparams.n_embd_head_k()); - GGML_ASSERT(n_layer == 1); // eagle3 has only one decoder layer + GGML_ASSERT(n_layer == 1); // EAGLE-3 has only one decoder layer ggml_tensor * cur; ggml_tensor * inpL; - // eagle3 Decoder receives: - // 1. Token embeddings (e.g.from eagle3's own tok_embd for Llama 3.3 70B, or target model for Llama 3.1 8B) + // EAGLE3 Decoder receives: + // 1. Token embeddings (e.g.from EAGLE3's own tok_embd for Llama 3.3 70B, or target model for Llama 3.1 8B) // 2. g_embeddings from encoder - auto * tok_embd = model.tok_embd; - if (model.tok_embd == nullptr) { - GGML_ASSERT(cparams.ctx_other != nullptr); - const auto * model_other = llama_get_model(cparams.ctx_other); - - GGML_ASSERT(model_other->tok_embd != nullptr && "EAGLE3 decoder requires token embeddings (own or from target model)"); - tok_embd = model_other->tok_embd; - } - - auto inp = std::make_unique(n_embd); - - inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_tokens); - ggml_set_input(inp->tokens); - - inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd, n_tokens); - ggml_set_input(inp->embd); - - ggml_tensor * inp_embd = ggml_get_rows(ctx0, tok_embd, inp->tokens); + // Choose token_embd_eagle3: prefer EAGLE3's own if available (Llama 3.3 70B), else use target's (Llama 3.1 8B) + ggml_tensor * token_embd_eagle3 = (model.tok_embd != nullptr) ? model.tok_embd : model.target_tok_embd; + GGML_ASSERT(token_embd_eagle3 != nullptr && "EAGLE3 decoder requires token embeddings (own or from target model)"); + ggml_tensor * inp_embd = build_inp_embd(token_embd_eagle3); cb(inp_embd, "inp_embd", -1); - ggml_tensor * inp_g = inp->embd; - cb(inp_g, "inp_g_embeddings", -1); - - res->add_input(std::move(inp)); + // TODO: refactor into llm_graph_input + ggml_tensor * inp_g = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd, n_tokens); + ggml_set_input(inp_g); + cb(inp_g, "inp_g_embeddings", -1); // TODO: do not change the name! refactor into llm_graph_input inpL = inp_g; @@ -192,6 +129,7 @@ llama_model_eagle3::graph::graph(const llama_model & model, const llm_gra const float kq_scale = 1.0f/sqrtf(float(n_embd_head)); + ggml_tensor * inp_out_ids = build_inp_out_ids(); // Single decoder layer (il = 0) const int il = 0; { @@ -203,7 +141,7 @@ llama_model_eagle3::graph::graph(const llama_model & model, const llm_gra // Apply hidden_norm to inp_g ggml_tensor * g_norm = build_norm(inp_g, - model.layers[il].attn_norm_2, NULL, + model.layers[il].eagle3_hidden_norm, NULL, LLM_NORM_RMS, -1); cb(g_norm, "g_norm", il); @@ -211,7 +149,7 @@ llama_model_eagle3::graph::graph(const llama_model & model, const llm_gra // - false (default): use raw inp_g for residual // - true: use normalized g_norm for residual // inpL is the concatenated input (normalized inp_embd + normalized inp_g) - ggml_tensor * inpSA = hparams.norm_before_residual ? g_norm : inpL; + ggml_tensor * inpSA = hparams.eagle3_norm_before_residual ? g_norm : inpL; // Concatenate normalized inp_embd and normalized inp_g cur = ggml_concat(ctx0, embd_norm, g_norm, il); @@ -250,9 +188,13 @@ llama_model_eagle3::graph::graph(const llama_model & model, const llm_gra cb(Kcur, "Kcur_rope", il); cur = build_attn(inp_attn, - model.layers[il].wo, NULL, nullptr, + model.layers[il].wo, nullptr, nullptr, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il); + if (inp_out_ids) { + cur = ggml_get_rows(ctx0, cur, inp_out_ids); + inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids); + } // Add residual and update it ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA); cb(ffn_inp, "ffn_inp", il); @@ -282,7 +224,7 @@ llama_model_eagle3::graph::graph(const llama_model & model, const llm_gra // Output prenorm state (for next token's g_embeddings in autoregressive generation) ggml_set_output(cur); - res->t_h_nextn = cur; + res->t_embd = cur; cur = build_norm(cur, model.output_norm, NULL, @@ -290,31 +232,7 @@ llama_model_eagle3::graph::graph(const llama_model & model, const llm_gra cb(cur, "result_norm", -1); // lm_head - projects to draft vocabulary - // if the draft has no own output projection, inherit the target model's lm_head - auto * output = model.output; - if (output == nullptr) { - GGML_ASSERT(cparams.ctx_other != nullptr); - const auto * model_other = llama_get_model(cparams.ctx_other); - - GGML_ASSERT(model_other->output != nullptr && "EAGLE3 decoder requires an output projection (own or from target model)"); - output = model_other->output; - } - cur = build_lora_mm(output, cur); - - if (model.d2t) { - const int64_t n_draft_vocab = cur->ne[0]; - const int64_t n_outputs = cur->ne[1]; - const int64_t n_vocab = (int64_t) model.vocab.n_tokens(); - - GGML_ASSERT(model.d2t->type == GGML_TYPE_I64); - GGML_ASSERT(model.d2t->ne[0] == n_draft_vocab); - - ggml_tensor * logits = ggml_fill(ctx0, ggml_new_tensor_3d(ctx0, GGML_TYPE_F32, 1, n_vocab, n_outputs), -INFINITY); - cur = ggml_set_rows(ctx0, logits, - ggml_reshape_3d(ctx0, cur, 1, n_draft_vocab, n_outputs), - ggml_reshape_3d(ctx0, model.d2t, n_draft_vocab, 1, 1)); - cur = ggml_reshape_2d(ctx0, cur, n_vocab, n_outputs); - } + cur = build_lora_mm(model.output, cur); cb(cur, "result_output", -1); res->t_logits = cur; diff --git a/src/models/models.h b/src/models/models.h index d89ab96d0271..21a8ae4ff57c 100644 --- a/src/models/models.h +++ b/src/models/models.h @@ -1992,3 +1992,29 @@ struct llama_model_step35 : public llama_model_base { std::unique_ptr build_arch_graph(const llm_graph_params & params) const override; }; + +struct llm_build_eagle3_encode : public llm_graph_context { + llm_build_eagle3_encode(const llama_model & model, const llm_graph_params & params); +private: + ggml_tensor * build_inp_embd() const; +}; + +struct llm_build_eagle3_decode : public llm_graph_context { + llm_build_eagle3_decode(const llama_model & model, const llm_graph_params & params); +}; + +struct llm_build_dflash_encode : public llm_graph_context { + llm_build_dflash_encode(const llama_model & model, const llm_graph_params & params); +private: + ggml_tensor * build_inp_embd() const; +}; + +struct llm_build_dflash_decode : public llm_graph_context { + llm_build_dflash_decode(const llama_model & model, const llm_graph_params & params); +}; + +struct llm_build_openai_moe_iswa : public llm_graph_context { + llm_build_openai_moe_iswa(const llama_model & model, const llm_graph_params & params); +}; + + diff --git a/src/models/qwen35.cpp b/src/models/qwen35.cpp index 6783d98ec204..23a02cbf599a 100644 --- a/src/models/qwen35.cpp +++ b/src/models/qwen35.cpp @@ -156,6 +156,7 @@ llama_model_qwen35::graph::graph(const llama_model & model, const llm_graph_para // MTP/NextN layers are loaded as extra decoder blocks but not executed in the main pass. for (int il = 0; il < n_layer; ++il) { + res->t_layer_inp[il] = inpL; ggml_tensor * inpSA = inpL; cur = build_norm(inpL, model.layers[il].attn_norm, nullptr, LLM_NORM_RMS, il); diff --git a/src/models/qwen35moe.cpp b/src/models/qwen35moe.cpp index eb5e9a406a15..857da486ea28 100644 --- a/src/models/qwen35moe.cpp +++ b/src/models/qwen35moe.cpp @@ -180,6 +180,8 @@ llama_model_qwen35moe::graph::graph(const llama_model & model, const llm_graph_p // MTP/NextN layers are loaded as extra decoder blocks but not executed in the main pass. for (int il = 0; il < n_layer; ++il) { ggml_tensor * inpSA = inpL; + res->t_layer_inp[il] = inpL; + cur = build_norm(inpL, model.layers[il].attn_norm, nullptr, LLM_NORM_RMS, il); cb(cur, "attn_norm", il); diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp index 1c2d60cf89e6..10f83d65b9f9 100644 --- a/tools/server/server-context.cpp +++ b/tools/server/server-context.cpp @@ -51,7 +51,17 @@ static uint32_t server_n_outputs_max(const common_params & params) { return n_batch; } - const uint32_t n_outputs_per_seq = 1 + common_speculative_n_max(¶ms.speculative); + // Max outputs needed per sequence based on declared speculative types + uint32_t n_max = (uint32_t) common_speculative_n_max(¶ms.speculative); + + // Also account for draft modes enabled by flags (--dflash, --eagle3) that aren't + // yet in the speculative types list at this point (they are added later during + // common_speculative_init, after the target context is created) + if (params.speculative.draft.dflash || params.speculative.draft.eagle3) { + n_max = std::max(n_max, (uint32_t) std::max(0, params.speculative.draft.n_max)); + } + + const uint32_t n_outputs_per_seq = 1 + n_max; const uint64_t n_outputs = (uint64_t) params.n_parallel * n_outputs_per_seq; @@ -1141,7 +1151,7 @@ struct server_context_impl { slots.clear(); - // Always probe seq_rm capability — used for completion checkpoints, not only speculative. + // Always probe seq_rm (completion checkpoints). Do not skip when speculative is off (#217). ctx_tgt_seq_rm_type = common_context_can_seq_rm(ctx_tgt); // initialize slots @@ -1149,11 +1159,15 @@ struct server_context_impl { slots.emplace_back(); } - // try speculative decoding (only when a non-NONE speculative type is configured) - const bool is_speculative_enabled = std::any_of( - params_base.speculative.types.begin(), - params_base.speculative.types.end(), - [](auto t) { return t != COMMON_SPECULATIVE_TYPE_NONE; }); + // Speculative init only when a type or --dflash/--eagle3 convenience flag is set. + // Still requires seq_rm support for draft rollback. + const bool is_speculative_enabled = + params_base.speculative.draft.dflash || + params_base.speculative.draft.eagle3 || + std::any_of( + params_base.speculative.types.begin(), + params_base.speculative.types.end(), + [](auto t) { return t != COMMON_SPECULATIVE_TYPE_NONE; }); if (is_speculative_enabled) { if (ctx_tgt_seq_rm_type == COMMON_CONTEXT_SEQ_RM_TYPE_NO) { SRV_WRN("%s", "speculative decoding not supported by this context\n");