Обновить
4K+
188

Пользователь

0,1
Рейтинг
157
Подписчики
Отправить сообщение

8ГБ ОЗУ и 1ГБ GPU. Какую версию лучше поставить?

Свежие Qwen3.5-4B и Gemma-4-E4B. Помимо текста, у этих моделей есть Vision модуль, можно распознавать, переводить, создавать макеты на основе изображений.

Отличная работа. На сколько сложно будет добавить поддержку этой модели в Unsloth?

Они недавно выпустили Unsloth Studio, в которой удобный интерфейс для обучения, легче работать с датасетами, благодаря чему в разы проще обучать различные современные модели (Fine-tune, LoRA, QLoRA 4-bit).

Но не разглядели главное, на контексте в 90к.

Вот типичные 10к, которые покрывают большинство локальных задач:

30.5 t/s
30.5 t/s

Проще и быстрее поставить qwen code, в котором есть Qwen3.6-Plus с большими бесплатными лимитами, там 1m контекста, качество в разы лучше и быстрая скорость, а для кого-то всё что хуже Opus 4.6 пустая трата времени.

Для переводить, распознавать картинки, вести диалог и тому подобному очень много контекста не нужно, точнее его нужно сбрасывать, а не накапливать. С помощью cmoe можно высвободить много видеопамяти для других параллельных задач, при этом получить скорость большую, чем выдают LM Studio или ollama. Для агентного использования можно прибегать к субагентам, чтобы не накапливать контекст.

Но вообще, на большом контексте важна скорость PP, чтобы её увеличить, нужно увеличить размер пакетов -ub 3072 -b 3072, или больше. Опционально можно квантовать KV-кэш -ctk q8_0 -ctv q8_0, чтобы снизить расход памяти на контекст в 2 раза. Сейчас тестируют TurboQuant от Google Research, которые заявляют, что можно квантовать контекст до 3.5 бит без заметных потерь, что снизить расход памяти в ~4 раза.

90k контекст, pp 579 t/s, tg 17.4 t/s
90k контекст, pp 579 t/s, tg 17.4 t/s

90к контекст, подготовка промпта заняла 2.5 минуты, что для агентного программирования хороший результат. Просадка tg не только за счёт контекста, но и ncmoe увеличено с 13 до 22, чтобы вместить столько контекста.

DDR4 16Gb (2x8gb 3600 Mhz), Ryzen 3600 6 ядер, RTX 3060 12gb, Win10, драйвера nvidia свежие, cuda 13.1.

Gemma-4-26B-A4B-it-Q4_K_M, запуск с -ncmoe 13, генерация 33 t/s:

Qwen3.5-35B-A3B-UD-Q4_K_XL, запуск с -ncmoe 22, генерация 32 t/s:

Подбор оптимального параметра, -ncmoe 18,20,22
Подбор оптимального параметра, -ncmoe 18,20,22

Модели не влезают в 12гб VRAM, но если использовать перераспределение MoE тензоров и запускать напрямую llama.cpp, в которой по умолчанию включен параметр -fit, который автоматически подберет оптимальные параметры запуска, или настраивать -ncmoe вручную, то можно получить ощутимую прибавку к скорости по сравнению со стандартным способом ngl, который по умолчанию используется в Ollama и LM Studio.

4060 Ti 16Gb выдает 65 t/s. Модель именно та, что по умолчанию предлагается в LM Studio:

Gemma-4-26B-A4B, 4060 Ti 16Gb, Win11, -ncmoe 2, 65 t/s
Gemma-4-26B-A4B, 4060 Ti 16Gb, Win11, -ncmoe 2, 65 t/s

Подробнее, как можно получить больше скорости:

Тут, конечно, это уже надо на нормальном датасете обучать, чтобы заметить разницу. Но архитектура старая, поддержки triton нет, обучение будет в разы дольше, чем дообучить даже, например, Qwen3.5 4B.

Но в целом можно попробовать и так. Сходу при запуске инференса со Sparse Attn ошибка:

cuda\TensorCompare.cu:109: block: [0,0,0], thread: [0,0,0] Assertion `input[0] != 0` failed.
    next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1)
                  ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
torch.AcceleratorError: CUDA error: device-side assert triggered

Если посмотреть вывод модели, то вероятности обнулены, логиты в бесконечности, а маска внимания все маркирует как False. Можно попробовать починить:

modeling_rugpt3xl.py
"""PyTorch RuGPT-3 XL model.

GPT-3-style decoder-only transformer (1.3B) trained on Russian text.
Architecture: absolute position embeddings, pre-norm layers, GELU activation,
tied LM head.
"""

import math
from typing import List, Optional, Tuple, Union

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint

from transformers.activations import ACT2FN
from transformers.cache_utils import Cache, DynamicCache
from transformers.generation import GenerationMixin
from transformers.modeling_outputs import (
    BaseModelOutputWithPast,
    CausalLMOutputWithPast,
)
from transformers.modeling_utils import PreTrainedModel
from transformers.utils import logging

from .configuration_rugpt3xl import RuGPT3XLConfig

logger = logging.get_logger(__name__)


