0x00

리서치 / 일일 리포트

  • 읽은 자료 2건
  • 정본 반영 2개 파일
  • 레이더 1건
한 줄 요약X가 홈 피드에서 인기 포스트를 fav 수 구간별 비율로 뷰어마다 걷어내는 홀드아웃 필터를 넣었다. 기본값은 꺼져 있다.

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

  • 같은 커밋에서 click-dwell 랭킹 항이 저조 fav 페널티 대신 dwell과 click의 곱으로 재정의됐다(게이트 기본값은 꺼짐).
  • 5일 전에 생긴 LLM-judge 검색 관련성 액션 두 개가 되돌려졌고, 광고 delayed conversion attribution은 3-tier로 넓어졌다.
  • Meta의 LIGE-GR 논문을 레이더에 심층 분석 필요 항목으로 추가했다.

1인기 포스트를 뷰어마다 일부 걷어내는 홀드아웃 필터가 생겼다 — X

자료 xai-org/x-algorithm@42266f3 — 소스코드, 2026-09-17 05:09Z, GitHub 커밋. 64개 파일이 바뀌었다(+2288/−3627). 이 노트들은 4세대 공개 뒤의 X 커밋에 차례로 번호를 붙여 부르며, 이 커밋은 4세대+23이다.

배경 파일 수 64개는 4세대+22(파일 26개) 대비 두 배가 넘고, 삭제 3627줄이 추가 2288줄을 크게 웃돈다. VF(visibility-filtering)는 포스트마다 이 뷰어에게 보여도 되는지 판정하는 서비스다. 큰 삭제분은 대부분 이 VF 서브레포의 자체 twemcache 클라이언트 폴더(3,234줄)가 전부 사라진 데서 나왔다. 이번 커밋은 홈믹서(For You와 Following 피드 요청을 받는 서비스)의 랭킹·필터 스택에 세 가지 방향의 변경을 함께 넣었다.

새로 알게 된 것A. 인기 포스트를 뷰어마다 일정 비율 걷어내는 필터가 파이프라인 마지막에 생겼다

홈믹서 파이프라인 마지막에 완전히 새로운 필터가 연결됐다. 후보를 떨어뜨리는 이 필터의 이름은 FavHoldoutFilter다. 이 필터는 fav_count(포스트의 favorite 누적 수)를 계단식으로 홀드아웃 확률에 매핑한다. 유저·포스트 결정론적 해싱을 통해 인기 있는 포스트를 뷰어별로 일정 비율만 걷어낸다.

  • 홈믹서에 새 필터 FavHoldoutFilter가 신설됐고, fav count 계단별로 홀드아웃 확률을 정한다 확인 — home-mixer/filters/fav_holdout_filter.rs(166줄 신규).
    • 이 필터는 home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs에서 InventoryHoldoutFilter 바로 뒤에 연결됐다. 4세대 필터 리스트의 마지막 자리다.

    • 결정 함수는 다음 계단 테이블이다.

      const STEPS: &'static [(i64, u32)] = &[
          (10000, 15), (5000, 15), (3000, 15), (2000, 14), (1500, 13), (1000, 13),
          (750, 12), (500, 11), (300, 10), (200, 9), (150, 8), (100, 7),
          (75, 7), (50, 6), (30, 6), (20, 5), (15, 5), (10, 5),
          (7, 4), (5, 4), (3, 3), (2, 3), (1, 2),
      ];
      pub(crate) fn holdout_percent(fav_count: Option<i64>) -> u32 {
          let favs = fav_count.unwrap_or(0);
          if favs <= 1 { return 0; }
          for &(k, pct) in Self::STEPS { if favs > k { return pct; } }
          0
      }
      
    • 비율은 fav_count가 10,000을 넘으면 15%, 5,000 초과도 15%, 2,000 초과 14%, 1,000 초과 13%, 750 초과 12%, 500 초과 11%, 100 초과 7%, 20 초과 5%, ..., 2 초과 3%, 1 초과 2%다.

    • fav_count가 1 이하면 0%로, 뷰어 전원이 항상 본다.

    • 홀드아웃 판정은 InventoryHoldoutFilter::is_held_out(post_id, viewer_id, percent)을 재사용한다.

    • 이 함수는 splitmix64 계열의 이중 mixing 해시로 (post_id, viewer_id) 쌍의 100-modular 버킷을 구하고, 버킷이 percent 미만이면 홀드아웃한다.

    • 즉 같은 유저는 같은 포스트를 결정론적으로 홀드아웃하고, 같은 포스트라도 뷰어마다 다른 부분집합만 홀드아웃한다.

    • FavHoldoutFilter의 기본 param은 EnableFavHoldout = false다. Param 정의는 home-mixer/params/param.rs의 rust_home_mixer_enable_fav_holdout이다.

