Skip to content

Commit 7fa88bd

Browse files
zhouyuchen.zycclaude
andcommitted
feat(models): add WuKong (ICML 2024) via onboarding pipeline
- WuKongLayer: stackable FMB (X X^T -> MLP) + LCB (linear embedding recombination) with residual; registered in custom_objects. - WuKong model: stacks N WuKong layers over field embeddings + DNN head. - Wire registry + tests + docs; mark implemented in candidate KB. - audit: 33/33 models fully wired. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f3ffb0f commit 7fa88bd

14 files changed

Lines changed: 218 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Introduction](https://zhuanlan.zhihu.com/p/53231955)) and [welcome to join us!](
8484
| OneTrans | [industrial 2024][OneTrans: Unified Feature Interaction and Sequence Modeling with One Transformer in Industrial Recommender Systems]() |
8585
| FinalMLP | [AAAI 2023][FinalMLP: An Enhanced Two-Stream MLP Model for CTR Prediction](https://arxiv.org/abs/2304.00902) |
8686
| MaskNet | [DLP-KDD 2021][MaskNet: Introducing Feature-Wise Multiplication to CTR Ranking Models by Instance-Guided Mask](https://arxiv.org/abs/2102.07619) |
87+
| WuKong | [ICML 2024][Wukong: Towards a Scaling Law for Large-Scale Recommendation](https://arxiv.org/abs/2403.02545) |
8788

8889
## Citation
8990

benchmarks/RESULTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,4 @@ Auto-recorded verify outcomes for models added through `benchmarks.onboard`.
172172
- **OneTrans**: see `benchmarks/onboard/reports/OneTrans.md`
173173
- **FinalMLP**: see `benchmarks/onboard/reports/FinalMLP.md`
174174
- **MaskNet**: see `benchmarks/onboard/reports/MaskNet.md`
175+
- **WuKong**: see `benchmarks/onboard/reports/WuKong.md`

benchmarks/onboard/candidates.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
"ref_impl_url": "https://github.com/reczoo/FuxiCTR",
103103
"paper_metric": null,
104104
"difficulty": "medium",
105-
"status": "candidate"
105+
"status": "implemented"
106106
},
107107
{
108108
"name": "FCN",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Verify report: WuKong
2+
3+
- category: **single**
4+
- correctness:
5+
- audit (fully wired): PASS
6+
- unit test: PASS (2 passed, 3 warnings in 18.12s)
7+
- effectiveness (primary metric = AUC):
8+
- DeepFM: AUC=0.5771 LogLoss=0.7297 [ok] (baseline)
9+
- WuKong: AUC=0.5627 LogLoss=0.5764 [ok] <- new model
10+
11+
## Verdict
12+
13+
correctness PASS. WuKong AUC=0.5627; DeepFM baseline AUC=0.5771 (new model is below).

benchmarks/registry.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from deepctr.feature_column import DenseFeat, SparseFeat
1111
from deepctr.models import (AFM, BST, CCPM, DCN, DIEN, DIFM, DIN, DSIN, EDCN, FGCNN, FLEN, FNN,
1212
FwFM, IFM, MLR, MMOE, NFM, ONN, PLE, PNN, WDL, AutoInt, DCNMix,
13-
DeepFEFM, DeepFM, ESMM, FiBiNET, SharedBottom, xDeepFM, OneTrans, FinalMLP, MaskNet)
13+
DeepFEFM, DeepFM, ESMM, FiBiNET, SharedBottom, xDeepFM, OneTrans, FinalMLP, MaskNet, WuKong)
1414

1515
_TF2 = version.parse(tf.__version__) >= version.parse("2.0.0")
1616
_TF28 = version.parse(tf.__version__) >= version.parse("2.8.0")
@@ -68,6 +68,7 @@ def _regroup(feature_columns, n_groups=3):
6868
"EDCN": lambda lin, dnn, task: EDCN(_sparse_only(lin), _sparse_only(dnn), task=task), # sparse-only,
6969
'FinalMLP': lambda lin, dnn, task: FinalMLP(lin, dnn, task=task),
7070
'MaskNet': lambda lin, dnn, task: MaskNet(lin, dnn, task=task),
71+
'WuKong': lambda lin, dnn, task: WuKong(lin, dnn, task=task),
7172
}
7273

7374
# --------------------------------------------------------------------------- #

deepctr/layers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .sequence import (AttentionSequencePoolingLayer, BiasEncoding, BiLSTM,
1313
KMaxPooling, SequencePoolingLayer, WeightedSequenceLayer,
1414
Transformer, DynamicGRU, PositionEncoding)
15+
from .wukong import WuKongLayer
1516
from .utils import NoMask, Hash, Linear, _Add, combined_dnn_input, softmax, reduce_sum, Concat
1617

1718
custom_objects = {'tf': tf,
@@ -57,4 +58,5 @@
5758
'SinusoidalPositionEncoding': SinusoidalPositionEncoding,
5859
'TokenSlice': TokenSlice,
5960
'InteractionAggregation': InteractionAggregation,
61+
'WuKongLayer': WuKongLayer,
6062
}

deepctr/layers/wukong.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# -*- coding:utf-8 -*-
2+
"""Custom layer for the WuKong model: one stackable interaction layer made of a
3+
Factorization-Machine Block (FMB) and a Linear Compression Block (LCB), joined
4+
by a residual connection.
5+
6+
Reference:
7+
Zhang B, et al. Wukong: Towards a Scaling Law for Large-Scale Recommendation.
8+
ICML 2024. (https://arxiv.org/abs/2403.02545)
9+
"""
10+
11+
import tensorflow as tf
12+
from tensorflow.keras.layers import Layer, Dropout
13+
14+
from .normalization import LayerNormalization
15+
16+
17+
class WuKongLayer(Layer):
18+
"""One WuKong interaction layer over a token matrix ``X`` of shape (B, k, d).
19+
20+
* FMB: explicit pairwise interactions ``X X^T`` (B, k, k) → MLP → reshaped to
21+
``(B, n_fmb, d)`` and layer-normed.
22+
* LCB: a learned linear recombination of the input embeddings → ``(B, n_lcb, d)``.
23+
* Output: ``concat([FMB, LCB], axis=1) + X`` (residual), shape ``(B, k, d)``,
24+
so layers stack uniformly.
25+
26+
Input / output shape: ``(batch_size, k, d)``.
27+
"""
28+
29+
def __init__(self, num_fmb_tokens=None, fmb_hidden_dim=128, dropout_rate=0.0,
30+
seed=1024, **kwargs):
31+
super(WuKongLayer, self).__init__(**kwargs)
32+
self.num_fmb_tokens = num_fmb_tokens
33+
self.fmb_hidden_dim = fmb_hidden_dim
34+
self.dropout_rate = dropout_rate
35+
self.seed = seed
36+
self.dropout = Dropout(dropout_rate, seed=seed)
37+
self.ln = LayerNormalization()
38+
39+
def build(self, input_shape):
40+
self.k = int(input_shape[1])
41+
self.d = int(input_shape[2])
42+
# split output tokens so n_fmb + n_lcb == k (keeps the layer stackable)
43+
self.n_fmb = self.num_fmb_tokens if self.num_fmb_tokens else max(1, self.k // 2)
44+
self.n_fmb = min(self.n_fmb, self.k)
45+
self.n_lcb = self.k - self.n_fmb
46+
47+
# FMB: flatten(X X^T) (k*k) -> hidden -> n_fmb*d
48+
self.fmb_w1 = self.add_weight(name="fmb_w1", shape=(self.k * self.k, self.fmb_hidden_dim),
49+
initializer="glorot_uniform")
50+
self.fmb_b1 = self.add_weight(name="fmb_b1", shape=(self.fmb_hidden_dim,),
51+
initializer="zeros")
52+
self.fmb_w2 = self.add_weight(name="fmb_w2", shape=(self.fmb_hidden_dim, self.n_fmb * self.d),
53+
initializer="glorot_uniform")
54+
self.fmb_b2 = self.add_weight(name="fmb_b2", shape=(self.n_fmb * self.d,),
55+
initializer="zeros")
56+
# LCB: (n_lcb, k) linear recombination of input embeddings
57+
if self.n_lcb > 0:
58+
self.lcb_w = self.add_weight(name="lcb_w", shape=(self.n_lcb, self.k),
59+
initializer="glorot_uniform")
60+
super(WuKongLayer, self).build(input_shape)
61+
62+
def call(self, inputs, training=None, **kwargs):
63+
x = inputs # (B, k, d)
64+
# ── FMB ──
65+
inter = tf.matmul(x, x, transpose_b=True) # (B, k, k)
66+
flat = tf.reshape(inter, [-1, self.k * self.k]) # (B, k*k)
67+
h = tf.nn.relu(tf.matmul(flat, self.fmb_w1) + self.fmb_b1)
68+
h = self.dropout(h, training=training)
69+
h = tf.matmul(h, self.fmb_w2) + self.fmb_b2 # (B, n_fmb*d)
70+
fmb = tf.reshape(h, [-1, self.n_fmb, self.d]) # (B, n_fmb, d)
71+
fmb = self.ln(fmb)
72+
73+
# ── LCB ──
74+
if self.n_lcb > 0:
75+
lcb = tf.einsum("lk,bkd->bld", self.lcb_w, x) # (B, n_lcb, d)
76+
out = tf.concat([fmb, lcb], axis=1) # (B, k, d)
77+
else:
78+
out = fmb
79+
80+
# ── residual ──
81+
return out + x
82+
83+
def compute_output_shape(self, input_shape):
84+
return input_shape
85+
86+
def get_config(self):
87+
config = {"num_fmb_tokens": self.num_fmb_tokens, "fmb_hidden_dim": self.fmb_hidden_dim,
88+
"dropout_rate": self.dropout_rate, "seed": self.seed}
89+
base = super(WuKongLayer, self).get_config()
90+
base.update(config)
91+
return base

deepctr/models/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from .finalmlp import FinalMLP
2626
from .masknet import MaskNet
27+
from .wukong import WuKong
2728
__all__ = ["AFM", "CCPM", "DCN", "IFM", "DIFM", "DCNMix", "MLR", "DeepFM", "MLR", "NFM", "DIN", "DIEN", "FNN", "PNN",
2829
"WDL", "xDeepFM", "AutoInt", "ONN", "FGCNN", "DSIN", "FiBiNET", 'FLEN', "FwFM", "BST", "DeepFEFM",
29-
"SharedBottom", "ESMM", "MMOE", "PLE", 'EDCN', "OneTrans", "FinalMLP", "MaskNet"]
30+
"SharedBottom", "ESMM", "MMOE", "PLE", 'EDCN', "OneTrans", "FinalMLP", "MaskNet", "WuKong"]

deepctr/models/wukong.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# -*- coding:utf-8 -*-
2+
"""
3+
WuKong
4+
5+
Reference:
6+
[1] Zhang B, Luo L, Chen Y, et al. Wukong: Towards a Scaling Law for
7+
Large-Scale Recommendation. ICML 2024. (https://arxiv.org/abs/2403.02545)
8+
"""
9+
10+
from tensorflow.keras.models import Model
11+
from tensorflow.keras.layers import Dense, Flatten
12+
13+
from ..feature_column import build_input_features, get_linear_logit, input_from_feature_columns
14+
from ..layers.core import PredictionLayer, DNN
15+
from ..layers.utils import concat_func, combined_dnn_input, add_func
16+
from ..layers.wukong import WuKongLayer
17+
18+
19+
def WuKong(linear_feature_columns, dnn_feature_columns, num_layers=3, fmb_hidden_dim=128,
20+
num_fmb_tokens=None, dnn_hidden_units=(128, 64), l2_reg_linear=0.00001,
21+
l2_reg_embedding=0.00001, l2_reg_dnn=0, seed=1024, dnn_dropout=0,
22+
dnn_activation='relu', dnn_use_bn=False, task='binary'):
23+
"""Instantiates the WuKong architecture.
24+
25+
Stacks ``num_layers`` WuKong interaction layers (Factorization-Machine Block +
26+
Linear Compression Block, with residuals) over the stacked field embeddings,
27+
then a DNN head. All sparse / var-len features must share one ``embedding_dim``
28+
(the field embeddings are stacked into a (B, num_fields, d) token matrix).
29+
30+
:param linear_feature_columns: features for the linear part.
31+
:param dnn_feature_columns: features for the deep/interaction part.
32+
:param num_layers: number of stacked WuKong layers.
33+
:param fmb_hidden_dim: hidden size of each FMB's MLP.
34+
:param num_fmb_tokens: FMB output tokens per layer (default: half the fields).
35+
:param dnn_hidden_units: layer sizes of the DNN head.
36+
:param task: ``"binary"`` or ``"regression"``.
37+
:return: A Keras model instance.
38+
"""
39+
features = build_input_features(linear_feature_columns + dnn_feature_columns)
40+
inputs_list = list(features.values())
41+
42+
linear_logit = get_linear_logit(features, linear_feature_columns, seed=seed, prefix='linear',
43+
l2_reg=l2_reg_linear)
44+
45+
sparse_embedding_list, dense_value_list = input_from_feature_columns(
46+
features, dnn_feature_columns, l2_reg_embedding, seed)
47+
if not sparse_embedding_list:
48+
raise ValueError("WuKong requires at least one embedding (sparse/var-len) feature.")
49+
50+
# stack field embeddings into a (B, num_fields, d) token matrix
51+
x = concat_func(sparse_embedding_list, axis=1)
52+
for _ in range(num_layers):
53+
x = WuKongLayer(num_fmb_tokens=num_fmb_tokens, fmb_hidden_dim=fmb_hidden_dim,
54+
dropout_rate=dnn_dropout, seed=seed)(x)
55+
56+
interaction_out = Flatten()(x)
57+
dnn_input = combined_dnn_input([interaction_out], dense_value_list)
58+
dnn_out = DNN(dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn,
59+
seed=seed)(dnn_input)
60+
dnn_logit = Dense(1, use_bias=False)(dnn_out)
61+
62+
final_logit = add_func([linear_logit, dnn_logit])
63+
output = PredictionLayer(task)(final_logit)
64+
65+
model = Model(inputs=inputs_list, outputs=output)
66+
return model

docs/source/Features.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,12 @@ Applies instance-guided multiplicative masks on feature embeddings / hidden laye
327327

328328
[MaskNet: Introducing Feature-Wise Multiplication to CTR Ranking Models by Instance-Guided Mask](https://arxiv.org/abs/2102.07619)
329329

330+
### WuKong
331+
332+
Stacks factorization-machine blocks (LCB/FMB) with MLPs into a dense architecture whose quality scales predictably with model size (a recommendation scaling law).
333+
334+
[Wukong: Towards a Scaling Law for Large-Scale Recommendation](https://arxiv.org/abs/2403.02545)
335+
330336
## Sequence Models
331337

332338
### DIN (Deep Interest Network)

0 commit comments

Comments
 (0)