forty nodes, quite a few features.
From 22.6 million listings to 40 nodes: how AmazMe reused user histories in its winning Avito ML Cup 2025 solution.
archive date · Around the competition's end in May 2025; exact original publication date is unverified. · text updated 2026-10-14
Avito had about 22.6 million listings. The task was to recommend 40 nodes: groups of listings such as “iPhone 13.” A click on one phone and a contact request for another could point to the same target, but they carried different evidence of intent.
Our team, AmazMe, finished first in Avito ML Cup 2025, with Recall@40 of 0.22375 on the private leaderboard. Grigory Vyaznikov and I presented the solution at Data Fest 2025.
One part of the solution was especially reusable: a learned matrix of node relationships. Feed it click counts and it produces one set of scores. Feed it recent activity or contact days and it produces another. The ranker could compare those views of the same user without a separate model fit for each history.
Pick the right-sized thing to recommend
The catalogue had three levels: 53 broad categories, 424,068 nodes, and about 22.6 million listings. A category might be “Phones,” a node “iPhone 13,” and a listing a particular phone offered in a particular city.
At node level, activity on different listings contributes to the same target. Listing details, such as location, remain available for features later. Modeling every advertisement separately would spread those interactions across a much larger space.
We kept the top 30,000 nodes, generated roughly 300 candidates per user, and used a boosting ranker to choose 40. The ranker could spend its effort on that shortlist, but it could never rescue a relevant node the candidate generators had missed.
We used the last 14 days to validate candidate generation and train the ranker, retaining the baseline’s temporal setup. History was shallow relative to the target period: adding weekly slices would change both the available history and the prediction horizon, muddying the comparison.
One learned matrix, several histories
Linear autoencoders formed a substantial part of the solution. We explored EASE and related models, including SANSA, ELSA, RDLAE, and data-adaptive normalization. The scoring operation is a multiplication: user history X goes through a learned matrix W of node relationships.
Imagine a user clicks listings in the same node ten times. Those clicks could all happen in one afternoon, or they could be spread across several days. The count for that node is ten either way. The number of active days separates the two histories.
We built sparse interaction matrices for counts, contact indicators, time decay, position decay, and the number of distinct days with clicks or contacts. Each keeps a different detail from the event stream. Time decay weights freshness; position decay weights where an event sits in the history. Contact indicators and contact days preserve evidence of attempts to reach sellers.
Once W was fitted, these histories could all use it:
count_scores = X_count @ W
recent_scores = X_time_decay @ W
contact_scores = X_contact_days @ W
The node relationships stay fixed. Our hypothetical user’s ten clicks contribute according to repetition in the count view and according to days in the active-day view. That changes how strongly this part of the history activates the same links. Across a history with several nodes, it can also change their relative contributions.

Producing another signal still costs a sparse input and a multiplication, but the model fit has already happened. These signals can disagree, and the ranker gets to use that disagreement. They also share a limit: changing X cannot teach W a new node relationship.
What goes into W
The choice of linear model determines which relationships W can learn.
EASE sets the weight matrix’s diagonal to zero, so a node cannot reconstruct itself through its own diagonal weight. RDLAE relaxes that constraint:
EASE: diag(W) = 0
RDLAE: diag(W) <= xi
The diagonal is the direct route from a node in the history back to that same node in the score vector. RDLAE’s bound gives the model some freedom to retain this input signal alongside relationships to other nodes.
Data-Adaptive Normalization addresses a different problem: popular nodes and highly active users can dominate interaction statistics, while strong local relationships can keep recommendations in a narrow neighbourhood.
It adjusts scaling on both sides. Item-side normalization changes the influence of popular and rare nodes; user-side normalization changes how histories contribute to learned correlations. This changes the evidence used to fit W. Relaxing the diagonal changes which relationships W may retain. We could then reuse the fitted matrix with the history views above.
A click has several possible meanings
Counts and active days describe repetition, but opening a listing and trying to reach its seller are also different actions. We combined action types and gave contacts more weight than clicks in the linear models.
Contact statistics needed smoothing. A node with very few observations could get an impressive ratio from a small denominator. Bayesian weighting mixes a global ratio with local counts: when counts are small, the prior carries more of the estimate; as observations accumulate, the local ratio gains influence.
The slides compare a fixed regularization parameter C, an adaptive choice based on mean counts, and additive smoothing. These give a sparse contact statistic a usable scale before it becomes model input.
Our enhanced RDLAE recipe also used a logarithmic transform:
X = X_bool * log(1 + bayesian_weighted)
Smoothing stabilizes the statistic. The logarithm compresses large weights so they do not contribute in direct proportion to their size. The binary mask keeps the input tied to observed interactions: a contact weight can strengthen an existing entry without creating one where X_bool is zero.
Give the sequence two views
All these matrices aggregate events. They can preserve recency weights or active-day counts, but they lose the sequence itself. We used SASRec from RePlay on node sequences and category sequences to keep order. Nodes track the specific targets; categories give a broader view of the same activity.
The SASRec Barlow Twins variant, credited in our slides to Ivan Razvorotnev, Evgeny Frolov, and Marina Munkhoeva, creates two randomly augmented views of each sequence. SASRec encodes both, and a cross-correlation matrix compares their embedding dimensions across a batch.
Its loss pushes matching dimensions toward correlation one and different dimensions toward zero: the two views should agree without filling the embedding with redundant information. This supplements the prediction loss, with coefficients controlling the extra objective’s weight.
We then combined the click and contact outputs of this model:
score = (SasRecBT_click + 0.5 * SasRecBT_is_contact)
* sqrt(bayesian_weighted)
The 0.5 weights the contact model’s output in this blend, separately from the action weights in the linear models. The square root brings the Bayesian contact weighting into the score as a multiplier that grows more slowly than the underlying weight.
The slides compare generators on their own in this intermediate evaluation snapshot:
| Generator | Recall@40 |
|---|---|
| SASRec | 0.150572 |
| SASRec Barlow Twins | 0.158431 |
| SASRecBT with the click/contact recipe | 0.183645 |
| EASE DAN | 0.173511 |
| RDLAE with the enhanced input recipe | 0.199538 |
RDLAE had the strongest Recall@40 in this comparison, which helps explain why linear models remained central to candidate generation. These figures are separate from the final private leaderboard and do not isolate each generator’s contribution to the ensemble. A weaker standalone list can still supply candidates or features the others miss.
Let the ranker resolve the disagreements
By the time a candidate reached the final ranker, it had scores from several models and history views. The ranker had 44 features covering those model scores and ranks, contact ratios at node and category level, the user’s last seen or contacted category, and location counts within a node.
A raw score carries a model’s strength of preference; rank gives the candidate’s position in that model’s list. Keeping both lets the boosting model compare signals even when their score scales differ.
Listing detail also returns here. Counts for the user’s most common location within a node add information that disappeared when interactions were aggregated to nodes. Node and category contact statistics give context at two resolutions, while the last seen or contacted category supplies a recent signal.
Training sampled 10 positive and 30 negative examples per user. We trained CatBoostRanker and LightGBM, split folds by user, and ran Optuna for each fold, evaluating Recall@40. A user’s candidates stayed together on one side of the fold split.
For the hypothetical user with ten clicks, the ranker could now compare count-based scores with active-day and recent-history scores, alongside contact evidence, sequence predictions, and location context. It chose the final 40 nodes from roughly 300 candidates, with several views of the same history available for that decision.