새로 알게 된 것B. 기존 인벤토리 홀드아웃 필터가 포스트 종류별로 쓰는 비율 param 세 개가 확인됐다

  • 인벤토리 홀드아웃 필터의 3-티어 holdout 비율 params가 확인됐다 확인 — home-mixer/filters/inventory_holdout_filter.rs.
    • 필터 이름은 InventoryHoldoutFilter다. 이것으로 오래 열려 있던 미공개 항목이 해소된다.
    • 필터 파일은 InventoryHoldoutOriginalsPercent·InventoryHoldoutRepliesPercent·InventoryHoldoutRetweetsPercent 세 param을 참조한다.
    • 포스트 종류 판별은 retweeted_tweet_id.is_some() → Retweet, in_reply_to_tweet_id.is_some() → Reply, 그 외 → Original이다. quoted post는 Original로 취급한다.
    • 세 값은 각각 .min(100)으로 클램프된다.
    • 필터는 EnableInventoryHoldout=true AND 세 값 중 하나라도 >0일 때만 활성화된다.
    • 홀드아웃 대상은 get_original_tweet_id()(리트윗이면 원본, 아니면 자기 자신)에 대해 결정론적으로 해싱된다. 즉 리트윗은 원본과 같은 홀드아웃 결정을 공유한다.

새로 알게 된 것C. click-dwell 랭킹 항이 저조 fav 페널티 대신 dwell과 클릭 확률의 곱을 쓸 수 있게 됐다

랭킹 스코어링에서 dwell 예측을 fav 비율로 조정하던 항이 삭제되고, 그 자리에 훨씬 단순한 게이트가 들어섰다. 삭제된 것은 이전에 도입됐던 EnableClickDwellLowFavRatePenalty 로직이다. 이 로직은 4개 튜닝 파라미터를 곱해 (fav/baseline)^alpha로 dwell 예측을 조정하던 항이었다. 새 게이트 EnableCdwellOnImpr가 켜지면 click_dwell_time 예측을 click 확률로 곱한다(dwell × click). click_dwell 헤드는 2026-09-16 커밋(4세대+22)부터 이진 분류 확률이므로, 이 곱은 초가 아니라 임프레션 기준의 확률이다.

  • 랭킹 스코어에서 EnableClickDwellLowFavRatePenalty 계열이 전부 삭제되고 EnableCdwellOnImpr로 대체됐다 확인 — home-mixer/scorers/ranking_scorer.rs(+10/−32).
    • param 쪽 변경은 home-mixer/params/param.rs에 있다.

    • ScoringWeights가 들고 있던 5개 필드(enable_click_dwell_low_fav_rate_penalty와 baseline/alpha/floor/cap)와 대응 param 5개가 삭제됐다. 이 param들은 4세대 공개(2026-08-13) 때부터 있던 것이다 확인 — param.rs:388-412.

    • 그 자리에 enable_cdwell_on_impr: bool 하나가 들어왔다.

    • param.rs mirror에는 EnableCdwellOnImpr 정의가 다음처럼 남는다.

      param!(EnableCdwellOnImpr, bool, "rust_home_mixer_enable_cdwell_on_impr", false);
      
    • 기존 함수 low_fav_penalized_click_dwell이 click_dwell_term으로 이름이 바뀌었고, 계산은 다음처럼 간단해졌다.

      fn click_dwell_term(&self, scores: &PhoenixScores) -> Option<f64> {
          if !self.enable_cdwell_on_impr {
              return scores.click_dwell_time;
          }
          match (scores.click_dwell_time, scores.click_score) {
              (Some(cd), Some(click)) => Some(cd * click),
              (cd, None) => cd,
              (None, _) => None,
          }
      }
      
    • 게이트 관측 라벨도 gate.click_dwell_low_fav_rate_penalty → gate.cdwell_on_impr로 바뀌었다.

새로 알게 된 것D. 신규 저자 부스트에서 Treatment 팔의 MoE 조건이 빠지고, 포스트 나이 상한은 24시간에서 48시간으로 늘었다