def _make_sparse_layout(
    num_heads: int,
    num_blocks: int,
    num_local_blocks: int,
    num_global_blocks: int,
    num_different_global_patterns: int,
    device: torch.device,
) -> torch.Tensor:
    """Build FixedSparsity boolean layout on *device*.

    Returns [num_heads, num_blocks, num_blocks] bool tensor.
    """
    layout = torch.zeros(
        num_heads, num_blocks, num_blocks, dtype=torch.bool, device=device,
    )

    for win in range(0, num_blocks, num_local_blocks):
        end = min(win + num_local_blocks, num_blocks)
        sz = end - win
        layout[:, win:end, win:end] = torch.tril(
            torch.ones(sz, sz, dtype=torch.bool, device=device)
        )

    for h in range(num_heads):
        first = num_local_blocks - (
            1 + h % num_different_global_patterns
        ) * num_global_blocks
        reg_end = num_blocks - (num_blocks % num_local_blocks)
        for gi in range(first, reg_end, num_local_blocks):
            layout[h, gi:, gi : gi + num_global_blocks] = True
        if reg_end < num_blocks:
            s = min(reg_end + first, num_blocks - num_global_blocks)
            layout[h, s:, s : s + num_global_blocks] = True

    return layout


class RuGPT3XLAttention(nn.Module):
    def __init__(self, config: RuGPT3XLConfig, layer_idx: int):
        super().__init__()
        self.config = config
        self.layer_idx = layer_idx
        self.hidden_size = config.hidden_size
        self.num_heads = config.num_attention_heads
        self.head_dim = self.hidden_size // self.num_heads
        self.scale = self.head_dim ** -0.5

        self.q_proj = nn.Linear(self.hidden_size, self.hidden_size)
        self.k_proj = nn.Linear(self.hidden_size, self.hidden_size)
        self.v_proj = nn.Linear(self.hidden_size, self.hidden_size)
        self.o_proj = nn.Linear(self.hidden_size, self.hidden_size)

        self.attn_dropout = nn.Dropout(config.attention_dropout)
        self.resid_dropout = nn.Dropout(config.output_dropout)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: Optional[torch.Tensor] = None,
        position_ids: Optional[torch.LongTensor] = None,
        past_key_value: Optional[Cache] = None,
        output_attentions: bool = False,
        use_cache: bool = False,
        **kwargs,
    ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Cache]]:
        bsz, q_len, _ = hidden_states.size()

        query = self.q_proj(hidden_states)
        key = self.k_proj(hidden_states)
        value = self.v_proj(hidden_states)

        query = query.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
        key = key.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
        value = value.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)

        if past_key_value is not None:
            key, value = past_key_value.update(key, value, self.layer_idx)

        attn_weights = torch.matmul(query, key.transpose(2, 3)) * self.scale

        if attention_mask is not None:
            attn_weights = attn_weights + attention_mask

        attn_weights = F.softmax(attn_weights, dim=-1, dtype=torch.float32).to(
            query.dtype
        )
        attn_weights = self.attn_dropout(attn_weights)

        attn_output = torch.matmul(attn_weights, value)
        attn_output = attn_output.transpose(1, 2).contiguous()
        attn_output = attn_output.reshape(bsz, q_len, self.hidden_size)

        attn_output = self.o_proj(attn_output)
        attn_output = self.resid_dropout(attn_output)

        return (
            attn_output,
            attn_weights if output_attentions else None,
            past_key_value,
        )


class RuGPT3XMLP(nn.Module):
    def __init__(self, config: RuGPT3XLConfig):
        super().__init__()
        self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size)
        self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size)
        self.act_fn = ACT2FN[config.hidden_act]
        self.dropout = nn.Dropout(config.output_dropout)

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        return self.dropout(self.down_proj(self.act_fn(self.up_proj(hidden_states))))


class RuGPT3XLDecoderLayer(nn.Module):
    def __init__(self, config: RuGPT3XLConfig, layer_idx: int):
        super().__init__()
        self.input_layernorm = nn.LayerNorm(
            config.hidden_size, eps=config.layer_norm_eps
        )
        self.self_attn = RuGPT3XLAttention(config, layer_idx)
        self.post_attention_layernorm = nn.LayerNorm(
            config.hidden_size, eps=config.layer_norm_eps
        )
        self.mlp = RuGPT3XMLP(config)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: Optional[torch.Tensor] = None,
        position_ids: Optional[torch.LongTensor] = None,
        past_key_value: Optional[Cache] = None,
        output_attentions: bool = False,
        use_cache: bool = False,
        **kwargs,
    ) -> Tuple[torch.Tensor, ...]:
        residual = hidden_states
        hidden_states = self.input_layernorm(hidden_states)
        hidden_states, self_attn_weights, present_key_value = self.self_attn(
            hidden_states=hidden_states,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_value=past_key_value,
            output_attentions=output_attentions,
            use_cache=use_cache,
            **kwargs,
        )
        hidden_states = residual + hidden_states

        residual = hidden_states
        hidden_states = self.post_attention_layernorm(hidden_states)
        hidden_states = self.mlp(hidden_states)
        hidden_states = residual + hidden_states

        outputs = (hidden_states,)
        if output_attentions:
            outputs += (self_attn_weights,)
        if use_cache:
            outputs += (present_key_value,)
        return outputs


