한 줄 요약X가 아이템의 semantic ID(SID)를 생성해 후보를 뽑는 retrieval 모델과, 그 서비스를 부르는 For You 후보 소스를 함께 공개했다. 소스는 기본으로 꺼져 있다.
1SID를 생성해 후보를 뽑는 retrieval이 For You 후보 소스로 연결됐다 — X
자료xai-org/x-algorithm@76843a5 — xai-org, 2026-10-02T02:06:55Z, 코드. 이 노트들은 4세대 공개 뒤의 X 커밋에 차례로 번호를 붙여 부르며, 이 커밋은 4세대+34다. 규모는 +5227/−756, 파일 105개다.
배경 X의 추천 스택은 지난 넉 달 동안 Phoenix(2026년 재구축한 GPU 랭킹 스택)를 중심으로 재편돼 왔다. 그 안에서 "retrieval"은 두 갈래로 나뉘어 있었다.
하나는 two-tower retrieval이다. 유저 타워와 아이템 타워의 임베딩 내적으로 ANN 검색을 돌리는 전통적인 OON(out-of-network, 팔로우하지 않은 계정의 포스트) 소스다. 2026-09-30 커밋에서 xrecsys_two_tower.py가 6-preset 카탈로그로 승격됐고, SID 6-level, seqpack, Home/Immersive 두 head 공유가 명시됐다.
다른 하나는 generative retrieval(SID generation)이다. RQ-VAE나 비슷한 양자화로 아이템을 다단계 토큰 시퀀스(semantic ID, 줄여서 SID)로 압축한 다음, Transformer가 유저 히스토리를 조건으로 SID 토큰을 생성하는 방식이다. Google TIGER(NeurIPS 2023, arXiv 2305.05065)·Alibaba PailitaoGR(2025)·Kuaishou OneRec(2025)·Tencent BARGE(2026)·ByteDance GEAR(arXiv 2026-09-30 v1, 2026-10-02 daily) 같은 라인이 이쪽이다.
X는 그동안 SID 토큰을 학습 config의 sid_num_levels로 노출해 왔다. two-tower 모델이 아이템 타워의 입력 피처로 쓰는 형태였다. SID를 생성해 그 자체로 후보를 뽑는 generative retrieval 경로는 공개된 적이 없었다.
2026-09-04(4세대+15)에는 "Phoenix 서빙 엔진에서 SID retrieval 사이드카가 통째 제거"됐다는 기록이 있다. 당시 사이드카는 Phoenix 요청에서 유저 히스토리의 각 포스트 ID를 SID로 변환해 주는 lookup 역할이었고, 그 역할은 요청에 SID를 미리 실어 넣는 형태로 흡수됐다. 그때까지 X의 "SID retrieval"은 아이템 ID → SID 토큰 lookup이라는 좁은 의미였다.
이번 커밋은 그 좁은 의미를 완전히 바꾼다. 신규 파일 가운데 SID 관련 코드가 다섯 묶음이고, 다음 세 가지가 함께 들어왔다.
Phoenix 쪽에 SID generative retrieval 전용 Transformer의 학습 config, 모델, 트레이너, 인퍼런스 러너가 새로 생겼다.
home-mixer(For You 피드를 조립하는 서비스)에 이 서비스를 호출하는 SidSource 후보 소스가 추가됐다.
이 소스는 응답 후보에 shared_prefix_depth를 점수로 붙이고, ForYouPhoenixRetrievalMoe로 라벨해 흘려보낸다.
새로 알게 된 것A. home-mixer가 별도 SID retrieval 서비스를 부르는 후보 소스를 갖췄다
home-mixer에 새 후보 소스가 생겼고, 이 소스는 별도 서비스로 분리된 generative SID retrieval microservice를 호출한다 확인 — home-mixer/sources/sid_source.rs(신규 +112).
min_prefix_depth=3은 공유 접두사가 6-level SID 중 3개 이상 일치하는 후보만 반환하라는 요청 쪽 필터다.
seed마다 최대 100개 포스트, 전체 상한은 800개다. 50 × 100 = 5000이 이론적 상한이지만 800으로 다시 조여진다.
gRPC 클라이언트는 두 경로를 병행한다: xDS 경로를 먼저 세우고, 안 되면 DNS 경로로 폴백한다 확인.
xDS P2C로 로드밸런서를 1차로 세운다. 이 경로는 discovery authority 기반의 xdstp://discovery-*/envoy.config.endpoint.v3.ClusterLoadAssignment/fou-xai-recsys-sid-retrieval.prod.prod:grpc-tls를 쓴다.
xDS 빌드가 10초 안에 끝나지 못하거나 bootstrap이 없으면 DNS 경로(RotatingChannel, 60초 간격 refresh)로 폴백한다.
관련 이름은 Haiku transformed 함수 _embed_sid_token(code, level), 레벨별 테이블 sid_emb_level_{level}(크기 [256, 128])이다.
레벨 뒤 프로젝션의 이름은 sid_emb_proj_{level}이다.
모델의 positive action은 SERVER_TWEET_FAV 단 하나다 확인 — line 40. fav 하나로 좁혀진 포지티브다.
hard-negative action 7개는 negative supervision에 쓰인다.
목록은 CLIENT_TWEET_REPORT, _NOT_INTERESTED_IN, _SEE_FEWER, _UNFOLLOW_AUTHOR, _BLOCK_AUTHOR, _MUTE_AUTHOR, _NOT_RELEVANT다.
서빙 쪽 beam decoder의 평가 조건은 eval_num_batches=20·eval_beam_width=128·eval_bs_per_device=8이다 확인.
인퍼런스 러너는 beam search로 SID를 생성하는 단계와, 생성된 SID 접두사를 실제 post_id 집합으로 되돌리는 단계를 나눠 담는다 확인 — sid_retrieval_runner.py:21-80.
러너 파일은 sid_retrieval_runner.py이고, 되돌리는 단계는 SidPostIndex/SidBeamResolver가 맡는다.
러너의 하이퍼파라미터 중 beam_width·decode_levels가 플래그다. decode_levels < 6이면 접두사 수준에서 생성을 멈추고 접두사 공유 집합을 반환한다(prefix retrieval).
이것이 home-mixer SidSourceMinPrefixDepth=3과 짝을 이룬다. 서버는 3~6단계까지 생성하되, 3단계에서 매칭된 모든 post는 접두사 깊이 3을 score로 받아 반환될 수 있다.
_CorpusView.sentinel_idx·post_ids_with_sentinel·author_ids_with_sentinel 같은 필드는 코퍼스가 사전 빌드된 인덱스(SID → posts) 형태로 로드돼 있다는 걸 보여 준다.
즉 서빙 시점의 코퍼스는 "SID 트라이 + leaf에 담긴 post_id 집합"이고, beam search가 트라이 위에서 top-K 경로를 뽑으면 그 경로 아래 posts가 후보가 된다 추정. 코드에서 SidBeamResolver의 세부 구조는 다른 파일(xrex/inference/sid_post_index.py)에 있고, 이번 커밋 diff에는 나오지 않았다.
이 설계는 공개된 generative retrieval 계열과 겹친다. Google TIGER(NeurIPS 2023)·Alibaba PailitaoGR(2025)·Kuaishou OneRec(2025)·Tencent BARGE(2026)·ByteDance GEAR(2026-09-30)가 그 계열이다. X판은 다음 점이 다르다.
6-level × 256-codebook이라는 비교적 좁은 공간을 쓴다(TIGER는 3-level × 256에 충돌 구분 토큰 1개, OneRec는 3-level × 1024).
1023토큰 유저 액션 히스토리를 그대로 조건으로 넣는다(TIGER는 학습 때 유저 히스토리를 최대 20개로 제한).
shared_prefix_depth를 상위 랭킹의 스코어로 넘긴다(TIGER는 생성 확률을 쓰는 쪽이다).
vocab 설계를 two-tower 모델과 공유한다. 다만 임베딩 테이블은 지금 모델마다 따로 있다(아래 의미 참조).
two-tower는 벡터 공간 ANN을, SID retrieval은 코드 공간 beam search를 돌린다. 둘은 duplication이 아니라 상보 구조로 선언된 셈이다.
새로 알게 된 것C. Phoenix에 임베딩 테이블을 행 단위로 나눠 다루는 CUDA 커널 묶음이 생겼다
Phoenix의 기존 async_emb 커널이 column 방향으로 샤드된 임베딩 테이블을 다룬다면, 새 row_emb는 row 방향으로 샤드된 테이블을 다룬다 확인.
vocab이 N개면 N / group_size개 row가 각 rank에 올라가는 구조다.
이 설계는 vocab이 수억 규모(아이템 100M · 작성자 30M)로 커져 임베딩 테이블이 GPU 수십~수백 장에 걸쳐야 할 때 중요해진다 추정. 각 토큰 lookup은 자기 소속 rank에만 질의하면 되고, all-to-all은 토큰을 lookup rank로 보낼 때 발생한다 추정.
Python 바인딩 row_emb.py는 JAX FFI target 6개를 CUDA 플랫폼에 등록하고, shard spec을 NamedTuple 하나에 묶어 넘긴다 확인 — row_emb.py:30-80, 100-290.
record() 함수가 이제 CallerRequest 가드 구조체를 리턴한다(must_use로 annotation).
핸들러가 요청 처리 중 request.mark_success() 또는 .mark_failure()를 호출하지 않고 그대로 drop되면 Cancelled로 집계된다.
Drop impl이 메트릭을 발행한다. RAII 패턴이다.
새 테스트가 대시보드 생성 Python 스크립트와 Rust 쪽 상수를 함께 고정한다 확인.
테스트 이름은 dashboard_generator_pins_the_requests_by_caller_metric다.
스크립트가 REQUESTS_BY_CALLER_METRIC = "vf_requests_by_caller"·REQUESTS_BY_CALLER_NO_DEADLINE_FILTER = 'deadline="absent"'·success/non-success filter 네 개 상수를 코드에 담고 있어야 Rust 테스트가 통과한다.
메트릭 정의가 Rust 코드와 대시보드 코드 양쪽에서 동시에 drift 체크되는 설계다. 어제의 _drift_checks 메트릭이 UI 번들 drift를 보던 것과 비슷한 패턴이다.
새로 알게 된 것F. home-mixer가 cold-start 관련 분기 두 개를 걷어 내고, 작성자 룰 평가를 공용 crate로 옮겼다
Control arm의 cold-start 부스트가 이제 retrieval 소스와 관계없이 Control corpus의 모든 포스트를 대상으로 삼는다 확인(author_cold_start.rs).
is_arm_gated_retrieval(Phoenix MoE/Cold retrieval 소스 판정) 함수가 삭제됐다.
과거 Control arm은 Control corpus인 포스트 중에서도 Phoenix MoE/Cold retrieval 출신을 cold-start 부스트 대상에서 제외했다.
테스트 cold_retrieval_keeps_its_score_in_every_arm가 삭제되고, 새 테스트 control_viewer_lifts_cold_retrieval_from_control_corpus가 그 자리를 대체한다. 새 테스트는 "Control 뷰어가 cold retrieval 후보도 Control corpus 소속이면 들어 올린다"를 명시한다.
메트릭 home_mixer.cold_started_posts_total의 is_moe 라벨도 제거됐다. Phoenix MoE/Cold retrieval인지 여부는 더 이상 추적되지 않는다.
어제까지 cold-start 부스트는 Phoenix retrieval을 arm × corpus × 소스의 3축으로 보고 소스 축에서 걸러냈는데, 이제 arm × corpus 2축으로 좁혀진다.
cached posts가 있을 때 engagement count를 이제 모든 후보에 대해 조건 없이 다시 가져온다 확인(engagement_counts_hydrator.rs).
과거 has_cached_posts일 때는 cold_start_base_eligible을 통과한 후보만 engagement count를 다시 fetch하고, 나머지는 preserve_counts로 cached 값을 유지했다. 이제 모든 후보를 unconditional로 재fetch한다.
preserve_counts helper가 삭제됐다.
새 테스트 cached_posts_refetch_all_candidates는 cold-start eligibility와 무관하게 세 후보 전부가 새 counts로 교체됨을 고정한다.
호출 경로에서 ColdStartFollowerCap·cold_start_base_eligible 의존성이 둘 다 걷혔다.
engagement counts와 cold-start 사이의 상호의존이 끊어졌다. 뒤에 오는 cold-start 로직은 engagement count가 "최신"임을 전제할 수 있고, 하이드레이터 쪽은 더 단순해졌다.
home-mixer 안에 있던 작성자 룰 평가 유틸이 공용 crate로 올라갔다 확인(author_rules.rs).
home-mixer/util/author_rules.rs(156줄)가 삭제되고, 심볼은 xai_feature_switches::AuthorRulesEvaluator로 이동했다. 리팩터다.
moka cache 사이즈 500k · TTL 10분 · 64 shard 구성이 공용 crate 쪽에 복제됐을 것으로 보인다 추정. 그 구현은 이번 diff에 노출되지 않았다.
새로 알게 된 것그 밖의 변경
VF의 필터 코드에는 테스트 두 개만 추가됐고, 정책 변화는 없다.
파일은 filter.rs(+94/−2)다.
추가된 테스트는 pure_core_alone_says_a_post_is_a_retweet, without_a_pure_core_answer_a_post_reads_as_an_original이다.
둘 다 PureCore(트윗 코어 메타) hydration이 실패하거나 성공할 때 viewer_mutes_retweets 규칙과 nullcasted_tweet 규칙이 각각 어떤 fallback으로 결론을 내는지를 고정한다. 기존 fault 처리 behavior에 lock을 추가한 테스트다.
VF 파일 세 개의 변경은 어제의 Fetcher<V> 재작성 후속으로 보이는 소폭 리팩터다 추정. 테스트 추가 위주다.
visibility-filtering-client/tweet_safety_label.rs(+39/−12)는 thrift decoder 테스트 확장이다 추정.
home-mixer/params/param.rs(+31/−1)의 증분은 위 5개 SID source param 추가로 인한 것이다. cron sync 시각이 바뀌지 않았다면 유의미한 다른 변화는 없다.
정본 반영services/x.md의 공개 소스 4세대 섹션에 "### 4세대+34 (2026-10-02, 76843a5)" 소절을 새로 만들었다. ## 후보 생성 (retrieval) 절의 retrieval 소스 표에는 SidSource 행을 추가하고, "SID generative retrieval microservice. seed post ID → 6-level × 256-codebook SID beam search → shared_prefix_depth score, served_type=ForYouPhoenixRetrievalMoe로 라벨, EnableSidSource=false 기본 off"로 적었다.
의미 SID를 직접 생성해 후보로 돌려주는 경로가 X 코드에서 처음 공개됐다. 다만 기본값이 off이므로, 실 트래픽 적용의 관측 지점은 FeatureSwitches cron에서 켜지는 날이다. 이번 커밋에서는 세 겹의 사실이 겹친다.
X가 generative SID retrieval을 "별도 microservice + 전용 Transformer 학습·서빙 스택 + home-mixer 후보 소스 배선"의 완결된 세 축으로 공개한 첫 커밋이다.
지금까지 X의 SID는 두 곳에서 간접적으로 모습을 비쳤다. 하나는 two-tower 아이템 타워의 입력 피처(2026-09-30 공개된 xrecsys_two_tower.py의 use_post_sid 플래그)다. 다른 하나는 phoenix-rankall의 백필 QPS 800k 상한(어제 공개된 SID_BACKFILL_QPS_CAP = 800_000)이다.
SID를 직접 생성해서 그 결과를 candidate로 리턴하는 라인은 공개되지 않았다.
오늘 커밋은 아래 체인 전체를 한 번에 공개한다.
Phoenix 쪽에 전용 Transformer 모델(8-layer, GQA 20:4, 1030-length context, 6-level × 256-codebook)의 config/모델/트레이너/러너 네 파일을 신설했다.
별도 microservice xai-recsys-sid-retrieval.prod.fou.twitter.biz의 gRPC client와 request shape(seed_post_ids, shared_prefix_depth scoring, prefix retrieval 조건 min_prefix_depth=3)을 공개했다.
home-mixer SidSource로 PhoenixMOESource와 같은 ServedType으로 라벨된 후보를 흘려보낸다.
이 세 부분이 각기 별개 커밋으로 흩어졌다면 "준비 중" 신호였겠지만, 한 커밋에 묶여 공개된 사실은 연결이 완성에 가깝다는 뜻이다.
다만 EnableSidSource=false가 기본(off)이므로, 실 트래픽 적용은 FeatureSwitches cron에서 ON 되는 날이 관측 지점이다.
two-tower retrieval과 SID generative retrieval은 "서로 대체"가 아니라 "동일 vocab 공간 위의 두 레이어"로 설계됐다.
두 모델은 같은 유저·아이템·작성자 hash vocab(100M · 30M · double hashing scales/biases 똑같음)을 공유한다. xrecsys_sid_retrieval.py:30-45와 2026-09-30 공개된 xrecsys_two_tower.py의 수치를 대조하면 드러난다.
generative retrieval은 아래 두 가지 비용이 큰데, two-tower가 ANN으로 돌릴 수 있는 broad recall을 먼저 맡고 SID retrieval이 접두사 공유도로 보강 recall을 담당하는 상보 구조를 고른 것으로 보인다 추정.
하나는 SID 생성 시간 복잡도다(beam_width × decode_levels × 코드북 크기).
다른 하나는 코퍼스 쪽 SID 인덱스를 유지하는 비용이다.
같은 vocab이면 양쪽 모델이 공유 임베딩 테이블로 돌 수도 있다. 그러나 지금 공개된 config에서는 양쪽 모델이 각자 임베딩 테이블을 가진다. 공유 임베딩으로 통합되는지는 다음 커밋에서 관측해야 한다.
row-sharded 임베딩 CUDA 커널 묶음은 Phoenix 임베딩 설계 자체의 2축 확장이다.
3,180줄짜리 row_emb가 generative retrieval 모델만을 위해 추가된 것은 아니다. 하지만 이번 커밋 묶음의 맥락에서 보면, SID generative retrieval이 요구하는 거대 vocab 임베딩(100M · 30M · 6-level × 256 codebook)이 트리거라는 해석이 자연스럽다.
공유 adapter recsys_async_emb.py가 기존 column-sharded 커널 async_emb와 신규 row-sharded 커널 row_emb 가운데 하나를 고르게 해, 모델별로 최적 샤딩을 선택할 여지를 마련한 셈이다.
two-tower는 유저 타워와 아이템 타워를 각각 column-shard로 유지해도 되지만, SID generative 모델은 generation 단계에서 각 토큰이 자기 vocab 범위만 참조하므로 row-shard가 더 compact한 통신 패턴을 낳는다 추정.
LLM judge action이 돌아온 것은 이와 별개의 흐름이다. 15일 전 vacate됐던 두 action이 번호를 바꿔(107/108 → 108/109) 재등장한 사실은 "LLM judge를 action 축에 담아 학습 target 중 하나로 소비"하려는 방향이 재조정 후 다시 올라왔음을 뜻한다. 번호가 바뀐 이유는 그 사이 107 자리가 CLIENT_TWEET_LIVE_STREAM_REPLY로 영구 점유됐기 때문이고, 108도 함께 비워 재사용하는 쪽을 택했다. 이번 공개는 모델 학습 config나 wire에서의 소비처를 보여 주지 않고, proto만 자리를 먼저 잡았다. 어느 head 가중치에 들어가는지, 어느 config의 target action list에 포함되는지는 다음 창에서 봐야 한다.
2추천과 관련된 새 글은 없었다 — Netflix·Meta 블로그
Netflix와 Meta의 엔지니어링 블로그에는 오늘 반영할 추천 관련 새 글이 없었다.
Netflix tech blog 피드 10건 중 최신 2건은 추천과 무관하다. "Trading a Cloud Identity for Your Own: Workload Attestation on Managed Compute"(2026-09-25, 인프라)와 "Leave the Class Path in the Rearview Mirror"(2026-09-18, Java packaging)다.
그 전의 "MAPS"·"GenRec"·"GenPage"는 이미 반영됐다.
Meta engineering.fb.com의 최신 글 "From User Sequences to Scaling Laws"(2026-09-02)는 이미 반영됐다. 그 뒤에 쌓인 네 건(Private Processing / Rebalancer / Petal / ZGateway)은 모두 인프라·네트워킹·개인정보 주제로, 추천과 무관하다.
레이더
레이더 수용 기준을 모두 넘은 논문은 0건이었다. 수용 기준은 다음 세 가지다.
① 플랫폼 저자 또는 프로덕션 A/B
② 메커니즘 신규성
③ 대규모 결과
대상은 arXiv cs.IR 2026-09-30 ~ 2026-10-01 창이고, 요청 30건 중 <entry> 30건을 전부 파싱했다. 아래 세 항목이 경계였지만 모두 탈락했다.
arXiv 2610.02057Optimizing Effective Training Time for Large-Scale Recommendation Systems. Meta의 추천 학습 인프라 운영 사례라 탈락했다.
Meta 소속으로 확인했다. PDF strings에 "Meta Platforms, Inc.", "Meta Engineering Blog", "Meta internal"이 다수 등장한다.
추천 학습 플릿의 ETT%(Effective Training Time)를 80% → 90%+로 올린 fleet-scale 운영 사례다. 비동기 체크포인팅 · 통신 제거 · 파이프라인 overlap · 동적 shape · PyTorch 2 compile cache 재사용 등을 다룬다.
탈락 사유는 추천 학습 인프라의 운영적 승리라는 점이다. 레이더 기준 ②의 retrieval·랭킹 메커니즘 신규성과는 성격이 다르다. 서비스 정본에도 "학습 처리량"은 들어가지 않는 항목이므로 반영하지 않았다.
arXiv 2610.01533Neither Black nor White: Balancing Semantic and Collaborative Signals with Graph-Informed Semantic IDs (GrIS). 프로덕션 배포 주장이 없어 탈락했다.
저자 전원이 Huawei Ireland Research Centre 소속임을 확인했다.
SID와 collaborative 신호를 그래프로 섞는 메커니즘을 다루지만, 프로덕션 배포 주장은 없다.
탈락 사유는 ①의 플랫폼·배포 조건이 비어 있다는 점이다.
arXiv 2609.39043Routing Between Generative and Collaborative User Profiles: A Serving-Time Gate for Controllable Novelty. 추적 대상 서비스가 아니어서 탈락했다.
저자는 Comcast Technology AI와 DePaul University 소속이다.
논문은 "serving-time routing gate가 LLM-profile 모델과 collaborative sequential 모델 사이에서 유저를 선택적으로 라우팅"하는 구조를 제안한다. NDCG 5% 손실 예산 안에서 Novelty@10 +6.5%를 얻고, 유저의 12.5%만 라우팅한다.
메커니즘은 흥미롭다(cost-aware 선택적 LLM 사용).
탈락 사유는 Comcast의 Xfinity Stream이 추적 대상 서비스가 아니며, 피드/랭킹 플랫폼으로서의 추적 가치가 낮다는 점이다(스트리밍 TV의 영상 추천).
다음에 볼 것
SID 소스 스위치가 cron에서 언제 켜지는지 본다 — FeatureSwitches cron이 이 스위치를 true로 돌리는 때가 실 트래픽 적용 시점이다. 스위치는 EnableSidSource param이다. 그때 SidSourceMaxSeeds·SidSourceMaxResults·SidSourceMinPrefixDepth 값이 config 기본값에서 바뀌는지도 함께 본다. home-mixer 쪽 관측 지점은 SidSource.retrieved{type=seeds|retrieved|hydrated} 메트릭이다.
LLM judge 액션(108/109)을 학습에서 어디에 쓰는지 본다 — proto만 선언됐고, 학습 config나 target head 쪽에서 소비하는 코드는 아직 보이지 않는다. 다음 커밋에서 xrecsys_search.py 또는 xrecsys_two_tower.py의 target action list에 108/109가 추가되면, 그때가 wire 선언에서 학습 소비로 승격되는 시점이다.
two-tower와 SID retrieval이 임베딩 테이블을 공유하게 되는지 본다 — 두 모델은 같은 vocab 설계를 쓰지만, 지금은 임베딩 테이블을 각자 가진다. 다음 커밋에서 체크포인트 공유나 임베딩 테이블 재사용이 생기면, 두 retrieval이 공유 임베딩 위의 두 head로 통합되는 신호다.
generative retrieval을 깊이 분석한다 — 아래 세 가지가 모두 전면 분석 대상이고, 심층 분석이 필요하다.
모델의 beam decoder 수학: prefix retrieval 멈춤 조건, score aggregation, SID → post 역해상의 corpus view 구조
학습 loss: positive=FAV 하나로 좁힌 상태의 negative sampling, hard negative action 7개의 가중치
모델마다 row_emb와 async_emb 가운데 어느 쪽을 쓰는지 본다 — 어느 모델이 row-sharded를, 어느 모델이 column-sharded를 쓰는지, is_row_sharded(context_handle) 분기의 실제 호출처가 어디인지가 다음 창의 관측 지점이다. 지금은 둘 다 adapter 레이어에서 지원되지만, 어느 모델이 "실제로" row-sharded를 쓰는지는 model config의 make_context_handle 호출을 따라가야 보인다.