0x00

리서치 / 교차 서비스 topics

피드 다양성 — 점수순 정렬을 일부러 망가뜨리는 레이어

랭커 점수대로만 정렬하면 같은 작성자, 같은 주제, 같은 영상이 연달아 나온다. 다양성 레이어는 이런 연속을 막는 레이어다. 방식은 크게 다섯 가지이고, 대부분의 서비스가 여러 방식을 겹쳐 쓴다.

  1. 감쇠 (decay): 같은 키(작성자 등)의 n번째 아이템 점수에 f(n) < 1을 곱한다. 아이템마다 따로 점수를 매기는 순수 pointwise 방식이고, 계산 비용이 싸다.
  2. 탐욕 재순위 (MMR, maximal marginal relevance): 이미 뽑은 것들과의 거리를 점수에 더한다. 점수가 지금까지 뽑은 리스트의 상태에 따라 달라진다.
  3. 슬레이트 최적화 (DPP, determinantal point process): 한 번에 보여 주는 아이템 묶음(슬레이트)을 하나의 집합으로 보고, 그 집합 전체의 행렬식을 최대화한다. 가장 비싸고 가장 원리적인 방식이다.
  4. 중복 제거 (dedup): 같은 것을 두 번 보여 주지 않는다. 다양성이라기보다 위생에 가깝다.
  5. 분포 보정 (calibration): 리스트의 장르·주제 분포를 유저 이력의 분포에 맞춘다. "서로 다르게"가 아니라 "이력과 비율이 같게"를 목표로 하므로, 목표 자체가 다르다. Netflix가 수식을 공개했다(아래 Netflix 절).

다만 이 목록으로 서비스를 비교하려면 먼저 "공개됐는가"를 봐야 한다. X는 네 가지(감쇠·MMR·DPP·dedup)가 전부 소스코드로 읽히지만, Meta는 주요 피드에 대해 넷 중 어느 것도 알고리즘 수준으로 공개한 적이 없다 추정. 검사한 코퍼스는 아래 "케이스: Meta" 절에 적었다. 이 부재 자체가 이 노트의 두 번째 발견이다.

케이스: X — 네 개를 다 쓴다

X는 감쇠, MMR, DPP, dedup 네 방식이 모두 공개 소스코드로 읽힌다 확인(누가 이걸 쓰는가). 2025 Scala 세대에는 MMR과 카테고리 페널티, dedup 필터 6종이 있고, 2026 세대에는 작성자 감쇠와 DPP, dedup 필터 3종이 있다 확인(누가 이걸 쓰는가 절). 2026-10-14 커밋은 학습된 슬레이트 디코더의 학습 코드를 공개했지만, 서빙 스위치는 기본값이 꺼져 있다 확인(5절).

1. 작성자 다양성 감쇠 (2026)

2026 세대의 작성자 감쇠는 같은 작성자의 포스트 가운데 점수순으로 몇 번째인가에 따라 점수에 배수를 곱하고, 이 배수에는 하한이 있다 확인. 배수를 정하는 함수는 아래와 같다 확인 — home-mixer/scorers/ranking_scorer.rs:186-188 (3세대 0bfc279).

fn diversity_multiplier(decay_factor: f64, floor: f64, position: usize) -> f64 {
    (1.0 - floor) * decay_factor.powf(position as f64) + floor
}

여기서 position은 점수 내림차순으로 정렬한 뒤 같은 작성자 안에서 몇 번째인가를 가리킨다. 최종 피드에서의 위치가 아니다 확인 — :198-214.

설계에서 눈여겨볼 점은 두 가지다.

  • 하한 floor가 있다. decay^n만 쓰면 같은 작성자의 10번째 포스트는 점수가 0에 수렴해 사실상 금지된다. floor를 두면 아무리 많아도 원래 점수의 floor배는 남는다. 즉 이 감쇠는 작성자당 상한이 아니라 페널티다.
  • decay_factor와 floor의 값은 FeatureSwitch(런타임 설정)에서 읽는다. 3세대(0bfc279)에는 그 값이 레포에 없었고, 4세대(47c1bcd) 미러의 기본값은 AuthorDiversityDecay 0.5, AuthorDiversityFloor 0.25다 확인 — home-mixer/params/param.rs.

3세대 첫 공개 커밋 aaa167b(2026-01-20)에서 이 감쇠기는 author_diversity_scorer.rs라는 별도 스코어러였다 확인 — aaa167b scorers/mod.rs. 0bfc279(2026-05-15)에서는 감쇠가 랭킹 스코어러 안으로 들어갔고, 이 파일은 남았지만 scorers/mod.rs 선언에서 빠졌다 확인 — 0bfc279 scorers/mod.rs. 파일은 4세대 공개 커밋 47c1bcd(2026-08-13)에서 삭제됐다 확인 — 파일 커밋 이력.

2. MMR — 2025 Scala

2025 Scala 세대의 MMR은 관련성 점수에 이미 고른 후보들과의 최소 거리를 가중해 더한다 확인. 리스트 앞쪽 구간에는 다양성이 적용되지 않고, 계산 결과는 원래 점수 자리를 덮어쓴다 확인.

