Qwen3.5 на двух V100, reverse SSH вместо Cloudflare в Telegram Mini App: собираю AI-репетитора английского

от автора

У меня в углу комнаты стоит сервер с двумя Tesla V100 32GB. Они доcтались мне для другой задачи, которая отвалилась, и полгода стояли мёртвым грузом. Параллельно я в очередной раз пробовал заниматься английским — Simpler, Doalingo, ещё пара продуктов. Хорошие, но мне не подходил формат: я хотел сценарий «открыл телефон дома на семь минут, поговорил, закрыл». Без расписания, без камеры, без поиска тьютора, который понимает мой акцент с пятого раза.

Сошлось.

Идея: Telegram Mini App, в нём кнопка «говорить», за ней — AI-репетитор, который слышит, что я сказал, отвечает голосом, помнит контекст разговора, тыкает в мои повторяющиеся ошибки и подбрасывает слова, которые я пытаюсь выучить. Полностью бесплатно.

Модель Qwen3.5 вышла 25 февраля , я её гоняю всего несколько недель, продукт сырой. Эта статья — про архитектурные решения и про то, на какие грабли я уже успел наступить.

TL;DR

  • Что: Telegram Mini App для разговорной практики английского. Два режима голоса.

  • Local-режим: vLLM с QuantTrio/Qwen3.5-35B-A3B-AWQ (35B total / 3B active, MoE, 25 GiB AWQ INT4) на 2× Tesla V100 32GB + faster-whisper + Kokoro-82M, всё на тех же картах.

  • Сетевой стык: домашний сервер ↔ VPS через reverse SSH (autossh + systemd, restricted user с permitopen=localhost:23333). Никакого Cloudflare Tunnel, никакого Tailscale.

  • Главные мины: FlashAttention 2 на Volta не работает (нужен XFORMERS backend); bf16 на Volta тоже не работает (нужен --dtype float16); QuantTrio AWQ-сборка Qwen3.5 молча игнорирует enable_thinking=false в запросе (нужны серверные флаги vLLM); macOS Chrome не даёт второй AudioContext на 16 kHz если первый на 24 kHz (нужно ручной downsampling в AudioWorklet)

Архитектура одной картинкой

