모델 구조 규칙

모델 구조 규칙

Transformers는 모든 modeling_*.py, modular_*.py, configuration_*.py 파일에 일련의 정적 규칙을 적용합니다. mlinter 패키지가 검사 엔진을 제공하고, 레포지토리는 활성 규칙 집합을 utils/rules.toml에 유지합니다. 이 로컬 TOML을 통해 새 transformers-mlinter 릴리스를 기다리지 않고 규칙을 빠르게 활성화, 비활성화 또는 조정할 수 있습니다.

출처: 문서

본문

Transformers는 모든 modeling_*.py, modular_*.py, configuration_*.py 파일에 일련의 정적 규칙을 적용합니다. mlinter 패키지가 검사 엔진을 제공하고, 레포지토리는 활성 규칙 집합을 utils/rules.toml에 유지합니다. 이 로컬 TOML을 통해 새 transformers-mlinter 릴리스를 기다리지 않고 규칙을 빠르게 활성화, 비활성화 또는 조정할 수 있습니다.

[!TIP] 전체 검사기 참조와 사용법은 mlinter 문서를 참조하세요.

이것들은 모델링 코드를 추가하거나 변경할 때 기대되는 모델 규칙입니다. 이들은 코드베이스를 일관되게 유지하고 pipeline parallelism, device map, weight tying 같은 기능과의 호환성을 보장합니다.

검사기 실행

make typing은 레포 래퍼를 통해 ty 타입 검사기와 함께 mlinter를 실행하므로 utils/rules.toml을 인식합니다. 다음 명령으로 같은 래퍼를 직접 실행하세요.

python utils/check_modeling_structure.py                 # check all modeling files
python utils/check_modeling_structure.py --changed-only  # check only files changed vs origin/main
python utils/check_modeling_structure.py --list-rules    # list all rules and their enabled status
python utils/check_modeling_structure.py --rule TRF001   # show built-in docs for a specific rule

--changed-only 플래그는 개발 중 가장 빠른 옵션입니다. main 브랜치와 비교해 수정한 파일만 검사합니다. 래퍼 대신 mlinter를 직접 호출한다면 로컬 오버라이드가 적용되도록 --rules-toml utils/rules.toml을 전달하세요.

위반 수정

규칙 위반이 감지되면 오류는 다음과 같이 보입니다.

src/transformers/models/acme/modeling_acme.py:18: TRF013: AcmeModel.__init__ does not call self.post_init().

규칙 ID를 사용해 규칙 참조에서 수정 방법을 찾으세요. TRF013은 PreTrainedModel 서브클래스가 self.post_init()을 호출하지 않을 때 트리거됩니다. 이 메서드는 필수적인 마무리 단계를 수행하며, 생략하면 런타임 버그가 발생합니다.

 class AcmeModel(AcmePreTrainedModel):
     def __init__(self, config):
         super().__init__(config)
         self.layers = nn.ModuleList(
             [AcmeDecoderLayer(config) for _ in range(config.num_hidden_layers)]
         )
+        self.post_init()

규칙 참조

각 규칙 아래에는 그것이 시행하는 내용과 수정을 보여주는 diff가 있습니다. python utils/check_modeling_structure.py --rule TRF001을 실행하면 레포의 현재 규칙 집합으로 모든 규칙의 내장 문서를 볼 수 있습니다.

TRF001

PreTrainedModel의 config_class가 Config라는 이름인지 확인합니다. 불일치는 로딩, auto 클래스, 개발자 기대를 깨뜨릴 수 있습니다.

class AcmePreTrainedModel(PreTrainedModel):
-    config_class = WileConfig
+    config_class = AcmeConfig

TRF002

base_model_prefix가 비어 있지 않고 공백 없는 문자열 리터럴인지 확인합니다. 잘못된 접두사는 가중치 로드 키 매핑과 base-model 접근을 깨뜨릴 수 있습니다.

class AcmePreTrainedModel(PreTrainedModel):
-    base_model_prefix = ""
+    base_model_prefix = "model"

TRF003

forward에서 오래된 if not return_dict: return (x,) 패턴을 표시합니다. 수동 return_dict 분기는 장황하고 실수하기 쉽습니다. @capture_output 또는 @can_return_tuple이 처리하게 하세요.

-def forward(self, x, return_dict=None):
-    if not return_dict:
-        return (x,)
-    return AcmeModelOutput(last_hidden_state=x)
+@can_return_tuple
+def forward(self, x):
+    return AcmeModelOutput(last_hidden_state=x)

TRF004

어떤 모델 클래스도 tie_weights 메서드를 정의하지 않았는지 확인합니다. tie_weights를 재정의하면 로딩, device_map 계산, 저장이 깨집니다. tied 가중치는 _tied_weights_keys 클래스 속성으로 선언하세요.

-def tie_weights(self):
-    self.lm_head.weight = self.emb.weight
+class AcmeForCausalLM(AcmePreTrainedModel):
+    _tied_weights_keys = ["lm_head.weight"]

TRF005

제공될 때 _no_split_modules의 형태를 확인합니다. 잘못된 값은 device-map 분할과 샤딩을 깨뜨릴 수 있습니다.

-_no_split_modules = [SomeLayerClass, ""]
+_no_split_modules = ["AcmeDecoderLayer", "AcmeAttention"]

TRF006

forward 시그니처의 cache 인자가 본문에서 사용되는지 확인합니다. 사용되지 않는 cache 인자는 불완전한 캐싱 지원과 일관되지 않은 API 동작을 암시합니다.

def forward(self, x, past_key_values=None, use_cache=False):
+    if use_cache:
+        ...
     return x

TRF007

__init__에서 self.post_init() 이후의 self 속성 할당을 확인합니다. post_init 이후에 모델 구조를 변경하면 그 초기화와 마무리 작업을 우회합니다.

def __init__(self, config):
     ...
-    self.post_init()
-    self.proj = nn.Linear(...)
+    self.proj = nn.Linear(...)
+    self.post_init()

TRF008

모델 클래스의 add_start_docstrings가 비어 있지 않은 인자를 받는지 확인합니다. 빈 인자는 불분명하고 품질이 낮은 생성 API 문서를 만듭니다.

-@add_start_docstrings("")
+@add_start_docstrings("The Acme model.")
 class AcmeModel(AcmePreTrainedModel):
     ...

TRF009

모델의 배포 구현 파일(modeling_, configuration_, processing_, image_processing_, video_processing_, feature_extraction_, tokenization_* 및 generation_*.py)에서 다른 모델의 패키지로 import하는 것을 표시합니다. 세 가지 형태가 해당됩니다: 패키지 경로(from transformers.models.other.modeling_other import X), 상대 경로(from ..other.configuration_other import X), 공개 API(from transformers import OtherModel) — 마지막의 경우 소유 디렉터리를 클래스 이름에서 추론하고 정의한 클래스와 대조해 확인하므로 해결할 수 없는 이름은 그대로 둡니다. 범위 밖: modular_*.py(다른 모델 위에 구축하는 것이 목적이고, 컨버터가 그 import를 평탄화함), convert_*.py, __init__.py, auto 아래의 파일. 허용된 대상: 모델 자신의 디렉터리, auto, timm_wrapper. 한 모델, 하나의 정의: 모델 A의 동작은 자체 파일에 있으며, 모델 B에 대한 변경이 조용히 모델 A를 바꾸면 안 됩니다 — modeling뿐만 아니라 모든 파일 종류에 대해 그렇습니다. 하위 config에는 AutoConfig와 CONFIG_MAPPING을 통해 접근하고, 다른 모델의 코드 위에 구축하려면 modular 파일을 사용하세요.

