Skip to content

feat(examples): add gpt-oss-120b example - #83

Open
ymwangg wants to merge 4 commits into
mainfrom
feat/gpt-oss-120b-example
Open

feat(examples): add gpt-oss-120b example#83
ymwangg wants to merge 4 commits into
mainfrom
feat/gpt-oss-120b-example

Conversation

@ymwangg

@ymwangg ymwangg commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

A NKIPy implementation of OpenAI's gpt-oss-120b MoE model for AWS Trainium (TRN2), as a new examples/models/gpt_oss_120b example separate from the existing 20b example. Unlike the 20b example (dense/batched numpy MoE, TP-only), this targets scale: blockwise MoE (NKI kernel + C++ pybind11 index builder), TP + prefill expert-parallel + data-parallel, NKI prefill/decode attention, and a 4-layer fused decode kernel.

Scope is core model only (no vLLM plugin). Verified end-to-end on trn2.48xlarge (coherent generation — "The capital of France is Paris") with a presharded TP8 checkpoint.

Highlights

  • Standalone nki (beta-3) kernels throughout — rmsnorm, router, fused rank-slice, fused rmsnorm-gemm, blockwise MoE, and the flash-attention prefill/decode stack. No neuronxcc dependency remains. All device-validated; decode throughput recovered to baseline after migration (single-DMA sin/cos rope gather).
  • HuggingFace checkpoint support as the defaultscripts/hf_tensor_preparation.py reads a HuggingFace gpt_oss checkpoint (e.g. openai/gpt-oss-120b), dequantizes the MXFP4 MoE weights to bf16, remaps to the internal layout, and shards for TP. The HF→internal mapping is validated byte-identical to the original OpenAI block.* pipeline (openai_tensor_preparation.py), which is kept as an alternative.
  • Async decode (opt-in via GPT_OSS_120B_ASYNC=1) — submits the per-step decode kernel chain (embedding → fused decode blocks → sampling) non-blocking on the runtime FIFO channel via DeviceKernel.submit / SpikeAsyncFuture.wait (feat(runtime): non-blocking submit for DeviceKernel via SpikeAsync #84), with a bounded in-flight window (GPT_OSS_120B_ASYNC_WINDOW, default 4). This is the same non-blocking submit/wait pattern the gpt-oss-20b example uses. Overlaps host dispatch with device compute; output is byte-identical to the blocking path.
  • Configurable rank→core mapping (GPT_OSS_CORE_STRIDE) so ranks spread across devices instead of packing onto a few (avoids HBM OOM).
  • README/chat.sh document setup (incl. pybind11), weight prep, the verified parallel layout, and single-node TRN2 sizing constraints.

Performance

Measured on trn2.48xlarge, 64-rank layout (DP8 / EP8 / TP8), batch=1, single prompt, 64 generated tokens:

Decode path tok/s
Sync (blocking __call__) 33.5
Async (GPT_OSS_120B_ASYNC=1) 43.7

Async decode gives a 1.30× speedup at batch=1 while producing byte-identical output. TTFT is unchanged (~0.37s).

Authors

Co-authored by: Zhenyu Song (zhenyus@amazon.com), Lingfan Yu (lingfany@amazon.com), Yanming Wang (yanmwang@amazon.com), Ziyang Xu (ziyangx@amazon.com).


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@ymwangg
ymwangg requested a review from a team August 19, 2026 19:18
@ymwangg
ymwangg force-pushed the feat/gpt-oss-120b-example branch from f28661e to 332fc58 Compare August 24, 2026 18:16
A NKIPy implementation of OpenAI's gpt-oss-120b MoE model for AWS Trainium
(TRN2), with tensor/expert/data parallelism (TP/EP/DP).

- Standalone nki (beta-3) kernels throughout: rmsnorm, router, fused
  rank-slice, fused rmsnorm-gemm, blockwise MoE, and the flash-attention
  prefill/decode stack; no neuronxcc dependency.
- HuggingFace gpt_oss checkpoint support as the default weight-prep path
  (MXFP4 dequant + reshard), with the OpenAI block.* checkpoint path kept
  as an alternative.

Co-authored-by: Zhenyu Song <zhenyus@amazon.com>
Co-authored-by: Lingfan Yu <lingfany@amazon.com>
Co-authored-by: Yanming Wang <yanmwang@amazon.com>
Co-authored-by: Ziyang Xu <ziyangx@amazon.com>
@ymwangg
ymwangg force-pushed the feat/gpt-oss-120b-example branch from 332fc58 to c50b3fa Compare August 27, 2026 21:36
Async decode (opt-in via GPT_OSS_120B_ASYNC=1): submit the per-step decode
kernel chain (embedding -> fused decode blocks -> sampling) non-blocking on
the runtime FIFO channel with a bounded in-flight window
(GPT_OSS_120B_ASYNC_WINDOW, default 4), waiting on the oldest in-flight future
before the window overflows and on all outstanding futures (in FIFO order) at
step end. Overlaps host dispatch with device compute; ~1.30x decode at
batch=1 / 64-rank (33.5 -> 43.7 tok/s, decode MBU 19.1% -> 24.9%), output
byte-identical. Uses DeviceKernel.submit / SpikeAsyncFuture.wait from the
runtime (#84), the same non-blocking submit/wait pattern the gpt-oss-20b async
decode path uses.

Launcher / usability fixes:
- chat.py: tolerate --checkpoint given with or without the -TP{tp} suffix (and
  a trailing slash), with a real error message instead of a bare AssertionError.
- chat.sh: activate the repo venv (so python AND neuronx-cc resolve there),
  default to the single-node-verified layout (DP8/EP8, was DP16/EP4), and guard
  the port-cleanup lsof so it doesn't abort under set -e.
- config.py: drop NEURON_RT_ASYNC_EXEC_MAX_INFLIGHT_REQUESTS (removed from NRT;
  only produced a per-rank warning).
- README: document the layout defaults, the 128-rank (LNC1 + ep8) recipe, and
  the HBM/LNC sizing notes.
@ymwangg
ymwangg force-pushed the feat/gpt-oss-120b-example branch from c50b3fa to 4ae36d6 Compare August 27, 2026 23:23
blockwise_nki_static fully unrolled its block loop (Python range over
num_static_blocks, ~159 in the 64-rank layout), which dominated compile:
recompiled once per prefill layer, it was ~91% of "Preparing kernels".

When BUFFER_DEGREE == 1 (the prefill path) there is no cross-block buffer
rotation to unroll for, so iterate blocks with a real device loop
(nl.dynamic_range) instead. block_idx becomes a runtime nisa.VirtualRegister,
so the two block-indexed gathers (load_token_indices, _load_block_expert) now
select the block via a DMA scalar_offset rather than a static element offset.
BUFFER_DEGREE > 1 (tokengen/decode) keeps the static unroll so the compiler can
rotate buffer_idx = block_idx % BUFFER_DEGREE and software-pipeline weight DMA;
that kernel (blockwise_nki_tokengen_one_tile_replicated_hidden_state) is
untouched.

Measured (trn2, 64-rank DP8/EP8/TP8): "Preparing kernels" ~15 min -> ~1.8 min
(~8x). Prefill costs ~15% (TTFT 0.39 -> 0.46s at a 960-token prompt) from the
lost cross-block overlap; decode is a different kernel and unchanged. Unit tests
(test_blockwise_nki, bf16 + float8, prefill + tokengen) pass; full-model
generation is byte-identical to the unrolled kernel.
Flip GPT_OSS_120B_ASYNC to default on (was opt-in). The decode step already
submits its kernel chain (embedding -> fused decode blocks -> sampling)
non-blocking on the runtime FIFO channel via DeviceKernel.submit /
SpikeAsyncFuture.wait, overlapping host dispatch with device compute; making it
the default gives that speedup out of the box. Set GPT_OSS_120B_ASYNC=0 to fall
back to the blocking path for reference / debugging.

Measured (trn2, 64-rank DP8/EP8/TP8, batch=1): decode 33.8 -> 44.1 tok/s
(~1.30x), byte-identical output. Verified as the default (no env vars set) end
to end, together with the device-loop prefill kernel.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant