diff --git a/cactus-engine/src/model.cpp b/cactus-engine/src/model.cpp index 456d471aa..e66a0e312 100644 --- a/cactus-engine/src/model.cpp +++ b/cactus-engine/src/model.cpp @@ -1187,7 +1187,6 @@ void Model::move_cache_states(Component& source, Component& target, size_t logic logical_current != std::numeric_limits::max()) { auto* meta = static_cast(target.graph->get_output(static_cast(dst_node))); if (meta && logical_current < meta[0]) { - meta[4] = std::min(meta[4], logical_current); meta[0] = logical_current; } } @@ -1202,7 +1201,6 @@ void Model::set_cache_current_len(Component& comp, size_t len) { if (comp.graph->get_node_op_type(static_cast(node_id)) != OpType::KV_CACHE_STATE) continue; auto* meta = static_cast(comp.graph->get_output(static_cast(node_id))); if (!meta || len >= meta[0]) continue; - meta[4] = std::min(meta[4], len); meta[0] = len; } } diff --git a/cactus-engine/tests/run.cpp b/cactus-engine/tests/run.cpp index e412cdce8..359e4bd3c 100644 --- a/cactus-engine/tests/run.cpp +++ b/cactus-engine/tests/run.cpp @@ -247,6 +247,9 @@ struct TokenPrinter { std::string pending; bool in_code_fence = false; bool color = false; + bool suppress_thinking_stream = false; + bool show_thinking = false; + std::string stream_carry; bool label_printed = false; std::thread spinner_thread; std::atomic spinner_running{false}; @@ -261,10 +264,12 @@ struct TokenPrinter { in_code_fence = false; label_printed = false; color = stdout_is_terminal(); + suppress_thinking_stream = false; + stream_carry.clear(); } void start_thinking() { - if (!color) return; + if (!color || spinner_running.load()) return; spinner_running = true; spinner_thread = std::thread([this]() { const char* frames[] = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}; @@ -434,25 +439,61 @@ struct TokenPrinter { } void on_token(const char* text) { - print_label(); if (!saw_first) { first = std::chrono::steady_clock::now(); saw_first = true; } ++count; if (!text) return; - pending += text; + feed(text); + std::cout << std::flush; + } + + void feed(const std::string& chunk) { + static const std::vector markers = { + "<|channel>", "", "<|turn>", "", "<|think|>", "", + }; + stream_carry += chunk; + size_t i = 0; + while (i < stream_carry.size()) { + const std::string* matched = nullptr; + bool maybe_partial = false; + for (const auto& m : markers) { + if (stream_carry.compare(i, m.size(), m) == 0) { matched = &m; break; } + size_t avail = stream_carry.size() - i; + if (avail < m.size() && stream_carry.compare(i, avail, m, 0, avail) == 0) maybe_partial = true; + } + if (matched) { + if (*matched == "<|channel>") { suppress_thinking_stream = true; if (!show_thinking) start_thinking(); } + else if (*matched == "") { suppress_thinking_stream = false; stop_thinking(); } + i += matched->size(); + continue; + } + if (maybe_partial) break; + if (!suppress_thinking_stream || show_thinking) emit_visible(stream_carry[i]); + ++i; + } + stream_carry.erase(0, i); + } + + void emit_visible(char c) { + print_label(); + pending += c; size_t nl; while ((nl = pending.find('\n')) != std::string::npos) { render_line(pending.substr(0, nl)); std::cout << "\n"; pending.erase(0, nl + 1); } - std::cout << std::flush; } // Flush the trailing partial line once generation completes (no trailing newline). void finish() { + stop_thinking(); + if (!stream_carry.empty() && (!suppress_thinking_stream || show_thinking)) { + for (char c : stream_carry) emit_visible(c); + } + stream_carry.clear(); print_label(); if (!pending.empty()) { render_line(pending); @@ -893,6 +934,7 @@ int main(int argc, char** argv) { std::vector current_pcm; TokenPrinter printer; g_printer = &printer; + printer.show_thinking = thinking; bool auto_send = !initial_prompt.empty() || !current_audio.empty() || !current_image.empty(); while (true) { @@ -990,7 +1032,7 @@ int main(int argc, char** argv) { + ",\"auto_handoff\":" + (auto_handoff ? "true" : "false") + ",\"confidence_threshold\":" + std::to_string(confidence_threshold) + ",\"cloud_timeout_ms\":" + std::to_string(cloud_timeout_ms) - + ",\"stop_sequences\":[\"<|im_end|>\",\"\"]}"; + + ",\"stop_sequences\":[\"<|im_end|>\",\"\",\"\"]}"; if (!current_image.empty()) std::cout << "[image: " << current_image << "]\n"; if (!current_audio.empty()) std::cout << "[audio: " << current_audio << "]\n"; diff --git a/cactus-graph/cactus_graph.h b/cactus-graph/cactus_graph.h index 55aa7076b..cd37fd4d2 100644 --- a/cactus-graph/cactus_graph.h +++ b/cactus-graph/cactus_graph.h @@ -1073,7 +1073,7 @@ CACTUS_FFI_EXPORT int cactus_graph_gaussian_topk( CACTUS_FFI_EXPORT int cactus_graph_moe_layer_gated( cactus_graph_t graph, cactus_node_t hidden, cactus_node_t routing_probs, cactus_node_t topk_indices, const cactus_node_t* w1_weights, const cactus_node_t* w3_weights, const cactus_node_t* w2_weights, - size_t num_experts, size_t num_experts_per_tok, bool normalize_routing, float epsilon, float routed_scaling_factor, cactus_node_t* out); + size_t num_experts, size_t num_experts_per_tok, bool normalize_routing, float epsilon, float routed_scaling_factor, int32_t activation, cactus_node_t* out); CACTUS_FFI_EXPORT int cactus_graph_moe_layer_ungated( cactus_graph_t graph, cactus_node_t hidden, cactus_node_t routing_probs, cactus_node_t topk_indices, const cactus_node_t* w1_weights, const cactus_node_t* w2_weights, diff --git a/cactus-graph/src/builder.cpp b/cactus-graph/src/builder.cpp index 88778a168..7e82ed65b 100644 --- a/cactus-graph/src/builder.cpp +++ b/cactus-graph/src/builder.cpp @@ -1220,7 +1220,12 @@ size_t CactusGraph::add_node(OpType op_type, const std::vector& inputs, } Precision result_precision = params.output_precision; - if (op_type == OpType::PRECISION_CAST || op_type == OpType::EMBEDDING) { + if (op_type == OpType::PRECISION_CAST || + op_type == OpType::EMBEDDING || + op_type == OpType::TOPK || + op_type == OpType::SCATTER_TOPK || + op_type == OpType::SAMPLE || + op_type == OpType::GAUSSIAN_TOPK) { result_precision = params.output_precision; } else if (!inputs.empty()) { result_precision = nodes_[node_index_map_[inputs[0]]]->output_buffer.precision; @@ -1305,8 +1310,12 @@ size_t CactusGraph::altup_correct(size_t coefs, size_t innovation, const size_t* size_t CactusGraph::gaussian_topk(size_t input, float ppf) { const auto& in_buf = get_output_buffer(input); + if (in_buf.precision != Precision::FP16) { + throw std::runtime_error("gaussian_topk only supports FP16 input"); + } OpParams params; params.scalar = ppf; + params.output_precision = in_buf.precision; return add_node(OpType::GAUSSIAN_TOPK, {input}, in_buf.shape, params); } diff --git a/cactus-graph/src/graph_ffi.cpp b/cactus-graph/src/graph_ffi.cpp index 685b6aa3d..74ce9a8e6 100644 --- a/cactus-graph/src/graph_ffi.cpp +++ b/cactus-graph/src/graph_ffi.cpp @@ -1341,7 +1341,7 @@ int cactus_graph_gaussian_topk(cactus_graph_t graph, cactus_node_t input, float int cactus_graph_moe_layer_gated(cactus_graph_t graph, cactus_node_t hidden, cactus_node_t routing_probs, cactus_node_t topk_indices, const cactus_node_t* w1_weights, const cactus_node_t* w3_weights, const cactus_node_t* w2_weights, - size_t num_experts, size_t num_experts_per_tok, bool normalize_routing, float epsilon, float routed_scaling_factor, cactus_node_t* out) { + size_t num_experts, size_t num_experts_per_tok, bool normalize_routing, float epsilon, float routed_scaling_factor, int32_t activation, cactus_node_t* out) { if (!graph || !w1_weights || !w3_weights || !w2_weights || !out) return fail_invalid("Invalid args to cactus_graph_moe_layer_gated"); try { std::vector w1(num_experts), w3(num_experts), w2(num_experts); @@ -1350,7 +1350,7 @@ int cactus_graph_moe_layer_gated(cactus_graph_t graph, cactus_node_t hidden, cac w3[i] = static_cast(w3_weights[i]); w2[i] = static_cast(w2_weights[i]); } - *out = static_cast(as_graph(graph)->graph.moe_layer(static_cast(hidden), static_cast(routing_probs), static_cast(topk_indices), w1, w3, w2, num_experts, num_experts_per_tok, normalize_routing, epsilon, routed_scaling_factor)); + *out = static_cast(as_graph(graph)->graph.moe_layer(static_cast(hidden), static_cast(routing_probs), static_cast(topk_indices), w1, w3, w2, num_experts, num_experts_per_tok, normalize_routing, epsilon, routed_scaling_factor, static_cast(activation))); return 0; } catch (const std::exception& e) { last_error_message = e.what(); diff --git a/cactus-graph/src/ops_nn.cpp b/cactus-graph/src/ops_nn.cpp index d2c84d198..bee9810ba 100644 --- a/cactus-graph/src/ops_nn.cpp +++ b/cactus-graph/src/ops_nn.cpp @@ -83,6 +83,7 @@ void compute_matmul_node(GraphNode& node, const std::vector moe_compact_hidden_buf; thread_local std::vector<__fp16> moe_gate_buf; + thread_local std::vector<__fp16> moe_gate_pad_buf; thread_local std::vector<__fp16> moe_up_buf; thread_local std::vector<__fp16> moe_expert_out_buf; thread_local std::vector moe_expert_offsets_buf; @@ -185,18 +186,21 @@ void compute_moe_layer_node(GraphNode& node, const std::vector size_t { + if (!std::isfinite(raw_idx) || raw_idx < 0.0f) { + throw std::runtime_error("moe_layer got invalid expert index"); + } + size_t idx = static_cast(raw_idx + 0.5f); + if (idx >= num_experts) { + throw std::runtime_error("moe_layer got expert index out of range"); + } + return idx; + }; + std::memset(expert_offsets, 0, (num_experts + 1) * sizeof(size_t)); for (size_t tok = 0; tok < token_count; ++tok) { for (size_t k = 0; k < top_k; ++k) { - float raw_idx = topk_idx[tok * top_k + k]; - if (!std::isfinite(raw_idx)) { - throw std::runtime_error("moe_layer got non-finite expert index"); - } - size_t idx = static_cast(raw_idx + 0.5f); - if (idx >= num_experts) { - throw std::runtime_error("moe_layer got expert index out of range"); - } - expert_offsets[idx + 1]++; + expert_offsets[expert_index(topk_idx[tok * top_k + k]) + 1]++; } } @@ -210,7 +214,7 @@ void compute_moe_layer_node(GraphNode& node, const std::vector(topk_idx[tok * top_k + k] + 0.5f); + size_t idx = expert_index(topk_idx[tok * top_k + k]); expert_tokens_flat[moe_write_cursors[idx]++] = tok; } } @@ -220,7 +224,7 @@ void compute_moe_layer_node(GraphNode& node, const std::vector(topk_idx[tok * top_k + k] + 0.5f); + size_t idx = expert_index(topk_idx[tok * top_k + k]); sum_probs += routing_prob(tok, idx); } routing_denom[tok] = sum_probs + eps; @@ -283,7 +287,23 @@ void compute_moe_layer_node(GraphNode& node, const std::vector