816B Parameters on Two Mac Studios: What Meta's Muse Spark Open Actually Ships With

·11 min read·Evergreen Tools Team

On October 15, 2026, Meta released the weights for Muse Spark Open, the first time the Muse Spark line has been open since its April debut. 816 billion total parameters with about 42 billion active per token, a context window of up to 1 million tokens, and prompts that can mix text with images, audio or video, with text-only output. The starting point was Muse Spark 1.2 in August, with safety post-training layered on so the model holds up without the guardrails of Meta's own services. Meta calls it the most capable open-weight model to come out of a US lab. The comparisons are what carry information: 61.8 on Meta's own JobBench, more than double the 31.1 from Inkling, another US open model, but 76.4 on Terminal-Bench 2.1 against Kimi K3 at 85 and both GLM 5.3 and Qwen3.8 ahead. On tool use it flips again, where 89.2 on MCP-Atlas beat every rival Meta listed. Picking an open-weight model is never about picking the strongest one, it is about picking the tier of capability you can control, and this release draws that line clearly.

A close-up of a logic board

Two desktop machines with a terabyte between them is the demo. Production is a different conversation.

Where this release actually sits

Three things are easier to read separately. First, the capability tier: Meta states plainly that its most capable systems remain closed, with Muse Wonder reachable only through the API and Muse Code, while Muse Spark Open is August's Muse Spark 1.2 plus safety post-training, released as weights. Second, reach: the weights are on Hugging Face and in the AWS, Google and Microsoft model catalogs, and seven inference providers offered hosted access on day one, including Groq and Fireworks AI. Meta is not expecting everyone to run it themselves. Third, the licence: a new Muse Model License. Stack those up and you get a very specific position. It does not promise the best model. It promises one you can hold yourself, with no fee for adapting it.

# The Muse Model License has thresholds in it. Encode them, do not remember them.
from dataclasses import dataclass

PROHIBITED = ("military", "weapons", "critical_infrastructure")

@dataclass
class UseCase:
    name: str
    is_hosted_service: bool = False
    trailing_12m_gross_revenue_usd: float = 0.0
    sector: str = "general"
    ships_inside_own_product: bool = True
    displays_attribution_label: bool = False

REVENUE_THRESHOLD = 20_000_000

def assess(uc):
    findings = []
    if uc.sector in PROHIBITED:
        findings.append(("BLOCK", f"sector '{uc.sector}' is excluded by the licence"))
    if uc.is_hosted_service and uc.trailing_12m_gross_revenue_usd > REVENUE_THRESHOLD:
        findings.append(("AGREEMENT_REQUIRED",
                         f"hosted service above ${REVENUE_THRESHOLD:,} trailing-12m revenue"))
    if uc.is_hosted_service and uc.trailing_12m_gross_revenue_usd <= REVENUE_THRESHOLD:
        findings.append(("OK", "hosted service under the revenue threshold, no separate agreement"))
    if not uc.is_hosted_service and uc.ships_inside_own_product:
        findings.append(("OK", "adapting or shipping in your own product carries no fee"))
    if not uc.displays_attribution_label:
        findings.append(("LABEL_REQUIRED", 'must show a "Built with Muse Spark Open" label'))
    return findings

for uc in [
    UseCase("internal coding agent", sector="general", displays_attribution_label=True),
    UseCase("resold inference API", is_hosted_service=True, trailing_12m_gross_revenue_usd=48_000_000),
    UseCase("small hosted wrapper", is_hosted_service=True, trailing_12m_gross_revenue_usd=1_200_000),
    UseCase("defence analytics", sector="military", displays_attribution_label=True),
]:
    print(uc.name)
    for status, note in assess(uc):
        print(f"   {status:18} {note}")

816B total, 42B active: one model, two different hardware problems

A sparse model has to be read as two numbers, because they map to two different bottlenecks. Total parameters decide whether it fits: about 1,520GiB in BF16, 760GiB in FP8, 380GiB in NVFP4. That is a hard wall. Active parameters decide the cost per token: roughly 42 billion activated means decode compute lands near a 42B dense model, so latency feels fine. Meta's demo sits exactly at the intersection. Two Mac Studio machines with M5 Ultra chips, about a terabyte of memory combined, linked through MLX. FP8 fills something over 70% of that pool, leaving the rest for KV cache. The interesting part is not that a Mac can run 816B. It is that the combination of small active and large total moves the self-hosting barrier from compute to memory capacity, and memory is something you can buy your way out of.