-from transformers.models.llama.modeling_llama import LlamaAttention
-from transformers import CLIPTextModelWithProjection
+# Keep implementation local to this model's own files.
+# To build on another model, write modular_acme.py; to reuse a snippet,
+# copy it with a # Copied from comment.

TRF010

configuration_.py와 modular_.py의 직접 PreTrainedConfig/PretrainedConfig 서브클래스가 @strict(accept_kwargs=True)를 갖는지 확인합니다. 그것이 없으면 새 config가 레포의 런타임 타입 검증 계약을 놓치고 dataclass 기반 config 표준에서 벗어납니다.

+@strict(accept_kwargs=True)
 class AcmeConfig(PreTrainedConfig):
     ...

TRF011

PreTrainedModel 서브클래스의 forward()에서 torch.nn.Identity가 갖지 않을 서브모듈 속성 접근을 표시합니다: self.layers에 대한 루프 변수, 그리고 표준 nn.Module 속성이 아닌 self.. . pipeline parallelism이 어떤 서브모듈이든 torch.nn.Identity로 교체할 수 있으므로, 거기서 커스텀 속성(예: decoder_layer.attention_type)을 읽으면 런타임에 AttributeError가 발생합니다. 레이어별 메타데이터는 self.config에서 읽으세요.

def forward(self, ...):
-    for decoder_layer in self.layers:
+    for i, decoder_layer in enumerate(self.layers):
         hidden_states = decoder_layer(
             hidden_states,
-            attention_mask=causal_mask_mapping[decoder_layer.attention_type],
+            attention_mask=causal_mask_mapping[self.config.layer_types[i]],
         )

TRF012

init_weights 안에서 모듈 가중치에 대한 제자리(in-place) 연산(.normal(), .zero_(), ...)을 표시합니다. 파라미터는 재초기화가 여전히 필요한지 추적하는 내부 플래그를 가지고 있으며, 제자리 연산은 그것을 우회합니다. init 프리미티브를 사용하세요.

+from transformers import initialization as init
+
 def _init_weights(self, module):
-    module.weight.normal_(mean=0.0, std=0.02)
+    init.normal_(module.weight, mean=0.0, std=0.02)

TRF013

__init__을 정의하는 모든 PreTrainedModel 서브클래스가 self.post_init()을 호출하는지 확인합니다. modular 파일에서는 super().init()이 부모의 post_init을 전파하므로 해당됩니다. post_init은 필수 마무리(가중치 초기화, 그래디언트 체크포인팅 설정 등)를 수행하며, 건너뛰면 미묘한 런타임 버그가 발생합니다.

class AcmeModel(AcmePreTrainedModel):
     def __init__(self, config):
         super().__init__(config)
         self.layers = nn.ModuleList(...)
+        self.post_init()

TRF014

네이티브 모델 통합 파일에서 trust_remote_code가 사용되거나 (kwargs로) 전달되는 것을 표시합니다. trust_remote_code는 바이너리를 포함한 임의 코드를 로드합니다 — 사용자를 위한 고급 기능이지, 네이티브 통합이 의존할 수 있는 것이 아닙니다. 원격 코드는 transformers에서 검토되거나 유지될 수 없기 때문입니다.

class AcmeModel(AcmePreTrainedModel):
     def __init__(self, config):
         super().__init__(config)
-        self.model = AutoModel.from_pretrained(..., trust_remote_code=True)
+        self.model = AutoModel.from_pretrained(...)

TRF015

PreTrainedModel 서브클래스가 비어 있지 않은 _tied_weights_keys를 설정할 때, 동반 configuration 파일에 tie_word_embeddings 필드가 있는지 확인합니다. 그것이 없으면 사용자가 weight tying을 제어할 수 없습니다: 모델은 무조건 tying하므로 직렬화 왕복과 헤드가 untied된 파인튜닝이 깨집니다.

# configuration_foo.py
 @strict(accept_kwargs=True)
 class FooConfig(PreTrainedConfig):
     hidden_size: int = 768
+    tie_word_embeddings: bool = True

TRF016

image_processing_.py 또는 video_processing_.py 클래스가 boolean do_* 속성(do_resize, do_rescale, do_normalize, do_convert_rgb 등)을 선언하고 preprocess() 또는 _preprocess()를 재정의할 때, 각 플래그가 여전히 거기서 소비되는지 확인합니다: 직접 참조, super().preprocess/_preprocess(..., **kwargs)로 위임, 또는 — 이미지 프로세서만 — _preprocess_image_like_inputs/_prepare_image_like_inputs를 통해 전달. do_sample_frames는 예외입니다: 기본 preprocess()가 _preprocess() 실행 전에 그것을 소비합니다. 재정의가 전혀 참조하지 않는 do_X는 죽은 것입니다: do_X=False를 설정해도 효과가 없고 연산은 어쨌든 실행되며, 호출별 오버라이드가 조용히 깨집니다.

class AcmeVideoProcessor(BaseVideoProcessor):
     do_resize = True
     do_normalize = True

     def _preprocess(
         self,
         videos,
+        do_resize: bool,
+        do_normalize: bool,
         size,
         image_mean,
         image_std,
         **kwargs,
     ):
         for video in videos:
-            video = self.resize(video, size=size)
-            video = self.normalize(video, image_mean, image_std)
+            if do_resize:
+                video = self.resize(video, size=size)
+            if do_normalize:
+                video = self.normalize(video, image_mean, image_std)

TRF017

@auto_docstring과 @dataclass를 모두 가진 클래스에서 @auto_docstring이 먼저 오는지 확인합니다. 데코레이터는 아래에서 위로 적용되므로, 맨 위의 @dataclass는 아직 합성된 __init__이 없는 클래스에서 @auto_docstring을 먼저 실행합니다: 그러면 서브클래스가 아닌 부모의 init.__doc__을 수정합니다.

-@dataclass
 @auto_docstring(
     custom_intro="""
     Output type of `AcmeForPreTraining`.
     """
 )
+@dataclass
 class AcmeForPreTrainingOutput(ModelOutput):
     ...

TRF018

_init_weights(self, module, ...)를 재정의하는 모든 PreTrainedModel 서브클래스가 super()._init_weights(...)를 통해 올라가는지 확인합니다. modular 파일도 센티널 PreTrainedModel._init_weights(self, module), PreTrainedModel._init_weights(module), raise AttributeError(...)를 허용합니다. 의도적인 전체 오버라이드의 경우 메서드 위에 # trf-ignore: TRF018로 억제하세요. 기본 _init_weights는 표준 모듈 유형(Linear, Embedding, LayerNorm, RotaryEmbedding 등)을 다룹니다. 그것을 건너뛰면 오버라이드가 놓친 모든 서브모듈이 초기화되지 않은 채로 남습니다 — 이는 테스트를 통과하고 훨씬 나중에 미묘한 가중치 초기화 버그로 드러납니다 (cf. https://github.com/huggingface/transformers/pull/45597).

from ... import initialization as init

 def _init_weights(self, module):
+    super()._init_weights(module)
     if isinstance(module, AcmeCustomLayer):
-        module.gate.data.zero_()
+        init.zeros_(module.gate)

TRF019