MMR 점수는 아래 식으로 계산한다 확인 — DiversityRescoringFeatureHydrator.scala:102.

score = relevance + diversityWeight × minDistance(후보, 이미 선택된 것들)
  • 임베딩 차원은 128이다 확인 :32.
  • 거리는 코사인 유사도가 아니라 L2 정규화한 임베딩 사이의 L2 거리다 확인 :64-69.

상위 구간에는 다양성이 아예 적용되지 않는다. minDistance는 아래 조건이 맞으면 거리를 계산하지 않고 기본값을 쓴다 확인 :92-93,102.

if (selected.isEmpty || selected.size < (1 - diversityRatio) * n) None   // → getOrElse(2.0)

즉 기본값 2.0은 "첫 후보일 때"만 쓰이는 것이 아니라, 리스트 앞의 (1-diversityRatio)×n개 전부에 쓰인다. 상수 2.0이 모두에게 똑같이 더해지므로, 그 구간은 순수 relevance 순서를 그대로 유지한다. 다양성은 리스트 뒤쪽에만 적용된다.

MMR은 ScoreFeature 값을 전부 덮어쓴다 확인 :28,70,91. relevance를 ScoreFeature에서 읽어 같은 자리에 다시 쓴다. 즉 이 시점 이후 "랭커 점수"라고 부르는 값은 이미 다양성이 섞인 값이다.

같은 세대에는 카테고리 버전이 따로 있다. SimClusters(팔로우 그래프를 커뮤니티로 나눈 클러스터)의 카테고리 카운트로 탐욕 페널티를 매기며, 계산은 아래 두 줄이다 확인 — CategoryDiversityRescoringFeatureHydrator.scala:69,72.

penalty += math.log(cnt + 1) / math.log(2)          // log2(cnt+1)
score = math.max(relevance - weight * penalty, 0.00001)

MMR은 점수에 더하고, 카테고리 버전은 점수에서 뺀다. MMR은 "다른 것에 보상"이고, 카테고리 버전은 "같은 것에 벌점"이다. 둘 다 같은 파이프라인에 들어 있고, 각각 파라미터로 켜고 끈다(param-gated) 확인 — ScoredTweetsRecommendationPipelineConfig.scala:580-601.

  • MMR 쪽 파라미터: TwhinDiversityRescoringParam :589-590
  • 카테고리 쪽 파라미터: CategoryDiversityRescoringParam :593-594

둘 다 켜지면 겹쳐 적용된다. 카테고리 버전이 relevance로 읽는 ScoreFeature는 이미 MMR이 덮어쓴 값이다. 두 보정은 서로 독립적이지 않고 직렬로 합성된다 확인. 근거는 앞에서 본 MMR의 in-place 덮어쓰기다.

두 보정은 top-K 자르기 뒤에 실행되는 것으로 보이지만, 실행 위치를 코드로 확인할 수는 없다 추정.

  • 둘은 postSelectionFeatureHydration이라는 이름의 단계에 실려 있어, top-K 자르기 이후에 실행되는 것으로 보인다 추정. 근거는 단계 이름과, 이 단계가 resultSelectors(:574-577, DropRequestedMaxResults 포함) 뒤에 배치됐다는 것뿐이다.
  • 그런데 레포에 벤더링된 프레임워크 코드 product-mixer/core의 RecommendationPipelineConfig.scala는 resultSelectors(:136) → postSelectionFilters(:142)만 정의한다. postSelectionFeatureHydration이라는 훅 자체가 없다. 즉 실행 위치를 코드로 확인할 수 없다 확인(부재) — grep -r postSelectionFeatureHydration product-mixer/ → 0건

3. DPP — 2026, 별도 서비스

2026 세대의 DPP는 별도 서비스가 계산한다 확인. home-mixer(For You 피드를 내주는 X의 서비스)의 스코어러 VMRanker가 gRPC로 이 서비스를 호출하고, 응답 점수로 기존 점수를 덮어쓴다 확인 — home-mixer/scorers/vm_ranker.rs:47-52 (3세대 0bfc279).

DPP 파라미터는 theta와 max_selected_rank 가운데 하나라도 양수일 때만 만들어진다 확인 — vm_ranker.rs:112-120.

let dpp_params = if dpp_theta > 0.0 || dpp_max_selected_rank > 0 {
    Some(DppParams { theta: dpp_theta, max_selected_rank: dpp_max_selected_rank })
} else { None };

theta는 관련성과 다양성 사이의 트레이드오프를 조절하고, max_selected_rank는 DPP를 적용할 상위 구간의 길이로 보인다 추정. 근거는 이름과 DPP 관행이고, 코드에는 설명이 없다.

