A Unified Network for Sequence Modeling and Feature Interaction in Large-scale Recommendation
UniDot is a post-click conversion-rate (pCVR) model that unifies the two model families industrial recommenders usually keep separate — feature-interaction models over multi-field user/item features, and sequential models over user-behavior histories — into a single stackable block. It was the runner-up on the Industrial track of the TAAC × KDD Cup 2026 (Tencent Uni-Rec Challenge), scoring 0.83217 test AUC — 0.037% behind the winning entry.
The design starts from the factorization-machine point of view: the embedding inner product that powers collaborative filtering is the same primitive as attention's query·key scoring, so one dot-product of tokens can underlie both feature interaction and sequence modeling. UniDot follows the SlimPer framework, which frames ranking as iterative refinement of a personalization state.
The paper, slides, and poster are in paper/.
Non-sequential fields and multi-domain behavioral sequences are tokenized into
one shared token space. The model stacks a single macro-block (UniDotBlock)
in which two buses run in parallel and exchange state every layer:
- Token-mixing bus — a swappable cross-token slot (Wukong: parallel LCB + FMB by default) over concatenated user / item / pooled-sequence tokens.
- Sequence-retrieval bus — item tokens cross-attend the behavioral histories (per-domain + a merged cross-domain stream).
- MLP-Mixer fusion (
FuseFFN) — exchanges residual deltas between the two buses each layer. - FM Highway — routes explicit per-layer dot-product interactions (per-sequence dots, aggregated grams, cross-bus user×item dots) around the residual stack straight to the classifier, keeping second-order signal alive at negligible cost.
The sequence side is embedded once per forward pass and shared by all consumers, which bounds inference latency. Training uses a dual sparse/dense optimizer (Adagrad for embedding tables, Muon for matrix weights with an AdamW auxiliary group for 1-D params), an auxiliary conversion-delay head, and multi-path mutual learning (DML) — used by the submitted configuration.
See .claude/CLAUDE.md for a full architectural walkthrough
and paper/ for the formal description.
Official metric is ROC AUC on a 12M-example held-out test set.
| Rank | AUC | Gap to #1 |
|---|---|---|
| 1 | 0.83254 | — |
| 2 — UniDot (ours) | 0.83217 | 0.037% |
| UniDot, single path ★ | 0.83184 | 0.070% |
| 3 | 0.83145 | 0.109% |
| 4 | 0.83080 | 0.174% |
| 5 | 0.83073 | 0.181% |
★ Serving a single path of the multi-path model — at 1× inference cost —
still scores 0.83184, which would itself have placed second. Multi-path training
pulls even a single served model to a better minimum, so DML is a training
boost you don't have to pay for at serving time (config.DML_INFER_MODE='single').
From the competition baseline to the final submission, +1.818 AUC points:
| Step | Test AUC | Δ |
|---|---|---|
| Competition baseline | 0.81398 | — |
| + UniDot architecture | 0.82500 | +1.102 |
| + item-id hash embedding, token/batch tuning | 0.82704 | +0.204 |
| + more FM-Highway dot products, depthwise-conv pre-trunk | 0.82736 | +0.032 |
| + auxiliary delay loss, FAFE, LR tuning | 0.82894 | +0.158 |
| + EMA weights | 0.82993 | +0.099 |
+ scale d_model 64 → 128 |
0.83043 | +0.050 |
| + multi-path DML (N=2) | 0.83196 | +0.153 |
| + all-data retrain | 0.83217 | +0.021 |
The architecture itself is the single largest jump (+1.10), from the released schema alone — no hand-crafted cross features.
Component ablations (tiny mode, single path, d_model=64, in-distribution
held-out — so absolute numbers differ from the leaderboard):
| Variant | AUC | LogLoss | ΔAUC |
|---|---|---|---|
| Full UniDot (6 macro-layers) | 0.83657 | 0.2151 | — |
| − FM Highway (all) | 0.83530 | 0.2159 | −0.127% |
| − cross-bus dots only | 0.83570 | 0.2156 | −0.087% |
| − token-mixing bus (identity) | 0.83590 | 0.2161 | −0.067% |
| − sequence cross-attention | 0.83604 | 0.2153 | −0.053% |
| − merged cross-domain stream | 0.83619 | 0.2152 | −0.038% |
| − multi-channel seq pool | 0.83624 | 0.2155 | −0.033% |
| − FuseFFN (no bus fusion) | 0.83679 | 0.2154 | +0.022% |
The FM Highway is the costliest component to remove (−0.127%), which is the paper's central claim: explicit second-order signal routed around the residual stack is worth keeping.
Honest caveat: dropping FuseFFN slightly raises AUC (+0.022%) while
worsening LogLoss, so that module's contribution is not conclusive. An
input-conditioned (second-order) fuser is the clearest open direction — hence the
--fuse_type {mixer,cross_attn,bilinear,asym} variants in the code.
Scaling: held-out AUC grows log-linearly at ≈+0.0025 AUC per doubling of training data (4M → 32M) and had not saturated. Dense capacity was the productive axis; doubling embedding width alone gave no A/B win.
| Training / test samples | 35M / 12M (round 2) |
| Behavioral domains | 4 (9 / 14 / 12 / 10 fields) + 1 merged stream |
| Largest ID feature | ~9.4M distinct values |
| Total parameters | ~2.1B (embedding-dominated) |
| Dense parameters / FLOPs | 120.6M / 42.5 GFLOP (d_model=128, 2-path) |
| Training hardware | 4 GPUs, effective batch ≈12k |
| Inference on 12M examples | ≈14,500 s (2-path) / ≈7,200 s (single path) |
bf16 autocast costs <0.0001 AUC. The submitted configuration is d_model=128,
6 macro-layers × 2 mix-blocks, sequence windows a:256 b:256 c:512 d:512 plus a
merged abcd:512 stream. Full hyperparameters are in the paper's appendix.
| File | Description |
|---|---|
unified_model.py |
The UnifiedModel (UniDot) architecture — tokenizers, sequence stage, UniDotBlock, FM Highway, classifier head. |
common.py |
Shared building blocks — RoPE/attention (PyTorch SDPA), SwiGLU, compression blocks (LCB/NCB), sequence embedder. |
dataset.py |
PCVRParquetDataset (streaming IterableDataset) + feature schema and time bucketing. |
trainer.py |
Pointwise BCE training loop with AUC + LogLoss eval, dual optimizer, cold restart, EMA, self-contained checkpointing. |
dml.py |
Deep Mutual Learning trainer (DMLTrainer / DMLModel) — N mutually-distilling peers. |
train.py |
Entry point — argument parser, data/model/trainer wiring, build_model_from_args. |
infer.py |
Streaming inference — rebuilds the model from the checkpoint and writes predictions. |
muon.py |
Muon optimizer implementation. |
utils.py |
Logging, early stopping, seeding. |
config.py |
Launch MODE + inference-time defaults. |
run.sh / run_dml.sh |
Reference training launchers (single-model / multi-path). |
Requires Python 3.10+ and a CUDA-capable GPU.
pip install torch pyarrow numpy scikit-learn tqdm tensorboard python-dotenvAttention uses PyTorch's built-in scaled-dot-product attention (SDPA); no FlashAttention install is required.
Optional:
muon-optimize— needed only for--dense_optimizer muon(the default inrun.sh; use--dense_optimizer adamwotherwise).
The model streams Parquet files containing per-event user/item features and
behavioral sequences (see schema.json). The task is binary: predict conversion
(label_type == 2), with roughly 12% positives.
The competition data is 35M training / 12M test events over 142 columns: 54
user-int and 17 user-dense fids, 17 item-int and 4 item-dense fids, 11
pre-trained embedding fids, and 4 behavioral domains (9 / 14 / 12 / 10 fields
each). The full dataset is not redistributed here, but a 1,000-row sample
with the same schema is available for smoke tests:
TAAC2026/second_round_sample_1000
(CC-BY-NC-4.0 — note this is more restrictive than this repo's MIT code license).
Paths are supplied via environment variables (loaded from .env in local
mode). Copy the template and fill in your paths:
cp .env.example .env
# then edit .env:
# TRAIN_DATA_PATH a parquet file or a directory of shards
# TRAIN_CKPT_PATH where checkpoints are written
# TRAIN_LOG_PATH text logs
# TRAIN_TF_EVENTS_PATH TensorBoard events
# SCHEMA_SRC_PARQUET a parquet whose schema matches the training dataconfig.MODE == 'local' (the default) auto-loads .env and pins one GPU. The
launcher picks torchrun DDP when more than one GPU is visible, so pin a single
device for a smoke test. Point --train_data_path at any small parquet with the
expected schema (a single-row-group file needs --split_by_time so the
row-level train/val split has rows on both sides).
Grab the 1,000-row sample:
huggingface-cli download TAAC2026/second_round_sample_1000 --repo-type dataset --local-dir ./sample_dataCUDA_VISIBLE_DEVICES=0 bash run.sh \
--train_data_path ./sample_data \
--split_by_time \
--num_epochs 2 \
--reinit_cardinality_threshold 0 \
--skip_num_slots 0run.sh holds the reference configuration (6 macro-layers, 4 behavioral domains
plus a merged stream, per-position fid compression, DIN conditioning, Muon dense
optimizer). Override any flag on the command line:
bash run.sh # reference config
bash run.sh --num_epochs 20 --dropout_rate 0.1 # override flagsbash run_dml.shrun_dml.sh mirrors run.sh's training recipe and trains N mutually-distilling
peers; at inference config.DML_INFER_MODE selects 'mean' (average all peers,
best accuracy) or 'single' (serve one peer at 1× cost).
infer.py reads paths from environment variables, rebuilds the model from the
checkpoint's train_config.json, and writes predictions:
MODEL_OUTPUT_PATH=/path/to/checkpoint \
EVAL_DATA_PATH=/path/to/test.parquet \
EVAL_RESULT_PATH=/path/to/predictions \
python3 infer.pyInference-time behavior (batch size, torch.compile, autocast, matmul
precision) is configured in config.py.
config.MODE selects the run profile:
local— auto-pins one GPU and loads.env; for demo / single-box development.tiny— real-data path, single GPU, capped sample count for rapid A/B.remote— full multi-GPU DDP run.
run.sh reads MODE and appends the appropriate flags (sample caps, learning
rates, DDP launcher).
If you use this code, please cite the paper:
@inproceedings{unidot2026,
title = {UniDot: A Unified Network for Sequence Modeling and
Feature Interaction in Large-scale Recommendation},
author = {Lin, Rongcheng and Sun, Yan and Zhang, Jamey and
Xiong, Guanglei and Ji, Ivan and Chen, Xianjie and Bu, Shujian},
booktitle = {KDD Cup 2026 Tencent UniRec Challenge Workshop},
year = {2026}
}Released under the MIT License — free to use, modify, and distribute, including for commercial purposes.