# 816B total, 42B active. Two numbers, two different hardware problems.
PRECISIONS = {"bf16": 2.0, "fp8": 1.0, "nvfp4": 0.5}

def weights_gib(total_b, precision):
    return total_b * 1e9 * PRECISIONS[precision] / (1024 ** 3)

def active_flops_per_token(active_b):
    """Rough dense-equivalent decode compute: 2 x active params per token."""
    return 2 * active_b * 1e9

print("Muse Spark Open (816B total / 42B active)")
for p in ("bf16", "fp8", "nvfp4"):
    print(f"  {p:6} weights -> {weights_gib(816, p):7.0f} GiB")
print(f"  decode compute per token -> {active_flops_per_token(42)/1e9:.0f} GFLOPs (dense-equivalent)")

print()
print("The demo setup: two Mac Studio M5 Ultra, ~1TB memory combined, linked by MLX.")
for p in ("fp8", "nvfp4"):
    share = weights_gib(816, p) / 1024
    print(f"  {p:6} fills {share*100:5.1f}% of a 1TB pool, leaving {1024 - weights_gib(816, p):6.0f} GiB for KV cache")

# Active parameters set how fast each token decodes; total parameters set whether
# the thing fits at all. A 42B decode profile inside an 816B footprint is the
# whole trade: good latency, unforgiving capacity.
Server racks in a data centre

Seven providers offered hosted access on day one, which tells you who Meta expects to serve it.

Read the thresholds in the licence, not the summary

Meta describes the Muse Model License as limited commercial terms, but the decisions live in specific numbers. Adapting the model, or shipping it inside your own software, carries no fee. Selling it as a hosted service once the company has passed $20 million in gross revenue over a 12-month period requires a separate agreement, and note that both conditions must hold. Products have to display a Built with Muse Spark Open label. Military and critical infrastructure uses are excluded. The practical risk here is not at large companies. It is in two middle cases: a hosted wrapper whose revenue has just crossed the threshold, and a small product that forgot the label. That argues for encoding the thresholds in CI rather than remembering them, because licence terms are not a thing anyone keeps in their head accurately.

