Feast on RHOAI 快速上手:在 Red Hat OpenShift AI 上端到端跑通本地与远程拓扑的 Feature Store
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
本篇技术指南基于仓库中的 RHOAI 快速入门示例(examples/rhoai-quickstart/README.md 与配套笔记本 feast-demo-quickstart.ipynb),演示如何把 Feast 作为特征存储运行在 Red Hat OpenShift AI(RHOAI)平台上。读完本文,你将能够:在 RHOAI 沙箱中创建 Workbench 并运行 Feast 示例笔记本;用 Driver 样例数据完成特征仓库初始化、历史特征生成、批量推理、在线物化与在线特征读取;并进一步搭建 remote online server 与 remote registry server 两种客户端-服务端拓扑,理解各端口、配置项与 Feast SDK 的实际行为。
环境准备:两种运行方式与适用前提
README 给出的前提是:示例以 Jupyter Notebook 展示 Feast 能力,你需要一个可以执行 Jupyter 的环境,二选一:
- 在本地机器运行 Jupyter:适合希望脱离平台独立验证的读者,按 Jupyter 官方文档安装即可。
- 在 RHOAI 平台运行 Jupyter:本快速入门的目标场景,直接在 RHOAI 的 Workbench(Notebook 环境)里执行示例。
如果还没有 RHOAI 集群,README 建议使用 Red Hat OpenShift AI 的开发者沙箱(developer sandbox)来体验本示例,并建议新用户在动手前先用官方渠道的入门视频和产品文档(Red Hat OpenShift AI 文档中的 Data Science Projects 章节)熟悉平台。
在 RHOAI 沙箱上创建 Workbench
README 给出的操作步骤:
- 进入你的项目(namespace)下的Dev页面;
- 打开Workbenches标签页;
- 点击Create Workbench,填写所需信息;
- 点击Create。
Workbench 就绪后,README 给出两种方式把示例导入并运行:
- 克隆仓库:把 Feast 仓库克隆进 Workbench,然后运行
examples/rhoai-quickstart/下的笔记本; - 上传文件:把必要文件上传到已有 Workbench,直接执行笔记本。
笔记本整体流程:围绕 Driver 实体的三段式演示
feast-demo-quickstart.ipynb 以Driver(司机)实体为载体演示 Feast 的核心能力。README 将笔记本归纳为三部分:
- 设置 Feast 仓库:加载样例 driver 数据、生成训练数据集、执行离线推理(批量打分)、把批量特征摄入在线存储,并学习如何用
FeatureView与FeatureService拉取推理特征; - 配置 Remote Online 拓扑:启动 remote online server 与客户端,用 remote online client 实时取特征;
- 配置 Remote Registry 拓扑:启动 remote registry server 与客户端,用 remote registry client 读取 Feast 元数据(FeatureView、FeatureService 等)。
README 同时说明:该笔记本也可以在独立环境(standalone)中完整执行,并不强依赖 RHOAI。
第 1 步:安装 Feast 与版本一致性要求
笔记本的第一个代码单元:
# WE MUST ENSURE PYTHON CONSISTENCY BETWEEN NOTEBOOK AND FEAST SERVERS # LAUNCH THIS NOTEBOOK FROM A CLEAN PYTHON ENVIRONMENT >3.9 %pip install -q feast==0.40.1 # grpcio is needed as a later section to run the feast registry server. %pip install -q grpcio两个要点值得注意:
- Python 版本与一致性:注释明确要求从干净的 Python 环境(>3.9)启动笔记本,且笔记本端与 Feast 服务端之间的 Python 版本必须保持一致。这是远程拓扑能正常工作的隐含前提;
- grpcio 依赖:笔记本后段要用到
feast serve_registry启动 gRPC 协议的 Registry 服务,因此提前安装grpcio。
示例固定的 Feast 版本为 0.40.1,且从输出可见 RHOAI Workbench 的默认内核为 Python 3.9.16,路径位于容器内/opt/app-root/(如/opt/app-root/src/feast)。如果你在本机复现,需把笔记本中出现的绝对路径替换为自己环境下的实际路径。
第 2 步:feast init创建项目并解读 feature_store.yaml
!rm -rf my_feast_project !feast init my_feast_project %cd my_feast_project/feature_repofeast init会打印Creating a new Feast repository in .../my_feast_project。从源码看,init命令定义在 cli.py,支持--minimal与--template选项(模板可选local、aws、gcp、snowflake、postgres等,默认local),实际建库逻辑在 repo_operations.py 的init_repo。本示例未传模板参数,即使用默认 local 模板,生成带示例数据的完整 feature store 骨架。
生成的仓库结构(笔记本中的find输出):
. +-- data +-- |-- driver_stats.parquet +-- __init__.py +-- feature_store.yaml +-- example_repo.py +-- test_workflow.py各文件职责(README/笔记本的说明):
data/:存放本示例的 parquet 样例数据;example_repo.py:创建 Feast 对象(FeatureView、FeatureService、OnDemandFeatureView)的代码;feature_store.yaml:Feast 的全部仓库级配置;test_workflow.py:演示 Feast 关键命令(定义、读取、推送特征)的 Python 代码。
feature_store.yaml实际内容:
project: my_feast_project # By default, the registry is a file (but can be turned into a more scalable SQL-backed registry) registry: data/registry.db # The provider primarily specifies default offline / online stores & storing the registry in a given cloud provider: local online_store: type: sqlite path: data/online_store.db entity_key_serialization_version: 2参数解读:
| 配置项 | 示例值 | 含义 |
|---|---|---|
project | my_feast_project | 项目名,用于命名空间隔离,在线存储的表名会带此前缀(后文可见my_feast_project_driver_hourly_stats) |
registry | data/registry.db | 注册表默认是文件型(SQLite);可换成 SQL 支持的注册表以获得更好的可扩展性 |
provider | local | provider 主要决定默认的离线/在线存储与注册表落盘位置,参见 local provider 文档 |
online_store.type | sqlite | 本地在线存储,见 sqlite online store 参考 |
entity_key_serialization_version | 2 | 实体键序列化版本,决定在线存储中实体键的编码方式 |
feature_store.yaml的完整字段说明可参考 feature-store-yaml 参考文档。
第 3 步:样例数据与 Feast 对象定义
driver_stats.parquet:1807 行的司机小时级统计
data/driver_stats.parquet由feast init生成,是本示例的“离线数据源”。用 pandas 读取后可见 1807 行 × 6 列,schema 如下:
| 列 | 类型 | 说明 |
|---|---|---|
event_timestamp | datetime64[ns, UTC] | 事件时间(司机统计的小时时间戳) |
driver_id | int64 | 实体 join key(1001–1005 等司机 ID) |
conv_rate | float32 | 接单转化率 |
acc_rate | float32 | 事故率 |
avg_daily_trips | int32 | 日均行程数 |
created | datetime | 行写入时间(created timestamp 列) |
FileSource 定义
笔记本展示该数据源在特征定义文件中的写法(路径为 RHOAI 容器内的绝对路径,本机复现时请替换):
driver_stats_source = FileSource( name="driver_hourly_stats_source", path="/opt/app-root/src/feast/examples/rhoai-quickstart/my_feast_project/feature_repo/data/driver_stats.parquet", timestamp_field="event_timestamp", created_timestamp_column="created", )timestamp_field与created_timestamp_column分别指定事件时间与物化时间列,是点-in-time join 与增量物化(后文materialize-incremental)的依据。
feast apply:把代码定义落成注册表对象
在存在feature_store.yaml的目录下执行feast apply。注意笔记本先执行了rm -rf .ipynb_checkpoints/——Jupyter 的 checkpoints 目录会干扰feast apply的仓库扫描。
feast apply的实际输出创建了:
- 实体
driver; - 批量特征视图
driver_hourly_stats与driver_hourly_stats_fresh(后者同时挂了 push source,支持在线推送); - 按需特征视图(OnDemand FeatureView)
transformed_conv_rate与transformed_conv_rate_fresh(对conv_rate做加值变换,输出conv_rate_plus_val1/conv_rate_plus_val2); - 特征服务
driver_activity_v2、driver_activity_v1、driver_activity_v3; - SQLite 在线表
my_feast_project_driver_hourly_stats与my_feast_project_driver_hourly_stats_fresh。
输出中还有一条提示:On demand feature view is an experimental feature... the functionality does not scale well for offline retrieval,即按需特征视图目前是实验性能力,离线批量读取场景的扩展性有限。
第 4 步:生成训练数据集(get_historical_features)
训练数据需要“实体 + 时间戳 + 标签”。笔记本先构造 entity dataframe:
entity_df = pd.DataFrame.from_dict( { # entity's join key -> entity values "driver_id": [1001, 1002, 1003], # "event_timestamp" (reserved key) -> timestamps "event_timestamp": [ datetime(2021, 4, 12, 10, 59, 42), datetime(2021, 4, 12, 8, 12, 10), datetime(2021, 4, 12, 16, 40, 26), ], # (optional) label name -> label values. Feast does not process these "label_driver_reported_satisfaction": [1, 5, 3], # values we're using for an on-demand transformation "val_to_add": [1, 2, 3], "val_to_add_2": [10, 20, 30], } )要点:event_timestamp是保留键;label_*列原样透传,Feast 不处理;val_to_add/val_to_add_2是喂给 OnDemand 变换的输入列。然后:
store = FeatureStore(repo_path=".") training_df = store.get_historical_features( entity_df=entity_df, features=[ "driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate", "driver_hourly_stats:avg_daily_trips", "transformed_conv_rate:conv_rate_plus_val1", "transformed_conv_rate:conv_rate_plus_val2", ], ).to_df()返回的training_df共 10 列:3 个司机 ID 行,包含原始特征(conv_rate/acc_rate/avg_daily_trips,float32/int32)、按需变换特征(conv_rate_plus_val1/conv_rate_plus_val2,float64)以及透传的 label 列。注意按需特征的值等于conv_rate + val_to_add(如 0.058821 + 1 = 1.058821),验证了变换逻辑生效。README 强调带时间戳的原因:我们希望同一司机在不同时间点的特征分别入模(点-in-time join)。
第 5 步:离线推理(批量打分)
批量打分的做法与训练数据生成相同,只是把event_timestamp换成当前时间:
entity_df["event_timestamp"] = pd.to_datetime("now", utc=True) training_df = store.get_historical_features( entity_df=entity_df, features=[ "driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate", "driver_hourly_stats:avg_daily_trips", "transformed_conv_rate:conv_rate_plus_val1", "transformed_conv_rate:conv_rate_plus_val2", ], ).to_df()输出显示三行司机在“now”时刻拿到的特征向量(如 driver 1002 的conv_rate_plus_val1 = 2.311688),说明历史特征接口在推理场景下的用法。
第 6 步:把批量特征物化进在线存储
!feast materialize-incremental $(date -u +"%Y-%m-%dT%H:%M:%S")该命令从离线存储取特征写入在线存储。实际输出:
Materializing 2 feature views to 2024-09-24 18:02:06+00:00 into the sqlite online store. driver_hourly_stats from 2024-09-23 18:02:09+00:00 to 2024-09-24 18:02:06+00:00: 100%|████████...| 5/5 driver_hourly_stats_fresh from 2024-09-23 18:02:09+00:00 to 2024-09-24 18:02:06+00:00: 100%|████████...| 5/5解读:物化窗口为“上次物化时间(2024-09-23 18:02)→ 传入的截止时间(2024-09-24 18:02)”,即增量模式;apply时记录的materialization_intervals决定了起点,这也解释了为什么driver_hourly_stats输出里先打印了一个 0/5 的进度条——首次物化需要分片扫描离线 parquet。OnDemand 特征视图不参与物化(feast apply时也未为它们建表)。
第 7 步:在线特征读取与 FeatureService
直接按特征字符串读取
feature_vector = store.get_online_features( features=[ "driver_hourly_stats:conv_rate", "driver_hourly_stats:acc_rate", "driver_hourly_stats:avg_daily_trips", ], entity_rows=[ # {join_key: entity_value} {"driver_id": 1004}, {"driver_id": 1005}, ], ).to_dict()输出:
{'acc_rate': [0.49898454546928406, 0.2943153381347656], 'avg_daily_trips': [178, 74], 'conv_rate': [0.19129787385463715, 0.5790505409240723], 'driver_id': [1004, 1005]}entity_rows是{join_key: entity_value}字典列表,每个字典对应一个实体的“最新值”读取。
用 FeatureService 解耦消费端
FeatureService用于把“特征视图定义”和“应用需要的特征集合”解耦。笔记本额外定义并注册了driver_activity_v4(指向driver_stats_fresh_fv):
driver_activity_v4 = FeatureService( name="driver_activity_v4", features=[example_repo.driver_stats_fresh_fv], ) feature_store = FeatureStore(".") feature_store.apply([driver_activity_v4])随后可以用 FeatureService 对象(而不仅是字符串列表)调用同一套 API:
feature_vector = feature_store.get_online_features( features=driver_activity_v4, entity_rows=[{"driver_id": 1004}, {"driver_id": 1005}], ).to_dict()结果与直接按字符串读取完全一致,证明两种引用方式等价。
第 8 步:Remote Online 拓扑——feast serve 与 remote 客户端
启动在线服务
在 feature_repo 目录下后台启动在线服务:
import subprocess # Run feast serve in the background feast_online_server_process = subprocess.Popen(["feast", "serve"])输出显示 gunicorn(uvicorn worker)在http://127.0.0.1:6566上监听。随后用ps -ef | grep 'feast serve'确认进程存在。
默认端口6566并非魔法数字:serve命令在 serve.py 中定义,--port默认值即 6566,--host默认127.0.0.1。该命令还有若干对生产环境有价值的参数,默认值如下(摘自同一源码):
| 参数 | 默认值 | 说明 |
|---|---|---|
--host | 127.0.0.1 | 监听地址 |
--port | 6566 | 监听端口 |
--type | http | 服务类型,http或grpc |
--workers | 1 | worker 进程数,-1表示按 CPU 核数自动计算(2*cores+1) |
--worker-connections | 1000 | 每个 worker 的最大并发连接 |
--max-requests | 1000 | worker 处理多少请求后重启(防内存泄漏) |
--max-requests-jitter | 50 | 防 worker 同时重启的抖动量 |
--keep-alive-timeout | 30 | keep-alive 连接超时(秒) |
--registry_ttl_sec | 60 | 注册表元数据刷新周期(秒) |
--cert/--key | 空 | 两者必须成对提供,以启动 TLS 模式(见 TLS 启动指南) |
--metrics | 关闭 | 启用 Metrics Server |
完整服务参考见 feature servers 文档。
构造 remote online 客户端配置
笔记本在remote-online/目录动态生成feature_store.yaml:
project: my_feast_project registry: './../my_feast_project/feature_repo/data/registry.db' # 复用本地注册表,简化演示 provider: local online_store: type: remote path: http://127.0.0.1:6566 entity_key_serialization_version: 3两个关键细节:
online_store.type: remote时,path就是在线服务地址。从源码看,RemoteOnlineStoreConfig 的path默认值正是http://localhost:6566,且还提供cert(TLS 模式下的客户端证书路径)、connection_pool_size(默认 50)、connection_idle_timeout(默认 300 秒)、connection_retries(默认 3 次,指数退避)等连接池参数——笔记本示例未显式配置,全部走默认值;entity_key_serialization_version从主仓库的2变成3:客户端与服务端序列化版本必须对齐,否则实体键无法正确解码。这是 remote 拓扑最容易被忽略的坑。
初始化并读取
online_feature_store_client = FeatureStore('.') online_feature_store_client.apply([]) online_features_stores_client = online_feature_store_client.get_online_features( features=driver_activity_v4, entity_rows=[{"driver_id": 1004}, {"driver_id": 1005}], ).to_dict()输出与本地直读完全一致,同时在线服务日志打印POST /get-online-features HTTP/1.1 200——证明请求确实经由 6566 端口的 HTTP 接口完成,而不是走了本地 SQLite。
笔记本的说明指出:为了演示简洁,此处客户端仍指向my_feast_project的本地注册表,而生产环境可以进一步把 registry、online、offline 三类服务全部远程化,用 feature store client 统一访问(相关拓扑见 生产部署拓扑指南)。
第 9 步:Remote Registry 拓扑——feast serve_registry 与元数据读取
Registry 保存 FeatureService、FeatureView 等全部 Feast 元数据。除直接读本地注册表外,还可以以客户端-服务端模式访问:启动 registry server,客户端通过 remote registry 读取。默认端口为6570。
启动 registry server
先切回my_feast_project/feature_repo目录(registry server 需要在含feature_store.yaml的仓库目录下启动),然后:
import subprocess feast_remote_registry_server_process = subprocess.Popen(["feast", "serve_registry"]) # 用 ps -ef | grep 'feast serve_registry' 确认进程serve_registry命令定义见 serve.py:--port默认DEFAULT_REGISTRY_SERVER_PORT(即 constants.py 中的6570),--grpc/--no-grpc默认开启 gRPC(本例使用的就是 gRPC 通道,这也是第 1 步要装grpcio的原因),--rest-api可额外启用 REST 注册表服务,--cert/--key同样支持 TLS。
构造 remote registry 客户端配置
在remote-registry/目录生成feature_store.yaml:
project: my_feast_project registry: registry_type: remote path: localhost:6570 provider: local online_store: type: remote path: http://127.0.0.1:6566 entity_key_serialization_version: 3注意 registry 的path是host:port形式(gRPC 地址),与 online 的http://...URL 形式不同。
通过远程注册表读取元数据
registry_feature_store_client = FeatureStore('.') registry_feature_store_client.apply([]) # Listing all feature views using remote registry client registry_feature_store_client.list_all_feature_views(allow_cache=False) # Listing all feature services using remote registry client registry_feature_store_client.list_feature_services()list_all_feature_views返回的元数据对象信息量很大,可核对第 3 步feast apply的产物:driver_hourly_stats(entities=['driver']、ttl=1 天、batch_source 为 parquet FileSource、features 为conv_rate-Float32/acc_rate-Float32/avg_daily_trips-Int64、materialization_intervals记录了物化窗口);driver_hourly_stats_fresh(额外带 PUSH_SOURCE 流数据源);两个 OnDemand 特征视图(mode = pandas,source 指向对应 feature view 投影与RequestSource)。list_feature_services则返回driver_activity_v1/v2/v3/v4及其feature_view_projections组成。
收尾:停止服务并核对进程
笔记本最后把两个后台进程终止:
feast_online_server_process.terminate() # Stop the remote Feast online server feast_remote_registry_server_process.terminate() # stops the remote registry server随后ps -ef | grep 'feast serve'应只剩 grep 自身。从输出日志看,feast serve收到 SIGTERM 后 gunicorn 会走Handling signal: term → Shutting down → Application shutdown complete的优雅关闭流程(其中Error while closing socket [Errno 9] Bad file descriptor是容器内常见噪声,可忽略)。
小结与延伸阅读
本示例完整覆盖了 Feast “本地仓库 → 批量摄取 → 在线服务 → 客户端-服务端拓扑” 的闭环,且每个环节都给出了可直接复制的配置与 API 调用。示例目录只有两个文件(README 与 笔记本),其余所有能力(FileSource、FeatureView/OnDemandFeatureView、FeatureService、get_historical_features/get_online_features、materialize-incremental、feast serve/serve_registry)都来自当前仓库的 Python SDK(sdk/python/feast)。若继续深入,建议按以下路径:
- 概念:实体、点-in-time join、特征拉取、feature_store.yaml 全字段参考;
- 服务与部署:feature servers 参考、远程在线存储、生产部署拓扑、TLS 模式启动服务器;
- 同类示例:仓库 examples 目录下的 quickstart、operator-quickstart 等,可对照本例的 local 拓扑理解 Kubernetes Operator 方式部署的差异。
【免费下载链接】feastThe Open Source Feature Store for AI/ML项目地址: https://gitcode.com/GitHub_Trending/fe/feast
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考