author cold-start scorer(신규 저자 포스트에 부스트를 거는 스코어러)의 Treatment 팔에 걸려 있던 두 가지 게이트가 함께 완화됐다. MoE 조건은 2026-09-04 커밋(4세대+16)부터, age 상한은 4세대부터 있던 것이다. Treatment 팔이 요구하던 is_phoenix_moe(candidate) 조건이 제거돼, 이제 Treatment 코퍼스 후보라면 MoE(mixture of experts, 입력마다 일부 전문가 네트워크만 쓰는 구조) 라벨과 무관하게 cold-start 대상이 된다. 또 ColdStartMaxPostAgeSecs가 86400(24시간) → 172800(48시간)으로 두 배가 됐다. Treatment 팔에만 걸리던 이 age 상한은 이제 모든 팔에 적용된다. 회귀 테스트는 3개가 제거되고 하나의 이름이 통합됐다. 테스트 이름도 바뀌었다. treatment_viewer_cold_starts_treatment_moe_only는 treatment_viewer_cold_starts_treatment_authors_only로, treatment_skips_post_older_than_max_post_age는 skips_post_older_than_max_post_age로 바뀌었다.

  • Author cold-start scorer의 Treatment 팔에서 두 게이트가 함께 완화됐다 확인 — home-mixer/scorers/author_cold_start.rs(+7/−50).
    • 이전에는 ViewerArm::Treatment 자격이 corpus == Treatment && is_phoenix_moe(c)였다. 이번에 is_phoenix_moe(c) 조건이 제거돼 corpus == Treatment만 확인한다.
    • Treatment 팔에만 age 상한을 걸던 별도 함수 cold_start_freshness_eligible(arm, c, max_age)가 전부 삭제됐다.
    • 이제 apply_cold_start가 모든 팔에 대해 duration_since_creation_opt(c.tweet_id).is_some_and(|age| age <= params.max_post_age)를 필터에 건다.
    • control_ignores_max_post_age 회귀 테스트가 삭제되고, skips_post_older_than_max_post_age로 이름이 통합됐다.
  • ColdStartMaxPostAgeSecs 기본값이 86400 → 172800(24시간 → 48시간)으로 바뀌었다 확인 — home-mixer/params/param.rs.
    • 이 값은 위 필터에서 max post age로 쓰인다. 즉 이제 신규 저자 부스트가 걸리는 포스트가 생성 후 48시간 안쪽 후보로 넓어졌다.

새로 알게 된 것E. 5일 전에 생긴 LLM-judge 검색 관련성 라벨 두 개가 되돌려지고, 그 자리에 라이브 스트림 답글 신호로 추정되는 액션과 예약 자리가 들어왔다

Phoenix(후보마다 참여 확률을 예측하는 X의 랭킹 트랜스포머)의 서빙 프로토 recsys.proto에서는 두 가지 변경이 있다. 하나는 4세대+20(6bb4594, 2026-09-12)에서 신설됐던 검색 관련성 라벨 두 개가 5일 만에 되돌려진 것이다. 두 라벨(LLM_JUDGE_*)은 LLM_JUDGE_RELEVANT_TO_SEARCH=107과 LLM_JUDGE_NOT_RELEVANT_TO_SEARCH=108이다. 그 자리는 CLIENT_TWEET_LIVE_STREAM_REPLY=107과 RESERVED_108로 대체됐다. CLIENT_TWEET_LIVE_STREAM_REPLY는 라이브 스트림 답글 신호로 추정되고, RESERVED_108은 예약 자리다. 다른 하나는 광고 delayed conversion attribution의 3-tier 확장이다.

  • Phoenix proto에서 검색 관련성 라벨 두 개가 삭제되고, 같은 번호에 새 액션과 예약 자리가 들어왔다 확인 — phoenix/crates/serving/xai-recsys-proto/proto/recsys.proto.
    • 삭제된 것은 LLM_JUDGE_RELEVANT_TO_SEARCH(107)·LLM_JUDGE_NOT_RELEVANT_TO_SEARCH(108)이다. 대체한 것은 CLIENT_TWEET_LIVE_STREAM_REPLY(107)·RESERVED_108(108)이다.

    • 근거에는 파이썬 미러 phoenix/python/common/xai-proto/proto/recsys.proto도 포함된다.

    • 두 라벨은 5일 전 4세대+20(6bb4594, 2026-09-12)에 신설된 것들인데, 이 커밋에서 되돌려진다.

      // before (4세대+20)
      LLM_JUDGE_RELEVANT_TO_SEARCH = 107;
      LLM_JUDGE_NOT_RELEVANT_TO_SEARCH = 108;
      // after (4세대+23)
      CLIENT_TWEET_LIVE_STREAM_REPLY = 107;
      RESERVED_108 = 108;
      

새로 알게 된 것F. 광고 delayed conversion attribution에 tier 2·3 액션 20종이 추가됐다

