Skip to content

Alpamayo 1.5 on Thor: full-request engine with precision presets and FlashDrive streaming backend - #229

Open
LiangSu8899 wants to merge 21 commits into
mainfrom
feat/alpamayo15-thor-public
Open

LiangSu8899 wants to merge 21 commits into
mainfrom
feat/alpamayo15-thor-public

Conversation

@LiangSu8899

@LiangSu8899 LiangSu8899 commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Related issue: #223

Alpamayo 1.5 on Jetson AGX Thor (SM110) in two opt-in forms, each measured against its own reference on the same inputs:

  • Full requests (flashrt_full): the official request procedure (nvidia/Alpamayo-1.5-10B) on FlashRT kernels, with three precision presets. Reference: the official model.
  • Streaming (fd_flashrt): FlashDrive's streaming procedure (z-lab base / PARO / DFlash checkpoints, FlashDrive's orchestration) on FlashRT backends. Reference: FlashDrive's own implementation.

The two procedures produce different outputs and are not compared with each other.

Performance

Thor, locked clocks, NVIDIA PyTorch 26.05 / CUDA 13.2. Nine PhysicalAI-AV clips, three seeds, 1719 steady windows per arm, 128 reasoning tokens, one trajectory sample; latency is model time per window (tokenized inputs on the host to the trajectory on the host). Paired ADE differences carry 95% bootstrap intervals over clips.

Full requests:

Path mean ms median ms p90 ms vs official peak GB paired ADE vs official
Official Alpamayo 1.5 (BF16, SDPA) 3322 3168 3755 - 21.6 -
FlashRT preset="bf16" 2122 1968 2514 1.57x 22.3 +0.008 [-0.011, +0.042]
FlashRT preset="balanced" (default) 967 916 1097 3.44x 35.0 -0.001 [-0.052, +0.053]
FlashRT preset="fast" 841 790 962 3.95x 32.0 -0.019 [-0.080, +0.037]

Streaming:

Path mean ms median ms p90 ms vs FlashDrive peak GB paired ADE vs FlashDrive
FlashDrive (unmodified, torch.compile) 958 856 1057 - 13.3 -
FlashRT backend, defaults 476 417 533 2.01x 26.0 -0.041 [-0.104, +0.011]
FlashRT backend, INT8 decode + INT8 prefill + FP8 expert tiers 392 335 433 2.44x 27.1 -0.052 [-0.161, +0.024]

Every ADE interval includes zero: each FlashRT path is indistinguishable from its reference on this evaluation (offline ADE of sampled trajectories; not a closed-loop metric).

Presets and tiers

Full-request preset Decode Expert Prefill Vision Needs
bf16 official BF16 BF16 BF16 (cuBLAS) BF16 official checkpoint
balanced (default) W4A8 ParoQuant (grouped few-row GEMMs) FP8 FP8 (2-SM tcgen05) BF16 (2-SM 256x256 tcgen05) paro_checkpoint (z-lab PARO)
fast W4A8 + INT4 LM head FP8 NVFP4 W4A4 (per-row scales from fused producers) BF16 paro_checkpoint, flash_rt_fp4 module

Streaming backend tiers (off by default, each validated above and singly in the guide): decode_weights="int8", prefill_weights="int8", expert_weights="fp8".

Usage

.venv-official/bin/python benchmarks/alpamayo15_thor.py --prepare --windows 120 --output data/example.pt

# full requests: reference, default preset, other presets
.venv-official/bin/python benchmarks/alpamayo15_thor.py --arm official --inputs data/example.pt --seed 0 --output results/official.json
.venv-official/bin/python benchmarks/alpamayo15_thor.py --arm flashrt_full --inputs data/example.pt --seed 0 --output results/flashrt-balanced.json
.venv-official/bin/python benchmarks/alpamayo15_thor.py --arm flashrt_full --preset fast --inputs data/example.pt --seed 0 --output results/flashrt-fast.json

# streaming: reference, FlashRT backend, backend with the three tiers
.venv-flashdrive/bin/python benchmarks/alpamayo15_thor.py --arm flashdrive --inputs data/example.pt --seed 0 --output results/flashdrive.json
.venv-flashdrive/bin/python benchmarks/alpamayo15_thor.py --arm fd_flashrt --inputs data/example.pt --seed 0 --output results/fd-flashrt.json
.venv-flashdrive/bin/python benchmarks/alpamayo15_thor.py --arm fd_flashrt --engine-kw decode_weights=int8,prefill_weights=int8,expert_weights=fp8 --inputs data/example.pt --seed 0 --output results/fd-flashrt-tiers.json