processing_*.py의 *ProcessorKwargs TypedDict에 비어 있지 않은 _defaults가 있는 것을 표시합니다. 컷오프 날짜 이전에 릴리스된 모델은 예외입니다. 하드코딩된 _defaults는 프로세서 설정을 Python 소스 전체에 흩뿌리고, config에서 재정의하기 어렵고, 코드를 부풀립니다. Hub의 processor_config.json에서는 체크포인트와 함께 이동하며 코드 변경 없이 업데이트할 수 있습니다.

class Gemma4ProcessorKwargs(ProcessingKwargs, total=False):
-    _defaults = {
-        "text_kwargs": {"padding": False},
-        "images_kwargs": {"return_tensors": "pt"},
-    }
     images_kwargs: Gemma4ImageProcessorKwargs

TRF020

configuration이 kv_lora_rank(Multi-head Latent Attention)을 선언하는 모델 디렉터리에서, KV LoRA 확장 프로젝션(kv_b_proj 또는 nn.Linear(config.kv_lora_rank, ...))을 소유하는 attention 클래스를 확인합니다: 확장은 forward()가 호출하는 전용 메서드(예: expand_kv)에 있어야 하며 인라인이면 안 됩니다. modular 파일에서는 가져온 기본 클래스에서 상속된 메서드가 해당됩니다. 외부 백엔드(vLLM/SGLang)는 확장을 재정의하여 압축된 KV 캐시를 직접 소비합니다. forward()에 인라인되면 재정의할 것이 없으므로, 백엔드는 전체 캐시를 구체화해야 합니다 — MLA가 존재하는 이유인 메모리 절약을 잃게 됩니다.

+    def expand_kv(self, k_nope, k_pe):
+        key_shape = (*k_nope.shape[:-1], -1, self.qk_nope_head_dim + self.v_head_dim)
+        k_nope = self.kv_b_proj(k_nope).view(key_shape).transpose(1, 2)
+        k_nope, value_states = torch.split(k_nope, [self.qk_nope_head_dim, self.v_head_dim], dim=-1)
+        k_pe = k_pe.expand(*k_nope.shape[:-1], -1)
+        key_states = torch.cat((k_nope, k_pe), dim=-1)
+        return key_states, value_states
+
     def forward(self, hidden_states, ...):
         ...
-        k_nope = self.kv_b_proj(k_pass).view(key_shape).transpose(1, 2)
-        k_nope, value_states = torch.split(k_nope, [self.qk_nope_head_dim, self.v_head_dim], dim=-1)
-        k_pe = k_rot.expand(*k_nope.shape[:-1], -1)
-        key_states = torch.cat((k_nope, k_pe), dim=-1)
+        key_states, value_states = self.expand_kv(k_pass, k_rot)

TRF021

modeling_.py와 modular_.py에서 <value>가 Python 스칼라로 확실히 해석되는 torch.tensor(<value>, ..., device=<non-cpu>)를 표시합니다 — 숫자 리터럴과 그에 대한 산술, torch.finfo/iinfo 필드, 스칼라를 반환하는 내장 함수와 math.* 호출, 정확히 한 번 바인딩된 로컬, 클래스 본문에서 할당된 self., 동반 configuration 파일에서 int/float/bool로 주석이 달린 config 필드(attribute_map을 따름)에서 비롯된 것. 시퀀스일 수도 있는 것(eos_token_id: int | list[int] | None)이나 해결할 수 없는 것은 그대로 둡니다. init, _init_weights, post_init, post_init은 예외입니다: 그들은 캡처 영역 내에서 실행되지 않습니다. torch.tensor(, device=)는 값을 호스트에서 구체화한 다음 장치로 복사합니다; CUDA graph 캡처는 그 복사를 금지하므로 모델을 캡처할 수 없습니다. torch.full((), , dtype=..., device=...)는 캡처 가능한 커널과 동기화 없이 동일한 0-d 텐서를 장치에서 채웁니다.

def get_placeholder_mask(self, input_ids, inputs_embeds):
     special_image_mask = (
         inputs_embeds
         == self.get_input_embeddings()(
-            torch.tensor(self.config.image_token_id, dtype=torch.long, device=inputs_embeds.device)
+            torch.full((), self.config.image_token_id, dtype=torch.long, device=inputs_embeds.device)
         )
     ).all(-1)

TRF022

modeling_.py 또는 modular_.py의 _no_split_modules 목록에 있는 모든 문자열이 그 파일에서 정의된, 그 파일로 가져온, 또는 같은 모델 디렉터리의 형제 모듈에 의해 정의된 클래스를 지명하는지 확인합니다. TRF005를 보완하며, TRF005는 값의 형태만 검사합니다. device_map은 런타임에 이러한 문자열을 module.__class__.__name__과 대조하므로, 낡았거나 철자가 틀린 이름은 아무것도 일치하지 않고 조용히 무시됩니다 — 함께 유지해야 할 모듈이 여전히 장치 간에 분할될 수 있습니다. 다른 모델의 클래스를 지명하는 항목은 수정하지 말고 삭제하세요: post_init은 이미 하위 서브모델에서 _no_split_modules를 수집합니다.

class VideoLlavaPreTrainedModel(PreTrainedModel):
-    _no_split_modules = ["VideoLlavaVisionAttention"]

TRF023

configuration_.py와 modular_.py에서 상위 논문의 약어 대신 표준 이름으로 명명된 *Config 필드를 표시합니다: d_model/n_embd -> hidden_size, d_ff/d_inner/ffn_dim/ffn_hidden_size/expansion_ratio -> intermediate_size, d_head -> head_dim, n_head/n_heads -> num_attention_heads, n_layer/n_layers/num_blocks -> num_hidden_layers. 필드는 클래스 본문과 init/post_init 할당 및 기본값에서 읽습니다. 모호하지만 관용적인 이름(num_heads, num_layers, embed_dim, mlp_ratio)은 표시되지 않으며, 컷오프 날짜 이전에 추가된 모델은 그대로 유지합니다. 모델의 형태를 읽는 모든 것 — device_map 계획, tensor/pipeline 병렬 계획, 양자화, PEFT, attention-backend 선택, attribute_map 소비자 — 은 표준 이름을 조회하므로, 같은 양을 d_model로 철자한 config는 조용히 이 모두에서 빠집니다. 체크포인트 자신의 철자는 conversion 스크립트에서 매핑하세요.

@strict(accept_kwargs=True)
 class AcmeConfig(PreTrainedConfig):
-    d_model: int = 1024
-    d_ff: int = 4096
-    n_heads: int = 16
-    n_layers: int = 24
+    hidden_size: int = 1024
+    intermediate_size: int = 4096
+    num_attention_heads: int = 16
+    num_hidden_layers: int = 24

TRF024

modeling_.py와 modular_.py에서 torch.nn 생성자(Linear, Embedding, LayerNorm, RMSNorm, GroupNorm, BatchNorm*, InstanceNorm*, Convd, ConvTransposed, Bilinear, MultiheadAttention)의 차원 인자에서 8보다 큰 정수 리터럴을 위치 또는 키워드(in_features, out_features, in_channels, out_channels, num_embeddings, embedding_dim, embed_dim, normalized_shape, num_channels, hidden_size)로 표시합니다. 연산자 형태 인자(kernel_size, stride, padding, num_groups)는 무시됩니다; 최대 8까지의 리터럴은 스칼라 head, 이진 분류기, RGB 채널 수를 깨끗하게 유지합니다. 컷오프 날짜 이전에 추가된 모델은 예외입니다. 하드코딩된 너비는 모듈을 하나의 체크포인트 크기에 고정합니다: 다른 스케일의 동일한 아키텍처는 형태 불일치로 로드되고, 그 값을 가리키는 config 필드가 없으므로 from_pretrained은 어떤 값이 잘못되었는지 말할 수 없습니다. 또한 진실의 원천을 분할하므로, config를 편집해도 빌드되는 모델이 더 이상 변경되지 않습니다.

