Repository navigation
[doc] align feature docs with fg behavior - #674
Conversation
cc299c3 to
49f007a
Compare
| # limitations under the License. | ||
|
|
||
| __version__ = "1.4.8" | ||
| __version__ = "1.4.11" |
There was a problem hiding this comment.
This doc-only PR bumps __version__ 1.4.8 → 1.4.11, skipping 1.4.9/1.4.10, while the PR body says "Docs and proto comments only". Master is still at 1.4.8, so merging publishes the 3-step jump. Intentional sync with an internal release train, or a rebase artifact? If intentional, worth a mention in the PR body; otherwise drop it or make it a single increment.
| - 其余配置同IdFeature | ||
| - Mask为字符串类型时,只有空串和`false`表示False,其余取值(包括`"0"`)都是True; | ||
| 需要用0/1做Mask时,输入要是整型数组而不是字符串 | ||
| - 其余配置同IdFeature,但`value_dim`只能是1:FG内部会把该特征当作序列特征处理, |
There was a problem hiding this comment.
This new claim ("value_dim只能是1 … value_dim=0时会被重置为1,每个元素只保留第一个值") is contradicted by the repo's own pyfg-driven tests:
bool_mask_feature_test.pybuilds a non-sequence BoolMaskFeature withvalue_dimunset andseparator: ",", andIdFeature.value_dimreturns 0 in that case (id_feature.py:49-57), so the fg json carriesvalue_dim: 0.- In
FG_NORMALmode,Feature._parse()runs the actual engine:self._fg_op.process_arrow(...)where_fg_op = pyfg.FgArrowHandler(...)(feature.py:966-967, ~1023) — pyfg 1.0.6, the same version this PR says it re-measured with (requirements/runtime.txt:17). - The green tests expect multi-value output:
"1,2,3,4"+ mask"true,false,true,false"→ values[1, 3], lengths[2]— two values kept from one element, not just the first. The untouched example table right below (输出["123", "90"]) shows the same.
If the 0→1 rewrite is real, it must be scoped (e.g. only for bool mask as a sequence sub-feature, or only in the online pai-fg build vs pyfg) — as written it contradicts the checkout's passing tests and the table ten lines below, and would push users away from multi-value bool-mask IDs that demonstrably work in FG_NORMAL. If training pyfg and serving fg genuinely differ here, that's a train/serve skew the doc should say explicitly. The same claim in feature.proto:944-945 needs the same treatment.
| - **kv_delimiter**: 可选项,指定输入特征中的kv对之间的分隔符,默认为 ":",只能是单个符号 | ||
|
|
||
| - 其余配置同RawFeature | ||
| - **normalizer**: 可选项,连续值变换方式,用法同RawFeature |
There was a problem hiding this comment.
This new bullet advertises a knob that tzrec silently drops: KvDotProduct._fg_json (tzrec/features/kv_dot_product.py:64-84) never copies normalizer into the fg json (no normalizer reference in the file at all), unlike raw_feature.py, overlap_feature.py, combine_feature.py, lookup_feature.py, match_feature.py, custom_feature.py, and there is no generic injection in BaseFeature.fg_json (feature.py:1025-1040). A user setting method=log10 here gets a silent no-op → mis-scaled dense values / mis-bucketization, with no error. Either drop this bullet, or note that it's currently ignored — the real fix (forwarding normalizer in _fg_json) is a code change outside this doc PR's scope and deserves its own issue/PR.
PS, pre-existing and out of scope but squarely in this audit's territory: the kv_delimiter bullet two lines above says the kv separator is configurable (默认 ":"), but the proto field is optional float kv_delimiter = 8 (feature.proto:794) — a string like "|" can't even be parsed into it, and kv_dot_product.py:75-76 would forward a float to fg. Worth a follow-up.
| | proximity_avg_dist | 计算query term在title中的邻近度 (average pairwise distance) | 取值为[0, length(title)+1], length(title)+1表示没有匹配的term | | ||
|
|
||
| - 其余配置同RawFeature | ||
| - 其余配置同RawFeature,但不支持`default_value`、`value_dim`和`hash_bucket_size`等类别型分箱。 |
There was a problem hiding this comment.
This section still contains the same class of inconsistency the PR fixes elsewhere: the method bullet above (line 560) still reads "可选 query_common_ratio | title_common_ratio | is_contain | is_equal" — 4 of the 9 methods — while the table right below lists all 9 and this PR updates the proto comment (feature.proto:638-640) to the full list. Worth updating the bullet too (or having it defer to the table), since the PR body specifically calls out this 4-of-9 gap.
| - **skey**: 特征FG所依赖子键字段的来源。可以设置为ALL,将指定pkey下面所有skey的值 | ||
| - **combiner**: 如果key为多值,可以设置combiner来对查找的值进行聚合,支持`sum`/`mean`/`min`/`max` | ||
| - **need_discrete**: 查到的值是否为离散值,默认为false | ||
| - **need_discrete**: 查到的值是否为离散值,默认为false。配置为true时输出必须是字符串,即必须同时配置`hash_bucket_size`/`vocab_list`/`vocab_dict`/`vocab_file`之一,否则FG会报错 |
There was a problem hiding this comment.
The enumeration misses num_buckets: tzrec's own emitter treats it as a valid discrete bucketizer for match features — match_feature.py:150-154 sets value_type: "string" + needDiscrete: true when num_buckets is configured — so need_discrete=true + num_buckets produces string output and does not hit the fg error. The very next bullet (prefix suppressed "配置num_buckets时不加") implies that combination exists. Suggest "必须同时配置hash_bucket_size/num_buckets/vocab_list/vocab_dict/vocab_file之一". The proto comment (feature.proto:501-503, "a hash/vocab bucketizer must be set") has the same gap.
| // match map value is sparse or numeric, | ||
| // when need_discrete is true, combiner will be empty string | ||
| // match map value is sparse or numeric. when need_discrete is true, the | ||
| // match output must be a string, i.e. a hash/vocab bucketizer must be set, |
There was a problem hiding this comment.
"hash/vocab bucketizer" excludes num_buckets, but tzrec's emitter treats num_buckets as a string-output discrete bucketizer for match features (match_feature.py:150-154 sets value_type="string" + needDiscrete=true), so need_discrete=true + num_buckets does not raise. Suggest "a bucketizer (hash_bucket_size/num_buckets/vocab_*) must be set". Minor grammar: "otherwise fg raises" reads truncated — "otherwise fg raises an error". (Same gap in the feature.md bullet; see the comment there.)
| // id value dimensions, default = 0, when use in seq, default = 1 | ||
| // if value_dim = 0, it supports id with multi-value | ||
| // id value dimensions, fg handles bool mask as a sequence feature and | ||
| // rewrites value_dim 0 to 1, so multi-value input keeps its first value only |
There was a problem hiding this comment.
Same concern as the feature.md bullet: bool_mask_feature_test.py runs pyfg 1.0.6 via FG_NORMAL (_parse → pyfg.FgArrowHandler.process_arrow) with value_dim: 0 in the fg json and expects multi-value output ([1, 3], lengths [2]), so "rewrites value_dim 0 to 1 … keeps its first value only" is contradicted by the checkout's green tests for the non-sequence case. Please re-verify and scope the claim (sequence sub-feature? online pai-fg build?) or drop it.
Review summary (multi-area: code quality, performance, test coverage, security, docs accuracy)Overall a careful, well-researched audit — most changed lines check out against the code (verified e.g.
Optional follow-ups (non-blocking): Security review found nothing noteworthy. Note: the dedicated documentation-accuracy pass over 🤖 Generated with Claude Code |
| bool_mask_feature { | ||
| feature_name: "query_doc_sim" | ||
| expression: ["user:click_items", "item:is_valid"] | ||
| embedding_dim: 16 |
There was a problem hiding this comment.
Adding embedding_dim makes this example parse/serialize, but it still can't run in a pipeline: BoolMaskFeature extends IdFeature and is always sparse (id_feature.py:64-69), and with no hash_bucket_size/num_buckets/vocab_*/zch, num_embeddings raises ValueError("...must set hash_bucket_size or num_buckets or vocab_list or vocab_dict or zch.zch_size") (id_feature.py:86-95) as soon as embedding configs are built. Both unit tests always set a bucketizer, and the sequence example below sets num_buckets: 1000. Since the PR's stated goal is that copying the example into a pipeline config works, consider adding e.g. hash_bucket_size: 1000 here too.
| - 如果需要跟tensorflow的hash保持一致,可以设置环境变量 USE_FARM_HASH_TO_BUCKETIZE=true | ||
|
|
||
| - **num_buckets**: buckets数量, 仅仅当输入是integer类型时,可以使用num_buckets | ||
| - **num_buckets**: buckets数量, 仅仅当输入是integer类型时,可以使用num_buckets。输入为空或者不在\[0, num_buckets)范围内时,分箱结果为0,并打一条ERROR日志 |
There was a problem hiding this comment.
The "输入为空…分箱结果为0" half contradicts the repo's own pyfg-driven test: test_id_feature_with_num_buckets (id_feature_test.py:525-563, FG_NORMAL → real pyfg 1.0.6) feeds "" with num_buckets: 100 and expects it dropped — values [0,1,2], lengths [2,0,1] — with the IdFeature default default_value: ""; with default_value: "3" it yields 3, not 0. Empty input only lands in bucket 0 if default_value is explicitly "0". Suggest scoping the bucket-0 + ERROR-log claim to values that actually reach bucketization (non-integer / out-of-range), and letting empty input follow the documented default_value semantics above.
| - **default_value**: 没查到时的取值,默认为`0`。注意配置为空字符串时FG会不输出任何值,离散特征在模型中的嵌入为零向量 | ||
| - **need_discrete**: 查到的值是否为离散值,默认为false | ||
| - **need_key**: 查到的值是否拼接key作为前缀,默认为false | ||
| - **need_key**: 查到的值是否拼接key作为前缀,默认为false。只在输出是字符串(即配置了`hash_bucket_size`/`vocab_list`/`vocab_dict`/`vocab_file`或`need_discrete=true`)时生效,否则FG会忽略它并打一条WARNING日志 |
There was a problem hiding this comment.
Same enumeration gap as the MatchFeature need_discrete bullet: num_buckets also produces string output for LookupFeature — lookup_feature.py:160-162 sets value_type: "string" for num_buckets, which then forces needDiscrete: true (lines 180-181) — so need_key does take effect with num_buckets, contrary to this list. Consider adding num_buckets here too.
PS (also applies to the default_value bullet on line 315 and the ExprFeature one on line 386): "配置为空字符串时FG会不输出任何值" is only true for non-sequence usage. For sequence features tzrec rewrites an empty default_value to "0" before fg ever sees it (BaseFeature.default_value, feature.py:557-571, with a WARNING; and the fg_json wrapper at feature.py:1030-1034), so a one-clause caveat would make those bullets accurate for sequence_* usage.
|
Follow-up: the documentation-accuracy pass has now completed (all five review areas finished). Three additional findings posted inline on
Housekeeping note: one sub-review initially flagged the two required-field example fixes ( Net: no security or wire-format concerns (proto diff is comment-only). The items to resolve before merge are the BoolMaskFeature |
Fix the places where the feature docs and proto comments describe fg behavior that fg does not implement, and document the behaviors that silently change feature values. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VkWBjeAQP8rcsutwL6rkfV
49f007a to
31613bc
Compare
# Conflicts: # docs/source/feature/feature.md
b0235cb to
68c58c8
Compare
Doc-only follow-up to the same fg audit as #672 and #673, rebased onto master now that both have landed. Everything here was checked against the fg C++ (the fg markdown docs contain errors of their own) and, where behavior was involved, measured with pyfg 1.0.6.
Corrections — the docs described behavior fg does not have
combinerbullet removed: the proto field and the emitter line are both commented out, and fg's match feature has no combiner concept at all.need_key/show_pkey/show_skeyonly work on string output — fg logs "will be ignored, because the value_type is not string" and drops them otherwise, and the match prefixes only exist on the discrete path. Also noted that fg's own default forshow_pkey/show_skeyis true for ahitmatch withneed_discrete, so migrating a hand-written fg json changes the values.need_discreterequires a string output — with a numeric output fg raisesneed_discrete set, but match output type is not string.separatoris additive — fg splits on\x1dand the configured character, it does not replace it.StringSplitV2re-adds\x1deven where the call site passes a single char, and the AVX-512 variant ORs the two masks, so this holds on the batch path tzrec uses, not only in the single-record API.separatoron RawFeature only applies whenvalue_dim > 1— and the input must split into exactlyvalue_dimvalues.normalizerruns after the combiner —log10(sum(x)), notsum(log10(x)), unlike RawFeature which normalizes per value.num_bucketsadditionally truncates the aggregate to an integer.value_mapmisses are skipped, not counted as 0, so they changemeanandcount.methodbullet listed 4 of the 9 methods the table below it documents.value_dimto 1; measured,value_dim0 and 1 give identical output.value_dimdefaults to 1 here (fg's own default is 0), so a multi-value operator output is truncated unlessvalue_dim: 0is set.default_valueon LookupFeature/ExprFeature defaults to"0"here while fg's native default is empty, which means "drop the value" — worth knowing because a NaN/Inf expression result becomes a literal 0. Sequence features are called out separately: tzrec rewrites an empty default to"0"before fg sees it.Additions — behaviors that were documented nowhere
boundariesbucket intervals (left-closed, right-open, measured);num_bucketsout-of-range values collapsing to bucket 0; LookupFeature's un-aggregated multi-value output via an empty combiner; bool mask string truthiness (only""andfalseare false, so a"1,0,1,0"string mask keeps everything); ExprFeature's%operator and temp-variable/comma expressions;fill_missingnot applying to broadcast length-1 variables; CustomFeature'svalue_typeselection,normalizerbeing ignored for string output,is_op_thread_safe, and*_fileparams not being collected as assets; and the fact that OverlapFeature/KvDotProduct support far less of RawFeature's surface than "其余配置同RawFeature" implies (including fg's-1empty-input default forindex_of).Examples that were not valid configs
ExprFeature's sequence-mask example omittedsequence_delimand theBoolMaskFeatureexample omittedembedding_dim, both proto2requiredfields:text_format.Parseaccepts them and the getters fall back, butIsInitialized()is False andSerializeToString()raises, so copying either example into a pipeline config breaks as soon as the config is serialized. The BoolMask example additionally had no bucketizer, sonum_embeddingsraisedValueErroras soon as embedding configs were built — it now setshash_bucket_size.Review follow-ups
Corrections applied from the review, each re-measured first: the
num_bucketsbucket-0 claim now covers only out-of-range values, since an empty input followsdefault_value(""is dropped,default_value: "3"yields 3);num_bucketsadded to theneed_discrete/need_keystring-output enumerations, sincematch_feature.pyandlookup_feature.pyboth emitvalue_type: "string"for it; the OverlapFeaturemethodbullet; and the BoolMaskFeaturevalue_dimclaim, which measurement showed was about the value count per element, not aboutvalue_dimat all. Two other review items resolved themselves in the merge: the KvDotProductnormalizerbullet is accurate now that #673 emits it, and the version bump is gone.Test Plan
Docs and proto comments only — no emitted fg json changes. Every
feature_configs { ... }block infeature.mdandfaq.mdwas parsed withtext_formatand serialized, which is how the invalid examples were found.tzrec/features/*_test.pypass,pre-commit run --files <changed>(mdformat included) andpyrefly checkare clean, and the proto regenerates.🤖 Generated with Claude Code
https://claude.ai/code/session_01VkWBjeAQP8rcsutwL6rkfV