Tạo một loại mới có thể phân phát

Tài liệu này giải thích cách mở rộng Dịch vụ TensorFlow bằng một loại dịch vụ mới. Loại có thể phân phát nổi bật nhất là SavedModelBundle , nhưng có thể hữu ích khi xác định các loại có thể phân phát khác để phân phát dữ liệu đi cùng với mô hình của bạn. Ví dụ bao gồm: bảng tra cứu từ vựng, logic chuyển đổi tính năng. Bất kỳ lớp C++ nào cũng có thể là một lớp có thể phục vụ được, ví dụ: int , std::map<string, int> hoặc bất kỳ lớp nào được xác định trong tệp nhị phân của bạn -- chúng ta hãy gọi nó là YourServable .

Xác định LoaderSourceAdapter cho YourServable

Để kích hoạt TensorFlow Serve để quản lý và phục vụ YourServable , bạn cần xác định hai điều:

  1. Lớp Loader tải, cung cấp quyền truy cập và hủy tải một phiên bản của YourServable .

  2. SourceAdapter khởi tạo trình tải từ một số định dạng dữ liệu cơ bản, ví dụ: đường dẫn hệ thống tệp. Để thay thế cho SourceAdapter , bạn có thể viết Source hoàn chỉnh. Tuy nhiên, vì cách tiếp cận SourceAdapter phổ biến hơn và mang tính mô-đun hơn nên chúng tôi tập trung vào nó ở đây.

Sự trừu tượng Loader được định nghĩa trong core/loader.h . Nó yêu cầu bạn xác định các phương thức để tải, truy cập và dỡ bỏ loại có thể phục vụ của bạn. Dữ liệu mà từ đó dịch vụ có thể tải có thể đến từ bất kỳ đâu, nhưng thông thường dữ liệu đó đến từ đường dẫn hệ thống lưu trữ. Hãy giả sử đó là trường hợp của YourServable . Giả sử thêm rằng bạn đã có Source<StoragePath> mà bạn hài lòng (nếu không, hãy xem tài liệu Nguồn tùy chỉnh ).

Ngoài Loader , bạn sẽ cần xác định SourceAdapter để khởi tạo Loader từ một đường dẫn lưu trữ nhất định. Hầu hết các trường hợp sử dụng đơn giản đều có thể chỉ định chính xác hai đối tượng bằng lớp SimpleLoaderSourceAdapter (trong core/simple_loader.h ). Các trường hợp sử dụng nâng cao có thể chọn chỉ định riêng các lớp LoaderSourceAdapter bằng cách sử dụng các API cấp thấp hơn, ví dụ: nếu SourceAdapter cần giữ lại một số trạng thái và/hoặc nếu trạng thái cần được chia sẻ giữa các phiên bản Loader .

Có một triển khai tham chiếu của một bản đồ băm đơn giản có thể phân phát sử dụng SimpleLoaderSourceAdapter trong servables/hashmap/hashmap_source_adapter.cc . Bạn có thể thấy thuận tiện khi tạo một bản sao của HashmapSourceAdapter và sau đó sửa đổi nó cho phù hợp với nhu cầu của mình.

Việc triển khai HashmapSourceAdapter có hai phần:

  1. Logic để tải bản đồ băm từ một tệp, trong LoadHashmapFromFile() .

  2. Việc sử dụng SimpleLoaderSourceAdapter để xác định SourceAdapter phát ra các trình tải hashmap dựa trên LoadHashmapFromFile() . SourceAdapter mới có thể được khởi tạo từ thông báo giao thức cấu hình thuộc loại HashmapSourceAdapterConfig . Hiện tại, thông báo cấu hình chỉ chứa định dạng tệp và nhằm mục đích triển khai tham chiếu, chỉ hỗ trợ một định dạng đơn giản.

    Lưu ý lệnh gọi Detach() trong hàm hủy. Lệnh gọi này là bắt buộc để tránh chạy đua giữa trạng thái phá bỏ và bất kỳ lời gọi nào đang diễn ra của lambda Người tạo trong các luồng khác. (Mặc dù bộ điều hợp nguồn đơn giản này không có bất kỳ trạng thái nào, nhưng lớp cơ sở vẫn buộc Detach() được gọi.)

Sắp xếp để tải các đối tượng YourServable vào trình quản lý

Dưới đây là cách nối SourceAdapter mới của bạn cho trình tải YourServable với nguồn đường dẫn lưu trữ cơ bản và trình quản lý (có khả năng xử lý lỗi kém; mã thực nên cẩn thận hơn):

Đầu tiên, tạo người quản lý:

std::unique_ptr<AspiredVersionsManager> manager = ...;

Sau đó, tạo bộ điều hợp nguồn YourServable và cắm nó vào trình quản lý:

auto your_adapter = new YourServableSourceAdapter(...);
ConnectSourceToTarget(your_adapter, manager.get());

Cuối cùng, tạo một nguồn đường dẫn đơn giản và cắm nó vào bộ điều hợp của bạn:

std::unique_ptr<FileSystemStoragePathSource> path_source;
// Here are some FileSystemStoragePathSource config settings that ought to get
// it working, but for details please see its documentation.
FileSystemStoragePathSourceConfig config;
// We just have a single servable stream. Call it "default".
config.set_servable_name("default");
config.set_base_path(FLAGS::base_path /* base path for our servable files */);
config.set_file_system_poll_wait_seconds(1);
TF_CHECK_OK(FileSystemStoragePathSource::Create(config, &path_source));
ConnectSourceToTarget(path_source.get(), your_adapter.get());

Truy cập các đối tượng YourServable đã tải

Đây là cách xử lý YourServable đã tải và sử dụng nó:

auto handle_request = serving::ServableRequest::Latest("default");
ServableHandle<YourServable*> servable;
Status status = manager->GetServableHandle(handle_request, &servable);
if (!status.ok()) {
  LOG(INFO) << "Zero versions of 'default' servable have been loaded so far";
  return;
}
// Use the servable.
(*servable)->SomeYourServableMethod();

Nâng cao: Sắp xếp nhiều phiên bản có thể phân phát để chia sẻ trạng thái

SourceAdapters có thể chứa trạng thái được chia sẻ giữa nhiều dịch vụ được phát ra. Ví dụ:

  • Nhóm luồng được chia sẻ hoặc tài nguyên khác mà nhiều máy chủ sử dụng.

  • Cấu trúc dữ liệu chỉ đọc được chia sẻ mà nhiều phiên bản có thể phân phát sử dụng để tránh tốn thời gian và không gian khi sao chép cấu trúc dữ liệu trong mỗi phiên bản có thể phân phát.

Trạng thái chia sẻ có thời gian khởi tạo và kích thước không đáng kể (ví dụ: nhóm luồng) có thể được SourceAdapter tạo ra một cách háo hức, sau đó nhúng một con trỏ tới nó trong mỗi trình tải có thể phục vụ được phát ra. Việc tạo trạng thái chia sẻ đắt tiền hoặc lớn phải được hoãn lại cho lệnh gọi Loader::Load() có thể áp dụng đầu tiên, tức là do người quản lý quản lý. Một cách đối xứng, lệnh gọi Loader::Unload() tới thiết bị có thể phục vụ cuối cùng bằng cách sử dụng trạng thái chia sẻ lớn/đắt sẽ phá bỏ nó.