class AcmeAtomEmbedding(nn.Module):
     def __init__(self, config):
         super().__init__()
-        self.proj = nn.Linear(768, 3072, bias=False)
-        self.norm = nn.LayerNorm(3072)
+        self.proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
+        self.norm = nn.LayerNorm(config.intermediate_size)

TRF025

modeling_.py와 modular_.py에서 PreTrainedModel에서 상속하지 않는 클래스 내부의 마스크 팩토리(masking_utils 진입점 create_causal_mask, create_bidirectional_mask, create_sliding_window_causal_mask, create_chunked_causal_mask, create_masks_for_generate 및 모든 create_*_mask 헬퍼) 호출을 표시합니다 — 레이어, attention 모듈, 인코더 같은 평범한 nn.Module 블록. 마스크 구성은 레이어마다 변하지 않는 O(시퀀스 길이 제곱) 작업이므로, 레이어에서 빌드하면 그 비용을 레이어마다 한 번 지불하고 각 레이어가 자신의 마스크를 소유하여 attention 백엔드에 준비된 단일 마스크를 건네줄 수 없게 됩니다. 모델에서 한 번 빌드하고 아래로 전달하세요.

class AcmeLayer(nn.Module):
     def forward(self, hidden_states, attention_mask=None, **kwargs):
-        attention_mask = create_causal_mask(
-            config=self.config, input_embeds=hidden_states, attention_mask=attention_mask, ...
-        )
         return self.self_attn(hidden_states, attention_mask, **kwargs)

 class AcmeModel(AcmePreTrainedModel):
     def forward(self, input_ids=None, attention_mask=None, **kwargs):
+        causal_mask = create_causal_mask(
+            config=self.config, input_embeds=inputs_embeds, attention_mask=attention_mask, ...
+        )
         for layer in self.layers:
-            hidden_states = layer(hidden_states, attention_mask, **kwargs)
+            hidden_states = layer(hidden_states, causal_mask, **kwargs)

TRF026

modeling_.py와 modular_.py에서 __init__과 forward만 정의하고, __init__에서 정확히 하나의 self.을 할당하며, forward 본문이 정확히 return self.<attr>(...)인(앞의 docstring은 무시) PreTrainedModel이 아닌 클래스를 표시합니다. 다른 메서드, 추가 속성, return 앞의 문장, forward의 super() 호출은 클래스가 자신의 작업을 수행함을 의미합니다. 래퍼는 아무것도 계산하지 않으면서 모든 가중치 이름, _no_split_modules, 병렬 계획, 모든 conversion 매핑에 수준을 추가하고, 독자는 그것을 알아내기 위해 클래스를 하나 더 열어야 합니다. PreTrainedModel 서브클래스는 예외입니다: forward가 위임만 하더라도 from_pretrained와 auto 클래스를 위해 존재합니다.

-class AcmeAtomTransformer(nn.Module):
-    def __init__(self, config):
-        super().__init__()
-        self.encoder = AcmeEncoder(config)
-
-    def forward(self, hidden_states, **kwargs):
-        return self.encoder(hidden_states, **kwargs)
-
 class AcmeModel(AcmePreTrainedModel):
     def __init__(self, config):
         super().__init__(config)
-        self.atom_transformer = AcmeAtomTransformer(config)
+        self.encoder = AcmeEncoder(config)

TRF027

modeling_.py, modular_.py, configuration_*.py의 모든 assert 문을 표시합니다. python -O는 assert를 제거하므로, 그것으로 작성된 형태나 config 검사는 최적화된 실행에서 조용히 사라집니다. assert는 또한 이름 없는 AssertionError를 주는데, ValueError는 위반 값을 지명하고 무엇을 해야 할지 말할 수 있습니다.

def forward(self, hidden_states):
-    assert hidden_states.dim() == 3
+    if hidden_states.dim() != 3:
+        raise ValueError(f"Expected a 3D tensor, got shape {tuple(hidden_states.shape)}.")

TRF028

modeling_.py, modular_.py, configuration_.py, processing_.py, image_processing_.py, video_processing_.py의 처음 25줄에서 Licensed under the <name> License 줄 다음에 표준 보증 문단의 모든 절이 오는지 확인합니다. You may obtain a copy of the License at에서 limitations under the License.까지. 줄은 먼저 평탄화되고 소문자로 바뀌므로, 줄바꿈과 주석 스타일은 중요하지 않습니다. 라이선스 이름과 저작권 줄은 확인하지 않습니다: 그것들은 모델마다 다릅니다. 헤더 없이 배포된 파일은 그 출처를 모호하게 남기고, 나중에 추가하는 것은 이미 릴리스된 파일을 건드리는 것을 의미합니다. Apache License만 일치시키면 실제로 발생하는 결함(중간에 잘린 문단, URL 뒤에서 멈춘 헤더, 잘못된 검색-교체로 망가진 헤더)이 통과하게 됩니다.

+# Copyright 2026 The HuggingFace Team. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# ...
 """PyTorch Acme model."""

TRF029

modeling_.py와 modular_.py에서 config와 함께 명확히 config 필드인 이름(hidden_size, num_attention_heads, intermediate_size, head_dim, num_hidden_layers, embed_dim, dropout, eps, patch_size, rope_theta 등)의 인자를 받는 __init__을 표시합니다. None 기본값이 있는 선택적 파라미터는 예외입니다: 그것은 오버라이드이지 두 번째 진리의 원천이 아니며, 하나의 MLP 클래스가 MoE 모델의 밀집 및 전문가 너비 둘 다를 서빙하는 방법입니다. hidden_size: int = 1024 같은 하드코딩된 기본값은 그렇지 않습니다 — 호출자가 아무것도 전달하지 않을 때 config를 이깁니다. kosmos2는 허용 목록에 있습니다: 그 문서 페이지는 디렉터리 이름에서 파생되지 않으므로 컷오프가 그것을 예외로 할 수 없습니다. 같은 숫자가 이제 두 개의 진리 원천을 가지며 호출자가 승자를 고르므로, config를 편집해도 빌드되는 모델이 더 이상 변경되지 않습니다. 또한 아키텍처 지식을 속하지 않는 모든 호출 지점으로 밀어냅니다.

class AcmeAttention(nn.Module):
-    def __init__(self, config, embed_dim, num_heads, dropout):
+    def __init__(self, config, layer_idx=None):
         super().__init__()
-        self.embed_dim = embed_dim
-        self.num_heads = num_heads
+        self.embed_dim = config.hidden_size
+        self.num_heads = config.num_attention_heads

 class AcmeMLP(nn.Module):
     # an optional override is fine: omitting it reads the config
     def __init__(self, config, intermediate_size=None):
         super().__init__()
         self.intermediate_size = intermediate_size or config.intermediate_size

TRF030