광고 delayed conversion attribution(시간이 지나 늦게 일어난 전환을 광고에 귀속하는 일)이 3-tier 구조로 확장됐다. 기존의 단일 지연 창에서 Tier 2·Tier 3까지 이어지는 구조다. proto에서는 reserved 214 to 252 범위 안쪽 20개가 실 액션으로 채워졌다.

  • 광고 delayed conversion attribution이 3-tier로 확장되며 20개 액션 이름이 신설됐다(214-233) 확인 — 같은 proto.
    • 기존 reserved 214 to 252가 reserved 234 to 252로 좁혀졌고, 214-233 자리에 각각 tier 2와 tier 3가 붙었다. Tier 2는 214-223, tier 3은 224-233이다.

      ADS_ATTRIBUTED_KEY_CLICK_CONVERSION_DELAYED_TIER_2 = 214;
      ADS_ATTRIBUTED_CLICK_CONVERSION_DELAYED_TIER_2 = 215;
      ADS_PURCHASE_CONVERSION_DELAYED_TIER_2 = 216;
      ADS_MID_FUNNEL_CONVERSION_DELAYED_TIER_2 = 217;
      ADS_ADD_TO_CART_CONVERSION_DELAYED_TIER_2 = 218;
      ADS_UPPER_FUNNEL_CONVERSION_DELAYED_TIER_2 = 219;
      ADS_WEB_CONVERSION_DELAYED_TIER_2 = 220;
      ADS_SEARCH_CONVERSION_DELAYED_TIER_2 = 221;
      ADS_SIGN_UP_CONVERSION_DELAYED_TIER_2 = 222;
      ADS_CHECKOUT_INITIATED_CONVERSION_DELAYED_TIER_2 = 223;
      // 그 다음 224-233에 같은 10개의 TIER_3 계열이 이어진다
      
    • 기존 *_DELAYED(204-213) 10종이 4세대+6(28e414f, 2026-08-21)에 신설된 지 한 달 만에 tier 2·3이 추가된 형태다.

    • 각 tier가 관측 창의 어느 구간을 커버하는지는 이번 커밋에서 명시되지 않는다. tier 간의 학습 loss 처리(모두 곱해 학습하는지, 시차별로 서로 다른 loss weight를 태우는지)도 이번 커밋에서는 명시되지 않는다.

새로 알게 된 것G. VF가 3,234줄의 자체 twemcache 클라이언트를 지우고 회사 공용 KV 캐시 클라이언트로 옮겼다

visibility-filtering 서브레포에서는 큰 재편이 있었다. VF 서브레포가 자체 twemcache 프로토콜/커넥션·링/디스커버리를 유지하는 대신, 회사 공용 KV 캐시 abstraction으로 옮겨 간 것이다. visibility-filtering/twemcache/ 아래 11개 파일이 모두 제거됐다. safety_label_source/twemcache.rs는 새 crate xai_cache의 xai_cache::CacheClient(Key·Value·KVCacheError 재수출)에 의존하도록 rebase됐다.

  • VF 서브레포의 자체 twemcache 클라이언트가 전부 제거됐다 확인 — visibility-filtering/twemcache/ 아래 11개 파일 총 3,234줄이 삭제됐다.
    • 제거된 파일은 accrual.rs 267줄, client.rs 640줄, connection.rs 762줄, discovery.rs 121줄, error.rs, host_pool.rs 359줄이다. 나머지는 host_pool_tests.rs 639줄, key.rs 92줄, metrics.rs 87줄, mod.rs 13줄, ring.rs 239줄이다.

    • 대체 코드로는 safety_label_source/twemcache.rs의 상단 import가 다음처럼 바뀌었다.

      // before
      use crate::twemcache::{Key, TwemcacheClient, TwemcacheError, Value};
      // after
      use xai_cache::{CacheClient, KVCacheError, Key, Value};
      
    • fallback 매핑도 재정렬됐다. 이전 매핑은 TwemcacheError::Timeout → FallbackReason::Timeout, Backpressure → Backpressure, Unavailable|Io → Other였다. 새 매핑은 KVCacheError::Timeout(_) → Timeout, Backpressure → Backpressure, _ → Other로 통합됐다.

    • 요컨대 VF는 자체 유지하던 twemcache 프로토콜 클라이언트(accrual·discovery·host_pool·ring 포함, client.rs만 640줄)를 전부 회사 공통 KV 캐시 abstraction으로 옮겼다. 유지보수 표면이 축소되고, 다른 서비스가 쓰는 클라이언트와 동작 특성이 통일된다.

새로 알게 된 것H. VF에 새 RPC 인터페이스와 Thrift Action 응답 디코더가 생겼다

