Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions dflash/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ add_library(dflash_common STATIC
src/laguna/laguna_target_graph.cpp
src/laguna/laguna_daemon.cpp
src/laguna/laguna_backend.cpp
src/common/backend_ipc.cpp
src/common/dflash_feature_ring.cpp
src/common/dflash_capture.cpp
src/common/dflash_draft_ipc.cpp
Expand Down Expand Up @@ -671,6 +672,27 @@ if(DFLASH27B_TESTS)
endif()
endif()

if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/ipc/backend_ipc_main.cpp")
add_executable(backend_ipc_daemon
src/ipc/backend_ipc_main.cpp
)
target_include_directories(backend_ipc_daemon PRIVATE ${DFLASH27B_SRC_INCLUDE_DIRS})
if(DFLASH27B_GPU_BACKEND STREQUAL "hip")
target_compile_definitions(backend_ipc_daemon PRIVATE DFLASH27B_BACKEND_HIP=1 GGML_USE_HIP)
else()
target_compile_definitions(backend_ipc_daemon PRIVATE
DFLASH27B_BACKEND_CUDA=1
DFLASH27B_CUDA_MIN_SM=${_dflash_cuda_min_sm})
endif()
target_link_libraries(backend_ipc_daemon PRIVATE dflash_common ggml ${DFLASH27B_GGML_BACKEND_TARGET} pthread)
if(DFLASH27B_GPU_BACKEND STREQUAL "cuda")
find_package(CUDAToolkit REQUIRED)
target_link_libraries(backend_ipc_daemon PRIVATE CUDA::cudart)
else()
target_link_libraries(backend_ipc_daemon PRIVATE hip::host)
endif()
endif()

# Tokenizer test harness (no GPU needed — links static lib for tokenizer + GGUF reader)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/test_tokenizer_harness.cpp")
add_executable(test_tokenizer_harness test/test_tokenizer_harness.cpp)
Expand Down
16 changes: 8 additions & 8 deletions dflash/docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ dflash/src/
│ ├── snapshot_backend.h # Platform-aware snapshot backend selection
│ ├── dflash_target.h # DFlashTarget interface (spec decode)
│ ├── daemon_loop.{h,cpp} # Generic stdin/stdout daemon loop
│ ├── backend_ipc.{h,cpp} # Generic backend IPC process lifecycle
│ ├── device_placement.h # Multi-GPU placement config
│ ├── gguf_inspect.{h,cpp}# Read arch + layer count from GGUF
│ ├── layer_split_utils.{h,cpp} # compute_layer_ranges()
│ ├── dflash_layer_split_runtime.h # LayerSplitRuntimeConfig + ActivationPair
│ ├── dflash_feature_ring.{h,cpp} # DraftFeatureMirror + ring copy helpers
│ ├── dflash_capture.{h,cpp} # target_capture_index() helper
│ ├── dflash_draft_ipc.{h,cpp} # DFlash draft IPC client + remote copy
│ ├── dflash_draft_ipc_daemon.cpp # Generic DFlash draft IPC daemon body
│ ├── dflash_draft_ipc_daemon.cpp # DFlash draft mode for backend_ipc_daemon
│ ├── dflash_draft_graph.{h,cpp} # Universal build_draft_step (DFlash draft graph)
│ ├── dflash_spec_decode.{h,cpp} # Generic spec-decode loop over DFlashTarget
│ ├── ddtree.{h,cpp} # Dynamic Draft Tree algorithm
Expand Down Expand Up @@ -240,15 +241,14 @@ Key components:
Bridge qwen35 internals (`TargetWeights`, `TargetCache`,
`TargetLayerSplitShard`) to the generic `DFlashTarget` interface so the
shared spec-decode loop can drive verification.
- **Feature transfer + draft daemon** (`common/dflash_feature_ring.{h,cpp}`,
`common/dflash_capture.{h,cpp}`, `common/dflash_draft_ipc.{h,cpp}`,
`common/dflash_draft_ipc_daemon.cpp`):
- **Feature transfer + backend IPC daemon** (`common/backend_ipc.{h,cpp}`,
`common/dflash_feature_ring.{h,cpp}`, `common/dflash_capture.{h,cpp}`,
`common/dflash_draft_ipc.{h,cpp}`, `common/dflash_draft_ipc_daemon.cpp`):
Move captured target activations into the draft-side ring buffer
(`DraftFeatureMirror`) and ship them across processes/GPUs. The IPC
client, parent-side feature-slice helper, and the daemon body itself
all live in `common/` and are reusable by any DFlash target architecture
(the DFlash draft model is a single universal Qwen3-style network shared
across every target).
process lifecycle is shared through `backend_ipc`; the DFlash draft client,
parent-side feature-slice helper, and daemon mode stay on top of that common
process layer and remain reusable by any DFlash target architecture.

### Qwen3Backend, Gemma4Backend, LagunaBackend

Expand Down
9 changes: 5 additions & 4 deletions dflash/docs/MIXED_BACKEND.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ kept at host-data or process boundaries:
```bash
cmake -S . -B build-cuda -DCMAKE_BUILD_TYPE=Release \
-DDFLASH27B_GPU_BACKEND=cuda
cmake --build build-cuda --target pflash_daemon test_dflash -j
cmake --build build-cuda --target pflash_daemon test_dflash backend_ipc_daemon -j

cmake -S . -B build-hip -DCMAKE_BUILD_TYPE=Release \
-DDFLASH27B_GPU_BACKEND=hip \
-DDFLASH27B_HIP_ARCHITECTURES=<your-gfx-arch>
cmake --build build-hip --target pflash_daemon test_dflash -j
cmake --build build-hip --target pflash_daemon test_dflash backend_ipc_daemon -j
```

## PFlash phase split
Expand Down Expand Up @@ -69,13 +69,14 @@ python scripts/phase_split_dual_gpu.py run-prompt \

## DFlash draft split

For DFlash, the target process can launch a separate draft IPC daemon from a
For DFlash, the target process can launch a separate backend IPC daemon from a
different backend build. The target process keeps target execution and any
target layer split inside its own backend binary.

Use these `test_dflash` options for the target process:

- `--draft-ipc-bin <path>` points to the other backend's `test_dflash` binary.
- `--draft-ipc-bin <path>` points to the other backend's `backend_ipc_daemon`
binary.
- `--draft-ipc-gpu <id>` selects the draft daemon device in that backend's
visible-device namespace.
- `--draft-ipc-work-dir <path>` selects where temporary IPC payload files are
Expand Down
5 changes: 5 additions & 0 deletions dflash/src/common/backend_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ std::string detect_arch(const char * model_path) {
return info.arch;
}

bool arch_supports_remote_draft(const std::string & arch) {
return arch == "qwen35";
}

std::unique_ptr<ModelBackend> create_backend(const BackendArgs & args) {
if (!args.model_path) {
std::fprintf(stderr, "[backend_factory] model_path is null\n");
Expand All @@ -38,6 +42,7 @@ std::unique_ptr<ModelBackend> create_backend(const BackendArgs & args) {
cfg.draft_path = args.draft_path;
cfg.device = args.device;
cfg.draft_gpu = args.draft_device.gpu;
cfg.remote_draft = args.remote_draft;
cfg.stream_fd = args.stream_fd;
cfg.fa_window = args.fa_window;
cfg.kq_stride_pad = args.kq_stride_pad;
Expand Down
4 changes: 4 additions & 0 deletions dflash/src/common/backend_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "model_backend.h"
#include "placement/placement_config.h"
#include "placement/remote_draft_config.h"

#include <memory>
#include <string>
Expand All @@ -31,6 +32,7 @@ struct BackendArgs {
// Device placement
DevicePlacement device;
DevicePlacement draft_device;
RemoteDraftConfig remote_draft;

// I/O — only used when running under daemon_loop (legacy). The new
// server passes -1 and uses on_token callbacks instead.
Expand Down Expand Up @@ -62,4 +64,6 @@ std::unique_ptr<ModelBackend> create_backend(const BackendArgs & args);
// Useful for early dispatch (e.g. printing which backend will be used).
std::string detect_arch(const char * model_path);

bool arch_supports_remote_draft(const std::string & arch);

} // namespace dflash::common
182 changes: 182 additions & 0 deletions dflash/src/common/backend_ipc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// backend_ipc.cpp - generic backend IPC process launcher.

#include "backend_ipc.h"
#include "io_utils.h"

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <vector>

#if !defined(_WIN32)
# include <cerrno>
# include <cstring>
# include <sys/stat.h>
# include <sys/wait.h>
# include <unistd.h>
#endif

namespace dflash::common {

const char * backend_ipc_mode_name(BackendIpcMode mode) {
switch (mode) {
case BackendIpcMode::DFlashDraft: return "dflash-draft";
}
return "unknown";
}

bool parse_backend_ipc_mode(const std::string & value, BackendIpcMode & out) {
if (value == "dflash-draft") {
out = BackendIpcMode::DFlashDraft;
return true;
}
return false;
}

bool BackendIpcProcess::start(const BackendIpcLaunchConfig & cfg) {
#if defined(_WIN32)
(void)cfg;
std::fprintf(stderr, "Backend IPC is only implemented on POSIX hosts\n");
return false;
#else
close();
if (cfg.bin.empty() || cfg.payload_path.empty()) return false;
if (!init_work_dir(cfg.work_dir)) return false;

int cmd_pipe[2] = {-1, -1};
int stream_pipe[2] = {-1, -1};
if (::pipe(cmd_pipe) != 0 || ::pipe(stream_pipe) != 0) {
std::fprintf(stderr, "backend-ipc pipe failed: %s\n", std::strerror(errno));
if (cmd_pipe[0] >= 0) ::close(cmd_pipe[0]);
if (cmd_pipe[1] >= 0) ::close(cmd_pipe[1]);
if (stream_pipe[0] >= 0) ::close(stream_pipe[0]);
if (stream_pipe[1] >= 0) ::close(stream_pipe[1]);
return false;
}

pid_ = ::fork();
if (pid_ < 0) {
std::fprintf(stderr, "backend-ipc fork failed: %s\n", std::strerror(errno));
::close(cmd_pipe[0]); ::close(cmd_pipe[1]);
::close(stream_pipe[0]); ::close(stream_pipe[1]);
pid_ = -1;
return false;
}
if (pid_ == 0) {
if (cmd_pipe[0] != STDIN_FILENO && ::dup2(cmd_pipe[0], STDIN_FILENO) < 0) {
std::fprintf(stderr, "backend-ipc dup2 failed: %s\n", std::strerror(errno));
_exit(127);
}
if (cmd_pipe[0] != STDIN_FILENO) ::close(cmd_pipe[0]);
::close(cmd_pipe[1]);
::close(stream_pipe[0]);

std::vector<std::string> argv_storage;
argv_storage.reserve(cfg.args.size() + 5);
argv_storage.emplace_back(cfg.bin);
argv_storage.emplace_back(
std::string("--backend-ipc-mode=") + backend_ipc_mode_name(cfg.mode));
argv_storage.emplace_back(cfg.payload_path);
for (const std::string & arg : cfg.args) argv_storage.emplace_back(arg);
argv_storage.emplace_back("--stream-fd=" + std::to_string(stream_pipe[1]));

std::vector<char *> argv;
argv.reserve(argv_storage.size() + 1);
for (std::string & arg : argv_storage) argv.push_back(arg.data());
argv.push_back(nullptr);
::execv(cfg.bin.c_str(), argv.data());
std::fprintf(stderr, "backend-ipc exec failed: %s: %s\n",
cfg.bin.c_str(), std::strerror(errno));
_exit(127);
}

::close(cmd_pipe[0]);
::close(stream_pipe[1]);
stream_fd_ = stream_pipe[0];
cmd_ = ::fdopen(cmd_pipe[1], "w");
if (!cmd_) {
std::fprintf(stderr, "backend-ipc fdopen failed: %s\n", std::strerror(errno));
::close(cmd_pipe[1]);
close();
return false;
}
int32_t status = -1;
if (!read_exact_fd(stream_fd_, &status, sizeof(status)) || status != 0) {
std::fprintf(stderr, "backend-ipc daemon did not become ready (status=%d)\n", status);
close();
return false;
}
active_ = true;
std::printf("[backend-ipc] ready mode=%s bin=%s work_dir=%s\n",
backend_ipc_mode_name(cfg.mode), cfg.bin.c_str(), work_dir_.c_str());
return true;
#endif
}

void BackendIpcProcess::close() {
#if !defined(_WIN32)
if (cmd_) {
std::fclose(cmd_);
cmd_ = nullptr;
}
if (stream_fd_ >= 0) {
::close(stream_fd_);
stream_fd_ = -1;
}
if (pid_ > 0) {
int status = 0;
::waitpid(pid_, &status, 0);
pid_ = -1;
}
if (owns_work_dir_ && !work_dir_.empty()) {
::rmdir(work_dir_.c_str());
}
#endif
active_ = false;
owns_work_dir_ = false;
work_dir_.clear();
seq_ = 0;
}

std::string BackendIpcProcess::next_path(const char * prefix) {
return work_dir_ + "/" + prefix + "_" + std::to_string(seq_++) + ".bin";
}

#if !defined(_WIN32)
bool BackendIpcProcess::init_work_dir(const std::string & requested) {
if (!requested.empty()) {
work_dir_ = requested;
owns_work_dir_ = false;
if (::mkdir(work_dir_.c_str(), 0700) != 0) {
if (errno != EEXIST) {
std::fprintf(stderr, "backend-ipc mkdir failed: %s: %s\n",
work_dir_.c_str(), std::strerror(errno));
return false;
}
struct stat st;
if (::stat(work_dir_.c_str(), &st) != 0 || !S_ISDIR(st.st_mode)) {
std::fprintf(stderr, "backend-ipc work_dir is not a directory: %s\n",
work_dir_.c_str());
return false;
}
}
return true;
}
const char * tmp = std::getenv("TMPDIR");
std::string templ = std::string(tmp && *tmp ? tmp : "/tmp") +
"/backend-ipc-XXXXXX";
std::vector<char> buf(templ.begin(), templ.end());
buf.push_back('\0');
char * dir = ::mkdtemp(buf.data());
if (!dir) {
std::fprintf(stderr, "backend-ipc mkdtemp failed: %s\n", std::strerror(errno));
return false;
}
work_dir_ = dir;
owns_work_dir_ = true;
return true;
}
#endif

} // namespace dflash::common
65 changes: 65 additions & 0 deletions dflash/src/common/backend_ipc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// backend_ipc.h - generic backend IPC process launcher.
//
// Owns the out-of-process backend daemon lifecycle: fork/exec, command pipe,
// binary status stream, and scratch work directory. Individual IPC modes keep
// their own payload protocol on top of this process wrapper.

#pragma once

#include <cstdio>
#include <string>
#include <vector>

#if !defined(_WIN32)
# include <sys/types.h>
#endif

namespace dflash::common {

enum class BackendIpcMode {
DFlashDraft,
};

const char * backend_ipc_mode_name(BackendIpcMode mode);
bool parse_backend_ipc_mode(const std::string & value, BackendIpcMode & out);

struct BackendIpcLaunchConfig {
std::string bin;
BackendIpcMode mode = BackendIpcMode::DFlashDraft;
std::string payload_path;
std::vector<std::string> args;
std::string work_dir;
};

class BackendIpcProcess {
public:
BackendIpcProcess() = default;
BackendIpcProcess(const BackendIpcProcess &) = delete;
BackendIpcProcess & operator=(const BackendIpcProcess &) = delete;
~BackendIpcProcess() { close(); }

bool start(const BackendIpcLaunchConfig & cfg);
void close();

bool active() const { return active_; }
FILE * command_stream() const { return cmd_; }
int stream_fd() const { return stream_fd_; }
const std::string & work_dir() const { return work_dir_; }

std::string next_path(const char * prefix);

private:
#if !defined(_WIN32)
bool init_work_dir(const std::string & requested);

pid_t pid_ = -1;
#endif
FILE * cmd_ = nullptr;
int stream_fd_ = -1;
std::string work_dir_;
int seq_ = 0;
bool owns_work_dir_ = false;
bool active_ = false;
};

} // namespace dflash::common
Loading
Loading