modeling_.py와 modular_.py에서 config 또는 self.config에 뿌리를 둔 3단 이상의 속성 체인을 표시합니다. config.hidden_size(한 홉)와 config.text_config.hidden_size(두 홉, 일반적인 하위 config 접근)는 괜찮습니다. 줄마다 위반 하나. config.diffusion_config.atom_encoder_config.hidden_size를 걷는 모듈은 자신의 슬라이스가 아닌 전체 config 계층에 결합되므로, 재사용하거나 테스트하거나 다른 하위 config를 줄 수 없습니다. 관련 하위 config를 아래로 전달하면 체인이 한 홉으로 줄어듭니다.

class AcmeAtomEncoder(nn.Module):
     def __init__(self, config):
         super().__init__()
-        self.norm = AcmeLayerNorm(config.diffusion_config.atom_encoder_config.hidden_size)
+        self.norm = AcmeLayerNorm(config.hidden_size)

TRF031

modeling_.py와 modular_.py에서 기반에 Output으로 끝나는 것이 포함되지 않은 최상위 @dataclass를 표시합니다. 두 개 이상의 필수 필드가 아닌 한 — 그것들은 내부 인자 번들이며, ModelOutput는 런타임에 그것을 거부합니다. 일반 출력 dataclass는 튜플처럼 인덱싱되지 않고, return_dict=False에서 살아남지 못하며, @auto_docstring에 보이지 않으므로 그 필드가 생성된 API 문서에 도달하지 못합니다. ModelOutput은 세 가지를 모두 무료로 얻습니다.

@auto_docstring
 @dataclass
-class AcmeStructureOutput:
+class AcmeStructureOutput(ModelOutput):
     positions: torch.Tensor
     confidence: Optional[torch.Tensor] = None

TRF032

modeling_.py와 modular_.py에서 크기 1e3 이상의 부정된 숫자 리터럴로 호출된 masked_fill, masked_fill_, full, full_like, new_full을 표시합니다. 하드코딩된 -1e9는 float16에서 -inf로 오버플로우하고 float32에서 가장 작은 값과는 거리가 멀므로, 같은 마스크는 dtype마다 다르게 동작하고 softmax 후 NaN을 생성할 수 있습니다. torch.finfo(dtype).min은 실제로 실행 중인 어떤 dtype이든 가장 작은 표현 가능한 값입니다.

-attention_scores = attention_scores.masked_fill(~mask, -1e9)
+attention_scores = attention_scores.masked_fill(~mask, torch.finfo(attention_scores.dtype).min)

TRF033

modeling_.py와 modular_.py에서 이름이 set_로 시작하는 메서드를 표시합니다. PreTrainedModel 계약 메서드 set_input_embeddings, set_output_embeddings, set_decoder, set_encoder, set_attn_implementation, set_default_language는 예외입니다. setter는 동작을 호출 순서에 의존하게 만듭니다: 값이 config에 없으므로 저장되지 않고, from_pretrained로 복원되지 않으며, device-map이나 병렬 계획에 보이지 않습니다. 사용자는 그것을 호출해야 한다는 것을 알아야 하고, 잊는 것은 조용합니다.

class AcmeTriangleAttention(nn.Module):
-    def set_chunk_size(self, chunk_size):
-        self.chunk_size = chunk_size
+    def __init__(self, config):
+        super().__init__()
+        self.chunk_size = config.chunk_size

TRF034

modeling_.py와 modular_.py에서 기본 체인을 통해 GradientCheckpointingLayer에 도달하지 않는 nn.ModuleList(...)에서 인스턴스화된 로컬 정의 *Layer/*Block 클래스를 표시합니다; modular 파일은 상대 import를 형제 모델로 따라가며, 해결되지 않은 체인은 결론이 없습니다. 범위 밖: supports_gradient_checkpointing = True를 설정하지 않아 레이어를 건너뛰는 대신 gradient_checkpointing_enable()에서 raise하는 모델; 통계가 두 번 다시 계산될 nn.BatchNorm*/nn.InstanceNorm*을 가진 레이어; 토큰 믹서가 아닌 스택(attention, modulation, mixer, SSM 모듈이 self.x = Y(...)로 할당된 것으로 나타남). gradient_checkpointing_enable()은 레이어가 GradientCheckpointingLayer일 때만 감쌉니다. 트렁크의 평범한 nn.Module은 조용히 건너뛰어, 훈련이 전체 활성화를 할당하면서도 체크포인팅된 것처럼 보이고 OOM이 원인에서 멀리 드러납니다. 다른 곳 — conv 백본, 디코드 헤드 — 에서는 트레이드오프가 작성자의 몫이므로 규칙은 개입하지 않습니다.

-class AcmeDecoderLayer(nn.Module):
+class AcmeDecoderLayer(GradientCheckpointingLayer):
     def __init__(self, config, layer_idx):
         super().__init__()

TRF035

modeling_.py, modular_.py, configuration_*.py에서 코드가 있든 없든 # noqa 주석을 표시합니다. modular 파일에서 F401, F821, F822는 허용됩니다: 그것은 사용하는 모든 이름을 정의하지 않도록 의도된 생성 소스이므로, ruff의 미정의 이름 계열이 올바른 코드에 불을 붙입니다 — 컨버터가 채우는 __all__ 항목, 부모 모델에 사는 클래스, 재-export하기 위해 유지된 import. 그 코드들만 지명하는 # noqa는 건너뜁니다; 다른 것을 지명하는 것은 남은 코드에 대해 보고됩니다. 맨몸의 # noqa는 항상 보고되며, modular 파일에서는 메시지가 코드를 요구합니다. 모델 파일은 레포의 lint 규칙이 적용되는 일반 코드이므로, 억제는 근본 문제가 그대로 남았음을 의미합니다 — 그리고 맨몸 # noqa는 또한 그 줄의 모든 미래 위반을 숨깁니다. 대신 코드를 수정하세요.

-from ...modeling_utils import PreTrainedModel  # noqa: F401
+from ...modeling_utils import PreTrainedModel

TRF036

modeling_.py와 modular_.py에서 어떤 nn.Sequential(...) 구성이든 표시합니다. Sequential은 자식을 위치로 이름 짓므로 가중치가 mlp.0.weight와 mlp.2.weight에 놓입니다: conversion 매핑, _tied_weights_keys, 모든 병렬 계획이 그 다음 인덱스를 참조하고, 레이어를 삽입하면 그 뒤의 모든 것을 이름을 다시 바꿉니다. 또한 forward를 숨기므로, 단계 사이의 dtype 캐스트와 잔차가 발생하는 곳에서 보이지 않게 됩니다.

-        self.mlp = nn.Sequential(
-            nn.Linear(config.hidden_size, config.intermediate_size),
-            nn.GELU(),
-            nn.Linear(config.intermediate_size, config.hidden_size),
-        )
+        self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
+        self.act = ACT2FN[config.hidden_act]
+        self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)

TRF037

modeling_.py와 modular_.py에서 einsum 호출을 표시하고, 리터럴일 때 방정식을 보고합니다. 기본적으로 비활성화: einsum은 때때로 수축을 표현하는 가장 명확한 방법이므로 이것은 하드 컨벤션이 아니라 옵트인입니다. einsum 방정식은 독자가 디코딩해야 하는 표기법으로 형태를 인코딩하고, 동적으로 구축된 방정식은 어떤 수축이 실행되는지 숨깁니다. 명시적인 matmul/transpose 연산으로 확장하세요.

-        pair_bias = torch.einsum("bqhc,bkhc->bhqk", query_states, key_states)
+        pair_bias = query_states.permute(0, 2, 1, 3) @ key_states.permute(0, 2, 3, 1)