VF 서비스에 새 RPC가 생겼고, 그 응답을 Thrift로 푸는 클라이언트 함수도 함께 들어왔다. 새 RPC evaluate_tweets는 VisibilityFilteringService 트레이트에 추가됐다. 새 파일 visibility-filtering-client/evaluated.rs(122줄)는 이 RPC의 응답 payload를 Thrift Action으로 디코드하는 함수 decode_action을 제공한다.

  • VF 서비스에 evaluate_tweets RPC와 Thrift Action payload 디코더가 신설됐다 확인 — visibility-filtering-client/evaluated.rs(122줄 신규).
    • visibility-filtering-client/vf_client.rs·visibility-filtering-client/tweet_safety_label.rs의 트레이트에 새 async 함수가 추가됐다.

    • 스텁 서비스가 Err(Status::unimplemented("unused"))를 내는 것으로 봐서, 클라이언트 인터페이스만 열어 두고 실 구현은 대기 중이다.

    • evaluated.rs가 정의하는 세 상태는 다음 코드에 있다.

      pub enum EvaluationResult { Evaluated(Box<Action>), NotEvaluated, Failed }
      pub const MAX_ACTION_BYTES: usize = 64 * 1024;
      pub fn decode_action(bytes: &[u8]) -> Result<Action>
      
    • decode_action이 거는 조건은 다음과 같다.

      • 64KB payload 제한(TConfiguration의 4개 상한을 모두 64KiB로)
      • Compact Thrift 프로토콜
      • trailing byte 금지
      • Action::NotEvaluated는 거부
    • 즉 evaluate_tweets의 응답이 Evaluated(Action)의 Thrift 인코딩이어야 한다.

    • Action은 xai_x_thrift::action의 오랜 규격이다. 변종에는 MessageLink·LimitedAction·TweetVisibilityNudgeAction 등이 있다.

    • 요컨대 VF의 응답 인터페이스가 필터링된 사유 코드 열거형뿐 아니라 완전한 Action 오브젝트를 클라이언트에 되돌리는 방향으로 확장되기 시작했다.

    • xDS LDS listener 마이그레이션을 열어 뒀던 4세대+20과 짝으로 읽으면, VF가 client → server 단순 필터에서 richer한 정책 판정 서비스로 이동 중이다.

새로 알게 된 것I. Under-the-Hood 투명성 툴의 takedown 라벨을 매일 재계산하고 미집행 사유를 가리는 인프라가 생겼다

Under-the-Hood(UTH, 유저가 자기 계정과 포스트에 붙은 가시성 영향 라벨을 조회하는 X의 투명성 툴)의 감사 라벨을 매일 재계산하고, 미집행 라벨을 서빙 전에 편집하는 인프라가 새로 들어왔다. UTH 서브레포에는 계정 takedown 라벨 재현을 위한 daily 잡 스택이 새로 붙었다. 새 파일은 UthDailyPostTakedownLabelsJob.scala(283줄), TakedownDeltas.scala(169줄), TakedownLabels.scala(103줄), TakedownReplay.scala(80줄)이다. 여기에 더해 underTheHoodLabels.strato에 +164줄이 추가됐다. Gizmoduck(X의 유저 정보 서비스) 쪽에는 RedactUnenforcedTakedownsFilter.scala(81줄)과 ReadFilters.scala(39줄)이 신설됐다. tweet-entity-service에는 TakedownReasonsTransform.scala(73줄)·TakedownReasonsColumn.scala(25줄)이 신설됐다.

새로 알게 된 것그 밖의 변경

impl FavHoldoutFilter {
    const STEPS: &'static [(i64, u32)] = &[
        (10000, 15), (5000, 15), (3000, 15), (2000, 14), (1500, 13), (1000, 13),
        (750, 12), (500, 11), (300, 10), (200, 9), (150, 8), (100, 7),
        (75, 7), (50, 6), (30, 6), (20, 5), (15, 5), (10, 5),
        (7, 4), (5, 4), (3, 3), (2, 3), (1, 2),
    ];
    pub(crate) fn holdout_percent(fav_count: Option<i64>) -> u32 {
        let favs = fav_count.unwrap_or(0);
        if favs <= 1 { return 0; }
        for &(k, pct) in Self::STEPS { if favs > k { return pct; } }
        0
    }
}

— home-mixer/filters/fav_holdout_filter.rs, 2026-09-17

fn click_dwell_term(&self, scores: &PhoenixScores) -> Option<f64> {
    if !self.enable_cdwell_on_impr {
        return scores.click_dwell_time;
    }
    match (scores.click_dwell_time, scores.click_score) {
        (Some(cd), Some(click)) => Some(cd * click),
        (cd, None) => cd,
        (None, _) => None,
    }
}

— home-mixer/scorers/ranking_scorer.rs, 2026-09-17