DPP 서비스 본체는 4세대 공개(2026-08-13)부터 레포의 vm-ranker/에 있다 확인. 커널 행렬은 후보 점수와 임베딩 코사인 유사도로 만든다 확인 — vm-ranker/dpp.rs:94-133 (HEAD 862a123, 2026-10-17).

  • 커널 원소는 L_ij = exp(α·q_i) · exp(α·q_j) · cos(e_i, e_j)이고, q_i는 최고 점수로 나눈 후보 점수, α = θ / (2(1 − θ))다 확인(같은 파일).
  • 선택은 Cholesky 분해를 한 행씩 갱신하는 탐욕 DPP다 확인 — dpp.rs:223.
  • 4세대 공개 커밋에도 같은 커널이 있다 확인 — vm-ranker/dpp.rs:94-124.
  • 3세대(0bfc279)까지는 레포에 클라이언트와 proto 매핑만 있었고, xai_vm_ranker_proto는 외부 크레이트였다 확인.

호출이 실패하면 DPP 결과가 조용히 사라진다. gRPC가 실패하면 모든 후보에 Err를 반환하는데, 파이프라인의 update_all은 이 Err를 로그도 남기지 않고 건너뛴다 확인 — candidate-pipeline/scorer.rs (update_all 기본 구현), vm_ranker.rs:33-38.

fn update_all(&self, candidates: &mut [C], scored: Vec<Result<C, String>>) {
    for (candidate, scored) in candidates.iter_mut().zip(scored) {
        if let Ok(scored) = scored { self.update(candidate, scored); }
    }
}

→ VMRanker에 장애가 생기면 다양성 최적화 없이 순수 점수순 피드가 나간다. 응답에는 아무 표시도 남지 않는다. 이것은 fail-open 동작이다. 이 상태를 관측하려면 별도 지표가 필요하다.

VMRanker 전체는 EnableVMRanker FeatureSwitch로 끌 수 있다 확인 — vm_ranker.rs:18-20.

topic 요청에서는 DPP를 기본으로 건너뛴다(2026-10-14). 새 스위치 TopicDppEnabled의 기본값이 false다 확인 — vm-ranker/scoring/mod.rs:85-87.

4. 중복 제거 — 실제로는 여기가 제일 촘촘하다

중복 제거는 두 세대 모두 여러 필터로 나뉘어 있다 확인. 2026 세대의 필터는 다음 세 가지다 확인 home-mixer/filters/:

  • drop_duplicates_filter
  • retweet_deduplication_filter
  • dedup_conversation_filter

2025 Scala 세대는 dedup 필터만 6종을 두고 있고, 필터마다 발동 조건이 좁다 확인.

필터 규칙 위치
RetweetDeduplicationFilter 후보 풀의 비(非)리트윗 전부를 먼저 seen에 넣는다. 그다음 리트윗은 자기 id나 원본 id가 seen에 있으면 제거한다 :27-36
MediaIdDeduplicationFilter 미디어가 정확히 1개인 후보만 대상으로 삼는다. 다중 미디어는 대상이 아니다 :26,33-35
ClipClusterDeduplicationFilter 클러스터가 1종일 때만 적용된다. Map[Long,Long]의 distinct 값 개수를 세므로, 이미지 4장이 같은 클러스터면 이것도 대상이 된다 :24-27,29
DuplicateConversationTweetsFilter 조상을 제거한 뒤, 같은 루트의 답글은 maxBy(ScoreFeature) 하나만 남긴다. 루트가 없는(대화가 아닌) 후보는 전부 통과한다 :33-40
QuoteDeduplicationFilter 인용된 원본 쪽을 지운다(인용 포스트가 아니다) :23-26, 파이프라인 연결 :603,609
KeepTopKCandidatesPerCommunity 커뮤니티당 1개만 남기고, 전체 커뮤니티 후보는 3개로 제한한다 (MaxCandidatesPerCommunity = 1, MaxCommunityCandidates = 3) :14-15,30,32

MediaIdDeduplicationFilter가 if (mediaIds.size == 1) 조건을 둔 것은 의도적인 보수성으로 보인다. 이미지 4장짜리 포스트 두 개가 한 장만 겹칠 때 지우면 과하기 때문이다 추정. 코드에 이유는 없다. 덧붙이면 파일명은 MediaDeduplicationFilter.scala인데 object 이름은 MediaIdDeduplicationFilter다 확인.

QuoteDeduplicationFilter의 방향은 직관과 반대다. "인용 중복 제거"라고 읽으면 인용 포스트를 지울 것 같지만, 실제로는 인용당한 원본을 피드에서 뺀다. 인용문이 원본을 이미 포함해서 보여 주기 때문이다 추정.

팔로하지 않은 계정의 후보(OON, out-of-network) 생성을 전담하는 tweet-mixer에는 저신호 유저에게만 작성자당 1개를 남기는 규칙이 따로 있다 확인 — HomeRecommendedTweetsRecommendationPipelineConfig.scala:364. 저신호 유저는 팔로우 그래프가 작은 유저다.

  • 코드는 DropDuplicateCandidates(duplicationKey = _.features.getOrElse(AuthorIdFeature, None))다.

5. 학습된 슬레이트 디코더 — 2026-10, 학습 코드만 공개

X가 2026-10-14 커밋에서 학습 코드를 공개한 페이지 단위 RL 디코더는 슬롯을 고르는 단계와 학습 손실 양쪽에 다양성 페널티를 넣는다 확인. 서빙 스위치는 기본값이 꺼져 있다 확인.