Python APIs: Alpamayo15TorchFrontendThor(checkpoint, engine_kwargs={"paro_checkpoint": ...}) and Alpamayo15FlashDriveTorchFrontendThor(FlashDriveCheckpoints(...)). Installation, pinned dependencies, checkpoints, the protocol, stage breakdowns, numerics contracts and limitations: docs/alpamayo15.md.

Validation

On Thor at the head commit: tests/test_alpamayo15_thor.py 131 passed, tests/test_alpamayo15_flashdrive_thor.py 66 passed (kernel exactness against the official op sequences and against Marlin / ParoQuant, fused-vs-separate kernel identity, preset and option validation, checkpoint integration). The bf16 engine keeps the official numerics up to kernel reduction order; balanced / fast and the streaming tiers are numerics changes gated by the nine-clip, three-seed evaluation above. Kernels are Thor-only and default OFF (FLASHRT_ENABLE_ALPAMAYO15); model dependencies stay optional.

The FlashDrive backend no longer calls vLLM Marlin, cuBLAS or torch
elementwise ops inside the streaming windows:

- W4A8 GEMMs for the verification block and the trajectory-start row run
  on a few-row mma.sync kernel over the packed INT4 nibbles, zero points
  and integer group scales (Marlin's integer arithmetic exactly; one warp
  per 16-row tile streaming the weights at the DRAM rate, K split across
  warps for narrow outputs; a shared-memory ring variant for N < 2048).
- The DFlash draft (fc + hidden norm, both layers with FA4 attention of
  the block rows over context + block keys, SiLU MLP) and the LM head
  run on a few-row BF16 kernel over fragment-ordered weight copies;
  `decode_weights="fp8"` keeps per-row FP8 copies for the W8A16 kernel.
- The vision tower and the action expert run on tcgen05 BF16 GEMMs with
  the bias, GELU and residual add fused into the epilogue, a LayerNorm
  kernel and a fused SiLU-mul over the gate|up output; `expert_weights=
  "fp8"` adds a W8A8 path (per-row FP8 activations and weights, scales in
  the epilogue). `vision_gemm`/`expert_gemm="torch"` keep cuBLAS.
- The frame-block shift of the whole cache (keys, values, rotated view)
  is one launch.
- Options: `act_rot="dense"` (the ParoQuant rotation as one 128x128 FP16
  matrix per group on tensor cores), `w4a8_ktile=256`, `pdl` (programmatic
  dependent launch of every backend kernel, off by default).

The W4A8 activation buffers are padded to whole 32-row tiles and the
persistent buffers zero-initialised: the tcgen05 prefill GEMM reads the
padded last token tile, which made two processes with the same seed
diverge. Two processes now reproduce all 117 windows of the default clip.
Tests cover exactness against float64 references and Marlin, the
fragment layouts, repeatability of every new kernel and of every
primitive on identical state, and the three backend configurations.
…ow in one kernel per input

Over the streaming window the producer of each W4A8 input (residual update +
RMSNorm, SiLU-mul of the gate|up output, or the attention output) and its
ParoQuant preparation (channel scale, eight rotation layers, row absmax, INT8
quantization) now run as one kernel over tiles of 8 or 16 rows. The tile is
held column-major in shared memory as 16-byte chunks of 8 rows (one pad chunk
per 8 channels), so a rotation lane moves 8 rows per access and the producers'
transposing stores stay conflict-free; the widest input (down projection)
spreads a tile over a thread-block cluster whose CTAs share the row absmax
through distributed shared memory. The RMSNorm keeps the summation order of
the separate kernel, so every output is bit-identical to the previous path
(unit tests over all producer modes, tile sizes and cluster shapes). The
few-row verification and trajectory-start passes keep the separate kernels,
whose grids spread the rotation groups over the SMs.

Thor, author clip, seed 0, 117 windows: prefill 177.8 -> 155.6 ms, window
482.0 -> 465.6 ms mean (paired -16.4), tokens identical.
… K tile, INT8 weight tiers

