Image-Text-to-Text
Transformers
Safetensors
deepseek_v41
text-generation
nvfp4
fp4
deepseek
Mixture of Experts
multimodal
libertai
8-bit precision
fp8
Instructions to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4")# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4
- SGLang
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4 with Docker Model Runner:
docker model run hf.co/LibertAIDAI/DeepSeek-V4.1-Flash-NVFP4
Final card and build scripts: measured numbers, Engram caveat, pruning analysis
Browse files- dsv41_fp4_stream.py +129 -2
dsv41_fp4_stream.py
CHANGED
|
@@ -50,7 +50,7 @@ Source tensor naming (flat, no "model." prefix, "ffn" not "mlp"):
|
|
| 50 |
layers.{L}.engram.embed.scale e8m0 [rows, 8]
|
| 51 |
mtp.{M}.ffn.experts.{E}... same as backbone
|
| 52 |
"""
|
| 53 |
-
import argparse, json, os, re, shutil, sys, time
|
| 54 |
import torch
|
| 55 |
from safetensors import safe_open
|
| 56 |
from safetensors.torch import save_file
|
|
@@ -97,7 +97,8 @@ def quantize_e2m1_codes(x: torch.Tensor) -> torch.Tensor:
|
|
| 97 |
code, matching what the FP4 hardware conversion does.
|
| 98 |
"""
|
| 99 |
mag = x.abs().clamp(max=FP4_MAX)
|
| 100 |
-
|
|
|
|
| 101 |
# midpoints between consecutive grid points: .25 .75 1.25 1.75 2.5 3.5 5.0
|
| 102 |
mid = (grid[1:] + grid[:-1]) / 2
|
| 103 |
code = torch.bucketize(mag, mid) # 0..7
|
|
@@ -200,6 +201,124 @@ def quantize_engram_to_fp4(f, name, sname, rows, cols, chunk_rows=1 << 20, devic
|
|
| 200 |
|
| 201 |
# ------------------------------------------------------------------ shard loop
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
DEVICE = "cpu"
|
| 204 |
|
| 205 |
|
|
@@ -280,6 +399,14 @@ def main():
|
|
| 280 |
sp = os.path.join(args.src, s)
|
| 281 |
if not os.path.exists(sp):
|
| 282 |
print(f" SKIP (absent) {s}", flush=True); continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
wmap, tb = process_shard(sp, os.path.join(args.out, s), do_e, do_g, stats)
|
| 284 |
full_map.update(wmap); total += tb
|
| 285 |
cos = (f" engram_cos={stats['engram_cos_sum']/stats['engram_cos_n']:.6f}"
|
|
|
|
| 50 |
layers.{L}.engram.embed.scale e8m0 [rows, 8]
|
| 51 |
mtp.{M}.ffn.experts.{E}... same as backbone
|
| 52 |
"""
|
| 53 |
+
import argparse, json, os, re, shutil, struct, sys, time
|
| 54 |
import torch
|
| 55 |
from safetensors import safe_open
|
| 56 |
from safetensors.torch import save_file
|
|
|
|
| 97 |
code, matching what the FP4 hardware conversion does.
|
| 98 |
"""
|
| 99 |
mag = x.abs().clamp(max=FP4_MAX)
|
| 100 |
+
# boundaries must live on x's device -- torch.bucketize will not cross devices
|
| 101 |
+
grid = FP4_TABLE[:8].to(x.device) # 8 magnitudes, ascending
|
| 102 |
# midpoints between consecutive grid points: .25 .75 1.25 1.75 2.5 3.5 5.0
|
| 103 |
mid = (grid[1:] + grid[:-1]) / 2
|
| 104 |
code = torch.bucketize(mag, mid) # 0..7
|
|
|
|
| 201 |
|
| 202 |
# ------------------------------------------------------------------ shard loop
|
| 203 |
|
| 204 |
+
|
| 205 |
+
# ------------------------------------------- engram shard: stream straight to disk
|
| 206 |
+
|
| 207 |
+
ST_DTYPE = {torch.uint8: ("U8", 1), torch.int8: ("I8", 1),
|
| 208 |
+
torch.float8_e4m3fn: ("F8_E4M3", 1), torch.float8_e8m0fnu: ("F8_E8M0", 1),
|
| 209 |
+
torch.bfloat16: ("BF16", 2), torch.float16: ("F16", 2),
|
| 210 |
+
torch.float32: ("F32", 4), torch.int32: ("I32", 4), torch.int64: ("I64", 8)}
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def write_engram_shard(src_path, dst_path, device="cpu", chunk_rows=1 << 20):
|
| 214 |
+
"""Quantize and write an Engram shard without ever holding the table in RAM.
|
| 215 |
+
|
| 216 |
+
The obvious implementation -- build the packed tensor, hand the dict to save_file --
|
| 217 |
+
needs ~47 GiB resident for the weights alone, and on a GB10 the GPU allocator draws on
|
| 218 |
+
that same pool. It got OOM-killed at 83%. So we emit the safetensors container by hand:
|
| 219 |
+
the format is an 8-byte little-endian header length, that many bytes of JSON naming
|
| 220 |
+
each tensor's dtype/shape/byte-range, then the raw buffers back to back. Every offset
|
| 221 |
+
is known before a single value is computed, so the packed table can be streamed to the
|
| 222 |
+
file a chunk at a time.
|
| 223 |
+
|
| 224 |
+
Scales are the one thing still buffered (~3 GB for 384M rows x 8) because they are
|
| 225 |
+
produced by the same pass that produces the packed rows but live elsewhere in the file.
|
| 226 |
+
|
| 227 |
+
Peak memory is one chunk plus the scale buffer.
|
| 228 |
+
"""
|
| 229 |
+
stats = {"engram": 0, "pass": 0, "cos_sum": 0.0, "cos_n": 0}
|
| 230 |
+
with safe_open(src_path, framework="pt") as f:
|
| 231 |
+
keys = list(f.keys())
|
| 232 |
+
emb = [k for k in keys if ENGRAM_RE.match(k)]
|
| 233 |
+
consumed = set()
|
| 234 |
+
plan = [] # (name, dtype_str, shape, kind, src)
|
| 235 |
+
for name in emb:
|
| 236 |
+
base = name[:-len(".weight")]
|
| 237 |
+
sname = base + ".scale"
|
| 238 |
+
rows, cols = f.get_slice(name).get_shape()
|
| 239 |
+
nblk = cols // SRC_BLOCK
|
| 240 |
+
plan.append((base + ".weight", "U8", [rows, cols // 2], "stream", (name, sname)))
|
| 241 |
+
plan.append((base + ".scale", "F8_E8M0", [rows, nblk], "scale", base))
|
| 242 |
+
consumed.add(name); consumed.add(sname)
|
| 243 |
+
for name in sorted(keys):
|
| 244 |
+
if name in consumed:
|
| 245 |
+
continue
|
| 246 |
+
plan.append((name, None, list(f.get_slice(name).get_shape()), "copy", name))
|
| 247 |
+
|
| 248 |
+
# resolve dtypes for the copied tensors (cheap: they are all small here)
|
| 249 |
+
resolved = []
|
| 250 |
+
for name, dts, shape, kind, srcref in plan:
|
| 251 |
+
if kind == "copy":
|
| 252 |
+
dts = ST_DTYPE[f.get_tensor(name).dtype][0]
|
| 253 |
+
resolved.append((name, dts, shape, kind, srcref))
|
| 254 |
+
|
| 255 |
+
header, off = {}, 0
|
| 256 |
+
for name, dts, shape, kind, srcref in resolved:
|
| 257 |
+
n = 1
|
| 258 |
+
for d in shape:
|
| 259 |
+
n *= d
|
| 260 |
+
nbytes = n * {"U8": 1, "I8": 1, "F8_E4M3": 1, "F8_E8M0": 1,
|
| 261 |
+
"BF16": 2, "F16": 2, "F32": 4, "I32": 4, "I64": 8}[dts]
|
| 262 |
+
header[name] = {"dtype": dts, "shape": shape, "data_offsets": [off, off + nbytes]}
|
| 263 |
+
off += nbytes
|
| 264 |
+
header["__metadata__"] = {"format": "pt"}
|
| 265 |
+
blob = json.dumps(header).encode("utf-8")
|
| 266 |
+
pad = (-len(blob)) % 8 # keep the data 8-byte aligned
|
| 267 |
+
blob += b" " * pad
|
| 268 |
+
|
| 269 |
+
scale_buf = {}
|
| 270 |
+
with open(dst_path, "wb", buffering=1 << 22) as out:
|
| 271 |
+
out.write(struct.pack("<Q", len(blob)))
|
| 272 |
+
out.write(blob)
|
| 273 |
+
for name, dts, shape, kind, srcref in resolved:
|
| 274 |
+
if kind == "stream":
|
| 275 |
+
wname, sname = srcref
|
| 276 |
+
base = name[:-len(".weight")]
|
| 277 |
+
rows, cols = f.get_slice(wname).get_shape()
|
| 278 |
+
nblk = cols // SRC_BLOCK
|
| 279 |
+
sb = torch.empty((rows, nblk), dtype=torch.float8_e8m0fnu)
|
| 280 |
+
wsl, ssl = f.get_slice(wname), f.get_slice(sname)
|
| 281 |
+
t0 = time.time()
|
| 282 |
+
for lo in range(0, rows, chunk_rows):
|
| 283 |
+
hi = min(lo + chunk_rows, rows)
|
| 284 |
+
n = hi - lo
|
| 285 |
+
v = (wsl[lo:hi].to(device).float().view(n, nblk, SRC_BLOCK)
|
| 286 |
+
* ssl[lo:hi].to(device).float().unsqueeze(-1))
|
| 287 |
+
amax = v.abs().amax(dim=-1, keepdim=True)
|
| 288 |
+
exp = torch.ceil(torch.log2((amax / FP4_MAX).clamp(min=1e-38))).clamp(-127, 127)
|
| 289 |
+
ns = torch.pow(torch.tensor(2.0, device=v.device), exp)
|
| 290 |
+
codes = quantize_e2m1_codes(v / ns)
|
| 291 |
+
out.write(pack_e2m1(codes.view(n, cols)).to("cpu")
|
| 292 |
+
.flatten().view(torch.uint8).numpy().tobytes())
|
| 293 |
+
sb[lo:hi] = ns.squeeze(-1).to(torch.float8_e8m0fnu).to("cpu")
|
| 294 |
+
|
| 295 |
+
k = min(4096, n)
|
| 296 |
+
a = v[:k].flatten(1)
|
| 297 |
+
b = (FP4_TABLE.to(v.device)[codes[:k].long()].view(k, nblk, SRC_BLOCK)
|
| 298 |
+
* ns[:k]).flatten(1)
|
| 299 |
+
num = (a * b).sum(1); den = a.norm(dim=1) * b.norm(dim=1)
|
| 300 |
+
ok = den > 0
|
| 301 |
+
if ok.any():
|
| 302 |
+
stats["cos_sum"] += float((num[ok] / den[ok]).sum())
|
| 303 |
+
stats["cos_n"] += int(ok.sum())
|
| 304 |
+
del v, codes, ns, amax, exp, a, b, num, den, ok
|
| 305 |
+
print(f" {base}: {hi}/{rows} ({100*hi/rows:.1f}%) "
|
| 306 |
+
f"cos={stats['cos_sum']/max(stats['cos_n'],1):.6f} "
|
| 307 |
+
f"[{time.time()-t0:.0f}s]", flush=True)
|
| 308 |
+
scale_buf[base + ".scale"] = sb
|
| 309 |
+
stats["engram"] += 1
|
| 310 |
+
elif kind == "scale":
|
| 311 |
+
out.write(scale_buf.pop(srcref + ".scale")
|
| 312 |
+
.flatten().view(torch.uint8).numpy().tobytes())
|
| 313 |
+
else:
|
| 314 |
+
t = f.get_tensor(srcref).contiguous()
|
| 315 |
+
# reinterpret as raw bytes for every dtype: numpy has no bfloat16 or
|
| 316 |
+
# float8, so .numpy() is not an option on most of what passes through
|
| 317 |
+
out.write(t.flatten().view(torch.uint8).numpy().tobytes())
|
| 318 |
+
stats["pass"] += 1
|
| 319 |
+
return stats
|
| 320 |
+
|
| 321 |
+
|
| 322 |
DEVICE = "cpu"
|
| 323 |
|
| 324 |
|
|
|
|
| 399 |
sp = os.path.join(args.src, s)
|
| 400 |
if not os.path.exists(sp):
|
| 401 |
print(f" SKIP (absent) {s}", flush=True); continue
|
| 402 |
+
has_engram = any(ENGRAM_RE.match(k) for k, v in index["weight_map"].items() if v == s)
|
| 403 |
+
if has_engram and do_g:
|
| 404 |
+
st = write_engram_shard(sp, os.path.join(args.out, s), device=DEVICE)
|
| 405 |
+
stats["engram"] += st["engram"]; stats["pass"] += st["pass"]
|
| 406 |
+
stats["engram_cos_sum"] += st["cos_sum"]; stats["engram_cos_n"] += st["cos_n"]
|
| 407 |
+
print(f" {s} [streamed engram shard] engram={st['engram']} pass={st['pass']} "
|
| 408 |
+
f"cos={st['cos_sum']/max(st['cos_n'],1):.6f} [{time.time()-t0:.0f}s]", flush=True)
|
| 409 |
+
continue
|
| 410 |
wmap, tb = process_shard(sp, os.path.join(args.out, s), do_e, do_g, stats)
|
| 411 |
full_map.update(wmap); total += tb
|
| 412 |
cos = (f" engram_cos={stats['engram_cos_sum']/stats['engram_cos_n']:.6f}"
|