정본 반영 services/x.md의 5개 절을 갱신했다.

  • 필터 21개 절(line 355): 필터 리스트 끝에 FavHoldoutFilter(4세대+23 신규)를 추가했다. InventoryHoldoutFilter 항목의 "홀드아웃 비율 param은 확인이 필요하다"를 삭제했다. 그 자리는 3-tier param(InventoryHoldoutOriginalsPercent·InventoryHoldoutRepliesPercent·InventoryHoldoutRetweetsPercent) 연결을 확정 기술로 대체했다. 새 FavHoldoutFilter 항목도 추가했다.
  • Attention 스코어링 항(line 147): click_dwell 항의 계산이 4세대+23에서 재정의됐다는 문장을 추가했다. 바뀐 내용은 EnableClickDwellLowFavRatePenalty(4세대 공개 때부터 있던 param) → EnableCdwellOnImpr(dwell × click_score)다.
  • 미공개 목록(line 1416, 2535): LLM_JUDGE_RELEVANT_TO_SEARCH·LLM_JUDGE_NOT_RELEVANT_TO_SEARCH의 연결처를 묻는 항을 삭제했다. 4세대+20 도입 → 4세대+23 되돌림으로 vacate됐음을 명시했다. InventoryHoldoutFilter의 홀드아웃 비율 param 미공개 줄은 해소되어 삭제했다.
  • 광고 conversion 액션 절(line 1686 부근): 4세대+23의 tier 2/3 확장 20종 항목을 4세대+9 항목 뒤에 추가했다.
  • 커밋 시퀀스 절(line 2545): fad2f71(4세대+22) 뒤에 42266f3(2026-09-17, 4세대+23)을 추가하고, "4세대 이후 커밋 23개"를 "4세대 이후 커밋 24개"로 고쳤다.

또 4세대+22 뒤에 새 절 '4세대+23 (2026-09-17, 42266f3)'을 추가해, 새로 알게 된 것 열 가지(A–J)를 요약했다.

