diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index c295aed46..3069e8cae 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -390,6 +390,7 @@ add_library(dflash_common STATIC # DeepSeek V4 Flash target arch src/deepseek4/deepseek4_loader.cpp src/deepseek4/deepseek4_graph.cpp + src/deepseek4/deepseek4_roctx.cpp src/deepseek4/deepseek4_backend.cpp src/deepseek4/deepseek4_daemon.cpp src/deepseek4/deepseek4_layer_split_adapter.cpp @@ -756,6 +757,8 @@ target_link_libraries(dflash_common ${DFLASH27B_GGML_BACKEND_TARGET} ggml-base nlohmann_json::nlohmann_json + PRIVATE + ${CMAKE_DL_LIBS} ) # OpenMP for parallel MoE expert compute kernel (saturate memory bandwidth). find_package(OpenMP) @@ -1006,6 +1009,20 @@ if(DFLASH27B_TESTS) endif() list(APPEND _raw_unit_test_targets test_deepseek4_unit) endif() + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_deepseek4_roctx.cpp") + add_executable(test_deepseek4_roctx + test/test_deepseek4_roctx.cpp + src/deepseek4/deepseek4_roctx.cpp + src/common/target_shard_ipc_daemon.cpp) + target_include_directories(test_deepseek4_roctx PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src) + if(DFLASH27B_GPU_BACKEND STREQUAL "hip") + target_compile_definitions(test_deepseek4_roctx PRIVATE + DFLASH27B_BACKEND_HIP=1) + endif() + target_link_libraries(test_deepseek4_roctx PRIVATE ${CMAKE_DL_LIBS}) + list(APPEND _raw_unit_test_targets test_deepseek4_roctx) + endif() if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_ggml_rmsnorm_batch.cpp") add_executable(test_ggml_rmsnorm_batch test/test_ggml_rmsnorm_batch.cpp) target_include_directories(test_ggml_rmsnorm_batch PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS}) diff --git a/server/docs/DS4.md b/server/docs/DS4.md index 6ce6cdb98..3735fd1d2 100644 --- a/server/docs/DS4.md +++ b/server/docs/DS4.md @@ -231,6 +231,7 @@ The runtime logs the chosen split with a `[deepseek4-split] auto-split:` banner. |----------|---------| | `DFLASH_DS4_CUDA_LAYERS` | Override the auto-split heuristic and pin the first `N` DeepSeek4 layers to CUDA. The remaining `43 - N` layers run on the Halo shard. | | `DFLASH_DS4_TIMING` | Enable DS4 timing logs for the layer-split parent and target-shard daemon. Useful for profiling prefill/decode breakdowns; leave unset for normal runs. | +| `DFLASH_DS4_ROCTX` | HIP-only, default-off semantic ROCTX ranges for an external rocprof trace. The library is loaded dynamically only when set to `1`, `true`, `yes`, or `on`. | | `DFLASH_DS4_SPEC` / `DFLASH_DS4_DRAFT` | Enable DSpark and select its GGUF. | | `DFLASH_DS4_DRAFT_BACKEND` / `DFLASH_DS4_DRAFT_GPU` | Backend and device for the in-process drafter. | | `DFLASH_DS4_MOE_TP` | Enable routed-expert partitioning. | @@ -261,6 +262,21 @@ The runtime logs the chosen split with a `[deepseek4-split] auto-split:` banner. The old per-expert IPC worker is retired. The `DFLASH_DS4_MOE_TP*` variables above configure the in-process route-owner implementation. +### External ROCm traces + +Set `DFLASH_DS4_ROCTX=1` when collecting an external rocprof marker trace. +The runtime emits balanced `ds4.prefill`, `ds4.spec_decode`, and +`ds4.layer_range` ranges with the applicable mode, token count, layer bounds, +and device. The marker layer does not use HIP events, synchronize a stream, or +calculate timings; rocprof remains the timing authority. Non-HIP builds emit +no markers or ROCTX library calls, and an unset or false value does not load +ROCTX. + +Use the marker-trace option supported by the installed rocprof version (for +example, `rocprofv3 --marker-trace -- `), then correlate these +host scopes with the kernel and memory-copy tracks. A target-machine trace is +still required to assess instrumentation perturbation. + ## DSpark Speculative Decode DeepSeek4 uses the shared DFlash DSpark head implementation together with a diff --git a/server/docs/ENVIRONMENT.md b/server/docs/ENVIRONMENT.md index a877c3812..0b21c852d 100644 --- a/server/docs/ENVIRONMENT.md +++ b/server/docs/ENVIRONMENT.md @@ -42,6 +42,7 @@ consolidation of this list into CLI flags is tracked as follow-up work. | `GGML_SCHED_PROFILE` / `GGML_SCHED_PROFILE_MIN_SPLITS` | unset / 1 | DEBUG: report scheduler splits, copy volume, submission time, and source/destination synchronization time. | | `DFLASH_DS4_TP_FUSED_CACHE_SLOTS` | 2 | BURN-IN: number of heterogeneous verifier schedulers retained; higher values retain substantially more scratch on both GPUs. | | `DFLASH_DS4_VERIFY_FORCE_GRAPH_REPLAY` | unset | OPT-IN: bypass graph property scans only after warmup; scheduler-generation checks remain mandatory. | +| `DFLASH_DS4_ROCTX` | unset | DEBUG: on HIP builds, dynamically load ROCTX and emit semantic DS4 prefill, speculative-decode, and layer-range markers for external rocprof traces. No events, timing, or device synchronization are added. | | `GGML_DS4_FA_SERIAL_INDEX_SCAN` | unset | DEBUG/A-B: restore the serial indexed-attention mask scan instead of the long-context HIP parallel scan. | | `DFLASH_MOE_PREFILL_PERSISTENT_OWNER_ALLOC` | 1 for qualified long heterogeneous prefill | KILL SWITCH: =0 restores per-layer route/owner scratch allocation. | | `DFLASH_MOE_TP_*` / `DFLASH_MOE_HYBRID_PREFILL_EAGER` | unset | BURN-IN: model-neutral names for common heterogeneous-MoE scheduling and kernel policy. Existing `DFLASH_DS4_*` names remain compatibility aliases. | @@ -107,6 +108,7 @@ consolidation of this list into CLI flags is tracked as follow-up work. - `DFLASH_DS4_MOE_TP_INPROC` - deepseek4_backend.cpp - `DFLASH_DS4_MOE_TP_PEER_HOT` - deepseek4_backend.cpp - `DFLASH_DS4_ROUTING_STATS_OUT` - deepseek4_backend.cpp +- `DFLASH_DS4_ROCTX` - deepseek4_roctx.cpp - `DFLASH_DS4_SEQ_VERIFY` - deepseek4_dspark_spec.cpp - `DFLASH_DS4_SPEC` - deepseek4_backend.cpp - `DFLASH_DS4_SPEC_REFERENCE_EXACT` - deepseek4_dspark_spec.cpp diff --git a/server/src/common/inference_phase.h b/server/src/common/inference_phase.h new file mode 100644 index 000000000..3abc6bb00 --- /dev/null +++ b/server/src/common/inference_phase.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace dflash::common { + +enum class InferencePhase : int32_t { + Unspecified = 0, + Exact = 1, + Dense = 2, + Sparse = 3, + Decode = 4, + Verify = 5, + ReferenceExact = 6, + Sequential = 7, + Batched = 8, +}; + +constexpr int32_t inference_phase_wire_value(InferencePhase phase) { + return static_cast(phase); +} + +constexpr bool inference_phase_from_wire_value(int32_t value, + InferencePhase & phase) { + if (value < inference_phase_wire_value(InferencePhase::Unspecified) || + value > inference_phase_wire_value(InferencePhase::Batched)) { + return false; + } + phase = static_cast(value); + return true; +} + +} // namespace dflash::common diff --git a/server/src/common/target_shard_ipc.cpp b/server/src/common/target_shard_ipc.cpp index 8d4dc6148..64f98b2cc 100644 --- a/server/src/common/target_shard_ipc.cpp +++ b/server/src/common/target_shard_ipc.cpp @@ -231,6 +231,7 @@ bool TargetShardIpcSession::forward( const size_t bytes = boundary_activation.size() * sizeof(float); const int want_argmax = req.want_argmax ? 1 : 0; const int want_logits = req.want_logits ? 1 : 0; + const int32_t semantic_phase = inference_phase_wire_value(req.semantic_phase); const int forward_ubatch = ubatch > 0 ? ubatch : n_tokens; std::vector empty_ids; const std::vector * ids = token_ids ? token_ids : &empty_ids; @@ -243,14 +244,16 @@ bool TargetShardIpcSession::forward( bytes, process_.shared_payload_capacity()); return false; } - std::fprintf(cmd, "forward_shared %d %d %d %d %zu %" PRIu64 " %d %d %d\n", + std::fprintf(cmd, "forward_shared %d %d %d %d %zu %" PRIu64 " %d %d %d %d\n", base_pos, n_tokens, want_argmax, want_logits, bytes, seq, - token_ids ? 1 : 0, forward_ubatch, (int)ids->size()); + token_ids ? 1 : 0, forward_ubatch, (int)ids->size(), + semantic_phase); std::fflush(cmd); } else if (payload_fd >= 0) { - std::fprintf(cmd, "forward_pipe %d %d %d %d %zu %d %d %d\n", + std::fprintf(cmd, "forward_pipe %d %d %d %d %zu %d %d %d %d\n", base_pos, n_tokens, want_argmax, want_logits, bytes, - token_ids ? 1 : 0, forward_ubatch, (int)ids->size()); + token_ids ? 1 : 0, forward_ubatch, (int)ids->size(), + semantic_phase); std::fflush(cmd); if (!write_exact_fd(payload_fd, boundary_activation.data(), bytes)) { std::fprintf(stderr, "target-shard payload write failed\n"); diff --git a/server/src/common/target_shard_ipc.h b/server/src/common/target_shard_ipc.h index fb97d9e7e..f18622db4 100644 --- a/server/src/common/target_shard_ipc.h +++ b/server/src/common/target_shard_ipc.h @@ -3,6 +3,7 @@ #pragma once #include "backend_ipc.h" +#include "inference_phase.h" #include "ggml.h" #include "ggml-backend.h" @@ -39,6 +40,7 @@ struct TargetShardForwardRequest { int ubatch = 0; bool want_argmax = false; bool want_logits = false; + InferencePhase semantic_phase = InferencePhase::Unspecified; }; struct TargetShardForwardResponse { diff --git a/server/src/common/target_shard_ipc_daemon.cpp b/server/src/common/target_shard_ipc_daemon.cpp index 20045be49..ee90f0ad8 100644 --- a/server/src/common/target_shard_ipc_daemon.cpp +++ b/server/src/common/target_shard_ipc_daemon.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,21 @@ bool stream_daemon_status(const TargetShardDaemonCallbacks & callbacks, return true; } +bool forward_payload_bytes(int hidden, int n_tokens, size_t & bytes) { + if (hidden <= 0 || n_tokens <= 0 || + static_cast(n_tokens) > + std::numeric_limits::max() / static_cast(hidden)) { + return false; + } + const size_t elements = static_cast(n_tokens) * + static_cast(hidden); + if (elements > std::numeric_limits::max() / sizeof(float)) { + return false; + } + bytes = elements * sizeof(float); + return true; +} + } // namespace int run_target_shard_ipc_daemon_loop( @@ -117,34 +133,40 @@ int run_target_shard_ipc_daemon_loop( int has_token_ids = 0; int forward_ubatch = 0; int token_count = 0; + int32_t semantic_phase_value = 0; size_t bytes = 0; bool payload_ok = false; + bool framing_fields_present = false; + bool command_fields_ok = false; + bool pipe_forward = false; + bool shared_forward = false; + uint64_t shared_seq = 0; if (cmd == "forward_pipe") { + pipe_forward = true; iss >> base_pos >> n_tokens >> want_argmax >> want_logits >> bytes >> has_token_ids >> forward_ubatch >> token_count; - const size_t expected_bytes = - (size_t)std::max(0, n_tokens) * (size_t)hidden * sizeof(float); - if (payload_fd >= 0 && n_tokens > 0 && bytes == expected_bytes) { - host_act.assign(bytes / sizeof(float), 0.0f); - payload_ok = read_exact_fd(payload_fd, host_act.data(), bytes); + framing_fields_present = static_cast(iss); + if (framing_fields_present) { + iss >> semantic_phase_value; + const bool phase_present = static_cast(iss); + if (phase_present) { + iss >> std::ws; + command_fields_ok = iss.eof(); + } } } else if (cmd == "forward_shared") { - uint64_t seq = 0; - iss >> base_pos >> n_tokens >> want_argmax >> want_logits >> bytes >> seq >> - has_token_ids >> forward_ubatch >> token_count; - const size_t expected_bytes = - (size_t)std::max(0, n_tokens) * (size_t)hidden * sizeof(float); - const auto * header = - static_cast(shared_payload); - if (shared_payload && shared_payload != MAP_FAILED && shared_payload_data && - seq != 0 && n_tokens > 0 && bytes == expected_bytes && - backend_ipc_payload_in_bounds(0, bytes, shared_payload_capacity) && - backend_ipc_shared_payload_header_matches( - header, seq, static_cast(bytes))) { - host_act.assign(bytes / sizeof(float), 0.0f); - std::memcpy(host_act.data(), shared_payload_data, bytes); - payload_ok = true; + shared_forward = true; + iss >> base_pos >> n_tokens >> want_argmax >> want_logits >> bytes >> + shared_seq >> has_token_ids >> forward_ubatch >> token_count; + framing_fields_present = static_cast(iss); + if (framing_fields_present) { + iss >> semantic_phase_value; + const bool phase_present = static_cast(iss); + if (phase_present) { + iss >> std::ws; + command_fields_ok = iss.eof(); + } } } else { if (cmd == "reset_request_state") { @@ -194,19 +216,46 @@ int run_target_shard_ipc_daemon_loop( continue; } - bool ok = payload_ok && base_pos >= 0 && n_tokens > 0; - if (ok && has_token_ids) { - ok = payload_fd >= 0 && token_count == n_tokens; - if (ok) { - token_ids.assign((size_t)n_tokens, 0); - ok = read_exact_fd(payload_fd, token_ids.data(), - sizeof(int32_t) * token_ids.size()); + size_t expected_bytes = 0; + const bool framing_ok = framing_fields_present && base_pos >= 0 && + forward_ubatch >= 0 && (want_argmax == 0 || want_argmax == 1) && + (want_logits == 0 || want_logits == 1) && + (has_token_ids == 0 || has_token_ids == 1) && + forward_payload_bytes(hidden, n_tokens, expected_bytes) && + bytes == expected_bytes && + token_count == (has_token_ids ? n_tokens : 0); + + if (framing_ok && pipe_forward && payload_fd >= 0) { + host_act.assign(bytes / sizeof(float), 0.0f); + payload_ok = read_exact_fd(payload_fd, host_act.data(), bytes); + } else if (framing_ok && shared_forward) { + const auto * header = + static_cast(shared_payload); + if (shared_payload && shared_payload != MAP_FAILED && + shared_payload_data && shared_seq != 0 && + backend_ipc_payload_in_bounds(0, bytes, shared_payload_capacity) && + backend_ipc_shared_payload_header_matches( + header, shared_seq, static_cast(bytes))) { + host_act.assign(bytes / sizeof(float), 0.0f); + std::memcpy(host_act.data(), shared_payload_data, bytes); + payload_ok = true; } - } else { - token_ids.clear(); - ok = ok && token_count == 0; } + const int token_fd = payload_fd >= 0 ? payload_fd : stream_fd; + bool token_ids_ok = !has_token_ids; + token_ids.clear(); + if (framing_ok && has_token_ids) { + token_ids.assign(static_cast(n_tokens), 0); + token_ids_ok = read_exact_fd( + token_fd, token_ids.data(), sizeof(int32_t) * token_ids.size()); + } + + InferencePhase semantic_phase = InferencePhase::Unspecified; + const bool semantic_phase_ok = command_fields_ok && + inference_phase_from_wire_value(semantic_phase_value, semantic_phase); + bool ok = framing_ok && payload_ok && token_ids_ok && semantic_phase_ok; + TargetShardDaemonForwardResponse resp; if (ok) { TargetShardDaemonForwardRequest req; @@ -215,6 +264,7 @@ int run_target_shard_ipc_daemon_loop( req.ubatch = forward_ubatch > 0 ? forward_ubatch : n_tokens; req.want_argmax = want_argmax != 0; req.want_logits = want_logits != 0; + req.semantic_phase = semantic_phase; req.boundary_activation = &host_act; req.token_ids = has_token_ids ? &token_ids : nullptr; ok = callbacks.forward(req, resp); @@ -222,6 +272,7 @@ int run_target_shard_ipc_daemon_loop( const int32_t status = ok ? 0 : -1; if (!write_exact_fd(stream_fd, &status, sizeof(status))) break; + if (!framing_ok) break; if (!ok) continue; if (!write_exact_fd(stream_fd, &resp.last_tok, sizeof(resp.last_tok))) break; diff --git a/server/src/common/target_shard_ipc_daemon.h b/server/src/common/target_shard_ipc_daemon.h index 89442bfab..aba986aad 100644 --- a/server/src/common/target_shard_ipc_daemon.h +++ b/server/src/common/target_shard_ipc_daemon.h @@ -2,6 +2,8 @@ #pragma once +#include "inference_phase.h" + #include #include #include @@ -15,6 +17,7 @@ struct TargetShardDaemonForwardRequest { int ubatch = 0; bool want_argmax = false; bool want_logits = false; + InferencePhase semantic_phase = InferencePhase::Unspecified; const std::vector * boundary_activation = nullptr; const std::vector * token_ids = nullptr; }; diff --git a/server/src/deepseek4/deepseek4_backend.cpp b/server/src/deepseek4/deepseek4_backend.cpp index e9220d6e2..c4020415b 100644 --- a/server/src/deepseek4/deepseek4_backend.cpp +++ b/server/src/deepseek4/deepseek4_backend.cpp @@ -1,4 +1,5 @@ // DeepSeek4Backend implementation — AR-only decode, chunked prefill. +#include "deepseek4_roctx.h" #include "deepseek4_backend.h" #include "deepseek4_internal.h" @@ -1311,6 +1312,12 @@ int DeepSeek4Backend::do_prefill(const std::vector & tokens, int kv_offset, int snap_slot, int snap_pos) { + const InferencePhase phase = deepseek4_roctx_prefill_phase( + prefill_attention_mode_name(cfg_.prefill_mode)); + const DeepSeek4RoctxPhaseScope roctx_phase(phase); + const DeepSeek4RoctxRange roctx_range( + "ds4.prefill", + {phase, static_cast(tokens.size()), 0, w_.n_layer, cfg_.device.gpu}); // The all-hot layer-range path supports causal chunked prefill. The // optimized graph snapshots the previous raw SWA window, attends over // that snapshot plus the current ubatch, and commits only the final SWA @@ -1540,6 +1547,7 @@ bool DeepSeek4Backend::do_decode(int committed, int n_gen, const DaemonIO & io, const BudgetHook & budget_hook, bool * forced_close_out) { + const DeepSeek4RoctxPhaseScope roctx_phase(InferencePhase::Decode); if (forced_close_out) *forced_close_out = false; const bool timing = env_flag_enabled("DFLASH_DS4_TIMING"); const auto phase_t0 = Clock::now(); diff --git a/server/src/deepseek4/deepseek4_dspark_spec.cpp b/server/src/deepseek4/deepseek4_dspark_spec.cpp index 21596aa6c..31831578d 100644 --- a/server/src/deepseek4/deepseek4_dspark_spec.cpp +++ b/server/src/deepseek4/deepseek4_dspark_spec.cpp @@ -23,6 +23,7 @@ #include "deepseek4_dspark.h" #include "deepseek4_internal.h" +#include "deepseek4_roctx.h" #include "internal.h" #include "common/dspark_head.h" @@ -585,6 +586,7 @@ bool deepseek4_dspark_verify_forward(ggml_backend_t backend, MoeHybridStorage * moe_hybrid, MoeExpertComputeRuntime * expert_runtime, MoeHybridRoutingStats * routing_stats) { + const DeepSeek4RoctxPhaseScope roctx_phase(InferencePhase::Verify); std::vector hc_state; std::vector all_logits; std::vector last_logits; @@ -664,6 +666,12 @@ bool run_deepseek4_dspark_spec_decode( spec_env_flag("DFLASH_DS4_FULL_SNAP"); const bool seq_verify_mode = reference_exact || spec_env_flag("DFLASH_DS4_SEQ_VERIFY"); + const InferencePhase roctx_phase = reference_exact + ? InferencePhase::ReferenceExact + : (seq_verify_mode ? InferencePhase::Sequential : InferencePhase::Batched); + const DeepSeek4RoctxRange roctx_range( + "ds4.spec_decode", + {roctx_phase, n_gen, 0, target_w.n_layer, device}); const bool async_rollback = spec_env_flag("DFLASH_DS4_ASYNC_ROLLBACK"); const bool pinned_rollback = spec_env_flag("DFLASH_DS4_PINNED_ROLLBACK"); const bool draft_overlap_probe = diff --git a/server/src/deepseek4/deepseek4_graph.cpp b/server/src/deepseek4/deepseek4_graph.cpp index 88a8d8263..7014fafd4 100644 --- a/server/src/deepseek4/deepseek4_graph.cpp +++ b/server/src/deepseek4/deepseek4_graph.cpp @@ -10,6 +10,7 @@ #include "deepseek4_internal.h" #include "deepseek4_hc_cuda.h" +#include "deepseek4_roctx.h" #include "internal.h" #include "../common/step_graph.h" #include "../common/cuda_graph_overrides.h" @@ -6618,7 +6619,6 @@ bool deepseek4_step_layer_range( const int n_hc = w.n_hc; const int hc_dim = n_hc * n_embd; const bool is_last_shard = (layer_end >= w.n_layer); - const bool fused_hybrid_ready = moe_hybrid && !expert_runtime && moe_hybrid->materialized_cold_experts && @@ -6738,6 +6738,17 @@ bool deepseek4_step_layer_range( return true; } + // Emit only executable leaf ranges. The compressor-boundary wrapper above + // recursively invokes this function, and marking both parent and children + // would double-count the phase in external trace summaries. + const InferencePhase roctx_phase = deepseek4_roctx_layer_phase( + verify_hooks != nullptr, n_tokens, + deepseek4_roctx_prefill_phase( + prefill_attention_mode_name(cache.prefill_mode))); + const DeepSeek4RoctxRange roctx_range( + "ds4.layer_range", + {roctx_phase, n_tokens, layer_begin, layer_end, device}); + // Initialize HC state. // First shard (layer_begin=0): embed is token embeddings [n_embd × n_tokens], // replicate into n_hc streams. diff --git a/server/src/deepseek4/deepseek4_layer_split_adapter.cpp b/server/src/deepseek4/deepseek4_layer_split_adapter.cpp index dce8abcb0..6b1cd737c 100644 --- a/server/src/deepseek4/deepseek4_layer_split_adapter.cpp +++ b/server/src/deepseek4/deepseek4_layer_split_adapter.cpp @@ -5,6 +5,7 @@ // HC state is transferred between shards at the boundary. #include "deepseek4_layer_split_adapter.h" +#include "deepseek4_roctx.h" #include "deepseek4_internal.h" #include "common/layer_split_runtime.h" #include "common/gguf_inspect.h" @@ -543,6 +544,7 @@ bool DeepSeek4LayerSplitAdapter::run_mixed_forward( req.boundary_activation = &hidden_out; req.token_ids = &tokens; req.want_logits = (logits_out != nullptr); + req.semantic_phase = deepseek4_roctx_current_phase(); TargetShardForwardResponse resp; std::vector remote_logits; @@ -575,6 +577,16 @@ bool DeepSeek4LayerSplitAdapter::prefill( const std::vector & prompt, int base_pos, int & last_tok) { + const InferencePhase phase = shards_.empty() + ? InferencePhase::Exact + : deepseek4_roctx_prefill_phase( + prefill_attention_mode_name(shards_.front().cache.prefill_mode)); + const DeepSeek4RoctxPhaseScope roctx_phase(phase); + const int n_layers = shards_.empty() ? -1 : shards_.front().weights.n_layer; + const DeepSeek4RoctxRange roctx_range( + "ds4.prefill", + {phase, static_cast(prompt.size()), 0, n_layers, + cfg_.device.gpu}); const int chunk_size = cfg_.chunk > 0 ? cfg_.chunk : 512; const int n_prompt = (int)prompt.size(); @@ -600,6 +612,7 @@ bool DeepSeek4LayerSplitAdapter::decode_ar( const std::vector & history_prefix, std::vector & out_tokens, const DaemonIO & io) { + const DeepSeek4RoctxPhaseScope roctx_phase(InferencePhase::Decode); if (shards_.empty()) return false; const DeepSeek4Weights & w = shards_[0].weights; diff --git a/server/src/deepseek4/deepseek4_roctx.cpp b/server/src/deepseek4/deepseek4_roctx.cpp new file mode 100644 index 000000000..72c5741e1 --- /dev/null +++ b/server/src/deepseek4/deepseek4_roctx.cpp @@ -0,0 +1,227 @@ +#include "deepseek4_roctx.h" + +#include +#include +#include +#include +#include + +#if defined(DFLASH27B_BACKEND_HIP) +# if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +# else +# include +# endif +#endif + +namespace dflash::common { +namespace { + +thread_local InferencePhase current_phase = InferencePhase::Unspecified; + +bool equals_ignore_case(const char * lhs, const char * rhs) { + if (!lhs || !rhs) return false; + while (*lhs && *rhs) { + if (std::tolower(static_cast(*lhs)) != + std::tolower(static_cast(*rhs))) { + return false; + } + ++lhs; + ++rhs; + } + return *lhs == '\0' && *rhs == '\0'; +} + +#if defined(DFLASH27B_BACKEND_HIP) +# if defined(_WIN32) +void * platform_open() { + return reinterpret_cast(LoadLibraryA("roctx64.dll")); +} +DeepSeek4RoctxPush platform_find_push(void * handle) { + return reinterpret_cast( + GetProcAddress(reinterpret_cast(handle), "roctxRangePushA")); +} +DeepSeek4RoctxPop platform_find_pop(void * handle) { + return reinterpret_cast( + GetProcAddress(reinterpret_cast(handle), "roctxRangePop")); +} +void platform_close(void * handle) { + FreeLibrary(reinterpret_cast(handle)); +} +# else +void * platform_open() { + return dlopen("libroctx64.so", RTLD_LAZY | RTLD_LOCAL); +} +DeepSeek4RoctxPush platform_find_push(void * handle) { + return reinterpret_cast(dlsym(handle, "roctxRangePushA")); +} +DeepSeek4RoctxPop platform_find_pop(void * handle) { + return reinterpret_cast(dlsym(handle, "roctxRangePop")); +} +void platform_close(void * handle) { + dlclose(handle); +} +# endif +void platform_diagnose(const char * message) { + std::fprintf(stderr, "[deepseek4-roctx] %s\n", message); +} +#endif + +DeepSeek4RoctxCallbacks runtime_callbacks() { +#if defined(DFLASH27B_BACKEND_HIP) + static const DeepSeek4RoctxCallbacks callbacks = + deepseek4_roctx_load_callbacks( + true, {platform_open, platform_find_push, platform_find_pop, + platform_close, platform_diagnose}); +#else + static const DeepSeek4RoctxCallbacks callbacks{}; +#endif + return callbacks; +} + +DeepSeek4RoctxCallbacks configured_callbacks() { + // Serving configuration is process-scoped. Cache the disabled path too so + // hot layer-range calls do not repeatedly query the environment. + static const DeepSeek4RoctxCallbacks callbacks = [] { + if (!deepseek4_roctx_env_enabled(std::getenv("DFLASH_DS4_ROCTX"))) { + return DeepSeek4RoctxCallbacks{}; + } + return runtime_callbacks(); + }(); + return callbacks; +} + +void append_field(char * message, size_t capacity, size_t & used, + const char * format, const char * value) { + if (!value || !value[0] || used >= capacity) return; + const int written = std::snprintf(message + used, capacity - used, format, value); + if (written > 0) used += std::min(static_cast(written), capacity - used - 1); +} + +void append_field(char * message, size_t capacity, size_t & used, + const char * format, int value) { + if (value < 0 || used >= capacity) return; + const int written = std::snprintf(message + used, capacity - used, format, value); + if (written > 0) used += std::min(static_cast(written), capacity - used - 1); +} + +} // namespace + +bool deepseek4_roctx_env_enabled(const char * value) { + return value && (std::strcmp(value, "1") == 0 || + equals_ignore_case(value, "true") || + equals_ignore_case(value, "yes") || + equals_ignore_case(value, "on")); +} + +DeepSeek4RoctxCallbacks deepseek4_roctx_load_callbacks( + bool enabled, DeepSeek4RoctxLoader loader) { + if (!enabled) return {}; + if (!loader.open || !loader.find_push || !loader.find_pop || !loader.close) { + return {}; + } + + void * handle = loader.open(); + if (!handle) { + if (loader.diagnose) { + loader.diagnose( + "DFLASH_DS4_ROCTX is enabled, but the ROCTX library could not " + "be loaded; markers are disabled"); + } + return {}; + } + + const DeepSeek4RoctxPush push = loader.find_push(handle); + const DeepSeek4RoctxPop pop = loader.find_pop(handle); + if (!push || !pop) { + loader.close(handle); + if (loader.diagnose) { + loader.diagnose( + "DFLASH_DS4_ROCTX is enabled, but required ROCTX range symbols " + "are missing; markers are disabled"); + } + return {}; + } + + // Successful libraries remain resident for process lifetime because range + // callbacks can run on serving threads and during static teardown. + return {push, pop}; +} + +DeepSeek4RoctxPhaseScope::DeepSeek4RoctxPhaseScope(InferencePhase phase) + : previous_(current_phase) { + current_phase = phase; +} + +DeepSeek4RoctxPhaseScope::~DeepSeek4RoctxPhaseScope() { + current_phase = previous_; +} + +InferencePhase deepseek4_roctx_prefill_phase(const char * mode) { + if (mode && std::strcmp(mode, "exact") == 0) return InferencePhase::Exact; + if (mode && std::strcmp(mode, "dense") == 0) return InferencePhase::Dense; + if (mode && std::strcmp(mode, "sparse") == 0) return InferencePhase::Sparse; + return InferencePhase::Unspecified; +} + +InferencePhase deepseek4_roctx_current_phase() { + return current_phase; +} + +InferencePhase deepseek4_roctx_layer_phase( + bool verify, int n_tokens, InferencePhase prefill_phase) { + if (current_phase != InferencePhase::Unspecified) return current_phase; + if (verify) return InferencePhase::Verify; + return n_tokens == 1 ? InferencePhase::Unspecified : prefill_phase; +} + +const char * deepseek4_roctx_phase_name(InferencePhase phase) { + switch (phase) { + case InferencePhase::Exact: return "exact"; + case InferencePhase::Dense: return "dense"; + case InferencePhase::Sparse: return "sparse"; + case InferencePhase::Decode: return "decode"; + case InferencePhase::Verify: return "verify"; + case InferencePhase::ReferenceExact: return "reference_exact"; + case InferencePhase::Sequential: return "sequential"; + case InferencePhase::Batched: return "batched"; + case InferencePhase::Unspecified: return "unspecified"; + } + return "unspecified"; +} + +DeepSeek4RoctxRange::DeepSeek4RoctxRange( + const char * scope, const DeepSeek4RoctxMetadata & metadata) + : DeepSeek4RoctxRange(scope, metadata, true, configured_callbacks()) {} + +DeepSeek4RoctxRange::DeepSeek4RoctxRange( + const char * scope, const DeepSeek4RoctxMetadata & metadata, + bool enabled, DeepSeek4RoctxCallbacks callbacks) { + if (!enabled || !scope || !scope[0] || !callbacks.push || !callbacks.pop) return; + + // A stack buffer avoids allocation and per-layer string ownership. ROCTX + // consumes the message during push, so the buffer need not outlive this call. + char message[256]; + const int initial = std::snprintf(message, sizeof(message), "%s", scope); + size_t used = initial > 0 + ? std::min(static_cast(initial), sizeof(message) - 1) + : 0; + append_field(message, sizeof(message), used, " mode=%s", + deepseek4_roctx_phase_name(metadata.phase)); + append_field(message, sizeof(message), used, " tokens=%d", metadata.tokens); + append_field(message, sizeof(message), used, " layer_begin=%d", metadata.layer_begin); + append_field(message, sizeof(message), used, " layer_end=%d", metadata.layer_end); + append_field(message, sizeof(message), used, " device=%d", metadata.device); + + if (callbacks.push(message) >= 0) { + pop_ = callbacks.pop; + pushed_ = true; + } +} + +DeepSeek4RoctxRange::~DeepSeek4RoctxRange() { + if (pushed_ && pop_) pop_(); +} + +} // namespace dflash::common diff --git a/server/src/deepseek4/deepseek4_roctx.h b/server/src/deepseek4/deepseek4_roctx.h new file mode 100644 index 000000000..ebb66dcf0 --- /dev/null +++ b/server/src/deepseek4/deepseek4_roctx.h @@ -0,0 +1,80 @@ +// Optional ROCTX ranges for correlating DeepSeek4 host phases with rocprof. +// The facility is policy-neutral: it records semantic scopes only and never +// synchronizes a device or measures elapsed time. + +#pragma once + +#include "common/inference_phase.h" + +namespace dflash::common { + +struct DeepSeek4RoctxMetadata { + InferencePhase phase = InferencePhase::Unspecified; + int tokens = -1; + int layer_begin = -1; + int layer_end = -1; + int device = -1; +}; + +// Callback injection keeps range lifetime and metadata independently testable +// without requiring ROCm or loading ROCTX in unit tests. +struct DeepSeek4RoctxCallbacks { + int (*push)(const char * message) = nullptr; + int (*pop)() = nullptr; +}; + +using DeepSeek4RoctxPush = int (*)(const char * message); +using DeepSeek4RoctxPop = int (*)(); + +// Small loader seam used by the platform adapter and focused unit tests. +struct DeepSeek4RoctxLoader { + void * (*open)() = nullptr; + DeepSeek4RoctxPush (*find_push)(void * handle) = nullptr; + DeepSeek4RoctxPop (*find_pop)(void * handle) = nullptr; + void (*close)(void * handle) = nullptr; + void (*diagnose)(const char * message) = nullptr; +}; + +bool deepseek4_roctx_env_enabled(const char * value); +DeepSeek4RoctxCallbacks deepseek4_roctx_load_callbacks( + bool enabled, DeepSeek4RoctxLoader loader); + +// Propagates caller-owned semantics through tokenwise exact-prefill calls, +// where n_tokens == 1 alone cannot distinguish prefill from decode. +class DeepSeek4RoctxPhaseScope { +public: + explicit DeepSeek4RoctxPhaseScope(InferencePhase phase); + ~DeepSeek4RoctxPhaseScope(); + + DeepSeek4RoctxPhaseScope(const DeepSeek4RoctxPhaseScope &) = delete; + DeepSeek4RoctxPhaseScope & operator=(const DeepSeek4RoctxPhaseScope &) = delete; + +private: + InferencePhase previous_ = InferencePhase::Unspecified; +}; + +InferencePhase deepseek4_roctx_prefill_phase(const char * mode); +InferencePhase deepseek4_roctx_current_phase(); +InferencePhase deepseek4_roctx_layer_phase( + bool verify, int n_tokens, InferencePhase prefill_phase); +const char * deepseek4_roctx_phase_name(InferencePhase phase); + +class DeepSeek4RoctxRange { +public: + DeepSeek4RoctxRange(const char * scope, + const DeepSeek4RoctxMetadata & metadata = {}); + DeepSeek4RoctxRange(const char * scope, + const DeepSeek4RoctxMetadata & metadata, + bool enabled, + DeepSeek4RoctxCallbacks callbacks); + ~DeepSeek4RoctxRange(); + + DeepSeek4RoctxRange(const DeepSeek4RoctxRange &) = delete; + DeepSeek4RoctxRange & operator=(const DeepSeek4RoctxRange &) = delete; + +private: + int (*pop_)() = nullptr; + bool pushed_ = false; +}; + +} // namespace dflash::common diff --git a/server/src/deepseek4/deepseek4_target_shard_ipc_daemon.cpp b/server/src/deepseek4/deepseek4_target_shard_ipc_daemon.cpp index 655ff1e47..034650f34 100644 --- a/server/src/deepseek4/deepseek4_target_shard_ipc_daemon.cpp +++ b/server/src/deepseek4/deepseek4_target_shard_ipc_daemon.cpp @@ -6,6 +6,7 @@ #include "deepseek4_layer_split_adapter.h" #include "deepseek4_internal.h" +#include "deepseek4_roctx.h" #include "common/target_shard_ipc.h" #include "common/target_shard_ipc_daemon.h" @@ -216,6 +217,7 @@ int run_deepseek4_target_shard_ipc_daemon( callbacks.forward = [&](const TargetShardDaemonForwardRequest & req, TargetShardDaemonForwardResponse & resp) -> bool { + const DeepSeek4RoctxPhaseScope roctx_phase(req.semantic_phase); const int n_tokens = req.n_tokens; if (!req.boundary_activation || n_tokens <= 0 || req.base_pos < 0 || req.base_pos + n_tokens > max_ctx || diff --git a/server/test/test_deepseek4_roctx.cpp b/server/test/test_deepseek4_roctx.cpp new file mode 100644 index 000000000..678b99488 --- /dev/null +++ b/server/test/test_deepseek4_roctx.cpp @@ -0,0 +1,349 @@ +#include "deepseek4/deepseek4_roctx.h" +#include "common/io_utils.h" +#include "common/target_shard_ipc_daemon.h" + +#include +#include +#include +#include +#include +#include +#include + +#if !defined(_WIN32) +#include +#endif + +using namespace dflash::common; + +static_assert(std::is_same_v, int32_t>); +static_assert(std::is_same_v< + decltype(TargetShardDaemonForwardRequest{}.semantic_phase), InferencePhase>); + +namespace { + +int failures = 0; +std::vector events; +int push_result = 0; +int loader_open_calls = 0; +int loader_close_calls = 0; +int loader_diagnostic_calls = 0; +std::string loader_diagnostic; + +#define CHECK(condition) do { \ + if (!(condition)) { \ + ++failures; \ + std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + } \ +} while (0) + +int record_push(const char * message) { + events.emplace_back(std::string("push:") + message); + return push_result; +} + +int record_pop() { + events.emplace_back("pop"); + return 0; +} + +void * loader_open_fail() { + ++loader_open_calls; + return nullptr; +} + +void * loader_open_success() { + ++loader_open_calls; + return reinterpret_cast(1); +} + +DeepSeek4RoctxPush loader_find_push(void *) { + return record_push; +} + +DeepSeek4RoctxPush loader_find_push_missing(void *) { + return nullptr; +} + +DeepSeek4RoctxPop loader_find_pop(void *) { + return record_pop; +} + +void loader_close(void *) { + ++loader_close_calls; +} + +void loader_diagnose(const char * message) { + ++loader_diagnostic_calls; + loader_diagnostic = message; +} + +void reset_loader_state() { + loader_open_calls = 0; + loader_close_calls = 0; + loader_diagnostic_calls = 0; + loader_diagnostic.clear(); +} + +bool return_failure_with_range() { + const DeepSeek4RoctxRange range( + "ds4.layer_range", {InferencePhase::Verify, 4, 0, 43, 0}, true, + {record_push, record_pop}); + return false; +} + +void test_env_policy() { + CHECK(!deepseek4_roctx_env_enabled(nullptr)); + CHECK(!deepseek4_roctx_env_enabled("")); + CHECK(!deepseek4_roctx_env_enabled("0")); + CHECK(!deepseek4_roctx_env_enabled("false-ish")); + CHECK(deepseek4_roctx_env_enabled("1")); + CHECK(deepseek4_roctx_env_enabled("true")); + CHECK(deepseek4_roctx_env_enabled("YES")); + CHECK(deepseek4_roctx_env_enabled("On")); +} + +void test_exact_prefill_phase_label() { + CHECK(deepseek4_roctx_layer_phase( + false, 1, InferencePhase::Exact) == InferencePhase::Unspecified); + { + const DeepSeek4RoctxPhaseScope prefill(InferencePhase::Exact); + CHECK(deepseek4_roctx_layer_phase( + false, 1, InferencePhase::Exact) == InferencePhase::Exact); + { + const DeepSeek4RoctxPhaseScope decode(InferencePhase::Decode); + CHECK(deepseek4_roctx_current_phase() == InferencePhase::Decode); + } + CHECK(deepseek4_roctx_current_phase() == InferencePhase::Exact); + CHECK(deepseek4_roctx_layer_phase( + true, 1, InferencePhase::Exact) == InferencePhase::Exact); + } + CHECK(deepseek4_roctx_current_phase() == InferencePhase::Unspecified); +} + +void test_phase_mapping_and_wire_roundtrip() { + const InferencePhase phases[] = { + InferencePhase::Unspecified, + InferencePhase::Exact, + InferencePhase::Dense, + InferencePhase::Sparse, + InferencePhase::Decode, + InferencePhase::Verify, + InferencePhase::ReferenceExact, + InferencePhase::Sequential, + InferencePhase::Batched, + }; + const char * names[] = { + "unspecified", "exact", "dense", "sparse", "decode", "verify", + "reference_exact", "sequential", "batched", + }; + for (size_t i = 0; i < sizeof(phases) / sizeof(phases[0]); ++i) { + InferencePhase decoded = InferencePhase::Unspecified; + CHECK(inference_phase_from_wire_value( + inference_phase_wire_value(phases[i]), decoded)); + CHECK(decoded == phases[i]); + CHECK(std::string(deepseek4_roctx_phase_name(decoded)) == names[i]); + } + InferencePhase decoded = InferencePhase::Exact; + CHECK(!inference_phase_from_wire_value(-1, decoded)); + CHECK(!inference_phase_from_wire_value(9, decoded)); + + TargetShardDaemonForwardRequest remote_request; + remote_request.semantic_phase = InferencePhase::Decode; + { + const DeepSeek4RoctxPhaseScope remote_phase(remote_request.semantic_phase); + CHECK(deepseek4_roctx_current_phase() == InferencePhase::Decode); + } + CHECK(deepseek4_roctx_current_phase() == InferencePhase::Unspecified); +} + +#if !defined(_WIN32) +void test_malformed_phase_does_not_desynchronize_pipe(const char * malformed_phase) { + int payload_fds[2] = {-1, -1}; + int response_fds[2] = {-1, -1}; + CHECK(pipe(payload_fds) == 0); + CHECK(pipe(response_fds) == 0); + if (payload_fds[0] < 0 || response_fds[0] < 0) return; + + const float malformed_activation[] = {1.0f, 2.0f}; + const int32_t malformed_token = 11; + const float valid_activation[] = {3.0f, 4.0f}; + const int32_t valid_token = 22; + CHECK(write_exact_fd(payload_fds[1], malformed_activation, + sizeof(malformed_activation))); + CHECK(write_exact_fd(payload_fds[1], &malformed_token, + sizeof(malformed_token))); + CHECK(write_exact_fd(payload_fds[1], valid_activation, + sizeof(valid_activation))); + CHECK(write_exact_fd(payload_fds[1], &valid_token, sizeof(valid_token))); + close(payload_fds[1]); + + std::ostringstream command_text; + command_text << "forward_pipe 0 1 0 0 8 1 1 1" << malformed_phase << '\n'; + command_text << "forward_pipe 1 1 0 0 8 1 1 1 " + << inference_phase_wire_value(InferencePhase::Decode) << '\n'; + command_text << "quit\n"; + std::istringstream commands(command_text.str()); + + int callback_calls = 0; + TargetShardDaemonCallbacks callbacks; + callbacks.forward = [&](const TargetShardDaemonForwardRequest & request, + TargetShardDaemonForwardResponse & response) { + ++callback_calls; + CHECK(request.semantic_phase == InferencePhase::Decode); + CHECK(request.base_pos == 1); + CHECK(request.boundary_activation && + *request.boundary_activation == std::vector({3.0f, 4.0f})); + CHECK(request.token_ids && + *request.token_ids == std::vector({22})); + response.last_tok = 77; + return true; + }; + + std::streambuf * previous_input = std::cin.rdbuf(commands.rdbuf()); + std::cin.clear(); + const int rc = run_target_shard_ipc_daemon_loop( + 2, 8, response_fds[1], payload_fds[0], -1, 0, std::move(callbacks)); + std::cin.rdbuf(previous_input); + std::cin.clear(); + close(payload_fds[0]); + close(response_fds[1]); + + int32_t responses[4] = {}; + CHECK(read_exact_fd(response_fds[0], responses, sizeof(responses))); + close(response_fds[0]); + CHECK(rc == 0); + CHECK(callback_calls == 1); + CHECK(responses[0] == 0); + CHECK(responses[1] == -1); + CHECK(responses[2] == 0); + CHECK(responses[3] == 77); +} + +void test_malformed_phases_do_not_desynchronize_pipe() { + test_malformed_phase_does_not_desynchronize_pipe(""); + test_malformed_phase_does_not_desynchronize_pipe(" 4 extra"); + test_malformed_phase_does_not_desynchronize_pipe(" 99"); +} +#endif + +void test_disabled_loader_is_silent_and_unopened() { + reset_loader_state(); + const DeepSeek4RoctxCallbacks callbacks = deepseek4_roctx_load_callbacks( + false, {loader_open_success, loader_find_push, loader_find_pop, + loader_close, loader_diagnose}); + CHECK(!callbacks.push && !callbacks.pop); + CHECK(loader_open_calls == 0); + CHECK(loader_close_calls == 0); + CHECK(loader_diagnostic_calls == 0); +} + +void test_missing_library_is_diagnosed() { + reset_loader_state(); + const DeepSeek4RoctxCallbacks callbacks = deepseek4_roctx_load_callbacks( + true, {loader_open_fail, loader_find_push, loader_find_pop, + loader_close, loader_diagnose}); + CHECK(!callbacks.push && !callbacks.pop); + CHECK(loader_open_calls == 1); + CHECK(loader_close_calls == 0); + CHECK(loader_diagnostic_calls == 1); + CHECK(loader_diagnostic.find("library could not be loaded") != + std::string::npos); +} + +void test_missing_symbol_closes_and_is_diagnosed() { + reset_loader_state(); + const DeepSeek4RoctxCallbacks callbacks = deepseek4_roctx_load_callbacks( + true, {loader_open_success, loader_find_push_missing, loader_find_pop, + loader_close, loader_diagnose}); + CHECK(!callbacks.push && !callbacks.pop); + CHECK(loader_open_calls == 1); + CHECK(loader_close_calls == 1); + CHECK(loader_diagnostic_calls == 1); + CHECK(loader_diagnostic.find("range symbols are missing") != + std::string::npos); +} + +void test_disabled_is_silent() { + events.clear(); + { + const DeepSeek4RoctxRange range( + "ds4.prefill", {InferencePhase::Exact, 8, 0, 43, 0}, false, + {record_push, record_pop}); + } + CHECK(events.empty()); +} + +void test_metadata_and_balance() { + events.clear(); + push_result = 0; + { + const DeepSeek4RoctxRange range( + "ds4.layer_range", {InferencePhase::Exact, 4, 2, 17, 1}, true, + {record_push, record_pop}); + CHECK(events.size() == 1); + CHECK(events[0] == + "push:ds4.layer_range mode=exact tokens=4 layer_begin=2 layer_end=17 device=1"); + } + CHECK(events.size() == 2); + CHECK(events[1] == "pop"); +} + +void test_failed_push_is_not_popped() { + events.clear(); + push_result = -1; + { + const DeepSeek4RoctxRange range( + "ds4.spec_decode", {InferencePhase::ReferenceExact, 32, -1, -1, 0}, true, + {record_push, record_pop}); + } + CHECK(events.size() == 1); + CHECK(events[0] == + "push:ds4.spec_decode mode=reference_exact tokens=32 device=0"); + push_result = 0; +} + +void test_early_failure_balances_range() { + events.clear(); + CHECK(!return_failure_with_range()); + CHECK(events.size() == 2); + CHECK(events[0] == + "push:ds4.layer_range mode=verify tokens=4 layer_begin=0 layer_end=43 device=0"); + CHECK(events[1] == "pop"); +} + +void test_missing_callback_is_silent() { + events.clear(); + { + const DeepSeek4RoctxRange no_push( + "ds4.prefill", {}, true, {nullptr, record_pop}); + const DeepSeek4RoctxRange no_pop( + "ds4.prefill", {}, true, {record_push, nullptr}); + } + CHECK(events.empty()); +} + +} // namespace + +int main() { + test_env_policy(); + test_exact_prefill_phase_label(); + test_phase_mapping_and_wire_roundtrip(); +#if !defined(_WIN32) + test_malformed_phases_do_not_desynchronize_pipe(); +#endif + test_disabled_loader_is_silent_and_unopened(); + test_missing_library_is_diagnosed(); + test_missing_symbol_closes_and_is_diagnosed(); + test_disabled_is_silent(); + test_metadata_and_balance(); + test_failed_push_is_not_popped(); + test_early_failure_balances_range(); + test_missing_callback_is_silent(); + if (failures) { + std::fprintf(stderr, "FAILED: %d assertion(s)\n", failures); + return 1; + } + std::fprintf(stderr, "deepseek4_roctx: ok\n"); + return 0; +}