Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions freevideo_engine/adaln_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import re
import shutil
import struct
import time
from . import disk_space

CONTRACT = 'minimax-h3-adaln-silu-linear-3x6-v1'
Expand Down Expand Up @@ -268,9 +269,23 @@ def restore_projections(cache, manifest):
plan = _download_plan()
prefix = remote.get('prefix', 'cache')
asset_path(cache, prefix) # Validate before constructing a remote URL.
# These files take minutes: give the page byte progress, at most once a
# second and at every file boundary.
shown = [0.]
def report(done, speed, force=False):
now = time.monotonic()
if force or now - shown[0] >= 1:
shown[0] = now
print(json.dumps(dict(event='adaln_sources_download', done_bytes=done, total_bytes=needed,
bytes_per_second=speed)), flush=True)
before = 0
report(0, 0., True)
for row in missing:
source = dict(repo=remote['repo'], revision=remote['revision'], file=prefix+'/'+row['file'])
network.download(network.model_urls(plan, source), asset_path(cache, row['file']), row['sha256'],
lambda done, size, speed, **_: report(before + min(done, row['bytes']), speed),
size=row['bytes'], headers_for=model_headers, network=plan, category='models',
stall_seconds=30, slow_seconds=15, low_speed_limit=64 * 1024)
before += row['bytes']
report(before, 0., True)
return rows
3 changes: 3 additions & 0 deletions freevideo_engine/adaptive.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ def classify_failure(error, metrics=None):
else:
kind, outcome, retry = 'code_error', 'code_failure', False
result = dict(kind=kind, outcome=outcome, retryable=retry, reason=str(error))
if isinstance(details.get('runtime_libraries'), dict):
# Found by the worker that loaded the libraries; the parent cannot see them.
result['runtime_libraries'] = details['runtime_libraries']
allocation = getattr(error, 'freevideo_allocation', details.get('allocation'))
if isinstance(allocation, dict) and type(allocation.get('fp8_ff_activation_stash_bytes')) is int and allocation['fp8_ff_activation_stash_bytes'] > 0:
result['allocation'] = {'fp8_ff_activation_stash_bytes': allocation['fp8_ff_activation_stash_bytes']}
Expand Down
30 changes: 30 additions & 0 deletions freevideo_engine/backends/mps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,42 @@
Model loading/streaming are integrated separately. Unsupported services fail
explicitly; model operations never route to CUDA or implicit CPU fallback.
"""
import ctypes
import math
import os
import sys

from .base import BackendCapabilities, DeviceBackend


def recommended_working_set_bytes():
"""Metal's default-device working-set recommendation without a Torch allocator."""
if sys.platform != 'darwin':
return None
try:
objc = ctypes.CDLL('/usr/lib/libobjc.A.dylib')
metal = ctypes.CDLL('/System/Library/Frameworks/Metal.framework/Metal')
metal.MTLCreateSystemDefaultDevice.argtypes, metal.MTLCreateSystemDefaultDevice.restype = [], ctypes.c_void_p
objc.sel_registerName.argtypes, objc.sel_registerName.restype = [ctypes.c_char_p], ctypes.c_void_p
message = objc.objc_msgSend
message.argtypes, message.restype = [ctypes.c_void_p, ctypes.c_void_p], ctypes.c_uint64
working_set = objc.sel_registerName(b'recommendedMaxWorkingSetSize')
release = objc.sel_registerName(b'release')
if not working_set or not release:
return None
device = metal.MTLCreateSystemDefaultDevice()
if not device:
return None
try:
return int(message(device, working_set))
finally:
# The Create rule gives this probe an owned device reference.
message.restype = None
message(device, release)
except Exception:
return None


def allocator_ceiling(budget, recommended, total, available, owned, reserve, explicit=None):
values = (budget, recommended, total, available, owned, reserve)
if any(type(x) not in (int, float) or not math.isfinite(x) or x < 0 for x in values):
Expand Down
15 changes: 11 additions & 4 deletions freevideo_engine/backends/mps_grouped_qkv.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
Project independent output channels per head group. Reuse original norms,
RoPE, window attention, delta scans and full-width output projections. Grouped
GEMM/text-state shapes can change rounding; native/full-output checks are required.
This adapter is not enabled by the engine default policy yet.
Int8 projections take their channel slice of the same product (bit-identical).
Planning binds this adapter only where the smallest original partition's full
Q/K/V do not fit, and for a stage retried after running out of memory.
"""
import types

Expand Down Expand Up @@ -36,9 +38,14 @@ def install(model, attention, *, head_chunk=4, row_chunk=1024, qk_prepare=None):
def project(module, value, channels, count, dim):
if not isinstance(module, torch.nn.Linear):
raise TypeError('Grouped MPS attention requires eager Linear projections')
weight = module.weight[channels]
bias = None if module.bias is None else module.bias[channels]
output = F.linear(value, weight, bias)
channel_forward = getattr(module, '_freevideo_project_channels', None)
if channel_forward is not None:
# Int8 storage: the same product as the full projection, these columns only.
output = channel_forward(value, channels)
else:
weight = module.weight[channels]
bias = None if module.bias is None else module.bias[channels]
output = F.linear(value, weight, bias)
output = apply_lora(module, value, output, channels=channels)
policy['largest_projected_elements'] = max(policy['largest_projected_elements'], output.numel())
return output.unflatten(-1, (count, dim))
Expand Down
26 changes: 26 additions & 0 deletions freevideo_engine/backends/mps_int8.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,31 @@ def forward(module, value):
stats['int8_forwards'] += 1
return output.reshape(*value.shape[:-1], module.out_features)

def project_channels(module, value, channels):
# Grouped Q/K/V projections take one head group's output channels. The
# int8 product computes every output element independently over the
# same quantized input row, so a channel slice equals those columns of
# the full forward, and Q/K/V still share one input quantization.
weight = module.weight
bias = None if module.bias is None else module.bias[channels]
if weight.dtype == torch.int8:
if value.dtype != torch.bfloat16 or value.device.type != 'mps' or weight.is_meta:
raise RuntimeError('Int8 weights need a loaded layer and BF16 MPS inputs')
pair = (weight, module.weight_scale)
elif value.dtype != torch.bfloat16 or value.device.type != 'mps' or weight.is_meta:
return torch.nn.functional.linear(value, weight[channels], bias)
else:
pair = quantized_weight(weight)
rows = value.reshape(-1, value.shape[-1])
if rows.stride(1) != 1:
rows = rows.contiguous()
output = matmul(quantized_input(rows, getattr(module, '_freevideo_convrot', False)),
pair[0][channels], pair[1][channels])
if bias is not None:
output += bias
stats['int8_channel_forwards'] = stats.get('int8_channel_forwards', 0) + 1
return output.reshape(*value.shape[:-1], output.shape[-1])

for name, module in model.named_modules():
# Only matrices the prepared model stores as int8 get int8 placeholders;
# every other Linear keeps its BF16 storage and ordinary forward.
Expand All @@ -459,6 +484,7 @@ def forward(module, value):
module._freevideo_convrot = True
stats['convrot_modules'] += 1
module.forward = types.MethodType(forward, module)
module._freevideo_project_channels = types.MethodType(project_channels, module)
stats['modules'] += 1
return dict(implementation='mps-int8-tensorops-v1', storage=storage or 'quantize-on-load',
modules=stats['modules'])
Loading