Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 6 additions & 1 deletion examples/quickstart-cpp/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
build
build/
__pycache__/
*.py[cod]
.pytest_cache/
.venv/
venv/
138 changes: 81 additions & 57 deletions examples/quickstart-cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,80 +1,104 @@
cmake_minimum_required(VERSION 3.16)
project(SimpleCppFlowerClient VERSION 0.10
DESCRIPTION "Creates a Simple C++ Flower client that trains a linear model on synthetic data."
project(SimpleCppFlowerClient
VERSION 1.0
DESCRIPTION "Creates a simple C++ Flower client that trains a linear model on synthetic data."
LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
option(USE_LOCAL_FLWR "Use local Flower directory instead of fetching from GitHub" OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

######################
### Download gRPC
set(FLWR_SOURCE_ROOT "" CACHE PATH "Path to the Flower repository root")
set(GRPC_CPP_PLUGIN_EXECUTABLE "" CACHE FILEPATH "Path to grpc_cpp_plugin")

include(FetchContent)
FetchContent_Declare(
gRPC
GIT_REPOSITORY https://github.com/grpc/grpc
GIT_TAG v1.43.2
)
set(FETCHCONTENT_QUIET OFF)
FetchContent_MakeAvailable(gRPC)
find_package(Threads REQUIRED)
find_package(Protobuf REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(gRPC CONFIG QUIET)
find_package(PkgConfig QUIET)

set(_PROTOBUF_LIBPROTOBUF libprotobuf)
set(_REFLECTION grpc++_reflection)
set(_PROTOBUF_PROTOC $<TARGET_FILE:protoc>)
set(_GRPC_GRPCPP grpc++)
if(CMAKE_CROSSCOMPILING)
find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
if(TARGET protobuf::libprotobuf)
set(QUICKSTART_PROTOBUF_LIB protobuf::libprotobuf)
else()
set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:grpc_cpp_plugin>)
set(QUICKSTART_PROTOBUF_LIB ${Protobuf_LIBRARIES})
endif()

######################
### FLWR_LIB
if(USE_LOCAL_FLWR)
set(FLWR_SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../..")
if(gRPC_FOUND)
set(QUICKSTART_GRPC_LIB gRPC::grpc++)
elseif(PkgConfig_FOUND)
pkg_check_modules(GRPCPP REQUIRED IMPORTED_TARGET grpc++)
set(QUICKSTART_GRPC_LIB PkgConfig::GRPCPP)
else()
FetchContent_Declare(
flwr_repo
GIT_REPOSITORY https://github.com/flwrlabs/flower.git
GIT_TAG main
)
message(FATAL_ERROR "Could not locate gRPC. Install gRPC C++ or provide a CMake package.")
endif()

FetchContent_GetProperties(flwr_repo)
if(NOT flwr_repo_POPULATED)
FetchContent_Populate(flwr_repo)
endif()
if(NOT GRPC_CPP_PLUGIN_EXECUTABLE)
find_program(GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
endif()
if(NOT GRPC_CPP_PLUGIN_EXECUTABLE)
message(FATAL_ERROR "Could not find grpc_cpp_plugin")
endif()

set(FLWR_SOURCE_ROOT "${flwr_repo_SOURCE_DIR}")
if(FLWR_SOURCE_ROOT)
get_filename_component(FLWR_SOURCE_ROOT "${FLWR_SOURCE_ROOT}" ABSOLUTE)
else()
get_filename_component(FLWR_SOURCE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../.." ABSOLUTE)
endif()

set(FLWR_SDK_PATH "${FLWR_SOURCE_ROOT}/framework/cc/flwr")
set(FLWR_PROTO_ROOT "${FLWR_SOURCE_ROOT}/framework/proto")
if(NOT EXISTS "${FLWR_SDK_PATH}/include/client.h")
message(FATAL_ERROR "Could not find Flower C++ SDK headers at ${FLWR_SDK_PATH}")
endif()
if(NOT EXISTS "${FLWR_PROTO_ROOT}/flwr/proto/fleet.proto")
message(FATAL_ERROR "Could not find Flower proto sources at ${FLWR_PROTO_ROOT}")
endif()

file(GLOB FLWR_SRCS "${FLWR_SDK_PATH}/src/*.cc")
file(GLOB FLWR_PROTO_SRCS "${FLWR_SDK_PATH}/include/flwr/proto/*.cc")
set(FLWR_INCLUDE_DIR "${FLWR_SDK_PATH}/include")

add_library(flwr ${FLWR_SRCS} ${FLWR_PROTO_SRCS})
set(GENERATED_PROTO_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
file(GLOB FLWR_PROTO_FILES CONFIGURE_DEPENDS "${FLWR_PROTO_ROOT}/flwr/proto/*.proto")
set(GENERATED_PROTO_SRCS)
set(GENERATED_PROTO_HDRS)

target_link_libraries(flwr
${_REFLECTION}
${_GRPC_GRPCPP}
${_PROTOBUF_LIBPROTOBUF}
)
foreach(PROTO_FILE ${FLWR_PROTO_FILES})
get_filename_component(PROTO_NAME "${PROTO_FILE}" NAME_WE)
set(PB_CC "${GENERATED_PROTO_DIR}/flwr/proto/${PROTO_NAME}.pb.cc")
set(PB_H "${GENERATED_PROTO_DIR}/flwr/proto/${PROTO_NAME}.pb.h")
set(GRPC_CC "${GENERATED_PROTO_DIR}/flwr/proto/${PROTO_NAME}.grpc.pb.cc")
set(GRPC_H "${GENERATED_PROTO_DIR}/flwr/proto/${PROTO_NAME}.grpc.pb.h")

target_include_directories(flwr PUBLIC
${FLWR_INCLUDE_DIR}
)
add_custom_command(
OUTPUT "${PB_CC}" "${PB_H}" "${GRPC_CC}" "${GRPC_H}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${GENERATED_PROTO_DIR}"
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
--proto_path=${FLWR_PROTO_ROOT}
--cpp_out=${GENERATED_PROTO_DIR}
--grpc_out=${GENERATED_PROTO_DIR}
--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN_EXECUTABLE}
"${PROTO_FILE}"
DEPENDS "${PROTO_FILE}"
VERBATIM)

######################
### FLWR_CLIENT
file(GLOB FLWR_CLIENT_SRCS src/*.cc)
set(EXECUTABLE_NAME flwr_client)
add_executable(${EXECUTABLE_NAME} ${FLWR_CLIENT_SRCS})
list(APPEND GENERATED_PROTO_SRCS "${PB_CC}" "${GRPC_CC}")
list(APPEND GENERATED_PROTO_HDRS "${PB_H}" "${GRPC_H}")
endforeach()

target_include_directories(${EXECUTABLE_NAME} PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
${FLWR_INCLUDE_DIR}
add_library(flwr_proto STATIC ${GENERATED_PROTO_SRCS} ${GENERATED_PROTO_HDRS})
target_include_directories(flwr_proto PUBLIC
"${GENERATED_PROTO_DIR}"
${Protobuf_INCLUDE_DIRS}
)
target_link_libraries(flwr_proto PUBLIC
${QUICKSTART_GRPC_LIB}
${QUICKSTART_PROTOBUF_LIB}
Threads::Threads
)

target_link_libraries(${EXECUTABLE_NAME}
flwr
file(GLOB FLWR_CLIENT_SRCS CONFIGURE_DEPENDS src/*.cc)
add_executable(flwr_client ${FLWR_CLIENT_SRCS})
target_include_directories(flwr_client PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
"${GENERATED_PROTO_DIR}"
"${FLWR_SDK_PATH}/include"
)
target_link_libraries(flwr_client PRIVATE flwr_proto ${QUICKSTART_GRPC_LIB})
target_link_libraries(flwr_client PRIVATE OpenSSL::Crypto)
58 changes: 47 additions & 11 deletions examples/quickstart-cpp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,70 @@ dataset: [Synthetic]
framework: [C++]
---

# Flower Clients in C++ (under development)
# Flower Clients in C++

> [!WARNING]\
> This example is compatible with `flwr<1.13.0`. We are currently updating it to the newer `flwr run` way of running Flower Apps.
In this example you will train a linear model on synthetic data using external
C++ clients and a Python `ServerApp`.

In this example you will train a linear model on synthetic data using C++ clients.
This quickstart uses the Flower `grpc-rere` Fleet API used by recent Flower
versions. The C++ clients connect to a running SuperLink, receive train and
evaluate messages, serialize replies with Flower `RecordDict` payloads, and
push the resulting objects back through the SuperLink object store.

## Acknowledgements

Many thanks to the original contributors to this code:

- Lekang Jiang (original author and main contributor)
- Francisco José Solís (code re-organization)
- Francisco Jose Solis (code re-organization)
- Andreea Zaharia (training algorithm and data generation)

## Install requirements

You'll need CMake and Python with `flwr` installed.
You'll need Python with `flwr>=1.31.0`, CMake, a C++17 compiler, gRPC C++,
protobuf, `protoc`, `grpc_cpp_plugin`, and OpenSSL.

### Building the example
Install the Python dependencies from this directory:

This example provides you with a `CMakeLists.txt` file to configure and build the client. Feel free to take a look inside it to see what is happening under the hood.
```bash
python -m pip install -e .
```

## Building the example

This example provides a `CMakeLists.txt` file to configure and build the C++
client.

From `examples/quickstart-cpp` inside a Flower checkout:

```bash
cmake -S . -B build
cmake --build build
cmake --build build -j
```

If this directory is built outside the Flower repository, pass the Flower source
tree explicitly:

```bash
cmake -S . -B build -DFLWR_SOURCE_ROOT=/path/to/flower
cmake --build build -j
```

If gRPC/protobuf are installed in a custom prefix:

```bash
export CMAKE_PREFIX_PATH=/path/to/grpc-prefix
export PATH=/path/to/grpc-prefix/bin:$PATH
cmake -S . -B build \
-DFLWR_SOURCE_ROOT=/path/to/flower \
-DGRPC_CPP_PLUGIN_EXECUTABLE=/path/to/grpc_cpp_plugin
cmake --build build -j
```

## Run the `Flower SuperLink`, the two clients, and the `Flower ServerApp` in separate terminals

```bash
flwr-superlink --insecure
flower-superlink --insecure
```

```bash
Expand All @@ -47,5 +79,9 @@ build/flwr_client 1 127.0.0.1:9092
```

```bash
flower-server-app server:app --insecure
flwr run . --stream
```

The `client.py` file only provides a placeholder `ClientApp` entry point for
Flower App metadata. The actual training clients are the external C++
`build/flwr_client` processes.
19 changes: 19 additions & 0 deletions examples/quickstart-cpp/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Placeholder ClientApp for the C++ quickstart.

The actual clients in this example are external C++ processes. Flower App
metadata still requires a ClientApp entry point in recent Flower versions, so
this module exists to make that boundary explicit.
"""

import flwr as fl
from flwr.common import Context


def client_fn(context: Context):
raise RuntimeError(
"This quickstart uses external C++ clients. Start build/flwr_client "
"processes instead of executing the Python ClientApp."
)


app = fl.client.ClientApp(client_fn=client_fn)
Loading