TRF038

모든 modeling_.py, processing_.py, image_processing_.py, video_processing_.py, feature_extraction_.py, tokenization_.py 파일이 일치하는 tests/models//test_.py를 가지는지 확인합니다(modeling_acme.py -> tests/models/acme/test_modeling_acme.py). 예외: configuration_.py는 동반 test_modeling_.py의 ConfigTester로 다루어지고, tokenization_utils.py는 토크나이저를 통해 다루어지는 헬퍼입니다; tokenization__fast.py는 느린 대응물의 테스트 파일에 매핑됩니다. modular_*.py는 한 번에 여러 계열을 정의할 수 있으므로, 그 클래스는 이름 접미사로 라우팅됩니다 — XxxModel/XxxPreTrainedModel/XxxFor modelling, XxxImageProcessor(Fast) 이미지 처리, XxxProcessor processing, XxxVideoProcessor 비디오 처리, XxxFeatureExtractor 피처 추출, XxxTokenizer(Fast) 토크나이제이션, XxxConfig 건너뜀 — 그리고 누락된 테스트 파일마다 위반 하나가 보고됩니다. # trf-ignore: TRF038은 지원되지 않습니다; allowlist_models를 사용하여 예외가 검토에서 보이게 하세요. 테스트 파일이 없는 소스 파일은 회귀 커버리지가 없습니다: 깨진 forward pass, 잘못된 conversion 매핑, 특수 토큰을 버리는 토크나이저가 영구히 깨진 채 들어올 수 있습니다. 랜덤 가중치를 가진 더미 config나 작은 수제 어휘면 이 중 어느 것이든 실행하기에 충분합니다.

src/transformers/models/acme/modeling_acme.py
+tests/models/acme/test_modeling_acme.py
 src/transformers/models/acme/tokenization_acme.py
+tests/models/acme/test_tokenization_acme.py

TRF039

if is_*_available(): import ... 블록(is_vision_available() and is_torch_available() 같은 조합 포함)을 찾고, 이름이 파일의 다른 곳(문자열 타입 힌트와 all 포함)에서 참조되지 않을 때 import를 표시합니다. ruff의 사용되지 않은 import 검사는 이것들을 정리하지 않습니다: import가 도달 가능하므로 블록 자체는 괜찮아 보입니다. 리팩터링이 더 이상 PIL.Image, torch 등을 필요로 하지 않으면, 가드된 import가 죽은 무게와 파일의 실제 의존성에 대한 오해 소지 있는 신호로 남습니다.

if is_vision_available():
-    from PIL import Image

TRF040

modeling_.py와 modular_.py에서 @capture_outputs와 @can_return_tuple 둘 다로 장식된 메서드를 표시합니다. TRF003을 보완하며, TRF003은 forward()의 수동 return_dict 분기를 다룹니다. 두 데코레이터 모두 return_dict를 pop하므로, 가장 바깥쪽 것만 진짜 값을 봅니다. @capture_outputs가 이미 to_tuple 변환을 처리하므로 @can_return_tuple은 중복입니다.

-@can_return_tuple
 @merge_with_config_defaults
 @capture_outputs
 @auto_docstring
 def forward(self, x):
     return AcmeModelOutput(last_hidden_state=x)

TRF041

modeling_.py와 modular_.py에서 조건이 # CODEPATH: 주석 없이 config.* 또는 self.config.* 속성을 읽는 모든 if/elif와 조건식을 표시합니다. 주석은 분기 줄이나 바로 위의 연속 주석 블록에 수용됩니다. 어떤 config 속성도 계산되며 boolean 플래그뿐만이 아닙니다. 구조적으로 예외: X if X is not None else fallback — 테스트된 필드 자체가 결과 중 하나인 경우(getattr(config, x, default)의 길게 쓴 형태)로, 그래프를 포크할 수 없습니다; 그리고 가드 — else가 없고 본문이 raise만 하거나 warn/log만 하는 if — 는 한쪽이 중단되고 그 뒤로 발산하는 것이 없기 때문입니다. 단순히 None을 언급하는 것은 자격이 되지 않습니다: config.vision_config is not None은 여전히 주석을 요구합니다. 필드별 예외: 체크포인트 발산을 게이트하지 않는 프레임워크 배선 — loss를 선택하는 problem_type, 활성화를 조회하는 hidden_act, num_labels, use_cache, is_decoder, 특수 토큰 id, summary_* 헤드 설정; 전체 목록은 mlinter/trf041.py의 DEFAULT_EXEMPT_ATTRIBUTES이며, 규칙 테이블의 ignored_attributes = [...]로 프로젝트별로 확장됩니다. 모델은 0열의 모듈 수준 # trf-ignore: TRF041 config.scale_embedding, config.auxiliary_loss로 자신의 필드 중 하나를 파일 전체에서 예외로 할 수 있습니다(self.config.x, config.x, x는 같은 필드입니다). 적어도 하나의 필드를 지명해야 합니다 — 맨몸 # trf-ignore: TRF041은 줄별 의미를 유지합니다 — 그리고 조건은 읽는 모든 필드가 예외일 때만 건너뜁니다. config로 게이트된 분기는 같은 파일의 두 번째 아키텍처이며, 코드는 두 절반이 모두 여전히 도달 가능한지 말할 수 없습니다 — 이것이 죽은 실험 분기가 릴리스를 생존하는 방식입니다. 규칙은 분기를 금지하지 않습니다: Rust의 // SAFETY:처럼, 각 측면을 사용하는 체크포인트를 옆에 적어 두라고 요청합니다. 누구도 하나를 지명할 수 없는 분기는 삭제할 분기입니다.

+        # CODEPATH: ESMC-6B ships pre-normalised embeddings, the 300M/600M checkpoints do not.
         if config.use_embedding_norm:
             hidden_states = self.embedding_norm(hidden_states)

-        if config.msa_encoder_enabled:
-            hidden_states = self.msa_encoder(hidden_states)
+        # no released checkpoint sets msa_encoder_enabled -> branch removed

TRF042

tests/models//test_tokenization_.py에서 파일이 TokenizerTesterMixin을 상속하는 수집된 테스트 클래스를 정의하는지 확인합니다. 러너가 수집하는 클래스만 해당됩니다 — TestCase 기반 또는 다른 모델의 테스트 클래스가 기반일 때의 *Test 명명 규칙 — 따라서 헬퍼 전용 파일은 건너뛰고, 스위트를 믹스인한 헬퍼는 실제 테스트 클래스에 대한 규칙을 충족하지 않습니다. 상속은 같은 파일의 기반과 이름으로 가져온 다른 모델의 토크나이저 테스트를 따라갑니다; 해결할 수 없는 기반은 결코 계산되지 않습니다. 스위트를 실행하지 않는 첫 번째 테스트 클래스에서 보고됩니다. auto는 허용 목록입니다: test_tokenization_auto.py는 AutoTokenizer 해상도가 아니라 토크나이저를 테스트합니다. TokenizerTesterMixin은 encode/decode 왕복, 패딩과 절단, 특수 토큰 처리, 추가 토큰 지속성, 저장/로드 동등성이 실제로 확인되는 곳입니다. 수제 토큰 id 목록 몇 개만 주장하는 테스트는 토크나이저가 그 모든 면에서 깨진 상태에서도 통과하고, 검토에서는 여전히 테스트된 것처럼 보입니다.