# Video input is the expensive modality. Budget it before you promise anything.
def frame_budget(duration_s, fps, tokens_per_frame, context=1_000_000, reserve=0.15):
    frames = duration_s * fps
    usable = context * (1 - reserve)          # leave room for prompt and output
    per_clip = frames * tokens_per_frame
    return {
        "frames": round(frames),
        "tokens": round(per_clip),
        "clips_in_one_context": int(usable // per_clip) if per_clip else None,
        "fits": per_clip <= usable,
    }

print("1M-token context, 15% held back for prompt and output:")
for dur, fps, tpf in ((10, 1, 900), (60, 1, 900), (60, 2, 900), (600, 1, 900)):
    print(f"  {dur:>4}s @ {fps} fps, {tpf} tok/frame ->", frame_budget(dur, fps, tpf))

# Meta's own demo was different in kind: it walked footage frame by frame to pick
# and crop shots, which is an offline batch job, not a single-context problem.
# Decide which of the two you are actually building.

Omnimodal input, where video is the expensive column

Input accepts text, images, audio and video. Output is text only. That asymmetry is the cost structure in miniature. Text and image costs are linear and predictable. Video is not. At one frame per second and roughly 900 tokens a frame, ten seconds is about 9,000 tokens and a minute is about 54,000. Two frames per second doubles that to roughly 108,000 tokens for the same minute. A 1M-token context sounds enormous until you hold back 15% for the prompt and the output, at which point long video forces a choice between sparser sampling and splitting the work into segments. Meta's own demo makes the distinction: it walked through footage in DaVinci Resolve frame by frame to pick and crop shots for vertical video. That is an offline batch job, not a single-context problem. Decide which of the two you are building before you size anything.

# 220 tools across 36 MCP servers. Routing is the hard part, not calling.
from dataclasses import dataclass

@dataclass
class Tool:
    server: str
    name: str
    description: str

TOOLS = [
    Tool("github-mcp", "create_pull_request", "Open a pull request from a branch"),
    Tool("github-mcp", "list_issues", "List issues filtered by label and state"),
    Tool("postgres-mcp", "run_query", "Execute a read-only SQL query"),
    Tool("postgres-mcp", "describe_table", "Return column names and types for a table"),
    Tool("slack-mcp", "post_message", "Send a message to a channel"),
    Tool("fs-mcp", "read_file", "Read a text file from the workspace"),
]
STOPWORDS = {"a", "an", "the", "to", "of", "in", "and", "for", "by", "with", "from"}

def index(tools):
    inv = {}
    for t in tools:
        for w in (t.name + " " + t.description).lower().replace("_", " ").split():
            if w not in STOPWORDS:
                inv.setdefault(w, []).append((t.server, t.name))
    return inv

INV = index(TOOLS)

def route(task, k=3):
    words = [w for w in task.lower().split() if w not in STOPWORDS]
    votes = {}
    for w in words:
        for key in INV.get(w, []):
            votes[key] = votes.get(key, 0) + 1
    ranked = sorted(votes.items(), key=lambda kv: -kv[1])[:k]
    return [(name, server, score) for (server, name), score in ranked]

for task in ("list open issues in the repo", "read the config file and describe the table", "post a message"):
    print(task, "->", route(task))

# MCP-Atlas gives the model the task without naming the tools, which is the same
# discipline as this: retrieve first, then call. Selection quality is what a
# 89.2 measures, not raw API competence.
A padlock on a keyboard

A model licence is a contract with thresholds in it. Read the thresholds, not the summary.

220 tools across 36 MCP servers: retrieval is the hard part

The 89.2 on MCP-Atlas deserves its own paragraph because of what that benchmark withholds. The tasks are plain language and the tools are not named. The model has to work out which of 220 tools across 36 live Model Context Protocol servers to call, and in what order. That is the same situation a real agent is in, and it changes where the bottleneck sits: once you have hundreds of tools, the problem is no longer whether the model can use a tool, it is whether it can retrieve the right one. The engineering answer is unglamorous. Build an inverted index over tool names and descriptions, retrieve and rank candidates for the task, put only the top few in context, and let the model make the final choice. Past a few hundred tools, the quality of that step moves success rates more than a model upgrade does.

# Long-horizon agents need a stopping rule, or a 42B decode profile runs all night.
import time
from dataclasses import dataclass, field

@dataclass
class Budget:
    max_steps: int = 40
    max_seconds: float = 900.0
    max_failed_steps: int = 4
    started: float = field(default_factory=time.time)
    steps: int = 0
    failures: int = 0

    def exhausted(self):
        if self.steps >= self.max_steps:
            return "step budget"
        if time.time() - self.started >= self.max_seconds:
            return "wall clock budget"
        if self.failures >= self.max_failed_steps:
            return "consecutive failure budget"
        return None

def run_loop(plan, observe, budget):
    history = []
    while True:
        reason = budget.exhausted()
        if reason:
            return {"stopped": reason, "steps": budget.steps, "history": history}
        action = plan(history)
        result = observe(action)
        budget.steps += 1
        budget.failures = budget.failures + 1 if not result.get("ok") else 0
        history.append({"action": action, "ok": result.get("ok"), "obs": result.get("obs")})
        if result.get("done"):
            return {"stopped": "goal reached", "steps": budget.steps, "history": history}

STEPS = [{"ok": False, "obs": "test failed"}, {"ok": False, "obs": "test failed"},
         {"ok": True, "obs": "test passed"}, {"ok": True, "done": True, "obs": "ship it"}]
state = {"i": 0}

def plan(history):
    return f"attempt_{state['i']}"

def observe(action):
    i = state["i"]
    state["i"] += 1
    return STEPS[i] if i < len(STEPS) else {"ok": True, "done": True}

print(run_loop(plan, observe, Budget()))

# Self-correction only works if there is a stop condition that is not "the model
# decided it was finished." Give the loop a budget it cannot argue with.

Long-horizon agents need a stopping rule the model cannot argue with

Muse Spark Open is pitched at long-running agentic work: plan, act, observe, self-correct, trained on real developer workflows including multi-file refactors and long debugging sessions. The largest engineering risk in a loop like that is not capability, it is the absence of a floor. A model running at a 42B decode profile all night produces a real bill and real side effects. So the loop needs an external budget: a maximum step count, a maximum wall-clock time, and a cap on consecutive failures, with any one of them forcing an exit rather than waiting for the model to decide it is finished. This lines up with what Arena's alignment work found, where unauthorized actions and deceptive completions both climb as sessions get longer. Making the budget a non-negotiable part of the loop is much cheaper than filtering the logs afterwards. One more thing worth checking: Muse Code subscriptions default to Contributor mode, which permits training on your prompts, code and files in exchange for more usage. Check that setting before you paste a private repository into it.

# Open weights, closed flagship. Compare what you would actually be choosing between.
ITEMS = [
    {"name": "Muse Spark Open", "open": True,  "params_b": 816, "context": 1_000_000,
     "note": "weights on Hugging Face; 7 hosted providers; Muse Model License"},
    {"name": "Muse Wonder",     "open": False, "params_b": None, "context": None,
     "note": "Meta Model API and Muse Code only; $4/M input, $20/M output"},
    {"name": "Muse Wonder Full","open": False, "params_b": None, "context": None,
     "note": "Power and Maximum Muse Code plans only, not on pay-as-you-go API"},
    {"name": "Muse Glimmer",    "open": True,  "params_b": 30, "context": None,
     "note": "previous open release, August 2026"},
]

def choose(need_self_host, need_max_capability, need_training_control):
    for item in ITEMS:
        if need_max_capability and not item["open"]:
            return item["name"]
    if not need_self_host:
        return "hosted API of your choice"
    for item in ITEMS:
        if item["open"] and (item["params_b"] or 0) >= 100:
            return item["name"]
    return "Muse Glimmer"

print("max capability, no self-hosting ->", choose(False, True, False))
print("self-host, frontier-ish          ->", choose(True, False, False))
print("self-host, small footprint       ->", choose(True, False, True))

# One more thing worth flagging: Muse Code subscriptions default to Contributor
# mode, which permits training on your prompts, code and files in exchange for
# more usage. Check that setting before you paste a private repository in.

📌 Frequently Asked Questions

How do Muse Spark Open and Muse Wonder relate?

They are different products. Muse Wonder and Muse Wonder Full shipped on October 12 as closed models available only through the Meta Model API and Muse Code. Muse Spark Open shipped its weights on October 15, built from August's Muse Spark 1.2 plus safety post-training, and can be self-hosted. Meta says its most capable systems remain closed.

How big is it, and can you actually run it?

816 billion total parameters with about 42 billion active per token. All the weights have to stay resident: roughly 1,520GiB in BF16, 760GiB in FP8 or 380GiB in NVFP4. Meta's demo used two Mac Studio machines with M5 Ultra chips, about a terabyte of memory between them, linked by MLX, running Muse Code through bug fixes and new features for the open-source Mastodon client Ice Cubes.

What does the licence restrict?

A new Muse Model License that Meta describes as limited commercial terms. Adapting the model or shipping it inside your own software carries no fee. Selling it as a hosted service once the company has passed $20 million in gross revenue over a 12-month period requires a separate agreement. Products must display a Built with Muse Spark Open label, and military and critical infrastructure uses are excluded.

How good is it at coding compared with other open models?

It depends on the task. On Terminal-Bench 2.1, Kimi K3 leads at 85 and both GLM 5.3 and Qwen3.8 finish above Muse Spark Open's 76.4. On JobBench it scores 61.8, more than twice Inkling's 31.1 and well ahead of Nvidia's Nemotron 3 Ultra at 27.2. On tool use it scored 89.2 on MCP-Atlas, beating every rival Meta listed.

Where do you get the weights?

The weights can be downloaded from Hugging Face and appear in the model catalogs of AWS, Google and Microsoft. Seven inference providers offered hosted access at launch, including Groq and Fireworks AI. Meta says Muse Spark Open cleared the release thresholds in its Meta Superintelligence Scaling Framework, with a full evaluation report still to come.