The pairs of every ParoQuant rotation layer and channel group are reordered
on the host so that the eight pairs a quarter-warp of the tile preparation
kernel handles together touch distinct shared-memory bank groups with their
first channel; the rotation itself is unchanged (pairs of a layer are
disjoint) and the outputs stay bit-identical. The blockwise tcgen05 prefill
GEMM now runs two 128-channel groups per mainloop step by default (identical
integers).

Two precision tiers for the streaming path, both off by default:
- decode_weights="int8": per-row symmetric INT8 copies of the LM head and the
  DFlash draft linears on the W8A16 kernel, which gains an INT8 code path next
  to FP8 E4M3 (same bytes, ~4x finer weight steps). Nine clips x three seeds
  on Thor: ADE +0.008 vs the BF16 default (95% CI [-0.031, +0.063]), -0.023 vs
  the FlashDrive author stack; block step 37.4 -> 30.6 ms per round.
- prefill_weights="int8": per-channel INT8 requantization of Marlin's
  group-scaled integers on a plain INT8 tcgen05 GEMM with the token and
  channel scales in the epilogue (171-179 TOPS vs 115-138 blockwise on Thor).

Thor, author clip, seed 0, 117 windows, defaults: window 465.6 -> 454.4 ms
mean (paired -11.2), prefill 155.6 -> 144.4, tokens identical.
…sured tiers in the docs

The per-channel INT8 prefill GEMM runs on 256 x 128 x 128 tiles with the 2-SM
UMMA (2 x 1 clusters): 201-211 TOPS on Thor against 171-179 for the 1-SM
128 x 128 tile, identical integer results. The blockwise W4A8 GEMM gained the
same 2-SM form behind its flags for comparison (no gain there: its per-group
promotion, not operand traffic, is the limit) and a test that the K tile 128 /
256 and 1-SM / 2-SM forms produce identical outputs.

Docs: nine clips x three seeds on Thor for the defaults and every tier
(INT8 decode weights, INT8 prefill weights, FP8 expert weights, combinations),
latency and trajectory error paired against the FlashDrive author stack.
…and prefill tiers

Full-request engine (Alpamayo15Thor):
- vision tower on the tcgen05 BF16 GEMMs with bias, GELU and residual fused
  into the epilogues (2-SM 256 x 128 tiles for every form), LayerNorm kernel,
  one CUDA graph per frame grid; vision_gemm="torch" keeps the cuBLAS path
- action expert on 64-row tcgen05 tiles with fused q/k/v and gate|up weights
  and the split SiLU-mul kernel (no cuBLAS split-K reduce launches)
- prefill: gate and up as two cuBLAS GEMMs over the fused weight (cuBLAS is
  slower on the fused shape), vectorized SiLU-mul in place; the tcgen05 BF16
  GEMM measured slower than cuBLAS at these shapes and is not used there
- precision tiers: decode_weights="int8" (W8A16 INT8 path), expert_weights="fp8"
  (W8A8 per-row), prefill_weights="fp8" (W8A8 per-row on 2-SM FP8 tiles; the
  K=12288 down projection as four column blocks that fit the L2)

Kernels: FP8 GEMM gains 128-row and 2-SM 256-row tiles and an output leading
dimension; the LayerNorm, split SiLU-mul (now gate/up pointers with a leading
dimension) and per-row FP8 quantization kernels move under the alpamayo15_
prefix as shared Alpamayo 1.5 kernels; the quantization uses the same division
as the torch reference. The FlashDrive backend imports the shared GEMM wrapper,
graph capture and vision tower from the engine.

Tests: shared kernel tests moved to the engine file, 2-SM tile identity, FP8
column-block outputs, option validation, a synthetic Qwen3-VL tower comparing
the torch, CUTLASS and captured-graph paths.
…ducers

The W8A8 prefill tier quantized the RMSNorm and SiLU-mul outputs in a second
pass over each row. The producers now write the FP8 codes and row scales
themselves (rms_fp8_rows, silu_mul_fp8_rows: same codes and scales as the
BF16 kernel followed by the row quantization, the BF16 row is never written),
leaving one quantization launch per layer for the attention output. Tests
compare both fused kernels with the separate ones bit for bit.
… producers, PDL and kernel tuning