의미 세 가지 방향의 변경이 이번 커밋에서 함께 왔다. 인기 포스트 홀드아웃, click_dwell 항의 재정의, cold-start 게이트 완화다. 여기에 Phoenix proto의 변경이 네 번째 층으로 더해진다.

  • 첫째, 홈믹서 랭킹이 인기 있는 포스트에 대한 뷰어별 결정론적 홀드아웃이라는 새 축을 얻었다.
    • InventoryHoldoutFilter가 종류(원본·답글·리트윗) 기준 3-tier 홀드아웃이었다면, FavHoldoutFilter는 인기(fav count) 기준 23-tier 계단 홀드아웃이다.
    • 두 필터는 뷰어와 포스트 쌍에 대해 결정론적이지만 서로 독립된 결정을 내린다. 그래서 이제 홈믹서가 아직 스코어링 이전 단계에서 두 종류의 counterfactual 홀드아웃 실험을 병렬로 태울 수 있다.
    • 하나는 포스트 종류별로 균일한 홀드아웃이고, 다른 하나는 인기도별 계단식 홀드아웃이다.
    • 왜 인기순 홀드아웃인가? 10K+ fav 포스트를 15% 홀드아웃해 두면, 그 뷰어들이 그 포스트 없이 다음 최선의 후보에 어떻게 반응하는지 관측 가능해진다.
    • 즉 상위 인기 포스트의 마진 기여도(이 포스트가 홈에서 사라졌을 때의 유저 손실)를 실측 실험으로 잡을 수 있는 인프라가 열린다.
    • 4세대+21의 응답 다양성 3단 관측 side effect와 짝지어 보면, X 랭킹 실측 인프라가 집합 단위 counterfactual인 다양성과 포스트 단위 counterfactual인 홀드아웃이라는 두 축으로 확장되고 있다 추정.
    • 4세대+21의 다양성 통계 관측이 A/B 실험 없이도 slate(한 번에 보여 주는 후보 묶음) 구성을 관측한다면, 홀드아웃 필터는 A/B 실험을 필터 내부에서 자동으로 태우는 형태다.
  • 둘째, click_dwell 랭킹 항의 정의가 완전히 바뀌었다.
    • 이전 EnableClickDwellLowFavRatePenalty는 fav 비율이 낮은 포스트의 dwell 예측을 (fav/baseline)^alpha로 낮춰, 부풀린 dwell만 보고 오판하는 것을 억제하는 heuristic penalty였다.
    • 4세대+22에서 click_dwell 헤드가 sigmoid BCE 이진 분류로 바뀌면서, 헤드의 실효 스케일이 60배 축소된 상태였다.
    • 이번 커밋은 fav-based penalty를 덜어내는 방향으로 정리했다(4개 튜닝 param과 대응 필드가 삭제됐다).
    • 그 자리에 들어온 EnableCdwellOnImpr는 대안 접근이다. click_dwell 예측(클릭 시의 dwell 확률/시간)을 click 확률로 곱해 임프레션 당 기댓값으로 변환한다.
    • 이 재정의는 두 헤드의 신호를 곱해 얻는 값이므로, "dwell을 늘리는 헤드"와 "click을 늘리는 헤드"가 서로 상쇄나 이중 카운트되는 문제를 줄인다.
    • 이 재정의는 Netflix N-tower·YouTube ranker의 관행과 정렬된다. 이들은 오래전부터 dwell 헤드를 conditional 확률(P(dwell|click))로 학습한 뒤, 서빙에서 P(click)과 곱해 unconditional 기댓값을 만든다.
    • ⚠ 주장 변경: 어제(2026-09-17) 정본은 "click_dwell_time 헤드의 실효 기여는 이전 대비 ~60배 축소 상태"로 기술했다. 그런데 ContClickDwellTimeWeight가 조정되지 않은 상태에서 이 헤드가 click_score와의 곱으로 재정의될 경우, 실효 스케일은 다시 변한다.
      • 정확히는 두 경우로 나뉜다. EnableCdwellOnImpr=false(default)이면 60배 축소가 유지된다.
      • 켜지면 확률·확률의 곱(0-1 × 0-1)이 된다. 이전 항은 click_dwell 값에 fav 비율 배수를 곱했고(게이트가 꺼지면 click_dwell 값 그대로), click 확률을 곱하는 항은 없었다 확인 — ranking_scorer.rs diff.
      • Weight 절대치가 유지되면 실효 스케일은 60배 축소가 아니라 훨씬 더 큰 축소로 갈 수 있고, 이 헤드의 랭킹 기여가 사실상 남은 헤드에 흡수되는 방향이 될 수 있다.
      • 실 배포 여부는 다음 커밋의 EnableCdwellOnImpr mirror 전환과 ContClickDwellTimeWeight 재조정 여부에서 관측될 것이다 추정.
  • 셋째, cold-start scorer의 Treatment 팔에서 두 게이트가 함께 완화됐다.
    • is_phoenix_moe 요구가 사라진 것은, cold-start 대상이 이제 MoE 라벨 없이도 Treatment 실험에서 활성된다는 뜻이다.
    • 2026-09-04 커밋(4세대+16)에서 생긴 이 게이트는 Phoenix MoE 헤드가 cold-start 후보에 대해서만 학습에 활용되도록 제한하는 실험 조건이었는데, 실측 결과 이 조건이 필요하지 않았거나 오히려 손실이었다는 판단이 되어야 완화된다 추정.
    • Max age의 24h → 48h 확장은 신규 저자 부스트 창을 두 배로 넓혔다는 뜻이다. 이 상한은 작성자 경력이 아니라 포스트 나이에 걸리므로, 생성 후 48시간 안의 포스트까지 cold-start 대상이 된다.
    • Treatment 팔에서는 두 변경이 모두 cold-start 투입량을 늘린다. 반면 상한이 없던 Control·Holdout 팔에는 48시간 상한이 새로 걸려 조건이 오히려 조여졌다 확인 — 위 D의 author_cold_start.rs diff.
  • 네 번째 층으로, Phoenix proto의 두 변경이 향후 랭킹 신호의 편성을 알려 준다.
    • LLM_JUDGE_* 라벨 두 개가 5일 만에 vacate된 것은, xAI가 검색 관련성 랭킹 학습에 LLM judge 라벨을 별도 축으로 이식하려던 방향을 일단 접었다는 신호다.
    • 여기에는 두 가능성이 있다. 판정을 다른 방식으로 흘려 넣었을 수도 있고(예: 기존 CLIENT_TWEET_RELEVANT_TO_SEARCH=105 라벨 채널을 그대로 재사용), 라벨 축 자체를 재정의 대기로 놔뒀을 수도 있다.
    • 자리에 들어온 CLIENT_TWEET_LIVE_STREAM_REPLY(107)은 라이브 스트림 답글에 대한 클라이언트 시그널로 보이며, 다음 커밋에서 학습 신호 편입 여부를 관측해야 한다.
    • Delayed conversion attribution의 3-tier 확장은 이번 커밋에서 가장 견고한 광고 인프라 연결이다.
    • 광고 conversion 관측 창이 지금까지 "즉시"와 "delayed" 두 개였다면, 이제 delayed도 tier 2·3으로 나눠 관측 시차별로 별도 헤드에 흘려 준다.
    • 이는 delayed 학습이 시차 편향을 완화하기 위해 시차별 mask를 적용하는 문헌의 표준 접근(Chapelle 2014, Yasui & Sakata 2020 계열)과 방향이 일치한다 추정.
    • 4세대+22에서 정식 연결된 ADS_WEB_CT_PURCHASE_VALUE Huber ratio loss와 짝으로 읽으면, X의 광고 학습 스택이 금액 회귀와 시차별 delayed 헤드로 이동 중이다.
  • VF twemcache 제거와 evaluate_tweets RPC 확장은 랭킹 축이 아니지만 관측 대상이다.
    • VF가 client → server 응답을 sigil enum에서 완전한 Thrift Action 오브젝트로 확장하기 시작했다.
    • 이 방향이 지속되면, 홈믹서가 지금까지 VF 응답을 FilteredReason으로만 소비하던 자리에서 richer한 정책 판정을 소비할 여지가 생긴다.
  • UTH 라벨 재현 잡 스택 신설은 X가 발표한 Under-the-Hood 투명성 툴의 감사 라벨 daily 재계산을 위한 인프라다.
    • 이 라인은 랭킹이 아니라 유저 감사 툴 서빙이므로 정본 반영 대상은 아니다. 다만 저장소 파일 구성상 랭킹 코드와 같은 레포에 병존한다는 사실은 유의미하다.
    • X가 "왜 내 포스트가 이렇게 취급됐나"를 유저가 직접 조회 가능하게 하는 라인을 랭킹·안전과 같은 리포지토리에서 유지한다는 의도가 남아 있다.

