Skip to content

Commit 15b4184

Browse files
authored
Fix hstu inference cudagraph capture on sm100 (#463)
* Fix hstu inference cudagraph capture on sm100 * Add fix for inference utest * Clean up code format
1 parent 9a3bf5d commit 15b4184

14 files changed

Lines changed: 265 additions & 69 deletions

File tree

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@
88
[submodule "third_party/FlexKV"]
99
path = third_party/FlexKV
1010
url = https://github.com/taco-project/FlexKV.git
11+
[submodule "third_party/nv-embedding-cache"]
12+
path = third_party/nv-embedding-cache
13+
url = https://github.com/NVIDIA/nv-embedding-cache.git

corelib/dynamicemb/dynamicemb/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
# limitations under the License.
1515

1616
from .dump_load import DynamicEmbDump, DynamicEmbLoad
17-
from .incremental_dump import DeltaDumpResult, pop_evicted_keys
1817
from .dynamicemb_config import (
1918
BATCH_SIZE_PER_DUMP,
2019
DynamicEmbCheckMode,
@@ -33,6 +32,7 @@
3332
string_to_evict_strategy,
3433
)
3534
from .embedding_admission import FrequencyAdmissionStrategy, KVCounter
35+
from .incremental_dump import DeltaDumpResult, pop_evicted_keys
3636
from .optimizer import EmbOptimType, OptimizerArgs
3737
from .types import (
3838
BUCKET_ALIGNMENT,

corelib/dynamicemb/dynamicemb/jit/score_jit.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,9 @@ def _remap_score_function(fn, perm):
9494
tree = ast.parse(src)
9595
func = tree.body[0]
9696
if not isinstance(func, ast.FunctionDef) or not func.args.args:
97-
raise TypeError("score_function must be a plain function taking "
98-
"(scores, cur_timestamp).")
97+
raise TypeError(
98+
"score_function must be a plain function taking " "(scores, cur_timestamp)."
99+
)
99100
func.decorator_list = [] # don't re-apply decorators when we recompile
100101
scores_name = func.args.args[0].arg
101102
n = len(perm)
@@ -154,9 +155,7 @@ def score_function_key(fn, perm, cc_major: int, cc_minor: int) -> int:
154155
return key or 1
155156

156157

157-
def register_score_function(
158-
fn, score_strategy, cc_major: int, cc_minor: int
159-
) -> int:
158+
def register_score_function(fn, score_strategy, cc_major: int, cc_minor: int) -> int:
160159
"""numba-compile fn -> LTO-IR, link into the custom cubin, cache under its
161160
key. Returns the key to pass as score_fn_key on inserts. Idempotent.
162161

corelib/dynamicemb/dynamicemb/key_value_table.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,9 +1113,7 @@ def _pop_state_evicted_keys(state: DynamicEmbTableState, table_id: int) -> torch
11131113
# (== the table's index_type, which may be int32/uint32), so a hardcoded
11141114
# int64 here would make callers that concat/compare across empty and
11151115
# non-empty pops hit a dtype mismatch.
1116-
return torch.empty(
1117-
0, dtype=state.key_index_map.key_type, device=state.device
1118-
)
1116+
return torch.empty(0, dtype=state.key_index_map.key_type, device=state.device)
11191117
keys = torch.cat(state.evicted_key_chunks)
11201118
tids = torch.cat(state.evicted_tid_chunks)
11211119
mask = tids == table_id

corelib/dynamicemb/setup.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,9 @@ def get_version():
108108
# DEMB_ARCHS="80;90;100".
109109
DEMB_ARCHS = [
110110
a.strip()
111-
for a in os.environ.get("DEMB_ARCHS", "75;80;90;100;120").replace(",", ";").split(";")
111+
for a in os.environ.get("DEMB_ARCHS", "75;80;90;100;120")
112+
.replace(",", ";")
113+
.split(";")
112114
if a.strip()
113115
]
114116

@@ -188,7 +190,6 @@ def get_extensions():
188190
readme = f.read()
189191
import time
190192

191-
192193
EVICT_TU = "src/jit/evict_lrulfu.cu"
193194

194195

@@ -211,18 +212,31 @@ def compile_evict_fatbins():
211212
# compiles the same kernels.cuh / <cub/cub.cuh> / cooperative_groups headers,
212213
# so it needs the same relaxed-constexpr + extended-lambda support to stay
213214
# buildable across CUDA versions.
214-
common = ["-std=c++17", "-O3", "--use_fast_math",
215-
"--expt-relaxed-constexpr", "--expt-extended-lambda",
216-
f"-I{root_path / 'src'}"]
215+
common = [
216+
"-std=c++17",
217+
"-O3",
218+
"--use_fast_math",
219+
"--expt-relaxed-constexpr",
220+
"--expt-extended-lambda",
221+
f"-I{root_path / 'src'}",
222+
]
217223

218224
variants = [
219225
("LexFreqTsComparator", "evict_lrulfu_lex.fatbin", "sm"),
220226
("UserFnComparator", "evict_lrulfu_custom.fatbin", "lto"),
221227
]
222228
for comparator, out_name, code_kind in variants:
223229
out = str(out_dir / out_name)
224-
cmd = ([nvcc, "--fatbin", *_gencode_flags(code_kind), *common,
225-
f"-DDEMB_EVICT_COMPARATOR={comparator}", src, "-o", out])
230+
cmd = [
231+
nvcc,
232+
"--fatbin",
233+
*_gencode_flags(code_kind),
234+
*common,
235+
f"-DDEMB_EVICT_COMPARATOR={comparator}",
236+
src,
237+
"-o",
238+
out,
239+
]
226240
print(f"[dynamicemb] nvcc evict fatbin ({comparator}): {' '.join(cmd)}")
227241
subprocess.run(cmd, check=True)
228242
print(f"[dynamicemb] {out_name}: {os.path.getsize(out)} bytes")

corelib/dynamicemb/test/unit_tests/table_operation/test_lru_lfu.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -450,12 +450,16 @@ def test_lru_lfu_custom_score_function_ranks_by_its_dimension(
450450
probes = {"OLD": (keys[:1], tids[:1]), "HF": (keys[1:2], tids[1:2])}
451451

452452
table.lookup(
453-
keys[1:], tids[1:], ScoreArg(name="frequency", value=ones[1:], policy=ScorePolicy.LRU_LFU)
453+
keys[1:],
454+
tids[1:],
455+
ScoreArg(name="frequency", value=ones[1:], policy=ScorePolicy.LRU_LFU),
454456
)
455457
torch.cuda.synchronize()
456458
for _ in range(28):
457459
table.lookup(
458-
keys[1:2], tids[1:2], ScoreArg(name="frequency", value=ones[1:2], policy=ScorePolicy.LRU_LFU)
460+
keys[1:2],
461+
tids[1:2],
462+
ScoreArg(name="frequency", value=ones[1:2], policy=ScorePolicy.LRU_LFU),
459463
)
460464
torch.cuda.synchronize()
461465

@@ -477,12 +481,18 @@ def test_lru_lfu_custom_score_function_ranks_by_its_dimension(
477481
ev_keys, ev_tids = probes[evicted]
478482
sv_keys, sv_tids = probes[survivor]
479483
_, ev_found, _ = table.lookup(
480-
ev_keys, ev_tids, ScoreArg(name="frequency", value=None, policy=ScorePolicy.CONST)
484+
ev_keys,
485+
ev_tids,
486+
ScoreArg(name="frequency", value=None, policy=ScorePolicy.CONST),
481487
)
482488
_, sv_found, _ = table.lookup(
483-
sv_keys, sv_tids, ScoreArg(name="frequency", value=None, policy=ScorePolicy.CONST)
489+
sv_keys,
490+
sv_tids,
491+
ScoreArg(name="frequency", value=None, policy=ScorePolicy.CONST),
484492
)
485-
assert not torch.any(ev_found), f"{score_fn.__name__} should evict the {evicted} probe"
493+
assert not torch.any(
494+
ev_found
495+
), f"{score_fn.__name__} should evict the {evicted} probe"
486496
assert torch.all(sv_found), f"{score_fn.__name__} should keep the {survivor} probe"
487497

488498

@@ -506,7 +516,9 @@ def test_lru_lfu_decay_matches_python_oracle(current_device):
506516
one, one_tid, one_val = keys[i : i + 1], tids[i : i + 1], ones[i : i + 1]
507517
for _ in range(reps):
508518
table.lookup(
509-
one, one_tid, ScoreArg(name="frequency", value=one_val, policy=ScorePolicy.LRU_LFU)
519+
one,
520+
one_tid,
521+
ScoreArg(name="frequency", value=one_val, policy=ScorePolicy.LRU_LFU),
510522
)
511523
torch.cuda.synchronize()
512524

@@ -551,8 +563,8 @@ def test_lru_lfu_score_function_logical_order_remap(current_device):
551563
physical frequency, so on identical tables they evict the SAME keys."""
552564
device = torch.cuda.current_device()
553565

554-
def _run(fn, strat):
555-
table = _custom_table(fn, strat)
566+
def _run(fn, strat): # codespell:ignore strat
567+
table = _custom_table(fn, strat) # codespell:ignore strat
556568
n = 100
557569
keys = torch.arange(1, 1 + n, dtype=torch.int64, device=device)
558570
tids = torch.zeros(n, dtype=torch.int64, device=device)
@@ -561,7 +573,9 @@ def _run(fn, strat):
561573
# Give each key a distinct frequency (key i looked up n-1-i extra times).
562574
for r in range(1, n):
563575
table.lookup(
564-
keys[:r], tids[:r], ScoreArg(name="frequency", value=ones[:r], policy=ScorePolicy.LRU_LFU)
576+
keys[:r],
577+
tids[:r],
578+
ScoreArg(name="frequency", value=ones[:r], policy=ScorePolicy.LRU_LFU),
565579
)
566580
torch.cuda.synchronize()
567581
n_new = 40
@@ -758,7 +772,9 @@ def test_lru_lfu_default_evictor_timestamp_tiebreak(current_device):
758772
idx_parts = []
759773
for g in range(G):
760774
s = g * per
761-
idx_g, _ = _insert(table, keys[s : s + per], tids[s : s + per], ones[s : s + per])
775+
idx_g, _ = _insert(
776+
table, keys[s : s + per], tids[s : s + per], ones[s : s + per]
777+
)
762778
idx_parts.append(idx_g)
763779
torch.cuda.synchronize()
764780
idx = torch.cat(idx_parts)

docker/Dockerfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,14 +256,13 @@ RUN git clone https://github.com/jiayus-nvidia/flash-attention.git flash-attenti
256256
# -- Layer 4, 5: FlexKV and NVE. PYNVE_DISABLE_AVX512 only meaningful on x86;
257257
# leave it unset on arm64 (AVX512 doesn't exist there).
258258
COPY third_party/FlexKV /workspace/deps/FlexKV
259+
COPY third_party/nv-embedding-cache /workspace/deps/nve
259260
RUN cd FlexKV && mkdir -p build && cd build && \
260261
TORCH_CUDA_ARCH_LIST="7.5 8.0 8.6 9.0 10.0 12.0" cmake .. && \
261262
TORCH_CUDA_ARCH_LIST="7.5 8.0 8.6 9.0 10.0 12.0" cmake --build . -j8 && \
262263
cd .. && mkdir -p flexkv/lib && cp -P build/lib/*.so* flexkv/lib/ && \
263264
MAX_JOBS=8 FLEXKV_DEBUG=0 TORCH_CUDA_ARCH_LIST="7.5 8.0 8.6 9.0 10.0 12.0" python3 setup.py install && \
264-
cd /workspace/deps && \
265-
git clone -b v26.05 --recursive https://github.com/NVIDIA/nv-embedding-cache.git nve && \
266-
cd nve && git submodule update --init --recursive && \
265+
cd /workspace/deps/nve && \
267266
cd third_party/ && git clone https://github.com/NVIDIA/NVTX.git NVTX && cd .. && \
268267
if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then AVX512_ENV=""; else AVX512_ENV="PYNVE_DISABLE_AVX512=1"; fi && \
269268
env TORCH_CUDA_ARCH_LIST="7.5 8.0 8.6 9.0 10.0 12.0" PYNVE_WITH_TORCH_BINDINGS=1 ${AVX512_ENV} \
@@ -351,4 +350,3 @@ RUN cd ./examples/hstu && \
351350
inference_aoti/triton_aoti/hstu_gr_ranking_kvcache/config.pbtxt
352351

353352
WORKDIR /workspace/recsys-examples/
354-

examples/hstu/inference/triton/hstu_export_aligned/model.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ def initialize(self, args):
3838

3939
if self._max_batch_size < 1 or self._max_batch_size > 8:
4040
raise ValueError(
41-
"HSTU_MAX_BATCH_SIZE must be in [1, 8], got "
42-
f"{self._max_batch_size}"
41+
"HSTU_MAX_BATCH_SIZE must be in [1, 8], got " f"{self._max_batch_size}"
4342
)
4443
for required_path in (hstu_root, gin_config, checkpoint_dir):
4544
if not required_path.exists():
@@ -82,9 +81,7 @@ def initialize(self, args):
8281
data_processor._item_feature_name,
8382
data_processor._action_feature_name,
8483
]
85-
self._contextual_feature_names = list(
86-
data_processor._contextual_feature_names
87-
)
84+
self._contextual_feature_names = list(data_processor._contextual_feature_names)
8885
self._item_feature_name = data_processor._item_feature_name
8986
self._action_feature_name = data_processor._action_feature_name
9087
self._max_num_candidates = int(dataset_args.max_num_candidates)
@@ -195,9 +192,7 @@ def execute(self, requests):
195192
device=self._device, dtype=torch.int64
196193
)
197194

198-
batch = self._make_batch(
199-
values, lengths, num_candidates, batch_size
200-
)
195+
batch = self._make_batch(values, lengths, num_candidates, batch_size)
201196
with torch.inference_mode():
202197
logits = self._model(batch).float().cpu().numpy()
203198
responses.append(

examples/hstu/inference_aoti/export_inference_gr_ranking_kvcache.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -603,9 +603,7 @@ def forward(
603603
):
604604
rebuilt = self._rebuild_batch(values, lengths, num_candidates)
605605
logits, auxiliary_output = _split_model_outputs(
606-
self.inner(
607-
rebuilt, user_ids.cpu(), total_history_lengths.cpu()
608-
)
606+
self.inner(rebuilt, user_ids.cpu(), total_history_lengths.cpu())
609607
)
610608
if auxiliary_output is not None:
611609
raise RuntimeError(
@@ -748,9 +746,7 @@ def forward(
748746
dump_dir, f"batch_{dump_idx:06d}_compiled_logits.pt"
749747
),
750748
)
751-
print(
752-
f" [Batch {dump_idx + 1}] Dumped C++ replay tensors"
753-
)
749+
print(f" [Batch {dump_idx + 1}] Dumped C++ replay tensors")
754750
dump_idx += 1
755751
eval_module(logits.cuda(), batch.labels.values())
756752

examples/hstu/inference_aoti/test_tritonserver_aoti_hstu_model.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,7 @@ def _merge_samples(samples: Sequence[InputSample]) -> list[np.ndarray]:
179179
lengths = np.concatenate(
180180
[
181181
np.asarray(
182-
[
183-
sample.feature_lengths[feature_index]
184-
for sample in samples
185-
],
182+
[sample.feature_lengths[feature_index] for sample in samples],
186183
dtype=samples[0].feature_lengths.dtype,
187184
)
188185
for feature_index in range(num_features)
@@ -489,9 +486,11 @@ def main() -> int:
489486
print(f"Slept {args.post_warmup_sleep_seconds:.3f} seconds after warmup")
490487

491488
result = None
492-
for cache_set_index, user_id_offset, cache_set_input_cases in (
493-
cache_measurement_sets
494-
):
489+
for (
490+
cache_set_index,
491+
user_id_offset,
492+
cache_set_input_cases,
493+
) in cache_measurement_sets:
495494
print(
496495
f"Cache measurement set {cache_set_index}/"
497496
f"{CACHE_MEASUREMENT_SET_COUNT}: user_id_offset={user_id_offset}"

0 commit comments

Comments
 (0)