calibration.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. """Graph-safe sidecar collection for MoE quantization calibration.
  2. The fused kernels keep route-indexed activation scratch alive until return.
  3. These small Triton sidecars consume that scratch immediately, accumulating
  4. gate-square-weighted moments and copying a deterministic subsample into
  5. caller-owned ring buffers. All allocations and persistence remain the
  6. integration caller's responsibility; the functions below only launch kernels
  7. against stable tensors and are therefore safe to capture and replay.
  8. """
  9. from __future__ import annotations
  10. from dataclasses import dataclass
  11. import torch
  12. import triton
  13. import triton.language as tl
  14. @dataclass(frozen=True)
  15. class RouteInputBuffers:
  16. enabled: torch.Tensor
  17. epoch_counter: torch.Tensor
  18. epoch: torch.Tensor
  19. tokens_routed: torch.Tensor
  20. gate_sum: torch.Tensor
  21. gate_sq_sum: torch.Tensor
  22. input_sq_sum: torch.Tensor
  23. input_weight_sum: torch.Tensor
  24. input_count: torch.Tensor
  25. sample_cursor: torch.Tensor
  26. sample_dropped: torch.Tensor
  27. sample_slots: torch.Tensor
  28. sample_values: torch.Tensor
  29. sample_weight: torch.Tensor
  30. sample_observation: torch.Tensor
  31. sample_experts: torch.Tensor
  32. sample_gates: torch.Tensor
  33. sample_split: torch.Tensor
  34. @dataclass(frozen=True)
  35. class MidBuffers:
  36. enabled: torch.Tensor
  37. epoch: torch.Tensor
  38. mid_sq_sum: torch.Tensor
  39. mid_weight_sum: torch.Tensor
  40. mid_count: torch.Tensor
  41. sample_cursor: torch.Tensor
  42. sample_dropped: torch.Tensor
  43. sample_slots: torch.Tensor
  44. sample_values: torch.Tensor
  45. sample_weight: torch.Tensor
  46. sample_observation: torch.Tensor
  47. sample_expert: torch.Tensor
  48. sample_split: torch.Tensor
  49. @triton.jit
  50. def _mix64(value):
  51. value ^= value >> 30
  52. value *= 0xBF58476D1CE4E5B9
  53. value ^= value >> 27
  54. value *= 0x94D049BB133111EB
  55. return value ^ (value >> 31)
  56. @triton.jit
  57. def _advance_epoch_kernel(enabled, epoch_counter, epoch):
  58. active = tl.load(enabled).to(tl.int32)
  59. current = tl.load(epoch_counter).to(tl.int64)
  60. tl.store(epoch, current)
  61. tl.store(epoch_counter, current + active)
  62. @triton.jit
  63. def _route_stats_kernel(
  64. topk_ids,
  65. topk_weights,
  66. is_padding,
  67. enabled,
  68. tokens_routed,
  69. gate_sum,
  70. gate_sq_sum,
  71. num_tokens: tl.constexpr,
  72. topk: tl.constexpr,
  73. num_experts: tl.constexpr,
  74. collect_routing: tl.constexpr,
  75. BLOCK_TOPK: tl.constexpr,
  76. ):
  77. token = tl.program_id(0)
  78. if token >= num_tokens:
  79. return
  80. slots = tl.arange(0, BLOCK_TOPK)
  81. routes = token * topk + slots
  82. live_token = tl.load(enabled).to(tl.int1) & ~tl.load(is_padding + token)
  83. expert = tl.load(topk_ids + routes, mask=slots < topk, other=-1).to(tl.int32)
  84. live = live_token & (slots < topk) & (expert >= 0) & (expert < num_experts)
  85. if collect_routing:
  86. weight = tl.load(topk_weights + routes, mask=live, other=0.0).to(tl.float64)
  87. tl.atomic_add(tokens_routed + expert, 1, mask=live)
  88. tl.atomic_add(gate_sum + expert, weight, mask=live)
  89. tl.atomic_add(gate_sq_sum + expert, weight * weight, mask=live)
  90. @triton.jit
  91. def _input_moment_kernel(
  92. x,
  93. topk_ids,
  94. topk_weights,
  95. is_padding,
  96. enabled,
  97. epoch,
  98. square_sum,
  99. weight_sum,
  100. count,
  101. num_tokens: tl.constexpr,
  102. topk: tl.constexpr,
  103. width: tl.constexpr,
  104. expert_begin: tl.constexpr,
  105. expert_end: tl.constexpr,
  106. sample_rate: tl.constexpr,
  107. BLOCK: tl.constexpr,
  108. ):
  109. token = tl.program_id(0)
  110. block = tl.program_id(1)
  111. if token >= num_tokens:
  112. return
  113. live_token = tl.load(enabled).to(tl.int1) & ~tl.load(is_padding + token)
  114. current_epoch = tl.load(epoch).to(tl.uint64)
  115. selected = (_mix64(current_epoch ^ token.to(tl.uint64)) % sample_rate) == 0
  116. offsets = block * BLOCK + tl.arange(0, BLOCK)
  117. feature_mask = live_token & selected & (offsets < width)
  118. values = tl.load(x + token * width + offsets, mask=feature_mask, other=0.0).to(
  119. tl.float32
  120. )
  121. values_sq = values * values
  122. for route_slot in tl.static_range(0, topk):
  123. route = token * topk + route_slot
  124. expert = tl.load(topk_ids + route).to(tl.int32)
  125. live = live_token & selected & (expert >= expert_begin) & (expert < expert_end)
  126. local_expert = expert - expert_begin
  127. weight = tl.load(topk_weights + route, mask=live, other=0.0).to(tl.float32)
  128. out = local_expert * width + offsets
  129. tl.atomic_add(
  130. square_sum + out,
  131. values_sq * weight * weight,
  132. mask=live & (offsets < width),
  133. )
  134. if block == 0 and live:
  135. weight64 = weight.to(tl.float64)
  136. tl.atomic_add(weight_sum + local_expert, weight64 * weight64)
  137. tl.atomic_add(count + local_expert, 1)
  138. @triton.jit
  139. def _input_sample_meta_kernel(
  140. topk_ids,
  141. topk_weights,
  142. is_padding,
  143. enabled,
  144. epoch,
  145. cursor,
  146. dropped,
  147. slots,
  148. sample_weight,
  149. sample_observation,
  150. sample_experts,
  151. sample_gates,
  152. sample_split,
  153. num_tokens: tl.constexpr,
  154. topk: tl.constexpr,
  155. capacity: tl.constexpr,
  156. sample_rate: tl.constexpr,
  157. validation_modulus: tl.constexpr,
  158. ):
  159. token = tl.program_id(0)
  160. if token >= num_tokens:
  161. return
  162. live = tl.load(enabled).to(tl.int1) & ~tl.load(is_padding + token)
  163. current_epoch = tl.load(epoch).to(tl.uint64)
  164. selected = (_mix64(current_epoch ^ token.to(tl.uint64)) % sample_rate) == 0
  165. live &= selected
  166. slot = tl.atomic_add(cursor, 1, mask=live)
  167. accepted = live & (slot < capacity)
  168. tl.store(slots + token, tl.where(accepted, slot, -1))
  169. if live & ~accepted:
  170. tl.atomic_add(dropped, 1)
  171. if accepted:
  172. route_offsets = token * topk + tl.arange(0, 32)
  173. mask = tl.arange(0, 32) < topk
  174. experts = tl.load(topk_ids + route_offsets, mask=mask, other=-1).to(tl.int32)
  175. weights = tl.load(topk_weights + route_offsets, mask=mask, other=0.0).to(
  176. tl.float32
  177. )
  178. total_weight = tl.sum(weights * weights, axis=0)
  179. observation = (current_epoch << 32) | token.to(tl.uint64)
  180. tl.store(sample_weight + slot, total_weight)
  181. tl.store(sample_observation + slot, observation.to(tl.int64))
  182. tl.store(sample_experts + slot * topk + tl.arange(0, 32), experts, mask=mask)
  183. tl.store(sample_gates + slot * topk + tl.arange(0, 32), weights, mask=mask)
  184. split_key = observation ^ 0x6A09E667F3BCC909
  185. split = (_mix64(split_key) % validation_modulus) == 0
  186. tl.store(sample_split + slot, split.to(tl.int8))
  187. @triton.jit
  188. def _sample_values_kernel(
  189. source,
  190. slots,
  191. output,
  192. rows: tl.constexpr,
  193. width: tl.constexpr,
  194. source_stride: tl.constexpr,
  195. BLOCK: tl.constexpr,
  196. ):
  197. row = tl.program_id(0)
  198. block = tl.program_id(1)
  199. if row >= rows:
  200. return
  201. slot = tl.load(slots + row).to(tl.int32)
  202. offsets = block * BLOCK + tl.arange(0, BLOCK)
  203. mask = (slot >= 0) & (offsets < width)
  204. values = tl.load(source + row * source_stride + offsets, mask=mask, other=0.0)
  205. tl.store(output + slot * width + offsets, values, mask=mask)
  206. @triton.jit
  207. def _paired_sample_values_kernel(
  208. source,
  209. slots,
  210. output,
  211. ready,
  212. rows: tl.constexpr,
  213. width: tl.constexpr,
  214. BLOCK: tl.constexpr,
  215. ):
  216. row = tl.program_id(0)
  217. block = tl.program_id(1)
  218. if row >= rows:
  219. return
  220. slot = tl.load(slots + row).to(tl.int32)
  221. offsets = block * BLOCK + tl.arange(0, BLOCK)
  222. mask = (slot >= 0) & (offsets < width)
  223. values = tl.load(source + row * width + offsets, mask=mask, other=0.0)
  224. tl.store(output + slot * width + offsets, values, mask=mask)
  225. tl.store(ready + slot + offsets, 1, mask=(slot >= 0) & (offsets == 0))
  226. @triton.jit
  227. def _unscale_route_rows_kernel(
  228. values,
  229. topk_ids,
  230. expert_map,
  231. scales,
  232. num_routes: tl.constexpr,
  233. num_experts: tl.constexpr,
  234. width: tl.constexpr,
  235. scale_stride: tl.constexpr,
  236. scale_offset: tl.constexpr,
  237. BLOCK: tl.constexpr,
  238. ):
  239. route = tl.program_id(0)
  240. block = tl.program_id(1)
  241. if route >= num_routes:
  242. return
  243. expert = tl.load(topk_ids + route).to(tl.int32)
  244. live = (expert >= 0) & (expert < num_experts)
  245. local_expert = tl.load(expert_map + expert, mask=live, other=-1).to(tl.int32)
  246. live &= local_expert >= 0
  247. offsets = block * BLOCK + tl.arange(0, BLOCK)
  248. mask = live & (offsets < width)
  249. value = tl.load(values + route * width + offsets, mask=mask, other=0.0).to(
  250. tl.float32
  251. )
  252. scale = tl.load(
  253. scales + local_expert * scale_stride + scale_offset + offsets,
  254. mask=mask,
  255. other=1.0,
  256. ).to(tl.float32)
  257. tl.store(values + route * width + offsets, value / scale, mask=mask)
  258. @triton.jit
  259. def _mid_moment_kernel(
  260. source,
  261. topk_ids,
  262. topk_weights,
  263. expert_map,
  264. is_padding,
  265. enabled,
  266. epoch,
  267. square_sum,
  268. weight_sum,
  269. count,
  270. num_tokens: tl.constexpr,
  271. topk: tl.constexpr,
  272. num_experts: tl.constexpr,
  273. width: tl.constexpr,
  274. source_stride: tl.constexpr,
  275. sample_rate: tl.constexpr,
  276. has_expert_map: tl.constexpr,
  277. BLOCK: tl.constexpr,
  278. ):
  279. token = tl.program_id(0)
  280. block = tl.program_id(1)
  281. if token >= num_tokens:
  282. return
  283. live_token = tl.load(enabled).to(tl.int1) & ~tl.load(is_padding + token)
  284. current_epoch = tl.load(epoch).to(tl.uint64)
  285. selected = (_mix64(current_epoch ^ token.to(tl.uint64)) % sample_rate) == 0
  286. offsets = block * BLOCK + tl.arange(0, BLOCK)
  287. for route_slot in tl.static_range(0, topk):
  288. route = token * topk + route_slot
  289. expert = tl.load(topk_ids + route).to(tl.int32)
  290. live = live_token & selected & (expert >= 0) & (expert < num_experts)
  291. if has_expert_map:
  292. local_expert = tl.load(expert_map + expert, mask=live, other=-1).to(
  293. tl.int32
  294. )
  295. live &= local_expert >= 0
  296. mask = live & (offsets < width)
  297. values = tl.load(
  298. source + route * source_stride + offsets, mask=mask, other=0.0
  299. ).to(tl.float32)
  300. weight = tl.load(topk_weights + route, mask=live, other=0.0).to(tl.float32)
  301. out = expert * width + offsets
  302. tl.atomic_add(square_sum + out, values * values * weight * weight, mask=mask)
  303. if block == 0 and live:
  304. weight64 = weight.to(tl.float64)
  305. tl.atomic_add(weight_sum + expert, weight64 * weight64)
  306. tl.atomic_add(count + expert, 1)
  307. @triton.jit
  308. def _mid_sample_meta_kernel(
  309. topk_ids,
  310. topk_weights,
  311. expert_map,
  312. is_padding,
  313. enabled,
  314. epoch,
  315. cursor,
  316. dropped,
  317. slots,
  318. sample_weight,
  319. sample_observation,
  320. sample_expert,
  321. sample_split,
  322. num_routes: tl.constexpr,
  323. topk: tl.constexpr,
  324. num_experts: tl.constexpr,
  325. capacity: tl.constexpr,
  326. sample_rate: tl.constexpr,
  327. validation_modulus: tl.constexpr,
  328. has_expert_map: tl.constexpr,
  329. ):
  330. route = tl.program_id(0)
  331. if route >= num_routes:
  332. return
  333. token = route // topk
  334. route_slot = route - token * topk
  335. live = tl.load(enabled).to(tl.int1) & ~tl.load(is_padding + token)
  336. expert = tl.load(topk_ids + route).to(tl.int32)
  337. live &= (expert >= 0) & (expert < num_experts)
  338. if has_expert_map:
  339. local_expert = tl.load(expert_map + expert, mask=live, other=-1).to(tl.int32)
  340. live &= local_expert >= 0
  341. current_epoch = tl.load(epoch).to(tl.uint64)
  342. key = current_epoch ^ route.to(tl.uint64) ^ (expert.to(tl.uint64) << 20)
  343. selected = (_mix64(key) % sample_rate) == 0
  344. live &= selected
  345. slot = tl.atomic_add(cursor, 1, mask=live)
  346. accepted = live & (slot < capacity)
  347. tl.store(slots + route, tl.where(accepted, slot, -1))
  348. if live & ~accepted:
  349. tl.atomic_add(dropped, 1)
  350. if accepted:
  351. weight = tl.load(topk_weights + route).to(tl.float32)
  352. observation = (
  353. (current_epoch << 32)
  354. | (token.to(tl.uint64) << 8)
  355. | route_slot.to(tl.uint64)
  356. )
  357. tl.store(sample_weight + slot, weight * weight)
  358. tl.store(sample_observation + slot, observation.to(tl.int64))
  359. tl.store(sample_expert + slot, expert)
  360. token_observation = (current_epoch << 32) | token.to(tl.uint64)
  361. split_key = token_observation ^ 0x6A09E667F3BCC909
  362. split = (_mix64(split_key) % validation_modulus) == 0
  363. tl.store(sample_split + slot, split.to(tl.int8))
  364. def collect_route_input(
  365. x: torch.Tensor,
  366. topk_weights: torch.Tensor,
  367. topk_ids: torch.Tensor,
  368. is_padding: torch.Tensor,
  369. buffers: RouteInputBuffers,
  370. *,
  371. num_experts: int,
  372. expert_begin: int,
  373. expert_end: int,
  374. moment_sample_rate: int,
  375. hessian_sample_rate: int,
  376. validation_modulus: int,
  377. collect_routing: bool,
  378. ) -> None:
  379. """Collect exact routing, input diagonal moments, and raw H13 samples."""
  380. if x.ndim != 2 or topk_ids.shape != topk_weights.shape:
  381. raise ValueError("invalid route/input calibration shapes")
  382. if topk_ids.shape[0] != x.shape[0] or is_padding.numel() < x.shape[0]:
  383. raise ValueError("route/input calibration token dimensions differ")
  384. m, width = map(int, x.shape)
  385. topk = int(topk_ids.shape[1])
  386. if topk > 32:
  387. raise ValueError("raw route sampling supports at most 32 experts per token")
  388. _advance_epoch_kernel[(1,)](buffers.enabled, buffers.epoch_counter, buffers.epoch)
  389. _route_stats_kernel[(m,)](
  390. topk_ids,
  391. topk_weights,
  392. is_padding,
  393. buffers.enabled,
  394. buffers.tokens_routed,
  395. buffers.gate_sum,
  396. buffers.gate_sq_sum,
  397. num_tokens=m,
  398. topk=topk,
  399. num_experts=int(num_experts),
  400. collect_routing=bool(collect_routing),
  401. BLOCK_TOPK=triton.next_power_of_2(topk),
  402. )
  403. block = 128
  404. _input_moment_kernel[(m, triton.cdiv(width, block))](
  405. x,
  406. topk_ids,
  407. topk_weights,
  408. is_padding,
  409. buffers.enabled,
  410. buffers.epoch,
  411. buffers.input_sq_sum,
  412. buffers.input_weight_sum,
  413. buffers.input_count,
  414. num_tokens=m,
  415. topk=topk,
  416. width=width,
  417. expert_begin=int(expert_begin),
  418. expert_end=int(expert_end),
  419. sample_rate=max(int(moment_sample_rate), 1),
  420. BLOCK=block,
  421. )
  422. if buffers.sample_values.numel():
  423. capacity = int(buffers.sample_values.shape[0])
  424. if tuple(buffers.sample_experts.shape) != (capacity, topk):
  425. raise ValueError("sample_experts must have shape [capacity, topk]")
  426. if tuple(buffers.sample_gates.shape) != (capacity, topk):
  427. raise ValueError("sample_gates must have shape [capacity, topk]")
  428. if buffers.sample_split.numel() != capacity:
  429. raise ValueError("sample_split must have one entry per sample slot")
  430. buffers.sample_slots[:m].fill_(-1)
  431. _input_sample_meta_kernel[(m,)](
  432. topk_ids,
  433. topk_weights,
  434. is_padding,
  435. buffers.enabled,
  436. buffers.epoch,
  437. buffers.sample_cursor,
  438. buffers.sample_dropped,
  439. buffers.sample_slots,
  440. buffers.sample_weight,
  441. buffers.sample_observation,
  442. buffers.sample_experts,
  443. buffers.sample_gates,
  444. buffers.sample_split,
  445. num_tokens=m,
  446. topk=topk,
  447. capacity=capacity,
  448. sample_rate=max(int(hessian_sample_rate), 1),
  449. validation_modulus=max(int(validation_modulus), 1),
  450. )
  451. _sample_values_kernel[(m, triton.cdiv(width, block))](
  452. x,
  453. buffers.sample_slots,
  454. buffers.sample_values,
  455. rows=m,
  456. width=width,
  457. source_stride=width,
  458. BLOCK=block,
  459. )
  460. def collect_paired_token_rows(
  461. source: torch.Tensor,
  462. sample_slots: torch.Tensor,
  463. sample_values: torch.Tensor,
  464. sample_ready: torch.Tensor,
  465. ) -> None:
  466. """Copy rows selected by a preceding ``collect_route_input`` call."""
  467. if source.ndim != 2 or sample_values.ndim != 2:
  468. raise ValueError("paired token samples require rank-2 tensors")
  469. rows, width = map(int, source.shape)
  470. if int(sample_slots.numel()) < rows:
  471. raise ValueError("sample_slots is smaller than the paired source")
  472. if int(sample_values.shape[1]) != width:
  473. raise ValueError("paired sample width differs from its source")
  474. if int(sample_ready.numel()) != int(sample_values.shape[0]):
  475. raise ValueError("sample_ready must have one entry per sample slot")
  476. if not sample_values.numel():
  477. return
  478. block = 128
  479. _paired_sample_values_kernel[(rows, triton.cdiv(width, block))](
  480. source,
  481. sample_slots,
  482. sample_values,
  483. sample_ready,
  484. rows=rows,
  485. width=width,
  486. BLOCK=block,
  487. )
  488. def unscale_route_rows_(
  489. values: torch.Tensor,
  490. topk_ids: torch.Tensor,
  491. expert_map: torch.Tensor,
  492. scales: torch.Tensor,
  493. *,
  494. num_experts: int,
  495. scale_stride: int,
  496. scale_offset: int = 0,
  497. ) -> None:
  498. """Undo an expert-local diagonal scale on route-major rows in place."""
  499. if values.ndim != 2 or topk_ids.ndim != 2:
  500. raise ValueError("route unscale requires rank-2 values and top-k IDs")
  501. routes = int(topk_ids.numel())
  502. width = int(values.shape[1])
  503. if int(values.shape[0]) != routes:
  504. raise ValueError("route unscale values must contain one row per top-k route")
  505. if int(expert_map.numel()) != int(num_experts):
  506. raise ValueError("expert_map length must equal num_experts")
  507. if int(scale_stride) < width or int(scale_offset) < 0:
  508. raise ValueError("invalid route unscale stride/offset")
  509. if scales.numel() < int(scale_offset) + width:
  510. raise ValueError("route unscale scale table is too small")
  511. block = 128
  512. _unscale_route_rows_kernel[(routes, triton.cdiv(width, block))](
  513. values,
  514. topk_ids,
  515. expert_map,
  516. scales,
  517. num_routes=routes,
  518. num_experts=int(num_experts),
  519. width=width,
  520. scale_stride=int(scale_stride),
  521. scale_offset=int(scale_offset),
  522. BLOCK=block,
  523. )
  524. def collect_mid(
  525. source: torch.Tensor,
  526. topk_weights: torch.Tensor,
  527. topk_ids: torch.Tensor,
  528. expert_map: torch.Tensor | None,
  529. is_padding: torch.Tensor,
  530. buffers: MidBuffers,
  531. *,
  532. num_experts: int,
  533. width: int,
  534. source_stride: int,
  535. moment_sample_rate: int,
  536. hessian_sample_rate: int,
  537. validation_modulus: int,
  538. ) -> None:
  539. """Collect canonical w2-input moments/samples from route-indexed scratch."""
  540. m, topk = map(int, topk_ids.shape)
  541. routes = m * topk
  542. if topk_weights.shape != topk_ids.shape or is_padding.numel() < m:
  543. raise ValueError("invalid mid calibration routing shapes")
  544. if source.numel() < routes * int(source_stride):
  545. raise ValueError("canonical activation source is smaller than route geometry")
  546. has_map = expert_map is not None
  547. if expert_map is None:
  548. expert_map = buffers.sample_expert # valid non-null placeholder
  549. elif int(expert_map.numel()) != int(num_experts):
  550. raise ValueError("expert_map length must equal num_experts")
  551. block = 128
  552. _mid_moment_kernel[(m, triton.cdiv(int(width), block))](
  553. source,
  554. topk_ids,
  555. topk_weights,
  556. expert_map,
  557. is_padding,
  558. buffers.enabled,
  559. buffers.epoch,
  560. buffers.mid_sq_sum,
  561. buffers.mid_weight_sum,
  562. buffers.mid_count,
  563. num_tokens=m,
  564. topk=topk,
  565. num_experts=int(num_experts),
  566. width=int(width),
  567. source_stride=int(source_stride),
  568. sample_rate=max(int(moment_sample_rate), 1),
  569. has_expert_map=has_map,
  570. BLOCK=block,
  571. )
  572. if buffers.sample_values.numel():
  573. capacity = int(buffers.sample_values.shape[0])
  574. if buffers.sample_split.numel() != capacity:
  575. raise ValueError("sample_split must have one entry per sample slot")
  576. buffers.sample_slots[:routes].fill_(-1)
  577. _mid_sample_meta_kernel[(routes,)](
  578. topk_ids,
  579. topk_weights,
  580. expert_map,
  581. is_padding,
  582. buffers.enabled,
  583. buffers.epoch,
  584. buffers.sample_cursor,
  585. buffers.sample_dropped,
  586. buffers.sample_slots,
  587. buffers.sample_weight,
  588. buffers.sample_observation,
  589. buffers.sample_expert,
  590. buffers.sample_split,
  591. num_routes=routes,
  592. topk=topk,
  593. num_experts=int(num_experts),
  594. capacity=capacity,
  595. sample_rate=max(int(hessian_sample_rate), 1),
  596. validation_modulus=max(int(validation_modulus), 1),
  597. has_expert_map=has_map,
  598. )
  599. _sample_values_kernel[(routes, triton.cdiv(int(width), block))](
  600. source,
  601. buffers.sample_slots,
  602. buffers.sample_values,
  603. rows=routes,
  604. width=int(width),
  605. source_stride=int(source_stride),
  606. BLOCK=block,
  607. )
  608. __all__ = [
  609. "MidBuffers",
  610. "RouteInputBuffers",
  611. "collect_mid",
  612. "collect_paired_token_rows",
  613. "collect_route_input",
  614. "unscale_route_rows_",
  615. ]