검증에서 뒤집힌 것

이번 커밋으로 이전 daily의 주장 하나가 뒤집혔고, 다른 하나는 부분적으로 뒤집힌다.

  • ⚠ 주장 변경 #1: LLM-judge 검색 관련성 액션 두 개에 대한 4세대+20 관측이 뒤집혔다.
    • 대상은 LLM_JUDGE_RELEVANT_TO_SEARCH·LLM_JUDGE_NOT_RELEVANT_TO_SEARCH다.
    • 2026-09-13 daily(4세대+20)는 이 두 액션이 "Phoenix 검색 랭킹 config xrecsys_search(4세대+4에서 도입)의 자연스러운 확장"이라고 했다. 또 "grox나 dedicated LLM judge가 (query, tweet) 쌍에 relevant·not 라벨을 흘려 주는 파이프라인이 어딘가에 있어야 한다"고 했다.
    • 5일 만에 vacate된 것으로 봐서, 라벨의 프로덕션 연결이 진행되지 않은 채 되돌려졌다고 봐야 한다.
    • 정본에서 이 두 라벨의 미공개 언급을 삭제한다.
  • ⚠ 주장 변경 #2: 4세대+22 daily의 "click_dwell_time 헤드의 실효 기여는 이전 대비 ~60배 축소 상태" 서술이 부분적으로 뒤집힌다.
    • 4세대+23에서 EnableCdwellOnImpr가 도입되면서, 이 헤드의 최종 값은 (click_dwell_prob × click_prob)이 된다.
    • 이는 60배 축소보다 더 큰 축소를 낳는다(확률 × 확률 = 원단위와 비교해 훨씬 작은 스케일).
    • 실측 스케일은 ContClickDwellTimeWeight 조정 및 EnableCdwellOnImpr 활성화 여부에 좌우된다.
    • 정본의 Attention 헤드 절에 이 재정의를 반영한다.

레이더

LIGE-GR(Meta)은 레이더의 이번 달 신규 항목으로, SARA(Kuaishou)는 관찰 중 스레드에 추가한다.

  • LIGE-GR(Meta)은 arXiv에서 통과한 새 항목이다. Instagram Reels 1.14% time-spent, Facebook Video 0.72% time-spent 개선을 주장한다. 주제는 랭킹 → 생성형 추천으로의 smooth leap이다.
    • 우리 추적 서비스(Instagram·Facebook·Threads)의 direct evidence이므로 레이더 심층 분석 후보다.
    • radar.md의 ## 이번 달 신규에 추가한다(심층 분석 필요 표시).
  • SARA(Kuaishou)는 MLLM 기반 추천용 articulated rationale을 다룬다. Kuaishou 프로덕션 배포(30일+, 240M 유저)를 주장한다.
    • Kuaishou는 우리 추적 서비스가 아니지만, 대규모 프로덕션 MLLM 추천 배포이므로 관찰 중 스레드에 추가한다.
  • ANGLE은 원-스텝 스폰서 서치 광고 retrieval이다(2609.18296). 배포 주체 저자 소속을 확인해서 플랫폼 기업 여부를 판단해야 한다.
  • PCap은 어제(2026-09-17) 이미 radar에 편입됐다. 중복 처리하지 않는다.

다음에 볼 것