gpt-oss-120b LiteRT-LM

This repository is a conversion of openai/gpt-oss-120b to LiteRT-LM, written one layer at a time with the LiteRT Tensor API and packed with litert-lm-builder 0.19.1. It runs on the CPU with LiteRT-LM 0.19.1 from PyPI on a Mac with 128 GB of memory: on an Apple M4 Max it prefills 49.94 tokens/s and decodes 4.75 tokens/s at 1,024 / 256 tokens (2026-11-10). The 77.3 GB file is mapped into memory whole, and the process adds 5.5 to 7.0 GB: peak resident memory was 80.6 GB for the Python example below and 82.1 GB for the benchmark.

openai/gpt-oss-120b is OpenAI's open-weight reasoning model: 36 layers alternating sliding-window (128 tokens) and full attention, 128 experts per layer with 4 used for each token, 117B parameters with 5.1B active (source model card). It writes its reasoning in an analysis channel before the answer (the harmony format).

What differs from the source: the experts' MXFP4 weights are re-quantized to int4, and the expert activation is LiteRT-LM's GELU (tanh form) without the source's clamp at 7, because the moe op of LiteRT-LM 0.19.1 has no clamp and no SiLU. Without the clamps the computation differs noticeably in the later layers (the activation note below has the numbers). On 12 questions, greedy replies from this file got the same verdict as the source model's on all 12 (Accuracy).

Available Artifacts

File Quantization Context Size
gpt_oss_120b_mixed_int4_ekv4096.litertlm experts int4 blockwise (block 32, fp32 scales); attention q / k / v / o and lm_head int8 per channel; router, biases, sinks, norms and token table fp32; fp32 KV cache 4096 77.26 GB

Conversion Notes

  • The decoder is written with the LiteRT Tensor API one tensor at a time from the checkpoint at revision b5c939de, one layer read at a time. No fp32 copy of the whole model is made.
  • Each layer's 128 experts are one TFLite custom op moe, which LiteRT runs with XNNPACK on the CPU: only the 4 routed experts are computed for each token. The router (with its bias), the top 4 and the softmax over their 4 logits are ordinary ops in the graph.
  • Runtime floor: LiteRT-LM 0.19.0 (min_runtime_version in the file's metadata). The moe op of the experts declares activation='gelu_tanh'; the GPU moe kernel of LiteRT-LM 0.18 only supports activation='gelu'.
  • Experts: each MXFP4 block of 32 values is re-quantized to int4 with an fp32 scale chosen per block (the least squared error over a grid of candidates; a block whose values fit int4 is exact). Against the MXFP4 values, the relative RMS error is 5.8 to 6.6 % per expert tensor (gate, up and down of the 36 layers, 108 tensors; mean 6.0 %). Int4 block scales below 2^-14 are raised to 2^-14 (446 blocks).
  • The experts' biases: the moe op has no bias input, so each expert's input gets one more block of 32 columns that carries a constant (1/1024) and the biases (2,880 → 2,912 inputs; the expert weights grow by 2.2 %). This is exact.
  • The activation: the source computes (clamp(up, -7, 7) + 1) · g · σ(1.702 g) with g = min(gate, 7). This file computes (up + 1) · GELU_tanh(gate): x · σ(1.702 x) is replaced by GELU in its tanh form (the largest difference between the two is 0.021, at x = 2.29) and the clamps are dropped. The clamps matter, more so in the later layers. The activation was compared with the source's clamped SwiGLU on the source's Transformers code in float32, over all 36 layers and three conversations with the source model's replies (718 tokens, 413 of them reply positions), once as written and once with this file's activation (the MXFP4 weights unchanged): the share of routed (token, expert) pairs that reach a clamp grows from 4 % in layer 0 to 48 % in layer 11, and is 72 to 100 % in every layer from 14 on. With this file's activation, the hidden state differs from the source's by 1.4 to 5.5 % (relative L2) in layers 0 to 5 and by 21 to 66 % from layer 6 on. At the output, that variant still picks the source's next token at 399 of the 413 reply positions (96.6 %, teacher-forced on the source's replies); this file adds the int4 experts on top. The Accuracy section compares whole replies.
  • Attention: q / k / v / o with their biases, the per-head sink logits, the 128-token window on the even layers (a mask), and YaRN RoPE (factor 32, the source's settings) are written as in the source's Transformers code.
  • The residual add after each moe op is written as (x + moe) + moe * 0 (numerically exact), so a GPU delegate cannot fuse the add into the moe kernel.
  • Signatures prefill_128, prefill_1024 and decode; the KV cache holds 4,096 tokens.
  • The chat template is the source's chat_template.jinja with four edits for LiteRT-LM: .items() → |items (the runtime's template engine has no Python methods); every earlier assistant turn takes the source's inference form (final + <|end|>, the analysis dropped), as LiteRT-LM needs each turn's render to start with the previous one; message content as a list of typed parts; a guard for an empty message list. For 7 conversations, with and without tools, it gives the same text as the source's template through Transformers and through LiteRT-LM 0.19.1's own template engine.
  • Reply channels: the metadata declares the harmony headers as channels, so a reply's content is the final message's text alone. The analysis goes to channels["analysis"] and reasoning_content (and is left out of the KV cache before the next turn, as LiteRT-LM does for a reasoning channel); the headers (<|start|>assistant<|channel|>final<|message|> and the tool-call headers) go to their own channels.
  • Reasoning effort: the template's default medium.
  • Tool calls: the metadata type is generic_model, so LiteRT-LM returns a tool call as text: the app reads the function name from the commentary_header or tool_header channel and the arguments from the content, and the reply stops at <|call|> (stop token 200012). To continue after a tool call, start a new conversation with the history, including the assistant's tool_calls and the tool's result.
  • The sampler: the source's generation_config.json sets do_sample and no temperature, top_p or top_k, so the files carry Transformers' defaults for that case (type TOP_P: top_k 50, top_p 1.0, temperature 1.0). Stop tokens: 200002 <|return|>, 199999 <|endoftext|>, 200012 <|call|>.
  • The conversion steps and how each part was checked: john-rocky/hf-to-litertlm: gpt_oss_120b_work.