class RuGPT3XLPreTrainedModel(PreTrainedModel):
    config_class = RuGPT3XLConfig
    base_model_prefix = "model"
    supports_gradient_checkpointing = True
    _no_split_modules = ["RuGPT3XLDecoderLayer"]
    _skip_keys_device_placement = ["past_key_values"]
    _supports_cache_class = True

    def _init_weights(self, module):
        std = self.config.initializer_range
        if isinstance(module, nn.Linear):
            module.weight.data.normal_(mean=0.0, std=std)
            if module.bias is not None:
                module.bias.data.zero_()
        elif isinstance(module, nn.Embedding):
            module.weight.data.normal_(mean=0.0, std=std)
            if module.padding_idx is not None:
                module.weight.data[module.padding_idx].zero_()
        elif isinstance(module, nn.LayerNorm):
            module.bias.data.zero_()
            module.weight.data.fill_(1.0)


class RuGPT3XLModel(RuGPT3XLPreTrainedModel):
    """Bare RuGPT-3 XL transformer outputting raw hidden states."""

    def __init__(self, config: RuGPT3XLConfig):
        super().__init__(config)
        self.padding_idx = config.pad_token_id
        self.vocab_size = config.vocab_size

        self.embed_tokens = nn.Embedding(
            config.vocab_size, config.hidden_size, self.padding_idx
        )
        self.embed_positions = nn.Embedding(
            config.max_position_embeddings, config.hidden_size
        )
        self.embed_dropout = nn.Dropout(config.embedding_dropout)

        self.layers = nn.ModuleList(
            [
                RuGPT3XLDecoderLayer(config, layer_idx)
                for layer_idx in range(config.num_hidden_layers)
            ]
        )
        self.norm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)

        # Sparse attention config
        self._sparse_layers: set = set()
        if getattr(config, "sparse_mode", "none") == "alternating":
            self._sparse_layers = {
                i for i in range(config.num_hidden_layers) if i % 2 == 0
            }
        elif getattr(config, "sparse_mode", "none") == "all":
            self._sparse_layers = set(range(config.num_hidden_layers))

        # Sparse layout will be lazily built on first forward.
        # NOT registered as a buffer to avoid meta-device corruption.
        self._sparse_layout: Optional[torch.Tensor] = None

        self.gradient_checkpointing = False
        self.post_init()

    def _get_sparse_layout(self, device: torch.device) -> torch.Tensor:
        """Return sparse layout tensor on *device*, building it if necessary."""
        if self._sparse_layout is not None and self._sparse_layout.device == device:
            return self._sparse_layout

        cfg = self.config
        num_blocks = cfg.max_position_embeddings // cfg.sparse_block_size
        self._sparse_layout = _make_sparse_layout(
            num_heads=cfg.num_attention_heads,
            num_blocks=num_blocks,
            num_local_blocks=cfg.sparse_num_local_blocks,
            num_global_blocks=cfg.sparse_num_global_blocks,
            num_different_global_patterns=cfg.sparse_num_different_global_patterns,
            device=device,
        )
        return self._sparse_layout

    def get_input_embeddings(self):
        return self.embed_tokens

    def set_input_embeddings(self, value):
        self.embed_tokens = value

    def forward(
        self,
        input_ids: Optional[torch.LongTensor] = None,
        attention_mask: Optional[torch.Tensor] = None,
        position_ids: Optional[torch.LongTensor] = None,
        past_key_values: Optional[Union[Cache, List[torch.FloatTensor]]] = None,
        inputs_embeds: Optional[torch.FloatTensor] = None,
        use_cache: Optional[bool] = None,
        output_attentions: Optional[bool] = None,
        output_hidden_states: Optional[bool] = None,
        return_dict: Optional[bool] = None,
        **kwargs,
    ) -> Union[Tuple, BaseModelOutputWithPast]:
        output_attentions = (
            output_attentions
            if output_attentions is not None
            else self.config.output_attentions
        )
        output_hidden_states = (
            output_hidden_states
            if output_hidden_states is not None
            else self.config.output_hidden_states
        )
        use_cache = use_cache if use_cache is not None else self.config.use_cache
        return_dict = (
            return_dict if return_dict is not None else self.config.use_return_dict
        )

        if input_ids is not None and inputs_embeds is not None:
            raise ValueError(
                "You cannot specify both input_ids and inputs_embeds"
            )
        if input_ids is not None:
            batch_size, seq_length = input_ids.shape[:2]
        elif inputs_embeds is not None:
            batch_size, seq_length = inputs_embeds.shape[:2]
        else:
            raise ValueError(
                "You have to specify either input_ids or inputs_embeds"
            )

        if self.gradient_checkpointing and self.training and use_cache:
            logger.warning_once(
                "`use_cache=True` is incompatible with gradient checkpointing. "
                "Setting `use_cache=False`."
            )
            use_cache = False

        past_key_values_length = 0
        if use_cache:
            if past_key_values is None:
                past_key_values = DynamicCache()
            past_key_values_length = past_key_values.get_seq_length()

        if position_ids is None:
            device = (
                input_ids.device if input_ids is not None else inputs_embeds.device
            )
            position_ids = torch.arange(
                past_key_values_length,
                seq_length + past_key_values_length,
                dtype=torch.long,
                device=device,
            ).unsqueeze(0)

        if inputs_embeds is None:
            inputs_embeds = self.embed_tokens(input_ids)

        position_embeds = self.embed_positions(position_ids)
        hidden_states = self.embed_dropout(inputs_embeds + position_embeds)

        # Dense causal mask
        causal_mask = self._build_causal_mask(
            batch_size,
            seq_length,
            past_key_values_length,
            hidden_states.dtype,
            hidden_states.device,
            attention_mask,
        )

        # Sparse causal mask (lazily build layout on correct device)
        sparse_mask = None
        if self._sparse_layers:
            sparse_layout = self._get_sparse_layout(hidden_states.device)
            sparse_mask = self._build_sparse_causal_mask(
                seq_length,
                past_key_values_length,
                hidden_states.dtype,
                hidden_states.device,
                sparse_layout,
                self.config.sparse_block_size,
                attention_mask,
            )

        all_hidden_states = () if output_hidden_states else None
        all_self_attns = () if output_attentions else None
        next_decoder_cache = None

        for layer_idx, decoder_layer in enumerate(self.layers):
            if output_hidden_states:
                all_hidden_states += (hidden_states,)

            layer_mask = (
                sparse_mask
                if (layer_idx in self._sparse_layers and sparse_mask is not None)
                else causal_mask
            )

            if self.gradient_checkpointing and self.training:
                layer_outputs = self._gradient_checkpointing_func(
                    decoder_layer.__call__,
                    hidden_states,
                    layer_mask,
                    position_ids,
                    past_key_values,
                    output_attentions,
                    use_cache,
                )
            else:
                layer_outputs = decoder_layer(
                    hidden_states,
                    attention_mask=layer_mask,
                    position_ids=position_ids,
                    past_key_value=past_key_values,
                    output_attentions=output_attentions,
                    use_cache=use_cache,
                )

            hidden_states = layer_outputs[0]
            if use_cache:
                next_decoder_cache = layer_outputs[
                    2 if output_attentions else 1
                ]
            if output_attentions:
                all_self_attns += (layer_outputs[1],)

        hidden_states = self.norm(hidden_states)

        if output_hidden_states:
            all_hidden_states += (hidden_states,)

        next_cache = next_decoder_cache if use_cache else None

        if not return_dict:
            return tuple(
                v
                for v in [
                    hidden_states,
                    next_cache,
                    all_hidden_states,
                    all_self_attns,
                ]
                if v is not None
            )
        return BaseModelOutputWithPast(
            last_hidden_state=hidden_states,
            past_key_values=next_cache,
            hidden_states=all_hidden_states,
            attentions=all_self_attns,
        )

    @staticmethod
    def _build_causal_mask(
        batch_size: int,
        seq_length: int,
        past_length: int,
        dtype: torch.dtype,
        device: torch.device,
        attention_mask: Optional[torch.Tensor] = None,
    ) -> torch.Tensor:
        total_length = past_length + seq_length
        causal = torch.full(
            (seq_length, total_length),
            torch.finfo(dtype).min,
            device=device,
        )
        causal = causal.masked_fill(
            torch.arange(total_length, device=device).unsqueeze(0)
            <= torch.arange(
                past_length, past_length + seq_length, device=device
            ).unsqueeze(1),
            0.0,
        )
        causal = causal.unsqueeze(0).unsqueeze(0)

        if attention_mask is not None:
            pad_mask = (
                (1 - attention_mask[:, None, None, :].to(dtype))
                * torch.finfo(dtype).min
            )
            causal = causal + pad_mask

        return causal

    @staticmethod
    def _build_sparse_causal_mask(
        seq_length: int,
        past_length: int,
        dtype: torch.dtype,
        device: torch.device,
        sparse_layout: torch.Tensor,
        block_size: int,
        attention_mask: Optional[torch.Tensor] = None,
    ) -> torch.Tensor:
        total_length = past_length + seq_length
        num_blocks = sparse_layout.shape[1]

        q_block = (
            torch.arange(past_length, past_length + seq_length, device=device)
            // block_size
        ).clamp(max=num_blocks - 1)
        k_block = (
            torch.arange(total_length, device=device) // block_size
        ).clamp(max=num_blocks - 1)

        block_ok = sparse_layout[:, q_block][:, :, k_block]

        q_pos = torch.arange(
            past_length, past_length + seq_length, device=device
        ).unsqueeze(1)
        k_pos = torch.arange(total_length, device=device).unsqueeze(0)
        causal_ok = k_pos <= q_pos

        allowed = block_ok & causal_ok.unsqueeze(0)

        min_val = torch.finfo(dtype).min
        mask = torch.where(allowed, 0.0, min_val).to(dtype).unsqueeze(0)

        if attention_mask is not None:
            pad_mask = (
                (1 - attention_mask[:, None, None, :].to(dtype)) * min_val
            )
            mask = mask + pad_mask

        return mask


