Image Feature Extraction
Transformers
PyTorch
pathology
vision
vit
feature-extraction
knowledge-distillation
Instructions to use luoxd96/PathAGG with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use luoxd96/PathAGG with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-feature-extraction", model="luoxd96/PathAGG")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("luoxd96/PathAGG", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Add transformers model card layout (vitb/vits AutoModel)
Browse files- LICENSE +14 -0
- README.md +74 -0
- modeling_pathagg.py +202 -0
- requirements.txt +6 -0
- vitb/config.json +22 -0
- vitb/modeling_pathagg.py +202 -0
- vitb/pytorch_model.bin +3 -0
- vits/config.json +22 -0
- vits/modeling_pathagg.py +202 -0
- vits/pytorch_model.bin +3 -0
LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) 2026 Xiangde Luo
|
| 2 |
+
|
| 3 |
+
This work (source code, documentation, and released model weights) is licensed under the
|
| 4 |
+
Creative Commons Attribution-NonCommercial 4.0 International License (CC BY-NC 4.0).
|
| 5 |
+
|
| 6 |
+
You may copy, redistribute, and adapt the material for non-commercial purposes only,
|
| 7 |
+
provided appropriate credit is given. Commercial use is not permitted.
|
| 8 |
+
|
| 9 |
+
Full license text:
|
| 10 |
+
https://creativecommons.org/licenses/by-nc/4.0/legalcode
|
| 11 |
+
Human-readable summary:
|
| 12 |
+
https://creativecommons.org/licenses/by-nc/4.0/
|
| 13 |
+
|
| 14 |
+
THE MATERIAL IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-nc-4.0
|
| 3 |
+
library_name: transformers
|
| 4 |
+
tags:
|
| 5 |
+
- pathology
|
| 6 |
+
- vision
|
| 7 |
+
- vit
|
| 8 |
+
- feature-extraction
|
| 9 |
+
- knowledge-distillation
|
| 10 |
+
- pytorch
|
| 11 |
+
pipeline_tag: image-feature-extraction
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# PathAGG
|
| 15 |
+
|
| 16 |
+
Multi-teacher pathology foundation students distilled with **CRADIOv4**-style aggregation from [Virchow2](https://huggingface.co/paige-ai/Virchow2), [UNI2-h](https://huggingface.co/MahmoodLab/UNI2-h), and [H1](https://huggingface.co/bioptimus/H-optimus-1) (H-optimus-1).
|
| 17 |
+
|
| 18 |
+
Trained on **~66M** TCGA + HISTAI patches (~**1000 GPU-hours** / student on 8× H100).
|
| 19 |
+
Code & EVA dumps: [Luoxd1996/PathAGG](https://github.com/Luoxd1996/PathAGG).
|
| 20 |
+
|
| 21 |
+
| Variant | `subfolder` | Embed | Official EVA avg (9 tasks) |
|
| 22 |
+
|---------|-------------|------:|---------------------------:|
|
| 23 |
+
| ViT-B/14 | `vitb` | 768 | **79.57** |
|
| 24 |
+
| ViT-S/14 | `vits` | 384 | **77.93** |
|
| 25 |
+
|
| 26 |
+
**License: [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) — non-commercial / research only.**
|
| 27 |
+
|
| 28 |
+
## Load (recommended)
|
| 29 |
+
|
| 30 |
+
```bash
|
| 31 |
+
pip install torch timm transformers torchvision Pillow
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
```python
|
| 35 |
+
import torch
|
| 36 |
+
from PIL import Image
|
| 37 |
+
from torchvision import transforms
|
| 38 |
+
from transformers import AutoModel
|
| 39 |
+
|
| 40 |
+
preprocess = transforms.Compose([
|
| 41 |
+
transforms.Resize(224, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 42 |
+
transforms.CenterCrop(224),
|
| 43 |
+
transforms.ToTensor(),
|
| 44 |
+
transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
|
| 45 |
+
])
|
| 46 |
+
|
| 47 |
+
model = AutoModel.from_pretrained(
|
| 48 |
+
"luoxd96/PathAGG",
|
| 49 |
+
subfolder="vitb", # or "vits"
|
| 50 |
+
trust_remote_code=True,
|
| 51 |
+
).eval().cuda()
|
| 52 |
+
|
| 53 |
+
img = Image.open("patch.png").convert("RGB")
|
| 54 |
+
x = preprocess(img).unsqueeze(0).cuda()
|
| 55 |
+
|
| 56 |
+
with torch.inference_mode():
|
| 57 |
+
cls = model(x) # [1, 768] or [1, 384]
|
| 58 |
+
cls, patch = model(x, return_patch=True) # patch: [1, 256, D]
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
## EVA results (%)
|
| 62 |
+
|
| 63 |
+
| Task | Split | ViT-S | ViT-B |
|
| 64 |
+
|------|-------|------:|------:|
|
| 65 |
+
| BreakHis | val | 74.36 | 84.62 |
|
| 66 |
+
| CRC | val | 95.93 | 96.41 |
|
| 67 |
+
| Gleason | val | 78.72 | 77.33 |
|
| 68 |
+
| MHIST | val | 81.85 | 82.20 |
|
| 69 |
+
| PCam | test | 93.88 | 93.75 |
|
| 70 |
+
| Cam16Small | test | 83.59 | 85.03 |
|
| 71 |
+
| PANDASmall | test | 66.28 | 67.93 |
|
| 72 |
+
| CoNSeP | val | 63.58 | 64.04 |
|
| 73 |
+
| MoNuSAC | val | 63.22 | 64.82 |
|
| 74 |
+
| **Official Avg** | | **77.93** | **79.57** |
|
modeling_pathagg.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PathAGG Hugging Face loaders (trust_remote_code).
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
from transformers import AutoModel
|
| 5 |
+
model = AutoModel.from_pretrained("luoxd96/PathAGG", subfolder="vitb", trust_remote_code=True)
|
| 6 |
+
cls = model(images) # [B, D]
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from transformers import PretrainedConfig, PreTrainedModel
|
| 17 |
+
from transformers.utils import cached_file
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class PathAGGConfig(PretrainedConfig):
|
| 21 |
+
model_type = "pathagg"
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
variant: str = "vitb",
|
| 26 |
+
img_size: int = 224,
|
| 27 |
+
patch_size: int = 14,
|
| 28 |
+
embed_dim: int = 768,
|
| 29 |
+
depth: int = 12,
|
| 30 |
+
num_heads: int = 12,
|
| 31 |
+
num_register_tokens: int = 4,
|
| 32 |
+
mlp_ratio: float = 4.0,
|
| 33 |
+
qkv_bias: bool = True,
|
| 34 |
+
init_values: float | None = None,
|
| 35 |
+
no_embed_class: bool = False,
|
| 36 |
+
return_patch: bool = False,
|
| 37 |
+
**kwargs: Any,
|
| 38 |
+
) -> None:
|
| 39 |
+
super().__init__(**kwargs)
|
| 40 |
+
self.variant = variant
|
| 41 |
+
self.img_size = img_size
|
| 42 |
+
self.patch_size = patch_size
|
| 43 |
+
self.embed_dim = embed_dim
|
| 44 |
+
self.depth = depth
|
| 45 |
+
self.num_heads = num_heads
|
| 46 |
+
self.num_register_tokens = num_register_tokens
|
| 47 |
+
self.mlp_ratio = mlp_ratio
|
| 48 |
+
self.qkv_bias = qkv_bias
|
| 49 |
+
self.init_values = init_values
|
| 50 |
+
self.no_embed_class = no_embed_class
|
| 51 |
+
self.return_patch = return_patch
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _build_backbone(config: PathAGGConfig) -> nn.Module:
|
| 55 |
+
from timm.models.vision_transformer import VisionTransformer
|
| 56 |
+
|
| 57 |
+
kwargs: dict[str, Any] = dict(
|
| 58 |
+
img_size=config.img_size,
|
| 59 |
+
patch_size=config.patch_size,
|
| 60 |
+
in_chans=3,
|
| 61 |
+
num_classes=0,
|
| 62 |
+
global_pool="",
|
| 63 |
+
embed_dim=config.embed_dim,
|
| 64 |
+
depth=config.depth,
|
| 65 |
+
num_heads=config.num_heads,
|
| 66 |
+
mlp_ratio=config.mlp_ratio,
|
| 67 |
+
qkv_bias=config.qkv_bias,
|
| 68 |
+
reg_tokens=config.num_register_tokens,
|
| 69 |
+
)
|
| 70 |
+
if config.init_values is not None:
|
| 71 |
+
kwargs["init_values"] = config.init_values
|
| 72 |
+
if config.no_embed_class:
|
| 73 |
+
kwargs["no_embed_class"] = True
|
| 74 |
+
return VisionTransformer(**kwargs)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class PathAGGModel(PreTrainedModel):
|
| 78 |
+
"""Pathology multi-teacher KD student. Forward returns CLS ``[B, D]`` by default."""
|
| 79 |
+
|
| 80 |
+
config_class = PathAGGConfig
|
| 81 |
+
base_model_prefix = "pathagg"
|
| 82 |
+
_no_split_modules = ["Block"]
|
| 83 |
+
|
| 84 |
+
def __init__(self, config: PathAGGConfig) -> None:
|
| 85 |
+
super().__init__(config)
|
| 86 |
+
self.return_patch = bool(config.return_patch)
|
| 87 |
+
self.model = _build_backbone(config)
|
| 88 |
+
# Do not call post_init() random re-init; weights come from checkpoint.
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def embed_dim(self) -> int:
|
| 92 |
+
return int(self.model.embed_dim)
|
| 93 |
+
|
| 94 |
+
@property
|
| 95 |
+
def num_register_tokens(self) -> int:
|
| 96 |
+
return int(getattr(self.model, "reg_tokens", getattr(self.model, "num_register_tokens", 4)))
|
| 97 |
+
|
| 98 |
+
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
|
| 99 |
+
return self.model.forward_features(x)
|
| 100 |
+
|
| 101 |
+
def forward(
|
| 102 |
+
self,
|
| 103 |
+
pixel_values: torch.Tensor | None = None,
|
| 104 |
+
x: torch.Tensor | None = None,
|
| 105 |
+
return_patch: bool | None = None,
|
| 106 |
+
**kwargs: Any,
|
| 107 |
+
):
|
| 108 |
+
if pixel_values is None and x is None:
|
| 109 |
+
raise ValueError("provide pixel_values=... or x=...")
|
| 110 |
+
images = pixel_values if pixel_values is not None else x
|
| 111 |
+
tokens = self.forward_features(images)
|
| 112 |
+
cls = tokens[:, 0]
|
| 113 |
+
use_patch = self.return_patch if return_patch is None else bool(return_patch)
|
| 114 |
+
if not use_patch:
|
| 115 |
+
return cls
|
| 116 |
+
num_prefix = int(getattr(self.model, "num_prefix_tokens", 1 + self.num_register_tokens))
|
| 117 |
+
patch = tokens[:, num_prefix:]
|
| 118 |
+
return cls, patch
|
| 119 |
+
|
| 120 |
+
@classmethod
|
| 121 |
+
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
|
| 122 |
+
"""Load config + weights without transformers gamma↔weight remapping (LayerScale)."""
|
| 123 |
+
subfolder = kwargs.pop("subfolder", "")
|
| 124 |
+
local_files_only = kwargs.pop("local_files_only", False)
|
| 125 |
+
revision = kwargs.pop("revision", None)
|
| 126 |
+
cache_dir = kwargs.pop("cache_dir", None)
|
| 127 |
+
token = kwargs.pop("token", None)
|
| 128 |
+
if token is None:
|
| 129 |
+
token = kwargs.pop("use_auth_token", None)
|
| 130 |
+
kwargs.pop("trust_remote_code", None)
|
| 131 |
+
kwargs.pop("torch_dtype", None)
|
| 132 |
+
kwargs.pop("device_map", None)
|
| 133 |
+
kwargs.pop("low_cpu_mem_usage", None)
|
| 134 |
+
return_patch = kwargs.pop("return_patch", None)
|
| 135 |
+
config = kwargs.pop("config", None)
|
| 136 |
+
|
| 137 |
+
if config is None:
|
| 138 |
+
config = PathAGGConfig.from_pretrained(
|
| 139 |
+
pretrained_model_name_or_path,
|
| 140 |
+
subfolder=subfolder,
|
| 141 |
+
local_files_only=local_files_only,
|
| 142 |
+
revision=revision,
|
| 143 |
+
cache_dir=cache_dir,
|
| 144 |
+
token=token,
|
| 145 |
+
**kwargs,
|
| 146 |
+
)
|
| 147 |
+
if return_patch is not None:
|
| 148 |
+
config.return_patch = bool(return_patch)
|
| 149 |
+
|
| 150 |
+
model = cls(config)
|
| 151 |
+
|
| 152 |
+
weight_file = cached_file(
|
| 153 |
+
pretrained_model_name_or_path,
|
| 154 |
+
"pytorch_model.bin",
|
| 155 |
+
subfolder=subfolder,
|
| 156 |
+
local_files_only=local_files_only,
|
| 157 |
+
revision=revision,
|
| 158 |
+
cache_dir=cache_dir,
|
| 159 |
+
token=token,
|
| 160 |
+
)
|
| 161 |
+
if weight_file is None:
|
| 162 |
+
raise FileNotFoundError(
|
| 163 |
+
f"pytorch_model.bin not found under {pretrained_model_name_or_path!r} (subfolder={subfolder!r})"
|
| 164 |
+
)
|
| 165 |
+
try:
|
| 166 |
+
state = torch.load(weight_file, map_location="cpu", weights_only=True)
|
| 167 |
+
except TypeError:
|
| 168 |
+
state = torch.load(weight_file, map_location="cpu")
|
| 169 |
+
|
| 170 |
+
# transformers may have renamed LayerScale gamma→weight in some pipelines; normalize back.
|
| 171 |
+
fixed = {}
|
| 172 |
+
for k, v in state.items():
|
| 173 |
+
if k.endswith(".ls1.weight") or k.endswith(".ls2.weight"):
|
| 174 |
+
fixed[k[: -len(".weight")] + ".gamma"] = v
|
| 175 |
+
else:
|
| 176 |
+
fixed[k] = v
|
| 177 |
+
missing, unexpected = model.load_state_dict(fixed, strict=True)
|
| 178 |
+
if missing or unexpected:
|
| 179 |
+
raise RuntimeError(f"load failed: missing={missing}, unexpected={unexpected}")
|
| 180 |
+
model.eval()
|
| 181 |
+
return model
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def get_preprocess(img_size: int = 224):
|
| 185 |
+
"""ImageNet normalize preprocess (PIL → tensor)."""
|
| 186 |
+
from torchvision import transforms
|
| 187 |
+
|
| 188 |
+
return transforms.Compose(
|
| 189 |
+
[
|
| 190 |
+
transforms.Resize(img_size, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 191 |
+
transforms.CenterCrop(img_size),
|
| 192 |
+
transforms.ToTensor(),
|
| 193 |
+
transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
|
| 194 |
+
]
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
__all__ = [
|
| 199 |
+
"PathAGGConfig",
|
| 200 |
+
"PathAGGModel",
|
| 201 |
+
"get_preprocess",
|
| 202 |
+
]
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.0
|
| 2 |
+
timm>=0.9.12
|
| 3 |
+
transformers>=4.40.0
|
| 4 |
+
torchvision>=0.15
|
| 5 |
+
Pillow>=9.0
|
| 6 |
+
huggingface_hub>=0.23.0
|
vitb/config.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": ["PathAGGModel"],
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoConfig": "modeling_pathagg.PathAGGConfig",
|
| 5 |
+
"AutoModel": "modeling_pathagg.PathAGGModel"
|
| 6 |
+
},
|
| 7 |
+
"model_type": "pathagg",
|
| 8 |
+
"variant": "vitb",
|
| 9 |
+
"img_size": 224,
|
| 10 |
+
"patch_size": 14,
|
| 11 |
+
"embed_dim": 768,
|
| 12 |
+
"depth": 12,
|
| 13 |
+
"num_heads": 12,
|
| 14 |
+
"num_register_tokens": 4,
|
| 15 |
+
"mlp_ratio": 4.0,
|
| 16 |
+
"qkv_bias": true,
|
| 17 |
+
"init_values": null,
|
| 18 |
+
"no_embed_class": false,
|
| 19 |
+
"return_patch": false,
|
| 20 |
+
"torch_dtype": "float32",
|
| 21 |
+
"transformers_version": "4.40.0"
|
| 22 |
+
}
|
vitb/modeling_pathagg.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PathAGG Hugging Face loaders (trust_remote_code).
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
from transformers import AutoModel
|
| 5 |
+
model = AutoModel.from_pretrained("luoxd96/PathAGG", subfolder="vitb", trust_remote_code=True)
|
| 6 |
+
cls = model(images) # [B, D]
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from transformers import PretrainedConfig, PreTrainedModel
|
| 17 |
+
from transformers.utils import cached_file
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class PathAGGConfig(PretrainedConfig):
|
| 21 |
+
model_type = "pathagg"
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
variant: str = "vitb",
|
| 26 |
+
img_size: int = 224,
|
| 27 |
+
patch_size: int = 14,
|
| 28 |
+
embed_dim: int = 768,
|
| 29 |
+
depth: int = 12,
|
| 30 |
+
num_heads: int = 12,
|
| 31 |
+
num_register_tokens: int = 4,
|
| 32 |
+
mlp_ratio: float = 4.0,
|
| 33 |
+
qkv_bias: bool = True,
|
| 34 |
+
init_values: float | None = None,
|
| 35 |
+
no_embed_class: bool = False,
|
| 36 |
+
return_patch: bool = False,
|
| 37 |
+
**kwargs: Any,
|
| 38 |
+
) -> None:
|
| 39 |
+
super().__init__(**kwargs)
|
| 40 |
+
self.variant = variant
|
| 41 |
+
self.img_size = img_size
|
| 42 |
+
self.patch_size = patch_size
|
| 43 |
+
self.embed_dim = embed_dim
|
| 44 |
+
self.depth = depth
|
| 45 |
+
self.num_heads = num_heads
|
| 46 |
+
self.num_register_tokens = num_register_tokens
|
| 47 |
+
self.mlp_ratio = mlp_ratio
|
| 48 |
+
self.qkv_bias = qkv_bias
|
| 49 |
+
self.init_values = init_values
|
| 50 |
+
self.no_embed_class = no_embed_class
|
| 51 |
+
self.return_patch = return_patch
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _build_backbone(config: PathAGGConfig) -> nn.Module:
|
| 55 |
+
from timm.models.vision_transformer import VisionTransformer
|
| 56 |
+
|
| 57 |
+
kwargs: dict[str, Any] = dict(
|
| 58 |
+
img_size=config.img_size,
|
| 59 |
+
patch_size=config.patch_size,
|
| 60 |
+
in_chans=3,
|
| 61 |
+
num_classes=0,
|
| 62 |
+
global_pool="",
|
| 63 |
+
embed_dim=config.embed_dim,
|
| 64 |
+
depth=config.depth,
|
| 65 |
+
num_heads=config.num_heads,
|
| 66 |
+
mlp_ratio=config.mlp_ratio,
|
| 67 |
+
qkv_bias=config.qkv_bias,
|
| 68 |
+
reg_tokens=config.num_register_tokens,
|
| 69 |
+
)
|
| 70 |
+
if config.init_values is not None:
|
| 71 |
+
kwargs["init_values"] = config.init_values
|
| 72 |
+
if config.no_embed_class:
|
| 73 |
+
kwargs["no_embed_class"] = True
|
| 74 |
+
return VisionTransformer(**kwargs)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class PathAGGModel(PreTrainedModel):
|
| 78 |
+
"""Pathology multi-teacher KD student. Forward returns CLS ``[B, D]`` by default."""
|
| 79 |
+
|
| 80 |
+
config_class = PathAGGConfig
|
| 81 |
+
base_model_prefix = "pathagg"
|
| 82 |
+
_no_split_modules = ["Block"]
|
| 83 |
+
|
| 84 |
+
def __init__(self, config: PathAGGConfig) -> None:
|
| 85 |
+
super().__init__(config)
|
| 86 |
+
self.return_patch = bool(config.return_patch)
|
| 87 |
+
self.model = _build_backbone(config)
|
| 88 |
+
# Do not call post_init() random re-init; weights come from checkpoint.
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def embed_dim(self) -> int:
|
| 92 |
+
return int(self.model.embed_dim)
|
| 93 |
+
|
| 94 |
+
@property
|
| 95 |
+
def num_register_tokens(self) -> int:
|
| 96 |
+
return int(getattr(self.model, "reg_tokens", getattr(self.model, "num_register_tokens", 4)))
|
| 97 |
+
|
| 98 |
+
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
|
| 99 |
+
return self.model.forward_features(x)
|
| 100 |
+
|
| 101 |
+
def forward(
|
| 102 |
+
self,
|
| 103 |
+
pixel_values: torch.Tensor | None = None,
|
| 104 |
+
x: torch.Tensor | None = None,
|
| 105 |
+
return_patch: bool | None = None,
|
| 106 |
+
**kwargs: Any,
|
| 107 |
+
):
|
| 108 |
+
if pixel_values is None and x is None:
|
| 109 |
+
raise ValueError("provide pixel_values=... or x=...")
|
| 110 |
+
images = pixel_values if pixel_values is not None else x
|
| 111 |
+
tokens = self.forward_features(images)
|
| 112 |
+
cls = tokens[:, 0]
|
| 113 |
+
use_patch = self.return_patch if return_patch is None else bool(return_patch)
|
| 114 |
+
if not use_patch:
|
| 115 |
+
return cls
|
| 116 |
+
num_prefix = int(getattr(self.model, "num_prefix_tokens", 1 + self.num_register_tokens))
|
| 117 |
+
patch = tokens[:, num_prefix:]
|
| 118 |
+
return cls, patch
|
| 119 |
+
|
| 120 |
+
@classmethod
|
| 121 |
+
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
|
| 122 |
+
"""Load config + weights without transformers gamma↔weight remapping (LayerScale)."""
|
| 123 |
+
subfolder = kwargs.pop("subfolder", "")
|
| 124 |
+
local_files_only = kwargs.pop("local_files_only", False)
|
| 125 |
+
revision = kwargs.pop("revision", None)
|
| 126 |
+
cache_dir = kwargs.pop("cache_dir", None)
|
| 127 |
+
token = kwargs.pop("token", None)
|
| 128 |
+
if token is None:
|
| 129 |
+
token = kwargs.pop("use_auth_token", None)
|
| 130 |
+
kwargs.pop("trust_remote_code", None)
|
| 131 |
+
kwargs.pop("torch_dtype", None)
|
| 132 |
+
kwargs.pop("device_map", None)
|
| 133 |
+
kwargs.pop("low_cpu_mem_usage", None)
|
| 134 |
+
return_patch = kwargs.pop("return_patch", None)
|
| 135 |
+
config = kwargs.pop("config", None)
|
| 136 |
+
|
| 137 |
+
if config is None:
|
| 138 |
+
config = PathAGGConfig.from_pretrained(
|
| 139 |
+
pretrained_model_name_or_path,
|
| 140 |
+
subfolder=subfolder,
|
| 141 |
+
local_files_only=local_files_only,
|
| 142 |
+
revision=revision,
|
| 143 |
+
cache_dir=cache_dir,
|
| 144 |
+
token=token,
|
| 145 |
+
**kwargs,
|
| 146 |
+
)
|
| 147 |
+
if return_patch is not None:
|
| 148 |
+
config.return_patch = bool(return_patch)
|
| 149 |
+
|
| 150 |
+
model = cls(config)
|
| 151 |
+
|
| 152 |
+
weight_file = cached_file(
|
| 153 |
+
pretrained_model_name_or_path,
|
| 154 |
+
"pytorch_model.bin",
|
| 155 |
+
subfolder=subfolder,
|
| 156 |
+
local_files_only=local_files_only,
|
| 157 |
+
revision=revision,
|
| 158 |
+
cache_dir=cache_dir,
|
| 159 |
+
token=token,
|
| 160 |
+
)
|
| 161 |
+
if weight_file is None:
|
| 162 |
+
raise FileNotFoundError(
|
| 163 |
+
f"pytorch_model.bin not found under {pretrained_model_name_or_path!r} (subfolder={subfolder!r})"
|
| 164 |
+
)
|
| 165 |
+
try:
|
| 166 |
+
state = torch.load(weight_file, map_location="cpu", weights_only=True)
|
| 167 |
+
except TypeError:
|
| 168 |
+
state = torch.load(weight_file, map_location="cpu")
|
| 169 |
+
|
| 170 |
+
# transformers may have renamed LayerScale gamma→weight in some pipelines; normalize back.
|
| 171 |
+
fixed = {}
|
| 172 |
+
for k, v in state.items():
|
| 173 |
+
if k.endswith(".ls1.weight") or k.endswith(".ls2.weight"):
|
| 174 |
+
fixed[k[: -len(".weight")] + ".gamma"] = v
|
| 175 |
+
else:
|
| 176 |
+
fixed[k] = v
|
| 177 |
+
missing, unexpected = model.load_state_dict(fixed, strict=True)
|
| 178 |
+
if missing or unexpected:
|
| 179 |
+
raise RuntimeError(f"load failed: missing={missing}, unexpected={unexpected}")
|
| 180 |
+
model.eval()
|
| 181 |
+
return model
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def get_preprocess(img_size: int = 224):
|
| 185 |
+
"""ImageNet normalize preprocess (PIL → tensor)."""
|
| 186 |
+
from torchvision import transforms
|
| 187 |
+
|
| 188 |
+
return transforms.Compose(
|
| 189 |
+
[
|
| 190 |
+
transforms.Resize(img_size, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 191 |
+
transforms.CenterCrop(img_size),
|
| 192 |
+
transforms.ToTensor(),
|
| 193 |
+
transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
|
| 194 |
+
]
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
__all__ = [
|
| 199 |
+
"PathAGGConfig",
|
| 200 |
+
"PathAGGModel",
|
| 201 |
+
"get_preprocess",
|
| 202 |
+
]
|
vitb/pytorch_model.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:74c871a8f8756b7b0470b37001768a17cc1f68753081a8134c45354a76c752d7
|
| 3 |
+
size 342914855
|
vits/config.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": ["PathAGGModel"],
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoConfig": "modeling_pathagg.PathAGGConfig",
|
| 5 |
+
"AutoModel": "modeling_pathagg.PathAGGModel"
|
| 6 |
+
},
|
| 7 |
+
"model_type": "pathagg",
|
| 8 |
+
"variant": "vits",
|
| 9 |
+
"img_size": 224,
|
| 10 |
+
"patch_size": 14,
|
| 11 |
+
"embed_dim": 384,
|
| 12 |
+
"depth": 12,
|
| 13 |
+
"num_heads": 6,
|
| 14 |
+
"num_register_tokens": 4,
|
| 15 |
+
"mlp_ratio": 4.0,
|
| 16 |
+
"qkv_bias": true,
|
| 17 |
+
"init_values": 1e-5,
|
| 18 |
+
"no_embed_class": true,
|
| 19 |
+
"return_patch": false,
|
| 20 |
+
"torch_dtype": "float32",
|
| 21 |
+
"transformers_version": "4.40.0"
|
| 22 |
+
}
|
vits/modeling_pathagg.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PathAGG Hugging Face loaders (trust_remote_code).
|
| 2 |
+
|
| 3 |
+
Usage:
|
| 4 |
+
from transformers import AutoModel
|
| 5 |
+
model = AutoModel.from_pretrained("luoxd96/PathAGG", subfolder="vitb", trust_remote_code=True)
|
| 6 |
+
cls = model(images) # [B, D]
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
from typing import Any
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from transformers import PretrainedConfig, PreTrainedModel
|
| 17 |
+
from transformers.utils import cached_file
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class PathAGGConfig(PretrainedConfig):
|
| 21 |
+
model_type = "pathagg"
|
| 22 |
+
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
variant: str = "vitb",
|
| 26 |
+
img_size: int = 224,
|
| 27 |
+
patch_size: int = 14,
|
| 28 |
+
embed_dim: int = 768,
|
| 29 |
+
depth: int = 12,
|
| 30 |
+
num_heads: int = 12,
|
| 31 |
+
num_register_tokens: int = 4,
|
| 32 |
+
mlp_ratio: float = 4.0,
|
| 33 |
+
qkv_bias: bool = True,
|
| 34 |
+
init_values: float | None = None,
|
| 35 |
+
no_embed_class: bool = False,
|
| 36 |
+
return_patch: bool = False,
|
| 37 |
+
**kwargs: Any,
|
| 38 |
+
) -> None:
|
| 39 |
+
super().__init__(**kwargs)
|
| 40 |
+
self.variant = variant
|
| 41 |
+
self.img_size = img_size
|
| 42 |
+
self.patch_size = patch_size
|
| 43 |
+
self.embed_dim = embed_dim
|
| 44 |
+
self.depth = depth
|
| 45 |
+
self.num_heads = num_heads
|
| 46 |
+
self.num_register_tokens = num_register_tokens
|
| 47 |
+
self.mlp_ratio = mlp_ratio
|
| 48 |
+
self.qkv_bias = qkv_bias
|
| 49 |
+
self.init_values = init_values
|
| 50 |
+
self.no_embed_class = no_embed_class
|
| 51 |
+
self.return_patch = return_patch
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _build_backbone(config: PathAGGConfig) -> nn.Module:
|
| 55 |
+
from timm.models.vision_transformer import VisionTransformer
|
| 56 |
+
|
| 57 |
+
kwargs: dict[str, Any] = dict(
|
| 58 |
+
img_size=config.img_size,
|
| 59 |
+
patch_size=config.patch_size,
|
| 60 |
+
in_chans=3,
|
| 61 |
+
num_classes=0,
|
| 62 |
+
global_pool="",
|
| 63 |
+
embed_dim=config.embed_dim,
|
| 64 |
+
depth=config.depth,
|
| 65 |
+
num_heads=config.num_heads,
|
| 66 |
+
mlp_ratio=config.mlp_ratio,
|
| 67 |
+
qkv_bias=config.qkv_bias,
|
| 68 |
+
reg_tokens=config.num_register_tokens,
|
| 69 |
+
)
|
| 70 |
+
if config.init_values is not None:
|
| 71 |
+
kwargs["init_values"] = config.init_values
|
| 72 |
+
if config.no_embed_class:
|
| 73 |
+
kwargs["no_embed_class"] = True
|
| 74 |
+
return VisionTransformer(**kwargs)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class PathAGGModel(PreTrainedModel):
|
| 78 |
+
"""Pathology multi-teacher KD student. Forward returns CLS ``[B, D]`` by default."""
|
| 79 |
+
|
| 80 |
+
config_class = PathAGGConfig
|
| 81 |
+
base_model_prefix = "pathagg"
|
| 82 |
+
_no_split_modules = ["Block"]
|
| 83 |
+
|
| 84 |
+
def __init__(self, config: PathAGGConfig) -> None:
|
| 85 |
+
super().__init__(config)
|
| 86 |
+
self.return_patch = bool(config.return_patch)
|
| 87 |
+
self.model = _build_backbone(config)
|
| 88 |
+
# Do not call post_init() random re-init; weights come from checkpoint.
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def embed_dim(self) -> int:
|
| 92 |
+
return int(self.model.embed_dim)
|
| 93 |
+
|
| 94 |
+
@property
|
| 95 |
+
def num_register_tokens(self) -> int:
|
| 96 |
+
return int(getattr(self.model, "reg_tokens", getattr(self.model, "num_register_tokens", 4)))
|
| 97 |
+
|
| 98 |
+
def forward_features(self, x: torch.Tensor) -> torch.Tensor:
|
| 99 |
+
return self.model.forward_features(x)
|
| 100 |
+
|
| 101 |
+
def forward(
|
| 102 |
+
self,
|
| 103 |
+
pixel_values: torch.Tensor | None = None,
|
| 104 |
+
x: torch.Tensor | None = None,
|
| 105 |
+
return_patch: bool | None = None,
|
| 106 |
+
**kwargs: Any,
|
| 107 |
+
):
|
| 108 |
+
if pixel_values is None and x is None:
|
| 109 |
+
raise ValueError("provide pixel_values=... or x=...")
|
| 110 |
+
images = pixel_values if pixel_values is not None else x
|
| 111 |
+
tokens = self.forward_features(images)
|
| 112 |
+
cls = tokens[:, 0]
|
| 113 |
+
use_patch = self.return_patch if return_patch is None else bool(return_patch)
|
| 114 |
+
if not use_patch:
|
| 115 |
+
return cls
|
| 116 |
+
num_prefix = int(getattr(self.model, "num_prefix_tokens", 1 + self.num_register_tokens))
|
| 117 |
+
patch = tokens[:, num_prefix:]
|
| 118 |
+
return cls, patch
|
| 119 |
+
|
| 120 |
+
@classmethod
|
| 121 |
+
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
|
| 122 |
+
"""Load config + weights without transformers gamma↔weight remapping (LayerScale)."""
|
| 123 |
+
subfolder = kwargs.pop("subfolder", "")
|
| 124 |
+
local_files_only = kwargs.pop("local_files_only", False)
|
| 125 |
+
revision = kwargs.pop("revision", None)
|
| 126 |
+
cache_dir = kwargs.pop("cache_dir", None)
|
| 127 |
+
token = kwargs.pop("token", None)
|
| 128 |
+
if token is None:
|
| 129 |
+
token = kwargs.pop("use_auth_token", None)
|
| 130 |
+
kwargs.pop("trust_remote_code", None)
|
| 131 |
+
kwargs.pop("torch_dtype", None)
|
| 132 |
+
kwargs.pop("device_map", None)
|
| 133 |
+
kwargs.pop("low_cpu_mem_usage", None)
|
| 134 |
+
return_patch = kwargs.pop("return_patch", None)
|
| 135 |
+
config = kwargs.pop("config", None)
|
| 136 |
+
|
| 137 |
+
if config is None:
|
| 138 |
+
config = PathAGGConfig.from_pretrained(
|
| 139 |
+
pretrained_model_name_or_path,
|
| 140 |
+
subfolder=subfolder,
|
| 141 |
+
local_files_only=local_files_only,
|
| 142 |
+
revision=revision,
|
| 143 |
+
cache_dir=cache_dir,
|
| 144 |
+
token=token,
|
| 145 |
+
**kwargs,
|
| 146 |
+
)
|
| 147 |
+
if return_patch is not None:
|
| 148 |
+
config.return_patch = bool(return_patch)
|
| 149 |
+
|
| 150 |
+
model = cls(config)
|
| 151 |
+
|
| 152 |
+
weight_file = cached_file(
|
| 153 |
+
pretrained_model_name_or_path,
|
| 154 |
+
"pytorch_model.bin",
|
| 155 |
+
subfolder=subfolder,
|
| 156 |
+
local_files_only=local_files_only,
|
| 157 |
+
revision=revision,
|
| 158 |
+
cache_dir=cache_dir,
|
| 159 |
+
token=token,
|
| 160 |
+
)
|
| 161 |
+
if weight_file is None:
|
| 162 |
+
raise FileNotFoundError(
|
| 163 |
+
f"pytorch_model.bin not found under {pretrained_model_name_or_path!r} (subfolder={subfolder!r})"
|
| 164 |
+
)
|
| 165 |
+
try:
|
| 166 |
+
state = torch.load(weight_file, map_location="cpu", weights_only=True)
|
| 167 |
+
except TypeError:
|
| 168 |
+
state = torch.load(weight_file, map_location="cpu")
|
| 169 |
+
|
| 170 |
+
# transformers may have renamed LayerScale gamma→weight in some pipelines; normalize back.
|
| 171 |
+
fixed = {}
|
| 172 |
+
for k, v in state.items():
|
| 173 |
+
if k.endswith(".ls1.weight") or k.endswith(".ls2.weight"):
|
| 174 |
+
fixed[k[: -len(".weight")] + ".gamma"] = v
|
| 175 |
+
else:
|
| 176 |
+
fixed[k] = v
|
| 177 |
+
missing, unexpected = model.load_state_dict(fixed, strict=True)
|
| 178 |
+
if missing or unexpected:
|
| 179 |
+
raise RuntimeError(f"load failed: missing={missing}, unexpected={unexpected}")
|
| 180 |
+
model.eval()
|
| 181 |
+
return model
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def get_preprocess(img_size: int = 224):
|
| 185 |
+
"""ImageNet normalize preprocess (PIL → tensor)."""
|
| 186 |
+
from torchvision import transforms
|
| 187 |
+
|
| 188 |
+
return transforms.Compose(
|
| 189 |
+
[
|
| 190 |
+
transforms.Resize(img_size, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 191 |
+
transforms.CenterCrop(img_size),
|
| 192 |
+
transforms.ToTensor(),
|
| 193 |
+
transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
|
| 194 |
+
]
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
__all__ = [
|
| 199 |
+
"PathAGGConfig",
|
| 200 |
+
"PathAGGModel",
|
| 201 |
+
"get_preprocess",
|
| 202 |
+
]
|
vits/pytorch_model.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5b54b6ea8748aa8b97234fb7d0e53aecddd6de01a958946f20e0f273f514a84a
|
| 3 |
+
size 86592207
|