- 읽은 자료 2건
- 정본 반영 1개 파일
- 레이더 0건
이 경로를 만든 9b0dc31 커밋은 grox PTOS(정책 기반 위반 카테고리 판정) 심판 스택과 For You 랭킹 모델 Phoenix의 서빙·학습 스택에서도 크게 움직였다. Multi-step reply spam이 오늘의 헤드라인이지만, 대응하는 정본(서비스별 현재 이해를 담는 services/ 노트) 갱신이 5건 더 있어 각각 절을 나눠 썼다. 오늘 함께 본 다른 변화는 다음과 같다.
- PTOS 심판 스택에서 카테고리마다 모델이 갈린 첫 실체가 확인됐다. 불법·규제 행위 카테고리의 심판만 Grok 4.1-fast로 옮겨 갔다.
- Phoenix의 서빙 엔진에서 SID(포스트를 나타내는 이산 코드) 조회 사이드카가 완전히 제거됐다. 히스토리 SID는 이제 요청에 실려 온다.
- Phoenix 학습 코드에 H100용 rematerialization 정책이 새로 생겼다. H100에서도 학습이 돈다는 첫 코드 신호다.
1grox에 스레드 chain 단위 답글 스팸 판정 경로가 네 번째로 연결됐다 — X
자료 xai-org/x-algorithm@9b0dc31 — 공개 소스코드, 2026-09-04 00:37 UTC, X.AI Corp. 이 노트들은 4세대 공개 뒤의 X 커밋에 차례로 번호를 붙여 부르며, 이 커밋은 4세대+15다.
배경 grox는 X의 nearline LLM 주석 파이프라인이다. 답글 품질 관리 축에서 grox는 지금까지 판정 흐름 세 개를 갖고 있었다 확인 — generators.py의 PLANS_TO_INJECT. PlanReplyRanking은 답글 하나에 점수를 매기고, PlanSpamComment는 답글 하나가 스팸인지 판정하며, PlanCoordinatedSpam은 여러 계정이 담합한 반복 답글을 잡는다. 세 흐름 모두 4세대에서 통합 이력이 길다.
- 4세대+3(2026-08-18, 11a71f8)에서 따로 있던 spam·comment·reply-ranking generator가
ReplyRankingTaskGenerator하나로 합쳐졌다. - 답글 스팸·답글 랭킹 태스크의 팔로워 게이팅 임계는 2026-08-13부터 08-25까지 15k → 30k → 40k → 60k → 80k → 100k → 120k로 여섯 번 올랐다 확인 —
task_filter.py.- 올린 커밋은 차례로 c65aa17, b089ce6, 11a71f8, 28e414f, d011592, 0d3cdd8이고, 이 커밋 시점의 값은 120k다.
- 비교 대상은 답글 작성자가 아니라 root와 답글 대상 계정의 팔로워 수다.
- Kafka-first 저장 순서, Manhattan monotone-only write, LM 프롬프트에 팔로워 수와 bio를 넣는 옵션 같은 신뢰성 장치는 4세대+9(45b48ba)·+10(24c6094)에서 갖춰졌다.
이번 커밋은 이 흐름에 네 번째 판정 경로인 PlanMultiStepReplySpam을 붙였다. 이름의 Multi-step은 "판정 단위가 개별 답글이 아니라 답글 chain 여러 개"라는 뜻이다.
새로 알게 된 것A. 한 저자가 자기 답글에 다시 답한 chain만 판정에 들어간다
진입 필터는 아래 8가지 조건을 모두 통과한 답글만 판정에 넣는다 확인 —
task_multi_step_reply_spam.py. 필터 클래스는TaskMultiStepReplySpamFilter다.post.ancestors가 있고 그중 삭제(None)된 것이 없다. 즉 답글이고 스레드가 온전하다.- 대상 포스트, 직계 부모, 스레드 root의 user가 모두 살아 있다.
- 답글 작성자가 스레드 root 작성자와 다르다. 원 포스터가 자기 스레드에 다는 답글은 건너뛴다.
GROK_USER_ID·GORK_USER_ID는 건너뛴다. Grok과 Gork 자체가 다는 답글은 대상이 아니다.- 스레드 깊이가 2 이상이다. "root → 첫 답글" 한 단계짜리는 들어오지 않는다.
- 직계 부모의 작성자가 대상 포스트의 작성자와 같다. 같은 사람이 자기 답글에 또 답글을 다는 chain만 진입한다.
- root 작성자의 팔로워가 1,000 이상이다("blast radius"). root 노출이 유의미한 스레드만 들인다.
- 답글 작성자가 high page-rank v2 유저나 grey badge 유저면 건너뛴다. 이미 자리 잡은 계정은 대상이 아니다. 두 여부는
UserStratoLoader.is_high_page_rank_v2_user와is_grey_badge_user를asyncio.gather로 병렬 조회해 확인한다.
8개 조건 가운데 특히 6번이 이 파이프라인의 도메인을 정한다. 그 도메인은 자기 대화 chain으로 root 노출을 훔치는 답글 스팸 chain이다. 예를 들어 유명인의 트윗에 A가 답글을 달고, 그 답글에 A가 다시 답글을 달고, 또 A가 답글을 다는 식으로 chain을 잇는다. 이렇게 root 트윗의 노출을 자기 광고나 유인으로 끌어당기는 패턴이 대상이다.
7번의 임계 1,000은 단발성 판정의 120k보다 두 자릿수 낮다. 이 파이프라인은 root 노출이 상당한 스레드에 폭넓게 돈다.
class TaskMultiStepReplySpamFilter(TaskFilterWithPost): FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION = 1000 ... if post.ancestors[-1].user.id != post.user.id: Metrics.counter("task.filter.skipped.count").add( 1, attributes={"filter": cls.FILTER_NAME, "reason": "parent_not_same_author"}, ) return False root_user_follower_count = post.ancestors[0].user.follower_count or 0 if root_user_follower_count < cls.FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION: Metrics.counter("task.filter.skipped.count").add( 1, attributes={"filter": cls.FILTER_NAME, "reason": "low_blast_radius"}, ) return False—
task_multi_step_reply_spam.py, 2026-09-04
새로 알게 된 것B. Gemma 2가 스레드 전체를 읽고 스팸인 포스트 번호들을 고른다
LM에 보내는 human 메시지의 구조가 코드에 그대로 드러난다 확인 —
classifier_multi_step_reply_spam.py.- 메시지를 만드는 함수는
MultiStepReplySpamScorer._to_convo다. - 시스템 프롬프트는 README 정책(게이밍 방지)에 따라 export에서 빠졌다.
- "Reply Author Info" 절은 답글 작성자의 유저 프로필(handle, follower count, name, bio)을 렌더링한다.
- "Thread" 절은 스레드의 모든 포스트를
#### Post N형식의 인덱스 번호와 함께 나열한다. root에는 "(root post — never flag)" 태그를, 마지막 포스트에는 "(the reply being evaluated)" 태그를 붙인다. - 스레드가
MAX_THREAD_SIZE = 10을 넘으면 앞 5개와 뒤 5개만 남기고 중간을 잘라 낸다. - 각 포스트에
include_follower_count=True와max_media=2를 적용하고, root에만include_bio=True를 적용한다.
즉 이 파이프라인은 LM에게 포스트 하나하나가 스팸인지 묻지 않는다. 스레드 전체를 보고 스팸 인덱스 집합을 뽑아내라는 형태의 태스크를 준다. 응답은 JSON
{"spam_post_indexes": [1, 2, 3], "reason": "..."}이다.content.append( "\n\n# Thread\n\nThe reply thread is listed below; each post is labeled with a `#### Post N` index. " "Post 0 is the original (root) post and the highest-numbered post is the reply being evaluated. " "Refer to posts only by their integer index.\n\n" ) for i, p in enumerate(posts): if i == 0: tag = " (root post — never flag)" elif i == last: tag = " (the reply being evaluated)" else: tag = "" content.append(f"\n\n#### Post {i}{tag}\n\n") content.extend( PostRenderer.render( p, max_media=MAX_MEDIA_PER_POST, include_follower_count=True, include_bio=i == 0, ) )—
classifier_multi_step_reply_spam.py, 2026-09-04- 메시지를 만드는 함수는
판정 LM은 Gemma 2다 확인.
- 코드는
model_name = GEMMA_2이고,OaiSampler로 호출한다. - 4세대+9(45b48ba)에서 grox의
USE_GEMMA_CATEGORIES에 Spam이 들어간 뒤로 grox는 Gemma를 밀고 있고, 이번 선택은 그 흐름의 연장이다. - Multi-step reply spam이 처음부터 Gemma 전용이라는 점은 grok API 예산을 아끼는 방향과 들어맞는다 추정.
- 코드는
새로 알게 된 것C. LM의 답은 세 단계로 걸러진 뒤 스팸 라벨과 0점으로 저장된다
- LM 출력을 그대로 믿지 않고 3단계 안전장치로 거른다 확인.
_drop_if_newest_reply_not_flagged는 LM이 지목한 인덱스 집합에 판정 대상(스레드 마지막 포스트)이 없으면 결과 전체를 버린다. Task 진입 조건이 "이 반응이 스팸일 수 있어야 파이프라인이 도는 것"이므로, 마지막 반응을 스팸이 아니라고 본 결과는 정합성 위반이다._drop_other_author_posts는 LM이 스레드의 다른 저자 포스트를 스팸으로 지목하면 그 포스트를 뺀다. 이 파이프라인의 대상이 "같은 저자가 자기 답글에 자기가 답한 chain"이므로, 다른 저자의 포스트가 결과에 나오면 무시한다._drop_singleton은 결과가 정확히 1개면 버린다. chain 스팸은 정의상 "여러 후속 답글"이 대상이어서, 하나만 잡힌 결과는 유효하지 않다.
- 저장 태스크는 걸러진 결과에서 지목된 포스트마다 스팸 라벨을 붙이고, 답글 랭킹 점수를 0.0으로 강제 저장한다 확인.
- 태스크 이름은
TaskWriteMultiStepReplySpamReplyRanking이다. - 지목된 각 포스트에
_apply_reply_spam_label(post_id, author_id)를 적용한다. 단발성 판정과 같은 헬퍼다. save_reply_ranking_score(..., ReplyRankingScore(score=0.0, reasoning=reasoning[-500:]))로 점수를 0.0으로 강제 저장한다. 가장 낮은 신뢰를 뜻한다. reasoning은 마지막 500자만 남긴다.save_reply_ranking_kafka_v2(...)로 v2 Kafka에 emit한다.DISABLE_RULES = [DisableTaskForNonProd]로 dev와 staging에서는 실제 label을 붙이지 않는다.
- 태스크 이름은
새로 알게 된 것D. 새 파이프라인은 새 파일 4개와 기존 파일 2개 수정으로 이뤄지고, 기존 판정과 같은 이벤트 소스에서 함께 돈다
새 파일 4개(474줄)가 생겼고 기존 파일 2개가 수정됐다 확인 —
grox/flows/reply_spam/.- 파이프라인 이름은
PlanMultiStepReplySpam이다. task_multi_step_reply_spam.py(206줄)plan_multi_step_reply_spam.py(29줄)classifier_multi_step_reply_spam.py(226줄)state_multi_step_reply_spam.py(13줄)prompts.py+5줄:multi_step_reply_spam_system_prompt를 추가했다.generators.py+7줄: 두 스트림 generator의PLANS_TO_INJECT에 이 plan key를 넣었다.
- 파이프라인 이름은
Plan DAG는 필터, media hydration, 판정, 저장 순서로 이어진다 확인 —
plan_multi_step_reply_spam.py.task_multi_step_reply_spam_filter → task_media_hydration → task_multi_step_reply_spam_detection → task_write_multi_step_reply_spam_reply_rankingTaskMediaHydration을 필터 통과 뒤에만 돌리는 순서가 눈에 띈다. media hydration은 비용이 크므로, 필터가 상당 부분을 잘라 낸 뒤에만 실행된다.답글 랭킹 스트림 generator 두 개가 새 Plan을 주입 목록에 추가한다 확인 —
generators.py.- 두 generator는
ReplyRankingTaskGenerator(kafkaTOPIC_UNIFIED_POSTS_V3)와ReplyRankingRecoveryTaskGenerator이고, 주입 목록은PLANS_TO_INJECT다. - 즉 기존 세 판정(단발성 reply_spam, coordinated_spam, spam_comment)과 이 multi-step 판정이 같은 v3 이벤트 소스에서 병렬로 돈다.
- 두 generator는
정본 반영 services/x.md에 "4세대+15 공개로 해소된 것 / 신규 (2026-09-04)" 절을 새로 만들었다. "Grox (LLM 주석)" 아래에는 새 소절 ### 4세대+15 (2026-09-04, 9b0dc31) — grox 스레드 단위 multi-step reply spam 분류기 신설 + PTOS ...을 추가했다. 헤더의 최종 갱신은 2026-09-04에서 2026-09-05로 올렸고, 출처 절에도 새 파일 6개의 링크를 추가했다.
의미 grox의 답글 스팸 판정에 세 번째 축이 붙었다. 한 저자가 자기 답글 chain으로 root 노출을 훔치는 시간축 담합 스팸이다.
- 정본의 이전 판은 두 흐름을 알았다. 답글 스팸은 개별 답글을 대상으로 판정하고, coordinated_spam은 "여러 계정이 담합한 반복 답글"을 대상으로 한다. coordinated_spam이 계정 담합 축이라면 multi-step은 시간축 chain 담합이다.
- 게이팅 임계가 root 팔로워 1k로 낮게 잡히면서, 이 파이프라인은 root 저자의 노출 자산이 상당한 스레드에 광범위하게 도는 것으로 보인다. 그리고 판정을 Gemma에 맡겨 grok API 예산을 아끼는 방향과 들어맞는다.
- 기존 정본의 grox reply_spam 이해와 부딪히지 않는다. 오히려 정본이 다뤄 온 단발성 판정의 follower 120k 임계가 유지되면서, 훨씬 낮은 임계(≥ 1k)의 chain 판정이 새 층으로 붙는 두 층 구조로 넓어진 것으로 이해할 수 있다.
2PTOS 심판 모델이 카테고리마다 갈린 첫 실체가 확인됐다 — X
자료 xai-org/x-algorithm@9b0dc31 — 같은 커밋(4세대+15)의 grox/flows/ptos/classifier.py와 grox/flows/ptos/task_safety_ptos_policy.py.
배경 grox의 PTOS(정책 기반으로 위반 카테고리를 판정하는 계층) 스택에는 4세대 동안 first-pass 분류기 위에 심판(cross-validator) 계층이 차츰 붙어 왔다.
- 4세대+2(2026-08-17, b089ce6)에서 성인 콘텐츠 disagreement 심판이 도입됐다. 이름은
SafetyPtosAdultContentCrossValidationJudge이고, 모델은EAPI_GROK_4_5_X_ALGO였다(classifier.py:557-561). - 4세대+4~+6(2026-08-19~21)에서는 아동 안전 cross-validation과 deluxe 재분류가 Grok 4.6 internal을 부르는 비율(dial)이 10% → 30% → 100%로 올랐다. 이틀 만에 4.5 internal을 대체했고, 4.5 internal은 코드에서 완전히 삭제됐다(
classifier.py:114, 281, 477). - 4세대+7(2026-08-24, d011592)에서는 성인 콘텐츠 심판이 쓰던
EAPI_GROK_4_5_X_ALGO를 함께 쓰는SafetyPtosPolicyCrossValidator가 새로 생겼다. 이 심판은 ChildSafety, ViolentMedia, IllegalAndRegulatedBehaviors 3개 카테고리를 모두 단일 모델(4.5-X_ALGO)로 처리했다. - 정본은 이 상태를 "X는 primary(4.6-internal)와 judge(4.5-X_ALGO)를 서로 다른 모델 세대·튜닝으로 유지하는 이중 스택"이라고 요약했다. 그리고 심판 스택의 카테고리별 모델 분화가 다음 관측 포인트가 될 것이라고 남겨 뒀다.
이번 커밋에서 그 분화가 첫 실체로 확인됐다.
새로 알게 된 것A. IllegalAndRegulatedBehaviors 심판만 Grok 4.5에서 4.1-fast로 옮겨 갔다
심판이 쓸 모델로 Grok 4.1-fast-X_ALGO가 새로 등록됐다 확인 —
grox/flows/ptos/classifier.py.- 모델 이름은
EAPI_GROK_4_1_FAST_X_ALGO다. SafetyPtosPolicyCrossValidator.__init__이 4.5-X_ALGO sampler 옆에 4.1-fast-X_ALGO sampler를 나란히 초기화한다.
eapi_4_1 = grox_config.get_eapi_model(ModelName.EAPI_GROK_4_1_FAST_X_ALGO) self.eapi_4_1_x_algo = EapiSampler(EapiModelConfig(**eapi_4_1.model_dump()))—
grox/flows/ptos/classifier.py, 2026-09-04- 모델 이름은
IllegalAndRegulatedBehaviors 카테고리의 심판만 4.1-fast로 옮겨 갔다 확인.
- 바뀐 함수는
_validate_illegal_and_regulated_behaviors다.
- metric = "safety_ptos.illegal_and_regulated_behaviors_cross_model_validate_with_grok_4_5" + metric = "safety_ptos.illegal_and_regulated_behaviors_cross_model_validate_with_grok_4_1" ... - async with _eapi_4_5_x_algo_breaker.guard(): - raw = await self.eapi_4_5_x_algo.sample( - convo.interleaveToEapi(), conversation_id=convo.conversation_id - ) + raw = await self.eapi_4_1_x_algo.sample( + convo.interleaveToEapi(), conversation_id=convo.conversation_id + )—
grox/flows/ptos/classifier.py, 2026-09-04- 서킷브레이커 guard도 사라졌다. 4.5-X_ALGO는
_eapi_4_5_x_algo_breaker로 감싸 호출했지만, 4.1-fast는 breaker 없이 직접 호출한다. - ChildSafety·ViolentMedia는 여전히 4.5-X_ALGO를 breaker와 함께 호출한다. 즉 카테고리마다 심판 모델이 다르게 배정됐다.
- 바뀐 함수는
새로 알게 된 것B. 같은 카테고리의 first-pass는 이제 확률 게이트 없이 Gemma로 간다
IllegalAndRegulatedBehaviors가 first-pass를 Gemma로 판정하는 카테고리 목록에 들어갔다 확인.
- 목록은
USE_GEMMA_CATEGORIES다.
USE_GEMMA_CATEGORIES = { SafetyPolicyCategory.Spam, + SafetyPolicyCategory.IllegalAndRegulatedBehaviors, }- 목록은
first-pass 라우팅에서 Gemma로 보낼지를 확률로 정하던 게이트가 모두 걷혔다 확인.
- if ( - sample_for_gemma - and not self.deluxe - and self.use_gemma - and random.random() < self.use_oai_gemma4_dial - ): + if sample_for_gemma: try: return await self.oai_gemma4.sample( convo.to_openai_messages(), conversation_id=convo.conversation_id )—
grox/flows/ptos/classifier.py, 2026-09-04- 즉 Spam과 IllegalAndRegulatedBehaviors 두 카테고리는 first-pass가 100% Gemma로 라우팅되고, grok는 폴백만 맡는다.
self.use_oai_gemma4_dial = 1.0상수 초기화도 삭제됐다.
새로 알게 된 것C. IllegalAndRegulatedBehaviors 판정은 이제 Gemma, 재분류, 4.1-fast 심판의 세 층을 거친다
재분류를 거는 카테고리에 IllegalAndRegulatedBehaviors가 추가됐다 확인 —
task_safety_ptos_policy.py.- 바뀐 곳은
task_safety_ptos_policy.py의 재분류 트리거다.
- elif violation.category == SafetyPolicyCategory.ViolentMedia: + elif violation.category in ( + SafetyPolicyCategory.ViolentMedia, + SafetyPolicyCategory.IllegalAndRegulatedBehaviors, + ): policy = await active_classifier.classify_policy_for_violation( post, violation )- 이전에는 ViolentMedia만 이 재분류 경로(deluxe 4.3 카테고리 판정 재확인)를 탔다.
- 즉 이번 커밋으로 IllegalAndRegulatedBehaviors 흐름은 first-pass Gemma → deluxe cross-model 재분류(비쌈) → 심판 Grok 4.1-fast의 세 층 스택이 됐다.
- 바뀐 곳은
정본 반영 services/x.md의 "4세대+15 공개로 해소된 것 / 신규" 절에 이 3건을 추가했다. 카테고리별 심판 모델 분화가 첫 실체로 확인된 사실은 4세대+7 정본 정리 아래의 "심판 확장의 다음 카테고리" 관측 포인트를 갱신한다.
의미 PTOS 심판 층 안에서 카테고리마다 심판 모델이 따로 튜닝된다는 사실이 이번에 확정됐다. 정본의 이전 판은 "X가 primary와 judge를 서로 다른 모델로 유지"까지 알았다.
- Judge 모델의 선택 기준은 카테고리별 판정 난도, 심판 latency 요구, primary와 judge의 disagreement 특성 셋으로 정해지는 것으로 보인다 추정.
fast접미사가 4.1 심판에 붙고 breaker 없이 호출하는 것을 보면, IllegalAndRegulatedBehaviors는 판정이 상대적으로 명확해서(예: 마약·무기·아동 노출같이 라벨 자체가 강한 신호를 담고 있어) 빠르고 저렴한 심판으로 충분하다는 판단이다 추정.- 반면 ChildSafety·ViolentMedia는 판정이 미묘해서(예: violent media에서 만화·게임과 실제를 가르는 일) 4.5 심판이 필요하다는 정책이다.
- Spam·IllegalAndRegulatedBehaviors의 first-pass Gemma 100% 승격은 grok API 예산을 아끼는 방향과 들어맞는다. 다음 승격 후보로는 폭력·혐오 발화가 남아 있는 것으로 관측된다.
3Phoenix 서빙 엔진에서 SID 조회 사이드카가 완전히 제거됐다 — X
자료 xai-org/x-algorithm@9b0dc31 — 같은 커밋(4세대+15)의 phoenix/crates/serving/xai-recsys-engine/ 서브트리와 phoenix/xrex/inference/.
배경 Semantic ID(SID)는 RQ-VAE 계열의 계층 코드북으로 post embedding을 이산 코드워드로 표현하는 기법이다.
- X에서는 4세대+3(2026-08-18, 11a71f8)에서 VMRanker 요청에 SID 필드 7개가 연결되면서, 3-level 계층 코드북이 있다는 사실이 처음 확인됐다.
- VMRanker는 피드에 낼 후보 묶음을 다양성 기준으로 재정렬하는 단계다.
- 필드는
sid_known,sid_k_l1/l2/l3,sid_gap_l1/l2/l3다.
- 4세대+13(2026-09-01, 7ba7768)에서는 랭커 post tower에 dense reconstruction을 넣는 코드가 연결만 되고 꺼진 상태(wired-but-off)로 공개됐다.
- 이 코드는 오프라인
.safetensors에서 RQ-VAE 형태 stacked residual codebook을 로드한다. - 파일은
phoenix/xrex/models/recsys_sid.py(72줄 신규)이고, 기본값은sid_embedding_mode="learned"다.
- 이 코드는 오프라인
- 그리고 Phoenix 서빙 엔진에는 사이드카 gRPC 클라이언트
sid_client.rs가 있었다. 유저 히스토리의 post_id마다 SID 서비스에 따로 조회를 걸어 코드워드를 채우는 옵션 경로였다. 4세대+3의 정본 정리에서 이 사이드카는 "옵션"으로만 기록됐다.
이번 커밋은 이 사이드카를 완전히 걷어 냈다.
새로 알게 된 것A. SID 클라이언트와 그 연결 코드가 Rust, Python, CLI에서 모두 지워졌다
SID 조회 클라이언트의 Rust 구현과 Python 바인딩이 모두 지워졌다 확인 — xai-org/x-algorithm@9b0dc31.
- 삭제된 파일은
sid_client.rs(189줄)와sid_retrieval_runner.py(17줄)다. - 사라진 것은
SemanticIdClientRust 클라이언트와 Python 바인딩PySemanticIdClient다. Cargo.toml·pyproject.toml에서xai-recsys-sid-proto의존성이 제거됐고,src/lib.rs의pub mod sid_client;선언도 지워졌다.
- 삭제된 파일은
Python struct와 서버 매크로에서 SID 클라이언트 필드가 빠졌다 확인 —
phoenix/crates/serving/xai-recsys-engine/src/python.rs.RecsysPredictorImpl과RecsysRetrievalPredictorImpl두 struct에서sid_client: Option<Arc<crate::sid_client::SemanticIdClient>>필드가 제거됐다.server_impl매크로가 받던sid_client: Option<&crate::sid_client::PySemanticIdClient>인자와add_class::<PySemanticIdClient>()등록도 함께 지워졌다.
추론 CLI와 model runner에서 SID 엔드포인트 설정(
--sid_endpoint)이 사라졌다 확인 —phoenix/xrex/inference/launch_inference.py.- CLI에서는
--sid_endpointargparse 인자와runner.sid_endpoint = args.sid_endpoint대입이 제거됐다. phoenix/xrex/inference/model_runner.py에서는sid_endpoint: str | None = None필드가 제거되고,create_server의 SID client 조립 코드 12줄이 사라졌다.- 지워진 두 분기와 새로 생긴
sid_num_levels줄이 새 계약을 드러낸다.
- if _use_post_sid and self.sid_endpoint and sid_num_levels > 0: - sid_client = xai_recsys_engine.PySemanticIdClient( - self.sid_endpoint, - sid_num_levels, - ) - logger.info( - "SID client connected: endpoint=%s, sid_num_levels=%d", - self.sid_endpoint, - sid_num_levels, - ) - elif _use_post_sid: - logger.info( - "Parsing history SIDs from the request (sid_num_levels=%d); no sid_endpoint", - sid_num_levels, - ) + user_tower = self.model_config.user_tower_config + sid_num_levels = user_tower.sid_num_levels if user_tower.use_post_sid else 0—
phoenix/xrex/inference/model_runner.py, 2026-09-04즉 두 실행 모드(SID client 연결, 또는 request에서 파싱) 가운데 앞의 것이 사라지고 뒤의 것만 남았다. 히스토리 SID는 요청에 실려 들어온다는 것이 새 계약이다.
- CLI에서는
새로 알게 된 것그 밖의 변경
- D2H 전송에 CUDA pinned host memory를 쓰는 flag가 기본으로 켜졌다 확인.
- flag는
use_pinned_d2h이고, 기본값이 False → True로 바뀌었다. A 카드 마지막 불릿이 다룬 파일에서 부수적으로 바뀐 것이다. - 이 옵션은 4세대 공개(2026-08-13) 때 이미 기본 False로 있었고, 22일 만에 기본값이 됐다 확인 —
model_runner.py:492. - 도움말은 이렇게 썼다: "Use CUDA pinned host memory for D2H transfer (~50 GB/s vs JAX's ~3 GB/s). Saves ~29ms/inference. Default True; pass False to opt out."
- flag는
정본 반영 services/x.md의 "4세대+15 공개로 해소된 것 / 신규"에 이 사실을 추가했다. 4세대+3에서 "Phoenix serving SID 사이드카는 옵션"이라고 기록한 것을 "옵션이 완전히 제거, SID는 request 계약"으로 갱신한다.
의미 Phoenix retrieval SID 파이프라인 아키텍처가 상당히 정리됐다. SID codeword 조회 책임이 online serving 밖으로 옮겨 갔다.
- 정본의 이전 판은 세 가지를 알았다.
- phoenix-rankall이 SID 인덱스 window(
video_14day등)를 관리한다. - VMRanker에 SID 필드 7개가 연결돼 있다.
- 서빙 엔진에는 옵션인 SID 사이드카가 있다.
- phoenix-rankall이 SID 인덱스 window(
- 이번 커밋으로 서빙 엔진에서 SID 사이드카가 완전히 사라지고, 요청에 SID가 직접 실린다는 사실이 확정된다.
- For You 피드 요청을 처리하는 home-mixer(또는 candidate hydration 층)가 candidate hydration 단계에서 SID를 미리 뽑아 request에 담고, phoenix-rankall은 그것을 그대로 소비한다. 요청당 추가 gRPC 호출 하나가 사라지고, SID 인프라의 fault domain이 좁아진다.
- 다만 이번 diff에서는 home-mixer 쪽 SID hydrator의 명세가 아직 확인되지 않았다. SID를 채우는 hydrator의 이름·소스와 storage 백엔드(Manhattan? Strato? phoenix-rankall 자체?)가 후속 관측 포인트다.
4Phoenix 학습이 GB300 외에 H100에서도 돈다는 첫 코드 신호가 나왔다 — X
자료 xai-org/x-algorithm@9b0dc31 — 같은 커밋(4세대+15)의 phoenix/xrex/models/remat.py(+17줄)와 phoenix/xrex/cutedsl/ranker_attention_fa4.py(+139/−75).
배경 Phoenix 학습 하드웨어에 대한 정본은 "Phoenix 프로덕션 학습이 GB300에서 돈다"는 것을 확정한 상태였다. 이 확정은 4세대+6(2026-08-21, 28e414f)에서 _GB300_OVERRIDES가 등장하며 이뤄졌다.
- Rematerialization(역전파 때 일부 활성값을 저장하지 않고 다시 계산해 메모리를 아끼는 기법) 정책은
SAVE_GB300_RECSYS = 29하나뿐이었다. - GB300 override는 GB300 하드웨어에 맞춘 프리셋을 배정했다. 값은
bs_per_device=512·attn_impl="cutedsl_ranker_varlen_attn"·remat_policy=RematType.SAVE_GB300_RECSYS다. - FA4(NVIDIA CUTLASS/CUTe DSL 기반 flash attention v4) 커널은 히스토리 tile이 항상 tile 크기(128)의 배수로 채워진다고 가정했다. 이 가정 아래 layout을 python 호스트에서 미리 만들어 캐시(
_DENSE_BS_LAYOUT_CACHE)에 담아 썼다.
이번 커밋은 H100용 remat 정책을 새로 등록하고, FA4 커널이 가변 길이 히스토리를 직접 다루도록 고쳤다.
새로 알게 된 것A. H100용 remat 정책이 생겼지만, H100 전용 설정 묶음은 이 커밋에 아직 없다
H100용 remat 정책이 새로 등록됐다 확인 —
phoenix/xrex/models/remat.py.- 새 값은
SAVE_H100_RECSYS = 32다.
class RematType(enum.IntEnum): WHOLE = 0 SAVE_GB300_RECSYS = 29 + SAVE_H100_RECSYS = 32custom_remat_policy(policy)에 새 분기가 붙었다. 이 분기는 GB300과 같은 이름 리스트를 rematerialization checkpoint 대상으로 저장한다.- 이름 리스트:
attn_outputs·dense_outputs·dense_outputs_individual·attn·gate_up_proj·dense_up_proj·query_heads_rope·key_heads_rope·cutedsl_attn_outputs·query_heads·key_heads·value_heads·scalar_stats
- 이름 리스트:
- 두 정책이 이름 리스트는 같지만 enum 값을 따로 갖는 것은, GB300과 H100에 각각 다른 튜닝(예: 저장 텐서 우선순위 프리셋)이 뒤에 붙을 가능성을 열어 둔 구조다.
_H100_OVERRIDES같은 hardware-specific config block은 아직 이 커밋에 없다.
- 새 값은
새로 알게 된 것B. FA4 attention 커널이 아이템마다 다른 유효 히스토리 길이를 받아 마스킹한다
FA4 attention 커널이 가변 길이 히스토리를 직접 지원하도록 바뀌었다 확인 —
phoenix/xrex/cutedsl/ranker_attention_fa4.py.build_dense_block_sparse_layout시그니처가 이렇게 바뀌었다.
-def build_dense_block_sparse_layout(batch_size, seq_len, hist_len, num_q_heads): +def build_dense_block_sparse_layout(seq_len, hist_len, num_q_heads, hist_valid_len):- 새 인자
hist_valid_len은 배치의 아이템마다 실제 유효 히스토리 토큰 수를 받는다. - 계산은
import numpy as np기반 호스트 사전 계산에서jnp.where·jnp.arange·jnp.broadcast_to기반의 JIT 친화적 코드로 옮겨 갔다._DENSE_BS_LAYOUT_CACHE캐시는 완전히 제거됐다. - 캐시를 없앤 이유는 명확하다.
hist_valid_len이 배치마다 달라지므로 layout이 결정 함수가 아니라 배치 데이터의 함수가 됐고, 캐싱이 무의미해진다. - 새 필드
valid_block_upper·valid_block_lower가 attention mask의 상한과 하한을 배치별로 넘긴다. 이 값으로 커널이 partial-history block(히스토리 tile 안의 실제 유효 토큰이 tile 크기 이하일 때)을 정확히 마스킹한다. is_partial = is_hist & (t == n_full) & (has_partial == 1)가 partial block의 위치를 표시한다.ranker_attention_fa4시그니처는 optional 인자valid_block_upper=None·valid_block_lower=None을 받도록 넓어졌다.custom_vjp는 새 인자들도 gradient 계산에 포함하려고bs_args로 압축·분해한다.input_output_aliases가{12: 0, 13: 1, 14: 2}에서{14: 0, 15: 1, 16: 2}로 밀렸다.
정본 반영 services/x.md의 "4세대+15 공개로 해소된 것 / 신규"에 이 두 사실을 추가했다. 4세대+6에서 "Phoenix 프로덕션 학습이 GB300에서 돈다"고 확정한 정본 내용 아래에 "H100에서도 도는 첫 신호"를 덧붙인다.
의미 X의 학습 하드웨어가 GB300만이 아니라는 사실이 이번에 코드로 확인됐다.
- GB300은 flagship 학습, H100은 secondary·backfill·research용일 가능성이 있다 추정.
_H100_OVERRIDES가 다음 커밋에서 나타나면 H100과 GB300의 배치·attention·remat 튜닝 차이가 드러난다.- FA4 attention의 가변 길이 히스토리 지원은 4세대+6의
cutedsl_ranker_varlen_attnvarlen 시맨틱을 히스토리 축까지 넓힌 것으로 이해할 수 있다. - 신규 유저와 짧은 세션에서 학습·추론 정확성이 개선된다. 이전에는 짧은 히스토리를 tile 크기까지 padding해서 attend 대상이 늘어나거나 attention 시맨틱이 오염됐을 가능성이 있었다.
- 이 관찰은
sequence-transformer-ranking정본 노트의 attn 커널 축에 붙는다.
5visibility-filtering 코드가 800줄 넘게 순감했다(개요만 확인) — X
자료 xai-org/x-algorithm@9b0dc31 — 같은 커밋(4세대+15)의 visibility-filtering/ 서브트리 여러 파일.
배경 visibility-filtering(VF)은 X의 fail-close 필터 스택이다. 4세대 동안 이 서브트리는 크게 두 방향으로 바뀌어 왔다.
- 규칙 리팩터는 정책을 데이터 지향 표현으로 옮기는 방향이다. 4세대+11의
RuleContext분리, +14의Box<dyn Rule>소거가 여기에 속한다. - 안전망 제거는 재작성 초기의 안전 코드를 실증 뒤에 걷어 내는 방향이다. 4세대+8의 VMRanker 128줄 삭제, +9의 media fallback cache 292줄 삭제가 여기에 속한다.
이번 커밋은 안전망 제거 방향을 대규모로 이어 간다.
새로 알게 된 것A. hydration과 safety-label 코드가 크게 줄었고, 회귀 방어망은 두꺼워졌다
VF 서브트리의 파일별 증감은 다음과 같다 확인 — xai-org/x-algorithm@9b0dc31의 파일 delta.
| 파일 | +/− |
|---|---|
visibility-filtering/config.rs |
+7/−118 |
visibility-filtering/rules/registry.rs |
+8/−378 |
visibility-filtering/rules/tweet_rules.rs |
+50/−129 |
visibility-filtering/hydration/mod.rs |
+11/−90 |
visibility-filtering/hydration/fallback_cache.rs |
+25/−87 |
visibility-filtering/hydration/batch.rs |
0/−19 |
visibility-filtering/hydration/gizmoduck_hydrator.rs |
+36/−42 |
visibility-filtering/hydration/tes_hydrator.rs |
+3/−33 |
visibility-filtering/get_safety_labels.rs |
0/−40 |
visibility-filtering/safety_label_source/codec.rs |
+15/−206 |
visibility-filtering/safety_label_source/manhattan.rs |
0/−10 |
visibility-filtering/models/safety_labels.rs |
+13/−19 |
visibility-filtering/filter_tweets.rs |
+17/−65 |
visibility-filtering/rules/author_rules.rs |
+3/−27 |
회귀 방어망은 더 두꺼워졌다 확인 — 같은 커밋. visibility-filtering/rules/fixtures.rs(+81)·golden_corpus.rs(+102)에서 줄이 늘었다. 단순화와 회귀 방어의 균형은 유지됐다.
정본 반영 services/x.md의 "4세대+15 공개로 해소된 것 / 신규"에 개요 수준만 추가했다. 삭제 지점마다의 시맨틱은 심층 분석 필요 항목으로 남겼다. 어떤 wire 포맷과 라벨 소스가 걷혔는지, 어떤 hydrator entry가 제거됐는지가 여기에 해당한다.
의미 재작성이 안정된 뒤 안전망을 걷어 내는 흐름이 확실한 패턴으로 자리 잡았다. 4세대+8·+9·+11·+14·+15 다섯 커밋에 걸쳐 visibility-filtering이 통제된 순감으로 얇아지고 있다.
- 정본의 이전 판은 "4세대 VF는 Rust 재작성 초기라 안전망이 많다"고 봤다. 이 관찰은 그 판단을 "재작성이 성숙해 안전망이 걷어지는 단계"로 갱신한다.
safety_label_source/codec.rs의 206줄 삭제는 wire 포맷 축의 통합을 시사하므로, 후속 심층 분석 대상이다.
6브라질 선거 필터가 정기 갱신됐고, 외부 PR #88이 머지됐다 — X
자료 A xai-org/x-algorithm@9b0dc31 — 같은 커밋(4세대+15)의 home-mixer/filters/brazil_2026_election_filter.rs.
자료 B xai-org/x-algorithm@e4dcedd — PR #88 머지, 외부 기여 (joshs1017dev), 2026-09-03 20:28 UTC.
배경 Brazil2026ElectionFilter는 브라질 선거관리위원회(TSE)에 신고된 계정을 홈 피드에서 걸러 내는 정치·규제 대응 필터다. 이 필터는 X의 정기 유지 보수가 매주 이어진다는 관찰의 근거다. 정본에는 이 필터의 이력이 이렇게 기록돼 있다.
- 4세대+1(2026-08-14): 필터 신설(665 계정)
- 4세대+8(2026-08-25): TSE dadosabertos 2026-08-25 판 반영(+3,379줄)
- 4세대+10(2026-08-28): 주석 재정리
이번 커밋은 이 필터의 계정 목록을 다시 갱신했다.
새로 알게 된 것A. 후보 계정 목록이 다시 검증됐고, 파일의 동작 방식은 그대로다
- 하드코딩 계정 목록이 2,388개에서 2,554개로 순증 166개 늘었다 확인 —
brazil_2026_election_filter.rs의 테스트 assert.- patch는
// @handle항목 167개를 더하고 1개를 뺐다. 새로 들어간 계정은 @nanacachen, @AecioNeves, @lgmbrasilia, @crismonteirosp, @rodrigolimamdh, @samiabomfim, @gabrielmagno_13 등이다. - 각 계정은 obfuscated user_id와 주석의 @username으로 들어 있다.
- patch는
- "not the candidate's actual account" 판정이 2명 새로 생겼다. @BENICIODIAS와 @CARLOSSAMPAIO다 확인 — 같은 patch.
- 계정 주인이 후보자가 아닌 것으로 판단됐다는 X 내부 검증 결과다.
- "no live account found" 목록에서 2개가 빠졌고 3개가 더해졌다 확인 — 같은 patch. 빠진 것은 계정이 살아났다는 뜻이고, 더해진 것은 계정이 사라졌다는 뜻이다.
- 파일의 동작 방식(팔로우 예외 유지, obfuscated user_id 노출, @username 주석 공개)은 그대로다.
새로 알게 된 것B. 외부 기여자의 PR #88이 실제로 머지됐다
- 외부 기여자 joshs1017dev의 PR(e4dcedd)이 VF lookup 전에 dedup하는 두 줄을 추가했다.
- 위치는
home-mixer/candidate_hydrators/vf_candidate_hydrator.rs의in_network_idsVF lookup 앞이다.
- 위치는
- 4세대의 오픈소스 태도가 실제로 외부 PR을 받는다는 증거다.
- 성능 최적화 하나짜리라 정본 시맨틱 변경은 없다.
정본 반영 services/x.md의 "4세대+15 공개로 해소된 것 / 신규"에 두 건을 모두 추가했다.
의미 정치인 X 계정 매핑을 매주 계속 검증하는 관행이 이어지고 있다. 이 정기 갱신 관행 자체가 필터가 활성 프로덕션 코드 경로임을 확인해 준다. 외부 PR 머지 관행은 4세대가 "코드 던지고 방치"가 아니라 실제 외부 기여를 받는 유지형 오픈소스임을 다시 확인해 준다.
검증에서 뒤집힌 것
없음. 오늘 커밋은 정본을 넓히는 방향이고, 기존 정본을 뒤집지 않는다. 다만 4세대+3에서 "Phoenix serving SID 사이드카는 옵션"이라고 기록한 것을 "옵션이 완전히 제거, SID는 request 계약"으로 갱신한다. 뒤집기라기보다 시점이 지나며 생긴 변화다.
레이더
없음. arXiv cs.IR 30건은 파싱에 성공했고, 산업 블로그는 토요일이라 건너뛰었다. Meta·Netflix는 신규 자료가 없었다. 30건을 triage한 결과 통과는 0건이다.
- CORE, EPIC, LLM4AIGQ, HypRQ-VAE, UniCon CTR, Slow-Fast Thinkers Rec, SelfDR, DoPR 등 8건은 추천·CTR 관련이었다. 하지만 모두 학술 institution 저자 위주였고, 프로덕션 배포나 A/B 주장이 없어 탈락했다.
- 나머지 22건은 세 부류로 탈락했다.
- benchmark·평가 방법론: SHELF, MultiGhostBench, Incremental Pooled Eval, Training seeds stability
- 도메인 제한: POI·법률·의료·과학·야구
- LLM 사용 방법론: RAG·private retrieval·essay grading
다음에 볼 것
- 새 답글 스팸 파이프라인이 언제 켜졌고 판정 metric이 어떻게 나오는지 본다 — 이번 diff에는 experiment 게이팅이 보이지 않는다.
PlanMultiStepReplySpam이 프로덕션 트래픽 100%에서 도는지 확인한다.- 카운터의 실제 배분, 즉 어느 필터가 얼마나 발동하는지가 다음 관측 포인트다. 대상 카운터는
multi_step_reply_spam.scored.count·newest_not_flagged_dropped.count·other_author_dropped.count·singleton_dropped.count·json_repaired.count·invalid.count다. - 후처리 3단이 얼마나 자주 발동하는지에 따라 LM의 원래 정확도와 후처리가 만드는 최종 정확도의 차이가 드러난다.
- home-mixer(또는 candidate hydration 층)에서 SID를 채우는 hydrator의 이름과 소스를 찾는다 —
sid_client제거 뒤 SID를 request에 담는 지점이 어디인지 본다.- SID codeword storage 백엔드가 무엇인지(Manhattan? Strato? phoenix-rankall 자체?) 본다.
- 이관에 dual-write 기간이 있었는지 이력을 확인한다.
- H100 전용 설정 묶음(
_H100_OVERRIDES)이 언제 나오는지, H100과 GB300의 튜닝이 어떻게 다른지 본다 — 이번에는 remat 정책만 등록됐다.- batch size·attention impl·checkpoint 주기·LR 등이 H100에 어떻게 다르게 배정되는지가 다음 커밋에서 나온다.
- Grok 4.1-fast 심판이 4.5 X_ALGO와 무엇이 다른지 본다 — 볼 것은 latency·비용·정확도의 trade-off다.
- 4.1-fast 심판의 모델 이름은
EAPI_GROK_4_1_FAST_X_ALGO다. - 왜 IllegalAndRegulatedBehaviors만 4.1-fast로 옮겼는지 정책 근거를 찾는다.
- ChildSafety·ViolentMedia 심판도 4.1-fast로 옮겨 가는 시점을 본다.
- 4.1-fast 심판의 모델 이름은
- safety_label_source/codec.rs에서 206줄을 지운 실체를 확인한다 — 어떤 wire 포맷과 라벨 소스를 걷어 냈는지 본다. safety_labels codec 통합 방향을 정본에 확정하려면 다음 관측이 필요하다.
- 다음에 Gemma로 옮겨 갈 카테고리를 본다 — Spam·IllegalAndRegulatedBehaviors 다음이 폭력 발화·혐오 발화·성인 콘텐츠 순서일지가 관심사다. Gemma 대체가 어디까지 넓어지는지가 grok API 예산과 판정 정확성의 trade-off를 드러낸다.