class RuGPT3XLForCausalLM(RuGPT3XLPreTrainedModel, GenerationMixin):
    _tied_weights_keys = {"lm_head.weight": "model.embed_tokens.weight"}
    _supports_cache_class = True

    def __init__(self, config: RuGPT3XLConfig):
        super().__init__(config)
        self.model = RuGPT3XLModel(config)
        self.vocab_size = config.vocab_size
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        self.post_init()

    def get_input_embeddings(self):
        return self.model.embed_tokens

    def set_input_embeddings(self, value):
        self.model.embed_tokens = value

    def get_output_embeddings(self):
        return self.lm_head

    def set_output_embeddings(self, new_embeddings):
        self.lm_head = new_embeddings

    def get_decoder(self):
        return self.model

    def set_decoder(self, decoder):
        self.model = decoder

    def forward(
        self,
        input_ids: Optional[torch.LongTensor] = None,
        attention_mask: Optional[torch.Tensor] = None,
        position_ids: Optional[torch.LongTensor] = None,
        past_key_values: Optional[Union[Cache, List[torch.FloatTensor]]] = None,
        inputs_embeds: Optional[torch.FloatTensor] = None,
        labels: Optional[torch.LongTensor] = None,
        use_cache: Optional[bool] = None,
        output_attentions: Optional[bool] = None,
        output_hidden_states: Optional[bool] = None,
        return_dict: Optional[bool] = None,
        **kwargs,
    ) -> Union[Tuple, CausalLMOutputWithPast]:
        output_attentions = (
            output_attentions
            if output_attentions is not None
            else self.config.output_attentions
        )
        output_hidden_states = (
            output_hidden_states
            if output_hidden_states is not None
            else self.config.output_hidden_states
        )
        return_dict = (
            return_dict if return_dict is not None else self.config.use_return_dict
        )

        outputs = self.model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            position_ids=position_ids,
            past_key_values=past_key_values,
            inputs_embeds=inputs_embeds,
            use_cache=use_cache,
            output_attentions=output_attentions,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
        )

        hidden_states = outputs[0]
        logits = self.lm_head(hidden_states).float()

        loss = None
        if labels is not None:
            shift_logits = logits[..., :-1, :].contiguous()
            shift_labels = labels[..., 1:].contiguous()
            loss_fct = nn.CrossEntropyLoss()
            shift_logits = shift_logits.view(-1, self.config.vocab_size)
            shift_labels = shift_labels.view(-1).to(shift_logits.device)
            loss = loss_fct(shift_logits, shift_labels)

        if not return_dict:
            output = (logits,) + outputs[1:]
            return (loss,) + output if loss is not None else output

        return CausalLMOutputWithPast(
            loss=loss,
            logits=logits,
            past_key_values=outputs.past_key_values,
            hidden_states=outputs.hidden_states,
            attentions=outputs.attentions,
        )

    def prepare_inputs_for_generation(
        self,
        input_ids,
        past_key_values=None,
        attention_mask=None,
        inputs_embeds=None,
        **kwargs,
    ):
        if past_key_values is not None:
            past_length = past_key_values.get_seq_length()
            if (
                attention_mask is not None
                and attention_mask.shape[1] > input_ids.shape[1]
            ):
                input_ids = input_ids[
                    :, -(attention_mask.shape[1] - past_length) :
                ]
            elif past_length < input_ids.shape[1]:
                input_ids = input_ids[:, past_length:]

        position_ids = kwargs.get("position_ids", None)
        if attention_mask is not None and position_ids is None:
            position_ids = attention_mask.long().cumsum(-1) - 1
            position_ids.masked_fill_(attention_mask == 0, 1)
        if position_ids is not None and past_key_values is not None:
            position_ids = position_ids[:, -input_ids.shape[1] :]

        if inputs_embeds is not None and past_key_values is None:
            model_inputs = {"inputs_embeds": inputs_embeds}
        else:
            model_inputs = {"input_ids": input_ids}

        model_inputs.update(
            {
                "position_ids": position_ids,
                "past_key_values": past_key_values,
                "use_cache": kwargs.get("use_cache"),
                "attention_mask": attention_mask,
            }
        )
        return model_inputs