- decode_weights="w4a8" with paro_checkpoint: the decode projections run as
  the ParoQuant W4A8 linears of the Alpamayo-1.5-10B-PARO checkpoint on the
  few-row W4A8 kernels (activation preparation + Marlin integer arithmetic),
  the LM head as INT8. flash_rt/models/alpamayo15/marlin_w4a8.py holds the
  Marlin layout index maps (following vLLM's marlin_utils, Apache-2.0), the
  unpacking, the few-row operand packing and the checkpoint loader; the
  FlashDrive backend imports them. The few-row W4A8 GEMMs gain an output
  leading dimension and a BF16 output.
- vision_weights="fp8": W8A8 vision blocks on a 2-SM FP8 GEMM with bias,
  GELU and residual fused into the epilogue; LayerNorm -> FP8 rows kernel.
- the W8A8 expert tier quantizes inside its FP32-residual RMSNorm and
  SiLU-mul producers; shared row-quantization device helper header.
- pdl: programmatic dependent launch on every engine kernel and GEMM
  (alpamayo15_set_pdl, shared with the FlashDrive backend; off by default).
- RMSNorm kernels issue four loads per step in the same per-thread order
  (bit-identical outputs); the vision RoPE kernel uses one thread per vector
  pair (no idle lanes).

Tests: Marlin layout round trip, fused producers against the separate kernels
bit for bit, FP8 GEMM epilogue forms, LayerNorm -> FP8 rows, option checks.
… and table prefetch, INT4 LM head

Engine presets bf16 (default), balanced (W4A8 decode + FP8 expert + FP8 prefill)
and fast (balanced + INT4 LM head); the weight options override a preset.

Decode: the ParoQuant projections of a shared input run as one grouped few-row
W4A8 GEMM launch (q|k|v, o, gate|up, down) on a weight layout that carries each
tile's zero points and integer group scales in front of its nibbles; up to four
rows the per-token INT8 quantization runs in the GEMM prologue (rotation pass +
GEMM per group), bit-exact with the two-pass preparation; each GEMM prefetches
the next group's rotation tables into the L2 while streaming its weights (a cold
read of a few hundred KB right before a streaming kernel costs ~30% of that
kernel on Thor). FA4 decode attention with one KV split. Own INT4 LM head
(group 128, zero points, per-channel normalized integer scales rounded up,
padded vocabulary, per-token INT8 activations).

Vision LayerNorm one warp per row (also the LayerNorm -> FP8 producer); the
prefill RMSNorm -> FP8 producer reads and writes 8-byte vectors.

Author clip, 24 windows: balanced 895 / 788 ms (decode 22.1 ms/token), fast
849 / 773 (20.8); nine clips: balanced 1027 / 972, fast 1006 / 949 (ADE
neutral or better against the default and the official model).
…measured backends and tiers

A cuBLASLt BF16 GEMM with bias, GELU and beta-1 residual fused into its
epilogue (column-major formulation, cached plans) and vision_gemm="cublaslt",
which runs the blocks' fc1 on it: 147 against 125 TFLOPS on Thor, about 3 ms
per request, but last-bit differences that resample a few windows (nine clips:
ADE +0.047 [-0.007, +0.153]), so the tcgen05 tower stays the default. The
other block GEMMs measured no better on cuBLASLt in the pipeline (the fc2
choice 1.35 ms against 0.90). The prefill tier's SiLU-mul -> FP8 producer
keeps four vectors in flight (same codes).

Measured and left out: the prompt's last-token logits from the quantized LM
head (5 ms, ADE +0.055 [+0.001, +0.161]) and the repository's NVFP4 GEMM at the
prefill shapes (224 TOPS against 250 for the FP8 2-SM tile).
…t preset takes it

