diff --git a/backends/xnnpack/CMakeLists.txt b/backends/xnnpack/CMakeLists.txt index 002d351155d..bc2bfc5fe61 100644 --- a/backends/xnnpack/CMakeLists.txt +++ b/backends/xnnpack/CMakeLists.txt @@ -73,6 +73,20 @@ foreach(fbs_file ${_xnnpack_schema__srcs}) endforeach() # Generate the headers from the .fbs files. + +if (WIN32) +add_custom_command( + OUTPUT ${_xnnpack_schema__outputs} + COMMAND + ${FLATC_EXECUTABLE} --cpp --cpp-std c++11 --scoped-enums -o + "${_xnnpack_schema__include_dir}/executorch/backends/xnnpack/serialization" + ${_xnnpack_schema__srcs} + COMMAND powershell -Command "Move-Item -Path ${_xnnpack_flatbuffer__outputs} -Destination ${_xnnpack_schema__outputs}" + WORKING_DIRECTORY ${EXECUTORCH_ROOT} + COMMENT "Generating xnnpack_schema headers" + VERBATIM +) +else() add_custom_command( OUTPUT ${_xnnpack_schema__outputs} COMMAND @@ -84,6 +98,7 @@ add_custom_command( COMMENT "Generating xnnpack_schema headers" VERBATIM ) +endif() add_library(xnnpack_schema INTERFACE ${_xnnpack_schema__outputs}) set_target_properties(xnnpack_schema PROPERTIES LINKER_LANGUAGE CXX) diff --git a/build/resolve_buck.py b/build/resolve_buck.py index d70fe94b415..804ab22a033 100644 --- a/build/resolve_buck.py +++ b/build/resolve_buck.py @@ -11,7 +11,6 @@ import platform import stat import sys -import tempfile import urllib.request from dataclasses import dataclass @@ -85,6 +84,12 @@ class BuckInfo: archive_name="buck2-x86_64-apple-darwin.zst", target_versions=["3eb1ae97ea963086866b4d2d9ffa966d"], ), + ("windows", "x86_64"): BuckInfo( + archive_name="buck2-x86_64-pc-windows-msvc.exe.zst", + target_versions=[ + "bf1685c4c4ddd9de4592b5a955cb7326fd01e6c4d5f561643422bed961a17401" + ], + ), } @@ -135,6 +140,8 @@ def resolve_buck2(args: argparse.Namespace) -> Union[str, int]: os_family = "linux" elif sys.platform.startswith("darwin"): os_family = "darwin" + elif sys.platform.startswith("win"): + os_family = "windows" platform_key = (os_family, arch) if platform_key not in BUCK_PLATFORM_MAP: @@ -193,12 +200,12 @@ def resolve_buck2(args: argparse.Namespace) -> Union[str, int]: buck2_archive_url = f"https://github.com/facebook/buck2/releases/download/{target_buck_version}/{buck_info.archive_name}" - with tempfile.NamedTemporaryFile() as archive_file: + try: print(f"Downloading buck2 from {buck2_archive_url}...", file=sys.stderr) - urllib.request.urlretrieve(buck2_archive_url, archive_file.name) + archive_file, _ = urllib.request.urlretrieve(buck2_archive_url) # Extract and chmod. - with open(archive_file.name, "rb") as f: + with open(archive_file, "rb") as f: data = f.read() decompressed_bytes = zstd.decompress(data) @@ -207,6 +214,8 @@ def resolve_buck2(args: argparse.Namespace) -> Union[str, int]: file_stat = os.stat(buck2_local_path) os.chmod(buck2_local_path, file_stat.st_mode | stat.S_IEXEC) + finally: + os.remove(archive_file) return buck2_local_path diff --git a/devtools/CMakeLists.txt b/devtools/CMakeLists.txt index 776d421a8d3..144995803c3 100644 --- a/devtools/CMakeLists.txt +++ b/devtools/CMakeLists.txt @@ -157,19 +157,45 @@ file(MAKE_DIRECTORY ${_program_schema__include_dir}/executorch/devtools/bundled_program ) +# Note that the flatcc project actually writes its outputs into the source +# tree instead of under the binary directory, and there's no way to change +# that behavior. +if(WIN32) + set(_flatcc_bin_path ${_flatcc_source_dir}/bin/${CMAKE_BUILD_TYPE}/flatcc) +else() + set(_flatcc_bin_path ${_flatcc_source_dir}/bin/flatcc) +endif() + +if(WIN32) + add_custom_command( + OUTPUT ${_etdump_schema__outputs} + COMMAND + # Note that the flatcc project actually writes its outputs into the source + # tree instead of under the binary directory, and there's no way to change + # that behavior. + ${_flatcc_bin_path} -cwr -o + ${_program_schema__include_dir}/executorch/devtools/etdump + ${_etdump_schema__srcs} + # COMMAND powershell -Command "Remove-Item -Path " ${_etdump_schema_cleanup_paths} " -Force -ErrorAction SilentlyContinue" + COMMAND powershell -Command "if (" "'${_etdump_schema_cleanup_paths}'" "-ne '') { Remove-Item -Path " "'${_etdump_schema_cleanup_paths}'" " -Force -ErrorAction SilentlyContinue }" + DEPENDS ${_etdump_schema_gen_dep} + COMMENT "Generating etdump headers" + ) +else() add_custom_command( OUTPUT ${_etdump_schema__outputs} COMMAND # Note that the flatcc project actually writes its outputs into the source # tree instead of under the binary directory, and there's no way to change # that behavior. - ${_flatcc_source_dir}/bin/flatcc -cwr -o + ${_flatcc_bin_path} -cwr -o ${_program_schema__include_dir}/executorch/devtools/etdump ${_etdump_schema__srcs} COMMAND rm -f ${_etdump_schema_cleanup_paths} DEPENDS ${_etdump_schema_gen_dep} COMMENT "Generating etdump headers" ) +endif() add_library( etdump ${CMAKE_CURRENT_SOURCE_DIR}/etdump/etdump_flatcc.cpp diff --git a/examples/models/llama/CMakeLists.txt b/examples/models/llama/CMakeLists.txt index 6a4aee11d22..7097f3e9c15 100644 --- a/examples/models/llama/CMakeLists.txt +++ b/examples/models/llama/CMakeLists.txt @@ -80,7 +80,7 @@ find_package(gflags REQUIRED) # find `executorch` libraries Same as for gflags set(executorch_DIR ${CMAKE_CURRENT_BINARY_DIR}/../../../lib/cmake/ExecuTorch) find_package(executorch CONFIG REQUIRED) -if(CMAKE_TOOLCHAIN_IOS OR ANDROID) +if(CMAKE_TOOLCHAIN_IOS OR ANDROID OR WIN32 OR APPLE) target_link_options_shared_lib(executorch) endif() @@ -98,6 +98,13 @@ add_subdirectory(runner) set(link_libraries gflags) set(_srcs main.cpp) +# in Windows, cpublas and extension_threadpool have duplicated symbols +if(WIN32 AND TARGET xnnpack_backend) + set(CPUBLAS "") +else() + set(CPUBLAS "cpublas") +endif() + if(EXECUTORCH_BUILD_KERNELS_OPTIMIZED) list( APPEND @@ -105,7 +112,7 @@ if(EXECUTORCH_BUILD_KERNELS_OPTIMIZED) optimized_native_cpu_ops_lib optimized_kernels portable_kernels - cpublas + ${CPUBLAS} eigen_blas ) target_link_options_shared_lib(optimized_native_cpu_ops_lib) @@ -131,10 +138,18 @@ if(EXECUTORCH_BUILD_TORCHAO) endif() set(XNNPACK_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../backends/xnnpack) + +# in Windows, xnnpack_backend and extension_threadpool have duplicated symbols +if(WIN32 AND TARGET xnnpack_backend) + set(EXTENSION_THREADPOOL "") +else() + set(EXTENSION_THREADPOOL "extension_threadpool") +endif() + # Extra compile option and include dir for pthreadpool if(EXECUTORCH_BUILD_PTHREADPOOL) list(APPEND _common_compile_options -DET_USE_THREADPOOL) - list(APPEND link_libraries extension_threadpool pthreadpool) + list(APPEND link_libraries ${EXTENSION_THREADPOOL} pthreadpool) list(APPEND _common_include_directories ${XNNPACK_ROOT}/third-party/pthreadpool/include ) @@ -142,7 +157,7 @@ endif() # Extra sources for cpuinfo if(EXECUTORCH_BUILD_CPUINFO) - list(APPEND link_libraries extension_threadpool cpuinfo) + list(APPEND link_libraries ${EXTENSION_THREADPOOL} cpuinfo) list(APPEND _common_include_directories ${XNNPACK_ROOT}/third-party/cpuinfo/include ) @@ -216,9 +231,6 @@ target_include_directories(llama_main PUBLIC ${_common_include_directories}) target_link_libraries(llama_main PUBLIC llama_runner ${link_libraries}) target_compile_options(llama_main PUBLIC ${_common_compile_options}) -if(APPLE) - target_link_options_shared_lib(executorch) -endif() # Print all summary executorch_print_configuration_summary() diff --git a/examples/models/llama/install_requirements.bat b/examples/models/llama/install_requirements.bat new file mode 100644 index 00000000000..e084a2191f8 --- /dev/null +++ b/examples/models/llama/install_requirements.bat @@ -0,0 +1,16 @@ +rem Install snakeviz for cProfile flamegraph +rem Install sentencepiece for llama tokenizer +pip install snakeviz sentencepiece + +rem Install torchao. +pip install "%~dp0/../../../third-party/ao" + +rem Install lm-eval for Model Evaluation with lm-evalution-harness +rem Install tiktoken for tokenizer +pip install lm_eval==0.4.5 +pip install tiktoken blobfile +rem Restore numpy if >= 2.0 +pip install "numpy<2.0" + +rem Call the install helper for further setup +python examples/models/llama/install_requirement_helper.py diff --git a/examples/models/llama/runner/CMakeLists.txt b/examples/models/llama/runner/CMakeLists.txt index 79fcd267af0..f1e563dc5b3 100644 --- a/examples/models/llama/runner/CMakeLists.txt +++ b/examples/models/llama/runner/CMakeLists.txt @@ -36,6 +36,11 @@ include(${EXECUTORCH_SRCS_FILE}) # build llama_runner library list(TRANSFORM _llama_runner__srcs PREPEND "${EXECUTORCH_ROOT}/") +if (WIN32) + set_property(TARGET extension_module PROPERTY + IMPORTED_IMPLIB "${EXECUTORCH_ROOT}/cmake-out/lib/extension_module.lib") + +endif() target_include_directories( extension_module INTERFACE ${_common_include_directories} @@ -52,6 +57,7 @@ list(APPEND _llama_runner__srcs if(CMAKE_TOOLCHAIN_IOS OR ANDROID OR APPLE + OR WIN32 ) # Building a share library on iOS requires code signing On Android we see # duplicated registration when using shared lib @@ -84,4 +90,4 @@ target_link_libraries(llama_runner PUBLIC ${llama_runner_deps}) target_include_directories( llama_runner INTERFACE ${_common_include_directories} ${EXECUTORCH_ROOT} ) -target_compile_options(llama_runner PUBLIC ${_preprocessor_flag}) +target_compile_options(llama_runner PUBLIC ${_preprocessor_flag}) \ No newline at end of file diff --git a/extension/data_loader/file_data_loader.cpp b/extension/data_loader/file_data_loader.cpp index 1d097cfd989..3a3f663b90b 100644 --- a/extension/data_loader/file_data_loader.cpp +++ b/extension/data_loader/file_data_loader.cpp @@ -14,10 +14,10 @@ #include #include +#include #include #include #include -#include #include #include @@ -71,6 +71,9 @@ FileDataLoader::~FileDataLoader() { std::free(const_cast(file_name_)); // fd_ can be -1 if this instance was moved from, but closing a negative fd is // safe (though it will return an error). + if (fd_ == -1) { + return; + } ::close(fd_); } diff --git a/extension/data_loader/mman.h b/extension/data_loader/mman.h new file mode 100644 index 00000000000..e03c0a86fb6 --- /dev/null +++ b/extension/data_loader/mman.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +// This file ensures that mman.h compatible functions are defined in the global +// namespace for windows and posix environments. + +#pragma once + +#include + +#ifndef _WIN32 + +#include +#include + +ET_INLINE long get_os_page_size(){return sysconf(_SC_PAGESIZE);} + +#else + +#define NOMINMAX +#include +#undef NOMINMAX +#include + +#include + +ET_INLINE long get_os_page_size() { + SYSTEM_INFO si; + GetSystemInfo(&si); + long pagesize = si.dwAllocationGranularity > si.dwPageSize + ? si.dwAllocationGranularity + : si.dwPageSize; + return pagesize; +} + +#endif diff --git a/extension/data_loader/mman_windows.cpp b/extension/data_loader/mman_windows.cpp new file mode 100644 index 00000000000..d5f4f136e22 --- /dev/null +++ b/extension/data_loader/mman_windows.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (c) Google Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the MIT license. + */ + +/* + * Adapted from: https://code.google.com/archive/p/mman-win32/ + * + * mman-win32 + * mman library for Windows + * + * A light implementation of the mmap functions for MinGW. + * + * The mmap-win32 library implements a wrapper for mmap functions around the + * memory mapping Windows API. + */ + +#include +#include +#include + +#include "mman_windows.h" + +#ifndef STATUS_SECTION_TOO_BIG +#define STATUS_SECTION_TOO_BIG ((NTSTATUS)0xC0000040L) +#endif + +#ifndef FILE_MAP_EXECUTE +#define FILE_MAP_EXECUTE 0x0020 +#endif /* FILE_MAP_EXECUTE */ + +#define RETURN_IF_FAILED(hr) \ + do { \ + if (FAILED((hr))) { \ + return hr; \ + } \ + } while (false) + +namespace { + +HRESULT try_grow_process_memory_working_set(DWORD dwSizeRequired) { + // Get current working set + size_t minWorkingSetInitial; + size_t maxWorkingSet; + if (!GetProcessWorkingSetSize( + GetCurrentProcess(), &minWorkingSetInitial, &maxWorkingSet)) { + return GetLastError(); + } + + // Calculate new sizes + size_t minWorkingSet = minWorkingSetInitial + dwSizeRequired; + if (minWorkingSet < minWorkingSetInitial) { + return HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + + if (maxWorkingSet < minWorkingSet) { + maxWorkingSet = minWorkingSet; + } + + // Grow working set + if (!SetProcessWorkingSetSize( + GetCurrentProcess(), minWorkingSet, maxWorkingSet)) { + return GetLastError(); + } + return S_OK; +} + +HRESULT virtual_lock(void* pMem, DWORD dwSize) { + if (!VirtualLock(pMem, dwSize)) { + return GetLastError(); + } + return S_OK; +} + +HRESULT virtual_lock_allowing_working_set_growth(void* pMem, DWORD dwSize) { + HRESULT hr = virtual_lock(pMem, dwSize); + + if (hr == HRESULT_FROM_WIN32(STATUS_SECTION_TOO_BIG)) { + // Attempt to grow the process working set and try again + RETURN_IF_FAILED(try_grow_process_memory_working_set(dwSize)); + RETURN_IF_FAILED(virtual_lock(pMem, dwSize)); + } + + return hr; +} + +static int __map_mman_error(const DWORD err, const int deferr) { + if (err == 0) { + return 0; + } + // TODO: implement + return err; +} + +static DWORD __map_mmap_prot_page(const int prot) { + DWORD protect = 0; + + if (prot == PROT_NONE) { + return protect; + } + if ((prot & PROT_EXEC) != 0) { + protect = + ((prot & PROT_WRITE) != 0) ? PAGE_EXECUTE_READWRITE : PAGE_EXECUTE_READ; + } else { + protect = ((prot & PROT_WRITE) != 0) ? PAGE_READWRITE : PAGE_READONLY; + } + return protect; +} + +static DWORD __map_mmap_prot_file(const int prot) { + DWORD desiredAccess = 0; + + if (prot == PROT_NONE) { + return desiredAccess; + } + if ((prot & PROT_READ) != 0) { + desiredAccess |= FILE_MAP_READ; + } + if ((prot & PROT_WRITE) != 0) { + desiredAccess |= FILE_MAP_WRITE; + } + if ((prot & PROT_EXEC) != 0) { + desiredAccess |= FILE_MAP_EXECUTE; + } + return desiredAccess; +} + +} // namespace + +void* mmap(void* addr, size_t len, int prot, int flags, int fildes, off_t off) { + HANDLE fm, h; + + void* map = MAP_FAILED; + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4293) +#endif + + const DWORD dwFileOffsetLow = (sizeof(off_t) <= sizeof(DWORD)) + ? (DWORD)off + : (DWORD)(off & 0xFFFFFFFFL); + const DWORD dwFileOffsetHigh = (sizeof(off_t) <= sizeof(DWORD)) + ? (DWORD)0 + : (DWORD)((off >> 32) & 0xFFFFFFFFL); + const DWORD protect = __map_mmap_prot_page(prot); + const DWORD desiredAccess = __map_mmap_prot_file(prot); + + const off_t maxSize = off + (off_t)len; + + const DWORD dwMaxSizeLow = (sizeof(off_t) <= sizeof(DWORD)) + ? (DWORD)maxSize + : (DWORD)(maxSize & 0xFFFFFFFFL); + const DWORD dwMaxSizeHigh = (sizeof(off_t) <= sizeof(DWORD)) + ? (DWORD)0 + : (DWORD)((maxSize >> 32) & 0xFFFFFFFFL); + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + errno = 0; + + if (len == 0 + /* Unsupported flag combinations */ + || (flags & MAP_FIXED) != 0 + /* Usupported protection combinations */ + || prot == PROT_EXEC) { + errno = EINVAL; + return MAP_FAILED; + } + + h = ((flags & MAP_ANONYMOUS) == 0) ? (HANDLE)_get_osfhandle(fildes) + : INVALID_HANDLE_VALUE; + + if ((flags & MAP_ANONYMOUS) == 0 && h == INVALID_HANDLE_VALUE) { + errno = EBADF; + return MAP_FAILED; + } + + fm = CreateFileMapping(h, NULL, protect, dwMaxSizeHigh, dwMaxSizeLow, NULL); + + if (fm == NULL) { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + map = + MapViewOfFile(fm, desiredAccess, dwFileOffsetHigh, dwFileOffsetLow, len); + + CloseHandle(fm); + + if (map == NULL) { + errno = __map_mman_error(GetLastError(), EPERM); + return MAP_FAILED; + } + + return map; +} + +int munmap(void* addr, size_t len) { + if (UnmapViewOfFile(addr)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mprotect(void* addr, size_t len, int prot) { + DWORD newProtect = __map_mmap_prot_page(prot); + DWORD oldProtect = 0; + + if (VirtualProtect(addr, len, newProtect, &oldProtect)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int msync(void* addr, size_t len, int flags) { + if (FlushViewOfFile(addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} + +int mlock(const void* addr, size_t len) { + HRESULT hr = virtual_lock_allowing_working_set_growth((LPVOID)addr, len); + if (SUCCEEDED(hr)) { + return 0; + } + + errno = __map_mman_error(hr, EPERM); + + return -1; +} + +int munlock(const void* addr, size_t len) { + if (VirtualUnlock((LPVOID)addr, len)) + return 0; + + errno = __map_mman_error(GetLastError(), EPERM); + + return -1; +} diff --git a/extension/data_loader/mman_windows.h b/extension/data_loader/mman_windows.h new file mode 100644 index 00000000000..4c247cd4fca --- /dev/null +++ b/extension/data_loader/mman_windows.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) Google Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the MIT license. + */ + +/* + * Adapted from: https://code.google.com/archive/p/mman-win32/ + * + * mman-win32 + * mman library for Windows + * + * A light implementation of the mmap functions for MinGW. + * + * The mmap-win32 library implements a wrapper for mmap functions around the + * memory mapping Windows API. + */ + +#ifndef _SYS_MMAN_H_ +#define _SYS_MMAN_H_ + +#ifndef _WIN32_WINNT // Allow use of features specific to Windows XP or later. +#define _WIN32_WINNT \ + 0x0501 // Change this to the appropriate value to target other versions of + // Windows. +#endif + +/* All the headers include this file. */ +#ifndef _MSC_VER +#include <_mingw.h> +#endif + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PROT_NONE 0 +#define PROT_READ 1 +#define PROT_WRITE 2 +#define PROT_EXEC 4 + +#define MAP_FILE 0 +#define MAP_SHARED 1 +#define MAP_PRIVATE 2 +#define MAP_TYPE 0xf +#define MAP_FIXED 0x10 +#define MAP_ANONYMOUS 0x20 +#define MAP_ANON MAP_ANONYMOUS + +#define MAP_FAILED ((void*)-1) + +/* Flags for msync. */ +#define MS_ASYNC 1 +#define MS_SYNC 2 +#define MS_INVALIDATE 4 + +void* mmap(void* addr, size_t len, int prot, int flags, int fildes, off_t off); +int munmap(void* addr, size_t len); +int mprotect(void* addr, size_t len, int prot); +int msync(void* addr, size_t len, int flags); +int mlock(const void* addr, size_t len); +int munlock(const void* addr, size_t len); + +#ifdef __cplusplus +}; +#endif + +#endif /* _SYS_MMAN_H_ */ diff --git a/extension/data_loader/mmap_data_loader.cpp b/extension/data_loader/mmap_data_loader.cpp index ebe74f95266..d18b7b1a98e 100644 --- a/extension/data_loader/mmap_data_loader.cpp +++ b/extension/data_loader/mmap_data_loader.cpp @@ -6,6 +6,7 @@ * LICENSE file in the root directory of this source tree. */ +#include #include #include @@ -13,10 +14,8 @@ #include #include -#include #include #include -#include #include #include @@ -63,14 +62,16 @@ MmapDataLoader::~MmapDataLoader() { std::free(const_cast(file_name_)); // fd_ can be -1 if this instance was moved from, but closing a negative fd is // safe (though it will return an error). - ::close(fd_); + if (fd_ != -1) { + ::close(fd_); + } } Result MmapDataLoader::from( const char* file_name, MmapDataLoader::MlockConfig mlock_config) { // Cache the page size. - long page_size = sysconf(_SC_PAGESIZE); + long page_size = get_os_page_size(); if (page_size < 0) { ET_LOG(Error, "Could not get page size: %s (%d)", ::strerror(errno), errno); return Error::AccessFailed; @@ -182,12 +183,24 @@ Result MmapDataLoader::load( Range range = get_overlapping_pages(static_cast(offset), size, page_size_); + size_t map_size = range.size; + #ifdef _WIN32 + // On Windows, don't mmap-in memory past end of on-disk file. + // + // The Windows implementation of mmap uses CreateFileMapping which returns + // error STATUS_SECTION_TOO_BIG (0xc0000040) if we try to map past the end + // of the last page of a file mapped in as read-only. + if (range.start + range.size > file_size_) { + map_size = file_size_ - range.start; + } + #endif + // Map the pages read-only. MAP_PRIVATE vs. MAP_SHARED doesn't matter since // the data is read-only, but use PRIVATE just to further avoid accidentally // modifying the file. void* pages = ::mmap( nullptr, - range.size, + map_size, PROT_READ, MAP_PRIVATE, fd_, diff --git a/extension/data_loader/targets.bzl b/extension/data_loader/targets.bzl index fcc7cba5419..1a2b77cfaeb 100644 --- a/extension/data_loader/targets.bzl +++ b/extension/data_loader/targets.bzl @@ -69,7 +69,16 @@ def define_common_targets(): runtime.cxx_library( name = "mmap_data_loader", - srcs = ["mmap_data_loader.cpp"], + srcs = [ + "mmap_data_loader.cpp", + "mman_windows.cpp" + ] if host_info().os.is_windows else [ + "mmap_data_loader.cpp" + ], + headers = [ + "mman.h", + "mman_windows.h" + ], exported_headers = ["mmap_data_loader.h"], visibility = [ "//executorch/test/...", diff --git a/extension/data_loader/test/mmap_data_loader_test.cpp b/extension/data_loader/test/mmap_data_loader_test.cpp index a76121109a8..b217705c618 100644 --- a/extension/data_loader/test/mmap_data_loader_test.cpp +++ b/extension/data_loader/test/mmap_data_loader_test.cpp @@ -6,12 +6,11 @@ * LICENSE file in the root directory of this source tree. */ +#include #include #include -#include - #include #include @@ -34,7 +33,7 @@ class MmapDataLoaderTest : public ::testing::Test { executorch::runtime::runtime_init(); // Get the page size and ensure it's a power of 2. - long page_size = sysconf(_SC_PAGESIZE); + long page_size = get_os_page_size(); ASSERT_GT(page_size, 0); ASSERT_EQ(page_size & ~(page_size - 1), page_size); page_size_ = page_size; diff --git a/extension/llm/custom_ops/sdpa_with_kv_cache.py b/extension/llm/custom_ops/sdpa_with_kv_cache.py index 85021266b59..8041e25d406 100644 --- a/extension/llm/custom_ops/sdpa_with_kv_cache.py +++ b/extension/llm/custom_ops/sdpa_with_kv_cache.py @@ -10,6 +10,7 @@ # pyre-unsafe +import os import logging from pathlib import Path @@ -24,7 +25,11 @@ op2 = torch.ops.llama.fast_hadamard_transform.default assert op2 is not None except: - libs = list(Path(__file__).parent.resolve().glob("libcustom_ops_aot_lib.*")) + lib_name_pattern = "libcustom_ops_aot_lib.*" + if os.name == "nt": + lib_name_pattern = "custom_ops_aot_lib.*" + + libs = list(Path(__file__).parent.resolve().glob(lib_name_pattern)) assert len(libs) == 1, f"Expected 1 library but got {len(libs)}" logging.info(f"Loading custom ops library: {libs[0]}") torch.ops.load_library(libs[0]) diff --git a/extension/llm/runner/util.h b/extension/llm/runner/util.h index 04d4eccc4a7..6f4954d6e0d 100644 --- a/extension/llm/runner/util.h +++ b/extension/llm/runner/util.h @@ -44,7 +44,7 @@ ET_EXPERIMENTAL void inline safe_printf(const char* piece) { ET_EXPERIMENTAL long inline time_in_ms() { // return time in milliseconds, for benchmarking the model speed struct timespec time; - clock_gettime(CLOCK_REALTIME, &time); + timespec_get(&time, TIME_UTC); return time.tv_sec * 1000 + time.tv_nsec / 1000000; } diff --git a/extension/module/CMakeLists.txt b/extension/module/CMakeLists.txt index 7d6c2485eed..6680f36cb35 100644 --- a/extension/module/CMakeLists.txt +++ b/extension/module/CMakeLists.txt @@ -20,6 +20,7 @@ list(TRANSFORM _extension_module__srcs PREPEND "${EXECUTORCH_ROOT}/") if(CMAKE_TOOLCHAIN_IOS OR CMAKE_TOOLCHAIN_ANDROID OR APPLE + OR WIN32 ) # Building a share library on iOS requires code signing On Android we see # duplicated registration when using shared lib diff --git a/install_requirements.bat b/install_requirements.bat index 4cfe4b21c4b..74ec74ab2cb 100644 --- a/install_requirements.bat +++ b/install_requirements.bat @@ -7,14 +7,8 @@ rem This batch file provides a basic functionality similar to the bash script. cd /d "%~dp0" -rem Find the names of the python tools to use (replace with your actual python installation) -if "%PYTHON_EXECUTABLE%"=="" ( - if "%CONDA_DEFAULT_ENV%"=="" OR "%CONDA_DEFAULT_ENV%"=="base" OR NOT EXIST "python" ( - set PYTHON_EXECUTABLE=python3 - ) else ( - set PYTHON_EXECUTABLE=python - ) -) +rem Under windows it's always python +set PYTHON_EXECUTABLE=python "%PYTHON_EXECUTABLE%" install_requirements.py %* diff --git a/kernels/quantized/CMakeLists.txt b/kernels/quantized/CMakeLists.txt index 9d2b14d8eb0..18ac299e742 100644 --- a/kernels/quantized/CMakeLists.txt +++ b/kernels/quantized/CMakeLists.txt @@ -112,7 +112,7 @@ if(NOT CMAKE_GENERATOR STREQUAL "Xcode" quantized_pybind_kernels_lib DEPS portable_lib ) target_link_libraries( - quantized_ops_aot_lib PUBLIC quantized_ops_pybind_lib + quantized_ops_aot_lib PUBLIC quantized_ops_pybind_lib PUBLIC executorch_core PUBLIC portable_kernels ) # pip wheels will need to be able to find the dependent libraries. On diff --git a/runtime/platform/compiler.h b/runtime/platform/compiler.h index bc07470387a..16b08d23fae 100644 --- a/runtime/platform/compiler.h +++ b/runtime/platform/compiler.h @@ -99,15 +99,8 @@ #endif // (__cplusplus) >= 202002L -/// Define a C symbol with weak linkage. -#ifdef _MSC_VER -// There currently doesn't seem to be a great way to do this in Windows and -// given that weak linkage is not really critical on Windows, we'll just leave -// it as a stub. -#define ET_WEAK -#else +// Building on Windows also need this. #define ET_WEAK __attribute__((weak)) -#endif /** * Annotation marking a function as printf-like, providing compiler support diff --git a/runtime/platform/targets.bzl b/runtime/platform/targets.bzl index 42bb851e2cf..0c94c70c06b 100644 --- a/runtime/platform/targets.bzl +++ b/runtime/platform/targets.bzl @@ -68,6 +68,7 @@ def define_common_targets(): "log.h", "profiler.h", "runtime.h", + "unistd.h", ], srcs = [ "abort.cpp", diff --git a/runtime/platform/unistd.h b/runtime/platform/unistd.h new file mode 100644 index 00000000000..c8bc4866702 --- /dev/null +++ b/runtime/platform/unistd.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @file + * unistd.h related macros for POSIX/Windows compatibility. + */ +#pragma once + +#if defined(_WIN32) && !defined(_WIN64) +#error \ + "You're trying to build ExecuTorch with a too old version of Windows. We need Windows 64-bit." +#endif + +#if !defined(_WIN64) +#include +#else +#include +#define O_RDONLY _O_RDONLY +#define open _open +#define close _close +#define read _read +#define write _write +#define stat _stat64 +#define fstat _fstat64 +#define off_t _off_t +#define lseek _lseeki64 + +#include // For ssize_t. +#include +// To avoid conflicts with std::numeric_limits::max() in +// file_data_loader.cpp. +#undef max + +inline ssize_t pread(int fd, void* buf, size_t nbytes, size_t offset) { + OVERLAPPED overlapped; /* The offset for ReadFile. */ + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.Offset = offset; + overlapped.OffsetHigh = offset >> 32; + + BOOL result; /* The result of ReadFile. */ + DWORD bytes_read; /* The number of bytes read. */ + HANDLE file = (HANDLE)_get_osfhandle(fd); + + result = ReadFile(file, buf, nbytes, &bytes_read, &overlapped); + DWORD error = GetLastError(); + if (!result) { + if (error == ERROR_IO_PENDING) { + result = GetOverlappedResult(file, &overlapped, &bytes_read, TRUE); + if (!result) { + error = GetLastError(); + } + } + } + if (!result) { + // Translate error into errno. + switch (error) { + case ERROR_HANDLE_EOF: + errno = 0; + break; + default: + errno = EIO; + break; + } + return -1; + } + return bytes_read; +} + +#endif // !defined(_WIN64) \ No newline at end of file diff --git a/setup.py b/setup.py index ff1afa89bd6..f457ac99e8e 100644 --- a/setup.py +++ b/setup.py @@ -225,7 +225,7 @@ def src_path(self, installer: "InstallerBuildExt") -> Path: self.src = self.src.replace("%BUILD_TYPE%", cfg) else: # Remove %BUILD_TYPE% from the path. - self.src = self.src.replace("/%BUILD_TYPE%", "") + self.src = self.src.replace("%BUILD_TYPE%/", "") # Construct the full source path, resolving globs. If there are no glob # pattern characters, this will just ensure that the source file exists. @@ -263,7 +263,7 @@ def __init__( output is in a subdirectory named after the build type. For single- config generators (like Makefile Generators or Ninja), this placeholder will be removed. - src_name: The name of the file to install + src_name: The name of the file to install. dst: The path to install to, relative to the root of the pip package. If dst ends in "/", it is treated as a directory. Otherwise it is treated as a filename. @@ -305,13 +305,17 @@ def dst_path(self, installer: "InstallerBuildExt") -> Path: class BuiltExtension(_BaseExtension): """An extension that installs a python extension that was built by cmake.""" - def __init__(self, src: str, modpath: str): + def __init__(self, src_dir: str, src_name: str, modpath: str): """Initializes a BuiltExtension. Args: - src: The path to the file to install (typically a shared library), - relative to the cmake-out directory. May be an fnmatch-style - glob that matches exactly one file. If the path ends in `.so`, + src_dir: The directory of the file to install, relative to the cmake-out + directory. A placeholder %BUILD_TYPE% will be replaced with the build + type for multi-config generators (like Visual Studio) where the build + output is in a subdirectory named after the build type. For single- + config generators (like Makefile Generators or Ninja), this placeholder + will be removed. + src_name: The name of the file to install. If the path ends in `.so`, this class will also look for similarly-named `.dylib` files. modpath: The dotted path of the python module that maps to the extension. @@ -319,6 +323,7 @@ def __init__(self, src: str, modpath: str): assert ( "/" not in modpath ), f"modpath must be a dotted python module path: saw '{modpath}'" + src = os.path.join(src_dir, src_name) # This is a real extension, so use the modpath as the name. super().__init__(src=src, dst=modpath, name=modpath) @@ -658,7 +663,9 @@ def get_ext_modules() -> List[Extension]: # portable kernels, and a selection of backends. This lets users # load and execute .pte files from python. BuiltExtension( - "_portable_lib.*", "executorch.extension.pybindings._portable_lib" + src_dir="%BUILD_TYPE%/", + src_name="_portable_lib.cp*", + modpath="executorch.extension.pybindings._portable_lib", ) ) if ShouldBuild.llama_custom_ops():