공개된 디코더는 immersive(몰입형) 지면용 페이지 단위 RL 디코더 page_rl이다 확인 — phoenix/xrex/models/page_decoder.py. 디코더는 슬롯을 하나씩 고르며, 각 단계에서 아래 두 피처를 음수 가중 피처로 logit에 더한다 확인 — page_decoder.py:85-119, 233-247.

  • "이미 고른 것과 작성자가 같은가"
  • "SID 앞자리가 몇 단계 겹치는가". SID(semantic ID)는 아이템을 여러 단계의 계층 코드로 나타낸 ID다.

학습 손실에도 같은 작성자나 비슷한 SID를 연달아 놓을 때 받는 페널티(가중 0.5)가 들어간다 확인 — page_decoder.py:569-578.

  • 위 1~3이 점수를 정한 뒤에 다양성을 규칙으로 적용한다면, 이 방식은 다양성 페널티의 크기를 체류 보상과 함께 학습한다. Netflix GenPage(단일 트랜스포머 페이지 생성)가 행 선택을 생성 분포 안으로 넣은 것과 같은 방향이다 추정. 근거는 두 회사 모두 다양성을 별도 레이어가 아닌 생성 모델 안에 둔다는 구조적 유사성뿐이다.
  • 서빙 스위치 serve_page_decoder는 기본값이 False이고, home-mixer에는 이 디코더를 쓰는 코드가 없다 확인. 배포 범위는 모른다.
  • 자세한 내용은 x.md에서 2026-10-14 커밋을 다룬 절을 본다. 이 노트들은 4세대 공개 뒤의 X 커밋에 차례로 번호를 붙여 부르며, 이 커밋은 4세대+41이다.

↑ 목차

케이스: Meta — 주요 피드에 대해서는 아무것도 공개하지 않았다

Meta는 랭킹 문서를 꾸준히 공개하면서도 다양성과 dedup만은 알고리즘 수준으로 한 번도 쓰지 않았다 추정. X에서 감쇠 함수의 형태, MMR 수식, dedup 필터 6종의 발동 조건이 줄 단위로 읽히는 것과 대비된다. 이것은 우리 조사가 빠뜨린 부분이 아니라, 우리가 찾아낸 발견이다.

  • Meta가 계속 공개한 문서는 지면별 AI system card 30종, DSA 리스크 평가 문서, 엔지니어링 블로그다.
    • AI system card는 지면마다 랭킹 방식을 설명하는 문서이고, DSA는 EU 디지털서비스법이다.
  • 이 문서들은 예측 헤드 이름, 개별 피처 문장, 단계별 후보 수, 가중치를 정하는 방법까지 밝혔다.

Meta 주요 피드에서 얻을 수 있는 다양성 서술은 아래가 전부다.

지면 공개된 문장 전부 등급과 출처
Facebook Feed 블로그(2021)의 pass 2 설명: "the contextual pass. Here, contextual features, such as content-type diversity rules, are added to help diversify Juan's News Feed." 카드 설명: "tries to ensure that your Feed has a balanced mix of content types. That means, for example, you wouldn't see multiple video posts in a row" 확인 engineering.fb.com, 2021 · fb-feed 카드
Instagram Feed "The system also tries to ensure your feed has a balanced mix of content types." Facebook 문장보다 오히려 덜 구체적이다 확인 ig-feed 카드
Instagram (Mosseri) "we try to avoid showing too many posts from the same person in a row, or too many suggested posts back to back" 확인 Instagram Ranking Explained, 2023
Instagram Explore "in case we would like to increase the diversity of results, we might shuffle items based on some business rules (e.g., 'Do not show items from the same authors in a sequence')". Meta 유기 지면에서 유일하게 규칙을 예시로 든 문장이다. 다만 "in case we would like to"라고 했으므로 실제 발동 여부는 미확정이다 확인 Scaling the Instagram Explore recommendations system, 2023
Threads 한 문장도 없다. "multiple video posts in a row" 수준의 서술조차 없다 관찰 2026-08-09 (ig-threads-feed 카드 본문 1건)

MMR/DPP 같은 알고리즘 이름, 감쇠 함수, 임계값 N, dedup 규칙은 어느 것도 1차 출처를 찾지 못했다(no primary source found) 추정.

  • 검사한 코퍼스는 위 표의 1차 출처 6건과 Transparency Center 시스템 카드 15종, engineering.fb.com·ai.meta.com·about.instagram.com 본문이다(2026-08-09 열람).
  • 이 결과가 미공개 자료까지 없다는 근거는 아니다.
  • 작성자 다양성만 예외다. Explore가 "same authors in a sequence"를 예시로 들었다(위 표).
  • ⚠ Instagram 알림 블로그(2025)는 "maximal marginal relevance (MMR)"를 이름으로 부른다. 다만 피드가 아니라 알림이므로, 위 부재 주장의 범위는 피드 지면 한정이다 확인.

알 수 있는 것은 다양성이 적용되는 위치뿐이다. 다양성은 pass 2, 즉 포인트와이즈 스코어링이 끝난 뒤의 별도 패스에서 적용된다 확인 2021. X가 MMR·DPP를 선택 이후 단계에 두는 것과 구조적으로 같은 자리다 추정.