NVFP4 GEMM forms for the prefill shapes in the flash_rt_fp4 module (1-SM
128x128x256, 2-SM 256x128x256 and 256x256x256 block-scaled tiles; on Thor the
256x256 2-SM tile runs 500-575 TOPS against the FP8 tile's 250), with the output
leading dimension and a device alpha. Row quantizers for bf16 operands: a
per-tensor scale from per-block maxima that keeps the UE4M3 block-16 scales in
the normal range, e2m1 codes by the hardware conversion, scale factors in the
CUTLASS tile-interleaved layout, the GEMM alphas of an input's consumers.

prefill_weights="nvfp4": static NVFP4 weights, each GEMM input quantized once
per consumer group, the 36 layers' projections on the 2-SM tile. Prefill 290 ->
233 ms per request on the author clip; nine clips 884 / 836 ms (3.76x the
official model) at a neutral ADE (-0.022 vs the BF16 default [-0.090, +0.026],
-0.008 vs official [-0.076, +0.054]). The fast preset is now balanced + NVFP4
prefill + INT4 LM head.
…-row alpha GEMM epilogue

The W4A4 prefill tier quantized each GEMM input with a per-tensor scale in two
passes (per-block maxima, then codes) after a BF16 producer. The activations
now carry per-row scales, so the RMSNorm and SiLU-mul producers quantize their
row completely from shared memory (codes, UE4M3 block scales in the GEMM's
scale layout, and the row's alphas row scale x weight scale for the input's
consumers), the attention output goes through a per-row quantizer, and the
NVFP4 GEMM applies the alpha vector in its epilogue (PerRowLinCombPerRowBias
with beta and bias off; the scalar form stays for the weights). The BF16
intermediates and both passes disappear from the tier: prefill 233 -> 185 ms,
the fast preset 807 -> 741 ms on the author clip and 884 -> 837 ms on the nine
clips (ADE -0.036 vs the BF16 default [-0.139, +0.036]).

Remove a copy of the prefill's NVFP4 block that had been left in the expert
forward. Tests: per-row quantizer and per-row alpha GEMM against the torch
emulation (zero rows, second consumer, exact per-row powers of two, small rows
no worse than large), fused producers against the separate kernels plus the
per-row quantizer (residual bit-exact, SiLU bit-exact, RMSNorm within one
e2m1 step), the gate | up stride form, wider rows, rejections.
…EMMs

Add tile 3 = 256 x 256 x 64 on a 2-SM UMMA (identity, GELU tanh / erf and
residual epilogues) to the BF16 GEMM and make it the vision tower's default:
at S = 11520 qkv 736 -> 551 us, fc1 972 -> 708, fc2 969 -> 816, proj 331 ->
337 (N = 1152). The N tiling leaves the K reduction order unchanged, so the
outputs equal the 256 x 128 tile's on every GEMM and the tokens and
trajectories are the same; the tower drops from 141 to 122 ms per request.
preset="balanced" (W4A8 ParoQuant decode, FP8 expert, FP8 prefill) becomes
the default: over three seeds of the nine-clip evaluation it is neutral
against the official model (ADE -0.001 [-0.052, +0.053]) at 3.4x its speed;
on the author clip 867 / 870 ms over 120 windows against 1873 for the BF16
engine. It needs the ParoQuant checkpoint directory (paro_checkpoint); the
engine refuses to start without it and names preset="bf16" as the
official-weights alternative. The option checks run before the checkpoint
check, so a wrong option is reported as such.
…takes presets and tiers

Rewrite docs/alpamayo15.md around the two inference forms (full requests
against the official model, streaming against FlashDrive): installation,
checkpoints, usage, one benchmark protocol (nine clips, three seeds, model
time per window, paired ADE with clip-level intervals) and one results table
per form, each arm against its own reference, plus a stage table per form on
the example clip; precision tiers and execution contracts follow. The
historical per-step tables and the earlier eight-clip protocol are dropped.

benchmarks/alpamayo15_thor.py gains --preset (bf16 / balanced / fast; the
engine's default is balanced, so the runner resolves --paro into the
ParoQuant directory) and --engine-kw for single tiers of the engine or the
streaming backend.
@LiangSu8899
LiangSu8899 deleted the feat/alpamayo15-thor-public branch October 18, 2026 20:28
@LiangSu8899 LiangSu8899 changed the title Add Alpamayo 1.5 Thor full-request and FlashDrive backends Alpamayo 1.5 on Thor: full-request engine with precision presets and FlashDrive streaming backend Oct 18, 2026
@LiangSu8899 LiangSu8899 reopened this Oct 18, 2026
@LiangSu8899
LiangSu8899 restored the feat/alpamayo15-thor-public branch October 18, 2026 20:30
@LiangSu8899 LiangSu8899 reopened this Oct 18, 2026
…esets, streaming backend), FlashDrive acknowledgment
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