Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PYBIND11_ENABLE_EXTRAS ON)

# Options for backends.
set(INFINI_OPS_DECLARATIONS "" CACHE STRING "Additional public operator APIs, even without active providers")

option(WITH_CPU "Enable CPU backend" OFF)
option(WITH_NVIDIA "Enable CUDA backend" OFF)
option(WITH_ILUVATAR "Enable Iluvatar GPU backend" OFF)
Expand Down Expand Up @@ -419,10 +421,10 @@ if(WITH_NINETOOTHED)
endif()

if(WITH_TRITON)
if(NOT WITH_NVIDIA)
if(NOT WITH_NVIDIA AND NOT WITH_HYGON)
message(
FATAL_ERROR
"`WITH_TRITON` requires `WITH_NVIDIA=ON` because NVIDIA is the only implemented Triton JIT backend."
"`WITH_TRITON` requires `WITH_NVIDIA=ON` or `WITH_HYGON=ON`."
)
endif()
if(NOT GENERATE_PYTHON_BINDINGS)
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,6 @@ Useful entry points:
## License

See [LICENSE](LICENSE).

Hygon Clover diffusion and native Triton JIT setup are described in
[the Clover operator guide](docs/clover-hygon.md).
8 changes: 8 additions & 0 deletions THIRD_PARTY.txt
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,11 @@ from actual package metadata. That conservative package inventory can include
materials for components outside these two DSOs; it is intentionally not filtered
by fixed hashes or used to restrict build capability. A complete wheel or SDK
redistribution must retain its own applicable notices, including secondary ones.

Clover preview kernels
src/triton/ops/clover_block_conv1d/jit.py and
src/triton/ops/clover_local_memory/jit.py are adapted from SGLang PR #46927,
WilliamZR/sglang commit 058939af24d5cdc44bb4b8852d0ddfab555ed71e,
python/sglang/kernels/ops/{mamba/clover_conv_triton.py,attention/clover_gdn_triton.py}.
They retain Apache-2.0 licensing. Changes provide InfiniOps JIT entry points,
Hygon launch geometry, and bounds checks for the state pool.
77 changes: 77 additions & 0 deletions docs/clover-hygon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Hygon Clover operators

These operators support the companion InfiniLM Clover implementation. Model
forward stays in C++; Hygon driver modules launch warmed Triton binaries.
Compilation uses the installed DTK Triton HIP target (for example `gfx936`,
wave size 64), not CUDA compute-capability arithmetic.

## Build