-class AcmeTokenizationTest(unittest.TestCase):
+class AcmeTokenizationTest(TokenizerTesterMixin, unittest.TestCase):
+    tokenizer_class = AcmeTokenizer
+    test_slow_tokenizer = True

TRF043

이름이 Attention으로 끝나는 클래스의 forward 시그니처에서 선언된 position_ids 파라미터를 표시합니다. position_ids는 flash-attention 패딩 없는 훈련에서 다운스트림에서 소비되며 **kwargs를 통해 흘러야 합니다. 시그니처에 이름을 지정하면 attention 인터페이스가 그것을 읽기 전에 삼켜버립니다; llama 표준은 position_embeddings와 **kwargs를 전달합니다.

class AcmeAttention(nn.Module):
     def forward(
         self,
         hidden_states,
         position_embeddings,
         attention_mask=None,
-        position_ids=None,
         **kwargs: Unpack[TransformersKwargs],
     ):

TRF044

modeling_.py와 modular_.py의 어떤 함수에서든 cache_position이라는 파라미터를 표시합니다. cache_position은 v5에서 모든 모델에서 제거되었습니다. 그것을 다시 도입하면(보통 pre-v5 소스에서 복사) 죽은 인자를 모든 레이어를 통해 스레드합니다; 캐시 업데이트는 past_key_values.update(key_states, value_states, self.layer_idx)이며 위치 스레딩이 없습니다.

def forward(
     self,
     hidden_states,
     past_key_values=None,
-    cache_position=None,
     **kwargs,
 ):
-    key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_position)
+    key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx)

TRF045

modeling_.py와 modular_.py의 forward 시그니처에서 레거시 output_attentions, output_hidden_states, return_dict 파라미터를 표시합니다. 컷오프 날짜 이전에 기여된 모델은 예외입니다. 데코레이터 스택이 출력 제어를 소유합니다: @capture_outputs는 config에 대해 output_* 플래그를 해석하고 _can_record_outputs를 통해 텐서를 기록하며, @can_return_tuple이 return_dict을 처리합니다. 그것들을 시그니처에 선언하면 데코레이터에서 벗어나는 수동 플래그 스레딩을 다시 도입합니다.

+@capture_outputs
 def forward(
     self,
     input_ids,
-    output_attentions=None,
-    output_hidden_states=None,
-    return_dict=None,
     **kwargs: Unpack[TransformersKwargs],
 ):

TRF046

modeling_.py와 modular_.py의 forward 메서드에서 self 속성에 대한 할당을 표시합니다. forward 중에 작성된 상태는 배칭, torch.compile, 모듈에 대한 추론을 깨뜨립니다. 전달된 상태는 명시적으로 전달됩니다(캐시 객체, generate 루프); config나 정적 형태에만 의존하는 값은 __init__에 속합니다.

def forward(self, hidden_states):
-    self.sequence_length = hidden_states.shape[1]
-    embeddings = self.compute_embeddings(self.sequence_length)
+    embeddings = self.compute_embeddings(hidden_states.shape[1])

TRF047

image_processing_.py와 video_processing_.py의 preprocess, _preprocess, call, post_process* 메서드에서 self 속성에 대한 할당을 표시합니다. 호출 간 상태를 지니는 프로세서는 preprocess-many-then-postprocess 배칭을 깨뜨립니다: 두 번째 preprocess가 첫 번째 postprocess가 필요로 하는 상태를 덮어씁니다. 값을 반환하거나 메서드 체인을 통해 전달하세요.

def _preprocess(self, images, **kwargs):
-    self.original_sizes = [image.shape[-2:] for image in images]
+    original_sizes = [image.shape[-2:] for image in images]
     ...
+    return BatchFeature(data={"pixel_values": pixel_values, "original_sizes": original_sizes})

TRF048

클래스 수준 _tied_weights_keys 선언의 list/tuple/set 리터럴을 표시합니다. v5는 _tied_weights_keys를 각 tied 대상 파라미터를 그 소스에 매핑하는 dict로 변경했습니다. list 형식은 더 이상 어떤 파라미터가 소스인지 말하지 않으므로, tying, device_map 계산, 저장이 조용히 잘못 동작합니다.

class AcmeForCausalLM(AcmePreTrainedModel):
-    _tied_weights_keys = ["lm_head.weight"]
+    _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}

TRF049

modeling_.py와 modular_.py의 init 메서드에서 init 호출을 표시합니다: nn.init.* / init.* 프리미티브와 자신의 파라미터에 대한 제자리 이니셜라이저(self.weight.data.normal_()). 모델은 meta 장치에서 인스턴스화되므로, __init__에 작성된 텐서 값은 로드 전에 버려집니다: 거기서만 초기화된 파라미터는 처음부터 파인튜닝하거나 meta-장치 재로드 후에 랜덤 콘텐츠를 보유합니다. __init__에서 torch.empty로 할당하고, _init_weights에서 초기화하세요.

class AcmeEmbeddings(nn.Module):
     def __init__(self, config):
         super().__init__()
         self.position_embedding = nn.Parameter(torch.empty(config.num_positions, config.hidden_size))
-        nn.init.trunc_normal_(self.position_embedding, std=config.initializer_range)

 class AcmePreTrainedModel(PreTrainedModel):
     def _init_weights(self, module):
         super()._init_weights(module)
+        if isinstance(module, AcmeEmbeddings):
+            init.trunc_normal_(module.position_embedding, std=self.config.initializer_range)

TRF050

이름이 Attention으로 끝나는 클래스의 __init__에서 *RotaryEmbedding 클래스 호출을 표시합니다. Model은 단일 rotary_emb를 소유하고 inv_freq를 한 번 만들며, position_embeddings로 cos/sin을 아래로 전달합니다. attention 레이어마다 하나의 rotary 모듈은 버퍼를 중복하고, 레이어마다 주파수를 다시 계산하며, attention이 position_embeddings를 받는다는 계약에서 벗어납니다.

class AcmeAttention(nn.Module):
     def __init__(self, config, layer_idx):
         super().__init__()
-        self.rotary_emb = AcmeRotaryEmbedding(config)

 class AcmeModel(AcmePreTrainedModel):
     def __init__(self, config):
         super().__init__(config)
+        self.rotary_emb = AcmeRotaryEmbedding(config)

TRF051

modeling_.py와 modular_.py에서 _attn_implementation 속성에 대한 비교를 표시합니다. 백엔드 디스패치는 ALL_ATTENTION_FUNCTIONS.get_interface에 속하며, 백엔드 조건부 텐서 처리(패딩, 리셰이핑)는 integrations/ 아래의 공유 래퍼에 속합니다. 인라인 분기는 모델 본문을 커널 인식하게 만들고 새 백엔드가 등록될 때 깨집니다.

-if self.config._attn_implementation == "flash_attention_2":
-    attn_output = flash_path(query_states, key_states, value_states)
-else:
-    attn_output = eager_path(query_states, key_states, value_states)
+attention_interface = ALL_ATTENTION_FUNCTIONS.get_interface(self.config._attn_implementation, eager_attention_forward)
+attn_output, attn_weights = attention_interface(self, query_states, key_states, value_states, ...)

TRF052

modeling_.py와 modular_.py에서 _ATTENTION_CLASSES로 끝나는 이름에 대한 모듈 수준 할당을 표시합니다. dict에서 선택한 백엔드별 attention 클래스는 인터페이스 이전의 관용구입니다: 거의 동일한 클래스가 서로 벌어지고, ALL_ATTENTION_FUNCTIONS에 등록된 hub attention 커널은 결코 그것에 도달하지 않습니다. 인터페이스를 통해 디스패치하는 하나의 attention 클래스가 dict를 대체합니다; 레거시 부모에서 그것을 전파하지 마세요.

