Feast 0.13 三大特性深度解析:On-Demand 特征转换、Python 特征服务器与无实体特征视图
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
本文基于 Feast 官方博客《Feast 0.13 adds on-demand transforms, feature servers, and feature views without entities》(docs/blog/feast-0-13-adds-on-demand-transforms-feature-servers-and-feature-views-without-entities.md)展开。这篇 2021 年 10 月 2 日由 Danny Chiao、Tsotne Tabidze、Achal Shah 和 Felix Wang 发布的版本公告,是 Feast 从"特征存储"走向"特征计算平台"的关键节点:它一次性引入了 On-Demand Feature Views(按需转换特征视图,含 request data 概念)、Python Feature Server(HTTP 在线特征服务)以及无实体特征视图三项能力。读完本文,你将理解这三项特性的设计动机与 0.13 时代的原始用法,并能对照当前仓库源码(如 sdk/python/feast/on_demand_feature_view.py、sdk/python/feast/feature_server.py)掌握它们在今天的真实形态与可复制的用法。
需要说明的适用前提:0.13 中的 On-Demand Feature Views 与 Python Feature Server 当时均标注为实验特性(Experimental),官方明确提示"实验特性的 API 在未来可能随时变更"。本文后半部分会逐一说明这些 API 在当前仓库中的演进与兼容关系,实际编码请以当前仓库代码为准。
一、发布背景:Feast 0.13 解决了什么问题
博客原文列出了 0.13 版本的三项核心新增:
- [实验] On demand feature views:允许对已有特征和请求数据(request data)做转换以生成新特征,转换逻辑在历史检索(训练路径)和在线检索(服务路径)中一致执行。其中"request data"是一个全新概念——指仅在预测请求时刻才可用(例如用户发起的 HTTP 请求中携带的字段)的数据,它可以作为转换的输入。
- [实验] Python feature servers:提供一个本地 HTTP 服务器来对外提供在线特征,使任何能发起 HTTP 请求的编程语言都能从 Feast 取数。博客同时预告了两个路线图方向:Serverless 部署与低延迟 Java 特征服务器"即将推出"。
- Feature views without entities:允许定义仅按事件时间戳(event timestamp)参与连接的、不带实体的特征视图——定义和取数时都不需要再提供实体名与实体值列表。
博客原文还附了一个初始化仓库的基础命令示例:
$ feast init feature_repo Creating a new Feast repository in /home/tsotne/feast/feature_repo.这三项能力共同指向一个目标:降低"训练/服务偏差(training-serving skew)"并拓宽特征的输入边界。下文逐一深入。
二、On-Demand Feature Views:让训练与服务共用同一份转换逻辑
2.1 设计动机与典型用例
博客原文给出的定义是:On demand feature views 允许用户利用已有特征和请求数据做转换并创建新特征,用户以 Python 函数定义转换逻辑,该逻辑同时执行于历史检索与在线检索两条路径。这正是解决训练/服务偏差的关键机制——同一份 UDF 决定两条路径的特征产出。
博客列举了三类典型用例(特征名原文照录):
- 交易类特征:如
transaction_amount_greater_than_7d_average,其特征输入本身就是交易、预订或订单事件的一部分; - 依赖当前位置或时间的特征:如
user_account_age、distance_driver_customer; - 键空间大到无法预计算的交叉特征(feature crosses):如
movie_category_x_movie_rating或lat_bucket_x_lon_bucket。
这三类用例的共同点是:输入中有一部分不是存在离线表里的历史数据,而是"请求时刻"才出现的上下文,或者组合空间大到无法预物化——这正是普通 Batch/Stream Feature View 无法覆盖的盲区。
2.2 Request Data 概念:从 RequestDataSource 到 RequestSource
博客 0.13 版本的原始写法(历史 API,用于忠实呈现当时形态):
# 定义一个 request data source,编码仅在请求时刻可用的特征/信息 # (例如属于用户发起的 HTTP 请求的一部分) input_request = RequestDataSource( name="vals_to_add", schema={ "val_to_add": ValueType.INT64, } )对照当前源码,该概念在 sdk/python/feast/data_source.py 中实现为RequestSource类(类名从RequestDataSource更名为RequestSource,schema 从字典改为Field列表):
@typechecked class RequestSource(DataSource): """ RequestSource that can be used to provide input features for on demand transforms Attributes: name: Name of the request data source schema: Schema mapping from the input feature name to a ValueType ... """ name: str schema: List[Field]对应的当前 API 等价写法:
from feast import Field, RequestSource from feast.types import Int64 input_request = RequestSource( name="vals_to_add", schema=[Field(name="val_to_add", dtype=Int64)], )RequestSource继承自DataSource但没有任何物理存储后端,它只是一个"请求时刻字段"的 schema 声明——在线取数时这些字段从entity_rows中取值,历史取数时从entity_df中取值,随后进入 ODFV 的转换函数。
2.3 源码实现:on_demand_feature_view 装饰器
ODFV 的完整定义位于 sdk/python/feast/on_demand_feature_view.py:OnDemandFeatureView类定义于第 116 行,注册用的on_demand_feature_view装饰器定义于第 1360-1457 行。从源码结构看,其参数集相比 0.13 已大幅扩展,核心参数与默认值如下:
| 参数 | 类型/默认值 | 作用 |
|---|---|---|
name | Optional[str] | 视图名,缺省时取用户函数名(参考文档亦确认"视图名即函数名",如transformed_conv_rate) |
entities | Optional[List[Entity]] | 关联实体列表 |
schema | list[Field] | 转换输出的特征字段定义 |
sources | Optional[list[FeatureView \| RequestSource \| FeatureViewProjection]] | 输入源:既有特征视图 + 请求数据源,即 0.13 引入的组合方式 |
input_schema | Optional[list[Field]] | 请求时刻输入但不是聚合列的上下文字段(阈值、币种等) |
aggregations | Optional[List[Aggregation]] | 声明式聚合(与转换函数互斥,见参考文档) |
mode | str = "pandas" | 执行模式:"pandas"接收/返回 DataFrame;"python"使用原生 Python 处理值列表或单行字典 |
write_to_online_store | bool = False | True表示转换在写入时执行并物化到在线库(加速在线读);False(默认)表示转换在读取时执行 |
singleton | bool = False | 仅在mode="python"下可用:UDF 逐行(单条字典)处理 |
track_metrics | bool = False | 是否为该 ODFV 输出 Prometheus 计时指标(需服务器以 metrics 模式启动) |
explode | bool = False | 转换是否会把一行输入炸开为多行 |
装饰器内部有两个值得注意的实现细节(sdk/python/feast/on_demand_feature_view.py):
def mainify(obj) -> None: # Needed to allow dill to properly serialize the udf. Otherwise, clients will need to have a file with the same # name as the original file defining the ODFV. if obj.__module__ != "__main__": obj.__module__ = "__main__" def decorator(user_function): udf_string = dill.source.getsource(user_function) mainify(user_function) on_demand_feature_view_obj = OnDemandFeatureView(...)其一,UDF 源码通过dill.source.getsource捕获为字符串(udf_string),即转换逻辑是以文本形式序列化进 registry的,而不是依赖调用方本地能 import 到该函数——这是转换能"在任意执行点(本地、特征服务器、转换服务器)被还原执行"的基础。其二,mainify强制把模块名改成__main__,规避 dill 反序列化时对源文件路径的依赖。
2.4 完整的读时转换示例(继承自仓库参考文档)
博客 0.13 只给出了请求数据源片段,完整的 ODFV 定义用法可参考当前仓库的参考文档 docs/reference/beta-on-demand-feature-view.md。以 Pandas 模式为例:
from feast import Field, RequestSource from feast.on_demand_feature_view import on_demand_feature_view from feast.types import Float64, Int64 import pandas as pd input_request = RequestSource( name="vals_to_add", schema=[ Field(name="val_to_add", dtype=Int64), Field(name="val_to_add_2", dtype=Int64), ], ) @on_demand_feature_view( sources=[driver_hourly_stats_view, input_request], schema=[ Field(name="conv_rate_plus_val1", dtype=Float64), Field(name="conv_rate_plus_val2", dtype=Float64), ], mode="pandas", ) def transformed_conv_rate(features_df: pd.DataFrame) -> pd.DataFrame: df = pd.DataFrame() df["conv_rate_plus_val1"] = features_df["conv_rate"] + features_df["val_to_add"] df["conv_rate_plus_val2"] = features_df["conv_rate"] + features_df["val_to_add_2"] return df参考文档同时给出:原生 Python 列表输入模式(mode="python")、单行字典模式(singleton=True)、声明式聚合(Aggregation(column=..., function="sum"/"mean"/"count", time_window=...),聚合列自动命名为{function}_{column},如sum_trips)、写时转换(write_to_online_store=True,配合store.push摄入输入列)以及物化预转换数据的transform_on_write=False路径。取数时通过store.get_historical_features(features=["transformed_conv_rate:conv_rate_plus_val1", ...])与store.get_online_features(...)引用,后者在entity_rows中直接携带请求数据字段,例如:
entity_rows = [ { "driver_id": 1001, "val_to_add": 1, "val_to_add_2": 2, } ]2.5 CLI 与管理命令
参考文档 docs/reference/beta-on-demand-feature-view.md 记载了两条 ODFV 管理命令,对应实现位于 sdk/python/feast/cli/on_demand_feature_views.py(describe在第 21 行、list在第 45 行):
feast on-demand-feature-views list # 列出 feast apply 后注册的所有 ODFV feast on-demand-feature-views describe <NAME> # 查看某个 ODFV 的定义另外,参考文档的 Troubleshooting 一节说明:feast apply会对 ODFV 做"构造随机输入并执行 UDF 推断输出 schema"的校验,复杂逻辑被误伤时可用store.apply([my_odfv], skip_feature_view_validation=True)或feast apply --skip-feature-view-validation跳过(该开关只跳过视图名唯一性、ODFV 校验与 schema 推断,不跳过数据源校验与基础设施更新)。
2.6 从"本地执行"到转换服务器
博客原文明确写道:"Currently, these transformations are executed locally. Future milestones include building a feature transformation server for executing transformations at higher scale."(当前转换在本地执行,未来里程碑是构建更高规模的转换服务器。)从源码结构看,这一路线图已兑现:仓库中存在 sdk/python/feast/transformation_server.py(独立转换服务器)以及FeatureStore.serve_transformations(port)方法(sdk/python/feast/feature_store.py),且在线特征服务器侧具备读时执行转换的能力——参考文档明确指出,即使以transform_on_write=False跳过了写入时转换,特征服务器仍可在 API 调用期间为缺失值或需要实时计算的特征执行转换。
三、Python Feature Server:用 HTTP 把特征服务化
3.1 0.13 的定位
博客原文对 Python feature server 的定位是:提供 HTTP 端点对外提供特征,从而"让任何能发起 HTTP 请求的编程语言都能从 Feast 取特征"。当时的限制与路线:只能本地运行;远程 Serverless 特征服务器"正在开发中";低延迟 Java 特征服务器也在开发中。
3.2 当前实现:FastAPI 端点全貌
当前仓库中,Python 特征服务器实现于 sdk/python/feast/feature_server.py(约 1500 行),基于 FastAPI 构建。从源码看,除博客时期核心的在线取数端点外,端点面已扩展为完整的服务面:
| 端点 | 方法 | 位置 | 说明 |
|---|---|---|---|
/get-online-features | POST | L610 | 核心在线特征检索入口 |
/search | POST | L694 | 在线向量相似度检索 |
/push | POST | L812 | 推送特征数据入在线库 |
/write-to-online-store | POST | L920 | 写入在线库(含 ODFV 写时转换路径) |
/health | GET | L935 | 健康检查 |
/chat | GET/POST | L943 | 会话类接口 |
/materialize、/materialize-incremental | POST | L958、L1017 | 物化任务触发 |
所有端点均挂载Depends(inject_user_details)依赖,意味着鉴权(含 RBAC 权限断言)是服务端内建能力。典型取数请求形如:
curl -X POST http://localhost:6559/get-online-features \ -H 'Content-Type: application/json' \ -d '{ "features": ["driver_hourly_stats:conv_rate"], "entity_rows": [{"driver_id": 1001}] }'(请求体字段结构与文件中GetOnlineFeaturesRequest模型一致;端口按实际启动参数为准。)
启动方式上,sdk/python/feast/feature_store.py 提供FeatureStore.serve(...)系列方法(含serve_ui、serve_registry、serve_offline、serve_transformations等变体),完整的部署形态(包括 Kubernetes Operator + Helm Chart 下的无状态多副本部署)见 docs/reference/feature-servers/python-feature-server.md。
3.3 路线图兑现情况
博客中"coming soon"的两项——Serverless 部署与 Java 特征服务器——在当前仓库中均已可见实体:Java 侧有 java/serving(服务端)与 java/serving-client(客户端),并配套发布脚本 infra/scripts/publish-java-sdk.sh;Go 侧有 go/internal/feast/onlineserving 与嵌入式示例 go/embedded/online_features.go;Kubernetes 侧有 infra/charts/feast-feature-server 与 infra/feast-operator。可以这样理解 0.13 的定位:它确立了"特征即 HTTP 服务"的抽象,后续各语言、各部署形态都是在这一抽象上的横向扩展。
四、Feature Views Without Entities:只按事件时间连接的视图
4.1 概念
博客原文的表述是:这类特征视图允许你声明"仅按事件时间戳连接(joined on event timestamps)"的特征;在定义和取数时都不需要实体/实体值列表。它的价值在于:对于全局统计、环境类、时间序列类特征(没有"哪个 driver / 哪个 user"的维度),用户不必再编造一个假实体(如id = 0)来适配带实体的接口。
4.2 源码实现:DUMMY_ENTITY 占位机制
从源码结构看,"无实体"在 Feast 内部并不是删除实体概念,而是引入了一个占位实体。sdk/python/feast/feature_view.py 中定义了:
# DUMMY_ENTITY is a placeholder entity used in entityless FeatureViews DUMMY_ENTITY_ID = "__dummy_id" DUMMY_ENTITY_NAME = "__dummy" DUMMY_ENTITY_VAL = "" DUMMY_ENTITY = Entity( name=DUMMY_ENTITY_NAME, join_keys=[DUMMY_ENTITY_ID], value_type=ValueType.UNKNOWN, ) DUMMY_ENTITY_FIELD = Field( name=DUMMY_ENTITY_ID, dtype=from_value_type(ValueType.STRING), )也就是说:当用户声明一个不带实体的视图时,registry 内部会为该视图补上名为__dummy、join key 为__dummy_id、取值为空串的占位实体。这样下游的 point-in-time join、在线键拼装(key composition)等统一走实体路径的代码无需为"无实体"写特判分支——实体化抽象得以保持单一。这一设计与 docs/getting-started/concepts/batch-feature-view.md 和 docs/getting-started/concepts/feature-view.md 中关于实体与连接键的描述相衔接。
4.3 定义与取数形态
定义一个无实体特征视图的典型形态(entities置空,内部按上述占位机制处理):
from feast import BatchFeatureView, Field from feast.types import Float64 driver_stats_entityless = BatchFeatureView( batch_source=batch_source, schema=[Field(name="global_conv_rate", dtype=Float64)], entities=[], # 无实体视图:仅按 event_timestamp 参与连接 )取数时对应地省略实体值:get_historical_features不需要为该视图提供实体列,get_online_features的entity_rows中也不需要driver_id之类的键——请求只需给出时间语义,由事件时间戳驱动连接。这与第二章 ODFV 是天然互补的:无实体视图为 ODFV 提供"全局上下文"输入,request data 提供"请求时刻"输入,两者在同一转换函数中组合,覆盖博客列举的交易类、位置/时间类与交叉特征三类用例。
五、总结:0.13 的三项能力在今天的坐标
| 0.13 特性 | 0.13 状态 | 当前仓库对应实现 |
|---|---|---|
| On-demand feature views | 实验,转换本地执行 | sdk/python/feast/on_demand_feature_view.py、docs/reference/beta-on-demand-feature-view.md,执行点扩展至特征服务器/转换服务器(sdk/python/feast/transformation_server.py) |
| Request data 概念 | 首次引入 | sdk/python/feast/data_source.py 的RequestSource |
| Python feature server | 实验,仅本地 | sdk/python/feast/feature_server.py,另见 Java/Go 实现与 Operator/Helm 部署 |
| Feature views without entities | 正式特性 | sdk/python/feast/feature_view.py 的DUMMY_ENTITY占位机制 |
使用注意事项:本文代码示例区分了"0.13 原始写法"与"当前 API 写法",两者类名(RequestDataSource→RequestSource)与 schema 形式(字典 →Field列表)不同,复制前请确认所用版本;ODFV 相关 API 在博客时期即被声明为实验特性,仓库参考文档同样保留 Beta 警示,生产使用前建议先在小数据集上跑通get_historical_features与get_online_features双路径验证(参考文档给出的 7 步开发工作流),再用feast on-demand-feature-views list/describe核对注册结果。
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考