From a649bc355701bbf7f9dbc236389e34a436a12399 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 12 Feb 2026 00:36:14 +0000 Subject: [PATCH 1/2] Fix namespace references in api_client.cc Qualify color constants and APIClient with the gcpp namespace in gemma/api_client.cc to resolve potential symbol lookup issues. --- gemma/api_client.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gemma/api_client.cc b/gemma/api_client.cc index e6ce1913..23c54be8 100644 --- a/gemma/api_client.cc +++ b/gemma/api_client.cc @@ -394,11 +394,11 @@ int main(int argc, char* argv[]) { client_args.port = 443; } - std::cout << BOLD << YELLOW << "🚀 Testing API Server at " << client_args.host - << ":" << client_args.port << RESET << std::endl; + std::cout << gcpp::BOLD << gcpp::YELLOW << "🚀 Testing API Server at " << client_args.host + << ":" << client_args.port << gcpp::RESET << std::endl; try { - APIClient client(client_args.host, client_args.port, client_args.api_key, + gcpp::APIClient client(client_args.host, client_args.port, client_args.api_key, client_args.model); if (client_args.interactive) { @@ -408,7 +408,7 @@ int main(int argc, char* argv[]) { client.TestGenerateContent(client_args.prompt, true); } } catch (const std::exception& e) { - std::cerr << RED << "❌ Error: " << e.what() << RESET << std::endl; + std::cerr << gcpp::RED << "❌ Error: " << e.what() << gcpp::RESET << std::endl; std::cerr << "Make sure the API server is running:" << std::endl; std::cerr << " ./build/gemma_api_server --tokenizer --weights " From 94648da6f24d7615f7aa078d1b29288a7beb26eb Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 12 Feb 2026 00:42:38 +0000 Subject: [PATCH 2/2] Support building with Emscripten Update CMake configuration and utility functions to enable compilation with Emscripten. This includes setting Wasm-specific flags like memory64 and SIMD, implementing platform-specific memory detection, and adding guards for features like OpenSSL that may be unavailable in a web environment. --- CMakeLists.txt | 16 +++++++++++++++ compression/types.h | 2 ++ gemma/api_client.cc | 49 ++++++++++++++++++++++++++++++++++++--------- util/allocator.cc | 9 +++++++++ util/threading.cc | 4 ++++ 5 files changed, 71 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47d7c4c2..a5eed3f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,19 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if(EMSCRIPTEN) + add_compile_options("-sMEMORY64") + add_compile_options("-msimd128") + add_compile_options("-pthread") + add_link_options("-sALLOW_MEMORY_GROWTH") + add_link_options("-sMAXIMUM_MEMORY=16GB") + add_link_options("-sNODERAWFS") + add_link_options("-sMEMORY64") + add_link_options("-sSTACK_SIZE=2MB") + add_link_options("-pthread") + add_link_options("-sPROXY_TO_PTHREAD") +endif() + FetchContent_Declare(highway GIT_REPOSITORY https://github.com/google/highway.git GIT_TAG 3b680cde3a556bead9cc23c8f595d07a44d5a0d5 EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(highway) @@ -60,6 +73,9 @@ set(BENCHMARK_ENABLE_GTEST_TESTS OFF) FetchContent_Declare(benchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.8.2 EXCLUDE_FROM_ALL) FetchContent_MakeAvailable(benchmark) +if(EMSCRIPTEN) + target_compile_options(benchmark PRIVATE -Wno-c2y-extensions) +endif() # Base source files set(SOURCES diff --git a/compression/types.h b/compression/types.h index dc22f4ca..cd26e078 100644 --- a/compression/types.h +++ b/compression/types.h @@ -50,6 +50,8 @@ namespace gcpp { // yet use any AVX 10.2 features. #define GEMMA_DISABLED_TARGETS \ (HWY_SCALAR | HWY_SSE2 | HWY_SSSE3 | HWY_SSE4 | HWY_AVX10_2) +#elif HWY_ARCH_WASM +#define GEMMA_DISABLED_TARGETS HWY_SCALAR #endif // HWY_ARCH_* #endif // GEMMA_DISABLED_TARGETS diff --git a/gemma/api_client.cc b/gemma/api_client.cc index 23c54be8..e7f99626 100644 --- a/gemma/api_client.cc +++ b/gemma/api_client.cc @@ -51,10 +51,15 @@ class APIClient { use_https_(port == 443), interactive_mode_(false) { if (use_https_) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT ssl_client_ = std::make_unique(host, port); ssl_client_->set_read_timeout(60, 0); ssl_client_->set_write_timeout(60, 0); ssl_client_->enable_server_certificate_verification(false); +#else + std::cerr << "Error: HTTPS requested but OpenSSL not found." << std::endl; + exit(1); +#endif } else { client_ = std::make_unique(host, port); client_->set_read_timeout(60, 0); @@ -109,8 +114,17 @@ class APIClient { if (!api_key_.empty()) { headers.emplace("X-goog-api-key", api_key_); } - auto res = use_https_ ? ssl_client_->Get("/v1beta/models", headers) - : client_->Get("/v1beta/models", headers); + httplib::Result res; + if (use_https_) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + res = ssl_client_->Get("/v1beta/models", headers); +#else + std::cerr << "Error: HTTPS requested but OpenSSL not found." << std::endl; + exit(1); +#endif + } else { + res = client_->Get("/v1beta/models", headers); + } if (res && res->status == 200) { json response = json::parse(res->body); @@ -213,11 +227,17 @@ class APIClient { if (!api_key_.empty()) { headers.emplace("X-goog-api-key", api_key_); } - - auto res = use_https_ ? ssl_client_->Post(endpoint, headers, request.dump(), - "application/json") - : client_->Post(endpoint, headers, request.dump(), - "application/json"); + httplib::Result res; + if (use_https_) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + res = ssl_client_->Post(endpoint, headers, request.dump(), "application/json"); +#else + std::cerr << "Error: HTTPS requested but OpenSSL not found." << std::endl; + exit(1); +#endif + } else { + res = client_->Post(endpoint, headers, request.dump(), "application/json"); + } if (res && res->status == 200) { json response = json::parse(res->body); @@ -300,8 +320,17 @@ class APIClient { httplib::Response res; httplib::Error error; - bool success = use_https_ ? ssl_client_->send(req, res, error) - : client_->send(req, res, error); + bool success = false; + if (use_https_) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + success = ssl_client_->send(req, res, error); +#else + std::cerr << "Error: HTTPS requested but OpenSSL not found." << std::endl; + exit(1); +#endif + } else { + success = client_->send(req, res, error); + } if (res.status == 200 && !accumulated_response.empty()) { return json{ @@ -322,7 +351,9 @@ class APIClient { private: std::unique_ptr client_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT std::unique_ptr ssl_client_; +#endif std::string host_; int port_; std::string api_key_; diff --git a/util/allocator.cc b/util/allocator.cc index 41084490..ecedba5d 100644 --- a/util/allocator.cc +++ b/util/allocator.cc @@ -101,6 +101,9 @@ size_t DetectPageSize() { size_t len = sizeof(data); HWY_ASSERT(sysctlbyname("vm.pagesize", &data, &len, nullptr, 0) == 0); return data; +#elif defined(__EMSCRIPTEN__) + // Pages in Wasm are always 64KiB. + return 65536; #else return 0; #endif @@ -123,6 +126,9 @@ size_t DetectTotalMiB(size_t page_bytes) { HWY_ASSERT(sysctl(mib, sizeof(mib) / sizeof(*mib), &data, &len, nullptr, 0) == 0); return data >> 20; +#elif defined(__EMSCRIPTEN__) + // The maximum linear memory in Wasm is currently specified at 16GiB. + return 16384; #else #error "Port" #endif @@ -199,6 +205,9 @@ size_t Allocator::FreeMiB() const { sysctlbyname("vm.page_inactive_count", &inactive, &len, nullptr, 0); sysctlbyname("vm.page_speculative_count", &speculative, &len, nullptr, 0); return (free + inactive + speculative) * base_page_bytes_ >> 20; +#elif defined(__EMSCRIPTEN__) + // There's no way to emulate this in emscripten so we lie. + return 16384; #else #error "Port" #endif diff --git a/util/threading.cc b/util/threading.cc index d4765192..4ac5b6e9 100644 --- a/util/threading.cc +++ b/util/threading.cc @@ -113,6 +113,10 @@ NestedPools::NestedPools(const BoundedTopology& topology, const Allocator& allocator, size_t max_threads, Tristate pin) : pinning_(pin) { +#ifdef __EMSCRIPTEN__ + // Node runs out of memory with a large number of workers. Cap it for now. + if (max_threads == 0 || max_threads > 32) max_threads = 32; +#endif const size_t num_clusters = topology.NumClusters(); const size_t cluster_workers_cap = DivideMaxAcross(max_threads, num_clusters);