0x00

리서치 / 일일 리포트

  • 읽은 자료 2건
  • 정본 반영 2개 파일
  • 레이더 1건
한 줄 요약X가 For You 랭킹 모델의 학습에 광고 구매액 loss를 연결하고(기본 가중 0), 클릭 후 체류 시간 head를 기본 설정부터 10초 기준 이진 분류로 바꿨다.

오늘 함께 본 다른 변화는 다음과 같다.

  • X는 같은 커밋에서 성별 피처의 임베딩 인덱스 매핑을 소리 없이 바꿨다.
  • X의 광고 브랜드 안전 V2 게이트는 미러 값이 true에서 false로 롤백됐다.
  • Meta 저자들이 Facebook Marketplace 후보 생성 단계의 카테고리 상한을 유저마다 다르게 거는 PCap 논문을 냈다.

1광고 구매액 loss를 학습에 연결하고, 체류 시간 head를 이진 분류로 바꿨다 — X

자료 xai-org/x-algorithm@fad2f71 — GitHub 커밋, 2026-09-16 01:11Z, 소스코드. 바뀐 파일은 26개(+552/−233)다. 이 노트들은 4세대 공개 뒤 매일 이어지는 X 커밋에 차례로 번호를 붙여 부르며, 이 커밋은 4세대+22다.

배경 Phoenix(X For You 피드의 랭킹 트랜스포머)는 행동마다 출력 head를 두고, 체류 시간이나 구매액 같은 연속값은 continuous head로 예측한다. 4세대+21(2d4a03c)에서는 광고 구매액 continuous head의 wire만 열렸다. 이 head는 ADS_WEB_CT_PURCHASE_VALUE이고, 요청 필드는 PurchaseValueBaseline이다. 이번 커밋은 세 가지 옵트인 연결(purchase value, binary loss, click_dwell feature)을 함께 넣었고, 그중 이 head를 학습 loss까지 잇는 것이 가장 눈에 띄는 변경이다.

새로 알게 된 것A. 광고 구매액 loss가 학습 그래프에 연결됐지만, 기본 가중은 0이다

  • Phoenix 학습 그래프에 광고 구매액을 예측하는 Huber loss가 정식으로 연결됐고, 기본 가중은 0이다 확인 — phoenix/xrex/models/loss_recsys.py, phoenix/xrex/models/recsys_model.py.
    • loss 함수는 purchase_value_loss_compute다.
    • target은 광고주 28일 평균 대비 ratio이고, raw USD가 아니다.
    • valid mask는 여덟 항의 AND다. 여덟 항은 label_valid & padding & !negative_sample & delayed_mask & has_click & has_purchase & keeper_mask & isfinite/positive on ratio·baseline이다.
    • 활성 조건을 강제하는 checker validate_purchase_value가 다음 네 가지를 검증한다.
      • 슬롯 5가 실제로 있는지
      • 액션 head ADS_PURCHASE_CONVERSION이 실제로 있는지
      • Huber delta가 positive이고 finite인지
      • 같은 슬롯에 generic continuous loss가 붙어 있지 않은지
    • 서빙 활성화 함수는 Softplus다.
  • 광고주 baseline이 학습 데이터에도 실린다 확인 — phoenix/xrex/data/recsys/recsys_batch.py, phoenix/xrex/data/parquet_recsys.py, phoenix/xrex/data/recsys/feature_config.py.
    • PostSeq에 value_label_valid: bool과 value_baseline_mean_usd: float32 두 슬롯이 추가됐다.
    • Parquet 리더 _read_value_labels가 valueLabelValidSeq·valueBaselineMeanUsdSeq 컬럼을 읽어 (batch, seq) 배열로 푼다. 두 컬럼의 타입은 각각 pa.list_(bool_/float32, seq_len)이다.