Performance

Speed still being tuned (updates follow).

Apple M4 Max (128 GB, macOS 27.0), LiteRT-LM 0.19.1 from PyPI, 1,024 prefill tokens and 256 decode tokens, measured on 2026-11-10.

File Backend Prefill (tok/s) Decode (tok/s) Time to first token Peak footprint
gpt_oss_120b_mixed_int4_ekv4096.litertlm CPU, 8 threads 49.94 4.75 20.7 s 6.98 GB
  • Command: litert-lm benchmark gpt_oss_120b_mixed_int4_ekv4096.litertlm --backend cpu -p 1024 -d 256 --max-num-tokens 4096 --runs 1 --cache no --cpu-thread-count 8 (one run after the warm-up run).
  • Peak footprint: from /usr/bin/time -l. It leaves out the pages of the mapped file; the peak resident memory, which counts them, was 82.1 GB.
  • The file's pages were already in memory when the run started.
  • The GPU has not been measured yet. No phone was tried: the file is 77.3 GB.

Accuracy

12 questions and a 3-turn conversation on the CPU, greedy on both sides, up to 2,048 generated tokens, a new conversation for each question, the source's chat template with its default system message (reasoning effort medium) and no tools. The questions: two sums, two facts, an opposite word, a French translation, 0.9 vs 0.11, a rhyme, and four code tasks (two Python functions, a bug fix and a SQL query) run on test cases. The 3 turns ask for a Python function scale(xs, k), then a side question, then an edit of the function. Each reply's final channel is judged by the same function on both sides.

File Backend Same verdict as the source model Ends with a stop token 3 turns
gpt_oss_120b_mixed_int4_ekv4096.litertlm CPU, 8 threads 12 / 12 12 / 12 pass
  • In the 3-turn conversation, the source model's third reply renamed the function, so its tests could not call it; this file's third reply kept the name and passed its 4 tests.
  • The source model's replies came from MLX (mlx_lm 0.31.3) running the source's MXFP4 experts as MXFP4 and every other parameter in float32. Teacher-forced through the source's Transformers code in float32, that generator's next token matched at all 413 positions of three of its replies (questions 1, 9 and 10, up to 256 tokens each).

Runtime notes

  • Long replies. With LiteRT-LM 0.19.1, a blocking send_message that is still generating after 10 minutes fails with DEADLINE_EXCEEDED (the engine's default timeout). This file decodes about 4.8 tokens/s on the CPU above, so 10 minutes is about 2,800 tokens, reasoning included. Use send_message_async, or cap max_output_tokens.
  • A message that does not fit. On LiteRT-LM 0.19.1, after a message fails because it does not fit in the context window (4,096 tokens), every later message in that conversation fails too; start a new conversation.

Run

pip install litert-lm==0.19.1
hf download litert-community/gpt-oss-120b gpt_oss_120b_mixed_int4_ekv4096.litertlm --local-dir .
import litert_lm

engine = litert_lm.Engine(
    "gpt_oss_120b_mixed_int4_ekv4096.litertlm",
    backend=litert_lm.Backend.CPU(thread_count=8),
    cache_dir=":nocache",
)
conversation = engine.create_conversation()
reply = conversation.send_message("What is the capital of Japan? Answer briefly.")
print("Reasoning:", reply.channels.get("analysis", ""))
print("Answer:", reply)

Output on the Mac above (the file's sampler, top-k 50 at temperature 1.0, so the text varies from run to run):

Reasoning: User asks: "What is the capital of Japan? Answer briefly." Provide brief answer: Tokyo.
Answer: Tokyo.

License

Apache License 2.0, the license of openai/gpt-oss-120b; see also the source's usage policy.

Downloads last month
12
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for litert-community/gpt-oss-120b

Quantized
(136)
this model