listwise(리스트 전체를 함께 보는) 예측과 delivery frequency control이 존재한다는 진술도 있다 확인 ai.meta.com, 2023. 다만 그 형태는 공개되지 않았다.

"pointwise and listwise predictions"

"personalized methodology for delivery frequency control to optimize for long-term user value"

다만 Facebook은 다양성을 랭킹 피처로도 쓴다

Facebook에서는 같은 다양성 개념이 재순위 규칙이자 동시에 랭킹 입력이다. 카드에 대응 피처가 실재한다 확인 fb-feed.

"Number of times you have seen posts with the same content type (e.g. video, photo, etc) as the current post in the past hour"

이 저장소가 기록한 범위에서 home-mixer 서빙 경로에 있는 X의 다양성은 전부 사후 점수 조작(감쇠·MMR·DPP·dedup)이고, "최근에 같은 유형을 몇 번 봤나" 계열의 랭커 피처는 등장하지 않는다. 다만 X 랭커의 전체 피처 목록을 살펴보고 확인한 것은 아니라서 추정이다. 확인하면 이 대조가 성립하는지 아닌지가 정해진다(아래 "아직 확인 못한 것").

다양성 페널티를 학습하는 5절의 디코더는 서빙 스위치 기본값이 꺼져 있어 이 대조에서 뺐다 확인(5절).

유일한 예외 — Instagram 알림의 곱셈 페널티 (2025)

Instagram 알림 랭킹을 다룬 글은 Meta 전체에서 가장 기계적인 다양성 공개다. 다만 대상은 피드가 아니라 알림이다. 이 프레임워크는 알림 후보의 기본 관련성 점수에, 최근 보낸 알림과의 유사도에 따라 점수를 깎는 배수를 곱한다 확인(아래 본문 인용).

Score(c) = R(c) × D(c)
D(c) = ∏_{i=1..m} (1 - w_i · p_i(c))
p_i(c) = max_{h ∈ H} sim_i(c, h)

출처: A New Ranking Framework for Better Notification Quality on Instagram, 2025-09-02

⚠ 위 세 줄은 verbatim 인용이 아니다. 원문에서 이 수식들은 LaTeX 이미지(latex.php?latex=…)로 렌더링돼 있어, 본문 텍스트로는 존재하지 않는다. 위 표기는 그 이미지를 보고 옮겨 적은 것이다 관찰 2026-08-09 (글 본문을 받아 수식 자리가 전부 이미지 태그임을 확인).

다만 각 구성요소는 이미지가 아닌 본문 산문이 그대로 뒷받침한다 확인 같은 글:

  • 곱셈으로 결합한다: "the product of its base ranking score and a diversity demotion multiplier"
  • "R(c) represents the candidate's base relevance score"
  • "D(c) ∈ [0,1] is a penalty factor that reduces the score based on similarity to recently sent notifications"
  • "sim_i(·,·) is a predefined similarity function for dimension i"

→ 곱 기호와 max 연산자의 정확한 형태만 이미지에 의존하고, 나머지(곱셈 구조, R와 D의 정의, 비교 대상이 최근 발송 이력이라는 점)는 텍스트로 확인된다 추정. 아래 해석은 그 텍스트에 근거한다.

  • 기호의 뜻은 다음과 같다.
    • R(c): base relevance
    • D(c) ∈ [0,1]: 페널티
    • H: 최근 발송 이력
    • w_i ∈ [0,1]: 차원별 강도
    • τ_i: 유사도 임계값
  • 베이스라인 구현은 p_i(c)를 이렇게 정의한다: "equals 1 if the similarity exceeds a threshold τ_i and 0 otherwise". 즉 연속 유사도가 아니라 계단 함수다.
  • 차원은 4종이다: content / author / notification type / product surface

X의 두 형태와 나란히 놓으면 셋이 전부 다르다.

비교 기준 집합 방향 결합
X 2025 MMR 이 요청에서 이미 선택된 후보들 거리에 보상 덧셈
X 2025 카테고리 페널티 이 요청의 카테고리 카운트 같은 것에 벌점 덧셈
IG 2025 알림 최근 발송 이력 H (요청 밖) 유사한 것에 벌점 곱셈

두 가지가 특히 중요하다.

  • 기준 집합이 다르다. X의 MMR은 슬레이트 안에서 후보끼리 비교하고, Instagram 알림은 과거에 이미 보낸 것과 비교한다. 알림은 슬레이트가 아니라 시간축 위에 한 건씩 나가는 스트림이라서, 비교 대상이 이력일 수밖에 없다 추정.
  • 곱셈이라 거부권이 된다. w_i = 1이고 유사도가 임계를 넘으면 D(c) = 0이 되어 점수 전체가 0이 된다. X의 덧셈형은 페널티가 아무리 커도 relevance가 이길 수 있고, 카테고리 쪽은 max(…, 0.00001)로 하한을 둔다 확인. 곱셈 페널티가 거부권(hard veto)으로 작동한다는 성질은 Instagram 2020 value model과 같은 성격이다 → multi-task-ranking.md.