새로 알게 된 것B. 이진 분류 loss 종류가 새로 생겼고, 클릭 체류 시간 head가 기본 설정에서 이를 쓴다

  • continuous loss에 새 종류 binary가 생겨, 특정 continuous head를 threshold 기준으로 이진화한 뒤 sigmoid와 BCE로 학습할 수 있게 됐다 확인 — phoenix/xrex/configs/xrecsys.py, phoenix/xrex/models/recsys_feature_prep.py.
    • 첫 사용처는 click_dwell_time이다(threshold=10s, default weight=1.0).
    • 기본 학습 config는 이번 커밋 안에서 click_dwell_time head를 10초 기준 이진 분류로 학습하게 됐다. _home_direct_packed_base의 default가 click_dwell_loss_weight: 1.0과 enable_click_dwell_time: True(feature-prep)로 바뀌었다.
    • ContinuousActionLossConfig가 새 loss_type="binary"를 받고, binary_threshold: float = 10.0 필드가 추가됐다.
    • 활성화는 sigmoid로 강제된다. output_cap은 출력이 확률 [0,1]이라 assert로 금지된다.
    • loss는 binary_threshold_loss_compute(gt_raw, logit, valid_mask, negative_sample_mask, threshold=10.0, mask_negatives=True, raw_weights=None)가 계산한다. 이 함수는 gt_binary = (gt_raw > threshold).float()로 정답을 이진화한 뒤 optax sigmoid BCE로 loss를 낸다.
    • history encoder는 history_seq.continuous_actions[..., 2]를 log1p(clip(x, 0, 60)) / log1p(60)로 정규화하고, hist_click_dwell_time_vec 임베딩과 곱해 유저 이력 벡터에 더한다.
  • continuous head 출력을 푸는 함수가 binary head의 raw logit도 함께 돌려주도록 바뀌었다 확인 — phoenix/xrex/models/recsys_model.py.
    • decode_continuous의 반환형이 jax.Array 하나에서 (activated: jax.Array, head_logits_by_index: dict[int, jax.Array]) 2-tuple로 바뀌었다(2283-2285줄).
    • 이를 부르는 모델 __call__의 반환이 2-tuple에서 (logits, continuous_predictions, head_logits_by_index) 3-tuple로 바뀌었다.
    • binary loss는 sigmoid 이전의 raw logit이 있어야 수치적으로 안정되게 계산할 수 있다. 그래서 binary head마다 raw logit tensor를 함께 보낸다.
    • 서빙 경로도 __call__의 3-tuple을 받지만, dict는 _head_logits로 받아 버린다.
  • 서빙 때 sigmoid head의 예측값은 원단위로 되돌리지만, binary head는 예외다 확인 — phoenix/xrex/models/recsys_model.py.
    • _rescale_sigmoid_heads_for_inference가 sigmoid head의 예측값에 norm_scale을 곱해 원단위로 되돌린다.
    • 예외 조건은 if loss_config.activation != "sigmoid" or loss_config.loss_type == "binary": continue다.
    • 즉 binary head는 서빙에서 확률 [0,1] 그대로 남는다.

새로 알게 된 것C. 성별 피처의 임베딩 인덱스가 하드코딩(Male=2·Female=1)에서 proto enum 값 그대로로 소리 없이 바뀌었다

  • 성별 피처 user_gender의 카테고리컬 임베딩 인덱스가 하드코딩(Male=2, Female=1, other=0)에서 proto enum 정수를 그대로 캐스팅하는 방식으로 소리 없이 바뀌었다 확인 — phoenix/crates/common/xai-recsys/src/util.rs.
    • 새 회귀 테스트 user_gender_is_raw_proto_enum은 세 값(Male·Female·Unknown) 모두 g as i16와 정확히 일치해야 한다고 고정한다.

새로 알게 된 것D. 광고 브랜드 안전 V2 게이트가 미러에서 true → false로 롤백됐다

  • 광고 브랜드 안전 V2 게이트(EnableAdsBrandSafetyVerdictV2)의 미러 값이 true → false로 롤백됐다 확인 — home-mixer/params/param.rs (+2/−2).
    • EnableAdsBrandSafetyVerdictV2는 home-mixer(For You 피드를 조립하는 서비스)의 param 파일에 있다. 이 파일은 프로덕션 설정값을 크론으로 옮겨 오는 미러다.
    • mirror sync 시각은 2026-09-14T16:22:15Z → 2026-09-15T16:25:02Z로 바뀌었다.