Теперь запускается нормально и можно обучить, и чтобы проверить работает ли вообще отсечение, в forward можно добавить отладочную информацию:

Добавить в forward
        next_decoder_cache = None

        # === start debug ===
        if seq_length > 1 and sparse_mask is not None:
            total_len = seq_length + past_key_values_length
            
            min_val_dense = torch.finfo(causal_mask.dtype).min
            min_val_sparse = torch.finfo(sparse_mask.dtype).min
            
            q_dim = causal_mask.shape[-2]
            k_dim = causal_mask.shape[-1]
            total_elements = q_dim * k_dim
            
            num_matrices_dense = causal_mask.numel() // total_elements
            num_matrices_sparse = sparse_mask.numel() // total_elements
            
            dense_blocked = int((causal_mask == min_val_dense).sum().item() / num_matrices_dense)
            sparse_blocked = int((sparse_mask == min_val_sparse).sum().item() / num_matrices_sparse)
            extra_blocked = sparse_blocked - dense_blocked
            
            allowed_in_dense = total_elements - dense_blocked
            sparse_penalty_pct = (extra_blocked / allowed_in_dense) * 100 if allowed_in_dense > 0 else 0.0

            print(f"\n[SPARSE DEBUG] seq_len={seq_length}, total_len={total_len}")
            print(f"Каузальная маска (Dense): shape={tuple(causal_mask.shape)}, blocked={dense_blocked}/{total_elements} ({(dense_blocked/total_elements)*100:.1f}%)")
            print(f"Разреженная маска (Sparse): shape={tuple(sparse_mask.shape)}, blocked={sparse_blocked}/{total_elements} ({(sparse_blocked/total_elements)*100:.1f}%)")
            print(f"Уровень разреженности (Sparse): +{sparse_penalty_pct:.1f}% (отсечено {extra_blocked} связей)")
        # === end debug === 

        for layer_idx, decoder_layer in enumerate(self.layers):