retrieval 단계 다양성 — Instagram은 여기가 주력일 수 있다

Instagram의 다양성은 랭킹 후 재배열보다 후보 생성 단계에서 미리 섞는 쪽이 주력일 수 있다 추정. 랭킹 후 서술은 "too many" 수준인데, retrieval 쪽은 메서드 이름과 파라미터 값까지 나오기 때문이다.

retrieval DSL(후보 생성 쿼리를 적는 언어)에는 diversify_by(seed_id, method=round_robin) 한 줄이 있다 확인 — Designing a Constrained Exploration System, 2020. 이것은 seed 계정 단위 라운드로빈이고, 같은 쿼리의 max_media_per_account=10도 같은 성격이다.

X도 tweet-mixer의 라운드로빈 위브(소스·시그널 버킷을 돌아가며 섞는 방식)와 저신호 유저의 작성자당 1개 dedup을 retrieval 단계에 두므로, 적용 단계 자체는 공통이다 추정 → candidate-allocation.md.

디벙크 — "Instagram은 같은 계정 3연속을 막는다"

널리 반복되는 이 이야기의 Meta 1차 출처를 찾지 못했다 추정. 검사한 코퍼스는 Meta 케이스 절과 같다(카드 15종과 Meta 블로그 3계열, 2026-08-09 열람). Mosseri의 실제 문장에 숫자가 없다는 것만은 직접 확인했다 관찰 2026-08-09 (아래 인용 글 본문 1건).

Mosseri의 실제 문장은 아래와 같다 확인 — Instagram Ranking Explained, 2023-05-31.

"we try to avoid showing too many posts from the same person in a row, or too many suggested posts back to back"

  • "too many"라고 했으므로 임계값이 없다. "try to avoid"라고 했으므로 하드 제약도 아니다.
  • "3"이라는 숫자를 Meta 문서에서 찾지 못했다 추정(같은 코퍼스).
  • 같은 문장이 서로 다른 두 제약을 말한다. 하나는 같은 작성자의 연속이고, 다른 하나는 suggested(비연결) 포스트의 연속이다. 뒤의 것은 Facebook 쪽에 대응 서술이 없다.
  • 아이러니하게도 "3"이라는 상수가 실제로 코드에 있는 쪽은 X다 확인. MaxCandidatesPerCommunity = 1과 MaxCommunityCandidates = 3이 그 값이다 확인(4절 표). 작성자 다양성도 X는 함수 형태((1-floor)·decay^n + floor)까지 읽히고, 계수 값은 4세대(47c1bcd) 미러에 decay 0.5, floor 0.25로 있다 확인(1절).
  • 이 오해는 X의 "인네트워크 50:50"(팔로한 계정과 팔로하지 않은 계정의 포스트를 반씩 섞는다는 이야기)과 같은 형태다 추정. 정성적인 문장 하나가 유통 과정에서 구체적인 상수로 굳는 것이다 추정. 그 오해를 다룬 절은 candidate-allocation.md에 있다.

↑ 목차

케이스: Netflix — 다양성의 단위가 리스트가 아니라 페이지다

TMIS 2015 기준으로 Netflix의 다양성은 재순위 레이어가 아니라 행 선택 단계에 있다 확인. 홈이 단일 스트림이 아니라 행×열의 2차원 페이지라서, X·Meta와는 다양성이 적용되는 위치도 다르다. 다섯 번째 방식인 calibration의 1차 출처도 Netflix 쪽에 있지만, 프로덕션 배포 진술은 없다 추정.

회원당 후보 행 수만 개에서 약 40개를 고를 때 "관련성과 다양성으로 최적화"한다 확인 TMIS 2015.

  • 2015년부터 템플릿을 버렸고, "BYW 행이 하나도 없는 홈도, 절반이 BYW인 홈도" 허용한다. BYW는 Because You Watched 행이다.
  • 템플릿을 버렸기 때문에 다양성은 아이템 순서를 조작하는 문제가 아니라 페이지 구성 문제다.
  • X의 MMR·DPP가 "정렬된 리스트를 어떻게 재배열할까"를 다룬다면, Netflix는 "어떤 관점의 행들을 나란히 놓을까"를 다룬다.

다섯 번째 방식인 calibration의 1차 출처는 Steck의 논문이다. Calibrated Recommendations (RecSys 2018)은 유저가 로맨스 70%/액션 30%를 봤으면 추천 리스트도 그 비율을 유지하도록, KL-divergence 페널티로 탐욕 재순위한다. 형태는 MMR과 같은 탐욕 재순위지만, 목표는 "아이템끼리 다르게"가 아니라 "이력 분포와 같게"이다. 정확도만 최적화하면 다수 취향(액션)이 리스트를 독점한다. calibration은 그 독점(crowding-out)을 막는 장치다.

  • ⚠ 프로덕션 배포 진술이 없다. 논문은 방법론 제안이다. Netflix 블로그와 발표 어디에서도 "calibration을 배포했다"는 문장을 찾지 못했다 추정. 검사 코퍼스는 netflixtechblog 전체 태그와 research.netflix.com 출판 목록이다(2026-08-09 열람). "Netflix는 calibrated recommendations를 쓴다"고 쓰면 안 되고, "Netflix 연구자가 제안했다"까지가 안전하다.
  • 그래도 이 노트에 두는 이유가 있다. 감쇠·MMR·DPP·dedup 넷은 모두 "같음에 벌점" 계열인데, calibration만 분포 일치라는 다른 기준을 따른다. 그래서 다양성 레이어를 볼 때 "무엇을 목표로 하는 다양성인가"를 먼저 묻게 만든다.

