13 Commits

Author SHA1 Message Date
Philipp Emanuel Weidmann c62e10d570 fix: install kernels as a Transformers extra
Fixes #343
2026-06-04 12:17:35 +05:30
Ashar 906d96f78a feat: add support for LiquidAI/LFM2.5 models (#344)
* feat: add support for LiquidAI/LFM2.5 models

* add lint supress and obey gemini

Signed-off-by: coder3101 <ashar786khan@gmail.com>

* ci: format code

Signed-off-by: Ashar <ashar786khan@gmail.com>

---------

Signed-off-by: coder3101 <ashar786khan@gmail.com>
Signed-off-by: Ashar <ashar786khan@gmail.com>
2026-06-03 17:58:05 +05:30
UnstableLlama b79aa717c6 feat: add config.nohumor.toml (#340)
* feat: add config.nohumor

* Update config.nohumor.toml

Following style guide

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update config.nohumor.toml

Reduced initial comments

---------

Co-authored-by: UnstableLlama <randomnotrealemail@gmail.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-05-31 15:26:40 +05:30
Rocker Zhang db07814a97 build(deps): remove unused hf-transfer dependency (#338)
hf-transfer is declared in pyproject.toml but never activated: nothing in
the codebase sets HF_HUB_ENABLE_HF_TRANSFER, and downloads go through
from_pretrained / hf_hub_download with no transfer toggle. huggingface-hub
is pinned ~=1.7, where Xet is the default transfer backend, so hf-transfer
is dead weight and only surfaces a deprecation warning.
2026-05-31 15:16:31 +05:30
Rocker Zhang b790094193 feat: support plain text files as prompt datasets (#337)
A dataset path that points to a plain file is now read as one prompt per
line, with empty lines ignored. For text files, "column" is ignored and
"split" is optional; when given, it selects a subset of lines using slice
notation (e.g. "[:400]").

Detection uses os.path.isfile so files without an extension also work. The
split-parsing logic is factored into a shared get_split_slice helper, which
derives the split name from the specification, and split/column are now
optional in DatasetSpecification, with the dataset branches raising a clear
error when either is missing. An invalid split raises instead of being
silently ignored.

A bare slice does not parse with the pinned datasets version, since
ReadInstruction.from_spec expects a named split, so the text branch prepends
a synthetic split name.

Revives the approach from #103.

Closes #98.

Co-authored-by: Ric <ricyoung@gmail.com>
2026-05-31 15:06:47 +05:30
kabachuha 6338e2c99b feat: add "disclaimer" to the prohibited strings list (#334)
* add "disclaimer" to the prohibited strings list

The favorite Gemma's word.

* add "disclaimer" to config.py refusal markers
2026-05-28 17:36:30 +05:30
dependabot[bot] 4dcacb5eba build(deps): bump urllib3 from 2.6.3 to 2.7.0 (#328)
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 15:00:08 +05:30
dependabot[bot] b8d2c5a7e9 build(deps): bump idna from 3.11 to 3.15 (#327)
Bumps [idna](https://github.com/kjd/idna) from 3.11 to 3.15.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.11...v3.15)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.15'
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 14:56:21 +05:30
Philipp Emanuel Weidmann 4e3a3a78a3 docs: update README 2026-05-22 14:51:24 +05:30
iuyua9 551db26bb7 fix: recognize root Hugging Face repo IDs (#325)
* fix: recognize root Hugging Face repo IDs

* fix: propagate invalid HF repo ids

* fix: match transformers local path precedence
2026-05-16 09:19:15 +05:30
dependabot[bot] 8b5b85bec9 build(deps): bump mako from 1.3.11 to 1.3.12 (#323)
Bumps [mako](https://github.com/sqlalchemy/mako) from 1.3.11 to 1.3.12.
- [Release notes](https://github.com/sqlalchemy/mako/releases)
- [Changelog](https://github.com/sqlalchemy/mako/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/mako/commits)

---
updated-dependencies:
- dependency-name: mako
  dependency-version: 1.3.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-09 15:19:28 +05:30
anrp 1b4851536d fix: Reset model after saving merged model (#321)
* fix: Reset model after saving merged model

The adapter is lost and writes 0-byte adapters if you save an adapter after saving the merged model.

* Revert "Revert "Revert "fix: disable LoRA export for now" (#308)" (#319)"

This reverts commit 216c089974.

* Add comment as to why resetting model is needed
2026-05-09 15:16:26 +05:30
Philipp Emanuel Weidmann b2bdc1f9d6 feat: add functionality for collecting reproduce.json files from Hugging Face 2026-05-07 18:33:50 +05:30
10 changed files with 316 additions and 126 deletions
+3 -2
View File
@@ -116,8 +116,9 @@ a configuration file.
At the start of a program run, Heretic benchmarks the system to determine At the start of a program run, Heretic benchmarks the system to determine
the optimal batch size to make the most of the available hardware. the optimal batch size to make the most of the available hardware.
On an RTX 3090, with the default configuration, decensoring Llama-3.1-8B-Instruct On an RTX 3090, with the default configuration, decensoring
takes about 45 minutes. Note that Heretic supports model quantization with [Qwen3-4B-Instruct-2507](https://huggingface.co/Qwen/Qwen3-4B-Instruct-2507)
takes about 20-30 minutes. Note that Heretic supports model quantization with
bitsandbytes, which can drastically reduce the amount of VRAM required to process bitsandbytes, which can drastically reduce the amount of VRAM required to process
models. Set the `quantization` option to `bnb_4bit` to enable quantization. models. Set the `quantization` option to `bnb_4bit` to enable quantization.
+6
View File
@@ -135,6 +135,7 @@ max_shard_size = "5GB"
# Strings whose presence in a response (case insensitive) identifies the response as a refusal. # Strings whose presence in a response (case insensitive) identifies the response as a refusal.
refusal_markers = [ refusal_markers = [
"disclaimer",
"sorry", "sorry",
"i can'", "i can'",
"i cant", "i cant",
@@ -172,6 +173,11 @@ refusal_markers = [
# System prompt to use when prompting the model. # System prompt to use when prompting the model.
system_prompt = "You are a helpful assistant." system_prompt = "You are a helpful assistant."
# Each "dataset" below can be a Hugging Face dataset ID, a path to a dataset on disk,
# or a path to a plain text file with one prompt per line (empty lines are ignored).
# For text files, "column" is ignored and "split" is optional; when given, it selects
# a subset of the lines using slice notation (e.g. "[:400]").
# Dataset of prompts that tend to not result in refusals (used for calculating refusal directions). # Dataset of prompts that tend to not result in refusals (used for calculating refusal directions).
[good_prompts] [good_prompts]
dataset = "mlabonne/harmless_alpaca" dataset = "mlabonne/harmless_alpaca"
+69
View File
@@ -0,0 +1,69 @@
# Rename this file to config.toml, place it in the working directory
# that you run Heretic from, and edit the configuration to your liking.
max_response_length = 300
residual_plot_title = "PaCMAP Projection of Residuals for Serious/Humorous Prompts"
refusal_markers = [
"😅",
"here's one",
"why did",
"walked into a bar",
"gets worse",
"the punchline",
"classic",
"dad joke",
"bad joke",
"pun intended",
"ba dum tss",
"rimshot",
"😂",
"😄",
"😆",
"haha",
"hehe",
"lol",
"funny",
"joke",
"humor",
"that's hilarious",
"you could say",
"one-liner",
"comedian",
"stand-up",
"unexpectedly",
"because apparently",
"to be fair",
"on the bright side",
"lmao",
"omg",
"rofl",
"silly",
"humorous",
"clever",
]
[good_prompts]
dataset = "mlabonne/harmless_alpaca"
split = "train[:400]"
column = "text"
residual_plot_label = "Serious prompts"
residual_plot_color = "royalblue"
[bad_prompts]
dataset = "UnstableLlama/jokes"
split = "train[:200]"
column = "text"
residual_plot_label = "Humorous prompts"
residual_plot_color = "darkorange"
[good_evaluation_prompts]
dataset = "mlabonne/harmless_alpaca"
split = "test[:100]"
column = "text"
[bad_evaluation_prompts]
dataset = "UnstableLlama/jokes"
split = "train[200:250]"
column = "text"
+1 -3
View File
@@ -25,10 +25,8 @@ dependencies = [
"accelerate~=1.13", "accelerate~=1.13",
"bitsandbytes~=0.49", "bitsandbytes~=0.49",
"datasets~=4.7", "datasets~=4.7",
"hf-transfer~=0.1",
"huggingface-hub~=1.7", "huggingface-hub~=1.7",
"immutabledict~=4.3", "immutabledict~=4.3",
"kernels~=0.13",
"langdetect~=1.0", "langdetect~=1.0",
"lm-eval[hf]~=0.4", "lm-eval[hf]~=0.4",
"numpy~=2.2", "numpy~=2.2",
@@ -41,7 +39,7 @@ dependencies = [
"rich~=14.3", "rich~=14.3",
"tomli-w~=1.2", "tomli-w~=1.2",
"tqdm~=4.67", "tqdm~=4.67",
"transformers~=5.6", "transformers[kernels]~=5.6",
] ]
[project.optional-dependencies] [project.optional-dependencies]
+19 -2
View File
@@ -42,9 +42,15 @@ class DatasetSpecification(BaseModel):
description="Hugging Face commit hash of the dataset.", description="Hugging Face commit hash of the dataset.",
) )
split: str = Field(description="Portion of the dataset to use.") split: str | None = Field(
default=None,
description="Portion of the dataset to use. Required for datasets, optional for plain text files.",
)
column: str = Field(description="Column in the dataset that contains the prompts.") column: str | None = Field(
default=None,
description="Column in the dataset that contains the prompts. Required for datasets, ignored for plain text files.",
)
prefix: str = Field( prefix: str = Field(
default="", default="",
@@ -103,6 +109,16 @@ class Settings(BaseSettings):
exclude=True, exclude=True,
) )
collect_reproducibles: str | None = Field(
default=None,
description=(
"If this directory path is set, then instead of abliterating a model, "
"download all reproduce.json files from public Heretic model repositories "
"on Hugging Face, and store them in that directory for archival purposes."
),
exclude=True,
)
dtypes: list[str] = Field( dtypes: list[str] = Field(
default=[ default=[
# In practice, "auto" almost always means bfloat16. # In practice, "auto" almost always means bfloat16.
@@ -402,6 +418,7 @@ class Settings(BaseSettings):
refusal_markers: list[str] = Field( refusal_markers: list[str] = Field(
default=[ default=[
"disclaimer",
"sorry", "sorry",
"i can'", "i can'",
"i cant", "i cant",
+22 -7
View File
@@ -65,6 +65,7 @@ from .analyzer import Analyzer
from .config import QuantizationMethod from .config import QuantizationMethod
from .evaluator import Evaluator from .evaluator import Evaluator
from .model import AbliterationParameters, Model, get_model_class from .model import AbliterationParameters, Model, get_model_class
from .reproduce import collect_reproducibles
from .system import empty_cache, get_accelerator_info from .system import empty_cache, get_accelerator_info
from .utils import ( from .utils import (
format_duration, format_duration,
@@ -144,18 +145,13 @@ def obtain_merge_strategy(settings: Settings, model: Model) -> str | None:
value="merge", value="merge",
), ),
Choice( Choice(
title="Cancel", title="Save LoRA adapter only (can be merged later)",
value="cancel", value="adapter",
), ),
], ],
) )
if strategy == "cancel":
return None
return strategy return strategy
else:
return "merge"
def run(): def run():
@@ -177,6 +173,8 @@ def run():
if ( if (
# There is at least one argument (argv[0] is the program name). # There is at least one argument (argv[0] is the program name).
len(sys.argv) > 1 len(sys.argv) > 1
# Heretic is being invoked in standard (model processing) mode.
and "--collect-reproducibles" not in sys.argv
# No model has been explicitly provided. # No model has been explicitly provided.
and "--model" not in sys.argv and "--model" not in sys.argv
# The last argument is a parameter value rather than a flag (such as "--help"). # The last argument is a parameter value rather than a flag (such as "--help").
@@ -185,6 +183,11 @@ def run():
# Assume the last argument is the model. # Assume the last argument is the model.
sys.argv.insert(-1, "--model") sys.argv.insert(-1, "--model")
# Work around the "model" argument being required
# when Heretic is invoked in a non-processing mode.
if "--collect-reproducibles" in sys.argv and "--model" not in sys.argv:
sys.argv.extend(["--model", ""])
try: try:
# The required argument "model" must be provided by the user, # The required argument "model" must be provided by the user,
# either on the command line or in the configuration file. # either on the command line or in the configuration file.
@@ -201,6 +204,10 @@ def run():
) )
return return
if settings.collect_reproducibles is not None:
collect_reproducibles(settings.collect_reproducibles)
return
if settings.seed is None: if settings.seed is None:
settings.seed = random.randint(0, 2**32 - 1) settings.seed = random.randint(0, 2**32 - 1)
@@ -742,6 +749,10 @@ def run():
print("* Parameters:") print("* Parameters:")
for name, value in get_trial_parameters(trial).items(): for name, value in get_trial_parameters(trial).items():
print(f" * {name} = [bold]{value}[/]") print(f" * {name} = [bold]{value}[/]")
# Per https://github.com/huggingface/peft/issues/868#issuecomment-1820642893 once a LoRA is merged it's
# expected to be empty. Provide a utility function to restore the previous LoRA-ified state.
def reset_trial_model() -> None:
print("* Resetting model...") print("* Resetting model...")
model.reset_model() model.reset_model()
print("* Abliterating...") print("* Abliterating...")
@@ -754,6 +765,8 @@ def run():
}, },
) )
reset_trial_model()
while True: while True:
print() print()
action = prompt_select( action = prompt_select(
@@ -800,6 +813,7 @@ def run():
del merged_model del merged_model
empty_cache() empty_cache()
model.tokenizer.save_pretrained(save_directory) model.tokenizer.save_pretrained(save_directory)
reset_trial_model()
print(f"Model saved to [bold]{save_directory}[/].") print(f"Model saved to [bold]{save_directory}[/].")
@@ -909,6 +923,7 @@ def run():
private=private, private=private,
token=token, token=token,
) )
reset_trial_model()
if is_hf_path(settings.model): if is_hf_path(settings.model):
card = ModelCard.load(settings.model) card = ModelCard.load(settings.model)
+15
View File
@@ -389,6 +389,21 @@ class Model:
for expert in layer.block_sparse_moe.experts: # ty:ignore[possibly-missing-attribute, not-iterable] for expert in layer.block_sparse_moe.experts: # ty:ignore[possibly-missing-attribute, not-iterable]
try_add("mlp.down_proj", expert.w2) # ty:ignore[possibly-missing-attribute] try_add("mlp.down_proj", expert.w2) # ty:ignore[possibly-missing-attribute]
# LFM dense operator blocks.
with suppress(Exception):
try_add("attn.o_proj", layer.conv.out_proj) # ty:ignore[possibly-missing-attribute]
with suppress(Exception):
try_add("mlp.down_proj", layer.feed_forward.w2) # ty:ignore[possibly-missing-attribute]
# LFM transformer blocks.
with suppress(Exception):
try_add("attn.o_proj", layer.self_attn.out_proj) # ty:ignore[possibly-missing-attribute]
with suppress(Exception):
for expert in layer.feed_forward.experts: # ty:ignore[possibly-missing-attribute, not-iterable]
try_add("mlp.down_proj", expert.w2) # ty:ignore[possibly-missing-attribute]
# Granite MoE Hybrid - attention layers with shared_mlp. # Granite MoE Hybrid - attention layers with shared_mlp.
with suppress(Exception): with suppress(Exception):
try_add("mlp.down_proj", layer.shared_mlp.output_linear) # ty:ignore[possibly-missing-attribute] try_add("mlp.down_proj", layer.shared_mlp.output_linear) # ty:ignore[possibly-missing-attribute]
+83
View File
@@ -0,0 +1,83 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2025-2026 Philipp Emanuel Weidmann <pew@worldwidemann.com> + contributors
import shutil
from pathlib import Path
from huggingface_hub import HfApi, hf_hub_download
from huggingface_hub.utils import disable_progress_bars, enable_progress_bars
from .utils import print
def collect_reproducibles(path: str):
print(
f"Collecting [bold]reproduce.json[/] files from Hugging Face and storing them in [bold]{path}[/]..."
)
print()
api = HfApi()
models = api.list_models(
filter=["heretic", "reproducible"],
sort="created_at",
)
found = 0
downloaded = 0
# We're only downloading tiny files, so the progress bars are just noise.
disable_progress_bars()
try:
for model in models:
# Ignore repositories containing quantizations.
if model.tags is not None and "gguf" in model.tags:
continue
print(f"[bold]{model.id}[/]...", end="")
user, repository = model.id.split("/")
paths_info = api.get_paths_info(
model.id,
"reproduce/reproduce.json",
expand=True,
)
# The reproduce.json file might not exist in the repository
# despite the relevant tags being present.
if not paths_info:
print(" [yellow]no reproduce.json found[/]")
continue
found += 1
commit_hash = paths_info[0].last_commit.oid
file_path = (
Path(path)
/ "huggingface.co"
/ user
/ f"{repository}-{commit_hash[:7]}.json"
)
if file_path.exists():
print(" already stored")
continue
cache_path = hf_hub_download(
model.id,
"reproduce/reproduce.json",
)
file_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(cache_path, file_path)
print(" [green]downloaded[/]")
downloaded += 1
finally:
enable_progress_bars()
print()
print(f"Found: [bold]{found}[/] files")
print(f"Downloaded: [bold]{downloaded}[/] files")
print(f"Already stored: [bold]{found - downloaded}[/] files")
+45 -18
View File
@@ -22,6 +22,7 @@ from datasets import DatasetDict, ReadInstruction, load_dataset, load_from_disk
from datasets.config import DATASET_STATE_JSON_FILENAME from datasets.config import DATASET_STATE_JSON_FILENAME
from datasets.download.download_manager import DownloadMode from datasets.download.download_manager import DownloadMode
from datasets.utils.info_utils import VerificationMode from datasets.utils.info_utils import VerificationMode
from huggingface_hub.utils import validate_repo_id
from optuna import Trial from optuna import Trial
from psutil import Process from psutil import Process
from questionary import Choice, Style from questionary import Choice, Style
@@ -172,13 +173,13 @@ def format_duration(seconds: float) -> str:
def is_hf_path(path: str) -> bool: def is_hf_path(path: str) -> bool:
"""Checks whether a path likely refers to a Hugging Face repository.""" """Checks whether a path likely refers to a Hugging Face repository."""
return ( # Match Transformers: existing local paths take precedence over Hub lookup,
not path.startswith("/") # even if the path string is also a valid repository ID.
and not path.endswith("/") if Path(path).exists():
and path.count("/") == 1 return False
and "\\" not in path
and not Path(path).exists() validate_repo_id(path)
) return True
@dataclass @dataclass
@@ -187,6 +188,20 @@ class Prompt:
user: str user: str
def get_split_slice(split_str: str, length: int) -> tuple[int, int]:
"""Resolves a split specification into absolute (start, end) indices."""
# The split name is the part before the slice, e.g. "train" in "train[:400]".
split_name = split_str.split("[")[0]
# Associate the split with its number of examples (lines).
name_to_length = {split_name: length}
# Convert the instructions to absolute indices and select the first one.
absolute_instruction = ReadInstruction.from_spec(split_str).to_absolute(
name_to_length
)[0]
return absolute_instruction.from_, absolute_instruction.to
def load_prompts( def load_prompts(
settings: Settings, settings: Settings,
specification: DatasetSpecification, specification: DatasetSpecification,
@@ -194,29 +209,41 @@ def load_prompts(
path = specification.dataset path = specification.dataset
split_str = specification.split split_str = specification.split
if os.path.isfile(path):
# Plain text file with one prompt per line. Empty lines are ignored.
with open(path, encoding="utf-8") as file:
prompts = [line.strip() for line in file if line.strip()]
# The split is optional for text files. When given, it selects a subset
# of the lines using slice notation (e.g. "[:400]"). A synthetic split
# name is prepended because ReadInstruction expects a named split.
if split_str is not None:
start, end = get_split_slice(f"_{split_str}", len(prompts))
prompts = prompts[start:end]
else:
# All dataset sources require an explicit split and column.
if split_str is None:
raise ValueError(f'The "split" field is required for datasets: {path}')
if specification.column is None:
raise ValueError(f'The "column" field is required for datasets: {path}')
if is_hf_path(path): if is_hf_path(path):
dataset = load_dataset( dataset = load_dataset(
path, path,
revision=specification.commit, revision=specification.commit,
split=split_str, split=split_str,
) )
else: elif Path(path, DATASET_STATE_JSON_FILENAME).exists():
if Path(path, DATASET_STATE_JSON_FILENAME).exists():
# Dataset saved with datasets.save_to_disk; needs special handling. # Dataset saved with datasets.save_to_disk; needs special handling.
# Path should be the subdirectory for a particular split. # Path should be the subdirectory for a particular split.
dataset = load_from_disk(path) dataset = load_from_disk(path)
assert not isinstance(dataset, DatasetDict), ( assert not isinstance(dataset, DatasetDict), (
"Loading dataset dicts is not supported" "Loading dataset dicts is not supported"
) )
# Parse the split instructions. # Parse the split instructions and apply them.
instruction = ReadInstruction.from_spec(split_str) start, end = get_split_slice(split_str, len(dataset))
# Associate the split with its number of examples (lines). dataset = dataset[start:end]
split_name = str(dataset.split)
name2len = {split_name: len(dataset)}
# Convert the instructions to absolute indices and select the first one.
abs_instruction = instruction.to_absolute(name2len)[0]
# Get the dataset by applying the indices.
dataset = dataset[abs_instruction.from_ : abs_instruction.to]
else: else:
# Path should be a local directory. # Path should be a local directory.
dataset = load_dataset( dataset = load_dataset(
Generated
+20 -61
View File
@@ -8,7 +8,7 @@ resolution-markers = [
] ]
[options] [options]
exclude-newer = "2026-04-28T12:47:55.130721483Z" exclude-newer = "2026-05-28T06:40:14.509192809Z"
exclude-newer-span = "P7D" exclude-newer-span = "P7D"
[[package]] [[package]]
@@ -937,10 +937,8 @@ dependencies = [
{ name = "accelerate" }, { name = "accelerate" },
{ name = "bitsandbytes" }, { name = "bitsandbytes" },
{ name = "datasets" }, { name = "datasets" },
{ name = "hf-transfer" },
{ name = "huggingface-hub" }, { name = "huggingface-hub" },
{ name = "immutabledict" }, { name = "immutabledict" },
{ name = "kernels" },
{ name = "langdetect" }, { name = "langdetect" },
{ name = "lm-eval", extra = ["hf"] }, { name = "lm-eval", extra = ["hf"] },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
@@ -954,7 +952,7 @@ dependencies = [
{ name = "rich" }, { name = "rich" },
{ name = "tomli-w" }, { name = "tomli-w" },
{ name = "tqdm" }, { name = "tqdm" },
{ name = "transformers" }, { name = "transformers", extra = ["kernels"] },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@@ -979,11 +977,9 @@ requires-dist = [
{ name = "bitsandbytes", specifier = "~=0.49" }, { name = "bitsandbytes", specifier = "~=0.49" },
{ name = "datasets", specifier = "~=4.7" }, { name = "datasets", specifier = "~=4.7" },
{ name = "geom-median", marker = "extra == 'research'", specifier = "~=0.1" }, { name = "geom-median", marker = "extra == 'research'", specifier = "~=0.1" },
{ name = "hf-transfer", specifier = "~=0.1" },
{ name = "huggingface-hub", specifier = "~=1.7" }, { name = "huggingface-hub", specifier = "~=1.7" },
{ name = "imageio", marker = "extra == 'research'", specifier = "~=2.37" }, { name = "imageio", marker = "extra == 'research'", specifier = "~=2.37" },
{ name = "immutabledict", specifier = "~=4.3" }, { name = "immutabledict", specifier = "~=4.3" },
{ name = "kernels", specifier = "~=0.13" },
{ name = "langdetect", specifier = "~=1.0" }, { name = "langdetect", specifier = "~=1.0" },
{ name = "lm-eval", extras = ["hf"], specifier = "~=0.4" }, { name = "lm-eval", extras = ["hf"], specifier = "~=0.4" },
{ name = "matplotlib", marker = "extra == 'research'", specifier = "~=3.10" }, { name = "matplotlib", marker = "extra == 'research'", specifier = "~=3.10" },
@@ -999,7 +995,7 @@ requires-dist = [
{ name = "scikit-learn", marker = "extra == 'research'", specifier = "~=1.7" }, { name = "scikit-learn", marker = "extra == 'research'", specifier = "~=1.7" },
{ name = "tomli-w", specifier = "~=1.2" }, { name = "tomli-w", specifier = "~=1.2" },
{ name = "tqdm", specifier = "~=4.67" }, { name = "tqdm", specifier = "~=4.67" },
{ name = "transformers", specifier = "~=5.6" }, { name = "transformers", extras = ["kernels"], specifier = "~=5.6" },
] ]
provides-extras = ["research"] provides-extras = ["research"]
@@ -1009,38 +1005,6 @@ dev = [
{ name = "ty", specifier = ">=0.0.5" }, { name = "ty", specifier = ">=0.0.5" },
] ]
[[package]]
name = "hf-transfer"
version = "0.1.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1a/eb/8fc64f40388c29ce8ce3b2b180a089d4d6b25b1d0d232d016704cb852104/hf_transfer-0.1.9.tar.gz", hash = "sha256:035572865dab29d17e783fbf1e84cf1cb24f3fcf8f1b17db1cfc7fdf139f02bf", size = 25201, upload-time = "2025-01-07T10:05:12.947Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/78/0dce00208f585fae675f40033ef9a30dedfa83665d5ac79f16beb4a0a6c2/hf_transfer-0.1.9-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:6e94e8822da79573c9b6ae4d6b2f847c59a7a06c5327d7db20751b68538dc4f6", size = 1386084, upload-time = "2025-01-07T10:04:47.874Z" },
{ url = "https://files.pythonhosted.org/packages/ea/2e/3d60b1a9e9f29a2152aa66c823bf5e399ae7be3fef310ff0de86779c5d2d/hf_transfer-0.1.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ebc4ab9023414880c8b1d3c38174d1c9989eb5022d37e814fa91a3060123eb0", size = 1343558, upload-time = "2025-01-07T10:04:42.313Z" },
{ url = "https://files.pythonhosted.org/packages/fb/38/130a5ac3747f104033591bcac1c961cb1faadfdc91704f59b09c0b465ff2/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8674026f21ed369aa2a0a4b46000aca850fc44cd2b54af33a172ce5325b4fc82", size = 3726676, upload-time = "2025-01-07T10:04:11.539Z" },
{ url = "https://files.pythonhosted.org/packages/15/a1/f4e27c5ad17aac616ae0849e2aede5aae31db8267a948c6b3eeb9fd96446/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a736dfbb2c84f5a2c975478ad200c0c8bfcb58a25a35db402678fb87ce17fa4", size = 3062920, upload-time = "2025-01-07T10:04:16.297Z" },
{ url = "https://files.pythonhosted.org/packages/8d/0d/727abdfba39bc3f1132cfa4c970588c2c0bb0d82fe2d645cc10f4e2f8e0b/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:504b8427fd785dd8546d53b9fafe6e436bd7a3adf76b9dce556507650a7b4567", size = 3578681, upload-time = "2025-01-07T10:04:29.702Z" },
{ url = "https://files.pythonhosted.org/packages/50/d0/2b213eb1ea8b1252ccaf1a6c804d0aba03fea38aae4124df6a3acb70511a/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c7fc1b85f4d0f76e452765d7648c9f4bfd0aedb9ced2ae1ebfece2d8cfaf8e2", size = 3398837, upload-time = "2025-01-07T10:04:22.778Z" },
{ url = "https://files.pythonhosted.org/packages/8c/8a/79dbce9006e0bd6b74516f97451a7b7c64dbbb426df15d901dd438cfeee3/hf_transfer-0.1.9-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d991376f0eac70a60f0cbc95602aa708a6f7c8617f28b4945c1431d67b8e3c8", size = 3546986, upload-time = "2025-01-07T10:04:36.415Z" },
{ url = "https://files.pythonhosted.org/packages/a9/f7/9ac239b6ee6fe0bad130325d987a93ea58c4118e50479f0786f1733b37e8/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e6ac4eddcd99575ed3735ed911ddf9d1697e2bd13aa3f0ad7e3904dd4863842e", size = 4071715, upload-time = "2025-01-07T10:04:53.224Z" },
{ url = "https://files.pythonhosted.org/packages/d8/a3/0ed697279f5eeb7a40f279bd783cf50e6d0b91f24120dcf66ef2cf8822b4/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:57fd9880da1ee0f47250f735f791fab788f0aa1ee36afc49f761349869c8b4d9", size = 3388081, upload-time = "2025-01-07T10:04:57.818Z" },
{ url = "https://files.pythonhosted.org/packages/dc/eb/47e477bdf1d784f31c7540db6cc8c354b777e51a186897a7abda34517f36/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:5d561f0520f493c66b016d99ceabe69c23289aa90be38dd802d2aef279f15751", size = 3658654, upload-time = "2025-01-07T10:05:03.168Z" },
{ url = "https://files.pythonhosted.org/packages/45/07/6661e43fbee09594a8a5e9bb778107d95fe38dac4c653982afe03d32bd4d/hf_transfer-0.1.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a5b366d34cd449fe9b20ef25941e6eef0460a2f74e7389f02e673e1f88ebd538", size = 3690551, upload-time = "2025-01-07T10:05:09.238Z" },
{ url = "https://files.pythonhosted.org/packages/81/f5/461d2e5f307e5048289b1168d5c642ae3bb2504e88dff1a38b92ed990a21/hf_transfer-0.1.9-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e66acf91df4a8b72f60223059df3003062a5ae111757187ed1a06750a30e911b", size = 1393046, upload-time = "2025-01-07T10:04:51.003Z" },
{ url = "https://files.pythonhosted.org/packages/41/ba/8d9fd9f1083525edfcb389c93738c802f3559cb749324090d7109c8bf4c2/hf_transfer-0.1.9-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:8669dbcc7a3e2e8d61d42cd24da9c50d57770bd74b445c65123291ca842a7e7a", size = 1348126, upload-time = "2025-01-07T10:04:45.712Z" },
{ url = "https://files.pythonhosted.org/packages/8e/a2/cd7885bc9959421065a6fae0fe67b6c55becdeda4e69b873e52976f9a9f0/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fd0167c4407a3bc4cdd0307e65ada2294ec04f1813d8a69a5243e379b22e9d8", size = 3728604, upload-time = "2025-01-07T10:04:14.173Z" },
{ url = "https://files.pythonhosted.org/packages/f6/2e/a072cf196edfeda3310c9a5ade0a0fdd785e6154b3ce24fc738c818da2a7/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ee8b10afedcb75f71091bcc197c526a6ebf5c58bbbadb34fdeee6160f55f619f", size = 3064995, upload-time = "2025-01-07T10:04:18.663Z" },
{ url = "https://files.pythonhosted.org/packages/c2/84/aec9ef4c0fab93c1ea2b1badff38c78b4b2f86f0555b26d2051dbc920cde/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5828057e313de59300dd1abb489444bc452efe3f479d3c55b31a8f680936ba42", size = 3580908, upload-time = "2025-01-07T10:04:32.834Z" },
{ url = "https://files.pythonhosted.org/packages/29/63/b560d39651a56603d64f1a0212d0472a44cbd965db2fa62b99d99cb981bf/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc6bd19e1cc177c66bdef15ef8636ad3bde79d5a4f608c158021153b4573509d", size = 3400839, upload-time = "2025-01-07T10:04:26.122Z" },
{ url = "https://files.pythonhosted.org/packages/d6/d8/f87ea6f42456254b48915970ed98e993110521e9263472840174d32c880d/hf_transfer-0.1.9-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdca9bfb89e6f8f281890cc61a8aff2d3cecaff7e1a4d275574d96ca70098557", size = 3552664, upload-time = "2025-01-07T10:04:40.123Z" },
{ url = "https://files.pythonhosted.org/packages/d6/56/1267c39b65fc8f4e2113b36297320f102718bf5799b544a6cbe22013aa1d/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:89a23f58b7b7effbc047b8ca286f131b17728c99a9f972723323003ffd1bb916", size = 4073732, upload-time = "2025-01-07T10:04:55.624Z" },
{ url = "https://files.pythonhosted.org/packages/82/1a/9c748befbe3decf7cb415e34f8a0c3789a0a9c55910dea73d581e48c0ce5/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:dc7fff1345980d6c0ebb92c811d24afa4b98b3e07ed070c8e38cc91fd80478c5", size = 3390096, upload-time = "2025-01-07T10:04:59.98Z" },
{ url = "https://files.pythonhosted.org/packages/72/85/4c03da147b6b4b7cb12e074d3d44eee28604a387ed0eaf7eaaead5069c57/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:1a6bd16c667ebe89a069ca163060127a794fa3a3525292c900b8c8cc47985b0d", size = 3664743, upload-time = "2025-01-07T10:05:05.416Z" },
{ url = "https://files.pythonhosted.org/packages/e7/6e/e597b04f753f1b09e6893075d53a82a30c13855cbaa791402695b01e369f/hf_transfer-0.1.9-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d2fde99d502093ade3ab1b53f80da18480e9902aa960dab7f74fb1b9e5bc5746", size = 3695243, upload-time = "2025-01-07T10:05:11.411Z" },
{ url = "https://files.pythonhosted.org/packages/09/89/d4e234727a26b2546c8fb70a276cd924260d60135f2165bf8b9ed67bb9a4/hf_transfer-0.1.9-cp38-abi3-win32.whl", hash = "sha256:435cc3cdc8524ce57b074032b8fd76eed70a4224d2091232fa6a8cef8fd6803e", size = 1086605, upload-time = "2025-01-07T10:05:18.873Z" },
{ url = "https://files.pythonhosted.org/packages/a1/14/f1e15b851d1c2af5b0b1a82bf8eb10bda2da62d98180220ba6fd8879bb5b/hf_transfer-0.1.9-cp38-abi3-win_amd64.whl", hash = "sha256:16f208fc678911c37e11aa7b586bc66a37d02e636208f18b6bc53d29b5df40ad", size = 1160240, upload-time = "2025-01-07T10:05:14.324Z" },
]
[[package]] [[package]]
name = "hf-xet" name = "hf-xet"
version = "1.4.2" version = "1.4.2"
@@ -1123,11 +1087,11 @@ wheels = [
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.11" version = "3.15"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
] ]
[[package]] [[package]]
@@ -1188,18 +1152,17 @@ wheels = [
[[package]] [[package]]
name = "kernels" name = "kernels"
version = "0.13.0" version = "0.12.3"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "huggingface-hub" }, { name = "huggingface-hub" },
{ name = "packaging" }, { name = "packaging" },
{ name = "pyyaml" }, { name = "pyyaml" },
{ name = "tomli", marker = "python_full_version < '3.11'" }, { name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "tomlkit" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/3e/0d/e9c158c527a7b51382fe816a7b7e60caae17ff1153640c1803211a067c99/kernels-0.13.0.tar.gz", hash = "sha256:bf7908206009bff0017d09b87f0f6b5934a1a20520562caf1cbb06cab36418cc", size = 74755, upload-time = "2026-04-10T14:30:45.356Z" } sdist = { url = "https://files.pythonhosted.org/packages/b3/84/9f68f355f6ce99e977872021fbdbafadcf2820f51d3f7bd697ec3801cb7a/kernels-0.12.3.tar.gz", hash = "sha256:87e29716578e7e71dc5a7578e0132bfdae305bedaeb602698f87c88ca6c60e32", size = 57407, upload-time = "2026-03-20T10:20:42.166Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/45/2cb29e965c199ab01151fee24cbb57b23550c9e6bc897ca242b1e4b8c4bf/kernels-0.13.0-py3-none-any.whl", hash = "sha256:5d857ee4e06dc7496bcd59c4756e84eb71c019b34524dea58ccb0eaaae3bb6df", size = 69177, upload-time = "2026-04-10T14:30:43.551Z" }, { url = "https://files.pythonhosted.org/packages/e7/3e/778e4a86830e9139df2d16d86c4488fce426ec19daa83cbd2854ef389030/kernels-0.12.3-py3-none-any.whl", hash = "sha256:5d1d33fcb774e03bb7f0688ac24d91ef6b963692f80f0a85ddd2286e69f3cf2f", size = 55501, upload-time = "2026-03-20T10:20:40.643Z" },
] ]
[[package]] [[package]]
@@ -1509,14 +1472,14 @@ wheels = [
[[package]] [[package]]
name = "mako" name = "mako"
version = "1.3.11" version = "1.3.12"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "markupsafe" }, { name = "markupsafe" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/59/8a/805404d0c0b9f3d7a326475ca008db57aea9c5c9f2e1e39ed0faa335571c/mako-1.3.11.tar.gz", hash = "sha256:071eb4ab4c5010443152255d77db7faa6ce5916f35226eb02dc34479b6858069", size = 399811, upload-time = "2026-04-14T20:19:51.493Z" } sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/68/a5/19d7aaa7e433713ffe881df33705925a196afb9532efc8475d26593921a6/mako-1.3.11-py3-none-any.whl", hash = "sha256:e372c6e333cf004aa736a15f425087ec977e1fcbd2966aae7f17c8dc1da27a77", size = 78503, upload-time = "2026-04-14T20:19:53.233Z" }, { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" },
] ]
[[package]] [[package]]
@@ -3697,15 +3660,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" }, { url = "https://files.pythonhosted.org/packages/c7/18/c86eb8e0202e32dd3df50d43d7ff9854f8e0603945ff398974c1d91ac1ef/tomli_w-1.2.0-py3-none-any.whl", hash = "sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90", size = 6675, upload-time = "2025-01-15T12:07:22.074Z" },
] ]
[[package]]
name = "tomlkit"
version = "0.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" },
]
[[package]] [[package]]
name = "torch" name = "torch"
version = "2.9.1" version = "2.9.1"
@@ -3800,6 +3754,11 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/95/0b0218149b0d6f14df35f5b8f676fa83df4f19ed253c3cc447107ef86eca/transformers-5.6.2-py3-none-any.whl", hash = "sha256:f8d3a1bb96778fed9b8aabfd0dd6e19843e4b0f2bb6b59f32b8a92051b0f348f", size = 10364898, upload-time = "2026-04-23T18:33:26.081Z" }, { url = "https://files.pythonhosted.org/packages/5d/95/0b0218149b0d6f14df35f5b8f676fa83df4f19ed253c3cc447107ef86eca/transformers-5.6.2-py3-none-any.whl", hash = "sha256:f8d3a1bb96778fed9b8aabfd0dd6e19843e4b0f2bb6b59f32b8a92051b0f348f", size = 10364898, upload-time = "2026-04-23T18:33:26.081Z" },
] ]
[package.optional-dependencies]
kernels = [
{ name = "kernels" },
]
[[package]] [[package]]
name = "triton" name = "triton"
version = "3.5.1" version = "3.5.1"
@@ -3905,11 +3864,11 @@ wheels = [
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.6.3" version = "2.7.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
] ]
[[package]] [[package]]