Так как Sparse Attn тут настроен на 128 токенов, то если запрос меньше, то Sparse должна быть равна Dense и фактически не работать:

Запрос длиннее 128, тут Sparse успешно отсекает и с виду нормально работает:

Попробовал обучить LoRA на первом попавшемся датасете диалогов Den4ikAI/russian_dialogues, датасет на 2.5 млн строк или 40m токенов. Обучил только на первых 10000 за 5-10 минут.

Обучилась успешно, и приобрела особый стиль общения, который присутствовал в датасете:

Датасет, наверное, не очень удачный, но это хороший пример, что если есть base, то из базы можно выровнять (alignment) модель до различных состояний.

Вы пытаетесь работать с base моделью как с instruct моделью.

Base (или pretrain) - это 1 шаг обучения LLM из 3. Смысл pretrain в том, чтобы модель умела составлять буквы в слова, слова в предложения, предложения были орфографически верные, логически правильные, набирала базу знаний и так далее. Такая нейросеть умеет продолжать текст, вы пишите ей “cat = кошка, dog = собака, duck =” и она продолжает “утка”.

Но если вы пытаетесь с ней общаться в чате, то ей будет послан шаблон чата, который может выглядеть очень не типично, например, вот так:

"<|im_start|>user\n{question}<|im_end|>\n<|im_start|>assistant\n{answer}<|im_end|>"

Поэтому базовая модель в таком сценарии начинает генерировать хаотичные ответы. Чтобы base научилась нормально вести диалог, нужно обучить её шаблону.

Для примера, обучим эту ruGPT3 XL base до уровня instruct (до очень примитивного, так как датасет должен быть куда разнообразнее). Генерируем 100 пар вопрос-ответ такого вида:

Теперь из этих пар нужно создать датасет согласно шаблону-чата модели, в данном случае это:

Вопрос: ...
Ответ: ...

Обучаем нейросеть как finetune целиком или как LoRA адаптер. Для примера хватит 3 эпох на 100 примерах:

Через пару минут “ruGPT3XL-instruct” готова. Теперь можно задать какой-нибудь вопрос, которого не было в дополнительном датасете:

Кто такой Шекспир?
Кто такой Шекспир?
Ты знаешь что-то про Fallout?
Ты знаешь что-то про Fallout?

Мы не учили модель отвечать, кто такой Шекспир, и не учили её ничему про Fallout, это уже было в модели. Мы только научили её обрабатывать шаблон чата, и, побочно, структуре ответа как из энциклопедии. Аналогично её можно научить агентным задачам и так далее.

Только просто выделения контекста недостаточно, нужно его еще заполнить на 100%

Да нет, память целиком выделяет в момент загрузки. Заполненность влияет только на скорость генерации.

GPT-OSS-20B для контекста использует SWA, механизм который в разы снижает потребление VRAM, чтобы его активировать, нужно включить Flash Attention. Полные 128к требуют +6гб VRAM, если включить квантование KV-кэша Q8_0, то будет +3Гб.

В llama.cpp по умолчанию работает новый режим --fit, который автоматически подбирает оптимальные параметры для эффективного использования GPU минус 1гб VRAM (параметр настраивается) и включает flash attention, поэтому такое отличие от LM Studio.

Под эффективным имеется ввиду другое распределение тензоров, те, которые используются всегда, пойдут на GPU, остальные, которые используются периодически, на CPU, и если осталась VRAM, то будет заполнена разреженными слоями с экспертами. Почему это так работает, выше я уже кидал ссылку на статью Вам нужна RAM, а не VRAM, где это объясняется.

В LM Studio можно включить "половину" от этого режима галочкой Force Model Expert Weights onto CPU (во время выбора модели зажать Alt для расширенных настроек), и перепроверить, что включен Flash Attention, чтобы оптимизировать расход памяти под контекст.

Модель для сложных задач и длинных ответов: mlx-community/Qwen2.5-72B-Instruct-4bit (~12 токен/с).

Qwen2.5 устарела на ~1.5 года, даже Qwen3 уже не особо актуальна. Переходите на новые более качественные модели, ваша машина легко потянет современные хорошие MoE-модели, которые в разы быстрее чем Qwen2.5-72B и, что важнее, намного качественнее.

Из современных моделей к 128Гб подойдут: OpenAI GPT-OSS-120B, GLM-4.5-Air 110B, Minimax M2.1 229B (в динамическом квантовании UD gguf, mlx не влезет в 128гб). Малые версии тоже есть, например, Qwen3-30B-A3B-2507 и остальные из современного списка, при этом с того момента успели выйти хорошие новинки.

Динамическое квантование от Unsloth позволяет опустится ниже 4-бит квантования, при этом сохраняя достаточно хорошее качество, так что можно запустить и Qwen3-235B-A22B, и свежий Minimax M2.1 229B.

Бенчмарк программирования Aider Polyglot для 1, 2, 3-битного динамического квантования UD:

UD-Q3_K_XL почти не отличается от оригинала, UD-Q2_K_XL хуже на 13%, UD-Q1_K_XL хуже на 30%
UD-Q3_K_XL почти не отличается от оригинала, UD-Q2_K_XL хуже на 13%, UD-Q1_K_XL хуже на 30%

Ноутбук у меня вполне бодрый (i9, 64 GB RAM, RTX 4070)
Идея была простая: докупить eGPU (а лучше - несколько) и получить относительно мощный сетап без покупки отдельной рабочей станции

Вообще, этот ноутбук позволяет запускать на хорошей скорости GPT-OSS-120B или GLM-4.5-Air и без eGPU, 64Гб RAM хватит, а через 4070 будет приличное ускорение для MoE.

Подробнее как запускать такое на ноутбуке или ПК где достаточно RAM и есть немного VRAM:
Запускаем GPT-OSS-120B на 6 Гб GPU и ускоряем до 30 t/s. Вам нужна RAM, а не VRAM.

Cerebras осенью представили метод REAP для вырезания "лишних" экспертов из MoE уменьшая размер модели до 50%, по их словам почти без потерь в области программирования и tool-calling: https://arxiv.org/abs/2510.13999

В целом это работает, но вопрос качества остаётся открытым, например, из того, что сразу бросается в глаза, у модели пропадает умение отвечать на русском языке.

На huggingface много моделей в REAP виде уже готовы: https://huggingface.co/models?search=reap

Спасибо за замеры, обычно это называют benchmaxxing. Вот тут у человека, делающего llm-translate, схожий отзыв: https://habr.com/ru/articles/951416/comments/#comment_28935346

Есть еще tencent/Hunyuan-MT-7B. Тестировали?

Попробовал, ситуация интересная. Мне перевод показался плохим - в каких-то местах выдуманные куски, модель путает "вы-ты", странные конструкции. Но при этом формальная оценка - 91,33, выше, чем у любой другой модели. Добавил результаты в гугл-таблицу, ссылка на которую приведена в статье.

Phi-2 (2.7B), Mistral-7B, Llama-3-8B, Gemma-7B

Перечисленные не очень свежие, Mistral-7B и вовсе 2023 года, одна из самых-самых первых, давно вышли более качественнее модели, если брать в том же размере, то это Ministral-3-8B-Instruct-2512, у Phi вышла Phi-4, Gemma выпустила Gemma3, а вместо Llama3 - Qwen3.

Модели, которые можно запустить на GTX 3060 (12 ГБ)

С недавних пор на 3060 можно запустить и вполне себе большие модели, и с нормальной скоростью. Только нужна ОЗУ и можно ускорять MoE модели с помощью одной GPU.

Если стандартные 16Гб RAM, то можно запускать Qwen3-30B-A3B (включая Vision), Granite-4.0-H-Small 32B, GPT-OSS-20B. Если есть 64Гб, то можно запускать, например, GPT-OSS-120B или GLM-4.5-Air, на 96Гб можно запускать новую MiniMax-M2.1 230B, а на 192Гб DDR5 уже можно запустить DeepSeek R1 671B и V3.1.

Вот, например, на медленной AMD RX 6600 (скорость памяти 224 ГБ/с, а у 3060 скорость 360 ГБ/с, скорость LLM напрямую зависит от скорости памяти), модель GPT-OSS-120B выдает 13 t/s:

GPT-OSS-120B, AMD RX 6600 8Гб + Ryzen 5600g 64Гб DDR4-3600, Windows 11, 13 t/s
GPT-OSS-120B, AMD RX 6600 8Гб + Ryzen 5600g 64Гб DDR4-3600, Windows 11, 13 t/s

а по скорости все равно выигрывают nvidia видеокарты, причем даже не самые свежие.
новейший ryzen ai max используется в gpd win 5. Вы на полном серьезе считаете, что в карманной приставке будет какая-то мощь, способная потянуть ИИ? Ну загрузите вы какую-нибудь большую модель в 128гб, а дальше что? Отдача 1-2 токена в секунду?

Размер устройства не имеет значения, имеет значение количество каналов памяти и тип памяти.

Скорость генерации LLM линейно зависит от скорости памяти, в GPU используют быструю GDDR6X и DDR7 и широкую шину памяти, получая скорость 1 Тб/c на 4090. В Ryzen AI Max+ 365, как и в NVIDIA DGX Spark, используется DDR5 и всего 4 канала памяти, скорость памяти 256 Гб/с. Для сравнения у 4060ti всего 288 Гб/с, что немногим больше.

Смотря на какой архитектуре модель: Dense или MoE. Новый Devstral 2 123B сделан как Dense, там будет 3 t/s, но многие переходят на MoE, поэтому там будет скорость намного выше.

Ryzen AI Max+ выдает 50 t/s на GPT-OSS-120B, это очень комфортная скорость для работы, и на 128Гб можно запустить более качественные модели, вроде GLM-4.5-Air или MiniMax-M2.1 230B, скорость будет в районе 25-30 t/s.

Подробнее про MoE модели: Запускаем GPT-OSS-120B на 6 Гб GPU и ускоряем до 30 t/s. Вам нужна RAM, а не VRAM. Параметр -cmoe для ускорения MoE LLM