-ACME_ATTENTION_CLASSES = {
-    "eager": AcmeAttention,
-    "flash_attention_2": AcmeFlashAttention2,
-    "sdpa": AcmeSdpaAttention,
-}
+class AcmeAttention(nn.Module):
+    def forward(self, ...):
+        attention_interface = ALL_ATTENTION_FUNCTIONS.get_interface(self.config._attn_implementation, eager_attention_forward)

TRF053

modeling_.py와 modular_.py에서 labels[..., 1:]처럼 슬라이싱으로 shift_logits/shift_labels(및 shifted_ 변형)를 만드는 할당을 표시합니다. 이미 shift된 라벨을 받는 것(shift_labels = kwargs.pop("shift_labels", labels))은 올바른 관용구이며 표시되지 않습니다. self.loss_function은 labels를 스스로 shift하므로, modeling 코드에서 미리 shift하면 이중 shift된 대상으로 훈련하거나 특수 loss 경로를 강제합니다. Decoder-only 모델은 원시 labels를 전달하고 loss가 shift하게 합니다. Encoder-decoder 모델은 그 반대 경우입니다: 그들의 labels는 앞에 붙은 decoder 시작 토큰으로 이미 shift되었으므로, loss가 다시 shift하지 않도록 shift_labels=labels를 전달합니다.

if labels is not None:
-    shift_logits = logits[..., :-1, :].contiguous()
-    shift_labels = labels[..., 1:].contiguous()
-    loss = nn.functional.cross_entropy(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1))
+    # decoder-only: labels are unshifted, the loss shifts them
+    loss = self.loss_function(logits=logits, labels=labels, vocab_size=self.config.vocab_size)
+    # encoder-decoder: labels are already shifted, hand them over as shift_labels
+    loss = self.loss_function(logits=logits, labels=labels, shift_labels=labels, vocab_size=self.config.vocab_size)

TRF055

modeling_.py와 modular_.py에서 PreTrainedModel 서브클래스의 클래스 속성으로 config = SomeConfig를 표시합니다. PreTrainedModel.__init_subclass__는 inspect.get_annotations(cls)를 통해 config 주석에서 config_class를 파생합니다. 할당은 그 호출이 보고하지 않는 떠도는 클래스 속성을 만들므로, 서브클래스는 조용히 부모의 config_class를 유지합니다. 맨몸 주석은 런타임 값이 없고, 속성을 만들지 않으며, 올바르게 잡힙니다.

class Gemma4VisionModel(Gemma4PreTrainedModel):
     """The Gemma 4 Vision Encoder."""
-    config = Gemma4VisionConfig
+    config: Gemma4VisionConfig

TRF056

modeling_.py와 modular_.py에서 어떤 forward 내부의 .item()과 .tolist() 호출을 표시합니다. split(...)의 split-size 인자에 공급되는 .tolist()는 예외입니다: torch.split은 Python int가 필요합니다. 두 호출 모두 텐서를 호스트로 다시 읽으며, dynamo가 추적할 수 없으므로 그래프가 깨집니다.

-        for grid, item in zip(grid_thw.tolist(), split_items):
-            _, height, width = grid
-            merged.append(self.patch_merger(item, size=(height, width)))
+        for grid, item in zip(grid_thw, split_items):
+            merged.append(self.patch_merger(item, size=(grid[1], grid[2])))

TRF057

공개 PreTrainedModel 서브클래스(<Model>PreTrainedModel, <Model>Model, <Model>For<Task>, 백본), PreTrainedConfig 서브클래스, ModelOutput 서브클래스, 이미지 프로세서, ProcessorMixin 서브클래스 및 그 공개 메서드 forward, get_image_features, get_video_features, get_audio_features, get_text_features, preprocess, __call__에 @auto_docstring을 확인합니다. modular_*.py는 그것에서 생성된 파일에 대해 검사됩니다. 그것이 없으면 클래스는 소개와 파라미터 문서 없이 배포되고, 메서드는 인자 문서, Returns 섹션, 사용 예제 없이 배포됩니다 — 이 모든 것은 auto_docstring.py에서 오는 대신 모델마다 손으로 작성해야 합니다.

+@auto_docstring
 @dataclass
 class AcmeModelOutputWithPast(ModelOutput):
     logits: torch.FloatTensor | None = None

+@auto_docstring
 class AcmeForConditionalGeneration(AcmePreTrainedModel):
+    @auto_docstring
     def forward(self, input_ids, pixel_values=None, **kwargs):
         ...

TRF058

modeling_.py와 modular_.py에서 버퍼 이름이 문자열 리터럴인 register_buffer("<name>", ...) 호출을 any 수신자(self 또는 layer.mamba 같은 다른 모듈)에서 표시합니다. 계산된 이름 — 변수나 f-string, 예: 루프 내의 레이어당 하나의 버퍼 — 은 속성 할당 대응물이 없으므로 예외입니다. torch>=2.5에서 nn.Buffer는 nn.Parameter처럼 평범한 속성 할당을 통해 버퍼를 등록합니다. 메서드 호출로 생성된 버퍼는 __init__ 실행의 부작용으로만 존재하므로, 그것을 조정하려는 modular 파일은 전체 __init__을 재정의해야 합니다. 속성으로 할당되면 그 자체로 상속되고 재정의될 수 있습니다.

-        self.register_buffer("inv_freq", inv_freq, persistent=False)
-        self.register_buffer(
-            "position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False
-        )
+        self.inv_freq = nn.Buffer(inv_freq, persistent=False)
+        self.position_ids = nn.Buffer(torch.arange(config.max_position_embeddings).expand((1, -1)), persistent=False)

TRF059

tensor-parallel 계획이 moe_tp_experts를 할당하는 모델 디렉터리에서, 라우팅된 *Experts 클래스가 hidden states, top-k 인덱스, top-k 라우팅 가중치를 처음 세 개의 위치적 forward 인자로 받는지 확인합니다. selected_experts, routing_weights 같은 별칭은 허용됩니다. MoeExpertsParallel은 위치적 인자 3에 그래디언트 변환을 적용합니다. 다른 시그니처는 그것을 잘못된 텐서에 조용히 적용하거나 건너뜁니다.

class AcmeExperts(nn.Module):
-    def forward(self, hidden_states):
+    def forward(self, hidden_states, top_k_index, top_k_weights):
         ...

위반 억제

규칙 위반을 억제해야 한다면 아래 두 옵션 중 하나를 사용하세요.

인라인 억제

위반 줄에 # trf-ignore: RULE_ID 주석을 추가하세요. 검토자가 억제가 정당한 이유를 이해할 수 있도록 설명을 포함하세요.

# trf-ignore: TRF011 — mask is derived from self.config, not the layer
hidden_states = layer(hidden_states, attention_mask=mask_from_config)

trf-ignore를 사용해 코드에서 수정해야 할 위반을 침묵시키지 마세요.

allowlist_models

즉시 수정할 수 없는 레거시 코드가 있는 모델의 경우, mlinter rules.toml의 관련 규칙 allowlist_models 목록에 모델의 디렉터리 이름을 추가하세요.

[rules.TRF004]
allowlist_models = ["existing_model", "your_model_name"]

더 알아보기 (Learn more)