kenshin commited on
Commit
18d130e
·
1 Parent(s): fb3100d

Remove VeOmni runtime dependency

Browse files
README.md CHANGED
@@ -92,8 +92,6 @@ conda activate llada-image
92
  pip install -r requirements.txt
93
  ```
94
 
95
- The published LLaDA2 text encoder uses `veomni.ops.fused_moe_forward`. Install a compatible LLaDA2 / VeOmni runtime before running inference.
96
-
97
  ### 2. Run inference
98
 
99
  The pipeline accepts a prompt and, for editing, an optional reference image.
 
92
  pip install -r requirements.txt
93
  ```
94
 
 
 
95
  ### 2. Run inference
96
 
97
  The pipeline accepts a prompt and, for editing, an optional reference image.
text_encoder/fused_moe_ops.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Bytedance Ltd. and/or its affiliates
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Standalone, inference-only VeOmni v0.1.0 fused-MoE compatibility shim.
16
+
17
+ This module preserves the ``veomni.ops.fused_moe_forward`` call signature used
18
+ by VeOmni v0.1.0 while removing VeOmni's training, Expert Parallelism (EP), NPU,
19
+ and Seed-kernel dependencies. It is intended for single-device inference only.
20
+
21
+ The CUDA fast path uses a small Triton grouped-linear kernel. If Triton is not
22
+ available, the tensors are not on CUDA, or ``LLADA_MOE_BACKEND=eager`` is set,
23
+ the implementation falls back to ordinary PyTorch operations.
24
+
25
+ Replace the original model-code import with, for example,
26
+ ``from .fused_moe_v010 import fused_moe_forward``.
27
+
28
+ Derived from ByteDance-Seed/VeOmni v0.1.0.post1:
29
+ https://github.com/ByteDance-Seed/VeOmni/tree/v0.1.0.post1
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import os
35
+
36
+ import torch
37
+ import torch.nn.functional as F
38
+
39
+ try:
40
+ import triton
41
+ import triton.language as tl
42
+ except ImportError: # The eager fallback does not require Triton.
43
+ triton = None
44
+ tl = None
45
+
46
+
47
+ _SUPPORTED_TRITON_DTYPES = (torch.float16, torch.bfloat16)
48
+
49
+
50
+ if triton is not None:
51
+
52
+ @triton.jit
53
+ def _grouped_linear_kernel(
54
+ input_ptr,
55
+ weight_ptr,
56
+ output_ptr,
57
+ expert_cumsum_ptr,
58
+ N: tl.constexpr,
59
+ K: tl.constexpr,
60
+ BLOCK_M: tl.constexpr,
61
+ BLOCK_N: tl.constexpr,
62
+ BLOCK_K: tl.constexpr,
63
+ ):
64
+ """Compute per-expert ``input @ weight.T`` for contiguous tensors."""
65
+ block_m = tl.program_id(axis=0)
66
+ block_n = tl.program_id(axis=1)
67
+ expert = tl.program_id(axis=2)
68
+
69
+ expert_start = tl.load(expert_cumsum_ptr + expert - 1, mask=expert > 0, other=0)
70
+ expert_end = tl.load(expert_cumsum_ptr + expert)
71
+ expert_tokens = expert_end - expert_start
72
+
73
+ if block_m * BLOCK_M >= expert_tokens:
74
+ return
75
+
76
+ row_offsets = block_m * BLOCK_M + tl.arange(0, BLOCK_M)
77
+ col_offsets = block_n * BLOCK_N + tl.arange(0, BLOCK_N)
78
+ k_offsets = tl.arange(0, BLOCK_K)
79
+
80
+ input_ptrs = (
81
+ input_ptr
82
+ + (expert_start + row_offsets[:, None]) * K
83
+ + k_offsets[None, :]
84
+ )
85
+ weight_ptrs = (
86
+ weight_ptr
87
+ + expert * N * K
88
+ + col_offsets[None, :] * K
89
+ + k_offsets[:, None]
90
+ )
91
+
92
+ accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
93
+ for k_block in range(0, tl.cdiv(K, BLOCK_K)):
94
+ remaining_k = K - k_block * BLOCK_K
95
+ inputs = tl.load(
96
+ input_ptrs,
97
+ mask=(row_offsets[:, None] < expert_tokens) & (k_offsets[None, :] < remaining_k),
98
+ other=0.0,
99
+ )
100
+ weights = tl.load(
101
+ weight_ptrs,
102
+ mask=(col_offsets[None, :] < N) & (k_offsets[:, None] < remaining_k),
103
+ other=0.0,
104
+ )
105
+ accumulator += tl.dot(inputs, weights)
106
+ input_ptrs += BLOCK_K
107
+ weight_ptrs += BLOCK_K
108
+
109
+ output_ptrs = (
110
+ output_ptr
111
+ + (expert_start + row_offsets[:, None]) * N
112
+ + col_offsets[None, :]
113
+ )
114
+ tl.store(
115
+ output_ptrs,
116
+ accumulator,
117
+ mask=(row_offsets[:, None] < expert_tokens) & (col_offsets[None, :] < N),
118
+ )
119
+
120
+
121
+ def _validate_inputs(
122
+ num_experts: int,
123
+ routing_weights: torch.Tensor,
124
+ selected_experts: torch.Tensor,
125
+ hidden_states: torch.Tensor,
126
+ fc1_1_weight: torch.Tensor,
127
+ fc1_2_weight: torch.Tensor,
128
+ fc2_weight: torch.Tensor,
129
+ ) -> None:
130
+ if num_experts <= 0:
131
+ raise ValueError(f"num_experts must be positive, got {num_experts}")
132
+ if torch.is_grad_enabled():
133
+ raise RuntimeError(
134
+ "This standalone fused_moe_forward is inference-only. Call it under "
135
+ "torch.no_grad() or torch.inference_mode()."
136
+ )
137
+ if hidden_states.ndim != 2:
138
+ raise ValueError(f"hidden_states must have shape [tokens, hidden], got {tuple(hidden_states.shape)}")
139
+ if routing_weights.ndim != 2 or selected_experts.shape != routing_weights.shape:
140
+ raise ValueError(
141
+ "routing_weights and selected_experts must have the same [tokens, top_k] shape, got "
142
+ f"{tuple(routing_weights.shape)} and {tuple(selected_experts.shape)}"
143
+ )
144
+ if routing_weights.shape[1] == 0:
145
+ raise ValueError("top_k must be positive")
146
+ if routing_weights.shape[0] != hidden_states.shape[0]:
147
+ raise ValueError("routing_weights and hidden_states must contain the same number of tokens")
148
+ if selected_experts.dtype not in (torch.int32, torch.int64):
149
+ raise TypeError(f"selected_experts must be int32 or int64, got {selected_experts.dtype}")
150
+ if fc1_1_weight.ndim != 3 or fc1_2_weight.ndim != 3 or fc2_weight.ndim != 3:
151
+ raise ValueError("expert weights must be rank-3 tensors")
152
+ if fc1_1_weight.shape != fc1_2_weight.shape:
153
+ raise ValueError("fc1_1_weight and fc1_2_weight must have identical shapes")
154
+
155
+ experts, intermediate_size, hidden_size = fc1_1_weight.shape
156
+ expected_fc2_shape = (experts, hidden_size, intermediate_size)
157
+ if experts != num_experts:
158
+ raise ValueError(f"num_experts={num_experts}, but the weights contain {experts} experts")
159
+ if hidden_states.shape[1] != hidden_size:
160
+ raise ValueError(f"hidden size is {hidden_states.shape[1]}, but the weights expect {hidden_size}")
161
+ if tuple(fc2_weight.shape) != expected_fc2_shape:
162
+ raise ValueError(f"fc2_weight must have shape {expected_fc2_shape}, got {tuple(fc2_weight.shape)}")
163
+ if selected_experts.numel():
164
+ # These scalar checks synchronize CUDA once, before launching harder-to-debug kernels.
165
+ min_expert = int(selected_experts.min().item())
166
+ max_expert = int(selected_experts.max().item())
167
+ if min_expert < 0 or max_expert >= num_experts:
168
+ raise ValueError(f"selected expert IDs must be in [0, {num_experts}), got [{min_expert}, {max_expert}]")
169
+
170
+ devices = {
171
+ hidden_states.device,
172
+ routing_weights.device,
173
+ selected_experts.device,
174
+ fc1_1_weight.device,
175
+ fc1_2_weight.device,
176
+ fc2_weight.device,
177
+ }
178
+ if len(devices) != 1:
179
+ raise ValueError(f"all inputs and weights must be on one device, got {sorted(map(str, devices))}")
180
+
181
+
182
+ def _route_tokens(
183
+ num_experts: int,
184
+ routing_weights: torch.Tensor,
185
+ selected_experts: torch.Tensor,
186
+ hidden_states: torch.Tensor,
187
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
188
+ """Sort routed token copies by expert and return the inverse permutation."""
189
+ top_k = selected_experts.shape[1]
190
+ flat_experts = selected_experts.reshape(-1).to(torch.int64)
191
+ order = torch.argsort(flat_experts, stable=True)
192
+ sorted_hidden_states = hidden_states[torch.div(order, top_k, rounding_mode="floor")].contiguous()
193
+ sorted_routing_weights = routing_weights.reshape(-1)[order].contiguous()
194
+ tokens_per_expert = torch.bincount(flat_experts, minlength=num_experts)
195
+ expert_cumsum = torch.cumsum(tokens_per_expert, dim=0, dtype=torch.int32).contiguous()
196
+ return sorted_hidden_states, sorted_routing_weights, expert_cumsum, order
197
+
198
+
199
+ def _unroute_tokens(
200
+ sorted_outputs: torch.Tensor,
201
+ order: torch.Tensor,
202
+ num_tokens: int,
203
+ top_k: int,
204
+ ) -> torch.Tensor:
205
+ restored = torch.empty_like(sorted_outputs)
206
+ restored[order] = sorted_outputs
207
+ # VeOmni's v0.1.0 gather kernel accumulates the top-k outputs in FP32.
208
+ return restored.view(num_tokens, top_k, -1).sum(dim=1, dtype=torch.float32).to(sorted_outputs.dtype)
209
+
210
+
211
+ def _grouped_linear_triton(
212
+ inputs: torch.Tensor,
213
+ weights: torch.Tensor,
214
+ expert_cumsum: torch.Tensor,
215
+ ) -> torch.Tensor:
216
+ if triton is None: # pragma: no cover - guarded by the caller
217
+ raise RuntimeError("Triton is not available")
218
+ if not inputs.is_contiguous() or not weights.is_contiguous():
219
+ raise ValueError("the Triton path requires contiguous inputs and expert weights")
220
+
221
+ num_experts, output_size, input_size = weights.shape
222
+ if inputs.shape[1] != input_size:
223
+ raise ValueError(f"input width is {inputs.shape[1]}, but the weights expect {input_size}")
224
+
225
+ output = torch.empty((inputs.shape[0], output_size), dtype=inputs.dtype, device=inputs.device)
226
+ block_m, block_n, block_k = 128, 128, 32
227
+ grid = (
228
+ triton.cdiv(inputs.shape[0], block_m),
229
+ triton.cdiv(output_size, block_n),
230
+ num_experts,
231
+ )
232
+ with torch.cuda.device(inputs.device):
233
+ _grouped_linear_kernel[grid](
234
+ inputs,
235
+ weights,
236
+ output,
237
+ expert_cumsum,
238
+ N=output_size,
239
+ K=input_size,
240
+ BLOCK_M=block_m,
241
+ BLOCK_N=block_n,
242
+ BLOCK_K=block_k,
243
+ num_warps=8,
244
+ num_stages=3,
245
+ )
246
+ return output
247
+
248
+
249
+ def _triton_moe_forward(
250
+ num_experts: int,
251
+ routing_weights: torch.Tensor,
252
+ selected_experts: torch.Tensor,
253
+ hidden_states: torch.Tensor,
254
+ fc1_1_weight: torch.Tensor,
255
+ fc1_2_weight: torch.Tensor,
256
+ fc2_weight: torch.Tensor,
257
+ ) -> torch.Tensor:
258
+ sorted_hidden, sorted_routing, expert_cumsum, order = _route_tokens(
259
+ num_experts, routing_weights, selected_experts, hidden_states
260
+ )
261
+ gate = _grouped_linear_triton(sorted_hidden, fc1_1_weight, expert_cumsum)
262
+ up = _grouped_linear_triton(sorted_hidden, fc1_2_weight, expert_cumsum)
263
+ intermediate = F.silu(gate) * up
264
+ intermediate.mul_(sorted_routing.unsqueeze(-1))
265
+ sorted_outputs = _grouped_linear_triton(intermediate.contiguous(), fc2_weight, expert_cumsum)
266
+ return _unroute_tokens(sorted_outputs, order, hidden_states.shape[0], selected_experts.shape[1])
267
+
268
+
269
+ def _eager_moe_forward(
270
+ num_experts: int,
271
+ routing_weights: torch.Tensor,
272
+ selected_experts: torch.Tensor,
273
+ hidden_states: torch.Tensor,
274
+ fc1_1_weight: torch.Tensor,
275
+ fc1_2_weight: torch.Tensor,
276
+ fc2_weight: torch.Tensor,
277
+ ) -> torch.Tensor:
278
+ sorted_hidden, sorted_routing, expert_cumsum, order = _route_tokens(
279
+ num_experts, routing_weights, selected_experts, hidden_states
280
+ )
281
+ expert_ends = expert_cumsum.to(device="cpu", dtype=torch.int64).tolist()
282
+ outputs: list[torch.Tensor] = []
283
+ start = 0
284
+ for expert, end in enumerate(expert_ends):
285
+ if end > start:
286
+ expert_inputs = sorted_hidden[start:end]
287
+ gate = F.linear(expert_inputs, fc1_1_weight[expert])
288
+ up = F.linear(expert_inputs, fc1_2_weight[expert])
289
+ intermediate = F.silu(gate) * up
290
+ intermediate.mul_(sorted_routing[start:end].unsqueeze(-1))
291
+ outputs.append(F.linear(intermediate, fc2_weight[expert]))
292
+ start = end
293
+
294
+ sorted_outputs = torch.cat(outputs, dim=0) if outputs else hidden_states.new_empty((0, hidden_states.shape[1]))
295
+ return _unroute_tokens(sorted_outputs, order, hidden_states.shape[0], selected_experts.shape[1])
296
+
297
+
298
+ def fused_moe_forward(
299
+ module: torch.nn.Module,
300
+ num_experts: int,
301
+ routing_weights: torch.Tensor,
302
+ selected_experts: torch.Tensor,
303
+ hidden_states: torch.Tensor,
304
+ fc1_1_weight: torch.Tensor,
305
+ fc1_2_weight: torch.Tensor,
306
+ fc2_weight: torch.Tensor,
307
+ ) -> torch.Tensor:
308
+ """Run the VeOmni v0.1.0 split-weight MoE operation for inference.
309
+
310
+ ``module`` is retained for call-site compatibility. Like VeOmni's original
311
+ non-EP implementation, this function does not use it.
312
+
313
+ Set ``LLADA_MOE_BACKEND`` to ``auto`` (default), ``triton``, or ``eager``.
314
+ The ``triton`` setting fails loudly if its requirements are not met;
315
+ ``auto`` falls back to the PyTorch implementation.
316
+ """
317
+ del module
318
+ _validate_inputs(
319
+ num_experts,
320
+ routing_weights,
321
+ selected_experts,
322
+ hidden_states,
323
+ fc1_1_weight,
324
+ fc1_2_weight,
325
+ fc2_weight,
326
+ )
327
+
328
+ backend = os.getenv("LLADA_MOE_BACKEND", "auto").lower()
329
+ if backend not in {"auto", "triton", "eager"}:
330
+ raise ValueError(f"LLADA_MOE_BACKEND must be auto, triton, or eager; got {backend!r}")
331
+
332
+ compute_dtype = fc1_1_weight.dtype
333
+ if fc1_2_weight.dtype != compute_dtype or fc2_weight.dtype != compute_dtype:
334
+ raise TypeError("all expert weights must have the same dtype")
335
+ hidden_states = hidden_states.to(dtype=compute_dtype)
336
+ routing_weights = routing_weights.to(dtype=compute_dtype)
337
+
338
+ if hidden_states.shape[0] == 0:
339
+ return hidden_states
340
+
341
+ can_use_triton = (
342
+ triton is not None
343
+ and hidden_states.is_cuda
344
+ and compute_dtype in _SUPPORTED_TRITON_DTYPES
345
+ and fc1_1_weight.is_contiguous()
346
+ and fc1_2_weight.is_contiguous()
347
+ and fc2_weight.is_contiguous()
348
+ )
349
+ if backend == "triton" and not can_use_triton:
350
+ raise RuntimeError(
351
+ "The Triton backend requires Triton, CUDA tensors, contiguous expert weights, "
352
+ "and float16 or bfloat16 weights."
353
+ )
354
+ if backend != "eager" and can_use_triton:
355
+ return _triton_moe_forward(
356
+ num_experts,
357
+ routing_weights,
358
+ selected_experts,
359
+ hidden_states,
360
+ fc1_1_weight,
361
+ fc1_2_weight,
362
+ fc2_weight,
363
+ )
364
+ return _eager_moe_forward(
365
+ num_experts,
366
+ routing_weights,
367
+ selected_experts,
368
+ hidden_states,
369
+ fc1_1_weight,
370
+ fc1_2_weight,
371
+ fc2_weight,
372
+ )
373
+
374
+
375
+ __all__ = ["fused_moe_forward"]
text_encoder/modeling_llada2uni_moe.py CHANGED
@@ -41,7 +41,7 @@ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_u
41
  from transformers.modeling_utils import PreTrainedModel
42
  from transformers.pytorch_utils import ALL_LAYERNORM_LAYERS
43
  from transformers.utils import logging
44
- from veomni.ops import fused_moe_forward
45
 
46
  from .configuration_llada2uni_moe import LLaDA2MoeConfig
47
 
 
41
  from transformers.modeling_utils import PreTrainedModel
42
  from transformers.pytorch_utils import ALL_LAYERNORM_LAYERS
43
  from transformers.utils import logging
44
+ from .fused_moe_ops import fused_moe_forward
45
 
46
  from .configuration_llada2uni_moe import LLaDA2MoeConfig
47