This guide is for engineers, ML architects, and operators who need to understand how the Grok-1 reference implementation is built — the model architecture, the parallelism strategy, the checkpoint format, and the operational characteristics of running a 314B-parameter Mixture-of-Experts model. It is written to be exhaustive enough that a competent engineer could reconstruct or extend the implementation from this document alone.
1. Executive Summary
xai-org/grok-1 is the open-weights release of xAI's Grok-1 large language model, accompanied by a compact JAX + Haiku reference implementation. The repository's purpose is narrow and deliberate: load the released 314B-parameter checkpoint and run a forward/sampling pass to validate model correctness.
| Attribute | Value |
|---|---|
| Model family | Grok-1 (autoregressive transformer LM) |
| Total parameters | 314B |
| Architecture | Mixture of Experts (8 experts, 2 active per token) |
| Framework | JAX 0.4.25 + dm-haiku 0.0.12 |
| Source files | 4 Python modules (model.py, runners.py, checkpoint.py, run.py) |
| Total source size | ~77 KB |
| Parallelism | SPMD sharding via JAX Mesh + shard_map/pjit |
| Weight format | 8-bit quantized (QuantizedWeight8bit) |
| License | Apache 2.0 (code and weights) |
| Source commit | 7050ed2 (2024-03-19) |
Important
This is inference reference code, not a serving stack. There is no training loop, no fine-tuning, no REST/gRPC server, no CLI, and no autoscaling. The MoE implementation is explicitly unoptimized — the README states it "was chosen to avoid the need for custom kernels to validate the correctness of the model." Treat throughput as a non-goal of this repository. Any production deployment requires building substantial infrastructure around this core.
Note
Because the user-facing surface is a single hard-coded example (
run.py), most of the engineering value here is in the architecture the code reveals. This guide therefore weights heavily toward the model internals, sharding, and checkpoint subsystems.
2. Architecture Overview
High-level data flow
run.py (main)
│ builds LanguageModelConfig(TransformerConfig(...))
▼
InferenceRunner.initialize()
│ ┌───────────────────────────────────────────┐
│ │ make_mesh((1,8),(1,1)) → jax Mesh │
│ │ hk.transform(forward / sample / prefill) │
│ │ jax.eval_shape → partition rules → sharding │
│ │ checkpoint.restore() → 314B params on mesh │
│ └───────────────────────────────────────────┘
▼
InferenceRunner.run() (Python generator)
│ yields ready → receives Request(prompt,...)
▼
tokenizer.encode → prefill_memory (pjit) → sample_step (pjit) loop
│ per-step: LanguageModel.__call__ → sample_token
▼
tokenizer.decode → output string
Component inventory
| Module | Responsibility | Key symbols |
|---|---|---|
model.py |
The neural network: attention, MoE, transformer stack, embeddings, quantized weights, partition rules | LanguageModel, Transformer, DecoderLayer, MultiHeadAttention, MoELayer, Router, DenseBlock, RMSNorm, RotaryEmbedding, InOutEmbed, QuantizedWeight8bit, TransformerConfig, LanguageModelConfig |
runners.py |
Orchestration: device mesh, JIT compilation, KV-cache memory, the sampling loop | InferenceRunner, ModelRunner, sample_token, top_p_filter, make_mesh, sample_from_model, Request, SampleSettings, SampleOutput |
checkpoint.py |
Loading sharded tensors from disk into the distributed state | restore, load_tensors, replace_with_load_state, fast_unpickle, copy_to_shm |
run.py |
Entry point that wires the model config to the runner and prints one completion | main |
Note
The dependency graph is acyclic and shallow:
run.py → runners.py → {model.py, checkpoint.py}, andcheckpoint.py → model.py(forQuantizedWeight8bit). All 27 classes and 80 functions extract cleanly because the codebase is idiomatic Python/Haiku.
3. The Model (model.py)
Configuration objects
The model is described declaratively by two dataclasses, instantiated in run.py:
LanguageModelConfig — the outer wrapper:
| Field | Example value | Meaning |
|---|---|---|
vocab_size |
131072 (128 × 1024) |
Token vocabulary size |
pad_token |
0 |
Padding token id |
eos_token |
2 |
End-of-sequence token id |
sequence_len |
8192 |
Maximum context length |
embedding_init_scale |
1.0 |
Embedding init scaling |
embedding_multiplier_scale |
78.38367... |
Multiplier applied to input embeddings |
output_multiplier_scale |
0.57735... |
Multiplier applied to output logits |
fprop_dtype |
jnp.bfloat16 |
Forward-pass compute dtype |
shard_embeddings |
True |
Whether to shard the embedding matrix |
TransformerConfig — the inner transformer:
| Field | Example value | Meaning |
|---|---|---|
emb_size |
6144 (48 × 128) |
Model hidden dimension |
widening_factor |
8 |
FFN expansion factor |
key_size |
128 |
Per-head key/query dimension |
num_q_heads |
48 |
Query heads (grouped-query attention) |
num_kv_heads |
8 |
Key/value heads |
num_layers |
64 |
Decoder layers |
attn_output_multiplier |
0.08838... |
Attention logit scaling |
num_experts |
8 |
MoE experts per layer |
num_selected_experts |
2 |
Experts evaluated per token |
shard_activations |
True |
Whether activations are model-sharded |
data_axis / model_axis |
"data" / "model" |
Mesh axis names for sharding |
Tip
These dataclass fields are the configuration reference for the model — there are no environment variables or config files. To change model behavior you edit these kwargs in
run.py. The values above are the released Grok-1 configuration; changing them requires a matching checkpoint.
Attention: grouped-query + RoPE
MultiHeadAttention implements grouped-query attention (GQA): 48 query heads are reshaped to share 8 key/value heads (query_heads.reshape(b, t, kv_h, h // kv_h, d)). Salient details:
- Rotary embeddings (
RotaryEmbedding, RoPE, base exponent10000) are applied to both query and key heads, offset by the current KV-cache step for correct positions during incremental decoding. - Logit soft-capping: attention logits are scaled by
attn_output_multiplierand then passed throughmax_attn_val * tanh(logits / max_attn_val)withmax_attn_val = 30.0, bounding them before softmax. - Softmax in fp32: attention weights are always computed in
float32for numerical stability, then cast back to the forward dtype. - Causal masking: a lower-triangular mask is combined with the padding mask; masked positions are set to
-1e30before softmax.
Mixture of Experts (MoELayer + Router)
Each decoder layer with num_experts > 1 routes tokens through a sparse MoE block:
Router._compute_routing_probcomputes routing logits in fp32 and applies softmax → per-token expert probabilities.jax.lax.top_kselects the topnum_selected_experts(2) experts per token.- The expert FFN (
DenseBlock, a gated GeLU MLP:gelu(linear(x)) * linear_v(x) → linear_1) isvmap-ped across all 8 experts viahk.experimental.transparent_lift. - Two
shard_map-wrapped matmuls (moe_slow_matmul1/moe_slow_matmul2) compute expert outputs and combine them with one-hot selection masks, scaled by the routing gate.
Warning
The functions are literally named
moe_slow_matmul1andmoe_slow_matmul2. As the README states, this MoE path is intentionally inefficient — it computes dense expert matmuls and selects with one-hot masks rather than gathering, to avoid custom kernels. This is correct but compute-wasteful; it is the single biggest performance gap versus a production MoE kernel.
Note
The FFN hidden size is computed by
ffn_size(emb_size, widening_factor)asint(widening_factor * emb_size) * 2 // 3, rounded up to a multiple of 8 — the standard "2/3 widening" used for gated (SwiGLU-style) MLPs.
Transformer stack and language model head
DecoderLayerapplies pre-/post-norm RMSNorm around the attention and FFN/MoE blocks with residual connections (h += h_attn,h += h_dense), each wrapped in sharding constraints.Transformer.__call__loops over all 64 layers, threading the per-layerKVMemorythrough for incremental decoding, and returns the final embeddings plus the updatedMemory.LanguageModelembeds tokens viaInOutEmbed(with tied input/output weights —in_out_embed.decode()reuses the embedding matrix transposed), applies theembedding_multiplier_scale, runs the transformer, applies a final RMSNorm, decodes to logits, and appliesoutput_multiplier_scale.
Quantized weights
QuantizedWeight8bit is a small dataclass holding weight (int8) and scales (the per-block dequant scales), registered as a JAX pytree node so it flows through tree_map/tree_flatten. The Linear and MoE matmul paths detect hasattr(w, "scales") and dequantize on the fly (w.weight * w.scales) inside a shard_map. This is how the 314B model fits in 8-bit storage while computing in bfloat16.
4. Inference Runtime (runners.py)
ModelRunner — model lifecycle
ModelRunner owns model initialization and weight loading:
initialize()setsfprop_dtype = bfloat16, computes batch sizes frombs_per_device × local_gpus × replicas, builds the mesh viamake_mesh, and constructs thehk.transform-ed forward functions.get_state_sharding()usesjax.eval_shape+ the model'spartition_rules()(viaapply_rules) to derive aPartitionSpectree for every parameter without materializing weights — this is what makes loading a 314B model tractable.load_or_init()either initializes fresh parameters or callscheckpoint.restore()to load the checkpoint shaped to the derived sharding.
InferenceRunner — compilation and sampling
InferenceRunner.initialize() does the heavy lifting:
- Loads the SentencePiece tokenizer from
tokenizer_path. hk.transforms four functions:hk_forward,hk_sample_step,hk_new_memory,hk_prefill_memory.- Computes parameter sharding via
eval_shape+apply_rules. - Wraps the step functions in
pjit.pjitwith explicitin_shardings/out_shardingsanddonate_argnumsfor the KV-cache memory (donation enables in-place buffer reuse).
InferenceRunner.run() is a Python generator implementing continuous batching:
- Allocates a fixed-size batch of KV-cache memory and a free-slot list.
- On each
yield, accepts aRequest, tokenizes the prompt, pads it to a bucket size (get_pad_bucket), and runsprefill_memoryinto a free batch slot. - Runs
sample_stepacross the whole batch, harvests finished sequences (whenlen(all_tokens) >= max_len), decodes them, frees their slots, and yields the output string.
Note
Padding uses fixed buckets (
pad_sizes=(1024,)) so the compiledprefill_memoryis reused across prompts of varying length. Prompts longer than the bucket are left-truncated (pad_to_sizekeeps the lastsizetokens). Be aware that very long prompts silently lose their beginning.
Sampling (sample_token + top_p_filter)
sample_token divides logits by temperature, masks disallowed tokens to -1e10, applies nucleus (top-p) filtering, then draws with jax.random.categorical. It also returns the chosen token's probability and the top-8 candidate tokens/probabilities (TOP_K = 8) for inspection.
top_p_filter sorts logits, computes the cumulative softmax mass, and masks out tokens below the 1 - top_p threshold (setting them to -1e10).
Device mesh (make_mesh)
make_mesh builds a 2-D hybrid device mesh with axes ("data", "model") using mesh_utils.create_hybrid_device_mesh, combining a local_mesh_config (intra-host) and between_hosts_config (inter-host). The example uses (1, 8) local — pure 8-way model parallelism on a single host.
5. Checkpoint Subsystem (checkpoint.py)
The checkpoint is a directory ckpt-0/ of per-tensor pickle files named tensor{NNNNN}_{NNN} (tensor index + shard index). Loading is parallel and shared-memory-staged.
| Function | Role |
|---|---|
restore() |
Top-level entry: flattens the expected state shapes, loads tensors, unflattens, validates keys, converts host-local arrays to a global sharded array |
load_tensors() |
Loads tensors in parallel via a 32-worker ThreadPoolExecutor; only the shards owned by this process are read, others are zero-filled |
replace_with_load_state() |
Merges loaded tensors into an init-state pytree, applying optional rename/exclude regex rules per parameter path |
fast_unpickle() / fast_pickle() |
Pickle load/save that stages the file through /dev/shm |
copy_to_shm() / copy_from_shm() |
Context managers that copy a file into Linux shared memory for fast random access |
Caution
Loading uses Python
pickle(pickle.load) on checkpoint files, andcheckpoint.pyregistersQuantizedWeight8bitintosys.modules['__main__']so the pickle can resolve it. Unpickling executes arbitrary code embedded in the file. Only load checkpoints from trusted sources (the official xAI release). A maliciousckpt-0could run code on your host. This is the single most important security consideration when operating this code.
Important
restore()performs a key-set sanity check between the checkpoint and the code-defined parameters and raises a descriptiveValueErrorlisting params missing in the checkpoint vs. missing in code. This is your first line of defense against a partial/corrupted download or an architecture mismatch.
6. Sharding & Parallelism Deep Dive
Grok-1 is too large for one device; the entire design is built around JAX SPMD sharding across the ("data", "model") mesh.
- Partition rules (
TRANSFORMER_PARTITION_RULES,LM_PARTITION_RULES) are ordered(regex-path → PartitionSpec)pairs.apply_ruleswalks each parameter's pytree path, matches it against the rules, and assigns aPartitionSpec. Attention/FFN weights are sharded across bothdataandmodelaxes; norms, biases, and the router are mostly replicated (P(None)). - Activation sharding is applied throughout via
with_sharding_constraint(a no-op when no physical mesh is present, enabling single-device debugging). - MoE sharding uses
shard_mapwith explicit in/out specs so the slow matmuls run distributed across themodelaxis with apsumreduction. - KV cache (
Memory/KVMemory) is sharded withget_memory_sharding()and donated topjitso buffers are reused in place across decode steps.
Note
Because sharding is rule-driven by parameter path regexes, renaming layers or changing the module hierarchy will silently drop parameters from the rules (they fall to
P(None)/ replicated) — watch the"no matching found!"log lines emitted byapply_ruleswhen modifying the model.
7. Operations
Running
There is one operational command:
python run.py
All behavior (model config, mesh, prompt, sampling) is set in run.py. Operational "configuration" means editing that file. Logging is enabled at INFO via logging.basicConfig(level=logging.INFO); there is also a separate "rank" logger used for per-process diagnostics during loading and sharding.
What to watch in the logs
| Log message | Phase |
|---|---|
Initializing mesh for ... |
Mesh construction |
Loading checkpoint at .../ckpt-0 |
Checkpoint load start |
Restored from ckpt: ... / Not found in ckpt: ... |
Per-parameter load status |
Precompile {size} |
Prefill compilation per pad bucket |
Compiling... / Done compiling. |
Sample-step XLA compilation |
Operational characteristics
- Startup is dominated by checkpoint loading and XLA compilation, not generation.
- No persistence/state beyond the loaded weights — each process run reloads everything.
- No graceful shutdown / health endpoints — it is a script.
- Determinism: seeds are fixed (
rng_seed=42in the convenience path), but cross-hardware/XLA differences mean outputs are not bit-identical across machines.
Tip
To stand up anything resembling a service, you would keep one
InferenceRunner.run()generator alive (paying the load+compile cost once) and feed itRequestobjects from your own server loop. The generator already supports continuous batching across the fixed batch size — that is the natural integration seam.
8. Security Analysis
| Area | Finding | Recommendation |
|---|---|---|
| Checkpoint deserialization | pickle.load on checkpoint files; arbitrary code execution risk |
Load only the official, integrity-verified checkpoint; never accept third-party ckpt-0 directories |
__main__ injection |
sys.modules['__main__'].QuantizedWeight8bit is set to enable unpickling |
Acceptable for trusted checkpoints; do not generalize this pattern to untrusted inputs |
| Shared memory usage | Tensors staged through /dev/shm |
Ensure /dev/shm is on a trusted, single-tenant host; multi-tenant nodes could expose staged tensors |
| Network exposure | None — no server, no listening sockets | If you build a server around it, add authentication, input limits, and rate limiting yourself |
| Input validation | Prompts are tokenized as-is; long prompts are silently left-truncated | Enforce prompt length limits at your integration layer |
| Secrets | None present; no credentials, keys, or tokens in the repository | N/A |
Caution
The dominant security risk is untrusted checkpoint loading via pickle. There is no signature verification or safe-tensors path in this repository. If you cannot fully trust a checkpoint's provenance, do not load it with this code.
9. Code Quality & Conventions
- Style: formatted/linted with Ruff (
pyproject.toml: line length 100, 4-space indent; a handful of lint rules ignored, e.g.E731/F403). - Idioms: heavy use of Haiku modules (
hk.Module,hk.transform,hk.transparent), JAX functional transforms (vmap,pjit,shard_map,eval_shape), andNamedTuple/@dataclassfor typed containers. - Assertions as contracts: the attention path uses dense
assertstatements on tensor shapes/dtypes — excellent as executable documentation, but they run unconditionally (no-Oguard) and raise bareAssertionErrors. - Numerical care: routing and softmax are forced to fp32; logit soft-capping bounds attention; compute dtype is bfloat16. These reflect production-grade attention to stability.
- Clarity over speed: the codebase consistently chooses readable, kernel-free implementations (the MoE matmuls being the prime example), consistent with its stated role as a correctness reference.
Extracted surface (for reference)
| Metric | Count |
|---|---|
| Source files analyzed | 4 |
| Classes | 27 |
| Functions | 80 |
| Explicit error raises | 4 (ValueError) |
| CLI commands | 0 (no CLI exists) |
| API endpoints | 0 (no server exists) |
| Config keys (env/file) | 0 (config is Python kwargs in run.py) |
10. Potential Improvements
These are observations from the source, framed as fork/extension opportunities — not defects in a reference implementation.
- [ ] Optimize the MoE path. Replace
moe_slow_matmul1/2's dense-then-one-hot approach with a gather/scatter or a fused expert kernel. This is the largest throughput lever and is explicitly acknowledged in the README. - [ ] Add a safe checkpoint format. Migrate from
pickletosafetensors(or add signature verification) to remove the arbitrary-code-execution risk during loading. - [ ] Parameterize
run.py. Add anargparse/config layer so the prompt, mesh shape,max_len,temperature, andnucleus_pcan be set without editing source — currently the only way to change them. - [ ] Expose a serving seam. Wrap the existing continuous-batching generator in a thin server (with auth, length limits, and backpressure) so the load+compile cost is amortized.
- [ ] Guard prompt truncation. Left-truncation of over-long prompts is silent; surface a warning or reject prompts exceeding the pad bucket / context length.
- [ ] Make
/dev/shmstaging optional. Provide a fallback path for environments without large shared memory to broaden portability beyond Linux GPU hosts.
11. Quick Reference
Module map
| File | Lines of responsibility |
|---|---|
run.py |
Config + example entry point |
model.py |
Architecture (attention, MoE, transformer, embeddings, sharding rules, quantized weights) |
runners.py |
Mesh, compilation, KV-cache, sampling loop |
checkpoint.py |
Parallel sharded checkpoint loading via /dev/shm + pickle |
Key constants and defaults
| Name | Value | Location |
|---|---|---|
CKPT_PATH |
./checkpoints/ |
run.py |
| Tokenizer path | ./tokenizer.model |
run.py |
local_mesh_config |
(1, 8) |
run.py |
between_hosts_config |
(1, 1) |
run.py |
bs_per_device |
0.125 |
run.py |
pad_sizes |
(1024,) |
run.py |
TOP_K |
8 |
runners.py / model.py |
max_attn_val |
30.0 |
model.py (attention soft-cap) |
| RoPE base exponent | 10000 |
model.py |
| Default rng seed | 42 |
runners.py |
Dependencies
| Package | Version |
|---|---|
dm_haiku |
0.0.12 |
jax[cuda12-pip] |
0.4.25 |
numpy |
1.26.4 |
sentencepiece |
0.2.0 |
Operational facts
- Entry point:
python run.py(no arguments). - Hardware: multi-GPU, CUDA 12, Linux with sizable
/dev/shm. - Weights: download
ckpt-0(HuggingFacexai-org/grok-1or magnet torrent) intocheckpoints/. - License: Apache 2.0 (code and weights).