새로 알게 된 것E. grox(Grok 기반 nearline 분류기)의 답글 스팸 분류기가 스레드 팔로워 150k를 넘으면 전용 파인튜닝 모델을 쓰고, task 자격 임계는 150k → 180k로 올랐다

  • 답글 스팸 분류기가 스레드 팔로워 수에 따라 판정 모델을 oai-gemma4-26b-reply-spam과 기본 모델로 나누고, task_filter 임계도 올렸다 확인 — 세 파일에 걸친 변경이다.
    • 근거 파일은 grox/flows/reply_spam/constants.py, grox/flows/reply_spam/classifier_simple_reply_scorer.py, grox/flows/reply_spam/task_filter.py다.
    • 상수 GEMMA_REPLY_SPAM = "oai-gemma4-26b-reply-spam"이 새로 생겼다. 이 모델은 전용 파인튜닝 변종으로 추정된다. GEMMA_REPLY_SPAM_MIN_FOLLOWERS = 150_000은 모델 라우팅에, GROK_GEMMA_FOLLOWER_SPLIT = 180_000은 task 자격에 쓴다.
    • 판정 모델은 SimpleReplyScorer._sampler(post)가 고른다. max(post.ancestors[0].user.follower_count, post.ancestors[-1].user.follower_count) > 150_000이면 oai_gemma4_reply_spam을, 그 이하면 기본 oai_gemma4를 쓴다.
    • task filter 두 곳의 팔로워 임계가 150000 → GROK_GEMMA_FOLLOWER_SPLIT(180000)로 바뀌었다. 4세대+17(49815da)의 150k에서 180k로 다시 올린 것이다.
      • 바뀐 상수는 TaskSpamFilter.FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION과 TaskReplyRankingFilter.FOLLOWER_COUNT_THRESHOLD_FOR_REPLY_RANKING이다.