Install an InfiniRT revision containing the Hygon driver interface first
([InfiniRT PR #52](https://github.com/InfiniTensor/InfiniRT/pull/52)). Then:

```sh
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX="$INFINI_ROOT" -DINFINI_RT_ROOT="$INFINI_ROOT" \
-DWITH_CPU=ON -DWITH_HYGON=ON -DHYGON_ARCH=gfx936 -DWITH_TRITON=ON \
-DGENERATE_PYTHON_BINDINGS=ON -DAUTO_DETECT_DEVICES=OFF \
-DAUTO_DETECT_BACKENDS=OFF
cmake --build build -j 12
cmake --install build
```

The Python `infini.triton` package must be importable. CMake installs its package
contents at the prefix, while wheel installation supplies the `infini` namespace.
Include DTK's CUDA compatibility library directory in `LD_LIBRARY_PATH`.

`INFINI_OPS_DECLARATIONS` accepts a semicolon-separated list of additional public
operator APIs. These can have no provider in the selected build: capability
queries return unsupported, and execution is rejected. This allows a downstream
application to compile its complete model registry without claiming that every
registered model runs on Hygon. InfiniLM supplies a matching declaration preset.

## Operator contracts and source alignment

The Clover-specific APIs target the released Clover model equations and state
layout, rather than claiming to be PyTorch standard operators. Preview kernel
provenance and licensing are recorded in `THIRD_PARTY.txt`.

| Operator | Inputs and behavior |
| --- | --- |
| `CloverBlockConv1d` | Packed `[tokens,channels]` BF16/FP16 input, `[slots,channels,3]` clean history, slot indices, left `[channels,1,4]` and learned right `[channels,1,3]` weights, int8 validity, block width up to 32. Produces SiLU output without updating history. |
| `CloverLocalMemory` | Q/K/V `[1,tokens,heads,128]`, FP32 gates and `[slots,value_heads,128,128]` state, slot indices, validity and three per-head scalars. Implements bidirectional block attention plus the decayed clean-history bucket. Does not update state. |
| `CloverRecurrentGdn` | Packed Q/K/V, gates/beta, sequence offsets and initial/final slot vectors. Implements normalized causal delta recurrence with FP32 persistent state; writes sequence output and final state. Input/final slot indices must be valid and final destinations must not race. |
| `CloverCoreset` | Contiguous `[tokens,256]` router logits, block width and routing mass. Keeps the stable top-p support of aggregated per-token top-8 scores and masks other logits. Requires an explicit workspace. |
| `CloverLiveMask` | Current/previous int64 token IDs and the mask ID. Marks rows live if either token is masked, or refresh is forced; updates previous IDs. |
| `CloverMaskRoutes` | Replaces inactive rows' int32 expert IDs with the out-of-range sentinel `num_experts`. |
| `CloverMergeMoe` | Selects fresh routed output for live rows and cached output for other rows; updates the cache. Output and fresh input may alias. |
| `MoeFusedDense` (Hygon) | Existing packed dense-MoE API, with BF16/FP16 weights, top-k weights/IDs and aligned expert metadata. Triton computes gate/up, fused SiLU/down, and weighted reduction. Invalid routes contribute zero; alignment block size 16 is supported and expert biases are unsupported. Requires an explicit workspace. |

The live-row protocol is an optional approximation for iterative diffusion,
adapted from the supplied dllm-h100 TV6 study. It is not a general guarantee that
fixed token IDs imply unchanged hidden states. InfiniLM refreshes every five
previews and always runs full causal replay before committing state.

The standard `MoeAlignBlockSize`, causal convolution, gating, cast, fill,
multiplication, triangular masks and rotary providers also gain Hygon coverage.
They reuse this repository's existing native CUDA-like implementations.

## Validation

Actual BW/DTK runs compare preview operators to independent Torch equations and
verify bitwise state immutability, mixed-length causal state commits, coreset
selection, dense MoE, liveness and cache merging. Public-header and wrapper/JIT
unit tests cover the HIP architecture string and metadata without CUDA scratch
fields. The companion InfiniLM campaign runs full checkpoint generation.

Workspace-bearing operators are invoked through the C++ API with an owning
workspace. The native test adapter in `tests/native/clover_workspace_smoke.cpp`
exercises that path. A missing workspace is rejected before launching a kernel.

Build and run the workspace adapter against the installed SDK:

```sh
python tests/native/build_clover_workspace.py
PYTHONPATH="build/clover-tests:$PYTHONPATH" python tests/native/check_clover_workspace.py
python -m pytest tests/test_clover_preview.py tests/test_clover_live_rows.py -q
```
6 changes: 6 additions & 0 deletions docs/operator-api-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,9 @@ unless callers need it.
When applying these rules, record the selected upstream interface and any
intentional C++ adaptations in the pull request. Follow `CONTRIBUTING.md` for
compatibility, testing, and validation requirements.

## Clover diffusion state operators

The `Clover*` operators use the released Clover model equations, with packed
request/state-pool arguments for inference. Their exact contracts and source
provenance are documented in [the Hygon Clover guide](clover-hygon.md).
11 changes: 11 additions & 0 deletions scripts/generate_wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2293,6 +2293,12 @@ def _dispatch_gen_batch_size():
help="Include Triton backend implementations.",
)

parser.add_argument(
"--declarations",
nargs="*",
default=[],
help="Also emit operator APIs with no active provider.",
)
args = parser.parse_args()

for directory in (_BINDINGS_DIR, _GENERATED_SRC_DIR, _INCLUDE_DIR):
Expand All @@ -2309,6 +2315,11 @@ def _dispatch_gen_batch_size():
with_native_w4=args.with_native_w4,
)

for name in _normalize_op_allowlist(args.declarations):
if not (_BASE_DIR / f"{name}.h").is_file():
raise ValueError(f"Unknown operator declaration: {name}")
ops.setdefault(name, [])

if config_path is not None:
config = ops_config.load_ops_config(config_path)
ops = _select_ops_from_config(ops, config, config_path)
Expand Down
8 changes: 8 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ if(WITH_TRITON OR GENERATE_PYTHON_BINDINGS)
find_package(pybind11 CONFIG REQUIRED)
endif()

if(WITH_TRITON AND WITH_HYGON)
target_link_directories(infiniops PRIVATE "${HYGON_CUDA_ROOT}/lib64")
target_link_libraries(infiniops PRIVATE cuda)
endif()

