benchmarks/README.md documents benchmarks/bench_offload_cache_copy.py as synthetic
and runnable with no arguments:
python benchmarks/bench_offload_cache_copy.py
Run that way it always dies partway through, and it dies quietly enough that the missing
coverage is easy not to notice — the earlier profiles have already printed their rows.
What happens
--models defaults to all 8 profiles in MODELS. For each profile the default slot
counts are [profile.experts, int(0.4 * profile.layers * profile.experts)]. For
minimax-m2.5-marlin (62 layers, 256 experts) the second value is
int(0.4 * 62 * 256) = 6348, which OffloadMoeCache.validate_rebuild rejects:
ValueError: moe_cache_size=6348 exceeds the marlin backend's slot limit of 992
(vLLM moe_align_block_size caps padded experts at 1024); reduce moe_cache_size
or force --nvfp4-backend triton
Full traceback:
minimax-m2.5-marlin (nvfp4_marlin, 4 banks, L=62 E=256 k=8) ensure_experts + copy_missing | cache_slots=6348 (47.1 GiB) | expert_bytes=7.59 MiB
bs active miss_rate misses time_ms copy_MiB bw_GBps tok_ms
-- ------ --------- ------ ------- -------- ------- ------
Traceback (most recent call last):
File "benchmarks/bench_offload_cache_copy.py", line 248, in <module>
main()
File "benchmarks/bench_offload_cache_copy.py", line 242, in main
print_table(
File "benchmarks/bench_offload_cache_copy.py", line 210, in print_table
cache = make_cache(profile, cache_slots, device)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "benchmarks/bench_offload_cache_copy.py", line 99, in make_cache
cache = OffloadMoeCache(
^^^^^^^^^^^^^^^^
File "<string>", line 14, in __init__
File "python/freetoken/moe/offload_cache.py", line 151, in __post_init__
self.validate_rebuild(self.cache_size)
File "python/freetoken/moe/offload_cache.py", line 416, in validate_rebuild
raise ValueError(
ValueError: moe_cache_size=6348 exceeds the marlin backend's slot limit of 992 (vLLM moe_align_block_size caps padded experts at 1024); reduce moe_cache_size or force --nvfp4-backend triton
The exception is not caught, so the process exits. minimax-m2.5-marlin is the 4th of
the 8 profiles, so the remaining four — minimax-m2.5-triton, glm4.7-nvfp4,
gpt-oss-20b, gpt-oss-120b — never run. The observed run produced 56 table rows and
then the traceback.
This is deterministic by construction: the offending slot count is computed from the
profile's own constants, not sampled, so the default invocation fails every time on any
machine.
The remaining profiles are fine
Naming them explicitly runs them to completion:
python benchmarks/bench_offload_cache_copy.py \
--models minimax-m2.5-triton gpt-oss-20b gpt-oss-120b glm4.7-nvfp4
exits 0 and produces 64 rows. So the only broken thing is the derived slot count for one
profile, and the blast radius is that half the sweep is silently unreachable by default.
Suggested direction
Clamping the derived slot count to the backend's limit for that quant format would keep
the default sweep complete. If clamping is the wrong call because the benchmark is
supposed to measure the requested size or nothing, then skipping that one slot count
with a printed note would still be better than aborting the whole sweep — a loud skip is
recoverable, a truncated table that looks finished is not.
Happy to send a patch in whichever direction you prefer.
Environment
- FreeToken
0.1.2 (ft --version), tested at commit bd372b6
- GPU: NVIDIA H100 80GB HBM3 (sm_90), 4 in the box; one used
- NVIDIA driver: 580.126.16
- CPU: Intel Xeon Platinum 8462Y+, 56 threads; 2015 GiB system RAM
- OS: Linux 5.15.0-157-generic
- CUDA toolkit: nvcc release 13.1, V13.1.115
- torch 2.11.0+cu130, Python 3.12.13
- Install:
uv pip install -e ".[accel,dev]"
The benchmark is synthetic, so no checkpoint is involved.
benchmarks/README.mddocumentsbenchmarks/bench_offload_cache_copy.pyas syntheticand runnable with no arguments:
Run that way it always dies partway through, and it dies quietly enough that the missing
coverage is easy not to notice — the earlier profiles have already printed their rows.
What happens
--modelsdefaults to all 8 profiles inMODELS. For each profile the default slotcounts are
[profile.experts, int(0.4 * profile.layers * profile.experts)]. Forminimax-m2.5-marlin(62 layers, 256 experts) the second value isint(0.4 * 62 * 256) = 6348, whichOffloadMoeCache.validate_rebuildrejects:Full traceback:
The exception is not caught, so the process exits.
minimax-m2.5-marlinis the 4th ofthe 8 profiles, so the remaining four —
minimax-m2.5-triton,glm4.7-nvfp4,gpt-oss-20b,gpt-oss-120b— never run. The observed run produced 56 table rows andthen the traceback.
This is deterministic by construction: the offending slot count is computed from the
profile's own constants, not sampled, so the default invocation fails every time on any
machine.
The remaining profiles are fine
Naming them explicitly runs them to completion:
exits 0 and produces 64 rows. So the only broken thing is the derived slot count for one
profile, and the blast radius is that half the sweep is silently unreachable by default.
Suggested direction
Clamping the derived slot count to the backend's limit for that quant format would keep
the default sweep complete. If clamping is the wrong call because the benchmark is
supposed to measure the requested size or nothing, then skipping that one slot count
with a printed note would still be better than aborting the whole sweep — a loud skip is
recoverable, a truncated table that looks finished is not.
Happy to send a patch in whichever direction you prefer.
Environment
0.1.2(ft --version), tested at commitbd372b6uv pip install -e ".[accel,dev]"The benchmark is synthetic, so no checkpoint is involved.