중복 제거는 반대 방향일 수 있다. X는 dedup 필터 6종으로 같은 것을 지운다. 반면 Netflix 홈은 같은 타이틀이 여러 행에 나오는 것을 허용하는 것으로 보인다.

  • 행마다 장르, BYW, Trending처럼 추천 이유가 다르고 아트워크 개인화(2017 전면 배포 확인)까지 겹치면, 같은 타이틀의 중복 노출이 "다른 설명의 재시도"로 기능한다 추정.
  • 행 간 중복 규칙의 1차 출처는 찾지 못했고, 직접 관찰도 아직 하지 않았다.
  • 확인되면 "dedup은 위생"이라는 맨 위 목록 4번의 전제가 지면 구조에 따라 뒤집히는 사례가 된다.

아트워크는 지각된 다양성을 움직이는 별도 조절 수단이다. 같은 타이틀이라도 회원마다 다른 이미지를 밴딧(반응을 보며 선택을 조정하는 온라인 학습)으로 고르며, 이 방식은 2017년에 전면 배포됐다 확인 — 블로그: "we've rolled it out to everyone!"

아이템 집합이 같아도 화면의 시각적 다양성은 달라진다. X·Meta 문헌에는 대응물이 없는 레이어다.

자세한 내용은 netflix.md에 있다.

↑ 목차

이 케이스에서 일반화할 수 있는 것

  • 다양성은 랭킹 점수를 덮어쓴다. X는 2025 MMR과 2026 VMRanker 둘 다 score 필드를 그대로 덮어쓴다. "랭커 점수"를 로그에서 볼 때는 어느 단계의 값인지 확인해야 한다. X 2026이 weighted_score와 score를 별도 필드로 남기는 이유가 이것이다 확인 ranking_scorer.rs:277-281.
  • 감쇠에는 floor를 둔다. 상한이 아니라 페널티로 만들어야 인기 작성자가 완전히 사라지지 않는다.
  • 비싼 다양성(MMR/DPP)은 후보 풀 전체가 아니라 이미 좁혀진 리스트에 적용된다. X 2025·2026 모두 선택 이후 단계에 배치돼 있다 추정(2025는 훅이 벤더링된 코어에 없어서 실행 위치를 확인하지 못했다).
  • 다양성은 리스트 앞이 아니라 뒤쪽에 적용한다. X 2025 MMR은 앞의 (1-diversityRatio)×n개에 상수를 더해 사실상 순수 relevance 순서를 보존한다. 거리 보상은 그 뒤부터만 준다. 상위 몇 개는 정확도를 따르고 그 아래는 다양성을 따르는데, 이 구획이 명시적이다.
  • 다양성 레이어는 fail-open이다. 이 레이어가 없어도 피드는 나간다. 그래서 조용히 멈춰도 알아채지 못한다. 감쇠는 같은 프로세스 안에서 돌아서 멈추지 않고, DPP는 원격 호출이라서 멈춘다. 이 비대칭이 감쇠를 남겨 두는 이유를 설명한다 추정.
  • dedup 규칙은 좁게 잡는다. X는 "정확히 1개일 때만"이라는 조건을 반복해서 쓴다. 지나치게 지우는 쪽이 덜 지우는 쪽보다 비싸다고 본 것으로 보인다 추정.
  • 공개하지 않는 것도 데이터다. Meta는 예측 헤드·후보 수·가중치를 정하는 방법까지 공개하면서 다양성·dedup만 일관되게 침묵한다. 다양성 규칙은 곧 "무엇을 덜 보여주는가"라서 정책 논쟁의 표적이 되기 쉽기 때문일 수 있다 추정. 근거는 2025년에 데모션(노출 강등) 문서가 대폭 삭제된 패턴과 방향이 같다는 것뿐이다 → facebook.md.
  • 페널티가 곱셈이냐 덧셈이냐로 거부권 여부가 갈린다. IG 알림의 곱셈은 유사하면 점수를 0으로 만든다. X MMR·카테고리의 덧셈은 relevance가 이길 여지를 남긴다. 같은 "다양성 페널티"라는 이름이 정반대 강도를 가리킨다.
  • 다양성 로직을 볼 때는 비교 기준 집합을 먼저 물어야 한다. X MMR처럼 슬레이트 내부와 비교하는지, IG 알림처럼 과거 노출·발송 이력과 비교하는지를 먼저 확인해야 한다. 같은 max-similarity 수식이라도 이 선택에 따라 완전히 다른 방식이 된다.
  • 다양성이 사후 재순위에만 있으란 법은 없다. Facebook은 "최근 1시간 같은 콘텐츠 타입 노출 횟수"를 랭킹 피처로 쓰고, Instagram은 retrieval DSL에 라운드로빈을 명시한다. 사후 레이어만 찾으면 절반을 놓친다.
  • 다양성의 단위는 지면 구조가 정한다. 단일 스트림(X·Meta)에서는 리스트 내 인접성이 문제라서 감쇠·MMR이 답이다. 2차원 페이지(Netflix)에서는 행 구성이 문제라서 다양성이 행 선택 알고리즘 안으로 들어간다. 같은 "다양성"이라는 말이 조작하는 대상이 다르다.
  • "같음에 벌점"과 "분포 일치"는 다른 목표다. 감쇠·MMR·DPP는 인접한 같음을 벌하고, calibration은 리스트 전체의 장르 분포를 이력에 맞춘다. 전자는 최고 점수 취향이 리스트를 독점해도 아이템만 다르면 통과시킨다. 후자는 그 독점 자체(crowding-out)를 막는다.
  • 수식 공개와 배포는 별개다. Netflix calibration은 RecSys 논문으로 수식 전체가 공개된 몇 안 되는 다양성 알고리즘인데, 배포 진술이 없다. 반대로 Meta는 배포는 확실한데(pass 2) 수식이 없다. "공개 수준"과 "배포 확실성"은 서로 독립적인 기준이다.