새로 알게 된 것그 밖의 변경

  • continuous head마다 clicked-only pairwise concordance 지표인 XAUC가 붙었다 확인 — phoenix/xrex/models/recsys_model.py.
    • XAUC는 클릭된 표본끼리 짝을 지어, 예측의 순서가 실제 값의 순서와 맞는 정도를 잰다.
    • 함수는 metric_xauc(pred, gt, valid_mask, max_samples=2048)다.
    • 표본을 flat reshape한 뒤 stride = max(N // 2048, 1)로 최대 2048개까지 stride 샘플링하고, 2048×2048 outer difference로 pairwise concordance를 계산한다.
    • 유효한 짝은 pair_valid = mask_i & mask_j & (dy != 0)이다. concordant는 sign(dy) == sign(dp), tied는 dp == 0이고, tied는 0.5로 가중한다.
    • xauc_mask = _cont_loss_mask & (xauc_gt > 0)로 positive-only 표본에만 잰다.
    • 각 continuous head마다 {metric_name}-xauc-clicked stat을 내고, binary head는 -rce·-ratio-pos를 추가로 낸다.
  • H100 학습의 attention 커널 override가 CUTe DSL 커널로 바뀌었다 확인 — phoenix/xrex/configs/xrecsys.py (+13/−1).
    • 값은 pallas_ranker_varlen_attn → cutedsl_ranker_varlen_attn으로 바뀌었다. 이로써 GB300과 같은 CUTe DSL 계열로 맞추는 전환이 마지막 남은 곳까지 끝났다.
    • 4세대+21에서 xrecsys_two_tower.py의 override는 이미 CUTe DSL이었고, 이번에는 xrecsys.py(랭킹 config 계보)까지 정리했다.
  • VMRanker(후보 점수를 별도 랭킹 서버로 보내 다시 다듬는 단계)의 gRPC 클라이언트에 DSCP=InferenceCritical 태그가 붙었다 확인 — home-mixer/clients/vm_ranker_client.rs.
    • DSCP는 IP 헤더의 QoS 필드이고, 데이터센터 트래픽의 분류와 우선순위에 쓰인다.
    • LoadBalancedChannel::builder 체인에 .dscp(Some(Dscp::InferenceCritical)) 한 줄이 추가됐다.
  • grox가 인용 포스트를 렌더링할 때 by @handle과 자기 인용(self-quote) 마커를 붙인다 확인 — grox/core/lm/post.py.
    • 인용자와 인용 대상의 handle이 같으면 "(this Post's author, quoting their OWN post)"가 추가된다.
  • AES(abuse-enforcement-service, 모델 점수로 계정 조치를 정하는 서비스)의 decision_outcome에 감사 조인 키 score_id가 전달된다 확인 — abuse-enforcement-service/service-lib/src/lib.rs.
    • 테스트 두 개가 정상 케이스와 legacy(빈 문자열) 케이스를 모두 통과시킨다.
  • VF(visibility-filtering, 포스트마다 이 뷰어에게 보여도 되는지 판정하는 서비스) 서브레포에서는 13개 enum이 손으로 짠 as_str() 구현에서 strum::IntoStaticStr 매크로로 통일됐다. 5개 파일에 걸친 변경이다.
    • 대상 enum은 RequestOutcome·SourceOutcome·CacheTier·CacheResult·ManhattanResult·BatchStage·ConnEvent·HydratorOutcome·SafetyLevel·LabelSource·FallbackReason·FailureKind·WarmKeyResult다.
    • 관측 라벨 문자열은 snake_case 그대로 유지되므로 순수 리팩터이고, -180줄이다.
    • 관측 지표에서 관측할 수 있는 변화는 없고, 이 리팩터의 상세는 여기서 다루지 않는다.
class ContinuousActionLossConfig(Config):
    loss_weight: float = 0.0
    loss_type: Literal["mse", "mae", "huber", "tweedie", "binary"] = "mae"
    tweedie_power: float = 1.5
    binary_threshold: float = 10.0

— phoenix/xrex/models/recsys_model.py, 2026-09-16.

ContinuousActionLossConfig(
    action_index=recsys_pb2.ContinuousActionName.CLICK_DWELL_TIME,
    metric_name="click-dwell-binary",
    loss_weight=mparams.get("click_dwell_loss_weight", 0.0),
    loss_type="binary",
    binary_threshold=10.0,
    norm_config=NormConfig(norm_scale=60.0),
),

— phoenix/xrex/configs/xrecsys.py, 2026-09-16. _home_direct_packed_base() mparams에는 "click_dwell_loss_weight": 1.0이 동시에 세팅됐다.

def _rescale_sigmoid_heads_for_inference(
    candidate_continuous_predictions, continuous_action_losses, product_surface,
):
    for loss_config in continuous_action_losses:
        if loss_config.activation != "sigmoid" or loss_config.loss_type == "binary":
            continue
        ...

— phoenix/xrex/models/recsys_model.py, 2026-09-16. Binary 헤드가 서빙에서 확률 [0,1] 그대로 남는 근거.

정본 반영 services/x.md의 4세대 표에 4세대+22 행을 추가했고, ### 4세대+22 (2026-09-16, fad2f71) 상세 절을 서브블록 12개(A~L)로 새로 만들었다. ## 출처에는 커밋 sha를 추가하고, 4세대 이후 커밋 수를 21 → 23으로 고쳤다. ## 랭킹의 Attention 카테고리 헤드 표에는 click_dwell이 이진 분류로 바뀌었고 ContClickDwellTimeWeight는 미러상 그대로이므로 실효 기여가 ~60배 축소된 상태라는 각주를 달았다. **최종 갱신:** 2026-09-16 → 2026-09-17로 올렸다.

의미 이번 커밋에서는 광고 학습 target, 체류 시간 head의 학습 방식, 성별 피처 인덱스라는 세 방향이 함께 움직였다.

  • 첫째, 광고 랭킹의 학습 target이 옥션 목적 함수에 더 가까워졌다.
    • 지금까지의 광고 head는 대부분 클릭·컨버전 확률을 예측했다. ADS_WEB_CT_PURCHASE_VALUE는 실제 금액의 회귀 target이고, 광고주의 28일 평균 대비 상대 ratio로 표현된다.
    • Value-optimizing 옥션(Meta VBB, Google tCPA/tROAS)이 같은 방향의 학습 target을 쓴다.
    • purchase_value_loss_weight의 default가 0이라서, 실제 활성 여부는 다음 커밋의 mparams(학습 config가 가중치 같은 값을 읽어 오는 파라미터 묶음)에 이 값이 등장하는 시점을 관측해야 한다.
  • 둘째, click_dwell_time head의 학습 의미가 회귀 → 이진 분류로 바뀌었다.
    • Netflix N-tower·YouTube dwell 계열은 "dwell 여부(binary)"와 "dwell time(regression)"을 분리 head로 둔다. 이와 대비되게 X는 하나의 head를 이진 분류로 통일한 셈이다.
    • 학습 target은 "정확한 dwell 초 예측"에서 "10초 이상 dwell 확률"로 옮겨 갔다.
    • 다만 서빙 쪽과의 대칭이 흔들린다. _rescale_sigmoid_heads_for_inference가 binary head에는 원단위 복원을 명시적으로 건너뛰기 때문에, 이 head는 서빙에서 확률 [0,1] 그대로 남는다.
    • ContClickDwellTimeWeight가 미러상 바뀌지 않았으므로, 랭킹 스코어러에서 이 head의 실효 기여는 이전(초 단위 예측을 곱하던 때)보다 최소 60배 줄어든다.
    • 배포에 맞춰 가중치가 조정됐다는 근거는 이번 커밋에서 확인되지 않는다.
  • 셋째, user_gender 카테고리컬 인덱스가 소리 없이 재매핑됐다.
    • proto Gender enum이 흔한 관행인 UNKNOWN=0, MALE=1, FEMALE=2를 따른다면, user_gender에서 Male/Female의 임베딩 테이블 인덱스가 이전(Male=2, Female=1)과 swap된다.
    • 재학습 전에는 서빙 시 성별 시그널의 부호가 뒤바뀐다.
    • 또 이전에 0으로 뭉쳐지던 non-binary 값이 이제 proto 정의대로 별도 인덱스를 갖는다.
    • 이 마이그레이션과 짝지어진 학습 리셋이 없다는 점이 관측 대상이다. proto Gender 정의는 다음 커밋에서 확인해야 한다.
  • XAUC 지표가 붙은 이유는 dwell·purchase-value 같은 continuous head에서 magnitude 정확도보다 순서 concordance가 옥션 성능과 직결되기 때문이다.
    • 광고 value optimization에서는 "매출 예측 절대값의 정확도"보다 "매출 큰 순서를 맞히는 능력"이 옥션 결정에 더 큰 영향을 미친다.
    • stride 샘플링은 랜덤이 아니고, 시퀀스 패킹 순서에 종속된 편향이 있다. 이는 학습 스텝당 2048² 이상의 pair 계산은 하지 않겠다는 예산 제한이 우선한 것으로 읽힌다.
  • VMRanker에 붙은 DSCP=InferenceCritical은 4세대+8에서 이 경로가 얇게 정리됐던 것과 짝을 이룬다. VMRanker가 홈 랭킹의 tail latency에 크리티컬하다는 배포 결정이 서 있다는 방증이다.
  • EnableAdsBrandSafetyVerdictV2의 true → false 롤백은 4세대+2에서 신설된 뒤 4세대+7에서 HighRisk 승격까지 갔던 확장 방향이 실측 지표에서 문제를 냈거나 재검토 단계에 있음을 시사한다.
  • grox 답글 스팸의 이원 모델 라우팅을 4세대+21의 coordinated_spam 임계 상향(10k → 30k)과 짝으로 읽으면, 전체 스팸 파이프라인이 높은 팔로워 대역과 낮은 팔로워 대역을 각기 다른 모델·기준으로 처리한다는 방향으로 세분화되는 중이다.
  • 자기 인용(self-QT) 마커는 리치 확대를 노린 자기 부스팅 패턴을 grox 판정기가 의미 수준에서 인식하도록 프롬프트 층에서 힌트를 준다.

↑ 목차

2Marketplace 후보 생성 단계의 카테고리 상한을 유저마다 다르게 거는 PCap — Meta

자료 PCap: Personalized Retrieval-Stage Diversity Capping in Facebook Marketplace — arXiv 2609.16452, 2026-09-15 제출, 논문. 저자 11인은 전원 Meta(Menlo Park·Boston 소속) @meta.com이며, HTML 판에서 확인했다.

배경 후보 생성(retrieval) 단계에서 다양성을 확보하는 방식은 지금까지 저장소 코퍼스에서 크게 두 축이었다. 하나는 랭킹·재순위에서 MMR·DPP처럼 후보끼리 비슷할수록 벌점을 주는 pairwise similarity penalty로 상위 후보를 재배열하는 축이고, X의 VMRanker DPP와 유튜브의 rerank stage가 여기에 속한다. 다른 하나는 후보 생성기 개수 자체를 늘려 소스 다양성으로 커버리지를 확보하는 축이고, Netflix multi-source retrieval이 그 예다.

논문이 겨냥하는 문제는 waterfall 그리드형 marketplace 지면의 특성이다. multi-source retrieval에서는 source마다 자기 objective에 따른 카테고리 편향이 있어서(예: 어떤 source는 electronics를 과대표한다), 상위 후보 pool의 카테고리 분포가 치우친다. 결국 유저가 참여하는 카테고리도 좁아진다. 또 유저마다 다양성을 원하는 정도가 다른데 모든 유저에게 같은 다양성 정책을 적용하면, "좁게 클릭하는 유저"에게는 과도한 다양성이, "넓게 클릭하는 유저"에게는 부족한 다양성이 걸릴 수 있다. 이번 논문(PCap)은 기존 두 축 사이에 새 축을 넣는다. retrieval scanning 단계에서 카테고리별 상한(cap)을 걸되, 상한 자체를 유저별로 개인화하는 접근이다.

새로 알게 된 것A. PCap은 클릭 카테고리의 엔트로피로 유저를 다양성 버킷에 나누고, 버킷마다 다른 카테고리 상한을 건다

  • 유저의 다양성 선호는 Shannon entropy로 계산한 프록시로 가늠한다 확인 — arXiv 2609.16452 HTML.
    • 식은 $S(u) = -\sum_{c \in C} p(c|u) \log p(c|u)$이고, p(c|u)는 최근 클릭으로 구한 유저 u의 카테고리 c 확률이다.
    • 카테고리 그룹은 Facebook Product Taxonomy(FPT)의 수백 개 카테고리를 쓴다.
    • 점수를 정규화한 뒤 유저를 6개 다양성 버킷으로 나눠 정렬한다. bucket-1이 가장 좁게, bucket-6이 가장 넓게 클릭하는 유저다.
  • 카테고리별 상한은 multi-source retrieval의 shard-scanning과 aggregation 두 단계에서 건다 확인 — arXiv 2609.16452 HTML.
    • 상한은 retrieval source fetch count × 튜닝된 multiplier fk × 유저 버킷의 다양성 multiplier mu로 산출된다.
    • source의 세부 이름은 공개되지 않았지만, 인덱서 shard 여러 개와 aggregation stage가 있다는 것은 명시돼 있다.

새로 알게 된 것B. 상한 파라미터는 범위를 좁혀 가는 연속 A/B 절차로 찾는다

  • 파라미터 탐색 방법으로 Parameter Tuning Sequence(PTS)를 새로 만들었다 확인 — arXiv 2609.16452 HTML.
    • PTS는 sequential range-narrowing A/B 절차다. control arm을 고정한 채 후보 arm을 며칠씩 소량 트래픽에 노출해, 온라인 grid search를 대신한다.
    • 여섯 개 버킷과 여러 파라미터가 만드는 고차원 조합 공간을 탐색하는 데 쓴다.

새로 알게 된 것C. Marketplace Feed 프로덕션 A/B의 두 단계 모두에서 VPV가 유의하게 올랐다

  • Marketplace Feed 프로덕션 A/B에서 VPV는 두 단계 모두 올랐고, Phase 2에서는 PDP Clicks와 Marketplace Sessions도 함께 올랐다 확인 — arXiv 2609.16452 HTML.
    • 지표의 뜻은 VPV가 Viewport Views, PDP가 Product Detail Page Clicks, Marketplace Sessions가 Marketplace 진입 세션이다.
    • Phase 1(uniform cap vs no cap)에서는 VPV가 +0.3088% 올랐고, p<0.01이었다. PF Latency는 +8.6 ms였다.
    • Phase 2(PCap vs uniform cap)에서는 VPV +0.2243%, PDP Clicks +0.2250%, Marketplace Sessions +0.1708% 세 지표가 모두 p<0.01로 올랐다. PF Latency는 +0.8 ms였다.
    • 다양성 점수(Pagination Feed)는 bucket-1에서 −1.0537%, bucket-6에서 +0.2469% 변했다.

"We propose a personalized capping framework (PCap) to improve the diversity in Facebook Marketplace by introducing user-level diversity constraints at the retrieval stage. PCap models individual diversity preferences using Shannon entropy-based scoring, segments users into diversity buckets, and applies personalized category caps during multi-source candidate retrieval." — PCap: Personalized Retrieval-Stage Diversity Capping in Facebook Marketplace, 2026-09-15.

정본 반영 radar.md의 ## 이번 달 신규 절 맨 위에 PCap 엔트리를 추가했다. Facebook Marketplace는 저장소의 정본 서비스가 아니어서 레이더에서 다룬다. 현재 정본은 x·facebook·instagram·threads·netflix이고, Marketplace는 없다. 다음 관측이 붙으면 topics/feed-diversity.md로 승격할 후보다.

의미 PCap은 저장소가 이미 추적하는 topics/feed-diversity.md에 새 각도를 더한다. 기존 다양성 논의는 MMR·DPP 같은 pairwise 접근(랭킹·재순위)과 multi-source retrieval 대응이 주였다. PCap은 retrieval scanning에 카테고리 상한을 걸되 상한 자체를 유저별로 다르게 세팅하는 방식이고, 두 축 사이에 있는 새 각도다.

  • 첫째, 지연 증가가 작다. Phase 2의 latency +0.8 ms는 랭킹·재순위 단계의 다양성 처리와 대비된다.
    • DPP는 행렬식 계산으로 P99가 백 ms 단위로 튈 수 있다.
    • scanning 단계에서 counter를 유지하며 카테고리별 상한만 강제하는 접근은 O(n)이라 지연 상한이 뚜렷하다.
  • 둘째, carrier metric까지 함께 올라간다는 점이 크다.
    • Marketplace Sessions +0.17%는 유저가 이 지면에 다시 돌아오는 빈도가 올랐다는 뜻이다. 따라서 다양성 개선이 참여의 정체가 아니라 참여의 확장으로 이어졌음을 시사한다.
    • 이는 다양성 처리가 "engagement를 깎는 trade-off"라는 통상적 인식과 대비된다.
  • 셋째, 버킷별 다양성 점수가 반대 방향으로 움직인 것(bucket-1 −1.05%, bucket-6 +0.25%)이 개인화의 근거로 작동한다.
    • "좁게 클릭하는 유저는 좁게, 넓게 클릭하는 유저는 넓게"라는 의미가 다양성 점수 자체에서 관측되고 있다.
    • 다만 논문이 버킷별 참여 지표를 자세히 나누지 않았기 때문에, 이 방향이 실제 참여에 어떻게 이어지는지는 후속 관측 대상이다.
  • 넷째, PTS라는 튜닝 방법 자체가 재사용할 수 있는 자산이다.
    • sequential range-narrowing으로 범위를 좁혀 가는 온라인 grid search는 랭킹 하이퍼파라미터(예: 헤드 가중치)나 다른 retrieval 튜닝에 옮겨 쓸 수 있다.

다음 관측 대상은 두 가지다. Meta의 다른 지면(Facebook Feed, Instagram, Reels)으로 같은 스택이 확장됐다는 진술이 나오는지, 그리고 두 번째 회사가 retrieval-stage cap의 개인화 접근을 채택하는지다.

Facebook Marketplace는 이 저장소의 정본 서비스가 아니어서 승격 대상은 아니다. 다만 topics/feed-diversity.md에 다양성 개인화의 새 각도로 붙는 관찰이다.

↑ 목차

검증에서 뒤집힌 것

없음.

↑ 목차

레이더

레이더에 PCap 엔트리를 추가했다. 위치는 radar.md의 ## 이번 달 신규 절이고, Meta Facebook Marketplace의 retrieval-stage 다양성 개인화 캡핑을 다룬다.

↑ 목차

다음에 볼 것

↑ 목차