if(WITH_TRITON)
target_link_libraries(infiniops PRIVATE
Python::Python
Expand Down Expand Up @@ -1290,6 +1295,9 @@ if(GENERATE_OPERATOR_CALL_INSTANTIATIONS OR GENERATE_PYTHON_BINDINGS)
# failures.

set(GENERATOR_ARGS --devices ${DEVICE_LIST})
if(INFINI_OPS_DECLARATIONS)
list(APPEND GENERATOR_ARGS --declarations ${INFINI_OPS_DECLARATIONS})
endif()
if(_infini_ops_ops_config)
list(APPEND GENERATOR_ARGS
--ops-config "${_infini_ops_ops_config}")
Expand Down
49 changes: 49 additions & 0 deletions src/base/clover_block_conv1d.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#pragma once

#include "operator.h"

namespace infini::ops {

// Clover preview convolution reads a clean prefix without updating its state.
class CloverBlockConv1d : public Operator<CloverBlockConv1d> {
public:
CloverBlockConv1d(const Tensor input, const Tensor state,
const Tensor indices, const Tensor weight,
const Tensor right_weight, const Tensor valid,
size_t block_size, Tensor out) {
assert((input.ndim() == 2 && out.shape() == input.shape() &&
state.ndim() == 3 && weight.ndim() == 3 &&
weight.shape() == Tensor::Shape{input.size(1), 1, 4} &&
right_weight.shape() == Tensor::Shape{input.size(1), 1, 3} &&
state.size(1) == input.size(1) && state.size(2) == 3 &&
indices.ndim() == 1 && block_size > 0 && block_size <= 32 &&
input.size(0) == indices.numel() * block_size &&
valid.shape() == Tensor::Shape{input.size(0)}) &&
"CloverBlockConv1d: invalid block or state shape.");
assert((input.dtype() == DataType::kBFloat16 ||
input.dtype() == DataType::kFloat16) &&
"CloverBlockConv1d: expected BF16 or FP16 input.");
for (const auto& tensor : {state, weight, right_weight, out}) {
assert(tensor.dtype() == input.dtype() &&
"CloverBlockConv1d: input and weight dtypes must match.");
}
assert((indices.dtype() == DataType::kInt32 ||
indices.dtype() == DataType::kInt64) &&
indices.IsContiguous() && valid.dtype() == DataType::kInt8 &&
valid.IsContiguous() &&
"CloverBlockConv1d: expected contiguous indices and int8 validity.");
for (const auto& tensor :
{state, indices, weight, right_weight, valid, out}) {
assert(tensor.device() == input.device() &&
"CloverBlockConv1d: device mismatch.");
}
assert(!out.HasBroadcastDim() && "CloverBlockConv1d: overlapping output.");
}

virtual void operator()(const Tensor input, const Tensor state,
const Tensor indices, const Tensor weight,
const Tensor right_weight, const Tensor valid,
size_t block_size, Tensor out) const = 0;
};

} // namespace infini::ops
23 changes: 23 additions & 0 deletions src/base/clover_coreset.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include "operator.h"

namespace infini::ops {

class CloverCoreset : public Operator<CloverCoreset> {
public:
CloverCoreset(const Tensor logits, size_t block_size, double top_p,
Tensor out) {
assert(logits.ndim() == 2 && logits.size(1) == 256 && block_size > 0 &&
block_size <= 32 && logits.size(0) % block_size == 0 &&
out.shape() == logits.shape() && out.dtype() == logits.dtype() &&
logits.IsContiguous() && out.IsContiguous() &&
logits.device() == out.device() && top_p > 0 && top_p <= 1 &&
"CloverCoreset: invalid logits, block size or routing mass.");
}

virtual void operator()(const Tensor logits, size_t block_size, double top_p,
Tensor out) const = 0;
};

} // namespace infini::ops
22 changes: 22 additions & 0 deletions src/base/clover_live_mask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once
#include "operator.h"

namespace infini::ops {

class CloverLiveMask : public Operator<CloverLiveMask> {
public:
CloverLiveMask(const Tensor ids, Tensor previous, int64_t mask_id,
bool refresh, Tensor live) {
assert(ids.IsContiguous() && previous.IsContiguous() &&
live.IsContiguous() && ids.numel() == previous.numel() &&
ids.numel() == live.numel() && ids.dtype() == DataType::kInt64 &&
previous.dtype() == ids.dtype() && live.dtype() == DataType::kInt8 &&
ids.device() == previous.device() && ids.device() == live.device() &&
"CloverLiveMask: invalid token state.");
}

virtual void operator()(const Tensor ids, Tensor previous, int64_t mask_id,
bool refresh, Tensor live) const = 0;
};

} // namespace infini::ops
59 changes: 59 additions & 0 deletions src/base/clover_local_memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#pragma once

#include "operator.h"

namespace infini::ops {

class CloverLocalMemory : public Operator<CloverLocalMemory> {
public:
CloverLocalMemory(const Tensor q, const Tensor k, const Tensor v,
const Tensor g, const Tensor state, const Tensor indices,
const Tensor valid, const Tensor logit_scale,
const Tensor history_bias, const Tensor history_c,
size_t block_size, Tensor out) {
assert((q.ndim() == 4 && q.size(0) == 1 && k.shape() == q.shape() &&
v.ndim() == 4 && v.size(0) == 1 && v.size(1) == q.size(1) &&
q.size(2) > 0 && v.size(2) > 0 && v.size(2) % q.size(2) == 0 &&
q.size(3) == 128 && v.size(3) == 128 && out.shape() == v.shape() &&
state.ndim() == 4 && state.size(1) == v.size(2) &&
state.size(2) == 128 && state.size(3) == 128 &&
indices.ndim() == 1 && block_size > 0 && block_size <= 32 &&
q.size(1) == indices.numel() * block_size &&
g.shape() == Tensor::Shape{1, q.size(1), v.size(2)} &&
valid.shape() == Tensor::Shape{q.size(1)}) &&
"CloverLocalMemory: invalid sequence, head or state shape.");
assert(
(q.dtype() == DataType::kBFloat16 || q.dtype() == DataType::kFloat16) &&
k.dtype() == q.dtype() && v.dtype() == q.dtype() &&
out.dtype() == q.dtype() && state.dtype() == DataType::kFloat32 &&
g.dtype() == DataType::kFloat32 &&
"CloverLocalMemory: expected reduced data and FP32 state/gates.");
assert((indices.dtype() == DataType::kInt32 ||
indices.dtype() == DataType::kInt64) &&
indices.IsContiguous() && valid.dtype() == DataType::kInt8 &&
valid.IsContiguous() &&
"CloverLocalMemory: invalid index/validity dtype.");
for (const auto& scalar : {logit_scale, history_bias, history_c}) {
assert(scalar.ndim() == 1 && scalar.numel() == v.size(2) &&
scalar.IsContiguous() &&
(scalar.dtype() == q.dtype() ||
scalar.dtype() == DataType::kFloat32) &&
"CloverLocalMemory: invalid per-head scalars.");
}
for (const auto& tensor : {k, v, g, state, indices, valid, logit_scale,
history_bias, history_c, out}) {
assert(tensor.device() == q.device() &&
"CloverLocalMemory: device mismatch.");
}
assert(!out.HasBroadcastDim() && "CloverLocalMemory: overlapping output.");
}

virtual void operator()(const Tensor q, const Tensor k, const Tensor v,
const Tensor g, const Tensor state,
const Tensor indices, const Tensor valid,
const Tensor logit_scale, const Tensor history_bias,
const Tensor history_c, size_t block_size,
Tensor out) const = 0;
};

} // namespace infini::ops
22 changes: 22 additions & 0 deletions src/base/clover_mask_routes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#pragma once
#include "operator.h"

namespace infini::ops {

class CloverMaskRoutes : public Operator<CloverMaskRoutes> {
public:
CloverMaskRoutes(const Tensor ids, const Tensor live, int64_t num_experts,
Tensor out) {
assert(ids.ndim() == 2 && ids.IsContiguous() && live.IsContiguous() &&
out.IsContiguous() && live.numel() == ids.size(0) &&
out.shape() == ids.shape() && ids.dtype() == DataType::kInt32 &&
out.dtype() == ids.dtype() && live.dtype() == DataType::kInt8 &&
ids.device() == live.device() && ids.device() == out.device() &&
"CloverMaskRoutes: invalid routes.");
}

virtual void operator()(const Tensor ids, const Tensor live,
int64_t num_experts, Tensor out) const = 0;
};

} // namespace infini::ops
24 changes: 24 additions & 0 deletions src/base/clover_merge_moe.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once
#include "operator.h"

namespace infini::ops {

class CloverMergeMoe : public Operator<CloverMergeMoe> {
public:
CloverMergeMoe(const Tensor fresh, const Tensor live, Tensor cache,
Tensor out) {
assert(fresh.ndim() == 2 && live.numel() == fresh.size(0) &&
cache.shape() == fresh.shape() && out.shape() == fresh.shape() &&
cache.dtype() == fresh.dtype() && out.dtype() == fresh.dtype() &&
live.dtype() == DataType::kInt8 && fresh.IsContiguous() &&
cache.IsContiguous() && out.IsContiguous() && live.IsContiguous() &&
fresh.device() == live.device() &&
fresh.device() == cache.device() && fresh.device() == out.device() &&
"CloverMergeMoe: invalid cached rows.");
}

virtual void operator()(const Tensor fresh, const Tensor live, Tensor cache,
Tensor out) const = 0;
};

} // namespace infini::ops
Loading