При этом тот же MoE, предложенный китайцами, используется во всех флагманских моделях по всему миру

MoE существует очень давно, ещё до появления LLM переводчик NLLB-200 был сделан на MoE.

Для LLM впервые MoE было реализовано в 2023 году в Mixtral 8x7B, французским стартапом Mistral AI. Чуть позже они выпустили Mixtral 8x22B, и это уже был уровень что-то вроде "ChatGPT дома". В тот же период была представлена DBRX MoE модель от Databricks и WizardLM2 от Microsoft, всё это на тот момент одни из лучших локальных моделей. Llama 3 была Dense.

После этого вышел GPT-4o, который стал и дешевле и быстрее, чем GPT-4, но с тем же качеством, причина по слухам как раз в том, что они перешли на MoE.

И только после всего этого бума MoE, известного в узких кругах, вышел DeepSeek v2, где был переход Dense на MoE. А вот DeepSeek R1 уже, конечно, принес мировую известность для MoE.

Ещё интересно, то, что Mistral первыми запустили волну MoE LLM, но при этом сами отказались от MoE и перешли обратно на Dense, недавно у них вышли новые модели, среди которых Devstral-2-123B, большая Dense модель, а новых MoE моделей у них совсем нет.

Сомневаюсь, что смогу посоветовать что-то особенное для 2+ машин.

llama.cpp RPC - не требовательный, работает без заморочек, но медленный, так как pipeline parallelism распараллеливает только данные, а не вычисления, вычисления идут последовательно по машинам.

sglang - экспериментально поддерживает gguf и может создавать кластер, умеет в настоящий tensor parallelism, чтобы полноценно параллелить и вычисления и данные.

vllm - тоже экспериментально поддерживает gguf, поддерживает tp, для распараллеливания надо использовать ray.

А на каком железе вы собрались запускать модель такого размера?

Запускал GLM-4.6 на игровом ПК, GLM-4.7 по сути по размерам не отличается, так что и скорости тоже. Требуется 11 Гб VRAM + память под контекст + 147 Гб ОЗУ.

Запускаем GPT-OSS-120B на 6 Гб GPU и ускоряем до 30 t/s. Вам нужна RAM, а не VRAM. Параметр -cmoe для ускорения MoE LLM

Запускаем настоящую DeepSeek R1 671B на игровом ПК и смотрим вменяемая ли она на огромном контексте (160к)

Очень интересно, как у них через RDMA получилось быстрее, чем просто разнести слои на разные машины и гонять данные по RPC, что llama.cpp и предлагает?

В exo используют тензорный параллелизм (Tensor Parallelism), а в RPC llama.cpp конвейерный параллелизм (Pipeline Parallelism). Это не особенность RDMA, просто для TP чем выше скорость и ниже задержки, тем быстрее синхронизация.

В TP над слоем работают одновременно все устройства, получая 2x и больше ускорение, но требуется интенсивный обмен результатами вычислений для синхронизации. В PP устройства просто работают друг за другом, такой способ не ускоряет, только масштабирует, а объем для обмена данными небольшой.

В llama.cpp нет поддержки TP, в ik_llama добавили что-то похожее через -sm graph, полноценный TP для gguf реализован в sglang или vllm.

Вообще, для нормальной работы 5000 серии нужна CUDA 13, для windows есть готовые билды, для линукса надо собрать llama.cpp с cuda-13.1.

Например, на RTX 5090 GPT OSS 120b с окном 4096 выдает 15 т/с, а если поставить окно 32000, то будет около 11 т/с (4 эксперта). Плюс сейчас постепенно много где появляется функция сжатия контекста — и в онлайновых, и в локальных фронтендах.

Можно быстрее. Я на медленной AMD RX 6600 запускал и получал 13 t/s. Для 128к контекста нужно всего 6Гб VRAM, так как GPT-OSS-120B использует SWA, который задействуется при включении flash-attention. Можно снизить до 3.5Гб если включить квантование KV-кэша Q8_0.

GPT-OSS-120B, AMD RX 6600 8 Гб + Ryzen 5600g 64Гб DDR4, Windows 11
GPT-OSS-120B, AMD RX 6600 8 Гб + Ryzen 5600g 64Гб DDR4, Windows 11

На 4090 со скоростью 37 t/s:

GPT-OSS-120B, 4090 + DDR5 4800Mhz
GPT-OSS-120B, 4090 + DDR5 4800Mhz

Запускаем GPT-OSS-120B на 6 Гб GPU и ускоряем до 30 t/s. Вам нужна RAM, а не VRAM. Параметр -cmoe для ускорения MoE LLM

В llama.cpp неделю назад добавили параметр --fit, включен по умолчанию, он автоматически подбирает параметры так, чтобы эффективно использовать GPU на максимум, для MoE моделей переключается с простого ngl на режим ncmoe и подбирает оптимальное значение. По умолчанию подбирает параметры так, чтобы осталось свободным 1 Гб VRAM, значение настраивается.

Также в llama.cpp добавили параметр --models-dir, для смены моделей налету без перезапуска сервера, в сочетании с --fit можно менять модели от маленьких до гигантских, не заморачиваясь с оптимальными настройками.

1
23 ...

Информация

В рейтинге
4 021-й
Зарегистрирован
Активность