1. Phoenix가 산출한 slate context를 home-mixer가 소비하기 시작 — X (4세대+5, d0cef2f) — For You
자료 xai-org/x-algorithm @ d0cef2f — "Open-source X Recommendation Algorithm" — 2026-08-20 20:22 UTC, 공개 소스코드. 파일 14개, +479/−200. 4세대+4(2026-08-19, aad7179, 파일 79 +3.3k/−0.7k) 대비 1/7 정도 규모지만 4세대+4에서 proto·config로 준비만 해 둔 두 스캐폴딩이 처음으로 실 파이프라인 배선으로 이어지는 커밋.
배경 4세대 이후 X 저장소는 매일 커밋 리듬으로 후속 드랍이 붙어 왔다: 8/14 4세대+1(c65aa17) → 8/17 4세대+2(b089ce6) → 8/18 4세대+3(11a71f8) → 8/19 4세대+4(aad7179, 대형). 그 다음 8/20에 나온 이번 드랍은 규모는 4세대+4의 대형 확장 뒤 소규모지만, 4세대+4가 준비한 두 스캐폴딩(Phoenix proto의 SlateContext.sid* 필드 신설, RewardOutputs 신설, xrecsys_search config)이 실제 서빙·랭킹 파이프라인 배선으로 넘어가는 시점이라는 게 이 커밋의 성격이다. 4세대~4세대+4의 canon 반영은 "proto·config가 준비됐다"까지였고, 지금은 SlateContext가 Phoenix 예측 응답을 통해 home-mixer로 실제 흘러들어가는 배선이 처음 코드로 확인된다.
또 새로 나오는 개념 몇 개:
- AI trend — X의 "Trending" 지면(사람이 많이 얘기 중인 이벤트) 항목. 이 커밋 이전엔 tweet에 "AI trend" 라벨을 붙이는 로직이 home-mixer 파이프라인엔 부재했다. Grok이 트윗을 관찰해 어떤 AI trend에 속하는지 판정한 결과를 Strato에 저장해 두는 upstream 파이프라인이 있었을 것으로 보이지만(
batch_get_tweet_ai_trendAPI) 이 저장소 밖[추정]. - served slate context — Phoenix 서빙 응답이 후보별로 재순위 재료(pool 랭크, fatigue, pre-diversity score, SID 3계층, SID gap 3계층)를 담아 돌려주는 스키마 필드. 4세대+4의 proto에 정의만 됐고, 이 커밋에서 소비 경로가 배선.
- SlateContext — DPP 재순위 슬레이트의 각 슬롯이 갖는 컨텍스트 벡터.
k(슬롯 인덱스),pool_rank(원 스코어 정렬 순위),pool_rank_gap(위 슬롯과의 pool rank 차),fatigue,pre_diversity_score,sid_known,sid_k_l1/l2/l3(3계층 SID 각각의 슬롯 인덱스),sid_gap_l1/l2/l3(각 계층 SID의 슬롯 간 gap). 4세대+3 VMRanker에서 SID 필드 7개가 배선됐던 것과 동일 필드 세트.
AI Trend Feedback Context Hydrator — 157줄 신규
파일 신규 [확인] — home-mixer/candidate_hydrators/ai_trend_feedback_context_hydrator.rs. 파이프라인 배선은 phoenix_candidate_pipeline.rs:424-428 — 기존 TopicFeedbackContextHydrator 바로 뒤에 나란히 추가:
Box::new(TopicFeedbackContextHydrator { strato_client: strato_client.clone() }),
Box::new(AiTrendFeedbackContextHydrator { strato_client: strato_client.clone() }),
게이트 3중 조건 [확인] — ai_trend_feedback_context_hydrator.rs:54-58:
query.params.get(EnableAiTrendFeedbackContext)
&& !query.is_topic_request()
&& !query.in_network_only
즉 (a) 게이트 파라미터 rust_home_mixer_enable_ai_trend_feedback_context 켜짐(default false), (b) topic 요청이 아님(topic 지면과 겹치지 않게), (c) Following 지면이 아님(OON 후보만 대상). Following 파이프라인에는 배선 안 됨.
후보 필터 — "standalone original post" 4중 조건 [확인] — ai_trend_feedback_context_hydrator.rs:17-22:
fn is_eligible_original_post(candidate: &PostCandidate) -> bool {
candidate.in_reply_to_tweet_id.is_none()
&& candidate.retweeted_tweet_id.is_none()
&& candidate.ancestors.is_empty()
&& candidate.following_replied_user_ids.is_empty()
}
답글·리트윗·대화 조상 있음·팔로잉이 답글 단 대상 — 이 넷 중 하나라도 걸리면 후보에서 제외. 즉 인용 아닌 원본 포스트만 AI trend 라벨을 붙일 수 있다.
Top-2 trend 선택 + trend당 후보 1개 랜덤 [확인] — ai_trend_feedback_context_hydrator.rs:24-52:
const TOP_TREND_COUNT: usize = 2;
fn select_feedback_targets(candidate_trends: &[(usize, i64)]) -> HashMap<usize, i64> {
let mut frequency: HashMap<i64, usize> = HashMap::new();
for (_, trend_id) in candidate_trends {
*frequency.entry(*trend_id).or_default() += 1;
}
let mut ranked: Vec<(i64, usize)> = frequency.into_iter().collect();
ranked.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
ranked.truncate(TOP_TREND_COUNT);
let mut rng = rand::rng();
let mut selected: HashMap<usize, i64> = HashMap::new();
let mut used_indices = std::collections::HashSet::new();
for (trend_id, _) in ranked {
let candidates_for_trend: Vec<usize> = candidate_trends
.iter()
.filter(|(idx, id)| !used_indices.contains(idx) && *id == trend_id)
.map(|(idx, _)| *idx)
.collect();
let Some(&idx) = candidates_for_trend.choose(&mut rng) else { continue };
used_indices.insert(idx);
selected.insert(idx, trend_id);
}
selected
}
절차: (1) eligible한 후보 각각에 Strato batch_get_tweet_ai_trend로 trend_id 조회 → (2) 후보들의 trend_id 빈도 히스토그램 → (3) 상위 2개 trend(동률이면 trend_id 작은 쪽 우선) → (4) 각 trend에 대해 후보 1개를 무작위로 선택 → (5) Strato batch_get_ai_trend_name로 그 trend의 표시 이름 조회. 즉 한 페이지에 최대 2개의 AI trend 라벨이 붙고, 각 trend당 한 후보에만 붙는다 — 같은 trend의 여러 후보를 같은 라벨로 도배하지 않는 억제 장치.
렌더 — URT TweetContext의 topic 파이프에 fallback으로 합류 [확인] — home-mixer/util/urt/post_marshaller.rs:72-110:
if let Some(topic) = post.topic_feedback_topic.as_ref().filter(|t| !t.is_empty()) {
return Some(TweetContext {
context_type: ContextType::TOPIC,
text: topic.clone(),
landing_url: None,
context: Some(TweetContextDetails::TopicFeedbackContext(TopicFeedbackContext {
topic: Some(topic.clone()),
url: None,
topic_id: post.topic_feedback_topic_id.clone(),
})),
..
});
}
let trend = post.ai_trend_name.as_ref().filter(|t| !t.is_empty())?;
let trend_id = post.ai_trend_id.as_ref().filter(|id| !id.is_empty())?;
let landing_url = Url {
url_type: UrlType::EXTERNAL_URL,
url: format!("https://x.com/i/trending/{trend_id}"),
urt_endpoint_options: None,
};
Some(TweetContext {
context_type: ContextType::TOPIC,
text: trend.clone(),
landing_url: Some(landing_url.clone()),
context: Some(TweetContextDetails::TopicFeedbackContext(TopicFeedbackContext {
topic: Some(trend.clone()),
url: Some(landing_url),
topic_id: None,
})),
..
})
즉 렌더 우선순위는 topic feedback이 있으면 topic으로, 없으면 AI trend로 폴백. 둘 다 URT에서는 ContextType::TOPIC이라는 같은 컨텍스트 타입으로 나가지만, AI trend 경로는 (a) landing_url이 채워진다는 점, (b) TopicFeedbackContext.topic_id가 None이라는 점에서 topic 경로와 다르다. 즉 유저 UI에서 이 트윗 위의 topic-형태 chip을 탭하면 topic feedback 페이지가 아니라 x.com/i/trending/{trend_id} (해당 trend 지면)로 이동한다.
"
https://x.com/i/trending/{trend_id}" —post_marshaller.rs:92
의미 X가 지금까지 사용해 온 topic feedback context(사용자 관심 topic 추론용 컨텍스트 라벨)와 나란한 두 번째 컨텍스트 채널을 열었다. topic이 이미 있으면 topic이 이기고, 없을 때만 AI trend가 들어가는 폴백 구조라 겹침이 없다. 관측 신호 확장 — 사용자가 이 chip을 탭·닫기·무시하는 반응이 곧 "이 유저는 이 AI trend에 관심 있는가"의 라벨 소스가 된다. 4세대~4세대+4가 반영해 온 "안전 파이프라인의 계층 확장"과 다른 방향으로 랭킹 상류의 시그널 소스 자체를 넓히는 흐름이 확인된다.
미공개: AI trend의 정의(누가·어떻게 trend id를 만드는지 — Grok으로 트윗 클러스터링? 사람 큐레이션?), batch_get_tweet_ai_trend upstream 파이프라인, EnableAiTrendFeedbackContext 실 배포 여부, TOP_TREND_COUNT = 2의 튜닝 이력. 게이트 이름의 rust_home_mixer_ 접두사는 이 저장소의 다른 rust-migration 게이트와 동일 규칙이라 A/B 실험 중일 가능성 [추정].
served_slate_context — Phoenix가 서빙에서 계산한 slate context를 home-mixer가 소비
4세대+4에서 Phoenix proto에 신설된 SlateContext.sid* 필드(각 슬롯의 SID 3계층 및 gap 3계층)가 이 커밋에서 실 파이프라인 소비 경로로 배선됐다. 세 곳이 동시에 열렸다:
(a) PostCandidate에 새 필드 [확인] — home-mixer/models/candidate.rs:26-27, 76-77:
pub struct PostCandidate {
pub score: Option<f64>,
pub slate_context: Option<SlateContext>,
#[serde(default)]
pub served_slate_context: Option<SlateContext>, // ← 신규
...
pub ai_trend_name: Option<String>, // ← 위 절 신규
pub ai_trend_id: Option<String>, // ← 위 절 신규
}
즉 후보 구조체에 slate_context 저장 슬롯이 두 개가 됐다 — 기존 slate_context(home-mixer가 계산해 캐시한 것), 신규 served_slate_context(Phoenix가 서빙에서 산출해 응답에 담아 온 것).
(b) Proto ↔ Rust 변환 impl [확인] — candidate.rs:99-118:
impl From<xai_recsys_proto::SlateContext> for SlateContext {
fn from(c: xai_recsys_proto::SlateContext) -> Self {
Self {
k: c.k,
pool_rank: c.pool_rank,
pool_rank_gap: c.pool_rank_gap,
fatigue: c.fatigue,
pre_diversity_score: c.pre_diversity_score,
sid_known: c.sid_known,
sid_k_l1: c.sid_k1,
sid_k_l2: c.sid_k2,
sid_k_l3: c.sid_k3,
sid_gap_l1: c.sid_gap1,
sid_gap_l2: c.sid_gap2,
sid_gap_l3: c.sid_gap3,
}
}
}
proto의 sid_k1/2/3·sid_gap1/2/3 짧은 이름을 home-mixer 쪽 명명 규칙(sid_k_l1/l2/l3·sid_gap_l1/l2/l3, "l"은 level)으로 rename하는 것이 이 impl의 역할.
(c) PhoenixScorer가 예측 응답에서 뽑아 채움 [확인] — home-mixer/scorers/phoenix_scorer.rs:107-125:
.map(|c| PostCandidate {
phoenix_scores: predictions.candidate_scores(&c.get_original_tweet_id()),
served_slate_context: predictions
.candidate_slate_context(&c.get_original_tweet_id())
.map(Into::into),
prediction_request_id: Some(query.prediction_id),
last_scored_at_ms,
..Default::default()
})
즉 Phoenix 예측 응답이 후보 스코어 24개(참여 확률 헤드)와 나란히 SlateContext도 담아 오면, PhoenixScorer가 그걸 served_slate_context에 세팅해 파이프라인에 흘려보낸다.
(d) RankingScorer가 세 갈래 폴백으로 소비 [확인] — home-mixer/scorers/ranking_scorer.rs:700-710, 806-825, 875-895:
fn served_slate_contexts(
query: &ScoredPostsQuery,
candidates: &[PostCandidate],
) -> Option<Vec<SlateContext>> {
if !query.params.get(UseServedSlateContext) {
return None;
}
candidates.iter().map(|c| c.served_slate_context).collect()
}
// ... 두 랭킹 경로(mpn_scoring 브랜치와 diversity 브랜치) 각각에서:
let persisted_contexts: Option<Vec<SlateContext>> =
match Self::served_slate_contexts(query, candidates) {
Some(served) => Some(served),
None if query.has_cached_posts => Self::stored_slate_contexts(candidates),
None => Some(Self::compute_slate_contexts(candidates, &weighted_scores)),
};
세 갈래 폴백: (1) UseServedSlateContext 게이트가 켜져 있고 Phoenix가 모든 후보에 대해 slate context를 보내왔으면 → 그걸 그대로 사용, (2) 게이트가 꺼졌거나 데이터 부족 + Redis 워밍 캐시 히트면 → 이전 요청의 저장된 slate context 재사용, (3) 그 외이면 → 로컬에서 compute_slate_contexts(candidates, weighted_scores)로 계산. 이 순서가 두 랭킹 브랜치 모두에 동일하게 적용.
의미 slate context 계산 지점이 home-mixer 로컬 → Phoenix 서빙으로 이동하는 마이그레이션의 첫 단계다. Phoenix가 예측을 만드는 그 순간 이미 각 후보의 pool 랭크·fatigue·pre-diversity·SID 3계층 정보를 알고 있으므로, home-mixer가 다시 계산하는 대신 서빙 응답에 태워 보내면 (a) 랭커 CPU 절약, (b) Phoenix가 갖는 더 풍부한 컨텍스트(예: SID 3계층은 Phoenix 없인 계산 못 함)를 랭킹 재순위에 반영, (c) home-mixer가 후보 배분·재순위만 담당하는 얇은 층으로 계속 축소되는 방향. 4세대+3에서 SID가 VMRanker에 배선된 것과 결이 통함 — SID는 정의상 Phoenix 학습 산출물이라 home-mixer 로컬 계산이 불가능하다. 이 커밋으로 그 문제가 서빙 시점에 해결.
UseServedSlateContext는 기본 false로 배선됐다. 즉 게이트만 열어 두고 실 롤아웃은 후속 파라미터 변경으로 예정된 상태. 관전 포인트: 이 게이트가 true로 승격되는 다음 커밋 시점 = Phoenix 서빙에서 slate context 산출이 프로덕션 트래픽에 반영되는 시점.
emb_table.rs 리팩터 — Python wrapper와 pyo3-free core 분리
파일 개편 [확인] — phoenix/crates/serving/xai-recsys-engine/src/emb_table.rs (+189/−169, 근본은 동치 리팩터). 기존 load_tensor 함수가 pyo3 타입(Bound<'py, PyString>, PyReadwriteArray1<'py, u8>, Bound<'py, PyList>) 및 pyo3 에러(PyValueError, PyOSError, PyTypeError)에 얽매여 있어서 non-Python 컨텍스트에서 재사용 불가였다. 이 커밋에서 core 로직을 pyo3-independent한 load_tensor_into로 뽑고, 기존 load_tensor를 그 core를 감싸는 얇은 wrapper로 재정의:
pub fn load_tensor_into(
path: &str,
urls: &str,
shard_sources: &[(String, String, usize, usize)],
tensor: &mut [u8],
row_size: usize,
num_row_segments: usize,
) -> Result<(), String> { ... }
#[pyfunction]
pub fn load_tensor<'py>(
py: Python<'py>,
path: Bound<'py, PyString>,
urls: Bound<'py, PyString>,
shard_sources: Bound<'py, PyList>,
mut tensor: PyReadwriteArray1<'py, u8>,
row_size: usize,
num_row_segments: usize,
) -> PyResult<()> {
let mut parsed = ...; // PyList → Vec<(String, String, usize, usize)>
let path = path.to_string();
let urls = urls.to_string();
let tensor_slice = tensor.as_slice_mut()?;
py.detach(|| {
load_tensor_into(&path, &urls, &parsed, tensor_slice, row_size, num_row_segments)
.map_err(PyOSError::new_err)
})
}
동시에 안전성 개선 몇 가지: (a) tokio runtime 생성의 unwrap()이 map_err(|e| format!("tokio runtime: {e}"))로, (b) prefix.len() < 3 방어 추가(checkpoint path too short: {path}), (c) row_segment_size % num_row_segments != 0 등의 modulo 체크가 is_multiple_of API로 통일.
의미 임베딩 테이블 로더 core를 순수 Rust 인터페이스로 노출해서 (i) 학습 파이프라인이나 (ii) Rust-native 서빙 코드 다른 경로에서 pyo3 스택 없이도 재사용할 준비. 4세대+4에서 Phoenix xrecsys_search가 vocab 100M/100M/30M로 커진 것과 결이 통함 — 큰 임베딩 테이블을 여러 서빙·학습 컨텍스트에서 로드해야 할 필요가 커지고 있다는 정황.
Grok 4.6 internal dial 0.1 → 0.3
한 줄 변경 [확인] — grox/flows/ptos/classifier.py:114:
-_GROK_4_6_INTERNAL_DIAL = 0.1
+_GROK_4_6_INTERNAL_DIAL = 0.3
4세대+4(2026-08-19)에서 도입된 shadow dial이 하루 만에 10% → 30%로 3배. _EAPI_4_6_INTERNAL_BREAKER_CONFIG 회로차단기(실패율 50%·600초 창·회복 600초·half-open 5회) 파라미터는 그대로. dial이 적용되는 두 위치(아동 안전 cross-model validation + deluxe 4.3 카테고리 재분류)도 그대로.
의미 4.5 → 4.6 gradual rollout에서 shadow가 (a) 회로차단 트립 안 되고, (b) 판정 disagreement rate가 허용 범위였음을 뒷받침한다 [추정] — 아니면 3배로 dial을 못 올린다. 4세대+2가 도입한 Grok 4.5 심판 계층이 매일 정교화되고 있는 흐름(+2 도입 → +3 로깅 세분화 → +4 4.6 shadow 10% → +5 30%)의 4번째 커밋. 관전 포인트: 다음 커밋에서 50%나 100%로 뛰면 4.6 완전 전환에 근접, 다시 낮아지면 회귀 관측.
grok_sampler stop_predicate — 스트리밍 응답 조기 종료
grox/libs/grok_sampler/llm.py (+27/−13) [확인] — 해당 파일:
기존 _sample_streaming는 raw stream에서 토큰을 받는 대로 계속 소비했다. 이 커밋에서 (a) async with aclosing(...)으로 raw stream을 명시적 컨텍스트에 감싸 조기 종료 시 정리 보장, (b) 각 토큰 yield 뒤 stop_predicate(resp) 체크해서 True면 즉시 break + 카운터 llm.sample.early_stop.count 증가:
stop_predicate: Callable[[str], bool] | None = kwargs.get("stop_predicate")
...
async with aclosing(self._sample_streaming_raw(request, **kwargs)) as raw_stream:
async for tok in raw_stream:
...
yield tok.text
if stop_predicate is not None and stop_predicate(resp):
logger.info(f"Response complete after {tokens_received} tokens, cancelling the stream")
Metrics.counter("llm.sample.early_stop.count").add(1, attributes=attributes)
break
의미 안전 판정 응답이 JSON 태그(<json>...</json>)로 감싸져 오는 경우가 많은데, 태그 닫히면 뒤 토큰은 무의미하다. stop_predicate=lambda r: "</json>" in r 같은 콜백으로 조기 종료해 (a) 토큰 비용 절감, (b) TTFT 이후 지연 감소. 4세대+4에서 도입된 Grok 4.6 shadow가 늘어난 트래픽을 감당하기 위한 sampling 인프라 최적화로 볼 여지 [추정] — dial 30%로 오르는 시점과 같은 커밋에 있다는 정황.
profile_image 필드 배선
grox/core/data_loaders/data_types.py + post_mapper.py [확인]:
class User(BaseModel):
urls: list[str] | None = None
affiliated_business: AffiliatedBusiness | None = None
recent_posts: list["Post"] | None = None
profile_image: "Image | None" = None # ← 신규
AuthorMetadata.profileImageUrl이 존재하면 Image(url=...)로 채운다. 이전엔 User 스키마가 URL/affiliated business/최근 포스트만 담고 프로필 이미지는 없었다. Grok classifier가 프로필 이미지를 판정 컨텍스트로 참조할 수 있게 됐다 (예: 광고 브랜드 안전 판정에서 프로필 이미지의 성인 컨텐츠 여부를 참고하는 시나리오). 4세대+2에서 도입된 성인 컨텐츠 cross-validation 심판이나 후속 판정 계층 확장의 upstream 데이터 배선 준비로 볼 여지 [추정].
요약 표 — 이 커밋의 6개 방향
| 방향 | 파일 | 규모 | 성격 |
|---|---|---|---|
| AI trend feedback context | ai_trend_feedback_context_hydrator.rs + post_marshaller.rs + candidate.rs + param.rs |
+157/+29+ ... | 신규 기능 — For You 컨텍스트 라벨 채널 하나 추가 |
| served_slate_context | phoenix_scorer.rs + ranking_scorer.rs + candidate.rs + param.rs |
+22/−10 등 | 배선 시작 — 4세대+4 proto 소비 경로 |
| emb_table 리팩터 | emb_table.rs |
+189/−169 | 리팩터 (동치) — core를 pyo3-free로 |
| Grok 4.6 dial 3배 | classifier.py |
+1/−1 | 파라미터 변경 — shadow 10% → 30% |
| grok_sampler stop_predicate | llm.py |
+27/−13 | 인프라 — 조기 종료 콜백 |
| profile_image 필드 | data_types.py + post_mapper.py |
+7/+0 | 스키마 확장 — User에 이미지 필드 |
정본 반영 services/x.md:
- 세대 테이블에 4세대+5 (d0cef2f, 2026-08-20) 행 추가 (+479/−200, 파일 14)
- 한 줄 요약에 (1) AI trend feedback context hydrator, (2) served_slate_context 배선 시작, (3) emb_table core 분리, (4) Grok 4.6 dial 0.3, (5) grok_sampler stop_predicate, (6) profile_image 6개 항목 반영
- "후보 생성" 대신 새 하위 절 "AI trend feedback context — 4세대+5 신규" 를 랭킹·필터 사이에 삽입 (topic feedback과 나란한 채널로 위치)
- "Phoenix 학습·인덱스" 절의 4세대+4 하위 소절 뒤에 "4세대+5 (2026-08-20) — Phoenix 서빙이 산출한 slate context를 home-mixer가 소비 시작" 소절 신설
- "Grox → PtOS Adult Content Cross-Validation" 절의 4세대+4 항목 뒤에 "4세대+5 (2026-08-20) Grok 4.6 internal dial 0.1 → 0.3 (3배 상향)" 한 줄 추가
- 열린 질문 절에 "4세대+5 드랍으로 해소된 것 / 신규 (2026-08-22)" 서브섹션 추가 (5개 해소 + 5개 심층 분석 필요)
- 출처 절에 d0cef2f 관련 파일 6개 링크 추가
의미 4세대~4세대+4가 반영해 온 canon의 성격이 다시 확인됐다. 매일 커밋 리듬에서 proto 스캐폴딩과 실 소비 배선이 하루 이틀 차이로 도미노처럼 이어진다. 4세대+4의 SlateContext proto가 4세대+5에서 소비되기 시작한 것은 이 리듬의 세 번째 사례(첫 번째: 4세대+2 V2 라벨 → +3 dual-write 재설계, 두 번째: 4세대+3 VMRanker SID 배선 → +4 proto의 sid* 필드 정식화, 세 번째: 4세대+4 SlateContext proto → +5 phoenix_scorer 배선). AI trend 컨텍스트 채널 신설과 함께, X의 랭킹·후보 파이프라인이 (a) 상류 시그널 소스 확장(AI trend) + (b) Phoenix 서빙 산출물을 home-mixer가 더 얇게 소비하는 방향의 두 축으로 동시 확장 중이라는 관찰이 굳어진다.
2. Xiaohongshu OneModel — 유기·광고·상점 3개 지면 통합 랭커, 프로덕션 A/B [레이더 승격]
자료 OneModel: A Unified Foundation for Platform-Scale Multi-Scenario Ranking (arXiv 2608.18606) — 2026-08-19 arXiv 제출 (v2, v1은 초기 제출), 논문. 저자 14명(Yinqi Zhang, Peiyu Hu, Yuntian Tang, Siying Gu, Jiahao Liang, Longxin Kou, Haiqing Hu, Shuman Zhuang 등) — 초록에 명시된 배포 서비스는 Xiaohongshu(小红书, 중국 라이프스타일 소셜 플랫폼).
배경 Xiaohongshu는 canon이 아직 추적하지 않는 서비스지만, 여러 비즈니스 스트림(유기 추천, 광고, 상점 상품)을 하나의 앱에서 통합 서빙하는 특성이 (X·Meta·Netflix의 서비스별 파편 랭커 대비) 이례적이다. 이 논문은 세 개 지면을 하나의 랭커로 통합하는 프로덕션 배포 진술로, 다음 세 프로덕션 진술군에 새로운 각도로 붙는다: (a) Netflix Foundation Model(단일 FM에서 서비스별 head), (b) Meta GEM(광고 domain 안에서 IG·FB 통합 backbone), (c) Yandex Music Gryphon-v2/Sona(retrieval + ranking 통합). OneModel은 그 중 하나의 backbone이 서로 다른 비즈니스 스트림(유기·광고·상점)을 동시에 서빙하는 각도로 넘어간다.
핵심 주장 (arxiv abstract):
"Platform-scale recommender systems often span multiple business streams such as organic recommendation, advertising, and merchant services, where user behaviors form a continuous cross-stream trajectory. Maintaining separate ranking systems fragments user representations and increases engineering cost. We propose OneModel, a unified framework for multi-stream final ranking. OneModel maps heterogeneous behaviors into shared event sequences, learns long-context user representations with an action-oriented backbone, and introduces Scenario-aware Information Modulation to balance cross-stream transfer and stream-specific specialization."
— OneModel (arXiv 2608.18606) abstract (논문)
핵심 아키텍처 요소:
- 공유 이벤트 시퀀스 — 유기·광고·상점 세 스트림의 사용자 행동을 하나의 이벤트 시퀀스 스키마로 통합
- action-oriented backbone — long-context user representation 학습기 (구체 아키텍처는 abstract에 없음)
- Scenario-aware Information Modulation — cross-stream transfer(공유)와 stream-specific specialization(전용) 사이의 밸런스를 스위칭·게이팅
프로덕션 배포 최적화 요소: stratified user representation, multi-objective training, feature decomposition, user feature prefetching, shared user-tower computation, graph-level inference optimization.
A/B 결과 — Xiaohongshu 프로덕션 배포:
| 지면 | 지표 | 결과 |
|---|---|---|
| Explore Feed (유기 추천) | Time Spent | +0.33% |
| Explore Feed (유기 추천) | Engagement | +1.25% |
| Feed Advertising (광고) | advertising value | +3.43% |
| Feed Advertising (광고) | CTR | +8.18% |
| Merchant Recommendation (상점) | DGMV (Daily GMV) | +1.1867% |
| Merchant Recommendation (상점) | GPM (GMV Per Mille) | +2.1585% |
의미 LLM-native 랭킹 스레드와 결이 조금 다르지만 "단일 랭커가 여러 비즈니스 스트림을 통합 서빙"이라는 회사 차원 아키텍처 결단의 세 번째 프로덕션 진술 — 첫째 Netflix FM(서비스 다양성, foundation → head), 둘째 Meta GEM(광고 domain 안에서 지면 다양성), 셋째 Xiaohongshu OneModel(비즈니스 스트림 다양성). Meta GEM은 광고·IG·FB로 확장했지만 여전히 광고 domain이 중심이고, Xiaohongshu OneModel은 광고·유기·상점을 대등하게 통합한다는 점이 새로운 각도.
정본 반영 radar.md "이번 달 신규" 절에 OneModel 엔트리 추가 (2~4줄 포인터). 아직 서비스 정본(services/xiaohongshu.md 신설)으로 승격은 하지 않는다 — canon의 추적 서비스가 아니고, 두 번째 회사(중국 밖 플랫폼)의 유사 진술이 나오면 topic (multi-task-ranking.md 또는 신규 unified-multi-scenario topic) 승격 후보.
검증에서 뒤집힌 것
없음.
레이더
- OneModel (Xiaohongshu) (arXiv 2608.18606, 2026-08-19) — 위 절 참조. radar.md "이번 달 신규"에 추가
다음에 볼 것
- X d0cef2f 후속 커밋 관찰 —
UseServedSlateContext게이트의 프로덕션 true 승격 시점(= Phoenix 서빙 산출 slate context가 실 트래픽에 반영되는 시점),EnableAiTrendFeedbackContext게이트 활성, Grok 4.6 dial의 다음 이동(30% → 50%/100% or 하락). 매일 리듬이 유지되면 다음 커밋에서 관측 가능 - X
AiTrend정의 upstream 추적 —batch_get_tweet_ai_trend/batch_get_ai_trend_name의 Strato 컬럼이 이 저장소 밖이지만 향후 관련 문서(예:docs/하위 신규 md, README 갱신) 나오면 반영 - Xiaohongshu OneModel 후속 — v2가 8/19 제출이라 논문 자체는 프리프린트. 학회 캠퍼(RecSys/KDD/WWW 2026·2027) 게재 확인, 저자들의 후속 발표 (특히 "action-oriented backbone" 아키텍처 세부, cross-stream transfer 게이팅 메커니즘), Xiaohongshu의 다른 지면(검색?) 배포 진술이 나오는지
- X 4세대+4 심층 분석 (지연): aad7179의
phoenix/xrex/models/recsys_model.py(+265/−25) 트레이스는 여전히 미완 —RewardOutputs가 어느 학습 루프에서 채워지고 소비되는지, link-open 5분위가 quantile regression인지. 사람이 트리거할 때 착수