ajh-code's picture
Add files using upload-large-folder tool
16f5171 verified
Raw
History Blame Contribute Delete
7.34 kB
"""Standalone loader for project-local mixed NVFP4/MXFP8 S2-Pro checkpoints."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
from typing import Any
import torch
from safetensors.torch import load_file
from fish_speech.models.text2semantic.llama import (
BaseModelArgs,
DualARTransformer,
precompute_freqs_cis,
)
from fish_speech.tokenizer import FishTokenizer
from experimental.fp8 import MXFP8Linear
from .modules import NVFP4Linear
CHECKPOINT_FORMAT = "fish-s2-pro-project-local-nvfp4-mixed"
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
while chunk := handle.read(8 * 1024 * 1024):
digest.update(chunk)
return digest.hexdigest()
def _install_empty_projection(
model: DualARTransformer,
record: dict[str, Any],
*,
w4a16_max_m: int,
) -> None:
name = str(record["name"])
in_features = int(record["in_features"])
out_features = int(record["out_features"])
precision = str(record["precision"])
original = model.get_submodule(name)
if not isinstance(original, torch.nn.Linear):
raise TypeError(f"Expected an unmodified Linear at {name}, got {type(original)}")
if (original.in_features, original.out_features) != (in_features, out_features):
raise ValueError(f"Checkpoint shape metadata does not match config at {name}")
if precision.startswith("nvfp4_w4a16_through_m"):
unsupported = (
"low_rank_corrected",
"sparse_channel_corrected",
"hadamard",
)
if any(marker in precision for marker in unsupported):
raise ValueError(
f"This standalone loader does not support corrected/rotated NVFP4: {name}"
)
replacement: torch.nn.Module = NVFP4Linear(
torch.empty(
out_features,
in_features // 2,
dtype=torch.uint8,
device="meta",
),
torch.empty(
out_features,
in_features // 16,
dtype=torch.float8_e4m3fn,
device="meta",
),
torch.empty((), dtype=torch.float32, device="meta"),
in_features=in_features,
out_features=out_features,
w4a16_max_m=w4a16_max_m,
)
elif precision == "mxfp8_w8a8":
replacement = MXFP8Linear(
torch.empty(
out_features,
in_features,
dtype=torch.float8_e4m3fn,
device="meta",
),
torch.empty(
in_features // 128,
out_features,
dtype=torch.int32,
device="meta",
),
in_features=in_features,
out_features=out_features,
)
else:
raise ValueError(f"Unsupported precision record for {name}: {precision}")
parent_name, attribute = name.rsplit(".", 1)
setattr(model.get_submodule(parent_name), attribute, replacement)
@torch.inference_mode()
def load_mixed_nvfp4_checkpoint(
path: str | Path,
*,
device: str | torch.device = "cuda:0",
max_length: int = 3072,
verify_checksums: bool = False,
) -> DualARTransformer:
"""Load a mixed checkpoint without materializing its BF16 source projections."""
path = Path(path)
metadata = json.loads((path / "quantization.json").read_text())
if metadata.get("format") != CHECKPOINT_FORMAT:
raise ValueError(f"Unsupported checkpoint format: {metadata.get('format')}")
device = torch.device(device)
if device.type != "cuda" or torch.cuda.get_device_capability(device)[0] != 12:
raise RuntimeError("This mixed NVFP4/MXFP8 artifact currently requires sm_120")
if verify_checksums:
for filename, record in metadata["checksums"].items():
file_path = path / filename
if file_path.stat().st_size != int(record["bytes"]):
raise RuntimeError(f"Size mismatch for {filename}")
if _sha256(file_path) != record["sha256"]:
raise RuntimeError(f"SHA256 mismatch for {filename}")
conversion = metadata["conversion"]
records = conversion["records"]
if len(records) != 180:
raise ValueError(f"Expected 180 mixed projection records, found {len(records)}")
if int(conversion["correction_parameters"]) != 0:
raise ValueError("This loader intentionally rejects correction-bearing artifacts")
config = BaseModelArgs.from_pretrained(str(path))
config.max_seq_len = max_length
with torch.device("meta"):
model = DualARTransformer(config)
model.tokenizer = FishTokenizer.from_pretrained(path)
for record in records:
_install_empty_projection(
model,
record,
w4a16_max_m=int(conversion["w4a16_max_m"]),
)
index_path = path / "model.safetensors.index.json"
if index_path.is_file():
index = json.loads(index_path.read_text())
shard_names = sorted(set(index["weight_map"].values()))
else:
shard_names = ["model.safetensors"]
expected_keys = set(model.state_dict())
loaded_keys: set[str] = set()
for shard_name in shard_names:
shard = load_file(path / shard_name, device="cpu")
unexpected = set(shard) - expected_keys
if unexpected:
raise RuntimeError(
f"Unexpected checkpoint tensors in {shard_name}: {sorted(unexpected)[:5]}"
)
model.load_state_dict(shard, strict=False, assign=True)
loaded_keys.update(shard)
missing = expected_keys - loaded_keys
if missing:
raise RuntimeError(f"Missing checkpoint tensors: {sorted(missing)[:5]}")
# These buffers are non-persistent, so reconstruct them after the meta load.
model.freqs_cis = precompute_freqs_cis(
config.max_seq_len,
config.head_dim,
config.rope_base,
)
model.causal_mask = torch.tril(
torch.ones(config.max_seq_len, config.max_seq_len, dtype=torch.bool)
)
model.fast_freqs_cis = precompute_freqs_cis(
config.num_codebooks,
config.fast_head_dim,
config.rope_base,
)
model = model.to(device=device).eval()
sampling = metadata.get("qualified_sampling", {})
model.fixed_temperature = torch.tensor(
sampling.get("temperature", 1.0), device=device, dtype=torch.float
)
model.fixed_top_p = torch.tensor(
sampling.get("top_p", 0.85), device=device, dtype=torch.float
)
model.fixed_repetition_penalty = torch.tensor(1.5, device=device, dtype=torch.float)
model._cache_setup_done = False
nvfp4_count = sum(isinstance(module, NVFP4Linear) for module in model.modules())
mxfp8_count = sum(isinstance(module, MXFP8Linear) for module in model.modules())
if (nvfp4_count, mxfp8_count) != (60, 120):
raise RuntimeError(
f"Expected 60 NVFP4 and 120 MXFP8 modules, got {nvfp4_count}/{mxfp8_count}"
)
meta_tensors = [
name for name, tensor in model.state_dict().items() if tensor.device.type == "meta"
]
if meta_tensors:
raise RuntimeError(f"Checkpoint left meta tensors: {meta_tensors[:5]}")
return model