Skip to content
Draft
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
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