↑ 목차

누가 이걸 쓰는가

X는 다양성의 네 방식이 전부 소스코드로 읽힌다 확인. 반면 Threads는 다양성과 dedup의 1차 출처가 하나도 없다 관찰 2026-08-09.

  • X: 네 방식이 전부 소스코드로 읽힌다. → x.md 확인
    • 2025: MMR, 카테고리 페널티, dedup 6종
    • 2026: 작성자 감쇠, DPP, dedup 3종. 2026-10부터는 학습된 슬레이트 디코더의 학습 코드도 있다.
  • Facebook: pass 2 컨텍스트 패스에 content-type 다양성 규칙이 있다. 알고리즘은 미공개이고, 구체적인 규칙은 "multiple video posts in a row" 한 줄뿐이다. 대신 같은 개념이 랭킹 피처로도 존재한다. → facebook.md 확인
  • Instagram: 피드에는 "too many … in a row" 수준의 서술뿐이다. retrieval에는 diversify_by(round_robin)이 있다. 알림에만 곱셈 페널티 수식을 전부 공개했다. → instagram.md 확인
  • Threads: 다양성과 dedup의 1차 출처가 완전히 없다. → threads.md 관찰 2026-08-09 (ig-threads-feed 카드 본문과 Threads 엔지니어링 블로그 2편)
  • Netflix: 다양성이 행 선택 단계에 있다(2015~). calibration 수식을 공개했다(2018, 배포 미확인). 아트워크 개인화라는 별도 조절 수단이 있고, dedup은 오히려 느슨할 가능성이 있다. → netflix.md 확인
  • 그 외 서비스: 아직 조사하지 않았다. 열린 질문이다.

↑ 목차

아직 확인 못한 것

아래 질문에는 아직 답하지 못했다.

  • X 랭커 입력에 "최근 같은 콘텐츠 타입 노출 횟수" 계열 피처가 있는가. 없다고 쓰려면 레포 전체를 확인해야 한다. Facebook과의 대조가 이 확인에 달려 있다.
  • Meta pass 2 다양성 규칙의 형태. 하드 슬롯 제약인지 점수 페널티인지조차 모른다 → facebook.md 열린 질문 2.
  • Instagram 알림 프레임워크가 피드에도 적용되는가. 글은 알림 지면으로 한정해 서술한다.
  • Threads에 다양성·dedup 레이어가 실재하는가. 부재가 "공개 안 함"인지 "없음"인지 판별할 수 없다.
  • Meta의 listwise 예측과 delivery frequency control의 형태. 존재 진술만 있고, 기계적인 서술은 없다.
  • Netflix calibration의 배포 여부. 논문만 있고 배포 진술은 없다.
  • Netflix 행 간 중복 허용 규칙. 1차 출처도 직접 관찰도 아직 없다.
  • GenPage 체제에서 다양성이 어디로 가는가. 행 선택이라는 명시적인 다양성 적용 부분이 사라지면, 다양성은 학습된 생성 분포에 암묵적으로 녹아든다. 이에 대한 진술은 없다 → netflix.md.

↑ 목차

출처

공개 소스코드

  • xai-org/x-algorithm — 2026, 공개 소스코드 (Apache 2.0). home-mixer/scorers/, home-mixer/filters/, candidate-pipeline/. 5절과 DPP 절의 topic 요청 단락은 커밋 17c96bf(2026-10-14) 기준이다. 1절과 3절의 home-mixer 줄 번호 인용은 0bfc279, 4세대 기본값은 47c1bcd, DPP 커널은 862a123 기준이다. 그 밖의 sha 없는 줄 번호 인용은 작성 시점(2026-08) 기준이다.
  • twitter/the-algorithm — 2025-09-03 커밋 c54bec0, 공개 소스코드. home-mixer/.

Meta 블로그

Meta Transparency Center (규제 대응 1차 문서)

Netflix

↑ 목차