┌──────────────────────────────┐                ┌────────────────────────────────────┐│ Дом (V100 server)            │                │ VPS (public IP)                    ││                              │                │                                    ││  ┌────────────────────────┐  │  reverse SSH   │  ┌──────────────────────────────┐  ││  │ vLLM :23333            │◀─┼────────────────┼─▶│ FastAPI backend              │  ││  │ Qwen3.5-35B-A3B-AWQ    │  │  autossh +     │  │  /api/* + /ws/voice          │  ││  │ tensor-parallel = 4    │  │  systemd       │  └──────────────────────────────┘  ││  └────────────────────────┘  │                │  ┌──────────────────────────────┐  ││  ┌────────────────────────┐  │                │  │ aiogram 3 bot (long poll)    │  ││  │ faster-whisper :23334  │◀─┼────────────────┤  └──────────────────────────────┘  ││  └────────────────────────┘  │                │  ┌──────────────────────────────┐  ││  ┌────────────────────────┐  │                │  │ Mini App (React+Vite SPA)    │  ││  │ Kokoro TTS :23335      │◀─┼────────────────┤  └──────────────────────────────┘  ││  └────────────────────────┘  │                │       ▲                            │└──────────────────────────────┘                │       │  Nginx + Let's Encrypt     │                                                │       │                            │                                                │  ┌────┴─────────────────────────┐  │                                                │  │  MySQL 8  +  Redis 7         │  │                                                │  └──────────────────────────────┘  │                                                └───────┬────────────────────────────┘                                                        │  HTTPS / WSS                                                        ▼                                                ┌───────────────┐                                                │   Telegram    │                                                │   client      │                                                └───────────────┘

Три поддомена за общим Nginx на VPS:

englishbot.<мой домен>      → Mini App (React SPA в контейнере с nginx внутри)api.<мой домен>     → FastAPI backend (/api/* + /ws/*)admin.<мой домен>   → Admin Panel (React SPA)

Бэкенд, бот, Mini App и админка — четыре сервиса в одном docker-compose.yml на VPS. MySQL и Redis пока живут прямо на хосте VPS. LLM, STT и TTS — на домашнем сервере с GPU. Между ними — reverse SSH-туннель.

Почему четыре V100 и почему Qwen3.5-35B-A3B-AWQ

V100 — это Volta, compute capability 7.0, релиз 2017. По меркам 2026 года — олдскул. Никакого FlashAttention 2, никакого bf16, на ROCm и Triton-контейнерах их выбрасывают как класс.

Из бенчмарка E2E Networks (вышел в марте 2026, sweep по vLLM/TGI/TensorRT-LLM на 7B–70B):

«V100 is vLLM-only. TGI, TensorRT-LLM, and Triton’s latest containers reject Volta GPUs entirely.»

Это, кстати, и ответ на вопрос «почему vLLM, а не TGI» — никакого выбора у меня и не было. На 2×V100 PCIe в FP16 авторы того же отчёта получили 400,2 tok/s на Llama-2-7B (p50 latency 2 559 мс, gen_len 256) и 228,8 tok/s на Llama-2-13B (p50 latency 4 475 мс). Для одного-двух одновременных голосовых разговоров — более чем.

Теперь модель. Я честно пробовал Llama-3.1-8B и Qwen2.5-14B; обе для тьютора английского ощущались «вежливыми, но плосковатыми». Когда вышел Qwen3.5, я сразу пошёл смотреть, что есть в AWQ — потому что класть 70B в FP16 на 4×32 GB после KV-cache и оверхеда vLLM физически не хватает.

QuantTrio/Qwen3.5-35B-A3B-AWQлёг как родной. Архитектура — это, на мой взгляд, самое интересное в Qwen3.5. Дословно из карточки модели:

«Number of Parameters: 35B in total and 3B activated. Number of Layers: 40. Hidden Layout: 10 × (3 × (Gated DeltaNet → MoE) → 1 × (Gated Attention → MoE)). Mixture Of Experts: Number of Experts: 256. Number of Activated Experts: 8 Routed + 1 Shared. Context Length: 262,144 natively and extensible up to 1,010,000 tokens.»

Перевожу: из 40 слоёв только 10 — это полноценное Gated Attention. Остальные 30 — Gated DeltaNet, линейная attention. KV-cache считается только для тех самых десяти, и считается не квадратично по длине контекста. Грубо: при 16 query-головах и 2 KV-головах с head_dim=256 на каждый слой full-attention выходит около 2 × 2 × 256 × 2 байт = 2 KB на токен на слой, всего ~20 KB/токен на десять слоёв. На 262 144 токена контекста — около 5,4 GB на сессию в FP16. При tensor-parallel-size=2 это ~2,7 GB на карту на одну сессию. На 32 GB V100 — реально.

Грабля №1: FlashAttention и Volta

Первый запуск vLLM на V100 встретил меня логом:

Cannot use FlashAttention-2 backend for Volta and Turing GPUs.Using XFormers backend.

Это нормально. Это даже не варнинг — это просто факт жизни. Из апстрима Dao-AILab/flash-attention, issue #1760:

«I understand that FlashAttention 2+ requires SM80+, which V100s don’t support.»

И отдельно, vLLM issue #6173:

«RuntimeError: FlashAttention only supports Ampere GPUs or newer.»

Решение: явно ставим бэкенд XFORMERS. Не FLASH_ATTN, не FLASHINFER, не FLASHMLA. На Volta из живых вариантов — XFORMERS и TORCH_SDPA. TRITON_ATTN существует, но его основная отладка идёт на AMD ROCm и Intel XPU; на Volta это untested path, я туда не полез. Из блога vLLM по Triton-backend (март 2026):

«Since the Triton attention backend is always present, it also serves as a fallback backend, if FlashAttention, FlashInfer, or other dependencies are unavailable or fail to import.»

То есть он fallback, не первичный выбор. На V100 — XFORMERS.

Грабля №2: bf16 на Volta тоже не работает

Если запустить vLLM без явного --dtype, на V100 получишь:

Bfloat16 is only supported on GPUs with compute capability of at least 8.0.Your Tesla V100-SXM2-32GB GPU has compute capability 7.0.You can use float16 instead by explicitly setting the dtype flag in CLI,for example: --dtype=half

Поэтому в start.sh стоит --dtype float16. Никаких сюрпризов от автоматического подбора dtype.

Грабля №3: AWQ + concurrency — резкое падение throughput

Это самое неприятное и не очень обсуждаемое. vLLM issue #20469, по AWQ-сборке Qwen3-14B на одном A100 80GB:

«We observed a significant drop in output tokens per second when serving Qwen/Qwen3-14B-AWQ on a single A100 80GB GPU using vLLM v0.9.1 with --max-model-len 16384. At concurrency=1, the model achieves ~52 output tokens per second. However, this drops sharply to ~12 at concurrency=5 and ~3 at concurrency=25.»

Перевожу: на одном запросе ты получаешь 52 tok/s, на пяти параллельных — 12, на 25 — три. Это AWQ Marlin-ядра ведут себя плохо под concurrency. На Volta с tensor-parallel=4 ситуация другая (TP помогает), но иллюзий про линейное масштабирование строить не нужно.

Практический вывод для бота: на free-tier с 10 минутами в день одного пользователя одновременно говорящих будет немного. Если пойдёт нагрузка — придётся либо ставить вторую инстанцию vLLM на другой набор карт, либо запускать в FP16 без квантизации (но тогда 35B физически не влезет). Это та самая «знаю, что неидеально» точка.

vLLM start.sh — без сокращений

/home/user/1Cat-vLLM/start_vllm.sh:

#!/usr/bin/env bashset -ecd "$(dirname "$0")"source venv/bin/activateexec python -m vllm.entrypoints.openai.api_server \    --model QuantTrio/Qwen3.5-35B-A3B-AWQ \    --quantization awq \    --dtype float16 \    --gpu-memory-utilization 0.90 \    --max-model-len 262144 \    --tensor-parallel-size 4 \    --max-num-seqs 8 \    --max-num-batched-tokens 65536 \    --attention-backend XFORMERS \    --skip-mm-profiling \    --limit-mm-per-prompt '{"image":0,"video":0}' \    --compilation-config '{"cudagraph_mode":"full_and_piecewise","cudagraph_capture_sizes":[1,2,4,8,16,32]}' \    --disable-custom-all-reduce \    --reasoning-parser qwen3 \    --default-chat-template-kwargs '{"enable_thinking": false}' \    --host 0.0.0.0 \    --port 23333

Построчно:

  • --quantization awq --dtype float16 — единственный рабочий вариант на Volta.

  • --gpu-memory-utilization 0.90 — десять процентов оставляю буфером, всё остальное под веса и KV-cache.

  • --max-model-len 262144 — нативный контекст Qwen3.5. Можно опустить до 32 768 — авторская карточка модели прямо это рекомендует для скромных конфигов («--max-model-len 32768 --tensor-parallel-size 4 --enable-expert-parallel»). Я держу 262K, потому что на бота-репетитора большой контекст приходит не часто, а когда приходит — он очень нужен.

  • --tensor-parallel-size 4 — все четыре карты в один pipeline. Официальный гайд Qwen Team рекомендует TP=8, но у меня их физически четыре.

  • --max-num-seqs 8 --max-num-batched-tokens 65536 — крыша по параллельным сессиям и токенам в одном батче.

  • --attention-backend XFORMERS — см. граблю №1.

  • --limit-mm-per-prompt '{"image":0,"video":0}' --skip-mm-profiling — multimodal я не использую, отключаю профилирование памяти.

  • --disable-custom-all-reduce — кастомный all-reduce на старых интерконнектах PCIe Gen3 ведёт себя нестабильно, родной NCCL надёжнее.

  • --compilation-config — захват CUDA-graphs для типовых размеров батча. Холодный старт чуть дольше, hot path заметно быстрее.

  • --reasoning-parser qwen3 --default-chat-template-kwargs '{"enable_thinking": false}' — про это отдельная грабля, см. ниже.

Грабля №4: enable_thinking=false в QuantTrio молча игнорируется

Qwen3.5 — reasoning-модель. В голосовом боте это катастрофа: TTS озвучит ход рассуждения целиком. «Hmm, let me think. The user asked me about… well, in English we usually say…» — и пять секунд тишины пока модель додумает.

Документация Qwen говорит: добавляй в запрос chat_template_kwargs.enable_thinking=false и всё. Документация vLLM подтверждает: можно прокинуть через extra_body. По факту в QuantTrio AWQ-сборке это просто не срабатывает.

vLLM issue #35574 (закрыт после фикса в апстриме):

«The vllm/vllm-openai:qwen3_5 image is deployed. Describe the bug: Adding "chat_template_kwargs": {"enable_thinking": false} to the HTTP body does not disable the thinking process.»

HuggingFace discussion по Qwen3.5-27B:

«WARNING 02-25 15:46:28 [protocol.py:51] The following fields were present in the request but ignored: {'enable_thinking', 'extra_body'}»

И вишенка прямо из карточки QuantTrio:

«Qwen3.5 does not officially support the soft switch of Qwen3, i.e., /think and /nothink. Qwen3.5 will think by default before response.»

То есть soft-switch через /no_think в промпте не работает, через extra_body тоже игнорируется, а модель всё равно думает.

Решение, которое в итоге заработало надёжно: серверные флаги vLLM --reasoning-parser qwen3 плюс --default-chat-template-kwargs '{"enable_thinking": false}'. Это аргументы запуска самого сервера, а не отдельного запроса — vLLM применяет их ко всему трафику, и <think>...</think> теги либо не генерируются вовсе, либо отрезаются парсером перед отдачей в content.

Defense in depth: на стороне бэкенда я ещё дополнительно прогоняю ответ через regex, который выгрызает <think>...</think>. Если что-то протекло — TTS не получит. Это паранойя, но цена ошибки выше цены трёх строк кода.

Reverse SSH вместо Cloudflare Tunnel

В 2026 году, если ты хочешь выставить домашний сервис наружу через VPS, тебе на каждом углу советуют Cloudflare Tunnel или Tailscale Funnel. Я выбрал reverse SSH, и сейчас объясню почему.

Cloudflare Tunnel. Технически работает. Но: бесплатный тариф формально запрещает streaming-нагрузку, а у меня по туннелю прогоняется голосовой WebSocket. Плюс Cloudflare декриптит HTTPS на edge — то есть в plaintext видит каждый запрос:

«When you use Cloudflare Tunnel, Cloudflare decrypts your HTTPS traffic at their edge servers, inspects it, and then re-encrypts it before forwarding to your origin. This means Cloudflare can see the plaintext content of every request and response passing through your tunnel.»

Для большинства проектов это не проблема. Для голосового потока с initData-токенами Telegram — мне это просто не нравится.

Tailscale Funnel. Публичный доступ через инфраструктуру Tailscale-релея. Хорошо для не-критичных вещей, но это ещё одна третья сторона в пути от Telegram к моей V100.

Reverse SSH через autossh + systemd. Старо, проверено, понятно. V100 сама коннектится наружу к VPS под отдельным пользователем tunnel, у которого нет shell (command="/bin/false"), нет TTY, и которому разрешён ровно один port-forward (permitopen="localhost:23333"). Если кто-то украдёт ключ — он сможет открыть ровно этот один порт на VPS, и больше ничего.

VPS-side /home/tunnel/.ssh/authorized_keys:

command="/bin/false",no-agent-forwarding,no-X11-forwarding,no-pty,permitopen="localhost:23333" \  ssh-ed25519 AAAA...KEY... v100-to-vps-tunnel

В /etc/ssh/sshd_config:

AllowTcpForwarding yesClientAliveInterval 30ClientAliveCountMax 3

V100-side /etc/systemd/system/vllm-tunnel.service:

[Unit]Description=Reverse SSH tunnel V100 -> VPS (vLLM :23333)After=network-online.targetWants=network-online.target[Service]Type=simpleUser=userEnvironment="AUTOSSH_GATETIME=0"Environment="AUTOSSH_PORT=0"ExecStart=/usr/bin/autossh -M 0 -N -T \  -o "ServerAliveInterval=30" \  -o "ServerAliveCountMax=3" \  -o "ExitOnForwardFailure=yes" \  -o "StrictHostKeyChecking=accept-new" \  -o "TCPKeepAlive=yes" \  -i /home/user/.ssh/vps_tunnel \  -R 23333:localhost:23333 \  tunnel@<vps-ip>Restart=alwaysRestartSec=10[Install]WantedBy=multi-user.target

Что тут происходит:

  • autossh -M 0 — отключаем встроенный monitor port autossh, полагаемся на родной ServerAliveInterval openssh. На современных версиях ssh это работает надёжнее.

  • AUTOSSH_GATETIME=0 — коннект сразу при старте, не ждём «успешного» периода.

  • ServerAliveInterval=30, ServerAliveCountMax=3 — если три heartbeat-а подряд не ответили (90 секунд), процесс умирает.

  • ExitOnForwardFailure=yes — если VPS-порт занят другим процессом, мы громко падаем, а не висим молча. systemd перезапустит.

  • Restart=always, RestartSec=10 — после смерти процесса ждём 10 секунд и поднимаемся снова.

Бэкенд на VPS видит vLLM просто как http://localhost:23333/v1. Поскольку бэкенд живёт в Docker, в docker-compose.yml ему нужен extra_hosts для разрешения хоста:

backend:  build: ./backend  extra_hosts:    - "host.docker.internal:host-gateway"  # ...

И в .env:

LLM_PROVIDER=vllmVLLM_BASE_URL=http://host.docker.internal:23333/v1VLLM_MODEL_NAME=QuantTrio/Qwen3.5-35B-A3B-AWQ

Никаких TLS-сертификатов между V100 и VPS — всё внутри SSH-туннеля и так шифровано. Никаких DNS-записей для V100 — у неё нет ни одной публично известной точки входа.

Честная оговорка: это single point of failure. Если V100 упала, или интернет дома моргнул, или autossh подвис на десять секунд — локальный voice-режим недоступен. Для pet-проекта на ранней стадии я считаю это приемлемым; на проде я бы поставил вторую инстанцию в другом физическом месте и балансер. Но до этого пока далеко.

Один контраргумент, который мне присылали: «autossh редундантен, современный openssh сам умеет heartbeat». Это правда — если правильно настроить ServerAliveInterval, openssh упадёт сам, и systemd его поднимет. autossh даёт более явный exit code и пара дополнительных опций для retry — мне с ним комфортнее, но systemd-only вариант тоже валиден.

Локальный voice-режим: faster-whisper + Kokoro-82M

STT — faster-whisper. Это обёртка над CTranslate2 над OpenAI Whisper. По сравнению с whisper.cpp (winner на CPU/edge) faster-whisper на GPU быстрее и проще интегрируется в Python-пайплайн. Используется INT8 квантизация для economy или FP16 для качества — я в первой версии оставил FP16.

TTS — Kokoro-82M. Это сейчас, на мой взгляд, лучший open-weight TTS до миллиарда параметров. На 19 мая 2026 на Artificial Analysis TTS Leaderboard Kokoro v1.0 имеет ELO 1056 и занимает 5-е место среди open-weight моделей (топ-1 — Fish Audio S2 Pro с ELO 1128). 82M параметров, лицензия Apache 2.0, обучена на ~100 часах разрешённого аудио — для модели такого качества это уникальное сочетание.

Латентность: по официальным замерам авторов CodeSOTA на NVIDIA A100 — RTF 0.03 (10 секунд аудио синтезируется за 0,3 секунды). На V100 я ожидаю в районе RTF 0.05–0.10, но прямых публичных бенчмарков для Kokoro на V100 нет — это extrapolated цифра, не верьте на слово, я буду мерить и опубликую.

Голос — af_heart, sample rate 24 kHz, моно, PCM s16le. Между моим бэкендом и Kokoro-сервером — WebSocket с маленьким понятным протоколом:

client → server:  {"type": "config", "voice": "af_heart", "speed": 1.0, "lang": "a"}server → client:  {"type": "ready"}client → server:  {"type": "text", "text": "How was your weekend?"}server → client:  {"type": "audio", "data": "<base64 PCM chunk>"}   # 40 ms каждыйserver → client:  {"type": "audio", "data": "..."}...server → client:  {"type": "done"}client → server:  {"type": "close"}

Чанки по 40 мс (960 семплов при 24 kHz) — компромисс между frequency message-passing и плавностью воспроизведения в браузере. Сам Kokoro не стримит (это offline-модель), но я режу её вывод на чанки на стороне сервера — это даёт фронту что-то проигрывать пока остальное синтезируется.

Реализация сервера — простой FastAPI + KPipeline + один asyncio.Lock на доступ к pipeline. Лок нужен потому что KPipeline не thread-safe; сериализация запросов в одну очередь — это нормально, синтез всё равно быстрее realtime даже на одной карте.

Telegram initData и WebSocket-аутентификация

Каждое подключение Mini App к /ws/voice валидирует подпись Telegram initData. Схема стандартная (https://core.telegram.org/bots/webapps), но в неё многие не вписывают одну важную проверку.

Алгоритм:

  1. data_check_string = все ключи initData кроме hash, отсортированные по ключу, в формате key=value, склеенные через \n.

  2. secret_key = HMAC_SHA256(bot_token, "WebAppData") (порядок аргументов важен: bot_token — это data, «WebAppData» — это key).

  3. computed_hash = HMAC_SHA256(data_check_string, secret_key).hex().

  4. Сравниваем computed_hash с hash из initData. Не совпало — закрываем коннект с кодом 4001.

И, что забывают:

  1. Проверяем auth_date — не старше 5 минут. Без этой проверки можно перехватить initData и переиспользовать вечно.

Реализация в FastAPI Depends, около 20 строк:

import hashlibimport hmacimport timefrom urllib.parse import parse_qslBOT_TOKEN = settings.BOT_TOKEN.encode("utf-8")SECRET_KEY = hmac.new(b"WebAppData", BOT_TOKEN, hashlib.sha256).digest()INIT_DATA_TTL_SEC = 300def validate_init_data(init_data: str) -> dict | None:    """Возвращает распарсенный initData или None если подпись/время не сошлись."""    parsed = dict(parse_qsl(init_data, strict_parsing=True))    received_hash = parsed.pop("hash", None)    if not received_hash:        return None    # Проверка времени    auth_date = int(parsed.get("auth_date", 0))    if abs(time.time() - auth_date) > INIT_DATA_TTL_SEC:        return None    # Проверка подписи    data_check_string = "\n".join(        f"{k}={v}" for k, v in sorted(parsed.items())    )    computed = hmac.new(        SECRET_KEY, data_check_string.encode("utf-8"), hashlib.sha256    ).hexdigest()    if not hmac.compare_digest(computed, received_hash):        return None    return parsed

И в обработчике:

@app.websocket("/ws/voice")async def voice_ws(websocket: WebSocket):    init_data = websocket.query_params.get("init_data", "")    parsed = validate_init_data(init_data)    if parsed is None and settings.ENVIRONMENT == "production":        await websocket.close(code=4001)        return    await websocket.accept()    # ... дальше run_gemini_session или run_local_session

В development-режиме валидация отключается — это нужно для wscat/websocat-тестов локально. В production — невалидная подпись или просроченный auth_date = 4001.

Что я знаю, что неидеально

Список фейлов и архитектурных мин, на которые мне будет приятно получить критику в комментариях:

  • Reverse SSH = single point of failure. Упала V100 — упал локальный voice-режим.

  • vLLM в одном инстансе. Нет реплик, нет балансера. При concurrency >5 на AWQ-сборке throughput резко падает (см. граблю №3).

  • MySQL 8 на одном инстансе. Нет master-replica, нет PITR-бэкапов, только nightly dumps.

  • Нет E2E voice-тестов. Тестируется отдельно: STT, LLM, TTS, WS. Интеграция целиком — только руками.

  • Kokoro RTF на V100 — оценочный, прямых публичных замеров нет, я буду публиковать свои.

  • Нет собственных throughput-метрик Qwen3.5-35B-A3B-AWQ на 2×V100, пока. Модель свежая, нагрузки нет — мерить нечего; ожидаю собрать данные за несколько недель боевой эксплуатации и опубликовать отдельно.

Что дальше

Сейчас я в режиме «работает, дальше копим живую нагрузку и метрики».

Если хочется потрогать руками — бот: @kmo_ai_english_bot

Буду рад жёстким комментариям, особенно по граблям.

Источники, на которые я ссылался по ходу статьи:

  • QuantTrio/Qwen3.5-35B-A3B-AWQ model card — huggingface.co/QuantTrio/Qwen3.5-35B-A3B-AWQ

  • E2E Networks LLM Inference Benchmark — e2enetworks.com (vLLM, TGI, TensorRT-LLM на A30 vs V100)

  • vLLM Recipes — Qwen3.5 & Qwen3.6 Usage Guide — docs.vllm.ai

  • vLLM issues #20469 (AWQ concurrency drop), #35574 (enable_thinking игнорится), #6173 (FlashAttention on Volta)

  • 0110.be — Resampling audio via a Web Audio API Audio Worklet

  • DeepWiki / marcinmatys/whisper_streaming — audio processing chapter

  • Artificial Analysis TTS Leaderboard — artificialanalysis.ai/text-to-speech/leaderboard

  • Telegram Mini Apps initData spec — core.telegram.org/bots/webapps

  • vLLM Triton Attention Backend Deep Dive — vllm.ai/blog/2026-03-04

ссылка на оригинал статьи https://habr.com/ru/articles/1042166/