A FHE está mudando o jogo e estamos todos convidados a jogar.
Se você não tem ideia do que estou falando, significa que ultimamente você tem vivido debaixo de uma rocha. Navegue por alguns dos recursos abrangentes do FHE.org e volte.
Para que a FHE cumpra sua promessa ao mundo da tecnologia, ela deve vir acompanhada de uma nova geração de ferramentas de força industrial para desenvolvimento, compilação e execução em tempo de execução, que qualquer pessoa possa usar para criar aplicativos homomórficos facilmente.
Atualmente, porém, muitos especialistas e empresas no espaço FHE ainda investem a maior parte de seu tempo e esforços na melhoria da criptografia por trás do FHE, em vez de se concentrarem na construção de excelentes ferramentas de desenvolvimento para não especialistas. Ouça-me bem aqui: aprimorar as funcionalidades e o desempenho principais do FHE sempre será uma boa notícia. Mas no grande esquema das coisas, estas melhorias incrementais promovem a adopção global, na melhor das hipóteses, marginalmente. Eles terão impacto na adoção em algum momento, mas não agora .
Do meu ponto de vista, é óbvio que o mundo da tecnologia precisa hoje de cadeias de ferramentas FHE poderosas e fáceis de desenvolver, para começar a desbloquear histórias de sucesso impulsionadas pelo FHE e mover o FHE de uma tendência tecnológica para uma mudança de paradigma real no negócio de segurança digital. Acredito que o estado actual do conhecimento sobre a FHE - tanto científica como tecnologicamente - já é suficiente para construir tais ferramentas no presente e disponibilizá-las às massas conhecedoras de tecnologia sem mais demora. A integração contínua de novos recursos se desenvolverá naturalmente com o tempo, sempre acontece.
Mas o problema é o seguinte: o FHE vem em vários sabores. Dependendo de qual esquema criptográfico você está usando - ou do uso específico que você faz dele, surge uma maneira diferente de representar a computação e executar programas homomórficos. É como se os esquemas FHE fossem animais completamente diferentes, um fornecendo máquinas de Turing e outro cálculo lambda. A biodiversidade é sempre boa em tecnologia ou não, mas também significa que é necessário definir uma estratégia viável ao instrumentar a FHE na prática.
Minha empresa Zama concentra-se em um esquema FHE específico que é o TFHE . TFHE alcança processamento de dados homomórficos com recursos muito específicos: bootstrappings super-rápidos e cálculos expressos como redes de pesquisas de tabela. Adquirimos uma compreensão profunda de como essas peculiaridades - que costumavam fazer do TFHE uma espécie de oprimido no espaço FHE - podem ser traduzidas em bibliotecas homomórficas, compilação, máquinas virtuais ou aceleração de hardware.
Outros concorrentes proeminentes do FHE como CKKS , BGV ou BFV envolvem conceitos muito diferentes em sua instrumentação prática: bootstrappings são muito lentos para serem uma opção, então o processamento é limitado em profundidade, no entanto os dados podem ser vetorizados com lotes massivos, cálculos expressos como circuitos polinomiais e - no caso do CKKS - os resultados são aproximados. Portanto, a tradução de BGV/BFV e CKKS em compiladores e tempos de execução requer uma mentalidade totalmente diferente dos criadores de ferramentas.
No entanto, é improvável que os desenvolvedores se importem muito com qual esquema específico está alimentando seu conjunto de ferramentas e ambientes de tempo de execução FHE, desde que sejam facilmente operados e os requisitos de desempenho sejam atendidos em aplicativos homomórficos implantados. Eles próprios são construtores criativos e seu foco deve estar nas novas experiências que podem oferecer aos seus usuários.
Portanto, o objetivo final dos facilitadores da tecnologia FHE é conceber e fornecer ferramentas que não estejam apenas prontas para seu horário nobre no multiverso de desenvolvimento, mas que sejam fortes o suficiente para estabelecer um novo padrão no setor. Para conseguir isso, eles têm que arriscar sobre qual esquema FHE tem maior probabilidade de levá-los até lá.
Vamos jogar um jogo.
Considere um desenvolvedor que não conhece nada das complexidades do FHE, mas deseja construir um aplicativo homomórfico. Você é o criador da ferramenta aqui, está enfrentando aquele desenvolvedor, de quem pode esperar alguma familiaridade com as práticas comuns de desenvolvimento e os fundamentos da ciência da computação, mas todo o resto - matemática avançada e coisas do gênero, está fora dos limites. Como você pode fazer com que eles produzam o aplicativo FHE por conta própria?
Este artigo explora várias estratégias para ganhar esse jogo apostando no TFHE.
Dependendo da natureza da aplicação – processamento de dados personalizado, redes neurais, contratos inteligentes ou programação de uso geral, serão explorados caminhos vencedores habilitados para TFHE. Esta exploração nos levará por um caminho fácil, um caminho difícil e alguns outros intermediários, com diferentes níveis de prontidão tecnológica na sua realização prática.
TFHE significa Toro FHE. Também conhecido como CGGI devido aos nomes de seus descobridores, o TFHE ocupa uma posição única no cenário do FHE: é o mecanismo mais conhecido para permitir o bootstrapping programável (PBS).
Resumindo, um PBS é uma consulta de tabela homomórfica. Ele retorna uma criptografia de T[x]
onde T
é uma função tabulada de sua escolha, dada uma criptografia de um índice x
. Sua velocidade de execução não depende das entradas de T
, mas apenas do número de entradas, e está na faixa de milissegundos. Além disso, um PBS redefine o ruído de criptografia incorporado em seu texto cifrado de saída, para que você possa compor PBSs em sequência indefinidamente, sabendo que seu aplicativo homomórfico sempre lidará com textos cifrados limpos.
O modelo computacional que o TFHE defende é por excelência.
A unidade elementar de processamento nos programas TFHE se parece exatamente com um neurônio e compõe 2 operações homomórficas básicas:
Uma combinação linear de entradas que retorna E(x)
onde x = w_1 x_1 + … + w_n x_n modulo m
, dadas as entradas criptografadas E(x_1), …, E(x_n)
e um conjunto de pesos de texto simples w_1, …, w_n
.
Um PBS que calcula E(T[x])
de E(x)
onde T
é alguma tabela de texto simples de tamanho m
.
No "neurônio TFHE", m
, x_i
, w_i
, bem como as entradas T[0], …, T[m-1]
são todos inteiros, e pode-se escolher livremente os "parâmetros" m
, w_1, …, w_n
e T
. Dado que a combinação linear tem custo quase zero, o gargalo da eficiência é o PBS cujo tempo de execução depende exclusivamente do módulo m
: a velocidade diminui à medida que m
aumenta. Isto convida a utilizar valores pequenos - ímpares ou pares, mas pelo menos 2 - para módulos em neurónios TFHE, embora seja necessário encontrar um compromisso para evitar uma redução demasiado drástica da sua expressividade computacional.
Agora, da mesma forma que os neurônios são montados em camadas nas redes neurais para se beneficiar do paralelismo e dos truques de HPC, as redes TFHE acumulam camadas de neurônios TFHE. Cada camada apresenta um módulo de matriz de pesos, um módulo comum m
e um vetor de tabelas de pesquisa de tamanho m
. No entanto, o módulo pode diferir na camada anterior ou na próxima, assim como o formato da camada.
E isso encerra o TFHE! Pronto, só precisamos expressar sistematicamente funções como redes de pesquisa. Agora temos nosso modelo computacional homomórfico.
Na realidade, o TFHE suporta múltiplas extensões desse modelo (gráficos arbitrários de operadores de nível inferior, textos cifrados de vários tipos, pesquisas de tabelas com múltiplas saídas, múltiplas formas de empacotar variáveis, etc.). Mas podemos ignorar essas melhorias por enquanto porque a visão de rede de pesquisa já é poderosa o suficiente para nos permitir converter um programa simples em um equivalente homomórfico e executá-lo. Portanto, podemos nos concentrar em como fazer exatamente isso, pelo menos para uma primeira iteração da tecnologia.
Como tal, uma rede TFHE é apenas um modelo e ainda não está pronta para execução adequada dentro de um aplicativo homomórfico. Embora módulos, matrizes de peso e tabelas de pesquisa sejam totalmente especificados para todas as suas camadas, ainda falta um ingrediente crucial que é uma parametrização criptográfica.
Os parâmetros criptográficos ditam todas as métricas possíveis sobre o que acontecerá dentro da rede em tempo de execução: tamanhos concretos de texto cifrado, as dimensões reais do tensor das chaves de troca de chave e de inicialização internas aos PBSs, várias opções algorítmicas dentro de operadores homomórficos de baixo nível, como as precisões do texto simples e o ruído os níveis evoluem em toda a rede e, em última análise, as especificidades de como criptografar e descriptografar. Eles também prevêem o uso e o desempenho da memória.
Descobrir qual conjunto de parâmetros otimiza a execução de uma rede TFHE pode ser complicado e, em qualquer caso, terrivelmente difícil - mesmo para especialistas - fazer no estilo caneta e papel como nos primeiros dias do FHE, já que tudo depende de tudo . Além disso, vários conjuntos ótimos podem coexistir simultaneamente, exigindo assim uma arbitragem entre o tamanho da chave pública, a latência do caminho crítico ou o rendimento máximo. Felizmente, o problema de automatizar esta tarefa foi resolvido nos últimos anos e agora existem poderosas ferramentas de otimização para determinar rapidamente a melhor instanciação criptográfica de uma determinada rede TFHE.
Uma vez instanciada com parâmetros criptográficos, uma rede TFHE torna-se verdadeiramente executável, ou mais exatamente, é passível de um verdadeiro executável através de uma passagem apropriada de compilação.
Um programa TFHE é uma coleção de redes TFHE parametrizadas coladas por "lógica simples".
A lógica pura é feita de
instruções operando sobre variáveis de texto simples (ou seja, variáveis normais e não criptografadas),
ramificação, incondicional ou condicionada a predicados de texto simples,
leitura e escrita de memória em endereços de texto simples, aritmética de ponteiros,
chamadas para sub-rotinas/funções.
Basicamente, a lógica simples contém qualquer lógica de programação suportada pela linguagem, com exclusão de um único caso: modificar variáveis criptografadas do programa, que é prerrogativa das partes TFHE do programa. A única coisa que a lógica simples pode fazer com textos cifrados - e também com chaves públicas do TFHE - é movê-los sem alterações e alimentá-los nas partes do TFHE, como se elas estivessem rodando dentro de seu próprio coprocessador ou contêiner separado.
Para todos os efeitos, um programa que atenda a esta definição está completo e pronto para se tornar uma aplicação homomórfica completa, qualquer que seja a linguagem de programação. A compilação personalizada fornecerá esse mapeamento final, e o objeto resultante poderá então ser executado em um tempo de execução habilitado para TFHE ou como um executável independente e independente.
Uma linguagem dedicada pode ser usada para unificar a representação de programas TFHE - como alguns DSL, ou melhor, um dialeto MLIR - para que a compilação possa ser realizada com o mesmo compilador back-end.
A natureza precisa do tempo de execução (biblioteca compartilhada/dinâmica, VM ou outro) é apenas uma modalidade aqui. Qualquer uma das opções levará a um aplicativo homomórfico operacional alimentado por TFHE que pode ser implantado e exposto aos usuários.
Agora vamos voltar ao jogo por um minuto.
Estamos diante de um desenvolvedor que não sabe nada sobre TFHE ou acima, mas deseja construir um aplicativo homomórfico. Suponha que lançamos o compilador discutido acima e o tempo de execução habilitado para TFHE, se houver.
Nosso objetivo está estabelecido, só precisamos de um programa TFHE para chegar a um executável. Mas... como diabos vamos fazer com que o desenvolvedor produza algo tão específico quanto um programa TFHE?
Aí vem o caminho fácil para vencer: você encapsula toda a complexidade em uma API FHE de caixa preta.
Em qualquer linguagem de programação, um programa (simples) pode ser visto essencialmente como uma combinação de 3 ingredientes:
a lógica programática, feita de instruções nativas da linguagem e construções de escopo,
variáveis e uma seleção de tipos de dados que o programa lhes atribui, escolhidos entre todos os tipos de dados possíveis,
chama uma seleção de funções externas, selecionadas entre todas as funções externas disponíveis, que o programa utiliza para operar em suas variáveis.
Os tipos de dados têm sua própria sublinguagem, uma mistura de tipos nativos e construções de digitação para estender esses tipos nativos e combiná-los em tipos estruturados de nível superior. O sistema de tipos destina-se a fornecer expressividade suficiente para cobrir praticamente qualquer estrutura de dados que o programa possa exigir. Funções externas são aquelas fornecidas por bibliotecas, padrão ou não, mas também podem ser invocadas implicitamente por instruções nativas da linguagem (pense em operadores para aritmética modular ou divisão).
Portanto, programas simples são na verdade "simples":
Ouça-me bem aqui. Não estou dizendo que todos os conceitos de programação de alto nível, como polimorfismo, objetos com seus membros e atributos, classes e herança hierárquica, modelos, macros, características, threads, recursão, açúcar sintático, anotações e todos os outros custos zero imagináveis as abstrações fornecidas pela linguagem são necessárias e simples para o desenvolvedor manipular, embora tenham sido inventadas para simplificar seu trabalho.
Só estou dizendo que, para o nosso propósito, eles são apenas decorações inócuas que desaparecem em tempo de compilação, porque uma versão reduzida, porém equivalente, do programa na forma normal é inferida da fonte. Essa versão do programa, expressa em qualquer representação intermediária (IR), é composta de blocos básicos em linha reta - sequências de instruções que são elementares naquela IR - conectados por algum gráfico de fluxo de controle.
Agora é este programa na forma normal que é "simples". Quero dizer, simples o suficiente para ser aumentado com capacidades homomórficas.
Você ganha o jogo lançando uma biblioteca FHE nativa do idioma e deixando o desenvolvedor decidir como usá-la da melhor forma.
A biblioteca irá expor novos tipos de dados - criptografados, para complementar os simples - e uma coleção de funções homomórficas que imitam (mais ou menos) as funções simples com as quais o desenvolvedor está familiarizado, só que funcionam com tipos de dados criptografados. Resumindo, você está estendendo o sistema de tipos e o ecossistema da biblioteca e deixando a inteligência do desenvolvedor descobrir como usar essas extensões para criar seu aplicativo homomórfico.
Isto não está particularmente ligado ao TFHE, qualquer esquema FHE servirá. É nisso que os fornecedores das várias bibliotecas FHE se concentram: melhorar a usabilidade e a facilidade de uso do FHE, expondo funções homomórficas de alto nível que parecem o mais próximas possível da experiência de codificação simples. Toda a complexidade criptográfica é abstraída em caixas pretas para as quais o programa fará chamadas oracle.
Isso pode funcionar bem para o desenvolvedor, é claro. Bem... se você cumprir sua parte no acordo como fornecedor da biblioteca.
Eles descobrirão um programa homomórfico que agora se parece com isto.
Agora existem variáveis simples e criptografadas coexistentes e o programa precisa manter uma divisão estrita entre esses dois tipos de objetos. Isso ocorre porque existe uma regra de ouro do FHE que diz que quando você aplica uma função a uma mistura de argumentos simples e criptografados, o resultado é necessariamente criptografado, por exemplo, fhe_add(E(x), y)
retorna E(x+y)
e breve. Portanto, variáveis simples podem entrar em algumas funções FHE, mas nunca sair delas. A criptografia homomórfica “contamina” tudo o que toca por meio da computação.
Então, veja... como você ramifica condicionalmente para uma variável criptografada?
Bem, você não pode. Mas não é um grande problema.
Em um aplicativo FHE, a ramificação condicional só pode atuar em booleanos simples, não em booleanos criptografados. Como você saberia para onde pular com base em um bit criptografado? Você não tem a chave privada do usuário para descriptografar esse bit.
Felizmente, o FHE também oferece truques simples para remediar isso.
Suponha que o desenvolvedor inicialmente quisesse fazer algo como
if (x == 0) then y = 3 else z = 7
mas percebe que, nesse ponto, a variável x
estará realmente criptografada. Como adaptar esse pedaço de código?
A primeira coisa a fazer é retrabalhar a instrução if
simples para obter uma parte equivalente de código de linha reta que usa multiplexação:
bit = (x == 0) // bit = 1 if x == 0 otherwise 0 y = 3 * bit + y * (1 - bit) // y = 3 if bit == 1 otherwise no change z = z * bit + 7 * (1 - bit) // z = 7 if bit == 0 otherwise no change
Em uma segunda passagem, o desenvolvedor precisa propagar o fato de que x
é do tipo criptografado nas linhas subsequentes:
bit = fhe_is_equal(x, 0) // bit, y_new and z_new are encrypted y_new = fhe_add(fhe_mul(3, bit), fhe_mul(y, fhe_sub(1, bit))) z_new = fhe_add(fhe_mul(z, bit), fhe_mul(7, fhe_sub(1, bit)))
Pode-se verificar se isso é funcionalmente equivalente à intenção inicial do desenvolvedor e pronto.
Se a linguagem de programação permitir que os operadores nativos sejam sobrecarregados, a API FHE pode até tornar supérfluas as funções explícitas do FHE do segundo trecho, de modo que a primeira reescrita seja a única coisa que o desenvolvedor precisa fazer.
Para dar uma dose extra de açúcar sintático ao desenvolvedor, você pode até expor um operador ternário sobrecarregado a? b : c
em que qualquer argumento pode ser criptografado ou não. O trecho de código se torna ainda mais simples:
bit = (x == 0) // bit = 1 if x == 0 otherwise 0 y_new = bit? 3: y // y = 3 if bit == 1 otherwise no change z_new = bit? z: 7 // z = 7 if bit == 0 otherwise no change
Isso generaliza facilmente para instruções if/switch
arbitrárias: toda vez que há uma condição criptografada para verificar, o desenvolvedor só precisa usar a multiplexação para fundir os vários corpos da instrução em um único bloco de código linear equivalente.
Agora, construções de loop envolvendo condições criptografadas podem ser regularizadas com o mesmo espírito. Tomemos por exemplo
for (i = 0; i < x; i++) do <body> // i is plain, x is encrypted
onde x
deve ser do tipo criptografado. Primeiro, decomponha isso em uma instrução for
simples e uma instrução if
irregular:
for (i = 0; i < known_max_value_of_x; i++) do if (i < x) then <body> // i is plain, x is encrypted
e então regularize a instrução if
como antes:
for (i = 0; i < known_max_value_of_x; i++) do bit = (i < x) // i is plain, x and bit are encrypted <new_body> // new body regularized using bit? _ : _
Observe que isso requer um valor known_max_value_of_x
. O valor máximo suportado pelo tipo criptografado de x
pode ser usado por padrão, mas em muitos casos o desenvolvedor conhece um limite superior muito melhor em x
que permite reduzir a contagem total de loops a um mínimo estrito.
No final das contas, as transformações acima são facilmente generalizadas em um método sistemático para regularizar o fluxo de controle irregular, que é fácil para os codificadores assimilarem e adicionarem aos seus hábitos de codificação.
O fhEVM da Zama é uma estrutura de código aberto completa para o desenvolvimento e implantação de contratos inteligentes confidenciais na Máquina Virtual Ethereum (EVM). Os contratos fhEVM são contratos simples do Solidity que são construídos usando conjuntos de ferramentas tradicionais do Solidity. A experiência de desenvolvimento familiar é aumentada pelo FHE pela biblioteca TFHE.sol
, que fornece tipos de dados criptografados e substituições FHE para funções padrão.
Os tipos de dados criptografados suportados são atualmente
ebool, euint4, euint8, euint16, euint32, euint64, eaddress
e inteiros assinados criptografados também serão incluídos em breve. Variáveis criptografadas são criadas a partir de textos cifrados de entrada brutos usando construtores dedicados:
function mint(bytes calldata encryptedAmount) public onlyContractOwner { euint64 amount = TFHE.asEuint64(encryptedAmount); balances[contractOwner] = balances[contractOwner] + amount; totalSupply = totalSupply + amount; }
Os operadores nativos do Solidity +, -, *, &, |, ^, etc
estão sobrecarregados para conveniência do desenvolvedor, e os operadores de comparação fornecidos eq, ne, gt, lt, ge, le
retornam um booleano criptografado ebool
. O operador ternário homomórfico _? _ : _
é chamado select
:
function bid(bytes calldata encryptedBid) internal { euint32 bid = TFHE.asEuint32(encryptedBid); ebool isAbove = TFHE.le(bid, highestBid); // Replace highest bid highestBid = TFHE.select(isAbove, bid, highestBid); }
No lado do tempo de execução, o fhEVM fornece um EVM habilitado para TFHE onde funções homomórficas são expostas como contratos pré-compilados, o que resulta da integração da biblioteca TFHE-rs
Rust de código aberto.
Consulte o white paper do fhEVM para saber mais sobre isso.
Lembra-se de como são os programas TFHE executáveis, redes TFHE parametrizadas montadas por lógica simples? Bem, você só precisa mapear a lógica do software do desenvolvedor para isso.
O primeiro requisito é ter certeza de que a lógica do programa é “simples”. Isso é exatamente o que ensinamos o desenvolvedor a produzir sozinho, regularizando suas instruções de fluxo de controle. Então, estamos realmente bem nisso agora.
O segundo requisito é que todas as funções homomórficas chamadas pelo programa sejam mapeadas para redes TFHE parametrizadas pré-estabelecidas. Isso é mais complexo do que parece por vários motivos.
Pré-estabelecer uma rede TFHE parametrizada que implemente uma determinada função não é necessariamente trivial.
Apenas construir uma adição homomórfica de 2 inteiros não assinados criptografados de 64 bits leva você a muitas opções técnicas: como representar as entradas de 64 bits como vetores de inteiros modulares? Com que módulo (ou múltiplos módulos) exatamente? E então como você realiza um circuito de adição de 64 bits com camadas de pesquisas de tabela?
Muitas opções lá. Mas você acabará se decidindo por meio de uma boa engenharia e conduzindo muitos experimentos.
Supondo que você implementou redes TFHE para todas as funções da API, você quer ter certeza de que elas podem ser compostas à vontade, como blocos de Lego.
Isto não é necessariamente garantido porque a melhor forma de representar o mesmo tipo de dados criptografados pode diferir de uma função para outra. Portanto, você precisa adotar um formato aritmético comum para representar cada tipo de dados criptografados, sem incorrer em muita degradação na eficiência de suas redes TFHE.
Novamente, há muitas opções e você precisará arbitrar entre elas.
Supondo que todas as redes TFHE sejam agora totalmente compatíveis nos seus formatos de entrada/saída, a capacidade de composição ainda não pode ser garantida.
Isso ocorre porque os parâmetros criptográficos que instanciam uma rede TFHE podem não ser compatíveis com aqueles usados para instanciar outra. Mais particularmente, o nível de ruído de criptografia nos textos cifrados de entrada e saída deve ser ajustado para determinar a capacidade real de composição.
Isso é semelhante à impedância em circuitos eletrônicos; não há como conectar um circuito a outro se houver uma incompatibilidade na impedância. Você precisa primeiro alinhar seus níveis de impedância, e é a mesma coisa aqui. A maneira mais fácil de fazer isso é usar conjuntos fixos de parâmetros - talvez apenas um conjunto único - que são ajustados para garantir o alinhamento em todas as funções da API. Posteriormente, será corrigido o formato das chaves públicas dos usuários, bem como os parâmetros utilizados na criptografia e descriptografia dos usuários, quaisquer que sejam os códigos do desenvolvedor.
Se você encontrar uma maneira de satisfazer esses 3 requisitos ao criar e parametrizar suas redes TFHE, e ainda assim obter um bom desempenho geral, parabéns! Você conseguiu.
Eles são bons o suficiente para programação de uso geral, assumindo novamente que a API FHE é abrangente o suficiente para fornecer substituições homomórficas para todas as funções padrão que o desenvolvedor espera, com total capacidade de composição.
Mas podem não ser bons o suficiente para programas especializados em
funções grandes e com uso intensivo de computação, como no aprendizado de máquina,
funções personalizadas e não padrão.
É aí que entra a compilação homomórfica.
O caminho difícil começa aqui: para vencer o jogo fora da programação de uso geral, você agora entregará um compilador TFHE.
O compilador cuidará do que o desenvolvedor não tem ideia de fazer sozinho. Ele será alimentado pela contribuição do desenvolvedor - seja qual for a sua decisão - e terá que completar as partes que faltam para chegar a um programa TFHE.
Vejamos exemplos típicos de aplicativos não padronizados.
Ao transformar uma rede neural simples em um equivalente homomórfico, o desenvolvedor construirá e implantará um serviço de inferência homomórfica em que as entradas e saídas do usuário são criptografadas de ponta a ponta.
Supõe-se que o desenvolvedor conheça o aprendizado de máquina bem o suficiente para produzir um modelo quantizado treinado ou já possua um.
As especificidades de como a quantização é feita realmente importam aqui, já que seu compilador exigirá que o modelo seja basicamente uma rede TFHE - ou que seja facilmente acessível a uma através de uma simples reescrita. Sabe-se que as técnicas de código aberto disponíveis suportam essa forma de quantização, seja pela pós-quantização de um modelo pré-treinado, ou preferencialmente pela realização de Quantization-Aware Training (QAT), que atinge níveis de precisão de última geração em comparação com para modelos não quantizados treinados no mesmo conjunto de dados.
Essencialmente, os módulos usados nas camadas da rede TFHE são potências variáveis de 2, de modo que a precisão dos sinais de ativação é medida em bits. Os pesos são inteiros assinados e as próprias funções de ativação são quantizadas e instanciadas como pesquisas de tabela. Quando as ativações tabulam uma função de sinal rígido deslocada com um deslocamento aprendido, esta definição abrange tipos de modelo como BNNs , TNNs e suas generalizações de vários bits. Mas, em princípio, é possível usar tabelas de pesquisa arbitrárias em funções de ativação e, portanto, elas podem até ser aprendidas durante o treinamento para obter melhores precisões.
O que o desenvolvedor não sabe fazer é converter sua rede TFHE em um executável homomórfico. Portanto, o único ingrediente que falta aqui é apenas uma parametrização criptográfica dessa rede, e isso é tudo que o seu compilador terá que fazer antes de prosseguir para o estágio de compilação back-end.
Lembre-se que a parametrização de uma rede TFHE fornece uma instanciação criptográfica que pode ser executada. Ele também controla todas as métricas relativas a essa execução, como o tamanho total das chaves públicas do usuário e o tempo total de execução. Portanto, a parametrização é de importância crítica aqui, pois o desenvolvedor procura reduzir a latência de inferência a um mínimo absoluto.
Existem muitos graus de liberdade nos parâmetros criptográficos de uma rede TFHE para forçar todos eles. Além disso, as métricas a serem otimizadas dependem de 2 componentes que são totalmente externos à rede e dependem dos algoritmos que o tempo de execução utiliza para realizar operações TFHE de baixo nível:
Uma coleção de fórmulas de ruído . Uma fórmula de ruído relaciona as distribuições de entrada e saída do ruído de criptografia nos pontos finais de um operador, usando os parâmetros do operador como variáveis desconhecidas. Estabelecê-los requer análise científica humana e validação experimental.
Uma coleção de métricas de custo . As métricas de custo prevêem a eficiência multidimensional (uso de memória, tempo de execução, etc.) de um operador em função de seus parâmetros. Normalmente são inferidos a partir de medições de referência através de uma análise de melhor ajuste.
Qualquer alteração na implementação do tempo de execução deve ser refletida nestes 2 módulos, pois ambos são fortemente dependentes de algoritmo e de hardware.
As fórmulas de ruído e o modelo de custo do tempo de execução, juntamente com a rede TFHE fornecida, formulam uma instância específica de toda uma classe de problemas de otimização e esta instância é entregue a um otimizador dedicado. Estamos falando de programação não linear inteira mista com múltiplos objetivos aqui, portanto, descobrir uma frente de Pareto de conjuntos ideais de parâmetros requer a solução dessa classe não trivial de problemas de otimização.
A boa ciência e engenharia levaram à resolução deste tipo de problemas de otimização em questão de segundos, e compiladores TFHE como o Concrete já apresentam um eficiente otimizador de parâmetros TFHE como módulo interno.
Várias melhorias podem tornar os otimizadores TFHE ainda mais rápidos no futuro, mas mesmo sem elas, pode-se considerar a parametrização das redes TFHE como - praticamente - um negócio fechado.
Esses aplicativos são de um tipo totalmente diferente. O desenvolvedor mantém a especificação matemática de uma função personalizada a ser implementada, juntamente com uma restrição de precisão. Tomemos por exemplo
onde x
é um número real entre 0 e 1, e usar uma aproximação G
de F
é aceitável, desde que
O desenvolvedor sabe que implementar G
no verso de um envelope, compondo funções de API padrão, provavelmente será muito abaixo do ideal.
O que seu compilador fará é fabricar - dinamicamente - uma nova rede TFHE projetada especificamente para satisfazer a especificação de G
. Em seguida, ele irá parametrizá-lo e prosseguir com a compilação back-end para produzir o aplicativo homomórfico.
Bem, é aí que a estrada de repente se torna muito mais acidentada.
Actualmente, existe uma falta de conhecimento científico sobre como as redes TFHE, com a definição exacta que afirmei anteriormente, podem ser sintetizadas automaticamente. Mesmo as pesquisas sobre a síntese de tipos adjacentes de circuitos que também dependem da aritmética modular de valores inteiros são, na melhor das hipóteses, escassas. Sem falar em qualquer sintetizador pré-existente capaz de realizar esta tarefa de A a Z.
Uma maneira de resolver o problema é explorar uma fração do poder computacional das redes TFHE, reduzindo-as a circuitos booleanos. Por exemplo, um neurônio TFHE pode ser forçado a atuar como porta booleana ternária
por
x_1, x_2, x_3
como valores 0/1,m = 4
e seus pesos para (w_1, w_2, w_3) = (2, -1, 1)
,[0, 1, 1, 0]
.
Forçando brutalmente todos os pesos e tabelas possíveis com o mesmo módulo, pode-se então constituir um dicionário de neurônios TFHE que pode servir para calcular portas ternárias. O próximo passo consiste em
Esta é apenas uma estratégia ilustrativa entre muitas porque os métodos que dependem de circuitos booleanos são abundantes. Você também pode considerar apenas as portas binárias usuais e implementá-las com neurônios TFHE restritos. O Cingulata da CEA e - mais tarde - o transpiler FHE do Google foram precisamente os pioneiros nesse caminho com o TFHE.
A síntese booleana faz uso de técnicas agressivas de otimização de circuitos e, em geral, é um problema técnico resolvido - ou quase isso, tornando essa abordagem sólida e prática para quem constrói o compilador.
No entanto, uma vez que uma rede TFHE é gerada dessa forma, a sua largura e profundidade podem acabar sendo anormalmente altas, levando a um desempenho geral insatisfatório. Portanto, há uma suspeita generalizada de que, ao relaxar o condicionamento booleano - totalmente artificial - dos neurônios TFHE, pode-se aproveitar toda a sua expressividade e obter redes muito menores e mais superficiais.
Mas, novamente, são necessárias mais pesquisas para identificar claramente como fazer isso. É possível que abordagens totalmente diferentes, como aprender a rede TFHE com algum método de treinamento apropriado emprestado do aprendizado de máquina, acabem fornecendo resultados superiores. O tempo vai dizer.
Supondo que nosso problema de síntese seja resolvido e produza redes TFHE personalizadas eficientes, você estará pronto para juntar todas as peças móveis e projetar um compilador que faça tudo isso:
Seria necessário como entrada um programa simples, onde variáveis sensíveis são simplesmente anotadas como criptografadas.
Aceitaria redes neurais pré-treinadas ou outros modelos de aprendizado de máquina e os reinterpretaria como redes TFHE.
Ele aceitaria modelos de funções feitos apenas de uma especificação matemática e realizaria síntese em tempo real para gerar redes TFHE personalizadas.
Isso transformaria todas as redes TFHE não parametrizadas deixadas na unidade de compilação em instâncias executáveis usando um módulo otimizador sempre que necessário.
Ele executaria o estágio de back-end da compilação para uma variedade de tempos de execução TFHE alvo e/ou arquiteturas de hardware.
Aproveitaria aceleradores de hardware específicos (através do seu modelo de custo) para permitir a síntese e parametrização de redes TFHE mais rápidas.
Inferno, você também pode incluir algum suporte para uma regularização automatizada do fluxo de controle, para que o desenvolvedor nem precise mais se preocupar com isso.
Isso ofereceria aos criadores de aplicativos FHE a melhor experiência de desenvolvimento.
No contexto da programação FHE de uso geral, uma biblioteca TFHE pode fornecer modularidade e uma experiência de desenvolvimento totalmente autônoma com conjuntos de ferramentas pré-existentes.
A TFHE é pioneira em técnicas de compilação específicas que podem satisfazer as necessidades do desenvolvedor além desse ponto, mais particularmente para inferência de aprendizado de máquina criptografada e, nos próximos anos, para processamento de dados criptografados em alta velocidade e outras aplicações FHE personalizadas.
No geral, o TFHE fornece um caminho tecnológico claro para criar cadeias de ferramentas FHE mais integradas e adaptáveis que podem se tornar grandes no mundo do desenvolvimento de software e gerar uma nova onda de soluções que priorizam a privacidade, que qualquer um pode construir e executar com uma facilidade sem precedentes.
Ao focar apenas nas redes de pesquisa TFHE, acabei de usar um modelo computacional entre vários que o TFHE pode suportar. À medida que a investigação revela progressivamente mais das suas capacidades, não há dúvida de que novas formas de instrumentar o TFHE surgirão à superfície.
Quais e quando é outra história. Mas por trás dessa história esconde-se uma série de outras questões interessantes e potencialmente esclarecedoras sobre o futuro da computação confidencial.